mirror of
https://github.com/neovim/neovim.git
synced 2026-08-27 01:21:48 +00:00
fix(decor): 'breakindent'/'showbreak' gap ignores active highlight #41073
Problem:
'breakindent' and 'showbreak' draw their own padding with no
reference to whatever decoration or syntax highlight is currently
active, so it goes unhighlighted even mid-highlight, not just past a
real EOL. Gating this on the decoration's `hl_eol` flag (as an
earlier version of this fix did) missed plain highlights with no
`hl_eol` at all, which have the exact same problem.
Solution:
Snapshot decor_attr into decor_attr_save right before it can be
reset by 'linebreak' filler handling, and pass it into
handle_breakindent()/handle_showbreak_and_filler() to extend into
their padding: it is not a real end of the highlight, just screen
cells with no buffer text. Like the 'linebreak' filler, an
underline/strikethrough/overline is excluded, since it looks like a
broken line drawn over the gap. This also fixes 'breakindent' losing
the highlight right after a real 'linebreak' word-push, since that
reset otherwise leaked into the next row.
(cherry picked from commit 866d61e91a)
This commit is contained in:
committed by
github-actions[bot]
parent
b1e88d1ed4
commit
c34d02ac79
@@ -786,15 +786,17 @@ static bool attr_has_line_deco(int attr)
|
||||
return (ae.rgb_ae_attr & mask) != 0 || (ae.cterm_ae_attr & mask) != 0;
|
||||
}
|
||||
|
||||
static void handle_breakindent(win_T *wp, winlinevars_T *wlv)
|
||||
static void handle_breakindent(win_T *wp, winlinevars_T *wlv, int gap_decor_attr)
|
||||
{
|
||||
// draw 'breakindent': indent wrapped text accordingly
|
||||
// if wlv->need_showbreak is set, breakindent also applies
|
||||
if (wp->w_p_bri && (wlv->row > wlv->startrow + wlv->filler_lines
|
||||
|| wlv->need_showbreak)) {
|
||||
int attr = 0;
|
||||
// Extend the still-active decoration/syntax highlight (e.g. a full-width code-block background)
|
||||
// into the indent; it is not ending here, just skipping over screen cells with no buffer text.
|
||||
int attr = gap_decor_attr;
|
||||
if (wlv->diff_hlf != (hlf_T)0) {
|
||||
attr = win_hl_attr(wp, (int)wlv->diff_hlf);
|
||||
attr = hl_combine_attr(attr, win_hl_attr(wp, (int)wlv->diff_hlf));
|
||||
}
|
||||
int num = get_breakindent_win(wp, ml_get_buf(wp->w_buffer, wlv->lnum));
|
||||
if (wlv->row == wlv->startrow) {
|
||||
@@ -836,7 +838,7 @@ static void handle_breakindent(win_T *wp, winlinevars_T *wlv)
|
||||
}
|
||||
}
|
||||
|
||||
static void handle_showbreak_and_filler(win_T *wp, winlinevars_T *wlv)
|
||||
static void handle_showbreak_and_filler(win_T *wp, winlinevars_T *wlv, int gap_decor_attr)
|
||||
{
|
||||
int remaining = wp->w_view_width - wlv->off;
|
||||
if (wlv->filler_todo > wlv->filler_lines - wlv->n_virt_lines) {
|
||||
@@ -852,8 +854,10 @@ static void handle_showbreak_and_filler(win_T *wp, winlinevars_T *wlv)
|
||||
char *const sbr = get_showbreak_value(wp);
|
||||
if (*sbr != NUL && wlv->need_showbreak) {
|
||||
// Draw 'showbreak' at the start of each broken line.
|
||||
// Combine 'showbreak' with 'cursorline', prioritizing 'showbreak'.
|
||||
int attr = hl_combine_attr(wlv->cul_attr, win_hl_attr(wp, HLF_AT));
|
||||
// Combine 'showbreak' with 'cursorline' and the still-active decoration/syntax highlight,
|
||||
// prioritizing 'showbreak'.
|
||||
int attr = hl_combine_attr(wlv->cul_attr, gap_decor_attr);
|
||||
attr = hl_combine_attr(attr, win_hl_attr(wp, HLF_AT));
|
||||
colnr_T vcol_before = wlv->vcol;
|
||||
draw_col_buf(wp, wlv, sbr, strlen(sbr), attr, NULL, true);
|
||||
wlv->vcol_sbr = wlv->vcol;
|
||||
@@ -1118,6 +1122,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, b
|
||||
int search_attr = 0; // attributes desired by 'hlsearch' or ComplMatchIns
|
||||
int vcol_save_attr = 0; // saved attr for 'cursorcolumn'
|
||||
int decor_attr = 0; // attributes desired by syntax and extmarks
|
||||
int decor_attr_save = 0; // decor_attr saved for gap_decor_attr
|
||||
bool has_syntax = false; // this buffer has syntax highl.
|
||||
int folded_attr = 0; // attributes for folded line
|
||||
int eol_hl_off = 0; // 1 if highlighted char after EOL
|
||||
@@ -1807,13 +1812,15 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, b
|
||||
}
|
||||
}
|
||||
|
||||
// Check if 'breakindent' applies and show it.
|
||||
// Check if 'breakindent' applies and show it. Like the 'linebreak' filler, attrs must not
|
||||
// extend into this gap either (see attr_has_line_deco()).
|
||||
int const gap_decor_attr = attr_has_line_deco(decor_attr_save) ? 0 : decor_attr_save;
|
||||
if (!wp->w_briopt_sbr) {
|
||||
handle_breakindent(wp, &wlv);
|
||||
handle_breakindent(wp, &wlv, gap_decor_attr);
|
||||
}
|
||||
handle_showbreak_and_filler(wp, &wlv);
|
||||
handle_showbreak_and_filler(wp, &wlv, gap_decor_attr);
|
||||
if (wp->w_briopt_sbr) {
|
||||
handle_breakindent(wp, &wlv);
|
||||
handle_breakindent(wp, &wlv, gap_decor_attr);
|
||||
}
|
||||
|
||||
wlv.col = wlv.off;
|
||||
@@ -2348,6 +2355,8 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, b
|
||||
wlv.char_attr);
|
||||
}
|
||||
|
||||
decor_attr_save = decor_attr; // save current attr
|
||||
|
||||
// we don't want linebreak to apply for lines that start with
|
||||
// leading spaces, followed by long letters (since it would add
|
||||
// a break at the beginning of a line and this might be unexpected)
|
||||
@@ -2370,11 +2379,9 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, b
|
||||
wlv.n_extra = win_charsize(cstype, wlv.vcol, p, utf_ptr2CharInfo(p).value,
|
||||
&csarg).width - 1;
|
||||
|
||||
// Do not bleed an underline/strikethrough/overline into the filler for the pushed-down
|
||||
// word (TABs keep their own full-width highlight): drawn over blank cells, they look like
|
||||
// a broken line. A plain background/reverse-video highlight does not have this problem,
|
||||
// so leave it be. search_attr also resets when the search/match's own span ends exactly
|
||||
// here (on_last_col), matching its pre-existing semantics.
|
||||
// Do not bleed attrs into the filler for the pushed-down word (TABs keep their own
|
||||
// full-width highlight; see attr_has_line_deco()). search_attr also resets when its own
|
||||
// span ends exactly here (on_last_col), matching its pre-existing semantics.
|
||||
if (mb_c != TAB) {
|
||||
if (on_last_col || attr_has_line_deco(search_attr)) {
|
||||
search_attr = 0;
|
||||
|
||||
@@ -766,6 +766,63 @@ describe('decorations providers', function()
|
||||
}
|
||||
end)
|
||||
|
||||
it("'hl_eol' and plain highlights extend into 'linebreak'/'breakindent'/'showbreak' gaps", function()
|
||||
command('set wrap linebreak breakindent breakindentopt=shift:2')
|
||||
api.nvim_buf_set_lines(0, 0, -1, false, {
|
||||
' if vim.g.colors_name == "ferricferric" then',
|
||||
})
|
||||
local ns = api.nvim_create_namespace('code_bg')
|
||||
local id = api.nvim_buf_set_extmark(0, ns, 0, 0, { end_row = 1, hl_group = 'DiffAdd', hl_eol = true })
|
||||
screen:expect([[
|
||||
{13:^ if vim.g.colors_name == }|
|
||||
{13: "ferricferric" then }|
|
||||
{1:~ }|*5
|
||||
|
|
||||
]])
|
||||
|
||||
-- Extends across the 'showbreak' character too.
|
||||
command('set showbreak=>')
|
||||
screen:add_extra_attr_ids({
|
||||
[100] = { background = Screen.colors.LightBlue },
|
||||
[101] = { bold = true, foreground = Screen.colors.Blue1, background = Screen.colors.LightBlue },
|
||||
})
|
||||
screen:expect([[
|
||||
{100:^ if vim.g.colors_name == }|
|
||||
{100: }{101:>}{100:"ferricferric" then }|
|
||||
{1:~ }|*5
|
||||
|
|
||||
]])
|
||||
|
||||
-- No hl_eol here: these gaps have no real text, but the highlight is still active, not
|
||||
-- ending, so it must show through regardless.
|
||||
api.nvim_buf_set_extmark(0, ns, 0, 0, { id = id, end_row = 1, hl_group = 'DiffAdd' })
|
||||
screen:expect([[
|
||||
{100:^ if vim.g.colors_name == }|
|
||||
{100: }{101:>}{100:"ferricferric" then} |
|
||||
{1:~ }|*5
|
||||
|
|
||||
]])
|
||||
end)
|
||||
|
||||
it('breakindent keeps the highlight after a real linebreak word-push', function()
|
||||
-- The word pushed down by 'linebreak' resets decor_attr (it hides real word text, see listlbr_spec.lua), which must not then leak into
|
||||
-- 'breakindent's own gap on the row the word lands on.
|
||||
screen:try_resize(30, 5)
|
||||
screen:add_extra_attr_ids({ [100] = { background = Screen.colors.LightBlue } })
|
||||
command('set wrap linebreak breakindent')
|
||||
local line = ' This is a long indented line of plain text that will wrap nicely.'
|
||||
api.nvim_buf_set_lines(0, 0, -1, false, { line })
|
||||
local ns = api.nvim_create_namespace('code_bg2')
|
||||
api.nvim_buf_set_extmark(0, ns, 0, 0, { end_row = 0, end_col = #line, hl_group = 'DiffAdd' })
|
||||
screen:expect([[
|
||||
{100:^ This is a long indented }|
|
||||
{100: line of plain text that }|
|
||||
{100: will wrap nicely.} |
|
||||
{1:~ }|
|
||||
|
|
||||
]])
|
||||
end)
|
||||
|
||||
it('can create and remove signs when CursorMoved autocommand validates botline #18661', function()
|
||||
exec_lua([[
|
||||
local lines = {}
|
||||
|
||||
Reference in New Issue
Block a user