diff options
Diffstat (limited to 'docs/examples/readline.lua.html')
| -rw-r--r-- | docs/examples/readline.lua.html | 552 |
1 files changed, 552 insertions, 0 deletions
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 @@ | |||
| 1 | <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" | ||
| 2 | "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> | ||
| 3 | <html> | ||
| 4 | <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> | ||
| 5 | <head> | ||
| 6 | <title>Lua-System docs</title> | ||
| 7 | <link rel="stylesheet" href="../ldoc.css" type="text/css" /> | ||
| 8 | </head> | ||
| 9 | <body> | ||
| 10 | |||
| 11 | <div id="container"> | ||
| 12 | |||
| 13 | <div id="product"> | ||
| 14 | <div id="product_logo"></div> | ||
| 15 | <div id="product_name"><big><b></b></big></div> | ||
| 16 | <div id="product_description"></div> | ||
| 17 | </div> <!-- id="product" --> | ||
| 18 | |||
| 19 | |||
| 20 | <div id="main"> | ||
| 21 | |||
| 22 | |||
| 23 | <!-- Menu --> | ||
| 24 | |||
| 25 | <div id="navigation"> | ||
| 26 | <br/> | ||
| 27 | <h1>Lua-System</h1> | ||
| 28 | |||
| 29 | |||
| 30 | <ul> | ||
| 31 | <li><a href="../index.html">Index</a></li> | ||
| 32 | </ul> | ||
| 33 | |||
| 34 | |||
| 35 | |||
| 36 | <h2>Examples</h2> | ||
| 37 | <ul class="nowrap"> | ||
| 38 | <li><a href="../examples/compat.lua.html">compat.lua</a></li> | ||
| 39 | <li><a href="../examples/flag_debugging.lua.html">flag_debugging.lua</a></li> | ||
| 40 | <li><a href="../examples/password_input.lua.html">password_input.lua</a></li> | ||
| 41 | <li><a href="../examples/read.lua.html">read.lua</a></li> | ||
| 42 | <li><strong>readline.lua</strong></li> | ||
| 43 | <li><a href="../examples/spinner.lua.html">spinner.lua</a></li> | ||
| 44 | <li><a href="../examples/spiral_snake.lua.html">spiral_snake.lua</a></li> | ||
| 45 | <li><a href="../examples/terminalsize.lua.html">terminalsize.lua</a></li> | ||
| 46 | </ul> | ||
| 47 | <h2>Modules</h2> | ||
| 48 | <ul class="nowrap"> | ||
| 49 | <li><a href="../modules/system.html">system</a></li> | ||
| 50 | </ul> | ||
| 51 | <h2>Classes</h2> | ||
| 52 | <ul class="nowrap"> | ||
| 53 | <li><a href="../classes/bitflags.html">bitflags</a></li> | ||
| 54 | </ul> | ||
| 55 | <h2>Topics</h2> | ||
| 56 | <ul class=""> | ||
| 57 | <li><a href="../topics/01-introduction.md.html">1. Introduction</a></li> | ||
| 58 | <li><a href="../topics/02-development.md.html">2. Development</a></li> | ||
| 59 | <li><a href="../topics/03-terminal.md.html">3. Terminal functionality</a></li> | ||
| 60 | <li><a href="../topics/CHANGELOG.md.html">CHANGELOG</a></li> | ||
| 61 | <li><a href="../topics/LICENSE.md.html">MIT License</a></li> | ||
| 62 | </ul> | ||
| 63 | |||
| 64 | </div> | ||
| 65 | |||
| 66 | <div id="content"> | ||
| 67 | |||
| 68 | <h2>readline.lua</h2> | ||
| 69 | <pre> | ||
| 70 | <span class="comment">--- An example class for reading a line of input from the user in a non-blocking way. | ||
| 71 | </span><span class="comment">-- It uses ANSI escape sequences to move the cursor and handle input. | ||
| 72 | </span><span class="comment">-- It can be used to read a line of input from the user, with a prompt. | ||
| 73 | </span><span class="comment">-- It can handle double-width UTF-8 characters. | ||
| 74 | </span><span class="comment">-- It can be used asynchroneously if <a href="../modules/system.html#sleep">system.sleep</a> is patched to yield to a coroutine scheduler. | ||
| 75 | </span> | ||
| 76 | <span class="keyword">local</span> sys = <span class="global">require</span>(<span class="string">"system"</span>) | ||
| 77 | |||
| 78 | |||
| 79 | <span class="comment">-- Mapping of key-sequences to key-names | ||
| 80 | </span><span class="keyword">local</span> key_names = { | ||
| 81 | [<span class="string">"\27[C"</span>] = <span class="string">"right"</span>, | ||
| 82 | [<span class="string">"\27[D"</span>] = <span class="string">"left"</span>, | ||
| 83 | [<span class="string">"\127"</span>] = <span class="string">"backspace"</span>, | ||
| 84 | [<span class="string">"\27[3~"</span>] = <span class="string">"delete"</span>, | ||
| 85 | [<span class="string">"\27[H"</span>] = <span class="string">"home"</span>, | ||
| 86 | [<span class="string">"\27[F"</span>] = <span class="string">"end"</span>, | ||
| 87 | [<span class="string">"\27"</span>] = <span class="string">"escape"</span>, | ||
| 88 | [<span class="string">"\9"</span>] = <span class="string">"tab"</span>, | ||
| 89 | [<span class="string">"\27[Z"</span>] = <span class="string">"shift-tab"</span>, | ||
| 90 | } | ||
| 91 | |||
| 92 | <span class="keyword">if</span> sys.windows <span class="keyword">then</span> | ||
| 93 | key_names[<span class="string">"\13"</span>] = <span class="string">"enter"</span> | ||
| 94 | <span class="keyword">else</span> | ||
| 95 | key_names[<span class="string">"\10"</span>] = <span class="string">"enter"</span> | ||
| 96 | <span class="keyword">end</span> | ||
| 97 | |||
| 98 | |||
| 99 | <span class="comment">-- Mapping of key-names to key-sequences | ||
| 100 | </span><span class="keyword">local</span> key_sequences = {} | ||
| 101 | <span class="keyword">for</span> k, v <span class="keyword">in</span> <span class="global">pairs</span>(key_names) <span class="keyword">do</span> | ||
| 102 | key_sequences[v] = k | ||
| 103 | <span class="keyword">end</span> | ||
| 104 | |||
| 105 | |||
| 106 | <span class="comment">-- bell character | ||
| 107 | </span><span class="keyword">local</span> <span class="keyword">function</span> <span class="function-name">bell</span>() | ||
| 108 | <span class="global">io</span>.<span class="function-name">write</span>(<span class="string">"\7"</span>) | ||
| 109 | <span class="global">io</span>.<span class="function-name">flush</span>() | ||
| 110 | <span class="keyword">end</span> | ||
| 111 | |||
| 112 | |||
| 113 | <span class="comment">-- generate string to move cursor horizontally | ||
| 114 | </span><span class="comment">-- positive goes right, negative goes left | ||
| 115 | </span><span class="keyword">local</span> <span class="keyword">function</span> <span class="function-name">cursor_move_horiz</span>(n) | ||
| 116 | <span class="keyword">if</span> n == <span class="number">0</span> <span class="keyword">then</span> | ||
| 117 | <span class="keyword">return</span> <span class="string">""</span> | ||
| 118 | <span class="keyword">end</span> | ||
| 119 | <span class="keyword">return</span> <span class="string">"\27["</span> .. (n > <span class="number">0</span> <span class="keyword">and</span> n <span class="keyword">or</span> -n) .. (n > <span class="number">0</span> <span class="keyword">and</span> <span class="string">"C"</span> <span class="keyword">or</span> <span class="string">"D"</span>) | ||
| 120 | <span class="keyword">end</span> | ||
| 121 | |||
| 122 | |||
| 123 | <span class="comment">-- -- generate string to move cursor vertically | ||
| 124 | </span><span class="comment">-- -- positive goes down, negative goes up | ||
| 125 | </span><span class="comment">-- local function cursor_move_vert(n) | ||
| 126 | </span><span class="comment">-- if n == 0 then | ||
| 127 | </span><span class="comment">-- return "" | ||
| 128 | </span><span class="comment">-- end | ||
| 129 | </span><span class="comment">-- return "\27[" .. (n > 0 and n or -n) .. (n > 0 and "B" or "A") | ||
| 130 | </span><span class="comment">-- end | ||
| 131 | </span> | ||
| 132 | |||
| 133 | <span class="comment">-- -- log to the line above the current line | ||
| 134 | </span><span class="comment">-- local function log(...) | ||
| 135 | </span><span class="comment">-- local arg = { n = select("#", ...), ...} | ||
| 136 | </span><span class="comment">-- for i = 1, arg.n do | ||
| 137 | </span><span class="comment">-- arg[i] = tostring(arg[i]) | ||
| 138 | </span><span class="comment">-- end | ||
| 139 | </span><span class="comment">-- arg = " " .. table.concat(arg, " ") .. " " | ||
| 140 | </span> | ||
| 141 | <span class="comment">-- io.write(cursor_move_vert(-1), arg, cursor_move_vert(1), cursor_move_horiz(-#arg)) | ||
| 142 | </span><span class="comment">-- end | ||
| 143 | </span> | ||
| 144 | |||
| 145 | <span class="comment">-- UTF8 character size in bytes | ||
| 146 | </span><span class="comment">-- @tparam number b the byte value of the first byte of a UTF8 character | ||
| 147 | </span><span class="keyword">local</span> <span class="keyword">function</span> <span class="function-name">utf8size</span>(b) | ||
| 148 | <span class="keyword">return</span> b < <span class="number">128</span> <span class="keyword">and</span> <span class="number">1</span> <span class="keyword">or</span> b < <span class="number">224</span> <span class="keyword">and</span> <span class="number">2</span> <span class="keyword">or</span> b < <span class="number">240</span> <span class="keyword">and</span> <span class="number">3</span> <span class="keyword">or</span> b < <span class="number">248</span> <span class="keyword">and</span> <span class="number">4</span> | ||
| 149 | <span class="keyword">end</span> | ||
| 150 | |||
| 151 | |||
| 152 | |||
| 153 | <span class="keyword">local</span> utf8parse <span class="keyword">do</span> | ||
| 154 | <span class="keyword">local</span> utf8_value_mt = { | ||
| 155 | __tostring = <span class="keyword">function</span>(self) | ||
| 156 | <span class="keyword">return</span> <span class="global">table</span>.<span class="function-name">concat</span>(self, <span class="string">""</span>) | ||
| 157 | <span class="keyword">end</span>, | ||
| 158 | } | ||
| 159 | |||
| 160 | <span class="comment">-- Parses a UTF8 string into list of individual characters. | ||
| 161 | </span> <span class="comment">-- key 'chars' gets the length in UTF8 characters, whilst # returns the length | ||
| 162 | </span> <span class="comment">-- for display (to handle double-width UTF8 chars). | ||
| 163 | </span> <span class="comment">-- in the list the double-width characters are followed by an empty string. | ||
| 164 | </span> <span class="comment">-- @tparam string s the UTF8 string to parse | ||
| 165 | </span> <span class="comment">-- @treturn table the list of characters | ||
| 166 | </span> <span class="keyword">function</span> <span class="function-name">utf8parse</span>(s) | ||
| 167 | <span class="keyword">local</span> t = <span class="global">setmetatable</span>({ chars = <span class="number">0</span> }, utf8_value_mt) | ||
| 168 | <span class="keyword">local</span> i = <span class="number">1</span> | ||
| 169 | <span class="keyword">while</span> i <= #s <span class="keyword">do</span> | ||
| 170 | <span class="keyword">local</span> b = s:<span class="function-name">byte</span>(i) | ||
| 171 | <span class="keyword">local</span> w = <span class="function-name">utf8size</span>(b) | ||
| 172 | <span class="keyword">local</span> char = s:<span class="function-name">sub</span>(i, i + w - <span class="number">1</span>) | ||
| 173 | t[#t + <span class="number">1</span>] = char | ||
| 174 | t.chars = t.chars + <span class="number">1</span> | ||
| 175 | <span class="keyword">if</span> sys.<span class="function-name">utf8cwidth</span>(char) == <span class="number">2</span> <span class="keyword">then</span> | ||
| 176 | <span class="comment">-- double width character, add empty string to keep the length of the | ||
| 177 | </span> <span class="comment">-- list the same as the character width on screen | ||
| 178 | </span> t[#t + <span class="number">1</span>] = <span class="string">""</span> | ||
| 179 | <span class="keyword">end</span> | ||
| 180 | i = i + w | ||
| 181 | <span class="keyword">end</span> | ||
| 182 | <span class="keyword">return</span> t | ||
| 183 | <span class="keyword">end</span> | ||
| 184 | <span class="keyword">end</span> | ||
| 185 | |||
| 186 | |||
| 187 | |||
| 188 | <span class="comment">-- inline tests for utf8parse | ||
| 189 | </span><span class="comment">-- do | ||
| 190 | </span><span class="comment">-- local t = utf8parse("a你b好c") | ||
| 191 | </span><span class="comment">-- assert(t[1] == "a") | ||
| 192 | </span><span class="comment">-- assert(t[2] == "你") -- double width | ||
| 193 | </span><span class="comment">-- assert(t[3] == "") | ||
| 194 | </span><span class="comment">-- assert(t[4] == "b") | ||
| 195 | </span><span class="comment">-- assert(t[5] == "好") -- double width | ||
| 196 | </span><span class="comment">-- assert(t[6] == "") | ||
| 197 | </span><span class="comment">-- assert(t[7] == "c") | ||
| 198 | </span><span class="comment">-- assert(#t == 7) -- size as displayed | ||
| 199 | </span><span class="comment">-- end | ||
| 200 | </span> | ||
| 201 | |||
| 202 | |||
| 203 | <span class="comment">-- readline class | ||
| 204 | </span> | ||
| 205 | <span class="keyword">local</span> readline = {} | ||
| 206 | readline.__index = readline | ||
| 207 | |||
| 208 | |||
| 209 | <span class="comment">--- Create a new readline object. | ||
| 210 | </span><span class="comment">-- @tparam table opts the options for the readline object | ||
| 211 | </span><span class="comment">-- @tparam[opt=""] string opts.prompt the prompt to display | ||
| 212 | </span><span class="comment">-- @tparam[opt=80] number opts.max_length the maximum length of the input (in characters, not bytes) | ||
| 213 | </span><span class="comment">-- @tparam[opt=""] string opts.value the default value | ||
| 214 | </span><span class="comment">-- @tparam[opt=<code>#value</code>] number opts.position of the cursor in the input | ||
| 215 | </span><span class="comment">-- @tparam[opt={"\10"/"\13"}] table opts.exit_keys an array of keys that will cause the readline to exit | ||
| 216 | </span><span class="comment">-- @treturn readline the new readline object | ||
| 217 | </span><span class="keyword">function</span> readline.<span class="function-name">new</span>(opts) | ||
| 218 | <span class="keyword">local</span> value = <span class="function-name">utf8parse</span>(opts.value <span class="keyword">or</span> <span class="string">""</span>) | ||
| 219 | <span class="keyword">local</span> prompt = <span class="function-name">utf8parse</span>(opts.prompt <span class="keyword">or</span> <span class="string">""</span>) | ||
| 220 | <span class="keyword">local</span> pos = <span class="global">math</span>.<span class="function-name">floor</span>(opts.position <span class="keyword">or</span> (#value + <span class="number">1</span>)) | ||
| 221 | pos = <span class="global">math</span>.<span class="function-name">max</span>(<span class="global">math</span>.<span class="function-name">min</span>(pos, (#value + <span class="number">1</span>)), <span class="number">1</span>) | ||
| 222 | <span class="keyword">local</span> len = <span class="global">math</span>.<span class="function-name">floor</span>(opts.max_length <span class="keyword">or</span> <span class="number">80</span>) | ||
| 223 | <span class="keyword">if</span> len < <span class="number">1</span> <span class="keyword">then</span> | ||
| 224 | <span class="global">error</span>(<span class="string">"max_length must be at least 1"</span>, <span class="number">2</span>) | ||
| 225 | <span class="keyword">end</span> | ||
| 226 | |||
| 227 | <span class="keyword">if</span> value.chars > len <span class="keyword">then</span> | ||
| 228 | <span class="global">error</span>(<span class="string">"value is longer than max_length"</span>, <span class="number">2</span>) | ||
| 229 | <span class="keyword">end</span> | ||
| 230 | |||
| 231 | <span class="keyword">local</span> exit_keys = {} | ||
| 232 | <span class="keyword">for</span> _, key <span class="keyword">in</span> <span class="global">ipairs</span>(opts.exit_keys <span class="keyword">or</span> {}) <span class="keyword">do</span> | ||
| 233 | exit_keys[key] = <span class="keyword">true</span> | ||
| 234 | <span class="keyword">end</span> | ||
| 235 | <span class="keyword">if</span> exit_keys[<span class="number">1</span>] == <span class="keyword">nil</span> <span class="keyword">then</span> | ||
| 236 | <span class="comment">-- nothing provided, default to Enter-key | ||
| 237 | </span> exit_keys[<span class="number">1</span>] = key_sequences.enter | ||
| 238 | <span class="keyword">end</span> | ||
| 239 | |||
| 240 | <span class="keyword">local</span> self = { | ||
| 241 | value = value, <span class="comment">-- the default value | ||
| 242 | </span> max_length = len, <span class="comment">-- the maximum length of the input | ||
| 243 | </span> prompt = prompt, <span class="comment">-- the prompt to display | ||
| 244 | </span> position = pos, <span class="comment">-- the current position in the input | ||
| 245 | </span> drawn_before = <span class="keyword">false</span>, <span class="comment">-- if the prompt has been drawn | ||
| 246 | </span> exit_keys = exit_keys, <span class="comment">-- the keys that will cause the readline to exit | ||
| 247 | </span> } | ||
| 248 | |||
| 249 | <span class="global">setmetatable</span>(self, readline) | ||
| 250 | <span class="keyword">return</span> self | ||
| 251 | <span class="keyword">end</span> | ||
| 252 | |||
| 253 | |||
| 254 | |||
| 255 | <span class="comment">-- draw the prompt and the input value, and position the cursor. | ||
| 256 | </span><span class="keyword">local</span> <span class="keyword">function</span> <span class="function-name">draw</span>(self, redraw) | ||
| 257 | <span class="keyword">if</span> redraw <span class="keyword">or</span> <span class="keyword">not</span> self.drawn_before <span class="keyword">then</span> | ||
| 258 | <span class="comment">-- we are at start of prompt | ||
| 259 | </span> self.drawn_before = <span class="keyword">true</span> | ||
| 260 | <span class="keyword">else</span> | ||
| 261 | <span class="comment">-- we are at current cursor position, move to start of prompt | ||
| 262 | </span> <span class="global">io</span>.<span class="function-name">write</span>(<span class="function-name">cursor_move_horiz</span>(-(#self.prompt + self.position))) | ||
| 263 | <span class="keyword">end</span> | ||
| 264 | <span class="comment">-- write prompt & value | ||
| 265 | </span> <span class="global">io</span>.<span class="function-name">write</span>(<span class="global">tostring</span>(self.prompt) .. <span class="global">tostring</span>(self.value)) | ||
| 266 | <span class="comment">-- clear remainder of input size | ||
| 267 | </span> <span class="global">io</span>.<span class="function-name">write</span>(<span class="global">string</span>.<span class="function-name">rep</span>(<span class="string">" "</span>, self.max_length - self.value.chars)) | ||
| 268 | <span class="global">io</span>.<span class="function-name">write</span>(<span class="function-name">cursor_move_horiz</span>(-(self.max_length - self.value.chars))) | ||
| 269 | <span class="comment">-- move to cursor position | ||
| 270 | </span> <span class="global">io</span>.<span class="function-name">write</span>(<span class="function-name">cursor_move_horiz</span>(-(#self.value + <span class="number">1</span> - self.position))) | ||
| 271 | <span class="global">io</span>.<span class="function-name">flush</span>() | ||
| 272 | <span class="keyword">end</span> | ||
| 273 | |||
| 274 | |||
| 275 | <span class="keyword">local</span> handle_key <span class="keyword">do</span> <span class="comment">-- keyboard input handler | ||
| 276 | </span> | ||
| 277 | <span class="keyword">local</span> key_handlers | ||
| 278 | key_handlers = { | ||
| 279 | left = <span class="keyword">function</span>(self) | ||
| 280 | <span class="keyword">if</span> self.position == <span class="number">1</span> <span class="keyword">then</span> | ||
| 281 | <span class="function-name">bell</span>() | ||
| 282 | <span class="keyword">return</span> | ||
| 283 | <span class="keyword">end</span> | ||
| 284 | |||
| 285 | <span class="keyword">local</span> new_pos = self.position - <span class="number">1</span> | ||
| 286 | <span class="keyword">while</span> self.value[new_pos] == <span class="string">""</span> <span class="keyword">do</span> <span class="comment">-- skip empty strings; double width chars | ||
| 287 | </span> new_pos = new_pos - <span class="number">1</span> | ||
| 288 | <span class="keyword">end</span> | ||
| 289 | |||
| 290 | <span class="global">io</span>.<span class="function-name">write</span>(<span class="function-name">cursor_move_horiz</span>(-(self.position - new_pos))) | ||
| 291 | <span class="global">io</span>.<span class="function-name">flush</span>() | ||
| 292 | self.position = new_pos | ||
| 293 | <span class="keyword">end</span>, | ||
| 294 | |||
| 295 | right = <span class="keyword">function</span>(self) | ||
| 296 | <span class="keyword">if</span> self.position == #self.value + <span class="number">1</span> <span class="keyword">then</span> | ||
| 297 | <span class="function-name">bell</span>() | ||
| 298 | <span class="keyword">return</span> | ||
| 299 | <span class="keyword">end</span> | ||
| 300 | |||
| 301 | <span class="keyword">local</span> new_pos = self.position + <span class="number">1</span> | ||
| 302 | <span class="keyword">while</span> self.value[new_pos] == <span class="string">""</span> <span class="keyword">do</span> <span class="comment">-- skip empty strings; double width chars | ||
| 303 | </span> new_pos = new_pos + <span class="number">1</span> | ||
| 304 | <span class="keyword">end</span> | ||
| 305 | |||
| 306 | <span class="global">io</span>.<span class="function-name">write</span>(<span class="function-name">cursor_move_horiz</span>(new_pos - self.position)) | ||
| 307 | <span class="global">io</span>.<span class="function-name">flush</span>() | ||
| 308 | self.position = new_pos | ||
| 309 | <span class="keyword">end</span>, | ||
| 310 | |||
| 311 | backspace = <span class="keyword">function</span>(self) | ||
| 312 | <span class="keyword">if</span> self.position == <span class="number">1</span> <span class="keyword">then</span> | ||
| 313 | <span class="function-name">bell</span>() | ||
| 314 | <span class="keyword">return</span> | ||
| 315 | <span class="keyword">end</span> | ||
| 316 | |||
| 317 | <span class="keyword">while</span> self.value[self.position - <span class="number">1</span>] == <span class="string">""</span> <span class="keyword">do</span> <span class="comment">-- remove empty strings; double width chars | ||
| 318 | </span> <span class="global">io</span>.<span class="function-name">write</span>(<span class="function-name">cursor_move_horiz</span>(-<span class="number">1</span>)) | ||
| 319 | self.position = self.position - <span class="number">1</span> | ||
| 320 | <span class="global">table</span>.<span class="function-name">remove</span>(self.value, self.position) | ||
| 321 | <span class="keyword">end</span> | ||
| 322 | <span class="comment">-- remove char itself | ||
| 323 | </span> <span class="global">io</span>.<span class="function-name">write</span>(<span class="function-name">cursor_move_horiz</span>(-<span class="number">1</span>)) | ||
| 324 | self.position = self.position - <span class="number">1</span> | ||
| 325 | <span class="global">table</span>.<span class="function-name">remove</span>(self.value, self.position) | ||
| 326 | self.value.chars = self.value.chars - <span class="number">1</span> | ||
| 327 | <span class="function-name">draw</span>(self) | ||
| 328 | <span class="keyword">end</span>, | ||
| 329 | |||
| 330 | home = <span class="keyword">function</span>(self) | ||
| 331 | <span class="keyword">local</span> new_pos = <span class="number">1</span> | ||
| 332 | <span class="global">io</span>.<span class="function-name">write</span>(<span class="function-name">cursor_move_horiz</span>(new_pos - self.position)) | ||
| 333 | self.position = new_pos | ||
| 334 | <span class="keyword">end</span>, | ||
| 335 | |||
| 336 | [<span class="string">"end"</span>] = <span class="keyword">function</span>(self) | ||
| 337 | <span class="keyword">local</span> new_pos = #self.value + <span class="number">1</span> | ||
| 338 | <span class="global">io</span>.<span class="function-name">write</span>(<span class="function-name">cursor_move_horiz</span>(new_pos - self.position)) | ||
| 339 | self.position = new_pos | ||
| 340 | <span class="keyword">end</span>, | ||
| 341 | |||
| 342 | delete = <span class="keyword">function</span>(self) | ||
| 343 | <span class="keyword">if</span> self.position > #self.value <span class="keyword">then</span> | ||
| 344 | <span class="function-name">bell</span>() | ||
| 345 | <span class="keyword">return</span> | ||
| 346 | <span class="keyword">end</span> | ||
| 347 | |||
| 348 | key_handlers.<span class="function-name">right</span>(self) | ||
| 349 | key_handlers.<span class="function-name">backspace</span>(self) | ||
| 350 | <span class="keyword">end</span>, | ||
| 351 | } | ||
| 352 | |||
| 353 | |||
| 354 | <span class="comment">-- handles a single input key/ansi-sequence. | ||
| 355 | </span> <span class="comment">-- @tparam string key the key or ansi-sequence (from <a href="../modules/system.html#readansi">system.readansi</a>) | ||
| 356 | </span> <span class="comment">-- @tparam string keytype the type of the key, either "char" or "ansi" (from <a href="../modules/system.html#readansi">system.readansi</a>) | ||
| 357 | </span> <span class="comment">-- @treturn string status the status of the key handling, either "ok", "exit_key" or an error message | ||
| 358 | </span> <span class="keyword">function</span> <span class="function-name">handle_key</span>(self, key, keytype) | ||
| 359 | <span class="keyword">if</span> self.exit_keys[key] <span class="keyword">then</span> | ||
| 360 | <span class="comment">-- registered exit key | ||
| 361 | </span> <span class="keyword">return</span> <span class="string">"exit_key"</span> | ||
| 362 | <span class="keyword">end</span> | ||
| 363 | |||
| 364 | <span class="keyword">local</span> handler = key_handlers[key_names[key] <span class="keyword">or</span> <span class="keyword">true</span> ] | ||
| 365 | <span class="keyword">if</span> handler <span class="keyword">then</span> | ||
| 366 | <span class="function-name">handler</span>(self) | ||
| 367 | <span class="keyword">return</span> <span class="string">"ok"</span> | ||
| 368 | <span class="keyword">end</span> | ||
| 369 | |||
| 370 | <span class="keyword">if</span> keytype == <span class="string">"ansi"</span> <span class="keyword">then</span> | ||
| 371 | <span class="comment">-- we got an ansi sequence, but dunno how to handle it, ignore | ||
| 372 | </span> <span class="comment">-- print("unhandled ansi: ", key:sub(2,-1), string.byte(key, 1, -1)) | ||
| 373 | </span> <span class="function-name">bell</span>() | ||
| 374 | <span class="keyword">return</span> <span class="string">"ok"</span> | ||
| 375 | <span class="keyword">end</span> | ||
| 376 | |||
| 377 | <span class="comment">-- just a single key | ||
| 378 | </span> <span class="keyword">if</span> key < <span class="string">" "</span> <span class="keyword">then</span> | ||
| 379 | <span class="comment">-- control character | ||
| 380 | </span> <span class="function-name">bell</span>() | ||
| 381 | <span class="keyword">return</span> <span class="string">"ok"</span> | ||
| 382 | <span class="keyword">end</span> | ||
| 383 | |||
| 384 | <span class="keyword">if</span> self.value.chars >= self.max_length <span class="keyword">then</span> | ||
| 385 | <span class="function-name">bell</span>() | ||
| 386 | <span class="keyword">return</span> <span class="string">"ok"</span> | ||
| 387 | <span class="keyword">end</span> | ||
| 388 | |||
| 389 | <span class="comment">-- insert the key into the value | ||
| 390 | </span> <span class="keyword">if</span> sys.<span class="function-name">utf8cwidth</span>(key) == <span class="number">2</span> <span class="keyword">then</span> | ||
| 391 | <span class="comment">-- double width character, insert empty string after it | ||
| 392 | </span> <span class="global">table</span>.<span class="function-name">insert</span>(self.value, self.position, <span class="string">""</span>) | ||
| 393 | <span class="global">table</span>.<span class="function-name">insert</span>(self.value, self.position, key) | ||
| 394 | self.position = self.position + <span class="number">2</span> | ||
| 395 | <span class="global">io</span>.<span class="function-name">write</span>(<span class="function-name">cursor_move_horiz</span>(<span class="number">2</span>)) | ||
| 396 | <span class="keyword">else</span> | ||
| 397 | <span class="global">table</span>.<span class="function-name">insert</span>(self.value, self.position, key) | ||
| 398 | self.position = self.position + <span class="number">1</span> | ||
| 399 | <span class="global">io</span>.<span class="function-name">write</span>(<span class="function-name">cursor_move_horiz</span>(<span class="number">1</span>)) | ||
| 400 | <span class="keyword">end</span> | ||
| 401 | self.value.chars = self.value.chars + <span class="number">1</span> | ||
| 402 | <span class="function-name">draw</span>(self) | ||
| 403 | <span class="keyword">return</span> <span class="string">"ok"</span> | ||
| 404 | <span class="keyword">end</span> | ||
| 405 | <span class="keyword">end</span> | ||
| 406 | |||
| 407 | |||
| 408 | |||
| 409 | <span class="comment">--- Get_size returns the maximum size of the input box (prompt + input). | ||
| 410 | </span><span class="comment">-- The size is in rows and columns. Columns is determined by | ||
| 411 | </span><span class="comment">-- the prompt and the <code>max_length * 2</code> (characters can be double-width). | ||
| 412 | </span><span class="comment">-- @treturn number the number of rows (always 1) | ||
| 413 | </span><span class="comment">-- @treturn number the number of columns | ||
| 414 | </span><span class="keyword">function</span> readline:<span class="function-name">get_size</span>() | ||
| 415 | <span class="keyword">return</span> <span class="number">1</span>, #self.prompt + self.max_length * <span class="number">2</span> | ||
| 416 | <span class="keyword">end</span> | ||
| 417 | |||
| 418 | |||
| 419 | |||
| 420 | <span class="comment">--- Get coordinates of the cursor in the input box (prompt + input). | ||
| 421 | </span><span class="comment">-- The coordinates are 1-based. They are returned as row and column, within the | ||
| 422 | </span><span class="comment">-- size as reported by <code>get_size</code>. | ||
| 423 | </span><span class="comment">-- @treturn number the row of the cursor (always 1) | ||
| 424 | </span><span class="comment">-- @treturn number the column of the cursor | ||
| 425 | </span><span class="keyword">function</span> readline:<span class="function-name">get_cursor</span>() | ||
| 426 | <span class="keyword">return</span> <span class="number">1</span>, #self.prompt + self.position | ||
| 427 | <span class="keyword">end</span> | ||
| 428 | |||
| 429 | |||
| 430 | |||
| 431 | <span class="comment">--- Set the coordinates of the cursor in the input box (prompt + input). | ||
| 432 | </span><span class="comment">-- The coordinates are 1-based. They are expected to be within the | ||
| 433 | </span><span class="comment">-- size as reported by <code>get_size</code>, and beyond the prompt. | ||
| 434 | </span><span class="comment">-- If the position is invalid, it will be corrected. | ||
| 435 | </span><span class="comment">-- Use the results to check if the position was adjusted. | ||
| 436 | </span><span class="comment">-- @tparam number row the row of the cursor (always 1) | ||
| 437 | </span><span class="comment">-- @tparam number col the column of the cursor | ||
| 438 | </span><span class="comment">-- @return results of get_cursor | ||
| 439 | </span><span class="keyword">function</span> readline:<span class="function-name">set_cursor</span>(row, col) | ||
| 440 | <span class="keyword">local</span> l_prompt = #self.prompt | ||
| 441 | <span class="keyword">local</span> l_value = #self.value | ||
| 442 | |||
| 443 | <span class="keyword">if</span> col < l_prompt + <span class="number">1</span> <span class="keyword">then</span> | ||
| 444 | col = l_prompt + <span class="number">1</span> | ||
| 445 | <span class="keyword">elseif</span> col > l_prompt + l_value + <span class="number">1</span> <span class="keyword">then</span> | ||
| 446 | col = l_prompt + l_value + <span class="number">1</span> | ||
| 447 | <span class="keyword">end</span> | ||
| 448 | |||
| 449 | <span class="keyword">while</span> self.value[col - l_prompt] == <span class="string">""</span> <span class="keyword">do</span> | ||
| 450 | col = col - <span class="number">1</span> <span class="comment">-- on an empty string, so move back to start of double-width char | ||
| 451 | </span> <span class="keyword">end</span> | ||
| 452 | |||
| 453 | <span class="keyword">local</span> new_pos = col - l_prompt | ||
| 454 | |||
| 455 | <span class="function-name">cursor_move_horiz</span>(self.position - new_pos) | ||
| 456 | <span class="global">io</span>.<span class="function-name">flush</span>() | ||
| 457 | |||
| 458 | self.position = new_pos | ||
| 459 | <span class="keyword">return</span> self:<span class="function-name">get_cursor</span>() | ||
| 460 | <span class="keyword">end</span> | ||
| 461 | |||
| 462 | |||
| 463 | |||
| 464 | <span class="comment">--- Read a line of input from the user. | ||
| 465 | </span><span class="comment">-- It will first print the <code>prompt</code> and then wait for input. Ensure the cursor | ||
| 466 | </span><span class="comment">-- is at the correct position before calling this function. This function will | ||
| 467 | </span><span class="comment">-- do all cursor movements in a relative way. | ||
| 468 | </span><span class="comment">-- Can be called again after an exit-key or timeout has occurred. Just make sure | ||
| 469 | </span><span class="comment">-- the cursor is at the same position where is was when it returned the last time. | ||
| 470 | </span><span class="comment">-- Alternatively the cursor can be set to the position of the prompt (the position | ||
| 471 | </span><span class="comment">-- the cursor was in before the first call), and the parameter <code>redraw</code> can be set | ||
| 472 | </span><span class="comment">-- to <code>true</code>. | ||
| 473 | </span><span class="comment">-- @tparam[opt=math.huge] number timeout the maximum time to wait for input in seconds | ||
| 474 | </span><span class="comment">-- @tparam[opt=false] boolean redraw if <code>true</code> the prompt will be redrawn (cursor must be at prompt position!) | ||
| 475 | </span><span class="comment">-- @treturn[1] string the input string as entered the user | ||
| 476 | </span><span class="comment">-- @treturn[1] string the exit-key used to exit the readline (see <code>new</code>) | ||
| 477 | </span><span class="comment">-- @treturn[2] nil when input is incomplete | ||
| 478 | </span><span class="comment">-- @treturn[2] string error message, the reason why the input is incomplete, <code>"timeout"</code>, or an error reading a key | ||
| 479 | </span><span class="keyword">function</span> readline:<span class="function-name">__call</span>(timeout, redraw) | ||
| 480 | <span class="function-name">draw</span>(self, redraw) | ||
| 481 | timeout = timeout <span class="keyword">or</span> <span class="global">math</span>.huge | ||
| 482 | <span class="keyword">local</span> timeout_end = sys.<span class="function-name">gettime</span>() + timeout | ||
| 483 | |||
| 484 | <span class="keyword">while</span> <span class="keyword">true</span> <span class="keyword">do</span> | ||
| 485 | <span class="keyword">local</span> key, keytype = sys.<span class="function-name">readansi</span>(timeout_end - sys.<span class="function-name">gettime</span>()) | ||
| 486 | <span class="keyword">if</span> <span class="keyword">not</span> key <span class="keyword">then</span> | ||
| 487 | <span class="comment">-- error or timeout | ||
| 488 | </span> <span class="keyword">return</span> <span class="keyword">nil</span>, keytype | ||
| 489 | <span class="keyword">end</span> | ||
| 490 | |||
| 491 | <span class="keyword">local</span> status = <span class="function-name">handle_key</span>(self, key, keytype) | ||
| 492 | <span class="keyword">if</span> status == <span class="string">"exit_key"</span> <span class="keyword">then</span> | ||
| 493 | <span class="keyword">return</span> <span class="global">tostring</span>(self.value), key | ||
| 494 | |||
| 495 | <span class="keyword">elseif</span> status ~= <span class="string">"ok"</span> <span class="keyword">then</span> | ||
| 496 | <span class="global">error</span>(<span class="string">"unknown status received: "</span> .. <span class="global">tostring</span>(status)) | ||
| 497 | <span class="keyword">end</span> | ||
| 498 | <span class="keyword">end</span> | ||
| 499 | <span class="keyword">end</span> | ||
| 500 | |||
| 501 | |||
| 502 | |||
| 503 | <span class="comment">-- return readline -- normally we'd return here, but for the example we continue | ||
| 504 | </span> | ||
| 505 | |||
| 506 | |||
| 507 | |||
| 508 | <span class="keyword">local</span> backup = sys.<span class="function-name">termbackup</span>() | ||
| 509 | |||
| 510 | <span class="comment">-- setup Windows console to handle ANSI processing | ||
| 511 | </span>sys.<span class="function-name">setconsoleflags</span>(<span class="global">io</span>.stdout, sys.<span class="function-name">getconsoleflags</span>(<span class="global">io</span>.stdout) + sys.COF_VIRTUAL_TERMINAL_PROCESSING) | ||
| 512 | sys.<span class="function-name">setconsoleflags</span>(<span class="global">io</span>.stdin, sys.<span class="function-name">getconsoleflags</span>(<span class="global">io</span>.stdin) + sys.CIF_VIRTUAL_TERMINAL_INPUT) | ||
| 513 | <span class="comment">-- set output to UTF-8 | ||
| 514 | </span>sys.<span class="function-name">setconsoleoutputcp</span>(sys.CODEPAGE_UTF8) | ||
| 515 | |||
| 516 | <span class="comment">-- setup Posix terminal to disable canonical mode and echo | ||
| 517 | </span>sys.<span class="function-name">tcsetattr</span>(<span class="global">io</span>.stdin, sys.TCSANOW, { | ||
| 518 | lflag = sys.<span class="function-name">tcgetattr</span>(<span class="global">io</span>.stdin).lflag - sys.L_ICANON - sys.L_ECHO, | ||
| 519 | }) | ||
| 520 | <span class="comment">-- setup stdin to non-blocking mode | ||
| 521 | </span>sys.<span class="function-name">setnonblock</span>(<span class="global">io</span>.stdin, <span class="keyword">true</span>) | ||
| 522 | |||
| 523 | |||
| 524 | <span class="keyword">local</span> rl = readline.<span class="function-name">new</span>{ | ||
| 525 | prompt = <span class="string">"Enter something: "</span>, | ||
| 526 | max_length = <span class="number">60</span>, | ||
| 527 | value = <span class="string">"Hello, 你-好 World 🚀!"</span>, | ||
| 528 | <span class="comment">-- position = 2, | ||
| 529 | </span> exit_keys = {key_sequences.enter, <span class="string">"\27"</span>, <span class="string">"\t"</span>, <span class="string">"\27[Z"</span>}, <span class="comment">-- enter, escape, tab, shift-tab | ||
| 530 | </span>} | ||
| 531 | |||
| 532 | |||
| 533 | <span class="keyword">local</span> result, key = <span class="function-name">rl</span>() | ||
| 534 | <span class="global">print</span>(<span class="string">""</span>) <span class="comment">-- newline after input, to move cursor down from the input line | ||
| 535 | </span><span class="global">print</span>(<span class="string">"Result (string): '"</span> .. result .. <span class="string">"'"</span>) | ||
| 536 | <span class="global">print</span>(<span class="string">"Result (bytes):"</span>, result:<span class="function-name">byte</span>(<span class="number">1</span>,-<span class="number">1</span>)) | ||
| 537 | <span class="global">print</span>(<span class="string">"Exit-Key (bytes):"</span>, key:<span class="function-name">byte</span>(<span class="number">1</span>,-<span class="number">1</span>)) | ||
| 538 | |||
| 539 | |||
| 540 | <span class="comment">-- Clean up afterwards | ||
| 541 | </span>sys.<span class="function-name">termrestore</span>(backup)</pre> | ||
| 542 | |||
| 543 | |||
| 544 | </div> <!-- id="content" --> | ||
| 545 | </div> <!-- id="main" --> | ||
| 546 | <div id="about"> | ||
| 547 | <i>generated by <a href="http://github.com/lunarmodules/LDoc">LDoc 1.5.0</a></i> | ||
| 548 | <i style="float:right;">Last updated 2024-06-20 23:11:37 </i> | ||
| 549 | </div> <!-- id="about" --> | ||
| 550 | </div> <!-- id="container" --> | ||
| 551 | </body> | ||
| 552 | </html> | ||
