-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Vladimir Motyka
committed
Mar 20, 2024
1 parent
bd64d15
commit 7ec13b1
Showing
5 changed files
with
1,754 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 <lua.h> | ||
#include <lauxlib.h> | ||
/* #include <string.h> strlen() & friends, including strerror */ | ||
/* #include <unistd.h> isatty() */ | ||
|
||
/* --------------- from man readline -------------------- */ | ||
#include <stdio.h> | ||
#include <stdlib.h> | ||
/* #include <strings.h> 2.8 20210106 strerror is not in string.h ? */ | ||
#include <string.h> | ||
#include <readline/readline.h> | ||
#include <readline/history.h> | ||
/* 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; | ||
} | ||
|
Oops, something went wrong.