diff options
Diffstat (limited to 'src/textadept.c')
-rw-r--r-- | src/textadept.c | 1723 |
1 files changed, 840 insertions, 883 deletions
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,6 +909,571 @@ int WINAPI WinMain(HINSTANCE _, HINSTANCE __, LPSTR lpCmdLine, int ___) { lua_setmetatable(l, (n > 0) ? n : n - 1); \ } +static int lfind_next(lua_State *L) { + return (f_clicked(fnext_button, NULL), 0); +} + +static int lfind_prev(lua_State *L) { + return (f_clicked(fprev_button, NULL), 0); +} + +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; +} + +static int lfind_replace(lua_State *L) { + return (f_clicked(r_button, NULL), 0); +} + +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 + 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); + return 1; +} + +#if GTK +#define toggle(w, b) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w), b) +#elif NCURSES +#define toggle(w, b) w = b +#endif +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; +} + +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; +} + +/** + * Prints a warning. + * @param s The warning to print. + */ +static void warn(const char *s) { printf("Warning: %s\n", s); } + +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, 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); +#endif + 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); + return 1; +} + +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; +} + +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); + 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; +} + +/** + * 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 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_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); + } + 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; +} + +/** + * 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 + */ +static int l_rawgetiint(lua_State *L, int index, int n) { + lua_rawgeti(L, index, n); + int ret = lua_tointeger(L, -1); + lua_pop(L, 1); // integer + return ret; +} + +/** + * Pushes a menu created from the table at the given valid index onto the stack. + * Consult the LuaDoc for the table format. + * @param L The Lua state. + * @param index The stack index of the table to create the menu from. + * @param callback An optional GTK callback function associated with each menu + * item. + * @param submenu Flag indicating whether or not this menu is a submenu. + */ +static void l_pushmenu(lua_State *L, int index, void (*callback)(void), + int submenu) { +#if GTK + GtkWidget *menu = gtk_menu_new(), *menu_item = 0, *submenu_root = 0; + const char *label; + lua_pushvalue(L, index); // copy to stack top so relative indices can be used + lua_getfield(L, -1, "title"); + if (!lua_isnil(L, -1) || submenu) { // title required for submenu + label = !lua_isnil(L, -1) ? lua_tostring(L, -1) : "notitle"; + submenu_root = gtk_menu_item_new_with_mnemonic(label); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(submenu_root), menu); + } + lua_pop(L, 1); // title + lua_pushnil(L); + while (lua_next(L, -2)) { + if (lua_istable(L, -1)) { + lua_getfield(L, -1, "title"); + int is_submenu = !lua_isnil(L, -1); + lua_pop(L, 1); // title + if (is_submenu) { + l_pushmenu(L, -1, callback, TRUE); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), + (GtkWidget *)lua_touserdata(L, -1)); + lua_pop(L, 1); // menu + } else if (lua_rawlen(L, -1) == 2 || lua_rawlen(L, -1) == 4) { + lua_rawgeti(L, -1, 1); + label = lua_tostring(L, -1); + lua_pop(L, 1); // label + int menu_id = l_rawgetiint(L, -1, 2); + int key = l_rawgetiint(L, -1, 3); + int modifiers = l_rawgetiint(L, -1, 4); + if (label) { + if (g_str_has_prefix(label, "gtk-")) + menu_item = gtk_image_menu_item_new_from_stock(label, NULL); + else if (strcmp(label, "separator") == 0) + menu_item = gtk_separator_menu_item_new(); + else + menu_item = gtk_menu_item_new_with_mnemonic(label); + if (key || modifiers) + gtk_widget_add_accelerator(menu_item, "activate", accel, key, + modifiers, GTK_ACCEL_VISIBLE); + g_signal_connect(menu_item, "activate", G_CALLBACK(callback), + GINT_TO_POINTER(menu_id)); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item); + } + } else warn("menu: { 'label', id_num [, keycode, mods] } expected"); + } + lua_pop(L, 1); // value + } + lua_pop(L, 1); // table copy + lua_pushlightuserdata(L, !submenu_root ? menu : submenu_root); +#elif NCURSES + lua_pushnil(L); // TODO: create and push menu (memory management?). +#endif +} + +#if GTK +static void m_clicked(GtkWidget *menu, void *id) { + lL_event(lua, "menu_clicked", LUA_TNUMBER, GPOINTER_TO_INT(id), -1); +} +#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 + return 1; +} + +static int lgui__index(lua_State *L) { + const char *key = lua_tostring(L, 2); + if (strcmp(key, "statusbar_text") == 0) + lua_pushstring(L, statusbar_text); + else if (strcmp(key, "clipboard_text") == 0) { +#if GTK + char *text = gtk_clipboard_wait_for_text( + gtk_clipboard_get(GDK_SELECTION_CLIPBOARD)); + lua_pushstring(L, text ? text : ""); + if (text) free(text); +#elif NCURSES + lua_pushstring(L, ""); // TODO: get Xclipboard text? +#endif + } else if (strcmp(key, "size") == 0) { +#if GTK + int width, height; + gtk_window_get_size(GTK_WINDOW(window), &width, &height); +#elif NCURSES + int width = COLS, height = LINES; +#endif + lua_newtable(L); + lua_pushinteger(L, width), lua_rawseti(L, -2, 1); + lua_pushinteger(L, height), lua_rawseti(L, -2, 2); + } else lua_rawget(L, 1); + return 1; +} + +static void set_statusbar_text(const char *text, int bar) { +#if GTK + if (!statusbar[0] || !statusbar[1]) return; // unavailable on startup + gtk_statusbar_pop(GTK_STATUSBAR(statusbar[bar]), 0); + gtk_statusbar_push(GTK_STATUSBAR(statusbar[bar]), 0, text); +#elif NCURSES + for (int i = ((bar == 0) ? 0 : 20); i < ((bar == 0) ? 20 : COLS); i++) + mvaddch(LINES - 1, i, ' '); // clear statusbar + mvaddstr(LINES - 1, (bar == 0) ? 0 : COLS - strlen(text), text), refresh(); +#endif +} + +static int lgui__newindex(lua_State *L) { + const char *key = lua_tostring(L, 2); + if (strcmp(key, "title") == 0) { +#if GTK + gtk_window_set_title(GTK_WINDOW(window), lua_tostring(L, 3)); +#elif NCURSES + for (int i = 0; i < COLS; i++) mvaddch(0, i, ' '); // clear titlebar + mvaddstr(0, 0, lua_tostring(L, 3)), refresh(); +#endif + } else if (strcmp(key, "clipboard_text") == 0) + luaL_argerror(L, 3, "read-only property"); + else if (strcmp(key, "docstatusbar_text") == 0) + set_statusbar_text(lua_tostring(L, 3), 1); + else if (strcmp(key, "statusbar_text") == 0) { + if (statusbar_text) free(statusbar_text); + statusbar_text = copy(luaL_optstring(L, 3, "")); + set_statusbar_text(statusbar_text, 0); + } else if (strcmp(key, "menubar") == 0) { +#if GTK + luaL_argcheck(L, lua_istable(L, 3), 3, "table of menus expected"); + GtkWidget *new_menubar = gtk_menu_bar_new(); + lua_pushnil(L); + while (lua_next(L, 3)) { + luaL_argcheck(L, lua_isuserdata(L, -1), 3, "table of menus expected"); + GtkWidget *menu_item = (GtkWidget *)lua_touserdata(L, -1); + gtk_menu_shell_append(GTK_MENU_SHELL(new_menubar), menu_item); + lua_pop(L, 1); // value + } + GtkWidget *vbox = gtk_widget_get_parent(menubar); + gtk_container_remove(GTK_CONTAINER(vbox), menubar); + menubar = new_menubar; + gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0); + gtk_box_reorder_child(GTK_BOX(vbox), menubar, 0); + gtk_widget_show_all(menubar); +#if __OSX__ + gtk_osxapplication_set_menu_bar(osxapp, GTK_MENU_SHELL(menubar)); + gtk_widget_hide(menubar); +#endif +#endif + } else if (strcmp(key, "size") == 0) { +#if GTK + luaL_argcheck(L, lua_istable(L, 3) && lua_rawlen(L, 3) == 2, 3, + "{ width, height } table expected"); + int w = l_rawgetiint(L, 3, 1), h = l_rawgetiint(L, 3, 2); + if (w > 0 && h > 0) gtk_window_resize(GTK_WINDOW(window), w, h); +#endif + } else lua_rawset(L, 1); + return 0; +} + +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 + 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; +} + +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 +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 + +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); + } + g_timeout_add(timeout * 1000, emit_timeout, (void *)refs); +#elif NCURSES + luaL_error(L, "not implemented in this environment"); +#endif + return 0; +} + +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; +} + /** * Initializes or re-initializes the Lua state. * Populates the state with global variables and functions, then runs the @@ -1057,18 +1606,83 @@ static int lL_dofile(lua_State *L, const char *filename) { } /** - * Returns the view at the given acceptable index as a Scintilla view. + * Checks whether the function argument narg is a Scintilla view and returns + * this view cast to a Scintilla. * @param L The Lua state. - * @param index Stack index of the view. + * @param narg The stack index of the Scintilla view. * @return Scintilla view */ -static Scintilla *l_toview(lua_State *L, int index) { - lua_getfield(L, index, "widget_pointer"); +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, 1); // widget pointer + lua_pop(L, 3); // widget_pointer, metatable, metatable return view; } +// 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) { + split_view(lL_checkview(L, 1), lua_toboolean(L, 2)); + lua_pushvalue(L, 1); // old view + lua_getglobal(L, "view"); // new view + return 2; +} + +static int lview_unsplit(lua_State *L) { + lua_pushboolean(L, unsplit_view(lL_checkview(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; +} + /** * Adds the Scintilla view with a metatable to the 'views' registry table. * @param L The Lua state. @@ -1116,16 +1730,202 @@ static void lL_removeview(lua_State *L, Scintilla *view) { } /** - * Returns the buffer at the given acceptable index as a Scintilla document. + * Checks whether the function argument narg is a Scintilla document. If not, + * raises an error. * @param L The Lua state. - * @param index Stack index of the buffer. + * @param narg The stack index of the Scintilla document. * @return Scintilla document */ -static sptr_t l_todoc(lua_State *L, int index) { - lua_getfield(L, index, "doc_pointer"); +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); - lua_pop(L, 1); // doc_pointer - return doc; + 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; +} + +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; +} + +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; +} + +/** + * 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; + } + + // 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; } /** @@ -1150,6 +1950,19 @@ static void lL_adddoc(lua_State *L, sptr_t doc) { } /** + * 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; +} + +/** * 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 @@ -1284,168 +2097,6 @@ static void l_pushdoc(lua_State *L, sptr_t doc) { 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 - */ -static int l_rawgetiint(lua_State *L, int index, int n) { - lua_rawgeti(L, index, n); - int ret = lua_tointeger(L, -1); - lua_pop(L, 1); // integer - 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. - * @param L The Lua state. - * @param index The stack index of the table to create the menu from. - * @param callback An optional GTK callback function associated with each menu - * item. - * @param submenu Flag indicating whether or not this menu is a submenu. - */ -static void l_pushmenu(lua_State *L, int index, void (*callback)(void), - int submenu) { -#if GTK - GtkWidget *menu = gtk_menu_new(), *menu_item = 0, *submenu_root = 0; - const char *label; - lua_pushvalue(L, index); // copy to stack top so relative indices can be used - lua_getfield(L, -1, "title"); - if (!lua_isnil(L, -1) || submenu) { // title required for submenu - label = !lua_isnil(L, -1) ? lua_tostring(L, -1) : "notitle"; - submenu_root = gtk_menu_item_new_with_mnemonic(label); - gtk_menu_item_set_submenu(GTK_MENU_ITEM(submenu_root), menu); - } - lua_pop(L, 1); // title - lua_pushnil(L); - while (lua_next(L, -2)) { - if (lua_istable(L, -1)) { - lua_getfield(L, -1, "title"); - int is_submenu = !lua_isnil(L, -1); - lua_pop(L, 1); // title - if (is_submenu) { - l_pushmenu(L, -1, callback, TRUE); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), - (GtkWidget *)lua_touserdata(L, -1)); - lua_pop(L, 1); // menu - } else if (lua_rawlen(L, -1) == 2 || lua_rawlen(L, -1) == 4) { - lua_rawgeti(L, -1, 1); - label = lua_tostring(L, -1); - lua_pop(L, 1); // label - int menu_id = l_rawgetiint(L, -1, 2); - int key = l_rawgetiint(L, -1, 3); - int modifiers = l_rawgetiint(L, -1, 4); - if (label) { - if (g_str_has_prefix(label, "gtk-")) - menu_item = gtk_image_menu_item_new_from_stock(label, NULL); - else if (strcmp(label, "separator") == 0) - menu_item = gtk_separator_menu_item_new(); - else - menu_item = gtk_menu_item_new_with_mnemonic(label); - if (key || modifiers) - gtk_widget_add_accelerator(menu_item, "activate", accel, key, - modifiers, GTK_ACCEL_VISIBLE); - g_signal_connect(menu_item, "activate", G_CALLBACK(callback), - GINT_TO_POINTER(menu_id)); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item); - } - } else warn("menu: { 'label', id_num [, keycode, mods] } expected"); - } - lua_pop(L, 1); // value - } - lua_pop(L, 1); // table copy - lua_pushlightuserdata(L, !submenu_root ? menu : submenu_root); -#elif NCURSES - lua_pushnil(L); // TODO: create and push menu (memory management?). -#endif -} - /******************************************************************************/ /********************** Lua Notifications/Event Handlers **********************/ /******************************************************************************/ @@ -1557,697 +2208,3 @@ static void lL_showcontextmenu(lua_State *L, void *event) { 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; -} - -static int lgui__index(lua_State *L) { - const char *key = lua_tostring(L, 2); - if (strcmp(key, "statusbar_text") == 0) - lua_pushstring(L, statusbar_text); - else if (strcmp(key, "clipboard_text") == 0) { -#if GTK - char *text = gtk_clipboard_wait_for_text( - gtk_clipboard_get(GDK_SELECTION_CLIPBOARD)); - lua_pushstring(L, text ? text : ""); - if (text) free(text); -#elif NCURSES - lua_pushstring(L, ""); // TODO: get Xclipboard text? -#endif - } else if (strcmp(key, "size") == 0) { -#if GTK - int width, height; - gtk_window_get_size(GTK_WINDOW(window), &width, &height); -#elif NCURSES - int width = COLS, height = LINES; -#endif - lua_newtable(L); - lua_pushinteger(L, width), lua_rawseti(L, -2, 1); - lua_pushinteger(L, height), lua_rawseti(L, -2, 2); - } else lua_rawget(L, 1); - return 1; -} - -static void set_statusbar_text(const char *text, int bar) { -#if GTK - if (!statusbar[0] || !statusbar[1]) return; // unavailable on startup - gtk_statusbar_pop(GTK_STATUSBAR(statusbar[bar]), 0); - gtk_statusbar_push(GTK_STATUSBAR(statusbar[bar]), 0, text); -#elif NCURSES - for (int i = ((bar == 0) ? 0 : 20); i < ((bar == 0) ? 20 : COLS); i++) - mvaddch(LINES - 1, i, ' '); // clear statusbar - mvaddstr(LINES - 1, (bar == 0) ? 0 : COLS - strlen(text), text), refresh(); -#endif -} - -static int lgui__newindex(lua_State *L) { - const char *key = lua_tostring(L, 2); - if (strcmp(key, "title") == 0) { -#if GTK - gtk_window_set_title(GTK_WINDOW(window), lua_tostring(L, 3)); -#elif NCURSES - for (int i = 0; i < COLS; i++) mvaddch(0, i, ' '); // clear titlebar - mvaddstr(0, 0, lua_tostring(L, 3)), refresh(); -#endif - } else if (strcmp(key, "clipboard_text") == 0) - luaL_argerror(L, 3, "read-only property"); - else if (strcmp(key, "docstatusbar_text") == 0) - set_statusbar_text(lua_tostring(L, 3), 1); - else if (strcmp(key, "statusbar_text") == 0) { - if (statusbar_text) free(statusbar_text); - statusbar_text = copy(luaL_optstring(L, 3, "")); - set_statusbar_text(statusbar_text, 0); - } else if (strcmp(key, "menubar") == 0) { -#if GTK - luaL_argcheck(L, lua_istable(L, 3), 3, "table of menus expected"); - GtkWidget *new_menubar = gtk_menu_bar_new(); - lua_pushnil(L); - while (lua_next(L, 3)) { - luaL_argcheck(L, lua_isuserdata(L, -1), 3, "table of menus expected"); - GtkWidget *menu_item = (GtkWidget *)lua_touserdata(L, -1); - gtk_menu_shell_append(GTK_MENU_SHELL(new_menubar), menu_item); - lua_pop(L, 1); // value - } - GtkWidget *vbox = gtk_widget_get_parent(menubar); - gtk_container_remove(GTK_CONTAINER(vbox), menubar); - menubar = new_menubar; - gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0); - gtk_box_reorder_child(GTK_BOX(vbox), menubar, 0); - gtk_widget_show_all(menubar); -#if __OSX__ - gtk_osxapplication_set_menu_bar(osxapp, GTK_MENU_SHELL(menubar)); - gtk_widget_hide(menubar); -#endif -#endif - } else if (strcmp(key, "size") == 0) { -#if GTK - luaL_argcheck(L, lua_istable(L, 3) && lua_rawlen(L, 3) == 2, 3, - "{ width, height } table expected"); - int w = l_rawgetiint(L, 3, 1), h = l_rawgetiint(L, 3, 2); - if (w > 0 && h > 0) gtk_window_resize(GTK_WINDOW(window), w, h); -#endif - } else lua_rawset(L, 1); - 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); - return 1; -} - -#if GTK -#define toggle(w, b) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w), b) -#elif NCURSES -#define toggle(w, b) w = b -#endif -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; -} - -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; -} - -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; -} - -/******************************************************************************/ -/****************** Lua CFunctions *******************/ -/****************** (For documentation, consult the LuaDoc) *******************/ -/******************************************************************************/ - -static int lbuffer_check_global(lua_State *L) { - lL_globaldoccheck(L, 1); - return 0; -} - -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; -} - -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 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 lview_split(lua_State *L) { - split_view(lL_checkview(L, 1), lua_toboolean(L, 2)); - lua_pushvalue(L, 1); // old view - lua_getglobal(L, "view"); // new view - return 2; -} - -static int lview_unsplit(lua_State *L) { - lua_pushboolean(L, unsplit_view(lL_checkview(L, 1))); - 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); - 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; -} - -// 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 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; -} - -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_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); - } - 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; -} - -#if GTK -static void m_clicked(GtkWidget *menu, void *id) { - lL_event(lua, "menu_clicked", LUA_TNUMBER, GPOINTER_TO_INT(id), -1); -} -#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 - 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); - } - if (!converted) luaL_error(L, "Conversion failed"); - 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; -} - -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 -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 - -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); - } - g_timeout_add(timeout * 1000, emit_timeout, (void *)refs); -#elif NCURSES - luaL_error(L, "not implemented in this environment"); -#endif - return 0; -} - -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; -} - -static int lfind_next(lua_State *L) { - return (f_clicked(fnext_button, NULL), 0); -} - -static int lfind_prev(lua_State *L) { - return (f_clicked(fprev_button, NULL), 0); -} - -static int lfind_replace(lua_State *L) { - return (f_clicked(r_button, NULL), 0); -} - -static int lfind_replace_all(lua_State *L) { - return (f_clicked(ra_button, NULL), 0); -} - -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; -} - -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, 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); -#endif - return 0; -} |