diff --git a/C-readline.c b/C-readline.c new file mode 100644 index 0000000..eb10e81 --- /dev/null +++ b/C-readline.c @@ -0,0 +1,370 @@ +/* + C-readline.c - readline and history bindings for Lua + + This Lua5 module is Copyright (c) 2013, Peter J Billam + www.pjb.com.au + + This module is free software; you can redistribute it and/or + modify it under the same terms as Lua5 itself. +*/ + +#include +#include +/* #include strlen() & friends, including strerror */ +/* #include isatty() */ + +/* --------------- from man readline -------------------- */ +#include +#include +/* #include 2.8 20210106 strerror is not in string.h ? */ +#include +#include +#include +/* http://cnswww.cns.cwru.edu/php/chet/readline/rltop.html + http://cnswww.cns.cwru.edu/php/chet/readline/readline.html#SEC52 + (totally alarming :-( ) + http://cnswww.cns.cwru.edu/php/chet/readline/history.html#IDX5 + (only moderately alarming) + http://cnswww.cns.cwru.edu/php/chet/readline/readline.html#IDX207 + Variable: FILE * rl_instream + The stdio stream from which Readline reads input. + If NULL, Readline defaults to stdin. + Variable: FILE * rl_outstream + The stdio stream to which Readline performs output. + If NULL, Readline defaults to stdout. + http://man7.org/linux/man-pages/man3/ctermid.3.html + http://man7.org/linux/man-pages/man3/fopen.3.html + http://man7.org/linux/man-pages/man3/fileno.3.html + http://man7.org/linux/man-pages/man3/isatty.3.html +*/ +/* see Programming in Lua p.233 */ +/* apparently a BUG: after being invoked, c_readline leaves SIGWINCH + handling messed up, and the kernel unable to follow further changes + in size; thence also tput, stty size, resize, $COLS $ROWS, etc... + Only xwininfo -id $WINDOWID seems to get up-to-date data. + Surprisingly, rl_catch_sigwinch and rl_cleanup_after_signal have no effect + http://cnswww.cns.cwru.edu/php/chet/readline/readline.html#SEC43 +*/ + +static int c_readline(lua_State *L) { /* prompt in, line out */ + size_t len; + const char *prompt = lua_tolstring(L, 1, &len); + char buffer[L_ctermid]; + const char *devtty = ctermid(buffer); /* 20130919 1.1 */ + FILE *tty_stream; + if (devtty != NULL) { + tty_stream = fopen(devtty, "a+"); + if (tty_stream != NULL) { + /* int tty_fd = fileno(tty_stream); */ + rl_instream = tty_stream; + rl_outstream = tty_stream; + } + } + /* rl_catch_sigwinch = 0; rl_set_signals(); no effect :-( 1.3 */ + char *line = readline(prompt); /* 3.2 it's not a const */ + /* rl_cleanup_after_signal(); rl_clear_signals(); no effect :-( 1.3 */ + /* lua_pushstring(L, line); */ + /* 3.2 did lua_pushstring create a copy of the string ? */ + /* lua_pushfstring(L, "%s", line); 3.2 */ + if (line == NULL) { /* 3.3 fix by zash.se, Prosody developer */ + lua_pushnil(L); + } else { + lua_pushfstring(L, "%s", line); + // lua_pushstring(L, line); should be fine as well + } + if (tty_stream != NULL) { fclose(tty_stream); } + free(line); /* 3.2 fixes memory leak */ + return 1; +} + +static int c_tabcompletion(lua_State *L) { /* Lua stack: is_on */ + int is_on = lua_toboolean(L, 1); + if (is_on) { + rl_bind_key ('\t', rl_complete); + } else { + rl_bind_key ('\t', rl_insert); + } + return 0; +} + +static int c_history_length(lua_State *L) { /* void in, length out */ + lua_Integer n = history_length; + lua_pushinteger(L, n); + return 1; +} + +static int c_using_history(lua_State *L) { /* void in, void out */ + using_history(); + return 0; +} + +static int c_clear_history(lua_State *L) { /* void in, void out */ + clear_history(); + return 0; +} + +static int c_add_history(lua_State *L) { /* Lua stack: str to be added */ + size_t len; + const char *str = lua_tolstring(L, 1, &len); + add_history(str); + return 0; +} + +static int c_append_history(lua_State *L) { /* num,filename in, rc out */ + lua_Integer num = lua_tointeger(L, 1); + size_t len; + const char *filename = lua_tolstring(L, 2, &len); + lua_Integer rc = append_history(num, filename); + lua_pushinteger(L, rc); + return 1; +} + +static int c_read_history(lua_State *L) { /* filename in, returncode out */ + size_t len; + const char *filename = lua_tolstring(L, 1, &len); + lua_Integer rc = read_history(filename); + lua_pushinteger(L, rc); + return 1; + /* so maybe we should provide access to char *strerror(int errnum); */ +} + +static int c_strerror(lua_State *L) { /* errnum in, errstr out */ + lua_Integer errnum = lua_tointeger(L, 1); + const char * str = strerror(errnum); + lua_pushstring(L, str); + return 1; +} + +static int c_stifle_history(lua_State *L) { /* Lua stack: num */ + lua_Integer num = lua_tointeger(L, 1); + stifle_history(num); + return 0; +} + +/* unused ... +static int c_write_history(lua_State *L) { // filename in, returncode out + size_t len; + const char *filename = lua_tolstring(L, 1, &len); + lua_Integer rc = write_history(filename); + lua_pushinteger(L, rc); + return 1; +} +*/ + +static int c_history_truncate_file(lua_State *L) { /* filename,num in rc out */ + size_t len; + const char *filename = lua_tolstring(L, 1, &len); + lua_Integer num = lua_tointeger(L, 2); + lua_Integer rc = history_truncate_file(filename, num); + lua_pushinteger(L, rc); + return 1; +} + +/* ------------------ alternate interface ---------------------- */ + +/* + * saves the last given callback handler and its Lua state + * + * ouch: this is not reentrant! + */ +static int alternate_interface_callback = LUA_NOREF; +static lua_State *last_state = NULL; +static FILE *callback_tty_stream; /* <-- new */ + +/* + * calls the registered callback handler with `line` + */ +static void handler_calls_lua_callback (char *line) { + lua_rawgeti(last_state, LUA_REGISTRYINDEX, alternate_interface_callback); + lua_pushstring(last_state, line); + lua_call(last_state, 1, 0); +} + +static int c_callback_handler_install(lua_State *L) { + char buffer[L_ctermid]; + const char *prompt; + /* copied from c_readline */ + const char *devtty = ctermid(buffer); /* 20130919 1.1 */ + if (devtty != NULL) { + callback_tty_stream = fopen(devtty, "a+"); + if (callback_tty_stream != NULL) { + rl_instream = callback_tty_stream; + rl_outstream = callback_tty_stream; + } + } + prompt = luaL_checkstring(L, 1); + luaL_checktype(L, 2, LUA_TFUNCTION); + luaL_unref(L, LUA_REGISTRYINDEX, alternate_interface_callback); + alternate_interface_callback = luaL_ref(L, LUA_REGISTRYINDEX); + rl_callback_handler_install(prompt, handler_calls_lua_callback); + last_state = L; + return 0; +} + +static int c_callback_read_char(lua_State *L) { + rl_callback_read_char(); + return 0; +} + +#ifdef RL_VERSION_MAJOR +#if RL_VERSION_MAJOR >= 7 +static int c_callback_sigcleanup(lua_State *L) { + rl_callback_sigcleanup(); + return 0; +} +#endif +#endif + +static int c_callback_handler_remove(lua_State *L) { + if (callback_tty_stream != NULL) { fclose(callback_tty_stream); } /* new */ + rl_callback_handler_remove(); + return 0; +} + +/* --------------- interface to custom completion ------------- */ + +/* + * this isn't reentrant either — and reuses last_state + */ + +static int complete_callback = LUA_NOREF; +static char **completions = NULL; + +char *dummy_generator(const char *text, int state) { + return completions[state]; +} + +static char **handler_calls_completion_callback(const char *text, int start, int end) { + size_t i; /* ? int ? */ + size_t number_of_completions; + + rl_attempted_completion_over = 1; + lua_settop(last_state, 0); /* 2.1 */ + lua_rawgeti(last_state, LUA_REGISTRYINDEX, complete_callback); + lua_pushstring(last_state, rl_line_buffer); + lua_pushinteger(last_state, (lua_Integer) start+1); + lua_pushinteger(last_state, (lua_Integer) end+1); + lua_call(last_state, 3, 1); + luaL_checktype(last_state, 1, LUA_TTABLE); + /* lua_rawlen is not available in lua5.1. Use lua_objlen instead */ + /* http://www.lua.org/manual/5.1/manual.html#lua_objlen */ +#if LUA_VERSION_NUM >= 502 + number_of_completions = lua_rawlen(last_state, 1); +#else + number_of_completions = lua_objlen(last_state, 1); +#endif + if (!number_of_completions) return NULL; + + /* malloc never fails due to overcommit */ + completions = malloc(sizeof(char *)*(1+number_of_completions)); + + for (i = 0; i < number_of_completions; i++) { + size_t length; + const char *tmp; + lua_rawgeti(last_state, 1, i+1); + tmp = luaL_checkstring(last_state, 2); +#if LUA_VERSION_NUM >= 502 + length = 1 + lua_rawlen(last_state, 2); +#else + length = 1 + lua_objlen(last_state, 2); +#endif + completions[i] = malloc(sizeof(char)*length); + strncpy(completions[i], tmp, length); + lua_remove(last_state, 2); + } + + /* sentinel NULL means: end of list */ + completions[number_of_completions] = NULL; + + return rl_completion_matches(text, dummy_generator); +} + +static int c_set_readline_name(lua_State *L) { + luaL_checktype(L, 1, LUA_TSTRING); + rl_readline_name = (const char *) lua_tolstring(L, 1, NULL); /* 2.8 */ + return 0; +} + +static int c_set_complete_function(lua_State *L) { + luaL_checktype(L, 1, LUA_TFUNCTION); + luaL_unref(L, LUA_REGISTRYINDEX, complete_callback); + complete_callback = luaL_ref(L, LUA_REGISTRYINDEX); + rl_attempted_completion_function = handler_calls_completion_callback; + last_state = L; + return 0; +} + +static int c_set_default_completer(lua_State *L) { + rl_attempted_completion_function = NULL; + return 0; +} + +static int c_set_completion_append_character(lua_State *L) { /* 2.2 */ + size_t len; + const char *s = lua_tolstring(L, -1, &len); /* PiL4 p.280 */ + rl_completion_append_character = s[0]; + return 0; +} + +/* ----------------- evolved from C-midialsa.c ---------------- */ +struct constant { /* Gems p. 334 */ + const char * name; + int value; +}; +static const struct constant constants[] = { + /* {"Version", Version}, */ + {NULL, 0} +}; + +static const luaL_Reg prv[] = { /* private functions */ + {"add_history", c_add_history}, + {"append_history", c_append_history}, + {"clear_history", c_clear_history}, + {"history_length", c_history_length}, + {"history_truncate_file", c_history_truncate_file}, + {"read_history", c_read_history}, + {"readline", c_readline}, + {"stifle_history", c_stifle_history}, + {"strerror", c_strerror}, + {"tabcompletion", c_tabcompletion}, + {"using_history", c_using_history}, + {"callback_handler_install", c_callback_handler_install}, + {"callback_read_char", c_callback_read_char}, +#ifdef RL_VERSION_MAJOR +#if RL_VERSION_MAJOR >= 7 + {"callback_sigcleanup", c_callback_sigcleanup}, +#endif +#endif + {"callback_handler_remove", c_callback_handler_remove}, + {"set_readline_name", c_set_readline_name}, + {"set_complete_function", c_set_complete_function}, + {"set_default_complete_function", c_set_default_completer}, + {"set_completion_append_character", c_set_completion_append_character}, + {NULL, NULL} +}; + +static int initialise(lua_State *L) { /* Lua Programming Gems p. 335 */ + /* Lua stack: aux table, prv table, dat table */ + int index; /* define constants in module namespace */ + for (index = 0; constants[index].name != NULL; ++index) { + lua_pushinteger(L, constants[index].value); + lua_setfield(L, 3, constants[index].name); + } + /* lua_pushvalue(L, 1); * set the aux table as environment */ + /* lua_replace(L, LUA_ENVIRONINDEX); + unnecessary here, fortunately, because it fails in 5.2 */ + lua_pushvalue(L, 2); /* register the private functions */ +#if LUA_VERSION_NUM >= 502 + luaL_setfuncs(L, prv, 0); /* 5.2 */ + return 0; +#else + luaL_register(L, NULL, prv); /* 5.1 */ + return 0; +#endif +} + +int luaopen_readline(lua_State *L) { + lua_pushcfunction(L, initialise); + return 1; +} + diff --git a/doc/readline.html b/doc/readline.html new file mode 100644 index 0000000..c6bc850 --- /dev/null +++ b/doc/readline.html @@ -0,0 +1,474 @@ +readline.lua + + + + +
+

 +      +readline.lua +

