diff options
author | 2012-05-25 08:25:56 -0400 | |
---|---|---|
committer | 2012-05-25 08:25:56 -0400 | |
commit | 127d19c16219f321958f0a0bcdce278a935e9b47 (patch) | |
tree | 4d8d3f9128b98abc30998db7a00032adb4da2193 /src/textadept.c | |
parent | 027cc4a608900776dce43ad693ae557512a77aac (diff) | |
download | textadept-127d19c16219f321958f0a0bcdce278a935e9b47.tar.gz textadept-127d19c16219f321958f0a0bcdce278a935e9b47.zip |
Finished removing forward declarations; src/textadept.c
Diffstat (limited to 'src/textadept.c')
-rw-r--r-- | src/textadept.c | 2326 |
1 files changed, 1145 insertions, 1181 deletions
diff --git a/src/textadept.c b/src/textadept.c index 70f21337..df6069ad 100644 --- a/src/textadept.c +++ b/src/textadept.c @@ -59,10 +59,6 @@ typedef GtkWidget Scintilla; #endif #define copy(s) strcpy(malloc(strlen(s) + 1), s) -/******************************************************************************/ -/***************************** Forward Declarations ***************************/ -/******************************************************************************/ - // Window char *textadept_home = NULL; Scintilla *focused_view; @@ -73,6 +69,8 @@ GtkAccelGroup *accel; GtkOSXApplication *osxapp; #endif #endif +static void new_buffer(sptr_t); +static Scintilla *new_view(sptr_t); // Find/Replace #if GTK @@ -118,260 +116,92 @@ char *statusbar_text = NULL; static int tVOID = 0, tINT = 1, tLENGTH = 2, /*tPOSITION = 3, tCOLOUR = 4,*/ tBOOL = 5, tKEYMOD = 6, tSTRING = 7, tSTRINGRESULT = 8; static int lL_init(lua_State *, int, char **, int); -static void l_close(lua_State *); -static int lL_dofile(lua_State *, const char *); -static void lL_addview(lua_State *, Scintilla *); -static void lL_removeview(lua_State *, Scintilla *); -static void lL_adddoc(lua_State *, sptr_t); -static void lL_removedoc(lua_State *, sptr_t); -static void lL_gotodoc(lua_State *, Scintilla *, int, int); -static int lL_event(lua_State *, const char *, ...); -static void lL_notify(lua_State *, struct SCNotification *); -static void lL_showcontextmenu(lua_State *, void *); -static void lL_cleartable(lua_State *, int); -static void l_pushview(lua_State *, Scintilla *); -static void l_pushdoc(lua_State *, sptr_t); + #define l_setglobalview(l, v) (l_pushview(l, v), lua_setglobal(l, "view")) #define l_setglobaldoc(l, d) (l_pushdoc(l, d), lua_setglobal(l, "buffer")) +#define l_setcfunction(l, n, k, f) \ + (lua_pushcfunction(l, f), lua_setfield(l, (n > 0) ? n : n - 1, k)) +#define l_setmetatable(l, n, k, i, ni) { \ + if (luaL_newmetatable(l, k)) { \ + l_setcfunction(l, -1, "__index", i); \ + l_setcfunction(l, -1, "__newindex", ni); \ + } \ + lua_setmetatable(l, (n > 0) ? n : n - 1); \ +} LUALIB_API int (luaopen_lpeg) (lua_State *); LUALIB_API int (luaopen_lfs) (lua_State *); -// Scintilla signals. - -/** - * Change focus to the given Scintilla view. - * Generates 'view_before_switch' and 'view_after_switch' events. - * @param view The Scintilla view to focus. - */ -static void goto_view(Scintilla *view) { - if (!closing) lL_event(lua, "view_before_switch", -1); - focused_view = view; - l_setglobalview(lua, view); - l_setglobaldoc(lua, SS(view, SCI_GETDOCPOINTER, 0, 0)); - if (!closing) lL_event(lua, "view_after_switch", -1); -} - -/** - * Signal for a Scintilla notification. - */ -static void s_notify(Scintilla *view, int _, void *lParam, void*__) { - struct SCNotification *n = (struct SCNotification *)lParam; - if (focused_view == view || n->nmhdr.code == SCN_URIDROPPED) { - if (focused_view != view) goto_view(view); - lL_notify(lua, n); - } else if (n->nmhdr.code == SCN_SAVEPOINTLEFT) { - Scintilla *prev = focused_view; - goto_view(view); - lL_notify(lua, n); - goto_view(prev); // do not let a split view steal focus - } -} - -#if GTK -/** - * Signal for a Scintilla command. - * Currently handles SCEN_SETFOCUS. - */ -static void s_command(GtkWidget *view, int wParam, void*_, void*__) { - if (wParam >> 16 == SCEN_SETFOCUS) goto_view(view); -} - -/** - * Signal for a Scintilla keypress. - */ -static int s_keypress(GtkWidget *view, GdkEventKey *event, void*_) { - return lL_event(lua, "keypress", LUA_TNUMBER, event->keyval, LUA_TBOOLEAN, - event->state & GDK_SHIFT_MASK, LUA_TBOOLEAN, - event->state & GDK_CONTROL_MASK, LUA_TBOOLEAN, - event->state & GDK_MOD1_MASK, LUA_TBOOLEAN, - event->state & GDK_META_MASK, -1); -} - -/** - * Signal for a Scintilla mouse click. - */ -static int s_buttonpress(GtkWidget*_, GdkEventButton *event, void*__) { - if (event->type == GDK_BUTTON_PRESS && event->button == 3) - return (lL_showcontextmenu(lua, (void *)event), TRUE); - return FALSE; -} -#endif - -/** - * Creates a new Scintilla document and adds it to the Lua state. - * Generates 'buffer_before_switch' and 'buffer_new' events. - * @param doc Almost always zero, except for the first Scintilla view created, - * in which its doc pointer would be given here. - * @see lL_adddoc - */ -static void new_buffer(sptr_t doc) { - if (!doc) { // create the new document - doc = SS(focused_view, SCI_CREATEDOCUMENT, 0, 0); - lL_event(lua, "buffer_before_switch", -1); - lL_adddoc(lua, doc); - lL_gotodoc(lua, focused_view, -1, FALSE); - } else { - // The first Scintilla window already has a pre-created buffer. - lL_adddoc(lua, doc); - SS(focused_view, SCI_ADDREFDOCUMENT, 0, doc); - } - l_setglobaldoc(lua, doc); - lL_event(lua, "buffer_new", -1); -} - -/** - * Removes the Scintilla buffer from the current Scintilla view. - * @param doc The Scintilla document. - * @see lL_removedoc - */ -static void delete_buffer(sptr_t doc) { - lL_removedoc(lua, doc); - SS(focused_view, SCI_RELEASEDOCUMENT, 0, doc); -} - -/** - * Creates a new Scintilla view. - * Generates a 'view_new' event. - * @param doc The document to load in the new view. Almost never zero, except - * for the first Scintilla view created, in which there is no doc pointer. - * @return Scintilla view - * @see lL_addview - */ -static Scintilla *new_view(sptr_t doc) { -#if GTK - Scintilla *view = scintilla_new(); - gtk_widget_set_size_request(view, 1, 1); // minimum size - signal(view, SCINTILLA_NOTIFY, s_notify); - signal(view, "command", s_command); - signal(view, "key-press-event", s_keypress); - signal(view, "button-press-event", s_buttonpress); -#elif NCURSES - Scintilla *view = scintilla_new(s_notify); -#endif - SS(view, SCI_USEPOPUP, 0, 0); - lL_addview(lua, view); - focused_view = view; - focus_view(view); - if (doc) { - SS(view, SCI_SETDOCPOINTER, 0, doc); - l_setglobaldoc(lua, doc); - } else new_buffer(SS(view, SCI_GETDOCPOINTER, 0, 0)); - l_setglobalview(lua, view); - lL_event(lua, "view_new", -1); - return view; -} - -/** - * Removes a Scintilla view. - * @param view The Scintilla view to remove. - * @see lL_removeview - */ -static void delete_view(Scintilla *view) { - lL_removeview(lua, view); - scintilla_delete(view); -} - -/** - * Splits the given Scintilla view into two views. - * The new view shows the same document as the original one. - * @param view The Scintilla view to split. - * @param vertical Flag indicating whether to split the view vertically or - * horozontally. - */ -static void split_view(Scintilla *view, int vertical) { - sptr_t curdoc = SS(view, SCI_GETDOCPOINTER, 0, 0); - int first_line = SS(view, SCI_GETFIRSTVISIBLELINE, 0, 0); - int current_pos = SS(view, SCI_GETCURRENTPOS, 0, 0); - int anchor = SS(view, SCI_GETANCHOR, 0, 0); - -#if GTK - GtkAllocation allocation; - gtk_widget_get_allocation(view, &allocation); - int middle = (vertical ? allocation.width : allocation.height) / 2; - - g_object_ref(view); - GtkWidget *view2 = new_view(curdoc); - GtkWidget *parent = gtk_widget_get_parent(view); - gtk_container_remove(GTK_CONTAINER(parent), view); - GtkWidget *pane = vertical ? gtk_hpaned_new() : gtk_vpaned_new(); - gtk_paned_add1(GTK_PANED(pane), view), gtk_paned_add2(GTK_PANED(pane), view2); - gtk_container_add(GTK_CONTAINER(parent), pane); - gtk_paned_set_position(GTK_PANED(pane), middle); - gtk_widget_show_all(pane); - g_object_unref(view); -#elif NCURSES - WINDOW *win = scintilla_get_window(view); - int x, y; - getbegyx(win, y, x); - int width = getmaxx(win) - x, height = getmaxy(win) - y; - wresize(win, vertical ? height : height / 2, vertical ? width / 2 : width); - Scintilla *view2 = new_view(curdoc); - wresize(scintilla_get_window(view2), vertical ? height : height / 2, - vertical ? width / 2 : width); - mvwin(scintilla_get_window(view2), vertical ? y : y + height / 2, - vertical ? x + width / 2 : x); - // TODO: draw split +#if LUAJIT +#define LUA_OK 0 +#define lua_rawlen lua_objlen +#define LUA_OPEQ 0 +#define lua_compare(l, a, b, _) lua_equal(l, a, b) +#define lL_openlib(l, n, f) \ + (lua_pushcfunction(l, f), lua_pushstring(l, n), lua_call(l, 1, 0)) +#else +#define lL_openlib(l, n, f) (luaL_requiref(l, n, f, 1), lua_pop(l, 1)) #endif - focus_view(view2); - - SS(view2, SCI_SETSEL, anchor, current_pos); - int new_first_line = SS(view2, SCI_GETFIRSTVISIBLELINE, 0, 0); - SS(view2, SCI_LINESCROLL, first_line - new_first_line, 0); -} -#if GTK /** - * Remove all Scintilla views from the given pane and delete them. - * @param pane The GTK pane to remove Scintilla views from. - * @see delete_view + * Prints a warning. + * @param s The warning to print. */ -static void remove_views_from_pane(GtkWidget *pane) { - GtkWidget *child1 = gtk_paned_get_child1(GTK_PANED(pane)); - GtkWidget *child2 = gtk_paned_get_child2(GTK_PANED(pane)); - GTK_IS_PANED(child1) ? remove_views_from_pane(child1) : delete_view(child1); - GTK_IS_PANED(child2) ? remove_views_from_pane(child2) : delete_view(child2); -} -#endif +static void warn(const char *s) { printf("Warning: %s\n", s); } /** - * Unsplits the pane a given Scintilla view is in and keeps the view. - * All views in the other pane are deleted. - * @param view The Scintilla view to keep when unsplitting. - * @see remove_views_from_pane - * @see delete_view + * 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 unsplit_view(Scintilla *view) { -#if GTK - GtkWidget *pane = gtk_widget_get_parent(view); - if (!GTK_IS_PANED(pane)) return FALSE; - GtkWidget *other = gtk_paned_get_child1(GTK_PANED(pane)); - if (other == view) other = gtk_paned_get_child2(GTK_PANED(pane)); - g_object_ref(view), g_object_ref(other); - gtk_container_remove(GTK_CONTAINER(pane), view); - gtk_container_remove(GTK_CONTAINER(pane), other); - GTK_IS_PANED(other) ? remove_views_from_pane(other) : delete_view(other); - GtkWidget *parent = gtk_widget_get_parent(pane); - gtk_container_remove(GTK_CONTAINER(parent), pane); - if (GTK_IS_PANED(parent)) { - if (!gtk_paned_get_child1(GTK_PANED(parent))) - gtk_paned_add1(GTK_PANED(parent), view); - else - gtk_paned_add2(GTK_PANED(parent), view); - } else gtk_container_add(GTK_CONTAINER(parent), view); - gtk_widget_show_all(parent); - gtk_widget_grab_focus(GTK_WIDGET(view)); - g_object_unref(view), g_object_unref(other); - return TRUE; -#elif NCURSES - return FALSE; -#endif +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; } -// Textadept signals. - #if GTK -// Application signal. - #if GLIB_CHECK_VERSION(2,28,0) && SINGLE_INSTANCE /** * Processes a remote Textadept's command line arguments. @@ -399,79 +229,7 @@ static int a_command_line(GApplication *app, GApplicationCommandLine *cmdline, return 0; } #endif - -// Window signals. - -/** - * Signal for a Textadept window focus change. - */ -static int w_focus(GtkWidget*_, GdkEventFocus *event, void*__) { - if (focused_view && !gtk_widget_has_focus(focused_view)) - gtk_widget_grab_focus(focused_view); - return FALSE; -} - -/** - * Signal for a Textadept keypress. - * Currently handled keypresses: - * - Escape: hides the find box if it is open. - */ -static int w_keypress(GtkWidget*_, GdkEventKey *event, void*__) { - if (event->keyval == 0xff1b && gtk_widget_get_visible(findbox) && - !gtk_widget_has_focus(command_entry)) { - gtk_widget_hide(findbox); - gtk_widget_grab_focus(focused_view); - return TRUE; - } else return FALSE; -} - -/** - * Signal for exiting Textadept. - * Generates a 'quit' event. - * Closes the Lua state and releases resources. - * @see l_close - */ -static int w_exit(GtkWidget*_, GdkEventAny*__, void*___) { - if (!lL_event(lua, "quit", -1)) return TRUE; - l_close(lua); - scintilla_release_resources(); - gtk_main_quit(); - return FALSE; -} - -#if __OSX__ -/** - * Signal for opening files from OSX. - * Generates an 'appleevent_odoc' event for each document sent. - */ -static int w_open_osx(GtkOSXApplication*_, char *path, void*__) { - lL_event(lua, "appleevent_odoc", LUA_TSTRING, path, -1); - return TRUE; -} - -/** - * Signal for block terminating Textadept from OSX. - * Generates a 'quit' event. - */ -static int w_exit_osx(GtkOSXApplication*_, void*__) { - return !lL_event(lua, "quit", -1); -} - -/** - * Signal for terminating Textadept from OSX. - * Closes the Lua state and releases resources. - * @see l_close - */ -static void w_quit_osx(GtkOSXApplication*_, void*__) { - l_close(lua); - scintilla_release_resources(); - g_object_unref(osxapp); - gtk_main_quit(); -} #endif -#endif // if GTK - -// Find/replace signals. /** * Adds the given text to the find/replace history list if it is not at the top. @@ -525,390 +283,6 @@ static void f_clicked(FindButton button, void*_) { } } -#if GTK -// Command entry completion signals. - -/** - * The match function for the command entry. - * Since the completion list is filled by Lua, every item is a "match". - */ -static int cc_matchfunc(GtkEntryCompletion*_, const char *__, GtkTreeIter*___, - void*____) { return 1; } - -/** - * Replaces the current word (consisting of alphanumeric and underscore - * characters) with the match text. - */ -static int cc_matchselected(GtkEntryCompletion*_, GtkTreeModel *model, - GtkTreeIter *iter, void*__) { - const char *text = gtk_entry_get_text(GTK_ENTRY(command_entry)), *p; - for (p = text + strlen(text) - 1; g_ascii_isalnum(*p) || *p == '_'; p--) - g_signal_emit_by_name(G_OBJECT(command_entry), "move-cursor", - GTK_MOVEMENT_VISUAL_POSITIONS, -1, TRUE, 0); - if (p < text + strlen(text) - 1) - g_signal_emit_by_name(G_OBJECT(command_entry), "backspace", 0); - - char *match; - gtk_tree_model_get(model, iter, 0, &match, -1); - g_signal_emit_by_name(G_OBJECT(command_entry), "insert-at-cursor", match, 0); - g_free(match); - - gtk_list_store_clear(cc_store); - return TRUE; -} - -// Command entry signals. - -/** - * Signal for the 'enter' key being pressed in the Command Entry. - */ -static void c_activate(GtkWidget *entry, void*_) { - lL_event(lua, "command_entry_command", LUA_TSTRING, - gtk_entry_get_text(GTK_ENTRY(entry)), -1); -} - -/** - * Signal for a keypress inside the Command Entry. - */ -static int c_keypress(GtkWidget*_, GdkEventKey *event, void*__) { - return lL_event(lua, "command_entry_keypress", LUA_TNUMBER, event->keyval, - LUA_TBOOLEAN, event->state & GDK_SHIFT_MASK, LUA_TBOOLEAN, - event->state & GDK_CONTROL_MASK, LUA_TBOOLEAN, - event->state & GDK_MOD1_MASK, LUA_TBOOLEAN, - event->state & GDK_META_MASK, -1); -} -#endif // if GTK - -/** - * Creates the Find box. - */ -static FindBox *new_findbox() { -#if GTK -#define attach(w, x1, x2, y1, y2, xo, yo, xp, yp) \ - gtk_table_attach(GTK_TABLE(findbox), w, x1, x2, y1, y2, xo, yo, xp, yp) -#define EXPAND_FILL (GtkAttachOptions)(GTK_EXPAND | GTK_FILL) -#define SHRINK_FILL (GtkAttachOptions)(GTK_SHRINK | GTK_FILL) - - findbox = gtk_table_new(2, 6, FALSE); - find_store = gtk_list_store_new(1, G_TYPE_STRING); - repl_store = gtk_list_store_new(1, G_TYPE_STRING); - - flabel = gtk_label_new_with_mnemonic("_Find:"); - rlabel = gtk_label_new_with_mnemonic("R_eplace:"); - GtkWidget *find_combo = gtk_combo_box_entry_new_with_model( - GTK_TREE_MODEL(find_store), 0); - gtk_combo_box_entry_set_text_column(GTK_COMBO_BOX_ENTRY(find_combo), 0); - g_object_unref(find_store); - gtk_combo_box_set_focus_on_click(GTK_COMBO_BOX(find_combo), FALSE); - find_entry = gtk_bin_get_child(GTK_BIN(find_combo)); - gtk_entry_set_activates_default(GTK_ENTRY(find_entry), TRUE); - GtkWidget *replace_combo = gtk_combo_box_entry_new_with_model( - GTK_TREE_MODEL(repl_store), 0); - gtk_combo_box_entry_set_text_column(GTK_COMBO_BOX_ENTRY(replace_combo), 0); - g_object_unref(repl_store); - gtk_combo_box_set_focus_on_click(GTK_COMBO_BOX(replace_combo), FALSE); - replace_entry = gtk_bin_get_child(GTK_BIN(replace_combo)); - gtk_entry_set_activates_default(GTK_ENTRY(replace_entry), TRUE); - fnext_button = gtk_button_new_with_mnemonic("Find _Next"); - fprev_button = gtk_button_new_with_mnemonic("Find _Prev"); - r_button = gtk_button_new_with_mnemonic("_Replace"); - ra_button = gtk_button_new_with_mnemonic("Replace _All"); - match_case = gtk_check_button_new_with_mnemonic("_Match case"); - whole_word = gtk_check_button_new_with_mnemonic("_Whole word"); - lua_pattern = gtk_check_button_new_with_mnemonic("_Lua pattern"); - in_files = gtk_check_button_new_with_mnemonic("_In files"); - - gtk_label_set_mnemonic_widget(GTK_LABEL(flabel), find_entry); - gtk_label_set_mnemonic_widget(GTK_LABEL(rlabel), replace_entry); - - attach(find_combo, 1, 2, 0, 1, EXPAND_FILL, SHRINK_FILL, 5, 0); - attach(replace_combo, 1, 2, 1, 2, EXPAND_FILL, SHRINK_FILL, 5, 0); - attach(flabel, 0, 1, 0, 1, SHRINK_FILL, SHRINK_FILL, 5, 0); - attach(rlabel, 0, 1, 1, 2, SHRINK_FILL, SHRINK_FILL, 5, 0); - attach(fnext_button, 2, 3, 0, 1, SHRINK_FILL, SHRINK_FILL, 0, 0); - attach(fprev_button, 3, 4, 0, 1, SHRINK_FILL, SHRINK_FILL, 0, 0); - attach(r_button, 2, 3, 1, 2, SHRINK_FILL, SHRINK_FILL, 0, 0); - attach(ra_button, 3, 4, 1, 2, SHRINK_FILL, SHRINK_FILL, 0, 0); - attach(match_case, 4, 5, 0, 1, SHRINK_FILL, SHRINK_FILL, 5, 0); - attach(whole_word, 4, 5, 1, 2, SHRINK_FILL, SHRINK_FILL, 5, 0); - attach(lua_pattern, 5, 6, 0, 1, SHRINK_FILL, SHRINK_FILL, 5, 0); - attach(in_files, 5, 6, 1, 2, SHRINK_FILL, SHRINK_FILL, 5, 0); - - signal(fnext_button, "clicked", f_clicked); - signal(fprev_button, "clicked", f_clicked); - signal(r_button, "clicked", f_clicked); - signal(ra_button, "clicked", f_clicked); - - gtk_widget_set_can_default(fnext_button, TRUE); - gtk_widget_set_can_focus(fnext_button, FALSE); - gtk_widget_set_can_focus(fprev_button, FALSE); - gtk_widget_set_can_focus(r_button, FALSE); - gtk_widget_set_can_focus(ra_button, FALSE); - gtk_widget_set_can_focus(match_case, FALSE); - gtk_widget_set_can_focus(whole_word, FALSE); - gtk_widget_set_can_focus(lua_pattern, FALSE); - gtk_widget_set_can_focus(in_files, FALSE); -#endif - - return findbox; -} - -/** - * Creates the Textadept window. - * The window contains a menubar, frame for Scintilla views, hidden find box, - * hidden command entry, and two status bars: one for notifications and the - * other for buffer status. - */ -static void new_window() { -#if GTK - GList *icon_list = NULL; - const char *icons[] = { "16x16", "32x32", "48x48", "64x64", "128x128" }; - for (int i = 0; i < 5; i++) { - char *icon_file = g_strconcat(textadept_home, "/core/images/ta_", icons[i], - ".png", NULL); - GdkPixbuf *pb = gdk_pixbuf_new_from_file(icon_file, NULL); - if (pb) icon_list = g_list_prepend(icon_list, pb); - g_free(icon_file); - } - gtk_window_set_default_icon_list(icon_list); - g_list_foreach(icon_list, (GFunc)g_object_unref, NULL); - g_list_free(icon_list); - - window = gtk_window_new(GTK_WINDOW_TOPLEVEL); - gtk_widget_set_name(window, "textadept"); - gtk_window_set_default_size(GTK_WINDOW(window), 500, 400); - signal(window, "delete-event", w_exit); - signal(window, "focus-in-event", w_focus); - signal(window, "key-press-event", w_keypress); - accel = gtk_accel_group_new(); - -#if __OSX__ - gtk_osxapplication_set_use_quartz_accelerators(osxapp, FALSE); - osx_signal(osxapp, "NSApplicationOpenFile", w_open_osx); - osx_signal(osxapp, "NSApplicationBlockTermination", w_exit_osx); - osx_signal(osxapp, "NSApplicationWillTerminate", w_quit_osx); -#endif - - GtkWidget *vbox = gtk_vbox_new(FALSE, 0); - gtk_container_add(GTK_CONTAINER(window), vbox); - - menubar = gtk_menu_bar_new(); - gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0); - - GtkWidget *hbox = gtk_hbox_new(FALSE, 0); - gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0); - - GtkWidget *view = new_view(0); - gtk_box_pack_start(GTK_BOX(hbox), view, TRUE, TRUE, 0); - - GtkWidget *find = new_findbox(); - gtk_box_pack_start(GTK_BOX(vbox), find, FALSE, FALSE, 5); - - command_entry = gtk_entry_new(); - signal(command_entry, "activate", c_activate); - signal(command_entry, "key-press-event", c_keypress); - gtk_box_pack_start(GTK_BOX(vbox), command_entry, FALSE, FALSE, 0); - - command_entry_completion = gtk_entry_completion_new(); - signal(command_entry_completion, "match-selected", cc_matchselected); - gtk_entry_completion_set_match_func(command_entry_completion, cc_matchfunc, - NULL, NULL); - gtk_entry_completion_set_popup_set_width(command_entry_completion, FALSE); - gtk_entry_completion_set_text_column(command_entry_completion, 0); - cc_store = gtk_list_store_new(1, G_TYPE_STRING); - gtk_entry_completion_set_model(command_entry_completion, - GTK_TREE_MODEL(cc_store)); - gtk_entry_set_completion(GTK_ENTRY(command_entry), command_entry_completion); - - GtkWidget *hboxs = gtk_hbox_new(FALSE, 0); - gtk_box_pack_start(GTK_BOX(vbox), hboxs, FALSE, FALSE, 0); - - statusbar[0] = gtk_statusbar_new(); - gtk_statusbar_push(GTK_STATUSBAR(statusbar[0]), 0, ""); - gtk_statusbar_set_has_resize_grip(GTK_STATUSBAR(statusbar[0]), FALSE); - gtk_box_pack_start(GTK_BOX(hboxs), statusbar[0], TRUE, TRUE, 0); - - statusbar[1] = gtk_statusbar_new(); - gtk_statusbar_push(GTK_STATUSBAR(statusbar[1]), 0, ""); - g_object_set(G_OBJECT(statusbar[1]), "width-request", 400, NULL); - gtk_box_pack_start(GTK_BOX(hboxs), statusbar[1], FALSE, FALSE, 0); - - gtk_widget_show_all(window); - gtk_widget_hide(menubar); // hide initially - gtk_widget_hide(findbox); // hide initially - gtk_widget_hide(command_entry); // hide initially -#elif NCURSES - Scintilla *view = new_view(0); - wresize(scintilla_get_window(view), LINES - 2, COLS); - mvwin(scintilla_get_window(view), 1, 0); -#endif -} - -/** - * Runs Textadept. - * Initializes the Lua state, creates the user interface, and then runs - * `core/init.lua` followed by `init.lua`. - * @param argc The number of command line params. - * @param argv The array of command line params. - */ -int main(int argc, char **argv) { -#if GTK - gtk_init(&argc, &argv); -#elif NCURSES - TermKey *tk = termkey_new(0, TERMKEY_FLAG_NOTERMIOS); - initscr(); // raw()/cbreak() and noecho() are taken care of in libtermkey -#endif - -#if !(__WIN32__ || __OSX__ || __BSD__) - textadept_home = malloc(FILENAME_MAX); - readlink("/proc/self/exe", textadept_home, FILENAME_MAX); -#elif __WIN32__ - textadept_home = malloc(FILENAME_MAX); - GetModuleFileName(0, textadept_home, FILENAME_MAX); -#elif __OSX__ - osxapp = g_object_new(GTK_TYPE_OSX_APPLICATION, NULL); - char *path = quartz_application_get_resource_path(); - textadept_home = g_filename_from_utf8((const char *)path, -1, NULL, NULL, - NULL); - g_free(path); -#elif __BSD__ - textadept_home = malloc(FILENAME_MAX); - int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 }; - size_t cb = FILENAME_MAX; - sysctl(mib, 4, textadept_home, &cb, NULL, 0); -#endif -#if !(__WIN32__ || __OSX__) - char *last_slash = strrchr(textadept_home, '/'); -#elif !__OSX__ - char *last_slash = strrchr(textadept_home, '\\'); -#endif - if (last_slash) *last_slash = '\0'; - -#if GTK -#if GLIB_CHECK_VERSION(2,28,0) && SINGLE_INSTANCE - int force = FALSE; - for (int i = 0; i < argc; i++) - if (strcmp("-f", argv[i]) == 0 || strcmp("--force", argv[i]) == 0) { - force = TRUE; - break; - } - GApplication *app = g_application_new("textadept.editor", - G_APPLICATION_HANDLES_COMMAND_LINE); - g_signal_connect(app, "command-line", G_CALLBACK(a_command_line), 0); - int registered = g_application_register(app, NULL, NULL); - if (!registered || !g_application_get_is_remote(app) || force) { -#endif -#endif - - setlocale(LC_NUMERIC, "C"); - if (lua = luaL_newstate(), !lL_init(lua, argc, argv, FALSE)) return 1; - new_window(); - lL_dofile(lua, "init.lua"); -#if __OSX__ - gtk_osxapplication_ready(osxapp); -#endif - -#if GTK -#if GLIB_CHECK_VERSION(2,28,0) && SINGLE_INSTANCE - gtk_main(); - } else g_application_run(app, argc, argv); - g_object_unref(app); -#else - gtk_main(); -#endif -#elif NCURSES - TermKeyResult res; - TermKeyKey key; - int c = 0; - while ((res = termkey_waitkey(tk, &key)) != TERMKEY_RES_EOF) { - if (res == TERMKEY_RES_ERROR) continue; - switch (key.type) { - case TERMKEY_TYPE_UNICODE: c = key.code.codepoint; break; - case TERMKEY_TYPE_KEYSYM: - switch (key.code.sym) { - case TERMKEY_SYM_BACKSPACE: c = SCK_BACK; break; - case TERMKEY_SYM_TAB: c = SCK_TAB; break; - case TERMKEY_SYM_ENTER: c = SCK_RETURN; break; - case TERMKEY_SYM_ESCAPE: c = SCK_ESCAPE; break; - case TERMKEY_SYM_UP: c = SCK_UP; break; - case TERMKEY_SYM_DOWN: c = SCK_DOWN; break; - case TERMKEY_SYM_LEFT: c = SCK_LEFT; break; - case TERMKEY_SYM_RIGHT: c = SCK_RIGHT; break; - case TERMKEY_SYM_INSERT: c = SCK_INSERT; break; - case TERMKEY_SYM_DELETE: c = SCK_DELETE; break; - case TERMKEY_SYM_PAGEUP: c = SCK_PRIOR; break; - case TERMKEY_SYM_PAGEDOWN: c = SCK_NEXT; break; - case TERMKEY_SYM_HOME: c = SCK_HOME; break; - case TERMKEY_SYM_END: c = SCK_END; break; - default: break; - } - break; - default: continue; - } -// if (c == SCK_ESCAPE && gtk_widget_get_visible(findbox) && -// !gtk_widget_has_focus(command_entry)) { -// gtk_widget_hide(findbox); -// gtk_widget_grab_focus(focused_view); -// } else - curs_set(0); // disable cursor when Scintilla has focus - if (!lL_event(lua, "keypress", LUA_TNUMBER, c, LUA_TBOOLEAN, - key.modifiers & TERMKEY_KEYMOD_SHIFT, LUA_TBOOLEAN, - key.modifiers & TERMKEY_KEYMOD_CTRL, LUA_TBOOLEAN, - key.modifiers & TERMKEY_KEYMOD_ALT, LUA_TBOOLEAN, FALSE, -1)) - scintilla_send_key(focused_view, c, key.modifiers & TERMKEY_KEYMOD_SHIFT, - key.modifiers & TERMKEY_KEYMOD_CTRL, - key.modifiers & TERMKEY_KEYMOD_ALT); - if (quit && lL_event(lua, "quit", -1)) { - l_close(lua); - break; - } else quit = FALSE; -// redrawwin(stdscr); - wrefresh(scintilla_get_window(focused_view)); - redrawwin(scintilla_get_window(focused_view)); - } - endwin(); - termkey_destroy(tk); -#endif - - free(textadept_home); - return 0; -} - -#if __WIN32__ -/** - * Runs Textadept in Windows. - * @see main - */ -int WINAPI WinMain(HINSTANCE _, HINSTANCE __, LPSTR lpCmdLine, int ___) { - return main(1, &lpCmdLine); -} -#endif - -/******************************************************************************/ -/******************************** Lua Interface *******************************/ -/******************************************************************************/ - -#if LUAJIT -#define LUA_OK 0 -#define lua_rawlen lua_objlen -#define LUA_OPEQ 0 -#define lua_compare(l, a, b, _) lua_equal(l, a, b) -#define lL_openlib(l, n, f) \ - (lua_pushcfunction(l, f), lua_pushstring(l, n), lua_call(l, 1, 0)) -#else -#define lL_openlib(l, n, f) (luaL_requiref(l, n, f, 1), lua_pop(l, 1)) -#endif - -#define l_setcfunction(l, n, k, f) \ - (lua_pushcfunction(l, f), lua_setfield(l, (n > 0) ? n : n - 1, k)) -#define l_setmetatable(l, n, k, i, ni) { \ - if (luaL_newmetatable(l, k)) { \ - l_setcfunction(l, -1, "__index", i); \ - l_setcfunction(l, -1, "__newindex", ni); \ - } \ - lua_setmetatable(l, (n > 0) ? n : n - 1); \ -} - static int lfind_next(lua_State *L) { return (f_clicked(fnext_button, NULL), 0); } @@ -1044,12 +418,6 @@ static int lce_focus(lua_State *L) { 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); @@ -1120,6 +488,19 @@ static int lgui_dialog(lua_State *L) { return 1; } +/** + * 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 +} + #if GTK #define child1(p) gtk_paned_get_child1(GTK_PANED(p)) #define child2(p) gtk_paned_get_child2(GTK_PANED(p)) @@ -1170,6 +551,32 @@ static Scintilla *l_toview(lua_State *L, int index) { return view; } +/** + * 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 +} + +/** + * Change focus to the given Scintilla view. + * Generates 'view_before_switch' and 'view_after_switch' events. + * @param view The Scintilla view to focus. + */ +static void goto_view(Scintilla *view) { + if (!closing) lL_event(lua, "view_before_switch", -1); + focused_view = view; + l_setglobalview(lua, view); + l_setglobaldoc(lua, SS(view, SCI_GETDOCPOINTER, 0, 0)); + if (!closing) lL_event(lua, "view_after_switch", -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; @@ -1383,373 +790,119 @@ static int lgui__newindex(lua_State *L) { 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 - * 'core/init.lua' script. + * Checks whether the function argument narg is a Scintilla document. If not, + * raises an error. * @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. + * @param narg The stack index of the Scintilla document. + * @return Scintilla document */ -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 - const char *charset = 0; - g_get_charset(&charset); - lua_pushstring(L, charset), lua_setglobal(L, "_CHARSET"); -#elif NCURSES - lua_pushboolean(L, 1), lua_setglobal(L, "NCURSES"); - lua_pushstring(L, "UTF-8"), lua_setglobal(L, "_CHARSET"); // TODO: get charset -#endif - +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 +} - 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_check_global(lua_State *L) { + lL_globaldoccheck(L, 1); + return 0; } /** - * Loads and runs the given file. + * Returns the buffer at the given acceptable index as a Scintilla document. * @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. + * @param index Stack index of the buffer. + * @return Scintilla document */ -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 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; } /** - * Checks whether the function argument narg is a Scintilla view and returns - * this view cast to a Scintilla. + * Switches to a document in the given view. * @param L The Lua state. - * @param narg The stack index of the Scintilla view. - * @return Scintilla view + * @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 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; -} - -// 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; +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)); + } + 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 } /** - * Adds the Scintilla view with a metatable to the 'views' registry table. + * 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 view The Scintilla view to add. + * @param doc The Scintilla document to remove. + * @see lL_adddoc */ -static void lL_addview(lua_State *L, Scintilla *view) { +static void lL_removedoc(lua_State *L, sptr_t doc) { 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_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 -} - -/** - * 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"); + lua_getfield(L, LUA_REGISTRYINDEX, "ta_buffers"); 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 + 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); // views - lua_pushvalue(L, -1), lua_setfield(L, LUA_REGISTRYINDEX, "ta_views"); - lua_setglobal(L, "_VIEWS"); + lua_pop(L, 1); // buffers + lua_pushvalue(L, -1), lua_setfield(L, LUA_REGISTRYINDEX, "ta_buffers"); + lua_setglobal(L, "_BUFFERS"); } /** - * 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 + * Removes the Scintilla buffer from the current Scintilla view. + * @param doc The Scintilla document. + * @see lL_removedoc */ -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; +static void delete_buffer(sptr_t doc) { + lL_removedoc(lua, doc); + SS(focused_view, SCI_RELEASEDOCUMENT, 0, doc); } static int lbuffer_delete(lua_State *L) { @@ -1950,85 +1103,371 @@ static void lL_adddoc(lua_State *L, sptr_t doc) { } /** - * Returns the buffer at the given acceptable index as a Scintilla document. + * Creates a new Scintilla document and adds it to the Lua state. + * Generates 'buffer_before_switch' and 'buffer_new' events. + * @param doc Almost always zero, except for the first Scintilla view created, + * in which its doc pointer would be given here. + * @see lL_adddoc + */ +static void new_buffer(sptr_t doc) { + if (!doc) { // create the new document + doc = SS(focused_view, SCI_CREATEDOCUMENT, 0, 0); + lL_event(lua, "buffer_before_switch", -1); + lL_adddoc(lua, doc); + lL_gotodoc(lua, focused_view, -1, FALSE); + } else { + // The first Scintilla window already has a pre-created buffer. + lL_adddoc(lua, doc); + SS(focused_view, SCI_ADDREFDOCUMENT, 0, doc); + } + l_setglobaldoc(lua, doc); + lL_event(lua, "buffer_new", -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 + 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; +} + +/** + * Loads and runs the given file. * @param L The Lua state. - * @param index Stack index of the buffer. - * @return Scintilla document + * @param filename The file name relative to textadept_home. + * @return 1 if there are no errors or 0 in case of errors. */ -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 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 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; } /** - * 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. + * Clears a table at the given valid index by setting all of its keys to nil. * @param L The Lua state. - * @param doc The Scintilla document to remove. - * @see lL_adddoc + * @param index The stack index of the table. */ -static void lL_removedoc(lua_State *L, sptr_t doc) { - lua_getfield(L, LUA_REGISTRYINDEX, "ta_views"); +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)) { - 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_pushnil(L), lua_rawset(L, -3); + lua_pushnil(L); // key for lua_next } - lua_pop(L, 1); // views + lua_pop(L, 1); // table copy +} + +/** + * 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 + const char *charset = 0; + g_get_charset(&charset); + lua_pushstring(L, charset), lua_setglobal(L, "_CHARSET"); +#elif NCURSES + lua_pushboolean(L, 1), lua_setglobal(L, "NCURSES"); + lua_pushstring(L, "UTF-8"), lua_setglobal(L, "_CHARSET"); // TODO: get charset +#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; +} + +#if GTK +/** + * Signal for a Textadept window focus change. + */ +static int w_focus(GtkWidget*_, GdkEventFocus *event, void*__) { + if (focused_view && !gtk_widget_has_focus(focused_view)) + gtk_widget_grab_focus(focused_view); + return FALSE; +} + +/** + * Signal for a Textadept keypress. + * Currently handled keypresses: + * - Escape: hides the find box if it is open. + */ +static int w_keypress(GtkWidget*_, GdkEventKey *event, void*__) { + if (event->keyval == 0xff1b && gtk_widget_get_visible(findbox) && + !gtk_widget_has_focus(command_entry)) { + gtk_widget_hide(findbox); + gtk_widget_grab_focus(focused_view); + return TRUE; + } else return FALSE; +} +#endif + +/** + * 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"); 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 + 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 } - lua_pop(L, 1); // buffers - lua_pushvalue(L, -1), lua_setfield(L, LUA_REGISTRYINDEX, "ta_buffers"); - lua_setglobal(L, "_BUFFERS"); + lua_pop(L, 1); // views + lua_pushvalue(L, -1), lua_setfield(L, LUA_REGISTRYINDEX, "ta_views"); + lua_setglobal(L, "_VIEWS"); } /** - * 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. + * Removes a Scintilla view. + * @param view The Scintilla view to remove. + * @see lL_removeview */ -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)); - } - 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 +static void delete_view(Scintilla *view) { + lL_removeview(lua, view); + scintilla_delete(view); +} + +#if GTK +/** + * Remove all Scintilla views from the given pane and delete them. + * @param pane The GTK pane to remove Scintilla views from. + * @see delete_view + */ +static void remove_views_from_pane(GtkWidget *pane) { + GtkWidget *child1 = gtk_paned_get_child1(GTK_PANED(pane)); + GtkWidget *child2 = gtk_paned_get_child2(GTK_PANED(pane)); + GTK_IS_PANED(child1) ? remove_views_from_pane(child1) : delete_view(child1); + GTK_IS_PANED(child2) ? remove_views_from_pane(child2) : delete_view(child2); +} +#endif + +/** + * Unsplits the pane a given Scintilla view is in and keeps the view. + * All views in the other pane are deleted. + * @param view The Scintilla view to keep when unsplitting. + * @see remove_views_from_pane + * @see delete_view + */ +static int unsplit_view(Scintilla *view) { +#if GTK + GtkWidget *pane = gtk_widget_get_parent(view); + if (!GTK_IS_PANED(pane)) return FALSE; + GtkWidget *other = gtk_paned_get_child1(GTK_PANED(pane)); + if (other == view) other = gtk_paned_get_child2(GTK_PANED(pane)); + g_object_ref(view), g_object_ref(other); + gtk_container_remove(GTK_CONTAINER(pane), view); + gtk_container_remove(GTK_CONTAINER(pane), other); + GTK_IS_PANED(other) ? remove_views_from_pane(other) : delete_view(other); + GtkWidget *parent = gtk_widget_get_parent(pane); + gtk_container_remove(GTK_CONTAINER(parent), pane); + if (GTK_IS_PANED(parent)) { + if (!gtk_paned_get_child1(GTK_PANED(parent))) + gtk_paned_add1(GTK_PANED(parent), view); + else + gtk_paned_add2(GTK_PANED(parent), view); + } else gtk_container_add(GTK_CONTAINER(parent), view); + gtk_widget_show_all(parent); + gtk_widget_grab_focus(GTK_WIDGET(view)); + g_object_unref(view), g_object_unref(other); + return TRUE; +#elif NCURSES + return FALSE; +#endif } /** @@ -2051,108 +1490,52 @@ static void l_close(lua_State *L) { lua_close(L); } -/******************************************************************************/ -/*************************** Lua Utility Functions ****************************/ -/******************************************************************************/ - +#if GTK /** - * 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. + * Signal for exiting Textadept. + * Generates a 'quit' event. + * Closes the Lua state and releases resources. + * @see l_close */ -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 +static int w_exit(GtkWidget*_, GdkEventAny*__, void*___) { + if (!lL_event(lua, "quit", -1)) return TRUE; + l_close(lua); + scintilla_release_resources(); + gtk_main_quit(); + return FALSE; } +#if __OSX__ /** - * 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 + * Signal for opening files from OSX. + * Generates an 'appleevent_odoc' event for each document sent. */ -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 w_open_osx(GtkOSXApplication*_, char *path, void*__) { + lL_event(lua, "appleevent_odoc", LUA_TSTRING, path, -1); + return TRUE; } /** - * 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 + * Signal for block terminating Textadept from OSX. + * Generates a 'quit' event. */ -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 w_exit_osx(GtkOSXApplication*_, void*__) { + return !lL_event(lua, "quit", -1); } -/******************************************************************************/ -/********************** 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. + * Signal for terminating Textadept from OSX. + * Closes the Lua state and releases resources. + * @see l_close */ -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 void w_quit_osx(GtkOSXApplication*_, void*__) { + l_close(lua); + scintilla_release_resources(); + g_object_unref(osxapp); + gtk_main_quit(); } +#endif +#endif // if GTK /** * Emits a Scintilla notification event. @@ -2185,6 +1568,42 @@ static void lL_notify(lua_State *L, struct SCNotification *n) { } /** + * Signal for a Scintilla notification. + */ +static void s_notify(Scintilla *view, int _, void *lParam, void*__) { + struct SCNotification *n = (struct SCNotification *)lParam; + if (focused_view == view || n->nmhdr.code == SCN_URIDROPPED) { + if (focused_view != view) goto_view(view); + lL_notify(lua, n); + } else if (n->nmhdr.code == SCN_SAVEPOINTLEFT) { + Scintilla *prev = focused_view; + goto_view(view); + lL_notify(lua, n); + goto_view(prev); // do not let a split view steal focus + } +} + +#if GTK +/** + * Signal for a Scintilla command. + * Currently handles SCEN_SETFOCUS. + */ +static void s_command(GtkWidget *view, int wParam, void*_, void*__) { + if (wParam >> 16 == SCEN_SETFOCUS) goto_view(view); +} + +/** + * Signal for a Scintilla keypress. + */ +static int s_keypress(GtkWidget *view, GdkEventKey *event, void*_) { + return lL_event(lua, "keypress", LUA_TNUMBER, event->keyval, LUA_TBOOLEAN, + event->state & GDK_SHIFT_MASK, LUA_TBOOLEAN, + event->state & GDK_CONTROL_MASK, LUA_TBOOLEAN, + event->state & GDK_MOD1_MASK, LUA_TBOOLEAN, + event->state & GDK_META_MASK, -1); +} + +/** * 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. @@ -2208,3 +1627,548 @@ static void lL_showcontextmenu(lua_State *L, void *event) { lua_pop(L, 1); // gui.context_menu } else lua_pop(L, 1); // non-table } + +/** + * Signal for a Scintilla mouse click. + */ +static int s_buttonpress(GtkWidget*_, GdkEventButton *event, void*__) { + if (event->type == GDK_BUTTON_PRESS && event->button == 3) + return (lL_showcontextmenu(lua, (void *)event), TRUE); + return FALSE; +} +#endif + +/** + * 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; +} + +// 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; +} + +/** + * Splits the given Scintilla view into two views. + * The new view shows the same document as the original one. + * @param view The Scintilla view to split. + * @param vertical Flag indicating whether to split the view vertically or + * horozontally. + */ +static void split_view(Scintilla *view, int vertical) { + sptr_t curdoc = SS(view, SCI_GETDOCPOINTER, 0, 0); + int first_line = SS(view, SCI_GETFIRSTVISIBLELINE, 0, 0); + int current_pos = SS(view, SCI_GETCURRENTPOS, 0, 0); + int anchor = SS(view, SCI_GETANCHOR, 0, 0); + +#if GTK + GtkAllocation allocation; + gtk_widget_get_allocation(view, &allocation); + int middle = (vertical ? allocation.width : allocation.height) / 2; + + g_object_ref(view); + GtkWidget *view2 = new_view(curdoc); + GtkWidget *parent = gtk_widget_get_parent(view); + gtk_container_remove(GTK_CONTAINER(parent), view); + GtkWidget *pane = vertical ? gtk_hpaned_new() : gtk_vpaned_new(); + gtk_paned_add1(GTK_PANED(pane), view), gtk_paned_add2(GTK_PANED(pane), view2); + gtk_container_add(GTK_CONTAINER(parent), pane); + gtk_paned_set_position(GTK_PANED(pane), middle); + gtk_widget_show_all(pane); + g_object_unref(view); +#elif NCURSES + WINDOW *win = scintilla_get_window(view); + int x, y; + getbegyx(win, y, x); + int width = getmaxx(win) - x, height = getmaxy(win) - y; + wresize(win, vertical ? height : height / 2, vertical ? width / 2 : width); + Scintilla *view2 = new_view(curdoc); + wresize(scintilla_get_window(view2), vertical ? height : height / 2, + vertical ? width / 2 : width); + mvwin(scintilla_get_window(view2), vertical ? y : y + height / 2, + vertical ? x + width / 2 : x); + // TODO: draw split +#endif + focus_view(view2); + + SS(view2, SCI_SETSEL, anchor, current_pos); + int new_first_line = SS(view2, SCI_GETFIRSTVISIBLELINE, 0, 0); + SS(view2, SCI_LINESCROLL, first_line - new_first_line, 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. + * @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 +} + +/** + * Creates a new Scintilla view. + * Generates a 'view_new' event. + * @param doc The document to load in the new view. Almost never zero, except + * for the first Scintilla view created, in which there is no doc pointer. + * @return Scintilla view + * @see lL_addview + */ +static Scintilla *new_view(sptr_t doc) { +#if GTK + Scintilla *view = scintilla_new(); + gtk_widget_set_size_request(view, 1, 1); // minimum size + signal(view, SCINTILLA_NOTIFY, s_notify); + signal(view, "command", s_command); + signal(view, "key-press-event", s_keypress); + signal(view, "button-press-event", s_buttonpress); +#elif NCURSES + Scintilla *view = scintilla_new(s_notify); +#endif + SS(view, SCI_USEPOPUP, 0, 0); + lL_addview(lua, view); + focused_view = view; + focus_view(view); + if (doc) { + SS(view, SCI_SETDOCPOINTER, 0, doc); + l_setglobaldoc(lua, doc); + } else new_buffer(SS(view, SCI_GETDOCPOINTER, 0, 0)); + l_setglobalview(lua, view); + lL_event(lua, "view_new", -1); + return view; +} + +/** + * Creates the Find box. + */ +static FindBox *new_findbox() { +#if GTK +#define attach(w, x1, x2, y1, y2, xo, yo, xp, yp) \ + gtk_table_attach(GTK_TABLE(findbox), w, x1, x2, y1, y2, xo, yo, xp, yp) +#define EXPAND_FILL (GtkAttachOptions)(GTK_EXPAND | GTK_FILL) +#define SHRINK_FILL (GtkAttachOptions)(GTK_SHRINK | GTK_FILL) + + findbox = gtk_table_new(2, 6, FALSE); + find_store = gtk_list_store_new(1, G_TYPE_STRING); + repl_store = gtk_list_store_new(1, G_TYPE_STRING); + + flabel = gtk_label_new_with_mnemonic("_Find:"); + rlabel = gtk_label_new_with_mnemonic("R_eplace:"); + GtkWidget *find_combo = gtk_combo_box_entry_new_with_model( + GTK_TREE_MODEL(find_store), 0); + gtk_combo_box_entry_set_text_column(GTK_COMBO_BOX_ENTRY(find_combo), 0); + g_object_unref(find_store); + gtk_combo_box_set_focus_on_click(GTK_COMBO_BOX(find_combo), FALSE); + find_entry = gtk_bin_get_child(GTK_BIN(find_combo)); + gtk_entry_set_activates_default(GTK_ENTRY(find_entry), TRUE); + GtkWidget *replace_combo = gtk_combo_box_entry_new_with_model( + GTK_TREE_MODEL(repl_store), 0); + gtk_combo_box_entry_set_text_column(GTK_COMBO_BOX_ENTRY(replace_combo), 0); + g_object_unref(repl_store); + gtk_combo_box_set_focus_on_click(GTK_COMBO_BOX(replace_combo), FALSE); + replace_entry = gtk_bin_get_child(GTK_BIN(replace_combo)); + gtk_entry_set_activates_default(GTK_ENTRY(replace_entry), TRUE); + fnext_button = gtk_button_new_with_mnemonic("Find _Next"); + fprev_button = gtk_button_new_with_mnemonic("Find _Prev"); + r_button = gtk_button_new_with_mnemonic("_Replace"); + ra_button = gtk_button_new_with_mnemonic("Replace _All"); + match_case = gtk_check_button_new_with_mnemonic("_Match case"); + whole_word = gtk_check_button_new_with_mnemonic("_Whole word"); + lua_pattern = gtk_check_button_new_with_mnemonic("_Lua pattern"); + in_files = gtk_check_button_new_with_mnemonic("_In files"); + + gtk_label_set_mnemonic_widget(GTK_LABEL(flabel), find_entry); + gtk_label_set_mnemonic_widget(GTK_LABEL(rlabel), replace_entry); + + attach(find_combo, 1, 2, 0, 1, EXPAND_FILL, SHRINK_FILL, 5, 0); + attach(replace_combo, 1, 2, 1, 2, EXPAND_FILL, SHRINK_FILL, 5, 0); + attach(flabel, 0, 1, 0, 1, SHRINK_FILL, SHRINK_FILL, 5, 0); + attach(rlabel, 0, 1, 1, 2, SHRINK_FILL, SHRINK_FILL, 5, 0); + attach(fnext_button, 2, 3, 0, 1, SHRINK_FILL, SHRINK_FILL, 0, 0); + attach(fprev_button, 3, 4, 0, 1, SHRINK_FILL, SHRINK_FILL, 0, 0); + attach(r_button, 2, 3, 1, 2, SHRINK_FILL, SHRINK_FILL, 0, 0); + attach(ra_button, 3, 4, 1, 2, SHRINK_FILL, SHRINK_FILL, 0, 0); + attach(match_case, 4, 5, 0, 1, SHRINK_FILL, SHRINK_FILL, 5, 0); + attach(whole_word, 4, 5, 1, 2, SHRINK_FILL, SHRINK_FILL, 5, 0); + attach(lua_pattern, 5, 6, 0, 1, SHRINK_FILL, SHRINK_FILL, 5, 0); + attach(in_files, 5, 6, 1, 2, SHRINK_FILL, SHRINK_FILL, 5, 0); + + signal(fnext_button, "clicked", f_clicked); + signal(fprev_button, "clicked", f_clicked); + signal(r_button, "clicked", f_clicked); + signal(ra_button, "clicked", f_clicked); + + gtk_widget_set_can_default(fnext_button, TRUE); + gtk_widget_set_can_focus(fnext_button, FALSE); + gtk_widget_set_can_focus(fprev_button, FALSE); + gtk_widget_set_can_focus(r_button, FALSE); + gtk_widget_set_can_focus(ra_button, FALSE); + gtk_widget_set_can_focus(match_case, FALSE); + gtk_widget_set_can_focus(whole_word, FALSE); + gtk_widget_set_can_focus(lua_pattern, FALSE); + gtk_widget_set_can_focus(in_files, FALSE); +#endif + + return findbox; +} + +#if GTK +/** + * Signal for the 'enter' key being pressed in the Command Entry. + */ +static void c_activate(GtkWidget *entry, void*_) { + lL_event(lua, "command_entry_command", LUA_TSTRING, + gtk_entry_get_text(GTK_ENTRY(entry)), -1); +} + +/** + * Signal for a keypress inside the Command Entry. + */ +static int c_keypress(GtkWidget*_, GdkEventKey *event, void*__) { + return lL_event(lua, "command_entry_keypress", LUA_TNUMBER, event->keyval, + LUA_TBOOLEAN, event->state & GDK_SHIFT_MASK, LUA_TBOOLEAN, + event->state & GDK_CONTROL_MASK, LUA_TBOOLEAN, + event->state & GDK_MOD1_MASK, LUA_TBOOLEAN, + event->state & GDK_META_MASK, -1); +} + +/** + * Replaces the current word (consisting of alphanumeric and underscore + * characters) with the match text. + */ +static int cc_matchselected(GtkEntryCompletion*_, GtkTreeModel *model, + GtkTreeIter *iter, void*__) { + const char *text = gtk_entry_get_text(GTK_ENTRY(command_entry)), *p; + for (p = text + strlen(text) - 1; g_ascii_isalnum(*p) || *p == '_'; p--) + g_signal_emit_by_name(G_OBJECT(command_entry), "move-cursor", + GTK_MOVEMENT_VISUAL_POSITIONS, -1, TRUE, 0); + if (p < text + strlen(text) - 1) + g_signal_emit_by_name(G_OBJECT(command_entry), "backspace", 0); + + char *match; + gtk_tree_model_get(model, iter, 0, &match, -1); + g_signal_emit_by_name(G_OBJECT(command_entry), "insert-at-cursor", match, 0); + g_free(match); + + gtk_list_store_clear(cc_store); + return TRUE; +} + +/** + * The match function for the command entry. + * Since the completion list is filled by Lua, every item is a "match". + */ +static int cc_matchfunc(GtkEntryCompletion*_, const char *__, GtkTreeIter*___, + void*____) { return 1; } + +#endif // if GTK + +/** + * Creates the Textadept window. + * The window contains a menubar, frame for Scintilla views, hidden find box, + * hidden command entry, and two status bars: one for notifications and the + * other for buffer status. + */ +static void new_window() { +#if GTK + GList *icon_list = NULL; + const char *icons[] = { "16x16", "32x32", "48x48", "64x64", "128x128" }; + for (int i = 0; i < 5; i++) { + char *icon_file = g_strconcat(textadept_home, "/core/images/ta_", icons[i], + ".png", NULL); + GdkPixbuf *pb = gdk_pixbuf_new_from_file(icon_file, NULL); + if (pb) icon_list = g_list_prepend(icon_list, pb); + g_free(icon_file); + } + gtk_window_set_default_icon_list(icon_list); + g_list_foreach(icon_list, (GFunc)g_object_unref, NULL); + g_list_free(icon_list); + + window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_widget_set_name(window, "textadept"); + gtk_window_set_default_size(GTK_WINDOW(window), 500, 400); + signal(window, "delete-event", w_exit); + signal(window, "focus-in-event", w_focus); + signal(window, "key-press-event", w_keypress); + accel = gtk_accel_group_new(); + +#if __OSX__ + gtk_osxapplication_set_use_quartz_accelerators(osxapp, FALSE); + osx_signal(osxapp, "NSApplicationOpenFile", w_open_osx); + osx_signal(osxapp, "NSApplicationBlockTermination", w_exit_osx); + osx_signal(osxapp, "NSApplicationWillTerminate", w_quit_osx); +#endif + + GtkWidget *vbox = gtk_vbox_new(FALSE, 0); + gtk_container_add(GTK_CONTAINER(window), vbox); + + menubar = gtk_menu_bar_new(); + gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0); + + GtkWidget *hbox = gtk_hbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0); + + GtkWidget *view = new_view(0); + gtk_box_pack_start(GTK_BOX(hbox), view, TRUE, TRUE, 0); + + GtkWidget *find = new_findbox(); + gtk_box_pack_start(GTK_BOX(vbox), find, FALSE, FALSE, 5); + + command_entry = gtk_entry_new(); + signal(command_entry, "activate", c_activate); + signal(command_entry, "key-press-event", c_keypress); + gtk_box_pack_start(GTK_BOX(vbox), command_entry, FALSE, FALSE, 0); + + command_entry_completion = gtk_entry_completion_new(); + signal(command_entry_completion, "match-selected", cc_matchselected); + gtk_entry_completion_set_match_func(command_entry_completion, cc_matchfunc, + NULL, NULL); + gtk_entry_completion_set_popup_set_width(command_entry_completion, FALSE); + gtk_entry_completion_set_text_column(command_entry_completion, 0); + cc_store = gtk_list_store_new(1, G_TYPE_STRING); + gtk_entry_completion_set_model(command_entry_completion, + GTK_TREE_MODEL(cc_store)); + gtk_entry_set_completion(GTK_ENTRY(command_entry), command_entry_completion); + + GtkWidget *hboxs = gtk_hbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(vbox), hboxs, FALSE, FALSE, 0); + + statusbar[0] = gtk_statusbar_new(); + gtk_statusbar_push(GTK_STATUSBAR(statusbar[0]), 0, ""); + gtk_statusbar_set_has_resize_grip(GTK_STATUSBAR(statusbar[0]), FALSE); + gtk_box_pack_start(GTK_BOX(hboxs), statusbar[0], TRUE, TRUE, 0); + + statusbar[1] = gtk_statusbar_new(); + gtk_statusbar_push(GTK_STATUSBAR(statusbar[1]), 0, ""); + g_object_set(G_OBJECT(statusbar[1]), "width-request", 400, NULL); + gtk_box_pack_start(GTK_BOX(hboxs), statusbar[1], FALSE, FALSE, 0); + + gtk_widget_show_all(window); + gtk_widget_hide(menubar); // hide initially + gtk_widget_hide(findbox); // hide initially + gtk_widget_hide(command_entry); // hide initially +#elif NCURSES + Scintilla *view = new_view(0); + wresize(scintilla_get_window(view), LINES - 2, COLS); + mvwin(scintilla_get_window(view), 1, 0); +#endif +} + +/** + * Runs Textadept. + * Initializes the Lua state, creates the user interface, and then runs + * `core/init.lua` followed by `init.lua`. + * @param argc The number of command line params. + * @param argv The array of command line params. + */ +int main(int argc, char **argv) { +#if GTK + gtk_init(&argc, &argv); +#elif NCURSES + TermKey *tk = termkey_new(0, TERMKEY_FLAG_NOTERMIOS); + initscr(); // raw()/cbreak() and noecho() are taken care of in libtermkey +#endif + +#if !(__WIN32__ || __OSX__ || __BSD__) + textadept_home = malloc(FILENAME_MAX); + readlink("/proc/self/exe", textadept_home, FILENAME_MAX); +#elif __WIN32__ + textadept_home = malloc(FILENAME_MAX); + GetModuleFileName(0, textadept_home, FILENAME_MAX); +#elif __OSX__ + osxapp = g_object_new(GTK_TYPE_OSX_APPLICATION, NULL); + char *path = quartz_application_get_resource_path(); + textadept_home = g_filename_from_utf8((const char *)path, -1, NULL, NULL, + NULL); + g_free(path); +#elif __BSD__ + textadept_home = malloc(FILENAME_MAX); + int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 }; + size_t cb = FILENAME_MAX; + sysctl(mib, 4, textadept_home, &cb, NULL, 0); +#endif +#if !(__WIN32__ || __OSX__) + char *last_slash = strrchr(textadept_home, '/'); +#elif !__OSX__ + char *last_slash = strrchr(textadept_home, '\\'); +#endif + if (last_slash) *last_slash = '\0'; + +#if GTK +#if GLIB_CHECK_VERSION(2,28,0) && SINGLE_INSTANCE + int force = FALSE; + for (int i = 0; i < argc; i++) + if (strcmp("-f", argv[i]) == 0 || strcmp("--force", argv[i]) == 0) { + force = TRUE; + break; + } + GApplication *app = g_application_new("textadept.editor", + G_APPLICATION_HANDLES_COMMAND_LINE); + g_signal_connect(app, "command-line", G_CALLBACK(a_command_line), 0); + int registered = g_application_register(app, NULL, NULL); + if (!registered || !g_application_get_is_remote(app) || force) { +#endif +#endif + + setlocale(LC_NUMERIC, "C"); + if (lua = luaL_newstate(), !lL_init(lua, argc, argv, FALSE)) return 1; + new_window(); + lL_dofile(lua, "init.lua"); +#if __OSX__ + gtk_osxapplication_ready(osxapp); +#endif + +#if GTK +#if GLIB_CHECK_VERSION(2,28,0) && SINGLE_INSTANCE + gtk_main(); + } else g_application_run(app, argc, argv); + g_object_unref(app); +#else + gtk_main(); +#endif +#elif NCURSES + TermKeyResult res; + TermKeyKey key; + int c = 0; + while ((res = termkey_waitkey(tk, &key)) != TERMKEY_RES_EOF) { + if (res == TERMKEY_RES_ERROR) continue; + switch (key.type) { + case TERMKEY_TYPE_UNICODE: c = key.code.codepoint; break; + case TERMKEY_TYPE_KEYSYM: + switch (key.code.sym) { + case TERMKEY_SYM_BACKSPACE: c = SCK_BACK; break; + case TERMKEY_SYM_TAB: c = SCK_TAB; break; + case TERMKEY_SYM_ENTER: c = SCK_RETURN; break; + case TERMKEY_SYM_ESCAPE: c = SCK_ESCAPE; break; + case TERMKEY_SYM_UP: c = SCK_UP; break; + case TERMKEY_SYM_DOWN: c = SCK_DOWN; break; + case TERMKEY_SYM_LEFT: c = SCK_LEFT; break; + case TERMKEY_SYM_RIGHT: c = SCK_RIGHT; break; + case TERMKEY_SYM_INSERT: c = SCK_INSERT; break; + case TERMKEY_SYM_DELETE: c = SCK_DELETE; break; + case TERMKEY_SYM_PAGEUP: c = SCK_PRIOR; break; + case TERMKEY_SYM_PAGEDOWN: c = SCK_NEXT; break; + case TERMKEY_SYM_HOME: c = SCK_HOME; break; + case TERMKEY_SYM_END: c = SCK_END; break; + default: break; + } + break; + default: continue; + } +// if (c == SCK_ESCAPE && gtk_widget_get_visible(findbox) && +// !gtk_widget_has_focus(command_entry)) { +// gtk_widget_hide(findbox); +// gtk_widget_grab_focus(focused_view); +// } else + curs_set(0); // disable cursor when Scintilla has focus + if (!lL_event(lua, "keypress", LUA_TNUMBER, c, LUA_TBOOLEAN, + key.modifiers & TERMKEY_KEYMOD_SHIFT, LUA_TBOOLEAN, + key.modifiers & TERMKEY_KEYMOD_CTRL, LUA_TBOOLEAN, + key.modifiers & TERMKEY_KEYMOD_ALT, LUA_TBOOLEAN, FALSE, -1)) + scintilla_send_key(focused_view, c, key.modifiers & TERMKEY_KEYMOD_SHIFT, + key.modifiers & TERMKEY_KEYMOD_CTRL, + key.modifiers & TERMKEY_KEYMOD_ALT); + if (quit && lL_event(lua, "quit", -1)) { + l_close(lua); + break; + } else quit = FALSE; +// redrawwin(stdscr); + wrefresh(scintilla_get_window(focused_view)); + redrawwin(scintilla_get_window(focused_view)); + } + endwin(); + termkey_destroy(tk); +#endif + + free(textadept_home); + return 0; +} + +#if __WIN32__ +/** + * Runs Textadept in Windows. + * @see main + */ +int WINAPI WinMain(HINSTANCE _, HINSTANCE __, LPSTR lpCmdLine, int ___) { + return main(1, &lpCmdLine); +} +#endif |