mirror of
https://github.com/neovim/neovim.git
synced 2026-08-15 03:49:28 +00:00
fix(ui): highlight bleeds into 'linebreak' filler #41072
Problem: When 'linebreak' pushes a word entirely to the next screen row, the filler cells left on the current row keep whatever highlight was set by the last real character before the break, even when that highlight should not extend past it (e.g. an underline, which looks broken drawn over blank cells). Solution: Reset decor_attr and area_attr at the filler when their attribute has an underline, undercurl, strikethrough, or overline; otherwise leave them, since a plain background or reverse-video highlight looks correct extending through blank filler cells, regardless of where the pushed-down word happens to end. search_attr keeps the same check, plus its pre-existing on_last_col case (its own match ending exactly here).
This commit is contained in:
@@ -800,6 +800,16 @@ static void draw_statuscol(win_T *wp, winlinevars_T *wlv, int col_rows, statusco
|
||||
draw_col_fill(wlv, schar_from_ascii(' '), stcp->width - width, cur_attr);
|
||||
}
|
||||
|
||||
/// Whether "attr" draws an underline, undercurl, strikethrough, or overline: a line tied to text
|
||||
/// glyphs that looks broken drawn over blank filler cells, unlike a plain background/reverse-video
|
||||
/// highlight.
|
||||
static bool attr_has_line_deco(int attr)
|
||||
{
|
||||
HlAttrs ae = syn_attr2entry(attr);
|
||||
int32_t const mask = HL_UNDERLINE_MASK | HL_STRIKETHROUGH | HL_OVERLINE;
|
||||
return (ae.rgb_ae_attr & mask) != 0 || (ae.cterm_ae_attr & mask) != 0;
|
||||
}
|
||||
|
||||
static void handle_breakindent(win_T *wp, winlinevars_T *wlv)
|
||||
{
|
||||
// draw 'breakindent': indent wrapped text accordingly
|
||||
@@ -2403,11 +2413,21 @@ 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;
|
||||
|
||||
if (on_last_col && mb_c != TAB) {
|
||||
// Do not continue search/match highlighting over the
|
||||
// line break, but for TABs the highlighting should
|
||||
// include the complete width of the character
|
||||
search_attr = 0;
|
||||
// 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.
|
||||
if (mb_c != TAB) {
|
||||
if (on_last_col || attr_has_line_deco(search_attr)) {
|
||||
search_attr = 0;
|
||||
}
|
||||
if (attr_has_line_deco(decor_attr)) {
|
||||
decor_attr = 0;
|
||||
}
|
||||
if (attr_has_line_deco(area_attr)) {
|
||||
area_attr = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (mb_c == TAB && wlv.n_extra + wlv.col > view_width) {
|
||||
|
||||
@@ -6,7 +6,7 @@ local Screen = require('test.functional.ui.screen')
|
||||
|
||||
local describe, it, before_each = t.describe, t.it, t.before_each
|
||||
local feed, insert, source = n.feed, n.insert, n.source
|
||||
local clear, feed_command, expect = n.clear, n.feed_command, n.expect
|
||||
local clear, command, feed_command, expect = n.clear, n.command, n.feed_command, n.expect
|
||||
|
||||
describe('listlbr', function()
|
||||
before_each(clear)
|
||||
@@ -274,4 +274,81 @@ describe('listlbr', function()
|
||||
{9:E490: No fold found} |
|
||||
]])
|
||||
end)
|
||||
|
||||
it('does not bleed match/extmark highlight into the filler before a wrapped word', function()
|
||||
local screen = Screen.new(60, 3)
|
||||
screen:add_extra_attr_ids({ [100] = { underline = true } })
|
||||
source([[
|
||||
set wrap linebreak
|
||||
call setline(1, '[license-commit]: https://github.com/neovim/neovim/commit/b17d9691a24099c9210289f16afb1a498a89d803')
|
||||
highlight TestUL gui=underline cterm=underline
|
||||
]])
|
||||
|
||||
-- Highlight extends past the break point: must not bleed into the filler before the
|
||||
-- pushed-down word, whether applied via matchadd() (search_attr) or an extmark (decor_attr).
|
||||
local expected = [[
|
||||
^[license-commit]: {100:https://github.com/neovim/neovim/commit/} |
|
||||
{100:b17d9691a24099c9210289f16afb1a498a89d803} |
|
||||
|
|
||||
]]
|
||||
|
||||
command([[call matchadd('TestUL', 'https://.*$')]])
|
||||
screen:expect(expected)
|
||||
|
||||
-- Confirm the highlight actually toggles off before re-checking via the extmark path below,
|
||||
-- so the second `expect(expected)` proves that path redraws correctly instead of coasting on
|
||||
-- leftover state from matchadd().
|
||||
command('call clearmatches()')
|
||||
screen:expect([[
|
||||
^[license-commit]: https://github.com/neovim/neovim/commit/ |
|
||||
b17d9691a24099c9210289f16afb1a498a89d803 |
|
||||
|
|
||||
]])
|
||||
|
||||
command([[call nvim_buf_add_highlight(0, -1, 'TestUL', 0, 18, -1)]])
|
||||
screen:expect(expected)
|
||||
end)
|
||||
|
||||
it('extends a to-end-of-line syntax highlight through the filler', function()
|
||||
local screen = Screen.new(30, 5)
|
||||
screen:add_extra_attr_ids({ [100] = { reverse = true } })
|
||||
source([[
|
||||
set wrap linebreak
|
||||
call setline(1, 'section.heading:' . repeat(' ', 40) . 'END')
|
||||
syntax match TestBar /^.*$/
|
||||
highlight TestBar gui=reverse cterm=reverse
|
||||
]])
|
||||
|
||||
-- Unlike the tests above: the filler here is real trailing padding covered by the same 'to end
|
||||
-- of line' match, so it must keep the highlight instead of going blank.
|
||||
screen:expect([[
|
||||
{100:^section. }|
|
||||
{100:heading: }|
|
||||
{100: END} |
|
||||
{1:~ }|
|
||||
|
|
||||
]])
|
||||
end)
|
||||
|
||||
it('extends a background highlight into the filler for the last word on a line', 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
|
||||
]])
|
||||
|
||||
-- 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)]])
|
||||
screen:expect([[
|
||||
{100:^word1. }|
|
||||
{100:word2verylongwordtha}|
|
||||
{100:tpushesdown} |
|
||||
{1:~ }|
|
||||
|
|
||||
]])
|
||||
end)
|
||||
end)
|
||||
|
||||
Reference in New Issue
Block a user