aboutsummaryrefslogtreecommitdiff
path: root/examples
diff options
context:
space:
mode:
authorThijs Schreijer <thijs@thijsschreijer.nl>2024-06-20 22:43:06 +0200
committerGitHub <noreply@github.com>2024-06-20 22:43:06 +0200
commitc1a64c1b75f97cef97965b3bd9a941564a6270bd (patch)
treeb9a92dff6462abd5859c3c76f19748fad5d6c025 /examples
parent47c24eed0191f8f72646be63dee94ac2b35eb062 (diff)
parentb87e6d6d762ee823e81dd7a8984f330eb4018fd8 (diff)
downloadluasystem-c1a64c1b75f97cef97965b3bd9a941564a6270bd.tar.gz
luasystem-c1a64c1b75f97cef97965b3bd9a941564a6270bd.tar.bz2
luasystem-c1a64c1b75f97cef97965b3bd9a941564a6270bd.zip
Merge pull request #21 from lunarmodules/terminal
Diffstat (limited to 'examples')
-rw-r--r--examples/compat.lua40
-rw-r--r--examples/flag_debugging.lua7
-rw-r--r--examples/password_input.lua59
-rw-r--r--examples/read.lua70
-rw-r--r--examples/readline.lua472
-rw-r--r--examples/spinner.lua64
-rw-r--r--examples/spiral_snake.lua72
-rw-r--r--examples/terminalsize.lua37
8 files changed, 821 insertions, 0 deletions
diff --git a/examples/compat.lua b/examples/compat.lua
new file mode 100644
index 0000000..c712105
--- /dev/null
+++ b/examples/compat.lua
@@ -0,0 +1,40 @@
1-- This example shows how to remove platform differences to create a
2-- cross-platform level playing field.
3
4local sys = require "system"
5
6
7
8if sys.windows then
9 -- Windows holds multiple copies of environment variables, to ensure `getenv`
10 -- returns what `setenv` sets we need to use the `system.getenv` instead of
11 -- `os.getenv`.
12 os.getenv = sys.getenv -- luacheck: ignore
13
14 -- Set console output to UTF-8 encoding.
15 sys.setconsoleoutputcp(sys.CODEPAGE_UTF8)
16
17 -- Set up the terminal to handle ANSI escape sequences on Windows.
18 if sys.isatty(io.stdout) then
19 sys.setconsoleflags(io.stdout, sys.getconsoleflags(io.stdout) + sys.COF_VIRTUAL_TERMINAL_PROCESSING)
20 end
21 if sys.isatty(io.stderr) then
22 sys.setconsoleflags(io.stderr, sys.getconsoleflags(io.stderr) + sys.COF_VIRTUAL_TERMINAL_PROCESSING)
23 end
24 if sys.isatty(io.stdin) then
25 sys.setconsoleflags(io.stdin, sys.getconsoleflags(io.stdout) + sys.ENABLE_VIRTUAL_TERMINAL_INPUT)
26 end
27
28
29else
30 -- On Posix, one can set a variable to an empty string, but on Windows, this
31 -- will remove the variable from the environment. To make this consistent
32 -- across platforms, we will remove the variable from the environment if the
33 -- value is an empty string.
34 local old_setenv = sys.setenv
35 function sys.setenv(name, value)
36 if value == "" then value = nil end
37 return old_setenv(name, value)
38 end
39end
40
diff --git a/examples/flag_debugging.lua b/examples/flag_debugging.lua
new file mode 100644
index 0000000..5f1d496
--- /dev/null
+++ b/examples/flag_debugging.lua
@@ -0,0 +1,7 @@
1local sys = require "system"
2
3-- Print the Windows Console flags for stdin
4sys.listconsoleflags(io.stdin)
5
6-- Print the Posix termios flags for stdin
7sys.listtermflags(io.stdin)
diff --git a/examples/password_input.lua b/examples/password_input.lua
new file mode 100644
index 0000000..2994062
--- /dev/null
+++ b/examples/password_input.lua
@@ -0,0 +1,59 @@
1local sys = require "system"
2
3print [[
4
5This example shows how to disable the "echo" of characters read to the console,
6useful for reading secrets from the user.
7
8]]
9
10--- Function to read from stdin without echoing the input (for secrets etc).
11-- It will (in a platform agnostic way) disable echo on the terminal, read the
12-- input, and then re-enable echo.
13-- @param ... Arguments to pass to `io.stdin:read()`
14-- @return the results of `io.stdin:read(...)`
15local function read_secret(...)
16 local w_oldflags, p_oldflags
17
18 if sys.isatty(io.stdin) then
19 -- backup settings, configure echo flags
20 w_oldflags = sys.getconsoleflags(io.stdin)
21 p_oldflags = sys.tcgetattr(io.stdin)
22 -- set echo off to not show password on screen
23 assert(sys.setconsoleflags(io.stdin, w_oldflags - sys.CIF_ECHO_INPUT))
24 assert(sys.tcsetattr(io.stdin, sys.TCSANOW, { lflag = p_oldflags.lflag - sys.L_ECHO }))
25 end
26
27 local secret, err = io.stdin:read(...)
28
29 -- restore settings
30 if sys.isatty(io.stdin) then
31 io.stdout:write("\n") -- Add newline after reading the password
32 sys.setconsoleflags(io.stdin, w_oldflags)
33 sys.tcsetattr(io.stdin, sys.TCSANOW, p_oldflags)
34 end
35
36 return secret, err
37end
38
39
40
41-- Get username
42io.write("Username: ")
43local username = io.stdin:read("*l")
44
45-- Get the secret
46io.write("Password: ")
47local password = read_secret("*l")
48
49-- Get domainname
50io.write("Domain : ")
51local domain = io.stdin:read("*l")
52
53
54-- Print the results
55print("")
56print("Here's what we got:")
57print(" username: " .. username)
58print(" password: " .. password)
59print(" domain : " .. domain)
diff --git a/examples/read.lua b/examples/read.lua
new file mode 100644
index 0000000..4b57b54
--- /dev/null
+++ b/examples/read.lua
@@ -0,0 +1,70 @@
1local sys = require "system"
2
3print [[
4
5This example shows how to do a non-blocking read from the cli.
6
7]]
8
9-- setup Windows console to handle ANSI processing
10local of_in = sys.getconsoleflags(io.stdin)
11local of_out = sys.getconsoleflags(io.stdout)
12sys.setconsoleflags(io.stdout, sys.getconsoleflags(io.stdout) + sys.COF_VIRTUAL_TERMINAL_PROCESSING)
13sys.setconsoleflags(io.stdin, sys.getconsoleflags(io.stdin) + sys.CIF_VIRTUAL_TERMINAL_INPUT)
14
15-- setup Posix terminal to use non-blocking mode, and disable line-mode
16local of_attr = sys.tcgetattr(io.stdin)
17local of_block = sys.getnonblock(io.stdin)
18sys.setnonblock(io.stdin, true)
19sys.tcsetattr(io.stdin, sys.TCSANOW, {
20 lflag = of_attr.lflag - sys.L_ICANON - sys.L_ECHO, -- disable canonical mode and echo
21})
22
23-- cursor sequences
24local get_cursor_pos = "\27[6n"
25
26
27
28print("Press a key, or 'A' to get cursor position, 'ESC' to exit")
29while true do
30 local key, keytype
31
32 -- wait for a key
33 while not key do
34 key, keytype = sys.readansi(math.huge)
35 end
36
37 if key == "A" then io.write(get_cursor_pos); io.flush() end
38
39 -- check if we got a key or ANSI sequence
40 if keytype == "char" then
41 -- just a key
42 local b = key:byte()
43 if b < 32 then
44 key = "." -- replace control characters with a simple "." to not mess up the screen
45 end
46
47 print("you pressed: " .. key .. " (" .. b .. ")")
48 if b == 27 then
49 print("Escape pressed, exiting")
50 break
51 end
52
53 elseif keytype == "ansi" then
54 -- we got an ANSI sequence
55 local seq = { key:byte(1, #key) }
56 print("ANSI sequence received: " .. key:sub(2,-1), "(bytes: " .. table.concat(seq, ", ")..")")
57
58 else
59 print("unknown key type received: " .. tostring(keytype))
60 end
61end
62
63
64
65-- Clean up afterwards
66sys.setnonblock(io.stdin, false)
67sys.setconsoleflags(io.stdout, of_out)
68sys.setconsoleflags(io.stdin, of_in)
69sys.tcsetattr(io.stdin, sys.TCSANOW, of_attr)
70sys.setnonblock(io.stdin, of_block)
diff --git a/examples/readline.lua b/examples/readline.lua
new file mode 100644
index 0000000..ff215dd
--- /dev/null
+++ b/examples/readline.lua
@@ -0,0 +1,472 @@
1--- An example class for reading a line of input from the user in a non-blocking way.
2-- It uses ANSI escape sequences to move the cursor and handle input.
3-- It can be used to read a line of input from the user, with a prompt.
4-- It can handle double-width UTF-8 characters.
5-- It can be used asynchroneously if `system.sleep` is patched to yield to a coroutine scheduler.
6
7local sys = require("system")
8
9
10-- Mapping of key-sequences to key-names
11local key_names = {
12 ["\27[C"] = "right",
13 ["\27[D"] = "left",
14 ["\127"] = "backspace",
15 ["\27[3~"] = "delete",
16 ["\27[H"] = "home",
17 ["\27[F"] = "end",
18 ["\27"] = "escape",
19 ["\9"] = "tab",
20 ["\27[Z"] = "shift-tab",
21}
22
23if sys.windows then
24 key_names["\13"] = "enter"
25else
26 key_names["\10"] = "enter"
27end
28
29
30-- Mapping of key-names to key-sequences
31local key_sequences = {}
32for k, v in pairs(key_names) do
33 key_sequences[v] = k
34end
35
36
37-- bell character
38local function bell()
39 io.write("\7")
40 io.flush()
41end
42
43
44-- generate string to move cursor horizontally
45-- positive goes right, negative goes left
46local function cursor_move_horiz(n)
47 if n == 0 then
48 return ""
49 end
50 return "\27[" .. (n > 0 and n or -n) .. (n > 0 and "C" or "D")
51end
52
53
54-- -- generate string to move cursor vertically
55-- -- positive goes down, negative goes up
56-- local function cursor_move_vert(n)
57-- if n == 0 then
58-- return ""
59-- end
60-- return "\27[" .. (n > 0 and n or -n) .. (n > 0 and "B" or "A")
61-- end
62
63
64-- -- log to the line above the current line
65-- local function log(...)
66-- local arg = { n = select("#", ...), ...}
67-- for i = 1, arg.n do
68-- arg[i] = tostring(arg[i])
69-- end
70-- arg = " " .. table.concat(arg, " ") .. " "
71
72-- io.write(cursor_move_vert(-1), arg, cursor_move_vert(1), cursor_move_horiz(-#arg))
73-- end
74
75
76-- UTF8 character size in bytes
77-- @tparam number b the byte value of the first byte of a UTF8 character
78local function utf8size(b)
79 return b < 128 and 1 or b < 224 and 2 or b < 240 and 3 or b < 248 and 4
80end
81
82
83
84local utf8parse do
85 local utf8_value_mt = {
86 __tostring = function(self)
87 return table.concat(self, "")
88 end,
89 }
90
91 -- Parses a UTF8 string into list of individual characters.
92 -- key 'chars' gets the length in UTF8 characters, whilst # returns the length
93 -- for display (to handle double-width UTF8 chars).
94 -- in the list the double-width characters are followed by an empty string.
95 -- @tparam string s the UTF8 string to parse
96 -- @treturn table the list of characters
97 function utf8parse(s)
98 local t = setmetatable({ chars = 0 }, utf8_value_mt)
99 local i = 1
100 while i <= #s do
101 local b = s:byte(i)
102 local w = utf8size(b)
103 local char = s:sub(i, i + w - 1)
104 t[#t + 1] = char
105 t.chars = t.chars + 1
106 if sys.utf8cwidth(char) == 2 then
107 -- double width character, add empty string to keep the length of the
108 -- list the same as the character width on screen
109 t[#t + 1] = ""
110 end
111 i = i + w
112 end
113 return t
114 end
115end
116
117
118
119-- inline tests for utf8parse
120-- do
121-- local t = utf8parse("a你b好c")
122-- assert(t[1] == "a")
123-- assert(t[2] == "你") -- double width
124-- assert(t[3] == "")
125-- assert(t[4] == "b")
126-- assert(t[5] == "好") -- double width
127-- assert(t[6] == "")
128-- assert(t[7] == "c")
129-- assert(#t == 7) -- size as displayed
130-- end
131
132
133
134-- readline class
135
136local readline = {}
137readline.__index = readline
138
139
140--- Create a new readline object.
141-- @tparam table opts the options for the readline object
142-- @tparam[opt=""] string opts.prompt the prompt to display
143-- @tparam[opt=80] number opts.max_length the maximum length of the input (in characters, not bytes)
144-- @tparam[opt=""] string opts.value the default value
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
147-- @treturn readline the new readline object
148function readline.new(opts)
149 local value = utf8parse(opts.value or "")
150 local prompt = utf8parse(opts.prompt or "")
151 local pos = math.floor(opts.position or (#value + 1))
152 pos = math.max(math.min(pos, (#value + 1)), 1)
153 local len = math.floor(opts.max_length or 80)
154 if len < 1 then
155 error("max_length must be at least 1", 2)
156 end
157
158 if value.chars > len then
159 error("value is longer than max_length", 2)
160 end
161
162 local exit_keys = {}
163 for _, key in ipairs(opts.exit_keys or {}) do
164 exit_keys[key] = true
165 end
166 if exit_keys[1] == nil then
167 -- nothing provided, default to Enter-key
168 exit_keys[1] = key_sequences.enter
169 end
170
171 local self = {
172 value = value, -- the default value
173 max_length = len, -- the maximum length of the input
174 prompt = prompt, -- the prompt to display
175 position = pos, -- the current position in the input
176 drawn_before = false, -- if the prompt has been drawn
177 exit_keys = exit_keys, -- the keys that will cause the readline to exit
178 }
179
180 setmetatable(self, readline)
181 return self
182end
183
184
185
186-- draw the prompt and the input value, and position the cursor.
187local function draw(self, redraw)
188 if redraw or not self.drawn_before then
189 -- we are at start of prompt
190 self.drawn_before = true
191 else
192 -- we are at current cursor position, move to start of prompt
193 io.write(cursor_move_horiz(-(#self.prompt + self.position)))
194 end
195 -- write prompt & value
196 io.write(tostring(self.prompt) .. tostring(self.value))
197 -- clear remainder of input size
198 io.write(string.rep(" ", self.max_length - self.value.chars))
199 io.write(cursor_move_horiz(-(self.max_length - self.value.chars)))
200 -- move to cursor position
201 io.write(cursor_move_horiz(-(#self.value + 1 - self.position)))
202 io.flush()
203end
204
205
206local handle_key do -- keyboard input handler
207
208 local key_handlers
209 key_handlers = {
210 left = function(self)
211 if self.position == 1 then
212 bell()
213 return
214 end
215
216 local new_pos = self.position - 1
217 while self.value[new_pos] == "" do -- skip empty strings; double width chars
218 new_pos = new_pos - 1
219 end
220
221 io.write(cursor_move_horiz(-(self.position - new_pos)))
222 io.flush()
223 self.position = new_pos
224 end,
225
226 right = function(self)
227 if self.position == #self.value + 1 then
228 bell()
229 return
230 end
231
232 local new_pos = self.position + 1
233 while self.value[new_pos] == "" do -- skip empty strings; double width chars
234 new_pos = new_pos + 1
235 end
236
237 io.write(cursor_move_horiz(new_pos - self.position))
238 io.flush()
239 self.position = new_pos
240 end,
241
242 backspace = function(self)
243 if self.position == 1 then
244 bell()
245 return
246 end
247
248 while self.value[self.position - 1] == "" do -- remove empty strings; double width chars
249 io.write(cursor_move_horiz(-1))
250 self.position = self.position - 1
251 table.remove(self.value, self.position)
252 end
253 -- remove char itself
254 io.write(cursor_move_horiz(-1))
255 self.position = self.position - 1
256 table.remove(self.value, self.position)
257 self.value.chars = self.value.chars - 1
258 draw(self)
259 end,
260
261 home = function(self)
262 local new_pos = 1
263 io.write(cursor_move_horiz(new_pos - self.position))
264 self.position = new_pos
265 end,
266
267 ["end"] = function(self)
268 local new_pos = #self.value + 1
269 io.write(cursor_move_horiz(new_pos - self.position))
270 self.position = new_pos
271 end,
272
273 delete = function(self)
274 if self.position > #self.value then
275 bell()
276 return
277 end
278
279 key_handlers.right(self)
280 key_handlers.backspace(self)
281 end,
282 }
283
284
285 -- handles a single input key/ansi-sequence.
286 -- @tparam string key the key or ansi-sequence (from `system.readansi`)
287 -- @tparam string keytype the type of the key, either "char" or "ansi" (from `system.readansi`)
288 -- @treturn string status the status of the key handling, either "ok", "exit_key" or an error message
289 function handle_key(self, key, keytype)
290 if self.exit_keys[key] then
291 -- registered exit key
292 return "exit_key"
293 end
294
295 local handler = key_handlers[key_names[key] or true ]
296 if handler then
297 handler(self)
298 return "ok"
299 end
300
301 if keytype == "ansi" then
302 -- we got an ansi sequence, but dunno how to handle it, ignore
303 -- print("unhandled ansi: ", key:sub(2,-1), string.byte(key, 1, -1))
304 bell()
305 return "ok"
306 end
307
308 -- just a single key
309 if key < " " then
310 -- control character
311 bell()
312 return "ok"
313 end
314
315 if self.value.chars >= self.max_length then
316 bell()
317 return "ok"
318 end
319
320 -- insert the key into the value
321 if sys.utf8cwidth(key) == 2 then
322 -- double width character, insert empty string after it
323 table.insert(self.value, self.position, "")
324 table.insert(self.value, self.position, key)
325 self.position = self.position + 2
326 io.write(cursor_move_horiz(2))
327 else
328 table.insert(self.value, self.position, key)
329 self.position = self.position + 1
330 io.write(cursor_move_horiz(1))
331 end
332 self.value.chars = self.value.chars + 1
333 draw(self)
334 return "ok"
335 end
336end
337
338
339
340--- Get_size returns the maximum size of the input box (prompt + input).
341-- The size is in rows and columns. Columns is determined by
342-- the prompt and the `max_length * 2` (characters can be double-width).
343-- @treturn number the number of rows (always 1)
344-- @treturn number the number of columns
345function readline:get_size()
346 return 1, #self.prompt + self.max_length * 2
347end
348
349
350
351--- Get coordinates of the cursor in the input box (prompt + input).
352-- The coordinates are 1-based. They are returned as row and column, within the
353-- size as reported by `get_size`.
354-- @treturn number the row of the cursor (always 1)
355-- @treturn number the column of the cursor
356function readline:get_cursor()
357 return 1, #self.prompt + self.position
358end
359
360
361
362--- Set the coordinates of the cursor in the input box (prompt + input).
363-- The coordinates are 1-based. They are expected to be within the
364-- size as reported by `get_size`, and beyond the prompt.
365-- If the position is invalid, it will be corrected.
366-- Use the results to check if the position was adjusted.
367-- @tparam number row the row of the cursor (always 1)
368-- @tparam number col the column of the cursor
369-- @return results of get_cursor
370function readline:set_cursor(row, col)
371 local l_prompt = #self.prompt
372 local l_value = #self.value
373
374 if col < l_prompt + 1 then
375 col = l_prompt + 1
376 elseif col > l_prompt + l_value + 1 then
377 col = l_prompt + l_value + 1
378 end
379
380 while self.value[col - l_prompt] == "" do
381 col = col - 1 -- on an empty string, so move back to start of double-width char
382 end
383
384 local new_pos = col - l_prompt
385
386 cursor_move_horiz(self.position - new_pos)
387 io.flush()
388
389 self.position = new_pos
390 return self:get_cursor()
391end
392
393
394
395--- Read a line of input from the user.
396-- It will first print the `prompt` and then wait for input. Ensure the cursor
397-- is at the correct position before calling this function. This function will
398-- do all cursor movements in a relative way.
399-- Can be called again after an exit-key or timeout has occurred. Just make sure
400-- the cursor is at the same position where is was when it returned the last time.
401-- Alternatively the cursor can be set to the position of the prompt (the position
402-- the cursor was in before the first call), and the parameter `redraw` can be set
403-- to `true`.
404-- @tparam[opt=math.huge] number timeout the maximum time to wait for input in seconds
405-- @tparam[opt=false] boolean redraw if `true` the prompt will be redrawn (cursor must be at prompt position!)
406-- @treturn[1] string the input string as entered the user
407-- @treturn[1] string the exit-key used to exit the readline (see `new`)
408-- @treturn[2] nil when input is incomplete
409-- @treturn[2] string error message, the reason why the input is incomplete, `"timeout"`, or an error reading a key
410function readline:__call(timeout, redraw)
411 draw(self, redraw)
412 timeout = timeout or math.huge
413 local timeout_end = sys.gettime() + timeout
414
415 while true do
416 local key, keytype = sys.readansi(timeout_end - sys.gettime())
417 if not key then
418 -- error or timeout
419 return nil, keytype
420 end
421
422 local status = handle_key(self, key, keytype)
423 if status == "exit_key" then
424 return tostring(self.value), key
425
426 elseif status ~= "ok" then
427 error("unknown status received: " .. tostring(status))
428 end
429 end
430end
431
432
433
434-- return readline -- normally we'd return here, but for the example we continue
435
436
437
438
439local backup = sys.termbackup()
440
441-- setup Windows console to handle ANSI processing
442sys.setconsoleflags(io.stdout, sys.getconsoleflags(io.stdout) + sys.COF_VIRTUAL_TERMINAL_PROCESSING)
443sys.setconsoleflags(io.stdin, sys.getconsoleflags(io.stdin) + sys.CIF_VIRTUAL_TERMINAL_INPUT)
444-- set output to UTF-8
445sys.setconsoleoutputcp(sys.CODEPAGE_UTF8)
446
447-- setup Posix terminal to disable canonical mode and echo
448sys.tcsetattr(io.stdin, sys.TCSANOW, {
449 lflag = sys.tcgetattr(io.stdin).lflag - sys.L_ICANON - sys.L_ECHO,
450})
451-- setup stdin to non-blocking mode
452sys.setnonblock(io.stdin, true)
453
454
455local rl = readline.new{
456 prompt = "Enter something: ",
457 max_length = 60,
458 value = "Hello, 你-好 World 🚀!",
459 -- position = 2,
460 exit_keys = {key_sequences.enter, "\27", "\t", "\27[Z"}, -- enter, escape, tab, shift-tab
461}
462
463
464local result, key = rl()
465print("") -- newline after input, to move cursor down from the input line
466print("Result (string): '" .. result .. "'")
467print("Result (bytes):", result:byte(1,-1))
468print("Exit-Key (bytes):", key:byte(1,-1))
469
470
471-- Clean up afterwards
472sys.termrestore(backup)
diff --git a/examples/spinner.lua b/examples/spinner.lua
new file mode 100644
index 0000000..e518e60
--- /dev/null
+++ b/examples/spinner.lua
@@ -0,0 +1,64 @@
1local sys = require("system")
2
3print [[
4
5An example to display a spinner, whilst a long running task executes.
6
7]]
8
9
10-- start make backup, to auto-restore on exit
11sys.autotermrestore()
12-- configure console
13sys.setconsoleflags(io.stdin, sys.getconsoleflags(io.stdin) - sys.CIF_ECHO_INPUT - sys.CIF_LINE_INPUT)
14local of = sys.tcgetattr(io.stdin)
15sys.tcsetattr(io.stdin, sys.TCSANOW, { lflag = of.lflag - sys.L_ICANON - sys.L_ECHO })
16sys.setnonblock(io.stdin, true)
17
18
19
20local function hideCursor()
21 io.write("\27[?25l")
22 io.flush()
23end
24
25local function showCursor()
26 io.write("\27[?25h")
27 io.flush()
28end
29
30local function left(n)
31 io.write("\27[",n or 1,"D")
32 io.flush()
33end
34
35
36
37local spinner do
38 local spin = [[|/-\]]
39 local i = 1
40 spinner = function()
41 hideCursor()
42 io.write(spin:sub(i, i))
43 left()
44 i = i + 1
45 if i > #spin then i = 1 end
46
47 if sys.readkey(0) ~= nil then
48 while sys.readkey(0) ~= nil do end -- consume keys pressed
49 io.write(" ");
50 left()
51 showCursor()
52 return true
53 else
54 return false
55 end
56 end
57end
58
59io.stdout:write("press any key to stop the spinner... ")
60while not spinner() do
61 sys.sleep(0.1)
62end
63
64print("Done!")
diff --git a/examples/spiral_snake.lua b/examples/spiral_snake.lua
new file mode 100644
index 0000000..84a2040
--- /dev/null
+++ b/examples/spiral_snake.lua
@@ -0,0 +1,72 @@
1local sys = require "system"
2
3print [[
4
5This example will draw a snake like spiral on the screen. Showing ANSI escape
6codes for moving the cursor around.
7
8]]
9
10-- backup term settings with auto-restore on exit
11sys.autotermrestore()
12
13-- setup Windows console to handle ANSI processing
14sys.setconsoleflags(io.stdout, sys.getconsoleflags(io.stdout) + sys.COF_VIRTUAL_TERMINAL_PROCESSING)
15
16-- start drawing the spiral.
17-- start from current pos, then right, then up, then left, then down, and again.
18local x, y = 1, 1 -- current position
19local dx, dy = 1, 0 -- direction after each step
20local wx, wy = 30, 30 -- width and height of the room
21local mx, my = 1, 1 -- margin
22
23-- commands to move the cursor
24local move_left = "\27[1D"
25local move_right = "\27[1C"
26local move_up = "\27[1A"
27local move_down = "\27[1B"
28
29-- create room: 30 empty lines
30print(("\n"):rep(wy))
31local move = move_right
32
33while wx > 0 and wy > 0 do
34 sys.sleep(0.01) -- slow down the drawing a little
35 io.write("*" .. move_left .. move )
36 io.flush()
37 x = x + dx
38 y = y + dy
39
40 if x > wx and move == move_right then
41 -- end of move right
42 dx = 0
43 dy = 1
44 move = move_up
45 wy = wy - 1
46 my = my + 1
47 elseif y > wy and move == move_up then
48 -- end of move up
49 dx = -1
50 dy = 0
51 move = move_left
52 wx = wx - 1
53 mx = mx + 1
54 elseif x < mx and move == move_left then
55 -- end of move left
56 dx = 0
57 dy = -1
58 move = move_down
59 wy = wy - 1
60 my = my + 1
61 elseif y < my and move == move_down then
62 -- end of move down
63 dx = 1
64 dy = 0
65 move = move_right
66 wx = wx - 1
67 mx = mx + 1
68 end
69end
70
71io.write(move_down:rep(15))
72print("\nDone!")
diff --git a/examples/terminalsize.lua b/examples/terminalsize.lua
new file mode 100644
index 0000000..105a415
--- /dev/null
+++ b/examples/terminalsize.lua
@@ -0,0 +1,37 @@
1local sys = require("system")
2
3sys.autotermrestore() -- set up auto restore of terminal settings on exit
4
5-- setup Windows console to handle ANSI processing
6sys.setconsoleflags(io.stdout, sys.getconsoleflags(io.stdout) + sys.COF_VIRTUAL_TERMINAL_PROCESSING)
7sys.setconsoleflags(io.stdin, sys.getconsoleflags(io.stdin) + sys.CIF_VIRTUAL_TERMINAL_INPUT)
8
9-- setup Posix to disable canonical mode and echo
10local of_attr = sys.tcgetattr(io.stdin)
11sys.setnonblock(io.stdin, true)
12sys.tcsetattr(io.stdin, sys.TCSANOW, {
13 lflag = of_attr.lflag - sys.L_ICANON - sys.L_ECHO, -- disable canonical mode and echo
14})
15
16
17-- generate string to move cursor horizontally
18-- positive goes right, negative goes left
19local function cursor_move_horiz(n)
20 if n == 0 then
21 return ""
22 end
23 return "\27[" .. (n > 0 and n or -n) .. (n > 0 and "C" or "D")
24end
25
26
27local rows, cols
28print("Change the terminal window size, press any key to exit")
29while not sys.readansi(0.2) do -- use readansi to not leave stray bytes in the input buffer
30 local nrows, ncols = sys.termsize()
31 if rows ~= nrows or cols ~= ncols then
32 rows, cols = nrows, ncols
33 local text = "Terminal size: " .. rows .. "x" .. cols .. " "
34 io.write(text .. cursor_move_horiz(-#text))
35 io.flush()
36 end
37end