+ + + + + +
+ + + + + + + +
+ + + +

+

+
+

NAME

+

readline - a simple interface +to the readline and history libraries

+

+

+
+

SYNOPSIS

+
+ local RL = require 'readline'
+ RL.set_options{ keeplines=1000, histfile='~/.synopsis_history' }
+ RL.set_readline_name('fennel')
+
+ -- the Standard Interface
+ local str = RL.readline('Please enter some filename: ')
+ local save_options = RL.set_options{ completion=false }
+ str = RL.readline('Please type a line which can include Tabs: ')
+ RL.set_options(save_options)
+ str = RL.readline('Now tab-filename-completion is working again: ')
+ ...
+
+ -- the Alternate Interface
+ local poll = require 'posix.poll'.poll
+ local line = nil
+ local linehandler = function (str)
+    RL.add_history(str)
+    RL.handler_remove()
+    line = str
+ end
+ RL.handler_install("prompt> ", linehandler)
+ local fds = {[0] = {events={IN={true}}}}
+ while true do
+    poll(fds, -1)
+    if fds[0].revents.IN then
+       RL.read_char()  -- only if there's something to be read
+    else
+       -- do some useful background task
+    end
+    if line then break end
+ end
+ print("got line: " .. line)
+
+ -- Custom Completion
+ local reserved_words = {
+   'and', 'assert', 'break', 'do', 'else', 'elseif', 'end', 'false',
+   'for', 'function', 'if', 'ipairs', 'local', 'nil', 'not', 'pairs',
+   'print', 'require', 'return', 'then', 'tonumber', 'tostring',
+   'true', 'type', 'while',
+ }
+ RL.set_complete_list(reserved_words)
+ line = RL.readline('now it expands lua reserved words: ')
+
+ ...
+ RL.save_history() ; os.exit()
+
+

