From bb4fd73c317cc88beb5e58c1abf52138abed107f Mon Sep 17 00:00:00 2001
From: Thijs Schreijer
Date: Thu, 20 Jun 2024 23:16:29 +0200
Subject: Release v0.4.0 (#24)
---
CHANGELOG.md | 14 +
LICENSE.md | 2 +-
docs/classes/bitflags.html | 305 +++++++
docs/examples/compat.lua.html | 119 +++
docs/examples/flag_debugging.lua.html | 87 ++
docs/examples/password_input.lua.html | 139 ++++
docs/examples/read.lua.html | 150 ++++
docs/examples/readline.lua.html | 552 +++++++++++++
docs/examples/spinner.lua.html | 144 ++++
docs/examples/spiral_snake.lua.html | 152 ++++
docs/examples/terminalsize.lua.html | 117 +++
docs/index.html | 369 ++-------
docs/modules/system.html | 1418 +++++++++++++++++++++++++++++++++
docs/topics/01-introduction.md.html | 27 +-
docs/topics/02-development.md.html | 91 +++
docs/topics/03-terminal.md.html | 225 ++++++
docs/topics/CHANGELOG.md.html | 55 +-
docs/topics/LICENSE.md.html | 29 +-
rockspecs/luasystem-0.4.0-1.rockspec | 85 ++
src/core.c | 5 +-
20 files changed, 3774 insertions(+), 311 deletions(-)
create mode 100644 docs/classes/bitflags.html
create mode 100644 docs/examples/compat.lua.html
create mode 100644 docs/examples/flag_debugging.lua.html
create mode 100644 docs/examples/password_input.lua.html
create mode 100644 docs/examples/read.lua.html
create mode 100644 docs/examples/readline.lua.html
create mode 100644 docs/examples/spinner.lua.html
create mode 100644 docs/examples/spiral_snake.lua.html
create mode 100644 docs/examples/terminalsize.lua.html
create mode 100644 docs/modules/system.html
create mode 100644 docs/topics/02-development.md.html
create mode 100644 docs/topics/03-terminal.md.html
create mode 100644 rockspecs/luasystem-0.4.0-1.rockspec
diff --git a/CHANGELOG.md b/CHANGELOG.md
index eb85c94..a3544ea 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -28,6 +28,20 @@ The scope of what is covered by the version number excludes:
## Version history
+### Version 0.4.0, released 20-Jun-2024
+
+- Feat: `getconsoleflags` and `setconsoleflags` for getting/setting the current console configuration flags on Windows
+- Feat: `getconsolecp` and `setconsolecp` for getting/setting the console codepage on Windows
+- Feat: `getconsoleoutputcp` and `setconsoleoutputcp` for getting/setting the console output codepage on Windows
+- Feat: `tcgetattr` and `tcsetattr` for getting/setting the current console configuration flags on Posix
+- Feat: `getnonblock` and `setnonblock` for getting/setting the non-blocking flag on Posix
+- Feat: `bitflags`: a support feature for the above flag type controls to facilitate bit manipulation without resorting to binary operations (to also support PuC Lua 5.1)
+- Feat: `readkey` reads a keyboard input from `stdin` in a non-blocking way (utf8, also on Windows)
+- Feat: `readansi` reads a keyboard input from `stdin` in a non-blocking way, parses ansi and utf8 sequences
+- Feat: `termsize` gets the current terminal size in rows and columns
+- Feat: `utf8cwidth` and `utf8swidth` for getting the display width (in columns) of respectively a single utf8 character, or a utf8 string
+- Feat: helpers; `termbackup`, `termrestore`, `autotermrestore`, and `termwrap` for managing the many terminal settings on all platforms.
+
### Version 0.3.0, released 15-Dec-2023
- Feat: on Windows `sleep` now has a precision parameter
diff --git a/LICENSE.md b/LICENSE.md
index df2befb..16c86d0 100644
--- a/LICENSE.md
+++ b/LICENSE.md
@@ -1,6 +1,6 @@
# MIT License
-### Copyright (c) 2016-2023 Oscar Lim
+### Copyright (c) 2016-2024 Oscar Lim
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/docs/classes/bitflags.html b/docs/classes/bitflags.html
new file mode 100644
index 0000000..f063149
--- /dev/null
+++ b/docs/classes/bitflags.html
@@ -0,0 +1,305 @@
+
+
+
+
+ Lua-System docs
+
+
+
+
+
The bitflag object makes it easy to manipulate flags in a bitmask.
+
+
It has metamethods that do the hard work, adding flags sets them, substracting
+ unsets them. Comparing flags checks if all flags in the second set are also set
+ in the first set. The has method checks if all flags in the second set are
+ also set in the first set, but behaves slightly different.
+
+
Indexing allows checking values or setting them by bit index (eg. 0-7 for flags
+ in the first byte).
+
+
NOTE: unavailable flags (eg. Windows flags on a Posix system) should not be
+ omitted, but be assigned a value of 0. This is because the has method will
+ return false if the flags are checked and the value is 0.
+
+
See system.bitflag (the constructor) for extensive examples on usage.
Creates a new bitflag object from the given value.
+
+
+
+
+
+
+
+
Bit flags
+
+
+ Bitflag objects can be used to easily manipulate and compare bit flags.
+ These are primarily for use with the terminal functions, but can be used
+ in other places as well.
+
+
+
+
+ bitflag:has_all_of (subset)
+
+
+ Checks if all the flags in the given subset are set.
+If the flags to check has a value 0, it will always return false. So if there are flags that are
+unsupported on a platform, they can be set to 0 and the has_all_of function will
+return false if the flags are checked.
+
+
+
+
+
+ boolean
+ true if all the flags are set, false otherwise.
+
+
+
+
+
Usage:
+
+
local sys = require'system'
+local flags = sys.bitflag(12) -- b1100
+local myflags = sys.bitflag(15) -- b1111
+print(flags:has_all_of(myflags)) -- false, not all bits in myflags are set in flags
+print(myflags:has_all_of(flags)) -- true, all bits in flags are set in myflags
+
+
+
+
+
+ bitflag:has_any_of (subset)
+
+
+ Checks if any of the flags in the given subset are set.
+If the flags to check has a value 0, it will always return false. So if there are flags that are
+unsupported on a platform, they can be set to 0 and the has_any_of function will
+return false if the flags are checked.
+
+
+
+
+
+ boolean
+ true if any of the flags are set, false otherwise.
+
+
+
+
+
Usage:
+
+
local sys = require'system'
+local flags = sys.bitflag(12) -- b1100
+local myflags = sys.bitflag(7) -- b0111
+print(flags:has_any_of(myflags)) -- true, some bits in myflags are set in flags
+print(myflags:has_any_of(flags)) -- true, some bits in flags are set in myflags
+
+
+
+
+
+ bitflag:value ()
+
+
+ Retrieves the numeric value of the bitflag object.
+
+
+
+
Returns:
+
+
+ number
+ the numeric value of the bitflags.
+
+
+
+
+
+-- This example shows how to remove platform differences to create a
+-- cross-platform level playing field.
+
+local sys = require"system"
+
+
+
+if sys.windows then
+ -- Windows holds multiple copies of environment variables, to ensure getenv
+-- returns what setenv sets we need to use the system.getenv instead of
+-- os.getenv.
+os.getenv = sys.getenv -- luacheck: ignore
+
+ -- Set console output to UTF-8 encoding.
+ sys.setconsoleoutputcp(sys.CODEPAGE_UTF8)
+
+ -- Set up the terminal to handle ANSI escape sequences on Windows.
+if sys.isatty(io.stdout) then
+ sys.setconsoleflags(io.stdout, sys.getconsoleflags(io.stdout) + sys.COF_VIRTUAL_TERMINAL_PROCESSING)
+ end
+ if sys.isatty(io.stderr) then
+ sys.setconsoleflags(io.stderr, sys.getconsoleflags(io.stderr) + sys.COF_VIRTUAL_TERMINAL_PROCESSING)
+ end
+ if sys.isatty(io.stdin) then
+ sys.setconsoleflags(io.stdin, sys.getconsoleflags(io.stdout) + sys.ENABLE_VIRTUAL_TERMINAL_INPUT)
+ end
+
+
+else
+ -- On Posix, one can set a variable to an empty string, but on Windows, this
+-- will remove the variable from the environment. To make this consistent
+-- across platforms, we will remove the variable from the environment if the
+-- value is an empty string.
+local old_setenv = sys.setenv
+ function sys.setenv(name, value)
+ if value == ""then value = nilend
+ returnold_setenv(name, value)
+ end
+end
+
+
+
+
+
+generated by LDoc 1.5.0
+Last updated 2024-06-20 23:11:37
+
+local sys = require"system"
+
+-- Print the Windows Console flags for stdin
+sys.listconsoleflags(io.stdin)
+
+-- Print the Posix termios flags for stdin
+sys.listtermflags(io.stdin)
+
+
+
+
+
+generated by LDoc 1.5.0
+Last updated 2024-06-20 23:11:37
+
+local sys = require"system"
+
+print[[
+
+This example shows how to disable the "echo" of characters read to the console,
+useful for reading secrets from the user.
+
+]]
+
+--- Function to read from stdin without echoing the input (for secrets etc).
+-- It will (in a platform agnostic way) disable echo on the terminal, read the
+-- input, and then re-enable echo.
+-- @param ... Arguments to pass to io.stdin:read()
+-- @return the results of io.stdin:read(...)
+localfunctionread_secret(...)
+ local w_oldflags, p_oldflags
+
+ if sys.isatty(io.stdin) then
+ -- backup settings, configure echo flags
+ w_oldflags = sys.getconsoleflags(io.stdin)
+ p_oldflags = sys.tcgetattr(io.stdin)
+ -- set echo off to not show password on screen
+assert(sys.setconsoleflags(io.stdin, w_oldflags - sys.CIF_ECHO_INPUT))
+ assert(sys.tcsetattr(io.stdin, sys.TCSANOW, { lflag = p_oldflags.lflag - sys.L_ECHO }))
+ end
+
+ local secret, err = io.stdin:read(...)
+
+ -- restore settings
+if sys.isatty(io.stdin) then
+ io.stdout:write("\n") -- Add newline after reading the password
+ sys.setconsoleflags(io.stdin, w_oldflags)
+ sys.tcsetattr(io.stdin, sys.TCSANOW, p_oldflags)
+ end
+
+ return secret, err
+end
+
+
+
+-- Get username
+io.write("Username: ")
+local username = io.stdin:read("*l")
+
+-- Get the secret
+io.write("Password: ")
+local password = read_secret("*l")
+
+-- Get domainname
+io.write("Domain : ")
+local domain = io.stdin:read("*l")
+
+
+-- Print the results
+print("")
+print("Here's what we got:")
+print(" username: " .. username)
+print(" password: " .. password)
+print(" domain : " .. domain)
+
+
+
+
+
+generated by LDoc 1.5.0
+Last updated 2024-06-20 23:11:37
+
+--- An example class for reading a line of input from the user in a non-blocking way.
+-- It uses ANSI escape sequences to move the cursor and handle input.
+-- It can be used to read a line of input from the user, with a prompt.
+-- It can handle double-width UTF-8 characters.
+-- It can be used asynchroneously if system.sleep is patched to yield to a coroutine scheduler.
+
+local sys = require("system")
+
+
+-- Mapping of key-sequences to key-names
+local key_names = {
+ ["\27[C"] = "right",
+ ["\27[D"] = "left",
+ ["\127"] = "backspace",
+ ["\27[3~"] = "delete",
+ ["\27[H"] = "home",
+ ["\27[F"] = "end",
+ ["\27"] = "escape",
+ ["\9"] = "tab",
+ ["\27[Z"] = "shift-tab",
+}
+
+if sys.windows then
+ key_names["\13"] = "enter"
+else
+ key_names["\10"] = "enter"
+end
+
+
+-- Mapping of key-names to key-sequences
+local key_sequences = {}
+for k, v inpairs(key_names) do
+ key_sequences[v] = k
+end
+
+
+-- bell character
+localfunctionbell()
+ io.write("\7")
+ io.flush()
+end
+
+
+-- generate string to move cursor horizontally
+-- positive goes right, negative goes left
+localfunctioncursor_move_horiz(n)
+ if n == 0then
+ return""
+ end
+ return"\27[" .. (n > 0and n or -n) .. (n > 0and"C"or"D")
+end
+
+
+-- -- generate string to move cursor vertically
+-- -- positive goes down, negative goes up
+-- local function cursor_move_vert(n)
+-- if n == 0 then
+-- return ""
+-- end
+-- return "\27[" .. (n > 0 and n or -n) .. (n > 0 and "B" or "A")
+-- end
+
+
+-- -- log to the line above the current line
+-- local function log(...)
+-- local arg = { n = select("#", ...), ...}
+-- for i = 1, arg.n do
+-- arg[i] = tostring(arg[i])
+-- end
+-- arg = " " .. table.concat(arg, " ") .. " "
+
+-- io.write(cursor_move_vert(-1), arg, cursor_move_vert(1), cursor_move_horiz(-#arg))
+-- end
+
+
+-- UTF8 character size in bytes
+-- @tparam number b the byte value of the first byte of a UTF8 character
+localfunctionutf8size(b)
+ return b < 128and1or b < 224and2or b < 240and3or b < 248and4
+end
+
+
+
+local utf8parse do
+ local utf8_value_mt = {
+ __tostring = function(self)
+ returntable.concat(self, "")
+ end,
+ }
+
+ -- Parses a UTF8 string into list of individual characters.
+-- key 'chars' gets the length in UTF8 characters, whilst # returns the length
+-- for display (to handle double-width UTF8 chars).
+-- in the list the double-width characters are followed by an empty string.
+-- @tparam string s the UTF8 string to parse
+-- @treturn table the list of characters
+functionutf8parse(s)
+ local t = setmetatable({ chars = 0 }, utf8_value_mt)
+ local i = 1
+ while i <= #s do
+ local b = s:byte(i)
+ local w = utf8size(b)
+ local char = s:sub(i, i + w - 1)
+ t[#t + 1] = char
+ t.chars = t.chars + 1
+ if sys.utf8cwidth(char) == 2then
+ -- double width character, add empty string to keep the length of the
+-- list the same as the character width on screen
+ t[#t + 1] = ""
+ end
+ i = i + w
+ end
+ return t
+ end
+end
+
+
+
+-- inline tests for utf8parse
+-- do
+-- local t = utf8parse("a你b好c")
+-- assert(t[1] == "a")
+-- assert(t[2] == "你") -- double width
+-- assert(t[3] == "")
+-- assert(t[4] == "b")
+-- assert(t[5] == "好") -- double width
+-- assert(t[6] == "")
+-- assert(t[7] == "c")
+-- assert(#t == 7) -- size as displayed
+-- end
+
+
+
+-- readline class
+
+local readline = {}
+readline.__index = readline
+
+
+--- Create a new readline object.
+-- @tparam table opts the options for the readline object
+-- @tparam[opt=""] string opts.prompt the prompt to display
+-- @tparam[opt=80] number opts.max_length the maximum length of the input (in characters, not bytes)
+-- @tparam[opt=""] string opts.value the default value
+-- @tparam[opt=#value] number opts.position of the cursor in the input
+-- @tparam[opt={"\10"/"\13"}] table opts.exit_keys an array of keys that will cause the readline to exit
+-- @treturn readline the new readline object
+function readline.new(opts)
+ local value = utf8parse(opts.value or"")
+ local prompt = utf8parse(opts.prompt or"")
+ local pos = math.floor(opts.position or (#value + 1))
+ pos = math.max(math.min(pos, (#value + 1)), 1)
+ local len = math.floor(opts.max_length or80)
+ if len < 1then
+ error("max_length must be at least 1", 2)
+ end
+
+ if value.chars > len then
+ error("value is longer than max_length", 2)
+ end
+
+ local exit_keys = {}
+ for _, key inipairs(opts.exit_keys or {}) do
+ exit_keys[key] = true
+ end
+ if exit_keys[1] == nilthen
+ -- nothing provided, default to Enter-key
+ exit_keys[1] = key_sequences.enter
+ end
+
+ local self = {
+ value = value, -- the default value
+ max_length = len, -- the maximum length of the input
+ prompt = prompt, -- the prompt to display
+ position = pos, -- the current position in the input
+ drawn_before = false, -- if the prompt has been drawn
+ exit_keys = exit_keys, -- the keys that will cause the readline to exit
+ }
+
+ setmetatable(self, readline)
+ return self
+end
+
+
+
+-- draw the prompt and the input value, and position the cursor.
+localfunctiondraw(self, redraw)
+ if redraw ornot self.drawn_before then
+ -- we are at start of prompt
+ self.drawn_before = true
+ else
+ -- we are at current cursor position, move to start of prompt
+io.write(cursor_move_horiz(-(#self.prompt + self.position)))
+ end
+ -- write prompt & value
+io.write(tostring(self.prompt) .. tostring(self.value))
+ -- clear remainder of input size
+io.write(string.rep(" ", self.max_length - self.value.chars))
+ io.write(cursor_move_horiz(-(self.max_length - self.value.chars)))
+ -- move to cursor position
+io.write(cursor_move_horiz(-(#self.value + 1 - self.position)))
+ io.flush()
+end
+
+
+local handle_key do-- keyboard input handler
+
+ local key_handlers
+ key_handlers = {
+ left = function(self)
+ if self.position == 1then
+ bell()
+ return
+ end
+
+ local new_pos = self.position - 1
+ while self.value[new_pos] == ""do-- skip empty strings; double width chars
+ new_pos = new_pos - 1
+ end
+
+ io.write(cursor_move_horiz(-(self.position - new_pos)))
+ io.flush()
+ self.position = new_pos
+ end,
+
+ right = function(self)
+ if self.position == #self.value + 1then
+ bell()
+ return
+ end
+
+ local new_pos = self.position + 1
+ while self.value[new_pos] == ""do-- skip empty strings; double width chars
+ new_pos = new_pos + 1
+ end
+
+ io.write(cursor_move_horiz(new_pos - self.position))
+ io.flush()
+ self.position = new_pos
+ end,
+
+ backspace = function(self)
+ if self.position == 1then
+ bell()
+ return
+ end
+
+ while self.value[self.position - 1] == ""do-- remove empty strings; double width chars
+io.write(cursor_move_horiz(-1))
+ self.position = self.position - 1
+ table.remove(self.value, self.position)
+ end
+ -- remove char itself
+io.write(cursor_move_horiz(-1))
+ self.position = self.position - 1
+ table.remove(self.value, self.position)
+ self.value.chars = self.value.chars - 1
+ draw(self)
+ end,
+
+ home = function(self)
+ local new_pos = 1
+ io.write(cursor_move_horiz(new_pos - self.position))
+ self.position = new_pos
+ end,
+
+ ["end"] = function(self)
+ local new_pos = #self.value + 1
+ io.write(cursor_move_horiz(new_pos - self.position))
+ self.position = new_pos
+ end,
+
+ delete = function(self)
+ if self.position > #self.value then
+ bell()
+ return
+ end
+
+ key_handlers.right(self)
+ key_handlers.backspace(self)
+ end,
+ }
+
+
+ -- handles a single input key/ansi-sequence.
+-- @tparam string key the key or ansi-sequence (from system.readansi)
+-- @tparam string keytype the type of the key, either "char" or "ansi" (from system.readansi)
+-- @treturn string status the status of the key handling, either "ok", "exit_key" or an error message
+functionhandle_key(self, key, keytype)
+ if self.exit_keys[key] then
+ -- registered exit key
+return"exit_key"
+ end
+
+ local handler = key_handlers[key_names[key] ortrue ]
+ if handler then
+ handler(self)
+ return"ok"
+ end
+
+ if keytype == "ansi"then
+ -- we got an ansi sequence, but dunno how to handle it, ignore
+-- print("unhandled ansi: ", key:sub(2,-1), string.byte(key, 1, -1))
+bell()
+ return"ok"
+ end
+
+ -- just a single key
+if key < " "then
+ -- control character
+bell()
+ return"ok"
+ end
+
+ if self.value.chars >= self.max_length then
+ bell()
+ return"ok"
+ end
+
+ -- insert the key into the value
+if sys.utf8cwidth(key) == 2then
+ -- double width character, insert empty string after it
+table.insert(self.value, self.position, "")
+ table.insert(self.value, self.position, key)
+ self.position = self.position + 2
+ io.write(cursor_move_horiz(2))
+ else
+ table.insert(self.value, self.position, key)
+ self.position = self.position + 1
+ io.write(cursor_move_horiz(1))
+ end
+ self.value.chars = self.value.chars + 1
+ draw(self)
+ return"ok"
+ end
+end
+
+
+
+--- Get_size returns the maximum size of the input box (prompt + input).
+-- The size is in rows and columns. Columns is determined by
+-- the prompt and the max_length * 2 (characters can be double-width).
+-- @treturn number the number of rows (always 1)
+-- @treturn number the number of columns
+function readline:get_size()
+ return1, #self.prompt + self.max_length * 2
+end
+
+
+
+--- Get coordinates of the cursor in the input box (prompt + input).
+-- The coordinates are 1-based. They are returned as row and column, within the
+-- size as reported by get_size.
+-- @treturn number the row of the cursor (always 1)
+-- @treturn number the column of the cursor
+function readline:get_cursor()
+ return1, #self.prompt + self.position
+end
+
+
+
+--- Set the coordinates of the cursor in the input box (prompt + input).
+-- The coordinates are 1-based. They are expected to be within the
+-- size as reported by get_size, and beyond the prompt.
+-- If the position is invalid, it will be corrected.
+-- Use the results to check if the position was adjusted.
+-- @tparam number row the row of the cursor (always 1)
+-- @tparam number col the column of the cursor
+-- @return results of get_cursor
+function readline:set_cursor(row, col)
+ local l_prompt = #self.prompt
+ local l_value = #self.value
+
+ if col < l_prompt + 1then
+ col = l_prompt + 1
+ elseif col > l_prompt + l_value + 1then
+ col = l_prompt + l_value + 1
+ end
+
+ while self.value[col - l_prompt] == ""do
+ col = col - 1-- on an empty string, so move back to start of double-width char
+end
+
+ local new_pos = col - l_prompt
+
+ cursor_move_horiz(self.position - new_pos)
+ io.flush()
+
+ self.position = new_pos
+ return self:get_cursor()
+end
+
+
+
+--- Read a line of input from the user.
+-- It will first print the prompt and then wait for input. Ensure the cursor
+-- is at the correct position before calling this function. This function will
+-- do all cursor movements in a relative way.
+-- Can be called again after an exit-key or timeout has occurred. Just make sure
+-- the cursor is at the same position where is was when it returned the last time.
+-- Alternatively the cursor can be set to the position of the prompt (the position
+-- the cursor was in before the first call), and the parameter redraw can be set
+-- to true.
+-- @tparam[opt=math.huge] number timeout the maximum time to wait for input in seconds
+-- @tparam[opt=false] boolean redraw if true the prompt will be redrawn (cursor must be at prompt position!)
+-- @treturn[1] string the input string as entered the user
+-- @treturn[1] string the exit-key used to exit the readline (see new)
+-- @treturn[2] nil when input is incomplete
+-- @treturn[2] string error message, the reason why the input is incomplete, "timeout", or an error reading a key
+function readline:__call(timeout, redraw)
+ draw(self, redraw)
+ timeout = timeout ormath.huge
+ local timeout_end = sys.gettime() + timeout
+
+ whiletruedo
+ local key, keytype = sys.readansi(timeout_end - sys.gettime())
+ ifnot key then
+ -- error or timeout
+returnnil, keytype
+ end
+
+ local status = handle_key(self, key, keytype)
+ if status == "exit_key"then
+ returntostring(self.value), key
+
+ elseif status ~= "ok"then
+ error("unknown status received: " .. tostring(status))
+ end
+ end
+end
+
+
+
+-- return readline -- normally we'd return here, but for the example we continue
+
+
+
+
+local backup = sys.termbackup()
+
+-- setup Windows console to handle ANSI processing
+sys.setconsoleflags(io.stdout, sys.getconsoleflags(io.stdout) + sys.COF_VIRTUAL_TERMINAL_PROCESSING)
+sys.setconsoleflags(io.stdin, sys.getconsoleflags(io.stdin) + sys.CIF_VIRTUAL_TERMINAL_INPUT)
+-- set output to UTF-8
+sys.setconsoleoutputcp(sys.CODEPAGE_UTF8)
+
+-- setup Posix terminal to disable canonical mode and echo
+sys.tcsetattr(io.stdin, sys.TCSANOW, {
+ lflag = sys.tcgetattr(io.stdin).lflag - sys.L_ICANON - sys.L_ECHO,
+})
+-- setup stdin to non-blocking mode
+sys.setnonblock(io.stdin, true)
+
+
+local rl = readline.new{
+ prompt = "Enter something: ",
+ max_length = 60,
+ value = "Hello, 你-好 World 🚀!",
+ -- position = 2,
+ exit_keys = {key_sequences.enter, "\27", "\t", "\27[Z"}, -- enter, escape, tab, shift-tab
+}
+
+
+local result, key = rl()
+print("") -- newline after input, to move cursor down from the input line
+print("Result (string): '" .. result .. "'")
+print("Result (bytes):", result:byte(1,-1))
+print("Exit-Key (bytes):", key:byte(1,-1))
+
+
+-- Clean up afterwards
+sys.termrestore(backup)
+
+
+
+
+
+generated by LDoc 1.5.0
+Last updated 2024-06-20 23:11:37
+
+local sys = require"system"
+
+print[[
+
+This example will draw a snake like spiral on the screen. Showing ANSI escape
+codes for moving the cursor around.
+
+]]
+
+-- backup term settings with auto-restore on exit
+sys.autotermrestore()
+
+-- setup Windows console to handle ANSI processing
+sys.setconsoleflags(io.stdout, sys.getconsoleflags(io.stdout) + sys.COF_VIRTUAL_TERMINAL_PROCESSING)
+
+-- start drawing the spiral.
+-- start from current pos, then right, then up, then left, then down, and again.
+local x, y = 1, 1-- current position
+local dx, dy = 1, 0-- direction after each step
+local wx, wy = 30, 30-- width and height of the room
+local mx, my = 1, 1-- margin
+
+-- commands to move the cursor
+local move_left = "\27[1D"
+local move_right = "\27[1C"
+local move_up = "\27[1A"
+local move_down = "\27[1B"
+
+-- create room: 30 empty lines
+print(("\n"):rep(wy))
+local move = move_right
+
+while wx > 0and wy > 0do
+ sys.sleep(0.01) -- slow down the drawing a little
+io.write("*" .. move_left .. move )
+ io.flush()
+ x = x + dx
+ y = y + dy
+
+ if x > wx and move == move_right then
+ -- end of move right
+ dx = 0
+ dy = 1
+ move = move_up
+ wy = wy - 1
+ my = my + 1
+ elseif y > wy and move == move_up then
+ -- end of move up
+ dx = -1
+ dy = 0
+ move = move_left
+ wx = wx - 1
+ mx = mx + 1
+ elseif x < mx and move == move_left then
+ -- end of move left
+ dx = 0
+ dy = -1
+ move = move_down
+ wy = wy - 1
+ my = my + 1
+ elseif y < my and move == move_down then
+ -- end of move down
+ dx = 1
+ dy = 0
+ move = move_right
+ wx = wx - 1
+ mx = mx + 1
+ end
+end
+
+io.write(move_down:rep(15))
+print("\nDone!")
+
+
+
+
+
+generated by LDoc 1.5.0
+Last updated 2024-06-20 23:11:37
+
NOTE: Windows has multiple copies of environment variables. For this reason,
-the setenv function will not work with Lua's os.getenv on Windows. If you want
-to use setenv then consider patching os.getenv with this implementation of getenv.
-
-
-
Parameters:
-
-
name
- string
- name of the environment variable
-
-
-
-
Returns:
-
-
- string or nil
- value of the environment variable, or nil if the variable is not set
-
-
-
-
-
-
-
-
- getenvs ()
-
-
- Returns a table with all environment variables.
-
-
-
-
Returns:
-
-
- table
- table with all environment variables and their values
-
-
-
-
-
-
-
-
- setenv (name[, value])
-
-
- Sets an environment variable.
-
-
NOTE: Windows has multiple copies of environment variables. For this reason, the
-setenv function will not work with Lua's os.getenv on Windows. If you want to use
-it then consider patching os.getenv with the implementation of system.getenv.
-
-
-
Parameters:
-
-
name
- string
- name of the environment variable
-
-
value
- string
- value of the environment variable, if nil the variable will be deleted (on
-Windows, setting an empty string, will also delete the variable)
- (optional)
-
-
-
-
Returns:
-
-
- boolean
- success
-
-
-
-
-
-
-
-
random Functions
-
-
-
-
- random ([length=1])
-
-
- Generate random bytes.
-This uses CryptGenRandom() on Windows, and /dev/urandom on other platforms. It will return the
-requested number of bytes, or an error, never a partial result.
-
-
-
Parameters:
-
-
length
- int
- number of bytes to get
- (default 1)
-
NOTE: Windows has multiple copies of environment variables. For this reason,
+the setenv function will not work with Lua's os.getenv on Windows. If you want
+to use setenv then consider patching os.getenv with this implementation of getenv.
+
+
+
Parameters:
+
+
name
+ string
+ name of the environment variable
+
+
+
+
Returns:
+
+
+ string or nil
+ value of the environment variable, or nil if the variable is not set
+
+
+
+
+
+
+
+
+ getenvs ()
+
+
+ Returns a table with all environment variables.
+
+
+
+
Returns:
+
+
+ table
+ table with all environment variables and their values
+
+
+
+
+
+
+
+
+ setenv (name[, value])
+
+
+ Sets an environment variable.
+
+
NOTE: Windows has multiple copies of environment variables. For this reason, the
+setenv function will not work with Lua's os.getenv on Windows. If you want to use
+it then consider patching os.getenv with the implementation of system.getenv.
+
+
+
Parameters:
+
+
name
+ string
+ name of the environment variable
+
+
value
+ string
+ value of the environment variable, if nil the variable will be deleted (on
+Windows, setting an empty string, will also delete the variable)
+ (optional)
+
+
+
+
Returns:
+
+
+ boolean
+ success
+
+
+
+
+
+
+
+
Random
+
+
+
+
+ random ([length=1])
+
+
+ Generate random bytes.
+This uses CryptGenRandom() on Windows, and /dev/urandom on other platforms. It will return the
+requested number of bytes, or an error, never a partial result.
+
+
+
Parameters:
+
+
length
+ int
+ number of bytes to get
+ (default 1)
+
+ Reads a key from the console non-blocking. This function should not be called
+directly, but through the system.readkey or system.readansi functions. It
+will return the next byte from the input stream, or nil if no key was pressed.
+
+
On Posix, io.stdin must be set to non-blocking mode using setnonblock
+and canonical mode must be turned off using tcsetattr,
+before calling this function. Otherwise it will block. No conversions are
+done on Posix, so the byte read is returned as-is.
+
+
On Windows this reads a wide character and converts it to UTF-8. Multi-byte
+sequences will be buffered internally and returned one byte at a time.
+
+
+
+
Returns:
+
+
+ integer
+ the byte read from the input stream
+
+
+ Backs up terminal settings and restores them on application exit.
+ Calls termbackup to back up terminal settings and sets up a GC method to
+ automatically restore them on application exit (also works on Lua 5.1).
+
+
+
+
+ Gets the current console code page (Windows).
+
+
+
+
Returns:
+
+
+ int
+ the current code page (always 65001 on Posix systems)
+
+
+
+
+
+
+
+
+ getconsoleflags (file)
+
+
+ Gets console flags (Windows).
+The CIF_ and COF_ constants are available on the module table. Where CIF are the
+input flags (for use with io.stdin) and COF are the output flags (for use with
+io.stdout/io.stderr).
+
+
local system = require('system')
+
+local flags = system.getconsoleflags(io.stdout)
+print("Current stdout flags:", tostring(flags))
+
+if flags:has_all_of(system.COF_VIRTUAL_TERMINAL_PROCESSING + system.COF_PROCESSED_OUTPUT) then
+ print("Both flags are set")
+else
+ print("At least one flag is not set")
+end
+
+
+
+
+
+ getconsoleoutputcp ()
+
+
+ Gets the current console output code page (Windows).
+
+
+
+
Returns:
+
+
+ int
+ the current code page (always 65001 on Posix systems)
+
+
+
+
+
+
+
+
+ getnonblock (fd)
+
+
+ Gets non-blocking mode status for a file (Posix).
+
+
+
+
+
+ boolean
+ true if the file is a tty
+
+
+
+
+
Usage:
+
+
local system = require('system')
+if system.isatty(io.stdin) then
+ -- enable ANSI coloring etc on Windows, does nothing in Posix.
+local flags = system.getconsoleflags(io.stdout)
+ system.setconsoleflags(io.stdout, flags + sys.COF_VIRTUAL_TERMINAL_PROCESSING)
+end
+
+
+
+
+
+ listconsoleflags (fh)
+
+
+ Debug function for console flags (Windows).
+ Pretty prints the current flags set for the handle.
+
+
+
-- Print the flags for stdin/out/err
+system.listconsoleflags(io.stdin)
+system.listconsoleflags(io.stdout)
+system.listconsoleflags(io.stderr)
+
+
+
+
+
+ readansi (timeout)
+
+
+ Reads a single key, if it is the start of ansi escape sequence then it reads
+ the full sequence. The key can be a multi-byte string in case of multibyte UTF-8 character.
+ This function uses system.readkey, and hence system.sleep to wait until either a key is
+ available or the timeout is reached.
+ It returns immediately if a key is available or if timeout is less than or equal to 0.
+ In case of an ANSI sequence, it will return the full sequence as a string.
+
+
+
Parameters:
+
+
timeout
+ number
+ the timeout in seconds.
+
+
+
+
Returns:
+
+
+ string
+ the character that was received (can be multi-byte), or a complete ANSI sequence
+
+ string
+ the type of input: "char" for a single key, "ansi" for an ANSI sequence
+
+
Or
+
+
+ nil
+ in case of an error
+
+ string
+ error message; "timeout" if the timeout was reached.
+
+ string
+ partial result in case of an error while reading a sequence, the sequence so far.
+
+
+
+
+
+
+
+
+ readkey (timeout)
+
+
+ Reads a single byte from the console, with a timeout.
+ This function uses system.sleep to wait until either a byte is available or the timeout is reached.
+ The sleep period is exponentially backing off, starting at 0.0125 seconds, with a maximum of 0.2 seconds.
+ It returns immediately if a byte is available or if timeout is less than or equal to 0.
+
+
Using system.readansi is preferred over this function. Since this function can leave stray/invalid
+ byte-sequences in the input buffer, while system.readansi reads full ANSI and UTF8 sequences.
+
+
+
Parameters:
+
+
timeout
+ number
+ the timeout in seconds.
+
+
+
+
Returns:
+
+
+ byte
+ the byte value that was read.
+
+
Or
+
+
+ nil
+ if no key was read
+
+ string
+ error message; "timeout" if the timeout was reached.
+
+
+
+
+
+
+
+
+ setconsolecp (cp)
+
+
+ Sets the current console code page (Windows).
+
+
+
+
+
+ bool
+ true on success (always true on Posix systems)
+
+
+
+
+
+
+
+
+ setconsoleflags (file, bitflags)
+
+
+ Sets the console flags (Windows).
+The CIF_ and COF_ constants are available on the module table. Where CIF are the
+input flags (for use with io.stdin) and COF are the output flags (for use with
+io.stdout/io.stderr).
+
+
local system = require('system')
+system.listconsoleflags(io.stdout) -- List all the available flags and their current status
+
+local flags = system.getconsoleflags(io.stdout)
+assert(system.setconsoleflags(io.stdout,
+ flags + system.COF_VIRTUAL_TERMINAL_PROCESSING)
+
+system.listconsoleflags(io.stdout) -- List again to check the differences
+
+
+
+
+
+ setconsoleoutputcp (cp)
+
+
+ Sets the current console output code page (Windows).
+
+
+
local system = require('system')
+
+local status = assert(tcgetattr(io.stdin))
+if status.iflag:has_all_of(system.I_IGNBRK) then
+ print("Ignoring break condition")
+end
+
+
+
+
+
+ tcsetattr (fd, actions, termios)
+
+
+ Set termios state (Posix).
+This function will set the flags as given.
+
+
The I_, O_, and L_ constants are available on the module table. They are the respective
+flags for the iflags, oflags, and lflags bitmasks.
+
+
To see flag status and constant names check listtermflags. For their meaning check
+the manpage.
+
+
Note: only iflag, oflag, and lflag are supported at the moment. The other fields are ignored.
+
+
+
local system = require('system')
+
+local status = assert(tcgetattr(io.stdin))
+ifnot status.lflag:has_all_of(system.L_ECHO) then
+ -- if echo is off, turn echoing newlines on
+tcsetattr(io.stdin, system.TCSANOW, { lflag = status.lflag + system.L_ECHONL }))
+end
+
+
+
+
+
+ termbackup ()
+
+
+ Returns a backup of terminal settings for stdin/out/err.
+ Handles terminal/console flags, Windows codepage, and non-block flags on the streams.
+ Backs up terminal/console flags only if a stream is a tty.
+
+
+
+
Returns:
+
+
+ table with backup of terminal settings
+
+
+
+
+
+
+
+
+ termrestore (backup)
+
+
+ Restores terminal settings from a backup
+
+
+
Parameters:
+
+
backup
+ table
+ the backup of terminal settings, see termbackup.
+
+
+
+
Returns:
+
+
+ boolean
+ true
+
+
+
+
+
+
+
+
+ termsize ()
+
+
+ Get the size of the terminal in rows and columns.
+
+
+
+
+ Wraps a function to automatically restore terminal settings upon returning.
+ Calls termbackup before calling the function and termrestore after.
+
+
+
Parameters:
+
+
f
+ function
+ function to wrap
+
+
+
+
Returns:
+
+
+ function
+ wrapped function
+
+
+
+
+
+
+
+
+ utf8cwidth (utf8_char)
+
+
+ Get the width of a utf8 character for terminal display.
+
+
+
Parameters:
+
+
utf8_char
+ string
+ the utf8 character to check, only the width of the first character will be returned
+
+
+
+
Returns:
+
+
+ int
+ the display width in columns of the first character in the string (0 for an empty string)
+
+