# HG changeset patch # User Bram Moolenaar # Date 1590846304 -7200 # Node ID d067be761cd7de3679324a932555a965faae7352 # Parent 49d2f01322db036c81fe5e6c880a1754885b9e71 patch 8.2.0845: text properties crossing lines not handled correctly Commit: https://github.com/vim/vim/commit/87be9be1db6b6d8fb57ef14e05f23a84e5e8bea0 Author: Bram Moolenaar 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) diff --git a/src/memline.c b/src/memline.c --- a/src/memline.c +++ b/src/memline.c @@ -3420,7 +3420,6 @@ adjust_text_props_for_delete( int done_del; int done_this; textprop_T prop_del; - textprop_T prop_this; bhdr_T *hp; DATA_BL *dp; int idx; @@ -3451,7 +3450,8 @@ adjust_text_props_for_delete( 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; + 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) @@ -3466,24 +3466,24 @@ adjust_text_props_for_delete( } found = FALSE; - for (done_this = 0; done_this < this_props_len; done_this += sizeof(textprop_T)) + 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 + int flag = above ? TP_FLAG_CONT_NEXT + : TP_FLAG_CONT_PREV; + textprop_T prop_this; + + mch_memmove(&prop_this, text + textlen + done_del, + sizeof(textprop_T)); + if ((prop_this.tp_flags & flag) + && 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"); + prop_this.tp_flags &= ~flag; + mch_memmove(text + textlen + done_del, &prop_this, + sizeof(textprop_T)); + break; } } if (!found) diff --git a/src/ops.c b/src/ops.c --- a/src/ops.c +++ b/src/ops.c @@ -1887,6 +1887,7 @@ do_join( char_u *curr_start = NULL; char_u *cend; char_u *newp; + size_t newp_len; char_u *spaces; // number of spaces inserted before a line int endcurr1 = NUL; int endcurr2 = NUL; @@ -1900,8 +1901,8 @@ do_join( && has_format_option(FO_REMOVE_COMS); int prev_was_comment; #ifdef FEAT_PROP_POPUP - textprop_T **prop_lines = NULL; - int *prop_lengths = NULL; + int propcount = 0; // number of props over all joined lines + int props_remaining; #endif if (save_undo && u_save((linenr_T)(curwin->w_cursor.lnum - 1), @@ -1932,6 +1933,9 @@ do_join( for (t = 0; t < count; ++t) { curr = curr_start = ml_get((linenr_T)(curwin->w_cursor.lnum + t)); +#ifdef FEAT_PROP_POPUP + propcount += count_props((linenr_T) (curwin->w_cursor.lnum + t), t > 0); +#endif if (t == 0 && setmark && !cmdmod.lockmarks) { // Set the '[ mark. @@ -2014,7 +2018,11 @@ do_join( col = sumsize - currsize - spaces[count - 1]; // allocate the space for the new line - newp = alloc(sumsize + 1); + newp_len = sumsize + 1; +#ifdef FEAT_PROP_POPUP + newp_len += propcount * sizeof(textprop_T); +#endif + newp = alloc(newp_len); if (newp == NULL) { ret = FAIL; @@ -2023,20 +2031,6 @@ do_join( cend = newp + sumsize; *cend = 0; -#ifdef FEAT_PROP_POPUP - // 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 = ALLOC_CLEAR_MULT(textprop_T *, count - 1); - prop_lengths = ALLOC_CLEAR_MULT(int, count - 1); - 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. @@ -2045,12 +2039,16 @@ do_join( * column. This is not Vi compatible, but Vi deletes the marks, thus that * should not really be a problem. */ +#ifdef FEAT_PROP_POPUP + props_remaining = propcount; +#endif for (t = count - 1; ; --t) { int spaces_removed; cend -= currsize; mch_memmove(cend, curr, (size_t)currsize); + if (spaces[t] > 0) { cend -= spaces[t]; @@ -2063,15 +2061,14 @@ do_join( mark_col_adjust(curwin->w_cursor.lnum + t, (colnr_T)0, (linenr_T)-t, (long)(cend - newp - spaces_removed), spaces_removed); +#ifdef FEAT_PROP_POPUP + prepend_joined_props(newp + sumsize + 1, propcount, &props_remaining, + curwin->w_cursor.lnum + t, t == count - 1, + (long)(cend - newp), spaces_removed); +#endif + if (t == 0) break; -#ifdef FEAT_PROP_POPUP - 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 (remove_comments) curr += comments[t - 1]; @@ -2080,13 +2077,7 @@ do_join( currsize = (int)STRLEN(curr); } -#ifdef FEAT_PROP_POPUP - 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); + ml_replace_len(curwin->w_cursor.lnum, newp, newp_len, TRUE, FALSE); if (setmark && !cmdmod.lockmarks) { diff --git a/src/proto/textprop.pro b/src/proto/textprop.pro --- a/src/proto/textprop.pro +++ b/src/proto/textprop.pro @@ -3,6 +3,7 @@ int find_prop_type_id(char_u *name, buf_ void f_prop_add(typval_T *argvars, typval_T *rettv); void prop_add_common(linenr_T start_lnum, colnr_T start_col, dict_T *dict, buf_T *default_buf, typval_T *dict_arg); int get_text_props(buf_T *buf, linenr_T lnum, char_u **props, int will_change); +int count_props(linenr_T lnum, int only_starting); int find_visible_prop(win_T *wp, int type_id, int id, textprop_T *prop, linenr_T *found_lnum); proptype_T *text_prop_type_by_id(buf_T *buf, int id); void f_prop_clear(typval_T *argvars, typval_T *rettv); @@ -18,6 +19,5 @@ void clear_global_prop_types(void); void clear_buf_prop_types(buf_T *buf); int adjust_prop_columns(linenr_T lnum, colnr_T col, int bytes_added, int flags); 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); +void prepend_joined_props(char_u *new_props, int propcount, int *props_remaining, linenr_T lnum, int add_all, long col, int removed); /* 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,7 +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 | +8#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+0#0000000#e0e0e08@1|e|n+0#40ff4011#ffffff0|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#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| +0&#ffff4012|f+0&#ffffff0|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 @@ -460,9 +460,11 @@ func Test_prop_open_line() call assert_equal('nex xtwoxx', getline(2)) let exp_first = [deepcopy(expected[0])] let exp_first[0].length = 1 + let exp_first[0].end = 0 call assert_equal(exp_first, prop_list(1)) let expected[0].col = 1 let expected[0].length = 2 + let expected[0].start = 0 let expected[1].col -= 2 call assert_equal(expected, prop_list(2)) call DeletePropTypes() @@ -575,11 +577,13 @@ func Test_prop_substitute() \ copy(expected_props[3]), \ ] let expected_props[0].length = 5 + let expected_props[0].end = 0 unlet expected_props[3] unlet expected_props[2] call assert_equal(expected_props, prop_list(1)) let new_props[0].length = 6 + let new_props[0].start = 0 let new_props[1].col = 1 let new_props[1].length = 1 let new_props[2].col = 3 @@ -1228,4 +1232,25 @@ func Test_prop_func_invalid_args() call assert_fails("call prop_type_list([])", 'E715:') endfunc +func Test_split_join() + new + call prop_type_add('test', {'highlight': 'ErrorMsg'}) + call setline(1, 'just some text') + call prop_add(1, 6, {'length': 4, 'type': 'test'}) + + " Split in middle of "some" + execute "normal! 8|i\" + call assert_equal([{'id': 0, 'col': 6, 'end': 0, 'type': 'test', 'length': 2, 'start': 1}], + \ prop_list(1)) + call assert_equal([{'id': 0, 'col': 1, 'end': 1, 'type': 'test', 'length': 2, 'start': 0}], + \ prop_list(2)) + + " Join the two lines back together + normal! 1GJ + call assert_equal([{'id': 0, 'col': 6, 'end': 1, 'type': 'test', 'length': 5, 'start': 1}], prop_list(1)) + + bwipe! + call prop_type_delete('test') +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/textprop.c b/src/textprop.c --- 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 diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -747,6 +747,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 845, +/**/ 844, /**/ 843,