From 6ddcc84a6cd725b386da5f6dea7f6512c1f501d3 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Wed, 4 Mar 2026 07:42:05 +0800 Subject: [PATCH] vim-patch:9.2.0102: 'listchars' "leadtab" not used in :list (#38142) Problem: 'listchars' "leadtab" not used in :list (after 9.2.0088). Solution: Also check for "leadtab" when using :list. Fix memory leak on E1572 if "multispace" or "leadmultispace" is set (zeertzjq). closes: vim/vim#19557 https://github.com/vim/vim/commit/5845741d69655a731288ab105c8fd9fe46e12df9 --- src/nvim/message.c | 21 +++++++++++++++------ src/nvim/optionstr.c | 13 ++++++++++--- test/old/testdir/gen_opt_test.vim | 12 +++++++----- test/old/testdir/test_listchars.vim | 13 ++++++++++++- 4 files changed, 44 insertions(+), 15 deletions(-) diff --git a/src/nvim/message.c b/src/nvim/message.c index 723362f681..f5ede4a8a1 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -2101,7 +2101,9 @@ void msg_prt_line(const char *s, bool list) } } // find end of leading whitespace - if (curwin->w_p_lcs_chars.lead || curwin->w_p_lcs_chars.leadmultispace != NULL) { + if (curwin->w_p_lcs_chars.lead + || curwin->w_p_lcs_chars.leadmultispace != NULL + || curwin->w_p_lcs_chars.leadtab1 != NUL) { lead = s; while (ascii_iswhite(lead[0])) { lead++; @@ -2169,11 +2171,18 @@ void msg_prt_line(const char *s, bool list) sc = schar_from_ascii(' '); sc_extra = schar_from_ascii(' '); } else { - sc = (n_extra == 0 && curwin->w_p_lcs_chars.tab3) - ? curwin->w_p_lcs_chars.tab3 - : curwin->w_p_lcs_chars.tab1; - sc_extra = curwin->w_p_lcs_chars.tab2; - sc_final = curwin->w_p_lcs_chars.tab3; + schar_T lcs_tab1 = curwin->w_p_lcs_chars.tab1; + schar_T lcs_tab2 = curwin->w_p_lcs_chars.tab2; + schar_T lcs_tab3 = curwin->w_p_lcs_chars.tab3; + // check if leadtab is set in 'listchars' + if (lead != NULL && s <= lead && curwin->w_p_lcs_chars.leadtab1 != NUL) { + lcs_tab1 = curwin->w_p_lcs_chars.leadtab1; + lcs_tab2 = curwin->w_p_lcs_chars.leadtab2; + lcs_tab3 = curwin->w_p_lcs_chars.leadtab3; + } + sc = (n_extra == 0 && lcs_tab3) ? lcs_tab3 : lcs_tab1; + sc_extra = lcs_tab2; + sc_final = lcs_tab3; hl_id = HLF_0; } } else if (c == NUL && list && curwin->w_p_lcs_chars.eol != NUL) { diff --git a/src/nvim/optionstr.c b/src/nvim/optionstr.c index 507294c6c6..91f52bbcd2 100644 --- a/src/nvim/optionstr.c +++ b/src/nvim/optionstr.c @@ -2313,6 +2313,8 @@ const char *set_chars_option(win_T *wp, const char *value, CharsOption what, boo // first round: check for valid value, second round: assign values for (int round = 0; round <= (apply ? 1 : 0); round++) { + bool has_tab = false, has_leadtab = false; + if (round > 0) { // After checking that the value is valid: set defaults for (int i = 0; i < entries; i++) { @@ -2456,6 +2458,11 @@ const char *set_chars_option(win_T *wp, const char *value, CharsOption what, boo tab[i].name.data); } } + if (tab[i].cp == &lcs_chars.tab2) { + has_tab = true; + } else { // tab[i].cp == &lcs_chars.leadtab2 + has_leadtab = true; + } } if (*s == ',' || *s == NUL) { @@ -2489,10 +2496,10 @@ const char *set_chars_option(win_T *wp, const char *value, CharsOption what, boo p++; } } - } - if (what == kListchars && lcs_chars.leadtab2 != NUL && lcs_chars.tab2 == NUL) { - return e_leadtab_requires_tab; + if (what == kListchars && has_leadtab && !has_tab) { + return e_leadtab_requires_tab; + } } if (apply) { diff --git a/test/old/testdir/gen_opt_test.vim b/test/old/testdir/gen_opt_test.vim index 1c67cc6126..3b8c0ef6ff 100644 --- a/test/old/testdir/gen_opt_test.vim +++ b/test/old/testdir/gen_opt_test.vim @@ -276,11 +276,13 @@ let test_values = { "\ " ['xxx', ':none', 'xxx:', 'x:non', 'y:mok3', 'z:kittty']], \ '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', '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']], + \ 'listchars': [['', 'eol:x', 'tab:xy', 'tab:xyz', 'space:x', 'lead:x', + \ 'multispace:xxxy', 'tab:xy,leadtab:xyz', 'leadtab:xyz,tab:xy', + \ 'leadmultispace:xxxy', 'trail:x', 'extends:x', 'precedes:x', + \ 'conceal:x', 'eol:\\x24', 'eol:\\u21b5', 'eol:\\U000021b5', + \ 'eol:x,space:y', 'nbsp:x'], + \ ['xxx', 'eol:', 'leadtab:xyz', 'multispace:xxxy,leadtab:xyz', + \ 'leadmultispace:xxxy,leadtab:xyz,multispace:yyyx']], \ '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 index 2191237576..3455984afd 100644 --- a/test/old/testdir/test_listchars.vim +++ b/test/old/testdir/test_listchars.vim @@ -392,6 +392,8 @@ func Test_listchars() \ 'text>---tab ' \ ] call Check_listchars(expected, 3, 20) + call assert_equal(expected->mapnew({_, s -> trim(s, ' ', 2)}) + [' '], + \ split(execute("%list"), "\n")) " Test leadtab with unicode characters normal ggdG @@ -399,6 +401,7 @@ func Test_listchars() call append(0, ["\ttext"]) let expected = ['├──────┤text'] call Check_listchars(expected, 1, 12) + call assert_equal(expected + [' '], split(execute("%list"), "\n")) " Test leadtab with mixed indentation (spaces + tabs) normal ggdG @@ -406,6 +409,7 @@ func Test_listchars() call append(0, [" \t text"]) let expected = ['.+******.text'] call Check_listchars(expected, 1, 13) + call assert_equal(expected + [' '], split(execute("%list"), "\n")) " Test leadtab with pipe character normal ggdG @@ -413,6 +417,7 @@ func Test_listchars() call append(0, ["\ttext"]) let expected = ['| text'] call Check_listchars(expected, 1, 12) + call assert_equal(expected + [' '], split(execute("%list"), "\n")) " Test leadtab with unicode bar normal ggdG @@ -420,6 +425,7 @@ func Test_listchars() call append(0, ["\ttext"]) let expected = ['│ text'] call Check_listchars(expected, 1, 12) + call assert_equal(expected + [' '], split(execute("%list"), "\n")) " Test leadtab vs tab distinction (leading vs non-leading) " In a line with only tabs, they aren't considered leading. @@ -438,6 +444,8 @@ func Test_listchars() \ '>------->------- ' \ ] call Check_listchars(expected, 4, 32) + call assert_equal(expected->mapnew({_, s -> trim(s, ' ', 2)}) + [' '], + \ split(execute("%list"), "\n")) " Test leadtab with trail and space normal ggdG @@ -453,6 +461,8 @@ func Test_listchars() \ '+*******..text<<' \ ] call Check_listchars(expected, 3, 16) + call assert_equal(expected->mapnew({_, s -> trim(s, ' ', 2)}) + [' '], + \ split(execute("%list"), "\n")) " Test leadtab with eol normal ggdG @@ -463,7 +473,8 @@ func Test_listchars() \ 'text>---tab$ ' \ ] call Check_listchars(expected, 2, 13) - + call assert_equal(expected->mapnew({_, s -> trim(s, ' ', 2)}) + ['$'], + \ split(execute("%list"), "\n")) " test nbsp normal ggdG