neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

put_spec.lua (29023B)


      1 local t = require('test.testutil')
      2 local n = require('test.functional.testnvim')()
      3 local Screen = require('test.functional.ui.screen')
      4 
      5 local clear = n.clear
      6 local insert = n.insert
      7 local feed = n.feed
      8 local expect = n.expect
      9 local eq = t.eq
     10 local map = vim.tbl_map
     11 local filter = vim.tbl_filter
     12 local command = n.command
     13 local curbuf_contents = n.curbuf_contents
     14 local fn = n.fn
     15 local dedent = t.dedent
     16 
     17 local function reset()
     18  command('bwipe! | new')
     19  insert([[
     20  Line of words 1
     21  Line of words 2]])
     22  command('goto 1')
     23  feed('itest_string.<esc>u')
     24  fn.setreg('a', 'test_stringa', 'V')
     25  fn.setreg('b', 'test_stringb\ntest_stringb\ntest_stringb', 'b')
     26  fn.setreg('"', 'test_string"', 'v')
     27 end
     28 
     29 -- We check the last inserted register ". in each of these tests because it is
     30 -- implemented completely differently in do_put().
     31 -- It is implemented differently so that control characters and imap'ped
     32 -- characters work in the same manner when pasted as when inserted.
     33 describe('put command', function()
     34  clear()
     35  before_each(reset)
     36 
     37  local function visual_marks_zero()
     38    for _, v in pairs(fn.getpos("'<")) do
     39      if v ~= 0 then
     40        return false
     41      end
     42    end
     43    for _, v in pairs(fn.getpos("'>")) do
     44      if v ~= 0 then
     45        return false
     46      end
     47    end
     48    return true
     49  end
     50 
     51  -- {{{ Where test definitions are run
     52  local function run_test_variations(test_variations, extra_setup)
     53    reset()
     54    if extra_setup then
     55      extra_setup()
     56    end
     57    local init_contents = curbuf_contents()
     58    local init_cursorpos = fn.getcurpos()
     59    local assert_no_change = function(exception_table, after_undo)
     60      expect(init_contents)
     61      -- When putting the ". register forwards, undo doesn't move
     62      -- the cursor back to where it was before.
     63      -- This is because it uses the command character 'a' to
     64      -- start the insert, and undo after that leaves the cursor
     65      -- one place to the right (unless we were at the end of the
     66      -- line when we pasted).
     67      if not (exception_table.undo_position and after_undo) then
     68        eq(init_cursorpos, fn.getcurpos())
     69      end
     70    end
     71 
     72    for _, test in pairs(test_variations) do
     73      it(test.description, function()
     74        if extra_setup then
     75          extra_setup()
     76        end
     77        local orig_dotstr = fn.getreg('.')
     78        t.ok(visual_marks_zero())
     79        -- Make sure every test starts from the same conditions
     80        assert_no_change(test.exception_table, false)
     81        local was_cli = test.test_action()
     82        test.test_assertions(test.exception_table, false)
     83        -- Check that undo twice puts us back to the original conditions
     84        -- (i.e. puts the cursor and text back to before)
     85        feed('u')
     86        assert_no_change(test.exception_table, true)
     87 
     88        -- Should not have changed the ". register
     89        -- If we paste the ". register with a count we can't avoid
     90        -- changing this register, hence avoid this check.
     91        if not test.exception_table.dot_reg_changed then
     92          eq(orig_dotstr, fn.getreg('.'))
     93        end
     94 
     95        -- Doing something, undoing it, and then redoing it should
     96        -- leave us in the same state as just doing it once.
     97        -- For :ex actions we want '@:', for normal actions we want '.'
     98 
     99        -- The '.' redo doesn't work for visual put so just exit if
    100        -- it was tested.
    101        -- We check that visual put was used by checking if the '< and
    102        -- '> marks were changed.
    103        if not visual_marks_zero() then
    104          return
    105        end
    106 
    107        if test.exception_table.undo_position then
    108          fn.setpos('.', init_cursorpos)
    109        end
    110        if was_cli then
    111          feed('@:')
    112        else
    113          feed('.')
    114        end
    115 
    116        test.test_assertions(test.exception_table, true)
    117      end)
    118    end
    119  end -- run_test_variations()
    120  -- }}}
    121 
    122  local function create_test_defs(
    123    test_defs,
    124    command_base,
    125    command_creator, -- {{{
    126    expect_base,
    127    expect_creator
    128  )
    129    local rettab = {}
    130    local exceptions
    131    for _, v in pairs(test_defs) do
    132      if v[4] then
    133        exceptions = v[4]
    134      else
    135        exceptions = {}
    136      end
    137      table.insert(rettab, {
    138        test_action = command_creator(command_base, v[1]),
    139        test_assertions = expect_creator(expect_base, v[2]),
    140        description = v[3],
    141        exception_table = exceptions,
    142      })
    143    end
    144    return rettab
    145  end -- create_test_defs() }}}
    146 
    147  local function find_cursor_position(expect_string) -- {{{
    148    -- There must only be one occurrence of the character 'x' in
    149    -- expect_string.
    150    -- This function removes that occurrence, and returns the position that
    151    -- it was in.
    152    -- This returns the cursor position that would leave the 'x' in that
    153    -- place if we feed 'ix<esc>' and the string existed before it.
    154    for linenum, line in pairs(fn.split(expect_string, '\n', 1)) do
    155      local column = line:find('x')
    156      if column then
    157        return { linenum, column }, expect_string:gsub('x', '')
    158      end
    159    end
    160  end -- find_cursor_position() }}}
    161 
    162  -- Action function creators {{{
    163  local function create_p_action(test_map, substitution)
    164    local temp_val = test_map:gsub('p', substitution)
    165    return function()
    166      feed(temp_val)
    167      return false
    168    end
    169  end
    170 
    171  local function create_put_action(command_base, substitution)
    172    local temp_val = command_base:gsub('put', substitution)
    173    return function()
    174      feed(':' .. temp_val .. '<CR>')
    175      return true
    176    end
    177  end
    178  -- }}}
    179 
    180  -- Expect function creator {{{
    181  local function expect_creator(conversion_function, expect_base, conversion_table)
    182    local temp_expect_string = conversion_function(expect_base, conversion_table)
    183    local cursor_position, expect_string = find_cursor_position(temp_expect_string)
    184    return function(exception_table, after_redo)
    185      expect(expect_string)
    186 
    187      -- Have to use getcurpos() instead of api.nvim_win_get_cursor(0) in
    188      -- order to account for virtualedit.
    189      -- We always want the curswant element in getcurpos(), which is
    190      -- sometimes different to the column element in
    191      -- api.nvim_win_get_cursor(0).
    192      -- NOTE: The ".gp command leaves the cursor after the pasted text
    193      -- when running, but does not when the command is redone with the
    194      -- '.' command.
    195      if not (exception_table.redo_position and after_redo) then
    196        local actual_position = fn.getcurpos()
    197        eq(cursor_position, { actual_position[2], actual_position[5] })
    198      end
    199    end
    200  end -- expect_creator() }}}
    201 
    202  -- Test definitions {{{
    203  local function copy_def(def)
    204    local rettab = { '', {}, '', nil }
    205    rettab[1] = def[1]
    206    for k, v in pairs(def[2]) do
    207      rettab[2][k] = v
    208    end
    209    rettab[3] = def[3]
    210    if def[4] then
    211      rettab[4] = {}
    212      for k, v in pairs(def[4]) do
    213        rettab[4][k] = v
    214      end
    215    end
    216    return rettab
    217  end
    218 
    219  local normal_command_defs = {
    220    {
    221      'p',
    222      { cursor_after = false, put_backwards = false, dot_register = false },
    223      'pastes after cursor with p',
    224    },
    225    {
    226      'gp',
    227      { cursor_after = true, put_backwards = false, dot_register = false },
    228      'leaves cursor after text with gp',
    229    },
    230    {
    231      '".p',
    232      { cursor_after = false, put_backwards = false, dot_register = true },
    233      'works with the ". register',
    234    },
    235    {
    236      '".gp',
    237      { cursor_after = true, put_backwards = false, dot_register = true },
    238      'gp works with the ". register',
    239      { redo_position = true },
    240    },
    241    {
    242      'P',
    243      { cursor_after = false, put_backwards = true, dot_register = false },
    244      'pastes before cursor with P',
    245    },
    246    {
    247      'gP',
    248      { cursor_after = true, put_backwards = true, dot_register = false },
    249      'gP pastes before cursor and leaves cursor after text',
    250    },
    251    {
    252      '".P',
    253      { cursor_after = false, put_backwards = true, dot_register = true },
    254      'P works with ". register',
    255    },
    256    {
    257      '".gP',
    258      { cursor_after = true, put_backwards = true, dot_register = true },
    259      'gP works with ". register',
    260      { redo_position = true },
    261    },
    262  }
    263 
    264  -- Add a definition applying a count for each definition above.
    265  -- Could do this for each transformation (p -> P, p -> gp etc), but I think
    266  -- it's neater this way (balance between being explicit and too verbose).
    267  for i = 1, #normal_command_defs do
    268    local cur = normal_command_defs[i]
    269 
    270    -- Make modified copy of current definition that includes a count.
    271    local newdef = copy_def(cur)
    272    newdef[2].count = 2
    273    cur[2].count = 1
    274    newdef[1] = '2' .. newdef[1]
    275    newdef[3] = 'double ' .. newdef[3]
    276 
    277    if cur[2].dot_register then
    278      if not cur[4] then
    279        newdef[4] = {}
    280      end
    281      newdef[4].dot_reg_changed = true
    282    end
    283 
    284    normal_command_defs[#normal_command_defs + 1] = newdef
    285  end
    286 
    287  local ex_command_defs = {
    288    {
    289      'put',
    290      { put_backwards = false, dot_register = false },
    291      'pastes linewise forwards with :put',
    292    },
    293    {
    294      'put!',
    295      { put_backwards = true, dot_register = false },
    296      'pastes linewise backwards with :put!',
    297    },
    298    {
    299      'put .',
    300      { put_backwards = false, dot_register = true },
    301      'pastes linewise with the dot register',
    302    },
    303    {
    304      'put! .',
    305      { put_backwards = true, dot_register = true },
    306      'pastes linewise backwards with the dot register',
    307    },
    308  }
    309 
    310  local function non_dotdefs(def_table)
    311    return filter(function(d)
    312      return not d[2].dot_register
    313    end, def_table)
    314  end
    315 
    316  -- }}}
    317 
    318  -- Conversion functions {{{
    319  local function convert_charwise(expect_base, conversion_table, virtualedit_end, visual_put)
    320    expect_base = dedent(expect_base)
    321    -- There is no difference between 'P' and 'p' when VIsual_active
    322    if not visual_put then
    323      if conversion_table.put_backwards then
    324        -- Special case for virtualedit at the end of a line.
    325        local replace_string
    326        if not virtualedit_end then
    327          replace_string = 'test_stringx"%1'
    328        else
    329          replace_string = 'test_stringx"'
    330        end
    331        expect_base = expect_base:gsub('(.)test_stringx"', replace_string)
    332      end
    333    end
    334    if conversion_table.count > 1 then
    335      local rep_string = 'test_string"'
    336      local extra_puts = rep_string:rep(conversion_table.count - 1)
    337      expect_base = expect_base:gsub('test_stringx"', extra_puts .. 'test_stringx"')
    338    end
    339    if conversion_table.cursor_after then
    340      expect_base = expect_base:gsub('test_stringx"', 'test_string"x')
    341    end
    342    if conversion_table.dot_register then
    343      expect_base = expect_base:gsub('(test_stringx?)"', '%1.')
    344    end
    345    return expect_base
    346  end -- convert_charwise()
    347 
    348  local function make_back(string)
    349    local prev_line
    350    local rettab = {}
    351    local string_found = false
    352    for _, line in pairs(fn.split(string, '\n', 1)) do
    353      if line:find('test_string') then
    354        string_found = true
    355        table.insert(rettab, line)
    356      else
    357        if string_found then
    358          if prev_line then
    359            table.insert(rettab, prev_line)
    360            prev_line = nil
    361          end
    362          table.insert(rettab, line)
    363        else
    364          table.insert(rettab, prev_line)
    365          prev_line = line
    366        end
    367      end
    368    end
    369    -- In case there are no lines after the text that was put.
    370    if prev_line and string_found then
    371      table.insert(rettab, prev_line)
    372    end
    373    return table.concat(rettab, '\n')
    374  end -- make_back()
    375 
    376  local function convert_linewise(expect_base, conversion_table, _, use_a, indent)
    377    expect_base = dedent(expect_base)
    378    if conversion_table.put_backwards then
    379      expect_base = make_back(expect_base)
    380    end
    381    local p_str = 'test_string"'
    382    if use_a then
    383      p_str = 'test_stringa'
    384    end
    385 
    386    if conversion_table.dot_register then
    387      expect_base = expect_base:gsub('x' .. p_str, 'xtest_string.')
    388      p_str = 'test_string.'
    389    end
    390 
    391    if conversion_table.cursor_after then
    392      expect_base = expect_base:gsub('x' .. p_str .. '\n', p_str .. '\nx')
    393    end
    394 
    395    -- The 'indent' argument is only used here because a single put with an
    396    -- indent doesn't require special handling. It doesn't require special
    397    -- handling because the cursor is never put before the indent, hence
    398    -- the modification of 'test_stringx"' gives the same overall answer as
    399    -- modifying '    test_stringx"'.
    400 
    401    -- Only happens when using normal mode command actions.
    402    if conversion_table.count and conversion_table.count > 1 then
    403      if not indent then
    404        indent = ''
    405      end
    406      local rep_string = indent .. p_str .. '\n'
    407      local extra_puts = rep_string:rep(conversion_table.count - 1)
    408      local orig_string, new_string
    409      if conversion_table.cursor_after then
    410        orig_string = indent .. p_str .. '\nx'
    411        new_string = extra_puts .. orig_string
    412      else
    413        orig_string = indent .. 'x' .. p_str .. '\n'
    414        new_string = orig_string .. extra_puts
    415      end
    416      expect_base = expect_base:gsub(orig_string, new_string)
    417    end
    418    return expect_base
    419  end
    420 
    421  local function put_x_last(orig_line, p_str)
    422    local prev_end, cur_end, cur_start = 0, 0, 0
    423    while cur_start do
    424      prev_end = cur_end
    425      cur_start, cur_end = orig_line:find(p_str, prev_end)
    426    end
    427    -- Assume (because that is the only way I call it) that p_str matches
    428    -- the pattern 'test_string.'
    429    return orig_line:sub(1, prev_end - 1) .. 'x' .. orig_line:sub(prev_end)
    430  end
    431 
    432  local function convert_blockwise(
    433    expect_base,
    434    conversion_table,
    435    visual,
    436    use_b,
    437    trailing_whitespace
    438  )
    439    expect_base = dedent(expect_base)
    440    local p_str = 'test_string"'
    441    if use_b then
    442      p_str = 'test_stringb'
    443    end
    444 
    445    if conversion_table.dot_register then
    446      expect_base = expect_base:gsub('(x?)' .. p_str, '%1test_string.')
    447      -- Looks strange, but the dot is a special character in the pattern
    448      -- and a literal character in the replacement.
    449      expect_base = expect_base:gsub('test_stringx.', 'test_stringx.')
    450      p_str = 'test_string.'
    451    end
    452 
    453    -- No difference between 'p' and 'P' in visual mode.
    454    if not visual then
    455      if conversion_table.put_backwards then
    456        -- One for the line where the cursor is left, one for all other
    457        -- lines.
    458        expect_base = expect_base:gsub('([^x])' .. p_str, p_str .. '%1')
    459        expect_base = expect_base:gsub('([^x])x' .. p_str, 'x' .. p_str .. '%1')
    460        if not trailing_whitespace then
    461          expect_base = expect_base:gsub(' \n', '\n')
    462          expect_base = expect_base:gsub(' $', '')
    463        end
    464      end
    465    end
    466 
    467    if conversion_table.count and conversion_table.count > 1 then
    468      local p_pattern = p_str:gsub('%.', '%%.')
    469      expect_base = expect_base:gsub(p_pattern, p_str:rep(conversion_table.count))
    470      expect_base =
    471        expect_base:gsub('test_stringx([b".])', p_str:rep(conversion_table.count - 1) .. '%0')
    472    end
    473 
    474    if conversion_table.cursor_after then
    475      if not visual then
    476        local prev_line
    477        local rettab = {}
    478        local prev_in_block = false
    479        for _, line in pairs(fn.split(expect_base, '\n', 1)) do
    480          if line:find('test_string') then
    481            if prev_line then
    482              prev_line = prev_line:gsub('x', '')
    483              table.insert(rettab, prev_line)
    484            end
    485            prev_line = line
    486            prev_in_block = true
    487          else
    488            if prev_in_block then
    489              prev_line = put_x_last(prev_line, p_str)
    490              table.insert(rettab, prev_line)
    491              prev_in_block = false
    492            end
    493            table.insert(rettab, line)
    494          end
    495        end
    496        if prev_line and prev_in_block then
    497          table.insert(rettab, put_x_last(prev_line, p_str))
    498        end
    499 
    500        expect_base = table.concat(rettab, '\n')
    501      else
    502        expect_base = expect_base:gsub('x(.)', '%1x')
    503      end
    504    end
    505 
    506    return expect_base
    507  end
    508  -- }}}
    509 
    510  -- Convenience functions {{{
    511  local function run_normal_mode_tests(
    512    test_string,
    513    base_map,
    514    extra_setup,
    515    virtualedit_end,
    516    selection_string
    517  )
    518    local function convert_closure(e, c)
    519      return convert_charwise(e, c, virtualedit_end, selection_string)
    520    end
    521    local function expect_normal_creator(expect_base, conversion_table)
    522      local test_expect = expect_creator(convert_closure, expect_base, conversion_table)
    523      return function(exception_table, after_redo)
    524        test_expect(exception_table, after_redo)
    525        if selection_string then
    526          if not conversion_table.put_backwards then
    527            eq(selection_string, fn.getreg('"'))
    528          end
    529        else
    530          eq('test_string"', fn.getreg('"'))
    531        end
    532      end
    533    end
    534    run_test_variations(
    535      create_test_defs(
    536        normal_command_defs,
    537        base_map,
    538        create_p_action,
    539        test_string,
    540        expect_normal_creator
    541      ),
    542      extra_setup
    543    )
    544  end -- run_normal_mode_tests()
    545 
    546  local function convert_linewiseer(expect_base, conversion_table)
    547    return expect_creator(convert_linewise, expect_base, conversion_table)
    548  end
    549 
    550  local function run_linewise_tests(expect_base, base_command, extra_setup)
    551    local linewise_test_defs = create_test_defs(
    552      ex_command_defs,
    553      base_command,
    554      create_put_action,
    555      expect_base,
    556      convert_linewiseer
    557    )
    558    run_test_variations(linewise_test_defs, extra_setup)
    559  end -- run_linewise_tests()
    560  -- }}}
    561 
    562  -- Actual tests
    563  describe('default pasting', function()
    564    local expect_string = [[
    565    Ltest_stringx"ine of words 1
    566    Line of words 2]]
    567    run_normal_mode_tests(expect_string, 'p')
    568 
    569    run_linewise_tests(
    570      [[
    571      Line of words 1
    572      xtest_string"
    573      Line of words 2]],
    574      'put'
    575    )
    576  end)
    577 
    578  describe('linewise register', function()
    579    -- put with 'p'
    580    local local_ex_command_defs = non_dotdefs(normal_command_defs)
    581    local base_expect_string = [[
    582    Line of words 1
    583    xtest_stringa
    584    Line of words 2]]
    585    local function local_convert_linewise(expect_base, conversion_table)
    586      return convert_linewise(expect_base, conversion_table, nil, true)
    587    end
    588    local function expect_lineput(expect_base, conversion_table)
    589      return expect_creator(local_convert_linewise, expect_base, conversion_table)
    590    end
    591    run_test_variations(
    592      create_test_defs(
    593        local_ex_command_defs,
    594        '"ap',
    595        create_p_action,
    596        base_expect_string,
    597        expect_lineput
    598      )
    599    )
    600 
    601    -- put with :put
    602    local linewise_put_defs = non_dotdefs(ex_command_defs)
    603    base_expect_string = [[
    604    Line of words 1
    605    xtest_stringa
    606    Line of words 2]]
    607    run_test_variations(
    608      create_test_defs(
    609        linewise_put_defs,
    610        'put a',
    611        create_put_action,
    612        base_expect_string,
    613        convert_linewiseer
    614      )
    615    )
    616  end)
    617 
    618  describe('blockwise register', function()
    619    local blockwise_put_defs = non_dotdefs(normal_command_defs)
    620    local test_base = [[
    621    Lxtest_stringbine of words 1
    622    Ltest_stringbine of words 2
    623     test_stringb]]
    624 
    625    local function expect_block_creator(expect_base, conversion_table)
    626      return expect_creator(function(e, c)
    627        return convert_blockwise(e, c, nil, true)
    628      end, expect_base, conversion_table)
    629    end
    630 
    631    run_test_variations(
    632      create_test_defs(blockwise_put_defs, '"bp', create_p_action, test_base, expect_block_creator)
    633    )
    634  end)
    635 
    636  it('adds correct indentation when put with [p and ]p', function()
    637    feed('G>>"a]pix<esc>')
    638    -- luacheck: ignore
    639    expect([[
    640    Line of words 1
    641    	Line of words 2
    642    	xtest_stringa]])
    643    feed('uu"a[pix<esc>')
    644    -- luacheck: ignore
    645    expect([[
    646    Line of words 1
    647    	xtest_stringa
    648    	Line of words 2]])
    649  end)
    650 
    651  describe('linewise paste with autoindent', function()
    652    -- luacheck: ignore
    653    run_linewise_tests(
    654      [[
    655        Line of words 1
    656        	Line of words 2
    657        xtest_string"]],
    658      'put',
    659      function()
    660        fn.setline('$', '	Line of words 2')
    661        -- Set curswant to '8' to be at the end of the tab character
    662        -- This is where the cursor is put back after the 'u' command.
    663        fn.setpos('.', { 0, 2, 1, 0, 8 })
    664        command('set autoindent')
    665      end
    666    )
    667  end)
    668 
    669  describe('put inside tabs with virtualedit', function()
    670    local test_string = [[
    671    Line of words 1
    672       test_stringx"     Line of words 2]]
    673    run_normal_mode_tests(test_string, 'p', function()
    674      fn.setline('$', '	Line of words 2')
    675      command('setlocal virtualedit=all')
    676      fn.setpos('.', { 0, 2, 1, 2, 3 })
    677    end)
    678  end)
    679 
    680  describe('put after the line with virtualedit', function()
    681    -- luacheck: ignore 621
    682    local test_string = [[
    683    Line of words 1  test_stringx"
    684    	Line of words 2]]
    685    run_normal_mode_tests(test_string, 'p', function()
    686      fn.setline('$', '	Line of words 2')
    687      command('setlocal virtualedit=all')
    688      fn.setpos('.', { 0, 1, 16, 1, 17 })
    689    end, true)
    690  end)
    691 
    692  describe('Visual put', function()
    693    describe('basic put', function()
    694      local test_string = [[
    695      test_stringx" words 1
    696      Line of words 2]]
    697      run_normal_mode_tests(test_string, 'v2ep', nil, nil, 'Line of')
    698    end)
    699    describe('over trailing newline', function()
    700      local test_string = 'Line of test_stringx"Line of words 2'
    701      run_normal_mode_tests(test_string, 'v$p', function()
    702        fn.setpos('.', { 0, 1, 9, 0, 9 })
    703      end, nil, 'words 1\n')
    704    end)
    705    describe('linewise mode', function()
    706      local test_string = [[
    707      xtest_string"
    708      Line of words 2]]
    709      local function expect_vis_linewise(expect_base, conversion_table)
    710        return expect_creator(function(e, c)
    711          return convert_linewise(e, c, nil, nil)
    712        end, expect_base, conversion_table)
    713      end
    714      run_test_variations(
    715        create_test_defs(
    716          normal_command_defs,
    717          'Vp',
    718          create_p_action,
    719          test_string,
    720          expect_vis_linewise
    721        ),
    722        function()
    723          fn.setpos('.', { 0, 1, 1, 0, 1 })
    724        end
    725      )
    726 
    727      describe('with whitespace at bol', function()
    728        local function expect_vis_lineindented(expect_base, conversion_table)
    729          local test_expect = expect_creator(function(e, c)
    730            return convert_linewise(e, c, nil, nil, '    ')
    731          end, expect_base, conversion_table)
    732          return function(exception_table, after_redo)
    733            test_expect(exception_table, after_redo)
    734            if not conversion_table.put_backwards then
    735              eq('Line of words 1\n', fn.getreg('"'))
    736            end
    737          end
    738        end
    739        local base_expect_string = [[
    740            xtest_string"
    741        Line of words 2]]
    742        run_test_variations(
    743          create_test_defs(
    744            normal_command_defs,
    745            'Vp',
    746            create_p_action,
    747            base_expect_string,
    748            expect_vis_lineindented
    749          ),
    750          function()
    751            feed('i    test_string.<esc>u')
    752            fn.setreg('"', '    test_string"', 'v')
    753          end
    754        )
    755      end)
    756    end)
    757 
    758    describe('blockwise visual mode', function()
    759      local test_base = [[
    760        test_stringx"e of words 1
    761        test_string"e of words 2]]
    762 
    763      local function expect_block_creator(expect_base, conversion_table)
    764        local test_expect = expect_creator(function(e, c)
    765          return convert_blockwise(e, c, true)
    766        end, expect_base, conversion_table)
    767        return function(e, c)
    768          test_expect(e, c)
    769          if not conversion_table.put_backwards then
    770            eq('Lin\nLin', fn.getreg('"'))
    771          end
    772        end
    773      end
    774 
    775      local select_down_test_defs = create_test_defs(
    776        normal_command_defs,
    777        '<C-v>jllp',
    778        create_p_action,
    779        test_base,
    780        expect_block_creator
    781      )
    782      run_test_variations(select_down_test_defs)
    783 
    784      -- Undo and redo of a visual block put leave the cursor in the top
    785      -- left of the visual block area no matter where the cursor was
    786      -- when it started.
    787      local undo_redo_no = map(function(table)
    788        local rettab = copy_def(table)
    789        if not rettab[4] then
    790          rettab[4] = {}
    791        end
    792        rettab[4].undo_position = true
    793        rettab[4].redo_position = true
    794        return rettab
    795      end, normal_command_defs)
    796 
    797      -- Selection direction doesn't matter
    798      run_test_variations(
    799        create_test_defs(
    800          undo_redo_no,
    801          '<C-v>kllp',
    802          create_p_action,
    803          test_base,
    804          expect_block_creator
    805        ),
    806        function()
    807          fn.setpos('.', { 0, 2, 1, 0, 1 })
    808        end
    809      )
    810 
    811      describe('blockwise cursor after undo', function()
    812        -- A bit of a hack of the reset above.
    813        -- In the tests that selection direction doesn't matter, we
    814        -- don't check the undo/redo position because it doesn't fit
    815        -- the same pattern as everything else.
    816        -- Here we fix this by directly checking the undo/redo position
    817        -- in the test_assertions of our test definitions.
    818        local function assertion_creator(_, _)
    819          return function(_, _)
    820            feed('u')
    821            -- Have to use feed('u') here to set curswant, because
    822            -- ex_undo() doesn't do that.
    823            eq({ 0, 1, 1, 0, 1 }, fn.getcurpos())
    824            feed('<C-r>')
    825            eq({ 0, 1, 1, 0, 1 }, fn.getcurpos())
    826          end
    827        end
    828 
    829        run_test_variations(
    830          create_test_defs(undo_redo_no, '<C-v>kllp', create_p_action, test_base, assertion_creator),
    831          function()
    832            fn.setpos('.', { 0, 2, 1, 0, 1 })
    833          end
    834        )
    835      end)
    836    end)
    837 
    838    describe("with 'virtualedit'", function()
    839      describe('splitting a tab character', function()
    840        local base_expect_string = [[
    841        Line of words 1
    842          test_stringx"     Line of words 2]]
    843        run_normal_mode_tests(base_expect_string, 'vp', function()
    844          fn.setline('$', '	Line of words 2')
    845          command('setlocal virtualedit=all')
    846          fn.setpos('.', { 0, 2, 1, 2, 3 })
    847        end, nil, ' ')
    848      end)
    849      describe('after end of line', function()
    850        local base_expect_string = [[
    851        Line of words 1  test_stringx"
    852        Line of words 2]]
    853        run_normal_mode_tests(base_expect_string, 'vp', function()
    854          command('setlocal virtualedit=all')
    855          fn.setpos('.', { 0, 1, 16, 2, 18 })
    856        end, true, ' ')
    857      end)
    858    end)
    859  end)
    860 
    861  describe('. register special tests', function()
    862    -- luacheck: ignore 621
    863    before_each(reset)
    864    it('applies control character actions', function()
    865      feed('i<C-t><esc>u')
    866      expect([[
    867      Line of words 1
    868      Line of words 2]])
    869      feed('".p')
    870      expect([[
    871      	Line of words 1
    872      Line of words 2]])
    873      feed('u1go<C-v>j".p')
    874      eq(
    875        [[
    876 ine of words 1
    877 ine of words 2]],
    878        curbuf_contents()
    879      )
    880    end)
    881 
    882    local screen
    883    setup(function()
    884      screen = Screen.new()
    885    end)
    886 
    887    local function bell_test(actions, should_ring)
    888      if should_ring then
    889        -- check bell is not set by nvim before the action
    890        screen:sleep(50)
    891      end
    892      t.ok(not screen.bell and not screen.visualbell)
    893      actions()
    894      screen:expect {
    895        condition = function()
    896          if should_ring then
    897            if not screen.bell and not screen.visualbell then
    898              error('Bell was not rung after action')
    899            end
    900          else
    901            if screen.bell or screen.visualbell then
    902              error('Bell was rung after action')
    903            end
    904          end
    905        end,
    906        unchanged = not should_ring,
    907      }
    908      screen.bell = false
    909      screen.visualbell = false
    910    end
    911 
    912    it('should not ring the bell with gp at end of line', function()
    913      bell_test(function()
    914        feed('$".gp')
    915      end)
    916 
    917      -- Even if the last character is a multibyte character.
    918      reset()
    919      fn.setline(1, 'helloม')
    920      bell_test(function()
    921        feed('$".gp')
    922      end)
    923    end)
    924 
    925    it('should not ring the bell with gp and end of file', function()
    926      fn.setpos('.', { 0, 2, 1, 0 })
    927      bell_test(function()
    928        feed('$vl".gp')
    929      end)
    930    end)
    931 
    932    it('should ring the bell when deleting if not appropriate', function()
    933      t.skip(t.is_os('bsd'), 'crashes on freebsd')
    934 
    935      command('goto 2')
    936      feed('i<bs><esc>')
    937      expect([[
    938      ine of words 1
    939      Line of words 2]])
    940      bell_test(function()
    941        feed('".P')
    942      end, true)
    943    end)
    944 
    945    it('should restore cursor position after undo of ".p', function()
    946      local origpos = fn.getcurpos()
    947      feed('".pu')
    948      eq(origpos, fn.getcurpos())
    949    end)
    950 
    951    it("should be unaffected by 'autoindent' with V\".2p", function()
    952      command('set autoindent')
    953      feed('i test_string.<esc>u')
    954      feed('V".2p')
    955      expect([[
    956       test_string.
    957       test_string.
    958      Line of words 2]])
    959    end)
    960  end)
    961 end)