Repository navigation
Conversation
`wrapcol`(from `virtcol2col()`) is a **byte index**, but it was compared against `strdisplaywidth()`, a **display width**. And, while `wrap` & `breakindent` are set, both `strdisplaywidth()` & `virtcol()` are *inflated* (`72` spaces measure as `104` cells in a `40` column window). The *"No enough text after indent"* check therefore missed the last wrap of a line & inserted a wrap indent **before the line's last character**, so the indent's border/spaces showed up inside the text or pushed the last character onto its own row, > quoted quoted quote▋ d > quoted quoted▋ The checks now stay in byte space(`#line`) & require more than a single character to be left after the wrap point.
`wrap` indents are placed at the wrap columns of the width they were rendered at. Buffers were only rendered again on cursor movements, text changes & `wrap`/`linebreak` option changes, so resizing a window(e.g. narrowing a split & widening it back) left the indents behind in the middle of a line. `WinResized` & `VimResized` now render the buffers that are on screen again, debounced(`50`ms) the same way cursor movements are.
`test/wrap_tail_indent.md` holds block quotes & list items padded to exact byte lengths(72/73/86/90/88/71) that wrap into the cases `fine_wrap()` used to mangle, plus controls(heading, paragraph, table, code block) that must render unchanged, & the steps to check both fixes. `test/minimal_init.lua` runs the plugin from a checkout, so the fixture can be checked without `lazy.nvim`, ```sh nvim -u test/minimal_init.lua test/wrap_tail_indent.md ```
|
Here is a temporary fix in config without changing the source code: config = function (_, opts)
require("markview").setup(opts);
-- Workaround for upstream: markview recomputes its wrap indents only on render
-- triggers (cursor move, TextChanged, OptionSet wrap/linebreak), never on resize.
-- The indent is inserted as `virt_text_pos = "inline"` extmarks at the wrap columns
-- of whatever width it rendered at, so after narrowing a window (e.g. a split) and
-- widening it back, stray indent/border text stays stuck mid-line. Re-render the
-- visible buffers on resize so the marks are placed at the current wrap columns.
local timer = vim.uv.new_timer();
vim.api.nvim_create_autocmd({ "WinResized", "VimResized" }, {
group = vim.api.nvim_create_augroup("UserMarkviewResizeReflow", { clear = true }),
desc = "Re-render markview preview so wrap indents match the new window width",
callback = function ()
-- Resizes arrive in bursts (mouse drag, per-step terminal resize).
timer:stop();
timer:start(50, 0, vim.schedule_wrap(function ()
local actions = require("markview.actions");
local state = require("markview.state");
if not state.enabled() then
return;
end
for _, buf in ipairs(state.get_enabled_buffers()) do
if vim.api.nvim_buf_is_valid(buf) and vim.fn.win_findbuf(buf)[1] then
if actions.in_preview_mode() then
require("markview").render(buf);
else
actions.clear(buf);
end
end
end
end));
end,
});
-- Workaround for upstream: `wrap.fine_wrap()` compares the *byte* index of a wrap
-- point against a *display width* (`strdisplaywidth()`/`virtcol()`), and both of
-- those are inflated when `wrap` + `breakindent` are set (72 spaces measure as 104
-- cells). Its guards therefore miss the last wrap of a line and insert a wrap
-- indent *before the final character*, so the indent's border/spaces stick out in
-- the middle of the text (`> quoted quote▋ d`, `> quoted quoted▋ `).
-- A wrap indent only makes sense when more than one character follows it, so drop
-- those marks - only the ones carrying this call's own indent text, not marks from
-- other renderers - and only when at most the line's last character follows them.
local wrap = require("markview.wrap");
local fine_wrap = wrap.fine_wrap;
-- Text of a `virt_text` chunk list, highlighting ignored (highlight groups may
-- come back as strings or tables, the leading text never does).
local function virt_text_text (chunks)
local out = {};
for i, chunk in ipairs(chunks or {}) do
out[i] = chunk[1] or "";
end
return table.concat(out);
end
if fine_wrap then
wrap.fine_wrap = function (buffer, win, row, ns, indent)
fine_wrap(buffer, win, row, ns, indent);
local line = vim.api.nvim_buf_get_lines(buffer, row, row + 1, false)[1];
if not line then
return;
end
-- 1-based index of the last non-blank byte.
local last = #line;
while last > 0 and line:sub(last, last):match("%s") do
last = last - 1;
end
local indent_text = virt_text_text(indent);
for _, mark in ipairs(vim.api.nvim_buf_get_extmarks(buffer, ns, { row, 0 }, { row, -1 }, { details = true })) do
-- `mark[3]` is 0-based, so `mark[3] + 1` is the 1-based insertion point.
if mark[4].virt_text_pos == "inline"
and virt_text_text(mark[4].virt_text) == indent_text
and (mark[3] + 1) >= last then
vim.api.nvim_buf_del_extmark(buffer, ns, mark[1]);
end
end
end;
end
end,
|
|
@jsun969 I have already tried similar thing(s), a heuristic/assumption based approach doesn't cover all cases. I encourage you to read the explanation why it isn't working, before chugging this down AI's throat. For example,
As you can see, the decorations aren't getting correctly applied to the wrapped line.
Same issue persists for up to 3 characters.
On the 4th character the calculation becomes accurate again. My implementation tries to add as many wrap indentations as possible. On the other hand, your implementation tries to add as few wrap indentations instead. Both of these work based on assumption of where a line should get wrapped. Unfortunately, a lot of options can change it. So, unless you can get this data from the editor(e.g. via |



Description
Wrapped lines can end up with extra spaces that shouldn't be there:
wrap.fine_wrap()adds a wrap indent even where there is no text left for it, so a stray space(the block quote border) lands in the middle of the text.Test
Repro:
nvim -u test/minimal_init.lua test/wrap_tail_indent.md, then make the window ~40 columns wide & wide again(:vsplitthen:only, or drag the terminal pane wider).Note:
test/minimal_init.lua& thetest/wrap_tail_indent.mdfiles were only added for my own testing, I can remove them if you don't want them in the project.Demo
Before (left window):

After (left window):
