0
0
mirror of https://github.com/vim/vim.git synced 2025-09-29 04:34:16 -04:00

patch 8.2.3424: a sequence of spaces is hard to see in list mode

Problem:    A sequence of spaces is hard to see in list mode.
Solution:   Add the "multispace" option to 'listchars'. (closes #8834)
This commit is contained in:
zeertzjq
2021-09-10 16:58:30 +02:00
committed by Bram Moolenaar
parent 07802044b9
commit f14b8ba137
7 changed files with 264 additions and 29 deletions

View File

@@ -4939,16 +4939,25 @@ A jump table for the options with a short description can be found at |Q_op|.
*lcs-space* *lcs-space*
space:c Character to show for a space. When omitted, spaces space:c Character to show for a space. When omitted, spaces
are left blank. are left blank.
*lcs-multispace*
multispace:c...
One or more characters to use cyclically to show for
multiple consecutive spaces. Overrides the "space"
setting, except for single spaces. When omitted, the
"space" setting is used. For example,
`:set listchars=multispace:---+` shows ten consecutive
spaces as:
---+---+--
*lcs-lead* *lcs-lead*
lead:c Character to show for leading spaces. When omitted, lead:c Character to show for leading spaces. When omitted,
leading spaces are blank. Overrides the "space" leading spaces are blank. Overrides the "space" and
setting for leading spaces. You can combine it with "multispace" settings for leading spaces. You can
"tab:", for example: > combine it with "tab:", for example: >
:set listchars+=tab:>-,lead:. :set listchars+=tab:>-,lead:.
< *lcs-trail* < *lcs-trail*
trail:c Character to show for trailing spaces. When omitted, trail:c Character to show for trailing spaces. When omitted,
trailing spaces are blank. Overrides the "space" trailing spaces are blank. Overrides the "space" and
setting for trailing spaces. "multispace" settings for trailing spaces.
*lcs-extends* *lcs-extends*
extends:c Character to show in the last column, when 'wrap' is extends:c Character to show in the last column, when 'wrap' is
off and the line continues beyond the right of the off and the line continues beyond the right of the

View File

@@ -340,6 +340,8 @@ win_line(
#endif #endif
colnr_T trailcol = MAXCOL; // start of trailing spaces colnr_T trailcol = MAXCOL; // start of trailing spaces
colnr_T leadcol = 0; // start of leading spaces colnr_T leadcol = 0; // start of leading spaces
int in_multispace = FALSE; // in multiple consecutive spaces
int multispace_pos = 0; // position in lcs-multispace string
#ifdef FEAT_LINEBREAK #ifdef FEAT_LINEBREAK
int need_showbreak = FALSE; // overlong line, skipping first x int need_showbreak = FALSE; // overlong line, skipping first x
// chars // chars
@@ -736,6 +738,7 @@ win_line(
if (wp->w_p_list) if (wp->w_p_list)
{ {
if (wp->w_lcs_chars.space if (wp->w_lcs_chars.space
|| wp->w_lcs_chars.multispace != NULL
|| wp->w_lcs_chars.trail || wp->w_lcs_chars.trail
|| wp->w_lcs_chars.lead || wp->w_lcs_chars.lead
|| wp->w_lcs_chars.nbsp) || wp->w_lcs_chars.nbsp)
@@ -2011,6 +2014,11 @@ win_line(
} }
#endif #endif
in_multispace = c == ' '
&& ((ptr > line + 1 && ptr[-2] == ' ') || *ptr == ' ');
if (!in_multispace)
multispace_pos = 0;
// 'list': Change char 160 to 'nbsp' and space to 'space' // 'list': Change char 160 to 'nbsp' and space to 'space'
// setting in 'listchars'. But not when the character is // setting in 'listchars'. But not when the character is
// followed by a composing character (use mb_l to check that). // followed by a composing character (use mb_l to check that).
@@ -2022,12 +2030,21 @@ win_line(
&& wp->w_lcs_chars.nbsp) && wp->w_lcs_chars.nbsp)
|| (c == ' ' || (c == ' '
&& mb_l == 1 && mb_l == 1
&& wp->w_lcs_chars.space && (wp->w_lcs_chars.space
|| (in_multispace
&& wp->w_lcs_chars.multispace != NULL))
&& ptr - line >= leadcol && ptr - line >= leadcol
&& ptr - line <= trailcol))) && ptr - line <= trailcol)))
{ {
c = (c == ' ') ? wp->w_lcs_chars.space : if (in_multispace && wp->w_lcs_chars.multispace != NULL)
wp->w_lcs_chars.nbsp; {
c = wp->w_lcs_chars.multispace[multispace_pos++];
if (wp->w_lcs_chars.multispace[multispace_pos] == NUL)
multispace_pos = 0;
}
else
c = (c == ' ') ? wp->w_lcs_chars.space
: wp->w_lcs_chars.nbsp;
if (area_attr == 0 && search_attr == 0) if (area_attr == 0 && search_attr == 0)
{ {
n_attr = 1; n_attr = 1;

View File

@@ -1841,6 +1841,8 @@ msg_prt_line(char_u *s, int list)
int attr = 0; int attr = 0;
char_u *trail = NULL; char_u *trail = NULL;
char_u *lead = NULL; char_u *lead = NULL;
int in_multispace = FALSE;
int multispace_pos = 0;
int l; int l;
char_u buf[MB_MAXBYTES + 1]; char_u buf[MB_MAXBYTES + 1];
@@ -1912,6 +1914,10 @@ msg_prt_line(char_u *s, int list)
{ {
attr = 0; attr = 0;
c = *s++; c = *s++;
in_multispace = c == ' '
&& ((col > 0 && s[-2] == ' ') || *s == ' ');
if (!in_multispace)
multispace_pos = 0;
if (c == TAB && (!list || curwin->w_lcs_chars.tab1)) if (c == TAB && (!list || curwin->w_lcs_chars.tab1))
{ {
// tab amount depends on current column // tab amount depends on current column
@@ -1963,22 +1969,33 @@ msg_prt_line(char_u *s, int list)
// the same in plain text. // the same in plain text.
attr = HL_ATTR(HLF_8); attr = HL_ATTR(HLF_8);
} }
else if (c == ' ' && lead != NULL && s <= lead) else if (c == ' ')
{
if (lead != NULL && s <= lead)
{ {
c = curwin->w_lcs_chars.lead; c = curwin->w_lcs_chars.lead;
attr = HL_ATTR(HLF_8); attr = HL_ATTR(HLF_8);
} }
else if (c == ' ' && trail != NULL && s > trail) else if (trail != NULL && s > trail)
{ {
c = curwin->w_lcs_chars.trail; c = curwin->w_lcs_chars.trail;
attr = HL_ATTR(HLF_8); attr = HL_ATTR(HLF_8);
} }
else if (c == ' ' && list && curwin->w_lcs_chars.space != NUL) else if (list && in_multispace
&& curwin->w_lcs_chars.multispace != NULL)
{
c = curwin->w_lcs_chars.multispace[multispace_pos++];
if (curwin->w_lcs_chars.multispace[multispace_pos] == NUL)
multispace_pos = 0;
attr = HL_ATTR(HLF_8);
}
else if (list && curwin->w_lcs_chars.space != NUL)
{ {
c = curwin->w_lcs_chars.space; c = curwin->w_lcs_chars.space;
attr = HL_ATTR(HLF_8); attr = HL_ATTR(HLF_8);
} }
} }
}
if (c == NUL) if (c == NUL)
break; break;

View File

@@ -4787,6 +4787,8 @@ set_chars_option(win_T *wp, char_u **varp)
int round, i, len, entries; int round, i, len, entries;
char_u *p, *s; char_u *p, *s;
int c1 = 0, c2 = 0, c3 = 0; int c1 = 0, c2 = 0, c3 = 0;
char_u *last_multispace; // Last occurrence of "multispace:"
int multispace_len = 0; // Length of lcs-multispace string
struct charstab struct charstab
{ {
int *cp; int *cp;
@@ -4853,6 +4855,13 @@ set_chars_option(win_T *wp, char_u **varp)
{ {
lcs_chars.tab1 = NUL; lcs_chars.tab1 = NUL;
lcs_chars.tab3 = NUL; lcs_chars.tab3 = NUL;
if (multispace_len)
{
lcs_chars.multispace = ALLOC_MULT(int, multispace_len + 1);
lcs_chars.multispace[multispace_len] = NUL;
}
else
lcs_chars.multispace = NULL;
} }
else else
{ {
@@ -4877,19 +4886,19 @@ set_chars_option(win_T *wp, char_u **varp)
s = p + len + 1; s = p + len + 1;
c1 = mb_ptr2char_adv(&s); c1 = mb_ptr2char_adv(&s);
if (mb_char2cells(c1) > 1) if (mb_char2cells(c1) > 1)
continue; return e_invarg;
if (tab[i].cp == &lcs_chars.tab2) if (tab[i].cp == &lcs_chars.tab2)
{ {
if (*s == NUL) if (*s == NUL)
continue; return e_invarg;
c2 = mb_ptr2char_adv(&s); c2 = mb_ptr2char_adv(&s);
if (mb_char2cells(c2) > 1) if (mb_char2cells(c2) > 1)
continue; return e_invarg;
if (!(*s == ',' || *s == NUL)) if (!(*s == ',' || *s == NUL))
{ {
c3 = mb_ptr2char_adv(&s); c3 = mb_ptr2char_adv(&s);
if (mb_char2cells(c3) > 1) if (mb_char2cells(c3) > 1)
continue; return e_invarg;
} }
} }
@@ -4914,13 +4923,57 @@ set_chars_option(win_T *wp, char_u **varp)
} }
if (i == entries) if (i == entries)
{
len = STRLEN("multispace");
if ((varp == &p_lcs || varp == &wp->w_p_lcs)
&& STRNCMP(p, "multispace", len) == 0
&& p[len] == ':'
&& p[len + 1] != NUL)
{
s = p + len + 1;
if (round == 0)
{
// Get length of lcs-multispace string in first round
last_multispace = p;
multispace_len = 0;
while (*s != NUL && *s != ',')
{
c1 = mb_ptr2char_adv(&s);
if (mb_char2cells(c1) > 1)
return e_invarg; return e_invarg;
++multispace_len;
}
if (multispace_len == 0)
// lcs-multispace cannot be an empty string
return e_invarg;
p = s;
}
else
{
int multispace_pos = 0;
while (*s != NUL && *s != ',')
{
c1 = mb_ptr2char_adv(&s);
if (p == last_multispace)
lcs_chars.multispace[multispace_pos++] = c1;
}
p = s;
}
}
else
return e_invarg;
}
if (*p == ',') if (*p == ',')
++p; ++p;
} }
} }
if (tab == lcstab) if (tab == lcstab)
{
if (wp->w_lcs_chars.multispace != NULL)
vim_free(wp->w_lcs_chars.multispace);
wp->w_lcs_chars = lcs_chars; wp->w_lcs_chars = lcs_chars;
}
return NULL; // no error return NULL; // no error
} }

