aboutsummaryrefslogtreecommitdiff
path: root/test/functional/editor
diff options
context:
space:
mode:
authorJosh Rahm <rahm@google.com>2022-07-18 19:37:18 +0000
committerJosh Rahm <rahm@google.com>2022-07-18 19:37:18 +0000
commit308e1940dcd64aa6c344c403d4f9e0dda58d9c5c (patch)
tree35fe43e01755e0f312650667004487a44d6b7941 /test/functional/editor
parent96a00c7c588b2f38a2424aeeb4ea3581d370bf2d (diff)
parente8c94697bcbe23a5c7b07c292b90a6b70aadfa87 (diff)
downloadrneovim-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.lua23
-rw-r--r--test/functional/editor/completion_spec.lua60
-rw-r--r--test/functional/editor/ctrl_c_spec.lua94
-rw-r--r--test/functional/editor/jump_spec.lua143
-rw-r--r--test/functional/editor/langmap_spec.lua36
-rw-r--r--test/functional/editor/macro_spec.lua55
-rw-r--r--test/functional/editor/mark_spec.lua401
-rw-r--r--test/functional/editor/meta_key_spec.lua54
-rw-r--r--test/functional/editor/mode_cmdline_spec.lua105
-rw-r--r--test/functional/editor/mode_insert_spec.lua58
-rw-r--r--test/functional/editor/put_spec.lua26
-rw-r--r--test/functional/editor/tabpage_spec.lua28
-rw-r--r--test/functional/editor/undo_spec.lua67
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)