changeset 26242:685206b54ecf v8.2.3652

patch 8.2.3652: can only get text properties one line at a time Commit: https://github.com/vim/vim/commit/e021662f39b38ef7cf27e13850d0ce6890e48376 Author: Yegappan Lakshmanan <yegappan@yahoo.com> Date: Tue Nov 23 11:46:32 2021 +0000 patch 8.2.3652: can only get text properties one line at a time 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)
author Bram Moolenaar <Bram@vim.org>
date Tue, 23 Nov 2021 13:00:05 +0100
parents ae5abfa1efc8
children 80035b0c5ca8
files runtime/doc/textprop.txt src/testdir/test_textprop.vim src/textprop.c src/version.c
diffstat 4 files changed, 423 insertions(+), 29 deletions(-) [+]
line wrap: on
line diff
--- a/runtime/doc/textprop.txt
+++ b/runtime/doc/textprop.txt
@@ -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}.
+		Returns a List with all the text properties in line {lnum}.
 
-		When {props} contains a "bufnr" item, use this buffer instead
-		of the current buffer.
+		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_l
 		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()
 <
--- a/src/testdir/test_textprop.vim
+++ b/src/testdir/test_textprop.vim
@@ -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
--- a/src/textprop.c
+++ b/src/textprop.c
@@ -897,52 +897,259 @@ f_prop_find(typval_T *argvars, typval_T 
 }
 
 /*
+ * 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);
 }
 
 /*
--- a/src/version.c
+++ b/src/version.c
@@ -758,6 +758,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    3652,
+/**/
     3651,
 /**/
     3650,