summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorzeertzjq <zeertzjq@outlook.com>2021-09-10 16:58:30 +0200
committerBram Moolenaar <Bram@vim.org>2021-09-10 16:58:30 +0200
commitf14b8ba1373f569705cb80419248054100b02360 (patch)
tree57858129c7f87dc01ea87fde7f19133ceaaa9c2b
parent07802044b90b2cbcc64b2dfe235f019d7c37589c (diff)
downloadvim-git-8.2.3424.tar.gz
patch 8.2.3424: a sequence of spaces is hard to see in list modev8.2.3424
Problem: A sequence of spaces is hard to see in list mode. Solution: Add the "multispace" option to 'listchars'. (closes #8834)
-rw-r--r--runtime/doc/options.txt19
-rw-r--r--src/drawline.c23
-rw-r--r--src/message.c43
-rw-r--r--src/screen.c63
-rw-r--r--src/structs.h1
-rw-r--r--src/testdir/test_listchars.vim142
-rw-r--r--src/version.c2
7 files changed, 264 insertions, 29 deletions
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index a82bf82bc..591e649d7 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -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
diff --git a/src/drawline.c b/src/drawline.c
index 2ad83f4b5..7d6f669ba 100644
--- a/src/drawline.c
+++ b/src/drawline.c
@@ -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;
diff --git a/src/message.c b/src/message.c
index bb0dbb2bd..0cd09da9f 100644
--- a/src/message.c
+++ b/src/message.c
@@ -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,20 +1969,31 @@ 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 == ' ')
{
- c = curwin->w_lcs_chars.lead;
- attr = HL_ATTR(HLF_8);
- }
- else if (c == ' ' && 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)
- {
- c = curwin->w_lcs_chars.space;
- attr = HL_ATTR(HLF_8);
+ if (lead != NULL && s <= lead)
+ {
+ c = curwin->w_lcs_chars.lead;
+ attr = HL_ATTR(HLF_8);
+ }
+ else if (trail != NULL && s > trail)
+ {
+ c = curwin->w_lcs_chars.trail;
+ attr = HL_ATTR(HLF_8);
+ }
+ 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);
+ }
}
}
diff --git a/src/screen.c b/src/screen.c
index bfb6bf7ba..6a9f63248 100644
--- a/src/screen.c
+++ b/src/screen.c
@@ -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)
- return e_invarg;
+ {
+ 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
}
diff --git a/src/structs.h b/src/structs.h
index 8a94fc115..eae6cd928 100644
--- a/src/structs.h
+++ b/src/structs.h
@@ -3376,6 +3376,7 @@ typedef struct
int tab3;
int trail;
int lead;
+ int *multispace;
#ifdef FEAT_CONCEAL
int conceal;
#endif
diff --git a/src/testdir/test_listchars.vim b/src/testdir/test_listchars.vim
index 160d247c0..4cbd3650b 100644
--- a/src/testdir/test_listchars.vim
+++ b/src/testdir/test_listchars.vim
@@ -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⇔']
+ call append(0, [" a\tb c" .. nbsp .. "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()
diff --git a/src/version.c b/src/version.c
index 9f4ea7613..75d083e9d 100644
--- a/src/version.c
+++ b/src/version.c
@@ -756,6 +756,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
+ 3424,
+/**/
3423,
/**/
3422,