changeset 19100:91bb12995034 v8.2.0110

patch 8.2.0110: prop_find() is not implemented Commit: https://github.com/vim/vim/commit/e05a89ac6399a8c7d164c99fdab6841d999a9128 Author: Bram Moolenaar <Bram@vim.org> Date: Fri Jan 10 19:56:46 2020 +0100 patch 8.2.0110: prop_find() is not implemented Problem: prop_find() is not implemented. Solution: Implement prop_find(). (Ryan Hackett, closes https://github.com/vim/vim/issues/5421, closes https://github.com/vim/vim/issues/4970)
author Bram Moolenaar <Bram@vim.org>
date Fri, 10 Jan 2020 20:00:04 +0100
parents 1a951a4beee3
children 7c682fab000b
files runtime/doc/textprop.txt src/evalfunc.c src/proto/textprop.pro src/testdir/test_textprop.vim src/textprop.c src/version.c
diffstat 6 files changed, 319 insertions(+), 31 deletions(-) [+]
line wrap: on
line diff
--- a/runtime/doc/textprop.txt
+++ b/runtime/doc/textprop.txt
@@ -154,8 +154,6 @@ prop_add({lnum}, {col}, {props})
 		added to. When not found, the global property types are used.
 		If not found an error is given.
 
-		See |text-properties| for information about text properties.
-
 		Can also be used as a |method|: >
 			GetLnum()->prop_add(col, props)
 
@@ -168,14 +166,11 @@ prop_clear({lnum} [, {lnum-end} [, {prop
 		When {props} contains a "bufnr" item use this buffer,
 		otherwise use the current buffer.
 
-		See |text-properties| for information about text properties.
-
 		Can also be used as a |method|: >
 			GetLnum()->prop_clear()
 <
 							*prop_find()*
 prop_find({props} [, {direction}])
-		{not implemented yet}
 		Search for a text property as specified with {props}:
 		   id		property with this ID
 		   type		property with this type name
@@ -198,8 +193,6 @@ prop_find({props} [, {direction}])
 		as with prop_list(), and additionally an "lnum" entry.
 		If no match is found then an empty Dict is returned.
 
-		See |text-properties| for information about text properties.
-
 
 prop_list({lnum} [, {props}])				*prop_list()*
 		Return a List with all text properties in line {lnum}.
@@ -223,8 +216,6 @@ 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.
 
-		See |text-properties| for information about text properties.
-
 		Can also be used as a |method|: >
 			GetLnum()->prop_list()
 <
@@ -248,8 +239,6 @@ prop_remove({props} [, {lnum} [, {lnum-e
 
 		Returns the number of properties that were removed.
 
-		See |text-properties| for information about text properties.
-
 		Can also be used as a |method|: >
 			GetProps()->prop_remove()
 
@@ -275,8 +264,6 @@ prop_type_add({name}, {props})		*prop_ty
 		   end_incl	when TRUE inserts at the end position will be
 				included in the text property
 
-		See |text-properties| for information about text properties.
-
 		Can also be used as a |method|: >
 			GetPropName()->prop_type_add(props)
 
@@ -285,8 +272,6 @@ prop_type_change({name}, {props})			*pro
 		property with this name does not exist an error is given.
 		The {props} argument is just like |prop_type_add()|.
 
-		See |text-properties| for information about text properties.
-
 		Can also be used as a |method|: >
 			GetPropName()->prop_type_change(props)
 
@@ -301,8 +286,6 @@ prop_type_delete({name} [, {props}])			*
 
 		When text property type {name} is not found there is no error.
 
-		See |text-properties| for information about text properties.
-
 		Can also be used as a |method|: >
 			GetPropName()->prop_type_delete()
 
@@ -316,8 +299,6 @@ prop_type_get([{name} [, {props}])			*pr
 		{props} can contain a "bufnr" item.  When it is given, use
 		this buffer instead of the global property types.
 
-		See |text-properties| for information about text properties.
-
 		Can also be used as a |method|: >
 			GetPropName()->prop_type_get()
 
@@ -327,8 +308,6 @@ prop_type_list([{props}])				*prop_type_
 		{props} can contain a "bufnr" item.  When it is given, use
 		this buffer instead of the global property types.
 
-		See |text-properties| for information about text properties.
-
 
 ==============================================================================
 3. When text changes				*text-prop-changes*
--- a/src/evalfunc.c
+++ b/src/evalfunc.c
@@ -620,6 +620,7 @@ static funcentry_T global_functions[] =
 #ifdef FEAT_PROP_POPUP
     {"prop_add",	3, 3, FEARG_1,	  f_prop_add},
     {"prop_clear",	1, 3, FEARG_1,	  f_prop_clear},
+    {"prop_find",	1, 2, FEARG_1,	  f_prop_find},
     {"prop_list",	1, 2, FEARG_1,	  f_prop_list},
     {"prop_remove",	1, 3, FEARG_1,	  f_prop_remove},
     {"prop_type_add",	2, 2, FEARG_1,	  f_prop_type_add},
--- a/src/proto/textprop.pro
+++ b/src/proto/textprop.pro
@@ -6,6 +6,7 @@ int get_text_props(buf_T *buf, linenr_T 
 int find_visible_prop(win_T *wp, int type_id, int id, textprop_T *prop, linenr_T *found_lnum);
 proptype_T *text_prop_type_by_id(buf_T *buf, int id);
 void f_prop_clear(typval_T *argvars, typval_T *rettv);
+void f_prop_find(typval_T *argvars, typval_T *rettv);
 void f_prop_list(typval_T *argvars, typval_T *rettv);
 void f_prop_remove(typval_T *argvars, typval_T *rettv);
 void f_prop_type_add(typval_T *argvars, typval_T *rettv);
--- a/src/testdir/test_textprop.vim
+++ b/src/testdir/test_textprop.vim
@@ -103,6 +103,118 @@ func Get_expected_props()
       \ ]
 endfunc
 
+func Test_prop_find()
+  new
+  call setline(1, ['one one one', 'twotwo', 'three', 'fourfour', 'five', 'sixsix'])
+ 
+  " Add two text props on lines 1 and 5, and one spanning lines 2 to 4. 
+  call prop_type_add('prop_name', {'highlight': 'Directory'})
+  call prop_add(1, 5, {'type': 'prop_name', 'id': 10, 'length': 3})
+  call prop_add(2, 4, {'type': 'prop_name', 'id': 11, 'end_lnum': 4, 'end_col': 9})
+  call prop_add(5, 4, {'type': 'prop_name', 'id': 12, 'length': 1})
+
+  let expected = [
+    \ {'lnum': 1, 'col': 5, 'length': 3, 'id': 10, 'type': 'prop_name', 'start': 1, 'end': 1},
+    \ {'lnum': 2, 'col': 4, 'id': 11, 'type': 'prop_name', 'start': 1, 'end': 0},
+    \ {'lnum': 5, 'col': 4, 'length': 1, 'id': 12, 'type': 'prop_name', 'start': 1, 'end': 1}
+    \ ]
+
+  " Starting at line 5 col 1 this should find the prop at line 5 col 4.
+  call cursor(5,1)
+  let result = prop_find({'type': 'prop_name'}, 'f')
+  call assert_equal(expected[2], result)
+
+  " With skipstart left at false (default), this should find the prop at line
+  " 5 col 4.
+  let result = prop_find({'type': 'prop_name', 'lnum': 5, 'col': 4}, 'b')
+  call assert_equal(expected[2], result)
+
+  " With skipstart set to true, this should skip the prop at line 5 col 4.
+  let result = prop_find({'type': 'prop_name', 'lnum': 5, 'col': 4, 'skipstart': 1}, 'b')
+  unlet result.length
+  call assert_equal(expected[1], result)
+
+  " Search backwards from line 1 col 10 to find the prop on the same line.
+  let result = prop_find({'type': 'prop_name', 'lnum': 1, 'col': 10}, 'b')
+  call assert_equal(expected[0], result)
+
+  " with skipstart set to false, if the start position is anywhere between the
+  " start and end lines of a text prop (searching forward or backward), the
+  " result should be the prop on the first line (the line with 'start' set to 1).
+  call cursor(3,1)
+  let result = prop_find({'type': 'prop_name'}, 'f')
+  unlet result.length
+  call assert_equal(expected[1], result)
+  let result = prop_find({'type': 'prop_name'}, 'b')
+  unlet result.length
+  call assert_equal(expected[1], result)
+
+  " with skipstart set to true, if the start position is anywhere between the
+  " start and end lines of a text prop (searching forward or backward), all lines
+  " of the prop will be skipped.
+  let result = prop_find({'type': 'prop_name', 'skipstart': 1}, 'b')
+  call assert_equal(expected[0], result)
+  let result = prop_find({'type': 'prop_name', 'skipstart': 1}, 'f')
+  call assert_equal(expected[2], result)
+
+  " Use skipstart to search through all props with type name 'prop_name'.
+  " First forward...
+  let lnum = 1
+  let col = 1
+  let i = 0
+  for exp in expected
+    let result = prop_find({'type': 'prop_name', 'lnum': lnum, 'col': col, 'skipstart': 1}, 'f')
+    if !has_key(exp, "length")
+      unlet result.length
+    endif
+    call assert_equal(exp, result)
+    let lnum = result.lnum
+    let col = result.col
+    let i = i + 1
+  endfor
+
+  " ...then backwards.
+  let lnum = 6
+  let col = 4
+  let i = 2
+  while i >= 0
+    let result = prop_find({'type': 'prop_name', 'lnum': lnum, 'col': col, 'skipstart': 1}, 'b')
+    if !has_key(expected[i], "length")
+      unlet result.length
+    endif
+    call assert_equal(expected[i], result)
+    let lnum = result.lnum
+    let col = result.col
+    let i = i - 1
+  endwhile 
+
+  " Starting from line 6 col 1 search backwards for prop with id 10.
+  call cursor(6,1)
+  let result = prop_find({'id': 10, 'skipstart': 1}, 'b')
+  call assert_equal(expected[0], result)
+
+  " Starting from line 1 col 1 search forwards for prop with id 12.
+  call cursor(1,1)
+  let result = prop_find({'id': 12}, 'f')
+  call assert_equal(expected[2], result)
+
+  " Search for a prop with an unknown id.
+  let result = prop_find({'id': 999}, 'f')
+  call assert_equal({}, result)
+
+  " Search backwards from the proceeding position of the prop with id 11
+  " (at line num 2 col 4). This should return an empty dict.
+  let result = prop_find({'id': 11, 'lnum': 2, 'col': 3}, 'b')
+  call assert_equal({}, result)
+
+  " When lnum is given and col is omitted, use column 1.
+  let result = prop_find({'type': 'prop_name', 'lnum': 1}, 'f')
+  call assert_equal(expected[0], result)
+
+  call prop_clear(1,6)
+  call prop_type_delete('prop_name')
+endfunc
+
 func Test_prop_add()
   new
   call AddPropTypes()
--- a/src/textprop.c
+++ b/src/textprop.c
@@ -469,6 +469,24 @@ find_type_by_id(hashtab_T *ht, int id)
 }
 
 /*
+ * Fill 'dict' with text properties in 'prop'.
+ */
+    static void
+prop_fill_dict(dict_T *dict, textprop_T *prop, buf_T *buf)
+{
+    proptype_T *pt;
+
+    dict_add_number(dict, "col", prop->tp_col);
+    dict_add_number(dict, "length", prop->tp_len);
+    dict_add_number(dict, "id", prop->tp_id);
+    dict_add_number(dict, "start", !(prop->tp_flags & TP_FLAG_CONT_PREV));
+    dict_add_number(dict, "end", !(prop->tp_flags & TP_FLAG_CONT_NEXT));
+    pt = text_prop_type_by_id(buf, prop->tp_type);
+    if (pt != NULL)
+	dict_add_string(dict, "type", pt->pt_name);
+}
+
+/*
  * Find a property type by ID in "buf" or globally.
  * Returns NULL if not found.
  */
@@ -537,6 +555,190 @@ f_prop_clear(typval_T *argvars, typval_T
 }
 
 /*
+ * prop_find({props} [, {direction}])
+ */
+    void
+f_prop_find(typval_T *argvars, typval_T *rettv)
+{
+    pos_T       *cursor = &curwin->w_cursor;
+    dict_T      *dict;
+    buf_T       *buf = curbuf;
+    dictitem_T  *di;
+    int         lnum_start;
+    int         start_pos_has_prop = 0;
+    int         seen_end = 0;
+    int         id = -1;
+    int         type_id = -1;
+    int         skipstart = 0;
+    int         lnum = -1;
+    int         col = -1;
+    int         dir = 1;    // 1 = forward, -1 = backward
+
+    if (argvars[0].v_type != VAR_DICT || argvars[0].vval.v_dict == NULL)
+    {
+	emsg(_(e_invarg));
+	return;
+    }
+    dict = argvars[0].vval.v_dict;
+
+    if (get_bufnr_from_arg(&argvars[0], &buf) == FAIL)
+	return;
+    if (buf->b_ml.ml_mfp == NULL)
+	return;
+
+    if (argvars[1].v_type != VAR_UNKNOWN)
+    {
+	char_u      *dir_s = tv_get_string(&argvars[1]);
+
+	if (*dir_s == 'b')
+	    dir = -1;
+	else if (*dir_s != 'f')
+	{
+	    emsg(_(e_invarg));
+	    return;
+	}
+    }
+
+    di = dict_find(dict, (char_u *)"lnum", -1);
+    if (di != NULL)
+	lnum = tv_get_number(&di->di_tv);
+
+    di = dict_find(dict, (char_u *)"col", -1);
+    if (di != NULL)
+	col = tv_get_number(&di->di_tv);
+
+    if (lnum == -1)
+    {
+	lnum = cursor->lnum;
+	col = cursor->col + 1;
+    }
+    else if (col == -1)
+	col = 1;
+
+    if (lnum < 1 || lnum > buf->b_ml.ml_line_count)
+    {
+	emsg(_(e_invrange));
+	return;
+    }
+
+    di = dict_find(dict, (char_u *)"skipstart", -1);
+    if (di != NULL)
+	skipstart = tv_get_number(&di->di_tv);
+
+    if (dict_find(dict, (char_u *)"id", -1) != NULL)
+	id = dict_get_number(dict, (char_u *)"id");
+    if (dict_find(dict, (char_u *)"type", -1))
+    {
+	char_u	    *name = dict_get_string(dict, (char_u *)"type", FALSE);
+	proptype_T  *type = lookup_prop_type(name, buf);
+
+	if (type == NULL)
+	    return;
+	type_id = type->pt_id;
+    }
+    if (id == -1 && type_id == -1)
+    {
+	emsg(_("E968: Need at least one of 'id' or 'type'"));
+	return;
+    }
+
+    lnum_start = lnum;
+
+    if (rettv_dict_alloc(rettv) == FAIL)
+	return;
+
+    while (1)
+    {
+	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;
+	int	    prop_start;
+	int	    prop_end;
+
+	for (i = 0; i < count; ++i)
+	{
+	    mch_memmove(&prop, text + textlen + i * sizeof(textprop_T),
+			    sizeof(textprop_T));
+
+	    if (prop.tp_id == id || prop.tp_type == type_id)
+	    {
+		// Check if the starting position has text props.
+		if (lnum_start == lnum)
+		{
+		    if (col >= prop.tp_col
+				       && (col <= prop.tp_col + prop.tp_len-1))
+			start_pos_has_prop = 1;
+		}
+		else
+		{
+		    // Not at the first line of the search so adjust col to
+		    // indicate that we're continuing from prev/next line.
+		    if (dir < 0)
+			col = buf->b_ml.ml_line_len;
+		    else
+			col = 1;
+		}
+
+		prop_start = !(prop.tp_flags & TP_FLAG_CONT_PREV);
+		prop_end = !(prop.tp_flags & TP_FLAG_CONT_NEXT);
+		if (!prop_start && prop_end && dir > 0)
+		    seen_end = 1;
+
+		// Skip lines without the start flag.
+		if (!prop_start)
+		{
+		    // Always search backwards for start when search started
+		    // on a prop and we're not skipping.
+		    if (start_pos_has_prop && !skipstart)
+			dir = -1;
+		    break;
+		}
+
+		// If skipstart is true, skip the prop at start pos (even if
+		// continued from another line).
+		if (start_pos_has_prop && skipstart && !seen_end)
+		{
+		    start_pos_has_prop = 0;
+		    break;
+		}
+
+		if (dir < 0)
+		{
+		    if (col < prop.tp_col)
+			break;
+		}
+		else
+		{
+		    if (col > prop.tp_col + prop.tp_len-1)
+			break;
+		}
+
+		prop_fill_dict(rettv->vval.v_dict, &prop, buf);
+		dict_add_number(rettv->vval.v_dict, "lnum", lnum);
+
+		return;
+	    }
+	}
+
+	if (dir > 0)
+	{
+	    if (lnum >= buf->b_ml.ml_line_count)
+		break;
+	    lnum++;
+	}
+	else
+	{
+	    if (lnum <= 1)
+		break;
+	    lnum--;
+	}
+    }
+}
+
+/*
  * prop_list({lnum} [, {bufnr}])
  */
     void
@@ -564,7 +766,6 @@ f_prop_list(typval_T *argvars, typval_T 
 							 / sizeof(textprop_T));
 	int	    i;
 	textprop_T  prop;
-	proptype_T  *pt;
 
 	for (i = 0; i < count; ++i)
 	{
@@ -574,15 +775,7 @@ f_prop_list(typval_T *argvars, typval_T 
 		break;
 	    mch_memmove(&prop, text + textlen + i * sizeof(textprop_T),
 							   sizeof(textprop_T));
-	    dict_add_number(d, "col", prop.tp_col);
-	    dict_add_number(d, "length", prop.tp_len);
-	    dict_add_number(d, "id", prop.tp_id);
-	    dict_add_number(d, "start", !(prop.tp_flags & TP_FLAG_CONT_PREV));
-	    dict_add_number(d, "end", !(prop.tp_flags & TP_FLAG_CONT_NEXT));
-	    pt = text_prop_type_by_id(buf, prop.tp_type);
-	    if (pt != NULL)
-		dict_add_string(d, "type", pt->pt_name);
-
+	    prop_fill_dict(d, &prop, buf);
 	    list_append_dict(rettv->vval.v_list, d);
 	}
     }
--- a/src/version.c
+++ b/src/version.c
@@ -743,6 +743,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    110,
+/**/
     109,
 /**/
     108,