changeset 15292:ba6f0f1bb9d0 v8.1.0654

patch 8.1.0654: when deleting a line text property flags are not adjusted commit https://github.com/vim/vim/commit/c1a9bc1a7284bd0e60f9bddfef6a4ee733bfc838 Author: Bram Moolenaar <Bram@vim.org> Date: Fri Dec 28 21:59:29 2018 +0100 patch 8.1.0654: when deleting a line text property flags are not adjusted Problem: When deleting a line text property flags are not adjusted. Solution: Adjust text property flags in preceding and following lines.
author Bram Moolenaar <Bram@vim.org>
date Fri, 28 Dec 2018 22:00:05 +0100
parents 11c4950be418
children 0613129efe93
files src/memline.c src/misc2.c src/proto/misc2.pro src/testdir/test_textprop.vim src/version.c
diffstat 5 files changed, 193 insertions(+), 10 deletions(-) [+]
line wrap: on
line diff
--- a/src/memline.c
+++ b/src/memline.c
@@ -3214,6 +3214,104 @@ ml_replace_len(linenr_T lnum, char_u *li
     return OK;
 }
 
+#ifdef FEAT_TEXT_PROP
+/*
+ * Adjust text properties in line "lnum" for a deleted line.
+ * When "above" is true this is the line above the deleted line.
+ * "del_props" are the properties of the deleted line.
+ */
+    static void
+adjust_text_props_for_delete(
+	buf_T	    *buf,
+	linenr_T    lnum,
+	char_u	    *del_props,
+	int	    del_props_len,
+	int	    above)
+{
+    int		did_get_line = FALSE;
+    int		done_del;
+    int		done_this;
+    textprop_T	prop_del;
+    textprop_T	prop_this;
+    bhdr_T	*hp;
+    DATA_BL	*dp;
+    int		idx;
+    int		line_start;
+    long	line_size;
+    int		this_props_len;
+    char_u	*text;
+    size_t	textlen;
+    int		found;
+
+    for (done_del = 0; done_del < del_props_len; done_del += sizeof(textprop_T))
+    {
+	mch_memmove(&prop_del, del_props + done_del, sizeof(textprop_T));
+	if ((above && (prop_del.tp_flags & TP_FLAG_CONT_PREV)
+		    && !(prop_del.tp_flags & TP_FLAG_CONT_NEXT))
+		|| (!above && (prop_del.tp_flags & TP_FLAG_CONT_NEXT)
+		    && !(prop_del.tp_flags & TP_FLAG_CONT_PREV)))
+	{
+	    if (!did_get_line)
+	    {
+		did_get_line = TRUE;
+		if ((hp = ml_find_line(buf, lnum, ML_FIND)) == NULL)
+		    return;
+
+		dp = (DATA_BL *)(hp->bh_data);
+		idx = lnum - buf->b_ml.ml_locked_low;
+		line_start = ((dp->db_index[idx]) & DB_INDEX_MASK);
+		if (idx == 0)		// first line in block, text at the end
+		    line_size = dp->db_txt_end - line_start;
+		else
+		    line_size = ((dp->db_index[idx - 1]) & DB_INDEX_MASK) - line_start;
+		text = (char_u *)dp + line_start;
+		textlen = STRLEN(text) + 1;
+		if ((long)textlen >= line_size)
+		{
+		    if (above)
+			internal_error("no text property above deleted line");
+		    else
+			internal_error("no text property below deleted line");
+		    return;
+		}
+		this_props_len = line_size - textlen;
+	    }
+
+	    found = FALSE;
+	    for (done_this = 0; done_this < this_props_len; done_this += sizeof(textprop_T))
+	    {
+		mch_memmove(&prop_this, text + textlen + done_del, sizeof(textprop_T));
+		if (prop_del.tp_id == prop_this.tp_id
+			&& prop_del.tp_type == prop_this.tp_type)
+		{
+		    int flag = above ? TP_FLAG_CONT_NEXT : TP_FLAG_CONT_PREV;
+
+		    found = TRUE;
+		    if (prop_this.tp_flags & flag)
+		    {
+			prop_this.tp_flags &= ~flag;
+			mch_memmove(text + textlen + done_del, &prop_this, sizeof(textprop_T));
+		    }
+		    else if (above)
+			internal_error("text property above deleted line does not continue");
+		    else
+			internal_error("text property below deleted line does not continue");
+		}
+	    }
+	    if (!found)
+	    {
+		if (above)
+		    internal_error("text property above deleted line not found");
+		else
+		    internal_error("text property below deleted line not found");
+	    }
+
+	    buf->b_ml.ml_flags |= (ML_LOCKED_DIRTY | ML_LOCKED_POS);
+	}
+    }
+}
+#endif
+
 /*
  * Delete line "lnum" in the current buffer.
  * When "message" is TRUE may give a "No lines in buffer" message.
@@ -3245,6 +3343,11 @@ ml_delete_int(buf_T *buf, linenr_T lnum,
     int		line_start;
     long	line_size;
     int		i;
+    int		ret = FAIL;
+#ifdef FEAT_TEXT_PROP
+    char_u	*textprop_save = NULL;
+    int		textprop_save_len;
+#endif
 
     if (lnum < 1 || lnum > buf->b_ml.ml_line_count)
 	return FAIL;
@@ -3272,9 +3375,9 @@ ml_delete_int(buf_T *buf, linenr_T lnum,
     }
 
 /*
- * find the data block containing the line
- * This also fills the stack with the blocks from the root to the data block
- * This also releases any locked block.
+ * Find the data block containing the line.
+ * This also fills the stack with the blocks from the root to the data block.
+ * This also releases any locked block..
  */
     mfp = buf->b_ml.ml_mfp;
     if (mfp == NULL)
