view src/testdir/test_edit.vim @ 33811:06219b3bdaf3 v9.0.2121

patch 9.0.2121: [security]: use-after-free in ex_substitute Commit: https://github.com/vim/vim/commit/26c11c56888d01e298cd8044caf860f3c26f57bb Author: Christian Brabandt <cb@256bit.org> Date: Wed Nov 22 21:26:41 2023 +0100 patch 9.0.2121: [security]: use-after-free in ex_substitute Problem: [security]: use-after-free in ex_substitute Solution: always allocate memory closes: #13552 A recursive :substitute command could cause a heap-use-after free in Vim (CVE-2023-48706). The whole reproducible test is a bit tricky, I can only reproduce this reliably when no previous substitution command has been used yet (which is the reason, the test needs to run as first one in the test_substitute.vim file) and as a combination of the `:~` command together with a :s command that contains the special substitution atom `~\=` which will make use of a sub-replace special atom and calls a vim script function. There was a comment in the existing :s code, that already makes the `sub` variable allocate memory so that a recursive :s call won't be able to cause any issues here, so this was known as a potential problem already. But for the current test-case that one does not work, because the substitution does not start with `\=` but with `~\=` (and since there does not yet exist a previous substitution atom, Vim will simply increment the `sub` pointer (which then was not allocated dynamically) and later one happily use a sub-replace special expression (which could then free the `sub` var). The following commit fixes this, by making the sub var always using allocated memory, which also means we need to free the pointer whenever we leave the function. Since sub is now always an allocated variable, we also do no longer need the sub_copy variable anymore, since this one was used to indicated when sub pointed to allocated memory (and had therefore to be freed on exit) and when not. Github Security Advisory: https://github.com/vim/vim/security/advisories/GHSA-c8qm-x72m-q53q Signed-off-by: Christian Brabandt <cb@256bit.org>
author Christian Brabandt <cb@256bit.org>
date Wed, 22 Nov 2023 22:15:05 +0100
parents def9fc5c92d1
children 8687845c326c
line wrap: on
line source

" Test for edit functions

if exists("+t_kD")
  let &t_kD="[3;*~"
endif

source check.vim
source screendump.vim

" Needed for testing basic rightleft: Test_edit_rightleft
source view_util.vim

" Needs to come first until the bug in getchar() is
" fixed: https://groups.google.com/d/msg/vim_dev/fXL9yme4H4c/bOR-U6_bAQAJ
func Test_edit_00b()
  new
  call setline(1, ['abc '])
  inoreabbr <buffer> h here some more
  call cursor(1, 4)
  " <c-l> expands the abbreviation and ends insertmode
  call feedkeys(":set im\<cr> h\<c-l>:set noim\<cr>", 'tix')
  call assert_equal(['abc here some more '], getline(1,'$'))
  iunabbr <buffer> h
  bw!
endfunc

func Test_edit_01()
  " set for Travis CI?
  "  set nocp noesckeys
  new
  " 1) empty buffer
  call assert_equal([''], getline(1,'$'))
  " 2) delete in an empty line
  call feedkeys("i\<del>\<esc>", 'tnix')
  call assert_equal([''], getline(1,'$'))
  %d
  " 3) delete one character
  call setline(1, 'a')
  call feedkeys("i\<del>\<esc>", 'tnix')
  call assert_equal([''], getline(1,'$'))
  %d
  " 4) delete a multibyte character
  call setline(1, "\u0401")
  call feedkeys("i\<del>\<esc>", 'tnix')
  call assert_equal([''], getline(1,'$'))
  %d
  " 5.1) delete linebreak with 'bs' option containing eol
  let _bs=&bs
  set bs=eol
  call setline(1, ["abc def", "ghi jkl"])
  call cursor(1, 1)
  call feedkeys("A\<del>\<esc>", 'tnix')
  call assert_equal(['abc defghi jkl'], getline(1, 2))
  %d
  " 5.2) delete linebreak with backspace option w/out eol
  set bs=
  call setline(1, ["abc def", "ghi jkl"])
  call cursor(1, 1)
  call feedkeys("A\<del>\<esc>", 'tnix')
  call assert_equal(["abc def", "ghi jkl"], getline(1, 2))
  let &bs=_bs
  bw!
endfunc

func Test_edit_02()
  " Change cursor position in InsertCharPre command
  new
  call setline(1, 'abc')
  call cursor(1, 1)
  fu! DoIt(...)
    call cursor(1, 4)
    if len(a:000)
      let v:char=a:1
    endif
  endfu
  au InsertCharPre <buffer> :call DoIt('y')
  call feedkeys("ix\<esc>", 'tnix')
  call assert_equal(['abcy'], getline(1, '$'))
  " Setting <Enter> in InsertCharPre
  au! InsertCharPre <buffer> :call DoIt("\n")
  call setline(1, 'abc')
  call cursor(1, 1)
  call feedkeys("ix\<esc>", 'tnix')
  call assert_equal(['abc', ''], getline(1, '$'))
  %d
  au! InsertCharPre
  " Change cursor position in InsertEnter command
  " 1) when setting v:char, keeps changed cursor position
  au! InsertEnter <buffer> :call DoIt('y')
  call setline(1, 'abc')
  call cursor(1, 1)
  call feedkeys("ix\<esc>", 'tnix')
  call assert_equal(['abxc'], getline(1, '$'))
  " 2) when not setting v:char, restores changed cursor position
  au! InsertEnter <buffer> :call DoIt()
  call setline(1, 'abc')
  call cursor(1, 1)
  call feedkeys("ix\<esc>", 'tnix')
  call assert_equal(['xabc'], getline(1, '$'))
  au! InsertEnter
  delfu DoIt
  bw!
endfunc

func Test_edit_03()
  " Change cursor after <c-o> command to end of line
  new
  call setline(1, 'abc')
  call cursor(1, 1)
  call feedkeys("i\<c-o>$y\<esc>", 'tnix')
  call assert_equal(['abcy'], getline(1, '$'))
  %d
  call setline(1, 'abc')
  call cursor(1, 1)
  call feedkeys("i\<c-o>80|y\<esc>", 'tnix')
  call assert_equal(['abcy'], getline(1, '$'))
  %d
  call setline(1, 'abc')
  call feedkeys("Ad\<c-o>:s/$/efg/\<cr>hij", 'tnix')
  call assert_equal(['hijabcdefg'], getline(1, '$'))
  bw!
endfunc

func Test_edit_04()
  " test for :stopinsert
  new
  call setline(1, 'abc')
  call cursor(1, 1)
  call feedkeys("i\<c-o>:stopinsert\<cr>$", 'tnix')
  call feedkeys("aX\<esc>", 'tnix')
  call assert_equal(['abcX'], getline(1, '$'))
  %d
  bw!
endfunc

func Test_edit_05()
  " test for folds being opened
  new
  call setline(1, ['abcX', 'abcX', 'zzzZ'])
  call cursor(1, 1)
  set foldmethod=manual foldopen+=insert
  " create fold for those two lines
  norm! Vjzf
  call feedkeys("$ay\<esc>", 'tnix')
  call assert_equal(['abcXy', 'abcX', 'zzzZ'], getline(1, '$'))
  %d
  call setline(1, ['abcX', 'abcX', 'zzzZ'])
  call cursor(1, 1)
  set foldmethod=manual foldopen-=insert
  " create fold for those two lines
  norm! Vjzf
  call feedkeys("$ay\<esc>", 'tnix')
  call assert_equal(['abcXy', 'abcX', 'zzzZ'], getline(1, '$'))
  %d
  bw!
endfunc

func Test_edit_06()
  " Test in diff mode
  CheckFeature diff
  CheckExecutable diff
  new
  call setline(1, ['abc', 'xxx', 'yyy'])
  vnew
  call setline(1, ['abc', 'zzz', 'xxx', 'yyy'])
  wincmd p
  diffthis
  wincmd p
  diffthis
  wincmd p
  call cursor(2, 1)
  norm! zt
  call feedkeys("Ozzz\<esc>", 'tnix')
  call assert_equal(['abc', 'zzz', 'xxx', 'yyy'], getline(1,'$'))
  bw!
  bw!
endfunc

func Test_edit_07()
  " 1) Test with completion <c-l> when popupmenu is visible
  new
  call setline(1, 'J')

  func! ListMonths()
    call complete(col('.')-1, ['January', 'February', 'March',
    \ 'April', 'May', 'June', 'July', 'August', 'September',
    \ 'October', 'November', 'December'])
    return ''
  endfunc
  inoremap <buffer> <F5> <C-R>=ListMonths()<CR>

  call feedkeys("A\<f5>\<c-p>". repeat("\<down>", 6)."\<c-l>\<down>\<c-l>\<cr>", 'tx')
  call assert_equal(['July'], getline(1,'$'))
  " 1) Test completion when InsertCharPre kicks in
  %d
  call setline(1, 'J')
  fu! DoIt()
    if v:char=='u'
      let v:char='an'
    endif
  endfu
  au InsertCharPre <buffer> :call DoIt()
  call feedkeys("A\<f5>\<c-p>u\<cr>\<c-l>\<cr>", 'tx')
  call assert_equal(["Jan\<c-l>",''], 1->getline('$'))
  %d
  call setline(1, 'J')
  call feedkeys("A\<f5>\<c-p>u\<down>\<c-l>\<cr>", 'tx')
  call assert_equal(["January"], 1->getline('$'))

  delfu ListMonths
  delfu DoIt
  iunmap <buffer> <f5>
  bw!
endfunc

func Test_edit_08()
  " reset insertmode from i_ctrl-r_=
  let g:bufnr = bufnr('%')
  new
  call setline(1, ['abc'])
  call cursor(1, 4)
  call feedkeys(":set im\<cr>ZZZ\<c-r>=setbufvar(g:bufnr,'&im', 0)\<cr>",'tnix')
  call assert_equal(['abZZZc'], getline(1,'$'))
  call assert_equal([0, 1, 1, 0], getpos('.'))
  call assert_false(0, '&im')
  bw!
  unlet g:bufnr
endfunc

