diff options
Diffstat (limited to 'system/init.lua')
-rw-r--r-- | system/init.lua | 110 |
1 files changed, 75 insertions, 35 deletions
diff --git a/system/init.lua b/system/init.lua index a81978e..9b71d4d 100644 --- a/system/init.lua +++ b/system/init.lua | |||
@@ -10,17 +10,21 @@ local system = require 'system.core' | |||
10 | --- UTF8 codepage. | 10 | --- UTF8 codepage. |
11 | -- To be used with `system.setconsoleoutputcp` and `system.setconsolecp`. | 11 | -- To be used with `system.setconsoleoutputcp` and `system.setconsolecp`. |
12 | -- @field CODEPAGE_UTF8 The Windows CodePage for UTF8. | 12 | -- @field CODEPAGE_UTF8 The Windows CodePage for UTF8. |
13 | -- @within Terminal_UTF-8 | ||
13 | system.CODEPAGE_UTF8 = 65001 | 14 | system.CODEPAGE_UTF8 = 65001 |
14 | 15 | ||
15 | do | 16 | do |
16 | local backup_mt = {} | 17 | local backup_indicator = {} |
17 | 18 | ||
18 | --- Returns a backup of terminal settings for stdin/out/err. | 19 | --- Returns a backup of terminal settings for stdin/out/err. |
19 | -- Handles terminal/console flags, Windows codepage, and non-block flags on the streams. | 20 | -- Handles terminal/console flags, Windows codepage, and non-block flags on the streams. |
20 | -- Backs up terminal/console flags only if a stream is a tty. | 21 | -- Backs up terminal/console flags only if a stream is a tty. |
21 | -- @return table with backup of terminal settings | 22 | -- @return table with backup of terminal settings |
23 | -- @within Terminal_Backup | ||
22 | function system.termbackup() | 24 | function system.termbackup() |
23 | local backup = setmetatable({}, backup_mt) | 25 | local backup = { |
26 | __type = backup_indicator, -- cannot set a metatable, since autotermrestore uses it for GC | ||
27 | } | ||
24 | 28 | ||
25 | if system.isatty(io.stdin) then | 29 | if system.isatty(io.stdin) then |
26 | backup.console_in = system.getconsoleflags(io.stdin) | 30 | backup.console_in = system.getconsoleflags(io.stdin) |
@@ -50,8 +54,9 @@ do | |||
50 | --- Restores terminal settings from a backup | 54 | --- Restores terminal settings from a backup |
51 | -- @tparam table backup the backup of terminal settings, see `termbackup`. | 55 | -- @tparam table backup the backup of terminal settings, see `termbackup`. |
52 | -- @treturn boolean true | 56 | -- @treturn boolean true |
57 | -- @within Terminal_Backup | ||
53 | function system.termrestore(backup) | 58 | function system.termrestore(backup) |
54 | if getmetatable(backup) ~= backup_mt then | 59 | if type(backup) ~= "table" or backup.__type ~= backup_indicator then |
55 | error("arg #1 to termrestore, expected backup table, got " .. type(backup), 2) | 60 | error("arg #1 to termrestore, expected backup table, got " .. type(backup), 2) |
56 | end | 61 | end |
57 | 62 | ||
@@ -106,6 +111,7 @@ do -- autotermrestore | |||
106 | -- @treturn[1] boolean true | 111 | -- @treturn[1] boolean true |
107 | -- @treturn[2] nil if the backup was already created | 112 | -- @treturn[2] nil if the backup was already created |
108 | -- @treturn[2] string error message | 113 | -- @treturn[2] string error message |
114 | -- @within Terminal_Backup | ||
109 | function system.autotermrestore() | 115 | function system.autotermrestore() |
110 | if global_backup then | 116 | if global_backup then |
111 | return nil, "global terminal backup was already set up" | 117 | return nil, "global terminal backup was already set up" |
@@ -134,6 +140,7 @@ do | |||
134 | -- Calls `termbackup` before calling the function and `termrestore` after. | 140 | -- Calls `termbackup` before calling the function and `termrestore` after. |
135 | -- @tparam function f function to wrap | 141 | -- @tparam function f function to wrap |
136 | -- @treturn function wrapped function | 142 | -- @treturn function wrapped function |
143 | -- @within Terminal_Backup | ||
137 | function system.termwrap(f) | 144 | function system.termwrap(f) |
138 | if type(f) ~= "function" then | 145 | if type(f) ~= "function" then |
139 | error("arg #1 to wrap, expected function, got " .. type(f), 2) | 146 | error("arg #1 to wrap, expected function, got " .. type(f), 2) |
@@ -153,6 +160,7 @@ end | |||
153 | --- Debug function for console flags (Windows). | 160 | --- Debug function for console flags (Windows). |
154 | -- Pretty prints the current flags set for the handle. | 161 | -- Pretty prints the current flags set for the handle. |
155 | -- @param fh file handle (`io.stdin`, `io.stdout`, `io.stderr`) | 162 | -- @param fh file handle (`io.stdin`, `io.stdout`, `io.stderr`) |
163 | -- @within Terminal_Windows | ||
156 | -- @usage -- Print the flags for stdin/out/err | 164 | -- @usage -- Print the flags for stdin/out/err |
157 | -- system.listconsoleflags(io.stdin) | 165 | -- system.listconsoleflags(io.stdin) |
158 | -- system.listconsoleflags(io.stdout) | 166 | -- system.listconsoleflags(io.stdout) |
@@ -192,6 +200,7 @@ end | |||
192 | --- Debug function for terminal flags (Posix). | 200 | --- Debug function for terminal flags (Posix). |
193 | -- Pretty prints the current flags set for the handle. | 201 | -- Pretty prints the current flags set for the handle. |
194 | -- @param fh file handle (`io.stdin`, `io.stdout`, `io.stderr`) | 202 | -- @param fh file handle (`io.stdin`, `io.stdout`, `io.stderr`) |
203 | -- @within Terminal_Posix | ||
195 | -- @usage -- Print the flags for stdin/out/err | 204 | -- @usage -- Print the flags for stdin/out/err |
196 | -- system.listconsoleflags(io.stdin) | 205 | -- system.listconsoleflags(io.stdin) |
197 | -- system.listconsoleflags(io.stdout) | 206 | -- system.listconsoleflags(io.stdout) |
@@ -230,32 +239,40 @@ end | |||
230 | do | 239 | do |
231 | --- Reads a single byte from the console, with a timeout. | 240 | --- Reads a single byte from the console, with a timeout. |
232 | -- This function uses `fsleep` to wait until either a byte is available or the timeout is reached. | 241 | -- This function uses `fsleep` to wait until either a byte is available or the timeout is reached. |
233 | -- The sleep period is exponentially backing off, starting at 0.0125 seconds, with a maximum of 0.2 seconds. | 242 | -- The sleep period is exponentially backing off, starting at 0.0125 seconds, with a maximum of 0.1 seconds. |
234 | -- It returns immediately if a byte is available or if `timeout` is less than or equal to `0`. | 243 | -- It returns immediately if a byte is available or if `timeout` is less than or equal to `0`. |
235 | -- | 244 | -- |
236 | -- Using `system.readansi` is preferred over this function. Since this function can leave stray/invalid | 245 | -- Using `system.readansi` is preferred over this function. Since this function can leave stray/invalid |
237 | -- byte-sequences in the input buffer, while `system.readansi` reads full ANSI and UTF8 sequences. | 246 | -- byte-sequences in the input buffer, while `system.readansi` reads full ANSI and UTF8 sequences. |
238 | -- @tparam number timeout the timeout in seconds. | 247 | -- @tparam number timeout the timeout in seconds. |
239 | -- @tparam[opt=system.sleep] function fsleep the function to call for sleeping. | 248 | -- @tparam[opt=system.sleep] function fsleep the function to call for sleeping; `ok, err = fsleep(secs)` |
240 | -- @treturn[1] byte the byte value that was read. | 249 | -- @treturn[1] byte the byte value that was read. |
241 | -- @treturn[2] nil if no key was read | 250 | -- @treturn[2] nil if no key was read |
242 | -- @treturn[2] string error message; `"timeout"` if the timeout was reached. | 251 | -- @treturn[2] string error message when the timeout was reached (`"timeout"`), or if `sleep` failed. |
252 | -- @within Terminal_Input | ||
243 | function system.readkey(timeout, fsleep) | 253 | function system.readkey(timeout, fsleep) |
244 | if type(timeout) ~= "number" then | 254 | if type(timeout) ~= "number" then |
245 | error("arg #1 to readkey, expected timeout in seconds, got " .. type(timeout), 2) | 255 | error("arg #1 to readkey, expected timeout in seconds, got " .. type(timeout), 2) |
246 | end | 256 | end |
247 | 257 | ||
248 | local interval = 0.0125 | 258 | local interval = 0.0125 |
249 | local key = system._readkey() | 259 | local ok |
260 | local key, err = system._readkey() | ||
250 | while key == nil and timeout > 0 do | 261 | while key == nil and timeout > 0 do |
251 | (fsleep or system.sleep)(math.min(interval, timeout)) | 262 | if err then |
263 | return nil, err | ||
264 | end | ||
265 | ok, err = (fsleep or system.sleep)(math.min(interval, timeout)) | ||
266 | if not ok then | ||
267 | return nil, err | ||
268 | end | ||
252 | timeout = timeout - interval | 269 | timeout = timeout - interval |
253 | interval = math.min(0.2, interval * 2) | 270 | interval = math.min(0.1, interval * 2) |
254 | key = system._readkey() | 271 | key, err = system._readkey() |
255 | end | 272 | end |
256 | 273 | ||
257 | if key then | 274 | if key or err then |
258 | return key | 275 | return key, err |
259 | end | 276 | end |
260 | return nil, "timeout" | 277 | return nil, "timeout" |
261 | end | 278 | end |
@@ -264,7 +281,6 @@ end | |||
264 | 281 | ||
265 | 282 | ||
266 | do | 283 | do |
267 | local left_over_key | ||
268 | local sequence -- table to store the sequence in progress | 284 | local sequence -- table to store the sequence in progress |
269 | local utf8_length -- length of utf8 sequence currently being processed | 285 | local utf8_length -- length of utf8 sequence currently being processed |
270 | local unpack = unpack or table.unpack | 286 | local unpack = unpack or table.unpack |
@@ -278,10 +294,20 @@ do | |||
278 | -- @tparam number timeout the timeout in seconds. | 294 | -- @tparam number timeout the timeout in seconds. |
279 | -- @tparam[opt=system.sleep] function fsleep the function to call for sleeping. | 295 | -- @tparam[opt=system.sleep] function fsleep the function to call for sleeping. |
280 | -- @treturn[1] string the character that was received (can be multi-byte), or a complete ANSI sequence | 296 | -- @treturn[1] string the character that was received (can be multi-byte), or a complete ANSI sequence |
281 | -- @treturn[1] string the type of input: `"char"` for a single key, `"ansi"` for an ANSI sequence | 297 | -- @treturn[1] string the type of input: `"ctrl"` for 0-31 and 127 bytes, `"char"` for other UTF-8 characters, `"ansi"` for an ANSI sequence |
282 | -- @treturn[2] nil in case of an error | 298 | -- @treturn[2] nil in case of an error |
283 | -- @treturn[2] string error message; `"timeout"` if the timeout was reached. | 299 | -- @treturn[2] string error message; `"timeout"` if the timeout was reached. |
284 | -- @treturn[2] string partial result in case of an error while reading a sequence, the sequence so far. | 300 | -- @treturn[2] string partial result in case of an error while reading a sequence, the sequence so far. |
301 | -- The function retains its own internal buffer, so on the next call the incomplete buffer is used to | ||
302 | -- complete the sequence. | ||
303 | -- @within Terminal_Input | ||
304 | -- @usage | ||
305 | -- local key, keytype = system.readansi(5) | ||
306 | -- if keytype == "char" then ... end -- printable character | ||
307 | -- if keytype ~= "char" then ... end -- non-printable character or sequence | ||
308 | -- if keytype == "ansi" then ... end -- a multi-byte sequence, but not a UTF8 character | ||
309 | -- if keytype ~= "ansi" then ... end -- a valid UTF8 character (which includes control characters) | ||
310 | -- if keytype == "ctrl" then ... end -- a single-byte ctrl character (0-31, 127) | ||
285 | function system.readansi(timeout, fsleep) | 311 | function system.readansi(timeout, fsleep) |
286 | if type(timeout) ~= "number" then | 312 | if type(timeout) ~= "number" then |
287 | error("arg #1 to readansi, expected timeout in seconds, got " .. type(timeout), 2) | 313 | error("arg #1 to readansi, expected timeout in seconds, got " .. type(timeout), 2) |
@@ -292,40 +318,54 @@ do | |||
292 | 318 | ||
293 | if not sequence then | 319 | if not sequence then |
294 | -- no sequence in progress, read a key | 320 | -- no sequence in progress, read a key |
295 | 321 | local err | |
296 | if left_over_key then | 322 | key, err = system.readkey(timeout, fsleep) |
297 | -- we still have a cached key from the last call | 323 | if key == nil then -- timeout or error |
298 | key = left_over_key | 324 | return nil, err |
299 | left_over_key = nil | ||
300 | else | ||
301 | -- read a new key | ||
302 | local err | ||
303 | key, err = system.readkey(timeout, fsleep) | ||
304 | if key == nil then -- timeout or error | ||
305 | return nil, err | ||
306 | end | ||
307 | end | 325 | end |
308 | 326 | ||
309 | if key == 27 then | 327 | if key == 27 then |
310 | -- looks like an ansi escape sequence, immediately read next char | 328 | -- looks like an ansi escape sequence, immediately read next char |
311 | -- as an heuristic against manually typing escape sequences | 329 | -- as an heuristic against manually typing escape sequences |
312 | local key2 = system.readkey(0, fsleep) | 330 | local key2 = system.readkey(0, fsleep) |
313 | if key2 ~= 91 and key2 ~= 79 then -- we expect either "[" or "O" for an ANSI sequence | 331 | if key2 == nil then |
314 | -- not the expected [ or O character, so we return the key as is | 332 | -- no key available, return the escape key, on its own |
315 | -- and store the extra key read for the next call | 333 | sequence = nil |
316 | left_over_key = key2 | 334 | return string.char(key), "ctrl" |
317 | return string.char(key), "char" | 335 | |
336 | elseif key2 == 91 then | ||
337 | -- "[" means it is for sure an ANSI sequence | ||
338 | sequence = { key, key2 } | ||
339 | |||
340 | elseif key2 == 79 then | ||
341 | -- "O" means it is either an ANSI sequence or just an <alt>+O key stroke | ||
342 | -- check if there is yet another byte available | ||
343 | local key3 = system.readkey(0, fsleep) | ||
344 | if key3 == nil then | ||
345 | -- no key available, return the <alt>O key stroke, report as ANSI | ||
346 | sequence = nil | ||
347 | return string.char(key, key2), "ansi" | ||
348 | end | ||
349 | -- it's an ANSI sequence, marked with <ESC>O | ||
350 | if (key3 >= 65 and key3 <= 90) or (key3 >= 97 and key3 <= 126) then | ||
351 | -- end of sequence, return the full sequence | ||
352 | return string.char(key, key2, key3), "ansi" | ||
353 | end | ||
354 | sequence = { key, key2, key3 } | ||
355 | |||
356 | else | ||
357 | -- not an ANSI sequence, but an <alt>+<key2> key stroke, so report as ANSI | ||
358 | sequence = nil | ||
359 | return string.char(key, key2), "ansi" | ||
318 | end | 360 | end |
319 | 361 | ||
320 | -- escape sequence detected | ||
321 | sequence = { key, key2 } | ||
322 | else | 362 | else |
323 | -- check UTF8 length | 363 | -- check UTF8 length |
324 | utf8_length = key < 128 and 1 or key < 224 and 2 or key < 240 and 3 or key < 248 and 4 | 364 | utf8_length = key < 128 and 1 or key < 224 and 2 or key < 240 and 3 or key < 248 and 4 |
325 | if utf8_length == 1 then | 365 | if utf8_length == 1 then |
326 | -- single byte character | 366 | -- single byte character |
327 | utf8_length = nil | 367 | utf8_length = nil |
328 | return string.char(key), "char" | 368 | return string.char(key), ((key <= 31 or key == 127) and "ctrl" or "char") |
329 | else | 369 | else |
330 | -- UTF8 sequence detected | 370 | -- UTF8 sequence detected |
331 | sequence = { key } | 371 | sequence = { key } |