mirror of
https://github.com/neovim/neovim.git
synced 2025-09-19 17:58:18 +00:00
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:
@@ -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)
|
||||
|
@@ -1214,6 +1214,7 @@ struct window_S {
|
||||
int tab3; ///< third tab character
|
||||
int lead;
|
||||
int trail;
|
||||
int *multispace;
|
||||
int conceal;
|
||||
} w_p_lcs_chars;
|
||||
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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++;
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -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()
|
||||
|
Reference in New Issue
Block a user