func Test_edit_09()
  " test i_CTRL-\ combinations
  new
  call setline(1, ['abc', 'def', 'ghi'])
  call cursor(1, 1)
  " 1) CTRL-\ CTLR-N
  call feedkeys(":set im\<cr>\<c-\>\<c-n>ccABC\<c-l>", 'txin')
  call assert_equal(['ABC', 'def', 'ghi'], getline(1,'$'))
  call setline(1, ['ABC', 'def', 'ghi'])
  " 2) CTRL-\ CTLR-G
  call feedkeys("j0\<c-\>\<c-g>ZZZ\<cr>\<c-l>", 'txin')
  call assert_equal(['ABC', 'ZZZ', 'def', 'ghi'], getline(1,'$'))
  call feedkeys("I\<c-\>\<c-g>YYY\<c-l>", 'txin')
  call assert_equal(['ABC', 'ZZZ', 'YYYdef', 'ghi'], getline(1,'$'))
  set noinsertmode
  " 3) CTRL-\ CTRL-O
  call setline(1, ['ABC', 'ZZZ', 'def', 'ghi'])
  call cursor(1, 1)
  call feedkeys("A\<c-o>ix", 'txin')
  call assert_equal(['ABxC', 'ZZZ', 'def', 'ghi'], getline(1,'$'))
  call feedkeys("A\<c-\>\<c-o>ix", 'txin')
  call assert_equal(['ABxCx', 'ZZZ', 'def', 'ghi'], getline(1,'$'))
  " 4) CTRL-\ a (should be inserted literally, not special after <c-\>
  call setline(1, ['ABC', 'ZZZ', 'def', 'ghi'])
  call cursor(1, 1)
  call feedkeys("A\<c-\>a", 'txin')
  call assert_equal(["ABC\<c-\>a", 'ZZZ', 'def', 'ghi'], getline(1, '$'))
  bw!
endfunc

func Test_edit_11()
  " Test that indenting kicks in
  new
  set cindent
  call setline(1, ['{', '', ''])
  call cursor(2, 1)
  call feedkeys("i\<c-f>int c;\<esc>", 'tnix')
  call cursor(3, 1)
  call feedkeys("\<Insert>/* comment */", 'tnix')
  call assert_equal(['{', "\<tab>int c;", "/* comment */"], getline(1, '$'))
  " added changed cindentkeys slightly
  set cindent cinkeys+=*/
  call setline(1, ['{', '', ''])
  call cursor(2, 1)
  call feedkeys("i\<c-f>int c;\<esc>", 'tnix')
  call cursor(3, 1)
  call feedkeys("i/* comment */", 'tnix')
  call assert_equal(['{', "\<tab>int c;", "\<tab>/* comment */"], getline(1, '$'))
  set cindent cinkeys+==end
  call feedkeys("oend\<cr>\<esc>", 'tnix')
  call assert_equal(['{', "\<tab>int c;", "\<tab>/* comment */", "\tend", ''], getline(1, '$'))
  set cinkeys-==end
  %d
  " Use indentexpr instead of cindenting
  func! Do_Indent()
    if v:lnum == 3
      return 3*shiftwidth()
    else
      return 2*shiftwidth()
    endif
  endfunc
  setl indentexpr=Do_Indent() indentkeys+=*/
  call setline(1, ['{', '', ''])
  call cursor(2, 1)
  call feedkeys("i\<c-f>int c;\<esc>", 'tnix')
  call cursor(3, 1)
  call feedkeys("i/* comment */", 'tnix')
  call assert_equal(['{', "\<tab>\<tab>int c;", "\<tab>\<tab>\<tab>/* comment */"], getline(1, '$'))
  set cinkeys&vim indentkeys&vim
  set nocindent indentexpr=
  delfu Do_Indent
  bw!
endfunc

func Test_edit_11_indentexpr()
  " Test that indenting kicks in
  new
  " Use indentexpr instead of cindenting
  func! Do_Indent()
    let pline=prevnonblank(v:lnum)
    if empty(getline(v:lnum))
      if getline(pline) =~ 'if\|then'
        return shiftwidth()
      else
        return 0
      endif
    else
        return 0
    endif
  endfunc
  setl indentexpr=Do_Indent() indentkeys+=0=then,0=fi
  call setline(1, ['if [ $this ]'])
  call cursor(1, 1)
  call feedkeys("othen\<cr>that\<cr>fi", 'tnix')
  call assert_equal(['if [ $this ]', "then", "\<tab>that", "fi"], getline(1, '$'))
  set cinkeys&vim indentkeys&vim
  set nocindent indentexpr=
  delfu Do_Indent

  " Using a script-local function
  func s:NewIndentExpr()
  endfunc
  set indentexpr=s:NewIndentExpr()
  call assert_equal(expand('<SID>') .. 'NewIndentExpr()', &indentexpr)
  call assert_equal(expand('<SID>') .. 'NewIndentExpr()', &g:indentexpr)
  set indentexpr=<SID>NewIndentExpr()
  call assert_equal(expand('<SID>') .. 'NewIndentExpr()', &indentexpr)
  call assert_equal(expand('<SID>') .. 'NewIndentExpr()', &g:indentexpr)
  setlocal indentexpr=
  setglobal indentexpr=s:NewIndentExpr()
  call assert_equal(expand('<SID>') .. 'NewIndentExpr()', &g:indentexpr)
  call assert_equal('', &indentexpr)
  new
  call assert_equal(expand('<SID>') .. 'NewIndentExpr()', &indentexpr)
  bw!
  setglobal indentexpr=<SID>NewIndentExpr()
  call assert_equal(expand('<SID>') .. 'NewIndentExpr()', &g:indentexpr)
  call assert_equal('', &indentexpr)
  new
  call assert_equal(expand('<SID>') .. 'NewIndentExpr()', &indentexpr)
  bw!
  set indentexpr&

  bw!
endfunc

" Test changing indent in replace mode
func Test_edit_12()
  new
  call setline(1, ["\tabc", "\tdef"])
  call cursor(2, 4)
  call feedkeys("R^\<c-d>", 'tnix')
  call assert_equal(["\tabc", "def"], getline(1, '$'))
  call assert_equal([0, 2, 2, 0], '.'->getpos())
  %d
  call setline(1, ["\tabc", "\t\tdef"])
  call cursor(2, 2)
  call feedkeys("R^\<c-d>", 'tnix')
  call assert_equal(["\tabc", "def"], getline(1, '$'))
  call assert_equal([0, 2, 1, 0], getpos('.'))
  %d
  call setline(1, ["\tabc", "\t\tdef"])
  call cursor(2, 2)
  call feedkeys("R\<c-t>", 'tnix')
  call assert_equal(["\tabc", "\t\t\tdef"], getline(1, '$'))
  call assert_equal([0, 2, 2, 0], getpos('.'))
  bw!
  10vnew
  call setline(1, ["\tabc", "\t\tdef"])
  call cursor(2, 2)
  call feedkeys("R\<c-t>", 'tnix')
  call assert_equal(["\tabc", "\t\t\tdef"], getline(1, '$'))
  call assert_equal([0, 2, 2, 0], getpos('.'))
  %d
  set sw=4
  call setline(1, ["\tabc", "\t\tdef"])
  call cursor(2, 2)
  call feedkeys("R\<c-t>\<c-t>", 'tnix')
  call assert_equal(["\tabc", "\t\t\tdef"], getline(1, '$'))
  call assert_equal([0, 2, 2, 0], getpos('.'))
  %d
  call setline(1, ["\tabc", "\t\tdef"])
  call cursor(2, 2)
  call feedkeys("R\<c-t>\<c-t>", 'tnix')
  call assert_equal(["\tabc", "\t\t\tdef"], getline(1, '$'))
  call assert_equal([0, 2, 2, 0], getpos('.'))
  set sw&

  " In replace mode, after hitting enter in a line with tab characters,
  " pressing backspace should restore the tab characters.
  %d
  setlocal autoindent backspace=2
  call setline(1, "\tone\t\ttwo")
  exe "normal ggRred\<CR>six" .. repeat("\<BS>", 8)
  call assert_equal(["\tone\t\ttwo"], getline(1, '$'))
  bw!
endfunc

func Test_edit_13()
  " Test smartindenting
  new
  set smartindent autoindent
  call setline(1, ["\tabc"])
  call feedkeys("A {\<cr>more\<cr>}\<esc>", 'tnix')
  call assert_equal(["\tabc {", "\t\tmore", "\t}"], getline(1, '$'))
  set smartindent& autoindent&
  bwipe!

  " Test autoindent removing indent of blank line.
  new
  call setline(1, '    foo bar baz')
  set autoindent
  exe "normal 0eea\<CR>\<CR>\<Esc>"
  call assert_equal("    foo bar", getline(1))
  call assert_equal("", getline(2))
  call assert_equal("    baz", getline(3))
  set autoindent&

  " pressing <C-U> to erase line should keep the indent with 'autoindent'
  set backspace=2 autoindent
  %d
  exe "normal i\tone\<CR>three\<C-U>two"
  call assert_equal(["\tone", "\ttwo"], getline(1, '$'))
  set backspace& autoindent&

  bwipe!
endfunc

" Test for autoindent removing indent when insert mode is stopped.  Some parts
" of the code is exercised only when interactive mode is used. So use Vim in a
" terminal.
func Test_autoindent_remove_indent()
  CheckRunVimInTerminal
  let buf = RunVimInTerminal('-N Xarifile', {'rows': 6, 'cols' : 20})
  call TermWait(buf)
  call term_sendkeys(buf, ":set autoindent\n")
  " leaving insert mode in a new line with indent added by autoindent, should
  " remove the indent.
  call term_sendkeys(buf, "i\<Tab>foo\<CR>\<Esc>")
  " Need to delay for some time, otherwise the code in getchar.c will not be
  " exercised.
  call TermWait(buf, 50)
  " when a line is wrapped and the cursor is at the start of the second line,
  " leaving insert mode, should move the cursor back to the first line.
  call term_sendkeys(buf, "o" .. repeat('x', 20) .. "\<Esc>")
  " Need to delay for some time, otherwise the code in getchar.c will not be
  " exercised.
  call TermWait(buf, 50)
  call term_sendkeys(buf, ":w\n")
  call TermWait(buf)
  call StopVimInTerminal(buf)
  call assert_equal(["\tfoo", '', repeat('x', 20)], readfile('Xarifile'))
  call delete('Xarifile')
endfunc

func Test_edit_CR()
  " Test for <CR> in insert mode
  " basically only in quickfix mode it's tested, the rest
  " has been taken care of by other tests
  CheckFeature quickfix
  botright new
  call writefile(range(1, 10), 'Xqflist.txt', 'D')
  call setqflist([{'filename': 'Xqflist.txt', 'lnum': 2}])
  copen
  set modifiable
  call feedkeys("A\<cr>", 'tnix')
  call assert_equal('Xqflist.txt', bufname(''))
  call assert_equal(2, line('.'))
  cclose
  botright new
  call setloclist(0, [{'filename': 'Xqflist.txt', 'lnum': 10}])
  lopen
  set modifiable
  call feedkeys("A\<cr>", 'tnix')
  call assert_equal('Xqflist.txt', bufname(''))
  call assert_equal(10, line('.'))
  call feedkeys("A\<Enter>", 'tnix')
  call feedkeys("A\<kEnter>", 'tnix')
  call feedkeys("A\n", 'tnix')
  call feedkeys("A\r", 'tnix')
  call assert_equal(map(range(1, 10), 'string(v:val)') + ['', '', '', ''], getline(1, '$'))

  bw!
  lclose
