vim-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 vim/vim#8834)
f14b8ba137
This commit is contained in:
zeertzjq
2021-09-19 13:13:44 +08:00
parent 177e87525a
commit 51567db4b6
6 changed files with 251 additions and 28 deletions

View File

@@ -3751,16 +3751,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
@@ -3785,7 +3794,8 @@ A jump table for the options with a short description can be found at |Q_op|.
:set lcs=tab:>-,eol:<,nbsp:%
:set lcs=extends:>,precedes:<
< |hl-NonText| highlighting will be used for "eol", "extends" and
"precedes". |hl-Whitespace| for "nbsp", "space", "tab" and "trail".
"precedes". |hl-Whitespace| for "nbsp", "space", "tab", "multispace",
"lead" and "trail".
*'lpl'* *'nolpl'* *'loadplugins'* *'noloadplugins'*
'loadplugins' 'lpl' boolean (default on)

View File

@@ -1214,6 +1214,7 @@ struct window_S {
int tab3; ///< third tab character
int lead;
int trail;
int *multispace;
int conceal;
} w_p_lcs_chars;

View File

@@ -1707,6 +1707,8 @@ void msg_prt_line(char_u *s, int list)
int n;
int attr = 0;
char_u *lead = NULL;
bool in_multispace = false;
int multispace_pos = 0;
char_u *trail = NULL;
int l;
@@ -1771,6 +1773,10 @@ void msg_prt_line(char_u *s, int list)
} else {
attr = 0;
c = *s++;
in_multispace = c == ' ' && ((col > 0 && s[-2] == ' ') || *s == ' ');
if (!in_multispace) {
multispace_pos = 0;
}
if (c == TAB && (!list || curwin->w_p_lcs_chars.tab1)) {
// tab amount depends on current column
n_extra = tabstop_padding(col,
@@ -1786,11 +1792,11 @@ void msg_prt_line(char_u *s, int list)
: curwin->w_p_lcs_chars.tab1;
c_extra = curwin->w_p_lcs_chars.tab2;
c_final = curwin->w_p_lcs_chars.tab3;
attr = HL_ATTR(HLF_8);
attr = HL_ATTR(HLF_0);
}
} else if (c == 160 && list && curwin->w_p_lcs_chars.nbsp != NUL) {
c = curwin->w_p_lcs_chars.nbsp;
attr = HL_ATTR(HLF_8);
attr = HL_ATTR(HLF_0);
} else if (c == NUL && list && curwin->w_p_lcs_chars.eol != NUL) {
p_extra = (char_u *)"";
c_extra = NUL;
@@ -1807,16 +1813,24 @@ void msg_prt_line(char_u *s, int list)
c = *p_extra++;
/* Use special coloring to be able to distinguish <hex> from
* the same in plain text. */
attr = HL_ATTR(HLF_8);
} else if (c == ' ' && lead != NULL && s <= lead) {
attr = HL_ATTR(HLF_0);
} else if (c == ' ') {
if (lead != NULL && s <= lead) {
c = curwin->w_p_lcs_chars.lead;
attr = HL_ATTR(HLF_8);
} else if (c == ' ' && trail != NULL && s > trail) {
attr = HL_ATTR(HLF_0);
} else if (trail != NULL && s > trail) {
c = curwin->w_p_lcs_chars.trail;
attr = HL_ATTR(HLF_8);
} else if (c == ' ' && list && curwin->w_p_lcs_chars.space != NUL) {
attr = HL_ATTR(HLF_0);
} else if (list && in_multispace && curwin->w_p_lcs_chars.multispace != NULL) {
c = curwin->w_p_lcs_chars.multispace[multispace_pos++];
if (curwin->w_p_lcs_chars.multispace[multispace_pos] == NUL) {
multispace_pos = 0;
}
attr = HL_ATTR(HLF_0);
} else if (list && curwin->w_p_lcs_chars.space != NUL) {
c = curwin->w_p_lcs_chars.space;
attr = HL_ATTR(HLF_8);
attr = HL_ATTR(HLF_0);
}
}
}

View File

