diff options
author | Thijs Schreijer <thijs@thijsschreijer.nl> | 2024-05-06 11:44:47 +0200 |
---|---|---|
committer | Thijs Schreijer <thijs@thijsschreijer.nl> | 2024-05-20 12:43:55 +0200 |
commit | dcd5d62501e61e0f6901d4d4687ab56430a4b8a7 (patch) | |
tree | 4501938052c0f62279eaae66c34811d4b5232fa2 /system | |
parent | 1d64b5790f26760cb830336ccca9d51474b73ae8 (diff) | |
download | luasystem-dcd5d62501e61e0f6901d4d4687ab56430a4b8a7.tar.gz luasystem-dcd5d62501e61e0f6901d4d4687ab56430a4b8a7.tar.bz2 luasystem-dcd5d62501e61e0f6901d4d4687ab56430a4b8a7.zip |
add example for reading a line from the terminal, non-blocking
Handles utf8, and character width
Diffstat (limited to 'system')
-rw-r--r-- | system/init.lua | 126 |
1 files changed, 67 insertions, 59 deletions
diff --git a/system/init.lua b/system/init.lua index 893cd91..c232cd2 100644 --- a/system/init.lua +++ b/system/init.lua | |||
@@ -2,45 +2,11 @@ | |||
2 | -- @module init | 2 | -- @module init |
3 | 3 | ||
4 | local sys = require 'system.core' | 4 | local sys = require 'system.core' |
5 | local global_backup -- global backup for terminal settings | ||
6 | |||
7 | |||
8 | |||
9 | local add_gc_method do | ||
10 | -- feature detection; __GC meta-method, not available in all Lua versions | ||
11 | local has_gc = false | ||
12 | local tt = setmetatable({}, { -- luacheck: ignore | ||
13 | __gc = function() has_gc = true end | ||
14 | }) | ||
15 | |||
16 | -- clear table and run GC to trigger | ||
17 | tt = nil | ||
18 | collectgarbage() | ||
19 | collectgarbage() | ||
20 | |||
21 | |||
22 | if has_gc then | ||
23 | -- use default GC mechanism since it is available | ||
24 | function add_gc_method(t, f) | ||
25 | setmetatable(t, { __gc = f }) | ||
26 | end | ||
27 | else | ||
28 | -- create workaround using a proxy userdata, typical for Lua 5.1 | ||
29 | function add_gc_method(t, f) | ||
30 | local proxy = newproxy(true) | ||
31 | getmetatable(proxy).__gc = function() | ||
32 | t["__gc_proxy"] = nil | ||
33 | f(t) | ||
34 | end | ||
35 | t["__gc_proxy"] = proxy | ||
36 | end | ||
37 | end | ||
38 | end | ||
39 | 5 | ||
40 | 6 | ||
41 | 7 | ||
42 | --- Returns a backup of terminal setting for stdin/out/err. | 8 | --- Returns a backup of terminal setting for stdin/out/err. |
43 | -- Handles terminal/console flags and non-block flags on the streams. | 9 | -- Handles terminal/console flags, Windows codepage, and non-block flags on the streams. |
44 | -- Backs up terminal/console flags only if a stream is a tty. | 10 | -- Backs up terminal/console flags only if a stream is a tty. |
45 | -- @return table with backup of terminal settings | 11 | -- @return table with backup of terminal settings |
46 | function sys.termbackup() | 12 | function sys.termbackup() |
@@ -63,6 +29,9 @@ function sys.termbackup() | |||
63 | backup.block_out = sys.getnonblock(io.stdout) | 29 | backup.block_out = sys.getnonblock(io.stdout) |
64 | backup.block_err = sys.getnonblock(io.stderr) | 30 | backup.block_err = sys.getnonblock(io.stderr) |
65 | 31 | ||
32 | backup.consoleoutcodepage = sys.getconsoleoutputcp() | ||
33 | backup.consolecp = sys.getconsolecp() | ||
34 | |||
66 | return backup | 35 | return backup |
67 | end | 36 | end |
68 | 37 | ||
@@ -82,25 +51,65 @@ function sys.termrestore(backup) | |||
82 | if backup.block_in ~= nil then sys.setnonblock(io.stdin, backup.block_in) end | 51 | if backup.block_in ~= nil then sys.setnonblock(io.stdin, backup.block_in) end |
83 | if backup.block_out ~= nil then sys.setnonblock(io.stdout, backup.block_out) end | 52 | if backup.block_out ~= nil then sys.setnonblock(io.stdout, backup.block_out) end |
84 | if backup.block_err ~= nil then sys.setnonblock(io.stderr, backup.block_err) end | 53 | if backup.block_err ~= nil then sys.setnonblock(io.stderr, backup.block_err) end |
54 | |||
55 | if backup.consoleoutcodepage then sys.setconsoleoutputcp(backup.consoleoutcodepage) end | ||
56 | if backup.consolecp then sys.setconsolecp(backup.consolecp) end | ||
85 | return true | 57 | return true |
86 | end | 58 | end |
87 | 59 | ||
88 | 60 | ||
89 | 61 | ||
90 | --- Backs up terminal settings and restores them on application exit. | 62 | do -- autotermrestore |
91 | -- Calls `termbackup` to back up terminal settings and sets up a GC method to | 63 | local global_backup -- global backup for terminal settings |
92 | -- automatically restore them on application exit (also works on Lua 5.1). | 64 | |
93 | -- @treturn[1] boolean true | 65 | |
94 | -- @treturn[2] nil if the backup was already created | 66 | local add_gc_method do |
95 | -- @treturn[2] string error message | 67 | -- feature detection; __GC meta-method, not available in all Lua versions |
96 | function sys.autotermrestore() | 68 | local has_gc = false |
97 | if global_backup then | 69 | local tt = setmetatable({}, { -- luacheck: ignore |
98 | return nil, "global terminal backup was already set up" | 70 | __gc = function() has_gc = true end |
71 | }) | ||
72 | |||
73 | -- clear table and run GC to trigger | ||
74 | tt = nil | ||
75 | collectgarbage() | ||
76 | collectgarbage() | ||
77 | |||
78 | |||
79 | if has_gc then | ||
80 | -- use default GC mechanism since it is available | ||
81 | function add_gc_method(t, f) | ||
82 | setmetatable(t, { __gc = f }) | ||
83 | end | ||
84 | else | ||
85 | -- create workaround using a proxy userdata, typical for Lua 5.1 | ||
86 | function add_gc_method(t, f) | ||
87 | local proxy = newproxy(true) | ||
88 | getmetatable(proxy).__gc = function() | ||
89 | t["__gc_proxy"] = nil | ||
90 | f(t) | ||
91 | end | ||
92 | t["__gc_proxy"] = proxy | ||
93 | end | ||
94 | end | ||
95 | end | ||
96 | |||
97 | |||
98 | --- Backs up terminal settings and restores them on application exit. | ||
99 | -- Calls `termbackup` to back up terminal settings and sets up a GC method to | ||
100 | -- automatically restore them on application exit (also works on Lua 5.1). | ||
101 | -- @treturn[1] boolean true | ||
102 | -- @treturn[2] nil if the backup was already created | ||
103 | -- @treturn[2] string error message | ||
104 | function sys.autotermrestore() | ||
105 | if global_backup then | ||
106 | return nil, "global terminal backup was already set up" | ||
107 | end | ||
108 | global_backup = sys.termbackup() | ||
109 | add_gc_method(global_backup, function(self) | ||
110 | sys.termrestore(self) end) | ||
111 | return true | ||
99 | end | 112 | end |
100 | global_backup = sys.termbackup() | ||
101 | add_gc_method(global_backup, function(self) | ||
102 | sys.termrestore(self) end) | ||
103 | return true | ||
104 | end | 113 | end |
105 | 114 | ||
106 | 115 | ||
@@ -208,12 +217,9 @@ end | |||
208 | 217 | ||
209 | 218 | ||
210 | do | 219 | do |
211 | local _readkey = sys.readkey | ||
212 | local interval = 0.1 | ||
213 | |||
214 | --- Reads a single byte from the console, with a timeout. | 220 | --- Reads a single byte from the console, with a timeout. |
215 | -- This function uses `system.sleep` to wait in increments of 0.1 seconds until either a byte is | 221 | -- This function uses `system.sleep` to wait until either a byte is available or the timeout is reached. |
216 | -- available or the timeout is reached. | 222 | -- The sleep period is exponentially backing off, starting at 0.0125 seconds, with a maximum of 0.2 seconds. |
217 | -- It returns immediately if a byte is available or if `timeout` is less than or equal to `0`. | 223 | -- It returns immediately if a byte is available or if `timeout` is less than or equal to `0`. |
218 | -- @tparam number timeout the timeout in seconds. | 224 | -- @tparam number timeout the timeout in seconds. |
219 | -- @treturn[1] integer the key code of the key that was received | 225 | -- @treturn[1] integer the key code of the key that was received |
@@ -224,11 +230,13 @@ do | |||
224 | error("arg #1 to readkey, expected timeout in seconds, got " .. type(timeout), 2) | 230 | error("arg #1 to readkey, expected timeout in seconds, got " .. type(timeout), 2) |
225 | end | 231 | end |
226 | 232 | ||
227 | local key = _readkey() | 233 | local interval = 0.0125 |
234 | local key = sys._readkey() | ||
228 | while key == nil and timeout > 0 do | 235 | while key == nil and timeout > 0 do |
229 | sys.sleep(interval) | 236 | sys.sleep(math.min(interval, timeout)) |
230 | timeout = timeout - interval | 237 | timeout = timeout - interval |
231 | key = _readkey() | 238 | interval = math.min(0.2, interval * 2) |
239 | key = sys._readkey() | ||
232 | end | 240 | end |
233 | 241 | ||
234 | if key then | 242 | if key then |
@@ -246,14 +254,14 @@ do | |||
246 | local utf8_length -- length of utf8 sequence currently being processed | 254 | local utf8_length -- length of utf8 sequence currently being processed |
247 | local unpack = unpack or table.unpack | 255 | local unpack = unpack or table.unpack |
248 | 256 | ||
249 | -- Reads a single key, if it is the start of ansi escape sequence then it reads | 257 | --- Reads a single key, if it is the start of ansi escape sequence then it reads |
250 | -- the full sequence. | 258 | -- the full sequence. The key can be a multi-byte string in case of multibyte UTF-8 character. |
251 | -- This function uses `system.readkey`, and hence `system.sleep` to wait until either a key is | 259 | -- This function uses `system.readkey`, and hence `system.sleep` to wait until either a key is |
252 | -- available or the timeout is reached. | 260 | -- available or the timeout is reached. |
253 | -- It returns immediately if a key is available or if `timeout` is less than or equal to `0`. | 261 | -- It returns immediately if a key is available or if `timeout` is less than or equal to `0`. |
254 | -- In case of an ANSI sequence, it will return the full sequence as a string. | 262 | -- In case of an ANSI sequence, it will return the full sequence as a string. |
255 | -- @tparam number timeout the timeout in seconds. | 263 | -- @tparam number timeout the timeout in seconds. |
256 | -- @treturn[1] string the character that was received, or a complete ANSI sequence | 264 | -- @treturn[1] string the character that was received (can be multi-byte), or a complete ANSI sequence |
257 | -- @treturn[1] string the type of input: `"char"` for a single key, `"ansi"` for an ANSI sequence | 265 | -- @treturn[1] string the type of input: `"char"` for a single key, `"ansi"` for an ANSI sequence |
258 | -- @treturn[2] nil in case of an error | 266 | -- @treturn[2] nil in case of an error |
259 | -- @treturn[2] string error message; `"timeout"` if the timeout was reached. | 267 | -- @treturn[2] string error message; `"timeout"` if the timeout was reached. |