Patch 8.2.3652
Problem:    Can only get text properties one line at a time.
Solution:   Add options to prop_list() to use a range of lines and filter by
            types. (Yegappan Lakshmanan, closes #9138)
Files:      runtime/doc/textprop.txt, src/textprop.c,
            src/testdir/test_textprop.vim


*** ../vim-8.2.3651/runtime/doc/textprop.txt    2021-09-17 20:06:53.120560956 
+0100
--- runtime/doc/textprop.txt    2021-11-23 11:38:46.587158933 +0000
***************
*** 230,242 ****
  
  
  prop_list({lnum} [, {props}])                         *prop_list()*
!               Return a List with all text properties in line {lnum}.
  
!               When {props} contains a "bufnr" item, use this buffer instead
!               of the current buffer.
  
                The properties are ordered by starting column and priority.
                Each property is a Dict with these entries:
                   col          starting column
                   length       length in bytes, one more if line break is
                                included
--- 230,254 ----
  
  
  prop_list({lnum} [, {props}])                         *prop_list()*
!               Returns a List with all the text properties in line {lnum}.
  
!               The following optional items are supported in {props}:
!                  bufnr        use this buffer instead of the current buffer
!                  end_lnum     return text properties in all the lines
!                               between {lnum} and {end_lnum} (inclusive).
!                               A negative value is used as an offset from the
!                               last buffer line; -1 refers to the last buffer
!                               line.
!                  types        List of property type names. Return only text
!                               properties that match one of the type names.
!                  ids          List of property identifiers. Return only text
!                               properties with one of these identifiers.
  
                The properties are ordered by starting column and priority.
                Each property is a Dict with these entries:
+                  lnum         starting line number. Present only when
+                               returning text properties between {lnum} and
+                               {end_lnum}.
                   col          starting column
                   length       length in bytes, one more if line break is
                                included
***************
*** 253,258 ****
--- 265,294 ----
                When "end" is zero the property continues in the next line.
                The line break after this line is included.
  
+               Returns an empty list on error.
+ 
+               Examples:
+                  " get text properties placed in line 5
+                  echo prop_list(5)
+                  " get text properties placed in line 20 in buffer 4
+                  echo prop_list(20, {'bufnr': 4})
+                  " get all the text properties between line 1 and 20
+                  echo prop_list(1, {'end_lnum': 20})
+                  " get all the text properties of type 'myprop'
+                  echo prop_list(1, {'types': ['myprop'],
+                                               \ 'end_lnum': -1})
+                  " get all the text properties of type 'prop1' or 'prop2'
+                  echo prop_list(1, {'types': ['prop1', 'prop2'],
+                                               \ 'end_lnum': -1})
+                  " get all the text properties with ID 8
+                  echo prop_list(1, {'ids': [8], 'end_lnum': line('$')})
+                  " get all the text properties with ID 10 and 20
+                  echo prop_list(1, {'ids': [10, 20], 'end_lnum': -1})
+                  " get text properties with type 'myprop' and ID 100
+                  " in buffer 4.
+                  echo prop_list(1, {'bufnr': 4, 'types': ['myprop'],
+                                       \ 'ids': [100], 'end_lnum': -1})
+ 
                Can also be used as a |method|: >
                        GetLnum()->prop_list()
  <
***************
*** 262,268 ****
                {lnum-end} is given, remove matching text properties from line
                {lnum} to {lnum-end} (inclusive).
                When {lnum} is omitted remove matching text properties from
!               all lines.
  
                {props} is a dictionary with these fields:
                   id           remove text properties with this ID
--- 298,305 ----
                {lnum-end} is given, remove matching text properties from line
                {lnum} to {lnum-end} (inclusive).
                When {lnum} is omitted remove matching text properties from
!               all lines (this requires going over all lines, thus will be a
!               bit slow for a buffer with many lines).
  
                {props} is a dictionary with these fields:
                   id           remove text properties with this ID
***************
*** 294,301 ****
                                properties the one with the highest priority
                                will be used; negative values can be used, the
                                default priority is zero
!                  combine      when TRUE combine the highlight with any
!                               syntax highlight; when omitted or FALSE syntax
                                highlight will not be used
                   start_incl   when TRUE inserts at the start position will
                                be included in the text property
--- 331,338 ----
                                properties the one with the highest priority
                                will be used; negative values can be used, the
                                default priority is zero
!                  combine      when omitted or TRUE combine the highlight
!                               with any syntax highlight; when FALSE syntax
                                highlight will not be used
                   start_incl   when TRUE inserts at the start position will
                                be included in the text property
*** ../vim-8.2.3651/src/textprop.c      2021-08-16 20:38:38.131122584 +0100
--- src/textprop.c      2021-11-23 11:41:05.338750937 +0000
***************
*** 897,948 ****
  }
  
  /*
   * prop_list({lnum} [, {bufnr}])
   */
      void
  f_prop_list(typval_T *argvars, typval_T *rettv)
  {
!     linenr_T lnum;
!     buf_T    *buf = curbuf;
  
      if (in_vim9script()
            && (check_for_number_arg(argvars, 0) == FAIL
                || check_for_opt_dict_arg(argvars, 1) == FAIL))
        return;
  
!     lnum = tv_get_number(&argvars[0]);
      if (argvars[1].v_type != VAR_UNKNOWN)
      {
        if (get_bufnr_from_arg(&argvars[1], &buf) == FAIL)
            return;
-     }
-     if (lnum < 1 || lnum > buf->b_ml.ml_line_count)
-     {
-       emsg(_(e_invalid_range));
-       return;
-     }
  
!     if (rettv_list_alloc(rettv) == OK)
!     {
!       char_u      *text = ml_get_buf(buf, lnum, FALSE);
!       size_t      textlen = STRLEN(text) + 1;
!       int         count = (int)((buf->b_ml.ml_line_len - textlen)
!                                                        / sizeof(textprop_T));
!       int         i;
!       textprop_T  prop;
  
!       for (i = 0; i < count; ++i)
        {
!           dict_T *d = dict_alloc();
  
!           if (d == NULL)
!               break;
!           mch_memmove(&prop, text + textlen + i * sizeof(textprop_T),
!                                                          sizeof(textprop_T));
!           prop_fill_dict(d, &prop, buf);
!           list_append_dict(rettv->vval.v_list, d);
        }
      }
  }
  
  /*
--- 897,1155 ----
  }
  
  /*
+  * Returns TRUE if 'type_or_id' is in the 'types_or_ids' list.
+  */
+     static int
+ prop_type_or_id_in_list(int *types_or_ids, int len, int type_or_id)
+ {
+     int i;
+ 
+     for (i = 0; i < len; i++)
+       if (types_or_ids[i] == type_or_id)
+           return TRUE;
+ 
+     return FALSE;
+ }
+ 
+ /*
+  * Return all the text properties in line 'lnum' in buffer 'buf' in 'retlist'.
+  * If 'prop_types' is not NULL, then return only the text properties with
+  * matching property type in the 'prop_types' array.
+  * If 'prop_ids' is not NULL, then return only the text properties with
+  * an identifier in the 'props_ids' array.
+  * If 'add_lnum' is TRUE, then add the line number also to the text property
+  * dictionary.
+  */
+     static void
+ get_props_in_line(
+       buf_T           *buf,
+       linenr_T        lnum,
+       int             *prop_types,
+       int             prop_types_len,
+       int             *prop_ids,
+       int             prop_ids_len,
+       list_T          *retlist,
+       int             add_lnum)
+ {
+     char_u    *text = ml_get_buf(buf, lnum, FALSE);
+     size_t    textlen = STRLEN(text) + 1;
+     int               count;
+     int               i;
+     textprop_T        prop;
+ 
+     count = (int)((buf->b_ml.ml_line_len - textlen) / sizeof(textprop_T));
+     for (i = 0; i < count; ++i)
+     {
+       mch_memmove(&prop, text + textlen + i * sizeof(textprop_T),
+               sizeof(textprop_T));
+       if ((prop_types == NULL
+                   || prop_type_or_id_in_list(prop_types, prop_types_len,
+                       prop.tp_type))
+               && (prop_ids == NULL ||
+                   prop_type_or_id_in_list(prop_ids, prop_ids_len,
+                       prop.tp_id)))
+       {
+           dict_T *d = dict_alloc();
+ 
+           if (d == NULL)
+               break;
+           prop_fill_dict(d, &prop, buf);
+           if (add_lnum)
+               dict_add_number(d, "lnum", lnum);
+           list_append_dict(retlist, d);
+       }
+     }
+ }
+ 
+ /*
+  * Convert a List of property type names into an array of property type
+  * identifiers. Returns a pointer to the allocated array. Returns NULL on
+  * error. 'num_types' is set to the number of returned property types.
+  */
+     static int *
+ get_prop_types_from_names(list_T *l, buf_T *buf, int *num_types)
+ {
+     int               *prop_types;
+     listitem_T        *li;
+     int               i;
+     char_u    *name;
+     proptype_T        *type;
+ 
+     *num_types = 0;
+ 
+     prop_types = ALLOC_MULT(int, list_len(l));
+     if (prop_types == NULL)
+       return NULL;
+ 
+     i = 0;
+     FOR_ALL_LIST_ITEMS(l, li)
+     {
+       if (li->li_tv.v_type != VAR_STRING)
+       {
+           emsg(_(e_stringreq));
+           goto errret;
+       }
+       name = li->li_tv.vval.v_string;
+       if (name == NULL)
+           goto errret;
+ 
+       type = lookup_prop_type(name, buf);
+       if (type == NULL)
+           goto errret;
+       prop_types[i++] = type->pt_id;
+     }
+ 
+     *num_types = i;
+     return prop_types;
+ 
+ errret:
+     VIM_CLEAR(prop_types);
+     return NULL;
+ }
+ 
+ /*
+  * Convert a List of property identifiers into an array of property
+  * identifiers.  Returns a pointer to the allocated array. Returns NULL on
+  * error. 'num_ids' is set to the number of returned property identifiers.
+  */
+     static int *
+ get_prop_ids_from_list(list_T *l, int *num_ids)
+ {
+     int               *prop_ids;
+     listitem_T        *li;
+     int               i;
+     int               id;
+     int               error;
+ 
+     *num_ids = 0;
+ 
+     prop_ids = ALLOC_MULT(int, list_len(l));
+     if (prop_ids == NULL)
+       return NULL;
+ 
+     i = 0;
+     FOR_ALL_LIST_ITEMS(l, li)
+     {
+       error = FALSE;
+       id = tv_get_number_chk(&li->li_tv, &error);
+       if (error)
+           goto errret;
+ 
+       prop_ids[i++] = id;
+     }
+ 
+     *num_ids = i;
+     return prop_ids;
+ 
+ errret:
+     VIM_CLEAR(prop_ids);
+     return NULL;
+ }
+ 
+ /*
   * prop_list({lnum} [, {bufnr}])
   */
      void
  f_prop_list(typval_T *argvars, typval_T *rettv)
  {
!     linenr_T  lnum;
!     linenr_T  start_lnum;
!     linenr_T  end_lnum;
!     buf_T     *buf = curbuf;
!     int               add_lnum = FALSE;
!     int               *prop_types = NULL;
!     int               prop_types_len = 0;
!     int               *prop_ids = NULL;
!     int               prop_ids_len = 0;
!     list_T    *l;
!     dictitem_T        *di;
  
      if (in_vim9script()
            && (check_for_number_arg(argvars, 0) == FAIL
                || check_for_opt_dict_arg(argvars, 1) == FAIL))
        return;
  
!     if (rettv_list_alloc(rettv) != OK)
!       return;
! 
!     // default: get text properties on current line
!     start_lnum = tv_get_number(&argvars[0]);
!     end_lnum = start_lnum;
      if (argvars[1].v_type != VAR_UNKNOWN)
      {
+       dict_T *d;
+ 
+       if (argvars[1].v_type != VAR_DICT)
+       {
+           emsg(_(e_dictreq));
+           return;
+       }
+       d = argvars[1].vval.v_dict;
+ 
        if (get_bufnr_from_arg(&argvars[1], &buf) == FAIL)
            return;
  
!       if (d != NULL && (di = dict_find(d, (char_u *)"end_lnum", -1)) != NULL)
!       {
!           if (di->di_tv.v_type != VAR_NUMBER)
!           {
!               emsg(_(e_numberreq));
!               return;
!           }
!           end_lnum = tv_get_number(&di->di_tv);
!           if (end_lnum < 0)
!               // negative end_lnum is used as an offset from the last buffer
!               // line
!               end_lnum = buf->b_ml.ml_line_count + end_lnum + 1;
!           else if (end_lnum > buf->b_ml.ml_line_count)
!               end_lnum = buf->b_ml.ml_line_count;
!           add_lnum = TRUE;
!       }
!       if (d != NULL && (di = dict_find(d, (char_u *)"types", -1)) != NULL)
!       {
!           if (di->di_tv.v_type != VAR_LIST)
!           {
!               emsg(_(e_listreq));
!               return;
!           }
  
!           l = di->di_tv.vval.v_list;
!           if (l != NULL && list_len(l) > 0)
!           {
!               prop_types = get_prop_types_from_names(l, buf, &prop_types_len);
!               if (prop_types == NULL)
!                   return;
!           }
!       }
!       if (d != NULL && (di = dict_find(d, (char_u *)"ids", -1)) != NULL)
        {
!           if (di->di_tv.v_type != VAR_LIST)
!           {
!               emsg(_(e_listreq));
!               goto errret;
!           }
  
!           l = di->di_tv.vval.v_list;
!           if (l != NULL && list_len(l) > 0)
!           {
!               prop_ids = get_prop_ids_from_list(l, &prop_ids_len);
!               if (prop_ids == NULL)
!                   goto errret;
!           }
        }
      }
+     if (start_lnum < 1 || start_lnum > buf->b_ml.ml_line_count
+               || end_lnum < 1 || end_lnum < start_lnum)
+       emsg(_(e_invalid_range));
+     else
+       for (lnum = start_lnum; lnum <= end_lnum; lnum++)
+           get_props_in_line(buf, lnum, prop_types, prop_types_len,
+                   prop_ids, prop_ids_len,
+                   rettv->vval.v_list, add_lnum);
+ 
+ errret:
+     VIM_CLEAR(prop_types);
+     VIM_CLEAR(prop_ids);
  }
  
  /*
*** ../vim-8.2.3651/src/testdir/test_textprop.vim       2021-08-25 
16:01:57.132832684 +0100
--- src/testdir/test_textprop.vim       2021-11-23 11:34:52.611847493 +0000
***************
*** 5,10 ****
--- 5,11 ----
  CheckFeature textprop
  
  source screendump.vim
+ source vim9.vim
  
  func Test_proptype_global()
    call prop_type_add('comment', {'highlight': 'Directory', 'priority': 123, 
'start_incl': 1, 'end_incl': 1})
***************
*** 1645,1650 ****
--- 1646,1799 ----
    endtry
  enddef
  
+ " Tests for the prop_list() function
+ func Test_prop_list()
+   let lines =<< trim END
+     new
+     call AddPropTypes()
+     call setline(1, repeat([repeat('a', 60)], 10))
+     call prop_add(1, 4, {'type': 'one', 'id': 5, 'end_col': 6})
+     call prop_add(1, 5, {'type': 'two', 'id': 10, 'end_col': 7})
+     call prop_add(3, 12, {'type': 'one', 'id': 20, 'end_col': 14})
+     call prop_add(3, 13, {'type': 'two', 'id': 10, 'end_col': 15})
+     call prop_add(5, 20, {'type': 'one', 'id': 10, 'end_col': 22})
+     call prop_add(5, 21, {'type': 'two', 'id': 20, 'end_col': 23})
+     call assert_equal([
+           \ {'id': 5, 'col': 4, 'type_bufnr': 0, 'end': 1,
+           \  'type': 'one', 'length': 2, 'start': 1},
+           \ {'id': 10, 'col': 5, 'type_bufnr': 0, 'end': 1,
+           \  'type': 'two', 'length': 2, 'start': 1}], prop_list(1))
+     #" text properties between a few lines
+     call assert_equal([
+           \ {'lnum': 3, 'id': 20, 'col': 12, 'type_bufnr': 0, 'end': 1,
+           \  'type': 'one', 'length': 2, 'start': 1},
+           \ {'lnum': 3, 'id': 10, 'col': 13, 'type_bufnr': 0, 'end': 1,
+           \  'type': 'two', 'length': 2, 'start': 1},
+           \ {'lnum': 5, 'id': 10, 'col': 20, 'type_bufnr': 0, 'end': 1,
+           \  'type': 'one', 'length': 2, 'start': 1},
+           \ {'lnum': 5, 'id': 20, 'col': 21, 'type_bufnr': 0, 'end': 1,
+           \  'type': 'two', 'length': 2, 'start': 1}],
+           \ prop_list(2, {'end_lnum': 5}))
+     #" text properties across all the lines
+     call assert_equal([
+           \ {'lnum': 1, 'id': 5, 'col': 4, 'type_bufnr': 0, 'end': 1,
+           \  'type': 'one', 'length': 2, 'start': 1},
+           \ {'lnum': 3, 'id': 20, 'col': 12, 'type_bufnr': 0, 'end': 1,
+           \  'type': 'one', 'length': 2, 'start': 1},
+           \ {'lnum': 5, 'id': 10, 'col': 20, 'type_bufnr': 0, 'end': 1,
+           \  'type': 'one', 'length': 2, 'start': 1}],
+           \ prop_list(1, {'types': ['one'], 'end_lnum': -1}))
+     #" text properties with the specified identifier
+     call assert_equal([
+           \ {'lnum': 3, 'id': 20, 'col': 12, 'type_bufnr': 0, 'end': 1,
+           \  'type': 'one', 'length': 2, 'start': 1},
+           \ {'lnum': 5, 'id': 20, 'col': 21, 'type_bufnr': 0, 'end': 1,
+           \  'type': 'two', 'length': 2, 'start': 1}],
+           \ prop_list(1, {'ids': [20], 'end_lnum': 10}))
+     #" text properties of the specified type and id
+     call assert_equal([
+           \ {'lnum': 1, 'id': 10, 'col': 5, 'type_bufnr': 0, 'end': 1,
+           \  'type': 'two', 'length': 2, 'start': 1},
+           \ {'lnum': 3, 'id': 10, 'col': 13, 'type_bufnr': 0, 'end': 1,
+           \  'type': 'two', 'length': 2, 'start': 1}],
+           \ prop_list(1, {'types': ['two'], 'ids': [10], 'end_lnum': 20}))
+     call assert_equal([], prop_list(1, {'ids': [40, 50], 'end_lnum': 10}))
+     call assert_equal([], prop_list(6, {'end_lnum': 10}))
+     call assert_equal([], prop_list(2, {'end_lnum': 2}))
+     #" error cases
+     call assert_fails("echo prop_list(1, {'end_lnum': -20})", 'E16:')
+     call assert_fails("echo prop_list(4, {'end_lnum': 2})", 'E16:')
+     call assert_fails("echo prop_list(1, {'end_lnum': '$'})", 'E889:')
+     call assert_fails("echo prop_list(1, {'types': ['blue'], 'end_lnum': 
10})",
+           \ 'E971:')
+     call assert_fails("echo prop_list(1, {'types': ['one', 'blue'],
+           \ 'end_lnum': 10})", 'E971:')
+     call assert_fails("echo prop_list(1, {'types': ['one', 10],
+           \ 'end_lnum': 10})", 'E928:')
+     call assert_fails("echo prop_list(1, {'types': ['']})", 'E971:')
+     call assert_equal([], prop_list(2, {'types': []}))
+     call assert_equal([], prop_list(2, {'types': test_null_list()}))
+     call assert_fails("call prop_list(1, {'types': {}})", 'E714:')
+     call assert_fails("call prop_list(1, {'types': 'one'})", 'E714:')
+     call assert_equal([], prop_list(2, {'types': ['one'],
+           \ 'ids': test_null_list()}))
+     call assert_equal([], prop_list(2, {'types': ['one'], 'ids': []}))
+     call assert_fails("call prop_list(1, {'types': ['one'], 'ids': {}})",
+           \ 'E714:')
+     call assert_fails("call prop_list(1, {'types': ['one'], 'ids': 10})",
+           \ 'E714:')
+     call assert_fails("call prop_list(1, {'types': ['one'], 'ids': [[]]})",
+           \ 'E745:')
+     call assert_fails("call prop_list(1, {'types': ['one'], 'ids': [10, 
[]]})",
+           \ 'E745:')
+ 
+     #" get text properties from a non-current buffer
+     wincmd w
+     call assert_equal([
+           \ {'lnum': 1, 'id': 5, 'col': 4, 'type_bufnr': 0, 'end': 1,
+           \ 'type': 'one', 'length': 2, 'start': 1},
+           \ {'lnum': 1, 'id': 10, 'col': 5, 'type_bufnr': 0, 'end': 1,
+           \ 'type': 'two', 'length': 2, 'start': 1},
+           \ {'lnum': 3, 'id': 20, 'col': 12, 'type_bufnr': 0, 'end': 1,
+           \ 'type': 'one', 'length': 2, 'start': 1},
+           \ {'lnum': 3, 'id': 10, 'col': 13, 'type_bufnr': 0, 'end': 1,
+           \ 'type': 'two', 'length': 2, 'start': 1}],
+           \ prop_list(1, {'bufnr': winbufnr(1), 'end_lnum': 4}))
+     wincmd w
+ 
+     #" get text properties after clearing all the properties
+     call prop_clear(1, line('$'))
+     call assert_equal([], prop_list(1, {'end_lnum': 10}))
+ 
+     call prop_add(2, 4, {'type': 'one', 'id': 5, 'end_col': 6})
+     call prop_add(2, 4, {'type': 'two', 'id': 10, 'end_col': 6})
+     call prop_add(2, 4, {'type': 'three', 'id': 15, 'end_col': 6})
+     #" get text properties with a list of types
+     call assert_equal([
+           \ {'id': 10, 'col': 4, 'type_bufnr': 0, 'end': 1,
+           \  'type': 'two', 'length': 2, 'start': 1},
+           \ {'id': 5, 'col': 4, 'type_bufnr': 0, 'end': 1,
+           \  'type': 'one', 'length': 2, 'start': 1}],
+           \ prop_list(2, {'types': ['one', 'two']}))
+     call assert_equal([
+           \ {'id': 15, 'col': 4, 'type_bufnr': 0, 'end': 1,
+           \  'type': 'three', 'length': 2, 'start': 1},
+           \ {'id': 5, 'col': 4, 'type_bufnr': 0, 'end': 1,
+           \  'type': 'one', 'length': 2, 'start': 1}],
+           \ prop_list(2, {'types': ['one', 'three']}))
+     #" get text properties with a list of identifiers
+     call assert_equal([
+           \ {'id': 10, 'col': 4, 'type_bufnr': 0, 'end': 1,
+           \  'type': 'two', 'length': 2, 'start': 1},
+           \ {'id': 5, 'col': 4, 'type_bufnr': 0, 'end': 1,
+           \  'type': 'one', 'length': 2, 'start': 1}],
+           \ prop_list(2, {'ids': [5, 10, 20]}))
+     call prop_clear(1, line('$'))
+     call assert_equal([], prop_list(2, {'types': ['one', 'two']}))
+     call assert_equal([], prop_list(2, {'ids': [5, 10, 20]}))
+ 
+     #" get text properties from a hidden buffer
+     edit! Xaaa
+     call setline(1, repeat([repeat('b', 60)], 10))
+     call prop_add(1, 4, {'type': 'one', 'id': 5, 'end_col': 6})
+     call prop_add(4, 8, {'type': 'two', 'id': 10, 'end_col': 10})
+     VAR bnr = bufnr()
+     hide edit Xbbb
+     call assert_equal([
+           \ {'lnum': 1, 'id': 5, 'col': 4, 'type_bufnr': 0, 'end': 1,
+           \  'type': 'one', 'length': 2, 'start': 1},
+           \ {'lnum': 4, 'id': 10, 'col': 8, 'type_bufnr': 0, 'end': 1,
+           \  'type': 'two', 'length': 2, 'start': 1}],
+           \ prop_list(1, {'bufnr': bnr,
+           \ 'types': ['one', 'two'], 'ids': [5, 10], 'end_lnum': -1}))
+     #" get text properties from an unloaded buffer
+     bunload! Xaaa
+     call assert_equal([], prop_list(1, {'bufnr': bnr, 'end_lnum': -1}))
  
+     call DeletePropTypes()
+     :%bw!
+   END
+   call CheckLegacyAndVim9Success(lines)
+ endfunc
  
  " vim: shiftwidth=2 sts=2 expandtab
*** ../vim-8.2.3651/src/version.c       2021-11-22 21:58:37.918668436 +0000
--- src/version.c       2021-11-23 11:36:52.087495811 +0000
***************
*** 759,760 ****
--- 759,762 ----
  {   /* Add new patch number below this line */
+ /**/
+     3652,
  /**/

-- 
Every person is responsible for the choices he makes.

 /// 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/20211123114818.1DBA41C3E0C%40moolenaar.net.

Raspunde prin e-mail lui