commit a96665cf4867c82f5bbcae5a1c01542c0d15859e
parent 8a207b3e19e97a9cb4ce0be6d2cd4ff1fe8bc870
Author: luukvbaal <luukvbaal@gmail.com>
Date: Sat, 24 May 2025 20:31:20 +0200
fix(extui): using tracked message column in cleared buffer (#34154)
Problem: Using tracked message column as column in cleared buffer.
Solution: Ensure column does not exceed current line length.
Further work to ensure carriage return and cmdline block mode
work properly.
Diffstat:
2 files changed, 47 insertions(+), 39 deletions(-)
diff --git a/runtime/lua/vim/_extui/cmdline.lua b/runtime/lua/vim/_extui/cmdline.lua
@@ -164,8 +164,8 @@ end
--- Clear cmdline buffer and leave the cmdline.
function M.cmdline_block_hide()
- M.cmdline_hide(nil, true)
M.row = 0
+ M.cmdline_hide(nil, true)
end
return M
diff --git a/runtime/lua/vim/_extui/messages.lua b/runtime/lua/vim/_extui/messages.lua
@@ -170,7 +170,7 @@ end
-- We need to keep track of the current message column to be able to
-- append or overwrite messages for :echon or carriage returns.
-local prev_tar, col = '', 0
+local col = 0
---@param tar 'box'|'cmd'|'more'|'prompt'
---@param content MsgContent
---@param replace_last boolean
@@ -178,8 +178,7 @@ local prev_tar, col = '', 0
---@param more boolean? If true, route messages that exceed the target window to more window.
function M.show_msg(tar, content, replace_last, append, more)
local msg, restart, cr, dupe, count = '', false, false, 0, 0
- col = tar == prev_tar and col or 0
- prev_tar = tar
+ append = append and col > 0
if M[tar] then -- tar == 'box'|'cmd'
if tar == ext.cfg.msg.pos then
@@ -187,18 +186,11 @@ function M.show_msg(tar, content, replace_last, append, more)
for _, chunk in ipairs(content) do
msg = msg .. chunk[2]
end
- dupe = (msg == M.prev_msg and M.dupe + 1 or 0)
+ dupe = (msg == M.prev_msg and ext.cmd.row == 0 and M.dupe + 1 or 0)
end
cr = M[tar].count > 0 and msg:sub(1, 1) == '\r'
- col = M[tar].count > 0 and append and not cr and col or 0
restart = M[tar].count > 0 and (replace_last or dupe > 0)
- -- Reset indicators the next event loop iteration.
- if M.cmd.count == 0 and tar == 'cmd' then
- vim.schedule(function()
- M.cmd.lines, M.cmd.count = 0, 0
- end)
- end
count = M[tar].count + ((restart or msg == '\n') and 0 or 1)
end
@@ -211,8 +203,9 @@ function M.show_msg(tar, content, replace_last, append, more)
---@type integer Start row after last line in the target buffer, unless
---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 col > 0) and 1 or 0)
- local start_row, start_col, width = row, col, M.box.width
+ 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 start_row, width = row, M.box.width
local lines, marks = {}, {} ---@type string[], [integer, integer, vim.api.keyset.set_extmark][]
-- Accumulate to be inserted and highlighted message chunks for a non-repeated message.
@@ -223,10 +216,6 @@ function M.show_msg(tar, content, replace_last, append, more)
-- 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]', '')
- local tail = #head == 0 and lines[idx] and lines[idx]:sub(#mid + 1) or ''
- lines[idx] = ('%s%s%s'):format(head, mid, tail)
- width = tar == 'box' and math.max(width, api.nvim_strwidth(lines[idx])) or 0
-
-- Remove previous highlight from overwritten text.
if #head == 0 and marks[#marks] and marks[#marks][1] == row then
if marks[#marks][1] < row then
@@ -236,27 +225,39 @@ function M.show_msg(tar, content, replace_last, append, more)
marks[#marks][2] = math.max(marks[#marks][2], #mid)
end
+ 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 = col + #mid, hl_group = chunk[3] } }
+ marks[#marks + 1] = { row, col, { end_col = end_col, hl_group = chunk[3] } }
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]
+ else
+ local tail = #head == 0 and lines[idx] and lines[idx]:sub(#mid + 1) or ''
+ lines[idx] = ('%s%s%s'):format(head, mid, tail)
+ end
+ width = tar == 'box' and math.max(width, api.nvim_strwidth(lines[idx] or start_line)) or 0
+
if str:sub(-1) == '\n' then
- row, idx = row + 1, idx + 1
+ 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
- col = str:sub(-1) ~= '\0' and 0 or col + #mid
- head = ''
+ head, col = '', end_col
end
end
if not M[tar] or dupe == 0 then
-- Add highlighted message to buffer.
- if start_row == line_count - 1 and (start_col > 0 or cr) then
- local first_row = api.nvim_buf_get_lines(ext.bufs[tar], start_row, start_row + 1, false)[1]
- local end_col = math.min(#first_row, start_col + #lines[1])
- api.nvim_buf_set_text(ext.bufs[tar], start_row, start_col, -1, end_col, { lines[1] })
- start_row = start_row + 1
- table.remove(lines, 1)
- end
- api.nvim_buf_set_lines(ext.bufs[tar], start_row, -1, false, lines)
+ 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
@@ -287,13 +288,6 @@ function M.show_msg(tar, content, replace_last, append, more)
M.box.width = width
end
elseif tar == 'cmd' and dupe == 0 then
- local h = api.nvim_win_text_height(ext.wins[ext.tab].cmd, {})
- if more and h.all > ext.cmdheight then
- api.nvim_buf_set_lines(ext.bufs.cmd, start_row, -1, false, {})
- M.msg_history_show({ { 'spill', content } }) -- show message in 'more' window
- return
- end
-
fn.clearmatches(ext.wins[ext.tab].cmd) -- Clear matchparen highlights.
if ext.cmd.row > 0 then
-- In block mode the cmdheight is already dynamic, so just print the full message
@@ -302,6 +296,13 @@ function M.show_msg(tar, content, replace_last, append, more)
ext.cmd.cmdline_show({}, 0, ':', '', ext.cmd.indent, 0, 0)
api.nvim__redraw({ flush = true, cursor = true, win = ext.wins[ext.tab].cmd })
else
+ local h = api.nvim_win_text_height(ext.wins[ext.tab].cmd, {})
+ if more and h.all > ext.cmdheight then
+ api.nvim_buf_set_lines(ext.bufs.cmd, start_row, -1, false, {})
+ M.msg_history_show({ { 'spill', content } }) -- show message in 'more' window
+ return
+ end
+
api.nvim_win_set_cursor(ext.wins[ext.tab][tar], { 1, 0 })
ext.cmd.highlighter.active[ext.bufs.cmd] = nil
-- Place [+x] indicator for lines that spill over 'cmdheight'.
@@ -315,6 +316,13 @@ function M.show_msg(tar, content, replace_last, append, more)
set_virttext('msg')
M.prev_msg, M.dupe, M[tar].count = msg, dupe, count
end
+
+ -- Reset message state the next event loop iteration.
+ if start_row == 0 or ext.cmd.row > 0 then
+ vim.schedule(function()
+ col, M.cmd.lines, M.cmd.count = 0, 0, 0
+ end)
+ end
end
local append_more = 0
@@ -357,8 +365,8 @@ function M.msg_show(kind, content, _, _, append)
end
if tar == 'cmd' then
- if ext.cmd.level > 0 then
- return -- Do not overwrite an active cmdline.
+ if ext.cmd.level > 0 and ext.cmd.row == 0 then
+ return -- Do not overwrite an active cmdline unless in block mode.
end
-- Store the time when an error message was emitted in order to not overwrite
-- it with 'last' virt_text in the cmdline to give the user a chance to read it.