" Tests for register operations

" This test must be executed first to check for empty and unset registers.
func Test_aaa_empty_reg_test()
  call assert_fails('normal @@', 'E748:')
  call assert_fails('normal @%', 'E354:')
  call assert_fails('normal @#', 'E354:')
  call assert_fails('normal @!', 'E354:')
  call assert_fails('normal @:', 'E30:')
  call assert_fails('normal @.', 'E29:')
  call assert_fails('put /', 'E35:')
  call assert_fails('put .', 'E29:')
endfunc

func Test_yank_shows_register()
    enew
    set report=0
    call setline(1, ['foo', 'bar'])
    " Line-wise
    exe 'norm! yy'
    call assert_equal('1 line yanked', v:statusmsg)
    exe 'norm! "zyy'
    call assert_equal('1 line yanked into "z', v:statusmsg)
    exe 'norm! yj'
    call assert_equal('2 lines yanked', v:statusmsg)
    exe 'norm! "zyj'
    call assert_equal('2 lines yanked into "z', v:statusmsg)

    " Block-wise
    exe "norm! \<C-V>y"
    call assert_equal('block of 1 line yanked', v:statusmsg)
    exe "norm! \<C-V>\"zy"
    call assert_equal('block of 1 line yanked into "z', v:statusmsg)
    exe "norm! \<C-V>jy"
    call assert_equal('block of 2 lines yanked', v:statusmsg)
    exe "norm! \<C-V>j\"zy"
    call assert_equal('block of 2 lines yanked into "z', v:statusmsg)

    bwipe!
endfunc

func Test_display_registers()
    e file1
    e file2
    call setline(1, ['foo', 'bar'])
    /bar
    exe 'norm! y2l"axx'
    call feedkeys("i\<C-R>=2*4\n\<esc>")
    call feedkeys(":ls\n", 'xt')

    " these commands work in the sandbox
    let a = execute('sandbox display')
    " When X11 connection is not available, there is a warning W23
    " filter this out (we could also run the :display comamand twice)
    let a = substitute(a, 'W23.*0\n', '', '')
    let b = execute('sandbox registers')

    call assert_equal(a, b)
    call assert_match('^\nType Name Content\n'
          \ .         '  c  ""   a\n'
          \ .         '  c  "0   ba\n'
          \ .         '  c  "a   b\n'
          \ .         '.*'
          \ .         '  c  "-   a\n'
          \ .         '.*'
          \ .         '  c  ":   ls\n'
          \ .         '  c  "%   file2\n'
          \ .         '  c  "#   file1\n'
          \ .         '  c  "/   bar\n'
          \ .         '  c  "=   2\*4', a)

    let a = execute('registers a')
    call assert_match('^\nType Name Content\n'
          \ .         '  c  "a   b', a)

    let a = execute('registers :')
    call assert_match('^\nType Name Content\n'
          \ .         '  c  ":   ls', a)

    bwipe!
endfunc

func Test_register_one()
  " delete a line goes into register one
  new
  call setline(1, "one")
  normal dd
  call assert_equal("one\n", @1)

  " delete a word does not change register one, does change "-
  call setline(1, "two")
  normal de
  call assert_equal("one\n", @1)
  call assert_equal("two", @-)

  " delete a word with a register does not change register one
  call setline(1, "three")
  normal "ade
  call assert_equal("three", @a)
  call assert_equal("one\n", @1)

  " delete a word with register DOES change register one with one of a list of
  " operators
  " %
  call setline(1, ["(12)3"])
  normal "ad%
  call assert_equal("(12)", @a)
  call assert_equal("(12)", @1)

  " (
  call setline(1, ["first second"])
  normal $"ad(
  call assert_equal("first secon", @a)
  call assert_equal("first secon", @1)

  " )
  call setline(1, ["First Second."])
  normal gg0"ad)
  call assert_equal("First Second.", @a)
  call assert_equal("First Second.", @1)

  " `
  call setline(1, ["start here."])
  normal gg0fhmx0"ad`x
  call assert_equal("start ", @a)
  call assert_equal("start ", @1)

  " /
  call setline(1, ["searchX"])
  exe "normal gg0\"ad/X\<CR>"
  call assert_equal("search", @a)
  call assert_equal("search", @1)

  " ?
  call setline(1, ["Ysearch"])
  exe "normal gg$\"ad?Y\<CR>"
  call assert_equal("Ysearc", @a)
  call assert_equal("Ysearc", @1)

  " n
  call setline(1, ["Ynext"])
  normal gg$"adn
  call assert_equal("Ynex", @a)
  call assert_equal("Ynex", @1)

  " N
  call setline(1, ["prevY"])
  normal gg0"adN
  call assert_equal("prev", @a)
  call assert_equal("prev", @1)

  " }
  call setline(1, ["one", ""])
  normal gg0"ad}
  call assert_equal("one\n", @a)
  call assert_equal("one\n", @1)

  " {
  call setline(1, ["", "two"])
  normal 2G$"ad{
  call assert_equal("\ntw", @a)
  call assert_equal("\ntw", @1)

  bwipe!
endfunc

