summaryrefslogtreecommitdiff
path: root/examples/read.lua
blob: 7a1c747c5c810d6215944c575456166d80990074 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
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"



local read_input do
  local left_over_key

  -- Reads a single key, if it is a 27 (start of ansi escape sequence) then it reads
  -- the rest of the sequence.
  -- This function is non-blocking, and will return nil if no key is available.
  -- In case of an ANSI sequence, it will return the full sequence as a string.
  -- @return nil|string the key read, or nil if no key is available
  function read_input()
    if left_over_key then
      -- we still have a cached key, return it
      local key = left_over_key
      left_over_key = nil
      return string.char(key)
    end

    local key = sys.readkey()
    if key == nil then
      return nil
    end

    if key ~= 27 then
      return string.char(key)
    end

    -- looks like an ansi escape sequence, immediately read next char
    -- as an heuristic against manually typing escape sequences
    local brack = sys.readkey()
    if brack ~= 91 then
      -- not the expected [ character, so we return the key as is
      -- and store the extra key read for the next call
      left_over_key = brack
      return string.char(key)
    end

    -- escape sequence detected, read the rest of the sequence
    local seq = { key, brack }
    while true do
      key = sys.readkey()
      table.insert(seq, key)
      if (key >= 65 and key <= 90) or (key >= 97 and key <= 126) then
        -- end of sequence, return the full sequence
        return string.char((unpack or table.unpack)(seq))
      end
    end
    -- unreachable
  end
end



print("Press a key, or 'A' to get cursor position, 'ESC' to exit")
while true do
  local key

  -- wait for a key, and sleep a bit to not do a busy-wait
  while not key do
    key = read_input()
    if not key then sys.sleep(0.1) end
  end

  if key == "A" then io.write(get_cursor_pos); io.flush() end

  -- check if we got a key or ANSI sequence
  if #key == 1 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

  else
    -- we got an ANSI sequence
    local seq = { key:byte(1, #key) }
    print("ANSI sequence received: " .. key:sub(2,-1), "(bytes: " .. table.concat(seq, ", ")..")")
  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)