You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

获取缓冲区窗口显示内容副本及breakindent折行缩进计算求助

Neovim插件开发:处理breakindent时的折行缩进计算问题

我正在开发一款Neovim插件,需要在缓冲区视图内展示浮动窗口,核心需求是确定窗口中哪些行列已被缓冲区内容占用,从而把浮动窗口放在不遮挡代码的空白区域(即图中绿色标记的空白区域)。

我的思路是编写代码返回缓冲区可视内容的精确副本,通过行字符数与缓冲区宽度的差值计算可用空白空间。breakindent禁用时实现很简单,但开启后实际表现和文档不符:

  • 设置breakindentopt:min:20和shift:0时,可视缓冲区宽度缩小到某一阈值后,折行内容会向左偏移,且该阈值和设置的20无关,我发现阈值是原始缩进级别+18字符,但不清楚这个数值的来源;
  • 尝试支持breakindentopt:column选项时,情况更复杂,完全摸不着头绪。

我需要更可靠的实现方法,或者能指导我计算折行实际缩进级别的思路(vim.fn.indent(lineNum)只能返回行首缩进,无法处理折行)。以下是我的现有实现:

伪代码

- 获取缓冲区第一可视行号:vim.line('w0')
- 根据窗口高度计算预期的最后一行行号
- 提取该范围内的缓冲区行
- 逐行展开直到填满窗口高度:
  1. 计算缓冲区宽度(窗口宽度 - 侧边栏宽度)
  2. 截取行内容到缓冲区宽度,作为第一部分加入结果
  3. 剩余内容用空格填充到对应缩进级别,重复上述步骤

现有Lua代码

-- unwrap a line into multiple lines if it spans multiple rows
-- buffer_width: the width of the visible buffer area (window width - the gutter!)
local function unwrap_line(line, buffer_width, indent_size)
   local unwrapped_lines = {}
   local indent_offset = 0

   -- perform some dark magic to determine the shift in indentation level
   -- that is applied to the rows containing the wrapped lines

   -- once the buffer_width shrinks below the magic_wrap_size, wrapped rows
   -- will be indented by -1 for each col smaller than the magic_wrap_size.
   -- for some reason unbeknownst to mankind this magic size is determined by taking
   -- the original indent size, and adding breakindentopt:min - 2
   local magic_wrap_size = indent_size + 20 - 2 -- 20 is the default breakindentopt:min

   -- if breakindent
   if vim.o.breakindent then
      -- if column is set,
      local column_value_string = vim.o.breakindentopt:match("column:([%-]?%d+)")
      if column_value_string then
         indent_offset = tonumber(column_value_string) - indent_size
      else
         -- start offset at the shift value
         local shift_value_string = vim.o.breakindentopt:match("shift:([%-]?%d+)")
         if shift_value_string then
            indent_offset = indent_offset + tonumber(shift_value_string)
         end
         -- adjust the magic_wrap_size if min is set
         local min_value_string = vim.o.breakindentopt:match("min:([%-]?%d+)")
         if min_value_string then
            magic_wrap_size = indent_size + indent_offset + tonumber(min_value_string) - 2
         end
      end

      -- take only negative offset values
      if magic_wrap_size > buffer_width then
         indent_offset = indent_offset + buffer_width - magic_wrap_size
      end
   else
      -- cancel out the indent size
      indent_offset = -indent_size
   end

   -- if a line is longer than the buffer width, split the line at buffer_width
   -- and add the first part to the unwrapped lines
   -- then pad the second part with spaces to the indent size and add it to the unwrapped lines
   -- then repeat until the line is fully unwrapped
   while #line > buffer_width do
      local first_part = string.sub(line, 1, buffer_width)
      table.insert(unwrapped_lines, first_part)
      local second_part = string.sub(line, buffer_width + 1)
      line = string.rep(" ", indent_size + indent_offset) .. second_part
   end

   -- insert (rest of) the line
   table.insert(unwrapped_lines, line)
   return unwrapped_lines
end

-- gets the lines that are visible in the buffer area, unwrapped
-- (i.e. if a line spans multiple rows, each row is now it's own line)
local function get_visible_lines_unwrapped(window_housing_buffer, visible_buffer_width, visible_buffer_height)
   -- get the 0-based index of the first line that is visible in the buffer area
   local index_first_line
   vim.api.nvim_win_call(window_housing_buffer, function()
      index_first_line = vim.fn.line("w0") - 1
   end)
   -- get the 0-based index of the line where we expect the last line to be, in the case that none of the lines are wrapped
   local expected_index_last_line = index_first_line + visible_buffer_height
   -- get the lines from the expected span
   local expected_lines = vim.api.nvim_buf_get_lines(
      vim.api.nvim_win_get_buf(window_housing_buffer),
      index_first_line,
      expected_index_last_line,
      false
   )

   local visible_lines = {}
   -- for each line in the expected span, unwrap the line and add it to the visible lines
   for i, line in ipairs(expected_lines) do
      -- satisfied when we have enough lines (or we could grab too many if the last line is wrapped)
      local satisfied = false
      local indent_size = 0
      vim.api.nvim_win_call(window_housing_buffer, function()
         indent_size = vim.fn.indent(index_first_line + i)
      end)

      -- if the indent_size
      local unwrapped_lines = unwrap_line(line, visible_buffer_width, indent_size)
      -- for each unwrapped line, add it to the visible lines
      for _, unwrapped_line in ipairs(unwrapped_lines) do
         table.insert(visible_lines, unwrapped_line)
         -- if we have enough lines, break
         if #visible_lines == visible_buffer_height then
            satisfied = true
            break
         end
      end
      if satisfied then
         break
      end
   end
   return visible_lines
end

内容的提问来源于stack exchange,提问作者KamielDev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 06:34:59