aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md5
-rw-r--r--spec/04-term_spec.lua41
-rw-r--r--system/init.lua53
3 files changed, 78 insertions, 21 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 28e7272..2048c92 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -27,6 +27,11 @@ The scope of what is covered by the version number excludes:
27 27
28## Version history 28## Version history
29 29
30### version xxx, unreleased
31
32- Docs: document readansi internal buffer for incomplete sequences.
33- Fix: ensure to properly parse `<alt>+key` key presses
34
30### version 0.6.0, released 10-Apr-2025 35### version 0.6.0, released 10-Apr-2025
31 36
32- Fix: when sleep returns an error, pass that on in `readkey`. 37- Fix: when sleep returns an error, pass that on in `readkey`.
diff --git a/spec/04-term_spec.lua b/spec/04-term_spec.lua
index b3de461..0ce0033 100644
--- a/spec/04-term_spec.lua
+++ b/spec/04-term_spec.lua
@@ -928,10 +928,31 @@ describe("Terminal:", function()
928 end) 928 end)
929 929
930 930
931 it("reads ANSI escape sequences, just an '<esc>O' (<alt><O> key press)", function()
932 setbuffer("\27O")
933 assert.are.same({"\27O", "ansi"}, {system.readansi(0)})
934 end)
935
936
937 it("reads <alt>+key key presses; <esc>+<key>", function()
938 setbuffer("\27a\27b\27c\27d")
939 assert.are.same({"\27a", "ansi"}, {system.readansi(0)})
940 assert.are.same({"\27b", "ansi"}, {system.readansi(0)})
941 assert.are.same({"\27c", "ansi"}, {system.readansi(0)})
942 assert.are.same({"\27d", "ansi"}, {system.readansi(0)})
943 end)
944
945
946 it("reads <ctrl><alt>[ key press; <esc>+<esc>", function()
947 setbuffer("\27\27\27\27")
948 assert.are.same({"\27\27", "ansi"}, {system.readansi(0)})
949 assert.are.same({"\27\27", "ansi"}, {system.readansi(0)})
950 end)
951
952
931 it("returns a single <esc> character if no sequence is found", function() 953 it("returns a single <esc> character if no sequence is found", function()
932 setbuffer("\27\27[A") 954 setbuffer("\27")
933 assert.are.same({"\27", "char"}, {system.readansi(0)}) 955 assert.are.same({"\27", "char"}, {system.readansi(0)})
934 assert.are.same({"\27[A", "ansi"}, {system.readansi(0)})
935 end) 956 end)
936 957
937 958
@@ -945,6 +966,22 @@ describe("Terminal:", function()
945 assert.is.near(1, timing, 0.5) -- this also works for MacOS in CI 966 assert.is.near(1, timing, 0.5) -- this also works for MacOS in CI
946 end) 967 end)
947 968
969
970 it("incomplete ANSI sequences will be completed on next call", function()
971 setbuffer("\27[")
972 assert.are.same({nil, "timeout", "\27["}, {system.readansi(0)})
973 setbuffer("A")
974 assert.are.same({"\27[A", "ansi"}, {system.readansi(0)})
975 end)
976
977
978 it("incomplete UTF8 sequences will be completed on next call", function()
979 setbuffer(string.char(240, 159))
980 assert.are.same({nil, "timeout", string.char(240, 159)}, {system.readansi(0)})
981 setbuffer(string.char(154, 128))
982 assert.are.same({"🚀", "char"}, {system.readansi(0)})
983 end)
984
948 end) 985 end)
949 986
950 end) 987 end)
diff --git a/system/init.lua b/system/init.lua
index 28fe65c..e0e5f21 100644
--- a/system/init.lua
+++ b/system/init.lua
@@ -267,7 +267,6 @@ end
267 267
268 268
269do 269do
270 local left_over_key
271 local sequence -- table to store the sequence in progress 270 local sequence -- table to store the sequence in progress
272 local utf8_length -- length of utf8 sequence currently being processed 271 local utf8_length -- length of utf8 sequence currently being processed
273 local unpack = unpack or table.unpack 272 local unpack = unpack or table.unpack
@@ -285,6 +284,8 @@ do
285 -- @treturn[2] nil in case of an error 284 -- @treturn[2] nil in case of an error
286 -- @treturn[2] string error message; `"timeout"` if the timeout was reached. 285 -- @treturn[2] string error message; `"timeout"` if the timeout was reached.
287 -- @treturn[2] string partial result in case of an error while reading a sequence, the sequence so far. 286 -- @treturn[2] string partial result in case of an error while reading a sequence, the sequence so far.
287 -- The function retains its own internal buffer, so on the next call the incomplete buffer is used to
288 -- complete the sequence.
288 function system.readansi(timeout, fsleep) 289 function system.readansi(timeout, fsleep)
289 if type(timeout) ~= "number" then 290 if type(timeout) ~= "number" then
290 error("arg #1 to readansi, expected timeout in seconds, got " .. type(timeout), 2) 291 error("arg #1 to readansi, expected timeout in seconds, got " .. type(timeout), 2)
@@ -295,33 +296,47 @@ do
295 296
296 if not sequence then 297 if not sequence then
297 -- no sequence in progress, read a key 298 -- no sequence in progress, read a key
298 299 local err
299 if left_over_key then 300 key, err = system.readkey(timeout, fsleep)
300 -- we still have a cached key from the last call 301 if key == nil then -- timeout or error
301 key = left_over_key 302 return nil, err
302 left_over_key = nil
303 else
304 -- read a new key
305 local err
306 key, err = system.readkey(timeout, fsleep)
307 if key == nil then -- timeout or error
308 return nil, err
309 end
310 end 303 end
311 304
312 if key == 27 then 305 if key == 27 then
313 -- looks like an ansi escape sequence, immediately read next char 306 -- looks like an ansi escape sequence, immediately read next char
314 -- as an heuristic against manually typing escape sequences 307 -- as an heuristic against manually typing escape sequences
315 local key2 = system.readkey(0, fsleep) 308 local key2 = system.readkey(0, fsleep)
316 if key2 ~= 91 and key2 ~= 79 then -- we expect either "[" or "O" for an ANSI sequence 309 if key2 == nil then
317 -- not the expected [ or O character, so we return the key as is 310 -- no key available, return the escape key, on its own
318 -- and store the extra key read for the next call 311 sequence = nil
319 left_over_key = key2
320 return string.char(key), "char" 312 return string.char(key), "char"
313
314 elseif key2 == 91 then
315 -- "[" means it is for sure an ANSI sequence
316 sequence = { key, key2 }
317
318 elseif key2 == 79 then
319 -- "O" means it is either an ANSI sequence or just an <alt>+O key stroke
320 -- check if there is yet another byte available
321 local key3 = system.readkey(0, fsleep)
322 if key3 == nil then
323 -- no key available, return the <alt>O key stroke, report as ANSI
324 sequence = nil
325 return string.char(key, key2), "ansi"
326 end
327 -- it's an ANSI sequence, marked with <ESC>O
328 if (key3 >= 65 and key3 <= 90) or (key3 >= 97 and key3 <= 126) then
329 -- end of sequence, return the full sequence
330 return string.char(key, key2, key3), "ansi"
331 end
332 sequence = { key, key2, key3 }
333
334 else
335 -- not an ANSI sequence, but an <alt>+<key2> key stroke, so report as ANSI
336 sequence = nil
337 return string.char(key, key2), "ansi"
321 end 338 end
322 339
323 -- escape sequence detected
324 sequence = { key, key2 }
325 else 340 else
326 -- check UTF8 length 341 -- check UTF8 length
327 utf8_length = key < 128 and 1 or key < 224 and 2 or key < 240 and 3 or key < 248 and 4 342 utf8_length = key < 128 and 1 or key < 224 and 2 or key < 240 and 3 or key < 248 and 4