aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormitchell <70453897+667e-11@users.noreply.github.com>2012-05-25 08:25:56 -0400
committermitchell <70453897+667e-11@users.noreply.github.com>2012-05-25 08:25:56 -0400
commit127d19c16219f321958f0a0bcdce278a935e9b47 (patch)
tree4d8d3f9128b98abc30998db7a00032adb4da2193
parent027cc4a608900776dce43ad693ae557512a77aac (diff)
downloadtextadept-127d19c16219f321958f0a0bcdce278a935e9b47.tar.gz
textadept-127d19c16219f321958f0a0bcdce278a935e9b47.zip
Finished removing forward declarations; src/textadept.c
-rw-r--r--src/textadept.c2326
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