# HG changeset patch # User Bram Moolenaar # Date 1659726003 -7200 # Node ID e1c370197030cc0f0bc853c702b43797c8b81a8c # Parent 257b2726b2a258e88a75c0d88e80fef83e1ad2cc patch 9.0.0145: substitute that joins lines drops text properties Commit: https://github.com/vim/vim/commit/213bbaf15afc628e5f83d1ae6526631ca8292292 Author: Bram Moolenaar Date: Fri Aug 5 19:46:48 2022 +0100 patch 9.0.0145: substitute that joins lines drops text properties Problem: Substitute that joins lines drops text properties. Solution: Move text properties of the last line to the new line. diff --git a/src/ex_cmds.c b/src/ex_cmds.c --- a/src/ex_cmds.c +++ b/src/ex_cmds.c @@ -3709,6 +3709,9 @@ ex_substitute(exarg_T *eap) int save_ma = 0; int save_sandbox = 0; #endif +#ifdef FEAT_PROP_POPUP + textprop_T *text_props = NULL; +#endif cmd = eap->arg; if (!global_busy) @@ -4049,6 +4052,7 @@ ex_substitute(exarg_T *eap) #ifdef FEAT_PROP_POPUP int apc_flags = APC_SAVE_FOR_UNDO | APC_SUBSTITUTE; colnr_T total_added = 0; + int text_prop_count = 0; #endif /* @@ -4501,8 +4505,59 @@ ex_substitute(exarg_T *eap) } else { - p1 = ml_get(sub_firstlnum + nmatch - 1); + linenr_T lastlnum = sub_firstlnum + nmatch - 1; +#ifdef FEAT_PROP_POPUP + if (curbuf->b_has_textprop) + { + char_u *prop_start; + + // Props in the first line may be shortened or deleted + if (adjust_prop_columns(lnum, + total_added + regmatch.startpos[0].col, + -MAXCOL, apc_flags)) + apc_flags &= ~APC_SAVE_FOR_UNDO; + total_added -= (colnr_T)STRLEN( + sub_firstline + regmatch.startpos[0].col); + + // Props in the last line may be moved or deleted + if (adjust_prop_columns(lastlnum, + 0, -regmatch.endpos[0].col, apc_flags)) + // When text properties are changed, need to save + // for undo first, unless done already. + apc_flags &= ~APC_SAVE_FOR_UNDO; + + // Copy the text props of the last line, they will be + // later appended to the changed line. + text_prop_count = get_text_props(curbuf, lastlnum, + &prop_start, FALSE); + if (text_prop_count > 0) + { + // TODO: what when we already did this? + vim_free(text_props); + text_props = ALLOC_MULT(textprop_T, + text_prop_count); + if (text_props != NULL) + { + int pi; + + mch_memmove(text_props, prop_start, + text_prop_count * sizeof(textprop_T)); + // After joining the text prop columns will + // increase. + for (pi = 0; pi < text_prop_count; ++pi) + text_props[pi].tp_col += + regmatch.startpos[0].col + sublen - 1; + } + } + } +#endif + p1 = ml_get(lastlnum); nmatch_tl += nmatch - 1; +#ifdef FEAT_PROP_POPUP + if (curbuf->b_has_textprop) + total_added += (colnr_T)STRLEN( + p1 + regmatch.endpos[0].col); +#endif } copy_len = regmatch.startpos[0].col - copycol; needed_len = copy_len + ((unsigned)STRLEN(p1) @@ -4708,7 +4763,10 @@ skip: if (u_savesub(lnum) != OK) break; ml_replace(lnum, new_start, TRUE); - +#ifdef FEAT_PROP_POPUP + if (text_props != NULL) + add_text_props(lnum, text_props, text_prop_count); +#endif if (nmatch_tl > 0) { /* @@ -4793,6 +4851,10 @@ skip: outofmem: vim_free(sub_firstline); // may have to free allocated copy of the line +#ifdef FEAT_PROP_POPUP + vim_free(text_props); +#endif + // ":s/pat//n" doesn't move the cursor if (subflags.do_count) curwin->w_cursor = old_cursor; diff --git a/src/proto/textprop.pro b/src/proto/textprop.pro --- a/src/proto/textprop.pro +++ b/src/proto/textprop.pro @@ -6,6 +6,7 @@ int prop_add_common(linenr_T start_lnum, 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 last_line); int find_visible_prop(win_T *wp, int type_id, int id, textprop_T *prop, linenr_T *found_lnum); +void add_text_props(linenr_T lnum, textprop_T *text_props, int text_prop_count); proptype_T *text_prop_type_by_id(buf_T *buf, int id); void f_prop_clear(typval_T *argvars, typval_T *rettv); void f_prop_find(typval_T *argvars, typval_T *rettv); 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 @@ -1363,15 +1363,18 @@ func Test_proptype_substitute2() \ #{type_bufnr: 0, id: 0, col: 50, end: 1, type: 'number', length: 4, start: 1}] " TODO - return - " Add some text in between - %s/\s\+/ /g - call assert_equal(expected, prop_list(1) + prop_list(2) + prop_list(3)) + if 0 + " Add some text in between + %s/\s\+/ /g + call assert_equal(expected, prop_list(1) + prop_list(2) + prop_list(3)) - " remove some text - :1s/[a-z]\{3\}//g - let expected = [{'id': 0, 'col': 10, 'end': 1, 'type': 'number', 'length': 3, 'start': 1}] - call assert_equal(expected, prop_list(1)) + " remove some text + :1s/[a-z]\{3\}//g + let expected = [{'id': 0, 'col': 10, 'end': 1, 'type': 'number', 'length': 3, 'start': 1}] + call assert_equal(expected, prop_list(1)) + endif + + call prop_type_delete('number') bwipe! endfunc @@ -1388,6 +1391,36 @@ func Test_proptype_substitute3() bwipe! endfunc +func Test_proptype_substitute_join() + new + call setline(1, [ + \ 'This is some end', + \ 'start is highlighted end', + \ 'some is highlighted', + \ 'start is also highlighted']) + + call prop_type_add('number', {'highlight': 'ErrorMsg'}) + + call prop_add(1, 6, {'length': 2, 'type': 'number'}) + call prop_add(2, 7, {'length': 2, 'type': 'number'}) + call prop_add(3, 6, {'length': 2, 'type': 'number'}) + call prop_add(4, 7, {'length': 2, 'type': 'number'}) + " The highlighted "is" in line 1, 2 and 4 is kept and ajudsted. + " The highlighted "is" in line 3 is deleted. + let expected = [ + \ #{type_bufnr: 0, id: 0, col: 6, end: 1, type: 'number', length: 2, start: 1}, + \ #{type_bufnr: 0, id: 0, col: 21, end: 1, type: 'number', length: 2, start: 1}, + \ #{type_bufnr: 0, id: 0, col: 43, end: 1, type: 'number', length: 2, start: 1}] + + s/end\nstart/joined/ + s/end\n.*\nstart/joined/ + call assert_equal('This is some joined is highlighted joined is also highlighted', getline(1)) + call assert_equal(expected, prop_list(1)) + + call prop_type_delete('number') + bwipe! +endfunc + func SaveOptions() let d = #{tabstop: &tabstop, \ softtabstop: &softtabstop, diff --git a/src/textprop.c b/src/textprop.c --- a/src/textprop.c +++ b/src/textprop.c @@ -12,7 +12,6 @@ * * TODO: * - Adjust text property column and length when text is inserted/deleted. - * -> a :substitute with a multi-line match * -> 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? @@ -683,6 +682,29 @@ set_text_props(linenr_T lnum, char_u *pr curbuf->b_ml.ml_flags |= ML_LINE_DIRTY; } +/* + * Add "text_props" with "text_prop_count" text propertis to line "lnum". + */ + void +add_text_props(linenr_T lnum, textprop_T *text_props, int text_prop_count) +{ + char_u *text; + char_u *newtext; + int proplen = text_prop_count * (int)sizeof(textprop_T); + + text = ml_get(lnum); + newtext = alloc(curbuf->b_ml.ml_line_len + proplen); + if (newtext == NULL) + return; + mch_memmove(newtext, text, curbuf->b_ml.ml_line_len); + mch_memmove(newtext + curbuf->b_ml.ml_line_len, text_props, proplen); + if (curbuf->b_ml.ml_flags & (ML_LINE_DIRTY | ML_ALLOCATED)) + vim_free(curbuf->b_ml.ml_line_ptr); + curbuf->b_ml.ml_line_ptr = newtext; + curbuf->b_ml.ml_line_len += proplen; + curbuf->b_ml.ml_flags |= ML_LINE_DIRTY; +} + static proptype_T * find_type_by_id(hashtab_T *ht, int id) { diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -736,6 +736,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 145, +/**/ 144, /**/ 143,