diff options
-rw-r--r-- | doc/manual.md | 4 | ||||
-rw-r--r-- | modules/textadept/command_entry.lua | 39 | ||||
-rw-r--r-- | test/test.lua | 62 |
3 files changed, 98 insertions, 7 deletions
diff --git a/doc/manual.md b/doc/manual.md index c8769162..91cb0f8b 100644 --- a/doc/manual.md +++ b/doc/manual.md @@ -1344,6 +1344,7 @@ simply use `split`. Finally, these commands are runnable on startup using the `-e` and `--execute` command line switches. Pressing `Ctrl+H` (`^H` | `M-H` or `M-S-H`) shows help for the current command. +Pressing `Up` or `Down` cycles through command history.  @@ -1366,7 +1367,8 @@ available completions. Use the arrow keys to make a selection and press `Enter` Executing Lua commands is just one of the many "modes" the command entry has. The [command entry API documentation][] has more information on modes and how to -create new ones. +create new ones. Each mode has its own history that can be cycled through using +the `Up` or `Down` keys. [command entry API documentation]: api.html#ui.command_entry diff --git a/modules/textadept/command_entry.lua b/modules/textadept/command_entry.lua index 347f6ab0..168b53f5 100644 --- a/modules/textadept/command_entry.lua +++ b/modules/textadept/command_entry.lua @@ -6,13 +6,33 @@ local M = ui.command_entry --[[ This comment is for LuaDoc. --- -- Textadept's Command Entry. --- It supports multiple modes that each have their own functionality, such as +-- It supports multiple modes that each have their own functionality (such as -- running Lua code, searching for text incrementally, and filtering text --- through shell commands. +-- through shell commands) and history. -- @field height (number) -- The height in pixels of the command entry. module('ui.command_entry')]] +-- Command history per mode. +-- The current mode is in the `mode` field. +-- @class table +-- @name history +local history = {} + +-- Cycles through command history for the current mode. +-- @param prev Flag that indicates whether to cycle to the previous command or +-- the next one. +local function cycle_history(prev) + if M:auto_c_active() then M[prev and 'line_up' or 'line_down'](M) return end + local mode_history = history[history.mode] + if not mode_history or prev and mode_history.pos <= 1 then return end + if not prev and mode_history.pos >= #mode_history then return end + M:line_delete() + local i, bound = prev and -1 or 1, prev and 1 or #mode_history + mode_history.pos = math[prev and 'max' or 'min'](mode_history.pos + i, bound) + M:add_text(mode_history[mode_history.pos]) +end + --- -- A metatable with typical platform-specific key bindings for text entries. -- This metatable may be used to add basic editing and movement keys to command @@ -31,6 +51,9 @@ M.editing_keys = {__index = { [not OSX and 'cz' or 'mz'] = function() M:undo() end, [not OSX and 'cZ' or 'mZ'] = function() M:redo() end, [not OSX and 'cy' or '\0'] = function() M:redo() end, + up = function() cycle_history(true) end, down = cycle_history, + [(OSX or CURSES) and 'cp' or '\0'] = function() cycle_history(true) end, + [(OSX or CURSES) and 'cn' or '\0'] = cycle_history, -- Movement keys. [(OSX or CURSES) and 'cf' or '\0'] = function() M:char_right() end, [(OSX or CURSES) and 'cb' or '\0'] = function() M:char_left() end, @@ -104,7 +127,7 @@ local function complete_lua() if (not ok or type(result) ~= 'table') and symbol ~= '' then return end local cmpls = {} part = '^' .. part - local sep = string.char(buffer.auto_c_type_separator) + local sep = string.char(M.auto_c_type_separator) local XPM = textadept.editing.XPM_IMAGES if not ok or symbol == 'buffer' then local sci = _SCINTILLA @@ -180,11 +203,17 @@ function M.run(f, keys, lexer, height) keys['\n'] = function() if M:auto_c_active() then return false end -- allow Enter to autocomplete M.focus() -- hide - if f then f((M:get_text())) end + if not f then return end + local mode_history = history[history.mode] + mode_history[#mode_history + 1] = M:get_text() + mode_history.pos = #mode_history + 1 + f((M:get_text())) end end if not getmetatable(keys) then setmetatable(keys, M.editing_keys) end - M:select_all() + if f and not history[f] then history[f] = {pos = 0} end + history.mode = f + M:set_text('') M.focus() M:set_lexer(lexer or 'text') M.height = M:text_height(0) * (height or 1) diff --git a/test/test.lua b/test/test.lua index a584cacc..b884f188 100644 --- a/test/test.lua +++ b/test/test.lua @@ -1081,8 +1081,8 @@ function test_command_entry_run() end local function run_lua_command(command) - ui.command_entry:set_text(command) ui.command_entry.run() + ui.command_entry:set_text(command) assert_equal(ui.command_entry:get_lexer(), 'lua') events.emit(events.KEYPRESS, not CURSES and 0xFF0D or 343) -- \n end @@ -1131,6 +1131,11 @@ local function assert_lua_autocompletion(text, first_item) events.emit(events.KEYPRESS, string.byte('\t')) assert_equal(ui.command_entry:auto_c_active(), true) assert_equal(ui.command_entry.auto_c_current_text, first_item) + events.emit(events.KEYPRESS, not CURSES and 0xFF54 or 300) -- down + events.emit(events.KEYPRESS, not CURSES and 0xFF52 or 301) -- up + assert_equal(ui.command_entry:get_text(), text) -- no history cycling + assert_equal(ui.command_entry:auto_c_active(), true) + assert_equal(ui.command_entry.auto_c_current_text, first_item) ui.command_entry:auto_c_cancel() end @@ -1146,6 +1151,61 @@ function test_command_entry_complete_lua() ui.command_entry:focus() -- hide end +function test_command_entry_history() + local one, two = function() end, function() end + + ui.command_entry.run(one) + events.emit(events.KEYPRESS, not CURSES and 0xFF52 or 301) -- up + assert_equal(ui.command_entry:get_text(), '') -- no prior history + events.emit(events.KEYPRESS, not CURSES and 0xFF54 or 300) -- down + assert_equal(ui.command_entry:get_text(), '') -- no further history + ui.command_entry:add_text('foo') + events.emit(events.KEYPRESS, not CURSES and 0xFF0D or 343) -- \n + + ui.command_entry.run(two) + events.emit(events.KEYPRESS, not CURSES and 0xFF52 or 301) -- up + assert_equal(ui.command_entry:get_text(), '') -- no prior history + events.emit(events.KEYPRESS, not CURSES and 0xFF54 or 300) -- down + assert_equal(ui.command_entry:get_text(), '') -- no further history + ui.command_entry:add_text('bar') + events.emit(events.KEYPRESS, not CURSES and 0xFF0D or 343) -- \n + + ui.command_entry.run(one) + assert_equal(ui.command_entry:get_text(), '') + events.emit(events.KEYPRESS, not CURSES and 0xFF52 or 301) -- up + assert_equal(ui.command_entry:get_text(), 'foo') + events.emit(events.KEYPRESS, not CURSES and 0xFF52 or 301) -- up + assert_equal(ui.command_entry:get_text(), 'foo') -- no prior history + events.emit(events.KEYPRESS, not CURSES and 0xFF54 or 300) -- down + assert_equal(ui.command_entry:get_text(), 'foo') -- no further history + ui.command_entry:set_text('baz') + events.emit(events.KEYPRESS, not CURSES and 0xFF0D or 343) -- \n + + ui.command_entry.run(one) + events.emit(events.KEYPRESS, not CURSES and 0xFF52 or 301) -- up + assert_equal(ui.command_entry:get_text(), 'baz') + events.emit(events.KEYPRESS, not CURSES and 0xFF52 or 301) -- up + assert_equal(ui.command_entry:get_text(), 'foo') + events.emit(events.KEYPRESS, not CURSES and 0xFF54 or 300) -- down + assert_equal(ui.command_entry:get_text(), 'baz') + events.emit(events.KEYPRESS, not CURSES and 0xFF52 or 301) -- up, 'foo' + events.emit(events.KEYPRESS, not CURSES and 0xFF0D or 343) -- \n + + ui.command_entry.run(one) + events.emit(events.KEYPRESS, not CURSES and 0xFF52 or 301) -- up + assert_equal(ui.command_entry:get_text(), 'foo') + events.emit(events.KEYPRESS, not CURSES and 0xFF52 or 301) -- up + assert_equal(ui.command_entry:get_text(), 'baz') + events.emit(events.KEYPRESS, not CURSES and 0xFF52 or 301) -- up + assert_equal(ui.command_entry:get_text(), 'foo') + events.emit(events.KEYPRESS, not CURSES and 0xFF1B or 7) -- esc + + ui.command_entry.run(two) + events.emit(events.KEYPRESS, not CURSES and 0xFF52 or 301) -- up + assert_equal(ui.command_entry:get_text(), 'bar') + events.emit(events.KEYPRESS, not CURSES and 0xFF1B or 7) -- esc +end + function test_editing_auto_pair() buffer.new() -- Single selection. |