func Test_recording_status_in_ex_line()
  set noruler
  norm qx
  redraw!
  call assert_equal('recording @x', Screenline(&lines))
  set shortmess=q
  redraw!
  call assert_equal('recording', Screenline(&lines))
  set shortmess&
  norm q
  redraw!
  call assert_equal('', Screenline(&lines))
  set ruler
  norm qx
  redraw!
  call assert_match('recording @x\s*0,0-1\s*All', Screenline(&lines))
  set shortmess=q
  redraw!
  call assert_match('recording\s*0,0-1\s*All', Screenline(&lines))
  set shortmess&
  norm q
  redraw!
  call assert_match('\s*0,0-1\s*All', Screenline(&lines))
endfunc

" Check that replaying a typed sequence does not use an Esc and following
" characters as an escape sequence.
func Test_recording_esc_sequence()
  new
  try
    let save_F2 = &t_F2
  catch
  endtry
  let t_F2 = "\<Esc>OQ"
  call feedkeys("qqiTest\<Esc>", "xt")
  call feedkeys("OQuirk\<Esc>q", "xt")
  call feedkeys("Go\<Esc>@q", "xt")
  call assert_equal(['Quirk', 'Test', 'Quirk', 'Test'], getline(1, 4))
  bwipe!
  if exists('save_F2')
    let &t_F2 = save_F2
  else
    set t_F2=
  endif
endfunc

func Test_recording_with_select_mode()
  new
  call feedkeys("qacc12345\<Esc>gH98765\<Esc>q", "tx")
  call assert_equal("98765", getline(1))
  call assert_equal("cc12345\<Esc>gH98765\<Esc>", @a)
  call setline(1, 'asdf')
  normal! @a
  call assert_equal("98765", getline(1))
  bwipe!
endfunc

func Run_test_recording_with_select_mode_utf8()
  new

  " Test with different text lengths: 5, 7, 9, 11, 13, 15, to check that
  " a character isn't split between two buffer blocks.
  for s in ['12345', '口=口', '口口口', '口=口=口', '口口=口口', '口口口口口']
    " 0x80 is K_SPECIAL
    " 0x9B is CSI
    " 哦: 0xE5 0x93 0xA6
    " 洛: 0xE6 0xB4 0x9B
    " 固: 0xE5 0x9B 0xBA
    " 四: 0xE5 0x9B 0x9B
    " 最: 0xE6 0x9C 0x80
    " 倒: 0xE5 0x80 0x92
    " 倀: 0xE5 0x80 0x80
    for c in ['哦', '洛', '固', '四', '最', '倒', '倀']
      call setline(1, 'asdf')
      call feedkeys($"qacc{s}\<Esc>gH{c}\<Esc>q", "tx")
      call assert_equal(c, getline(1))
      call assert_equal($"cc{s}\<Esc>gH{c}\<Esc>", @a)
      call setline(1, 'asdf')
      normal! @a
      call assert_equal(c, getline(1))

      " Test with Shift modifier.
      let shift_c = eval($'"\<S-{c}>"')
      call setline(1, 'asdf')
      call feedkeys($"qacc{s}\<Esc>gH{shift_c}\<Esc>q", "tx")
      call assert_equal(c, getline(1))
      call assert_equal($"cc{s}\<Esc>gH{shift_c}\<Esc>", @a)
      call setline(1, 'asdf')
      normal! @a
      call assert_equal(c, getline(1))
    endfor
  endfor

  bwipe!
endfunc

func Test_recording_with_select_mode_utf8()
  call Run_test_recording_with_select_mode_utf8()
endfunc

" This must be done as one of the last tests, because it starts the GUI, which
" cannot be undone.
func Test_zz_recording_with_select_mode_utf8_gui()
  CheckCanRunGui

  gui -f
  call Run_test_recording_with_select_mode_utf8()
endfunc

func Test_recording_append_utf8()
  new

  let keys = "cc哦洛固四最倒倀\<Esc>0"
  call feedkeys($'qr{keys}q', 'xt')
  call assert_equal(keys, @r)

  let morekeys = "A…foobar\<Esc>0"
  call feedkeys($'qR{morekeys}q', 'xt')
  call assert_equal(keys .. morekeys, @r)

  bwipe!
endfunc

func Test_recording_with_super_mod()
  if "\<D-j>"[-1:] == '>'
    throw 'Skipped: <D- modifier not supported'
  endif

  nnoremap <D-j> <Ignore>
  let s = repeat("\<D-j>", 1000)
  " This used to crash MNV
  call feedkeys($'qr{s}q', 'tx')
  call assert_equal(s, @r)
  nunmap <D-j>
endfunc

" Test for executing the last used register (@)
func Test_last_used_exec_reg()
  " Test for the @: command
  let a = ''
  call feedkeys(":let a ..= 'MNV'\<CR>", 'xt')
  normal @:
  call assert_equal('MNVMNV', a)

  " Test for the @= command
  let x = ''
  let a = ":let x ..= 'MNV'\<CR>"
  exe "normal @=a\<CR>"
  normal @@
  call assert_equal('MNVMNV', x)

  " Test for the @. command
  let a = ''
  call feedkeys("i:let a ..= 'Edit'\<CR>", 'xt')
  normal @.
  normal @@
  call assert_equal('EditEdit', a)

  " Test for repeating the last command-line in visual mode
  call append(0, 'register')
  normal gg
  let @r = ''
  call feedkeys("v:yank R\<CR>", 'xt')
  call feedkeys("v@:", 'xt')
  call assert_equal("\nregister\nregister\n", @r)

  enew!
endfunc

