From 027cc4a608900776dce43ad693ae557512a77aac Mon Sep 17 00:00:00 2001 From: mitchell <70453897+667e-11@users.noreply.github.com> Date: Thu, 24 May 2012 02:10:30 -0400 Subject: Removed more forward declarations; src/textadept.c --- src/textadept.c | 2031 +++++++++++++++++++++++++++---------------------------- 1 file changed, 994 insertions(+), 1037 deletions(-) (limited to 'src') diff --git a/src/textadept.c b/src/textadept.c index 1d4567d7..70f21337 100644 --- a/src/textadept.c +++ b/src/textadept.c @@ -135,22 +135,6 @@ static void l_pushdoc(lua_State *, sptr_t); #define l_setglobaldoc(l, d) (l_pushdoc(l, d), lua_setglobal(l, "buffer")) LUALIB_API int (luaopen_lpeg) (lua_State *); LUALIB_API int (luaopen_lfs) (lua_State *); -static int lbuf_property(lua_State *), - lview__index(lua_State *), lview__newindex(lua_State *), - lgui__index(lua_State *), lgui__newindex(lua_State *), - lfind__index(lua_State *), lfind__newindex(lua_State *), - lce__index(lua_State *), lce__newindex(lua_State *), - lbuffer_check_global(lua_State *), lbuffer_delete(lua_State *), - lbuffer_new(lua_State *), lbuffer_text_range(lua_State *), - lview_split(lua_State *), lview_unsplit(lua_State *), - lgui_dialog(lua_State *), lgui_get_split_table(lua_State *), - lgui_goto_view(lua_State *), lview_goto_buffer(lua_State *), - lgui_menu(lua_State *), lstring_iconv(lua_State *), - lquit(lua_State *), lreset(lua_State *), ltimeout(lua_State *), - lfind_focus(lua_State *), lfind_next(lua_State *), - lfind_prev(lua_State *), lfind_replace(lua_State *), - lfind_replace_all(lua_State *), - lce_focus(lua_State *), lce_show_completions(lua_State *); // Scintilla signals. @@ -925,379 +909,302 @@ int WINAPI WinMain(HINSTANCE _, HINSTANCE __, LPSTR lpCmdLine, int ___) { lua_setmetatable(l, (n > 0) ? n : n - 1); \ } -/** - * Initializes or re-initializes the Lua state. - * Populates the state with global variables and functions, then runs the - * 'core/init.lua' script. - * @param L The Lua state. - * @param argc The number of command line parameters. - * @param argv The array of command line parameters. - * @param reinit Flag indicating whether or not to reinitialize the Lua state. - * @return TRUE on success, FALSE otherwise. - */ -static int lL_init(lua_State *L, int argc, char **argv, int reinit) { - if (!reinit) { - lua_newtable(L); - for (int i = 0; i < argc; i++) - lua_pushstring(L, argv[i]), lua_rawseti(L, -2, i); - lua_setfield(L, LUA_REGISTRYINDEX, "ta_arg"); - lua_newtable(L), lua_setfield(L, LUA_REGISTRYINDEX, "ta_buffers"); - lua_newtable(L), lua_setfield(L, LUA_REGISTRYINDEX, "ta_views"); - } else { // clear package.loaded and _G - lua_getglobal(L, "package"), lua_getfield(L, -1, "loaded"); - lL_cleartable(L, -1); - lua_pop(L, 2); // package and package.loaded -#if !LUAJIT - lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS); - lL_cleartable(L, -1); - lua_pop(L, 1); // _G -#else - lL_cleartable(L, LUA_GLOBALSINDEX); -#endif - } - luaL_openlibs(L); - lL_openlib(L, "lpeg", luaopen_lpeg); - lL_openlib(L, "lfs", luaopen_lfs); +static int lfind_next(lua_State *L) { + return (f_clicked(fnext_button, NULL), 0); +} - lua_newtable(L); - lua_newtable(L); - l_setcfunction(L, -1, "find_next", lfind_next); - l_setcfunction(L, -1, "find_prev", lfind_prev); - l_setcfunction(L, -1, "focus", lfind_focus); - l_setcfunction(L, -1, "replace", lfind_replace); - l_setcfunction(L, -1, "replace_all", lfind_replace_all); - l_setmetatable(L, -1, "ta_find", lfind__index, lfind__newindex); - lua_setfield(L, -2, "find"); - lua_newtable(L); - l_setcfunction(L, -1, "focus", lce_focus); - l_setcfunction(L, -1, "show_completions", lce_show_completions); - l_setmetatable(L, -1, "ta_command_entry", lce__index, lce__newindex); - lua_setfield(L, -2, "command_entry"); - l_setcfunction(L, -1, "dialog", lgui_dialog); - l_setcfunction(L, -1, "get_split_table", lgui_get_split_table); - l_setcfunction(L, -1, "goto_view", lgui_goto_view); - l_setcfunction(L, -1, "menu", lgui_menu); - l_setmetatable(L, -1, "ta_gui", lgui__index, lgui__newindex); - lua_setglobal(L, "gui"); +static int lfind_prev(lua_State *L) { + return (f_clicked(fprev_button, NULL), 0); +} - lua_getglobal(L, "_G"); - l_setcfunction(L, -1, "new_buffer", lbuffer_new); - l_setcfunction(L, -1, "quit", lquit); - l_setcfunction(L, -1, "reset", lreset); - l_setcfunction(L, -1, "timeout", ltimeout); - lua_pop(L, 1); // _G +static int lfind_focus(lua_State *L) { +#if GTK + if (!gtk_widget_has_focus(findbox)) { + gtk_widget_show(findbox); + gtk_widget_grab_focus(find_entry); + gtk_widget_grab_default(fnext_button); + } else { + gtk_widget_grab_focus(focused_view); + gtk_widget_hide(findbox); + } +#elif NCURSES + // TODO: toggle findbox focus. +#endif + return 0; +} - lua_getglobal(L, "string"); - l_setcfunction(L, -1, "iconv", lstring_iconv); - lua_pop(L, 1); // string +static int lfind_replace(lua_State *L) { + return (f_clicked(r_button, NULL), 0); +} - lua_getfield(L, LUA_REGISTRYINDEX, "ta_arg"), lua_setglobal(L, "arg"); - lua_getfield(L, LUA_REGISTRYINDEX, "ta_buffers"); - lua_setglobal(L, "_BUFFERS"); - lua_getfield(L, LUA_REGISTRYINDEX, "ta_views"), lua_setglobal(L, "_VIEWS"); - lua_pushstring(L, textadept_home), lua_setglobal(L, "_HOME"); -#if __WIN32__ - lua_pushboolean(L, 1), lua_setglobal(L, "WIN32"); -#elif __OSX__ - lua_pushboolean(L, 1), lua_setglobal(L, "OSX"); +static int lfind_replace_all(lua_State *L) { + return (f_clicked(ra_button, NULL), 0); +} + +#if GTK +#define toggled(w) gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(w)) +#elif NCURSES +#define toggled(w) w #endif +static int lfind__index(lua_State *L) { + const char *key = lua_tostring(L, 2); + if (strcmp(key, "find_entry_text") == 0) #if GTK - const char *charset = 0; - g_get_charset(&charset); - lua_pushstring(L, charset), lua_setglobal(L, "_CHARSET"); + lua_pushstring(L, gtk_entry_get_text(GTK_ENTRY(find_entry))); #elif NCURSES - lua_pushboolean(L, 1), lua_setglobal(L, "NCURSES"); - lua_pushstring(L, "UTF-8"), lua_setglobal(L, "_CHARSET"); // TODO: get charset + lua_pushstring(L, find_text); #endif - - - if (lL_dofile(L, "core/init.lua")) { - lua_getglobal(L, "_SCINTILLA"); - lua_getfield(L, -1, "constants"); - lua_setfield(L, LUA_REGISTRYINDEX, "ta_constants"); - lua_getfield(L, -1, "functions"); - lua_setfield(L, LUA_REGISTRYINDEX, "ta_functions"); - lua_getfield(L, -1, "properties"); - lua_setfield(L, LUA_REGISTRYINDEX, "ta_properties"); - lua_pop(L, 1); // _SCINTILLA - return TRUE; - } - lua_close(L); - return FALSE; + else if (strcmp(key, "replace_entry_text") == 0) +#if GTK + lua_pushstring(L, gtk_entry_get_text(GTK_ENTRY(replace_entry))); +#elif NCURSES + lua_pushstring(L, repl_text); +#endif + else if (strcmp(key, "match_case") == 0) + lua_pushboolean(L, toggled(match_case)); + else if (strcmp(key, "whole_word") == 0) + lua_pushboolean(L, toggled(whole_word)); + else if (strcmp(key, "lua") == 0) + lua_pushboolean(L, toggled(lua_pattern)); + else if (strcmp(key, "in_files") == 0) + lua_pushboolean(L, toggled(in_files)); + else + lua_rawget(L, 1); + return 1; } -/** - * Loads and runs the given file. - * @param L The Lua state. - * @param filename The file name relative to textadept_home. - * @return 1 if there are no errors or 0 in case of errors. - */ -static int lL_dofile(lua_State *L, const char *filename) { - char *file = malloc(strlen(textadept_home) + 1 + strlen(filename) + 1); - stpcpy(stpcpy(stpcpy(file, textadept_home), "/"), filename); - int ok = (luaL_dofile(L, file) == LUA_OK); - if (!ok) { #if GTK - GtkWidget *dialog = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, - GTK_MESSAGE_ERROR, - GTK_BUTTONS_OK, "%s\n", - lua_tostring(L, -1)); - gtk_dialog_run(GTK_DIALOG(dialog)); - gtk_widget_destroy(dialog); +#define toggle(w, b) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w), b) #elif NCURSES - WINDOW *win = newwin(0, 0, 0, 0); - wprintw(win, lua_tostring(L, -1)), wrefresh(win); - getch(); - delwin(win); +#define toggle(w, b) w = b #endif - lua_settop(L, 0); - } - free(file); - return ok; +static int lfind__newindex(lua_State *L) { + const char *key = lua_tostring(L, 2); + if (strcmp(key, "find_entry_text") == 0) { +#if GTK + gtk_entry_set_text(GTK_ENTRY(find_entry), lua_tostring(L, 3)); +#elif NCURSES + if (find_text) free(find_text); + find_text = copy(lua_tostring(L, 3)); +#endif + } else if (strcmp(key, "replace_entry_text") == 0) { +#if GTK + gtk_entry_set_text(GTK_ENTRY(replace_entry), lua_tostring(L, 3)); +#elif NCURSES + if (repl_text) free(repl_text); + repl_text = copy(lua_tostring(L, 3)); +#endif + } else if (strcmp(key, "match_case") == 0) + toggle(match_case, lua_toboolean(L, -1)); + else if (strcmp(key, "whole_word") == 0) + toggle(whole_word, lua_toboolean(L, -1)); + else if (strcmp(key, "lua") == 0) + toggle(lua_pattern, lua_toboolean(L, -1)); + else if (strcmp(key, "in_files") == 0) + toggle(in_files, lua_toboolean(L, -1)); +#if GTK + else if (strcmp(key, "find_label_text") == 0) + gtk_label_set_text_with_mnemonic(GTK_LABEL(flabel), lua_tostring(L, 3)); + else if (strcmp(key, "replace_label_text") == 0) + gtk_label_set_text_with_mnemonic(GTK_LABEL(rlabel), lua_tostring(L, 3)); + else if (strcmp(key, "find_next_button_text") == 0) + gtk_button_set_label(GTK_BUTTON(fnext_button), lua_tostring(L, 3)); + else if (strcmp(key, "find_prev_button_text") == 0) + gtk_button_set_label(GTK_BUTTON(fprev_button), lua_tostring(L, 3)); + else if (strcmp(key, "replace_button_text") == 0) + gtk_button_set_label(GTK_BUTTON(r_button), lua_tostring(L, 3)); + else if (strcmp(key, "replace_all_button_text") == 0) + gtk_button_set_label(GTK_BUTTON(ra_button), lua_tostring(L, 3)); + else if (strcmp(key, "match_case_label_text") == 0) + gtk_button_set_label(GTK_BUTTON(match_case), lua_tostring(L, 3)); + else if (strcmp(key, "whole_word_label_text") == 0) + gtk_button_set_label(GTK_BUTTON(whole_word), lua_tostring(L, 3)); + else if (strcmp(key, "lua_pattern_label_text") == 0) + gtk_button_set_label(GTK_BUTTON(lua_pattern), lua_tostring(L, 3)); + else if (strcmp(key, "in_files_label_text") == 0) + gtk_button_set_label(GTK_BUTTON(in_files), lua_tostring(L, 3)); +#endif + else + lua_rawset(L, 1); + return 0; } -/** - * Returns the view at the given acceptable index as a Scintilla view. - * @param L The Lua state. - * @param index Stack index of the view. - * @return Scintilla view - */ -static Scintilla *l_toview(lua_State *L, int index) { - lua_getfield(L, index, "widget_pointer"); - Scintilla *view = (Scintilla *)lua_touserdata(L, -1); - lua_pop(L, 1); // widget pointer - return view; +static int lce_focus(lua_State *L) { +#if GTK + if (!gtk_widget_has_focus(command_entry)) { + gtk_widget_show(command_entry); + gtk_widget_grab_focus(command_entry); + } else { + gtk_widget_hide(command_entry); + gtk_widget_grab_focus(focused_view); + } +#elif NCURSES + // TODO: ce toggle focus. +#endif + return 0; } /** - * Adds the Scintilla view with a metatable to the 'views' registry table. - * @param L The Lua state. - * @param view The Scintilla view to add. + * Prints a warning. + * @param s The warning to print. */ -static void lL_addview(lua_State *L, Scintilla *view) { - lua_getfield(L, LUA_REGISTRYINDEX, "ta_views"); - lua_newtable(L); - lua_pushlightuserdata(L, view); - lua_pushvalue(L, -1), lua_setfield(L, -3, "widget_pointer"); - l_setcfunction(L, -2, "goto_buffer", lview_goto_buffer); - l_setcfunction(L, -2, "split", lview_split); - l_setcfunction(L, -2, "unsplit", lview_unsplit); - l_setmetatable(L, -2, "ta_view", lview__index, lview__newindex); - // vs[userdata] = v, vs[#vs + 1] = v, vs[v] = #vs - lua_pushvalue(L, -2), lua_settable(L, -4); - lua_pushvalue(L, -1), lua_rawseti(L, -3, lua_rawlen(L, -3) + 1); - lua_pushinteger(L, lua_rawlen(L, -2)), lua_settable(L, -3); - lua_pop(L, 1); // views -} +static void warn(const char *s) { printf("Warning: %s\n", s); } -/** - * Removes the Scintilla view from the 'views' registry table. - * The view must have been previously added with lL_addview. - * @param L The Lua state. - * @param view The Scintilla view to remove. - * @see lL_addview - */ -static void lL_removeview(lua_State *L, Scintilla *view) { - lua_newtable(L); - lua_getfield(L, LUA_REGISTRYINDEX, "ta_views"); +static int lce_show_completions(lua_State *L) { +#if GTK + luaL_checktype(L, 1, LUA_TTABLE); + GtkEntryCompletion *completion = gtk_entry_get_completion( + GTK_ENTRY(command_entry)); + GtkListStore *store = GTK_LIST_STORE( + gtk_entry_completion_get_model(completion)); + gtk_list_store_clear(store); lua_pushnil(L); - while (lua_next(L, -2)) { - if (lua_isnumber(L, -2) && view != l_toview(L, -1)) { - lua_getfield(L, -1, "widget_pointer"); - // vs[userdata] = v, vs[#vs + 1] = v, vs[v] = #vs - lua_pushvalue(L, -2), lua_rawseti(L, -6, lua_rawlen(L, -6) + 1); - lua_pushvalue(L, -2), lua_settable(L, -6); - lua_pushinteger(L, lua_rawlen(L, -4)), lua_settable(L, -5); - } else lua_pop(L, 1); // value + while (lua_next(L, 1)) { + if (lua_type(L, -1) == LUA_TSTRING) { + GtkTreeIter iter; + gtk_list_store_append(store, &iter); + gtk_list_store_set(store, &iter, 0, lua_tostring(L, -1), -1); + } else warn("command_entry.show_completions: non-string value ignored"); + lua_pop(L, 1); // value } - lua_pop(L, 1); // views - lua_pushvalue(L, -1), lua_setfield(L, LUA_REGISTRYINDEX, "ta_views"); - lua_setglobal(L, "_VIEWS"); + gtk_entry_completion_complete(completion); +#endif + return 0; } -/** - * Returns the buffer at the given acceptable index as a Scintilla document. - * @param L The Lua state. - * @param index Stack index of the buffer. - * @return Scintilla document - */ -static sptr_t l_todoc(lua_State *L, int index) { - lua_getfield(L, index, "doc_pointer"); - sptr_t doc = (sptr_t)lua_touserdata(L, -1); - lua_pop(L, 1); // doc_pointer - return doc; +static int lce__index(lua_State *L) { + const char *key = lua_tostring(L, 2); + if (strcmp(key, "entry_text") == 0) +#if GTK + lua_pushstring(L, gtk_entry_get_text(GTK_ENTRY(command_entry))); +#elif NCURSES + lua_pushstring(L, command_text); +#endif + else + lua_rawget(L, 1); + return 1; } -/** - * Adds a Scintilla document with a metatable to the 'buffers' registry table. - * @param L The Lua state. - * @param doc The Scintilla document to add. - */ -static void lL_adddoc(lua_State *L, sptr_t doc) { - lua_getfield(L, LUA_REGISTRYINDEX, "ta_buffers"); - lua_newtable(L); - lua_pushlightuserdata(L, (sptr_t *)doc); // TODO: can this fail? - lua_pushvalue(L, -1), lua_setfield(L, -3, "doc_pointer"); - l_setcfunction(L, -2, "check_global", lbuffer_check_global); - l_setcfunction(L, -2, "delete", lbuffer_delete); - l_setcfunction(L, -2, "text_range", lbuffer_text_range); - l_setmetatable(L, -2, "ta_buffer", lbuf_property, lbuf_property); - // bs[userdata] = b, bs[#bs + 1] = b, bs[b] = #bs - lua_pushvalue(L, -2), lua_settable(L, -4); - lua_pushvalue(L, -1), lua_rawseti(L, -3, lua_rawlen(L, -3) + 1); - lua_pushinteger(L, lua_rawlen(L, -2)), lua_settable(L, -3); - lua_pop(L, 1); // buffers +static int lce__newindex(lua_State *L) { + const char *key = lua_tostring(L, 2); + if (strcmp(key, "entry_text") == 0) { +#if GTK + gtk_entry_set_text(GTK_ENTRY(command_entry), lua_tostring(L, 3)); +#elif NCURSES + if (command_text) free(command_text); + command_text = copy(lua_tostring(L, 3)); +#endif + } else lua_rawset(L, 1); + return 0; } -/** - * Removes the Scintilla document from the 'buffers' registry table. - * The document must have been previously added with lL_adddoc. - * It is removed from any other views showing it first. Therefore, ensure the - * length of 'buffers' is more than one unless quitting the application. - * @param L The Lua state. - * @param doc The Scintilla document to remove. - * @see lL_adddoc - */ -static void lL_removedoc(lua_State *L, sptr_t doc) { - lua_getfield(L, LUA_REGISTRYINDEX, "ta_views"); - lua_pushnil(L); - while (lua_next(L, -2)) { - if (lua_isnumber(L, -2)) { - Scintilla *view = l_toview(L, -1); - if (doc == SS(view, SCI_GETDOCPOINTER, 0, 0)) - lL_gotodoc(L, view, -1, TRUE); - } - lua_pop(L, 1); // value - } - lua_pop(L, 1); // views +static int lgui_dialog(lua_State *L) { + GCDialogType type = gcocoadialog_type(luaL_checkstring(L, 1)); + int i, j, k, n = lua_gettop(L) - 1, argc = n; + for (i = 2; i < n + 2; i++) + if (lua_istable(L, i)) argc += lua_rawlen(L, i) - 1; + const char **argv = malloc((argc + 1) * sizeof(const char *)); + for (i = 0, j = 2; j < n + 2; j++) + if (lua_istable(L, j)) { + int len = lua_rawlen(L, j); + for (k = 1; k <= len; k++) { + lua_rawgeti(L, j, k); + argv[i++] = luaL_checkstring(L, -1); + lua_pop(L, 1); + } + } else argv[i++] = luaL_checkstring(L, j); + argv[argc] = 0; + char *out = gcocoadialog(type, argc, argv); + lua_pushstring(L, out); + free(out), free(argv); + return 1; +} + +#if GTK +#define child1(p) gtk_paned_get_child1(GTK_PANED(p)) +#define child2(p) gtk_paned_get_child2(GTK_PANED(p)) + +static void l_pushsplittable(lua_State *L, GtkWidget *c1, GtkWidget *c2) { lua_newtable(L); - lua_getfield(L, LUA_REGISTRYINDEX, "ta_buffers"); - lua_pushnil(L); - while (lua_next(L, -2)) { - if (lua_isnumber(L, -2) && doc != l_todoc(L, -1)) { - lua_getfield(L, -1, "doc_pointer"); - // bs[userdata] = b, bs[#bs + 1] = b, bs[b] = #bs - lua_pushvalue(L, -2), lua_rawseti(L, -6, lua_rawlen(L, -6) + 1); - lua_pushvalue(L, -2), lua_settable(L, -6); - lua_pushinteger(L, lua_rawlen(L, -4)), lua_settable(L, -5); - } else lua_pop(L, 1); // value - } - lua_pop(L, 1); // buffers - lua_pushvalue(L, -1), lua_setfield(L, LUA_REGISTRYINDEX, "ta_buffers"); - lua_setglobal(L, "_BUFFERS"); + if (GTK_IS_PANED(c1)) + l_pushsplittable(L, child1(c1), child2(c1)); + else + l_pushview(L, c1); + lua_rawseti(L, -2, 1); + if (GTK_IS_PANED(c2)) + l_pushsplittable(L, child1(c2), child2(c2)); + else + l_pushview(L, c2); + lua_rawseti(L, -2, 2); + lua_pushboolean(L, GTK_IS_HPANED(gtk_widget_get_parent(c1))); + lua_setfield(L, -2, "vertical"); + int size = gtk_paned_get_position(GTK_PANED(gtk_widget_get_parent(c1))); + lua_pushinteger(L, size), lua_setfield(L, -2, "size"); +} +#endif + +static int lgui_get_split_table(lua_State *L) { +#if GTK + GtkWidget *pane = gtk_widget_get_parent(focused_view); + if (GTK_IS_PANED(pane)) { + while (GTK_IS_PANED(gtk_widget_get_parent(pane))) + pane = gtk_widget_get_parent(pane); + l_pushsplittable(L, child1(pane), child2(pane)); + } else l_pushview(L, focused_view); +#elif NCURSES + l_pushview(L, focused_view); // TODO: push split table +#endif + return 1; } /** - * Switches to a document in the given view. + * Returns the view at the given acceptable index as a Scintilla view. * @param L The Lua state. - * @param view The Scintilla view. - * @param n Relative or absolute index of the document to switch to. An absolute - * n of -1 represents the last document. - * @param relative Flag indicating whether or not n is relative. + * @param index Stack index of the view. + * @return Scintilla view */ -static void lL_gotodoc(lua_State *L, Scintilla *view, int n, int relative) { - if (relative && n == 0) return; - lua_getfield(L, LUA_REGISTRYINDEX, "ta_buffers"); +static Scintilla *l_toview(lua_State *L, int index) { + lua_getfield(L, index, "widget_pointer"); + Scintilla *view = (Scintilla *)lua_touserdata(L, -1); + lua_pop(L, 1); // widget pointer + return view; +} + +static int lgui_goto_view(lua_State *L) { + int n = luaL_checkinteger(L, 1), relative = lua_toboolean(L, 2); + if (relative && n == 0) return 0; + lua_getfield(L, LUA_REGISTRYINDEX, "ta_views"); if (relative) { - l_pushdoc(L, SS(view, SCI_GETDOCPOINTER, 0, 0)), lua_gettable(L, -2); + l_pushview(L, focused_view), lua_gettable(L, -2); n = lua_tointeger(L, -1) + n; - lua_pop(L, 1); // index - if (n > lua_rawlen(L, -1)) + if (n > lua_rawlen(L, -2)) n = 1; else if (n < 1) - n = lua_rawlen(L, -1); - lua_rawgeti(L, -1, n); + n = lua_rawlen(L, -2); + lua_rawgeti(L, -2, n); } else { - luaL_argcheck(L, (n > 0 && n <= lua_rawlen(L, -1)) || n == -1, 2, - "no Buffer exists at that index"); - lua_rawgeti(L, -1, (n > 0) ? n : lua_rawlen(L, -1)); + luaL_argcheck(L, n > 0 && n <= lua_rawlen(L, -1), 1, + "no View exists at that index"); + lua_rawgeti(L, -1, n); } - sptr_t doc = l_todoc(L, -1); - SS(view, SCI_SETDOCPOINTER, 0, doc); - l_setglobaldoc(L, doc); - lua_pop(L, 2); // buffer table and buffers + Scintilla *view = l_toview(L, -1); + focus_view(view); +#if GTK + // gui.dialog() interferes with focus so gtk_widget_grab_focus() does not + // always work. If this is the case, ensure goto_view() is called. + if (!gtk_widget_has_focus(view)) goto_view(view); +#endif + return 0; } /** - * Closes the Lua state. - * Unsplits and destroys all Scintilla views and removes all Scintilla - * documents, before closing the state. + * Returns the value t[n] as an integer where t is the value at the given valid + * index. + * The access is raw; that is, it does not invoke metamethods. * @param L The Lua state. - */ -static void l_close(lua_State *L) { - closing = TRUE; - while (unsplit_view(focused_view)) ; // need space to fix compiler warning - lua_getfield(L, LUA_REGISTRYINDEX, "ta_buffers"); - lua_pushnil(L); - while (lua_next(L, -2)) { - if (lua_isnumber(L, -2)) delete_buffer(l_todoc(L, -1)); - lua_pop(L, 1); // value - } - lua_pop(L, 1); // buffers - scintilla_delete(focused_view); - lua_close(L); -} - -/******************************************************************************/ -/*************************** Lua Utility Functions ****************************/ -/******************************************************************************/ - -/** - * Clears a table at the given valid index by setting all of its keys to nil. - * @param L The Lua state. - * @param index The stack index of the table. - */ -static void lL_cleartable(lua_State *L, int index) { - lua_pushvalue(L, index); // copy to stack top so relative indices can be used - lua_pushnil(L); - while (lua_next(L, -2)) { - lua_pop(L, 1); // value - lua_pushnil(L), lua_rawset(L, -3); - lua_pushnil(L); // key for lua_next - } - lua_pop(L, 1); // table copy -} - -/** - * Pushes the Scintilla view onto the stack. - * The view must have previously been added with lL_addview. - * @param L The Lua state. - * @param view The Scintilla view to push. - * @see lL_addview - */ -static void l_pushview(lua_State *L, Scintilla *view) { - lua_getfield(L, LUA_REGISTRYINDEX, "ta_views"); - lua_pushlightuserdata(L, view), lua_gettable(L, -2); - lua_remove(L, -2); // views -} - -/** - * Pushes the Scintilla document onto the stack. - * The document must have previously been added with lL_adddoc. - * @param L The Lua state. - * @param doc The document to push. - * @see lL_adddoc - */ -static void l_pushdoc(lua_State *L, sptr_t doc) { - lua_getfield(L, LUA_REGISTRYINDEX, "ta_buffers"); - lua_pushlightuserdata(L, (sptr_t *)doc), lua_gettable(L, -2); - lua_remove(L, -2); // buffers -} - -/** - * Prints a warning. - * @param s The warning to print. - */ -static void warn(const char *s) { printf("Warning: %s\n", s); } - -/** - * Returns the value t[n] as an integer where t is the value at the given valid - * index. - * The access is raw; that is, it does not invoke metamethods. - * @param L The Lua state. - * @param index The stack index of the table. - * @param n The index in the table to get. - * @return integer + * @param index The stack index of the table. + * @param n The index in the table to get. + * @return integer */ static int l_rawgetiint(lua_State *L, int index, int n) { lua_rawgeti(L, index, n); @@ -1306,81 +1213,6 @@ static int l_rawgetiint(lua_State *L, int index, int n) { return ret; } -/** - * Returns the value t[k] as a string where t is the value at the given valid - * index. - * The access is raw; that is, it does not invoke metamethods. - * @param L The Lua state. - * @param index The stack index of the table. - * @param k String key in the table to get. - * @return string - */ -static const char *l_rawgetstr(lua_State *L, int index, const char *k) { - lua_pushstring(L, k); - lua_rawget(L, index); - const char *str = lua_tostring(L, -1); - lua_pop(L, 1); // string - return str; -} - -/** - * Checks whether the function argument narg is the given Scintilla parameter - * type and returns it cast to the proper type. - * @param L The Lua state. - * @param narg The stack index of the Scintilla parameter. - * @param type The Scintilla type to convert to. - * @return Scintilla param - */ -static long lL_checkscintillaparam(lua_State *L, int *narg, int type) { - if (type == tSTRING) - return (long)luaL_checkstring(L, (*narg)++); - else if (type == tBOOL) - return lua_toboolean(L, (*narg)++); - else if (type == tKEYMOD) { - int key = luaL_checkinteger(L, (*narg)++) & 0xFFFF; - return key | ((luaL_checkinteger(L, (*narg)++) & - (SCMOD_SHIFT | SCMOD_CTRL | SCMOD_ALT)) << 16); - } else if (type > tVOID && type < tBOOL) - return luaL_checklong(L, (*narg)++); - else - return 0; -} - -/** - * Checks whether the function argument narg is a Scintilla view and returns - * this view cast to a Scintilla. - * @param L The Lua state. - * @param narg The stack index of the Scintilla view. - * @return Scintilla view - */ -static Scintilla *lL_checkview(lua_State *L, int narg) { - luaL_getmetatable(L, "ta_view"); - lua_getmetatable(L, narg); - luaL_argcheck(L, lua_compare(L, -1, -2, LUA_OPEQ), narg, "View expected"); - lua_getfield(L, (narg > 0) ? narg : narg - 2, "widget_pointer"); - Scintilla *view = (Scintilla *)lua_touserdata(L, -1); - lua_pop(L, 3); // widget_pointer, metatable, metatable - return view; -} - -/** - * Checks whether the function argument narg is a Scintilla document. If not, - * raises an error. - * @param L The Lua state. - * @param narg The stack index of the Scintilla document. - * @return Scintilla document - */ -static void lL_globaldoccheck(lua_State *L, int narg) { - luaL_getmetatable(L, "ta_buffer"); - lua_getmetatable(L, (narg > 0) ? narg : narg - 1); - luaL_argcheck(L, lua_compare(L, -1, -2, LUA_OPEQ), narg, "Buffer expected"); - lua_getfield(L, (narg > 0) ? narg : narg - 2, "doc_pointer"); - sptr_t doc = (sptr_t)lua_touserdata(L, -1); - luaL_argcheck(L, doc == SS(focused_view, SCI_GETDOCPOINTER, 0, 0), narg, - "this buffer is not the current one"); - lua_pop(L, 3); // doc_pointer, metatable, metatable -} - /** * Pushes a menu created from the table at the given valid index onto the stack. * Consult the LuaDoc for the table format. @@ -1446,284 +1278,20 @@ static void l_pushmenu(lua_State *L, int index, void (*callback)(void), #endif } -/******************************************************************************/ -/********************** Lua Notifications/Event Handlers **********************/ -/******************************************************************************/ - -/** - * Emits an event. - * @param L The Lua state. - * @param name The event name. - * @param ... Arguments to pass with the event. Each pair of arguments should be - * a Lua type followed by the data value itself. For LUA_TLIGHTUSERDATA and - * LUA_TTABLE types, push the data values to the stack and give the value - * returned by luaL_ref(); luaL_unref() will be called appropriately. The list - * must be terminated with a -1. - * @return TRUE or FALSE depending on the boolean value returned by the event - * handler, if any. - */ -static int lL_event(lua_State *L, const char *name, ...) { - int ret = FALSE; - lua_getglobal(L, "events"); - if (lua_istable(L, -1)) { - lua_getfield(L, -1, "emit"); - lua_remove(L, -2); // events table - if (lua_isfunction(L, -1)) { - lua_pushstring(L, name); - int n = 1; - va_list ap; - va_start(ap, name); - int type = va_arg(ap, int); - while (type != -1) { - void *arg = va_arg(ap, void*); - if (type == LUA_TNIL) - lua_pushnil(L); - else if (type == LUA_TBOOLEAN) - lua_pushboolean(L, (long)arg); - else if (type == LUA_TNUMBER) - lua_pushinteger(L, (long)arg); - else if (type == LUA_TSTRING) - lua_pushstring(L, (char *)arg); - else if (type == LUA_TLIGHTUSERDATA || type == LUA_TTABLE) { - long ref = (long)arg; - lua_rawgeti(L, LUA_REGISTRYINDEX, ref); - luaL_unref(L, LUA_REGISTRYINDEX, ref); - } else warn("events.emit: ignored invalid argument type"); - n++; - type = va_arg(ap, int); - } - va_end(ap); - if (lua_pcall(L, n, 1, 0) == LUA_OK) - ret = lua_toboolean(L, -1); - else - lL_event(L, "error", LUA_TSTRING, lua_tostring(L, -1), -1); - lua_pop(L, 1); // result - } else lua_pop(L, 1); // non-function - } else lua_pop(L, 1); // non-table - return ret; -} - -/** - * Emits a Scintilla notification event. - * @param L The Lua state. - * @param n The Scintilla notification struct. - * @see lL_event - */ -static void lL_notify(lua_State *L, struct SCNotification *n) { - lua_newtable(L); - lua_pushinteger(L, n->nmhdr.code), lua_setfield(L, -2, "code"); - lua_pushinteger(L, n->position), lua_setfield(L, -2, "position"); - lua_pushinteger(L, n->ch), lua_setfield(L, -2, "ch"); - lua_pushinteger(L, n->modifiers), lua_setfield(L, -2, "modifiers"); - //lua_pushinteger(L, n->modificationType); - //lua_setfield(L, -2, "modification_type"); - lua_pushstring(L, n->text), lua_setfield(L, -2, "text"); - //lua_pushinteger(L, n->length), lua_setfield(L, -2, "length"); - //lua_pushinteger(L, n->linesAdded), lua_setfield(L, -2, "lines_added"); - //lua_pushinteger(L, n->message), lua_setfield(L, -2, "message"); - lua_pushinteger(L, n->wParam), lua_setfield(L, -2, "wParam"); - lua_pushinteger(L, n->lParam), lua_setfield(L, -2, "lParam"); - lua_pushinteger(L, n->line), lua_setfield(L, -2, "line"); - //lua_pushinteger(L, n->foldLevelNow), lua_setfield(L, -2, "fold_level_now"); - //lua_pushinteger(L, n->foldLevelPrev); - //lua_setfield(L, -2, "fold_level_prev"); - lua_pushinteger(L, n->margin), lua_setfield(L, -2, "margin"); - lua_pushinteger(L, n->x), lua_setfield(L, -2, "x"); - lua_pushinteger(L, n->y), lua_setfield(L, -2, "y"); - lL_event(L, "SCN", LUA_TTABLE, luaL_ref(L, LUA_REGISTRYINDEX), -1); +#if GTK +static void m_clicked(GtkWidget *menu, void *id) { + lL_event(lua, "menu_clicked", LUA_TNUMBER, GPOINTER_TO_INT(id), -1); } +#endif -/** - * Shows the context menu for a Scintilla view based on a mouse event. - * @param L The Lua state. - * @param event An optional GTK mouse button event. - */ -static void lL_showcontextmenu(lua_State *L, void *event) { - lua_getglobal(L, "gui"); - if (lua_istable(L, -1)) { - lua_getfield(L, -1, "context_menu"); - if (lua_isuserdata(L, -1)) { +static int lgui_menu(lua_State *L) { + luaL_checktype(L, 1, LUA_TTABLE); #if GTK - GtkWidget *menu = (GtkWidget *)lua_touserdata(L, -1); - gtk_widget_show_all(menu); - gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, - event ? ((GdkEventButton *)event)->button : 0, - gdk_event_get_time((GdkEvent *)event)); + l_pushmenu(L, -1, m_clicked, FALSE); #elif NCURSES - // TODO: popup context menu. + // TODO: create menu and manage memory. #endif - } else if (!lua_isnil(L, -1)) - warn("gui.context_menu: menu expected"); - lua_pop(L, 1); // gui.context_menu - } else lua_pop(L, 1); // non-table -} - -/******************************************************************************/ -/********************* Lua Functions *********************/ -/********************* (Stack Maintenence is Unnecessary) *********************/ -/******************************************************************************/ - -/** - * Calls a function as a Scintilla function. - * Does not remove any arguments from the stack, but does push results. - * @param L The Lua state. - * @param msg The Scintilla message. - * @param wtype The type of Scintilla wParam. - * @param ltype The type of Scintilla lParam. - * @param rtype The type of the Scintilla return. - * @param arg The stack index of the first Scintilla parameter. Subsequent - * elements will also be passed to Scintilla as needed. - * @return number of results pushed onto the stack. - * @see lL_checkscintillaparam - */ -static int l_callscintilla(lua_State *L, int msg, int wtype, int ltype, - int rtype, int arg) { - uptr_t wparam = 0; - sptr_t lparam = 0, len = 0; - int params_needed = 2, string_return = FALSE; - char *return_string = 0; - - // Even though the SCI_PRIVATELEXERCALL interface has ltype int, the LPeg - // lexer API uses different types depending on wparam. Modify ltype - // appropriately. See the LPeg lexer API for more information. - if (msg == SCI_PRIVATELEXERCALL) { - ltype = tSTRINGRESULT; - int c = luaL_checklong(L, arg); - if (c == SCI_GETDIRECTFUNCTION || c == SCI_SETDOCPOINTER) - ltype = tINT; - else if (c == SCI_SETLEXERLANGUAGE) - ltype = tSTRING; - } - - // Set wParam and lParam appropriately for Scintilla based on wtype and ltype. - if (wtype == tLENGTH && ltype == tSTRING) { - wparam = (uptr_t)lua_rawlen(L, arg); - lparam = (sptr_t)luaL_checkstring(L, arg); - params_needed = 0; - } else if (ltype == tSTRINGRESULT) { - string_return = TRUE; - params_needed = (wtype == tLENGTH) ? 0 : 1; - } - if (params_needed > 0) wparam = lL_checkscintillaparam(L, &arg, wtype); - if (params_needed > 1) lparam = lL_checkscintillaparam(L, &arg, ltype); - if (string_return) { // create a buffer for the return string - len = SS(focused_view, msg, wparam, 0); - if (wtype == tLENGTH) wparam = len; - return_string = malloc(len + 1), return_string[len] = '\0'; - if (msg == SCI_GETTEXT || msg == SCI_GETSELTEXT || msg == SCI_GETCURLINE) - len--; // Scintilla appends '\0' for these messages; compensate - lparam = (sptr_t)return_string; - } - - // Send the message to Scintilla and return the appropriate values. - sptr_t result = SS(focused_view, msg, wparam, lparam); - arg = lua_gettop(L); - if (string_return) lua_pushlstring(L, return_string, len); - if (rtype == tBOOL) lua_pushboolean(L, result); - if (rtype > tVOID && rtype < tBOOL) lua_pushinteger(L, result); - if (return_string) free(return_string); - return lua_gettop(L) - arg; -} - -static int lbuf_closure(lua_State *L) { - // If optional buffer argument is given, check it. - if (lua_istable(L, 1)) lL_globaldoccheck(L, 1); - // Interface table is of the form { msg, rtype, wtype, ltype }. - return l_callscintilla(L, l_rawgetiint(L, lua_upvalueindex(1), 1), - l_rawgetiint(L, lua_upvalueindex(1), 3), - l_rawgetiint(L, lua_upvalueindex(1), 4), - l_rawgetiint(L, lua_upvalueindex(1), 2), - lua_istable(L, 1) ? 2 : 1); -} - -static int lbuf_property(lua_State *L) { - int newindex = (lua_gettop(L) == 3); - luaL_getmetatable(L, "ta_buffer"); - lua_getmetatable(L, 1); // metatable can be either ta_buffer or ta_bufferp - int is_buffer = lua_compare(L, -1, -2, LUA_OPEQ); - lua_pop(L, 2); // metatable, metatable - - // If the key is a Scintilla function, return a callable closure. - if (is_buffer && !newindex) { - lua_getfield(L, LUA_REGISTRYINDEX, "ta_functions"); - lua_pushvalue(L, 2), lua_gettable(L, -2); - if (lua_istable(L, -1)) return (lua_pushcclosure(L, lbuf_closure, 1), 1); - lua_pop(L, 2); // non-table, ta_functions - } - - // If the key is a Scintilla property, determine if it is an indexible one or - // not. If so, return a table with the appropriate metatable; otherwise call - // Scintilla to get or set the property's value. - lua_getfield(L, LUA_REGISTRYINDEX, "ta_properties"); - if (is_buffer) - lua_pushvalue(L, 2); // key is given - else - lua_getfield(L, 1, "property"); // indexible property - lua_gettable(L, -2); - if (lua_istable(L, -1)) { - // Interface table is of the form { get_id, set_id, rtype, wtype }. - if (!is_buffer) - lua_getfield(L, 1, "buffer"), lL_globaldoccheck(L, -1), lua_pop(L, 1); - else - lL_globaldoccheck(L, 1); - if (is_buffer && l_rawgetiint(L, -1, 4) != tVOID) { // indexible property - lua_newtable(L); - lua_pushvalue(L, 2), lua_setfield(L, -2, "property"); - lua_pushvalue(L, 1), lua_setfield(L, -2, "buffer"); - l_setmetatable(L, -1, "ta_bufferp", lbuf_property, lbuf_property); - return 1; - } - int msg = l_rawgetiint(L, -1, !newindex ? 1 : 2); - int wtype = l_rawgetiint(L, -1, !newindex ? 4 : 3); - int ltype = !newindex ? tVOID : l_rawgetiint(L, -1, 4); - int rtype = !newindex ? l_rawgetiint(L, -1, 3) : tVOID; - if (newindex && (ltype != tVOID || wtype == tSTRING)) { - int temp = wtype; - wtype = ltype, ltype = temp; - } - luaL_argcheck(L, msg != 0, !newindex ? 2 : 3, - !newindex ? "write-only property" : "read-only property"); - return l_callscintilla(L, msg, wtype, ltype, rtype, - (!is_buffer || !newindex) ? 2 : 3); - } else lua_pop(L, 2); // non-table, ta_properties - - !newindex ? lua_rawget(L, 1) : lua_rawset(L, 1); - return 1; -} - -static int lview__index(lua_State *L) { - const char *key = lua_tostring(L, 2); - if (strcmp(key, "buffer") == 0) - l_pushdoc(L, SS(lL_checkview(L, 1), SCI_GETDOCPOINTER, 0, 0)); - else if (strcmp(key, "size") == 0) { - Scintilla *view = lL_checkview(L, 1); -#if GTK - if (GTK_IS_PANED(gtk_widget_get_parent(view))) { - int pos = gtk_paned_get_position(GTK_PANED(gtk_widget_get_parent(view))); - lua_pushinteger(L, pos); - } else lua_pushnil(L); -#elif NCURSES - lua_pushnil(L); -#endif - } else lua_rawget(L, 1); - return 1; -} - -static int lview__newindex(lua_State *L) { - const char *key = lua_tostring(L, 2); - if (strcmp(key, "buffer") == 0) - luaL_argerror(L, 3, "read-only property"); - else if (strcmp(key, "size") == 0) { -#if GTK - GtkWidget *pane = gtk_widget_get_parent(lL_checkview(L, 1)); - int size = luaL_checkinteger(L, 3); - if (size < 0) size = 0; - if (GTK_IS_PANED(pane)) gtk_paned_set_position(GTK_PANED(pane), size); -#elif NCURSES - // TODO: set size. -#endif - } else lua_rawset(L, 1); - return 0; + return 1; } static int lgui__index(lua_State *L) { @@ -1815,160 +1383,257 @@ static int lgui__newindex(lua_State *L) { return 0; } -#if GTK -#define toggled(w) gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(w)) -#elif NCURSES -#define toggled(w) w -#endif -static int lfind__index(lua_State *L) { - const char *key = lua_tostring(L, 2); - if (strcmp(key, "find_entry_text") == 0) -#if GTK - lua_pushstring(L, gtk_entry_get_text(GTK_ENTRY(find_entry))); -#elif NCURSES - lua_pushstring(L, find_text); -#endif - else if (strcmp(key, "replace_entry_text") == 0) -#if GTK - lua_pushstring(L, gtk_entry_get_text(GTK_ENTRY(replace_entry))); -#elif NCURSES - lua_pushstring(L, repl_text); -#endif - else if (strcmp(key, "match_case") == 0) - lua_pushboolean(L, toggled(match_case)); - else if (strcmp(key, "whole_word") == 0) - lua_pushboolean(L, toggled(whole_word)); - else if (strcmp(key, "lua") == 0) - lua_pushboolean(L, toggled(lua_pattern)); - else if (strcmp(key, "in_files") == 0) - lua_pushboolean(L, toggled(in_files)); - else - lua_rawget(L, 1); +static int lbuffer_new(lua_State *L) { + new_buffer(0); + lua_getfield(L, LUA_REGISTRYINDEX, "ta_buffers"); + lua_rawgeti(L, -1, lua_rawlen(L, -1)); return 1; } +static int lquit(lua_State *L) { #if GTK -#define toggle(w, b) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w), b) + GdkEventAny event; + event.type = GDK_DELETE; + event.window = gtk_widget_get_window(window); + event.send_event = TRUE; + gdk_event_put((GdkEvent *)(&event)); #elif NCURSES -#define toggle(w, b) w = b + quit = true; #endif -static int lfind__newindex(lua_State *L) { - const char *key = lua_tostring(L, 2); - if (strcmp(key, "find_entry_text") == 0) { + return 0; +} + +static int lreset(lua_State *L) { + lL_event(L, "reset_before", -1); + lL_init(L, 0, NULL, TRUE); + lua_pushboolean(L, TRUE), lua_setglobal(L, "RESETTING"); + l_setglobalview(L, focused_view); + l_setglobaldoc(L, SS(focused_view, SCI_GETDOCPOINTER, 0, 0)); + lL_dofile(L, "init.lua"); + lua_pushnil(L), lua_setglobal(L, "RESETTING"); + lL_event(L, "reset_after", -1); + return 0; +} + #if GTK - gtk_entry_set_text(GTK_ENTRY(find_entry), lua_tostring(L, 3)); -#elif NCURSES - if (find_text) free(find_text); - find_text = copy(lua_tostring(L, 3)); +static int emit_timeout(void *data) { + int *refs = (int *)data; + lua_rawgeti(lua, LUA_REGISTRYINDEX, refs[0]); // function + int nargs = 0, repeat = TRUE; + while (refs[++nargs]) lua_rawgeti(lua, LUA_REGISTRYINDEX, refs[nargs]); + int ok = (lua_pcall(lua, nargs - 1, 1, 0) == LUA_OK); + if (!ok || !lua_toboolean(lua, -1)) { + while (--nargs >= 0) luaL_unref(lua, LUA_REGISTRYINDEX, refs[nargs]); + repeat = FALSE; + if (!ok) lL_event(lua, "error", LUA_TSTRING, lua_tostring(lua, -1), -1); + } + lua_pop(lua, 1); // result + return repeat; +} #endif - } else if (strcmp(key, "replace_entry_text") == 0) { + +static int ltimeout(lua_State *L) { #if GTK - gtk_entry_set_text(GTK_ENTRY(replace_entry), lua_tostring(L, 3)); + double timeout = luaL_checknumber(L, 1); + luaL_argcheck(L, timeout > 0, 1, "timeout must be > 0"); + luaL_argcheck(L, lua_isfunction(L, 2), 2, "function expected"); + int n = lua_gettop(L); + int *refs = (int *)calloc(n, sizeof(int)); + lua_pushvalue(L, 2); + refs[0] = luaL_ref(L, LUA_REGISTRYINDEX); + for (int i = 3; i <= n; i++) { + lua_pushvalue(L, i); + refs[i - 2] = luaL_ref(L, LUA_REGISTRYINDEX); + } + g_timeout_add(timeout * 1000, emit_timeout, (void *)refs); #elif NCURSES - if (repl_text) free(repl_text); - repl_text = copy(lua_tostring(L, 3)); -#endif - } else if (strcmp(key, "match_case") == 0) - toggle(match_case, lua_toboolean(L, -1)); - else if (strcmp(key, "whole_word") == 0) - toggle(whole_word, lua_toboolean(L, -1)); - else if (strcmp(key, "lua") == 0) - toggle(lua_pattern, lua_toboolean(L, -1)); - else if (strcmp(key, "in_files") == 0) - toggle(in_files, lua_toboolean(L, -1)); -#if GTK - else if (strcmp(key, "find_label_text") == 0) - gtk_label_set_text_with_mnemonic(GTK_LABEL(flabel), lua_tostring(L, 3)); - else if (strcmp(key, "replace_label_text") == 0) - gtk_label_set_text_with_mnemonic(GTK_LABEL(rlabel), lua_tostring(L, 3)); - else if (strcmp(key, "find_next_button_text") == 0) - gtk_button_set_label(GTK_BUTTON(fnext_button), lua_tostring(L, 3)); - else if (strcmp(key, "find_prev_button_text") == 0) - gtk_button_set_label(GTK_BUTTON(fprev_button), lua_tostring(L, 3)); - else if (strcmp(key, "replace_button_text") == 0) - gtk_button_set_label(GTK_BUTTON(r_button), lua_tostring(L, 3)); - else if (strcmp(key, "replace_all_button_text") == 0) - gtk_button_set_label(GTK_BUTTON(ra_button), lua_tostring(L, 3)); - else if (strcmp(key, "match_case_label_text") == 0) - gtk_button_set_label(GTK_BUTTON(match_case), lua_tostring(L, 3)); - else if (strcmp(key, "whole_word_label_text") == 0) - gtk_button_set_label(GTK_BUTTON(whole_word), lua_tostring(L, 3)); - else if (strcmp(key, "lua_pattern_label_text") == 0) - gtk_button_set_label(GTK_BUTTON(lua_pattern), lua_tostring(L, 3)); - else if (strcmp(key, "in_files_label_text") == 0) - gtk_button_set_label(GTK_BUTTON(in_files), lua_tostring(L, 3)); + luaL_error(L, "not implemented in this environment"); #endif - else - lua_rawset(L, 1); return 0; } -static int lce__index(lua_State *L) { - const char *key = lua_tostring(L, 2); - if (strcmp(key, "entry_text") == 0) -#if GTK - lua_pushstring(L, gtk_entry_get_text(GTK_ENTRY(command_entry))); -#elif NCURSES - lua_pushstring(L, command_text); -#endif - else - lua_rawget(L, 1); +static int lstring_iconv(lua_State *L) { + size_t text_len = 0; + char *text = (char *)luaL_checklstring(L, 1, &text_len); + const char *to = luaL_checkstring(L, 2); + const char *from = luaL_checkstring(L, 3); + int converted = FALSE; + iconv_t cd = iconv_open(to, from); + if (cd != (iconv_t) -1) { + char *out = malloc(text_len + 1); + char *outp = out; + size_t inbytesleft = text_len, outbytesleft = text_len; + if (iconv(cd, &text, &inbytesleft, &outp, &outbytesleft) != -1) { + lua_pushlstring(L, out, outp - out); + converted = TRUE; + } + free(out); + iconv_close(cd); + } + if (!converted) luaL_error(L, "Conversion failed"); return 1; } -static int lce__newindex(lua_State *L) { - const char *key = lua_tostring(L, 2); - if (strcmp(key, "entry_text") == 0) { +/** + * Initializes or re-initializes the Lua state. + * Populates the state with global variables and functions, then runs the + * 'core/init.lua' script. + * @param L The Lua state. + * @param argc The number of command line parameters. + * @param argv The array of command line parameters. + * @param reinit Flag indicating whether or not to reinitialize the Lua state. + * @return TRUE on success, FALSE otherwise. + */ +static int lL_init(lua_State *L, int argc, char **argv, int reinit) { + if (!reinit) { + lua_newtable(L); + for (int i = 0; i < argc; i++) + lua_pushstring(L, argv[i]), lua_rawseti(L, -2, i); + lua_setfield(L, LUA_REGISTRYINDEX, "ta_arg"); + lua_newtable(L), lua_setfield(L, LUA_REGISTRYINDEX, "ta_buffers"); + lua_newtable(L), lua_setfield(L, LUA_REGISTRYINDEX, "ta_views"); + } else { // clear package.loaded and _G + lua_getglobal(L, "package"), lua_getfield(L, -1, "loaded"); + lL_cleartable(L, -1); + lua_pop(L, 2); // package and package.loaded +#if !LUAJIT + lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS); + lL_cleartable(L, -1); + lua_pop(L, 1); // _G +#else + lL_cleartable(L, LUA_GLOBALSINDEX); +#endif + } + luaL_openlibs(L); + lL_openlib(L, "lpeg", luaopen_lpeg); + lL_openlib(L, "lfs", luaopen_lfs); + + lua_newtable(L); + lua_newtable(L); + l_setcfunction(L, -1, "find_next", lfind_next); + l_setcfunction(L, -1, "find_prev", lfind_prev); + l_setcfunction(L, -1, "focus", lfind_focus); + l_setcfunction(L, -1, "replace", lfind_replace); + l_setcfunction(L, -1, "replace_all", lfind_replace_all); + l_setmetatable(L, -1, "ta_find", lfind__index, lfind__newindex); + lua_setfield(L, -2, "find"); + lua_newtable(L); + l_setcfunction(L, -1, "focus", lce_focus); + l_setcfunction(L, -1, "show_completions", lce_show_completions); + l_setmetatable(L, -1, "ta_command_entry", lce__index, lce__newindex); + lua_setfield(L, -2, "command_entry"); + l_setcfunction(L, -1, "dialog", lgui_dialog); + l_setcfunction(L, -1, "get_split_table", lgui_get_split_table); + l_setcfunction(L, -1, "goto_view", lgui_goto_view); + l_setcfunction(L, -1, "menu", lgui_menu); + l_setmetatable(L, -1, "ta_gui", lgui__index, lgui__newindex); + lua_setglobal(L, "gui"); + + lua_getglobal(L, "_G"); + l_setcfunction(L, -1, "new_buffer", lbuffer_new); + l_setcfunction(L, -1, "quit", lquit); + l_setcfunction(L, -1, "reset", lreset); + l_setcfunction(L, -1, "timeout", ltimeout); + lua_pop(L, 1); // _G + + lua_getglobal(L, "string"); + l_setcfunction(L, -1, "iconv", lstring_iconv); + lua_pop(L, 1); // string + + lua_getfield(L, LUA_REGISTRYINDEX, "ta_arg"), lua_setglobal(L, "arg"); + lua_getfield(L, LUA_REGISTRYINDEX, "ta_buffers"); + lua_setglobal(L, "_BUFFERS"); + lua_getfield(L, LUA_REGISTRYINDEX, "ta_views"), lua_setglobal(L, "_VIEWS"); + lua_pushstring(L, textadept_home), lua_setglobal(L, "_HOME"); +#if __WIN32__ + lua_pushboolean(L, 1), lua_setglobal(L, "WIN32"); +#elif __OSX__ + lua_pushboolean(L, 1), lua_setglobal(L, "OSX"); +#endif #if GTK - gtk_entry_set_text(GTK_ENTRY(command_entry), lua_tostring(L, 3)); + const char *charset = 0; + g_get_charset(&charset); + lua_pushstring(L, charset), lua_setglobal(L, "_CHARSET"); #elif NCURSES - if (command_text) free(command_text); - command_text = copy(lua_tostring(L, 3)); + lua_pushboolean(L, 1), lua_setglobal(L, "NCURSES"); + lua_pushstring(L, "UTF-8"), lua_setglobal(L, "_CHARSET"); // TODO: get charset #endif - } else lua_rawset(L, 1); - return 0; -} -/******************************************************************************/ -/****************** Lua CFunctions *******************/ -/****************** (For documentation, consult the LuaDoc) *******************/ -/******************************************************************************/ -static int lbuffer_check_global(lua_State *L) { - lL_globaldoccheck(L, 1); - return 0; + if (lL_dofile(L, "core/init.lua")) { + lua_getglobal(L, "_SCINTILLA"); + lua_getfield(L, -1, "constants"); + lua_setfield(L, LUA_REGISTRYINDEX, "ta_constants"); + lua_getfield(L, -1, "functions"); + lua_setfield(L, LUA_REGISTRYINDEX, "ta_functions"); + lua_getfield(L, -1, "properties"); + lua_setfield(L, LUA_REGISTRYINDEX, "ta_properties"); + lua_pop(L, 1); // _SCINTILLA + return TRUE; + } + lua_close(L); + return FALSE; } -static int lbuffer_delete(lua_State *L) { - lL_globaldoccheck(L, 1); - sptr_t doc = SS(focused_view, SCI_GETDOCPOINTER, 0, 0); - lua_getfield(L, LUA_REGISTRYINDEX, "ta_buffers"); - if (lua_rawlen(L, -1) == 1) new_buffer(0); - lL_gotodoc(L, focused_view, -1, TRUE); - delete_buffer(doc); - lL_event(L, "buffer_deleted", -1), - lL_event(L, "buffer_after_switch", -1); - return 0; +/** + * Loads and runs the given file. + * @param L The Lua state. + * @param filename The file name relative to textadept_home. + * @return 1 if there are no errors or 0 in case of errors. + */ +static int lL_dofile(lua_State *L, const char *filename) { + char *file = malloc(strlen(textadept_home) + 1 + strlen(filename) + 1); + stpcpy(stpcpy(stpcpy(file, textadept_home), "/"), filename); + int ok = (luaL_dofile(L, file) == LUA_OK); + if (!ok) { +#if GTK + GtkWidget *dialog = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_OK, "%s\n", + lua_tostring(L, -1)); + gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); +#elif NCURSES + WINDOW *win = newwin(0, 0, 0, 0); + wprintw(win, lua_tostring(L, -1)), wrefresh(win); + getch(); + delwin(win); +#endif + lua_settop(L, 0); + } + free(file); + return ok; } -static int lbuffer_new(lua_State *L) { - new_buffer(0); - lua_getfield(L, LUA_REGISTRYINDEX, "ta_buffers"); - lua_rawgeti(L, -1, lua_rawlen(L, -1)); - return 1; +/** + * Checks whether the function argument narg is a Scintilla view and returns + * this view cast to a Scintilla. + * @param L The Lua state. + * @param narg The stack index of the Scintilla view. + * @return Scintilla view + */ +static Scintilla *lL_checkview(lua_State *L, int narg) { + luaL_getmetatable(L, "ta_view"); + lua_getmetatable(L, narg); + luaL_argcheck(L, lua_compare(L, -1, -2, LUA_OPEQ), narg, "View expected"); + lua_getfield(L, (narg > 0) ? narg : narg - 2, "widget_pointer"); + Scintilla *view = (Scintilla *)lua_touserdata(L, -1); + lua_pop(L, 3); // widget_pointer, metatable, metatable + return view; } -static int lbuffer_text_range(lua_State *L) { - lL_globaldoccheck(L, 1); - struct Sci_TextRange tr; - tr.chrg.cpMin = luaL_checkinteger(L, 2); - tr.chrg.cpMax = luaL_checkinteger(L, 3); - luaL_argcheck(L, tr.chrg.cpMin <= tr.chrg.cpMax, 3, "start > end"); - tr.lpstrText = malloc(tr.chrg.cpMax - tr.chrg.cpMin + 1); - SS(focused_view, SCI_GETTEXTRANGE, 0, (long)(&tr)); - lua_pushlstring(L, tr.lpstrText, tr.chrg.cpMax - tr.chrg.cpMin); - if (tr.lpstrText) free(tr.lpstrText); - return 1; +// If the indexed view is not currently focused, temporarily focus it so calls +// to handlers will not throw 'indexed buffer is not the focused one' error. +static int lview_goto_buffer(lua_State *L) { + Scintilla *view = lL_checkview(L, 1), *prev_view = focused_view; + int n = luaL_checkinteger(L, 2), relative = lua_toboolean(L, 3); + int switch_focus = (view != focused_view); + if (switch_focus) SS(view, SCI_SETFOCUS, TRUE, 0); + lL_event(L, "buffer_before_switch", -1); + lL_gotodoc(L, view, n, relative); + lL_event(L, "buffer_after_switch", -1); + if (switch_focus) SS(view, SCI_SETFOCUS, FALSE, 0), focus_view(prev_view); + return 0; } static int lview_split(lua_State *L) { @@ -1983,271 +1648,563 @@ static int lview_unsplit(lua_State *L) { return 1; } +static int lview__index(lua_State *L) { + const char *key = lua_tostring(L, 2); + if (strcmp(key, "buffer") == 0) + l_pushdoc(L, SS(lL_checkview(L, 1), SCI_GETDOCPOINTER, 0, 0)); + else if (strcmp(key, "size") == 0) { + Scintilla *view = lL_checkview(L, 1); #if GTK -#define child1(p) gtk_paned_get_child1(GTK_PANED(p)) -#define child2(p) gtk_paned_get_child2(GTK_PANED(p)) - -static void l_pushsplittable(lua_State *L, GtkWidget *c1, GtkWidget *c2) { - lua_newtable(L); - if (GTK_IS_PANED(c1)) - l_pushsplittable(L, child1(c1), child2(c1)); - else - l_pushview(L, c1); - lua_rawseti(L, -2, 1); - if (GTK_IS_PANED(c2)) - l_pushsplittable(L, child1(c2), child2(c2)); - else - l_pushview(L, c2); - lua_rawseti(L, -2, 2); - lua_pushboolean(L, GTK_IS_HPANED(gtk_widget_get_parent(c1))); - lua_setfield(L, -2, "vertical"); - int size = gtk_paned_get_position(GTK_PANED(gtk_widget_get_parent(c1))); - lua_pushinteger(L, size), lua_setfield(L, -2, "size"); -} -#endif - -static int lgui_get_split_table(lua_State *L) { -#if GTK - GtkWidget *pane = gtk_widget_get_parent(focused_view); - if (GTK_IS_PANED(pane)) { - while (GTK_IS_PANED(gtk_widget_get_parent(pane))) - pane = gtk_widget_get_parent(pane); - l_pushsplittable(L, child1(pane), child2(pane)); - } else l_pushview(L, focused_view); + if (GTK_IS_PANED(gtk_widget_get_parent(view))) { + int pos = gtk_paned_get_position(GTK_PANED(gtk_widget_get_parent(view))); + lua_pushinteger(L, pos); + } else lua_pushnil(L); #elif NCURSES - l_pushview(L, focused_view); // TODO: push split table + lua_pushnil(L); #endif + } else lua_rawget(L, 1); return 1; } -// If the indexed view is not currently focused, temporarily focus it so calls -// to handlers will not throw 'indexed buffer is not the focused one' error. -static int lview_goto_buffer(lua_State *L) { - Scintilla *view = lL_checkview(L, 1), *prev_view = focused_view; - int n = luaL_checkinteger(L, 2), relative = lua_toboolean(L, 3); - int switch_focus = (view != focused_view); - if (switch_focus) SS(view, SCI_SETFOCUS, TRUE, 0); - lL_event(L, "buffer_before_switch", -1); - lL_gotodoc(L, view, n, relative); - lL_event(L, "buffer_after_switch", -1); - if (switch_focus) SS(view, SCI_SETFOCUS, FALSE, 0), focus_view(prev_view); +static int lview__newindex(lua_State *L) { + const char *key = lua_tostring(L, 2); + if (strcmp(key, "buffer") == 0) + luaL_argerror(L, 3, "read-only property"); + else if (strcmp(key, "size") == 0) { +#if GTK + GtkWidget *pane = gtk_widget_get_parent(lL_checkview(L, 1)); + int size = luaL_checkinteger(L, 3); + if (size < 0) size = 0; + if (GTK_IS_PANED(pane)) gtk_paned_set_position(GTK_PANED(pane), size); +#elif NCURSES + // TODO: set size. +#endif + } else lua_rawset(L, 1); return 0; } -static int lgui_dialog(lua_State *L) { - GCDialogType type = gcocoadialog_type(luaL_checkstring(L, 1)); - int i, j, k, n = lua_gettop(L) - 1, argc = n; - for (i = 2; i < n + 2; i++) - if (lua_istable(L, i)) argc += lua_rawlen(L, i) - 1; - const char **argv = malloc((argc + 1) * sizeof(const char *)); - for (i = 0, j = 2; j < n + 2; j++) - if (lua_istable(L, j)) { - int len = lua_rawlen(L, j); - for (k = 1; k <= len; k++) { - lua_rawgeti(L, j, k); - argv[i++] = luaL_checkstring(L, -1); - lua_pop(L, 1); - } - } else argv[i++] = luaL_checkstring(L, j); - argv[argc] = 0; - char *out = gcocoadialog(type, argc, argv); - lua_pushstring(L, out); - free(out), free(argv); - return 1; +/** + * Adds the Scintilla view with a metatable to the 'views' registry table. + * @param L The Lua state. + * @param view The Scintilla view to add. + */ +static void lL_addview(lua_State *L, Scintilla *view) { + lua_getfield(L, LUA_REGISTRYINDEX, "ta_views"); + lua_newtable(L); + lua_pushlightuserdata(L, view); + lua_pushvalue(L, -1), lua_setfield(L, -3, "widget_pointer"); + l_setcfunction(L, -2, "goto_buffer", lview_goto_buffer); + l_setcfunction(L, -2, "split", lview_split); + l_setcfunction(L, -2, "unsplit", lview_unsplit); + l_setmetatable(L, -2, "ta_view", lview__index, lview__newindex); + // vs[userdata] = v, vs[#vs + 1] = v, vs[v] = #vs + lua_pushvalue(L, -2), lua_settable(L, -4); + lua_pushvalue(L, -1), lua_rawseti(L, -3, lua_rawlen(L, -3) + 1); + lua_pushinteger(L, lua_rawlen(L, -2)), lua_settable(L, -3); + lua_pop(L, 1); // views } -static int lgui_goto_view(lua_State *L) { - int n = luaL_checkinteger(L, 1), relative = lua_toboolean(L, 2); - if (relative && n == 0) return 0; +/** + * Removes the Scintilla view from the 'views' registry table. + * The view must have been previously added with lL_addview. + * @param L The Lua state. + * @param view The Scintilla view to remove. + * @see lL_addview + */ +static void lL_removeview(lua_State *L, Scintilla *view) { + lua_newtable(L); lua_getfield(L, LUA_REGISTRYINDEX, "ta_views"); - if (relative) { - l_pushview(L, focused_view), lua_gettable(L, -2); - n = lua_tointeger(L, -1) + n; - if (n > lua_rawlen(L, -2)) - n = 1; - else if (n < 1) - n = lua_rawlen(L, -2); - lua_rawgeti(L, -2, n); - } else { - luaL_argcheck(L, n > 0 && n <= lua_rawlen(L, -1), 1, - "no View exists at that index"); - lua_rawgeti(L, -1, n); + lua_pushnil(L); + while (lua_next(L, -2)) { + if (lua_isnumber(L, -2) && view != l_toview(L, -1)) { + lua_getfield(L, -1, "widget_pointer"); + // vs[userdata] = v, vs[#vs + 1] = v, vs[v] = #vs + lua_pushvalue(L, -2), lua_rawseti(L, -6, lua_rawlen(L, -6) + 1); + lua_pushvalue(L, -2), lua_settable(L, -6); + lua_pushinteger(L, lua_rawlen(L, -4)), lua_settable(L, -5); + } else lua_pop(L, 1); // value } - Scintilla *view = l_toview(L, -1); - focus_view(view); -#if GTK - // gui.dialog() interferes with focus so gtk_widget_grab_focus() does not - // always work. If this is the case, ensure goto_view() is called. - if (!gtk_widget_has_focus(view)) goto_view(view); -#endif + lua_pop(L, 1); // views + lua_pushvalue(L, -1), lua_setfield(L, LUA_REGISTRYINDEX, "ta_views"); + lua_setglobal(L, "_VIEWS"); +} + +/** + * Checks whether the function argument narg is a Scintilla document. If not, + * raises an error. + * @param L The Lua state. + * @param narg The stack index of the Scintilla document. + * @return Scintilla document + */ +static void lL_globaldoccheck(lua_State *L, int narg) { + luaL_getmetatable(L, "ta_buffer"); + lua_getmetatable(L, (narg > 0) ? narg : narg - 1); + luaL_argcheck(L, lua_compare(L, -1, -2, LUA_OPEQ), narg, "Buffer expected"); + lua_getfield(L, (narg > 0) ? narg : narg - 2, "doc_pointer"); + sptr_t doc = (sptr_t)lua_touserdata(L, -1); + luaL_argcheck(L, doc == SS(focused_view, SCI_GETDOCPOINTER, 0, 0), narg, + "this buffer is not the current one"); + lua_pop(L, 3); // doc_pointer, metatable, metatable +} + +static int lbuffer_check_global(lua_State *L) { + lL_globaldoccheck(L, 1); return 0; } -#if GTK -static void m_clicked(GtkWidget *menu, void *id) { - lL_event(lua, "menu_clicked", LUA_TNUMBER, GPOINTER_TO_INT(id), -1); +static int lbuffer_delete(lua_State *L) { + lL_globaldoccheck(L, 1); + sptr_t doc = SS(focused_view, SCI_GETDOCPOINTER, 0, 0); + lua_getfield(L, LUA_REGISTRYINDEX, "ta_buffers"); + if (lua_rawlen(L, -1) == 1) new_buffer(0); + lL_gotodoc(L, focused_view, -1, TRUE); + delete_buffer(doc); + lL_event(L, "buffer_deleted", -1), + lL_event(L, "buffer_after_switch", -1); + return 0; } -#endif -static int lgui_menu(lua_State *L) { - luaL_checktype(L, 1, LUA_TTABLE); -#if GTK - l_pushmenu(L, -1, m_clicked, FALSE); -#elif NCURSES - // TODO: create menu and manage memory. -#endif +static int lbuffer_text_range(lua_State *L) { + lL_globaldoccheck(L, 1); + struct Sci_TextRange tr; + tr.chrg.cpMin = luaL_checkinteger(L, 2); + tr.chrg.cpMax = luaL_checkinteger(L, 3); + luaL_argcheck(L, tr.chrg.cpMin <= tr.chrg.cpMax, 3, "start > end"); + tr.lpstrText = malloc(tr.chrg.cpMax - tr.chrg.cpMin + 1); + SS(focused_view, SCI_GETTEXTRANGE, 0, (long)(&tr)); + lua_pushlstring(L, tr.lpstrText, tr.chrg.cpMax - tr.chrg.cpMin); + if (tr.lpstrText) free(tr.lpstrText); return 1; } -static int lstring_iconv(lua_State *L) { - size_t text_len = 0; - char *text = (char *)luaL_checklstring(L, 1, &text_len); - const char *to = luaL_checkstring(L, 2); - const char *from = luaL_checkstring(L, 3); - int converted = FALSE; - iconv_t cd = iconv_open(to, from); - if (cd != (iconv_t) -1) { - char *out = malloc(text_len + 1); - char *outp = out; - size_t inbytesleft = text_len, outbytesleft = text_len; - if (iconv(cd, &text, &inbytesleft, &outp, &outbytesleft) != -1) { - lua_pushlstring(L, out, outp - out); - converted = TRUE; - } - free(out); - iconv_close(cd); +/** + * Checks whether the function argument narg is the given Scintilla parameter + * type and returns it cast to the proper type. + * @param L The Lua state. + * @param narg The stack index of the Scintilla parameter. + * @param type The Scintilla type to convert to. + * @return Scintilla param + */ +static long lL_checkscintillaparam(lua_State *L, int *narg, int type) { + if (type == tSTRING) + return (long)luaL_checkstring(L, (*narg)++); + else if (type == tBOOL) + return lua_toboolean(L, (*narg)++); + else if (type == tKEYMOD) { + int key = luaL_checkinteger(L, (*narg)++) & 0xFFFF; + return key | ((luaL_checkinteger(L, (*narg)++) & + (SCMOD_SHIFT | SCMOD_CTRL | SCMOD_ALT)) << 16); + } else if (type > tVOID && type < tBOOL) + return luaL_checklong(L, (*narg)++); + else + return 0; +} + +/** + * Calls a function as a Scintilla function. + * Does not remove any arguments from the stack, but does push results. + * @param L The Lua state. + * @param msg The Scintilla message. + * @param wtype The type of Scintilla wParam. + * @param ltype The type of Scintilla lParam. + * @param rtype The type of the Scintilla return. + * @param arg The stack index of the first Scintilla parameter. Subsequent + * elements will also be passed to Scintilla as needed. + * @return number of results pushed onto the stack. + * @see lL_checkscintillaparam + */ +static int l_callscintilla(lua_State *L, int msg, int wtype, int ltype, + int rtype, int arg) { + uptr_t wparam = 0; + sptr_t lparam = 0, len = 0; + int params_needed = 2, string_return = FALSE; + char *return_string = 0; + + // Even though the SCI_PRIVATELEXERCALL interface has ltype int, the LPeg + // lexer API uses different types depending on wparam. Modify ltype + // appropriately. See the LPeg lexer API for more information. + if (msg == SCI_PRIVATELEXERCALL) { + ltype = tSTRINGRESULT; + int c = luaL_checklong(L, arg); + if (c == SCI_GETDIRECTFUNCTION || c == SCI_SETDOCPOINTER) + ltype = tINT; + else if (c == SCI_SETLEXERLANGUAGE) + ltype = tSTRING; } - if (!converted) luaL_error(L, "Conversion failed"); + + // Set wParam and lParam appropriately for Scintilla based on wtype and ltype. + if (wtype == tLENGTH && ltype == tSTRING) { + wparam = (uptr_t)lua_rawlen(L, arg); + lparam = (sptr_t)luaL_checkstring(L, arg); + params_needed = 0; + } else if (ltype == tSTRINGRESULT) { + string_return = TRUE; + params_needed = (wtype == tLENGTH) ? 0 : 1; + } + if (params_needed > 0) wparam = lL_checkscintillaparam(L, &arg, wtype); + if (params_needed > 1) lparam = lL_checkscintillaparam(L, &arg, ltype); + if (string_return) { // create a buffer for the return string + len = SS(focused_view, msg, wparam, 0); + if (wtype == tLENGTH) wparam = len; + return_string = malloc(len + 1), return_string[len] = '\0'; + if (msg == SCI_GETTEXT || msg == SCI_GETSELTEXT || msg == SCI_GETCURLINE) + len--; // Scintilla appends '\0' for these messages; compensate + lparam = (sptr_t)return_string; + } + + // Send the message to Scintilla and return the appropriate values. + sptr_t result = SS(focused_view, msg, wparam, lparam); + arg = lua_gettop(L); + if (string_return) lua_pushlstring(L, return_string, len); + if (rtype == tBOOL) lua_pushboolean(L, result); + if (rtype > tVOID && rtype < tBOOL) lua_pushinteger(L, result); + if (return_string) free(return_string); + return lua_gettop(L) - arg; +} + +static int lbuf_closure(lua_State *L) { + // If optional buffer argument is given, check it. + if (lua_istable(L, 1)) lL_globaldoccheck(L, 1); + // Interface table is of the form { msg, rtype, wtype, ltype }. + return l_callscintilla(L, l_rawgetiint(L, lua_upvalueindex(1), 1), + l_rawgetiint(L, lua_upvalueindex(1), 3), + l_rawgetiint(L, lua_upvalueindex(1), 4), + l_rawgetiint(L, lua_upvalueindex(1), 2), + lua_istable(L, 1) ? 2 : 1); +} + +static int lbuf_property(lua_State *L) { + int newindex = (lua_gettop(L) == 3); + luaL_getmetatable(L, "ta_buffer"); + lua_getmetatable(L, 1); // metatable can be either ta_buffer or ta_bufferp + int is_buffer = lua_compare(L, -1, -2, LUA_OPEQ); + lua_pop(L, 2); // metatable, metatable + + // If the key is a Scintilla function, return a callable closure. + if (is_buffer && !newindex) { + lua_getfield(L, LUA_REGISTRYINDEX, "ta_functions"); + lua_pushvalue(L, 2), lua_gettable(L, -2); + if (lua_istable(L, -1)) return (lua_pushcclosure(L, lbuf_closure, 1), 1); + lua_pop(L, 2); // non-table, ta_functions + } + + // If the key is a Scintilla property, determine if it is an indexible one or + // not. If so, return a table with the appropriate metatable; otherwise call + // Scintilla to get or set the property's value. + lua_getfield(L, LUA_REGISTRYINDEX, "ta_properties"); + if (is_buffer) + lua_pushvalue(L, 2); // key is given + else + lua_getfield(L, 1, "property"); // indexible property + lua_gettable(L, -2); + if (lua_istable(L, -1)) { + // Interface table is of the form { get_id, set_id, rtype, wtype }. + if (!is_buffer) + lua_getfield(L, 1, "buffer"), lL_globaldoccheck(L, -1), lua_pop(L, 1); + else + lL_globaldoccheck(L, 1); + if (is_buffer && l_rawgetiint(L, -1, 4) != tVOID) { // indexible property + lua_newtable(L); + lua_pushvalue(L, 2), lua_setfield(L, -2, "property"); + lua_pushvalue(L, 1), lua_setfield(L, -2, "buffer"); + l_setmetatable(L, -1, "ta_bufferp", lbuf_property, lbuf_property); + return 1; + } + int msg = l_rawgetiint(L, -1, !newindex ? 1 : 2); + int wtype = l_rawgetiint(L, -1, !newindex ? 4 : 3); + int ltype = !newindex ? tVOID : l_rawgetiint(L, -1, 4); + int rtype = !newindex ? l_rawgetiint(L, -1, 3) : tVOID; + if (newindex && (ltype != tVOID || wtype == tSTRING)) { + int temp = wtype; + wtype = ltype, ltype = temp; + } + luaL_argcheck(L, msg != 0, !newindex ? 2 : 3, + !newindex ? "write-only property" : "read-only property"); + return l_callscintilla(L, msg, wtype, ltype, rtype, + (!is_buffer || !newindex) ? 2 : 3); + } else lua_pop(L, 2); // non-table, ta_properties + + !newindex ? lua_rawget(L, 1) : lua_rawset(L, 1); return 1; } -static int lquit(lua_State *L) { -#if GTK - GdkEventAny event; - event.type = GDK_DELETE; - event.window = gtk_widget_get_window(window); - event.send_event = TRUE; - gdk_event_put((GdkEvent *)(&event)); -#elif NCURSES - quit = true; -#endif - return 0; +/** + * Adds a Scintilla document with a metatable to the 'buffers' registry table. + * @param L The Lua state. + * @param doc The Scintilla document to add. + */ +static void lL_adddoc(lua_State *L, sptr_t doc) { + lua_getfield(L, LUA_REGISTRYINDEX, "ta_buffers"); + lua_newtable(L); + lua_pushlightuserdata(L, (sptr_t *)doc); // TODO: can this fail? + lua_pushvalue(L, -1), lua_setfield(L, -3, "doc_pointer"); + l_setcfunction(L, -2, "check_global", lbuffer_check_global); + l_setcfunction(L, -2, "delete", lbuffer_delete); + l_setcfunction(L, -2, "text_range", lbuffer_text_range); + l_setmetatable(L, -2, "ta_buffer", lbuf_property, lbuf_property); + // bs[userdata] = b, bs[#bs + 1] = b, bs[b] = #bs + lua_pushvalue(L, -2), lua_settable(L, -4); + lua_pushvalue(L, -1), lua_rawseti(L, -3, lua_rawlen(L, -3) + 1); + lua_pushinteger(L, lua_rawlen(L, -2)), lua_settable(L, -3); + lua_pop(L, 1); // buffers } -static int lreset(lua_State *L) { - lL_event(L, "reset_before", -1); - lL_init(L, 0, NULL, TRUE); - lua_pushboolean(L, TRUE), lua_setglobal(L, "RESETTING"); - l_setglobalview(L, focused_view); - l_setglobaldoc(L, SS(focused_view, SCI_GETDOCPOINTER, 0, 0)); - lL_dofile(L, "init.lua"); - lua_pushnil(L), lua_setglobal(L, "RESETTING"); - lL_event(L, "reset_after", -1); - return 0; +/** + * Returns the buffer at the given acceptable index as a Scintilla document. + * @param L The Lua state. + * @param index Stack index of the buffer. + * @return Scintilla document + */ +static sptr_t l_todoc(lua_State *L, int index) { + lua_getfield(L, index, "doc_pointer"); + sptr_t doc = (sptr_t)lua_touserdata(L, -1); + lua_pop(L, 1); // doc_pointer + return doc; } -#if GTK -static int emit_timeout(void *data) { - int *refs = (int *)data; - lua_rawgeti(lua, LUA_REGISTRYINDEX, refs[0]); // function - int nargs = 0, repeat = TRUE; - while (refs[++nargs]) lua_rawgeti(lua, LUA_REGISTRYINDEX, refs[nargs]); - int ok = (lua_pcall(lua, nargs - 1, 1, 0) == LUA_OK); - if (!ok || !lua_toboolean(lua, -1)) { - while (--nargs >= 0) luaL_unref(lua, LUA_REGISTRYINDEX, refs[nargs]); - repeat = FALSE; - if (!ok) lL_event(lua, "error", LUA_TSTRING, lua_tostring(lua, -1), -1); +/** + * Removes the Scintilla document from the 'buffers' registry table. + * The document must have been previously added with lL_adddoc. + * It is removed from any other views showing it first. Therefore, ensure the + * length of 'buffers' is more than one unless quitting the application. + * @param L The Lua state. + * @param doc The Scintilla document to remove. + * @see lL_adddoc + */ +static void lL_removedoc(lua_State *L, sptr_t doc) { + lua_getfield(L, LUA_REGISTRYINDEX, "ta_views"); + lua_pushnil(L); + while (lua_next(L, -2)) { + if (lua_isnumber(L, -2)) { + Scintilla *view = l_toview(L, -1); + if (doc == SS(view, SCI_GETDOCPOINTER, 0, 0)) + lL_gotodoc(L, view, -1, TRUE); + } + lua_pop(L, 1); // value + } + lua_pop(L, 1); // views + lua_newtable(L); + lua_getfield(L, LUA_REGISTRYINDEX, "ta_buffers"); + lua_pushnil(L); + while (lua_next(L, -2)) { + if (lua_isnumber(L, -2) && doc != l_todoc(L, -1)) { + lua_getfield(L, -1, "doc_pointer"); + // bs[userdata] = b, bs[#bs + 1] = b, bs[b] = #bs + lua_pushvalue(L, -2), lua_rawseti(L, -6, lua_rawlen(L, -6) + 1); + lua_pushvalue(L, -2), lua_settable(L, -6); + lua_pushinteger(L, lua_rawlen(L, -4)), lua_settable(L, -5); + } else lua_pop(L, 1); // value + } + lua_pop(L, 1); // buffers + lua_pushvalue(L, -1), lua_setfield(L, LUA_REGISTRYINDEX, "ta_buffers"); + lua_setglobal(L, "_BUFFERS"); +} + +/** + * Switches to a document in the given view. + * @param L The Lua state. + * @param view The Scintilla view. + * @param n Relative or absolute index of the document to switch to. An absolute + * n of -1 represents the last document. + * @param relative Flag indicating whether or not n is relative. + */ +static void lL_gotodoc(lua_State *L, Scintilla *view, int n, int relative) { + if (relative && n == 0) return; + lua_getfield(L, LUA_REGISTRYINDEX, "ta_buffers"); + if (relative) { + l_pushdoc(L, SS(view, SCI_GETDOCPOINTER, 0, 0)), lua_gettable(L, -2); + n = lua_tointeger(L, -1) + n; + lua_pop(L, 1); // index + if (n > lua_rawlen(L, -1)) + n = 1; + else if (n < 1) + n = lua_rawlen(L, -1); + lua_rawgeti(L, -1, n); + } else { + luaL_argcheck(L, (n > 0 && n <= lua_rawlen(L, -1)) || n == -1, 2, + "no Buffer exists at that index"); + lua_rawgeti(L, -1, (n > 0) ? n : lua_rawlen(L, -1)); } - lua_pop(lua, 1); // result - return repeat; + sptr_t doc = l_todoc(L, -1); + SS(view, SCI_SETDOCPOINTER, 0, doc); + l_setglobaldoc(L, doc); + lua_pop(L, 2); // buffer table and buffers } -#endif -static int ltimeout(lua_State *L) { -#if GTK - double timeout = luaL_checknumber(L, 1); - luaL_argcheck(L, timeout > 0, 1, "timeout must be > 0"); - luaL_argcheck(L, lua_isfunction(L, 2), 2, "function expected"); - int n = lua_gettop(L); - int *refs = (int *)calloc(n, sizeof(int)); - lua_pushvalue(L, 2); - refs[0] = luaL_ref(L, LUA_REGISTRYINDEX); - for (int i = 3; i <= n; i++) { - lua_pushvalue(L, i); - refs[i - 2] = luaL_ref(L, LUA_REGISTRYINDEX); +/** + * Closes the Lua state. + * Unsplits and destroys all Scintilla views and removes all Scintilla + * documents, before closing the state. + * @param L The Lua state. + */ +static void l_close(lua_State *L) { + closing = TRUE; + while (unsplit_view(focused_view)) ; // need space to fix compiler warning + lua_getfield(L, LUA_REGISTRYINDEX, "ta_buffers"); + lua_pushnil(L); + while (lua_next(L, -2)) { + if (lua_isnumber(L, -2)) delete_buffer(l_todoc(L, -1)); + lua_pop(L, 1); // value } - g_timeout_add(timeout * 1000, emit_timeout, (void *)refs); -#elif NCURSES - luaL_error(L, "not implemented in this environment"); -#endif - return 0; + lua_pop(L, 1); // buffers + scintilla_delete(focused_view); + lua_close(L); } -static int lfind_focus(lua_State *L) { -#if GTK - if (!gtk_widget_has_focus(findbox)) { - gtk_widget_show(findbox); - gtk_widget_grab_focus(find_entry); - gtk_widget_grab_default(fnext_button); - } else { - gtk_widget_grab_focus(focused_view); - gtk_widget_hide(findbox); +/******************************************************************************/ +/*************************** Lua Utility Functions ****************************/ +/******************************************************************************/ + +/** + * Clears a table at the given valid index by setting all of its keys to nil. + * @param L The Lua state. + * @param index The stack index of the table. + */ +static void lL_cleartable(lua_State *L, int index) { + lua_pushvalue(L, index); // copy to stack top so relative indices can be used + lua_pushnil(L); + while (lua_next(L, -2)) { + lua_pop(L, 1); // value + lua_pushnil(L), lua_rawset(L, -3); + lua_pushnil(L); // key for lua_next } -#elif NCURSES - // TODO: toggle findbox focus. -#endif - return 0; + lua_pop(L, 1); // table copy } -static int lfind_next(lua_State *L) { - return (f_clicked(fnext_button, NULL), 0); +/** + * Pushes the Scintilla view onto the stack. + * The view must have previously been added with lL_addview. + * @param L The Lua state. + * @param view The Scintilla view to push. + * @see lL_addview + */ +static void l_pushview(lua_State *L, Scintilla *view) { + lua_getfield(L, LUA_REGISTRYINDEX, "ta_views"); + lua_pushlightuserdata(L, view), lua_gettable(L, -2); + lua_remove(L, -2); // views } -static int lfind_prev(lua_State *L) { - return (f_clicked(fprev_button, NULL), 0); +/** + * Pushes the Scintilla document onto the stack. + * The document must have previously been added with lL_adddoc. + * @param L The Lua state. + * @param doc The document to push. + * @see lL_adddoc + */ +static void l_pushdoc(lua_State *L, sptr_t doc) { + lua_getfield(L, LUA_REGISTRYINDEX, "ta_buffers"); + lua_pushlightuserdata(L, (sptr_t *)doc), lua_gettable(L, -2); + lua_remove(L, -2); // buffers } -static int lfind_replace(lua_State *L) { - return (f_clicked(r_button, NULL), 0); -} +/******************************************************************************/ +/********************** Lua Notifications/Event Handlers **********************/ +/******************************************************************************/ -static int lfind_replace_all(lua_State *L) { - return (f_clicked(ra_button, NULL), 0); +/** + * Emits an event. + * @param L The Lua state. + * @param name The event name. + * @param ... Arguments to pass with the event. Each pair of arguments should be + * a Lua type followed by the data value itself. For LUA_TLIGHTUSERDATA and + * LUA_TTABLE types, push the data values to the stack and give the value + * returned by luaL_ref(); luaL_unref() will be called appropriately. The list + * must be terminated with a -1. + * @return TRUE or FALSE depending on the boolean value returned by the event + * handler, if any. + */ +static int lL_event(lua_State *L, const char *name, ...) { + int ret = FALSE; + lua_getglobal(L, "events"); + if (lua_istable(L, -1)) { + lua_getfield(L, -1, "emit"); + lua_remove(L, -2); // events table + if (lua_isfunction(L, -1)) { + lua_pushstring(L, name); + int n = 1; + va_list ap; + va_start(ap, name); + int type = va_arg(ap, int); + while (type != -1) { + void *arg = va_arg(ap, void*); + if (type == LUA_TNIL) + lua_pushnil(L); + else if (type == LUA_TBOOLEAN) + lua_pushboolean(L, (long)arg); + else if (type == LUA_TNUMBER) + lua_pushinteger(L, (long)arg); + else if (type == LUA_TSTRING) + lua_pushstring(L, (char *)arg); + else if (type == LUA_TLIGHTUSERDATA || type == LUA_TTABLE) { + long ref = (long)arg; + lua_rawgeti(L, LUA_REGISTRYINDEX, ref); + luaL_unref(L, LUA_REGISTRYINDEX, ref); + } else warn("events.emit: ignored invalid argument type"); + n++; + type = va_arg(ap, int); + } + va_end(ap); + if (lua_pcall(L, n, 1, 0) == LUA_OK) + ret = lua_toboolean(L, -1); + else + lL_event(L, "error", LUA_TSTRING, lua_tostring(L, -1), -1); + lua_pop(L, 1); // result + } else lua_pop(L, 1); // non-function + } else lua_pop(L, 1); // non-table + return ret; } -static int lce_focus(lua_State *L) { -#if GTK - if (!gtk_widget_has_focus(command_entry)) { - gtk_widget_show(command_entry); - gtk_widget_grab_focus(command_entry); - } else { - gtk_widget_hide(command_entry); - gtk_widget_grab_focus(focused_view); - } -#elif NCURSES - // TODO: ce toggle focus. -#endif - return 0; +/** + * Emits a Scintilla notification event. + * @param L The Lua state. + * @param n The Scintilla notification struct. + * @see lL_event + */ +static void lL_notify(lua_State *L, struct SCNotification *n) { + lua_newtable(L); + lua_pushinteger(L, n->nmhdr.code), lua_setfield(L, -2, "code"); + lua_pushinteger(L, n->position), lua_setfield(L, -2, "position"); + lua_pushinteger(L, n->ch), lua_setfield(L, -2, "ch"); + lua_pushinteger(L, n->modifiers), lua_setfield(L, -2, "modifiers"); + //lua_pushinteger(L, n->modificationType); + //lua_setfield(L, -2, "modification_type"); + lua_pushstring(L, n->text), lua_setfield(L, -2, "text"); + //lua_pushinteger(L, n->length), lua_setfield(L, -2, "length"); + //lua_pushinteger(L, n->linesAdded), lua_setfield(L, -2, "lines_added"); + //lua_pushinteger(L, n->message), lua_setfield(L, -2, "message"); + lua_pushinteger(L, n->wParam), lua_setfield(L, -2, "wParam"); + lua_pushinteger(L, n->lParam), lua_setfield(L, -2, "lParam"); + lua_pushinteger(L, n->line), lua_setfield(L, -2, "line"); + //lua_pushinteger(L, n->foldLevelNow), lua_setfield(L, -2, "fold_level_now"); + //lua_pushinteger(L, n->foldLevelPrev); + //lua_setfield(L, -2, "fold_level_prev"); + lua_pushinteger(L, n->margin), lua_setfield(L, -2, "margin"); + lua_pushinteger(L, n->x), lua_setfield(L, -2, "x"); + lua_pushinteger(L, n->y), lua_setfield(L, -2, "y"); + lL_event(L, "SCN", LUA_TTABLE, luaL_ref(L, LUA_REGISTRYINDEX), -1); } -static int lce_show_completions(lua_State *L) { +/** + * Shows the context menu for a Scintilla view based on a mouse event. + * @param L The Lua state. + * @param event An optional GTK mouse button event. + */ +static void lL_showcontextmenu(lua_State *L, void *event) { + lua_getglobal(L, "gui"); + if (lua_istable(L, -1)) { + lua_getfield(L, -1, "context_menu"); + if (lua_isuserdata(L, -1)) { #if GTK - luaL_checktype(L, 1, LUA_TTABLE); - GtkEntryCompletion *completion = gtk_entry_get_completion( - GTK_ENTRY(command_entry)); - GtkListStore *store = GTK_LIST_STORE( - gtk_entry_completion_get_model(completion)); - gtk_list_store_clear(store); - lua_pushnil(L); - while (lua_next(L, 1)) { - if (lua_type(L, -1) == LUA_TSTRING) { - GtkTreeIter iter; - gtk_list_store_append(store, &iter); - gtk_list_store_set(store, &iter, 0, lua_tostring(L, -1), -1); - } else warn("command_entry.show_completions: non-string value ignored"); - lua_pop(L, 1); // value - } - gtk_entry_completion_complete(completion); + GtkWidget *menu = (GtkWidget *)lua_touserdata(L, -1); + gtk_widget_show_all(menu); + gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, + event ? ((GdkEventButton *)event)->button : 0, + gdk_event_get_time((GdkEvent *)event)); +#elif NCURSES + // TODO: popup context menu. #endif - return 0; + } else if (!lua_isnil(L, -1)) + warn("gui.context_menu: menu expected"); + lua_pop(L, 1); // gui.context_menu + } else lua_pop(L, 1); // non-table } -- cgit v1.2.3