diff options
author | Thijs Schreijer <thijs@thijsschreijer.nl> | 2025-03-01 19:17:17 +0100 |
---|---|---|
committer | Thijs Schreijer <thijs@thijsschreijer.nl> | 2025-03-01 19:17:17 +0100 |
commit | c847fef4095aa82d66fa3b0891298005362a4dfd (patch) | |
tree | 4931efe9b91a3be764ae4a9ec7c794a92f3d83a1 | |
parent | f5f01c663816235bed38b3056771c6898744f567 (diff) | |
download | luasystem-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.md | 8 | ||||
-rw-r--r-- | examples/readline.lua | 6 | ||||
-rw-r--r-- | system/init.lua | 21 |
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 | ||
115 | Both functions require a timeout to be provided which allows for proper asynchronous | 115 | Both `readkey` and `readansi` require a timeout to be provided which allows for proper asynchronous |
116 | code to be written. Since the underlying sleep method used is `system.sleep`, just patching | 116 | code to be written. The underlying sleep method to use can be provided, and defaults to `system.sleep`. |
117 | that function with a coroutine based yielding one should be all that is needed to make | 117 | Just passing a coroutine enabled sleep method should be all that is needed to make |
118 | the result work with asynchroneous coroutine schedulers. | 118 | the 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 |
148 | function readline.new(opts) | 149 | function 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 | ||
230 | do | 230 | do |
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 |