diff options
-rw-r--r-- | core/.command_entry.lua | 7 | ||||
-rw-r--r-- | core/.find.lua | 58 | ||||
-rw-r--r-- | core/.pm.lua | 38 | ||||
-rw-r--r-- | core/events.lua | 75 | ||||
-rw-r--r-- | core/ext/command_entry.lua | 69 | ||||
-rw-r--r-- | core/ext/find.lua | 58 | ||||
-rw-r--r-- | core/ext/pm.lua | 53 | ||||
-rw-r--r-- | core/ext/pm/modules_browser.lua | 6 | ||||
-rw-r--r-- | src/Makefile | 4 | ||||
-rw-r--r-- | src/lua_interface.c | 524 | ||||
-rw-r--r-- | src/textadept.c | 138 | ||||
-rw-r--r-- | src/textadept.h | 19 |
12 files changed, 478 insertions, 571 deletions
diff --git a/core/.command_entry.lua b/core/.command_entry.lua index 3d5e24fd..c1a06f45 100644 --- a/core/.command_entry.lua +++ b/core/.command_entry.lua @@ -16,10 +16,3 @@ command_entry = { entry_text = nil } --- 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/.find.lua b/core/.find.lua index bea87c36..40829cb1 100644 --- a/core/.find.lua +++ b/core/.find.lua @@ -33,7 +33,7 @@ module('textadept.find') --- -- Textadept's find table. -- @class table --- @name textadept.find +-- @name find -- @field find_entry_text The text in the find entry. -- @field replace_entry_text The text in the replace entry. -- @field match_case Flag indicating whether or not case-sensitive search is @@ -53,68 +53,20 @@ find = { function focus() end --- --- [Local table] Text escape sequences with their associated characters. --- @class table --- @name escapes -local escapes = {} - ---- --- Finds and selects text in the current buffer. --- This is used by the find dialog. It is recommended to use the --- buffer:search_in_target() or buffer:search_next() and buffer:search_prev() --- functions for scripting. --- @param text The text to find. --- @param next Flag indicating whether or not the search direction is forward. --- @param flags Search flags. This is a number mask of 4 flags: match case (2), --- whole word (4), Lua pattern (8), and in files (16) joined with binary OR. --- If nil, this is determined based on the checkboxes in the find box. --- @param nowrap Flag indicating whether or not the search won't wrap. --- @param wrapped Utility flag indicating whether or not the search has wrapped --- for displaying useful statusbar information. This flag is used and set --- internally, and should not be set otherwise. --- @return position of the found text or -1 -function find.find(text, next, flags, nowrap, wrapped) end - ---- --- Replaces found text. --- This function is used by the find dialog. It is not recommended to call it --- via scripts. --- textadept.find.find is called first, to select any found text. The selected --- text is then replaced by the specified replacement text. --- This function ignores 'Find in Files'. --- @param rtext The text to replace found text with. It can contain both Lua --- capture items (%n where 1 <= n <= 9) for Lua pattern searches and %() --- sequences for embedding Lua code for any search. --- @see find.find -function find.replace(rtext) end - ---- --- Replaces all found text. --- This function is used by the find dialog. It is not recommended to call it --- via scripts. --- If any text is selected, all found text in that selection is replaced. --- This function ignores 'Find in Files'. --- @param ftext The text to find. --- @param rtext The text to replace found text with. --- @param flags The number mask identical to the one in 'find'. --- @see find.find -function find.replace_all(ftext, rtext, flags) end - ---- -- Mimicks a press of the 'Find Next' button in the Find box. -function find.call_find_next() end +function find_next() end --- -- Mimicks a press of the 'Find Prev' button in the Find box. -function find.call_find_prev() end +function find_prev() end --- -- Mimicks a press of the 'Replace' button in the Find box. -function find.call_replace() end +function replace() end --- -- Mimicks a press of the 'Replace All' button in the Find box. -function find.call_replace_all() end +function replace_all() end --- -- Goes to the next or previous file found relative to the file diff --git a/core/.pm.lua b/core/.pm.lua index 3b1ffbd8..5371d286 100644 --- a/core/.pm.lua +++ b/core/.pm.lua @@ -61,12 +61,6 @@ module('textadept.pm') -- current GtkTreePath). pm = { entry_text = nil, width = nil, cursor = nil } ---- Focuses the project manager entry. -function focus() end - ---- Clears the project manager contents. -function clear() end - --- Requests the project manager to get its contents based on its entry text. function activate() end @@ -75,3 +69,35 @@ function activate() end -- manager entry combo box. -- @param prefix The text to add. function add_browser(prefix) end + +--- Clears the project manager contents. +function clear() end + +--- +-- Adds contents to the Project Manager view. +-- @param contents Table of tables to for display in the treeview (single +-- level). Each key in the return table is the treeview item's ID. The table +-- value has the following recognized fields: +-- * parent - boolean value indicating if this entry can contain children. +-- If true, an expanding arrow is displayed next to the entry. +-- * pixbuf - a string representing a GTK stock-id whose icon is displayed +-- next to an entry. +-- * text - the entry's Pango marked-up display text. +-- Note that only a SINGLE level of data needs to be returned. When parents +-- are expanded, this function is called again to get that level of data. +-- @param parent String representation of parent GtkTreePath to add the child +-- contents to. +function fill(contents, parent) + +--- Focuses the project manager entry. +function focus() end + +--- +-- Shows a context menu. +-- @param menu Table of menu items. It consists of an ordered list of strings +-- to be used to construct a context menu. The strings are handled as follows: +-- * 'gtk-*' - a stock menu item is created based on the GTK stock-id. +-- * 'separator' - a menu separator item is created. +-- * Otherwise a regular menu item with a mnemonic is created. +-- @param event The GDK event associated with the context menu request. +function show_context_menu(menu, event) end diff --git a/core/events.lua b/core/events.lua index 3d0cadee..28e446c9 100644 --- a/core/events.lua +++ b/core/events.lua @@ -71,7 +71,35 @@ module('textadept.events', package.seeall) -- alt: flag indicating whether or not alt is pressed. -- menu_clicked(menu_id) -- menu_id: the numeric ID of the menu item. --- pm_view_filled() +-- pm_contents_request(full_path, expanding) +-- full_path: a numerically indexed table of treeview item parents. The +-- first index contains the text of pm_entry. Subsequent indexes contain +-- the ID's of parents of the child requested for expanding (if any). +-- expanding: indicates if the contents of a parent are being requested. +-- pm_item_selected(selected_item, gdkevent) +-- selected_item: identical to 'full_path' for 'pm_contents_request' event. +-- gdkevent: the GDK event associated with the request. It must be passed to +-- pm.show_context_menu() +-- pm_context_menu_request(selected_item) +-- selected_item: identical to 'full_path' for 'pm_contents_request' event. +-- pm_menu_clicked(menu_id, selected_item) +-- menu_id: the numeric ID for the menu item. +-- selected_item: identical to 'full_path' for 'pm_contents_request' event. +-- find(text, next) +-- text: the text to find. +-- next: flag indicating whether or not the search direction is forward. +-- replace(text) +-- text: the text to replace the current selection with. It can contain both +-- Lua capture items (%n where 1 <= n <= 9) for Lua pattern searches and %() +-- sequences for embedding Lua code for any search. +-- replace_all(find_text, repl_text) +-- find_text: the text to find. +-- repl_text: the text to replace found text with. +-- find_keypress(code) +-- code: the key code. +-- command_entry_completions_request() +-- command_entry_keypress(code) +-- code: the key code. local events = textadept.events @@ -163,41 +191,6 @@ function notification(n) if f then f(n) end end --- Textadept events. -function buffer_new() - return handle('buffer_new') -end -function buffer_deleted() - return handle('buffer_deleted') -end -function buffer_before_switch() - return handle('buffer_before_switch') -end -function buffer_after_switch() - return handle('buffer_after_switch') -end -function view_new() - return handle('view_new') -end -function view_before_switch() - return handle('view_before_switch') -end -function view_after_switch() - return handle('view_after_switch') -end -function quit() - return handle('quit') -end -function keypress(code, shift, control, alt) - return handle('keypress', code, shift, control, alt) -end -function menu_clicked(menu_id_str) - return handle('menu_clicked', tonumber(menu_id_str)) -end -function pm_view_filled() - return handle('pm_view_filled') -end - -- Default handlers to follow. add_handler('view_new', @@ -478,11 +471,9 @@ add_handler('quit', end) if MAC then - function appleevent_odoc(uri) return handle('uri_dropped', 'file://'..uri) end + add_handler('appleevent_odoc', + function(uri) return handle('uri_dropped', 'file://'..uri) end) end ---- --- Default error handler. --- Prints the errors to an error buffer. --- @param ... Error strings. -function error(...) textadept._print(locale.ERROR_BUFFER, ...) end +add_handler('error', + function(...) textadept._print(locale.ERROR_BUFFER, ...) end) diff --git a/core/ext/command_entry.lua b/core/ext/command_entry.lua index d2a64243..0170f21c 100644 --- a/core/ext/command_entry.lua +++ b/core/ext/command_entry.lua @@ -2,32 +2,51 @@ local textadept = _G.textadept local locale = _G.locale -local ce = textadept.command_entry --- LuaDoc is in core/.command_entry.lua -function ce.get_completions_for(command) - 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:find('^'..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:find('^'..prefix) then cmpls[#cmpls + 1] = f end +textadept.events.add_handler('command_entry_completions_request', + function(command) -- get a Lua completion list for the command being entered + 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:find('^'..prefix) then + cmpls[#cmpls + 1] = k end - else - for p in pairs(textadept.buffer_properties) do - if p:find('^'..prefix) then cmpls[#cmpls + 1] = p end + end + if path == 'buffer' then + if o == ':' then + for f in pairs(textadept.buffer_functions) do + if f:find('^'..prefix) then cmpls[#cmpls + 1] = f end + end + else + for p in pairs(textadept.buffer_properties) do + if p:find('^'..prefix) then cmpls[#cmpls + 1] = p end + end end end - end - table.sort(cmpls) - return cmpls -end + table.sort(cmpls) + textadept.command_entry.show_completions(cmpls) + end) + +textadept.events.add_handler('command_entry_command', + function(command) -- execute a Lua command + local f, err = loadstring(command) + if err then error(err) end + f() + end) + +textadept.events.add_handler('command_entry_keypress', + function(code) + local ce = textadept.command_entry + if code == 65307 then -- escape + ce.focus() -- toggle focus to hide + return true + elseif code == 65289 then -- tab + textadept.events.handle('command_entry_completions_request', + ce.entry_text) + return true + end + end) diff --git a/core/ext/find.lua b/core/ext/find.lua index b4f4efee..c97cefd3 100644 --- a/core/ext/find.lua +++ b/core/ext/find.lua @@ -10,14 +10,28 @@ local MARK_FIND = 0 local MARK_FIND_COLOR = 0x4D9999 local previous_view --- LuaDoc is in core/.find.lua. +--- +-- [Local table] Text escape sequences with their associated characters. +-- @class table +-- @name escapes local escapes = { ['\\a'] = '\a', ['\\b'] = '\b', ['\\f'] = '\f', ['\\n'] = '\n', ['\\r'] = '\r', ['\\t'] = '\t', ['\\v'] = '\v', ['\\\\'] = '\\' } --- LuaDoc is in core/.find.lua. -function find.find(text, next, flags, nowrap, wrapped) +--- +-- [Local function] Finds and selects text in the current buffer. +-- @param text The text to find. +-- @param next Flag indicating whether or not the search direction is forward. +-- @param flags Search flags. This is a number mask of 4 flags: match case (2), +-- whole word (4), Lua pattern (8), and in files (16) joined with binary OR. +-- If nil, this is determined based on the checkboxes in the find box. +-- @param nowrap Flag indicating whether or not the search won't wrap. +-- @param wrapped Utility flag indicating whether or not the search has wrapped +-- for displaying useful statusbar information. This flag is used and set +-- internally, and should not be set otherwise. +-- @return position of the found text or -1 +local function find_(text, next, flags, nowrap, wrapped) if #text == 0 then return end local buffer = buffer local first_visible_line = buffer.first_visible_line -- for 'no results found' @@ -128,7 +142,7 @@ function find.find(text, next, flags, nowrap, wrapped) buffer:goto_pos(buffer.length) end textadept.statusbar_text = locale.FIND_SEARCH_WRAPPED - result = find.find(text, next, flags, true, true) + result = find_(text, next, flags, true, true) if result == -1 then textadept.statusbar_text = locale.FIND_NO_RESULTS buffer:line_scroll(0, first_visible_line) @@ -141,9 +155,18 @@ function find.find(text, next, flags, nowrap, wrapped) return result end +textadept.events.add_handler('find', find_) --- LuaDoc is in core/.find.lua. -function find.replace(rtext) +--- +-- [Local function] Replaces found text. +-- 'find_' is called first, to select any found text. The selected text is then +-- replaced by the specified replacement text. +-- This function ignores 'Find in Files'. +-- @param rtext The text to replace found text with. It can contain both Lua +-- capture items (%n where 1 <= n <= 9) for Lua pattern searches and %() +-- sequences for embedding Lua code for any search. +-- @see find +local function replace(rtext) if #buffer:get_sel_text() == 0 then return end if find.in_files then find.in_files = false end local buffer = buffer @@ -179,9 +202,17 @@ function find.replace(rtext) buffer:goto_pos(buffer.current_pos) end end +textadept.events.add_handler('replace', replace) --- LuaDoc is in core/.find.lua. -function find.replace_all(ftext, rtext, flags) +--- +-- [Local function] Replaces all found text. +-- If any text is selected, all found text in that selection is replaced. +-- This function ignores 'Find in Files'. +-- @param ftext The text to find. +-- @param rtext The text to replace found text with. +-- @param flags The number mask identical to the one in 'find'. +-- @see find +local function replace_all(ftext, rtext, flags) if #ftext == 0 then return end if find.in_files then find.in_files = false end local buffer = buffer @@ -189,8 +220,8 @@ function find.replace_all(ftext, rtext, flags) local count = 0 if #buffer:get_sel_text() == 0 then buffer:goto_pos(0) - while(find.find(ftext, true, flags, true) ~= -1) do - find.replace(rtext) + while(find_(ftext, true, flags, true) ~= -1) do + replace(rtext) count = count + 1 end else @@ -201,13 +232,13 @@ function find.replace_all(ftext, rtext, flags) local end_marker = buffer:marker_add(buffer:line_from_position(e + 1), MARK_FIND) buffer:goto_pos(s) - local pos = find.find(ftext, true, flags, true) + local pos = find_(ftext, true, flags, true) while pos ~= -1 and pos < buffer:position_from_line( buffer:marker_line_from_handle(end_marker)) do - find.replace(rtext) + replace(rtext) count = count + 1 - pos = find.find(ftext, true, flags, true) + pos = find_(ftext, true, flags, true) end e = buffer:position_from_line(buffer:marker_line_from_handle(end_marker)) buffer:goto_pos(e) @@ -220,6 +251,7 @@ function find.replace_all(ftext, rtext, flags) string.format(locale.FIND_REPLACEMENTS_MADE, tostring(count)) buffer:end_undo_action() end +textadept.events.add_handler('replace_all', replace_all) --- -- [Local function] When the user double-clicks a found file, go to the line in diff --git a/core/ext/pm.lua b/core/ext/pm.lua index 67cbbbfb..8838582c 100644 --- a/core/ext/pm.lua +++ b/core/ext/pm.lua @@ -8,37 +8,36 @@ local current_browser = nil local last_browser_text = nil local browser_cursors = {} --- LuaDoc is in core/.browser.lua. -function pm.get_contents_for(full_path, expanding) - for _, browser in pairs(pm.browsers) do - if browser.matches(full_path[1]) then - current_browser = browser - if last_browser_text and last_browser_text ~= pm.entry_text then - -- Switching browsers, save the current one's cursor. - -- Don't reset last_browser_text here though, we still need to detect - -- the switch when the 'pm_view_filled' event is called so as to restore - -- the cursor to the new browser. - browser_cursors[last_browser_text] = pm.cursor +textadept.events.add_handler('pm_contents_request', + function(full_path, expanding) + for _, browser in pairs(pm.browsers) do + if browser.matches(full_path[1]) then + current_browser = browser + if last_browser_text and last_browser_text ~= pm.entry_text then + -- Switching browsers, save the current one's cursor. + -- Don't reset last_browser_text here though, we still need to detect + -- the switch when the 'pm_view_filled' event is called so as to restore + -- the cursor to the new browser. + browser_cursors[last_browser_text] = pm.cursor + end + pm.fill(browser.get_contents_for(full_path, expanding), expanding) + textadept.events.handle('pm_view_filled') end - return browser.get_contents_for(full_path, expanding) end - end -end + end) --- LuaDoc is in core/.browser.lua. -function pm.perform_action(selected_item) - current_browser.perform_action(selected_item) -end +textadept.events.add_handler('pm_item_selected', + function(selected_item) current_browser.perform_action(selected_item) end) --- LuaDoc is in core/.browser.lua. -function pm.get_context_menu(selected_item) - return current_browser.get_context_menu(selected_item) -end +textadept.events.add_handler('pm_context_menu_request', + function(selected_item, event) + pm.show_context_menu(current_browser.get_context_menu(selected_item), event) + end) --- LuaDoc is in core/.browser.lua. -function pm.perform_menu_action(menu_id, selected_item) - current_browser.perform_menu_action(menu_id, selected_item) -end +textadept.events.add_handler('pm_menu_clicked', + function(menu_id, selected_item) + current_browser.perform_menu_action(menu_id, selected_item) + end) -- LuaDoc is in core/.browser.lua. function pm.toggle_visible() @@ -51,7 +50,7 @@ function pm.toggle_visible() end textadept.events.add_handler('pm_view_filled', - function() -- tries to restore the cursor for a previous browser + function() -- try to restore previous browser cursor if last_browser_text ~= pm.entry_text then last_browser_text = pm.entry_text local previous_cursor = browser_cursors[pm.entry_text] diff --git a/core/ext/pm/modules_browser.lua b/core/ext/pm/modules_browser.lua index 4cbe715c..8472ee0d 100644 --- a/core/ext/pm/modules_browser.lua +++ b/core/ext/pm/modules_browser.lua @@ -84,8 +84,10 @@ function matches(entry_text) end local function modify_path(path) - path[1] = textadept.iconv(_HOME..'/modules', 'UTF-8', _CHARSET) - return path + local new_path = {} + for _, v in ipairs(path) do new_path[#new_path + 1] = v end + new_path[1] = textadept.iconv(_HOME..'/modules', 'UTF-8', _CHARSET) + return new_path end function get_contents_for(full_path) diff --git a/src/Makefile b/src/Makefile index 06857307..52fb26fe 100644 --- a/src/Makefile +++ b/src/Makefile @@ -4,9 +4,9 @@ INCLUDEDIRS=-Iscintilla-st/include -Ilua/include ifdef DEBUG -CXXFLAGS=-DDEBUG -g -DGTK -DSCI_LEXER -W -Wall +CXXFLAGS=-DDEBUG -g -DGTK -DSCI_LEXER -W -Wall -Wno-sign-compare else -CXXFLAGS=-DNDEBUG -O -DGTK -DSCI_LEXER -W -Wall +CXXFLAGS=-DNDEBUG -O -DGTK -DSCI_LEXER -W -Wall -Wno-sign-compare endif GTKFLAGS=$(shell pkg-config --cflags gtk+-2.0) GTKLIBS=$(shell pkg-config --libs gtk+-2.0 gthread-2.0) diff --git a/src/lua_interface.c b/src/lua_interface.c index 9a126cbf..b8101e20 100644 --- a/src/lua_interface.c +++ b/src/lua_interface.c @@ -58,12 +58,12 @@ static int l_buffer_mt_index(lua_State *lua), l_ce_mt_index(lua_State *lua), l_ce_mt_newindex(lua_State *lua); -static int l_cf_ta_buffer_new(lua_State *lua), - l_cf_buffer_delete(lua_State *lua), +static int l_cf_buffer_delete(lua_State *lua), l_cf_buffer_text_range(lua_State *lua), l_cf_view_focus(lua_State *lua), l_cf_view_split(lua_State *lua), l_cf_view_unsplit(lua_State *lua), + l_cf_ta_buffer_new(lua_State *lua), l_cf_ta_get_split_table(lua_State *lua), l_cf_ta_goto_window(lua_State *lua), l_cf_view_goto_buffer(lua_State *lua), @@ -71,16 +71,19 @@ static int l_cf_ta_buffer_new(lua_State *lua), l_cf_ta_iconv(lua_State *lua), l_cf_ta_reset(lua_State *lua), l_cf_ta_quit(lua_State *lua), - l_cf_pm_focus(lua_State *lua), - l_cf_pm_clear(lua_State *lua), l_cf_pm_activate(lua_State *lua), l_cf_pm_add_browser(lua_State *lua), + l_cf_pm_clear(lua_State *lua), + l_cf_pm_fill(lua_State *lua), + l_cf_pm_focus(lua_State *lua), + l_cf_pm_show_context_menu(lua_State *lua), l_cf_find_focus(lua_State *lua), - l_cf_call_find_next(lua_State *lua), - l_cf_call_find_prev(lua_State *lua), - l_cf_call_replace(lua_State *lua), - l_cf_call_replace_all(lua_State *lua), - l_cf_ce_focus(lua_State *lua); + l_cf_find_next(lua_State *lua), + l_cf_find_prev(lua_State *lua), + l_cf_find_replace(lua_State *lua), + l_cf_find_replace_all(lua_State *lua), + l_cf_ce_focus(lua_State *lua), + l_cf_ce_show_completions(lua_State *lua); /** * Inits or re-inits the Lua State. @@ -114,31 +117,34 @@ bool l_init(int argc, char **argv, bool reinit) { lua_newtable(lua); lua_newtable(lua); - l_cfunc(lua, l_cf_pm_focus, "focus"); - l_cfunc(lua, l_cf_pm_clear, "clear"); l_cfunc(lua, l_cf_pm_activate, "activate"); l_cfunc(lua, l_cf_pm_add_browser, "add_browser"); + l_cfunc(lua, l_cf_pm_clear, "clear"); + l_cfunc(lua, l_cf_pm_fill, "fill"); + l_cfunc(lua, l_cf_pm_focus, "focus"); + l_cfunc(lua, l_cf_pm_show_context_menu, "show_context_menu"); l_mt(lua, "_pm_mt", l_pm_mt_index, l_pm_mt_newindex); lua_setfield(lua, -2, "pm"); 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_call_find_next, "call_find_next"); - l_cfunc(lua, l_cf_call_find_prev, "call_find_prev"); - l_cfunc(lua, l_cf_call_replace, "call_replace"); - l_cfunc(lua, l_cf_call_replace_all, "call_replace_all"); + 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_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_goto_window, "goto_view"); l_cfunc(lua, l_cf_ta_gtkmenu, "gtkmenu"); l_cfunc(lua, l_cf_ta_iconv, "iconv"); - l_cfunc(lua, l_cf_ta_reset, "reset"); + l_cfunc(lua, l_cf_ta_buffer_new, "new_buffer"); l_cfunc(lua, l_cf_ta_quit, "quit"); + l_cfunc(lua, l_cf_ta_reset, "reset"); l_mt(lua, "_textadept_mt", l_ta_mt_index, l_ta_mt_newindex); lua_setglobal(lua, "textadept"); @@ -272,9 +278,9 @@ void l_goto_scintilla_window(GtkWidget *editor, int n, bool absolute) { lua_rawgeti(lua, -1, n); } editor = l_checkview(lua, -1); - if (!closing) l_handle_event("view_before_switch"); + if (!closing) l_handle_event("view_before_switch", -1); gtk_widget_grab_focus(editor); - if (!closing) l_handle_event("view_after_switch"); + if (!closing) l_handle_event("view_after_switch", -1); lua_pop(lua, 2); // view table and views } @@ -374,16 +380,6 @@ unsigned int l_get_docpointer_index(sptr_t doc) { return idx; } -#define l_set_bufferp(k, v) { \ - lua_pushstring(lua, k); \ - lua_pushinteger(lua, v); \ - lua_rawset(lua, -3); \ -} -#define l_get_bufferp(k, i) { \ - lua_pushstring(lua, k); \ - lua_rawget(lua, (i < 0) ? i - 1 : i); \ -} - /** * 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 @@ -414,10 +410,10 @@ void l_goto_scintilla_buffer(GtkWidget *editor, int n, bool absolute) { lua_rawgeti(lua, -1, n); } sptr_t doc = l_checkdocpointer(lua, -1); - if (!closing) l_handle_event("buffer_before_switch"); + if (!closing) l_handle_event("buffer_before_switch", -1); SS(sci, SCI_SETDOCPOINTER, 0, doc); l_set_buffer_global(sci); - if (!closing) l_handle_event("buffer_after_switch"); + if (!closing) l_handle_event("buffer_after_switch", -1); lua_pop(lua, 2); // buffer table and buffers } @@ -509,13 +505,19 @@ bool l_ista2function(const char *table, const char *key) { * values at the top of the stack. If false, discards the return values. * Defaults to false. */ -bool l_call_function(int nargs, int retn=0, bool keep_return=false) { +static bool l_call_function(int nargs, int retn=0, bool keep_return=false) { int ret = lua_pcall(lua, nargs, retn, 0); if (ret == 0) { bool result = (retn > 0) ? lua_toboolean(lua, -1) == 1 : true; if (retn > 0 && !keep_return) lua_pop(lua, retn); // retn return result; - } else l_handle_error(NULL); + } else { + if (focused_editor) + l_handle_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; } @@ -637,47 +639,48 @@ static void l_check_focused_buffer(lua_State *lua, int narg) { // Notification/event handlers /** - * Handles a Lua error. - * The main error message is at the top of the Lua stack. - * @param extramsg An additional error message to display. - */ -void l_handle_error(const char *extramsg) { - if (focused_editor && l_ista2function("events", "error")) { - l_insert(lua, -1); // shift error message down - if (extramsg) lua_pushstring(lua, extramsg); - l_call_function(extramsg ? 2 : 1); - } else { - printf("Lua Error: %s\n", lua_tostring(lua, -1)); - if (extramsg) printf("%s\n", extramsg); - } - lua_settop(lua, 0); -} - -/** * Handles a Textadept event. * @param s String event name. - * @param arg Optional string argument. - */ -bool l_handle_event(const char *s, const char *arg) { - if (!l_ista2function("events", s)) return false; - if (arg) lua_pushstring(lua, arg); - return l_call_function(arg ? 1 : 0, 1); -} - -/** - * Handles a Textadept keypress. - * @param keyval The key value of the key pressed. - * @param shift Flag indicating whether or not the shift modifier was held. - * @param control Flag indicating whether or not the control modifier was held. - * @param alt Flag indicating whether or not the alt modifier was held. + * @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. */ -bool l_handle_keypress(int keyval, bool shift, bool control, bool alt) { - if (!l_ista2function("events", "keypress")) return false; - lua_pushinteger(lua, keyval); - lua_pushboolean(lua, shift); - lua_pushboolean(lua, control); - lua_pushboolean(lua, alt); - return l_call_function(4, 1); +bool l_handle_event(const char *s, ...) { + if (!l_ista2function("events", "handle")) 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*); + switch(type) { + case LUA_TNIL: + lua_pushnil(lua); + break; + case LUA_TBOOLEAN: + lua_pushboolean(lua, reinterpret_cast<long>(arg)); + break; + case LUA_TNUMBER: + lua_pushinteger(lua, reinterpret_cast<long>(arg)); + break; + case LUA_TSTRING: + lua_pushstring(lua, reinterpret_cast<char*>(arg)); + break; + case LUA_TLIGHTUSERDATA: + case LUA_TTABLE: { + long ref = reinterpret_cast<long>(arg); + lua_rawgeti(lua, LUA_REGISTRYINDEX, ref); + luaL_unref(lua, LUA_REGISTRYINDEX, ref); + break; + } default: + warn("events.handle: ignored invalid argument type"); + } + n++; + type = va_arg(ap, int); + } + va_end(ap); + return l_call_function(n, 1); } #define l_pushscninteger(i, n) { \ @@ -740,206 +743,52 @@ void l_ta_popup_context_menu(GdkEventButton *event) { // Project Manager /** - * Creates and pushes a Lua table of parent nodes for the given Project Manager - * treeview path. + * Creates a Lua table of parent nodes for the given Project Manager treeview + * path and returns a reference to it. * The first table item is the PM Entry text, the next items are parents of the * given node in descending order, and the last item is the given node itself. + * The reference can be retrieved using lua_rawgeti. * @param store The GtkTreeStore of the PM view. * @param path The GtkTreePath of the node. If NULL, only the PM Entry text is * contained in the resulting table. + * @return int reference to the created table in LUA_REGISTRYINDEX. */ -void l_pushpathtable(GtkTreeStore *store, GtkTreePath *path) { +int l_pm_pathtableref(GtkTreeStore *store, GtkTreePath *path) { lua_newtable(lua); lua_pushstring(lua, gtk_entry_get_text(GTK_ENTRY(pm_entry))); lua_rawseti(lua, -2, 1); - if (!path) return; - GtkTreeIter iter; - while (gtk_tree_path_get_depth(path) > 0) { - char *item = 0; - gtk_tree_model_get_iter(GTK_TREE_MODEL(store), &iter, path); - gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, 1, &item, -1); - lua_pushstring(lua, item); - lua_rawseti(lua, -2, gtk_tree_path_get_depth(path) + 1); - g_free(item); - gtk_tree_path_up(path); - } -} - -/** - * Requests and adds contents to the Project Manager view. - * @param store The GtkTreeStore of the PM view. - * @param initial_iter An initial GtkTreeIter. If NULL, contents will be added - * to the treeview root. Otherwise they will be added to this parent node. - */ -void l_pm_view_fill(GtkTreeStore *store, GtkTreeIter *initial_iter) { - if (!l_ista2function("pm", "get_contents_for")) return; - if (initial_iter) { - GtkTreePath *path = - gtk_tree_model_get_path(GTK_TREE_MODEL(store), initial_iter); - l_pushpathtable(store, path); - gtk_tree_path_free(path); - } else l_pushpathtable(store, NULL); - lua_pushboolean(lua, initial_iter != NULL); - l_call_function(2, 1, true); - if (!lua_istable(lua, -1)) { - if (!lua_isnil(lua, -1)) warn("pm.get_contents_for: table expected"); - lua_pop(lua, 1); // non-table return - return; - } - - if (!initial_iter) gtk_tree_store_clear(store); - lua_pushnil(lua); - while (lua_next(lua, -2)) { - if (lua_istable(lua, -1) && lua_type(lua, -2) == LUA_TSTRING) { - GtkTreeIter iter, child; - gtk_tree_store_append(store, &iter, initial_iter); - gtk_tree_store_set(store, &iter, 1, lua_tostring(lua, -2), -1); - lua_getfield(lua, -1, "parent"); - if (lua_toboolean(lua, -1)) { - gtk_tree_store_append(store, &child, &iter); - gtk_tree_store_set(store, &child, 1, "\0dummy", -1); - } - lua_pop(lua, 1); // parent - lua_getfield(lua, -1, "pixbuf"); - if (lua_isstring(lua, -1)) - gtk_tree_store_set(store, &iter, 0, lua_tostring(lua, -1), -1); - else if (!lua_isnil(lua, -1)) - warn("pm.fill: non-string pixbuf key ignored"); - lua_pop(lua, 1); // pixbuf - lua_getfield(lua, -1, "text"); - gtk_tree_store_set(store, &iter, 2, lua_isstring(lua, -1) ? - lua_tostring(lua, -1) : lua_tostring(lua, -3), -1); - lua_pop(lua, 1); // display text - } else warn("pm.fill: string id key must have table value"); - lua_pop(lua, 1); // value + if (path) { + GtkTreeIter iter; + while (gtk_tree_path_get_depth(path) > 0) { + char *item = 0; + gtk_tree_model_get_iter(GTK_TREE_MODEL(store), &iter, path); + gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, 1, &item, -1); + lua_pushstring(lua, item); + lua_rawseti(lua, -2, gtk_tree_path_get_depth(path) + 1); + g_free(item); + gtk_tree_path_up(path); + } } - lua_pop(lua, 1); // returned table - - l_handle_event("pm_view_filled"); + return luaL_ref(lua, LUA_REGISTRYINDEX); } /** - * Requests and pops up a context menu for a selected Project Manager item. - * @param store The GtkTreeStore of the PM view. - * @param path The GtkTreePath of the item. + * Requests a popup context menu for a selected Project Manager item. * @param event The mouse button event. - * @param callback The GCallback associated with each menu item. - */ -void l_pm_popup_context_menu(GtkTreeStore *store, GtkTreePath *path, - GdkEventButton *event, GCallback callback) { - if (!l_ista2function("pm", "get_context_menu")) return; - l_pushpathtable(store, path); - l_call_function(1, 1, true); - if (lua_istable(lua, -1)) { - GtkWidget *menu = l_create_gtkmenu(lua, callback, false); - gtk_widget_show_all(menu); - gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, - event ? event->button : 0, - gdk_event_get_time(reinterpret_cast<GdkEvent*>(event))); - } else warn("pm.get_context_menu: table expected"); - lua_pop(lua, 1); // returned value -} - -/** - * Performs an action for the selected Project Manager item. - * @param store The GtkTreeStore of the PM view. - * @param path The GtkTreePath of the item. - */ -void l_pm_perform_action(GtkTreeStore *store, GtkTreePath *path) { - if (!l_ista2function("pm", "perform_action")) return; - l_pushpathtable(store, path); - l_call_function(1); -} - -/** - * Performs a selected menu action from a Project Manager item's context menu. - * @param store The GtkTreeStore of the PM view. - * @param path The GtkTreePath of the item. - * @param id The numeric ID for the menu item. - */ -void l_pm_perform_menu_action(GtkTreeStore *store, GtkTreePath *path, int id) { - if (!l_ista2function("pm", "perform_menu_action")) return; - lua_pushnumber(lua, id); - l_pushpathtable(store, path); - l_call_function(2); -} - -// Find/Replace - -/** - * Finds text in the current document. - * @param ftext The text to find. - * @param next Flag indicating whether or not to find next. If false, finds - * previous matches. - */ -void l_find(const char *ftext, bool next) { - if (!l_ista2function("find", "find")) return; - lua_pushstring(lua, ftext); - lua_pushboolean(lua, next); - l_call_function(2); -} - -/** - * Replaces text in the current document. - * @param rtext The text to replace the found text with. - */ -void l_find_replace(const char *rtext) { - if (!l_ista2function("find", "replace")) return; - lua_pushstring(lua, rtext); - l_call_function(1); -} - -/** - * Replaces all found text in the current document. - * @param ftext The text to find. - * @param rtext The text to replace the found text with. - */ -void l_find_replace_all(const char *ftext, const char *rtext) { - if (!l_ista2function("find", "replace_all")) return; - lua_pushstring(lua, ftext); - lua_pushstring(lua, rtext); - l_call_function(2); -} - -// Command Entry - -/** - * Executes a given command string as Lua code. - * @param command Lua code to execute. - */ -void l_ce_command(const char *command) { - int top = lua_gettop(lua); - if (luaL_dostring(lua, command) == 0) { - l_handle_event("update_ui"); - lua_settop(lua, top); - } else l_handle_error("Error executing command"); -} - -/** - * Requests and adds completions for the Command Entry Completion. - * @param store The GtkListStore to populate. */ -void l_cec_fill(GtkListStore *store) { - if (!l_ista2function("command_entry", "get_completions_for")) return; - lua_pushstring(lua, gtk_entry_get_text(GTK_ENTRY(command_entry))); - l_call_function(1, 1, true); - if (!lua_istable(lua, -1)) { - if (!lua_isnil(lua, -1)) warn("ce.get_completions_for: table expected"); - lua_pop(lua, 1); // non-table return - return; - } - - gtk_list_store_clear(store); - lua_pushnil(lua); - while (lua_next(lua, -2)) { - 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("ce.get_completions_for: non-string value ignored"); - lua_pop(lua, 1); // value - } - lua_pop(lua, 1); // returned table +void l_pm_popup_context_menu(GdkEventButton *event) { + GtkTreePath *path = NULL; + GtkTreeIter iter; + GtkTreeSelection *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(pm_view)); + GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(pm_view)); + if (gtk_tree_selection_get_selected(sel, NULL, &iter)) + path = gtk_tree_model_get_path(model, &iter); + lua_pushlightuserdata(lua, const_cast<GdkEventButton*>(event)); + int ref = luaL_ref(lua, LUA_REGISTRYINDEX); + l_handle_event("pm_context_menu_request", LUA_TTABLE, + l_pm_pathtableref(GTK_TREE_STORE(model), path), + LUA_TLIGHTUSERDATA, ref, -1); + if (path) gtk_tree_path_free(path); } // Lua functions (stack maintenence is unnecessary) @@ -1308,13 +1157,6 @@ static int l_ce_mt_newindex(lua_State *lua) { // Lua CFunctions. For documentation, consult the LuaDoc. -static int l_cf_ta_buffer_new(lua_State *lua) { - new_scintilla_buffer(SCINTILLA(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_delete(lua_State *lua) { l_check_focused_buffer(lua, 1); sptr_t doc = l_checkdocpointer(lua, 1); @@ -1324,10 +1166,17 @@ static int l_cf_buffer_delete(lua_State *lua) { else new_scintilla_buffer(SCINTILLA(focused_editor), true, true); remove_scintilla_buffer(doc); - l_handle_event("buffer_deleted"); + l_handle_event("buffer_deleted", -1); return 0; } +static int l_cf_ta_buffer_new(lua_State *lua) { + new_scintilla_buffer(SCINTILLA(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); #ifndef MAC @@ -1414,10 +1263,6 @@ static int l_cf_ta_goto_(lua_State *lua, GtkWidget *editor, bool buffer) { return 0; } -static int l_cf_ta_goto_window(lua_State *lua) { - return l_cf_ta_goto_(lua, focused_editor, false); -} - // 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) { @@ -1434,12 +1279,12 @@ static int l_cf_view_goto_buffer(lua_State *lua) { return 0; } +static int l_cf_ta_goto_window(lua_State *lua) { + return l_cf_ta_goto_(lua, focused_editor, false); +} + static void t_menu_activate(GtkWidget *, gpointer id) { - int menu_id = GPOINTER_TO_INT(id); - char *menu_id_str = static_cast<char*>(malloc(sizeof(char) * 12)); - sprintf(menu_id_str, "%i", menu_id); - l_handle_event("menu_clicked", menu_id_str); - g_free(menu_id_str); + l_handle_event("menu_clicked", LUA_TNUMBER, GPOINTER_TO_INT(id), -1); } static int l_cf_ta_gtkmenu(lua_State *lua) { @@ -1462,8 +1307,17 @@ static int l_cf_ta_iconv(lua_State *lua) { return 1; } +static int l_cf_ta_quit(lua_State *) { + GdkEventAny event; + event.type = GDK_DELETE; + event.window = window->window; + event.send_event = TRUE; + gdk_event_put(reinterpret_cast<GdkEvent*>(&event)); + return 0; +} + static int l_cf_ta_reset(lua_State *lua) { - l_handle_event("resetting"); + l_handle_event("resetting", -1); l_init(0, NULL, true); lua_pushboolean(lua, true); lua_setglobal(lua, "RESETTING"); @@ -1475,17 +1329,14 @@ static int l_cf_ta_reset(lua_State *lua) { return 0; } -static int l_cf_ta_quit(lua_State *) { - GdkEventAny event; - event.type = GDK_DELETE; - event.window = window->window; - event.send_event = TRUE; - gdk_event_put(reinterpret_cast<GdkEvent*>(&event)); +static int l_cf_pm_activate(lua_State *) { + g_signal_emit_by_name(G_OBJECT(pm_entry), "activate"); return 0; } -static int l_cf_pm_focus(lua_State *) { - pm_toggle_focus(); +static int l_cf_pm_add_browser(lua_State *lua) { + GtkWidget *pm_combo = gtk_widget_get_parent(pm_entry); + gtk_combo_box_append_text(GTK_COMBO_BOX(pm_combo), lua_tostring(lua, -1)); return 0; } @@ -1495,14 +1346,83 @@ static int l_cf_pm_clear(lua_State *) { return 0; } -static int l_cf_pm_activate(lua_State *) { - g_signal_emit_by_name(G_OBJECT(pm_entry), "activate"); +static int l_cf_pm_fill(lua_State *lua) { + luaL_checktype(lua, 1, LUA_TTABLE); + GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(pm_view)); + GtkTreeStore *store = GTK_TREE_STORE(model); + GtkTreeIter initial_iter, *initial_iter_p = NULL; + if (lua_gettop(lua) > 1 && lua_type(lua, 2) == LUA_TSTRING && + gtk_tree_model_get_iter_from_string(model, &initial_iter, + lua_tostring(lua, 2))) + initial_iter_p = &initial_iter; + if (!initial_iter_p) gtk_tree_store_clear(store); + GtkTreeIter iter, child; + lua_pushnil(lua); + while (lua_next(lua, 1)) { + if (lua_istable(lua, -1) && lua_type(lua, -2) == LUA_TSTRING) { + gtk_tree_store_append(store, &iter, initial_iter_p); + gtk_tree_store_set(store, &iter, 1, lua_tostring(lua, -2), -1); + lua_getfield(lua, -1, "parent"); + if (lua_toboolean(lua, -1)) { + gtk_tree_store_append(store, &child, &iter); + gtk_tree_store_set(store, &child, 1, "\0dummy", -1); + } + lua_pop(lua, 1); // parent + lua_getfield(lua, -1, "pixbuf"); + if (lua_isstring(lua, -1)) + gtk_tree_store_set(store, &iter, 0, lua_tostring(lua, -1), -1); + else if (!lua_isnil(lua, -1)) + warn("pm.fill: non-string pixbuf key ignored"); + lua_pop(lua, 1); // pixbuf + lua_getfield(lua, -1, "text"); + gtk_tree_store_set(store, &iter, 2, lua_isstring(lua, -1) ? + lua_tostring(lua, -1) : lua_tostring(lua, -3), -1); + lua_pop(lua, 1); // display text + } else warn("pm.fill: string id key must have table value"); + lua_pop(lua, 1); // value + } + if (initial_iter_p) { + char *item; + gtk_tree_model_iter_nth_child(model, &child, initial_iter_p, 0); + gtk_tree_model_get(model, &child, 1, &item, -1); + if (strcmp(reinterpret_cast<const char*>(item), "\0dummy") == 0) + gtk_tree_store_remove(store, &child); + g_free(item); + } return 0; } -static int l_cf_pm_add_browser(lua_State *lua) { - GtkWidget *pm_combo = gtk_widget_get_parent(pm_entry); - gtk_combo_box_append_text(GTK_COMBO_BOX(pm_combo), lua_tostring(lua, -1)); +static void pm_menu_activate(GtkWidget *, gpointer id) { + GtkTreePath *path = NULL; + GtkTreeIter iter; + GtkTreeSelection *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(pm_view)); + GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(pm_view)); + if (gtk_tree_selection_get_selected(sel, NULL, &iter)) + path = gtk_tree_model_get_path(model, &iter); + l_handle_event("pm_menu_clicked", LUA_TNUMBER, GPOINTER_TO_INT(id), + LUA_TTABLE, l_pm_pathtableref(GTK_TREE_STORE(model), path), + -1); + if (path) gtk_tree_path_free(path); +} + +static int l_cf_pm_show_context_menu(lua_State *lua) { + GdkEventButton *event = NULL; + if (lua_gettop(lua) > 1) { + if (lua_isuserdata(lua, 2)) + event = reinterpret_cast<GdkEventButton*>(lua_touserdata(lua, 2)); + lua_pop(lua, 1); // userdata + } + luaL_checktype(lua, 1, LUA_TTABLE); + GtkWidget *menu = l_create_gtkmenu(lua, G_CALLBACK(pm_menu_activate), false); + gtk_widget_show_all(menu); + gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, + event ? event->button : 0, + gdk_event_get_time(reinterpret_cast<GdkEvent*>(event))); + return 0; +} + +static int l_cf_pm_focus(lua_State *) { + pm_toggle_focus(); return 0; } @@ -1511,22 +1431,22 @@ static int l_cf_find_focus(lua_State *) { return 0; } -static int l_cf_call_find_next(lua_State *) { +static int l_cf_find_next(lua_State *) { g_signal_emit_by_name(G_OBJECT(fnext_button), "clicked"); return 0; } -static int l_cf_call_find_prev(lua_State *) { +static int l_cf_find_prev(lua_State *) { g_signal_emit_by_name(G_OBJECT(fprev_button), "clicked"); return 0; } -static int l_cf_call_replace(lua_State *) { +static int l_cf_find_replace(lua_State *) { g_signal_emit_by_name(G_OBJECT(r_button), "clicked"); return 0; } -static int l_cf_call_replace_all(lua_State *) { +static int l_cf_find_replace_all(lua_State *) { g_signal_emit_by_name(G_OBJECT(ra_button), "clicked"); return 0; } @@ -1535,3 +1455,23 @@ static int l_cf_ce_focus(lua_State *) { 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; +} diff --git a/src/textadept.c b/src/textadept.c index c0615351..cec9cb3c 100644 --- a/src/textadept.c +++ b/src/textadept.c @@ -42,7 +42,7 @@ static int pm_sort_iter_compare_func(GtkTreeModel *model, GtkTreeIter *a, static void pm_entry_activated(GtkWidget *, gpointer); static void pm_entry_changed(GtkComboBoxEntry *, gpointer); static gbool pm_keypress(GtkWidget *, GdkEventKey *event, gpointer); -static void pm_row_expanded(GtkTreeView *, GtkTreeIter *iter, GtkTreePath *, +static void pm_row_expanded(GtkTreeView *, GtkTreeIter *, GtkTreePath *path, gpointer); static void pm_row_collapsed(GtkTreeView *, GtkTreeIter *iter, GtkTreePath *, gpointer); @@ -50,7 +50,6 @@ static void pm_row_activated(GtkTreeView *view, GtkTreePath *path, GtkTreeViewColumn *, gpointer); static gbool pm_buttonpress(GtkTreeView *, GdkEventButton *event, gpointer); static gbool pm_popup_menu(GtkWidget *, gpointer); -static void pm_menu_activate(GtkWidget *, gpointer id); // Find/Replace GtkWidget *findbox, *find_entry, *replace_entry, *fnext_button, *fprev_button, @@ -60,6 +59,7 @@ GtkWidget *find_create_ui(); GtkListStore *find_store, *repl_store; static void find_button_clicked(GtkWidget *button, gpointer); +static gbool find_entry_keypress(GtkWidget *, GdkEventKey *event, gpointer); // Command Entry GtkWidget *command_entry; @@ -245,7 +245,7 @@ GtkWidget *new_scintilla_window(sptr_t buffer_id) { new_scintilla_buffer(SCINTILLA(editor), false, false); } else new_scintilla_buffer(SCINTILLA(editor), false, true); l_set_view_global(editor); - l_handle_event("view_new"); + l_handle_event("view_new", -1); return editor; } @@ -287,8 +287,8 @@ void new_scintilla_buffer(ScintillaObject *sci, bool create, bool addref) { SS(sci, SCI_ADDREFDOCUMENT, 0, doc); } l_set_buffer_global(sci); - l_handle_event("buffer_new"); - l_handle_event("update_ui"); // update document status + l_handle_event("buffer_new", -1); + l_handle_event("update_ui", -1); // update document status } /** @@ -422,11 +422,11 @@ void set_statusbar_text(const char *text, bool docbar) { * @see s_command */ static void switch_to_view(GtkWidget *editor) { - l_handle_event("view_before_switch"); + l_handle_event("view_before_switch", -1); focused_editor = editor; l_set_view_global(editor); l_set_buffer_global(SCINTILLA(editor)); - l_handle_event("view_after_switch"); + l_handle_event("view_after_switch", -1); } /** @@ -451,17 +451,18 @@ static void s_command(GtkWidget *editor, gint wParam, gpointer, gpointer) { /** * Signal for a Scintilla keypress. * Collects the modifier states as flags and calls Lua to handle the keypress. - * @see l_handle_keypress */ static gbool s_keypress(GtkWidget *, GdkEventKey *event, gpointer) { - bool shift = event->state & GDK_SHIFT_MASK; - bool control = event->state & GDK_CONTROL_MASK; + return l_handle_event("keypress", + LUA_TNUMBER, event->keyval, + LUA_TBOOLEAN, event->state & GDK_SHIFT_MASK, + LUA_TBOOLEAN, event->state & GDK_CONTROL_MASK, #ifndef MAC - bool alt = event->state & GDK_MOD1_MASK; + LUA_TBOOLEAN, event->state & GDK_MOD1_MASK, #else - bool alt = event->state & GDK_META_MASK; + LUA_TBOOLEAN, event->state & GDK_META_MASK, #endif - return l_handle_keypress(event->keyval, shift, control, alt) ? TRUE : FALSE; + -1) ? TRUE : FALSE; } /** @@ -505,7 +506,7 @@ static gbool w_keypress(GtkWidget *, GdkEventKey *event, gpointer) { * @see l_close */ static gbool w_exit(GtkWidget *, GdkEventAny *, gpointer) { - if (!l_handle_event("quit")) return TRUE; + if (!l_handle_event("quit", -1)) return TRUE; l_close(); scintilla_release_resources(); gtk_main_quit(); @@ -516,7 +517,6 @@ static gbool w_exit(GtkWidget *, GdkEventAny *, gpointer) { /** * Signal for an Open Document AppleEvent. * Generates a 'appleevent_odoc' event for each document sent. - * @see l_handle_event */ static OSErr w_ae_open(const AppleEvent *event, AppleEvent *, long) { AEDescList file_list; @@ -529,7 +529,7 @@ static OSErr w_ae_open(const AppleEvent *event, AppleEvent *, long) { NULL); CFURLRef url = CFURLCreateFromFSRef(kCFAllocatorDefault, &fsref); if (url) { - l_handle_event("appleevent_odoc", CFURL_TO_STR(url)); + l_handle_event("appleevent_odoc", LUA_TSTRING, CFURL_TO_STR(url), -1); CFRelease(url); } } @@ -657,19 +657,19 @@ static int pm_sort_iter_compare_func(GtkTreeModel *model, GtkTreeIter *a, /** * Signal for the activation of the Project Manager entry. * Requests contents for the Project Manager. - * @see l_pm_view_fill */ static void pm_entry_activated(GtkWidget *, gpointer) { - l_pm_view_fill(pm_store, NULL); + l_handle_event("pm_contents_request", LUA_TTABLE, + l_pm_pathtableref(pm_store, NULL), LUA_TNIL, 0, -1); } /** * Signal for a change of the text in the Project Manager entry. * Requests contents for the Project Manager. - * @see l_pm_view_fill */ static void pm_entry_changed(GtkComboBoxEntry *, gpointer) { - l_pm_view_fill(pm_store, NULL); + l_handle_event("pm_contents_request", LUA_TTABLE, + l_pm_pathtableref(pm_store, NULL), LUA_TNIL, 0, -1); } /** @@ -689,18 +689,13 @@ static gbool pm_keypress(GtkWidget *, GdkEventKey *event, gpointer) { * Requests contents for a Project Manager parent node being opened. * Since a parent is given a dummy child by default in order to indicate that * it is a parent, that dummy child is removed. - * @see l_pm_view_fill */ -static void pm_row_expanded(GtkTreeView *, GtkTreeIter *iter, GtkTreePath *, +static void pm_row_expanded(GtkTreeView *, GtkTreeIter *, GtkTreePath *path, gpointer) { - l_pm_view_fill(pm_store, iter); - GtkTreeIter child; - char *item; - gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(pm_store), &child, iter, 0); - gtk_tree_model_get(GTK_TREE_MODEL(pm_store), &child, 1, &item, -1); - if (strcmp(reinterpret_cast<const char*>(item), "\0dummy") == 0) - gtk_tree_store_remove(pm_store, &child); - g_free(item); + char *path_str = gtk_tree_path_to_string(path); + l_handle_event("pm_contents_request", LUA_TTABLE, + l_pm_pathtableref(pm_store, path), LUA_TSTRING, path_str, -1); + g_free(path_str); } /** @@ -724,27 +719,13 @@ static void pm_row_collapsed(GtkTreeView *, GtkTreeIter *iter, GtkTreePath *, * Performs the appropriate action on a selected Project Manager node. * If the node is a collapsed parent, it is expanded; otherwise the parent is * collapsed. If the node is not a parent at all, a Lua action is performed. - * @see l_pm_perform_action */ static void pm_row_activated(GtkTreeView *view, GtkTreePath *path, GtkTreeViewColumn *, gpointer) { if (!gtk_tree_view_expand_row(view, path, FALSE)) if (!gtk_tree_view_collapse_row(view, path)) - l_pm_perform_action(pm_store, path); -} - -/** - * Helper function to return the path of the selected Project Manager view item - * (if any). - * The returned GtkTreePath must be freed if it is not NULL. - */ -static GtkTreePath *pm_view_get_selection_path() { - GtkTreeIter iter; - GtkTreePath *path = 0; - GtkTreeSelection *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(pm_view)); - if (gtk_tree_selection_get_selected(sel, NULL, &iter)) - path = gtk_tree_model_get_path(GTK_TREE_MODEL(pm_store), &iter); - return path; + l_handle_event("pm_item_selected", LUA_TTABLE, + l_pm_pathtableref(pm_store, path), -1); } /** @@ -754,9 +735,7 @@ static GtkTreePath *pm_view_get_selection_path() { */ static gbool pm_buttonpress(GtkTreeView *, GdkEventButton *event, gpointer) { if (event->type != GDK_BUTTON_PRESS || event->button != 3) return FALSE; - GtkTreePath *path = pm_view_get_selection_path(); - l_pm_popup_context_menu(pm_store, path, event, G_CALLBACK(pm_menu_activate)); - if (path) gtk_tree_path_free(path); + l_pm_popup_context_menu(event); return TRUE; } @@ -766,24 +745,10 @@ static gbool pm_buttonpress(GtkTreeView *, GdkEventButton *event, gpointer) { * @see l_pm_popup_context_menu */ static gbool pm_popup_menu(GtkWidget *, gpointer) { - GtkTreePath *path = pm_view_get_selection_path(); - l_pm_popup_context_menu(pm_store, path, NULL, G_CALLBACK(pm_menu_activate)); - if (path) gtk_tree_path_free(path); + l_pm_popup_context_menu(NULL); return TRUE; } -/** - * Signal for a selected Project Manager menu item. - * Performs a Lua action for a selected menu item. - * @param id The numeric ID for the menu item. - * @see l_pm_perform_menu_action - */ -static void pm_menu_activate(GtkWidget *, gpointer id) { - GtkTreePath *path = pm_view_get_selection_path(); - l_pm_perform_menu_action(pm_store, path, GPOINTER_TO_INT(id)); - if (path) gtk_tree_path_free(path); -} - // Find/Replace #define attach(w, x1, x2, y1, y2, xo, yo, xp, yp) \ @@ -840,6 +805,7 @@ GtkWidget *find_create_ui() { attach(lua_opt, 5, 6, 0, 1, ao_normal, ao_normal, 5, 0); attach(in_files_opt, 5, 6, 1, 2, ao_normal, ao_normal, 5, 0); + signal(find_entry, "key-press-event", find_entry_keypress); signal(fnext_button, "clicked", find_button_clicked); signal(fprev_button, "clicked", find_button_clicked); signal(r_button, "clicked", find_button_clicked); @@ -906,20 +872,30 @@ static void find_button_clicked(GtkWidget *button, gpointer) { if (strlen(find_text) == 0) return; if (button == fnext_button || button == fprev_button) { find_add_to_history(find_text, find_store); - l_find(find_text, button == fnext_button); + l_handle_event("find", LUA_TSTRING, find_text, LUA_TBOOLEAN, + button == fnext_button, -1); } else { find_add_to_history(repl_text, repl_store); if (button == r_button) { - l_find_replace(repl_text); - l_find(find_text, true); - } else l_find_replace_all(find_text, repl_text); + l_handle_event("replace", LUA_TSTRING, repl_text, -1); + l_handle_event("find", LUA_TSTRING, find_text, LUA_TBOOLEAN, 1, -1); + } else + l_handle_event("replace_all", LUA_TSTRING, find_text, LUA_TSTRING, + repl_text, -1); } } +/** + * Signal for a Find entry keypress. + */ +static gbool find_entry_keypress(GtkWidget *, GdkEventKey *event, gpointer) { + return l_handle_event("find_keypress", LUA_TNUMBER, event->keyval, -1); +} + // Command Entry /** - * Toggles focus between a Scintilla window and the Lua command entry. + * Toggles focus between a Scintilla window and the Command Entry. * When the entry is visible, the statusbars are temporarily hidden. */ void ce_toggle_focus() { @@ -977,30 +953,18 @@ static gbool cec_match_selected(GtkEntryCompletion *, GtkTreeModel *model, // Signals /** - * Signal for the 'enter' key being pressed in the Lua command entry. - * Evaluates the input text as Lua code. + * Signal for the 'enter' key being pressed in the Command Entry. */ static void c_activated(GtkWidget *widget, gpointer) { - l_ce_command(gtk_entry_get_text(GTK_ENTRY(widget))); + l_handle_event("command_entry_command", LUA_TSTRING, + gtk_entry_get_text(GTK_ENTRY(widget)), -1); 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 - Display possible completions. + * Signal for a keypress inside the Command Entry. */ static gbool c_keypress(GtkWidget *, GdkEventKey *event, gpointer) { - if (event->state == 0) - switch(event->keyval) { - case 0xff1b: - ce_toggle_focus(); - return TRUE; - case 0xff09: - l_cec_fill(cec_store); - gtk_entry_completion_complete(command_entry_completion); - return TRUE; - } - return FALSE; + return l_handle_event("command_entry_keypress", LUA_TNUMBER, event->keyval, + -1); } diff --git a/src/textadept.h b/src/textadept.h index 666c361d..73064fdf 100644 --- a/src/textadept.h +++ b/src/textadept.h @@ -3,6 +3,7 @@ #ifndef TEXTADEPT_H #define TEXTADEPT_H +#include <stdarg.h> #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -62,23 +63,11 @@ void l_remove_scintilla_buffer(sptr_t doc); void l_goto_scintilla_buffer(GtkWidget *editor, int n, bool absolute); void l_set_buffer_global(ScintillaObject *sci); -void l_handle_error(const char *errmsg); -bool l_handle_event(const char *e, const char *arg=NULL); -bool l_handle_keypress(int keyval, bool shift, bool control, bool alt); +bool l_handle_event(const char *e, ...); void l_handle_scnnotification(SCNotification *n); void l_ta_popup_context_menu(GdkEventButton *event); -void l_pm_view_fill(GtkTreeStore *store, GtkTreeIter *initial_iter); -void l_pm_perform_action(GtkTreeStore *store, GtkTreePath *path); -void l_pm_popup_context_menu(GtkTreeStore *store, GtkTreePath *path, - GdkEventButton *event, GCallback callback); -void l_pm_perform_menu_action(GtkTreeStore *store, GtkTreePath *path, int id); - -void l_find(const char *ftext, bool next); -void l_find_replace(const char *rtext); -void l_find_replace_all(const char *ftext, const char *rtext); - -void l_ce_command(const char *command); -void l_cec_fill(GtkListStore *store); +int l_pm_pathtableref(GtkTreeStore *store, GtkTreePath *path); +void l_pm_popup_context_menu(GdkEventButton *event); #endif |