summaryrefslogtreecommitdiffstats
path: root/2021/captions/emacsconf-2021-ui--yak-shaving-to-a-ui-framework-help-i-accidentally-yak-shaved-my-way-to-writing-a-ui-framework-because-overlays-were-slow--erik-anderson--main.vtt
blob: 7ee41c3a2cb6f269eba35c1bff47e953802f1bd7 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
WEBVTT

00:07.600 --> 00:00:09.120
Hi! I'm Erik Anderson,

00:00:09.120 --> 00:00:10.559
and I'll be talking about tui,

00:00:10.559 --> 00:00:11.840
a user interface framework

00:11.840 --> 00:00:15.040
that I've written in Emacs Lisp.

00:15.040 --> 00:00:16.196
First, I want to talk a bit about

00:00:16.196 --> 00:00:17.296
the problem space of

00:00:17.296 --> 00:00:18.728
user interface development,

00:00:18.728 --> 00:00:20.880
specifically, I want to quickly illustrate

00:00:20.880 --> 00:00:22.320
some of the complexities involved

00:00:22.320 --> 00:00:26.480
with UI implementation in Emacs.

00:26.480 --> 00:00:27.920
In Emacs, we have the ubiquitous 

00:00:27.920 --> 00:00:29.920
buffer object type that forms the container

00:00:29.920 --> 00:00:31.920
for most content in Emacs.

00:00:31.920 --> 00:00:34.079
Most interfaces we interact with

00:00:34.079 --> 00:00:36.239
consist of character based content

00:00:36.239 --> 00:00:38.239
in a buffer that's presented

00:00:38.239 --> 00:00:40.079
in a window in frame.

00:40.079 --> 00:00:41.520
Although the underlying content

00:00:41.520 --> 00:00:44.320
may be textual, Emacs has capable APIs 

00:00:44.320 --> 00:00:47.680
to present rich content.

00:47.680 --> 00:00:49.200
The pervasiveness of buffers

00:00:49.200 --> 00:00:50.879
affords us wonderful flexibility.

00:00:50.879 --> 00:00:52.559
This presentation, for instance,

00:00:52.559 --> 00:00:55.520
is running in an Emacs buffer.

00:55.520 --> 00:00:57.420
Using Emacs's built-in basic

00:00:57.420 --> 00:00:59.199
button library, we can insert

00:00:59.199 --> 00:01:00.884
an interactive button

00:01:00.884 --> 00:01:01.760
that shows a message 

00:01:01.760 --> 00:01:06.080
in the minibuffer when clicked.

01:06.080 --> 00:01:09.200
What about UIs that express application state?

01:09.200 --> 00:01:11.439
Most applications don't have a static UI.

01:11.439 --> 00:01:13.280
As application state changes,

00:01:13.280 --> 00:01:14.320
the UI should change

00:01:14.320 --> 00:01:18.479
to display the desired content.

01:18.479 --> 00:01:20.320
One simplifying strategy is to simply

01:20.320 --> 00:01:23.600
re-render the entire UI upon any change.

01:23.600 --> 00:01:25.680
First erase the contents of the buffer,

01:25.680 --> 00:01:27.600
and then reinsert your UI again

00:01:27.600 --> 00:01:29.040
with desired changes,

00:01:29.040 --> 00:01:33.040
and restore things like point and region.

01:33.040 --> 00:01:34.560
Basic composition is possible

00:01:34.560 --> 00:01:35.759
with this approach.

00:01:35.759 --> 00:01:37.200
Simply insert the elements

00:01:37.200 --> 00:01:39.280
of the UI in sequence.

01:39.280 --> 00:01:40.640
Complex elements can be

00:01:40.640 --> 00:01:44.320
composed of multiple sub-elements.

01:44.320 --> 00:01:45.840
UIs can be made extensible,

00:01:45.840 --> 00:01:47.040
and expose this composition,

00:01:47.040 --> 00:01:49.360
for example, with insertion hooks

