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:
Freddie Haddad authored and GitHub committed 2026-08-07 05:09:48 -04:00
1 parent a4a544032a
commit a8da01f8e6
5 files changed
+46 -17

No files matched your search

+19 -5
View File
@@ -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:~ }|
|
+9 -9
View File
@@ -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:~ }|
|
]])