diff options
author | Thijs Schreijer <thijs@thijsschreijer.nl> | 2024-05-23 20:46:18 +0200 |
---|---|---|
committer | Thijs Schreijer <thijs@thijsschreijer.nl> | 2024-05-23 20:57:20 +0200 |
commit | 56db1511baeb0376a12915c69c1552b04010c26f (patch) | |
tree | d03aa6b4c33a6de39371e9be336c471bfd2cafc5 /doc_topics | |
parent | 8f8d34f03428dbaa6cac229bbe36efc6d80d186d (diff) | |
download | luasystem-56db1511baeb0376a12915c69c1552b04010c26f.tar.gz luasystem-56db1511baeb0376a12915c69c1552b04010c26f.tar.bz2 luasystem-56db1511baeb0376a12915c69c1552b04010c26f.zip |
cleanup and documentation
Diffstat (limited to 'doc_topics')
-rw-r--r-- | doc_topics/03-terminal.md | 124 |
1 files changed, 124 insertions, 0 deletions
diff --git a/doc_topics/03-terminal.md b/doc_topics/03-terminal.md new file mode 100644 index 0000000..06a6b96 --- /dev/null +++ b/doc_topics/03-terminal.md | |||
@@ -0,0 +1,124 @@ | |||
1 | # 3. Terminal functionality | ||
2 | |||
3 | Terminals are fundamentally different on Windows and Posix. So even though | ||
4 | `luasystem` provides primitives to manipulate both the Windows and Posix terminals, | ||
5 | the user will still have to write platform specific code. | ||
6 | |||
7 | To mitigate this a little, all functions are available on all platforms. They just | ||
8 | will be a no-op if invoked on another platform. This means that no platform specific | ||
9 | branching is required (but still possible) in user code. The user must simply set | ||
10 | up both platforms to make it work. | ||
11 | |||
12 | ## 3.1 Backup and Restore terminal settings | ||
13 | |||
14 | Since there are a myriad of settings available; | ||
15 | |||
16 | - `system.setconsoleflags` (Windows) | ||
17 | - `system.setconsolecp` (Windows) | ||
18 | - `system.setconsoleoutputcp` (Windows) | ||
19 | - `system.setnonblock` (Posix) | ||
20 | - `system.tcsetattr` (Posix) | ||
21 | |||
22 | Some helper functions are available to backup and restore them all at once. | ||
23 | See `termbackup`, `termrestore`, `autotermrestore` and `termwrap`. | ||
24 | |||
25 | |||
26 | ## 3.1 Terminal ANSI sequences | ||
27 | |||
28 | Windows is catching up with this. In Windows 10 (since 2019), the Windows Terminal application (not to be | ||
29 | mistaken for the `cmd` console application) supports ANSI sequences. However this | ||
30 | might not be enabled by default. | ||
31 | |||
32 | ANSI processing can be set up both on the input (key sequences, reading cursor position) | ||
33 | as well as on the output (setting colors and cursor shapes). | ||
34 | |||
35 | To enable it use `system.setconsoleflags` like this: | ||
36 | |||
37 | -- setup Windows console to handle ANSI processing on output | ||
38 | sys.setconsoleflags(io.stdout, sys.getconsoleflags(io.stdout) + sys.COF_VIRTUAL_TERMINAL_PROCESSING) | ||
39 | sys.setconsoleflags(io.stderr, sys.getconsoleflags(io.stderr) + sys.COF_VIRTUAL_TERMINAL_PROCESSING) | ||
40 | |||
41 | -- setup Windows console to handle ANSI processing on input | ||
42 | sys.setconsoleflags(io.stdin, sys.getconsoleflags(io.stdin) + sys.CIF_VIRTUAL_TERMINAL_INPUT) | ||
43 | |||
44 | |||
45 | ## 3.2 UTF-8 in/output and display width | ||
46 | |||
47 | ### 3.2.1 UTF-8 in/output | ||
48 | |||
49 | Where (most) Posix systems use UTF-8 by default, Windows internally uses UTF-16. More | ||
50 | recent versions of Lua also have UTF-8 support. So `luasystem` also focusses on UTF-8. | ||
51 | |||
52 | On Windows UTF-8 output can be enabled by setting the output codepage like this: | ||
53 | |||
54 | -- setup Windows output codepage to UTF-8; 65001 | ||
55 | sys.setconsoleoutputcp(65001) | ||
56 | |||
57 | Terminal input is handled by the [`_getwchar()`](https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/getchar-getwchar) function on Windows which returns | ||
58 | UTF-16 surrogate pairs. `luasystem` will automatically convert those to UTF-8. | ||
59 | So when using `readkey` or `readansi` to read keyboard input no additional changes | ||
60 | are required. | ||
61 | |||
62 | ### 3.2.2 UTF-8 display width | ||
63 | |||
64 | Typical western characters and symbols are single width characters and will use only | ||
65 | a single column when displayed on a terminal. However many characters from other | ||
66 | languages/cultures or emojis require 2 columns for display. | ||
67 | |||
68 | Typically the `wcwidth` function is used on Posix to check the number of columns | ||
69 | required for display. However since Windows doesn't provide this functionality a | ||
70 | custom implementation is included based on [the work by Markus Kuhn](http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c). | ||
71 | |||
72 | 2 functions are provided, `system.utf8cwidth` for a single character, and `system.utf8swidth` for | ||
73 | a string. When writing terminal applications the display width is relevant to | ||
74 | positioning the cursor properly. For an example see the [`examples/readline.lua`](../examples/readline.lua.html) file. | ||
75 | |||
76 | |||
77 | ## 3.3 reading keyboard input | ||
78 | |||
79 | ### 3.3.1 Non-blocking | ||
80 | |||
81 | There are 2 functions for keyboard input (actually 3, if taking `system._readkey` into | ||
82 | account): `readkey` and `readansi`. | ||
83 | |||
84 | `readkey` is a low level function and should preferably not be used, it returns | ||
85 | a byte at a time, and hence can leave stray/invalid byte sequences in the buffer if | ||
86 | only the start of a UTF-8 or ANSI sequence is consumed. | ||
87 | |||
88 | The preferred way is to use `readansi` which will parse and return entire characters in | ||
89 | single or multiple bytes, or a full ANSI sequence. | ||
90 | |||
91 | On Windows the input is read using [`_getwchar()`](https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/getchar-getwchar) which bypasses the terminal and reads | ||
92 | the input directly from the keyboard buffer. This means however that the character is | ||
93 | also not being echoed to the terminal (independent of the echo settings used with | ||
94 | `system.setconsoleflags`). | ||
95 | |||
96 | On Posix the traditional file approach is used, which: | ||
97 | |||
98 | - is blocking by default | ||
99 | - echoes input to the terminal | ||
100 | - requires enter to be pressed to pass the input (canonical mode) | ||
101 | |||
102 | To use non-blocking input here's how to set it up: | ||
103 | |||
104 | -- setup Windows console to disable echo and line input (not required since _getwchar is used, just for consistency) | ||
105 | sys.setconsoleflags(io.stdin, sys.getconsoleflags(io.stdin) - sys.CIF_ECHO_INPUT - sys.CIF_LINE_INPUT) | ||
106 | |||
107 | -- setup Posix by disabling echo, canonical mode, and making non-blocking | ||
108 | local of_attr = sys.tcgetattr(io.stdin) | ||
109 | sys.tcsetattr(io.stdin, sys.TCSANOW, { | ||
110 | lflag = of_attr.lflag - sys.L_ICANON - sys.L_ECHO, | ||
111 | }) | ||
112 | sys.setnonblock(io.stdin, true) | ||
113 | |||
114 | |||
115 | Both functions require a timeout to be provided which allows for proper asynchronous | ||
116 | code to be written. Since the underlying sleep method used is `system.sleep`, just patching | ||
117 | that function with a coroutine based yielding one should be all that is needed to make | ||
118 | the result work with asynchroneous coroutine schedulers. | ||
119 | |||
120 | ### 3.3.2 Blocking input | ||
121 | |||
122 | When using traditional input method like `io.stdin:read()` (which is blocking) the echo | ||
123 | and newline properties should be set on Windows similar to Posix. | ||
124 | For an example see [`examples/password_input.lua`](../examples/password_input.lua.html). | ||