diff options
author | 2008-06-17 16:43:09 -0400 | |
---|---|---|
committer | 2008-06-17 16:43:09 -0400 | |
commit | 102c01ba2db51190c7c79592a6ea2fdb29d4d4fa (patch) | |
tree | 781576920293b05c7585e410f8dc9d3f2e48a44f | |
parent | e47fe640c0cd6826fa2b46d3b3fad7268766a093 (diff) | |
download | textadept-102c01ba2db51190c7c79592a6ea2fdb29d4d4fa.tar.gz textadept-102c01ba2db51190c7c79592a6ea2fdb29d4d4fa.zip |
Added Tab-completion to Lua Command Entry.
-rw-r--r-- | core/.command_entry.lua | 25 | ||||
-rw-r--r-- | core/.textadept.lua | 4 | ||||
-rw-r--r-- | core/events.lua | 68 | ||||
-rw-r--r-- | core/ext/command_entry.lua | 36 | ||||
-rw-r--r-- | core/ext/key_commands.lua | 2 | ||||
-rw-r--r-- | init.lua | 1 | ||||
-rw-r--r-- | src/lua_interface.c | 67 | ||||
-rw-r--r-- | src/textadept.c | 84 | ||||
-rw-r--r-- | src/textadept.h | 8 |
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 } @@ -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); |