func Test_get_register()
  enew
  edit Xfile1
  edit Xfile2
  call assert_equal('Xfile2', getreg('%'))
  call assert_equal('Xfile1', getreg('#'))

  call feedkeys("iTwo\<Esc>", 'xt')
  call assert_equal('Two', getreg('.'))
  call assert_equal('', getreg('_'))
  call assert_beeps('normal ":yy')
  call assert_beeps('normal "%yy')
  call assert_beeps('normal ".yy')

  call assert_equal('', getreg("\<C-F>"))
  call assert_equal('', getreg("\<C-W>"))
  call assert_equal('', getreg("\<C-L>"))
  " Change the last used register to '"' for the next test
  normal! ""yy
  let @" = 'happy'
  call assert_equal('happy', getreg())
  call assert_equal('happy', getreg(''))

  call assert_equal('', getregtype('!'))
  call assert_fails('echo getregtype([])', 'E730:')
  call assert_equal('v', getregtype())
  call assert_equal('v', getregtype(''))

  " Test for inserting an invalid register content
  call assert_beeps('exe "normal i\<C-R>!"')

  " Test for inserting a register with multiple lines
  call deletebufline('', 1, '$')
  call setreg('r', ['a', 'b'])
  exe "normal i\<C-R>r"
  call assert_equal(['a', 'b', ''], getline(1, '$'))

  " Test for inserting a multi-line register in the command line
  call feedkeys(":\<C-R>r\<Esc>", 'xt')
  call assert_equal("a\rb\r", histget(':', -1))

  call assert_fails('let r = getreg("=", [])', 'E745:')
  call assert_fails('let r = getreg("=", 1, [])', 'E745:')
  enew!

  " Using a register in operator-pending mode should fail
  call assert_beeps('norm! c"')
endfunc

