From 06bff279296d8692f5ed514f5edb6e879fac6dc8 Mon Sep 17 00:00:00 2001 From: Volodymyr Chernetskyi Date: Fri, 4 Sep 2026 20:28:22 +0200 Subject: [PATCH] feat(ui2): highlight the [+x] spill indicator with MoreMsg #41679 Problem: The "spill" indicator that ui2 appends when messages overflow the available height is drawn with whatever highlight the message tail happens to have, so it is indistinguishable from the message text. Solution: Give the [+x] chunks an explicit `MoreMsg` highlight, and only fall back to the message tail highlight for chunks that don't carry one of their own. AI-assisted --- runtime/doc/news.txt | 1 + runtime/lua/vim/_core/ui2/messages.lua | 16 +++++----- test/functional/ui/messages2_spec.lua | 42 +++++++++++++------------- 3 files changed, 31 insertions(+), 28 deletions(-) diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index eec4318300a..39db5c29ff3 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -496,6 +496,7 @@ UI • |z=| (spell suggest) • The |ui2| dialog window scrolls with the mouse wheel, by 'mousescroll' lines. +• The |ui2| `[+x]` "spill" indicator is highlighted with |hl-MoreMsg|. VIMSCRIPT diff --git a/runtime/lua/vim/_core/ui2/messages.lua b/runtime/lua/vim/_core/ui2/messages.lua index e7e418494d3..2de45011934 100644 --- a/runtime/lua/vim/_core/ui2/messages.lua +++ b/runtime/lua/vim/_core/ui2/messages.lua @@ -1,6 +1,7 @@ local api, fn, o = vim.api, vim.fn, vim.o local nvim_on = require('vim._core.util').nvim_on local ui = require('vim._core.ui2') +local more_msg = api.nvim_get_hl_id_by_name('MoreMsg') -- Highlight for the [+x] indicators. ---@alias Msg { extid: integer, timer: uv.uv_timer_t? } ---@class vim._core.ui2.messages @@ -162,11 +163,11 @@ local function set_virttext(type, tgt) col = fn.virtcol2col(win, row + 1, texth.end_vcol - (scol - offset + width - mwidth)) end - -- Give virt_text the same highlight as the message tail. + -- Give virt_text without its own highlight the same highlight as the message tail. local pos, opts = { row, col }, { details = true, overlap = true, type = 'highlight' } local hl = api.nvim_buf_get_extmarks(ui.bufs[tgt], ui.ns, pos, pos, opts) for _, chunk in ipairs(hl[1] and chunks or {}) do - chunk[2] = hl[1][4].hl_group + chunk[2] = chunk[2] or hl[1][4].hl_group end else local mode = #M.virt.last[M.virt.idx.mode] @@ -340,7 +341,7 @@ function M.show_msg(tgt, kind, content, replace_last, append, id) local texth = api.nvim_win_text_height(ui.wins.cmd, { max_height = lines }) texth.all = math.max(texth.all, api.nvim_buf_line_count(ui.bufs.cmd)) local spill = texth.all > ui.cmdheight and (' [+%d]'):format(texth.all - ui.cmdheight) - M.virt.cmd[M.virt.idx.spill][1] = spill and { 0, spill } or nil + M.virt.cmd[M.virt.idx.spill][1] = spill and { 0, spill, more_msg } or nil M.cmd.msg_row = texth.end_row -- Expand the cmdline for a non-error message that doesn't fit. @@ -636,9 +637,9 @@ end local function set_top_bot_spill() local topspill = fn.line('w0', ui.wins.dialog) - 1 local botspill = api.nvim_buf_line_count(ui.bufs.dialog) - fn.line('w$', ui.wins.dialog) - M.virt.top[1][1] = topspill > 0 and { 0, (' [+%d]'):format(topspill) } or nil + M.virt.top[1][1] = topspill > 0 and { 0, (' [+%d]'):format(topspill), more_msg } or nil set_virttext('top', 'dialog') - M.virt.bot[1][1] = botspill > 0 and { 0, (' [+%d]'):format(botspill) } or nil + M.virt.bot[1][1] = botspill > 0 and { 0, (' [+%d]'):format(botspill), more_msg } or nil set_virttext('bot', 'dialog') api.nvim__redraw({ flush = true }) return topspill > 0 or botspill > 0 @@ -753,9 +754,10 @@ function M.set_pos(tgt, focus) -- Dismiss temporarily expanded cmdline on next keypress and update spill indicator. texth.all = math.max(texth.all, api.nvim_buf_line_count(ui.bufs.cmd)) local spill = texth.all > cfg.height and (' [+%d]'):format(texth.all - cfg.height) - M.virt.cmd[M.virt.idx.spill][1] = spill and { 0, spill } or nil + M.virt.cmd[M.virt.idx.spill][1] = spill and { 0, spill, more_msg } or nil set_virttext('cmd', 'cmd') - M.virt.cmd[M.virt.idx.spill][1] = { 0, (' [+%d]'):format(texth.all - ui.cmdheight) } + M.virt.cmd[M.virt.idx.spill][1] = + { 0, (' [+%d]'):format(texth.all - ui.cmdheight), more_msg } M.cmd_on_key = vim.on_key(cmd_on_key, ui.ns) elseif tgt == 'dialog' and set_top_bot_spill() then M.dialog_on_key = vim.on_key(dialog_on_key, M.dialog_on_key) diff --git a/test/functional/ui/messages2_spec.lua b/test/functional/ui/messages2_spec.lua index 2b5febec050..49b723dcfd6 100644 --- a/test/functional/ui/messages2_spec.lua +++ b/test/functional/ui/messages2_spec.lua @@ -79,7 +79,7 @@ describe('messages2', function() bar | baz | bar | - baz [+23] | + baz{6: [+23]} | ]]) -- Any key press resizes the cmdline and updates the spill indicator. feed('j') @@ -161,7 +161,7 @@ describe('messages2', function() {5: + [No Name] }{24: [No Name] }{2: }{24:X}| ^x | {1:~ }|*11 - foo [+1] 1,1 All| + foo{6: [+1]} 1,1 All| ]]) -- Don't enter the pager in insert mode. command('tabonly | call nvim_echo([["foo\n"]]->repeat(&lines), 1, {}) | startinsert') @@ -170,14 +170,14 @@ describe('messages2', function() {1:~ }|*5 {3: }| foo |*6 - foo [+8] | + foo{6: [+8]} | ]]) feed('') screen:expect([[ | ^x | {1:~ }|*11 - foo [+14] 2,1 All| + foo{6: [+14]} 2,1 All| ]]) feed('') -- First multiline message expands cmdline, additional message updates spill indicator. @@ -187,7 +187,7 @@ describe('messages2', function() {1:~ }|*5 {3: }| foo |*6 - foo [+9] | + foo{6: [+9]} | ]]) -- Do enter the pager in normal mode (with keybinding setup). -- Also checks that "messagesopt=pager:…" is normalized to the keytrans() form. @@ -404,7 +404,7 @@ describe('messages2', function() {1:~ }|*5 foo | bar |*5 - bar [+8] | + bar{6: [+8]} | ]]) end) @@ -501,7 +501,7 @@ describe('messages2', function() screen:expect([[ ^ | {1:~ }|*12 - foo [+1] | + foo{6: [+1]} | ]]) end) @@ -512,7 +512,7 @@ describe('messages2', function() {1:~ }|*5 {3: }| foo |*6 - foo [+8] | + foo{6: [+8]} | ]]) -- Place cmdline below expanded messages: #37653, without "more" title #38481. feed(':call setline(1, "foo")') @@ -521,7 +521,7 @@ describe('messages2', function() {1:~ }|*4 {3: }| foo |*6 - foo [+8] | + foo{6: [+8]} | {16::}{15:call} {25:setline}{16:(}{26:1}{16:,} {26:"foo"}{16:)}^ | ]]) -- No message closes expanded cmdline and keeps the entered command. @@ -676,7 +676,7 @@ describe('messages2', function() 3 | 4 | 5 | - 6 [+93] | + 6{6: [+93]} | Type number and (q or empty cancels): ^ | ]] command('set mousescroll=ver:2') @@ -687,13 +687,13 @@ describe('messages2', function() | {1:~ }|*4 {3: }| - 1 [+1] | + 1{6: [+1]} | 2 | 3 | 4 | 5 | 6 | - 7 [+92] | + 7{6: [+92]} | Type number and (q or empty cancels): ^ | ]]) feed('') @@ -703,13 +703,13 @@ describe('messages2', function() | {1:~ }|*4 {3: }| - 5 [+5] | + 5{6: [+5]} | 6 | 7 | 8 | 9 | 10 | - 11 [+88] | + 11{6: [+88]} | Type number and (q or empty cancels): ^ | ]]) feed('') @@ -719,7 +719,7 @@ describe('messages2', function() | {1:~ }|*4 {3: }| - 93 [+93] | + 93{6: [+93]} | 94 | 95 | 96 | @@ -739,13 +739,13 @@ describe('messages2', function() | {1:~ }|*4 {3: }| - 2 [+2] | + 2{6: [+2]} | 3 | 4 | 5 | 6 | 7 | - 8 [+91] | + 8{6: [+91]} | Type number and (q or empty cancels): ^ | ]]) feed('') @@ -753,13 +753,13 @@ describe('messages2', function() | {1:~ }|*4 {3: }| - 1 [+1] | + 1{6: [+1]} | 2 | 3 | 4 | 5 | 6 | - 7 [+92] | + 7{6: [+92]} | Type number and (q or empty cancels): ^ | ]]) feed('<0,0>') @@ -1032,7 +1032,7 @@ describe('messages2', function() ^ | {1:~ }|*2 foo |*2 - {14:f}oo [+6] | + {14:f}oo{6: [+6]} | ]]) feed('') screen:expect([[ @@ -1247,7 +1247,7 @@ describe('messages2', function() screen:expect([[ ^ | {1:~ }|*12 - foo [+1] | + foo{6: [+1]} | ]]) end)