+

+
+

DESCRIPTION

+

This Lua module offers a simple calling interface +to the GNU Readline/History Library. +

+The function +readline() +is a wrapper, which invokes the GNU +readline, adds the line to the end of the History List, +and then returns the line. Usually you call +save_history() +before the program exits, +so that the History List is saved to the histfile. +

+Various options can be changed using the +set_options{} +function. +

+The user can configure the GNU Readline +(e.g. vi or emacs keystrokes ?) +with their individual ~/.inputrc file, +see the INITIALIZATION FILE section of man readline.

+

+By default, the GNU readline library dialogues with the user +by reading from stdin and writing to stdout; +this fits very badly with applications that want to +use stdin and stdout to input and output data. +Therefore, this Lua module dialogues with the user on the controlling-terminal +of the process (typically /dev/tty) as returned by ctermid(). +

+Most of + +readline's Alternate Interface is now included, namely   +handler_install,   +read_char   +and +handler_remove.
+Some applications need to interleave keyboard I/O with file, device, +or window system I/O, typically by using a main loop to select() on +various file descriptors. +  To accommodate this need, readline +can also be invoked as a 'callback' function from an event loop, +and the Alternate Interface offers functions to do this.
+The Alternate Interface does offer tab-completion; but it does not +add to the history file, so you will probably want to call +RL.add_history(s) explicitly. +See handler_install() +

+Access to readline's +Custom Completion +is now provided. +

+This module does not work with lua -i +because that runs its own readline, +and the two conflict with each other. +

+ +
+

STANDARD INTERFACE

+

+

+ +

RL.set_options{ histfile='~/.myapp_history', keeplines=100 }

+

Returns the old options, so they can be restored later. +The auto_add option controls whether the line entered will be +added to the History List, +The default options are:
+   auto_add = true,
+   histfile = '~/.rl_lua_history',
+   keeplines = 500,
+   completion = true,
+   ignoredups = true,
+   minlength = 2,

+

Lines shorter than the minlength option will not be put on the +History List. Tilde expansion is performed on the histfile option. +The histfile option must be a string, so don't set it to nil. +If you want to avoid reading or writing your History List to the filesystem, +set histfile to the empty string. +So if you want no history behaviour (Up or Down arrows etc.) at all, then set +
+   set_options{ histfile='', auto_add=false, }
+ +

RL.set_readline_name( 'myapp' )

+

+Sets the internal libreadline variable rl_readline_name +for use with conditional directives in .inputrc +(see the +manual). +
It should be +invoked once, before calling readline(), +so that the name is set before .inputrc is sourced. +
+For example: if, in the initialization before first readline prompt, you +
+   RL.set_readline_name('fennel') +

+then the call to readline() +would execute this conditional in .inputrc
+   $if fennel
+     set blink-matching-paren On
+     set enable-bracketed-paste On
+   $endif +
+

+ +

RL.readline( prompt )

+

Displays the prompt and returns the text of the line the +user enters. A blank line returns the empty string. +If EOF is encountered while reading a line, and the line is empty, +nil is returned; +if an EOF is read with a non-empty line, it is treated as a newline.

+

If the auto_add option is true (which is the default), +the line the user enters will be added to the History List, +unless it's shorter than minlength, or it's the same +as the previous line and the ignoredups option is set.

+

+

+

RL.save_history()

+

Normally, you should call this function once, just before your program +exits. +It saves the lines the user has entered onto the end of the histfile +file. Then if necessary it truncates lines off the beginning of the +histfile to confine it to keeplines long.

+

+

+

RL.add_history( line )

+

Adds the line to the History List.
+With the Standard Interface, you'll only need this function +if you want to assume complete control over the strings that get added, +in which case you set: +
  + RL.set_options{ auto_add=false, }
+and then after calling readline(prompt) +you can process the line as you wish +and call add_history(line) if appropriate. +

+But with the Alternative Interface, you have to call +add_history(line) yourself, even if {auto_add=true}
+You should do this in the linehandler function, see below. + +

+

ALTERNATE INTERFACE

+Some applications need to interleave keyboard I/O with file, device, +or window system I/O, by using a main loop to select() on +various file descriptors. +
With the Alernate Interface, readline can +be invoked as a 'callback' function from an event loop. +

+The Alternate Interface does not add to the history file, +so you will probably want to call +RL.add_history(s) explicitly.
+You should do this within the linehandler function !
+(This constraint is due to what may be an unadvertised quirk +of libreadline.) +

+ +

RL.handler_install( prompt, linehandlerfunction )

+

+This function sets up the terminal, installs a linehandler function +that will receive the text of the line as an argument, +and displays the string prompt.   +A typical linehandler function might be:
+  linehandler = function (str)
+    RL.add_history(str)
+    RL.handler_remove()
+    line = str   -- line is a global, or an upvalue
+  end
+
+ +

+ +

RL.read_char()

+

+Whenever an application determines that keyboard input is available, +it should call read_char(), +which will read the next character from the current input source. +If that character completes the line, read_char will invoke the +linehandler function installed by handler_install to process the line. +
+Before calling the linehandler function, the terminal settings are reset to +the values they had before calling handler_install. +If the linehandler function returns, and the line handler remains installed, +the terminal settings are modified for Readline's use again. +EOF is indicated by calling the linehandler handler with a nil line. +

+ +

RL.handler_remove()

+

+Restore the terminal to its initial state and remove the line handler. +You may call this function from within the linehandler as well as independently. +If the linehandler function does not exit the program, +this function should be called before the program exits +to reset the terminal settings. +

+ +

CUSTOM COMPLETION

+ +

RL.set_complete_list( array_of_strings )

+

+This function sets up custom completion of an array of strings. +
+For example, the array_of_strings +might be the dictionary-words of a language, +or the reserved words of a programming language. +

+ +

RL.set_complete_function( completer_function )

+

+This is the lower-level function on which +set_complete_list() is based. +Its argument is a function which takes three arguments: +the text of the line as it stands, +and the indexes from and to, +which delimit the segment of the text (for example, the word) +which is to be completed. +This syntax is the same as string.sub(text, from, to) +
+The completer_function must return an array of the possible +completions. +
+For example, the completer_function of +set_complete_list() is: +

  local completer_function = function(text, from, to)
