aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/.command_entry.lua25
-rw-r--r--core/.textadept.lua4
-rw-r--r--core/events.lua68
-rw-r--r--core/ext/command_entry.lua36
-rw-r--r--core/ext/key_commands.lua2
-rw-r--r--init.lua1
-rw-r--r--src/lua_interface.c67
-rw-r--r--src/textadept.c84
-rw-r--r--src/textadept.h8
9 files changed, 205 insertions, 90 deletions
diff --git a/core/.command_entry.lua b/core/.command_entry.lua
new file mode 100644
index 00000000..6de23763
--- /dev/null
+++ b/core/.command_entry.lua
@@ -0,0 +1,25 @@
+-- Copyright 2007 Mitchell mitchell<att>caladbolg.net. See LICENSE.
+-- This is a DUMMY FILE used for making LuaDoc for built-in functions in the
+-- global textadept.command_entry table.
+
+---
+-- Textadept's Lua command entry.
+-- [Dummy file]
+module('textadept.command_entry')
+
+---
+-- Textadept's Lua command entry table.
+-- @class table
+-- @name textadept.command_entry
+-- @field entry_text The text in the entry.
+command_entry = {}
+
+--- Focuses the command entry.
+function focus() end
+
+---
+-- Gets completions for the current command_entry text.
+-- This function is called internally and shouldn't be called by script.
+-- @param command The command to complete.
+-- @return sorted table of completions
+function get_completions_for(command) end
diff --git a/core/.textadept.lua b/core/.textadept.lua
index 011c70aa..d58aed97 100644
--- a/core/.textadept.lua
+++ b/core/.textadept.lua
@@ -57,10 +57,6 @@ function goto_view(n, absolute) end
function get_split_table() end
---
--- Focuses the command entry.
-function focus_command() end
-
----
-- Creates a GTK menu, returning the userdata.
-- @param menu_table A table defining the menu. It is an ordered list of strings
-- that are handled as follows:
diff --git a/core/events.lua b/core/events.lua
index fffa5aca..7a1ed360 100644
--- a/core/events.lua
+++ b/core/events.lua
@@ -383,74 +383,6 @@ add_handler('quit',
return true
end)
-
----
--- Shows completions for the current command_entry text.
--- Opens a new buffer (if one hasn't already been opened) for printing possible
--- completions.
--- @param command The command to complete.
-function show_completions(command)
- local textadept = textadept
- local cmpl_buffer, goto
- if buffer.shows_completions then
- cmpl_buffer = buffer
- else
- for index, buffer in ipairs(textadept.buffers) do
- if buffer.shows_completions then
- cmpl_buffer = index
- goto = buffer.doc_pointer ~= textadept.focused_doc_pointer
- elseif buffer.doc_pointer == textadept.focused_doc_pointer then
- textadept.prev_buffer = index
- end
- end
- if not cmpl_buffer then
- cmpl_buffer = textadept.new_buffer()
- cmpl_buffer.shows_completions = true
- else
- if goto then view:goto(cmpl_buffer) end
- cmpl_buffer = textadept.buffers[cmpl_buffer]
- end
- end
- cmpl_buffer:clear_all()
-
- local substring = command:match('[%w_.:]+$') or ''
- local path, o, prefix = substring:match('^([%w_.:]-)([.:]?)([%w_]*)$')
- local ret, tbl = pcall(loadstring('return ('..path..')'))
- if not ret then tbl = getfenv(0) end
- if type(tbl) ~= 'table' then return end
- local cmpls = {}
- for k in pairs(tbl) do
- if type(k) == 'string' and k:match('^'..prefix) then
- cmpls[#cmpls + 1] = k
- end
- end
- if path == 'buffer' then
- if o == ':' then
- for f in pairs(textadept.buffer_functions) do
- if f:match('^'..prefix) then cmpls[#cmpls + 1] = f end
- end
- else
- for p in pairs(textadept.buffer_properties) do
- if p:match('^'..prefix) then cmpls[#cmpls + 1] = p end
- end
- end
- end
- table.sort(cmpls)
- for _, cmpl in ipairs(cmpls) do cmpl_buffer:add_text(cmpl..'\n') end
- cmpl_buffer:set_save_point()
-end
-
----
--- Hides the completion buffer if it is currently focused and restores the
--- previous focused buffer (if possible).
-function hide_completions()
- local textadept = textadept
- if buffer.shows_completions then
- buffer:close()
- if textadept.prev_buffer then view:goto_buffer(textadept.prev_buffer) end
- end
-end
-
---
-- Default error handler.
-- Opens a new buffer (if one hasn't already been opened) for printing errors.
diff --git a/core/ext/command_entry.lua b/core/ext/command_entry.lua
new file mode 100644
index 00000000..8c796610
--- /dev/null
+++ b/core/ext/command_entry.lua
@@ -0,0 +1,36 @@
+-- Copyright 2007-2008 Mitchell mitchell<att>caladbolg.net. See LICENSE.
+
+local ce = textadept.command_entry
+
+---
+-- Gets completions for the current command_entry text.
+-- This function is called internally and shouldn't be called by script.
+-- @param command The command to complete.
+-- @return sorted table of completions
+function ce.get_completions_for(command)
+ local textadept = textadept
+ local substring = command:match('[%w_.:]+$') or ''
+ local path, o, prefix = substring:match('^([%w_.:]-)([.:]?)([%w_]*)$')
+ local ret, tbl = pcall(loadstring('return ('..path..')'))
+ if not ret then tbl = getfenv(0) end
+ if type(tbl) ~= 'table' then return end
+ local cmpls = {}
+ for k in pairs(tbl) do
+ if type(k) == 'string' and k:match('^'..prefix) then
+ cmpls[#cmpls + 1] = k
+ end
+ end
+ if path == 'buffer' then
+ if o == ':' then
+ for f in pairs(textadept.buffer_functions) do
+ if f:match('^'..prefix) then cmpls[#cmpls + 1] = f end
+ end
+ else
+ for p in pairs(textadept.buffer_properties) do
+ if p:match('^'..prefix) then cmpls[#cmpls + 1] = p end
+ end
+ end
+ end
+ table.sort(cmpls)
+ return cmpls
+end
diff --git a/core/ext/key_commands.lua b/core/ext/key_commands.lua
index ca22ff37..83d88f47 100644
--- a/core/ext/key_commands.lua
+++ b/core/ext/key_commands.lua
@@ -207,7 +207,7 @@ keys.ct.v.t = { toggle_setting, 'use_tabs' }
keys.ct.v.w = { toggle_setting, 'view_ws' }
-- Miscellaneous commands.
-keys.cc = { t.focus_command }
+keys.cc = { t.command_entry.focus }
local m_events = t.events
keys.cab = { m_events.handle, 'call_tip_click', 1 }
keys.caf = { m_events.handle, 'call_tip_click', 2 }
diff --git a/init.lua b/init.lua
index 391e2db9..b57a810f 100644
--- a/init.lua
+++ b/init.lua
@@ -2,6 +2,7 @@
require 'ext/pm'
require 'ext/find'
+require 'ext/command_entry'
require 'ext/mime_types'
require 'ext/keys'
diff --git a/src/lua_interface.c b/src/lua_interface.c
index 34376f75..86709a5a 100644
--- a/src/lua_interface.c
+++ b/src/lua_interface.c
@@ -31,7 +31,8 @@ LF l_buffer_mt_index(LS *lua), l_buffer_mt_newindex(LS *lua),
l_view_mt_index(LS *lua), l_view_mt_newindex(LS *lua),
l_ta_mt_index(LS *lua), l_ta_mt_newindex(LS *lua),
l_pm_mt_index(LS *lua), l_pm_mt_newindex(LS *lua),
- l_find_mt_index(LS *lua), l_find_mt_newindex(LS *lua);
+ l_find_mt_index(LS *lua), l_find_mt_newindex(LS *lua),
+ l_ce_mt_index(LS *lua), l_ce_mt_newindex(LS *lua);
LF l_cf_ta_buffer_new(LS *lua),
l_cf_buffer_delete(LS *lua),
@@ -40,14 +41,14 @@ LF l_cf_ta_buffer_new(LS *lua),
l_cf_view_focus(LS *lua),
l_cf_view_split(LS *lua), l_cf_view_unsplit(LS *lua),
l_cf_ta_get_split_table(LS *lua),
- l_cf_ta_focus_command(LS *lua),
l_cf_ta_goto_window(LS *lua),
l_cf_view_goto_buffer(LS *lua),
l_cf_ta_gtkmenu(LS *lua),
l_cf_ta_popupmenu(LS *lua),
l_cf_ta_reset(LS *lua),
l_cf_pm_focus(LS *lua), l_cf_pm_clear(LS *lua), l_cf_pm_activate(LS *lua),
- l_cf_find_focus(LS *lua);
+ l_cf_find_focus(LS *lua),
+ l_cf_ce_focus(LS *lua);
const char
*views_dne = "textadept.views doesn't exist or was overwritten.",
@@ -95,10 +96,13 @@ void l_init(int argc, char **argv, bool reinit) {
l_cfunc(lua, l_cf_find_focus, "focus");
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_mt(lua, "_ce_mt", l_ce_mt_index, l_ce_mt_newindex);
+ lua_setfield(lua, -2, "command_entry");
l_cfunc(lua, l_cf_ta_buffer_new, "new_buffer");
l_cfunc(lua, l_cf_ta_goto_window, "goto_view");
l_cfunc(lua, l_cf_ta_get_split_table, "get_split_table");
- l_cfunc(lua, l_cf_ta_focus_command, "focus_command");
l_cfunc(lua, l_cf_ta_gtkmenu, "gtkmenu");
l_cfunc(lua, l_cf_ta_popupmenu, "popupmenu");
l_cfunc(lua, l_cf_ta_reset, "reset");
@@ -700,6 +704,40 @@ void l_ta_command(const char *command) {
} else l_handle_error(lua, "Error executing command.");
}
+// Command Entry
+
+/**
+ * Requests completions for the Command Entry Completion.
+ * @param entry_text The text in the Command Entry.
+ * @see l_cec_populate
+ */
+bool l_cec_get_completions_for(const char *entry_text) {
+ if (!l_is_ta_table_function("command_entry", "get_completions_for"))
+ return false;
+ lua_pushstring(lua, entry_text);
+ return l_call_function(1, 1, true);
+}
+
+/**
+ * Populates the Command Entry Completion with the contents of a Lua table at
+ * the stack top.
+ * @see l_cec_get_completions_for
+ */
+void l_cec_populate() {
+ GtkTreeIter iter;
+ if (!lua_istable(lua, -1))
+ return warn("command_entry.get_completions_for return not a table.");
+ gtk_tree_store_clear(cec_store);
+ lua_pushnil(lua);
+ while (lua_next(lua, -2)) {
+ if (lua_type(lua, -1) == LUA_TSTRING) {
+ gtk_tree_store_append(cec_store, &iter, NULL);
+ gtk_tree_store_set(cec_store, &iter, 0, lua_tostring(lua, -1), -1);
+ } else warn("command_entry.get_completions_for: string value expected.");
+ lua_pop(lua, 1); // value
+ } lua_pop(lua, 1); // returned table
+}
+
// Project Manager
/**
@@ -729,6 +767,7 @@ bool l_pm_get_contents_for(const char *entry_text, bool expanding) {
* @param initial_iter The initial GtkTreeIter. If not NULL, it is a treenode
* being expanded and the contents will be added to that expanding node.
* Defaults to NULL.
+ * @see l_pm_get_contents_for
*/
void l_pm_populate(GtkTreeIter *initial_iter) {
GtkTreeIter iter, child;
@@ -1125,6 +1164,22 @@ LF l_find_mt_newindex(LS *lua) {
return 0;
}
+LF l_ce_mt_index(LS *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;
+}
+
+LF l_ce_mt_newindex(LS *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.
LF l_cf_ta_buffer_new(LS *lua) {
@@ -1234,8 +1289,6 @@ LF l_cf_ta_get_split_table(LS *lua) {
return 1;
}
-LF l_cf_ta_focus_command(LS *) { command_toggle_focus(); return 0; }
-
LF l_cf_ta_goto_(LS *lua, GtkWidget *editor, bool buffer=true) {
int n = static_cast<int>(luaL_checkinteger(lua, 1));
bool absolute = lua_gettop(lua) > 1 ? lua_toboolean(lua, 2) == 1 : true;
@@ -1304,3 +1357,5 @@ LF l_cf_pm_activate(LS *) {
g_signal_emit_by_name(G_OBJECT(pm_entry), "activate"); return 0;
}
LF l_cf_find_focus(LS *) { find_toggle_focus(); return 0; }
+
+LF l_cf_ce_focus(LS *) { ce_toggle_focus(); return 0; }
diff --git a/src/textadept.c b/src/textadept.c
index 084d616a..e3024456 100644
--- a/src/textadept.c
+++ b/src/textadept.c
@@ -8,9 +8,15 @@
GtkWidget
*window, *focused_editor, *command_entry,
*menubar, *statusbar, *docstatusbar;
+GtkEntryCompletion *command_entry_completion;
+GtkTreeStore *cec_store;
static void c_activated(GtkWidget *widget, gpointer);
static bool c_keypress(GtkWidget *widget, GdkEventKey *event, gpointer);
+static int cec_match_func(GtkEntryCompletion *, const char *, GtkTreeIter *,
+ gpointer);
+static bool cec_match_selected(GtkEntryCompletion *, GtkTreeModel *model,
+ GtkTreeIter *iter, gpointer);
static void t_notification(GtkWidget*, gint, gpointer lParam, gpointer);
static void t_command(GtkWidget *editor, gint wParam, gpointer, gpointer);
static bool t_keypress(GtkWidget*, GdkEventKey *event, gpointer);
@@ -85,36 +91,59 @@ void create_ui() {
signal(window, "delete_event", w_exit);
signal(window, "focus-in-event", w_focus);
signal(window, "key_press_event", w_keypress);
+
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 *pane = gtk_hpaned_new();
gtk_box_pack_start(GTK_BOX(vbox), pane, true, true, 0);
+
GtkWidget *pm = pm_create_ui();
gtk_paned_add1(GTK_PANED(pane), pm);
+
GtkWidget *hbox = gtk_hbox_new(false, 0);
gtk_paned_add2(GTK_PANED(pane), hbox);
+
GtkWidget *editor = new_scintilla_window();
gtk_box_pack_start(GTK_BOX(hbox), editor, true, true, 0);
+
GtkWidget *find = find_create_ui();
gtk_box_pack_start(GTK_BOX(vbox), find, false, false, 5);
+
GtkWidget *hboxs = gtk_hbox_new(false, 0);
gtk_box_pack_start(GTK_BOX(vbox), hboxs, false, false, 0);
+
statusbar = gtk_statusbar_new();
gtk_statusbar_push(GTK_STATUSBAR(statusbar), 0, "");
gtk_statusbar_set_has_resize_grip(GTK_STATUSBAR(statusbar), false);
gtk_box_pack_start(GTK_BOX(hboxs), statusbar, true, true, 0);
+
command_entry = gtk_entry_new();
gtk_widget_set_name(command_entry, "textadept-command-entry");
signal(command_entry, "activate", c_activated);
signal(command_entry, "key_press_event", c_keypress);
g_object_set(G_OBJECT(command_entry), "width-request", 200, NULL);
gtk_box_pack_start(GTK_BOX(hboxs), command_entry, true, true, 0);
+
+ command_entry_completion = gtk_entry_completion_new();
+ signal(command_entry_completion, "match-selected", cec_match_selected);
+ gtk_entry_completion_set_match_func(command_entry_completion, cec_match_func,
+ NULL, NULL);
+ gtk_entry_completion_set_popup_set_width(command_entry_completion, false);
+ gtk_entry_completion_set_text_column(command_entry_completion, 0);
+ cec_store = gtk_tree_store_new(1, G_TYPE_STRING);
+ gtk_entry_completion_set_model(command_entry_completion,
+ GTK_TREE_MODEL(cec_store));
+ gtk_entry_set_completion(GTK_ENTRY(command_entry), command_entry_completion);
+
docstatusbar = gtk_statusbar_new();
gtk_statusbar_push(GTK_STATUSBAR(docstatusbar), 0, "");
g_object_set(G_OBJECT(docstatusbar), "width-request", 400, NULL);
gtk_box_pack_start(GTK_BOX(hboxs), docstatusbar, false, false, 0);
+
gtk_widget_show_all(window);
gtk_widget_hide(menubar); // hide initially
gtk_widget_hide(findbox); // hide initially
@@ -348,7 +377,7 @@ void set_docstatusbar_text(const char *text) {
* Toggles focus between a Scintilla window and the Lua command entry.
* When the entry is visible, the statusbars are temporarily hidden.
*/
-void command_toggle_focus() {
+void ce_toggle_focus() {
if (!GTK_WIDGET_HAS_FOCUS(command_entry)) {
gtk_widget_hide(statusbar); gtk_widget_hide(docstatusbar);
gtk_widget_show(command_entry);
@@ -368,34 +397,71 @@ void command_toggle_focus() {
* Generates a 'hide_completions' event.
*/
static void c_activated(GtkWidget *widget, gpointer) {
- l_handle_event("hide_completions");
l_ta_command(gtk_entry_get_text(GTK_ENTRY(widget)));
- command_toggle_focus();
+ ce_toggle_focus();
}
/**
* Signal for a keypress inside the Lua command entry.
* Currently handled keypresses:
* - Escape - Hide the completion buffer if it is open.
- * - Tab - Show completion buffer.
- * Generates a 'hide_completions' or 'show_completions' event as necessary.
+ * - Tab - Display possible completions.
*/
static bool c_keypress(GtkWidget *widget, GdkEventKey *event, gpointer) {
if (event->state == 0)
switch(event->keyval) {
case 0xff1b:
- l_handle_event("hide_completions");
- command_toggle_focus();
+ ce_toggle_focus();
return true;
case 0xff09:
- l_handle_event("show_completions",
- gtk_entry_get_text(GTK_ENTRY(widget)));
+ if (l_cec_get_completions_for(gtk_entry_get_text(GTK_ENTRY(widget)))) {
+ l_cec_populate();
+ gtk_entry_completion_complete(command_entry_completion);
+ }
return true;
}
return false;
}
/**
+ * Sets every item in the Command Entry Model to be a match.
+ * For each attempted completion, the Command Entry Model is filled with the
+ * results from a call to Lua to make a list of possible completions. Therefore,
+ * every item in the list is valid.
+ */
+static int cec_match_func(GtkEntryCompletion*, const char*, GtkTreeIter*,
+ gpointer) {
+ return true;
+}
+
+/**
+ * Enters the requested completion text into the Command Entry.
+ * The last word at the cursor is replaced with the completion. A word consists
+ * of any alphanumeric character or underscore.
+ */
+static bool cec_match_selected(GtkEntryCompletion*, GtkTreeModel *model,
+ GtkTreeIter *iter, gpointer) {
+ const char *entry_text = gtk_entry_get_text(GTK_ENTRY(command_entry));
+ const char *p = entry_text + strlen(entry_text) - 1;
+ while ((*p >= 'A' && *p <= 'Z') || (*p >= 'a' && *p <= 'z') ||
+ (*p >= '0' && *p <= '9') || *p == '_') {
+ g_signal_emit_by_name(G_OBJECT(command_entry), "move-cursor",
+ GTK_MOVEMENT_VISUAL_POSITIONS, -1, true, 0);
+ p--;
+ }
+ if (p < entry_text + strlen(entry_text) - 1)
+ g_signal_emit_by_name(G_OBJECT(command_entry), "backspace", 0);
+
+ char *text;
+ gtk_tree_model_get(model, iter, 0, &text, -1);
+ g_signal_emit_by_name(G_OBJECT(command_entry), "insert-at-cursor", text, 0);
+ g_free(text);
+
+ gtk_tree_store_clear(cec_store);
+ return true;
+}
+
+/**
* Signal for a Scintilla notification.
*/
static void t_notification(GtkWidget*, gint, gpointer lParam, gpointer) {
diff --git a/src/textadept.h b/src/textadept.h
index 70f83447..617d5766 100644
--- a/src/textadept.h
+++ b/src/textadept.h
@@ -23,7 +23,8 @@ extern GtkWidget
*window, *focused_editor, *command_entry,
*pm_container, *pm_entry, *pm_view,
*findbox, *find_entry, *replace_entry;
-extern GtkTreeStore *pm_store;
+extern GtkEntryCompletion *command_entry_completion;
+extern GtkTreeStore *cec_store, *pm_store;
extern lua_State *lua;
static const char *textadept_home = "/usr/share/textadept/";
@@ -45,7 +46,7 @@ void resize_split(GtkWidget *editor, int pos, bool increment=true);
void set_menubar(GtkWidget *menubar);
void set_statusbar_text(const char *text);
void set_docstatusbar_text(const char *text);
-void command_toggle_focus();
+void ce_toggle_focus();
void set_default_editor_properties(ScintillaObject *sci);
void set_default_buffer_properties(ScintillaObject *sci);
@@ -69,6 +70,9 @@ bool l_handle_keypress(int keyval, bool shift, bool control, bool alt);
void l_handle_scnnotification(SCNotification *n);
void l_ta_command(const char *command);
+bool l_cec_get_completions_for(const char *entry_text);
+void l_cec_populate();
+
bool l_pm_get_contents_for(const char *entry_text, bool expanding=false);
void l_pm_populate(GtkTreeIter *initial_iter=NULL);
void l_pm_get_full_path(GtkTreePath *path);