aboutsummaryrefslogtreecommitdiff
path: root/examples
diff options
context:
space:
mode:
authorThijs <thijs@thijsschreijer.nl>2023-11-16 09:09:54 +0100
committerThijs Schreijer <thijs@thijsschreijer.nl>2024-04-30 09:28:01 +0200
commitbd994461ef7c2553da9a6945c685152bad50eb8f (patch)
tree28adc32712f00a200a34357e731a570bf1a359dc /examples
parent47c24eed0191f8f72646be63dee94ac2b35eb062 (diff)
downloadluasystem-bd994461ef7c2553da9a6945c685152bad50eb8f.tar.gz
luasystem-bd994461ef7c2553da9a6945c685152bad50eb8f.tar.bz2
luasystem-bd994461ef7c2553da9a6945c685152bad50eb8f.zip
feat(term): getting/setting terminal config flags
Diffstat (limited to 'examples')
-rw-r--r--examples/compat.lua37
-rw-r--r--examples/flag_debugging.lua7
-rw-r--r--examples/password_input.lua59
-rw-r--r--examples/read.lua119
-rw-r--r--examples/spinner.lua64
-rw-r--r--examples/spiral_snake.lua72
6 files changed, 358 insertions, 0 deletions
diff --git a/examples/compat.lua b/examples/compat.lua
new file mode 100644
index 0000000..c00d44a
--- /dev/null
+++ b/examples/compat.lua
@@ -0,0 +1,37 @@
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.is_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 up the terminal to handle ANSI escape sequences on Windows.
15 if sys.isatty(io.stdout) then
16 sys.setconsoleflags(io.stdout, sys.getconsoleflags(io.stdout) + sys.COF_VIRTUAL_TERMINAL_PROCESSING)
17 end
18 if sys.isatty(io.stderr) then
19 sys.setconsoleflags(io.stderr, sys.getconsoleflags(io.stderr) + sys.COF_VIRTUAL_TERMINAL_PROCESSING)
20 end
21 if sys.isatty(io.stdin) then
22 sys.setconsoleflags(io.stdin, sys.getconsoleflags(io.stdout) + sys.ENABLE_VIRTUAL_TERMINAL_INPUT)
23 end
24
25
26else
27 -- On Posix, one can set a variable to an empty string, but on Windows, this
28 -- will remove the variable from the environment. To make this consistent
29 -- across platforms, we will remove the variable from the environment if the
30 -- value is an empty string.
31 local old_setenv = sys.setenv
32 function sys.setenv(name, value)
33 if value == "" then value = nil end
34 return old_setenv(name, value)
35 end
36end
37
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..7a1c747
--- /dev/null
+++ b/examples/read.lua
@@ -0,0 +1,119 @@
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
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")
80while true do
81 local key
82
83 -- wait for a key, and sleep a bit to not do a busy-wait
84 while not key do
85 key = read_input()
86 if not key then sys.sleep(0.1) end
87 end
88
89 if key == "A" then io.write(get_cursor_pos); io.flush() end
90
91 -- check if we got a key or ANSI sequence
92 if #key == 1 then
93 -- just a key
94 local b = key:byte()
95 if b < 32 then
96 key = "." -- replace control characters with a simple "." to not mess up the screen
97 end
98
99 print("you pressed: " .. key .. " (" .. b .. ")")
100 if b == 27 then
101 print("Escape pressed, exiting")
102 break
103 end
104
105 else
106 -- we got an ANSI sequence
107 local seq = { key:byte(1, #key) }
108 print("ANSI sequence received: " .. key:sub(2,-1), "(bytes: " .. table.concat(seq, ", ")..")")
109 end
110end
111
112
113
114-- Clean up afterwards
115sys.setnonblock(io.stdin, false)
116sys.setconsoleflags(io.stdout, of_out)
117sys.setconsoleflags(io.stdin, of_in)
118sys.tcsetattr(io.stdin, sys.TCSANOW, of_attr)
119sys.setnonblock(io.stdin, of_block)
diff --git a/examples/spinner.lua b/examples/spinner.lua
new file mode 100644
index 0000000..5526adc
--- /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.keypressed() then
48 sys.readkey() -- consume key 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!")