Complete With Readline

lua-users home
wiki

Difference (from prior major revision) (no other diffs)

Changed: 3,358c3
Here is implementation of completion for the stand-alone Lua interpreter lua.
It is based on the GNU Readline library. The standard Lua distribution already
uses Readline optionally in the Makefile, by defining LUA_USE_READLINE.
But Readline is only used for editing input command and for the history
management, not the completion. To add this, lua.c has to install a callback
function in Readline library. To simplify the implementation, this C function
is just in charge to call a Lua function that will make the decisions depending
on the context, and to return the results in the format expected by Readline.

It is indeed very similar in features than Mike Pall's advanced readline patch on LuaPowerPatches.
The difference is that it is written essentially in Lua, making it easier to customize for specific applications.


==C code in lua.c==


#ifdef LUA_USE_READLINE

/* This function is called repeatedly by rl_completion_matches inside
do_completion, each time returning one element from the Lua table. */
static char* table_iterator (const char* text, int state)
{
size_t len;
const char* str;
char* result;
lua_State* L = globalL;
lua_rawgeti(L, -1, state+1);
if(lua_isnil(L, -1))
return NULL;
str = lua_tolstring(L, -1, &len);
result = malloc(len + 1);
strcpy(result, str);
lua_pop(L, 1);
return result;
}

/* This function is called by readline() when the user wants a completion.
It has to return an array of strings that match the word in the context.
do_completion is called with the word to complete and its position inside
the editing buffer. It then calls back Lua function "completion" to do the
hard work, which returns a table of matches. rl_completion_matches helps
in the process to find the common matching part, and to sort the array.
On any error, Lua stack is recovered and NULL is returned. */
static char** do_completion (const char* text, int istart, int iend)
{
char** matches = NULL;
lua_State* L = globalL;
int top;

if(L == NULL)
return NULL;
do {
top = lua_gettop(L);
lua_getglobal(L, "completion");
if(!lua_isfunction(L, -1))
break;
lua_pushstring(L, text);
lua_pushstring(L, rl_line_buffer);
lua_pushinteger(L, istart+1);
lua_pushinteger(L, iend+1);
if(lua_pcall(L, 4, 1, 0))
break;
if(!lua_istable(L, -1))
break;
matches = rl_completion_matches (text, table_iterator);
} while(0);
lua_settop(L, top);
return (matches);
}

/* Initialise Readline for completion. It should be called from main().*/
static void init_completion(lua_State* L)
{
rl_attempted_completion_function = do_completion;
/* This is a list of Lua operators, separating words. */
rl_basic_word_break_characters = " \t\n\"\\'><=;+-*/%^~#{}()[].,";
/* Inhibits the default added space on completed words. */
rl_completion_append_character = 0;
/* The completion Lua function is loaded here. The user might prefer
to "require" a module or incorporate it into an existing script. */
luaL_dofile(L, "complete.lua");
}

#endif

==Lua Code complete.lua==


-- This function is called back by C function do_completion, itself called
-- back by readline library, in order to complete the current input line.
function completion(word, line, startpos, endpos)
-- The complete list of Lua keywords
local keywords = {
'and', 'break', 'do', 'else', 'elseif', 'end', 'false', 'for',
'function', 'if', 'in', 'local', 'nil', 'not', 'or', 'repeat',
'return', 'then', 'true', 'until', 'while' }

