summaryrefslogtreecommitdiff
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.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 @@
-- @module init
local sys = require 'system.core'
-local global_backup -- global backup for terminal settings
-
-
-
-local add_gc_method do
- -- feature detection; __GC meta-method, not available in all Lua versions
- local has_gc = false
- local tt = setmetatable({}, { -- luacheck: ignore
- __gc = function() has_gc = true end
- })
-
- -- clear table and run GC to trigger
- tt = nil
- collectgarbage()
- collectgarbage()
-
-
- if has_gc then
- -- use default GC mechanism since it is available
- function add_gc_method(t, f)
- setmetatable(t, { __gc = f })
- end
- else
- -- create workaround using a proxy userdata, typical for Lua 5.1
- function add_gc_method(t, f)
- local proxy = newproxy(true)
- getmetatable(proxy).__gc = function()
- t["__gc_proxy"] = nil
- f(t)
- end
- t["__gc_proxy"] = proxy
- end
- end
-end
--- Returns a backup of terminal setting for stdin/out/err.
--- Handles terminal/console flags and non-block flags on the streams.
+-- Handles terminal/console flags, Windows codepage, and non-block flags on the streams.
-- Backs up terminal/console flags only if a stream is a tty.
-- @return table with backup of terminal settings
function sys.termbackup()
@@ -63,6 +29,9 @@ function sys.termbackup()
backup.block_out = sys.getnonblock(io.stdout)
backup.block_err = sys.getnonblock(io.stderr)
+ backup.consoleoutcodepage = sys.getconsoleoutputcp()
+ backup.consolecp = sys.getconsolecp()
+
return backup
end
@@ -82,25 +51,65 @@ function sys.termrestore(backup)
if backup.block_in ~= nil then sys.setnonblock(io.stdin, backup.block_in) end
if backup.block_out ~= nil then sys.setnonblock(io.stdout, backup.block_out) end
if backup.block_err ~= nil then sys.setnonblock(io.stderr, backup.block_err) end
+
+ if backup.consoleoutcodepage then sys.setconsoleoutputcp(backup.consoleoutcodepage) end
+ if backup.consolecp then sys.setconsolecp(backup.consolecp) end
return true
end
---- Backs up terminal settings and restores them on application exit.
--- Calls `termbackup` to back up terminal settings and sets up a GC method to
--- automatically restore them on application exit (also works on Lua 5.1).
--- @treturn[1] boolean true
--- @treturn[2] nil if the backup was already created
--- @treturn[2] string error message
-function sys.autotermrestore()
- if global_backup then
- return nil, "global terminal backup was already set up"
+do -- autotermrestore
+ local global_backup -- global backup for terminal settings
+
+
+ local add_gc_method do
+ -- feature detection; __GC meta-method, not available in all Lua versions
+ local has_gc = false
+ local tt = setmetatable({}, { -- luacheck: ignore
+ __gc = function() has_gc = true end
+ })
+
+ -- clear table and run GC to trigger
+ tt = nil
+ collectgarbage()
+ collectgarbage()
+
+
+ if has_gc then
+ -- use default GC mechanism since it is available
+ function add_gc_method(t, f)
+ setmetatable(t, { __gc = f })
+ end
+ else
+ -- create workaround using a proxy userdata, typical for Lua 5.1
+ function add_gc_method(t, f)
+ local proxy = newproxy(true)
+ getmetatable(proxy).__gc = function()
+ t["__gc_proxy"] = nil
+ f(t)
+ end
+ t["__gc_proxy"] = proxy
+ end
+ end
+ end
+
+
+ --- Backs up terminal settings and restores them on application exit.
+ -- Calls `termbackup` to back up terminal settings and sets up a GC method to
+ -- automatically restore them on application exit (also works on Lua 5.1).
+ -- @treturn[1] boolean true
+ -- @treturn[2] nil if the backup was already created
+ -- @treturn[2] string error message
+ function sys.autotermrestore()
+ if global_backup then
+ return nil, "global terminal backup was already set up"
+ end
+ global_backup = sys.termbackup()
+ add_gc_method(global_backup, function(self)
+ sys.termrestore(self) end)
+ return true
end
- global_backup = sys.termbackup()
- add_gc_method(global_backup, function(self)
- sys.termrestore(self) end)
- return true
end
@@ -208,12 +217,9 @@ end
do
- local _readkey = sys.readkey
- local interval = 0.1
-
--- Reads a single byte from the console, with a timeout.
- -- This function uses `system.sleep` to wait in increments of 0.1 seconds until either a byte is
- -- available or the timeout is reached.
+ -- This function uses `system.sleep` to wait until either a byte is available or the timeout is reached.
+ -- The sleep period is exponentially backing off, starting at 0.0125 seconds, with a maximum of 0.2 seconds.
-- It returns immediately if a byte is available or if `timeout` is less than or equal to `0`.
-- @tparam number timeout the timeout in seconds.
-- @treturn[1] integer the key code of the key that was received
@@ -224,11 +230,13 @@ do
error("arg #1 to readkey, expected timeout in seconds, got " .. type(timeout), 2)
end
- local key = _readkey()
+ local interval = 0.0125
+ local key = sys._readkey()
while key == nil and timeout > 0 do
- sys.sleep(interval)
+ sys.sleep(math.min(interval, timeout))
timeout = timeout - interval
- key = _readkey()
+ interval = math.min(0.2, interval * 2)
+ key = sys._readkey()
end
if key then
@@ -246,14 +254,14 @@ do
local utf8_length -- length of utf8 sequence currently being processed
local unpack = unpack or table.unpack
- -- Reads a single key, if it is the start of ansi escape sequence then it reads
- -- the full sequence.
+ --- Reads a single key, if it is the start of ansi escape sequence then it reads
+ -- the full sequence. The key can be a multi-byte string in case of multibyte UTF-8 character.
-- This function uses `system.readkey`, and hence `system.sleep` to wait until either a key is
-- available or the timeout is reached.
-- It returns immediately if a key is available or if `timeout` is less than or equal to `0`.
-- In case of an ANSI sequence, it will return the full sequence as a string.
-- @tparam number timeout the timeout in seconds.
- -- @treturn[1] string the character that was received, or a complete ANSI sequence
+ -- @treturn[1] string the character that was received (can be multi-byte), or a complete ANSI sequence
-- @treturn[1] string the type of input: `"char"` for a single key, `"ansi"` for an ANSI sequence
-- @treturn[2] nil in case of an error
-- @treturn[2] string error message; `"timeout"` if the timeout was reached.