diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index cfc9d6d624..3eefd8b58f 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -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; diff --git a/src/nvim/decoration.h b/src/nvim/decoration.h index 6ce7357d0f..c321cda763 100644 --- a/src/nvim/decoration.h +++ b/src/nvim/decoration.h @@ -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; diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c index e1fa5c3cb0..fe8fa0e032 100644 --- a/src/nvim/drawline.c +++ b/src/nvim/drawline.c @@ -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; } diff --git a/test/functional/legacy/listlbr_spec.lua b/test/functional/legacy/listlbr_spec.lua index a4dfad6cf7..d085dcaa4a 100644 --- a/test/functional/legacy/listlbr_spec.lua +++ b/test/functional/legacy/listlbr_spec.lua @@ -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:~ }| | diff --git a/test/functional/ui/decorations_spec.lua b/test/functional/ui/decorations_spec.lua index 2ebffc8611..b3cda38a9f 100644 --- a/test/functional/ui/decorations_spec.lua +++ b/test/functional/ui/decorations_spec.lua @@ -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:~ }| | ]])