Patch 8.2.3424
Problem:    A sequence of spaces is hard to see in list mode.
Solution:   Add the "multispace" option to 'listchars'. (closes #8834)
Files:      runtime/doc/options.txt, src/drawline.c, src/message.c,
            src/screen.c, src/structs.h, src/testdir/test_listchars.vim


*** ../vim-8.2.3423/runtime/doc/options.txt     2021-08-28 20:42:43.636422753 
+0200
--- runtime/doc/options.txt     2021-09-10 16:56:55.790835753 +0200
***************
*** 4927,4942 ****
                                                        *lcs-space*
          space:c       Character to show for a space.  When omitted, spaces
                        are left blank.
                                                        *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: >
                                :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.
                                                        *lcs-extends*
          extends:c     Character to show in the last column, when 'wrap' is
                        off and the line continues beyond the right of the
--- 4939,4963 ----
                                                        *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" 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" 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
*** ../vim-8.2.3423/src/drawline.c      2021-09-07 20:45:27.796160030 +0200
--- src/drawline.c      2021-09-10 16:33:23.440290543 +0200
***************
*** 340,345 ****
--- 340,347 ----
  #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,741 ****
--- 738,744 ----
      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,2016 ****
--- 2014,2024 ----
                }
  #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,2033 ****
                             && wp->w_lcs_chars.nbsp)
                            || (c == ' '
                                && mb_l == 1
!                               && wp->w_lcs_chars.space
                                && ptr - line >= leadcol
                                && ptr - line <= trailcol)))
                {
!                   c = (c == ' ') ? wp->w_lcs_chars.space :
!                                                       wp->w_lcs_chars.nbsp;
                    if (area_attr == 0 && search_attr == 0)
                    {
                        n_attr = 1;
--- 2030,2050 ----
                             && wp->w_lcs_chars.nbsp)
                            || (c == ' '
                                && mb_l == 1
!                               && (wp->w_lcs_chars.space
!                                   || (in_multispace
!                                       && wp->w_lcs_chars.multispace != NULL))
                                && ptr - line >= leadcol
                                && ptr - line <= trailcol)))
                {
!                   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;
*** ../vim-8.2.3423/src/message.c       2021-08-14 14:00:58.233863891 +0200
--- src/message.c       2021-09-10 16:33:23.440290543 +0200
***************
*** 1841,1846 ****
--- 1841,1848 ----
      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,1917 ****
--- 1914,1923 ----
        {
            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,1982 ****
                // the same in plain text.
                attr = HL_ATTR(HLF_8);
            }
!           else if (c == ' ' && lead != NULL && s <= lead)
            {
!               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);
            }
        }
  
--- 1969,1999 ----
                // the same in plain text.
                attr = HL_ATTR(HLF_8);
            }
!           else if (c == ' ')
            {
!               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);
!               }
            }
        }
  
*** ../vim-8.2.3423/src/screen.c        2021-06-29 20:22:27.651393066 +0200
--- src/screen.c        2021-09-10 16:33:23.440290543 +0200
***************
*** 4787,4792 ****
--- 4787,4794 ----
      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,4858 ****
--- 4855,4867 ----
            {
                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,4895 ****
                    s = p + len + 1;
                    c1 = mb_ptr2char_adv(&s);
                    if (mb_char2cells(c1) > 1)
!                       continue;
                    if (tab[i].cp == &lcs_chars.tab2)
                    {
                        if (*s == NUL)
!                           continue;
                        c2 = mb_ptr2char_adv(&s);
                        if (mb_char2cells(c2) > 1)
!                           continue;
                        if (!(*s == ',' || *s == NUL))
                        {
                            c3 = mb_ptr2char_adv(&s);
                            if (mb_char2cells(c3) > 1)
!                               continue;
                        }
                    }
  
--- 4886,4904 ----
                    s = p + len + 1;
                    c1 = mb_ptr2char_adv(&s);
                    if (mb_char2cells(c1) > 1)
!                       return e_invarg;
                    if (tab[i].cp == &lcs_chars.tab2)
                    {
                        if (*s == NUL)
!                           return e_invarg;
                        c2 = mb_ptr2char_adv(&s);
                        if (mb_char2cells(c2) > 1)
!                           return e_invarg;
                        if (!(*s == ',' || *s == NUL))
                        {
                            c3 = mb_ptr2char_adv(&s);
                            if (mb_char2cells(c3) > 1)
!                               return e_invarg;
                        }
                    }
  
***************
*** 4914,4926 ****
            }
  
            if (i == entries)
!               return e_invarg;
            if (*p == ',')
                ++p;
        }
      }
      if (tab == lcstab)
        wp->w_lcs_chars = lcs_chars;
  
      return NULL;      // no error
  }
--- 4923,4979 ----
            }
  
            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
  }
*** ../vim-8.2.3423/src/structs.h       2021-09-09 23:01:10.506519642 +0200
--- src/structs.h       2021-09-10 16:33:23.440290543 +0200
***************
*** 3376,3381 ****
--- 3376,3382 ----
      int               tab3;
      int               trail;
      int               lead;
+     int               *multispace;
  #ifdef FEAT_CONCEAL
      int               conceal;
  #endif
*** ../vim-8.2.3423/src/testdir/test_listchars.vim      2021-07-08 
13:19:09.013465756 +0200
--- src/testdir/test_listchars.vim      2021-09-10 16:33:23.440290543 +0200
***************
*** 142,147 ****
--- 142,234 ----
  
    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,210 ****
    set encoding=utf-8
    set ff=unix
  
!   set listchars=eol:⇔,space:␣,nbsp:≠,tab:←↔→
    set list
  
    let nbsp = nr2char(0xa0)
!   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('$')))
    let &encoding=oldencoding
    enew!
    set listchars& ff&
  endfunction
  
  " Tests that space characters following composing character won't get replaced
  " by listchars.
  func Test_listchars_composing()
--- 278,346 ----
    set encoding=utf-8
    set ff=unix
  
!   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≡≢⇔']
    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()
*** ../vim-8.2.3423/src/version.c       2021-09-09 23:01:10.506519642 +0200
--- src/version.c       2021-09-10 16:44:47.191569024 +0200
***************
*** 757,758 ****
--- 757,760 ----
  {   /* Add new patch number below this line */
+ /**/
+     3424,
  /**/

-- 
hundred-and-one symptoms of being an internet addict:
23. You can't call your mother... she doesn't have VOIP

 /// Bram Moolenaar -- [email protected] -- http://www.Moolenaar.net   \\\
///                                                                      \\\
\\\        sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ ///
 \\\            help me help AIDS victims -- http://ICCF-Holland.org    ///

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/vim_dev/202109101459.18AExTua346946%40masaka.moolenaar.net.

Raspunde prin e-mail lui