aboutsummaryrefslogtreecommitdiff
path: root/system/init.lua
diff options
context:
space:
mode:
Diffstat (limited to 'system/init.lua')
-rw-r--r--system/init.lua110
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
13system.CODEPAGE_UTF8 = 65001 14system.CODEPAGE_UTF8 = 65001
14 15
15do 16do
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
230do 239do
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
266do 283do
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 }