" Test for the search command

source util/screendump.vim

func Test_search_cmdline()
  CheckOption incsearch

  " need to disable char_avail,
  " so that expansion of commandline works
  call test_override("char_avail", 1)
  new
  call setline(1, ['  1', '  2 these', '  3 the', '  4 their', '  5 there', '  6 their', '  7 the', '  8 them', '  9 these', ' 10 foobar'])
  " Test 1
  " CTRL-N / CTRL-P skips through the previous search history
  set noincsearch
  :1
  call feedkeys("/foobar\<cr>", 'tx')
  call feedkeys("/the\<cr>", 'tx')
  call assert_equal('the', @/)
  call feedkeys("/thes\<C-P>\<C-P>\<cr>", 'tx')
  call assert_equal('foobar', @/)

  " Test 2
  " Ctrl-G goes from one match to the next
  " until the end of the buffer
  set incsearch nowrapscan
  :1
  " first match
  call feedkeys("/the\<cr>", 'tx')
  call assert_equal('  2 these', getline('.'))
  :1
  " second match
  call feedkeys("/the\<C-G>\<cr>", 'tx')
  call assert_equal('  3 the', getline('.'))
  call assert_equal([0, 0, 0, 0], getpos('"'))
  :1
  " third match
  call feedkeys("/the".repeat("\<C-G>", 2)."\<cr>", 'tx')
  call assert_equal('  4 their', getline('.'))
  :1
  " fourth match
  call feedkeys("/the".repeat("\<C-G>", 3)."\<cr>", 'tx')
  call assert_equal('  5 there', getline('.'))
  :1
  " fifth match
  call feedkeys("/the".repeat("\<C-G>", 4)."\<cr>", 'tx')
  call assert_equal('  6 their', getline('.'))
  :1
  " sixth match
  call feedkeys("/the".repeat("\<C-G>", 5)."\<cr>", 'tx')
  call assert_equal('  7 the', getline('.'))
  :1
  " seventh match
  call feedkeys("/the".repeat("\<C-G>", 6)."\<cr>", 'tx')
  call assert_equal('  8 them', getline('.'))
  :1
  " eighth match
  call feedkeys("/the".repeat("\<C-G>", 7)."\<cr>", 'tx')
  call assert_equal('  9 these', getline('.'))
  :1
  " no further match
  call feedkeys("/the".repeat("\<C-G>", 8)."\<cr>", 'tx')
  call assert_equal('  9 these', getline('.'))
  call assert_equal([0, 0, 0, 0], getpos('"'))

  " Test 3
  " Ctrl-G goes from one match to the next
  " and continues back at the top
  set incsearch wrapscan
  :1
  " first match
  call feedkeys("/the\<cr>", 'tx')
  call assert_equal('  2 these', getline('.'))
  :1
  " second match
  call feedkeys("/the\<C-G>\<cr>", 'tx')
  call assert_equal('  3 the', getline('.'))
  :1
  " third match
  call feedkeys("/the".repeat("\<C-G>", 2)."\<cr>", 'tx')
  call assert_equal('  4 their', getline('.'))
  :1
  " fourth match
  call feedkeys("/the".repeat("\<C-G>", 3)."\<cr>", 'tx')
  call assert_equal('  5 there', getline('.'))
  :1
  " fifth match
  call feedkeys("/the".repeat("\<C-G>", 4)."\<cr>", 'tx')
  call assert_equal('  6 their', getline('.'))
  :1
  " sixth match
  call feedkeys("/the".repeat("\<C-G>", 5)."\<cr>", 'tx')
  call assert_equal('  7 the', getline('.'))
  :1
  " seventh match
  call feedkeys("/the".repeat("\<C-G>", 6)."\<cr>", 'tx')
  call assert_equal('  8 them', getline('.'))
  :1
  " eighth match
  call feedkeys("/the".repeat("\<C-G>", 7)."\<cr>", 'tx')
  call assert_equal('  9 these', getline('.'))
  :1
  " back at first match
  call feedkeys("/the".repeat("\<C-G>", 8)."\<cr>", 'tx')
  call assert_equal('  2 these', getline('.'))

  " Test 4
  " CTRL-T goes to the previous match
  set incsearch nowrapscan
  $
  " first match
  call feedkeys("?the\<cr>", 'tx')
  call assert_equal('  9 these', getline('.'))
  $
  " first match
  call feedkeys("?the\<C-G>\<cr>", 'tx')
  call assert_equal('  9 these', getline('.'))
  $
  " second match
  call feedkeys("?the".repeat("\<C-T>", 1)."\<cr>", 'tx')
  call assert_equal('  8 them', getline('.'))
  $
  " last match
  call feedkeys("?the".repeat("\<C-T>", 7)."\<cr>", 'tx')
  call assert_equal('  2 these', getline('.'))
  $
  " last match
  call feedkeys("?the".repeat("\<C-T>", 8)."\<cr>", 'tx')
  call assert_equal('  2 these', getline('.'))

  " Test 5
  " CTRL-T goes to the previous match
  set incsearch wrapscan
  $
  " first match
  call feedkeys("?the\<cr>", 'tx')
  call assert_equal('  9 these', getline('.'))
  $
  " first match at the top
  call feedkeys("?the\<C-G>\<cr>", 'tx')
  call assert_equal('  2 these', getline('.'))
  $
  " second match
  call feedkeys("?the".repeat("\<C-T>", 1)."\<cr>", 'tx')
  call assert_equal('  8 them', getline('.'))
  $
  " last match
  call feedkeys("?the".repeat("\<C-T>", 7)."\<cr>", 'tx')
  call assert_equal('  2 these', getline('.'))
  $
  " back at the bottom of the buffer
  call feedkeys("?the".repeat("\<C-T>", 8)."\<cr>", 'tx')
  call assert_equal('  9 these', getline('.'))

  " Test 6
  " CTRL-L adds to the search pattern
  set incsearch wrapscan
  1
  " first match
  call feedkeys("/the\<c-l>\<cr>", 'tx')
  call assert_equal('  2 these', getline('.'))
  1
  " go to next match of 'thes'
  call feedkeys("/the\<c-l>\<C-G>\<cr>", 'tx')
  call assert_equal('  9 these', getline('.'))
  1
  " wrap around
  call feedkeys("/the\<c-l>\<C-G>\<C-G>\<cr>", 'tx')
  call assert_equal('  2 these', getline('.'))
  1
  " wrap around
  set nowrapscan
  call feedkeys("/the\<c-l>\<C-G>\<C-G>\<cr>", 'tx')
  call assert_equal('  9 these', getline('.'))

  " Test 7
  " <bs> remove from match, but stay at current match
  set incsearch wrapscan
  1
  " first match
  call feedkeys("/thei\<cr>", 'tx')
  call assert_equal('  4 their', getline('.'))
  1
  " delete one char, add another
  call feedkeys("/thei\<bs>s\<cr>", 'tx')
  call assert_equal('  2 these', getline('.'))
  1
  " delete one char, add another,  go to previous match, add one char
  call feedkeys("/thei\<bs>s\<bs>\<C-T>\<c-l>\<cr>", 'tx')
  call assert_equal('  9 these', getline('.'))
  1
  " delete all chars, start from the beginning again
  call feedkeys("/them". repeat("\<bs>",4).'the\>'."\<cr>", 'tx')
  call assert_equal('  3 the', getline('.'))

  " clean up
  call test_override("char_avail", 0)
  bw!
endfunc

func Test_search_cmdline2()
  CheckOption incsearch

  " need to disable char_avail,
  " so that expansion of commandline works
  call test_override("char_avail", 1)
  new
  call setline(1, ['  1', '  2 these', '  3 the theother'])
  " Test 1
  " Ctrl-T goes correctly back and forth
  set incsearch
  1
  " first match
  call feedkeys("/the\<cr>", 'tx')
  call assert_equal('  2 these', getline('.'))
  1
  " go to next match (on next line)
  call feedkeys("/the\<C-G>\<cr>", 'tx')
  call assert_equal('  3 the theother', getline('.'))
  1
  " go to next match (still on line 3)
  call feedkeys("/the\<C-G>\<C-G>\<cr>", 'tx')
  call assert_equal('  3 the theother', getline('.'))
  1
  " go to next match (still on line 3)
  call feedkeys("/the\<C-G>\<C-G>\<C-G>\<cr>", 'tx')
  call assert_equal('  3 the theother', getline('.'))
  1
  " go to previous match (on line 3)
  call feedkeys("/the\<C-G>\<C-G>\<C-G>\<C-T>\<cr>", 'tx')
  call assert_equal('  3 the theother', getline('.'))
  1
  " go to previous match (on line 3)
  call feedkeys("/the\<C-G>\<C-G>\<C-G>\<C-T>\<C-T>\<cr>", 'tx')
  call assert_equal('  3 the theother', getline('.'))
  1
  " go to previous match (on line 2)
  call feedkeys("/the\<C-G>\<C-G>\<C-G>\<C-T>\<C-T>\<C-T>\<cr>", 'tx')
  call assert_equal('  2 these', getline('.'))
  1
  " go to previous match (on line 2)
  call feedkeys("/the\<C-G>\<C-R>\<C-W>\<cr>", 'tx')
  call assert_equal('theother', @/)

  " Test 2: keep the view,
  " after deleting a character from the search cmd
  call setline(1, ['  1', '  2 these', '  3 the', '  4 their', '  5 there', '  6 their', '  7 the', '  8 them', '  9 these', ' 10 foobar'])
  resize 5
  1
  call feedkeys("/foo\<bs>\<cr>", 'tx')
  redraw
  call assert_equal({'lnum': 10, 'leftcol': 0, 'col': 4, 'topfill': 0, 'topline': 6, 'coladd': 0, 'skipcol': 0, 'curswant': 4}, winsaveview())

  " remove all history entries
  for i in range(11)
      call histdel('/')
  endfor

  " Test 3: reset the view,
  " after deleting all characters from the search cmd
  norm! 1gg0
  " unfortunately, neither "/foo\<c-w>\<cr>", nor "/foo\<bs>\<bs>\<bs>\<cr>",
  " nor "/foo\<c-u>\<cr>" works to delete the commandline.
  " In that case Vim should return "E35 no previous regular expression",
  " but it looks like Vim still sees /foo and therefore the test fails.
  " Therefore, disabling this test
  "call assert_fails(feedkeys("/foo\<c-w>\<cr>", 'tx'), 'E35:')
  "call assert_equal({'lnum': 1, 'leftcol': 0, 'col': 0, 'topfill': 0, 'topline': 1, 'coladd': 0, 'skipcol': 0, 'curswant': 0}, winsaveview())

  " clean up
  set noincsearch
  call test_override("char_avail", 0)
  bw!
endfunc

func Test_use_sub_pat()
  split
  let @/ = ''
  func X()
    s/^/a/
    /
  endfunc
  call X()
  bwipe!
endfunc

func Test_searchpair()
  new
  call setline(1, ['other code', 'here [', ' [', ' " cursor here', ' ]]'])

  " should not give an error for using "42"
  call assert_equal(0, searchpair('a', 'b', 'c', '', 42))

  4
  call assert_equal(3, searchpair('\[', '', ']', 'bW'))
  call assert_equal([0, 3, 2, 0], getpos('.'))
  4
  call assert_equal(2, searchpair('\[', '', ']', 'bWr'))
  call assert_equal([0, 2, 6, 0], getpos('.'))
  4
  call assert_equal(1, searchpair('\[', '', ']', 'bWm'))
  call assert_equal([0, 3, 2, 0], getpos('.'))
  4|norm ^
  call assert_equal(5, searchpair('\[', '', ']', 'Wn'))
  call assert_equal([0, 4, 2, 0], getpos('.'))
  4
  call assert_equal(2, searchpair('\[', '', ']', 'bW',
        \                         'getline(".") =~ "^\\s*\["'))
  call assert_equal([0, 2, 6, 0], getpos('.'))
  set nomagic
  4
  call assert_equal(3, searchpair('\[', '', ']', 'bW'))
  call assert_equal([0, 3, 2, 0], getpos('.'))
  set magic
  4|norm ^
  call assert_equal(0, searchpair('{', '', '}', 'bW'))
  call assert_equal([0, 4, 2, 0], getpos('.'))

  %d
  call setline(1, ['if 1', '  if 2', '  else', '  endif 2', 'endif 1'])

  /\<if 1
  call assert_equal(5, searchpair('\<if\>', '\<else\>', '\<endif\>', 'W'))
  call assert_equal([0, 5, 1, 0], getpos('.'))
  /\<if 2
  call assert_equal(3, searchpair('\<if\>', '\<else\>', '\<endif\>', 'W'))
  call assert_equal([0, 3, 3, 0], getpos('.'))

  bwipe!
