diff options
author | ZyX <kp-pav@yandex.ru> | 2017-12-03 16:49:30 +0300 |
---|---|---|
committer | ZyX <kp-pav@yandex.ru> | 2017-12-03 16:49:30 +0300 |
commit | c49e22d3964d6c7ae1c24e8ad01b5fec4ca40b57 (patch) | |
tree | b7e59c416d1435725c65f8952b6e55c70544d97e /test/functional/ui | |
parent | 62108c3b0be46936c83f6d4c98b44ceb5e6f77fd (diff) | |
parent | 27a577586eace687c47e7398845178208cae524a (diff) | |
download | rneovim-c49e22d3964d6c7ae1c24e8ad01b5fec4ca40b57.tar.gz rneovim-c49e22d3964d6c7ae1c24e8ad01b5fec4ca40b57.tar.bz2 rneovim-c49e22d3964d6c7ae1c24e8ad01b5fec4ca40b57.zip |
Merge branch 'master' into s-dash-stdin
Diffstat (limited to 'test/functional/ui')
-rw-r--r-- | test/functional/ui/bufhl_spec.lua | 50 | ||||
-rw-r--r-- | test/functional/ui/cmdline_highlight_spec.lua | 893 | ||||
-rw-r--r-- | test/functional/ui/cmdline_spec.lua | 525 | ||||
-rw-r--r-- | test/functional/ui/cursor_spec.lua | 246 | ||||
-rw-r--r-- | test/functional/ui/highlight_spec.lua | 696 | ||||
-rw-r--r-- | test/functional/ui/inccommand_spec.lua | 1103 | ||||
-rw-r--r-- | test/functional/ui/input_spec.lua | 6 | ||||
-rw-r--r-- | test/functional/ui/mode_spec.lua | 227 | ||||
-rw-r--r-- | test/functional/ui/mouse_spec.lua | 78 | ||||
-rw-r--r-- | test/functional/ui/quickfix_spec.lua | 52 | ||||
-rw-r--r-- | test/functional/ui/screen.lua | 69 | ||||
-rw-r--r-- | test/functional/ui/screen_basic_spec.lua | 269 | ||||
-rw-r--r-- | test/functional/ui/searchhl_spec.lua | 34 | ||||
-rw-r--r-- | test/functional/ui/sign_spec.lua | 14 | ||||
-rw-r--r-- | test/functional/ui/syntax_conceal_spec.lua | 70 | ||||
-rw-r--r-- | test/functional/ui/tabline_spec.lua | 54 | ||||
-rw-r--r-- | test/functional/ui/wildmode_spec.lua | 256 |
17 files changed, 4008 insertions, 634 deletions
diff --git a/test/functional/ui/bufhl_spec.lua b/test/functional/ui/bufhl_spec.lua index 53fe303762..2143c01139 100644 --- a/test/functional/ui/bufhl_spec.lua +++ b/test/functional/ui/bufhl_spec.lua @@ -1,7 +1,8 @@ local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') + local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert -local execute, request, neq = helpers.execute, helpers.request, helpers.neq +local command, request, neq = helpers.command, helpers.request, helpers.neq if helpers.pending_win32(pending) then return end @@ -11,7 +12,7 @@ describe('Buffer highlighting', function() before_each(function() clear() - execute("syntax on") + command('syntax on') screen = Screen.new(40, 8) screen:attach() screen:set_default_attr_ids({ @@ -23,7 +24,8 @@ describe('Buffer highlighting', function() [6] = {foreground = Screen.colors.DarkCyan}, -- Identifier [7] = {bold = true}, [8] = {underline = true, bold = true, foreground = Screen.colors.SlateBlue}, - [9] = {foreground = Screen.colors.SlateBlue, underline = true} + [9] = {foreground = Screen.colors.SlateBlue, underline = true}, + [10] = {foreground = Screen.colors.Red} }) curbuf = request('nvim_get_current_buf') end) @@ -106,7 +108,7 @@ describe('Buffer highlighting', function() combining highlights from different sources]]) - execute("hi ImportantWord gui=bold cterm=bold") + command("hi ImportantWord gui=bold cterm=bold") id1 = add_hl(0, "ImportantWord", 0, 2, 8) add_hl(id1, "ImportantWord", 1, 12, -1) add_hl(id1, "ImportantWord", 2, 0, 9) @@ -131,7 +133,7 @@ describe('Buffer highlighting', function() {1:~ }| {1:~ }| {1:~ }| - :hi ImportantWord gui=bold cterm=bold | + | ]]) end) @@ -145,7 +147,7 @@ describe('Buffer highlighting', function() {1:~ }| {1:~ }| {1:~ }| - :hi ImportantWord gui=bold cterm=bold | + | ]]) end) @@ -159,7 +161,7 @@ describe('Buffer highlighting', function() {1:~ }| {1:~ }| {1:~ }| - :hi ImportantWord gui=bold cterm=bold | + | ]]) end) @@ -175,7 +177,7 @@ describe('Buffer highlighting', function() {1:~ }| {1:~ }| {1:~ }| - :hi ImportantWord gui=bold cterm=bold | + | ]]) end) @@ -192,7 +194,7 @@ describe('Buffer highlighting', function() | ]]) - execute(':3move 4') + command(':3move 4') screen:expect([[ a {5:longer} example | | @@ -201,7 +203,7 @@ describe('Buffer highlighting', function() {1:~ }| {1:~ }| {1:~ }| - ::3move 4 | + | ]]) end) end) @@ -254,4 +256,32 @@ describe('Buffer highlighting', function() | ]]) end) + + it('works with new syntax groups', function() + insert([[ + fancy code in a new fancy language]]) + add_hl(-1, "FancyLangItem", 0, 0, 5) + screen:expect([[ + fancy code in a new fancy languag^e | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + + command('hi FancyLangItem guifg=red') + screen:expect([[ + {10:fancy} code in a new fancy languag^e | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + end) end) diff --git a/test/functional/ui/cmdline_highlight_spec.lua b/test/functional/ui/cmdline_highlight_spec.lua new file mode 100644 index 0000000000..d87ce72599 --- /dev/null +++ b/test/functional/ui/cmdline_highlight_spec.lua @@ -0,0 +1,893 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') + +local eq = helpers.eq +local feed = helpers.feed +local clear = helpers.clear +local meths = helpers.meths +local funcs = helpers.funcs +local source = helpers.source +local dedent = helpers.dedent +local command = helpers.command +local curbufmeths = helpers.curbufmeths + +local screen + +-- Bug in input() handling: :redraw! will erase the whole prompt up until +-- user types something. It exists in Vim as well, so using `h<BS>` as +-- a workaround. +local function redraw_input() + feed('{REDRAW}h<BS>') +end + +before_each(function() + clear() + screen = Screen.new(40, 8) + screen:attach() + source([[ + highlight RBP1 guibg=Red + highlight RBP2 guibg=Yellow + highlight RBP3 guibg=Green + highlight RBP4 guibg=Blue + let g:NUM_LVLS = 4 + function Redraw() + redraw! + return '' + endfunction + let g:id = '' + cnoremap <expr> {REDRAW} Redraw() + function DoPrompt(do_return) abort + let id = g:id + let Cb = g:Nvim_color_input{g:id} + let out = input({'prompt': ':', 'highlight': Cb}) + let g:out{id} = out + return (a:do_return ? out : '') + endfunction + nnoremap <expr> {PROMPT} DoPrompt(0) + cnoremap <expr> {PROMPT} DoPrompt(1) + function RainBowParens(cmdline) + let ret = [] + let i = 0 + let lvl = 0 + while i < len(a:cmdline) + if a:cmdline[i] is# '(' + call add(ret, [i, i + 1, 'RBP' . ((lvl % g:NUM_LVLS) + 1)]) + let lvl += 1 + elseif a:cmdline[i] is# ')' + let lvl -= 1 + call add(ret, [i, i + 1, 'RBP' . ((lvl % g:NUM_LVLS) + 1)]) + endif + let i += 1 + endwhile + return ret + endfunction + function SplittedMultibyteStart(cmdline) + let ret = [] + let i = 0 + while i < len(a:cmdline) + let char = nr2char(char2nr(a:cmdline[i:])) + if a:cmdline[i:i + len(char) - 1] is# char + if len(char) > 1 + call add(ret, [i + 1, i + len(char), 'RBP2']) + endif + let i += len(char) + else + let i += 1 + endif + endwhile + return ret + endfunction + function SplittedMultibyteEnd(cmdline) + let ret = [] + let i = 0 + while i < len(a:cmdline) + let char = nr2char(char2nr(a:cmdline[i:])) + if a:cmdline[i:i + len(char) - 1] is# char + if len(char) > 1 + call add(ret, [i, i + 1, 'RBP1']) + endif + let i += len(char) + else + let i += 1 + endif + endwhile + return ret + endfunction + function Echoing(cmdline) + echo 'HERE' + return v:_null_list + endfunction + function Echoning(cmdline) + echon 'HERE' + return v:_null_list + endfunction + function Echomsging(cmdline) + echomsg 'HERE' + return v:_null_list + endfunction + function Echoerring(cmdline) + echoerr 'HERE' + return v:_null_list + endfunction + function Redrawing(cmdline) + redraw! + return v:_null_list + endfunction + function Throwing(cmdline) + throw "ABC" + return v:_null_list + endfunction + function Halting(cmdline) + while 1 + endwhile + endfunction + function ReturningGlobal(cmdline) + return g:callback_return + endfunction + function ReturningGlobal2(cmdline) + return g:callback_return[:len(a:cmdline)-1] + endfunction + function ReturningGlobalN(n, cmdline) + return g:callback_return{a:n} + endfunction + let g:recording_calls = [] + function Recording(cmdline) + call add(g:recording_calls, a:cmdline) + return [] + endfunction + ]]) + screen:set_default_attr_ids({ + RBP1={background = Screen.colors.Red}, + RBP2={background = Screen.colors.Yellow}, + RBP3={background = Screen.colors.Green}, + RBP4={background = Screen.colors.Blue}, + EOB={bold = true, foreground = Screen.colors.Blue1}, + ERR={foreground = Screen.colors.Grey100, background = Screen.colors.Red}, + SK={foreground = Screen.colors.Blue}, + PE={bold = true, foreground = Screen.colors.SeaGreen4} + }) +end) + +local function set_color_cb(funcname, callback_return, id) + meths.set_var('id', id or '') + if id and id ~= '' and funcs.exists('*' .. funcname .. 'N') then + command(('let g:Nvim_color_input%s = {cmdline -> %sN(%s, cmdline)}'):format( + id, funcname, id)) + if callback_return then + meths.set_var('callback_return' .. id, callback_return) + end + else + meths.set_var('Nvim_color_input', funcname) + if callback_return then + meths.set_var('callback_return', callback_return) + end + end +end +local function start_prompt(text) + feed('{PROMPT}' .. (text or '')) +end + +describe('Command-line coloring', function() + it('works', function() + set_color_cb('RainBowParens') + meths.set_option('more', false) + start_prompt() + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + :^ | + ]]) + feed('e') + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + :e^ | + ]]) + feed('cho ') + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + :echo ^ | + ]]) + feed('(') + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + :echo {RBP1:(}^ | + ]]) + feed('(') + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + :echo {RBP1:(}{RBP2:(}^ | + ]]) + feed('42') + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + :echo {RBP1:(}{RBP2:(}42^ | + ]]) + feed('))') + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + :echo {RBP1:(}{RBP2:(}42{RBP2:)}{RBP1:)}^ | + ]]) + feed('<BS>') + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + :echo {RBP1:(}{RBP2:(}42{RBP2:)}^ | + ]]) + redraw_input() + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + :echo {RBP1:(}{RBP2:(}42{RBP2:)}^ | + ]]) + end) + for _, func_part in ipairs({'', 'n', 'msg'}) do + it('disables :echo' .. func_part .. ' messages', function() + set_color_cb('Echo' .. func_part .. 'ing') + start_prompt('echo') + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + :echo^ | + ]]) + end) + end + it('does the right thing when hl start appears to split multibyte char', + function() + set_color_cb('SplittedMultibyteStart') + start_prompt('echo "«') + screen:expect([[ + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + :echo " | + {ERR:E5405: Chunk 0 start 7 splits multibyte }| + {ERR:character} | + :echo "«^ | + ]]) + feed('»') + screen:expect([[ + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + :echo " | + {ERR:E5405: Chunk 0 start 7 splits multibyte }| + {ERR:character} | + :echo "«»^ | + ]]) + end) + it('does the right thing when hl end appears to split multibyte char', + function() + set_color_cb('SplittedMultibyteEnd') + start_prompt('echo "«') + screen:expect([[ + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + :echo " | + {ERR:E5406: Chunk 0 end 7 splits multibyte ch}| + {ERR:aracter} | + :echo "«^ | + ]]) + end) + it('does the right thing when errorring', function() + set_color_cb('Echoerring') + start_prompt('e') + screen:expect([[ + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + : | + {ERR:E5407: Callback has thrown an exception:}| + {ERR: Vim(echoerr):HERE} | + :e^ | + ]]) + end) + it('silences :echo', function() + set_color_cb('Echoing') + start_prompt('e') + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + :e^ | + ]]) + eq('', meths.command_output('messages')) + end) + it('silences :echon', function() + set_color_cb('Echoning') + start_prompt('e') + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + :e^ | + ]]) + eq('', meths.command_output('messages')) + end) + it('silences :echomsg', function() + set_color_cb('Echomsging') + start_prompt('e') + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + :e^ | + ]]) + eq('', meths.command_output('messages')) + end) + it('does the right thing when throwing', function() + set_color_cb('Throwing') + start_prompt('e') + screen:expect([[ + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + : | + {ERR:E5407: Callback has thrown an exception:}| + {ERR: ABC} | + :e^ | + ]]) + end) + it('stops executing callback after a number of errors', function() + set_color_cb('SplittedMultibyteStart') + start_prompt('let x = "«»«»«»«»«»"\n') + screen:expect([[ + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + :let x = " | + {ERR:E5405: Chunk 0 start 10 splits multibyte}| + {ERR: character} | + ^:let x = "«»«»«»«»«»" | + ]]) + feed('\n') + screen:expect([[ + ^ | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + | + ]]) + eq('let x = "«»«»«»«»«»"', meths.get_var('out')) + local msg = '\nE5405: Chunk 0 start 10 splits multibyte character' + eq(msg:rep(1), funcs.execute('messages')) + end) + it('allows interrupting callback with <C-c>', function() + set_color_cb('Halting') + start_prompt('echo 42') + screen:expect([[ + ^ | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + | + ]]) + screen:sleep(500) + feed('<C-c>') + screen:expect([[ + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + : | + {ERR:E5407: Callback has thrown an exception:}| + {ERR: Keyboard interrupt} | + :echo 42^ | + ]]) + redraw_input() + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + :echo 42^ | + ]]) + feed('\n') + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + ^:echo 42 | + ]]) + feed('\n') + eq('echo 42', meths.get_var('out')) + feed('<C-c>') + screen:expect([[ + ^ | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + Type :quit<Enter> to exit Nvim | + ]]) + end) + it('works fine with NUL, NL, CR', function() + set_color_cb('RainBowParens') + start_prompt('echo ("<C-v><CR><C-v><Nul><C-v><NL>")') + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + :echo {RBP1:(}"{SK:^M^@^@}"{RBP1:)}^ | + ]]) + end) + it('errors out when callback returns something wrong', function() + command('cnoremap + ++') + set_color_cb('ReturningGlobal', '') + start_prompt('#') + screen:expect([[ + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + : | + {ERR:E5400: Callback should return list} | + :#^ | + ]]) + + feed('<CR><CR><CR>') + set_color_cb('ReturningGlobal', {{0, 1, 'Normal'}, 42}) + start_prompt('#') + screen:expect([[ + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + : | + {ERR:E5401: List item 1 is not a List} | + :#^ | + ]]) + + feed('<CR><CR><CR>') + set_color_cb('ReturningGlobal2', {{0, 1, 'Normal'}, {1}}) + start_prompt('+') + screen:expect([[ + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + :+ | + {ERR:E5402: List item 1 has incorrect length:}| + {ERR: 1 /= 3} | + :++^ | + ]]) + + feed('<CR><CR><CR>') + set_color_cb('ReturningGlobal2', {{0, 1, 'Normal'}, {2, 3, 'Normal'}}) + start_prompt('+') + screen:expect([[ + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + :+ | + {ERR:E5403: Chunk 1 start 2 not in range [1, }| + {ERR:2)} | + :++^ | + ]]) + + feed('<CR><CR><CR>') + set_color_cb('ReturningGlobal2', {{0, 1, 'Normal'}, {1, 3, 'Normal'}}) + start_prompt('+') + screen:expect([[ + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + :+ | + {ERR:E5404: Chunk 1 end 3 not in range (1, 2]}| + | + :++^ | + ]]) + end) + it('does not error out when called from a errorred out cycle', function() + set_color_cb('ReturningGlobal', {{0, 1, 'Normal'}}) + feed(dedent([[ + :set regexpengine=2 + :for pat in [' \ze*', ' \zs*'] + : try + : let l = matchlist('x x', pat) + : $put =input({'prompt':'>','highlight':'ReturningGlobal'}) + : + : $put ='E888 NOT detected for ' . pat + : catch + : $put =input({'prompt':'>','highlight':'ReturningGlobal'}) + : + : $put ='E888 detected for ' . pat + : endtry + :endfor + : + : + : + : + : + : + ]])) + eq({'', ':', 'E888 detected for \\ze*', ':', 'E888 detected for \\zs*'}, + curbufmeths.get_lines(0, -1, false)) + eq('', funcs.execute('messages')) + end) + it('allows nesting input()s', function() + set_color_cb('ReturningGlobal', {{0, 1, 'RBP1'}}, '') + start_prompt('1') + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + :{RBP1:1}^ | + ]]) + + set_color_cb('ReturningGlobal', {{0, 1, 'RBP2'}}, '1') + start_prompt('2') + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + :{RBP2:2}^ | + ]]) + + set_color_cb('ReturningGlobal', {{0, 1, 'RBP3'}}, '2') + start_prompt('3') + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + :{RBP3:3}^ | + ]]) + + set_color_cb('ReturningGlobal', {{0, 1, 'RBP4'}}, '3') + start_prompt('4') + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + :{RBP4:4}^ | + ]]) + + feed('<CR>') + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + :{RBP3:3}4^ | + ]]) + feed('<CR>') + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + :{RBP2:2}34^ | + ]]) + feed('<CR>') + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + :{RBP1:1}234^ | + ]]) + feed('<CR><CR><C-l>') + screen:expect([[ + ^ | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + | + ]]) + eq('1234', meths.get_var('out')) + eq('234', meths.get_var('out1')) + eq('34', meths.get_var('out2')) + eq('4', meths.get_var('out3')) + eq(0, funcs.exists('g:out4')) + end) + it('runs callback with the same data only once', function() + local function new_recording_calls(...) + eq({...}, meths.get_var('recording_calls')) + meths.set_var('recording_calls', {}) + end + set_color_cb('Recording') + start_prompt('') + -- Regression test. Disambiguation: + -- + -- new_recording_calls(expected_result) -- (actual_before_fix) + -- + feed('a') + new_recording_calls('a') -- ('a', 'a') + feed('b') + new_recording_calls('ab') -- ('a', 'ab', 'ab') + feed('c') + new_recording_calls('abc') -- ('ab', 'abc', 'abc') + feed('<BS>') + new_recording_calls('ab') -- ('abc', 'ab', 'ab') + feed('<BS>') + new_recording_calls('a') -- ('ab', 'a', 'a') + feed('<BS>') + new_recording_calls() -- ('a') + feed('<CR><CR>') + eq('', meths.get_var('out')) + end) +end) +describe('Ex commands coloring support', function() + it('works', function() + meths.set_var('Nvim_color_cmdline', 'RainBowParens') + feed(':echo (((1)))') + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + :echo {RBP1:(}{RBP2:(}{RBP3:(}1{RBP3:)}{RBP2:)}{RBP1:)}^ | + ]]) + end) + it('still executes command-line even if errored out', function() + meths.set_var('Nvim_color_cmdline', 'SplittedMultibyteStart') + feed(':let x = "«"\n') + eq('«', meths.get_var('x')) + local msg = 'E5405: Chunk 0 start 10 splits multibyte character' + eq('\n'..msg, funcs.execute('messages')) + end) + it('does not error out when called from a errorred out cycle', function() + -- Apparently when there is a cycle in which one of the commands errors out + -- this error may be caught by color_cmdline before it is presented to the + -- user. + feed(dedent([[ + :set regexpengine=2 + :for pat in [' \ze*', ' \zs*'] + : try + : let l = matchlist('x x', pat) + : $put ='E888 NOT detected for ' . pat + : catch + : $put ='E888 detected for ' . pat + : endtry + :endfor + ]])) + eq({'', 'E888 detected for \\ze*', 'E888 detected for \\zs*'}, + curbufmeths.get_lines(0, -1, false)) + eq('', funcs.execute('messages')) + end) + it('does not crash when using `n` in debug mode', function() + feed(':debug execute "echo 1"\n') + screen:expect([[ + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + Entering Debug mode. Type "cont" to con| + tinue. | + cmd: execute "echo 1" | + >^ | + ]]) + feed('n\n') + screen:expect([[ + {EOB:~ }| + {EOB:~ }| + Entering Debug mode. Type "cont" to con| + tinue. | + cmd: execute "echo 1" | + >n | + 1 | + {PE:Press ENTER or type command to continue}^ | + ]]) + feed('\n') + screen:expect([[ + ^ | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + | + ]]) + end) + it('does not prevent mapping error from cancelling prompt', function() + command("cnoremap <expr> x execute('throw 42')[-1]") + feed(':#x') + screen:expect([[ + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + :# | + {ERR:Error detected while processing :} | + {ERR:E605: Exception not caught: 42} | + :#^ | + ]]) + feed('<CR>') + screen:expect([[ + ^ | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + | + ]]) + feed('<CR>') + screen:expect([[ + ^ | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + | + ]]) + eq('\nError detected while processing :\nE605: Exception not caught: 42', + meths.command_output('messages')) + end) + it('errors out when failing to get callback', function() + meths.set_var('Nvim_color_cmdline', 42) + feed(':#') + screen:expect([[ + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + : | + {ERR:E5408: Unable to get g:Nvim_color_cmdlin}| + {ERR:e callback: Vim:E6000: Argument is not a}| + {ERR: function or function name} | + :#^ | + ]]) + end) +end) +describe('Expressions coloring support', function() + it('works', function() + meths.set_var('Nvim_color_expr', 'RainBowParens') + feed(':echo <C-r>=(((1)))') + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + ={RBP1:(}{RBP2:(}{RBP3:(}1{RBP3:)}{RBP2:)}{RBP1:)}^ | + ]]) + end) + it('errors out when failing to get callback', function() + meths.set_var('Nvim_color_expr', 42) + feed(':<C-r>=1') + screen:expect([[ + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + = | + {ERR:E5409: Unable to get g:Nvim_color_expr c}| + {ERR:allback: Vim:E6000: Argument is not a fu}| + {ERR:nction or function name} | + =1^ | + ]]) + end) +end) diff --git a/test/functional/ui/cmdline_spec.lua b/test/functional/ui/cmdline_spec.lua new file mode 100644 index 0000000000..0f8302b036 --- /dev/null +++ b/test/functional/ui/cmdline_spec.lua @@ -0,0 +1,525 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') +local clear, feed, eq = helpers.clear, helpers.feed, helpers.eq +local source = helpers.source +local ok = helpers.ok +local command = helpers.command + +describe('external cmdline', function() + local screen + local last_level = 0 + local cmdline = {} + local block = nil + + before_each(function() + clear() + cmdline, block = {}, nil + screen = Screen.new(25, 5) + screen:attach({rgb=true, ext_cmdline=true}) + screen:set_on_event_handler(function(name, data) + if name == "cmdline_show" then + local content, pos, firstc, prompt, indent, level = unpack(data) + ok(level > 0) + cmdline[level] = {content=content, pos=pos, firstc=firstc, + prompt=prompt, indent=indent} + last_level = level + elseif name == "cmdline_hide" then + local level = data[1] + cmdline[level] = nil + elseif name == "cmdline_special_char" then + local char, shift, level = unpack(data) + cmdline[level].special = {char, shift} + elseif name == "cmdline_pos" then + local pos, level = unpack(data) + cmdline[level].pos = pos + elseif name == "cmdline_block_show" then + block = data[1] + elseif name == "cmdline_block_append" then + block[#block+1] = data[1] + elseif name == "cmdline_block_hide" then + block = nil + end + end) + end) + + after_each(function() + screen:detach() + end) + + local function expect_cmdline(level, expected) + local attr_ids = screen._default_attr_ids + local attr_ignore = screen._default_attr_ignore + local actual = '' + for _, chunk in ipairs(cmdline[level] and cmdline[level].content or {}) do + local attrs, text = chunk[1], chunk[2] + if screen:_equal_attrs(attrs, {}) then + actual = actual..text + else + local attr_id = screen:_get_attr_id(attr_ids, attr_ignore, attrs) + actual = actual..'{' .. attr_id .. ':' .. text .. '}' + end + end + eq(expected, actual) + end + + it('works', function() + feed(':') + screen:expect([[ + ^ | + ~ | + ~ | + ~ | + | + ]], nil, nil, function() + eq(1, last_level) + eq({{ + content = { { {}, "" } }, + firstc = ":", + indent = 0, + pos = 0, + prompt = "" + }}, cmdline) + end) + + feed('sign') + screen:expect([[ + ^ | + ~ | + ~ | + ~ | + | + ]], nil, nil, function() + eq({{ + content = { { {}, "sign" } }, + firstc = ":", + indent = 0, + pos = 4, + prompt = "" + }}, cmdline) + end) + + feed('<Left>') + screen:expect([[ + ^ | + ~ | + ~ | + ~ | + | + ]], nil, nil, function() + eq({{ + content = { { {}, "sign" } }, + firstc = ":", + indent = 0, + pos = 3, + prompt = "" + }}, cmdline) + end) + + feed('<bs>') + screen:expect([[ + ^ | + ~ | + ~ | + ~ | + | + ]], nil, nil, function() + eq({{ + content = { { {}, "sin" } }, + firstc = ":", + indent = 0, + pos = 2, + prompt = "" + }}, cmdline) + end) + + feed('<Esc>') + screen:expect([[ + ^ | + ~ | + ~ | + ~ | + | + ]], nil, nil, function() + eq({}, cmdline) + end) + end) + + it("works with input()", function() + feed(':call input("input", "default")<cr>') + screen:expect([[ + ^ | + ~ | + ~ | + ~ | + | + ]], nil, nil, function() + eq({{ + content = { { {}, "default" } }, + firstc = "", + indent = 0, + pos = 7, + prompt = "input" + }}, cmdline) + end) + feed('<cr>') + screen:expect([[ + ^ | + ~ | + ~ | + ~ | + | + ]], nil, nil, function() + eq({}, cmdline) + end) + + end) + + it("works with special chars and nested cmdline", function() + feed(':xx<c-r>') + screen:expect([[ + ^ | + ~ | + ~ | + ~ | + | + ]], nil, nil, function() + eq({{ + content = { { {}, "xx" } }, + firstc = ":", + indent = 0, + pos = 2, + prompt = "", + special = {'"', true}, + }}, cmdline) + end) + + feed('=') + screen:expect([[ + ^ | + ~ | + ~ | + ~ | + | + ]], nil, nil, function() + eq({{ + content = { { {}, "xx" } }, + firstc = ":", + indent = 0, + pos = 2, + prompt = "", + special = {'"', true}, + },{ + content = { { {}, "" } }, + firstc = "=", + indent = 0, + pos = 0, + prompt = "", + }}, cmdline) + end) + + feed('1+2') + local expectation = {{ + content = { { {}, "xx" } }, + firstc = ":", + indent = 0, + pos = 2, + prompt = "", + special = {'"', true}, + },{ + content = { { {}, "1+2" } }, + firstc = "=", + indent = 0, + pos = 3, + prompt = "", + }} + screen:expect([[ + ^ | + ~ | + ~ | + ~ | + | + ]], nil, nil, function() + eq(expectation, cmdline) + end) + + -- erase information, so we check if it is retransmitted + cmdline = {} + command("redraw!") + -- redraw! forgets cursor position. Be OK with that, as UI should indicate + -- focus is at external cmdline anyway. + screen:expect([[ + | + ~ | + ~ | + ~ | + ^ | + ]], nil, nil, function() + eq(expectation, cmdline) + end) + + + feed('<cr>') + screen:expect([[ + | + ~ | + ~ | + ~ | + ^ | + ]], nil, nil, function() + eq({{ + content = { { {}, "xx3" } }, + firstc = ":", + indent = 0, + pos = 3, + prompt = "", + }}, cmdline) + end) + + feed('<esc>') + screen:expect([[ + ^ | + ~ | + ~ | + ~ | + | + ]], nil, nil, function() + eq({}, cmdline) + end) + end) + + it("works with function definitions", function() + feed(':function Foo()<cr>') + screen:expect([[ + ^ | + ~ | + ~ | + ~ | + | + ]], nil, nil, function() + eq({{ + content = { { {}, "" } }, + firstc = ":", + indent = 2, + pos = 0, + prompt = "", + }}, cmdline) + eq({{{{}, 'function Foo()'}}}, block) + end) + + feed('line1<cr>') + screen:expect([[ + ^ | + ~ | + ~ | + ~ | + | + ]], nil, nil, function() + eq({{{{}, 'function Foo()'}}, + {{{}, ' line1'}}}, block) + end) + + block = {} + command("redraw!") + screen:expect([[ + | + ~ | + ~ | + ~ | + ^ | + ]], nil, nil, function() + eq({{{{}, 'function Foo()'}}, + {{{}, ' line1'}}}, block) + end) + + + feed('endfunction<cr>') + screen:expect([[ + ^ | + ~ | + ~ | + ~ | + | + ]], nil, nil, function() + eq(nil, block) + end) + end) + + it("works with cmdline window", function() + feed(':make') + screen:expect([[ + ^ | + ~ | + ~ | + ~ | + | + ]], nil, nil, function() + eq({{ + content = { { {}, "make" } }, + firstc = ":", + indent = 0, + pos = 4, + prompt = "" + }}, cmdline) + end) + + feed('<c-f>') + screen:expect([[ + | + [No Name] | + :make^ | + [Command Line] | + | + ]], nil, nil, function() + eq({}, cmdline) + end) + + -- nested cmdline + feed(':yank') + screen:expect([[ + | + [No Name] | + :make^ | + [Command Line] | + | + ]], nil, nil, function() + eq({nil, { + content = { { {}, "yank" } }, + firstc = ":", + indent = 0, + pos = 4, + prompt = "" + }}, cmdline) + end) + + cmdline = {} + command("redraw!") + screen:expect([[ + | + [No Name] | + :make | + [Command Line] | + ^ | + ]], nil, nil, function() + eq({nil, { + content = { { {}, "yank" } }, + firstc = ":", + indent = 0, + pos = 4, + prompt = "" + }}, cmdline) + end) + + feed("<c-c>") + screen:expect([[ + | + [No Name] | + :make^ | + [Command Line] | + | + ]], nil, nil, function() + eq({}, cmdline) + end) + + feed("<c-c>") + screen:expect([[ + | + [No Name] | + :make^ | + [Command Line] | + | + ]], nil, nil, function() + eq({{ + content = { { {}, "make" } }, + firstc = ":", + indent = 0, + pos = 4, + prompt = "" + }}, cmdline) + end) + + cmdline = {} + command("redraw!") + screen:expect([[ + | + ~ | + ~ | + ~ | + ^ | + ]], nil, nil, function() + eq({{ + content = { { {}, "make" } }, + firstc = ":", + indent = 0, + pos = 4, + prompt = "" + }}, cmdline) + end) + end) + + it('works with inputsecret()', function() + feed(":call inputsecret('secret:')<cr>abc123") + screen:expect([[ + ^ | + ~ | + ~ | + ~ | + | + ]], nil, nil, function() + eq({{ + content = { { {}, "******" } }, + firstc = "", + indent = 0, + pos = 6, + prompt = "secret:" + }}, cmdline) + end) + end) + + it('works with highlighted cmdline', function() + source([[ + highlight RBP1 guibg=Red + highlight RBP2 guibg=Yellow + highlight RBP3 guibg=Green + highlight RBP4 guibg=Blue + let g:NUM_LVLS = 4 + function RainBowParens(cmdline) + let ret = [] + let i = 0 + let lvl = 0 + while i < len(a:cmdline) + if a:cmdline[i] is# '(' + call add(ret, [i, i + 1, 'RBP' . ((lvl % g:NUM_LVLS) + 1)]) + let lvl += 1 + elseif a:cmdline[i] is# ')' + let lvl -= 1 + call add(ret, [i, i + 1, 'RBP' . ((lvl % g:NUM_LVLS) + 1)]) + endif + let i += 1 + endwhile + return ret + endfunction + map <f5> :let x = input({'prompt':'>','highlight':'RainBowParens'})<cr> + "map <f5> :let x = input({'prompt':'>'})<cr> + ]]) + screen:set_default_attr_ids({ + RBP1={background = Screen.colors.Red}, + RBP2={background = Screen.colors.Yellow}, + RBP3={background = Screen.colors.Green}, + RBP4={background = Screen.colors.Blue}, + EOB={bold = true, foreground = Screen.colors.Blue1}, + ERR={foreground = Screen.colors.Grey100, background = Screen.colors.Red}, + SK={foreground = Screen.colors.Blue}, + PE={bold = true, foreground = Screen.colors.SeaGreen4} + }) + feed('<f5>(a(b)a)') + screen:expect([[ + ^ | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + | + ]], nil, nil, function() + expect_cmdline(1, '{RBP1:(}a{RBP2:(}b{RBP2:)}a{RBP1:)}') + end) + end) +end) diff --git a/test/functional/ui/cursor_spec.lua b/test/functional/ui/cursor_spec.lua new file mode 100644 index 0000000000..b47210a777 --- /dev/null +++ b/test/functional/ui/cursor_spec.lua @@ -0,0 +1,246 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') +local clear, meths = helpers.clear, helpers.meths +local eq = helpers.eq +local command = helpers.command + +describe('ui/cursor', function() + local screen + + before_each(function() + clear() + screen = Screen.new(25, 5) + screen:attach() + end) + + after_each(function() + screen:detach() + end) + + it("'guicursor' is published as a UI event", function() + local expected_mode_info = { + [1] = { + blinkoff = 0, + blinkon = 0, + blinkwait = 0, + cell_percentage = 0, + cursor_shape = 'block', + name = 'normal', + hl_id = 0, + id_lm = 0, + mouse_shape = 0, + short_name = 'n' }, + [2] = { + blinkoff = 0, + blinkon = 0, + blinkwait = 0, + cell_percentage = 0, + cursor_shape = 'block', + name = 'visual', + hl_id = 0, + id_lm = 0, + mouse_shape = 0, + short_name = 'v' }, + [3] = { + blinkoff = 0, + blinkon = 0, + blinkwait = 0, + cell_percentage = 25, + cursor_shape = 'vertical', + name = 'insert', + hl_id = 0, + id_lm = 0, + mouse_shape = 0, + short_name = 'i' }, + [4] = { + blinkoff = 0, + blinkon = 0, + blinkwait = 0, + cell_percentage = 20, + cursor_shape = 'horizontal', + name = 'replace', + hl_id = 0, + id_lm = 0, + mouse_shape = 0, + short_name = 'r' }, + [5] = { + blinkoff = 0, + blinkon = 0, + blinkwait = 0, + cell_percentage = 0, + cursor_shape = 'block', + name = 'cmdline_normal', + hl_id = 0, + id_lm = 0, + mouse_shape = 0, + short_name = 'c' }, + [6] = { + blinkoff = 0, + blinkon = 0, + blinkwait = 0, + cell_percentage = 25, + cursor_shape = 'vertical', + name = 'cmdline_insert', + hl_id = 0, + id_lm = 0, + mouse_shape = 0, + short_name = 'ci' }, + [7] = { + blinkoff = 0, + blinkon = 0, + blinkwait = 0, + cell_percentage = 20, + cursor_shape = 'horizontal', + name = 'cmdline_replace', + hl_id = 0, + id_lm = 0, + mouse_shape = 0, + short_name = 'cr' }, + [8] = { + blinkoff = 0, + blinkon = 0, + blinkwait = 0, + cell_percentage = 20, + cursor_shape = 'horizontal', + name = 'operator', + hl_id = 0, + id_lm = 0, + mouse_shape = 0, + short_name = 'o' }, + [9] = { + blinkoff = 0, + blinkon = 0, + blinkwait = 0, + cell_percentage = 25, + cursor_shape = 'vertical', + name = 'visual_select', + hl_id = 0, + id_lm = 0, + mouse_shape = 0, + short_name = 've' }, + [10] = { + name = 'cmdline_hover', + mouse_shape = 0, + short_name = 'e' }, + [11] = { + name = 'statusline_hover', + mouse_shape = 0, + short_name = 's' }, + [12] = { + name = 'statusline_drag', + mouse_shape = 0, + short_name = 'sd' }, + [13] = { + name = 'vsep_hover', + mouse_shape = 0, + short_name = 'vs' }, + [14] = { + name = 'vsep_drag', + mouse_shape = 0, + short_name = 'vd' }, + [15] = { + name = 'more', + mouse_shape = 0, + short_name = 'm' }, + [16] = { + name = 'more_lastline', + mouse_shape = 0, + short_name = 'ml' }, + [17] = { + blinkoff = 0, + blinkon = 0, + blinkwait = 0, + cell_percentage = 0, + cursor_shape = 'block', + name = 'showmatch', + hl_id = 0, + id_lm = 0, + short_name = 'sm' }, + } + + screen:expect(function() + -- Default 'guicursor', published on startup. + eq(expected_mode_info, screen._mode_info) + eq(true, screen._cursor_style_enabled) + eq('normal', screen.mode) + end) + + -- Event is published ONLY if the cursor style changed. + screen._mode_info = nil + command("echo 'test'") + screen:expect([[ + ^ | + ~ | + ~ | + ~ | + test | + ]], nil, nil, function() + eq(nil, screen._mode_info) + end) + + -- Change the cursor style. + helpers.command('set guicursor=n-v-c:block,i-ci-ve:ver25,r-cr-o:hor20' + ..',a:blinkwait700-blinkoff400-blinkon250-Cursor/lCursor' + ..',sm:block-blinkwait175-blinkoff150-blinkon175') + + -- Update the expected values. + for _, m in ipairs(expected_mode_info) do + if m.name == 'showmatch' then + if m.blinkon then m.blinkon = 175 end + if m.blinkoff then m.blinkoff = 150 end + if m.blinkwait then m.blinkwait = 175 end + else + if m.blinkon then m.blinkon = 250 end + if m.blinkoff then m.blinkoff = 400 end + if m.blinkwait then m.blinkwait = 700 end + end + if m.hl_id then m.hl_id = 48 end + if m.id_lm then m.id_lm = 49 end + end + + -- Assert the new expectation. + screen:expect(function() + eq(expected_mode_info, screen._mode_info) + eq(true, screen._cursor_style_enabled) + eq('normal', screen.mode) + end) + + -- Another cursor style. + meths.set_option('guicursor', 'n-v-c:ver35-blinkwait171-blinkoff172-blinkon173' + ..',ve:hor35,o:ver50,i-ci:block,r-cr:hor90,sm:ver42') + screen:expect(function() + local named = {} + for _, m in ipairs(screen._mode_info) do + named[m.name] = m + end + eq('vertical', named.normal.cursor_shape) + eq(35, named.normal.cell_percentage) + eq('horizontal', named.visual_select.cursor_shape) + eq(35, named.visual_select.cell_percentage) + eq('vertical', named.operator.cursor_shape) + eq(50, named.operator.cell_percentage) + eq('block', named.insert.cursor_shape) + eq('vertical', named.showmatch.cursor_shape) + eq(90, named.cmdline_replace.cell_percentage) + eq(171, named.normal.blinkwait) + eq(172, named.normal.blinkoff) + eq(173, named.normal.blinkon) + eq(42, named.showmatch.cell_percentage) + end) + end) + + it("empty 'guicursor' sets cursor_shape=block in all modes", function() + meths.set_option('guicursor', '') + screen:expect(function() + -- Empty 'guicursor' sets enabled=false. + eq(false, screen._cursor_style_enabled) + for _, m in ipairs(screen._mode_info) do + if m['cursor_shape'] ~= nil then + eq('block', m.cursor_shape) + eq(0, m.blinkon) + end + end + end) + end) + +end) diff --git a/test/functional/ui/highlight_spec.lua b/test/functional/ui/highlight_spec.lua index 945b16ef92..2252e3580f 100644 --- a/test/functional/ui/highlight_spec.lua +++ b/test/functional/ui/highlight_spec.lua @@ -2,23 +2,22 @@ local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') local os = require('os') local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert -local execute, request, eq = helpers.execute, helpers.request, helpers.eq +local command = helpers.command +local eval, exc_exec = helpers.eval, helpers.exc_exec +local feed_command, eq = helpers.feed_command, helpers.eq +local curbufmeths = helpers.curbufmeths -if helpers.pending_win32(pending) then return end - -describe('color scheme compatibility', function() +describe('colorscheme compatibility', function() before_each(function() clear() end) it('t_Co is set to 256 by default', function() - eq('256', request('vim_eval', '&t_Co')) - request('nvim_set_option', 't_Co', '88') - eq('88', request('vim_eval', '&t_Co')) + eq('256', eval('&t_Co')) end) end) -describe('manual syntax highlight', function() +describe('highlight: `:syntax manual`', function() -- When using manual syntax highlighting, it should be preserved even when -- switching buffers... bug did only occur without :set hidden -- Ref: vim patch 7.4.1236 @@ -41,18 +40,18 @@ describe('manual syntax highlight', function() end) it("works with buffer switch and 'hidden'", function() - execute('e tmp1.vim') - execute('e Xtest-functional-ui-highlight.tmp.vim') - execute('filetype on') - execute('syntax manual') - execute('set ft=vim') - execute('set syntax=ON') + feed_command('e tmp1.vim') + feed_command('e Xtest-functional-ui-highlight.tmp.vim') + feed_command('filetype on') + feed_command('syntax manual') + feed_command('set ft=vim') + feed_command('set syntax=ON') feed('iecho 1<esc>0') - execute('set hidden') - execute('w') - execute('bn') - execute('bp') + feed_command('set hidden') + feed_command('w') + feed_command('bn') + feed_command('bp') screen:expect([[ {1:^echo} 1 | {0:~ }| @@ -63,32 +62,32 @@ describe('manual syntax highlight', function() end) it("works with buffer switch and 'nohidden'", function() - execute('e tmp1.vim') - execute('e Xtest-functional-ui-highlight.tmp.vim') - execute('filetype on') - execute('syntax manual') - execute('set ft=vim') - execute('set syntax=ON') + command('e tmp1.vim') + command('e Xtest-functional-ui-highlight.tmp.vim') + command('filetype on') + command('syntax manual') + command('set filetype=vim fileformat=unix') + command('set syntax=ON') feed('iecho 1<esc>0') - execute('set nohidden') - execute('w') - execute('bn') - execute('bp') + command('set nohidden') + command('w') + command('silent bn') + eq("tmp1.vim", eval("fnamemodify(bufname('%'), ':t')")) + feed_command('silent bp') + eq("Xtest-functional-ui-highlight.tmp.vim", eval("fnamemodify(bufname('%'), ':t')")) screen:expect([[ {1:^echo} 1 | {0:~ }| {0:~ }| {0:~ }| - <ht.tmp.vim" 1L, 7C | + :silent bp | ]]) end) end) -describe('Default highlight groups', function() - -- Test the default attributes for highlight groups shown by the :highlight - -- command +describe('highlight defaults', function() local screen before_each(function() @@ -107,7 +106,7 @@ describe('Default highlight groups', function() [1] = {reverse = true, bold = true}, -- StatusLine [2] = {reverse = true} -- StatusLineNC }) - execute('sp', 'vsp', 'vsp') + feed_command('sp', 'vsp', 'vsp') screen:expect([[ ^ {2:|} {2:|} | {0:~ }{2:|}{0:~ }{2:|}{0:~ }| @@ -200,58 +199,31 @@ describe('Default highlight groups', function() it('insert mode text', function() feed('i') + screen:try_resize(53, 4) screen:expect([[ ^ | {0:~ }| {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| {1:-- INSERT --} | ]], {[0] = {bold=true, foreground=Screen.colors.Blue}, [1] = {bold = true}}) end) it('end of file markers', function() + screen:try_resize(53, 4) screen:expect([[ ^ | {1:~ }| {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| | ]], {[1] = {bold = true, foreground = Screen.colors.Blue}}) end) it('"wait return" text', function() + screen:try_resize(53, 4) feed(':ls<cr>') screen:expect([[ {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| :ls | 1 %a "[No Name]" line 1 | {1:Press ENTER or type command to continue}^ | @@ -259,89 +231,84 @@ describe('Default highlight groups', function() [1] = {bold = true, foreground = Screen.colors.SeaGreen}}) feed('<cr>') -- skip the "Press ENTER..." state or tests will hang end) + it('can be cleared and linked to other highlight groups', function() - execute('highlight clear ModeMsg') + screen:try_resize(53, 4) + feed_command('highlight clear ModeMsg') feed('i') screen:expect([[ ^ | {0:~ }| {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| -- INSERT -- | ]], {[0] = {bold=true, foreground=Screen.colors.Blue}, [1] = {bold=true}}) feed('<esc>') - execute('highlight CustomHLGroup guifg=red guibg=green') - execute('highlight link ModeMsg CustomHLGroup') + feed_command('highlight CustomHLGroup guifg=red guibg=green') + feed_command('highlight link ModeMsg CustomHLGroup') feed('i') screen:expect([[ ^ | {0:~ }| {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| {1:-- INSERT --} | ]], {[0] = {bold=true, foreground=Screen.colors.Blue}, [1] = {foreground = Screen.colors.Red, background = Screen.colors.Green}}) end) + it('can be cleared by assigning NONE', function() - execute('syn keyword TmpKeyword neovim') - execute('hi link TmpKeyword ErrorMsg') + screen:try_resize(53, 4) + feed_command('syn keyword TmpKeyword neovim') + feed_command('hi link TmpKeyword ErrorMsg') insert('neovim') screen:expect([[ {1:neovi^m} | {0:~ }| {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| | ]], { [0] = {bold=true, foreground=Screen.colors.Blue}, [1] = {foreground = Screen.colors.White, background = Screen.colors.Red} }) - execute("hi ErrorMsg term=NONE cterm=NONE ctermfg=NONE ctermbg=NONE" + feed_command("hi ErrorMsg term=NONE cterm=NONE ctermfg=NONE ctermbg=NONE" .. " gui=NONE guifg=NONE guibg=NONE guisp=NONE") screen:expect([[ neovi^m | {0:~ }| {0:~ }| + | + ]], {[0] = {bold=true, foreground=Screen.colors.Blue}}) + end) + + it('Cursor after `:hi clear|syntax reset` #6508', function() + command('highlight clear|syntax reset') + eq('guifg=bg guibg=fg', eval([[matchstr(execute('hi Cursor'), '\v(gui|cterm).*$')]])) + end) + + it('Whitespace highlight', function() + screen:try_resize(53, 4) + feed_command('highlight NonText gui=NONE guifg=#FF0000') + feed_command('set listchars=space:.,tab:>-,trail:*,eol:¬ list') + insert(' ne \t o\tv im ') + screen:expect([[ + ne{0:.>----.}o{0:>-----}v{0:..}im{0:*^*¬} | {0:~ }| {0:~ }| + | + ]], { + [0] = {foreground=Screen.colors.Red}, + [1] = {foreground=Screen.colors.Blue}, + }) + feed_command('highlight Whitespace gui=NONE guifg=#0000FF') + screen:expect([[ + ne{1:.>----.}o{1:>-----}v{1:..}im{1:*^*}{0:¬} | {0:~ }| {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - | - ]], {[0] = {bold=true, foreground=Screen.colors.Blue}}) + :highlight Whitespace gui=NONE guifg=#0000FF | + ]], { + [0] = {foreground=Screen.colors.Red}, + [1] = {foreground=Screen.colors.Blue}, + }) end) end) @@ -355,19 +322,19 @@ describe('guisp (special/undercurl)', function() end) it('can be set and is applied like foreground or background', function() - execute('syntax on') - execute('syn keyword TmpKeyword neovim') - execute('syn keyword TmpKeyword1 special') - execute('syn keyword TmpKeyword2 specialwithbg') - execute('syn keyword TmpKeyword3 specialwithfg') - execute('hi! Awesome guifg=red guibg=yellow guisp=red') - execute('hi! Awesome1 guisp=red') - execute('hi! Awesome2 guibg=yellow guisp=red') - execute('hi! Awesome3 guifg=red guisp=red') - execute('hi link TmpKeyword Awesome') - execute('hi link TmpKeyword1 Awesome1') - execute('hi link TmpKeyword2 Awesome2') - execute('hi link TmpKeyword3 Awesome3') + feed_command('syntax on') + feed_command('syn keyword TmpKeyword neovim') + feed_command('syn keyword TmpKeyword1 special') + feed_command('syn keyword TmpKeyword2 specialwithbg') + feed_command('syn keyword TmpKeyword3 specialwithfg') + feed_command('hi! Awesome guifg=red guibg=yellow guisp=red') + feed_command('hi! Awesome1 guisp=red') + feed_command('hi! Awesome2 guibg=yellow guisp=red') + feed_command('hi! Awesome3 guifg=red guisp=red') + feed_command('hi link TmpKeyword Awesome') + feed_command('hi link TmpKeyword1 Awesome1') + feed_command('hi link TmpKeyword2 Awesome2') + feed_command('hi link TmpKeyword3 Awesome3') insert([[ neovim awesome neovim @@ -401,7 +368,7 @@ describe('guisp (special/undercurl)', function() end) end) -describe("'cursorline' with 'listchars'", function() +describe("'listchars' highlight", function() local screen before_each(function() @@ -419,8 +386,8 @@ describe("'cursorline' with 'listchars'", function() [0] = {bold=true, foreground=Screen.colors.Blue}, [1] = {background=Screen.colors.Grey90} }) - execute('highlight clear ModeMsg') - execute('set cursorline') + feed_command('highlight clear ModeMsg') + feed_command('set cursorline') feed('i') screen:expect([[ {1:^ }| @@ -445,7 +412,7 @@ describe("'cursorline' with 'listchars'", function() {0:~ }| | ]]) - execute('set nocursorline') + feed_command('set nocursorline') screen:expect([[ abcdefg | kkasd^f | @@ -469,8 +436,8 @@ describe("'cursorline' with 'listchars'", function() ^f | | ]]) - execute('set cursorline') - execute('set cursorcolumn') + feed_command('set cursorline') + feed_command('set cursorcolumn') feed('kkiabcdefghijk<esc>hh') screen:expect([[ kkasd {1: } | @@ -509,11 +476,11 @@ describe("'cursorline' with 'listchars'", function() foreground=Screen.colors.Red, }, }) - execute('highlight clear ModeMsg') - execute('highlight SpecialKey guifg=#FF0000') - execute('set cursorline') - execute('set tabstop=8') - execute('set listchars=space:.,eol:¬,tab:>-,extends:>,precedes:<,trail:* list') + feed_command('highlight clear ModeMsg') + feed_command('highlight Whitespace guifg=#FF0000') + feed_command('set cursorline') + feed_command('set tabstop=8') + feed_command('set listchars=space:.,eol:¬,tab:>-,extends:>,precedes:<,trail:* list') feed('i\t abcd <cr>\t abcd <cr><esc>k') screen:expect([[ {5:>-------.}abcd{5:*}{4:¬} | @@ -530,7 +497,7 @@ describe("'cursorline' with 'listchars'", function() {4:~ }| | ]]) - execute('set nocursorline') + feed_command('set nocursorline') screen:expect([[ {5:^>-------.}abcd{5:*}{4:¬} | {5:>-------.}abcd{5:*}{4:¬} | @@ -538,7 +505,7 @@ describe("'cursorline' with 'listchars'", function() {4:~ }| :set nocursorline | ]]) - execute('set nowrap') + feed_command('set nowrap') feed('ALorem ipsum dolor sit amet<ESC>0') screen:expect([[ {5:^>-------.}abcd{5:.}Lorem{4:>}| @@ -547,7 +514,7 @@ describe("'cursorline' with 'listchars'", function() {4:~ }| | ]]) - execute('set cursorline') + feed_command('set cursorline') screen:expect([[ {2:^>-------.}{1:abcd}{2:.}{1:Lorem}{4:>}| {5:>-------.}abcd{5:*}{4:¬} | @@ -605,12 +572,12 @@ describe("'cursorline' with 'listchars'", function() bold=true, }, }) - execute('highlight clear ModeMsg') - execute('highlight SpecialKey guifg=#FF0000') - execute('set cursorline') - execute('set tabstop=8') - execute('set nowrap') - execute('set listchars=space:.,eol:¬,tab:>-,extends:>,precedes:<,trail:* list') + feed_command('highlight clear ModeMsg') + feed_command('highlight Whitespace guifg=#FF0000') + feed_command('set cursorline') + feed_command('set tabstop=8') + feed_command('set nowrap') + feed_command('set listchars=space:.,eol:¬,tab:>-,extends:>,precedes:<,trail:* list') feed('i\t abcd <cr>\t abcd Lorem ipsum dolor sit amet<cr><esc>kkk0') screen:expect([[ {2:^>-------.}{1:abcd}{2:*}{3:¬}{1: }| @@ -644,4 +611,453 @@ describe("'cursorline' with 'listchars'", function() | ]]) end) + + it("'cursorline' with :match", function() + screen:set_default_attr_ids({ + [0] = {bold=true, foreground=Screen.colors.Blue}, + [1] = {background=Screen.colors.Grey90}, + [2] = {foreground=Screen.colors.Red}, + [3] = {foreground=Screen.colors.Green1}, + }) + feed_command('highlight clear ModeMsg') + feed_command('highlight Whitespace guifg=#FF0000') + feed_command('highlight Error guifg=#00FF00') + feed_command('set nowrap') + feed('ia \t bc \t <esc>') + screen:expect([[ + a bc ^ | + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + feed_command('set listchars=space:.,eol:¬,tab:>-,extends:>,precedes:<,trail:* list') + screen:expect([[ + a{2:.>-----.}bc{2:*>---*^*}{0:¬} | + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + feed_command('match Error /\\s\\+$/') + screen:expect([[ + a{2:.>-----.}bc{3:*>---*^*}{0:¬} | + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + end) +end) + +describe("'winhighlight' highlight", function() + local screen + + before_each(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}, + }) + command("hi Background1 guibg=DarkBlue") + command("hi Background2 guibg=DarkGreen") + end) + + it('works for background color', function() + insert("aa") + command("split") + command("set winhl=Normal:Background1") + screen:expect([[ + {1:a^a }| + {2:~ }| + {2:~ }| + {11:[No Name] [+] }| + aa | + {0:~ }| + {4:[No Name] [+] }| + | + ]]) + + command("enew") + screen:expect([[ + {1:^ }| + {2:~ }| + {2:~ }| + {11:[No Name] }| + aa | + {0:~ }| + {4:[No Name] [+] }| + | + ]]) + end) + + it('handles invalid values', function() + command("set winhl=Normal:Background1") + screen:expect([[ + {1:^ }| + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + | + ]]) + + eq('Vim(set):E474: Invalid argument: winhl=xxx:yyy', + exc_exec("set winhl=xxx:yyy")) + eq('Normal:Background1', eval('&winhl')) + screen:expect([[ + {1:^ }| + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + | + ]]) + end) + + + it('works local to the buffer', function() + insert("aa") + command("split") + command("setlocal winhl=Normal:Background1") + screen:expect([[ + {1:a^a }| + {2:~ }| + {2:~ }| + {11:[No Name] [+] }| + aa | + {0:~ }| + {4:[No Name] [+] }| + | + ]]) + + command("enew") + screen:expect([[ + ^ | + {0:~ }| + {0:~ }| + {3:[No Name] }| + aa | + {0:~ }| + {4:[No Name] [+] }| + | + ]]) + + command("bnext") + screen:expect([[ + {1:^aa }| + {2:~ }| + {2:~ }| + {11:[No Name] [+] }| + aa | + {0:~ }| + {4:[No Name] [+] }| + <f 1 --100%-- col 1 | + ]]) + end) + + it('for inactive window background works', function() + command("set winhl=Normal:Background1,NormalNC:Background2") + -- tests global value is copied across split + command("split") + screen:expect([[ + {1:^ }| + {2:~ }| + {2:~ }| + {11:[No Name] }| + {5: }| + {6:~ }| + {12:[No Name] }| + | + ]]) + + feed("<c-w><c-w>") + screen:expect([[ + {5: }| + {6:~ }| + {6:~ }| + {12:[No Name] }| + {1:^ }| + {2:~ }| + {11:[No Name] }| + | + ]]) + + feed("<c-w><c-w>") + screen:expect([[ + {1:^ }| + {2:~ }| + {2:~ }| + {11:[No Name] }| + {5: }| + {6:~ }| + {12:[No Name] }| + | + ]]) + end) + + it('works with NormalNC', function() + command("hi NormalNC guibg=DarkMagenta") + -- tests global value is copied across split + command("split") + screen:expect([[ + ^ | + {0:~ }| + {0:~ }| + {3:[No Name] }| + {7: }| + {8:~ }| + {13:[No Name] }| + | + ]]) + + command("wincmd w") + screen:expect([[ + {7: }| + {8:~ }| + {8:~ }| + {13:[No Name] }| + ^ | + {0:~ }| + {3:[No Name] }| + | + ]]) + + + -- winbg=Normal:... overrides global NormalNC + command("set winhl=Normal:Background1") + screen:expect([[ + {7: }| + {8:~ }| + {8:~ }| + {13:[No Name] }| + {1:^ }| + {2:~ }| + {11:[No Name] }| + | + ]]) + + command("wincmd w") + screen:expect([[ + ^ | + {0:~ }| + {0:~ }| + {3:[No Name] }| + {1: }| + {2:~ }| + {14:[No Name] }| + | + ]]) + + command("wincmd w") + command("set winhl=Normal:Background1,NormalNC:Background2") + screen:expect([[ + {7: }| + {8:~ }| + {8:~ }| + {13:[No Name] }| + {1:^ }| + {2:~ }| + {11:[No Name] }| + | + ]]) + + command("wincmd w") + screen:expect([[ + ^ | + {0:~ }| + {0:~ }| + {3:[No Name] }| + {5: }| + {6:~ }| + {12:[No Name] }| + | + ]]) + end) + + it('background applies also to non-text', function() + insert('Lorem ipsum dolor sit amet ') + command('set shiftwidth=2') + feed('>>') + command('set number') + command('set breakindent') + command('set briopt=shift:5,min:0') + command('set list') + command('set showbreak=↪') + screen:expect([[ + {9: 1 } ^Lorem ipsum do| + {9: } {0:↪}lor sit | + {9: } {0:↪}amet{0:-} | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + + command('set winhl=Normal:Background1') + screen:expect([[ + {10: 1 }{1: ^Lorem ipsum do}| + {10: }{1: }{2:↪}{1:lor sit }| + {10: }{1: }{2:↪}{1:amet}{2:-}{1: }| + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + | + ]]) + + command('set nowrap') + command('set listchars+=extends:❯,precedes:❮') + feed('3w') + screen:expect([[ + {10: 1 }{2:❮}{1: dolor ^sit ame}{2:❯}| + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + | + ]]) + end) + + it('can override NonText, Conceal and EndOfBuffer', function() + curbufmeths.set_lines(0,-1,true, {"raa\000"}) + command('call matchaddpos("Conceal", [[1,2]], 0, -1, {"conceal": "#"})') + command('set cole=2 cocu=nvic') + command('split') + command('call matchaddpos("Conceal", [[1,2]], 0, -1, {"conceal": "#"})') + command('set winhl=SpecialKey:ErrorMsg,EndOfBuffer:Background1,' + ..'Conceal:Background2') + + screen:expect([[ + ^r{5:#}a{15:^@} | + {1:~ }| + {1:~ }| + {3:[No Name] [+] }| + r{19:#}a{16:^@} | + {0:~ }| + {4:[No Name] [+] }| + | + ]]) + end) + + it('can override LineNr, CursorColumn and ColorColumn', function() + insert('very text\nmore text') + command('set number') + command('set colorcolumn=2') + command('set cursorcolumn') + + command('split') + command('set winhl=LineNr:Background1,CursorColumn:Background2,' + ..'ColorColumn:ErrorMsg') + screen:expect([[ + {1: 1 }v{15:e}ry tex{5:t} | + {1: 2 }m{15:o}re tex^t | + {0:~ }| + {3:[No Name] [+] }| + {9: 1 }v{17:e}ry tex{18:t} | + {9: 2 }m{17:o}re text | + {4:[No Name] [+] }| + | + ]]) + end) + + it('can override Tabline', function() + command('tabnew') + command('set winhl=TabLine:Background1,TabLineSel:ErrorMsg') + + screen:expect([[ + {20: No Name] }{15: No Name]}{20:X}| + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + command("tabnext") + screen:expect([[ + {21: No Name] }{1: No Name]}{20:X}| + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + end) + + it('can override popupmenu', function() + insert('word wording wordy') + command('split') + command('set winhl=Pmenu:Background1,PmenuSel:Background2,' + ..'PmenuSbar:ErrorMsg,PmenuThumb:Normal') + screen:expect([[ + word wording word^y | + {0:~ }| + {0:~ }| + {3:[No Name] [+] }| + word wording wordy | + {0:~ }| + {4:[No Name] [+] }| + | + ]]) + + feed('oword<c-x><c-p>') + screen:expect([[ + word wording wordy | + wordy^ | + {1:word }{0: }| + {1:wording }{3: }| + {5:wordy }rdy | + wordy | + {4:[No Name] [+] }| + {21:-- }{22:match 1 of 3} | + ]]) + + feed('<esc>u<c-w><c-w>oword<c-x><c-p>') + screen:expect([[ + word wording wordy | + wordy | + {23:word }{0: }| + {23:wording }{4: }| + {24:wordy }rdy | + wordy^ | + {3:[No Name] [+] }| + {21:-- }{22:match 1 of 3} | + ]]) + end) end) diff --git a/test/functional/ui/inccommand_spec.lua b/test/functional/ui/inccommand_spec.lua index 3b31da0397..53fd17c309 100644 --- a/test/functional/ui/inccommand_spec.lua +++ b/test/functional/ui/inccommand_spec.lua @@ -1,10 +1,11 @@ local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') local clear = helpers.clear +local command = helpers.command local curbufmeths = helpers.curbufmeths local eq = helpers.eq local eval = helpers.eval -local execute = helpers.execute +local feed_command = helpers.feed_command local expect = helpers.expect local feed = helpers.feed local insert = helpers.insert @@ -13,17 +14,54 @@ local neq = helpers.neq local ok = helpers.ok local source = helpers.source local wait = helpers.wait +local nvim = helpers.nvim local default_text = [[ Inc substitution on two lines ]] +local multiline_text = [[ + 1 2 3 + A B C + 4 5 6 + X Y Z + 7 8 9 +]] + +local multimatch_text = [[ + a bdc eae a fgl lzia r + x +]] + +local multibyte_text = [[ + £ ¥ ѫѫ PEPPERS +£ ¥ ѫfѫ + a£ ѫ¥KOL +£ ¥ libm +£ ¥ +]] + +local long_multiline_text = [[ + 1 2 3 + A B C + 4 5 6 + X Y Z + 7 8 9 + K L M + a b c + d e f + q r s + x y z + £ m n + t œ ¥ +]] + local function common_setup(screen, inccommand, text) if screen then - execute("syntax on") - execute("set nohlsearch") - execute("hi Substitute guifg=red guibg=yellow") + command("syntax on") + command("set nohlsearch") + command("hi Substitute guifg=red guibg=yellow") screen:attach() screen:set_default_attr_ids({ [1] = {foreground = Screen.colors.Fuchsia}, @@ -46,7 +84,7 @@ local function common_setup(screen, inccommand, text) }) end - execute("set inccommand=" .. (inccommand and inccommand or "")) + command("set inccommand=" .. (inccommand and inccommand or "")) if text then insert(text) @@ -91,8 +129,8 @@ describe(":substitute, 'inccommand' preserves", function() local screen = Screen.new(30,10) common_setup(screen, "split", "ABC") - execute("%s/AB/BA/") - execute("ls") + feed_command("%s/AB/BA/") + feed_command("ls") screen:expect([[ {15:~ }| @@ -111,25 +149,25 @@ describe(":substitute, 'inccommand' preserves", function() for _, case in pairs{"", "split", "nosplit"} do it("various delimiters (inccommand="..case..")", function() insert(default_text) - execute("set inccommand=" .. case) + feed_command("set inccommand=" .. case) local delims = { '/', '#', ';', '%', ',', '@', '!', ''} for _,delim in pairs(delims) do - execute("%s"..delim.."lines"..delim.."LINES"..delim.."g") + feed_command("%s"..delim.."lines"..delim.."LINES"..delim.."g") expect([[ Inc substitution on two LINES ]]) - execute("undo") + feed_command("undo") end end) end for _, case in pairs{"", "split", "nosplit"} do it("'undolevels' (inccommand="..case..")", function() - execute("set undolevels=139") - execute("setlocal undolevels=34") - execute("set inccommand=" .. case) + feed_command("set undolevels=139") + feed_command("setlocal undolevels=34") + feed_command("set inccommand=" .. case) insert("as") feed(":%s/as/glork/<enter>") eq(meths.get_option('undolevels'), 139) @@ -139,8 +177,8 @@ describe(":substitute, 'inccommand' preserves", function() for _, case in ipairs({"", "split", "nosplit"}) do it("empty undotree() (inccommand="..case..")", function() - execute("set undolevels=1000") - execute("set inccommand=" .. case) + feed_command("set undolevels=1000") + feed_command("set inccommand=" .. case) local expected_undotree = eval("undotree()") -- Start typing an incomplete :substitute command. @@ -157,8 +195,8 @@ describe(":substitute, 'inccommand' preserves", function() for _, case in ipairs({"", "split", "nosplit"}) do it("undotree() with branches (inccommand="..case..")", function() - execute("set undolevels=1000") - execute("set inccommand=" .. case) + feed_command("set undolevels=1000") + feed_command("set inccommand=" .. case) -- Make some changes. feed([[isome text 1<C-\><C-N>]]) feed([[osome text 2<C-\><C-N>]]) @@ -192,7 +230,7 @@ describe(":substitute, 'inccommand' preserves", function() for _, case in pairs{"", "split", "nosplit"} do it("b:changedtick (inccommand="..case..")", function() - execute("set inccommand=" .. case) + feed_command("set inccommand=" .. case) feed([[isome text 1<C-\><C-N>]]) feed([[osome text 2<C-\><C-N>]]) local expected_tick = eval("b:changedtick") @@ -271,18 +309,18 @@ describe(":substitute, 'inccommand' preserves undo", function() local function test_sub(substring, split, redoable) clear() - execute("set inccommand=" .. split) + feed_command("set inccommand=" .. split) insert("1") feed("o2<esc>") - execute("undo") + feed_command("undo") feed("o3<esc>") if redoable then feed("o4<esc>") - execute("undo") + feed_command("undo") end feed(substring.. "<enter>") - execute("undo") + feed_command("undo") feed("g-") expect([[ @@ -297,15 +335,15 @@ describe(":substitute, 'inccommand' preserves undo", function() local function test_notsub(substring, split, redoable) clear() - execute("set inccommand=" .. split) + feed_command("set inccommand=" .. split) insert("1") feed("o2<esc>") - execute("undo") + feed_command("undo") feed("o3<esc>") if redoable then feed("o4<esc>") - execute("undo") + feed_command("undo") end feed(substring .. "<esc>") @@ -331,7 +369,7 @@ describe(":substitute, 'inccommand' preserves undo", function() local function test_threetree(substring, split) clear() - execute("set inccommand=" .. split) + feed_command("set inccommand=" .. split) insert("1") feed("o2<esc>") @@ -424,22 +462,22 @@ describe(":substitute, 'inccommand' preserves undo", function() for _, case in pairs(cases) do clear() common_setup(nil, case, default_text) - execute("set undolevels=0") + feed_command("set undolevels=0") feed("1G0") insert("X") feed(":%s/tw/MO/<esc>") - execute("undo") + feed_command("undo") expect(default_text) - execute("undo") + feed_command("undo") expect(default_text:gsub("Inc", "XInc")) - execute("undo") + feed_command("undo") - execute("%s/tw/MO/g") + feed_command("%s/tw/MO/g") expect(default_text:gsub("tw", "MO")) - execute("undo") + feed_command("undo") expect(default_text) - execute("undo") + feed_command("undo") expect(default_text:gsub("tw", "MO")) end end) @@ -450,13 +488,13 @@ describe(":substitute, 'inccommand' preserves undo", function() for _, case in pairs(cases) do clear() common_setup(screen, case, default_text) - execute("set undolevels=1") + feed_command("set undolevels=1") feed("1G0") insert("X") feed("IY<esc>") feed(":%s/tw/MO/<esc>") - -- execute("undo") here would cause "Press ENTER". + -- feed_command("undo") here would cause "Press ENTER". feed("u") expect(default_text:gsub("Inc", "XInc")) feed("u") @@ -508,13 +546,13 @@ describe(":substitute, 'inccommand' preserves undo", function() for _, case in pairs(cases) do clear() common_setup(screen, case, default_text) - execute("set undolevels=2") + feed_command("set undolevels=2") feed("2GAx<esc>") feed("Ay<esc>") feed("Az<esc>") feed(":%s/tw/AR<esc>") - -- using execute("undo") here will result in a "Press ENTER" prompt + -- feed_command("undo") here would cause "Press ENTER". feed("u") expect(default_text:gsub("lines", "linesxy")) feed("u") @@ -601,9 +639,9 @@ describe(":substitute, 'inccommand' preserves undo", function() clear() common_setup(screen, case, default_text) - execute("set undolevels=-1") + feed_command("set undolevels=-1") feed(":%s/tw/MO/g<enter>") - -- using execute("undo") here will result in a "Press ENTER" prompt + -- feed_command("undo") here will result in a "Press ENTER" prompt feed("u") if case == "split" then screen:expect([[ @@ -637,7 +675,7 @@ describe(":substitute, 'inccommand' preserves undo", function() clear() common_setup(screen, case, default_text) - execute("set undolevels=-1") + feed_command("set undolevels=-1") feed("1G") feed("IL<esc>") feed(":%s/tw/MO/g<esc>") @@ -676,18 +714,18 @@ describe(":substitute, inccommand=split", function() end) it("preserves 'modified' buffer flag", function() - execute("set nomodified") + feed_command("set nomodified") feed(":%s/tw") screen:expect([[ Inc substitution on | - two lines | + {12:tw}o lines | | {15:~ }| {15:~ }| {11:[No Name] }| - |2| two lines | - |4| two lines | - | + |2| {12:tw}o lines | + |4| {12:tw}o lines | + {15:~ }| {15:~ }| {15:~ }| {15:~ }| @@ -699,18 +737,37 @@ describe(":substitute, inccommand=split", function() eq(0, eval("&modified")) end) + it("shows preview when cmd modifiers are present", function() + -- one modifier + feed(':keeppatterns %s/tw/to') + screen:expect([[{12:to}o lines]], nil, nil, nil, true) + feed('<Esc>') + screen:expect([[two lines]], nil, nil, nil, true) + + -- multiple modifiers + feed(':keeppatterns silent %s/tw/to') + screen:expect([[{12:to}o lines]], nil, nil, nil, true) + feed('<Esc>') + screen:expect([[two lines]], nil, nil, nil, true) + + -- non-modifier prefix + feed(':silent tabedit %s/tw/to') + screen:expect([[two lines]], nil, nil, nil, true) + feed('<Esc>') + end) + it('shows split window when typing the pattern', function() feed(":%s/tw") screen:expect([[ Inc substitution on | - two lines | + {12:tw}o lines | | {15:~ }| {15:~ }| {11:[No Name] [+] }| - |2| two lines | - |4| two lines | - | + |2| {12:tw}o lines | + |4| {12:tw}o lines | + {15:~ }| {15:~ }| {15:~ }| {15:~ }| @@ -731,7 +788,7 @@ describe(":substitute, inccommand=split", function() {11:[No Name] [+] }| |2| o lines | |4| o lines | - | + {15:~ }| {15:~ }| {15:~ }| {15:~ }| @@ -743,14 +800,14 @@ describe(":substitute, inccommand=split", function() feed("x") screen:expect([[ Inc substitution on | - xo lines | + {12:x}o lines | | {15:~ }| {15:~ }| {11:[No Name] [+] }| |2| {12:x}o lines | |4| {12:x}o lines | - | + {15:~ }| {15:~ }| {15:~ }| {15:~ }| @@ -769,7 +826,7 @@ describe(":substitute, inccommand=split", function() {11:[No Name] [+] }| |2| o lines | |4| o lines | - | + {15:~ }| {15:~ }| {15:~ }| {15:~ }| @@ -784,14 +841,14 @@ describe(":substitute, inccommand=split", function() feed(":%s/tw/XX") screen:expect([[ Inc substitution on | - XXo lines | + {12:XX}o lines | | {15:~ }| {15:~ }| {11:[No Name] [+] }| |2| {12:XX}o lines | |4| {12:XX}o lines | - | + {15:~ }| {15:~ }| {15:~ }| {15:~ }| @@ -804,10 +861,10 @@ describe(":substitute, inccommand=split", function() it('does not show split window for :s/', function() feed("2gg") feed(":s/tw") - wait() + screen:sleep(1) screen:expect([[ Inc substitution on | - two lines | + {12:tw}o lines | Inc substitution on | two lines | | @@ -825,7 +882,7 @@ describe(":substitute, inccommand=split", function() end) it("'hlsearch' is active, 'cursorline' is not", function() - execute("set hlsearch cursorline") + feed_command("set hlsearch cursorline") feed("gg") -- Assert that 'cursorline' is active. @@ -851,14 +908,14 @@ describe(":substitute, inccommand=split", function() -- 'cursorline' is NOT active during preview. screen:expect([[ Inc substitution on | - {9:tw}o lines | + {12:tw}o lines | Inc substitution on | - {9:tw}o lines | + {12:tw}o lines | | {11:[No Name] [+] }| - |2| {9:tw}o lines | - |4| {9:tw}o lines | - | + |2| {12:tw}o lines | + |4| {12:tw}o lines | + {15:~ }| {15:~ }| {15:~ }| {15:~ }| @@ -873,14 +930,14 @@ describe(":substitute, inccommand=split", function() feed('M M M<esc>') feed(':%s/M/123/g') screen:expect([[ - 123 123 123 | + {12:123} {12:123} {12:123} | Inc substitution on | two lines | Inc substitution on | two lines | {11:[No Name] [+] }| |1| {12:123} {12:123} {12:123} | - | + {15:~ }| {15:~ }| {15:~ }| {15:~ }| @@ -891,6 +948,53 @@ describe(":substitute, inccommand=split", function() ]]) end) + it("highlights nothing when there's no match", function() + feed('gg') + feed(':%s/Inx') + screen:expect([[ + Inc substitution on | + two lines | + Inc substitution on | + two lines | + | + {11:[No Name] [+] }| + | + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {10:[Preview] }| + :%s/Inx^ | + ]]) + end) + + it('previews correctly when previewhight is small', function() + feed_command('set cwh=3') + feed_command('set hls') + feed('ggdG') + insert(string.rep('abc abc abc\n', 20)) + feed(':%s/abc/MMM/g') + screen:expect([[ + {12:MMM} {12:MMM} {12:MMM} | + {12:MMM} {12:MMM} {12:MMM} | + {12:MMM} {12:MMM} {12:MMM} | + {12:MMM} {12:MMM} {12:MMM} | + {12:MMM} {12:MMM} {12:MMM} | + {12:MMM} {12:MMM} {12:MMM} | + {12:MMM} {12:MMM} {12:MMM} | + {12:MMM} {12:MMM} {12:MMM} | + {12:MMM} {12:MMM} {12:MMM} | + {11:[No Name] [+] }| + | 1| {12:MMM} {12:MMM} {12:MMM} | + | 2| {12:MMM} {12:MMM} {12:MMM} | + | 3| {12:MMM} {12:MMM} {12:MMM} | + {10:[Preview] }| + :%s/abc/MMM/g^ | + ]]) + end) + it('actually replaces text', function() feed(":%s/tw/XX/g<Enter>") @@ -917,15 +1021,15 @@ describe(":substitute, inccommand=split", function() feed("gg") feed("2yy") feed("2000p") - execute("1,1000s/tw/BB/g") + feed_command("1,1000s/tw/BB/g") feed(":%s/tw/X") screen:expect([[ BBo lines | Inc substitution on | - Xo lines | + {12:X}o lines | Inc substitution on | - Xo lines | + {12:X}o lines | {11:[No Name] [+] }| |1001| {12:X}o lines | |1003| {12:X}o lines | @@ -983,9 +1087,9 @@ describe(":substitute, inccommand=split", function() -- Assert that 'inccommand' is ENABLED initially. eq("split", eval("&inccommand")) -- Set 'redrawtime' to minimal value, to ensure timeout is triggered. - execute("set redrawtime=1 nowrap") + feed_command("set redrawtime=1 nowrap") -- Load a big file. - execute("silent edit! test/functional/fixtures/bigfile.txt") + feed_command("silent edit! test/functional/fixtures/bigfile_oneline.txt") -- Start :substitute with a slow pattern. feed([[:%s/B.*N/x]]) wait() @@ -995,19 +1099,19 @@ describe(":substitute, inccommand=split", function() -- Assert that preview cleared (or never manifested). screen:expect([[ 0000;<control>;Cc;0;BN;;;;;N;N| - 0001;<control>;Cc;0;BN;;;;;N;S| - 0002;<control>;Cc;0;BN;;;;;N;S| - 0003;<control>;Cc;0;BN;;;;;N;E| - 0004;<control>;Cc;0;BN;;;;;N;E| - 0005;<control>;Cc;0;BN;;;;;N;E| - 0006;<control>;Cc;0;BN;;;;;N;A| - 0007;<control>;Cc;0;BN;;;;;N;B| - 0008;<control>;Cc;0;BN;;;;;N;B| - 0009;<control>;Cc;0;S;;;;;N;CH| - 000A;<control>;Cc;0;B;;;;;N;LI| - 000B;<control>;Cc;0;S;;;;;N;LI| - 000C;<control>;Cc;0;WS;;;;;N;F| - 000D;<control>;Cc;0;B;;;;;N;CA| + 2F923;CJK COMPATIBILITY IDEOGR| + 2F924;CJK COMPATIBILITY IDEOGR| + 2F925;CJK COMPATIBILITY IDEOGR| + 2F926;CJK COMPATIBILITY IDEOGR| + 2F927;CJK COMPATIBILITY IDEOGR| + 2F928;CJK COMPATIBILITY IDEOGR| + 2F929;CJK COMPATIBILITY IDEOGR| + 2F92A;CJK COMPATIBILITY IDEOGR| + 2F92B;CJK COMPATIBILITY IDEOGR| + 2F92C;CJK COMPATIBILITY IDEOGR| + 2F92D;CJK COMPATIBILITY IDEOGR| + 2F92E;CJK COMPATIBILITY IDEOGR| + 2F92F;CJK COMPATIBILITY IDEOGR| :%s/B.*N/x^ | ]]) @@ -1018,20 +1122,20 @@ describe(":substitute, inccommand=split", function() it("clears preview if non-previewable command is edited #5585", function() -- Put a non-previewable command in history. - execute("echo 'foo'") + feed_command("echo 'foo'") -- Start an incomplete :substitute command. feed(":1,2s/t/X") screen:expect([[ - Inc subsXitution on | - Xwo lines | + Inc subs{12:X}itution on | + {12:X}wo lines | Inc substitution on | two lines | | {11:[No Name] [+] }| |1| Inc subs{12:X}itution on | |2| {12:X}wo lines | - | + {15:~ }| {15:~ }| {15:~ }| {15:~ }| @@ -1079,7 +1183,7 @@ describe("inccommand=nosplit", function() end) it("works with :smagic, :snomagic", function() - execute("set hlsearch") + feed_command("set hlsearch") insert("Line *.3.* here") feed(":%smagic/3.*/X") -- start :smagic command @@ -1088,7 +1192,7 @@ describe("inccommand=nosplit", function() two lines | Inc substitution on | two lines | - Line *.X | + Line *.{12:X} | {15:~ }| {15:~ }| {15:~ }| @@ -1096,7 +1200,6 @@ describe("inccommand=nosplit", function() :%smagic/3.*/X^ | ]]) - feed([[<C-\><C-N>]]) -- cancel feed(":%snomagic/3.*/X") -- start :snomagic command screen:expect([[ @@ -1104,7 +1207,7 @@ describe("inccommand=nosplit", function() two lines | Inc substitution on | two lines | - Line *.X here | + Line *.{12:X} here | {15:~ }| {15:~ }| {15:~ }| @@ -1113,15 +1216,57 @@ describe("inccommand=nosplit", function() ]]) end) + it("shows preview when cmd modifiers are present", function() + -- one modifier + feed(':keeppatterns %s/tw/to') + screen:expect([[{12:to}o lines]], nil, nil, nil, true) + feed('<Esc>') + screen:expect([[two lines]], nil, nil, nil, true) + + -- multiple modifiers + feed(':keeppatterns silent %s/tw/to') + screen:expect([[{12:to}o lines]], nil, nil, nil, true) + feed('<Esc>') + screen:expect([[two lines]], nil, nil, nil, true) + + -- non-modifier prefix + feed(':silent tabedit %s/tw/to') + screen:expect([[two lines]], nil, nil, nil, true) + feed('<Esc>') + end) + + it("does not show window after toggling :set inccommand", function() + feed(":%s/tw/OKOK") + feed("<Esc>") + command("set icm=split") + feed(":%s/tw/OKOK") + feed("<Esc>") + command("set icm=nosplit") + feed(":%s/tw/OKOK") + wait() + screen:expect([[ + Inc substitution on | + {12:OKOK}o lines | + Inc substitution on | + {12:OKOK}o lines | + | + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + :%s/tw/OKOK^ | + ]]) + end) + it('never shows preview buffer', function() - execute("set hlsearch") + feed_command("set hlsearch") feed(":%s/tw") screen:expect([[ Inc substitution on | - {9:tw}o lines | + {12:tw}o lines | Inc substitution on | - {9:tw}o lines | + {12:tw}o lines | | {15:~ }| {15:~ }| @@ -1133,9 +1278,9 @@ describe("inccommand=nosplit", function() feed("/BM") screen:expect([[ Inc substitution on | - BMo lines | + {12:BM}o lines | Inc substitution on | - BMo lines | + {12:BM}o lines | | {15:~ }| {15:~ }| @@ -1147,9 +1292,9 @@ describe("inccommand=nosplit", function() feed("/") screen:expect([[ Inc substitution on | - BMo lines | + {12:BM}o lines | Inc substitution on | - BMo lines | + {12:BM}o lines | | {15:~ }| {15:~ }| @@ -1175,13 +1320,13 @@ describe("inccommand=nosplit", function() it("clears preview if non-previewable command is edited", function() -- Put a non-previewable command in history. - execute("echo 'foo'") + feed_command("echo 'foo'") -- Start an incomplete :substitute command. feed(":1,2s/t/X") screen:expect([[ - Inc subsXitution on | - Xwo lines | + Inc subs{12:X}itution on | + {12:X}wo lines | Inc substitution on | two lines | | @@ -1224,7 +1369,7 @@ describe(":substitute, 'inccommand' with a failing expression", function() it('in the pattern does nothing', function() for _, case in pairs(cases) do refresh(case) - execute("set inccommand=" .. case) + feed_command("set inccommand=" .. case) feed(":silent! %s/tw\\(/LARD/<enter>") expect(default_text) end @@ -1236,10 +1381,10 @@ describe(":substitute, 'inccommand' with a failing expression", function() local replacements = { "\\='LARD", "\\=xx_novar__xx" } for _, repl in pairs(replacements) do - execute("set inccommand=" .. case) + feed_command("set inccommand=" .. case) feed(":silent! %s/tw/" .. repl .. "/<enter>") expect(default_text:gsub("tw", "")) - execute("undo") + feed_command("undo") end end end) @@ -1291,14 +1436,14 @@ describe("'inccommand' and :cnoremap", function() it('work with remapped characters', function() for _, case in pairs(cases) do refresh(case) - local command = "%s/lines/LINES/g" + local cmd = "%s/lines/LINES/g" - for i = 1, string.len(command) do - local c = string.sub(command, i, i) - execute("cnoremap ".. c .. " " .. c) + for i = 1, string.len(cmd) do + local c = string.sub(cmd, i, i) + feed_command("cnoremap ".. c .. " " .. c) end - execute(command) + feed_command(cmd) expect([[ Inc substitution on two LINES @@ -1309,7 +1454,7 @@ describe("'inccommand' and :cnoremap", function() it('work when mappings move the cursor', function() for _, case in pairs(cases) do refresh(case) - execute("cnoremap ,S LINES/<left><left><left><left><left><left>") + feed_command("cnoremap ,S LINES/<left><left><left><left><left><left>") feed(":%s/lines/,Sor three <enter>") expect([[ @@ -1317,21 +1462,21 @@ describe("'inccommand' and :cnoremap", function() two or three LINES ]]) - execute("cnoremap ;S /X/<left><left><left>") + feed_command("cnoremap ;S /X/<left><left><left>") feed(":%s/;SI<enter>") expect([[ Xnc substitution on two or three LXNES ]]) - execute("cnoremap ,T //Y/<left><left><left>") + feed_command("cnoremap ,T //Y/<left><left><left>") feed(":%s,TX<enter>") expect([[ Ync substitution on two or three LYNES ]]) - execute("cnoremap ;T s//Z/<left><left><left>") + feed_command("cnoremap ;T s//Z/<left><left><left>") feed(":%;TY<enter>") expect([[ Znc substitution on @@ -1343,7 +1488,7 @@ describe("'inccommand' and :cnoremap", function() it('does not work with a failing mapping', function() for _, case in pairs(cases) do refresh(case) - execute("cnoremap <expr> x execute('bwipeout!')[-1].'x'") + feed_command("cnoremap <expr> x execute('bwipeout!')[-1].'x'") feed(":%s/tw/tox<enter>") @@ -1356,7 +1501,7 @@ describe("'inccommand' and :cnoremap", function() it('work when temporarily moving the cursor', function() for _, case in pairs(cases) do refresh(case) - execute("cnoremap <expr> x cursor(1, 1)[-1].'x'") + feed_command("cnoremap <expr> x cursor(1, 1)[-1].'x'") feed(":%s/tw/tox/g<enter>") expect(default_text:gsub("tw", "tox")) @@ -1366,7 +1511,7 @@ describe("'inccommand' and :cnoremap", function() it("work when a mapping disables 'inccommand'", function() for _, case in pairs(cases) do refresh(case) - execute("cnoremap <expr> x execute('set inccommand=')[-1]") + feed_command("cnoremap <expr> x execute('set inccommand=')[-1]") feed(":%s/tw/toxa/g<enter>") expect(default_text:gsub("tw", "toa")) @@ -1440,7 +1585,7 @@ describe("'inccommand' autocommands", function() local function register_autocmd(event) meths.set_var(event .. "_fired", {}) - execute("autocmd " .. event .. " * call add(g:" .. event .. "_fired, expand('<abuf>'))") + feed_command("autocmd " .. event .. " * call add(g:" .. event .. "_fired, expand('<abuf>'))") end it('are not fired when splitting', function() @@ -1493,12 +1638,12 @@ describe("'inccommand' split windows", function() refresh() feed("gg") - execute("vsplit") - execute("split") + feed_command("vsplit") + feed_command("split") feed(":%s/tw") screen:expect([[ Inc substitution on {10:|}Inc substitution on| - two lines {10:|}two lines | + {12:tw}o lines {10:|}{12:tw}o lines | {10:|} | {15:~ }{10:|}{15:~ }| {15:~ }{10:|}{15:~ }| @@ -1513,13 +1658,13 @@ describe("'inccommand' split windows", function() {15:~ }{10:|}{15:~ }| {11:[No Name] [+] }{10:|}{15:~ }| Inc substitution on {10:|}{15:~ }| - two lines {10:|}{15:~ }| + {12:tw}o lines {10:|}{15:~ }| {10:|}{15:~ }| {15:~ }{10:|}{15:~ }| {15:~ }{10:|}{15:~ }| {10:[No Name] [+] [No Name] [+] }| - |2| two lines | - | + |2| {12:tw}o lines | + {15:~ }| {15:~ }| {15:~ }| {15:~ }| @@ -1530,14 +1675,14 @@ describe("'inccommand' split windows", function() ]]) feed("<esc>") - execute("only") - execute("split") - execute("vsplit") + feed_command("only") + feed_command("split") + feed_command("vsplit") feed(":%s/tw") screen:expect([[ Inc substitution on {10:|}Inc substitution on| - two lines {10:|}two lines | + {12:tw}o lines {10:|}{12:tw}o lines | {10:|} | {15:~ }{10:|}{15:~ }| {15:~ }{10:|}{15:~ }| @@ -1552,13 +1697,13 @@ describe("'inccommand' split windows", function() {15:~ }{10:|}{15:~ }| {11:[No Name] [+] }{10:[No Name] [+] }| Inc substitution on | - two lines | + {12:tw}o lines | | {15:~ }| {15:~ }| {10:[No Name] [+] }| - |2| two lines | - | + |2| {12:tw}o lines | + {15:~ }| {15:~ }| {15:~ }| {15:~ }| @@ -1581,13 +1726,13 @@ describe("'inccommand' split windows", function() it("are not affected by various settings", function() for _, setting in pairs(settings) do refresh() - execute("set " .. setting) + feed_command("set " .. setting) feed(":%s/tw") screen:expect([[ Inc substitution on | - two lines | + {12:tw}o lines | | {15:~ }| {15:~ }| @@ -1607,8 +1752,8 @@ describe("'inccommand' split windows", function() {15:~ }| {15:~ }| {11:[No Name] [+] }| - |2| two lines | - | + |2| {12:tw}o lines | + {15:~ }| {15:~ }| {15:~ }| {15:~ }| @@ -1621,3 +1766,693 @@ describe("'inccommand' split windows", function() end) end) + +describe("'inccommand' with 'gdefault'", function() + before_each(function() + clear() + end) + + it("does not lock up #7244", function() + common_setup(nil, "nosplit", "{") + command("set gdefault") + feed(":s/{\\n") + eq({mode='c', blocking=false}, nvim("get_mode")) + feed("/A<Enter>") + expect("A") + eq({mode='n', blocking=false}, nvim("get_mode")) + end) + + it("with multiline text and range, does not lock up #7244", function() + common_setup(nil, "nosplit", "{\n\n{") + command("set gdefault") + feed(":%s/{\\n") + eq({mode='c', blocking=false}, nvim("get_mode")) + feed("/A<Enter>") + expect("A\nA") + eq({mode='n', blocking=false}, nvim("get_mode")) + end) + + it("does not crash on zero-width matches #7485", function() + common_setup(nil, "split", default_text) + command("set gdefault") + feed("gg") + feed("Vj") + feed(":s/\\%V") + eq({mode='c', blocking=false}, nvim("get_mode")) + feed("<Esc>") + eq({mode='n', blocking=false}, nvim("get_mode")) + end) + + it("removes highlights after abort for a zero-width match", function() + local screen = Screen.new(30,5) + common_setup(screen, "nosplit", default_text) + command("set gdefault") + + feed(":%s/\\%1c/a/") + screen:expect([[ + {12:a}Inc substitution on | + {12:a}two lines | + {12:a} | + {15:~ }| + :%s/\%1c/a/^ | + ]]) + + feed("<Esc>") + screen:expect([[ + Inc substitution on | + two lines | + ^ | + {15:~ }| + | + ]]) + end) + +end) + +describe(":substitute", function() + local screen = Screen.new(30,15) + + before_each(function() + clear() + end) + + it(", inccommand=split, highlights multiline substitutions", function() + common_setup(screen, "split", multiline_text) + feed("gg") + + feed(":%s/2\\_.*X") + screen:expect([[ + 1 {12:2 3} | + {12:A B C} | + {12:4 5 6} | + {12:X} Y Z | + 7 8 9 | + {11:[No Name] [+] }| + |1| 1 {12:2 3} | + |2|{12: A B C} | + |3|{12: 4 5 6} | + |4|{12: X} Y Z | + {15:~ }| + {15:~ }| + {15:~ }| + {10:[Preview] }| + :%s/2\_.*X^ | + ]]) + + feed("/MMM") + screen:expect([[ + 1 {12:MMM} Y Z | + 7 8 9 | + | + {15:~ }| + {15:~ }| + {11:[No Name] [+] }| + |1| 1 {12:MMM} Y Z | + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {10:[Preview] }| + :%s/2\_.*X/MMM^ | + ]]) + + feed("\\rK\\rLLL") + screen:expect([[ + 1 {12:MMM} | + {12:K} | + {12:LLL} Y Z | + 7 8 9 | + | + {11:[No Name] [+] }| + |1| 1 {12:MMM} | + |2|{12: K} | + |3|{12: LLL} Y Z | + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {10:[Preview] }| + :%s/2\_.*X/MMM\rK\rLLL^ | + ]]) + end) + + it(", inccommand=nosplit, highlights multiline substitutions", function() + common_setup(screen, "nosplit", multiline_text) + feed("gg") + + feed(":%s/2\\_.*X/MMM") + screen:expect([[ + 1 {12:MMM} Y Z | + 7 8 9 | + | + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + :%s/2\_.*X/MMM^ | + ]]) + + feed("\\rK\\rLLL") + screen:expect([[ + 1 {12:MMM} | + {12:K} | + {12:LLL} Y Z | + 7 8 9 | + | + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + :%s/2\_.*X/MMM\rK\rLLL^ | + ]]) + end) + + it(", inccommand=split, highlights multiple matches on a line", function() + common_setup(screen, "split", multimatch_text) + command("set gdefault") + feed("gg") + + feed(":%s/a/XLK") + screen:expect([[ + {12:XLK} bdc e{12:XLK}e {12:XLK} fgl lzi{12:XLK} r| + x | + | + {15:~ }| + {15:~ }| + {11:[No Name] [+] }| + |1| {12:XLK} bdc e{12:XLK}e {12:XLK} fgl lzi{12:X}| + {12:LK} r | + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {10:[Preview] }| + :%s/a/XLK^ | + ]]) + end) + + it(", inccommand=nosplit, highlights multiple matches on a line", function() + common_setup(screen, "nosplit", multimatch_text) + command("set gdefault") + feed("gg") + + feed(":%s/a/XLK") + screen:expect([[ + {12:XLK} bdc e{12:XLK}e {12:XLK} fgl lzi{12:XLK} r| + x | + | + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + :%s/a/XLK^ | + ]]) + end) + + it(", inccommand=split, with \\zs", function() + common_setup(screen, "split", multiline_text) + feed("gg") + + feed(":%s/[0-9]\\n\\zs[A-Z]/OKO") + screen:expect([[ + 1 2 3 | + {12:OKO} B C | + 4 5 6 | + {12:OKO} Y Z | + 7 8 9 | + {11:[No Name] [+] }| + |1| 1 2 3 | + |2| {12:OKO} B C | + |3| 4 5 6 | + |4| {12:OKO} Y Z | + {15:~ }| + {15:~ }| + {15:~ }| + {10:[Preview] }| + :%s/[0-9]\n\zs[A-Z]/OKO^ | + ]]) + end) + + it(", inccommand=nosplit, with \\zs", function() + common_setup(screen, "nosplit", multiline_text) + feed("gg") + + feed(":%s/[0-9]\\n\\zs[A-Z]/OKO") + screen:expect([[ + 1 2 3 | + {12:OKO} B C | + 4 5 6 | + {12:OKO} Y Z | + 7 8 9 | + | + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + :%s/[0-9]\n\zs[A-Z]/OKO^ | + ]]) + end) + + it(", inccommand=split, substitutions of different length", + function() + common_setup(screen, "split", "T T123 T2T TTT T090804\nx") + + feed(":%s/T\\([0-9]\\+\\)/\\1\\1/g") + screen:expect([[ + T {12:123123} {12:22}T TTT {12:090804090804} | + x | + {15:~ }| + {15:~ }| + {15:~ }| + {11:[No Name] [+] }| + |1| T {12:123123} {12:22}T TTT {12:090804090}| + {12:804} | + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {10:[Preview] }| + :%s/T\([0-9]\+\)/\1\1/g^ | + ]]) + end) + + it(", inccommand=nosplit, substitutions of different length", function() + common_setup(screen, "nosplit", "T T123 T2T TTT T090804\nx") + + feed(":%s/T\\([0-9]\\+\\)/\\1\\1/g") + screen:expect([[ + T {12:123123} {12:22}T TTT {12:090804090804} | + x | + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + :%s/T\([0-9]\+\)/\1\1/g^ | + ]]) + end) + + it(", inccommand=split, contraction of lines", function() + local text = [[ + T T123 T T123 T2T TT T23423424 + x + afa Q + adf la;lkd R + alx + ]] + + common_setup(screen, "split", text) + feed(":%s/[QR]\\n") + screen:expect([[ + afa {12:Q} | + adf la;lkd {12:R} | + alx | + | + {15:~ }| + {11:[No Name] [+] }| + |3| afa {12:Q} | + |4|{12: }adf la;lkd {12:R} | + |5|{12: }alx | + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {10:[Preview] }| + :%s/[QR]\n^ | + ]]) + + feed("/KKK") + screen:expect([[ + x | + afa {12:KKK}adf la;lkd {12:KKK}alx | + | + {15:~ }| + {15:~ }| + {11:[No Name] [+] }| + |3| afa {12:KKK}adf la;lkd {12:KKK}alx | + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {10:[Preview] }| + :%s/[QR]\n/KKK^ | + ]]) + end) + + it(", inccommand=nosplit, contraction of lines", function() + local text = [[ + T T123 T T123 T2T TT T23423424 + x + afa Q + adf la;lkd R + alx + ]] + + common_setup(screen, "nosplit", text) + feed(":%s/[QR]\\n/KKK") + screen:expect([[ + T T123 T T123 T2T TT T23423424| + x | + afa {12:KKK}adf la;lkd {12:KKK}alx | + | + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + :%s/[QR]\n/KKK^ | + ]]) + end) + + it(", inccommand=split, multibyte text", function() + common_setup(screen, "split", multibyte_text) + feed(":%s/£.*ѫ/X¥¥") + screen:expect([[ + {12:X¥¥} | + a{12:X¥¥}¥KOL | + £ ¥ libm | + £ ¥ | + | + {11:[No Name] [+] }| + |1| {12:X¥¥} PEPPERS | + |2| {12:X¥¥} | + |3| a{12:X¥¥}¥KOL | + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {10:[Preview] }| + :%s/£.*ѫ/X¥¥^ | + ]]) + + feed("\\ra££ ¥") + screen:expect([[ + {12:a££ ¥} | + a{12:X¥¥} | + {12:a££ ¥}¥KOL | + £ ¥ libm | + £ ¥ | + {11:[No Name] [+] }| + |1| {12:X¥¥} | + |2|{12: a££ ¥} PEPPERS | + |3| {12:X¥¥} | + |4|{12: a££ ¥} | + |5| a{12:X¥¥} | + |6|{12: a££ ¥}¥KOL | + {15:~ }| + {10:[Preview] }| + :%s/£.*ѫ/X¥¥\ra££ ¥^ | + ]]) + end) + + it(", inccommand=nosplit, multibyte text", function() + common_setup(screen, "nosplit", multibyte_text) + feed(":%s/£.*ѫ/X¥¥") + screen:expect([[ + {12:X¥¥} PEPPERS | + {12:X¥¥} | + a{12:X¥¥}¥KOL | + £ ¥ libm | + £ ¥ | + | + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + :%s/£.*ѫ/X¥¥^ | + ]]) + + feed("\\ra££ ¥") + screen:expect([[ + {12:X¥¥} | + {12:a££ ¥} PEPPERS | + {12:X¥¥} | + {12:a££ ¥} | + a{12:X¥¥} | + {12:a££ ¥}¥KOL | + £ ¥ libm | + £ ¥ | + | + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + :%s/£.*ѫ/X¥¥\ra££ ¥^ | + ]]) + end) + + it(", inccommand=split, small cmdwinheight", function() + common_setup(screen, "split", long_multiline_text) + command("set cmdwinheight=2") + + feed(":%s/[a-z]") + screen:expect([[ + X Y Z | + 7 8 9 | + K L M | + {12:a} b c | + {12:d} e f | + {12:q} r s | + {12:x} y z | + £ {12:m} n | + {12:t} œ ¥ | + | + {11:[No Name] [+] }| + | 7| {12:a} b c | + | 8| {12:d} e f | + {10:[Preview] }| + :%s/[a-z]^ | + ]]) + + feed("/JLKR £") + screen:expect([[ + X Y Z | + 7 8 9 | + K L M | + {12:JLKR £} b c | + {12:JLKR £} e f | + {12:JLKR £} r s | + {12:JLKR £} y z | + £ {12:JLKR £} n | + {12:JLKR £} œ ¥ | + | + {11:[No Name] [+] }| + | 7| {12:JLKR £} b c | + | 8| {12:JLKR £} e f | + {10:[Preview] }| + :%s/[a-z]/JLKR £^ | + ]]) + + feed("\\rѫ ab \\rXXXX") + screen:expect([[ + 7 8 9 | + K L M | + {12:JLKR £} | + {12:ѫ ab } | + {12:XXXX} b c | + {12:JLKR £} | + {12:ѫ ab } | + {12:XXXX} e f | + {12:JLKR £} | + {11:[No Name] [+] }| + | 7| {12:JLKR £} | + | 8|{12: ѫ ab } | + {10:[Preview] }| + :%s/[a-z]/JLKR £\rѫ ab \rXXX| + X^ | + ]]) + end) + + it(", inccommand=split, large cmdwinheight", function() + common_setup(screen, "split", long_multiline_text) + command("set cmdwinheight=11") + + feed(":%s/. .$") + screen:expect([[ + t {12:œ ¥} | + {11:[No Name] [+] }| + | 1| 1 {12:2 3} | + | 2| A {12:B C} | + | 3| 4 {12:5 6} | + | 4| X {12:Y Z} | + | 5| 7 {12:8 9} | + | 6| K {12:L M} | + | 7| a {12:b c} | + | 8| d {12:e f} | + | 9| q {12:r s} | + |10| x {12:y z} | + |11| £ {12:m n} | + {10:[Preview] }| + :%s/. .$^ | + ]]) + + feed("/ YYY") + screen:expect([[ + t {12: YYY} | + {11:[No Name] [+] }| + | 1| 1 {12: YYY} | + | 2| A {12: YYY} | + | 3| 4 {12: YYY} | + | 4| X {12: YYY} | + | 5| 7 {12: YYY} | + | 6| K {12: YYY} | + | 7| a {12: YYY} | + | 8| d {12: YYY} | + | 9| q {12: YYY} | + |10| x {12: YYY} | + |11| £ {12: YYY} | + {10:[Preview] }| + :%s/. .$/ YYY^ | + ]]) + + feed("\\r KKK") + screen:expect([[ + a {12: YYY} | + {11:[No Name] [+] }| + | 1| 1 {12: YYY} | + | 2|{12: KKK} | + | 3| A {12: YYY} | + | 4|{12: KKK} | + | 5| 4 {12: YYY} | + | 6|{12: KKK} | + | 7| X {12: YYY} | + | 8|{12: KKK} | + | 9| 7 {12: YYY} | + |10|{12: KKK} | + |11| K {12: YYY} | + {10:[Preview] }| + :%s/. .$/ YYY\r KKK^ | + ]]) + end) + + it(", inccommand=split, lookaround", function() + common_setup(screen, "split", "something\neverything\nsomeone") + feed([[:%s/\(some\)\@<lt>=thing/one/]]) + screen:expect([[ + some{12:one} | + everything | + someone | + {15:~ }| + {15:~ }| + {11:[No Name] [+] }| + |1| some{12:one} | + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {10:[Preview] }| + :%s/\(some\)\@<=thing/one/^ | + ]]) + + feed("<C-c>") + wait() + feed([[:%s/\(some\)\@<lt>!thing/one/]]) + screen:expect([[ + something | + every{12:one} | + someone | + {15:~ }| + {15:~ }| + {11:[No Name] [+] }| + |2| every{12:one} | + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {10:[Preview] }| + :%s/\(some\)\@<!thing/one/^ | + ]]) + + feed([[<C-c>]]) + wait() + feed([[:%s/some\(thing\)\@=/every/]]) + screen:expect([[ + {12:every}thing | + everything | + someone | + {15:~ }| + {15:~ }| + {11:[No Name] [+] }| + |1| {12:every}thing | + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {10:[Preview] }| + :%s/some\(thing\)\@=/every/^ | + ]]) + + feed([[<C-c>]]) + wait() + feed([[:%s/some\(thing\)\@!/every/]]) + screen:expect([[ + everything | + {12:every}one | + {15:~ }| + {15:~ }| + {15:~ }| + {11:[No Name] [+] }| + |3| {12:every}one | + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {10:[Preview] }| + :%s/some\(thing\)\@!/every/^ | + ]]) + end) +end) diff --git a/test/functional/ui/input_spec.lua b/test/functional/ui/input_spec.lua index cec19250d2..29d974b709 100644 --- a/test/functional/ui/input_spec.lua +++ b/test/functional/ui/input_spec.lua @@ -1,5 +1,5 @@ local helpers = require('test.functional.helpers')(after_each) -local clear, execute, nvim = helpers.clear, helpers.execute, helpers.nvim +local clear, feed_command, nvim = helpers.clear, helpers.feed_command, helpers.nvim local feed, next_message, eq = helpers.feed, helpers.next_message, helpers.eq local expect = helpers.expect local Screen = require('test.functional.ui.screen') @@ -12,7 +12,7 @@ describe('mappings', function() local add_mapping = function(mapping, send) local cmd = "nnoremap "..mapping.." :call rpcnotify("..cid..", 'mapped', '" ..send:gsub('<', '<lt>').."')<cr>" - execute(cmd) + feed_command(cmd) end local check_mapping = function(mapping, expected) @@ -57,7 +57,7 @@ describe('feeding large chunks of input with <Paste>', function() clear() screen = Screen.new() screen:attach() - execute('set ruler') + feed_command('set ruler') end) it('ok', function() diff --git a/test/functional/ui/mode_spec.lua b/test/functional/ui/mode_spec.lua new file mode 100644 index 0000000000..f0cedfeeb5 --- /dev/null +++ b/test/functional/ui/mode_spec.lua @@ -0,0 +1,227 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') + +local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert +local command, eval = helpers.command, helpers.eval +local eq = helpers.eq + +describe('ui mode_change event', function() + local screen + + before_each(function() + clear() + screen = Screen.new(25, 4) + screen:attach({rgb= true}) + screen:set_default_attr_ids( { + [0] = {bold=true, foreground=255}, + [1] = {bold=true, reverse=true}, + [2] = {bold=true}, + [3] = {reverse=true}, + }) + end) + + it('works in normal mode', function() + screen:expect([[ + ^ | + {0:~ }| + {0:~ }| + | + ]],nil,nil,function () + eq("normal", screen.mode) + end) + + feed('d') + screen:expect([[ + ^ | + {0:~ }| + {0:~ }| + | + ]],nil,nil,function () + eq("operator", screen.mode) + end) + + feed('<esc>') + screen:expect([[ + ^ | + {0:~ }| + {0:~ }| + | + ]],nil,nil,function () + eq("normal", screen.mode) + end) + end) + + it('works in insert mode', function() + feed('i') + screen:expect([[ + ^ | + {0:~ }| + {0:~ }| + {2:-- INSERT --} | + ]],nil,nil,function () + eq("insert", screen.mode) + end) + + feed('word<esc>') + screen:expect([[ + wor^d | + {0:~ }| + {0:~ }| + | + ]], nil, nil, function () + eq("normal", screen.mode) + end) + + command("set showmatch") + eq(eval('&matchtime'), 5) -- tenths of seconds + feed('a(stuff') + screen:expect([[ + word(stuff^ | + {0:~ }| + {0:~ }| + {2:-- INSERT --} | + ]], nil, nil, function () + eq("insert", screen.mode) + end) + + feed(')') + screen:expect([[ + word^(stuff) | + {0:~ }| + {0:~ }| + {2:-- INSERT --} | + ]], nil, nil, function () + eq("showmatch", screen.mode) + end) + + screen:sleep(400) + screen:expect([[ + word(stuff)^ | + {0:~ }| + {0:~ }| + {2:-- INSERT --} | + ]], nil, nil, function () + eq("insert", screen.mode) + end) + end) + + it('works in replace mode', function() + feed('R') + screen:expect([[ + ^ | + {0:~ }| + {0:~ }| + {2:-- REPLACE --} | + ]], nil, nil, function () + eq("replace", screen.mode) + end) + + feed('word<esc>') + screen:expect([[ + wor^d | + {0:~ }| + {0:~ }| + | + ]], nil, nil, function () + eq("normal", screen.mode) + end) + end) + + it('works in cmdline mode', function() + feed(':') + screen:expect([[ + | + {0:~ }| + {0:~ }| + :^ | + ]],nil,nil,function () + eq("cmdline_normal", screen.mode) + end) + + feed('x<left>') + screen:expect([[ + | + {0:~ }| + {0:~ }| + :^x | + ]],nil,nil,function () + eq("cmdline_insert", screen.mode) + end) + + feed('<insert>') + screen:expect([[ + | + {0:~ }| + {0:~ }| + :^x | + ]],nil,nil,function () + eq("cmdline_replace", screen.mode) + end) + + + feed('<right>') + screen:expect([[ + | + {0:~ }| + {0:~ }| + :x^ | + ]],nil,nil,function () + eq("cmdline_normal", screen.mode) + end) + + feed('<esc>') + screen:expect([[ + ^ | + {0:~ }| + {0:~ }| + | + ]],nil,nil,function () + eq("normal", screen.mode) + end) + end) + + it('works in visal mode', function() + insert("text") + feed('v') + screen:expect([[ + tex^t | + {0:~ }| + {0:~ }| + {2:-- VISUAL --} | + ]],nil,nil,function () + eq("visual", screen.mode) + end) + + feed('<esc>') + screen:expect([[ + tex^t | + {0:~ }| + {0:~ }| + | + ]],nil,nil,function () + eq("normal", screen.mode) + end) + + command('set selection=exclusive') + feed('v') + screen:expect([[ + tex^t | + {0:~ }| + {0:~ }| + {2:-- VISUAL --} | + ]],nil,nil,function () + eq("visual_select", screen.mode) + end) + + feed('<esc>') + screen:expect([[ + tex^t | + {0:~ }| + {0:~ }| + | + ]],nil,nil,function () + eq("normal", screen.mode) + end) + end) +end) + diff --git a/test/functional/ui/mouse_spec.lua b/test/functional/ui/mouse_spec.lua index b2fbedfb5e..3daf92eea0 100644 --- a/test/functional/ui/mouse_spec.lua +++ b/test/functional/ui/mouse_spec.lua @@ -1,21 +1,18 @@ local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') local clear, feed, meths = helpers.clear, helpers.feed, helpers.meths -local insert, execute = helpers.insert, helpers.execute +local insert, feed_command = helpers.insert, helpers.feed_command local eq, funcs = helpers.eq, helpers.funcs if helpers.pending_win32(pending) then return end -describe('Mouse input', function() +describe('ui/mouse/input', function() local screen before_each(function() clear() meths.set_option('mouse', 'a') meths.set_option('listchars', 'eol:$') - -- set mousetime to very high value to ensure that even in valgrind/travis, - -- nvim will still pick multiple clicks - meths.set_option('mousetime', 5000) screen = Screen.new(25, 5) screen:attach() screen:set_default_attr_ids({ @@ -119,13 +116,12 @@ describe('Mouse input', function() sel = { bold=true }, fill = { reverse=true } }) - screen.timeout = 15000 end) it('in tabline on filler space moves tab to the end', function() - execute('%delete') + feed_command('%delete') insert('this is foo') - execute('silent file foo | tabnew | file bar') + feed_command('silent file foo | tabnew | file bar') insert('this is bar') screen:expect([[ {tab: + foo }{sel: + bar }{fill: }{tab:X}| @@ -160,9 +156,9 @@ describe('Mouse input', function() return end - execute('%delete') + feed_command('%delete') insert('this is foo') - execute('silent file foo | tabnew | file bar') + feed_command('silent file foo | tabnew | file bar') insert('this is bar') screen:expect([[ {tab: + foo }{sel: + bar }{fill: }{tab:X}| @@ -190,9 +186,9 @@ describe('Mouse input', function() end) it('in tabline to the right moves tab right', function() - execute('%delete') + feed_command('%delete') insert('this is foo') - execute('silent file foo | tabnew | file bar') + feed_command('silent file foo | tabnew | file bar') insert('this is bar') screen:expect([[ {tab: + foo }{sel: + bar }{fill: }{tab:X}| @@ -220,9 +216,9 @@ describe('Mouse input', function() end) it('out of tabline under filler space moves tab to the end', function() - execute('%delete') + feed_command('%delete') insert('this is foo') - execute('silent file foo | tabnew | file bar') + feed_command('silent file foo | tabnew | file bar') insert('this is bar') screen:expect([[ {tab: + foo }{sel: + bar }{fill: }{tab:X}| @@ -265,9 +261,9 @@ describe('Mouse input', function() return end - execute('%delete') + feed_command('%delete') insert('this is foo') - execute('silent file foo | tabnew | file bar') + feed_command('silent file foo | tabnew | file bar') insert('this is bar') screen:expect([[ {tab: + foo }{sel: + bar }{fill: }{tab:X}| @@ -303,9 +299,9 @@ describe('Mouse input', function() end) it('out of tabline to the right moves tab right', function() - execute('%delete') + feed_command('%delete') insert('this is foo') - execute('silent file foo | tabnew | file bar') + feed_command('silent file foo | tabnew | file bar') insert('this is bar') screen:expect([[ {tab: + foo }{sel: + bar }{fill: }{tab:X}| @@ -352,9 +348,9 @@ describe('Mouse input', function() end) it('left click in default tabline (position 4) switches to tab', function() - execute('%delete') + feed_command('%delete') insert('this is foo') - execute('silent file foo | tabnew | file bar') + feed_command('silent file foo | tabnew | file bar') insert('this is bar') screen:expect([[ {tab: + foo }{sel: + bar }{fill: }{tab:X}| @@ -375,9 +371,9 @@ describe('Mouse input', function() it('left click in default tabline (position 24) closes tab', function() meths.set_option('hidden', true) - execute('%delete') + feed_command('%delete') insert('this is foo') - execute('silent file foo | tabnew | file bar') + feed_command('silent file foo | tabnew | file bar') insert('this is bar') screen:expect([[ {tab: + foo }{sel: + bar }{fill: }{tab:X}| @@ -398,9 +394,9 @@ describe('Mouse input', function() it('double click in default tabline (position 4) opens new tab', function() meths.set_option('hidden', true) - execute('%delete') + feed_command('%delete') insert('this is foo') - execute('silent file foo | tabnew | file bar') + feed_command('silent file foo | tabnew | file bar') insert('this is bar') screen:expect([[ {tab: + foo }{sel: + bar }{fill: }{tab:X}| @@ -421,13 +417,13 @@ describe('Mouse input', function() describe('%@ label', function() before_each(function() - execute([[ + feed_command([[ function Test(...) let g:reply = a:000 return copy(a:000) " Check for memory leaks: return should be freed endfunction ]]) - execute([[ + feed_command([[ function Test2(...) return call('Test', a:000 + [2]) endfunction @@ -533,9 +529,9 @@ describe('Mouse input', function() fill = { reverse=true }, vis = { background=Screen.colors.LightGrey } }) - execute('silent file foo | tabnew | file bar') + feed_command('silent file foo | tabnew | file bar') insert('this is bar') - execute('tabprevious') -- go to first tab + feed_command('tabprevious') -- go to first tab screen:expect([[ {sel: + foo }{tab: + bar }{fill: }{tab:X}| mouse | @@ -641,7 +637,7 @@ describe('Mouse input', function() mouse scrolling ]]) screen:try_resize(53, 14) - execute('sp', 'vsp') + feed_command('sp', 'vsp') screen:expect([[ lines {4:|}lines | to {4:|}to | @@ -754,12 +750,12 @@ describe('Mouse input', function() }) feed('ggdG') - execute('set concealcursor=n') - execute('set nowrap') - execute('syntax match NonText "\\<amet\\>" conceal') - execute('syntax match NonText "\\cs\\|g." conceal cchar=X') - execute('syntax match NonText "\\%(lo\\|cl\\)." conceal') - execute('syntax match NonText "Lo" conceal cchar=Y') + feed_command('set concealcursor=n') + feed_command('set nowrap') + feed_command('syntax match NonText "\\<amet\\>" conceal') + feed_command('syntax match NonText "\\cs\\|g." conceal cchar=X') + feed_command('syntax match NonText "\\%(lo\\|cl\\)." conceal') + feed_command('syntax match NonText "Lo" conceal cchar=Y') insert([[ Lorem ipsum dolor sit amet, consetetur sadipscing elitr. @@ -770,7 +766,7 @@ describe('Mouse input', function() end) it('(level 1) click on non-wrapped lines', function() - execute('let &conceallevel=1', 'echo') + feed_command('let &conceallevel=1', 'echo') feed('<esc><LeftMouse><0,0>') screen:expect([[ @@ -818,7 +814,7 @@ describe('Mouse input', function() end) -- level 1 - non wrapped it('(level 1) click on wrapped lines', function() - execute('let &conceallevel=1', 'let &wrap=1', 'echo') + feed_command('let &conceallevel=1', 'let &wrap=1', 'echo') feed('<esc><LeftMouse><0,0>') screen:expect([[ @@ -867,7 +863,7 @@ describe('Mouse input', function() it('(level 2) click on non-wrapped lines', function() - execute('let &conceallevel=2', 'echo') + feed_command('let &conceallevel=2', 'echo') feed('<esc><LeftMouse><0,0>') screen:expect([[ @@ -915,7 +911,7 @@ describe('Mouse input', function() end) -- level 2 - non wrapped it('(level 2) click on wrapped lines', function() - execute('let &conceallevel=2', 'let &wrap=1', 'echo') + feed_command('let &conceallevel=2', 'let &wrap=1', 'echo') feed('<esc><LeftMouse><0,0>') screen:expect([[ @@ -964,7 +960,7 @@ describe('Mouse input', function() it('(level 3) click on non-wrapped lines', function() - execute('let &conceallevel=3', 'echo') + feed_command('let &conceallevel=3', 'echo') feed('<esc><LeftMouse><0,0>') screen:expect([[ @@ -1012,7 +1008,7 @@ describe('Mouse input', function() end) -- level 3 - non wrapped it('(level 3) click on wrapped lines', function() - execute('let &conceallevel=3', 'let &wrap=1', 'echo') + feed_command('let &conceallevel=3', 'let &wrap=1', 'echo') feed('<esc><LeftMouse><0,0>') screen:expect([[ diff --git a/test/functional/ui/quickfix_spec.lua b/test/functional/ui/quickfix_spec.lua index 29b28fe9f0..b0d89ee3b6 100644 --- a/test/functional/ui/quickfix_spec.lua +++ b/test/functional/ui/quickfix_spec.lua @@ -1,7 +1,7 @@ local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') local clear, feed, meths = helpers.clear, helpers.feed, helpers.meths -local insert, execute = helpers.insert, helpers.execute +local insert, command = helpers.insert, helpers.command describe('quickfix selection highlight', function() @@ -28,8 +28,8 @@ describe('quickfix selection highlight', function() }) meths.set_option('errorformat', '%m %l') - execute('syntax on') - execute('highlight Search guibg=Green') + command('syntax on') + command('highlight Search guibg=Green') insert([[ Line 1 @@ -39,7 +39,7 @@ describe('quickfix selection highlight', function() Line 5 ]]) - execute('cad') + command('cad') feed('gg') screen:expect([[ @@ -52,12 +52,12 @@ describe('quickfix selection highlight', function() {1:~ }| {1:~ }| {1:~ }| - :cad | + | ]]) end) it('using default Search highlight group', function() - execute('copen') + command('copen') screen:expect([[ Line 1 | @@ -69,10 +69,10 @@ describe('quickfix selection highlight', function() |{3:5}| Line | || | {4:[Quickfix List] }| - :copen | + | ]]) - execute('cnext') + command('cnext') screen:expect([[ Line 1 | @@ -84,14 +84,14 @@ describe('quickfix selection highlight', function() |{3:5}| Line | || | {4:[Quickfix List] }| - :cnext | + | ]]) end) it('using QuickFixLine highlight group', function() - execute('highlight QuickFixLine guibg=Red') + command('highlight QuickFixLine guibg=Red') - execute('copen') + command('copen') screen:expect([[ Line 1 | @@ -103,10 +103,10 @@ describe('quickfix selection highlight', function() |{3:5}| Line | || | {4:[Quickfix List] }| - :copen | + | ]]) - execute('cnext') + command('cnext') screen:expect([[ Line 1 | @@ -118,16 +118,16 @@ describe('quickfix selection highlight', function() |{3:5}| Line | || | {4:[Quickfix List] }| - :cnext | + | ]]) end) it('combines with CursorLine', function() - execute('set cursorline') - execute('highlight QuickFixLine guifg=Red') - execute('highlight CursorLine guibg=Fuchsia') + command('set cursorline') + command('highlight QuickFixLine guifg=Red') + command('highlight CursorLine guibg=Fuchsia') - execute('copen') + command('copen') screen:expect([[ {9:Line 1 }| @@ -139,7 +139,7 @@ describe('quickfix selection highlight', function() |{3:5}| Line | || | {4:[Quickfix List] }| - :copen | + | ]]) feed('j') @@ -154,16 +154,16 @@ describe('quickfix selection highlight', function() |{3:5}| Line | || | {4:[Quickfix List] }| - :copen | + | ]]) end) it('QuickFixLine background takes precedence over CursorLine', function() - execute('set cursorline') - execute('highlight QuickFixLine guibg=Red') - execute('highlight CursorLine guibg=Fuchsia') + command('set cursorline') + command('highlight QuickFixLine guibg=Red') + command('highlight CursorLine guibg=Fuchsia') - execute('copen') + command('copen') screen:expect([[ {9:Line 1 }| @@ -175,7 +175,7 @@ describe('quickfix selection highlight', function() |{3:5}| Line | || | {4:[Quickfix List] }| - :copen | + | ]]) feed('j') @@ -190,7 +190,7 @@ describe('quickfix selection highlight', function() |{3:5}| Line | || | {4:[Quickfix List] }| - :copen | + | ]]) end) end) diff --git a/test/functional/ui/screen.lua b/test/functional/ui/screen.lua index 54f43387dc..a6b7fb2997 100644 --- a/test/functional/ui/screen.lua +++ b/test/functional/ui/screen.lua @@ -181,6 +181,7 @@ end -- expected: Expected screen state (string). Each line represents a screen -- row. Last character of each row (typically "|") is stripped. -- Common indentation is stripped. +-- Used as `condition` if NOT a string; must be the ONLY arg then. -- attr_ids: Expected text attributes. Screen rows are transformed according -- to this table, as follows: each substring S composed of -- characters having the same attributes will be substituted by @@ -191,18 +192,24 @@ end -- any: true: Succeed if `expected` matches ANY screen line(s). -- false (default): `expected` must match screen exactly. function Screen:expect(expected, attr_ids, attr_ignore, condition, any) - -- remove the last line and dedent - expected = dedent(expected:gsub('\n[ ]+$', '')) local expected_rows = {} - for row in expected:gmatch('[^\n]+') do - -- the last character should be the screen delimiter - row = row:sub(1, #row - 1) - table.insert(expected_rows, row) - end - if not any then - assert(self._height == #expected_rows, - "Expected screen state's row count(" .. #expected_rows - .. ') differs from configured height(' .. self._height .. ') of Screen.') + if type(expected) ~= "string" then + assert(not (attr_ids or attr_ignore or condition or any)) + condition = expected + expected = nil + else + -- Remove the last line and dedent. Note that gsub returns more then one + -- value. + expected = dedent(expected:gsub('\n[ ]+$', ''), 0) + for row in expected:gmatch('[^\n]+') do + row = row:sub(1, #row - 1) -- Last char must be the screen delimiter. + table.insert(expected_rows, row) + end + if not any then + assert(self._height == #expected_rows, + "Expected screen state's row count(" .. #expected_rows + .. ') differs from configured height(' .. self._height .. ') of Screen.') + end end local ids = attr_ids or self._default_attr_ids local ignore = attr_ignore or self._default_attr_ignore @@ -218,7 +225,9 @@ function Screen:expect(expected, attr_ids, attr_ignore, condition, any) actual_rows[i] = self:_row_repr(self._rows[i], ids, ignore) end - if any then + if expected == nil then + return + elseif any then -- Search for `expected` anywhere in the screen lines. local actual_screen_str = table.concat(actual_rows, '\n') if nil == string.find(actual_screen_str, expected) then @@ -242,7 +251,7 @@ function Screen:expect(expected, attr_ids, attr_ignore, condition, any) ..'Expected:\n |'..table.concat(msg_expected_rows, '|\n |')..'|\n' ..'Actual:\n |'..table.concat(actual_rows, '|\n |')..'|\n\n'..[[ To print the expect() call that would assert the current screen state, use -screen:snaphot_util(). In case of non-deterministic failures, use +screen:snapshot_util(). In case of non-deterministic failures, use screen:redraw_debug() to show all intermediate screen states. ]]) end end @@ -276,18 +285,13 @@ function Screen:wait(check, timeout) if failure_after_success then print([[ -Warning: Screen changes have been received after the expected state was seen. -This is probably due to an indeterminism in the test. Try adding -`wait()` (or even a separate `screen:expect(...)`) at a point of possible -indeterminism, typically in between a `feed()` or `execute()` which is non- -synchronous, and a synchronous api call. - -Note that sometimes a `wait` can trigger redraws and consequently generate more -indeterminism. If adding `wait` calls seems to increase the frequency of these -messages, try removing every `wait` call in the test. - -If everything else fails, use Screen:redraw_debug to help investigate what is - causing the problem. + +Warning: Screen changes were received after the expected state. This indicates +indeterminism in the test. Try adding wait() (or screen:expect(...)) between +asynchronous (feed(), nvim_input()) and synchronous API calls. + - Use Screen:redraw_debug() to investigate the problem. + - wait() can trigger redraws and consequently generate more indeterminism. + In that case try removing every wait(). ]]) local tb = debug.traceback() local index = string.find(tb, '\n%s*%[C]') @@ -309,10 +313,13 @@ function Screen:_redraw(updates) -- print(require('inspect')(update)) local method = update[1] for i = 2, #update do - local handler = self['_handle_'..method] + local handler_name = '_handle_'..method + local handler = self[handler_name] if handler ~= nil then handler(self, unpack(update[i])) else + assert(self._on_event, + "Add Screen:"..handler_name.." or call Screen:set_on_event_handler") self._on_event(method, update[i]) end end @@ -343,6 +350,11 @@ function Screen:_handle_resize(width, height) } end +function Screen:_handle_mode_info_set(cursor_style_enabled, mode_info) + self._cursor_style_enabled = cursor_style_enabled + self._mode_info = mode_info +end + function Screen:_handle_clear() self:_clear_block(self._scroll_region.top, self._scroll_region.bot, self._scroll_region.left, self._scroll_region.right) @@ -374,9 +386,8 @@ function Screen:_handle_mouse_off() self._mouse_enabled = false end -function Screen:_handle_mode_change(mode) - assert(mode == 'insert' or mode == 'replace' - or mode == 'normal' or mode == 'cmdline') +function Screen:_handle_mode_change(mode, idx) + assert(mode == self._mode_info[idx+1].name) self.mode = mode end diff --git a/test/functional/ui/screen_basic_spec.lua b/test/functional/ui/screen_basic_spec.lua index e511234e5e..b31d9cb32f 100644 --- a/test/functional/ui/screen_basic_spec.lua +++ b/test/functional/ui/screen_basic_spec.lua @@ -1,12 +1,13 @@ local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') local spawn, set_session, clear = helpers.spawn, helpers.set_session, helpers.clear -local feed, execute = helpers.feed, helpers.execute +local feed, command = helpers.feed, helpers.command local insert = helpers.insert local eq = helpers.eq local eval = helpers.eval +local iswin = helpers.iswin -describe('Initial screen', function() +describe('screen', function() local screen local nvim_argv = {helpers.nvim_prog, '-u', 'NONE', '-i', 'NONE', '-N', '--cmd', 'set shortmess+=I background=light noswapfile belloff= noshowcmd noruler', @@ -27,7 +28,7 @@ describe('Initial screen', function() screen:detach() end) - it('is the default initial screen', function() + it('default initial screen', function() screen:expect([[ ^ | {0:~ }| @@ -73,33 +74,29 @@ describe('Screen', function() describe(':suspend', function() it('is forwarded to the UI', function() local function check() - if not screen.suspended then - return 'Screen was not suspended' - end + eq(true, screen.suspended) end - execute('suspend') - screen:wait(check) + command('suspend') + screen:expect(check) screen.suspended = false feed('<c-z>') - screen:wait(check) + screen:expect(check) end) end) describe('bell/visual bell', function() it('is forwarded to the UI', function() feed('<left>') - screen:wait(function() - if not screen.bell or screen.visual_bell then - return 'Bell was not sent' - end + screen:expect(function() + eq(true, screen.bell) + eq(false, screen.visual_bell) end) screen.bell = false - execute('set visualbell') + command('set visualbell') feed('<left>') - screen:wait(function() - if not screen.visual_bell or screen.bell then - return 'Visual bell was not sent' - end + screen:expect(function() + eq(true, screen.visual_bell) + eq(false, screen.bell) end) end) end) @@ -107,36 +104,28 @@ describe('Screen', function() describe(':set title', function() it('is forwarded to the UI', function() local expected = 'test-title' - execute('set titlestring='..expected) - execute('set title') - screen:wait(function() - local actual = screen.title - if actual ~= expected then - return 'Expected title to be "'..expected..'" but was "'..actual..'"' - end + command('set titlestring='..expected) + command('set title') + screen:expect(function() + eq(expected, screen.title) end) end) it('has correct default title with unnamed file', function() local expected = '[No Name] - NVIM' - execute('set title') - screen:wait(function() - local actual = screen.title - if actual ~= expected then - return 'Expected title to be "'..expected..'" but was "'..actual..'"' - end + command('set title') + screen:expect(function() + eq(expected, screen.title) end) end) it('has correct default title with named file', function() - local expected = 'myfile (/mydir) - NVIM' - execute('set title') - execute('file /mydir/myfile') - screen:wait(function() - local actual = screen.title - if actual ~= expected then - return 'Expected title to be "'..expected..'" but was "'..actual..'"' - end + local expected = (iswin() and 'myfile (C:\\mydir) - NVIM' + or 'myfile (/mydir) - NVIM') + command('set title') + command(iswin() and 'file C:\\mydir\\myfile' or 'file /mydir/myfile') + screen:expect(function() + eq(expected, screen.title) end) end) end) @@ -144,13 +133,10 @@ describe('Screen', function() describe(':set icon', function() it('is forwarded to the UI', function() local expected = 'test-icon' - execute('set iconstring='..expected) - execute('set icon') - screen:wait(function() - local actual = screen.icon - if actual ~= expected then - return 'Expected title to be "'..expected..'" but was "'..actual..'"' - end + command('set iconstring='..expected) + command('set icon') + screen:expect(function() + eq(expected, screen.icon) end) end) end) @@ -158,7 +144,7 @@ describe('Screen', function() describe('window', function() describe('split', function() it('horizontal', function() - execute('sp') + command('sp') screen:expect([[ ^ | {0:~ }| @@ -173,13 +159,13 @@ describe('Screen', function() {0:~ }| {0:~ }| {3:[No Name] }| - :sp | + | ]]) end) it('horizontal and resize', function() - execute('sp') - execute('resize 8') + command('sp') + command('resize 8') screen:expect([[ ^ | {0:~ }| @@ -194,12 +180,14 @@ describe('Screen', function() {0:~ }| {0:~ }| {3:[No Name] }| - :resize 8 | + | ]]) end) it('horizontal and vertical', function() - execute('sp', 'vsp', 'vsp') + command('sp') + command('vsp') + command('vsp') screen:expect([[ ^ {3:|} {3:|} | {0:~ }{3:|}{0:~ }{3:|}{0:~ }| @@ -239,7 +227,9 @@ describe('Screen', function() describe('tabnew', function() it('creates a new buffer', function() - execute('sp', 'vsp', 'vsp') + command('sp') + command('vsp') + command('vsp') insert('hello') screen:expect([[ hell^o {3:|}hello {3:|}hello | @@ -257,7 +247,7 @@ describe('Screen', function() {3:[No Name] [+] }| | ]]) - execute('tabnew') + command('tabnew') insert('hello2') feed('h') screen:expect([[ @@ -276,7 +266,7 @@ describe('Screen', function() {0:~ }| | ]]) - execute('tabprevious') + command('tabprevious') screen:expect([[ {2: }{6:4}{2:+ [No Name] }{4: + [No Name] }{3: }{4:X}| hell^o {3:|}hello {3:|}hello | @@ -321,9 +311,9 @@ describe('Screen', function() describe('normal mode', function() -- https://code.google.com/p/vim/issues/detail?id=339 it("setting 'ruler' doesn't reset the preferred column", function() - execute('set virtualedit=') + command('set virtualedit=') feed('i0123456<cr>789<esc>kllj') - execute('set ruler') + command('set ruler') feed('k') screen:expect([[ 0123^456 | @@ -339,7 +329,7 @@ describe('Screen', function() {0:~ }| {0:~ }| {0:~ }| - :set ruler 1,5 All | + 1,5 All | ]]) end) end) @@ -404,7 +394,9 @@ describe('Screen', function() split windows ]]) - execute('sp', 'vsp', 'vsp') + command('sp') + command('vsp') + command('vsp') screen:expect([[ and {3:|}and {3:|}and | clearing {3:|}clearing {3:|}clearing | @@ -576,124 +568,61 @@ describe('Screen', function() end) end) - describe('mode change', function() - before_each(function() - screen:try_resize(25, 5) - end) - - it('works in normal mode', function() - screen:expect([[ - ^ | - {0:~ }| - {0:~ }| - {0:~ }| - | - ]],nil,nil,function () - eq("normal", screen.mode) - end) - end) - - it('works in insert mode', function() - feed('i') + describe('press enter', function() + it('does not crash on <F1> at “Press ENTER”', function() + command('nnoremap <F1> :echo "TEST"<CR>') + feed(':ls<CR>') screen:expect([[ - ^ | - {0:~ }| - {0:~ }| - {0:~ }| - {2:-- INSERT --} | - ]],nil,nil,function () - eq("insert", screen.mode) - end) - - feed('word<esc>') - screen:expect([[ - wor^d | - {0:~ }| - {0:~ }| - {0:~ }| - | - ]], nil, nil, function () - eq("normal", screen.mode) - end) - end) - - it('works in replace mode', function() - feed('R') - screen:expect([[ - ^ | - {0:~ }| - {0:~ }| - {0:~ }| - {2:-- REPLACE --} | - ]], nil, nil, function () - eq("replace", screen.mode) - end) - - feed('word<esc>') - screen:expect([[ - wor^d | - {0:~ }| - {0:~ }| - {0:~ }| - | - ]], nil, nil, function () - eq("normal", screen.mode) - end) - end) - - it('works in cmdline mode', function() - feed(':') - screen:expect([[ - | - {0:~ }| - {0:~ }| - {0:~ }| - :^ | - ]],nil,nil,function () - eq("cmdline", screen.mode) - end) - - feed('<esc>/') - screen:expect([[ - | - {0:~ }| - {0:~ }| - {0:~ }| - /^ | - ]],nil,nil,function () - eq("cmdline", screen.mode) - end) - - - feed('<esc>?') - screen:expect([[ - | - {0:~ }| - {0:~ }| - {0:~ }| - ?^ | - ]],nil,nil,function () - eq("cmdline", screen.mode) - end) - - feed('<esc>') + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + :ls | + 1 %a "[No Name]" line 1 | + {7:Press ENTER or type command to continue}^ | + ]]) + feed('<F1>') screen:expect([[ - ^ | - {0:~ }| - {0:~ }| - {0:~ }| - | - ]],nil,nil,function () - eq("normal", screen.mode) - end) + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + TEST | + ]]) end) end) +end) - it('nvim_ui_attach() handles very large width/height #2180', function() - screen:detach() - screen = Screen.new(999, 999) +describe('nvim_ui_attach()', function() + before_each(function() + clear() + end) + it('handles very large width/height #2180', function() + local screen = Screen.new(999, 999) screen:attach() eq(999, eval('&lines')) eq(999, eval('&columns')) end) + it('invalid option returns error', function() + local screen = Screen.new() + local status, rv = pcall(function() screen:attach({foo={'foo'}}) end) + eq(false, status) + eq('No such ui option', rv:match("No such .*")) + end) end) diff --git a/test/functional/ui/searchhl_spec.lua b/test/functional/ui/searchhl_spec.lua index 3914648e8f..11b18d015f 100644 --- a/test/functional/ui/searchhl_spec.lua +++ b/test/functional/ui/searchhl_spec.lua @@ -1,7 +1,7 @@ local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert -local execute = helpers.execute +local feed_command = helpers.feed_command if helpers.pending_win32(pending) then return end @@ -22,7 +22,7 @@ describe('search highlighting', function() end) it('is disabled by ":set nohlsearch"', function() - execute('set nohlsearch') + feed_command('set nohlsearch') insert("some text\nmore text") feed("gg/text<cr>") screen:expect([[ @@ -79,7 +79,7 @@ describe('search highlighting', function() /\<text\> | ]]) - execute("nohlsearch") + feed_command("nohlsearch") screen:expect([[ some text | more textstuff | @@ -92,8 +92,8 @@ describe('search highlighting', function() end) it('works with incsearch', function() - execute('set hlsearch') - execute('set incsearch') + feed_command('set hlsearch') + feed_command('set incsearch') insert([[ the first line in a little file @@ -156,8 +156,8 @@ describe('search highlighting', function() end) it('works with incsearch and offset', function() - execute('set hlsearch') - execute('set incsearch') + feed_command('set hlsearch') + feed_command('set incsearch') insert([[ not the match you're looking for the match is here]]) @@ -198,7 +198,7 @@ describe('search highlighting', function() end) it('works with multiline regexps', function() - execute('set hlsearch') + feed_command('set hlsearch') feed('4oa repeated line<esc>') feed('/line\\na<cr>') screen:expect([[ @@ -234,19 +234,19 @@ describe('search highlighting', function() [6] = {italic = true, background = colors.Magenta}, [7] = {bold = true, background = colors.Yellow}, } ) - execute('set hlsearch') + feed_command('set hlsearch') insert([[ very special text ]]) - execute("syntax on") - execute("highlight MyGroup guibg=Green gui=bold") - execute("highlight MyGroup2 guibg=Magenta gui=italic") - execute("call matchadd('MyGroup', 'special')") - execute("call matchadd('MyGroup2', 'text', 0)") + feed_command("syntax on") + feed_command("highlight MyGroup guibg=Green gui=bold") + feed_command("highlight MyGroup2 guibg=Magenta gui=italic") + feed_command("call matchadd('MyGroup', 'special')") + feed_command("call matchadd('MyGroup2', 'text', 0)") -- searchhl and matchadd matches are exclusive, only the higest priority -- is used (and matches with lower priorities are not combined) - execute("/ial te") + feed_command("/ial te") screen:expect([[ very {5:spec^ial}{2: te}{6:xt} | | @@ -257,7 +257,7 @@ describe('search highlighting', function() {4:search hit BOTTOM, continuing at TOP} | ]]) - execute("call clearmatches()") + feed_command("call clearmatches()") screen:expect([[ very spec{2:^ial te}xt | | @@ -270,7 +270,7 @@ describe('search highlighting', function() -- searchhl has priority over syntax, but in this case -- nonconflicting attributes are combined - execute("syntax keyword MyGroup special") + feed_command("syntax keyword MyGroup special") screen:expect([[ very {5:spec}{7:^ial}{2: te}xt | | diff --git a/test/functional/ui/sign_spec.lua b/test/functional/ui/sign_spec.lua index d02fc83809..e5c96f2ec0 100644 --- a/test/functional/ui/sign_spec.lua +++ b/test/functional/ui/sign_spec.lua @@ -1,6 +1,6 @@ local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') -local clear, feed, execute = helpers.clear, helpers.feed, helpers.execute +local clear, feed, command = helpers.clear, helpers.feed, helpers.command if helpers.pending_win32(pending) then return end @@ -25,11 +25,11 @@ describe('Signs', function() describe(':sign place', function() it('shadows previously placed signs', function() feed('ia<cr>b<cr>c<cr><esc>') - execute('sign define piet text=>> texthl=Search') - execute('sign define pietx text=>! texthl=Search') - execute('sign place 1 line=1 name=piet buffer=1') - execute('sign place 2 line=3 name=piet buffer=1') - execute('sign place 3 line=1 name=pietx buffer=1') + command('sign define piet text=>> texthl=Search') + command('sign define pietx text=>! texthl=Search') + command('sign place 1 line=1 name=piet buffer=1') + command('sign place 2 line=3 name=piet buffer=1') + command('sign place 3 line=1 name=pietx buffer=1') screen:expect([[ {1:>!}a | {2: }b | @@ -44,7 +44,7 @@ describe('Signs', function() {2: }{0:~ }| {2: }{0:~ }| {2: }{0:~ }| - :sign place 3 line=1 name=pietx buffer=1 | + | ]]) end) end) diff --git a/test/functional/ui/syntax_conceal_spec.lua b/test/functional/ui/syntax_conceal_spec.lua index ee3e4fa32a..28a104360d 100644 --- a/test/functional/ui/syntax_conceal_spec.lua +++ b/test/functional/ui/syntax_conceal_spec.lua @@ -1,6 +1,6 @@ local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') -local clear, feed, execute = helpers.clear, helpers.feed, helpers.execute +local clear, feed, command = helpers.clear, helpers.feed, helpers.command local insert = helpers.insert if helpers.pending_win32(pending) then return end @@ -25,7 +25,7 @@ describe('Screen', function() describe("match and conceal", function() before_each(function() - execute("let &conceallevel=1") + command("let &conceallevel=1") end) describe("multiple", function() @@ -38,7 +38,7 @@ describe('Screen', function() && && ]]) - execute("syn match dAmpersand '[&][&]' conceal cchar=∧") + command("syn match dAmpersand '[&][&]' conceal cchar=∧") end) it("double characters.", function() @@ -52,7 +52,7 @@ describe('Screen', function() ^ | {0:~ }| {0:~ }| - :syn match dAmpersand '[&][&]' conceal cchar=∧ | + | ]]) end) @@ -68,7 +68,7 @@ describe('Screen', function() | {0:~ }| {0:~ }| - :syn match dAmpersand '[&][&]' conceal cchar=∧ | + | ]]) end) @@ -84,7 +84,7 @@ describe('Screen', function() | {0:~ }| {0:~ }| - :syn match dAmpersand '[&][&]' conceal cchar=∧ | + | ]]) end) @@ -100,7 +100,7 @@ describe('Screen', function() | {0:~ }| {0:~ }| - :syn match dAmpersand '[&][&]' conceal cchar=∧ | + | ]]) end) @@ -116,15 +116,15 @@ describe('Screen', function() ^ | {0:~ }| {0:~ }| - :syn match dAmpersand '[&][&]' conceal cchar=∧ | + | ]]) end) end) -- multiple it("keyword instances in initially in the document.", function() feed("2ilambda<cr><ESC>") - execute("let &conceallevel=1") - execute("syn keyword kLambda lambda conceal cchar=λ") + command("let &conceallevel=1") + command("syn keyword kLambda lambda conceal cchar=λ") screen:expect([[ {1:λ} | {1:λ} | @@ -135,7 +135,7 @@ describe('Screen', function() {0:~ }| {0:~ }| {0:~ }| - :syn keyword kLambda lambda conceal cchar=λ | + | ]]) end) -- Keyword @@ -144,11 +144,11 @@ describe('Screen', function() before_each(function() feed("2") insert("<r> a region of text </r>\n") - execute("let &conceallevel=1") + command("let &conceallevel=1") end) it('initially and conceal it.', function() - execute("syn region rText start='<r>' end='</r>' conceal cchar=R") + command("syn region rText start='<r>' end='</r>' conceal cchar=R") screen:expect([[ {1:R} | {1:R} | @@ -166,7 +166,7 @@ describe('Screen', function() it('initially and conceal its start tag and end tag.', function() -- concealends has a known bug (todo.txt) where the first match won't -- be replaced with cchar. - execute("syn region rText matchgroup=rMatch start='<r>' end='</r>' concealends cchar=-") + command("syn region rText matchgroup=rMatch start='<r>' end='</r>' concealends cchar=-") screen:expect([[ {1: } a region of text {1:-} | {1: } a region of text {1:-} | @@ -182,7 +182,7 @@ describe('Screen', function() end) it('that are nested and conceal the nested region\'s start and end tags.', function() - execute("syn region rText contains=rText matchgroup=rMatch start='<r>' end='</r>' concealends cchar=-") + command("syn region rText contains=rText matchgroup=rMatch start='<r>' end='</r>' concealends cchar=-") insert("<r> A region with <r> a nested <r> nested region.</r> </r> </r>\n") screen:expect([[ {1: } a region of text {1:-} | @@ -201,10 +201,10 @@ describe('Screen', function() describe("a region of text", function() before_each(function() - execute("syntax conceal on") + command("syntax conceal on") feed("2") insert("<r> a region of text </r>\n") - execute("syn region rText start='<r>' end='</r>' cchar=-") + command("syn region rText start='<r>' end='</r>' cchar=-") end) it("and turn on implicit concealing", function() @@ -218,15 +218,15 @@ describe('Screen', function() {0:~ }| {0:~ }| {0:~ }| - :syn region rText start='<r>' end='</r>' cchar=- | + | ]]) end) it("and then turn on, then off, and then back on implicit concealing.", function() - execute("syntax conceal off") + command("syntax conceal off") feed("2") insert("<i> italian text </i>\n") - execute("syn region iText start='<i>' end='</i>' cchar=*") + command("syn region iText start='<i>' end='</i>' cchar=*") screen:expect([[ {1:-} | {1:-} | @@ -237,10 +237,10 @@ describe('Screen', function() {0:~ }| {0:~ }| {0:~ }| - :syn region iText start='<i>' end='</i>' cchar=* | + | ]]) - execute("syntax conceal on") - execute("syn region iText start='<i>' end='</i>' cchar=*") + command("syntax conceal on") + command("syn region iText start='<i>' end='</i>' cchar=*") screen:expect([[ {1:-} | {1:-} | @@ -251,7 +251,7 @@ describe('Screen', function() {0:~ }| {0:~ }| {0:~ }| - :syn region iText start='<i>' end='</i>' cchar=* | + | ]]) end) end) -- a region of text (implicit concealing) @@ -262,13 +262,13 @@ describe('Screen', function() insert("// No Conceal\n") insert('"Conceal without a cchar"\n') insert("+ With cchar\n\n") - execute("syn match noConceal '^//.*$'") - execute("syn match concealNoCchar '\".\\{-}\"$' conceal") - execute("syn match concealWCchar '^+.\\{-}$' conceal cchar=C") + command("syn match noConceal '^//.*$'") + command("syn match concealNoCchar '\".\\{-}\"$' conceal") + command("syn match concealWCchar '^+.\\{-}$' conceal cchar=C") end) it("0. No concealing.", function() - execute("let &conceallevel=0") + command("let &conceallevel=0") screen:expect([[ // No Conceal | "Conceal without a cchar" | @@ -279,12 +279,12 @@ describe('Screen', function() {0:~ }| {0:~ }| {0:~ }| - :let &conceallevel=0 | + | ]]) end) it("1. Conceal using cchar or reference listchars.", function() - execute("let &conceallevel=1") + command("let &conceallevel=1") screen:expect([[ // No Conceal | {1: } | @@ -295,12 +295,12 @@ describe('Screen', function() {0:~ }| {0:~ }| {0:~ }| - :let &conceallevel=1 | + | ]]) end) it("2. Hidden unless cchar is set.", function() - execute("let &conceallevel=2") + command("let &conceallevel=2") screen:expect([[ // No Conceal | | @@ -311,12 +311,12 @@ describe('Screen', function() {0:~ }| {0:~ }| {0:~ }| - :let &conceallevel=2 | + | ]]) end) it("3. Hide all concealed text.", function() - execute("let &conceallevel=3") + command("let &conceallevel=3") screen:expect([[ // No Conceal | | @@ -327,7 +327,7 @@ describe('Screen', function() {0:~ }| {0:~ }| {0:~ }| - :let &conceallevel=3 | + | ]]) end) end) -- conceallevel diff --git a/test/functional/ui/tabline_spec.lua b/test/functional/ui/tabline_spec.lua new file mode 100644 index 0000000000..e8271de0bf --- /dev/null +++ b/test/functional/ui/tabline_spec.lua @@ -0,0 +1,54 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') +local clear, command, eq = helpers.clear, helpers.command, helpers.eq + +describe('ui/ext_tabline', function() + local screen + local event_tabs, event_curtab + + before_each(function() + clear() + screen = Screen.new(25, 5) + screen:attach({rgb=true, ext_tabline=true}) + screen:set_on_event_handler(function(name, data) + if name == "tabline_update" then + event_curtab, event_tabs = unpack(data) + end + end) + end) + + after_each(function() + screen:detach() + end) + + it('publishes UI events', function() + command("tabedit another-tab") + + local expected_tabs = { + {tab = { id = 1 }, name = '[No Name]'}, + {tab = { id = 2 }, name = 'another-tab'}, + } + screen:expect([[ + ^ | + ~ | + ~ | + ~ | + | + ]], nil, nil, function() + eq({ id = 2 }, event_curtab) + eq(expected_tabs, event_tabs) + end) + + command("tabNext") + screen:expect([[ + ^ | + ~ | + ~ | + ~ | + | + ]], nil, nil, function() + eq({ id = 1 }, event_curtab) + eq(expected_tabs, event_tabs) + end) + end) +end) diff --git a/test/functional/ui/wildmode_spec.lua b/test/functional/ui/wildmode_spec.lua index 6a6dc99c3d..042969357e 100644 --- a/test/functional/ui/wildmode_spec.lua +++ b/test/functional/ui/wildmode_spec.lua @@ -1,36 +1,151 @@ local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') -local clear, feed, execute = helpers.clear, helpers.feed, helpers.execute +local clear, feed, command = helpers.clear, helpers.feed, helpers.command +local iswin = helpers.iswin local funcs = helpers.funcs +local eq = helpers.eq +local eval = helpers.eval +local retry = helpers.retry -if helpers.pending_win32(pending) then return end - -describe("'wildmode'", function() +describe("'wildmenu'", function() local screen - before_each(function() clear() screen = Screen.new(25, 5) screen:attach() end) - after_each(function() screen:detach() end) - describe("'wildmenu'", function() - it(':sign <tab> shows wildmenu completions', function() - execute('set wildmode=full') - execute('set wildmenu') - feed(':sign <tab>') - screen:expect([[ - | - ~ | - ~ | - define jump list > | - :sign define^ | - ]]) + it(':sign <tab> shows wildmenu completions', function() + command('set wildmode=full') + command('set wildmenu') + feed(':sign <tab>') + screen:expect([[ + | + ~ | + ~ | + define jump list > | + :sign define^ | + ]]) + end) + + it('does not crash after cycling back to original text', function() + command('set wildmode=full') + feed(':j<Tab><Tab><Tab>') + screen:expect([[ + | + ~ | + ~ | + join jumps | + :j^ | + ]]) + -- This would cause nvim to crash before #6650 + feed('<BS><Tab>') + screen:expect([[ + | + ~ | + ~ | + ! # & < = > @ > | + :!^ | + ]]) + end) + + it('is preserved during :terminal activity', function() + -- Because this test verifies a _lack_ of activity after screen:sleep(), we + -- must wait the full timeout. So make it reasonable. + screen.timeout = 1000 + + command('set wildmenu wildmode=full') + command('set scrollback=4') + if iswin() then + if helpers.pending_win32(pending) then return end + -- feed([[:terminal 1,2,3,4,5 | foreach-object -process {echo $_; sleep 0.1}]]) + else + feed([[:terminal for i in $(seq 1 5000); do printf 'foo\nfoo\nfoo\n'; sleep 0.1; done<cr>]]) + end + + feed([[<C-\><C-N>gg]]) + feed([[:sign <Tab>]]) -- Invoke wildmenu. + screen:sleep(50) -- Allow some terminal output. + screen:expect([[ + foo | + foo | + foo | + define jump list > | + :sign define^ | + ]]) + + -- cmdline CTRL-D display should also be preserved. + feed([[<C-\><C-N>]]) + feed([[:sign <C-D>]]) -- Invoke cmdline CTRL-D. + screen:sleep(50) -- Allow some terminal output. + screen:expect([[ + :sign | + define place | + jump undefine | + list unplace | + :sign ^ | + ]]) + + -- Exiting cmdline should show the buffer. + feed([[<C-\><C-N>]]) + screen:expect([[ + ^foo | + foo | + foo | + foo | + | + ]]) + end) + + it('ignores :redrawstatus called from a timer #7108', function() + -- Because this test verifies a _lack_ of activity after screen:sleep(), we + -- must wait the full timeout. So make it reasonable. + screen.timeout = 1000 + + command('set wildmenu wildmode=full') + command([[call timer_start(10, {->execute('redrawstatus')}, {'repeat':-1})]]) + feed([[<C-\><C-N>]]) + feed([[:sign <Tab>]]) -- Invoke wildmenu. + screen:sleep(30) -- Allow some timer activity. + screen:expect([[ + | + ~ | + ~ | + define jump list > | + :sign define^ | + ]]) + end) + + it('with laststatus=0, :vsplit, :term #2255', function() + -- Because this test verifies a _lack_ of activity after screen:sleep(), we + -- must wait the full timeout. So make it reasonable. + screen.timeout = 1000 + + if not iswin() then + command('set shell=sh') -- Need a predictable "$" prompt. + end + command('set laststatus=0') + command('vsplit') + command('term') + + -- Check for a shell prompt to verify that the terminal loaded. + retry(nil, nil, function() + if iswin() then + eq('Microsoft', eval("matchstr(join(getline(1, '$')), 'Microsoft')")) + else + eq('$', eval([[matchstr(getline(1), '\$')]])) + end end) + + feed([[<C-\><C-N>]]) + feed([[:<Tab>]]) -- Invoke wildmenu. + screen:sleep(10) -- Flush + -- Check only the last 2 lines, because the shell output is + -- system-dependent. + screen:expect('! # & < = > @ > \n:!^', nil, nil, nil, true) end) end) @@ -50,10 +165,10 @@ describe('command line completion', function() it('lists directories with empty PATH', function() local tmp = funcs.tempname() - execute('e '.. tmp) - execute('cd %:h') - execute("call mkdir('Xtest-functional-viml-compl-dir')") - execute('let $PATH=""') + command('e '.. tmp) + command('cd %:h') + command("call mkdir('Xtest-functional-viml-compl-dir')") + command('let $PATH=""') feed(':!<tab><bs>') screen:expect([[ | @@ -64,3 +179,100 @@ describe('command line completion', function() ]]) end) end) + +describe('ui/ext_wildmenu', function() + local screen + local items, selected = nil, nil + + before_each(function() + clear() + screen = Screen.new(25, 5) + screen:attach({rgb=true, ext_wildmenu=true}) + screen:set_on_event_handler(function(name, data) + if name == "wildmenu_show" then + items = data[1] + elseif name == "wildmenu_select" then + selected = data[1] + elseif name == "wildmenu_hide" then + items, selected = nil, nil + end + end) + end) + + after_each(function() + screen:detach() + end) + + it('works with :sign <tab>', function() + local expected = { + 'define', + 'jump', + 'list', + 'place', + 'undefine', + 'unplace', + } + + command('set wildmode=full') + command('set wildmenu') + feed(':sign <tab>') + screen:expect([[ + | + ~ | + ~ | + ~ | + :sign define^ | + ]], nil, nil, function() + eq(expected, items) + eq(0, selected) + end) + + feed('<tab>') + screen:expect([[ + | + ~ | + ~ | + ~ | + :sign jump^ | + ]], nil, nil, function() + eq(expected, items) + eq(1, selected) + end) + + feed('<left><left>') + screen:expect([[ + | + ~ | + ~ | + ~ | + :sign ^ | + ]], nil, nil, function() + eq(expected, items) + eq(-1, selected) + end) + + feed('<right>') + screen:expect([[ + | + ~ | + ~ | + ~ | + :sign define^ | + ]], nil, nil, function() + eq(expected, items) + eq(0, selected) + end) + + feed('a') + screen:expect([[ + | + ~ | + ~ | + ~ | + :sign definea^ | + ]], nil, nil, function() + eq(nil, items) + eq(nil, selected) + end) + end) +end) |