Skip to content

Commit 4164bb2

Browse files
committed
patch 8.1.0691: text properties are not adjusted for :substitute
Problem: Text properties are not adjusted for :substitute. Solution: Adjust text properties as well as possible.
1 parent 21b5038 commit 4164bb2

5 files changed

Lines changed: 182 additions & 18 deletions

File tree

src/ex_cmds.c

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5628,9 +5628,19 @@ do_sub(exarg_T *eap)
56285628
* - original text up to match
56295629
* - length of substituted part
56305630
* - original text after match
5631+
* Adjust text properties here, since we have all information
5632+
* needed.
56315633
*/
56325634
if (nmatch == 1)
5635+
{
56335636
p1 = sub_firstline;
5637+
#ifdef FEAT_TEXT_PROP
5638+
if (curbuf->b_has_textprop)
5639+
adjust_prop_columns(lnum, regmatch.startpos[0].col,
5640+
sublen - 1 - (regmatch.endpos[0].col
5641+
- regmatch.startpos[0].col));
5642+
#endif
5643+
}
56345644
else
56355645
{
56365646
p1 = ml_get(sub_firstlnum + nmatch - 1);
@@ -5732,11 +5742,12 @@ do_sub(exarg_T *eap)
57325742
STRMOVE(p1, p1 + 1);
57335743
else if (*p1 == CAR)
57345744
{
5735-
if (u_inssub(lnum) == OK) /* prepare for undo */
5745+
if (u_inssub(lnum) == OK) // prepare for undo
57365746
{
5737-
*p1 = NUL; /* truncate up to the CR */
5738-
ml_append(lnum - 1, new_start,
5739-
(colnr_T)(p1 - new_start + 1), FALSE);
5747+
colnr_T plen = (colnr_T)(p1 - new_start + 1);
5748+
5749+
*p1 = NUL; // truncate up to the CR
5750+
ml_append(lnum - 1, new_start, plen, FALSE);
57405751
mark_adjust(lnum + 1, (linenr_T)MAXLNUM, 1L, 0L);
57415752
if (subflags.do_ask)
57425753
appended_lines(lnum - 1, 1L);
@@ -5746,13 +5757,16 @@ do_sub(exarg_T *eap)
57465757
first_line = lnum;
57475758
last_line = lnum + 1;
57485759
}
5749-
/* All line numbers increase. */
5760+
#ifdef FEAT_TEXT_PROP
5761+
adjust_props_for_split(lnum, plen, 1);
5762+
#endif
5763+
// all line numbers increase
57505764
++sub_firstlnum;
57515765
++lnum;
57525766
++line2;
5753-
/* move the cursor to the new line, like Vi */
5767+
// move the cursor to the new line, like Vi
57545768
++curwin->w_cursor.lnum;
5755-
/* copy the rest */
5769+
// copy the rest
57565770
STRMOVE(new_start, p1 + 1);
57575771
p1 = new_start - 1;
57585772
}

src/proto/textprop.pro

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,5 @@ void f_prop_type_list(typval_T *argvars, typval_T *rettv);
1414
void clear_global_prop_types(void);
1515
void clear_buf_prop_types(buf_T *buf);
1616
void adjust_prop_columns(linenr_T lnum, colnr_T col, int bytes_added);
17+
void adjust_props_for_split(linenr_T lnum, int kept, int deleted);
1718
/* vim: set ft=c : */

src/testdir/test_textprop.vim

Lines changed: 63 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -89,30 +89,34 @@ func SetupPropsInFirstLine()
8989
call setline(1, 'one two three')
9090
call prop_add(1, 1, {'length': 3, 'id': 11, 'type': 'one'})
9191
call prop_add(1, 5, {'length': 3, 'id': 12, 'type': 'two'})
92-
call prop_add(1, 8, {'length': 5, 'id': 13, 'type': 'three'})
92+
call prop_add(1, 9, {'length': 5, 'id': 13, 'type': 'three'})
9393
call prop_add(1, 1, {'length': 13, 'id': 14, 'type': 'whole'})
9494
endfunc
9595

96-
let s:expected_props = [{'col': 1, 'length': 13, 'id': 14, 'type': 'whole', 'start': 1, 'end': 1},
96+
func Get_expected_props()
97+
return [
98+
\ {'col': 1, 'length': 13, 'id': 14, 'type': 'whole', 'start': 1, 'end': 1},
9799
\ {'col': 1, 'length': 3, 'id': 11, 'type': 'one', 'start': 1, 'end': 1},
98100
\ {'col': 5, 'length': 3, 'id': 12, 'type': 'two', 'start': 1, 'end': 1},
99-
\ {'col': 8, 'length': 5, 'id': 13, 'type': 'three', 'start': 1, 'end': 1},
101+
\ {'col': 9, 'length': 5, 'id': 13, 'type': 'three', 'start': 1, 'end': 1},
100102
\ ]
103+
endfunc
101104

102105
func Test_prop_add()
103106
new
104107
call AddPropTypes()
105108
call SetupPropsInFirstLine()
106-
call assert_equal(s:expected_props, prop_list(1))
109+
let expected_props = Get_expected_props()
110+
call assert_equal(expected_props, prop_list(1))
107111
call assert_fails("call prop_add(10, 1, {'length': 1, 'id': 14, 'type': 'whole'})", 'E966:')
108112
call assert_fails("call prop_add(1, 22, {'length': 1, 'id': 14, 'type': 'whole'})", 'E964:')
109113

110114
" Insert a line above, text props must still be there.
111115
call append(0, 'empty')
112-
call assert_equal(s:expected_props, prop_list(2))
116+
call assert_equal(expected_props, prop_list(2))
113117
" Delete a line above, text props must still be there.
114118
1del
115-
call assert_equal(s:expected_props, prop_list(1))
119+
call assert_equal(expected_props, prop_list(1))
116120

117121
" Prop without length or end column is zero length
118122
call prop_clear(1)
@@ -128,7 +132,7 @@ func Test_prop_remove()
128132
new
129133
call AddPropTypes()
130134
call SetupPropsInFirstLine()
131-
let props = deepcopy(s:expected_props)
135+
let props = Get_expected_props()
132136
call assert_equal(props, prop_list(1))
133137

134138
" remove by id
@@ -236,7 +240,7 @@ func Test_prop_clear()
236240
new
237241
call AddPropTypes()
238242
call SetupPropsInFirstLine()
239-
call assert_equal(s:expected_props, prop_list(1))
243+
call assert_equal(Get_expected_props(), prop_list(1))
240244

241245
call prop_clear(1)
242246
call assert_equal([], prop_list(1))
@@ -251,7 +255,7 @@ func Test_prop_clear_buf()
251255
call SetupPropsInFirstLine()
252256
let bufnr = bufnr('')
253257
wincmd w
254-
call assert_equal(s:expected_props, prop_list(1, {'bufnr': bufnr}))
258+
call assert_equal(Get_expected_props(), prop_list(1, {'bufnr': bufnr}))
255259

256260
call prop_clear(1, 1, {'bufnr': bufnr})
257261
call assert_equal([], prop_list(1, {'bufnr': bufnr}))
@@ -265,7 +269,7 @@ func Test_prop_setline()
265269
new
266270
call AddPropTypes()
267271
call SetupPropsInFirstLine()
268-
call assert_equal(s:expected_props, prop_list(1))
272+
call assert_equal(Get_expected_props(), prop_list(1))
269273

270274
call setline(1, 'foobar')
271275
call assert_equal([], prop_list(1))
@@ -280,7 +284,7 @@ func Test_prop_setbufline()
280284
call SetupPropsInFirstLine()
281285
let bufnr = bufnr('')
282286
wincmd w
283-
call assert_equal(s:expected_props, prop_list(1, {'bufnr': bufnr}))
287+
call assert_equal(Get_expected_props(), prop_list(1, {'bufnr': bufnr}))
284288

285289
call setbufline(bufnr, 1, 'foobar')
286290
call assert_equal([], prop_list(1, {'bufnr': bufnr}))
@@ -290,6 +294,54 @@ func Test_prop_setbufline()
290294
bwipe!
291295
endfunc
292296

297+
func Test_prop_substitute()
298+
new
299+
" Set first line to 'one two three'
300+
call AddPropTypes()
301+
call SetupPropsInFirstLine()
302+
let expected_props = Get_expected_props()
303+
call assert_equal(expected_props, prop_list(1))
304+
305+
" Change "n" in "one" to XX: 'oXXe two three'
306+
s/n/XX/
307+
let expected_props[0].length += 1
308+
let expected_props[1].length += 1
309+
let expected_props[2].col += 1
310+
let expected_props[3].col += 1
311+
call assert_equal(expected_props, prop_list(1))
312+
313+
" Delete "t" in "two" and "three" to XX: 'oXXe wo hree'
314+
s/t//g
315+
let expected_props[0].length -= 2
316+
let expected_props[2].length -= 1
317+
let expected_props[3].length -= 1
318+
let expected_props[3].col -= 1
319+
call assert_equal(expected_props, prop_list(1))
320+
321+
" Split the line by changing w to line break: 'oXXe ', 'o hree'
322+
" The long prop is split and spans both lines.
323+
" The props on "two" and "three" move to the next line.
324+
s/w/\r/
325+
let new_props = [
326+
\ copy(expected_props[0]),
327+
\ copy(expected_props[2]),
328+
\ copy(expected_props[3]),
329+
\ ]
330+
let expected_props[0].length = 5
331+
unlet expected_props[3]
332+
unlet expected_props[2]
333+
call assert_equal(expected_props, prop_list(1))
334+
335+
let new_props[0].length = 6
336+
let new_props[1].col = 1
337+
let new_props[1].length = 1
338+
let new_props[2].col = 3
339+
call assert_equal(new_props, prop_list(2))
340+
341+
call DeletePropTypes()
342+
bwipe!
343+
endfunc
344+
293345
" Setup a three line prop in lines 2 - 4.
294346
" Add short props in line 1 and 5.
295347
func Setup_three_line_prop()

src/textprop.c

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
*
1919
* TODO:
2020
* - Adjust text property column and length when text is inserted/deleted.
21+
* -> a :substitute with a multi-line match
22+
* -> search for changed_bytes() from ex_cmds.c
2123
* - Perhaps we only need TP_FLAG_CONT_NEXT and can drop TP_FLAG_CONT_PREV?
2224
* - Add an arrray for global_proptypes, to quickly lookup a prop type by ID
2325
* - Add an arrray for b_proptypes, to quickly lookup a prop type by ID
@@ -346,6 +348,34 @@ get_text_props(buf_T *buf, linenr_T lnum, char_u **props, int will_change)
346348
return (int)(proplen / sizeof(textprop_T));
347349
}
348350

351+
/*
352+
* Set the text properties for line "lnum" to "props" with length "len".
353+
* If "len" is zero text properties are removed, "props" is not used.
354+
* Any existing text properties are dropped.
355+
* Only works for the current buffer.
356+
*/
357+
static void
358+
set_text_props(linenr_T lnum, char_u *props, int len)
359+
{
360+
char_u *text;
361+
char_u *newtext;
362+
size_t textlen;
363+
364+
text = ml_get(lnum);
365+
textlen = STRLEN(text) + 1;
366+
newtext = alloc(textlen + len);
367+
if (newtext == NULL)
368+
return;
369+
mch_memmove(newtext, text, textlen);
370+
if (len > 0)
371+
mch_memmove(newtext + textlen, props, len);
372+
if (curbuf->b_ml.ml_flags & ML_LINE_DIRTY)
373+
vim_free(curbuf->b_ml.ml_line_ptr);
374+
curbuf->b_ml.ml_line_ptr = newtext;
375+
curbuf->b_ml.ml_line_len = textlen + len;
376+
curbuf->b_ml.ml_flags |= ML_LINE_DIRTY;
377+
}
378+
349379
static proptype_T *
350380
find_type_by_id(hashtab_T *ht, int id)
351381
{
@@ -976,4 +1006,69 @@ adjust_prop_columns(
9761006
}
9771007
}
9781008

1009+
/*
1010+
* Adjust text properties for a line that was split in two.
1011+
* "lnum" is the newly inserted line. The text properties are now on the line
1012+
* below it. "kept" is the number of bytes kept in the first line, while
1013+
* "deleted" is the number of bytes deleted.
1014+
*/
1015+
void
1016+
adjust_props_for_split(linenr_T lnum, int kept, int deleted)
1017+
{
1018+
char_u *props;
1019+
int count;
1020+
garray_T prevprop;
1021+
garray_T nextprop;
1022+
int i;
1023+
int skipped = kept + deleted;
1024+
1025+
if (!curbuf->b_has_textprop)
1026+
return;
1027+
count = get_text_props(curbuf, lnum + 1, &props, FALSE);
1028+
ga_init2(&prevprop, sizeof(textprop_T), 10);
1029+
ga_init2(&nextprop, sizeof(textprop_T), 10);
1030+
1031+
// Get the text properties, which are at "lnum + 1".
1032+
// Keep the relevant ones in the first line, reducing the length if needed.
1033+
// Copy the ones that include the split to the second line.
1034+
// Move the ones after the split to the second line.
1035+
for (i = 0; i < count; ++i)
1036+
{
1037+
textprop_T prop;
1038+
textprop_T *p;
1039+
1040+
// copy the prop to an aligned structure
1041+
mch_memmove(&prop, props + i * sizeof(textprop_T), sizeof(textprop_T));
1042+
1043+
if (prop.tp_col < kept && ga_grow(&prevprop, 1) == OK)
1044+
{
1045+
p = ((textprop_T *)prevprop.ga_data) + prevprop.ga_len;
1046+
*p = prop;
1047+
if (p->tp_col + p->tp_len >= kept)
1048+
p->tp_len = kept - p->tp_col;
1049+
++prevprop.ga_len;
1050+
}
1051+
1052+
if (prop.tp_col + prop.tp_len >= skipped && ga_grow(&nextprop, 1) == OK)
1053+
{
1054+
p = ((textprop_T *)nextprop.ga_data) + nextprop.ga_len;
1055+
*p = prop;
1056+
if (p->tp_col > skipped)
1057+
p->tp_col -= skipped - 1;
1058+
else
1059+
{
1060+
p->tp_len -= skipped - p->tp_col;
1061+
p->tp_col = 1;
1062+
}
1063+
++nextprop.ga_len;
1064+
}
1065+
}
1066+
1067+
set_text_props(lnum, prevprop.ga_data, prevprop.ga_len * sizeof(textprop_T));
1068+
ga_clear(&prevprop);
1069+
1070+
set_text_props(lnum + 1, nextprop.ga_data, nextprop.ga_len * sizeof(textprop_T));
1071+
ga_clear(&nextprop);
1072+
}
1073+
9791074
#endif // FEAT_TEXT_PROP

src/version.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -799,6 +799,8 @@ static char *(features[]) =
799799

800800
static int included_patches[] =
801801
{ /* Add new patch number below this line */
802+
/**/
803+
691,
802804
/**/
803805
690,
804806
/**/

0 commit comments

Comments
 (0)