endfunc

func Test_edit_CTRL_()
  CheckFeature rightleft
  " disabled for Windows builds, why?
  CheckNotMSWindows
  let _encoding=&encoding
  set encoding=utf-8
  " Test for CTRL-_
  new
  call setline(1, ['abc'])
  call cursor(1, 1)
  call feedkeys("i\<c-_>xyz\<esc>", 'tnix')
  call assert_equal(["\<C-_>xyzabc"], getline(1, '$'))
  call assert_false(&revins)
  set ari
  call setline(1, ['abc'])
  call cursor(1, 1)
  call feedkeys("i\<c-_>xyz\<esc>", 'tnix')
  call assert_equal(["æèñabc"], getline(1, '$'))
  call assert_true(&revins)
  call setline(1, ['abc'])
  call cursor(1, 1)
  call feedkeys("i\<c-_>xyz\<esc>", 'tnix')
  call assert_equal(["xyzabc"], getline(1, '$'))
  call assert_false(&revins)
  set noari
  let &encoding=_encoding
  bw!
endfunc

" needs to come first, to have the @. register empty
func Test_edit_00a_CTRL_A()
  " Test pressing CTRL-A
  new
  call setline(1, repeat([''], 5))
  call cursor(1, 1)
  try
    call feedkeys("A\<NUL>", 'tnix')
  catch /^Vim\%((\a\+)\)\=:E29/
    call assert_true(1, 'E29 error caught')
  endtry
  call cursor(1, 1)
  call feedkeys("Afoobar \<esc>", 'tnix')
  call cursor(2, 1)
  call feedkeys("A\<c-a>more\<esc>", 'tnix')
  call cursor(3, 1)
  call feedkeys("A\<NUL>and more\<esc>", 'tnix')
  call assert_equal(['foobar ', 'foobar more', 'foobar morend more', '', ''], getline(1, '$'))
  bw!
endfunc

func Test_edit_CTRL_EY()
  " Ctrl-E/ Ctrl-Y in insert mode completion to scroll
  10new
  call setline(1, range(1, 100))
  call cursor(30, 1)
  norm! z.
  call feedkeys("A\<c-x>\<c-e>\<c-e>\<c-e>\<c-e>\<c-e>", 'tnix')
  call assert_equal(30, winsaveview()['topline'])
  call assert_equal([0, 30, 2, 0], getpos('.'))
  call feedkeys("A\<c-x>\<c-e>\<c-e>\<c-e>\<c-e>\<c-e>", 'tnix')
  call feedkeys("A\<c-x>".repeat("\<c-y>", 10), 'tnix')
  call assert_equal(21, winsaveview()['topline'])
  call assert_equal([0, 30, 2, 0], getpos('.'))
  bw!
endfunc

func Test_edit_CTRL_G()
  new
  call setline(1, ['foobar', 'foobar', 'foobar'])
  call cursor(2, 4)
  call feedkeys("ioooooooo\<c-g>k\<c-r>.\<esc>", 'tnix')
  call assert_equal(['foooooooooobar', 'foooooooooobar', 'foobar'], getline(1, '$'))
  call assert_equal([0, 1, 11, 0], getpos('.'))
  call feedkeys("i\<c-g>k\<esc>", 'tnix')
  call assert_equal([0, 1, 10, 0], getpos('.'))
  call cursor(2, 4)
  call feedkeys("i\<c-g>jzzzz\<esc>", 'tnix')
  call assert_equal(['foooooooooobar', 'foooooooooobar', 'foozzzzbar'], getline(1, '$'))
  call assert_equal([0, 3, 7, 0], getpos('.'))
  call feedkeys("i\<c-g>j\<esc>", 'tnix')
  call assert_equal([0, 3, 6, 0], getpos('.'))
  call assert_nobeep("normal! i\<c-g>\<esc>")
  bw!
endfunc

func Test_edit_CTRL_I()
  " Tab in completion mode
  let path=expand("%:p:h")
  new
  call setline(1, [path. "/", ''])
  call feedkeys("Arunt\<c-x>\<c-f>\<tab>\<cr>\<esc>", 'tnix')
  call assert_match('runtest\.vim', getline(1))
  %d
  call writefile(['one', 'two', 'three'], 'Xinclude.txt', 'D')
  let include='#include Xinclude.txt'
  call setline(1, [include, ''])
  call cursor(2, 1)
  call feedkeys("A\<c-x>\<tab>\<cr>\<esc>", 'tnix')
  call assert_equal([include, 'one', ''], getline(1, '$'))
  call feedkeys("2ggC\<c-x>\<tab>\<down>\<cr>\<esc>", 'tnix')
  call assert_equal([include, 'two', ''], getline(1, '$'))
  call feedkeys("2ggC\<c-x>\<tab>\<down>\<down>\<cr>\<esc>", 'tnix')
  call assert_equal([include, 'three', ''], getline(1, '$'))
  call feedkeys("2ggC\<c-x>\<tab>\<down>\<down>\<down>\<cr>\<esc>", 'tnix')
  call assert_equal([include, '', ''], getline(1, '$'))
  bw!
endfunc

func Test_edit_CTRL_K()
  " Test pressing CTRL-K (basically only dictionary completion and digraphs
  " the rest is already covered
  call writefile(['A', 'AA', 'AAA', 'AAAA'], 'Xdictionary.txt', 'D')
  set dictionary=Xdictionary.txt
  new
  call setline(1, 'A')
  call cursor(1, 1)
  call feedkeys("A\<c-x>\<c-k>\<cr>\<esc>", 'tnix')
  call assert_equal(['AA', ''], getline(1, '$'))
  %d
  call setline(1, 'A')
  call cursor(1, 1)
  call feedkeys("A\<c-x>\<c-k>\<down>\<cr>\<esc>", 'tnix')
  call assert_equal(['AAA'], getline(1, '$'))
  %d
  call setline(1, 'A')
  call cursor(1, 1)
  call feedkeys("A\<c-x>\<c-k>\<down>\<down>\<cr>\<esc>", 'tnix')
  call assert_equal(['AAAA'], getline(1, '$'))
  %d
  call setline(1, 'A')
  call cursor(1, 1)
  call feedkeys("A\<c-x>\<c-k>\<down>\<down>\<down>\<cr>\<esc>", 'tnix')
  call assert_equal(['A'], getline(1, '$'))
  %d
  call setline(1, 'A')
  call cursor(1, 1)
  call feedkeys("A\<c-x>\<c-k>\<down>\<down>\<down>\<down>\<cr>\<esc>", 'tnix')
  call assert_equal(['AA'], getline(1, '$'))

  " press an unexpected key after dictionary completion
  %d
  call setline(1, 'A')
  call cursor(1, 1)
  call feedkeys("A\<c-x>\<c-k>\<c-]>\<cr>\<esc>", 'tnix')
  call assert_equal(['AA', ''], getline(1, '$'))
  %d
  call setline(1, 'A')
  call cursor(1, 1)
  call feedkeys("A\<c-x>\<c-k>\<c-s>\<cr>\<esc>", 'tnix')
  call assert_equal(["AA\<c-s>", ''], getline(1, '$'))
  %d
  call setline(1, 'A')
  call cursor(1, 1)
  call feedkeys("A\<c-x>\<c-k>\<c-f>\<cr>\<esc>", 'tnix')
  call assert_equal(["AA\<c-f>", ''], getline(1, '$'))

  set dictionary=
  %d
  call setline(1, 'A')
  call cursor(1, 1)
  let v:testing = 1
  try
    call feedkeys("A\<c-x>\<c-k>\<esc>", 'tnix')
  catch
    " error sleeps 2 seconds, when v:testing is not set
    let v:testing = 0
  endtry

  call test_override("char_avail", 1)
  set showcmd
  %d
  call feedkeys("A\<c-k>a:\<esc>", 'tnix')
  call assert_equal(['ä'], getline(1, '$'))
  call test_override("char_avail", 0)
  set noshowcmd

  bw!
endfunc

func Test_edit_CTRL_L()
  " Test Ctrl-X Ctrl-L (line completion)
  new
  set complete=.
  call setline(1, ['one', 'two', 'three', '', '', '', ''])
  call cursor(4, 1)
  call feedkeys("A\<c-x>\<c-l>\<esc>", 'tnix')
  call assert_equal(['one', 'two', 'three', 'three', '', '', ''], getline(1, '$'))
  call feedkeys("cct\<c-x>\<c-l>\<c-n>\<esc>", 'tnix')
  call assert_equal(['one', 'two', 'three', 't', '', '', ''], getline(1, '$'))
  call feedkeys("cct\<c-x>\<c-l>\<c-n>\<c-n>\<esc>", 'tnix')
  call assert_equal(['one', 'two', 'three', 'two', '', '', ''], getline(1, '$'))
  call feedkeys("cct\<c-x>\<c-l>\<c-n>\<c-n>\<c-n>\<esc>", 'tnix')
  call assert_equal(['one', 'two', 'three', 'three', '', '', ''], getline(1, '$'))
  call feedkeys("cct\<c-x>\<c-l>\<c-n>\<c-n>\<c-n>\<c-n>\<esc>", 'tnix')
  call assert_equal(['one', 'two', 'three', 't', '', '', ''], getline(1, '$'))
  call feedkeys("cct\<c-x>\<c-l>\<c-p>\<esc>", 'tnix')
  call assert_equal(['one', 'two', 'three', 'two', '', '', ''], getline(1, '$'))
  call feedkeys("cct\<c-x>\<c-l>\<c-p>\<c-p>\<esc>", 'tnix')
  call assert_equal(['one', 'two', 'three', 't', '', '', ''], getline(1, '$'))
  call feedkeys("cct\<c-x>\<c-l>\<c-p>\<c-p>\<c-p>\<esc>", 'tnix')
  call assert_equal(['one', 'two', 'three', 'three', '', '', ''], getline(1, '$'))
  set complete=
  call cursor(5, 1)
  call feedkeys("A\<c-x>\<c-l>\<c-p>\<c-n>\<esc>", 'tnix')
  call assert_equal(['one', 'two', 'three', 'three', "\<c-l>\<c-p>\<c-n>", '', ''], getline(1, '$'))
  set complete&
  %d
  if has("conceal") && has("syntax")
    call setline(1, ['foo', 'bar', 'foobar'])
    call test_override("char_avail", 1)
    set conceallevel=2 concealcursor=n
    syn on
    syn match ErrorMsg "^bar"
    call matchadd("Conceal", 'oo', 10, -1, {'conceal': 'X'})
    func! DoIt()
      let g:change=1
    endfunc
    au! TextChangedI <buffer> :call DoIt()

    call cursor(2, 1)
    call assert_false(exists("g:change"))
    call feedkeys("A \<esc>", 'tnix')
    call assert_equal(['foo', 'bar ', 'foobar'], getline(1, '$'))
    call assert_equal(1, g:change)

    call test_override("char_avail", 0)
    call clearmatches()
    syn off
    au! TextChangedI
    delfu DoIt
    unlet! g:change
  endif
  bw!
