local helpers = require('test.functional.helpers')(after_each) local clear = helpers.clear local feed = helpers.feed local eq = helpers.eq local expect = helpers.expect local eval = helpers.eval local funcs = helpers.funcs local insert = helpers.insert local write_file = helpers.write_file local exc_exec = helpers.exc_exec local command = helpers.command local Screen = require('test.functional.ui.screen') describe('mappings with ', function() local screen local tmpfile = 'X_ex_cmds_cmd_map' local function cmdmap(lhs, rhs) command('noremap '..lhs..' '..rhs..'') command('noremap! '..lhs..' '..rhs..'') end before_each(function() clear() screen = Screen.new(65, 8) screen:set_default_attr_ids({ [1] = {bold = true, foreground = Screen.colors.Blue1}, [2] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red}, [3] = {bold = true, foreground = Screen.colors.SeaGreen4}, [4] = {bold = true}, [5] = {background = Screen.colors.LightGrey}, [6] = {foreground = Screen.colors.Blue1}, [7] = {bold = true, reverse = true}, [8] = {background = Screen.colors.WebGray}, [9] = {background = Screen.colors.LightMagenta}, [10] = {foreground = Screen.colors.Red}, }) screen:attach() cmdmap('', 'let m = mode(1)') cmdmap('', 'normal! ww') cmdmap('', 'normal! "ay') cmdmap('', 'throw "very error"') command([[ function! TextObj() if mode() !=# "v" normal! v end call cursor(1,3) normal! o call cursor(2,4) endfunction]]) cmdmap('', 'call TextObj()') insert([[ some short lines of test text]]) feed('gg') cmdmap('', 'startinsert') cmdmap('', 'stopinsert') command("abbr foo let g:y = 17bar") end) after_each(function() os.remove(tmpfile) end) it('can be displayed', function() command('map ') screen:expect([[ ^some short lines | of test text | {1:~ }| {1:~ }| {1:~ }| {1:~ }| {1:~ }| {6:} {6:*} {6:}let m = mode(1){6:} | ]]) end) it('handles invalid mappings', function() command('let x = 0') command('noremap let x = 1') feed('') screen:expect([[ ^some short lines | of test text | {1:~ }| {1:~ }| {1:~ }| {1:~ }| {1:~ }| {2:E1136: mapping must end with before second } | ]]) command('noremap let x = 3') feed('') screen:expect([[ ^some short lines | of test text | {1:~ }| {1:~ }| {1:~ }| {1:~ }| {1:~ }| {2:E1255: mapping must end with } | ]]) eq(0, eval('x')) end) it('allows special keys and modifiers', function() command('noremap normal! ') feed('') screen:expect([[ some short lines | ^of test text | {1:~ }| {1:~ }| {1:~ }| {1:~ }| {1:~ }| | ]]) command('noremap normal! ') feed('') screen:expect([[ some short lines | of ^test text | {1:~ }| {1:~ }| {1:~ }| {1:~ }| {1:~ }| | ]]) end) it('handles string containing K_SPECIAL (0x80) bytes correctly', function() command([[noremap let g:str = 'foo…bar']]) feed('') eq('foo…bar', eval('g:str')) local str = eval([["foo\bar"]]) command([[noremap let g:str = ']]..str..[[']]) feed('') eq(str, eval('g:str')) command([[noremap let g:str = 'foobar']]) feed('') eq(str, eval('g:str')) end) it('works in various modes and sees correct `mode()` value', function() -- normal mode feed('') eq('n', eval('m')) -- visual mode feed('v') eq('v', eval('m')) -- didn't leave visual mode eq('v', eval('mode(1)')) feed('') eq('n', eval('mode(1)')) -- visual mapping in select mode feed('gh') eq('v', eval('m')) -- didn't leave select mode eq('s', eval('mode(1)')) feed('') eq('n', eval('mode(1)')) -- select mode mapping command('snoremap let m = mode(1)') feed('gh') eq('s', eval('m')) -- didn't leave select mode eq('s', eval('mode(1)')) feed('') eq('n', eval('mode(1)')) -- operator-pending mode feed("d") eq('no', eval('m')) -- did leave operator-pending mode eq('n', eval('mode(1)')) --insert mode feed('i') eq('i', eval('m')) eq('i', eval('mode(1)')) -- replace mode feed("") eq('R', eval('m')) eq('R', eval('mode(1)')) feed('') eq('n', eval('mode(1)')) -- virtual replace mode feed("gR") eq('Rv', eval('m')) eq('Rv', eval('mode(1)')) feed('') eq('n', eval('mode(1)')) -- langmap works, but is not distinguished in mode(1) feed(":set iminsert=1i") eq('i', eval('m')) eq('i', eval('mode(1)')) feed('') eq('n', eval('mode(1)')) feed(':') eq('c', eval('m')) eq('c', eval('mode(1)')) feed('') eq('n', eval('mode(1)')) -- terminal mode command('tnoremap let m = mode(1)') command('split | terminal') feed('i') eq('t', eval('mode(1)')) feed('') eq('t', eval('m')) eq('t', eval('mode(1)')) end) it('works in normal mode', function() cmdmap('', 'let s = [mode(1), v:count, v:register]') -- check v:count and v:register works feed('') eq({'n', 0, '"'}, eval('s')) feed('7') eq({'n', 7, '"'}, eval('s')) feed('"e') eq({'n', 0, 'e'}, eval('s')) feed('5"k') eq({'n', 5, 'k'}, eval('s')) feed('"+2') eq({'n', 2, '+'}, eval('s')) -- text object enters visual mode feed('') screen:expect([[ so{5:me short lines} | {5:of }^test text | {1:~ }| {1:~ }| {1:~ }| {1:~ }| {1:~ }| {4:-- VISUAL --} | ]]) feed('') -- startinsert feed('') eq('i', eval('mode(1)')) feed('') eq('n', eval('mode(1)')) cmdmap(',a', 'call feedkeys("aalpha") \\| let g:a = getline(2)') cmdmap(',b', 'call feedkeys("abeta", "x") \\| let g:b = getline(2)') feed(',a') screen:expect([[ some short lines | of alpha^test text | {1:~ }| {1:~ }| {1:~ }| {1:~ }| {1:~ }| {4:-- INSERT --} | ]]) -- feedkeys were not executed immediately eq({'n', 'of test text'}, eval('[m,a]')) eq('i', eval('mode(1)')) feed('') feed(',b') screen:expect([[ some short lines | of alphabet^atest text | {1:~ }| {1:~ }| {1:~ }| {1:~ }| {1:~ }| | ]]) -- feedkeys(..., 'x') was executed immediately, but insert mode gets aborted eq({'n', 'of alphabetatest text'}, eval('[m,b]')) eq('n', eval('mode(1)')) end) it('works in :normal command', function() command('noremap ,x call append(1, "xx")\\| call append(1, "aa")') command('noremap ,f nosuchcommand') command('noremap ,e throw "very error"\\| call append(1, "yy")') command('noremap ,m echoerr "The message."\\| call append(1, "zz")') command('noremap ,w for i in range(5)\\|if i==1\\|echoerr "Err"\\|endif\\|call append(1, i)\\|endfor') feed(":normal ,x") screen:expect([[ ^some short lines | aa | xx | of test text | {1:~ }| {1:~ }| {1:~ }| :normal ,x | ]]) eq('Vim:E492: Not an editor command: nosuchcommand', exc_exec("normal ,f")) eq('very error', exc_exec("normal ,e")) eq('Vim(echoerr):The message.', exc_exec("normal ,m")) feed('w') screen:expect([[ some ^short lines | aa | xx | of test text | {1:~ }| {1:~ }| {1:~ }| :normal ,x | ]]) command(':%d') eq('Vim(echoerr):Err', exc_exec("normal ,w")) screen:expect([[ ^ | 0 | {1:~ }| {1:~ }| {1:~ }| {1:~ }| {1:~ }| --No lines in buffer-- | ]]) command(':%d') feed(':normal ,w') screen:expect([[ ^ | 4 | 3 | 2 | 1 | 0 | {1:~ }| {2:Err} | ]]) end) it('works in visual mode', function() -- can extend visual mode feed('v') screen:expect([[ {5:some short }^lines | of test text | {1:~ }| {1:~ }| {1:~ }| {1:~ }| {1:~ }| {4:-- VISUAL --} | ]]) eq('v', funcs.mode(1)) -- can invoke operator, ending visual mode feed('') eq('n', funcs.mode(1)) eq({'some short l'}, funcs.getreg('a',1,1)) -- error doesn't interrupt visual mode feed('ggvw') screen:expect([[ {5:some }short lines | of test text | {1:~ }| {1:~ }| {7: }| {2:Error detected while processing :} | {2:E605: Exception not caught: very error} | {3:Press ENTER or type command to continue}^ | ]]) feed('') eq('E605: Exception not caught: very error', eval('v:errmsg')) -- still in visual mode, was consumed by the error prompt screen:expect([[ {5:some }^short lines | of test text | {1:~ }| {1:~ }| {1:~ }| {1:~ }| {1:~ }| {4:-- VISUAL --} | ]]) eq('v', funcs.mode(1)) feed('') screen:expect([[ so{5:me short lines} | {5:of }^test text | {1:~ }| {1:~ }| {1:~ }| {1:~ }| {1:~ }| {4:-- VISUAL --} | ]]) eq('v', funcs.mode(1)) -- startinsert gives "-- (insert) VISUAL --" mode feed('') screen:expect([[ so{5:me short lines} | {5:of }^test text | {1:~ }| {1:~ }| {1:~ }| {1:~ }| {1:~ }| {4:-- (insert) VISUAL --} | ]]) eq('v', eval('mode(1)')) feed('') eq('i', eval('mode(1)')) end) it('works in select mode', function() command('snoremap throw "very error"') command('snoremap normal! "by') -- can extend select mode feed('gh') screen:expect([[ {5:some short }^lines | of test text | {1:~ }| {1:~ }| {1:~ }| {1:~ }| {1:~ }| {4:-- SELECT --} | ]]) eq('s', funcs.mode(1)) -- visual mapping in select mode restart selct mode after operator feed('') eq('s', funcs.mode(1)) eq({'some short l'}, funcs.getreg('a',1,1)) -- select mode mapping works, and does not restart select mode feed('') eq('n', funcs.mode(1)) eq({'some short l'}, funcs.getreg('b',1,1)) -- error doesn't interrupt temporary visual mode feed('ggvw') screen:expect([[ {5:some }short lines | of test text | {1:~ }| {1:~ }| {7: }| {2:Error detected while processing :} | {2:E605: Exception not caught: very error} | {3:Press ENTER or type command to continue}^ | ]]) feed('') eq('E605: Exception not caught: very error', eval('v:errmsg')) -- still in visual mode, was consumed by the error prompt screen:expect([[ {5:some }^short lines | of test text | {1:~ }| {1:~ }| {1:~ }| {1:~ }| {1:~ }| {4:-- VISUAL --} | ]]) -- quirk: restoration of select mode is not performed eq('v', funcs.mode(1)) -- error doesn't interrupt select mode feed('ggvw') screen:expect([[ {5:some }short lines | of test text | {1:~ }| {1:~ }| {7: }| {2:Error detected while processing :} | {2:E605: Exception not caught: very error} | {3:Press ENTER or type command to continue}^ | ]]) feed('') eq('E605: Exception not caught: very error', eval('v:errmsg')) -- still in select mode, was consumed by the error prompt screen:expect([[ {5:some }^short lines | of test text | {1:~ }| {1:~ }| {1:~ }| {1:~ }| {1:~ }| {4:-- SELECT --} | ]]) -- quirk: restoration of select mode is not performed eq('s', funcs.mode(1)) feed('') screen:expect([[ so{5:me short lines} | {5:of }^test text | {1:~ }| {1:~ }| {1:~ }| {1:~ }| {1:~ }| {4:-- SELECT --} | ]]) eq('s', funcs.mode(1)) -- startinsert gives "-- SELECT (insert) --" mode feed('') screen:expect([[ so{5:me short lines} | {5:of }^test text | {1:~ }| {1:~ }| {1:~ }| {1:~ }| {1:~ }| {4:-- (insert) SELECT --} | ]]) eq('s', eval('mode(1)')) feed('') eq('i', eval('mode(1)')) end) it('works in operator-pending mode', function() feed('d') expect([[ lines of test text]]) eq({'some short '}, funcs.getreg('"',1,1)) feed('.') expect([[ test text]]) eq({'lines', 'of '}, funcs.getreg('"',1,1)) feed('uu') expect([[ some short lines of test text]]) -- error aborts operator-pending, operator not performed feed('d') screen:expect([[ some short lines | of test text | {1:~ }| {1:~ }| {7: }| {2:Error detected while processing :} | {2:E605: Exception not caught: very error} | {3:Press ENTER or type command to continue}^ | ]]) feed('') eq('E605: Exception not caught: very error', eval('v:errmsg')) expect([[ some short lines of test text]]) feed('"bd') expect([[ soest text]]) eq(funcs.getreg('b',1,1), {'me short lines', 'of t'}) -- startinsert aborts operator feed('d') eq('i', eval('mode(1)')) expect([[ soest text]]) end) it('works in insert mode', function() -- works the same as ww feed('iindeed little ') screen:expect([[ indeed some short little ^lines | of test text | {1:~ }| {1:~ }| {1:~ }| {1:~ }| {1:~ }| {4:-- INSERT --} | ]]) feed('') screen:expect([[ indeed some short little lines | of test text | {1:~ }| {1:~ }| {7: }| {2:Error detected while processing :} | {2:E605: Exception not caught: very error} | {3:Press ENTER or type command to continue}^ | ]]) feed('') eq('E605: Exception not caught: very error', eval('v:errmsg')) -- still in insert screen:expect([[ indeed some short little ^lines | of test text | {1:~ }| {1:~ }| {1:~ }| {1:~ }| {1:~ }| {4:-- INSERT --} | ]]) eq('i', eval('mode(1)')) -- When entering visual mode from InsertEnter autocmd, an async event, or -- a mapping, vim ends up in undocumented "INSERT VISUAL" mode. If a -- vim patch decides to disable this mode, this test is expected to fail. feed('stuff ') screen:expect([[ in{5:deed some short little lines} | {5:of stuff }^test text | {1:~ }| {1:~ }| {1:~ }| {1:~ }| {1:~ }| {4:-- INSERT VISUAL --} | ]]) expect([[ indeed some short little lines of stuff test text]]) feed('') eq(funcs.getreg('a',1,1), {'deed some short little lines', 'of stuff t'}) -- still in insert screen:expect([[ in^deed some short little lines | of stuff test text | {1:~ }| {1:~ }| {1:~ }| {1:~ }| {1:~ }| {4:-- INSERT --} | ]]) eq('i', eval('mode(1)')) -- also works as part of abbreviation feed('foo ') screen:expect([[ in bar ^deed some short little lines | of stuff test text | {1:~ }| {1:~ }| {1:~ }| {1:~ }| {1:~ }| {4:-- INSERT --} | ]]) eq(17, eval('g:y')) -- :startinsert does nothing feed('') eq('i', eval('mode(1)')) -- :stopinsert works feed('') eq('n', eval('mode(1)')) end) it('works in insert completion (Ctrl-X) mode', function() feed('os') screen:expect([[ some short lines | some^ | {8:some } | {9:short }{1: }| {1:~ }| {1:~ }| {1:~ }| {4:-- Keyword Local completion (^N^P) }{3:match 1 of 2} | ]]) feed('') eq('ic', eval('m')) -- ensure a redraw, this would have moved the cursor -- instead if CTRL-X mode was left. feed('') screen:expect([[ some short lines | some^ | {9:some } | {9:short }{1: }| {1:~ }| {1:~ }| {1:~ }| {4:-- Keyword Local completion (^N^P) }{10:Back at original} | ]]) end) it('works in cmdline mode', function() feed(':text') eq('c', eval('m')) -- didn't leave cmdline mode eq('c', eval('mode(1)')) feed('') eq('n', eval('mode(1)')) screen:expect([[ ^some short lines | of test text | {1:~ }| {1:~ }| {1:~ }| {1:~ }| {1:~ }| {2:E492: Not an editor command: text} | ]]) feed(':echo 2') screen:expect([[ some short lines | of test text | {1:~ }| {7: }| :echo 2 | {2:Error detected while processing :} | {2:E605: Exception not caught: very error} | :echo 2^ | ]]) eq('E605: Exception not caught: very error', eval('v:errmsg')) -- didn't leave cmdline mode eq('c', eval('mode(1)')) feed('+2') screen:expect([[ some short lines | of test text | {7: }| :echo 2 | {2:Error detected while processing :} | {2:E605: Exception not caught: very error} | 4 | {3:Press ENTER or type command to continue}^ | ]]) -- however, message scrolling may cause extra CR prompt -- This is consistent with output from async events. feed('') screen:expect([[ ^some short lines | of test text | {1:~ }| {1:~ }| {1:~ }| {1:~ }| {1:~ }| | ]]) eq('n', eval('mode(1)')) feed(':let g:x = 3') screen:expect([[ some short lines | of test text | {1:~ }| {1:~ }| {1:~ }| {1:~ }| {1:~ }| :let g:x = 3^ | ]]) feed('+2') -- cursor was moved in the background screen:expect([[ some short ^lines | of test text | {1:~ }| {1:~ }| {1:~ }| {1:~ }| {1:~ }| :let g:x = 3+2 | ]]) eq(5, eval('g:x')) feed(':let g:y = 7') screen:expect([[ some short lines | of test text | {1:~ }| {1:~ }| {1:~ }| {1:~ }| {1:~ }| :let g:y = 7^ | ]]) eq('c', eval('mode(1)')) feed('+2') -- startinsert takes effect after leaving cmdline mode screen:expect([[ some short ^lines | of test text | {1:~ }| {1:~ }| {1:~ }| {1:~ }| {1:~ }| {4:-- INSERT --} | ]]) eq('i', eval('mode(1)')) eq(9, eval('g:y')) end) it("doesn't crash when invoking cmdline mode recursively #8859", function() cmdmap('', 'norm! :foo') feed(':bar') screen:expect([[ some short lines | of test text | {1:~ }| {1:~ }| {1:~ }| {1:~ }| {1:~ }| :bar^ | ]]) feed('x') screen:expect([[ some short lines | of test text | {1:~ }| {1:~ }| {1:~ }| {1:~ }| {1:~ }| :barx^ | ]]) end) it("works with mappings", function() command('new!') write_file(tmpfile, [[ map call do_it() function! s:do_it() let g:x = 10 endfunction ]]) command('source '..tmpfile) feed('') eq('', eval('v:errmsg')) eq(10, eval('g:x')) end) end)