0
0
mirror of https://github.com/vim/vim.git synced 2025-09-28 04:24:06 -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*
space:c Character to show for a space. When omitted, spaces
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*
lead:c Character to show for leading spaces. When omitted,
leading spaces are blank. Overrides the "space"
setting for leading spaces. You can combine it with
"tab:", for example: >
leading spaces are blank. Overrides the "space" and
"multispace" settings for leading spaces. You can
combine it with "tab:", for example: >
:set listchars+=tab:>-,lead:.
< *lcs-trail*
trail:c Character to show for trailing spaces. When omitted,
trailing spaces are blank. Overrides the "space"
setting for trailing spaces.
trailing spaces are blank. Overrides the "space" and
"multispace" settings for trailing spaces.
*lcs-extends*
extends:c Character to show in the last column, when 'wrap' is
off and the line continues beyond the right of the

View File

@@ -340,6 +340,8 @@ win_line(
#endif
colnr_T trailcol = MAXCOL; // start of trailing 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
int need_showbreak = FALSE; // overlong line, skipping first x
// chars
@@ -736,6 +738,7 @@ win_line(
if (wp->w_p_list)
{
if (wp->w_lcs_chars.space
|| wp->w_lcs_chars.multispace != NULL
|| wp->w_lcs_chars.trail
|| wp->w_lcs_chars.lead
|| wp->w_lcs_chars.nbsp)
@@ -2011,6 +2014,11 @@ win_line(
}
#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'
// setting in 'listchars'. But not when the character is
// followed by a composing character (use mb_l to check that).
@@ -2022,12 +2030,21 @@ win_line(
&& wp->w_lcs_chars.nbsp)
|| (c == ' '
&& 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 <= trailcol)))
{
c = (c == ' ') ? wp->w_lcs_chars.space :
wp->w_lcs_chars.nbsp;
if (in_multispace && wp->w_lcs_chars.multispace != NULL)
{
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)
{
n_attr = 1;

View File

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

View File

@@ -4787,6 +4787,8 @@ set_chars_option(win_T *wp, char_u **varp)
int round, i, len, entries;
char_u *p, *s;
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
{
int *cp;
@@ -4853,6 +4855,13 @@ set_chars_option(win_T *wp, char_u **varp)
{
lcs_chars.tab1 = 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
{
@@ -4877,19 +4886,19 @@ set_chars_option(win_T *wp, char_u **varp)
s = p + len + 1;
c1 = mb_ptr2char_adv(&s);
if (mb_char2cells(c1) > 1)
continue;
return e_invarg;
if (tab[i].cp == &lcs_chars.tab2)
{
if (*s == NUL)
continue;
return e_invarg;
c2 = mb_ptr2char_adv(&s);
if (mb_char2cells(c2) > 1)
continue;
return e_invarg;
if (!(*s == ',' || *s == NUL))
{
c3 = mb_ptr2char_adv(&s);
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)
{
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;
++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 == ',')
++p;
}
}
if (tab == lcstab)
{
if (wp->w_lcs_chars.multispace != NULL)
vim_free(wp->w_lcs_chars.multispace);
wp->w_lcs_chars = lcs_chars;
}
return NULL; // no error
}

View File

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

View File

@@ -142,6 +142,93 @@ func Test_listchars()
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
normal ggdG
set listchars=nbsp:X,trail:Y
@@ -191,20 +278,69 @@ func Test_listchars_unicode()
set encoding=utf-8
set ff=unix
set listchars=eol:⇔,space:␣,nbsp:≠,tab:←↔→
set listchars=eol:⇔,space:␣,multispace:≡≢≣,nbsp:≠,tab:←↔→
set list
let nbsp = nr2char(0xa0)
call append(0, [" a\tb c" .. nbsp .. "d "])
let expected = ['a←↔↔↔↔↔→b␣c≠d⇔']
let expected = ['≡≢≣≡≢≣≡≢a←↔↔↔↔↔→b␣c≠d≡≢⇔']
redraw!
call cursor(1, 1)
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
enew!
set listchars& ff&
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
" by listchars.
func Test_listchars_composing()

View File

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