]> granicus.if.org Git - vim/commitdiff
patch 8.2.3652: can only get text properties one line at a time v8.2.3652
authorYegappan Lakshmanan <yegappan@yahoo.com>
Tue, 23 Nov 2021 11:46:32 +0000 (11:46 +0000)
committerBram Moolenaar <Bram@vim.org>
Tue, 23 Nov 2021 11:46:32 +0000 (11:46 +0000)
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)

runtime/doc/textprop.txt
src/testdir/test_textprop.vim
src/textprop.c
src/version.c

index 56f7619d4f96295dedfa2b4a4701b951d09d522a..ed0fa5ef245ab60e52e4c645308a2788c647ac73 100644 (file)
@@ -1,4 +1,4 @@
-*textprop.txt*  For Vim version 8.2.  Last change: 2021 Aug 16
+*textprop.txt*  For Vim version 8.2.  Last change: 2021 Nov 23
 
 
                  VIM REFERENCE MANUAL    by Bram Moolenaar
@@ -230,13 +230,25 @@ prop_find({props} [, {direction}])
 
 
 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.
+               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,6 +265,30 @@ prop_list({lnum} [, {props}])                              *prop_list()*
                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()
 <
index 4b587846935293976acf236002f42b870626e810..4a12b63d69586cf47b3e3793fa695a7dd1c13daa 100644 (file)
@@ -5,6 +5,7 @@ source check.vim
 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,6 +1646,154 @@ def Test_prop_bufnr_zero()
   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
index 26c7f52084169d1f2297caf7445242a2a6d452e3..beb82a35b5119224c97646c622feddec1a7f039d 100644 (file)
@@ -896,53 +896,260 @@ f_prop_find(typval_T *argvars, typval_T *rettv)
     }
 }
 
+/*
+ * 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;
-    buf_T    *buf = curbuf;
+    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;
 
-    lnum = tv_get_number(&argvars[0]);
+    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 (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;
+       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;
+           }
 
-       for (i = 0; i < count; ++i)
+           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)
        {
-           dict_T *d = dict_alloc();
+           if (di->di_tv.v_type != VAR_LIST)
+           {
+               emsg(_(e_listreq));
+               goto errret;
+           }
 
-           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);
+           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);
 }
 
 /*
index f2bcb190f3dd042c14cd9d5420f7e6fa0470cebe..0644ba2226b67d6a098861ae26b95db573421568 100644 (file)
@@ -757,6 +757,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    3652,
 /**/
     3651,
 /**/