diff options
Diffstat (limited to 'test')
178 files changed, 10726 insertions, 2320 deletions
diff --git a/test/benchmark/bench_re_freeze_spec.lua b/test/benchmark/bench_regexp_spec.lua index ea41953014..903af5f574 100644 --- a/test/benchmark/bench_re_freeze_spec.lua +++ b/test/benchmark/bench_regexp_spec.lua @@ -1,4 +1,4 @@ --- Test for benchmarking RE engine. +-- Test for benchmarking the RE engine. local helpers = require('test.functional.helpers')(after_each) local insert, source = helpers.insert, helpers.source @@ -7,19 +7,19 @@ local clear, command = helpers.clear, helpers.command -- Temporary file for gathering benchmarking results for each regexp engine. local result_file = 'benchmark.out' -- Fixture containing an HTML fragment that can make a search appear to freeze. -local sample_file = 'test/benchmark/samples/re.freeze.txt' +local sample_file = 'src/nvim/testdir/samples/re.freeze.txt' -- Vim script code that does both the work and the benchmarking of that work. local measure_cmd = [[call Measure(%d, ']] .. sample_file .. [[', '\s\+\%%#\@<!$', '+5')]] local measure_script = [[ - func! Measure(re, file, pattern, arg) - let sstart=reltime() + func Measure(re, file, pattern, arg) + let sstart = reltime() - execute 'set re=' . a:re + execute 'set re=' .. a:re execute 'split' a:arg a:file call search(a:pattern, '', '', 10000) - q! + quit! $put =printf('file: %s, re: %d, time: %s', a:file, a:re, reltimestr(reltime(sstart))) endfunc]] diff --git a/test/benchmark/samples/re.freeze.txt b/test/benchmark/samples/re.freeze.txt deleted file mode 100644 index d768c23c5e..0000000000 --- a/test/benchmark/samples/re.freeze.txt +++ /dev/null @@ -1,6 +0,0 @@ -:set re=0 or 2 -Search for the pattern: /\s\+\%#\@<!$/ -vim should not freeze. - -<td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td> - diff --git a/test/cmakeconfig/paths.lua.in b/test/cmakeconfig/paths.lua.in index e3979981ba..6be238d838 100644 --- a/test/cmakeconfig/paths.lua.in +++ b/test/cmakeconfig/paths.lua.in @@ -6,7 +6,6 @@ for p in ("${TEST_INCLUDE_DIRS}" .. ";"):gmatch("[^;]+") do end module.test_build_dir = "${CMAKE_BINARY_DIR}" -module.test_include_path = module.test_build_dir .. "/test/includes/post" module.test_libnvim_path = "${TEST_LIBNVIM_PATH}" module.test_source_path = "${CMAKE_SOURCE_DIR}" module.test_lua_prg = "${LUA_PRG}" diff --git a/test/functional/api/autocmd_spec.lua b/test/functional/api/autocmd_spec.lua index a923f5df0e..22a1311ee9 100644 --- a/test/functional/api/autocmd_spec.lua +++ b/test/functional/api/autocmd_spec.lua @@ -14,14 +14,14 @@ before_each(clear) describe('autocmd api', function() describe('nvim_create_autocmd', function() - it('does not allow "command" and "callback" in the same autocmd', function() - local ok, _ = pcall(meths.create_autocmd, "BufReadPost", { + it('"command" and "callback" are mutually exclusive', function() + local rv = pcall_err(meths.create_autocmd, "BufReadPost", { pattern = "*.py,*.pyi", command = "echo 'Should Have Errored", - callback = "not allowed", + callback = "NotAllowed", }) - eq(false, ok) + eq("specify either 'callback' or 'command', not both", rv) end) it('doesnt leak when you use ++once', function() @@ -60,13 +60,13 @@ describe('autocmd api', function() end) it('does not allow passing buffer and patterns', function() - local ok = pcall(meths.create_autocmd, "Filetype", { + local rv = pcall_err(meths.create_autocmd, "Filetype", { command = "let g:called = g:called + 1", buffer = 0, pattern = "*.py", }) - eq(false, ok) + eq("cannot pass both: 'pattern' and 'buffer' for the same autocmd", rv) end) it('does not allow passing invalid buffers', function() @@ -613,6 +613,20 @@ describe('autocmd api', function() eq(false, success) matches("'group' must be a string or an integer", code) end) + + it('raises error for invalid pattern array', function() + local success, code = unpack(meths.exec_lua([[ + return {pcall(function() + vim.api.nvim_create_autocmd("FileType", { + pattern = {{}}, + command = "echo 'hello'", + }) + end)} + ]], {})) + + eq(false, success) + matches("All entries in 'pattern' must be strings", code) + end) end) describe('patterns', function() diff --git a/test/functional/api/buffer_spec.lua b/test/functional/api/buffer_spec.lua index 8f6fc666c9..6b13729994 100644 --- a/test/functional/api/buffer_spec.lua +++ b/test/functional/api/buffer_spec.lua @@ -3,6 +3,7 @@ local Screen = require('test.functional.ui.screen') local clear, nvim, buffer = helpers.clear, helpers.nvim, helpers.buffer local curbuf, curwin, eq = helpers.curbuf, helpers.curwin, helpers.eq local curbufmeths, ok = helpers.curbufmeths, helpers.ok +local describe_lua_and_rpc = helpers.describe_lua_and_rpc(describe) local meths = helpers.meths local funcs = helpers.funcs local request = helpers.request @@ -185,12 +186,13 @@ describe('api/buf', function() end) end) - describe('nvim_buf_get_lines, nvim_buf_set_lines', function() - local get_lines, set_lines = curbufmeths.get_lines, curbufmeths.set_lines - local line_count = curbufmeths.line_count + describe_lua_and_rpc('nvim_buf_get_lines, nvim_buf_set_lines', function(api) + local get_lines = api.curbufmeths.get_lines + local set_lines = api.curbufmeths.set_lines + local line_count = api.curbufmeths.line_count it('fails correctly when input is not valid', function() - eq(1, curbufmeths.get_number()) + eq(1, api.curbufmeths.get_number()) eq([[String cannot contain newlines]], pcall_err(bufmeths.set_lines, 1, 1, 2, false, {'b\na'})) end) @@ -198,7 +200,7 @@ describe('api/buf', function() it("fails if 'nomodifiable'", function() command('set nomodifiable') eq([[Buffer is not 'modifiable']], - pcall_err(bufmeths.set_lines, 1, 1, 2, false, {'a','b'})) + pcall_err(api.bufmeths.set_lines, 1, 1, 2, false, {'a','b'})) end) it('has correct line_count when inserting and deleting', function() @@ -354,7 +356,7 @@ describe('api/buf', function() Who would win? A real window with proper text]]) - local buf = meths.create_buf(false,true) + local buf = api.meths.create_buf(false,true) screen:expect([[ Who would win? | A real window | @@ -363,7 +365,7 @@ describe('api/buf', function() | ]]) - meths.buf_set_lines(buf, 0, -1, true, {'or some', 'scratchy text'}) + api.meths.buf_set_lines(buf, 0, -1, true, {'or some', 'scratchy text'}) feed('i') -- provoke redraw screen:expect([[ Who would win? | @@ -379,15 +381,15 @@ describe('api/buf', function() visible buffer line 1 line 2 ]]) - local hiddenbuf = meths.create_buf(false,true) + local hiddenbuf = api.meths.create_buf(false,true) command('vsplit') command('vsplit') feed('<c-w>l<c-w>l<c-w>l') eq(3, funcs.winnr()) feed('<c-w>h') eq(2, funcs.winnr()) - meths.buf_set_lines(hiddenbuf, 0, -1, true, - {'hidden buffer line 1', 'line 2'}) + api.meths.buf_set_lines(hiddenbuf, 0, -1, true, + {'hidden buffer line 1', 'line 2'}) feed('<c-w>p') eq(3, funcs.winnr()) end) @@ -579,13 +581,13 @@ describe('api/buf', function() end) end) - describe('nvim_buf_get_text', function() - local get_text = curbufmeths.get_text - + describe_lua_and_rpc('nvim_buf_get_text', function(api) + local get_text = api.curbufmeths.get_text before_each(function() insert([[ hello foo! - text]]) + text + more]]) end) it('works', function() @@ -593,16 +595,17 @@ describe('api/buf', function() eq({'hello foo!'}, get_text(0, 0, 0, 42, {})) eq({'foo!'}, get_text(0, 6, 0, 10, {})) eq({'foo!', 'tex'}, get_text(0, 6, 1, 3, {})) - eq({'foo!', 'tex'}, get_text(-2, 6, -1, 3, {})) + eq({'foo!', 'tex'}, get_text(-3, 6, -2, 3, {})) eq({''}, get_text(0, 18, 0, 20, {})) - eq({'ext'}, get_text(-1, 1, -1, 4, {})) + eq({'ext'}, get_text(-2, 1, -2, 4, {})) + eq({'hello foo!', 'text', 'm'}, get_text(0, 0, 2, 1, {})) end) it('errors on out-of-range', function() - eq('Index out of bounds', pcall_err(get_text, 2, 0, 3, 0, {})) - eq('Index out of bounds', pcall_err(get_text, -3, 0, 0, 0, {})) - eq('Index out of bounds', pcall_err(get_text, 0, 0, 2, 0, {})) - eq('Index out of bounds', pcall_err(get_text, 0, 0, -3, 0, {})) + eq('Index out of bounds', pcall_err(get_text, 2, 0, 4, 0, {})) + eq('Index out of bounds', pcall_err(get_text, -4, 0, 0, 0, {})) + eq('Index out of bounds', pcall_err(get_text, 0, 0, 3, 0, {})) + eq('Index out of bounds', pcall_err(get_text, 0, 0, -4, 0, {})) -- no ml_get errors should happen #19017 eq('', meths.get_vvar('errmsg')) end) diff --git a/test/functional/api/buffer_updates_spec.lua b/test/functional/api/buffer_updates_spec.lua index 3d257e9477..d5f06c8f1f 100644 --- a/test/functional/api/buffer_updates_spec.lua +++ b/test/functional/api/buffer_updates_spec.lua @@ -810,7 +810,7 @@ describe('API: buffer events:', function() local newlines = args[5] -- Size of the contained nvim instance is 23 lines, this might change - -- with the test setup. Note updates are continguous. + -- with the test setup. Note updates are contiguous. assert(#newlines <= 23) for i = 1,#newlines do diff --git a/test/functional/api/command_spec.lua b/test/functional/api/command_spec.lua index f19d7a362b..d0fb26edc7 100644 --- a/test/functional/api/command_spec.lua +++ b/test/functional/api/command_spec.lua @@ -114,6 +114,7 @@ describe('nvim_create_user_command', function() ]] eq({ + name = "CommandWithLuaCallback", args = [[this\ is a\ test]], fargs = {"this ", "is", "a test"}, bang = false, @@ -150,6 +151,7 @@ describe('nvim_create_user_command', function() ]=]) eq({ + name = "CommandWithLuaCallback", args = [[this includes\ a backslash: \\]], fargs = {"this", "includes a", "backslash:", "\\"}, bang = false, @@ -186,6 +188,7 @@ describe('nvim_create_user_command', function() ]=]) eq({ + name = "CommandWithLuaCallback", args = "a\\b", fargs = {"a\\b"}, bang = false, @@ -222,6 +225,7 @@ describe('nvim_create_user_command', function() ]=]) eq({ + name = "CommandWithLuaCallback", args = 'h\tey ', fargs = {[[h]], [[ey]]}, bang = true, @@ -258,6 +262,7 @@ describe('nvim_create_user_command', function() ]=]) eq({ + name = "CommandWithLuaCallback", args = "h", fargs = {"h"}, bang = false, @@ -294,6 +299,7 @@ describe('nvim_create_user_command', function() ]]) eq({ + name = "CommandWithLuaCallback", args = "", fargs = {}, -- fargs works without args bang = false, @@ -342,6 +348,7 @@ describe('nvim_create_user_command', function() ]] eq({ + name = "CommandWithOneOrNoArg", args = "hello I'm one argument", fargs = {"hello I'm one argument"}, -- Doesn't split args bang = false, @@ -379,6 +386,7 @@ describe('nvim_create_user_command', function() -- f-args is an empty table if no args were passed eq({ + name = "CommandWithOneOrNoArg", args = "", fargs = {}, bang = false, @@ -427,6 +435,7 @@ describe('nvim_create_user_command', function() }) ]] eq({ + name = "CommandWithNoArgs", args = "", fargs = {}, bang = false, @@ -463,6 +472,7 @@ describe('nvim_create_user_command', function() ]]) -- register can be specified eq({ + name = "CommandWithNoArgs", args = "", fargs = {}, bang = false, diff --git a/test/functional/api/highlight_spec.lua b/test/functional/api/highlight_spec.lua index 3b36563d21..5941d4c68b 100644 --- a/test/functional/api/highlight_spec.lua +++ b/test/functional/api/highlight_spec.lua @@ -11,6 +11,9 @@ local ok = helpers.ok local assert_alive = helpers.assert_alive describe('API: highlight',function() + clear() + Screen.new() -- initialize Screen.colors + local expected_rgb = { background = Screen.colors.Yellow, foreground = Screen.colors.Red, @@ -29,13 +32,16 @@ describe('API: highlight',function() italic = true, reverse = true, underline = true, - undercurl = true, - underdouble = true, - underdotted = true, - underdashed = true, strikethrough = true, + altfont = true, nocombine = true, } + local expected_undercurl = { + background = Screen.colors.Yellow, + foreground = Screen.colors.Red, + special = Screen.colors.Blue, + undercurl = true, + } before_each(function() clear() @@ -56,9 +62,13 @@ describe('API: highlight',function() eq('Invalid highlight id: 30000', string.match(emsg, 'Invalid.*')) -- Test all highlight properties. - command('hi NewHighlight gui=underline,bold,undercurl,underdouble,underdotted,underdashed,italic,reverse,strikethrough,nocombine') + command('hi NewHighlight gui=underline,bold,italic,reverse,strikethrough,altfont,nocombine') eq(expected_rgb2, nvim("get_hl_by_id", hl_id, true)) + -- Test undercurl + command('hi NewHighlight gui=undercurl') + eq(expected_undercurl, nvim("get_hl_by_id", hl_id, true)) + -- Test nil argument. err, emsg = pcall(meths.get_hl_by_id, { nil }, false) eq(false, err) @@ -204,17 +214,14 @@ describe("API: set highlight", function() bold = true, italic = true, reverse = true, - undercurl = true, underline = true, - underdashed = true, - underdotted = true, - underdouble = true, strikethrough = true, + altfont = true, cterm = { italic = true, reverse = true, - undercurl = true, strikethrough = true, + altfont = true, nocombine = true, } } @@ -224,20 +231,17 @@ describe("API: set highlight", function() bold = true, italic = true, reverse = true, - undercurl = true, underline = true, - underdashed = true, - underdotted = true, - underdouble = true, strikethrough = true, + altfont = true, } local highlight3_result_cterm = { background = highlight_color.ctermbg, foreground = highlight_color.ctermfg, italic = true, reverse = true, - undercurl = true, strikethrough = true, + altfont = true, nocombine = true, } @@ -293,7 +297,7 @@ describe("API: set highlight", function() exec_capture('highlight Test_hl')) meths.set_hl(0, 'Test_hl2', highlight3_config) - eq('Test_hl2 xxx cterm=undercurl,italic,reverse,strikethrough,nocombine ctermfg=8 ctermbg=15 gui=bold,underline,undercurl,underdouble,underdotted,underdashed,italic,reverse,strikethrough guifg=#ff0000 guibg=#0032aa', + eq('Test_hl2 xxx cterm=italic,reverse,strikethrough,altfont,nocombine ctermfg=8 ctermbg=15 gui=bold,underline,italic,reverse,strikethrough,altfont guifg=#ff0000 guibg=#0032aa', exec_capture('highlight Test_hl2')) -- Colors are stored with the name they are defined, but diff --git a/test/functional/api/keymap_spec.lua b/test/functional/api/keymap_spec.lua index 30c351b26a..5be4425162 100644 --- a/test/functional/api/keymap_spec.lua +++ b/test/functional/api/keymap_spec.lua @@ -1066,7 +1066,7 @@ describe('nvim_buf_set_keymap, nvim_buf_del_keymap', function() eq({'rhs'}, bufmeths.get_lines(0, 0, 1, 1)) end) - it("does not crash when setting keymap in a non-existing buffer #13541", function() + it("does not crash when setting mapping in a non-existing buffer #13541", function() pcall_err(bufmeths.set_keymap, 100, '', 'lsh', 'irhs<Esc>', {}) helpers.assert_alive() end) diff --git a/test/functional/api/proc_spec.lua b/test/functional/api/proc_spec.lua index 0fbf58a8e7..2028a8fba5 100644 --- a/test/functional/api/proc_spec.lua +++ b/test/functional/api/proc_spec.lua @@ -3,12 +3,12 @@ local helpers = require('test.functional.helpers')(after_each) local clear = helpers.clear local eq = helpers.eq local funcs = helpers.funcs -local iswin = helpers.iswin local neq = helpers.neq local nvim_argv = helpers.nvim_argv local request = helpers.request local retry = helpers.retry local NIL = helpers.NIL +local is_os = helpers.is_os describe('API', function() before_each(clear) @@ -19,26 +19,26 @@ describe('API', function() -- Might be non-zero already (left-over from some other test?), -- but this is not what is tested here. - local initial_childs = request('nvim_get_proc_children', this_pid) + local initial_children = request('nvim_get_proc_children', this_pid) local job1 = funcs.jobstart(nvim_argv) retry(nil, nil, function() - eq(#initial_childs + 1, #request('nvim_get_proc_children', this_pid)) + eq(#initial_children + 1, #request('nvim_get_proc_children', this_pid)) end) local job2 = funcs.jobstart(nvim_argv) retry(nil, nil, function() - eq(#initial_childs + 2, #request('nvim_get_proc_children', this_pid)) + eq(#initial_children + 2, #request('nvim_get_proc_children', this_pid)) end) funcs.jobstop(job1) retry(nil, nil, function() - eq(#initial_childs + 1, #request('nvim_get_proc_children', this_pid)) + eq(#initial_children + 1, #request('nvim_get_proc_children', this_pid)) end) funcs.jobstop(job2) retry(nil, nil, function() - eq(#initial_childs, #request('nvim_get_proc_children', this_pid)) + eq(#initial_children, #request('nvim_get_proc_children', this_pid)) end) end) @@ -62,7 +62,7 @@ describe('API', function() it('returns process info', function() local pid = funcs.getpid() local pinfo = request('nvim_get_proc', pid) - eq((iswin() and 'nvim.exe' or 'nvim'), pinfo.name) + eq((is_os('win') and 'nvim.exe' or 'nvim'), pinfo.name) eq(pid, pinfo.pid) eq('number', type(pinfo.ppid)) neq(pid, pinfo.ppid) diff --git a/test/functional/api/server_notifications_spec.lua b/test/functional/api/server_notifications_spec.lua index 1c554b05a3..53642858b2 100644 --- a/test/functional/api/server_notifications_spec.lua +++ b/test/functional/api/server_notifications_spec.lua @@ -1,13 +1,16 @@ local helpers = require('test.functional.helpers')(after_each) +local assert_log = helpers.assert_log local eq, clear, eval, command, nvim, next_msg = helpers.eq, helpers.clear, helpers.eval, helpers.command, helpers.nvim, helpers.next_msg local meths = helpers.meths local exec_lua = helpers.exec_lua local retry = helpers.retry -local isCI = helpers.isCI +local is_ci = helpers.is_ci local assert_alive = helpers.assert_alive -local uname = helpers.uname +local skip = helpers.skip + +local testlog = 'Xtest-server-notify-log' describe('notify', function() local channel @@ -17,6 +20,10 @@ describe('notify', function() channel = nvim('get_api_info')[1] end) + after_each(function() + os.remove(testlog) + end) + describe('passing a valid channel id', function() it('sends the notification/args to the corresponding channel', function() eval('rpcnotify('..channel..', "test-event", 1, 2, 3)') @@ -72,23 +79,18 @@ describe('notify', function() end) it('unsubscribe non-existing event #8745', function() + clear{env={ + NVIM_LOG_FILE=testlog, + }} nvim('subscribe', 'event1') nvim('unsubscribe', 'doesnotexist') + assert_log("tried to unsubscribe unknown event 'doesnotexist'", testlog, 10) nvim('unsubscribe', 'event1') assert_alive() end) it('cancels stale events on channel close', function() - if uname() == 'freebsd' then - pending('Failing FreeBSD test') - end - if isCI() then - pending('hangs on CI #14083 #15251') - return - elseif helpers.skip_fragile(pending) then - return - end - if helpers.pending_win32(pending) then return end + skip(is_ci(), 'hangs on CI #14083 #15251') local catchan = eval("jobstart(['cat'], {'rpc': v:true})") local catpath = eval('exepath("cat")') eq({id=catchan, argv={catpath}, stream='job', mode='rpc', client = {}}, exec_lua ([[ diff --git a/test/functional/api/server_requests_spec.lua b/test/functional/api/server_requests_spec.lua index 00a4dd041d..ceff390dc5 100644 --- a/test/functional/api/server_requests_spec.lua +++ b/test/functional/api/server_requests_spec.lua @@ -250,7 +250,7 @@ describe('server -> client', function() pcall(funcs.jobstop, jobid) end) - if helpers.pending_win32(pending) then return end + if helpers.skip(helpers.is_os('win')) then return end it('rpc and text stderr can be combined', function() local status, rv = pcall(funcs.rpcrequest, jobid, 'poll') diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index af6cee7e90..8fcdd9620b 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -12,7 +12,6 @@ local exec = helpers.exec local eval = helpers.eval local expect = helpers.expect local funcs = helpers.funcs -local iswin = helpers.iswin local meths = helpers.meths local matches = helpers.matches local pesc = helpers.pesc @@ -30,6 +29,7 @@ local exec_lua = helpers.exec_lua local exc_exec = helpers.exc_exec local insert = helpers.insert local expect_exit = helpers.expect_exit +local skip = helpers.skip local pcall_err = helpers.pcall_err local format_string = helpers.format_string @@ -166,7 +166,7 @@ describe('API', function() echo nvim_exec('echo Avast_ye_hades(''ahoy!'')', 1) ]], true)) - eq('Vim(call):E5555: API call: Vim(echo):E121: Undefined variable: s:pirate', + matches('Vim%(echo%):E121: Undefined variable: s:pirate$', pcall_err(request, 'nvim_exec', [[ let s:pirate = 'script-scoped varrrrr' call nvim_exec('echo s:pirate', 1) @@ -208,12 +208,12 @@ describe('API', function() end) it('execution error', function() - eq('Vim:E492: Not an editor command: bogus_command', + eq('nvim_exec(): Vim:E492: Not an editor command: bogus_command', pcall_err(request, 'nvim_exec', 'bogus_command', false)) eq('', nvim('eval', 'v:errmsg')) -- v:errmsg was not updated. eq('', eval('v:exception')) - eq('Vim(buffer):E86: Buffer 23487 does not exist', + eq('nvim_exec(): Vim(buffer):E86: Buffer 23487 does not exist', pcall_err(request, 'nvim_exec', 'buffer 23487', false)) eq('', eval('v:errmsg')) -- v:errmsg was not updated. eq('', eval('v:exception')) @@ -399,7 +399,7 @@ describe('API', function() end) it('returns shell |:!| output', function() - local win_lf = iswin() and '\r' or '' + local win_lf = is_os('win') and '\r' or '' eq(':!echo foo\r\n\nfoo'..win_lf..'\n', nvim('command_output', [[!echo foo]])) end) @@ -485,7 +485,7 @@ describe('API', function() throw 'wtf' endfunction ]]) - eq('wtf', pcall_err(request, 'nvim_call_function', 'Foo', {})) + eq('function Foo, line 1: wtf', pcall_err(request, 'nvim_call_function', 'Foo', {})) eq('', eval('v:exception')) eq('', eval('v:errmsg')) -- v:errmsg was not updated. end) @@ -604,10 +604,10 @@ describe('API', function() eq([[Error loading lua: [string "<nvim>"]:0: unexpected symbol]], pcall_err(meths.exec_lua, 'aa=bb\0', {})) - eq([[Error executing lua: [string "<nvim>"]:0: attempt to call global 'bork' (a nil value)]], + eq([[attempt to call global 'bork' (a nil value)]], pcall_err(meths.exec_lua, 'bork()', {})) - eq('Error executing lua: [string "<nvim>"]:0: did\nthe\nfail', + eq('did\nthe\nfail', pcall_err(meths.exec_lua, 'error("did\\nthe\\nfail")', {})) end) @@ -1108,6 +1108,14 @@ describe('API', function() nvim('paste', 'a', true, -1) eq('a', funcs.getcmdline()) end) + it('pasted text is saved in cmdline history when <CR> comes from mapping #20957', function() + command('cnoremap <CR> <CR>') + feed(':') + nvim('paste', 'echo', true, -1) + eq('', funcs.histget(':')) + feed('<CR>') + eq('echo', funcs.histget(':')) + end) it('pasting with empty last chunk in Cmdline mode', function() local screen = Screen.new(20, 4) screen:attach() @@ -1140,7 +1148,7 @@ describe('API', function() end) it('vim.paste() failure', function() nvim('exec_lua', 'vim.paste = (function(lines, phase) error("fake fail") end)', {}) - eq([[Error executing lua: [string "<nvim>"]:0: fake fail]], + eq('fake fail', pcall_err(request, 'nvim_paste', 'line 1\nline 2\nline 3', false, 1)) end) end) @@ -1799,9 +1807,11 @@ describe('API', function() }, ['jumps'] = eval(([[ - filter(map(getjumplist()[0], 'filter( - { "f": expand("#".v:val.bufnr.":p"), "l": v:val.lnum }, - { k, v -> k != "l" || v != 1 })'), '!empty(v:val.f)') + filter(map(add( + getjumplist()[0], { 'bufnr': bufnr('%'), 'lnum': getcurpos()[1] }), + 'filter( + { "f": expand("#".v:val.bufnr.":p"), "l": v:val.lnum }, + { k, v -> k != "l" || v != 1 })'), '!empty(v:val.f)') ]]):gsub('\n', '')), ['bufs'] = eval([[ @@ -2116,7 +2126,7 @@ describe('API', function() pty='?', } local event = meths.get_var("opened_event") - if not iswin() then + if not is_os('win') then info.pty = event.info.pty neq(nil, string.match(info.pty, "^/dev/")) end @@ -2132,7 +2142,7 @@ describe('API', function() stream = 'job', id = 4, argv = ( - iswin() and { + is_os('win') and { eval('&shell'), '/s', '/c', @@ -2154,7 +2164,7 @@ describe('API', function() -- :terminal with args + stopped process. eq(1, eval('jobstop(&channel)')) eval('jobwait([&channel], 1000)') -- Wait. - expected2.pty = (iswin() and '?' or '') -- pty stream was closed. + expected2.pty = (is_os('win') and '?' or '') -- pty stream was closed. eq(expected2, eval('nvim_get_chan_info(&channel)')) end) end) @@ -2315,12 +2325,6 @@ describe('API', function() meths.set_option('isident', '') end) - local it_maybe_pending = it - if helpers.isCI() and os.getenv('CONFIGURATION') == 'MSVC_32' then - -- For "works with &opt" (flaky on MSVC_32), but not easy to skip alone. #10241 - it_maybe_pending = pending - end - local function simplify_east_api_node(line, east_api_node) if east_api_node == NIL then return nil @@ -2517,7 +2521,7 @@ describe('API', function() end end require('test.unit.viml.expressions.parser_tests')( - it_maybe_pending, _check_parsing, hl, fmtn) + it, _check_parsing, hl, fmtn) end) describe('nvim_list_uis', function() @@ -2721,7 +2725,7 @@ describe('API', function() eq({}, meths.get_runtime_file("foobarlang/", true)) end) it('can handle bad patterns', function() - if helpers.pending_win32(pending) then return end + skip(is_os('win')) eq("Vim:E220: Missing }.", pcall_err(meths.get_runtime_file, "{", false)) @@ -3040,7 +3044,7 @@ describe('API', function() meths.buf_set_mark(buf, 'F', 2, 2, {}) meths.buf_set_name(buf, "mybuf") local mark = meths.get_mark('F', {}) - -- Compare the path tail ony + -- Compare the path tail only assert(string.find(mark[4], "mybuf$")) eq({2, 2, buf.id, mark[4]}, mark) end) @@ -3124,6 +3128,24 @@ describe('API', function() eq('E539: Illegal character <}>', pcall_err(meths.eval_statusline, '%{%}', {})) end) + it('supports various items', function() + eq({ str = '0', width = 1 }, + meths.eval_statusline('%l', { maxwidth = 5 })) + command('set readonly') + eq({ str = '[RO]', width = 4 }, + meths.eval_statusline('%r', { maxwidth = 5 })) + local screen = Screen.new(80, 24) + screen:attach() + command('set showcmd') + feed('1234') + screen:expect({any = '1234'}) + eq({ str = '1234', width = 4 }, + meths.eval_statusline('%S', { maxwidth = 5 })) + feed('56') + screen:expect({any = '123456'}) + eq({ str = '<3456', width = 5 }, + meths.eval_statusline('%S', { maxwidth = 5 })) + end) describe('highlight parsing', function() it('works', function() eq({ @@ -3191,6 +3213,17 @@ describe('API', function() 'TextWithNoHighlight%#WarningMsg#TextWithWarningHighlight', { use_winbar = true, highlights = true })) end) + it('no memory leak with click functions', function() + meths.eval_statusline('%@ClickFunc@StatusLineStringWithClickFunc%T', {}) + eq({ + str = 'StatusLineStringWithClickFunc', + width = 29 + }, + meths.eval_statusline( + '%@ClickFunc@StatusLineStringWithClickFunc%T', + {}) + ) + end) end) end) describe('nvim_parse_cmd', function() @@ -3851,14 +3884,16 @@ describe('API', function() eq("", meths.cmd({ cmd = "Foo", bang = false }, { output = true })) end) it('works with modifiers', function() - -- with :silent output is still captured + -- with silent = true output is still captured eq('1', meths.cmd({ cmd = 'echomsg', args = { '1' }, mods = { silent = true } }, { output = true })) -- but message isn't added to message history eq('', meths.cmd({ cmd = 'messages' }, { output = true })) + meths.create_user_command("Foo", 'set verbose', {}) eq(" verbose=1", meths.cmd({ cmd = "Foo", mods = { verbose = 1 } }, { output = true })) + meths.create_user_command("Mods", "echo '<mods>'", {}) eq('keepmarks keeppatterns silent 3verbose aboveleft horizontal', meths.cmd({ cmd = "Mods", mods = { @@ -3870,6 +3905,7 @@ describe('API', function() verbose = 3, } }, { output = true })) eq(0, meths.get_option_value("verbose", {})) + command('edit foo.txt | edit bar.txt') eq(' 1 #h "foo.txt" line 1', meths.cmd({ cmd = "buffers", mods = { filter = { pattern = "foo", force = false } } }, @@ -3877,6 +3913,13 @@ describe('API', function() eq(' 2 %a "bar.txt" line 1', meths.cmd({ cmd = "buffers", mods = { filter = { pattern = "foo", force = true } } }, { output = true })) + + -- with emsg_silent = true error is suppresed + feed([[:lua vim.api.nvim_cmd({ cmd = 'call', mods = { emsg_silent = true } }, {})<CR>]]) + eq('', meths.cmd({ cmd = 'messages' }, { output = true })) + -- error from the next command typed is not suppressed #21420 + feed(':call<CR><CR>') + eq('E471: Argument required', meths.cmd({ cmd = 'messages' }, { output = true })) end) it('works with magic.file', function() exec_lua([[ diff --git a/test/functional/api/window_spec.lua b/test/functional/api/window_spec.lua index 7c65cf9c37..ecab6a4713 100644 --- a/test/functional/api/window_spec.lua +++ b/test/functional/api/window_spec.lua @@ -7,6 +7,7 @@ local clear, nvim, curbuf, curbuf_contents, window, curwin, eq, neq, helpers.tabpage local poke_eventloop = helpers.poke_eventloop local curwinmeths = helpers.curwinmeths +local exec = helpers.exec local funcs = helpers.funcs local request = helpers.request local NIL = helpers.NIL @@ -229,6 +230,46 @@ describe('API/win', function() | ]]) end) + + it('updates cursorcolumn in non-current window', function() + local screen = Screen.new(60, 8) + screen:set_default_attr_ids({ + [1] = {bold = true, foreground = Screen.colors.Blue}, -- NonText + [2] = {background = Screen.colors.Grey90}, -- CursorColumn + [3] = {bold = true, reverse = true}, -- StatusLine + [4] = {reverse = true}, -- StatusLineNC + }) + screen:attach() + command('set cursorcolumn') + insert([[ + aaa + bbb + ccc + ddd]]) + local oldwin = curwin() + command('vsplit') + screen:expect([[ + aa{2:a} │aa{2:a} | + bb{2:b} │bb{2:b} | + cc{2:c} │cc{2:c} | + dd^d │ddd | + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {3:[No Name] [+] }{4:[No Name] [+] }| + | + ]]) + window('set_cursor', oldwin, {2, 0}) + screen:expect([[ + aa{2:a} │{2:a}aa | + bb{2:b} │bbb | + cc{2:c} │{2:c}cc | + dd^d │{2:d}dd | + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {3:[No Name] [+] }{4:[No Name] [+] }| + | + ]]) + end) end) describe('{get,set}_height', function() @@ -243,6 +284,22 @@ describe('API/win', function() window('set_height', nvim('list_wins')[2], 2) eq(2, window('get_height', nvim('list_wins')[2])) end) + + it('do not cause ml_get errors with foldmethod=expr #19989', function() + insert([[ + aaaaa + bbbbb + ccccc]]) + command('set foldmethod=expr') + exec([[ + new + let w = nvim_get_current_win() + wincmd w + call nvim_win_set_height(w, 5) + ]]) + feed('l') + eq('', meths.get_vvar('errmsg')) + end) end) describe('{get,set}_width', function() @@ -257,6 +314,22 @@ describe('API/win', function() window('set_width', nvim('list_wins')[2], 2) eq(2, window('get_width', nvim('list_wins')[2])) end) + + it('do not cause ml_get errors with foldmethod=expr #19989', function() + insert([[ + aaaaa + bbbbb + ccccc]]) + command('set foldmethod=expr') + exec([[ + vnew + let w = nvim_get_current_win() + wincmd w + call nvim_win_set_width(w, 5) + ]]) + feed('l') + eq('', meths.get_vvar('errmsg')) + end) end) describe('{get,set,del}_var', function() @@ -450,6 +523,8 @@ describe('API/win', function() it('closing current (float) window of another tabpage #15313', function() command('tabedit') + command('botright split') + local prevwin = curwin().id eq(2, eval('tabpagenr()')) local win = meths.open_win(0, true, { relative='editor', row=10, col=10, width=50, height=10 @@ -459,7 +534,7 @@ describe('API/win', function() eq(1, eval('tabpagenr()')) meths.win_close(win, false) - eq(1001, meths.tabpage_get_win(tab).id) + eq(prevwin, meths.tabpage_get_win(tab).id) assert_alive() end) end) diff --git a/test/functional/autocmd/autocmd_spec.lua b/test/functional/autocmd/autocmd_spec.lua index 90254b7415..fb5bab445c 100644 --- a/test/functional/autocmd/autocmd_spec.lua +++ b/test/functional/autocmd/autocmd_spec.lua @@ -9,6 +9,7 @@ local neq = helpers.neq local eval = helpers.eval local feed = helpers.feed local clear = helpers.clear +local matches = helpers.matches local meths = helpers.meths local pcall_err = helpers.pcall_err local funcs = helpers.funcs @@ -424,17 +425,50 @@ describe('autocmd', function() end) it('gives E814 when there are no other floating windows', function() - eq('Vim(close):E814: Cannot close window, only autocmd window would remain', + eq('BufAdd Autocommands for "Xa.txt": Vim(close):E814: Cannot close window, only autocmd window would remain', pcall_err(command, 'doautoall BufAdd')) end) it('gives E814 when there are other floating windows', function() meths.open_win(0, true, {width = 10, height = 10, relative = 'editor', row = 10, col = 10}) - eq('Vim(close):E814: Cannot close window, only autocmd window would remain', + eq('BufAdd Autocommands for "Xa.txt": Vim(close):E814: Cannot close window, only autocmd window would remain', pcall_err(command, 'doautoall BufAdd')) end) end) + it('closing `aucmd_win` using API gives E813', function() + exec_lua([[ + vim.cmd('tabnew') + _G.buf = vim.api.nvim_create_buf(true, true) + ]]) + matches('Vim:E813: Cannot close autocmd window$', pcall_err(exec_lua, [[ + vim.api.nvim_buf_call(_G.buf, function() + local win = vim.api.nvim_get_current_win() + vim.api.nvim_win_close(win, true) + end) + ]])) + matches('Vim:E813: Cannot close autocmd window$', pcall_err(exec_lua, [[ + vim.api.nvim_buf_call(_G.buf, function() + local win = vim.api.nvim_get_current_win() + vim.cmd('tabnext') + vim.api.nvim_win_close(win, true) + end) + ]])) + matches('Vim:E813: Cannot close autocmd window$', pcall_err(exec_lua, [[ + vim.api.nvim_buf_call(_G.buf, function() + local win = vim.api.nvim_get_current_win() + vim.api.nvim_win_hide(win) + end) + ]])) + matches('Vim:E813: Cannot close autocmd window$', pcall_err(exec_lua, [[ + vim.api.nvim_buf_call(_G.buf, function() + local win = vim.api.nvim_get_current_win() + vim.cmd('tabnext') + vim.api.nvim_win_hide(win) + end) + ]])) + end) + it(':doautocmd does not warn "No matching autocommands" #10689', function() local screen = Screen.new(32, 3) screen:attach() @@ -476,14 +510,14 @@ describe('autocmd', function() it('during RecordingLeave event', function() command([[autocmd RecordingLeave * let v:event.regname = '']]) - eq('Vim(let):E46: Cannot change read-only variable "v:event.regname"', + eq('RecordingLeave Autocommands for "*": Vim(let):E46: Cannot change read-only variable "v:event.regname"', pcall_err(command, 'normal! qqq')) end) it('during TermClose event', function() command('autocmd TermClose * let v:event.status = 0') command('terminal') - eq('Vim(let):E46: Cannot change read-only variable "v:event.status"', + eq('TermClose Autocommands for "*": Vim(let):E46: Cannot change read-only variable "v:event.status"', pcall_err(command, 'bdelete!')) end) end) diff --git a/test/functional/autocmd/cmdline_spec.lua b/test/functional/autocmd/cmdline_spec.lua index 8ec06dc148..82fb9b9444 100644 --- a/test/functional/autocmd/cmdline_spec.lua +++ b/test/functional/autocmd/cmdline_spec.lua @@ -73,7 +73,7 @@ describe('cmdline autocommands', function() {1:~ }| {4: }| : | - {2:E5500: autocmd has thrown an exception: Vim(echoerr):FAIL} | + {2:CmdlineEnter Autocommands for "*": Vim(echoerr):FAIL} | :^ | ]]) @@ -82,9 +82,9 @@ describe('cmdline autocommands', function() | {4: }| : | - {2:E5500: autocmd has thrown an exception: Vim(echoerr):FAIL} | + {2:CmdlineEnter Autocommands for "*": Vim(echoerr):FAIL} | :put ='lorem ipsum' | - {2:E5500: autocmd has thrown an exception: Vim(echoerr):very error} | + {2:CmdlineLeave Autocommands for "*": Vim(echoerr):very error} | | {3:Press ENTER or type command to continue}^ | ]]) @@ -111,9 +111,9 @@ describe('cmdline autocommands', function() lorem ipsum | {4: }| : | - {2:E5500: autocmd has thrown an exception: Vim(echoerr):FAIL} | + {2:CmdlineEnter Autocommands for "*": Vim(echoerr):FAIL} | :put ='lorem ipsum' | - {2:E5500: autocmd has thrown an exception: Vim(echoerr):change erreor} | + {2:CmdlineChanged Autocommands for "*": Vim(echoerr):change erreor} | :put ='lorem ipsum'^ | ]]) @@ -123,9 +123,9 @@ describe('cmdline autocommands', function() lorem ipsum | {4: }| : | - {2:E5500: autocmd has thrown an exception: Vim(echoerr):FAIL} | + {2:CmdlineEnter Autocommands for "*": Vim(echoerr):FAIL} | :put ='lorem ipsum' | - {2:E5500: autocmd has thrown an exception: Vim(echoerr):change erreor} | + {2:CmdlineChanged Autocommands for "*": Vim(echoerr):change erreor} | :put ='lorem ipsum^' | ]]) @@ -134,22 +134,22 @@ describe('cmdline autocommands', function() screen:expect([[ {4: }| : | - {2:E5500: autocmd has thrown an exception: Vim(echoerr):FAIL} | + {2:CmdlineEnter Autocommands for "*": Vim(echoerr):FAIL} | :put ='lorem ipsum' | - {2:E5500: autocmd has thrown an exception: Vim(echoerr):change erreor} | + {2:CmdlineChanged Autocommands for "*": Vim(echoerr):change erreor} | :put ='lorem ipsum.' | - {2:E5500: autocmd has thrown an exception: Vim(echoerr):change erreor} | + {2:CmdlineChanged Autocommands for "*": Vim(echoerr):change erreor} | :put ='lorem ipsum.^' | ]]) feed('<cr>') screen:expect([[ :put ='lorem ipsum' | - {2:E5500: autocmd has thrown an exception: Vim(echoerr):change erreor} | + {2:CmdlineChanged Autocommands for "*": Vim(echoerr):change erreor} | :put ='lorem ipsum.' | - {2:E5500: autocmd has thrown an exception: Vim(echoerr):change erreor} | + {2:CmdlineChanged Autocommands for "*": Vim(echoerr):change erreor} | :put ='lorem ipsum.' | - {2:E5500: autocmd has thrown an exception: Vim(echoerr):very error} | + {2:CmdlineLeave Autocommands for "*": Vim(echoerr):very error} | | {3:Press ENTER or type command to continue}^ | ]]) @@ -185,6 +185,14 @@ describe('cmdline autocommands', function() eq({'notification', 'CmdlineLeave', {{cmdtype='=', cmdlevel=2, abort=false}}}, next_msg()) end) + it('no crash with recursive use of v:event #19484', function() + command('autocmd CmdlineEnter * normal :') + feed(':') + eq({'notification', 'CmdlineEnter', {{cmdtype=':', cmdlevel=1}}}, next_msg()) + feed('<CR>') + eq({'notification', 'CmdlineLeave', {{cmdtype=':', cmdlevel=1, abort=false}}}, next_msg()) + end) + it('supports CmdlineChanged' ,function() command("autocmd CmdlineChanged * call rpcnotify(g:channel, 'CmdlineChanged', v:event, getcmdline())") feed(':') @@ -215,7 +223,6 @@ describe('cmdline autocommands', function() eq({'notification', 'CmdlineChanged', {{cmdtype='=', cmdlevel=2}, "1+1"}}, next_msg()) feed('<cr>') eq({'notification', 'CmdlineLeave', {{cmdtype='=', cmdlevel=2, abort=false}}}, next_msg()) - eq({'notification', 'CmdlineChanged', {{cmdtype=':', cmdlevel=1}, "let x = "}}, next_msg()) eq({'notification', 'CmdlineChanged', {{cmdtype=':', cmdlevel=1}, "let x = 2"}}, next_msg()) feed('<cr>') eq({'notification', 'CmdlineLeave', {{cmdtype=':', cmdlevel=1, abort=false}}}, next_msg()) diff --git a/test/functional/autocmd/dirchanged_spec.lua b/test/functional/autocmd/dirchanged_spec.lua index 45dc06b39b..828cffa460 100644 --- a/test/functional/autocmd/dirchanged_spec.lua +++ b/test/functional/autocmd/dirchanged_spec.lua @@ -1,12 +1,12 @@ local lfs = require('lfs') -local h = require('test.functional.helpers')(after_each) +local helpers = require('test.functional.helpers')(after_each) -local clear = h.clear -local command = h.command -local eq = h.eq -local eval = h.eval -local request = h.request -local iswin = h.iswin +local clear = helpers.clear +local command = helpers.command +local eq = helpers.eq +local eval = helpers.eval +local request = helpers.request +local is_os = helpers.is_os describe('autocmd DirChanged and DirChangedPre', function() local curdir = string.gsub(lfs.currentdir(), '\\', '/') @@ -21,8 +21,8 @@ describe('autocmd DirChanged and DirChangedPre', function() curdir .. '\\XTEST-FUNCTIONAL-AUTOCMD-DIRCHANGED.DIR3', } - setup(function() for _, dir in pairs(dirs) do h.mkdir(dir) end end) - teardown(function() for _, dir in pairs(dirs) do h.rmdir(dir) end end) + setup(function() for _, dir in pairs(dirs) do helpers.mkdir(dir) end end) + teardown(function() for _, dir in pairs(dirs) do helpers.rmdir(dir) end end) before_each(function() clear() @@ -159,7 +159,7 @@ describe('autocmd DirChanged and DirChangedPre', function() eq(1, eval('g:cdprecount')) eq(1, eval('g:cdcount')) - if iswin() then + if is_os('win') then command('lcd '..win_dirs[1]) eq({}, eval('g:evpre')) eq({}, eval('g:ev')) @@ -182,7 +182,7 @@ describe('autocmd DirChanged and DirChangedPre', function() eq(2, eval('g:cdprecount')) eq(2, eval('g:cdcount')) - if iswin() then + if is_os('win') then command('tcd '..win_dirs[2]) eq({}, eval('g:evpre')) eq({}, eval('g:ev')) @@ -204,7 +204,7 @@ describe('autocmd DirChanged and DirChangedPre', function() eq(3, eval('g:cdprecount')) eq(3, eval('g:cdcount')) - if iswin() then + if is_os('win') then command('cd '..win_dirs[3]) eq({}, eval('g:evpre')) eq({}, eval('g:ev')) @@ -229,7 +229,7 @@ describe('autocmd DirChanged and DirChangedPre', function() eq(4, eval('g:cdprecount')) eq(4, eval('g:cdcount')) - if iswin() then + if is_os('win') then command('split '..win_dirs[1]..'/baz') eq({}, eval('g:evpre')) eq({}, eval('g:ev')) @@ -278,7 +278,7 @@ describe('autocmd DirChanged and DirChangedPre', function() eq(9, eval('g:cdprecount')) -- same CWD, no DirChangedPre event eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event - if iswin() then + if is_os('win') then command('tabnew') -- tab 3 eq(9, eval('g:cdprecount')) -- same CWD, no DirChangedPre event eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event diff --git a/test/functional/autocmd/focus_spec.lua b/test/functional/autocmd/focus_spec.lua index e3c9e1f9ee..d7a87e17ed 100644 --- a/test/functional/autocmd/focus_spec.lua +++ b/test/functional/autocmd/focus_spec.lua @@ -6,7 +6,7 @@ local nvim_prog = helpers.nvim_prog local feed_command = helpers.feed_command local feed_data = thelpers.feed_data -if helpers.pending_win32(pending) then return end +if helpers.skip(helpers.is_os('win')) then return end describe('autoread TUI FocusGained/FocusLost', function() local f1 = 'xtest-foo' @@ -33,18 +33,37 @@ describe('autoread TUI FocusGained/FocusLost', function() helpers.write_file(path, '') lfs.touch(path, os.time() - 10) - feed_command('edit '..path) - feed_data('\027[O') screen:expect{grid=[[ {1: } | {4:~ }| {4:~ }| {4:~ }| + {5:[No Name] }| + | + {3:-- TERMINAL --} | + ]]} + feed_command('edit '..path) + screen:expect{grid=[[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| {5:xtest-foo }| :edit xtest-foo | {3:-- TERMINAL --} | ]]} + feed_data('\027[O') + feed_data('\027[O') + screen:expect{grid=[[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:xtest-foo }| + :edit xtest-foo | + {3:-- TERMINAL --} | + ]], unchanged=true} helpers.write_file(path, expected_addition) diff --git a/test/functional/autocmd/signal_spec.lua b/test/functional/autocmd/signal_spec.lua index d4f65cc61d..738064090a 100644 --- a/test/functional/autocmd/signal_spec.lua +++ b/test/functional/autocmd/signal_spec.lua @@ -5,11 +5,10 @@ local command = helpers.command local eq = helpers.eq local funcs = helpers.funcs local next_msg = helpers.next_msg +local is_os = helpers.is_os +local skip = helpers.skip -if helpers.pending_win32(pending) then - -- Only applies to POSIX systems. - return -end +if skip(is_os('win'), 'Only applies to POSIX systems') then return end local function posix_kill(signame, pid) os.execute('kill -s '..signame..' -- '..pid..' >/dev/null') diff --git a/test/functional/autocmd/termxx_spec.lua b/test/functional/autocmd/termxx_spec.lua index 859c2ebf44..0a33f1b2ac 100644 --- a/test/functional/autocmd/termxx_spec.lua +++ b/test/functional/autocmd/termxx_spec.lua @@ -5,11 +5,13 @@ local clear, command, nvim, testprg = helpers.clear, helpers.command, helpers.nvim, helpers.testprg local eval, eq, neq, retry = helpers.eval, helpers.eq, helpers.neq, helpers.retry +local matches = helpers.matches local ok = helpers.ok local feed = helpers.feed local pcall_err = helpers.pcall_err local assert_alive = helpers.assert_alive -local iswin = helpers.iswin +local skip = helpers.skip +local is_os = helpers.is_os describe('autocmd TermClose', function() before_each(function() @@ -22,7 +24,8 @@ describe('autocmd TermClose', function() local function test_termclose_delete_own_buf() command('autocmd TermClose * bdelete!') command('terminal') - eq('Vim(bdelete):E937: Attempt to delete a buffer that is in use', pcall_err(command, 'bdelete!')) + matches('^TermClose Autocommands for "%*": Vim%(bdelete%):E937: Attempt to delete a buffer that is in use: term://', + pcall_err(command, 'bdelete!')) assert_alive() end @@ -45,7 +48,7 @@ describe('autocmd TermClose', function() end) it('triggers when long-running terminal job gets stopped', function() - nvim('set_option', 'shell', iswin() and 'cmd.exe' or 'sh') + nvim('set_option', 'shell', is_os('win') and 'cmd.exe' or 'sh') command('autocmd TermClose * let g:test_termclose = 23') command('terminal') command('call jobstop(b:terminal_job_id)') @@ -53,7 +56,7 @@ describe('autocmd TermClose', function() end) it('kills job trapping SIGTERM', function() - if iswin() then return end + skip(is_os('win')) nvim('set_option', 'shell', 'sh') nvim('set_option', 'shellcmdflag', '-c') command([[ let g:test_job = jobstart('trap "" TERM && echo 1 && sleep 60', { ]] @@ -73,7 +76,7 @@ describe('autocmd TermClose', function() end) it('kills PTY job trapping SIGHUP and SIGTERM', function() - if iswin() then return end + skip(is_os('win')) nvim('set_option', 'shell', 'sh') nvim('set_option', 'shellcmdflag', '-c') command([[ let g:test_job = jobstart('trap "" HUP TERM && echo 1 && sleep 60', { ]] diff --git a/test/functional/autocmd/win_scrolled_resized_spec.lua b/test/functional/autocmd/win_scrolled_resized_spec.lua new file mode 100644 index 0000000000..4957f56dd4 --- /dev/null +++ b/test/functional/autocmd/win_scrolled_resized_spec.lua @@ -0,0 +1,329 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') + +local clear = helpers.clear +local eq = helpers.eq +local eval = helpers.eval +local exec = helpers.exec +local command = helpers.command +local feed = helpers.feed +local meths = helpers.meths +local assert_alive = helpers.assert_alive + +before_each(clear) + +describe('WinResized', function() + -- oldtest: Test_WinResized() + it('works', function() + exec([[ + set scrolloff=0 + call setline(1, ['111', '222']) + vnew + call setline(1, ['aaa', 'bbb']) + new + call setline(1, ['foo', 'bar']) + + let g:resized = 0 + au WinResized * let g:resized += 1 + au WinResized * let g:v_event = deepcopy(v:event) + ]]) + eq(0, eval('g:resized')) + + -- increase window height, two windows will be reported + feed('<C-W>+') + eq(1, eval('g:resized')) + eq({windows = {1002, 1001}}, eval('g:v_event')) + + -- increase window width, three windows will be reported + feed('<C-W>>') + eq(2, eval('g:resized')) + eq({windows = {1002, 1001, 1000}}, eval('g:v_event')) + end) +end) + +describe('WinScrolled', function() + local win_id + + before_each(function() + win_id = meths.get_current_win().id + command(string.format('autocmd WinScrolled %d let g:matched = v:true', win_id)) + exec([[ + let g:scrolled = 0 + au WinScrolled * let g:scrolled += 1 + au WinScrolled * let g:amatch = str2nr(expand('<amatch>')) + au WinScrolled * let g:afile = str2nr(expand('<afile>')) + au WinScrolled * let g:v_event = deepcopy(v:event) + ]]) + end) + + after_each(function() + eq(true, eval('g:matched')) + eq(win_id, eval('g:amatch')) + eq(win_id, eval('g:afile')) + end) + + it('is triggered by scrolling vertically', function() + local lines = {'123', '123'} + meths.buf_set_lines(0, 0, -1, true, lines) + eq(0, eval('g:scrolled')) + + feed('<C-E>') + eq(1, eval('g:scrolled')) + eq({ + all = {leftcol = 0, topline = 1, topfill = 0, width = 0, height = 0, skipcol = 0}, + ['1000'] = {leftcol = 0, topline = 1, topfill = 0, width = 0, height = 0, skipcol = 0}, + }, eval('g:v_event')) + + feed('<C-Y>') + eq(2, eval('g:scrolled')) + eq({ + all = {leftcol = 0, topline = 1, topfill = 0, width = 0, height = 0, skipcol = 0}, + ['1000'] = {leftcol = 0, topline = -1, topfill = 0, width = 0, height = 0, skipcol = 0}, + }, eval('g:v_event')) + end) + + it('is triggered by scrolling horizontally', function() + command('set nowrap') + local width = meths.win_get_width(0) + local line = '123' .. ('*'):rep(width * 2) + local lines = {line, line} + meths.buf_set_lines(0, 0, -1, true, lines) + eq(0, eval('g:scrolled')) + + feed('zl') + eq(1, eval('g:scrolled')) + eq({ + all = {leftcol = 1, topline = 0, topfill = 0, width = 0, height = 0, skipcol = 0}, + ['1000'] = {leftcol = 1, topline = 0, topfill = 0, width = 0, height = 0, skipcol = 0}, + }, eval('g:v_event')) + + feed('zh') + eq(2, eval('g:scrolled')) + eq({ + all = {leftcol = 1, topline = 0, topfill = 0, width = 0, height = 0, skipcol = 0}, + ['1000'] = {leftcol = -1, topline = 0, topfill = 0, width = 0, height = 0, skipcol = 0}, + }, eval('g:v_event')) + end) + + it('is triggered by horizontal scrolling from cursor move', function() + command('set nowrap') + local lines = {'', '', 'Foo'} + meths.buf_set_lines(0, 0, -1, true, lines) + meths.win_set_cursor(0, {3, 0}) + eq(0, eval('g:scrolled')) + + feed('zl') + eq(1, eval('g:scrolled')) + eq({ + all = {leftcol = 1, topline = 0, topfill = 0, width = 0, height = 0, skipcol = 0}, + ['1000'] = {leftcol = 1, topline = 0, topfill = 0, width = 0, height = 0, skipcol = 0}, + }, eval('g:v_event')) + + feed('zl') + eq(2, eval('g:scrolled')) + eq({ + all = {leftcol = 1, topline = 0, topfill = 0, width = 0, height = 0, skipcol = 0}, + ['1000'] = {leftcol = 1, topline = 0, topfill = 0, width = 0, height = 0, skipcol = 0}, + }, eval('g:v_event')) + + feed('h') + eq(3, eval('g:scrolled')) + eq({ + all = {leftcol = 1, topline = 0, topfill = 0, width = 0, height = 0, skipcol = 0}, + ['1000'] = {leftcol = -1, topline = 0, topfill = 0, width = 0, height = 0, skipcol = 0}, + }, eval('g:v_event')) + + feed('zh') + eq(4, eval('g:scrolled')) + eq({ + all = {leftcol = 1, topline = 0, topfill = 0, width = 0, height = 0, skipcol = 0}, + ['1000'] = {leftcol = -1, topline = 0, topfill = 0, width = 0, height = 0, skipcol = 0}, + }, eval('g:v_event')) + end) + + -- oldtest: Test_WinScrolled_long_wrapped() + it('is triggered by scrolling on a long wrapped line #19968', function() + local height = meths.win_get_height(0) + local width = meths.win_get_width(0) + meths.buf_set_lines(0, 0, -1, true, {('foo'):rep(height * width)}) + meths.win_set_cursor(0, {1, height * width - 1}) + eq(0, eval('g:scrolled')) + + feed('gj') + eq(1, eval('g:scrolled')) + eq({ + all = {leftcol = 0, topline = 0, topfill = 0, width = 0, height = 0, skipcol = width}, + ['1000'] = {leftcol = 0, topline = 0, topfill = 0, width = 0, height = 0, skipcol = width}, + }, eval('g:v_event')) + + feed('0') + eq(2, eval('g:scrolled')) + eq({ + all = {leftcol = 0, topline = 0, topfill = 0, width = 0, height = 0, skipcol = width}, + ['1000'] = {leftcol = 0, topline = 0, topfill = 0, width = 0, height = 0, skipcol = -width}, + }, eval('g:v_event')) + + feed('$') + eq(3, eval('g:scrolled')) + end) + + it('is triggered when the window scrolls in Insert mode', function() + local height = meths.win_get_height(0) + local lines = {} + for i = 1, height * 2 do + lines[i] = tostring(i) + end + meths.buf_set_lines(0, 0, -1, true, lines) + + feed('M') + eq(0, eval('g:scrolled')) + + feed('i<C-X><C-E><Esc>') + eq(1, eval('g:scrolled')) + eq({ + all = {leftcol = 0, topline = 1, topfill = 0, width = 0, height = 0, skipcol = 0}, + ['1000'] = {leftcol = 0, topline = 1, topfill = 0, width = 0, height = 0, skipcol = 0}, + }, eval('g:v_event')) + + feed('i<C-X><C-Y><Esc>') + eq(2, eval('g:scrolled')) + eq({ + all = {leftcol = 0, topline = 1, topfill = 0, width = 0, height = 0, skipcol = 0}, + ['1000'] = {leftcol = 0, topline = -1, topfill = 0, width = 0, height = 0, skipcol = 0}, + }, eval('g:v_event')) + + feed('L') + eq(2, eval('g:scrolled')) + + feed('A<CR><Esc>') + eq(3, eval('g:scrolled')) + eq({ + all = {leftcol = 0, topline = 1, topfill = 0, width = 0, height = 0, skipcol = 0}, + ['1000'] = {leftcol = 0, topline = 1, topfill = 0, width = 0, height = 0, skipcol = 0}, + }, eval('g:v_event')) + end) +end) + +describe('WinScrolled', function() + -- oldtest: Test_WinScrolled_mouse() + it('is triggered by mouse scrolling in another window', function() + local screen = Screen.new(75, 10) + screen:attach() + exec([[ + set nowrap scrolloff=0 + set mouse=a + call setline(1, ['foo']->repeat(32)) + split + let g:scrolled = 0 + au WinScrolled * let g:scrolled += 1 + ]]) + eq(0, eval('g:scrolled')) + + -- With the upper split focused, send a scroll-down event to the unfocused one. + meths.input_mouse('wheel', 'down', '', 0, 6, 0) + eq(1, eval('g:scrolled')) + + -- Again, but this time while we're in insert mode. + feed('i') + meths.input_mouse('wheel', 'down', '', 0, 6, 0) + feed('<Esc>') + eq(2, eval('g:scrolled')) + end) + + -- oldtest: Test_WinScrolled_close_curwin() + it('closing window does not cause use-after-free #13265', function() + exec([[ + set nowrap scrolloff=0 + call setline(1, ['aaa', 'bbb']) + vsplit + au WinScrolled * close + ]]) + + -- This was using freed memory + feed('<C-E>') + assert_alive() + end) + + -- oldtest: Test_WinScrolled_diff() + it('is triggered for both windows when scrolling in diff mode', function() + exec([[ + set diffopt+=foldcolumn:0 + call setline(1, ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']) + vnew + call setline(1, ['d', 'e', 'f', 'g', 'h', 'i']) + windo diffthis + au WinScrolled * let g:v_event = deepcopy(v:event) + ]]) + + feed('<C-E>') + eq({ + all = {leftcol = 0, topline = 1, topfill = 1, width = 0, height = 0, skipcol = 0}, + ['1000'] = {leftcol = 0, topline = 1, topfill = 0, width = 0, height = 0, skipcol = 0}, + ['1001'] = {leftcol = 0, topline = 0, topfill = -1, width = 0, height = 0, skipcol = 0}, + }, eval('g:v_event')) + + feed('2<C-E>') + eq({ + all = {leftcol = 0, topline = 2, topfill = 2, width = 0, height = 0, skipcol = 0}, + ['1000'] = {leftcol = 0, topline = 2, topfill = 0, width = 0, height = 0, skipcol = 0}, + ['1001'] = {leftcol = 0, topline = 0, topfill = -2, width = 0, height = 0, skipcol = 0}, + }, eval('g:v_event')) + + feed('<C-E>') + eq({ + all = {leftcol = 0, topline = 2, topfill = 0, width = 0, height = 0, skipcol = 0}, + ['1000'] = {leftcol = 0, topline = 1, topfill = 0, width = 0, height = 0, skipcol = 0}, + ['1001'] = {leftcol = 0, topline = 1, topfill = 0, width = 0, height = 0, skipcol = 0}, + }, eval('g:v_event')) + + feed('2<C-Y>') + eq({ + all = {leftcol = 0, topline = 3, topfill = 1, width = 0, height = 0, skipcol = 0}, + ['1000'] = {leftcol = 0, topline = -2, topfill = 0, width = 0, height = 0, skipcol = 0}, + ['1001'] = {leftcol = 0, topline = -1, topfill = 1, width = 0, height = 0, skipcol = 0}, + }, eval('g:v_event')) + end) + + it('is triggered by mouse scrolling in unfocused floating window #18222', function() + local screen = Screen.new(80, 24) + screen:attach() + + exec([[ + let g:scrolled = 0 + autocmd WinScrolled * let g:scrolled += 1 + autocmd WinScrolled * let g:amatch = expand('<amatch>') + autocmd WinScrolled * let g:v_event = deepcopy(v:event) + ]]) + eq(0, eval('g:scrolled')) + + local buf = meths.create_buf(true, true) + meths.buf_set_lines(buf, 0, -1, false, {'a', 'b', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n'}) + local win = meths.open_win(buf, false, { + height = 5, + width = 10, + col = 0, + row = 1, + relative = 'editor', + style = 'minimal' + }) + local winid_str = tostring(win.id) + -- WinScrolled should not be triggered when creating a new floating window + eq(0, eval('g:scrolled')) + + meths.input_mouse('wheel', 'down', '', 0, 3, 3) + eq(1, eval('g:scrolled')) + eq(winid_str, eval('g:amatch')) + eq({ + all = {leftcol = 0, topline = 3, topfill = 0, width = 0, height = 0, skipcol = 0}, + [winid_str] = {leftcol = 0, topline = 3, topfill = 0, width = 0, height = 0, skipcol = 0}, + }, eval('g:v_event')) + + meths.input_mouse('wheel', 'up', '', 0, 3, 3) + eq(2, eval('g:scrolled')) + eq(tostring(win.id), eval('g:amatch')) + eq({ + all = {leftcol = 0, topline = 3, topfill = 0, width = 0, height = 0, skipcol = 0}, + [winid_str] = {leftcol = 0, topline = -3, topfill = 0, width = 0, height = 0, skipcol = 0}, + }, eval('g:v_event')) + end) +end) diff --git a/test/functional/autocmd/winscrolled_spec.lua b/test/functional/autocmd/winscrolled_spec.lua deleted file mode 100644 index 12b8e7c42d..0000000000 --- a/test/functional/autocmd/winscrolled_spec.lua +++ /dev/null @@ -1,99 +0,0 @@ -local helpers = require('test.functional.helpers')(after_each) - -local clear = helpers.clear -local eq = helpers.eq -local eval = helpers.eval -local command = helpers.command -local feed = helpers.feed -local meths = helpers.meths -local assert_alive = helpers.assert_alive - -before_each(clear) - -describe('WinScrolled', function() - local win_id - - before_each(function() - win_id = meths.get_current_win().id - command(string.format('autocmd WinScrolled %d let g:matched = v:true', win_id)) - command('let g:scrolled = 0') - command('autocmd WinScrolled * let g:scrolled += 1') - command([[autocmd WinScrolled * let g:amatch = str2nr(expand('<amatch>'))]]) - command([[autocmd WinScrolled * let g:afile = str2nr(expand('<afile>'))]]) - end) - - after_each(function() - eq(true, eval('g:matched')) - eq(win_id, eval('g:amatch')) - eq(win_id, eval('g:afile')) - end) - - it('is triggered by scrolling vertically', function() - local lines = {'123', '123'} - meths.buf_set_lines(0, 0, -1, true, lines) - eq(0, eval('g:scrolled')) - feed('<C-E>') - eq(1, eval('g:scrolled')) - end) - - it('is triggered by scrolling horizontally', function() - command('set nowrap') - local width = meths.win_get_width(0) - local line = '123' .. ('*'):rep(width * 2) - local lines = {line, line} - meths.buf_set_lines(0, 0, -1, true, lines) - eq(0, eval('g:scrolled')) - feed('zl') - eq(1, eval('g:scrolled')) - end) - - it('is triggered by horizontal scrolling from cursor move', function() - command('set nowrap') - local lines = {'', '', 'Foo'} - meths.buf_set_lines(0, 0, -1, true, lines) - meths.win_set_cursor(0, {3, 0}) - eq(0, eval('g:scrolled')) - feed('zl') - eq(1, eval('g:scrolled')) - feed('zl') - eq(2, eval('g:scrolled')) - feed('h') - eq(3, eval('g:scrolled')) - end) - - it('is triggered by scrolling on a long wrapped line #19968', function() - local height = meths.win_get_height(0) - local width = meths.win_get_width(0) - meths.buf_set_lines(0, 0, -1, true, {('foo'):rep(height * width)}) - meths.win_set_cursor(0, {1, height * width - 1}) - eq(0, eval('g:scrolled')) - feed('gj') - eq(1, eval('g:scrolled')) - feed('0') - eq(2, eval('g:scrolled')) - feed('$') - eq(3, eval('g:scrolled')) - end) - - it('is triggered when the window scrolls in Insert mode', function() - local height = meths.win_get_height(0) - local lines = {} - for i = 1, height * 2 do - lines[i] = tostring(i) - end - meths.buf_set_lines(0, 0, -1, true, lines) - feed('L') - eq(0, eval('g:scrolled')) - feed('A<CR><Esc>') - eq(1, eval('g:scrolled')) - end) -end) - -it('closing window in WinScrolled does not cause use-after-free #13265', function() - local lines = {'aaa', 'bbb'} - meths.buf_set_lines(0, 0, -1, true, lines) - command('vsplit') - command('autocmd WinScrolled * close') - feed('<C-E>') - assert_alive() -end) diff --git a/test/functional/core/channels_spec.lua b/test/functional/core/channels_spec.lua index ca52404d3b..8275575c24 100644 --- a/test/functional/core/channels_spec.lua +++ b/test/functional/core/channels_spec.lua @@ -1,5 +1,4 @@ local helpers = require('test.functional.helpers')(after_each) -local uname = helpers.uname local clear, eq, eval, next_msg, ok, source = helpers.clear, helpers.eq, helpers.eval, helpers.next_msg, helpers.ok, helpers.source local command, funcs, meths = helpers.command, helpers.funcs, helpers.meths @@ -12,6 +11,7 @@ local retry = helpers.retry local expect_twostreams = helpers.expect_twostreams local assert_alive = helpers.assert_alive local pcall_err = helpers.pcall_err +local skip = helpers.skip describe('channels', function() local init = [[ @@ -145,7 +145,7 @@ describe('channels', function() end it('can use stdio channel with pty', function() - if helpers.pending_win32(pending) then return end + skip(is_os('win')) source([[ let g:job_opts = { \ 'on_stdout': function('OnEvent'), @@ -178,8 +178,7 @@ describe('channels', function() command("call chansend(id, 'incomplet\004')") - local is_bsd = not not string.find(uname(), 'bsd') - local bsdlike = is_bsd or is_os('mac') + local bsdlike = is_os('bsd') or is_os('mac') local extra = bsdlike and "^D\008\008" or "" expect_twoline(id, "stdout", "incomplet"..extra, "[1, ['incomplet'], 'stdin']", true) @@ -199,7 +198,7 @@ describe('channels', function() it('stdio channel can use rpc and stderr simultaneously', function() - if helpers.pending_win32(pending) then return end + skip(is_os('win')) source([[ let g:job_opts = { \ 'on_stderr': function('OnEvent'), diff --git a/test/functional/core/exit_spec.lua b/test/functional/core/exit_spec.lua index 8cad7adfa6..05a69e1992 100644 --- a/test/functional/core/exit_spec.lua +++ b/test/functional/core/exit_spec.lua @@ -89,14 +89,14 @@ describe(':cquit', function() end) it('exits with redir msg for multiple exit codes after :cquit 1 2', function() - test_cq('cquit 1 2', nil, 'Vim(cquit):E488: Trailing characters: 2: cquit 1 2') + test_cq('cquit 1 2', nil, 'nvim_exec(): Vim(cquit):E488: Trailing characters: 2: cquit 1 2') end) it('exits with redir msg for non-number exit code after :cquit X', function() - test_cq('cquit X', nil, 'Vim(cquit):E488: Trailing characters: X: cquit X') + test_cq('cquit X', nil, 'nvim_exec(): Vim(cquit):E488: Trailing characters: X: cquit X') end) it('exits with redir msg for negative exit code after :cquit -1', function() - test_cq('cquit -1', nil, 'Vim(cquit):E488: Trailing characters: -1: cquit -1') + test_cq('cquit -1', nil, 'nvim_exec(): Vim(cquit):E488: Trailing characters: -1: cquit -1') end) end) diff --git a/test/functional/core/fileio_spec.lua b/test/functional/core/fileio_spec.lua index d1ff5b8036..4e9891a4de 100644 --- a/test/functional/core/fileio_spec.lua +++ b/test/functional/core/fileio_spec.lua @@ -1,3 +1,4 @@ +local lfs = require('lfs') local helpers = require('test.functional.helpers')(after_each) local assert_log = helpers.assert_log @@ -5,6 +6,7 @@ local assert_nolog = helpers.assert_nolog local clear = helpers.clear local command = helpers.command local eq = helpers.eq +local neq = helpers.neq local ok = helpers.ok local feed = helpers.feed local funcs = helpers.funcs @@ -19,19 +21,21 @@ local read_file = helpers.read_file local tmpname = helpers.tmpname local trim = helpers.trim local currentdir = helpers.funcs.getcwd -local iswin = helpers.iswin local assert_alive = helpers.assert_alive +local check_close = helpers.check_close local expect_exit = helpers.expect_exit local write_file = helpers.write_file local Screen = require('test.functional.ui.screen') local feed_command = helpers.feed_command -local uname = helpers.uname +local skip = helpers.skip +local is_os = helpers.is_os +local is_ci = helpers.is_ci describe('fileio', function() before_each(function() end) after_each(function() - expect_exit(command, ':qall!') + check_close() os.remove('Xtest_startup_shada') os.remove('Xtest_startup_file1') os.remove('Xtest_startup_file1~') @@ -87,9 +91,7 @@ describe('fileio', function() end) it('backup #9709', function() - if uname() == 'freebsd' then - pending('Failing FreeBSD test') - end + skip(is_ci('cirrus')) clear({ args={ '-i', 'Xtest_startup_shada', '--cmd', 'set directory=Xtest_startup_swapdir' } }) @@ -109,9 +111,7 @@ describe('fileio', function() end) it('backup with full path #11214', function() - if uname() == 'freebsd' then - pending('Failing FreeBSD test') - end + skip(is_ci('cirrus')) clear() mkdir('Xtest_backupdir') command('set backup') @@ -124,7 +124,7 @@ describe('fileio', function() -- Backup filename = fullpath, separators replaced with "%". local backup_file_name = string.gsub(currentdir()..'/Xtest_startup_file1', - iswin() and '[:/\\]' or '/', '%%') .. '~' + is_os('win') and '[:/\\]' or '/', '%%') .. '~' local foo_contents = trim(read_file('Xtest_backupdir/'..backup_file_name)) local foobar_contents = trim(read_file('Xtest_startup_file1')) @@ -132,6 +132,53 @@ describe('fileio', function() eq('foo', foo_contents); end) + it('backup symlinked files #11349', function() + skip(is_ci('cirrus')) + clear() + + local initial_content = 'foo' + local link_file_name = 'Xtest_startup_file2' + local backup_file_name = link_file_name .. '~' + + write_file('Xtest_startup_file1', initial_content, false) + lfs.link('Xtest_startup_file1', link_file_name, true) + command('set backup') + command('set backupcopy=yes') + command('edit ' .. link_file_name) + feed('Abar<esc>') + command('write') + + local backup_raw = read_file(backup_file_name) + neq(nil, backup_raw, "Expected backup file " .. backup_file_name .. "to exist but did not") + eq(initial_content, trim(backup_raw), 'Expected backup to contain original contents') + end) + + + it('backup symlinked files in first available backupdir #11349', function() + skip(is_ci('cirrus')) + clear() + + local initial_content = 'foo' + local backup_dir = 'Xtest_backupdir' + local sep = helpers.get_pathsep() + local link_file_name = 'Xtest_startup_file2' + local backup_file_name = backup_dir .. sep .. link_file_name .. '~' + + write_file('Xtest_startup_file1', initial_content, false) + lfs.link('Xtest_startup_file1', link_file_name, true) + mkdir(backup_dir) + command('set backup') + command('set backupcopy=yes') + command('set backupdir=.__this_does_not_exist__,' .. backup_dir) + command('edit ' .. link_file_name) + feed('Abar<esc>') + command('write') + + local backup_raw = read_file(backup_file_name) + neq(nil, backup_raw, "Expected backup file " .. backup_file_name .. " to exist but did not") + eq(initial_content, trim(backup_raw), 'Expected backup to contain original contents') + end) + it('readfile() on multibyte filename #10586', function() clear() local text = { @@ -224,7 +271,7 @@ describe('tmpdir', function() end) after_each(function() - expect_exit(command, ':qall!') + check_close() os.remove(testlog) end) @@ -248,14 +295,10 @@ describe('tmpdir', function() clear({ env={ NVIM_LOG_FILE=testlog, TMPDIR=faketmp, } }) matches(tmproot_pat, funcs.stdpath('run')) -- Tickle vim_mktempdir(). -- Assert that broken tmpdir root was handled. - retry(nil, 1000, function() - assert_log('tempdir root not a directory', testlog, 100) - end) + assert_log('tempdir root not a directory', testlog, 100) -- "…/nvim.<user>/" has wrong permissions: - if iswin() then - return -- TODO(justinmk): need setfperm/getfperm on Windows. #8244 - end + skip(is_os('win'), 'TODO(justinmk): need setfperm/getfperm on Windows. #8244') os.remove(testlog) os.remove(tmproot) mkdir(tmproot) @@ -263,9 +306,7 @@ describe('tmpdir', function() clear({ env={ NVIM_LOG_FILE=testlog, TMPDIR=faketmp, } }) matches(tmproot_pat, funcs.stdpath('run')) -- Tickle vim_mktempdir(). -- Assert that broken tmpdir root was handled. - retry(nil, 1000, function() - assert_log('tempdir root has invalid permissions', testlog, 100) - end) + assert_log('tempdir root has invalid permissions', testlog, 100) end) it('too long', function() diff --git a/test/functional/core/job_spec.lua b/test/functional/core/job_spec.lua index 02ff18bdda..1bae626b98 100644 --- a/test/functional/core/job_spec.lua +++ b/test/functional/core/job_spec.lua @@ -13,7 +13,6 @@ local retry = helpers.retry local meths = helpers.meths local NIL = helpers.NIL local poke_eventloop = helpers.poke_eventloop -local iswin = helpers.iswin local get_pathsep = helpers.get_pathsep local pathroot = helpers.pathroot local exec_lua = helpers.exec_lua @@ -23,6 +22,8 @@ local expect_msg_seq = helpers.expect_msg_seq local pcall_err = helpers.pcall_err local matches = helpers.matches local Screen = require('test.functional.ui.screen') +local skip = helpers.skip +local is_os = helpers.is_os describe('jobs', function() local channel @@ -55,7 +56,7 @@ describe('jobs', function() it('must specify env option as a dict', function() command("let g:job_opts.env = v:true") local _, err = pcall(function() - if iswin() then + if is_os('win') then nvim('command', "let j = jobstart('set', g:job_opts)") else nvim('command', "let j = jobstart('env', g:job_opts)") @@ -68,7 +69,7 @@ describe('jobs', function() nvim('command', "let $VAR = 'abc'") nvim('command', "let $TOTO = 'goodbye world'") nvim('command', "let g:job_opts.env = {'TOTO': 'hello world'}") - if iswin() then + if is_os('win') then nvim('command', [[call jobstart('echo %TOTO% %VAR%', g:job_opts)]]) else nvim('command', [[call jobstart('echo $TOTO $VAR', g:job_opts)]]) @@ -87,12 +88,12 @@ describe('jobs', function() end) it('append environment with pty #env', function() - if helpers.pending_win32(pending) then return end + skip(is_os('win')) nvim('command', "let $VAR = 'abc'") nvim('command', "let $TOTO = 'goodbye world'") nvim('command', "let g:job_opts.pty = v:true") nvim('command', "let g:job_opts.env = {'TOTO': 'hello world'}") - if iswin() then + if is_os('win') then nvim('command', [[call jobstart('echo %TOTO% %VAR%', g:job_opts)]]) else nvim('command', [[call jobstart('echo $TOTO $VAR', g:job_opts)]]) @@ -122,7 +123,7 @@ describe('jobs', function() -- -- Rather than expecting a completely empty environment, ensure that $VAR -- is *not* in the environment but $TOTO is. - if iswin() then + if is_os('win') then nvim('command', [[call jobstart('echo %TOTO% %VAR%', g:job_opts)]]) expect_msg_seq({ {'notification', 'stdout', {0, {'hello world %VAR%', ''}}} @@ -141,7 +142,7 @@ describe('jobs', function() -- Since $Toto is being set in the job, it should take precedence over the -- global $TOTO on Windows nvim('command', "let g:job_opts = {'env': {'Toto': 'def'}, 'stdout_buffered': v:true}") - if iswin() then + if is_os('win') then nvim('command', [[let j = jobstart('set | find /I "toto="', g:job_opts)]]) else nvim('command', [[let j = jobstart('env | grep -i toto=', g:job_opts)]]) @@ -150,7 +151,7 @@ describe('jobs', function() nvim('command', "let g:output = Normalize(g:job_opts.stdout)") local actual = eval('g:output') local expected - if iswin() then + if is_os('win') then -- Toto is normalized to TOTO so we can detect duplicates, and because -- Windows doesn't care about case expected = {'TOTO=def', ''} @@ -164,7 +165,7 @@ describe('jobs', function() it('uses &shell and &shellcmdflag if passed a string', function() nvim('command', "let $VAR = 'abc'") - if iswin() then + if is_os('win') then nvim('command', "let j = jobstart('echo %VAR%', g:job_opts)") else nvim('command', "let j = jobstart('echo $VAR', g:job_opts)") @@ -176,7 +177,7 @@ describe('jobs', function() it('changes to given / directory', function() nvim('command', "let g:job_opts.cwd = '/'") - if iswin() then + if is_os('win') then nvim('command', "let j = jobstart('cd', g:job_opts)") else nvim('command', "let j = jobstart('pwd', g:job_opts)") @@ -191,7 +192,7 @@ describe('jobs', function() local dir = eval("resolve(tempname())"):gsub("/", get_pathsep()) mkdir(dir) nvim('command', "let g:job_opts.cwd = '" .. dir .. "'") - if iswin() then + if is_os('win') then nvim('command', "let j = jobstart('cd', g:job_opts)") else nvim('command', "let j = jobstart('pwd', g:job_opts)") @@ -215,7 +216,7 @@ describe('jobs', function() local dir = eval('resolve(tempname())."-bogus"') local _, err = pcall(function() nvim('command', "let g:job_opts.cwd = '" .. dir .. "'") - if iswin() then + if is_os('win') then nvim('command', "let j = jobstart('cd', g:job_opts)") else nvim('command', "let j = jobstart('pwd', g:job_opts)") @@ -225,7 +226,7 @@ describe('jobs', function() end) it('error on non-executable `cwd`', function() - if iswin() then return end -- N/A for Windows + skip(is_os('win'), 'Not applicable for Windows') local dir = 'Xtest_not_executable_dir' mkdir(dir) @@ -248,7 +249,7 @@ describe('jobs', function() end local executable_jobid = new_job() - local exe = iswin() and './test/functional/fixtures' or './test/functional/fixtures/non_executable.txt' + local exe = is_os('win') and './test/functional/fixtures' or './test/functional/fixtures/non_executable.txt' eq("Vim:E475: Invalid value for argument cmd: '"..exe.."' is not executable", pcall_err(eval, "jobstart(['"..exe.."'])")) eq("", eval("v:errmsg")) @@ -702,7 +703,7 @@ describe('jobs', function() describe('jobwait', function() before_each(function() - if iswin() then + if is_os('win') then helpers.set_shell_powershell() end end) @@ -786,7 +787,7 @@ describe('jobs', function() feed_command('call rpcnotify(g:channel, "ready") | '.. 'call rpcnotify(g:channel, "wait", '.. 'jobwait([jobstart("'.. - (iswin() and 'Start-Sleep 10' or 'sleep 10').. + (is_os('win') and 'Start-Sleep 10' or 'sleep 10').. '; exit 55")]))') eq({'notification', 'ready', {}}, next_msg()) feed('<c-c>') @@ -797,7 +798,7 @@ describe('jobs', function() feed_command('call rpcnotify(g:channel, "ready") | '.. 'call rpcnotify(g:channel, "wait", '.. 'jobwait([jobstart("'.. - (iswin() and 'Start-Sleep 10' or 'sleep 10').. + (is_os('win') and 'Start-Sleep 10' or 'sleep 10').. '; exit 55")], 10000))') eq({'notification', 'ready', {}}, next_msg()) feed('<c-c>') @@ -805,7 +806,7 @@ describe('jobs', function() end) it('can be called recursively', function() - if helpers.pending_win32(pending) then return end -- TODO: Need `cat`. + skip(is_os('win'), "TODO: Need `cat`") source([[ let g:opts = {} let g:counter = 0 @@ -930,7 +931,7 @@ describe('jobs', function() -- ..c.."', '-c', '"..c.."'])") -- Create child with several descendants. - if iswin() then + if is_os('win') then source([[ function! s:formatprocs(pid, prefix) let result = '' @@ -979,13 +980,13 @@ describe('jobs', function() endfunction ]]) end - local sleep_cmd = (iswin() + local sleep_cmd = (is_os('win') and 'ping -n 31 127.0.0.1' or 'sleep 30') local j = eval("jobstart('"..sleep_cmd..' | '..sleep_cmd..' | '..sleep_cmd.."')") local ppid = funcs.jobpid(j) local children - if iswin() then + if is_os('win') then local status, result = pcall(retry, nil, nil, function() children = meths.get_proc_children(ppid) -- On Windows conhost.exe may exist, and @@ -1006,7 +1007,7 @@ describe('jobs', function() -- Assert that nvim_get_proc() sees the children. for _, child_pid in ipairs(children) do local info = meths.get_proc(child_pid) - -- eq((iswin() and 'nvim.exe' or 'nvim'), info.name) + -- eq((is_os('win') and 'nvim.exe' or 'nvim'), info.name) eq(ppid, info.ppid) end -- Kill the root of the tree. @@ -1027,7 +1028,7 @@ describe('jobs', function() end) describe('running tty-test program', function() - if helpers.pending_win32(pending) then return end + if skip(is_os('win')) then return end local function next_chunk() local rv while true do @@ -1124,7 +1125,7 @@ describe("pty process teardown", function() end) it("does not prevent/delay exit. #4798 #4900", function() - if helpers.pending_win32(pending) then return end + skip(is_os('win')) -- Use a nested nvim (in :term) to test without --headless. feed_command(":terminal '"..helpers.nvim_prog .."' -u NONE -i NONE --cmd '"..nvim_set.."' " diff --git a/test/functional/core/log_spec.lua b/test/functional/core/log_spec.lua index 3b1ccd9559..f682df4155 100644 --- a/test/functional/core/log_spec.lua +++ b/test/functional/core/log_spec.lua @@ -6,7 +6,6 @@ local eq = helpers.eq local exec_lua = helpers.exec_lua local expect_exit = helpers.expect_exit local request = helpers.request -local retry = helpers.retry describe('log', function() local testlog = 'Xtest_logging' @@ -40,9 +39,7 @@ describe('log', function() }}) local tid = _G._nvim_test_id - retry(nil, 1000, function() - assert_log(tid..'%.%d+%.%d +server_init:%d+: test log message', testlog, 100) - end) + assert_log(tid..'%.%d+%.%d +server_init:%d+: test log message', testlog, 100) exec_lua([[ local j1 = vim.fn.jobstart({ vim.v.progpath, '-es', '-V1', '+foochild', '+qa!' }, vim.empty_dict()) @@ -50,8 +47,6 @@ describe('log', function() ]]) -- Child Nvim spawned by jobstart() appends "/c" to parent name. - retry(nil, 1000, function() - assert_log('%.%d+%.%d/c +server_init:%d+: test log message', testlog, 100) - end) + assert_log('%.%d+%.%d/c +server_init:%d+: test log message', testlog, 100) end) end) diff --git a/test/functional/core/main_spec.lua b/test/functional/core/main_spec.lua index f6fb859ccc..ab11e14a67 100644 --- a/test/functional/core/main_spec.lua +++ b/test/functional/core/main_spec.lua @@ -9,6 +9,8 @@ local clear = helpers.clear local funcs = helpers.funcs local nvim_prog_abs = helpers.nvim_prog_abs local write_file = helpers.write_file +local is_os = helpers.is_os +local skip = helpers.skip describe('Command-line option', function() describe('-s', function() @@ -49,12 +51,12 @@ describe('Command-line option', function() eq(#('100500\n'), attrs.size) end) it('does not crash after reading from stdin in non-headless mode', function() - if helpers.pending_win32(pending) then return end + skip(is_os('win')) local screen = Screen.new(40, 8) screen:attach() local args = { nvim_prog_abs(), '-u', 'NONE', '-i', 'NONE', - '--cmd', 'set noswapfile shortmess+=IFW fileformats=unix', + '--cmd', '"set noswapfile shortmess+=IFW fileformats=unix"', '-s', '-' } diff --git a/test/functional/core/path_spec.lua b/test/functional/core/path_spec.lua index 61fae7622c..a786887bbd 100644 --- a/test/functional/core/path_spec.lua +++ b/test/functional/core/path_spec.lua @@ -3,16 +3,16 @@ local clear = helpers.clear local eq = helpers.eq local eval = helpers.eval local command = helpers.command -local iswin = helpers.iswin local insert = helpers.insert local feed = helpers.feed +local is_os = helpers.is_os describe('path collapse', function() local targetdir local expected_path local function join_path(...) - local pathsep = (iswin() and '\\' or '/') + local pathsep = (is_os('win') and '\\' or '/') return table.concat({...}, pathsep) end diff --git a/test/functional/core/startup_spec.lua b/test/functional/core/startup_spec.lua index a32c801c97..1be5de6488 100644 --- a/test/functional/core/startup_spec.lua +++ b/test/functional/core/startup_spec.lua @@ -12,6 +12,7 @@ local eval = helpers.eval local exec_lua = helpers.exec_lua local feed = helpers.feed local funcs = helpers.funcs +local pesc = helpers.pesc local mkdir = helpers.mkdir local mkdir_p = helpers.mkdir_p local nvim_prog = helpers.nvim_prog @@ -20,11 +21,12 @@ local read_file = helpers.read_file local retry = helpers.retry local rmdir = helpers.rmdir local sleep = helpers.sleep -local iswin = helpers.iswin local startswith = helpers.startswith local write_file = helpers.write_file local meths = helpers.meths local alter_slashes = helpers.alter_slashes +local is_os = helpers.is_os +local dedent = helpers.dedent local testfile = 'Xtest_startuptime' after_each(function() @@ -41,10 +43,36 @@ describe('startup', function() it('--startuptime', function() clear({ args = {'--startuptime', testfile}}) - retry(nil, 1000, function() - assert_log('sourcing', testfile, 100) - assert_log("require%('vim%._editor'%)", testfile, 100) - end) + assert_log('sourcing', testfile, 100) + assert_log("require%('vim%._editor'%)", testfile, 100) + end) + + it('-D does not hang #12647', function() + clear() + local screen + screen = Screen.new(60, 7) + screen:attach() + command([[let g:id = termopen('"]]..nvim_prog.. + [[" -u NONE -i NONE --cmd "set noruler" -D')]]) + screen:expect([[ + ^ | + | + Entering Debug mode. Type "cont" to continue. | + nvim_exec() | + cmd: aunmenu * | + > | + | + ]]) + command([[call chansend(g:id, "cont\n")]]) + screen:expect([[ + ^ | + ~ | + ~ | + ~ | + [No Name] | + | + | + ]]) end) end) @@ -57,6 +85,124 @@ describe('startup', function() os.remove('Xtest_startup_ttyout') end) + describe('-l Lua', function() + local function assert_l_out(expected, nvim_args, lua_args, script, input) + local args = { nvim_prog } + vim.list_extend(args, nvim_args or {}) + vim.list_extend(args, { '-l', (script or 'test/functional/fixtures/startup.lua') }) + vim.list_extend(args, lua_args or {}) + local out = funcs.system(args, input):gsub('\r\n', '\n') + return eq(dedent(expected), out) + end + + it('failure modes', function() + -- nvim -l <empty> + matches('nvim%.?e?x?e?: Argument missing after: "%-l"', funcs.system({ nvim_prog, '-l' })) + eq(1, eval('v:shell_error')) + end) + + it('os.exit() sets Nvim exitcode', function() + -- nvim -l foo.lua -arg1 -- a b c + assert_l_out([[ + bufs: + nvim args: 7 + lua args: { "-arg1", "--exitcode", "73", "--arg2", + [0] = "test/functional/fixtures/startup.lua" + }]], + {}, + { '-arg1', "--exitcode", "73", '--arg2' } + ) + eq(73, eval('v:shell_error')) + end) + + it('Lua-error sets Nvim exitcode', function() + eq(0, eval('v:shell_error')) + matches('E5113: .* my pearls!!', + funcs.system({ nvim_prog, '-l', 'test/functional/fixtures/startup-fail.lua' })) + eq(1, eval('v:shell_error')) + matches('E5113: .* %[string "error%("whoa"%)"%]:1: whoa', + funcs.system({ nvim_prog, '-l', '-' }, 'error("whoa")')) + eq(1, eval('v:shell_error')) + end) + + it('executes stdin "-"', function() + assert_l_out('arg0=- args=2 whoa', + nil, + { 'arg1', 'arg 2' }, + '-', + "print(('arg0=%s args=%d %s'):format(_G.arg[0], #_G.arg, 'whoa'))") + assert_l_out('biiig input: 1000042', + nil, + nil, + '-', + ('print("biiig input: "..("%s"):len())'):format(string.rep('x', (1000 * 1000) + 42))) + eq(0, eval('v:shell_error')) + end) + + it('sets _G.arg', function() + -- nvim -l foo.lua [args] + assert_l_out([[ + bufs: + nvim args: 7 + lua args: { "-arg1", "--arg2", "--", "arg3", + [0] = "test/functional/fixtures/startup.lua" + }]], + {}, + { '-arg1', '--arg2', '--', 'arg3' } + ) + eq(0, eval('v:shell_error')) + + -- nvim file1 file2 -l foo.lua -arg1 -- file3 file4 + assert_l_out([[ + bufs: file1 file2 + nvim args: 10 + lua args: { "-arg1", "arg 2", "--", "file3", "file4", + [0] = "test/functional/fixtures/startup.lua" + }]], + { 'file1', 'file2', }, + { '-arg1', 'arg 2', '--', 'file3', 'file4' } + ) + eq(0, eval('v:shell_error')) + + -- nvim -l foo.lua <vim args> + assert_l_out([[ + bufs: + nvim args: 5 + lua args: { "-c", "set wrap?", + [0] = "test/functional/fixtures/startup.lua" + }]], + {}, + { '-c', 'set wrap?' } + ) + eq(0, eval('v:shell_error')) + + -- nvim <vim args> -l foo.lua <vim args> + assert_l_out( + -- luacheck: ignore 611 (Line contains only whitespaces) + [[ + wrap + + bufs: + nvim args: 7 + lua args: { "-c", "set wrap?", + [0] = "test/functional/fixtures/startup.lua" + }]], + { '-c', 'set wrap?' }, + { '-c', 'set wrap?' } + ) + eq(0, eval('v:shell_error')) + end) + + it('disables swapfile/shada/config/plugins', function() + assert_l_out('updatecount=0 shadafile=NONE loadplugins=false scriptnames=1', + nil, + nil, + '-', + [[print(('updatecount=%d shadafile=%s loadplugins=%s scriptnames=%d'):format( + vim.o.updatecount, vim.o.shadafile, tostring(vim.o.loadplugins), math.max(1, #vim.fn.split(vim.fn.execute('scriptnames'),'\n'))))]]) + end) + end) + it('pipe at both ends: has("ttyin")==0 has("ttyout")==0', function() -- system() puts a pipe at both ends. local out = funcs.system({ nvim_prog, '-u', 'NONE', '-i', 'NONE', '--headless', @@ -65,6 +211,7 @@ describe('startup', function() '+q' }) eq('0 0', out) end) + it('with --embed: has("ttyin")==0 has("ttyout")==0', function() local screen = Screen.new(25, 3) -- Remote UI connected by --embed. @@ -76,10 +223,11 @@ describe('startup', function() 0 0 | ]]) end) + it('in a TTY: has("ttyin")==1 has("ttyout")==1', function() local screen = Screen.new(25, 4) screen:attach() - if iswin() then + if is_os('win') then command([[set shellcmdflag=/s\ /c shellxquote=\"]]) end -- Running in :terminal @@ -94,8 +242,9 @@ describe('startup', function() | ]]) end) + it('output to pipe: has("ttyin")==1 has("ttyout")==0', function() - if iswin() then + if is_os('win') then command([[set shellcmdflag=/s\ /c shellxquote=\"]]) end -- Running in :terminal @@ -110,8 +259,9 @@ describe('startup', function() read_file('Xtest_startup_ttyout')) end) end) + it('input from pipe: has("ttyin")==0 has("ttyout")==1', function() - if iswin() then + if is_os('win') then command([[set shellcmdflag=/s\ /c shellxquote=\"]]) end -- Running in :terminal @@ -127,10 +277,11 @@ describe('startup', function() read_file('Xtest_startup_ttyout')) end) end) + it('input from pipe (implicit) #7679', function() local screen = Screen.new(25, 4) screen:attach() - if iswin() then + if is_os('win') then command([[set shellcmdflag=/s\ /c shellxquote=\"]]) end -- Running in :terminal @@ -146,6 +297,7 @@ describe('startup', function() | ]]) end) + it('input from pipe + file args #7679', function() eq('ohyeah\r\n0 0 bufs=3', funcs.system({nvim_prog, '-n', '-u', 'NONE', '-i', 'NONE', '--headless', @@ -237,11 +389,11 @@ describe('startup', function() it('-es/-Es disables swapfile, user config #8540', function() for _,arg in ipairs({'-es', '-Es'}) do local out = funcs.system({nvim_prog, arg, - '+set swapfile? updatecount? shada?', + '+set swapfile? updatecount? shadafile?', "+put =execute('scriptnames')", '+%print'}) local line1 = string.match(out, '^.-\n') -- updatecount=0 means swapfile was disabled. - eq(" swapfile updatecount=0 shada=!,'100,<50,s10,h\n", line1) + eq(" swapfile updatecount=0 shadafile=\n", line1) -- Standard plugins were loaded, but not user config. eq('health.vim', string.match(out, 'health.vim')) eq(nil, string.match(out, 'init.vim')) @@ -265,11 +417,13 @@ describe('startup', function() { 'put =mode(1)', 'print', '' })) end) - it('fails on --embed with -es/-Es', function() - matches('nvim[.exe]*: %-%-embed conflicts with %-es/%-Es', + it('fails on --embed with -es/-Es/-l', function() + matches('nvim[.exe]*: %-%-embed conflicts with %-es/%-Es/%-l', funcs.system({nvim_prog, '--embed', '-es' })) - matches('nvim[.exe]*: %-%-embed conflicts with %-es/%-Es', + matches('nvim[.exe]*: %-%-embed conflicts with %-es/%-Es/%-l', funcs.system({nvim_prog, '--embed', '-Es' })) + matches('nvim[.exe]*: %-%-embed conflicts with %-es/%-Es/%-l', + funcs.system({nvim_prog, '--embed', '-l', 'foo.lua' })) end) it('does not crash if --embed is given twice', function() @@ -464,6 +618,19 @@ describe('startup', function() clear{args={'--cmd', 'set packpath^=test/functional/fixtures', '--cmd', [[ lua _G.test_loadorder = {} vim.cmd "runtime! filen.lua" ]]}, env={XDG_CONFIG_HOME='test/functional/fixtures/'}} eq({'ordinary', 'FANCY', 'FANCY after', 'ordinary after'}, exec_lua [[ return _G.test_loadorder ]]) end) + + it('window widths are correct when modelines set &columns with tabpages', function() + write_file('tab1.noft', 'vim: columns=81') + write_file('tab2.noft', 'vim: columns=81') + finally(function() + os.remove('tab1.noft') + os.remove('tab2.noft') + end) + clear({args = {'-p', 'tab1.noft', 'tab2.noft'}}) + eq(81, meths.win_get_width(0)) + command('tabnext') + eq(81, meths.win_get_width(0)) + end) end) describe('sysinit', function() @@ -518,32 +685,6 @@ describe('sysinit', function() eval('printf("loaded %d xdg %d vim %d", g:loaded, get(g:, "xdg", 0), get(g:, "vim", 0))')) end) - it('fixed hang issue with -D (#12647)', function() - local screen - screen = Screen.new(60, 7) - screen:attach() - command([[let g:id = termopen('"]]..nvim_prog.. - [[" -u NONE -i NONE --cmd "set noruler" -D')]]) - screen:expect([[ - ^ | - Entering Debug mode. Type "cont" to continue. | - nvim_exec() | - cmd: aunmenu * | - > | - <" -u NONE -i NONE --cmd "set noruler" -D 1,1 All| - | - ]]) - command([[call chansend(g:id, "cont\n")]]) - screen:expect([[ - ^ | - ~ | - ~ | - [No Name] | - | - <" -u NONE -i NONE --cmd "set noruler" -D 1,0-1 All| - | - ]]) - end) end) describe('user config init', function() @@ -576,7 +717,81 @@ describe('user config init', function() eq(funcs.fnamemodify(init_lua_path, ':p'), eval('$MYVIMRC')) end) - describe 'with explicitly provided config'(function() + describe('with existing .exrc in cwd', function() + local exrc_path = '.exrc' + local xstate = 'Xstate' + + local function setup_exrc_file(filename) + exrc_path = filename + + if string.find(exrc_path, "%.lua$") then + write_file(exrc_path, string.format([[ + vim.g.exrc_file = "%s" + ]], exrc_path)) + else + write_file(exrc_path, string.format([[ + let g:exrc_file = "%s" + ]], exrc_path)) + end + end + + before_each(function() + write_file(init_lua_path, [[ + vim.o.exrc = true + vim.g.exrc_file = '---' + ]]) + mkdir_p(xstate .. pathsep .. (is_os('win') and 'nvim-data' or 'nvim')) + end) + + after_each(function() + os.remove(exrc_path) + rmdir(xstate) + end) + + for _, filename in ipairs({ '.exrc', '.nvimrc', '.nvim.lua' }) do + it('loads ' .. filename, function () + setup_exrc_file(filename) + + clear{ args_rm = {'-u'}, env={ XDG_CONFIG_HOME=xconfig, XDG_STATE_HOME=xstate } } + -- The 'exrc' file is not trusted, and the prompt is skipped because there is no UI. + eq('---', eval('g:exrc_file')) + + local screen = Screen.new(50, 8) + screen:attach() + funcs.termopen({nvim_prog}) + screen:expect({ any = pesc('[i]gnore, (v)iew, (d)eny, (a)llow:') }) + -- `i` to enter Terminal mode, `a` to allow + feed('ia') + screen:expect([[ + | + ~ | + ~ | + ~ | + ~ | + [No Name] 0,0-1 All| + | + -- TERMINAL -- | + ]]) + feed(':echo g:exrc_file<CR>') + screen:expect(string.format([[ + | + ~ | + ~ | + ~ | + ~ | + [No Name] 0,0-1 All| + %s%s| + -- TERMINAL -- | + ]], filename, string.rep(' ', 50 - #filename))) + + clear{ args_rm = {'-u'}, env={ XDG_CONFIG_HOME=xconfig, XDG_STATE_HOME=xstate } } + -- The 'exrc' file is now trusted. + eq(filename, eval('g:exrc_file')) + end) + end + end) + + describe('with explicitly provided config', function() local custom_lua_path = table.concat({xhome, 'custom.lua'}, pathsep) before_each(function() write_file(custom_lua_path, [[ @@ -591,7 +806,7 @@ describe('user config init', function() end) end) - describe 'VIMRC also exists'(function() + describe('VIMRC also exists', function() before_each(function() write_file(table.concat({xconfig, 'nvim', 'init.vim'}, pathsep), [[ let g:vim_rc = 1 @@ -637,7 +852,7 @@ describe('runtime:', function() end) it('loads plugin/*.lua from start packages', function() - local plugin_path = table.concat({xconfig, 'nvim', 'pack', 'catagory', + local plugin_path = table.concat({xconfig, 'nvim', 'pack', 'category', 'start', 'test_plugin'}, pathsep) local plugin_folder_path = table.concat({plugin_path, 'plugin'}, pathsep) local plugin_file_path = table.concat({plugin_folder_path, 'plugin.lua'}, @@ -665,7 +880,7 @@ describe('runtime:', function() end) it('loads plugin/*.lua from site packages', function() - local nvimdata = iswin() and "nvim-data" or "nvim" + local nvimdata = is_os('win') and "nvim-data" or "nvim" local plugin_path = table.concat({xdata, nvimdata, 'site', 'pack', 'xa', 'start', 'yb'}, pathsep) local plugin_folder_path = table.concat({plugin_path, 'plugin'}, pathsep) local plugin_after_path = table.concat({plugin_path, 'after', 'plugin'}, pathsep) diff --git a/test/functional/editor/completion_spec.lua b/test/functional/editor/completion_spec.lua index 6cdac3c079..22857efe5b 100644 --- a/test/functional/editor/completion_spec.lua +++ b/test/functional/editor/completion_spec.lua @@ -935,6 +935,9 @@ describe('completion', function() eq({'api'}, funcs.getcompletion('vim.ap', 'lua')) eq({'tbl_filter'}, funcs.getcompletion('vim.tbl_fil', 'lua')) eq({'vim'}, funcs.getcompletion('print(vi', 'lua')) + -- fuzzy completion is not supported, so the result should be the same + command('set wildoptions+=fuzzy') + eq({'vim'}, funcs.getcompletion('vi', 'lua')) end) end) @@ -1029,7 +1032,8 @@ describe('completion', function() ]]) end) - it('TextChangedP autocommand', function() + -- oldtest: Test_ChangedP() + it('TextChangedI and TextChangedP autocommands', function() curbufmeths.set_lines(0, 1, false, { 'foo', 'bar', 'foobar'}) source([[ set complete=. completeopt=menuone @@ -1128,6 +1132,49 @@ describe('completion', function() call cursor(4, 1) ]]) + -- v:event.size should be set with ext_popupmenu #20646 + screen:set_option('ext_popupmenu', true) + feed('Sf<C-N>') + screen:expect({grid = [[ + foo | + bar | + foobar | + f^ | + {0:~ }| + {0:~ }| + {0:~ }| + {3:-- Keyword completion (^N^P) }{5:Back at original} | + ]], popupmenu = { + anchor = { 1, 3, 0 }, + items = { { "foo", "", "", "" }, { "foobar", "", "", "" } }, + pos = -1 + }}) + eq({completed_item = {}, width = 0, + height = 2, size = 2, + col = 0, row = 4, scrollbar = false}, + eval('g:event')) + feed('oob') + screen:expect({grid = [[ + foo | + bar | + foobar | + foob^ | + {0:~ }| + {0:~ }| + {0:~ }| + {3:-- Keyword completion (^N^P) }{5:Back at original} | + ]], popupmenu = { + anchor = { 1, 3, 0 }, + items = { { "foobar", "", "", "" } }, + pos = -1 + }}) + eq({completed_item = {}, width = 0, + height = 1, size = 1, + col = 0, row = 4, scrollbar = false}, + eval('g:event')) + feed('<Esc>') + screen:set_option('ext_popupmenu', false) + feed('Sf<C-N>') screen:expect([[ foo | diff --git a/test/functional/editor/mark_spec.lua b/test/functional/editor/mark_spec.lua index 2440867c6e..b3b190ef79 100644 --- a/test/functional/editor/mark_spec.lua +++ b/test/functional/editor/mark_spec.lua @@ -40,59 +40,59 @@ describe('named marks', function() it("errors when set out of range with :mark", function() command("edit " .. file1) local err = pcall_err(helpers.exec_capture, "1000mark x") - eq("Vim(mark):E16: Invalid range: 1000mark x", err) + eq("nvim_exec(): Vim(mark):E16: Invalid range: 1000mark x", err) end) it("errors when set out of range with :k", function() command("edit " .. file1) local err = pcall_err(helpers.exec_capture, "1000kx") - eq("Vim(k):E16: Invalid range: 1000kx", err) + eq("nvim_exec(): Vim(k):E16: Invalid range: 1000kx", err) end) it("errors on unknown mark name with :mark", function() command("edit " .. file1) local err = pcall_err(helpers.exec_capture, "mark #") - eq("Vim(mark):E191: Argument must be a letter or forward/backward quote", err) + eq("nvim_exec(): Vim(mark):E191: Argument must be a letter or forward/backward quote", err) end) it("errors on unknown mark name with '", function() command("edit " .. file1) local err = pcall_err(helpers.exec_capture, "normal! '#") - eq("Vim(normal):E78: Unknown mark", err) + eq("nvim_exec(): Vim(normal):E78: Unknown mark", err) end) it("errors on unknown mark name with `", function() command("edit " .. file1) local err = pcall_err(helpers.exec_capture, "normal! `#") - eq("Vim(normal):E78: Unknown mark", err) + eq("nvim_exec(): Vim(normal):E78: Unknown mark", err) end) it("errors when moving to a mark that is not set with '", function() command("edit " .. file1) local err = pcall_err(helpers.exec_capture, "normal! 'z") - eq("Vim(normal):E20: Mark not set", err) + eq("nvim_exec(): Vim(normal):E20: Mark not set", err) err = pcall_err(helpers.exec_capture, "normal! '.") - eq("Vim(normal):E20: Mark not set", err) + eq("nvim_exec(): Vim(normal):E20: Mark not set", err) end) it("errors when moving to a mark that is not set with `", function() command("edit " .. file1) local err = pcall_err(helpers.exec_capture, "normal! `z") - eq("Vim(normal):E20: Mark not set", err) + eq("nvim_exec(): Vim(normal):E20: Mark not set", err) err = pcall_err(helpers.exec_capture, "normal! `>") - eq("Vim(normal):E20: Mark not set", err) + eq("nvim_exec(): Vim(normal):E20: Mark not set", err) end) it("errors when moving to a global mark that is not set with '", function() command("edit " .. file1) local err = pcall_err(helpers.exec_capture, "normal! 'Z") - eq("Vim(normal):E20: Mark not set", err) + eq("nvim_exec(): Vim(normal):E20: Mark not set", err) end) it("errors when moving to a global mark that is not set with `", function() command("edit " .. file1) local err = pcall_err(helpers.exec_capture, "normal! `Z") - eq("Vim(normal):E20: Mark not set", err) + eq("nvim_exec(): Vim(normal):E20: Mark not set", err) end) it("can move to them using '", function() @@ -153,7 +153,7 @@ describe('named marks', function() command("next") command("bw! " .. file1 ) local err = pcall_err(helpers.exec_capture, "normal! 'A") - eq("Vim(normal):E92: Buffer 1 not found", err) + eq("nvim_exec(): Vim(normal):E92: Buffer 1 not found", err) os.remove(file1) end) @@ -330,7 +330,7 @@ describe('named marks view', function() os.remove(file2) end) - it('is restored', function() + it('is restored in normal mode but not op-pending mode', function() local screen = Screen.new(5, 8) screen:attach() command("edit " .. file1) @@ -358,6 +358,18 @@ describe('named marks view', function() 8 line | | ]]) + -- not in op-pending mode #20886 + feed("ggj=`a") + screen:expect([[ + 1 line | + ^2 line | + 3 line | + 4 line | + 5 line | + 6 line | + 7 line | + | + ]]) end) it('is restored across files', function() diff --git a/test/functional/editor/tabpage_spec.lua b/test/functional/editor/tabpage_spec.lua index f8ca6986bd..a7f629a76b 100644 --- a/test/functional/editor/tabpage_spec.lua +++ b/test/functional/editor/tabpage_spec.lua @@ -144,4 +144,10 @@ describe('tabpage', function() command(' silent :keepalt :: ::: silent! -2 tabmove') eq(1, funcs.nvim_tabpage_get_number(0)) end) + + it(':tabs does not overflow IObuff with long path with comma #20850', function() + meths.buf_set_name(0, ('x'):rep(1024) .. ',' .. ('x'):rep(1024)) + command('tabs') + assert_alive() + end) end) diff --git a/test/functional/editor/undo_spec.lua b/test/functional/editor/undo_spec.lua index a041428cdc..d66ab352ef 100644 --- a/test/functional/editor/undo_spec.lua +++ b/test/functional/editor/undo_spec.lua @@ -9,6 +9,8 @@ local feed = helpers.feed local feed_command = helpers.feed_command local insert = helpers.insert local funcs = helpers.funcs +local exec = helpers.exec +local exec_lua = helpers.exec_lua local function lastmessage() local messages = funcs.split(funcs.execute('messages'), '\n') @@ -67,6 +69,79 @@ describe('u CTRL-R g- g+', function() undo_and_redo(4, 'u', '<C-r>', '1') undo_and_redo(4, 'g-', 'g+', '1') end) + + describe('undo works correctly when writing in Insert mode', function() + before_each(function() + exec([[ + edit Xtestfile.txt + set undolevels=100 undofile + write + ]]) + end) + + after_each(function() + command('bwipe!') + os.remove('Xtestfile.txt') + os.remove('Xtestfile.txt.un~') + end) + + -- oldtest: Test_undo_after_write() + it('using <Cmd> mapping', function() + command('imap . <Cmd>write<CR>') + feed('Otest.<CR>boo!!!<Esc>') + expect([[ + test + boo!!! + ]]) + + feed('u') + expect([[ + test + ]]) + + feed('u') + expect('') + end) + + it('using Lua mapping', function() + exec_lua([[ + vim.api.nvim_set_keymap('i', '.', '', {callback = function() + vim.cmd('write') + end}) + ]]) + feed('Otest.<CR>boo!!!<Esc>') + expect([[ + test + boo!!! + ]]) + + feed('u') + expect([[ + test + ]]) + + feed('u') + expect('') + end) + + it('using RPC call', function() + feed('Otest') + command('write') + feed('<CR>boo!!!<Esc>') + expect([[ + test + boo!!! + ]]) + + feed('u') + expect([[ + test + ]]) + + feed('u') + expect('') + end) + end) end) describe(':undo! command', function() diff --git a/test/functional/ex_cmds/cd_spec.lua b/test/functional/ex_cmds/cd_spec.lua index 42a811f5da..5ed71651c7 100644 --- a/test/functional/ex_cmds/cd_spec.lua +++ b/test/functional/ex_cmds/cd_spec.lua @@ -9,6 +9,8 @@ local clear = helpers.clear local command = helpers.command local exc_exec = helpers.exc_exec local pathsep = helpers.get_pathsep() +local skip = helpers.skip +local is_os = helpers.is_os -- These directories will be created for testing local directories = { @@ -279,9 +281,7 @@ describe("getcwd()", function () end) it("returns empty string if working directory does not exist", function() - if helpers.iswin() then - return - end + skip(is_os('win')) command("cd "..directories.global) command("call delete('../"..directories.global.."', 'd')") eq("", helpers.eval("getcwd()")) diff --git a/test/functional/ex_cmds/map_spec.lua b/test/functional/ex_cmds/map_spec.lua index c6bdd017bd..ec912053b2 100644 --- a/test/functional/ex_cmds/map_spec.lua +++ b/test/functional/ex_cmds/map_spec.lua @@ -3,6 +3,7 @@ local Screen = require('test.functional.ui.screen') local eq = helpers.eq local exec = helpers.exec +local exec_capture = helpers.exec_capture local feed = helpers.feed local meths = helpers.meths local clear = helpers.clear @@ -30,12 +31,12 @@ describe(':*map', function() expect('-foo-') end) - it('shows <nop> as mapping rhs', function() + it('shows <Nop> as mapping rhs', function() command('nmap asdf <Nop>') eq([[ n asdf <Nop>]], - helpers.exec_capture('nmap asdf')) + exec_capture('nmap asdf')) end) it('mappings with description can be filtered', function() @@ -48,7 +49,7 @@ n asdf3 qwert do the other thing n asdf1 qwert do the one thing]], - helpers.exec_capture('filter the nmap')) + exec_capture('filter the nmap')) end) it('<Plug> mappings ignore nore', function() @@ -84,6 +85,12 @@ n asdf1 qwert eq(2, meths.eval('x')) eq('Some te', eval("getline('.')")) end) + + it(':unmap with rhs works when lhs is in another bucket #21530', function() + command('map F <Plug>Foo') + command('unmap <Plug>Foo') + eq('\nNo mapping found', exec_capture('map F')) + end) end) describe('Screen', function() @@ -161,6 +168,7 @@ describe('Screen', function() ]]) end) + -- oldtest: Test_expr_map_restore_cursor() it('cursor is restored after :map <expr> which redraws statusline vim-patch:8.1.2336', function() exec([[ call setline(1, ['one', 'two', 'three']) @@ -246,6 +254,7 @@ describe('Screen', function() ]]) end) + -- oldtest: Test_map_listing() it('listing mappings clears command line vim-patch:8.2.4401', function() screen:try_resize(40, 5) command('nmap a b') diff --git a/test/functional/ex_cmds/mksession_spec.lua b/test/functional/ex_cmds/mksession_spec.lua index ee8da2932d..0a0c7ca410 100644 --- a/test/functional/ex_cmds/mksession_spec.lua +++ b/test/functional/ex_cmds/mksession_spec.lua @@ -5,7 +5,6 @@ local Screen = require('test.functional.ui.screen') local clear = helpers.clear local command = helpers.command local get_pathsep = helpers.get_pathsep -local iswin = helpers.iswin local eq = helpers.eq local neq = helpers.neq local funcs = helpers.funcs @@ -14,6 +13,8 @@ local pesc = helpers.pesc local rmdir = helpers.rmdir local sleep = helpers.sleep local meths = helpers.meths +local skip = helpers.skip +local is_os = helpers.is_os local file_prefix = 'Xtest-functional-ex_cmds-mksession_spec' @@ -177,7 +178,7 @@ describe(':mksession', function() command('cd ' .. cwd_dir) command('mksession ' .. session_path) command('%bwipeout!') - if iswin() then + if is_os('win') then sleep(100) -- Make sure all child processes have exited. end @@ -188,16 +189,13 @@ describe(':mksession', function() local expected_cwd = cwd_dir .. '/' .. tab_dir matches('^term://' .. pesc(expected_cwd) .. '//%d+:', funcs.expand('%')) command('%bwipeout!') - if iswin() then + if is_os('win') then sleep(100) -- Make sure all child processes have exited. end end) it('restores CWD for :terminal buffer at root directory #16988', function() - if iswin() then - pending('N/A for Windows') - return - end + skip(is_os('win'), 'N/A for Windows') local screen local cwd_dir = funcs.fnamemodify('.', ':p:~'):gsub([[[\/]*$]], '') diff --git a/test/functional/ex_cmds/quickfix_commands_spec.lua b/test/functional/ex_cmds/quickfix_commands_spec.lua index 94b7fa1a84..4d9d8eeb90 100644 --- a/test/functional/ex_cmds/quickfix_commands_spec.lua +++ b/test/functional/ex_cmds/quickfix_commands_spec.lua @@ -1,5 +1,7 @@ local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') +local feed = helpers.feed local eq = helpers.eq local clear = helpers.clear local funcs = helpers.funcs @@ -123,3 +125,22 @@ describe('quickfix', function() os.remove(file) end) end) + +it(':vimgrep can specify Unicode pattern without delimiters', function() + eq('Vim(vimgrep):E480: No match: →', exc_exec('vimgrep → test/functional/fixtures/tty-test.c')) + local screen = Screen.new(40, 6) + screen:set_default_attr_ids({ + [0] = {bold = true, foreground = Screen.colors.Blue}, -- NonText + [1] = {reverse = true}, -- IncSearch + }) + screen:attach() + feed('i→<Esc>:vimgrep →') + screen:expect([[ + {1:→} | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + :vimgrep →^ | + ]]) +end) diff --git a/test/functional/ex_cmds/source_spec.lua b/test/functional/ex_cmds/source_spec.lua index 10ebefd8cd..64c3464be7 100644 --- a/test/functional/ex_cmds/source_spec.lua +++ b/test/functional/ex_cmds/source_spec.lua @@ -14,9 +14,9 @@ local eval = helpers.eval local exec_capture = helpers.exec_capture local neq = helpers.neq local matches = helpers.matches -local iswin = helpers.iswin local mkdir = helpers.mkdir local rmdir = helpers.rmdir +local is_os = helpers.is_os describe(':source', function() before_each(function() @@ -44,7 +44,7 @@ describe(':source', function() end) it("changing 'shellslash' changes the result of expand()", function() - if not iswin() then + if not is_os('win') then pending("'shellslash' only works on Windows") return end diff --git a/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua b/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua index 69404039ff..8eed00c973 100644 --- a/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua +++ b/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua @@ -1,12 +1,14 @@ local Screen = require('test.functional.ui.screen') local helpers = require('test.functional.helpers')(after_each) local lfs = require('lfs') +local luv = require('luv') local eq, eval, expect, exec = helpers.eq, helpers.eval, helpers.expect, helpers.exec local assert_alive = helpers.assert_alive local clear = helpers.clear local command = helpers.command local feed = helpers.feed +local funcs = helpers.funcs local nvim_prog = helpers.nvim_prog local ok = helpers.ok local rmdir = helpers.rmdir @@ -100,7 +102,7 @@ describe('swapfile detection', function() -- attempt to create a swapfile in different directory. local init = [[ set directory^=]]..swapdir:gsub([[\]], [[\\]])..[[// - set swapfile fileformat=unix undolevels=-1 hidden + set swapfile fileformat=unix nomodified undolevels=-1 nohidden ]] before_each(function() nvim0 = spawn(new_argv()) @@ -263,4 +265,79 @@ describe('swapfile detection', function() ]]) nvim2:close() end) + + -- oldtest: Test_nocatch_process_still_running() + it('allows deleting swapfile created before boot vim-patch:8.2.2586', function() + local screen = Screen.new(75, 30) + screen:set_default_attr_ids({ + [0] = {bold = true, foreground = Screen.colors.Blue}, -- NonText + [1] = {bold = true, foreground = Screen.colors.SeaGreen}, -- MoreMsg + [2] = {background = Screen.colors.Red, foreground = Screen.colors.White}, -- ErrorMsg + }) + screen:attach() + + exec(init) + command('set nohidden') + + exec([=[ + " Make a copy of the current swap file to "Xswap". + " Return the name of the swap file. + func CopySwapfile() + preserve + " get the name of the swap file + let swname = split(execute("swapname"))[0] + let swname = substitute(swname, '[[:blank:][:cntrl:]]*\(.\{-}\)[[:blank:][:cntrl:]]*$', '\1', '') + " make a copy of the swap file in Xswap + set binary + exe 'sp ' . fnameescape(swname) + w! Xswap + set nobinary + return swname + endfunc + ]=]) + + -- Edit a file and grab its swapfile. + exec([[ + edit Xswaptest + call setline(1, ['a', 'b', 'c']) + ]]) + local swname = funcs.CopySwapfile() + + -- Forget we edited this file + exec([[ + new + only! + bwipe! Xswaptest + ]]) + + os.rename('Xswap', swname) + + feed(':edit Xswaptest<CR>') + screen:expect({any = table.concat({ + pesc('{2:E325: ATTENTION}'), + 'file name: .*Xswaptest', + 'process ID: %d* %(STILL RUNNING%)', + pesc('{1:[O]pen Read-Only, (E)dit anyway, (R)ecover, (Q)uit, (A)bort: }^'), + }, '.*')}) + + feed('e') + + -- Forget we edited this file + exec([[ + new + only! + bwipe! Xswaptest + ]]) + + -- pretend that the swapfile was created before boot + lfs.touch(swname, os.time() - luv.uptime() - 10) + + feed(':edit Xswaptest<CR>') + screen:expect({any = table.concat({ + pesc('{2:E325: ATTENTION}'), + pesc('{1:[O]pen Read-Only, (E)dit anyway, (R)ecover, (D)elete it, (Q)uit, (A)bort: }^'), + }, '.*')}) + + feed('e') + end) end) diff --git a/test/functional/ex_cmds/trust_spec.lua b/test/functional/ex_cmds/trust_spec.lua new file mode 100644 index 0000000000..10ee02a790 --- /dev/null +++ b/test/functional/ex_cmds/trust_spec.lua @@ -0,0 +1,176 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') + +local eq = helpers.eq +local clear = helpers.clear +local command = helpers.command +local pathsep = helpers.get_pathsep() +local is_os = helpers.is_os +local funcs = helpers.funcs + +describe(':trust', function() + local xstate = 'Xstate' + + setup(function() + helpers.mkdir_p(xstate .. pathsep .. (is_os('win') and 'nvim-data' or 'nvim')) + end) + + teardown(function() + helpers.rmdir(xstate) + end) + + before_each(function() + helpers.write_file('test_file', 'test') + clear{env={XDG_STATE_HOME=xstate}} + end) + + after_each(function() + os.remove('test_file') + end) + + it('trust then deny then remove a file using current buffer', function() + local screen = Screen.new(80, 8) + screen:attach() + screen:set_default_attr_ids({ + [1] = {bold = true, foreground = Screen.colors.Blue1}, + }) + + local cwd = funcs.getcwd() + local hash = funcs.sha256(helpers.read_file('test_file')) + + command('edit test_file') + command('trust') + screen:expect([[ + ^test | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + "]] .. cwd .. pathsep .. [[test_file" trusted.{MATCH:%s+}| + ]]) + local trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq(string.format('%s %s', hash, cwd .. pathsep .. 'test_file'), vim.trim(trust)) + + command('trust ++deny') + screen:expect([[ + ^test | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + "]] .. cwd .. pathsep .. [[test_file" denied.{MATCH:%s+}| + ]]) + trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq(string.format('! %s', cwd .. pathsep .. 'test_file'), vim.trim(trust)) + + command('trust ++remove') + screen:expect([[ + ^test | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + "]] .. cwd .. pathsep .. [[test_file" removed.{MATCH:%s+}| + ]]) + trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq(string.format(''), vim.trim(trust)) + end) + + it('deny then trust then remove a file using current buffer', function() + local screen = Screen.new(80, 8) + screen:attach() + screen:set_default_attr_ids({ + [1] = {bold = true, foreground = Screen.colors.Blue1}, + }) + + local cwd = funcs.getcwd() + local hash = funcs.sha256(helpers.read_file('test_file')) + + command('edit test_file') + command('trust ++deny') + screen:expect([[ + ^test | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + "]] .. cwd .. pathsep .. [[test_file" denied.{MATCH:%s+}| + ]]) + local trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq(string.format('! %s', cwd .. pathsep .. 'test_file'), vim.trim(trust)) + + command('trust') + screen:expect([[ + ^test | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + "]] .. cwd .. pathsep .. [[test_file" trusted.{MATCH:%s+}| + ]]) + trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq(string.format('%s %s', hash, cwd .. pathsep .. 'test_file'), vim.trim(trust)) + + command('trust ++remove') + screen:expect([[ + ^test | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + "]] .. cwd .. pathsep .. [[test_file" removed.{MATCH:%s+}| + ]]) + trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq(string.format(''), vim.trim(trust)) + end) + + it('deny then remove a file using file path', function() + local screen = Screen.new(80, 8) + screen:attach() + screen:set_default_attr_ids({ + [1] = {bold = true, foreground = Screen.colors.Blue1}, + }) + + local cwd = funcs.getcwd() + + command('trust ++deny test_file') + screen:expect([[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + "]] .. cwd .. pathsep .. [[test_file" denied.{MATCH:%s+}| + ]]) + local trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq(string.format('! %s', cwd .. pathsep .. 'test_file'), vim.trim(trust)) + + command('trust ++remove test_file') + screen:expect([[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + "]] .. cwd .. pathsep .. [[test_file" removed.{MATCH:%s+}| + ]]) + trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq(string.format(''), vim.trim(trust)) + end) +end) diff --git a/test/functional/ex_cmds/verbose_spec.lua b/test/functional/ex_cmds/verbose_spec.lua index e6f67ef18e..000e746f1c 100644 --- a/test/functional/ex_cmds/verbose_spec.lua +++ b/test/functional/ex_cmds/verbose_spec.lua @@ -77,7 +77,7 @@ nohlsearch script_location), result) end) - it('"Last set" for keymap set by Lua', function() + it('"Last set" for mapping set by Lua', function() local result = exec_capture(':verbose map <leader>key1') eq(string.format([[ @@ -86,7 +86,7 @@ n \key1 * :echo "test"<CR> script_location), result) end) - it('"Last set" for keymap set by vim.keymap', function() + it('"Last set" for mapping set by vim.keymap', function() local result = exec_capture(':verbose map <leader>key2') eq(string.format([[ diff --git a/test/functional/ex_cmds/write_spec.lua b/test/functional/ex_cmds/write_spec.lua index 14035a4341..1ccd27875e 100644 --- a/test/functional/ex_cmds/write_spec.lua +++ b/test/functional/ex_cmds/write_spec.lua @@ -8,8 +8,9 @@ local command = helpers.command local feed_command = helpers.feed_command local funcs = helpers.funcs local meths = helpers.meths -local iswin = helpers.iswin -local uname = helpers.uname +local skip = helpers.skip +local is_os = helpers.is_os +local is_ci = helpers.is_ci local fname = 'Xtest-functional-ex_cmds-write' local fname_bak = fname .. '~' @@ -20,6 +21,9 @@ describe(':write', function() os.remove('test_bkc_file.txt') os.remove('test_bkc_link.txt') os.remove('test_fifo') + os.remove('test/write/p_opt.txt') + os.remove('test/write') + os.remove('test') os.remove(fname) os.remove(fname_bak) os.remove(fname_broken) @@ -35,7 +39,7 @@ describe(':write', function() it('&backupcopy=auto preserves symlinks', function() command('set backupcopy=auto') write_file('test_bkc_file.txt', 'content0') - if iswin() then + if is_os('win') then command("silent !mklink test_bkc_link.txt test_bkc_file.txt") else command("silent !ln -s test_bkc_file.txt test_bkc_link.txt") @@ -53,12 +57,10 @@ describe(':write', function() end) it('&backupcopy=no replaces symlink with new file', function() - if uname() == 'freebsd' then - pending('Failing FreeBSD test') - end + skip(is_ci('cirrus')) command('set backupcopy=no') write_file('test_bkc_file.txt', 'content0') - if iswin() then + if is_os('win') then command("silent !mklink test_bkc_link.txt test_bkc_file.txt") else command("silent !ln -s test_bkc_file.txt test_bkc_link.txt") @@ -77,7 +79,7 @@ describe(':write', function() it("appends FIFO file", function() -- mkfifo creates read-only .lnk files on Windows - if iswin() or eval("executable('mkfifo')") == 0 then + if is_os('win') or eval("executable('mkfifo')") == 0 then pending('missing "mkfifo" command') end @@ -94,14 +96,36 @@ describe(':write', function() fifo:close() end) - it('errors out correctly', function() - if uname() == 'freebsd' then - pending('Failing FreeBSD test') + it("++p creates missing parent directories", function() + eq(0, eval("filereadable('p_opt.txt')")) + command("write ++p p_opt.txt") + eq(1, eval("filereadable('p_opt.txt')")) + os.remove("p_opt.txt") + + eq(0, eval("filereadable('p_opt.txt')")) + command("write ++p ./p_opt.txt") + eq(1, eval("filereadable('p_opt.txt')")) + os.remove("p_opt.txt") + + eq(0, eval("filereadable('test/write/p_opt.txt')")) + command("write ++p test/write/p_opt.txt") + eq(1, eval("filereadable('test/write/p_opt.txt')")) + + eq(('Vim(write):E32: No file name'), pcall_err(command, 'write ++p test_write/')) + if not is_os('win') then + eq(('Vim(write):E17: "'..funcs.fnamemodify('.', ':p:h')..'" is a directory'), + pcall_err(command, 'write ++p .')) + eq(('Vim(write):E17: "'..funcs.fnamemodify('.', ':p:h')..'" is a directory'), + pcall_err(command, 'write ++p ./')) end + end) + + it('errors out correctly', function() + skip(is_ci('cirrus')) command('let $HOME=""') eq(funcs.fnamemodify('.', ':p:h'), funcs.fnamemodify('.', ':p:h:~')) -- Message from check_overwrite - if not iswin() then + if not is_os('win') then eq(('Vim(write):E17: "'..funcs.fnamemodify('.', ':p:h')..'" is a directory'), pcall_err(command, 'write .')) end @@ -120,7 +144,7 @@ describe(':write', function() funcs.setfperm(fname, 'r--------') eq('Vim(write):E505: "Xtest-functional-ex_cmds-write" is read-only (add ! to override)', pcall_err(command, 'write')) - if iswin() then + if is_os('win') then eq(0, os.execute('del /q/f ' .. fname)) eq(0, os.execute('rd /q/s ' .. fname_bak)) else @@ -128,8 +152,7 @@ describe(':write', function() eq(true, os.remove(fname_bak)) end write_file(fname_bak, 'TTYX') - -- FIXME: exc_exec('write!') outputs 0 in Windows - if iswin() then return end + skip(is_os('win'), [[FIXME: exc_exec('write!') outputs 0 in Windows]]) lfs.link(fname_bak .. ('/xxxxx'):rep(20), fname, true) eq('Vim(write):E166: Can\'t open linked file for writing', pcall_err(command, 'write!')) diff --git a/test/functional/ex_cmds/wviminfo_spec.lua b/test/functional/ex_cmds/wviminfo_spec.lua index 7c00daf1d7..861a977ea6 100644 --- a/test/functional/ex_cmds/wviminfo_spec.lua +++ b/test/functional/ex_cmds/wviminfo_spec.lua @@ -3,14 +3,14 @@ local lfs = require('lfs') local clear = helpers.clear local command, eq, neq, write_file = helpers.command, helpers.eq, helpers.neq, helpers.write_file -local iswin = helpers.iswin local read_file = helpers.read_file +local is_os = helpers.is_os describe(':wshada', function() local shada_file = 'wshada_test' before_each(function() - clear{args={'-i', iswin() and 'nul' or '/dev/null', + clear{args={'-i', is_os('win') and 'nul' or '/dev/null', -- Need 'swapfile' for these tests. '--cmd', 'set swapfile undodir=. directory=. viewdir=. backupdir=. belloff= noshowcmd noruler'}, args_rm={'-n', '-i', '--cmd'}} diff --git a/test/functional/fixtures/CMakeLists.txt b/test/functional/fixtures/CMakeLists.txt index a5410c2f8c..6e64b1e4dc 100644 --- a/test/functional/fixtures/CMakeLists.txt +++ b/test/functional/fixtures/CMakeLists.txt @@ -1,14 +1,23 @@ -add_executable(tty-test EXCLUDE_FROM_ALL tty-test.c) -target_link_libraries(tty-test ${LIBUV_LIBRARIES}) +add_library(test_lib INTERFACE) +if(MINGW) + target_link_libraries(test_lib INTERFACE -municode) +endif() +if(WIN32) + target_compile_definitions(test_lib INTERFACE MSWIN) +endif() +target_link_libraries(test_lib INTERFACE nvim) +add_executable(tty-test EXCLUDE_FROM_ALL tty-test.c) add_executable(shell-test EXCLUDE_FROM_ALL shell-test.c) # Fake pwsh (powershell) for testing make_filter_cmd(). #16271 add_executable(pwsh-test EXCLUDE_FROM_ALL shell-test.c) add_executable(printargs-test EXCLUDE_FROM_ALL printargs-test.c) add_executable(printenv-test EXCLUDE_FROM_ALL printenv-test.c) -if(MINGW) - set_target_properties(printenv-test PROPERTIES LINK_FLAGS -municode) -endif() - add_executable(streams-test EXCLUDE_FROM_ALL streams-test.c) -target_link_libraries(streams-test ${LIBUV_LIBRARIES}) + +target_link_libraries(tty-test PRIVATE test_lib) +target_link_libraries(shell-test PRIVATE test_lib) +target_link_libraries(pwsh-test PRIVATE test_lib) +target_link_libraries(printargs-test PRIVATE test_lib) +target_link_libraries(printenv-test PRIVATE test_lib) +target_link_libraries(streams-test PRIVATE test_lib) diff --git a/test/functional/fixtures/startup-fail.lua b/test/functional/fixtures/startup-fail.lua new file mode 100644 index 0000000000..adcfe2a201 --- /dev/null +++ b/test/functional/fixtures/startup-fail.lua @@ -0,0 +1,7 @@ +-- Test "nvim -l foo.lua …" with a Lua error. + +local function main() + error('my pearls!!') +end + +main() diff --git a/test/functional/fixtures/startup.lua b/test/functional/fixtures/startup.lua new file mode 100644 index 0000000000..d0e60309bd --- /dev/null +++ b/test/functional/fixtures/startup.lua @@ -0,0 +1,35 @@ +-- Test "nvim -l foo.lua …" + +local function printbufs() + local bufs = '' + for _, v in ipairs(vim.api.nvim_list_bufs()) do + local b = vim.fn.bufname(v) + if b:len() > 0 then + bufs = ('%s %s'):format(bufs, b) + end + end + print(('bufs:%s'):format(bufs)) +end + +local function parseargs(args) + local exitcode = nil + for i = 1, #args do + if args[i] == '--exitcode' then + exitcode = tonumber(args[i + 1]) + end + end + return exitcode +end + +local function main() + printbufs() + print('nvim args:', #vim.v.argv) + print('lua args:', vim.inspect(_G.arg)) + + local exitcode = parseargs(_G.arg) + if type(exitcode) == 'number' then + os.exit(exitcode) + end +end + +main() diff --git a/test/functional/helpers.lua b/test/functional/helpers.lua index 723d2ccfa4..6400db9f87 100644 --- a/test/functional/helpers.lua +++ b/test/functional/helpers.lua @@ -1,5 +1,4 @@ require('coxpcall') -local busted = require('busted') local luv = require('luv') local lfs = require('lfs') local mpack = require('mpack') @@ -54,7 +53,6 @@ if module.nvim_dir == module.nvim_prog then module.nvim_dir = "." end -local iswin = global_helpers.iswin local prepend_argv if os.getenv('VALGRIND') then @@ -112,6 +110,10 @@ function module.request(method, ...) return rv end +function module.request_lua(method, ...) + return module.exec_lua([[return vim.api[...](select(2, ...))]], method, ...) +end + function module.next_msg(timeout) return session:next_message(timeout and timeout or 10000) end @@ -240,7 +242,7 @@ function module.run_session(lsession, request_cb, notification_cb, setup_cb, tim end loop_running = true - session:run(on_request, on_notification, on_setup, timeout) + lsession:run(on_request, on_notification, on_setup, timeout) loop_running = false if last_error then local err = last_error @@ -248,7 +250,7 @@ function module.run_session(lsession, request_cb, notification_cb, setup_cb, tim error(err) end - return session.eof_err + return lsession.eof_err end function module.run(request_cb, notification_cb, setup_cb, timeout) @@ -299,12 +301,18 @@ function module.eval(expr) return module.request('nvim_eval', expr) end --- Executes a VimL function. +-- Executes a VimL function via RPC. -- Fails on VimL error, but does not update v:errmsg. function module.call(name, ...) return module.request('nvim_call_function', name, {...}) end +-- Executes a VimL function via Lua. +-- Fails on VimL error, but does not update v:errmsg. +function module.call_lua(name, ...) + return module.exec_lua([[return vim.call(...)]], name, ...) +end + -- Sends user input to Nvim. -- Does not fail on VimL error, but v:errmsg will be updated. local function nvim_feed(input) @@ -388,9 +396,12 @@ local function remove_args(args, args_rm) return new_args end -function module.check_close(old_session) +function module.check_close() + if not session then + return + end local start_time = luv.now() - old_session:close() + session:close() luv.update_time() -- Update cached value of luv.now() (libuv: uv_now()). local end_time = luv.now() local delta = end_time - start_time @@ -399,12 +410,13 @@ function module.check_close(old_session) "This indicates a likely problem with the test even if it passed!\n") io.stdout:flush() end + session = nil end --- @param io_extra used for stdin_fd, see :help ui-option function module.spawn(argv, merge, env, keep, io_extra) - if session and not keep then - module.check_close(session) + if not keep then + module.check_close() end local child_stream = ChildProcessStream.spawn( @@ -421,28 +433,6 @@ function module.connect(file_or_address) return Session.new(stream) end --- Calls fn() until it succeeds, up to `max` times or until `max_ms` --- milliseconds have passed. -function module.retry(max, max_ms, fn) - assert(max == nil or max > 0) - assert(max_ms == nil or max_ms > 0) - local tries = 1 - local timeout = (max_ms and max_ms or 10000) - local start_time = luv.now() - while true do - local status, result = pcall(fn) - if status then - return result - end - luv.update_time() -- Update cached value of luv.now() (libuv: uv_now()). - if (max and tries >= max) or (luv.now() - start_time > timeout) then - busted.fail(string.format("retry() attempts: %d\n%s", tries, tostring(result)), 2) - end - tries = tries + 1 - luv.sleep(20) -- Avoid hot loop... - end -end - -- Starts a new global Nvim session. -- -- Parameters are interpreted as startup args, OR a map with these keys: @@ -456,8 +446,14 @@ end -- clear('-e') -- clear{args={'-e'}, args_rm={'-i'}, env={TERM=term}} function module.clear(...) + module.set_session(module.spawn_argv(false, ...)) +end + +-- same params as clear, but does returns the session instead +-- of replacing the default session +function module.spawn_argv(keep, ...) local argv, env, io_extra = module.new_argv(...) - module.set_session(module.spawn(argv, nil, env, nil, io_extra)) + return module.spawn(argv, nil, env, keep, io_extra) end -- Builds an argument list for use in clear(). @@ -547,7 +543,7 @@ function module.source(code) end function module.has_powershell() - return module.eval('executable("'..(iswin() and 'powershell' or 'pwsh')..'")') == 1 + return module.eval('executable("'..(is_os('win') and 'powershell' or 'pwsh')..'")') == 1 end --- Sets Nvim shell to powershell. @@ -560,9 +556,9 @@ function module.set_shell_powershell(fake) if not fake then assert(found) end - local shell = found and (iswin() and 'powershell' or 'pwsh') or module.testprg('pwsh-test') + local shell = found and (is_os('win') and 'powershell' or 'pwsh') or module.testprg('pwsh-test') local set_encoding = '[Console]::InputEncoding=[Console]::OutputEncoding=[System.Text.UTF8Encoding]::new();' - local cmd = set_encoding..'Remove-Item -Force '..table.concat(iswin() + local cmd = set_encoding..'Remove-Item -Force '..table.concat(is_os('win') and {'alias:cat', 'alias:echo', 'alias:sleep', 'alias:sort'} or {'alias:echo'}, ',')..';' module.exec([[ @@ -575,8 +571,16 @@ function module.set_shell_powershell(fake) return found end -function module.nvim(method, ...) - return module.request('nvim_'..method, ...) +function module.create_callindex(func) + local table = {} + setmetatable(table, { + __index = function(tbl, arg1) + local ret = function(...) return func(arg1, ...) end + tbl[arg1] = ret + return ret + end, + }) + return table end local function ui(method, ...) @@ -587,23 +591,83 @@ function module.nvim_async(method, ...) session:notify('nvim_'..method, ...) end -function module.buffer(method, ...) - return module.request('nvim_buf_'..method, ...) -end +module.async_meths = module.create_callindex(module.nvim_async) +module.uimeths = module.create_callindex(ui) -function module.window(method, ...) - return module.request('nvim_win_'..method, ...) -end +local function create_api(request, call) + local m = {} + function m.nvim(method, ...) + return request('nvim_'..method, ...) + end + + function m.buffer(method, ...) + return request('nvim_buf_'..method, ...) + end + + function m.window(method, ...) + return request('nvim_win_'..method, ...) + end + + function m.tabpage(method, ...) + return request('nvim_tabpage_'..method, ...) + end + + function m.curbuf(method, ...) + if not method then + return m.nvim('get_current_buf') + end + return m.buffer(method, 0, ...) + end + + function m.curwin(method, ...) + if not method then + return m.nvim('get_current_win') + end + return m.window(method, 0, ...) + end + + function m.curtab(method, ...) + if not method then + return m.nvim('get_current_tabpage') + end + return m.tabpage(method, 0, ...) + end + + m.funcs = module.create_callindex(call) + m.meths = module.create_callindex(m.nvim) + m.bufmeths = module.create_callindex(m.buffer) + m.winmeths = module.create_callindex(m.window) + m.tabmeths = module.create_callindex(m.tabpage) + m.curbufmeths = module.create_callindex(m.curbuf) + m.curwinmeths = module.create_callindex(m.curwin) + m.curtabmeths = module.create_callindex(m.curtab) -function module.tabpage(method, ...) - return module.request('nvim_tabpage_'..method, ...) + return m end -function module.curbuf(method, ...) - if not method then - return module.nvim('get_current_buf') +module.rpc = { + api = create_api(module.request, module.call), +} + +module.lua = { + api = create_api(module.request_lua, module.call_lua), +} + +module.describe_lua_and_rpc = function(describe) + return function(what, tests) + local function d(flavour) + describe(string.format('%s (%s)', what, flavour), function(...) + return tests(module[flavour].api, ...) + end) + end + + d('rpc') + d('lua') end - return module.buffer(method, 0, ...) +end + +for name, fn in pairs(module.rpc.api) do + module[name] = fn end function module.poke_eventloop() @@ -622,20 +686,6 @@ function module.curbuf_contents() return table.concat(module.curbuf('get_lines', 0, -1, true), '\n') end -function module.curwin(method, ...) - if not method then - return module.nvim('get_current_win') - end - return module.window(method, 0, ...) -end - -function module.curtab(method, ...) - if not method then - return module.nvim('get_current_tabpage') - end - return module.tabpage(method, 0, ...) -end - function module.expect(contents) return eq(dedent(contents), module.curbuf_contents()) end @@ -751,25 +801,10 @@ function module.exc_exec(cmd) return ret end -function module.create_callindex(func) - local table = {} - setmetatable(table, { - __index = function(tbl, arg1) - local ret = function(...) return func(arg1, ...) end - tbl[arg1] = ret - return ret - end, - }) - return table -end - --- Helper to skip tests. Returns true in Windows systems. --- pending_fn is pending() from busted -function module.pending_win32(pending_fn) - if iswin() then - if pending_fn ~= nil then - pending_fn('FIXME: Windows', function() end) - end +function module.skip(cond, reason) + if cond then + local pending = getfenv(2).pending + pending(reason or 'FIXME') return true else return false @@ -792,17 +827,6 @@ function module.skip_fragile(pending_fn, cond) return false end -module.funcs = module.create_callindex(module.call) -module.meths = module.create_callindex(module.nvim) -module.async_meths = module.create_callindex(module.nvim_async) -module.uimeths = module.create_callindex(ui) -module.bufmeths = module.create_callindex(module.buffer) -module.winmeths = module.create_callindex(module.window) -module.tabmeths = module.create_callindex(module.tabpage) -module.curbufmeths = module.create_callindex(module.curbuf) -module.curwinmeths = module.create_callindex(module.curwin) -module.curtabmeths = module.create_callindex(module.curtab) - function module.exec(code) return module.meths.exec(code, false) end @@ -816,13 +840,13 @@ function module.exec_lua(code, ...) end function module.get_pathsep() - return iswin() and '\\' or '/' + return is_os('win') and '\\' or '/' end --- Gets the filesystem root dir, namely "/" or "C:/". function module.pathroot() local pathsep = package.config:sub(1,1) - return iswin() and (module.nvim_dir:sub(1,2)..pathsep) or '/' + return is_os('win') and (module.nvim_dir:sub(1,2)..pathsep) or '/' end --- Gets the full `…/build/bin/{name}` path of a test program produced by @@ -830,7 +854,7 @@ end --- --- @param name (string) Name of the test program. function module.testprg(name) - local ext = module.iswin() and '.exe' or '' + local ext = module.is_os('win') and '.exe' or '' return ('%s/%s%s'):format(module.nvim_dir, name, ext) end @@ -857,7 +881,7 @@ function module.missing_provider(provider) end function module.alter_slashes(obj) - if not iswin() then + if not is_os('win') then return obj end if type(obj) == 'string' then @@ -875,7 +899,7 @@ function module.alter_slashes(obj) end local load_factor = 1 -if global_helpers.isCI() then +if global_helpers.is_ci() then -- Compute load factor only once (but outside of any tests). module.clear() module.request('nvim_command', 'source src/nvim/testdir/load.vim') @@ -908,14 +932,14 @@ end -- Kill process with given pid function module.os_kill(pid) - return os.execute((iswin() + return os.execute((is_os('win') and 'taskkill /f /t /pid '..pid..' > nul' or 'kill -9 '..pid..' > /dev/null')) end -- Create folder with non existing parents function module.mkdir_p(path) - return os.execute((iswin() + return os.execute((is_os('win') and 'mkdir '..path or 'mkdir -p '..path)) end diff --git a/test/functional/legacy/011_autocommands_spec.lua b/test/functional/legacy/011_autocommands_spec.lua index 0fa9290f3c..7ae851467f 100644 --- a/test/functional/legacy/011_autocommands_spec.lua +++ b/test/functional/legacy/011_autocommands_spec.lua @@ -18,11 +18,11 @@ local clear, feed_command, expect, eq, neq, dedent, write_file, feed = helpers.clear, helpers.feed_command, helpers.expect, helpers.eq, helpers.neq, helpers.dedent, helpers.write_file, helpers.feed local command = helpers.command -local iswin = helpers.iswin local read_file = helpers.read_file +local is_os = helpers.is_os local function has_gzip() - local null = iswin() and 'nul' or '/dev/null' + local null = is_os('win') and 'nul' or '/dev/null' return os.execute('gzip --help >' .. null .. ' 2>&1') == 0 end diff --git a/test/functional/legacy/025_jump_tag_hidden_spec.lua b/test/functional/legacy/025_jump_tag_hidden_spec.lua index dd89a3680e..15bd56a601 100644 --- a/test/functional/legacy/025_jump_tag_hidden_spec.lua +++ b/test/functional/legacy/025_jump_tag_hidden_spec.lua @@ -23,7 +23,7 @@ describe('jump to a tag with hidden set', function() feed_command('set hidden') -- Create a link from test25.dir to the current directory. - if helpers.iswin() then + if helpers.is_os('win') then feed_command('!rd /q/s test25.dir') feed_command('!mklink /j test25.dir .') else @@ -33,7 +33,7 @@ describe('jump to a tag with hidden set', function() -- Create tags.text, with the current directory name inserted. feed_command('/tags line') - feed_command('r !' .. (helpers.iswin() and 'cd' or 'pwd')) + feed_command('r !' .. (helpers.is_os('win') and 'cd' or 'pwd')) feed('d$/test<cr>') feed('hP:.w! tags.test<cr>') @@ -44,7 +44,7 @@ describe('jump to a tag with hidden set', function() feed('G<C-]> x:yank a<cr>') feed_command("call delete('tags.test')") feed_command("call delete('Xxx')") - if helpers.iswin() then + if helpers.is_os('win') then feed_command('!rd /q test25.dir') else feed_command('!rm -f test25.dir') diff --git a/test/functional/legacy/045_folding_spec.lua b/test/functional/legacy/045_folding_spec.lua deleted file mode 100644 index 7d7856fd37..0000000000 --- a/test/functional/legacy/045_folding_spec.lua +++ /dev/null @@ -1,214 +0,0 @@ --- Tests for folding. -local Screen = require('test.functional.ui.screen') - -local helpers = require('test.functional.helpers')(after_each) -local feed, insert, feed_command, expect_any = - helpers.feed, helpers.insert, helpers.feed_command, helpers.expect_any - -describe('folding', function() - local screen - - before_each(function() - helpers.clear() - - screen = Screen.new(20, 8) - screen:attach() - end) - - it('creation, opening, moving (to the end) and closing', function() - insert([[ - 1 aa - 2 bb - 3 cc - last - ]]) - - -- Basic test if a fold can be created, opened, moving to the end and - -- closed. - feed_command('1') - feed('zf2j') - feed_command('call append("$", "manual " . getline(foldclosed(".")))') - feed('zo') - feed_command('call append("$", foldclosed("."))') - feed(']z') - feed_command('call append("$", getline("."))') - feed('zc') - feed_command('call append("$", getline(foldclosed(".")))') - - expect_any([[ - manual 1 aa - -1 - 3 cc - 1 aa]]) - end) - - it("foldmethod=marker", function() - screen:try_resize(20, 10) - insert([[ - dd {{{ - ee {{{ }}} - ff }}} - ]]) - feed_command('set fdm=marker fdl=1') - feed_command('2') - feed_command('call append("$", "line 2 foldlevel=" . foldlevel("."))') - feed('[z') - feed_command('call append("$", foldlevel("."))') - feed('jo{{ <esc>r{jj') -- writes '{{{' and moves 2 lines bot - feed_command('call append("$", foldlevel("."))') - feed('kYpj') - feed_command('call append("$", foldlevel("."))') - - helpers.poke_eventloop() - screen:expect([[ - dd {{{ | - ee {{{ }}} | - {{{ | - ff }}} | - ff }}} | - ^ | - line 2 foldlevel=2 | - 1 | - 1 | - | - ]]) - - end) - - it("foldmethod=indent", function() - screen:try_resize(20, 8) - feed_command('set fdm=indent sw=2') - insert([[ - aa - bb - cc - last - ]]) - feed_command('call append("$", "foldlevel line3=" . foldlevel(3))') - feed_command('call append("$", foldlevel(2))') - feed('zR') - - helpers.poke_eventloop() - screen:expect([[ - aa | - bb | - cc | - last | - ^ | - foldlevel line3=2 | - 1 | - | - ]]) - end) - - it("foldmethod=syntax", function() - screen:try_resize(35, 15) - insert([[ - 1 aa - 2 bb - 3 cc - 4 dd {{{ - 5 ee {{{ }}} - 6 ff }}} - 7 gg - 8 hh - 9 ii - a jj - b kk - last]]) - feed_command('set fdm=syntax fdl=0') - feed_command('syn region Hup start="dd" end="ii" fold contains=Fd1,Fd2,Fd3') - feed_command('syn region Fd1 start="ee" end="ff" fold contained') - feed_command('syn region Fd2 start="gg" end="hh" fold contained') - feed_command('syn region Fd3 start="commentstart" end="commentend" fold contained') - feed('Gzk') - feed_command('call append("$", "folding " . getline("."))') - feed('k') - feed_command('call append("$", getline("."))') - feed('jAcommentstart <esc>Acommentend<esc>') - feed_command('set fdl=1') - feed('3j') - feed_command('call append("$", getline("."))') - feed_command('set fdl=0') - feed('zO<C-L>j') -- <C-L> redraws screen - feed_command('call append("$", getline("."))') - feed_command('set fdl=0') - expect_any([[ - folding 9 ii - 3 cc - 9 ii - a jj]]) - end) - - it("foldmethod=expression", function() - insert([[ - 1 aa - 2 bb - 3 cc - 4 dd {{{ - 5 ee {{{ }}} - 6 ff }}} - 7 gg - 8 hh - 9 ii - a jj - b kk - last ]]) - - feed_command([[ - fun Flvl() - let l = getline(v:lnum) - if l =~ "bb$" - return 2 - elseif l =~ "gg$" - return "s1" - elseif l =~ "ii$" - return ">2" - elseif l =~ "kk$" - return "0" - endif - return "=" - endfun - ]]) - feed_command('set fdm=expr fde=Flvl()') - feed_command('/bb$') - feed_command('call append("$", "expr " . foldlevel("."))') - feed_command('/hh$') - feed_command('call append("$", foldlevel("."))') - feed_command('/ii$') - feed_command('call append("$", foldlevel("."))') - feed_command('/kk$') - feed_command('call append("$", foldlevel("."))') - - expect_any([[ - expr 2 - 1 - 2 - 0]]) - end) - - it('can be opened after :move', function() - -- luacheck: ignore - screen:try_resize(35, 8) - insert([[ - Test fdm=indent and :move bug END - line2 - Test fdm=indent START - line3 - line4]]) - feed_command('set noai nosta ') - feed_command('set fdm=indent') - feed_command('1m1') - feed('2jzc') - feed_command('m0') - feed('zR') - - expect_any([[ - Test fdm=indent START - line3 - line4 - Test fdm=indent and :move bug END - line2]]) - end) -end) - diff --git a/test/functional/legacy/055_list_and_dict_types_spec.lua b/test/functional/legacy/055_list_and_dict_types_spec.lua index c0ff3ed17a..75294b3786 100644 --- a/test/functional/legacy/055_list_and_dict_types_spec.lua +++ b/test/functional/legacy/055_list_and_dict_types_spec.lua @@ -330,214 +330,6 @@ describe('list and dictionary types', function() same list: 1]]) end) - it('locked variables (part 1)', function() - source([=[ - let l = [] - for depth in range(5) - $put ='depth is ' . depth - for u in range(3) - unlet l - let l = [0, [1, [2, 3]], {4: 5, 6: {7: 8}}] - exe "lockvar " . depth . " l" - if u == 1 - exe "unlockvar l" - elseif u == 2 - exe "unlockvar " . depth . " l" - endif - let ps = islocked("l") . islocked("l[1]") . islocked("l[1][1]") . - \ islocked("l[1][1][0]") . '-' . islocked("l[2]") . - \ islocked("l[2]['6']") . islocked("l[2]['6'][7]") - $put =ps - let ps = '' - try - let l[1][1][0] = 99 - let ps .= 'p' - catch - let ps .= 'F' - endtry - try - let l[1][1] = [99] - let ps .= 'p' - catch - let ps .= 'F' - endtry - try - let l[1] = [99] - let ps .= 'p' - catch - let ps .= 'F' - endtry - try - let l[2]['6'][7] = 99 - let ps .= 'p' - catch - let ps .= 'F' - endtry - try - let l[2][6] = {99: 99} - let ps .= 'p' - catch - let ps .= 'F' - endtry - try - let l[2] = {99: 99} - let ps .= 'p' - catch - let ps .= 'F' - endtry - try - let l = [99] - let ps .= 'p' - catch - let ps .= 'F' - endtry - $put =ps - endfor - endfor]=]) - expect([[ - - depth is 0 - 0000-000 - ppppppp - 0000-000 - ppppppp - 0000-000 - ppppppp - depth is 1 - 1000-000 - ppppppF - 0000-000 - ppppppp - 0000-000 - ppppppp - depth is 2 - 1100-100 - ppFppFF - 0000-000 - ppppppp - 0000-000 - ppppppp - depth is 3 - 1110-110 - pFFpFFF - 0010-010 - pFppFpp - 0000-000 - ppppppp - depth is 4 - 1111-111 - FFFFFFF - 0011-011 - FFpFFpp - 0000-000 - ppppppp]]) - end) - - -- TODO In the original test the 5th line of this source() call was used. - -- But now the test only passes if I comment it. - it('unletting locked variables', function() - source([=[ - let l = [] - for depth in range(5) - $put ='depth is ' . depth - for u in range(3) - "unlet l - let l = [0, [1, [2, 3]], {4: 5, 6: {7: 8}}] - exe "lockvar " . depth . " l" - if u == 1 - exe "unlockvar l" - elseif u == 2 - exe "unlockvar " . depth . " l" - endif - let ps = islocked("l") . islocked("l[1]") . islocked("l[1][1]") . - \ islocked("l[1][1][0]") . '-' . islocked("l[2]") . - \ islocked("l[2]['6']") . islocked("l[2]['6'][7]") - $put =ps - let ps = '' - try - unlet l[2]['6'][7] - let ps .= 'p' - catch - let ps .= 'F' - endtry - try - unlet l[2][6] - let ps .= 'p' - catch - let ps .= 'F' - endtry - try - unlet l[2] - let ps .= 'p' - catch - let ps .= 'F' - endtry - try - unlet l[1][1][0] - let ps .= 'p' - catch - let ps .= 'F' - endtry - try - unlet l[1][1] - let ps .= 'p' - catch - let ps .= 'F' - endtry - try - unlet l[1] - let ps .= 'p' - catch - let ps .= 'F' - endtry - try - unlet l - let ps .= 'p' - catch - let ps .= 'F' - endtry - $put =ps - endfor - endfor]=]) - expect([[ - - depth is 0 - 0000-000 - ppppppp - 0000-000 - ppppppp - 0000-000 - ppppppp - depth is 1 - 1000-000 - ppFppFp - 0000-000 - ppppppp - 0000-000 - ppppppp - depth is 2 - 1100-100 - pFFpFFp - 0000-000 - ppppppp - 0000-000 - ppppppp - depth is 3 - 1110-110 - FFFFFFp - 0010-010 - FppFppp - 0000-000 - ppppppp - depth is 4 - 1111-111 - FFFFFFp - 0011-011 - FppFppp - 0000-000 - ppppppp]]) - end) - it('locked variables and :unlet or list / dict functions', function() source([[ $put ='Locks and commands or functions:' @@ -676,30 +468,6 @@ describe('list and dictionary types', function() ['a', 'b', 3]]=]) end) - it('locked variables (part 2)', function() - feed_command( - 'let l = [1, 2, 3, 4]', - 'lockvar! l', - '$put =string(l)', - 'unlockvar l[1]', - 'unlet l[0:1]', - '$put =string(l)', - 'unlet l[1:2]', - '$put =string(l)', - 'unlockvar l[1]', - 'let l[0:1] = [0, 1]', - '$put =string(l)', - 'let l[1:2] = [0, 1]', - '$put =string(l)') - expect([=[ - - [1, 2, 3, 4] - [1, 2, 3, 4] - [1, 2, 3, 4] - [1, 2, 3, 4] - [1, 2, 3, 4]]=]) - end) - it(':lockvar/islocked() triggering script autoloading.', function() source([[ set rtp+=test/functional/fixtures diff --git a/test/functional/legacy/097_glob_path_spec.lua b/test/functional/legacy/097_glob_path_spec.lua index dd5a26ad3b..a62dc4d4c8 100644 --- a/test/functional/legacy/097_glob_path_spec.lua +++ b/test/functional/legacy/097_glob_path_spec.lua @@ -10,7 +10,7 @@ describe('glob() and globpath()', function() setup(clear) setup(function() - if helpers.iswin() then + if helpers.is_os('win') then os.execute("md sautest\\autoload") os.execute(".>sautest\\autoload\\Test104.vim 2>nul") os.execute(".>sautest\\autoload\\footest.vim 2>nul") @@ -28,7 +28,7 @@ describe('glob() and globpath()', function() -- Consistent sorting of file names command('set nofileignorecase') - if helpers.iswin() then + if helpers.is_os('win') then command([[$put =glob('Xxx{')]]) command([[$put =glob('Xxx$')]]) @@ -72,7 +72,7 @@ describe('glob() and globpath()', function() end) teardown(function() - if helpers.iswin() then + if helpers.is_os('win') then os.execute('del /q/f Xxx{ Xxx$') os.execute('rd /q /s sautest') else diff --git a/test/functional/legacy/breakindent_spec.lua b/test/functional/legacy/breakindent_spec.lua new file mode 100644 index 0000000000..d7779684a4 --- /dev/null +++ b/test/functional/legacy/breakindent_spec.lua @@ -0,0 +1,44 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') +local clear = helpers.clear +local exec = helpers.exec +local feed = helpers.feed + +before_each(clear) + +describe('breakindent', function() + -- oldtest: Test_cursor_position_with_showbreak() + it('cursor shown at correct position with showbreak', function() + local screen = Screen.new(75, 6) + screen:set_default_attr_ids({ + [0] = {bold = true, foreground = Screen.colors.Blue}, -- NonText + [1] = {background = Screen.colors.Grey, foreground = Screen.colors.DarkBlue}, -- SignColumn + [2] = {bold = true}, -- ModeMsg + }) + screen:attach() + exec([[ + let &signcolumn = 'yes' + let &showbreak = '+' + let leftcol = win_getid()->getwininfo()->get(0, {})->get('textoff') + eval repeat('x', &columns - leftcol - 1)->setline(1) + eval 'second line'->setline(2) + ]]) + screen:expect([[ + {1: }^xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | + {1: }second line | + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + feed('AX') + screen:expect([[ + {1: }xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxX| + {1: }^second line | + {0:~ }| + {0:~ }| + {0:~ }| + {2:-- INSERT --} | + ]]) + end) +end) diff --git a/test/functional/legacy/delete_spec.lua b/test/functional/legacy/delete_spec.lua index 4ba4c8d356..cefcd6c6c4 100644 --- a/test/functional/legacy/delete_spec.lua +++ b/test/functional/legacy/delete_spec.lua @@ -48,7 +48,7 @@ describe('Test for delete()', function() it('symlink directory delete', function() command("call mkdir('Xdir1')") - if helpers.iswin() then + if helpers.is_os('win') then command("silent !mklink /j Xlink Xdir1") else command("silent !ln -s Xdir1 Xlink") diff --git a/test/functional/legacy/digraph_spec.lua b/test/functional/legacy/digraph_spec.lua new file mode 100644 index 0000000000..0cb0bb84be --- /dev/null +++ b/test/functional/legacy/digraph_spec.lua @@ -0,0 +1,46 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') +local clear = helpers.clear +local feed = helpers.feed + +before_each(clear) + +describe('digraph', function() + -- oldtest: Test_entering_digraph() + it('characters displayed on the screen', function() + local screen = Screen.new(10, 6) + screen:set_default_attr_ids({ + [0] = {bold = true, foreground = Screen.colors.Blue}, -- NonText + [1] = {foreground = Screen.colors.Blue}, -- SpecialKey + [2] = {bold = true}, -- ModeMsg + }) + screen:attach() + feed('i<C-K>') + screen:expect([[ + {1:^?} | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {2:-- INSERT -} | + ]]) + feed('1') + screen:expect([[ + {1:^1} | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {2:-- INSERT -} | + ]]) + feed('2') + screen:expect([[ + ½^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {2:-- INSERT -} | + ]]) + end) +end) diff --git a/test/functional/legacy/display_spec.lua b/test/functional/legacy/display_spec.lua index 9160129a02..f9b78f5dcd 100644 --- a/test/functional/legacy/display_spec.lua +++ b/test/functional/legacy/display_spec.lua @@ -58,6 +58,51 @@ describe('display', function() ]]) end) + -- oldtest: Test_matchparen_clear_highlight() + it('matchparen highlight is cleared when switching buffer', function() + local screen = Screen.new(20, 5) + screen:set_default_attr_ids({ + [0] = {bold = true, foreground = Screen.colors.Blue}, + [1] = {background = Screen.colors.Cyan}, + }) + screen:attach() + + local screen1 = [[ + {1:^()} | + {0:~ }| + {0:~ }| + {0:~ }| + | + ]] + local screen2 = [[ + ^aa | + {0:~ }| + {0:~ }| + {0:~ }| + | + ]] + + exec([[ + source $VIMRUNTIME/plugin/matchparen.vim + set hidden + call setline(1, ['()']) + normal 0 + ]]) + screen:expect(screen1) + + exec([[ + enew + exe "normal iaa\<Esc>0" + ]]) + screen:expect(screen2) + + feed('<C-^>') + screen:expect(screen1) + + feed('<C-^>') + screen:expect(screen2) + end) + local function run_test_display_lastline(euro) local screen = Screen.new(75, 10) screen:set_default_attr_ids({ diff --git a/test/functional/legacy/edit_spec.lua b/test/functional/legacy/edit_spec.lua index 7fc5f11a79..362d33a0fd 100644 --- a/test/functional/legacy/edit_spec.lua +++ b/test/functional/legacy/edit_spec.lua @@ -1,4 +1,5 @@ local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') local clear = helpers.clear local command = helpers.command local expect = helpers.expect @@ -7,20 +8,51 @@ local sleep = helpers.sleep before_each(clear) --- oldtest: Test_autoindent_remove_indent() -it('autoindent removes indent when Insert mode is stopped', function() - command('set autoindent') - -- leaving insert mode in a new line with indent added by autoindent, should - -- remove the indent. - feed('i<Tab>foo<CR><Esc>') - -- Need to delay for sometime, otherwise the code in getchar.c will not be - -- exercised. - sleep(50) - -- when a line is wrapped and the cursor is at the start of the second line, - -- leaving insert mode, should move the cursor back to the first line. - feed('o' .. ('x'):rep(20) .. '<Esc>') - -- Need to delay for sometime, otherwise the code in getchar.c will not be - -- exercised. - sleep(50) - expect('\tfoo\n\n' .. ('x'):rep(20)) +describe('edit', function() + -- oldtest: Test_autoindent_remove_indent() + it('autoindent removes indent when Insert mode is stopped', function() + command('set autoindent') + -- leaving insert mode in a new line with indent added by autoindent, should + -- remove the indent. + feed('i<Tab>foo<CR><Esc>') + -- Need to delay for sometime, otherwise the code in getchar.c will not be + -- exercised. + sleep(50) + -- when a line is wrapped and the cursor is at the start of the second line, + -- leaving insert mode, should move the cursor back to the first line. + feed('o' .. ('x'):rep(20) .. '<Esc>') + -- Need to delay for sometime, otherwise the code in getchar.c will not be + -- exercised. + sleep(50) + expect('\tfoo\n\n' .. ('x'):rep(20)) + end) + + -- oldtest: Test_edit_insert_reg() + it('inserting a register using CTRL-R', function() + local screen = Screen.new(10, 6) + screen:set_default_attr_ids({ + [0] = {bold = true, foreground = Screen.colors.Blue}, -- NonText + [1] = {foreground = Screen.colors.Blue}, -- SpecialKey + [2] = {bold = true}, -- ModeMsg + }) + screen:attach() + feed('a<C-R>') + screen:expect([[ + {1:^"} | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {2:-- INSERT -} | + ]]) + feed('=') + screen:expect([[ + {1:"} | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + =^ | + ]]) + end) end) diff --git a/test/functional/legacy/eval_spec.lua b/test/functional/legacy/eval_spec.lua index 05d853622e..b5e45a86c1 100644 --- a/test/functional/legacy/eval_spec.lua +++ b/test/functional/legacy/eval_spec.lua @@ -639,7 +639,7 @@ describe('eval', function() end) it('function name includes a colon', function() - eq('Vim(function):E128: Function name must start with a capital or "s:": b:test()\\nendfunction', + eq('Vim(function):E884: Function name cannot contain a colon: b:test()\\nendfunction', exc_exec(dedent([[ function! b:test() endfunction]]))) diff --git a/test/functional/legacy/excmd_spec.lua b/test/functional/legacy/excmd_spec.lua index ece88d26bd..eb480a6689 100644 --- a/test/functional/legacy/excmd_spec.lua +++ b/test/functional/legacy/excmd_spec.lua @@ -7,12 +7,12 @@ local exec_lua = helpers.exec_lua local expect_exit = helpers.expect_exit local feed = helpers.feed local funcs = helpers.funcs -local iswin = helpers.iswin local meths = helpers.meths local read_file = helpers.read_file local source = helpers.source local eq = helpers.eq local write_file = helpers.write_file +local is_os = helpers.is_os local function sizeoflong() if not exec_lua('return pcall(require, "ffi")') then @@ -376,7 +376,7 @@ describe(':confirm command dialog', function() {3:(Y)es, [N]o: }^ | ]]) feed('Y') - if iswin() then + if is_os('win') then screen:expect([[ foobar | {0:~ }| @@ -416,7 +416,7 @@ describe(':confirm command dialog', function() {3:(Y)es, [N]o: }^ | ]]) feed('Y') - if iswin() then + if is_os('win') then screen:expect([[ foobar | {1: }| @@ -493,7 +493,7 @@ describe(':confirm command dialog', function() {3:(Y)es, [N]o: }^ | ]]) feed('Y') - if iswin() then + if is_os('win') then screen:expect([[ a | b | diff --git a/test/functional/legacy/filechanged_spec.lua b/test/functional/legacy/filechanged_spec.lua index 1f23528d61..cea1d6ac30 100644 --- a/test/functional/legacy/filechanged_spec.lua +++ b/test/functional/legacy/filechanged_spec.lua @@ -1,6 +1,8 @@ local helpers = require('test.functional.helpers')(after_each) local clear, source = helpers.clear, helpers.source local call, eq, meths = helpers.call, helpers.eq, helpers.meths +local is_os = helpers.is_os +local skip = helpers.skip local function expected_empty() eq({}, meths.get_vvar('errors')) @@ -15,7 +17,7 @@ describe('file changed dialog', function() end) it('works', function() - if helpers.pending_win32(pending) then return end + skip(is_os('win')) source([[ func Test_file_changed_dialog() au! FileChangedShell diff --git a/test/functional/legacy/fold_spec.lua b/test/functional/legacy/fold_spec.lua new file mode 100644 index 0000000000..83513a3f94 --- /dev/null +++ b/test/functional/legacy/fold_spec.lua @@ -0,0 +1,335 @@ +-- Tests for folding. +local Screen = require('test.functional.ui.screen') + +local helpers = require('test.functional.helpers')(after_each) +local feed, insert, feed_command, expect_any = + helpers.feed, helpers.insert, helpers.feed_command, helpers.expect_any +local command = helpers.command +local exec = helpers.exec + +describe('folding', function() + local screen + + before_each(function() + helpers.clear() + + screen = Screen.new(45, 8) + screen:set_default_attr_ids({ + [1] = {bold = true, foreground = Screen.colors.Blue}, -- NonText + [2] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.LightGrey}, -- Folded + [3] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.Grey}, -- FoldColumn + [4] = {foreground = Screen.colors.Brown}, -- LineNr + }) + screen:attach() + end) + + it('creation, opening, moving (to the end) and closing', function() + insert([[ + 1 aa + 2 bb + 3 cc + last + ]]) + + -- Basic test if a fold can be created, opened, moving to the end and + -- closed. + feed_command('1') + feed('zf2j') + feed_command('call append("$", "manual " . getline(foldclosed(".")))') + feed('zo') + feed_command('call append("$", foldclosed("."))') + feed(']z') + feed_command('call append("$", getline("."))') + feed('zc') + feed_command('call append("$", getline(foldclosed(".")))') + + expect_any([[ + manual 1 aa + -1 + 3 cc + 1 aa]]) + end) + + it("foldmethod=marker", function() + screen:try_resize(20, 10) + insert([[ + dd {{{ + ee {{{ }}} + ff }}} + ]]) + feed_command('set fdm=marker fdl=1') + feed_command('2') + feed_command('call append("$", "line 2 foldlevel=" . foldlevel("."))') + feed('[z') + feed_command('call append("$", foldlevel("."))') + feed('jo{{ <esc>r{jj') -- writes '{{{' and moves 2 lines bot + feed_command('call append("$", foldlevel("."))') + feed('kYpj') + feed_command('call append("$", foldlevel("."))') + + helpers.poke_eventloop() + screen:expect([[ + dd {{{ | + ee {{{ }}} | + {{{ | + ff }}} | + ff }}} | + ^ | + line 2 foldlevel=2 | + 1 | + 1 | + | + ]]) + + end) + + it("foldmethod=indent", function() + screen:try_resize(20, 8) + feed_command('set fdm=indent sw=2') + insert([[ + aa + bb + cc + last + ]]) + feed_command('call append("$", "foldlevel line3=" . foldlevel(3))') + feed_command('call append("$", foldlevel(2))') + feed('zR') + + helpers.poke_eventloop() + screen:expect([[ + aa | + bb | + cc | + last | + ^ | + foldlevel line3=2 | + 1 | + | + ]]) + end) + + it("foldmethod=syntax", function() + screen:try_resize(35, 15) + insert([[ + 1 aa + 2 bb + 3 cc + 4 dd {{{ + 5 ee {{{ }}} + 6 ff }}} + 7 gg + 8 hh + 9 ii + a jj + b kk + last]]) + feed_command('set fdm=syntax fdl=0') + feed_command('syn region Hup start="dd" end="ii" fold contains=Fd1,Fd2,Fd3') + feed_command('syn region Fd1 start="ee" end="ff" fold contained') + feed_command('syn region Fd2 start="gg" end="hh" fold contained') + feed_command('syn region Fd3 start="commentstart" end="commentend" fold contained') + feed('Gzk') + feed_command('call append("$", "folding " . getline("."))') + feed('k') + feed_command('call append("$", getline("."))') + feed('jAcommentstart <esc>Acommentend<esc>') + feed_command('set fdl=1') + feed('3j') + feed_command('call append("$", getline("."))') + feed_command('set fdl=0') + feed('zO<C-L>j') -- <C-L> redraws screen + feed_command('call append("$", getline("."))') + feed_command('set fdl=0') + expect_any([[ + folding 9 ii + 3 cc + 9 ii + a jj]]) + end) + + it("foldmethod=expression", function() + insert([[ + 1 aa + 2 bb + 3 cc + 4 dd {{{ + 5 ee {{{ }}} + 6 ff }}} + 7 gg + 8 hh + 9 ii + a jj + b kk + last ]]) + + feed_command([[ + fun Flvl() + let l = getline(v:lnum) + if l =~ "bb$" + return 2 + elseif l =~ "gg$" + return "s1" + elseif l =~ "ii$" + return ">2" + elseif l =~ "kk$" + return "0" + endif + return "=" + endfun + ]]) + feed_command('set fdm=expr fde=Flvl()') + feed_command('/bb$') + feed_command('call append("$", "expr " . foldlevel("."))') + feed_command('/hh$') + feed_command('call append("$", foldlevel("."))') + feed_command('/ii$') + feed_command('call append("$", foldlevel("."))') + feed_command('/kk$') + feed_command('call append("$", foldlevel("."))') + + expect_any([[ + expr 2 + 1 + 2 + 0]]) + end) + + it('can be opened after :move', function() + -- luacheck: ignore + screen:try_resize(35, 8) + insert([[ + Test fdm=indent and :move bug END + line2 + Test fdm=indent START + line3 + line4]]) + feed_command('set noai nosta ') + feed_command('set fdm=indent') + feed_command('1m1') + feed('2jzc') + feed_command('m0') + feed('zR') + + expect_any([[ + Test fdm=indent START + line3 + line4 + Test fdm=indent and :move bug END + line2]]) + end) + + -- oldtest: Test_folds_with_rnu() + it('with relative line numbers', function() + command('set fdm=marker rnu foldcolumn=2') + command('call setline(1, ["{{{1", "nline 1", "{{{1", "line 2"])') + + screen:expect([[ + {3:+ }{4: 0 }{2:^+-- 2 lines: ·························}| + {3:+ }{4: 1 }{2:+-- 2 lines: ·························}| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + feed("j") + screen:expect([[ + {3:+ }{4: 1 }{2:+-- 2 lines: ·························}| + {3:+ }{4: 0 }{2:^+-- 2 lines: ·························}| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + end) + + -- oldtest: Test_foldclose_opt() + it('foldclose=all', function() + exec([[ + set foldmethod=manual foldclose=all foldopen=all + call setline(1, ['one', 'two', 'three', 'four']) + 2,3fold + ]]) + + screen:expect([[ + ^one | + {2:+-- 2 lines: two····························}| + four | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + feed('2G') + screen:expect([[ + one | + ^two | + three | + four | + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + feed('4G') + screen:expect([[ + one | + {2:+-- 2 lines: two····························}| + ^four | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + feed('3G') + screen:expect([[ + one | + two | + ^three | + four | + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + feed('1G') + screen:expect([[ + ^one | + {2:+-- 2 lines: two····························}| + four | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + feed('2G') + screen:expect([[ + one | + ^two | + three | + four | + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + feed('k') + screen:expect([[ + ^one | + {2:+-- 2 lines: two····························}| + four | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + end) +end) diff --git a/test/functional/legacy/gf_spec.lua b/test/functional/legacy/gf_spec.lua index f1b1790ba1..9f725446be 100644 --- a/test/functional/legacy/gf_spec.lua +++ b/test/functional/legacy/gf_spec.lua @@ -10,6 +10,7 @@ describe('gf', function() it('is not allowed when buffer is locked', function() command('au OptionSet diff norm! gf') command([[call setline(1, ['Xfile1', 'line2', 'line3', 'line4'])]]) - eq('Vim(normal):E788: Not allowed to edit another buffer now', pcall_err(command, 'diffthis')) + eq('OptionSet Autocommands for "diff": Vim(normal):E788: Not allowed to edit another buffer now', + pcall_err(command, 'diffthis')) end) end) diff --git a/test/functional/legacy/increment_spec.lua b/test/functional/legacy/increment_spec.lua index d51f9a2e02..d35f4bdae6 100644 --- a/test/functional/legacy/increment_spec.lua +++ b/test/functional/legacy/increment_spec.lua @@ -285,7 +285,7 @@ describe('Ctrl-A/Ctrl-X on visual selections', function() " 1 " 1 " 1 - " Expexted: + " Expected: " 1) g Ctrl-A on block selected indented lines " 2 " 1 diff --git a/test/functional/legacy/match_spec.lua b/test/functional/legacy/match_spec.lua index 271f844f9d..b6e45c396c 100644 --- a/test/functional/legacy/match_spec.lua +++ b/test/functional/legacy/match_spec.lua @@ -2,6 +2,7 @@ local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') local clear = helpers.clear local exec = helpers.exec +local feed = helpers.feed before_each(clear) @@ -36,3 +37,90 @@ describe('matchaddpos()', function() ]]) end) end) + +describe('match highlighting', function() + -- oldtest: Test_match_in_linebreak() + it('does not continue in linebreak vim-patch:8.2.3698', function() + local screen = Screen.new(75, 10) + screen:set_default_attr_ids({ + [0] = {bold = true, foreground = Screen.colors.Blue}, -- NonText + [1] = {background = Screen.colors.Red, foreground = Screen.colors.White}, -- ErrorMsg + }) + screen:attach() + exec([=[ + set breakindent linebreak breakat+=] + call printf('%s]%s', repeat('x', 50), repeat('x', 70))->setline(1) + call matchaddpos('ErrorMsg', [[1, 51]]) + ]=]) + screen:expect([[ + ^xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx{1:]} | + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + end) + + it('is shown with incsearch vim-patch:8.2.3940', function() + local screen = Screen.new(75, 6) + screen:set_default_attr_ids({ + [0] = {bold = true, foreground = Screen.colors.Blue}, -- NonText + [1] = {background = Screen.colors.Yellow}, -- Search + [2] = {background = Screen.colors.Red, foreground = Screen.colors.White}, -- ErrorMsg + }) + screen:attach() + exec([[ + set incsearch + call setline(1, range(20)) + call matchaddpos('ErrorMsg', [3]) + ]]) + screen:expect([[ + ^0 | + 1 | + {2:2} | + 3 | + 4 | + | + ]]) + feed(':s/0') + screen:expect([[ + {1:0} | + 1 | + {2:2} | + 3 | + 4 | + :s/0^ | + ]]) + end) + + it('on a Tab vim-patch:8.2.4062', function() + local screen = Screen.new(75, 10) + screen:set_default_attr_ids({ + [0] = {bold = true, foreground = Screen.colors.Blue}, -- NonText + [1] = {background = Screen.colors.Red, foreground = Screen.colors.White}, -- ErrorMsg + }) + screen:attach() + exec([[ + set linebreak + call setline(1, "\tix") + call matchadd('ErrorMsg', '\t') + ]]) + screen:expect([[ + {1: ^ }ix | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + end) +end) diff --git a/test/functional/legacy/memory_usage_spec.lua b/test/functional/legacy/memory_usage_spec.lua index eec89aa919..59839157ea 100644 --- a/test/functional/legacy/memory_usage_spec.lua +++ b/test/functional/legacy/memory_usage_spec.lua @@ -3,15 +3,14 @@ local clear = helpers.clear local eval = helpers.eval local eq = helpers.eq local feed_command = helpers.feed_command -local iswin = helpers.iswin local retry = helpers.retry local ok = helpers.ok local source = helpers.source local poke_eventloop = helpers.poke_eventloop -local uname = helpers.uname local load_adjust = helpers.load_adjust local write_file = helpers.write_file -local isCI = helpers.isCI +local is_os = helpers.is_os +local is_ci = helpers.is_ci local function isasan() local version = eval('execute("version")') @@ -22,8 +21,8 @@ clear() if isasan() then pending('ASAN build is difficult to estimate memory usage', function() end) return -elseif iswin() then - if isCI('github') then +elseif is_os('win') then + if is_ci('github') then pending('Windows runners in Github Actions do not have a stable environment to estimate memory usage', function() end) return elseif eval("executable('wmic')") == 0 then @@ -38,7 +37,7 @@ end local monitor_memory_usage = { memory_usage = function(self) local handle - if iswin() then + if is_os('win') then handle = io.popen('wmic process where processid=' ..self.pid..' get WorkingSetSize') else handle = io.popen('ps -o rss= -p '..self.pid) @@ -167,9 +166,9 @@ describe('memory usage', function() local last = monitor_memory_usage(pid) -- The usage may be a bit less than the last value, use 80%. -- Allow for 20% tolerance at the upper limit. That's very permissive, but - -- otherwise the test fails sometimes. On Sourcehut CI with FreeBSD we need to - -- be even much more permissive. - local upper_multiplier = uname() == 'freebsd' and 19 or 12 + -- otherwise the test fails sometimes. On FreeBSD we need to be even much + -- more permissive. + local upper_multiplier = is_os('freebsd') and 19 or 12 local lower = before.last * 8 / 10 local upper = load_adjust((after.max + (after.last - before.last)) * upper_multiplier / 10) check_result({before=before, after=after, last=last}, @@ -179,7 +178,7 @@ describe('memory usage', function() end) it('releases memory when closing windows when folds exist', function() - if helpers.is_os('mac') then + if is_os('mac') then pending('macOS memory compression causes flakiness') end local pid = eval('getpid()') diff --git a/test/functional/legacy/search_spec.lua b/test/functional/legacy/search_spec.lua index 5a94fca794..3f1f85cf28 100644 --- a/test/functional/legacy/search_spec.lua +++ b/test/functional/legacy/search_spec.lua @@ -14,7 +14,7 @@ describe('search cmdline', function() before_each(function() clear() - command('set nohlsearch') + command('set nohlsearch inccommand=') screen = Screen.new(20, 3) screen:attach() screen:set_default_attr_ids({ @@ -472,8 +472,8 @@ describe('search cmdline', function() funcs.winsaveview()) end) + -- oldtest: Test_search_cmdline4(). it("CTRL-G with 'incsearch' and ? goes in the right direction", function() - -- oldtest: Test_search_cmdline4(). screen:try_resize(40, 4) command('enew!') funcs.setline(1, {' 1 the first', ' 2 the second', ' 3 the third'}) @@ -573,8 +573,8 @@ describe('search cmdline', function() ]]) end) + -- oldtest: Test_incsearch_sort_dump(). it('incsearch works with :sort', function() - -- oldtest: Test_incsearch_sort_dump(). screen:try_resize(20, 4) command('set incsearch hlsearch scrolloff=0') funcs.setline(1, {'another one 2', 'that one 3', 'the one 1'}) @@ -589,8 +589,8 @@ describe('search cmdline', function() feed('<esc>') end) + -- oldtest: Test_incsearch_vimgrep_dump(). it('incsearch works with :vimgrep family', function() - -- oldtest: Test_incsearch_vimgrep_dump(). screen:try_resize(30, 4) command('set incsearch hlsearch scrolloff=0') funcs.setline(1, {'another one 2', 'that one 3', 'the one 1'}) @@ -640,6 +640,41 @@ describe('search cmdline', function() ]]) feed('<esc>') end) + + -- oldtest: Test_incsearch_substitute_dump2() + it('detects empty pattern properly vim-patch:8.2.2295', function() + screen:try_resize(70, 6) + exec([[ + set incsearch hlsearch scrolloff=0 + for n in range(1, 4) + call setline(n, "foo " . n) + endfor + call setline(5, "abc|def") + 3 + ]]) + + feed([[:%s/\vabc|]]) + screen:expect([[ + foo 1 | + foo 2 | + foo 3 | + foo 4 | + abc|def | + :%s/\vabc|^ | + ]]) + feed('<Esc>') + + -- The following should not be highlighted + feed([[:1,5s/\v|]]) + screen:expect([[ + foo 1 | + foo 2 | + foo 3 | + foo 4 | + abc|def | + :1,5s/\v|^ | + ]]) + end) end) describe('Search highlight', function() diff --git a/test/functional/legacy/search_stat_spec.lua b/test/functional/legacy/search_stat_spec.lua index c2ca393a56..9fcf798836 100644 --- a/test/functional/legacy/search_stat_spec.lua +++ b/test/functional/legacy/search_stat_spec.lua @@ -17,6 +17,7 @@ describe('search stat', function() screen:attach() end) + -- oldtest: Test_search_stat_screendump() it('right spacing with silent mapping vim-patch:8.1.1970', function() exec([[ set shortmess-=S @@ -57,6 +58,7 @@ describe('search stat', function() ]]) end) + -- oldtest: Test_search_stat_foldopen() it('when only match is in fold vim-patch:8.2.0840', function() exec([[ set shortmess-=S @@ -86,6 +88,7 @@ describe('search stat', function() screen:expect_unchanged() end) + -- oldtest: Test_search_stat_then_gd() it('is cleared by gd and gD vim-patch:8.2.3583', function() exec([[ call setline(1, ['int cat;', 'int dog;', 'cat = dog;']) @@ -120,6 +123,7 @@ describe('search stat', function() ]]) end) + -- oldtest: Test_search_stat_and_incsearch() it('is not broken by calling searchcount() in tabline vim-patch:8.2.4378', function() exec([[ call setline(1, ['abc--c', '--------abc', '--abc']) diff --git a/test/functional/legacy/source_spec.lua b/test/functional/legacy/source_spec.lua new file mode 100644 index 0000000000..f31521607d --- /dev/null +++ b/test/functional/legacy/source_spec.lua @@ -0,0 +1,32 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') +local clear = helpers.clear +local feed = helpers.feed +local write_file = helpers.write_file + +before_each(clear) + +describe(':source!', function() + -- oldtest: Test_nested_script() + it('gives E22 when scripts nested too deep', function() + write_file('Xscript.vim', [[ + :source! Xscript.vim + ]]) + local screen = Screen.new(75, 6) + screen:set_default_attr_ids({ + [0] = {bold = true, foreground = Screen.colors.Blue}, -- NonText + [1] = {background = Screen.colors.Red, foreground = Screen.colors.White}, -- ErrorMsg + }) + screen:attach() + feed(':source! Xscript.vim\n') + screen:expect([[ + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {1:E22: Scripts nested too deep} | + ]]) + os.remove('Xscript.vim') + end) +end) diff --git a/test/functional/legacy/statusline_spec.lua b/test/functional/legacy/statusline_spec.lua index e2b30a7c82..c5b17f8749 100644 --- a/test/functional/legacy/statusline_spec.lua +++ b/test/functional/legacy/statusline_spec.lua @@ -68,4 +68,87 @@ describe('statusline', function() | ]]) end) + + -- oldtest: Test_statusline_showcmd() + it('showcmdloc=statusline works', function() + screen:set_default_attr_ids({ + [0] = {bold = true, foreground = Screen.colors.Blue}, -- NonText + [1] = {background = Screen.colors.LightGrey}, -- Visual + [2] = {bold = true}, -- MoreMsg + [3] = {bold = true, reverse = true}, -- StatusLine + [5] = {background = Screen.colors.LightGrey, foreground = Screen.colors.DarkBlue}, -- Folded + }) + exec([[ + func MyStatusLine() + return '%S' + endfunc + + set showcmd + set laststatus=2 + set statusline=%S + set showcmdloc=statusline + call setline(1, ['a', 'b', 'c']) + set foldopen+=jump + 1,2fold + 3 + ]]) + + feed('g') + screen:expect([[ + {5:+-- 2 lines: a···································}| + ^c | + {0:~ }| + {0:~ }| + {0:~ }| + {3:g }| + | + ]]) + + -- typing "gg" should open the fold + feed('g') + screen:expect([[ + ^a | + b | + c | + {0:~ }| + {0:~ }| + {3: }| + | + ]]) + + feed('<C-V>Gl') + screen:expect([[ + {1:a} | + {1:b} | + {1:c}^ | + {0:~ }| + {0:~ }| + {3:3x2 }| + {2:-- VISUAL BLOCK --} | + ]]) + + feed('<Esc>1234') + screen:expect([[ + a | + b | + ^c | + {0:~ }| + {0:~ }| + {3:1234 }| + | + ]]) + + feed('<Esc>:set statusline=<CR>') + feed(':<CR>') + feed('1234') + screen:expect([[ + a | + b | + ^c | + {0:~ }| + {0:~ }| + {3:[No Name] [+] 1234 }| + : | + ]]) + end) end) diff --git a/test/functional/legacy/syn_attr_spec.lua b/test/functional/legacy/syn_attr_spec.lua index 06e8427e27..e6573da5d3 100644 --- a/test/functional/legacy/syn_attr_spec.lua +++ b/test/functional/legacy/syn_attr_spec.lua @@ -4,10 +4,10 @@ local command = helpers.command local eq = helpers.eq local eval = helpers.eval -before_each(clear) - -- oldtest: Test_missing_attr() -it('synIDattr() works', function() +describe('synIDattr()', function() + setup(clear) + local bool_attrs = { 'bold', 'italic', @@ -22,39 +22,55 @@ it('synIDattr() works', function() 'nocombine', } - command('hi Mine cterm=NONE gui=NONE') - eq('Mine', eval([[synIDattr(hlID("Mine"), "name")]])) - for _, mode in ipairs({'cterm', 'gui'}) do - eq('', eval(([[synIDattr("Mine"->hlID(), "bg", '%s')]]):format(mode))) - eq('', eval(([[synIDattr("Mine"->hlID(), "fg", '%s')]]):format(mode))) - eq('', eval(([[synIDattr("Mine"->hlID(), "sp", '%s')]]):format(mode))) - for _, attr in ipairs(bool_attrs) do - eq('', eval(([[synIDattr(hlID("Mine"), "%s", '%s')]]):format(attr, mode))) - eq('', eval(([[synIDattr(hlID("Mine"), "%s", '%s')]]):format(attr, mode))) - eq('', eval(([[synIDattr(hlID("Mine"), "%s", '%s')]]):format(attr, mode))) + describe(':hi Mine cterm=NONE gui=NONE', function() + setup(function() + command(':hi Mine cterm=NONE gui=NONE') + end) + + it('"name"', function() + eq('Mine', eval([[synIDattr(hlID("Mine"), "name")]])) + end) + + local function none_test(attr, mode) + it(('"%s"'):format(attr), function() + eq('', eval(([[synIDattr(hlID("Mine"), "%s", '%s')]]):format(attr, mode))) + end) end - eq('', eval(([[synIDattr(hlID("Mine"), "inverse", '%s')]]):format(mode))) + + for _, mode in ipairs({'cterm', 'gui'}) do + describe(('"%s"'):format(mode), function() + for _, attr in ipairs(bool_attrs) do + none_test(attr, mode) + end + for _, attr in ipairs({'inverse', 'bg', 'fg', 'sp'}) do + none_test(attr, mode) + end + end) + end + end) + + local function attr_test(attr1, attr2) + local cmd = (':hi Mine cterm=%s gui=%s'):format(attr1, attr2) + it(cmd, function() + command(cmd) + eq('1', eval(([[synIDattr("Mine"->hlID(), "%s", 'cterm')]]):format(attr1))) + eq('', eval(([[synIDattr(hlID("Mine"), "%s", 'cterm')]]):format(attr2))) + eq('', eval(([[synIDattr("Mine"->hlID(), "%s", 'gui')]]):format(attr1))) + eq('1', eval(([[synIDattr(hlID("Mine"), "%s", 'gui')]]):format(attr2))) + end) end for i, attr1 in ipairs(bool_attrs) do local attr2 = bool_attrs[i - 1] or bool_attrs[#bool_attrs] - - command(('hi Mine cterm=%s gui=%s'):format(attr1, attr2)) - eq('1', eval(([[synIDattr(hlID("Mine"), "%s", 'cterm')]]):format(attr1))) - eq('', eval(([[synIDattr(hlID("Mine"), "%s", 'cterm')]]):format(attr2))) - eq('', eval(([[synIDattr("Mine"->hlID(), "%s", 'gui')]]):format(attr1))) - eq('1', eval(([[synIDattr("Mine"->hlID(), "%s", 'gui')]]):format(attr2))) - - command(('hi Mine cterm=%s gui=%s'):format(attr2, attr1)) - eq('', eval(([[synIDattr("Mine"->hlID(), "%s", 'cterm')]]):format(attr1))) - eq('1', eval(([[synIDattr("Mine"->hlID(), "%s", 'cterm')]]):format(attr2))) - eq('1', eval(([[synIDattr(hlID("Mine"), "%s", 'gui')]]):format(attr1))) - eq('', eval(([[synIDattr(hlID("Mine"), "%s", 'gui')]]):format(attr2))) + attr_test(attr1, attr2) + attr_test(attr2, attr1) end - command('hi Mine cterm=reverse gui=inverse') - eq('1', eval([[synIDattr(hlID("Mine"), "reverse", 'cterm')]])) - eq('1', eval([[synIDattr(hlID("Mine"), "inverse", 'cterm')]])) - eq('1', eval([[synIDattr(hlID("Mine"), "reverse", 'gui')]])) - eq('1', eval([[synIDattr(hlID("Mine"), "inverse", 'gui')]])) + it(':hi Mine cterm=reverse gui=inverse', function() + command(':hi Mine cterm=reverse gui=inverse') + eq('1', eval([[synIDattr(hlID("Mine"), "reverse", 'cterm')]])) + eq('1', eval([[synIDattr(hlID("Mine"), "inverse", 'cterm')]])) + eq('1', eval([[synIDattr(hlID("Mine"), "reverse", 'gui')]])) + eq('1', eval([[synIDattr(hlID("Mine"), "inverse", 'gui')]])) + end) end) diff --git a/test/functional/legacy/tabline_spec.lua b/test/functional/legacy/tabline_spec.lua new file mode 100644 index 0000000000..6b368d1857 --- /dev/null +++ b/test/functional/legacy/tabline_spec.lua @@ -0,0 +1,100 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') +local clear = helpers.clear +local exec = helpers.exec +local feed = helpers.feed + +before_each(clear) + +describe('tabline', function() + local screen + + before_each(function() + screen = Screen.new(50, 7) + screen:attach() + end) + + -- oldtest: Test_tabline_showcmd() + it('showcmdloc=tabline works', function() + screen:set_default_attr_ids({ + [0] = {bold = true, foreground = Screen.colors.Blue}, -- NonText + [1] = {background = Screen.colors.LightGrey}, -- Visual + [2] = {bold = true}, -- MoreMsg, TabLineSel + [3] = {reverse = true}, -- TabLineFill + [4] = {background = Screen.colors.LightGrey, underline = true}, -- TabLine + [5] = {background = Screen.colors.LightGrey, foreground = Screen.colors.DarkBlue}, -- Folded + }) + exec([[ + func MyTabLine() + return '%S' + endfunc + + set showcmd + set showtabline=2 + set tabline=%!MyTabLine() + set showcmdloc=tabline + call setline(1, ['a', 'b', 'c']) + set foldopen+=jump + 1,2fold + 3 + ]]) + + feed('g') + screen:expect([[ + {3:g }| + {5:+-- 2 lines: a···································}| + ^c | + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + + -- typing "gg" should open the fold + feed('g') + screen:expect([[ + {3: }| + ^a | + b | + c | + {0:~ }| + {0:~ }| + | + ]]) + + feed('<C-V>Gl') + screen:expect([[ + {3:3x2 }| + {1:a} | + {1:b} | + {1:c}^ | + {0:~ }| + {0:~ }| + {2:-- VISUAL BLOCK --} | + ]]) + + feed('<Esc>1234') + screen:expect([[ + {3:1234 }| + a | + b | + ^c | + {0:~ }| + {0:~ }| + | + ]]) + + feed('<Esc>:set tabline=<CR>') + feed(':<CR>') + feed('1234') + screen:expect([[ + {2: + [No Name] }{3: }{4:1234}{3: }| + a | + b | + ^c | + {0:~ }| + {0:~ }| + : | + ]]) + end) +end) diff --git a/test/functional/legacy/vimscript_spec.lua b/test/functional/legacy/vimscript_spec.lua new file mode 100644 index 0000000000..f59a87f824 --- /dev/null +++ b/test/functional/legacy/vimscript_spec.lua @@ -0,0 +1,90 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') +local clear = helpers.clear +local exec = helpers.exec +local feed = helpers.feed +local meths = helpers.meths + +before_each(clear) + +describe('Vim script', function() + -- oldtest: Test_deep_nest() + it('Error when if/for/while/try/function is nested too deep',function() + local screen = Screen.new(80, 24) + screen:attach() + meths.set_option('laststatus', 2) + exec([[ + " Deep nesting of if ... endif + func Test1() + let @a = join(repeat(['if v:true'], 51), "\n") + let @a ..= "\n" + let @a ..= join(repeat(['endif'], 51), "\n") + @a + let @a = '' + endfunc + + " Deep nesting of for ... endfor + func Test2() + let @a = join(repeat(['for i in [1]'], 51), "\n") + let @a ..= "\n" + let @a ..= join(repeat(['endfor'], 51), "\n") + @a + let @a = '' + endfunc + + " Deep nesting of while ... endwhile + func Test3() + let @a = join(repeat(['while v:true'], 51), "\n") + let @a ..= "\n" + let @a ..= join(repeat(['endwhile'], 51), "\n") + @a + let @a = '' + endfunc + + " Deep nesting of try ... endtry + func Test4() + let @a = join(repeat(['try'], 51), "\n") + let @a ..= "\necho v:true\n" + let @a ..= join(repeat(['endtry'], 51), "\n") + @a + let @a = '' + endfunc + + " Deep nesting of function ... endfunction + func Test5() + let @a = join(repeat(['function X()'], 51), "\n") + let @a ..= "\necho v:true\n" + let @a ..= join(repeat(['endfunction'], 51), "\n") + @a + let @a = '' + endfunc + ]]) + screen:expect({any = '%[No Name%]'}) + feed(':call Test1()<CR>') + screen:expect({any = 'E579: '}) + feed('<C-C>') + screen:expect({any = '%[No Name%]'}) + feed(':call Test2()<CR>') + screen:expect({any = 'E585: '}) + feed('<C-C>') + screen:expect({any = '%[No Name%]'}) + feed(':call Test3()<CR>') + screen:expect({any = 'E585: '}) + feed('<C-C>') + screen:expect({any = '%[No Name%]'}) + feed(':call Test4()<CR>') + screen:expect({any = 'E601: '}) + feed('<C-C>') + screen:expect({any = '%[No Name%]'}) + feed(':call Test5()<CR>') + screen:expect({any = 'E1058: '}) + end) + + -- oldtest: Test_typed_script_var() + it('using s: with a typed command', function() + local screen = Screen.new(80, 24) + screen:attach() + feed(":echo get(s:, 'foo', 'x')\n") + screen:expect({any = 'E116: '}) + end) +end) diff --git a/test/functional/legacy/visual_mode_spec.lua b/test/functional/legacy/visual_mode_spec.lua index 8b5dd0c2dc..1a08fb4c0e 100644 --- a/test/functional/legacy/visual_mode_spec.lua +++ b/test/functional/legacy/visual_mode_spec.lua @@ -1,31 +1,28 @@ local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') -local call = helpers.call local clear = helpers.clear local feed = helpers.feed -local feed_command = helpers.feed_command -local funcs = helpers.funcs -local meths = helpers.meths -local eq = helpers.eq local exec = helpers.exec -describe('visual line mode', function() - local screen +before_each(clear) +describe('visual line mode', function() + -- oldtest: Test_visual_block_scroll() it('redraws properly after scrolling with matchparen loaded and scrolloff=1', function() - clear{args={'-u', 'NORC'}} - screen = Screen.new(30, 7) + local screen = Screen.new(30, 7) screen:attach() screen:set_default_attr_ids({ [1] = {bold = true}, [2] = {background = Screen.colors.LightGrey}, }) - eq(1, meths.get_var('loaded_matchparen')) - feed_command('set scrolloff=1') - funcs.setline(1, {'a', 'b', 'c', 'd', 'e', '', '{', '}', '{', 'f', 'g', '}'}) - call('cursor', 5, 1) + exec([[ + source $VIMRUNTIME/plugin/matchparen.vim + set scrolloff=1 + call setline(1, ['a', 'b', 'c', 'd', 'e', '', '{', '}', '{', 'f', 'g', '}']) + call cursor(5, 1) + ]]) feed('V<c-d><c-d>') screen:expect([[ @@ -41,8 +38,8 @@ describe('visual line mode', function() end) describe('visual block mode', function() + -- oldtest: Test_visual_block_with_virtualedit() it('shows selection correctly with virtualedit=block', function() - clear() local screen = Screen.new(30, 7) screen:set_default_attr_ids({ [1] = {bold = true}, -- ModeMsg diff --git a/test/functional/legacy/window_cmd_spec.lua b/test/functional/legacy/window_cmd_spec.lua index 8b89c55f5b..0e9775060d 100644 --- a/test/functional/legacy/window_cmd_spec.lua +++ b/test/functional/legacy/window_cmd_spec.lua @@ -6,9 +6,11 @@ local exec_lua = helpers.exec_lua local feed = helpers.feed describe('splitkeep', function() - local screen = Screen.new() + local screen + before_each(function() clear('--cmd', 'set splitkeep=screen') + screen = Screen.new() screen:attach() end) @@ -193,4 +195,33 @@ describe('splitkeep', function() :quit | ]]) end) + + -- oldtest: Test_splitkeep_status() + it('does not scroll when split in callback', function() + exec([[ + call setline(1, ['a', 'b', 'c']) + set nomodified + set splitkeep=screen + let win = winnr() + wincmd s + wincmd j + ]]) + feed(':call win_move_statusline(win, 1)<CR>') + screen:expect([[ + a | + b | + c | + ~ | + ~ | + ~ | + ~ | + [No Name] | + ^a | + b | + c | + ~ | + [No Name] | + | + ]]) + end) end) diff --git a/test/functional/lua/diagnostic_spec.lua b/test/functional/lua/diagnostic_spec.lua index 28a8679205..d364986ad7 100644 --- a/test/functional/lua/diagnostic_spec.lua +++ b/test/functional/lua/diagnostic_spec.lua @@ -16,7 +16,7 @@ describe('vim.diagnostic', function() exec_lua [[ require('vim.diagnostic') - function make_diagnostic(msg, x1, y1, x2, y2, severity, source) + function make_diagnostic(msg, x1, y1, x2, y2, severity, source, code) return { lnum = x1, col = y1, @@ -25,23 +25,24 @@ describe('vim.diagnostic', function() message = msg, severity = severity, source = source, + code = code, } end - function make_error(msg, x1, y1, x2, y2, source) - return make_diagnostic(msg, x1, y1, x2, y2, vim.diagnostic.severity.ERROR, source) + function make_error(msg, x1, y1, x2, y2, source, code) + return make_diagnostic(msg, x1, y1, x2, y2, vim.diagnostic.severity.ERROR, source, code) end - function make_warning(msg, x1, y1, x2, y2, source) - return make_diagnostic(msg, x1, y1, x2, y2, vim.diagnostic.severity.WARN, source) + function make_warning(msg, x1, y1, x2, y2, source, code) + return make_diagnostic(msg, x1, y1, x2, y2, vim.diagnostic.severity.WARN, source, code) end - function make_info(msg, x1, y1, x2, y2, source) - return make_diagnostic(msg, x1, y1, x2, y2, vim.diagnostic.severity.INFO, source) + function make_info(msg, x1, y1, x2, y2, source, code) + return make_diagnostic(msg, x1, y1, x2, y2, vim.diagnostic.severity.INFO, source, code) end - function make_hint(msg, x1, y1, x2, y2, source) - return make_diagnostic(msg, x1, y1, x2, y2, vim.diagnostic.severity.HINT, source) + function make_hint(msg, x1, y1, x2, y2, source, code) + return make_diagnostic(msg, x1, y1, x2, y2, vim.diagnostic.severity.HINT, source, code) end function count_diagnostics(bufnr, severity, namespace) @@ -89,20 +90,25 @@ describe('vim.diagnostic', function() 'DiagnosticFloatingError', 'DiagnosticFloatingHint', 'DiagnosticFloatingInfo', + 'DiagnosticFloatingOk', 'DiagnosticFloatingWarn', 'DiagnosticHint', 'DiagnosticInfo', + 'DiagnosticOk', 'DiagnosticSignError', 'DiagnosticSignHint', 'DiagnosticSignInfo', + 'DiagnosticSignOk', 'DiagnosticSignWarn', 'DiagnosticUnderlineError', 'DiagnosticUnderlineHint', 'DiagnosticUnderlineInfo', + 'DiagnosticUnderlineOk', 'DiagnosticUnderlineWarn', 'DiagnosticVirtualTextError', 'DiagnosticVirtualTextHint', 'DiagnosticVirtualTextInfo', + 'DiagnosticVirtualTextOk', 'DiagnosticVirtualTextWarn', 'DiagnosticWarn', }, exec_lua([[return vim.fn.getcompletion('Diagnostic', 'highlight')]])) @@ -159,6 +165,24 @@ describe('vim.diagnostic', function() ]]) end) + it('removes diagnostic from stale cache on reset', function() + local diagnostics = exec_lua [[ + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { + make_error('Diagnostic #1', 1, 1, 1, 1), + make_error('Diagnostic #2', 2, 1, 2, 1), + }) + local other_bufnr = vim.fn.bufadd('test | test') + vim.cmd('noautocmd bwipeout! ' .. diagnostic_bufnr) + return vim.diagnostic.get(diagnostic_bufnr) + ]] + eq(2, #diagnostics) + diagnostics = exec_lua [[ + vim.diagnostic.reset() + return vim.diagnostic.get() + ]] + eq(0, #diagnostics) + end) + it('resolves buffer number 0 to the current buffer', function() eq(2, exec_lua [[ vim.api.nvim_set_current_buf(diagnostic_bufnr) @@ -1160,6 +1184,44 @@ end) eq(" some_linter: 👀 Warning", result[1][2][1]) eq(" another_linter: 🔥 Error", result[2][2][1]) end) + + it('can add a suffix to virtual text', function() + eq(' Some error ✘', exec_lua [[ + local diagnostics = { + make_error('Some error', 0, 0, 0, 0), + } + + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics, { + underline = false, + virtual_text = { + prefix = '', + suffix = ' ✘', + } + }) + + local extmarks = get_virt_text_extmarks(diagnostic_ns) + local virt_text = extmarks[1][4].virt_text[2][1] + return virt_text + ]]) + + eq(' Some error [err-code]', exec_lua [[ + local diagnostics = { + make_error('Some error', 0, 0, 0, 0, nil, 'err-code'), + } + + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics, { + underline = false, + virtual_text = { + prefix = '', + suffix = function(diag) return string.format(' [%s]', diag.code) end, + } + }) + + local extmarks = get_virt_text_extmarks(diagnostic_ns) + local virt_text = extmarks[1][4].virt_text[2][1] + return virt_text + ]]) + end) end) describe('set()', function() @@ -1776,10 +1838,55 @@ end) return lines ]]) - eq("Error executing lua: .../diagnostic.lua:0: prefix: expected 'string' or 'table' or 'function', got 42", + eq(".../diagnostic.lua:0: prefix: expected string|table|function, got number", pcall_err(exec_lua, [[ vim.diagnostic.open_float({ prefix = 42 }) ]])) end) + it('can add a suffix to diagnostics', function() + -- Default is to render the diagnostic error code + eq({'1. Syntax error [code-x]', '2. Some warning [code-y]'}, exec_lua [[ + local diagnostics = { + make_error("Syntax error", 0, 1, 0, 3, nil, "code-x"), + make_warning("Some warning", 1, 1, 1, 3, nil, "code-y"), + } + vim.api.nvim_win_set_buf(0, diagnostic_bufnr) + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) + local float_bufnr, winnr = vim.diagnostic.open_float({header = false, scope = "buffer"}) + local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) + vim.api.nvim_win_close(winnr, true) + return lines + ]]) + + eq({'1. Syntax error', '2. Some warning'}, exec_lua [[ + local diagnostics = { + make_error("Syntax error", 0, 1, 0, 3, nil, "code-x"), + make_warning("Some warning", 1, 1, 1, 3, nil, "code-y"), + } + vim.api.nvim_win_set_buf(0, diagnostic_bufnr) + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) + local float_bufnr, winnr = vim.diagnostic.open_float({header = false, scope = "buffer", suffix = ""}) + local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) + vim.api.nvim_win_close(winnr, true) + return lines + ]]) + + -- Suffix is rendered on the last line of a multiline diagnostic + eq({'1. Syntax error', ' More context [code-x]'}, exec_lua [[ + local diagnostics = { + make_error("Syntax error\nMore context", 0, 1, 0, 3, nil, "code-x"), + } + vim.api.nvim_win_set_buf(0, diagnostic_bufnr) + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) + local float_bufnr, winnr = vim.diagnostic.open_float({header = false, scope = "buffer"}) + local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) + vim.api.nvim_win_close(winnr, true) + return lines + ]]) + + eq(".../diagnostic.lua:0: suffix: expected string|table|function, got number", + pcall_err(exec_lua, [[ vim.diagnostic.open_float({ suffix = 42 }) ]])) + end) + it('works with the old signature', function() eq({'1. Syntax error'}, exec_lua [[ local diagnostics = { @@ -2017,5 +2124,31 @@ end) return vim.g.diagnostic_autocmd_triggered == diagnostic_bufnr ]]) end) + + it("checks if diagnostics are disabled in a buffer", function() + eq({true, true, true , true}, exec_lua [[ + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { + make_error('Diagnostic #1', 1, 1, 1, 1), + }) + vim.api.nvim_set_current_buf(diagnostic_bufnr) + vim.diagnostic.disable() + return { + vim.diagnostic.is_disabled(), + vim.diagnostic.is_disabled(diagnostic_bufnr), + vim.diagnostic.is_disabled(diagnostic_bufnr, diagnostic_ns), + vim.diagnostic.is_disabled(_, diagnostic_ns), + } + ]]) + + eq({false, false, false , false}, exec_lua [[ + vim.diagnostic.enable() + return { + vim.diagnostic.is_disabled(), + vim.diagnostic.is_disabled(diagnostic_bufnr), + vim.diagnostic.is_disabled(diagnostic_bufnr, diagnostic_ns), + vim.diagnostic.is_disabled(_, diagnostic_ns), + } + ]]) + end) end) end) diff --git a/test/functional/lua/ffi_spec.lua b/test/functional/lua/ffi_spec.lua index 80c01a2b8c..18b13a8959 100644 --- a/test/functional/lua/ffi_spec.lua +++ b/test/functional/lua/ffi_spec.lua @@ -29,32 +29,37 @@ describe('ffi.cdef', function() typedef struct window_S win_T; typedef struct {} stl_hlrec_t; typedef struct {} StlClickRecord; + typedef struct {} statuscol_T; typedef struct {} Error; win_T *find_window_by_handle(int Window, Error *err); int build_stl_str_hl( win_T *wp, - char_u *out, + char *out, size_t outlen, - char_u *fmt, - int use_sandbox, - char_u fillchar, + char *fmt, + char *opt_name, + int opt_scope, + int fillchar, int maxwidth, stl_hlrec_t **hltab, - StlClickRecord **tabtab + StlClickRecord **tabtab, + statuscol_T *scp ); ]] return ffi.C.build_stl_str_hl( ffi.C.find_window_by_handle(0, ffi.new('Error')), - ffi.new('char_u[1024]'), + ffi.new('char[1024]'), 1024, - ffi.cast('char_u*', 'StatusLineOfLength20'), + ffi.cast('char*', 'StatusLineOfLength20'), + nil, 0, 0, 0, nil, + nil, nil ) ]=]) diff --git a/test/functional/lua/fs_spec.lua b/test/functional/lua/fs_spec.lua index 3123ec324c..642d36f63a 100644 --- a/test/functional/lua/fs_spec.lua +++ b/test/functional/lua/fs_spec.lua @@ -1,4 +1,5 @@ local helpers = require('test.functional.helpers')(after_each) +local lfs = require('lfs') local clear = helpers.clear local exec_lua = helpers.exec_lua @@ -7,10 +8,43 @@ local mkdir_p = helpers.mkdir_p local rmdir = helpers.rmdir local nvim_dir = helpers.nvim_dir local test_build_dir = helpers.test_build_dir -local iswin = helpers.iswin local nvim_prog = helpers.nvim_prog +local is_os = helpers.is_os -local nvim_prog_basename = iswin() and 'nvim.exe' or 'nvim' +local nvim_prog_basename = is_os('win') and 'nvim.exe' or 'nvim' + +local test_basename_dirname_eq = { + '~/foo/', + '~/foo', + '~/foo/bar.lua', + 'foo.lua', + ' ', + '', + '.', + '..', + '../', + '~', + '/usr/bin', + '/usr/bin/gcc', + '/', + '/usr/', + '/usr', + 'c:/usr', + 'c:/', + 'c:', + 'c:/users/foo', + 'c:/users/foo/bar.lua', + 'c:/users/foo/bar/../', +} + +local tests_windows_paths = { + 'c:\\usr', + 'c:\\', + 'c:', + 'c:\\users\\foo', + 'c:\\users\\foo\\bar.lua', + 'c:\\users\\foo\\bar\\..\\', +} before_each(clear) @@ -41,19 +75,73 @@ describe('vim.fs', function() local nvim_dir = ... return vim.fs.dirname(nvim_dir) ]], nvim_dir)) + + local function test_paths(paths) + for _, path in ipairs(paths) do + eq( + exec_lua([[ + local path = ... + return vim.fn.fnamemodify(path,':h'):gsub('\\', '/') + ]], path), + exec_lua([[ + local path = ... + return vim.fs.dirname(path) + ]], path), + path + ) + end + end + + test_paths(test_basename_dirname_eq) + if is_os('win') then + test_paths(tests_windows_paths) + end end) end) describe('basename()', function() it('works', function() + eq(nvim_prog_basename, exec_lua([[ local nvim_prog = ... return vim.fs.basename(nvim_prog) ]], nvim_prog)) + + local function test_paths(paths) + for _, path in ipairs(paths) do + eq( + exec_lua([[ + local path = ... + return vim.fn.fnamemodify(path,':t'):gsub('\\', '/') + ]], path), + exec_lua([[ + local path = ... + return vim.fs.basename(path) + ]], path), + path + ) + end + end + + test_paths(test_basename_dirname_eq) + if is_os('win') then + test_paths(tests_windows_paths) + end end) end) describe('dir()', function() + before_each(function() + lfs.mkdir('testd') + lfs.mkdir('testd/a') + lfs.mkdir('testd/a/b') + lfs.mkdir('testd/a/b/c') + end) + + after_each(function() + rmdir('testd') + end) + it('works', function() eq(true, exec_lua([[ local dir, nvim = ... @@ -65,6 +153,71 @@ describe('vim.fs', function() return false ]], nvim_dir, nvim_prog_basename)) end) + + it('works with opts.depth and opts.skip', function() + io.open('testd/a1', 'w'):close() + io.open('testd/b1', 'w'):close() + io.open('testd/c1', 'w'):close() + io.open('testd/a/a2', 'w'):close() + io.open('testd/a/b2', 'w'):close() + io.open('testd/a/c2', 'w'):close() + io.open('testd/a/b/a3', 'w'):close() + io.open('testd/a/b/b3', 'w'):close() + io.open('testd/a/b/c3', 'w'):close() + io.open('testd/a/b/c/a4', 'w'):close() + io.open('testd/a/b/c/b4', 'w'):close() + io.open('testd/a/b/c/c4', 'w'):close() + + local function run(dir, depth, skip) + local r = exec_lua([[ + local dir, depth, skip = ... + local r = {} + local skip_f + if skip then + skip_f = function(n) + if vim.tbl_contains(skip or {}, n) then + return false + end + end + end + for name, type_ in vim.fs.dir(dir, { depth = depth, skip = skip_f }) do + r[name] = type_ + end + return r + ]], dir, depth, skip) + return r + end + + local exp = {} + + exp['a1'] = 'file' + exp['b1'] = 'file' + exp['c1'] = 'file' + exp['a'] = 'directory' + + eq(exp, run('testd', 1)) + + exp['a/a2'] = 'file' + exp['a/b2'] = 'file' + exp['a/c2'] = 'file' + exp['a/b'] = 'directory' + + eq(exp, run('testd', 2)) + + exp['a/b/a3'] = 'file' + exp['a/b/b3'] = 'file' + exp['a/b/c3'] = 'file' + exp['a/b/c'] = 'directory' + + eq(exp, run('testd', 3)) + eq(exp, run('testd', 999, {'a/b/c'})) + + exp['a/b/c/a4'] = 'file' + exp['a/b/c/b4'] = 'file' + exp['a/b/c/c4'] = 'file' + + eq(exp, run('testd', 999)) + end) end) describe('find()', function() @@ -77,6 +230,11 @@ describe('vim.fs', function() local dir, nvim = ... return vim.fs.find(nvim, { path = dir, type = 'file' }) ]], test_build_dir, nvim_prog_basename)) + eq({nvim_dir}, exec_lua([[ + local dir = ... + local parent, name = dir:match('^(.*/)([^/]+)$') + return vim.fs.find(name, { path = parent, upward = true, type = 'directory' }) + ]], nvim_dir)) end) it('accepts predicate as names', function() @@ -102,7 +260,7 @@ describe('vim.fs', function() eq('C:/Users/jdoe', exec_lua [[ return vim.fs.normalize('C:\\Users\\jdoe') ]]) end) it('works with ~', function() - if iswin() then + if is_os('win') then pending([[$HOME does not exist on Windows ¯\_(ツ)_/¯]]) end eq(os.getenv('HOME') .. '/src/foo', exec_lua [[ return vim.fs.normalize('~/src/foo') ]]) diff --git a/test/functional/lua/help_spec.lua b/test/functional/lua/help_spec.lua index 251275b5cc..b396e2ba30 100644 --- a/test/functional/lua/help_spec.lua +++ b/test/functional/lua/help_spec.lua @@ -19,10 +19,11 @@ describe(':help docs', function() local rv = exec_lua([[return require('scripts.gen_help_html').validate('./build/runtime/doc')]]) -- Check that we actually found helpfiles. ok(rv.helpfiles > 100, '>100 :help files', rv.helpfiles) + eq({}, rv.invalid_links, 'invalid tags in :help docs') + eq({}, rv.invalid_urls, 'invalid URLs in :help docs') -- Check that parse errors did not increase wildly. -- TODO: Fix all parse errors in :help files. - ok(rv.err_count < 150, '<150 parse errors', rv.err_count) - eq({}, rv.invalid_links, exec_lua([[return 'found invalid :help tag links:\n'..vim.inspect(...)]], rv.invalid_links)) + ok(rv.err_count < 250, '<250 parse errors', rv.err_count) end) it('gen_help_html.lua generates HTML', function() @@ -43,7 +44,7 @@ describe(':help docs', function() tmpdir ) eq(4, #rv.helpfiles) - ok(rv.err_count <= 1, '<=1 parse errors', rv.err_count) - eq({}, rv.invalid_links, exec_lua([[return 'found invalid :help tag links:\n'..vim.inspect(...)]], rv.invalid_links)) + eq(0, rv.err_count, 'parse errors in :help docs') + eq({}, rv.invalid_links, 'invalid tags in :help docs') end) end) diff --git a/test/functional/lua/inspector_spec.lua b/test/functional/lua/inspector_spec.lua new file mode 100644 index 0000000000..5e488bb082 --- /dev/null +++ b/test/functional/lua/inspector_spec.lua @@ -0,0 +1,56 @@ +local helpers = require('test.functional.helpers')(after_each) +local exec_lua = helpers.exec_lua +local eq = helpers.eq +local eval = helpers.eval +local clear = helpers.clear + +describe('vim.inspect_pos', function() + before_each(function() + clear() + end) + + it('it returns items', function() + local ret = exec_lua([[ + local buf = vim.api.nvim_create_buf(true, false) + vim.api.nvim_set_current_buf(buf) + vim.api.nvim_buf_set_lines(0, 0, -1, false, {"local a = 123"}) + vim.api.nvim_buf_set_option(buf, "filetype", "lua") + vim.cmd("syntax on") + return {buf, vim.inspect_pos(0, 0, 10)} + ]]) + local buf, items = unpack(ret) + eq('', eval('v:errmsg')) + eq({ + buffer = buf, + col = 10, + row = 0, + extmarks = {}, + treesitter = {}, + semantic_tokens = {}, + syntax = { + { + hl_group = 'luaNumber', + hl_group_link = 'Constant', + }, + }, + }, items) + end) +end) + +describe('vim.show_pos', function() + before_each(function() + clear() + end) + + it('it does not error', function() + exec_lua([[ + local buf = vim.api.nvim_create_buf(true, false) + vim.api.nvim_set_current_buf(buf) + vim.api.nvim_buf_set_lines(0, 0, -1, false, {"local a = 123"}) + vim.api.nvim_buf_set_option(buf, "filetype", "lua") + vim.cmd("syntax on") + return {buf, vim.show_pos(0, 0, 10)} + ]]) + eq('', eval('v:errmsg')) + end) +end) diff --git a/test/functional/lua/overrides_spec.lua b/test/functional/lua/overrides_spec.lua index 32c1615a45..3f107811ae 100644 --- a/test/functional/lua/overrides_spec.lua +++ b/test/functional/lua/overrides_spec.lua @@ -8,12 +8,12 @@ local feed = helpers.feed local clear = helpers.clear local funcs = helpers.funcs local meths = helpers.meths -local iswin = helpers.iswin local command = helpers.command local write_file = helpers.write_file local exec_capture = helpers.exec_capture local exec_lua = helpers.exec_lua local pcall_err = helpers.pcall_err +local is_os = helpers.is_os local screen @@ -135,7 +135,7 @@ describe('print', function() print("very slow") vim.api.nvim_command("sleep 1m") -- force deferred event processing end - ]], (iswin() and "timeout 1") or "sleep 0.1") + ]], (is_os('win') and "timeout 1") or "sleep 0.1") eq('very slow\nvery fast', exec_capture('lua test()')) end) end) diff --git a/test/functional/lua/runtime_spec.lua b/test/functional/lua/runtime_spec.lua index e9c34c9228..b659f2eacb 100644 --- a/test/functional/lua/runtime_spec.lua +++ b/test/functional/lua/runtime_spec.lua @@ -4,6 +4,7 @@ local clear = helpers.clear local eq = helpers.eq local eval = helpers.eval local exec = helpers.exec +local funcs = helpers.funcs local mkdir_p = helpers.mkdir_p local rmdir = helpers.rmdir local write_file = helpers.write_file @@ -29,6 +30,7 @@ describe('runtime:', function() after_each(function() rmdir(plug_dir) + exec('bwipe!') end) describe('colors', function() @@ -39,6 +41,8 @@ describe('runtime:', function() mkdir_p(colorscheme_folder) write_file(colorscheme_file, [[vim.g.lua_colorscheme = 1]]) + eq({'new_colorscheme'}, funcs.getcompletion('new_c', 'color')) + exec('colorscheme new_colorscheme') eq(1, eval('g:lua_colorscheme')) @@ -64,23 +68,25 @@ describe('runtime:', function() it('loads lua compilers', function() local compiler_file = compiler_folder .. sep .. 'new_compiler.lua' mkdir_p(compiler_folder) - write_file(compiler_file, [[vim.g.lua_compiler = 1]]) + write_file(compiler_file, [[vim.b.lua_compiler = 1]]) + + eq({'new_compiler'}, funcs.getcompletion('new_c', 'compiler')) exec('compiler new_compiler') - eq(1, eval('g:lua_compiler')) + eq(1, eval('b:lua_compiler')) rmdir(compiler_folder) end) it('loads vim compilers when both lua and vim version exist', function() local compiler_file = compiler_folder .. sep .. 'new_compiler' mkdir_p(compiler_folder) - write_file(compiler_file..'.vim', [[let g:compiler = 'vim']]) - write_file(compiler_file..'.lua', [[vim.g.compiler = 'lua']]) + write_file(compiler_file..'.vim', [[let b:compiler = 'vim']]) + write_file(compiler_file..'.lua', [[vim.b.compiler = 'lua']]) exec('compiler new_compiler') - eq('vim', eval('g:compiler')) + eq('vim', eval('b:compiler')) rmdir(compiler_folder) end) end) @@ -91,10 +97,12 @@ describe('runtime:', function() it('loads lua ftplugins', function() local ftplugin_file = table.concat({ftplugin_folder , 'new-ft.lua'}, sep) mkdir_p(ftplugin_folder) - write_file(ftplugin_file , [[vim.g.lua_ftplugin = 1]]) + write_file(ftplugin_file , [[vim.b.lua_ftplugin = 1]]) + + eq({'new-ft'}, funcs.getcompletion('new-f', 'filetype')) exec [[set filetype=new-ft]] - eq(1, eval('g:lua_ftplugin')) + eq(1, eval('b:lua_ftplugin')) rmdir(ftplugin_folder) end) end) @@ -105,10 +113,12 @@ describe('runtime:', function() it('loads lua indents', function() local indent_file = table.concat({indent_folder , 'new-ft.lua'}, sep) mkdir_p(indent_folder) - write_file(indent_file , [[vim.g.lua_indent = 1]]) + write_file(indent_file , [[vim.b.lua_indent = 1]]) + + eq({'new-ft'}, funcs.getcompletion('new-f', 'filetype')) exec [[set filetype=new-ft]] - eq(1, eval('g:lua_indent')) + eq(1, eval('b:lua_indent')) rmdir(indent_folder) end) end) @@ -116,24 +126,32 @@ describe('runtime:', function() describe('syntax', function() local syntax_folder = table.concat({plug_dir, 'syntax'}, sep) - it('loads lua syntaxes on filetype change', function() + before_each(function() local syntax_file = table.concat({syntax_folder , 'my-lang.lua'}, sep) mkdir_p(syntax_folder) - write_file(syntax_file , [[vim.g.lua_syntax = 1]]) + write_file(syntax_file , [[vim.b.current_syntax = 'my-lang']]) + exec([[let b:current_syntax = '']]) + end) + it('loads lua syntaxes on filetype change', function() exec('set filetype=my-lang') - eq(1, eval('g:lua_syntax')) - rmdir(syntax_folder) + eq('my-lang', eval('b:current_syntax')) end) it('loads lua syntaxes on syntax change', function() - local syntax_file = table.concat({syntax_folder , 'my-lang.lua'}, sep) - mkdir_p(syntax_folder) - write_file(syntax_file , [[vim.g.lua_syntax = 5]]) - exec('set syntax=my-lang') - eq(5, eval('g:lua_syntax')) - rmdir(syntax_folder) + eq('my-lang', eval('b:current_syntax')) + end) + + it('loads lua syntaxes for :ownsyntax', function() + exec('ownsyntax my-lang') + eq('my-lang', eval('w:current_syntax')) + eq('', eval('b:current_syntax')) + end) + + it('lua syntaxes are included in cmdline completion', function() + eq({'my-lang'}, funcs.getcompletion('my-l', 'filetype')) + eq({'my-lang'}, funcs.getcompletion('my-l', 'syntax')) end) end) diff --git a/test/functional/lua/secure_spec.lua b/test/functional/lua/secure_spec.lua new file mode 100644 index 0000000000..2647b2be46 --- /dev/null +++ b/test/functional/lua/secure_spec.lua @@ -0,0 +1,284 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') + +local eq = helpers.eq +local clear = helpers.clear +local command = helpers.command +local pathsep = helpers.get_pathsep() +local is_os = helpers.is_os +local curbufmeths = helpers.curbufmeths +local exec_lua = helpers.exec_lua +local feed_command = helpers.feed_command +local feed = helpers.feed +local funcs = helpers.funcs +local pcall_err = helpers.pcall_err +local matches = helpers.matches + +describe('vim.secure', function() + describe('read()', function() + local xstate = 'Xstate' + + setup(function() + helpers.mkdir_p(xstate .. pathsep .. (is_os('win') and 'nvim-data' or 'nvim')) + end) + + teardown(function() + helpers.rmdir(xstate) + end) + + before_each(function() + helpers.write_file('Xfile', [[ + let g:foobar = 42 + ]]) + clear{env={XDG_STATE_HOME=xstate}} + end) + + after_each(function() + os.remove('Xfile') + helpers.rmdir(xstate) + end) + + it('works', function() + local screen = Screen.new(80, 8) + screen:attach() + screen:set_default_attr_ids({ + [1] = {bold = true, foreground = Screen.colors.Blue1}, + [2] = {bold = true, reverse = true}, + [3] = {bold = true, foreground = Screen.colors.SeaGreen}, + [4] = {reverse = true}, + }) + + local cwd = funcs.getcwd() + + -- Need to use feed_command instead of exec_lua because of the confirmation prompt + feed_command([[lua vim.secure.read('Xfile')]]) + screen:expect{grid=[[ + | + {1:~ }| + {1:~ }| + {1:~ }| + {2: }| + :lua vim.secure.read('Xfile') | + {3:]] .. cwd .. pathsep .. [[Xfile is untrusted}{MATCH:%s+}| + {3:[i]gnore, (v)iew, (d)eny, (a)llow: }^ | + ]]} + feed('d') + screen:expect{grid=[[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]} + + local trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq(string.format('! %s', cwd .. pathsep .. 'Xfile'), vim.trim(trust)) + eq(helpers.NIL, exec_lua([[return vim.secure.read('Xfile')]])) + + os.remove(funcs.stdpath('state') .. pathsep .. 'trust') + + feed_command([[lua vim.secure.read('Xfile')]]) + screen:expect{grid=[[ + | + {1:~ }| + {1:~ }| + {1:~ }| + {2: }| + :lua vim.secure.read('Xfile') | + {3:]] .. cwd .. pathsep .. [[Xfile is untrusted}{MATCH:%s+}| + {3:[i]gnore, (v)iew, (d)eny, (a)llow: }^ | + ]]} + feed('a') + screen:expect{grid=[[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]} + + local hash = funcs.sha256(helpers.read_file('Xfile')) + trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq(string.format('%s %s', hash, cwd .. pathsep .. 'Xfile'), vim.trim(trust)) + eq(helpers.NIL, exec_lua([[vim.secure.read('Xfile')]])) + + os.remove(funcs.stdpath('state') .. pathsep .. 'trust') + + feed_command([[lua vim.secure.read('Xfile')]]) + screen:expect{grid=[[ + | + {1:~ }| + {1:~ }| + {1:~ }| + {2: }| + :lua vim.secure.read('Xfile') | + {3:]] .. cwd .. pathsep .. [[Xfile is untrusted}{MATCH:%s+}| + {3:[i]gnore, (v)iew, (d)eny, (a)llow: }^ | + ]]} + feed('i') + screen:expect{grid=[[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]} + + -- Trust database is not updated + trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq(nil, trust) + + feed_command([[lua vim.secure.read('Xfile')]]) + screen:expect{grid=[[ + | + {1:~ }| + {1:~ }| + {1:~ }| + {2: }| + :lua vim.secure.read('Xfile') | + {3:]] .. cwd .. pathsep .. [[Xfile is untrusted}{MATCH:%s+}| + {3:[i]gnore, (v)iew, (d)eny, (a)llow: }^ | + ]]} + feed('v') + screen:expect{grid=[[ + ^let g:foobar = 42 | + {1:~ }| + {1:~ }| + {2:]] .. funcs.fnamemodify(cwd, ':~') .. pathsep .. [[Xfile [RO]{MATCH:%s+}| + | + {1:~ }| + {4:[No Name] }| + | + ]]} + + -- Trust database is not updated + trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq(nil, trust) + + -- Cannot write file + pcall_err(command, 'write') + eq(true, curbufmeths.get_option('readonly')) + end) + end) + + describe('trust()', function() + local xstate = 'Xstate' + + setup(function() + helpers.mkdir_p(xstate .. pathsep .. (is_os('win') and 'nvim-data' or 'nvim')) + end) + + teardown(function() + helpers.rmdir(xstate) + end) + + before_each(function() + helpers.write_file('test_file', 'test') + end) + + after_each(function() + os.remove('test_file') + end) + + it('returns error when passing both path and bufnr', function() + matches('"path" and "bufnr" are mutually exclusive', + pcall_err(exec_lua, [[vim.secure.trust({action='deny', bufnr=0, path='test_file'})]])) + end) + + it('returns error when passing neither path or bufnr', function() + matches('one of "path" or "bufnr" is required', + pcall_err(exec_lua, [[vim.secure.trust({action='deny'})]])) + end) + + it('trust then deny then remove a file using bufnr', function() + local cwd = funcs.getcwd() + local hash = funcs.sha256(helpers.read_file('test_file')) + local full_path = cwd .. pathsep .. 'test_file' + + command('edit test_file') + eq({true, full_path}, exec_lua([[return {vim.secure.trust({action='allow', bufnr=0})}]])) + local trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq(string.format('%s %s', hash, full_path), vim.trim(trust)) + + eq({true, full_path}, exec_lua([[return {vim.secure.trust({action='deny', bufnr=0})}]])) + trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq(string.format('! %s', full_path), vim.trim(trust)) + + eq({true, full_path}, exec_lua([[return {vim.secure.trust({action='remove', bufnr=0})}]])) + trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq('', vim.trim(trust)) + end) + + it('deny then trust then remove a file using bufnr', function() + local cwd = funcs.getcwd() + local hash = funcs.sha256(helpers.read_file('test_file')) + local full_path = cwd .. pathsep .. 'test_file' + + command('edit test_file') + eq({true, full_path}, exec_lua([[return {vim.secure.trust({action='deny', bufnr=0})}]])) + local trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq(string.format('! %s', full_path), vim.trim(trust)) + + eq({true, full_path}, exec_lua([[return {vim.secure.trust({action='allow', bufnr=0})}]])) + trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq(string.format('%s %s', hash, full_path), vim.trim(trust)) + + eq({true, full_path}, exec_lua([[return {vim.secure.trust({action='remove', bufnr=0})}]])) + trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq('', vim.trim(trust)) + end) + + it('trust using bufnr then deny then remove a file using path', function() + local cwd = funcs.getcwd() + local hash = funcs.sha256(helpers.read_file('test_file')) + local full_path = cwd .. pathsep .. 'test_file' + + command('edit test_file') + eq({true, full_path}, exec_lua([[return {vim.secure.trust({action='allow', bufnr=0})}]])) + local trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq(string.format('%s %s', hash, full_path), vim.trim(trust)) + + eq({true, full_path}, exec_lua([[return {vim.secure.trust({action='deny', path='test_file'})}]])) + trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq(string.format('! %s', full_path), vim.trim(trust)) + + eq({true, full_path}, exec_lua([[return {vim.secure.trust({action='remove', path='test_file'})}]])) + trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq('', vim.trim(trust)) + end) + + it('deny then trust then remove a file using bufnr', function() + local cwd = funcs.getcwd() + local hash = funcs.sha256(helpers.read_file('test_file')) + local full_path = cwd .. pathsep .. 'test_file' + + command('edit test_file') + eq({true, full_path}, exec_lua([[return {vim.secure.trust({action='deny', path='test_file'})}]])) + local trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq(string.format('! %s', full_path), vim.trim(trust)) + + eq({true, full_path}, exec_lua([[return {vim.secure.trust({action='allow', bufnr=0})}]])) + trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq(string.format('%s %s', hash, full_path), vim.trim(trust)) + + eq({true, full_path}, exec_lua([[return {vim.secure.trust({action='remove', path='test_file'})}]])) + trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq('', vim.trim(trust)) + end) + + it('trust returns error when buffer not associated to file', function() + command('new') + eq({false, 'buffer is not associated with a file'}, + exec_lua([[return {vim.secure.trust({action='allow', bufnr=0})}]])) + end) + end) +end) diff --git a/test/functional/lua/spell_spec.lua b/test/functional/lua/spell_spec.lua index 7e831f16a7..b3de6a0912 100644 --- a/test/functional/lua/spell_spec.lua +++ b/test/functional/lua/spell_spec.lua @@ -15,7 +15,7 @@ describe('vim.spell', function() end it('can handle nil', function() - eq([[Error executing lua: [string "<nvim>"]:0: bad argument #1 to 'check' (expected string)]], + eq([[bad argument #1 to 'check' (expected string)]], pcall_err(exec_lua, [[vim.spell.check(nil)]])) end) diff --git a/test/functional/lua/thread_spec.lua b/test/functional/lua/thread_spec.lua index e183ce3a57..c7f2783cf3 100644 --- a/test/functional/lua/thread_spec.lua +++ b/test/functional/lua/thread_spec.lua @@ -272,7 +272,7 @@ describe('threadpool', function() work:queue({}) ]]) - eq([[Error executing lua: [string "<nvim>"]:0: Error: thread arg not support type 'function' at 1]], + eq([[Error: thread arg not support type 'function' at 1]], status) end) diff --git a/test/functional/lua/ui_event_spec.lua b/test/functional/lua/ui_event_spec.lua index 57ffcf7b4e..6481da900e 100644 --- a/test/functional/lua/ui_event_spec.lua +++ b/test/functional/lua/ui_event_spec.lua @@ -117,4 +117,31 @@ describe('vim.ui_attach', function() }) eq(0, helpers.eval('v:shell_error')) end) + + it('can receive accurate message kinds even if they are history', function() + exec_lua([[ + vim.cmd.echomsg("'message1'") + print('message2') + vim.ui_attach(ns, { ext_messages = true }, on_event) + vim.cmd.echomsg("'message3'") + ]]) + feed(':messages<cr>') + feed('<cr>') + + local actual = exec_lua([[ + return vim.tbl_filter(function (event) + return event[1] == "msg_history_show" + end, events) + ]]) + eq({ + { + 'msg_history_show', + { + { 'echomsg', { { 0, 'message1' } } }, + { '', { { 0, 'message2' } } }, + { 'echomsg', { { 0, 'message3' } } }, + }, + }, + }, actual, inspect(actual)) + end) end) diff --git a/test/functional/lua/ui_spec.lua b/test/functional/lua/ui_spec.lua index 3fcb2dec8d..9ee99b4905 100644 --- a/test/functional/lua/ui_spec.lua +++ b/test/functional/lua/ui_spec.lua @@ -4,6 +4,7 @@ local exec_lua = helpers.exec_lua local clear = helpers.clear local feed = helpers.feed local eval = helpers.eval +local poke_eventloop = helpers.poke_eventloop describe('vim.ui', function() before_each(function() @@ -83,5 +84,50 @@ describe('vim.ui', function() feed('abcdefg<cr>') eq('abcdefg', exec_lua('return result')) end) + + it('can input empty text #18144', function() + feed(':lua vim.ui.input({}, function(input) result = input end)<cr>') + feed('<cr>') + eq('', exec_lua('return result')) + end) + + it('can input empty text with cancelreturn opt #18144', function() + feed(':lua vim.ui.input({ cancelreturn = "CANCEL" }, function(input) result = input end)<cr>') + feed('<cr>') + eq('', exec_lua('return result')) + end) + + it('can return nil when aborted with ESC #18144', function() + feed(':lua result = "on_confirm not called"<cr>') + feed(':lua vim.ui.input({}, function(input) result = input end)<cr>') + feed('Inputted Text<esc>') + -- Note: When `result == nil`, exec_lua('returns result') returns vim.NIL + eq(true, exec_lua('return (nil == result)')) + end) + + it('can return opts.cacelreturn when aborted with ESC with cancelreturn opt #18144', function() + feed(':lua result = "on_confirm not called"<cr>') + feed(':lua vim.ui.input({ cancelreturn = "CANCEL" }, function(input) result = input end)<cr>') + feed('Inputted Text<esc>') + eq('CANCEL', exec_lua('return result')) + end) + + it('can return nil when interrupted with Ctrl-C #18144', function() + feed(':lua result = "on_confirm not called"<cr>') + feed(':lua vim.ui.input({}, function(input) result = input end)<cr>') + poke_eventloop() -- This is needed because Ctrl-C flushes input + feed('Inputted Text<c-c>') + eq(true, exec_lua('return (nil == result)')) + end) + + it('can return the identical object when an arbitrary opts.cancelreturn object is given', function() + feed(':lua fn = function() return 42 end<CR>') + eq(42, exec_lua('return fn()')) + feed(':lua vim.ui.input({ cancelreturn = fn }, function(input) result = input end)<cr>') + feed('cancel<esc>') + eq(true, exec_lua('return (result == fn)')) + eq(42, exec_lua('return result()')) + end) + end) end) diff --git a/test/functional/lua/uri_spec.lua b/test/functional/lua/uri_spec.lua index 2cb0b26c6d..416e9e1f02 100644 --- a/test/functional/lua/uri_spec.lua +++ b/test/functional/lua/uri_spec.lua @@ -2,6 +2,8 @@ local helpers = require('test.functional.helpers')(after_each) local clear = helpers.clear local exec_lua = helpers.exec_lua local eq = helpers.eq +local is_os = helpers.is_os +local skip = helpers.skip local write_file = require('test.helpers').write_file describe('URI methods', function() @@ -167,7 +169,7 @@ describe('URI methods', function() describe('uri from bufnr', function() it('Windows paths should not be treated as uris', function() - if not helpers.iswin() then return end + skip(not is_os('win'), "Not applicable on non-Windows") local file = helpers.tmpname() write_file(file, 'Test content') diff --git a/test/functional/lua/vim_spec.lua b/test/functional/lua/vim_spec.lua index 47a0004183..867f366d06 100644 --- a/test/functional/lua/vim_spec.lua +++ b/test/functional/lua/vim_spec.lua @@ -2,6 +2,7 @@ local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') +local nvim_prog = helpers.nvim_prog local funcs = helpers.funcs local meths = helpers.meths local command = helpers.command @@ -22,7 +23,6 @@ local remove_trace = helpers.remove_trace local mkdir_p = helpers.mkdir_p local rmdir = helpers.rmdir local write_file = helpers.write_file -local expect_exit = helpers.expect_exit local poke_eventloop = helpers.poke_eventloop local assert_alive = helpers.assert_alive @@ -160,31 +160,31 @@ describe('lua stdlib', function() it("vim.str_utfindex/str_byteindex", function() exec_lua([[_G.test_text = "xy åäö ɧ 汉语 ↥ 🤦x🦄 å بِيَّ\000ъ"]]) - local indicies32 = {[0]=0,1,2,3,5,7,9,10,12,13,16,19,20,23,24,28,29,33,34,35,37,38,40,42,44,46,48,49,51} - local indicies16 = {[0]=0,1,2,3,5,7,9,10,12,13,16,19,20,23,24,28,28,29,33,33,34,35,37,38,40,42,44,46,48,49,51} - for i,k in pairs(indicies32) do + local indices32 = {[0]=0,1,2,3,5,7,9,10,12,13,16,19,20,23,24,28,29,33,34,35,37,38,40,42,44,46,48,49,51} + local indices16 = {[0]=0,1,2,3,5,7,9,10,12,13,16,19,20,23,24,28,28,29,33,33,34,35,37,38,40,42,44,46,48,49,51} + for i,k in pairs(indices32) do eq(k, exec_lua("return vim.str_byteindex(_G.test_text, ...)", i), i) end - for i,k in pairs(indicies16) do + for i,k in pairs(indices16) do eq(k, exec_lua("return vim.str_byteindex(_G.test_text, ..., true)", i), i) end - matches(": index out of range$", pcall_err(exec_lua, "return vim.str_byteindex(_G.test_text, ...)", #indicies32 + 1)) - matches(": index out of range$", pcall_err(exec_lua, "return vim.str_byteindex(_G.test_text, ..., true)", #indicies16 + 1)) + eq("index out of range", pcall_err(exec_lua, "return vim.str_byteindex(_G.test_text, ...)", #indices32 + 1)) + eq("index out of range", pcall_err(exec_lua, "return vim.str_byteindex(_G.test_text, ..., true)", #indices16 + 1)) local i32, i16 = 0, 0 local len = 51 for k = 0,len do - if indicies32[i32] < k then + if indices32[i32] < k then i32 = i32 + 1 end - if indicies16[i16] < k then + if indices16[i16] < k then i16 = i16 + 1 - if indicies16[i16+1] == indicies16[i16] then + if indices16[i16+1] == indices16[i16] then i16 = i16 + 1 end end eq({i32, i16}, exec_lua("return {vim.str_utfindex(_G.test_text, ...)}", k), k) end - matches(": index out of range$", pcall_err(exec_lua, "return vim.str_utfindex(_G.test_text, ...)", len + 1)) + eq("index out of range", pcall_err(exec_lua, "return vim.str_utfindex(_G.test_text, ...)", len + 1)) end) it("vim.str_utf_start", function() @@ -419,6 +419,12 @@ describe('lua stdlib', function() return getmetatable(t2) == mt ]])) + ok(exec_lua([[ + local t1 = {a = vim.NIL} + local t2 = vim.deepcopy(t1) + return t2.a == vim.NIL + ]])) + matches('Cannot deepcopy object of type thread', pcall_err(exec_lua, [[ local thread = coroutine.create(function () return 0 end) @@ -430,6 +436,8 @@ describe('lua stdlib', function() it('vim.pesc', function() eq('foo%-bar', exec_lua([[return vim.pesc('foo-bar')]])) eq('foo%%%-bar', exec_lua([[return vim.pesc(vim.pesc('foo-bar'))]])) + -- pesc() returns one result. #20751 + eq({'x'}, exec_lua([[return {vim.pesc('x')}]])) -- Validates args. matches('s: expected string, got number', @@ -504,6 +512,8 @@ describe('lua stdlib', function() eq(NIL, exec_lua("return vim.tbl_get({ unindexable = function () end }, 'unindexable', 'missing_key')")) eq(NIL, exec_lua("return vim.tbl_get({}, 'missing_key')")) eq(NIL, exec_lua("return vim.tbl_get({})")) + eq(1, exec_lua("return select('#', vim.tbl_get({}))")) + eq(1, exec_lua("return select('#', vim.tbl_get({ nested = {} }, 'nested', 'missing_key'))")) end) it('vim.tbl_extend', function() @@ -753,6 +763,20 @@ describe('lua stdlib', function() pcall_err(exec_lua, code)) end) + it('vim.spairs', function() + local res = '' + local table = { + ccc=1, + bbb=2, + ddd=3, + aaa=4 + } + for key, _ in vim.spairs(table) do + res = res .. key + end + matches('aaabbbcccddd', res) + end) + it('vim.call, vim.fn', function() eq(true, exec_lua([[return vim.call('sin', 0.0) == 0.0 ]])) eq(true, exec_lua([[return vim.fn.sin(0.0) == 0.0 ]])) @@ -1016,11 +1040,11 @@ describe('lua stdlib', function() eq('hi', funcs.luaeval "vim.g.testing") eq(123, funcs.luaeval "vim.g.other") eq(5120.1, funcs.luaeval "vim.g.floaty") - eq(NIL, funcs.luaeval "vim.g.nonexistant") + eq(NIL, funcs.luaeval "vim.g.nonexistent") eq(NIL, funcs.luaeval "vim.g.nullvar") -- lost over RPC, so test locally: eq({false, true}, exec_lua [[ - return {vim.g.nonexistant == vim.NIL, vim.g.nullvar == vim.NIL} + return {vim.g.nonexistent == vim.NIL, vim.g.nullvar == vim.NIL} ]]) eq({hello="world"}, funcs.luaeval "vim.g.to_delete") @@ -1123,12 +1147,12 @@ describe('lua stdlib', function() eq('bye', funcs.luaeval "vim.b[BUF].testing") eq(123, funcs.luaeval "vim.b.other") eq(5120.1, funcs.luaeval "vim.b.floaty") - eq(NIL, funcs.luaeval "vim.b.nonexistant") - eq(NIL, funcs.luaeval "vim.b[BUF].nonexistant") + eq(NIL, funcs.luaeval "vim.b.nonexistent") + eq(NIL, funcs.luaeval "vim.b[BUF].nonexistent") eq(NIL, funcs.luaeval "vim.b.nullvar") -- lost over RPC, so test locally: eq({false, true}, exec_lua [[ - return {vim.b.nonexistant == vim.NIL, vim.b.nullvar == vim.NIL} + return {vim.b.nonexistent == vim.NIL, vim.b.nullvar == vim.NIL} ]]) matches([[attempt to index .* nil value]], @@ -1207,7 +1231,7 @@ describe('lua stdlib', function() eq(NIL, funcs.luaeval "vim.b.testing") eq(NIL, funcs.luaeval "vim.b.other") - eq(NIL, funcs.luaeval "vim.b.nonexistant") + eq(NIL, funcs.luaeval "vim.b.nonexistent") end) it('vim.w', function() @@ -1226,8 +1250,8 @@ describe('lua stdlib', function() eq('hi', funcs.luaeval "vim.w.testing") eq('bye', funcs.luaeval "vim.w[WIN].testing") eq(123, funcs.luaeval "vim.w.other") - eq(NIL, funcs.luaeval "vim.w.nonexistant") - eq(NIL, funcs.luaeval "vim.w[WIN].nonexistant") + eq(NIL, funcs.luaeval "vim.w.nonexistent") + eq(NIL, funcs.luaeval "vim.w[WIN].nonexistent") matches([[attempt to index .* nil value]], pcall_err(exec_lua, 'return vim.w[WIN][0].testing')) @@ -1305,7 +1329,7 @@ describe('lua stdlib', function() eq(NIL, funcs.luaeval "vim.w.testing") eq(NIL, funcs.luaeval "vim.w.other") - eq(NIL, funcs.luaeval "vim.w.nonexistant") + eq(NIL, funcs.luaeval "vim.w.nonexistent") end) it('vim.t', function() @@ -1317,10 +1341,10 @@ describe('lua stdlib', function() eq('hi', funcs.luaeval "vim.t.testing") eq(123, funcs.luaeval "vim.t.other") - eq(NIL, funcs.luaeval "vim.t.nonexistant") + eq(NIL, funcs.luaeval "vim.t.nonexistent") eq('hi', funcs.luaeval "vim.t[0].testing") eq(123, funcs.luaeval "vim.t[0].other") - eq(NIL, funcs.luaeval "vim.t[0].nonexistant") + eq(NIL, funcs.luaeval "vim.t[0].nonexistent") matches([[attempt to index .* nil value]], pcall_err(exec_lua, 'return vim.t[0][0].testing')) @@ -1387,7 +1411,7 @@ describe('lua stdlib', function() eq(NIL, funcs.luaeval "vim.t.testing") eq(NIL, funcs.luaeval "vim.t.other") - eq(NIL, funcs.luaeval "vim.t.nonexistant") + eq(NIL, funcs.luaeval "vim.t.nonexistent") end) it('vim.env', function() @@ -2179,6 +2203,22 @@ describe('lua stdlib', function() end) end) -- vim.opt + describe('opt_local', function() + it('should be able to append to an array list type option', function() + eq({ "foo,bar,baz,qux" }, exec_lua [[ + local result = {} + + vim.opt.tags = "foo,bar" + vim.opt_local.tags:append("baz") + vim.opt_local.tags:append("qux") + + table.insert(result, vim.bo.tags) + + return result + ]]) + end) + end) + it('vim.cmd', function() exec_lua [[ vim.cmd "autocmd BufNew * ++once lua BUF = vim.fn.expand('<abuf>')" @@ -2227,13 +2267,19 @@ describe('lua stdlib', function() eq(true, exec_lua[[return vim.g.test]]) end) - it('vim.region', function() - insert(helpers.dedent( [[ - text tααt tααt text - text tαxt txtα tex - text tαxt tαxt - ]])) - eq({5,15}, exec_lua[[ return vim.region(0,{1,5},{1,14},'v',true)[1] ]]) + describe('vim.region', function() + it('charwise', function() + insert(helpers.dedent( [[ + text tααt tααt text + text tαxt txtα tex + text tαxt tαxt + ]])) + eq({5,15}, exec_lua[[ return vim.region(0,{1,5},{1,14},'v',true)[1] ]]) + end) + it('blockwise', function() + insert([[αα]]) + eq({0,5}, exec_lua[[ return vim.region(0,{0,0},{0,4},'3',true)[0] ]]) + end) end) describe('vim.on_key', function() @@ -2665,6 +2711,46 @@ describe('lua stdlib', function() a.nvim_buf_call(a.nvim_create_buf(false, true), function() vim.cmd "redraw" end) ]] end) + + it('can be nested crazily with hidden buffers', function() + eq(true, exec_lua([[ + local function scratch_buf_call(fn) + local buf = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_option(buf, 'cindent', true) + return vim.api.nvim_buf_call(buf, function() + return vim.api.nvim_get_current_buf() == buf + and vim.api.nvim_buf_get_option(buf, 'cindent') + and fn() + end) and vim.api.nvim_buf_delete(buf, {}) == nil + end + + return scratch_buf_call(function() + return scratch_buf_call(function() + return scratch_buf_call(function() + return scratch_buf_call(function() + return scratch_buf_call(function() + return scratch_buf_call(function() + return scratch_buf_call(function() + return scratch_buf_call(function() + return scratch_buf_call(function() + return scratch_buf_call(function() + return scratch_buf_call(function() + return scratch_buf_call(function() + return true + end) + end) + end) + end) + end) + end) + end) + end) + end) + end) + end) + end) + ]])) + end) end) describe('vim.api.nvim_win_call', function() @@ -2838,9 +2924,14 @@ describe('lua: builtin modules', function() end) - it('does not work when disabled without runtime', function() - clear{args={'--luamod-dev'}, env={VIMRUNTIME='fixtures/a'}} - expect_exit(exec_lua, [[return vim.tbl_count {x=1,y=2}]]) + it('fails when disabled without runtime', function() + clear() + command("let $VIMRUNTIME='fixtures/a'") + -- Use system([nvim,…]) instead of clear() to avoid stderr noise. #21844 + local out = funcs.system({nvim_prog, '--clean', '--luamod-dev', + [[+call nvim_exec_lua('return vim.tbl_count {x=1,y=2}')]], '+qa!'}):gsub('\r\n', '\n') + eq(1, eval('v:shell_error')) + matches("'vim%.shared' not found", out) end) end) diff --git a/test/functional/lua/xdiff_spec.lua b/test/functional/lua/xdiff_spec.lua index d55268fc78..3121ac051f 100644 --- a/test/functional/lua/xdiff_spec.lua +++ b/test/functional/lua/xdiff_spec.lua @@ -74,11 +74,13 @@ describe('xdiff bindings', function() end) it('with error callback', function() - exec_lua([[on_hunk = function(sa, ca, sb, cb) + exec_lua[[ + on_hunk = function(sa, ca, sb, cb) error('ERROR1') - end]]) + end + ]] - eq([[Error executing lua: [string "<nvim>"]:0: error running function on_hunk: [string "<nvim>"]:0: ERROR1]], + eq([[error running function on_hunk: [string "<nvim>"]:0: ERROR1]], pcall_err(exec_lua, [[vim.diff(a1, b1, {on_hunk = on_hunk})]])) end) @@ -135,19 +137,19 @@ describe('xdiff bindings', function() end) it('can handle bad args', function() - eq([[Error executing lua: [string "<nvim>"]:0: Expected at least 2 arguments]], + eq([[Expected at least 2 arguments]], pcall_err(exec_lua, [[vim.diff('a')]])) - eq([[Error executing lua: [string "<nvim>"]:0: bad argument #1 to 'diff' (expected string)]], + eq([[bad argument #1 to 'diff' (expected string)]], pcall_err(exec_lua, [[vim.diff(1, 2)]])) - eq([[Error executing lua: [string "<nvim>"]:0: bad argument #3 to 'diff' (expected table)]], + eq([[bad argument #3 to 'diff' (expected table)]], pcall_err(exec_lua, [[vim.diff('a', 'b', true)]])) - eq([[Error executing lua: [string "<nvim>"]:0: unexpected key: bad_key]], + eq([[unexpected key: bad_key]], pcall_err(exec_lua, [[vim.diff('a', 'b', { bad_key = true })]])) - eq([[Error executing lua: [string "<nvim>"]:0: on_hunk is not a function]], + eq([[on_hunk is not a function]], pcall_err(exec_lua, [[vim.diff('a', 'b', { on_hunk = true })]])) end) diff --git a/test/functional/options/autochdir_spec.lua b/test/functional/options/autochdir_spec.lua index 74959a8e76..0b6fe9533c 100644 --- a/test/functional/options/autochdir_spec.lua +++ b/test/functional/options/autochdir_spec.lua @@ -16,7 +16,7 @@ describe("'autochdir'", function() -- With 'autochdir' on, we should get the directory of tty-test.c. clear('--cmd', 'set autochdir', targetdir..'/tty-test.c') - eq(helpers.iswin() and expected:gsub('/', '\\') or expected, funcs.getcwd()) + eq(helpers.is_os('win') and expected:gsub('/', '\\') or expected, funcs.getcwd()) end) it('is not overwritten by getwinvar() call #17609',function() diff --git a/test/functional/options/defaults_spec.lua b/test/functional/options/defaults_spec.lua index 130ed73c34..84ec43f4cb 100644 --- a/test/functional/options/defaults_spec.lua +++ b/test/functional/options/defaults_spec.lua @@ -3,6 +3,7 @@ local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') local assert_alive = helpers.assert_alive +local assert_log = helpers.assert_log local meths = helpers.meths local command = helpers.command local clear = helpers.clear @@ -12,13 +13,15 @@ local eq = helpers.eq local ok = helpers.ok local funcs = helpers.funcs local insert = helpers.insert -local iswin = helpers.iswin local neq = helpers.neq local mkdir = helpers.mkdir local rmdir = helpers.rmdir local alter_slashes = helpers.alter_slashes local tbl_contains = helpers.tbl_contains local expect_exit = helpers.expect_exit +local is_os = helpers.is_os + +local testlog = 'Xtest-defaults-log' describe('startup defaults', function() describe(':filetype', function() @@ -240,7 +243,7 @@ describe('startup defaults', function() describe('$NVIM_LOG_FILE', function() local xdgdir = 'Xtest-startup-xdg-logpath' - local xdgstatedir = iswin() and xdgdir..'/nvim-data' or xdgdir..'/nvim' + local xdgstatedir = is_os('win') and xdgdir..'/nvim-data' or xdgdir..'/nvim' after_each(function() os.remove('Xtest-logpath') rmdir(xdgdir) @@ -275,11 +278,15 @@ describe('XDG defaults', function() -- Need separate describe() blocks to not run clear() twice. -- Do not put before_each() here for the same reasons. + after_each(function() + os.remove(testlog) + end) + it("&runtimepath data-dir matches stdpath('data') #9910", function() clear() local rtp = eval('split(&runtimepath, ",")') local rv = {} - local expected = (iswin() + local expected = (is_os('win') and { [[\nvim-data\site]], [[\nvim-data\site\after]], } or { '/nvim/site', '/nvim/site/after', }) @@ -327,16 +334,17 @@ describe('XDG defaults', function() return vimruntime, libdir end - local env_sep = iswin() and ';' or ':' - local data_dir = iswin() and 'nvim-data' or 'nvim' - local state_dir = iswin() and 'nvim-data' or 'nvim' - local root_path = iswin() and 'C:' or '' + local env_sep = is_os('win') and ';' or ':' + local data_dir = is_os('win') and 'nvim-data' or 'nvim' + local state_dir = is_os('win') and 'nvim-data' or 'nvim' + local root_path = is_os('win') and 'C:' or '' describe('with too long XDG variables', function() before_each(function() clear({ args_rm={'runtimepath'}, env={ + NVIM_LOG_FILE=testlog, XDG_CONFIG_HOME=(root_path .. ('/x'):rep(4096)), XDG_CONFIG_DIRS=(root_path .. ('/a'):rep(2048) .. env_sep.. root_path .. ('/b'):rep(2048) @@ -351,6 +359,10 @@ describe('XDG defaults', function() end) it('are correctly set', function() + if not is_os('win') then + assert_log('Failed to start server: no such file or directory: /X/X/X', testlog, 10) + end + local vimruntime, libdir = vimruntime_and_libdir() eq(((root_path .. ('/x'):rep(4096) .. '/nvim' @@ -412,6 +424,7 @@ describe('XDG defaults', function() clear({ args_rm={'runtimepath'}, env={ + NVIM_LOG_FILE=testlog, XDG_CONFIG_HOME='$XDG_DATA_HOME', XDG_CONFIG_DIRS='$XDG_DATA_DIRS', XDG_DATA_HOME='$XDG_CONFIG_HOME', @@ -422,6 +435,10 @@ describe('XDG defaults', function() end) it('are not expanded', function() + if not is_os('win') then + assert_log('Failed to start server: no such file or directory: %$XDG_RUNTIME_DIR%/', testlog, 10) + end + local vimruntime, libdir = vimruntime_and_libdir() eq((('$XDG_DATA_HOME/nvim' .. ',$XDG_DATA_DIRS/nvim' @@ -497,7 +514,7 @@ describe('XDG defaults', function() it('are escaped properly', function() local vimruntime, libdir = vimruntime_and_libdir() - local path_sep = iswin() and '\\' or '/' + local path_sep = is_os('win') and '\\' or '/' eq(('\\, \\, \\,' .. path_sep .. 'nvim' .. ',\\,-\\,-\\,' .. path_sep .. 'nvim' .. ',-\\,-\\,-' .. path_sep .. 'nvim' @@ -549,9 +566,9 @@ end) describe('stdpath()', function() -- Windows appends 'nvim-data' instead of just 'nvim' to prevent collisions -- due to XDG_CONFIG_HOME, XDG_DATA_HOME and XDG_STATE_HOME being the same. - local datadir = iswin() and 'nvim-data' or 'nvim' - local statedir = iswin() and 'nvim-data' or 'nvim' - local env_sep = iswin() and ';' or ':' + local datadir = is_os('win') and 'nvim-data' or 'nvim' + local statedir = is_os('win') and 'nvim-data' or 'nvim' + local env_sep = is_os('win') and ';' or ':' it('acceptance', function() clear() -- Do not explicitly set any env vars. @@ -704,7 +721,7 @@ describe('stdpath()', function() context('returns a List', function() -- Some OS specific variables the system would have set. local function base_env() - if iswin() then + if is_os('win') then return { HOME='C:\\Users\\docwhat', -- technically, is not a usual PATH HOMEDRIVE='C:', diff --git a/test/functional/options/mousescroll_spec.lua b/test/functional/options/mousescroll_spec.lua index 2c9b2d175e..5bff45a836 100644 --- a/test/functional/options/mousescroll_spec.lua +++ b/test/functional/options/mousescroll_spec.lua @@ -97,6 +97,24 @@ describe("'mousescroll'", function() eq(10, screencol()) scroll('left') eq(10, screencol()) + + -- vertical scrolling is still disabled with non-zero 'scrolloff' value + command('set scrolloff=1') + + eq(10, screenrow()) + scroll('up') + eq(10, screenrow()) + scroll('down') + eq(10, screenrow()) + + -- also in insert mode + feed('i') + + eq(10, screenrow()) + scroll('up') + eq(10, screenrow()) + scroll('down') + eq(10, screenrow()) end) local test_vertical_scrolling = function() diff --git a/test/functional/plugin/ccomplete_spec.lua b/test/functional/plugin/ccomplete_spec.lua new file mode 100644 index 0000000000..903f16fc73 --- /dev/null +++ b/test/functional/plugin/ccomplete_spec.lua @@ -0,0 +1,61 @@ +local helpers = require('test.functional.helpers')(after_each) +local clear = helpers.clear +local command = helpers.command +local eq = helpers.eq +local eval = helpers.eval +local feed = helpers.feed +local write_file = helpers.write_file + +describe('ccomplete#Complete', function() + setup(function() + -- Realistic tags generated from neovim source tree using `ctags -R *` + write_file( + 'Xtags', + [[ +augroup_del src/nvim/autocmd.c /^void augroup_del(char *name, bool stupid_legacy_mode)$/;" f typeref:typename:void +augroup_exists src/nvim/autocmd.c /^bool augroup_exists(const char *name)$/;" f typeref:typename:bool +augroup_find src/nvim/autocmd.c /^int augroup_find(const char *name)$/;" f typeref:typename:int +aupat_get_buflocal_nr src/nvim/autocmd.c /^int aupat_get_buflocal_nr(char *pat, int patlen)$/;" f typeref:typename:int +aupat_is_buflocal src/nvim/autocmd.c /^bool aupat_is_buflocal(char *pat, int patlen)$/;" f typeref:typename:bool +expand_get_augroup_name src/nvim/autocmd.c /^char *expand_get_augroup_name(expand_T *xp, int idx)$/;" f typeref:typename:char * +expand_get_event_name src/nvim/autocmd.c /^char *expand_get_event_name(expand_T *xp, int idx)$/;" f typeref:typename:char * +]] + ) + end) + + before_each(function() + clear() + command('set tags=Xtags') + end) + + teardown(function() + os.remove('Xtags') + end) + + it('can complete from Xtags', function() + local completed = eval('ccomplete#Complete(0, "a")') + eq(5, #completed) + eq('augroup_del(', completed[1].word) + eq('f', completed[1].kind) + + local aupat = eval('ccomplete#Complete(0, "aupat")') + eq(2, #aupat) + eq('aupat_get_buflocal_nr(', aupat[1].word) + eq('f', aupat[1].kind) + end) + + it('does not error when returning no matches', function() + local completed = eval('ccomplete#Complete(0, "doesnotmatch")') + eq({}, completed) + end) + + it('can find the beginning of a word for C', function() + command('set filetype=c') + feed('i int something = augroup') + local result = eval('ccomplete#Complete(1, "")') + eq(#' int something = ', result) + + local completed = eval('ccomplete#Complete(0, "augroup")') + eq(3, #completed) + end) +end) diff --git a/test/functional/plugin/cfilter_spec.lua b/test/functional/plugin/cfilter_spec.lua new file mode 100644 index 0000000000..8b1e75b495 --- /dev/null +++ b/test/functional/plugin/cfilter_spec.lua @@ -0,0 +1,106 @@ +local helpers = require('test.functional.helpers')(after_each) +local clear = helpers.clear +local command = helpers.command +local eq = helpers.eq +local funcs = helpers.funcs + +describe('cfilter.lua', function() + before_each(function() + clear() + command('packadd cfilter') + end) + + for _, list in ipairs({ + { + name = 'Cfilter', + get = funcs.getqflist, + set = funcs.setqflist, + }, + { + name = 'Lfilter', + get = function() + return funcs.getloclist(0) + end, + set = function(items) + return funcs.setloclist(0, items) + end, + }, + }) do + local filter = function(s, bang) + if not bang then + bang = '' + else + bang = '!' + end + + command(string.format('%s%s %s', list.name, bang, s)) + end + + describe((':%s'):format(list.name), function() + it('does not error on empty list', function() + filter('nothing') + eq({}, funcs.getqflist()) + end) + + it('requires an argument', function() + local ok = pcall(filter, '') + eq(false, ok) + end) + + local test = function(name, s, res, map, bang) + it(('%s (%s)'):format(name, s), function() + list.set({ + { filename = 'foo', lnum = 1, text = 'bar' }, + { filename = 'foo', lnum = 2, text = 'baz' }, + { filename = 'foo', lnum = 3, text = 'zed' }, + }) + + filter(s, bang) + + local got = list.get() + if map then + got = map(got) + end + eq(res, got) + end) + end + + local toname = function(qflist) + return funcs.map(qflist, 'v:val.text') + end + + test('filters with no matches', 'does not match', {}) + + test('filters with matches', 'ba', { 'bar', 'baz' }, toname) + test('filters with matches', 'z', { 'baz', 'zed' }, toname) + test('filters with matches', '^z', { 'zed' }, toname) + test('filters with not matches', '^z', { 'bar', 'baz' }, toname, true) + + it('also supports using the / register', function() + list.set({ + { filename = 'foo', lnum = 1, text = 'bar' }, + { filename = 'foo', lnum = 2, text = 'baz' }, + { filename = 'foo', lnum = 3, text = 'zed' }, + }) + + funcs.setreg('/', 'ba') + filter('/') + + eq({ 'bar', 'baz' }, toname(list.get())) + end) + + it('also supports using the / register with bang', function() + list.set({ + { filename = 'foo', lnum = 1, text = 'bar' }, + { filename = 'foo', lnum = 2, text = 'baz' }, + { filename = 'foo', lnum = 3, text = 'zed' }, + }) + + funcs.setreg('/', 'ba') + filter('/', true) + + eq({ 'zed' }, toname(list.get())) + end) + end) + end +end) diff --git a/test/functional/plugin/editorconfig_spec.lua b/test/functional/plugin/editorconfig_spec.lua new file mode 100644 index 0000000000..e6a2550aba --- /dev/null +++ b/test/functional/plugin/editorconfig_spec.lua @@ -0,0 +1,210 @@ +local helpers = require('test.functional.helpers')(after_each) +local clear = helpers.clear +local command = helpers.command +local eq = helpers.eq +local pathsep = helpers.get_pathsep() +local curbufmeths = helpers.curbufmeths +local funcs = helpers.funcs +local meths = helpers.meths + +local testdir = 'Xtest-editorconfig' + +local function test_case(name, expected) + local filename = testdir .. pathsep .. name + command('edit ' .. filename) + for opt, val in pairs(expected) do + eq(val, curbufmeths.get_option(opt), name) + end +end + +setup(function() + helpers.mkdir_p(testdir) + helpers.write_file( + testdir .. pathsep .. '.editorconfig', + [[ + root = true + + [3_space.txt] + indent_style = space + indent_size = 3 + tab_width = 3 + + [4_space.py] + indent_style = space + indent_size = 4 + tab_width = 8 + + [space.txt] + indent_style = space + indent_size = tab + + [tab.txt] + indent_style = tab + + [4_tab.txt] + indent_style = tab + indent_size = 4 + tab_width = 4 + + [4_tab_width_of_8.txt] + indent_style = tab + indent_size = 4 + tab_width = 8 + + [lf.txt] + end_of_line = lf + + [crlf.txt] + end_of_line = crlf + + [cr.txt] + end_of_line = cr + + [utf-8.txt] + charset = utf-8 + + [utf-8-bom.txt] + charset = utf-8-bom + + [utf-16be.txt] + charset = utf-16be + + [utf-16le.txt] + charset = utf-16le + + [latin1.txt] + charset = latin1 + + [with_newline.txt] + insert_final_newline = true + + [without_newline.txt] + insert_final_newline = false + + [trim.txt] + trim_trailing_whitespace = true + + [no_trim.txt] + trim_trailing_whitespace = false + + [max_line_length.txt] + max_line_length = 42 + ]] + ) +end) + +teardown(function() + helpers.rmdir(testdir) +end) + +describe('editorconfig', function() + before_each(function() + -- Remove -u NONE so that plugins (i.e. editorconfig.lua) are loaded + clear({ args_rm = { '-u' } }) + end) + + it('sets indent options', function() + test_case('3_space.txt', { + expandtab = true, + shiftwidth = 3, + softtabstop = -1, + tabstop = 3, + }) + + test_case('4_space.py', { + expandtab = true, + shiftwidth = 4, + softtabstop = -1, + tabstop = 8, + }) + + test_case('space.txt', { + expandtab = true, + shiftwidth = 0, + softtabstop = 0, + }) + + test_case('tab.txt', { + expandtab = false, + shiftwidth = 0, + softtabstop = 0, + }) + + test_case('4_tab.txt', { + expandtab = false, + shiftwidth = 4, + softtabstop = -1, + tabstop = 4, + }) + + test_case('4_tab_width_of_8.txt', { + expandtab = false, + shiftwidth = 4, + softtabstop = -1, + tabstop = 8, + }) + end) + + it('sets end-of-line options', function() + test_case('lf.txt', { fileformat = 'unix' }) + test_case('crlf.txt', { fileformat = 'dos' }) + test_case('cr.txt', { fileformat = 'mac' }) + end) + + it('sets encoding options', function() + test_case('utf-8.txt', { fileencoding = 'utf-8', bomb = false }) + test_case('utf-8-bom.txt', { fileencoding = 'utf-8', bomb = true }) + test_case('utf-16be.txt', { fileencoding = 'utf-16', bomb = false }) + test_case('utf-16le.txt', { fileencoding = 'utf-16le', bomb = false }) + test_case('latin1.txt', { fileencoding = 'latin1', bomb = false }) + end) + + it('sets newline options', function() + test_case('with_newline.txt', { fixendofline = true, endofline = true }) + test_case('without_newline.txt', { fixendofline = false, endofline = false }) + end) + + it('respects trim_trailing_whitespace', function() + local filename = testdir .. pathsep .. 'trim.txt' + -- luacheck: push ignore 613 + local untrimmed = [[ +This line ends in whitespace +So does this one +And this one +But not this one +]] + -- luacheck: pop + local trimmed = untrimmed:gsub('%s+\n', '\n') + + helpers.write_file(filename, untrimmed) + command('edit ' .. filename) + command('write') + command('bdelete') + eq(trimmed, helpers.read_file(filename)) + + filename = testdir .. pathsep .. 'no_trim.txt' + helpers.write_file(filename, untrimmed) + command('edit ' .. filename) + command('write') + command('bdelete') + eq(untrimmed, helpers.read_file(filename)) + end) + + it('sets textwidth', function() + test_case('max_line_length.txt', { textwidth = 42 }) + end) + + it('can be disabled globally', function() + meths.set_var('editorconfig', false) + meths.set_option_value('shiftwidth', 42, {}) + test_case('3_space.txt', { shiftwidth = 42 }) + end) + + it('can be disabled per-buffer', function() + meths.set_option_value('shiftwidth', 42, {}) + local bufnr = funcs.bufadd(testdir .. pathsep .. '3_space.txt') + meths.buf_set_var(bufnr, 'editorconfig', false) + test_case('3_space.txt', { shiftwidth = 42 }) + test_case('4_space.py', { shiftwidth = 4 }) + end) +end) diff --git a/test/functional/plugin/health_spec.lua b/test/functional/plugin/health_spec.lua index ba66117fb1..97d32313e5 100644 --- a/test/functional/plugin/health_spec.lua +++ b/test/functional/plugin/health_spec.lua @@ -5,7 +5,7 @@ local Screen = require('test.functional.ui.screen') local clear = helpers.clear local curbuf_contents = helpers.curbuf_contents local command = helpers.command -local eq, neq = helpers.eq, helpers.neq +local eq, neq, matches = helpers.eq, helpers.neq, helpers.matches local getcompletion = helpers.funcs.getcompletion describe(':checkhealth', function() @@ -29,8 +29,7 @@ describe(':checkhealth', function() -- Do this after startup, otherwise it just breaks $VIMRUNTIME. command("let $VIM='zub'") command("checkhealth nvim") - eq("ERROR: $VIM is invalid: zub", - string.match(curbuf_contents(), "ERROR: $VIM .* zub")) + matches('ERROR $VIM .* zub', curbuf_contents()) end) it('completions can be listed via getcompletion()', function() clear() @@ -56,21 +55,22 @@ describe('health.vim', function() command("checkhealth full_render") helpers.expect([[ + ============================================================================== full_render: health#full_render#check - ======================================================================== - ## report 1 - - OK: life is fine - - WARNING: no what installed - - ADVICE: - - pip what - - make what - - ## report 2 - - INFO: stuff is stable - - ERROR: why no hardcopy - - ADVICE: - - :help |:hardcopy| - - :help |:TOhtml| + + report 1 ~ + - OK life is fine + - WARNING no what installed + - ADVICE: + - pip what + - make what + + report 2 ~ + - stuff is stable + - ERROR why no hardcopy + - ADVICE: + - :help |:hardcopy| + - :help |:TOhtml| ]]) end) @@ -78,26 +78,29 @@ describe('health.vim', function() command("checkhealth success1 success2 test_plug") helpers.expect([[ + ============================================================================== success1: health#success1#check - ======================================================================== - ## report 1 - - OK: everything is fine - ## report 2 - - OK: nothing to see here + report 1 ~ + - OK everything is fine + + report 2 ~ + - OK nothing to see here + ============================================================================== success2: health#success2#check - ======================================================================== - ## another 1 - - OK: ok + another 1 ~ + - OK ok + + ============================================================================== test_plug: require("test_plug.health").check() - ======================================================================== - ## report 1 - - OK: everything is fine - ## report 2 - - OK: nothing to see here + report 1 ~ + - OK everything is fine + + report 2 ~ + - OK nothing to see here ]]) end) @@ -107,13 +110,14 @@ describe('health.vim', function() -- and the Lua healthcheck is used instead. helpers.expect([[ + ============================================================================== test_plug: require("test_plug.health").check() - ======================================================================== - ## report 1 - - OK: everything is fine - ## report 2 - - OK: nothing to see here + report 1 ~ + - OK everything is fine + + report 2 ~ + - OK nothing to see here ]]) end) @@ -121,13 +125,14 @@ describe('health.vim', function() command("checkhealth test_plug.submodule") helpers.expect([[ + ============================================================================== test_plug.submodule: require("test_plug.submodule.health").check() - ======================================================================== - ## report 1 - - OK: everything is fine - ## report 2 - - OK: nothing to see here + report 1 ~ + - OK everything is fine + + report 2 ~ + - OK nothing to see here ]]) end) @@ -138,30 +143,34 @@ describe('health.vim', function() local received = table.concat(buf_lines, '\n', 1, #buf_lines - 5) local expected = helpers.dedent([[ + ============================================================================== test_plug: require("test_plug.health").check() - ======================================================================== - ## report 1 - - OK: everything is fine - ## report 2 - - OK: nothing to see here + report 1 ~ + - OK everything is fine + + report 2 ~ + - OK nothing to see here + ============================================================================== test_plug.submodule: require("test_plug.submodule.health").check() - ======================================================================== - ## report 1 - - OK: everything is fine - ## report 2 - - OK: nothing to see here + report 1 ~ + - OK everything is fine + report 2 ~ + - OK nothing to see here + + ============================================================================== test_plug.submodule_empty: require("test_plug.submodule_empty.health").check() - ======================================================================== - - ERROR: The healthcheck report for "test_plug.submodule_empty" plugin is empty. + - ERROR The healthcheck report for "test_plug.submodule_empty" plugin is empty. + + ============================================================================== test_plug.submodule_failed: require("test_plug.submodule_failed.health").check() - ======================================================================== - - ERROR: Failed to run healthcheck for "test_plug.submodule_failed" plugin. Exception: - function health#check, line 20]]) + + - ERROR Failed to run healthcheck for "test_plug.submodule_failed" plugin. Exception: + function health#check, line 25]]) eq(expected, received) end) @@ -169,11 +178,12 @@ describe('health.vim', function() command("checkhealth broken") helpers.expect([[ + ============================================================================== broken: health#broken#check - ======================================================================== - - ERROR: Failed to run healthcheck for "broken" plugin. Exception: - function health#check[20]..health#broken#check, line 1 - caused an error + + - ERROR Failed to run healthcheck for "broken" plugin. Exception: + function health#check[25]..health#broken#check, line 1 + caused an error ]]) end) @@ -181,9 +191,10 @@ describe('health.vim', function() command("checkhealth test_plug.submodule_empty") helpers.expect([[ + ============================================================================== test_plug.submodule_empty: require("test_plug.submodule_empty.health").check() - ======================================================================== - - ERROR: The healthcheck report for "test_plug.submodule_empty" plugin is empty. + + - ERROR The healthcheck report for "test_plug.submodule_empty" plugin is empty. ]]) end) @@ -198,38 +209,38 @@ describe('health.vim', function() local expected = global_helpers.dedent([[ + ============================================================================== test_plug.submodule_failed: require("test_plug.submodule_failed.health").check() - ======================================================================== - - ERROR: Failed to run healthcheck for "test_plug.submodule_failed" plugin. Exception: - function health#check, line 20]]) + + - ERROR Failed to run healthcheck for "test_plug.submodule_failed" plugin. Exception: + function health#check, line 25]]) eq(expected, received) end) it("highlights OK, ERROR", function() - local screen = Screen.new(72, 10) + local screen = Screen.new(50, 12) screen:attach() screen:set_default_attr_ids({ Ok = { foreground = Screen.colors.Grey3, background = 6291200 }, Error = { foreground = Screen.colors.Grey100, background = Screen.colors.Red }, - Heading = { bold=true, foreground=Screen.colors.Magenta }, - Heading2 = { foreground = Screen.colors.SlateBlue }, - Bar = { foreground = 0x6a0dad }, - Bullet = { bold=true, foreground=Screen.colors.Brown }, + Heading = { foreground = tonumber('0x6a0dad') }, + Bar = { foreground = Screen.colors.LightGrey, background = Screen.colors.DarkGrey }, }) command("checkhealth foo success1") - command("1tabclose") - command("set laststatus=0") + command("set nowrap laststatus=0") screen:expect{grid=[[ - ^ | - {Heading:foo: } | - {Bar:========================================================================}| - {Bullet: -} {Error:ERROR}: No healthcheck found for "foo" plugin. | - | - {Heading:success1: health#success1#check} | - {Bar:========================================================================}| - {Heading2:##}{Heading: report 1} | - {Bullet: -} {Ok:OK}: everything is fine | - | + ^ | + {Bar:──────────────────────────────────────────────────}| + {Heading:foo: } | + | + - {Error:ERROR} No healthcheck found for "foo" plugin. | + | + {Bar:──────────────────────────────────────────────────}| + {Heading:success1: health#success1#check} | + | + {Heading:report 1} | + - {Ok:OK} everything is fine | + | ]]} end) @@ -238,9 +249,10 @@ describe('health.vim', function() -- luacheck: ignore 613 helpers.expect([[ + ============================================================================== non_existent_healthcheck: - ======================================================================== - - ERROR: No healthcheck found for "non_existent_healthcheck" plugin. + + - ERROR No healthcheck found for "non_existent_healthcheck" plugin. ]]) end) diff --git a/test/functional/plugin/lsp/codelens_spec.lua b/test/functional/plugin/lsp/codelens_spec.lua index ecc2f579b8..3d7a15a191 100644 --- a/test/functional/plugin/lsp/codelens_spec.lua +++ b/test/functional/plugin/lsp/codelens_spec.lua @@ -58,6 +58,41 @@ describe('vim.lsp.codelens', function() ]], bufnr) eq({[1] = {'Lens1', 'LspCodeLens'}}, virtual_text_chunks) + end) + + it('can clear all lens', function() + local fake_uri = "file:///fake/uri" + local bufnr = exec_lua([[ + fake_uri = ... + local bufnr = vim.uri_to_bufnr(fake_uri) + local lines = {'So', 'many', 'lines'} + vim.fn.bufload(bufnr) + vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) + return bufnr + ]], fake_uri) + + exec_lua([[ + local bufnr = ... + local lenses = { + { + range = { + start = { line = 0, character = 0, }, + ['end'] = { line = 0, character = 0 } + }, + command = { title = 'Lens1', command = 'Dummy' } + }, + } + vim.lsp.codelens.on_codelens(nil, lenses, {method='textDocument/codeLens', client_id=1, bufnr=bufnr}) + ]], bufnr) + + local stored_lenses = exec_lua('return vim.lsp.codelens.get(...)', bufnr) + eq(1, #stored_lenses) + + exec_lua([[ + vim.lsp.codelens.clear() + ]]) + stored_lenses = exec_lua('return vim.lsp.codelens.get(...)', bufnr) + eq(0, #stored_lenses) end) end) diff --git a/test/functional/plugin/lsp/helpers.lua b/test/functional/plugin/lsp/helpers.lua new file mode 100644 index 0000000000..028ccb9e2c --- /dev/null +++ b/test/functional/plugin/lsp/helpers.lua @@ -0,0 +1,176 @@ +local helpers = require('test.functional.helpers')(nil) + +local clear = helpers.clear +local exec_lua = helpers.exec_lua +local run = helpers.run +local stop = helpers.stop +local NIL = helpers.NIL + +local M = {} + +function M.clear_notrace() + -- problem: here be dragons + -- solution: don't look too closely for dragons + clear {env={ + NVIM_LUA_NOTRACK="1"; + VIMRUNTIME=os.getenv"VIMRUNTIME"; + }} +end + +M.create_server_definition = [[ + function _create_server(opts) + opts = opts or {} + local server = {} + server.messages = {} + + function server.cmd(dispatchers) + local closing = false + local handlers = opts.handlers or {} + local srv = {} + + function srv.request(method, params, callback) + table.insert(server.messages, { + method = method, + params = params, + }) + local handler = handlers[method] + if handler then + local response, err = handler(method, params) + callback(err, response) + elseif method == 'initialize' then + callback(nil, { + capabilities = opts.capabilities or {} + }) + elseif method == 'shutdown' then + callback(nil, nil) + end + local request_id = #server.messages + return true, request_id + end + + function srv.notify(method, params) + table.insert(server.messages, { + method = method, + params = params + }) + if method == 'exit' then + dispatchers.on_exit(0, 15) + end + end + + function srv.is_closing() + return closing + end + + function srv.terminate() + closing = true + end + + return srv + end + + return server + end +]] + +-- Fake LSP server. +M.fake_lsp_code = 'test/functional/fixtures/fake-lsp-server.lua' +M.fake_lsp_logfile = 'Xtest-fake-lsp.log' + +local function fake_lsp_server_setup(test_name, timeout_ms, options, settings) + exec_lua([=[ + lsp = require('vim.lsp') + local test_name, fixture_filename, logfile, timeout, options, settings = ... + TEST_RPC_CLIENT_ID = lsp.start_client { + cmd_env = { + NVIM_LOG_FILE = logfile; + NVIM_LUA_NOTRACK = "1"; + }; + cmd = { + vim.v.progpath, '-Es', '-u', 'NONE', '--headless', + "-c", string.format("lua TEST_NAME = %q", test_name), + "-c", string.format("lua TIMEOUT = %d", timeout), + "-c", "luafile "..fixture_filename, + }; + handlers = setmetatable({}, { + __index = function(t, method) + return function(...) + return vim.rpcrequest(1, 'handler', ...) + end + end; + }); + workspace_folders = {{ + uri = 'file://' .. vim.loop.cwd(), + name = 'test_folder', + }}; + on_init = function(client, result) + TEST_RPC_CLIENT = client + vim.rpcrequest(1, "init", result) + end; + flags = { + allow_incremental_sync = options.allow_incremental_sync or false; + debounce_text_changes = options.debounce_text_changes or 0; + }; + settings = settings; + on_exit = function(...) + vim.rpcnotify(1, "exit", ...) + end; + } + ]=], test_name, M.fake_lsp_code, M.fake_lsp_logfile, timeout_ms or 1e3, options or {}, settings or {}) +end + +function M.test_rpc_server(config) + if config.test_name then + M.clear_notrace() + fake_lsp_server_setup(config.test_name, config.timeout_ms or 1e3, config.options, config.settings) + end + local client = setmetatable({}, { + __index = function(_, name) + -- Workaround for not being able to yield() inside __index for Lua 5.1 :( + -- Otherwise I would just return the value here. + return function(...) + return exec_lua([=[ + local name = ... + if type(TEST_RPC_CLIENT[name]) == 'function' then + return TEST_RPC_CLIENT[name](select(2, ...)) + else + return TEST_RPC_CLIENT[name] + end + ]=], name, ...) + end + end; + }) + local code, signal + local function on_request(method, args) + if method == "init" then + if config.on_init then + config.on_init(client, unpack(args)) + end + return NIL + end + if method == 'handler' then + if config.on_handler then + config.on_handler(unpack(args)) + end + end + return NIL + end + local function on_notify(method, args) + if method == 'exit' then + code, signal = unpack(args) + return stop() + end + end + -- TODO specify timeout? + -- run(on_request, on_notify, config.on_setup, 1000) + run(on_request, on_notify, config.on_setup) + if config.on_exit then + config.on_exit(code, signal) + end + stop() + if config.test_name then + exec_lua("vim.api.nvim_exec_autocmds('VimLeavePre', { modeline = false })") + end +end + +return M diff --git a/test/functional/plugin/lsp/semantic_tokens_spec.lua b/test/functional/plugin/lsp/semantic_tokens_spec.lua new file mode 100644 index 0000000000..9c1ba86fe1 --- /dev/null +++ b/test/functional/plugin/lsp/semantic_tokens_spec.lua @@ -0,0 +1,1219 @@ +local helpers = require('test.functional.helpers')(after_each) +local lsp_helpers = require('test.functional.plugin.lsp.helpers') +local Screen = require('test.functional.ui.screen') + +local command = helpers.command +local dedent = helpers.dedent +local eq = helpers.eq +local exec_lua = helpers.exec_lua +local feed = helpers.feed +local feed_command = helpers.feed_command +local insert = helpers.insert +local matches = helpers.matches + +local clear_notrace = lsp_helpers.clear_notrace +local create_server_definition = lsp_helpers.create_server_definition + +before_each(function() + clear_notrace() +end) + +after_each(function() + exec_lua("vim.api.nvim_exec_autocmds('VimLeavePre', { modeline = false })") +end) + +describe('semantic token highlighting', function() + + describe('general', function() + local text = dedent([[ + #include <iostream> + + int main() + { + int x; + #ifdef __cplusplus + std::cout << x << "\n"; + #else + printf("%d\n", x); + #endif + } + }]]) + + local legend = [[{ + "tokenTypes": [ + "variable", "variable", "parameter", "function", "method", "function", "property", "variable", "class", "interface", "enum", "enumMember", "type", "type", "unknown", "namespace", "typeParameter", "concept", "type", "macro", "comment" + ], + "tokenModifiers": [ + "declaration", "deprecated", "deduced", "readonly", "static", "abstract", "virtual", "dependentName", "defaultLibrary", "usedAsMutableReference", "functionScope", "classScope", "fileScope", "globalScope" + ] + }]] + + local response = [[{ + "data": [ 2, 4, 4, 3, 8193, 2, 8, 1, 1, 1025, 1, 7, 11, 19, 8192, 1, 4, 3, 15, 8448, 0, 5, 4, 0, 8448, 0, 8, 1, 1, 1024, 1, 0, 5, 20, 0, 1, 0, 22, 20, 0, 1, 0, 6, 20, 0 ], + "resultId": 1 + }]] + + local edit_response = [[{ + "edits": [ {"data": [ 2, 8, 1, 3, 8193, 1, 7, 11, 19, 8192, 1, 4, 3, 15, 8448, 0, 5, 4, 0, 8448, 0, 8, 1, 3, 8192 ], "deleteCount": 25, "start": 5 } ], + "resultId":"2" + }]] + + local screen + before_each(function() + screen = Screen.new(40, 16) + screen:attach() + screen:set_default_attr_ids { + [1] = { bold = true, foreground = Screen.colors.Blue1 }; + [2] = { foreground = Screen.colors.DarkCyan }; + [3] = { foreground = Screen.colors.SlateBlue }; + [4] = { bold = true, foreground = Screen.colors.SeaGreen }; + [5] = { foreground = tonumber('0x6a0dad') }; + [6] = { foreground = Screen.colors.Blue1 }; + [7] = { bold = true, foreground = Screen.colors.DarkCyan }; + [8] = { bold = true, foreground = Screen.colors.SlateBlue }; + } + command([[ hi link @namespace Type ]]) + command([[ hi link @function Special ]]) + command([[ hi @declaration gui=bold ]]) + + exec_lua(create_server_definition) + exec_lua([[ + local legend, response, edit_response = ... + server = _create_server({ + capabilities = { + semanticTokensProvider = { + full = { delta = true }, + legend = vim.fn.json_decode(legend), + }, + }, + handlers = { + ['textDocument/semanticTokens/full'] = function() + return vim.fn.json_decode(response) + end, + ['textDocument/semanticTokens/full/delta'] = function() + return vim.fn.json_decode(edit_response) + end, + } + }) + ]], legend, response, edit_response) + end) + + it('buffer is highlighted when attached', function() + exec_lua([[ + bufnr = vim.api.nvim_get_current_buf() + vim.api.nvim_win_set_buf(0, bufnr) + client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) + ]]) + + insert(text) + + screen:expect { grid = [[ + #include <iostream> | + | + int {8:main}() | + { | + int {7:x}; | + #ifdef {5:__cplusplus} | + {4:std}::{2:cout} << {2:x} << "\n"; | + {6:#else} | + {6: printf("%d\n", x);} | + {6:#endif} | + } | + ^} | + {1:~ }| + {1:~ }| + {1:~ }| + | + ]] } + end) + + it('buffer is unhighlighted when client is detached', function() + exec_lua([[ + bufnr = vim.api.nvim_get_current_buf() + vim.api.nvim_win_set_buf(0, bufnr) + client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) + ]]) + + insert(text) + + exec_lua([[ + vim.notify = function() end + vim.lsp.buf_detach_client(bufnr, client_id) + ]]) + + screen:expect { grid = [[ + #include <iostream> | + | + int main() | + { | + int x; | + #ifdef __cplusplus | + std::cout << x << "\n"; | + #else | + printf("%d\n", x); | + #endif | + } | + ^} | + {1:~ }| + {1:~ }| + {1:~ }| + | + ]] } + end) + + it('buffer is highlighted and unhighlighted when semantic token highlighting is started and stopped' + , function() + exec_lua([[ + bufnr = vim.api.nvim_get_current_buf() + vim.api.nvim_win_set_buf(0, bufnr) + client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) + ]]) + + insert(text) + + exec_lua([[ + vim.notify = function() end + vim.lsp.semantic_tokens.stop(bufnr, client_id) + ]]) + + screen:expect { grid = [[ + #include <iostream> | + | + int main() | + { | + int x; | + #ifdef __cplusplus | + std::cout << x << "\n"; | + #else | + printf("%d\n", x); | + #endif | + } | + ^} | + {1:~ }| + {1:~ }| + {1:~ }| + | + ]] } + + exec_lua([[ + vim.lsp.semantic_tokens.start(bufnr, client_id) + ]]) + + screen:expect { grid = [[ + #include <iostream> | + | + int {8:main}() | + { | + int {7:x}; | + #ifdef {5:__cplusplus} | + {4:std}::{2:cout} << {2:x} << "\n"; | + {6:#else} | + {6: printf("%d\n", x);} | + {6:#endif} | + } | + ^} | + {1:~ }| + {1:~ }| + {1:~ }| + | + ]] } + end) + + it('buffer is re-highlighted when force refreshed', function() + exec_lua([[ + bufnr = vim.api.nvim_get_current_buf() + vim.api.nvim_win_set_buf(0, bufnr) + client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) + ]]) + + insert(text) + + screen:expect { grid = [[ + #include <iostream> | + | + int {8:main}() | + { | + int {7:x}; | + #ifdef {5:__cplusplus} | + {4:std}::{2:cout} << {2:x} << "\n"; | + {6:#else} | + {6: printf("%d\n", x);} | + {6:#endif} | + } | + ^} | + {1:~ }| + {1:~ }| + {1:~ }| + | + ]] } + + exec_lua([[ + vim.lsp.semantic_tokens.force_refresh(bufnr) + ]]) + + screen:expect { grid = [[ + #include <iostream> | + | + int {8:main}() | + { | + int {7:x}; | + #ifdef {5:__cplusplus} | + {4:std}::{2:cout} << {2:x} << "\n"; | + {6:#else} | + {6: printf("%d\n", x);} | + {6:#endif} | + } | + ^} | + {1:~ }| + {1:~ }| + {1:~ }| + | + ]], unchanged = true } + + local messages = exec_lua('return server.messages') + local token_request_count = 0 + for _, message in ipairs(messages) do + assert(message.method ~= 'textDocument/semanticTokens/full/delta', 'delta request received') + if message.method == 'textDocument/semanticTokens/full' then + token_request_count = token_request_count + 1 + end + end + eq(2, token_request_count) + end) + + it('destroys the highlighter if the buffer is deleted', function() + exec_lua([[ + bufnr = vim.api.nvim_get_current_buf() + vim.api.nvim_win_set_buf(0, bufnr) + client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) + ]]) + + insert(text) + + local highlighters = exec_lua([[ + vim.api.nvim_buf_delete(bufnr, { force = true }) + local semantic_tokens = vim.lsp.semantic_tokens + return semantic_tokens.__STHighlighter.active + ]]) + + eq({}, highlighters) + end) + + it('updates highlights with delta request on buffer change', function() + exec_lua([[ + bufnr = vim.api.nvim_get_current_buf() + vim.api.nvim_win_set_buf(0, bufnr) + client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) + ]]) + + insert(text) + screen:expect { grid = [[ + #include <iostream> | + | + int {8:main}() | + { | + int {7:x}; | + #ifdef {5:__cplusplus} | + {4:std}::{2:cout} << {2:x} << "\n"; | + {6:#else} | + {6: printf("%d\n", x);} | + {6:#endif} | + } | + ^} | + {1:~ }| + {1:~ }| + {1:~ }| + | + ]] } + feed_command('%s/int x/int x()/') + feed_command('noh') + screen:expect { grid = [[ + #include <iostream> | + | + int {8:main}() | + { | + ^int {8:x}(); | + #ifdef {5:__cplusplus} | + {4:std}::{2:cout} << {3:x} << "\n"; | + {6:#else} | + {6: printf("%d\n", x);} | + {6:#endif} | + } | + } | + {1:~ }| + {1:~ }| + {1:~ }| + :noh | + ]] } + end) + + it('prevents starting semantic token highlighting with invalid conditions', function() + exec_lua([[ + bufnr = vim.api.nvim_get_current_buf() + vim.api.nvim_win_set_buf(0, bufnr) + client_id = vim.lsp.start_client({ name = 'dummy', cmd = server.cmd }) + notifications = {} + vim.notify = function(...) table.insert(notifications, 1, {...}) end + ]]) + eq(false, exec_lua("return vim.lsp.buf_is_attached(bufnr, client_id)")) + + insert(text) + + local notifications = exec_lua([[ + vim.lsp.semantic_tokens.start(bufnr, client_id) + return notifications + ]]) + matches('%[LSP%] Client with id %d not attached to buffer %d', notifications[1][1]) + + notifications = exec_lua([[ + vim.lsp.semantic_tokens.start(bufnr, client_id + 1) + return notifications + ]]) + matches('%[LSP%] No client with id %d', notifications[1][1]) + end) + + it('opt-out: does not activate semantic token highlighting if disabled in client attach', + function() + exec_lua([[ + bufnr = vim.api.nvim_get_current_buf() + vim.api.nvim_win_set_buf(0, bufnr) + client_id = vim.lsp.start({ + name = 'dummy', + cmd = server.cmd, + on_attach = vim.schedule_wrap(function(client, bufnr) + client.server_capabilities.semanticTokensProvider = nil + end), + }) + ]]) + eq(true, exec_lua("return vim.lsp.buf_is_attached(bufnr, client_id)")) + + insert(text) + + screen:expect { grid = [[ + #include <iostream> | + | + int main() | + { | + int x; | + #ifdef __cplusplus | + std::cout << x << "\n"; | + #else | + printf("%d\n", x); | + #endif | + } | + ^} | + {1:~ }| + {1:~ }| + {1:~ }| + | + ]] } + + local notifications = exec_lua([[ + local notifications = {} + vim.notify = function(...) table.insert(notifications, 1, {...}) end + vim.lsp.semantic_tokens.start(bufnr, client_id) + return notifications + ]]) + eq('[LSP] Server does not support semantic tokens', notifications[1][1]) + + screen:expect { grid = [[ + #include <iostream> | + | + int main() | + { | + int x; | + #ifdef __cplusplus | + std::cout << x << "\n"; | + #else | + printf("%d\n", x); | + #endif | + } | + ^} | + {1:~ }| + {1:~ }| + {1:~ }| + | + ]], unchanged = true } + end) + + it('ignores null responses from the server', function() + exec_lua([[ + local legend, response, edit_response = ... + server2 = _create_server({ + capabilities = { + semanticTokensProvider = { + full = { delta = false }, + }, + }, + handlers = { + ['textDocument/semanticTokens/full'] = function() + return nil + end, + ['textDocument/semanticTokens/full/delta'] = function() + return nil + end, + } + }) + bufnr = vim.api.nvim_get_current_buf() + vim.api.nvim_win_set_buf(0, bufnr) + client_id = vim.lsp.start({ name = 'dummy', cmd = server2.cmd }) + ]]) + eq(true, exec_lua("return vim.lsp.buf_is_attached(bufnr, client_id)")) + + insert(text) + + screen:expect { grid = [[ + #include <iostream> | + | + int main() | + { | + int x; | + #ifdef __cplusplus | + std::cout << x << "\n"; | + #else | + printf("%d\n", x); | + #endif | + } | + ^} | + {1:~ }| + {1:~ }| + {1:~ }| + | + ]] } + end) + + it('does not send delta requests if not supported by server', function() + exec_lua([[ + local legend, response, edit_response = ... + server2 = _create_server({ + capabilities = { + semanticTokensProvider = { + full = { delta = false }, + legend = vim.fn.json_decode(legend), + }, + }, + handlers = { + ['textDocument/semanticTokens/full'] = function() + return vim.fn.json_decode(response) + end, + ['textDocument/semanticTokens/full/delta'] = function() + return vim.fn.json_decode(edit_response) + end, + } + }) + bufnr = vim.api.nvim_get_current_buf() + vim.api.nvim_win_set_buf(0, bufnr) + client_id = vim.lsp.start({ name = 'dummy', cmd = server2.cmd }) + ]], legend, response, edit_response) + + insert(text) + screen:expect { grid = [[ + #include <iostream> | + | + int {8:main}() | + { | + int {7:x}; | + #ifdef {5:__cplusplus} | + {4:std}::{2:cout} << {2:x} << "\n"; | + {6:#else} | + {6: printf("%d\n", x);} | + {6:#endif} | + } | + ^} | + {1:~ }| + {1:~ }| + {1:~ }| + | + ]] } + feed_command('%s/int x/int x()/') + feed_command('noh') + + -- the highlights don't change because our fake server sent the exact + -- same result for the same method (the full request). "x" would have + -- changed to highlight index 3 had we sent a delta request + screen:expect { grid = [[ + #include <iostream> | + | + int {8:main}() | + { | + ^int {7:x}(); | + #ifdef {5:__cplusplus} | + {4:std}::{2:cout} << {2:x} << "\n"; | + {6:#else} | + {6: printf("%d\n", x);} | + {6:#endif} | + } | + } | + {1:~ }| + {1:~ }| + {1:~ }| + :noh | + ]] } + local messages = exec_lua('return server2.messages') + local token_request_count = 0 + for _, message in ipairs(messages) do + assert(message.method ~= 'textDocument/semanticTokens/full/delta', 'delta request received') + if message.method == 'textDocument/semanticTokens/full' then + token_request_count = token_request_count + 1 + end + end + eq(2, token_request_count) + end) + end) + + describe('token array decoding', function() + for _, test in ipairs({ + { + it = 'clangd-15 on C', + text = [[char* foo = "\n";]], + response = [[{"data": [0, 6, 3, 0, 8193], "resultId": "1"}]], + legend = [[{ + "tokenTypes": [ + "variable", "variable", "parameter", "function", "method", "function", "property", "variable", "class", "interface", "enum", "enumMember", "type", "type", "unknown", "namespace", "typeParameter", "concept", "type", "macro", "comment" + ], + "tokenModifiers": [ + "declaration", "deprecated", "deduced", "readonly", "static", "abstract", "virtual", "dependentName", "defaultLibrary", "usedAsMutableReference", "functionScope", "classScope", "fileScope", "globalScope" + ] + }]], + expected = { + { + line = 0, + modifiers = { + 'declaration', + 'globalScope', + }, + start_col = 6, + end_col = 9, + type = 'variable', + extmark_added = true, + }, + }, + }, + { + it = 'clangd-15 on C++', + text = [[#include <iostream> +int main() +{ + #ifdef __cplusplus + const int x = 1; + std::cout << x << std::endl; + #else + comment + #endif +}]], + response = [[{"data": [1, 4, 4, 3, 8193, 2, 9, 11, 19, 8192, 1, 12, 1, 1, 1033, 1, 2, 3, 15, 8448, 0, 5, 4, 0, 8448, 0, 8, 1, 1, 1032, 0, 5, 3, 15, 8448, 0, 5, 4, 3, 8448, 1, 0, 7, 20, 0, 1, 0, 11, 20, 0, 1, 0, 8, 20, 0], "resultId": "1"}]], + legend = [[{ + "tokenTypes": [ + "variable", "variable", "parameter", "function", "method", "function", "property", "variable", "class", "interface", "enum", "enumMember", "type", "type", "unknown", "namespace", "typeParameter", "concept", "type", "macro", "comment" + ], + "tokenModifiers": [ + "declaration", "deprecated", "deduced", "readonly", "static", "abstract", "virtual", "dependentName", "defaultLibrary", "usedAsMutableReference", "functionScope", "classScope", "fileScope", "globalScope" + ] + }]], + expected = { + { -- main + line = 1, + modifiers = { 'declaration', 'globalScope' }, + start_col = 4, + end_col = 8, + type = 'function', + extmark_added = true, + }, + { -- __cplusplus + line = 3, + modifiers = { 'globalScope' }, + start_col = 9, + end_col = 20, + type = 'macro', + extmark_added = true, + }, + { -- x + line = 4, + modifiers = { 'declaration', 'readonly', 'functionScope' }, + start_col = 12, + end_col = 13, + type = 'variable', + extmark_added = true, + }, + { -- std + line = 5, + modifiers = { 'defaultLibrary', 'globalScope' }, + start_col = 2, + end_col = 5, + type = 'namespace', + extmark_added = true, + }, + { -- cout + line = 5, + modifiers = { 'defaultLibrary', 'globalScope' }, + start_col = 7, + end_col = 11, + type = 'variable', + extmark_added = true, + }, + { -- x + line = 5, + modifiers = { 'readonly', 'functionScope' }, + start_col = 15, + end_col = 16, + type = 'variable', + extmark_added = true, + }, + { -- std + line = 5, + modifiers = { 'defaultLibrary', 'globalScope' }, + start_col = 20, + end_col = 23, + type = 'namespace', + extmark_added = true, + }, + { -- endl + line = 5, + modifiers = { 'defaultLibrary', 'globalScope' }, + start_col = 25, + end_col = 29, + type = 'function', + extmark_added = true, + }, + { -- #else comment #endif + line = 6, + modifiers = {}, + start_col = 0, + end_col = 7, + type = 'comment', + extmark_added = true, + }, + { + line = 7, + modifiers = {}, + start_col = 0, + end_col = 11, + type = 'comment', + extmark_added = true, + }, + { + line = 8, + modifiers = {}, + start_col = 0, + end_col = 8, + type = 'comment', + extmark_added = true, + }, + }, + }, + { + it = 'sumneko_lua', + text = [[-- comment +local a = 1 +b = "as"]], + response = [[{"data": [0, 0, 10, 17, 0, 1, 6, 1, 8, 1, 1, 0, 1, 8, 8]}]], + legend = [[{ + "tokenTypes": [ + "namespace", "type", "class", "enum", "interface", "struct", "typeParameter", "parameter", "variable", "property", "enumMember", "event", "function", "method", "macro", "keyword", "modifier", "comment", "string", "number", "regexp", "operator" + ], + "tokenModifiers": [ + "declaration", "definition", "readonly", "static", "deprecated", "abstract", "async", "modification", "documentation", "defaultLibrary" + ] + }]], + expected = { + { + line = 0, + modifiers = {}, + start_col = 0, + end_col = 10, + type = 'comment', -- comment + extmark_added = true, + }, + { + line = 1, + modifiers = { 'declaration' }, -- a + start_col = 6, + end_col = 7, + type = 'variable', + extmark_added = true, + }, + { + line = 2, + modifiers = { 'static' }, -- b (global) + start_col = 0, + end_col = 1, + type = 'variable', + extmark_added = true, + }, + }, + }, + { + it = 'rust-analyzer', + text = [[pub fn main() { + break rust; + /// what? +} +]], + response = [[{"data": [0, 0, 3, 1, 0, 0, 4, 2, 1, 0, 0, 3, 4, 14, 524290, 0, 4, 1, 45, 0, 0, 1, 1, 45, 0, 0, 2, 1, 26, 0, 1, 4, 5, 1, 8192, 0, 6, 4, 52, 0, 0, 4, 1, 48, 0, 1, 4, 9, 0, 1, 1, 0, 1, 26, 0], "resultId": "1"}]], + legend = [[{ + "tokenTypes": [ + "comment", "keyword", "string", "number", "regexp", "operator", "namespace", "type", "struct", "class", "interface", "enum", "enumMember", "typeParameter", "function", "method", "property", "macro", "variable", + "parameter", "angle", "arithmetic", "attribute", "attributeBracket", "bitwise", "boolean", "brace", "bracket", "builtinAttribute", "builtinType", "character", "colon", "comma", "comparison", "constParameter", "derive", + "dot", "escapeSequence", "formatSpecifier", "generic", "label", "lifetime", "logical", "macroBang", "operator", "parenthesis", "punctuation", "selfKeyword", "semicolon", "typeAlias", "toolModule", "union", "unresolvedReference" + ], + "tokenModifiers": [ + "documentation", "declaration", "definition", "static", "abstract", "deprecated", "readonly", "defaultLibrary", "async", "attribute", "callable", "constant", "consuming", "controlFlow", "crateRoot", "injected", "intraDocLink", + "library", "mutable", "public", "reference", "trait", "unsafe" + ] + }]], + expected = { + { + line = 0, + modifiers = {}, + start_col = 0, + end_col = 3, -- pub + type = 'keyword', + extmark_added = true, + }, + { + line = 0, + modifiers = {}, + start_col = 4, + end_col = 6, -- fn + type = 'keyword', + extmark_added = true, + }, + { + line = 0, + modifiers = { 'declaration', 'public' }, + start_col = 7, + end_col = 11, -- main + type = 'function', + extmark_added = true, + }, + { + line = 0, + modifiers = {}, + start_col = 11, + end_col = 12, + type = 'parenthesis', + extmark_added = true, + }, + { + line = 0, + modifiers = {}, + start_col = 12, + end_col = 13, + type = 'parenthesis', + extmark_added = true, + }, + { + line = 0, + modifiers = {}, + start_col = 14, + end_col = 15, + type = 'brace', + extmark_added = true, + }, + { + line = 1, + modifiers = { 'controlFlow' }, + start_col = 4, + end_col = 9, -- break + type = 'keyword', + extmark_added = true, + }, + { + line = 1, + modifiers = {}, + start_col = 10, + end_col = 13, -- rust + type = 'unresolvedReference', + extmark_added = true, + }, + { + line = 1, + modifiers = {}, + start_col = 13, + end_col = 13, + type = 'semicolon', + extmark_added = true, + }, + { + line = 2, + modifiers = { 'documentation' }, + start_col = 4, + end_col = 11, + type = 'comment', -- /// what? + extmark_added = true, + }, + { + line = 3, + modifiers = {}, + start_col = 0, + end_col = 1, + type = 'brace', + extmark_added = true, + }, + }, + }, + }) do + it(test.it, function() + exec_lua(create_server_definition) + exec_lua([[ + local legend, resp = ... + server = _create_server({ + capabilities = { + semanticTokensProvider = { + full = { delta = false }, + legend = vim.fn.json_decode(legend), + }, + }, + handlers = { + ['textDocument/semanticTokens/full'] = function() + return vim.fn.json_decode(resp) + end, + } + }) + bufnr = vim.api.nvim_get_current_buf() + vim.api.nvim_win_set_buf(0, bufnr) + client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) + ]], test.legend, test.response) + + insert(test.text) + + local highlights = exec_lua([[ + local semantic_tokens = vim.lsp.semantic_tokens + return semantic_tokens.__STHighlighter.active[bufnr].client_state[client_id].current_result.highlights + ]]) + eq(test.expected, highlights) + end) + end + end) + + describe('token decoding with deltas', function() + for _, test in ipairs({ + { + it = 'semantic_tokens_delta: clangd-15 on C', + legend = [[{ + "tokenTypes": [ + "variable", "variable", "parameter", "function", "method", "function", "property", "variable", "class", "interface", "enum", "enumMember", "type", "type", "unknown", "namespace", "typeParameter", "concept", "type", "macro", "comment" + ], + "tokenModifiers": [ + "declaration", "deprecated", "deduced", "readonly", "static", "abstract", "virtual", "dependentName", "defaultLibrary", "usedAsMutableReference", "functionScope", "classScope", "fileScope", "globalScope" + ] + }]], + text1 = [[char* foo = "\n";]], + edit = [[ggO<Esc>]], + response1 = [[{"data": [0, 6, 3, 0, 8193], "resultId": "1"}]], + response2 = [[{"edits": [{ "start": 0, "deleteCount": 1, "data": [1] }], "resultId": "2"}]], + expected1 = { + { + line = 0, + modifiers = { + 'declaration', + 'globalScope', + }, + start_col = 6, + end_col = 9, + type = 'variable', + extmark_added = true, + } + }, + expected2 = { + { + line = 1, + modifiers = { + 'declaration', + 'globalScope', + }, + start_col = 6, + end_col = 9, + type = 'variable', + extmark_added = true, + } + }, + }, + { + it = 'response with multiple delta edits', + legend = [[{ + "tokenTypes": [ + "variable", "variable", "parameter", "function", "method", "function", "property", "variable", "class", "interface", "enum", "enumMember", "type", "type", "unknown", "namespace", "typeParameter", "concept", "type", "macro", "comment" + ], + "tokenModifiers": [ + "declaration", "deprecated", "deduced", "readonly", "static", "abstract", "virtual", "dependentName", "defaultLibrary", "usedAsMutableReference", "functionScope", "classScope", "fileScope", "globalScope" + ] + }]], + text1 = dedent([[ + #include <iostream> + + int main() + { + int x; + #ifdef __cplusplus + std::cout << x << "\n"; + #else + printf("%d\n", x); + #endif + }]]), + text2 = [[#include <iostream> + +int main() +{ + int x(); + double y; +#ifdef __cplusplus + std::cout << x << "\n"; +#else + printf("%d\n", x); +#endif +}]], + response1 = [[{ + "data": [ 2, 4, 4, 3, 8193, 2, 8, 1, 1, 1025, 1, 7, 11, 19, 8192, 1, 4, 3, 15, 8448, 0, 5, 4, 0, 8448, 0, 8, 1, 1, 1024, 1, 0, 5, 20, 0, 1, 0, 22, 20, 0, 1, 0, 6, 20, 0 ], + "resultId": 1 + }]], + response2 = [[{ + "edits": [ {"data": [ 2, 8, 1, 3, 8193, 1, 11, 1, 1, 1025 ], "deleteCount": 5, "start": 5}, {"data": [ 0, 8, 1, 3, 8192 ], "deleteCount": 5, "start": 25 } ], + "resultId":"2" + }]], + expected1 = { + { + line = 2, + start_col = 4, + end_col = 8, + modifiers = { 'declaration', 'globalScope' }, + type = 'function', + extmark_added = true, + }, + { + line = 4, + start_col = 8, + end_col = 9, + modifiers = { 'declaration', 'functionScope' }, + type = 'variable', + extmark_added = true, + }, + { + line = 5, + start_col = 7, + end_col = 18, + modifiers = { 'globalScope' }, + type = 'macro', + extmark_added = true, + }, + { + line = 6, + start_col = 4, + end_col = 7, + modifiers = { 'defaultLibrary', 'globalScope' }, + type = 'namespace', + extmark_added = true, + }, + { + line = 6, + start_col = 9, + end_col = 13, + modifiers = { 'defaultLibrary', 'globalScope' }, + type = 'variable', + extmark_added = true, + }, + { + line = 6, + start_col = 17, + end_col = 18, + extmark_added = true, + modifiers = { 'functionScope' }, + type = 'variable', + }, + { + line = 7, + start_col = 0, + end_col = 5, + extmark_added = true, + modifiers = {}, + type = 'comment', + }, + { + line = 8, + end_col = 22, + modifiers = {}, + start_col = 0, + type = 'comment', + extmark_added = true, + }, + { + line = 9, + start_col = 0, + end_col = 6, + modifiers = {}, + type = 'comment', + extmark_added = true, + } + }, + expected2 = { + { + line = 2, + start_col = 4, + end_col = 8, + modifiers = { 'declaration', 'globalScope' }, + type = 'function', + extmark_added = true, + }, + { + line = 4, + start_col = 8, + end_col = 9, + modifiers = { 'declaration', 'globalScope' }, + type = 'function', + extmark_added = true, + }, + { + line = 5, + end_col = 12, + start_col = 11, + modifiers = { 'declaration', 'functionScope' }, + type = 'variable', + extmark_added = true, + }, + { + line = 6, + start_col = 7, + end_col = 18, + modifiers = { 'globalScope' }, + type = 'macro', + extmark_added = true, + }, + { + line = 7, + start_col = 4, + end_col = 7, + modifiers = { 'defaultLibrary', 'globalScope' }, + type = 'namespace', + extmark_added = true, + }, + { + line = 7, + start_col = 9, + end_col = 13, + modifiers = { 'defaultLibrary', 'globalScope' }, + type = 'variable', + extmark_added = true, + }, + { + line = 7, + start_col = 17, + end_col = 18, + extmark_added = true, + modifiers = { 'globalScope' }, + type = 'function', + }, + { + line = 8, + start_col = 0, + end_col = 5, + extmark_added = true, + modifiers = {}, + type = 'comment', + }, + { + line = 9, + end_col = 22, + modifiers = {}, + start_col = 0, + type = 'comment', + extmark_added = true, + }, + { + line = 10, + start_col = 0, + end_col = 6, + modifiers = {}, + type = 'comment', + extmark_added = true, + } + }, + }, + { + it = 'optional token_edit.data on deletion', + legend = [[{ + "tokenTypes": [ + "comment", "keyword", "operator", "string", "number", "regexp", "type", "class", "interface", "enum", "enumMember", "typeParameter", "function", "method", "property", "variable", "parameter", "module", "intrinsic", "selfParameter", "clsParameter", "magicFunction", "builtinConstant", "parenthesis", "curlybrace", "bracket", "colon", "semicolon", "arrow" + ], + "tokenModifiers": [ + "declaration", "static", "abstract", "async", "documentation", "typeHint", "typeHintComment", "readonly", "decorator", "builtin" + ] + }]], + text1 = [[string = "test"]], + text2 = [[]], + response1 = [[{"data": [0, 0, 6, 15, 1], "resultId": "1"}]], + response2 = [[{"edits": [{ "start": 0, "deleteCount": 5 }], "resultId": "2"}]], + expected1 = { + { + line = 0, + modifiers = { + 'declaration', + }, + start_col = 0, + end_col = 6, + type = 'variable', + extmark_added = true, + } + }, + expected2 = { + }, + }, + }) do + it(test.it, function() + exec_lua(create_server_definition) + exec_lua([[ + local legend, resp1, resp2 = ... + server = _create_server({ + capabilities = { + semanticTokensProvider = { + full = { delta = true }, + legend = vim.fn.json_decode(legend), + }, + }, + handlers = { + ['textDocument/semanticTokens/full'] = function() + return vim.fn.json_decode(resp1) + end, + ['textDocument/semanticTokens/full/delta'] = function() + return vim.fn.json_decode(resp2) + end, + } + }) + bufnr = vim.api.nvim_get_current_buf() + vim.api.nvim_win_set_buf(0, bufnr) + client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) + + -- speed up vim.api.nvim_buf_set_lines calls by changing debounce to 10 for these tests + semantic_tokens = vim.lsp.semantic_tokens + vim.schedule(function() + semantic_tokens.stop(bufnr, client_id) + semantic_tokens.start(bufnr, client_id, { debounce = 10 }) + end) + ]], test.legend, test.response1, test.response2) + + insert(test.text1) + + local highlights = exec_lua([[ + return semantic_tokens.__STHighlighter.active[bufnr].client_state[client_id].current_result.highlights + ]]) + + eq(test.expected1, highlights) + + if test.edit then + feed(test.edit) + else + exec_lua([[ + local text = ... + vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, vim.fn.split(text, "\n")) + vim.wait(15) -- wait fot debounce + ]], test.text2) + end + + highlights = exec_lua([[ + return semantic_tokens.__STHighlighter.active[bufnr].client_state[client_id].current_result.highlights + ]]) + + eq(test.expected2, highlights) + end) + end + end) +end) diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 425427be54..5229022564 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -1,8 +1,9 @@ local helpers = require('test.functional.helpers')(after_each) +local lsp_helpers = require('test.functional.plugin.lsp.helpers') local assert_log = helpers.assert_log -local clear = helpers.clear local buf_lines = helpers.buf_lines +local clear = helpers.clear local command = helpers.command local dedent = helpers.dedent local exec_lua = helpers.exec_lua @@ -14,133 +15,28 @@ local pesc = helpers.pesc local insert = helpers.insert local funcs = helpers.funcs local retry = helpers.retry +local stop = helpers.stop local NIL = helpers.NIL local read_file = require('test.helpers').read_file local write_file = require('test.helpers').write_file -local isCI = helpers.isCI +local is_ci = helpers.is_ci local meths = helpers.meths +local is_os = helpers.is_os +local skip = helpers.skip --- Use these to get access to a coroutine so that I can run async tests and use --- yield. -local run, stop = helpers.run, helpers.stop +local clear_notrace = lsp_helpers.clear_notrace +local create_server_definition = lsp_helpers.create_server_definition +local fake_lsp_code = lsp_helpers.fake_lsp_code +local fake_lsp_logfile = lsp_helpers.fake_lsp_logfile +local test_rpc_server = lsp_helpers.test_rpc_server -- TODO(justinmk): hangs on Windows https://github.com/neovim/neovim/pull/11837 -if helpers.pending_win32(pending) then return end - --- Fake LSP server. -local fake_lsp_code = 'test/functional/fixtures/fake-lsp-server.lua' -local fake_lsp_logfile = 'Xtest-fake-lsp.log' +if skip(is_os('win')) then return end teardown(function() os.remove(fake_lsp_logfile) end) -local function clear_notrace() - -- problem: here be dragons - -- solution: don't look for dragons to closely - clear {env={ - NVIM_LUA_NOTRACK="1"; - VIMRUNTIME=os.getenv"VIMRUNTIME"; - }} -end - - -local function fake_lsp_server_setup(test_name, timeout_ms, options, settings) - exec_lua([=[ - lsp = require('vim.lsp') - local test_name, fixture_filename, logfile, timeout, options, settings = ... - TEST_RPC_CLIENT_ID = lsp.start_client { - cmd_env = { - NVIM_LOG_FILE = logfile; - NVIM_LUA_NOTRACK = "1"; - }; - cmd = { - vim.v.progpath, '-Es', '-u', 'NONE', '--headless', - "-c", string.format("lua TEST_NAME = %q", test_name), - "-c", string.format("lua TIMEOUT = %d", timeout), - "-c", "luafile "..fixture_filename, - }; - handlers = setmetatable({}, { - __index = function(t, method) - return function(...) - return vim.rpcrequest(1, 'handler', ...) - end - end; - }); - workspace_folders = {{ - uri = 'file://' .. vim.loop.cwd(), - name = 'test_folder', - }}; - on_init = function(client, result) - TEST_RPC_CLIENT = client - vim.rpcrequest(1, "init", result) - end; - flags = { - allow_incremental_sync = options.allow_incremental_sync or false; - debounce_text_changes = options.debounce_text_changes or 0; - }; - settings = settings; - on_exit = function(...) - vim.rpcnotify(1, "exit", ...) - end; - } - ]=], test_name, fake_lsp_code, fake_lsp_logfile, timeout_ms or 1e3, options or {}, settings or {}) -end - -local function test_rpc_server(config) - if config.test_name then - clear_notrace() - fake_lsp_server_setup(config.test_name, config.timeout_ms or 1e3, config.options, config.settings) - end - local client = setmetatable({}, { - __index = function(_, name) - -- Workaround for not being able to yield() inside __index for Lua 5.1 :( - -- Otherwise I would just return the value here. - return function(...) - return exec_lua([=[ - local name = ... - if type(TEST_RPC_CLIENT[name]) == 'function' then - return TEST_RPC_CLIENT[name](select(2, ...)) - else - return TEST_RPC_CLIENT[name] - end - ]=], name, ...) - end - end; - }) - local code, signal - local function on_request(method, args) - if method == "init" then - if config.on_init then - config.on_init(client, unpack(args)) - end - return NIL - end - if method == 'handler' then - if config.on_handler then - config.on_handler(unpack(args)) - end - end - return NIL - end - local function on_notify(method, args) - if method == 'exit' then - code, signal = unpack(args) - return stop() - end - end - -- TODO specify timeout? - -- run(on_request, on_notify, config.on_setup, 1000) - run(on_request, on_notify, config.on_setup) - if config.on_exit then - config.on_exit(code, signal) - end - stop() - if config.test_name then - exec_lua("vim.api.nvim_exec_autocmds('VimLeavePre', { modeline = false })") - end -end - describe('LSP', function() before_each(function() clear_notrace() @@ -236,9 +132,9 @@ describe('LSP', function() end) it('should invalid cmd argument', function() - eq('Error executing lua: .../lsp.lua:0: cmd: expected list, got nvim', + eq('.../lsp.lua:0: cmd: expected list, got nvim', pcall_err(_cmd_parts, 'nvim')) - eq('Error executing lua: .../lsp.lua:0: cmd argument: expected string, got number', + eq('.../lsp.lua:0: cmd argument: expected string, got number', pcall_err(_cmd_parts, {'nvim', 1})) end) end) @@ -316,7 +212,7 @@ describe('LSP', function() end) it('should succeed with manual shutdown', function() - if isCI() then + if is_ci() then pending('hangs the build on CI #14028, re-enable with freeze timeout #14204') return elseif helpers.skip_fragile(pending) then @@ -420,24 +316,12 @@ describe('LSP', function() it('should detach buffer on bufwipe', function() clear() + exec_lua(create_server_definition) local result = exec_lua([[ - local server = function(dispatchers) - local closing = false - return { - request = function(method, params, callback) - if method == 'initialize' then - callback(nil, { capabilities = {} }) - end - end, - notify = function(...) - end, - is_closing = function() return closing end, - terminate = function() closing = true end - } - end + local server = _create_server() local bufnr = vim.api.nvim_create_buf(false, true) vim.api.nvim_set_current_buf(bufnr) - local client_id = vim.lsp.start({ name = 'detach-dummy', cmd = server }) + local client_id = vim.lsp.start({ name = 'detach-dummy', cmd = server.cmd }) assert(client_id, "lsp.start must return client_id") local client = vim.lsp.get_client_by_id(client_id) local num_attached_before = vim.tbl_count(client.attached_buffers) @@ -572,6 +456,70 @@ describe('LSP', function() } end) + it('BufWritePre does not send notifications if server lacks willSave capabilities', function() + clear() + exec_lua(create_server_definition) + local messages = exec_lua([[ + local server = _create_server({ + capabilities = { + textDocumentSync = { + willSave = false, + willSaveWaitUntil = false, + } + }, + }) + local client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) + local buf = vim.api.nvim_get_current_buf() + vim.api.nvim_exec_autocmds('BufWritePre', { buffer = buf, modeline = false }) + vim.lsp.stop_client(client_id) + return server.messages + ]]) + eq(#messages, 4) + eq(messages[1].method, 'initialize') + eq(messages[2].method, 'initialized') + eq(messages[3].method, 'shutdown') + eq(messages[4].method, 'exit') + end) + + it('BufWritePre sends willSave / willSaveWaitUntil, applies textEdits', function() + clear() + exec_lua(create_server_definition) + local result = exec_lua([[ + local server = _create_server({ + capabilities = { + textDocumentSync = { + willSave = true, + willSaveWaitUntil = true, + } + }, + handlers = { + ['textDocument/willSaveWaitUntil'] = function() + local text_edit = { + range = { + start = { line = 0, character = 0 }, + ['end'] = { line = 0, character = 0 }, + }, + newText = 'Hello' + } + return { text_edit, } + end + }, + }) + local buf = vim.api.nvim_get_current_buf() + local client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) + vim.api.nvim_exec_autocmds('BufWritePre', { buffer = buf, modeline = false }) + vim.lsp.stop_client(client_id) + return { + messages = server.messages, + lines = vim.api.nvim_buf_get_lines(buf, 0, -1, true) + } + ]]) + local messages = result.messages + eq('textDocument/willSave', messages[3].method) + eq('textDocument/willSaveWaitUntil', messages[4].method) + eq({'Hello'}, result.lines) + end) + it('saveas sends didOpen if filename changed', function() local expected_handlers = { { NIL, {}, { method = 'shutdown', client_id = 1 } }, @@ -2470,7 +2418,7 @@ describe('LSP', function() }, uri = "file:///test_a" }, - contanerName = "TestAContainer" + containerName = "TestAContainer" }, { deprecated = false, @@ -2489,7 +2437,7 @@ describe('LSP', function() }, uri = "file:///test_b" }, - contanerName = "TestBContainer" + containerName = "TestBContainer" } } return vim.lsp.util.symbols_to_items(sym_info, nil) @@ -2649,6 +2597,33 @@ describe('LSP', function() eq(10, pos.col) end) + it('jumps to a Location if focus is true via handler', function() + exec_lua(create_server_definition) + local result = exec_lua([[ + local server = _create_server() + local client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) + local result = { + uri = 'file:///fake/uri', + selection = { + start = { line = 0, character = 9 }, + ['end'] = { line = 0, character = 9 } + }, + takeFocus = true, + } + local ctx = { + client_id = client_id, + method = 'window/showDocument', + } + vim.lsp.handlers['window/showDocument'](nil, result, ctx) + vim.lsp.stop_client(client_id) + return { + cursor = vim.api.nvim_win_get_cursor(0) + } + ]]) + eq(1, result.cursor[1]) + eq(9, result.cursor[2]) + end) + it('jumps to a Location if focus not set', function() local pos = show_document(location(0, 9, 0, 9), nil, true) eq(1, pos.line) @@ -3434,51 +3409,60 @@ describe('LSP', function() } end) it('format formats range in visual mode', function() + exec_lua(create_server_definition) local result = exec_lua([[ - local messages = {} - local server = function(dispatchers) - local closing = false - return { - request = function(method, params, callback) - table.insert(messages, { - method = method, - params = params, - }) - if method == 'initialize' then - callback(nil, { - capabilities = { - documentFormattingProvider = true, - documentRangeFormattingProvider = true, - } - }) - end - end, - notify = function(...) - end, - is_closing = function() - return closing - end, - terminate = function() - closing = true - end - } - end + local server = _create_server({ capabilities = { + documentFormattingProvider = true, + documentRangeFormattingProvider = true, + }}) local bufnr = vim.api.nvim_get_current_buf() - local client_id = vim.lsp.start({ name = 'dummy', cmd = server }) + local client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) vim.api.nvim_win_set_buf(0, bufnr) vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, {'foo', 'bar'}) vim.api.nvim_win_set_cursor(0, { 1, 0 }) vim.cmd.normal('v') vim.api.nvim_win_set_cursor(0, { 2, 3 }) vim.lsp.buf.format({ bufnr = bufnr, false }) - return messages + return server.messages ]]) - eq("textDocument/rangeFormatting", result[2].method) + eq("textDocument/rangeFormatting", result[3].method) local expected_range = { start = { line = 0, character = 0 }, ['end'] = { line = 1, character = 4 }, } - eq(expected_range, result[2].params.range) + eq(expected_range, result[3].params.range) + end) + it('Aborts with notify if no clients support requested method', function() + exec_lua(create_server_definition) + exec_lua([[ + vim.notify = function(msg, _) + notify_msg = msg + end + ]]) + local fail_msg = "[LSP] Format request failed, no matching language servers." + local function check_notify(name, formatting, range_formatting) + local timeout_msg = "[LSP][" .. name .. "] timeout" + exec_lua([[ + local formatting, range_formatting, name = ... + local server = _create_server({ capabilities = { + documentFormattingProvider = formatting, + documentRangeFormattingProvider = range_formatting, + }}) + vim.lsp.start({ name = name, cmd = server.cmd }) + notify_msg = nil + vim.lsp.buf.format({ name = name, timeout_ms = 1 }) + ]], formatting, range_formatting, name) + eq(formatting and timeout_msg or fail_msg, exec_lua('return notify_msg')) + exec_lua([[ + notify_msg = nil + vim.lsp.buf.format({ name = name, timeout_ms = 1, range = {start={1, 0}, ['end']={1, 0}}}) + ]]) + eq(range_formatting and timeout_msg or fail_msg, exec_lua('return notify_msg')) + end + check_notify("none", false, false) + check_notify("formatting", true, false) + check_notify("rangeFormatting", false, true) + check_notify("both", true, true) end) end) describe('cmd', function() diff --git a/test/functional/plugin/man_spec.lua b/test/functional/plugin/man_spec.lua index 3e63c5df9a..c6c7d2b03d 100644 --- a/test/functional/plugin/man_spec.lua +++ b/test/functional/plugin/man_spec.lua @@ -6,6 +6,10 @@ local exec_lua = helpers.exec_lua local funcs = helpers.funcs local nvim_prog = helpers.nvim_prog local matches = helpers.matches +local write_file = helpers.write_file +local tmpname = helpers.tmpname +local skip = helpers.skip +local is_ci = helpers.is_ci clear() if funcs.executable('man') == 0 then @@ -156,4 +160,17 @@ describe(':Man', function() local args = {nvim_prog, '--headless', '+autocmd VimLeave * echo "quit works!!"', '+Man!', '+call nvim_input("q")'} matches('quit works!!', funcs.system(args, {'manpage contents'})) end) + + it('reports non-existent man pages for absolute paths', function() + skip(is_ci('cirrus')) + local actual_file = tmpname() + -- actual_file must be an absolute path to an existent file for us to test against it + matches('^/.+', actual_file) + write_file(actual_file, '') + local args = {nvim_prog, '--headless', '+:Man ' .. actual_file, '+q'} + matches(('Error detected while processing command line:\r\n' .. + 'man.lua: "no manual entry for %s"'):format(actual_file), + funcs.system(args, {''})) + os.remove(actual_file) + end) end) diff --git a/test/functional/plugin/shada_spec.lua b/test/functional/plugin/shada_spec.lua index dda8077f05..93cf6d2b77 100644 --- a/test/functional/plugin/shada_spec.lua +++ b/test/functional/plugin/shada_spec.lua @@ -2140,7 +2140,7 @@ end) describe('plugin/shada.vim', function() local epoch = os.date('%Y-%m-%dT%H:%M:%S', 0) - local eol = helpers.iswin() and '\r\n' or '\n' + local eol = helpers.is_os('win') and '\r\n' or '\n' before_each(function() -- Note: reset() is called explicitly in each test. os.remove(fname) diff --git a/test/functional/preload.lua b/test/functional/preload.lua index 74f03eaecf..6a5a2e907e 100644 --- a/test/functional/preload.lua +++ b/test/functional/preload.lua @@ -2,10 +2,11 @@ -- Busted started doing this to help provide more isolation. See issue #62 -- for more information about this. local helpers = require('test.functional.helpers')(nil) -local iswin = helpers.iswin +require('test.functional.ui.screen') local busted = require("busted") +local is_os = helpers.is_os -if iswin() then +if is_os('win') then local ffi = require('ffi') ffi.cdef[[ typedef int errno_t; diff --git a/test/functional/provider/clipboard_spec.lua b/test/functional/provider/clipboard_spec.lua index 401dc84ccc..2c5185a974 100644 --- a/test/functional/provider/clipboard_spec.lua +++ b/test/functional/provider/clipboard_spec.lua @@ -106,8 +106,53 @@ describe('clipboard', function() basic_register_test() end) - it('`:redir @+>` with invalid g:clipboard shows exactly one error #7184', - function() + it('using "+ in Normal mode with invalid g:clipboard always shows error', function() + insert('a') + command("let g:clipboard = 'bogus'") + feed('"+yl') + screen:expect([[ + ^a | + {0:~ }| + {0:~ }| + clipboard: No provider. Try ":checkhealth" or ":h clipboard". | + ]]) + feed('"+p') + screen:expect([[ + a^a | + {0:~ }| + {0:~ }| + clipboard: No provider. Try ":checkhealth" or ":h clipboard". | + ]]) + end) + + it('using clipboard=unnamedplus with invalid g:clipboard shows error once', function() + insert('a') + command("let g:clipboard = 'bogus'") + command('set clipboard=unnamedplus') + feed('yl') + screen:expect([[ + ^a | + {0:~ }| + {0:~ }| + clipboard: No provider. Try ":checkhealth" or ":h clipboard". | + ]]) + feed(':<CR>') + screen:expect([[ + ^a | + {0:~ }| + {0:~ }| + : | + ]]) + feed('p') + screen:expect([[ + a^a | + {0:~ }| + {0:~ }| + : | + ]]) + end) + + it('`:redir @+>` with invalid g:clipboard shows exactly one error #7184', function() command("let g:clipboard = 'bogus'") feed_command('redir @+> | :silent echo system("cat CONTRIBUTING.md") | redir END') screen:expect([[ @@ -118,8 +163,7 @@ describe('clipboard', function() ]]) end) - it('`:redir @+>|bogus_cmd|redir END` + invalid g:clipboard must not recurse #7184', - function() + it('`:redir @+>|bogus_cmd|redir END` + invalid g:clipboard must not recurse #7184', function() command("let g:clipboard = 'bogus'") feed_command('redir @+> | bogus_cmd | redir END') screen:expect{grid=[[ diff --git a/test/functional/provider/perl_spec.lua b/test/functional/provider/perl_spec.lua index aff5e36e24..ce92831f4c 100644 --- a/test/functional/provider/perl_spec.lua +++ b/test/functional/provider/perl_spec.lua @@ -9,6 +9,8 @@ local curbufmeths = helpers.curbufmeths local insert = helpers.insert local expect = helpers.expect local feed = helpers.feed +local is_os = helpers.is_os +local skip = helpers.skip do clear() @@ -24,7 +26,7 @@ before_each(function() end) describe('legacy perl provider', function() - if helpers.pending_win32(pending) then return end + skip(is_os('win')) it('feature test', function() eq(1, eval('has("perl")')) @@ -68,7 +70,7 @@ describe('legacy perl provider', function() end) describe('perl provider', function() - if helpers.pending_win32(pending) then return end + skip(is_os('win')) teardown(function () os.remove('Xtest-perl-hello.pl') os.remove('Xtest-perl-hello-plugin.pl') diff --git a/test/functional/shada/compatibility_spec.lua b/test/functional/shada/compatibility_spec.lua index a5ef60d91f..fb656735dd 100644 --- a/test/functional/shada/compatibility_spec.lua +++ b/test/functional/shada/compatibility_spec.lua @@ -12,7 +12,7 @@ local wshada, sdrcmd, shada_fname = get_shada_rw('Xtest-functional-shada-compati local mock_file_path = '/a/b/' local mock_file_path2 = '/d/e/' -if helpers.iswin() then +if helpers.is_os('win') then mock_file_path = 'C:/a/' mock_file_path2 = 'C:/d/' end diff --git a/test/functional/shada/merging_spec.lua b/test/functional/shada/merging_spec.lua index 2d44b0a950..da2fbbe029 100644 --- a/test/functional/shada/merging_spec.lua +++ b/test/functional/shada/merging_spec.lua @@ -14,7 +14,7 @@ local wshada, sdrcmd, shada_fname = get_shada_rw('Xtest-functional-shada-merging.shada') local mock_file_path = '/a/b/' -if helpers.iswin() then +if helpers.is_os('win') then mock_file_path = 'C:/a/' end diff --git a/test/functional/shada/shada_spec.lua b/test/functional/shada/shada_spec.lua index f5a81eb2ef..88a99d9b55 100644 --- a/test/functional/shada/shada_spec.lua +++ b/test/functional/shada/shada_spec.lua @@ -5,6 +5,8 @@ local meths, nvim_command, funcs, eq = local write_file, spawn, set_session, nvim_prog, exc_exec = helpers.write_file, helpers.spawn, helpers.set_session, helpers.nvim_prog, helpers.exc_exec +local is_os = helpers.is_os +local skip = helpers.skip local lfs = require('lfs') local paths = require('test.cmakeconfig.paths') @@ -248,7 +250,7 @@ describe('ShaDa support code', function() end) it('does not crash when ShaDa file directory is not writable', function() - if helpers.pending_win32(pending) then return end + skip(is_os('win')) funcs.mkdir(dirname, '', 0) eq(0, funcs.filewritable(dirname)) diff --git a/test/functional/terminal/altscreen_spec.lua b/test/functional/terminal/altscreen_spec.lua index e4e1aa5fa2..cbe5e06005 100644 --- a/test/functional/terminal/altscreen_spec.lua +++ b/test/functional/terminal/altscreen_spec.lua @@ -6,7 +6,7 @@ local feed_data = thelpers.feed_data local enter_altscreen = thelpers.enter_altscreen local exit_altscreen = thelpers.exit_altscreen -if helpers.pending_win32(pending) then return end +if helpers.skip(helpers.is_os('win')) then return end describe(':terminal altscreen', function() local screen diff --git a/test/functional/terminal/api_spec.lua b/test/functional/terminal/api_spec.lua index 5305b8af9c..724791343d 100644 --- a/test/functional/terminal/api_spec.lua +++ b/test/functional/terminal/api_spec.lua @@ -2,7 +2,7 @@ local helpers = require('test.functional.helpers')(after_each) local child_session = require('test.functional.terminal.helpers') local ok = helpers.ok -if helpers.pending_win32(pending) then return end +if helpers.skip(helpers.is_os('win')) then return end describe('api', function() local screen @@ -19,6 +19,16 @@ describe('api', function() end) it("qa! RPC request during insert-mode", function() + screen:expect{grid=[[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {4:~ }| + | + {3:-- TERMINAL --} | + ]]} + -- Start the socket from the child nvim. child_session.feed_data(":echo serverstart('"..socket_name.."')\n") @@ -67,4 +77,3 @@ describe('api', function() socket_session1:request("nvim_command", "qa!") end) end) - diff --git a/test/functional/terminal/buffer_spec.lua b/test/functional/terminal/buffer_spec.lua index 9d10f43ec6..9c8b983ff7 100644 --- a/test/functional/terminal/buffer_spec.lua +++ b/test/functional/terminal/buffer_spec.lua @@ -15,6 +15,8 @@ local matches = helpers.matches local exec_lua = helpers.exec_lua local sleep = helpers.sleep local funcs = helpers.funcs +local is_os = helpers.is_os +local skip = helpers.skip describe(':terminal buffer', function() local screen @@ -200,7 +202,7 @@ describe(':terminal buffer', function() -- Save the buffer number of the terminal for later testing. local tbuf = eval('bufnr("%")') - local exitcmd = helpers.iswin() + local exitcmd = helpers.is_os('win') and "['cmd', '/c', 'exit']" or "['sh', '-c', 'exit']" source([[ @@ -262,7 +264,7 @@ describe(':terminal buffer', function() end) it('it works with set rightleft #11438', function() - if helpers.pending_win32(pending) then return end + skip(is_os('win')) local columns = eval('&columns') feed(string.rep('a', columns)) command('set rightleft') @@ -410,6 +412,14 @@ describe('on_lines does not emit out-of-bounds line indexes when', function() feed_command('bdelete!') eq('', exec_lua([[return _G.cb_error]])) end) + + it('runs TextChangedT event', function() + meths.set_var('called', 0) + command('autocmd TextChangedT * ++once let g:called = 1') + feed_command('terminal') + feed('iaa') + eq(1, meths.get_var('called')) + end) end) it('terminal truncates number of composing characters to 5', function() diff --git a/test/functional/terminal/channel_spec.lua b/test/functional/terminal/channel_spec.lua index b5f3c2bd31..2ca7cdb0a2 100644 --- a/test/functional/terminal/channel_spec.lua +++ b/test/functional/terminal/channel_spec.lua @@ -7,6 +7,7 @@ local command = helpers.command local pcall_err = helpers.pcall_err local feed = helpers.feed local poke_eventloop = helpers.poke_eventloop +local is_os = helpers.is_os describe('terminal channel is closed and later released if', function() local screen @@ -92,3 +93,17 @@ describe('terminal channel is closed and later released if', function() eq(chans - 1, eval('len(nvim_list_chans())')) end) end) + +it('chansend sends lines to terminal channel in proper order', function() + clear() + local screen = Screen.new(100, 20) + screen:attach() + local shells = is_os('win') and {'cmd.exe', 'pwsh.exe -nop', 'powershell.exe -nop'} or {'sh'} + for _, sh in ipairs(shells) do + command([[bdelete! | let id = termopen(']] .. sh .. [[')]]) + command([[call chansend(id, ['echo "hello"', 'echo "world"', ''])]]) + screen:expect{ + any=[[echo "hello".*echo "world"]] + } + end +end) diff --git a/test/functional/terminal/cursor_spec.lua b/test/functional/terminal/cursor_spec.lua index 2d1c790d2f..98ac03211a 100644 --- a/test/functional/terminal/cursor_spec.lua +++ b/test/functional/terminal/cursor_spec.lua @@ -9,6 +9,8 @@ local matches = helpers.matches local feed_command = helpers.feed_command local hide_cursor = thelpers.hide_cursor local show_cursor = thelpers.show_cursor +local is_os = helpers.is_os +local skip = helpers.skip describe(':terminal cursor', function() local screen @@ -88,7 +90,7 @@ describe(':terminal cursor', function() describe('when invisible', function() it('is not highlighted and is detached from screen cursor', function() - if helpers.pending_win32(pending) then return end + skip(is_os('win')) hide_cursor() screen:expect([[ tty ready | @@ -361,7 +363,7 @@ describe('buffer cursor position is correct in terminal without number column', end) describe('in a line with single-cell composed multibyte characters and no trailing spaces,', function() - if helpers.pending_win32(pending) then return end -- These tests fail on Windows. Encoding problem? + if skip(is_os('win'), "Encoding problem?") then return end before_each(function() setup_ex_register('µ̳µ̳µ̳µ̳µ̳µ̳µ̳µ̳') @@ -444,7 +446,7 @@ describe('buffer cursor position is correct in terminal without number column', end) describe('in a line with double-cell multibyte characters and no trailing spaces,', function() - if helpers.pending_win32(pending) then return end -- These tests fail on Windows. Encoding problem? + skip(is_os('win'), "Encoding problem?") before_each(function() setup_ex_register('哦哦哦哦哦哦哦哦') @@ -741,7 +743,7 @@ describe('buffer cursor position is correct in terminal with number column', fun end) describe('in a line with single-cell composed multibyte characters and no trailing spaces,', function() - if helpers.pending_win32(pending) then return end -- These tests fail on Windows. Encoding problem? + if skip(is_os('win'), "Encoding problem?") then return end before_each(function() setup_ex_register('µ̳µ̳µ̳µ̳µ̳µ̳µ̳µ̳') @@ -824,7 +826,7 @@ describe('buffer cursor position is correct in terminal with number column', fun end) describe('in a line with double-cell multibyte characters and no trailing spaces,', function() - if helpers.pending_win32(pending) then return end -- These tests fail on Windows. Encoding problem? + skip(is_os('win'), "Encoding problem?") before_each(function() setup_ex_register('哦哦哦哦哦哦哦哦') diff --git a/test/functional/terminal/edit_spec.lua b/test/functional/terminal/edit_spec.lua index aeb4b7cc2e..80287bb3d0 100644 --- a/test/functional/terminal/edit_spec.lua +++ b/test/functional/terminal/edit_spec.lua @@ -36,7 +36,7 @@ describe(':edit term://*', function() end) it("runs TermOpen early enough to set buffer-local 'scrollback'", function() - if helpers.pending_win32(pending) then return end + if helpers.skip(helpers.is_os('win')) then return end local columns, lines = 20, 4 local scr = get_screen(columns, lines) local rep = 97 diff --git a/test/functional/terminal/ex_terminal_spec.lua b/test/functional/terminal/ex_terminal_spec.lua index 36f9f90143..6b7e93a864 100644 --- a/test/functional/terminal/ex_terminal_spec.lua +++ b/test/functional/terminal/ex_terminal_spec.lua @@ -8,9 +8,10 @@ local feed_command, eval = helpers.feed_command, helpers.eval local funcs = helpers.funcs local retry = helpers.retry local ok = helpers.ok -local iswin = helpers.iswin local command = helpers.command -local uname = helpers.uname +local skip = helpers.skip +local is_os = helpers.is_os +local is_ci = helpers.is_ci describe(':terminal', function() local screen @@ -46,11 +47,8 @@ describe(':terminal', function() end) it("reads output buffer on terminal reporting #4151", function() - if uname() == 'freebsd' then - pending('Failing FreeBSD test') - end - if helpers.pending_win32(pending) then return end - if iswin() then + skip(is_ci('cirrus') or is_os('win')) + if is_os('win') then feed_command([[terminal powershell -NoProfile -NoLogo -Command Write-Host -NoNewline "\"$([char]27)[6n\""; Start-Sleep -Milliseconds 500 ]]) else feed_command([[terminal printf '\e[6n'; sleep 0.5 ]]) @@ -59,7 +57,7 @@ describe(':terminal', function() end) it("in normal-mode :split does not move cursor", function() - if iswin() then + if is_os('win') then feed_command([[terminal for /L \\%I in (1,0,2) do ( echo foo & ping -w 100 -n 1 127.0.0.1 > nul )]]) else feed_command([[terminal while true; do echo foo; sleep .1; done]]) @@ -146,7 +144,7 @@ describe(':terminal (with fake shell)', function() end it('with no argument, acts like termopen()', function() - if helpers.pending_win32(pending) then return end + skip(is_os('win')) terminal_with_fake_shell() retry(nil, 4 * screen.timeout, function() screen:expect([[ @@ -170,7 +168,7 @@ describe(':terminal (with fake shell)', function() end) it("with no argument, but 'shell' has arguments, acts like termopen()", function() - if helpers.pending_win32(pending) then return end + skip(is_os('win')) nvim('set_option', 'shell', testprg('shell-test')..' -t jeff') terminal_with_fake_shell() screen:expect([[ @@ -182,7 +180,7 @@ describe(':terminal (with fake shell)', function() end) it('executes a given command through the shell', function() - if helpers.pending_win32(pending) then return end + skip(is_os('win')) command('set shellxquote=') -- win: avoid extra quotes terminal_with_fake_shell('echo hi') screen:expect([[ @@ -194,7 +192,7 @@ describe(':terminal (with fake shell)', function() end) it("executes a given command through the shell, when 'shell' has arguments", function() - if helpers.pending_win32(pending) then return end + skip(is_os('win')) nvim('set_option', 'shell', testprg('shell-test')..' -t jeff') command('set shellxquote=') -- win: avoid extra quotes terminal_with_fake_shell('echo hi') @@ -207,7 +205,7 @@ describe(':terminal (with fake shell)', function() end) it('allows quotes and slashes', function() - if helpers.pending_win32(pending) then return end + skip(is_os('win')) command('set shellxquote=') -- win: avoid extra quotes terminal_with_fake_shell([[echo 'hello' \ "world"]]) screen:expect([[ @@ -244,7 +242,7 @@ describe(':terminal (with fake shell)', function() end) it('works with :find', function() - if helpers.pending_win32(pending) then return end + skip(is_os('win')) terminal_with_fake_shell() screen:expect([[ ^ready $ | @@ -255,7 +253,7 @@ describe(':terminal (with fake shell)', function() eq('term://', string.match(eval('bufname("%")'), "^term://")) feed([[<C-\><C-N>]]) feed_command([[find */shadacat.py]]) - if iswin() then + if is_os('win') then eq('scripts\\shadacat.py', eval('bufname("%")')) else eq('scripts/shadacat.py', eval('bufname("%")')) @@ -263,7 +261,7 @@ describe(':terminal (with fake shell)', function() end) it('works with gf', function() - if helpers.pending_win32(pending) then return end + skip(is_os('win')) command('set shellxquote=') -- win: avoid extra quotes terminal_with_fake_shell([[echo "scripts/shadacat.py"]]) retry(nil, 4 * screen.timeout, function() diff --git a/test/functional/terminal/helpers.lua b/test/functional/terminal/helpers.lua index d69f3207f1..7247361649 100644 --- a/test/functional/terminal/helpers.lua +++ b/test/functional/terminal/helpers.lua @@ -4,19 +4,31 @@ local helpers = require('test.functional.helpers')(nil) local Screen = require('test.functional.ui.screen') local testprg = helpers.testprg +local exec_lua = helpers.exec_lua local feed_command, nvim = helpers.feed_command, helpers.nvim local function feed_data(data) - -- A string containing NUL bytes is not converted to a Blob when - -- calling nvim_set_var() API, so convert it using Lua instead. - nvim('exec_lua', 'vim.g.term_data = ...', {data}) - nvim('command', 'call jobsend(b:terminal_job_id, term_data)') + if type(data) == 'table' then + data = table.concat(data, '\n') + end + exec_lua('vim.api.nvim_chan_send(vim.b.terminal_job_id, ...)', data) end local function feed_termcode(data) - -- feed with the job API - nvim('command', 'call jobsend(b:terminal_job_id, "\\x1b'..data..'")') + feed_data('\027' .. data) +end + +local function make_lua_executor(session) + return function(code, ...) + local status, rv = session:request('nvim_exec_lua', code, {...}) + if not status then + session:stop() + error(rv[2]) + end + return rv + end end + -- some helpers for controlling the terminal. the codes were taken from -- infocmp xterm-256color which is less what libvterm understands -- civis/cnorm @@ -62,7 +74,10 @@ local function screen_setup(extra_rows, command, cols, opts) [9] = {foreground = 4}, [10] = {foreground = 121}, -- "Press ENTER" in embedded :terminal session. [11] = {foreground = tonumber('0x00000b')}, - [12] = {reverse = true, foreground = tonumber('0x000079')}, + [12] = {underline = true}, + [13] = {underline = true, reverse = true}, + [14] = {underline = true, reverse = true, bold = true}, + [15] = {underline = true, foreground = 12}, }) screen:attach(opts or {rgb=false}) @@ -109,6 +124,7 @@ end return { feed_data = feed_data, feed_termcode = feed_termcode, + make_lua_executor = make_lua_executor, hide_cursor = hide_cursor, show_cursor = show_cursor, enter_altscreen = enter_altscreen, diff --git a/test/functional/terminal/highlight_spec.lua b/test/functional/terminal/highlight_spec.lua index a119d4acd3..2ac45771d4 100644 --- a/test/functional/terminal/highlight_spec.lua +++ b/test/functional/terminal/highlight_spec.lua @@ -7,6 +7,8 @@ local nvim_prog_abs = helpers.nvim_prog_abs local eq, eval = helpers.eq, helpers.eval local funcs = helpers.funcs local nvim_set = helpers.nvim_set +local is_os = helpers.is_os +local skip = helpers.skip describe(':terminal highlight', function() local screen @@ -58,7 +60,7 @@ describe(':terminal highlight', function() end) local function pass_attrs() - if helpers.pending_win32(pending) then return end + skip(is_os('win')) screen:expect(sub([[ tty ready | {NUM:text}text{10: } | @@ -73,7 +75,7 @@ describe(':terminal highlight', function() it('will pass the corresponding attributes', pass_attrs) it('will pass the corresponding attributes on scrollback', function() - if helpers.pending_win32(pending) then return end + skip(is_os('win')) pass_attrs() local lines = {} for i = 1, 8 do @@ -197,7 +199,7 @@ describe(':terminal highlight forwarding', function() end) it('will handle cterm and rgb attributes', function() - if helpers.pending_win32(pending) then return end + skip(is_os('win')) thelpers.set_fg(3) thelpers.feed_data('text') thelpers.feed_termcode('[38:2:255:128:0m') @@ -249,7 +251,7 @@ describe(':terminal highlight with custom palette', function() end) it('will use the custom color', function() - if helpers.pending_win32(pending) then return end + skip(is_os('win')) thelpers.set_fg(3) thelpers.feed_data('text') thelpers.clear_attrs() diff --git a/test/functional/terminal/mouse_spec.lua b/test/functional/terminal/mouse_spec.lua index 6e2c851df7..50c8f5e7df 100644 --- a/test/functional/terminal/mouse_spec.lua +++ b/test/functional/terminal/mouse_spec.lua @@ -3,6 +3,8 @@ local thelpers = require('test.functional.terminal.helpers') local clear, eq, eval = helpers.clear, helpers.eq, helpers.eval local feed, nvim, command = helpers.feed, helpers.nvim, helpers.command local feed_data = thelpers.feed_data +local is_os = helpers.is_os +local skip = helpers.skip describe(':terminal mouse', function() local screen @@ -66,7 +68,7 @@ describe(':terminal mouse', function() end) it('does not leave terminal mode on left-release', function() - if helpers.pending_win32(pending) then return end + skip(is_os('win')) feed('<LeftRelease>') eq('t', eval('mode(1)')) end) @@ -87,7 +89,7 @@ describe(':terminal mouse', function() end) it('will forward mouse press, drag and release to the program', function() - if helpers.pending_win32(pending) then return end + skip(is_os('win')) feed('<LeftMouse><1,2>') screen:expect([[ line27 | @@ -131,7 +133,7 @@ describe(':terminal mouse', function() end) it('will forward mouse scroll to the program', function() - if helpers.pending_win32(pending) then return end + skip(is_os('win')) feed('<ScrollWheelUp><0,0>') screen:expect([[ line27 | @@ -145,7 +147,7 @@ describe(':terminal mouse', function() end) it('dragging and scrolling do not interfere with each other', function() - if helpers.pending_win32(pending) then return end + skip(is_os('win')) feed('<LeftMouse><1,2>') screen:expect([[ line27 | @@ -199,7 +201,7 @@ describe(':terminal mouse', function() end) it('will forward mouse clicks to the program with the correct even if set nu', function() - if helpers.pending_win32(pending) then return end + skip(is_os('win')) command('set number') -- When the display area such as a number is clicked, it returns to the -- normal mode. @@ -230,7 +232,7 @@ describe(':terminal mouse', function() end) describe('with a split window and other buffer', function() - if helpers.pending_win32(pending) then return end + skip(is_os('win')) before_each(function() feed('<c-\\><c-n>:vsp<cr>') screen:expect([[ @@ -287,7 +289,7 @@ describe(':terminal mouse', function() ]]) end) - it('wont lose focus if another window is scrolled', function() + it("won't lose focus if another window is scrolled", function() feed('<ScrollWheelUp><4,0><ScrollWheelUp><4,0>') screen:expect([[ {7: 21 }line │line30 | @@ -310,6 +312,34 @@ describe(':terminal mouse', function() ]]) end) + it("scrolling another window respects 'mousescroll'", function() + command('set mousescroll=ver:1') + feed('<ScrollWheelUp><4,0>') + screen:expect([[ + {7: 26 }line │line30 | + {7: 27 }line │rows: 5, cols: 25 | + {7: 28 }line │rows: 5, cols: 24 | + {7: 29 }line │mouse enabled | + {7: 30 }line │{1: } | + ========== ========== | + {3:-- TERMINAL --} | + ]]) + command('set mousescroll=ver:10') + feed('<ScrollWheelUp><4,0>') + screen:expect([[ + {7: 16 }line │line30 | + {7: 17 }line │rows: 5, cols: 25 | + {7: 18 }line │rows: 5, cols: 24 | + {7: 19 }line │mouse enabled | + {7: 20 }line │{1: } | + ========== ========== | + {3:-- TERMINAL --} | + ]]) + command('set mousescroll=ver:0') + feed('<ScrollWheelUp><4,0>') + screen:expect_unchanged() + end) + it('will lose focus if another window is clicked', function() feed('<LeftMouse><5,1>') screen:expect([[ diff --git a/test/functional/terminal/scrollback_spec.lua b/test/functional/terminal/scrollback_spec.lua index b491cb2735..a4899c8219 100644 --- a/test/functional/terminal/scrollback_spec.lua +++ b/test/functional/terminal/scrollback_spec.lua @@ -3,7 +3,6 @@ local helpers = require('test.functional.helpers')(after_each) local thelpers = require('test.functional.terminal.helpers') local clear, eq, curbuf = helpers.clear, helpers.eq, helpers.curbuf local feed, testprg, feed_command = helpers.feed, helpers.testprg, helpers.feed_command -local iswin = helpers.iswin local eval = helpers.eval local command = helpers.command local matches = helpers.matches @@ -15,6 +14,8 @@ local feed_data = thelpers.feed_data local pcall_err = helpers.pcall_err local exec_lua = helpers.exec_lua local assert_alive = helpers.assert_alive +local skip = helpers.skip +local is_os = helpers.is_os describe(':terminal scrollback', function() local screen @@ -139,7 +140,7 @@ describe(':terminal scrollback', function() describe('and height decreased by 1', function() - if helpers.pending_win32(pending) then return end + if skip(is_os('win')) then return end local function will_hide_top_line() feed([[<C-\><C-N>]]) screen:try_resize(screen._width - 2, screen._height - 1) @@ -185,7 +186,7 @@ describe(':terminal scrollback', function() -- XXX: Can't test this reliably on Windows unless the cursor is _moved_ -- by the resize. http://docs.libuv.org/en/v1.x/signal.html -- See also: https://github.com/rprichard/winpty/issues/110 - if helpers.pending_win32(pending) then return end + if skip(is_os('win')) then return end describe('and the height is decreased by 2', function() before_each(function() @@ -264,7 +265,7 @@ describe(':terminal scrollback', function() -- XXX: Can't test this reliably on Windows unless the cursor is _moved_ -- by the resize. http://docs.libuv.org/en/v1.x/signal.html -- See also: https://github.com/rprichard/winpty/issues/110 - if helpers.pending_win32(pending) then return end + if skip(is_os('win')) then return end local function pop_then_push() screen:try_resize(screen._width, screen._height + 1) screen:expect([[ @@ -346,7 +347,7 @@ end) describe(':terminal prints more lines than the screen height and exits', function() it('will push extra lines to scrollback', function() - if helpers.pending_win32(pending) then return end + skip(is_os('win')) clear() local screen = Screen.new(30, 7) screen:attach({rgb=false}) @@ -396,21 +397,21 @@ describe("'scrollback' option", function() it('set to 0 behaves as 1', function() local screen - if iswin() then + if is_os('win') then screen = thelpers.screen_setup(nil, "['cmd.exe']", 30) else screen = thelpers.screen_setup(nil, "['sh']", 30) end curbufmeths.set_option('scrollback', 0) - feed_data(('%s REP 31 line%s'):format(testprg('shell-test'), iswin() and '\r' or '\n')) + feed_data(('%s REP 31 line%s'):format(testprg('shell-test'), is_os('win') and '\r' or '\n')) screen:expect{any='30: line '} retry(nil, nil, function() expect_lines(7) end) end) it('deletes lines (only) if necessary', function() local screen - if iswin() then + if is_os('win') then command([[let $PROMPT='$$']]) screen = thelpers.screen_setup(nil, "['cmd.exe']", 30) else @@ -423,7 +424,7 @@ describe("'scrollback' option", function() -- Wait for prompt. screen:expect{any='%$'} - feed_data(('%s REP 31 line%s'):format(testprg('shell-test'), iswin() and '\r' or '\n')) + feed_data(('%s REP 31 line%s'):format(testprg('shell-test'), is_os('win') and '\r' or '\n')) screen:expect{any='30: line '} retry(nil, nil, function() expect_lines(33, 2) end) @@ -436,8 +437,8 @@ describe("'scrollback' option", function() -- 'scrollback' option is synchronized with the internal sb_buffer. command('sleep 100m') - feed_data(('%s REP 41 line%s'):format(testprg('shell-test'), iswin() and '\r' or '\n')) - if iswin() then + feed_data(('%s REP 41 line%s'):format(testprg('shell-test'), is_os('win') and '\r' or '\n')) + if is_os('win') then screen:expect{grid=[[ 37: line | 38: line | @@ -461,8 +462,8 @@ describe("'scrollback' option", function() expect_lines(58) -- Verify off-screen state - matches((iswin() and '^36: line[ ]*$' or '^35: line[ ]*$'), eval("getline(line('w0') - 1)")) - matches((iswin() and '^27: line[ ]*$' or '^26: line[ ]*$'), eval("getline(line('w0') - 10)")) + matches((is_os('win') and '^36: line[ ]*$' or '^35: line[ ]*$'), eval("getline(line('w0') - 1)")) + matches((is_os('win') and '^27: line[ ]*$' or '^26: line[ ]*$'), eval("getline(line('w0') - 10)")) end) it('deletes extra lines immediately', function() @@ -606,7 +607,7 @@ describe("pending scrollback line handling", function() end) it("does not crash after nvim_buf_call #14891", function() - if helpers.pending_win32(pending) then return end + skip(is_os('win')) exec_lua [[ local a = vim.api local bufnr = a.nvim_create_buf(false, true) diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index 3c56ad5f79..b28728057f 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -5,15 +5,15 @@ -- http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Bracketed-Paste-Mode local helpers = require('test.functional.helpers')(after_each) -local uname = helpers.uname local thelpers = require('test.functional.terminal.helpers') local Screen = require('test.functional.ui.screen') -local assert_alive = helpers.assert_alive local eq = helpers.eq local feed_command = helpers.feed_command local feed_data = thelpers.feed_data local clear = helpers.clear local command = helpers.command +local dedent = helpers.dedent +local exec = helpers.exec local testprg = helpers.testprg local retry = helpers.retry local nvim_prog = helpers.nvim_prog @@ -22,16 +22,24 @@ local ok = helpers.ok local read_file = helpers.read_file local funcs = helpers.funcs local meths = helpers.meths +local is_ci = helpers.is_ci +local is_os = helpers.is_os +local new_pipename = helpers.new_pipename +local spawn_argv = helpers.spawn_argv +local set_session = helpers.set_session +local feed = helpers.feed +local eval = helpers.eval -if helpers.pending_win32(pending) then return end +if helpers.skip(helpers.is_os('win')) then return end describe('TUI', function() local screen local child_session + local child_exec_lua before_each(function() clear() - local child_server = helpers.new_pipename() + local child_server = new_pipename() screen = thelpers.screen_setup(0, string.format([=[["%s", "--listen", "%s", "-u", "NONE", "-i", "NONE", "--cmd", "%s laststatus=2 background=dark"]]=], nvim_prog, child_server, nvim_set)) @@ -45,6 +53,7 @@ describe('TUI', function() {3:-- TERMINAL --} | ]]) child_session = helpers.connect(child_server) + child_exec_lua = thelpers.make_lua_executor(child_session) end) -- Wait for mode in the child Nvim (avoid "typeahead race" #10826). @@ -67,7 +76,16 @@ describe('TUI', function() it('rapid resize #7572 #7628', function() -- Need buffer rows to provoke the behavior. - feed_data(":edit test/functional/fixtures/bigfile.txt:") + feed_data(":edit test/functional/fixtures/bigfile.txt\n") + screen:expect([[ + {1:0}000;<control>;Cc;0;BN;;;;;N;NULL;;;; | + 0001;<control>;Cc;0;BN;;;;;N;START OF HEADING;;;; | + 0002;<control>;Cc;0;BN;;;;;N;START OF TEXT;;;; | + 0003;<control>;Cc;0;BN;;;;;N;END OF TEXT;;;; | + {5:test/functional/fixtures/bigfile.txt }| + :edit test/functional/fixtures/bigfile.txt | + {3:-- TERMINAL --} | + ]]) command('call jobresize(b:terminal_job_id, 58, 9)') command('call jobresize(b:terminal_job_id, 62, 13)') command('call jobresize(b:terminal_job_id, 100, 42)') @@ -84,25 +102,9 @@ describe('TUI', function() command('call jobresize(b:terminal_job_id, 1, 4)') screen:try_resize(57, 17) command('call jobresize(b:terminal_job_id, 57, 17)') - assert_alive() - end) - - it('resize at startup', function() - -- Issues: #17285 #15044 #11330 - screen:try_resize(50, 10) - feed_command([[call termopen([v:progpath, '--clean', '--cmd', 'let start = reltime() | while v:true | if reltimefloat(reltime(start)) > 2 | break | endif | endwhile']) | sleep 500m | vs new]]) - screen:expect([[ - {1: } │ | - {4:~ }│{4:~ }| - {4:~ }│{4:~ }| - {4:~ }│{4:~ }| - {4:~ }│{4:~ }| - {4:~ }│{5:[No Name] 0,0-1 All}| - {4:~ }│ | - {5:new }{MATCH:<.*[/\]nvim }| - | - {3:-- TERMINAL --} | - ]]) + retry(nil, nil, function() + eq({true, 57}, {child_session:request('nvim_win_get_width', 0)}) + end) end) it('accepts resize while pager is active', function() @@ -634,37 +636,35 @@ describe('TUI', function() tabnew highlight Tabline ctermbg=NONE ctermfg=NONE cterm=underline ]]) - local attrs = screen:get_default_attr_ids() - attrs[11] = {underline = true} screen:expect([[ - {11: + [No Name] + [No Name] }{3: [No Name] }{1: }{11:X}| + {12: + [No Name] + [No Name] }{3: [No Name] }{1: }{12:X}| {1: } | {4:~ }| {4:~ }| {5:[No Name] }| | {3:-- TERMINAL --} | - ]], attrs) + ]]) feed_data('\027[57421;5u') -- CTRL + KP_PAGE_UP screen:expect([[ - {11: + [No Name] }{3: + [No Name] }{11: [No Name] }{1: }{11:X}| + {12: + [No Name] }{3: + [No Name] }{12: [No Name] }{1: }{12:X}| 0123456789/*-{1:+} | = | {4:~ }| {5:[No Name] [+] }| | {3:-- TERMINAL --} | - ]], attrs) + ]]) feed_data('\027[57422;5u') -- CTRL + KP_PAGE_DOWN screen:expect([[ - {11: + [No Name] + [No Name] }{3: [No Name] }{1: }{11:X}| + {12: + [No Name] + [No Name] }{3: [No Name] }{1: }{12:X}| {1: } | {4:~ }| {4:~ }| {5:[No Name] }| | {3:-- TERMINAL --} | - ]], attrs) + ]]) end) it('mouse events work with right-click menu', function() @@ -679,31 +679,28 @@ describe('TUI', function() highlight Pmenu ctermbg=NONE ctermfg=NONE cterm=underline,reverse highlight PmenuSel ctermbg=NONE ctermfg=NONE cterm=underline,reverse,bold ]]) - local attrs = screen:get_default_attr_ids() - attrs[11] = {underline = true, reverse = true} - attrs[12] = {underline = true, reverse = true, bold = true} meths.input_mouse('right', 'press', '', 0, 0, 4) screen:expect([[ {1:p}opup menu test | - {4:~ }{11: foo }{4: }| - {4:~ }{11: bar }{4: }| - {4:~ }{11: baz }{4: }| + {4:~ }{13: foo }{4: }| + {4:~ }{13: bar }{4: }| + {4:~ }{13: baz }{4: }| {5:[No Name] [+] }| | {3:-- TERMINAL --} | - ]], attrs) + ]]) meths.input_mouse('right', 'release', '', 0, 0, 4) screen:expect_unchanged() meths.input_mouse('move', '', '', 0, 3, 6) screen:expect([[ {1:p}opup menu test | - {4:~ }{11: foo }{4: }| - {4:~ }{11: bar }{4: }| - {4:~ }{12: baz }{4: }| + {4:~ }{13: foo }{4: }| + {4:~ }{13: bar }{4: }| + {4:~ }{14: baz }{4: }| {5:[No Name] [+] }| | {3:-- TERMINAL --} | - ]], attrs) + ]]) meths.input_mouse('left', 'press', '', 0, 2, 6) screen:expect([[ {1:p}opup menu test | @@ -713,7 +710,7 @@ describe('TUI', function() {5:[No Name] [+] }| :let g:menustr = 'bar' | {3:-- TERMINAL --} | - ]], attrs) + ]]) meths.input_mouse('left', 'release', '', 0, 2, 6) screen:expect_unchanged() end) @@ -819,12 +816,11 @@ describe('TUI', function() end) it('paste: terminal mode', function() - if os.getenv('GITHUB_ACTIONS') ~= nil then + if is_ci('github') then pending("tty-test complains about not owning the terminal -- actions/runner#241") - return end - feed_data(':set statusline=^^^^^^^\n') - feed_data(':terminal '..testprg('tty-test')..'\n') + child_exec_lua('vim.o.statusline="^^^^^^^"') + child_exec_lua('vim.cmd.terminal(...)', testprg('tty-test')) feed_data('i') screen:expect{grid=[[ tty ready | @@ -1086,8 +1082,7 @@ describe('TUI', function() wait_for_mode('i') -- "bracketed paste" feed_data('\027[200~'..expected..'\027[201~') - -- FIXME: Data race between the two feeds - if uname() == 'freebsd' then screen:sleep(1) end + expect_child_buf_lines({expected}) feed_data(' end') expected = expected..' end' screen:expect([[ @@ -1206,6 +1201,15 @@ describe('TUI', function() it('paste: split "start paste" code', function() feed_data('i') + screen:expect{grid=[[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] }| + {3:-- INSERT --} | + {3:-- TERMINAL --} | + ]]} -- Send split "start paste" sequence. feed_data('\027[2') feed_data('00~pasted from terminal\027[201~') @@ -1222,6 +1226,15 @@ describe('TUI', function() it('paste: split "stop paste" code', function() feed_data('i') + screen:expect{grid=[[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] }| + {3:-- INSERT --} | + {3:-- TERMINAL --} | + ]]} -- Send split "stop paste" sequence. feed_data('\027[200~pasted from terminal\027[20') feed_data('1~') @@ -1247,6 +1260,15 @@ describe('TUI', function() end)(vim.paste) ]], {}) feed_data('i') + screen:expect{grid=[[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] }| + {3:-- INSERT --} | + {3:-- TERMINAL --} | + ]]} feed_data('\027[200~pasted') -- phase 1 screen:expect([[ pasted{1: } | @@ -1286,6 +1308,7 @@ describe('TUI', function() [7] = {reverse = true, foreground = Screen.colors.SeaGreen4}, [8] = {foreground = Screen.colors.SeaGreen4}, [9] = {bold = true, foreground = Screen.colors.Blue1}, + [10] = {foreground = Screen.colors.Blue}, }) feed_data(':hi SpecialKey ctermfg=3 guifg=SeaGreen\n') @@ -1306,9 +1329,9 @@ describe('TUI', function() feed_data(':set termguicolors\n') screen:expect([[ {7:^}{8:G} | - {9:~ }| - {9:~ }| - {9:~ }| + {9:~}{10: }| + {9:~}{10: }| + {9:~}{10: }| {3:[No Name] [+] }| :set termguicolors | {4:-- TERMINAL --} | @@ -1327,9 +1350,8 @@ describe('TUI', function() end) it('forwards :term palette colors with termguicolors', function() - if os.getenv('GITHUB_ACTIONS') ~= nil then + if is_ci('github') then pending("tty-test complains about not owning the terminal -- actions/runner#241") - return end screen:set_rgb_cterm(true) screen:set_default_attr_ids({ @@ -1340,12 +1362,9 @@ describe('TUI', function() [5] = {{foreground = tonumber('0xff8000')}, {}}, }) - feed_data(':set statusline=^^^^^^^\n') - feed_data(':set termguicolors\n') - feed_data(':terminal '..testprg('tty-test')..'\n') - -- Depending on platform the above might or might not fit in the cmdline - -- so clear it for consistent behavior. - feed_data(':\027') + child_exec_lua('vim.o.statusline="^^^^^^^"') + child_exec_lua('vim.o.termguicolors=true') + child_exec_lua('vim.cmd.terminal(...)', testprg('tty-test')) screen:expect{grid=[[ {1:t}ty ready | | @@ -1384,7 +1403,7 @@ describe('TUI', function() | {4:~ }| {5: }| - [[['chan', 0], ['height', 6], ['override', v:false| + [[['chan', 1], ['height', 6], ['override', v:false| ], ['rgb', v:false], ['width', 50]]] | {10:Press ENTER or type command to continue}{1: } | {3:-- TERMINAL --} | @@ -1392,18 +1411,14 @@ describe('TUI', function() end) it('allows grid to assume wider ambiguous-width characters than host terminal #19686', function() - child_session:request('nvim_buf_set_lines', 0, 0, 0, true, { ('℃'):rep(60), ('℃'):rep(60) }) + child_session:request('nvim_buf_set_lines', 0, 0, -1, true, { ('℃'):rep(60), ('℃'):rep(60) }) child_session:request('nvim_win_set_option', 0, 'cursorline', true) child_session:request('nvim_win_set_option', 0, 'list', true) child_session:request('nvim_win_set_option', 0, 'listchars', 'eol:$') - local attrs = screen:get_default_attr_ids() - attrs[11] = {underline = true} -- CursorLine - attrs[12] = {underline = true, reverse = true} -- CursorLine and TermCursor - attrs[13] = {underline = true, foreground = 12} -- CursorLine and NonText feed_data('gg') local singlewidth_screen = [[ - {12:℃}{11:℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃}| - {11:℃℃℃℃℃℃℃℃℃℃}{13:$}{11: }| + {13:℃}{12:℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃}| + {12:℃℃℃℃℃℃℃℃℃℃}{15:$}{12: }| ℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃| ℃℃℃℃℃℃℃℃℃℃{4:$} | {5:[No Name] [+] }| @@ -1413,23 +1428,83 @@ describe('TUI', function() -- When grid assumes "℃" to be double-width but host terminal assumes it to be single-width, the -- second cell of "℃" is a space and the attributes of the "℃" are applied to it. local doublewidth_screen = [[ - {12:℃}{11: ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ }| - {11:℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ }| - {11:℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ }{13:$}{11: }| + {13:℃}{12: ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ }| + {12:℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ }| + {12:℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ }{15:$}{12: }| ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ >{4:@@@}| {5:[No Name] [+] }| | {3:-- TERMINAL --} | ]] - screen:expect(singlewidth_screen, attrs) + screen:expect(singlewidth_screen) child_session:request('nvim_set_option', 'ambiwidth', 'double') - screen:expect(doublewidth_screen, attrs) + screen:expect(doublewidth_screen) child_session:request('nvim_set_option', 'ambiwidth', 'single') - screen:expect(singlewidth_screen, attrs) + screen:expect(singlewidth_screen) child_session:request('nvim_call_function', 'setcellwidths', {{{0x2103, 0x2103, 2}}}) - screen:expect(doublewidth_screen, attrs) + screen:expect(doublewidth_screen) child_session:request('nvim_call_function', 'setcellwidths', {{{0x2103, 0x2103, 1}}}) - screen:expect(singlewidth_screen, attrs) + screen:expect(singlewidth_screen) + end) + + it('draws correctly when cursor_address overflows #21643', function() + helpers.skip(helpers.is_os('mac'), 'FIXME: crashes/errors on macOS') + screen:try_resize(77, 834) + retry(nil, nil, function() + eq({true, 831}, {child_session:request('nvim_win_get_height', 0)}) + end) + -- Use full screen message so that redrawing afterwards is more deterministic. + child_session:notify('nvim_command', 'intro') + screen:expect({any = 'Nvim'}) + -- Going to top-left corner needs 3 bytes. + -- Setting underline attribute needs 9 bytes. + -- With screen width 77, 63857 characters need 829 full screen lines. + -- Drawing each full screen line needs 77 + 2 = 79 bytes (2 bytes for CR LF). + -- The incomplete screen line needs 24 + 3 = 27 bytes. + -- The whole line needs 3 + 9 + 79 * 829 + 27 = 65530 bytes. + -- The cursor_address that comes after will overflow the 65535-byte buffer. + local line = ('a'):rep(63857) .. '℃' + child_session:notify('nvim_exec_lua', [[ + vim.api.nvim_buf_set_lines(0, 0, -1, true, {...}) + vim.o.cursorline = true + ]], {line, 'b'}) + -- Close the :intro message and redraw the lines. + feed_data('\n') + screen:expect( + '{13:a}{12:' .. ('a'):rep(76) .. '}|\n' + .. ('{12:' .. ('a'):rep(77) .. '}|\n'):rep(828) + .. '{12:' .. ('a'):rep(24) .. '℃' .. (' '):rep(52) .. '}|\n' .. dedent([[ + b | + {5:[No Name] [+] }| + | + {3:-- TERMINAL --} |]])) + end) + + it('visual bell (padding) does not crash #21610', function() + feed_data ':set visualbell\n' + screen:expect{grid=[[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] }| + :set visualbell | + {3:-- TERMINAL --} | + ]]} + + -- move left is enough to invoke the bell + feed_data 'h' + -- visual change to show we process events after this + feed_data 'i' + screen:expect{grid=[[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] }| + {3:-- INSERT --} | + {3:-- TERMINAL --} | + ]]} end) end) @@ -1439,6 +1514,35 @@ describe('TUI', function() os.remove('testF') end) + it('resize at startup #17285 #15044 #11330', function() + local screen = Screen.new(50, 10) + screen:set_default_attr_ids({ + [1] = {reverse = true}, + [2] = {bold = true, foreground = Screen.colors.Blue}, + [3] = {bold = true}, + [4] = {foreground = tonumber('0x4040ff'), fg_indexed = true}, + [5] = {bold = true, reverse = true}, + }) + screen:attach() + exec([[ + call termopen([v:progpath, '--clean', '--cmd', 'let start = reltime() | while v:true | if reltimefloat(reltime(start)) > 2 | break | endif | endwhile']) + sleep 500m + vs new + ]]) + screen:expect([[ + ^ │ | + {2:~ }│{4:~ }| + {2:~ }│{4:~ }| + {2:~ }│{4:~ }| + {2:~ }│{4:~ }| + {2:~ }│{4:~ }| + {2:~ }│{5:[No Name] 0,0-1 All}| + {2:~ }│ | + {5:new }{MATCH:<.*[/\]nvim }| + | + ]]) + end) + it('with non-tty (pipe) stdout/stderr', function() local screen = thelpers.screen_setup(0, '"'..nvim_prog ..' -u NONE -i NONE --cmd \'set noswapfile noshowcmd noruler\' --cmd \'normal iabc\' > /dev/null 2>&1 && cat testF && rm testF"') @@ -1457,6 +1561,15 @@ describe('TUI', function() it('<C-h> #10134', function() local screen = thelpers.screen_setup(0, '["'..nvim_prog ..[[", "-u", "NONE", "-i", "NONE", "--cmd", "set noruler", "--cmd", ':nnoremap <C-h> :echomsg "\<C-h\>"<CR>']]..']') + screen:expect{grid=[[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] }| + | + {3:-- TERMINAL --} | + ]]} command([[call chansend(b:terminal_job_id, "\<C-h>")]]) screen:expect([[ @@ -1483,6 +1596,15 @@ describe('TUI UIEnter/UILeave', function() ..[[, "--cmd", "autocmd VimEnter * :call add(g:evs, 'VimEnter')"]] ..']' ) + screen:expect{grid=[[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] }| + | + {3:-- TERMINAL --} | + ]]} feed_data(":echo g:evs\n") screen:expect{grid=[[ {1: } | @@ -1503,61 +1625,88 @@ describe('TUI FocusGained/FocusLost', function() clear() screen = thelpers.screen_setup(0, '["'..nvim_prog ..'", "-u", "NONE", "-i", "NONE", "--cmd", "set noswapfile noshowcmd noruler"]') - feed_data(":autocmd FocusGained * echo 'gained'\n") - feed_data(":autocmd FocusLost * echo 'lost'\n") - feed_data("\034\016") -- CTRL-\ CTRL-N - end) - - it('in normal-mode', function() - retry(2, 3 * screen.timeout, function() - feed_data('\027[I') - screen:expect([[ + screen:expect{grid=[[ {1: } | {4:~ }| {4:~ }| {4:~ }| {5:[No Name] }| - gained | + | {3:-- TERMINAL --} | - ]]) + ]]} + feed_data(":autocmd FocusGained * echo 'gained'\n") + feed_data(":autocmd FocusLost * echo 'lost'\n") + feed_data("\034\016") -- CTRL-\ CTRL-N + end) - feed_data('\027[O') - screen:expect([[ + it('in normal-mode', function() + screen:expect{grid=[[ {1: } | {4:~ }| {4:~ }| {4:~ }| {5:[No Name] }| - lost | + :autocmd FocusLost * echo 'lost' | {3:-- TERMINAL --} | - ]]) + ]]} + retry(2, 3 * screen.timeout, function() + feed_data('\027[I') + screen:expect([[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] }| + gained | + {3:-- TERMINAL --} | + ]]) + + feed_data('\027[O') + screen:expect([[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] }| + lost | + {3:-- TERMINAL --} | + ]]) end) end) it('in insert-mode', function() feed_command('set noshowmode') feed_data('i') - retry(2, 3 * screen.timeout, function() - feed_data('\027[I') - screen:expect([[ - {1: } | - {4:~ }| - {4:~ }| - {4:~ }| - {5:[No Name] }| - gained | - {3:-- TERMINAL --} | - ]]) - feed_data('\027[O') - screen:expect([[ + screen:expect{grid=[[ {1: } | {4:~ }| {4:~ }| {4:~ }| {5:[No Name] }| - lost | + :set noshowmode | {3:-- TERMINAL --} | - ]]) + ]]} + retry(2, 3 * screen.timeout, function() + feed_data('\027[I') + screen:expect([[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] }| + gained | + {3:-- TERMINAL --} | + ]]) + feed_data('\027[O') + screen:expect([[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] }| + lost | + {3:-- TERMINAL --} | + ]]) end) end) @@ -1594,6 +1743,15 @@ describe('TUI FocusGained/FocusLost', function() feed_data(":autocmd!\n") feed_data(":autocmd FocusLost * call append(line('$'), 'lost')\n") feed_data(":autocmd FocusGained * call append(line('$'), 'gained')\n") + screen:expect{grid=[[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] }| + | + {3:-- TERMINAL --} | + ]]} retry(2, 3 * screen.timeout, function() -- Enter cmdline-mode. feed_data(':') @@ -1652,9 +1810,18 @@ describe('TUI FocusGained/FocusLost', function() feed_data(":echom 'msg1'|echom 'msg2'|echom 'msg3'|echom 'msg4'|echom 'msg5'\n") -- Execute :messages to provoke the press-enter prompt. feed_data(":messages\n") + screen:expect{grid=[[ + msg1 | + msg2 | + msg3 | + msg4 | + msg5 | + {10:Press ENTER or type command to continue}{1: } | + {3:-- TERMINAL --} | + ]]} feed_data('\027[I') feed_data('\027[I') - screen:expect([[ + screen:expect{grid=[[ msg1 | msg2 | msg3 | @@ -1662,7 +1829,7 @@ describe('TUI FocusGained/FocusLost', function() msg5 | {10:Press ENTER or type command to continue}{1: } | {3:-- TERMINAL --} | - ]]) + ]], unchanged=true} end) end) @@ -1670,7 +1837,6 @@ end) -- does not initialize the TUI. describe("TUI 't_Co' (terminal colors)", function() local screen - local is_freebsd = (uname() == 'freebsd') local function assert_term_colors(term, colorterm, maxcolors) helpers.clear({env={TERM=term}, args={}}) @@ -1773,7 +1939,7 @@ describe("TUI 't_Co' (terminal colors)", function() -- which is raised to 16 by COLORTERM. it("TERM=screen no COLORTERM uses 8/256 colors", function() - if is_freebsd then + if is_os('freebsd') then assert_term_colors("screen", nil, 256) else assert_term_colors("screen", nil, 8) @@ -1781,7 +1947,7 @@ describe("TUI 't_Co' (terminal colors)", function() end) it("TERM=screen COLORTERM=screen uses 16/256 colors", function() - if is_freebsd then + if is_os('freebsd') then assert_term_colors("screen", "screen", 256) else assert_term_colors("screen", "screen", 16) @@ -1944,8 +2110,6 @@ end) -- does not initialize the TUI. describe("TUI 'term' option", function() local screen - local is_bsd = not not string.find(uname(), 'bsd') - local is_macos = not not string.find(uname(), 'darwin') local function assert_term(term_envvar, term_expected) clear() @@ -1971,11 +2135,11 @@ describe("TUI 'term' option", function() end) it('gets system-provided term if $TERM is valid', function() - if uname() == "openbsd" then + if is_os('openbsd') then assert_term("xterm", "xterm") - elseif is_bsd then -- BSD lacks terminfo, builtin is always used. + elseif is_os('bsd') then -- BSD lacks terminfo, builtin is always used. assert_term("xterm", "builtin_xterm") - elseif is_macos then + elseif is_os('mac') then local status, _ = pcall(assert_term, "xterm", "xterm") if not status then pending("macOS: unibilium could not find terminfo") @@ -2033,7 +2197,7 @@ describe("TUI", function() retry(nil, 3000, function() -- Wait for log file to be flushed. local log = read_file('Xtest_tui_verbose_log') or '' - eq('--- Terminal info --- {{{\n', string.match(log, '%-%-%- Terminal.-\n')) + eq('--- Terminal info --- {{{\n', string.match(log, '%-%-%- Terminal.-\n')) -- }}} ok(#log > 50) end) end) @@ -2157,3 +2321,163 @@ describe('TUI bg color', function() screen:expect{any='new_bg=dark'} end) end) + +-- These tests require `thelpers` because --headless/--embed +-- does not initialize the TUI. +describe("TUI as a client", function() + + it("connects to remote instance (with its own TUI)", function() + local server_super = spawn_argv(false) -- equivalent to clear() + local client_super = spawn_argv(true) + + set_session(server_super) + local server_pipe = new_pipename() + local screen_server = thelpers.screen_setup(0, + string.format([=[["%s", "--listen", "%s", "-u", "NONE", "-i", "NONE", "--cmd", "%s laststatus=2 background=dark"]]=], + nvim_prog, server_pipe, nvim_set)) + + feed_data("iHello, World") + screen_server:expect{grid=[[ + Hello, World{1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] [+] }| + {3:-- INSERT --} | + {3:-- TERMINAL --} | + ]]} + feed_data("\027") + screen_server:expect{grid=[[ + Hello, Worl{1:d} | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]} + + set_session(client_super) + local screen_client = thelpers.screen_setup(0, + string.format([=[["%s", "-u", "NONE", "-i", "NONE", "--server", "%s", "--remote-ui"]]=], + nvim_prog, server_pipe)) + + screen_client:expect{grid=[[ + Hello, Worl{1:d} | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]} + + feed_data(":q!\n") + + server_super:close() + client_super:close() + end) + + it("connects to remote instance (--headless)", function() + local server = helpers.spawn_argv(false) -- equivalent to clear() + local client_super = spawn_argv(true) + + set_session(server) + local server_pipe = eval'v:servername' + feed'iHalloj!<esc>' + + set_session(client_super) + local screen = thelpers.screen_setup(0, + string.format([=[["%s", "-u", "NONE", "-i", "NONE", "--server", "%s", "--remote-ui"]]=], + nvim_prog, server_pipe)) + + screen:expect{grid=[[ + Halloj{1:!} | + {4:~ }| + {4:~ }| + {4:~ }| + {4:~ }| + | + {3:-- TERMINAL --} | + ]]} + + client_super:close() + server:close() + end) + + + it("throws error when no server exists", function() + clear() + local screen = thelpers.screen_setup(0, + string.format([=[["%s", "-u", "NONE", "-i", "NONE", "--server", "127.0.0.1:2436546", "--remote-ui"]]=], + nvim_prog), 60) + + screen:expect([[ + Remote ui failed to start: {MATCH:.*}| + | + [Process exited 1]{1: } | + | + | + | + {3:-- TERMINAL --} | + ]]) + end) + + it("exits when server quits", function() + local server_super = spawn_argv(false) -- equivalent to clear() + local client_super = spawn_argv(true) + + set_session(server_super) + local server_pipe = new_pipename() + local screen_server = thelpers.screen_setup(0, + string.format([=[["%s", "--listen", "%s", "-u", "NONE", "-i", "NONE", "--cmd", "%s laststatus=2 background=dark"]]=], + nvim_prog, server_pipe, nvim_set)) + + feed_data("iHello, World") + screen_server:expect{grid=[[ + Hello, World{1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] [+] }| + {3:-- INSERT --} | + {3:-- TERMINAL --} | + ]]} + feed_data("\027") + screen_server:expect{grid=[[ + Hello, Worl{1:d} | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]} + + set_session(client_super) + local screen_client = thelpers.screen_setup(0, + string.format([=[["%s", "-u", "NONE", "-i", "NONE", "--server", "%s", "--remote-ui"]]=], + nvim_prog, server_pipe)) + + screen_client:expect{grid=[[ + Hello, Worl{1:d} | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]} + + -- quitting the server + set_session(server_super) + feed_data(":q!\n") + screen_server:expect({any="Process exited 0"}) + + -- assert that client has exited + screen_client:expect({any="Process exited 0"}) + + server_super:close() + client_super:close() + end) +end) diff --git a/test/functional/terminal/window_spec.lua b/test/functional/terminal/window_spec.lua index 0d3295cf32..80e9d78400 100644 --- a/test/functional/terminal/window_spec.lua +++ b/test/functional/terminal/window_spec.lua @@ -3,11 +3,12 @@ local thelpers = require('test.functional.terminal.helpers') local feed_data = thelpers.feed_data local feed, clear = helpers.feed, helpers.clear local poke_eventloop = helpers.poke_eventloop -local iswin = helpers.iswin local command = helpers.command local retry = helpers.retry local eq = helpers.eq local eval = helpers.eval +local skip = helpers.skip +local is_os = helpers.is_os describe(':terminal window', function() local screen @@ -18,7 +19,7 @@ describe(':terminal window', function() end) it('sets topline correctly #8556', function() - if helpers.pending_win32(pending) then return end + skip(is_os('win')) -- Test has hardcoded assumptions of dimensions. eq(7, eval('&lines')) feed_data('\n\n\n') -- Add blank lines. @@ -54,9 +55,7 @@ describe(':terminal window', function() {3:-- TERMINAL --} | ]]) - if iswin() then - return -- win: :terminal resize is unreliable #7007 - end + skip(is_os('win'), 'win: :terminal resize is unreliable #7007') -- numberwidth=9 feed([[<C-\><C-N>]]) @@ -172,7 +171,7 @@ describe(':terminal with multigrid', function() ]]) screen:try_resize_grid(2, 20, 10) - if iswin() then + if is_os('win') then screen:expect{any="rows: 10, cols: 20"} else screen:expect([[ @@ -201,7 +200,7 @@ describe(':terminal with multigrid', function() end screen:try_resize_grid(2, 70, 3) - if iswin() then + if is_os('win') then screen:expect{any="rows: 3, cols: 70"} else screen:expect([[ @@ -223,7 +222,7 @@ describe(':terminal with multigrid', function() end screen:try_resize_grid(2, 0, 0) - if iswin() then + if is_os('win') then screen:expect{any="rows: 6, cols: 50"} else screen:expect([[ diff --git a/test/functional/terminal/window_split_tab_spec.lua b/test/functional/terminal/window_split_tab_spec.lua index b62d173cea..1d77e1e92e 100644 --- a/test/functional/terminal/window_split_tab_spec.lua +++ b/test/functional/terminal/window_split_tab_spec.lua @@ -8,9 +8,9 @@ local command = helpers.command local eq = helpers.eq local eval = helpers.eval local meths = helpers.meths -local iswin = helpers.iswin local sleep = helpers.sleep local retry = helpers.retry +local is_os = helpers.is_os describe(':terminal', function() local screen @@ -96,7 +96,7 @@ describe(':terminal', function() local w1, h1 = screen._width - 3, screen._height - 2 local w2, h2 = w1 - 6, h1 - 3 - if iswin() then + if is_os('win') then -- win: SIGWINCH is unreliable, use a weaker test. #7506 retry(3, 30000, function() screen:try_resize(w1, h1) diff --git a/test/functional/treesitter/highlight_spec.lua b/test/functional/treesitter/highlight_spec.lua index ae3f42ff0a..2a2311c0fa 100644 --- a/test/functional/treesitter/highlight_spec.lua +++ b/test/functional/treesitter/highlight_spec.lua @@ -605,8 +605,8 @@ describe('treesitter highlighting', function() }} eq({ - {capture='Error', metadata = { priority='101' }}; - {capture='type', metadata = { } }; + {capture='Error', metadata = { priority='101' }, lang='c' }; + {capture='type', metadata = { }, lang='c' }; }, exec_lua [[ return vim.treesitter.get_captures_at_pos(0, 0, 2) ]]) end) diff --git a/test/functional/treesitter/language_spec.lua b/test/functional/treesitter/language_spec.lua index ed84dedb5a..f95b05a1cc 100644 --- a/test/functional/treesitter/language_spec.lua +++ b/test/functional/treesitter/language_spec.lua @@ -13,21 +13,21 @@ before_each(clear) describe('treesitter language API', function() -- error tests not requiring a parser library it('handles missing language', function() - eq("Error executing lua: .../language.lua:0: no parser for 'borklang' language, see :help treesitter-parsers", + eq(".../language.lua:0: no parser for 'borklang' language, see :help treesitter-parsers", pcall_err(exec_lua, "parser = vim.treesitter.get_parser(0, 'borklang')")) -- actual message depends on platform - matches("Error executing lua: Failed to load parser: uv_dlopen: .+", + matches("Failed to load parser for language 'borklang': uv_dlopen: .+", pcall_err(exec_lua, "parser = vim.treesitter.require_language('borklang', 'borkbork.so')")) -- Should not throw an error when silent eq(false, exec_lua("return vim.treesitter.require_language('borklang', nil, true)")) eq(false, exec_lua("return vim.treesitter.require_language('borklang', 'borkbork.so', true)")) - eq("Error executing lua: .../language.lua:0: no parser for 'borklang' language, see :help treesitter-parsers", + eq(".../language.lua:0: no parser for 'borklang' language, see :help treesitter-parsers", pcall_err(exec_lua, "parser = vim.treesitter.inspect_language('borklang')")) - matches("Error executing lua: Failed to load parser: uv_dlsym: .+", + matches("Failed to load parser: uv_dlsym: .+", pcall_err(exec_lua, 'vim.treesitter.require_language("c", nil, false, "borklang")')) end) @@ -76,7 +76,7 @@ describe('treesitter language API', function() eq('c', exec_lua("return vim.treesitter.get_parser(0):lang()")) command("set filetype=borklang") -- Should throw an error when filetype changes to borklang - eq("Error executing lua: .../language.lua:0: no parser for 'borklang' language, see :help treesitter-parsers", + eq(".../language.lua:0: no parser for 'borklang' language, see :help treesitter-parsers", pcall_err(exec_lua, "new_parser = vim.treesitter.get_parser(0)")) end) diff --git a/test/functional/treesitter/parser_spec.lua b/test/functional/treesitter/parser_spec.lua index ccbd55df0e..f006ad4539 100644 --- a/test/functional/treesitter/parser_spec.lua +++ b/test/functional/treesitter/parser_spec.lua @@ -5,6 +5,8 @@ local eq = helpers.eq local insert = helpers.insert local exec_lua = helpers.exec_lua local feed = helpers.feed +local is_os = helpers.is_os +local skip = helpers.skip before_each(clear) @@ -12,8 +14,6 @@ describe('treesitter parser API', function() clear() it('parses buffer', function() - if helpers.pending_win32(pending) then return end - insert([[ int main() { int x = 3; @@ -684,7 +684,7 @@ int x = INT_MAX; end) it("should not inject bad languages", function() - if helpers.pending_win32(pending) then return end + skip(is_os('win')) exec_lua([=[ vim.treesitter.add_directive("inject-bad!", function(match, _, _, pred, metadata) metadata.language = "{" diff --git a/test/functional/ui/cmdline_highlight_spec.lua b/test/functional/ui/cmdline_highlight_spec.lua index fa5771a8b3..eb5de693bd 100644 --- a/test/functional/ui/cmdline_highlight_spec.lua +++ b/test/functional/ui/cmdline_highlight_spec.lua @@ -61,7 +61,7 @@ before_each(function() endwhile return ret endfunction - function SplittedMultibyteStart(cmdline) + function SplitMultibyteStart(cmdline) let ret = [] let i = 0 while i < len(a:cmdline) @@ -77,7 +77,7 @@ before_each(function() endwhile return ret endfunction - function SplittedMultibyteEnd(cmdline) + function SplitMultibyteEnd(cmdline) let ret = [] let i = 0 while i < len(a:cmdline) @@ -296,7 +296,7 @@ describe('Command-line coloring', function() end it('does the right thing when hl start appears to split multibyte char', function() - set_color_cb('SplittedMultibyteStart') + set_color_cb('SplitMultibyteStart') start_prompt('echo "«') screen:expect{grid=[[ | @@ -322,7 +322,7 @@ describe('Command-line coloring', function() end) it('does the right thing when hl end appears to split multibyte char', function() - set_color_cb('SplittedMultibyteEnd') + set_color_cb('SplitMultibyteEnd') start_prompt('echo "«') screen:expect([[ | @@ -335,17 +335,17 @@ describe('Command-line coloring', function() :echo "«^ | ]]) end) - it('does the right thing when errorring', function() + it('does the right thing when erroring', function() set_color_cb('Echoerring') start_prompt('e') screen:expect([[ | {EOB:~ }| - {EOB:~ }| {MSEP: }| : | {ERR:E5407: Callback has thrown an exception:}| - {ERR: Vim(echoerr):HERE} | + {ERR: function DoPrompt[3]..Echoerring, line }| + {ERR:1: Vim(echoerr):HERE} | :e^ | ]]) end) @@ -400,16 +400,16 @@ describe('Command-line coloring', function() screen:expect([[ | {EOB:~ }| - {EOB:~ }| {MSEP: }| : | {ERR:E5407: Callback has thrown an exception:}| + {ERR: function DoPrompt[3]..Throwing, line 1:}| {ERR: ABC} | :e^ | ]]) end) it('stops executing callback after a number of errors', function() - set_color_cb('SplittedMultibyteStart') + set_color_cb('SplitMultibyteStart') start_prompt('let x = "«»«»«»«»«»"') screen:expect([[ | @@ -772,7 +772,7 @@ describe('Ex commands coloring', function() ]]) end) it('still executes command-line even if errored out', function() - meths.set_var('Nvim_color_cmdline', 'SplittedMultibyteStart') + meths.set_var('Nvim_color_cmdline', 'SplitMultibyteStart') feed(':let x = "«"\n') eq('«', meths.get_var('x')) local msg = 'E5405: Chunk 0 start 10 splits multibyte character' diff --git a/test/functional/ui/cmdline_spec.lua b/test/functional/ui/cmdline_spec.lua index 8003947078..1c9ac7f7ba 100644 --- a/test/functional/ui/cmdline_spec.lua +++ b/test/functional/ui/cmdline_spec.lua @@ -4,10 +4,12 @@ local clear, feed = helpers.clear, helpers.feed local source = helpers.source local command = helpers.command local assert_alive = helpers.assert_alive -local uname = helpers.uname +local poke_eventloop = helpers.poke_eventloop local exec = helpers.exec local eval = helpers.eval local eq = helpers.eq +local is_os = helpers.is_os +local meths = helpers.meths local function new_screen(opt) local screen = Screen.new(25, 5) @@ -717,7 +719,7 @@ describe('cmdline redraw', function() end) it('with <Cmd>', function() - if string.find(uname(), 'bsd') then + if is_os('bsd') then pending('FIXME #10804') end command('cmap a <Cmd>call sin(0)<CR>') -- no-op @@ -1351,4 +1353,57 @@ describe('cmdheight=0', function() {1:~ }│{1:~ }| ]]) end) + + it('no assert failure with showcmd', function() + command('set showcmd cmdheight=0') + feed('d') + screen:expect([[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ]]) + assert_alive() + end) + + it('can only be resized to 0 if set explicitly', function() + command('set laststatus=2') + command('resize +1') + screen:expect([[ + ^ | + {1:~ }| + {1:~ }| + {2:[No Name] }| + | + ]]) + command('set cmdheight=0') + command('resize -1') + command('resize +1') + screen:expect([[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {2:[No Name] }| + ]]) + end) + + it("cannot be resized at all with external messages", function() + clear() + screen = new_screen({rgb=true, ext_messages=true}) + command('set laststatus=2 mouse=a') + command('resize -1') + screen:expect([[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {3:[No Name] }| + ]]) + meths.input_mouse('left', 'press', '', 0, 6, 10) + poke_eventloop() + meths.input_mouse('left', 'drag', '', 0, 5, 10) + screen:expect_unchanged() + end) end) diff --git a/test/functional/ui/cursor_spec.lua b/test/functional/ui/cursor_spec.lua index 03cd4bfd06..e261f0dfab 100644 --- a/test/functional/ui/cursor_spec.lua +++ b/test/functional/ui/cursor_spec.lua @@ -215,7 +215,7 @@ describe('ui/cursor', function() m.hl_id = 60 m.attr = {background = Screen.colors.DarkGray} end - if m.id_lm then m.id_lm = 61 end + if m.id_lm then m.id_lm = 62 end end -- Assert the new expectation. diff --git a/test/functional/ui/decorations_spec.lua b/test/functional/ui/decorations_spec.lua index 0a5eefbf38..489c33d8b1 100644 --- a/test/functional/ui/decorations_spec.lua +++ b/test/functional/ui/decorations_spec.lua @@ -176,7 +176,13 @@ describe('decorations providers', function() beamtrace = {} local function on_do(kind, ...) if kind == 'win' or kind == 'spell' then - a.nvim_buf_set_extmark(0, ns, 0, 0, { end_row = 2, end_col = 23, spell = true, ephemeral = true }) + a.nvim_buf_set_extmark(0, ns, 0, 0, { + end_row = 2, + end_col = 23, + spell = true, + priority = 20, + ephemeral = true + }) end table.insert(beamtrace, {kind, ...}) end @@ -234,6 +240,36 @@ describe('decorations providers', function() {1:~ }| | ]]} + + -- spell=false with lower priority doesn't disable spell + local ns = meths.create_namespace "spell" + local id = helpers.curbufmeths.set_extmark(ns, 0, 0, { priority = 30, end_row = 2, end_col = 23, spell = false }) + + screen:expect{grid=[[ + I am well written text. | + i am not capitalized. | + I am a ^speling mistakke. | + | + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]} + + -- spell=false with higher priority does disable spell + helpers.curbufmeths.set_extmark(ns, 0, 0, { id = id, priority = 10, end_row = 2, end_col = 23, spell = false }) + + screen:expect{grid=[[ + I am well written text. | + {15:i} am not capitalized. | + I am a {16:^speling} {16:mistakke}. | + | + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]} + end) it('can predefine highlights', function() @@ -564,6 +600,7 @@ describe('extmark decorations', function() [24] = {bold = true}; [25] = {background = Screen.colors.LightRed}; [26] = {background=Screen.colors.DarkGrey, foreground=Screen.colors.LightGrey}; + [27] = {background = Screen.colors.Plum1}; } ns = meths.create_namespace 'test' @@ -959,6 +996,55 @@ end]] | ]]) end) + + it('avoids redraw issue #20651', function() + exec_lua[[ + vim.cmd.normal'10oXXX' + vim.cmd.normal'gg' + local ns = vim.api.nvim_create_namespace('ns') + + local bufnr = vim.api.nvim_create_buf(false, true) + vim.api.nvim_open_win(bufnr, false, { relative = 'win', height = 1, width = 1, row = 0, col = 0 }) + + vim.api.nvim_create_autocmd('CursorMoved', { callback = function() + local row = vim.api.nvim_win_get_cursor(0)[1] - 1 + vim.api.nvim_buf_set_extmark(0, ns, row, 0, { id = 1 }) + vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, {}) + vim.schedule(function() + vim.api.nvim_buf_set_extmark(0, ns, row, 0, { + id = 1, + virt_text = {{'HELLO', 'Normal'}}, + }) + end) + end + }) + ]] + + for _ = 1, 3 do + helpers.sleep(10) + feed 'j' + end + + screen:expect{grid=[[ + {27: } | + XXX | + XXX | + ^XXX HELLO | + XXX | + XXX | + XXX | + XXX | + XXX | + XXX | + XXX | + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]} + + end) + end) describe('decorations: virtual lines', function() @@ -1065,7 +1151,7 @@ if (h->n_buckets < new_n_buckets) { // expand ]]} meths.buf_set_extmark(0, ns, 5, 0, { - virt_lines = { {{"^^ REVIEW:", "Todo"}, {" new_vals variable seems unneccesary?", "Comment"}} }; + virt_lines = { {{"^^ REVIEW:", "Todo"}, {" new_vals variable seems unnecessary?", "Comment"}} }; }) -- TODO: what about the cursor?? screen:expect{grid=[[ @@ -1078,7 +1164,7 @@ if (h->n_buckets < new_n_buckets) { // expand if (kh_is_map && val_size) { | ^char *new_vals = {3:krealloc}( h->vals_buf, new_n_| buckets * val_size); | - {5:^^ REVIEW:}{6: new_vals variable seems unneccesary?} | + {5:^^ REVIEW:}{6: new_vals variable seems unnecessary?} | h->vals_buf = new_vals; | | ]]} @@ -2085,4 +2171,39 @@ describe('decorations: virt_text', function() ]]} end) + it('redraws correctly when re-using extmark ids', function() + command 'normal 5ohello' + + screen:expect{grid=[[ + | + hello | + hello | + hello | + hello | + hell^o | + {3:~ }| + {3:~ }| + {3:~ }| + | + ]]} + + local ns = meths.create_namespace('ns') + for row = 1, 5 do + meths.buf_set_extmark(0, ns, row, 0, { id = 1, virt_text = {{'world', 'Normal'}} }) + end + + screen:expect{grid=[[ + | + hello | + hello | + hello | + hello | + hell^o world | + {3:~ }| + {3:~ }| + {3:~ }| + | + ]]} + end) + end) diff --git a/test/functional/ui/diff_spec.lua b/test/functional/ui/diff_spec.lua index 36dc5addcd..dbdf3823ec 100644 --- a/test/functional/ui/diff_spec.lua +++ b/test/functional/ui/diff_spec.lua @@ -8,6 +8,8 @@ local insert = helpers.insert local write_file = helpers.write_file local dedent = helpers.dedent local exec = helpers.exec +local eq = helpers.eq +local meths = helpers.meths describe('Diff mode screen', function() local fname = 'Xtest-functional-diff-screen-1' @@ -1073,6 +1075,182 @@ int main(int argc, char **argv) :e | ]]) end) + + describe('line matching diff algorithm', function() + setup(function() + local f1 = [[if __name__ == "__main__": + import sys + app = QWidgets.QApplication(sys.args) + MainWindow = QtWidgets.QMainWindow() + ui = UI_MainWindow() + ui.setupUI(MainWindow) + MainWindow.show() + sys.exit(app.exec_())]] + write_file(fname, f1, false) + local f2 = [[if __name__ == "__main__": + import sys + comment these things + #app = QWidgets.QApplication(sys.args) + #MainWindow = QtWidgets.QMainWindow() + add a completely different line here + #ui = UI_MainWindow() + add another new line + ui.setupUI(MainWindow) + MainWindow.show() + sys.exit(app.exec_())]] + write_file(fname_2, f2, false) + end) + + it('diffopt+=linematch:20', function() + reread() + feed(':set diffopt=internal,filler<cr>') + screen:expect([[ + {1: }^if __name__ == "__│{1: }if __name__ == "_| + {1: } import sys │{1: } import sys | + {1: }{9: }{8:app = QWidgets}│{1: }{9: }{8:comment these}| + {1: }{9: }{8:MainWindow = Q}│{1: }{9: }{8:#app = QWidge}| + {1: }{9: }{8:ui = UI_}{9:MainWi}│{1: }{9: }{8:#MainWindow =}| + {1: }{2:------------------}│{1: }{4: add a complet}| + {1: }{2:------------------}│{1: }{4: #ui = UI_Main}| + {1: }{2:------------------}│{1: }{4: add another n}| + {1: } ui.setupUI(Mai│{1: } ui.setupUI(Ma| + {1: } MainWindow.sho│{1: } MainWindow.sh| + {1: } sys.exit(app.e│{1: } sys.exit(app.| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {7:<onal-diff-screen-1 }{3:<l-diff-screen-1.2 }| + :set diffopt=internal,filler | + ]]) + + feed('G') + feed(':set diffopt+=linematch:20<cr>') + screen:expect([[ + {1: }if __name__ == "__│{1: }if __name__ == "_| + {1: } import sys │{1: } import sys | + {1: }{2:------------------}│{1: }{4: comment these}| + {1: }{9: app = QWidgets}│{1: }{9: }{8:#}{9:app = QWidge}| + {1: }{9: MainWindow = Q}│{1: }{9: }{8:#}{9:MainWindow =}| + {1: }{2:------------------}│{1: }{4: add a complet}| + {1: }{9: ui = UI_MainWi}│{1: }{9: }{8:#}{9:ui = UI_Main}| + {1: }{2:------------------}│{1: }{4: add another n}| + {1: } ui.setupUI(Mai│{1: } ui.setupUI(Ma| + {1: } MainWindow.sho│{1: } MainWindow.sh| + {1: } ^sys.exit(app.e│{1: } sys.exit(app.| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {7:<onal-diff-screen-1 }{3:<l-diff-screen-1.2 }| + :set diffopt+=linematch:20 | + ]]) + end) + end) + + describe('line matching diff algorithm with icase', function() + setup(function() + local f1 = [[DDD +_aa]] + write_file(fname, f1, false) + local f2 = [[DDD +AAA +ccca]] + write_file(fname_2, f2, false) + end) + it('diffopt+=linematch:20,icase', function() + reread() + feed(':set diffopt=internal,filler,linematch:20<cr>') + screen:expect([[ + {1: }^DDD │{1: }DDD | + {1: }{2:------------------}│{1: }{4:AAA }| + {1: }{8:_a}{9:a }│{1: }{8:ccc}{9:a }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {7:<onal-diff-screen-1 }{3:<l-diff-screen-1.2 }| + | + ]]) + feed(':set diffopt+=icase<cr>') + screen:expect([[ + {1: }^DDD │{1: }DDD | + {1: }{8:_}{9:aa }│{1: }{8:A}{9:AA }| + {1: }{2:------------------}│{1: }{4:ccca }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {7:<onal-diff-screen-1 }{3:<l-diff-screen-1.2 }| + :set diffopt+=icase | + ]]) + end) + end) + + describe('line matching diff algorithm with iwhiteall', function() + setup(function() + local f1 = [[BB + AAA]] + write_file(fname, f1, false) + local f2 = [[BB + AAB +AAAB]] + write_file(fname_2, f2, false) + end) + it('diffopt+=linematch:20,iwhiteall', function() + reread() + feed(':set diffopt=internal,filler,linematch:20<cr>') + screen:expect{grid=[[ + {1: }^BB │{1: }BB | + {1: }{9: AA}{8:A}{9: }│{1: }{9: AA}{8:B}{9: }| + {1: }{2:------------------}│{1: }{4:AAAB }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {7:<onal-diff-screen-1 }{3:<l-diff-screen-1.2 }| + | + ]]} + feed(':set diffopt+=iwhiteall<cr>') + screen:expect{grid=[[ + {1: }^BB │{1: }BB | + {1: }{2:------------------}│{1: }{4: AAB }| + {1: }{9: AAA }│{1: }{9:AAA}{8:B}{9: }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {7:<onal-diff-screen-1 }{3:<l-diff-screen-1.2 }| + :set diffopt+=iwhiteall | + ]]} + end) + end) end) it('win_update redraws lines properly', function() @@ -1315,6 +1493,26 @@ it('Align the filler lines when changing text in diff mode', function() ]]} end) +it("diff mode doesn't restore invalid 'foldcolumn' value #21647", function() + clear() + local screen = Screen.new(60, 6) + screen:set_default_attr_ids({ + [0] = {foreground = Screen.colors.Blue, bold = true}; + }) + screen:attach() + eq('0', meths.get_option_value('foldcolumn', {})) + command('diffsplit | bd') + screen:expect([[ + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + eq('0', meths.get_option_value('foldcolumn', {})) +end) + -- oldtest: Test_diff_binary() it('diff mode works properly if file contains NUL bytes vim-patch:8.2.3925', function() clear() diff --git a/test/functional/ui/embed_spec.lua b/test/functional/ui/embed_spec.lua index e7addd1b85..cd2b48213d 100644 --- a/test/functional/ui/embed_spec.lua +++ b/test/functional/ui/embed_spec.lua @@ -51,7 +51,7 @@ local function test_embed(ext_linegrid) end) it("doesn't erase output when setting color scheme", function() - if 'openbsd' == helpers.uname() then + if helpers.is_os('openbsd') then pending('FIXME #10804') end startup('--cmd', 'echoerr "foo"', '--cmd', 'color default', '--cmd', 'echoerr "bar"') diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua index 1a9a13f7d4..6759510ad1 100644 --- a/test/functional/ui/float_spec.lua +++ b/test/functional/ui/float_spec.lua @@ -25,36 +25,6 @@ describe('float window', function() clear() command('hi VertSplit gui=reverse') end) - local attrs = { - [0] = {bold=true, foreground=Screen.colors.Blue}, - [1] = {background = Screen.colors.LightMagenta}, - [2] = {background = Screen.colors.LightMagenta, bold = true, foreground = Screen.colors.Blue1}, - [3] = {bold = true}, - [4] = {bold = true, reverse = true}, - [5] = {reverse = true}, - [6] = {background = Screen.colors.LightMagenta, bold = true, reverse = true}, - [7] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red}, - [8] = {bold = true, foreground = Screen.colors.SeaGreen4}, - [9] = {background = Screen.colors.LightGrey, underline = true}, - [10] = {background = Screen.colors.LightGrey, underline = true, bold = true, foreground = Screen.colors.Magenta}, - [11] = {bold = true, foreground = Screen.colors.Magenta}, - [12] = {background = Screen.colors.Red, bold = true, foreground = Screen.colors.Blue1}, - [13] = {background = Screen.colors.WebGray}, - [14] = {foreground = Screen.colors.Brown}, - [15] = {background = Screen.colors.Grey20}, - [16] = {background = Screen.colors.Grey20, bold = true, foreground = Screen.colors.Blue1}, - [17] = {background = Screen.colors.Yellow}, - [18] = {foreground = Screen.colors.Brown, background = Screen.colors.Grey20}, - [19] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.WebGray}, - [20] = {bold = true, foreground = Screen.colors.Brown}, - [21] = {background = Screen.colors.Gray90}, - [22] = {background = Screen.colors.LightRed}, - [23] = {foreground = Screen.colors.Black, background = Screen.colors.White}; - [24] = {foreground = Screen.colors.Black, background = Screen.colors.Grey80}; - [25] = {blend = 100, background = Screen.colors.Gray0}; - [26] = {blend = 80, background = Screen.colors.Gray0}; - [27] = {background = Screen.colors.LightGray}; - } it('behavior', function() -- Create three windows and test that ":wincmd <direction>" changes to the @@ -93,7 +63,7 @@ describe('float window', function() end) it('closed immediately by autocmd #11383', function() - eq('Error executing lua: [string "<nvim>"]:0: Window was closed immediately', + eq('Window was closed immediately', pcall_err(exec_lua, [[ local a = vim.api local function crashes(contents) @@ -118,7 +88,7 @@ describe('float window', function() end) it('closed immediately by autocmd after win_enter #15548', function() - eq('Error executing lua: [string "<nvim>"]:0: Window was closed immediately', + eq('Window was closed immediately', pcall_err(exec_lua, [[ vim.cmd "autocmd BufLeave * ++once quit!" local buf = vim.api.nvim_create_buf(true, true) @@ -198,6 +168,29 @@ describe('float window', function() eq(7, pos[2]) end) + it('opened with correct position relative to the mouse', function() + meths.input_mouse('left', 'press', '', 0, 10, 10) + local pos = exec_lua([[ + local bufnr = vim.api.nvim_create_buf(false, true) + + local opts = { + width = 10, + height = 10, + col = 1, + row = 2, + relative = 'mouse', + style = 'minimal' + } + + local win_id = vim.api.nvim_open_win(bufnr, false, opts) + + return vim.api.nvim_win_get_position(win_id) + ]]) + + eq(12, pos[1]) + eq(11, pos[2]) + end) + it('opened with correct position relative to the cursor', function() local pos = exec_lua([[ local bufnr = vim.api.nvim_create_buf(false, true) @@ -421,6 +414,15 @@ describe('float window', function() eq(winids, eval('winids')) end) + it('no crash with bufpos and non-existent window', function() + command('new') + local closed_win = meths.get_current_win().id + command('close') + local buf = meths.create_buf(false,false) + meths.open_win(buf, true, {relative='win', win=closed_win, width=1, height=1, bufpos={0,0}}) + assert_alive() + end) + it("no segfault when setting minimal style after clearing local 'fillchars' #19510", function() local float_opts = {relative = 'editor', row = 1, col = 1, width = 1, height = 1} local float_win = meths.open_win(0, true, float_opts) @@ -430,6 +432,13 @@ describe('float window', function() assert_alive() end) + it("'scroll' is computed correctly when opening float with splitkeep=screen #20684", function() + meths.set_option('splitkeep', 'screen') + local float_opts = {relative = 'editor', row = 1, col = 1, width = 10, height = 10} + local float_win = meths.open_win(0, true, float_opts) + eq(5, meths.win_get_option(float_win, 'scroll')) + end) + describe('with only one tabpage,', function() local float_opts = {relative = 'editor', row = 1, col = 1, width = 1, height = 1} local old_buf, old_win @@ -717,10 +726,41 @@ describe('float window', function() end) local function with_ext_multigrid(multigrid) - local screen + local screen, attrs before_each(function() screen = Screen.new(40,7) screen:attach {ext_multigrid=multigrid} + attrs = { + [0] = {bold=true, foreground=Screen.colors.Blue}, + [1] = {background = Screen.colors.LightMagenta}, + [2] = {background = Screen.colors.LightMagenta, bold = true, foreground = Screen.colors.Blue1}, + [3] = {bold = true}, + [4] = {bold = true, reverse = true}, + [5] = {reverse = true}, + [6] = {background = Screen.colors.LightMagenta, bold = true, reverse = true}, + [7] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red}, + [8] = {bold = true, foreground = Screen.colors.SeaGreen4}, + [9] = {background = Screen.colors.LightGrey, underline = true}, + [10] = {background = Screen.colors.LightGrey, underline = true, bold = true, foreground = Screen.colors.Magenta}, + [11] = {bold = true, foreground = Screen.colors.Magenta}, + [12] = {background = Screen.colors.Red, bold = true, foreground = Screen.colors.Blue1}, + [13] = {background = Screen.colors.WebGray}, + [14] = {foreground = Screen.colors.Brown}, + [15] = {background = Screen.colors.Grey20}, + [16] = {background = Screen.colors.Grey20, bold = true, foreground = Screen.colors.Blue1}, + [17] = {background = Screen.colors.Yellow}, + [18] = {foreground = Screen.colors.Brown, background = Screen.colors.Grey20}, + [19] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.WebGray}, + [20] = {bold = true, foreground = Screen.colors.Brown}, + [21] = {background = Screen.colors.Gray90}, + [22] = {background = Screen.colors.LightRed}, + [23] = {foreground = Screen.colors.Black, background = Screen.colors.White}; + [24] = {foreground = Screen.colors.Black, background = Screen.colors.Grey80}; + [25] = {blend = 100, background = Screen.colors.Gray0}; + [26] = {blend = 80, background = Screen.colors.Gray0}; + [27] = {background = Screen.colors.LightGray}; + [28] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.LightGray}; + } screen:set_default_attr_ids(attrs) end) @@ -1312,6 +1352,54 @@ describe('float window', function() end end) + it("would not break 'minimal' style with statuscolumn set", function() + command('set number') + command('set signcolumn=yes') + command('set colorcolumn=1') + command('set cursorline') + command('set foldcolumn=1') + command('set statuscolumn=%l%s%C') + command('hi NormalFloat guibg=#333333') + feed('ix<cr>y<cr><esc>gg') + meths.open_win(0, false, {relative='editor', width=20, height=4, row=4, col=10, style='minimal'}) + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [3:----------------------------------------]| + ## grid 2 + {20:1}{19: }{20: }{22:^x}{21: }| + {14:2}{19: }{14: }{22:y} | + {14:3}{19: }{14: }{22: } | + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + | + ## grid 4 + {15:x }| + {15:y }| + {15: }| + {15: }| + ]], float_pos={[4] = {{id = 1001}, "NW", 1, 4, 10, true}}} + else + screen:expect{grid=[[ + {20:1}{19: }{20: }{22:^x}{21: }| + {14:2}{19: }{14: }{22:y} | + {14:3}{19: }{14: }{22: } {15:x } | + {0:~ }{15:y }{0: }| + {0:~ }{15: }{0: }| + {0:~ }{15: }{0: }| + | + ]]} + end + end) + it('can have border', function() local buf = meths.create_buf(false, false) meths.buf_set_lines(buf, 0, -1, true, {' halloj! ', @@ -1709,6 +1797,222 @@ describe('float window', function() end end) + it('validates title title_pos', function() + local buf = meths.create_buf(false,false) + eq("title requires border to be set", + pcall_err(meths.open_win,buf, false, { + relative='editor', width=9, height=2, row=2, col=5, title='Title', + })) + eq("title_pos requires title to be set", + pcall_err(meths.open_win,buf, false, { + relative='editor', width=9, height=2, row=2, col=5, + border='single', title_pos='left', + })) + end) + + it('validate title_pos in nvim_win_get_config', function() + local title_pos = exec_lua([[ + local bufnr = vim.api.nvim_create_buf(false, false) + local opts = { + relative = 'editor', + col = 2, + row = 5, + height = 2, + width = 9, + border = 'double', + title = 'Test', + title_pos = 'center' + } + + local win_id = vim.api.nvim_open_win(bufnr, true, opts) + return vim.api.nvim_win_get_config(win_id).title_pos + ]]) + + eq('center', title_pos) + end) + + + it('border with title', function() + local buf = meths.create_buf(false, false) + meths.buf_set_lines(buf, 0, -1, true, {' halloj! ', + ' BORDAA '}) + local win = meths.open_win(buf, false, { + relative='editor', width=9, height=2, row=2, col=5, border="double", + title = "Left",title_pos = "left", + }) + + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [3:----------------------------------------]| + ## grid 2 + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + | + ## grid 5 + {5:╔}{11:Left}{5:═════╗}| + {5:║}{1: halloj! }{5:║}| + {5:║}{1: BORDAA }{5:║}| + {5:╚═════════╝}| + ]], float_pos={ + [5] = { { id = 1002 }, "NW", 1, 2, 5, true } + }, win_viewport={ + [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1}; + [5] = {win = {id = 1002}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 2}; + }} + else + screen:expect{grid=[[ + ^ | + {0:~ }| + {0:~ }{5:╔}{11:Left}{5:═════╗}{0: }| + {0:~ }{5:║}{1: halloj! }{5:║}{0: }| + {0:~ }{5:║}{1: BORDAA }{5:║}{0: }| + {0:~ }{5:╚═════════╝}{0: }| + | + ]]} + end + + meths.win_set_config(win, {title= "Center",title_pos="center"}) + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [3:----------------------------------------]| + ## grid 2 + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + | + ## grid 5 + {5:╔═}{11:Center}{5:══╗}| + {5:║}{1: halloj! }{5:║}| + {5:║}{1: BORDAA }{5:║}| + {5:╚═════════╝}| + ]], float_pos={ + [5] = { { id = 1002 }, "NW", 1, 2, 5, true } + }, win_viewport={ + [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1}; + [5] = {win = {id = 1002}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 2}; + }} + else + screen:expect{grid=[[ + ^ | + {0:~ }| + {0:~ }{5:╔═}{11:Center}{5:══╗}{0: }| + {0:~ }{5:║}{1: halloj! }{5:║}{0: }| + {0:~ }{5:║}{1: BORDAA }{5:║}{0: }| + {0:~ }{5:╚═════════╝}{0: }| + | + ]]} + end + + meths.win_set_config(win, {title= "Right",title_pos="right"}) + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [3:----------------------------------------]| + ## grid 2 + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + | + ## grid 5 + {5:╔════}{11:Right}{5:╗}| + {5:║}{1: halloj! }{5:║}| + {5:║}{1: BORDAA }{5:║}| + {5:╚═════════╝}| + ]], float_pos={ + [5] = { { id = 1002 }, "NW", 1, 2, 5, true } + }, win_viewport={ + [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1}; + [5] = {win = {id = 1002}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 2}; + }} + else + screen:expect{grid=[[ + ^ | + {0:~ }| + {0:~ }{5:╔════}{11:Right}{5:╗}{0: }| + {0:~ }{5:║}{1: halloj! }{5:║}{0: }| + {0:~ }{5:║}{1: BORDAA }{5:║}{0: }| + {0:~ }{5:╚═════════╝}{0: }| + | + ]]} + end + + meths.win_set_config(win, {title= { {"🦄"},{"BB"}},title_pos="right"}) + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [3:----------------------------------------]| + ## grid 2 + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + | + ## grid 5 + {5:╔═════}🦄BB{5:╗}| + {5:║}{1: halloj! }{5:║}| + {5:║}{1: BORDAA }{5:║}| + {5:╚═════════╝}| + ]], float_pos={ + [5] = { { id = 1002 }, "NW", 1, 2, 5, true } + }, win_viewport={ + [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1}; + [5] = {win = {id = 1002}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 2}; + }} + else + screen:expect{grid=[[ + ^ | + {0:~ }| + {0:~ }{5:╔═════}🦄BB{5:╗}{0: }| + {0:~ }{5:║}{1: halloj! }{5:║}{0: }| + {0:~ }{5:║}{1: BORDAA }{5:║}{0: }| + {0:~ }{5:╚═════════╝}{0: }| + | + ]]} + end + end) + it('terminates border on edge of viewport when window extends past viewport', function() local buf = meths.create_buf(false, false) meths.open_win(buf, false, {relative='editor', width=40, height=7, row=0, col=0, border="single"}) @@ -2867,6 +3171,66 @@ describe('float window', function() ]]} end + command('set laststatus=0') + command('botright vnew') + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----]{5:│}[6:--------------------]| + [2:----]{5:│}[6:--------------------]| + [2:----]{5:│}[6:--------------------]| + [2:----]{5:│}[6:--------------------]| + [2:----]{5:│}[6:--------------------]| + [2:----]{5:│}[6:--------------------]| + [2:----]{5:│}[6:--------------------]| + [2:----]{5:│}[6:--------------------]| + [2:----]{5:│}[6:--------------------]| + [3:-------------------------]| + ## grid 2 + exam| + ple | + text| + tha| + t is| + wid| + er t| + han | + the | + ## grid 3 + | + ## grid 5 + {1:some info! }| + ## grid 6 + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ]], float_pos={ + [5] = { { + id = 1002 + }, "SW", 2, 8, 0, true } + }} + else + screen:expect{grid=[[ + exam{5:│}^ | + ple {5:│}{0:~ }| + text{5:│}{0:~ }| + tha{5:│}{0:~ }| + t is{5:│}{0:~ }| + wid{5:│}{0:~ }| + er t{5:│}{0:~ }| + {1:some info! }{0: }| + the {5:│}{0:~ }| + | + ]]} + end + command('close') + meths.win_set_config(win, {relative='win', bufpos={1,32}, anchor='NW', col=-2}) if multigrid then screen:expect{grid=[[ @@ -2962,6 +3326,54 @@ describe('float window', function() | ]]} end + + command('%fold') + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:-------------------------]| + [2:-------------------------]| + [2:-------------------------]| + [2:-------------------------]| + [2:-------------------------]| + [2:-------------------------]| + [2:-------------------------]| + [2:-------------------------]| + [2:-------------------------]| + [3:-------------------------]| + ## grid 2 + {28:^+-- 5 lines: just some··}| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + | + ## grid 5 + {1:some info! }| + ]], float_pos={ + [5] = { { + id = 1002 + }, "NW", 2, 2, 0, true } + }} + else + screen:expect{grid=[[ + {28:^+-- 5 lines: just some··}| + {0:~ }| + {1:some info! }{0: }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]} + end end) it('validates cursor even when window is not entered', function() @@ -8512,6 +8924,34 @@ describe('float window', function() ]]} end end) + + describe('no crash after moving and closing float window #21547', function() + local function test_float_move_close(cmd) + local float_opts = {relative = 'editor', row = 1, col = 1, width = 10, height = 10} + meths.open_win(meths.create_buf(false, false), true, float_opts) + if multigrid then + screen:expect({float_pos = {[4] = {{id = 1001}, 'NW', 1, 1, 1, true}}}) + end + command(cmd) + exec_lua([[ + vim.api.nvim_win_set_config(0, {relative = 'editor', row = 2, col = 2}) + vim.api.nvim_win_close(0, {}) + vim.api.nvim_echo({{''}}, false, {}) + ]]) + if multigrid then + screen:expect({float_pos = {}}) + end + assert_alive() + end + + it('if WinClosed autocommand flushes UI', function() + test_float_move_close('autocmd WinClosed * ++once redraw') + end) + + it('if closing buffer flushes UI', function() + test_float_move_close('autocmd BufWinLeave * ++once redraw') + end) + end) end describe('with ext_multigrid', function() diff --git a/test/functional/ui/fold_spec.lua b/test/functional/ui/fold_spec.lua index 3c143d87ca..46a478c1ea 100644 --- a/test/functional/ui/fold_spec.lua +++ b/test/functional/ui/fold_spec.lua @@ -7,7 +7,8 @@ local insert = helpers.insert local expect = helpers.expect local funcs = helpers.funcs local meths = helpers.meths -local source = helpers.source +local exec = helpers.exec +local exec_lua = helpers.exec_lua local assert_alive = helpers.assert_alive @@ -199,50 +200,6 @@ describe("folded lines", function() end end) - it("highlighting with relative line numbers", function() - command("set relativenumber cursorline cursorlineopt=number foldmethod=marker") - feed_command("set foldcolumn=2") - funcs.setline(1, '{{{1') - funcs.setline(2, 'line 1') - funcs.setline(3, '{{{1') - funcs.setline(4, 'line 2') - feed("j") - if multigrid then - screen:expect([[ - ## grid 1 - [2:---------------------------------------------]| - [2:---------------------------------------------]| - [2:---------------------------------------------]| - [2:---------------------------------------------]| - [2:---------------------------------------------]| - [2:---------------------------------------------]| - [2:---------------------------------------------]| - [3:---------------------------------------------]| - ## grid 2 - {7:+ }{8: 1 }{5:+-- 2 lines: ·························}| - {7:+ }{9: 0 }{5:^+-- 2 lines: ·························}| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - ## grid 3 - :set foldcolumn=2 | - ]]) - else - screen:expect([[ - {7:+ }{8: 1 }{5:+-- 2 lines: ·························}| - {7:+ }{9: 0 }{5:^+-- 2 lines: ·························}| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - :set foldcolumn=2 | - ]]) - end - end) - it("work with spell", function() command("set spell") insert(content1) @@ -1714,7 +1671,7 @@ describe("folded lines", function() end) it('does not crash when foldtext is longer than columns #12988', function() - source([[ + exec([[ function! MyFoldText() abort return repeat('-', &columns + 100) endfunction @@ -1761,7 +1718,7 @@ describe("folded lines", function() it('work correctly with :move #18668', function() screen:try_resize(45, 12) - source([[ + exec([[ set foldmethod=expr foldexpr=indent(v:lnum) let content = ['', '', 'Line1', ' Line2', ' Line3', \ 'Line4', ' Line5', ' Line6', @@ -1896,6 +1853,128 @@ describe("folded lines", function() ]]) end end) + + it('fold attached virtual lines are drawn correctly #21837', function() + funcs.setline(1, 'line 1') + funcs.setline(2, 'line 2') + funcs.setline(3, 'line 3') + funcs.setline(4, 'line 4') + feed("zfj") + exec_lua([[ + local ns = vim.api.nvim_create_namespace("ns") + vim.api.nvim_buf_set_extmark(0, ns, 0, 0, { virt_lines_above = true, virt_lines = {{{"virt_line above line 1", ""}}} }) + vim.api.nvim_buf_set_extmark(0, ns, 1, 0, { virt_lines = {{{"virt_line below line 2", ""}}} }) + vim.api.nvim_buf_set_extmark(0, ns, 2, 0, { virt_lines_above = true, virt_lines = {{{"virt_line above line 3", ""}}} }) + vim.api.nvim_buf_set_extmark(0, ns, 3, 0, { virt_lines = {{{"virt_line below line 4", ""}}} }) + ]]) + if multigrid then + screen:expect([[ + ## grid 1 + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [3:---------------------------------------------]| + ## grid 2 + {5:^+-- 2 lines: line 1·························}| + virt_line above line 3 | + line 3 | + line 4 | + virt_line below line 4 | + {1:~ }| + {1:~ }| + ## grid 3 + | + ]]) + else + screen:expect([[ + {5:^+-- 2 lines: line 1·························}| + virt_line above line 3 | + line 3 | + line 4 | + virt_line below line 4 | + {1:~ }| + {1:~ }| + | + ]]) + end + + feed('jzfj') + if multigrid then + screen:expect([[ + ## grid 1 + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [3:---------------------------------------------]| + ## grid 2 + {5:+-- 2 lines: line 1·························}| + {5:^+-- 2 lines: line 3·························}| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + | + ]]) + else + screen:expect([[ + {5:+-- 2 lines: line 1·························}| + {5:^+-- 2 lines: line 3·························}| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + end + + feed('kzo<C-Y>') + funcs.setline(5, 'line 5') + if multigrid then + screen:expect([[ + ## grid 1 + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [3:---------------------------------------------]| + ## grid 2 + virt_line above line 1 | + ^line 1 | + line 2 | + virt_line below line 2 | + {5:+-- 2 lines: line 3·························}| + line 5 | + {1:~ }| + ## grid 3 + | + ]]) + else + screen:expect([[ + virt_line above line 1 | + ^line 1 | + line 2 | + virt_line below line 2 | + {5:+-- 2 lines: line 3·························}| + line 5 | + {1:~ }| + | + ]]) + end + end) end describe("with ext_multigrid", function() diff --git a/test/functional/ui/highlight_spec.lua b/test/functional/ui/highlight_spec.lua index 2a6ee0d56c..61abe341c7 100644 --- a/test/functional/ui/highlight_spec.lua +++ b/test/functional/ui/highlight_spec.lua @@ -6,6 +6,7 @@ local command, exec = helpers.command, helpers.exec local eval = helpers.eval local feed_command, eq = helpers.feed_command, helpers.eq local curbufmeths = helpers.curbufmeths +local funcs = helpers.funcs local meths = helpers.meths describe('colorscheme compatibility', function() @@ -13,7 +14,9 @@ describe('colorscheme compatibility', function() clear() end) - it('t_Co is set to 256 by default', function() + it('&t_Co exists and is set to 256 by default', function() + eq(1, funcs.exists('&t_Co')) + eq(1, funcs.exists('+t_Co')) eq('256', eval('&t_Co')) end) end) @@ -1049,6 +1052,7 @@ describe('CursorLine and CursorLineNr highlights', function() ]]) end) + -- oldtest: Test_cursorline_after_yank() it('always updated. vim-patch:8.1.0849', function() local screen = Screen.new(50,5) screen:set_default_attr_ids({ @@ -1082,6 +1086,7 @@ describe('CursorLine and CursorLineNr highlights', function() ]]) end) + -- oldtest: Test_cursorline_with_visualmode() it('with visual area. vim-patch:8.1.1001', function() local screen = Screen.new(50,5) screen:set_default_attr_ids({ @@ -1109,6 +1114,7 @@ describe('CursorLine and CursorLineNr highlights', function() ]]) end) + -- oldtest: Test_cursorline_callback() it('is updated if cursor is moved up from timer vim-patch:8.2.4591', function() local screen = Screen.new(50, 8) screen:set_default_attr_ids({ @@ -1238,6 +1244,7 @@ describe('CursorLine and CursorLineNr highlights', function() }) end) + -- oldtest: Test_diff_with_cursorline_number() it('CursorLineNr shows correctly just below filler lines', function() local screen = Screen.new(50,12) screen:set_default_attr_ids({ @@ -1358,6 +1365,7 @@ describe('CursorColumn highlight', function() ]]) end) + -- oldtest: Test_cursorcolumn_callback() it('is updated if cursor is moved from timer', function() exec([[ call setline(1, ['aaaaa', 'bbbbb', 'ccccc', 'ddddd']) @@ -1399,7 +1407,7 @@ describe('ColorColumn highlight', function() before_each(function() clear() screen = Screen.new(40, 15) - Screen:set_default_attr_ids({ + screen:set_default_attr_ids({ [1] = {background = Screen.colors.LightRed}, -- ColorColumn [2] = {background = Screen.colors.Grey90}, -- CursorLine [3] = {foreground = Screen.colors.Brown}, -- LineNr @@ -1413,6 +1421,7 @@ describe('ColorColumn highlight', function() screen:attach() end) + -- oldtest: Test_colorcolumn() it('when entering a buffer vim-patch:8.1.2073', function() exec([[ set nohidden @@ -1444,6 +1453,7 @@ describe('ColorColumn highlight', function() ]]) end) + -- oldtest: Test_colorcolumn_bri() it("in 'breakindent' vim-patch:8.2.1689", function() exec([[ call setline(1, 'The quick brown fox jumped over the lazy dogs') @@ -1468,6 +1478,7 @@ describe('ColorColumn highlight', function() ]]) end) + -- oldtest: Test_colorcolumn_sbr() it("in 'showbreak' vim-patch:8.2.1689", function() exec([[ call setline(1, 'The quick brown fox jumped over the lazy dogs') @@ -1687,6 +1698,7 @@ describe("'number' and 'relativenumber' highlight", function() ]]) end) + -- oldtest: Test_relativenumber_callback() it('relative number highlight is updated if cursor is moved from timer', function() local screen = Screen.new(50, 8) screen:set_default_attr_ids({ @@ -1735,38 +1747,40 @@ describe("'winhighlight' highlight", function() clear() screen = Screen.new(20,8) screen:attach() - screen:set_default_attr_ids({ - [0] = {bold=true, foreground=Screen.colors.Blue}, - [1] = {background = Screen.colors.DarkBlue}, - [2] = {background = Screen.colors.DarkBlue, bold = true, foreground = Screen.colors.Blue1}, - [3] = {bold = true, reverse = true}, - [4] = {reverse = true}, - [5] = {background = Screen.colors.DarkGreen}, - [6] = {background = Screen.colors.DarkGreen, bold = true, foreground = Screen.colors.Blue1}, - [7] = {background = Screen.colors.DarkMagenta}, - [8] = {background = Screen.colors.DarkMagenta, bold = true, foreground = Screen.colors.Blue1}, - [9] = {foreground = Screen.colors.Brown}, - [10] = {foreground = Screen.colors.Brown, background = Screen.colors.DarkBlue}, - [11] = {background = Screen.colors.DarkBlue, bold = true, reverse = true}, - [12] = {background = Screen.colors.DarkGreen, reverse = true}, - [13] = {background = Screen.colors.Magenta4, reverse = true}, - [14] = {background = Screen.colors.DarkBlue, reverse = true}, - [15] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red}, - [16] = {foreground = Screen.colors.Blue1}, - [17] = {background = Screen.colors.LightRed}, - [18] = {background = Screen.colors.Gray90}, - [19] = {foreground = Screen.colors.LightGrey, background = Screen.colors.DarkGray}, - [20] = {background = Screen.colors.LightGrey, underline = true}, - [21] = {bold = true}, - [22] = {bold = true, foreground = Screen.colors.SeaGreen4}, - [23] = {background = Screen.colors.LightMagenta}, - [24] = {background = Screen.colors.WebGray}, - [25] = {bold = true, foreground = Screen.colors.Green1}, - [26] = {background = Screen.colors.Red}, - [27] = {background = Screen.colors.DarkBlue, bold = true, foreground = Screen.colors.Green1}, - [28] = {bold = true, foreground = Screen.colors.Brown}, + screen:set_default_attr_ids { + [0] = {bold=true, foreground=Screen.colors.Blue}; + [1] = {background = Screen.colors.DarkBlue}; + [2] = {background = Screen.colors.DarkBlue, bold = true, foreground = Screen.colors.Blue1}; + [3] = {bold = true, reverse = true}; + [4] = {reverse = true}; + [5] = {background = Screen.colors.DarkGreen}; + [6] = {background = Screen.colors.DarkGreen, bold = true, foreground = Screen.colors.Blue1}; + [7] = {background = Screen.colors.DarkMagenta}; + [8] = {background = Screen.colors.DarkMagenta, bold = true, foreground = Screen.colors.Blue1}; + [9] = {foreground = Screen.colors.Brown}; + [10] = {foreground = Screen.colors.Brown, background = Screen.colors.DarkBlue}; + [11] = {background = Screen.colors.DarkBlue, bold = true, reverse = true}; + [12] = {background = Screen.colors.DarkGreen, reverse = true}; + [13] = {background = Screen.colors.Magenta4, reverse = true}; + [14] = {background = Screen.colors.DarkBlue, reverse = true}; + [15] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red}; + [16] = {foreground = Screen.colors.Blue1}; + [17] = {background = Screen.colors.LightRed}; + [18] = {background = Screen.colors.Gray90}; + [19] = {foreground = Screen.colors.LightGrey, background = Screen.colors.DarkGray}; + [20] = {background = Screen.colors.LightGrey, underline = true}; + [21] = {bold = true}; + [22] = {bold = true, foreground = Screen.colors.SeaGreen4}; + [23] = {background = Screen.colors.LightMagenta}; + [24] = {background = Screen.colors.WebGray}; + [25] = {bold = true, foreground = Screen.colors.Green1}; + [26] = {background = Screen.colors.Red}; + [27] = {background = Screen.colors.DarkBlue, bold = true, foreground = Screen.colors.Green1}; + [28] = {bold = true, foreground = Screen.colors.Brown}; [29] = {foreground = Screen.colors.Blue1, background = Screen.colors.Red, bold = true}; - }) + [30] = {background = tonumber('0xff8800')}; + [31] = {background = tonumber('0xff8800'), bold = true, foreground = Screen.colors.Blue}; + } command("hi Background1 guibg=DarkBlue") command("hi Background2 guibg=DarkGreen") end) @@ -2011,6 +2025,33 @@ describe("'winhighlight' highlight", function() ]]) end) + it('updates background to changed linked group', function() + command("split") + command("setlocal winhl=Normal:FancyGroup") -- does not yet exist + screen:expect{grid=[[ + ^ | + {0:~ }| + {0:~ }| + {3:[No Name] }| + | + {0:~ }| + {4:[No Name] }| + | + ]]} + + command("hi FancyGroup guibg=#FF8800") -- nice orange + screen:expect{grid=[[ + {30:^ }| + {31:~ }| + {31:~ }| + {3:[No Name] }| + | + {0:~ }| + {4:[No Name] }| + | + ]]} + end) + it('background applies also to non-text', function() command('set sidescroll=0') insert('Lorem ipsum dolor sit amet ') diff --git a/test/functional/ui/hlstate_spec.lua b/test/functional/ui/hlstate_spec.lua index dc74d6d401..55f873e827 100644 --- a/test/functional/ui/hlstate_spec.lua +++ b/test/functional/ui/hlstate_spec.lua @@ -4,9 +4,10 @@ local Screen = require('test.functional.ui.screen') local clear, insert = helpers.clear, helpers.insert local command = helpers.command local meths = helpers.meths -local iswin = helpers.iswin local testprg = helpers.testprg local thelpers = require('test.functional.terminal.helpers') +local skip = helpers.skip +local is_os = helpers.is_os describe('ext_hlstate detailed highlights', function() local screen @@ -182,7 +183,7 @@ describe('ext_hlstate detailed highlights', function() end) it("work with :terminal", function() - if helpers.pending_win32(pending) then return end + skip(is_os('win')) screen:set_default_attr_ids({ [1] = {{}, {{hi_name = "TermCursorNC", ui_name = "TermCursorNC", kind = "ui"}}}, @@ -211,7 +212,7 @@ describe('ext_hlstate detailed highlights', function() thelpers.set_bold() thelpers.feed_data('z\n') -- TODO(bfredl): check if this distinction makes sense - if iswin() then + if is_os('win') then screen:expect([[ ^tty ready | x {5:y z} | @@ -237,7 +238,7 @@ describe('ext_hlstate detailed highlights', function() thelpers.feed_termcode("[A") thelpers.feed_termcode("[2C") - if iswin() then + if is_os('win') then screen:expect([[ ^tty ready | x {6:y}{5: z} | diff --git a/test/functional/ui/linematch_spec.lua b/test/functional/ui/linematch_spec.lua new file mode 100644 index 0000000000..697677aa67 --- /dev/null +++ b/test/functional/ui/linematch_spec.lua @@ -0,0 +1,995 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') + +local feed = helpers.feed +local clear = helpers.clear +local write_file = helpers.write_file + +describe('Diff mode screen with 3 diffs open', function() + local fname = 'Xtest-functional-diff-screen-1' + local fname_2 = fname .. '.2' + local fname_3 = fname .. '.3' + local screen + + local reread = function() + feed(':e<cr><c-w>w:e<cr><c-w>w:e<cr><c-w>w') + end + + setup(function() + clear() + os.remove(fname) + os.remove(fname_2) + os.remove(fname_3) + end) + + teardown(function() + os.remove(fname) + os.remove(fname_2) + os.remove(fname_3) + end) + + before_each(function() + clear() + feed(':set diffopt+=linematch:30<cr>') + feed(':e ' .. fname .. '<cr>') + feed(':vnew ' .. fname_2 .. '<cr>') + feed(':vnew ' .. fname_3 .. '<cr>') + feed(':windo diffthis<cr>') + + screen = Screen.new(100, 16) + screen:attach() + screen:set_default_attr_ids({ + [1] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.Gray}; + [2] = {foreground = Screen.colors.Blue1, bold = true, background = Screen.colors.LightCyan1}; + [3] = {reverse = true}; + [4] = {background = Screen.colors.LightBlue}; + [5] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.LightGray}; + [6] = {foreground = Screen.colors.Blue1, bold = true}; + [7] = {reverse = true, bold = true}; + [8] = {background = Screen.colors.Red1, bold = true}; + [10] = {foreground = Screen.colors.Brown}; + [9] = {background = Screen.colors.Plum1}; + }) + feed('<c-w>=') + feed(':windo set nu!<cr>') + end) + + describe('setup the diff screen to look like a merge conflict with 3 files in diff mode', function() + before_each(function() + + local f1 = [[ + + common line + AAA + AAA + AAA + ]] + local f2 = [[ + + common line + <<<<<<< HEAD + AAA + AAA + AAA + ======= + BBB + BBB + BBB + >>>>>>> branch1 + ]] + local f3 = [[ + + common line + BBB + BBB + BBB + ]] + + write_file(fname, f1, false) + write_file(fname_2, f2, false) + write_file(fname_3, f3, false) + reread() + end) + + it('get from window 1', function() + feed('1<c-w>w') + feed(':2,6diffget screen-1.2<cr>') + screen:expect([[ + {1: }{10: 1 }^ │{1: }{10: 1 } │{1: }{10: 1 } | + {1: }{10: 2 }common line │{1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: 3 }{9:<<<<<<< HEAD }│{1: }{10: 3 }{9:<<<<<<< HEAD }│{1: }{10: }{2:---------------------------}| + {1: }{10: 4 } AAA │{1: }{10: 4 } AAA │{1: }{10: 3 } AAA | + {1: }{10: 5 } AAA │{1: }{10: 5 } AAA │{1: }{10: 4 } AAA | + {1: }{10: 6 } AAA │{1: }{10: 6 } AAA │{1: }{10: 5 } AAA | + {1: }{10: 7 }{9:======= }│{1: }{10: 7 }{9:======= }│{1: }{10: }{2:---------------------------}| + {1: }{10: 8 }{9: BBB }│{1: }{10: 8 }{9: BBB }│{1: }{10: }{2:---------------------------}| + {1: }{10: 9 }{9: BBB }│{1: }{10: 9 }{9: BBB }│{1: }{10: }{2:---------------------------}| + {1: }{10: 10 }{9: BBB }│{1: }{10: 10 }{9: BBB }│{1: }{10: }{2:---------------------------}| + {1: }{10: 11 }{9:>>>>>>> branch1 }│{1: }{10: 11 }{9:>>>>>>> branch1 }│{1: }{10: }{2:---------------------------}| + {1: }{10: 12 } │{1: }{10: 12 } │{1: }{10: 6 } | + {6:~ }│{6:~ }│{6:~ }| + {6:~ }│{6:~ }│{6:~ }| + {7:<-functional-diff-screen-1.3 [+] }{3:<est-functional-diff-screen-1.2 Xtest-functional-diff-screen-1 }| + :2,6diffget screen-1.2 | + ]]) + end) + + it('get from window 2', function() + feed('2<c-w>w') + feed(':5,7diffget screen-1.3<cr>') + screen:expect([[ + {1: }{10: 1 } │{1: }{10: 1 }^ │{1: }{10: 1 } | + {1: }{10: 2 }common line │{1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: }{2:---------------------------}│{1: }{10: 3 }{4:<<<<<<< HEAD }│{1: }{10: }{2:---------------------------}| + {1: }{10: }{2:---------------------------}│{1: }{10: 4 }{9: AAA }│{1: }{10: 3 }{9: AAA }| + {1: }{10: 3 }{9: BBB }│{1: }{10: 5 }{9: BBB }│{1: }{10: }{2:---------------------------}| + {1: }{10: 4 }{9: }{8:BBB}{9: }│{1: }{10: 6 }{9: }{8:BBB}{9: }│{1: }{10: 4 }{9: }{8:AAA}{9: }| + {1: }{10: 5 }{9: }{8:BBB}{9: }│{1: }{10: 7 }{9: }{8:BBB}{9: }│{1: }{10: 5 }{9: }{8:AAA}{9: }| + {1: }{10: }{2:---------------------------}│{1: }{10: 8 }{4:>>>>>>> branch1 }│{1: }{10: }{2:---------------------------}| + {1: }{10: 6 } │{1: }{10: 9 } │{1: }{10: 6 } | + {6:~ }│{6:~ }│{6:~ }| + {6:~ }│{6:~ }│{6:~ }| + {6:~ }│{6:~ }│{6:~ }| + {6:~ }│{6:~ }│{6:~ }| + {6:~ }│{6:~ }│{6:~ }| + {3:<test-functional-diff-screen-1.3 }{7:<functional-diff-screen-1.2 [+] }{3:Xtest-functional-diff-screen-1 }| + :5,7diffget screen-1.3 | + ]]) + end) + + it('get from window 3', function() + feed('3<c-w>w') + feed(':5,6diffget screen-1.2<cr>') + screen:expect([[ + {1: }{10: 1 } │{1: }{10: 1 } │{1: }{10: 1 }^ | + {1: }{10: 2 }common line │{1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: }{2:---------------------------}│{1: }{10: 3 }{4:<<<<<<< HEAD }│{1: }{10: }{2:---------------------------}| + {1: }{10: }{2:---------------------------}│{1: }{10: 4 }{9: AAA }│{1: }{10: 3 }{9: AAA }| + {1: }{10: }{2:---------------------------}│{1: }{10: 5 }{9: AAA }│{1: }{10: 4 }{9: AAA }| + {1: }{10: }{2:---------------------------}│{1: }{10: 6 }{9: AAA }│{1: }{10: 5 }{9: AAA }| + {1: }{10: }{2:---------------------------}│{1: }{10: 7 }{9:======= }│{1: }{10: 6 }{9:======= }| + {1: }{10: 3 } BBB │{1: }{10: 8 } BBB │{1: }{10: 7 } BBB | + {1: }{10: 4 } BBB │{1: }{10: 9 } BBB │{1: }{10: 8 } BBB | + {1: }{10: 5 } BBB │{1: }{10: 10 } BBB │{1: }{10: 9 } BBB | + {1: }{10: }{2:---------------------------}│{1: }{10: 11 }{9:>>>>>>> branch1 }│{1: }{10: 10 }{9:>>>>>>> branch1 }| + {1: }{10: 6 } │{1: }{10: 12 } │{1: }{10: 11 } | + {6:~ }│{6:~ }│{6:~ }| + {6:~ }│{6:~ }│{6:~ }| + {3:<test-functional-diff-screen-1.3 <est-functional-diff-screen-1.2 }{7:<st-functional-diff-screen-1 [+] }| + :5,6diffget screen-1.2 | + ]]) + end) + + it('put from window 2 - part', function() + feed('2<c-w>w') + feed(':6,8diffput screen-1<cr>') + screen:expect([[ + {1: }{10: 1 } │{1: }{10: 1 }^ │{1: }{10: 1 } | + {1: }{10: 2 }common line │{1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: }{2:---------------------------}│{1: }{10: 3 }{4:<<<<<<< HEAD }│{1: }{10: }{2:---------------------------}| + {1: }{10: }{2:---------------------------}│{1: }{10: 4 }{9: AAA }│{1: }{10: 3 }{9: AAA }| + {1: }{10: }{2:---------------------------}│{1: }{10: 5 }{9: AAA }│{1: }{10: 4 }{9: AAA }| + {1: }{10: }{2:---------------------------}│{1: }{10: 6 }{9: AAA }│{1: }{10: 5 }{9: AAA }| + {1: }{10: }{2:---------------------------}│{1: }{10: 7 }{9:======= }│{1: }{10: 6 }{9:======= }| + {1: }{10: 3 }{9: BBB }│{1: }{10: 8 }{9: BBB }│{1: }{10: }{2:---------------------------}| + {1: }{10: 4 }{9: BBB }│{1: }{10: 9 }{9: BBB }│{1: }{10: }{2:---------------------------}| + {1: }{10: 5 } BBB │{1: }{10: 10 } BBB │{1: }{10: 7 } BBB | + {1: }{10: }{2:---------------------------}│{1: }{10: 11 }{4:>>>>>>> branch1 }│{1: }{10: }{2:---------------------------}| + {1: }{10: 6 } │{1: }{10: 12 } │{1: }{10: 8 } | + {6:~ }│{6:~ }│{6:~ }| + {6:~ }│{6:~ }│{6:~ }| + {3:<test-functional-diff-screen-1.3 }{7:<est-functional-diff-screen-1.2 }{3:<st-functional-diff-screen-1 [+] }| + :6,8diffput screen-1 | + ]]) + + end) + it('put from window 2 - part to end', function() + feed('2<c-w>w') + feed(':6,11diffput screen-1<cr>') + screen:expect([[ + {1: }{10: 1 } │{1: }{10: 1 }^ │{1: }{10: 1 } | + {1: }{10: 2 }common line │{1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: }{2:---------------------------}│{1: }{10: 3 }{4:<<<<<<< HEAD }│{1: }{10: }{2:---------------------------}| + {1: }{10: }{2:---------------------------}│{1: }{10: 4 }{9: AAA }│{1: }{10: 3 }{9: AAA }| + {1: }{10: }{2:---------------------------}│{1: }{10: 5 }{9: AAA }│{1: }{10: 4 }{9: AAA }| + {1: }{10: }{2:---------------------------}│{1: }{10: 6 }{9: AAA }│{1: }{10: 5 }{9: AAA }| + {1: }{10: }{2:---------------------------}│{1: }{10: 7 }{9:======= }│{1: }{10: 6 }{9:======= }| + {1: }{10: 3 } BBB │{1: }{10: 8 } BBB │{1: }{10: 7 } BBB | + {1: }{10: 4 } BBB │{1: }{10: 9 } BBB │{1: }{10: 8 } BBB | + {1: }{10: 5 } BBB │{1: }{10: 10 } BBB │{1: }{10: 9 } BBB | + {1: }{10: }{2:---------------------------}│{1: }{10: 11 }{9:>>>>>>> branch1 }│{1: }{10: 10 }{9:>>>>>>> branch1 }| + {1: }{10: 6 } │{1: }{10: 12 } │{1: }{10: 11 } | + {6:~ }│{6:~ }│{6:~ }| + {6:~ }│{6:~ }│{6:~ }| + {3:<test-functional-diff-screen-1.3 }{7:<est-functional-diff-screen-1.2 }{3:<st-functional-diff-screen-1 [+] }| + :6,11diffput screen-1 | + ]]) + + end) + end) +end) + +describe('Diff mode screen with 2 diffs open', function() + local fname = 'Xtest-functional-diff-screen-1' + local fname_2 = fname .. '.2' + local screen + + local reread = function() + feed(':e<cr><c-w>w:e<cr><c-w>w:e<cr><c-w>w') + end + + setup(function() + clear() + os.remove(fname) + os.remove(fname_2) + end) + + teardown(function() + os.remove(fname) + os.remove(fname_2) + end) + + before_each(function() + clear() + feed(':e ' .. fname .. '<cr>') + feed(':vnew ' .. fname_2 .. '<cr>') + feed(':windo diffthis<cr>') + + screen = Screen.new(100, 20) + screen:attach() + screen:set_default_attr_ids({ + [1] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.Gray}; + [2] = {foreground = Screen.colors.Blue1, bold = true, background = Screen.colors.LightCyan1}; + [3] = {reverse = true}; + [4] = {background = Screen.colors.LightBlue}; + [5] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.LightGray}; + [6] = {foreground = Screen.colors.Blue1, bold = true}; + [7] = {reverse = true, bold = true}; + [8] = {background = Screen.colors.Red1, bold = true}; + [10] = {foreground = Screen.colors.Brown}; + [9] = {background = Screen.colors.Plum1}; + }) + feed('<c-w>=') + feed(':windo set nu!<cr>') + end) + + describe('setup a diff with 2 files and set linematch:30', function() + before_each(function() + feed(':set diffopt+=linematch:30<cr>') + local f1 = [[ + +common line +common line + +DEFabc +xyz +xyz +xyz +DEFabc +DEFabc +DEFabc +common line +common line +DEF +common line +DEF +something + ]] + local f2 = [[ + +common line +common line + +ABCabc +ABCabc +ABCabc +ABCabc +common line +common line +common line +something + ]] + write_file(fname, f1, false) + write_file(fname_2, f2, false) + reread() + end) + + it('get from window 1 from line 5 to 9', function() + feed('1<c-w>w') + feed(':5,9diffget<cr>') + screen:expect([[ + {1:+ }{10: 1 }{5:^+-- 7 lines: common line··················}│{1:+ }{10: 1 }{5:+-- 7 lines: common line···················}| + {1: }{10: 8 }xyz │{1: }{10: 8 }xyz | + {1: }{10: 9 }DEFabc │{1: }{10: 9 }DEFabc | + {1: }{10: 10 }DEFabc │{1: }{10: 10 }DEFabc | + {1: }{10: 11 }DEFabc │{1: }{10: 11 }DEFabc | + {1: }{10: 12 }common line │{1: }{10: 12 }common line | + {1: }{10: 13 }common line │{1: }{10: 13 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 14 }{4:DEF }| + {1: }{10: 14 }common line │{1: }{10: 15 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 16 }{4:DEF }| + {1: }{10: 15 }something │{1: }{10: 17 }something | + {1: }{10: 16 } │{1: }{10: 18 } | + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {7:Xtest-functional-diff-screen-1.2 [+] }{3:Xtest-functional-diff-screen-1 }| + :5,9diffget | + ]]) + end) + it('get from window 2 from line 5 to 10', function() + feed('2<c-w>w') + feed(':5,10diffget<cr>') + screen:expect([[ + {1:- }{10: 1 } │{1:- }{10: 1 }^ | + {1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: 3 }common line │{1: }{10: 3 }common line | + {1: }{10: 4 } │{1: }{10: 4 } | + {1: }{10: 5 }ABCabc │{1: }{10: 5 }ABCabc | + {1: }{10: 6 }ABCabc │{1: }{10: 6 }ABCabc | + {1: }{10: 7 }ABCabc │{1: }{10: 7 }ABCabc | + {1: }{10: 8 }{8:ABC}{9:abc }│{1: }{10: 8 }{8:DEF}{9:abc }| + {1: }{10: 9 }common line │{1: }{10: 9 }common line | + {1: }{10: 10 }common line │{1: }{10: 10 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 11 }{4:DEF }| + {1: }{10: 11 }common line │{1: }{10: 12 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 13 }{4:DEF }| + {1: }{10: 12 }something │{1: }{10: 14 }something | + {1: }{10: 13 } │{1: }{10: 15 } | + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {3:Xtest-functional-diff-screen-1.2 }{7:Xtest-functional-diff-screen-1 [+] }| + :5,10diffget | + ]]) + end) + it('get all from window 2', function() + feed('2<c-w>w') + feed(':4,17diffget<cr>') + screen:expect([[ + {1: }{10: 1 } │{1: }{10: 1 }^ | + {1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: 3 }common line │{1: }{10: 3 }common line | + {1: }{10: 4 } │{1: }{10: 4 } | + {1: }{10: 5 }ABCabc │{1: }{10: 5 }ABCabc | + {1: }{10: 6 }ABCabc │{1: }{10: 6 }ABCabc | + {1: }{10: 7 }ABCabc │{1: }{10: 7 }ABCabc | + {1: }{10: 8 }ABCabc │{1: }{10: 8 }ABCabc | + {1: }{10: 9 }common line │{1: }{10: 9 }common line | + {1: }{10: 10 }common line │{1: }{10: 10 }common line | + {1: }{10: 11 }common line │{1: }{10: 11 }common line | + {1: }{10: 12 }something │{1: }{10: 12 }something | + {1: }{10: 13 } │{1: }{10: 13 } | + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {3:Xtest-functional-diff-screen-1.2 }{7:Xtest-functional-diff-screen-1 [+] }| + :4,17diffget | + ]]) + + end) + it('get all from window 1', function() + feed('1<c-w>w') + feed(':4,12diffget<cr>') + screen:expect([[ + {1: }{10: 1 }^ │{1: }{10: 1 } | + {1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: 3 }common line │{1: }{10: 3 }common line | + {1: }{10: 4 } │{1: }{10: 4 } | + {1: }{10: 5 }DEFabc │{1: }{10: 5 }DEFabc | + {1: }{10: 6 }xyz │{1: }{10: 6 }xyz | + {1: }{10: 7 }xyz │{1: }{10: 7 }xyz | + {1: }{10: 8 }xyz │{1: }{10: 8 }xyz | + {1: }{10: 9 }DEFabc │{1: }{10: 9 }DEFabc | + {1: }{10: 10 }DEFabc │{1: }{10: 10 }DEFabc | + {1: }{10: 11 }DEFabc │{1: }{10: 11 }DEFabc | + {1: }{10: 12 }common line │{1: }{10: 12 }common line | + {1: }{10: 13 }common line │{1: }{10: 13 }common line | + {1: }{10: 14 }DEF │{1: }{10: 14 }DEF | + {1: }{10: 15 }common line │{1: }{10: 15 }common line | + {1: }{10: 16 }DEF │{1: }{10: 16 }DEF | + {1: }{10: 17 }something │{1: }{10: 17 }something | + {1: }{10: 18 } │{1: }{10: 18 } | + {7:Xtest-functional-diff-screen-1.2 [+] }{3:Xtest-functional-diff-screen-1 }| + :4,12diffget | + ]]) + end) + it('get from window 1 using do 1 line 5', function() + feed('1<c-w>w') + feed('5gg') + feed('do') + screen:expect([[ + {1: }{10: 1 } │{1: }{10: 1 } | + {1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: 3 }common line │{1: }{10: 3 }common line | + {1: }{10: 4 } │{1: }{10: 4 } | + {1: }{10: 5 }^DEFabc │{1: }{10: 5 }DEFabc | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 6 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 7 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 8 }{4:xyz }| + {1: }{10: 6 }{8:ABC}{9:abc }│{1: }{10: 9 }{8:DEF}{9:abc }| + {1: }{10: 7 }{8:ABC}{9:abc }│{1: }{10: 10 }{8:DEF}{9:abc }| + {1: }{10: 8 }{8:ABC}{9:abc }│{1: }{10: 11 }{8:DEF}{9:abc }| + {1: }{10: 9 }common line │{1: }{10: 12 }common line | + {1: }{10: 10 }common line │{1: }{10: 13 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 14 }{4:DEF }| + {1: }{10: 11 }common line │{1: }{10: 15 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 16 }{4:DEF }| + {1: }{10: 12 }something │{1: }{10: 17 }something | + {1: }{10: 13 } │{1: }{10: 18 } | + {7:Xtest-functional-diff-screen-1.2 [+] }{3:Xtest-functional-diff-screen-1 }| + :e | + ]]) + end) + it('get from window 1 using do 2 line 6', function() + feed('1<c-w>w') + feed('6gg') + feed('do') + screen:expect([[ + {1: }{10: 1 } │{1: }{10: 1 } | + {1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: 3 }common line │{1: }{10: 3 }common line | + {1: }{10: 4 } │{1: }{10: 4 } | + {1: }{10: 5 }{8:ABC}{9:abc }│{1: }{10: 5 }{8:DEF}{9:abc }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 6 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 7 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 8 }{4:xyz }| + {1: }{10: 6 }^DEFabc │{1: }{10: 9 }DEFabc | + {1: }{10: 7 }DEFabc │{1: }{10: 10 }DEFabc | + {1: }{10: 8 }DEFabc │{1: }{10: 11 }DEFabc | + {1: }{10: 9 }common line │{1: }{10: 12 }common line | + {1: }{10: 10 }common line │{1: }{10: 13 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 14 }{4:DEF }| + {1: }{10: 11 }common line │{1: }{10: 15 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 16 }{4:DEF }| + {1: }{10: 12 }something │{1: }{10: 17 }something | + {1: }{10: 13 } │{1: }{10: 18 } | + {7:Xtest-functional-diff-screen-1.2 [+] }{3:Xtest-functional-diff-screen-1 }| + :e | + ]]) + end) + it('get from window 1 using do 2 line 7', function() + feed('1<c-w>w') + feed('7gg') + feed('do') + screen:expect([[ + {1: }{10: 1 } │{1: }{10: 1 } | + {1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: 3 }common line │{1: }{10: 3 }common line | + {1: }{10: 4 } │{1: }{10: 4 } | + {1: }{10: 5 }{8:ABC}{9:abc }│{1: }{10: 5 }{8:DEF}{9:abc }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 6 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 7 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 8 }{4:xyz }| + {1: }{10: 6 }DEFabc │{1: }{10: 9 }DEFabc | + {1: }{10: 7 }^DEFabc │{1: }{10: 10 }DEFabc | + {1: }{10: 8 }DEFabc │{1: }{10: 11 }DEFabc | + {1: }{10: 9 }common line │{1: }{10: 12 }common line | + {1: }{10: 10 }common line │{1: }{10: 13 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 14 }{4:DEF }| + {1: }{10: 11 }common line │{1: }{10: 15 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 16 }{4:DEF }| + {1: }{10: 12 }something │{1: }{10: 17 }something | + {1: }{10: 13 } │{1: }{10: 18 } | + {7:Xtest-functional-diff-screen-1.2 [+] }{3:Xtest-functional-diff-screen-1 }| + :e | + ]]) + end) + it('get from window 1 using do 2 line 11', function() + feed('1<c-w>w') + feed('11gg') + feed('do') + screen:expect([[ + {1: }{10: 1 } │{1: }{10: 1 } | + {1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: 3 }common line │{1: }{10: 3 }common line | + {1: }{10: 4 } │{1: }{10: 4 } | + {1: }{10: 5 }{8:ABC}{9:abc }│{1: }{10: 5 }{8:DEF}{9:abc }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 6 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 7 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 8 }{4:xyz }| + {1: }{10: 6 }{8:ABC}{9:abc }│{1: }{10: 9 }{8:DEF}{9:abc }| + {1: }{10: 7 }{8:ABC}{9:abc }│{1: }{10: 10 }{8:DEF}{9:abc }| + {1: }{10: 8 }{8:ABC}{9:abc }│{1: }{10: 11 }{8:DEF}{9:abc }| + {1: }{10: 9 }common line │{1: }{10: 12 }common line | + {1: }{10: 10 }common line │{1: }{10: 13 }common line | + {1: }{10: 11 }DEF │{1: }{10: 14 }DEF | + {1: }{10: 12 }^common line │{1: }{10: 15 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 16 }{4:DEF }| + {1: }{10: 13 }something │{1: }{10: 17 }something | + {1: }{10: 14 } │{1: }{10: 18 } | + {7:Xtest-functional-diff-screen-1.2 [+] }{3:Xtest-functional-diff-screen-1 }| + :e | + ]]) + end) + it('get from window 1 using do 2 line 12', function() + feed('1<c-w>w') + feed('12gg') + feed('do') + screen:expect([[ + {1: }{10: 1 } │{1: }{10: 1 } | + {1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: 3 }common line │{1: }{10: 3 }common line | + {1: }{10: 4 } │{1: }{10: 4 } | + {1: }{10: 5 }{8:ABC}{9:abc }│{1: }{10: 5 }{8:DEF}{9:abc }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 6 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 7 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 8 }{4:xyz }| + {1: }{10: 6 }{8:ABC}{9:abc }│{1: }{10: 9 }{8:DEF}{9:abc }| + {1: }{10: 7 }{8:ABC}{9:abc }│{1: }{10: 10 }{8:DEF}{9:abc }| + {1: }{10: 8 }{8:ABC}{9:abc }│{1: }{10: 11 }{8:DEF}{9:abc }| + {1: }{10: 9 }common line │{1: }{10: 12 }common line | + {1: }{10: 10 }common line │{1: }{10: 13 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 14 }{4:DEF }| + {1: }{10: 11 }common line │{1: }{10: 15 }common line | + {1: }{10: 12 }DEF │{1: }{10: 16 }DEF | + {1: }{10: 13 }^something │{1: }{10: 17 }something | + {1: }{10: 14 } │{1: }{10: 18 } | + {7:Xtest-functional-diff-screen-1.2 [+] }{3:Xtest-functional-diff-screen-1 }| + :e | + ]]) + end) + it('put from window 1 using dp 1 line 5', function() + feed('1<c-w>w') + feed('5gg') + feed('dp') + screen:expect([[ + {1: }{10: 1 } │{1: }{10: 1 } | + {1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: 3 }common line │{1: }{10: 3 }common line | + {1: }{10: 4 } │{1: }{10: 4 } | + {1: }{10: 5 }^ABCabc │{1: }{10: 5 }ABCabc | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 6 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 7 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 8 }{4:xyz }| + {1: }{10: 6 }{8:ABC}{9:abc }│{1: }{10: 9 }{8:DEF}{9:abc }| + {1: }{10: 7 }{8:ABC}{9:abc }│{1: }{10: 10 }{8:DEF}{9:abc }| + {1: }{10: 8 }{8:ABC}{9:abc }│{1: }{10: 11 }{8:DEF}{9:abc }| + {1: }{10: 9 }common line │{1: }{10: 12 }common line | + {1: }{10: 10 }common line │{1: }{10: 13 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 14 }{4:DEF }| + {1: }{10: 11 }common line │{1: }{10: 15 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 16 }{4:DEF }| + {1: }{10: 12 }something │{1: }{10: 17 }something | + {1: }{10: 13 } │{1: }{10: 18 } | + {7:Xtest-functional-diff-screen-1.2 }{3:Xtest-functional-diff-screen-1 [+] }| + :e | + ]]) + end) + it('put from window 1 using dp 2 line 6', function() + feed('1<c-w>w') + feed('6gg') + feed('dp') + screen:expect([[ + {1: }{10: 1 } │{1: }{10: 1 } | + {1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: 3 }common line │{1: }{10: 3 }common line | + {1: }{10: 4 } │{1: }{10: 4 } | + {1: }{10: 5 }{8:ABC}{9:abc }│{1: }{10: 5 }{8:DEF}{9:abc }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 6 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 7 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 8 }{4:xyz }| + {1: }{10: 6 }^ABCabc │{1: }{10: 9 }ABCabc | + {1: }{10: 7 }ABCabc │{1: }{10: 10 }ABCabc | + {1: }{10: 8 }ABCabc │{1: }{10: 11 }ABCabc | + {1: }{10: 9 }common line │{1: }{10: 12 }common line | + {1: }{10: 10 }common line │{1: }{10: 13 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 14 }{4:DEF }| + {1: }{10: 11 }common line │{1: }{10: 15 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 16 }{4:DEF }| + {1: }{10: 12 }something │{1: }{10: 17 }something | + {1: }{10: 13 } │{1: }{10: 18 } | + {7:Xtest-functional-diff-screen-1.2 }{3:Xtest-functional-diff-screen-1 [+] }| + :e | + ]]) + end) + it('put from window 1 using dp 2 line 7', function() + feed('1<c-w>w') + feed('7gg') + feed('dp') + screen:expect([[ + {1: }{10: 1 } │{1: }{10: 1 } | + {1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: 3 }common line │{1: }{10: 3 }common line | + {1: }{10: 4 } │{1: }{10: 4 } | + {1: }{10: 5 }{8:ABC}{9:abc }│{1: }{10: 5 }{8:DEF}{9:abc }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 6 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 7 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 8 }{4:xyz }| + {1: }{10: 6 }ABCabc │{1: }{10: 9 }ABCabc | + {1: }{10: 7 }^ABCabc │{1: }{10: 10 }ABCabc | + {1: }{10: 8 }ABCabc │{1: }{10: 11 }ABCabc | + {1: }{10: 9 }common line │{1: }{10: 12 }common line | + {1: }{10: 10 }common line │{1: }{10: 13 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 14 }{4:DEF }| + {1: }{10: 11 }common line │{1: }{10: 15 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 16 }{4:DEF }| + {1: }{10: 12 }something │{1: }{10: 17 }something | + {1: }{10: 13 } │{1: }{10: 18 } | + {7:Xtest-functional-diff-screen-1.2 }{3:Xtest-functional-diff-screen-1 [+] }| + :e | + ]]) + end) + it('put from window 1 using dp 2 line 11', function() + feed('1<c-w>w') + feed('11gg') + feed('dp') + screen:expect([[ + {1: }{10: 1 } │{1: }{10: 1 } | + {1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: 3 }common line │{1: }{10: 3 }common line | + {1: }{10: 4 } │{1: }{10: 4 } | + {1: }{10: 5 }{8:ABC}{9:abc }│{1: }{10: 5 }{8:DEF}{9:abc }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 6 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 7 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 8 }{4:xyz }| + {1: }{10: 6 }{8:ABC}{9:abc }│{1: }{10: 9 }{8:DEF}{9:abc }| + {1: }{10: 7 }{8:ABC}{9:abc }│{1: }{10: 10 }{8:DEF}{9:abc }| + {1: }{10: 8 }{8:ABC}{9:abc }│{1: }{10: 11 }{8:DEF}{9:abc }| + {1: }{10: 9 }common line │{1: }{10: 12 }common line | + {1: }{10: 10 }common line │{1: }{10: 13 }common line | + {1: }{10: 11 }^common line │{1: }{10: 14 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 15 }{4:DEF }| + {1: }{10: 12 }something │{1: }{10: 16 }something | + {1: }{10: 13 } │{1: }{10: 17 } | + {6:~ }│{6:~ }| + {7:Xtest-functional-diff-screen-1.2 }{3:Xtest-functional-diff-screen-1 [+] }| + :e | + ]]) + end) + it('put from window 1 using dp 2 line 12', function() + feed('1<c-w>w') + feed('12gg') + feed('dp') + screen:expect([[ + {1: }{10: 1 } │{1: }{10: 1 } | + {1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: 3 }common line │{1: }{10: 3 }common line | + {1: }{10: 4 } │{1: }{10: 4 } | + {1: }{10: 5 }{8:ABC}{9:abc }│{1: }{10: 5 }{8:DEF}{9:abc }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 6 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 7 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 8 }{4:xyz }| + {1: }{10: 6 }{8:ABC}{9:abc }│{1: }{10: 9 }{8:DEF}{9:abc }| + {1: }{10: 7 }{8:ABC}{9:abc }│{1: }{10: 10 }{8:DEF}{9:abc }| + {1: }{10: 8 }{8:ABC}{9:abc }│{1: }{10: 11 }{8:DEF}{9:abc }| + {1: }{10: 9 }common line │{1: }{10: 12 }common line | + {1: }{10: 10 }common line │{1: }{10: 13 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 14 }{4:DEF }| + {1: }{10: 11 }common line │{1: }{10: 15 }common line | + {1: }{10: 12 }^something │{1: }{10: 16 }something | + {1: }{10: 13 } │{1: }{10: 17 } | + {6:~ }│{6:~ }| + {7:Xtest-functional-diff-screen-1.2 }{3:Xtest-functional-diff-screen-1 [+] }| + :e | + ]]) + end) + it('put from window 2 using dp line 6', function() + feed('2<c-w>w') + feed('6gg') + feed('dp') + screen:expect([[ + {1: }{10: 1 } │{1: }{10: 1 } | + {1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: 3 }common line │{1: }{10: 3 }common line | + {1: }{10: 4 } │{1: }{10: 4 } | + {1: }{10: 5 }{8:ABC}{9:abc }│{1: }{10: 5 }{8:DEF}{9:abc }| + {1: }{10: 6 }xyz │{1: }{10: 6 }^xyz | + {1: }{10: 7 }xyz │{1: }{10: 7 }xyz | + {1: }{10: 8 }xyz │{1: }{10: 8 }xyz | + {1: }{10: 9 }{8:ABC}{9:abc }│{1: }{10: 9 }{8:DEF}{9:abc }| + {1: }{10: 10 }{8:ABC}{9:abc }│{1: }{10: 10 }{8:DEF}{9:abc }| + {1: }{10: 11 }{8:ABC}{9:abc }│{1: }{10: 11 }{8:DEF}{9:abc }| + {1: }{10: 12 }common line │{1: }{10: 12 }common line | + {1: }{10: 13 }common line │{1: }{10: 13 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 14 }{4:DEF }| + {1: }{10: 14 }common line │{1: }{10: 15 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 16 }{4:DEF }| + {1: }{10: 15 }something │{1: }{10: 17 }something | + {1: }{10: 16 } │{1: }{10: 18 } | + {3:Xtest-functional-diff-screen-1.2 [+] }{7:Xtest-functional-diff-screen-1 }| + :e | + ]]) + end) + it('put from window 2 using dp line 8', function() + feed('2<c-w>w') + feed('8gg') + feed('dp') + screen:expect([[ + {1: }{10: 1 } │{1: }{10: 1 } | + {1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: 3 }common line │{1: }{10: 3 }common line | + {1: }{10: 4 } │{1: }{10: 4 } | + {1: }{10: 5 }{8:ABC}{9:abc }│{1: }{10: 5 }{8:DEF}{9:abc }| + {1: }{10: 6 }xyz │{1: }{10: 6 }xyz | + {1: }{10: 7 }xyz │{1: }{10: 7 }xyz | + {1: }{10: 8 }xyz │{1: }{10: 8 }^xyz | + {1: }{10: 9 }{8:ABC}{9:abc }│{1: }{10: 9 }{8:DEF}{9:abc }| + {1: }{10: 10 }{8:ABC}{9:abc }│{1: }{10: 10 }{8:DEF}{9:abc }| + {1: }{10: 11 }{8:ABC}{9:abc }│{1: }{10: 11 }{8:DEF}{9:abc }| + {1: }{10: 12 }common line │{1: }{10: 12 }common line | + {1: }{10: 13 }common line │{1: }{10: 13 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 14 }{4:DEF }| + {1: }{10: 14 }common line │{1: }{10: 15 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 16 }{4:DEF }| + {1: }{10: 15 }something │{1: }{10: 17 }something | + {1: }{10: 16 } │{1: }{10: 18 } | + {3:Xtest-functional-diff-screen-1.2 [+] }{7:Xtest-functional-diff-screen-1 }| + :e | + ]]) + end) + it('put from window 2 using dp line 9', function() + feed('2<c-w>w') + feed('9gg') + feed('dp') + screen:expect([[ + {1: }{10: 1 } │{1: }{10: 1 } | + {1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: 3 }common line │{1: }{10: 3 }common line | + {1: }{10: 4 } │{1: }{10: 4 } | + {1: }{10: 5 }{8:ABC}{9:abc }│{1: }{10: 5 }{8:DEF}{9:abc }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 6 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 7 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 8 }{4:xyz }| + {1: }{10: 6 }DEFabc │{1: }{10: 9 }^DEFabc | + {1: }{10: 7 }DEFabc │{1: }{10: 10 }DEFabc | + {1: }{10: 8 }DEFabc │{1: }{10: 11 }DEFabc | + {1: }{10: 9 }common line │{1: }{10: 12 }common line | + {1: }{10: 10 }common line │{1: }{10: 13 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 14 }{4:DEF }| + {1: }{10: 11 }common line │{1: }{10: 15 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 16 }{4:DEF }| + {1: }{10: 12 }something │{1: }{10: 17 }something | + {1: }{10: 13 } │{1: }{10: 18 } | + {3:Xtest-functional-diff-screen-1.2 [+] }{7:Xtest-functional-diff-screen-1 }| + :e | + ]]) + end) + it('put from window 2 using dp line 17', function() + feed('2<c-w>w') + feed('17gg') + feed('dp') + screen:expect([[ + {1: }{10: 1 } │{1: }{10: 1 } | + {1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: 3 }common line │{1: }{10: 3 }common line | + {1: }{10: 4 } │{1: }{10: 4 } | + {1: }{10: 5 }{8:ABC}{9:abc }│{1: }{10: 5 }{8:DEF}{9:abc }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 6 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 7 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 8 }{4:xyz }| + {1: }{10: 6 }{8:ABC}{9:abc }│{1: }{10: 9 }{8:DEF}{9:abc }| + {1: }{10: 7 }{8:ABC}{9:abc }│{1: }{10: 10 }{8:DEF}{9:abc }| + {1: }{10: 8 }{8:ABC}{9:abc }│{1: }{10: 11 }{8:DEF}{9:abc }| + {1: }{10: 9 }common line │{1: }{10: 12 }common line | + {1: }{10: 10 }common line │{1: }{10: 13 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 14 }{4:DEF }| + {1: }{10: 11 }common line │{1: }{10: 15 }common line | + {1: }{10: 12 }DEF │{1: }{10: 16 }DEF | + {1: }{10: 13 }something │{1: }{10: 17 }^something | + {1: }{10: 14 } │{1: }{10: 18 } | + {3:Xtest-functional-diff-screen-1.2 [+] }{7:Xtest-functional-diff-screen-1 }| + :e | + ]]) + + end) + end) + describe('setup a diff with 2 files and set linematch:10', function() + before_each(function() + feed(':set diffopt+=linematch:10<cr>') + local f1 = [[ +common line +HIL + +aABCabc +aABCabc +aABCabc +aABCabc +common line +HIL +common line +something + ]] + local f2 = [[ +common line +DEF +GHI +something + +aDEFabc +xyz +xyz +xyz +aDEFabc +aDEFabc +aDEFabc +common line +DEF +GHI +something else +common line +something + ]] + write_file(fname, f1, false) + write_file(fname_2, f2, false) + reread() + end) + + it('enable linematch for the longest diff block by increasing the number argument passed to linematch', function() + feed('1<c-w>w') + -- linematch is disabled for the longest diff because it's combined line length is over 10 + screen:expect([[ + {1: }{10: 1 }^common line │{1: }{10: 1 }common line | + {1: }{10: 2 }{4:DEF }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 3 }{8:GHI}{9: }│{1: }{10: 2 }{8:HIL}{9: }| + {1: }{10: 4 }{4:something }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 5 } │{1: }{10: 3 } | + {1: }{10: 6 }{9:a}{8:DEF}{9:abc }│{1: }{10: 4 }{9:a}{8:ABC}{9:abc }| + {1: }{10: 7 }{8:xyz}{9: }│{1: }{10: 5 }{8:aABCabc}{9: }| + {1: }{10: 8 }{8:xyz}{9: }│{1: }{10: 6 }{8:aABCabc}{9: }| + {1: }{10: 9 }{8:xyz}{9: }│{1: }{10: 7 }{8:aABCabc}{9: }| + {1: }{10: 10 }{4:aDEFabc }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 11 }{4:aDEFabc }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 12 }{4:aDEFabc }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 13 }common line │{1: }{10: 8 }common line | + {1: }{10: 14 }{4:DEF }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 15 }{8:GHI}{9: }│{1: }{10: 9 }{8:HIL}{9: }| + {1: }{10: 16 }{4:something else }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 17 }common line │{1: }{10: 10 }common line | + {1: }{10: 18 }something │{1: }{10: 11 }something | + {7:Xtest-functional-diff-screen-1.2 }{3:Xtest-functional-diff-screen-1 }| + :e | + ]]) + -- enable it by increasing the number + feed(":set diffopt-=linematch:10<cr>") + feed(":set diffopt+=linematch:30<cr>") + screen:expect([[ + {1: }{10: 1 }^common line │{1: }{10: 1 }common line | + {1: }{10: 2 }{4:DEF }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 3 }{8:GHI}{9: }│{1: }{10: 2 }{8:HIL}{9: }| + {1: }{10: 4 }{4:something }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 5 } │{1: }{10: 3 } | + {1: }{10: 6 }{9:a}{8:DEF}{9:abc }│{1: }{10: 4 }{9:a}{8:ABC}{9:abc }| + {1: }{10: 7 }{4:xyz }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 8 }{4:xyz }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 9 }{4:xyz }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 10 }{9:a}{8:DEF}{9:abc }│{1: }{10: 5 }{9:a}{8:ABC}{9:abc }| + {1: }{10: 11 }{9:a}{8:DEF}{9:abc }│{1: }{10: 6 }{9:a}{8:ABC}{9:abc }| + {1: }{10: 12 }{9:a}{8:DEF}{9:abc }│{1: }{10: 7 }{9:a}{8:ABC}{9:abc }| + {1: }{10: 13 }common line │{1: }{10: 8 }common line | + {1: }{10: 14 }{4:DEF }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 15 }{8:GHI}{9: }│{1: }{10: 9 }{8:HIL}{9: }| + {1: }{10: 16 }{4:something else }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 17 }common line │{1: }{10: 10 }common line | + {1: }{10: 18 }something │{1: }{10: 11 }something | + {7:Xtest-functional-diff-screen-1.2 }{3:Xtest-functional-diff-screen-1 }| + :set diffopt+=linematch:30 | + ]]) + end) + it('get all from second window', function() + feed('2<c-w>w') + feed(':1,12diffget<cr>') + screen:expect([[ + {1: }{10: 1 }common line │{1: }{10: 1 }^common line | + {1: }{10: 2 }DEF │{1: }{10: 2 }DEF | + {1: }{10: 3 }GHI │{1: }{10: 3 }GHI | + {1: }{10: 4 }something │{1: }{10: 4 }something | + {1: }{10: 5 } │{1: }{10: 5 } | + {1: }{10: 6 }aDEFabc │{1: }{10: 6 }aDEFabc | + {1: }{10: 7 }xyz │{1: }{10: 7 }xyz | + {1: }{10: 8 }xyz │{1: }{10: 8 }xyz | + {1: }{10: 9 }xyz │{1: }{10: 9 }xyz | + {1: }{10: 10 }aDEFabc │{1: }{10: 10 }aDEFabc | + {1: }{10: 11 }aDEFabc │{1: }{10: 11 }aDEFabc | + {1: }{10: 12 }aDEFabc │{1: }{10: 12 }aDEFabc | + {1: }{10: 13 }common line │{1: }{10: 13 }common line | + {1: }{10: 14 }DEF │{1: }{10: 14 }DEF | + {1: }{10: 15 }GHI │{1: }{10: 15 }GHI | + {1: }{10: 16 }something else │{1: }{10: 16 }something else | + {1: }{10: 17 }common line │{1: }{10: 17 }common line | + {1: }{10: 18 }something │{1: }{10: 18 }something | + {3:Xtest-functional-diff-screen-1.2 }{7:Xtest-functional-diff-screen-1 [+] }| + :1,12diffget | + ]]) + end) + it('get all from first window', function() + feed('1<c-w>w') + feed(':1,19diffget<cr>') + screen:expect([[ + {1: }{10: 1 }^common line │{1: }{10: 1 }common line | + {1: }{10: 2 }HIL │{1: }{10: 2 }HIL | + {1: }{10: 3 } │{1: }{10: 3 } | + {1: }{10: 4 }aABCabc │{1: }{10: 4 }aABCabc | + {1: }{10: 5 }aABCabc │{1: }{10: 5 }aABCabc | + {1: }{10: 6 }aABCabc │{1: }{10: 6 }aABCabc | + {1: }{10: 7 }aABCabc │{1: }{10: 7 }aABCabc | + {1: }{10: 8 }common line │{1: }{10: 8 }common line | + {1: }{10: 9 }HIL │{1: }{10: 9 }HIL | + {1: }{10: 10 }common line │{1: }{10: 10 }common line | + {1: }{10: 11 }something │{1: }{10: 11 }something | + {1: }{10: 12 } │{1: }{10: 12 } | + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {7:Xtest-functional-diff-screen-1.2 [+] }{3:Xtest-functional-diff-screen-1 }| + :1,19diffget | + ]]) + end) + it('get part of the non linematched diff block in window 2 line 7 - 8 (non line matched block)', function() + feed('2<c-w>w') + feed(':7,8diffget<cr>') + screen:expect([[ + {1: }{10: 1 }common line │{1: }{10: 1 }^common line | + {1: }{10: 2 }{4:DEF }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 3 }{8:GHI}{9: }│{1: }{10: 2 }{8:HIL}{9: }| + {1: }{10: 4 }{4:something }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 5 } │{1: }{10: 3 } | + {1: }{10: 6 }{9:a}{8:DEF}{9:abc }│{1: }{10: 4 }{9:a}{8:ABC}{9:abc }| + {1: }{10: 7 }{8:xyz}{9: }│{1: }{10: 5 }{8:aABCabc}{9: }| + {1: }{10: 8 }{8:xyz}{9: }│{1: }{10: 6 }{8:aABCabc}{9: }| + {1: }{10: 9 }xyz │{1: }{10: 7 }xyz | + {1: }{10: 10 }aDEFabc │{1: }{10: 8 }aDEFabc | + {1: }{10: 11 }aDEFabc │{1: }{10: 9 }aDEFabc | + {1: }{10: 12 }aDEFabc │{1: }{10: 10 }aDEFabc | + {1: }{10: 13 }common line │{1: }{10: 11 }common line | + {1: }{10: 14 }{4:DEF }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 15 }{8:GHI}{9: }│{1: }{10: 12 }{8:HIL}{9: }| + {1: }{10: 16 }{4:something else }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 17 }common line │{1: }{10: 13 }common line | + {1: }{10: 18 }something │{1: }{10: 14 }something | + {3:Xtest-functional-diff-screen-1.2 }{7:Xtest-functional-diff-screen-1 [+] }| + :7,8diffget | + ]]) + end) + it('get part of the non linematched diff block in window 2 line 8 - 10 (line matched block)', function() + feed('2<c-w>w') + feed(':8,10diffget<cr>') + screen:expect([[ + {1: }{10: 1 }common line │{1: }{10: 1 }^common line | + {1: }{10: 2 }{4:DEF }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 3 }{8:GHI}{9: }│{1: }{10: 2 }{8:HIL}{9: }| + {1: }{10: 4 }{4:something }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 5 } │{1: }{10: 3 } | + {1: }{10: 6 }{9:a}{8:DEF}{9:abc }│{1: }{10: 4 }{9:a}{8:ABC}{9:abc }| + {1: }{10: 7 }{8:xyz}{9: }│{1: }{10: 5 }{8:aABCabc}{9: }| + {1: }{10: 8 }{8:xyz}{9: }│{1: }{10: 6 }{8:aABCabc}{9: }| + {1: }{10: 9 }{8:xyz}{9: }│{1: }{10: 7 }{8:aABCabc}{9: }| + {1: }{10: 10 }{4:aDEFabc }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 11 }{4:aDEFabc }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 12 }{4:aDEFabc }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 13 }common line │{1: }{10: 8 }common line | + {1: }{10: 14 }DEF │{1: }{10: 9 }DEF | + {1: }{10: 15 }GHI │{1: }{10: 10 }GHI | + {1: }{10: 16 }something else │{1: }{10: 11 }something else | + {1: }{10: 17 }common line │{1: }{10: 12 }common line | + {1: }{10: 18 }something │{1: }{10: 13 }something | + {3:Xtest-functional-diff-screen-1.2 }{7:Xtest-functional-diff-screen-1 [+] }| + :8,10diffget | + ]]) + end) + end) +end) + +describe('regressions', function() + local screen + + it("doesn't crash with long lines", function() + clear() + feed(':set diffopt+=linematch:30<cr>') + screen = Screen.new(100, 20) + screen:attach() + -- line must be greater than MATCH_CHAR_MAX_LEN + helpers.curbufmeths.set_lines(0, -1, false, { string.rep('a', 1000)..'hello' }) + helpers.exec 'vnew' + helpers.curbufmeths.set_lines(0, -1, false, { string.rep('a', 1010)..'world' }) + helpers.exec 'windo diffthis' + end) +end) diff --git a/test/functional/ui/messages_spec.lua b/test/functional/ui/messages_spec.lua index 0898d7fe75..3052a74f38 100644 --- a/test/functional/ui/messages_spec.lua +++ b/test/functional/ui/messages_spec.lua @@ -9,10 +9,13 @@ local meths = helpers.meths local async_meths = helpers.async_meths local test_build_dir = helpers.test_build_dir local nvim_prog = helpers.nvim_prog -local iswin = helpers.iswin +local exec = helpers.exec local exc_exec = helpers.exc_exec local exec_lua = helpers.exec_lua local poke_eventloop = helpers.poke_eventloop +local assert_alive = helpers.assert_alive +local is_os = helpers.is_os +local is_ci = helpers.is_ci describe('ui/ext_messages', function() local screen @@ -905,6 +908,13 @@ stack traceback: {1:~ }| ]]} end) + + it('does not truncate messages', function() + command('write Xtest') + screen:expect({messages={ + {content = { { '"Xtest" [New] 0L, 0B written' } }, kind = "" } + }}) + end) end) describe('ui/builtin messages', function() @@ -1234,6 +1244,45 @@ vimComment xxx match /\s"[^\-:.%#=*].*$/ms=s+1,lc=1 excludenl contains=@vim bar^ | ]]) end) + + it('consecutive calls to win_move_statusline() work after multiline message #21014',function() + async_meths.exec([[ + echo "\n" + call win_move_statusline(0, -4) + call win_move_statusline(0, 4) + ]], false) + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }| + {3: }| + | + {4:Press ENTER or type command to continue}^ | + ]]) + feed('<CR>') + screen:expect([[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + eq(1, meths.get_option('cmdheight')) + end) +end) + +it('calling screenstring() after redrawing between messages without UI #20999', function() + clear() + exec([[ + echo repeat('a', 100) + redraw + echo "\n" + call screenstring(1, 1) + ]]) + assert_alive() end) describe('ui/ext_messages', function() @@ -1263,7 +1312,6 @@ describe('ui/ext_messages', function() {1:~ }| {1:~ }| {1:~ }| - {1:~ }| {MATCH:.*}| {1:~ }| {1:~ }Nvim is open source and freely distributable{1: }| @@ -1274,6 +1322,8 @@ describe('ui/ext_messages', function() {1:~ }type :q{5:<Enter>} to exit {1: }| {1:~ }type :help{5:<Enter>} for help {1: }| {1:~ }| + {1:~ }type :help news{5:<Enter>} to see changes in v{MATCH:%d+%.%d+}| + {1:~ }| {MATCH:.*}| {MATCH:.*}| {1:~ }| @@ -1281,7 +1331,6 @@ describe('ui/ext_messages', function() {1:~ }| {1:~ }| {1:~ }| - {1:~ }| ]]) feed("<c-l>") @@ -1319,7 +1368,6 @@ describe('ui/ext_messages', function() | | | - | {MATCH:.*}| | Nvim is open source and freely distributable | @@ -1330,6 +1378,8 @@ describe('ui/ext_messages', function() type :q{5:<Enter>} to exit | type :help{5:<Enter>} for help | | + type :help news{5:<Enter>} to see changes in v{MATCH:%d+%.%d+}| + | {MATCH:.*}| {MATCH:.*}| | @@ -1337,7 +1387,6 @@ describe('ui/ext_messages', function() | | | - | ]], messages={ {content = { { "Press ENTER or type command to continue", 4 } }, kind = "return_prompt" } }} @@ -1410,7 +1459,6 @@ describe('ui/ext_messages', function() feed(":set mouse=a<cr>") meths.input_mouse('left', 'press', '', 0, 12, 10) poke_eventloop() - meths.input_mouse('left', 'drag', '', 0, 12, 10) meths.input_mouse('left', 'drag', '', 0, 11, 10) feed("<c-l>") feed(":set cmdheight<cr>") @@ -1455,7 +1503,7 @@ describe('ui/msg_puts_printf', function() screen = Screen.new(25, 5) screen:attach() - if iswin() then + if is_os('win') then if os.execute('chcp 932 > NUL 2>&1') ~= 0 then pending('missing japanese language features', function() end) return @@ -1466,7 +1514,7 @@ describe('ui/msg_puts_printf', function() if (exc_exec('lang ja_JP.UTF-8') ~= 0) then pending('Locale ja_JP.UTF-8 not supported', function() end) return - elseif helpers.isCI() then + elseif is_ci() then -- Fails non--Windows CI. Message catalog directory issue? pending('fails on unix CI', function() end) return diff --git a/test/functional/ui/mode_spec.lua b/test/functional/ui/mode_spec.lua index 9390f268b3..cf4eb034e0 100644 --- a/test/functional/ui/mode_spec.lua +++ b/test/functional/ui/mode_spec.lua @@ -17,6 +17,7 @@ describe('ui mode_change event', function() [1] = {bold=true, reverse=true}, [2] = {bold=true}, [3] = {reverse=true}, + [4] = {background=Screen.colors.Red, foreground=Screen.colors.White}, -- ErrorMsg }) end) @@ -43,6 +44,25 @@ describe('ui mode_change event', function() {0:~ }| | ]], mode="normal"} + + screen:try_resize(50, 4) + command('set nomodifiable') + + feed('c') + screen:expect{grid=[[ + ^ | + {0:~ }| + {0:~ }| + | + ]], mode="operator"} + + feed('c') + screen:expect{grid=[[ + ^ | + {0:~ }| + {0:~ }| + {4:E21: Cannot make changes, 'modifiable' is off} | + ]], mode="normal"} end) it('works in insert mode', function() diff --git a/test/functional/ui/mouse_spec.lua b/test/functional/ui/mouse_spec.lua index cb8dfdb8e1..f705678bd5 100644 --- a/test/functional/ui/mouse_spec.lua +++ b/test/functional/ui/mouse_spec.lua @@ -4,6 +4,7 @@ local clear, feed, meths = helpers.clear, helpers.feed, helpers.meths local insert, feed_command = helpers.insert, helpers.feed_command local eq, funcs = helpers.eq, helpers.funcs local command = helpers.command +local exec = helpers.exec describe('ui/mouse/input', function() local screen @@ -967,6 +968,49 @@ describe('ui/mouse/input', function() ]]) end) + it("'sidescrolloff' applies to horizontal scrolling", function() + command('set nowrap') + command('set sidescrolloff=4') + + feed("I <esc>020ib<esc>0") + screen:expect([[ + testing | + mouse | + ^bbbbbbbbbbbbbbbbbbbb supp| + {0:~ }| + | + ]]) + + meths.input_mouse('wheel', 'right', '', 0, 0, 27) + screen:expect([[ + g | + | + bbbb^bbbbbbbbbb support an| + {0:~ }| + | + ]]) + + -- window-local 'sidescrolloff' should override global value. #21162 + command('setlocal sidescrolloff=2') + feed('0') + screen:expect([[ + testing | + mouse | + ^bbbbbbbbbbbbbbbbbbbb supp| + {0:~ }| + | + ]]) + + meths.input_mouse('wheel', 'right', '', 0, 0, 27) + screen:expect([[ + g | + | + bb^bbbbbbbbbbbb support an| + {0:~ }| + | + ]]) + end) + describe('on concealed text', function() -- Helpful for reading the test expectations: -- :match Error /\^/ @@ -1687,4 +1731,133 @@ describe('ui/mouse/input', function() helpers.poke_eventloop() helpers.assert_alive() end) + + it('mousemodel=popup_setpos', function() + screen:try_resize(80, 24) + exec([[ + 5new + call setline(1, ['the dish ran away with the spoon', + \ 'the cow jumped over the moon' ]) + + set mouse=a mousemodel=popup_setpos + + aunmenu PopUp + nmenu PopUp.foo :let g:menustr = 'foo'<CR> + nmenu PopUp.bar :let g:menustr = 'bar'<CR> + nmenu PopUp.baz :let g:menustr = 'baz'<CR> + vmenu PopUp.foo y:<C-U>let g:menustr = 'foo'<CR> + vmenu PopUp.bar y:<C-U>let g:menustr = 'bar'<CR> + vmenu PopUp.baz y:<C-U>let g:menustr = 'baz'<CR> + ]]) + + meths.win_set_cursor(0, {1, 0}) + meths.input_mouse('right', 'press', '', 0, 0, 4) + meths.input_mouse('right', 'release', '', 0, 0, 4) + feed('<Down><Down><CR>') + eq('bar', meths.get_var('menustr')) + eq({1, 4}, meths.win_get_cursor(0)) + + -- Test for right click in visual mode inside the selection + funcs.setreg('"', '') + meths.win_set_cursor(0, {1, 9}) + feed('vee') + meths.input_mouse('right', 'press', '', 0, 0, 11) + meths.input_mouse('right', 'release', '', 0, 0, 11) + feed('<Down><CR>') + eq({1, 9}, meths.win_get_cursor(0)) + eq('ran away', funcs.getreg('"')) + + -- Test for right click in visual mode right before the selection + funcs.setreg('"', '') + meths.win_set_cursor(0, {1, 9}) + feed('vee') + meths.input_mouse('right', 'press', '', 0, 0, 8) + meths.input_mouse('right', 'release', '', 0, 0, 8) + feed('<Down><CR>') + eq({1, 8}, meths.win_get_cursor(0)) + eq('', funcs.getreg('"')) + + -- Test for right click in visual mode right after the selection + funcs.setreg('"', '') + meths.win_set_cursor(0, {1, 9}) + feed('vee') + meths.input_mouse('right', 'press', '', 0, 0, 17) + meths.input_mouse('right', 'release', '', 0, 0, 17) + feed('<Down><CR>') + eq({1, 17}, meths.win_get_cursor(0)) + eq('', funcs.getreg('"')) + + -- Test for right click in block-wise visual mode inside the selection + funcs.setreg('"', '') + meths.win_set_cursor(0, {1, 15}) + feed('<C-V>j3l') + meths.input_mouse('right', 'press', '', 0, 1, 16) + meths.input_mouse('right', 'release', '', 0, 1, 16) + feed('<Down><CR>') + eq({1, 15}, meths.win_get_cursor(0)) + eq('\0224', funcs.getregtype('"')) + + -- Test for right click in block-wise visual mode outside the selection + funcs.setreg('"', '') + meths.win_set_cursor(0, {1, 15}) + feed('<C-V>j3l') + meths.input_mouse('right', 'press', '', 0, 1, 1) + meths.input_mouse('right', 'release', '', 0, 1, 1) + feed('<Down><CR>') + eq({2, 1}, meths.win_get_cursor(0)) + eq('v', funcs.getregtype('"')) + eq('', funcs.getreg('"')) + + -- Test for right click in line-wise visual mode inside the selection + funcs.setreg('"', '') + meths.win_set_cursor(0, {1, 15}) + feed('V') + meths.input_mouse('right', 'press', '', 0, 0, 9) + meths.input_mouse('right', 'release', '', 0, 0, 9) + feed('<Down><CR>') + eq({1, 0}, meths.win_get_cursor(0)) -- After yanking, the cursor goes to 1,1 + eq('V', funcs.getregtype('"')) + eq(1, #funcs.getreg('"', 1, true)) + + -- Test for right click in multi-line line-wise visual mode inside the selection + funcs.setreg('"', '') + meths.win_set_cursor(0, {1, 15}) + feed('Vj') + meths.input_mouse('right', 'press', '', 0, 1, 19) + meths.input_mouse('right', 'release', '', 0, 1, 19) + feed('<Down><CR>') + eq({1, 0}, meths.win_get_cursor(0)) -- After yanking, the cursor goes to 1,1 + eq('V', funcs.getregtype('"')) + eq(2, #funcs.getreg('"', 1, true)) + + -- Test for right click in line-wise visual mode outside the selection + funcs.setreg('"', '') + meths.win_set_cursor(0, {1, 15}) + feed('V') + meths.input_mouse('right', 'press', '', 0, 1, 9) + meths.input_mouse('right', 'release', '', 0, 1, 9) + feed('<Down><CR>') + eq({2, 9}, meths.win_get_cursor(0)) + eq('', funcs.getreg('"')) + + -- Try clicking on the status line + funcs.setreg('"', '') + meths.win_set_cursor(0, {1, 9}) + feed('vee') + meths.input_mouse('right', 'press', '', 0, 5, 1) + meths.input_mouse('right', 'release', '', 0, 5, 1) + feed('<Down><CR>') + eq({1, 9}, meths.win_get_cursor(0)) + eq('ran away', funcs.getreg('"')) + + -- Try clicking outside the window + funcs.setreg('"', '') + meths.win_set_cursor(0, {2, 1}) + feed('vee') + meths.input_mouse('right', 'press', '', 0, 6, 1) + meths.input_mouse('right', 'release', '', 0, 6, 1) + feed('<Down><CR>') + eq(2, funcs.winnr()) + eq('', funcs.getreg('"')) + end) end) diff --git a/test/functional/ui/multigrid_spec.lua b/test/functional/ui/multigrid_spec.lua index 78a1e8c677..71adeb42a4 100644 --- a/test/functional/ui/multigrid_spec.lua +++ b/test/functional/ui/multigrid_spec.lua @@ -2381,8 +2381,52 @@ describe('ext_multigrid', function() end) it('with winbar', function() - command 'split' - command 'setlocal winbar=very\\ bar' + command('split') + screen:expect{grid=[[ + ## grid 1 + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + {11:[No Name] }| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + {12:[No Name] }| + [3:-----------------------------------------------------]| + ## grid 2 + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + | + ## grid 4 + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ]], win_viewport={ + [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1}; + [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1}; + }} + + -- XXX: hack to get notifications. Could use next_msg() also. + local orig_handle_win_pos = screen._handle_win_pos + local win_pos = {} + function screen._handle_win_pos(self, grid, win, startrow, startcol, width, height) + table.insert(win_pos, {grid, win, startrow, startcol, width, height}) + orig_handle_win_pos(self, grid, win, startrow, startcol, width, height) + end + + command('setlocal winbar=very%=bar') screen:expect{grid=[[ ## grid 1 [4:-----------------------------------------------------]| @@ -2408,7 +2452,7 @@ describe('ext_multigrid', function() ## grid 3 | ## grid 4 - {7:very bar }| + {7:very bar}| ^ | {1:~ }| {1:~ }| @@ -2418,6 +2462,45 @@ describe('ext_multigrid', function() [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1}; [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1}; }} + eq({}, win_pos) + + command('setlocal winbar=') + screen:expect{grid=[[ + ## grid 1 + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + {11:[No Name] }| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + {12:[No Name] }| + [3:-----------------------------------------------------]| + ## grid 2 + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + | + ## grid 4 + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ]], win_viewport={ + [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1}; + [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1}; + }} + eq({}, win_pos) end) it('with winbar dragging statusline with mouse works correctly', function() diff --git a/test/functional/ui/options_spec.lua b/test/functional/ui/options_spec.lua index 6f9cea8f24..9d20229ce1 100644 --- a/test/functional/ui/options_spec.lua +++ b/test/functional/ui/options_spec.lua @@ -24,6 +24,7 @@ describe('UI receives option updates', function() termguicolors=false, ttimeout=true, ttimeoutlen=50, + verbose=0, ext_cmdline=false, ext_popupmenu=false, ext_tabline=false, diff --git a/test/functional/ui/output_spec.lua b/test/functional/ui/output_spec.lua index 9bb067ed8e..223844405e 100644 --- a/test/functional/ui/output_spec.lua +++ b/test/functional/ui/output_spec.lua @@ -6,13 +6,14 @@ local mkdir, write_file, rmdir = helpers.mkdir, helpers.write_file, helpers.rmdi local eq = helpers.eq local feed = helpers.feed local feed_command = helpers.feed_command -local iswin = helpers.iswin local clear = helpers.clear local command = helpers.command local testprg = helpers.testprg local nvim_dir = helpers.nvim_dir local has_powershell = helpers.has_powershell local set_shell_powershell = helpers.set_shell_powershell +local skip = helpers.skip +local is_os = helpers.is_os describe("shell command :!", function() local screen @@ -36,7 +37,7 @@ describe("shell command :!", function() end) it("displays output without LF/EOF. #4646 #4569 #3772", function() - if helpers.pending_win32(pending) then return end + skip(is_os('win')) -- NOTE: We use a child nvim (within a :term buffer) -- to avoid triggering a UI flush. child_session.feed_data(":!printf foo; sleep 200\n") @@ -52,9 +53,7 @@ describe("shell command :!", function() end) it("throttles shell-command output greater than ~10KB", function() - if 'openbsd' == helpers.uname() then - pending('FIXME #10804') - end + skip(is_os('openbsd'), 'FIXME #10804') child_session.feed_data((":!%s REP 30001 foo\n"):format(testprg('shell-test'))) -- If we observe any line starting with a dot, then throttling occurred. @@ -96,9 +95,7 @@ describe("shell command :!", function() end) it('handles control codes', function() - if iswin() then - pending('missing printf') - end + skip(is_os('win'), 'missing printf') local screen = Screen.new(50, 4) screen:set_default_attr_ids { [1] = {bold = true, reverse = true}; @@ -173,10 +170,10 @@ describe("shell command :!", function() end) it("doesn't truncate Last line of shell output #3269", function() - command(helpers.iswin() + command(is_os('win') and [[nnoremap <silent>\l :!dir /b bang_filter_spec<cr>]] or [[nnoremap <silent>\l :!ls bang_filter_spec<cr>]]) - local result = (helpers.iswin() + local result = (is_os('win') and [[:!dir /b bang_filter_spec]] or [[:!ls bang_filter_spec ]]) feed([[\l]]) @@ -215,7 +212,7 @@ describe("shell command :!", function() it('handles multibyte sequences split over buffer boundaries', function() command('cd '..nvim_dir) - local cmd = iswin() and '!shell-test UTF-8 ' or '!./shell-test UTF-8' + local cmd = is_os('win') and '!shell-test UTF-8 ' or '!./shell-test UTF-8' feed_command(cmd) -- Note: only the first example of split composed char works screen:expect([[ @@ -265,7 +262,7 @@ describe("shell command :!", function() | Press ENTER or type command to continue^ | ]]) - if iswin() then + if is_os('win') then feed_command([[!& 'cmd.exe' /c 'echo $a']]) screen:expect([[ :!& 'cmd.exe' /c 'echo $a' | diff --git a/test/functional/ui/popupmenu_spec.lua b/test/functional/ui/popupmenu_spec.lua index 3c752875f0..c681453294 100644 --- a/test/functional/ui/popupmenu_spec.lua +++ b/test/functional/ui/popupmenu_spec.lua @@ -259,174 +259,339 @@ describe('ui/ext_popupmenu', function() {2:-- INSERT --} | ]]) - command('imap <f1> <cmd>call nvim_select_popupmenu_item(2,v:true,v:false,{})<cr>') - command('imap <f2> <cmd>call nvim_select_popupmenu_item(-1,v:false,v:false,{})<cr>') - command('imap <f3> <cmd>call nvim_select_popupmenu_item(1,v:false,v:true,{})<cr>') - feed('<C-r>=TestComplete()<CR>') - screen:expect{grid=[[ + command('set wildmenu') + command('set wildoptions=pum') + local expected_wildpum = { + { "define", "", "", "" }, + { "jump", "", "", "" }, + { "list", "", "", "" }, + { "place", "", "", "" }, + { "undefine", "", "", "" }, + { "unplace", "", "", "" }, + } + feed('<Esc>:sign <Tab>') + screen:expect({grid = [[ + | | - foo^ | {1:~ }| {1:~ }| {1:~ }| {1:~ }| {1:~ }| - {2:-- INSERT --} | - ]], popupmenu={ - items=expected, - pos=0, - anchor={1,1,0}, - }} + :sign define^ | + ]], popupmenu = { + items = expected_wildpum, + pos = 0, + anchor = { 1, 7, 6 }, + }}) - feed('<f1>') - screen:expect{grid=[[ + meths.select_popupmenu_item(-1, true, false, {}) + screen:expect({grid = [[ + | | - spam^ | {1:~ }| {1:~ }| {1:~ }| {1:~ }| {1:~ }| - {2:-- INSERT --} | - ]], popupmenu={ - items=expected, - pos=2, - anchor={1,1,0}, - }} + :sign ^ | + ]], popupmenu = { + items = expected_wildpum, + pos = -1, + anchor = { 1, 7, 6 }, + }}) - feed('<f2>') - screen:expect{grid=[[ + meths.select_popupmenu_item(5, true, false, {}) + screen:expect({grid = [[ + | | - spam^ | {1:~ }| {1:~ }| {1:~ }| {1:~ }| {1:~ }| - {2:-- INSERT --} | - ]], popupmenu={ - items=expected, - pos=-1, - anchor={1,1,0}, - }} + :sign unplace^ | + ]], popupmenu = { + items = expected_wildpum, + pos = 5, + anchor = { 1, 7, 6 }, + }}) - feed('<f3>') - screen:expect([[ + meths.select_popupmenu_item(-1, true, true, {}) + screen:expect({grid = [[ + | | - bar^ | {1:~ }| {1:~ }| {1:~ }| {1:~ }| {1:~ }| - {2:-- INSERT --} | - ]]) + :sign ^ | + ]]}) - -- also should work for builtin popupmenu - screen:set_option('ext_popupmenu', false) - feed('<C-r>=TestComplete()<CR>') - screen:expect([[ + feed('<Tab>') + screen:expect({grid = [[ | - foo^ | - {6:fo x the foo }{1: }| - {7:bar }{1: }| - {7:spam }{1: }| - {1:~ }| - {1:~ }| - {2:-- INSERT --} | - ]]) - - feed('<f1>') - screen:expect([[ | - spam^ | - {7:fo x the foo }{1: }| - {7:bar }{1: }| - {6:spam }{1: }| {1:~ }| {1:~ }| - {2:-- INSERT --} | - ]]) - - feed('<f2>') - screen:expect([[ - | - spam^ | - {7:fo x the foo }{1: }| - {7:bar }{1: }| - {7:spam }{1: }| {1:~ }| {1:~ }| - {2:-- INSERT --} | - ]]) + {1:~ }| + :sign define^ | + ]], popupmenu = { + items = expected_wildpum, + pos = 0, + anchor = { 1, 7, 6 }, + }}) - feed('<f3>') - screen:expect([[ + meths.select_popupmenu_item(5, true, true, {}) + screen:expect({grid = [[ + | | - bar^ | {1:~ }| {1:~ }| {1:~ }| {1:~ }| {1:~ }| - {2:-- INSERT --} | - ]]) + :sign unplace^ | + ]]}) - command('iunmap <f1>') - command('iunmap <f2>') - command('iunmap <f3>') - exec_lua([[ - vim.keymap.set('i', '<f1>', function() vim.api.nvim_select_popupmenu_item(2, true, false, {}) end) - vim.keymap.set('i', '<f2>', function() vim.api.nvim_select_popupmenu_item(-1, false, false, {}) end) - vim.keymap.set('i', '<f3>', function() vim.api.nvim_select_popupmenu_item(1, false, true, {}) end) - ]]) - feed('<C-r>=TestComplete()<CR>') - screen:expect([[ - | - foo^ | - {6:fo x the foo }{1: }| - {7:bar }{1: }| - {7:spam }{1: }| - {1:~ }| - {1:~ }| - {2:-- INSERT --} | - ]]) + local function test_pum_select_mappings() + screen:set_option('ext_popupmenu', true) + feed('<Esc>A<C-r>=TestComplete()<CR>') + screen:expect{grid=[[ + | + foo^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2:-- INSERT --} | + ]], popupmenu={ + items=expected, + pos=0, + anchor={1,1,0}, + }} - feed('<f1>') - screen:expect([[ - | - spam^ | - {7:fo x the foo }{1: }| - {7:bar }{1: }| - {6:spam }{1: }| - {1:~ }| - {1:~ }| - {2:-- INSERT --} | - ]]) + feed('<f1>') + screen:expect{grid=[[ + | + spam^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2:-- INSERT --} | + ]], popupmenu={ + items=expected, + pos=2, + anchor={1,1,0}, + }} - feed('<f2>') - screen:expect([[ - | - spam^ | - {7:fo x the foo }{1: }| - {7:bar }{1: }| - {7:spam }{1: }| - {1:~ }| - {1:~ }| - {2:-- INSERT --} | - ]]) + feed('<f2>') + screen:expect{grid=[[ + | + spam^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2:-- INSERT --} | + ]], popupmenu={ + items=expected, + pos=-1, + anchor={1,1,0}, + }} - feed('<f3>') - screen:expect([[ - | - bar^ | - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {2:-- INSERT --} | + feed('<f3>') + screen:expect([[ + | + bar^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2:-- INSERT --} | + ]]) + + feed('<Esc>:sign <Tab>') + screen:expect({grid = [[ + | + bar | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + :sign define^ | + ]], popupmenu = { + items = expected_wildpum, + pos = 0, + anchor = { 1, 7, 6 }, + }}) + + feed('<f1>') + screen:expect({grid = [[ + | + bar | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + :sign list^ | + ]], popupmenu = { + items = expected_wildpum, + pos = 2, + anchor = { 1, 7, 6 }, + }}) + + feed('<f2>') + screen:expect({grid = [[ + | + bar | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + :sign ^ | + ]], popupmenu = { + items = expected_wildpum, + pos = -1, + anchor = { 1, 7, 6 }, + }}) + + feed('<f3>') + screen:expect({grid = [[ + | + bar | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + :sign jump^ | + ]]}) + + -- also should work for builtin popupmenu + screen:set_option('ext_popupmenu', false) + feed('<Esc>A<C-r>=TestComplete()<CR>') + screen:expect([[ + | + foo^ | + {6:fo x the foo }{1: }| + {7:bar }{1: }| + {7:spam }{1: }| + {1:~ }| + {1:~ }| + {2:-- INSERT --} | + ]]) + + feed('<f1>') + screen:expect([[ + | + spam^ | + {7:fo x the foo }{1: }| + {7:bar }{1: }| + {6:spam }{1: }| + {1:~ }| + {1:~ }| + {2:-- INSERT --} | + ]]) + + feed('<f2>') + screen:expect([[ + | + spam^ | + {7:fo x the foo }{1: }| + {7:bar }{1: }| + {7:spam }{1: }| + {1:~ }| + {1:~ }| + {2:-- INSERT --} | + ]]) + + feed('<f3>') + screen:expect([[ + | + bar^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2:-- INSERT --} | + ]]) + + feed('<Esc>:sign <Tab>') + screen:expect([[ + | + bar {6: define } | + {1:~ }{7: jump }{1: }| + {1:~ }{7: list }{1: }| + {1:~ }{7: place }{1: }| + {1:~ }{7: undefine }{1: }| + {1:~ }{7: unplace }{1: }| + :sign define^ | + ]]) + + feed('<f1>') + screen:expect([[ + | + bar {7: define } | + {1:~ }{7: jump }{1: }| + {1:~ }{6: list }{1: }| + {1:~ }{7: place }{1: }| + {1:~ }{7: undefine }{1: }| + {1:~ }{7: unplace }{1: }| + :sign list^ | + ]]) + + feed('<f2>') + screen:expect([[ + | + bar {7: define } | + {1:~ }{7: jump }{1: }| + {1:~ }{7: list }{1: }| + {1:~ }{7: place }{1: }| + {1:~ }{7: undefine }{1: }| + {1:~ }{7: unplace }{1: }| + :sign ^ | + ]]) + + feed('<f3>') + screen:expect([[ + | + bar | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + :sign jump^ | + ]]) + end + + command('map! <f1> <cmd>call nvim_select_popupmenu_item(2,v:true,v:false,{})<cr>') + command('map! <f2> <cmd>call nvim_select_popupmenu_item(-1,v:false,v:false,{})<cr>') + command('map! <f3> <cmd>call nvim_select_popupmenu_item(1,v:false,v:true,{})<cr>') + test_pum_select_mappings() + + command('unmap! <f1>') + command('unmap! <f2>') + command('unmap! <f3>') + exec_lua([[ + vim.keymap.set('!', '<f1>', function() vim.api.nvim_select_popupmenu_item(2, true, false, {}) end) + vim.keymap.set('!', '<f2>', function() vim.api.nvim_select_popupmenu_item(-1, false, false, {}) end) + vim.keymap.set('!', '<f3>', function() vim.api.nvim_select_popupmenu_item(1, false, true, {}) end) ]]) + test_pum_select_mappings() feed('<esc>ddiaa bb cc<cr>') feed('<c-x><c-n>') @@ -780,6 +945,82 @@ describe('ui/ext_popupmenu', function() }} end) + + it('does not interfere with mousemodel=popup', function() + exec([[ + set mouse=a mousemodel=popup + + aunmenu PopUp + menu PopUp.foo :let g:menustr = 'foo'<CR> + menu PopUp.bar :let g:menustr = 'bar'<CR> + menu PopUp.baz :let g:menustr = 'baz'<CR> + ]]) + feed('o<C-r>=TestComplete()<CR>') + screen:expect{grid=[[ + | + foo^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2:-- INSERT --} | + ]], popupmenu={ + items=expected, + pos=0, + anchor={1,1,0}, + }} + + feed('<c-p>') + screen:expect{grid=[[ + | + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2:-- INSERT --} | + ]], popupmenu={ + items=expected, + pos=-1, + anchor={1,1,0}, + }} + + feed('<esc>') + screen:expect{grid=[[ + | + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]} + feed('<RightMouse><0,0>') + screen:expect([[ + | + {7:^foo } | + {7:bar }{1: }| + {7:baz }{1: }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + feed('<esc>') + screen:expect([[ + | + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + end) end) @@ -802,6 +1043,8 @@ describe('builtin popupmenu', function() [4] = {bold = true, reverse = true}, [5] = {bold = true, foreground = Screen.colors.SeaGreen}, [6] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red}, + [7] = {background = Screen.colors.Yellow}, -- Search + [8] = {foreground = Screen.colors.Red}, }) end) @@ -949,6 +1192,66 @@ describe('builtin popupmenu', function() ]]) end) + -- oldtest: Test_pum_with_preview_win() + it('preview window opened during completion', function() + exec([[ + funct Omni_test(findstart, base) + if a:findstart + return col(".") - 1 + endif + return [#{word: "one", info: "1info"}, #{word: "two", info: "2info"}, #{word: "three", info: "3info"}] + endfunc + set omnifunc=Omni_test + set completeopt+=longest + ]]) + feed('Gi<C-X><C-O>') + screen:expect([[ + ^ | + {n:one }{1: }| + {n:two }{1: }| + {n:three }{1: }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2:-- }{8:Back at original} | + ]]) + feed('<C-N>') + screen:expect([[ + 1info | + | + {1:~ }| + {3:[Scratch] [Preview] }| + one^ | + {s:one }{1: }| + {n:two }{1: }| + {n:three }{1: }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {4:[No Name] [+] }| + {2:-- }{5:match 1 of 3} | + ]]) + end) + it('with vsplits', function() insert('aaa aab aac\n') feed(':vsplit<cr>') @@ -2222,8 +2525,130 @@ describe('builtin popupmenu', function() {1:~ }{n: xyz }{1: }| :e あいう/123^ | ]]) + feed('<Esc>') - feed('<esc>') + -- Pressing <PageDown> should scroll the menu downward + feed(':sign <Tab><PageDown>') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }{n: define }{1: }| + {1:~ }{n: jump }{1: }| + {1:~ }{n: list }{1: }| + {1:~ }{n: place }{1: }| + {1:~ }{s: undefine }{1: }| + {1:~ }{n: unplace }{1: }| + :sign undefine^ | + ]]) + feed('<PageDown>') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }{n: define }{1: }| + {1:~ }{n: jump }{1: }| + {1:~ }{n: list }{1: }| + {1:~ }{n: place }{1: }| + {1:~ }{n: undefine }{1: }| + {1:~ }{s: unplace }{1: }| + :sign unplace^ | + ]]) + feed('<PageDown>') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }{n: define }{1: }| + {1:~ }{n: jump }{1: }| + {1:~ }{n: list }{1: }| + {1:~ }{n: place }{1: }| + {1:~ }{n: undefine }{1: }| + {1:~ }{n: unplace }{1: }| + :sign ^ | + ]]) + feed('<PageDown>') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }{s: define }{1: }| + {1:~ }{n: jump }{1: }| + {1:~ }{n: list }{1: }| + {1:~ }{n: place }{1: }| + {1:~ }{n: undefine }{1: }| + {1:~ }{n: unplace }{1: }| + :sign define^ | + ]]) + feed('<C-U>sign <Tab><Right><Right><PageDown>') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }{n: define }{1: }| + {1:~ }{n: jump }{1: }| + {1:~ }{n: list }{1: }| + {1:~ }{n: place }{1: }| + {1:~ }{n: undefine }{1: }| + {1:~ }{s: unplace }{1: }| + :sign unplace^ | + ]]) + + -- Pressing <PageUp> should scroll the menu upward + feed('<C-U>sign <Tab><PageUp>') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }{n: define }{1: }| + {1:~ }{n: jump }{1: }| + {1:~ }{n: list }{1: }| + {1:~ }{n: place }{1: }| + {1:~ }{n: undefine }{1: }| + {1:~ }{n: unplace }{1: }| + :sign ^ | + ]]) + feed('<PageUp>') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }{n: define }{1: }| + {1:~ }{n: jump }{1: }| + {1:~ }{n: list }{1: }| + {1:~ }{n: place }{1: }| + {1:~ }{n: undefine }{1: }| + {1:~ }{s: unplace }{1: }| + :sign unplace^ | + ]]) + feed('<PageUp>') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }{n: define }{1: }| + {1:~ }{s: jump }{1: }| + {1:~ }{n: list }{1: }| + {1:~ }{n: place }{1: }| + {1:~ }{n: undefine }{1: }| + {1:~ }{n: unplace }{1: }| + :sign jump^ | + ]]) + feed('<PageUp>') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }{s: define }{1: }| + {1:~ }{n: jump }{1: }| + {1:~ }{n: list }{1: }| + {1:~ }{n: place }{1: }| + {1:~ }{n: undefine }{1: }| + {1:~ }{n: unplace }{1: }| + :sign define^ | + ]]) + + feed('<Esc>') -- check positioning with multibyte char in pattern command("e långfile1") @@ -2320,7 +2745,7 @@ describe('builtin popupmenu', function() ]]) end) - it('wildoptions=pum with scrolled messages ', function() + it('wildoptions=pum with scrolled messages', function() screen:try_resize(40,10) command('set wildmenu') command('set wildoptions=pum') @@ -2417,6 +2842,49 @@ describe('builtin popupmenu', function() ]]} end) + it('wildoptions=pum with a wrapped line in buffer vim-patch:8.2.4655', function() + screen:try_resize(32, 10) + meths.buf_set_lines(0, 0, -1, true, { ('a'):rep(100) }) + command('set wildoptions+=pum') + feed('$') + feed(':sign <Tab>') + screen:expect([[ + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + aaaa {s: define } | + {1:~ }{n: jump }{1: }| + {1:~ }{n: list }{1: }| + {1:~ }{n: place }{1: }| + {1:~ }{n: undefine }{1: }| + {1:~ }{n: unplace }{1: }| + :sign define^ | + ]]) + end) + + -- oldtest: Test_wildmenu_pum_clear_entries() + it('wildoptions=pum when using Ctrl-E as wildchar vim-patch:9.0.1030', function() + screen:try_resize(30, 10) + exec([[ + set wildoptions=pum + set wildchar=<C-E> + ]]) + feed(':sign <C-E><C-E>') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }{s: define }{1: }| + {1:~ }{n: jump }{1: }| + {1:~ }{n: list }{1: }| + {1:~ }{n: place }{1: }| + {1:~ }{n: undefine }{1: }| + {1:~ }{n: unplace }{1: }| + :sign define^ | + ]]) + assert_alive() + end) + it("'pumblend' RGB-color", function() screen:try_resize(60,14) screen:set_default_attr_ids({ @@ -2872,6 +3340,156 @@ describe('builtin popupmenu', function() eq(false, screen.options.mousemoveevent) eq('bar', meths.get_var('menustr')) end) + + -- oldtest: Test_popup_command_dump() + it(':popup command', function() + exec([[ + func ChangeMenu() + aunmenu PopUp.&Paste + nnoremenu 1.40 PopUp.&Paste :echomsg "pasted"<CR> + echomsg 'changed' + return "\<Ignore>" + endfunc + + let lines =<< trim END + one two three four five + and one two Xthree four five + one more two three four five + END + call setline(1, lines) + + aunmenu * + source $VIMRUNTIME/menu.vim + ]]) + feed('/X<CR>:popup PopUp<CR>') + screen:expect([[ + one two three four five | + and one two {7:^X}three four five | + one more tw{n: Undo } | + {1:~ }{n: }{1: }| + {1:~ }{n: Paste }{1: }| + {1:~ }{n: }{1: }| + {1:~ }{n: Select Word }{1: }| + {1:~ }{n: Select Sentence }{1: }| + {1:~ }{n: Select Paragraph }{1: }| + {1:~ }{n: Select Line }{1: }| + {1:~ }{n: Select Block }{1: }| + {1:~ }{n: Select All }{1: }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + :popup PopUp | + ]]) + + -- go to the Paste entry in the menu + feed('jj') + screen:expect([[ + one two three four five | + and one two {7:^X}three four five | + one more tw{n: Undo } | + {1:~ }{n: }{1: }| + {1:~ }{s: Paste }{1: }| + {1:~ }{n: }{1: }| + {1:~ }{n: Select Word }{1: }| + {1:~ }{n: Select Sentence }{1: }| + {1:~ }{n: Select Paragraph }{1: }| + {1:~ }{n: Select Line }{1: }| + {1:~ }{n: Select Block }{1: }| + {1:~ }{n: Select All }{1: }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + :popup PopUp | + ]]) + + -- Select a word + feed('j') + screen:expect([[ + one two three four five | + and one two {7:^X}three four five | + one more tw{n: Undo } | + {1:~ }{n: }{1: }| + {1:~ }{n: Paste }{1: }| + {1:~ }{n: }{1: }| + {1:~ }{s: Select Word }{1: }| + {1:~ }{n: Select Sentence }{1: }| + {1:~ }{n: Select Paragraph }{1: }| + {1:~ }{n: Select Line }{1: }| + {1:~ }{n: Select Block }{1: }| + {1:~ }{n: Select All }{1: }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + :popup PopUp | + ]]) + + feed('<Esc>') + + -- Set an <expr> mapping to change a menu entry while it's displayed. + -- The text should not change but the command does. + -- Also verify that "changed" shows up, which means the mapping triggered. + command('nnoremap <expr> <F2> ChangeMenu()') + feed('/X<CR>:popup PopUp<CR><F2>') + screen:expect([[ + one two three four five | + and one two {7:^X}three four five | + one more tw{n: Undo } | + {1:~ }{n: }{1: }| + {1:~ }{n: Paste }{1: }| + {1:~ }{n: }{1: }| + {1:~ }{n: Select Word }{1: }| + {1:~ }{n: Select Sentence }{1: }| + {1:~ }{n: Select Paragraph }{1: }| + {1:~ }{n: Select Line }{1: }| + {1:~ }{n: Select Block }{1: }| + {1:~ }{n: Select All }{1: }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + changed | + ]]) + + -- Select the Paste entry, executes the changed menu item. + feed('jj<CR>') + screen:expect([[ + one two three four five | + and one two {7:^X}three four five | + one more two three four five | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + pasted | + ]]) + end) end) describe('builtin popupmenu with ui/ext_multigrid', function() diff --git a/test/functional/ui/screen.lua b/test/functional/ui/screen.lua index c44e147c4d..3b9cce0e6f 100644 --- a/test/functional/ui/screen.lua +++ b/test/functional/ui/screen.lua @@ -101,13 +101,10 @@ end local default_screen_timeout = default_timeout_factor * 3500 -do - local spawn, nvim_prog = helpers.spawn, helpers.nvim_prog - local session = spawn({nvim_prog, '-u', 'NONE', '-i', 'NONE', '-N', '--embed'}) +function Screen._init_colors(session) local status, rv = session:request('nvim_get_color_map') if not status then - print('failed to get color map') - os.exit(1) + error('failed to get color map') end local colors = rv local colornames = {} @@ -116,12 +113,15 @@ do -- this is just a helper to get any canonical name of a color colornames[rgb] = name end - session:close() Screen.colors = colors Screen.colornames = colornames end function Screen.new(width, height) + if not Screen.colors then + Screen._init_colors(get_session()) + end + if not width then width = 53 end @@ -1550,7 +1550,8 @@ function Screen:_get_attr_id(attr_state, attrs, hl_id) attr_state.modified = true return id end - return "UNEXPECTED "..self:_pprint_attrs(self._attr_table[hl_id][1]) + local kind = self._options.rgb and 1 or 2 + return "UNEXPECTED "..self:_pprint_attrs(self._attr_table[hl_id][kind]) else if self:_equal_attrs(attrs, {}) then -- ignore this attrs diff --git a/test/functional/ui/screen_basic_spec.lua b/test/functional/ui/screen_basic_spec.lua index 58c8238c35..3bd2289a73 100644 --- a/test/functional/ui/screen_basic_spec.lua +++ b/test/functional/ui/screen_basic_spec.lua @@ -5,8 +5,8 @@ local feed, command = helpers.feed, helpers.command local insert = helpers.insert local eq = helpers.eq local eval = helpers.eval -local iswin = helpers.iswin local funcs, meths, exec_lua = helpers.funcs, helpers.meths, helpers.exec_lua +local is_os = helpers.is_os describe('screen', function() local screen @@ -128,18 +128,18 @@ local function screen_tests(linegrid) end) it('has correct default title with named file', function() - local expected = (iswin() and 'myfile (C:\\mydir) - NVIM' or 'myfile (/mydir) - NVIM') + local expected = (is_os('win') and 'myfile (C:\\mydir) - NVIM' or 'myfile (/mydir) - NVIM') command('set title') - command(iswin() and 'file C:\\mydir\\myfile' or 'file /mydir/myfile') + command(is_os('win') and 'file C:\\mydir\\myfile' or 'file /mydir/myfile') screen:expect(function() eq(expected, screen.title) end) end) describe('is not changed by', function() - local file1 = iswin() and 'C:\\mydir\\myfile1' or '/mydir/myfile1' - local file2 = iswin() and 'C:\\mydir\\myfile2' or '/mydir/myfile2' - local expected = (iswin() and 'myfile1 (C:\\mydir) - NVIM' or 'myfile1 (/mydir) - NVIM') + local file1 = is_os('win') and 'C:\\mydir\\myfile1' or '/mydir/myfile1' + local file2 = is_os('win') and 'C:\\mydir\\myfile2' or '/mydir/myfile2' + local expected = (is_os('win') and 'myfile1 (C:\\mydir) - NVIM' or 'myfile1 (/mydir) - NVIM') local buf2 before_each(function() @@ -894,6 +894,31 @@ local function screen_tests(linegrid) :ls^ | ]]) end) + + it('VimResized autocommand does not cause invalid UI events #20692 #20759', function() + feed('<Esc>') + command([[autocmd VimResized * redrawtabline]]) + command([[autocmd VimResized * lua vim.api.nvim_echo({ { 'Hello' } }, false, {})]]) + command([[autocmd VimResized * let g:echospace = v:echospace]]) + meths.set_option('showtabline', 2) + screen:expect([[ + {2: + [No Name] }{3: }| + resiz^e | + {0:~ }| + {0:~ }| + | + ]]) + screen:try_resize(30, 6) + screen:expect([[ + {2: + [No Name] }{3: }| + resiz^e | + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + eq(29, meths.get_var('echospace')) + end) end) describe('press enter', function() @@ -1027,3 +1052,36 @@ describe('Screen default colors', function() end} end) end) + +it('CTRL-F or CTRL-B scrolls a page after UI attach/resize #20605', function() + clear() + local screen = Screen.new(100, 100) + screen:attach() + eq(100, meths.get_option('lines')) + eq(99, meths.get_option('window')) + eq(99, meths.win_get_height(0)) + feed('1000o<Esc>') + eq(903, funcs.line('w0')) + feed('<C-B>') + eq(806, funcs.line('w0')) + feed('<C-B>') + eq(709, funcs.line('w0')) + feed('<C-F>') + eq(806, funcs.line('w0')) + feed('<C-F>') + eq(903, funcs.line('w0')) + feed('G') + screen:try_resize(50, 50) + eq(50, meths.get_option('lines')) + eq(49, meths.get_option('window')) + eq(49, meths.win_get_height(0)) + eq(953, funcs.line('w0')) + feed('<C-B>') + eq(906, funcs.line('w0')) + feed('<C-B>') + eq(859, funcs.line('w0')) + feed('<C-F>') + eq(906, funcs.line('w0')) + feed('<C-F>') + eq(953, funcs.line('w0')) +end) diff --git a/test/functional/ui/sign_spec.lua b/test/functional/ui/sign_spec.lua index ff3e143126..7dcd4cff25 100644 --- a/test/functional/ui/sign_spec.lua +++ b/test/functional/ui/sign_spec.lua @@ -156,9 +156,12 @@ describe('Signs', function() {0:~ }| | ]]) + -- Check that 'statuscolumn' correctly applies numhl + command('set statuscolumn=%s%=%l\\ ') + screen:expect_unchanged() end) - it('higlights the cursorline sign with culhl', function() + it('highlights the cursorline sign with culhl', function() feed('ia<cr>b<cr>c<esc>') command('sign define piet text=>> texthl=Search culhl=ErrorMsg') command('sign place 1 line=1 name=piet buffer=1') @@ -233,11 +236,13 @@ describe('Signs', function() | ]]) command('set cursorlineopt=number') + command('hi! link SignColumn IncSearch') + feed('Go<esc>2G') screen:expect([[ {1:>>}a | {8:>>}^b | {1:>>}c | - {0:~ }| + {5: } | {0:~ }| {0:~ }| {0:~ }| @@ -249,6 +254,9 @@ describe('Signs', function() {0:~ }| | ]]) + -- Check that 'statuscolumn' cursorline/signcolumn highlights are the same (#21726) + command('set statuscolumn=%s') + screen:expect_unchanged() end) it('multiple signs #9295', function() diff --git a/test/functional/ui/spell_spec.lua b/test/functional/ui/spell_spec.lua index 1aa73e7b13..361f83d1ce 100644 --- a/test/functional/ui/spell_spec.lua +++ b/test/functional/ui/spell_spec.lua @@ -5,8 +5,8 @@ local Screen = require('test.functional.ui.screen') local clear = helpers.clear local feed = helpers.feed local insert = helpers.insert -local uname = helpers.uname local command = helpers.command +local is_os = helpers.is_os describe("'spell'", function() local screen @@ -27,7 +27,7 @@ describe("'spell'", function() end) it('joins long lines #7937', function() - if uname() == 'openbsd' then pending('FIXME #12104', function() end) return end + if is_os('openbsd') then pending('FIXME #12104', function() end) return end command('set spell') insert([[ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod @@ -51,6 +51,7 @@ describe("'spell'", function() end) + -- oldtest: Test_spell_screendump() it('has correct highlight at start of line', function() insert([[ "This is some text without any spell errors. Everything", @@ -70,7 +71,7 @@ describe("'spell'", function() "with missing caps here.", | ^ | | - ]]) + ]]) end) it('"noplainbuffer" and syntax #20385', function() diff --git a/test/functional/ui/statuscolumn_spec.lua b/test/functional/ui/statuscolumn_spec.lua new file mode 100644 index 0000000000..ae3b95fb0f --- /dev/null +++ b/test/functional/ui/statuscolumn_spec.lua @@ -0,0 +1,462 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') +local clear = helpers.clear +local command = helpers.command +local eq = helpers.eq +local eval = helpers.eval +local exec_lua = helpers.exec_lua +local feed = helpers.feed +local meths = helpers.meths +local pcall_err = helpers.pcall_err + +describe('statuscolumn', function() + local screen + before_each(function() + clear('--cmd', 'set number nuw=1 | call setline(1, repeat(["aaaaa"], 16)) | norm GM') + screen = Screen.new() + screen:attach() + end) + + it("fails with invalid 'statuscolumn'", function() + command([[set stc=%{v:relnum?v:relnum:(v:lnum==5?invalid:v:lnum)}\ ]]) + screen:expect([[ + 4 aaaaa | + 3 aaaaa | + 2 aaaaa | + 1 aaaaa | + 8 ^aaaaa | + 1 aaaaa | + 2 aaaaa | + 3 aaaaa | + 4 aaaaa | + 5 aaaaa | + 6 aaaaa | + 7 aaaaa | + 8 aaaaa | + | + ]]) + command('norm 5G') + eq('Vim(redraw):E121: Undefined variable: invalid', pcall_err(command, 'redraw!')) + eq('', eval('&statuscolumn')) + end) + + it("widens with irregular 'statuscolumn' width", function() + command([[set stc=%{v:relnum?v:relnum:(v:lnum==5?'bbbbb':v:lnum)}]]) + command('norm 5G | redraw!') + screen:expect([[ + 1 aaaaa | + bbbbba^eaaa | + 1 aaaaa | + 2 aaaaa | + 3 aaaaa | + 4 aaaaa | + 5 aaaaa | + 6 aaaaa | + 7 aaaaa | + 8 aaaaa | + 9 aaaaa | + 10 aaaaa | + 11 aaaaa | + | + ]]) + end) + + it("works with 'number' and 'relativenumber'", function() + command([[set stc=%{&nu?v:lnum:''}%=%{&rnu?'\ '.v:relnum:''}│]]) + screen:expect([[ + 4 │aaaaa | + 5 │aaaaa | + 6 │aaaaa | + 7 │aaaaa | + 8 │^aaaaa | + 9 │aaaaa | + 10│aaaaa | + 11│aaaaa | + 12│aaaaa | + 13│aaaaa | + 14│aaaaa | + 15│aaaaa | + 16│aaaaa | + | + ]]) + command([[set stc=%l%=%{&rnu?'\ ':''}%r│]]) + screen:expect_unchanged() + command([[set stc=%{&nu?v:lnum:''}%=%{&rnu?'\ '.v:relnum:''}│]]) + command('set relativenumber') + screen:expect([[ + 4 4│aaaaa | + 5 3│aaaaa | + 6 2│aaaaa | + 7 1│aaaaa | + 8 0│^aaaaa | + 9 1│aaaaa | + 10 2│aaaaa | + 11 3│aaaaa | + 12 4│aaaaa | + 13 5│aaaaa | + 14 6│aaaaa | + 15 7│aaaaa | + 16 8│aaaaa | + | + ]]) + command([[set stc=%l%=%{&rnu?'\ ':''}%r│]]) + screen:expect_unchanged() + command([[set stc=%{&nu?v:lnum:''}%=%{&rnu?'\ '.v:relnum:''}│]]) + command('norm 12GH') + screen:expect([[ + 4 0│^aaaaa | + 5 1│aaaaa | + 6 2│aaaaa | + 7 3│aaaaa | + 8 4│aaaaa | + 9 5│aaaaa | + 10 6│aaaaa | + 11 7│aaaaa | + 12 8│aaaaa | + 13 9│aaaaa | + 14 10│aaaaa | + 15 11│aaaaa | + 16 12│aaaaa | + | + ]]) + command([[set stc=%l%=%{&rnu?'\ ':''}%r│]]) + screen:expect_unchanged() + command([[set stc=%{&nu?v:lnum:''}%=%{&rnu?'\ '.v:relnum:''}│]]) + end) + + it("works with highlighted 'statuscolumn'", function() + command([[set stc=%#NonText#%{&nu?v:lnum:''}]] .. + [[%=%{&rnu&&(v:lnum%2)?'\ '.v:relnum:''}]] .. + [[%#LineNr#%{&rnu&&!(v:lnum%2)?'\ '.v:relnum:''}│]]) + screen:set_default_attr_ids({ + [0] = {bold = true, foreground = Screen.colors.Blue}, + [1] = {foreground = Screen.colors.Brown}, + }) + screen:expect([[ + {0:4 }{1:│}aaaaa | + {0:5 }{1:│}aaaaa | + {0:6 }{1:│}aaaaa | + {0:7 }{1:│}aaaaa | + {0:8 }{1:│}^aaaaa | + {0:9 }{1:│}aaaaa | + {0:10}{1:│}aaaaa | + {0:11}{1:│}aaaaa | + {0:12}{1:│}aaaaa | + {0:13}{1:│}aaaaa | + {0:14}{1:│}aaaaa | + {0:15}{1:│}aaaaa | + {0:16}{1:│}aaaaa | + | + ]]) + command('set relativenumber') + screen:expect([[ + {0:4 }{1: 4│}aaaaa | + {0:5 3}{1:│}aaaaa | + {0:6 }{1: 2│}aaaaa | + {0:7 1}{1:│}aaaaa | + {0:8 }{1: 0│}^aaaaa | + {0:9 1}{1:│}aaaaa | + {0:10}{1: 2│}aaaaa | + {0:11 3}{1:│}aaaaa | + {0:12}{1: 4│}aaaaa | + {0:13 5}{1:│}aaaaa | + {0:14}{1: 6│}aaaaa | + {0:15 7}{1:│}aaaaa | + {0:16}{1: 8│}aaaaa | + | + ]]) + command('set nonumber') + screen:expect([[ + {1:4│}aaaaa | + {0:3}{1:│}aaaaa | + {1:2│}aaaaa | + {0:1}{1:│}aaaaa | + {1:0│}^aaaaa | + {0:1}{1:│}aaaaa | + {1:2│}aaaaa | + {0:3}{1:│}aaaaa | + {1:4│}aaaaa | + {0:5}{1:│}aaaaa | + {1:6│}aaaaa | + {0:7}{1:│}aaaaa | + {1:8│}aaaaa | + | + ]]) + end) + + it('works with wrapped lines, signs and folds', function() + command([[set stc=%C%s%=%{v:virtnum?'':v:lnum}│\ ]]) + command("call setline(1,repeat([repeat('aaaaa',10)],16))") + screen:set_default_attr_ids({ + [0] = {bold = true, foreground = Screen.colors.Blue}, + [1] = {foreground = Screen.colors.Brown}, + [2] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.WebGrey}, + [3] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.LightGrey}, + [4] = {bold = true, foreground = Screen.colors.Brown}, + [5] = {background = Screen.colors.Grey90}, + }) + screen:expect([[ + {1: 4│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {1: │ }a | + {1: 5│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {1: │ }a | + {1: 6│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {1: │ }a | + {1: 7│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {1: │ }a | + {1: 8│ }^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {1: │ }a | + {1: 9│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {1: │ }a | + {1:10│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{0:@@@}| + | + ]]) + command("set stc=%C%s%=%l│\\ ") + screen:expect_unchanged() + command('set signcolumn=auto:2 foldcolumn=auto') + command('sign define piet1 text=>> texthl=LineNr') + command('sign define piet2 text=>! texthl=NonText') + command('sign place 1 line=4 name=piet1 buffer=1') + command('sign place 2 line=5 name=piet2 buffer=1') + command('sign place 3 line=6 name=piet1 buffer=1') + command('sign place 4 line=6 name=piet2 buffer=1') + screen:expect([[ + {1:>>}{2: }{1: 4│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │ }aaaaa | + {0:>!}{2: }{1: 5│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │ }aaaaa | + {1:>>}{0:>!}{1: 6│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │ }aaaaa | + {2: }{1: 7│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │ }aaaaa | + {2: }{1: 8│ }^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │ }aaaaa | + {2: }{1: 9│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │ }aaaaa | + {2: }{1:10│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{0:@@@}| + | + ]]) + command('norm zf$') + -- Check that alignment works properly with signs after %= + command([[set stc=%C%=%{v:virtnum?'':v:lnum}│%s\ ]]) + screen:expect([[ + {2: }{1: 4│>>}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │}{2: }{1: }aaaaaa | + {2: }{1: 5│}{0:>!}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │}{2: }{1: }aaaaaa | + {2: }{1: 6│>>}{0:>!}{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │}{2: }{1: }aaaaaa | + {2: }{1: 7│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │}{2: }{1: }aaaaaa | + {2:+}{1: 8│}{2: }{1: }{3:^+-- 1 line: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| + {2: }{1: 9│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │}{2: }{1: }aaaaaa | + {2: }{1:10│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │}{2: }{1: }aaaaaa | + | + ]]) + command('set cursorline') + screen:expect([[ + {2: }{1: 4│>>}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │}{2: }{1: }aaaaaa | + {2: }{1: 5│}{0:>!}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │}{2: }{1: }aaaaaa | + {2: }{1: 6│>>}{0:>!}{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │}{2: }{1: }aaaaaa | + {2: }{1: 7│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │}{2: }{1: }aaaaaa | + {2:+}{4: 8│}{2: }{4: }{5:^+-- 1 line: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| + {2: }{1: 9│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │}{2: }{1: }aaaaaa | + {2: }{1:10│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │}{2: }{1: }aaaaaa | + | + ]]) + -- v:lnum is the same value on wrapped lines + command([[set stc=%C%=%{v:lnum}│%s\ ]]) + screen:expect([[ + {2: }{1: 4│>>}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: 4│}{2: }{1: }aaaaaa | + {2: }{1: 5│}{0:>!}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: 5│}{2: }{1: }aaaaaa | + {2: }{1: 6│>>}{0:>!}{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: 6│}{2: }{1: }aaaaaa | + {2: }{1: 7│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: 7│}{2: }{1: }aaaaaa | + {2:+}{4: 8│}{2: }{4: }{5:^+-- 1 line: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| + {2: }{1: 9│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: 9│}{2: }{1: }aaaaaa | + {2: }{1:10│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1:10│}{2: }{1: }aaaaaa | + | + ]]) + -- v:relnum is the same value on wrapped lines + command([[set stc=%C%=\ %{v:relnum}│%s\ ]]) + screen:expect([[ + {2: }{1: 4│>>}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: 4│}{2: }{1: }aaaaaa | + {2: }{1: 3│}{0:>!}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: 3│}{2: }{1: }aaaaaa | + {2: }{1: 2│>>}{0:>!}{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: 2│}{2: }{1: }aaaaaa | + {2: }{1: 1│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: 1│}{2: }{1: }aaaaaa | + {2:+}{4: 0│}{2: }{4: }{5:^+-- 1 line: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| + {2: }{1: 1│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: 1│}{2: }{1: }aaaaaa | + {2: }{1: 2│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: 2│}{2: }{1: }aaaaaa | + | + ]]) + command([[set stc=%C%=\ %{v:virtnum?'':v:relnum}│%s\ ]]) + screen:expect([[ + {2: }{1: 4│>>}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │}{2: }{1: }aaaaaa | + {2: }{1: 3│}{0:>!}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │}{2: }{1: }aaaaaa | + {2: }{1: 2│>>}{0:>!}{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │}{2: }{1: }aaaaaa | + {2: }{1: 1│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │}{2: }{1: }aaaaaa | + {2:+}{4: 0│}{2: }{4: }{5:^+-- 1 line: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| + {2: }{1: 1│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │}{2: }{1: }aaaaaa | + {2: }{1: 2│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │}{2: }{1: }aaaaaa | + | + ]]) + -- Up to 9 signs in a line + command('set signcolumn=auto:9 foldcolumn=auto') + command('sign place 5 line=6 name=piet1 buffer=1') + command('sign place 6 line=6 name=piet2 buffer=1') + command('sign place 7 line=6 name=piet1 buffer=1') + command('sign place 8 line=6 name=piet2 buffer=1') + command('sign place 9 line=6 name=piet1 buffer=1') + command('sign place 10 line=6 name=piet2 buffer=1') + command('sign place 11 line=6 name=piet1 buffer=1') + screen:expect([[ + {2: }{1: 4│>>}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │}{2: }{1: }aaaaaaaaaaaaaaaaaaaa | + {2: }{1: 3│}{0:>!}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │}{2: }{1: }aaaaaaaaaaaaaaaaaaaa | + {2: }{1: 2│>>}{0:>!}{1:>>}{0:>!}{1:>>}{0:>!}{1:>>}{0:>!}{1:>> }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │}{2: }{1: }aaaaaaaaaaaaaaaaaaaa | + {2: }{1: 1│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │}{2: }{1: }aaaaaaaaaaaaaaaaaaaa | + {2:+}{4: 0│}{2: }{4: }{5:^+-- 1 line: aaaaaaaaaaaaaaaaa}| + {2: }{1: 1│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │}{2: }{1: }aaaaaaaaaaaaaaaaaaaa | + {2: }{1: 2│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │}{2: }{1: }aaaaaaaaaaaaaaaaaaaa | + | + ]]) + -- Status column is re-evaluated for virt_lines, buffer line, and wrapped line + exec_lua([[ + local ns = vim.api.nvim_create_namespace("ns") + vim.api.nvim_buf_set_extmark(0, ns, 5, 0, { + virt_lines_above = true, virt_lines = {{{"virt_line above", ""}}} }) + vim.api.nvim_buf_set_extmark(0, ns, 4, 0, { virt_lines = {{{"virt_line", ""}}} }) + ]]) + command('set foldcolumn=0 signcolumn=no') + command([[set stc=%{v:virtnum<0?'virtual':(!v:virtnum?'buffer':'wrapped')}%=%{'\ '.v:virtnum.'\ '.v:lnum}]]) + screen:expect([[ + {1:buffer 0 4}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {1:wrapped 1 4}aaaaaaaa | + {1:buffer 0 5}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {1:wrapped 1 5}aaaaaaaa | + {1:virtual-2 5}virt_line | + {1:virtual-2 5}virt_line above | + {1:buffer 0 6}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {1:wrapped 1 6}aaaaaaaa | + {1:buffer 0 7}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {1:wrapped 1 7}aaaaaaaa | + {4:buffer 0 8}{5:^+-- 1 line: aaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| + {1:buffer 0 9}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {1:wrapped 1 9}aaaaaaaa | + | + ]]) + end) + + it("works with 'statuscolumn' clicks", function() + command('set mousemodel=extend') + command([[ + function! MyClickFunc(minwid, clicks, button, mods) + let g:testvar = printf("%d %d %s %d", a:minwid, a:clicks, a:button, getmousepos().line) + if a:mods !=# ' ' + let g:testvar ..= '(' .. a:mods .. ')' + endif + endfunction + set stc=%0@MyClickFunc@%=%l%T + ]]) + meths.input_mouse('left', 'press', '', 0, 0, 0) + eq('0 1 l 4', eval("g:testvar")) + meths.input_mouse('left', 'press', '', 0, 0, 0) + eq('0 2 l 4', eval("g:testvar")) + meths.input_mouse('left', 'press', '', 0, 0, 0) + eq('0 3 l 4', eval("g:testvar")) + meths.input_mouse('left', 'press', '', 0, 0, 0) + eq('0 4 l 4', eval("g:testvar")) + meths.input_mouse('right', 'press', '', 0, 3, 0) + eq('0 1 r 7', eval("g:testvar")) + meths.input_mouse('right', 'press', '', 0, 3, 0) + eq('0 2 r 7', eval("g:testvar")) + meths.input_mouse('right', 'press', '', 0, 3, 0) + eq('0 3 r 7', eval("g:testvar")) + meths.input_mouse('right', 'press', '', 0, 3, 0) + eq('0 4 r 7', eval("g:testvar")) + command('set laststatus=2 winbar=%f') + command('let g:testvar=""') + -- Check that winbar click doesn't register as statuscolumn click + meths.input_mouse('right', 'press', '', 0, 0, 0) + eq('', eval("g:testvar")) + -- Check that statusline click doesn't register as statuscolumn click + meths.input_mouse('right', 'press', '', 0, 12, 0) + eq('', eval("g:testvar")) + end) + + it('click labels do not leak memory', function() + command([[ + set laststatus=2 + setlocal statuscolumn=%0@MyClickFunc@abcd%T + 4vsplit + setlocal statusline=abcd + redrawstatus + setlocal statusline= + only + redraw + ]]) + end) + + it('works with foldcolumn', function() + -- Fits maximum multibyte foldcolumn #21759 + command([[set stc=%C%=%l\ fdc=9 fillchars=foldsep:𒀀]]) + for _ = 0,8 do command('norm zfjzo') end + -- 'statuscolumn' is not drawn for `virt_lines_leftcol` lines + exec_lua([[ + local ns = vim.api.nvim_create_namespace("ns") + vim.api.nvim_buf_set_extmark(0, ns, 6, 0, { + virt_lines_leftcol = true, virt_lines = {{{"virt", ""}}} }) + vim.api.nvim_buf_set_extmark(0, ns, 7, 0, { + virt_lines_leftcol = true, virt_lines = {{{"virt", ""}}} }) + ]]) + feed('lh') -- force update wcol/row + screen:expect([[ + 4 aaaaa | + 5 aaaaa | + 6 aaaaa | + 7 aaaaa | + virt | + --------- 8 ^aaaaa | + virt | + 𒀀𒀀𒀀𒀀𒀀𒀀𒀀𒀀𒀀 9 aaaaa | + 10 aaaaa | + 11 aaaaa | + 12 aaaaa | + 13 aaaaa | + 14 aaaaa | + | + ]]) + command('set stc=') -- also for the default sign column + screen:expect_unchanged() + end) +end) diff --git a/test/functional/ui/statusline_spec.lua b/test/functional/ui/statusline_spec.lua index 18391a575d..1c184ff27d 100644 --- a/test/functional/ui/statusline_spec.lua +++ b/test/functional/ui/statusline_spec.lua @@ -164,6 +164,24 @@ describe('statusline clicks', function() meths.input_mouse('right', 'press', '', 0, 6, 5) eq('0 1 r', eval("g:testvar")) end) + + it('no memory leak with zero-width click labels', function() + command([[ + let &stl = '%@Test@%T%@MyClickFunc@%=%T%@Test@' + ]]) + meths.input_mouse('left', 'press', '', 0, 6, 0) + eq('0 1 l', eval("g:testvar")) + meths.input_mouse('right', 'press', '', 0, 6, 39) + eq('0 1 r', eval("g:testvar")) + end) + + it('no memory leak with truncated click labels', function() + command([[ + let &stl = '%@MyClickFunc@foo%X' .. repeat('a', 40) .. '%<t%@Test@bar%X%@Test@baz' + ]]) + meths.input_mouse('left', 'press', '', 0, 6, 2) + eq('0 1 l', eval("g:testvar")) + end) end) describe('global statusline', function() @@ -545,3 +563,29 @@ it('statusline is redrawn with :resize from <Cmd> mapping #19629', function() | ]]) end) + +it('showcmdloc=statusline does not show if statusline is too narrow', function() + clear() + local screen = Screen.new(40, 8) + screen:set_default_attr_ids({ + [0] = {bold = true, foreground = Screen.colors.Blue}, -- NonText + [1] = {bold = true, reverse = true}, -- StatusLine + [2] = {reverse = true}, -- StatusLineNC + }) + screen:attach() + command('set showcmd') + command('set showcmdloc=statusline') + command('1vsplit') + screen:expect([[ + ^ │ | + {0:~}│{0:~ }| + {0:~}│{0:~ }| + {0:~}│{0:~ }| + {0:~}│{0:~ }| + {0:~}│{0:~ }| + {1:< }{2:[No Name] }| + | + ]]) + feed('1234') + screen:expect_unchanged() +end) diff --git a/test/functional/ui/syntax_conceal_spec.lua b/test/functional/ui/syntax_conceal_spec.lua index e8798ddd93..1391985823 100644 --- a/test/functional/ui/syntax_conceal_spec.lua +++ b/test/functional/ui/syntax_conceal_spec.lua @@ -4,7 +4,7 @@ local clear, feed, command = helpers.clear, helpers.feed, helpers.command local eq = helpers.eq local insert = helpers.insert local poke_eventloop = helpers.poke_eventloop -local expect_exit = helpers.expect_exit +local exec = helpers.exec describe('Screen', function() local screen @@ -1001,25 +1001,32 @@ describe('Screen', function() eq({}, grid_lines) -- no redraw was done end) - -- Copy of Test_cursor_column_in_concealed_line_after_window_scroll in - -- test/functional/ui/syntax_conceal_spec.lua. - describe('concealed line after window scroll', function() - after_each(function() - expect_exit(command, ':qall!') - os.remove('Xcolesearch') - end) - - it('has the correct cursor column', function() + describe('concealed line has the correct cursor column', function() + -- oldtest: Test_cursor_column_in_concealed_line_after_window_scroll() + it('after window scroll', function() insert([[ - 3split - let m = matchadd('Conceal', '=') - setl conceallevel=2 concealcursor=nc - normal gg - "==expr== - ]]) + 3split + let m = matchadd('Conceal', '=') + setl conceallevel=2 concealcursor=nc + normal gg + "==expr==]]) + feed('gg') + command('file Xcolesearch') + command('set nomodified') - command('write Xcolesearch') - feed(":so %<CR>") + command('so') + screen:expect{grid=[[ + ^3split | + let m matchadd('Conceal', '') | + setl conceallevel2 concealcursornc | + {2:Xcolesearch }| + 3split | + let m = matchadd('Conceal', '=') | + setl conceallevel=2 concealcursor=nc | + normal gg | + {3:Xcolesearch }| + | + ]]} -- Jump to something that is beyond the bottom of the window, -- so there's a scroll down. @@ -1033,13 +1040,42 @@ describe('Screen', function() normal gg | "{5:^expr} | {2:Xcolesearch }| + 3split | + let m = matchadd('Conceal', '=') | + setl conceallevel=2 concealcursor=nc | normal gg | - "=={5:expr}== | - | - {0:~ }| {3:Xcolesearch }| /expr | ]]} end) + + -- oldtest: Test_cursor_column_in_concealed_line_after_leftcol_change() + it('after leftcol change', function() + exec([[ + 0put = 'ab' .. repeat('-', &columns) .. 'c' + call matchadd('Conceal', '-') + set nowrap ss=0 cole=3 cocu=n + ]]) + + -- Go to the end of the line (3 columns beyond the end of the screen). + -- Horizontal scroll would center the cursor in the screen line, but conceal + -- makes it go to screen column 1. + feed('$') + + -- Are the concealed parts of the current line really hidden? + -- Is the window's cursor column properly updated for conceal? + screen:expect{grid=[[ + ^c | + | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]} + end) end) end) diff --git a/test/functional/ui/tabline_spec.lua b/test/functional/ui/tabline_spec.lua index 809486d4db..2cdec62d01 100644 --- a/test/functional/ui/tabline_spec.lua +++ b/test/functional/ui/tabline_spec.lua @@ -84,3 +84,45 @@ describe('ui/ext_tabline', function() end} end) end) + +describe("tabline", function() + local screen + + before_each(function() + clear() + screen = Screen.new(42, 5) + screen:attach() + end) + + it('redraws when tabline option is set', function() + command('set tabline=asdf') + command('set showtabline=2') + screen:expect{grid=[[ + {1:asdf }| + ^ | + {2:~ }| + {2:~ }| + | + ]], attr_ids={ + [1] = {reverse = true}; + [2] = {bold = true, foreground = Screen.colors.Blue1}; + }} + command('set tabline=jkl') + screen:expect{grid=[[ + {1:jkl }| + ^ | + {2:~ }| + {2:~ }| + | + ]], attr_ids={ + [1] = {reverse = true}; + [2] = {bold = true, foreground = Screen.colors.Blue}; + }} + end) + + it('click definitions do not leak memory #21765', function() + command('set tabline=%@MyClickFunc@MyClickText%T') + command('set showtabline=2') + command('redrawtabline') + end) +end) diff --git a/test/functional/ui/wildmode_spec.lua b/test/functional/ui/wildmode_spec.lua index f1e818119e..50466c9473 100644 --- a/test/functional/ui/wildmode_spec.lua +++ b/test/functional/ui/wildmode_spec.lua @@ -1,13 +1,13 @@ local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') local clear, feed, command = helpers.clear, helpers.feed, helpers.command -local iswin = helpers.iswin local funcs = helpers.funcs local meths = helpers.meths local eq = helpers.eq local eval = helpers.eval local retry = helpers.retry local testprg = helpers.testprg +local is_os = helpers.is_os describe("'wildmenu'", function() local screen @@ -159,7 +159,7 @@ describe("'wildmenu'", function() -- must wait the full timeout. So make it reasonable. screen.timeout = 1000 - if not iswin() then + if not is_os('win') then command('set shell=sh') -- Need a predictable "$" prompt. command('let $PS1 = "$"') end @@ -169,7 +169,7 @@ describe("'wildmenu'", function() -- Check for a shell prompt to verify that the terminal loaded. retry(nil, nil, function() - if iswin() then + if is_os('win') then eq('Microsoft', eval("matchstr(join(getline(1, '$')), 'Microsoft')")) else eq('$', eval([[matchstr(getline(1), '\$')]])) diff --git a/test/functional/vimscript/api_functions_spec.lua b/test/functional/vimscript/api_functions_spec.lua index 8ca245f61a..c032ac3030 100644 --- a/test/functional/vimscript/api_functions_spec.lua +++ b/test/functional/vimscript/api_functions_spec.lua @@ -5,6 +5,7 @@ local neq, eq, command = helpers.neq, helpers.eq, helpers.command local clear, curbufmeths = helpers.clear, helpers.curbufmeths local exc_exec, expect, eval = helpers.exc_exec, helpers.expect, helpers.eval local insert, pcall_err = helpers.insert, helpers.pcall_err +local matches = helpers.matches local meths = helpers.meths describe('eval-API', function() @@ -49,7 +50,7 @@ describe('eval-API', function() it('cannot change texts if textlocked', function() command("autocmd TextYankPost <buffer> ++once call nvim_buf_set_lines(0, 0, -1, v:false, [])") - eq('Vim(call):E5555: API call: E565: Not allowed to change text or change window', + matches('Vim%(call%):E5555: API call: E565: Not allowed to change text or change window$', pcall_err(command, "normal! yy")) end) diff --git a/test/functional/vimscript/ctx_functions_spec.lua b/test/functional/vimscript/ctx_functions_spec.lua index d92a81c55b..5ee84a6d13 100644 --- a/test/functional/vimscript/ctx_functions_spec.lua +++ b/test/functional/vimscript/ctx_functions_spec.lua @@ -173,9 +173,9 @@ describe('context functions', function() call('SaveSFuncs') call('DeleteSFuncs') - eq('Vim(call):E117: Unknown function: s:greet', + eq('function Greet, line 1: Vim(call):E117: Unknown function: s:greet', pcall_err(command, [[call Greet('World')]])) - eq('Vim(call):E117: Unknown function: s:greet_all', + eq('function GreetAll, line 1: Vim(call):E117: Unknown function: s:greet_all', pcall_err(command, [[call GreetAll('World', 'One', 'Two', 'Three')]])) call('RestoreFuncs') @@ -287,9 +287,11 @@ describe('context functions', function() local with_jumps = { ['jumps'] = eval(([[ - filter(map(getjumplist()[0], 'filter( - { "f": expand("#".v:val.bufnr.":p"), "l": v:val.lnum }, - { k, v -> k != "l" || v != 1 })'), '!empty(v:val.f)') + filter(map(add( + getjumplist()[0], { 'bufnr': bufnr('%'), 'lnum': getcurpos()[1] }), + 'filter( + { "f": expand("#".v:val.bufnr.":p"), "l": v:val.lnum }, + { k, v -> k != "l" || v != 1 })'), '!empty(v:val.f)') ]]):gsub('\n', '')) } diff --git a/test/functional/vimscript/eval_spec.lua b/test/functional/vimscript/eval_spec.lua index 1fbdedb815..a8a901042b 100644 --- a/test/functional/vimscript/eval_spec.lua +++ b/test/functional/vimscript/eval_spec.lua @@ -16,13 +16,16 @@ local lfs = require('lfs') local clear = helpers.clear local eq = helpers.eq local exc_exec = helpers.exc_exec -local exec = helpers.exec +local exec_lua = helpers.exec_lua local exec_capture = helpers.exec_capture local eval = helpers.eval local command = helpers.command local write_file = helpers.write_file local meths = helpers.meths local sleep = helpers.sleep +local matches = helpers.matches +local pcall_err = helpers.pcall_err +local assert_alive = helpers.assert_alive local poke_eventloop = helpers.poke_eventloop local feed = helpers.feed @@ -68,13 +71,13 @@ describe("backtick expansion", function() end) it("with default 'shell'", function() - if helpers.iswin() then + if helpers.is_os('win') then command(":silent args `dir /b *2`") else command(":silent args `echo ***2`") end eq({ "file2", }, eval("argv()")) - if helpers.iswin() then + if helpers.is_os('win') then command(":silent args `dir /s/b *4`") eq({ "subdir\\file4", }, eval("map(argv(), 'fnamemodify(v:val, \":.\")')")) else @@ -148,79 +151,6 @@ describe('List support code', function() end) end) --- oldtest: Test_deep_nest() -it('Error when if/for/while/try/function is nested too deep',function() - clear() - local screen = Screen.new(80, 24) - screen:attach() - meths.set_option('laststatus', 2) - exec([[ - " Deep nesting of if ... endif - func Test1() - let @a = join(repeat(['if v:true'], 51), "\n") - let @a ..= "\n" - let @a ..= join(repeat(['endif'], 51), "\n") - @a - let @a = '' - endfunc - - " Deep nesting of for ... endfor - func Test2() - let @a = join(repeat(['for i in [1]'], 51), "\n") - let @a ..= "\n" - let @a ..= join(repeat(['endfor'], 51), "\n") - @a - let @a = '' - endfunc - - " Deep nesting of while ... endwhile - func Test3() - let @a = join(repeat(['while v:true'], 51), "\n") - let @a ..= "\n" - let @a ..= join(repeat(['endwhile'], 51), "\n") - @a - let @a = '' - endfunc - - " Deep nesting of try ... endtry - func Test4() - let @a = join(repeat(['try'], 51), "\n") - let @a ..= "\necho v:true\n" - let @a ..= join(repeat(['endtry'], 51), "\n") - @a - let @a = '' - endfunc - - " Deep nesting of function ... endfunction - func Test5() - let @a = join(repeat(['function X()'], 51), "\n") - let @a ..= "\necho v:true\n" - let @a ..= join(repeat(['endfunction'], 51), "\n") - @a - let @a = '' - endfunc - ]]) - screen:expect({any = '%[No Name%]'}) - feed(':call Test1()<CR>') - screen:expect({any = 'E579: '}) - feed('<C-C>') - screen:expect({any = '%[No Name%]'}) - feed(':call Test2()<CR>') - screen:expect({any = 'E585: '}) - feed('<C-C>') - screen:expect({any = '%[No Name%]'}) - feed(':call Test3()<CR>') - screen:expect({any = 'E585: '}) - feed('<C-C>') - screen:expect({any = '%[No Name%]'}) - feed(':call Test4()<CR>') - screen:expect({any = 'E601: '}) - feed('<C-C>') - screen:expect({any = '%[No Name%]'}) - feed(':call Test5()<CR>') - screen:expect({any = 'E1058: '}) -end) - describe("uncaught exception", function() before_each(clear) after_each(function() @@ -249,10 +179,10 @@ describe("uncaught exception", function() end) end) -describe('lambda function', function() +describe('listing functions using :function', function() before_each(clear) - it('can be shown using :function followed by <lambda> #20466', function() + it('works for lambda functions with <lambda> #20466', function() command('let A = {-> 1}') local num = exec_capture('echo A'):match("function%('<lambda>(%d+)'%)") eq(([[ @@ -260,4 +190,33 @@ describe('lambda function', function() 1 return 1 endfunction]]):format(num), exec_capture(('function <lambda>%s'):format(num))) end) + + -- FIXME: If the same function is deleted, the crash still happens. #20790 + it('does not crash if another function is deleted while listing', function() + local screen = Screen.new(80, 24) + screen:attach() + matches('Vim%(function%):E454: function list was modified', pcall_err(exec_lua, [=[ + vim.cmd([[ + func Func1() + endfunc + func Func2() + endfunc + func Func3() + endfunc + ]]) + + local ns = vim.api.nvim_create_namespace('test') + + vim.ui_attach(ns, { ext_messages = true }, function(event, _, content) + if event == 'msg_show' and content[1][2] == 'function Func1()' then + vim.cmd('delfunc Func3') + end + end) + + vim.cmd('function') + + vim.ui_detach(ns) + ]=])) + assert_alive() + end) end) diff --git a/test/functional/vimscript/executable_spec.lua b/test/functional/vimscript/executable_spec.lua index b49eb09512..43e4a29e1a 100644 --- a/test/functional/vimscript/executable_spec.lua +++ b/test/functional/vimscript/executable_spec.lua @@ -1,15 +1,16 @@ local helpers = require('test.functional.helpers')(after_each) -local eq, clear, call, iswin, write_file, command = - helpers.eq, helpers.clear, helpers.call, helpers.iswin, helpers.write_file, +local eq, clear, call, write_file, command = + helpers.eq, helpers.clear, helpers.call, helpers.write_file, helpers.command local exc_exec = helpers.exc_exec local eval = helpers.eval +local is_os = helpers.is_os describe('executable()', function() before_each(clear) it('returns 1 for commands in $PATH', function() - local exe = iswin() and 'ping' or 'ls' + local exe = is_os('win') and 'ping' or 'ls' eq(1, call('executable', exe)) command('let $PATH = fnamemodify("./test/functional/fixtures/bin", ":p")') eq(1, call('executable', 'null')) @@ -17,7 +18,7 @@ describe('executable()', function() eq(1, call('executable', 'false')) end) - if iswin() then + if is_os('win') then it('exepath respects shellslash', function() command('let $PATH = fnamemodify("./test/functional/fixtures/bin", ":p")') eq([[test\functional\fixtures\bin\null.CMD]], call('fnamemodify', call('exepath', 'null'), ':.')) @@ -56,8 +57,8 @@ describe('executable()', function() -- Some executable in build/bin/, *not* in $PATH nor CWD. local sibling_exe = 'printargs-test' -- Windows: siblings are in Nvim's "pseudo-$PATH". - local expected = iswin() and 1 or 0 - if iswin() then + local expected = is_os('win') and 1 or 0 + if is_os('win') then eq('arg1=lemon;arg2=sky;arg3=tree;', call('system', sibling_exe..' lemon sky tree')) end @@ -69,7 +70,7 @@ describe('executable()', function() clear() write_file('Xtest_not_executable', 'non-executable file') write_file('Xtest_executable', 'executable file (exec-bit set)') - if not iswin() then -- N/A for Windows. + if not is_os('win') then -- N/A for Windows. call('system', {'chmod', '-x', 'Xtest_not_executable'}) call('system', {'chmod', '+x', 'Xtest_executable'}) end @@ -90,14 +91,17 @@ describe('executable()', function() end) it('set, qualified as a path', function() - local expected = iswin() and 0 or 1 + local expected = is_os('win') and 0 or 1 eq(expected, call('executable', './Xtest_executable')) end) end) end) describe('executable() (Windows)', function() - if not iswin() then return end -- N/A for Unix. + if not is_os('win') then + pending('N/A for non-windows') + return + end local exts = {'bat', 'exe', 'com', 'cmd'} setup(function() diff --git a/test/functional/vimscript/execute_spec.lua b/test/functional/vimscript/execute_spec.lua index a733b098f5..5fe3d787cb 100644 --- a/test/functional/vimscript/execute_spec.lua +++ b/test/functional/vimscript/execute_spec.lua @@ -8,7 +8,7 @@ local funcs = helpers.funcs local Screen = require('test.functional.ui.screen') local command = helpers.command local feed = helpers.feed -local iswin = helpers.iswin +local is_os = helpers.is_os describe('execute()', function() before_each(clear) @@ -265,7 +265,7 @@ describe('execute()', function() -- This deviates from vim behavior, but is consistent -- with how nvim currently displays the output. it('captures shell-command output', function() - local win_lf = iswin() and '\13' or '' + local win_lf = is_os('win') and '\13' or '' eq('\n:!echo foo\r\n\nfoo'..win_lf..'\n', funcs.execute('!echo foo')) end) diff --git a/test/functional/vimscript/exepath_spec.lua b/test/functional/vimscript/exepath_spec.lua index 439dd96fcd..056f67e0ad 100644 --- a/test/functional/vimscript/exepath_spec.lua +++ b/test/functional/vimscript/exepath_spec.lua @@ -1,19 +1,20 @@ local helpers = require('test.functional.helpers')(after_each) -local eq, clear, call, iswin = - helpers.eq, helpers.clear, helpers.call, helpers.iswin +local eq, clear, call = + helpers.eq, helpers.clear, helpers.call local command = helpers.command local exc_exec = helpers.exc_exec local matches = helpers.matches +local is_os = helpers.is_os describe('exepath()', function() before_each(clear) it('returns 1 for commands in $PATH', function() - local exe = iswin() and 'ping' or 'ls' - local ext_pat = iswin() and '%.EXE$' or '$' + local exe = is_os('win') and 'ping' or 'ls' + local ext_pat = is_os('win') and '%.EXE$' or '$' matches(exe .. ext_pat, call('exepath', exe)) command('let $PATH = fnamemodify("./test/functional/fixtures/bin", ":p")') - ext_pat = iswin() and '%.CMD$' or '$' + ext_pat = is_os('win') and '%.CMD$' or '$' matches('null' .. ext_pat, call('exepath', 'null')) matches('true' .. ext_pat, call('exepath', 'true')) matches('false' .. ext_pat, call('exepath', 'false')) @@ -23,14 +24,14 @@ describe('exepath()', function() for _, input in ipairs({'v:null', 'v:true', 'v:false', '{}', '[]'}) do eq('Vim(call):E1174: String required for argument 1', exc_exec('call exepath('..input..')')) end - eq('Vim(call):E1142: Non-empty string required for argument 1', exc_exec('call exepath("")')) + eq('Vim(call):E1175: Non-empty string required for argument 1', exc_exec('call exepath("")')) command('let $PATH = fnamemodify("./test/functional/fixtures/bin", ":p")') for _, input in ipairs({'v:null', 'v:true', 'v:false'}) do eq('Vim(call):E1174: String required for argument 1', exc_exec('call exepath('..input..')')) end end) - if iswin() then + if is_os('win') then it('append extension if omitted', function() local filename = 'cmd' local pathext = '.exe' diff --git a/test/functional/vimscript/fnamemodify_spec.lua b/test/functional/vimscript/fnamemodify_spec.lua index d54a6db417..c3ecdd853c 100644 --- a/test/functional/vimscript/fnamemodify_spec.lua +++ b/test/functional/vimscript/fnamemodify_spec.lua @@ -1,12 +1,12 @@ local helpers = require('test.functional.helpers')(after_each) local clear = helpers.clear local eq = helpers.eq -local iswin = helpers.iswin local fnamemodify = helpers.funcs.fnamemodify local getcwd = helpers.funcs.getcwd local command = helpers.command local write_file = helpers.write_file local alter_slashes = helpers.alter_slashes +local is_os = helpers.is_os local function eq_slashconvert(expected, got) eq(alter_slashes(expected), alter_slashes(got)) @@ -27,7 +27,7 @@ describe('fnamemodify()', function() local root = helpers.pathroot() eq(root, fnamemodify([[/]], ':p:h')) eq(root, fnamemodify([[/]], ':p')) - if iswin() then + if is_os('win') then eq(root, fnamemodify([[\]], ':p:h')) eq(root, fnamemodify([[\]], ':p')) command('set shellslash') @@ -114,7 +114,7 @@ describe('fnamemodify()', function() it('handles shell escape', function() local expected - if iswin() then + if is_os('win') then -- we expand with double-quotes on Windows expected = [["hello there! quote ' newline]] .. '\n' .. [["]] else diff --git a/test/functional/vimscript/functions_spec.lua b/test/functional/vimscript/functions_spec.lua index 20c1400030..09b3334989 100644 --- a/test/functional/vimscript/functions_spec.lua +++ b/test/functional/vimscript/functions_spec.lua @@ -9,12 +9,12 @@ local helpers = require('test.functional.helpers')(after_each) local clear = helpers.clear local eval = helpers.eval -local iswin = helpers.iswin local matches = helpers.matches +local is_os = helpers.is_os before_each(clear) it('windowsversion()', function() clear() - matches(iswin() and '^%d+%.%d+$' or '^$', eval('windowsversion()')) + matches(is_os('win') and '^%d+%.%d+$' or '^$', eval('windowsversion()')) end) diff --git a/test/functional/vimscript/has_spec.lua b/test/functional/vimscript/has_spec.lua index 4d9b226434..2e26d603b3 100644 --- a/test/functional/vimscript/has_spec.lua +++ b/test/functional/vimscript/has_spec.lua @@ -2,7 +2,7 @@ local helpers = require('test.functional.helpers')(after_each) local eq = helpers.eq local clear = helpers.clear local funcs = helpers.funcs -local iswin = helpers.iswin +local is_os = helpers.is_os describe('has()', function() before_each(clear) @@ -51,7 +51,7 @@ describe('has()', function() end) it('"unnamedplus"', function() - if (not iswin()) and funcs.has("clipboard") == 1 then + if (not is_os('win')) and funcs.has("clipboard") == 1 then eq(1, funcs.has("unnamedplus")) else eq(0, funcs.has("unnamedplus")) diff --git a/test/functional/vimscript/hostname_spec.lua b/test/functional/vimscript/hostname_spec.lua index 6112cf64e3..7d4baa7213 100644 --- a/test/functional/vimscript/hostname_spec.lua +++ b/test/functional/vimscript/hostname_spec.lua @@ -3,7 +3,7 @@ local eq = helpers.eq local ok = helpers.ok local call = helpers.call local clear = helpers.clear -local iswin = helpers.iswin +local is_os = helpers.is_os describe('hostname()', function() before_each(clear) @@ -13,8 +13,8 @@ describe('hostname()', function() ok(string.len(actual) > 0) if call('executable', 'hostname') == 1 then local expected = string.gsub(call('system', 'hostname'), '[\n\r]', '') - eq((iswin() and expected:upper() or expected), - (iswin() and actual:upper() or actual)) + eq((is_os('win') and expected:upper() or expected), + (is_os('win') and actual:upper() or actual)) end end) end) diff --git a/test/functional/vimscript/map_functions_spec.lua b/test/functional/vimscript/map_functions_spec.lua index 8645b1e506..ba1b4d7a76 100644 --- a/test/functional/vimscript/map_functions_spec.lua +++ b/test/functional/vimscript/map_functions_spec.lua @@ -246,9 +246,9 @@ describe('mapset()', function() end) it('does not leak memory if lhs is missing', function() - eq('Error executing lua: Vim:E460: entries missing in mapset() dict argument', + eq('Vim:E460: entries missing in mapset() dict argument', pcall_err(exec_lua, [[vim.fn.mapset('n', false, {rhs = 'foo'})]])) - eq('Error executing lua: Vim:E460: entries missing in mapset() dict argument', + eq('Vim:E460: entries missing in mapset() dict argument', pcall_err(exec_lua, [[vim.fn.mapset('n', false, {callback = function() end})]])) end) end) diff --git a/test/functional/vimscript/msgpack_functions_spec.lua b/test/functional/vimscript/msgpack_functions_spec.lua index cab67d77e4..de5a721efe 100644 --- a/test/functional/vimscript/msgpack_functions_spec.lua +++ b/test/functional/vimscript/msgpack_functions_spec.lua @@ -5,7 +5,7 @@ local eval, eq = helpers.eval, helpers.eq local command = helpers.command local nvim = helpers.nvim local exc_exec = helpers.exc_exec -local iswin = helpers.iswin +local is_os = helpers.is_os describe('msgpack*() functions', function() before_each(clear) @@ -467,7 +467,7 @@ describe('msgpackparse() function', function() eval(cmd) eval(cmd) -- do it again (try to force segfault) local api_info = eval(cmd) -- do it again - if iswin() then + if is_os('win') then helpers.assert_alive() pending('msgpackparse() has a bug on windows') return diff --git a/test/functional/vimscript/null_spec.lua b/test/functional/vimscript/null_spec.lua index 2451da983e..1153baac46 100644 --- a/test/functional/vimscript/null_spec.lua +++ b/test/functional/vimscript/null_spec.lua @@ -69,7 +69,7 @@ describe('NULL', function() null_expr_test('can be splice-indexed', 'L[:]', 0, {}) null_expr_test('is not locked', 'islocked("v:_null_list")', 0, 0) null_test('is accepted by :for', 'for x in L|throw x|endfor', 0) - null_expr_test('does not crash append()', 'append(1, L)', 0, 0, function() + null_expr_test('does not crash append()', 'append(0, L)', 0, 0, function() eq({''}, curbufmeths.get_lines(0, -1, false)) end) null_expr_test('does not crash setline()', 'setline(1, L)', 0, 0, function() diff --git a/test/functional/vimscript/server_spec.lua b/test/functional/vimscript/server_spec.lua index 6e95459630..c89a0c4e93 100644 --- a/test/functional/vimscript/server_spec.lua +++ b/test/functional/vimscript/server_spec.lua @@ -1,11 +1,14 @@ local helpers = require('test.functional.helpers')(after_each) +local assert_log = helpers.assert_log local eq, neq, eval = helpers.eq, helpers.neq, helpers.eval local clear, funcs, meths = helpers.clear, helpers.funcs, helpers.meths -local iswin = helpers.iswin local ok = helpers.ok local matches = helpers.matches local pcall_err = helpers.pcall_err local mkdir = helpers.mkdir +local is_os = helpers.is_os + +local testlog = 'Xtest-server-log' local function clear_serverlist() for _, server in pairs(funcs.serverlist()) do @@ -14,12 +17,16 @@ local function clear_serverlist() end describe('server', function() + after_each(function() + os.remove(testlog) + end) + it('serverstart() stores sockets in $XDG_RUNTIME_DIR', function() local dir = 'Xtest_xdg_run' mkdir(dir) clear({ env={ XDG_RUNTIME_DIR=dir } }) matches(dir, funcs.stdpath('run')) - if not iswin() then + if not is_os('win') then matches(dir, funcs.serverstart()) end end) @@ -65,7 +72,7 @@ describe('server', function() eq('', meths.get_vvar('servername')) -- v:servername and $NVIM take the next available server. - local servername = (iswin() and [[\\.\pipe\Xtest-functional-server-pipe]] + local servername = (is_os('win') and [[\\.\pipe\Xtest-functional-server-pipe]] or './Xtest-functional-server-socket') funcs.serverstart(servername) eq(servername, meths.get_vvar('servername')) @@ -74,13 +81,20 @@ describe('server', function() end) it('serverstop() returns false for invalid input', function() - clear() + clear{env={ + NVIM_LOG_FILE=testlog, + NVIM_LISTEN_ADDRESS='.', + }} eq(0, eval("serverstop('')")) eq(0, eval("serverstop('bogus-socket-name')")) + assert_log('Not listening on bogus%-socket%-name', testlog, 10) end) it('parses endpoints', function() - clear() + clear{env={ + NVIM_LOG_FILE=testlog, + NVIM_LISTEN_ADDRESS='.', + }} clear_serverlist() eq({}, funcs.serverlist()) @@ -104,6 +118,7 @@ describe('server', function() if status then table.insert(expected, v4) pcall(funcs.serverstart, v4) -- exists already; ignore + assert_log('Failed to start server: address already in use: 127%.0%.0%.1', testlog, 10) end local v6 = '::1:12345' @@ -111,6 +126,7 @@ describe('server', function() if status then table.insert(expected, v6) pcall(funcs.serverstart, v6) -- exists already; ignore + assert_log('Failed to start server: address already in use: ::1', testlog, 10) end eq(expected, funcs.serverlist()) clear_serverlist() @@ -130,7 +146,7 @@ describe('server', function() local n = eval('len(serverlist())') -- Add some servers. - local servs = (iswin() + local servs = (is_os('win') and { [[\\.\pipe\Xtest-pipe0934]], [[\\.\pipe\Xtest-pipe4324]] } or { [[./Xtest-pipe0934]], [[./Xtest-pipe4324]] }) for _, s in ipairs(servs) do @@ -164,7 +180,7 @@ describe('startup --listen', function() end) it('sets v:servername, overrides $NVIM_LISTEN_ADDRESS', function() - local addr = (iswin() and [[\\.\pipe\Xtest-listen-pipe]] + local addr = (is_os('win') and [[\\.\pipe\Xtest-listen-pipe]] or './Xtest-listen-pipe') clear({ env={ NVIM_LISTEN_ADDRESS='./Xtest-env-pipe' }, args={ '--listen', addr } }) diff --git a/test/functional/vimscript/system_spec.lua b/test/functional/vimscript/system_spec.lua index ed822add72..7ada1c4bea 100644 --- a/test/functional/vimscript/system_spec.lua +++ b/test/functional/vimscript/system_spec.lua @@ -1,3 +1,5 @@ +-- Tests for system() and :! shell. + local helpers = require('test.functional.helpers')(after_each) local assert_alive = helpers.assert_alive @@ -9,9 +11,9 @@ local command = helpers.command local insert = helpers.insert local expect = helpers.expect local exc_exec = helpers.exc_exec -local iswin = helpers.iswin local os_kill = helpers.os_kill local pcall_err = helpers.pcall_err +local is_os = helpers.is_os local Screen = require('test.functional.ui.screen') @@ -85,7 +87,7 @@ describe('system()', function() end) it('does NOT run in shell', function() - if iswin() then + if is_os('win') then eq("%PATH%\n", eval("system(['powershell', '-NoProfile', '-NoLogo', '-ExecutionPolicy', 'RemoteSigned', '-Command', 'Write-Output', '%PATH%'])")) else eq("* $PATH %PATH%\n", eval("system(['echo', '*', '$PATH', '%PATH%'])")) @@ -94,7 +96,7 @@ describe('system()', function() end) it('sets v:shell_error', function() - if iswin() then + if is_os('win') then eval([[system("cmd.exe /c exit")]]) eq(0, eval('v:shell_error')) eval([[system("cmd.exe /c exit 1")]]) @@ -123,7 +125,7 @@ describe('system()', function() screen:attach() end) - if iswin() then + if is_os('win') then local function test_more() eq('root = true', eval([[get(split(system('"more" ".editorconfig"'), "\n"), 0, '')]])) end @@ -184,7 +186,7 @@ describe('system()', function() -- * on Windows, expected to default to Western European enc -- * on Linux, expected to default to UTF8 command([[let &shellcmdflag = '-NoLogo -NoProfile -ExecutionPolicy RemoteSigned -Command ']]) - eq(iswin() and '??\n' or 'ああ\n', eval([[system('Write-Output "ああ"')]])) + eq(is_os('win') and '??\n' or 'ああ\n', eval([[system('Write-Output "ああ"')]])) end) it('`echo` and waits for its return', function() @@ -213,7 +215,7 @@ describe('system()', function() screen:try_resize(72, 14) feed(':4verbose echo system("echo hi")<cr>') - if iswin() then + if is_os('win') then screen:expect{any=[[Executing command: "'fake_shell' 'cmdflag' '"echo hi"'"]]} else screen:expect{any=[[Executing command: "'fake_shell' 'cmdflag' 'echo hi'"]]} @@ -243,7 +245,7 @@ describe('system()', function() end) it('`yes` interrupted with CTRL-C', function() - feed(':call system("' .. (iswin() + feed(':call system("' .. (is_os('win') and 'for /L %I in (1,0,2) do @echo y' or 'yes') .. '")<cr>') screen:expect([[ @@ -260,7 +262,7 @@ describe('system()', function() ~ | ~ | ~ | -]] .. (iswin() +]] .. (is_os('win') and [[ :call system("for /L %I in (1,0,2) do @echo y") |]] or [[ @@ -286,7 +288,7 @@ describe('system()', function() it('`yes` interrupted with mapped CTRL-C', function() command('nnoremap <C-C> i') - feed(':call system("' .. (iswin() + feed(':call system("' .. (is_os('win') and 'for /L %I in (1,0,2) do @echo y' or 'yes') .. '")<cr>') screen:expect([[ @@ -303,7 +305,7 @@ describe('system()', function() ~ | ~ | ~ | -]] .. (iswin() +]] .. (is_os('win') and [[ :call system("for /L %I in (1,0,2) do @echo y") |]] or [[ @@ -330,7 +332,7 @@ describe('system()', function() describe('passing no input', function() it('returns the program output', function() - if iswin() then + if is_os('win') then eq("echoed\n", eval('system("echo echoed")')) else eq("echoed", eval('system("echo -n echoed")')) @@ -438,7 +440,7 @@ describe('systemlist()', function() before_each(clear) it('sets v:shell_error', function() - if iswin() then + if is_os('win') then eval([[systemlist("cmd.exe /c exit")]]) eq(0, eval('v:shell_error')) eval([[systemlist("cmd.exe /c exit 1")]]) @@ -617,12 +619,12 @@ describe('systemlist()', function() return end helpers.set_shell_powershell() - eq({iswin() and 'あ\r' or 'あ'}, eval([[systemlist('Write-Output あ')]])) + eq({is_os('win') and 'あ\r' or 'あ'}, eval([[systemlist('Write-Output あ')]])) -- Sanity test w/ default encoding -- * on Windows, expected to default to Western European enc -- * on Linux, expected to default to UTF8 command([[let &shellcmdflag = '-NoLogo -NoProfile -ExecutionPolicy RemoteSigned -Command ']]) - eq({iswin() and '?\r' or 'あ'}, eval([[systemlist('Write-Output あ')]])) + eq({is_os('win') and '?\r' or 'あ'}, eval([[systemlist('Write-Output あ')]])) end) end) @@ -639,7 +641,7 @@ describe('shell :!', function() 1 4 2]]) - if iswin() then + if is_os('win') then feed(':4verbose %!sort /R<cr>') screen:expect{ any=[[Executing command: .?& { Get%-Content .* | & sort /R } 2>&1 | Out%-File %-Encoding UTF8 .*; exit $LastExitCode"]] @@ -660,4 +662,33 @@ describe('shell :!', function() 1]]) end end) + + it(':{range}! without redirecting to buffer', function() + local screen = Screen.new(500, 10) + screen:attach() + insert([[ + 3 + 1 + 4 + 2]]) + feed(':4verbose %w !sort<cr>') + if is_os('win') then + screen:expect{ + any=[[Executing command: .?sort %< .*]] + } + else + screen:expect{ + any=[[Executing command: .?%(sort%) %< .*]] + + } + end + feed('<CR>') + helpers.set_shell_powershell(true) + feed(':4verbose %w !sort<cr>') + screen:expect{ + any=[[Executing command: .?& { Get%-Content .* | & sort }]] + } + feed('<CR>') + helpers.expect_exit(command, 'qall!') + end) end) diff --git a/test/functional/vimscript/writefile_spec.lua b/test/functional/vimscript/writefile_spec.lua index 5f693249a9..8c8da9dc88 100644 --- a/test/functional/vimscript/writefile_spec.lua +++ b/test/functional/vimscript/writefile_spec.lua @@ -111,6 +111,26 @@ describe('writefile()', function() pcall_err(command, ('call writefile([42], %s)'):format(ddname_tail))) end) + it('writefile(..., "p") creates missing parent directories', function() + os.remove(dname) + eq(nil, read_file(dfname)) + eq(0, funcs.writefile({'abc', 'def', 'ghi'}, dfname, 'p')) + eq('abc\ndef\nghi\n', read_file(dfname)) + os.remove(dfname) + os.remove(dname) + eq(nil, read_file(dfname)) + eq(0, funcs.writefile({'\na\nb\n'}, dfname, 'pb')) + eq('\0a\0b\0', read_file(dfname)) + os.remove(dfname) + os.remove(dname) + eq('Vim(call):E32: No file name', + pcall_err(command, ('call writefile([], "%s", "p")'):format(dfname .. '.d/'))) + eq(('Vim(call):E482: Can\'t open file ./ for writing: illegal operation on a directory'), + pcall_err(command, 'call writefile([], "./", "p")')) + eq(('Vim(call):E482: Can\'t open file . for writing: illegal operation on a directory'), + pcall_err(command, 'call writefile([], ".", "p")')) + end) + it('errors out with invalid arguments', function() write_file(fname, 'TEST') eq('Vim(call):E119: Not enough arguments for function: writefile', diff --git a/test/helpers.lua b/test/helpers.lua index a7eda60f87..82ff23bef8 100644 --- a/test/helpers.lua +++ b/test/helpers.lua @@ -1,6 +1,7 @@ require('test.compat') local shared = require('vim.shared') local assert = require('luassert') +local busted = require('busted') local luv = require('luv') local lfs = require('lfs') local relpath = require('pl.path').relpath @@ -45,6 +46,28 @@ function module.sleep(ms) luv.sleep(ms) end +-- Calls fn() until it succeeds, up to `max` times or until `max_ms` +-- milliseconds have passed. +function module.retry(max, max_ms, fn) + assert(max == nil or max > 0) + assert(max_ms == nil or max_ms > 0) + local tries = 1 + local timeout = (max_ms and max_ms or 10000) + local start_time = luv.now() + while true do + local status, result = pcall(fn) + if status then + return result + end + luv.update_time() -- Update cached value of luv.now() (libuv: uv_now()). + if (max and tries >= max) or (luv.now() - start_time > timeout) then + busted.fail(string.format("retry() attempts: %d\n%s", tries, tostring(result)), 2) + end + tries = tries + 1 + luv.sleep(20) -- Avoid hot loop... + end +end + local check_logs_useless_lines = { ['Warning: noted but unhandled ioctl']=1, ['could cause spurious value errors to appear']=2, @@ -87,6 +110,8 @@ end --- Asserts that `pat` matches (or *not* if inverse=true) any line in the tail of `logfile`. --- +--- Retries for 1 second in case of filesystem delay. +--- ---@param pat (string) Lua pattern to match lines in the log file ---@param logfile (string) Full path to log file (default=$NVIM_LOG_FILE) ---@param nrlines (number) Search up to this many log lines @@ -96,43 +121,34 @@ function module.assert_log(pat, logfile, nrlines, inverse) assert(logfile ~= nil, 'no logfile') nrlines = nrlines or 10 inverse = inverse or false - local lines = module.read_file_list(logfile, -nrlines) or {} - local msg = string.format('Pattern %q %sfound in log (last %d lines): %s:\n%s', - pat, (inverse and '' or 'not '), nrlines, logfile, ' '..table.concat(lines, '\n ')) - for _,line in ipairs(lines) do - if line:match(pat) then - if inverse then error(msg) else return end + + module.retry(nil, 1000, function() + local lines = module.read_file_list(logfile, -nrlines) or {} + local msg = string.format('Pattern %q %sfound in log (last %d lines): %s:\n%s', + pat, (inverse and '' or 'not '), nrlines, logfile, ' '..table.concat(lines, '\n ')) + for _,line in ipairs(lines) do + if line:match(pat) then + if inverse then error(msg) else return end + end end - end - if not inverse then error(msg) end + if not inverse then error(msg) end + end) end ---- Asserts that `pat` does NOT matche any line in the tail of `logfile`. +--- Asserts that `pat` does NOT match any line in the tail of `logfile`. --- --- @see assert_log function module.assert_nolog(pat, logfile, nrlines) return module.assert_log(pat, logfile, nrlines, true) end --- Invokes `fn` and returns the error string (with truncated paths), or raises --- an error if `fn` succeeds. --- --- Replaces line/column numbers with zero: --- shared.lua:0: in function 'gsplit' --- shared.lua:0: in function <shared.lua:0>' --- --- Usage: --- -- Match exact string. --- eq('e', pcall_err(function(a, b) error('e') end, 'arg1', 'arg2')) --- -- Match Lua pattern. --- matches('e[or]+$', pcall_err(function(a, b) error('some error') end, 'arg1', 'arg2')) --- -function module.pcall_err_withfile(fn, ...) +function module.pcall(fn, ...) assert(type(fn) == 'function') local status, rv = pcall(fn, ...) - if status == true then - error('expected failure, but got success') + if status then + return status, rv end + -- From: -- C:/long/path/foo.lua:186: Expected string, got number -- to: @@ -150,13 +166,37 @@ function module.pcall_err_withfile(fn, ...) -- We remove this so that the tests are not lua dependent. errmsg = errmsg:gsub('%s*%(tail call%): %?', '') - return errmsg + return status, errmsg +end + +-- Invokes `fn` and returns the error string (with truncated paths), or raises +-- an error if `fn` succeeds. +-- +-- Replaces line/column numbers with zero: +-- shared.lua:0: in function 'gsplit' +-- shared.lua:0: in function <shared.lua:0>' +-- +-- Usage: +-- -- Match exact string. +-- eq('e', pcall_err(function(a, b) error('e') end, 'arg1', 'arg2')) +-- -- Match Lua pattern. +-- matches('e[or]+$', pcall_err(function(a, b) error('some error') end, 'arg1', 'arg2')) +-- +function module.pcall_err_withfile(fn, ...) + assert(type(fn) == 'function') + local status, rv = module.pcall(fn, ...) + if status == true then + error('expected failure, but got success') + end + return rv end function module.pcall_err_withtrace(fn, ...) local errmsg = module.pcall_err_withfile(fn, ...) - return errmsg:gsub('.../helpers.lua:0: ', '') + return errmsg:gsub('^%.%.%./helpers%.lua:0: ', '') + :gsub('^Error executing lua:- ' ,'') + :gsub('^%[string "<nvim>"%]:0: ' ,'') end function module.pcall_err(...) @@ -257,43 +297,26 @@ function module.check_logs() table.concat(runtime_errors, ', '))) end -function module.iswin() - return package.config:sub(1,1) == '\\' +function module.sysname() + local platform = luv.os_uname() + if platform and platform.sysname then + return platform.sysname:lower() + end end --- Gets (lowercase) OS name from CMake, uname, or "win" if iswin(). -module.uname = (function() - local platform = nil - return (function() - if platform then - return platform - end - - if os.getenv("SYSTEM_NAME") then -- From CMAKE_HOST_SYSTEM_NAME. - platform = string.lower(os.getenv("SYSTEM_NAME")) - return platform - end - - local status, f = pcall(module.popen_r, 'uname', '-s') - if status then - platform = string.lower(f:read("*l")) - f:close() - elseif module.iswin() then - platform = 'windows' - else - error('unknown platform') - end - return platform - end) -end)() - function module.is_os(s) - if not (s == 'win' or s == 'mac' or s == 'unix') then + if not (s == 'win' + or s == 'mac' + or s == 'freebsd' + or s == 'openbsd' + or s == 'bsd') then error('unknown platform: '..tostring(s)) end - return ((s == 'win' and module.iswin()) - or (s == 'mac' and module.uname() == 'darwin') - or (s == 'unix')) + return ((s == 'win' and module.sysname():find('windows')) + or (s == 'mac' and module.sysname() == 'darwin') + or (s == 'freebsd' and module.sysname() == 'freebsd') + or (s == 'openbsd' and module.sysname() == 'openbsd') + or (s == 'bsd' and module.sysname():find('bsd'))) end local function tmpdir_get() @@ -319,11 +342,11 @@ module.tmpname = (function() return fname else local fname = os.tmpname() - if module.uname() == 'windows' and fname:sub(1, 2) == '\\s' then + if module.is_os('win') and fname:sub(1, 2) == '\\s' then -- In Windows tmpname() returns a filename starting with -- special sequence \s, prepend $TEMP path return tmpdir..fname - elseif fname:match('^/tmp') and module.uname() == 'darwin' then + elseif fname:match('^/tmp') and module.is_os('mac') then -- In OS X /tmp links to /private/tmp return '/private'..fname else @@ -366,14 +389,14 @@ function module.check_cores(app, force) exc_re = { os.getenv('NVIM_TEST_CORE_EXC_RE'), local_tmpdir } db_cmd = os.getenv('NVIM_TEST_CORE_DB_CMD') or gdb_db_cmd random_skip = os.getenv('NVIM_TEST_CORE_RANDOM_SKIP') - elseif 'darwin' == module.uname() then + elseif module.is_os('mac') then initial_path = '/cores' re = nil exc_re = { local_tmpdir } db_cmd = lldb_db_cmd else initial_path = '.' - if 'freebsd' == module.uname() then + if module.is_os('freebsd') then re = '/nvim.core$' else re = '/core[^/]*$' @@ -788,19 +811,19 @@ function module.write_file(name, text, no_dedent, append) file:close() end -function module.isCI(name) +function module.is_ci(name) local any = (name == nil) - assert(any or name == 'github') + assert(any or name == 'github' or name == 'cirrus') local gh = ((any or name == 'github') and nil ~= os.getenv('GITHUB_ACTIONS')) - return gh - + local cirrus = ((any or name == 'cirrus') and nil ~= os.getenv('CIRRUS_CI')) + return gh or cirrus end -- Gets the (tail) contents of `logfile`. -- Also moves the file to "${NVIM_LOG_FILE}.displayed" on CI environments. function module.read_nvim_log(logfile, ci_rename) logfile = logfile or os.getenv('NVIM_LOG_FILE') or '.nvimlog' - local is_ci = module.isCI() + local is_ci = module.is_ci() local keep = is_ci and 100 or 10 local lines = module.read_file_list(logfile, -keep) or {} local log = (('-'):rep(78)..'\n' diff --git a/test/includes/CMakeLists.txt b/test/includes/CMakeLists.txt deleted file mode 100644 index b4da4c0611..0000000000 --- a/test/includes/CMakeLists.txt +++ /dev/null @@ -1,32 +0,0 @@ -file(GLOB_RECURSE PRE_HEADERS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} pre/*.h) - -# We need to add the SDK directories on OS X, and perhaps other operating -# systems. -set(gen_cflags) -foreach(gen_include ${CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES}) - list(APPEND gen_cflags ${CMAKE_INCLUDE_FLAG_C}${gen_include}) -endforeach() - -get_directory_property(gen_cdefs COMPILE_DEFINITIONS) -foreach(gen_cdef ${gen_cdefs}) - if(NOT ${gen_cdef} MATCHES "INCLUDE_GENERATED_DECLARATIONS") - list(APPEND gen_cflags "-D${gen_cdef}") - endif() -endforeach() - -foreach(hfile ${PRE_HEADERS}) - string(REGEX REPLACE ^pre/ post/ post_hfile ${hfile}) - get_filename_component(hdir ${CMAKE_CURRENT_BINARY_DIR}/${post_hfile} DIRECTORY) - file(MAKE_DIRECTORY ${hdir}) - add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${post_hfile} - COMMAND ${CMAKE_C_COMPILER} -std=c99 -E -P - ${CMAKE_CURRENT_SOURCE_DIR}/${hfile} - ${gen_cflags} - -I${LIBUV_INCLUDE_DIRS} - -o ${CMAKE_CURRENT_BINARY_DIR}/${post_hfile}) - list(APPEND POST_HEADERS ${post_hfile}) -endforeach() - -add_custom_target(unittest-headers DEPENDS ${POST_HEADERS}) -set_target_properties(unittest-headers PROPERTIES FOLDER test) diff --git a/test/unit/buffer_spec.lua b/test/unit/buffer_spec.lua index 5dccc2f5a2..a54ea8c656 100644 --- a/test/unit/buffer_spec.lua +++ b/test/unit/buffer_spec.lua @@ -28,9 +28,9 @@ describe('buffer functions', function() setup(function() -- create the files - io.open(path1, 'w').close() - io.open(path2, 'w').close() - io.open(path3, 'w').close() + io.open(path1, 'w'):close() + io.open(path2, 'w'):close() + io.open(path3, 'w'):close() end) teardown(function() @@ -233,10 +233,12 @@ describe('buffer functions', function() output_buffer, buffer_byte_size, to_cstr(pat), - false, + NULL, + 0, fillchar, maximum_cell_count, NULL, + NULL, NULL) end diff --git a/test/unit/eval/typval_spec.lua b/test/unit/eval/typval_spec.lua index 6387f89fe4..128625374e 100644 --- a/test/unit/eval/typval_spec.lua +++ b/test/unit/eval/typval_spec.lua @@ -2175,7 +2175,8 @@ describe('typval.c', function() eq({a='TSET'}, dct2tbl(d1)) eq({a='TSET'}, dct2tbl(d2)) end) - itp('disallows overriding builtin or user functions', function() + pending('disallows overriding builtin or user functions: here be the dragons', function() + -- pending: see TODO below local d = dict() d.dv_scope = lib.VAR_DEF_SCOPE local f_lua = { @@ -2196,6 +2197,7 @@ describe('typval.c', function() local d5 = dict({Test=f_tv}) local d6 = dict({Test=p_tv}) eval0([[execute("function Test()\nendfunction")]]) + -- TODO: test breaks at this point tv_dict_extend(d, d2, 'force', 'E704: Funcref variable name must start with a capital: tr') tv_dict_extend(d, d3, 'force', @@ -2253,8 +2255,8 @@ describe('typval.c', function() local d1 = dict() alloc_log:check({a.dict(d1)}) eq(1, d1.dv_refcount) - eq(false, tv_dict_equal(nil, d1)) - eq(false, tv_dict_equal(d1, nil)) + eq(true, tv_dict_equal(nil, d1)) + eq(true, tv_dict_equal(d1, nil)) eq(true, tv_dict_equal(d1, d1)) eq(1, d1.dv_refcount) alloc_log:check({}) @@ -2623,7 +2625,7 @@ describe('typval.c', function() describe('check_lock()', function() local function tv_check_lock(lock, name, name_len, emsg) return check_emsg(function() - return lib.var_check_lock(lock, name, name_len) + return lib.value_check_lock(lock, name, name_len) end, emsg) end itp('works', function() @@ -2721,8 +2723,8 @@ describe('typval.c', function() local d1 = lua2typvalt({}) alloc_log:check({a.dict(d1.vval.v_dict)}) eq(1, d1.vval.v_dict.dv_refcount) - eq(false, tv_equal(nd, d1)) - eq(false, tv_equal(d1, nd)) + eq(true, tv_equal(nd, d1)) + eq(true, tv_equal(d1, nd)) eq(true, tv_equal(d1, d1)) eq(1, d1.vval.v_dict.dv_refcount) alloc_log:check({}) diff --git a/test/unit/helpers.lua b/test/unit/helpers.lua index 29ea0235be..14d8eda357 100644 --- a/test/unit/helpers.lua +++ b/test/unit/helpers.lua @@ -7,9 +7,6 @@ local global_helpers = require('test.helpers') local assert = require('luassert') local say = require('say') -local posix = nil -local syscall = nil - local check_cores = global_helpers.check_cores local dedent = global_helpers.dedent local neq = global_helpers.neq @@ -373,122 +370,91 @@ local function to_cstr(string) return cstr(#string + 1, string) end -local sc - -if posix ~= nil then - sc = { - fork = posix.fork, - pipe = posix.pipe, - read = posix.read, - write = posix.write, - close = posix.close, - wait = posix.wait, - exit = posix._exit, - } -elseif syscall ~= nil then - sc = { - fork = syscall.fork, - pipe = function() - local ret = {syscall.pipe()} - return ret[3], ret[4] - end, - read = function(rd, len) - return rd:read(nil, len) - end, - write = function(wr, s) - return wr:write(s) - end, - close = function(p) - return p:close() - end, - wait = syscall.wait, - exit = syscall.exit, - } -else - cimport_immediate('./test/unit/fixtures/posix.h') - sc = { - fork = function() - return tonumber(ffi.C.fork()) - end, - pipe = function() - local ret = ffi.new('int[2]', {-1, -1}) - ffi.errno(0) - local res = ffi.C.pipe(ret) - if (res ~= 0) then +cimport_immediate('./test/unit/fixtures/posix.h') +local sc = { + fork = function() + return tonumber(ffi.C.fork()) + end, + pipe = function() + local ret = ffi.new('int[2]', {-1, -1}) + ffi.errno(0) + local res = ffi.C.pipe(ret) + if (res ~= 0) then + local err = ffi.errno(0) + assert(res == 0, ("pipe() error: %u: %s"):format( + err, ffi.string(ffi.C.strerror(err)))) + end + assert(ret[0] ~= -1 and ret[1] ~= -1) + return ret[0], ret[1] + end, + read = function(rd, len) + local ret = ffi.new('char[?]', len, {0}) + local total_bytes_read = 0 + ffi.errno(0) + while total_bytes_read < len do + local bytes_read = tonumber(ffi.C.read( + rd, + ffi.cast('void*', ret + total_bytes_read), + len - total_bytes_read)) + if bytes_read == -1 then local err = ffi.errno(0) - assert(res == 0, ("pipe() error: %u: %s"):format( - err, ffi.string(ffi.C.strerror(err)))) - end - assert(ret[0] ~= -1 and ret[1] ~= -1) - return ret[0], ret[1] - end, - read = function(rd, len) - local ret = ffi.new('char[?]', len, {0}) - local total_bytes_read = 0 - ffi.errno(0) - while total_bytes_read < len do - local bytes_read = tonumber(ffi.C.read( - rd, - ffi.cast('void*', ret + total_bytes_read), - len - total_bytes_read)) - if bytes_read == -1 then - local err = ffi.errno(0) - if err ~= ffi.C.kPOSIXErrnoEINTR then - assert(false, ("read() error: %u: %s"):format( - err, ffi.string(ffi.C.strerror(err)))) - end - elseif bytes_read == 0 then - break - else - total_bytes_read = total_bytes_read + bytes_read + if err ~= ffi.C.kPOSIXErrnoEINTR then + assert(false, ("read() error: %u: %s"):format( + err, ffi.string(ffi.C.strerror(err)))) end + elseif bytes_read == 0 then + break + else + total_bytes_read = total_bytes_read + bytes_read end - return ffi.string(ret, total_bytes_read) - end, - write = function(wr, s) - local wbuf = to_cstr(s) - local total_bytes_written = 0 - ffi.errno(0) - while total_bytes_written < #s do - local bytes_written = tonumber(ffi.C.write( - wr, - ffi.cast('void*', wbuf + total_bytes_written), - #s - total_bytes_written)) - if bytes_written == -1 then - local err = ffi.errno(0) - if err ~= ffi.C.kPOSIXErrnoEINTR then - assert(false, ("write() error: %u: %s ('%s')"):format( - err, ffi.string(ffi.C.strerror(err)), s)) - end - elseif bytes_written == 0 then - break - else - total_bytes_written = total_bytes_written + bytes_written + end + return ffi.string(ret, total_bytes_read) + end, + write = function(wr, s) + local wbuf = to_cstr(s) + local total_bytes_written = 0 + ffi.errno(0) + while total_bytes_written < #s do + local bytes_written = tonumber(ffi.C.write( + wr, + ffi.cast('void*', wbuf + total_bytes_written), + #s - total_bytes_written)) + if bytes_written == -1 then + local err = ffi.errno(0) + if err ~= ffi.C.kPOSIXErrnoEINTR then + assert(false, ("write() error: %u: %s ('%s')"):format( + err, ffi.string(ffi.C.strerror(err)), s)) end + elseif bytes_written == 0 then + break + else + total_bytes_written = total_bytes_written + bytes_written end - return total_bytes_written - end, - close = ffi.C.close, - wait = function(pid) - ffi.errno(0) - while true do - local r = ffi.C.waitpid(pid, nil, ffi.C.kPOSIXWaitWUNTRACED) - if r == -1 then - local err = ffi.errno(0) - if err == ffi.C.kPOSIXErrnoECHILD then - break - elseif err ~= ffi.C.kPOSIXErrnoEINTR then - assert(false, ("waitpid() error: %u: %s"):format( - err, ffi.string(ffi.C.strerror(err)))) - end - else - assert(r == pid) + end + return total_bytes_written + end, + close = ffi.C.close, + wait = function(pid) + ffi.errno(0) + local stat_loc = ffi.new('int[1]', {0}) + while true do + local r = ffi.C.waitpid(pid, stat_loc, ffi.C.kPOSIXWaitWUNTRACED) + if r == -1 then + local err = ffi.errno(0) + if err == ffi.C.kPOSIXErrnoECHILD then + break + elseif err ~= ffi.C.kPOSIXErrnoEINTR then + assert(false, ("waitpid() error: %u: %s"):format( + err, ffi.string(ffi.C.strerror(err)))) end + else + assert(r == pid) end - end, - exit = ffi.C._exit, - } -end + end + return stat_loc[0] + end, + exit = ffi.C._exit, +} local function format_list(lst) local ret = '' @@ -730,18 +696,22 @@ local function check_child_err(rd) end end -local function itp_parent(rd, pid, allow_failure) - local err, emsg = pcall(check_child_err, rd) - sc.wait(pid) +local function itp_parent(rd, pid, allow_failure, location) + local ok, emsg = pcall(check_child_err, rd) + local status = sc.wait(pid) sc.close(rd) - if not err then + if not ok then if allow_failure then - io.stderr:write('Errorred out:\n' .. tostring(emsg) .. '\n') + io.stderr:write('Errorred out ('..status..'):\n' .. tostring(emsg) .. '\n') os.execute([[ sh -c "source ci/common/test.sh check_core_dumps --delete \"]] .. Paths.test_luajit_prg .. [[\""]]) else - error(emsg) + error(tostring(emsg)..'\nexit code: '..status) + end + elseif status ~= 0 then + if not allow_failure then + error("child process errored out with status "..status.."!\n\n"..location) end end end @@ -758,6 +728,11 @@ local function gen_itp(it) -- FIXME Fix tests with this true return end + + -- Pre-emptively calculating error location, wasteful, ugh! + -- But the way this code messes around with busted implies the real location is strictly + -- not available in the parent when an actual error occurs. so we have to do this here. + local location = debug.traceback() it(name, function() local rd, wr = sc.pipe() child_pid = sc.fork() @@ -768,7 +743,7 @@ local function gen_itp(it) sc.close(wr) local saved_child_pid = child_pid child_pid = nil - itp_parent(rd, saved_child_pid, allow_failure) + itp_parent(rd, saved_child_pid, allow_failure, location) end end) end diff --git a/test/unit/os/fs_spec.lua b/test/unit/os/fs_spec.lua index 0bb33772cd..c718244ea4 100644 --- a/test/unit/os/fs_spec.lua +++ b/test/unit/os/fs_spec.lua @@ -72,9 +72,9 @@ describe('fs.c', function() before_each(function() lfs.mkdir('unit-test-directory'); - io.open('unit-test-directory/test.file', 'w').close() + io.open('unit-test-directory/test.file', 'w'):close() - io.open('unit-test-directory/test_2.file', 'w').close() + io.open('unit-test-directory/test_2.file', 'w'):close() lfs.link('test.file', 'unit-test-directory/test_link.file', true) lfs.link('non_existing_file.file', 'unit-test-directory/test_broken_link.file', true) @@ -472,7 +472,7 @@ describe('fs.c', function() describe('os_remove', function() before_each(function() - io.open('unit-test-directory/test_remove.file', 'w').close() + io.open('unit-test-directory/test_remove.file', 'w'):close() end) after_each(function() diff --git a/test/unit/path_spec.lua b/test/unit/path_spec.lua index eb23a3cff1..1fc4e2496e 100644 --- a/test/unit/path_spec.lua +++ b/test/unit/path_spec.lua @@ -82,8 +82,8 @@ describe('path.c', function() local f2 = 'f2.o' before_each(function() -- create the three files that will be used in this spec - io.open(f1, 'w').close() - io.open(f2, 'w').close() + io.open(f1, 'w'):close() + io.open(f2, 'w'):close() end) after_each(function() @@ -355,7 +355,7 @@ end) describe('path.c', function() setup(function() lfs.mkdir('unit-test-directory'); - io.open('unit-test-directory/test.file', 'w').close() + io.open('unit-test-directory/test.file', 'w'):close() -- Since the tests are executed, they are called by an executable. We use -- that executable for several asserts. @@ -504,6 +504,16 @@ describe('path.c', function() eq(OK, result) end) + itp('does not remove trailing slash from non-existing relative directory #20847', function() + local expected = lfs.currentdir() .. '/non_existing_dir/' + local filename = 'non_existing_dir/' + local buflen = get_buf_len(expected, filename) + local do_expand = 1 + local buf, result = vim_FullName(filename, buflen, do_expand) + eq(expected, ffi.string(buf)) + eq(OK, result) + end) + itp('expands "./" to the current directory #7117', function() local expected = lfs.currentdir() .. '/unit-test-directory/test.file' local filename = './unit-test-directory/test.file' diff --git a/test/unit/search_spec.lua b/test/unit/search_spec.lua index ce37ebfc3a..be905bf5f0 100644 --- a/test/unit/search_spec.lua +++ b/test/unit/search_spec.lua @@ -37,7 +37,7 @@ end) describe('search_regcomp', function() local search_regcomp = function(pat, pat_save, pat_use, options ) local regmatch = ffi.new("regmmatch_T") - local fail = search.search_regcomp(to_cstr(pat), pat_save, pat_use, options, regmatch) + local fail = search.search_regcomp(to_cstr(pat), nil, pat_save, pat_use, options, regmatch) return fail, regmatch end diff --git a/test/unit/tui_spec.lua b/test/unit/tui_spec.lua index 36ce4a1493..25b70a17c2 100644 --- a/test/unit/tui_spec.lua +++ b/test/unit/tui_spec.lua @@ -9,6 +9,7 @@ local cinput = cimport("./src/nvim/tui/input.h") local rbuffer = cimport("./test/unit/fixtures/rbuffer.h") local globals = cimport("./src/nvim/globals.h") local multiqueue = cimport("./test/unit/fixtures/multiqueue.h") +local ui_client = cimport("./src/nvim/ui_client.h") itp('handle_background_color', function() local handle_background_color = cinput.ut_handle_background_color @@ -33,11 +34,9 @@ itp('handle_background_color', function() term_input.waiting_for_bg_response = 1 eq(kComplete, handle_background_color(term_input)) eq(0, term_input.waiting_for_bg_response) - eq(1, multiqueue.multiqueue_size(events)) - - local event = multiqueue.multiqueue_get(events) - local bg_event = ffi.cast("Event*", event.argv[1]) - eq(bg, ffi.string(bg_event.argv[0])) + eq(0, multiqueue.multiqueue_size(events)) + eq(bg, ({[0]="light", [1] = "dark", [-1] = "none"}) + [tonumber(ui_client.ui_client_bg_response)]) -- Buffer has been consumed. eq(0, rbuf.size) @@ -114,9 +113,7 @@ itp('handle_background_color', function() eq(kComplete, handle_background_color(term_input)) eq(0, term_input.waiting_for_bg_response) - local event = multiqueue.multiqueue_get(events) - local bg_event = ffi.cast("Event*", event.argv[1]) - eq('light', ffi.string(bg_event.argv[0])) + eq(0, tonumber(ui_client.ui_client_bg_response)) eq(0, multiqueue.multiqueue_size(events)) eq(0, rbuf.size) @@ -133,9 +130,7 @@ itp('handle_background_color', function() eq(kComplete, handle_background_color(term_input)) eq(0, term_input.waiting_for_bg_response) - event = multiqueue.multiqueue_get(events) - bg_event = ffi.cast("Event*", event.argv[1]) - eq('light', ffi.string(bg_event.argv[0])) + eq(0, tonumber(ui_client.ui_client_bg_response)) eq(0, multiqueue.multiqueue_size(events)) eq(0, rbuf.size) @@ -161,7 +156,7 @@ itp('handle_background_color', function() eq(kComplete, handle_background_color(term_input)) eq(0, term_input.waiting_for_bg_response) - eq(1, multiqueue.multiqueue_size(events)) + eq(0, multiqueue.multiqueue_size(events)) eq(3, rbuf.size) rbuffer.rbuffer_consumed(rbuf, rbuf.size) end) |