@@ -3301,6 +3404,21 @@ ml_delete_int(buf_T *buf, linenr_T lnum,
     if (netbeans_active())
 	netbeans_removed(buf, lnum, 0, (long)line_size);
 #endif
+#ifdef FEAT_TEXT_PROP
+    // If there are text properties, make a copy, so that we can update
+    // properties in preceding and following lines.
+    if (buf->b_has_textprop)
+    {
+	size_t	textlen = STRLEN((char_u *)dp + line_start) + 1;
+
+	if ((long)textlen < line_size)
+	{
+	    textprop_save_len = line_size - textlen;
+	    textprop_save = vim_memsave((char_u *)dp + line_start + textlen,
+							  textprop_save_len);
+	}
+    }
+#endif
 
 /*
  * special case: If there is only one line in the data block it becomes empty.
@@ -3322,13 +3440,13 @@ ml_delete_int(buf_T *buf, linenr_T lnum,
 	    ip = &(buf->b_ml.ml_stack[stack_idx]);
 	    idx = ip->ip_index;
 	    if ((hp = mf_get(mfp, ip->ip_bnum, 1)) == NULL)
-		return FAIL;
+		goto theend;
 	    pp = (PTR_BL *)(hp->bh_data);   /* must be pointer block */
 	    if (pp->pb_id != PTR_ID)
 	    {
 		IEMSG(_("E317: pointer block id wrong 4"));
 		mf_put(mfp, hp, FALSE, FALSE);
-		return FAIL;
+		goto theend;
 	    }
 	    count = --(pp->pb_count);
 	    if (count == 0)	    /* the pointer block becomes empty! */
@@ -3384,11 +3502,25 @@ ml_delete_int(buf_T *buf, linenr_T lnum,
 #ifdef FEAT_BYTEOFF
     ml_updatechunk(buf, lnum, line_size, ML_CHNK_DELLINE);
 #endif
-    return OK;
+    ret = OK;
+
+theend:
+#ifdef FEAT_TEXT_PROP
+    if (textprop_save != NULL)
+    {
+	// Adjust text properties in the line above and below.
+	if (lnum > 1)
+	    adjust_text_props_for_delete(buf, lnum - 1, textprop_save, textprop_save_len, TRUE);
+	if (lnum <= buf->b_ml.ml_line_count)
+	    adjust_text_props_for_delete(buf, lnum, textprop_save, textprop_save_len, FALSE);
+    }
+    vim_free(textprop_save);
+#endif
+    return ret;
 }
 
 /*
- * set the B_MARKED flag for line 'lnum'
+ * set the DB_MARKED flag for line 'lnum'
  */
     void
 ml_setmarked(linenr_T lnum)
@@ -3417,7 +3549,7 @@ ml_setmarked(linenr_T lnum)
 }
 
 /*
- * find the first line with its B_MARKED flag set
+ * find the first line with its DB_MARKED flag set
  */
     linenr_T
 ml_firstmarked(void)
