mirror of
				https://github.com/neovim/neovim.git
				synced 2025-11-04 09:44:31 +00:00 
			
		
		
		
	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.
			
			
This commit is contained in:
		@@ -204,68 +204,38 @@ 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)
 | 
			
		||||
      end
 | 
			
		||||
      local repl, pat = str:sub(1, -2), str:sub(-1)
 | 
			
		||||
      local end_col = col + #repl
 | 
			
		||||
 | 
			
		||||
      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] } }
 | 
			
		||||
      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 line_count < row + 1 then
 | 
			
		||||
        api.nvim_buf_set_lines(ext.bufs[tar], row, -1, false, { repl })
 | 
			
		||||
        line_count = line_count + 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)
 | 
			
		||||
        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
 | 
			
		||||
      width = tar == 'box' and math.max(width, api.nvim_strwidth(lines[idx] or start_line)) or 0
 | 
			
		||||
      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
 | 
			
		||||
 | 
			
		||||
      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
 | 
			
		||||
      if chunk[3] > 0 then
 | 
			
		||||
        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
 | 
			
		||||
      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])
 | 
			
		||||
      if pat == '\n' then
 | 
			
		||||
        row, col = row + 1, 0
 | 
			
		||||
      else
 | 
			
		||||
        col = pat == '\r' and 0 or end_col
 | 
			
		||||
      end
 | 
			
		||||
    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
 | 
			
		||||
@@ -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.
 | 
			
		||||
 
 | 
			
		||||
@@ -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', {}))
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user