From: Hirohito Higashi Date: Sun, 2 Aug 2026 17:20:29 +0000 (+0000) Subject: patch 9.2.0896: textprop: wrong Tab size in a line with virtual text above it X-Git-Tag: v9.2.0896^0 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=a556e21552e9978e670fd351c8fc263efffe47b1;p=thirdparty%2Fvim.git 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) Signed-off-by: Hirohito Higashi Signed-off-by: Christian Brabandt --- diff --git a/src/charset.c b/src/charset.c index bf7c59620f..c47180e2ce 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 a2679e1467..c799291ba6 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 d9b7b6b0a5..38037c9d5e 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 a566a47af8..a89ed55154 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, ["\tX"]) + 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, ["\t\tX"]) + 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 d1b8ad9311..30b9a310be 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, /**/