endfunc

func Test_edit_CTRL_N()
  " Check keyword completion
  for e in ['latin1', 'utf-8']
    exe 'set encoding=' .. e
    new
    set complete=.
    call setline(1, ['INFER', 'loWER', '', '', ])
    call cursor(3, 1)
    call feedkeys("Ai\<c-n>\<cr>\<esc>", "tnix")
    call feedkeys("ILO\<c-n>\<cr>\<esc>", 'tnix')
    call assert_equal(['INFER', 'loWER', 'i', 'LO', '', ''], getline(1, '$'), e)
    %d
    call setline(1, ['INFER', 'loWER', '', '', ])
    call cursor(3, 1)
    set ignorecase infercase
    call feedkeys("Ii\<c-n>\<cr>\<esc>", "tnix")
    call feedkeys("ILO\<c-n>\<cr>\<esc>", 'tnix')
    call assert_equal(['INFER', 'loWER', 'infer', 'LOWER', '', ''], getline(1, '$'), e)
    set noignorecase noinfercase
    %d
    call setline(1, ['one word', 'two word'])
    exe "normal! Goo\<C-P>\<C-X>\<C-P>"
    call assert_equal('one word', getline(3))
    %d
    set complete&
    bw!
  endfor
endfunc

func Test_edit_CTRL_O()
  " Check for CTRL-O in insert mode
  new
  inoreabbr <buffer> h here some more
  call setline(1, ['abc', 'def'])
  call cursor(1, 1)
  " Ctrl-O after an abbreviation
  exe "norm A h\<c-o>:set nu\<cr> text"
  call assert_equal(['abc here some more text', 'def'], getline(1, '$'))
  call assert_true(&nu)
  set nonu
  iunabbr <buffer> h
  " Ctrl-O at end of line with 've'=onemore
  call cursor(1, 1)
  call feedkeys("A\<c-o>:let g:a=getpos('.')\<cr>\<esc>", 'tnix')
  call assert_equal([0, 1, 23, 0], g:a)
  call cursor(1, 1)
  set ve=onemore
  call feedkeys("A\<c-o>:let g:a=getpos('.')\<cr>\<esc>", 'tnix')
  call assert_equal([0, 1, 24, 0], g:a)
  set ve=
  unlet! g:a
  bw!
endfunc

func Test_edit_CTRL_R()
  " Insert Register
  new
  call test_override("ALL", 1)
  set showcmd
  call feedkeys("AFOOBAR eins zwei\<esc>", 'tnix')
  call feedkeys("O\<c-r>.", 'tnix')
  call feedkeys("O\<c-r>=10*500\<cr>\<esc>", 'tnix')
  call feedkeys("O\<c-r>=getreg('=', 1)\<cr>\<esc>", 'tnix')
  call assert_equal(["getreg('=', 1)", '5000', "FOOBAR eins zwei", "FOOBAR eins zwei"], getline(1, '$'))
  call test_override("ALL", 0)
  set noshowcmd
  bw!
endfunc

func Test_edit_CTRL_S()
  " Test pressing CTRL-S (basically only spellfile completion)
  " the rest is already covered
  new
  if !has("spell")
    call setline(1, 'vim')
    call feedkeys("A\<c-x>ss\<cr>\<esc>", 'tnix')
    call assert_equal(['vims', ''], getline(1, '$'))
    bw!
    return
  endif
  call setline(1, 'vim')
  " spell option not yet set
  try
    call feedkeys("A\<c-x>\<c-s>\<cr>\<esc>", 'tnix')
  catch /^Vim\%((\a\+)\)\=:E756/
    call assert_true(1, 'error caught')
  endtry
  call assert_equal(['vim', ''], getline(1, '$'))
  %d
  setl spell spelllang=en
  call setline(1, 'vim')
  call cursor(1, 1)
  call feedkeys("A\<c-x>\<c-s>\<cr>\<esc>", 'tnix')
  call assert_equal(['Vim', ''], getline(1, '$'))
  %d
  call setline(1, 'vim')
  call cursor(1, 1)
  call feedkeys("A\<c-x>\<c-s>\<down>\<cr>\<esc>", 'tnix')
  call assert_equal(['Aim'], getline(1, '$'))
  %d
  call setline(1, 'vim')
  call cursor(1, 1)
  call feedkeys("A\<c-x>\<c-s>\<c-p>\<cr>\<esc>", 'tnix')
  call assert_equal(['vim', ''], getline(1, '$'))
  %d
  " empty buffer
  call cursor(1, 1)
  call feedkeys("A\<c-x>\<c-s>\<c-p>\<cr>\<esc>", 'tnix')
  call assert_equal(['', ''], getline(1, '$'))
  setl nospell
  bw!
endfunc

func Test_edit_CTRL_T()
  " Check for CTRL-T and CTRL-X CTRL-T in insert mode
  " 1) increase indent
  new
  call setline(1, "abc")
  call cursor(1, 1)
  call feedkeys("A\<c-t>xyz", 'tnix')
  call assert_equal(["\<tab>abcxyz"], getline(1, '$'))
  " 2) also when paste option is set
  set paste
  call setline(1, "abc")
  call cursor(1, 1)
  call feedkeys("A\<c-t>xyz", 'tnix')
  call assert_equal(["\<tab>abcxyz"], getline(1, '$'))
  set nopaste
  " CTRL-X CTRL-T (thesaurus complete)
  call writefile(['angry furious mad enraged'], 'Xthesaurus', 'D')
  set thesaurus=Xthesaurus
  call setline(1, 'mad')
  call cursor(1, 1)
  call feedkeys("A\<c-x>\<c-t>\<cr>\<esc>", 'tnix')
  call assert_equal(['mad', ''], getline(1, '$'))
  %d
  call setline(1, 'mad')
  call cursor(1, 1)
  call feedkeys("A\<c-x>\<c-t>\<c-n>\<cr>\<esc>", 'tnix')
  call assert_equal(['angry', ''], getline(1, '$'))
  %d
  call setline(1, 'mad')
  call cursor(1, 1)
  call feedkeys("A\<c-x>\<c-t>\<c-n>\<c-n>\<cr>\<esc>", 'tnix')
  call assert_equal(['furious', ''], getline(1, '$'))
  %d
  call setline(1, 'mad')
  call cursor(1, 1)
  call feedkeys("A\<c-x>\<c-t>\<c-n>\<c-n>\<c-n>\<cr>\<esc>", 'tnix')
  call assert_equal(['enraged', ''], getline(1, '$'))
  %d
  call setline(1, 'mad')
  call cursor(1, 1)
  call feedkeys("A\<c-x>\<c-t>\<c-n>\<c-n>\<c-n>\<c-n>\<cr>\<esc>", 'tnix')
  call assert_equal(['mad', ''], getline(1, '$'))
  %d
  call setline(1, 'mad')
  call cursor(1, 1)
  call feedkeys("A\<c-x>\<c-t>\<c-n>\<c-n>\<c-n>\<c-n>\<c-n>\<cr>\<esc>", 'tnix')
  call assert_equal(['mad', ''], getline(1, '$'))
  " Using <c-p> <c-n> when 'complete' is empty
  set complete=
  %d
  call setline(1, 'mad')
  call cursor(1, 1)
  call feedkeys("A\<c-x>\<c-t>\<c-n>\<cr>\<esc>", 'tnix')
  call assert_equal(['angry', ''], getline(1, '$'))
  %d
  call setline(1, 'mad')
  call cursor(1, 1)
  call feedkeys("A\<c-x>\<c-t>\<c-p>\<cr>\<esc>", 'tnix')
  call assert_equal(['mad', ''], getline(1, '$'))
  set complete&

  set thesaurus=
  %d
  call setline(1, 'mad')
  call cursor(1, 1)
  let v:testing = 1
  try
    call feedkeys("A\<c-x>\<c-t>\<esc>", 'tnix')
  catch
    " error sleeps 2 seconds, when v:testing is not set
    let v:testing = 0
  endtry
  call assert_equal(['mad'], getline(1, '$'))
  bw!
endfunc

" Test thesaurus completion with different encodings
func Test_thesaurus_complete_with_encoding()
  call writefile(['angry furious mad enraged'], 'Xthesaurus', 'D')
  set thesaurus=Xthesaurus
  for e in ['latin1', 'utf-8']
    exe 'set encoding=' .. e
    new
    call setline(1, 'mad')
    call cursor(1, 1)
    call feedkeys("A\<c-x>\<c-t>\<cr>\<esc>", 'tnix')
    call assert_equal(['mad', ''], getline(1, '$'))
    bw!
  endfor
  set thesaurus=
endfunc

" Test 'thesaurusfunc'
func MyThesaurus(findstart, base)
  let mythesaurus = [
        \ #{word: "happy",
        \   synonyms: "cheerful,blissful,flying high,looking good,peppy"},
        \ #{word: "kind",
        \   synonyms: "amiable,bleeding-heart,heart in right place"}]
  if a:findstart
    " locate the start of the word
    let line = getline('.')
    let start = col('.') - 1
    while start > 0 && line[start - 1] =~ '\a'
      let start -= 1
    endwhile
    return start
  else
    " find strings matching with "a:base"
    let res = []
    for w in mythesaurus
      if w.word =~ '^' . a:base
        call add(res, w.word)
        call extend(res, split(w.synonyms, ","))
      endif
    endfor
    return res
  endif
endfunc

