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-01 19:17:17 +0100
commitc847fef4095aa82d66fa3b0891298005362a4dfd (patch)
tree4931efe9b91a3be764ae4a9ec7c794a92f3d83a1
parentf5f01c663816235bed38b3056771c6898744f567 (diff)
downloadluasystem-feat/fsleep.tar.gz
luasystem-feat/fsleep.tar.bz2
luasystem-feat/fsleep.zip
feat(readkey): allow a sleep function to be passedfeat/fsleep
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--doc_topics/03-terminal.md8
-rw-r--r--examples/readline.lua6
-rw-r--r--system/init.lua21
3 files changed, 21 insertions, 14 deletions
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