diff src/textprop.c @ 20583:d067be761cd7 v8.2.0845

patch 8.2.0845: text properties crossing lines not handled correctly Commit: https://github.com/vim/vim/commit/87be9be1db6b6d8fb57ef14e05f23a84e5e8bea0 Author: Bram Moolenaar <Bram@vim.org> Date: Sat May 30 15:32:02 2020 +0200 patch 8.2.0845: text properties crossing lines not handled correctly Problem: Text properties crossing lines not handled correctly. Solution: When joining lines merge text properties if possible. (Axel Forsman, closes #5839, closes #5683)
author Bram Moolenaar <Bram@vim.org>
date Sat, 30 May 2020 15:45:04 +0200
parents d2153928b376
children 71a36879ac2a
line wrap: on
line diff
--- a/src/textprop.c
+++ b/src/textprop.c
@@ -380,6 +380,30 @@ get_text_props(buf_T *buf, linenr_T lnum
     return (int)(proplen / sizeof(textprop_T));
 }
 
+/**
+ * Return the number of text properties on line "lnum" in the current buffer.
+ * When "only_starting" is true only text properties starting in this line will
+ * be considered.
+ */
+    int
+count_props(linenr_T lnum, int only_starting)
+{
+    char_u	*props;
+    int		proplen = get_text_props(curbuf, lnum, &props, 0);
+    int		result = proplen;
+    int		i;
+    textprop_T	prop;
+
+    if (only_starting)
+	for (i = 0; i < proplen; ++i)
+	{
+	    mch_memmove(&prop, props + i * sizeof(prop), sizeof(prop));
+	    if (prop.tp_flags & TP_FLAG_CONT_PREV)
+		--result;
+	}
+    return result;
+}
+
 /*
  * Find text property "type_id" in the visible lines of window "wp".
  * Match "id" when it is > 0.
@@ -564,15 +588,15 @@ f_prop_find(typval_T *argvars, typval_T 
     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
+    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)
     {
@@ -652,7 +676,7 @@ f_prop_find(typval_T *argvars, typval_T 
 	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));
+							 / sizeof(textprop_T));
 	int	    i;
 	textprop_T  prop;
 	int	    prop_start;
@@ -856,8 +880,8 @@ f_prop_remove(typval_T *argvars, typval_
 	len = STRLEN(text) + 1;
 	if ((size_t)buf->b_ml.ml_line_len > len)
 	{
-	    static textprop_T textprop;  // static because of alignment
-	    unsigned          idx;
+	    static textprop_T	textprop;  // static because of alignment
+	    unsigned		idx;
 
 	    for (idx = 0; idx < (buf->b_ml.ml_line_len - len)
 						   / sizeof(textprop_T); ++idx)
@@ -1212,6 +1236,77 @@ clear_buf_prop_types(buf_T *buf)
     buf->b_proptypes = NULL;
 }
 
+// Struct used to return two values from adjust_prop().
+typedef struct
+{
+    int dirty;	    // if the property was changed
+    int can_drop;   // whether after this change, the prop may be removed
+} adjustres_T;
+
+/*
+ * Adjust the property for "added" bytes (can be negative) inserted at "col".
+ *
+ * Note that "col" is zero-based, while tp_col is one-based.
+ * Only for the current buffer.
+ * "flags" can have:
+ * APC_SUBSTITUTE:	Text is replaced, not inserted.
+ */
+    static adjustres_T
+adjust_prop(
+	textprop_T *prop,
+	colnr_T col,
+	int added,
+	int flags)
+{
+    proptype_T	*pt = text_prop_type_by_id(curbuf, prop->tp_type);
+    int		start_incl = (pt != NULL
+				    && (pt->pt_flags & PT_FLAG_INS_START_INCL))
+						   || (flags & APC_SUBSTITUTE);
+    int		end_incl = (pt != NULL
+				     && (pt->pt_flags & PT_FLAG_INS_END_INCL));
+		// Do not drop zero-width props if they later can increase in
+		// size.
+    int		droppable = !(start_incl || end_incl);
+    adjustres_T res = {TRUE, FALSE};
+
+    if (added > 0)
+    {
+	if (col + 1 <= prop->tp_col
+		- (start_incl || (prop->tp_len == 0 && end_incl)))
+	    // Change is entirely before the text property: Only shift
+	    prop->tp_col += added;
+	else if (col + 1 < prop->tp_col + prop->tp_len + end_incl)
+	    // Insertion was inside text property
+	    prop->tp_len += added;
+    }
+    else if (prop->tp_col > col + 1)
+    {
+	if (prop->tp_col + added < col + 1)
+	{
+	    prop->tp_len += (prop->tp_col - 1 - col) + added;
+	    prop->tp_col = col + 1;
+	    if (prop->tp_len <= 0)
+	    {
+		prop->tp_len = 0;
+		res.can_drop = droppable;
+	    }
+	}
+	else
+	    prop->tp_col += added;
+    }
+    else if (prop->tp_len > 0 && prop->tp_col + prop->tp_len > col)
+    {
+	int after = col - added - (prop->tp_col - 1 + prop->tp_len);
+
+	prop->tp_len += after > 0 ? added + after : added;
+	res.can_drop = prop->tp_len <= 0 && droppable;
+    }
+    else
+	res.dirty = FALSE;
+
+    return res;
+}
+
 /*
  * Adjust the columns of text properties in line "lnum" after position "col" to
  * shift by "bytes_added" (can be negative).
@@ -1232,7 +1327,6 @@ adjust_prop_columns(
 {
     int		proplen;
     char_u	*props;
-    proptype_T  *pt;
     int		dirty = FALSE;
     int		ri, wi;
     size_t	textlen;
@@ -1249,78 +1343,19 @@ adjust_prop_columns(
     for (ri = 0; ri < proplen; ++ri)
     {
 	textprop_T	prop;
-	int		start_incl, end_incl;
-	int		can_drop;
+	adjustres_T	res;
 
-	mch_memmove(&prop, props + ri * sizeof(textprop_T), sizeof(textprop_T));
-	pt = text_prop_type_by_id(curbuf, prop.tp_type);
-	start_incl = (pt != NULL && (pt->pt_flags & PT_FLAG_INS_START_INCL))
-						   || (flags & APC_SUBSTITUTE);
-	end_incl = (pt != NULL && (pt->pt_flags & PT_FLAG_INS_END_INCL));
-	// Do not drop zero-width props if they later can increase in size
-	can_drop = !(start_incl || end_incl);
-
-	if (bytes_added > 0)
+	mch_memmove(&prop, props + ri * sizeof(prop), sizeof(prop));
+	res = adjust_prop(&prop, col, bytes_added, flags);
+	if (res.dirty)
 	{
-	    if (col + 1 <= prop.tp_col
-			      - (start_incl || (prop.tp_len == 0 && end_incl)))
-	    {
-		// Change is entirely before the text property: Only shift
-		prop.tp_col += bytes_added;
-		// Save for undo if requested and not done yet.
-		if ((flags & APC_SAVE_FOR_UNDO) && !dirty)
-		    u_savesub(lnum);
-		dirty = TRUE;
-	    }
-	    else if (col + 1 < prop.tp_col + prop.tp_len + end_incl)
-	    {
-		// Insertion was inside text property
-		prop.tp_len += bytes_added;
-		// Save for undo if requested and not done yet.
-		if ((flags & APC_SAVE_FOR_UNDO) && !dirty)
-		    u_savesub(lnum);
-		dirty = TRUE;
-	    }
-	}
-	else if (prop.tp_col > col + 1)
-	{
-	    int len_changed = FALSE;
-
-	    if (prop.tp_col + bytes_added < col + 1)
-	    {
-		prop.tp_len += (prop.tp_col - 1 - col) + bytes_added;
-		prop.tp_col = col + 1;
-		len_changed = TRUE;
-	    }
-	    else
-		prop.tp_col += bytes_added;
 	    // Save for undo if requested and not done yet.
 	    if ((flags & APC_SAVE_FOR_UNDO) && !dirty)
 		u_savesub(lnum);
 	    dirty = TRUE;
-	    if (len_changed && prop.tp_len <= 0)
-	    {
-		prop.tp_len = 0;
-		if (can_drop)
-		    continue;  // drop this text property
-	    }
 	}
-	else if (prop.tp_len > 0 && prop.tp_col + prop.tp_len > col)
-	{
-	    int after = col - bytes_added - (prop.tp_col - 1 + prop.tp_len);
-
-	    if (after > 0)
-		prop.tp_len += bytes_added + after;
-	    else
-		prop.tp_len += bytes_added;
-	    // Save for undo if requested and not done yet.
-	    if ((flags & APC_SAVE_FOR_UNDO) && !dirty)
-		u_savesub(lnum);
-	    dirty = TRUE;
-	    if (prop.tp_len <= 0 && can_drop)
-		continue;  // drop this text property
-	}
-
+	if (res.can_drop)
+	    continue; // Drop this text property
 	mch_memmove(props + wi * sizeof(textprop_T), &prop, sizeof(textprop_T));
 	++wi;
     }
@@ -1372,26 +1407,38 @@ adjust_props_for_split(
     for (i = 0; i < count; ++i)
     {
 	textprop_T  prop;
-	textprop_T *p;
+	proptype_T *pt;
+	int	    start_incl, end_incl;
+	int	    cont_prev, cont_next;
 
 	// copy the prop to an aligned structure
 	mch_memmove(&prop, props + i * sizeof(textprop_T), sizeof(textprop_T));
 
-	if (prop.tp_col < kept && ga_grow(&prevprop, 1) == OK)
+	pt = text_prop_type_by_id(curbuf, prop.tp_type);
+	start_incl = (pt != NULL && (pt->pt_flags & PT_FLAG_INS_START_INCL));
+	end_incl = (pt != NULL && (pt->pt_flags & PT_FLAG_INS_END_INCL));
+	cont_prev = prop.tp_col + !start_incl <= kept;
+	cont_next = skipped <= prop.tp_col + prop.tp_len - !end_incl;
+
+	if (cont_prev && ga_grow(&prevprop, 1) == OK)
 	{
-	    p = ((textprop_T *)prevprop.ga_data) + prevprop.ga_len;
+	    textprop_T *p = ((textprop_T *)prevprop.ga_data) + prevprop.ga_len;
+
 	    *p = prop;
+	    ++prevprop.ga_len;
 	    if (p->tp_col + p->tp_len >= kept)
 		p->tp_len = kept - p->tp_col;
-	    ++prevprop.ga_len;
+	    if (cont_next)
+		p->tp_flags |= TP_FLAG_CONT_NEXT;
 	}
 
 	// Only add the property to the next line if the length is bigger than
 	// zero.
-	if (prop.tp_col + prop.tp_len > skipped && ga_grow(&nextprop, 1) == OK)
+	if (cont_next && ga_grow(&nextprop, 1) == OK)
 	{
-	    p = ((textprop_T *)nextprop.ga_data) + nextprop.ga_len;
+	    textprop_T *p = ((textprop_T *)nextprop.ga_data) + nextprop.ga_len;
 	    *p = prop;
+	    ++nextprop.ga_len;
 	    if (p->tp_col > skipped)
 		p->tp_col -= skipped - 1;
 	    else
@@ -1399,7 +1446,8 @@ adjust_props_for_split(
 		p->tp_len -= skipped - p->tp_col;
 		p->tp_col = 1;
 	    }
-	    ++nextprop.ga_len;
+	    if (cont_prev)
+		p->tp_flags |= TP_FLAG_CONT_PREV;
 	}
     }
 
@@ -1412,111 +1460,63 @@ adjust_props_for_split(
 }
 
 /*
- * Line "lnum" has been joined and will end up at column "col" in the new line.
- * "removed" bytes have been removed from the start of the line, properties
- * there are to be discarded.
- * Move the adjusted text properties to an allocated string, store it in
- * "prop_line" and adjust the columns.
+ * Prepend properties of joined line "lnum" to "new_props".
  */
     void
