mirror of
https://github.com/neovim/neovim.git
synced 2026-08-25 08:31:51 +00:00
fix(ui2): do not steal focus when consecutive cmds emit messages #41062
Problem: A message emitted while a previous expanded message is still
visible opens the pager and enters it, moving focus away from
the buffer window without an explicit request (#41061).
Solution: Only enter the pager when it was explicitly requested ("g<",
:messages, or entered from the expanded cmdline). An unfocused
pager is dismissed by the cmdline key handler, which stays armed
across the cmdline and no longer dismisses on non-typed keys
(#39221).
This commit is contained in:
@@ -852,7 +852,10 @@ This message is given when the |ui2| modal dialog is active.
|
||||
Only when the 'more' option is on.
|
||||
Highlighted with the |hl-MoreMsg| group.
|
||||
|
||||
With |ui2|: use "g<" to show messages in the pager.
|
||||
With |ui2|: use "g<" to show messages in the pager. When a command emits a
|
||||
message while a previous message is still displayed, the messages are
|
||||
collected in the pager window without focusing it: use "g<" to enter the
|
||||
pager, any other typed key dismisses it.
|
||||
|
||||
Type Effect ~
|
||||
<CR> or <NL> or j or <Down> one more line
|
||||
|
||||
@@ -413,6 +413,12 @@ function M.show_msg(tgt, kind, content, replace_last, append, id)
|
||||
end
|
||||
|
||||
local in_pager = false -- Whether the pager is or will be the current window.
|
||||
local pager_focus = false -- Whether the pager was explicitly requested (|g<|, :messages).
|
||||
|
||||
local function pager_shown()
|
||||
return not api.nvim_win_get_config(ui.wins.pager).hide
|
||||
end
|
||||
|
||||
--- Route the message to the appropriate sink.
|
||||
---
|
||||
---@param kind string
|
||||
@@ -437,7 +443,7 @@ function M.msg_show(kind, content, replace_last, _, append, id, trigger)
|
||||
-- When the pager is open always route typed commands there. This better simulates
|
||||
-- the UI1 behavior after opening the cmdline below a previous multiline message,
|
||||
-- and seems useful enough even when the pager was entered manually.
|
||||
or (trigger == 'typed_cmd' and in_pager and fn.getcmdwintype() == '') and 'pager'
|
||||
or (trigger == 'typed_cmd' and pager_shown() and fn.getcmdwintype() == '') and 'pager'
|
||||
-- Otherwise route to configured target:
|
||||
or (trigger ~= '' and ui.cfg.msg.targets[trigger])
|
||||
or id_target
|
||||
@@ -486,8 +492,8 @@ function M.msg_show(kind, content, replace_last, _, append, id, trigger)
|
||||
end
|
||||
ui.cmd.expand = ui.cmd.expand + (ui.cmd.expand > 0 and 1 or 0)
|
||||
|
||||
local enter_pager = tgt == 'pager' and not in_pager
|
||||
M.show_msg(tgt, kind, content, replace_last or enter_pager, append, id)
|
||||
local open_pager = tgt == 'pager' and not pager_shown()
|
||||
M.show_msg(tgt, kind, content, replace_last or open_pager, append, id)
|
||||
if kind == 'search_cmd' then
|
||||
-- Don't remember search_cmd message as actual message.
|
||||
M.cmd.ids, M.cmd.prev_msg = {}, ''
|
||||
@@ -495,7 +501,7 @@ function M.msg_show(kind, content, replace_last, _, append, id, trigger)
|
||||
-- Position cursor at start of first or last message at bottom of window.
|
||||
-- Keep Normal commands: nvim_win_set_cursor() only ensures visibility, while zb
|
||||
-- reliably places the final message at the bottom. :noautocmd avoids #40780.
|
||||
fn.win_execute(ui.wins.pager, 'noautocmd norm! ' .. (enter_pager and 'gg0' or 'G0zb'))
|
||||
fn.win_execute(ui.wins.pager, 'noautocmd norm! ' .. (open_pager and 'gg0' or 'G0zb'))
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -567,6 +573,8 @@ function M.msg_history_show(entries, prev_cmd)
|
||||
M.show_msg('pager', entry[1], entry[2], i == 1, entry[3], 0)
|
||||
end
|
||||
|
||||
-- Message history was explicitly requested (e.g. |g<| or |:messages|): enter the pager.
|
||||
pager_focus = true
|
||||
M.set_pos('pager')
|
||||
end
|
||||
|
||||
@@ -574,22 +582,29 @@ local typed_g = false
|
||||
local function cmd_on_key(key, typed)
|
||||
-- Don't dismiss for non-typed keys and mouse movement. When 'g' is passed (typed
|
||||
-- or mapped), wait until the next key to avoid flickering when the pager is opened.
|
||||
if not typed_g and (typed == '' or typed == '<MouseMove>' or typed == 'g' or key == 'g') then
|
||||
if typed == '' or (not typed_g and (typed == '<MouseMove>' or typed == 'g' or key == 'g')) then
|
||||
typed_g = typed == 'g' or key == 'g'
|
||||
return
|
||||
end
|
||||
vim.on_key(nil, ui.ns)
|
||||
if typed == ':' or ui.cmd.level > 0 then
|
||||
return -- Keep expanded messages open until cmdline closes.
|
||||
if typed == ':' or ui.cmd.level > 0 or fn.getcmdtype() ~= '' then
|
||||
-- Keep expanded messages open until the cmdline closes, including for the
|
||||
-- key that executes it. Stay armed: a command that emits no message would
|
||||
-- otherwise leave an unfocused pager without a handler.
|
||||
return
|
||||
end
|
||||
vim.on_key(nil, ui.ns)
|
||||
typed = fn.keytrans(typed)
|
||||
|
||||
-- Check if window was entered and reopen with original config.
|
||||
local mode = not api.nvim_get_mode().mode:match('[it]')
|
||||
-- Check if window was entered and reopen with original config. An already open
|
||||
-- pager is dismissed instead; "g<" passes through to reopen and enter it.
|
||||
local mode = not api.nvim_get_mode().mode:match('[it]') and not pager_shown()
|
||||
local enter = mode and (typed == '<CR>' or typed_g and (typed == '<lt>' or key == '<'))
|
||||
or (typed:find('LeftMouse') and fn.getmousepos().winid == ui.wins.cmd)
|
||||
if enter then
|
||||
pager_focus = true
|
||||
M.expand_msg('cmd', 'pager')
|
||||
elseif not in_pager then
|
||||
pcall(api.nvim_win_set_config, ui.wins.pager, { hide = true })
|
||||
end
|
||||
pcall(api.nvim_win_close, ui.wins.cmd, true)
|
||||
ui.check_targets()
|
||||
@@ -732,7 +747,13 @@ function M.set_pos(tgt)
|
||||
-- reliably places the final message at the bottom. :noautocmd avoids #40780.
|
||||
fn.win_execute(ui.wins.msg, 'noautocmd norm! Gzb')
|
||||
elseif tgt == 'pager' and not in_pager then
|
||||
enter_pager()
|
||||
if pager_focus then
|
||||
pager_focus = false
|
||||
enter_pager()
|
||||
else
|
||||
-- Reuse only the handler: M.cmd_on_key would mark the cmdline as expanded.
|
||||
vim.on_key(cmd_on_key, ui.ns)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -514,45 +514,41 @@ describe('messages2', function()
|
||||
{1:~ }|*12
|
||||
{16::}{15:call} {25:setline}{16:(}{26:1}{16:,} {26:"foo"}{16:)} |
|
||||
]])
|
||||
-- If command emits another message we enter the pager to closely mimic useful UI1 behavior.
|
||||
-- If command emits another message it is opened in the pager without focusing
|
||||
-- it, to closely mimic useful UI1 behavior. #41061
|
||||
command('echo "foo\nbar"')
|
||||
feed(':echo "baz"<CR>')
|
||||
screen:expect([[
|
||||
foo |
|
||||
^foo |
|
||||
{1:~ }|*8
|
||||
{3: }|
|
||||
foo |
|
||||
bar |
|
||||
^baz |
|
||||
baz |
|
||||
{16::}{15:echo} {26:"baz"} |
|
||||
]])
|
||||
-- Subsequent typed commands are appended to the pager.
|
||||
feed(':echo "typed append"<CR>')
|
||||
screen:expect([[
|
||||
foo |
|
||||
^foo |
|
||||
{1:~ }|*7
|
||||
{3: }|
|
||||
foo |
|
||||
bar |
|
||||
baz |
|
||||
^typed append |
|
||||
typed append |
|
||||
{16::}{15:echo} {26:"typed append"} |
|
||||
]])
|
||||
-- Other messages that fit 'cmdheight' are not.
|
||||
-- Any other typed key dismisses the pager.
|
||||
feed('n')
|
||||
screen:expect([[
|
||||
foo |
|
||||
{1:~ }|*7
|
||||
{3: }|
|
||||
foo |
|
||||
bar |
|
||||
baz |
|
||||
^typed append |
|
||||
^foo |
|
||||
{1:~ }|*12
|
||||
{9:E35: No previous regular expression} |
|
||||
]])
|
||||
-- Non-typed key doesn't dismiss expanded cmdline #39221
|
||||
command('nnoremap b :ls!<cr>:b<space>')
|
||||
feed('qb')
|
||||
feed('b')
|
||||
screen:expect([[
|
||||
foo |
|
||||
{1:~ }|*6
|
||||
@@ -566,6 +562,54 @@ describe('messages2', function()
|
||||
]])
|
||||
end)
|
||||
|
||||
it('pager for consecutive command messages is not focused #41061', function()
|
||||
local win = api.nvim_get_current_win()
|
||||
command('echo "foo\nbar"')
|
||||
feed(':echo "baz"<CR>')
|
||||
screen:expect([[
|
||||
^ |
|
||||
{1:~ }|*8
|
||||
{3: }|
|
||||
foo |
|
||||
bar |
|
||||
baz |
|
||||
{16::}{15:echo} {26:"baz"} |
|
||||
]])
|
||||
t.eq(win, api.nvim_get_current_win())
|
||||
-- "g<" enters the pager (showing the previous command output).
|
||||
feed('g<lt>')
|
||||
screen:expect([[
|
||||
|
|
||||
{1:~ }|*10
|
||||
{3: }|
|
||||
^baz |
|
||||
|
|
||||
]])
|
||||
t.neq(win, api.nvim_get_current_win())
|
||||
-- "q" closes the entered pager.
|
||||
feed('q')
|
||||
t.eq(win, api.nvim_get_current_win())
|
||||
-- A typed command that emits no message keeps the pager; the next key dismisses it.
|
||||
command('echo "foo\nbar"')
|
||||
feed(':echo "baz"<CR>')
|
||||
feed(':let g:x = 1<CR>')
|
||||
screen:expect([[
|
||||
^ |
|
||||
{1:~ }|*8
|
||||
{3: }|
|
||||
foo |
|
||||
bar |
|
||||
baz |
|
||||
{16::}{15:let} {25:g:x} {15:=} {26:1} |
|
||||
]])
|
||||
feed('j')
|
||||
screen:expect([[
|
||||
^ |
|
||||
{1:~ }|*12
|
||||
{16::}{15:let} {25:g:x} {15:=} {26:1} |
|
||||
]])
|
||||
end)
|
||||
|
||||
it('paging prompt dialog #35191', function()
|
||||
screen:try_resize(71, screen._height)
|
||||
-- Don't consume <Esc> when paging is not necessary.
|
||||
@@ -1045,27 +1089,30 @@ describe('messages2', function()
|
||||
vim.cmd.highlight('VisualNC') -- "list_cmd" kind goes to pager
|
||||
end)
|
||||
screen:expect([[
|
||||
|
|
||||
^ |
|
||||
{1:~ }|*10
|
||||
{3: }|
|
||||
^VisualNC xxx cleared {4:bar}|
|
||||
VisualNC xxx cleared {4:bar}|
|
||||
foo |
|
||||
]])
|
||||
command('hi VisualNC') -- cursor moved to last message in pager
|
||||
command('hi VisualNC') -- appended to the pager without focusing it
|
||||
screen:expect([[
|
||||
|
|
||||
^ |
|
||||
{1:~ }|*9
|
||||
{3: }|
|
||||
VisualNC xxx cleared |
|
||||
^VisualNC xxx cleared {4:bar}|
|
||||
VisualNC xxx cleared {4:bar}|
|
||||
foo |
|
||||
]])
|
||||
-- Any typed key dismisses the unfocused pager.
|
||||
feed('<Esc>')
|
||||
-- Duplicate indicator in msg and cmd target simultaneously
|
||||
command('close | echo "bar" | lua print("foo")')
|
||||
command('echo "bar" | lua print("foo")')
|
||||
command('echo "bar" | lua print("foo")')
|
||||
screen:expect([[
|
||||
^ |
|
||||
{1:~ }|*11
|
||||
{1:~ }{4:bar(1)}|
|
||||
{1:~ }{4:bar(2)}|
|
||||
foo(1) |
|
||||
]])
|
||||
finally(function()
|
||||
@@ -1076,7 +1123,7 @@ describe('messages2', function()
|
||||
screen:expect([[
|
||||
^ |
|
||||
{1:~ }|*11
|
||||
{1:~ }{4:bar(1)}|
|
||||
{1:~ }{4:bar(2)}|
|
||||
"Xfile" [New] 0L, 0B written |
|
||||
]])
|
||||
end)
|
||||
|
||||
Reference in New Issue
Block a user