endfunc

func Test_searchpair_timeout()
  CheckFeature reltime
  let g:test_is_flaky = 1

  func Waitabit()
    sleep 20m
    return 1  " skip match
  endfunc

  new
  call setline(1, range(100))
  call setline(1, "(start here")
  call setline(100, "end here)")
  let starttime = reltime()

  " A timeout of 100 msec should happen after about five times of 20 msec wait
  " in Waitabit().  When the timeout applies to each search the elapsed time
  " will be much longer.
  call assert_equal(0, searchpair('(', '\d', ')', '', "Waitabit()", 0, 100))
  let elapsed = reltime(starttime)->reltimefloat()
  call assert_inrange(0.09, 0.300, elapsed)

  delfunc Waitabit
  bwipe!
endfunc

func SearchpairSkip()
  let id = synID(line('.'), col('.'), 0)
  let attr = synIDattr(id, 'name')
  return attr !~ 'comment'
endfunc

func Test_searchpair_timeout_with_skip()
  let g:test_is_flaky = 1

  edit ../evalfunc.c
  if has('win32')
    " Windows timeouts are rather coarse grained, about 16ms.
    let ms = 20
    let min_time = 0.016
    let max_time = min_time * 10.0
  else
    let ms = 1
    let min_time = 0.001
    let max_time = min_time * 15.0
    if RunningWithValgrind()
      let max_time += 0.04  " this can be slow with valgrind
    endif
    if has('bsd')
      " test often fails with FreeBSD
      let max_time = max_time * 2.0
    endif
  endif
  let start = reltime()
  let found = searchpair('(', '', ')', 'crnm', 'SearchpairSkip()', 0, ms)
  let elapsed = reltimefloat(reltime(start))
  call assert_inrange(min_time, max_time, elapsed)

  bwipe!
endfunc

func Test_searchpairpos()
  new
  call setline(1, ['other code', 'here [', ' [', ' " cursor here', ' ]]'])

  4
  call assert_equal([3, 2], searchpairpos('\[', '', ']', 'bW'))
  call assert_equal([0, 3, 2, 0], getpos('.'))
  4
  call assert_equal([2, 6], searchpairpos('\[', '', ']', 'bWr'))
  call assert_equal([0, 2, 6, 0], getpos('.'))
  4|norm ^
  call assert_equal([5, 2], searchpairpos('\[', '', ']', 'Wn'))
  call assert_equal([0, 4, 2, 0], getpos('.'))
  4
  call assert_equal([2, 6], searchpairpos('\[', '', ']', 'bW',
        \                                 'getline(".") =~ "^\\s*\["'))
  call assert_equal([0, 2, 6, 0], getpos('.'))
  4
  call assert_equal([2, 6], searchpairpos('\[', '', ']', 'bWr'))
  call assert_equal([0, 2, 6, 0], getpos('.'))
  set nomagic
  4
  call assert_equal([3, 2], searchpairpos('\[', '', ']', 'bW'))
  call assert_equal([0, 3, 2, 0], getpos('.'))
  set magic
  4|norm ^
  call assert_equal([0, 0], searchpairpos('{', '', '}', 'bW'))
  call assert_equal([0, 4, 2, 0], getpos('.'))

  %d
  call setline(1, ['if 1', '  if 2', '  else', '  endif 2', 'endif 1'])
  /\<if 1
  call assert_equal([5, 1], searchpairpos('\<if\>', '\<else\>', '\<endif\>', 'W'))
  call assert_equal([0, 5, 1, 0], getpos('.'))
  /\<if 2
  call assert_equal([3, 3], searchpairpos('\<if\>', '\<else\>', '\<endif\>', 'W'))
  call assert_equal([0, 3, 3, 0], getpos('.'))

  q!
endfunc

func Test_searchpair_errors()
  call assert_fails("call searchpair([0], 'middle', 'end', 'bW', 'skip', 99, 100)", 'E730: Using a List as a String')
  call assert_fails("call searchpair('start', {-> 0}, 'end', 'bW', 'skip', 99, 100)", 'E729: Using a Funcref as a String')
  call assert_fails("call searchpair('start', 'middle', {'one': 1}, 'bW', 'skip', 99, 100)", 'E731: Using a Dictionary as a String')
  call assert_fails("call searchpair('start', 'middle', 'end', 'flags', 'skip', 99, 100)", 'E475: Invalid argument: flags')
  call assert_fails("call searchpair('start', 'middle', 'end', 'bW', 'func', -99, 100)", 'E475: Invalid argument: -99')
  call assert_fails("call searchpair('start', 'middle', 'end', 'bW', 'func', 99, -100)", 'E475: Invalid argument: -100')
  call assert_fails("call searchpair('start', 'middle', 'end', 'e')", 'E475: Invalid argument: e')
  call assert_fails("call searchpair('start', 'middle', 'end', 'sn')", 'E475: Invalid argument: sn')
endfunc

func Test_searchpairpos_errors()
  call assert_fails("call searchpairpos([0], 'middle', 'end', 'bW', 'skip', 99, 100)", 'E730: Using a List as a String')
  call assert_fails("call searchpairpos('start', {-> 0}, 'end', 'bW', 'skip', 99, 100)", 'E729: Using a Funcref as a String')
  call assert_fails("call searchpairpos('start', 'middle', {'one': 1}, 'bW', 'skip', 99, 100)", 'E731: Using a Dictionary as a String')
  call assert_fails("call searchpairpos('start', 'middle', 'end', 'flags', 'skip', 99, 100)", 'E475: Invalid argument: flags')
  call assert_fails("call searchpairpos('start', 'middle', 'end', 'bW', 'func', -99, 100)", 'E475: Invalid argument: -99')
  call assert_fails("call searchpairpos('start', 'middle', 'end', 'bW', 'func', 99, -100)", 'E475: Invalid argument: -100')
  call assert_fails("call searchpairpos('start', 'middle', 'end', 'e')", 'E475: Invalid argument: e')
  call assert_fails("call searchpairpos('start', 'middle', 'end', 'sn')", 'E475: Invalid argument: sn')
endfunc

func Test_searchpair_skip()
    func Zero()
      return 0
    endfunc
    func Partial(x)
      return a:x
    endfunc
    new
    call setline(1, ['{', 'foo', 'foo', 'foo', '}'])
    3 | call assert_equal(1, searchpair('{', '', '}', 'bWn', ''))
    3 | call assert_equal(1, searchpair('{', '', '}', 'bWn', '0'))
    3 | call assert_equal(1, searchpair('{', '', '}', 'bWn', {-> 0}))
    3 | call assert_equal(1, searchpair('{', '', '}', 'bWn', function('Zero')))
    3 | call assert_equal(1, searchpair('{', '', '}', 'bWn', function('Partial', [0])))
    bw!
endfunc

func Test_searchpair_leak()
  new
  call setline(1, 'if one else another endif')

  " The error in the skip expression caused memory to leak.
  call assert_fails("call searchpair('\\<if\\>', '\\<else\\>', '\\<endif\\>', '', '\"foo\" 2')", 'E15:')

  bwipe!
endfunc

func Test_searchc()
  " These commands used to cause memory overflow in searchc().
  new
  norm ixx
  exe "norm 0t\u93cf"
  bw!
endfunc

func Cmdline3_prep()
  " need to disable char_avail,
  " so that expansion of commandline works
  call test_override("char_avail", 1)
  new
  call setline(1, ['  1', '  2 the~e', '  3 the theother'])
  set incsearch
endfunc

func Incsearch_cleanup()
  set noincsearch
  call test_override("char_avail", 0)
  bw!
endfunc

func Test_search_cmdline3()
  CheckOption incsearch

  call Cmdline3_prep()
  1
  " first match
  call feedkeys("/the\<c-l>\<cr>", 'tx')
  call assert_equal('  2 the~e', getline('.'))

  call Incsearch_cleanup()
endfunc

func Test_search_cmdline3s()
  CheckOption incsearch

  call Cmdline3_prep()
  1
  call feedkeys(":%s/the\<c-l>/xxx\<cr>", 'tx')
  call assert_equal('  2 xxxe', getline('.'))
  undo
  call feedkeys(":%subs/the\<c-l>/xxx\<cr>", 'tx')
  call assert_equal('  2 xxxe', getline('.'))
  undo
  call feedkeys(":%substitute/the\<c-l>/xxx\<cr>", 'tx')
  call assert_equal('  2 xxxe', getline('.'))
  undo
  call feedkeys(":%smagic/the.e/xxx\<cr>", 'tx')
  call assert_equal('  2 xxx', getline('.'))
  undo
  call assert_fails(":%snomagic/the.e/xxx\<cr>", 'E486:')
  "
  call feedkeys(":%snomagic/the\\.e/xxx\<cr>", 'tx')
  call assert_equal('  2 xxx', getline('.'))

  call Incsearch_cleanup()
endfunc

func Test_search_cmdline3g()
  CheckOption incsearch

  call Cmdline3_prep()
  1
  call feedkeys(":g/the\<c-l>/d\<cr>", 'tx')
  call assert_equal('  3 the theother', getline(2))
  undo
  call feedkeys(":global/the\<c-l>/d\<cr>", 'tx')
  call assert_equal('  3 the theother', getline(2))
  undo
  call feedkeys(":g!/the\<c-l>/d\<cr>", 'tx')
  call assert_equal(1, line('$'))
  call assert_equal('  2 the~e', getline(1))
  undo
  call feedkeys(":global!/the\<c-l>/d\<cr>", 'tx')
  call assert_equal(1, line('$'))
  call assert_equal('  2 the~e', getline(1))

  call Incsearch_cleanup()
endfunc

func Test_search_cmdline3v()
  CheckOption incsearch

  call Cmdline3_prep()
  1
  call feedkeys(":v/the\<c-l>/d\<cr>", 'tx')
  call assert_equal(1, line('$'))
  call assert_equal('  2 the~e', getline(1))
  undo
  call feedkeys(":vglobal/the\<c-l>/d\<cr>", 'tx')
  call assert_equal(1, line('$'))
  call assert_equal('  2 the~e', getline(1))

  call Incsearch_cleanup()
endfunc

func Test_search_cmdline4()
  CheckOption incsearch

  " need to disable char_avail,
  " so that expansion of commandline works
  call test_override("char_avail", 1)
  new
  call setline(1, ['  1 the first', '  2 the second', '  3 the third'])
  set incsearch
  $
  call feedkeys("?the\<c-g>\<cr>", 'tx')
  call assert_equal('  3 the third', getline('.'))
  $
  call feedkeys("?the\<c-g>\<c-g>\<cr>", 'tx')
  call assert_equal('  1 the first', getline('.'))
  $
  call feedkeys("?the\<c-g>\<c-g>\<c-g>\<cr>", 'tx')
  call assert_equal('  2 the second', getline('.'))
  $
  call feedkeys("?the\<c-t>\<cr>", 'tx')
  call assert_equal('  1 the first', getline('.'))
  $
  call feedkeys("?the\<c-t>\<c-t>\<cr>", 'tx')
  call assert_equal('  3 the third', getline('.'))
  $
  call feedkeys("?the\<c-t>\<c-t>\<c-t>\<cr>", 'tx')
  call assert_equal('  2 the second', getline('.'))
  " clean up
  set noincsearch
  call test_override("char_avail", 0)
  bw!