+     local incomplete = string.sub(text, from, to)
+     local matches = {}
+     for i,v in ipairs(array_of_strings) do
+        if incomplete == string.sub(v, 1, #incomplete) then
+           matches[1 + #matches] = v
+        end
+     end
+     return matches
+  end
+
+but the completer_function can also have more complex behaviour. +Because it knows the contents of the line so far, +it could ask for a date in format 18 Aug 2018 +and offer three different completions for the three different fields.
+Or if the line so far seems to be in +Esperanto +it could offer completions in Esperanto, and so on. +

+By default, after every completion readline appends a space +to the string, so you can start the next word. +You can change this space to another character by calling +set_completion_append_character(s), +which sets the append_character +to the first byte of the string s. +For example, this sets it to the empty string:
+   RL.set_completion_append_character('')
+It only makes sense to call set_completion_append_character +from within a completer_function.
+After the completer_function has executed, +the readline library resets the append_character to the default space. +
+Setting the append_character to +',' or +':' or +'.' or +'-' +may not behave as you expect when trying to tab-complete the +following word, because readline treats those characters +as being part of a 'word', not as a delimiter between words. +

+

+

+
+

INSTALLATION

+

This module is available as a LuaRock in + +luarocks.org/modules/peterbillam +so you should be able to install it with the command:
+   $ su
+   Password:
+   # luarocks install readline
+

+or:
+  # luarocks install https://pjb.com.au/comp/lua/readline-3.2-0.rockspec + +

+If this results in an error message such as:
+ +   Error: Could not find expected file libreadline.a, or libreadline.so,
+then you need to find the appropriate directory with:
+   find /usr/lib -name 'libreadline.*' -print
+and then invoke:
+   luarocks install readline\
+   READLINE_INCDIR=/usr/local/Cellar/readline/8.1/include \
+   READLINE_LIBDIR=/usr/local/Cellar/readline/8.1/lib \
+   HISTORY_INCDIR=/usr/local/Cellar/readline/8.1/include \
+   HISTORY_LIBDIR=/usr/local/Cellar/readline/8.1/lib # or wherever

+accordingly. +

+It depends on the readline library and its header-files; +for example on Debian you may need:
+   # aptitude install libreadline6 libreadline6-dev +

+or on Centos you may need:
+   # yum install readline-devel

+

+You can see the source-code in:
+   https://pjb.com.au/comp/lua/readline-3.2.tar.gz +

+
+

CHANGES

+
+ 20221001 3.2 fix a memory leak in readline()
+ 20220420 3.1 reset OldHistoryLength if histfile gets set
+ 20210418 3.0 pass READLINE_INCDIR and READLINE_LIBDIR to gcc
+ 20210127 2.9 fix version number again
+ 20210106 2.8 include string.h
+ 20200801 2.7 add lua 5.4
+ 20200409 2.6 jaawerth: added set_readline_name()
+ 20190110 2.5 fix a lua_rawlen v. lua_objlen bug if using lua 5.1
+ 20180924 2.2 add set_completion_append_character
+ 20180912 2.1 C code stack-bug fix in handler_calls_completion_callback
+ 20180910 2.0 add set_complete_list and set_complete_function
+ 20180827 1.9 add handler_install read_char and handler_remove
+ 20151020 1.8 readline() returns nil correctly on EOF
+ 20150421 1.7 include lua5.3, and move pod and doc back to luarocks.org
+ 20150416 1.6 readline specified as an external dependency
+ 20140608 1.5 switch pod and doc over to using moonrocks
+ 20140519 1.4 installs as readline not Readline under luarocks 2.1.2
+ 20131031 1.3 readline erases final space if tab-completion is used
+ 20131020 1.2 set_options{histfile='~/d'} expands the tilde
+ 20130921 1.1 uses ctermid() (usually /dev/tty) to dialogue with the user
+ 20130918 1.0 first working version
+

+

+
+

AUTHORS

+

Peter Billam   +https://pjb.com.au/comp/contact.html
+Alexander Adler, University of Frankfurt, contributed the +Alternate Interface and +Custom Completion
+Jesse Wertheim, one of the developers of +Fennel, +contributed the +set_readline_name() function,
+wenxichang fixed the memory leak in +readline(). +

+
+

SEE ALSO

+ +

+ man readline   + http://www.gnu.org/s/readline
+ https://tiswww.case.edu/php/chet/readline/readline.html
+ +https://tiswww.case.edu/php/chet/readline/readline.html#SEC28 +  Readline Variables
+ https://tiswww.case.edu/php/chet/readline/readline.html#SEC41
+ https://tiswww.case.edu/php/chet/readline/readline.html#SEC45
+ /usr/share/readline/inputrc +   ~/.inputrc
+ http://lua-users.org/wiki/CompleteWithReadline
+ http://luaposix.github.io/luaposix
+ fennel-lang.org/
+ terminfo.lua
+ https://pjb.com.au
+ https://pjb.com.au/comp/index.html#lua +

+ +
+ + + diff --git a/readline-3.3-0.rockspec b/readline-3.3-0.rockspec new file mode 100644 index 0000000..7b958d9 --- /dev/null +++ b/readline-3.3-0.rockspec @@ -0,0 +1,44 @@ +package = "Readline" +version = "3.3-0" +source = { + url = "git+https://github.com/azadon/lua-readline.git", + tag = "3.3.0" +} +description = { + summary = "Interface to the readline library", + detailed = [[ + This Lua module offers a simple calling interface + to the GNU Readline/History Library. + ]], + homepage = "https://peterbillam.gitlab.io/pjb_lua/lua/readline.html", + license = "MIT/X11", +} +-- http://www.luarocks.org/en/Rockspec_format +dependencies = { + "lua >=5.1, <5.5", + "luaposix >= 30", +} +external_dependencies = { -- Duarn 20150216, 20150416 + READLINE = { + header = "readline/readline.h"; + library = "readline"; + }; + HISTORY = { + header = "readline/history.h"; + library = "history"; + } +} +build = { + type = "builtin", + modules = { + ["readline"] = "readline.lua", + ["C-readline"] = { + sources = { "C-readline.c" }, + incdirs = { "$(READLINE_INCDIR)", "$(HISTORY_INCDIR)" }, --20210418 + libdirs = { "$(READLINE_LIBDIR)", "$(HISTORY_LIBDIR)" }, --20210418 + libraries = { "readline", "history" }, + }, + }, + copy_directories = { "doc", "test" }, +} + diff --git a/readline.lua b/readline.lua new file mode 100644 index 0000000..939a054 --- /dev/null +++ b/readline.lua @@ -0,0 +1,603 @@ +--------------------------------------------------------------------- +-- This Lua5 module is Copyright (c) 2011, Peter J Billam -- +-- www.pjb.com.au -- +-- -- +-- This module is free software; you can redistribute it and/or -- +-- modify it under the same terms as Lua5 itself. -- +--------------------------------------------------------------------- + +local M = {} -- public interface +M.Version = '3.1' -- reset OldHistoryLength if histfile gets set +M.VersionDate = '20apr2022' + +--[[ +Alexander Adler suggests adding four Alternate-Interface functions: +https://tiswww.case.edu/php/chet/readline/readline.html#SEC41 +void rl_callback_handler_install (const char *prompt, rl_vcpfunc_t *lhandler) + Set up the terminal for readline I/O and display the initial expanded + value of prompt. Save the value of lhandler to use as a handler function + to call when a complete line of input has been entered. The handler + function receives the text of the line as an argument. As with readline(), + the handler function should free the line when it it finished with it. +void rl_callback_read_char (void) +void rl_callback_sigcleanup (void) +void rl_callback_handler_remove (void) +]] + +if string.tonumber then tonumber = string.tonumber end -- 5.4 + +-------------------- private utility functions ------------------- +local function warn(str) io.stderr:write(str,'\n') end +local function die(str) io.stderr:write(str,'\n') ; os.exit(1) end +local function qw(s) -- t = qw[[ foo bar baz ]] + local t = {} ; for x in s:gmatch("%S+") do t[#t+1] = x end ; return t +end +local function deepcopy(object) -- http://lua-users.org/wiki/CopyTable + local lookup_table = {} + local function _copy(object) + if type(object) ~= "table" then + return object + elseif lookup_table[object] then + return lookup_table[object] + end + local new_table = {} + lookup_table[object] = new_table + for index, value in pairs(object) do + new_table[_copy(index)] = _copy(value) + end + return setmetatable(new_table, getmetatable(object)) + end + return _copy(object) +end +local function sorted_keys(t) + local a = {} + for k,v in pairs(t) do a[#a+1] = k end + table.sort(a) + return a +end +local function touch(fn) + local f=io.open(fn,'r') -- or check if posix.stat(path) returns non-nil + if f then + f:close(); return true + else + f=io.open(fn,'w') + if f then + f:write(""); f:close(); return true + else + return false + end + end +end +local function homedir(user) + if not user and os.getenv('HOME') then return os.getenv('HOME') end + local P = nil + pcall(function() P = require 'posix' ; end ) + if type(P) == 'table' then -- we have posix + if not user then user = P.getpid('euid') end + return P.getpasswd(user, 'dir') or '/tmp' + end + warn('readline: HOME not set and luaposix not installed; using /tmp') + return '/tmp/' +end +local function tilde_expand(filename) + if string.match(filename, '^~') then + local user = string.match(filename, '^~(%a+)/') + local home = homedir(user) + filename = string.gsub(filename, '^~%a*', home) + end + return filename +end + + +---------------- from Lua Programming Gems p. 331 ---------------- +local require, table = require, table -- save the used globals +local aux, prv = {}, {} -- auxiliary & private C function tables +local initialise = require 'C-readline' +initialise(aux, prv, M) -- initialise the C lib with aux,prv & module tables + +------------------------ public functions ---------------------- + +prv.using_history() +local Option = { -- the default options + auto_add = true, + completion = true, + histfile = '~/.rl_lua_history', + ignoredups = true, + keeplines = 500, + minlength = 2, +} +local PreviousLine = '' + +function M.read_history () + local histfile = tilde_expand( Option['histfile'] ) + return prv.read_history ( histfile ) +end + +M.read_history( Option['histfile'] ) +local OldHistoryLength = prv.history_length() +-- print('OldHistoryLength='..tostring(OldHistoryLength)) + +------------------------ public functions ---------------------- + + +function M.set_options ( tbl ) + if tbl == nil then return end + if type(tbl) ~= 'table' then + die('set_options: argument must be a table, not '..type(tbl)) + end + local old_options = deepcopy(Option) + for k,v in pairs(tbl) do + if k == 'completion' then + if type(v) ~= 'boolean' then + die('set_options: completion must be boolean, not '..type(v)) + end + prv.tabcompletion ( v ) + Option[k] = v + elseif k == 'histfile' then + if v ~= Option['histfile'] then + if type(v) ~= 'string' then + die('set_options: histfile must be string, not '..type(v)) + end + Option[k] = v + prv.clear_history() + local rc = M.read_history( Option['histfile'] ) -- 1.2 + OldHistoryLength = prv.history_length() -- 3.1 + end + elseif k == 'keeplines' or k == 'minlength' then + if type(v) ~= 'number' then + die('set_options: '..k..' must be number, not '..type(v)) + end + Option[k] = v + elseif k == 'ignoredups' or k == 'auto_add' then + if type(v) ~= 'boolean' then + die('set_options: '..k..' must be boolean, not '..type(v)) + end + Option[k] = v + else + die('set_options: unrecognised option '..tostring(k)) + end + end + return old_options +end + +function M.readline ( prompt ) + prompt = prompt or '' + if type(prompt) ~= 'string' then + die('readline: prompt must be a string, not '..type(prompt)) + end + local line = prv.readline ( prompt ) -- might be nil if EOF... + if line == nil then return nil end -- 1.8 + if Option['completion'] then + line = string.gsub(line, ' $', '') -- 1.3, 2.0 + end + if Option['auto_add'] and line and line~='' + and string.len(line)>=Option['minlength'] then + if line ~= PreviousLine or not Option['ignoredups'] then + prv.add_history(line) + PreviousLine = line + end + end + return line +end + +function M.add_history ( str ) + if type(str) ~= 'string' then + die('add_history: str must be a string, not '..type(str)) + end + return prv.add_history ( str ) +end + +function M.save_history ( ) + if type(Option['histfile']) ~= 'string' then + die('save_history: histfile must be a string, not ' + .. type(Option['histfile'])) + end + if Option['histfile'] == '' then return end + local histfile = tilde_expand( Option['histfile'] ) + if type(Option['keeplines']) ~= 'number' then + die('save_history: keeplines must be a number, not ' + .. type(Option['keeplines'])) + end + local n = prv.history_length() + if n > OldHistoryLength then + touch(histfile) + local rc = prv.append_history(n-OldHistoryLength, histfile) + if rc ~= 0 then warn('append_history: '..prv.strerror(rc)) end + rc = prv.history_truncate_file ( histfile, Option['keeplines'] ) + if rc ~= 0 then warn('history_truncate_file: '..prv.strerror(rc)) end + -- reset OldHistoryLength in case it's used again ... er: 3.1? + -- OldHistoryLength = n -- is this useful ? + end + return +end + +--[[ +20220420 +https://tiswww.cwru.edu/php/chet/readline/history.html#SEC15 + +Function: int read_history (const char *filename) + Add the contents of filename to the history list, a line at a + time. If filename is NULL, then read from `~/.history'. Returns 0 + if successful, or errno if not. + +Function: int read_history_range (const char *filename, int from, int to) + Read a range of lines from filename, adding them to the history + list. Start reading at line from and end at to. If from is zero, start + at the beginning. If to is less than from, then read until the end of + the file. If filename is NULL, then read from `~/.history'. Returns + 0 if successful, or errno if not. + +Function: int write_history (const char *filename) + Write the current history to filename, overwriting filename if + necessary. If filename is NULL, then write the history list to + `~/.history'. Returns 0 on success, or errno on a read or write error. + +Function: int append_history (int nelements, const char *filename) + Append the last nelements of the history list to filename. If filename + is NULL, then append to `~/.history'. Returns 0 on success, or errno + on a read or write error. + +Function: int history_truncate_file (const char *filename, int nlines) + Truncate the history file filename, leaving only the last nlines + lines. If filename is NULL, then `~/.history' is truncated. Returns + 0 on success, or errno on failure. + +Seems something's going wrong with n-OldHistoryLength ... +]] + + +function M.strerror ( errnum ) + return prv.strerror(tonumber(errnum)) +end + +-------------------- The Alternate Interface ------------------- +function M.handler_install(prompt, linehandlerfunction) + prompt = prompt or '' + if type(prompt) ~= 'string' then + die('handler_install: prompt must be a string, not '..type(prompt)) + end + if type(linehandlerfunction) ~= 'function' then + die('handler_install: linehandlerfunction must be a function, not '.. + type(linehandlerfunction)) + end + prv.callback_handler_install(prompt, linehandlerfunction) +end + +M.read_char = prv.callback_read_char +M.handler_remove = prv.callback_handler_remove +-- M.sigcleanup will be nil unless C-readline.c was +-- compiled with RL_VERSION_MAJOR = 7 or greater +M.sigcleanup = prv.callback_sigcleanup + +-------------------- Custom Completion ------------------------ +M.set_readline_name = prv.set_readline_name +M.set_complete_function = prv.set_complete_function +M.set_default_complete_function = prv.set_default_complete_function +M.set_completion_append_character = prv.set_completion_append_character + +function M.set_complete_list(a) + if type(a) ~= 'table' then + die('set_complete_list: arg must be a table, not '..type(a)) + end + local completer_function = function(text, from, to) + local incomplete = string.sub(text, from, to) + local matches = {} + for i,v in ipairs(a) do + if incomplete == string.sub(v, 1, #incomplete) then + matches[1 + #matches] = v + end + end + return matches + end + M.set_complete_function(completer_function) +end + + +return M + +--[[ + +=pod + +=head1 NAME + +C - a simple interface to the I and I libraries + +=head1 SYNOPSIS + + local RL = require 'readline' + RL.set_options{ keeplines=1000, histfile='~/.synopsis_history' } + + -- the Standard Interface + local str = RL.readline('Please enter some filename: ') + local save_options = RL.set_options{ completion=false } + str = RL.readline('Please type a line which can include Tabs: ') + RL.set_options(save_options) + str = RL.readline('Now tab-filename-completion is working again: ') + ... + + -- the Alternate Interface + local poll = require 'posix.poll'.poll + local line = nil + local linehandler = function (str) + line = str + RL.handler_remove() + RL.add_history(str) + end + RL.handler_install("prompt> ", linehandler) + local fds = {[0] = {events={IN={true}}}} + while true do + poll(fds, -1) + if fds[0].revents.IN then + RL.read_char() -- only if there's something to be read + else + -- do some useful background task + end + if line then break end + end + print("got line: " .. line) + + -- Custom Completion + local reserved_words = { + 'and', 'assert', 'break', 'do', 'else', 'elseif', 'end', 'false', + 'for', 'function', 'if', 'ipairs', 'local', 'nil', 'not', 'pairs', + 'print', 'require', 'return', 'then', 'tonumber', 'tostring', + 'true', 'type', 'while', + } + RL.set_complete_list(reserved_words) + line = RL.readline('now it expands lua reserved words: ') + + ... + RL.save_history() ; os.exit() + +=head1 DESCRIPTION + +This Lua module offers a simple calling interface +to the GNU Readline/History Library. + +The function I is a wrapper, which invokes the GNU +I, adds the line to the end of the History List, +and then returns the line. +Usually you call I before the program exits, +so that the History List is saved to the I. + +Various options can be changed using the I function. + +The user can configure the GNU Readline (e.g. I or I keystrokes ?) +with their individual I<~/.inputrc> file, +see the I section of I. + +By default, the GNU I library dialogues with the user +by reading from I and writing to I; +This fits badly with applications that want to +use I and I to input and output data. +Therefore, this Lua module dialogues with the user on the controlling-terminal +of the process (typically I) as returned by I. + +=head1 STANDARD INTERFACE + +=head3 RL.set_options{ histfile='~/.myapp_history', keeplines=100 } + +Returns the old options, so they can be restored later. +The I option controls whether the line entered will be +added to the History List, +The default options are: + + auto_add = true, + histfile = '~/.rl_lua_history', + keeplines = 500, + completion = true, + ignoredups = true, + minlength = 2, + +Lines shorter than the I option will not be put on the History List. +Tilde expansion is performed on the I option. +The I option must be a string, so don't set it to I, +if you want to avoid reading or writing your History List to the filesystem, +set I to the empty string. +If you want no history behaviour (Up or Down arrows etc.) at all, then set + + set_options{ histfile='', auto_add=false, } + +=head3 RL.readline( prompt ) + +Displays the I and returns the text of the line the user enters. +A blank line returns the empty string. +If EOF is encountered while reading a line, and the line is empty, +I is returned; +if an EOF is read with a non-empty line, it is treated as a newline. + +If the I option is I (which is the default), +the line the user enters will be added to the History List, +unless it's shorter than I, +or it's the same as the previous line and the I option is set. + +=head3 RL.save_history() + +Normally, you should call this function before your program exits. +It saves the lines the user has entered onto the end of the I file. +Then if necessary it truncates lines off the beginning of the I +to confine it to I long. + +=head3 RL.add_history( line ) + +Adds the I to the History List. +You'll only need this function if you want to assume complete control +over the strings that get added, in which case you: + + RL.set_options{ auto_add=false, } + +and then after calling I +you can process the I as you wish +and call I if appropriate. + +=head1 ALTERNATE INTERFACE + +Some applications need to interleave keyboard I/O with file, device, +or window system I/O, by using a main loop to select() on various file +descriptors. +With the Alernate Interface, readline can be invoked as a 'callback' +function from an event loop. +The Alternate Interface does not add to the history file, so you will +probably want to call RL.add_history(s) explicitly + +=head3 RL.handler_install( prompt, linehandlerfunction ) + +This function sets up the terminal, installs a linehandler function +that will receive the text of the line as an argument, and displays the +string prompt. A typical linehandler function might be: + + linehandler = function (str) + RL.add_history(str) + RL.handler_remove() + line = str -- line is a global, or an upvalue + end + +=head3 RL.read_char() + +Whenever an application determines that keyboard input is available, it +should call read_char(), which will read the next character from the +current input source. If that character completes the line, read_char +will invoke the linehandler function installed by handler_install to +process the line. Before calling the linehandler function, the terminal +settings are reset to the values they had before calling +handler_install. If the linehandler function returns, and the line +handler remains installed, the terminal settings are modified for +Readline's use again. EOF is indicated by calling the linehandler +handler with a nil line. +Interface. + +=head3 RL.handler_remove() + +Restore the terminal to its initial state and remove the line handler. +You may call this function from within the linehandler as well as +independently. If the linehandler function does not exit the program, +this function should be called before the program exits to reset the +terminal settings. + +=head1 CUSTOM COMPLETION + +=head3 RL.set_complete_list( array_of_strings ) + +This function sets up custom completion of an array of strings. +For example, the I might be the dictionary-words of a +language, or the reserved words of a programming language. + +=head3 RL.set_complete_function( completer_function ) + +This is the lower-level function on which set_complete_list() is +based. Its argument is a function which takes three arguments: the text +of the line as it stands, and the indexes from and to, which delimit the +segment of the text (for example, the word) which is to be completed. +This syntax is the same as I +The I must return an array of the possible completions. +For example, the I of set_complete_list() is: + + local completer_function = function(text, from, to) + local incomplete = string.sub(text, from, to) + local matches = {} + for i,v in ipairs(array_of_strings) do + if incomplete == string.sub(v, 1, #incomplete) then + matches[1 + #matches] = v + end + end + return matches + end + +but the completer_function can also have more complex behaviour. Because +it knows the contents of the line so far, it could ask for a date in +format B<18 Aug 2018> and offer three different completions for the three +different fields. +Or if the line so far seems to be in Esperanto it could offer completions +in Esperanto, and so on. + +By default, after every completion readline appends a space to the +string, so you can start the next word. You can change this space to +another character by calling set_completion_append_character(s), +which sets the append_character to the first byte of the string B. +For example this sets it to the empty string: + + RL.set_completion_append_character('') + +It only makes sense to call I from within +a completer_function. +After the completer_function has executed, the readline library resets +the append_character to the default space. + +Setting the append_character to C<','> or C<':'> or C<'.'> or C<'-'> may not +behave as you expect when trying to tab-complete the following word, +because I treats those characters as being part of a 'word', +not as a delimiter between words. + +=head1 INSTALLATION + +This module is available as a LuaRock in +http://luarocks.org/modules/peterbillam +so you should be able to install it with the command: + + $ su + Password: + # luarocks install readline + +or: + + # luarocks install http://www.pjb.com.au/comp/lua/readline-2.2-0.rockspec + +It depends on the I library and its header-files; +for example on Debian you may need: + + # aptitude install libreadline6 libreadline6-dev + +or on Centos you may need: + + # yum install readline-devel + +=head1 CHANGES + + 20220420 3.1 reset OldHistoryLength if histfile gets set + 20210418 3.0 pass READLINE_INCDIR and READLINE_LIBDIR to gcc + 20210127 2.9 fix version number again + 20210106 2.8 add set_readline_name() and fix version number + 20200801 2.7 add 5.4 + 20180924 2.2 add set_completion_append_character + 20180912 2.1 C code stack-bug fix in handler_calls_completion_callback + 20180910 2.0 add set_complete_list and set_complete_function + 20180901 1.9 add handler_install read_char and handler_remove + 20151020 1.8 readline() returns nil correctly on EOF + 20150422 1.7 works with lua5.3 + 20140608 1.5 switch pod and doc over to using moonrocks + 20140519 1.4 installs as readline not Readline under luarocks 2.1.2 + 20131031 1.3 readline erases final space if tab-completion is used + 20131020 1.2 set_options{histfile='~/d'} expands the tilde + 20130921 1.1 uses ctermid() (usually /dev/tty) to dialogue with the user + 20130918 1.0 first working version + +=head1 AUTHOR + +Peter Billam, +http://www.pjb.com.au/comp/contact.html + +Alexander Adler, of the University of Frankfurt, contributed the +Alternate Interface functions. + +=head1 SEE ALSO + +=over 3 + + man readline + http://www.gnu.org/s/readline + https://tiswww.case.edu/php/chet/readline/readline.html + https://tiswww.cwru.edu/php/chet/readline/history.html#SEC15 + https://tiswww.case.edu/php/chet/readline/readline.html#SEC41 + https://tiswww.case.edu/php/chet/readline/readline.html#SEC45 + /usr/share/readline/inputrc + ~/.inputrc + http://lua-users.org/wiki/CompleteWithReadline + http://luaposix.github.io/luaposix + http://www.pjb.com.au + http://www.pjb.com.au/comp/index.html#lua + +=back + +=cut +]] diff --git a/test/test_rl.lua b/test/test_rl.lua new file mode 100644 index 0000000..b23ec1e --- /dev/null +++ b/test/test_rl.lua @@ -0,0 +1,263 @@ +#! /usr/local/bin/lua +local RL = require 'readline' +local poll = require 'posix.poll'.poll +-- local TC = require 'testcases' +-- luarocks install http://pjb.com.au/comp/lua/testcases-0.1-0.rockspec + +--------------------------- infrastructure ----------------------- +local eps = .000000001 +function equal(x, y) -- unused here + if #x ~= #y then return false end + local i; for i in pairs(x) do + if math.abs(x[i]-y[i]) > eps then return false end + end + return true +end +-- use Test::Simple tests => 6; +local Test = 73 ; local i_test = 0; local Failed = 0; +function ok(b,s) + i_test = i_test + 1 + if b then + io.write('ok '..i_test..' - '..s.."\n") + return true + else + io.write('not ok '..i_test..' - '..s.."\n") + Failed = Failed + 1 + return false + end +end + +local function qw(s) -- t = qw([[ foo bar baz ]]) + local t = {} + for x in s:gmatch("([-%s]+)") do t[#t+1] = x end + return t +end + +local function uname_minus_s() + local pipe = assert(io.popen('uname -s')) + local uname_output = pipe:read('*all') + pipe:close() + return string.gsub(uname_output, '%s$', '') +end + +-- strict.lua checks uses of undeclared global variables +-- All global variables must be 'declared' through a regular assignment +-- (even assigning nil will do) in a main chunk before being used +-- anywhere or assigned to inside a function. +local mt = getmetatable(_G) +if mt == nil then + mt = {} + setmetatable(_G, mt) +end +mt.__declared = {} +mt.__newindex = function (t, n, v) + if not mt.__declared[n] then + local w = debug.getinfo(2, "S").what + if w ~= "main" and w ~= "C" then + error("assign to undeclared variable '"..n.."'", 2) + end + mt.__declared[n] = true + end + rawset(t, n, v) +end +mt.__index = function (t, n) + if not mt.__declared[n] and debug.getinfo(2, "S").what ~= "C" then + error("variable '"..n.."' is not declared", 2) + end + return rawget(t, n) +end + +----------------------- here we go... ------------------------- + +print('Testing readline.lua '..RL.Version..', '..RL.VersionDate.. + ' on '..uname_minus_s()) + +if not ok(type(RL) == 'table', 'type of RL is table') then + print('type was '..type(RL)) +end + +RL.set_completion_append_character('') +RL.set_completion_append_character(' ') +RL.set_completion_append_character('X') + +-- for k,v in pairs(RL) do print(k,tostring(v)) end +local filename = '/tmp/test_rl_history' +os.remove(filename) +RL.set_options{histfile=filename , ignoredups=false} + +print('About to test the Alternative Interface ...') +local s0 = nil +RL.handler_install("Tab-completion should work: ", function(s) + s0 = s + RL.handler_remove() +end) +local fds = {[0] = {events={IN={true}}}} +while true do + poll(fds, -1) + if fds[0].revents.IN then -- read only if there's something to be read + RL.read_char() + else + -- do some useful background task + end + if s0 then break end -- don't add to the history this time... +end + +print('About to test the standard interface ...') +print('Please make all answers longer than two characters !') +local s1 = RL.readline('Please enter something: ') +if not ok(type(s1)=='string', "readline returned "..s1) then + print('xc='..tostring(xc)..' xv='..tostring(xv)) +end +local s2 = RL.readline('this time Up-arrow should work: ') +local s3 = RL.readline('enter a filename and test Tab-completion: ') +local save = RL.set_options{completion=false} +local s4 = RL.readline('now Tab-completion should be disabled: ') +RL.set_options(save) +local s5 = RL.readline('now it should be re-enabled :-) ') +RL.set_options{auto_add=false} +local s6 = RL.readline('this answer should not get added into the history: ') +RL.set_options(save) +local s7 = RL.readline('now it should be re-enabled :-) ') + +print('About to test the Alternative Interface again ...') +line = nil +local linehandler = function(s) + RL.handler_remove() + RL.add_history(s) + line = s +end +RL.handler_install("Please enter something: ", linehandler) +fds = {[0] = {events={IN={true}}}} +while true do + poll(fds, -1) + if fds[0].revents.IN then -- read only if there's something to be read + RL.read_char() + else + -- do some useful background task + end + if line then s8 = line ; break end +end + +local reserved_words = { + 'and', 'assert', 'break', 'do', 'else', 'elseif', 'end', + 'false', 'for', 'function', 'if', 'ipairs(', + 'io.flush(', 'io.input(', 'io.lines(', 'io.open(', 'io.output(', + 'io.popen(', 'io.read(', 'io.seek(', 'io.setvbuf(', 'io.stderr', + 'io.stdin', 'io.stdout', 'io.tmpfile(', 'io.write(', + 'local', + 'math.asin(', 'math.ceil(', 'math.cos(', 'math.deg(', 'math.floor(', + 'math.huge(', 'math.max(', 'math.maxinteger(', 'math.mod(', 'math.rad(', + 'math.random(', 'math.randomseed(', 'math.sin(', 'math.tan(', + 'math.tointeger(', 'math.type(', 'math.ult(', + 'nil', 'not', 'print', 'require', 'return', + 'os.clock(', 'os.date(', 'os.difftime(', 'os.execute(', 'os.exit(', + 'os.getenv(', 'os.remove(', 'os.rename(', 'os.time(', + 'string.byte(', 'string.char(', 'string.dump(', 'string.find(', + 'string.format(', 'string.gmatch(', 'string.gsub(', 'string.len(', + 'string.lower(', 'string.match(', 'string.pack(', 'string.rep(', + 'string.sub(', 'string.unpack(', 'string.unpack(', 'string.upper(', + 'table.concat(', 'table.insert(', 'table.move(', 'table.pack(', + 'table.remove(', 'table.sort(', 'table.unpack(', + 'then', 'tostring(', 'tonumber(', 'true', 'type', 'while', 'pairs(', 'print', +} +RL.set_complete_list(reserved_words) +local s9 = RL.readline('now it expands lua reserved words: ') + +line = nil +RL.handler_install("the same but with the Alternate Interface: ", linehandler) +-- fds = {[0] = {events={IN={true}}}} +-- RL.set_complete_list(reserved_words) +while true do + poll(fds, -1) + if fds[0].revents.IN then -- read only if there's something to be read + RL.read_char() + else + -- do some useful background task + end + if line then sA = line ; break end +end +-- print (sA) + +-- print(type(TC.empty_the_stack)) +--[[ +local comp_func = function () + TC.empty_the_stack() + return {'gloop'} +end +RL.set_complete_function(comp_func) +local sB = RL.readline("now the complete_function empties the stack: ") +local reg = debug.getregistry() +for k,v in pairs(reg) do print(k,v) end + +comp_func = function () + TC.dump_the_stack('dummy-string', nil, 42) + return {'gleep'} +end +RL.set_complete_function(comp_func) +local sC = RL.readline("now the complete_function dumps the stack: ") +]] + +comp_func_2 = function () + RL.set_completion_append_character('') + return {'glurp'} +end +RL.set_complete_function(comp_func_2) +local sD = RL.readline("now the completion_append_character is \\0: ") + +comp_func_3 = function () + return {'glork'} +end +RL.set_complete_function(comp_func_3) +local sD = RL.readline("the completion_append_character is ' ' again: ") + +RL.save_history() + +print('Now checking the saved histfile:') +local F = assert(io.open(filename)) +local lines = {} +for line in F:lines() do lines[#lines+1] = line end +F:close() +-- os.remove(filename) +if not ok(lines[1] == s1, 'line 1 was '..s1) then + print('lines[1]='..tostring(lines[1])..' s1='..tostring(s1)) +end +if not ok(lines[2] == s2, 'line 2 was '..s2) then + print('lines[2]='..tostring(lines[2])..' s2='..tostring(s2)) +end +if not ok(lines[3] == s3, 'line 3 was '..s3) then + print('lines[3]='..tostring(lines[3])..' s3='..tostring(s3)) +end +if not ok(lines[4] == s4, 'line 4 was '..s4) then + print('lines[4]='..tostring(lines[4])..' s4='..tostring(s4)) +end +if not ok(lines[5] == s5, 'line 5 was '..s5) then + print('lines[5]='..tostring(lines[5])..' s5='..tostring(s5)) +end +if not ok(lines[6] == s7, 'line 6 was '..s7) then + print('lines[6]='..tostring(lines[6])..' s7='..tostring(s7)) +end +if not ok(lines[7] == s8, 'line 7 was '..s8) then + print('lines[7]='..tostring(lines[7])..' s8='..tostring(s8)) +end +if not ok(lines[8] == s9, 'line 8 was '..s9) then + print('lines[8]="'..tostring(lines[8])..'" s9="'..tostring(s9)..'"') +end +if not ok(lines[9] == sA, 'line 9 was '..sA) then + print('lines[9]="'..tostring(lines[9])..'" sA="'..tostring(sA)..'"') +end +--[[ +if not ok(lines[10] == sB, 'line 10 was '..sB) then + print('lines[10]="'..tostring(lines[10])..'" sB="'..tostring(sB)..'"') +end +if not ok(lines[11] == sC, 'line 11 was '..sC) then + print('lines[11]="'..tostring(lines[11])..'" sC="'..tostring(sC)..'"') +end +]] +if Failed == 0 then + print('Passed all '..i_test..' tests :-)') +else + print('Failed '..Failed..' tests out of '..i_test) +end + + +os.exit()