WEBVTT

00:00.000 --> 00:10.480
OK, let's get started.

00:10.480 --> 00:14.240
Building a minimal terminal UI library for language

00:14.240 --> 00:17.240
Lua, but it's close platform.

00:17.240 --> 00:20.800
So it probably would, if anyone wants to build something

00:20.800 --> 00:26.280
like this, might be interesting to see what we ran into.

00:26.280 --> 00:28.600
Small shout out to LabLua and G-Sup for helping

00:28.600 --> 00:34.720
us out funding the project, which really helped us last summer.

00:34.720 --> 00:37.400
In the set for a sake of time, let's skip on that.

00:37.400 --> 00:40.680
I can show it demo of what it does today.

00:47.160 --> 00:52.520
So this is Lua Rocks, which is the CLI interface for a package

00:52.520 --> 00:56.360
manager, and we simply use it as an example.

00:56.360 --> 01:01.720
Like, can we wrap it in something useful in a text-based UI?

01:01.720 --> 01:05.800
So the first thing we did is we created some widgets on the command line.

01:05.800 --> 01:07.640
So it's at the command line becomes interactive.

01:07.640 --> 01:15.640
So it can actually walk around down execute commands and exit in this case.

01:15.640 --> 01:21.320
The second example we did is full screen application.

01:21.320 --> 01:27.560
Well, we have panels, we have a header bar, a keyboard shortcuts,

01:27.560 --> 01:31.400
we can move around, we can hike panels, show panels.

01:31.400 --> 01:34.280
So this is the current state of what it does.

01:38.840 --> 01:41.720
In fact, the presentation.

01:41.720 --> 01:45.560
So the goals, cross-platform, it had to work.

01:45.560 --> 01:49.640
The idea is mainly from when you start working with Lua,

01:49.640 --> 01:52.120
Lua's source distributed, which is hard on windows.

01:52.120 --> 01:56.520
So can we have a novice user built something of just

01:56.520 --> 01:57.640
works across the board?

01:57.640 --> 02:00.920
Just a basic application to get people started.

02:01.960 --> 02:06.360
Non-blocking input, Lua uses cover-teens.

02:06.360 --> 02:09.800
So non-blocking input is something that we really wanted.

02:11.160 --> 02:16.760
And no external dependencies to make installation easier.

02:16.760 --> 02:22.920
Non-bills explicitly, no mouse support, no overlapping windows and this kind of stuff.

02:22.920 --> 02:24.760
So what does it look like?

02:24.760 --> 02:25.880
It's the operating system.

02:25.880 --> 02:26.920
And there's two libraries.

02:26.920 --> 02:29.320
The first one is Lua's system, which is only providing

02:29.320 --> 02:32.040
C code with interfaces to the operating system.

02:32.040 --> 02:35.320
And the second one is the one which has far more code,

02:35.320 --> 02:38.360
that actually does all the drawing and everything else.

02:38.360 --> 02:40.840
But the good thing is that you're already in the target language.

02:40.840 --> 02:43.560
So you can write everything in the target language.

02:43.640 --> 02:47.240
We wanted Lua's system to be independent of platform,

02:47.240 --> 02:49.320
but because terminals are so fundamentally different

02:49.320 --> 02:52.200
on both systems and physics and windows,

02:52.200 --> 02:55.800
some details do leak through the terminal that Lua library.

02:58.680 --> 02:59.720
The easy part.

02:59.720 --> 03:02.280
To get this done, I'll see support.

03:02.280 --> 03:03.960
Windows gained it in 2019.

03:05.480 --> 03:08.120
That was like the big thing that made it viable,

03:08.120 --> 03:11.560
otherwise it just wouldn't have worked in my opinion.

03:11.880 --> 03:14.120
And getting a terminal size, this is a single API call,

03:14.120 --> 03:15.560
it's an old platforms.

03:15.560 --> 03:16.920
Everything else was a bit harder.

03:18.760 --> 03:23.400
The biggest challenge by far was the ambiguous with,

03:23.400 --> 03:25.880
display with, or display with in general.

03:25.880 --> 03:28.920
Windows does not have for UGFA or Unicode characters.

03:29.720 --> 03:32.200
Like emojis, which are two columns wide on the text screen.

03:33.080 --> 03:35.480
Chinese, East Asian languages, you have a lot of

03:35.480 --> 03:37.720
characters, two columns wide.