-adjust_props_for_join(
+prepend_joined_props(
+	char_u	    *new_props,
+	int	    propcount,
+	int	    *props_remaining,
 	linenr_T    lnum,
-	textprop_T  **prop_line,
-	int	    *prop_length,
+	int	    add_all,
 	long	    col,
 	int	    removed)
 {
-    int		proplen;
-    char_u	*props;
-    int		ri;
-    int		wi = 0;
+    char_u *props;
+    int	    proplen = get_text_props(curbuf, lnum, &props, FALSE);
+    int	    i;
+
+    for (i = proplen; i-- > 0; )
+    {
+	textprop_T  prop;
+	int	    end;
+
+	mch_memmove(&prop, props + i * sizeof(prop), sizeof(prop));
+	end = !(prop.tp_flags & TP_FLAG_CONT_NEXT);
 
-    proplen = get_text_props(curbuf, lnum, &props, FALSE);
-    if (proplen > 0)
-    {
-	*prop_line = ALLOC_MULT(textprop_T, proplen);
-	if (*prop_line != NULL)
+	adjust_prop(&prop, 0, -removed, 0); // Remove leading spaces
+	adjust_prop(&prop, -1, col, 0); // Make line start at its final colum
+
+	if (add_all || end)
+	    mch_memmove(new_props + --(*props_remaining) * sizeof(prop),
+							  &prop, sizeof(prop));
+	else
 	{
-	    for (ri = 0; ri < proplen; ++ri)
+	    int j;
+	    int	found = FALSE;
+
+	    // Search for continuing prop.
+	    for (j = *props_remaining; j < propcount; ++j)
 	    {
-		textprop_T *cp = *prop_line + wi;
+		textprop_T op;
 
-		mch_memmove(cp, props + ri * sizeof(textprop_T),
-							   sizeof(textprop_T));
-		if (cp->tp_col + cp->tp_len > removed)
+		mch_memmove(&op, new_props + j * sizeof(op), sizeof(op));
+		if ((op.tp_flags & TP_FLAG_CONT_PREV)
+			&& op.tp_id == prop.tp_id && op.tp_type == prop.tp_type)
 		{
-		    if (cp->tp_col > removed)
-			cp->tp_col += col;
-		    else
-		    {
-			// property was partly deleted, make it shorter
-			cp->tp_len -= removed - cp->tp_col;
-			cp->tp_col = col;
-		    }
-		    ++wi;
+		    found = TRUE;
+		    op.tp_len += op.tp_col - prop.tp_col;
+		    op.tp_col = prop.tp_col;
+		    // Start/end is taken care of when deleting joined lines
+		    op.tp_flags = prop.tp_flags;
+		    mch_memmove(new_props + j * sizeof(op), &op, sizeof(op));
+		    break;
 		}
 	    }
+	    if (!found)
+		internal_error("text property above joined line not found");
 	}
-	*prop_length = wi;
     }
 }
 
-/*
- * After joining lines: concatenate the text and the properties of all joined
- * lines into one line and replace the line.
- */
-    void
-join_prop_lines(
-	linenr_T    lnum,
-	char_u	    *newp,
-	textprop_T  **prop_lines,
-	int	    *prop_lengths,
-	int	    count)
-{
-    size_t	proplen = 0;
-    size_t	oldproplen;
-    char_u	*props;
-    int		i;
-    size_t	len;
-    char_u	*line;
-    size_t	l;
-
-    for (i = 0; i < count - 1; ++i)
-	proplen += prop_lengths[i];
-    if (proplen == 0)
-    {
-	ml_replace(lnum, newp, FALSE);
-	return;
-    }
-
-    // get existing properties of the joined line
-    oldproplen = get_text_props(curbuf, lnum, &props, FALSE);
-
-    len = STRLEN(newp) + 1;
-    line = alloc(len + (oldproplen + proplen) * sizeof(textprop_T));
-    if (line == NULL)
-	return;
-    mch_memmove(line, newp, len);
-    if (oldproplen > 0)
-    {
-	l = oldproplen * sizeof(textprop_T);
-	mch_memmove(line + len, props, l);
-	len += l;
-    }
-
-    for (i = 0; i < count - 1; ++i)
-	if (prop_lines[i] != NULL)
-	{
-	    l = prop_lengths[i] * sizeof(textprop_T);
-	    mch_memmove(line + len, prop_lines[i], l);
-	    len += l;
-	    vim_free(prop_lines[i]);
-	}
-
-    ml_replace_len(lnum, line, (colnr_T)len, TRUE, FALSE);
-    vim_free(newp);
-    vim_free(prop_lines);
-    vim_free(prop_lengths);
-}
-
 #endif // FEAT_PROP_POPUP