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 | |
| 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.
| -rw-r--r-- | Makefile | 8 | ||||
| -rw-r--r-- | README.md | 2 | ||||
| -rw-r--r-- | config.mk | 14 | ||||
| -rw-r--r-- | main.c | 13 | ||||
| -rw-r--r-- | ui-curses.c | 3 | ||||
| -rw-r--r-- | view.c | 331 | ||||
| -rw-r--r-- | view.h | 2 | ||||
| -rw-r--r-- | vis-cmds.c | 2 | ||||
| -rw-r--r-- | vis-core.h | 1 | ||||
| -rw-r--r-- | vis-lua.c | 491 | ||||
| -rw-r--r-- | vis-lua.h | 22 | ||||
| -rw-r--r-- | vis.c | 118 | ||||
| -rw-r--r-- | vis.h | 12 |
13 files changed, 746 insertions, 273 deletions
@@ -21,10 +21,10 @@ LIBTERMKEY_SHA1 = 0a78ba7aaa2f3b53f2273268366fef349c9be4ab #LIBLUA = lua-5.3.1 #LIBLUA_SHA1 = 1676c6a041d90b6982db8cef1e5fb26000ab6dee -#LIBLUA = lua-5.2.4 -#LIBLUA_SHA1 = ef15259421197e3d85b7d6e4871b8c26fd82c1cf -LIBLUA = lua-5.1.5 -LIBLUA_SHA1 = b3882111ad02ecc6b972f8c1241647905cb2e3fc +LIBLUA = lua-5.2.4 +LIBLUA_SHA1 = ef15259421197e3d85b7d6e4871b8c26fd82c1cf +#LIBLUA = lua-5.1.5 +#LIBLUA_SHA1 = b3882111ad02ecc6b972f8c1241647905cb2e3fc LIBLPEG = lpeg-1.0.0 LIBLPEG_SHA1 = 64a0920c9243b624a277c987d2219b6c50c43971 @@ -610,7 +610,7 @@ In order to build vis you will need a C99 compiler as well as: * [libcurses](http://www.gnu.org/software/ncurses/), preferably in the wide-character version * [libtermkey](http://www.leonerd.org.uk/code/libtermkey/) - * [lua](http://www.lua.org/) >= 5.1 + * [lua](http://www.lua.org/) >= 5.2 * [LPeg](http://www.inf.puc-rio.br/~roberto/lpeg/) >= 0.12 (runtime dependency required for syntax highlighting) @@ -1,6 +1,7 @@ # optional features HAVE_ACL=0 HAVE_SELINUX=0 +CONFIG_LUA=1 # vis version RELEASE = HEAD @@ -26,17 +27,22 @@ PREFIX ?= /usr/local MANPREFIX = ${PREFIX}/share/man SHAREPREFIX = ${PREFIX}/share/vis -CFLAGS_LUA = $(shell pkg-config --cflags lua5.1 2> /dev/null || echo "-I/usr/include/lua5.1") -CFLAGS_TERMKEY = $(shell pkg-config --cflags termkey 2> /dev/null || echo "") -CFLAGS_CURSES = $(shell pkg-config --cflags ncursesw 2> /dev/null || echo "-I/usr/include/ncursesw") +ifeq (${CONFIG_LUA},1) + CFLAGS_LUA = $(shell pkg-config --cflags lua5.2 2> /dev/null || echo "-I/usr/include/lua5.2") + LDFLAGS_LUA = $(shell pkg-config --libs lua5.2 2> /dev/null || echo "-llua") +endif -LDFLAGS_LUA = $(shell pkg-config --libs lua5.1 2> /dev/null || echo "-llua") +CFLAGS_TERMKEY = $(shell pkg-config --cflags termkey 2> /dev/null || echo "") LDFLAGS_TERMKEY = $(shell pkg-config --libs termkey 2> /dev/null || echo "-ltermkey") + +CFLAGS_CURSES = $(shell pkg-config --cflags ncursesw 2> /dev/null || echo "-I/usr/include/ncursesw") LDFLAGS_CURSES = $(shell pkg-config --libs ncursesw 2> /dev/null || echo "-lncursesw") LIBS = -lm -ldl -lc OS = $(shell uname) +CFLAGS += -DCONFIG_LUA=${CONFIG_LUA} + ifeq (${OS},Linux) ifeq (${HAVE_SELINUX},1) CFLAGS += -DHAVE_SELINUX @@ -6,6 +6,7 @@ #include "ui-curses.h" #include "vis.h" +#include "vis-lua.h" #include "text-util.h" #include "text-motions.h" #include "text-objects.h" @@ -1620,7 +1621,17 @@ static void signal_handler(int signum, siginfo_t *siginfo, void *context) { int main(int argc, char *argv[]) { - vis = vis_new(ui_curses_new()); + VisEvent event = { + .vis_start = vis_lua_start, + .vis_quit = vis_lua_quit, + .file_open = vis_lua_file_open, + .file_save = vis_lua_file_save, + .file_close = vis_lua_file_close, + .win_open = vis_lua_win_open, + .win_close = vis_lua_win_close, + }; + + vis = vis_new(ui_curses_new(), &event); if (!vis) return EXIT_FAILURE; diff --git a/ui-curses.c b/ui-curses.c index 5535dbc..a9ae5f9 100644 --- a/ui-curses.c +++ b/ui-curses.c @@ -26,8 +26,9 @@ #include <poll.h> #include <sys/ioctl.h> -#include "ui.h" #include "ui-curses.h" +#include "vis.h" +#include "text.h" #include "util.h" #include "text-util.h" @@ -19,7 +19,7 @@ #include <ctype.h> #include <errno.h> #include <regex.h> -#include "vis.h" +#include "vis-lua.h" #include "view.h" #include "text.h" #include "text-motions.h" @@ -115,6 +115,178 @@ static void view_cursors_free(Cursor *c); /* set/move current cursor position to a given (line, column) pair */ static size_t cursor_set(Cursor *cursor, Line *line, int col); +#if !CONFIG_LUA + +static void view_syntax_color(View *view) { } +bool view_syntax_set(View *view, const char *name) { return false; } + +#else + +static void view_syntax_color(View *view) { + lua_State *L = view->lua; + if (!L || !view->lexer_name) + return; + lua_getglobal(L, "vis"); + lua_getfield(L, -1, "lexers"); + if (lua_isnil(L, -1)) + return; + + /* maximal number of bytes to consider for syntax highlighting before + * the visible area */ + const size_t lexer_before_max = 16384; + /* absolute position to start syntax highlighting */ + const size_t lexer_start = view->start >= lexer_before_max ? view->start - lexer_before_max : 0; + /* number of bytes used for syntax highlighting before visible are */ + const size_t lexer_before = view->start - lexer_start; + /* number of bytes to read in one go */ + const size_t text_size = lexer_before + (view->end - view->start); + /* current buffer to work with */ + char text[text_size+1]; + /* bytes to process */ + const size_t text_len = text_bytes_get(view->text, lexer_start, text_size, text); + /* NUL terminate text section */ + text[text_len] = '\0'; + + lua_getfield(L, -1, "load"); + lua_pushstring(L, view->lexer_name); + lua_pcall(L, 1, 1, 0); + + lua_getfield(L, -1, "_TOKENSTYLES"); + lua_getfield(L, -2, "lex"); + + lua_pushvalue(L, -3); /* lexer obj */ + + const char *lex_text = text; + if (lexer_start > 0) { + /* try to start lexing at a line boundry */ + /* TODO: start at known state, handle nested lexers */ + const char *newline = memchr(text, '\n', lexer_before); + if (newline) + lex_text = newline; + } + + lua_pushlstring(L, lex_text, text_len - (lex_text - text)); + lua_pushinteger(L, 1 /* inital style: whitespace */); + + int token_count; + + if (lua_isfunction(L, -4) && !lua_pcall(L, 3, 1, 0) && lua_istable(L, -1) && + (token_count = lua_objlen(L, -1)) > 0) { + + size_t pos = lexer_before - (lex_text - text); + Line *line = view->topline; + int col = 0; + + for (int i = 1; i < token_count; i += 2) { + lua_rawgeti(L, -1, i); + //const char *name = lua_tostring(L, -1); + lua_gettable(L, -3); /* _TOKENSTYLES[token] */ + size_t token_style = lua_tointeger(L, -1); + lua_pop(L, 1); /* style */ + lua_rawgeti(L, -1, i + 1); + size_t token_end = lua_tointeger(L, -1) - 1; + lua_pop(L, 1); /* pos */ + + for (bool token_next = false; line; line = line->next, col = 0) { + for (; col < line->width; col++) { + if (pos < token_end) { + line->cells[col].attr = token_style; + pos += line->cells[col].len; + } else { + token_next = true; + break; + } + } + if (token_next) + break; + } + } + lua_pop(L, 1); + } + + lua_pop(L, 3); /* _TOKENSTYLES, language specific lexer, lexers global */ +} + +bool view_syntax_set(View *view, const char *name) { + if (!name) { + free(view->lexer_name); + view->lexer_name = NULL; + return true; + } + + lua_State *L = view->lua; + if (!L) + return false; + + /* Try to load the specified lexer and parse its token styles. + * Roughly equivalent to the following lua code: + * + * lang = vis.lexers.load(name) + * for token_name, id in pairs(lang._TOKENSTYLES) do + * ui->syntax_style(id, vis.lexers:get_style(lang, token_name); + */ + lua_getglobal(L, "vis"); + lua_getfield(L, -1, "lexers"); + + lua_getfield(L, -1, "STYLE_DEFAULT"); + view->ui->syntax_style(view->ui, UI_STYLE_DEFAULT, lua_tostring(L, -1)); + lua_pop(L, 1); + lua_getfield(L, -1, "STYLE_CURSOR"); + view->ui->syntax_style(view->ui, UI_STYLE_CURSOR, lua_tostring(L, -1)); + lua_pop(L, 1); + lua_getfield(L, -1, "STYLE_CURSOR_LINE"); + view->ui->syntax_style(view->ui, UI_STYLE_CURSOR_LINE, lua_tostring(L, -1)); + lua_pop(L, 1); + lua_getfield(L, -1, "STYLE_SELECTION"); + view->ui->syntax_style(view->ui, UI_STYLE_SELECTION, lua_tostring(L, -1)); + lua_pop(L, 1); + lua_getfield(L, -1, "STYLE_LINENUMBER"); + view->ui->syntax_style(view->ui, UI_STYLE_LINENUMBER, lua_tostring(L, -1)); + lua_pop(L, 1); + lua_getfield(L, -1, "STYLE_COLOR_COLUMN"); + view->ui->syntax_style(view->ui, UI_STYLE_COLOR_COLUMN, lua_tostring(L, -1)); + lua_pop(L, 1); + + lua_getfield(L, -1, "load"); + lua_pushstring(L, name); + + if (lua_pcall(L, 1, 1, 0)) + return false; + + if (!lua_istable(L, -1)) { + lua_pop(L, 2); + return false; + } + + view->lexer_name = strdup(name); + /* loop through all _TOKENSTYLES and parse them */ + lua_getfield(L, -1, "_TOKENSTYLES"); + lua_pushnil(L); /* first key */ + + while (lua_next(L, -2)) { + size_t id = lua_tointeger(L, -1); + //const char *name = lua_tostring(L, -2); + lua_pop(L, 1); /* remove value (=id), keep key (=name) */ + lua_getfield(L, -4, "get_style"); + lua_pushvalue(L, -5); /* lexer */ + lua_pushvalue(L, -5); /* lang */ + lua_pushvalue(L, -4); /* token_name */ + if (lua_pcall(L, 3, 1, 0)) + return false; + const char *style = lua_tostring(L, -1); + //printf("%s\t%d\t%s\n", name, id, style); + view->ui->syntax_style(view->ui, id, style); + lua_pop(L, 1); /* style */ + } + + lua_pop(L, 4); /* _TOKENSTYLES, grammar, lexers, vis */ + + return true; +} + +#endif + + void view_tabwidth_set(View *view, int tabwidth) { view->tabwidth = tabwidth; view_draw(view); @@ -456,86 +628,8 @@ void view_draw(View *view) { void view_update(View *view) { if (!view->need_update) return; - /* maximal number of bytes to consider for syntax highlighting before - * the visible area */ - const size_t lexer_before_max = 16384; - /* absolute position to start syntax highlighting */ - const size_t lexer_start = view->start >= lexer_before_max ? view->start - lexer_before_max : 0; - /* number of bytes used for syntax highlighting before visible are */ - const size_t lexer_before = view->start - lexer_start; - /* number of bytes to read in one go */ - const size_t text_size = lexer_before + (view->end - view->start); - /* current buffer to work with */ - char text[text_size+1]; - /* bytes to process */ - const size_t text_len = text_bytes_get(view->text, lexer_start, text_size, text); - /* NUL terminate text section */ - text[text_len] = '\0'; - - lua_State *L = view->lua; - if (L && view->lexer_name) { - - lua_getglobal(L, "vis"); - lua_getfield(L, -1, "lexers"); - lua_getfield(L, -1, "load"); - lua_pushstring(L, view->lexer_name); - lua_pcall(L, 1, 1, 0); - - lua_getfield(L, -1, "_TOKENSTYLES"); - lua_getfield(L, -2, "lex"); - - lua_pushvalue(L, -3); /* lexer obj */ - - const char *lex_text = text; - if (lexer_start > 0) { - /* try to start lexing at a line boundry */ - /* TODO: start at known state, handle nested lexers */ - const char *newline = memchr(text, '\n', lexer_before); - if (newline) - lex_text = newline; - } - - lua_pushlstring(L, lex_text, text_len - (lex_text - text)); - lua_pushinteger(L, 1 /* inital style: whitespace */); - - int token_count; - - if (lua_isfunction(L, -4) && !lua_pcall(L, 3, 1, 0) && lua_istable(L, -1) && - (token_count = lua_objlen(L, -1)) > 0) { - - size_t pos = lexer_before - (lex_text - text); - Line *line = view->topline; - int col = 0; - - for (int i = 1; i < token_count; i += 2) { - lua_rawgeti(L, -1, i); - //const char *name = lua_tostring(L, -1); - lua_gettable(L, -3); /* _TOKENSTYLES[token] */ - size_t token_style = lua_tointeger(L, -1); - lua_pop(L, 1); /* style */ - lua_rawgeti(L, -1, i + 1); - size_t token_end = lua_tointeger(L, -1) - 1; - lua_pop(L, 1); /* pos */ - - for (bool token_next = false; line; line = line->next, col = 0) { - for (; col < line->width; col++) { - if (pos < token_end) { - line->cells[col].attr = token_style; - pos += line->cells[col].len; - } else { - token_next = true; - break; - } - } - if (token_next) - break; - } - } - lua_pop(L, 1); - } - lua_pop(L, 3); /* _TOKENSTYLES, language specific lexer, lexers global */ - } + view_syntax_color(view); if (view->colorcolumn > 0 && view->colorcolumn <= view->width) { size_t lineno = 0; @@ -906,83 +1000,6 @@ void view_scroll_to(View *view, size_t pos) { view_cursors_scroll_to(view->cursor, pos); } -bool view_syntax_set(View *view, const char *name) { - if (!name) { - free(view->lexer_name); - view->lexer_name = NULL; - return true; - } - - lua_State *L = view->lua; - if (!L) - return false; - - /* Try to load the specified lexer and parse its token styles. - * Roughly equivalent to the following lua code: - * - * lang = vis.lexers.load(name) - * for token_name, id in pairs(lang._TOKENSTYLES) do - * ui->syntax_style(id, vis.lexers:get_style(lang, token_name); - */ - lua_getglobal(L, "vis"); - lua_getfield(L, -1, "lexers"); - - lua_getfield(L, -1, "STYLE_DEFAULT"); - view->ui->syntax_style(view->ui, UI_STYLE_DEFAULT, lua_tostring(L, -1)); - lua_pop(L, 1); - lua_getfield(L, -1, "STYLE_CURSOR"); - view->ui->syntax_style(view->ui, UI_STYLE_CURSOR, lua_tostring(L, -1)); - lua_pop(L, 1); - lua_getfield(L, -1, "STYLE_CURSOR_LINE"); - view->ui->syntax_style(view->ui, UI_STYLE_CURSOR_LINE, lua_tostring(L, -1)); - lua_pop(L, 1); - lua_getfield(L, -1, "STYLE_SELECTION"); - view->ui->syntax_style(view->ui, UI_STYLE_SELECTION, lua_tostring(L, -1)); - lua_pop(L, 1); - lua_getfield(L, -1, "STYLE_LINENUMBER"); - view->ui->syntax_style(view->ui, UI_STYLE_LINENUMBER, lua_tostring(L, -1)); - lua_pop(L, 1); - lua_getfield(L, -1, "STYLE_COLOR_COLUMN"); - view->ui->syntax_style(view->ui, UI_STYLE_COLOR_COLUMN, lua_tostring(L, -1)); - lua_pop(L, 1); - - lua_getfield(L, -1, "load"); - lua_pushstring(L, name); - - if (lua_pcall(L, 1, 1, 0)) - return false; - - if (!lua_istable(L, -1)) { - lua_pop(L, 2); - return false; - } - - view->lexer_name = strdup(name); - /* loop through all _TOKENSTYLES and parse them */ - lua_getfield(L, -1, "_TOKENSTYLES"); - lua_pushnil(L); /* first key */ - - while (lua_next(L, -2)) { - size_t id = lua_tointeger(L, -1); - //const char *name = lua_tostring(L, -2); - lua_pop(L, 1); /* remove value (=id), keep key (=name) */ - lua_getfield(L, -4, "get_style"); - lua_pushvalue(L, -5); /* lexer */ - lua_pushvalue(L, -5); /* lang */ - lua_pushvalue(L, -4); /* token_name */ - if (lua_pcall(L, 3, 1, 0)) - return false; - const char *style = lua_tostring(L, -1); - //printf("%s\t%d\t%s\n", name, id, style); - view->ui->syntax_style(view->ui, id, style); - lua_pop(L, 1); /* style */ - } - - lua_pop(L, 4); /* _TOKENSTYLES, grammar, lexers, vis */ - - return true; -} - const char *view_syntax_get(View *view) { return view->lexer_name; } @@ -3,7 +3,6 @@ #include <stddef.h> #include <stdbool.h> -#include <lua.h> #include "register.h" #include "text.h" #include "ui.h" @@ -11,6 +10,7 @@ typedef struct View View; typedef struct Cursor Cursor; typedef struct Selection Selection; +typedef struct lua_State lua_State; typedef struct { int width; /* display width i.e. number of columns ocupied by this character */ @@ -595,6 +595,8 @@ static bool cmd_write(Vis *vis, Filerange *range, enum CmdOpt opt, const char *a } if (strcmp(file->name, *name) == 0) file->stat = text_stat(text); + if (vis->event && vis->event->file_save) + vis->event->file_save(vis, file); } return true; } @@ -156,6 +156,7 @@ struct Vis { sigjmp_buf sigbus_jmpbuf; /* used to jump back to a known good state in the mainloop after (SIGBUS) */ Map *actions; /* registered editor actions / special keys commands */ lua_State *lua; /* lua context used for syntax highligthing */ + VisEvent *event; }; /** stuff used by multiple of the vis-* files */ 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 diff --git a/vis-lua.h b/vis-lua.h new file mode 100644 index 0000000..b0f1293 --- /dev/null +++ b/vis-lua.h @@ -0,0 +1,22 @@ +#ifndef VIS_LUA_H +#define VIS_LUA_H + +#if CONFIG_LUA +#include <lua.h> +#include <lualib.h> +#include <lauxlib.h> +#else +typedef struct lua_State lua_State; +#endif + +#include "vis.h" + +void vis_lua_start(Vis*); +void vis_lua_quit(Vis*); +void vis_lua_file_open(Vis*, File*); +void vis_lua_file_save(Vis*, File*); +void vis_lua_file_close(Vis*, File*); +void vis_lua_win_open(Vis*, Win*); +void vis_lua_win_close(Vis*, Win*); + +#endif
\ No newline at end of file @@ -34,9 +34,6 @@ #include <sys/ioctl.h> #include <sys/mman.h> #include <pwd.h> -#include <lauxlib.h> -#include <lualib.h> -#include <lua.h> #include <termkey.h> #include "vis.h" @@ -67,7 +64,8 @@ static void file_free(Vis *vis, File *file) { return; if (--file->refcount > 0) return; - + if (vis->event && vis->event->file_close) + vis->event->file_close(vis, file); text_free(file->text); free((char*)file->name); @@ -120,30 +118,17 @@ static File *file_new(Vis *vis, const char *filename) { if (filename) file->name = strdup(filename); + if (vis->event && vis->event->file_open) + vis->event->file_open(vis, file); return file; } void vis_window_name(Win *win, const char *filename) { - lua_State *L = win->vis->lua; File *file = win->file; if (filename != file->name) { free((char*)file->name); file->name = filename ? strdup(filename) : NULL; } - - if (filename && L) { - lua_getglobal(L, "vis"); - lua_getfield(L, -1, "lexers"); - lua_getfield(L, -1, "lexer_name"); - lua_pushstring(L, filename); - lua_pcall(L, 1, 1, 0); - if (lua_isstring(L, -1)) { - const char *lexer_name = lua_tostring(L, -1); - if (lexer_name) - view_syntax_set(win->view, lexer_name); - } - lua_pop(L, 2); /* return value: lexer name, lexers variable */ - } } static void windows_invalidate(Vis *vis, size_t start, size_t end) { @@ -189,6 +174,8 @@ static Win *window_new_file(Vis *vis, File *file) { vis->windows = win; vis->win = win; vis->ui->window_focus(win->ui); + if (vis->event && vis->event->win_open) + vis->event->win_open(vis, win); return win; } @@ -278,6 +265,8 @@ bool vis_window_new(Vis *vis, const char *filename) { void vis_window_close(Win *win) { Vis *vis = win->vis; + if (vis->event && vis->event->win_close) + vis->event->win_close(vis, win); file_free(vis, win->file); if (win->prev) win->prev->next = win->next; @@ -295,70 +284,14 @@ void vis_window_close(Win *win) { vis_draw(vis); } -Vis *vis_new(Ui *ui) { +Vis *vis_new(Ui *ui, VisEvent *event) { if (!ui) return NULL; Vis *vis = calloc(1, sizeof(Vis)); if (!vis) return NULL; - lua_State *L = luaL_newstate(); - if (!(vis->lua = L)) - goto err; - 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 */ - - /* try to load the lexer module */ - lua_getglobal(L, "require"); - lua_pushstring(L, "lexer"); - if (lua_pcall(L, 1, 1, 0)) { - lua_close(L); - vis->lua = L = NULL; - } else { - lua_newtable(L); /* vis */ - lua_pushvalue(L, -2); /* require return value */ - lua_setfield(L, -2, "lexers"); - lua_setglobal(L, "vis"); - } - + if (event && event->vis_start) + event->vis_start(vis); vis->ui = ui; vis->ui->init(vis->ui, vis); vis->tabwidth = 8; @@ -381,6 +314,7 @@ Vis *vis_new(Ui *ui) { if (!(vis->search_pattern = text_regex_new())) goto err; vis->mode_prev = vis->mode = &vis_modes[VIS_MODE_NORMAL]; + vis->event = event; return vis; err: vis_free(vis); @@ -390,8 +324,9 @@ err: void vis_free(Vis *vis) { if (!vis) return; - if (vis->lua) - lua_close(vis->lua); + if (vis->event && vis->event->vis_quit) + vis->event->vis_quit(vis); + vis->event = NULL; while (vis->windows) vis_window_close(vis->windows); file_free(vis, vis->prompt->file); @@ -1352,26 +1287,3 @@ const char *vis_file_name(File *file) { return file->name; } -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; -} @@ -13,6 +13,16 @@ typedef struct Win Win; #include "view.h" #include "register.h" +typedef struct { + void (*vis_start)(Vis*); + void (*vis_quit)(Vis*); + void (*file_open)(Vis*, File*); + void (*file_save)(Vis*, File*); + void (*file_close)(Vis*, File*); + void (*win_open)(Vis*, Win*); + void (*win_close)(Vis*, Win*); +} VisEvent; + typedef union { /* various types of arguments passed to key action functions */ bool b; int i; @@ -40,7 +50,7 @@ typedef struct { /* a key binding either refers to an action or an ali } KeyBinding; /* creates a new editor instance using the specified user interface */ -Vis *vis_new(Ui*); +Vis *vis_new(Ui*, VisEvent*); /* frees all resources associated with this editor instance, terminates ui */ void vis_free(Vis*); /* instructs the user interface to draw to an internal buffer */ |
