diff options
author | Josh Rahm <rahm@google.com> | 2022-07-18 19:37:18 +0000 |
---|---|---|
committer | Josh Rahm <rahm@google.com> | 2022-07-18 19:37:18 +0000 |
commit | 308e1940dcd64aa6c344c403d4f9e0dda58d9c5c (patch) | |
tree | 35fe43e01755e0f312650667004487a44d6b7941 /test/functional/editor | |
parent | 96a00c7c588b2f38a2424aeeb4ea3581d370bf2d (diff) | |
parent | e8c94697bcbe23a5c7b07c292b90a6b70aadfa87 (diff) | |
download | rneovim-308e1940dcd64aa6c344c403d4f9e0dda58d9c5c.tar.gz rneovim-308e1940dcd64aa6c344c403d4f9e0dda58d9c5c.tar.bz2 rneovim-308e1940dcd64aa6c344c403d4f9e0dda58d9c5c.zip |
Merge remote-tracking branch 'upstream/master' into rahm
Diffstat (limited to 'test/functional/editor')
-rw-r--r-- | test/functional/editor/K_spec.lua | 23 | ||||
-rw-r--r-- | test/functional/editor/completion_spec.lua | 60 | ||||
-rw-r--r-- | test/functional/editor/ctrl_c_spec.lua | 94 | ||||
-rw-r--r-- | test/functional/editor/jump_spec.lua | 143 | ||||
-rw-r--r-- | test/functional/editor/langmap_spec.lua | 36 | ||||
-rw-r--r-- | test/functional/editor/macro_spec.lua | 55 | ||||
-rw-r--r-- | test/functional/editor/mark_spec.lua | 401 | ||||
-rw-r--r-- | test/functional/editor/meta_key_spec.lua | 54 | ||||
-rw-r--r-- | test/functional/editor/mode_cmdline_spec.lua | 105 | ||||
-rw-r--r-- | test/functional/editor/mode_insert_spec.lua | 58 | ||||
-rw-r--r-- | test/functional/editor/put_spec.lua | 26 | ||||
-rw-r--r-- | test/functional/editor/tabpage_spec.lua | 28 | ||||
-rw-r--r-- | test/functional/editor/undo_spec.lua | 67 |
13 files changed, 1064 insertions, 86 deletions
diff --git a/test/functional/editor/K_spec.lua b/test/functional/editor/K_spec.lua index 40f36491e4..8ad81ac3d6 100644 --- a/test/functional/editor/K_spec.lua +++ b/test/functional/editor/K_spec.lua @@ -33,6 +33,29 @@ describe('K', function() feed('i'..test_file..'<ESC>K') retry(nil, nil, function() eq(1, eval('filereadable("'..test_file..'")')) end) eq({'fnord'}, eval("readfile('"..test_file.."')")) + -- Confirm that Neovim is still in terminal mode after K is pressed (#16692). + helpers.sleep(500) + eq('t', eval('mode()')) + feed('<space>') -- Any key, not just <space>, can be used here to escape. + eq('n', eval('mode()')) + end) + + it("<esc> kills the buffer for a running 'keywordprg' command", function() + helpers.source('set keywordprg=less') + eval('writefile(["hello", "world"], "' .. test_file .. '")') + feed('i' .. test_file .. '<esc>K') + eq('t', eval('mode()')) + -- Confirm that an arbitrary keypress doesn't escape (i.e., the process is + -- still running). If the process were no longer running, an arbitrary + -- keypress would escape. + helpers.sleep(500) + feed('<space>') + eq('t', eval('mode()')) + -- Confirm that <esc> kills the buffer for the running command. + local bufnr = eval('bufnr()') + feed('<esc>') + eq('n', eval('mode()')) + helpers.neq(bufnr, eval('bufnr()')) end) end) diff --git a/test/functional/editor/completion_spec.lua b/test/functional/editor/completion_spec.lua index befad29922..e27da0947f 100644 --- a/test/functional/editor/completion_spec.lua +++ b/test/functional/editor/completion_spec.lua @@ -1193,4 +1193,64 @@ describe('completion', function() eq('foobar', eval('g:word')) feed('<esc>') end) + + it('is stopped by :stopinsert from timer #12976', function() + screen:try_resize(32,14) + command([[call setline(1, ['hello', 'hullo', 'heeee', ''])]]) + feed('Gah<c-x><c-n>') + screen:expect([[ + hello | + hullo | + heeee | + hello^ | + {2:hello }{0: }| + {1:hullo }{0: }| + {1:heeee }{0: }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {3:-- }{4:match 1 of 3} | + ]]) + command([[call timer_start(100, { -> execute('stopinsert') })]]) + helpers.sleep(200) + feed('k') -- cursor should move up in Normal mode + screen:expect([[ + hello | + hullo | + heee^e | + hello | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + end) + + it('does not crash if text is changed by first call to complete function #17489', function() + source([[ + func Complete(findstart, base) abort + if a:findstart + let col = col('.') + call complete_add('#') + return col - 1 + else + return [] + endif + endfunc + + set completeopt=longest + set completefunc=Complete + ]]) + feed('ifoo#<C-X><C-U>') + assert_alive() + end) end) diff --git a/test/functional/editor/ctrl_c_spec.lua b/test/functional/editor/ctrl_c_spec.lua new file mode 100644 index 0000000000..60131bf2a4 --- /dev/null +++ b/test/functional/editor/ctrl_c_spec.lua @@ -0,0 +1,94 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') +local clear, feed, source = helpers.clear, helpers.feed, helpers.source +local command = helpers.command +local sleep = helpers.sleep + +describe("CTRL-C (mapped)", function() + local screen + + before_each(function() + clear() + screen = Screen.new(52, 6) + screen:attach() + end) + + it("interrupts :global", function() + -- Crashes luajit. + if helpers.skip_fragile(pending) then + return + end + + source([[ + set nomore nohlsearch undolevels=-1 + nnoremap <C-C> <NOP> + ]]) + + command("silent edit! test/functional/fixtures/bigfile.txt") + + screen:expect([[ + ^0000;<control>;Cc;0;BN;;;;;N;NULL;;;; | + 0001;<control>;Cc;0;BN;;;;;N;START OF HEADING;;;; | + 0002;<control>;Cc;0;BN;;;;;N;START OF TEXT;;;; | + 0003;<control>;Cc;0;BN;;;;;N;END OF TEXT;;;; | + 0004;<control>;Cc;0;BN;;;;;N;END OF TRANSMISSION;;;;| + | + ]]) + + local function test_ctrl_c(ms) + feed(":global/^/p<CR>") + screen:sleep(ms) + feed("<C-C>") + screen:expect{any="Interrupt"} + end + + -- The test is time-sensitive. Try different sleep values. + local ms_values = {100, 1000, 10000} + for i, ms in ipairs(ms_values) do + if i < #ms_values then + local status, _ = pcall(test_ctrl_c, ms) + if status then break end + else -- Call the last attempt directly. + test_ctrl_c(ms) + end + end + end) + + it('interrupts :sleep', function() + command('nnoremap <C-C> <Nop>') + feed(':sleep 100<CR>') + -- wait for :sleep to start + sleep(10) + feed('foo<C-C>') + -- wait for input buffer to be flushed + sleep(10) + feed('i') + screen:expect([[ + ^ | + ~ | + ~ | + ~ | + ~ | + -- INSERT -- | + ]]) + end) + + it('interrupts recursive mapping', function() + command('nnoremap <C-C> <Nop>') + command('nmap <F2> <Ignore><F2>') + feed('<F2>') + sleep(10) + feed('foo<C-C>') + -- wait for input buffer to be flushed + sleep(10) + feed('i') + screen:expect([[ + ^ | + ~ | + ~ | + ~ | + ~ | + -- INSERT -- | + ]]) + end) +end) diff --git a/test/functional/editor/jump_spec.lua b/test/functional/editor/jump_spec.lua index d09c20f226..63f522fe6e 100644 --- a/test/functional/editor/jump_spec.lua +++ b/test/functional/editor/jump_spec.lua @@ -1,4 +1,5 @@ local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') local clear = helpers.clear local command = helpers.command @@ -7,6 +8,7 @@ local funcs = helpers.funcs local feed = helpers.feed local exec_capture = helpers.exec_capture local write_file = helpers.write_file +local curbufmeths = helpers.curbufmeths describe('jumplist', function() local fname1 = 'Xtest-functional-normal-jump' @@ -137,3 +139,144 @@ describe("jumpoptions=stack behaves like 'tagstack'", function() exec_capture('jumps')) end) end) + +describe("jumpoptions=view", function() + local file1 = 'Xtestfile-functional-editor-jumps' + local file2 = 'Xtestfile-functional-editor-jumps-2' + local function content() + local c = {} + for i=1,30 do + c[i] = i .. " line" + end + return table.concat(c, "\n") + end + before_each(function() + clear() + write_file(file1, content(), false, false) + write_file(file2, content(), false, false) + command('set jumpoptions=view') + end) + after_each(function() + os.remove(file1) + os.remove(file2) + end) + + it('restores the view', function() + local screen = Screen.new(5, 8) + screen:attach() + command("edit " .. file1) + feed("12Gztj") + feed("gg<C-o>") + screen:expect([[ + 12 line | + ^13 line | + 14 line | + 15 line | + 16 line | + 17 line | + 18 line | + | + ]]) + end) + + it('restores the view across files', function() + local screen = Screen.new(5, 5) + screen:attach() + command("args " .. file1 .. " " .. file2) + feed("12Gzt") + command("next") + feed("G") + screen:expect([[ + 27 line | + 28 line | + 29 line | + ^30 line | + | + ]]) + feed("<C-o><C-o>") + screen:expect([[ + ^12 line | + 13 line | + 14 line | + 15 line | + | + ]]) + end) + + it('restores the view across files with <C-^>', function() + local screen = Screen.new(5, 5) + screen:attach() + command("args " .. file1 .. " " .. file2) + feed("12Gzt") + command("next") + feed("G") + screen:expect([[ + 27 line | + 28 line | + 29 line | + ^30 line | + | + ]]) + feed("<C-^>") + screen:expect([[ + ^12 line | + 13 line | + 14 line | + 15 line | + | + ]]) + end) + + it('falls back to standard behavior when view can\'t be recovered', function() + local screen = Screen.new(5, 8) + screen:attach() + command("edit " .. file1) + feed("7GzbG") + curbufmeths.set_lines(0, 2, true, {}) + -- Move to line 7, and set it as the last line visible on the view with zb, meaning to recover + -- the view it needs to put the cursor 7 lines from the top line. Then go to the end of the + -- file, delete 2 lines before line 7, meaning the jump/mark is moved 2 lines up to line 5. + -- Therefore when trying to jump back to it it's not possible to set a 7 line offset from the + -- mark position to the top line, since there's only 5 lines from the mark position to line 0. + -- Therefore falls back to standard behavior which is centering the view/line. + feed("<C-o>") + screen:expect([[ + 4 line | + 5 line | + 6 line | + ^7 line | + 8 line | + 9 line | + 10 line | + | + ]]) + end) + + it('falls back to standard behavior for a mark without a view', function() + local screen = Screen.new(5, 8) + screen:attach() + command('edit ' .. file1) + feed('10ggzzvwy') + screen:expect([[ + 7 line | + 8 line | + 9 line | + ^10 line | + 11 line | + 12 line | + 13 line | + | + ]]) + feed('`]') + screen:expect([[ + 7 line | + 8 line | + 9 line | + 10 ^line | + 11 line | + 12 line | + 13 line | + | + ]]) + end) +end) diff --git a/test/functional/editor/langmap_spec.lua b/test/functional/editor/langmap_spec.lua index e4349a22e7..b1070ecddc 100644 --- a/test/functional/editor/langmap_spec.lua +++ b/test/functional/editor/langmap_spec.lua @@ -30,7 +30,7 @@ describe("'langmap'", function() command('nmapclear') end) it("'langnoremap' is by default ON", function() - eq(eval('&langnoremap'), 1) + eq(1, eval('&langnoremap')) end) it("Results of maps are not converted when 'langnoremap' ON.", function() @@ -71,19 +71,19 @@ describe("'langmap'", function() feed('<C-w>s') local origwin = curwin() feed('<C-w>i') - neq(curwin(), origwin) + neq(origwin, curwin()) -- Works when setting a mark feed('yy3p3gg0mwgg0mi') - eq(call('getpos', "'i"), {0, 3, 1, 0}) - eq(call('getpos', "'w"), {0, 1, 1, 0}) + eq({0, 3, 1, 0}, call('getpos', "'i")) + eq({0, 1, 1, 0}, call('getpos', "'w")) feed('3dd') -- Works when moving to a mark feed("'i") - eq(call('getpos', '.'), {0, 1, 1, 0}) + eq({0, 1, 1, 0}, call('getpos', '.')) -- Works when selecting a register feed('qillqqwhhq') - eq(eval('@i'), 'hh') - eq(eval('@w'), 'll') + eq('hh', eval('@i')) + eq('ll', eval('@w')) feed('a<C-r>i<esc>') expect('illii www') feed('"ip') @@ -107,7 +107,7 @@ describe("'langmap'", function() expect('wwi www') feed('u@a') expect('wwi www') - eq(eval('@a'), ':s/i/w/gc\ryyn') + eq(':s/i/w/gc\ryyn', eval('@a')) end) it('insert-mode CTRL-G', function() command('set langmap=jk,kj') @@ -127,7 +127,7 @@ describe("'langmap'", function() helhellolo helxlo hello]]) - eq(eval('@a'), 'gg3|ahellojx') + eq('gg3|ahellojx', eval('@a')) end) it('command-line CTRL-\\', function() command('set langmap=en,ne') @@ -145,8 +145,8 @@ describe("'langmap'", function() set langmap=ij,ji ]]) feed(':let <C-R>i=1<CR>') - eq(eval('i_value'), 1) - eq(eval('j_value'), 0) + eq(1, eval('i_value')) + eq(0, eval('j_value')) end) -- it('-- More -- prompt', function() -- -- The 'b' 'j' 'd' 'f' commands at the -- More -- prompt @@ -186,17 +186,17 @@ describe("'langmap'", function() nnoremap x :call Map()<CR> ]]) feed('x1<CR>') - eq(eval('gotten_one'), 1) + eq(1, eval('gotten_one')) command('let g:gotten_one = 0') feed_command('call Map()') feed('1<CR>') - eq(eval('gotten_one'), 1) + eq(1, eval('gotten_one')) end) end) it('conversions are not applied during setreg()', function() call('setreg', 'i', 'ww') - eq(eval('@i'), 'ww') + eq('ww', eval('@i')) end) it('conversions not applied in insert mode', function() feed('aiiiwww') @@ -213,11 +213,11 @@ describe("'langmap'", function() iii]]) end) - local function testrecording(command_string, expect_string, setup_function) + local function testrecording(command_string, expect_string, setup_function, expect_macro) if setup_function then setup_function() end feed('qa' .. command_string .. 'q') expect(expect_string) - eq(helpers.funcs.nvim_replace_termcodes(command_string, true, true, true), + eq(expect_macro or helpers.funcs.nvim_replace_termcodes(command_string, true, true, true), eval('@a')) if setup_function then setup_function() end -- n.b. may need nvim_replace_termcodes() here. @@ -273,8 +273,8 @@ describe("'langmap'", function() it('treats control modified keys as characters', function() command('nnoremap <C-w> iw<esc>') command('nnoremap <C-i> ii<esc>') - testrecording('<C-w>', 'whello', local_setup) - testrecording('<C-i>', 'ihello', local_setup) + testrecording('<C-w>', 'whello', local_setup, eval([["\<*C-w>"]])) + testrecording('<C-i>', 'ihello', local_setup, eval([["\<*C-i>"]])) end) end) diff --git a/test/functional/editor/macro_spec.lua b/test/functional/editor/macro_spec.lua index c0c9256af2..53be7dcc62 100644 --- a/test/functional/editor/macro_spec.lua +++ b/test/functional/editor/macro_spec.lua @@ -6,28 +6,31 @@ local feed = helpers.feed local clear = helpers.clear local expect = helpers.expect local command = helpers.command +local funcs = helpers.funcs +local meths = helpers.meths local insert = helpers.insert local curbufmeths = helpers.curbufmeths +before_each(clear) + describe('macros', function() - before_each(clear) it('can be recorded and replayed', function() feed('qiahello<esc>q') expect('hello') - eq(eval('@i'), 'ahello') + eq('ahello', eval('@i')) feed('@i') expect('hellohello') - eq(eval('@i'), 'ahello') + eq('ahello', eval('@i')) end) it('applies maps', function() command('imap x l') command('nmap l a') feed('qilxxx<esc>q') expect('lll') - eq(eval('@i'), 'lxxx') + eq('lxxx', eval('@i')) feed('@i') expect('llllll') - eq(eval('@i'), 'lxxx') + eq('lxxx', eval('@i')) end) it('can be replayed with Q', function() @@ -47,9 +50,47 @@ hello]] end) end) -describe('reg_recorded()', function() - before_each(clear) +describe('immediately after a macro has finished executing,', function() + before_each(function() + command([[let @a = 'gg0']]) + end) + + describe('reg_executing() from RPC returns an empty string', function() + it('if the macro does not end with a <Nop> mapping', function() + feed('@a') + eq('', funcs.reg_executing()) + end) + + it('if the macro ends with a <Nop> mapping', function() + command('nnoremap 0 <Nop>') + feed('@a') + eq('', funcs.reg_executing()) + end) + end) + describe('characters from a mapping are not treated as a part of the macro #18015', function() + before_each(function() + command('nnoremap s qa') + end) + + it('if the macro does not end with a <Nop> mapping', function() + feed('@asq') -- "q" from "s" mapping should start recording a macro instead of being no-op + eq({mode = 'n', blocking = false}, meths.get_mode()) + expect('') + eq('', eval('@a')) + end) + + it('if the macro ends with a <Nop> mapping', function() + command('nnoremap 0 <Nop>') + feed('@asq') -- "q" from "s" mapping should start recording a macro instead of being no-op + eq({mode = 'n', blocking = false}, meths.get_mode()) + expect('') + eq('', eval('@a')) + end) + end) +end) + +describe('reg_recorded()', function() it('returns the correct value', function() feed [[qqyyq]] eq('q', eval('reg_recorded()')) diff --git a/test/functional/editor/mark_spec.lua b/test/functional/editor/mark_spec.lua new file mode 100644 index 0000000000..1eb76aa628 --- /dev/null +++ b/test/functional/editor/mark_spec.lua @@ -0,0 +1,401 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') +local meths = helpers.meths +local curbufmeths = helpers.curbufmeths +local clear = helpers.clear +local command = helpers.command +local funcs = helpers.funcs +local eq = helpers.eq +local feed = helpers.feed +local write_file = helpers.write_file +local pcall_err = helpers.pcall_err +local cursor = function() return helpers.meths.win_get_cursor(0) end + +describe('named marks', function() + local file1 = 'Xtestfile-functional-editor-marks' + local file2 = 'Xtestfile-functional-editor-marks-2' + before_each(function() + clear() + write_file(file1, '1test1\n1test2\n1test3\n1test4', false, false) + write_file(file2, '2test1\n2test2\n2test3\n2test4', false, false) + end) + after_each(function() + os.remove(file1) + os.remove(file2) + end) + + + it("can be set", function() + command("edit " .. file1) + command("mark a") + eq({1, 0}, curbufmeths.get_mark("a")) + feed("jmb") + eq({2, 0}, curbufmeths.get_mark("b")) + feed("jmB") + eq({3, 0}, curbufmeths.get_mark("B")) + command("4kc") + eq({4, 0}, curbufmeths.get_mark("c")) + end) + + it("errors when set out of range with :mark", function() + command("edit " .. file1) + local err = pcall_err(helpers.exec_capture, "1000mark x") + eq("Vim(mark):E16: Invalid range: 1000mark x", err) + end) + + it("errors when set out of range with :k", function() + command("edit " .. file1) + local err = pcall_err(helpers.exec_capture, "1000kx") + eq("Vim(k):E16: Invalid range: 1000kx", err) + end) + + it("errors on unknown mark name with :mark", function() + command("edit " .. file1) + local err = pcall_err(helpers.exec_capture, "mark #") + eq("Vim(mark):E191: Argument must be a letter or forward/backward quote", err) + end) + + it("errors on unknown mark name with '", function() + command("edit " .. file1) + local err = pcall_err(helpers.exec_capture, "normal! '#") + eq("Vim(normal):E78: Unknown mark", err) + end) + + it("errors on unknown mark name with `", function() + command("edit " .. file1) + local err = pcall_err(helpers.exec_capture, "normal! `#") + eq("Vim(normal):E78: Unknown mark", err) + end) + + it("errors when moving to a mark that is not set with '", function() + command("edit " .. file1) + local err = pcall_err(helpers.exec_capture, "normal! 'z") + eq("Vim(normal):E20: Mark not set", err) + err = pcall_err(helpers.exec_capture, "normal! '.") + eq("Vim(normal):E20: Mark not set", err) + end) + + it("errors when moving to a mark that is not set with `", function() + command("edit " .. file1) + local err = pcall_err(helpers.exec_capture, "normal! `z") + eq("Vim(normal):E20: Mark not set", err) + err = pcall_err(helpers.exec_capture, "normal! `>") + eq("Vim(normal):E20: Mark not set", err) + end) + + it("errors when moving to a global mark that is not set with '", function() + command("edit " .. file1) + local err = pcall_err(helpers.exec_capture, "normal! 'Z") + eq("Vim(normal):E20: Mark not set", err) + end) + + it("errors when moving to a global mark that is not set with `", function() + command("edit " .. file1) + local err = pcall_err(helpers.exec_capture, "normal! `Z") + eq("Vim(normal):E20: Mark not set", err) + end) + + it("can move to them using '", function() + command("args " .. file1 .. " " .. file2) + feed("j") + feed("ma") + feed("G'a") + eq({2, 0}, cursor()) + feed("mA") + command("next") + feed("'A") + eq(1, meths.get_current_buf().id) + eq({2, 0}, cursor()) + end) + + it("can move to them using `", function() + command("args " .. file1 .. " " .. file2) + feed("jll") + feed("ma") + feed("G`a") + eq({2, 2}, cursor()) + feed("mA") + command("next") + feed("`A") + eq(1, meths.get_current_buf().id) + eq({2, 2}, cursor()) + end) + + it("can move to them using g'", function() + command("args " .. file1 .. " " .. file2) + feed("jll") + feed("ma") + feed("Gg'a") + eq({2, 0}, cursor()) + feed("mA") + command("next") + feed("g'A") + eq(1, meths.get_current_buf().id) + eq({2, 0}, cursor()) + end) + + it("can move to them using g`", function() + command("args " .. file1 .. " " .. file2) + feed("jll") + feed("ma") + feed("Gg`a") + eq({2, 2}, cursor()) + feed("mA") + command("next") + feed("g`A") + eq(1, meths.get_current_buf().id) + eq({2, 2}, cursor()) + end) + + it("errors when it can't find the buffer", function() + command("args " .. file1 .. " " .. file2) + feed("mA") + command("next") + command("bw! " .. file1 ) + local err = pcall_err(helpers.exec_capture, "normal! 'A") + eq("Vim(normal):E92: Buffer 1 not found", err) + os.remove(file1) + end) + + it("leave a context mark when moving with '", function() + command("edit " .. file1) + feed("llmamA") + feed("10j0") -- first col, last line + local pos = cursor() + feed("'a") + feed("<C-o>") + eq(pos, cursor()) + feed("'A") + feed("<C-o>") + eq(pos, cursor()) + end) + + it("leave a context mark when moving with `", function() + command("edit " .. file1) + feed("llmamA") + feed("10j0") -- first col, last line + local pos = cursor() + feed("`a") + feed("<C-o>") + eq(pos, cursor()) + feed("`A") + feed("<C-o>") + eq(pos, cursor()) + end) + + it("leave a context mark when the mark changes buffer with g'", function() + command("args " .. file1 .. " " .. file2) + local pos + feed("GmA") + command("next") + pos = cursor() + command("clearjumps") + feed("g'A") -- since the mark is in another buffer, it leaves a context mark + feed("<C-o>") + eq(pos, cursor()) + end) + + it("leave a context mark when the mark changes buffer with g`", function() + command("args " .. file1 .. " " .. file2) + local pos + feed("GmA") + command("next") + pos = cursor() + command("clearjumps") + feed("g`A") -- since the mark is in another buffer, it leaves a context mark + feed("<C-o>") + eq(pos, cursor()) + end) + + it("do not leave a context mark when moving with g'", function() + command("edit " .. file1) + local pos + feed("ma") + pos = cursor() -- Mark pos + feed("10j0") -- first col, last line + feed("g'a") + feed("<C-o>") -- should do nothing + eq(pos, cursor()) + feed("mA") + pos = cursor() -- Mark pos + feed("10j0") -- first col, last line + feed("g'a") + feed("<C-o>") -- should do nothing + eq(pos, cursor()) + end) + + it("do not leave a context mark when moving with g`", function() + command("edit " .. file1) + local pos + feed("ma") + pos = cursor() -- Mark pos + feed("10j0") -- first col, last line + feed("g`a") + feed("<C-o>") -- should do nothing + eq(pos, cursor()) + feed("mA") + pos = cursor() -- Mark pos + feed("10j0") -- first col, last line + feed("g'a") + feed("<C-o>") -- should do nothing + eq(pos, cursor()) + end) + + it("open folds when moving to them", function() + command("edit " .. file1) + feed("jzfG") -- Fold from the second line to the end + command("3mark a") + feed("G") -- On top of the fold + assert(funcs.foldclosed('.') ~= -1) -- folded + feed("'a") + eq(-1, funcs.foldclosed('.')) + + feed("zc") + assert(funcs.foldclosed('.') ~= -1) -- folded + -- TODO: remove this workaround after fixing #15873 + feed("k`a") + eq(-1, funcs.foldclosed('.')) + + feed("zc") + assert(funcs.foldclosed('.') ~= -1) -- folded + feed("kg'a") + eq(-1, funcs.foldclosed('.')) + + feed("zc") + assert(funcs.foldclosed('.') ~= -1) -- folded + feed("kg`a") + eq(-1, funcs.foldclosed('.')) + end) + + it("do not open folds when moving to them doesn't move the cursor", function() + command("edit " .. file1) + feed("jzfG") -- Fold from the second line to the end + assert(funcs.foldclosed('.') == 2) -- folded + feed("ma") + feed("'a") + feed("`a") + feed("g'a") + feed("g`a") + -- should still be folded + eq(2, funcs.foldclosed('.')) + end) + + it("getting '{ '} '( ') does not move cursor", function() + meths.buf_set_lines(0, 0, 0, true, {'aaaaa', 'bbbbb', 'ccccc', 'ddddd', 'eeeee'}) + meths.win_set_cursor(0, {2, 0}) + funcs.getpos("'{") + eq({2, 0}, meths.win_get_cursor(0)) + funcs.getpos("'}") + eq({2, 0}, meths.win_get_cursor(0)) + funcs.getpos("'(") + eq({2, 0}, meths.win_get_cursor(0)) + funcs.getpos("')") + eq({2, 0}, meths.win_get_cursor(0)) + end) + + it('in command range does not move cursor #19248', function() + meths.create_user_command('Test', ':', {range = true}) + meths.buf_set_lines(0, 0, 0, true, {'aaaaa', 'bbbbb', 'ccccc', 'ddddd', 'eeeee'}) + meths.win_set_cursor(0, {2, 0}) + command([['{,'}Test]]) + eq({2, 0}, meths.win_get_cursor(0)) + end) +end) + +describe('named marks view', function() + local file1 = 'Xtestfile-functional-editor-marks' + local file2 = 'Xtestfile-functional-editor-marks-2' + local function content() + local c = {} + for i=1,30 do + c[i] = i .. " line" + end + return table.concat(c, "\n") + end + before_each(function() + clear() + write_file(file1, content(), false, false) + write_file(file2, content(), false, false) + command("set jumpoptions+=view") + end) + after_each(function() + os.remove(file1) + os.remove(file2) + end) + + it('is restored', function() + local screen = Screen.new(5, 8) + screen:attach() + command("edit " .. file1) + feed("<C-e>jWma") + feed("G'a") + local expected = [[ + 2 line | + ^3 line | + 4 line | + 5 line | + 6 line | + 7 line | + 8 line | + | + ]] + screen:expect({grid=expected}) + feed("G`a") + screen:expect([[ + 2 line | + 3 ^line | + 4 line | + 5 line | + 6 line | + 7 line | + 8 line | + | + ]]) + end) + + it('is restored across files', function() + local screen = Screen.new(5, 5) + screen:attach() + command("args " .. file1 .. " " .. file2) + feed("<C-e>mA") + local mark_view = [[ + ^2 line | + 3 line | + 4 line | + 5 line | + | + ]] + screen:expect(mark_view) + command("next") + screen:expect([[ + ^1 line | + 2 line | + 3 line | + 4 line | + | + ]]) + feed("'A") + screen:expect(mark_view) + end) + + it('fallback to standard behavior when view can\'t be recovered', function() + local screen = Screen.new(10, 10) + screen:attach() + command("edit " .. file1) + feed("7GzbmaG") -- Seven lines from the top + command("new") -- Screen size for window is now half the height can't be restored + feed("<C-w>p'a") + screen:expect([[ + | + ~ | + ~ | + ~ | + [No Name] | + 6 line | + ^7 line | + 8 line | + {MATCH:.*} | + | + ]]) + end) +end) diff --git a/test/functional/editor/meta_key_spec.lua b/test/functional/editor/meta_key_spec.lua index 2280f5bb24..23964ca10f 100644 --- a/test/functional/editor/meta_key_spec.lua +++ b/test/functional/editor/meta_key_spec.lua @@ -27,6 +27,14 @@ describe('meta-keys #8226 #13042', function() command('nnoremap <A-j> Aalt-j<Esc>') feed('<A-j><M-l>') expect('llo<ESC>;<ESC>;alt-jmeta-l') + -- Unmapped ALT-chord with characters containing K_SPECIAL bytes + command('nnoremap … A…<Esc>') + feed('<A-…><M-…>') + expect('llo<ESC>;<ESC>;alt-jmeta-l<ESC>…<ESC>…') + command("execute 'nnoremap' nr2char(0x40000000) 'AMAX<Esc>'") + command("call nvim_input('<A-'.nr2char(0x40000000).'>')") + command("call nvim_input('<M-'.nr2char(0x40000000).'>')") + expect('llo<ESC>;<ESC>;alt-jmeta-l<ESC>…<ESC>…<ESC>MAX<ESC>MAX') end) it('ALT/META, visual-mode', function() @@ -36,13 +44,20 @@ describe('meta-keys #8226 #13042', function() expect('peach') -- Unmapped ALT-chord resolves isolated (non-ALT) ESC mapping. #13086 #15869 command('vnoremap <ESC> A<lt>ESC>') - feed('viw<A-;><ESC>viw<M-;><ESC>') + feed('viw<A-;><Esc>viw<M-;><Esc>') expect('peach<ESC>;<ESC>;') -- Mapped ALT-chord behaves as mapped. command('vnoremap <M-l> Ameta-l<Esc>') command('vnoremap <A-j> Aalt-j<Esc>') feed('viw<A-j>viw<M-l>') expect('peach<ESC>;<ESC>;alt-jmeta-l') + -- Unmapped ALT-chord with characters containing K_SPECIAL bytes + feed('viw<A-…><Esc>viw<M-…><Esc>') + expect('peach<ESC>;<ESC>;alt-jmeta-l<ESC>…<ESC>…') + command("execute 'inoremap' nr2char(0x40000000) 'MAX'") + command("call nvim_input('viw<A-'.nr2char(0x40000000).'><Esc>')") + command("call nvim_input('viw<M-'.nr2char(0x40000000).'><Esc>')") + expect('peach<ESC>;<ESC>;alt-jmeta-l<ESC>…<ESC>…<ESC>MAX<ESC>MAX') end) it('ALT/META insert-mode', function() @@ -89,4 +104,41 @@ describe('meta-keys #8226 #13042', function() eq({ 0, 2, 1, 0, }, funcs.getpos('.')) eq('nt', eval('mode(1)')) end) + + it('ALT/META when recording a macro #13235', function() + command('inoremap <M-Esc> <lt>M-ESC>') + feed('ifoo<CR>bar<CR>baz<Esc>gg0') + -- <M-"> is reinterpreted as <Esc>" + feed('qrviw"ayC// This is some text: <M-">apq') + expect([[ + // This is some text: foo + bar + baz]]) + -- Should not insert an extra double quote or trigger <M-Esc> when replaying + feed('j0@rj0@@') + expect([[ + // This is some text: foo + // This is some text: bar + // This is some text: baz]]) + command('%delete') + end) + + it('ALT/META with special key when recording a macro', function() + command('inoremap <M-Esc> <lt>M-ESC>') + command('noremap <S-Tab> "') + command('noremap! <S-Tab> "') + feed('ifoo<CR>bar<CR>baz<Esc>gg0') + -- <M-S-Tab> is reinterpreted as <Esc><S-Tab> + feed('qrviw<S-Tab>ayC// This is some text: <M-S-Tab>apq') + expect([[ + // This is some text: foo + bar + baz]]) + -- Should not insert an extra double quote or trigger <M-Esc> when replaying + feed('j0@rj0@@') + expect([[ + // This is some text: foo + // This is some text: bar + // This is some text: baz]]) + end) end) diff --git a/test/functional/editor/mode_cmdline_spec.lua b/test/functional/editor/mode_cmdline_spec.lua index 0f7d821bb5..50cc5e17ee 100644 --- a/test/functional/editor/mode_cmdline_spec.lua +++ b/test/functional/editor/mode_cmdline_spec.lua @@ -3,67 +3,74 @@ local helpers = require('test.functional.helpers')(after_each) local clear, insert, funcs, eq, feed = helpers.clear, helpers.insert, helpers.funcs, helpers.eq, helpers.feed +local eval = helpers.eval local meths = helpers.meths -describe('cmdline CTRL-R', function() +describe('cmdline', function() before_each(clear) - it('pasting non-special register inserts <CR> *between* lines', function() - insert([[ - line1abc - line2somemoretext - ]]) - -- Yank 2 lines linewise, then paste to cmdline. - feed([[<C-\><C-N>gg0yj:<C-R>0]]) - -- <CR> inserted between lines, NOT after the final line. - eq('line1abc\rline2somemoretext', funcs.getcmdline()) + describe('Ctrl-R', function() + it('pasting non-special register inserts <CR> *between* lines', function() + insert([[ + line1abc + line2somemoretext + ]]) + -- Yank 2 lines linewise, then paste to cmdline. + feed([[<C-\><C-N>gg0yj:<C-R>0]]) + -- <CR> inserted between lines, NOT after the final line. + eq('line1abc\rline2somemoretext', funcs.getcmdline()) - -- Yank 2 lines charwise, then paste to cmdline. - feed([[<C-\><C-N>gg05lyvj:<C-R>0]]) - -- <CR> inserted between lines, NOT after the final line. - eq('abc\rline2', funcs.getcmdline()) + -- Yank 2 lines charwise, then paste to cmdline. + feed([[<C-\><C-N>gg05lyvj:<C-R>0]]) + -- <CR> inserted between lines, NOT after the final line. + eq('abc\rline2', funcs.getcmdline()) - -- Yank 1 line linewise, then paste to cmdline. - feed([[<C-\><C-N>ggyy:<C-R>0]]) - -- No <CR> inserted. - eq('line1abc', funcs.getcmdline()) - end) + -- Yank 1 line linewise, then paste to cmdline. + feed([[<C-\><C-N>ggyy:<C-R>0]]) + -- No <CR> inserted. + eq('line1abc', funcs.getcmdline()) + end) - it('pasting special register inserts <CR>, <NL>', function() - feed([[:<C-R>="foo\nbar\rbaz"<CR>]]) - eq('foo\nbar\rbaz', funcs.getcmdline()) + it('pasting special register inserts <CR>, <NL>', function() + feed([[:<C-R>="foo\nbar\rbaz"<CR>]]) + eq('foo\nbar\rbaz', funcs.getcmdline()) + end) end) -end) -describe('cmdline history', function() - before_each(clear) + it('Ctrl-Shift-V supports entering unsimplified key notations', function() + feed(':"<C-S-V><C-J><C-S-V><C-@><C-S-V><C-[><C-S-V><C-S-M><C-S-V><M-C-I><C-S-V><C-D-J><CR>') - it('correctly clears start of the history', function() - -- Regression test: check absence of the memory leak when clearing start of - -- the history using ex_getln.c/clr_history(). - eq(1, funcs.histadd(':', 'foo')) - eq(1, funcs.histdel(':')) - eq('', funcs.histget(':', -1)) + eq('"<C-J><C-@><C-[><C-S-M><M-C-I><C-D-J>', eval('@:')) end) - it('correctly clears end of the history', function() - -- Regression test: check absence of the memory leak when clearing end of - -- the history using ex_getln.c/clr_history(). - meths.set_option('history', 1) - eq(1, funcs.histadd(':', 'foo')) - eq(1, funcs.histdel(':')) - eq('', funcs.histget(':', -1)) - end) + describe('history', function() + it('correctly clears start of the history', function() + -- Regression test: check absence of the memory leak when clearing start of + -- the history using ex_getln.c/clr_history(). + eq(1, funcs.histadd(':', 'foo')) + eq(1, funcs.histdel(':')) + eq('', funcs.histget(':', -1)) + end) + + it('correctly clears end of the history', function() + -- Regression test: check absence of the memory leak when clearing end of + -- the history using ex_getln.c/clr_history(). + meths.set_option('history', 1) + eq(1, funcs.histadd(':', 'foo')) + eq(1, funcs.histdel(':')) + eq('', funcs.histget(':', -1)) + end) - it('correctly removes item from history', function() - -- Regression test: check that ex_getln.c/del_history_idx() correctly clears - -- history index after removing history entry. If it does not then deleting - -- history will result in a double free. - eq(1, funcs.histadd(':', 'foo')) - eq(1, funcs.histadd(':', 'bar')) - eq(1, funcs.histadd(':', 'baz')) - eq(1, funcs.histdel(':', -2)) - eq(1, funcs.histdel(':')) - eq('', funcs.histget(':', -1)) + it('correctly removes item from history', function() + -- Regression test: check that ex_getln.c/del_history_idx() correctly clears + -- history index after removing history entry. If it does not then deleting + -- history will result in a double free. + eq(1, funcs.histadd(':', 'foo')) + eq(1, funcs.histadd(':', 'bar')) + eq(1, funcs.histadd(':', 'baz')) + eq(1, funcs.histdel(':', -2)) + eq(1, funcs.histdel(':')) + eq('', funcs.histget(':', -1)) + end) end) end) diff --git a/test/functional/editor/mode_insert_spec.lua b/test/functional/editor/mode_insert_spec.lua index f03508035d..e3d3cdbd85 100644 --- a/test/functional/editor/mode_insert_spec.lua +++ b/test/functional/editor/mode_insert_spec.lua @@ -75,4 +75,62 @@ describe('insert-mode', function() expect('hello oooworld') end) end) + + describe('Ctrl-V', function() + it('supports entering the decimal value of a character', function() + feed('i<C-V>076<C-V>167') + expect('L§') + end) + + it('supports entering the octal value of a character with "o"', function() + feed('i<C-V>o114<C-V>o247<Esc>') + expect('L§') + end) + + it('supports entering the octal value of a character with "O"', function() + feed('i<C-V>O114<C-V>O247<Esc>') + expect('L§') + end) + + it('supports entering the hexadecimal value of a character with "x"', function() + feed('i<C-V>x4c<C-V>xA7<Esc>') + expect('L§') + end) + + it('supports entering the hexadecimal value of a character with "X"', function() + feed('i<C-V>X4c<C-V>XA7<Esc>') + expect('L§') + end) + + it('supports entering the hexadecimal value of a character with "u"', function() + feed('i<C-V>u25ba<C-V>u25C7<Esc>') + expect('►◇') + end) + + it('supports entering the hexadecimal value of a character with "U"', function() + feed('i<C-V>U0001f600<C-V>U0001F601<Esc>') + expect('😀😁') + end) + + it('entering character by value is interrupted by invalid character', function() + feed('i<C-V>76c<C-V>76<C-F2><C-V>u3c0j<C-V>u3c0<M-F3><C-V>U1f600j<C-V>U1f600<D-F4><Esc>') + expect('LcL<C-F2>πjπ<M-F3>😀j😀<D-F4>') + end) + + it('shows o, O, u, U, x, X, and digits with modifiers', function() + feed('i<C-V><M-o><C-V><D-o><C-V><M-O><C-V><D-O><Esc>') + expect('<M-o><D-o><M-O><D-O>') + feed('cc<C-V><M-u><C-V><D-u><C-V><M-U><C-V><D-U><Esc>') + expect('<M-u><D-u><M-U><D-U>') + feed('cc<C-V><M-x><C-V><D-x><C-V><M-X><C-V><D-X><Esc>') + expect('<M-x><D-x><M-X><D-X>') + feed('cc<C-V><M-1><C-V><D-2><C-V><M-7><C-V><D-8><Esc>') + expect('<M-1><D-2><M-7><D-8>') + end) + end) + + it('Ctrl-Shift-V supports entering unsimplified key notations', function() + feed('i<C-S-V><C-J><C-S-V><C-@><C-S-V><C-[><C-S-V><C-S-M><C-S-V><M-C-I><C-S-V><C-D-J><Esc>') + expect('<C-J><C-@><C-[><C-S-M><M-C-I><C-D-J>') + end) end) diff --git a/test/functional/editor/put_spec.lua b/test/functional/editor/put_spec.lua index 26967ecbba..cc9fce8f67 100644 --- a/test/functional/editor/put_spec.lua +++ b/test/functional/editor/put_spec.lua @@ -64,7 +64,7 @@ describe('put command', function() -- one place to the right (unless we were at the end of the -- line when we pasted). if not (exception_table.undo_position and after_undo) then - eq(funcs.getcurpos(), init_cursorpos) + eq(init_cursorpos, funcs.getcurpos()) end end @@ -86,7 +86,7 @@ describe('put command', function() -- If we paste the ". register with a count we can't avoid -- changing this register, hence avoid this check. if not test.exception_table.dot_reg_changed then - eq(funcs.getreg('.'), orig_dotstr) + eq(orig_dotstr, funcs.getreg('.')) end -- Doing something, undoing it, and then redoing it should @@ -138,9 +138,9 @@ describe('put command', function() end -- create_test_defs() }}} local function find_cursor_position(expect_string) -- {{{ - -- There must only be one occurance of the character 'x' in + -- There must only be one occurrence of the character 'x' in -- expect_string. - -- This function removes that occurance, and returns the position that + -- This function removes that occurrence, and returns the position that -- it was in. -- This returns the cursor position that would leave the 'x' in that -- place if we feed 'ix<esc>' and the string existed before it. @@ -507,9 +507,11 @@ describe('put command', function() return function(exception_table, after_redo) test_expect(exception_table, after_redo) if selection_string then - eq(getreg('"'), selection_string) + if not conversion_table.put_backwards then + eq(selection_string, getreg('"')) + end else - eq(getreg('"'), 'test_string"') + eq('test_string"', getreg('"')) end end end @@ -714,7 +716,9 @@ describe('put command', function() expect_base, conversion_table) return function(exception_table, after_redo) test_expect(exception_table, after_redo) - eq(getreg('"'), 'Line of words 1\n') + if not conversion_table.put_backwards then + eq('Line of words 1\n', getreg('"')) + end end end local base_expect_string = [[ @@ -748,7 +752,9 @@ describe('put command', function() end, expect_base, conversion_table) return function(e,c) test_expect(e,c) - eq(getreg('"'), 'Lin\nLin') + if not conversion_table.put_backwards then + eq('Lin\nLin', getreg('"')) + end end end @@ -800,9 +806,9 @@ describe('put command', function() feed('u') -- Have to use feed('u') here to set curswant, because -- ex_undo() doesn't do that. - eq(funcs.getcurpos(), {0, 1, 1, 0, 1}) + eq({0, 1, 1, 0, 1}, funcs.getcurpos()) feed('<C-r>') - eq(funcs.getcurpos(), {0, 1, 1, 0, 1}) + eq({0, 1, 1, 0, 1}, funcs.getcurpos()) end end diff --git a/test/functional/editor/tabpage_spec.lua b/test/functional/editor/tabpage_spec.lua index d1d6854b07..3b2c1db350 100644 --- a/test/functional/editor/tabpage_spec.lua +++ b/test/functional/editor/tabpage_spec.lua @@ -3,8 +3,11 @@ local helpers = require('test.functional.helpers')(after_each) local clear = helpers.clear local command = helpers.command local eq = helpers.eq +local neq = helpers.neq local feed = helpers.feed local eval = helpers.eval +local exec = helpers.exec +local funcs = helpers.funcs describe('tabpage', function() before_each(clear) @@ -34,5 +37,28 @@ describe('tabpage', function() eq(3, eval('tabpagenr()')) end) -end) + it('does not crash or loop 999 times if BufWipeout autocommand switches window #17868', function() + exec([[ + tabedit + let s:window_id = win_getid() + botright new + setlocal bufhidden=wipe + let g:win_closed = 0 + autocmd WinClosed * let g:win_closed += 1 + autocmd BufWipeout <buffer> call win_gotoid(s:window_id) + tabprevious + +tabclose + ]]) + neq(999, eval('g:win_closed')) + end) + + it(":tabmove handles modifiers and addr", function() + command('tabnew | tabnew | tabnew') + eq(4, funcs.nvim_tabpage_get_number(0)) + command(' silent :keepalt :: ::: silent! - tabmove') + eq(3, funcs.nvim_tabpage_get_number(0)) + command(' silent :keepalt :: ::: silent! -2 tabmove') + eq(1, funcs.nvim_tabpage_get_number(0)) + end) +end) diff --git a/test/functional/editor/undo_spec.lua b/test/functional/editor/undo_spec.lua index a023ca3d90..a041428cdc 100644 --- a/test/functional/editor/undo_spec.lua +++ b/test/functional/editor/undo_spec.lua @@ -2,9 +2,18 @@ local helpers = require('test.functional.helpers')(after_each) local clear = helpers.clear local command = helpers.command +local eval = helpers.eval local expect = helpers.expect +local eq = helpers.eq local feed = helpers.feed +local feed_command = helpers.feed_command local insert = helpers.insert +local funcs = helpers.funcs + +local function lastmessage() + local messages = funcs.split(funcs.execute('messages'), '\n') + return messages[#messages] +end describe('u CTRL-R g- g+', function() before_each(clear) @@ -59,3 +68,61 @@ describe('u CTRL-R g- g+', function() undo_and_redo(4, 'g-', 'g+', '1') end) end) + +describe(':undo! command', function() + before_each(function() + clear() + feed('i1 little bug in the code<Esc>') + feed('o1 little bug in the code<Esc>') + feed('oTake 1 down, patch it around<Esc>') + feed('o99 little bugs in the code<Esc>') + end) + it('works', function() + feed_command('undo!') + expect([[ + 1 little bug in the code + 1 little bug in the code + Take 1 down, patch it around]]) + feed('<C-r>') + eq('Already at newest change', lastmessage()) + end) + it('works with arguments', function() + feed_command('undo! 2') + expect([[ + 1 little bug in the code + 1 little bug in the code]]) + feed('<C-r>') + eq('Already at newest change', lastmessage()) + end) + it('correctly sets alternative redo', function() + feed('uo101 little bugs in the code<Esc>') + feed_command('undo!') + feed('<C-r>') + expect([[ + 1 little bug in the code + 1 little bug in the code + Take 1 down, patch it around + 99 little bugs in the code]]) + + feed('uuoTake 2 down, patch them around<Esc>') + feed('o101 little bugs in the code<Esc>') + feed_command('undo! 2') + feed('<C-r><C-r>') + expect([[ + 1 little bug in the code + 1 little bug in the code + Take 1 down, patch it around + 99 little bugs in the code]]) + end) + it('fails when attempting to redo or move to different undo branch', function() + feed_command('undo! 4') + eq('E5767: Cannot use :undo! to redo or move to a different undo branch', eval('v:errmsg')) + feed('u') + feed_command('undo! 4') + eq('E5767: Cannot use :undo! to redo or move to a different undo branch', eval('v:errmsg')) + feed('o101 little bugs in the code<Esc>') + feed('o101 little bugs in the code<Esc>') + feed_command('undo! 4') + eq('E5767: Cannot use :undo! to redo or move to a different undo branch', eval('v:errmsg')) + end) +end) |