Get an exact copy of the buffer as it appears in the window

For a plugin I’m developing, I want to show a floating window somewhere within the buffer view, but want to work out exactly which rows and columns in the window are already taken up by content in the buffer so that I can place the floating window in a location where it does not obscure the code.

What I mean with that is that if I have a window that looks like the left, I am trying to determine that the window positions marked in green are free.

enter image description here

To accomplish this, my plan was to write some code that returns an exact copy of the contents of the visible buffer, so that I can then calculate the empty space available by subtracting the buffer width with the amount of characters that row contains.

This is quite trivial for situations where breakindent is disabled, as when a line spans multiple rows, the wrapped content just starts at the very start of the buffer.

But, this becomes much more tricky when breakindent is enabled, the documentation doesn’t match my expectations. For example, the documentation for breakindentopt:min reads

min:{n}     Minimum text width that will be kept after
                applying 'breakindent', even if the resulting
                text should normally be narrower. This prevents
                text indented almost to the right window border
                occupying lot of vertical space when broken.
                (default: 20)

But even when explicitly setting breakindentopt:min:20 and shift:0 lines start being shifted towards the left when the visible buffer width starts shrinking below a certain width, and that width doesn’t seem to correlate to 20…

enter image description here

I’ve worked out that for some reason it starts shifting when the visible buffer width starts becoming smaller than the original indentation level + 18 characters, but where that value truly comes from, I don’t understand.
I then also tried supporting breakindentopt:column but that got me even more confused.

If anyone knows a better way of achieving this, or can point me in the right direction as to how I calculate the ACTUAL indentation level of a wrapping line (vim.fn.indent(lineNum) just returns the indentation level at the start of the line, and doesn’t care about wrapping lines), that would be nice.

I’ll share my code that I currently have, but since it’s quite a mouthful, here’s a pseudocode rundown:

     - Get the line num of the first visible line of the buffer with vim.line('w0')
     - Calculate the line num of the last expected line given the window height
     - Grab those lines from the buffer
     - unwrap each line until we have enough to fill the window height
     1. to unwrap a line, we calculate the buffer_width (window width - gutter width)
     2. we take the line up to buffer_width, add it as our first line
     3. we take the rest of the line, pad it with spaces to match the right indent level, and repeat with the padded result


    -- 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

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật