changeset 15251:17525ca95e1e v8.1.0634

patch 8.1.0634: text properties cannot cross line boundaries commit https://github.com/vim/vim/commit/e3d31b02a56710e64ef0c1eb6ac5afcc57cb4890 Author: Bram Moolenaar <Bram@vim.org> Date: Mon Dec 24 23:07:04 2018 +0100 patch 8.1.0634: text properties cannot cross line boundaries Problem: Text properties cannot cross line boundaries. Solution: Support multi-line text properties.
author Bram Moolenaar <Bram@vim.org>
date Mon, 24 Dec 2018 23:15:05 +0100
parents 82208e2fbdf3
children fdde04678443
files runtime/doc/eval.txt src/testdir/test_textprop.vim src/textprop.c src/version.c
diffstat 4 files changed, 147 insertions(+), 63 deletions(-) [+]
line wrap: on
line diff
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -1,4 +1,4 @@
-*eval.txt*	For Vim version 8.1.  Last change: 2018 Dec 18
+*eval.txt*	For Vim version 8.1.  Last change: 2018 Dec 24
 
 
 		  VIM REFERENCE MANUAL	  by Bram Moolenaar
@@ -2318,7 +2318,7 @@ prompt_setcallback({buf}, {expr}) none	s
 prompt_setinterrupt({buf}, {text}) none	set prompt interrupt function
 prompt_setprompt({buf}, {text}) none	set prompt text
 prop_add({lnum}, {col}, {props})  none	add a text property
-prop_clear({lnum} [, {lnum-end} [, {bufnr}]])
+prop_clear({lnum} [, {lnum-end} [, {props}]])
 				none	remove all text properties
 prop_find({props} [, {direction}])
 				Dict	search for a text property
@@ -6695,7 +6695,7 @@ prop_add({lnum}, {col}, {props})
 				     used for a property that does not
 				     continue in another line
 			"end_lnum" - line number for end of text
-			"end_col"  - column for end of text; not used when
+			"end_col"  - last column of the text; not used when
 				     "length" is present
 			"bufnr"	   - buffer to add the property to; when
 				     omitted the current buffer is used
@@ -6710,6 +6710,10 @@ prop_add({lnum}, {col}, {props})
 		property that spans more than one line.
 		When neither "length" nor "end_col" are passed the property
 		will apply to one character.
+		The property can end exactly at the last character of the
+		text, or just after it.  In the last case, if text is appended
+		to the line, the text property size will increase, also when
+		the property type does not have "end_incl" set.
 
 		"type" will first be looked up in the buffer the property is
 		added to. When not found, the global property types are used.
--- a/src/testdir/test_textprop.vim
+++ b/src/testdir/test_textprop.vim
@@ -197,4 +197,34 @@ func Test_prop_clear_buf()
   bwipe!
 endfunc
 
+func Test_prop_multiline()
+  call prop_type_add('comment', {'highlight': 'Directory'})
+  new
+  call setline(1, ['xxxxxxx', 'yyyyyyyyy', 'zzzzzzzz'])
+
+  " start halfway line 1, end halfway line 3
+  call prop_add(1, 3, {'end_lnum': 3, 'end_col': 5, 'type': 'comment'})
+  let expect1 = {'col': 3, 'length': 6, 'type': 'comment', 'start': 1, 'end': 0, 'id': 0}
+  call assert_equal([expect1], prop_list(1))
+  let expect2 = {'col': 1, 'length': 10, 'type': 'comment', 'start': 0, 'end': 0, 'id': 0}
+  call assert_equal([expect2], prop_list(2))
+  let expect3 = {'col': 1, 'length': 5, 'type': 'comment', 'start': 0, 'end': 1, 'id': 0}
+  call assert_equal([expect3], prop_list(3))
+  call prop_clear(1, 3)
+
+  " include all three lines
+  call prop_add(1, 1, {'end_lnum': 3, 'end_col': 999, 'type': 'comment'})
+  let expect1.col = 1
+  let expect1.length = 8
+  call assert_equal([expect1], prop_list(1))
+  call assert_equal([expect2], prop_list(2))
+  let expect3.length = 9
+  call assert_equal([expect3], prop_list(3))
+  call prop_clear(1, 3)
+
+  bwipe!
+  call prop_type_delete('comment')
+endfunc
+
+
 " TODO: screenshot test with highlighting
--- a/src/textprop.c
+++ b/src/textprop.c
@@ -17,10 +17,12 @@
  * Text properties have a type, which can be used to specify highlighting.
  *
  * TODO:
+ * - When deleting a line where a prop ended, adjust flag of previous line.
+ * - When deleting a line where a prop started, adjust flag of next line.
+ * - When inserting a line add props that continue from previous line.
+ * - Adjust property column and length when text is inserted/deleted
  * - Add an arrray for global_proptypes, to quickly lookup a proptype by ID
  * - Add an arrray for b_proptypes, to quickly lookup a proptype by ID
- * - adjust property column when text is inserted/deleted
- * - support properties that continue over a line break
  * - add mechanism to keep track of changed lines.
  */
 
@@ -47,6 +49,7 @@ static int proptype_id = 0;
 
 static char_u e_type_not_exist[] = N_("E971: Property type %s does not exist");
 static char_u e_invalid_col[] = N_("E964: Invalid column number: %ld");
+static char_u e_invalid_lnum[] = N_("E966: Invalid line number: %ld");
 
 /*
  * Find a property type by name, return the hashitem.
@@ -139,9 +142,11 @@ get_bufnr_from_arg(typval_T *arg, buf_T 
 f_prop_add(typval_T *argvars, typval_T *rettv UNUSED)
 {
     linenr_T	lnum;
-    colnr_T	col;
+    linenr_T	start_lnum;
+    linenr_T	end_lnum;
+    colnr_T	start_col;
+    colnr_T	end_col;
     dict_T	*dict;
-    colnr_T	length = 1;
     char_u	*type_name;
     proptype_T	*type;
     buf_T	*buf = curbuf;
@@ -154,11 +159,11 @@ f_prop_add(typval_T *argvars, typval_T *
     textprop_T	tmp_prop;
     int		i;
 
-    lnum = tv_get_number(&argvars[0]);
-    col = tv_get_number(&argvars[1]);
-    if (col < 1)
+    start_lnum = tv_get_number(&argvars[0]);
+    start_col = tv_get_number(&argvars[1]);
+    if (start_col < 1)
     {
-	EMSGN(_(e_invalid_col), (long)col);
+	EMSGN(_(e_invalid_col), (long)start_col);
 	return;
     }
     if (argvars[2].v_type != VAR_DICT)
@@ -177,22 +182,40 @@ f_prop_add(typval_T *argvars, typval_T *
 
     if (dict_find(dict, (char_u *)"end_lnum", -1) != NULL)
     {
-	// TODO: handle end_lnum
-	EMSG("Sorry, end_lnum not supported yet");
-	return;
+	end_lnum = dict_get_number(dict, (char_u *)"end_lnum");
+	if (end_lnum < start_lnum)
+	{
+	    EMSG2(_(e_invargval), "end_lnum");
+	    return;
+	}
     }
+    else
+	end_lnum = start_lnum;
 
     if (dict_find(dict, (char_u *)"length", -1) != NULL)
-	length = dict_get_number(dict, (char_u *)"length");
+    {
+	long length = dict_get_number(dict, (char_u *)"length");
+
+	if (length < 1 || end_lnum > start_lnum)
+	{
+	    EMSG2(_(e_invargval), "length");
+	    return;
+	}
+	end_col = start_col + length - 1;
+    }
     else if (dict_find(dict, (char_u *)"end_col", -1) != NULL)
     {
-	length = dict_get_number(dict, (char_u *)"end_col") - col;
-	if (length <= 0)
+	end_col = dict_get_number(dict, (char_u *)"end_col");
+	if (end_col <= 0)
 	{
 	    EMSG2(_(e_invargval), "end_col");
 	    return;
 	}
     }
+    else if (start_lnum == end_lnum)
+	end_col = start_col;
+    else
+	end_col = 1;
 
     if (dict_find(dict, (char_u *)"id", -1) != NULL)
 	id = dict_get_number(dict, (char_u *)"id");
@@ -204,61 +227,86 @@ f_prop_add(typval_T *argvars, typval_T *
     if (type == NULL)
 	return;
 
-    if (lnum < 1 || lnum > buf->b_ml.ml_line_count)
+    if (start_lnum < 1 || start_lnum > buf->b_ml.ml_line_count)
     {
-	EMSGN(_("E966: Invalid line number: %ld"), (long)lnum);
+	EMSGN(_(e_invalid_lnum), (long)start_lnum);
 	return;
     }
-
-    // Fetch the line to get the ml_line_len field updated.
-    proplen = get_text_props(buf, lnum, &props, TRUE);
-    textlen = buf->b_ml.ml_line_len - proplen * sizeof(textprop_T);
-
-    if (col >= (colnr_T)textlen - 1)
+    if (end_lnum < start_lnum || end_lnum > buf->b_ml.ml_line_count)
     {
-	EMSGN(_(e_invalid_col), (long)col);
+	EMSGN(_(e_invalid_lnum), (long)end_lnum);
 	return;
     }
 
-    // Allocate the new line with space for the new proprety.
-    newtext = alloc(buf->b_ml.ml_line_len + sizeof(textprop_T));
-    if (newtext == NULL)
-	return;
-    // Copy the text, including terminating NUL.
-    mch_memmove(newtext, buf->b_ml.ml_line_ptr, textlen);
+    for (lnum = start_lnum; lnum <= end_lnum; ++lnum)
+    {
+	colnr_T col;	// start column
+	long	length;	// in bytes
+
+	// Fetch the line to get the ml_line_len field updated.
+	proplen = get_text_props(buf, lnum, &props, TRUE);
+	textlen = buf->b_ml.ml_line_len - proplen * sizeof(textprop_T);
+
+	if (lnum == start_lnum)
+	    col = start_col;
+	else
+	    col = 1;
+	if (col - 1 > (colnr_T)textlen)
+	{
+	    EMSGN(_(e_invalid_col), (long)start_col);
+	    return;
+	}
+
+	if (lnum == end_lnum)
+	    length = end_col - col + 1;
+	else
+	    length = textlen - col + 1;
+	if (length > textlen)
+	    length = textlen;  // can include the end-of-line
+	if (length < 1)
+	    length = 1;
 
-    // Find the index where to insert the new property.
-    // Since the text properties are not aligned properly when stored with the
-    // text, we need to copy them as bytes before using it as a struct.
-    for (i = 0; i < proplen; ++i)
-    {
-	mch_memmove(&tmp_prop, props + i * sizeof(textprop_T),
-							   sizeof(textprop_T));
-	if (tmp_prop.tp_col >= col)
-	    break;
+	// Allocate the new line with space for the new proprety.
+	newtext = alloc(buf->b_ml.ml_line_len + sizeof(textprop_T));
+	if (newtext == NULL)
+	    return;
+	// Copy the text, including terminating NUL.
+	mch_memmove(newtext, buf->b_ml.ml_line_ptr, textlen);
+
+	// Find the index where to insert the new property.
+	// Since the text properties are not aligned properly when stored with the
+	// text, we need to copy them as bytes before using it as a struct.
+	for (i = 0; i < proplen; ++i)
+	{
+	    mch_memmove(&tmp_prop, props + i * sizeof(textprop_T),
+							       sizeof(textprop_T));
+	    if (tmp_prop.tp_col >= col)
+		break;
+	}
+	newprops = newtext + textlen;
+	if (i > 0)
+	    mch_memmove(newprops, props, sizeof(textprop_T) * i);
+
+	tmp_prop.tp_col = col;
+	tmp_prop.tp_len = length;
+	tmp_prop.tp_id = id;
+	tmp_prop.tp_type = type->pt_id;
+	tmp_prop.tp_flags = (lnum > start_lnum ? TP_FLAG_CONT_PREV : 0)
+			  | (lnum < end_lnum ? TP_FLAG_CONT_NEXT : 0);
+	mch_memmove(newprops + i * sizeof(textprop_T), &tmp_prop,
+							       sizeof(textprop_T));
+
+	if (i < proplen)
+	    mch_memmove(newprops + (i + 1) * sizeof(textprop_T),
+					    props + i * sizeof(textprop_T),
+					    sizeof(textprop_T) * (proplen - i));
+
+	if (buf->b_ml.ml_flags & ML_LINE_DIRTY)
+	    vim_free(buf->b_ml.ml_line_ptr);
+	buf->b_ml.ml_line_ptr = newtext;
+	buf->b_ml.ml_line_len += sizeof(textprop_T);
+	buf->b_ml.ml_flags |= ML_LINE_DIRTY;
     }
-    newprops = newtext + textlen;
-    if (i > 0)
-	mch_memmove(newprops, props, sizeof(textprop_T) * i);
-
-    tmp_prop.tp_col = col;
-    tmp_prop.tp_len = length;
-    tmp_prop.tp_id = id;
-    tmp_prop.tp_type = type->pt_id;
-    tmp_prop.tp_flags = 0;
-    mch_memmove(newprops + i * sizeof(textprop_T), &tmp_prop,
-							   sizeof(textprop_T));
-
-    if (i < proplen)
-	mch_memmove(newprops + (i + 1) * sizeof(textprop_T),
-					props + i * sizeof(textprop_T),
-					sizeof(textprop_T) * (proplen - i));
-
-    if (buf->b_ml.ml_flags & ML_LINE_DIRTY)
-	vim_free(buf->b_ml.ml_line_ptr);
-    buf->b_ml.ml_line_ptr = newtext;
-    buf->b_ml.ml_line_len += sizeof(textprop_T);
-    buf->b_ml.ml_flags |= ML_LINE_DIRTY;
 
     redraw_buf_later(buf, NOT_VALID);
 }
--- a/src/version.c
+++ b/src/version.c
@@ -800,6 +800,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    634,
+/**/
     633,
 /**/
     632,