03:37.720 --> 03:40.120
And for everything you draw on screen,

03:40.200 --> 03:42.680
you just don't want stuff to mess up your,

03:42.680 --> 03:44.200
display your construction, whatever you have.

03:46.760 --> 03:49.000
On Windows was fairly easy to do the output,

03:49.000 --> 03:51.800
but on Windows the hard part was like doing the input proper.

03:55.240 --> 03:58.120
So to handle this, we had to implement,

03:58.120 --> 04:01.720
use the by market school and implementation

04:01.720 --> 04:02.760
that wasn't available on Windows.

04:04.360 --> 04:06.760
And for the width checking, we had to

04:06.760 --> 04:09.720
create functions for character and strings, which is,

04:09.720 --> 04:10.760
like a very simple.

04:10.760 --> 04:13.720
And we had to implement a string.sub, which is the

04:13.720 --> 04:16.440
lower method for string mangling, cutting

04:16.440 --> 04:17.800
and pieces and rearranging.

04:18.840 --> 04:21.640
And we had to write two, one for UGFA characters,

04:21.640 --> 04:23.240
because it was a bit based.

04:24.440 --> 04:26.840
And the second one is the same thing,

04:26.840 --> 04:28.920
but then based on display columns.

04:28.920 --> 04:31.480
Because everything you do in your UI is basically based on the columns,

04:31.480 --> 04:33.880
you display and not on bytes or individual characters.

04:34.840 --> 04:36.840
This was the hard part.

04:36.840 --> 04:38.760
On top of that, we built,

04:38.760 --> 04:42.760
it goes input is one by by at a time from the keyboard.

04:44.600 --> 04:47.000
And it meant that we had to rebuild a line editor.

04:48.120 --> 04:52.680
We built this thing and it turned out this thing actually became like,

04:52.680 --> 04:56.200
the core of all text handling for everything we displayed,

04:56.200 --> 05:00.920
because it has these methods to check with columns everything.

05:01.880 --> 05:06.360
And so this thing, we wrote as a line editor,

05:06.360 --> 05:09.000
edges is all over the place in everything we do.

05:11.640 --> 05:14.440
The next picture challenge was non-blocking input.

05:14.440 --> 05:16.360
On post-6 it's fairly standard.

05:16.360 --> 05:18.600
You said it's a non-blocking, you read one by at the time,

05:18.600 --> 05:19.960
canonical mode and the whole shabang.

05:21.240 --> 05:23.960
When I wind those, there just is no such thing.

05:24.600 --> 05:28.440
So we fell back on the very old conno.h,

05:28.600 --> 05:31.240
which is like, I think somewhere from the 80s,

05:31.240 --> 05:33.000
but it's still available, it still works everywhere.

05:34.120 --> 05:36.040
And they can read directly from the keyboard buffer.

05:38.040 --> 05:40.840
Which means that on post-6, we can just read the single byte and on Windows,

05:40.840 --> 05:42.200
we have to go through a bunch of hoops.

05:43.960 --> 05:46.040
The main issue is that on Windows,

05:46.760 --> 05:49.400
you read a white character, you want to output a single byte.

05:50.680 --> 05:54.520
So we need to implement a buffer to store whatever the remainder is of the character that we're reading.

05:55.480 --> 05:58.680
And there's a bit of a thing with Windows that

05:58.680 --> 06:02.760
occasionally leaks, keyboard scan codes, raw codes,

06:02.760 --> 06:05.480
through the interface, you want to ask me why.

06:05.480 --> 06:08.200
Turn more scan, keep up, and you have to catch that.

06:08.200 --> 06:11.640
But that's one general way to go up against, it's easy to fix,

06:11.640 --> 06:14.440
but figuring out why the weird bytes are just coming in,

06:14.440 --> 06:15.320
that's the hard part.

06:18.520 --> 06:21.640
In the tellerization, this is a bit tricky on Windows,

06:21.800 --> 06:24.680
over in post-6 actually on Windows, it was pretty easy.

06:24.680 --> 06:30.280
But on post-6, it's the usual canonical echo and setting it to a non-blocking.

06:31.720 --> 06:36.600
But what turned out on, especially on Macs,

06:36.600 --> 06:40.680
it seems that if you set standard in to non-blocking,

06:40.680 --> 06:43.240
you're also setting standard out to non-blocking,

