mirror of
https://github.com/neovim/neovim.git
synced 2026-07-09 19:09:39 +00:00
Merge #40434 from justinmk/fixcmdwin
This commit is contained in:
@@ -18,12 +18,13 @@ local state = nil
|
||||
local cmdwin_types = { [':'] = true, ['/'] = true, ['?'] = true }
|
||||
|
||||
--- Fills the cmdwin buffer with the cmdline history.
|
||||
--- @return boolean filled Whether any lines were written.
|
||||
local function fill_history(buf, type)
|
||||
local histname = type == ':' and 'cmd' or (type == '/' or type == '?') and 'search' or nil
|
||||
assert(histname, 'cmdwin: unknown type: ' .. tostring(type))
|
||||
local n = vim.fn.histnr(histname)
|
||||
if n <= 0 then -- May be -1 if history is empty.
|
||||
return
|
||||
return false
|
||||
end
|
||||
local lines = {} --- @type string[]
|
||||
for i = 1, n do
|
||||
@@ -33,9 +34,11 @@ local function fill_history(buf, type)
|
||||
lines[#lines + 1] = (h:gsub('\n', '\0'))
|
||||
end
|
||||
end
|
||||
if #lines > 0 then
|
||||
vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines)
|
||||
if #lines == 0 then
|
||||
return false
|
||||
end
|
||||
vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines)
|
||||
return true
|
||||
end
|
||||
|
||||
--- Open the command-line window.
|
||||
@@ -71,16 +74,16 @@ function M.open(type, init_line, init_col)
|
||||
vim.bo[buf].buftype = 'nofile'
|
||||
vim.bo[buf].bufhidden = 'wipe'
|
||||
vim.bo[buf].swapfile = false
|
||||
vim.bo[buf].buflisted = false
|
||||
vim.bo[buf].buflisted = true -- #40431
|
||||
vim.wo[win][0].winfixbuf = true
|
||||
vim.wo[win][0].foldenable = false
|
||||
-- Show cmdwin-char via 'statuscolumn'.
|
||||
vim.wo[win][0].statuscolumn = '%#NonText#' .. type .. ' '
|
||||
vim.wo[win][0].statuscolumn = '%#NonText#' .. type
|
||||
|
||||
fill_history(buf, type)
|
||||
local filled = fill_history(buf, type)
|
||||
|
||||
-- Append the in-flight cmdline (or empty placeholder) as the last line.
|
||||
vim.api.nvim_buf_set_lines(buf, -1, -1, false, { init_line or '' })
|
||||
-- Append the in-flight cmdline as the last line (or only line if history is empty).
|
||||
vim.api.nvim_buf_set_lines(buf, filled and -1 or 0, -1, false, { init_line or '' })
|
||||
local last = vim.api.nvim_buf_line_count(buf)
|
||||
vim.api.nvim_win_set_cursor(win, { last, math.max(0, (init_col or 1) - 1) })
|
||||
|
||||
@@ -98,20 +101,6 @@ function M.open(type, init_line, init_col)
|
||||
caller_win = caller,
|
||||
}
|
||||
|
||||
-- Buffer-local mappings.
|
||||
vim.keymap.set(
|
||||
{ 'n', 'i' },
|
||||
'<CR>',
|
||||
[[<C-\><C-N><Cmd>lua require'vim._core.cmdwin'.confirm()<CR>]],
|
||||
{ buffer = buf, silent = true, desc = 'cmdwin: execute' }
|
||||
)
|
||||
vim.keymap.set(
|
||||
{ 'n', 'i', 'x' },
|
||||
'<C-C>',
|
||||
[[<C-\><C-N><Cmd>lua require'vim._core.cmdwin'.cancel()<CR>]],
|
||||
{ buffer = buf, silent = true, desc = 'cmdwin: cancel' }
|
||||
)
|
||||
|
||||
-- Clean up if the window is closed by other means (`:q`, `:close`, etc.).
|
||||
vim.api.nvim_create_autocmd({ 'WinClosed' }, {
|
||||
buffer = buf,
|
||||
@@ -143,25 +132,30 @@ function M._cleanup()
|
||||
end
|
||||
end
|
||||
|
||||
local function _confirm()
|
||||
if state == nil then
|
||||
return
|
||||
end
|
||||
--- Closes the cmdwin and returns its current line and type.
|
||||
--- @return string line, string type
|
||||
local function _close()
|
||||
local line = vim.api.nvim_get_current_line()
|
||||
local type = state.type
|
||||
local type = assert(state).type
|
||||
M._cleanup()
|
||||
return line, type
|
||||
end
|
||||
|
||||
--- Confirm and execute the current line as a cmdline.
|
||||
function M.confirm()
|
||||
local line, type = _confirm()
|
||||
if state == nil then -- Not in cmdwin (closed already?).
|
||||
return
|
||||
end
|
||||
local line, type = _close()
|
||||
vim.api.nvim_feedkeys(type .. line .. vim.keycode('<CR>'), 'nt', false)
|
||||
end
|
||||
|
||||
--- Cancel: close the cmdwin and re-enter cmdline mode with the line pre-filled (no execute).
|
||||
function M.cancel()
|
||||
local line, type = _confirm()
|
||||
if state == nil then -- Not in cmdwin (closed already?).
|
||||
return
|
||||
end
|
||||
local line, type = _close()
|
||||
vim.api.nvim_feedkeys(type .. line, 'nt', false)
|
||||
end
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include "nvim/eval/vars.h"
|
||||
#include "nvim/ex_cmds_defs.h"
|
||||
#include "nvim/ex_docmd.h"
|
||||
#include "nvim/ex_getln.h"
|
||||
#include "nvim/extmark.h"
|
||||
#include "nvim/extmark_defs.h"
|
||||
#include "nvim/fileio.h"
|
||||
@@ -740,6 +741,11 @@ static int insert_handle_key(InsertState *s)
|
||||
FALLTHROUGH;
|
||||
|
||||
case Ctrl_C: // End input mode
|
||||
if (s->c == Ctrl_C && bt_cmdwin(curbuf)) {
|
||||
got_int = false; // Don't interrupt autocmds etc.
|
||||
cmdwin_do_action("cancel"); // cmdwin: CTRL-C closes cmdwin, pre-fills cmdline.
|
||||
return 0; // exit insert mode
|
||||
}
|
||||
if (s->c == Ctrl_C && bt_prompt(curbuf)) {
|
||||
if (invoke_prompt_interrupt()) {
|
||||
if (!bt_prompt(curbuf)) {
|
||||
@@ -1100,6 +1106,10 @@ static int insert_handle_key(InsertState *s)
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (s->c == CAR && bt_cmdwin(curbuf)) {
|
||||
cmdwin_do_action("confirm"); // cmdwin: <CR> executes the command under the cursor.
|
||||
return 0; // exit insert mode
|
||||
}
|
||||
if ((mod_mask & MOD_MASK_SHIFT) == 0 && bt_prompt(curbuf)) {
|
||||
prompt_invoke_callback();
|
||||
if (!bt_prompt(curbuf)) {
|
||||
|
||||
@@ -1487,7 +1487,7 @@ static int command_line_execute(VimState *state, int key)
|
||||
// confirms/cancels cmdwin, it will reopen cmdline with the command pre-filled.
|
||||
if (ex_normal_busy == 0 && got_int == false) {
|
||||
s->c = open_cmdwin();
|
||||
s->some_key_typed = true;
|
||||
s->some_key_typed = true; // Treat c_CTRL-F as "typed" to skip the wait-return prompt.
|
||||
}
|
||||
} else {
|
||||
s->c = do_digraph(s->c);
|
||||
@@ -4572,25 +4572,37 @@ const char *did_set_cedit(optset_T *args)
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
int firstc;
|
||||
char *content; // owned
|
||||
int pos;
|
||||
int firstc; ///< ':'/'/'/'?'.
|
||||
char *content; ///< Initial cmdline (owned).
|
||||
int pos; ///< Cursor column.
|
||||
} CmdwinOpenArgs;
|
||||
|
||||
/// Deferred event: opens cmdwin after the current cmdline-reader exits. (Can't use vim.schedule,
|
||||
/// where the event could fire _during typeahead_; revisit after #40380).
|
||||
/// Synchronously calls `vim._core.cmdwin.<action>(...)`.
|
||||
static void cmdwin_invoke(const char *action, int firstc, char *content, int pos)
|
||||
{
|
||||
char fc[2] = { (char)firstc, 0 };
|
||||
typval_T tv_args[] = {
|
||||
{ .v_type = VAR_STRING, .vval.v_string = fc },
|
||||
{ .v_type = VAR_STRING, .vval.v_string = content ? content : "" },
|
||||
{ .v_type = VAR_NUMBER, .vval.v_number = pos + 1 },
|
||||
{ .v_type = VAR_UNKNOWN },
|
||||
};
|
||||
nlua_call_vimfn("vim._core.cmdwin", action, firstc ? tv_args : tv_args + 3, NULL);
|
||||
xfree(content);
|
||||
}
|
||||
|
||||
/// Calls `vim._core.cmdwin.<action>()`, synchronously.
|
||||
void cmdwin_do_action(const char *action)
|
||||
{
|
||||
cmdwin_invoke(action, 0, NULL, 0);
|
||||
}
|
||||
|
||||
/// Deferred event: opens cmdwin after the cmdline-reader unwinds. Can't run synchronously (cmdline
|
||||
/// is still being read), nor via vim.schedule (could fire _during typeahead_; revisit after #40380).
|
||||
static void open_cmdwin_event(void **argv)
|
||||
{
|
||||
CmdwinOpenArgs *a = argv[0];
|
||||
char fc[2] = { (char)a->firstc, 0 };
|
||||
typval_T tv_args[] = {
|
||||
{ .v_type = VAR_STRING, .vval.v_string = fc },
|
||||
{ .v_type = VAR_STRING, .vval.v_string = a->content ? a->content : "" },
|
||||
{ .v_type = VAR_NUMBER, .vval.v_number = a->pos + 1 },
|
||||
{ .v_type = VAR_UNKNOWN },
|
||||
};
|
||||
nlua_call_vimfn("vim._core.cmdwin", "open", tv_args, NULL);
|
||||
xfree(a->content);
|
||||
cmdwin_invoke("open", a->firstc, a->content, a->pos); // frees a->content
|
||||
xfree(a);
|
||||
}
|
||||
|
||||
@@ -4617,8 +4629,14 @@ static int open_cmdwin(void)
|
||||
|
||||
CmdwinOpenArgs *a = xmalloc(sizeof(*a));
|
||||
a->firstc = ft;
|
||||
a->content = ccline.cmdbuff ? xstrnsave(ccline.cmdbuff, (size_t)ccline.cmdlen) : NULL;
|
||||
a->pos = ccline.cmdpos;
|
||||
|
||||
// Capture the cmdline; will append to end of cmdwin.
|
||||
a->content = ccline.cmdbuff ? xstrnsave(ccline.cmdbuff, (size_t)ccline.cmdlen) : NULL;
|
||||
// Clear cmdline so that unwinding it (via Ctrl_C below) does not add it to history.
|
||||
ccline.cmdlen = 0;
|
||||
ccline.cmdpos = 0;
|
||||
|
||||
// Intentionally not using vim.scheduled, see note on `open_cmdwin_event`.
|
||||
loop_schedule_deferred(&main_loop, event_create(open_cmdwin_event, a));
|
||||
return Ctrl_C;
|
||||
|
||||
@@ -3859,8 +3859,11 @@ static void nv_down(cmdarg_T *cap)
|
||||
// <S-Down> is page down
|
||||
cap->arg = FORWARD;
|
||||
nv_page(cap);
|
||||
} else if (bt_cmdwin(curbuf) && cap->cmdchar == CAR) {
|
||||
// cmdwin: execute the command-line under the cursor.
|
||||
cmdwin_do_action("confirm");
|
||||
} else if (bt_quickfix(curbuf) && cap->cmdchar == CAR) {
|
||||
// Quickfix window only: view the result under the cursor.
|
||||
// Quickfix window: view the result under the cursor.
|
||||
qf_view_result(false);
|
||||
} else {
|
||||
if (bt_prompt(curbuf) && cap->cmdchar == CAR
|
||||
@@ -6174,9 +6177,13 @@ static void nv_esc(cmdarg_T *cap)
|
||||
&& cap->opcount == 0
|
||||
&& cap->count0 == 0
|
||||
&& cap->oap->regname == 0);
|
||||
bool cmdwin_cancel = cap->arg && bt_cmdwin(curbuf);
|
||||
|
||||
if (cap->arg) { // true for CTRL-C
|
||||
if (restart_edit == 0 && !bt_cmdwin(curbuf) && !VIsual_active && no_reason) {
|
||||
if (cmdwin_cancel) {
|
||||
got_int = false; // CTRL-C cancels cmdwin; don't interrupt autocmds etc.
|
||||
cmdwin_do_action("cancel");
|
||||
} else if (cap->arg) { // true for CTRL-C
|
||||
if (restart_edit == 0 && !VIsual_active && no_reason) {
|
||||
if (anyBufIsChanged()) {
|
||||
msg(_("Type :qa! and press <Enter> to abandon all changes"
|
||||
" and exit Nvim"), 0);
|
||||
@@ -6197,7 +6204,7 @@ static void nv_esc(cmdarg_T *cap)
|
||||
check_cursor_col(curwin); // make sure cursor is not beyond EOL
|
||||
curwin->w_set_curswant = true;
|
||||
redraw_curbuf_later(UPD_INVERTED);
|
||||
} else if (no_reason) {
|
||||
} else if (no_reason && !cmdwin_cancel) {
|
||||
vim_beep(kOptBoFlagEsc);
|
||||
}
|
||||
clearop(cap->oap);
|
||||
|
||||
@@ -17,11 +17,12 @@ describe('cmdwin', function()
|
||||
eq(':', fn.getcmdwintype())
|
||||
eq('[Command Line]', vim.fs.basename(api.nvim_buf_get_name(0)))
|
||||
-- cmdwin-char is shown in window-local 'statuscolumn'.
|
||||
eq('%#NonText#: ', api.nvim_get_option_value('statuscolumn', { win = 0 }))
|
||||
eq('%#NonText#:', api.nvim_get_option_value('statuscolumn', { win = 0 }))
|
||||
|
||||
eq('nofile', api.nvim_get_option_value('buftype', { buf = 0 }))
|
||||
eq('wipe', api.nvim_get_option_value('bufhidden', { buf = 0 }))
|
||||
eq(false, api.nvim_get_option_value('swapfile', { buf = 0 }))
|
||||
eq(true, api.nvim_get_option_value('buflisted', { buf = 0 })) -- #40431
|
||||
eq(true, api.nvim_get_option_value('winfixbuf', { win = 0 }))
|
||||
|
||||
-- <CR> executes the cmdline
|
||||
@@ -32,15 +33,23 @@ describe('cmdwin', function()
|
||||
end)
|
||||
|
||||
it('q/ opens cmdwin with search history', function()
|
||||
fn.histadd('/', 'foo')
|
||||
fn.histadd('/', 'bar')
|
||||
feed('q/')
|
||||
eq('/', fn.getcmdwintype())
|
||||
-- The "/" history is presented, plus an empty editable last line.
|
||||
eq({ 'foo', 'bar', '' }, api.nvim_buf_get_lines(0, 0, -1, false))
|
||||
end)
|
||||
|
||||
it('c_CTRL-F opens cmdwin pre-filled with current cmdline', function()
|
||||
fn.histadd(':', 'let g:x = 1')
|
||||
fn.histadd(':', 'let g:y = 2')
|
||||
feed(':echo "hi"<C-F>')
|
||||
n.poke_eventloop()
|
||||
eq(':', fn.getcmdwintype())
|
||||
eq('echo "hi"', api.nvim_get_current_line())
|
||||
-- The ":" history is presented. The in-flight cmdline must not be added to history (else it shows up twice).
|
||||
eq({ 'let g:x = 1', 'let g:y = 2', 'echo "hi"' }, api.nvim_buf_get_lines(0, 0, -1, false))
|
||||
eq(2, fn.histnr('cmd')) -- History unchanged (the in-flight cmdline was not added).
|
||||
end)
|
||||
|
||||
it('history entry with literal newline char', function()
|
||||
@@ -52,10 +61,13 @@ describe('cmdwin', function()
|
||||
|
||||
it('<C-C> in normal mode cancels without executing', function()
|
||||
feed('q:')
|
||||
feed('iecho "nope"<Esc>')
|
||||
feed('ilet g:executed = 1<Esc>')
|
||||
feed('<C-C>')
|
||||
eq('', fn.getcmdwintype())
|
||||
-- v:errmsg should not contain a successful echo from the cancelled line.
|
||||
-- The cancelled line is neither executed nor added to history. It is pre-filled into the
|
||||
-- cmdline; reopening via c_CTRL-F must then not duplicate it.
|
||||
eq(0, fn.exists('g:executed'))
|
||||
eq(-1, fn.histnr('cmd'))
|
||||
end)
|
||||
|
||||
it('async API calls work while cmdwin is open #40312', function()
|
||||
@@ -115,7 +127,7 @@ describe('cmdwin', function()
|
||||
})
|
||||
]])
|
||||
feed('q:')
|
||||
n.poke_eventloop() -- Ensure q: is processed before the <C-C> mapping fires.
|
||||
n.poke_eventloop() -- Ensure q: is processed before <C-C>.
|
||||
feed('<C-C>')
|
||||
eq({ 'enter::', 'leave::' }, exec_lua('return _G.events'))
|
||||
end)
|
||||
|
||||
@@ -1046,9 +1046,8 @@ describe('completion', function()
|
||||
screen:expect([[
|
||||
|
|
||||
{2:[No Name] }|
|
||||
{1:: } |
|
||||
{1:: }foo faa fee f^ |
|
||||
{1:~ }|*2
|
||||
{1::}foo faa fee f^ |
|
||||
{1:~ }|*3
|
||||
{3:[Command Line] }|
|
||||
{5:-- INSERT --} |
|
||||
]])
|
||||
@@ -1056,11 +1055,11 @@ describe('completion', function()
|
||||
screen:expect([[
|
||||
|
|
||||
{2:[No Name] }|
|
||||
{1:: } |
|
||||
{1:: }foo faa fee foo^ |
|
||||
{1:~ }{12: foo }{1: }|
|
||||
{1:~ }{4: faa }{1: }|
|
||||
{3:[Command Line}{4: fee }{3: }|
|
||||
{1::}foo faa fee foo^ |
|
||||
{1:~ }{12: foo }{1: }|
|
||||
{1:~ }{4: faa }{1: }|
|
||||
{1:~ }{4: fee }{1: }|
|
||||
{3:[Command Line] }|
|
||||
{5:-- Keyword Local completion (^N^P) }{6:match 1 of 3} |
|
||||
]])
|
||||
feed('<c-c>')
|
||||
|
||||
@@ -255,7 +255,7 @@ local function test_cmdline(linegrid)
|
||||
screen:expect([[
|
||||
|
|
||||
{2:[No Name] }|
|
||||
{1:: }mak^e |
|
||||
{1::}mak^e |
|
||||
{3:[Command Line] }|
|
||||
|
|
||||
]])
|
||||
@@ -266,7 +266,7 @@ local function test_cmdline(linegrid)
|
||||
grid = [[
|
||||
|
|
||||
{2:[No Name] }|
|
||||
{1:: }mak^e |
|
||||
{1::}mak^e |
|
||||
{3:[Command Line] }|
|
||||
|
|
||||
]],
|
||||
@@ -278,7 +278,7 @@ local function test_cmdline(linegrid)
|
||||
grid = [[
|
||||
|
|
||||
{2:[No Name] }|
|
||||
{1:: }mak^e |
|
||||
{1::}mak^e |
|
||||
{3:[Command Line] }|
|
||||
|
|
||||
]],
|
||||
@@ -291,7 +291,7 @@ local function test_cmdline(linegrid)
|
||||
grid = [[
|
||||
|
|
||||
{2:[No Name] }|
|
||||
{1:: }mak^e |
|
||||
{1::}mak^e |
|
||||
{3:[Command Line] }|
|
||||
|
|
||||
]],
|
||||
@@ -900,9 +900,8 @@ describe('cmdline redraw', function()
|
||||
|
|
||||
{1:~ }|*3
|
||||
{2:[No Name] }|
|
||||
{1:: } |
|
||||
{1:: }^a{17:bc} |
|
||||
{1:~ }|
|
||||
{1::}^a{17:bc} |
|
||||
{1:~ }|*2
|
||||
{3:[Command Line] }|
|
||||
{5:-- VISUAL --} |
|
||||
]])
|
||||
|
||||
@@ -208,8 +208,8 @@ describe('messages2', function()
|
||||
x |
|
||||
{1:~ }|*3
|
||||
─────────────────────────────────────────────────────|
|
||||
{1:: }echo "foo" | echo "bar\nbaz\n"->repeat(&lines) |
|
||||
{1:: }^ |
|
||||
{1::}echo "foo" | echo "bar\nbaz\n"->repeat(&lines) |
|
||||
{1::}^ |
|
||||
{1:~ }|*5
|
||||
{3:[Command Line] 2,0-1 All}|
|
||||
|
|
||||
@@ -219,8 +219,8 @@ describe('messages2', function()
|
||||
x |
|
||||
{1:~ }|*2
|
||||
─────────────────────────────────────────────────────|
|
||||
{1:: }echo "foo" | echo "bar\nbaz\n"->repeat(&lines) |
|
||||
{1:: }^ |
|
||||
{1::}echo "foo" | echo "bar\nbaz\n"->repeat(&lines) |
|
||||
{1::}^ |
|
||||
{1:~ }|*6
|
||||
{3:[Command Line] 2,0-1 All}|
|
||||
|
|
||||
@@ -230,8 +230,8 @@ describe('messages2', function()
|
||||
x |
|
||||
{1:~ }|*2
|
||||
─────────────────────────────────────────────────────|
|
||||
{1:: }echo "foo" | echo "bar\nbaz\n"->repeat(&lines) |
|
||||
{1:: }^ |
|
||||
{1::}echo "foo" | echo "bar\nbaz\n"->repeat(&lines) |
|
||||
{1::}^ |
|
||||
{1:~ }|*6
|
||||
{3:[Command Line] 2,0-1 All}|
|
||||
foo |
|
||||
@@ -252,8 +252,8 @@ describe('messages2', function()
|
||||
x |
|
||||
{1:~ }|*3
|
||||
─────────────────────────────────────────────────────|
|
||||
{1:: }echo "foo" | echo "bar\nbaz\n"->repeat(&lines) |
|
||||
{1:: }^ |
|
||||
{1::}echo "foo" | echo "bar\nbaz\n"->repeat(&lines) |
|
||||
{1::}^ |
|
||||
{1:~ }|*5
|
||||
{3:[Command Line] 2,0-1 All}|
|
||||
|
|
||||
@@ -272,9 +272,9 @@ describe('messages2', function()
|
||||
x |
|
||||
{1:~ }|*3
|
||||
─────────────────────────────────────────────────────|
|
||||
{1:: }echo "foo" | echo "bar\nbaz\n"->repeat(&lines) |
|
||||
{1:: }messages |
|
||||
{1:: }^ |
|
||||
{1::}echo "foo" | echo "bar\nbaz\n"->repeat(&lines) |
|
||||
{1::}messages |
|
||||
{1::}^ |
|
||||
{1:~ }|*4
|
||||
{3:[Command Line] 3,0-1 All}|
|
||||
{16::}{15:messages} |
|
||||
|
||||
@@ -970,8 +970,8 @@ describe('statuscolumn', function()
|
||||
{8: 9}aaaaa |
|
||||
{8:10}aaaaa |
|
||||
{2:[No Name] [+] }|
|
||||
{1:: }set stc=%^l |
|
||||
{1:: } |
|
||||
{1::}set stc=%^l |
|
||||
{1::} |
|
||||
{1:~ }|*5
|
||||
{3:[Command Line] }|
|
||||
:set stc=%l |
|
||||
|
||||
Reference in New Issue
Block a user