00:01:49.360 --> 00:01:52.320
like magit's status sections hook.

01:52.320 --> 00:01:54.159
This generally relies on elements

00:01:54.159 --> 00:01:56.640
being well-behaved inserting themselves,

00:01:56.640 --> 00:02:00.399
not affecting the rest of the buffer.

02:00.399 --> 00:02:02.960
If we find ourselves with complex UIs,

02:02.960 --> 00:02:04.640
large buffers, long lines,

00:02:04.640 --> 00:02:06.320
or poor rendering performance,

00:02:06.320 --> 00:02:09.039
we might consider partial UI updates

00:02:09.039 --> 00:02:11.360
rather than re-rendering completely.

02:11.360 --> 00:02:12.800
In that case, the complexity 

00:02:12.800 --> 00:02:15.840
for maintaining the UI quickly increases.

00:02:15.840 --> 00:02:17.360
As accessible as buffers are,

00:02:17.360 --> 00:02:19.120
we don't have high level abstractions

00:02:19.120 --> 00:02:20.879
for managing portions of a UI

00:02:20.879 --> 00:02:22.160
rendered to a buffer.

00:02:22.160 --> 00:02:23.520
(It) is left up to the programmers

00:02:23.520 --> 00:02:25.440
to track and update UI state.

00:02:25.440 --> 00:02:26.540
This is generally done by

00:02:26.540 --> 00:02:28.959
one of two methods, reflection,

00:02:28.959 --> 00:02:30.800
searching for strings or text properties

00:02:30.800 --> 00:02:34.239
within the buffer, or tracking segments 

00:02:34.239 --> 00:02:36.080
of a UI buffer manually 

00:02:36.080 --> 00:02:38.959
using numeric offsets, or marker, 

00:02:38.959 --> 00:02:45.280
or overlay objects.

02:45.280 --> 00:02:47.280
Here we have a basic timer component

02:47.280 --> 00:02:48.720
that shows elapsed time

00:02:48.720 --> 00:02:50.319
after it's inserted.

00:02:50.319 --> 00:02:52.160
It works, but has several problems.

00:02:52.160 --> 00:02:55.519
It doesn't restore the user's point or mark,

02:55.519 --> 00:02:59.120
so it snaps back after every render,

02:59.120 --> 00:03:01.040
after every update.

03:01.040 --> 00:03:03.360
It relies on singleton global state,

00:03:03.360 --> 00:03:05.124
so isn't designed to coexist

00:03:05.124 --> 00:03:12.000
with other instances of itself.

03:12.000 --> 00:03:13.200
It doesn't use a marker,

00:03:13.200 --> 00:03:14.640
so it's sensitive to content

00:03:14.640 --> 00:03:16.239
proceeding it, that's following it, 

00:03:16.239 --> 00:03:23.519
changing in the buffer.

03:23.519 --> 00:03:25.120
The update logic doesn't even consider

03:25.120 --> 00:03:26.799
which buffer it's trying to update.

00:03:26.799 --> 00:03:27.840
If I switch buffers,

00:03:27.840 --> 00:03:29.360
it will insert into another buffer,

00:03:29.360 --> 00:03:31.519
or even the minibuffer.

03:31.519 --> 00:03:33.360
It can't remove itself, or re-render

00:03:33.360 --> 00:03:34.879
if it gets corrupted, as you see.

00:03:34.879 --> 00:03:35.920
All in all, it's not

00:03:35.920 --> 00:03:38.400
a readily composable component.

03:38.400 --> 00:03:39.519
Addressing these components

00:03:39.519 --> 00:03:41.920
within this logic further increases 

00:03:41.920 --> 00:03:43.936
the implementation complexity

00:03:43.936 --> 00:03:45.680
of this component,

00:03:45.680 --> 00:03:46.640
and still this component

00:03:46.640 --> 00:03:47.280
would likely have