func Test_thesaurus_func()
  new
  set thesaurus=notused
  set thesaurusfunc=NotUsed
  setlocal thesaurusfunc=MyThesaurus
  call setline(1, "an ki")
  call cursor(1, 1)
  call feedkeys("A\<c-x>\<c-t>\<c-n>\<cr>\<esc>", 'tnix')
  call assert_equal(['an amiable', ''], getline(1, '$'))

  setlocal thesaurusfunc=NonExistingFunc
  call assert_fails("normal $a\<C-X>\<C-T>", 'E117:')

  setlocal thesaurusfunc=
  set thesaurusfunc=NonExistingFunc
  call assert_fails("normal $a\<C-X>\<C-T>", 'E117:')
  %bw!

  set thesaurusfunc=
  set thesaurus=
endfunc

func Test_edit_CTRL_U()
  " Test 'completefunc'
  new
  " -1, -2 and -3 are special return values
  let g:special=0
  fun! CompleteMonths(findstart, base)
    if a:findstart
      " locate the start of the word
      return g:special
    else
      " find months matching with "a:base"
      let res = []
      for m in split("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec")
        if m =~ '^\c'.a:base
          call add(res, {'word': m, 'abbr': m.' Month', 'icase': 0})
        endif
      endfor
      return {'words': res, 'refresh': 'always'}
    endif
  endfun
  set completefunc=CompleteMonths
  call setline(1, ['', ''])
  call cursor(1, 1)
  call feedkeys("AX\<c-x>\<c-u>\<cr>\<esc>", 'tnix')
  call assert_equal(['X', '', ''], getline(1, '$'))
  %d
  let g:special=-1
  call feedkeys("AX\<c-x>\<c-u>\<cr>\<esc>", 'tnix')
  call assert_equal(['XJan', ''], getline(1, '$'))
  %d
  let g:special=-2
  call feedkeys("AX\<c-x>\<c-u>\<cr>\<esc>", 'tnix')
  call assert_equal(['X', ''], getline(1, '$'))
  %d
  let g:special=-3
  call feedkeys("AX\<c-x>\<c-u>\<cr>\<esc>", 'tnix')
  call assert_equal(['X', ''], getline(1, '$'))
  %d
  let g:special=0
  call feedkeys("AM\<c-x>\<c-u>\<cr>\<esc>", 'tnix')
  call assert_equal(['Mar', ''], getline(1, '$'))
  %d
  call feedkeys("AM\<c-x>\<c-u>\<c-n>\<cr>\<esc>", 'tnix')
  call assert_equal(['May', ''], getline(1, '$'))
  %d
  call feedkeys("AM\<c-x>\<c-u>\<c-n>\<c-n>\<cr>\<esc>", 'tnix')
  call assert_equal(['M', ''], getline(1, '$'))
  delfu CompleteMonths
  %d
  try
    call feedkeys("A\<c-x>\<c-u>", 'tnix')
    call assert_fails(1, 'unknown completion function')
  catch /^Vim\%((\a\+)\)\=:E117/
    call assert_true(1, 'E117 error caught')
  endtry
  set completefunc=
  bw!
endfunc

func Test_edit_completefunc_delete()
  func CompleteFunc(findstart, base)
    if a:findstart == 1
      return col('.') - 1
    endif
    normal dd
    return ['a', 'b']
  endfunc
  new
  set completefunc=CompleteFunc
  call setline(1, ['', 'abcd', ''])
  2d
  call assert_fails("normal 2G$a\<C-X>\<C-U>", 'E565:')
  bwipe!
endfunc


func Test_edit_CTRL_Z()
  " Ctrl-Z when insertmode is not set inserts it literally
  new
  call setline(1, 'abc')
  call feedkeys("A\<c-z>\<esc>", 'tnix')
  call assert_equal(["abc\<c-z>"], getline(1,'$'))
  bw!
  " TODO: How to Test Ctrl-Z in insert mode, e.g. suspend?
endfunc

func Test_edit_DROP()
  CheckFeature dnd
  new
  call setline(1, ['abc def ghi'])
  call cursor(1, 1)
  try
    call feedkeys("i\<Drop>\<Esc>", 'tnix')
    call assert_fails(1, 'Invalid register name')
  catch /^Vim\%((\a\+)\)\=:E353/
    call assert_true(1, 'error caught')
  endtry
  bw!
endfunc

func Test_edit_CTRL_V()
  new
  call setline(1, ['abc'])
  call cursor(2, 1)

  " force some redraws
  set showmode showcmd
  call test_override('char_avail', 1)

  call feedkeys("A\<c-v>\<c-n>\<c-v>\<c-l>\<c-v>\<c-b>\<esc>", 'tnix')
  call assert_equal(["abc\x0e\x0c\x02"], getline(1, '$'))

  if has("rightleft") && exists("+rl")
    set rl
    call setline(1, ['abc'])
    call cursor(2, 1)
    call feedkeys("A\<c-v>\<c-n>\<c-v>\<c-l>\<c-v>\<c-b>\<esc>", 'tnix')
    call assert_equal(["abc\x0e\x0c\x02"], getline(1, '$'))
    set norl
  endif

  set noshowmode showcmd
  call test_override('char_avail', 0)

  " No modifiers should be applied to the char typed using i_CTRL-V_digit.
  call feedkeys(":append\<CR>\<C-V>76c\<C-V>76\<C-F2>\<C-V>u3c0j\<C-V>u3c0\<M-F3>\<CR>.\<CR>", 'tnix')
  call assert_equal('LcL<C-F2>πjπ<M-F3>', getline(2))

  if has('osx')
    " A char with a modifier should not be a valid char for i_CTRL-V_digit.
    call feedkeys("o\<C-V>\<D-j>\<C-V>\<D-1>\<C-V>\<D-o>\<C-V>\<D-x>\<C-V>\<D-u>", 'tnix')
    call assert_equal('<D-j><D-1><D-o><D-x><D-u>', getline(3))
  endif

  bw!
endfunc

func Test_edit_F1()
  CheckFeature quickfix

  " Pressing <f1>
  new
  call feedkeys(":set im\<cr>\<f1>\<c-l>", 'tnix')
  set noinsertmode
  call assert_equal('help', &buftype)
  bw
  bw
endfunc

func Test_edit_F21()
  " Pressing <f21>
  " sends a netbeans command
  CheckFeature netbeans_intg
  new
  " I have no idea what this is supposed to do :)
  call feedkeys("A\<F21>\<F1>\<esc>", 'tnix')
  bw
endfunc

func Test_edit_HOME_END()
  " Test Home/End Keys
  new
  set foldopen+=hor
  call setline(1, ['abc', 'def'])
  call cursor(1, 1)
  call feedkeys("AX\<Home>Y\<esc>", 'tnix')
  call cursor(2, 1)
  call feedkeys("iZ\<End>Y\<esc>", 'tnix')
  call assert_equal(['YabcX', 'ZdefY'], getline(1, '$'))

  set foldopen-=hor
  bw!
endfunc

func Test_edit_INS()
  " Test for Pressing <Insert>
  new
  call setline(1, ['abc', 'def'])
  call cursor(1, 1)
  call feedkeys("i\<Insert>ZYX>", 'tnix')
  call assert_equal(['ZYX>', 'def'], getline(1, '$'))
  call setline(1, ['abc', 'def'])
  call cursor(1, 1)
  call feedkeys("i\<Insert>Z\<Insert>YX>", 'tnix')
  call assert_equal(['ZYX>bc', 'def'], getline(1, '$'))
  bw!
endfunc

func Test_edit_LEFT_RIGHT()
  " Left, Shift-Left, Right, Shift-Right
  new
  call setline(1, ['abc def ghi', 'ABC DEF GHI', 'ZZZ YYY XXX'])
  let _ww=&ww
  set ww=
  call cursor(2, 1)
  call feedkeys("i\<left>\<esc>", 'tnix')
  call assert_equal([0, 2, 1, 0], getpos('.'))
  " Is this a bug, <s-left> does not respect whichwrap option
  call feedkeys("i\<s-left>\<esc>", 'tnix')
  call assert_equal([0, 1, 8, 0], getpos('.'))
  call feedkeys("i". repeat("\<s-left>", 3). "\<esc>", 'tnix')
  call assert_equal([0, 1, 1, 0], getpos('.'))
  call feedkeys("i\<right>\<esc>", 'tnix')
  call assert_equal([0, 1, 1, 0], getpos('.'))
  call feedkeys("i\<right>\<right>\<esc>", 'tnix')
  call assert_equal([0, 1, 2, 0], getpos('.'))
  call feedkeys("A\<right>\<esc>", 'tnix')
  call assert_equal([0, 1, 11, 0], getpos('.'))
  call feedkeys("A\<s-right>\<esc>", 'tnix')
  call assert_equal([0, 2, 1, 0], getpos('.'))
  call feedkeys("i\<s-right>\<esc>", 'tnix')
  call assert_equal([0, 2, 4, 0], getpos('.'))
  call cursor(3, 11)
  call feedkeys("A\<right>\<esc>", 'tnix')
  call feedkeys("A\<s-right>\<esc>", 'tnix')
  call assert_equal([0, 3, 11, 0], getpos('.'))
  call cursor(2, 11)
  " <S-Right> does not respect 'whichwrap' option
  call feedkeys("A\<s-right>\<esc>", 'tnix')
  call assert_equal([0, 3, 1, 0], getpos('.'))
  " Check motion when 'whichwrap' contains cursor keys for insert mode
  set ww+=[,]
  call cursor(2, 1)
  call feedkeys("i\<left>\<esc>", 'tnix')
  call assert_equal([0, 1, 11, 0], getpos('.'))
  call cursor(2, 11)
  call feedkeys("A\<right>\<esc>", 'tnix')
  call assert_equal([0, 3, 1, 0], getpos('.'))
  call cursor(2, 11)
  call feedkeys("A\<s-right>\<esc>", 'tnix')
  call assert_equal([0, 3, 1, 0], getpos('.'))
  let &ww = _ww
  bw!
endfunc

