Commit: patch 9.2.0572: lines disappear with wrapping virtual text after a double-width char

0 views
Skip to first unread message

Christian Brabandt

unread,
May 31, 2026, 3:00:14 PM (13 hours ago) May 31
to vim...@googlegroups.com
patch 9.2.0572: lines disappear with wrapping virtual text after a double-width char

Commit: https://github.com/vim/vim/commit/09f7fc60d3d76504cf46871277e471d034776583
Author: Hirohito Higashi <h.eas...@gmail.com>
Date: Sun May 31 18:43:42 2026 +0000

patch 9.2.0572: lines disappear with wrapping virtual text after a double-width char

Problem: With 'nowrap', when a line ends with a double-width character
exactly at the window width and has wrapping "after" virtual
text, the lines below disappear and "@@@" is shown.
Solution: Detect that the last character fills the rightmost column using
its displayed width (win_chartabsize(), so a <Tab> or double-width
character is handled like a single-width one), and also when it
overflows the last column. Also clarify in the help that "wrap"
only takes effect with the 'wrap' option set.

fixes: #20384
related: #12213
closes: #20395

Co-Authored-By: Claude Opus 4.8 (1M context) <nor...@anthropic.com>
Signed-off-by: zeertzjq <zeer...@outlook.com>
Signed-off-by: Christian Brabandt <c...@256bit.org>

diff --git a/runtime/doc/textprop.txt b/runtime/doc/textprop.txt
index 9fd5ff668..ce2867791 100644
--- a/runtime/doc/textprop.txt
+++ b/runtime/doc/textprop.txt
@@ -1,4 +1,4 @@
-*textprop.txt* For Vim version 9.2. Last change: 2026 Apr 07
+*textprop.txt* For Vim version 9.2. Last change: 2026 May 31


VIM REFERENCE MANUAL by Bram Moolenaar
@@ -177,7 +177,9 @@ prop_add({lnum}, {col}, {props})
When omitted "truncate" is used.
Note that this applies to the individual text
property, the 'wrap' option sets the overall
- behavior
+ behavior. "wrap" only takes effect when the
+ 'wrap' option is set; with 'nowrap' the text
+ is truncated at the right edge of the window.
All fields except "type" are optional.