00:03:47.280 --> 00:03:49.120
various subtle differences

03:49.120 --> 00:03:52.480
with other components

00:03:52.480 --> 00:03:58.959
implemented by other authors.

03:58.959 --> 00:04:00.319
For those of you unfamiliar

00:04:00.319 --> 00:04:02.159
with this term Yak Shaving,

00:04:02.159 --> 00:04:04.080
that is a quite technical term

00:04:04.080 --> 00:04:07.599
for any seemingly pointless activity,

00:04:07.599 --> 00:04:09.680
which is actually necessary

00:04:09.680 --> 00:04:10.879
to solve a problem,

04:10.879 --> 00:04:11.920
which solves a problem,

00:04:11.920 --> 00:04:14.799
which, several levels of recursion later,

04:14.799 --> 00:04:18.239
solves the real problem you're working on.

04:18.239 --> 00:04:19.943
The itch that led to this project

00:04:19.943 --> 00:04:21.840
was the desire to display a dense summary 

00:04:21.840 --> 00:04:24.400
of local Git repository statuses.

04:24.400 --> 00:04:26.560
Encountering various implementation

04:26.560 --> 00:04:30.080
complexity for building UI elements,

04:30.080 --> 00:04:31.680
it led to the yak shaving endeavor

00:04:31.680 --> 00:04:33.680
that produced tui.

04:33.680 --> 00:04:35.440
When I wrote the library,

04:35.440 --> 00:04:36.479
I had recently played with 

00:04:36.479 --> 00:04:39.360
a popular UI framework called React,

00:04:39.360 --> 00:04:41.840
and had an interest in learning

00:04:41.840 --> 00:04:44.080
about the internal architecture of React.

00:04:44.080 --> 00:04:45.680
So, rather than implement

00:04:45.680 --> 00:04:46.960
a string caching layer

00:04:46.960 --> 00:04:49.280
on top of tabulated list mode,

00:04:49.280 --> 00:04:50.360
I was rather inclined

00:04:50.360 --> 00:04:52.400
to go down the path of implementing

00:04:52.400 --> 00:04:58.960
the React API for Emacs Lisp.

04:58.960 --> 00:05:00.896
I'll offer a brief view of

00:05:00.896 --> 00:05:05.120
the tui Emacs Lisp API.

05:05.120 --> 00:05:07.360
Inserting component content 

00:05:07.360 --> 00:05:08.320
is pretty straightforward.

00:05:08.320 --> 00:05:10.160
You take a component tree

00:05:10.160 --> 00:05:16.240
and render it in an Emacs buffer.

05:16.240 --> 00:05:18.639
If any elements in that tree are updated,

05:18.639 --> 00:05:21.039
their respective content on the tree

00:05:21.039 --> 00:05:26.320
is updated automatically.

05:26.320 --> 00:05:27.919
Here's a basic re-implementation 

00:05:27.919 --> 00:05:29.919
of the straw man timer from earlier,

00:05:29.919 --> 00:05:32.400
using a macro for syntactic trigger.

05:32.400 --> 00:05:34.404
You'll notice that

00:05:34.404 --> 00:05:36.164
the signature includes its own

00:05:36.164 --> 00:05:44.560
object reference, arguments, and state.

05:44.560 --> 00:05:46.400
Associating arguments in the state

05:46.400 --> 00:05:51.360
with a component instance out of the box,

05:51.360 --> 00:05:54.400
makes it easy to design reusable components,

00:05:54.400 --> 00:06:06.080
and forms the basis for partial UI updates.

06:06.080 --> 00:06:07.840
The component rendering anchors

00:06:07.840 --> 00:06:09.199
are durable, so content can be

00:06:09.199 --> 00:06:11.360
added and removed surrounding content,

00:06:11.360 --> 00:06:12.479
or even within the region

00:06:12.479 --> 00:06:13.280
of the component,

00:06:13.280 --> 00:06:16.319
and replaced when it re-renders.