endfunc

func Test_search_cmdline5()
  CheckOption incsearch

  " Do not call test_override("char_avail", 1) so that <C-g> and <C-t> work
  " regardless char_avail.
  new
  call setline(1, ['  1 the first', '  2 the second', '  3 the third', ''])
  set incsearch
  1
  call feedkeys("/the\<c-g>\<c-g>\<cr>", 'tx')
  call assert_equal('  3 the third', getline('.'))
  $
  call feedkeys("?the\<c-t>\<c-t>\<c-t>\<cr>", 'tx')
  call assert_equal('  1 the first', getline('.'))
  " clean up
  set noincsearch
  bw!
endfunc

func Test_search_cmdline6()
  " Test that consecutive matches
  " are caught by <c-g>/<c-t>
  CheckOption incsearch

  " need to disable char_avail,
  " so that expansion of commandline works
  call test_override("char_avail", 1)
  new
  call setline(1, [' bbvimb', ''])
  set incsearch
  " first match
  norm! gg0
  call feedkeys("/b\<cr>", 'tx')
  call assert_equal([0,1,2,0], getpos('.'))
  " second match
  norm! gg0
  call feedkeys("/b\<c-g>\<cr>", 'tx')
  call assert_equal([0,1,3,0], getpos('.'))
  " third match
  norm! gg0
  call feedkeys("/b\<c-g>\<c-g>\<cr>", 'tx')
  call assert_equal([0,1,7,0], getpos('.'))
  " first match again
  norm! gg0
  call feedkeys("/b\<c-g>\<c-g>\<c-g>\<cr>", 'tx')
  call assert_equal([0,1,2,0], getpos('.'))
  set nowrapscan
  " last match
  norm! gg0
  call feedkeys("/b\<c-g>\<c-g>\<c-g>\<cr>", 'tx')
  call assert_equal([0,1,7,0], getpos('.'))
  " clean up
  set wrapscan&vim
  set noincsearch
  call test_override("char_avail", 0)
  bw!
endfunc

func Test_search_cmdline7()
  " Test that pressing <c-g> in an empty command line
  " does not move the cursor
  CheckOption incsearch

  " need to disable char_avail,
  " so that expansion of commandline works
  call test_override("char_avail", 1)
  new
  let @/ = 'b'
  call setline(1, [' bbvimb', ''])
  set incsearch
  " first match
  norm! gg0
  " moves to next match of previous search pattern, just like /<cr>
  call feedkeys("/\<c-g>\<cr>", 'tx')
  call assert_equal([0,1,2,0], getpos('.'))
  " moves to next match of previous search pattern, just like /<cr>
  call feedkeys("/\<cr>", 'tx')
  call assert_equal([0,1,3,0], getpos('.'))
  " moves to next match of previous search pattern, just like /<cr>
  call feedkeys("/\<c-t>\<cr>", 'tx')
  call assert_equal([0,1,7,0], getpos('.'))

  " using an offset uses the last search pattern
  call cursor(1, 1)
  call setline(1, ['1 bbvimb', ' 2 bbvimb'])
  let @/ = 'b'
  call feedkeys("//e\<c-g>\<cr>", 'tx')
  call assert_equal('1 bbvimb', getline('.'))
  call assert_equal(4, col('.'))
  call cursor(1, 1)
  call feedkeys("//+1\<c-g>\<cr>", 'tx')
  call assert_equal(' 2 bbvimb', getline('.'))
  call assert_equal([0, 2, 1, 0], getpos('.'))
  call setline(1, 'blah blah blah')
  call feedkeys("gg0/blah/e\<C-G>\<CR>", 'tx')
  call assert_equal([0, 1, 9, 0], getpos('.'))
  call feedkeys("gg0/blah/e\<C-G>\<C-G>\<CR>", 'tx')
  call assert_equal([0, 1, 14, 0], getpos('.'))
  call feedkeys("gg0/blah/e\<C-G>\<C-G>\<C-T>\<CR>", 'tx')
  call assert_equal([0, 1, 9, 0], getpos('.'))
  call cursor(1, col('$'))
  call feedkeys("?blah?e\<C-G>\<CR>", 'tx')
  call assert_equal([0, 1, 9, 0], getpos('.'))
  call feedkeys("gg0/blah/e+1\<C-G>\<CR>", 'tx')
  call assert_equal([0, 1, 10, 0], getpos('.'))
  call feedkeys("gg0/blah/e-2\<C-G>\<CR>", 'tx')
  call assert_equal([0, 1, 7, 0], getpos('.'))
  call setline(1, 'a/b a/b a/b')
  call feedkeys("gg0/a\\/b/e\<C-G>\<CR>", 'tx')
  call assert_equal([0, 1, 7, 0], getpos('.'))

  set noincsearch
  call test_override("char_avail", 0)
  bw!
endfunc

func Test_search_cmdline8()
  " Highlighting is cleared in all windows
  " since hls applies to all windows
  CheckOption incsearch
  CheckFeature terminal
  CheckNotGui
  if has("win32")
    throw "Skipped: Bug with sending <ESC> to terminal window not fixed yet"
  endif

  let h = winheight(0)
  if h < 3
    return
  endif
  " Prepare buffer text
  let lines = ['abb vim vim vi', 'vimvivim']
  call writefile(lines, 'Xsearch.txt', 'D')
  let buf = term_start([GetVimProg(), '--clean', '-c', 'set noswapfile', 'Xsearch.txt'], {'term_rows': 3})

  call WaitForAssert({-> assert_equal(lines, [term_getline(buf, 1), term_getline(buf, 2)])})

  call term_sendkeys(buf, ":set incsearch hlsearch\<cr>")
  call term_sendkeys(buf, ":14vsp\<cr>")
  call term_sendkeys(buf, "/vim\<cr>")
  call term_sendkeys(buf, "/b\<esc>")
  call term_sendkeys(buf, "gg0")
  call TermWait(buf, 250)
  let screen_line = term_scrape(buf, 1)
  let [a0,a1,a2,a3] = [screen_line[3].attr, screen_line[4].attr,
        \ screen_line[18].attr, screen_line[19].attr]
  call assert_notequal(a0, a1)
  call assert_notequal(a0, a3)
  call assert_notequal(a1, a2)
  call assert_equal(a0, a2)
  call assert_equal(a1, a3)

  " clean up
  bwipe!
endfunc

" Tests for regexp with various magic settings
func Run_search_regexp_magic_opt()
  put ='1 a aa abb abbccc'
  exe 'normal! /a*b\{2}c\+/e' . "\<CR>"
  call assert_equal([0, 2, 17, 0], getpos('.'))

  put ='2 d dd dee deefff'
  exe 'normal! /\Md\*e\{2}f\+/e' . "\<CR>"
  call assert_equal([0, 3, 17, 0], getpos('.'))

  set nomagic
  put ='3 g gg ghh ghhiii'
  exe 'normal! /g\*h\{2}i\+/e' . "\<CR>"
  call assert_equal([0, 4, 17, 0], getpos('.'))

  put ='4 j jj jkk jkklll'
  exe 'normal! /\mj*k\{2}l\+/e' . "\<CR>"
  call assert_equal([0, 5, 17, 0], getpos('.'))

  put ='5 m mm mnn mnnooo'
  exe 'normal! /\vm*n{2}o+/e' . "\<CR>"
  call assert_equal([0, 6, 17, 0], getpos('.'))

  put ='6 x ^aa$ x'
  exe 'normal! /\V^aa$' . "\<CR>"
  call assert_equal([0, 7, 5, 0], getpos('.'))

  set magic
  put ='7 (a)(b) abbaa'
  exe 'normal! /\v(a)(b)\2\1\1/e' . "\<CR>"
  call assert_equal([0, 8, 14, 0], getpos('.'))

  put ='8 axx [ab]xx'
  exe 'normal! /\V[ab]\(\[xy]\)\1' . "\<CR>"
  call assert_equal([0, 9, 7, 0], getpos('.'))

  %d
endfunc

func Test_search_regexp()
  enew!

  set regexpengine=1
  call Run_search_regexp_magic_opt()
  set regexpengine=2
  call Run_search_regexp_magic_opt()
  set regexpengine&

  set undolevels=100
  put ='9 foobar'
  put =''
  exe "normal! a\<C-G>u\<Esc>"
  normal G
  exe 'normal! dv?bar?' . "\<CR>"
  call assert_equal('9 foo', getline('.'))
  call assert_equal([0, 2, 5, 0], getpos('.'))
  call assert_equal(2, line('$'))
  normal u
  call assert_equal('9 foobar', getline('.'))
  call assert_equal([0, 2, 6, 0], getpos('.'))
  call assert_equal(3, line('$'))

  set undolevels&
  enew!
endfunc

func Test_search_cmdline_incsearch_highlight()
  CheckOption incsearch

  set incsearch hlsearch
  " need to disable char_avail,
  " so that expansion of commandline works
  call test_override("char_avail", 1)
  new
  call setline(1, ['aaa  1 the first', '  2 the second', '  3 the third'])

  1
  call feedkeys("/second\<cr>", 'tx')
  call assert_equal('second', @/)
  call assert_equal('  2 the second', getline('.'))

  " Canceling search won't change @/
  1
  let @/ = 'last pattern'
  call feedkeys("/third\<C-c>", 'tx')
  call assert_equal('last pattern', @/)
  call feedkeys("/third\<Esc>", 'tx')
  call assert_equal('last pattern', @/)
  call feedkeys("/3\<bs>\<bs>", 'tx')
  call assert_equal('last pattern', @/)
  call feedkeys("/third\<c-g>\<c-t>\<Esc>", 'tx')
  call assert_equal('last pattern', @/)

  " clean up
  set noincsearch nohlsearch
  call test_override("char_avail", 0)
  bw!
endfunc

