获取缓冲区窗口显示内容副本及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
相关产品推荐
相关产品推荐

