aboutsummaryrefslogtreecommitdiff
path: root/system
diff options
context:
space:
mode:
authorThijs Schreijer <thijs@thijsschreijer.nl>2024-05-06 11:44:47 +0200
committerThijs Schreijer <thijs@thijsschreijer.nl>2024-05-20 12:43:55 +0200
commitdcd5d62501e61e0f6901d4d4687ab56430a4b8a7 (patch)
tree4501938052c0f62279eaae66c34811d4b5232fa2 /system
parent1d64b5790f26760cb830336ccca9d51474b73ae8 (diff)
downloadluasystem-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.lua126
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
4local sys = require 'system.core' 4local sys = require 'system.core'
5local global_backup -- global backup for terminal settings
6
7
8
9local 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
38end
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
46function sys.termbackup() 12function 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
67end 36end
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
86end 58end
87 59
88 60
89 61
90--- Backs up terminal settings and restores them on application exit. 62do -- 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
96function 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
104end 113end
105 114
106 115
@@ -208,12 +217,9 @@ end
208 217
209 218
210do 219do
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.