func Test_search_cmdline_incsearch_highlight_attr()
  CheckOption incsearch
  CheckFeature terminal
  CheckNotGui

  let h = winheight(0)
  if h < 3
    return
  endif

  " Prepare buffer text
  let lines = ['abb vim vim vi', 'vimvivim']
  call writefile(lines, 'Xsearch.txt', 'D')
  let buf = term_start([GetVimProg(), '--clean', '-c', 'set noswapfile', 'Xsearch.txt'], {'term_rows': 3})

  call WaitForAssert({-> assert_equal(lines, [term_getline(buf, 1), term_getline(buf, 2)])})
  " wait for vim to complete initialization
  call TermWait(buf)

  " Get attr of normal(a0), incsearch(a1), hlsearch(a2) highlight
  call term_sendkeys(buf, ":set incsearch hlsearch\<cr>")
  call term_sendkeys(buf, '/b')
  call TermWait(buf, 100)
  let screen_line1 = term_scrape(buf, 1)
  call assert_true(len(screen_line1) > 2)
  " a0: attr_normal
  let a0 = screen_line1[0].attr
  " a1: attr_incsearch
  let a1 = screen_line1[1].attr
  " a2: attr_hlsearch
  let a2 = screen_line1[2].attr
  call assert_notequal(a0, a1)
  call assert_notequal(a0, a2)
  call assert_notequal(a1, a2)
  call term_sendkeys(buf, "\<cr>gg0")

  " Test incremental highlight search
  call term_sendkeys(buf, "/vim")
  call TermWait(buf, 100)
  " Buffer:
  " abb vim vim vi
  " vimvivim
  " Search: /vim
  let attr_line1 = [a0,a0,a0,a0,a1,a1,a1,a0,a2,a2,a2,a0,a0,a0]
  let attr_line2 = [a2,a2,a2,a0,a0,a2,a2,a2]
  call assert_equal(attr_line1, map(term_scrape(buf, 1)[:len(attr_line1)-1], 'v:val.attr'))
  call assert_equal(attr_line2, map(term_scrape(buf, 2)[:len(attr_line2)-1], 'v:val.attr'))

  " Test <C-g>
  call term_sendkeys(buf, "\<C-g>\<C-g>")
  call TermWait(buf, 100)
  let attr_line1 = [a0,a0,a0,a0,a2,a2,a2,a0,a2,a2,a2,a0,a0,a0]
  let attr_line2 = [a1,a1,a1,a0,a0,a2,a2,a2]
  call assert_equal(attr_line1, map(term_scrape(buf, 1)[:len(attr_line1)-1], 'v:val.attr'))
  call assert_equal(attr_line2, map(term_scrape(buf, 2)[:len(attr_line2)-1], 'v:val.attr'))

  " Test <C-t>
  call term_sendkeys(buf, "\<C-t>")
  call TermWait(buf, 100)
  let attr_line1 = [a0,a0,a0,a0,a2,a2,a2,a0,a1,a1,a1,a0,a0,a0]
  let attr_line2 = [a2,a2,a2,a0,a0,a2,a2,a2]
  call assert_equal(attr_line1, map(term_scrape(buf, 1)[:len(attr_line1)-1], 'v:val.attr'))
  call assert_equal(attr_line2, map(term_scrape(buf, 2)[:len(attr_line2)-1], 'v:val.attr'))

  " Type Enter and a1(incsearch highlight) should become a2(hlsearch highlight)
  call term_sendkeys(buf, "\<cr>")
  call TermWait(buf, 100)
  let attr_line1 = [a0,a0,a0,a0,a2,a2,a2,a0,a2,a2,a2,a0,a0,a0]
  let attr_line2 = [a2,a2,a2,a0,a0,a2,a2,a2]
  call assert_equal(attr_line1, map(term_scrape(buf, 1)[:len(attr_line1)-1], 'v:val.attr'))
  call assert_equal(attr_line2, map(term_scrape(buf, 2)[:len(attr_line2)-1], 'v:val.attr'))

  " Test nohlsearch. a2(hlsearch highlight) should become a0(normal highlight)
  call term_sendkeys(buf, ":1\<cr>")
  call term_sendkeys(buf, ":set nohlsearch\<cr>")
  call term_sendkeys(buf, "/vim")
  call TermWait(buf, 100)
  let attr_line1 = [a0,a0,a0,a0,a1,a1,a1,a0,a0,a0,a0,a0,a0,a0]
  let attr_line2 = [a0,a0,a0,a0,a0,a0,a0,a0]
  call assert_equal(attr_line1, map(term_scrape(buf, 1)[:len(attr_line1)-1], 'v:val.attr'))
  call assert_equal(attr_line2, map(term_scrape(buf, 2)[:len(attr_line2)-1], 'v:val.attr'))

  bwipe!
endfunc

func Test_incsearch_cmdline_modifier()
  CheckOption incsearch

  call test_override("char_avail", 1)
  new
  call setline(1, ['foo'])
  set incsearch
  " Test that error E14 does not occur in parsing command modifier.
  call feedkeys("V:tab", 'tx')

  call Incsearch_cleanup()
endfunc

func Test_incsearch_scrolling()
  CheckScreendump
  CheckRunVimInTerminal
  call assert_equal(0, &scrolloff)
  call writefile([
	\ 'let dots = repeat(".", 120)',
	\ 'set incsearch cmdheight=2 scrolloff=0',
	\ 'call setline(1, [dots, dots, dots, "", "target", dots, dots])',
	\ 'normal gg',
	\ 'redraw',
	\ ], 'Xscript', 'D')
  let buf = RunVimInTerminal('-S Xscript', {'rows': 9, 'cols': 70})
  " Need to send one key at a time to force a redraw
  call term_sendkeys(buf, '/')
  sleep 100m
  call term_sendkeys(buf, 't')
  sleep 100m
  call term_sendkeys(buf, 'a')
  sleep 100m
  call term_sendkeys(buf, 'r')
  sleep 100m
  call term_sendkeys(buf, 'g')
  call VerifyScreenDump(buf, 'Test_incsearch_scrolling_01', {})

  call term_sendkeys(buf, "\<Esc>")
  call StopVimInTerminal(buf)
endfunc

func Test_incsearch_search_dump()
  CheckOption incsearch
  CheckScreendump

  call writefile([
	\ 'set incsearch hlsearch scrolloff=0',
	\ 'for n in range(1, 8)',
	\ '  call setline(n, "foo " . n)',
	\ 'endfor',
	\ '3',
	\ ], 'Xis_search_script', 'D')
  let buf = RunVimInTerminal('-S Xis_search_script', {'rows': 9, 'cols': 70})
  " Give Vim a chance to redraw to get rid of the spaces in line 2 caused by
  " the 'ambiwidth' check.
  sleep 100m

  " Need to send one key at a time to force a redraw.
  call term_sendkeys(buf, '/fo')
  call VerifyScreenDump(buf, 'Test_incsearch_search_01', {})
  call term_sendkeys(buf, "\<Esc>")
  sleep 100m

  call term_sendkeys(buf, '/\v')
  call VerifyScreenDump(buf, 'Test_incsearch_search_02', {})
  call term_sendkeys(buf, "\<Esc>")

  call StopVimInTerminal(buf)
endfunc

func Test_hlsearch_dump()
  CheckOption hlsearch
  CheckScreendump

  call writefile([
	\ 'set hlsearch cursorline',
        \ 'call setline(1, ["xxx", "xxx", "xxx"])',
	\ '/.*',
	\ '2',
	\ ], 'Xhlsearch_script', 'D')
  let buf = RunVimInTerminal('-S Xhlsearch_script', {'rows': 6, 'cols': 50})
  call VerifyScreenDump(buf, 'Test_hlsearch_1', {})

  call term_sendkeys(buf, "/\\_.*\<CR>")
  call VerifyScreenDump(buf, 'Test_hlsearch_2', {})

  call StopVimInTerminal(buf)
endfunc

func Test_hlsearch_and_visual()
  CheckOption hlsearch
  CheckScreendump

  call writefile([
	\ 'set hlsearch',
        \ 'call setline(1, repeat(["xxx yyy zzz"], 3))',
        \ 'hi Search cterm=bold',
	\ '/yyy',
	\ 'call cursor(1, 6)',
	\ ], 'Xhlvisual_script', 'D')
  let buf = RunVimInTerminal('-S Xhlvisual_script', {'rows': 6, 'cols': 40})
  call term_sendkeys(buf, "vjj")
  call WaitForAssert({-> assert_match('VISUAL.*-\d', term_getline(buf, 6))}, 1000)
  call VerifyScreenDump(buf, 'Test_hlsearch_visual_1', {})
  call term_sendkeys(buf, "\<Esc>")

  call StopVimInTerminal(buf)
endfunc

func Test_hlsearch_block_visual_match()
  CheckScreendump

  let lines =<< trim END
    set hlsearch
    call setline(1, ['aa', 'bbbb', 'cccccc'])
  END
  call writefile(lines, 'Xhlsearch_block', 'D')
  let buf = RunVimInTerminal('-S Xhlsearch_block', {'rows': 9, 'cols': 60})

  call term_sendkeys(buf, "G\<C-V>$kk\<Esc>")
  sleep 100m
  call term_sendkeys(buf, "/\\%V\<CR>")
  sleep 100m
  call VerifyScreenDump(buf, 'Test_hlsearch_block_visual_match', {})

  call StopVimInTerminal(buf)
endfunc

func Test_incsearch_substitute()
  CheckOption incsearch

  call test_override("char_avail", 1)
  new
  set incsearch
  for n in range(1, 10)
    call setline(n, 'foo ' . n)
  endfor
  4
  call feedkeys(":.,.+2s/foo\<BS>o\<BS>o/xxx\<cr>", 'tx')
  call assert_equal('foo 3', getline(3))
  call assert_equal('xxx 4', getline(4))
  call assert_equal('xxx 5', getline(5))
  call assert_equal('xxx 6', getline(6))
  call assert_equal('foo 7', getline(7))

  call Incsearch_cleanup()
endfunc

func Test_incsearch_substitute_long_line()
  new
  call test_override("char_avail", 1)
  set incsearch

  call repeat('x', 100000)->setline(1)
  call feedkeys(':s/\%c', 'xt')
  redraw
  call feedkeys("\<Esc>", 'xt')

  call Incsearch_cleanup()
  bwipe!
endfunc

func Test_hlsearch_cursearch()
  CheckScreendump

  let lines =<< trim END
    set hlsearch scrolloff=0
    call setline(1, ['one', 'foo', 'bar', 'baz', 'foo the foo and foo', 'bar'])
    hi Search ctermbg=yellow
    hi CurSearch ctermbg=blue
  END
  call writefile(lines, 'Xhlsearch_cursearch', 'D')
  let buf = RunVimInTerminal('-S Xhlsearch_cursearch', {'rows': 9, 'cols': 60})

  call term_sendkeys(buf, "gg/foo\<CR>")
  call VerifyScreenDump(buf, 'Test_hlsearch_cursearch_single_line_1', {})

  call term_sendkeys(buf, "n")
  call VerifyScreenDump(buf, 'Test_hlsearch_cursearch_single_line_2', {})

  call term_sendkeys(buf, "n")
  call VerifyScreenDump(buf, 'Test_hlsearch_cursearch_single_line_2a', {})

  call term_sendkeys(buf, "n")
  call VerifyScreenDump(buf, 'Test_hlsearch_cursearch_single_line_2b', {})

  call term_sendkeys(buf, ":call setline(5, 'foo')\<CR>")
  call term_sendkeys(buf, "0?\<CR>")
  call VerifyScreenDump(buf, 'Test_hlsearch_cursearch_single_line_3', {})

  call term_sendkeys(buf, "gg/foo\\nbar\<CR>")
  call VerifyScreenDump(buf, 'Test_hlsearch_cursearch_multiple_line_1', {})

  call term_sendkeys(buf, ":call setline(1, ['---', 'abcdefg', 'hijkl', '---', 'abcdefg', 'hijkl'])\<CR>")
  call term_sendkeys(buf, "gg/efg\\nhij\<CR>")
  call VerifyScreenDump(buf, 'Test_hlsearch_cursearch_multiple_line_2', {})
  call term_sendkeys(buf, "h\<C-L>")
  call VerifyScreenDump(buf, 'Test_hlsearch_cursearch_multiple_line_3', {})
  call term_sendkeys(buf, "j\<C-L>")
  call VerifyScreenDump(buf, 'Test_hlsearch_cursearch_multiple_line_4', {})
  call term_sendkeys(buf, "h\<C-L>")
  call VerifyScreenDump(buf, 'Test_hlsearch_cursearch_multiple_line_5', {})

  " check clearing CurSearch when using it for another match
  call term_sendkeys(buf, "G?^abcd\<CR>Y")
  call term_sendkeys(buf, "kkP")
  call VerifyScreenDump(buf, 'Test_hlsearch_cursearch_changed_1', {})

  call StopVimInTerminal(buf)
endfunc

