]> granicus.if.org Git - vim/commitdiff
patch 8.2.3424: a sequence of spaces is hard to see in list mode v8.2.3424
authorzeertzjq <zeertzjq@outlook.com>
Fri, 10 Sep 2021 14:58:30 +0000 (16:58 +0200)
committerBram Moolenaar <Bram@vim.org>
Fri, 10 Sep 2021 14:58:30 +0000 (16:58 +0200)
Problem:    A sequence of spaces is hard to see in list mode.
Solution:   Add the "multispace" option to 'listchars'. (closes #8834)

runtime/doc/options.txt
src/drawline.c
src/message.c
src/screen.c
src/structs.h
src/testdir/test_listchars.vim
src/version.c

index a82bf82bcf6f188ab4ddd75f85c6e25b1d0c16be..591e649d70fd76456991ec5c79f8f768b6508ba8 100644 (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
index 2ad83f4b5e089c3ef895de46f353736d3e624740..7d6f669ba1a4f6074a0baa34b3542f35b3dd23e1 100644 (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;
index bb0dbb2bd1bfebda113760dbf87d8698bfcdc41a..0cd09da9f4a63d8c6a5c1b9c5984babfcb275c98 100644 (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,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);
+               }
            }
        }
 
index bfb6bf7baf811f50a09a825bc27d8efe7be9ff5c..6a9f63248736432b68a86cb469e99595c6dcff80 100644 (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)
-               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
 }
index 8a94fc115586501f2b53aef612c9f1a806ef2fa0..eae6cd928174dd675e1ca06bc6e4da8acc8b5855 100644 (file)
@@ -3376,6 +3376,7 @@ typedef struct
     int                tab3;
     int                trail;
     int                lead;
+    int                *multispace;
 #ifdef FEAT_CONCEAL
     int                conceal;
 #endif
index 160d247c0e24c167e1ccc3fa369cd123ee39276d..4cbd3650b496e5355c3086e90e4b3a24b99c6671 100644 (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⇔']
+  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()
index 9f4ea7613151bf805639a4b834dd59891ca6eaf0..75d083e9de637282947472c72fe5f8f58ccd7c88 100644 (file)
@@ -755,6 +755,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    3424,
 /**/
     3423,
 /**/