@@ -3442,6 +3442,8 @@ static char_u *set_chars_option(win_T *wp, char_u **varp, bool set)
int c1;
int c2 = 0;
int c3 = 0;
char_u *last_multispace; // Last occurrence of "multispace:"
int multispace_len = 0; // Length of lcs-multispace string
struct chars_tab {
int *cp; ///< char value
@@ -3511,6 +3513,15 @@ static char_u *set_chars_option(win_T *wp, char_u **varp, bool set)
if (varp == &p_lcs || varp == &wp->w_p_lcs) {
wp->w_p_lcs_chars.tab1 = NUL;
wp->w_p_lcs_chars.tab3 = NUL;
if (wp->w_p_lcs_chars.multispace != NULL) {
xfree(wp->w_p_lcs_chars.multispace);
}
if (multispace_len > 0) {
wp->w_p_lcs_chars.multispace = xmalloc((size_t)(multispace_len + 1) * sizeof(int));
wp->w_p_lcs_chars.multispace[multispace_len] = NUL;
} else {
wp->w_p_lcs_chars.multispace = NULL;
}
}
}
p = *varp;
@@ -3527,22 +3538,22 @@ static char_u *set_chars_option(win_T *wp, char_u **varp, bool set)
int c1len = utf_ptr2len(s);
c1 = mb_cptr2char_adv((const char_u **)&s);
if (mb_char2cells(c1) > 1 || (c1len == 1 && c1 > 127)) {
continue;
return e_invarg;
}
if (tab[i].cp == &wp->w_p_lcs_chars.tab2) {
if (*s == NUL) {
continue;
return e_invarg;
}
int c2len = utf_ptr2len(s);
c2 = mb_cptr2char_adv((const char_u **)&s);
if (mb_char2cells(c2) > 1 || (c2len == 1 && c2 > 127)) {
continue;
return e_invarg;
}
if (!(*s == ',' || *s == NUL)) {
int c3len = utf_ptr2len(s);
c3 = mb_cptr2char_adv((const char_u **)&s);
if (mb_char2cells(c3) > 1 || (c3len == 1 && c3 > 127)) {
continue;
return e_invarg;
}
}
}
@@ -3563,8 +3574,43 @@ static char_u *set_chars_option(win_T *wp, char_u **varp, bool set)
}
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 the first round
last_multispace = p;
multispace_len = 0;
while (*s != NUL && *s != ',') {
int c1len = utf_ptr2len(s);
c1 = mb_cptr2char_adv((const char_u **)&s);
if (mb_char2cells(c1) > 1 || (c1len == 1 && c1 > 127)) {
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_cptr2char_adv((const char_u **)&s);
if (p == last_multispace) {
wp->w_p_lcs_chars.multispace[multispace_pos++] = c1;
}
}
p = s;
}
} else {
return e_invarg;
}
}
if (*p == ',') {
p++;
}

View File

@@ -2081,6 +2081,8 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc
int change_end = -1; // last col of changed area
colnr_T trailcol = MAXCOL; // start of trailing spaces
colnr_T leadcol = 0; // start of leading spaces
bool in_multispace = false; // in multiple consecutive spaces
int multispace_pos = 0; // position in lcs-multispace string
bool need_showbreak = false; // overlong line, skip first x chars
sign_attrs_T sattrs[SIGN_SHOW_MAX]; // attributes for signs
int num_signs; // number of signs for line
@@ -2462,6 +2464,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc
if (wp->w_p_list && !has_fold && !end_fill) {
if (wp->w_p_lcs_chars.space
|| wp->w_p_lcs_chars.multispace != NULL
|| wp->w_p_lcs_chars.trail
|| wp->w_p_lcs_chars.lead
|| wp->w_p_lcs_chars.nbsp) {
@@ -3580,6 +3583,11 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc
}
}
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'.
// But not when the character is followed by a composing
// character (use mb_l to check that).
@@ -3591,10 +3599,18 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc
&& wp->w_p_lcs_chars.nbsp)
|| (c == ' '
&& mb_l == 1
&& wp->w_p_lcs_chars.space
&& (wp->w_p_lcs_chars.space
|| (in_multispace && wp->w_p_lcs_chars.multispace != NULL))
&& ptr - line >= leadcol
&& ptr - line <= trailcol))) {
if (in_multispace && wp->w_p_lcs_chars.multispace != NULL) {
c = wp->w_p_lcs_chars.multispace[multispace_pos++];
if (wp->w_p_lcs_chars.multispace[multispace_pos] == NUL) {
multispace_pos = 0;
}
} else {
c = (c == ' ') ? wp->w_p_lcs_chars.space : wp->w_p_lcs_chars.nbsp;
}
n_attr = 1;
extra_attr = win_hl_attr(wp, HLF_0);
saved_attr2 = char_attr; // save current attr

View File

@@ -140,6 +140,93 @@ func Test_listchars()
call assert_equal(expected, split(execute("%list"), "\n"))
" Test multispace
normal ggdG
set listchars=eol:$
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
@@ -189,20 +276,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=eol:$
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()