func Test_set_register()
  call assert_fails("call setreg('#', 200)", 'E86:')
  call assert_fails("call setreg('a', test_unknown())", 'E908:')

  edit Xfile_alt_1
  let b1 = bufnr('')
  edit Xfile_alt_2
  let b2 = bufnr('')
  edit Xfile_alt_3
  let b3 = bufnr('')
  call setreg('#', 'alt_1')
  call assert_equal('Xfile_alt_1', getreg('#'))
  call setreg('#', b2)
  call assert_equal('Xfile_alt_2', getreg('#'))

  let ab = 'regwrite'
  call setreg('=', '')
  call setreg('=', 'a', 'a')
  call setreg('=', 'b', 'a')
  call assert_equal('regwrite', getreg('='))

  " Test for setting a list of lines to special registers
  call setreg('/', [])
  call assert_equal('', @/)
  call setreg('=', [])
  call assert_equal('', @=)
  call assert_fails("call setreg('/', ['a', 'b'])", 'E883:')
  call assert_fails("call setreg('=', ['a', 'b'])", 'E883:')
  call assert_equal(0, setreg('_', ['a', 'b']))

  " Test for recording to a invalid register
  call assert_beeps('normal q$')

  " Appending to a register when recording
  call append(0, "text for clipboard test")
  normal gg
  call feedkeys('qrllq', 'xt')
  call feedkeys('qRhhq', 'xt')
  call assert_equal('llhh', getreg('r'))

  " Appending a list of characters to a register from different lines
  let @r = ''
  call append(0, ['abcdef', '123456'])
  normal gg"ry3l
  call cursor(2, 4)
  normal "Ry3l
  call assert_equal('abc456', @r)

  " Test for gP with multiple lines selected using characterwise motion
  %delete
  call append(0, ['mnv editor', 'mnv editor'])
  let @r = ''
  exe "normal ggwy/mnv /e\<CR>gP"
  call assert_equal(['mnv editor', 'mnv editor', 'mnv editor'], getline(1, 3))

  " Test for gP with . register
  %delete
  normal iabc
  normal ".gp
  call assert_equal('abcabc', getline(1))
  normal 0".gP
  call assert_equal('abcabcabc', getline(1))

  let @"=''
  call setreg('', '1')
  call assert_equal('1', @")
  call setreg('@', '2')
  call assert_equal('2', @")

  enew!
endfunc

" Test for blockwise register width calculations
func Test_set_register_blockwise_width()
  " Test for regular calculations and overriding the width
  call setreg('a', "12\n1234\n123", 'b')
  call assert_equal("\<c-v>4", getreginfo('a').regtype)
  call setreg('a', "12\n1234\n123", 'b1')
  call assert_equal("\<c-v>1", getreginfo('a').regtype)
  call setreg('a', "12\n1234\n123", 'b6')
  call assert_equal("\<c-v>6", getreginfo('a').regtype)

  " Test for Unicode parsing
  call setreg('a', "z😅😅z\n12345", 'b')
  call assert_equal("\<c-v>6", getreginfo('a').regtype)
  call setreg('a', ["z😅😅z", "12345"], 'b')
  call assert_equal("\<c-v>6", getreginfo('a').regtype)
endfunc

" Test for clipboard registers (* and +)
func Test_clipboard_regs()
  CheckNotGui
  CheckFeature clipboard_working

  new
  call append(0, "text for clipboard test")
  normal gg"*yiw
  call assert_equal('text', getreg('*'))
  normal gg2w"+yiw
  call assert_equal('clipboard', getreg('+'))

  " Test for replacing the clipboard register contents
  set clipboard=unnamed
  let @* = 'food'
  normal ggviw"*p
  call assert_equal('text', getreg('*'))
  call assert_equal('food for clipboard test', getline(1))
  normal ggviw"*p
  call assert_equal('food', getreg('*'))
  call assert_equal('text for clipboard test', getline(1))

  " Test for replacing the selection register contents
  set clipboard=unnamedplus
  let @+ = 'food'
  normal ggviw"+p
  call assert_equal('text', getreg('+'))
  call assert_equal('food for clipboard test', getline(1))
  normal ggviw"+p
  call assert_equal('food', getreg('+'))
  call assert_equal('text for clipboard test', getline(1))

  " Test for auto copying visually selected text to clipboard register
  call setline(1, "text for clipboard test")
  let @* = ''
  set clipboard=autoselect
  normal ggwwviwy
  call assert_equal('clipboard', @*)

  " Test for auto copying visually selected text to selection register
  let @+ = ''
  set clipboard=autoselectplus
  normal ggwviwy
  call assert_equal('for', @+)

  set clipboard&mnv
  bwipe!
endfunc

" When clipboard registers (* and +) are supported, check that they are
" independent for direct writes.
func Test_clipboard_regs_separate()
  CheckNotGui
  CheckFeature clipboard_working
  CheckTwoClipboards

  new
  call setline(1, ['foo'])

  " Check that various clipboard options do not result in one register
  " affecting the other.
  for clip in ['', 'autoselect', 'unnamed', 'unnamedplus']
    :execute 'set clipboard=' . clip
    " check that * does not affect +
    let @* = 'xxx'
    let @+ = 'xxx'
    :normal "*yw
    call assert_equal('foo', getreg('*'))
    call assert_equal('xxx', getreg('+'))

    " check that + does not affect *
    let @* = 'xxx'
    :normal "+yw
    call assert_equal('foo', getreg('+'))
    call assert_equal('xxx', getreg('*'))
  endfor

  set clipboard&mnv
  bwipe!
endfunc

" Test unnamed for both clipboard registers (* and +)
func Test_clipboard_regs_both_unnamed()
  CheckNotGui
  CheckFeature clipboard_working
  CheckTwoClipboards

  let @* = 'xxx'
  let @+ = 'xxx'

  new

  set clipboard=unnamed,unnamedplus
  call setline(1, ['foo', 'bar'])

  " op_yank copies to both
  :1
  :normal yw
  call assert_equal('foo', getreg('*'))
  call assert_equal('foo', getreg('+'))

  " op_delete only copies to '+'
  :2
  :normal dw
  call assert_equal('foo', getreg('*'))
  call assert_equal('bar', getreg('+'))

  set clipboard&mnv
  bwipe!
endfunc

" Test for restarting the current mode (insert or virtual replace) after
" executing the contents of a register
func Test_put_reg_restart_mode()
  new
  call append(0, 'editor')
  normal gg
  let @r = "imnv \<Esc>"
  call feedkeys("i\<C-O>@r\<C-R>=mode()\<CR>", 'xt')
  call assert_equal('mnvi editor', getline(1))

  call setline(1, 'editor')
  normal gg
  call feedkeys("gR\<C-O>@r\<C-R>=mode()\<CR>", 'xt')
  call assert_equal('mnvReditor', getline(1))

  bwipe!
endfunc

" Test for executing a register using :@ command
func Test_execute_register()
  call setreg('r', [])
  call assert_beeps('@r')
  let i = 1
  let @q = 'let i+= 1'
  @q
  @
  call assert_equal(3, i)

  " try to execute expression register and use a backspace to cancel it
  new
  call feedkeys("@=\<BS>ax\<CR>y", 'xt')
  call assert_equal(['x', 'y'], getline(1, '$'))
  bw!

  " cannot execute a register in operator pending mode
  call assert_beeps('normal! c@r')
endfunc

" Test for getting register info
func Test_get_reginfo()
  enew
  call setline(1, ['foo', 'bar'])

  exe 'norm! "zyy'
  let info = getreginfo('"')
  call assert_equal('z', info.points_to)
  call setreg('y', 'baz')
  call assert_equal('z', getreginfo('').points_to)
  call setreg('y', { 'isunnamed': v:true })
  call assert_equal('y', getreginfo('"').points_to)

  exe '$put'
  call assert_equal(getreg('y'), getline(3))
  call setreg('', 'qux')
  call assert_equal('0', getreginfo('').points_to)
  call setreg('x', 'quux')
  call assert_equal('0', getreginfo('').points_to)

  let info = getreginfo('')
  call assert_equal(getreg('', 1, 1), info.regcontents)
  call assert_equal(getregtype(''), info.regtype)

  exe "norm! 0\<c-v>e" .. '"zy'
  let info = getreginfo('z')
  call assert_equal(getreg('z', 1, 1), info.regcontents)
  call assert_equal(getregtype('z'), info.regtype)
  call assert_equal(1, +info.isunnamed)

  let info = getreginfo('"')
  call assert_equal('z', info.points_to)

  let @a="a1b2"
  nnoremap <F2> <Cmd>let g:RegInfo = getreginfo()<CR>
  exe "normal \"a\<F2>"
  call assert_equal({'regcontents': ['a1b2'], 'isunnamed': v:false,
        \ 'regtype': 'v'}, g:RegInfo)
  nunmap <F2>
  unlet g:RegInfo

  " The type of "isunnamed" was VAR_SPECIAL but should be VAR_BOOL.  Can only
  " be noticed when using json_encod().
  call setreg('a', 'foo')
  let reginfo = getreginfo('a')
  let expected = #{regcontents: ['foo'], isunnamed: v:false, regtype: 'v'}
  call assert_equal(json_encode(expected), json_encode(reginfo))

  bwipe!
endfunc

" Test for restoring register with dict from getreginfo
func Test_set_register_dict()
  enew!

  call setreg('"', #{ regcontents: ['one', 'two'],
        \ regtype: 'V', points_to: 'z' })
  call assert_equal(['one', 'two'], getreg('"', 1, 1))
  let info = getreginfo('"')
  call assert_equal('z', info.points_to)
  call assert_equal('V', info.regtype)
  call assert_equal(1, +getreginfo('z').isunnamed)

  call setreg('x', #{ regcontents: ['three', 'four'],
        \ regtype: 'v', isunnamed: v:true })
  call assert_equal(['three', 'four'], getreg('"', 1, 1))
  let info = getreginfo('"')
  call assert_equal('x', info.points_to)
  call assert_equal('v', info.regtype)
  call assert_equal(1, +getreginfo('x').isunnamed)

  call setreg('y', #{ regcontents: 'five',
        \ regtype: "\<c-v>", isunnamed: v:false })
  call assert_equal("\<c-v>4", getreginfo('y').regtype)
  call assert_equal(0, +getreginfo('y').isunnamed)
  call assert_equal(['three', 'four'], getreg('"', 1, 1))
  call assert_equal('x', getreginfo('"').points_to)

  call setreg('"', #{ regcontents: 'six' })
  call assert_equal('0', getreginfo('"').points_to)
  call assert_equal(1, +getreginfo('0').isunnamed)
  call assert_equal(['six'], getreginfo('0').regcontents)
  call assert_equal(['six'], getreginfo('"').regcontents)

  let @x = 'one'
  call setreg('x', {})
  call assert_equal(1, len(split(execute('reg x'), '\n')))

  call assert_fails("call setreg('0', #{regtype: 'V'}, 'v')", 'E118:')
  call assert_fails("call setreg('0', #{regtype: 'X'})", 'E475:')
  call assert_fails("call setreg('0', #{regtype: 'vy'})", 'E475:')

  bwipe!
endfunc

func Test_v_register()
  enew
  call setline(1, 'nothing')

  func s:Put()
    let s:register = v:register
    exec 'normal! "' .. v:register .. 'P'
  endfunc
  nnoremap <buffer> <plug>(test) :<c-u>call s:Put()<cr>
  xnoremap <buffer> <plug>(test) :<c-u>call s:Put()<cr>
  nmap <buffer> S <plug>(test)
  xmap <buffer> S <plug>(test)

  let @z = "testz\n"
  let @" = "test@\n"

  let s:register = ''
  call feedkeys('"_ddS', 'mx')
  call assert_equal('"', s:register)        " fails before 8.2.0929
  call assert_equal('test@', getline('.'))  " fails before 8.2.0929

  let s:register = ''
  call feedkeys('V"_dS', 'mx')
  call assert_equal('"', s:register)
  call assert_equal('test@', getline('.'))

  let s:register = ''
  call feedkeys('"zS', 'mx')
  call assert_equal('z', s:register)
  call assert_equal('testz', getline('.'))

  let s:register = ''
  call feedkeys('"zSS', 'mx')
  call assert_equal('"', s:register)
  call assert_equal('test@', getline('.'))

  let s:register = ''
  call feedkeys("\"z\<Ignore>S", 'mx')
  call assert_equal('z', s:register)
  call assert_equal('testz', getline('.'))

  let s:register = ''
  call feedkeys('"_S', 'mx')
  call assert_equal('_', s:register)

  let s:register = ''
  call feedkeys('V"zS', 'mx')
  call assert_equal('z', s:register)
  call assert_equal('testz', getline('.'))

  let s:register = ''
  call feedkeys('V"zSS', 'mx')
  call assert_equal('"', s:register)
  call assert_equal('test@', getline('.'))

  let s:register = ''
  call feedkeys("V\"z\<Ignore>S", 'mx')
  call assert_equal('z', s:register)
  call assert_equal('testz', getline('.'))

  let s:register = ''
  call feedkeys('V"_S', 'mx')
  call assert_equal('_', s:register)

  let s:register = ''
  normal "_ddS
  call assert_equal('"', s:register)        " fails before 8.2.0929
  call assert_equal('test@', getline('.'))  " fails before 8.2.0929

  let s:register = ''
  normal V"_dS
  call assert_equal('"', s:register)
  call assert_equal('test@', getline('.'))

  let s:register = ''
  execute 'normal "z:call' "s:Put()\n"
  call assert_equal('z', s:register)
  call assert_equal('testz', getline('.'))

  " Test operator and omap
  let @b = 'testb'
  func s:OpFunc(...)
    let s:register2 = v:register
  endfunc
  set opfunc=s:OpFunc

  normal "bg@l
  normal S
  call assert_equal('"', s:register)        " fails before 8.2.0929
  call assert_equal('b', s:register2)

  func s:Motion()
    let s:register1 = v:register
    normal! l
  endfunc
  onoremap <buffer> Q :<c-u>call s:Motion()<cr>

  normal "bg@Q
  normal S
  call assert_equal('"', s:register)
  call assert_equal('b', s:register1)
  call assert_equal('"', s:register2)

  set opfunc&
  bwipe!
endfunc

" Test for executing the contents of a register as an Ex command with line
" continuation.
func Test_execute_reg_as_ex_cmd()
  " Line continuation with just two lines
  let code =<< trim END
    let l = [
      \ 1]
  END
  let @r = code->join("\n")
  let l = []
  @r
  call assert_equal([1], l)

  " Line continuation with more than two lines
  let code =<< trim END
    let l = [
      \ 1,
      \ 2,
      \ 3]
  END
  let @r = code->join("\n")
  let l = []
  @r
  call assert_equal([1, 2, 3], l)

  " use comments interspersed with code
  let code =<< trim END
    let l = [
      "\ one
      \ 1,
      "\ two
      \ 2,
      "\ three
      \ 3]
  END
  let @r = code->join("\n")
  let l = []
  @r
  call assert_equal([1, 2, 3], l)

  " use line continuation in the middle
  let code =<< trim END
    let a = "one"
    let l = [
      \ 1,
      \ 2]
    let b = "two"
  END
  let @r = code->join("\n")
  let l = []
  @r
  call assert_equal([1, 2], l)
  call assert_equal("one", a)
  call assert_equal("two", b)

  " only one line with a \
  let @r = "\\let l = 1"
  call assert_fails('@r', 'E10:')

  " only one line with a "\
  let @r = '   "\ let i = 1'
  @r
  call assert_false(exists('i'))

  " first line also begins with a \
  let @r = "\\let l = [\n\\ 1]"
  call assert_fails('@r', 'E10:')

  " Test with a large number of lines
  let @r = "let str = \n"
  let @r ..= repeat("  \\ 'abcdefghijklmnopqrstuvwxyz' ..\n", 312)
  let @r ..= '  \ ""'
  @r
  call assert_equal(repeat('abcdefghijklmnopqrstuvwxyz', 312), str)
endfunc

" Test for clipboard registers with ASCII NUL
func Test_clipboard_nul()
  CheckFeature clipboard_working
  new

  " Test for putting ASCII NUL into the clipboard
  set clipboard=unnamed
  call append(0, "\ntest")
  normal ggyyp
  call assert_equal("^@test^@", strtrans(getreg('*')))
  call assert_equal(getline(1), getline(2))
  let b = split(execute(":reg *"), "\n")
  call assert_match('"\*\s*\^@test\^J',b[1])

  set clipboard&mnv
  bwipe!
endfunc

func Test_ve_blockpaste()
  new
  set ve=all
  0put =['QWERTZ','ASDFGH']
  call cursor(1,1)
  exe ":norm! \<C-V>3ljdP"
  call assert_equal(1, col('.'))
  call assert_equal(['QWERTZ', 'ASDFGH'], getline(1, 2))
  call cursor(1,1)
  exe ":norm! \<C-V>3ljd"
  call cursor(1,1)
  norm! $3lP
  call assert_equal(5, col('.'))
  call assert_equal(['TZ  QWER', 'GH  ASDF'], getline(1, 2))
  set ve&mnv
  bwipe!
endfunc

func Test_insert_small_delete()
  new
  call setline(1, ['foo foobar bar'])
  call cursor(1,1)
  exe ":norm! ciw'\<C-R>-'"
  call assert_equal("'foo' foobar bar", getline(1))
  exe ":norm! w.w."
  call assert_equal("'foo' 'foobar' 'bar'", getline(1))
  bwipe!
endfunc

" Record in insert mode using CTRL-O
func Test_record_in_insert_mode()
  new
  let @r = ''
  call setline(1, ['foo'])
  call feedkeys("i\<C-O>qrbaz\<C-O>q", 'xt')
  call assert_equal('baz', @r)
  bwipe!
endfunc

func Test_record_in_select_mode()
  new
  call setline(1, 'text')
  sil norm q00
  sil norm q
  call assert_equal('0ext', getline(1))

  %delete
  let @r = ''
  call setline(1, ['abc', 'abc', 'abc'])
  smap <F2> <Right><Right>,
  call feedkeys("qrgh\<F2>Dk\<Esc>q", 'xt')
  call assert_equal("gh\<F2>Dk\<Esc>", @r)
  norm j0@rj0@@
  call assert_equal([',Dk', ',Dk', ',Dk'], getline(1, 3))
  sunmap <F2>

  bwipe!
endfunc

" A mapping that ends recording should be removed from the recorded register.
func Test_end_record_using_mapping()
  new
  call setline(1, 'aaa')
  nnoremap s q
  call feedkeys('safas', 'tx')
  call assert_equal('fa', @a)
  nunmap s

  nnoremap xx q
  call feedkeys('0xxafaxx', 'tx')
  call assert_equal('fa', @a)
  nunmap xx

  nnoremap xsx q
  call feedkeys('0qafaxsx', 'tx')
  call assert_equal('fa', @a)
  nunmap xsx

  bwipe!
endfunc

" Starting a new recording should work immediately after replaying a recording
" that ends with a <Nop> mapping or a character search.
func Test_end_reg_executing()
  new
  nnoremap s <Nop>
  let @a = 's'
  call feedkeys("@aqaq\<Esc>", 'tx')
  call assert_equal('', @a)
  call assert_equal('', getline(1))

  call setline(1, 'aaa')
  nnoremap s qa
  let @a = 'fa'
  call feedkeys("@asq\<Esc>", 'tx')
  call assert_equal('', @a)
  call assert_equal('aaa', getline(1))

  nunmap s
  bwipe!
endfunc

func Test_reg_executing_in_range_normal()
  new
  set showcmd
  call setline(1, range(10))
  let g:log = []
  nnoremap s <Cmd>let g:log += [reg_executing()]<CR>
  let @r = 's'

  %normal @r
  call assert_equal(repeat(['r'], 10), g:log)

  nunmap s
  unlet g:log
  set showcmd&
  bwipe!
endfunc

" An operator-pending mode mapping shouldn't be applied to keys typed in
" Insert mode immediately after a character search when replaying.
func Test_replay_charsearch_omap()
  CheckFeature timers

  new
  call setline(1, 'foo[blah]')
  onoremap , k
  call timer_start(10, {-> feedkeys(",bar\<Esc>q", 't')})
  call feedkeys('qrct[', 'xt!')
  call assert_equal(',bar[blah]', getline(1))
  call assert_equal("ct[\<Ignore>,bar\<Esc>", @r)
  call assert_equal('ct[<Ignore>,bar<Esc>', keytrans(@r))
  undo
  call assert_equal('foo[blah]', getline(1))
  call feedkeys('@r', 'xt!')
  call assert_equal(',bar[blah]', getline(1))

  ounmap ,
  bwipe!
endfunc

" This was causing a crash because y_append was ending up being NULL
func Test_zero_y_append()
  " Run in a separate MNV instance because changing 'encoding' may cause
  " trouble for later tests.
  let lines =<< trim END
      d
      silent ?n
      next <sfile>
      so
      sil! norm 0VPSP
      set enc=latin1
       
  END
  call writefile(lines, 'XTest_zero_y_append', 'D')
  call RunMNV([], [], '-u NONE -i NONE -e -s -S XTest_zero_y_append -c qa\!')
endfunc

" Make sure that y_append is correctly reset
" and the previous register is working as expected
func Test_register_y_append_reset()
  new
  call setline(1, ['1',
    \ '2 ----------------------------------------------------',
    \ '3',
    \ '4',
    \ '5 ----------------------------------------------------',
    \ '6',
    \ '7',
    \ '8 ----------------------------------------------------',
    \ '9',
    \ '10 aaaaaaa 4.',
    \ '11 Game Dbl-Figures Leaders:',
    \ '12 Player Pts FG% 3P% FT% RB AS BL ST TO PF EFF',
    \ '13 bbbbbbbbb 12 (50 /0 /67 )/ 7/ 3/ 0/ 2/ 3/ 4/+15',
    \ '14 cccccc 12 (57 /67 /100)/ 2/ 1/ 1/ 0/ 1/ 3/+12',
    \ '15 ddddddd 10 (63 /0 /0 )/ 1/ 3/ 0/ 3/ 5/ 3/ +9',
    \ '16 4 5-15 0-3 2-2 5-12 1-1 3-4 33.3 0.0 100 41.7 100 75 12 14',
    \ '17 F 23-55 2-10 9-11 23-52 3-13 26-29 41.8 20 81.8 44.2 23.1 89.7 57 75',
    \ '18 4 3 6 3 2 3 3 4 3 3 7 3 1 4 6 -1 -1 +2 -1 -2',
    \ '19 F 13 19 5 10 4 17 22 9 14 32 13 4 20 17 -1 -13 -4 -3 -3 +5'])
  11
  exe "norm! \"a5dd"
  norm! j
  exe "norm! \"bY"
  norm! 2j
  exe "norm! \"BY"
  norm! 4k
  norm! 5dd
  norm! 3k
  " The next put should put the content of the unnamed register, not of
  " register b!
  norm! p
  call assert_equal(['1',
    \ '2 ----------------------------------------------------',
    \ '3',
    \ '4',
    \ '5 ----------------------------------------------------',
    \ '6',
    \ '10 aaaaaaa 4.',
    \ '16 4 5-15 0-3 2-2 5-12 1-1 3-4 33.3 0.0 100 41.7 100 75 12 14',
    \ '17 F 23-55 2-10 9-11 23-52 3-13 26-29 41.8 20 81.8 44.2 23.1 89.7 57 75',
    \ '18 4 3 6 3 2 3 3 4 3 3 7 3 1 4 6 -1 -1 +2 -1 -2',
    \ '19 F 13 19 5 10 4 17 22 9 14 32 13 4 20 17 -1 -13 -4 -3 -3 +5',
    \ '7',
    \ '8 ----------------------------------------------------',
    \ '9'], getline(1,'$'))
  bwipe!
endfunc

func Test_insert_small_delete_replace_mode()
  new
  call setline(1, ['foo', 'bar', 'foobar',  'bar'])
  let @- = 'foo'
  call cursor(2, 1)
  exe ":norm! R\<C-R>-\<C-R>-"
  call assert_equal('foofoo', getline(2))
  call cursor(3, 1)
  norm! D
  call assert_equal(['foo', 'foofoo', '',  'bar'], getline(1, 4))
  call cursor(4, 2)
  exe ":norm! R\<C-R>-ZZZZ"
  call assert_equal(['foo', 'foofoo', '',  'bfoobarZZZZ'], getline(1, 4))
  call cursor(1, 1)
  let @- = ''
  exe ":norm! R\<C-R>-ZZZ"
  call assert_equal(['ZZZ', 'foofoo', '',  'bfoobarZZZZ'], getline(1, 4))
  let @- = 'βbβ'
  call cursor(4, 1)
  exe ":norm! R\<C-R>-"
  call assert_equal(['ZZZ', 'foofoo', '',  'βbβobarZZZZ'], getline(1, 4))
  let @- = 'bβb'
  call cursor(4, 1)
  exe ":norm! R\<C-R>-"
  call assert_equal(['ZZZ', 'foofoo', '',  'bβbobarZZZZ'], getline(1, 4))
  let @- = 'βbβ'
  call cursor(4, 1)
  exe ":norm! R\<C-R>-"
  call assert_equal(['ZZZ', 'foofoo', '',  'βbβobarZZZZ'], getline(1, 4))
  bwipe!
endfunc

" Test for W24 when clipboard support is not available
func Test_clipboard_regs_not_working()
  CheckNotGui
  if !has("clipboard")
    new
    call append(0, "text for clipboard test")
    let mess = execute(':norm "*yiw')
    call assert_match('W24', mess)
    bw!
  endif
endfunc

" Check for W23 with a MNV with clipboard support,
" but when there is no available clipmethod
func Test_clipboard_regs_not_working2()
  CheckNotMac
  CheckRunMNVInTerminal
  CheckFeature clipboard
  set clipmethod=
  " Run in a separate MNV instance because changing 'encoding' may cause
  " trouble for later tests.
  let lines =<< trim END
      set clipmethod=
      call setline(1, 'abcdefg')
      let a=execute(':norm! "+yy')
      call writefile([a], 'Xclipboard_result.txt')
      set clipmethod&
  END
  call writefile(lines, 'XTest_clipboard', 'D')
  let buf = RunMNVInTerminal('-S XTest_clipboard', {})
  call term_sendkeys(buf, "\"+yy")
  call StopMNVInTerminal(buf)
  let result = readfile('Xclipboard_result.txt')
  call assert_match("^\\nW23:", result[0])
  set clipmethod&
endfunc

" This caused use-after-free
func Test_register_redir_display()
  CheckFeature clipboard
  " don't touch the clipboard, so only perform this, when the clipboard is not working
  if has("clipboard_working")
    throw "Skipped: skip touching the clipboard register!"
  endif
  let @"=''
  redir @+>
  disp +"
  redir END
  call assert_equal("\nType Name Content", getreg('+'))
  let a = [getreg('1'), getregtype('1')]
  let @1='register 1'
  redir @+
  disp 1
  redir END
  call assert_equal("register 1", getreg('1'))
  call setreg(1, a[0], a[1])
endfunc

" this caused an illegal memory access and a crash
func Test_register_cursor_column_negative()
  CheckRunMNVInTerminal
  let script =<< trim END
    f XREGISTER
    call setline(1, 'abcdef a')
    call setreg("a", "\n", 'c')
    call cursor(1, 7)
    call feedkeys("i\<C-R>\<C-P>azyx$#\<esc>", 't')
  END
  call writefile(script, 'XRegister123', 'D')
  let buf = RunMNVInTerminal('-S XRegister123', {})
  call term_sendkeys(buf, "\<c-g>")
  call WaitForAssert({-> assert_match('XREGISTER', term_getline(buf, 19))})
  call StopMNVInTerminal(buf)
endfunc

" test '] mark generated by op_yank
func Test_mark_from_yank()
  new
  " double quote object
  call setline(1, 'test "yank"  mark')
  normal! yi"
  call assert_equal([0, 1, 10, 0], getpos("']"))
  normal! ya"
  call assert_equal([0, 1, 13, 0], getpos("']"))
  " single quote object
  call setline(1, 'test ''yank''  mark')
  normal! yi'
  call assert_equal([0, 1, 10, 0], getpos("']"))
  normal! ya'
  call assert_equal([0, 1, 13, 0], getpos("']"))
  " paren object
  call setline(1, 'test (yank)  mark')
  call cursor(1, 9)
  normal! yi(
  call assert_equal([0, 1, 10, 0], getpos("']"))
  call cursor(1, 9)
  normal! ya(
  call assert_equal([0, 1, 11, 0], getpos("']"))
  " brace object
  call setline(1, 'test {yank}  mark')
  call cursor(1, 9)
  normal! yi{
  call assert_equal([0, 1, 10, 0], getpos("']"))
  call cursor(1, 9)
  normal! ya{
  call assert_equal([0, 1, 11, 0], getpos("']"))
  " bracket object
  call setline(1, 'test [yank]  mark')
  call cursor(1, 9)
  normal! yi[
  call assert_equal([0, 1, 10, 0], getpos("']"))
  call cursor(1, 9)
  normal! ya[
  call assert_equal([0, 1, 11, 0], getpos("']"))
  " block object
  call setline(1, 'test <yank>  mark')
  call cursor(1, 9)
  normal! yi<
  call assert_equal([0, 1, 10, 0], getpos("']"))
  call cursor(1, 9)
  normal! ya<
  call assert_equal([0, 1, 11, 0], getpos("']"))
  bw!
endfunc

func Test_insert_small_delete_linewise()
  new
  call setline(1, ['foo'])
  call cursor(1, 1)
  exe ":norm! \"-cc\<C-R>-"
  call assert_equal(['foo', ''], getline(1, '$'))
  bwipe!
endfunc

func Test_writing_readonly_regs()
  call assert_fails('let @. = "foo"', 'E354:')
  call assert_fails('let @% = "foo"', 'E354:')
  call assert_fails('let @: = "foo"', 'E354:')
  call assert_fails('let @~ = "foo"', 'E354:')
endfunc

" mnv: shiftwidth=2 sts=2 expandtab
