aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--examples/read.lua65
-rw-r--r--examples/spinner.lua4
-rw-r--r--system/init.lua147
3 files changed, 157 insertions, 59 deletions
diff --git a/examples/read.lua b/examples/read.lua
index 7a1c747..bd5cbff 100644
--- a/examples/read.lua
+++ b/examples/read.lua
@@ -25,71 +25,19 @@ local get_cursor_pos = "\27[6n"
25 25
26 26
27 27
28local read_input do
29 local left_over_key
30
31 -- Reads a single key, if it is a 27 (start of ansi escape sequence) then it reads
32 -- the rest of the sequence.
33 -- This function is non-blocking, and will return nil if no key is available.
34 -- In case of an ANSI sequence, it will return the full sequence as a string.
35 -- @return nil|string the key read, or nil if no key is available
36 function read_input()
37 if left_over_key then
38 -- we still have a cached key, return it
39 local key = left_over_key
40 left_over_key = nil
41 return string.char(key)
42 end
43
44 local key = sys.readkey()
45 if key == nil then
46 return nil
47 end
48
49 if key ~= 27 then
50 return string.char(key)
51 end
52
53 -- looks like an ansi escape sequence, immediately read next char
54 -- as an heuristic against manually typing escape sequences
55 local brack = sys.readkey()
56 if brack ~= 91 then
57 -- not the expected [ character, so we return the key as is
58 -- and store the extra key read for the next call
59 left_over_key = brack
60 return string.char(key)
61 end
62
63 -- escape sequence detected, read the rest of the sequence
64 local seq = { key, brack }
65 while true do
66 key = sys.readkey()
67 table.insert(seq, key)
68 if (key >= 65 and key <= 90) or (key >= 97 and key <= 126) then
69 -- end of sequence, return the full sequence
70 return string.char((unpack or table.unpack)(seq))
71 end
72 end
73 -- unreachable
74 end
75end
76
77
78
79print("Press a key, or 'A' to get cursor position, 'ESC' to exit") 28print("Press a key, or 'A' to get cursor position, 'ESC' to exit")
80while true do 29while true do
81 local key 30 local key, keytype
82 31
83 -- wait for a key, and sleep a bit to not do a busy-wait 32 -- wait for a key
84 while not key do 33 while not key do
85 key = read_input() 34 key, keytype = sys.readansi(math.huge)
86 if not key then sys.sleep(0.1) end
87 end 35 end
88 36
89 if key == "A" then io.write(get_cursor_pos); io.flush() end 37 if key == "A" then io.write(get_cursor_pos); io.flush() end
90 38
91 -- check if we got a key or ANSI sequence 39 -- check if we got a key or ANSI sequence
92 if #key == 1 then 40 if keytype == "key" then
93 -- just a key 41 -- just a key
94 local b = key:byte() 42 local b = key:byte()
95 if b < 32 then 43 if b < 32 then
@@ -102,10 +50,13 @@ while true do
102 break 50 break
103 end 51 end
104 52
105 else 53 elseif keytype == "ansi" then
106 -- we got an ANSI sequence 54 -- we got an ANSI sequence
107 local seq = { key:byte(1, #key) } 55 local seq = { key:byte(1, #key) }
108 print("ANSI sequence received: " .. key:sub(2,-1), "(bytes: " .. table.concat(seq, ", ")..")") 56 print("ANSI sequence received: " .. key:sub(2,-1), "(bytes: " .. table.concat(seq, ", ")..")")
57
58 else
59 print("unknown key type received: " .. tostring(keytype))
109 end 60 end
110end 61end
111 62
diff --git a/examples/spinner.lua b/examples/spinner.lua
index 5526adc..e518e60 100644
--- a/examples/spinner.lua
+++ b/examples/spinner.lua
@@ -44,8 +44,8 @@ local spinner do
44 i = i + 1 44 i = i + 1
45 if i > #spin then i = 1 end 45 if i > #spin then i = 1 end
46 46
47 if sys.keypressed() then 47 if sys.readkey(0) ~= nil then
48 sys.readkey() -- consume key pressed 48 while sys.readkey(0) ~= nil do end -- consume keys pressed
49 io.write(" "); 49 io.write(" ");
50 left() 50 left()
51 showCursor() 51 showCursor()
diff --git a/system/init.lua b/system/init.lua
index 93dd488..94c2f09 100644
--- a/system/init.lua
+++ b/system/init.lua
@@ -207,4 +207,151 @@ end
207 207
208 208
209 209
210do
211 local _readkey = sys.readkey
212 local interval = 0.1
213
214 --- 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
216 -- available or the timeout is reached.
217 -- 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.
219 -- @treturn[1] integer the key code of the key that was received
220 -- @treturn[2] nil if no key was read
221 -- @treturn[2] string error message; `"timeout"` if the timeout was reached.
222 function sys.readkey(timeout)
223 if type(timeout) ~= "number" then
224 error("arg #1 to readkey, expected timeout in seconds, got " .. type(timeout), 2)
225 end
226
227 local key = _readkey()
228 while key == nil and timeout > 0 do
229 sys.sleep(interval)
230 timeout = timeout - interval
231 key = _readkey()
232 end
233
234 if key then
235 return key
236 end
237 return nil, "timeout"
238 end
239end
240
241
242
243do
244 local left_over_key
245 local sequence -- table to store the sequence in progress
246 local utf8_length -- length of utf8 sequence currently being processed
247
248 -- Reads a single key, if it is the start of ansi escape sequence then it reads
249 -- the full sequence.
250 -- This function uses `system.readkey`, and hence `system.sleep` to wait until either a key is
251 -- available or the timeout is reached.
252 -- It returns immediately if a key is available or if `timeout` is less than or equal to `0`.
253 -- In case of an ANSI sequence, it will return the full sequence as a string.
254 -- @tparam number timeout the timeout in seconds.
255 -- @treturn[1] string the character that was received, or a complete ANSI sequence
256 -- @treturn[1] string the type of input: `"char"` for a single key, `"ansi"` for an ANSI sequence
257 -- @treturn[2] nil in case of an error
258 -- @treturn[2] string error message; `"timeout"` if the timeout was reached.
259 -- @treturn[2] string partial result in case of an error while reading a sequence, the sequence so far.
260 function sys.readansi(timeout)
261 if type(timeout) ~= "number" then
262 error("arg #1 to readansi, expected timeout in seconds, got " .. type(timeout), 2)
263 end
264
265 local key
266
267 if not sequence then
268 -- no sequence in progress, read a key
269
270 if left_over_key then
271 -- we still have a cached key from the last call
272 key = left_over_key
273 left_over_key = nil
274 else
275 -- read a new key
276 local err
277 key, err = sys.readkey(timeout)
278 if key == nil then -- timeout or error
279 return nil, err
280 end
281 end
282
283 if key == 27 then
284 -- looks like an ansi escape sequence, immediately read next char
285 -- as an heuristic against manually typing escape sequences
286 local key2 = sys.readkey(0)
287 if key2 ~= 91 and key2 ~= 79 then -- we expect either "[" or "O" for an ANSI sequence
288 -- not the expected [ or O character, so we return the key as is
289 -- and store the extra key read for the next call
290 left_over_key = key2
291 return string.char(key), "char"
292 end
293
294 -- escape sequence detected
295 sequence = { key, key2 }
296 else
297 -- check UTF8 length
298 utf8_length = key < 128 and 1 or key < 224 and 2 or key < 240 and 3 or key < 248 and 4
299 if utf8_length == 1 then
300 -- single byte character
301 utf8_length = nil
302 return string.char(key), "char"
303 else
304 -- UTF8 sequence detected
305 sequence = { key }
306 end
307 end
308 end
309
310 local err
311 if utf8_length then
312 -- read remainder of UTF8 sequence
313 local timeout_end = sys.gettime() + timeout
314 while true do
315 key, err = sys.readkey(timeout_end - sys.gettime())
316 if err then
317 break
318 end
319 table.insert(sequence, key)
320
321 if #sequence == utf8_length then
322 -- end of sequence, return the full sequence
323 local result = string.char((unpack or table.unpack)(sequence))
324 sequence = nil
325 utf8_length = nil
326 return result, "char"
327 end
328 end
329
330 else
331 -- read remainder of ANSI sequence
332 local timeout_end = sys.gettime() + timeout
333 while true do
334 key, err = sys.readkey(timeout_end - sys.gettime())
335 if err then
336 break
337 end
338 table.insert(sequence, key)
339
340 if (key >= 65 and key <= 90) or (key >= 97 and key <= 126) then
341 -- end of sequence, return the full sequence
342 local result = string.char((unpack or table.unpack)(sequence))
343 sequence = nil
344 return result, "ansi"
345 end
346 end
347 end
348
349 -- error, or timeout reached, return the sequence so far
350 local partial = string.char((unpack or table.unpack)(sequence))
351 return nil, err, partial
352 end
353end
354
355
356
210return sys 357return sys