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) --- 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 +++++++ 8 files changed, 1460 insertions(+) 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 (limited to 'docs/examples') diff --git a/docs/examples/compat.lua.html b/docs/examples/compat.lua.html new file mode 100644 index 0000000..a0abafe --- /dev/null +++ b/docs/examples/compat.lua.html @@ -0,0 +1,119 @@ + + + + + Lua-System docs + + + + +
+ +
+ +
+
+
+ + +
+ + + + + + +
+ +

compat.lua

+
+-- 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 = nil end
+    return old_setenv(name, value)
+  end
+end
+ + +
+
+
+generated by LDoc 1.5.0 +Last updated 2024-06-20 23:11:37 +
+
+ + diff --git a/docs/examples/flag_debugging.lua.html b/docs/examples/flag_debugging.lua.html new file mode 100644 index 0000000..38f506a --- /dev/null +++ b/docs/examples/flag_debugging.lua.html @@ -0,0 +1,87 @@ + + + + + Lua-System docs + + + + +
+ +
+ +
+
+
+ + +
+ + + + + + +
+ +

flag_debugging.lua

+
+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 +
+
+ + diff --git a/docs/examples/password_input.lua.html b/docs/examples/password_input.lua.html new file mode 100644 index 0000000..4234fbb --- /dev/null +++ b/docs/examples/password_input.lua.html @@ -0,0 +1,139 @@ + + + + + Lua-System docs + + + + +
+ +
+ +
+
+
+ + +
+ + + + + + +
+ +

password_input.lua

+
+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(...)
+local function read_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 +
+
+ + diff --git a/docs/examples/read.lua.html b/docs/examples/read.lua.html new file mode 100644 index 0000000..c7697d3 --- /dev/null +++ b/docs/examples/read.lua.html @@ -0,0 +1,150 @@ + + + + + Lua-System docs + + + + +
+ +
+ +
+
+
+ + +
+ + + + + + +
+ +

read.lua