func Test_edit_MOUSE()
  " This is a simple test, since we're not really using the mouse here
  CheckFeature mouse
  10new
  call setline(1, range(1, 100))
  call cursor(1, 1)
  call assert_equal(1, line('w0'))
  call assert_equal(10, line('w$'))
  set mouse=a
  " One scroll event moves three lines.
  call feedkeys("A\<ScrollWheelDown>\<esc>", 'tnix')
  call assert_equal(4, line('w0'))
  call assert_equal(13, line('w$'))
  " This should move by one page down.
  call feedkeys("A\<S-ScrollWheelDown>\<esc>", 'tnix')
  call assert_equal(14, line('w0'))
  set nostartofline
  " Another page down.
  call feedkeys("A\<C-ScrollWheelDown>\<esc>", 'tnix')
  call assert_equal(24, line('w0'))

  call assert_equal([0, 24, 2, 0], getpos('.'))
  call test_setmouse(4, 3)
  call feedkeys("A\<LeftMouse>\<esc>", 'tnix')
  call assert_equal([0, 27, 2, 0], getpos('.'))
  set mousemodel=extend
  call test_setmouse(5, 3)
  call feedkeys("A\<RightMouse>\<esc>\<esc>", 'tnix')
  call assert_equal([0, 28, 2, 0], getpos('.'))
  set mousemodel&
  call cursor(1, 100)
  norm! zt
  " this should move by a screen up, but when the test
  " is run, it moves up to the top of the buffer...
  call feedkeys("A\<ScrollWheelUp>\<esc>", 'tnix')
  call assert_equal([0, 1, 1, 0], getpos('.'))
  call cursor(1, 30)
  norm! zt
  call feedkeys("A\<S-ScrollWheelUp>\<esc>", 'tnix')
  call assert_equal([0, 1, 1, 0], getpos('.'))
  call cursor(1, 30)
  norm! zt
  call feedkeys("A\<C-ScrollWheelUp>\<esc>", 'tnix')
  call assert_equal([0, 1, 1, 0], getpos('.'))
  %d
  call setline(1, repeat(["12345678901234567890"], 100))
  call cursor(2, 1)
  call feedkeys("A\<ScrollWheelRight>\<esc>", 'tnix')
  call assert_equal([0, 2, 20, 0], getpos('.'))
  call feedkeys("A\<ScrollWheelLeft>\<esc>", 'tnix')
  call assert_equal([0, 2, 20, 0], getpos('.'))
  call feedkeys("A\<S-ScrollWheelRight>\<esc>", 'tnix')
  call assert_equal([0, 2, 20, 0], getpos('.'))
  call feedkeys("A\<S-ScrollWheelLeft>\<esc>", 'tnix')
  call assert_equal([0, 2, 20, 0], getpos('.'))
  call feedkeys("A\<C-ScrollWheelRight>\<esc>", 'tnix')
  call assert_equal([0, 2, 20, 0], getpos('.'))
  call feedkeys("A\<C-ScrollWheelLeft>\<esc>", 'tnix')
  call assert_equal([0, 2, 20, 0], getpos('.'))
  set mouse& startofline
  bw!
endfunc

func Test_edit_PAGEUP_PAGEDOWN()
  10new
  call setline(1, repeat(['abc def ghi'], 30))
  call cursor(1, 1)
  call feedkeys("i\<PageDown>\<esc>", 'tnix')
  call assert_equal([0, 9, 1, 0], getpos('.'))
  call feedkeys("i\<PageDown>\<esc>", 'tnix')
  call assert_equal([0, 17, 1, 0], getpos('.'))
  call feedkeys("i\<PageDown>\<esc>", 'tnix')
  call assert_equal([0, 25, 1, 0], getpos('.'))
  call feedkeys("i\<PageDown>\<esc>", 'tnix')
  call assert_equal([0, 30, 1, 0], getpos('.'))
  call feedkeys("i\<PageDown>\<esc>", 'tnix')
  call assert_equal([0, 30, 1, 0], getpos('.'))
  call feedkeys("A\<PageUp>\<esc>", 'tnix')
  call assert_equal([0, 29, 1, 0], getpos('.'))
  call feedkeys("A\<PageUp>\<esc>", 'tnix')
  call assert_equal([0, 21, 1, 0], getpos('.'))
  call feedkeys("A\<PageUp>\<esc>", 'tnix')
  call assert_equal([0, 13, 1, 0], getpos('.'))
  call feedkeys("A\<PageUp>\<esc>", 'tnix')
  call assert_equal([0, 5, 1, 0], getpos('.'))
  call feedkeys("A\<PageUp>\<esc>", 'tnix')
  call assert_equal([0, 5, 11, 0], getpos('.'))
  " <S-Up> is the same as <PageUp>
  " <S-Down> is the same as <PageDown>
  call cursor(1, 1)
  call feedkeys("i\<S-Down>\<esc>", 'tnix')
  call assert_equal([0, 9, 1, 0], getpos('.'))
  call feedkeys("i\<S-Down>\<esc>", 'tnix')
  call assert_equal([0, 17, 1, 0], getpos('.'))
  call feedkeys("i\<S-Down>\<esc>", 'tnix')
  call assert_equal([0, 25, 1, 0], getpos('.'))
  call feedkeys("i\<S-Down>\<esc>", 'tnix')
  call assert_equal([0, 30, 1, 0], getpos('.'))
  call feedkeys("i\<S-Down>\<esc>", 'tnix')
  call assert_equal([0, 30, 1, 0], getpos('.'))
  call feedkeys("A\<S-Up>\<esc>", 'tnix')
  call assert_equal([0, 29, 1, 0], getpos('.'))
  call feedkeys("A\<S-Up>\<esc>", 'tnix')
  call assert_equal([0, 21, 1, 0], getpos('.'))
  call feedkeys("A\<S-Up>\<esc>", 'tnix')
  call assert_equal([0, 13, 1, 0], getpos('.'))
  call feedkeys("A\<S-Up>\<esc>", 'tnix')
  call assert_equal([0, 5, 1, 0], getpos('.'))
  call feedkeys("A\<S-Up>\<esc>", 'tnix')
  call assert_equal([0, 5, 11, 0], getpos('.'))
  set nostartofline
  call cursor(30, 11)
  norm! zt
  call feedkeys("A\<PageUp>\<esc>", 'tnix')
  call assert_equal([0, 29, 11, 0], getpos('.'))
  call feedkeys("A\<PageUp>\<esc>", 'tnix')
  call assert_equal([0, 21, 11, 0], getpos('.'))
  call feedkeys("A\<PageUp>\<esc>", 'tnix')
  call assert_equal([0, 13, 11, 0], getpos('.'))
  call feedkeys("A\<PageUp>\<esc>", 'tnix')
  call assert_equal([0, 5, 11, 0], getpos('.'))
  call feedkeys("A\<PageUp>\<esc>", 'tnix')
  call assert_equal([0, 5, 11, 0], getpos('.'))
  call cursor(1, 1)
  call feedkeys("A\<PageDown>\<esc>", 'tnix')
  call assert_equal([0, 9, 11, 0], getpos('.'))
  call feedkeys("A\<PageDown>\<esc>", 'tnix')
  call assert_equal([0, 17, 11, 0], getpos('.'))
  call feedkeys("A\<PageDown>\<esc>", 'tnix')
  call assert_equal([0, 25, 11, 0], getpos('.'))
  call feedkeys("A\<PageDown>\<esc>", 'tnix')
  call assert_equal([0, 30, 11, 0], getpos('.'))
  call feedkeys("A\<PageDown>\<esc>", 'tnix')
  call assert_equal([0, 30, 11, 0], getpos('.'))
  " <S-Up> is the same as <PageUp>
  " <S-Down> is the same as <PageDown>
  call cursor(30, 11)
  norm! zt
  call feedkeys("A\<S-Up>\<esc>", 'tnix')
  call assert_equal([0, 29, 11, 0], getpos('.'))
  call feedkeys("A\<S-Up>\<esc>", 'tnix')
  call assert_equal([0, 21, 11, 0], getpos('.'))
  call feedkeys("A\<S-Up>\<esc>", 'tnix')
  call assert_equal([0, 13, 11, 0], getpos('.'))
  call feedkeys("A\<S-Up>\<esc>", 'tnix')
  call assert_equal([0, 5, 11, 0], getpos('.'))
  call feedkeys("A\<S-Up>\<esc>", 'tnix')
  call assert_equal([0, 5, 11, 0], getpos('.'))
  call cursor(1, 1)
  call feedkeys("A\<S-Down>\<esc>", 'tnix')
  call assert_equal([0, 9, 11, 0], getpos('.'))
  call feedkeys("A\<S-Down>\<esc>", 'tnix')
  call assert_equal([0, 17, 11, 0], getpos('.'))
  call feedkeys("A\<S-Down>\<esc>", 'tnix')
  call assert_equal([0, 25, 11, 0], getpos('.'))
  call feedkeys("A\<S-Down>\<esc>", 'tnix')
  call assert_equal([0, 30, 11, 0], getpos('.'))
  call feedkeys("A\<S-Down>\<esc>", 'tnix')
  call assert_equal([0, 30, 11, 0], getpos('.'))
  bw!
endfunc

func Test_edit_forbidden()
  new
  " 1) edit in the sandbox is not allowed
  call setline(1, 'a')
  com! Sandbox :sandbox call feedkeys("i\<del>\<esc>", 'tnix')
  call assert_fails(':Sandbox', 'E48:')
  com! Sandbox :sandbox exe "norm! i\<del>"
  call assert_fails(':Sandbox', 'E48:')
  delcom Sandbox
  call assert_equal(['a'], getline(1,'$'))

  " 2) edit with textlock set
  fu! DoIt()
    call feedkeys("i\<del>\<esc>", 'tnix')
  endfu
  au InsertCharPre <buffer> :call DoIt()
  try
    call feedkeys("ix\<esc>", 'tnix')
    call assert_fails(1, 'textlock')
  catch /^Vim\%((\a\+)\)\=:E565/ " catch E565: not allowed here
  endtry
  " TODO: Might be a bug: should x really be inserted here
  call assert_equal(['xa'], getline(1, '$'))
  delfu DoIt
  try
    call feedkeys("ix\<esc>", 'tnix')
    call assert_fails(1, 'unknown function')
  catch /^Vim\%((\a\+)\)\=:E117/ " catch E117: unknown function
  endtry
  au! InsertCharPre

  " 3) edit when completion is shown
  fun! Complete(findstart, base)
    if a:findstart
      return col('.')
    else
      call feedkeys("i\<del>\<esc>", 'tnix')
      return []
    endif
  endfun
  set completefunc=Complete
  try
    call feedkeys("i\<c-x>\<c-u>\<esc>", 'tnix')
    call assert_fails(1, 'change in complete function')
  catch /^Vim\%((\a\+)\)\=:E565/ " catch E565
  endtry
  delfu Complete
  set completefunc=

  if has("rightleft") && exists("+fkmap")
    " 4) 'R' when 'fkmap' and 'revins' is set.
    set revins fkmap
    try
      normal Ri
      call assert_fails(1, "R with 'fkmap' and 'ri' set")
    catch
    finally
      set norevins nofkmap
    endtry
  endif
  bw!
endfunc