@@ -3650,7 +3782,7 @@ ml_new_ptr(memfile_T *mfp)
 }
 
 /*
- * lookup line 'lnum' in a memline
+ * Lookup line 'lnum' in a memline.
  *
  *   action: if ML_DELETE or ML_INSERT the line count is updated while searching
  *	     if ML_FLUSH only flush a locked block
--- a/src/misc2.c
+++ b/src/misc2.c
@@ -1351,6 +1351,20 @@ vim_strnsave(char_u *string, int len)
 }
 
 /*
+ * Copy "p[len]" into allocated memory, ignoring NUL characters.
+ * Returns NULL when out of memory.
+ */
+    char_u *
+vim_memsave(char_u *p, int len)
+{
+    char_u *ret = alloc((unsigned)len);
+
+    if (ret != NULL)
+	mch_memmove(ret, p, (size_t)len);
+    return ret;
+}
+
+/*
  * Same as vim_strsave(), but any characters found in esc_chars are preceded
  * by a backslash.
  */
--- a/src/proto/misc2.pro
+++ b/src/proto/misc2.pro
@@ -24,7 +24,7 @@ int alloc_does_fail(long_u size);
 char_u *alloc(unsigned size);
 char_u *alloc_id(unsigned size, alloc_id_T id);
 char_u *alloc_clear(unsigned size);
-char_u * alloc_clear_id(unsigned size, alloc_id_T id);
+char_u *alloc_clear_id(unsigned size, alloc_id_T id);
 char_u *alloc_check(unsigned size);
 char_u *lalloc_clear(long_u size, int message);
 char_u *lalloc(long_u size, int message);
@@ -34,6 +34,7 @@ void do_outofmem_msg(long_u size);
 void free_all_mem(void);
 char_u *vim_strsave(char_u *string);
 char_u *vim_strnsave(char_u *string, int len);
+char_u *vim_memsave(char_u *p, int len);
 char_u *vim_strsave_escaped(char_u *string, char_u *esc_chars);
 char_u *vim_strsave_escaped_ext(char_u *string, char_u *esc_chars, int cc, int bsl);
 int csh_like_shell(void);
--- a/src/testdir/test_textprop.vim
+++ b/src/testdir/test_textprop.vim
@@ -197,6 +197,16 @@ func Test_prop_clear_buf()
   bwipe!
 endfunc
 
+" Setup a three line prop in lines 2 - 4.
+" Add short props in line 1 and 5.
+func Setup_three_line_prop()
+  new
+  call setline(1, ['one', 'twotwo', 'three', 'fourfour', 'five'])
+  call prop_add(1, 2, {'length': 1, 'type': 'comment'})
+  call prop_add(2, 4, {'end_lnum': 4, 'end_col': 5, 'type': 'comment'})
+  call prop_add(5, 2, {'length': 1, 'type': 'comment'})
+endfunc
+
 func Test_prop_multiline()
   call prop_type_add('comment', {'highlight': 'Directory'})
   new
@@ -223,6 +233,30 @@ func Test_prop_multiline()
   call prop_clear(1, 3)
 
   bwipe!
+
+  " Test deleting the first line with a prop.
+  call Setup_three_line_prop()
+  let expect2 = {'col': 4, 'length': 4, 'type': 'comment', 'start': 1, 'end': 0, 'id': 0}
+  call assert_equal([expect2], prop_list(2))
+  2del
+  let expect_short = {'col': 2, 'length': 1, 'type': 'comment', 'start': 1, 'end': 1, 'id': 0}
+  call assert_equal([expect_short], prop_list(1))
+  let expect2 = {'col': 1, 'length': 6, 'type': 'comment', 'start': 1, 'end': 0, 'id': 0}
+  call assert_equal([expect2], prop_list(2))
+  bwipe!
+
+  " Test deleting the last line with a prop.
+  call Setup_three_line_prop()
+  let expect3 = {'col': 1, 'length': 6, 'type': 'comment', 'start': 0, 'end': 0, 'id': 0}
+  call assert_equal([expect3], prop_list(3))
+  let expect4 = {'col': 1, 'length': 5, 'type': 'comment', 'start': 0, 'end': 1, 'id': 0}
+  call assert_equal([expect4], prop_list(4))
+  4del
+  let expect3 = {'col': 1, 'length': 6, 'type': 'comment', 'start': 0, 'end': 1, 'id': 0}
+  call assert_equal([expect3], prop_list(3))
+  call assert_equal([expect_short], prop_list(4))
+  bwipe!
+
   call prop_type_delete('comment')
 endfunc
 
--- 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 */
 /**/
+    654,
+/**/
     653,
 /**/
     652,