]> granicus.if.org Git - vim/commitdiff
patch 8.1.1341: text properties are lost when joining lines v8.1.1341
authorBram Moolenaar <Bram@vim.org>
Fri, 17 May 2019 17:56:34 +0000 (19:56 +0200)
committerBram Moolenaar <Bram@vim.org>
Fri, 17 May 2019 17:56:34 +0000 (19:56 +0200)
Problem:    Text properties are lost when joining lines.
Solution:   Move the text properties to the joined line.

src/ops.c
src/proto/textprop.pro
src/testdir/dumps/Test_textprop_01.dump
src/testdir/test_textprop.vim
src/textprop.c
src/version.c

index 2aa18c437c8f2307e78899336e508388155e9870..d77aa2ef5fb09a45870fdebd22dbaacdad990efd 100644 (file)
--- a/src/ops.c
+++ b/src/ops.c
@@ -1211,7 +1211,8 @@ do_execreg(
     int                retval = OK;
     int                remap;
 
-    if (regname == '@')                        /* repeat previous one */
+    // repeat previous one
+    if (regname == '@')
     {
        if (execreg_lastc == NUL)
        {
@@ -1220,7 +1221,7 @@ do_execreg(
        }
        regname = execreg_lastc;
     }
-                                       /* check for valid regname */
+    // check for valid regname
     if (regname == '%' || regname == '#' || !valid_yank_reg(regname, FALSE))
     {
        emsg_invreg(regname);
@@ -1232,11 +1233,13 @@ do_execreg(
     regname = may_get_selection(regname);
 #endif
 
-    if (regname == '_')                        /* black hole: don't stuff anything */
+    // black hole: don't stuff anything
+    if (regname == '_')
        return OK;
 
 #ifdef FEAT_CMDHIST
-    if (regname == ':')                        /* use last command line */
+    // use last command line
+    if (regname == ':')
     {
        if (last_cmdline == NULL)
        {
@@ -4438,7 +4441,10 @@ do_join(
                                  && has_format_option(FO_REMOVE_COMS);
     int                prev_was_comment;
 #endif
-
+#ifdef FEAT_TEXT_PROP
+    textprop_T **prop_lines = NULL;
+    int                *prop_lengths = NULL;
+#endif
 
     if (save_undo && u_save((linenr_T)(curwin->w_cursor.lnum - 1),
                            (linenr_T)(curwin->w_cursor.lnum + count)) == FAIL)
@@ -4463,8 +4469,9 @@ do_join(
 #endif
 
     /*
-     * Don't move anything, just compute the final line length
+     * Don't move anything yet, just compute the final line length
      * and setup the array of space strings lengths
+     * This loops forward over the joined lines.
      */
     for (t = 0; t < count; ++t)
     {
@@ -4556,8 +4563,24 @@ do_join(
     cend = newp + sumsize;
     *cend = 0;
 
+#ifdef FEAT_TEXT_PROP
+    // We need to move properties of the lines that are going to be deleted to
+    // the new long one.
+    if (curbuf->b_has_textprop && !text_prop_frozen)
+    {
+       // Allocate an array to copy the text properties of joined lines into.
+       // And another array to store the number of properties in each line.
+       prop_lines = (textprop_T **)alloc_clear(
+                                     (int)(count - 1) * sizeof(textprop_T *));
+       prop_lengths = (int *)alloc_clear((int)(count - 1) * sizeof(int));
+       if (prop_lengths == NULL)
+           VIM_CLEAR(prop_lines);
+    }
+#endif
+
     /*
      * Move affected lines to the new long one.
+     * This loops backwards over the joined lines, including the original line.
      *
      * Move marks from each deleted line to the joined line, adjusting the
      * column.  This is not Vi compatible, but Vi deletes the marks, thus that
@@ -4583,8 +4606,15 @@ do_join(
                         (long)(cend - newp - spaces_removed), spaces_removed);
        if (t == 0)
            break;
+#ifdef FEAT_TEXT_PROP
+       if (prop_lines != NULL)
+           adjust_props_for_join(curwin->w_cursor.lnum + t,
+                                     prop_lines + t - 1, prop_lengths + t - 1,
+                        (long)(cend - newp - spaces_removed), spaces_removed);
+#endif
+
        curr = curr_start = ml_get((linenr_T)(curwin->w_cursor.lnum + t - 1));
-#if defined(FEAT_COMMENTS) || defined(PROTO)
+#if defined(FEAT_COMMENTS)
        if (remove_comments)
            curr += comments[t - 1];
 #endif
@@ -4592,7 +4622,14 @@ do_join(
            curr = skipwhite(curr);
        currsize = (int)STRLEN(curr);
     }
-    ml_replace(curwin->w_cursor.lnum, newp, FALSE);
+
+#ifdef FEAT_TEXT_PROP
+    if (prop_lines != NULL)
+       join_prop_lines(curwin->w_cursor.lnum, newp,
+                                             prop_lines, prop_lengths, count);
+    else
+#endif
+       ml_replace(curwin->w_cursor.lnum, newp, FALSE);
 
     if (setmark)
     {
@@ -4605,7 +4642,6 @@ do_join(
      * the deleted line. */
     changed_lines(curwin->w_cursor.lnum, currsize,
                                               curwin->w_cursor.lnum + 1, 0L);
-
     /*
      * Delete following lines. To do this we move the cursor there
      * briefly, and then move it back. After del_lines() the cursor may
index 6d9553e287b814aede4798a84c55222b72d310a8..4d37174aef81d009a471ed0ed3fe6a9d254ffaed 100644 (file)
@@ -15,4 +15,6 @@ void clear_global_prop_types(void);
 void clear_buf_prop_types(buf_T *buf);
 void adjust_prop_columns(linenr_T lnum, colnr_T col, int bytes_added);
 void adjust_props_for_split(linenr_T lnum_props, linenr_T lnum_top, int kept, int deleted);
+void adjust_props_for_join(linenr_T lnum, textprop_T **prop_line, int *prop_length, long col, int removed);
+void join_prop_lines(linenr_T lnum, char_u *newp, textprop_T **prop_lines, int *prop_lengths, int count);
 /* vim: set ft=c : */
index bda2a56f004623699c84c9268ac313e235fce31c..9d3eb5c1915e4a7b7ce8a2409dbc3c313f45a571 100644 (file)
@@ -2,6 +2,7 @@
 | +0#af5f00255&@1|2| |N+0#0000000#ffff4012|u|m|b|é|r| |1+0#4040ff13&|2|3| +0#0000000&|ä|n|d| |t|h|œ|n| |4+0#4040ff13&|¾|7|.+0#0000000&| +0&#ffffff0@46
 | +0#af5f00255&@1|3| >-+8#0000000#ffff4012|x+8&#ffffff0|a+8#4040ff13&@1|x+8#0000000&|-@1|x+8#4040ff13&|b@1|x+8#0000000&|-@1|x|c+8#4040ff13&@1|x|-+8#0000000&@1|x+8#4040ff13&|d@1|x|-+8#0000000&@1| @45
 | +0#af5f00255&@1|4| |/+0#40ff4011&@1| |c|o|m@1|e|n|t| |w+0&#e0e0e08|i|t|h| |e+8&&|r@1|o|r| +0&#ffffff0|i|n| |i|t| +0#0000000&@43
+| +0#af5f00255&@1|5| |f+0#0000000&|i|r|s|t| |l+0&#ffff4012|i|n|e| @1|s|e|c|o|n|d| +0&#ffffff0|l|i|n|e| @1|t|h|i|r|d| |l|i|n|e| |f|o|u|r|t|h| |l+0&#ffff4012|i|n|e| +0&#ffffff0@23
 |~+0#4040ff13&| @73
 |~| @73
 | +0#0000000&@56|3|,|1| @10|A|l@1| 
index dc229c0197ef546d41cb7fd863ac4fc2b38a4293..635902c7e977029af5ccb7c959e5cd46cac72a77 100644 (file)
@@ -624,6 +624,10 @@ funct Test_textprop_screenshots()
        \       .. "'Numbér 123 änd thœn 4¾7.',"
        \       .. "'--aa--bb--cc--dd--',"
        \       .. "'// comment with error in it',"
+       \       .. "'first line',"
+       \       .. "'  second line  ',"
+       \       .. "'third line',"
+       \       .. "'   fourth line',"
        \       .. "])",
        \ "hi NumberProp ctermfg=blue",
        \ "hi LongProp ctermbg=yellow",
@@ -645,6 +649,10 @@ funct Test_textprop_screenshots()
        \ "call prop_add(3, 15, {'length': 2, 'type': 'both'})",
        \ "call prop_add(4, 12, {'length': 10, 'type': 'background'})",
        \ "call prop_add(4, 17, {'length': 5, 'type': 'error'})",
+       \ "call prop_add(5, 7, {'length': 4, 'type': 'long'})",
+       \ "call prop_add(6, 1, {'length': 8, 'type': 'long'})",
+       \ "call prop_add(8, 1, {'length': 1, 'type': 'long'})",
+       \ "call prop_add(8, 11, {'length': 4, 'type': 'long'})",
        \ "set number cursorline",
        \ "hi clear SpellBad",
        \ "set spell",
@@ -652,8 +660,11 @@ funct Test_textprop_screenshots()
        \ "hi Comment ctermfg=green",
        \ "normal 3G0llix\<Esc>lllix\<Esc>lllix\<Esc>lllix\<Esc>lllix\<Esc>lllix\<Esc>lllix\<Esc>lllix\<Esc>",
        \ "normal 3G0lli\<BS>\<Esc>",
+       \ "normal 6G0i\<BS>\<Esc>",
+       \ "normal 3J",
+       \ "normal 3G",
        \], 'XtestProp')
-  let buf = RunVimInTerminal('-S XtestProp', {'rows': 7})
+  let buf = RunVimInTerminal('-S XtestProp', {'rows': 8})
   call VerifyScreenDump(buf, 'Test_textprop_01', {})
 
   " clean up
index 4c78ff11c6307af869759d0b471fb39fdfd84921..8c86d01433140e5a88a7072dc1bde0b2a337dad5 100644 (file)
@@ -13,8 +13,8 @@
  * TODO:
  * - Adjust text property column and length when text is inserted/deleted.
  *   -> a :substitute with a multi-line match
- *   -> join two lines, also with BS in Insert mode
  *   -> search for changed_bytes() from misc1.c
+ *   -> search for mark_col_adjust()
  * - Perhaps we only need TP_FLAG_CONT_NEXT and can drop TP_FLAG_CONT_PREV?
  * - Add an arrray for global_proptypes, to quickly lookup a prop type by ID
  * - Add an arrray for b_proptypes, to quickly lookup a prop type by ID
@@ -1097,4 +1097,109 @@ adjust_props_for_split(
     ga_clear(&nextprop);
 }
 
+/*
+ * 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.
+ */
+    void
+adjust_props_for_join(
+       linenr_T    lnum,
+       textprop_T  **prop_line,
+       int         *prop_length,
+       long        col,
+       int         removed)
+{
+    int                proplen;
+    char_u     *props;
+    int                ri;
+    int                wi = 0;
+
+    proplen = get_text_props(curbuf, lnum, &props, FALSE);
+    if (proplen > 0)
+    {
+       *prop_line = (textprop_T *)alloc(proplen * (int)sizeof(textprop_T));
+       if (*prop_line != NULL)
+       {
+           for (ri = 0; ri < proplen; ++ri)
+           {
+               textprop_T *cp = *prop_line + wi;
+
+               mch_memmove(cp, props + ri * sizeof(textprop_T),
+                                                          sizeof(textprop_T));
+               if (cp->tp_col + cp->tp_len > removed)
+               {
+                   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;
+               }
+           }
+       }
+       *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;
+    int                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 = (int)STRLEN(newp) + 1;
+    line = alloc(len + (oldproplen + proplen) * (int)sizeof(textprop_T));
+    if (line == NULL)
+       return;
+    mch_memmove(line, newp, len);
+    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, len, TRUE, FALSE);
+    vim_free(newp);
+    vim_free(prop_lines);
+    vim_free(prop_lengths);
+}
+
 #endif // FEAT_TEXT_PROP
index 309ed3c09d787dd44951a49ed89161944ac65961..f9329ba8c4bcf41e34fa9673fe0db846d40a916f 100644 (file)
@@ -767,6 +767,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    1341,
 /**/
     1340,
 /**/