" Similar to Test_incsearch_substitute() but with a screendump halfway.
func Test_incsearch_substitute_dump()
  CheckOption incsearch
  CheckScreendump

  call writefile([
	\ 'set incsearch hlsearch scrolloff=0',
	\ 'for n in range(1, 10)',
	\ '  call setline(n, "foo " . n)',
	\ 'endfor',
	\ 'call setline(11, "bar 11")',
	\ '3',
	\ ], 'Xis_subst_script', 'D')
  let buf = RunVimInTerminal('-S Xis_subst_script', {'rows': 9, 'cols': 70})
  " Give Vim a chance to redraw to get rid of the spaces in line 2 caused by
  " the 'ambiwidth' check.
  sleep 100m

  " Need to send one key at a time to force a redraw.
  " Select three lines at the cursor with typed pattern.
  call term_sendkeys(buf, ':.,.+2s/')
  sleep 100m
  call term_sendkeys(buf, 'f')
  sleep 100m
  call term_sendkeys(buf, 'o')
  sleep 100m
  call term_sendkeys(buf, 'o')
  call VerifyScreenDump(buf, 'Test_incsearch_substitute_01', {})
  call term_sendkeys(buf, "\<Esc>")

  " Select three lines at the cursor using previous pattern.
  call term_sendkeys(buf, "/foo\<CR>")
  sleep 100m
  call term_sendkeys(buf, ':.,.+2s//')
  call VerifyScreenDump(buf, 'Test_incsearch_substitute_02', {})

  " Deleting last slash should remove the match.
  call term_sendkeys(buf, "\<BS>")
  call VerifyScreenDump(buf, 'Test_incsearch_substitute_03', {})
  call term_sendkeys(buf, "\<Esc>")

  " Reverse range is accepted
  call term_sendkeys(buf, ':5,2s/foo')
  call VerifyScreenDump(buf, 'Test_incsearch_substitute_04', {})
  call term_sendkeys(buf, "\<Esc>")

  " White space after the command is skipped
  call term_sendkeys(buf, ':2,3sub  /fo')
  call VerifyScreenDump(buf, 'Test_incsearch_substitute_05', {})
  call term_sendkeys(buf, "\<Esc>")

  " Command modifiers are skipped
  call term_sendkeys(buf, ':above below browse botr confirm keepmar keepalt keeppat keepjum filter xxx hide lockm leftabove noau noswap rightbel sandbox silent silent! $tab top unsil vert verbose 4,5s/fo.')
  call VerifyScreenDump(buf, 'Test_incsearch_substitute_06', {})
  call term_sendkeys(buf, "\<Esc>")

  " Cursorline highlighting at match
  call term_sendkeys(buf, ":set cursorline\<CR>")
  call term_sendkeys(buf, 'G9G')
  call term_sendkeys(buf, ':9,11s/bar')
  call VerifyScreenDump(buf, 'Test_incsearch_substitute_07', {})
  call term_sendkeys(buf, "\<Esc>")

  " Cursorline highlighting at cursor when no match
  call term_sendkeys(buf, ':9,10s/bar')
  call VerifyScreenDump(buf, 'Test_incsearch_substitute_08', {})
  call term_sendkeys(buf, "\<Esc>")

  " Only \v handled as empty pattern, does not move cursor
  call term_sendkeys(buf, '3G4G')
  call term_sendkeys(buf, ":nohlsearch\<CR>")
  call term_sendkeys(buf, ':6,7s/\v')
  call VerifyScreenDump(buf, 'Test_incsearch_substitute_09', {})
  call term_sendkeys(buf, "\<Esc>")

  call term_sendkeys(buf, ":set nocursorline\<CR>")

  " All matches are highlighted for 'hlsearch' after the incsearch canceled
  call term_sendkeys(buf, "1G*")
  call term_sendkeys(buf, ":2,5s/foo")
  sleep 100m
  call term_sendkeys(buf, "\<Esc>")
  call VerifyScreenDump(buf, 'Test_incsearch_substitute_10', {})

  call term_sendkeys(buf, ":split\<CR>")
  call term_sendkeys(buf, ":let @/ = 'xyz'\<CR>")
  call term_sendkeys(buf, ":%s/.")
  call VerifyScreenDump(buf, 'Test_incsearch_substitute_11', {})
  call term_sendkeys(buf, "\<BS>")
  call VerifyScreenDump(buf, 'Test_incsearch_substitute_12', {})
  call term_sendkeys(buf, "\<Esc>")
  call VerifyScreenDump(buf, 'Test_incsearch_substitute_13', {})
  call term_sendkeys(buf, ":%bwipe!\<CR>")
  call term_sendkeys(buf, ":only!\<CR>")

  "  get :'<,'>s command in history
  call term_sendkeys(buf, ":set cmdheight=2\<CR>")
  call term_sendkeys(buf, "aasdfasdf\<Esc>")
  call term_sendkeys(buf, "V:s/a/b/g\<CR>")
  " Using '<,'> does not give E20
  call term_sendkeys(buf, ":new\<CR>")
  call term_sendkeys(buf, "aasdfasdf\<Esc>")
  call term_sendkeys(buf, ":\<Up>\<Up>")
  call VerifyScreenDump(buf, 'Test_incsearch_substitute_14', {})
  call term_sendkeys(buf, "<Esc>")

  call StopVimInTerminal(buf)
endfunc

func Test_incsearch_highlighting()
  CheckOption incsearch
  CheckScreendump

  call writefile([
	\ 'set incsearch hlsearch',
	\ 'call setline(1, "hello/there")',
	\ ], 'Xis_subst_hl_script', 'D')
  let buf = RunVimInTerminal('-S Xis_subst_hl_script', {'rows': 4, 'cols': 20})
  " Give Vim a chance to redraw to get rid of the spaces in line 2 caused by
  " the 'ambiwidth' check.
  sleep 300m

  " Using a different search delimiter should still highlight matches
  " that contain a '/'.
  call term_sendkeys(buf, ":%s;ello/the")
  call VerifyScreenDump(buf, 'Test_incsearch_substitute_15', {})
  call term_sendkeys(buf, "<Esc>")

  call StopVimInTerminal(buf)
endfunc

func Test_incsearch_with_change()
  CheckFeature timers
  CheckOption incsearch
  CheckScreendump

  call writefile([
	\ 'set incsearch hlsearch scrolloff=0',
	\ 'call setline(1, ["one", "two ------ X", "three"])',
	\ 'call timer_start(200, { _ -> setline(2, "x")})',
	\ ], 'Xis_change_script', 'D')
  let buf = RunVimInTerminal('-S Xis_change_script', {'rows': 9, 'cols': 70})
  " Give Vim a chance to redraw to get rid of the spaces in line 2 caused by
  " the 'ambiwidth' check.
  sleep 300m

  " Highlight X, it will be deleted by the timer callback.
  call term_sendkeys(buf, ':%s/X')
  call VerifyScreenDump(buf, 'Test_incsearch_change_01', {})
  call term_sendkeys(buf, "\<Esc>")

  call StopVimInTerminal(buf)
endfunc

" Similar to Test_incsearch_substitute_dump() for :sort
func Test_incsearch_sort_dump()
  CheckOption incsearch
  CheckScreendump

  call writefile([
	\ 'set incsearch hlsearch scrolloff=0',
	\ 'call setline(1, ["another one 2", "that one 3", "the one 1"])',
	\ ], 'Xis_sort_script', 'D')
  let buf = RunVimInTerminal('-S Xis_sort_script', {'rows': 9, 'cols': 70})
  " Give Vim a chance to redraw to get rid of the spaces in line 2 caused by
  " the 'ambiwidth' check.
  sleep 100m

  call term_sendkeys(buf, ':sort ni u /on')
  call VerifyScreenDump(buf, 'Test_incsearch_sort_01', {})
  call term_sendkeys(buf, "\<Esc>")

  call term_sendkeys(buf, ':sort! /on')
  call VerifyScreenDump(buf, 'Test_incsearch_sort_02', {})
  call term_sendkeys(buf, "\<Esc>")

  call StopVimInTerminal(buf)
endfunc

" Similar to Test_incsearch_sort_dump() for :uniq
func Test_incsearch_uniq_dump()
  CheckOption incsearch
  CheckScreendump

  call writefile([
	\ 'set incsearch hlsearch scrolloff=0',
	\ 'call setline(1, ["another one 2", "that one 3", "the one 1"])',
	\ ], 'Xis_uniq_script', 'D')
  let buf = RunVimInTerminal('-S Xis_uniq_script', {'rows': 9, 'cols': 70})
  " Give Vim a chance to redraw to get rid of the spaces in line 2 caused by
  " the 'ambiwidth' check.
  sleep 100m

  call term_sendkeys(buf, ':uniq /on')
  call VerifyScreenDump(buf, 'Test_incsearch_uniq_01', {})
  call term_sendkeys(buf, "\<Esc>")

  call term_sendkeys(buf, ':uniq! /on')
  call VerifyScreenDump(buf, 'Test_incsearch_uniq_02', {})
  call term_sendkeys(buf, "\<Esc>")

  call StopVimInTerminal(buf)
endfunc

" Similar to Test_incsearch_substitute_dump() for :vimgrep famiry
func Test_incsearch_vimgrep_dump()
  CheckOption incsearch
  CheckScreendump

  call writefile([
	\ 'set incsearch hlsearch scrolloff=0',
	\ 'call setline(1, ["another one 2", "that one 3", "the one 1"])',
	\ ], 'Xis_vimgrep_script', 'D')
  let buf = RunVimInTerminal('-S Xis_vimgrep_script', {'rows': 9, 'cols': 70})
  " Give Vim a chance to redraw to get rid of the spaces in line 2 caused by
  " the 'ambiwidth' check.
  sleep 100m

  " Need to send one key at a time to force a redraw.
  call term_sendkeys(buf, ':vimgrep on')
  call VerifyScreenDump(buf, 'Test_incsearch_vimgrep_01', {})
  call term_sendkeys(buf, "\<Esc>")

  call term_sendkeys(buf, ':vimg /on/ *.txt')
  call VerifyScreenDump(buf, 'Test_incsearch_vimgrep_02', {})
  call term_sendkeys(buf, "\<Esc>")

  call term_sendkeys(buf, ':vimgrepadd "\<on')
  call VerifyScreenDump(buf, 'Test_incsearch_vimgrep_03', {})
  call term_sendkeys(buf, "\<Esc>")

  call term_sendkeys(buf, ':lv "tha')
  call VerifyScreenDump(buf, 'Test_incsearch_vimgrep_04', {})
  call term_sendkeys(buf, "\<Esc>")

  call term_sendkeys(buf, ':lvimgrepa "the" **/*.txt')
  call VerifyScreenDump(buf, 'Test_incsearch_vimgrep_05', {})
  call term_sendkeys(buf, "\<Esc>")

  call StopVimInTerminal(buf)
endfunc

func Test_keep_last_search_pattern()
  CheckOption incsearch

  new
  call setline(1, ['foo', 'foo', 'foo'])
  set incsearch
  call test_override("char_avail", 1)
  let @/ = 'bar'
  call feedkeys(":/foo/s//\<Esc>", 'ntx')
  call assert_equal('bar', @/)

  " no error message if pattern not found
  call feedkeys(":/xyz/s//\<Esc>", 'ntx')
  call assert_equal('bar', @/)

  bwipe!
  call test_override("ALL", 0)
  set noincsearch
endfunc

func Test_word_under_cursor_after_match()
  CheckOption incsearch

  new
  call setline(1, 'foo bar')
  set incsearch
  call test_override("char_avail", 1)
  try
    call feedkeys("/foo\<C-R>\<C-W>\<CR>", 'ntx')
  catch /E486:/
  endtry
  call assert_equal('foobar', @/)

  bwipe!
  call test_override("ALL", 0)
  set noincsearch
endfunc

func Test_subst_word_under_cursor()
  CheckOption incsearch

  new
  call setline(1, ['int SomeLongName;', 'for (xxx = 1; xxx < len; ++xxx)'])
  set incsearch
  call test_override("char_avail", 1)
  call feedkeys("/LongName\<CR>", 'ntx')
  call feedkeys(":%s/xxx/\<C-R>\<C-W>/g\<CR>", 'ntx')
  call assert_equal('for (SomeLongName = 1; SomeLongName < len; ++SomeLongName)', getline(2))

  bwipe!
  call test_override("ALL", 0)
  set noincsearch