-- Helper function registering possible completion words, verifying matches.
local matches = { }
local function add(value)
value = tostring(value)
if value:match("^"..word) then
matches[#matches+1] = value
end
end

-- This function does the same job as the default completion of readline,
-- completing paths and filenames. Rewritten because
-- rl_basic_word_break_characters is different.
-- Uses LuaFileSystem (lfs) module for this task.
local function filename_list(str)
local path, name = str:match("(.*)[\\/]+(.*)")
path = (path or ".").."/"
name = name or str
for f in lfs.dir(path) do
if (lfs.attributes(path..f) or {}).mode == 'directory' then
add(f.."/")
else
add(f)
end
end
end

-- This function makes a guess of the next character following an identifier,
-- based on the type of the value it holds.
local function postfix(value)
local t = type(value)
if t == 'function' or (getmetatable(value) or {}).__call then
return '('
elseif t == 'table' and #value > 0 then
return '['
elseif t == 'table' then
return '.'
else
return ' '
end
end

-- This function is called in a context where a keyword or a global
-- variable can be inserted. Local variables cannot be listed!
local function add_globals()
for _,k in ipairs(keywords) do
add(k..' ')
end
for k,v in pairs(_G) do
add(k..postfix(v))
end
end

-- Main completion function. It evaluates the current sub-expression
-- to determine its type. Currently supports tables fields, global
-- variables and function prototype completion.
local function contextual_list(expr, sep, str)
if str then return filename_list(str) end
if expr == nil or expr == "" then return add_globals() end
local v = loadstring("return "..expr)
if not v then return end
v = v()
local t = type(v)
if sep == '.' then
if t ~= 'table' then return end
for k,v2 in pairs(v) do
if type(k) == 'string' then
add(k..postfix(v2))
end
end
elseif sep == '[' then
if t ~= 'table' then return end
for k,v2 in pairs(v) do
if type(k) == 'number' then
add(k.."]"..postfix(v2))
end
end
if word ~= "" then add_globals() end
elseif sep == '(' then
-- This is a great place to return the prototype of the function,
-- in case your application has some mean to know it.
-- The following is just a useless example:
add("function "..expr.." (...)"..string.rep(" ", 40))
add("°") -- display trick
end
end

-- This complex function tries to simplify the input line, by removing
-- literal strings, full table constructors and balanced groups of
-- parentheses. Returns the sub-expression preceding the word, the
-- separator item ( '.', '[', '(' ) and the current string in case
-- of an unfinished string literal.
function simplify_expression(expr)
-- replace annoying sequences \' and \" inside literal strings
expr = expr:gsub("\\(['\"])", function(c) return
string.format("\\%03d", string.byte(c)) end)
local curstring
-- remove (finished and unfinished) literal strings
while true do
local idx1,_,equals = expr:find("%[(=*)%[")
local idx2,_,sign = expr:find("(['\"])")
if idx1 == nil and idx2 == nil then break end
local idx,startpat,endpat
if (idx1 or math.huge) < (idx2 or math.huge) then
idx,startpat,endpat = idx1, "%["..equals.."%[", "%]"..equals.."%]"
else
idx,startpat,endpat = idx2, sign, sign
end
if expr:sub(idx):find("^"..startpat..".-"..endpat) then
expr = expr:gsub(startpat.."(.-)"..endpat, " STRING ")
else
expr = expr:gsub(startpat.."(.*)", function(str)
curstring = str; return "(CURSTRING " end)
end
end
expr = expr:gsub("%b()"," PAREN ") -- remove groups of parentheses
expr = expr:gsub("%b{}"," TABLE ") -- remove table constructors
-- avoid two consecutive words without operator
expr = expr:gsub("(%w)%s+(%w)","%1|%2")
expr = expr:gsub("%s","") -- remove now useless spaces
-- This main regular expression looks for table indexes and function calls.
-- You may have to complete it depending on your application.
return curstring,expr:match("([%.%w%[%]_]-)([%.%[%(])"..word.."$")
end

-- Now calls the processing functions and returns the list of results.
local str, expr, sep = simplify_expression(line:sub(1,endpos))
contextual_list(expr, sep, str)
return matches
end


-- PatrickRapin


==readline as standalone lua module==
*If* your Lua interpreter is already compiled with readline, and you don't want to patch it, you can define a module
that does all of the completion hooks setup itself. The following is just a very simple adaption of the previous code, then using the same 'complete.lua' for configuration. I cut down on some of the comments, since the code is partially exactly the same as above.


/* build@ cc -shared readline.c -o readline.so
** Completely and absolutely minimal binding to the readline library
** Steve Donovan, 2007
** Merged with http://lua-users.org/wiki/CompleteWithReadline
** by Ulrik, 2010. I disclaim any copyright on my contributions.
*
* Enable completion in your lua REPL with:
* lua -lreadline -lcomplete
* where complete.lua defines a global function:
* completion (word, line, startpos, endpos) -> array of matches
*/
#include <stdlib.h>
#include <stdio.h>

#include <readline/readline.h>
#include <readline/history.h>

#define LUA_LIB
#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"

/* The readline library uses a lot of global variables,
* so we do too. Lua state for the completion function.
* I'm sorry for this.. */
static lua_State *stateL;


static int f_readline(lua_State* L)
{
const char* prompt = lua_tostring(L,1);
char *line = readline(prompt);
lua_pushstring(L, line);
/* readline return value must be free'd */
free(line);
return 1;
}

static int f_add_history(lua_State* L)
{
if (lua_strlen(L,1) > 0) {
add_history(lua_tostring(L, 1));
}
return 0;
}

static const struct luaL_reg lib[] = {
{"readline", f_readline},
{"add_history",f_add_history},
{NULL, NULL},
};

/* This function is called repeatedly by rl_completion_matches inside
do_completion, each time returning one element from the Lua table. */
static char* table_iterator (const char* text, int state)
{
size_t len;
const char* str;
char* result;
lua_State* L = stateL;
lua_rawgeti(L, -1, state+1);
if(lua_isnil(L, -1))
return NULL;
str = lua_tolstring(L, -1, &len);
result = malloc(len + 1);
strcpy(result, str);
lua_pop(L, 1);
return result;
}

/* This function is called by readline() when the user wants a completion.
... */
static char** do_completion (const char* text, int istart, int iend)
{
char** matches = NULL;
lua_State* L = stateL;
int top;

if(L == NULL)
return NULL;
do {
top = lua_gettop(L);
lua_getglobal(L, "completion");
if(!lua_isfunction(L, -1))
break;
lua_pushstring(L, text);
lua_pushstring(L, rl_line_buffer);
lua_pushinteger(L, istart+1);
lua_pushinteger(L, iend+1);
if(lua_pcall(L, 4, 1, 0))
break;
if(!lua_istable(L, -1))
break;
matches = rl_completion_matches (text, table_iterator);
} while(0);
lua_settop(L, top);
rl_completion_suppress_append = 1;
return (matches);
}

/* Initialise Readline for completion. It should be called from main().*/
static int init_completion(lua_State* L)
{
stateL = L;
rl_attempted_completion_function = do_completion;
/* This is a list of Lua operators, separating words. */
rl_basic_word_break_characters = " \t\n\"\\'><=;+-*/%^~#{}()[].,";
/* Inhibits the default added space on completed words. */
rl_completion_append_character = '\0';
/* The completion Lua function is loaded here. The user might prefer
to "require" a module or incorporate it into an existing script. */
return 0;
}

int luaopen_readline (lua_State *L) {
luaL_openlib (L, "readline", lib, 0);
(void) init_completion(L);
return 1;
}

-- Ulrik 2010 apr
See http://github.com/rrthomas/lua-rlcompleter/ for an implementation of completion for the stand-alone Lua interpreter lua using the GNU Readline library, as already used for command-line editing and history when LUA_USE_READLINE is defined. It is similar Mike Pall's readline patch in LuaPowerPatches, but makes customization easier by calling a Lua function to compute the completions.

Description

See http://github.com/rrthomas/lua-rlcompleter/ for an implementation of completion for the stand-alone Lua interpreter lua using the GNU Readline library, as already used for command-line editing and history when LUA_USE_READLINE is defined. It is similar Mike Pall's readline patch in LuaPowerPatches, but makes customization easier by calling a Lua function to compute the completions.


RecentChanges · preferences
edit · history
Last edited June 14, 2011 4:37 pm GMT (diff)