+
+local sys = require "system"
+
+print [[
+
+This example shows how to do a non-blocking read from the cli.
+
+]]
+
+-- setup Windows console to handle ANSI processing
+local of_in = sys.getconsoleflags(io.stdin)
+local of_out = sys.getconsoleflags(io.stdout)
+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)
+
+-- setup Posix terminal to use non-blocking mode, and disable line-mode
+local of_attr = sys.tcgetattr(io.stdin)
+local of_block = sys.getnonblock(io.stdin)
+sys.setnonblock(io.stdin, true)
+sys.tcsetattr(io.stdin, sys.TCSANOW, {
+  lflag = of_attr.lflag - sys.L_ICANON - sys.L_ECHO, -- disable canonical mode and echo
+})
+
+-- cursor sequences
+local get_cursor_pos = "\27[6n"
+
+
+
+print("Press a key, or 'A' to get cursor position, 'ESC' to exit")
+while true do
+  local key, keytype
+
+  -- wait for a key
+  while not key do
+    key, keytype = sys.readansi(math.huge)
+  end
+
+  if key == "A" then io.write(get_cursor_pos); io.flush() end
+
+  -- check if we got a key or ANSI sequence
+  if keytype == "char" then
+    -- just a key
+    local b = key:byte()
+    if b < 32 then
+      key = "." -- replace control characters with a simple "." to not mess up the screen
+    end
+
+    print("you pressed: " .. key .. " (" .. b .. ")")
+    if b == 27 then
+      print("Escape pressed, exiting")
+      break
+    end
+
+  elseif keytype == "ansi" then
+    -- we got an ANSI sequence
+    local seq = { key:byte(1, #key) }
+    print("ANSI sequence received: " .. key:sub(2,-1), "(bytes: " .. table.concat(seq, ", ")..")")
+
+  else
+    print("unknown key type received: " .. tostring(keytype))
+  end
+end
+
+
+
+-- Clean up afterwards
+sys.setnonblock(io.stdin, false)
+sys.setconsoleflags(io.stdout, of_out)
+sys.setconsoleflags(io.stdin, of_in)
+sys.tcsetattr(io.stdin, sys.TCSANOW, of_attr)
+sys.setnonblock(io.stdin, of_block)
+ + +
+
+
+generated by LDoc 1.5.0 +Last updated 2024-06-20 23:11:37 +
+
+ + diff --git a/docs/examples/readline.lua.html b/docs/examples/readline.lua.html new file mode 100644 index 0000000..7895a81 --- /dev/null +++ b/docs/examples/readline.lua.html @@ -0,0 +1,552 @@ + + + + + Lua-System docs + + + + +
+ +
+ +
+
+
+ + +
+ + + + + + +
+ +

readline.lua

+
+--- 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 in pairs(key_names) do
+  key_sequences[v] = k
+end
+
+
+-- bell character
+local function bell()
+  io.write("\7")
+  io.flush()
+end
+
+
+-- generate string to move cursor horizontally
+-- positive goes right, negative goes left
+local function cursor_move_horiz(n)
+  if n == 0 then
+    return ""
+  end
+  return "\27[" .. (n > 0 and n or -n) .. (n > 0 and "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
+local function utf8size(b)
+  return b < 128 and 1 or b < 224 and 2 or b < 240 and 3 or b < 248 and 4
+end
+
+
+
+local utf8parse do
+  local utf8_value_mt = {
+    __tostring = function(self)
+      return table.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
+  function utf8parse(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) == 2 then
+        -- 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 or 80)
+  if len < 1 then
+    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 in ipairs(opts.exit_keys or {}) do
+    exit_keys[key] = true
+  end
+  if exit_keys[1] == nil then
+    -- 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.
+local function draw(self, redraw)
+  if redraw or not 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 == 1 then
+        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 + 1 then
+        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 == 1 then
+        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
+  function handle_key(self, key, keytype)
+    if self.exit_keys[key] then
+      -- registered exit key
+      return "exit_key"
+    end
+
+    local handler = key_handlers[key_names[key] or true ]
+    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) == 2 then
+      -- 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()
+  return 1, #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()
+  return 1, #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 + 1 then
+    col = l_prompt + 1
+  elseif col > l_prompt + l_value + 1 then
+    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 or math.huge
+  local timeout_end = sys.gettime() + timeout
+
+  while true do
+    local key, keytype = sys.readansi(timeout_end - sys.gettime())
+    if not key then
+      -- error or timeout
+      return nil, keytype
+    end
+
+    local status = handle_key(self, key, keytype)
+    if status == "exit_key" then
+      return tostring(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 +
+
+ + diff --git a/docs/examples/spinner.lua.html b/docs/examples/spinner.lua.html new file mode 100644 index 0000000..181cbe5 --- /dev/null +++ b/docs/examples/spinner.lua.html @@ -0,0 +1,144 @@ + + + + + Lua-System docs + + + + +
+ +
+ +
+
+
+ + +
+ + + + + + +
+ +

spinner.lua

+
+local sys = require("system")
+
+print [[
+
+An example to display a spinner, whilst a long running task executes.
+
+]]
+
+
+-- start make backup, to auto-restore on exit
+sys.autotermrestore()
+-- configure console
+sys.setconsoleflags(io.stdin, sys.getconsoleflags(io.stdin) - sys.CIF_ECHO_INPUT - sys.CIF_LINE_INPUT)
+local of = sys.tcgetattr(io.stdin)
+sys.tcsetattr(io.stdin, sys.TCSANOW, { lflag = of.lflag - sys.L_ICANON - sys.L_ECHO })
+sys.setnonblock(io.stdin, true)
+
+
+
+local function hideCursor()
+  io.write("\27[?25l")
+  io.flush()
+end
+
+local function showCursor()
+  io.write("\27[?25h")
+  io.flush()
+end
+
+local function left(n)
+  io.write("\27[",n or 1,"D")
+  io.flush()
+end
+
+
+
+local spinner do
+  local spin = [[|/-\]]
+  local i = 1
+  spinner = function()
+    hideCursor()
+    io.write(spin:sub(i, i))
+    left()
+    i = i + 1
+    if i > #spin then i = 1 end
+
+    if sys.readkey(0) ~= nil then
+      while sys.readkey(0) ~= nil do end -- consume keys pressed
+      io.write(" ");
+      left()
+      showCursor()
+      return true
+    else
+      return false
+    end
+  end
+end
+
+io.stdout:write("press any key to stop the spinner... ")
+while not spinner() do
+  sys.sleep(0.1)
+end
+
+print("Done!")
+ + +
+
+
+generated by LDoc 1.5.0 +Last updated 2024-06-20 23:11:37 +
+
+ + diff --git a/docs/examples/spiral_snake.lua.html b/docs/examples/spiral_snake.lua.html new file mode 100644 index 0000000..7ebb838 --- /dev/null +++ b/docs/examples/spiral_snake.lua.html @@ -0,0 +1,152 @@ + + + + + Lua-System docs + + + + +
+ +
+ +
+
+
+ + +
+ + + + + + +
+ +

spiral_snake.lua

+
+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 > 0 and wy > 0 do
+  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 +
+
+ + diff --git a/docs/examples/terminalsize.lua.html b/docs/examples/terminalsize.lua.html new file mode 100644 index 0000000..d7f902d --- /dev/null +++ b/docs/examples/terminalsize.lua.html @@ -0,0 +1,117 @@ + + + + + Lua-System docs + + + + +
+ +
+ +
+
+
+ + +
+ + + + + + +
+ +

terminalsize.lua

+
+local sys = require("system")
+
+sys.autotermrestore()  -- set up auto restore of terminal settings on exit
+
+-- 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)
+
+-- setup Posix to disable canonical mode and echo
+local of_attr = sys.tcgetattr(io.stdin)
+sys.setnonblock(io.stdin, true)
+sys.tcsetattr(io.stdin, sys.TCSANOW, {
+  lflag = of_attr.lflag - sys.L_ICANON - sys.L_ECHO, -- disable canonical mode and echo
+})
+
+
+-- generate string to move cursor horizontally
+-- positive goes right, negative goes left
+local function cursor_move_horiz(n)
+  if n == 0 then
+    return ""
+  end
+  return "\27[" .. (n > 0 and n or -n) .. (n > 0 and "C" or "D")
+end
+
+
+local rows, cols
+print("Change the terminal window size, press any key to exit")
+while not sys.readansi(0.2) do  -- use readansi to not leave stray bytes in the input buffer
+  local nrows, ncols = sys.termsize()
+  if rows ~= nrows or cols ~= ncols then
+    rows, cols = nrows, ncols
+    local text = "Terminal size: " .. rows .. "x" .. cols .. "     "
+    io.write(text .. cursor_move_horiz(-#text))
+    io.flush()
+  end
+end
+ + +
+
+
+generated by LDoc 1.5.0 +Last updated 2024-06-20 23:11:37 +
+
+ + -- cgit v1.2.3-55-g6feb