commit c973c7ae6f5670f00f1054123cae81c6b767a3c8
parent a6e2c2234741d25fbff7fc1b6ecca1a2a86bf01c
Author: luukvbaal <luukvbaal@gmail.com>
Date: Mon, 26 May 2025 16:25:45 +0200
fix(extui): write each message chunk pattern separately (#34188)
Problem: Bulking message lines to write in a single API call is
complicated and still not correct w.r.t. overwriting
highlights.
Solution: Write each chunk pattern separately with it's highlight
such that it will be spliced correctly for message chunks
that contain a carriage return. Go with correctness over
performance until this proves to be too inefficient.
Also add an identifying name to the various extui buffers.
Diffstat:
2 files changed, 24 insertions(+), 50 deletions(-)
diff --git a/runtime/lua/vim/_extui/messages.lua b/runtime/lua/vim/_extui/messages.lua
@@ -204,70 +204,40 @@ function M.show_msg(tar, content, replace_last, append, more)
---this is the first message, or in case of a repeated or replaced message.
local row = M[tar] and count <= 1 and (tar == 'cmd' and ext.cmd.row or 0)
or line_count - ((replace_last or restart or cr or append) and 1 or 0)
- local start_line = append and api.nvim_buf_get_lines(ext.bufs[tar], row, row + 1, false)[1]
+ local curline = (cr or append) and api.nvim_buf_get_lines(ext.bufs[tar], row, row + 1, false)[1]
local start_row, width = row, M.box.width
- local lines, marks = {}, {} ---@type string[], [integer, integer, vim.api.keyset.set_extmark][]
+ col = append and not cr and math.min(col, #curline) or 0
-- Accumulate to be inserted and highlighted message chunks for a non-repeated message.
- for _, chunk in ipairs(dupe > 0 and tar == ext.cfg.msg.pos and {} or content) do
- local idx = (#lines == 0 and 1 or #lines)
- local head = lines[idx] or ''
-
+ for _, chunk in ipairs((M[tar] or dupe == 0) and content or {}) do
-- Split at newline and write to start of line after carriage return.
for str in (chunk[2] .. '\0'):gmatch('.-[\n\r%z]') do
- local mid = str:gsub('[\n\r%z]', '')
- -- Remove previous highlight from overwritten text.
- if #head == 0 and marks[#marks] and marks[#marks][1] == row then
- if marks[#marks][1] < row then
- marks[#marks + 1] = { row, 0, vim.deepcopy(marks[#marks][3]) }
- marks[#marks - 1][3].end_col = 0
- end
- marks[#marks][2] = math.max(marks[#marks][2], #mid)
+ local repl, pat = str:sub(1, -2), str:sub(-1)
+ local end_col = col + #repl
+
+ if line_count < row + 1 then
+ api.nvim_buf_set_lines(ext.bufs[tar], row, -1, false, { repl })
+ line_count = line_count + 1
+ else
+ local ecol = curline and math.min(end_col, #curline) or -1
+ api.nvim_buf_set_text(ext.bufs[tar], row, col, row, ecol, { repl })
end
+ curline = api.nvim_buf_get_lines(ext.bufs[tar], row, row + 1, false)[1]
+ width = tar == 'box' and math.max(width, api.nvim_strwidth(curline)) or 0
- col = append and not cr and col or 0
- local end_col = #mid + col
if chunk[3] > 0 then
- marks[#marks + 1] = { row, col, { end_col = end_col, hl_group = chunk[3] } }
+ local opts = { end_col = end_col, hl_group = chunk[3] } ---@type vim.api.keyset.set_extmark
+ api.nvim_buf_set_extmark(ext.bufs[tar], ext.ns, row, col, opts)
end
- if row == start_row then
- local ecol = math.min(end_col, start_line and #start_line or -1)
- if line_count < row + 1 then
- api.nvim_buf_set_lines(ext.bufs[tar], row, -1, false, { mid })
- line_count = line_count + 1
- else
- api.nvim_buf_set_text(ext.bufs[tar], row, col, row, ecol, { mid })
- end
- start_line = api.nvim_buf_get_lines(ext.bufs[tar], row, row + 1, false)[1]
+ if pat == '\n' then
+ row, col = row + 1, 0
else
- local tail = #head == 0 and lines[idx] and lines[idx]:sub(#mid + 1) or ''
- lines[idx] = ('%s%s%s'):format(head, mid, tail)
+ col = pat == '\r' and 0 or end_col
end
- width = tar == 'box' and math.max(width, api.nvim_strwidth(lines[idx] or start_line)) or 0
-
- if str:sub(-1) == '\n' then
- append, row, idx = false, row + 1, idx + (row > start_row and 1 or 0)
- elseif str:sub(-1) == '\r' then
- cr, append = true, false
- end
- head, col = '', end_col
end
end
- if not M[tar] or dupe == 0 then
- -- Add highlighted message to buffer.
- api.nvim_buf_set_lines(ext.bufs[tar], start_row + 1, -1, false, lines)
- for _, mark in ipairs(marks) do
- api.nvim_buf_set_extmark(ext.bufs[tar], ext.ns, mark[1], mark[2], mark[3])
- end
- M.virt.msg[M.virt.idx.dupe][1] = dupe ~= 0 and M.virt.msg[M.virt.idx.dupe][1] or nil
- else
- -- Place (x) indicator for repeated messages. Mainly to mitigate unnecessary
- -- resizing of the message box window, but also placed in the cmdline.
- M.virt.msg[M.virt.idx.dupe][1] = { 0, ('(%d)'):format(dupe) }
- end
-
if tar == 'box' then
api.nvim_win_set_width(ext.wins[ext.tab].box, width)
local h = api.nvim_win_text_height(ext.wins[ext.tab].box, { start_row = start_row })
@@ -313,8 +283,11 @@ function M.show_msg(tar, content, replace_last, append, more)
end
if M[tar] then
- set_virttext('msg')
+ -- Place (x) indicator for repeated messages. Mainly to mitigate unnecessary
+ -- resizing of the message box window, but also placed in the cmdline.
+ M.virt.msg[M.virt.idx.dupe][1] = dupe > 0 and { 0, ('(%d)'):format(dupe) } or nil
M.prev_msg, M.dupe, M[tar].count = msg, dupe, count
+ set_virttext('msg')
end
-- Reset message state the next event loop iteration.
diff --git a/runtime/lua/vim/_extui/shared.lua b/runtime/lua/vim/_extui/shared.lua
@@ -41,6 +41,7 @@ function M.tab_check_wins()
for _, type in ipairs({ 'box', 'cmd', 'more', 'prompt' }) do
if not api.nvim_buf_is_valid(M.bufs[type]) then
M.bufs[type] = api.nvim_create_buf(false, true)
+ api.nvim_buf_set_name(M.bufs[type], 'vim._extui.' .. type)
if type == 'cmd' then
-- Attach highlighter to the cmdline buffer.
local parser = assert(vim.treesitter.get_parser(M.bufs.cmd, 'vim', {}))