diff options
author | 2013-04-11 23:58:33 -0400 | |
---|---|---|
committer | 2013-04-11 23:58:33 -0400 | |
commit | a65f9ac0c460fba64b2d8ddde2149661c025ea65 (patch) | |
tree | 9f1d3e7b73756b264974d0be272a722fc92e483e | |
parent | 2a54e9fcfd955b8cf63fa42f41292d1176026721 (diff) | |
download | textadept-a65f9ac0c460fba64b2d8ddde2149661c025ea65.tar.gz textadept-a65f9ac0c460fba64b2d8ddde2149661c025ea65.zip |
Added key modes and changed the command entry to use them.
Removed obsoleted `events.COMMAND_ENTRY_COMMAND`.
-rw-r--r-- | core/events.lua | 7 | ||||
-rw-r--r-- | core/keys.lua | 122 | ||||
-rw-r--r-- | doc/14_Appendix.md | 2 | ||||
-rw-r--r-- | modules/textadept/command_entry.lua | 172 | ||||
-rw-r--r-- | modules/textadept/filter_through.lua | 87 | ||||
-rw-r--r-- | modules/textadept/find.lua | 129 | ||||
-rw-r--r-- | modules/textadept/keys.lua | 28 | ||||
-rw-r--r-- | modules/textadept/menu.lua | 4 | ||||
-rw-r--r-- | src/textadept.c | 41 |
9 files changed, 337 insertions, 255 deletions
diff --git a/core/events.lua b/core/events.lua index f7b2e997..6f755110 100644 --- a/core/events.lua +++ b/core/events.lua @@ -65,13 +65,6 @@ local M = {} -- Arguments: -- -- * _`ch`_: The text character byte. --- @field COMMAND_ENTRY_COMMAND (string) --- Called to run the command entered into the Command Entry. --- If any handler returns `true`, the Command Entry does not hide --- automatically. --- Arguments: --- --- * _`command`_: The command text. -- @field COMMAND_ENTRY_KEYPRESS (string) -- Called when pressing a key in the Command Entry. -- If any handler returns `true`, the key is not inserted into the entry. diff --git a/core/keys.lua b/core/keys.lua index f1bfb7d1..794c3001 100644 --- a/core/keys.lua +++ b/core/keys.lua @@ -11,13 +11,15 @@ local M = {} -- Key bindings are defined in the global table `keys`. Each key-value pair in -- `keys` consists of either a string key sequence and its associated command, -- a string lexer language (from the *lexers/* directory) with a table of key --- sequences and commands, or a key sequence with a table of more sequences and --- commands. The latter is part of what is called a "key chain". When searching --- for a command to run based on a key sequence, key bindings in the current --- lexer have priority, followed by the ones in the global table. This means if --- there are two commands with the same key sequence, the one specific to the --- current lexer is run. However, if the command returns the boolean value --- `false`, the lower-priority command is also run. (This is useful for +-- sequences and commands, a string key mode with a table of key sequences and +-- commands, or a key sequence with a table of more sequences and commands. The +-- latter is part of what is called a "key chain", to be discussed below. When +-- searching for a command to run based on a key sequence, key bindings in the +-- current key mode have priority. If no key mode is active, key bindings in the +-- current lexer have priority, followed by the ones in the global table. This +-- means if there are two commands with the same key sequence, the one specific +-- to the current lexer is run. However, if the command returns the boolean +-- value `false`, the lower-priority command is also run. (This is useful for -- language-specific modules to override commands like Adeptsense -- autocompletion, but fall back to word autocompletion if the first command -- fails.) @@ -52,18 +54,45 @@ local M = {} -- -- ## Commands -- --- Commands associated with key sequences can be either Lua functions, or --- tables containing Lua functions with a set of arguments to call the function --- with. Examples are: +-- Commands bound to key sequences can be either Lua functions, or tables +-- containing Lua functions with a set of arguments to call the function with. +-- Examples are: -- -- keys['cn'] = new_buffer -- keys['cs'] = buffer.save -- keys['a('] = {_M.textadept.editing.enclose, '(', ')'} +-- keys['cu'] = function() io.snapopen(_USERHOME) end -- --- Note that [`buffer`][] references are handled properly. +-- (The function and function table syntax are functionally equivalent. You can +-- use either.) +-- +-- [`buffer`][] references are handled properly in static contexts. -- -- [`buffer`]: buffer.html -- +-- ## Modes +-- +-- Sets of key bindings can be grouped together into modes. When a key +-- [mode](#MODE) is active, all key bindings defined outside the mode are +-- ignored until the mode is unset. Here is a simple vi mode example: +-- +-- keys.command_mode = { +-- ['h'] = buffer.char_left, +-- ['j'] = buffer.line_up, +-- ['k'] = buffer.line_down, +-- ['l'] = buffer.char_right, +-- ['i'] = function() +-- keys.MODE = nil +-- gui.statusbar_text = 'INSERT MODE' +-- end +-- } +-- keys['esc'] = function() keys.MODE = 'command_mode' end +-- events.connect(events.UPDATE_UI, function() +-- if keys.MODE == 'command_mode' then return end +-- gui.statusbar_text = 'INSERT MODE' +-- end) +-- keys.MODE = 'command_mode' -- default mode +-- -- ## Key Chains -- -- Key chains are a powerful concept. They allow multiple key bindings to be @@ -86,6 +115,11 @@ local M = {} -- The starting key of the key chain reserved for language-specific modules. -- The default value is `'cl'` on platforms other than Mac OSX, `'ml'` -- otherwise. Equivalent to `Ctrl+L` (`⌘L` on Mac OSX | `M-L` in curses). +-- @field MODE (string) +-- The current key mode. +-- When non-`nil`, all key bindings defined outside of `keys[MODE]` are +-- ignored. +-- The default value is `nil`. module('keys')]] local ADD = '' @@ -112,9 +146,9 @@ local error = function(e) events.emit(events.ERROR, e) end -- @name KEYSYMS M.KEYSYMS = { -- From Scintilla.h and cdk/curdefs.h. - [7] = 'esc', [8] = '\b', [9] = '\t', [13] = '\n', [27] = 'esc', [127] = 'del', + [7] = 'esc', -- From curses.h. - [263] = '\b', + [263] = '\b', [343] = '\n', -- From Scintilla.h. [300] = 'down', [301] = 'up', [302] = 'left', [303] = 'right', [304] = 'home', [305] = 'end', @@ -160,6 +194,8 @@ local function clear_key_sequence() -- Clearing a table is faster than re-creating one. if #keychain == 1 then keychain[1] = nil else keychain = {} end end +-- Export for command_entry.lua without creating LuaDoc. +if CURSES then M.clear_key_sequence = clear_key_sequence end -- Runs a given command. -- This is also used by *modules/textadept/menu.lua*. @@ -185,36 +221,30 @@ local function run_command(command, command_type) end M.run_command = run_command -- export for menu.lua without creating LuaDoc --- Return codes for `run_key_command()`. +-- Return codes for `key_command()`. local INVALID, PROPAGATE, CHAIN, HALT = -1, 0, 1, 2 -- Runs a key command associated with the current keychain. --- @param lexer Optional lexer name for lexer-specific commands. +-- @param prefix Optional prefix name for mode/lexer-specific commands. -- @return `INVALID`, `PROPAGATE`, `CHAIN`, or `HALT`. -local function run_key_command(lexer) - local key, key_type = keys, type(keys) - if lexer and key_type == 'table' and key[lexer] then key = key[lexer] end - if type(key) ~= 'table' then return INVALID end - - key = key[keychain[1]] - for i = 2, #keychain do +local function key_command(prefix) + local key = not prefix and M or M[prefix] + for i = 1, #keychain do if type(key) ~= 'table' then return INVALID end key = key[keychain[i]] end - key_type = type(key) - + local key_type = type(key) if key_type ~= 'function' and key_type ~= 'table' then return INVALID end if key_type == 'table' and #key == 0 and next(key) then gui.statusbar_text = _L['Keychain:']..' '..table.concat(keychain, ' ') return CHAIN end - return run_command(key, key_type) == false and PROPAGATE or HALT end -- Handles Textadept keypresses. --- It is called every time a key is pressed, and based on lexer, executes a --- command. The command is looked up in the `_G.keys` table. +-- It is called every time a key is pressed, and based on a mode or lexer, +-- executes a command. The command is looked up in the `_G.keys` table. -- @param code The keycode. -- @param shift Whether or not the Shift modifier is pressed. -- @param control Whether or not the Control modifier is pressed. @@ -222,40 +252,34 @@ end -- @param meta Whether or not the Command modifier on Mac OSX is pressed. -- @return `true` to stop handling the key; `nil` otherwise. local function keypress(code, shift, control, alt, meta) - local buffer = buffer - local key --print(code, M.KEYSYMS[code], shift, control, alt, meta) - if code < 256 then - key = (not CURSES or code ~= 7) and string_char(code) or M.KEYSYMS[code] - shift = shift and code < 32 -- for printable characters, key is upper case - else - key = M.KEYSYMS[code] - if not key then return end - end + local key = code < 256 and (not CURSES or code ~= 7) and string_char(code) or + M.KEYSYMS[code] + if not key then return end + shift = shift and (code >= 256 or code == 9) -- printable chars are uppercased local key_seq = (control and CTRL or '')..(alt and ALT or '').. (meta and OSX and META or '')..(shift and SHIFT or '')..key --print(key_seq) gui.statusbar_text = '' --if CURSES then gui.statusbar_text = '"'..key_seq..'"' end - if #keychain > 0 and key_seq == M.CLEAR then + local keychain_size = #keychain + if keychain_size > 0 and key_seq == M.CLEAR then clear_key_sequence() return true end - keychain[#keychain + 1] = key_seq + keychain[keychain_size + 1] = key_seq - local success - for i = 1, 2 do - local status = run_key_command(i == 1 and buffer:get_lexer(true)) - if status > 0 then -- CHAIN or HALT - if status == HALT then clear_key_sequence() end - return true - end - success = success or status ~= -1 + local status = PROPAGATE + if not M.MODE then + status = key_command(buffer:get_lexer(true)) + if status <= PROPAGATE and not M.MODE then status = key_command() end + else + status = key_command(M.MODE) end - local size = #keychain - clear_key_sequence() - if not success and size > 1 then -- INVALID keychain sequence + if status ~= CHAIN then clear_key_sequence() end + if status > PROPAGATE then return true end -- CHAIN or HALT + if status == INVALID and keychain_size > 0 then gui.statusbar_text = _L['Invalid sequence'] return true end diff --git a/doc/14_Appendix.md b/doc/14_Appendix.md index d24e8c4e..1e4fd663 100644 --- a/doc/14_Appendix.md +++ b/doc/14_Appendix.md @@ -104,7 +104,7 @@ As a special case, the empty capture `()` captures the current string position Textadept 5.5 beta introduced a curses version that can run in a terminal emulator. However, since curses is quite low-level in terms of graphics capability compared to GTK+, the curses version of Textadept lacks some features -in its editing component Scintilla: +in its editing component Scintilla and in general: * Any settings with alpha values are not supported. * Autocompletion lists cannot show images (pixmap surfaces are not supported). diff --git a/modules/textadept/command_entry.lua b/modules/textadept/command_entry.lua index 7e52282b..005af837 100644 --- a/modules/textadept/command_entry.lua +++ b/modules/textadept/command_entry.lua @@ -1,13 +1,79 @@ -- Copyright 2007-2013 Mitchell mitchell.att.foicica.com. See LICENSE. --- Modified by Jay Gould. +-- Abbreviated environment and commands from Jay Gould. + +local M = gui.command_entry --[[ This comment is for LuaDoc. --- -- Textadept's Command Entry. +-- +-- ## Modes +-- +-- The command entry supports multiple [modes][] that have their own sets of key +-- bindings stored in a separate table in `_G.keys` under a mode prefix key. +-- Mode names are arbitrary, but cannot conflict with lexer names or key +-- sequence strings (e.g. `'lua'` or `'send'`) due to the method used for +-- looking up key bindings. An example mode is "lua_command" mode for executing +-- Lua commands: +-- +-- local gui_ce = gui.command_entry +-- keys['ce'] = {gui_ce.enter_mode, 'lua_command'} +-- keys.lua_command = { +-- ['\t'] = gui_ce.complete_lua, +-- ['\n'] = {gui_ce.finish_mode, gui_ce.execute_lua} +-- } +-- +-- In this case, `Ctrl+E` opens the command entry and enters "lua_command" key +-- mode where the `Tab` and `Enter` keys have special, defined functionality. +-- (By default, `Esc` is pre-defined to exit any command entry mode.) `Tab` +-- shows a list of Lua completions for the entry text and `Enter` exits +-- "lua_command" key mode and executes the entered code. All other keys are +-- handled normally by the command entry. +-- +-- It is important to note that while in any command entry key mode, all editor +-- key bindings are ignored -- even if the editor, not the command entry, has +-- focus. You must first exit the key mode by pressing `Esc` (or `Enter` in the +-- case of the above "lua_command" key mode). +-- +-- [modes]: keys.html#Modes -- @field entry_text (string) -- The text in the entry. module('gui.command_entry')]] +--- +-- Opens the command entry in key mode *mode*. +-- Key bindings will be looked up in `keys[mode]` instead of `keys`. The `Esc` +-- (`⎋` on Mac OSX | `Esc` in curses) key exits the current mode, closes the +-- command entry, and restores normal key lookup. +-- This function is useful for binding keys to enter a command entry mode. +-- @param mode The key mode to enter into, or `nil` to exit the current mode. +-- @usage keys['ce'] = {gui.command_entry.enter_mode, 'command_entry'} +-- @see _G.keys.MODE +-- @name enter_mode +function M.enter_mode(mode) + keys.MODE = mode + if mode then keys[mode]['esc'] = M.enter_mode end + -- In curses, M.focus() does not return immediately, so the key sequence that + -- called M.focus() is still on the keychain. Clear it. + if CURSES then keys.clear_key_sequence() end + M.focus() +end + +--- +-- Exits the current key mode, closes the command entry, and calls function *f* +-- with the command entry text as an argument. +-- This is useful for binding keys to exit a command entry mode and perform an +-- action with the entered text. +-- @param f The function to call. It should accept the command entry text as an +-- argument. +-- @usage keys['\n'] = {gui.command_entry.finish_mode, gui.print} +-- @name finish_mode +function M.finish_mode(f) + M.enter_mode(nil) + f(M.entry_text) + if CURSES then return false end -- propagate to exit CDK entry on Enter +end + -- Environment for abbreviated commands. -- @class table -- @name env @@ -29,71 +95,73 @@ local env = setmetatable({}, { end, }) -local events = events - --- Execute a Lua command. -events.connect(events.COMMAND_ENTRY_COMMAND, function(command) - local f, err = load(command, nil, 'bt', env) +--- +-- Executes string *code* as Lua code. +-- Code is subject to an "abbreviated" environment where the `buffer`, `view`, +-- and `gui` tables are also considered as globals. +-- @param code The Lua code to execute. +-- @name execute_lua +function M.execute_lua(code) + local f, err = load(code, nil, 'bt', env) if err then error(err) end - if not CURSES then gui.command_entry.focus() end -- toggle focus to hide f() events.emit(events.UPDATE_UI) -end) +end +args.register('-e', '--execute', 1, M.execute_lua, 'Execute Lua code') -events.connect(events.COMMAND_ENTRY_KEYPRESS, function(code) - if keys.KEYSYMS[code] == 'esc' then - gui.command_entry.focus() -- toggle focus to hide - elseif keys.KEYSYMS[code] == '\t' then - local substring = gui.command_entry.entry_text:match('[%w_.:]+$') or '' - local path, o, prefix = substring:match('^([%w_.:]-)([.:]?)([%w_]*)$') - local f, err = load('return ('..path..')', nil, 'bt', env) - local ok, tbl = pcall(f) - local cmpls = {} - prefix = '^'..prefix - if not ok then -- shorthand notation - for _, t in ipairs{buffer, view, gui, _G} do - for k in pairs(t) do - if type(k) == 'string' and k:find(prefix) then - cmpls[#cmpls + 1] = k - end - end +--- +-- Shows a set of Lua code completions for string *code* or `entry_text`. +-- Completions are subject to an "abbreviated" environment where the `buffer`, +-- `view`, and `gui` tables are also considered as globals. +-- @param code The Lua code to complete. The default value is the value of +-- `entry_text`. +-- @name complete_lua +function M.complete_lua(code) + local substring = (code or M.entry_text):match('[%w_.:]+$') or '' + local path, op, prefix = substring:match('^([%w_.:]-)([.:]?)([%w_]*)$') + local f, err = load('return ('..path..')', nil, 'bt', env) + local ok, tbl = pcall(f) + local cmpls = {} + prefix = '^'..prefix + if not ok then -- shorthand notation + for _, t in ipairs{buffer, view, gui, _G} do + for k in pairs(t) do + if type(k) == 'string' and k:find(prefix) then cmpls[#cmpls + 1] = k end end + end + for f in pairs(_SCINTILLA.functions) do + if f:find(prefix) then cmpls[#cmpls + 1] = f end + end + for p in pairs(_SCINTILLA.properties) do + if p:find(prefix) then cmpls[#cmpls + 1] = p end + end + else + if type(tbl) ~= 'table' then return end + for k in pairs(tbl) do + if type(k) == 'string' and k:find(prefix) then cmpls[#cmpls + 1] = k end + end + if path == 'buffer' and op == ':' then for f in pairs(_SCINTILLA.functions) do if f:find(prefix) then cmpls[#cmpls + 1] = f end end + elseif path == 'buffer' and op == '.' then for p in pairs(_SCINTILLA.properties) do if p:find(prefix) then cmpls[#cmpls + 1] = p end end - else - if type(tbl) ~= 'table' then return end - 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(_SCINTILLA.functions) do - if f:find(prefix) then cmpls[#cmpls + 1] = f end - end - else - for p in pairs(_SCINTILLA.properties) do - if p:find(prefix) then cmpls[#cmpls + 1] = p end - end - end - end end - table.sort(cmpls) - gui.command_entry.show_completions(cmpls) - return true end -end) - --- Executes Lua code on startup. -local function execute(command) - local f, err = load(command, nil, 'bt', env) - if err then error(err) end - f() + table.sort(cmpls) + M.show_completions(cmpls) + return true end -args.register('-e', '--execute', 1, execute, 'Execute Lua code') + +local events = events +-- Pass command entry keys to the default keypress handler. +-- Since the command entry is designed to be modal, command entry key bindings +-- should stay separate from editor key bindings. +events.connect(events.COMMAND_ENTRY_KEYPRESS, function(...) + if keys.MODE then return events.emit(events.KEYPRESS, ...) end +end) --[[ The function below is a Lua C function. diff --git a/modules/textadept/filter_through.lua b/modules/textadept/filter_through.lua index fbff074c..59cb77b0 100644 --- a/modules/textadept/filter_through.lua +++ b/modules/textadept/filter_through.lua @@ -9,11 +9,12 @@ module('_M.textadept.filter_through')]] local cat = not WIN32 and 'cat' or 'type' local tmpfile = _USERHOME..'/.ft' -local filter_through_active = false --- --- Prompts the user for a Linux, BSD, Mac OSX, or Windows shell command to --- filter text through with standard input (stdin) as follows: +-- Passes selected or all buffer text to string shell command *cmd* as standard +-- input (stdin) and replaces the input text with the command's standard output +-- (stdout). +-- Standard input is as follows: -- -- 1. If text is selected and spans multiple lines, all text on the lines -- containing the selection is used. However, if the end of the selection is at @@ -22,57 +23,39 @@ local filter_through_active = false -- 2. If text is selected and spans a single line, only the selected text is -- used. -- 3. If no text is selected, the entire buffer is used. --- --- The input text is replaced with the standard output (stdout) of the command. +-- @param cmd The Linux, BSD, Mac OSX, or Windows shell command to filter text +-- through. -- @name filter_through -function M.filter_through() - filter_through_active = true - gui.command_entry.focus() -end - -local events = events - -events.connect(events.COMMAND_ENTRY_KEYPRESS, function(code) - if filter_through_active and keys.KEYSYMS[code] == 'esc' then - filter_through_active = false - end -end, 1) -- place before command_entry.lua's handler (if necessary) - --- Filter through. -events.connect(events.COMMAND_ENTRY_COMMAND, function(text) - if filter_through_active then - local buffer = buffer - local s, e = buffer.selection_start, buffer.selection_end - local input - if s ~= e then -- use selected lines as input - local i, j = buffer:line_from_position(s), buffer:line_from_position(e) - if i < j then - s = buffer:position_from_line(i) - if buffer.column[e] > 0 then e = buffer:position_from_line(j + 1) end - end - input = buffer:text_range(s, e) - else -- use whole buffer as input - input = buffer:get_text() - end - local f = io.open(tmpfile, 'wb') - f:write(input) - f:close() - local cmd = cat..' "'..tmpfile..'" | '..text - if WIN32 then cmd = cmd:gsub('/', '\\') end - local p = io.popen(cmd) - if s ~= e then - buffer.target_start, buffer.target_end = s, e - buffer:replace_target(p:read('*all')) - buffer:set_sel(buffer.target_start, buffer.target_end) - else - buffer:set_text(p:read('*all')) - buffer:goto_pos(s) +function M.filter_through(cmd) + local buffer = buffer + local s, e = buffer.selection_start, buffer.selection_end + local input + if s ~= e then -- use selected lines as input + local i, j = buffer:line_from_position(s), buffer:line_from_position(e) + if i < j then + s = buffer:position_from_line(i) + if buffer.column[e] > 0 then e = buffer:position_from_line(j + 1) end end - p:close() - os.remove(tmpfile) - filter_through_active = false - return true + input = buffer:text_range(s, e) + else -- use whole buffer as input + input = buffer:get_text() end -end, 1) -- place before command_entry.lua's handler (if necessary) + local f = io.open(tmpfile, 'wb') + f:write(input) + f:close() + local cmd = cat..' "'..tmpfile..'" | '..cmd + if WIN32 then cmd = cmd:gsub('/', '\\') end + local p = io.popen(cmd) + if s ~= e then + buffer.target_start, buffer.target_end = s, e + buffer:replace_target(p:read('*all')) + buffer:set_sel(buffer.target_start, buffer.target_end) + else + buffer:set_text(p:read('*all')) + buffer:goto_pos(s) + end + p:close() + os.remove(tmpfile) +end return M diff --git a/modules/textadept/find.lua b/modules/textadept/find.lua index d1105a7b..801c333f 100644 --- a/modules/textadept/find.lua +++ b/modules/textadept/find.lua @@ -1,6 +1,6 @@ -- Copyright 2007-2013 Mitchell mitchell.att.foicica.com. See LICENSE. -local find = gui.find +local M = gui.find --[[ This comment is for LuaDoc. --- @@ -60,17 +60,17 @@ local find = gui.find module('gui.find')]] local _L = _L -find.find_label_text = not CURSES and _L['_Find:'] or _L['Find:'] -find.replace_label_text = not CURSES and _L['R_eplace:'] or _L['Replace:'] -find.find_next_button_text = not CURSES and _L['Find _Next'] or _L['[Next]'] -find.find_prev_button_text = not CURSES and _L['Find _Prev'] or _L['[Prev]'] -find.replace_button_text = not CURSES and _L['_Replace'] or _L['[Replace]'] -find.replace_all_button_text = not CURSES and _L['Replace _All'] or _L['[All]'] -find.match_case_label_text = not CURSES and _L['_Match case'] or _L['Case(F1)'] -find.whole_word_label_text = not CURSES and _L['_Whole word'] or _L['Word(F2)'] -find.lua_pattern_label_text = not CURSES and _L['_Lua pattern'] or - _L['Pattern(F3)'] -find.in_files_label_text = not CURSES and _L['_In files'] or _L['Files(F4)'] +M.find_label_text = not CURSES and _L['_Find:'] or _L['Find:'] +M.replace_label_text = not CURSES and _L['R_eplace:'] or _L['Replace:'] +M.find_next_button_text = not CURSES and _L['Find _Next'] or _L['[Next]'] +M.find_prev_button_text = not CURSES and _L['Find _Prev'] or _L['[Prev]'] +M.replace_button_text = not CURSES and _L['_Replace'] or _L['[Replace]'] +M.replace_all_button_text = not CURSES and _L['Replace _All'] or _L['[All]'] +M.match_case_label_text = not CURSES and _L['_Match case'] or _L['Case(F1)'] +M.whole_word_label_text = not CURSES and _L['_Whole word'] or _L['Word(F2)'] +M.lua_pattern_label_text = not CURSES and _L['_Lua pattern'] or + _L['Pattern(F3)'] +M.in_files_label_text = not CURSES and _L['_In files'] or _L['Files(F4)'] -- Events. local events, events_connect = events, events.connect @@ -94,7 +94,7 @@ local preferred_view -- @see find_in_files -- @class table -- @name FILTER -find.FILTER = lfs.FILTER +M.FILTER = lfs.FILTER -- Text escape sequences with their associated characters. -- @class table @@ -114,7 +114,7 @@ local escapes = { -- the user is prompted for one. -- @see FILTER -- @name find_in_files -function find.find_in_files(utf8_dir) +function M.find_in_files(utf8_dir) if not utf8_dir then utf8_dir = gui.dialog('fileselect', '--title', _L['Find in Files'], @@ -125,13 +125,13 @@ function find.find_in_files(utf8_dir) end if utf8_dir == '' then return end - local text = find.find_entry_text - if not find.lua then text = text:gsub('([().*+?^$%%[%]-])', '%%%1') end - if not find.match_case then text = text:lower() end - if find.whole_word then text = '%f[%w_]'..text..'%f[^%w_]' end + local text = M.find_entry_text + if not M.lua then text = text:gsub('([().*+?^$%%[%]-])', '%%%1') end + if not M.match_case then text = text:lower() end + if M.whole_word then text = '%f[%w_]'..text..'%f[^%w_]' end local matches = {_L['Find:']..' '..text} lfs.dir_foreach(utf8_dir, function(file) - local match_case = find.match_case + local match_case = M.match_case local line_num = 1 for line in io.lines(file) do if (match_case and line or line:lower()):find(text) then @@ -140,7 +140,7 @@ function find.find_in_files(utf8_dir) end line_num = line_num + 1 end - end, find.FILTER, true) + end, M.FILTER, true) if #matches == 1 then matches[2] = _L['No results found'] end matches[#matches + 1] = '' if buffer._type ~= _L['[Files Found Buffer]'] then preferred_view = view end @@ -174,14 +174,14 @@ local function find_(text, next, flags, nowrap, wrapped) if not flags then flags = 0 - if find.match_case then flags = flags + c.SCFIND_MATCHCASE end - if find.whole_word then flags = flags + c.SCFIND_WHOLEWORD end - if find.lua then flags = flags + 8 end - if find.in_files then flags = flags + 16 end + if M.match_case then flags = flags + c.SCFIND_MATCHCASE end + if M.whole_word then flags = flags + c.SCFIND_WHOLEWORD end + if M.lua then flags = flags + 8 end + if M.in_files then flags = flags + 16 end end local result - find.captures = nil + M.captures = nil if flags < 8 then buffer:goto_pos(buffer[next and 'current_pos' or 'anchor'] + increment) @@ -193,12 +193,12 @@ local function find_(text, next, flags, nowrap, wrapped) local buffer_text = buffer:get_text(buffer.length) local results = {buffer_text:find(text, buffer.anchor + increment + 1)} if #results > 0 then - find.captures = {table.unpack(results, 3)} + M.captures = {table.unpack(results, 3)} buffer:set_sel(results[2], results[1] - 1) end result = results[1] or -1 else -- find in files - find.find_in_files() + M.find_in_files() return end @@ -222,47 +222,52 @@ local function find_(text, next, flags, nowrap, wrapped) end events_connect(events.FIND, find_) --- Finds and selects text incrementally in the current buffer from a start --- point. +-- Finds and selects text incrementally in the current buffer from a starting +-- position. -- Flags other than `SCFIND_MATCHCASE` are ignored. -- @param text The text to find. -local function find_incremental(text) - local flags = find.match_case and c.SCFIND_MATCHCASE or 0 - buffer:goto_pos(find.incremental_start or 0) - find_(text, true, flags) +-- @param next Flag indicating whether or not the search direction is forward. +local function find_incremental(text, next) + buffer:goto_pos(M.incremental_start or 0) + find_(text, next, M.match_case and c.SCFIND_MATCHCASE or 0) end --- --- Begins an incremental find using the command entry. +-- Begins an incremental search using the command entry if *text* is `nil`; +-- otherwise continues an incremental search by searching for the next instance +-- of string *text*. -- Only the `match_case` find option is recognized. Normal command entry --- functionality will be unavailable until the search is finished by pressing --- `Esc` (`⎋` on Mac OSX | `Esc` in curses). +-- functionality is unavailable until the search is finished by pressing `Esc` +-- (`⎋` on Mac OSX | `Esc` in curses). +-- @param text The text to incrementally search for, or `nil` to begin an +-- incremental search. -- @name find_incremental -function find.find_incremental() - find.incremental, find.incremental_start = true, buffer.current_pos +function M.find_incremental(text) + if text then find_incremental(text, true) return end + M.incremental_start = buffer.current_pos gui.command_entry.entry_text = '' - gui.command_entry.focus() + gui.command_entry.enter_mode('find_incremental') end -events_connect(events.COMMAND_ENTRY_KEYPRESS, function(code) - if not find.incremental then return end - if keys.KEYSYMS[code] == 'esc' then - find.incremental = nil - elseif keys.KEYSYMS[code] == '\b' then - find_incremental(gui.command_entry.entry_text:sub(1, -2)) - elseif code < 256 then - find_incremental(gui.command_entry.entry_text..string.char(code)) - end -end, 1) -- place before command_entry.lua's handler (if necessary) - --- "Find next" for incremental search. -events_connect(events.COMMAND_ENTRY_COMMAND, function(text) - if find.incremental then - find.incremental_start = buffer.current_pos + 1 - find_incremental(text) - return true - end -end, 1) -- place before command_entry.lua's handler (if necessary) +--- +-- Continues an incremental search by searching for the next match starting from +-- the current position. +-- @see find_incremental +-- @name find_incremental_next +function M.find_incremental_next() + M.incremental_start = buffer.current_pos + 1 + find_incremental(gui.command_entry.entry_text, true) +end + +--- +-- Continues an incremental search by searching for the previous match starting +-- from the current position. +-- @see find_incremental +-- @name find_incremental_prev +function M.find_incremental_prev() + M.incremental_start = buffer.current_pos - 1 + find_incremental(gui.command_entry.entry_text, false) +end -- Optimize for speed. local load, pcall = load, pcall @@ -295,11 +300,11 @@ end -- @see find local function replace(rtext) if buffer:get_sel_text() == '' then return end - if find.in_files then find.in_files = false end + if M.in_files then M.in_files = false end local buffer = buffer buffer:target_from_selection() rtext = rtext:gsub('%%%%', '\\037') -- escape '%%' - local captures = find.captures + local captures = M.captures if captures then for i = 1, #captures do rtext = rtext:gsub('%%'..i, (captures[i]:gsub('%%', '%%%%'))) @@ -326,7 +331,7 @@ events_connect(events.REPLACE, replace) -- @see find local function replace_all(ftext, rtext) if ftext == '' then return end - if find.in_files then find.in_files = false end + if M.in_files then M.in_files = false end local buffer = buffer buffer:begin_undo_action() local count = 0 @@ -389,7 +394,7 @@ events_connect(events.DOUBLE_CLICK, goto_file) -- @param next Optional flag indicating whether or not to go to the next file. -- The default value is `false`. -- @name goto_file_in_list -function find.goto_file_in_list(next) +function M.goto_file_in_list(next) local orig_view = _VIEWS[view] for _, buffer in ipairs(_BUFFERS) do if buffer._type == _L['[Files Found Buffer]'] then diff --git a/modules/textadept/keys.lua b/modules/textadept/keys.lua index 8fa15a19..e2d41770 100644 --- a/modules/textadept/keys.lua +++ b/modules/textadept/keys.lua @@ -298,7 +298,8 @@ events.connect(events.BUFFER_NEW, constantize_menu_buffer_functions) if not RESETTING then constantize_menu_buffer_functions() end local keys = keys -local io, gui, gui_find, buffer, view = io, gui, gui.find, buffer, view +local io, gui, gui_find, gui_ce = io, gui, gui.find, gui.command_entry +local buffer, view = buffer, view local m_textadept, m_editing = _M.textadept, _M.textadept.editing local m_bookmarks, m_snippets = m_textadept.bookmarks, m_textadept.snippets local OSX, c = OSX, _SCINTILLA.constants @@ -466,13 +467,13 @@ keys[not OSX and 'cj' or 'mj'] = m_editing.goto_line -- Tools. keys[not OSX and (not CURSES and 'ce' or 'mc') - or 'me'] = gui.command_entry.focus + or 'me'] = {gui_ce.enter_mode, 'lua_command'} keys[not OSX and (not CURSES and 'cE' or 'mC') or 'mE'] = utils.select_command keys[not OSX and 'cr' or 'mr'] = m_textadept.run.run keys[not OSX and (not CURSES and 'cR' or 'cmr') or 'mR'] = m_textadept.run.compile keys[not OSX and (not CURSES and 'c|' or 'c\\') - or 'm|'] = m_textadept.filter_through.filter_through + or 'm|'] = {gui_ce.enter_mode, 'filter_through'} -- Adeptsense. keys[not OSX and (not CURSES and 'c ' or 'c@') or 'aesc'] = m_textadept.adeptsense.complete @@ -594,4 +595,25 @@ elseif CURSES then keys.ck = utils.cut_to_eol end +-- Modes. +keys.lua_command = { + ['\t'] = gui_ce.complete_lua, + ['\n'] = {gui_ce.finish_mode, gui_ce.execute_lua} +} +keys.filter_through = { + ['\n'] = {gui_ce.finish_mode, m_textadept.filter_through.filter_through}, +} +keys.find_incremental = { + ['\n'] = gui_find.find_incremental_next, + ['cr'] = gui_find.find_incremental_prev, + ['\b'] = function() + gui_find.find_incremental(gui_ce.entry_text:sub(1, -2)) + return false -- propagate + end +} +setmetatable(keys.find_incremental, {__index = function(t, k) + if #k > 1 and k:find('^[cams]*.+$') then return end + gui_find.find_incremental(gui_ce.entry_text..k) + end}) + return M diff --git a/modules/textadept/menu.lua b/modules/textadept/menu.lua index f4784b5d..583b3f46 100644 --- a/modules/textadept/menu.lua +++ b/modules/textadept/menu.lua @@ -119,12 +119,12 @@ M.menubar = { {_L['_Jump to'], m_editing.goto_line}, }, { title = _L['_Tools'], - {_L['Command _Entry'], gui.command_entry.focus}, + {_L['Command _Entry'], {gui.command_entry.enter_mode, 'lua_command'}}, {_L['Select Co_mmand'], utils.select_command}, SEPARATOR, {_L['_Run'], m_textadept.run.run}, {_L['_Compile'], m_textadept.run.compile}, - {_L['_Filter Through'], _M.textadept.filter_through.filter_through}, + {_L['_Filter Through'], {gui.command_entry.enter_mode, 'filter_through'}}, SEPARATOR, { title = _L['_Adeptsense'], {_L['_Complete Symbol'], m_textadept.adeptsense.complete}, diff --git a/src/textadept.c b/src/textadept.c index 1e913aae..301c061d 100644 --- a/src/textadept.c +++ b/src/textadept.c @@ -549,33 +549,24 @@ static int lfind__newindex(lua_State *L) { * As a PROCESSFN, returns `TRUE` to continue key propagation. */ static int c_keypress(EObjectType _, void *object, void *data, chtype key) { - if (data && (key == KEY_ENTER || key == KEY_TAB)) return TRUE; - int ret = TRUE; - if (key == KEY_ENTER) { - fcopy(&command_text, getCDKEntryValue((CDKENTRY *)object)); - ret = lL_event(lua, "command_entry_command", LUA_TSTRING, command_text, -1); - } else { - int ctrl = key < 0x20 && key != 9 && key != 10 && key != 13 && key != 27; - if (ctrl) key = tolower(key ^ 0x40); - // TODO: F1-F12. - ret = !lL_event(lua, "command_entry_keypress", LUA_TNUMBER, key, - LUA_TBOOLEAN, FALSE, LUA_TBOOLEAN, ctrl, -1); - } + if (!data && key == KEY_TAB) return TRUE; // do not exit on Tab + int ctrl = key < 0x20 && key != 9 && key != 27; + if (ctrl) key = tolower(key ^ 0x40); + if (key == 27) key = SCK_ESCAPE; + int halt = lL_event(lua, "command_entry_keypress", LUA_TNUMBER, key, + LUA_TBOOLEAN, FALSE, LUA_TBOOLEAN, ctrl, -1); scintilla_refresh(focused_view), drawCDKEntry((CDKENTRY *)object, FALSE); - return key == KEY_TAB || ret; + return !halt || key == SCK_ESCAPE; } #endif /** `command_entry.focus()` Lua function. */ static int lce_focus(lua_State *L) { #if GTK - if (!gtk_widget_has_focus(command_entry)) { - gtk_widget_show(command_entry); - gtk_widget_grab_focus(command_entry); - } else { - gtk_widget_hide(command_entry); - gtk_widget_grab_focus(focused_view); - } + if (!gtk_widget_get_visible(command_entry)) + gtk_widget_show(command_entry), gtk_widget_grab_focus(command_entry); + else + gtk_widget_hide(command_entry), gtk_widget_grab_focus(focused_view); #elif CURSES if (command_entry) return 0; // already active CDKSCREEN *screen = initCDKScreen(newwin(1, 0, LINES - 2, 0)); @@ -585,7 +576,6 @@ static int lce_focus(lua_State *L) { command_entry = newCDKEntry(screen, LEFT, TOP, NULL, NULL, A_NORMAL, '_', vMIXED, 0, 0, 256, FALSE, FALSE); bindCDKObject(vENTRY, command_entry, KEY_TAB, c_keypress, NULL); - bindCDKObject(vENTRY, command_entry, KEY_ENTER, c_keypress, NULL); setCDKEntryPreProcess(command_entry, c_keypress, ""); setCDKEntryValue(command_entry, command_text); char *clipboard = get_clipboard(); @@ -607,12 +597,15 @@ static int lce_show_completions(lua_State *L) { luaL_checktype(L, 1, LUA_TTABLE); int len = lua_rawlen(L, 1); #if GTK + if (!gtk_widget_get_visible(command_entry)) + luaL_error(L, "command entry inactive"); 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); #elif CURSES + if (!command_entry) luaL_error(L, "command entry inactive"); const char **items = malloc(len * sizeof(const char *)); int width = 0; #endif @@ -2073,11 +2066,6 @@ static GtkWidget *new_findbox() { return findbox; } -/** Signal for the 'enter' key being pressed in the Command Entry. */ -static void c_activate(GtkWidget*_, void*__) { - lL_event(lua, "command_entry_command", LUA_TSTRING, command_text, -1); -} - /** Signal for a keypress inside the Command Entry. */ static int c_keypress(GtkWidget*_, GdkEventKey *event, void*__) { return lL_event(lua, "command_entry_keypress", LUA_TNUMBER, event->keyval, @@ -2167,7 +2155,6 @@ static void new_window() { gtk_box_pack_start(GTK_BOX(vbox), new_findbox(), FALSE, FALSE, 5); command_entry = gtk_entry_new(); - signal(command_entry, "activate", c_activate); signal(command_entry, "key-press-event", c_keypress); gtk_box_pack_start(GTK_BOX(vbox), command_entry, FALSE, FALSE, 0); |