# HG changeset patch # User Bram Moolenaar # Date 1557954015 -7200 # Node ID 1fc9cd08cf3c29190ce143ff1e370e7ebfb9dff6 # Parent 0e9c064b096cfdc346a4b8563b7b078552a09847 patch 8.1.1333: text properties don't always move after changes commit https://github.com/vim/vim/commit/45dd07f10af9bea86f8df77e92788209e209fdab Author: Bram Moolenaar Date: Wed May 15 22:45:37 2019 +0200 patch 8.1.1333: text properties don't always move after changes Problem: Text properties don't always move after changes. Solution: Update properties before reporting changes to listeners. Move text property when splitting a line. diff --git a/src/change.c b/src/change.c --- a/src/change.c +++ b/src/change.c @@ -641,12 +641,12 @@ changed_bytes(linenr_T lnum, colnr_T col void inserted_bytes(linenr_T lnum, colnr_T col, int added UNUSED) { - changed_bytes(lnum, col); - #ifdef FEAT_TEXT_PROP if (curbuf->b_has_textprop && added != 0) adjust_prop_columns(lnum, col, added); #endif + + changed_bytes(lnum, col); } /* @@ -2133,6 +2133,12 @@ open_line( ) mark_adjust(curwin->w_cursor.lnum + 1, (linenr_T)MAXLNUM, 1L, 0L); did_append = TRUE; +#ifdef FEAT_TEXT_PROP + if ((State & INSERT) && !(State & VREPLACE_FLAG)) + // properties after the split move to the next line + adjust_props_for_split(curwin->w_cursor.lnum, curwin->w_cursor.lnum, + curwin->w_cursor.col + 1, 0); +#endif } else { diff --git a/src/ex_cmds.c b/src/ex_cmds.c --- a/src/ex_cmds.c +++ b/src/ex_cmds.c @@ -5728,7 +5728,7 @@ do_sub(exarg_T *eap) last_line = lnum + 1; } #ifdef FEAT_TEXT_PROP - adjust_props_for_split(lnum, plen, 1); + adjust_props_for_split(lnum + 1, lnum, plen, 1); #endif // all line numbers increase ++sub_firstlnum; diff --git a/src/proto/textprop.pro b/src/proto/textprop.pro --- a/src/proto/textprop.pro +++ b/src/proto/textprop.pro @@ -14,5 +14,5 @@ void f_prop_type_list(typval_T *argvars, 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, int kept, int deleted); +void adjust_props_for_split(linenr_T lnum_props, linenr_T lnum_top, int kept, int deleted); /* vim: set ft=c : */ 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 @@ -151,6 +151,7 @@ endfunc func SetupOneLine() call setline(1, 'xonex xtwoxx') + normal gg0 call AddPropTypes() call prop_add(1, 2, {'length': 3, 'id': 11, 'type': 'one'}) call prop_add(1, 8, {'length': 3, 'id': 12, 'type': 'two'}) @@ -272,6 +273,66 @@ func Test_prop_replace() set bs& endfunc +func Test_prop_open_line() + new + + " open new line, props stay in top line + let expected = SetupOneLine() " 'xonex xtwoxx' + exe "normal o\" + call assert_equal('xonex xtwoxx', getline(1)) + call assert_equal('', getline(2)) + call assert_equal(expected, prop_list(1)) + call DeletePropTypes() + + " move all props to next line + let expected = SetupOneLine() " 'xonex xtwoxx' + exe "normal 0i\\" + call assert_equal('', getline(1)) + call assert_equal('xonex xtwoxx', getline(2)) + call assert_equal(expected, prop_list(2)) + call DeletePropTypes() + + " split just before prop, move all props to next line + let expected = SetupOneLine() " 'xonex xtwoxx' + exe "normal 0li\\" + call assert_equal('x', getline(1)) + call assert_equal('onex xtwoxx', getline(2)) + let expected[0].col -= 1 + let expected[1].col -= 1 + call assert_equal(expected, prop_list(2)) + call DeletePropTypes() + + " split inside prop, split first prop + let expected = SetupOneLine() " 'xonex xtwoxx' + exe "normal 0lli\\" + call assert_equal('xo', getline(1)) + call assert_equal('nex xtwoxx', getline(2)) + let exp_first = [deepcopy(expected[0])] + let exp_first[0].length = 1 + call assert_equal(exp_first, prop_list(1)) + let expected[0].col = 1 + let expected[0].length = 2 + let expected[1].col -= 2 + call assert_equal(expected, prop_list(2)) + call DeletePropTypes() + + " split just after first prop, empty prop and second prop move to next line + let expected = SetupOneLine() " 'xonex xtwoxx' + exe "normal 0fea\\" + call assert_equal('xone', getline(1)) + call assert_equal('x xtwoxx', getline(2)) + let exp_first = expected[0:0] + call assert_equal(exp_first, prop_list(1)) + let expected[0].col = 1 + let expected[0].length = 0 + let expected[1].col -= 4 + call assert_equal(expected, prop_list(2)) + call DeletePropTypes() + + bwipe! + set bs& +endfunc + func Test_prop_clear() new call AddPropTypes() diff --git a/src/textprop.c b/src/textprop.c --- a/src/textprop.c +++ b/src/textprop.c @@ -8,18 +8,15 @@ */ /* - * Text properties implementation. - * - * Text properties are attached to the text. They move with the text when - * text is inserted/deleted. - * - * Text properties have a user specified ID number, which can be unique. - * Text properties have a type, which can be used to specify highlighting. + * Text properties implementation. See ":help text-properties". * * TODO: * - When using 'cursorline' attributes should be merged. (#3912) * - Adjust text property column and length when text is inserted/deleted. + * -> splitting a line can create a zero-length property. Don't highlight it + * and extend it when inserting text. * -> a :substitute with a multi-line match + * -> join two lines, also with BS in Insert mode * -> search for changed_bytes() from misc1.c * - 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 @@ -28,8 +25,6 @@ * the index, like DB_MARKED? * - Also test line2byte() with many lines, so that ml_updatechunk() is taken * into account. - * - Add mechanism to keep track of changed lines, so that plugin can update - * text properties in these. * - Perhaps have a window-local option to disable highlighting from text * properties? */ @@ -1033,12 +1028,17 @@ adjust_prop_columns( /* * Adjust text properties for a line that was split in two. - * "lnum" is the newly inserted line. The text properties are now on the line - * below it. "kept" is the number of bytes kept in the first line, while + * "lnum_props" is the line that has the properties from before the split. + * "lnum_top" is the top line. + * "kept" is the number of bytes kept in the first line, while * "deleted" is the number of bytes deleted. */ void -adjust_props_for_split(linenr_T lnum, int kept, int deleted) +adjust_props_for_split( + linenr_T lnum_props, + linenr_T lnum_top, + int kept, + int deleted) { char_u *props; int count; @@ -1049,11 +1049,12 @@ adjust_props_for_split(linenr_T lnum, in if (!curbuf->b_has_textprop) return; - count = get_text_props(curbuf, lnum + 1, &props, FALSE); + + // Get the text properties from "lnum_props". + count = get_text_props(curbuf, lnum_props, &props, FALSE); ga_init2(&prevprop, sizeof(textprop_T), 10); ga_init2(&nextprop, sizeof(textprop_T), 10); - // Get the text properties, which are at "lnum + 1". // Keep the relevant ones in the first line, reducing the length if needed. // Copy the ones that include the split to the second line. // Move the ones after the split to the second line. @@ -1089,10 +1090,11 @@ adjust_props_for_split(linenr_T lnum, in } } - set_text_props(lnum, prevprop.ga_data, prevprop.ga_len * sizeof(textprop_T)); + set_text_props(lnum_top, prevprop.ga_data, + prevprop.ga_len * sizeof(textprop_T)); ga_clear(&prevprop); - - set_text_props(lnum + 1, nextprop.ga_data, nextprop.ga_len * sizeof(textprop_T)); + set_text_props(lnum_top + 1, nextprop.ga_data, + nextprop.ga_len * sizeof(textprop_T)); ga_clear(&nextprop); } 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 */ /**/ + 1333, +/**/ 1332, /**/ 1331,