aboutsummaryrefslogtreecommitdiff
path: root/test/functional/normal/put_spec.lua
diff options
context:
space:
mode:
Diffstat (limited to 'test/functional/normal/put_spec.lua')
-rw-r--r--test/functional/normal/put_spec.lua936
1 files changed, 936 insertions, 0 deletions
diff --git a/test/functional/normal/put_spec.lua b/test/functional/normal/put_spec.lua
new file mode 100644
index 0000000000..36d5e8b43c
--- /dev/null
+++ b/test/functional/normal/put_spec.lua
@@ -0,0 +1,936 @@
+local Screen = require('test.functional.ui.screen')
+local helpers = require('test.functional.helpers')(after_each)
+
+local clear = helpers.clear
+local insert = helpers.insert
+local feed = helpers.feed
+local expect = helpers.expect
+local eq = helpers.eq
+local map = helpers.map
+local filter = helpers.filter
+local execute = helpers.execute
+local curbuf_contents = helpers.curbuf_contents
+local funcs = helpers.funcs
+local dedent = helpers.dedent
+local getreg = funcs.getreg
+
+local function reset()
+ clear()
+ insert([[
+ Line of words 1
+ Line of words 2]])
+ execute('goto 1')
+ feed('itest_string.<esc>u')
+ funcs.setreg('a', 'test_stringa', 'V')
+ funcs.setreg('b', 'test_stringb\ntest_stringb\ntest_stringb', 'b')
+ funcs.setreg('"', 'test_string"', 'v')
+end
+
+-- We check the last inserted register ". in each of these tests because it is
+-- implemented completely differently in do_put().
+-- It is implemented differently so that control characters and imap'ped
+-- characters work in the same manner when pasted as when inserted.
+describe('put command', function()
+ -- Put a call to clear() here to force the connection to the server.
+ -- This means we can use the funcs.*() functions while mangling text before
+ -- the actual tests are run.
+ clear()
+ before_each(reset)
+
+ local function visual_marks_zero()
+ for _,v in pairs(funcs.getpos("'<")) do
+ if v ~= 0 then
+ return false
+ end
+ end
+ for _,v in pairs(funcs.getpos("'>")) do
+ if v ~= 0 then
+ return false
+ end
+ end
+ return true
+ end
+
+ -- {{{ Where test definitions are run
+ local function run_test_variations(test_variations, extra_setup)
+ reset()
+ if extra_setup then extra_setup() end
+ local init_contents = curbuf_contents()
+ local init_cursorpos = funcs.getcurpos()
+ local assert_no_change = function (exception_table, after_undo)
+ expect(init_contents)
+ -- When putting the ". register forwards, undo doesn't move
+ -- the cursor back to where it was before.
+ -- This is because it uses the command character 'a' to
+ -- start the insert, and undo after that leaves the cursor
+ -- 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)
+ end
+ end
+
+ for _, test in pairs(test_variations) do
+ it(test.description, function()
+ if extra_setup then extra_setup() end
+ local orig_dotstr = funcs.getreg('.')
+ helpers.ok(visual_marks_zero())
+ -- Make sure every test starts from the same conditions
+ assert_no_change(test.exception_table, false)
+ local was_cli = test.test_action()
+ test.test_assertions(test.exception_table, false)
+ -- Check that undo twice puts us back to the original conditions
+ -- (i.e. puts the cursor and text back to before)
+ feed('u')
+ assert_no_change(test.exception_table, true)
+
+ -- Should not have changed the ". register
+ -- 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)
+ end
+
+ -- Doing something, undoing it, and then redoing it should
+ -- leave us in the same state as just doing it once.
+ -- For :ex actions we want '@:', for normal actions we want '.'
+
+ -- The '.' redo doesn't work for visual put so just exit if
+ -- it was tested.
+ -- We check that visual put was used by checking if the '< and
+ -- '> marks were changed.
+ if not visual_marks_zero() then
+ return
+ end
+
+ if test.exception_table.undo_position then
+ funcs.setpos('.', init_cursorpos)
+ end
+ if was_cli then
+ feed('@:')
+ else
+ feed('.')
+ end
+
+ test.test_assertions(test.exception_table, true)
+ end)
+ end
+ end -- run_test_variations()
+ -- }}}
+
+ local function create_test_defs(test_defs, command_base, command_creator, -- {{{
+ expect_base, expect_creator)
+ local rettab = {}
+ local exceptions
+ for _, v in pairs(test_defs) do
+ if v[4] then
+ exceptions = v[4]
+ else
+ exceptions = {}
+ end
+ table.insert(rettab,
+ {
+ test_action = command_creator(command_base, v[1]),
+ test_assertions = expect_creator(expect_base, v[2]),
+ description = v[3],
+ exception_table = exceptions,
+ })
+ end
+ return rettab
+ end -- create_test_defs() }}}
+
+ local function find_cursor_position(expect_string) -- {{{
+ -- There must only be one occurance of the character 'x' in
+ -- expect_string.
+ -- This function removes that occurance, 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.
+ for linenum, line in pairs(funcs.split(expect_string, '\n', 1)) do
+ local column = line:find('x')
+ if column then
+ return {linenum, column}, expect_string:gsub('x', '')
+ end
+ end
+ end -- find_cursor_position() }}}
+
+ -- Action function creators {{{
+ local function create_p_action(test_map, substitution)
+ local temp_val = test_map:gsub('p', substitution)
+ return function()
+ feed(temp_val)
+ return false
+ end
+ end
+
+ local function create_put_action(command_base, substitution)
+ local temp_val = command_base:gsub('put', substitution)
+ return function()
+ execute(temp_val)
+ return true
+ end
+ end
+ -- }}}
+
+ -- Expect function creator {{{
+ local function expect_creator(conversion_function, expect_base, conversion_table)
+ local temp_expect_string = conversion_function(expect_base, conversion_table)
+ local cursor_position, expect_string = find_cursor_position(temp_expect_string)
+ return function(exception_table, after_redo)
+ expect(expect_string)
+
+ -- Have to use getcurpos() instead of curwinmeths.get_cursor() in
+ -- order to account for virtualedit.
+ -- We always want the curswant element in getcurpos(), which is
+ -- sometimes different to the column element in
+ -- curwinmeths.get_cursor().
+ -- NOTE: The ".gp command leaves the cursor after the pasted text
+ -- when running, but does not when the command is redone with the
+ -- '.' command.
+ if not (exception_table.redo_position and after_redo) then
+ local actual_position = funcs.getcurpos()
+ eq(cursor_position, {actual_position[2], actual_position[5]})
+ end
+ end
+ end -- expect_creator() }}}
+
+ -- Test definitions {{{
+ local function copy_def(def)
+ local rettab = { '', {}, '', nil }
+ rettab[1] = def[1]
+ for k,v in pairs(def[2]) do
+ rettab[2][k] = v
+ end
+ rettab[3] = def[3]
+ if def[4] then
+ rettab[4] = {}
+ for k,v in pairs(def[4]) do
+ rettab[4][k] = v
+ end
+ end
+ return rettab
+ end
+
+ local normal_command_defs = {
+ {
+ 'p',
+ {cursor_after = false, put_backwards = false, dot_register = false},
+ 'pastes after cursor with p',
+ },
+ {
+ 'gp',
+ {cursor_after = true, put_backwards = false, dot_register = false},
+ 'leaves cursor after text with gp',
+ },
+ {
+ '".p',
+ {cursor_after = false, put_backwards = false, dot_register = true},
+ 'works with the ". register',
+ },
+ {
+ '".gp',
+ {cursor_after = true, put_backwards = false, dot_register = true},
+ 'gp works with the ". register',
+ {redo_position = true},
+ },
+ {
+ 'P',
+ {cursor_after = false, put_backwards = true, dot_register = false},
+ 'pastes before cursor with P',
+ },
+ {
+ 'gP',
+ {cursor_after = true, put_backwards = true, dot_register = false},
+ 'gP pastes before cursor and leaves cursor after text',
+ },
+ {
+ '".P',
+ {cursor_after = false, put_backwards = true, dot_register = true},
+ 'P works with ". register',
+ },
+ {
+ '".gP',
+ {cursor_after = true, put_backwards = true, dot_register = true},
+ 'gP works with ". register',
+ {redo_position = true},
+ },
+ }
+
+ -- Add a definition applying a count for each definition above.
+ -- Could do this for each transformation (p -> P, p -> gp etc), but I think
+ -- it's neater this way (balance between being explicit and too verbose).
+ for i = 1,#normal_command_defs do
+ local cur = normal_command_defs[i]
+
+ -- Make modified copy of current definition that includes a count.
+ local newdef = copy_def(cur)
+ newdef[2].count = 2
+ cur[2].count = 1
+ newdef[1] = '2' .. newdef[1]
+ newdef[3] = 'double ' .. newdef[3]
+
+ if cur[2].dot_register then
+ if not cur[4] then
+ newdef[4] = {}
+ end
+ newdef[4].dot_reg_changed = true
+ end
+
+ normal_command_defs[#normal_command_defs + 1] = newdef
+ end
+
+ local ex_command_defs = {
+ {
+ 'put',
+ {put_backwards = false, dot_register = false},
+ 'pastes linewise forwards with :put',
+ },
+ {
+ 'put!',
+ {put_backwards = true, dot_register = false},
+ 'pastes linewise backwards with :put!',
+ },
+ {
+ 'put .',
+ {put_backwards = false, dot_register = true},
+ 'pastes linewise with the dot register',
+ },
+ {
+ 'put! .',
+ {put_backwards = true, dot_register = true},
+ 'pastes linewise backwards with the dot register',
+ },
+ }
+
+ local function non_dotdefs(def_table)
+ return filter(function(d) return not d[2].dot_register end, def_table)
+ end
+
+ -- }}}
+
+ -- Conversion functions {{{
+ local function convert_characterwise(expect_base, conversion_table,
+ virtualedit_end, visual_put)
+ expect_base = dedent(expect_base)
+ -- There is no difference between 'P' and 'p' when VIsual_active
+ if not visual_put then
+ if conversion_table.put_backwards then
+ -- Special case for virtualedit at the end of a line.
+ local replace_string
+ if not virtualedit_end then
+ replace_string = 'test_stringx"%1'
+ else
+ replace_string = 'test_stringx"'
+ end
+ expect_base = expect_base:gsub('(.)test_stringx"', replace_string)
+ end
+ end
+ if conversion_table.count > 1 then
+ local rep_string = 'test_string"'
+ local extra_puts = rep_string:rep(conversion_table.count - 1)
+ expect_base = expect_base:gsub('test_stringx"', extra_puts .. 'test_stringx"')
+ end
+ if conversion_table.cursor_after then
+ expect_base = expect_base:gsub('test_stringx"', 'test_string"x')
+ end
+ if conversion_table.dot_register then
+ expect_base = expect_base:gsub('(test_stringx?)"', '%1.')
+ end
+ return expect_base
+ end -- convert_characterwise()
+
+ local function make_back(string)
+ local prev_line
+ local rettab = {}
+ local string_found = false
+ for _, line in pairs(funcs.split(string, '\n', 1)) do
+ if line:find('test_string') then
+ string_found = true
+ table.insert(rettab, line)
+ else
+ if string_found then
+ if prev_line then
+ table.insert(rettab, prev_line)
+ prev_line = nil
+ end
+ table.insert(rettab, line)
+ else
+ table.insert(rettab, prev_line)
+ prev_line = line
+ end
+ end
+ end
+ -- In case there are no lines after the text that was put.
+ if prev_line and string_found then
+ table.insert(rettab, prev_line)
+ end
+ return table.concat(rettab, '\n')
+ end -- make_back()
+
+ local function convert_linewise(expect_base, conversion_table, _, use_a, indent)
+ expect_base = dedent(expect_base)
+ if conversion_table.put_backwards then
+ expect_base = make_back(expect_base)
+ end
+ local p_str = 'test_string"'
+ if use_a then
+ p_str = 'test_stringa'
+ end
+
+ if conversion_table.dot_register then
+ expect_base = expect_base:gsub('x' .. p_str, 'xtest_string.')
+ p_str = 'test_string.'
+ end
+
+ if conversion_table.cursor_after then
+ expect_base = expect_base:gsub('x' .. p_str .. '\n', p_str .. '\nx')
+ end
+
+ -- The 'indent' argument is only used here because a single put with an
+ -- indent doesn't require special handling. It doesn't require special
+ -- handling because the cursor is never put before the indent, hence
+ -- the modification of 'test_stringx"' gives the same overall answer as
+ -- modifying ' test_stringx"'.
+
+ -- Only happens when using normal mode command actions.
+ if conversion_table.count and conversion_table.count > 1 then
+ if not indent then
+ indent = ''
+ end
+ local rep_string = indent .. p_str .. '\n'
+ local extra_puts = rep_string:rep(conversion_table.count - 1)
+ local orig_string, new_string
+ if conversion_table.cursor_after then
+ orig_string = indent .. p_str .. '\nx'
+ new_string = extra_puts .. orig_string
+ else
+ orig_string = indent .. 'x' .. p_str .. '\n'
+ new_string = orig_string .. extra_puts
+ end
+ expect_base = expect_base:gsub(orig_string, new_string)
+ end
+ return expect_base
+ end
+
+ local function put_x_last(orig_line, p_str)
+ local prev_end, cur_end, cur_start = 0, 0, 0
+ while cur_start do
+ prev_end = cur_end
+ cur_start, cur_end = orig_line:find(p_str, prev_end)
+ end
+ -- Assume (because that is the only way I call it) that p_str matches
+ -- the pattern 'test_string.'
+ return orig_line:sub(1, prev_end - 1) .. 'x' .. orig_line:sub(prev_end)
+ end
+
+ local function convert_blockwise(expect_base, conversion_table, visual,
+ use_b, trailing_whitespace)
+ expect_base = dedent(expect_base)
+ local p_str = 'test_string"'
+ if use_b then
+ p_str = 'test_stringb'
+ end
+
+ if conversion_table.dot_register then
+ expect_base = expect_base:gsub('(x?)' .. p_str, '%1test_string.')
+ -- Looks strange, but the dot is a special character in the pattern
+ -- and a literal character in the replacement.
+ expect_base = expect_base:gsub('test_stringx.', 'test_stringx.')
+ p_str = 'test_string.'
+ end
+
+ -- No difference between 'p' and 'P' in visual mode.
+ if not visual then
+ if conversion_table.put_backwards then
+ -- One for the line where the cursor is left, one for all other
+ -- lines.
+ expect_base = expect_base:gsub('([^x])' .. p_str, p_str .. '%1')
+ expect_base = expect_base:gsub('([^x])x' .. p_str, 'x' .. p_str .. '%1')
+ if not trailing_whitespace then
+ expect_base = expect_base:gsub(' \n', '\n')
+ expect_base = expect_base:gsub(' $', '')
+ end
+ end
+ end
+
+ if conversion_table.count and conversion_table.count > 1 then
+ local p_pattern = p_str:gsub('%.', '%%.')
+ expect_base = expect_base:gsub(p_pattern,
+ p_str:rep(conversion_table.count))
+ expect_base = expect_base:gsub('test_stringx([b".])',
+ p_str:rep(conversion_table.count - 1)
+ .. '%0')
+ end
+
+ if conversion_table.cursor_after then
+ if not visual then
+ local prev_line
+ local rettab = {}
+ local prev_in_block = false
+ for _, line in pairs(funcs.split(expect_base, '\n', 1)) do
+ if line:find('test_string') then
+ if prev_line then
+ prev_line = prev_line:gsub('x', '')
+ table.insert(rettab, prev_line)
+ end
+ prev_line = line
+ prev_in_block = true
+ else
+ if prev_in_block then
+ prev_line = put_x_last(prev_line, p_str)
+ table.insert(rettab, prev_line)
+ prev_in_block = false
+ end
+ table.insert(rettab, line)
+ end
+ end
+ if prev_line and prev_in_block then
+ table.insert(rettab, put_x_last(prev_line, p_str))
+ end
+
+ expect_base = table.concat(rettab, '\n')
+ else
+ expect_base = expect_base:gsub('x(.)', '%1x')
+ end
+ end
+
+ return expect_base
+ end
+ -- }}}
+
+ -- Convenience functions {{{
+ local function run_normal_mode_tests(test_string, base_map, extra_setup,
+ virtualedit_end, selection_string)
+ local function convert_closure(e, c)
+ return convert_characterwise(e, c, virtualedit_end, selection_string)
+ end
+ local function expect_normal_creator(expect_base, conversion_table)
+ local test_expect = expect_creator(convert_closure, expect_base, conversion_table)
+ return function(exception_table, after_redo)
+ test_expect(exception_table, after_redo)
+ if selection_string then
+ eq(getreg('"'), selection_string)
+ else
+ eq(getreg('"'), 'test_string"')
+ end
+ end
+ end
+ run_test_variations(
+ create_test_defs(
+ normal_command_defs,
+ base_map,
+ create_p_action,
+ test_string,
+ expect_normal_creator
+ ),
+ extra_setup
+ )
+ end -- run_normal_mode_tests()
+
+ local function convert_linewiseer(expect_base, conversion_table)
+ return expect_creator(convert_linewise, expect_base, conversion_table)
+ end
+
+ local function run_linewise_tests(expect_base, base_command, extra_setup)
+ local linewise_test_defs = create_test_defs(
+ ex_command_defs, base_command,
+ create_put_action, expect_base, convert_linewiseer)
+ run_test_variations(linewise_test_defs, extra_setup)
+ end -- run_linewise_tests()
+ -- }}}
+
+ -- Actual tests
+ describe('default pasting', function()
+ local expect_string = [[
+ Ltest_stringx"ine of words 1
+ Line of words 2]]
+ run_normal_mode_tests(expect_string, 'p')
+
+ run_linewise_tests([[
+ Line of words 1
+ xtest_string"
+ Line of words 2]],
+ 'put'
+ )
+ end)
+
+ describe('linewise register', function()
+ -- put with 'p'
+ local local_ex_command_defs = non_dotdefs(normal_command_defs)
+ local base_expect_string = [[
+ Line of words 1
+ xtest_stringa
+ Line of words 2]]
+ local function local_convert_linewise(expect_base, conversion_table)
+ return convert_linewise(expect_base, conversion_table, nil, true)
+ end
+ local function expect_lineput(expect_base, conversion_table)
+ return expect_creator(local_convert_linewise, expect_base, conversion_table)
+ end
+ run_test_variations(
+ create_test_defs(
+ local_ex_command_defs,
+ '"ap',
+ create_p_action,
+ base_expect_string,
+ expect_lineput
+ )
+ )
+
+ -- put with :put
+ local linewise_put_defs = non_dotdefs(ex_command_defs)
+ base_expect_string = [[
+ Line of words 1
+ xtest_stringa
+ Line of words 2]]
+ run_test_variations(
+ create_test_defs(
+ linewise_put_defs,
+ 'put a', create_put_action,
+ base_expect_string, convert_linewiseer
+ )
+ )
+
+ end)
+
+ describe('blockwise register', function()
+ local blockwise_put_defs = non_dotdefs(normal_command_defs)
+ local test_base = [[
+ Lxtest_stringbine of words 1
+ Ltest_stringbine of words 2
+ test_stringb]]
+
+ local function expect_block_creator(expect_base, conversion_table)
+ return expect_creator(function(e,c) return convert_blockwise(e,c,nil,true) end,
+ expect_base, conversion_table)
+ end
+
+ run_test_variations(
+ create_test_defs(
+ blockwise_put_defs,
+ '"bp',
+ create_p_action,
+ test_base,
+ expect_block_creator
+ )
+ )
+ end)
+
+ it('adds correct indentation when put with [p and ]p', function()
+ feed('G>>"a]pix<esc>')
+ -- luacheck: ignore
+ expect([[
+ Line of words 1
+ Line of words 2
+ xtest_stringa]])
+ feed('uu"a[pix<esc>')
+ -- luacheck: ignore
+ expect([[
+ Line of words 1
+ xtest_stringa
+ Line of words 2]])
+ end)
+
+ describe('linewise paste with autoindent', function()
+ -- luacheck: ignore
+ run_linewise_tests([[
+ Line of words 1
+ Line of words 2
+ xtest_string"]],
+ 'put'
+ ,
+ function()
+ funcs.setline('$', ' Line of words 2')
+ -- Set curswant to '8' to be at the end of the tab character
+ -- This is where the cursor is put back after the 'u' command.
+ funcs.setpos('.', {0, 2, 1, 0, 8})
+ execute('set autoindent')
+ end
+ )
+ end)
+
+ describe('put inside tabs with virtualedit', function()
+ local test_string = [[
+ Line of words 1
+ test_stringx" Line of words 2]]
+ run_normal_mode_tests(test_string, 'p', function()
+ funcs.setline('$', ' Line of words 2')
+ execute('set virtualedit=all')
+ funcs.setpos('.', {0, 2, 1, 2, 3})
+ end)
+ end)
+
+ describe('put after the line with virtualedit', function()
+ local test_string = [[
+ Line of words 1 test_stringx"
+ Line of words 2]]
+ run_normal_mode_tests(test_string, 'p', function()
+ funcs.setline('$', ' Line of words 2')
+ execute('set virtualedit=all')
+ funcs.setpos('.', {0, 1, 16, 1, 17})
+ end, true)
+ end)
+
+ describe('Visual put', function()
+ describe('basic put', function()
+ local test_string = [[
+ test_stringx" words 1
+ Line of words 2]]
+ run_normal_mode_tests(test_string, 'v2ep', nil, nil, 'Line of')
+ end)
+ describe('over trailing newline', function()
+ local test_string = 'Line of test_stringx"Line of words 2'
+ run_normal_mode_tests(test_string, 'v$p', function()
+ funcs.setpos('.', {0, 1, 9, 0, 9})
+ end,
+ nil,
+ 'words 1\n')
+ end)
+ describe('linewise mode', function()
+ local test_string = [[
+ xtest_string"
+ Line of words 2]]
+ local function expect_vis_linewise(expect_base, conversion_table)
+ return expect_creator(function(e, c)
+ return convert_linewise(e, c, nil, nil)
+ end,
+ expect_base, conversion_table)
+ end
+ run_test_variations(
+ create_test_defs(
+ normal_command_defs,
+ 'Vp',
+ create_p_action,
+ test_string,
+ expect_vis_linewise
+ ),
+ function() funcs.setpos('.', {0, 1, 1, 0, 1}) end
+ )
+
+ describe('with whitespace at bol', function()
+ local function expect_vis_lineindented(expect_base, conversion_table)
+ local test_expect = expect_creator(function(e, c)
+ return convert_linewise(e, c, nil, nil, ' ')
+ end,
+ expect_base, conversion_table)
+ return function(exception_table, after_redo)
+ test_expect(exception_table, after_redo)
+ eq(getreg('"'), 'Line of words 1\n')
+ end
+ end
+ local base_expect_string = [[
+ xtest_string"
+ Line of words 2]]
+ run_test_variations(
+ create_test_defs(
+ normal_command_defs,
+ 'Vp',
+ create_p_action,
+ base_expect_string,
+ expect_vis_lineindented
+ ),
+ function()
+ feed('i test_string.<esc>u')
+ funcs.setreg('"', ' test_string"', 'v')
+ end
+ )
+ end)
+
+ end)
+
+ describe('blockwise visual mode', function()
+ local test_base = [[
+ test_stringx"e of words 1
+ test_string"e of words 2]]
+
+ local function expect_block_creator(expect_base, conversion_table)
+ local test_expect = expect_creator(function(e, c)
+ return convert_blockwise(e, c, true)
+ end, expect_base, conversion_table)
+ return function(e,c)
+ test_expect(e,c)
+ eq(getreg('"'), 'Lin\nLin')
+ end
+ end
+
+ local select_down_test_defs = create_test_defs(
+ normal_command_defs,
+ '<C-v>jllp',
+ create_p_action,
+ test_base,
+ expect_block_creator
+ )
+ run_test_variations(select_down_test_defs)
+
+
+ -- Undo and redo of a visual block put leave the cursor in the top
+ -- left of the visual block area no matter where the cursor was
+ -- when it started.
+ local undo_redo_no = map(function(table)
+ local rettab = copy_def(table)
+ if not rettab[4] then
+ rettab[4] = {}
+ end
+ rettab[4].undo_position = true
+ rettab[4].redo_position = true
+ return rettab
+ end,
+ normal_command_defs)
+
+ -- Selection direction doesn't matter
+ run_test_variations(
+ create_test_defs(
+ undo_redo_no,
+ '<C-v>kllp',
+ create_p_action,
+ test_base,
+ expect_block_creator
+ ),
+ function() funcs.setpos('.', {0, 2, 1, 0, 1}) end
+ )
+
+ describe('blockwise cursor after undo', function()
+ -- A bit of a hack of the reset above.
+ -- In the tests that selection direction doesn't matter, we
+ -- don't check the undo/redo position because it doesn't fit
+ -- the same pattern as everything else.
+ -- Here we fix this by directly checking the undo/redo position
+ -- in the test_assertions of our test definitions.
+ local function assertion_creator(_,_)
+ return 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})
+ feed('<C-r>')
+ eq(funcs.getcurpos(), {0, 1, 1, 0, 1})
+ end
+ end
+
+ run_test_variations(
+ create_test_defs(
+ undo_redo_no,
+ '<C-v>kllp',
+ create_p_action,
+ test_base,
+ assertion_creator
+ ),
+ function() funcs.setpos('.', {0, 2, 1, 0, 1}) end
+ )
+ end)
+ end)
+
+
+ describe("with 'virtualedit'", function()
+ describe('splitting a tab character', function()
+ local base_expect_string = [[
+ Line of words 1
+ test_stringx" Line of words 2]]
+ run_normal_mode_tests(
+ base_expect_string,
+ 'vp',
+ function()
+ funcs.setline('$', ' Line of words 2')
+ execute('set virtualedit=all')
+ funcs.setpos('.', {0, 2, 1, 2, 3})
+ end,
+ nil,
+ ' '
+ )
+ end)
+ describe('after end of line', function()
+ local base_expect_string = [[
+ Line of words 1 test_stringx"
+ Line of words 2]]
+ run_normal_mode_tests(
+ base_expect_string,
+ 'vp',
+ function()
+ execute('set virtualedit=all')
+ funcs.setpos('.', {0, 1, 16, 2, 18})
+ end,
+ true,
+ ' '
+ )
+ end)
+ end)
+ end)
+
+ describe('. register special tests', function()
+ before_each(reset)
+ it('applies control character actions', function()
+ feed('i<C-t><esc>u')
+ expect([[
+ Line of words 1
+ Line of words 2]])
+ feed('".p')
+ expect([[
+ Line of words 1
+ Line of words 2]])
+ feed('u1go<C-v>j".p')
+ eq([[
+ ine of words 1
+ ine of words 2]], curbuf_contents())
+ end)
+
+ local function bell_test(actions, should_ring)
+ local screen = Screen.new()
+ screen:attach()
+ helpers.ok(not screen.bell and not screen.visualbell)
+ actions()
+ helpers.wait()
+ screen:wait(function()
+ if should_ring then
+ if not screen.bell and not screen.visualbell then
+ return 'Bell was not rung after action'
+ end
+ else
+ if screen.bell or screen.visualbell then
+ return 'Bell was rung after action'
+ end
+ end
+ end)
+ screen:detach()
+ end
+
+ it('should not ring the bell with gp at end of line', function()
+ bell_test(function() feed('$".gp') end)
+
+ -- Even if the last character is a multibyte character.
+ reset()
+ funcs.setline(1, 'helloม')
+ bell_test(function() feed('$".gp') end)
+ end)
+
+ it('should not ring the bell with gp and end of file', function()
+ funcs.setpos('.', {0, 2, 1, 0})
+ bell_test(function() feed('$vl".gp') end)
+ end)
+
+ it('should ring the bell when deleting if not appropriate', function()
+ execute('goto 2')
+ feed('i<bs><esc>')
+ expect([[
+ ine of words 1
+ Line of words 2]])
+ bell_test(function() feed('".P') end, true)
+ end)
+
+ it('should restore cursor position after undo of ".p', function()
+ local origpos = funcs.getcurpos()
+ feed('".pu')
+ eq(origpos, funcs.getcurpos())
+ end)
+
+ it("should be unaffected by 'autoindent' with V\".2p", function()
+ execute('set autoindent')
+ feed('i test_string.<esc>u')
+ feed('V".2p')
+ expect([[
+ test_string.
+ test_string.
+ Line of words 2]])
+ end)
+ end)
+end)
+