diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index df4e078acc..989b038a1e 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -5285,9 +5285,11 @@ To enable the experimental UI (default opts shown): >lua require('vim._core.ui2').enable({ enable = true, -- Whether to enable or disable the UI. msg = { -- Options related to the message module. - ---@type 'cmd'|'msg' Where to place regular messages, either in the + ---@type 'cmd'|'msg' Default message target, either in the ---cmdline or in a separate ephemeral message window. - target = 'cmd', + ---@type string|table Default message target + or table mapping |ui-messages| kinds to a target. + targets = 'cmd', timeout = 4000, -- Time a message is visible in the message window. }, }) diff --git a/runtime/lua/vim/_core/ui2.lua b/runtime/lua/vim/_core/ui2.lua index 4e1d9a3534..b7ce79a76b 100644 --- a/runtime/lua/vim/_core/ui2.lua +++ b/runtime/lua/vim/_core/ui2.lua @@ -8,9 +8,11 @@ ---require('vim._core.ui2').enable({ --- enable = true, -- Whether to enable or disable the UI. --- msg = { -- Options related to the message module. ---- ---@type 'cmd'|'msg' Where to place regular messages, either in the +--- ---@type 'cmd'|'msg' Default message target, either in the --- ---cmdline or in a separate ephemeral message window. ---- target = 'cmd', +--- ---@type string|table Default message target +--- or table mapping |ui-messages| kinds to a target. +--- targets = 'cmd', --- timeout = 4000, -- Time a message is visible in the message window. --- }, ---}) @@ -43,9 +45,8 @@ local M = { cfg = { enable = true, msg = { -- Options related to the message module. - ---@type 'cmd'|'msg' Where to place regular messages, either in the - ---cmdline or in a separate ephemeral message window. - target = 'cmd', + target = 'cmd', ---@type 'cmd'|'msg' Default message target if not present in targets. + targets = {}, ---@type table Kind specific message targets. timeout = 4000, -- Time a message is visible in the message window. }, }, @@ -160,6 +161,8 @@ local scheduled_ui_callback = vim.schedule_wrap(ui_callback) function M.enable(opts) vim.validate('opts', opts, 'table', true) M.cfg = vim.tbl_deep_extend('keep', opts, M.cfg) + M.cfg.msg.target = type(M.cfg.msg.targets) == 'string' and M.cfg.msg.targets or M.cfg.msg.target + M.cfg.msg.targets = type(M.cfg.msg.targets) == 'table' and M.cfg.msg.targets or {} if #vim.api.nvim_list_uis() == 0 then return -- Don't prevent stdout messaging when no UIs are attached. end diff --git a/runtime/lua/vim/_core/ui2/messages.lua b/runtime/lua/vim/_core/ui2/messages.lua index e4a6311db7..1f946553c5 100644 --- a/runtime/lua/vim/_core/ui2/messages.lua +++ b/runtime/lua/vim/_core/ui2/messages.lua @@ -216,8 +216,6 @@ local function expand_msg(src) if tgt == 'cmd' and ui.cmd.highlighter then ui.cmd.highlighter.active[ui.bufs.cmd] = nil - elseif tgt == 'pager' then - api.nvim_command('norm! G') end else M.virt.msg[M.virt.idx.dupe][1] = nil @@ -329,7 +327,6 @@ function M.show_msg(tgt, kind, content, replace_last, append, id) if texth.all > math.ceil(o.lines * 0.5) then expand_msg(tgt) else - M.set_pos('msg') M.msg.width = width M.msg:start_timer(buf, id) end @@ -358,6 +355,11 @@ function M.show_msg(tgt, kind, content, replace_last, append, id) end end + -- Set pager/dialog/msg dimensions unless sent to expanded cmdline. + if tgt ~= 'cmd' and (tgt ~= 'msg' or M.msg.ids[id]) then + M.set_pos(tgt) + end + if M[tgt] and (tgt == 'cmd' or row == api.nvim_buf_line_count(buf) - 1) then -- Place (x) indicator for repeated messages. Mainly to mitigate unnecessary -- resizing of the message window, but also placed in the cmdline. @@ -385,7 +387,12 @@ end ---@param append boolean ---@param id integer|string function M.msg_show(kind, content, replace_last, _, append, id) - if kind == 'empty' then + -- Set the entered search command in the cmdline (if available). + local tgt = kind == 'search_cmd' and 'cmd' or ui.cfg.msg.targets[kind] or ui.cfg.msg.target + if kind == 'search_cmd' and ui.cmdheight == 0 then + -- Blocked by messaging() without ext_messages. TODO: look at other messaging() guards. + return + elseif kind == 'empty' then -- A sole empty message clears the cmdline. if ui.cfg.msg.target == 'cmd' and not next(M.cmd.ids) and ui.cmd.srow == 0 then M.msg_clear() @@ -398,9 +405,7 @@ function M.msg_show(kind, content, replace_last, _, append, id) M.virt.last[M.virt.idx.search] = content M.virt.last[M.virt.idx.cmd] = { { 0, (' '):rep(11) } } set_virttext('last') - elseif - (ui.cmd.prompt or (ui.cmd.level > 0 and ui.cfg.msg.target == 'cmd')) and ui.cmd.srow == 0 - then + elseif (ui.cmd.prompt or (ui.cmd.level > 0 and tgt == 'cmd')) and ui.cmd.srow == 0 then -- Route to dialog when a prompt is active, or message would overwrite active cmdline. replace_last = api.nvim_win_get_config(ui.wins.dialog).hide or kind == 'wildlist' if kind == 'wildlist' then @@ -408,14 +413,8 @@ function M.msg_show(kind, content, replace_last, _, append, id) end ui.cmd.dialog = true -- Ensure dialog is closed when cmdline is hidden. M.show_msg('dialog', kind, content, replace_last, append, id) - M.set_pos('dialog') else - -- Set the entered search command in the cmdline (if available). - local tgt = kind == 'search_cmd' and 'cmd' or ui.cfg.msg.target - if kind == 'search_cmd' and ui.cmdheight == 0 then - -- Blocked by messaging() without ext_messages. TODO: look at other messaging() guards. - return - elseif tgt == 'cmd' then + if tgt == 'cmd' then -- Store the time when an important message was emitted in order to not overwrite -- it with 'last' virt_text in the cmdline so that the user has a chance to read it. M.cmd.last_emsg = (kind == 'emsg' or kind == 'wmsg') and os.time() or M.cmd.last_emsg @@ -423,10 +422,13 @@ function M.msg_show(kind, content, replace_last, _, append, id) M.virt.last[M.virt.idx.search][1] = nil end - M.show_msg(tgt, kind, content, replace_last, append, id) + local enter_pager = tgt == 'pager' and api.nvim_get_current_win() ~= ui.wins.pager + M.show_msg(tgt, kind, content, replace_last or enter_pager, append, id) -- Don't remember search_cmd message as actual message. if kind == 'search_cmd' then M.cmd.ids, M.prev_msg = {}, '' + elseif api.nvim_get_current_win() == ui.wins.pager and not enter_pager then + api.nvim_command('norm! G') end end end @@ -522,7 +524,7 @@ function M.set_pos(tgt) return end vim.on_key(nil, ui.ns) - cmd_on_key, M[ui.cfg.msg.target].ids = nil, {} + cmd_on_key, M.cmd.ids = nil, {} -- Check if window was entered and reopen with original config. local entered = typed == '' @@ -581,8 +583,7 @@ function M.set_pos(tgt) end, M.dialog_on_key) elseif tgt == 'msg' then -- Ensure last line is visible and first line is at top of window. - local row = (texth.all > cfg.height and texth.end_row or 0) + 1 - api.nvim_win_set_cursor(ui.wins.msg, { row, 0 }) + fn.win_execute(ui.wins.msg, 'norm! Gzb') elseif tgt == 'pager' and api.nvim_get_current_win() ~= ui.wins.pager then if fn.getcmdwintype() ~= '' then -- Cannot leave the cmdwin to enter the pager, so close it. diff --git a/test/functional/ui/messages2_spec.lua b/test/functional/ui/messages2_spec.lua index 8e604867eb..6a9279abb9 100644 --- a/test/functional/ui/messages2_spec.lua +++ b/test/functional/ui/messages2_spec.lua @@ -4,7 +4,7 @@ local t = require('test.testutil') local n = require('test.functional.testnvim')() local Screen = require('test.functional.ui.screen') -local clear, command, exec_lua, feed = n.clear, n.command, n.exec_lua, n.feed +local api, clear, command, exec_lua, feed = n.api, n.clear, n.command, n.exec_lua, n.feed local msg_timeout = 200 local function set_msg_target_zero_ch() @@ -631,18 +631,14 @@ describe('messages2', function() baz | foo | ]]) - exec_lua(function() - vim.api.nvim_echo({ { 'foo' } }, true, { id = 2 }) - end) + api.nvim_echo({ { 'foo' } }, true, { id = 2 }) screen:expect([[ ^ | {1:~ }|*9 {3: }| foo |*3 ]]) - exec_lua(function() - vim.api.nvim_echo({ { 'bar\nbaz' } }, true, { id = 1 }) - end) + api.nvim_echo({ { 'bar\nbaz' } }, true, { id = 1 }) screen:expect([[ ^ | {1:~ }|*8 @@ -653,7 +649,7 @@ describe('messages2', function() ]]) -- Pressing a key immediately dismisses an expanded cmdline, and -- replacing a multiline, multicolored message doesn't error due - -- to unneccesarily inserted lines #37994. + -- to unnecessarily inserted lines #37994. feed('Q') screen:expect([[ ^ | @@ -664,12 +660,11 @@ describe('messages2', function() ]]) feed('Q') screen:expect_unchanged() + feed('') -- close expanded cmdline set_msg_target_zero_ch() - exec_lua(function() - vim.api.nvim_echo({ { 'foo' } }, true, { id = 1 }) - vim.api.nvim_echo({ { 'bar\nbaz' } }, true, { id = 2 }) - vim.api.nvim_echo({ { 'foo' } }, true, { id = 3 }) - end) + api.nvim_echo({ { 'foo' } }, true, { id = 1 }) + api.nvim_echo({ { 'bar\nbaz' } }, true, { id = 2 }) + api.nvim_echo({ { 'foo' } }, true, { id = 3 }) screen:expect([[ ^ | {1:~ }|*9 @@ -678,17 +673,13 @@ describe('messages2', function() {1:~ }{4:baz}| {1:~ }{4:foo}| ]]) - exec_lua(function() - vim.api.nvim_echo({ { 'foo' } }, true, { id = 2 }) - end) + api.nvim_echo({ { 'foo' } }, true, { id = 2 }) screen:expect([[ ^ | {1:~ }|*10 {1:~ }{4:foo}|*3 ]]) - exec_lua(function() - vim.api.nvim_echo({ { 'f', 'Conceal' }, { 'oo\nbar' } }, true, { id = 3 }) - end) + api.nvim_echo({ { 'f', 'Conceal' }, { 'oo\nbar' } }, true, { id = 3 }) screen:expect([[ ^ | {1:~ }|*9 @@ -704,7 +695,7 @@ describe('messages2', function() {1:~ }| {3: }| foo |*2 - {14:f}oo | + {14:f}oo [+6] | ]]) feed('') screen:expect([[ @@ -782,5 +773,56 @@ describe('messages2', function() {1:~ }|*12 {1:~ }{4:baz}| ]]) + -- Last message line is at bottom of window after closing it. + screen:try_resize(screen._width, 8) + command('mode | echo "1\n" | echo "2\n" | echo "3\n" | echo "4\n"') + screen:expect([[ + ^ | + {1:~ }|*3 + {1:~ }{4:3}| + {1:~ }{4: }| + {1:~ }{4:4}| + {1:~ }{4: }| + ]]) + command('fclose!') + screen:expect([[ + ^ | + {1:~ }|*7 + ]]) + command('echo "5\n"') + screen:expect([[ + ^ | + {1:~ }|*3 + {1:~ }{4:4}| + {1:~ }{4: }| + {1:~ }{4:5}| + {1:~ }{4: }| + ]]) + end) + + it('configured targets per kind', function() + exec_lua(function() + local cfg = { msg = { targets = { echo = 'msg', list_cmd = 'pager' } } } + require('vim._core.ui2').enable(cfg) + print('foo') -- "lua_print" kind goes to cmd + vim.cmd.echo('"bar"') -- "echo" kind goes to msg + vim.cmd.highlight('VisualNC') -- "list_cmd" kind goes to pager + end) + screen:expect([[ + | + {1:~ }|*10 + {3: }| + ^VisualNC xxx cleared {4:bar}| + foo | + ]]) + command('hi VisualNC') -- cursor moved to last message in pager + screen:expect([[ + | + {1:~ }|*9 + {3: }| + VisualNC xxx cleared | + ^VisualNC xxx cleared {4:bar}| + foo | + ]]) end) end)