endfunc

func Test_search_skip_all_matches()
  enew
  call setline(1, ['no match here',
        \ 'match this line',
        \ 'nope',
        \ 'match in this line',
        \ 'last line',
        \ ])
  call cursor(1, 1)
  let lnum = search('this', '', 0, 0, 'getline(".") =~ "this line"')
  " Only check that no match is found.  Previously it searched forever.
  call assert_equal(0, lnum)

  bwipe!
endfunc

func Test_search_undefined_behaviour()
  CheckFeature terminal

  let h = winheight(0)
  if h < 3
    return
  endif
  " did cause an undefined left shift
  let g:buf = term_start([GetVimProg(), '--clean', '-e', '-s', '-c', 'call search(getline("."))', 'samples/test000'], {'term_rows': 3})
  call assert_equal([''], getline(1, '$'))
  call term_sendkeys(g:buf, ":qa!\<cr>")
  bwipe!
endfunc

func Test_search_undefined_behaviour2()
  call search("\%UC0000000")
endfunc

" Test for search('multi-byte char', 'bce')
func Test_search_multibyte()
  let save_enc = &encoding
  set encoding=utf8
  enew!
  call append('$', 'Ａ')
  call cursor(2, 1)
  call assert_equal(2, search('Ａ', 'bce', line('.')))
  enew!
  let &encoding = save_enc
endfunc

" This was causing E874.  Also causes an invalid read?
func Test_look_behind()
  new
  call setline(1, '0\|\&\n\@<=')
  call search(getline("."))
  bwipe!
endfunc

func Test_search_visual_area_linewise()
  new
  call setline(1, ['aa', 'bb', 'cc'])
  exe "normal 2GV\<Esc>"
  for engine in [1, 2]
    exe 'set regexpengine=' .. engine
    exe "normal gg/\\%'<\<CR>>"
    call assert_equal([0, 2, 1, 0, 1], getcurpos(), 'engine ' .. engine)
    exe "normal gg/\\%'>\<CR>"
    call assert_equal([0, 2, 2, 0, 2], getcurpos(), 'engine ' .. engine)
  endfor

  bwipe!
  set regexpengine&
endfunc

func Test_search_sentence()
  new
  " this used to cause a crash
  /\%'(
  /
  bwipe
endfunc

" Test that there is no crash when there is a last search pattern but no last
" substitute pattern.
func Test_no_last_substitute_pat()
  " Use viminfo to set the last search pattern to a string and make the last
  " substitute pattern the most recent used and make it empty (NULL).
  call writefile(['~MSle0/bar', '~MSle0~&'], 'Xviminfo', 'D')
  rviminfo! Xviminfo
  call assert_fails('normal n', 'E35:')
endfunc

func Test_search_Ctrl_L_combining()
  " Make sure, that Ctrl-L works correctly with combining characters.
  " It uses an artificial example of an 'a' with 4 combining chars:
    " 'a' U+0061 Dec:97 LATIN SMALL LETTER A &#x61; /\%u61\Z "\u0061"
    " ' ̀' U+0300 Dec:768 COMBINING GRAVE ACCENT &#x300; /\%u300\Z "\u0300"
    " ' ́' U+0301 Dec:769 COMBINING ACUTE ACCENT &#x301; /\%u301\Z "\u0301"
    " ' ̇' U+0307 Dec:775 COMBINING DOT ABOVE &#x307; /\%u307\Z "\u0307"
    " ' ̣' U+0323 Dec:803 COMBINING DOT BELOW &#x323; /\%u323 "\u0323"
  " Those should also appear on the commandline
  CheckOption incsearch

  call Cmdline3_prep()
  1
  let bufcontent = ['', 'Miạ̀́̇m']
  call append('$', bufcontent)
  call feedkeys("/Mi\<c-l>\<c-l>\<cr>", 'tx')
  call assert_equal(5, line('.'))
  call assert_equal(bufcontent[1], @/)
  call Incsearch_cleanup()
endfunc

func Test_large_hex_chars1()
  " This used to cause a crash, the character becomes an NFA state.
  try
    /\%Ufffffc23
  catch
    call assert_match('E678:', v:exception)
  endtry
  try
    set re=1
    /\%Ufffffc23
  catch
    call assert_match('E678:', v:exception)
  endtry
  set re&
endfunc

func Test_large_hex_chars2()
  " This used to cause a crash, the character becomes an NFA state.
  try
    /[\Ufffffc1f]
  catch
    call assert_match('E1541:', v:exception)
  endtry
  try
    set re=1
    /[\Ufffffc1f]
  catch
    call assert_match('E1541:', v:exception)
  endtry
  set re&
endfunc

func Test_large_hex_chars3()
  " Validate max number of Unicode char
  try
    /[\UFFFFFFFF]
  catch
    call assert_match('E1541:', v:exception)
  endtry
  try
    /[\UFFFFFFF]
  catch
    call assert_match('E486:', v:exception)
  endtry
  try
    /\%#=2[\d32-\UFFFFFFFF]
  catch
    call assert_match('E1541:', v:exception)
  endtry
  try
    /\%#=1[\UFFFFFFFF]
  catch
    call assert_match('E1541:', v:exception)
  endtry
  try
    /\%#=1[\d32-\UFFFFFFFF]
  catch
    call assert_match('E945:', v:exception)
  endtry
endfunc

func Test_one_error_msg()
  " This was also giving an internal error
  call assert_fails('call search(" \\((\\v[[=P=]]){185}+             ")', 'E871:')
endfunc

func Test_incsearch_add_char_under_cursor()
  CheckOption incsearch

  set incsearch
  new
  call setline(1, ['find match', 'anything'])
  1
  call test_override('char_avail', 1)
  call feedkeys("fc/m\<C-L>\<C-L>\<C-L>\<C-L>\<C-L>\<CR>", 'tx')
  call assert_equal('match', @/)
  call test_override('char_avail', 0)

  set incsearch&
  bwipe!
endfunc

" Test for the search() function with match at the cursor position
func Test_search_match_at_curpos()
  new
  call append(0, ['foobar', '', 'one two', ''])

  normal gg

  eval 'foobar'->search('c')
  call assert_equal([1, 1], [line('.'), col('.')])

  normal j
  call search('^$', 'c')
  call assert_equal([2, 1], [line('.'), col('.')])

  call search('^$', 'bc')
  call assert_equal([2, 1], [line('.'), col('.')])

  exe "normal /two\<CR>"
  call search('.', 'c')
  call assert_equal([3, 5], [line('.'), col('.')])

  bw!
endfunc

" Test for error cases with the search() function
func Test_search_errors()
  call assert_fails("call search('pat', [])", 'E730:')
  call assert_fails("call search('pat', 'b', {})", 'E728:')
  call assert_fails("call search('pat', 'b', 1, [])", 'E745:')
  call assert_fails("call search('pat', 'ns')", 'E475:')
  call assert_fails("call search('pat', 'mr')", 'E475:')

  new
  call setline(1, ['foo', 'bar'])
  call assert_fails('call feedkeys("/foo/;/bar/;\<CR>", "tx")', 'E386:')
  bwipe!
endfunc

func Test_search_timeout()
  let g:test_is_flaky = 1
  new
  " use a complicated pattern that should be slow with the BT engine
  let pattern = '\%#=1a*.*X\@<=b*'

  " use a timeout of 50 msec
  let search_timeout = 0.05

  " fill the buffer so that it takes 15 times the timeout to search
  let slow_target_timeout = search_timeout * 15.0

  " Fill the buffer with more and more text until searching takes more time
  " than slow_target_timeout.
  for n in range(40, 400, 30)
      call setline(1, ['aaa', repeat('abc ', n), 'ccc'])
      let start = reltime()
      call search(pattern, '', 0)
      let elapsed = reltimefloat(reltime(start))
      if elapsed > slow_target_timeout
          break
      endif
  endfor
  call assert_true(elapsed > slow_target_timeout)

  " Check that the timeout kicks in, the time should be less than half of what
  " we measured without the timeout.  This is permissive, because the timer is
  " known to overrun, especially when using valgrind.
  let max_time = elapsed / 2.0
  let start = reltime()
  call search(pattern, '', 0, float2nr(search_timeout * 1000))
  let elapsed = reltimefloat(reltime(start))
  call assert_inrange(search_timeout * 0.9, max_time, elapsed)

  bwipe!
endfunc

func Test_search_display_pattern()
  new
  call setline(1, ['foo', 'bar', 'foobar'])

  call cursor(1, 1)
  let @/ = 'foo'
  let pat = @/->escape('()*?'. '\s\+')
  let g:a = execute(':unsilent :norm! n')
  call assert_match(pat, g:a)

  " right-left
  if exists("+rightleft")
    set rl
    call cursor(1, 1)
    let @/ = 'foo'
    let pat = 'oof/\s\+'
    let g:a = execute(':unsilent :norm! n')
    call assert_match(pat, g:a)
    set norl
  endif
endfunc

func Test_searchdecl()
  let lines =<< trim END
     int global;

     func()
     {
       int global;
       if (cond) {
	 int local;
       }
       int local;
       // comment
     }
  END
  new
  call setline(1, lines)
  10
  call assert_equal(0, searchdecl('local', 0, 0))
  call assert_equal(7, getcurpos()[1])

  10
  call assert_equal(0, 'local'->searchdecl(0, 1))
  call assert_equal(9, getcurpos()[1])

  10
  call assert_equal(0, searchdecl('global'))
  call assert_equal(5, getcurpos()[1])

  10
  call assert_equal(0, searchdecl('global', 1))
  call assert_equal(1, getcurpos()[1])

  bwipe!
endfunc

func Test_search_special()
  " this was causing illegal memory access and an endless loop
  set t_PE=
  exe "norm /\x80PS"
endfunc

" Test for command failures when the last search pattern is not set.
" Need to run this in a new vim instance where last search pattern is not set.
func Test_search_with_no_last_pat()
  let lines =<< trim [SCRIPT]
    call assert_fails("normal i\<C-R>/\e", 'E35:')
    call assert_fails("exe '/'", 'E35:')
    call assert_fails("exe '?'", 'E35:')
    call assert_fails("/", 'E35:')
    call assert_fails("?", 'E35:')
    call assert_fails("normal n", 'E35:')
    call assert_fails("normal N", 'E35:')
    call assert_fails("normal gn", 'E35:')
    call assert_fails("normal gN", 'E35:')
    call assert_fails("normal cgn", 'E35:')
    call assert_fails("normal cgN", 'E35:')
    let p = []
    let p = @/
    call assert_equal('', p)
    call assert_fails("normal :\<C-R>/", 'E35:')
    call assert_fails("//p", 'E35:')
    call assert_fails(";//p", 'E35:')
    call assert_fails("??p", 'E35:')
    call assert_fails(";??p", 'E35:')
    call assert_fails('g//p', ['E35:', 'E476:'])
    call assert_fails('v//p', ['E35:', 'E476:'])
    call writefile(v:errors, 'Xresult')
    qall!
  [SCRIPT]
  call writefile(lines, 'Xscript', 'D')

  if RunVim([], [], '--clean -S Xscript')
    call assert_equal([], readfile('Xresult'))
  endif
  call delete('Xresult')
endfunc

" Test for using the last substitute pattern without last search pattern.
func Test_search_with_last_substitute_pat()
  let lines =<< trim [SCRIPT]
    new
    set shortmess+=S
    call setline(1, repeat(['foofoo'], 3))
    %s/foo/bar/
    call assert_equal(repeat(['barfoo'], 3), getline(1, '$'))

    call cursor(1, 1)
    call assert_equal("/foo", execute('call feedkeys("/\r", "tx")', '')->trim())
    call assert_equal([0, 1, 4, 0], getpos('.'))

    if has('rightleft')
      set rightleft rightleftcmd=search
      call cursor(1, 1)
      call assert_equal("oof/", execute('call feedkeys("/\r", "tx")', '')->trim())
      call assert_equal([0, 1, 4, 0], getpos('.'))
    endif

    call writefile(v:errors, 'Xresult')
    qall!
  [SCRIPT]
  call writefile(lines, 'Xscript', 'D')

  if RunVim([], [], '--clean -S Xscript')
    call assert_equal([], readfile('Xresult'))
  endif
  call delete('Xresult')
