diff options
| author | Marc André Tanner <mat@brain-dump.org> | 2015-12-20 11:04:20 +0100 |
|---|---|---|
| committer | Marc André Tanner <mat@brain-dump.org> | 2015-12-26 21:29:15 +0100 |
| commit | 13a95942b6e33033e4bbbe27cc8cafc98c6924a2 (patch) | |
| tree | 04acfeef0b83b4d6e08212948313242b57779f73 /vis-lua.c | |
| parent | 39e2b6f1b7a750af55ceeadab977354919d0ccf5 (diff) | |
| download | vis-13a95942b6e33033e4bbbe27cc8cafc98c6924a2.tar.gz vis-13a95942b6e33033e4bbbe27cc8cafc98c6924a2.tar.xz | |
vis: refactor Lua integration
Lua support can now be disabled at compile time using:
$ make CONFIG_LUA=0
This commit also adds an initial Lua API and provides a few
default hooks.
We now also require Lua >= 5.2 due to the uservalue constructs.
In principle the same functionality could be implemented using
function environments from Lua 5.1.
Diffstat (limited to 'vis-lua.c')
| -rw-r--r-- | vis-lua.c | 491 |
1 files changed, 491 insertions, 0 deletions
diff --git a/vis-lua.c b/vis-lua.c new file mode 100644 index 0000000..d597772 --- /dev/null +++ b/vis-lua.c @@ -0,0 +1,491 @@ +#include <stdarg.h> +#include <stdio.h> +#include <string.h> +#include <sys/types.h> +#include <pwd.h> + +#include "vis-lua.h" +#include "vis-core.h" +#include "text-motions.h" + +#if !CONFIG_LUA + +void vis_lua_start(Vis *vis) { } +void vis_lua_quit(Vis *vis) { } +void vis_lua_file_open(Vis *vis, File *file) { } +void vis_lua_file_save(Vis *vis, File *file) { } +void vis_lua_file_close(Vis *vis, File *file) { } +void vis_lua_win_open(Vis *vis, Win *win) { } +void vis_lua_win_close(Vis *vis, Win *win) { } +bool vis_theme_load(Vis *vis, const char *name) { return true; } + +#else + +#if 0 +static void stack_dump_entry(lua_State *L, int i) { + int t = lua_type(L, i); + switch (t) { + case LUA_TNIL: + printf("nil"); + break; + case LUA_TBOOLEAN: + printf(lua_toboolean(L, i) ? "true" : "false"); + break; + case LUA_TLIGHTUSERDATA: + printf("lightuserdata(%p)", (void*)lua_touserdata(L, i)); + break; + case LUA_TNUMBER: + printf("%g", lua_tonumber(L, i)); + break; + case LUA_TSTRING: + printf("`%s'", lua_tostring(L, i)); + break; + case LUA_TTABLE: + printf("table["); + lua_pushnil(L); /* first key */ + while (lua_next(L, i > 0 ? i : i - 1)) { + stack_dump_entry(L, -2); + printf("="); + stack_dump_entry(L, -1); + printf(","); + lua_pop(L, 1); /* remove value, keep key */ + } + printf("]"); + break; + case LUA_TUSERDATA: + printf("userdata(%p)", (void*)lua_touserdata(L, i)); + break; + default: /* other values */ + printf("%s", lua_typename(L, t)); + break; + } +} + +static void stack_dump(lua_State *L, const char *format, ...) { + va_list ap; + va_start(ap, format); + vprintf(format, ap); + va_end(ap); + int top = lua_gettop(L); + for (int i = 1; i <= top; i++) { + printf("%d: ", i); + stack_dump_entry(L, i); + printf("\n"); + } + printf("\n\n"); +} + +#endif + +/* returns registry["vis.objects"][addr] if it is of correct type */ +static void *obj_get(lua_State *L, void *addr, const char *type) { + lua_getfield(L, LUA_REGISTRYINDEX, "vis.objects"); + lua_pushlightuserdata(L, addr); + lua_gettable(L, -2); + lua_remove(L, -2); + if (lua_isnil(L, -1)) { + lua_pop(L, 1); + return NULL; + } + return luaL_checkudata(L, -1, type); +} + +/* expects a userdatum at the top of the stack and sets + * + * registry["vis.objects"][addr] = userdata + */ +static void obj_set(lua_State *L, void *addr) { + lua_getfield(L, LUA_REGISTRYINDEX, "vis.objects"); + lua_pushlightuserdata(L, addr); + lua_pushvalue(L, -3); + lua_settable(L, -3); + lua_pop(L, 1); +} + +static void obj_del(lua_State *L, void *addr) { + lua_pushnil(L); + obj_set(L, addr); +} + +static void *obj_new(lua_State *L, void *addr, const char *type) { + if (!addr) + return NULL; + void **handle = (void**)obj_get(L, addr, type); + if (!handle) { + handle = lua_newuserdata(L, sizeof(addr)); + obj_set(L, addr); + *handle = addr; + luaL_getmetatable(L, type); + lua_setmetatable(L, -2); + lua_newtable(L); + lua_setuservalue(L, -2); + } + return *handle; +} + +static void *obj_arg_get(lua_State *L, int idx, const char *type) { + void **addr = luaL_checkudata(L, idx, type); + return obj_get(L, *addr, type); +} + +static void *obj_check(lua_State *L, int idx, const char *type) { + void **addr = luaL_checkudata(L, idx, type); + if (!obj_get(L, *addr, type)) + return NULL; + lua_pop(L, 1); + return *addr; +} + +static int index_common(lua_State *L) { + lua_getmetatable(L, 1); + lua_pushvalue(L, 2); + lua_gettable(L, -2); + if (lua_isnil(L, -1)) { + lua_getuservalue(L, 1); + lua_pushvalue(L, 2); + lua_gettable(L, -2); + } + return 1; +} + +static int newindex_common(lua_State *L) { + lua_getuservalue(L, 1); + lua_pushvalue(L, 2); + lua_pushvalue(L, 3); + lua_settable(L, -3); + return 0; +} + +static int windows_iter(lua_State *L); + +static int windows(lua_State *L) { + Vis *vis = (Vis*)lua_touserdata(L, lua_upvalueindex(1)); + Win **handle = lua_newuserdata(L, sizeof *handle); + *handle = vis->windows; + lua_pushcclosure(L, windows_iter, 1); + return 1; +} + +static int windows_iter(lua_State *L) { + Win **handle = (Win**)lua_touserdata(L, lua_upvalueindex(1)); + if (!*handle) + return 0; + Win *win = obj_new(L, *handle, "vis.window"); + if (!win) + return 0; + *handle = win->next; + return 1; +} + +static int files_iter(lua_State *L); + +static int files(lua_State *L) { + Vis *vis = (Vis*)lua_touserdata(L, lua_upvalueindex(1)); + File **handle = lua_newuserdata(L, sizeof *handle); + *handle = vis->files; + lua_pushcclosure(L, files_iter, 1); + return 1; +} + +static int files_iter(lua_State *L) { + File **handle = (File**)lua_touserdata(L, lua_upvalueindex(1)); + if (!*handle) + return 0; + File *file = obj_new(L, *handle, "vis.file"); + if (!file) + return 0; + *handle = file->next; + return 1; +} + +static int command(lua_State *L) { + Vis *vis = (Vis*)lua_touserdata(L, lua_upvalueindex(1)); + const char *cmd = luaL_checkstring(L, 1); + bool ret = vis_cmd(vis, cmd); + lua_pushboolean(L, ret); + return 1; +} + +static int info(lua_State *L) { + Vis *vis = (Vis*)lua_touserdata(L, lua_upvalueindex(1)); + const char *msg = luaL_checkstring(L, 1); + vis_info_show(vis, "%s", msg); + return 0; +} + +static const struct luaL_Reg vis_lua[] = { + { "files", files }, + { "windows", windows }, + { "command", command }, + { "info", info }, + { NULL, NULL }, +}; + +static int window_index(lua_State *L) { + Win *win = obj_check(L, 1, "vis.window"); + if (!win) { + lua_pushnil(L); + return 1; + } + + if (lua_isstring(L, 2)) { + const char *key = lua_tostring(L, 2); + if (strcmp(key, "file") == 0) { + obj_new(L, win->file, "vis.file"); + return 1; + } + } + + return index_common(L); +} + +static int window_newindex(lua_State *L) { + Win *win = obj_check(L, 1, "vis.window"); + if (!win) + return 0; + return newindex_common(L); +} + +static const struct luaL_Reg window_funcs[] = { + { "__index", window_index }, + { "__newindex", window_newindex }, + { NULL, NULL }, +}; + +static int file_index(lua_State *L) { + File *file = obj_check(L, 1, "vis.file"); + if (!file) { + lua_pushnil(L); + return 1; + } + + if (lua_isstring(L, 2)) { + const char *key = lua_tostring(L, 2); + if (strcmp(key, "name") == 0) { + lua_pushstring(L, file->name); + return 1; + } + } + + return index_common(L); +} + +static int file_newindex(lua_State *L) { + File *file = obj_check(L, 1, "vis.file"); + if (!file) + return 0; + return newindex_common(L); +} + +static int file_insert(lua_State *L) { + File *file = obj_check(L, 1, "vis.file"); + if (file) { + size_t pos = luaL_checkunsigned(L, 2); + size_t len; + luaL_checkstring(L, 3); + const char *data = lua_tolstring(L, 3, &len); + bool ret = text_insert(file->text, pos, data, len); + lua_pushboolean(L, ret); + } else { + lua_pushboolean(L, false); + } + return 1; +} + +static int file_delete(lua_State *L) { + File *file = obj_check(L, 1, "vis.file"); + if (file) { + size_t pos = luaL_checkunsigned(L, 2); + size_t len = luaL_checkunsigned(L, 3); + bool ret = text_delete(file->text, pos, len); + lua_pushboolean(L, ret); + } else { + lua_pushboolean(L, false); + } + return 1; +} + +static int file_lines_iter(lua_State *L); + +static int file_lines(lua_State *L) { + obj_arg_get(L, 1, "vis.file"); + size_t *pos = lua_newuserdata(L, sizeof *pos); + *pos = 0; + lua_pushcclosure(L, file_lines_iter, 2); + return 1; +} + +static int file_lines_iter(lua_State *L) { + File *file = *(File**)lua_touserdata(L, lua_upvalueindex(1)); + size_t *pos = lua_touserdata(L, lua_upvalueindex(2)); + size_t new_pos = text_line_next(file->text, *pos); + size_t len = new_pos - *pos; + if (len == 0) + return 0; + char *buf = malloc(len); + if (!buf) + return 0; + len = text_bytes_get(file->text, *pos, len, buf); + lua_pushlstring(L, buf, len); + free(buf); + *pos = new_pos; + return 1; +} + +static const struct luaL_Reg file_funcs[] = { + { "__index", file_index }, + { "__newindex", file_newindex }, + { "insert", file_insert }, + { "delete", file_delete }, + { "lines", file_lines }, + { NULL, NULL }, +}; + + +static void vis_lua_event(Vis *vis, const char *name) { + lua_State *L = vis->lua; + lua_getglobal(L, "vis"); + lua_getfield(L, -1, "events"); + if (lua_istable(L, -1)) { + lua_getfield(L, -1, name); + } + lua_remove(L, -2); +} + +void vis_lua_start(Vis *vis) { + lua_State *L = luaL_newstate(); + if (!L) + return; + vis->lua = L; + luaL_openlibs(L); + + /* try to get users home directory */ + const char *home = getenv("HOME"); + if (!home || !*home) { + struct passwd *pw = getpwuid(getuid()); + if (pw) + home = pw->pw_dir; + } + + /* extends lua's package.path with: + * - $VIS_PATH/lexers + * - $HOME/.vis/lexers + * - /usr/local/share/vis/lexers + * - /usr/share/vis/lexers + * - package.path (standard lua search path) + */ + int paths = 3; + lua_getglobal(L, "package"); + + const char *vis_path = getenv("VIS_PATH"); + if (vis_path) { + lua_pushstring(L, vis_path); + lua_pushstring(L, "/lexers/?.lua;"); + lua_concat(L, 2); + paths++; + } + + if (home && *home) { + lua_pushstring(L, home); + lua_pushstring(L, "/.vis/lexers/?.lua;"); + lua_concat(L, 2); + paths++; + } + + lua_pushstring(L, "/usr/local/share/vis/lexers/?.lua;"); + lua_pushstring(L, "/usr/share/vis/lexers/?.lua;"); + lua_getfield(L, -paths, "path"); + lua_concat(L, paths); + lua_setfield(L, -2, "path"); + lua_pop(L, 1); /* package */ + + /* table in registry to track lifetimes of C objects */ + lua_newtable(L); + lua_setfield(L, LUA_REGISTRYINDEX, "vis.objects"); + /* metatable used to type check user data */ + luaL_newmetatable(L, "vis.file"); + luaL_setfuncs(L, file_funcs, 0); + luaL_newmetatable(L, "vis.window"); + luaL_setfuncs(L, window_funcs, 0); + /* vis module table with up value as the C pointer */ + luaL_newlibtable(L, vis_lua); + lua_pushlightuserdata(L, vis); + luaL_setfuncs(L, vis_lua, 1); + lua_setglobal(L, "vis"); + + lua_getglobal(L, "require"); + lua_pushstring(L, "visrc"); + lua_pcall(L, 1, 0, 0); +} + +void vis_lua_quit(Vis *vis) { + lua_State *L = vis->lua; + if (L) + lua_close(L); +} + +void vis_lua_file_open(Vis *vis, File *file) { + +} + +void vis_lua_file_save(Vis *vis, File *file) { + +} + +void vis_lua_file_close(Vis *vis, File *file) { + lua_State *L = vis->lua; + vis_lua_event(vis, "file_close"); + if (lua_isfunction(L, -1)) { + obj_new(L, file, "vis.file"); + lua_pcall(L, 1, 0, 0); + } + obj_del(L, file); + lua_pop(L, 1); +} + +void vis_lua_win_open(Vis *vis, Win *win) { + lua_State *L = vis->lua; + vis_lua_event(vis, "win_open"); + if (lua_isfunction(L, -1)) { + obj_new(L, win, "vis.window"); + lua_pcall(L, 1, 0, 0); + } + lua_pop(L, 1); +} + +void vis_lua_win_close(Vis *vis, Win *win) { + lua_State *L = vis->lua; + vis_lua_event(vis, "win_close"); + if (lua_isfunction(L, -1)) { + obj_new(L, win, "vis.window"); + lua_pcall(L, 1, 0, 0); + } + obj_del(L, win); + lua_pop(L, 1); +} + +bool vis_theme_load(Vis *vis, const char *name) { + lua_State *L = vis->lua; + if (!L) + return false; + /* package.loaded['themes/'..name] = nil + * require 'themes/'..name */ + lua_pushstring(L, "themes/"); + lua_pushstring(L, name); + lua_concat(L, 2); + lua_getglobal(L, "package"); + lua_getfield(L, -1, "loaded"); + lua_pushvalue(L, -3); + lua_pushnil(L); + lua_settable(L, -3); + lua_pop(L, 2); + lua_getglobal(L, "require"); + lua_pushvalue(L, -2); + if (lua_pcall(L, 1, 0, 0)) + return false; + for (Win *win = vis->windows; win; win = win->next) + view_syntax_set(win->view, view_syntax_get(win->view)); + return true; +} + +#endif |
