From c157641a4e68e4b292a2149232dc75c6171c9310 Mon Sep 17 00:00:00 2001 From: Thijs Schreijer Date: Mon, 9 Jun 2025 22:31:00 +0200 Subject: fix(term): report only printables as characters readkey would return "char" as type for control characters, yet they are not printable. --- spec/04-term_spec.lua | 10 +++++++++- system/init.lua | 15 +++++++++++---- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/spec/04-term_spec.lua b/spec/04-term_spec.lua index 0ce0033..5dea046 100644 --- a/spec/04-term_spec.lua +++ b/spec/04-term_spec.lua @@ -902,6 +902,14 @@ describe("Terminal:", function() end) + it("reads a control byte for single-byte control characters", function() + setbuffer("\000\031\127") + assert.are.same({"\000", "ctrl"}, {system.readansi(0)}) + assert.are.same({"\031", "ctrl"}, {system.readansi(0)}) + assert.are.same({"\127", "ctrl"}, {system.readansi(0)}) + end) + + it("reads a multi-byte characters one at a time", function() setbuffer(string.char(226, 130, 172) .. -- "€" single string.char(240, 159, 154, 128)) -- "🚀" double @@ -952,7 +960,7 @@ describe("Terminal:", function() it("returns a single character if no sequence is found", function() setbuffer("\27") - assert.are.same({"\27", "char"}, {system.readansi(0)}) + assert.are.same({"\27", "ctrl"}, {system.readansi(0)}) end) diff --git a/system/init.lua b/system/init.lua index f05b237..eeaf38f 100644 --- a/system/init.lua +++ b/system/init.lua @@ -290,13 +290,20 @@ do -- @tparam number timeout the timeout in seconds. -- @tparam[opt=system.sleep] function fsleep the function to call for sleeping. -- @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[1] string the type of input: `"ctrl"` for 0-31 and 127 bytes, `"char"` for other UTF-8 characters, `"ansi"` for an ANSI sequence -- @treturn[2] nil in case of an error -- @treturn[2] string error message; `"timeout"` if the timeout was reached. -- @treturn[2] string partial result in case of an error while reading a sequence, the sequence so far. -- The function retains its own internal buffer, so on the next call the incomplete buffer is used to -- complete the sequence. -- @within Terminal_Input + -- @usage + -- local key, keytype = system.readansi(5) + -- if keytype == "char" then ... end -- printable character + -- if keytype ~= "char" then ... end -- non-printable character or sequence + -- if keytype == "ansi" then ... end -- a multi-byte sequence, but not a UTF8 character + -- if keytype ~= "ansi" then ... end -- a valid UTF8 character (which includes control characters) + -- if keytype == "ctrl" then ... end -- a single-byte ctrl character (0-31, 127) function system.readansi(timeout, fsleep) if type(timeout) ~= "number" then error("arg #1 to readansi, expected timeout in seconds, got " .. type(timeout), 2) @@ -320,7 +327,7 @@ do if key2 == nil then -- no key available, return the escape key, on its own sequence = nil - return string.char(key), "char" + return string.char(key), "ctrl" elseif key2 == 91 then -- "[" means it is for sure an ANSI sequence @@ -351,10 +358,10 @@ do else -- check UTF8 length utf8_length = key < 128 and 1 or key < 224 and 2 or key < 240 and 3 or key < 248 and 4 - if utf8_length == 1 then + if utf8_length == 1 then -- single byte character utf8_length = nil - return string.char(key), "char" + return string.char(key), ((key <= 31 or key == 127) and "ctrl" or "char") else -- UTF8 sequence detected sequence = { key } -- cgit v1.2.3-55-g6feb