mirror of
				https://github.com/neovim/neovim.git
				synced 2025-11-03 17:24:29 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			949 lines
		
	
	
		
			29 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			949 lines
		
	
	
		
			29 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
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.tbl_map
 | 
						|
local filter = helpers.tbl_filter
 | 
						|
local feed_command = helpers.feed_command
 | 
						|
local curbuf_contents = helpers.curbuf_contents
 | 
						|
local funcs = helpers.funcs
 | 
						|
local dedent = helpers.dedent
 | 
						|
local getreg = funcs.getreg
 | 
						|
 | 
						|
local function reset()
 | 
						|
  feed_command('enew!')
 | 
						|
  insert([[
 | 
						|
  Line of words 1
 | 
						|
  Line of words 2]])
 | 
						|
  feed_command('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')
 | 
						|
  feed_command('set virtualedit=')
 | 
						|
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()
 | 
						|
  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(init_cursorpos, funcs.getcurpos())
 | 
						|
      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(orig_dotstr, funcs.getreg('.'))
 | 
						|
        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 occurrence of the character 'x' in
 | 
						|
    -- expect_string.
 | 
						|
    -- 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.
 | 
						|
    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()
 | 
						|
      feed_command(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_charwise(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_charwise()
 | 
						|
 | 
						|
  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_charwise(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
 | 
						|
          if not conversion_table.put_backwards then
 | 
						|
            eq(selection_string, getreg('"'))
 | 
						|
          end
 | 
						|
        else
 | 
						|
          eq('test_string"', getreg('"'))
 | 
						|
        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})
 | 
						|
        feed_command('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')
 | 
						|
      feed_command('set virtualedit=all')
 | 
						|
      funcs.setpos('.', {0, 2, 1, 2, 3})
 | 
						|
    end)
 | 
						|
  end)
 | 
						|
 | 
						|
  describe('put after the line with virtualedit', function()
 | 
						|
    -- luacheck: ignore 621
 | 
						|
    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')
 | 
						|
      feed_command('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)
 | 
						|
            if not conversion_table.put_backwards then
 | 
						|
              eq('Line of words 1\n', getreg('"'))
 | 
						|
            end
 | 
						|
          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)
 | 
						|
          if not conversion_table.put_backwards then
 | 
						|
            eq('Lin\nLin', getreg('"'))
 | 
						|
          end
 | 
						|
        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({0, 1, 1, 0, 1}, funcs.getcurpos())
 | 
						|
            feed('<C-r>')
 | 
						|
            eq({0, 1, 1, 0, 1}, funcs.getcurpos())
 | 
						|
          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')
 | 
						|
            feed_command('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()
 | 
						|
            feed_command('set virtualedit=all')
 | 
						|
            funcs.setpos('.', {0, 1, 16, 2, 18})
 | 
						|
          end,
 | 
						|
          true,
 | 
						|
          ' '
 | 
						|
        )
 | 
						|
      end)
 | 
						|
    end)
 | 
						|
  end)
 | 
						|
 | 
						|
  describe('. register special tests', function()
 | 
						|
    -- luacheck: ignore 621
 | 
						|
    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 screen
 | 
						|
    setup(function()
 | 
						|
      screen = Screen.new()
 | 
						|
      screen:attach()
 | 
						|
    end)
 | 
						|
 | 
						|
    local function bell_test(actions, should_ring)
 | 
						|
      if should_ring then
 | 
						|
        -- check bell is not set by nvim before the action
 | 
						|
        screen:sleep(50)
 | 
						|
      end
 | 
						|
      helpers.ok(not screen.bell and not screen.visualbell)
 | 
						|
      actions()
 | 
						|
      screen:expect{condition=function()
 | 
						|
        if should_ring then
 | 
						|
          if not screen.bell and not screen.visualbell then
 | 
						|
            error('Bell was not rung after action')
 | 
						|
          end
 | 
						|
        else
 | 
						|
          if screen.bell or screen.visualbell then
 | 
						|
            error('Bell was rung after action')
 | 
						|
          end
 | 
						|
        end
 | 
						|
      end, unchanged=(not should_ring)}
 | 
						|
    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()
 | 
						|
        feed_command('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()
 | 
						|
      feed_command('set autoindent')
 | 
						|
      feed('i test_string.<esc>u')
 | 
						|
      feed('V".2p')
 | 
						|
      expect([[
 | 
						|
       test_string.
 | 
						|
       test_string.
 | 
						|
      Line of words 2]])
 | 
						|
    end)
 | 
						|
  end)
 | 
						|
end)
 | 
						|
 |