06:43.240 --> 06:44.680
and that's something you don't want.

06:44.680 --> 06:47.240
And the reason is that you have a file descriptor,

06:47.880 --> 06:50.440
but they can point to the same file description.

06:50.520 --> 06:53.880
If you set it to non-blocking, you modify the file description,

06:54.520 --> 06:56.520
and then the other one that points to the same description,

06:56.520 --> 06:57.480
also becomes non-blocking.

06:58.200 --> 07:01.000
So that's where we had another one of those,

07:01.000 --> 07:02.040
what the heck is happening here.

07:03.080 --> 07:07.400
Once you know, it's easy to fix, you make sure that each file descriptor

07:07.400 --> 07:10.200
gets its own file description, and then they're independent,

07:10.200 --> 07:11.160
then you can do whatever you want.

07:14.520 --> 07:18.280
So if you have individual bytes for keyboard reading coming in,

07:18.440 --> 07:21.800
then the first thing we do on the lowest level,

07:22.680 --> 07:24.200
like the three functions you see here,

07:24.200 --> 07:26.360
the top one is the C implementation that you showed.

07:27.400 --> 07:30.200
The Riki is actually the one where we immediately

07:30.200 --> 07:32.520
power this in a timeout and a sleep function.

07:34.680 --> 07:38.280
The reason we do it is because if we change the underlying

07:38.280 --> 07:39.640
implementation of the sleep function,

07:39.640 --> 07:42.520
to something non-blocking, if you use a coroutine scheduler,

07:42.520 --> 07:46.280
or whatever you have in the language that you're using

07:46.360 --> 07:49.000
for a lure that is co-routines,

07:51.000 --> 07:54.120
then it means that everything beyond it just becomes non-blocking.

07:55.640 --> 07:58.600
And the last function is actually the one that does

07:58.600 --> 08:01.640
all the extraction for parsing-answer codes and everything else.

08:02.680 --> 08:07.640
To give you a quick idea of what it looks like.

08:07.640 --> 08:09.880
So if you have a simple prompt in a terminal,

08:10.440 --> 08:12.840
you can see the double with characters,

08:12.920 --> 08:15.240
like the two Chinese characters of the ball with,

08:15.240 --> 08:17.800
as is the rocket.

08:20.280 --> 08:27.800
You can, well, you can copy paste some stuff, just works.

08:29.320 --> 08:33.000
But if you place the sleep method that we have in the input,

08:35.800 --> 08:38.120
then this is the second example, exact same code.

08:38.760 --> 08:41.880
Other than we replace the sleep function with something

08:41.880 --> 08:43.640
that deals with an algorithm in the top right and corner,

08:43.640 --> 08:45.400
we have a clock ticking, and despite it,

08:45.400 --> 08:49.480
I'm usually, if I would use the standard C functions from the C library

08:49.480 --> 08:51.320
to read input, it would be blocking.

08:51.320 --> 08:54.440
But in this case, we can do background work, timers, whatever.

08:59.880 --> 09:01.560
So from there,

09:03.960 --> 09:07.080
another thing to never ran into was spurring a slow initially,

09:07.080 --> 09:10.040
we tried, when we want to, if you draw some kind of skin,

09:10.120 --> 09:13.320
screen, you want to reset the state to what it was before you started drawing.

09:14.360 --> 09:17.640
So you can create a terminal for foreground colors, background colors,

09:17.640 --> 09:20.040
cursor position, but it turns out to be really slow.

09:21.080 --> 09:25.640
The reason it's slow is that when you write a sequence to the terminal,

09:26.680 --> 09:29.240
immediately read, the answer won't be there yet.

09:30.040 --> 09:32.440
Terminal needs processing time, so you have to do a sleep,

09:32.840 --> 09:37.000
and then you run into the underlying decision of the operating system,

09:37.640 --> 09:41.160
and a typically is anywhere between two and 15 milliseconds.

09:41.160 --> 09:44.440
So if you do it once on drawing a screen, not a problem.

09:44.440 --> 09:46.200
Typically, you create widgets.

09:46.200 --> 09:49.000
Every widget does the same thing like getting state,

09:49.000 --> 09:49.960
we store it afterwards.

09:50.920 --> 09:57.000
And if you do that, then it quickly adds up to 150 milliseconds,

09:57.000 --> 09:59.000
and then you will start noticing it and it comes sluggish.