func Test_edit_rightleft()
  " Cursor in rightleft mode moves differently
  CheckFeature rightleft
  call NewWindow(10, 20)
  call setline(1, ['abc', 'def', 'ghi'])
  call cursor(1, 2)
  set rightleft
  " Screen looks as expected
  let lines = ScreenLines([1, 4], winwidth(0))
  let expect = [
        \"                 cba",
        \"                 fed",
        \"                 ihg",
        \"                   ~"]
  call assert_equal(join(expect, "\n"), join(lines, "\n"))
  " 2) right moves to the left
  call feedkeys("i\<right>\<esc>x", 'txin')
  call assert_equal(['bc', 'def', 'ghi'], getline(1,'$'))
  call cursor(1, 2)
  call feedkeys("i\<s-right>\<esc>", 'txin')
  call cursor(1, 2)
  call feedkeys("i\<c-right>\<esc>", 'txin')
  " Screen looks as expected
  let lines = ScreenLines([1, 4], winwidth(0))
  let expect = [
        \"                  cb",
        \"                 fed",
        \"                 ihg",
        \"                   ~"]
  call assert_equal(join(expect, "\n"), join(lines, "\n"))
  " 2) left moves to the right
  call setline(1, ['abc', 'def', 'ghi'])
  call cursor(1, 2)
  call feedkeys("i\<left>\<esc>x", 'txin')
  call assert_equal(['ac', 'def', 'ghi'], getline(1,'$'))
  call cursor(1, 2)
  call feedkeys("i\<s-left>\<esc>", 'txin')
  call cursor(1, 2)
  call feedkeys("i\<c-left>\<esc>", 'txin')
  " Screen looks as expected
  let lines = ScreenLines([1, 4], winwidth(0))
  let expect = [
        \"                  ca",
        \"                 fed",
        \"                 ihg",
        \"                   ~"]
  call assert_equal(join(expect, "\n"), join(lines, "\n"))
  %d _
  call test_override('redraw_flag', 1)
  call test_override('char_avail', 1)
  call feedkeys("a\<C-V>x41", "xt")
  redraw!
  call assert_equal(repeat(' ', 19) .. 'A', Screenline(1))
  call test_override('ALL', 0)
  set norightleft
  bw!
endfunc

func Test_edit_complete_very_long_name()
  " Long directory names only work on Unix.
  CheckUnix

  let dirname = getcwd() . "/Xlongdir"
  let longdirname = dirname . repeat('/' . repeat('d', 255), 4)
  try
    call mkdir(longdirname, 'pR')
  catch /E739:/
    " Long directory name probably not supported.
    call delete(dirname, 'rf')
    return
  endtry

  " Try to get the Vim window position before setting 'columns', so that we can
  " move the window back to where it was.
  let winposx = getwinposx()
  let winposy = getwinposy()

  if winposx >= 0 && winposy >= 0 && !has('gui_running')
    " We did get the window position, but xterm may report the wrong numbers.
    " Move the window to the reported position and compute any offset.
    exe 'winpos ' . winposx . ' ' . winposy
    sleep 100m
    let x = getwinposx()
    if x >= 0
      let winposx += winposx - x
    endif
    let y = getwinposy()
    if y >= 0
      let winposy += winposy - y
    endif
  endif

  let save_columns = &columns
  " Need at least about 1100 columns to reproduce the problem.
  set columns=2000
  set noswapfile

  let longfilename = longdirname . '/' . repeat('a', 255)
  call writefile(['Totum', 'Table'], longfilename)
  new
  exe "next Xnofile " . longfilename
  exe "normal iT\<C-N>"

  bwipe!
  exe 'bwipe! ' . longfilename
  let &columns = save_columns
  if winposx >= 0 && winposy >= 0
    exe 'winpos ' . winposx . ' ' . winposy
  endif
  set swapfile&
endfunc

func Test_edit_backtick()
  next a\`b c
  call assert_equal('a`b', expand('%'))
  next
  call assert_equal('c', expand('%'))
  call assert_equal('a\`b c', expand('##'))
endfunc

func Test_edit_quit()
  edit foo.txt
  split
  new
  call setline(1, 'hello')
  3wincmd w
  redraw!
  call assert_fails('1q', 'E37:')
  bwipe! foo.txt
  only
endfunc

func Test_edit_alt()
  " Keeping the cursor line didn't happen when the first line has indent.
  new
  call setline(1, ['  one', 'two', 'three'])
  w XAltFile
  $
  call assert_equal(3, line('.'))
  e Xother
  e #
  call assert_equal(3, line('.'))

  bwipe XAltFile
  call delete('XAltFile')
endfunc

func Test_edit_InsertLeave()
  new
  au InsertLeavePre * let g:did_au_pre = 1
  au InsertLeave * let g:did_au = 1
  let g:did_au_pre = 0
  let g:did_au = 0
  call feedkeys("afoo\<Esc>", 'tx')
  call assert_equal(1, g:did_au_pre)
  call assert_equal(1, g:did_au)
  call assert_equal('foo', getline(1))

  let g:did_au_pre = 0
  let g:did_au = 0
  call feedkeys("Sbar\<C-C>", 'tx')
  call assert_equal(1, g:did_au_pre)
  call assert_equal(0, g:did_au)
  call assert_equal('bar', getline(1))

  inoremap x xx<Esc>
  let g:did_au_pre = 0
  let g:did_au = 0
  call feedkeys("Saax", 'tx')
  call assert_equal(1, g:did_au_pre)
  call assert_equal(1, g:did_au)
  call assert_equal('aaxx', getline(1))

  inoremap x xx<C-C>
  let g:did_au_pre = 0
  let g:did_au = 0
  call feedkeys("Sbbx", 'tx')
  call assert_equal(1, g:did_au_pre)
  call assert_equal(0, g:did_au)
  call assert_equal('bbxx', getline(1))

  bwipe!
  au! InsertLeave InsertLeavePre
  iunmap x
endfunc

func Test_edit_InsertLeave_undo()
  new XtestUndo
  set undofile
  au InsertLeave * wall
  exe "normal ofoo\<Esc>"
  call assert_equal(2, line('$'))
  normal u
  call assert_equal(1, line('$'))

  bwipe!
  au! InsertLeave
  call delete('XtestUndo')
  call delete(undofile('XtestUndo'))
  set undofile&
endfunc

" Test for inserting characters using CTRL-V followed by a number.
func Test_edit_special_chars()
  new

  let t = "o\<C-V>65\<C-V>x42\<C-V>o103 \<C-V>33a\<C-V>xfg\<C-V>o78\<Esc>"

  exe "normal " . t
  call assert_equal("ABC !a\<C-O>g\<C-G>8", getline(2))

  close!
endfunc

func Test_edit_startinsert()
  new
  set backspace+=start
  call setline(1, 'foobar')
  call feedkeys("A\<C-U>\<Esc>", 'xt')
  call assert_equal('', getline(1))

  call setline(1, 'foobar')
  call feedkeys(":startinsert!\<CR>\<C-U>\<Esc>", 'xt')
  call assert_equal('', getline(1))

  set backspace&
  bwipe!
endfunc

" Test for :startreplace and :startgreplace
func Test_edit_startreplace()
  new
  call setline(1, 'abc')
  call feedkeys("l:startreplace\<CR>xyz\e", 'xt')
  call assert_equal('axyz', getline(1))
  call feedkeys("0:startreplace!\<CR>abc\e", 'xt')
  call assert_equal('axyzabc', getline(1))
  call setline(1, "a\tb")
  call feedkeys("0l:startgreplace\<CR>xyz\e", 'xt')
  call assert_equal("axyz\tb", getline(1))
  call feedkeys("0i\<C-R>=execute('startreplace')\<CR>12\e", 'xt')
  call assert_equal("12axyz\tb", getline(1))
  close!
endfunc

func Test_edit_noesckeys()
  CheckNotGui
  new

  " <Left> moves cursor when 'esckeys' is set
  exe "set t_kl=\<Esc>OD"
  set esckeys
  call feedkeys("axyz\<Esc>ODX", "xt")
  call assert_equal("xyXz", getline(1))

  " <Left> exits Insert mode when 'esckeys' is off
  set noesckeys
  call setline(1, '')
  call feedkeys("axyz\<Esc>ODX", "xt")
  call assert_equal(["DX", "xyz"], getline(1, 2))

  bwipe!
  set esckeys
endfunc

" Test for running an invalid ex command in insert mode using CTRL-O
func Test_edit_ctrl_o_invalid_cmd()
  new
  set showmode showcmd
  " Avoid a sleep of 3 seconds. Zero might have side effects.
  call test_override('ui_delay', 50)
  let caught_e492 = 0
  try
    call feedkeys("i\<C-O>:invalid\<CR>abc\<Esc>", "xt")
  catch /E492:/
    let caught_e492 = 1
  endtry
  call assert_equal(1, caught_e492)
  call assert_equal('abc', getline(1))
  set showmode& showcmd&
  call test_override('ui_delay', 0)
  close!
endfunc

" Test for editing a file with a very long name
func Test_edit_illegal_filename()
  CheckEnglish
  new
  redir => msg
  exe 'edit ' . repeat('f', 5000)
  redir END
  call assert_match("Illegal file name$", split(msg, "\n")[0])
  close!
endfunc

" Test for editing a directory
func Test_edit_is_a_directory()
  CheckEnglish
  let dirname = getcwd() . "/Xeditdir"
  call mkdir(dirname, 'p')

  new
  redir => msg
  exe 'edit' dirname
  redir END
  call assert_match("is a directory$", split(msg, "\n")[0])
  bwipe!

  let dirname .= '/'

  new
  redir => msg
  exe 'edit' dirname
  redir END
  call assert_match("is a directory$", split(msg, "\n")[0])
  bwipe!

  call delete(dirname, 'rf')
endfunc

" Test for editing a file using invalid file encoding
func Test_edit_invalid_encoding()
  CheckEnglish
  call writefile([], 'Xinvfile', 'D')
  redir => msg
  new ++enc=axbyc Xinvfile
  redir END
  call assert_match('\[NOT converted\]', msg)
  close!
endfunc

