Commit: patch 9.2.0896: textprop: wrong Tab size in a line with virtual text above it

1 view
Skip to first unread message

Christian Brabandt

unread,
1:45 PM (7 hours ago) 1:45 PM
to vim...@googlegroups.com
patch 9.2.0896: textprop: wrong Tab size in a line with virtual text above it

Commit: https://github.com/vim/vim/commit/a556e21552e9978e670fd351c8fc263efffe47b1
Author: Hirohito Higashi <h.eas...@gmail.com>
Date: Sun Aug 2 17:20:29 2026 +0000

patch 9.2.0896: textprop: wrong Tab size in a line with virtual text above it

Problem: In a line with virtual text above it a Tab does not have the right
size, depending on the width of the window.
Solution: Do not count the columns of the virtual text for the size of the
Tab, neither when drawing nor when computing the column.

fixes: #12232
closes: #20901

Co-Authored-By: Claude Opus 5 (1M context) <nor...@anthropic.com>
Signed-off-by: Hirohito Higashi <h.eas...@gmail.com>
Signed-off-by: Christian Brabandt <c...@256bit.org>

diff --git a/src/charset.c b/src/charset.c
index bf7c59620..c47180e2c 100644
--- a/src/charset.c
+++ b/src/charset.c
@@ -1285,11 +1285,18 @@ win_lbr_chartabsize(

#if defined(FEAT_LINEBREAK) || defined(FEAT_PROP_POPUP)
int has_lcs_eol = wp->w_p_list && wp->w_lcs_chars.eol != NUL;
+ // Virtual text above the line is on its own screen line, it does not count
+ // for the size of a Tab.
+ colnr_T tab_vcol = vcol;
+
+# ifdef FEAT_PROP_POPUP
+ tab_vcol -= cts->cts_above_width;
+# endif

/*
* First get the normal size, without 'linebreak' or text properties
*/
- size = win_chartabsize(wp, s, vcol);
+ size = win_chartabsize(wp, s, tab_vcol);
# ifdef FEAT_LINEBREAK
if (*s == NUL)
{
@@ -1367,7 +1374,8 @@ win_lbr_chartabsize(
{
// tab size changes because of the inserted text
size -= tab_size;
- tab_size = win_chartabsize(wp, s, vcol + size);
+ tab_size = win_chartabsize(wp, s,
+ vcol + size - cts->cts_above_width);
size += tab_size;
}
# endif
@@ -1549,6 +1557,10 @@ win_lbr_chartabsize(
*tailp = size - size_before_lbr;

# ifdef FEAT_PROP_POPUP
+ if (cts->cts_first_char > 0)
+ // Remember the width for the size of a Tab later in the line. Use
+ // assignment, this may be called more than once for a character.
+ cts->cts_above_width = cts->cts_first_char;
size += cts->cts_first_char;
# endif
# endif
diff --git a/src/drawline.c b/src/drawline.c
index a2679e146..c799291ba 100644
--- a/src/drawline.c
+++ b/src/drawline.c
@@ -3334,7 +3334,9 @@ win_line(
if (c == TAB && (!wp->w_p_list || wp->w_lcs_chars.tab1))
{
int tab_len = 0;
- long vcol_adjusted = wlv.vcol; // removed showbreak len
+ // Virtual text and 'showbreak' do not count for the size
+ // of a Tab.
+ long vcol_adjusted = wlv.vcol - wlv.vcol_off_tp;
int lcs_tab1 = wp->w_lcs_chars.tab1;
int lcs_tab2 = wp->w_lcs_chars.tab2;
int lcs_tab3 = wp->w_lcs_chars.tab3;
@@ -3353,7 +3355,7 @@ win_line(
// only adjust the tab_len, when at the first column
// after the showbreak value was drawn
if (*sbr != NUL && wlv.vcol == wlv.vcol_sbr && wp->w_p_wrap)
- vcol_adjusted = wlv.vcol - MB_CHARLEN(sbr);
+ vcol_adjusted -= MB_CHARLEN(sbr);
#endif
// tab amount depends on current column
#ifdef FEAT_VARTABS
diff --git a/src/structs.h b/src/structs.h
index d9b7b6b0a..38037c9d5 100644
--- a/src/structs.h
+++ b/src/structs.h
@@ -5359,6 +5359,8 @@ typedef struct {
int cts_cur_text_width; // width of current inserted text
int cts_prop_lines; // nr of properties above or below
int cts_first_char; // width text props above the line
+ int cts_above_width; // width of text props above the line,
+ // kept for the whole line
int cts_with_trailing; // include size of trailing props with
// last character
int cts_start_incl; // prop has true "start_incl" arg
diff --git a/src/testdir/test_textprop.vim b/src/testdir/test_textprop.vim
index a566a47af..a89ed5515 100644
--- a/src/testdir/test_textprop.vim
+++ b/src/testdir/test_textprop.vim
@@ -3782,6 +3782,48 @@ func Test_prop_above_with_indent()
call prop_type_delete('indented')
endfunc

+" A Tab in the line is not affected by virtual text above it.
+func Test_prop_above_with_tab()
+ " Use a width that is not a multiple of 'tabstop', otherwise counting the
+ " virtual text for the size of a Tab happens to give the right result.
+ call NewWindow(10, 45)
+ setlocal tabstop=8
+ call setline(1, [" X"])
+ call prop_type_add('above', #{highlight: 'Search'})
+
+ " Get the column of the "X" without and with the virtual text.
+ redraw
+ let col_without = 0
+ for col in range(1, winwidth(0))
+ if screenstring(1, col) == 'X'
+ let col_without = col
+ break
+ endif
+ endfor
+ call assert_equal(9, col_without)
+
+ call prop_add(1, 0, #{type: 'above', text: 'text above', text_align: 'above'})
+ redraw
+ let col_with = 0
+ for col in range(1, winwidth(0))
+ if screenstring(2, col) == 'X'
+ let col_with = col
+ break
+ endif
+ endfor
+ call assert_equal(col_without, col_with)
+
+ " The cursor is placed on the character, also with a second Tab.
+ call setline(1, [" X"])
+ redraw
+ normal! 0fX
+ call assert_equal('X', screenstring(winline(), wincol()))
+
+ only!
+ bwipe!
+ call prop_type_delete('above')
+endfunc
+
func Test_prop_above_with_number()
CheckScreendump
CheckRunVimInTerminal
diff --git a/src/version.c b/src/version.c
index d1b8ad931..30b9a310b 100644
--- a/src/version.c
+++ b/src/version.c
@@ -758,6 +758,8 @@ static char *(features[]) =

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