10:00.280 --> 10:02.920
The way we work around this is actually by tracking state,

10:03.160 --> 10:07.240
to create its stacks in which we push the state,

10:07.240 --> 10:10.280
and then pop the state without actually querying the terminal.

10:10.280 --> 10:12.120
And that makes it really snappy.

10:13.400 --> 10:17.160
I use a number of items, the cursor shape, visibility,

10:17.160 --> 10:19.880
scroll region, text attributes that we create the stacks for.

10:20.600 --> 10:22.600
And they look pretty simple like this.

10:23.880 --> 10:27.880
You push in the red foreground color, and it's at brightness,

10:27.880 --> 10:29.400
everything else stays the same.

10:30.280 --> 10:32.120
You write a text, does it?

10:32.120 --> 10:34.680
Then you pop it, and then you restore it to the previous.

10:34.680 --> 10:37.800
Whatever the color was, there was on the previous stack slot, basically.

10:39.000 --> 10:41.560
This worked really nicely for all of those things

10:41.560 --> 10:43.400
that we otherwise would have queried the terminal for.

10:46.760 --> 10:50.520
API usage, if you look at everything in the end,

10:50.520 --> 10:52.120
I was actually surprised that it was so little.

10:52.760 --> 10:55.800
I would have expected to be more API calls for the underlying operating system

10:56.440 --> 11:00.360
that we would need than the end it was fairly straightforward.

11:03.400 --> 11:07.160
And then concluding, you look at the whole thing,

11:07.160 --> 11:10.040
I found in the end after Google Summer Code Project,

11:10.040 --> 11:13.640
the thing we achieved was actually way better than I expected,

11:13.640 --> 11:17.400
considering how snappy it was that we had a keyboard,

11:17.400 --> 11:22.440
a non-blocking handling, which has been a painful thing for a long time,

11:22.440 --> 11:23.640
at least in the lower language.

11:26.200 --> 11:28.840
But the main things here are the keyboard reading,

11:28.840 --> 11:31.800
and to make it independent over the platforms,

11:32.520 --> 11:35.400
and to display with that is really a painful thing to handle.

11:35.800 --> 11:37.880
Like for every single string you handle,

11:37.880 --> 11:40.840
you need to check with display columns, full summing.

11:43.080 --> 11:43.480
That's it.

11:45.240 --> 11:45.720
Questions?

11:46.360 --> 11:48.360
Thank you.

11:52.360 --> 11:53.640
Okay, the next speaker will come up.

11:55.640 --> 11:58.840
And also when people come in, try to focus a bit more.

12:00.840 --> 12:01.560
Any questions?

12:01.560 --> 12:01.800
Yeah.

12:06.440 --> 12:07.240
Sorry, can you say?

12:07.240 --> 12:10.040
It's a client, a product client.

12:11.800 --> 12:14.360
We could leave, basically it's an object,

12:14.520 --> 12:16.760
and it has methods for key handling.

12:16.760 --> 12:20.200
So you can inject FI modes if you want to.

12:21.320 --> 12:24.920
And I think, frankly, I don't use FI, so I don't know the shortcuts,

12:26.200 --> 12:28.840
but one of the entities that worked on it from the Google Summer Code,

12:28.840 --> 12:32.200
actually used, not sure it was FI, or maybe another,

12:32.200 --> 12:36.040
the competitor thing, but it should be possible.

12:37.640 --> 12:38.360
And you're going to see?

12:39.000 --> 12:40.120
Did you have any guidance?

12:40.120 --> 12:41.240
Just a small amount of images?

12:41.240 --> 12:43.080
And then you can see some of the colors.

12:43.080 --> 12:47.640
No, no, maybe next year Google Summer Code.

12:51.480 --> 12:55.400
I mean, it was mostly considering the platform in the penance.

12:56.040 --> 12:57.880
That was like the media thing to achieve.

12:57.880 --> 13:01.880
So we can have another build something that just works across the board.

13:01.880 --> 13:02.920
Something useful.

13:02.920 --> 13:03.800
That was the idea.

13:03.800 --> 13:08.040
And then the images, and etc, nice to have, but in a later stage.

13:09.320 --> 13:12.920
Yeah, that is the typical thing.

13:16.680 --> 13:17.240
Anything else?

13:21.000 --> 13:21.960
So, thank you very much.

