aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/events.lua7
-rw-r--r--core/keys.lua122
-rw-r--r--doc/14_Appendix.md2
-rw-r--r--modules/textadept/command_entry.lua172
-rw-r--r--modules/textadept/filter_through.lua87
-rw-r--r--modules/textadept/find.lua129
-rw-r--r--modules/textadept/keys.lua28
-rw-r--r--modules/textadept/menu.lua4
-rw-r--r--src/textadept.c41
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);