View File

@@ -3376,6 +3376,7 @@ typedef struct
int tab3; int tab3;
int trail; int trail;
int lead; int lead;
int *multispace;
#ifdef FEAT_CONCEAL #ifdef FEAT_CONCEAL
int conceal; int conceal;
#endif #endif

View File

@@ -142,6 +142,93 @@ func Test_listchars()
call assert_equal(expected, split(execute("%list"), "\n")) call assert_equal(expected, split(execute("%list"), "\n"))
" Test multispace
normal ggdG
set listchars&
set listchars+=multispace:yYzZ
set list
call append(0, [
\ ' ffff ',
\ ' i i gg',
\ ' h ',
\ ' j ',
\ ' 0 0 ',
\ ])
let expected = [
\ 'yYzZffffyYzZ$',
\ 'yYi iyYzZygg$',
\ ' hyYzZyYzZyY$',
\ 'yYzZyYzZyYj $',
\ 'yYzZ0yY0yYzZ$',
\ '$'
\ ]
redraw!
for i in range(1, 5)
call cursor(i, 1)
call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$')))
endfor
call assert_equal(expected, split(execute("%list"), "\n"))
" the last occurrence of 'multispace:' is used
set listchars+=space:x,multispace:XyY
let expected = [
\ 'XyYXffffXyYX$',
\ 'XyixiXyYXygg$',
\ 'xhXyYXyYXyYX$',
\ 'XyYXyYXyYXjx$',
\ 'XyYX0Xy0XyYX$',
\ '$'
\ ]
redraw!
for i in range(1, 5)
call cursor(i, 1)
call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$')))
endfor
call assert_equal(expected, split(execute("%list"), "\n"))
set listchars+=lead:>,trail:<
let expected = [
\ '>>>>ffff<<<<$',
\ '>>ixiXyYXygg$',
\ '>h<<<<<<<<<<$',
\ '>>>>>>>>>>j<$',
\ '>>>>0Xy0<<<<$',
\ '$'
\ ]
redraw!
for i in range(1, 5)
call cursor(i, 1)
call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$')))
endfor
call assert_equal(expected, split(execute("%list"), "\n"))
" removing 'multispace:'
set listchars-=multispace:XyY
set listchars-=multispace:yYzZ
let expected = [
\ '>>>>ffff<<<<$',
\ '>>ixixxxxxgg$',
\ '>h<<<<<<<<<<$',
\ '>>>>>>>>>>j<$',
\ '>>>>0xx0<<<<$',
\ '$'
\ ]
redraw!
for i in range(1, 5)
call cursor(i, 1)
call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$')))
endfor
call assert_equal(expected, split(execute("%list"), "\n"))
" test nbsp " test nbsp
normal ggdG normal ggdG
set listchars=nbsp:X,trail:Y set listchars=nbsp:X,trail:Y
@@ -191,20 +278,69 @@ func Test_listchars_unicode()
set encoding=utf-8 set encoding=utf-8
set ff=unix set ff=unix
set listchars=eol:⇔,space:␣,nbsp:≠,tab:←↔→ set listchars=eol:⇔,space:␣,multispace:≡≢≣,nbsp:≠,tab:←↔→
set list set list
let nbsp = nr2char(0xa0) let nbsp = nr2char(0xa0)
call append(0, ["a\tb c" .. nbsp .. "d"]) call append(0, [" a\tb c" .. nbsp .. "d "])
let expected = ['a←↔↔↔↔↔→b␣c≠d⇔'] let expected = ['≡≢≣≡≢≣≡≢a←↔↔↔↔↔→b␣c≠d≡≢⇔']
redraw! redraw!
call cursor(1, 1) call cursor(1, 1)
call assert_equal(expected, ScreenLines(1, virtcol('$'))) call assert_equal(expected, ScreenLines(1, virtcol('$')))
set listchars+=lead:⇨,trail:⇦
let expected = ['⇨⇨⇨⇨⇨⇨⇨⇨a←↔↔↔↔↔→b␣c≠d⇦⇦⇔']
redraw!
call cursor(1, 1)
call assert_equal(expected, ScreenLines(1, virtcol('$')))
let &encoding=oldencoding let &encoding=oldencoding
enew! enew!
set listchars& ff& set listchars& ff&
endfunction endfunction
func Test_listchars_invalid()
enew!
set ff=unix
set listchars&
set list
set ambiwidth=double
" No colon
call assert_fails('set listchars=x', 'E474:')
call assert_fails('set listchars=x', 'E474:')
call assert_fails('set listchars=multispace', 'E474:')
" Too short
call assert_fails('set listchars=space:', 'E474:')
call assert_fails('set listchars=tab:x', 'E474:')
call assert_fails('set listchars=multispace:', 'E474:')
" One occurrence too short
call assert_fails('set listchars=space:,space:x', 'E474:')
call assert_fails('set listchars=space:x,space:', 'E474:')
call assert_fails('set listchars=tab:x,tab:xx', 'E474:')
call assert_fails('set listchars=tab:xx,tab:x', 'E474:')
call assert_fails('set listchars=multispace:,multispace:x', 'E474:')
call assert_fails('set listchars=multispace:x,multispace:', 'E474:')
" Too long
call assert_fails('set listchars=space:xx', 'E474:')
call assert_fails('set listchars=tab:xxxx', 'E474:')
" Has non-single width character
call assert_fails('set listchars=space:·', 'E474:')
call assert_fails('set listchars=tab:·x', 'E474:')
call assert_fails('set listchars=tab:x·', 'E474:')
call assert_fails('set listchars=tab:xx·', 'E474:')
call assert_fails('set listchars=multispace:·', 'E474:')
call assert_fails('set listchars=multispace:xxx·', 'E474:')
enew!
set ambiwidth& listchars& ff&
endfunction
" Tests that space characters following composing character won't get replaced " Tests that space characters following composing character won't get replaced
" by listchars. " by listchars.
func Test_listchars_composing() func Test_listchars_composing()

View File

@@ -755,6 +755,8 @@ static char *(features[]) =
static int included_patches[] = static int included_patches[] =
{ /* Add new patch number below this line */ { /* Add new patch number below this line */
/**/
3424,
/**/ /**/
3423, 3423,
/**/ /**/