You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
376 lines
9.8 KiB
376 lines
9.8 KiB
/* vim:sts=4 sw=4 expandtab
|
|
*/
|
|
|
|
/*
|
|
* Copyright (c) 2011-2015 Rob Hoelz <rob@hoelz.ro>
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is furnished
|
|
* to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in all
|
|
* copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
|
|
#include <lua.h>
|
|
#include <lauxlib.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include "linenoise.h"
|
|
#include "encodings/utf8.h"
|
|
|
|
#define LN_COMPLETION_TYPE "linenoiseCompletions*"
|
|
|
|
#ifdef _WIN32
|
|
#define LN_EXPORT __declspec(dllexport)
|
|
#else
|
|
#define LN_EXPORT extern
|
|
#endif
|
|
|
|
#ifndef LUA_OK
|
|
#define LUA_OK 0
|
|
#endif
|
|
|
|
static int completion_func_ref = LUA_NOREF;
|
|
static int hints_func_ref = LUA_NOREF;
|
|
static lua_State *completion_state;
|
|
static int callback_error_ref;
|
|
|
|
static int handle_ln_error(lua_State *L)
|
|
{
|
|
lua_pushnil(L);
|
|
return 1;
|
|
}
|
|
|
|
static int handle_ln_ok(lua_State *L)
|
|
{
|
|
lua_pushboolean(L, 1);
|
|
return 1;
|
|
}
|
|
|
|
static int completion_callback_wrapper(const char *line, linenoiseCompletions *completions)
|
|
{
|
|
lua_State *L = completion_state;
|
|
int status;
|
|
|
|
lua_rawgeti(L, LUA_REGISTRYINDEX, completion_func_ref);
|
|
*((linenoiseCompletions **) lua_newuserdata(L, sizeof(linenoiseCompletions *))) = completions;
|
|
luaL_getmetatable(L, LN_COMPLETION_TYPE);
|
|
lua_setmetatable(L, -2);
|
|
|
|
lua_pushstring(L, line);
|
|
|
|
status = lua_pcall(L, 2, 0, 0);
|
|
|
|
if(status == LUA_OK) {
|
|
return 0;
|
|
} else {
|
|
lua_rawseti(L, LUA_REGISTRYINDEX, callback_error_ref);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
static char *
|
|
hints_callback_wrapper(const char *line, int *color, int *bold, int *err)
|
|
{
|
|
lua_State *L = completion_state;
|
|
char *result = NULL;
|
|
int status;
|
|
|
|
lua_rawgeti(L, LUA_REGISTRYINDEX, hints_func_ref);
|
|
|
|
lua_pushstring(L, line);
|
|
|
|
status = lua_pcall(L, 1, 2, 0);
|
|
if(status != LUA_OK) {
|
|
lua_rawseti(L, LUA_REGISTRYINDEX, callback_error_ref);
|
|
*err = 1;
|
|
return NULL;
|
|
}
|
|
|
|
if(!lua_isnoneornil(L, -2)) {
|
|
if(lua_isstring(L, -2)) {
|
|
const char *hint;
|
|
lua_Alloc alloc_f;
|
|
void *ud;
|
|
|
|
hint = lua_tostring(L, -2);
|
|
alloc_f = lua_getallocf(L, &ud);
|
|
result = alloc_f(&ud, NULL, LUA_TSTRING, strlen(hint) + 1);
|
|
if(result) {
|
|
strcpy(result, hint);
|
|
}
|
|
} else {
|
|
lua_pushfstring(L, "Invalid first value of type '%s' from hints callback - string or nil required", lua_typename(L, lua_type(L, -2)));
|
|
lua_rawseti(L, LUA_REGISTRYINDEX, callback_error_ref);
|
|
*err = 1;
|
|
lua_pop(L, 2);
|
|
return NULL;
|
|
}
|
|
|
|
if(!lua_isnoneornil(L, -1)) {
|
|
if(lua_istable(L, -1)) {
|
|
lua_getfield(L, -1, "color");
|
|
if(lua_isnumber(L, -1)) {
|
|
*color = lua_tointeger(L, -1);
|
|
} else if(!lua_isnoneornil(L, -1)) {
|
|
lua_pushfstring(L, "Invalid color value of type '%s' from hints callback - number or nil required", lua_typename(L, lua_type(L, -1)));
|
|
lua_rawseti(L, LUA_REGISTRYINDEX, callback_error_ref);
|
|
*err = 1;
|
|
lua_pop(L, 3);
|
|
// return the result to allow linenoise to free it
|
|
return result;
|
|
}
|
|
lua_pop(L, 1);
|
|
|
|
lua_getfield(L, -1, "bold");
|
|
*bold = lua_toboolean(L, -1);
|
|
lua_pop(L, 1);
|
|
} else {
|
|
lua_pushfstring(L, "Invalid second value of type '%s' from hints callback - table or nil required", lua_typename(L, lua_type(L, -1)));
|
|
lua_rawseti(L, LUA_REGISTRYINDEX, callback_error_ref);
|
|
*err = 1;
|
|
lua_pop(L, 2);
|
|
// return the result to allow linenoise to free it
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
|
|
lua_pop(L, 2);
|
|
|
|
return result;
|
|
}
|
|
|
|
static void
|
|
free_hints_callback(void *p)
|
|
{
|
|
lua_State *L = completion_state;
|
|
lua_Alloc alloc_f;
|
|
void *ud;
|
|
|
|
alloc_f = lua_getallocf(L, &ud);
|
|
|
|
alloc_f(ud, p, 0, 0);
|
|
}
|
|
|
|
static int l_linenoise(lua_State *L)
|
|
{
|
|
const char *prompt = luaL_checkstring(L, 1);
|
|
char *line;
|
|
|
|
completion_state = L;
|
|
lua_pushliteral(L, "");
|
|
lua_rawseti(L, LUA_REGISTRYINDEX, callback_error_ref);
|
|
line = linenoise(prompt);
|
|
completion_state = NULL;
|
|
|
|
lua_rawgeti(L, LUA_REGISTRYINDEX, callback_error_ref);
|
|
if(strlen(lua_tostring(L, -1)) != 0) {
|
|
lua_pushnil(L);
|
|
lua_insert(L, -2);
|
|
if(line) {
|
|
linenoiseFree(line);
|
|
}
|
|
return 2;
|
|
}
|
|
|
|
if(! line) {
|
|
return handle_ln_error(L);
|
|
}
|
|
lua_pushstring(L, line);
|
|
linenoiseFree(line);
|
|
return 1;
|
|
}
|
|
|
|
static int lines_next(lua_State *L)
|
|
{
|
|
lua_pushcfunction(L, l_linenoise);
|
|
lua_pushvalue(L, lua_upvalueindex(1));
|
|
lua_call(L, 1, 1);
|
|
return 1;
|
|
}
|
|
|
|
static int l_lines(lua_State *L)
|
|
{
|
|
luaL_checkstring(L, 1);
|
|
lua_pushcclosure(L, lines_next, 1);
|
|
return 1;
|
|
}
|
|
|
|
static int l_historyadd(lua_State *L)
|
|
{
|
|
const char *line = luaL_checkstring(L, 1);
|
|
|
|
if(! linenoiseHistoryAdd(line)) {
|
|
return handle_ln_error(L);
|
|
}
|
|
|
|
return handle_ln_ok(L);
|
|
}
|
|
|
|
static int l_historysetmaxlen(lua_State *L)
|
|
{
|
|
int len = luaL_checkinteger(L, 1);
|
|
|
|
if(! linenoiseHistorySetMaxLen(len)) {
|
|
return handle_ln_error(L);
|
|
}
|
|
|
|
return handle_ln_ok(L);
|
|
}
|
|
|
|
static int l_historysave(lua_State *L)
|
|
{
|
|
const char *filename = luaL_checkstring(L, 1);
|
|
|
|
if(linenoiseHistorySave((char *) filename) < 0) {
|
|
return handle_ln_error(L);
|
|
}
|
|
return handle_ln_ok(L);
|
|
}
|
|
|
|
static int l_historyload(lua_State *L)
|
|
{
|
|
const char *filename = luaL_checkstring(L, 1);
|
|
|
|
if(linenoiseHistoryLoad((char *) filename) < 0) {
|
|
return handle_ln_error(L);
|
|
}
|
|
return handle_ln_ok(L);
|
|
}
|
|
|
|
static int l_clearscreen(lua_State *L)
|
|
{
|
|
linenoiseClearScreen();
|
|
return handle_ln_ok(L);
|
|
}
|
|
|
|
static int l_setcompletion(lua_State *L)
|
|
{
|
|
if(lua_isnoneornil(L, 1)) {
|
|
luaL_unref(L, LUA_REGISTRYINDEX, completion_func_ref);
|
|
completion_func_ref = LUA_NOREF;
|
|
linenoiseSetCompletionCallback(NULL);
|
|
} else {
|
|
luaL_checktype(L, 1, LUA_TFUNCTION);
|
|
|
|
lua_pushvalue(L, 1);
|
|
if(completion_func_ref == LUA_NOREF) {
|
|
completion_func_ref = luaL_ref(L, LUA_REGISTRYINDEX);
|
|
} else {
|
|
lua_rawseti(L, LUA_REGISTRYINDEX, completion_func_ref);
|
|
}
|
|
linenoiseSetCompletionCallback(completion_callback_wrapper);
|
|
}
|
|
|
|
return handle_ln_ok(L);
|
|
}
|
|
|
|
static int l_addcompletion(lua_State *L)
|
|
{
|
|
linenoiseCompletions *completions = *((linenoiseCompletions **) luaL_checkudata(L, 1, LN_COMPLETION_TYPE));
|
|
const char *entry = luaL_checkstring(L, 2);
|
|
|
|
linenoiseAddCompletion(completions, (char *) entry);
|
|
|
|
return handle_ln_ok(L);
|
|
}
|
|
|
|
static int
|
|
l_setmultiline(lua_State *L)
|
|
{
|
|
int is_multi_line = lua_toboolean(L, 1);
|
|
|
|
linenoiseSetMultiLine(is_multi_line);
|
|
|
|
return handle_ln_ok(L);
|
|
}
|
|
|
|
static int
|
|
l_sethints(lua_State *L)
|
|
{
|
|
if(lua_isnoneornil(L, 1)) {
|
|
luaL_unref(L, LUA_REGISTRYINDEX, hints_func_ref);
|
|
hints_func_ref = LUA_NOREF;
|
|
|
|
linenoiseSetHintsCallback(NULL);
|
|
linenoiseSetFreeHintsCallback(NULL);
|
|
} else {
|
|
luaL_checktype(L, 1, LUA_TFUNCTION);
|
|
|
|
lua_pushvalue(L, 1);
|
|
if(hints_func_ref == LUA_NOREF) {
|
|
hints_func_ref = luaL_ref(L, LUA_REGISTRYINDEX);
|
|
} else {
|
|
lua_rawseti(L, LUA_REGISTRYINDEX, hints_func_ref);
|
|
}
|
|
linenoiseSetHintsCallback(hints_callback_wrapper);
|
|
linenoiseSetFreeHintsCallback(free_hints_callback);
|
|
}
|
|
return handle_ln_ok(L);
|
|
}
|
|
|
|
luaL_Reg linenoise_funcs[] = {
|
|
{ "linenoise", l_linenoise },
|
|
{ "historyadd", l_historyadd },
|
|
{ "historysetmaxlen", l_historysetmaxlen },
|
|
{ "historysave", l_historysave },
|
|
{ "historyload", l_historyload },
|
|
{ "clearscreen", l_clearscreen },
|
|
{ "setcompletion", l_setcompletion},
|
|
{ "addcompletion", l_addcompletion },
|
|
{ "setmultiline", l_setmultiline },
|
|
{ "sethints", l_sethints },
|
|
|
|
/* Aliases for more consistent function names */
|
|
{ "addhistory", l_historyadd },
|
|
{ "sethistorymaxlen", l_historysetmaxlen },
|
|
{ "savehistory", l_historysave },
|
|
{ "loadhistory", l_historyload },
|
|
|
|
{ "line", l_linenoise },
|
|
{ "lines", l_lines },
|
|
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
luaL_Reg linenoise_methods[] = {
|
|
{ "add", l_addcompletion },
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
LN_EXPORT int luaopen_linenoise(lua_State *L)
|
|
{
|
|
lua_pushliteral(L, "");
|
|
callback_error_ref = luaL_ref(L, LUA_REGISTRYINDEX);
|
|
|
|
lua_newtable(L);
|
|
|
|
luaL_newmetatable(L, LN_COMPLETION_TYPE);
|
|
lua_pushboolean(L, 0);
|
|
lua_setfield(L, -2, "__metatable");
|
|
lua_pushvalue(L, -1);
|
|
lua_setfield(L, -2, "__index");
|
|
|
|
#if LUA_VERSION_NUM > 501
|
|
luaL_setfuncs(L, linenoise_methods, 0);
|
|
lua_pop(L, 1);
|
|
luaL_setfuncs(L,linenoise_funcs,0);
|
|
#else
|
|
luaL_register(L, NULL, linenoise_methods);
|
|
lua_pop(L, 1);
|
|
luaL_register(L, NULL, linenoise_funcs);
|
|
#endif
|
|
return 1;
|
|
}
|
|
|