aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThijs Schreijer <thijs@thijsschreijer.nl>2025-03-01 19:17:17 +0100
committerThijs Schreijer <thijs@thijsschreijer.nl>2025-03-02 19:40:15 +0100
commit2981db32130b30c9b12e7347bfdbe2e7584e9274 (patch)
tree9dc1330b743b9f4aed15d92c8036a5a41024fc87
parentbdd02cba9ffa1eee3684063b46091f2f868b32b8 (diff)
downloadluasystem-2981db32130b30c9b12e7347bfdbe2e7584e9274.tar.gz
luasystem-2981db32130b30c9b12e7347bfdbe2e7584e9274.tar.bz2
luasystem-2981db32130b30c9b12e7347bfdbe2e7584e9274.zip
feat(readkey): allow a sleep function to be passed
There are cases where both a blocking and non-blocking sleep is needed. In those cases just poatching system.sleep isn't good enough. Hence now we can pass a sleep function.
-rw-r--r--CHANGELOG.md1
-rw-r--r--doc_topics/03-terminal.md8
-rw-r--r--examples/readline.lua6
-rw-r--r--system/init.lua21
4 files changed, 22 insertions, 14 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3d95d9f..af9a294 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -32,6 +32,7 @@ The scope of what is covered by the version number excludes:
32 32
33- Feat: when detecting character display width, also accept unicode codepoints (integers), 33- Feat: when detecting character display width, also accept unicode codepoints (integers),
34 since the Lua utf8 library returns codepoints, not strings 34 since the Lua utf8 library returns codepoints, not strings
35- Feat: allow passing in a sleep function to `readkey` and `readansi`
35- Fix: NetBSD fix compilation, undeclared directives 36- Fix: NetBSD fix compilation, undeclared directives
36- Refactor: random bytes; remove deprecated API usage on Windows, move to 37- Refactor: random bytes; remove deprecated API usage on Windows, move to
37 binary api instead of /dev/urandom file on linux and bsd 38 binary api instead of /dev/urandom file on linux and bsd
diff --git a/doc_topics/03-terminal.md b/doc_topics/03-terminal.md
index 9bad359..5bdf543 100644
--- a/doc_topics/03-terminal.md
+++ b/doc_topics/03-terminal.md
@@ -112,10 +112,10 @@ To use non-blocking input here's how to set it up:
112 sys.setnonblock(io.stdin, true) 112 sys.setnonblock(io.stdin, true)
113 113
114 114
115Both functions require a timeout to be provided which allows for proper asynchronous 115Both `readkey` and `readansi` require a timeout to be provided which allows for proper asynchronous
116code to be written. Since the underlying sleep method used is `system.sleep`, just patching 116code to be written. The underlying sleep method to use can be provided, and defaults to `system.sleep`.
117that function with a coroutine based yielding one should be all that is needed to make 117Just passing a coroutine enabled sleep method should be all that is needed to make
118the result work with asynchroneous coroutine schedulers. 118the result work with asynchroneous coroutine schedulers. Alternatively just patch `system.sleep`.
119 119
120### 3.3.2 Blocking input 120### 3.3.2 Blocking input
121 121
diff --git a/examples/readline.lua b/examples/readline.lua
index ff215dd..98da267 100644
--- a/examples/readline.lua
+++ b/examples/readline.lua
@@ -144,10 +144,12 @@ readline.__index = readline
144-- @tparam[opt=""] string opts.value the default value 144-- @tparam[opt=""] string opts.value the default value
145-- @tparam[opt=`#value`] number opts.position of the cursor in the input 145-- @tparam[opt=`#value`] number opts.position of the cursor in the input
146-- @tparam[opt={"\10"/"\13"}] table opts.exit_keys an array of keys that will cause the readline to exit 146-- @tparam[opt={"\10"/"\13"}] table opts.exit_keys an array of keys that will cause the readline to exit
147-- @tparam[opt=system.sleep] function opts.fsleep the sleep function to use (see `system.readansi`)
147-- @treturn readline the new readline object 148-- @treturn readline the new readline object
148function readline.new(opts) 149function readline.new(opts)
149 local value = utf8parse(opts.value or "") 150 local value = utf8parse(opts.value or "")
150 local prompt = utf8parse(opts.prompt or "") 151 local prompt = utf8parse(opts.prompt or "")
152 local fsleep = opts.fsleep or sys.sleep
151 local pos = math.floor(opts.position or (#value + 1)) 153 local pos = math.floor(opts.position or (#value + 1))
152 pos = math.max(math.min(pos, (#value + 1)), 1) 154 pos = math.max(math.min(pos, (#value + 1)), 1)
153 local len = math.floor(opts.max_length or 80) 155 local len = math.floor(opts.max_length or 80)
@@ -175,6 +177,7 @@ function readline.new(opts)
175 position = pos, -- the current position in the input 177 position = pos, -- the current position in the input
176 drawn_before = false, -- if the prompt has been drawn 178 drawn_before = false, -- if the prompt has been drawn
177 exit_keys = exit_keys, -- the keys that will cause the readline to exit 179 exit_keys = exit_keys, -- the keys that will cause the readline to exit
180 fsleep = fsleep, -- the sleep function to use
178 } 181 }
179 182
180 setmetatable(self, readline) 183 setmetatable(self, readline)
@@ -413,7 +416,7 @@ function readline:__call(timeout, redraw)
413 local timeout_end = sys.gettime() + timeout 416 local timeout_end = sys.gettime() + timeout
414 417
415 while true do 418 while true do
416 local key, keytype = sys.readansi(timeout_end - sys.gettime()) 419 local key, keytype = sys.readansi(timeout_end - sys.gettime(), self.fsleep)
417 if not key then 420 if not key then
418 -- error or timeout 421 -- error or timeout
419 return nil, keytype 422 return nil, keytype
@@ -458,6 +461,7 @@ local rl = readline.new{
458 value = "Hello, 你-好 World 🚀!", 461 value = "Hello, 你-好 World 🚀!",
459 -- position = 2, 462 -- position = 2,
460 exit_keys = {key_sequences.enter, "\27", "\t", "\27[Z"}, -- enter, escape, tab, shift-tab 463 exit_keys = {key_sequences.enter, "\27", "\t", "\27[Z"}, -- enter, escape, tab, shift-tab
464 fsleep = sys.sleep,
461} 465}
462 466
463 467
diff --git a/system/init.lua b/system/init.lua
index e99d0d4..a81978e 100644
--- a/system/init.lua
+++ b/system/init.lua
@@ -229,17 +229,18 @@ end
229 229
230do 230do
231 --- Reads a single byte from the console, with a timeout. 231 --- Reads a single byte from the console, with a timeout.
232 -- This function uses `system.sleep` to wait until either a byte is available or the timeout is reached. 232 -- 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. 233 -- The sleep period is exponentially backing off, starting at 0.0125 seconds, with a maximum of 0.2 seconds.
234 -- It returns immediately if a byte is available or if `timeout` is less than or equal to `0`. 234 -- It returns immediately if a byte is available or if `timeout` is less than or equal to `0`.
235 -- 235 --
236 -- Using `system.readansi` is preferred over this function. Since this function can leave stray/invalid 236 -- 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. 237 -- byte-sequences in the input buffer, while `system.readansi` reads full ANSI and UTF8 sequences.
238 -- @tparam number timeout the timeout in seconds. 238 -- @tparam number timeout the timeout in seconds.
239 -- @tparam[opt=system.sleep] function fsleep the function to call for sleeping.
239 -- @treturn[1] byte the byte value that was read. 240 -- @treturn[1] byte the byte value that was read.
240 -- @treturn[2] nil if no key was read 241 -- @treturn[2] nil if no key was read
241 -- @treturn[2] string error message; `"timeout"` if the timeout was reached. 242 -- @treturn[2] string error message; `"timeout"` if the timeout was reached.
242 function system.readkey(timeout) 243 function system.readkey(timeout, fsleep)
243 if type(timeout) ~= "number" then 244 if type(timeout) ~= "number" then
244 error("arg #1 to readkey, expected timeout in seconds, got " .. type(timeout), 2) 245 error("arg #1 to readkey, expected timeout in seconds, got " .. type(timeout), 2)
245 end 246 end
@@ -247,7 +248,7 @@ do
247 local interval = 0.0125 248 local interval = 0.0125
248 local key = system._readkey() 249 local key = system._readkey()
249 while key == nil and timeout > 0 do 250 while key == nil and timeout > 0 do
250 system.sleep(math.min(interval, timeout)) 251 (fsleep or system.sleep)(math.min(interval, timeout))
251 timeout = timeout - interval 252 timeout = timeout - interval
252 interval = math.min(0.2, interval * 2) 253 interval = math.min(0.2, interval * 2)
253 key = system._readkey() 254 key = system._readkey()
@@ -270,20 +271,22 @@ do
270 271
271 --- Reads a single key, if it is the start of ansi escape sequence then it reads 272 --- Reads a single key, if it is the start of ansi escape sequence then it reads
272 -- the full sequence. The key can be a multi-byte string in case of multibyte UTF-8 character. 273 -- the full sequence. The key can be a multi-byte string in case of multibyte UTF-8 character.
273 -- This function uses `system.readkey`, and hence `system.sleep` to wait until either a key is 274 -- This function uses `system.readkey`, and hence `fsleep` to wait until either a key is
274 -- available or the timeout is reached. 275 -- available or the timeout is reached.
275 -- It returns immediately if a key is available or if `timeout` is less than or equal to `0`. 276 -- It returns immediately if a key is available or if `timeout` is less than or equal to `0`.
276 -- In case of an ANSI sequence, it will return the full sequence as a string. 277 -- In case of an ANSI sequence, it will return the full sequence as a string.
277 -- @tparam number timeout the timeout in seconds. 278 -- @tparam number timeout the timeout in seconds.
279 -- @tparam[opt=system.sleep] function fsleep the function to call for sleeping.
278 -- @treturn[1] string the character that was received (can be multi-byte), or a complete ANSI sequence 280 -- @treturn[1] string the character that was received (can be multi-byte), or a complete ANSI sequence
279 -- @treturn[1] string the type of input: `"char"` for a single key, `"ansi"` for an ANSI sequence 281 -- @treturn[1] string the type of input: `"char"` for a single key, `"ansi"` for an ANSI sequence
280 -- @treturn[2] nil in case of an error 282 -- @treturn[2] nil in case of an error
281 -- @treturn[2] string error message; `"timeout"` if the timeout was reached. 283 -- @treturn[2] string error message; `"timeout"` if the timeout was reached.
282 -- @treturn[2] string partial result in case of an error while reading a sequence, the sequence so far. 284 -- @treturn[2] string partial result in case of an error while reading a sequence, the sequence so far.
283 function system.readansi(timeout) 285 function system.readansi(timeout, fsleep)
284 if type(timeout) ~= "number" then 286 if type(timeout) ~= "number" then
285 error("arg #1 to readansi, expected timeout in seconds, got " .. type(timeout), 2) 287 error("arg #1 to readansi, expected timeout in seconds, got " .. type(timeout), 2)
286 end 288 end
289 fsleep = fsleep or system.sleep
287 290
288 local key 291 local key
289 292
@@ -297,7 +300,7 @@ do
297 else 300 else
298 -- read a new key 301 -- read a new key
299 local err 302 local err
300 key, err = system.readkey(timeout) 303 key, err = system.readkey(timeout, fsleep)
301 if key == nil then -- timeout or error 304 if key == nil then -- timeout or error
302 return nil, err 305 return nil, err
303 end 306 end
@@ -306,7 +309,7 @@ do
306 if key == 27 then 309 if key == 27 then
307 -- looks like an ansi escape sequence, immediately read next char 310 -- looks like an ansi escape sequence, immediately read next char
308 -- as an heuristic against manually typing escape sequences 311 -- as an heuristic against manually typing escape sequences
309 local key2 = system.readkey(0) 312 local key2 = system.readkey(0, fsleep)
310 if key2 ~= 91 and key2 ~= 79 then -- we expect either "[" or "O" for an ANSI sequence 313 if key2 ~= 91 and key2 ~= 79 then -- we expect either "[" or "O" for an ANSI sequence
311 -- not the expected [ or O character, so we return the key as is 314 -- not the expected [ or O character, so we return the key as is
312 -- and store the extra key read for the next call 315 -- and store the extra key read for the next call
@@ -335,7 +338,7 @@ do
335 -- read remainder of UTF8 sequence 338 -- read remainder of UTF8 sequence
336 local timeout_end = system.gettime() + timeout 339 local timeout_end = system.gettime() + timeout
337 while true do 340 while true do
338 key, err = system.readkey(timeout_end - system.gettime()) 341 key, err = system.readkey(timeout_end - system.gettime(), fsleep)
339 if err then 342 if err then
340 break 343 break
341 end 344 end
@@ -354,7 +357,7 @@ do
354 -- read remainder of ANSI sequence 357 -- read remainder of ANSI sequence
355 local timeout_end = system.gettime() + timeout 358 local timeout_end = system.gettime() + timeout
356 while true do 359 while true do
357 key, err = system.readkey(timeout_end - system.gettime()) 360 key, err = system.readkey(timeout_end - system.gettime(), fsleep)
358 if err then 361 if err then
359 break 362 break
360 end 363 end