aboutsummaryrefslogtreecommitdiff
path: root/src/textadept.c
diff options
context:
space:
mode:
authormitchell <70453897+667e-11@users.noreply.github.com>2010-10-24 23:40:09 -0400
committermitchell <70453897+667e-11@users.noreply.github.com>2010-10-24 23:40:09 -0400
commit69aa0eba6708a4bcc0f8adc7936860f54ccc9510 (patch)
tree5278b3d4109def8203ee960fbd76c2790ca3a651 /src/textadept.c
parent6b47651c7dccd2d97d6636ed8d6531d664ea3789 (diff)
downloadtextadept-69aa0eba6708a4bcc0f8adc7936860f54ccc9510.tar.gz
textadept-69aa0eba6708a4bcc0f8adc7936860f54ccc9510.zip
Condensed textadept.h, textadept.c, lua_interface.c into textadept.c.
Diffstat (limited to 'src/textadept.c')
-rw-r--r--src/textadept.c1341
1 files changed, 1334 insertions, 7 deletions
diff --git a/src/textadept.c b/src/textadept.c
index 7bc57017..8f384538 100644
--- a/src/textadept.c
+++ b/src/textadept.c
@@ -1,6 +1,21 @@
// Copyright 2007-2010 Mitchell mitchell<att>caladbolg.net. See LICENSE.
-#include "textadept.h"
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <gtk/gtk.h>
+
+#define PLAT_GTK 1
+#include <Scintilla.h>
+#include <SciLexer.h>
+#include <ScintillaWidget.h>
+
+#include <gcocoadialog.h>
+#include <lua.h>
+#include <lualib.h>
+#include <lauxlib.h>
+
#if __WIN32__
#include <windows.h>
#define main main_
@@ -14,12 +29,35 @@
#endif
#define gbool gboolean
+#define SS(editor, m, w, l) scintilla_send_message(SCINTILLA(editor), m, w, l)
#define signal(o, s, c) g_signal_connect(G_OBJECT(o), s, G_CALLBACK(c), 0)
+#define streq(s1, s2) strcmp(s1, s2) == 0
+#define l_append(l, i) lua_rawseti(l, i, lua_objlen(l, i) + 1)
+#define l_cfunc(l, f, k) { \
+ lua_pushcfunction(l, f); \
+ lua_setfield(l, -2, k); \
+}
+#define l_mt(l, k, i, ni) { \
+ if (luaL_newmetatable(l, k)) { \
+ l_cfunc(l, i, "__index"); \
+ l_cfunc(l, ni, "__newindex"); \
+ } \
+ lua_setmetatable(l, -2); \
+}
+#define l_togtkwidget(l, i) (GtkWidget *)lua_touserdata(l, i)
+
+/******************************************************************************/
+/***************************** Forward Declarations ***************************/
+/******************************************************************************/
-// Textadept
+// Window
GtkWidget *window, *focused_editor, *menubar, *statusbar[2];
char *textadept_home;
+void create_ui();
+GtkWidget *new_scintilla_window(sptr_t);
+void new_scintilla_buffer(GtkWidget *, int, int);
+
static void s_notification(GtkWidget *, gint, gpointer, gpointer);
static void s_command(GtkWidget *, gint, gpointer, gpointer);
static gbool s_keypress(GtkWidget *, GdkEventKey *, gpointer);
@@ -53,6 +91,53 @@ static gbool cc_match_selected(GtkEntryCompletion *, GtkTreeModel *,
static void c_activated(GtkWidget *, gpointer);
static gbool c_keypress(GtkWidget *, GdkEventKey *, gpointer);
+// Lua
+lua_State *lua;
+int closing = FALSE;
+const char *statusbar_text = "";
+static int tVOID = 0, tINT = 1, tLENGTH = 2, /*tPOSITION = 3, tCOLOUR = 4,*/
+ tBOOL = 5, tKEYMOD = 6, tSTRING = 7, tSTRINGRESULT = 8;
+
+int l_init(int, char **, int);
+void l_close();
+int l_load_script(const char *);
+void l_add_scintilla_window(GtkWidget *);
+void l_remove_scintilla_window(GtkWidget *);
+void l_set_view_global(GtkWidget *);
+int l_add_scintilla_buffer(sptr_t);
+void l_remove_scintilla_buffer(sptr_t);
+void l_goto_scintilla_buffer(GtkWidget *, int, int);
+void l_set_buffer_global(GtkWidget *);
+int l_emit_event(const char *, ...);
+void l_emit_scnnotification(struct SCNotification *);
+void l_gui_popup_context_menu(GdkEventButton *);
+// Extra Lua libraries.
+LUALIB_API int (luaopen_lpeg) (lua_State *L);
+LUALIB_API int (luaopen_lfs) (lua_State *L);
+
+static void clear_table(lua_State *, int);
+static int l_buffer_mt_index(lua_State *), l_buffer_mt_newindex(lua_State *),
+ l_bufferp_mt_index(lua_State *), l_bufferp_mt_newindex(lua_State *),
+ l_view_mt_index(lua_State *), l_view_mt_newindex(lua_State *),
+ l_gui_mt_index(lua_State *), l_gui_mt_newindex(lua_State *),
+ l_find_mt_index(lua_State *), l_find_mt_newindex(lua_State *),
+ l_ce_mt_index(lua_State *), l_ce_mt_newindex(lua_State *);
+static int l_cf_buffer_delete(lua_State *), l_cf_buffer_text_range(lua_State *),
+ l_cf_view_focus(lua_State *), l_cf_view_split(lua_State *),
+ l_cf_view_unsplit(lua_State *), l_cf_buffer_new(lua_State *),
+ l_cf_gui_dialog(lua_State *), l_cf_gui_get_split_table(lua_State *),
+ l_cf_gui_goto_view(lua_State *), l_cf_view_goto_buffer(lua_State *),
+ l_cf_gui_gtkmenu(lua_State *), l_cf_string_iconv(lua_State *),
+ l_cf_reset(lua_State *), l_cf_quit(lua_State *),
+ l_cf_find_focus(lua_State *), l_cf_find_next(lua_State *),
+ l_cf_find_prev(lua_State *), l_cf_find_replace(lua_State *),
+ l_cf_find_replace_all(lua_State *), l_cf_ce_focus(lua_State *),
+ l_cf_ce_show_completions(lua_State *);
+
+/******************************************************************************/
+/******************************* GUI Interface ********************************/
+/******************************************************************************/
+
/**
* Runs Textadept in Linux or Mac.
* Inits the Lua State, creates the user interface, loads the core/init.lua
@@ -388,7 +473,9 @@ void set_statusbar_text(const char *text, int bar) {
gtk_statusbar_push(GTK_STATUSBAR(statusbar[bar]), 0, text);
}
-// Notifications/signals
+/******************************************************************************/
+/************************* GUI Notifications/Signals **************************/
+/******************************************************************************/
/**
* Helper function for switching the focused view to the given one.
@@ -525,7 +612,9 @@ static OSErr w_ae_quit(const AppleEvent *event, AppleEvent *reply, long ref) {
}
#endif
-// Find/Replace
+/******************************************************************************/
+/************************* Find/Replace GUI Interface *************************/
+/******************************************************************************/
#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)
@@ -635,7 +724,9 @@ static void find_add_to_history(const char *text, GtkListStore *store) {
}
}
-// Signals
+/******************************************************************************/
+/**************************** Find/Replace Signals ****************************/
+/******************************************************************************/
/**
* Signal for a Find frame button click.
@@ -660,7 +751,9 @@ static void find_button_clicked(GtkWidget *button, gpointer udata) {
}
}
-// Command Entry
+/******************************************************************************/
+/************************ Command Entry GUI Interface *************************/
+/******************************************************************************/
/**
* Toggles focus between a Scintilla window and the Command Entry.
@@ -716,7 +809,9 @@ static gbool cc_match_selected(GtkEntryCompletion *entry, GtkTreeModel *model,
return TRUE;
}
-// Signals
+/******************************************************************************/
+/*************************** Command Entry Signals ****************************/
+/******************************************************************************/
/**
* Signal for the 'enter' key being pressed in the Command Entry.
@@ -732,3 +827,1235 @@ static void c_activated(GtkWidget *entry, gpointer udata) {
static gbool c_keypress(GtkWidget *entry, GdkEventKey *event, gpointer udata) {
return l_emit_event("command_entry_keypress", LUA_TNUMBER, event->keyval, -1);
}
+
+/******************************************************************************/
+/******************************** Lua Interface *******************************/
+/******************************************************************************/
+
+#define l_openlib(l, n, f) { \
+ lua_pushcfunction(l, f); \
+ lua_pushstring(l, n); \
+ lua_call(l, 1, 0); \
+}
+#define l_archive(l, k) { \
+ lua_pushstring(l, k); \
+ lua_rawget(l, -2); \
+ lua_setfield(l, LUA_REGISTRYINDEX, k); \
+}
+
+/**
+ * Inits or re-inits the Lua State.
+ * Populates the state with global variables and functions, then runs the
+ * 'core/init.lua' script.
+ * @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 on failure.
+ */
+int l_init(int argc, char **argv, int reinit) {
+ if (!reinit) {
+ lua = lua_open();
+ lua_newtable(lua);
+ for (int i = 0; i < argc; i++) {
+ lua_pushstring(lua, argv[i]);
+ lua_rawseti(lua, -2, i);
+ }
+ lua_setfield(lua, LUA_REGISTRYINDEX, "arg");
+ lua_newtable(lua);
+ lua_setfield(lua, LUA_REGISTRYINDEX, "buffers");
+ lua_newtable(lua);
+ lua_setfield(lua, LUA_REGISTRYINDEX, "views");
+ } else { // clear package.loaded and _G
+ lua_getglobal(lua, "package");
+ lua_getfield(lua, -1, "loaded");
+ clear_table(lua, lua_gettop(lua));
+ lua_pop(lua, 2); // package and package.loaded
+ clear_table(lua, LUA_GLOBALSINDEX);
+ }
+ luaL_openlibs(lua);
+ l_openlib(lua, "lpeg", luaopen_lpeg);
+ l_openlib(lua, "lfs", luaopen_lfs);
+
+ lua_newtable(lua);
+ lua_newtable(lua);
+ l_cfunc(lua, l_cf_find_next, "find_next");
+ l_cfunc(lua, l_cf_find_prev, "find_prev");
+ l_cfunc(lua, l_cf_find_focus, "focus");
+ l_cfunc(lua, l_cf_find_replace, "replace");
+ l_cfunc(lua, l_cf_find_replace_all, "replace_all");
+ l_mt(lua, "_find_mt", l_find_mt_index, l_find_mt_newindex);
+ lua_setfield(lua, -2, "find");
+ lua_newtable(lua);
+ l_cfunc(lua, l_cf_ce_focus, "focus");
+ l_cfunc(lua, l_cf_ce_show_completions, "show_completions");
+ l_mt(lua, "_ce_mt", l_ce_mt_index, l_ce_mt_newindex);
+ lua_setfield(lua, -2, "command_entry");
+ l_cfunc(lua, l_cf_gui_dialog, "dialog");
+ l_cfunc(lua, l_cf_gui_get_split_table, "get_split_table");
+ l_cfunc(lua, l_cf_gui_goto_view, "goto_view");
+ l_cfunc(lua, l_cf_gui_gtkmenu, "gtkmenu");
+ l_mt(lua, "_gui_mt", l_gui_mt_index, l_gui_mt_newindex);
+ lua_setglobal(lua, "gui");
+
+ lua_getglobal(lua, "_G");
+ l_cfunc(lua, l_cf_buffer_new, "new_buffer");
+ l_cfunc(lua, l_cf_quit, "quit");
+ l_cfunc(lua, l_cf_reset, "reset");
+ lua_pop(lua, 1); // _G
+
+ lua_getglobal(lua, "string");
+ l_cfunc(lua, l_cf_string_iconv, "iconv");
+ lua_pop(lua, 1); // string
+
+ lua_getfield(lua, LUA_REGISTRYINDEX, "arg");
+ lua_setglobal(lua, "arg");
+ lua_getfield(lua, LUA_REGISTRYINDEX, "buffers");
+ lua_setglobal(lua, "_BUFFERS");
+ lua_getfield(lua, LUA_REGISTRYINDEX, "views");
+ lua_setglobal(lua, "_VIEWS");
+ lua_pushstring(lua, textadept_home);
+ lua_setglobal(lua, "_HOME");
+#if __WIN32__
+ lua_pushboolean(lua, 1);
+ lua_setglobal(lua, "WIN32");
+#elif MAC
+ lua_pushboolean(lua, 1);
+ lua_setglobal(lua, "MAC");
+#endif
+ const char *charset = 0;
+ g_get_charset(&charset);
+ lua_pushstring(lua, charset);
+ lua_setglobal(lua, "_CHARSET");
+
+ if (l_load_script("core/init.lua")) {
+ lua_getglobal(lua, "_SCINTILLA");
+ l_archive(lua, "constants");
+ l_archive(lua, "functions");
+ l_archive(lua, "properties");
+ lua_pop(lua, 1); // _SCINTILLA
+ return TRUE;
+ }
+ lua_close(lua);
+ return FALSE;
+}
+
+/**
+ * Loads and runs a given Lua script.
+ * @param script_file The path of the Lua script relative to textadept_home.
+ * @return TRUE on success, FALSE otherwise.
+ */
+int l_load_script(const char *script_file) {
+ char *script = g_strconcat(textadept_home, "/", script_file, NULL);
+ int retval = luaL_dofile(lua, script) == 0;
+ if (!retval) {
+ const char *errmsg = lua_tostring(lua, -1);
+ lua_settop(lua, 0);
+ GtkWidget *dialog = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_OK, "%s\n", errmsg);
+ gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_destroy(dialog);
+ }
+ g_free(script);
+ return retval;
+}
+
+/**
+ * Checks a specified stack element to see if it is a Scintilla window and
+ * returns it as a GtkWidget.
+ * Throws an error if the check is not satisfied.
+ * @param lua The Lua State.
+ * @param narg Relative stack index to check for a Scintilla window.
+ * @return GtkWidget Scintilla view.
+ */
+static GtkWidget *l_checkview(lua_State *lua, int narg) {
+ luaL_argcheck(lua, lua_istable(lua, narg), narg, "View expected");
+ lua_pushstring(lua, "widget_pointer");
+ lua_rawget(lua, (narg > 0) ? narg : narg - 1);
+ luaL_argcheck(lua, lua_islightuserdata(lua, -1), narg, "View expected");
+ GtkWidget *editor = l_togtkwidget(lua, -1);
+ lua_pop(lua, 1); // widget_pointer
+ return editor;
+}
+
+/**
+ * Adds a Scintilla window to the global '_VIEWS' table with a metatable.
+ * @param editor The Scintilla window to add.
+ */
+void l_add_scintilla_window(GtkWidget *editor) {
+ lua_getfield(lua, LUA_REGISTRYINDEX, "views");
+ lua_newtable(lua);
+ lua_pushlightuserdata(lua, (GtkWidget *)editor);
+ lua_setfield(lua, -2, "widget_pointer");
+ l_cfunc(lua, l_cf_view_focus, "focus");
+ l_cfunc(lua, l_cf_view_goto_buffer, "goto_buffer");
+ l_cfunc(lua, l_cf_view_split, "split");
+ l_cfunc(lua, l_cf_view_unsplit, "unsplit");
+ l_mt(lua, "_view_mt", l_view_mt_index, l_view_mt_newindex);
+ l_append(lua, -2); // pops table
+ lua_pop(lua, 1); // views
+}
+
+/**
+ * Removes a Scintilla window from the global '_VIEWS' table.
+ * @param editor The Scintilla window to remove.
+ */
+void l_remove_scintilla_window(GtkWidget *editor) {
+ lua_newtable(lua);
+ lua_getfield(lua, LUA_REGISTRYINDEX, "views");
+ lua_pushnil(lua);
+ while (lua_next(lua, -2))
+ (editor != l_checkview(lua, -1)) ? l_append(lua, -4) : lua_pop(lua, 1);
+ lua_pop(lua, 1); // views
+ lua_pushvalue(lua, -1);
+ lua_setfield(lua, LUA_REGISTRYINDEX, "views");
+ lua_setglobal(lua, "_VIEWS");
+}
+
+/**
+ * Changes focus a Scintilla window in the global '_VIEWS' table.
+ * @param editor The currently focused Scintilla window.
+ * @param n The index of the window in the '_VIEWS' table to focus.
+ * @param absolute Flag indicating whether or not the index specified in
+ * '_VIEWS' is absolute. If FALSE, focuses the window relative to the
+ * currently focused window for the given index.
+ * Throws an error if the view does not exist.
+ */
+void l_goto_scintilla_window(GtkWidget *editor, int n, int absolute) {
+ lua_getfield(lua, LUA_REGISTRYINDEX, "views");
+ if (!absolute) {
+ unsigned int idx = 1;
+ lua_pushnil(lua);
+ while (lua_next(lua, -2))
+ if (editor == l_checkview(lua, -1)) {
+ idx = lua_tointeger(lua, -2);
+ lua_pop(lua, 2); // key and value
+ break;
+ } else lua_pop(lua, 1); // value
+ idx += n;
+ if (idx > lua_objlen(lua, -1))
+ idx = 1;
+ else if (idx < 1)
+ idx = lua_objlen(lua, -1);
+ lua_rawgeti(lua, -1, idx);
+ } else {
+ luaL_argcheck(lua, n >= 0 && n <= lua_objlen(lua, -1), 1,
+ "no View exists at that index");
+ lua_rawgeti(lua, -1, n);
+ }
+ editor = l_checkview(lua, -1);
+ if (!closing) l_emit_event("view_before_switch", -1);
+ gtk_widget_grab_focus(editor);
+ if (!closing) l_emit_event("view_after_switch", -1);
+ lua_pop(lua, 2); // view table and views
+}
+
+/**
+ * Sets the global 'view' variable to be the specified Scintilla window.
+ * @param editor The Scintilla window to set 'view' to.
+ */
+void l_set_view_global(GtkWidget *editor) {
+ lua_getfield(lua, LUA_REGISTRYINDEX, "views");
+ lua_pushnil(lua);
+ while (lua_next(lua, -2))
+ if (editor == l_checkview(lua, -1)) {
+ lua_setglobal(lua, "view"); // value (view table)
+ lua_pop(lua, 1); // key
+ break;
+ } else lua_pop(lua, 1); // value
+ lua_pop(lua, 1); // views
+}
+
+/**
+ * Checks a specified element to see if it is a buffer table and returns the
+ * Scintilla document pointer associated with it.
+ * Throws an error if the check is not satisfied.
+ * @param lua The Lua State.
+ * @param narg Relative stack index to check for a buffer table.
+ */
+static sptr_t l_checkdocpointer(lua_State *lua, int narg) {
+ luaL_argcheck(lua, lua_istable(lua, narg), narg, "Buffer expected");
+ lua_pushstring(lua, "doc_pointer");
+ lua_rawget(lua, (narg > 0) ? narg : narg - 1);
+ luaL_argcheck(lua, lua_isnumber(lua, -1), narg, "Buffer expected");
+ sptr_t doc = lua_tointeger(lua, -1);
+ lua_pop(lua, 1); // doc_pointer
+ return doc;
+}
+
+/**
+ * Adds a Scintilla document to the global '_BUFFERS' table with a metatable.
+ * @param doc The Scintilla document to add.
+ * @return integer index of the new buffer in _BUFFERS.
+ */
+int l_add_scintilla_buffer(sptr_t doc) {
+ lua_getfield(lua, LUA_REGISTRYINDEX, "buffers");
+ lua_newtable(lua);
+ lua_pushinteger(lua, doc);
+ lua_setfield(lua, -2, "doc_pointer");
+ l_cfunc(lua, l_cf_buffer_delete, "delete");
+ l_cfunc(lua, l_cf_buffer_text_range, "text_range");
+ l_mt(lua, "_buffer_mt", l_buffer_mt_index, l_buffer_mt_newindex);
+ l_append(lua, -2); // pops table
+ int index = lua_objlen(lua, -1);
+ lua_pop(lua, 1); // buffers
+ return index;
+}
+
+/**
+ * Removes a Scintilla document from the global '_BUFFERS' table.
+ * If any views currently show the document to be removed, change the documents
+ * they show first.
+ * @param doc The Scintilla buffer to remove.
+ */
+void l_remove_scintilla_buffer(sptr_t doc) {
+ lua_getfield(lua, LUA_REGISTRYINDEX, "views");
+ lua_pushnil(lua);
+ while (lua_next(lua, -2)) {
+ GtkWidget *editor = l_checkview(lua, -1);
+ sptr_t that_doc = SS(editor, SCI_GETDOCPOINTER, 0, 0);
+ if (that_doc == doc) l_goto_scintilla_buffer(editor, -1, FALSE);
+ lua_pop(lua, 1); // value
+ }
+ lua_pop(lua, 1); // views
+ lua_newtable(lua);
+ lua_getfield(lua, LUA_REGISTRYINDEX, "buffers");
+ lua_pushnil(lua);
+ while (lua_next(lua, -2))
+ (doc != l_checkdocpointer(lua, -1)) ? l_append(lua, -4) : lua_pop(lua, 1);
+ lua_pop(lua, 1); // buffers
+ lua_pushvalue(lua, -1);
+ lua_setfield(lua, LUA_REGISTRYINDEX, "buffers");
+ lua_setglobal(lua, "_BUFFERS");
+}
+
+/**
+ * Retrieves the index in the global '_BUFFERS' table for a given Scintilla
+ * document.
+ * @param doc The Scintilla document to get the index of.
+ * @return int buffer index.
+ */
+unsigned int l_get_docpointer_index(sptr_t doc) {
+ lua_getfield(lua, LUA_REGISTRYINDEX, "buffers");
+ unsigned int idx = 1;
+ lua_pushnil(lua);
+ while (lua_next(lua, -2))
+ if (doc == l_checkdocpointer(lua, -1)) {
+ idx = lua_tointeger(lua, -2);
+ lua_pop(lua, 2); // key and value
+ break;
+ } else lua_pop(lua, 1); // value
+ lua_pop(lua, 1); // buffers
+ return idx;
+}
+
+/**
+ * Changes a Scintilla window's document to one in the global '_BUFFERS' table.
+ * Before doing so, it saves the scroll and caret positions in the current
+ * Scintilla document. Then when the new document is shown, its scroll and caret
+ * positions are restored.
+ * @param editor The Scintilla window to change the document of.
+ * @param n The index of the document in '_BUFFERS' to focus.
+ * @param absolute Flag indicating whether or not the index specified in
+ * '_BUFFERS' is absolute. If FALSE, focuses the document relative to the
+ * currently focused document for the given index.
+ * Throws an error if the buffer does not exist.
+ */
+void l_goto_scintilla_buffer(GtkWidget *editor, int n, int absolute) {
+ lua_getfield(lua, LUA_REGISTRYINDEX, "buffers");
+ if (!absolute) {
+ sptr_t doc = SS(editor, SCI_GETDOCPOINTER, 0, 0);
+ unsigned int idx = l_get_docpointer_index(doc);
+ idx += n;
+ if (idx > lua_objlen(lua, -1))
+ idx = 1;
+ else if (idx < 1)
+ idx = lua_objlen(lua, -1);
+ lua_rawgeti(lua, -1, idx);
+ } else {
+ luaL_argcheck(lua, n >= 0 && n <= lua_objlen(lua, -1), 2,
+ "no Buffer exists at that index");
+ lua_rawgeti(lua, -1, n);
+ }
+ sptr_t doc = l_checkdocpointer(lua, -1);
+ if (!closing) l_emit_event("buffer_before_switch", -1);
+ SS(editor, SCI_SETDOCPOINTER, 0, doc);
+ l_set_buffer_global(editor);
+ if (!closing) l_emit_event("buffer_after_switch", -1);
+ lua_pop(lua, 2); // buffer table and buffers
+}
+
+/**
+ * Sets the global 'buffer' variable to be the document in the specified
+ * Scintilla object.
+ * @param editor The Scintilla widget housing the buffer to be 'buffer'.
+ */
+void l_set_buffer_global(GtkWidget *editor) {
+ sptr_t doc = SS(editor, SCI_GETDOCPOINTER, 0, 0);
+ lua_getfield(lua, LUA_REGISTRYINDEX, "buffers");
+ lua_pushnil(lua);
+ while (lua_next(lua, -2))
+ if (doc == l_checkdocpointer(lua, -1)) {
+ lua_setglobal(lua, "buffer"); // value (buffer table)
+ lua_pop(lua, 1); // key
+ break;
+ } else lua_pop(lua, 1); // value
+ lua_pop(lua, 1); // buffers
+}
+
+/**
+ * Closes the Lua State.
+ * Unsplits all Scintilla windows recursively, removes all Scintilla documents,
+ * and deletes the last Scintilla window before closing the state.
+ */
+void l_close() {
+ closing = TRUE;
+ while (unsplit_window(focused_editor)) ; // need space to fix compiler warning
+ lua_getfield(lua, LUA_REGISTRYINDEX, "buffers");
+ lua_pushnil(lua);
+ while (lua_next(lua, -2)) {
+ sptr_t doc = l_checkdocpointer(lua, -1);
+ remove_scintilla_buffer(doc);
+ lua_pop(lua, 1); // value
+ }
+ lua_pop(lua, 1); // buffers
+ gtk_widget_destroy(focused_editor);
+ lua_close(lua);
+}
+
+/******************************************************************************/
+/*************************** Lua Utility Functions ****************************/
+/******************************************************************************/
+
+/**
+ * Recurses through a Lua table, setting each of its keys and values to nil,
+ * effectively clearing the table.
+ * @param lua The Lua State.
+ * @param abs_index The absolute stack index of the table to clear.
+ */
+static void clear_table(lua_State *lua, int abs_index) {
+ lua_pushnil(lua);
+ while (lua_next(lua, abs_index)) {
+ lua_pop(lua, 1); // value
+ lua_pushnil(lua);
+ lua_rawset(lua, abs_index);
+ lua_pushnil(lua); // get 'new' first key
+ }
+}
+
+/**
+ * Prints a warning.
+ * @param s The warning to print.
+ */
+static void warn(const char *s) { printf("Warning: %s\n", s); }
+
+/**
+ * Returns whether or not the value of the key of the given global table is a
+ * function.
+ * @param table The table to check for key in.
+ * @param key String key to check for in table.
+ * @return TRUE for function, FALSE otherwise.
+ */
+int l_is2function(const char *table, const char *key) {
+ lua_getglobal(lua, table);
+ if (lua_istable(lua, -1)) {
+ lua_getfield(lua, -1, key);
+ lua_remove(lua, -2); // table
+ if (lua_isfunction(lua, -1)) return TRUE;
+ lua_pop(lua, 1); // non-function
+ } else lua_pop(lua, 1); // non-table
+ return FALSE;
+}
+
+/**
+ * Calls a Lua function with a number of arguments and expected return values.
+ * The last argument is at the stack top, and each argument in reverse order is
+ * one element lower on the stack with the Lua function being under the first
+ * argument.
+ * @param nargs The number of arguments to pass to the Lua function to call.
+ * @param retn Optional number of expected return values. Defaults to 0.
+ * @param keep_return Optional flag indicating whether or not to keep the return
+ * values at the top of the stack. If FALSE, discards the return values.
+ * Defaults to FALSE.
+ * @return FALSE if an error occured or the function returns false explicitly;
+ * TRUE otherwise.
+ */
+static int l_call_function(int nargs, int retn, int keep_return) {
+ int ret = lua_pcall(lua, nargs, retn, 0);
+ if (ret == 0) {
+ int result = (retn > 0) ? lua_toboolean(lua, -1) == 1 : TRUE;
+ if (retn > 0 && !keep_return) lua_pop(lua, retn); // retn
+ return result;
+ } else {
+ if (focused_editor)
+ l_emit_event("error", LUA_TSTRING, lua_tostring(lua, -1), -1);
+ else
+ printf("Lua Error: %s\n", lua_tostring(lua, -1));
+ lua_settop(lua, 0);
+ }
+ return FALSE;
+}
+
+/**
+ * Performs a Lua rawget on a table at a given stack index and returns an int.
+ * @param lua The Lua State.
+ * @param index The relative index of the table to rawget from.
+ * @param n The index in the table to rawget.
+ * @return int result of lua_rawgeti().
+ */
+static int l_rawgeti_int(lua_State *lua, int index, int n) {
+ lua_rawgeti(lua, index, n);
+ int ret = lua_tointeger(lua, -1);
+ lua_pop(lua, 1); // integer
+ return ret;
+}
+
+/**
+ * Performs a Lua rawget on a table at a given stack index and returns a string.
+ * @param lua The Lua State.
+ * @param index The relative index of the table to rawget from.
+ * @param k String key in the table to rawget.
+ * @return string result of lua_rawget().
+ */
+static const char *l_rawget_str(lua_State *lua, int index, const char *k) {
+ lua_pushstring(lua, k);
+ lua_rawget(lua, index);
+ const char *str = lua_tostring(lua, -1);
+ lua_pop(lua, 1); // string
+ return str;
+}
+
+/**
+ * Creates a GtkMenu from a table at the top of the Lua stack.
+ * The table has a key 'title' and a numeric list of subitems.
+ * @param lua The Lua State.
+ * @param callback A GCallback associated with each menu item.
+ * @param submenu Flag indicating whether or not this menu is a submenu.
+ * @return GtkWidget menu.
+ */
+GtkWidget *l_create_gtkmenu(lua_State *lua, GCallback callback, int submenu) {
+ GtkWidget *menu = gtk_menu_new(), *menu_item = 0, *submenu_root = 0;
+ const char *label;
+ lua_getfield(lua, -1, "title");
+ if (!lua_isnil(lua, -1) || submenu) { // title required for submenu
+ label = !lua_isnil(lua, -1) ? lua_tostring(lua, -1) : "notitle";
+ submenu_root = gtk_menu_item_new_with_mnemonic(label);
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(submenu_root), menu);
+ }
+ lua_pop(lua, 1); // title
+ lua_pushnil(lua);
+ while (lua_next(lua, -2)) {
+ if (lua_istable(lua, -1)) {
+ lua_getfield(lua, -1, "title");
+ int is_submenu = !lua_isnil(lua, -1);
+ lua_pop(lua, 1); // title
+ if (is_submenu)
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu),
+ l_create_gtkmenu(lua, callback, TRUE));
+ else
+ if (lua_objlen(lua, -1) == 2) {
+ lua_rawgeti(lua, -1, 1);
+ lua_rawgeti(lua, -2, 2);
+ label = lua_tostring(lua, -2);
+ int menu_id = lua_tointeger(lua, -1);
+ lua_pop(lua, 2); // label and id
+ if (label) {
+ if (g_str_has_prefix(label, "gtk-"))
+ menu_item = gtk_image_menu_item_new_from_stock(label, NULL);
+ else if (streq(label, "separator"))
+ menu_item = gtk_separator_menu_item_new();
+ else
+ menu_item = gtk_menu_item_new_with_mnemonic(label);
+ g_signal_connect(menu_item, "activate", callback,
+ GINT_TO_POINTER(menu_id));
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item);
+ }
+ } else warn("gtkmenu: { 'menu label', id_number } expected");
+ }
+ lua_pop(lua, 1); // value
+ }
+ return !submenu_root ? menu : submenu_root;
+}
+
+/**
+ * Convert the stack element at a specified index to a Scintilla w and/or l long
+ * parameter based on type.
+ * @param lua The Lua State.
+ * @param type The Lua type the top stack element is.
+ * @param arg_idx The initial stack index to start converting at. It is
+ * incremented as parameters are read from the stack.
+ * @return long for Scintilla.
+ */
+static long l_toscintillaparam(lua_State *lua, int type, int *arg_idx) {
+ if (type == tSTRING)
+ return (long)luaL_checkstring(lua, (*arg_idx)++);
+ else if (type == tBOOL)
+ return lua_toboolean(lua, (*arg_idx)++);
+ else if (type == tKEYMOD) {
+ int key = luaL_checkinteger(lua, (*arg_idx)++) & 0xFFFF;
+ return key | ((luaL_checkinteger(lua, (*arg_idx)++) &
+ (SCMOD_SHIFT | SCMOD_CTRL | SCMOD_ALT)) << 16);
+ } else if (type > tVOID && type < tBOOL)
+ return luaL_checklong(lua, (*arg_idx)++);
+ else
+ return 0;
+}
+
+/**
+ * Checks if the Scintilla document of the buffer table at the index specified
+ * is the document of the focused Scintilla window.
+ * Throws an error if the check is not satisfied.
+ * @param lua The Lua State.
+ * @param narg The relative stack position of the buffer table.
+ */
+static void l_check_focused_buffer(lua_State *lua, int narg) {
+ sptr_t cur_doc = SS(focused_editor, SCI_GETDOCPOINTER, 0, 0);
+ luaL_argcheck(lua, cur_doc == l_checkdocpointer(lua, narg), 1,
+ "the indexed Buffer is not the focused one");
+}
+
+/******************************************************************************/
+/********************** Lua Notifications/Event Handlers **********************/
+/******************************************************************************/
+
+/**
+ * Handles a Textadept event.
+ * @param s String event name.
+ * @param ... Optional arguments to pass to the handler. The variable argument
+ * list should contain Lua types followed by the data of that type to pass.
+ * The list is terminated by a -1.
+ * @return FALSE on error or if event returns false explicitly; TRUE otherwise.
+ */
+int l_emit_event(const char *s, ...) {
+ if (!l_is2function("events", "emit")) return FALSE;
+ lua_pushstring(lua, s);
+ int n = 1;
+ va_list ap;
+ va_start(ap, s);
+ int type = va_arg(ap, int);
+ while (type != -1) {
+ void *arg = va_arg(ap, void*);
+ if (type == LUA_TNIL)
+ lua_pushnil(lua);
+ else if (type == LUA_TBOOLEAN)
+ lua_pushboolean(lua, (long)arg);
+ else if (type == LUA_TNUMBER)
+ lua_pushinteger(lua, (long)arg);
+ else if (type == LUA_TSTRING)
+ lua_pushstring(lua, (char *)arg);
+ else if (type == LUA_TLIGHTUSERDATA || type == LUA_TTABLE) {
+ long ref = (long)arg;
+ lua_rawgeti(lua, LUA_REGISTRYINDEX, ref);
+ luaL_unref(lua, LUA_REGISTRYINDEX, ref);
+ } else warn("events.emit: ignored invalid argument type");
+ n++;
+ type = va_arg(ap, int);
+ }
+ va_end(ap);
+ return l_call_function(n, 1, FALSE);
+}
+
+#define l_pushscninteger(i, n) { \
+ lua_pushinteger(lua, i); \
+ lua_setfield(lua, -2, n); \
+}
+
+/**
+ * Handles a Scintilla notification.
+ * @param n The Scintilla notification struct.
+ */
+void l_emit_scnnotification(struct SCNotification *n) {
+ if (!l_is2function("events", "notification")) return;
+ lua_newtable(lua);
+ l_pushscninteger(n->nmhdr.code, "code");
+ l_pushscninteger(n->position, "position");
+ l_pushscninteger(n->ch, "ch");
+ l_pushscninteger(n->modifiers, "modifiers");
+ //l_pushscninteger(n->modificationType, "modification_type");
+ lua_pushstring(lua, n->text);
+ lua_setfield(lua, -2, "text");
+ //l_pushscninteger(n->length, "length");
+ //l_pushscninteger(n->linesAdded, "lines_added");
+ //l_pushscninteger(n->message, "message");
+ l_pushscninteger(n->wParam, "wParam");
+ l_pushscninteger(n->lParam, "lParam");
+ l_pushscninteger(n->line, "line");
+ //l_pushscninteger(n->foldLevelNow, "fold_level_now");
+ //l_pushscninteger(n->foldLevelPrev, "fold_level_prev");
+ l_pushscninteger(n->margin, "margin");
+ //l_pushscninteger(n->x, "x");
+ //l_pushscninteger(n->y, "y");
+ l_call_function(1, 0, FALSE);
+}
+
+/**
+ * Requests and pops up a context menu for the Scintilla view.
+ * @param event The mouse button event.
+ */
+void l_gui_popup_context_menu(GdkEventButton *event) {
+ lua_getglobal(lua, "gui");
+ if (lua_istable(lua, -1)) {
+ lua_getfield(lua, -1, "context_menu");
+ if (lua_isuserdata(lua, -1)) {
+ GtkWidget *menu = l_togtkwidget(lua, -1);
+ gtk_widget_show_all(menu);
+ gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
+ event ? event->button : 0,
+ gdk_event_get_time((GdkEvent *)event));
+ } else if (!lua_isnil(lua, -1))
+ warn("gui.context_menu: gtkmenu expected");
+ lua_pop(lua, 1); // gui.context_menu
+ } else lua_pop(lua, 1);
+}
+
+/******************************************************************************/
+/********************* Lua Functions *********************/
+/********************* (Stack Maintenence is Unnecessary) *********************/
+/******************************************************************************/
+
+/**
+ * Calls Scintilla with appropriate parameters and returs appropriate values.
+ * @param lua The Lua State.
+ * @param editor The Scintilla window to call.
+ * @param msg The integer message index to call Scintilla with.
+ * @param p1_type The Lua type of p1, the Scintilla w parameter.
+ * @param p2_type The Lua type of p2, the Scintilla l parameter.
+ * @param rt_type The Lua type of the Scintilla return parameter.
+ * @param arg The index on the Lua stack where arguments to Scintilla begin.
+ */
+static int l_call_scintilla(lua_State *lua, GtkWidget *editor, int msg,
+ int p1_type, int p2_type, int rt_type, int arg) {
+ long params[2] = {0, 0};
+ int params_needed = 2, len = 0, string_return = FALSE;
+ char *return_string = 0;
+
+ // SCI_PRIVATELEXERCALL iface has p1_type int, p2_type int. Change p2_type
+ // appropriately. See LPeg lexer API for more info.
+ if (msg == SCI_PRIVATELEXERCALL) {
+ p2_type = tSTRINGRESULT;
+ int c = luaL_checklong(lua, arg);
+ if (c == SCI_GETDIRECTFUNCTION || c == SCI_SETDOCPOINTER) p2_type = tINT;
+ else if (c == SCI_SETLEXERLANGUAGE) p2_type = tSTRING;
+ }
+
+ // Set the w and l parameters appropriately for Scintilla.
+ if (p1_type == tLENGTH && p2_type == tSTRING) {
+ params[0] = (long)lua_strlen(lua, arg);
+ params[1] = (long)luaL_checkstring(lua, arg);
+ params_needed = 0;
+ } else if (p2_type == tSTRINGRESULT) {
+ string_return = TRUE;
+ params_needed = (p1_type == tLENGTH) ? 0 : 1;
+ }
+ if (params_needed > 0) params[0] = l_toscintillaparam(lua, p1_type, &arg);
+ if (params_needed > 1) params[1] = l_toscintillaparam(lua, p2_type, &arg);
+ if (string_return) { // if a string return, create a buffer for it
+ len = SS(editor, msg, params[0], 0);
+ if (p1_type == tLENGTH) params[0] = len;
+ return_string = malloc(len + 1);
+ return_string[len] = '\0';
+ if (msg == SCI_GETTEXT || msg == SCI_GETSELTEXT || msg == SCI_GETCURLINE)
+ len--; // Scintilla appends '\0' for these messages; compensate
+ params[1] = (long)return_string;
+ }
+
+ // Send the message to Scintilla and return the appropriate values.
+ int result = SS(editor, msg, params[0], params[1]);
+ arg = lua_gettop(lua);
+ if (string_return) lua_pushlstring(lua, return_string, len);
+ if (rt_type == tBOOL) lua_pushboolean(lua, result);
+ if (rt_type > tVOID && rt_type < tBOOL) lua_pushinteger(lua, result);
+ g_free(return_string);
+ return lua_gettop(lua) - arg;
+}
+
+/**
+ * Calls a Scintilla buffer function with upvalues from a closure.
+ * @param lua The Lua State.
+ * @see l_buffer_mt_index
+ */
+static int l_call_buffer_function(lua_State *lua) {
+ GtkWidget *editor = l_togtkwidget(lua, lua_upvalueindex(1));
+ int buffer_func_table_idx = lua_upvalueindex(2);
+ int msg = l_rawgeti_int(lua, buffer_func_table_idx, 1);
+ int rt_type = l_rawgeti_int(lua, buffer_func_table_idx, 2);
+ int p1_type = l_rawgeti_int(lua, buffer_func_table_idx, 3);
+ int p2_type = l_rawgeti_int(lua, buffer_func_table_idx, 4);
+ return l_call_scintilla(lua, editor, msg, p1_type, p2_type, rt_type, 2);
+}
+
+/**
+ * Metatable index for a buffer table.
+ * If the key is a Scintilla buffer function, push a closure so it can be called
+ * as a function. If the key is a non-indexable buffer property, call Scintilla
+ * to get it. If the key is an indexible buffer property, push a table with a
+ * metatable to access buffer property indices.
+ * @param lua The Lua State.
+ */
+static int l_buffer_mt_index(lua_State *lua) {
+ const char *key = luaL_checkstring(lua, 2);
+
+ lua_getfield(lua, LUA_REGISTRYINDEX, "functions");
+ lua_getfield(lua, -1, key);
+ lua_remove(lua, -2); // buffer functions
+ if (lua_istable(lua, -1)) {
+ l_check_focused_buffer(lua, 1);
+ // Of the form { msg, rt_type, p1_type, p2_type }
+ lua_pushlightuserdata(lua, (GtkWidget *)focused_editor);
+ lua_insert(lua, lua_gettop(lua) - 1); // shift buffer functions down
+ lua_pushcclosure(lua, l_call_buffer_function, 2);
+ return 1;
+ } else lua_pop(lua, 1); // non-table
+
+ lua_getfield(lua, LUA_REGISTRYINDEX, "properties");
+ lua_getfield(lua, -1, key);
+ lua_remove(lua, -2); // buffer properties
+ if (lua_istable(lua, -1)) {
+ l_check_focused_buffer(lua, 1);
+ // Of the form { get_id, set_id, rt_type, p1_type }
+ int msg = l_rawgeti_int(lua, -1, 1); // getter
+ int rt_type = l_rawgeti_int(lua, -1, 3);
+ int p1_type = l_rawgeti_int(lua, -1, 4);
+ if (p1_type != tVOID) { // indexible property
+ sptr_t doc = SS(focused_editor, SCI_GETDOCPOINTER, 0, 0);
+ lua_newtable(lua);
+ lua_pushstring(lua, key);
+ lua_setfield(lua, -2, "property");
+ lua_pushinteger(lua, doc);
+ lua_setfield(lua, -2, "doc_pointer");
+ l_mt(lua, "_bufferp_mt", l_bufferp_mt_index, l_bufferp_mt_newindex);
+ return 1;
+ } else return l_call_scintilla(lua, focused_editor, msg, p1_type, tVOID,
+ rt_type, 2);
+ } else lua_pop(lua, 1); // non-table
+
+ lua_rawget(lua, 1);
+ return 1;
+}
+
+/**
+ * Helper function for the buffer property metatable.
+ * Throws an error when trying to write to a read-only property or when trying
+ * to read a write-only property.
+ * @param lua The Lua State.
+ * @param n 1 for getter property, 2 for setter.
+ * @param prop String property name.
+ * @param arg The index on the Lua stack where arguments to Scintilla begin.
+ * For setter properties, it is 3 because the index is not an argument. For
+ * getter and setter properties, it is 2 because the index is an argument.
+ */
+static int l_bufferp_mt_(lua_State *lua, int n, const char *prop, int arg) {
+ lua_getfield(lua, LUA_REGISTRYINDEX, "properties");
+ lua_getfield(lua, -1, prop);
+ lua_remove(lua, -2); // buffer properties
+ if (lua_istable(lua, -1)) {
+ l_check_focused_buffer(lua, 1);
+ int msg = l_rawgeti_int(lua, -1, n); // getter (1) or setter (2)
+ int rt_type = (n == 1) ? l_rawgeti_int(lua, -1, 3) : tVOID;
+ int p1_type = l_rawgeti_int(lua, -1, (n == 1) ? 4 : 3);
+ int p2_type = (n == 2) ? l_rawgeti_int(lua, -1, 4) : tVOID;
+ if (n == 2 &&
+ (p2_type != tVOID || (p2_type == tVOID && p1_type == tSTRING))) {
+ int temp = p1_type;
+ p1_type = p2_type;
+ p2_type = temp;
+ }
+ luaL_argcheck(lua, msg != 0, arg,
+ (n == 1) ? "write-only property" : "read-only property");
+ return l_call_scintilla(lua, focused_editor, msg, p1_type, p2_type, rt_type,
+ arg);
+ } else lua_pop(lua, 1); // non-table
+
+ (lua_gettop(lua) > 2) ? lua_rawset(lua, 1) : lua_rawget(lua, 1);
+ return 0;
+}
+
+static int l_buffer_mt_newindex(lua_State *lua) {
+ return l_bufferp_mt_(lua, 2, lua_tostring(lua, 2), 3);
+}
+
+static int l_bufferp_mt_index(lua_State *lua) {
+ return l_bufferp_mt_(lua, 1, l_rawget_str(lua, 1, "property"), 2);
+}
+
+static int l_bufferp_mt_newindex(lua_State *lua) {
+ return l_bufferp_mt_(lua, 2, l_rawget_str(lua, 1, "property"), 2);
+}
+
+static int l_view_mt_index(lua_State *lua) {
+ const char *key = lua_tostring(lua, 2);
+ if (streq(key, "doc_pointer"))
+ lua_pushinteger(lua, SS(l_checkview(lua, 1), SCI_GETDOCPOINTER, 0, 0));
+ else if (streq(key, "size")) {
+ GtkWidget *editor = l_checkview(lua, 1);
+ if (GTK_IS_PANED(gtk_widget_get_parent(editor))) {
+ int pos = gtk_paned_get_position(
+ GTK_PANED(gtk_widget_get_parent(editor)));
+ lua_pushinteger(lua, pos);
+ } else lua_pushnil(lua);
+ } else lua_rawget(lua, 1);
+ return 1;
+}
+
+static int l_view_mt_newindex(lua_State *lua) {
+ const char *key = lua_tostring(lua, 2);
+ if (streq(key, "size")) {
+ GtkWidget *pane = gtk_widget_get_parent(l_checkview(lua, 1));
+ int size = luaL_checkinteger(lua, 3);
+ if (size < 0) size = 0;
+ if (GTK_IS_PANED(pane)) gtk_paned_set_position(GTK_PANED(pane), size);
+ } else lua_rawset(lua, 1);
+ return 0;
+}
+
+static int l_gui_mt_index(lua_State *lua) {
+ const char *key = lua_tostring(lua, 2);
+ if (streq(key, "title"))
+ lua_pushstring(lua, gtk_window_get_title(GTK_WINDOW(window)));
+ else if (streq(key, "focused_doc_pointer"))
+ lua_pushinteger(lua, SS(focused_editor, SCI_GETDOCPOINTER, 0, 0));
+ else if (streq(key, "statusbar_text"))
+ lua_pushstring(lua, statusbar_text);
+ else if (streq(key, "clipboard_text")) {
+ char *text = gtk_clipboard_wait_for_text(
+ gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
+ if (text) {
+ lua_pushstring(lua, text);
+ g_free(text);
+ } else lua_pushstring(lua, "");
+ } else if (streq(key, "size")) {
+ lua_newtable(lua);
+ int width, height;
+ gtk_window_get_size(GTK_WINDOW(window), &width, &height);
+ lua_pushinteger(lua, width);
+ lua_rawseti(lua, -2, 1);
+ lua_pushinteger(lua, height);
+ lua_rawseti(lua, -2, 2);
+ } else lua_rawget(lua, 1);
+ return 1;
+}
+
+static int l_gui_mt_newindex(lua_State *lua) {
+ const char *key = lua_tostring(lua, 2);
+ if (streq(key, "title"))
+ gtk_window_set_title(GTK_WINDOW(window), lua_tostring(lua, 3));
+ else if (streq(key, "focused_doc_pointer") || streq(key, "clipboard_text"))
+ luaL_argerror(lua, 3, "read-only property");
+ else if (streq(key, "docstatusbar_text"))
+ set_statusbar_text(lua_tostring(lua, 3), 1);
+ else if (streq(key, "statusbar_text")) {
+ statusbar_text = !lua_isnil(lua, 3) ? lua_tostring(lua, 3) : "";
+ set_statusbar_text(statusbar_text, 0);
+ } else if (streq(key, "menubar")) {
+ luaL_argcheck(lua, lua_istable(lua, 3), 3, "table of menus expected");
+ GtkWidget *menubar = gtk_menu_bar_new();
+ lua_pushnil(lua);
+ while (lua_next(lua, 3)) {
+ luaL_argcheck(lua, lua_isuserdata(lua, -1), 3, "table of menus expected");
+ GtkWidget *menu_item = l_togtkwidget(lua, -1);
+ gtk_menu_bar_append(GTK_MENU_BAR(menubar), menu_item);
+ lua_pop(lua, 1); // value
+ }
+ set_menubar(menubar);
+ } else if (streq(key, "size")) {
+ luaL_argcheck(lua, lua_istable(lua, 3) && lua_objlen(lua, 3) == 2, 3,
+ "{ width, height } table expected");
+ int width = l_rawgeti_int(lua, 3, 1);
+ int height = l_rawgeti_int(lua, 3, 2);
+ if (width > 0 && height > 0)
+ gtk_window_resize(GTK_WINDOW(window), width, height);
+ } else lua_rawset(lua, 1);
+ return 0;
+}
+
+#define toggled(w) gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(w))
+static int l_find_mt_index(lua_State *lua) {
+ const char *key = lua_tostring(lua, 2);
+ if (streq(key, "find_entry_text"))
+ lua_pushstring(lua, gtk_entry_get_text(GTK_ENTRY(find_entry)));
+ else if (streq(key, "replace_entry_text"))
+ lua_pushstring(lua, gtk_entry_get_text(GTK_ENTRY(replace_entry)));
+ else if (streq(key, "match_case"))
+ lua_pushboolean(lua, toggled(match_case_opt));
+ else if (streq(key, "whole_word"))
+ lua_pushboolean(lua, toggled(whole_word_opt));
+ else if (streq(key, "lua"))
+ lua_pushboolean(lua, toggled(lua_opt));
+ else if (streq(key, "in_files"))
+ lua_pushboolean(lua, toggled(in_files_opt));
+ else
+ lua_rawget(lua, 1);
+ return 1;
+}
+
+#define toggle(w, b) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w), b)
+static int l_find_mt_newindex(lua_State *lua) {
+ const char *key = lua_tostring(lua, 2);
+ if (streq(key, "find_entry_text"))
+ gtk_entry_set_text(GTK_ENTRY(find_entry), lua_tostring(lua, 3));
+ else if (streq(key, "replace_entry_text"))
+ gtk_entry_set_text(GTK_ENTRY(replace_entry), lua_tostring(lua, 3));
+ else if (streq(key, "match_case"))
+ toggle(match_case_opt, lua_toboolean(lua, -1) ? TRUE : FALSE);
+ else if (streq(key, "whole_word"))
+ toggle(whole_word_opt, lua_toboolean(lua, -1) ? TRUE : FALSE);
+ else if (streq(key, "lua"))
+ toggle(lua_opt, lua_toboolean(lua, -1) ? TRUE : FALSE);
+ else if (streq(key, "in_files"))
+ toggle(in_files_opt, lua_toboolean(lua, -1) ? TRUE : FALSE);
+ else
+ lua_rawset(lua, 1);
+ return 0;
+}
+
+static int l_ce_mt_index(lua_State *lua) {
+ const char *key = lua_tostring(lua, 2);
+ if (streq(key, "entry_text"))
+ lua_pushstring(lua, gtk_entry_get_text(GTK_ENTRY(command_entry)));
+ else
+ lua_rawget(lua, 1);
+ return 1;
+}
+
+static int l_ce_mt_newindex(lua_State *lua) {
+ const char *key = lua_tostring(lua, 2);
+ if (streq(key, "entry_text"))
+ gtk_entry_set_text(GTK_ENTRY(command_entry), lua_tostring(lua, 3));
+ else
+ lua_rawset(lua, 1);
+ return 0;
+}
+
+/******************************************************************************/
+/****************** Lua CFunctions *******************/
+/****************** (For documentation, consult the LuaDoc) *******************/
+/******************************************************************************/
+
+static int l_cf_buffer_delete(lua_State *lua) {
+ l_check_focused_buffer(lua, 1);
+ sptr_t doc = l_checkdocpointer(lua, 1);
+ lua_getfield(lua, LUA_REGISTRYINDEX, "buffers");
+ if (lua_objlen(lua, -1) > 1)
+ l_goto_scintilla_buffer(focused_editor, -1, FALSE);
+ else
+ new_scintilla_buffer(focused_editor, TRUE, TRUE);
+ remove_scintilla_buffer(doc);
+ l_emit_event("buffer_deleted", -1);
+ return 0;
+}
+
+static int l_cf_buffer_new(lua_State *lua) {
+ new_scintilla_buffer(focused_editor, TRUE, TRUE);
+ lua_getfield(lua, LUA_REGISTRYINDEX, "buffers");
+ lua_rawgeti(lua, -1, lua_objlen(lua, -1));
+ return 1;
+}
+
+static int l_cf_buffer_text_range(lua_State *lua) {
+ l_check_focused_buffer(lua, 1);
+ struct Sci_TextRange tr;
+ tr.chrg.cpMin = luaL_checkinteger(lua, 2);
+ tr.chrg.cpMax = luaL_checkinteger(lua, 3);
+ luaL_argcheck(lua, tr.chrg.cpMin <= tr.chrg.cpMax, 3, "start > end");
+ int length = tr.chrg.cpMax - tr.chrg.cpMin;
+ char *text = malloc(length + 1);
+ tr.lpstrText = text;
+ SS(focused_editor, SCI_GETTEXTRANGE, 0, (long)(&tr));
+ lua_pushlstring(lua, text, length);
+ g_free(text);
+ return 1;
+}
+
+static int l_cf_view_focus(lua_State *lua) {
+ GtkWidget *editor = l_checkview(lua, 1);
+ // editor might be an old reference; GTK_IS_WIDGET checks for a valid widget
+ if (GTK_IS_WIDGET(editor)) gtk_widget_grab_focus(editor);
+ return 0;
+}
+
+static int l_cf_view_split(lua_State *lua) {
+ GtkWidget *editor = l_checkview(lua, 1);
+ int vertical = TRUE;
+ if (lua_gettop(lua) > 1) vertical = lua_toboolean(lua, 2) == 1;
+ split_window(editor, vertical);
+ lua_pushvalue(lua, 1); // old view
+ lua_getglobal(lua, "view"); // new view
+ return 2;
+}
+
+static int l_cf_view_unsplit(lua_State *lua) {
+ GtkWidget *editor = l_checkview(lua, 1);
+ lua_pushboolean(lua, unsplit_window(editor));
+ return 1;
+}
+
+#define child1(p) gtk_paned_get_child1(GTK_PANED(p))
+#define child2(p) gtk_paned_get_child2(GTK_PANED(p))
+#define editor_dpi(editor) \
+ l_get_docpointer_index(SS(editor, SCI_GETDOCPOINTER, 0, 0))
+
+void l_create_entry(lua_State *lua, GtkWidget *c1, GtkWidget *c2,
+ int vertical) {
+ lua_newtable(lua);
+ if (GTK_IS_PANED(c1))
+ l_create_entry(lua, child1(c1), child2(c1), GTK_IS_HPANED(c1) == 1);
+ else
+ lua_pushinteger(lua, editor_dpi(c1));
+ lua_rawseti(lua, -2, 1);
+ if (GTK_IS_PANED(c2))
+ l_create_entry(lua, child1(c2), child2(c2), GTK_IS_HPANED(c2) == 1);
+ else
+ lua_pushinteger(lua, editor_dpi(c2));
+ lua_rawseti(lua, -2, 2);
+ lua_pushboolean(lua, vertical);
+ lua_setfield(lua, -2, "vertical");
+ int size = gtk_paned_get_position(GTK_PANED(gtk_widget_get_parent(c1)));
+ lua_pushinteger(lua, size);
+ lua_setfield(lua, -2, "size");
+}
+
+static int l_cf_gui_get_split_table(lua_State *lua) {
+ lua_getfield(lua, LUA_REGISTRYINDEX, "views");
+ if (lua_objlen(lua, -1) > 1) {
+ GtkWidget *pane = gtk_widget_get_parent(focused_editor);
+ while (GTK_IS_PANED(gtk_widget_get_parent(pane)))
+ pane = gtk_widget_get_parent(pane);
+ l_create_entry(lua, child1(pane), child2(pane), GTK_IS_HPANED(pane) == 1);
+ } else lua_pushinteger(lua, editor_dpi(focused_editor));
+ return 1;
+}
+
+static int l_cf_gui_goto_(lua_State *lua, GtkWidget *editor, int buffer) {
+ int n = luaL_checkinteger(lua, 1);
+ int absolute = (lua_gettop(lua) > 1) ? lua_toboolean(lua, 2) == 1 : TRUE;
+ buffer ? l_goto_scintilla_buffer(editor, n, absolute)
+ : l_goto_scintilla_window(editor, n, absolute);
+ return 0;
+}
+
+// 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 l_cf_view_goto_buffer(lua_State *lua) {
+ GtkWidget *editor = l_checkview(lua, 1);
+ int switch_focus = editor != focused_editor;
+ GtkWidget *orig_focused_editor = focused_editor;
+ if (switch_focus) SS(editor, SCI_SETFOCUS, TRUE, 0);
+ lua_remove(lua, 1); // view table
+ l_cf_gui_goto_(lua, editor, TRUE);
+ if (switch_focus) {
+ SS(editor, SCI_SETFOCUS, FALSE, 0);
+ gtk_widget_grab_focus(orig_focused_editor);
+ }
+ return 0;
+}
+
+static int l_cf_gui_dialog(lua_State *lua) {
+ GCDialogType type = gcocoadialog_type(luaL_checkstring(lua, 1));
+ int i, j, k, n = lua_gettop(lua) - 1, argc = n;
+ for (i = 2; i < n + 2; i++)
+ if (lua_type(lua, i) == LUA_TTABLE) argc += lua_objlen(lua, i) - 1;
+ const char *argv[argc];
+ for (i = 0, j = 2; j < n + 2; j++)
+ if (lua_type(lua, j) == LUA_TTABLE) {
+ int len = lua_objlen(lua, j);
+ for (k = 1; k <= len; k++) {
+ lua_rawgeti(lua, j, k);
+ argv[i++] = luaL_checkstring(lua, j + 1);
+ lua_pop(lua, 1);
+ }
+ } else argv[i++] = luaL_checkstring(lua, j);
+ char *out = gcocoadialog(type, argc, argv);
+ lua_pushstring(lua, out);
+ free(out);
+ return 1;
+}
+
+static int l_cf_gui_goto_view(lua_State *lua) {
+ return l_cf_gui_goto_(lua, focused_editor, FALSE);
+}
+
+static void t_menu_activate(GtkWidget *menu, gpointer id) {
+ l_emit_event("menu_clicked", LUA_TNUMBER, GPOINTER_TO_INT(id), -1);
+}
+
+static int l_cf_gui_gtkmenu(lua_State *lua) {
+ luaL_checktype(lua, 1, LUA_TTABLE);
+ GtkWidget *menu = l_create_gtkmenu(lua, G_CALLBACK(t_menu_activate), FALSE);
+ lua_pushlightuserdata(lua, (GtkWidget *)menu);
+ return 1;
+}
+
+static int l_cf_string_iconv(lua_State *lua) {
+ size_t text_len = 0, conv_len = 0;
+ const char *text = luaL_checklstring(lua, 1, &text_len);
+ const char *to = luaL_checkstring(lua, 2);
+ const char *from = luaL_checkstring(lua, 3);
+ char *converted = g_convert(text, text_len, to, from, NULL, &conv_len, NULL);
+ if (converted) {
+ lua_pushlstring(lua, converted, conv_len);
+ g_free(converted);
+ } else luaL_error(lua, "Conversion failed");
+ return 1;
+}
+
+static int l_cf_quit(lua_State *lua) {
+ GdkEventAny event;
+ event.type = GDK_DELETE;
+ event.window = window->window;
+ event.send_event = TRUE;
+ gdk_event_put((GdkEvent *)(&event));
+ return 0;
+}
+
+static int l_cf_reset(lua_State *lua) {
+ l_emit_event("reset_before", -1);
+ l_init(0, NULL, TRUE);
+ lua_pushboolean(lua, TRUE);
+ lua_setglobal(lua, "RESETTING");
+ l_load_script("init.lua");
+ lua_pushnil(lua);
+ lua_setglobal(lua, "RESETTING");
+ l_set_view_global(focused_editor);
+ l_set_buffer_global(focused_editor);
+ l_emit_event("reset_after", -1);
+ return 0;
+}
+
+static int l_cf_find_focus(lua_State *lua) {
+ find_toggle_focus();
+ return 0;
+}
+
+#define emit(o, s) { \
+ g_signal_emit_by_name(G_OBJECT(o), s); \
+ return 0; \
+}
+static int l_cf_find_next(lua_State *lua) { emit(fnext_button, "clicked") }
+static int l_cf_find_prev(lua_State *lua) { emit(fprev_button, "clicked") }
+static int l_cf_find_replace(lua_State *lua) { emit(r_button, "clicked") }
+static int l_cf_find_replace_all(lua_State *lua) { emit(ra_button, "clicked") }
+
+static int l_cf_ce_focus(lua_State *lua) {
+ ce_toggle_focus();
+ return 0;
+}
+
+static int l_cf_ce_show_completions(lua_State *lua) {
+ luaL_checktype(lua, 1, LUA_TTABLE);
+ GtkEntryCompletion *completion = gtk_entry_get_completion(
+ GTK_ENTRY(command_entry));
+ GtkListStore *store = GTK_LIST_STORE(
+ gtk_entry_completion_get_model(completion));
+ gtk_list_store_clear(store);
+ lua_pushnil(lua);
+ while (lua_next(lua, 1)) {
+ if (lua_type(lua, -1) == LUA_TSTRING) {
+ GtkTreeIter iter;
+ gtk_list_store_append(store, &iter);
+ gtk_list_store_set(store, &iter, 0, lua_tostring(lua, -1), -1);
+ } else warn("command_entry.show_completions: non-string value ignored");
+ lua_pop(lua, 1); // value
+ }
+ gtk_entry_completion_complete(completion);
+ return 0;
+}