06:16.319 --> 00:06:17.600
Components will also

00:06:17.600 --> 00:06:20.880
cleanly remove themselves from a buffer

00:06:20.880 --> 00:06:28.400
when instructed to.

06:28.400 --> 00:06:30.160
tui contains the core implementation

00:06:30.160 --> 00:06:33.440
of the React API, so components,

06:33.440 --> 00:06:35.840
their constituent props, state,

00:06:35.840 --> 00:06:38.000
and all of the lifecycle methods

00:06:38.000 --> 00:06:39.440
associated with them,

06:39.440 --> 00:06:41.120
as well as keys, refs,

00:06:41.120 --> 00:06:43.228
and the fundamental

00:06:43.228 --> 00:06:47.520
reconciliation algorithm of React.

06:47.520 --> 00:06:52.188
A variety of other React APIs

00:06:52.188 --> 00:06:58.080
that haven't been implemented yet.

06:58.080 --> 00:07:00.080
It contains some useful features so far,

07:00.080 --> 00:07:02.639
such as hot reloading, reflection,

00:07:02.639 --> 00:07:06.164
and various debugging tools,

00:07:06.164 --> 00:07:12.639
and some reconciliation logging.

07:12.639 --> 00:07:14.880
Lastly, I'd like to give you some quick

07:14.880 --> 00:07:19.039
visual taste of components built with tui.

07:19.039 --> 00:07:20.240
The grid view that motivated

00:07:20.240 --> 00:07:21.520
my development of this package

00:07:21.520 --> 00:07:23.039
is very similar to Magit's

00:07:23.039 --> 00:07:30.720
list repositories functionality.

07:30.720 --> 00:07:36.080
Essentially, tabulated list mode

07:36.080 --> 00:07:39.440
but portable and has a separated model

00:07:39.440 --> 00:07:49.360
and presentation layers.

07:49.360 --> 00:07:51.840
Here's a basic xkcd comic viewer

00:07:51.840 --> 00:08:07.360
showing a couple classics.

08:07.360 --> 00:08:10.080
A long-standing React tutorial is

08:10.080 --> 00:08:13.280
building a tic-tac-toe game

08:13.280 --> 00:08:14.879
as a bit of a gimmick,

08:14.879 --> 00:08:17.039
and I'm not quite satisfied with

00:08:17.039 --> 00:08:25.599
the buffering direction,

00:08:25.599 --> 00:08:27.120
but it got me thinking about

00:08:27.120 --> 00:08:28.639
layout engines with text,

00:08:28.639 --> 00:08:35.120
so it was interesting.

08:35.120 --> 00:08:38.560
And here's a small

00:08:38.560 --> 00:08:46.080
Unicode character viewer

08:46.080 --> 00:08:55.279
capable of showing a bunch of characters.

08:55.279 --> 00:08:57.279
If this piques your interest,

00:08:57.279 --> 00:08:59.200
I would encourage you to check it out.

08:59.200 --> 00:09:01.440
tui should be usable by anyone

00:09:01.440 --> 00:09:04.640
with some basic Elisp familiarity,

09:04.640 --> 00:09:09.680
no prior knowledge about JavaScript

00:09:09.680 --> 00:09:12.080
or React is necessary.

09:12.080 --> 00:09:13.732
I'd absolutely love to talk with people

00:09:13.732 --> 00:09:14.880
about the tui package,

00:09:14.880 --> 00:09:17.440
textual user interfaces in general,

00:09:17.440 --> 00:09:19.040
and really anything in Emacs.

00:09:19.040 --> 00:09:20.693
If you have any ideas, feedback,

00:09:20.693 --> 00:09:21.680
or want to contribute,

00:09:21.680 --> 00:09:23.360
please reach out.

09:23.360 --> 00:09:24.360
Thank you all for listening.

00:09:24.360 --> 00:09:27.000
[captions by bhavin192 (Bhavin Gandhi)]