diff options
Diffstat (limited to 'test/functional/ui')
-rw-r--r-- | test/functional/ui/bufhl_spec.lua | 19 | ||||
-rw-r--r-- | test/functional/ui/cmdline_highlight_spec.lua | 893 | ||||
-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 | 272 | ||||
-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 | 84 | ||||
-rw-r--r-- | test/functional/ui/output_spec.lua | 13 | ||||
-rw-r--r-- | test/functional/ui/quickfix_spec.lua | 52 | ||||
-rw-r--r-- | test/functional/ui/screen.lua | 171 | ||||
-rw-r--r-- | test/functional/ui/screen_basic_spec.lua | 275 | ||||
-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 | 57 | ||||
-rw-r--r-- | test/functional/ui/wildmode_spec.lua | 159 |
17 files changed, 2626 insertions, 662 deletions
diff --git a/test/functional/ui/bufhl_spec.lua b/test/functional/ui/bufhl_spec.lua index 53fe303762..e1e11203e0 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({ @@ -106,7 +107,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 +132,7 @@ describe('Buffer highlighting', function() {1:~ }| {1:~ }| {1:~ }| - :hi ImportantWord gui=bold cterm=bold | + | ]]) end) @@ -145,7 +146,7 @@ describe('Buffer highlighting', function() {1:~ }| {1:~ }| {1:~ }| - :hi ImportantWord gui=bold cterm=bold | + | ]]) end) @@ -159,7 +160,7 @@ describe('Buffer highlighting', function() {1:~ }| {1:~ }| {1:~ }| - :hi ImportantWord gui=bold cterm=bold | + | ]]) end) @@ -175,7 +176,7 @@ describe('Buffer highlighting', function() {1:~ }| {1:~ }| {1:~ }| - :hi ImportantWord gui=bold cterm=bold | + | ]]) end) @@ -192,7 +193,7 @@ describe('Buffer highlighting', function() | ]]) - execute(':3move 4') + command(':3move 4') screen:expect([[ a {5:longer} example | | @@ -201,7 +202,7 @@ describe('Buffer highlighting', function() {1:~ }| {1:~ }| {1:~ }| - ::3move 4 | + | ]]) 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/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..d1357ea525 100644 --- a/test/functional/ui/highlight_spec.lua +++ b/test/functional/ui/highlight_spec.lua @@ -2,23 +2,24 @@ 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, request, eq = helpers.feed_command, helpers.request, 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')) + eq('256', eval('&t_Co')) request('nvim_set_option', 't_Co', '88') - eq('88', request('vim_eval', '&t_Co')) + eq('88', 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 +42,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 +64,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 +108,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 +201,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 +233,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 +324,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 +370,7 @@ describe('guisp (special/undercurl)', function() end) end) -describe("'cursorline' with 'listchars'", function() +describe("'listchars' highlight", function() local screen before_each(function() @@ -419,8 +388,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 +414,7 @@ describe("'cursorline' with 'listchars'", function() {0:~ }| | ]]) - execute('set nocursorline') + feed_command('set nocursorline') screen:expect([[ abcdefg | kkasd^f | @@ -469,8 +438,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 +478,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 +499,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 +507,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 +516,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 +574,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 +613,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 35aeb6e67c..64965ccb94 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 @@ -21,9 +22,9 @@ local default_text = [[ 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}, @@ -42,10 +43,11 @@ local function common_setup(screen, inccommand, text) [14] = {foreground = Screen.colors.White, background = Screen.colors.Red}, [15] = {bold=true, foreground=Screen.colors.Blue}, [16] = {background=Screen.colors.Grey90}, -- cursorline + vis = {background=Screen.colors.LightGrey} }) end - execute("set inccommand=" .. (inccommand and inccommand or "")) + command("set inccommand=" .. (inccommand and inccommand or "")) if text then insert(text) @@ -90,8 +92,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:~ }| @@ -110,25 +112,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) @@ -138,8 +140,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. @@ -156,8 +158,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>]]) @@ -191,7 +193,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") @@ -207,6 +209,42 @@ describe(":substitute, 'inccommand' preserves", function() end) end + for _, case in pairs{"", "split", "nosplit"} do + it("visual selection for non-previewable command (inccommand="..case..") #5888", function() + local screen = Screen.new(30,10) + common_setup(screen, case, default_text) + feed('1G2V') + + feed(':s') + screen:expect([[ + {vis:Inc substitution on} | + t{vis:wo lines} | + | + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + :'<,'>s^ | + ]]) + + feed('o') + screen:expect([[ + {vis:Inc substitution on} | + t{vis:wo lines} | + | + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + :'<,'>so^ | + ]]) + end) + end + end) describe(":substitute, 'inccommand' preserves undo", function() @@ -234,18 +272,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([[ @@ -260,15 +298,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>") @@ -294,7 +332,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>") @@ -315,6 +353,14 @@ describe(":substitute, 'inccommand' preserves undo", function() feed("2u") feed(substring .. "<esc>") + expect([[ + 1]]) + feed("g-") + expect([[ + ]]) + feed("g+") + expect([[ + 1]]) feed("<c-r>") expect([[ 1 @@ -337,9 +383,7 @@ describe(":substitute, 'inccommand' preserves undo", function() 2]]) end - -- TODO(vim): This does not work, even in Vim. - -- Waiting for fix (perhaps from upstream). - pending("at a non-leaf of the undo tree", function() + it("at a non-leaf of the undo tree", function() for _, case in pairs(cases) do for _, str in pairs(substrings) do for _, redoable in pairs({true}) do @@ -381,22 +425,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) @@ -407,13 +451,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") @@ -439,7 +483,7 @@ describe(":substitute, 'inccommand' preserves undo", function() {15:~ }| {15:~ }| {15:~ }| - Already...st change | + Already ...t change | ]]) else screen:expect([[ @@ -452,7 +496,7 @@ describe(":substitute, 'inccommand' preserves undo", function() {15:~ }| {15:~ }| {15:~ }| - Already...st change | + Already ...t change | ]]) end end @@ -465,13 +509,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") @@ -491,7 +535,7 @@ describe(":substitute, 'inccommand' preserves undo", function() {15:~ }| {15:~ }| {15:~ }| - Already...st change | + Already ...t change | ]]) else screen:expect([[ @@ -504,7 +548,7 @@ describe(":substitute, 'inccommand' preserves undo", function() {15:~ }| {15:~ }| {15:~ }| - Already...st change | + Already ...t change | ]]) end @@ -531,7 +575,7 @@ describe(":substitute, 'inccommand' preserves undo", function() {15:~ }| {15:~ }| {15:~ }| - Already...st change | + Already ...t change | ]]) else screen:expect([[ @@ -544,7 +588,7 @@ describe(":substitute, 'inccommand' preserves undo", function() {15:~ }| {15:~ }| {15:~ }| - Already...st change | + Already ...t change | ]]) end screen:detach() @@ -558,9 +602,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([[ @@ -573,7 +617,7 @@ describe(":substitute, 'inccommand' preserves undo", function() {15:~ }| {15:~ }| {15:~ }| - Already...st change | + Already ...t change | ]]) else screen:expect([[ @@ -586,7 +630,7 @@ describe(":substitute, 'inccommand' preserves undo", function() {15:~ }| {15:~ }| {15:~ }| - Already...st change | + Already ...t change | ]]) end @@ -594,7 +638,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>") @@ -610,7 +654,7 @@ describe(":substitute, 'inccommand' preserves undo", function() {15:~ }| {15:~ }| {15:~ }| - Already...st change | + Already ...t change | ]]) end screen:detach() @@ -633,7 +677,7 @@ 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 | @@ -761,7 +805,7 @@ 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 | @@ -782,7 +826,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. @@ -874,7 +918,7 @@ 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([[ @@ -940,9 +984,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() @@ -952,19 +996,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^ | ]]) @@ -975,7 +1019,7 @@ 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") @@ -1036,7 +1080,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 @@ -1071,7 +1115,7 @@ describe("inccommand=nosplit", function() end) it('never shows preview buffer', function() - execute("set hlsearch") + feed_command("set hlsearch") feed(":%s/tw") screen:expect([[ @@ -1132,7 +1176,7 @@ 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") @@ -1181,7 +1225,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 @@ -1193,14 +1237,48 @@ 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) + it('in the range does not error #5912', function() + for _, case in pairs(cases) do + refresh(case) + feed(':100s/') + + screen:expect([[ + Inc substitution on | + two lines | + | + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + :100s/^ | + ]]) + + feed('<enter>') + screen:expect([[ + Inc substitution on | + two lines | + ^ | + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {14:E16: Invalid range} | + ]]) + end + end) + end) describe("'inccommand' and :cnoremap", function() @@ -1214,14 +1292,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 @@ -1232,7 +1310,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([[ @@ -1240,21 +1318,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 @@ -1266,7 +1344,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>") @@ -1279,7 +1357,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")) @@ -1289,7 +1367,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")) @@ -1363,7 +1441,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() @@ -1416,8 +1494,8 @@ 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| @@ -1453,9 +1531,9 @@ 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([[ @@ -1504,7 +1582,7 @@ 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") 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 17d949825a..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 | @@ -658,7 +654,7 @@ describe('Mouse input', function() {4:[No Name] [+] }| :vsp | ]]) - feed('<MouseUp><0,0>') + feed('<ScrollWheelDown><0,0>') screen:expect([[ mouse scrolling {4:|}lines | ^ {4:|}to | @@ -675,7 +671,7 @@ describe('Mouse input', function() {4:[No Name] [+] }| | ]]) - feed('<MouseDown><27,0>') + feed('<ScrollWheelUp><27,0>') screen:expect([[ mouse scrolling {4:|}text | ^ {4:|}with | @@ -692,7 +688,7 @@ describe('Mouse input', function() {4:[No Name] [+] }| | ]]) - feed('<MouseDown><27,7><MouseDown>') + feed('<ScrollWheelUp><27,7><ScrollWheelUp>') screen:expect([[ mouse scrolling {4:|}text | ^ {4:|}with | @@ -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/output_spec.lua b/test/functional/ui/output_spec.lua index d6d8f1c4e5..c6d564e8dc 100644 --- a/test/functional/ui/output_spec.lua +++ b/test/functional/ui/output_spec.lua @@ -8,13 +8,13 @@ describe("shell command :!", function() before_each(function() session.clear() screen = child_session.screen_setup(0, '["'..session.nvim_prog.. - '", "-u", "NONE", "-i", "NONE", "--cmd", "set noswapfile"]') + '", "-u", "NONE", "-i", "NONE", "--cmd", "'..session.nvim_set..'"]') screen:expect([[ {1: } | {4:~ }| {4:~ }| {4:~ }| - {5:[No Name] }| + {4:~ }| | {3:-- TERMINAL --} | ]]) @@ -25,14 +25,14 @@ describe("shell command :!", function() screen:detach() end) - it("displays output even without LF/EOF. #4646 #4569 #3772", function() + it("displays output without LF/EOF. #4646 #4569 #3772", function() -- NOTE: We use a child nvim (within a :term buffer) -- to avoid triggering a UI flush. child_session.feed_data(":!printf foo; sleep 200\n") screen:expect([[ {4:~ }| {4:~ }| - {5:[No Name] }| + {4:~ }| :!printf foo; sleep 200 | | foo | @@ -41,6 +41,11 @@ describe("shell command :!", function() end) it("throttles shell-command output greater than ~10KB", function() + if os.getenv("TRAVIS") and session.os_name() == "osx" then + pending("[Unreliable on Travis macOS.]", function() end) + return + end + screen.timeout = 20000 -- Avoid false failure on slow systems. child_session.feed_data( ":!for i in $(seq 2 3000); do echo XXXXXXXXXX $i; done\n") 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 2581b36711..a6b7fb2997 100644 --- a/test/functional/ui/screen.lua +++ b/test/functional/ui/screen.lua @@ -1,31 +1,17 @@ --- This module contains the Screen class, a complete Nvim screen implementation --- designed for functional testing. The goal is to provide a simple and --- intuitive API for verifying screen state after a set of actions. +-- This module contains the Screen class, a complete Nvim UI implementation +-- designed for functional testing (verifying screen state, in particular). -- --- The screen class exposes a single assertion method, "Screen:expect". This --- method takes a string representing the expected screen state and an optional --- set of attribute identifiers for checking highlighted characters(more on --- this later). --- --- The string passed to "expect" will be processed according to these rules: --- --- - Each line of the string represents and is matched individually against --- a screen row. --- - The entire string is stripped of common indentation --- - Expected screen rows are stripped of the last character. The last --- character should be used to write pipes(|) that make clear where the --- screen ends --- - The last line is stripped, so the string must have (row count + 1) --- lines. +-- Screen:expect() takes a string representing the expected screen state and an +-- optional set of attribute identifiers for checking highlighted characters. -- -- Example usage: -- -- local screen = Screen.new(25, 10) --- -- attach the screen to the current Nvim instance +-- -- Attach the screen to the current Nvim instance. -- screen:attach() --- --enter insert mode and type some text +-- -- Enter insert-mode and type some text. -- feed('ihello screen') --- -- declare an expectation for the eventual screen state +-- -- Assert the expected screen state. -- screen:expect([[ -- hello screen | -- ~ | @@ -39,31 +25,19 @@ -- -- INSERT -- | -- ]]) -- <- Last line is stripped -- --- Since screen updates are received asynchronously, "expect" is actually --- specifying the eventual screen state. This is how "expect" works: It will --- start the event loop with a timeout of 5 seconds. Each time it receives an --- update the expected state will be checked against the updated state. --- --- If the expected state matches the current state, the event loop will be --- stopped and "expect" will return. If the timeout expires, the last match --- error will be reported and the test will fail. +-- Since screen updates are received asynchronously, expect() actually specifies +-- the _eventual_ screen state. -- --- If the second argument is passed to "expect", the screen rows will be --- transformed before being matched against the string lines. The --- transformation rule is simple: Each substring "S" composed with characters --- having the exact same set of attributes will be substituted by "{K:S}", --- where K is a key associated the attribute set via the second argument of --- "expect". --- If a transformation table is present, unexpected attribute sets in the final --- state is considered an error. To make testing simpler, a list of attribute --- sets that should be ignored can be passed as a third argument. Alternatively, --- this third argument can be "true" to indicate that all unexpected attribute --- sets should be ignored. +-- This is how expect() works: +-- * It starts the event loop with a timeout. +-- * Each time it receives an update it checks that against the expected state. +-- * If the expected state matches the current state, the event loop will be +-- stopped and expect() will return. +-- * If the timeout expires, the last match error will be reported and the +-- test will fail. -- --- To illustrate how this works, let's say that in the above example we wanted --- to assert that the "-- INSERT --" string is highlighted with the bold --- attribute(which normally is), here's how the call to "expect" should look --- like: +-- Continuing the above example, say we want to assert that "-- INSERT --" is +-- highlighted with the bold attribute. The expect() call should look like this: -- -- NonText = Screen.colors.Blue -- screen:expect([[ @@ -81,29 +55,21 @@ -- -- In this case "b" is a string associated with the set composed of one -- attribute: bold. Note that since the {b:} markup is not a real part of the --- screen, the delimiter(|) had to be moved right. Also, the highlighting of the --- NonText markers (~) is ignored in this test. +-- screen, the delimiter "|" moved to the right. Also, the highlighting of the +-- NonText markers "~" is ignored in this test. +-- +-- Tests will often share a group of attribute sets to expect(). Those can be +-- defined at the beginning of a test: -- --- Multiple expect:s will likely share a group of attribute sets to test. --- Therefore these could be specified at the beginning of a test like this: -- NonText = Screen.colors.Blue -- screen:set_default_attr_ids( { -- [1] = {reverse = true, bold = true}, -- [2] = {reverse = true} -- }) -- screen:set_default_attr_ignore( {{}, {bold=true, foreground=NonText}} ) --- These can be overridden for a specific expect expression, by passing --- different sets as parameters. -- --- To help writing screen tests, there is a utility function --- "screen:snapshot_util()", that can be placed in a test file at any point an --- "expect(...)" should be. It will wait a short amount of time and then dump --- the current state of the screen, in the form of an "expect(..)" expression --- that would match it exactly. "snapshot_util" optionally also take the --- transformation and ignore set as parameters, like expect, or uses the default --- set. It will generate a larger attribute transformation set, if needed. --- To generate a text-only test without highlight checks, --- use `screen:snapshot_util({},true)` +-- To help write screen tests, see Screen:snapshot_util(). +-- To debug screen tests, see Screen:redraw_debug(). local helpers = require('test.functional.helpers')(nil) local request, run, uimeths = helpers.request, helpers.run, helpers.uimeths @@ -205,24 +171,45 @@ end function Screen:try_resize(columns, rows) uimeths.try_resize(columns, rows) + -- Give ourselves a chance to _handle_resize, which requires using + -- self.sleep() (for the resize notification) rather than run() + self:sleep(0.1) end -- Asserts that `expected` eventually matches the screen state. -- --- expected: Expected screen state (string). --- attr_ids: Text attribute definitions. --- attr_ignore: Ignored text attributes. +-- 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 +-- "{K:S}", where K is a key in `attr_ids`. Any unexpected +-- attributes in the final state are an error. +-- attr_ignore: Ignored text attributes, or `true` to ignore all. -- condition: Function asserting some arbitrary condition. -- 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) + 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 @@ -238,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 @@ -262,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 @@ -296,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]') @@ -329,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 @@ -363,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) @@ -394,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 @@ -541,8 +532,12 @@ function Screen:_current_screen() return table.concat(rv, '\n') end +-- Generates tests. Call it where Screen:expect() would be. Waits briefly, then +-- dumps the current screen state in the form of Screen:expect(). +-- Use snapshot_util({},true) to generate a text-only (no attributes) test. +-- +-- @see Screen:redraw_debug() function Screen:snapshot_util(attrs, ignore) - -- util to generate screen test self:sleep(250) self:print_snapshot(attrs, ignore) end @@ -629,7 +624,7 @@ function Screen:_pprint_attrs(attrs) return table.concat(items, ", ") end -function backward_find_meaningful(tbl, from) -- luacheck: ignore +local function backward_find_meaningful(tbl, from) -- luacheck: no unused for i = from or #tbl, 1, -1 do if tbl[i] ~= ' ' then return i + 1 diff --git a/test/functional/ui/screen_basic_spec.lua b/test/functional/ui/screen_basic_spec.lua index d03f98c26f..bfcdc7f652 100644 --- a/test/functional/ui/screen_basic_spec.lua +++ b/test/functional/ui/screen_basic_spec.lua @@ -1,16 +1,15 @@ 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 -if helpers.pending_win32(pending) then return end - -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', + '--cmd', 'set shortmess+=I background=light noswapfile belloff= noshowcmd noruler', '--embed'} before_each(function() @@ -28,7 +27,7 @@ describe('Initial screen', function() screen:detach() end) - it('is the default initial screen', function() + it('default initial screen', function() screen:expect([[ ^ | {0:~ }| @@ -74,33 +73,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) @@ -108,36 +103,27 @@ 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 + command('set title') + command('file /mydir/myfile') + screen:expect(function() + eq(expected, screen.title) end) end) end) @@ -145,13 +131,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) @@ -159,7 +142,7 @@ describe('Screen', function() describe('window', function() describe('split', function() it('horizontal', function() - execute('sp') + command('sp') screen:expect([[ ^ | {0:~ }| @@ -174,13 +157,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:~ }| @@ -195,12 +178,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:~ }| @@ -240,7 +225,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 | @@ -258,7 +245,7 @@ describe('Screen', function() {3:[No Name] [+] }| | ]]) - execute('tabnew') + command('tabnew') insert('hello2') feed('h') screen:expect([[ @@ -277,7 +264,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 | @@ -322,9 +309,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 | @@ -340,7 +327,7 @@ describe('Screen', function() {0:~ }| {0:~ }| {0:~ }| - :set ruler 1,5 All | + 1,5 All | ]]) end) end) @@ -405,7 +392,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 | @@ -563,11 +552,10 @@ describe('Screen', function() ]]) end) - -- FIXME this has some race conditions that cause it to fail periodically - pending('has minimum width/height values', function() + it('has minimum width/height values', function() screen:try_resize(1, 1) screen:expect([[ - -- INS^ERT --| + {2:-- INS^ERT --}| | ]]) feed('<esc>:ls') @@ -578,116 +566,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() + 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:~ }| - | - ]],nil,nil,function () - eq("normal", screen.mode) - end) - end) - - it('works in insert mode', function() - feed('i') - 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>') + {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([[ - wor^d | - {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('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>') - screen:expect([[ - ^ | - {0:~ }| - {0:~ }| - {0:~ }| - | - ]],nil,nil,function () - eq("normal", screen.mode) - end) - end) +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..56331a33b5 --- /dev/null +++ b/test/functional/ui/tabline_spec.lua @@ -0,0 +1,57 @@ +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/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) + + describe('externalized', function() + 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) +end) diff --git a/test/functional/ui/wildmode_spec.lua b/test/functional/ui/wildmode_spec.lua index 6a6dc99c3d..41a751c284 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([[ | |