mirror of
https://github.com/neovim/neovim.git
synced 2026-08-15 03:49:28 +00:00
fix(decor): highlight without 'hl_eol' bleeds into wrap gaps #41160
Problem: 'linebreak' filler and 'breakindent'/'showbreak' padding are screen cells with no buffer character behind them, yet a decoration draws over them whether or not it asked to cover such cells. A highlight bounded to its text then paints a tail out to the edge of the row, most visible on inline code spans from plugins. That same highlight already leaves the cells past the end of a line alone, so it treats identical cells two different ways. Solution: Only a decoration with 'hl_eol' draws the gaps, which is what the flag already means at the end of a line. A full-width background such as a fenced code block sets it and still covers them. Classic :syntax has no such flag and is unchanged.
This commit is contained in:
@@ -767,6 +767,7 @@ next_mark:
|
||||
int new_cur_end = 0;
|
||||
|
||||
int attr = 0;
|
||||
int hl_eol_attr = 0;
|
||||
int conceal = 0;
|
||||
schar_T conceal_char = 0;
|
||||
int conceal_attr = 0;
|
||||
@@ -789,6 +790,9 @@ next_mark:
|
||||
|
||||
if (r->attr_id > 0) {
|
||||
attr = hl_combine_attr(attr, r->attr_id);
|
||||
if (r->kind == kDecorKindHighlight && (r->data.sh.flags & kSHHlEol)) {
|
||||
hl_eol_attr = hl_combine_attr(hl_eol_attr, r->attr_id);
|
||||
}
|
||||
}
|
||||
|
||||
if (r->kind == kDecorKindHighlight && (r->data.sh.flags & kSHConceal)) {
|
||||
@@ -852,6 +856,7 @@ next_mark:
|
||||
state->col_last = col_last;
|
||||
|
||||
state->current = attr;
|
||||
state->current_hl_eol = hl_eol_attr;
|
||||
state->conceal = conceal;
|
||||
state->conceal_char = conceal_char;
|
||||
state->conceal_attr = conceal_attr;
|
||||
|
||||
@@ -87,6 +87,7 @@ typedef struct {
|
||||
int row;
|
||||
int col_last;
|
||||
int current;
|
||||
int current_hl_eol; ///< "current" limited to ranges with "hl_eol"
|
||||
int eol_col;
|
||||
|
||||
int conceal;
|
||||
|
||||
@@ -1146,7 +1146,8 @@ 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
|
||||
int syntax_attr = 0; // syntax-only part of "decor_attr"
|
||||
int gap_attr_save = 0; // attr for gaps showing no buffer text
|
||||
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
|
||||
@@ -1856,7 +1857,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, b
|
||||
|
||||
// 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;
|
||||
int const gap_decor_attr = attr_has_line_deco(gap_attr_save) ? 0 : gap_attr_save;
|
||||
if (!wp->w_briopt_sbr) {
|
||||
handle_breakindent(wp, &wlv, gap_decor_attr);
|
||||
}
|
||||
@@ -2275,6 +2276,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, b
|
||||
ptr++;
|
||||
|
||||
decor_attr = 0;
|
||||
syntax_attr = 0;
|
||||
if (extra_check) {
|
||||
const bool no_plain_buffer = (wp->w_s->b_p_spo_flags & kOptSpoFlagNoplainbuffer) != 0;
|
||||
bool can_spell = !no_plain_buffer;
|
||||
@@ -2315,6 +2317,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, b
|
||||
|
||||
if (has_decor && v > 0) {
|
||||
// extmarks take preceedence over syntax.c
|
||||
syntax_attr = decor_attr;
|
||||
decor_attr = hl_combine_attr(decor_attr, extmark_attr);
|
||||
decor_conceal = decor_state.conceal;
|
||||
can_spell = TRISTATE_TO_BOOL(decor_state.spell, can_spell);
|
||||
@@ -2397,7 +2400,10 @@ 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
|
||||
// Gaps ('linebreak' filler, 'breakindent'/'showbreak' padding) show no buffer text, so only
|
||||
// an "hl_eol" decoration draws there.
|
||||
gap_attr_save = has_decor ? hl_combine_attr(syntax_attr, decor_state.current_hl_eol)
|
||||
: decor_attr;
|
||||
|
||||
// we don't want linebreak to apply for lines that start with
|
||||
// leading spaces, followed by long letters (since it would add
|
||||
@@ -2429,6 +2435,9 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, b
|
||||
if (on_last_col || attr_has_line_deco(search_attr)) {
|
||||
search_attr = 0;
|
||||
}
|
||||
if (has_decor) {
|
||||
decor_attr = gap_attr_save;
|
||||
}
|
||||
if (attr_has_line_deco(decor_attr)) {
|
||||
decor_attr = 0;
|
||||
}
|
||||
|
||||
@@ -330,22 +330,36 @@ describe('listlbr', function()
|
||||
]])
|
||||
end)
|
||||
|
||||
it('extends a background highlight into the filler for the last word on a line', function()
|
||||
it('extends a background highlight into the filler only with hl_eol', function()
|
||||
local screen = Screen.new(20, 5)
|
||||
screen:add_extra_attr_ids({ [100] = { background = Screen.colors.Red } })
|
||||
source([[
|
||||
set wrap linebreak
|
||||
call setline(1, 'word1.word2verylongwordthatpushesdown')
|
||||
highlight TestBg guibg=Red ctermbg=Red
|
||||
let g:ns = nvim_create_namespace('')
|
||||
]])
|
||||
|
||||
-- The pushed-down word is the last thing on the line, so nothing absorbs the width overflow: a
|
||||
-- background highlight must still extend into the filler, since a solid color looks fine over
|
||||
-- blank cells (unlike underline/strikethrough, see tests above).
|
||||
command([[call nvim_buf_add_highlight(0, -1, 'TestBg', 0, 0, -1)]])
|
||||
-- 'hl_eol' covers cells with no text behind them, so the filler is covered too.
|
||||
command(
|
||||
'call nvim_buf_set_extmark(0, g:ns, 0, 0, '
|
||||
.. "{'end_row': 1, 'hl_group': 'TestBg', 'hl_eol': v:true})"
|
||||
)
|
||||
screen:expect([[
|
||||
{100:^word1. }|
|
||||
{100:word2verylongwordtha}|
|
||||
{100:tpushesdown }|
|
||||
{1:~ }|
|
||||
|
|
||||
]])
|
||||
|
||||
-- Without it the highlight decorates characters, so it stops where they do, just as an
|
||||
-- underline already does (see tests above).
|
||||
command('call nvim_buf_clear_namespace(0, -1, 0, -1)')
|
||||
command([[call nvim_buf_add_highlight(0, -1, 'TestBg', 0, 0, -1)]])
|
||||
screen:expect([[
|
||||
{100:^word1.} |
|
||||
{100:word2verylongwordtha}|
|
||||
{100:tpushesdown} |
|
||||
{1:~ }|
|
||||
|
|
||||
|
||||
@@ -767,7 +767,7 @@ describe('decorations providers', function()
|
||||
}
|
||||
end)
|
||||
|
||||
it("'hl_eol' and plain highlights extend into 'linebreak'/'breakindent'/'showbreak' gaps", function()
|
||||
it("'hl_eol' extends 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',
|
||||
@@ -794,31 +794,31 @@ describe('decorations providers', function()
|
||||
|
|
||||
]])
|
||||
|
||||
-- No hl_eol here: these gaps have no real text, but the highlight is still active, not
|
||||
-- ending, so it must show through regardless.
|
||||
-- Without 'hl_eol' the highlight decorates characters, so it leaves the gaps alone, the same
|
||||
-- way it already leaves the cells past the end of the line alone.
|
||||
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} |
|
||||
{100:^ if vim.g.colors_name == } |
|
||||
{1:>}{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.
|
||||
-- The word pushed down by 'linebreak' resets decor_attr (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' })
|
||||
api.nvim_buf_set_extmark(0, ns, 0, 0, { end_row = 1, hl_group = 'DiffAdd', hl_eol = true })
|
||||
screen:expect([[
|
||||
{100:^ This is a long indented }|
|
||||
{100: line of plain text that }|
|
||||
{100: will wrap nicely.} |
|
||||
{100: will wrap nicely. }|
|
||||
{1:~ }|
|
||||
|
|
||||
]])
|
||||
|
||||
Reference in New Issue
Block a user