# HG changeset patch # User Bram Moolenaar # Date 1558116006 -7200 # Node ID 6f453673eb19e512c5ac02de7ad326fc70189812 # Parent 9e2b1e4495796295d402c6bc4ca828e7c1936c0b patch 8.1.1341: text properties are lost when joining lines commit https://github.com/vim/vim/commit/80e737cc6ab6b68948f6765348b6881be861b200 Author: Bram Moolenaar Date: Fri May 17 19:56:34 2019 +0200 patch 8.1.1341: text properties are lost when joining lines Problem: Text properties are lost when joining lines. Solution: Move the text properties to the joined line. diff --git a/src/ops.c b/src/ops.c --- 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 diff --git a/src/proto/textprop.pro b/src/proto/textprop.pro --- a/src/proto/textprop.pro +++ b/src/proto/textprop.pro @@ -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 : */ diff --git a/src/testdir/dumps/Test_textprop_01.dump b/src/testdir/dumps/Test_textprop_01.dump --- a/src/testdir/dumps/Test_textprop_01.dump +++ b/src/testdir/dumps/Test_textprop_01.dump @@ -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| diff --git a/src/testdir/test_textprop.vim b/src/testdir/test_textprop.vim --- a/src/testdir/test_textprop.vim +++ b/src/testdir/test_textprop.vim @@ -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\lllix\lllix\lllix\lllix\lllix\lllix\lllix\", \ "normal 3G0lli\\", + \ "normal 6G0i\\", + \ "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 diff --git a/src/textprop.c b/src/textprop.c --- a/src/textprop.c +++ b/src/textprop.c @@ -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 diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -768,6 +768,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 1341, +/**/ 1340, /**/ 1339,