endfunc

" Test for using tilde (~) atom in search. This should use the last used
" substitute pattern
func Test_search_tilde_pat()
  let lines =<< trim [SCRIPT]
    set regexpengine=1
    call assert_fails('exe "normal /~\<CR>"', 'E33:')
    call assert_fails('exe "normal ?~\<CR>"', 'E33:')
    set regexpengine=2
    call assert_fails('exe "normal /~\<CR>"', ['E33:', 'E383:'])
    call assert_fails('exe "normal ?~\<CR>"', ['E33:', 'E383:'])
    set regexpengine&
    call writefile(v:errors, 'Xresult')
    qall!
  [SCRIPT]
  call writefile(lines, 'Xscript', 'D')
  if RunVim([], [], '--clean -S Xscript')
    call assert_equal([], readfile('Xresult'))
  endif
  call delete('Xresult')
endfunc

" Test for searching a pattern that is not present with 'nowrapscan'
func Test_search_pat_not_found()
  new
  set nowrapscan
  let @/ = '1abcxyz2'
  call assert_fails('normal n', 'E385:')
  call assert_fails('normal N', 'E384:')
  set wrapscan&
  bw
endfunc

" Test for v:searchforward variable
func Test_searchforward_var()
  new
  call setline(1, ['foo', '', 'foo'])
  call cursor(2, 1)
  let @/ = 'foo'
  let v:searchforward = 0
  normal N
  call assert_equal(3, line('.'))
  call cursor(2, 1)
  let v:searchforward = 1
  normal N
  call assert_equal(1, line('.'))
  bw!
endfunc

" Test for invalid regular expressions
func Test_invalid_regexp()
  set regexpengine=1
  call assert_fails("call search(repeat('\\(.\\)', 10))", 'E51:')
  call assert_fails("call search('\\%(')", 'E53:')
  call assert_fails("call search('\\(')", 'E54:')
  call assert_fails("call search('\\)')", 'E55:')
  call assert_fails("call search('x\\@#')", 'E59:')
  call assert_fails('call search(''\v%(%(%(%(%(%(%(%(%(%(%(a){1}){1}){1}){1}){1}){1}){1}){1}){1}){1}){1}'')', 'E60:')
  call assert_fails("call search('a\\+*')", 'E61:')
  call assert_fails("call search('\\_m')", 'E63:')
  call assert_fails("call search('\\+')", 'E64:')
  call assert_fails("call search('\\1')", 'E65:')
  call assert_fails("call search('\\z\\(\\)')", 'E66:')
  call assert_fails("call search('\\z2')", 'E67:')
  call assert_fails("call search('\\zx')", 'E68:')
  call assert_fails("call search('\\%[ab')", 'E69:')
  call assert_fails("call search('\\%[]')", 'E70:')
  call assert_fails("call search('\\%a')", 'E71:')
  call assert_fails("call search('ab\\%[\\(cd\\)]')", 'E369:')
  call assert_fails("call search('ab\\%[\\%(cd\\)]')", 'E369:')
  set regexpengine=2
  call assert_fails("call search('\\_')", 'E865:')
  call assert_fails("call search('\\+')", 'E866:')
  call assert_fails("call search('\\zx')", 'E867:')
  call assert_fails("call search('\\%a')", 'E867:')
  call assert_fails("call search('x\\@#')", 'E869:')
  call assert_fails("call search(repeat('\\(.\\)', 10))", 'E872:')
  call assert_fails("call search('\\_m')", 'E877:')
  call assert_fails("call search('\\%(')", 'E53:')
  call assert_fails("call search('\\(')", 'E54:')
  call assert_fails("call search('\\)')", 'E55:')
  call assert_fails("call search('\\z\\(\\)')", 'E66:')
  call assert_fails("call search('\\z2')", 'E67:')
  call assert_fails("call search('\\zx')", 'E867:')
  call assert_fails("call search('\\%[ab')", 'E69:')
  call assert_fails("call search('\\%[]')", 'E70:')
  call assert_fails("call search('\\%9999999999999999999999999999v')", 'E951:')
  set regexpengine&
  call assert_fails("call search('\\%#=3ab')", 'E864:')
endfunc

" Test for searching a very complex pattern in a string. Should switch the
" regexp engine from NFA to the old engine.
func Test_regexp_switch_engine()
  let l = readfile('samples/re.freeze.txt')
  let v = substitute(l[4], '..\@<!', '', '')
  call assert_equal(l[4], v)
endfunc

" Test for the \%V atom to search within visually selected text
func Test_search_in_visual_area()
  new
  call setline(1, ['foo bar1', 'foo bar2', 'foo bar3', 'foo bar4'])
  exe "normal 2GVjo/\\%Vbar\<CR>\<Esc>"
  call assert_equal([2, 5], [line('.'), col('.')])
  exe "normal 2GVj$?\\%Vbar\<CR>\<Esc>"
  call assert_equal([3, 5], [line('.'), col('.')])
  bw!
endfunc