" Test for the "charconvert" option
func Test_edit_charconvert()
  CheckEnglish
  call writefile(['one', 'two'], 'Xccfile', 'D')

  " set 'charconvert' to a non-existing function
  set charconvert=NonExitingFunc()
  new
  let caught_e117 = v:false
  try
    redir => msg
    edit ++enc=axbyc Xccfile
  catch /E117:/
    let caught_e117 = v:true
  finally
    redir END
  endtry
  call assert_true(caught_e117)
  call assert_equal(['one', 'two'], getline(1, '$'))
  call assert_match("Conversion with 'charconvert' failed", msg)
  close!
  set charconvert&

  " 'charconvert' function doesn't create an output file
  func Cconv1()
  endfunc
  set charconvert=Cconv1()
  new
  redir => msg
  edit ++enc=axbyc Xccfile
  redir END
  call assert_equal(['one', 'two'], getline(1, '$'))
  call assert_match("can't read output of 'charconvert'", msg)
  close!
  delfunc Cconv1
  set charconvert&

  " 'charconvert' function to convert to upper case
  func Cconv2()
    let data = readfile(v:fname_in)
    call map(data, 'toupper(v:val)')
    call writefile(data, v:fname_out)
  endfunc
  set charconvert=Cconv2()
  new Xccfile
  write ++enc=ucase Xccfile1
  call assert_equal(['ONE', 'TWO'], readfile('Xccfile1'))
  call delete('Xccfile1')
  close!
  delfunc Cconv2
  set charconvert&

  " 'charconvert' function removes the input file
  func Cconv3()
    call delete(v:fname_in)
  endfunc
  set charconvert=Cconv3()
  new
  call assert_fails('edit ++enc=lcase Xccfile', 'E202:')
  call assert_equal([''], getline(1, '$'))
  close!
  delfunc Cconv3
  set charconvert&
endfunc

" Test for editing a file without read permission
func Test_edit_file_no_read_perm()
  CheckUnix
  CheckNotRoot

  call writefile(['one', 'two'], 'Xnrpfile', 'D')
  call setfperm('Xnrpfile', '-w-------')
  new
  redir => msg
  edit Xnrpfile
  redir END
  call assert_equal(1, &readonly)
  call assert_equal([''], getline(1, '$'))
  call assert_match('\[Permission Denied\]', msg)
  close!
endfunc

" Using :edit without leaving 'insertmode' should not cause Insert mode to be
" re-entered immediately after <C-L>
func Test_edit_insertmode_ex_edit()
  CheckRunVimInTerminal

  let lines =<< trim END
    set insertmode noruler
    inoremap <C-B> <Cmd>edit Xfoo<CR>
  END
  call writefile(lines, 'Xtest_edit_insertmode_ex_edit', 'D')

  let buf = RunVimInTerminal('-S Xtest_edit_insertmode_ex_edit', #{rows: 6, wait_for_ruler: 0})
  " Somehow when using valgrind "INSERT" does not show up unless we send
  " something to the terminal.
  for i in range(30)
    if term_getline(buf, 6) =~ 'INSERT'
      break
    endif
    call term_sendkeys(buf, "-")
    sleep 100m
  endfor
  call WaitForAssert({-> assert_match('^-- INSERT --\s*$', term_getline(buf, 6))})
  call term_sendkeys(buf, "\<C-B>\<C-L>")
  call WaitForAssert({-> assert_notmatch('^-- INSERT --\s*$', term_getline(buf, 6))})

  " clean up
  call StopVimInTerminal(buf)
endfunc

" Pressing escape in 'insertmode' should beep
" FIXME: Execute this later, when using valgrind it makes the next test
" Test_edit_insertmode_ex_edit() fail.
func Test_z_edit_insertmode_esc_beeps()
  new
  set insertmode
  call assert_beeps("call feedkeys(\"one\<Esc>\", 'xt')")
  set insertmode&
  " unsupported "CTRL-G l" command should beep in insert mode.
  call assert_beeps("normal i\<C-G>l")
  bwipe!
endfunc

" Test for 'hkmap' and 'hkmapp'
func Test_edit_hkmap()
  CheckFeature rightleft
  if has('win32') && !has('gui')
    throw 'Skipped: fails on the MS-Windows terminal version'
  endif
  new

  set revins hkmap
  let str = 'abcdefghijklmnopqrstuvwxyz'
  let str ..= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
  let str ..= '`/'',.;'
  call feedkeys('i' .. str, 'xt')
  let expected = "óõú,.;"
  let expected ..= "ZYXWVUTSRQPONMLKJIHGFEDCBA"
  let expected ..= "æèñ'äåàãø/ôíîöêìçïéòë÷âáðù"
  call assert_equal(expected, getline(1))

  %d
  set revins hkmap hkmapp
  let str = 'abcdefghijklmnopqrstuvwxyz'
  let str ..= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
  call feedkeys('i' .. str, 'xt')
  let expected = "õYXWVUTSRQóOïíLKJIHGFEDêBA"
  let expected ..= "öòXùåèúæø'ôñðîì÷çéäâóǟãëáà"
  call assert_equal(expected, getline(1))

  set revins& hkmap& hkmapp&
  close!
endfunc

" Test for 'allowrevins' and using CTRL-_ in insert mode
func Test_edit_allowrevins()
  CheckFeature rightleft
  new
  set allowrevins
  call feedkeys("iABC\<C-_>DEF\<C-_>GHI", 'xt')
  call assert_equal('ABCFEDGHI', getline(1))
  set allowrevins&
  close!
endfunc

" Test for inserting a register in insert mode using CTRL-R
func Test_edit_insert_reg()
  new
  let g:Line = ''
  func SaveFirstLine()
    let g:Line = Screenline(1)
    return 'r'
  endfunc
  inoremap <expr> <buffer> <F2> SaveFirstLine()
  call test_override('redraw_flag', 1)
  call test_override('char_avail', 1)
  let @r = 'sample'
  call feedkeys("a\<C-R>=SaveFirstLine()\<CR>", "xt")
  call assert_equal('"', g:Line)
  call test_override('ALL', 0)
  close!
endfunc

" Test for positioning cursor after CTRL-R expression failed
func Test_edit_ctrl_r_failed()
  CheckRunVimInTerminal

  let buf = RunVimInTerminal('', #{rows: 6, cols: 60})

  " trying to insert a dictionary produces an error
  call term_sendkeys(buf, "i\<C-R>={}\<CR>")

  " ending Insert mode should put the cursor back on the ':'
  call term_sendkeys(buf, ":\<Esc>")
  call VerifyScreenDump(buf, 'Test_edit_ctlr_r_failed_1', {})

  call StopVimInTerminal(buf)
endfunc

" When a character is inserted at the last position of the last line in a
" window, the window contents should be scrolled one line up. If the top line
" is part of a fold, then the entire fold should be scrolled up.
func Test_edit_lastline_scroll()
  new
  let h = winheight(0)
  let lines = ['one', 'two', 'three']
  let lines += repeat(['vim'], h - 4)
  call setline(1, lines)
  call setline(h, repeat('x', winwidth(0) - 1))
  call feedkeys("GAx", 'xt')
  redraw!
  call assert_equal(h - 1, winline())
  call assert_equal(2, line('w0'))

  " scroll with a fold
  1,2fold
  normal gg
  call setline(h + 1, repeat('x', winwidth(0) - 1))
  call feedkeys("GAx", 'xt')
  redraw!
  call assert_equal(h - 1, winline())
  call assert_equal(3, line('w0'))

  close!
endfunc

func Test_edit_browse()
  " in the GUI this opens a file picker, we only test the terminal behavior
  CheckNotGui

  " ":browse xxx" checks for the FileExplorer augroup and assumes editing "."
  " works then.
  augroup FileExplorer
    au!
  augroup END

  " When the USE_FNAME_CASE is defined this used to cause a crash.
  browse enew
  bwipe!

  browse split
  bwipe!
endfunc

func Test_read_invalid()
  set encoding=latin1
  " This was not properly checking for going past the end.
  call assert_fails('r`=', 'E484')
  set encoding=utf-8
endfunc

" Test for the 'revins' option
func Test_edit_revins()
  CheckFeature rightleft
  new
  set revins
  exe "normal! ione\ttwo three"
  call assert_equal("eerht owt\teno", getline(1))
  call setline(1, "one\ttwo three")
  normal! gg$bi a
  call assert_equal("one\ttwo a three", getline(1))
  exe "normal! $bi\<BS>\<BS>"
  call assert_equal("one\ttwo a ree", getline(1))
  exe "normal! 0wi\<C-W>"
  call assert_equal("one\t a ree", getline(1))
  exe "normal! 0wi\<C-U>"
  call assert_equal("one\t ", getline(1))
  " newline in insert mode starts at the end of the line
  call setline(1, 'one two three')
  exe "normal! wi\nfour"
  call assert_equal(['one two three', 'ruof'], getline(1, '$'))
  set revins&
  bw!
endfunc

" Test for getting the character of the line below after "p"
func Test_edit_put_CTRL_E()
  set encoding=latin1
  new
  let @" = ''
  sil! norm orggRx
  sil! norm pr
  call assert_equal(['r', 'r'], getline(1, 2))
  bwipe!
  set encoding=utf-8
endfunc

" Test toggling of input method. See :help i_CTRL-^
func Test_edit_CTRL_hat()
  CheckFeature xim

  " FIXME: test fails with Motif GUI.
  "        test also fails when running in the GUI.
  CheckFeature gui_gtk
  CheckNotGui

  new

  call assert_equal(0, &iminsert)
  call feedkeys("i\<C-^>", 'xt')
  call assert_equal(2, &iminsert)
  call feedkeys("i\<C-^>", 'xt')
  call assert_equal(0, &iminsert)

  bwipe!
endfunc

" Weird long file name was going over the end of NameBuff
func Test_edit_overlong_file_name()
  CheckUnix

  file 0000000000000000000000000000
  file %%%%%%%%%%%%%%%%%%%%%%%%%%
  file %%%%%%
  set readonly
  set ls=2

  redraw!
  set noreadonly ls&
  bwipe!
endfunc

func Test_edit_shift_bs()
  CheckMSWindows

  " FIXME: this works interactively, but the test fails
  throw 'Skipped: Shift-Backspace Test not working correctly :('

  " Need to run this in Win32 Terminal, do not use CheckRunVimInTerminal
  if !has("terminal")
    return
  endif

  " Shift Backspace should work like Backspace in insert mode
  let lines =<< trim END
    call setline(1, ['abc'])
  END
  call writefile(lines, 'Xtest_edit_shift_bs', 'D')

  let buf = RunVimInTerminal('-S Xtest_edit_shift_bs', #{rows: 3})
  call term_sendkeys(buf, "A\<S-BS>-\<esc>")
  call TermWait(buf, 50)
  call assert_equal('ab-', term_getline(buf, 1))

  " clean up
  call StopVimInTerminal(buf)
endfunc

" vim: shiftwidth=2 sts=2 expandtab