commit aeddd66a2a07dfa32016d7289b845a25e5e86e2b
parent a8361c3afc5b9281814e9f16a9d4291e095b38fa
Author: zeertzjq <zeertzjq@outlook.com>
Date: Tue, 3 Mar 2026 09:35:26 +0800
Merge pull request #38133 from zeertzjq/vim-9.2.0088
vim-patch:9.2.{0088,0090}: 'listchars' "leadtab"
Diffstat:
11 files changed, 230 insertions(+), 57 deletions(-)
diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt
@@ -354,6 +354,7 @@ OPTIONS
• 'diffopt' `inline:` configures diff highlighting for changes within a line.
• 'fillchars' has new flag "foldinner".
• 'fsync' and 'grepformat' are now |global-local| options.
+• 'listchars' has new flag "leadtab".
• 'jumpoptions' flag "view" now applies when popping the |tagstack|.
• 'maxsearchcount' sets maximum value for |searchcount()| and defaults to 999.
• 'pummaxwidth' sets maximum width for the completion popup menu.
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
@@ -4102,7 +4102,7 @@ A jump table for the options with a short description can be found at |Q_op|.
The cursor is displayed at the start of the space a Tab character
occupies, not at the end as usual in Normal mode. To get this cursor
position while displaying Tabs with spaces, use: >vim
- set list lcs=tab:\ \
+ let &list = v:true | let &lcs = 'tab: '
<
Note that list mode will also affect formatting (set with 'textwidth'
or 'wrapmargin') when 'cpoptions' includes 'L'. See 'listchars' for
@@ -4170,6 +4170,15 @@ A jump table for the options with a short description can be found at |Q_op|.
<
Where "XXX" denotes the first non-blank characters in
the line.
+ *lcs-leadtab*
+ leadtab:xy[z]
+ Like |lcs-tab|, but only for leading tabs. When
+ omitted, the "tab" setting is used for leading tabs.
+ |lcs-tab| must also be set for this to work. *E1572*
+ You can combine it with "tab:", for example: >vim
+ let &listchars = 'tab:>-,leadtab:. '
+< This shows leading tabs as periods(.) and other tabs
+ as ">--".
*lcs-trail*
trail:c Character to show for trailing spaces. When omitted,
trailing spaces are blank. Overrides the "space" and
diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua
@@ -4097,7 +4097,7 @@ vim.go.lw = vim.go.lispwords
--- position while displaying Tabs with spaces, use:
---
--- ```vim
---- set list lcs=tab:\ \
+--- let &list = v:true | let &lcs = 'tab: '
--- ```
---
--- Note that list mode will also affect formatting (set with 'textwidth'
@@ -4180,6 +4180,18 @@ vim.wo.list = vim.o.list
---
--- Where "XXX" denotes the first non-blank characters in
--- the line.
+--- *lcs-leadtab*
+--- leadtab:xy[z]
+--- Like `lcs-tab`, but only for leading tabs. When
+--- omitted, the "tab" setting is used for leading tabs.
+--- `lcs-tab` must also be set for this to work. *E1572*
+--- You can combine it with "tab:", for example:
+---
+--- ```vim
+--- let &listchars = 'tab:>-,leadtab:. '
+--- ```
+--- This shows leading tabs as periods(.) and other tabs
+--- as ">--".
--- *lcs-trail*
--- trail:c Character to show for trailing spaces. When omitted,
--- trailing spaces are blank. Overrides the "space" and
diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h
@@ -1056,6 +1056,9 @@ typedef struct {
schar_T tab1; ///< first tab character
schar_T tab2; ///< second tab character
schar_T tab3; ///< third tab character
+ schar_T leadtab1;
+ schar_T leadtab2;
+ schar_T leadtab3;
schar_T lead;
schar_T trail;
schar_T *multispace;
diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c
@@ -1462,7 +1462,8 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, b
trailcol += (colnr_T)(ptr - line);
}
// find end of leading whitespace
- if (wp->w_p_lcs_chars.lead || wp->w_p_lcs_chars.leadmultispace != NULL) {
+ if (wp->w_p_lcs_chars.lead || wp->w_p_lcs_chars.leadmultispace != NULL
+ || wp->w_p_lcs_chars.leadtab1 != NUL) {
leadcol = 0;
while (ascii_iswhite(ptr[leadcol])) {
leadcol++;
@@ -2420,6 +2421,16 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, b
if (mb_c == TAB && (!wp->w_p_list || wp->w_p_lcs_chars.tab1)) {
int tab_len = 0;
colnr_T vcol_adjusted = wlv.vcol; // removed showbreak length
+ schar_T lcs_tab1 = wp->w_p_lcs_chars.tab1;
+ schar_T lcs_tab2 = wp->w_p_lcs_chars.tab2;
+ schar_T lcs_tab3 = wp->w_p_lcs_chars.tab3;
+ // check if leadtab is set in 'listchars'
+ if (wp->w_p_list && wp->w_p_lcs_chars.leadtab1 != NUL
+ && ptr < line + leadcol) {
+ lcs_tab1 = wp->w_p_lcs_chars.leadtab1;
+ lcs_tab2 = wp->w_p_lcs_chars.leadtab2;
+ lcs_tab3 = wp->w_p_lcs_chars.leadtab3;
+ }
char *const sbr = get_showbreak_value(wp);
// Only adjust the tab_len, when at the first column after the
@@ -2442,8 +2453,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, b
tab_len += wlv.vcol_off_co;
}
// boguscols before fix_for_boguscols() from above.
- if (wp->w_p_lcs_chars.tab1 && wlv.old_boguscols > 0
- && wlv.n_extra > tab_len) {
+ if (lcs_tab1 && wlv.old_boguscols > 0 && wlv.n_extra > tab_len) {
tab_len += wlv.n_extra - tab_len;
}
@@ -2451,15 +2461,15 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, b
// If wlv.n_extra > 0, it gives the number of chars
// to use for a tab, else we need to calculate the
// width for a tab.
- size_t tab2_len = schar_len(wp->w_p_lcs_chars.tab2);
+ size_t tab2_len = schar_len(lcs_tab2);
size_t len = (size_t)tab_len * tab2_len;
- if (wp->w_p_lcs_chars.tab3) {
- len += schar_len(wp->w_p_lcs_chars.tab3) - tab2_len;
+ if (lcs_tab3) {
+ len += schar_len(lcs_tab3) - tab2_len;
}
if (wlv.n_extra > 0) {
len += (size_t)(wlv.n_extra - tab_len);
}
- mb_schar = wp->w_p_lcs_chars.tab1;
+ mb_schar = lcs_tab1;
mb_c = schar_get_first_codepoint(mb_schar);
char *p = get_extra_buf(len + 1);
memset(p, ' ', len);
@@ -2470,11 +2480,11 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, b
tab_len = i;
break;
}
- schar_T lcs = wp->w_p_lcs_chars.tab2;
+ schar_T lcs = lcs_tab2;
// if tab3 is given, use it for the last char
- if (wp->w_p_lcs_chars.tab3 && i == tab_len - 1) {
- lcs = wp->w_p_lcs_chars.tab3;
+ if (lcs_tab3 && i == tab_len - 1) {
+ lcs = lcs_tab3;
}
size_t slen = schar_get_adv(&p, lcs);
wlv.n_extra += (int)slen - (saved_nextra > 0 ? 1 : 0);
@@ -2509,14 +2519,13 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, b
}
if (wp->w_p_list) {
- mb_schar = (wlv.n_extra == 0 && wp->w_p_lcs_chars.tab3)
- ? wp->w_p_lcs_chars.tab3 : wp->w_p_lcs_chars.tab1;
+ mb_schar = (wlv.n_extra == 0 && lcs_tab3) ? lcs_tab3 : lcs_tab1;
if (wp->w_p_lbr && wlv.p_extra != NULL && *wlv.p_extra != NUL) {
wlv.sc_extra = NUL; // using p_extra from above
} else {
- wlv.sc_extra = wp->w_p_lcs_chars.tab2;
+ wlv.sc_extra = lcs_tab2;
}
- wlv.sc_final = wp->w_p_lcs_chars.tab3;
+ wlv.sc_final = lcs_tab3;
wlv.n_attr = tab_len + 1;
wlv.extra_attr = win_hl_attr(wp, HLF_0);
saved_attr2 = wlv.char_attr; // save current attr
diff --git a/src/nvim/errors.h b/src/nvim/errors.h
@@ -219,6 +219,7 @@ EXTERN const char e_cannot_switch_to_a_closing_buffer[] INIT( = N_("E1546: Canno
EXTERN const char e_cannot_have_more_than_nr_diff_anchors[] INIT( = N_("E1549: Cannot have more than %d diff anchors"));
EXTERN const char e_failed_to_find_all_diff_anchors[] INIT( = N_("E1550: Failed to find all diff anchors"));
EXTERN const char e_diff_anchors_with_hidden_windows[] INIT( = N_("E1562: Diff anchors cannot be used with hidden diff windows"));
+EXTERN const char e_leadtab_requires_tab[] INIT( = N_("E1572: 'listchars' field \"leadtab\" requires \"tab\" to be specified"));
EXTERN const char e_trustfile[] INIT(= N_("E5570: Cannot update trust file: %s"));
EXTERN const char e_cannot_read_from_str_2[] INIT(= N_("E282: Cannot read from \"%s\""));
diff --git a/src/nvim/options.lua b/src/nvim/options.lua
@@ -5387,7 +5387,7 @@ local options = {
The cursor is displayed at the start of the space a Tab character
occupies, not at the end as usual in Normal mode. To get this cursor
position while displaying Tabs with spaces, use: >vim
- set list lcs=tab:\ \
+ let &list = v:true | let &lcs = 'tab: '
<
Note that list mode will also affect formatting (set with 'textwidth'
or 'wrapmargin') when 'cpoptions' includes 'L'. See 'listchars' for
@@ -5464,6 +5464,15 @@ local options = {
<
Where "XXX" denotes the first non-blank characters in
the line.
+ *lcs-leadtab*
+ leadtab:xy[z]
+ Like |lcs-tab|, but only for leading tabs. When
+ omitted, the "tab" setting is used for leading tabs.
+ |lcs-tab| must also be set for this to work. *E1572*
+ You can combine it with "tab:", for example: >vim
+ let &listchars = 'tab:>-,leadtab:. '
+ < This shows leading tabs as periods(.) and other tabs
+ as ">--".
*lcs-trail*
trail:c Character to show for trailing spaces. When omitted,
trailing spaces are blank. Overrides the "space" and
diff --git a/src/nvim/optionstr.c b/src/nvim/optionstr.c
@@ -2252,17 +2252,18 @@ static const struct chars_tab fcs_tab[] = {
static lcs_chars_T lcs_chars;
static const struct chars_tab lcs_tab[] = {
- CHARSTAB_ENTRY(&lcs_chars.eol, "eol", NULL, NULL),
- CHARSTAB_ENTRY(&lcs_chars.ext, "extends", NULL, NULL),
- CHARSTAB_ENTRY(&lcs_chars.nbsp, "nbsp", NULL, NULL),
- CHARSTAB_ENTRY(&lcs_chars.prec, "precedes", NULL, NULL),
- CHARSTAB_ENTRY(&lcs_chars.space, "space", NULL, NULL),
- CHARSTAB_ENTRY(&lcs_chars.tab2, "tab", NULL, NULL),
- CHARSTAB_ENTRY(&lcs_chars.lead, "lead", NULL, NULL),
- CHARSTAB_ENTRY(&lcs_chars.trail, "trail", NULL, NULL),
- CHARSTAB_ENTRY(&lcs_chars.conceal, "conceal", NULL, NULL),
- CHARSTAB_ENTRY(NULL, "multispace", NULL, NULL),
- CHARSTAB_ENTRY(NULL, "leadmultispace", NULL, NULL),
+ CHARSTAB_ENTRY(&lcs_chars.eol, "eol", NULL, NULL),
+ CHARSTAB_ENTRY(&lcs_chars.ext, "extends", NULL, NULL),
+ CHARSTAB_ENTRY(&lcs_chars.nbsp, "nbsp", NULL, NULL),
+ CHARSTAB_ENTRY(&lcs_chars.prec, "precedes", NULL, NULL),
+ CHARSTAB_ENTRY(&lcs_chars.space, "space", NULL, NULL),
+ CHARSTAB_ENTRY(&lcs_chars.tab2, "tab", NULL, NULL),
+ CHARSTAB_ENTRY(&lcs_chars.leadtab2, "leadtab", NULL, NULL),
+ CHARSTAB_ENTRY(&lcs_chars.lead, "lead", NULL, NULL),
+ CHARSTAB_ENTRY(&lcs_chars.trail, "trail", NULL, NULL),
+ CHARSTAB_ENTRY(&lcs_chars.conceal, "conceal", NULL, NULL),
+ CHARSTAB_ENTRY(NULL, "multispace", NULL, NULL),
+ CHARSTAB_ENTRY(NULL, "leadmultispace", NULL, NULL),
};
#undef CHARSTAB_ENTRY
@@ -2326,6 +2327,8 @@ const char *set_chars_option(win_T *wp, const char *value, CharsOption what, boo
if (what == kListchars) {
lcs_chars.tab1 = NUL;
lcs_chars.tab3 = NUL;
+ lcs_chars.leadtab1 = NUL;
+ lcs_chars.leadtab3 = NUL;
if (multispace_len > 0) {
lcs_chars.multispace = xmalloc(((size_t)multispace_len + 1) * sizeof(schar_T));
@@ -2433,7 +2436,7 @@ const char *set_chars_option(win_T *wp, const char *value, CharsOption what, boo
}
schar_T c2 = 0;
schar_T c3 = 0;
- if (tab[i].cp == &lcs_chars.tab2) {
+ if (tab[i].cp == &lcs_chars.tab2 || tab[i].cp == &lcs_chars.leadtab2) {
if (*s == NUL) {
return field_value_err(errbuf, errbuflen,
e_wrong_number_of_characters_for_field_str,
@@ -2461,6 +2464,10 @@ const char *set_chars_option(win_T *wp, const char *value, CharsOption what, boo
lcs_chars.tab1 = c1;
lcs_chars.tab2 = c2;
lcs_chars.tab3 = c3;
+ } else if (tab[i].cp == &lcs_chars.leadtab2) {
+ lcs_chars.leadtab1 = c1;
+ lcs_chars.leadtab2 = c2;
+ lcs_chars.leadtab3 = c3;
} else if (tab[i].cp != NULL) {
*(tab[i].cp) = c1;
}
@@ -2484,6 +2491,10 @@ const char *set_chars_option(win_T *wp, const char *value, CharsOption what, boo
}
}
+ if (what == kListchars && lcs_chars.leadtab2 != NUL && lcs_chars.tab2 == NUL) {
+ return e_leadtab_requires_tab;
+ }
+
if (apply) {
if (what == kListchars) {
xfree(wp->w_p_lcs_chars.multispace);
diff --git a/test/old/testdir/gen_opt_test.vim b/test/old/testdir/gen_opt_test.vim
@@ -277,10 +277,10 @@ let test_values = {
\ 'langmap': [['', 'xX', 'aA,bB'], ['xxx']],
\ 'lispoptions': [['', 'expr:0', 'expr:1'], ['xxx', 'expr:x', 'expr:']],
\ 'listchars': [['', 'eol:x', 'tab:xy', 'tab:xyz', 'space:x',
- \ 'multispace:xxxy', 'lead:x', 'leadmultispace:xxxy', 'trail:x',
- \ 'extends:x', 'precedes:x', 'conceal:x', 'nbsp:x', 'eol:\\x24',
- \ 'eol:\\u21b5', 'eol:\\U000021b5', 'eol:x,space:y'],
- \ ['xxx', 'eol:']],
+ \ 'multispace:xxxy', 'lead:x', 'tab:xy,leadtab:xyz', 'leadmultispace:xxxy',
+ \ 'trail:x', 'extends:x', 'precedes:x', 'conceal:x', 'nbsp:x',
+ \ 'eol:\\x24', 'eol:\\u21b5', 'eol:\\U000021b5', 'eol:x,space:y'],
+ \ ['xxx', 'eol:', 'leadtab:xyz']],
\ 'matchpairs': [['', '(:)', '(:),<:>'], ['xxx']],
\ 'maxsearchcount': [[1, 10, 100, 1000], [0, -1, 10000]],
\ 'messagesopt': [['hit-enter,history:1', 'hit-enter,history:10000',
diff --git a/test/old/testdir/test_listchars.vim b/test/old/testdir/test_listchars.vim
@@ -153,6 +153,21 @@ func Test_listchars()
call Check_listchars(expected, 5, -1, 6)
call assert_equal(expected, split(execute("%list"), "\n"))
+ " In a line with only spaces, they aren't considered leading even if "trail"
+ " isn't set.
+ set listchars-=trail:<
+ let expected = [
+ \ '>>>>ffffxxxx$',
+ \ '>>>>>>>>>>gg$',
+ \ 'hxxxxxxxxxxx$',
+ \ 'xxxxxxxxxxxx$',
+ \ '>>>>0xx0xxxx$',
+ \ '$'
+ \ ]
+ call Check_listchars(expected, 6)
+ call Check_listchars(expected, 5, -1, 6)
+ call assert_equal(expected, split(execute("%list"), "\n"))
+
" Test multispace
normal ggdG
set listchars=eol:$ " Accommodate Nvim default
@@ -163,6 +178,7 @@ func Test_listchars()
\ ' ffff ',
\ ' i i gg',
\ ' h ',
+ \ ' ',
\ ' j ',
\ ' 0 0 ',
\ ])
@@ -171,12 +187,13 @@ func Test_listchars()
\ 'yYzZffffyYzZ$',
\ 'yYi iyYzZygg$',
\ ' hyYzZyYzZyY$',
+ \ 'yYzZyYzZyYzZ$',
\ 'yYzZyYzZyYj $',
\ 'yYzZ0yY0yYzZ$',
\ '$'
\ ]
- call Check_listchars(expected, 6)
- call Check_listchars(expected, 5, -1, 6)
+ call Check_listchars(expected, 7)
+ call Check_listchars(expected, 6, -1, 6)
call assert_equal(expected, split(execute("%list"), "\n"))
" Test leadmultispace + multispace
@@ -189,6 +206,7 @@ func Test_listchars()
\ ' ffff ',
\ ' i i gg',
\ ' h ',
+ \ ' ',
\ ' j ',
\ ' 0 0 ',
\ ])
@@ -197,16 +215,17 @@ func Test_listchars()
\ '.-+*ffffyYzZ$',
\ '.-i iSyYzZgg$',
\ ' hyYzZyYzZyY$',
+ \ 'yYzZyYzZyYzZ$',
\ '.-+*.-+*.-j $',
\ '.-+*0yY0yYzZ$',
\ '$'
\ ]
call assert_equal('eol:$,multispace:yYzZ,nbsp:S,leadmultispace:.-+*', &listchars)
- call Check_listchars(expected, 6)
- call Check_listchars(expected, 5, -1, 1)
- call Check_listchars(expected, 5, -1, 2)
- call Check_listchars(expected, 5, -1, 3)
- call Check_listchars(expected, 5, -1, 6)
+ call Check_listchars(expected, 7)
+ call Check_listchars(expected, 6, -1, 1)
+ call Check_listchars(expected, 6, -1, 2)
+ call Check_listchars(expected, 6, -1, 3)
+ call Check_listchars(expected, 6, -1, 6)
call assert_equal(expected, split(execute("%list"), "\n"))
" Test leadmultispace without multispace
@@ -219,6 +238,7 @@ func Test_listchars()
\ ' ffff ',
\ ' i i gg',
\ ' h ',
+ \ ' ',
\ ' j ',
\ ' 0 0 ',
\ ])
@@ -227,16 +247,17 @@ func Test_listchars()
\ '.-+*ffff>>>>$',
\ '.-i+i+++++gg$',
\ '+h>>>>>>>>>>$',
+ \ '>>>>>>>>>>>>$',
\ '.-+*.-+*.-j>$',
\ '.-+*0++0>>>>$',
\ '$'
\ ]
call assert_equal('eol:$,nbsp:S,leadmultispace:.-+*,space:+,trail:>,eol:$', &listchars)
- call Check_listchars(expected, 6)
- call Check_listchars(expected, 5, -1, 1)
- call Check_listchars(expected, 5, -1, 2)
- call Check_listchars(expected, 5, -1, 3)
- call Check_listchars(expected, 5, -1, 6)
+ call Check_listchars(expected, 7)
+ call Check_listchars(expected, 6, -1, 1)
+ call Check_listchars(expected, 6, -1, 2)
+ call Check_listchars(expected, 6, -1, 3)
+ call Check_listchars(expected, 6, -1, 6)
call assert_equal(expected, split(execute("%list"), "\n"))
" Test leadmultispace only
@@ -249,6 +270,7 @@ func Test_listchars()
\ ' ffff ',
\ ' i i gg',
\ ' h ',
+ \ ' ',
\ ' j ',
\ ' 0 0 ',
\ ])
@@ -257,12 +279,13 @@ func Test_listchars()
\ '.-+*ffff ',
\ '.-i i gg',
\ ' h ',
+ \ ' ',
\ '.-+*.-+*.-j ',
\ '.-+*0 0 ',
\ ' '
\ ]
call assert_equal('leadmultispace:.-+*', &listchars)
- call Check_listchars(expected, 5, 12)
+ call Check_listchars(expected, 6, 12)
call assert_equal(expected, split(execute("%list"), "\n"))
" Changing the value of 'ambiwidth' twice shouldn't cause double-free when
@@ -281,6 +304,7 @@ func Test_listchars()
\ ' ffff ',
\ ' i i gg',
\ ' h ',
+ \ ' ',
\ ' j ',
\ ' 0 0 ',
\ ])
@@ -289,16 +313,17 @@ func Test_listchars()
\ '.-+*ffff----$',
\ '.-i-i-----gg$',
\ '<h----------$',
+ \ '------------$',
\ '.-+*.-+*.-j-$',
\ '.-+*0--0----$',
\ '$'
\ ]
call assert_equal('eol:$,lead:<,space:-,leadmultispace:.-+*', &listchars)
- call Check_listchars(expected, 6)
- call Check_listchars(expected, 5, -1, 1)
- call Check_listchars(expected, 5, -1, 2)
- call Check_listchars(expected, 5, -1, 3)
- call Check_listchars(expected, 5, -1, 6)
+ call Check_listchars(expected, 7)
+ call Check_listchars(expected, 6, -1, 1)
+ call Check_listchars(expected, 6, -1, 2)
+ call Check_listchars(expected, 6, -1, 3)
+ call Check_listchars(expected, 6, -1, 6)
call assert_equal(expected, split(execute("%list"), "\n"))
" the last occurrence of 'multispace:' is used
@@ -310,13 +335,14 @@ func Test_listchars()
\ 'XyYXffffXyYX$',
\ 'XyixiXyYXygg$',
\ 'xhXyYXyYXyYX$',
+ \ 'XyYXyYXyYXyY$',
\ 'XyYXyYXyYXjx$',
\ 'XyYX0Xy0XyYX$',
\ '$'
\ ]
call assert_equal('eol:$,multispace:yYzZ,space:x,multispace:XyY', &listchars)
- call Check_listchars(expected, 6)
- call Check_listchars(expected, 5, -1, 6)
+ call Check_listchars(expected, 7)
+ call Check_listchars(expected, 6, -1, 6)
call assert_equal(expected, split(execute("%list"), "\n"))
set listchars+=lead:>,trail:<
@@ -325,12 +351,13 @@ func Test_listchars()
\ '>>>>ffff<<<<$',
\ '>>ixiXyYXygg$',
\ '>h<<<<<<<<<<$',
+ \ '<<<<<<<<<<<<$',
\ '>>>>>>>>>>j<$',
\ '>>>>0Xy0<<<<$',
\ '$'
\ ]
- call Check_listchars(expected, 6)
- call Check_listchars(expected, 5, -1, 6)
+ call Check_listchars(expected, 7)
+ call Check_listchars(expected, 6, -1, 6)
call assert_equal(expected, split(execute("%list"), "\n"))
" removing 'multispace:'
@@ -341,14 +368,103 @@ func Test_listchars()
\ '>>>>ffff<<<<$',
\ '>>ixixxxxxgg$',
\ '>h<<<<<<<<<<$',
+ \ '<<<<<<<<<<<<$',
\ '>>>>>>>>>>j<$',
\ '>>>>0xx0<<<<$',
\ '$'
\ ]
- call Check_listchars(expected, 6)
- call Check_listchars(expected, 5, -1, 6)
+ call Check_listchars(expected, 7)
+ call Check_listchars(expected, 6, -1, 6)
call assert_equal(expected, split(execute("%list"), "\n"))
+ " Test leadtab basic functionality
+ normal ggdG
+ set listchars=tab:>-,leadtab:+*
+ set list
+ call append(0, [
+ \ "\ttext",
+ \ "\t\ttext",
+ \ "text\ttab"
+ \ ])
+ let expected = [
+ \ '+*******text ',
+ \ '+*******+*******text',
+ \ 'text>---tab '
+ \ ]
+ call Check_listchars(expected, 3, 20)
+
+ " Test leadtab with unicode characters
+ normal ggdG
+ set listchars=tab:>-,leadtab:├─┤
+ call append(0, ["\ttext"])
+ let expected = ['├──────┤text']
+ call Check_listchars(expected, 1, 12)
+
+ " Test leadtab with mixed indentation (spaces + tabs)
+ normal ggdG
+ set listchars=tab:>-,leadtab:+*,space:.
+ call append(0, [" \t text"])
+ let expected = ['.+******.text']
+ call Check_listchars(expected, 1, 13)
+
+ " Test leadtab with pipe character
+ normal ggdG
+ set listchars=tab:>-,leadtab:\|\
+ call append(0, ["\ttext"])
+ let expected = ['| text']
+ call Check_listchars(expected, 1, 12)
+
+ " Test leadtab with unicode bar
+ normal ggdG
+ set listchars=tab:>-,leadtab:│\
+ call append(0, ["\ttext"])
+ let expected = ['│ text']
+ call Check_listchars(expected, 1, 12)
+
+ " Test leadtab vs tab distinction (leading vs non-leading)
+ " In a line with only tabs, they aren't considered leading.
+ normal ggdG
+ set listchars=tab:>-,leadtab:+*
+ call append(0, [
+ \ "\tleading",
+ \ "text\tnot leading",
+ \ "\t\tmultiple leading",
+ \ "\t\t"
+ \ ])
+ let expected = [
+ \ '+*******leading ',
+ \ 'text>---not leading ',
+ \ '+*******+*******multiple leading',
+ \ '>------->------- '
+ \ ]
+ call Check_listchars(expected, 4, 32)
+
+ " Test leadtab with trail and space
+ normal ggdG
+ set listchars=tab:>-,leadtab:+*,trail:<,space:.
+ call append(0, [
+ \ "\ttext ",
+ \ " \ttext",
+ \ "\t text "
+ \ ])
+ let expected = [
+ \ '+*******text<< ',
+ \ '..+*****text ',
+ \ '+*******..text<<'
+ \ ]
+ call Check_listchars(expected, 3, 16)
+
+ " Test leadtab with eol
+ normal ggdG
+ set listchars=tab:>-,leadtab:+*,eol:$
+ call append(0, ["\ttext", "text\ttab"])
+ let expected = [
+ \ '+*******text$',
+ \ 'text>---tab$ '
+ \ ]
+ call Check_listchars(expected, 2, 13)
+
+
" test nbsp
normal ggdG
set listchars=nbsp:X,trail:Y
diff --git a/test/old/testdir/test_options.vim b/test/old/testdir/test_options.vim
@@ -628,7 +628,9 @@ func Test_set_completion_string_values()
call assert_equal('eol', getcompletion('set listchars+=', 'cmdline')[0])
call assert_equal(['multispace', 'leadmultispace'], getcompletion('set listchars+=', 'cmdline')[-2:])
+ call assert_equal(['tab', 'leadtab'], getcompletion('set listchars+=', 'cmdline')[5:6])
call assert_equal('eol', getcompletion('setl listchars+=', 'cmdline')[0])
+ call assert_equal(['tab', 'leadtab'], getcompletion('setl listchars+=', 'cmdline')[5:6])
call assert_equal(['multispace', 'leadmultispace'], getcompletion('setl listchars+=', 'cmdline')[-2:])
call assert_equal('stl', getcompletion('set fillchars+=', 'cmdline')[0])
call assert_equal('stl', getcompletion('setl fillchars+=', 'cmdline')[0])