" Test for searching with 'smartcase' and 'ignorecase'
func Test_search_smartcase()
  new
  call setline(1, ['', 'Hello'])
  set noignorecase nosmartcase
  call assert_fails('exe "normal /\\a\\_.\\(.*\\)O\<CR>"', 'E486:')

  set ignorecase nosmartcase
  exe "normal /\\a\\_.\\(.*\\)O\<CR>"
  call assert_equal([2, 1], [line('.'), col('.')])

  call cursor(1, 1)
  set ignorecase smartcase
  call assert_fails('exe "normal /\\a\\_.\\(.*\\)O\<CR>"', 'E486:')

  exe "normal /\\a\\_.\\(.*\\)o\<CR>"
  call assert_equal([2, 1], [line('.'), col('.')])

  " Test for using special atoms with 'smartcase'
  call setline(1, ['', '    Hello\ '])
  call cursor(1, 1)
  call feedkeys('/\_.\%(\uello\)\' .. "\<CR>", 'xt')
  call assert_equal([2, 4], [line('.'), col('.')])

  set ignorecase& smartcase&
  bw!
endfun

" Test 'smartcase' with utf-8.
func Test_search_smartcase_utf8()
  new
  let save_enc = &encoding
  set encoding=utf8 ignorecase smartcase

  call setline(1, 'Café cafÉ')
  1s/café/x/g
  call assert_equal('x x', getline(1))

  call setline(1, 'Café cafÉ')
  1s/cafÉ/x/g
  call assert_equal('Café x', getline(1))

  set ignorecase& smartcase&
  let &encoding = save_enc
  bwipe!
endfunc

" Test searching past the end of a file
func Test_search_past_eof()
  new
  call setline(1, ['Line'])
  exe "normal /\\n\\zs\<CR>"
  call assert_equal([1, 4], [line('.'), col('.')])
  bwipe!
endfunc

" Test setting the start of the match and still finding a next match in the
" same line.
func Test_search_set_start_same_line()
  new
  set cpo-=c

  call setline(1, ['1', '2', '3 .', '4', '5'])
  exe "normal /\\_s\\zs\\S\<CR>"
  call assert_equal([2, 1], [line('.'), col('.')])
  exe 'normal n'
  call assert_equal([3, 1], [line('.'), col('.')])
  exe 'normal n'
  call assert_equal([3, 3], [line('.'), col('.')])
  exe 'normal n'
  call assert_equal([4, 1], [line('.'), col('.')])
  exe 'normal n'
  call assert_equal([5, 1], [line('.'), col('.')])

  set cpo+=c
  bwipe!
endfunc

" Test for various search offsets
func Test_search_offset()
  " With /e, for a match in the first column of a line, the cursor should be
  " placed at the end of the previous line.
  new
  call setline(1, ['one two', 'three four'])
  call search('two\_.', 'e')
  call assert_equal([1, 7], [line('.'), col('.')])

  " with cursor at the beginning of the file, use /s+1
  " '+' without a digit is the same as +1, and 'b' is an alias for 's'
  for searchcmd in ['/two/s+1', '/two/s+', '/two/b+1', '/two/b+', '/']
    call cursor(1, 1)
    exe $"normal {searchcmd}\<CR>"
    call assert_equal([1, 6], [line('.'), col('.')], searchcmd)
    call assert_equal('/two/s+1', Screenline(&lines)->trim(), searchcmd)
  endfor

  " repeat the same search pattern with different offsets
  for [offset, col] in [['s', 5], ['e-1', 6], ['s+2', 7], ['s-2', 3], ['', 5]]
    let searchcmd = $'//{offset}'
    call cursor(1, 1)
    exe $"normal {searchcmd}\<CR>"
    call assert_equal([1, col], [line('.'), col('.')], searchcmd)
    call assert_equal(col == 5 ? '/two' : $'/two/{offset}',
                      \ Screenline(&lines)->trim(), searchcmd)
  endfor

  " with cursor at the end of the file, use /e-1
  " '-' without a digit is the same as -1
  for searchcmd in ['?three?e-1', '?three?e-', '?']
    call cursor(2, 10)
    exe $"normal {searchcmd}\<CR>"
    call assert_equal([2, 4], [line('.'), col('.')], searchcmd)
    call assert_equal('?three?e-1', Screenline(&lines)->trim(), searchcmd)
  endfor

  " repeat the same search pattern with different offsets
  for [offset, col] in [['e', 5], ['s+1', 2], ['e-2', 3], ['e+2', 7], ['', 1]]
    let searchcmd = $'??{offset}'
    call cursor(2, 10)
    exe $"normal {searchcmd}\<CR>"
    call assert_equal([2, col], [line('.'), col('.')], searchcmd)
    call assert_equal(col == 1 ? '?three' : $'?three?{offset}',
                      \ Screenline(&lines)->trim(), searchcmd)
  endfor

  " line offset - after the last line
  " '+' without a digit and '1' without a sign are the same as +1
  for searchcmd in ['/three/+1', '/three/+', '/three/1', '/']
    call cursor(1, 1)
    exe $"normal {searchcmd}\<CR>"
    call assert_equal([2, 1], [line('.'), col('.')], searchcmd)
    call assert_equal('/three/+1', Screenline(&lines)->trim(), searchcmd)
  endfor

  " repeat the same search pattern with different line offsets
  for [offset, lnum] in [['+0', 2], ['-1', 1], ['+2', 2], ['-2', 1]]
    let searchcmd = $'//{offset}'
    call cursor(1, 1)
    exe $"normal {searchcmd}\<CR>"
    call assert_equal([lnum, 1], [line('.'), col('.')], searchcmd)
    call assert_equal($'/three/{offset}',
                      \ Screenline(&lines)->trim(), searchcmd)
  endfor

  " line offset - before the first line
  " '-' without a digit is the same as -1
  for searchcmd in ['?one?-1', '?one?-', '?']
    call cursor(2, 1)
    exe $"normal {searchcmd}\<CR>"
    call assert_equal([1, 1], [line('.'), col('.')], searchcmd)
    call assert_equal('?one?-1', Screenline(&lines)->trim(), searchcmd)
  endfor

  " repeat the same search pattern with different line offsets
  for [offset, lnum] in [['+0', 1], ['+1', 2], ['-2', 1], ['+2', 2]]
    let searchcmd = $'??{offset}'
    call cursor(2, 1)
    exe $"normal {searchcmd}\<CR>"
    call assert_equal([lnum, 1], [line('.'), col('.')], searchcmd)
    call assert_equal($'?one?{offset}',
                      \ Screenline(&lines)->trim(), searchcmd)
  endfor

  " character offset - before the first character in the file
  call cursor(2, 1)
  exe "normal ?one?s-1\<CR>"
  call assert_equal([1, 1], [line('.'), col('.')])
  call assert_equal('?one?s-1', Screenline(&lines)->trim())
  call cursor(2, 1)
  exe "normal ?one?e-3\<CR>"
  call assert_equal([1, 1], [line('.'), col('.')])
  call assert_equal('?one?e-3', Screenline(&lines)->trim())

  " character offset - after the last character in the file
  call cursor(1, 1)
  exe "normal /four/s+4\<CR>"
  call assert_equal([2, 10], [line('.'), col('.')])
  call assert_equal('/four/s+4', Screenline(&lines)->trim())
  call cursor(1, 1)
  exe "normal /four/e+1\<CR>"
  call assert_equal([2, 10], [line('.'), col('.')])
  call assert_equal('/four/e+1', Screenline(&lines)->trim())

  bw!
endfunc

" Test for searching for matching parenthesis using %
func Test_search_match_paren()
  new
  call setline(1, "abc(def')'ghi'('jk'\\t'lm)no")
  " searching for a matching parenthesis should skip single quoted characters
  call cursor(1, 4)
  normal %
  call assert_equal([1, 25], [line('.'), col('.')])
  normal %
  call assert_equal([1, 4], [line('.'), col('.')])
  call cursor(1, 5)
  normal ])
  call assert_equal([1, 25], [line('.'), col('.')])
  call cursor(1, 24)
  normal [(
  call assert_equal([1, 4], [line('.'), col('.')])

  call setline(1, ['x" (a "b" )\', '")'])
  call cursor(1, 4)
  normal %
  call assert_equal([1, 11], [line('.'), col('.')])
  normal %
  call assert_equal([1, 4], [line('.'), col('.')])
  call cursor(1, 10)
  normal [(
  call assert_equal([1, 4], [line('.'), col('.')])
  call cursor(1, 4)
  normal ])
  call assert_equal([1, 11], [line('.'), col('.')])

  " matching parenthesis in 'virtualedit' mode with cursor after the eol
  call setline(1, 'abc(defgh)')
  set virtualedit=all
  normal 20|%
  call assert_equal(4, col('.'))
  set virtualedit&
  bw!
endfunc

" Test for searching a pattern and stopping before a specified line
func Test_search_stopline()
  new
  call setline(1, ['', '', '', 'vim'])
  call assert_equal(0, search('vim', 'n', 3))
  call assert_equal(4, search('vim', 'n', 4))
  call setline(1, ['vim', '', '', ''])
  call cursor(4, 1)
  call assert_equal(0, search('vim', 'bn', 2))
  call assert_equal(1, search('vim', 'bn', 1))
  bw!
endfunc

func Test_incsearch_highlighting_newline()
  CheckRunVimInTerminal
  CheckOption incsearch
  CheckScreendump
  new
  call test_override("char_avail", 1)

  let commands =<< trim [CODE]
    set incsearch nohls
    call setline(1, ['test', 'xxx'])
  [CODE]
  call writefile(commands, 'Xincsearch_nl', 'D')
  let buf = RunVimInTerminal('-S Xincsearch_nl', {'rows': 5, 'cols': 10})
  call TermWait(buf, 100)
  call term_sendkeys(buf, '/test')
  call VerifyScreenDump(buf, 'Test_incsearch_newline1', {})
  " Need to send one key at a time to force a redraw
  call term_sendkeys(buf, '\n')
  call VerifyScreenDump(buf, 'Test_incsearch_newline2', {})
  call term_sendkeys(buf, 'x')
  call VerifyScreenDump(buf, 'Test_incsearch_newline3', {})
  call term_sendkeys(buf, 'x')
  call VerifyScreenDump(buf, 'Test_incsearch_newline4', {})
  call term_sendkeys(buf, "\<CR>")
  call VerifyScreenDump(buf, 'Test_incsearch_newline5', {})
  call StopVimInTerminal(buf)

  " clean up
  call test_override("char_avail", 0)
  bw
endfunc

func Test_incsearch_substitute_dump2()
  CheckOption incsearch
  CheckScreendump

  call writefile([
	\ 'set incsearch hlsearch scrolloff=0',
	\ 'for n in range(1, 4)',
	\ '  call setline(n, "foo " . n)',
	\ 'endfor',
	\ 'call setline(5, "abc|def")',
	\ '3',
	\ ], 'Xis_subst_script2', 'D')
  let buf = RunVimInTerminal('-S Xis_subst_script2', {'rows': 9, 'cols': 70})

  call term_sendkeys(buf, ':%s/\vabc|')
  sleep 100m
  call VerifyScreenDump(buf, 'Test_incsearch_sub_01', {})
  call term_sendkeys(buf, "\<Esc>")

  " The following should not be highlighted
  call term_sendkeys(buf, ':1,5s/\v|')
  sleep 100m
  call VerifyScreenDump(buf, 'Test_incsearch_sub_02', {})


  call StopVimInTerminal(buf)
endfunc

func Test_incsearch_restore_view()
  CheckOption incsearch
  CheckScreendump

  let lines =<< trim [CODE]
    set incsearch nohlsearch
    setlocal scrolloff=0 smoothscroll
    call setline(1, [join(range(25), ' '), '', '', '', '', 'xxx'])
    call feedkeys("2\<C-E>", 't')
  [CODE]
  call writefile(lines, 'Xincsearch_restore_view', 'D')
  let buf = RunVimInTerminal('-S Xincsearch_restore_view', {'rows': 6, 'cols': 20})

  call VerifyScreenDump(buf, 'Test_incsearch_restore_view_01', {})
  call term_sendkeys(buf, '/xx')
  call VerifyScreenDump(buf, 'Test_incsearch_restore_view_02', {})
  call term_sendkeys(buf, 'x')
  call VerifyScreenDump(buf, 'Test_incsearch_restore_view_03', {})
  call term_sendkeys(buf, "\<Esc>")
  call VerifyScreenDump(buf, 'Test_incsearch_restore_view_01', {})

  call StopVimInTerminal(buf)
endfunc

func Test_pattern_is_uppercase_smartcase()
  new
  let input=['abc', 'ABC', 'Abc', 'abC']
  call setline(1, input)
  call cursor(1,1)
  " default, matches firstline
  %s/abc//g
  call assert_equal(['', 'ABC', 'Abc', 'abC'],
        \ getline(1, '$'))

  set smartcase ignorecase
  sil %d
  call setline(1, input)
  call cursor(1,1)
  " with smartcase and incsearch set, matches everything
  %s/abc//g
  call assert_equal(['', '', '', ''], getline(1, '$'))

  sil %d
  call setline(1, input)
  call cursor(1,1)
  " with smartcase and incsearch set and found an uppercase letter,
  " match only that.
  %s/abC//g
  call assert_equal(['abc', 'ABC', 'Abc', ''],
        \ getline(1, '$'))

  sil %d
  call setline(1, input)
  call cursor(1,1)
  exe "norm! vG$\<esc>"
  " \%V should not be detected as uppercase letter
  %s/\%Vabc//g
  call assert_equal(['', '', '', ''], getline(1, '$'))

  call setline(1, input)
  call cursor(1,1)
  exe "norm! vG$\<esc>"
  " \v%V should not be detected as uppercase letter
  %s/\v%Vabc//g
  call assert_equal(['', '', '', ''], getline(1, '$'))

  call setline(1, input)
  call cursor(1,1)
  exe "norm! vG$\<esc>"
  " \v%VabC should be detected as uppercase letter
  %s/\v%VabC//g
  call assert_equal(['abc', 'ABC', 'Abc', ''],
        \ getline(1, '$'))

  call setline(1, input)
  call cursor(1,1)
  " \Vabc should match everything
  %s/\Vabc//g
  call assert_equal(['', '', '', ''], getline(1, '$'))

  call setline(1, input + ['_abc'])
  " _ matches normally
  %s/\v_.*//g
  call assert_equal(['abc', 'ABC', 'Abc', 'abC', ''], getline(1, '$'))

  set smartcase& ignorecase&
  bw!
endfunc

func Test_no_last_search_pattern()
  CheckOption incsearch

  let @/ = ""
  set incsearch
  " these were causing a crash
  call feedkeys("//\<C-G>", 'xt')
  call feedkeys("//\<C-T>", 'xt')
  call feedkeys("??\<C-G>", 'xt')
  call feedkeys("??\<C-T>", 'xt')
endfunc

func Test_search_with_invalid_range()
  new
  let lines =<< trim END
    /\%.v
    5/
    c
  END
  call writefile(lines, 'Xrangesearch', 'D')
  source Xrangesearch

  bwipe!
endfunc

func Test_incsearch_delimiter_ctrlg()
  CheckOption incsearch
  CheckScreendump
  CheckRunVimInTerminal
  call assert_equal(0, &scrolloff)
  call writefile([
	\ 'set incsearch hls',
  \ 'call setline(1, ["1 vim inc", "2 vim /", "3 vim /", "4 vim ?", "5 vim ?"])',
	\ 'normal gg',
	\ 'redraw',
	\ ], 'Xscript_incsearch_delim', 'D')
  let buf = RunVimInTerminal('-S Xscript_incsearch_delim', {'rows': 6})

  call term_sendkeys(buf, '/')
  sleep 100m
  call term_sendkeys(buf, 'v')
  sleep 100m
  call term_sendkeys(buf, 'i')
  sleep 100m
  call term_sendkeys(buf, 'm')
  sleep 100m
  call term_sendkeys(buf, ' ')
  sleep 100m
  call term_sendkeys(buf, '/')
  sleep 100m
  call term_sendkeys(buf, "\<C-G>")
  call VerifyScreenDump(buf, 'Test_incsearch_delim_01', {})
  call term_sendkeys(buf, "\<Esc>")

  call term_sendkeys(buf, ":5\<cr>")
  call term_sendkeys(buf, '?')
  sleep 100m
  call term_sendkeys(buf, 'v')
  sleep 100m
  call term_sendkeys(buf, 'i')
  sleep 100m
  call term_sendkeys(buf, 'm')
  sleep 100m
  call term_sendkeys(buf, ' ')
  sleep 100m
  call term_sendkeys(buf, '?')
  sleep 100m
  call term_sendkeys(buf, "\<C-T>")
  call VerifyScreenDump(buf, 'Test_incsearch_delim_02', {})
  call term_sendkeys(buf, "\<Esc>")

  call term_sendkeys(buf, '/')
  sleep 100m
  call term_sendkeys(buf, 'v')
  sleep 100m
  call term_sendkeys(buf, 'i')
  sleep 100m
  call term_sendkeys(buf, 'm')
  sleep 100m
  call term_sendkeys(buf, ' ')
  sleep 100m
  call term_sendkeys(buf, '\/')
  sleep 100m
  call term_sendkeys(buf, "\<C-G>")
  call VerifyScreenDump(buf, 'Test_incsearch_delim_03', {})
  call term_sendkeys(buf, "\<Esc>")

  call term_sendkeys(buf, ":5\<cr>")
  call term_sendkeys(buf, '?')
  sleep 100m
  call term_sendkeys(buf, 'v')
  sleep 100m
  call term_sendkeys(buf, 'i')
  sleep 100m
  call term_sendkeys(buf, 'm')
  sleep 100m
  call term_sendkeys(buf, ' ')
  sleep 100m
  call term_sendkeys(buf, '\?')
  sleep 100m
  call term_sendkeys(buf, "\<C-T>")
  call VerifyScreenDump(buf, 'Test_incsearch_delim_04', {})
  call term_sendkeys(buf, "\<Esc>")

  call StopVimInTerminal(buf)
endfunc

" vim: shiftwidth=2 sts=2 expandtab
