1" Tests for defining text property types and adding text properties to the
2" buffer.
3
4source check.vim
5CheckFeature textprop
6
7source screendump.vim
8
9" test length zero
10
11func Test_proptype_global()
12  call prop_type_add('comment', {'highlight': 'Directory', 'priority': 123, 'start_incl': 1, 'end_incl': 1})
13  let proptypes = prop_type_list()
14  call assert_equal(1, len(proptypes))
15  call assert_equal('comment', proptypes[0])
16
17  let proptype = prop_type_get('comment')
18  call assert_equal('Directory', proptype['highlight'])
19  call assert_equal(123, proptype['priority'])
20  call assert_equal(1, proptype['start_incl'])
21  call assert_equal(1, proptype['end_incl'])
22
23  call prop_type_delete('comment')
24  call assert_equal(0, len(prop_type_list()))
25
26  call prop_type_add('one', {})
27  call assert_equal(1, len(prop_type_list()))
28  let proptype = 'one'->prop_type_get()
29  call assert_false(has_key(proptype, 'highlight'))
30  call assert_equal(0, proptype['priority'])
31  call assert_equal(0, proptype['start_incl'])
32  call assert_equal(0, proptype['end_incl'])
33
34  call prop_type_add('two', {})
35  call assert_equal(2, len(prop_type_list()))
36  call prop_type_delete('one')
37  call assert_equal(1, len(prop_type_list()))
38  call prop_type_delete('two')
39  call assert_equal(0, len(prop_type_list()))
40endfunc
41
42func Test_proptype_buf()
43  let bufnr = bufnr('')
44  call prop_type_add('comment', {'bufnr': bufnr, 'highlight': 'Directory', 'priority': 123, 'start_incl': 1, 'end_incl': 1})
45  let proptypes = prop_type_list({'bufnr': bufnr})
46  call assert_equal(1, len(proptypes))
47  call assert_equal('comment', proptypes[0])
48
49  let proptype = prop_type_get('comment', {'bufnr': bufnr})
50  call assert_equal('Directory', proptype['highlight'])
51  call assert_equal(123, proptype['priority'])
52  call assert_equal(1, proptype['start_incl'])
53  call assert_equal(1, proptype['end_incl'])
54
55  call prop_type_delete('comment', {'bufnr': bufnr})
56  call assert_equal(0, len({'bufnr': bufnr}->prop_type_list()))
57
58  call prop_type_add('one', {'bufnr': bufnr})
59  let proptype = prop_type_get('one', {'bufnr': bufnr})
60  call assert_false(has_key(proptype, 'highlight'))
61  call assert_equal(0, proptype['priority'])
62  call assert_equal(0, proptype['start_incl'])
63  call assert_equal(0, proptype['end_incl'])
64
65  call prop_type_add('two', {'bufnr': bufnr})
66  call assert_equal(2, len(prop_type_list({'bufnr': bufnr})))
67  call prop_type_delete('one', {'bufnr': bufnr})
68  call assert_equal(1, len(prop_type_list({'bufnr': bufnr})))
69  call prop_type_delete('two', {'bufnr': bufnr})
70  call assert_equal(0, len(prop_type_list({'bufnr': bufnr})))
71
72  call assert_fails("call prop_type_add('one', {'bufnr': 98764})", "E158:")
73endfunc
74
75func AddPropTypes()
76  call prop_type_add('one', {})
77  call prop_type_add('two', {})
78  call prop_type_add('three', {})
79  call prop_type_add('whole', {})
80endfunc
81
82func DeletePropTypes()
83  call prop_type_delete('one')
84  call prop_type_delete('two')
85  call prop_type_delete('three')
86  call prop_type_delete('whole')
87endfunc
88
89func SetupPropsInFirstLine()
90  call setline(1, 'one two three')
91  call prop_add(1, 1, {'length': 3, 'id': 11, 'type': 'one'})
92  eval 1->prop_add(5, {'length': 3, 'id': 12, 'type': 'two'})
93  call prop_add(1, 9, {'length': 5, 'id': 13, 'type': 'three'})
94  call prop_add(1, 1, {'length': 13, 'id': 14, 'type': 'whole'})
95endfunc
96
97func Get_expected_props()
98  return [
99      \ {'col': 1, 'length': 13, 'id': 14, 'type': 'whole', 'start': 1, 'end': 1},
100      \ {'col': 1, 'length': 3, 'id': 11, 'type': 'one', 'start': 1, 'end': 1},
101      \ {'col': 5, 'length': 3, 'id': 12, 'type': 'two', 'start': 1, 'end': 1},
102      \ {'col': 9, 'length': 5, 'id': 13, 'type': 'three', 'start': 1, 'end': 1},
103      \ ]
104endfunc
105
106func Test_prop_find()
107  new
108  call setline(1, ['one one one', 'twotwo', 'three', 'fourfour', 'five', 'sixsix'])
109
110  " Add two text props on lines 1 and 5, and one spanning lines 2 to 4.
111  call prop_type_add('prop_name', {'highlight': 'Directory'})
112  call prop_add(1, 5, {'type': 'prop_name', 'id': 10, 'length': 3})
113  call prop_add(2, 4, {'type': 'prop_name', 'id': 11, 'end_lnum': 4, 'end_col': 9})
114  call prop_add(5, 4, {'type': 'prop_name', 'id': 12, 'length': 1})
115
116  let expected = [
117    \ {'lnum': 1, 'col': 5, 'length': 3, 'id': 10, 'type': 'prop_name', 'start': 1, 'end': 1},
118    \ {'lnum': 2, 'col': 4, 'id': 11, 'type': 'prop_name', 'start': 1, 'end': 0},
119    \ {'lnum': 5, 'col': 4, 'length': 1, 'id': 12, 'type': 'prop_name', 'start': 1, 'end': 1}
120    \ ]
121
122  " Starting at line 5 col 1 this should find the prop at line 5 col 4.
123  call cursor(5,1)
124  let result = prop_find({'type': 'prop_name'}, 'f')
125  call assert_equal(expected[2], result)
126
127  " With skipstart left at false (default), this should find the prop at line
128  " 5 col 4.
129  let result = prop_find({'type': 'prop_name', 'lnum': 5, 'col': 4}, 'b')
130  call assert_equal(expected[2], result)
131
132  " With skipstart set to true, this should skip the prop at line 5 col 4.
133  let result = prop_find({'type': 'prop_name', 'lnum': 5, 'col': 4, 'skipstart': 1}, 'b')
134  unlet result.length
135  call assert_equal(expected[1], result)
136
137  " Search backwards from line 1 col 10 to find the prop on the same line.
138  let result = prop_find({'type': 'prop_name', 'lnum': 1, 'col': 10}, 'b')
139  call assert_equal(expected[0], result)
140
141  " with skipstart set to false, if the start position is anywhere between the
142  " start and end lines of a text prop (searching forward or backward), the
143  " result should be the prop on the first line (the line with 'start' set to 1).
144  call cursor(3,1)
145  let result = prop_find({'type': 'prop_name'}, 'f')
146  unlet result.length
147  call assert_equal(expected[1], result)
148  let result = prop_find({'type': 'prop_name'}, 'b')
149  unlet result.length
150  call assert_equal(expected[1], result)
151
152  " with skipstart set to true, if the start position is anywhere between the
153  " start and end lines of a text prop (searching forward or backward), all lines
154  " of the prop will be skipped.
155  let result = prop_find({'type': 'prop_name', 'skipstart': 1}, 'b')
156  call assert_equal(expected[0], result)
157  let result = prop_find({'type': 'prop_name', 'skipstart': 1}, 'f')
158  call assert_equal(expected[2], result)
159
160  " Use skipstart to search through all props with type name 'prop_name'.
161  " First forward...
162  let lnum = 1
163  let col = 1
164  let i = 0
165  for exp in expected
166    let result = prop_find({'type': 'prop_name', 'lnum': lnum, 'col': col, 'skipstart': 1}, 'f')
167    if !has_key(exp, "length")
168      unlet result.length
169    endif
170    call assert_equal(exp, result)
171    let lnum = result.lnum
172    let col = result.col
173    let i = i + 1
174  endfor
175
176  " ...then backwards.
177  let lnum = 6
178  let col = 4
179  let i = 2
180  while i >= 0
181    let result = prop_find({'type': 'prop_name', 'lnum': lnum, 'col': col, 'skipstart': 1}, 'b')
182    if !has_key(expected[i], "length")
183      unlet result.length
184    endif
185    call assert_equal(expected[i], result)
186    let lnum = result.lnum
187    let col = result.col
188    let i = i - 1
189  endwhile
190
191  " Starting from line 6 col 1 search backwards for prop with id 10.
192  call cursor(6,1)
193  let result = prop_find({'id': 10, 'skipstart': 1}, 'b')
194  call assert_equal(expected[0], result)
195
196  " Starting from line 1 col 1 search forwards for prop with id 12.
197  call cursor(1,1)
198  let result = prop_find({'id': 12}, 'f')
199  call assert_equal(expected[2], result)
200
201  " Search for a prop with an unknown id.
202  let result = prop_find({'id': 999}, 'f')
203  call assert_equal({}, result)
204
205  " Search backwards from the proceeding position of the prop with id 11
206  " (at line num 2 col 4). This should return an empty dict.
207  let result = prop_find({'id': 11, 'lnum': 2, 'col': 3}, 'b')
208  call assert_equal({}, result)
209
210  " When lnum is given and col is omitted, use column 1.
211  let result = prop_find({'type': 'prop_name', 'lnum': 1}, 'f')
212  call assert_equal(expected[0], result)
213
214  call prop_clear(1,6)
215  call prop_type_delete('prop_name')
216endfunc
217
218func Test_prop_add()
219  new
220  call AddPropTypes()
221  call SetupPropsInFirstLine()
222  let expected_props = Get_expected_props()
223  call assert_equal(expected_props, prop_list(1))
224  call assert_fails("call prop_add(10, 1, {'length': 1, 'id': 14, 'type': 'whole'})", 'E966:')
225  call assert_fails("call prop_add(1, 22, {'length': 1, 'id': 14, 'type': 'whole'})", 'E964:')
226
227  " Insert a line above, text props must still be there.
228  call append(0, 'empty')
229  call assert_equal(expected_props, prop_list(2))
230  " Delete a line above, text props must still be there.
231  1del
232  call assert_equal(expected_props, prop_list(1))
233
234  " Prop without length or end column is zero length
235  call prop_clear(1)
236  call prop_add(1, 5, {'type': 'two'})
237  let expected = [{'col': 5, 'length': 0, 'type': 'two', 'id': 0, 'start': 1, 'end': 1}]
238  call assert_equal(expected, prop_list(1))
239
240  call assert_fails("call prop_add(1, 5, {'type': 'two', 'bufnr': 234343})", 'E158:')
241
242  call DeletePropTypes()
243  bwipe!
244endfunc
245
246func Test_prop_remove()
247  new
248  call AddPropTypes()
249  call SetupPropsInFirstLine()
250  let props = Get_expected_props()
251  call assert_equal(props, prop_list(1))
252
253  " remove by id
254  call assert_equal(1, {'id': 12}->prop_remove(1))
255  unlet props[2]
256  call assert_equal(props, prop_list(1))
257
258  " remove by type
259  call assert_equal(1, prop_remove({'type': 'one'}, 1))
260  unlet props[1]
261  call assert_equal(props, prop_list(1))
262
263  " remove from unknown buffer
264  call assert_fails("call prop_remove({'type': 'one', 'bufnr': 123456}, 1)", 'E158:')
265
266  call DeletePropTypes()
267  bwipe!
268endfunc
269
270func SetupOneLine()
271  call setline(1, 'xonex xtwoxx')
272  normal gg0
273  call AddPropTypes()
274  call prop_add(1, 2, {'length': 3, 'id': 11, 'type': 'one'})
275  call prop_add(1, 8, {'length': 3, 'id': 12, 'type': 'two'})
276  let expected = [
277	\ {'col': 2, 'length': 3, 'id': 11, 'type': 'one', 'start': 1, 'end': 1},
278	\ {'col': 8, 'length': 3, 'id': 12, 'type': 'two', 'start': 1, 'end': 1},
279	\]
280  call assert_equal(expected, prop_list(1))
281  return expected
282endfunc
283
284func Test_prop_add_remove_buf()
285  new
286  let bufnr = bufnr('')
287  call AddPropTypes()
288  for lnum in range(1, 4)
289    call setline(lnum, 'one two three')
290  endfor
291  wincmd w
292  for lnum in range(1, 4)
293    call prop_add(lnum, 1, {'length': 3, 'id': 11, 'type': 'one', 'bufnr': bufnr})
294    call prop_add(lnum, 5, {'length': 3, 'id': 12, 'type': 'two', 'bufnr': bufnr})
295    call prop_add(lnum, 11, {'length': 3, 'id': 13, 'type': 'three', 'bufnr': bufnr})
296  endfor
297
298  let props = [
299	\ {'col': 1, 'length': 3, 'id': 11, 'type': 'one', 'start': 1, 'end': 1},
300	\ {'col': 5, 'length': 3, 'id': 12, 'type': 'two', 'start': 1, 'end': 1},
301	\ {'col': 11, 'length': 3, 'id': 13, 'type': 'three', 'start': 1, 'end': 1},
302	\]
303  call assert_equal(props, prop_list(1, {'bufnr': bufnr}))
304
305  " remove by id
306  let before_props = deepcopy(props)
307  unlet props[1]
308
309  call prop_remove({'id': 12, 'bufnr': bufnr}, 1)
310  call assert_equal(props, prop_list(1, {'bufnr': bufnr}))
311  call assert_equal(before_props, prop_list(2, {'bufnr': bufnr}))
312  call assert_equal(before_props, prop_list(3, {'bufnr': bufnr}))
313  call assert_equal(before_props, prop_list(4, {'bufnr': bufnr}))
314
315  call prop_remove({'id': 12, 'bufnr': bufnr}, 3, 4)
316  call assert_equal(props, prop_list(1, {'bufnr': bufnr}))
317  call assert_equal(before_props, prop_list(2, {'bufnr': bufnr}))
318  call assert_equal(props, prop_list(3, {'bufnr': bufnr}))
319  call assert_equal(props, prop_list(4, {'bufnr': bufnr}))
320
321  call prop_remove({'id': 12, 'bufnr': bufnr})
322  for lnum in range(1, 4)
323    call assert_equal(props, prop_list(lnum, {'bufnr': bufnr}))
324  endfor
325
326  " remove by type
327  let before_props = deepcopy(props)
328  unlet props[0]
329
330  call prop_remove({'type': 'one', 'bufnr': bufnr}, 1)
331  call assert_equal(props, prop_list(1, {'bufnr': bufnr}))
332  call assert_equal(before_props, prop_list(2, {'bufnr': bufnr}))
333  call assert_equal(before_props, prop_list(3, {'bufnr': bufnr}))
334  call assert_equal(before_props, prop_list(4, {'bufnr': bufnr}))
335
336  call prop_remove({'type': 'one', 'bufnr': bufnr}, 3, 4)
337  call assert_equal(props, prop_list(1, {'bufnr': bufnr}))
338  call assert_equal(before_props, prop_list(2, {'bufnr': bufnr}))
339  call assert_equal(props, prop_list(3, {'bufnr': bufnr}))
340  call assert_equal(props, prop_list(4, {'bufnr': bufnr}))
341
342  call prop_remove({'type': 'one', 'bufnr': bufnr})
343  for lnum in range(1, 4)
344    call assert_equal(props, prop_list(lnum, {'bufnr': bufnr}))
345  endfor
346
347  call DeletePropTypes()
348  wincmd w
349  bwipe!
350endfunc
351
352func Test_prop_backspace()
353  new
354  set bs=2
355  let expected = SetupOneLine() " 'xonex xtwoxx'
356
357  exe "normal 0li\<BS>\<Esc>fxli\<BS>\<Esc>"
358  call assert_equal('one xtwoxx', getline(1))
359  let expected[0].col = 1
360  let expected[1].col = 6
361  call assert_equal(expected, prop_list(1))
362
363  call DeletePropTypes()
364  bwipe!
365  set bs&
366endfunc
367
368func Test_prop_replace()
369  new
370  set bs=2
371  let expected = SetupOneLine() " 'xonex xtwoxx'
372
373  exe "normal 0Ryyy\<Esc>"
374  call assert_equal('yyyex xtwoxx', getline(1))
375  call assert_equal(expected, prop_list(1))
376
377  exe "normal ftRyy\<BS>"
378  call assert_equal('yyyex xywoxx', getline(1))
379  call assert_equal(expected, prop_list(1))
380
381  exe "normal 0fwRyy\<BS>"
382  call assert_equal('yyyex xyyoxx', getline(1))
383  call assert_equal(expected, prop_list(1))
384
385  exe "normal 0foRyy\<BS>\<BS>"
386  call assert_equal('yyyex xyyoxx', getline(1))
387  call assert_equal(expected, prop_list(1))
388
389  call DeletePropTypes()
390  bwipe!
391  set bs&
392endfunc
393
394func Test_prop_open_line()
395  new
396
397  " open new line, props stay in top line
398  let expected = SetupOneLine() " 'xonex xtwoxx'
399  exe "normal o\<Esc>"
400  call assert_equal('xonex xtwoxx', getline(1))
401  call assert_equal('', getline(2))
402  call assert_equal(expected, prop_list(1))
403  call DeletePropTypes()
404
405  " move all props to next line
406  let expected = SetupOneLine() " 'xonex xtwoxx'
407  exe "normal 0i\<CR>\<Esc>"
408  call assert_equal('', getline(1))
409  call assert_equal('xonex xtwoxx', getline(2))
410  call assert_equal(expected, prop_list(2))
411  call DeletePropTypes()
412
413  " split just before prop, move all props to next line
414  let expected = SetupOneLine() " 'xonex xtwoxx'
415  exe "normal 0li\<CR>\<Esc>"
416  call assert_equal('x', getline(1))
417  call assert_equal('onex xtwoxx', getline(2))
418  let expected[0].col -= 1
419  let expected[1].col -= 1
420  call assert_equal(expected, prop_list(2))
421  call DeletePropTypes()
422
423  " split inside prop, split first prop
424  let expected = SetupOneLine() " 'xonex xtwoxx'
425  exe "normal 0lli\<CR>\<Esc>"
426  call assert_equal('xo', getline(1))
427  call assert_equal('nex xtwoxx', getline(2))
428  let exp_first = [deepcopy(expected[0])]
429  let exp_first[0].length = 1
430  call assert_equal(exp_first, prop_list(1))
431  let expected[0].col = 1
432  let expected[0].length = 2
433  let expected[1].col -= 2
434  call assert_equal(expected, prop_list(2))
435  call DeletePropTypes()
436
437  " split just after first prop, second prop move to next line
438  let expected = SetupOneLine() " 'xonex xtwoxx'
439  exe "normal 0fea\<CR>\<Esc>"
440  call assert_equal('xone', getline(1))
441  call assert_equal('x xtwoxx', getline(2))
442  let exp_first = expected[0:0]
443  call assert_equal(exp_first, prop_list(1))
444  let expected = expected[1:1]
445  let expected[0].col -= 4
446  call assert_equal(expected, prop_list(2))
447  call DeletePropTypes()
448
449  bwipe!
450  set bs&
451endfunc
452
453func Test_prop_clear()
454  new
455  call AddPropTypes()
456  call SetupPropsInFirstLine()
457  call assert_equal(Get_expected_props(), prop_list(1))
458
459  eval 1->prop_clear()
460  call assert_equal([], 1->prop_list())
461
462  call DeletePropTypes()
463  bwipe!
464endfunc
465
466func Test_prop_clear_buf()
467  new
468  call AddPropTypes()
469  call SetupPropsInFirstLine()
470  let bufnr = bufnr('')
471  wincmd w
472  call assert_equal(Get_expected_props(), prop_list(1, {'bufnr': bufnr}))
473
474  call prop_clear(1, 1, {'bufnr': bufnr})
475  call assert_equal([], prop_list(1, {'bufnr': bufnr}))
476
477  wincmd w
478  call DeletePropTypes()
479  bwipe!
480endfunc
481
482func Test_prop_setline()
483  new
484  call AddPropTypes()
485  call SetupPropsInFirstLine()
486  call assert_equal(Get_expected_props(), prop_list(1))
487
488  call setline(1, 'foobar')
489  call assert_equal([], prop_list(1))
490
491  call DeletePropTypes()
492  bwipe!
493endfunc
494
495func Test_prop_setbufline()
496  new
497  call AddPropTypes()
498  call SetupPropsInFirstLine()
499  let bufnr = bufnr('')
500  wincmd w
501  call assert_equal(Get_expected_props(), prop_list(1, {'bufnr': bufnr}))
502
503  call setbufline(bufnr, 1, 'foobar')
504  call assert_equal([], prop_list(1, {'bufnr': bufnr}))
505
506  wincmd w
507  call DeletePropTypes()
508  bwipe!
509endfunc
510
511func Test_prop_substitute()
512  new
513  " Set first line to 'one two three'
514  call AddPropTypes()
515  call SetupPropsInFirstLine()
516  let expected_props = Get_expected_props()
517  call assert_equal(expected_props, prop_list(1))
518
519  " Change "n" in "one" to XX: 'oXXe two three'
520  s/n/XX/
521  let expected_props[0].length += 1
522  let expected_props[1].length += 1
523  let expected_props[2].col += 1
524  let expected_props[3].col += 1
525  call assert_equal(expected_props, prop_list(1))
526
527  " Delete "t" in "two" and "three" to XX: 'oXXe wo hree'
528  s/t//g
529  let expected_props[0].length -= 2
530  let expected_props[2].length -= 1
531  let expected_props[3].length -= 1
532  let expected_props[3].col -= 1
533  call assert_equal(expected_props, prop_list(1))
534
535  " Split the line by changing w to line break: 'oXXe ', 'o hree'
536  " The long prop is split and spans both lines.
537  " The props on "two" and "three" move to the next line.
538  s/w/\r/
539  let new_props = [
540	\ copy(expected_props[0]),
541	\ copy(expected_props[2]),
542	\ copy(expected_props[3]),
543	\ ]
544  let expected_props[0].length = 5
545  unlet expected_props[3]
546  unlet expected_props[2]
547  call assert_equal(expected_props, prop_list(1))
548
549  let new_props[0].length = 6
550  let new_props[1].col = 1
551  let new_props[1].length = 1
552  let new_props[2].col = 3
553  call assert_equal(new_props, prop_list(2))
554
555  call DeletePropTypes()
556  bwipe!
557endfunc
558
559func Test_prop_change_indent()
560  call prop_type_add('comment', {'highlight': 'Directory'})
561  new
562  call setline(1, ['    xxx', 'yyyyy'])
563  call prop_add(2, 2, {'length': 2, 'type': 'comment'})
564  let expect = {'col': 2, 'length': 2, 'type': 'comment', 'start': 1, 'end': 1, 'id': 0}
565  call assert_equal([expect], prop_list(2))
566
567  set shiftwidth=3
568  normal 2G>>
569  call assert_equal('   yyyyy', getline(2))
570  let expect.col += 3
571  call assert_equal([expect], prop_list(2))
572
573  normal 2G==
574  call assert_equal('    yyyyy', getline(2))
575  let expect.col = 6
576  call assert_equal([expect], prop_list(2))
577
578  call prop_clear(2)
579  call prop_add(2, 2, {'length': 5, 'type': 'comment'})
580  let expect.col = 2
581  let expect.length = 5
582  call assert_equal([expect], prop_list(2))
583
584  normal 2G<<
585  call assert_equal(' yyyyy', getline(2))
586  let expect.length = 2
587  call assert_equal([expect], prop_list(2))
588
589  set shiftwidth&
590  call prop_type_delete('comment')
591endfunc
592
593" Setup a three line prop in lines 2 - 4.
594" Add short props in line 1 and 5.
595func Setup_three_line_prop()
596  new
597  call setline(1, ['one', 'twotwo', 'three', 'fourfour', 'five'])
598  call prop_add(1, 2, {'length': 1, 'type': 'comment'})
599  call prop_add(2, 4, {'end_lnum': 4, 'end_col': 5, 'type': 'comment'})
600  call prop_add(5, 2, {'length': 1, 'type': 'comment'})
601endfunc
602
603func Test_prop_multiline()
604  eval 'comment'->prop_type_add({'highlight': 'Directory'})
605  new
606  call setline(1, ['xxxxxxx', 'yyyyyyyyy', 'zzzzzzzz'])
607
608  " start halfway line 1, end halfway line 3
609  call prop_add(1, 3, {'end_lnum': 3, 'end_col': 5, 'type': 'comment'})
610  let expect1 = {'col': 3, 'length': 6, 'type': 'comment', 'start': 1, 'end': 0, 'id': 0}
611  call assert_equal([expect1], prop_list(1))
612  let expect2 = {'col': 1, 'length': 10, 'type': 'comment', 'start': 0, 'end': 0, 'id': 0}
613  call assert_equal([expect2], prop_list(2))
614  let expect3 = {'col': 1, 'length': 4, 'type': 'comment', 'start': 0, 'end': 1, 'id': 0}
615  call assert_equal([expect3], prop_list(3))
616  call prop_clear(1, 3)
617
618  " include all three lines
619  call prop_add(1, 1, {'end_lnum': 3, 'end_col': 999, 'type': 'comment'})
620  let expect1.col = 1
621  let expect1.length = 8
622  call assert_equal([expect1], prop_list(1))
623  call assert_equal([expect2], prop_list(2))
624  let expect3.length = 9
625  call assert_equal([expect3], prop_list(3))
626  call prop_clear(1, 3)
627
628  bwipe!
629
630  " Test deleting the first line of a multi-line prop.
631  call Setup_three_line_prop()
632  let expect_short = {'col': 2, 'length': 1, 'type': 'comment', 'start': 1, 'end': 1, 'id': 0}
633  call assert_equal([expect_short], prop_list(1))
634  let expect2 = {'col': 4, 'length': 4, 'type': 'comment', 'start': 1, 'end': 0, 'id': 0}
635  call assert_equal([expect2], prop_list(2))
636  2del
637  call assert_equal([expect_short], prop_list(1))
638  let expect2 = {'col': 1, 'length': 6, 'type': 'comment', 'start': 1, 'end': 0, 'id': 0}
639  call assert_equal([expect2], prop_list(2))
640  bwipe!
641
642  " Test deleting the last line of a multi-line prop.
643  call Setup_three_line_prop()
644  let expect3 = {'col': 1, 'length': 6, 'type': 'comment', 'start': 0, 'end': 0, 'id': 0}
645  call assert_equal([expect3], prop_list(3))
646  let expect4 = {'col': 1, 'length': 4, 'type': 'comment', 'start': 0, 'end': 1, 'id': 0}
647  call assert_equal([expect4], prop_list(4))
648  4del
649  let expect3.end = 1
650  call assert_equal([expect3], prop_list(3))
651  call assert_equal([expect_short], prop_list(4))
652  bwipe!
653
654  " Test appending a line below the multi-line text prop start.
655  call Setup_three_line_prop()
656  let expect2 = {'col': 4, 'length': 4, 'type': 'comment', 'start': 1, 'end': 0, 'id': 0}
657  call assert_equal([expect2], prop_list(2))
658  call append(2, "new line")
659  call assert_equal([expect2], prop_list(2))
660  let expect3 = {'col': 1, 'length': 9, 'type': 'comment', 'start': 0, 'end': 0, 'id': 0}
661  call assert_equal([expect3], prop_list(3))
662  bwipe!
663
664  call prop_type_delete('comment')
665endfunc
666
667func Test_prop_line2byte()
668  call prop_type_add('comment', {'highlight': 'Directory'})
669  new
670  call setline(1, ['line1', 'second line', ''])
671  set ff=unix
672  call assert_equal(19, line2byte(3))
673  call prop_add(1, 1, {'end_col': 3, 'type': 'comment'})
674  call assert_equal(19, line2byte(3))
675
676  bwipe!
677  call prop_type_delete('comment')
678endfunc
679
680func Test_prop_byte2line()
681  new
682  set ff=unix
683  call setline(1, ['one one', 'two two', 'three three', 'four four', 'five'])
684  call assert_equal(4, byte2line(line2byte(4)))
685  call assert_equal(5, byte2line(line2byte(5)))
686
687  call prop_type_add('prop', {'highlight': 'Directory'})
688  call prop_add(3, 1, {'length': 5, 'type': 'prop'})
689  call assert_equal(4, byte2line(line2byte(4)))
690  call assert_equal(5, byte2line(line2byte(5)))
691
692  bwipe!
693  call prop_type_delete('prop')
694endfunc
695
696func Test_prop_undo()
697  new
698  call prop_type_add('comment', {'highlight': 'Directory'})
699  call setline(1, ['oneone', 'twotwo', 'three'])
700  " Set 'undolevels' to break changes into undo-able pieces.
701  set ul&
702
703  call prop_add(1, 3, {'end_col': 5, 'type': 'comment'})
704  let expected = [{'col': 3, 'length': 2, 'id': 0, 'type': 'comment', 'start': 1, 'end': 1} ]
705  call assert_equal(expected, prop_list(1))
706
707  " Insert a character, then undo.
708  exe "normal 0lllix\<Esc>"
709  set ul&
710  let expected[0].length = 3
711  call assert_equal(expected, prop_list(1))
712  undo
713  let expected[0].length = 2
714  call assert_equal(expected, prop_list(1))
715
716  " Delete a character, then undo
717  exe "normal 0lllx"
718  set ul&
719  let expected[0].length = 1
720  call assert_equal(expected, prop_list(1))
721  undo
722  let expected[0].length = 2
723  call assert_equal(expected, prop_list(1))
724
725  " Delete the line, then undo
726  1d
727  set ul&
728  call assert_equal([], prop_list(1))
729  undo
730  call assert_equal(expected, prop_list(1))
731
732  " Insert a character, delete two characters, then undo with "U"
733  exe "normal 0lllix\<Esc>"
734  set ul&
735  let expected[0].length = 3
736  call assert_equal(expected, prop_list(1))
737  exe "normal 0lllxx"
738  set ul&
739  let expected[0].length = 1
740  call assert_equal(expected, prop_list(1))
741  normal U
742  let expected[0].length = 2
743  call assert_equal(expected, prop_list(1))
744
745  " substitute a word, then undo
746  call setline(1, 'the number 123 is highlighted.')
747  call prop_add(1, 12, {'length': 3, 'type': 'comment'})
748  let expected = [{'col': 12, 'length': 3, 'id': 0, 'type': 'comment', 'start': 1, 'end': 1} ]
749  call assert_equal(expected, prop_list(1))
750  set ul&
751  1s/number/foo
752  let expected[0].col = 9
753  call assert_equal(expected, prop_list(1))
754  undo
755  let expected[0].col = 12
756  call assert_equal(expected, prop_list(1))
757  call prop_clear(1)
758
759  " substitute with backslash
760  call setline(1, 'the number 123 is highlighted.')
761  call prop_add(1, 12, {'length': 3, 'type': 'comment'})
762  let expected = [{'col': 12, 'length': 3, 'id': 0, 'type': 'comment', 'start': 1, 'end': 1} ]
763  call assert_equal(expected, prop_list(1))
764  1s/the/\The
765  call assert_equal(expected, prop_list(1))
766  1s/^/\\
767  let expected[0].col += 1
768  call assert_equal(expected, prop_list(1))
769  1s/^/\~
770  let expected[0].col += 1
771  call assert_equal(expected, prop_list(1))
772  1s/123/12\\3
773  let expected[0].length += 1
774  call assert_equal(expected, prop_list(1))
775  call prop_clear(1)
776
777  bwipe!
778  call prop_type_delete('comment')
779endfunc
780
781func Test_prop_delete_text()
782  new
783  call prop_type_add('comment', {'highlight': 'Directory'})
784  call setline(1, ['oneone', 'twotwo', 'three'])
785
786  " zero length property
787  call prop_add(1, 3, {'type': 'comment'})
788  let expected = [{'col': 3, 'length': 0, 'id': 0, 'type': 'comment', 'start': 1, 'end': 1} ]
789  call assert_equal(expected, prop_list(1))
790
791  " delete one char moves the property
792  normal! x
793  let expected = [{'col': 2, 'length': 0, 'id': 0, 'type': 'comment', 'start': 1, 'end': 1} ]
794  call assert_equal(expected, prop_list(1))
795
796  " delete char of the property has no effect
797  normal! lx
798  let expected = [{'col': 2, 'length': 0, 'id': 0, 'type': 'comment', 'start': 1, 'end': 1} ]
799  call assert_equal(expected, prop_list(1))
800
801  " delete more chars moves property to first column, is not deleted
802  normal! 0xxxx
803  let expected = [{'col': 1, 'length': 0, 'id': 0, 'type': 'comment', 'start': 1, 'end': 1} ]
804  call assert_equal(expected, prop_list(1))
805
806  bwipe!
807  call prop_type_delete('comment')
808endfunc
809
810" screenshot test with textprop highlighting
811func Test_textprop_screenshot_various()
812  CheckScreendump
813  " The Vim running in the terminal needs to use utf-8.
814  if g:orig_encoding != 'utf-8'
815    throw 'Skipped: not using utf-8'
816  endif
817  call writefile([
818	\ "call setline(1, ["
819	\	.. "'One two',"
820	\	.. "'Numbér 123 änd thœn 4¾7.',"
821	\	.. "'--aa--bb--cc--dd--',"
822	\	.. "'// comment with error in it',"
823	\	.. "'first line',"
824	\	.. "'  second line  ',"
825	\	.. "'third line',"
826	\	.. "'   fourth line',"
827	\	.. "])",
828	\ "hi NumberProp ctermfg=blue",
829	\ "hi LongProp ctermbg=yellow",
830	\ "hi BackgroundProp ctermbg=lightgrey",
831	\ "hi UnderlineProp cterm=underline",
832	\ "call prop_type_add('number', {'highlight': 'NumberProp'})",
833	\ "call prop_type_add('long', {'highlight': 'NumberProp'})",
834	\ "call prop_type_change('long', {'highlight': 'LongProp'})",
835	\ "call prop_type_add('start', {'highlight': 'NumberProp', 'start_incl': 1})",
836	\ "call prop_type_add('end', {'highlight': 'NumberProp', 'end_incl': 1})",
837	\ "call prop_type_add('both', {'highlight': 'NumberProp', 'start_incl': 1, 'end_incl': 1})",
838	\ "call prop_type_add('background', {'highlight': 'BackgroundProp', 'combine': 0})",
839	\ "call prop_type_add('backgroundcomb', {'highlight': 'NumberProp', 'combine': 1})",
840	\ "eval 'backgroundcomb'->prop_type_change({'highlight': 'BackgroundProp'})",
841	\ "call prop_type_add('error', {'highlight': 'UnderlineProp'})",
842	\ "call prop_add(1, 4, {'end_lnum': 3, 'end_col': 3, 'type': 'long'})",
843	\ "call prop_add(2, 9, {'length': 3, 'type': 'number'})",
844	\ "call prop_add(2, 24, {'length': 4, 'type': 'number'})",
845	\ "call prop_add(3, 3, {'length': 2, 'type': 'number'})",
846	\ "call prop_add(3, 7, {'length': 2, 'type': 'start'})",
847	\ "call prop_add(3, 11, {'length': 2, 'type': 'end'})",
848	\ "call prop_add(3, 15, {'length': 2, 'type': 'both'})",
849	\ "call prop_add(4, 6, {'length': 3, 'type': 'background'})",
850	\ "call prop_add(4, 12, {'length': 10, 'type': 'backgroundcomb'})",
851	\ "call prop_add(4, 17, {'length': 5, 'type': 'error'})",
852	\ "call prop_add(5, 7, {'length': 4, 'type': 'long'})",
853	\ "call prop_add(6, 1, {'length': 8, 'type': 'long'})",
854	\ "call prop_add(8, 1, {'length': 1, 'type': 'long'})",
855	\ "call prop_add(8, 11, {'length': 4, 'type': 'long'})",
856	\ "set number cursorline",
857	\ "hi clear SpellBad",
858	\ "set spell",
859	\ "syn match Comment '//.*'",
860	\ "hi Comment ctermfg=green",
861	\ "normal 3G0llix\<Esc>lllix\<Esc>lllix\<Esc>lllix\<Esc>lllix\<Esc>lllix\<Esc>lllix\<Esc>lllix\<Esc>",
862	\ "normal 3G0lli\<BS>\<Esc>",
863	\ "normal 6G0i\<BS>\<Esc>",
864	\ "normal 3J",
865	\ "normal 3G",
866	\], 'XtestProp')
867  let buf = RunVimInTerminal('-S XtestProp', {'rows': 8})
868  call VerifyScreenDump(buf, 'Test_textprop_01', {})
869
870  " clean up
871  call StopVimInTerminal(buf)
872  call delete('XtestProp')
873endfunc
874
875func RunTestVisualBlock(width, dump)
876  call writefile([
877	\ "call setline(1, ["
878	\	.. "'xxxxxxxxx 123 x',"
879	\	.. "'xxxxxxxx 123 x',"
880	\	.. "'xxxxxxx 123 x',"
881	\	.. "'xxxxxx 123 x',"
882	\	.. "'xxxxx 123 x',"
883	\	.. "'xxxx 123 xx',"
884	\	.. "'xxx 123 xxx',"
885	\	.. "'xx 123 xxxx',"
886	\	.. "'x 123 xxxxx',"
887	\	.. "' 123 xxxxxx',"
888	\	.. "])",
889	\ "hi SearchProp ctermbg=yellow",
890	\ "call prop_type_add('search', {'highlight': 'SearchProp'})",
891	\ "call prop_add(1, 11, {'length': 3, 'type': 'search'})",
892	\ "call prop_add(2, 10, {'length': 3, 'type': 'search'})",
893	\ "call prop_add(3, 9, {'length': 3, 'type': 'search'})",
894	\ "call prop_add(4, 8, {'length': 3, 'type': 'search'})",
895	\ "call prop_add(5, 7, {'length': 3, 'type': 'search'})",
896	\ "call prop_add(6, 6, {'length': 3, 'type': 'search'})",
897	\ "call prop_add(7, 5, {'length': 3, 'type': 'search'})",
898	\ "call prop_add(8, 4, {'length': 3, 'type': 'search'})",
899	\ "call prop_add(9, 3, {'length': 3, 'type': 'search'})",
900	\ "call prop_add(10, 2, {'length': 3, 'type': 'search'})",
901	\ "normal 1G6|\<C-V>" .. repeat('l', a:width - 1) .. "10jx",
902	\], 'XtestPropVis')
903  let buf = RunVimInTerminal('-S XtestPropVis', {'rows': 12})
904  call VerifyScreenDump(buf, 'Test_textprop_vis_' .. a:dump, {})
905
906  " clean up
907  call StopVimInTerminal(buf)
908  call delete('XtestPropVis')
909endfunc
910
911" screenshot test with Visual block mode operations
912func Test_textprop_screenshot_visual()
913  CheckScreendump
914
915  " Delete two columns while text props are three chars wide.
916  call RunTestVisualBlock(2, '01')
917
918  " Same, but delete four columns
919  call RunTestVisualBlock(4, '02')
920endfunc
921
922func Test_textprop_after_tab()
923  CheckScreendump
924
925  let lines =<< trim END
926       call setline(1, [
927             \ "\txxx",
928             \ "x\txxx",
929             \ ])
930       hi SearchProp ctermbg=yellow
931       call prop_type_add('search', {'highlight': 'SearchProp'})
932       call prop_add(1, 2, {'length': 3, 'type': 'search'})
933       call prop_add(2, 3, {'length': 3, 'type': 'search'})
934  END
935  call writefile(lines, 'XtestPropTab')
936  let buf = RunVimInTerminal('-S XtestPropTab', {'rows': 6})
937  call VerifyScreenDump(buf, 'Test_textprop_tab', {})
938
939  " clean up
940  call StopVimInTerminal(buf)
941  call delete('XtestPropTab')
942endfunc
943
944func Test_textprop_with_syntax()
945  CheckScreendump
946
947  let lines =<< trim END
948       call setline(1, [
949             \ "(abc)",
950             \ ])
951       syn match csParens "[()]" display
952       hi! link csParens MatchParen
953
954       call prop_type_add('TPTitle', #{ highlight: 'Title' })
955       call prop_add(1, 2, #{type: 'TPTitle', end_col: 5})
956  END
957  call writefile(lines, 'XtestPropSyn')
958  let buf = RunVimInTerminal('-S XtestPropSyn', {'rows': 6})
959  call VerifyScreenDump(buf, 'Test_textprop_syn_1', {})
960
961  " clean up
962  call StopVimInTerminal(buf)
963  call delete('XtestPropSyn')
964endfunc
965
966" Adding a text property to a new buffer should not fail
967func Test_textprop_empty_buffer()
968  call prop_type_add('comment', {'highlight': 'Search'})
969  new
970  call prop_add(1, 1, {'type': 'comment'})
971  close
972  call prop_type_delete('comment')
973endfunc
974
975" Adding a text property with invalid highlight should be ignored.
976func Test_textprop_invalid_highlight()
977  call assert_fails("call prop_type_add('dni', {'highlight': 'DoesNotExist'})", 'E970:')
978  new
979  call setline(1, ['asdf','asdf'])
980  call prop_add(1, 1, {'length': 4, 'type': 'dni'})
981  redraw
982  bwipe!
983  call prop_type_delete('dni')
984endfunc
985
986" Adding a text property to an empty buffer and then editing another
987func Test_textprop_empty_buffer_next()
988  call prop_type_add("xxx", {})
989  call prop_add(1, 1, {"type": "xxx"})
990  next X
991  call prop_type_delete('xxx')
992endfunc
993
994func Test_textprop_remove_from_buf()
995  new
996  let buf = bufnr('')
997  call prop_type_add('one', {'bufnr': buf})
998  call prop_add(1, 1, {'type': 'one', 'id': 234})
999  file x
1000  edit y
1001  call prop_remove({'id': 234, 'bufnr': buf}, 1)
1002  call prop_type_delete('one', {'bufnr': buf})
1003  bwipe! x
1004  close
1005endfunc
1006
1007func Test_textprop_in_unloaded_buf()
1008  edit Xaaa
1009  call setline(1, 'aaa')
1010  write
1011  edit Xbbb
1012  call setline(1, 'bbb')
1013  write
1014  let bnr = bufnr('')
1015  edit Xaaa
1016
1017  call prop_type_add('ErrorMsg', #{highlight:'ErrorMsg'})
1018  call assert_fails("call prop_add(1, 1, #{end_lnum: 1, endcol: 2, type: 'ErrorMsg', bufnr: bnr})", 'E275:')
1019  exe 'buf ' .. bnr
1020  call assert_equal('bbb', getline(1))
1021  call assert_equal(0, prop_list(1)->len())
1022
1023  bwipe! Xaaa
1024  bwipe! Xbbb
1025  cal delete('Xaaa')
1026  cal delete('Xbbb')
1027endfunc
1028
1029func Test_proptype_substitute2()
1030  new
1031  " text_prop.vim
1032  call setline(1, [
1033        \ 'The   num  123 is smaller than 4567.',
1034        \ '123 The number 123 is smaller than 4567.',
1035        \ '123 The number 123 is smaller than 4567.'])
1036
1037  call prop_type_add('number', {'highlight': 'ErrorMsg'})
1038
1039  call prop_add(1, 12, {'length': 3, 'type': 'number'})
1040  call prop_add(2, 1, {'length': 3, 'type': 'number'})
1041  call prop_add(3, 36, {'length': 4, 'type': 'number'})
1042  set ul&
1043  let expected = [{'id': 0, 'col': 13, 'end': 1, 'type': 'number', 'length': 3, 'start': 1},
1044        \ {'id': 0, 'col': 1, 'end': 1, 'type': 'number', 'length': 3, 'start': 1},
1045        \ {'id': 0, 'col': 50, 'end': 1, 'type': 'number', 'length': 4, 'start': 1}]
1046  " Add some text in between
1047  %s/\s\+/   /g
1048  call assert_equal(expected, prop_list(1) + prop_list(2) + prop_list(3))
1049
1050  " remove some text
1051  :1s/[a-z]\{3\}//g
1052  let expected = [{'id': 0, 'col': 10, 'end': 1, 'type': 'number', 'length': 3, 'start': 1}]
1053  call assert_equal(expected, prop_list(1))
1054  bwipe!
1055endfunc
1056
1057func SaveOptions()
1058  let d = #{tabstop: &tabstop,
1059	  \ softtabstop: &softtabstop,
1060	  \ shiftwidth: &shiftwidth,
1061	  \ expandtab: &expandtab,
1062	  \ foldmethod: '"' .. &foldmethod .. '"',
1063	  \ }
1064  return d
1065endfunc
1066
1067func RestoreOptions(dict)
1068  for name in keys(a:dict)
1069    exe 'let &' .. name .. ' = ' .. a:dict[name]
1070  endfor
1071endfunc
1072
1073func Test_textprop_noexpandtab()
1074  new
1075  let save_dict = SaveOptions()
1076
1077  set tabstop=8
1078  set softtabstop=4
1079  set shiftwidth=4
1080  set noexpandtab
1081  set foldmethod=marker
1082
1083  call feedkeys("\<esc>\<esc>0Ca\<cr>\<esc>\<up>", "tx")
1084  call prop_type_add('test', {'highlight': 'ErrorMsg'})
1085  call prop_add(1, 1, {'end_col': 2, 'type': 'test'})
1086  call feedkeys("0i\<tab>", "tx")
1087  call prop_remove({'type': 'test'})
1088  call prop_add(1, 2, {'end_col': 3, 'type': 'test'})
1089  call feedkeys("A\<left>\<tab>", "tx")
1090  call prop_remove({'type': 'test'})
1091  try
1092    " It is correct that this does not pass
1093    call prop_add(1, 6, {'end_col': 7, 'type': 'test'})
1094    " Has already collapsed here, start_col:6 does not result in an error
1095    call feedkeys("A\<left>\<tab>", "tx")
1096  catch /^Vim\%((\a\+)\)\=:E964/
1097  endtry
1098  call prop_remove({'type': 'test'})
1099  call prop_type_delete('test')
1100
1101  call RestoreOptions(save_dict)
1102  bwipe!
1103endfunc
1104
1105func Test_textprop_noexpandtab_redraw()
1106  new
1107  let save_dict = SaveOptions()
1108
1109  set tabstop=8
1110  set softtabstop=4
1111  set shiftwidth=4
1112  set noexpandtab
1113  set foldmethod=marker
1114
1115  call feedkeys("\<esc>\<esc>0Ca\<cr>\<space>\<esc>\<up>", "tx")
1116  call prop_type_add('test', {'highlight': 'ErrorMsg'})
1117  call prop_add(1, 1, {'end_col': 2, 'type': 'test'})
1118  call feedkeys("0i\<tab>", "tx")
1119  " Internally broken at the next line
1120  call feedkeys("A\<left>\<tab>", "tx")
1121  redraw
1122  " Index calculation failed internally on next line
1123  call prop_add(1, 1, {'end_col': 2, 'type': 'test'})
1124  call prop_remove({'type': 'test', 'all': v:true})
1125  call prop_type_delete('test')
1126  call prop_type_delete('test')
1127
1128  call RestoreOptions(save_dict)
1129  bwipe!
1130endfunc
1131
1132func Test_textprop_ins_str()
1133  new
1134  call setline(1, 'just some text')
1135  call prop_type_add('test', {'highlight': 'ErrorMsg'})
1136  call prop_add(1, 1, {'end_col': 2, 'type': 'test'})
1137  call assert_equal([{'id': 0, 'col': 1, 'end': 1, 'type': 'test', 'length': 1, 'start': 1}], prop_list(1))
1138
1139  call feedkeys("foi\<F8>\<Esc>", "tx")
1140  call assert_equal('just s<F8>ome text', getline(1))
1141  call assert_equal([{'id': 0, 'col': 1, 'end': 1, 'type': 'test', 'length': 1, 'start': 1}], prop_list(1))
1142
1143  bwipe!
1144  call prop_remove({'type': 'test'})
1145  call prop_type_delete('test')
1146endfunc
1147