It is an error when both "length" and "end_lnum" or "end_col"
diff --git a/src/drawline.c b/src/drawline.c
index 3ebb56a17..0b912e1ea 100644
--- a/src/drawline.c
+++ b/src/drawline.c
@@ -2479,7 +2479,11 @@ win_line(
// displaying that character.
// Or when not wrapping and at the rightmost column.

- int only_below_follows = !wp->w_p_wrap && wlv.col == wp->w_width - 1;
+ // Use the displayed width so a double-width or <Tab> last
+ // character filling the rightmost column is detected too.
+ int only_below_follows = !wp->w_p_wrap
+ && wlv.col + win_chartabsize(wp, ptr, wlv.vcol)
+ >= wp->w_width;
int suffix_flags = text_prop_suffix_flags[text_prop_next];

text_prop_follows = (suffix_flags
diff --git a/src/testdir/dumps/Test_prop_with_text_after_wide_char_1.dump b/src/testdir/dumps/Test_prop_with_text_after_wide_char_1.dump
new file mode 100644
index 000000000..36dfed3ff
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_with_text_after_wide_char_1.dump
@@ -0,0 +1,8 @@
+>x+0&#ffffff0@42|口*&
+|s+&|e|c|o|n|d| |l|i|n|e| @33
+|t|h|i|r|d| |l|i|n|e| @34
+|~+0#4040ff13&| @43
+|~| @43
+|~| @43
+|~| @43
+| +0#0000000&@26|1|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_with_text_after_wide_char_2.dump b/src/testdir/dumps/Test_prop_with_text_after_wide_char_2.dump
new file mode 100644
index 000000000..50136517f
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_with_text_after_wide_char_2.dump
@@ -0,0 +1,8 @@
+>x+0&#ffffff0@38|>+0#4040ff13&
+|b+0#0000000&|e|t|w|e@1|n| |l|i|n|e| @27
+|x@31| @7
+|l|a|s|t| |l|i|n|e| @30
+|~+0#4040ff13&| @38
+|~| @38
+|~| @38
+| +0#0000000&@21|1|,|1| @10|A|l@1|
diff --git a/src/testdir/test_textprop.vim b/src/testdir/test_textprop.vim
index a57493bc8..7d868ec06 100644
--- a/src/testdir/test_textprop.vim
+++ b/src/testdir/test_textprop.vim
@@ -3574,6 +3574,56 @@ func Test_props_with_text_after_nowrap()
call StopVimInTerminal(buf)
endfunc

+func Test_props_with_text_after_wide_char_at_end()
+ CheckScreendump
+ CheckRunVimInTerminal
+
+ " The buffer line ends with a double-width character exactly at the window
+ " width and has wrapping "after" virtual text. This must not leave blank
+ " lines or "@@@", see issue #20384.
+ let lines =<< trim END
+ vim9script
+ set nowrap
+ setline(1, [repeat('x', 43) .. '口', 'second line', 'third line'])
+ prop_type_add('errtype', {highlight: 'WarningMsg', text_wrap: 'wrap'})
+ prop_add(1, 0, {type: 'errtype', text_padding_left: 3, text: 'E>'})
+ END
+ call writefile(lines, 'XscriptPropsAfterWideChar', 'D')
+ let buf = RunVimInTerminal('-S XscriptPropsAfterWideChar', #{rows: 8, cols: 45})
+ call VerifyScreenDump(buf, 'Test_prop_with_text_after_wide_char_1', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_props_with_text_after_wide_char_overflow()
+ CheckScreendump
+ CheckRunVimInTerminal
+
+ " Like above, but the last character reaches the rightmost column without
+ " starting on it: a double-width character that does not fit in the last
+ " column, and a <Tab> that expands up to the window width. Both must be
+ " detected as filling the line so the wrapping "after" text does not cause
+ " blank lines, "@@@" or a spurious wrap with 'nowrap'.
+ let lines =<< trim END
+ vim9script
+ set nowrap tabstop=8 noexpandtab
+ setline(1, [
+ repeat('x', 39) .. '口',
+ 'between line',
+ repeat('x', 32) .. " ",
+ 'last line',
+ ])
+ prop_type_add('errtype', {highlight: 'WarningMsg', text_wrap: 'wrap'})
+ prop_add(1, 0, {type: 'errtype', text_padding_left: 3, text: 'E>'})
+ prop_add(3, 0, {type: 'errtype', text_padding_left: 3, text: 'E>'})
+ END
+ call writefile(lines, 'XscriptPropsAfterWideOverflow', 'D')
+ let buf = RunVimInTerminal('-S XscriptPropsAfterWideOverflow', #{rows: 8, cols: 40})
+ call VerifyScreenDump(buf, 'Test_prop_with_text_after_wide_char_2', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
func Test_prop_with_text_below_cul()
CheckScreendump
CheckRunVimInTerminal
diff --git a/src/testdir/test_vim9_cmd.vim b/src/testdir/test_vim9_cmd.vim
index f90d71971..a4c2d05d4 100644
--- a/src/testdir/test_vim9_cmd.vim
+++ b/src/testdir/test_vim9_cmd.vim
@@ -2157,4 +2157,25 @@ def Test_map_legacy_expr()
v9.CheckDefAndScriptSuccess(lines)
enddef

+" :call on a funcref stored in a dict member used to fail with E1017 in Vim9
+" script because get_lval() treated the subscript as a re-declaration.
+def Test_call_dict_funcref()
+ var lines =<< trim END
+ vim9script
+ var d: dict<any> = {}
+ var marker = ''
+ def F()
+ marker = 'called'
+ enddef
+ d.key = F
+ d['k2'] = F
+ call d.key()
+ assert_equal('called', marker)
+ marker = ''
+ call d['k2']()
+ assert_equal('called', marker)
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
" vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker
diff --git a/src/userfunc.c b/src/userfunc.c
index bd4c0bbc3..ff5cf76a0 100644
--- a/src/userfunc.c
+++ b/src/userfunc.c
@@ -6273,7 +6273,7 @@ ex_delfunction(exarg_T *eap)
int is_global = FALSE;

p = eap->arg;
- name = trans_function_name_ext(&p, &is_global, eap->skip, 0, &fudi,
+ name = trans_function_name_ext(&p, &is_global, eap->skip, TFN_NO_DECL, &fudi,
NULL, NULL, NULL);
vim_free(fudi.fd_newkey);
if (name == NULL)
@@ -6823,7 +6823,7 @@ ex_call(exarg_T *eap)
return;
}

- tofree = trans_function_name_ext(&arg, NULL, FALSE, TFN_INT,
+ tofree = trans_function_name_ext(&arg, NULL, FALSE, TFN_INT | TFN_NO_DECL,
&fudi, &partial, vim9script ? &type : NULL, &ufunc);
if (fudi.fd_newkey != NULL)
{
diff --git a/src/version.c b/src/version.c
index a90dc1cb9..ac58fbdf8 100644
--- a/src/version.c
+++ b/src/version.c
@@ -729,6 +729,8 @@ static char *(features[]) =

static int included_patches[] =
{ /* Add new patch number below this line */
+/**/
+ 572,
/**/
571,
/**/
Reply all
Reply to author
Forward
0 new messages