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. |
