" Test for :mksession, :mkview and :loadview in latin1 encoding

set encoding=latin1
scriptencoding latin1

CheckFeature mksession

" Test for storing global and local argument list in a session file
" This one must be done first.
func Test__mksession_arglocal()
  enew | only
  n a b c
  new
  arglocal
  mksession! Xtest_mks.out

  %bwipe!
  %argdelete
  argglobal
  source Xtest_mks.out
  call assert_equal(2, winnr('$'))
  call assert_equal(2, arglistid(1))
  call assert_equal(0, arglistid(2))

  %bwipe!
  %argdelete
  argglobal
  call delete('Xtest_mks.out')
endfunc

func Test_mksession_arglocal_localdir()
  argglobal
  %argdelete

  call mkdir('Xa', 'R')
  call writefile(['This is Xb'], 'Xa/Xb.txt', 'D')
  let olddir = getcwd()
  let oldargs = argv()

  for tabpage in [v:false, v:true]
    let msg = tabpage ? 'tabpage-local' : 'window-local'

    exe tabpage ? 'tabnew' : 'botright new'
    exe tabpage ? 'tcd Xa' : 'lcd Xa'
    let localdir = getcwd()
    arglocal
    $argadd Xb.txt
    let localargs = argv()
    exe tabpage ? 'tabprev' : 'wincmd p'
    call assert_equal(olddir, getcwd(), msg)
    call assert_equal(oldargs, argv(), msg)
    mksession! Xtest_mks_localdir.out
    exe tabpage ? '+tabclose' : '$close'
    bwipe! Xa/Xb.txt

    source Xtest_mks_localdir.out
    exe tabpage ? 'tabnext' : 'wincmd b'
    call assert_equal(localdir, getcwd(), msg)
    call assert_equal(localargs, argv(), msg)
    $argument
    call assert_equal('This is Xb', getline(1), msg)

    bwipe!
    call assert_equal(olddir, getcwd(), msg)
    call assert_equal(oldargs, argv(), msg)
    call delete('Xtest_mks_localdir.out')
  endfor
endfunc

func Test_mksession()
  tabnew
  let wrap_save = &wrap
  set sessionoptions=buffers splitbelow fileencoding=latin1
  defer execute('set sessionoptions& splitbelow&')
  call setline(1, [
    \   'start:',
    \   'no multibyte chAracter',
    \   '	one leaDing tab',
    \   '    four leadinG spaces',
    \   'two		consecutive tabs',
    \   'two	tabs	in one line',
    \   'one ä multibyteCharacter',
    \   'aä Ä  two multiByte characters',
    \   'Aäöü  three mulTibyte characters',
    \   'short line',
    \ ])
  let tmpfile = 'Xtemp'
  exec 'w! ' . tmpfile
  /^start:
  set wrap
  vsplit
  norm! j16|
  split
  norm! j16|
  split
  norm! j16|
  split
  norm! j8|
  split
  norm! j8|
  split
  norm! j16|
  split
  norm! j16|
  split
  norm! j16|
  split
  norm! j$
  wincmd l

  set nowrap
  /^start:
  norm! j16|3zl
  split
  norm! j016|3zl
  split
  norm! j016|3zl
  split
  norm! j08|3zl
  split
  norm! j08|3zl
  split
  norm! j016|3zl
  split
  norm! j016|3zl
  split
  norm! j016|3zl
  split
  call wincol()
  mksession! Xtest_mks.out
  let li = filter(readfile('Xtest_mks.out'), 'v:val =~# "\\(^ *normal! [0$]\\|^ *exe ''normal!\\)"')
  let expected = [
    \   '  normal! 016|',
    \   '  normal! 016|',
    \   '  normal! 016|',
    \   '  normal! 08|',
    \   '  normal! 08|',
    \   '  normal! 016|',
    \   '  normal! 016|',
    \   '  normal! 016|',
    \   '  normal! $',
    \   "    exe 'normal! ' .. c .. '|zs' .. 16 .. '|'",
    \   "    normal! 016|",
    \   "    exe 'normal! ' .. c .. '|zs' .. 16 .. '|'",
    \   "    normal! 016|",
    \   "    exe 'normal! ' .. c .. '|zs' .. 16 .. '|'",
    \   "    normal! 016|",
    \   "    exe 'normal! ' .. c .. '|zs' .. 8 .. '|'",
    \   "    normal! 08|",
    \   "    exe 'normal! ' .. c .. '|zs' .. 8 .. '|'",
    \   "    normal! 08|",
    \   "    exe 'normal! ' .. c .. '|zs' .. 16 .. '|'",
    \   "    normal! 016|",
    \   "    exe 'normal! ' .. c .. '|zs' .. 16 .. '|'",
    \   "    normal! 016|",
    \   "    exe 'normal! ' .. c .. '|zs' .. 16 .. '|'",
    \   "    normal! 016|",
    \   "    exe 'normal! ' .. c .. '|zs' .. 16 .. '|'",
    \   "    normal! 016|"
    \ ]
  call assert_equal(expected, li)
  tabclose!

  call delete('Xtest_mks.out')
  call delete(tmpfile)
  let &wrap = wrap_save
endfunc

def Test_mksession_skiprtp()
  mksession! Xtest_mks.out
  var found_rtp = 0
  var found_pp = 0
  for line in readfile('Xtest_mks.out')
    if line =~ 'set runtimepath'
      found_rtp += 1
    endif
    if line =~ 'set packpath'
      found_pp += 1
    endif
  endfor
  assert_equal(1, found_rtp)
  assert_equal(1, found_pp)
  delete('Xtest_mks.out')

  set sessionoptions+=skiprtp
  mksession! Xtest_mks.out
  var found = 0
  for line in readfile('Xtest_mks.out')
    if line =~ 'set \(runtimepath\|packpath\)'
      found = 1
      break
    endif
  endfor
  assert_equal(0, found)
  delete('Xtest_mks.out')
  set sessionoptions&
enddef

func Test_mksession_winheight()
  new
  set winheight=10
  defer execute('set winheight&')
  set winminheight=2
  defer execute('set winminheight&')
  mksession! Xtest_mks.out
  source Xtest_mks.out

  call delete('Xtest_mks.out')
endfunc

func Test_mksession_large_winheight()
  set winheight=999
  defer execute('set winheight&')
  mksession! Xtest_mks_winheight.out
  set winheight&
  source Xtest_mks_winheight.out
  call delete('Xtest_mks_winheight.out')
endfunc

func Test_mksession_zero_winheight()
  set winminheight=0
  defer execute('set winminheight&')
  edit SomeFile
  split
  wincmd _
  mksession! Xtest_mks_zero
  set winminheight&
  let text = readfile('Xtest_mks_zero')->join()
  call delete('Xtest_mks_zero')
  close
  " check there is no divide by zero
  call assert_notmatch('/ 0[^0-9]', text)
endfunc

func Test_mksession_rtp()
  " TODO: fix problem with backslashes on Win32
  CheckNotMSWindows

  new
  let _rtp=&rtp
  " Make a real long (invalid) runtimepath value,
  " that should exceed PATH_MAX (hopefully)
  let newrtp=&rtp.',~'.repeat('/foobar', 1000)
  let newrtp.=",".expand("$HOME")."/.vim"
  let &rtp=newrtp

  " determine expected value
  let expected=split(&rtp, ',')
  let expected = map(expected, '"legacy set runtimepath+=".v:val')
  let expected = ['legacy set runtimepath='] + expected
  let expected = map(expected, {v,w -> substitute(w, $HOME, "~", "g")})

  mksession! Xtest_mks.out
  let &rtp=_rtp
  let li = filter(readfile('Xtest_mks.out'), 'v:val =~# "runtimepath"')
  call assert_equal(expected, li)

  call delete('Xtest_mks.out')
endfunc

func Test_mksession_arglist()
  %argdel
  next file1 file2 file3 file4
  new
  next | next
  mksession! Xtest_mks.out
  source Xtest_mks.out
  call assert_equal(['file1', 'file2', 'file3', 'file4'], argv())
  call assert_equal(2, argidx())
  wincmd w
  call assert_equal(0, argidx())

  call delete('Xtest_mks.out')
  enew | only
  argdel *
endfunc

func Test_mksession_one_buffer_two_windows()
  set splitbelow
  defer execute('set splitbelow&')

  edit Xtest1
  new Xtest2
  split
  mksession! Xtest_mks.out
  let lines = readfile('Xtest_mks.out')
  let count1 = 0
  let count2 = 0
  let count2buf = 0
  let bufexists = 0
  for line in lines
    if line =~ 'edit \f*Xtest1$'
      let count1 += 1
    endif
    if line =~ 'edit \f\{-}Xtest2'
      let count2 += 1
    endif
    if line =~ 'buffer \f\{-}Xtest2'
      let count2buf += 1
    endif
    if line =~ 'bufexists(fnamemodify(.*, ":p")'
      let bufexists += 1
    endif
  endfor
  call assert_equal(1, count1, 'Xtest1 count')
  call assert_equal(2, count2, 'Xtest2 count')
  call assert_equal(2, count2buf, 'Xtest2 buffer count')
  call assert_equal(2, bufexists)

  close
  bwipe!
  call delete('Xtest_mks.out')
endfunc

func Test_mksession_lcd_multiple_tabs()
  tabnew
  tabnew
  lcd .
  tabfirst
  lcd .
  mksession! Xtest_mks.out
  tabonly
  source Xtest_mks.out
  call assert_true(haslocaldir(), 'Tab 1 localdir')
  tabnext 2
  call assert_true(!haslocaldir(), 'Tab 2 localdir')
  tabnext 3
  call assert_true(haslocaldir(), 'Tab 3 localdir')
  call delete('Xtest_mks.out')
endfunc

" Test for tabpage-local directory
func Test_mksession_tcd_multiple_tabs()
  let save_cwd = getcwd()
  call mkdir('Xtopdir')
  cd Xtopdir
  call mkdir('Xtabdir1')
  call mkdir('Xtabdir2')
  call mkdir('Xtabdir3')
  call mkdir('Xwindir1')
  call mkdir('Xwindir2')
  call mkdir('Xwindir3')
  tcd Xtabdir1
  botright new
  wincmd t
  lcd ../Xwindir1
  tabnew
  tcd ../Xtabdir2
  botright new
  lcd ../Xwindir2
  tabnew
  tcd ../Xtabdir3
  botright new
  lcd ../Xwindir3
  tabfirst
  1wincmd w
  mksession! Xtest_mks.out
  only | tabonly
  source Xtest_mks.out
  call assert_equal('Xtabdir1', fnamemodify(getcwd(-1, 1), ':t'))
  call assert_equal('Xwindir1', fnamemodify(getcwd(1, 1), ':t'))
  call assert_equal('Xtabdir1', fnamemodify(getcwd(2, 1), ':t'))
  call assert_equal('Xtabdir2', fnamemodify(getcwd(-1, 2), ':t'))
  call assert_equal('Xtabdir2', fnamemodify(getcwd(1, 2), ':t'))
  call assert_equal('Xwindir2', fnamemodify(getcwd(2, 2), ':t'))
  call assert_equal('Xtabdir3', fnamemodify(getcwd(-1, 3), ':t'))
  call assert_equal('Xtabdir3', fnamemodify(getcwd(1, 3), ':t'))
  call assert_equal('Xwindir3', fnamemodify(getcwd(2, 3), ':t'))
  %bwipe
  call chdir(save_cwd)
  call delete("Xtopdir", "rf")
endfunc

func Test_mksession_blank_tabs()
  tabnew
  tabnew
  tabnew
  tabnext 3
  mksession! Xtest_mks.out
  tabnew
  tabnew
  tabnext 2
  source Xtest_mks.out
  call assert_equal(4, tabpagenr('$'), 'session restore should restore number of tabs')
  call assert_equal(3, tabpagenr(), 'session restore should restore the active tab')
  call delete('Xtest_mks.out')
endfunc

func Test_mksession_buffer_count()
  argglobal
  %argdelete

  set hidden

  " Edit exactly three files in the current session.
  %bwipe!
  e Xfoo | tabe Xbar | tabe Xbaz
  tabdo write
  mksession! Xtest_mks.out

  " Verify that loading the session does not create additional buffers.
  %bwipe!
  source Xtest_mks.out
  call assert_equal(3, len(getbufinfo()))

  " Clean up.
  call delete('Xfoo')
  call delete('Xbar')
  call delete('Xbaz')
  call delete('Xtest_mks.out')
  %bwipe!
  set hidden&
endfunc

func Test_mksession_buffer_order()
  %bwipe!
  e Xfoo | e Xbar | e Xbaz | e Xqux
  bufdo write
  mksession! Xtest_mks.out

  " Verify that loading the session preserves order of buffers
  %bwipe!
  source Xtest_mks.out

  let s:buf_info = getbufinfo()
  call assert_true(s:buf_info[0]['name'] =~# 'Xfoo$')
  call assert_true(s:buf_info[1]['name'] =~# 'Xbar$')
  call assert_true(s:buf_info[2]['name'] =~# 'Xbaz$')
  call assert_true(s:buf_info[3]['name'] =~# 'Xqux$')

  " Clean up.
  call delete('Xfoo')
  call delete('Xbar')
  call delete('Xbaz')
  call delete('Xqux')
  call delete('Xtest_mks.out')
  %bwipe!
endfunc

if has('extra_search')

func Test_mksession_hlsearch()
  set hlsearch
  mksession! Xtest_mks.out
  nohlsearch
  source Xtest_mks.out
  call assert_equal(1, v:hlsearch, 'session should restore search highlighting state')
  nohlsearch
  mksession! Xtest_mks.out
  source Xtest_mks.out
  call assert_equal(0, v:hlsearch, 'session should restore search highlighting state')
  call delete('Xtest_mks.out')
endfunc

endif


func Test_mksession_blank_windows()
  split
  split
  split
  3 wincmd w
  mksession! Xtest_mks.out
  split
  split
  2 wincmd w
  source Xtest_mks.out
  call assert_equal(4, winnr('$'), 'session restore should restore number of windows')
  call assert_equal(3, winnr(), 'session restore should restore the active window')
  call delete('Xtest_mks.out')
endfunc

func Test_mksession_terminal_shell()
  CheckFeature terminal
  CheckFeature quickfix

  terminal
  mksession! Xtest_mks.out
  let lines = readfile('Xtest_mks.out')
  let term_cmd = ''
  for line in lines
    if line =~ '^exe '':terminal'
      let term_cmd = line
    elseif line =~ 'badd.*' . &shell
      call assert_report('unexpected shell line: ' . line)
    endif
  endfor
  call assert_match('exe '':terminal ++curwin ++cols='' \.\. ((&columns \* \d\+ + \d\+) \/ \d\+) \.\. '' ++rows='' \.\. ((&lines \* \d\+ + \d\+) \/ \d\+)\s.*$', term_cmd)

  call StopShellInTerminal(bufnr('%'))
  call delete('Xtest_mks.out')
endfunc

func Test_mksession_terminal_no_restore_cmdarg()
  CheckFeature terminal

  terminal ++norestore
  mksession! Xtest_mks.out
  let lines = readfile('Xtest_mks.out')
  let term_cmd = ''
  for line in lines
    if line =~ '^exe '':terminal'
      call assert_report('session must not restore terminal')
    endif
  endfor

  call StopShellInTerminal(bufnr('%'))
  call delete('Xtest_mks.out')
endfunc

func Test_mksession_terminal_no_restore_funcarg()
  CheckFeature terminal

  let buf = Run_shell_in_terminal({'norestore': 1})
  mksession! Xtest_mks.out
  let lines = readfile('Xtest_mks.out')
  let term_cmd = ''
  for line in lines
    if line =~ '^exe '':terminal'
      call assert_report('session must not restore terminal')
    endif
  endfor

  call StopShellInTerminal(buf)
  call delete('Xtest_mks.out')
endfunc

func Test_mksession_terminal_no_restore_func()
  CheckFeature terminal

  terminal
  call term_setrestore(bufnr('%'), 'NONE')
  mksession! Xtest_mks.out
  let lines = readfile('Xtest_mks.out')
  let term_cmd = ''
  for line in lines
    if line =~ '^exe '':terminal'
      call assert_report('session must not restore terminal')
    endif
  endfor

  call StopShellInTerminal(bufnr('%'))
  call delete('Xtest_mks.out')
endfunc

func Test_mksession_terminal_no_ssop()
  CheckFeature terminal

  terminal
  set sessionoptions-=terminal
  mksession! Xtest_mks.out
  let lines = readfile('Xtest_mks.out')
  let term_cmd = ''
  for line in lines
    if line =~ '^exe '':terminal'
      call assert_report('session must not restore terminal')
    endif
  endfor

  call StopShellInTerminal(bufnr('%'))
  call delete('Xtest_mks.out')
  set sessionoptions&
endfunc

func Test_mksession_terminal_restore_other()
  CheckFeature terminal
  CheckFeature quickfix

  terminal
  eval bufnr('%')->term_setrestore('other')
  mksession! Xtest_mks.out
  let lines = readfile('Xtest_mks.out')
  let term_cmd = ''
  for line in lines
    if line =~ '^exe '':terminal'
      let term_cmd = line
    endif
  endfor
  call assert_match('exe '':terminal ++curwin ++cols='' \.\. ((&columns \* \d\+ + \d\+) \/ \d\+) \.\. '' ++rows='' \.\. ((&lines \* \d\+ + \d\+) \/ \d\+).*other', term_cmd)

  call StopShellInTerminal(bufnr('%'))
  call delete('Xtest_mks.out')
endfunc

func Test_mksession_terminal_shared_windows()
  CheckFeature terminal

  terminal
  let term_buf = bufnr()
  new
  execute "buffer" term_buf
  mksession! Xtest_mks.out

  let lines = readfile('Xtest_mks.out')
  let found_creation = 0
  let found_use = 0
  let found_var = 0

  for line in lines
    if line =~ '^exe '':terminal'
      let found_creation = 1
      call assert_match('exe '':terminal ++curwin ++cols='' \.\. ((&columns \* \d\+ + \d\+) \/ \d\+) \.\. '' ++rows='' \.\. ((&lines \* \d\+ + \d\+) \/ \d\+)', line)
    elseif line =~ $"^var term_buf_{term_buf}: number = bufnr()$"
      let found_var = 1
    elseif line =~ "^execute 'buffer ' . term_buf_" . term_buf . "$"
      let found_use = 1
    endif
  endfor

  call assert_true(found_creation && found_use && found_var)

  call StopShellInTerminal(term_buf)
  call delete('Xtest_mks.out')
endfunc

func Test_mksession_terminal_shell_command()
  CheckFeature terminal

  set sessionoptions+=terminal
  terminal
  let term_buf = bufnr()
  eval term_buf->term_setrestore('echo HELLO_WORLD')
  mksession! Xtest_mks.out

  call StopShellInTerminal(term_buf)

  source Xtest_mks.out

  let restored_buf = bufnr()
  call assert_equal('terminal', getbufvar(restored_buf, '&buftype'))
  call WaitForAssert({-> assert_match(
        \ 'HELLO_WORLD',
        \ term_getline(restored_buf, 1))})
  call WaitForAssert({-> assert_match(
        \ 'finished',
        \ term_getstatus(restored_buf))})

  bwipe!
  call delete('Xtest_mks.out')
endfunc

" Plain :terminal stores no command (tl_command == NULL), so nothing is
" written after the ':terminal ++curwin ...' line.  Restoring must still
" produce a running shell terminal.
func Test_mksession_terminal_default_restore()
  CheckFeature terminal

  terminal
  let term_buf = bufnr()
  mksession! Xtest_mks.out
  call StopShellInTerminal(term_buf)
  %bwipe!

  source Xtest_mks.out
  let restored = bufnr()
  call assert_equal('terminal', getbufvar(restored, '&buftype'))
  call WaitForAssert({-> assert_match('running', term_getstatus(restored))})
  call StopShellInTerminal(restored)

  %bwipe!
  call delete('Xtest_mks.out')
endfunc

func Test_mkview_terminal_windows()
  CheckFeature terminal

  " create two windows on the same terminal to check this is handled OK
  terminal
  let term_buf = bufnr()
  exe 'sbuf ' .. term_buf
  mkview! Xtestview

  call StopShellInTerminal(term_buf)
  call delete('Xtestview')
endfunc

func Test_mkview_open_folds()
  enew!

  call append(0, ['a', 'b', 'c'])
  1,3fold
  write! Xtestfile

  call assert_notequal(-1, foldclosed(1))
  call assert_notequal(-1, foldclosed(2))
  call assert_notequal(-1, foldclosed(3))

  " Save the view with folds closed
  mkview! Xtestview

  " zR affects 'foldlevel', make sure the option is applied after the folds
  " have been recreated.
  " Open folds to ensure they get closed when restoring the view
  normal zR

  call assert_equal(-1, foldclosed(1))
  call assert_equal(-1, foldclosed(2))
  call assert_equal(-1, foldclosed(3))

  source Xtestview

  call assert_notequal(-1, foldclosed(1))
  call assert_notequal(-1, foldclosed(2))
  call assert_notequal(-1, foldclosed(3))

  call delete('Xtestview')
  call delete('Xtestfile')
  %bwipe
endfunc

func Test_mkview_no_balt()
  edit Xtestfile1
  edit Xtestfile2

  mkview! Xtestview
  bdelete Xtestfile1

  source Xtestview
  call assert_equal(0, buflisted('Xtestfile1'))

  call delete('Xtestview')
  %bwipe
endfunc

func Test_mksession_no_balt()
  edit Xtestfile1
  edit Xtestfile2

  bdelete Xtestfile1
  mksession! Xtestview

  source Xtestview
  call assert_equal(0, buflisted('Xtestfile1'))

  call delete('Xtestview')
  %bwipe
endfunc

" Test :mkview with a file argument.
func Test_mkview_file()
  " Create a view with line number and a fold.
  help :mkview
  set number
  norm! V}zf0
  let pos = getpos('.')
  let linefoldclosed1 = foldclosed('.')
  mkview! Xview
  set nonumber
  norm! zrj
  " We can close the help window, as mkview with a file name should
  " generate a command to edit the file.
  helpclose

  source Xview
  call assert_equal(1, &number)
  call assert_match('\*:mkview\*$', getline('.'))
  call assert_equal(pos, getpos('.'))
  call assert_equal(linefoldclosed1, foldclosed('.'))

  " Creating a view again with the same file name should fail (file
  " already exists). But with a !, the previous view should be
  " overwritten without error.
  help :loadview
  call assert_fails('mkview Xview', 'E189:')
  call assert_match('\*:loadview\*$', getline('.'))
  mkview! Xview
  call assert_match('\*:loadview\*$', getline('.'))

  call delete('Xview')
  bwipe
endfunc

" Test :mkview and :loadview with a custom 'viewdir'.
func Test_mkview_loadview_with_viewdir()
  set viewdir=Xviewdir

  help :mkview
  set number
  norm! V}zf
  let pos = getpos('.')
  let linefoldclosed1 = foldclosed('.')
  mkview 1
  set nonumber
  norm! zrj

  loadview 1

  " The directory Xviewdir/ should have been created and the view
  " should be stored in that directory.
  call assert_equal('Xviewdir/' .
        \           substitute(
        \             substitute(
        \               expand('%:p'), '/', '=+', 'g'), ':', '=-', 'g') . '=1.vim',
        \           glob('Xviewdir/*'))
  call assert_equal(1, &number)
  call assert_match('\*:mkview\*$', getline('.'))
  call assert_equal(pos, getpos('.'))
  call assert_equal(linefoldclosed1, foldclosed('.'))

  call delete('Xviewdir', 'rf')
  set viewdir&
  helpclose
endfunc

func Test_mkview_no_file_name()
  new
  " :mkview or :mkview {nr} should fail in an unnamed buffer.
  call assert_fails('mkview', 'E32:')
  call assert_fails('mkview 1', 'E32:')

  " :mkview {file} should succeed in an unnamed buffer.
  mkview Xview
  help
  source Xview
  call assert_equal('', bufname('%'))

  call delete('Xview')
  %bwipe
endfunc

func Test_mkview_loadview_jumplist()
  set viewdir=Xviewdir
  au BufWinLeave * silent mkview
  au BufWinEnter * silent loadview

  edit Xfile1
  call setline(1, ['a', 'bbbbbbb', 'c'])
  normal j3l
  call assert_equal([2, 4], getcurpos()[1:2])
  write

  edit Xfile2
  call setline(1, ['d', 'eeeeeee', 'f'])
  normal j5l
  call assert_equal([2, 6], getcurpos()[1:2])
  write

  edit Xfile3
  call setline(1, ['g', 'h', 'iiiii'])
  normal jj3l
  call assert_equal([3, 4], getcurpos()[1:2])
  write

  edit Xfile1
  call assert_equal([2, 4], getcurpos()[1:2])
  edit Xfile2
  call assert_equal([2, 6], getcurpos()[1:2])
  edit Xfile3
  call assert_equal([3, 4], getcurpos()[1:2])

  exe "normal \<C-O>"
  call assert_equal('Xfile2', expand('%'))
  call assert_equal([2, 6], getcurpos()[1:2])
  exe "normal \<C-O>"
  call assert_equal('Xfile1', expand('%'))
  call assert_equal([2, 4], getcurpos()[1:2])

  au! BufWinLeave
  au! BufWinEnter
  bwipe!
  call delete('Xviewdir', 'rf')
  call delete('Xfile1')
  call delete('Xfile2')
  call delete('Xfile3')
  set viewdir&
endfunc

" A clean session (one empty buffer, one window, and one tab) should not
" set any error messages when sourced because no commands should fail.
func Test_mksession_no_errmsg()
  let v:errmsg = ''
  %bwipe!
  mksession! Xtest_mks.out
  source Xtest_mks.out
  call assert_equal('', v:errmsg)
  call delete('Xtest_mks.out')
endfunc

func Test_mksession_quote_in_filename()
  " only Unix can handle this weird filename
  CheckUnix

  let v:errmsg = ''
  %bwipe!
  split another
  split x'y\"z
  mksession! Xtest_mks_quoted.out
  %bwipe!
  source Xtest_mks_quoted.out
  call assert_true(bufexists("x'y\"z"))

  %bwipe!
  call delete('Xtest_mks_quoted.out')
endfunc

" Test for storing global variables in a session file
func Test_mksession_globals()
  set sessionoptions+=globals

  " create different global variables
  let g:Global_string = "Sun is shining\r\n"
  let g:Global_count = 100
  let g:Global_pi = 3.14
  let g:Global_neg_float = -2.68

  mksession! Xtest_mks.out

  unlet g:Global_string
  unlet g:Global_count
  unlet g:Global_pi
  unlet g:Global_neg_float

  source Xtest_mks.out
  call assert_equal("Sun is shining\r\n", g:Global_string)
  call assert_equal(100, g:Global_count)
  call assert_equal(3.14, g:Global_pi)
  call assert_equal(-2.68, g:Global_neg_float)

  unlet g:Global_string
  unlet g:Global_count
  unlet g:Global_pi
  unlet g:Global_neg_float
  call delete('Xtest_mks.out')
  set sessionoptions&
endfunc

" Test for changing backslash to forward slash in filenames
func Test_mksession_slash()
  if exists('+shellslash')
    throw 'Skipped: cannot use backslash in file name'
  endif
  enew
  %bwipe!
  e a\\b\\c
  mksession! Xtest_mks1.out
  set sessionoptions+=slash
  mksession! Xtest_mks2.out

  %bwipe!
  source Xtest_mks1.out
  call assert_equal('a\b\c', bufname(''))
  %bwipe!
  source Xtest_mks2.out
  call assert_equal('a/b/c', bufname(''))

  %bwipe!
  call delete('Xtest_mks1.out')
  call delete('Xtest_mks2.out')
  set sessionoptions&
endfunc

" Test for changing directory to the session file directory
func Test_mksession_sesdir()
  let save_cwd = getcwd()
  call mkdir('Xproj')
  mksession! Xproj/Xtest_mks1.out
  set sessionoptions-=curdir
  set sessionoptions+=sesdir
  mksession! Xproj/Xtest_mks2.out

  source Xproj/Xtest_mks1.out
  call assert_equal('testdir', fnamemodify(getcwd(), ':t'))
  source Xproj/Xtest_mks2.out
  call assert_equal('Xproj', fnamemodify(getcwd(), ':t'))
  call chdir(save_cwd)
  %bwipe

  set sessionoptions&
  call delete('Xproj', 'rf')
endfunc

" Test for saving and restoring the tab-local working directory when there is
" only a single tab and 'tabpages' is not in 'sessionoptions'.
func Test_mksession_tcd_single_tabs()
  only | tabonly

  let save_cwd = getcwd()
  set sessionoptions-=tabpages
  set sessionoptions+=curdir
  call mkdir('Xtopdir1')
  call mkdir('Xtopdir2')

  " There are two tab pages, the current one has local cwd set to 'Xtopdir2'.
  exec 'tcd ' .. save_cwd .. '/Xtopdir1'
  tabnew
  exec 'tcd ' .. save_cwd .. '/Xtopdir2'
  mksession! Xtest_tcd_single

  source Xtest_tcd_single
  call assert_equal(2, haslocaldir())
  call assert_equal('Xtopdir2', fnamemodify(getcwd(-1, 0), ':t'))
  %bwipe

  set sessionoptions&
  call chdir(save_cwd)
  call delete('Xtopdir1', 'rf')
  call delete('Xtopdir2', 'rf')
endfunc

" Test for storing the 'lines' and 'columns' settings
func Test_mksession_resize()
  mksession! Xtest_mks1.out
  set sessionoptions+=resize
  mksession! Xtest_mks2.out

  let lines = readfile('Xtest_mks1.out')
  let found_resize = v:false
  for line in lines
    if line =~ '^set lines='
      let found_resize = v:true
      break
    endif
  endfor
  call assert_false(found_resize)
  let lines = readfile('Xtest_mks2.out')
  let found_resize = v:false
  for line in lines
    if line =~ '^set lines='
      let found_resize = v:true
      break
    endif
  endfor
  call assert_true(found_resize)

  call delete('Xtest_mks1.out')
  call delete('Xtest_mks2.out')
  set sessionoptions&
endfunc

" Test for mksession with a named scratch buffer
func Test_mksession_scratch()
  enew | only
  file Xscratch
  set buftype=nofile
  mksession! Xtest_mks.out
  %bwipe
  source Xtest_mks.out
  call assert_equal('Xscratch', bufname(''))
  call assert_equal('nofile', &buftype)
  %bwipe
  call delete('Xtest_mks.out')
endfunc

" Test for mksession with fold options
func Test_mksession_foldopt()
  set sessionoptions-=options
  set sessionoptions+=folds
  new
  setlocal foldenable
  setlocal foldmethod=expr
  setlocal foldmarker=<<<,>>>
  setlocal foldignore=%
  setlocal foldlevel=2
  setlocal foldminlines=10
  setlocal foldnestmax=15
  mksession! Xtest_mks.out
  close
  %bwipe

  source Xtest_mks.out
  call assert_true(&foldenable)
  call assert_equal('expr', &foldmethod)
  call assert_equal('<<<,>>>', &foldmarker)
  call assert_equal('%', &foldignore)
  call assert_equal(2, &foldlevel)
  call assert_equal(10, &foldminlines)
  call assert_equal(15, &foldnestmax)

  close
  %bwipe
  set sessionoptions&
  call delete('Xtest_mks.out')
endfunc

" Test for mksession with "help" but not "options" in 'sessionoptions'
func Test_mksession_help_noopt()
  set sessionoptions-=options
  set sessionoptions+=help
  help
  let fname = expand('%')
  mksession! Xtest_mks.out
  bwipe

  source Xtest_mks.out
  call assert_equal('help', &buftype)
  call assert_equal('help', &filetype)
  call assert_equal(fname, expand('%'))
  call assert_false(&modifiable)
  call assert_true(&readonly)

  helpclose
  help index
  let fname = expand('%')
  mksession! Xtest_mks.out
  bwipe

  source Xtest_mks.out
  call assert_equal('help', &buftype)
  call assert_equal(fname, expand('%'))

  call delete('Xtest_mks.out')
  set sessionoptions&
endfunc

" Test for mksession with window position
func Test_mksession_winpos()
  " Only applicable in GUI Vim
  CheckGui
  " Window position does not work in gtk4 gui
  CheckNotFeature gui_gtk4

  set sessionoptions+=winpos
  mksession! Xtest_mks.out
  let found_winpos = v:false
  let lines = readfile('Xtest_mks.out')
  for line in lines
    if line =~ '^winpos '
      let found_winpos = v:true
      break
    endif
  endfor
  call assert_true(found_winpos)
  call delete('Xtest_mks.out')
  set sessionoptions&
endfunc

" Test for mksession without options restores winminheight
func Test_mksession_winminheight()
  set winheight& winwidth& winminheight& winminwidth&
  set sessionoptions-=options
  defer execute('set sessionoptions&')
  split
  mksession! Xtest_mks.out
  defer delete('Xtest_mks.out')
  let found_restore = 0
  let lines = readfile('Xtest_mks.out')
  for line in lines
    if line =~ '= save_winmin\(width\|height\)'
      let found_restore += 1
    endif
  endfor
  call assert_equal(2, found_restore)
  " Test with multiple tabpages
  tab split | split | tab split | split
  call assert_equal(3, tabpagenr('$'))
  mksession! Xtest_mks.out
  tabclose | tabclose | close
  call assert_equal(1, tabpagenr('$'))
  set winheight=2 winminheight=2 winwidth=2 winminwidth=2
  defer execute('set winheight& winwidth& winminheight& winminwidth&')
  source Xtest_mks.out
  call assert_equal(3, tabpagenr('$'))
  call assert_equal([2, 2], [&winminheight, &winminwidth])
  tabclose | tabclose | close
endfunc

" Test for mksession with and without options restores shortmess
func Test_mksession_shortmess()
  " Without options
  set sessionoptions-=options
  split
  mksession! Xtest_mks.out
  let found_save = 0
  let found_restore = 0
  let lines = readfile('Xtest_mks.out')
  for line in lines
    let line = trim(line)

    if line ==# 'shortmess_save = &shortmess'
      let found_save += 1
    endif

    if found_save !=# 0 && line ==# '&shortmess = shortmess_save'
      let found_restore += 1
    endif
  endfor
  call assert_equal(1, found_save)
  call assert_equal(1, found_restore)
  call delete('Xtest_mks.out')
  close
  set sessionoptions&

  " With options
  set sessionoptions+=options
  split
  mksession! Xtest_mks.out
  let found_restore = 0
  let lines = readfile('Xtest_mks.out')
  for line in lines
    if line =~# '\(var \)\@<!shortmess_save'
      let found_restore += 1
    endif
  endfor
  call assert_equal(0, found_restore)
  call delete('Xtest_mks.out')
  close
  set sessionoptions&
endfunc

" Test that when Vim loading session has 'A' in 'shortmess' it does not
" complain about an existing swapfile.
func Test_mksession_shortmess_with_A()
  edit Xtestfile
  write
  let fname = swapname('%')
  let cont = readblob(fname)
  set sessionoptions-=options
  mksession Xtestsession
  bwipe!

  " Recreate the swap file to pretend the file is being edited
  call writefile(cont, fname, 'D')
  set shortmess+=A
  source Xtestsession

  set shortmess&
  set sessionoptions&
  call delete('Xtestsession')
  call delete('Xtestfile')
endfunc

" Test for mksession with 'compatible' option
func Test_mksession_compatible()
  mksession! Xtest_mks1.out
  set compatible
  mksession! Xtest_mks2.out
  set nocp

  let test_success = v:false
  let lines = readfile('Xtest_mks1.out')
  for line in lines
    if line =~ '^if &cp | set nocp | endif'
      let test_success = v:true
      break
    endif
  endfor
  call assert_true(test_success)

  let test_success = v:false
  let lines = readfile('Xtest_mks2.out')
  for line in lines
    if line =~ '^if !&cp | set cp | endif'
      let test_success = v:true
      break
    endif
  endfor
  call assert_true(test_success)

  call delete('Xtest_mks1.out')
  call delete('Xtest_mks2.out')
  set compatible&
  set sessionoptions&
endfunc

func s:ClearMappings()
  mapclear
  omapclear
  mapclear!
  lmapclear
  tmapclear
endfunc

func Test_mkvimrc()
  let entries = [
        \ ['', 'nothing', '<Nop>'],
        \ ['n', 'normal', 'NORMAL<Up>'],
        \ ['v', 'visual', 'VISUAL<S-Down>'],
        \ ['s', 'select', 'SELECT<C-Left>'],
        \ ['x', 'visualonly', 'VISUALONLY<M-Right>'],
        \ ['o', 'operator', 'OPERATOR'],
        \ ['i', 'insert', 'INSERT'],
        \ ['l', 'lang', 'LANG'],
        \ ['c', 'command', 'COMMAND'],
        \ ['t', 'terminal', 'TERMINAL'],
        \ ]
  for entry in entries
    exe entry[0] .. 'map ' .. entry[1] .. ' ' .. entry[2]
  endfor

  mkvimrc Xtestvimrc

  call s:ClearMappings()
  for entry in entries
    call assert_equal('', maparg(entry[1], entry[0]))
  endfor

  source Xtestvimrc

  for entry in entries
    call assert_equal(entry[2], maparg(entry[1], entry[0]))
  endfor

  call s:ClearMappings()

  " the 'pastetoggle', 'wildchar' and 'wildcharm' option values should be
  " stored as key names in the vimrc file
  set pastetoggle=<F5>
  set wildchar=<F6>
  set wildcharm=<F7>
  call assert_fails('mkvimrc Xtestvimrc', 'E189: "Xtestvimrc" exists')
  mkvimrc! Xtestvimrc
  let content = readfile('Xtestvimrc')
  call assert_notequal(-1, index(content, 'legacy set pastetoggle=<F5>'))
  call assert_notequal(-1, index(content, 'set wildchar=<F6>'))
  call assert_notequal(-1, index(content, 'set wildcharm=<F7>'))
  set pastetoggle& wildchar& wildcharm&

  call delete('Xtestvimrc')
endfunc

func Test_scrolloff()
  set sessionoptions+=localoptions
  setlocal so=1 siso=1
  mksession! Xtest_mks.out
  setlocal so=-1 siso=-1
  source Xtest_mks.out
  call assert_equal(1, &l:so)
  call assert_equal(1, &l:siso)
  call delete('Xtest_mks.out')
  setlocal so& siso&
  set sessionoptions&
endfunc

func Test_altfile()
  edit Xone
  split Xtwo
  edit Xtwoalt
  edit #
  wincmd w
  edit Xonealt
  edit #
  mksession! Xtest_altfile
  only
  bwipe Xonealt
  bwipe Xtwoalt
  bwipe!
  source Xtest_altfile
  call assert_equal('Xone', bufname())
  call assert_equal('Xonealt', bufname('#'))
  wincmd w
  call assert_equal('Xtwo', bufname())
  call assert_equal('Xtwoalt', bufname('#'))
  only
  bwipe!
  call delete('Xtest_altfile')
endfunc

" Test for creating views with manual folds
func Test_mkview_manual_fold()
  call writefile(range(1,10), 'Xmkvfile', 'D')
  new Xmkvfile
  " create recursive folds
  5,6fold
  4,7fold
  mkview Xview
  normal zE
  source Xview
  call assert_equal([-1, 4, 4, 4, 4, -1], [foldclosed(3), foldclosed(4),
        \ foldclosed(5), foldclosed(6), foldclosed(7), foldclosed(8)])
  " open one level of fold
  4foldopen
  mkview! Xview
  normal zE
  source Xview
  call assert_equal([-1, -1, 5, 5, -1, -1], [foldclosed(3), foldclosed(4),
        \ foldclosed(5), foldclosed(6), foldclosed(7), foldclosed(8)])
  " open all the folds
  %foldopen!
  mkview! Xview
  normal zE
  source Xview
  call assert_equal([-1, -1, -1, -1, -1, -1], [foldclosed(3), foldclosed(4),
        \ foldclosed(5), foldclosed(6), foldclosed(7), foldclosed(8)])
  call delete('Xview')
  bw!
endfunc

" Test for handling invalid folds within views
func Test_mkview_ignore_invalid_folds()
  call writefile(range(1,10), 'Xmkvfile', 'D')
  new Xmkvfile
  " create some folds
  5,6fold
  4,7fold
  mkview Xview
  normal zE
  " delete lines to make folds invalid
  call deletebufline('', 6, '$')
  source Xview
  call assert_equal([-1, -1, -1, -1, -1, -1], [foldclosed(3), foldclosed(4),
        \ foldclosed(5), foldclosed(6), foldclosed(7), foldclosed(8)])
  call delete('Xview')
  bw!
endfunc

" Test default 'viewdir' value
func Test_mkview_default_home()
  if has('win32')
    " use escape() to handle backslash path separators
    call assert_match('^' .. escape($ORIGHOME, '\') .. '/vimfiles', &viewdir)
  elseif has('unix')
    call assert_match(
          \ '^' .. $ORIGHOME .. '/.vim\|' ..
          \ '^' .. $XDG_CONFIG_HOME .. '/vim'
          \ , &viewdir)
  elseif has('amiga')
    call assert_match('^home:vimfiles', &viewdir)
  elseif has('mac')
    call assert_match('^' .. $VIM .. '/vimfiles', &viewdir)
  endif
endfunc

" Test vim9 expression mappings
func Test_mksession_vim9_expr_mappings()

  CheckFeature packages

  " Create a dummy vim9 plugin
  const base = getcwd() . '/rtdir'
  const root = base . '/pack/test/opt/dummy9'
  call mkdir(root . '/plugin', 'p')
  let plugin_sources =<< trim END
    vim9script
    import autoload 'dummy9.vim'
    nnoremap <expr> dummy-test dummy9.Test() .. "<CR>"
  END
  call writefile(plugin_sources, root . '/plugin/dummy9.vim')

  call mkdir(root . '/autoload', 'p')
  let auto_sources =<< trim END
    vim9script
    const ref_txt = 'Hello from vim9 dummy plugin!'
    export def Test(): string
      writefile([ref_txt], 'XDummyOutput')
      return has("gui_running") ? '' : $':echomsg "{ref_txt}"'
    enddef
  END
  call writefile(auto_sources, root . '/autoload/dummy9.vim')

  " clean up later
  defer delete(base, 'rf')

  " Load and check the plugin
  const ref_txt = 'Hello from vim9 dummy plugin!'
  let orig_packpath = &packpath
  let &packpath .= ',' . base
  let orig_runtimepath = &runtimepath
  packadd dummy9
  defer execute('let &packpath = orig_runtimepath')
  defer execute('let &runtimepath = orig_runtimepath')
  messages clear
  normal dummy-test

  if !has('gui_running')
    call assert_match(ref_txt, execute('messages'), 'No vim9 plugin dummy.Test() execution')
  endif
  call assert_true(filereadable('XDummyOutput'), 'Output file was not created by Vim9 plugin')
  call assert_equal([ref_txt], readfile('XDummyOutput'))
  call delete('XDummyOutput')

  " Create a session file
  mksession! XDummySession.vim
  defer delete('XDummySession.vim')
  call assert_true(filereadable('XDummySession.vim'), 'Session file was not created')

  " Check the session file mappings are operational
  let test_sources =<< trim END
    " load session
    source XDummySession.vim
    " execute vim9 expression mapping
    normal dummy-test
    " on my way
    cq
  END
  call writefile(test_sources, 'XTest.vim', 'D')
  " spawn a new Vim instance to load the session and execute the mapping
  call system(GetVimCommand('XTest.vim'))
  defer delete('XDummyOutput')
  call assert_true(filereadable('XDummyOutput'),
        \ 'Expected output file was not created by Vim9 plugin')
  call assert_equal([ref_txt], readfile('XDummyOutput'))

endfunc

" Test legacy vimscript expression mappings
func Test_mksession_legacy_expr_mappings()

  CheckFeature packages

  " Create a dummy vim9 plugin
  const base = getcwd() . '/rtdir'
  const root = base . '/pack/test/opt/dummy'
  call mkdir(root . '/plugin', 'p')

  " clean up later
  defer delete(base, 'rf')

  let plugin_sources =<< trim END
    nnoremap <expr> dummy-test dummy#Test() . "<CR>"
  END
  call writefile(plugin_sources, root . '/plugin/dummy.vim')

  call mkdir(root . '/autoload', 'p')
  let auto_sources =<< trim END
    const s:ref_txt = 'Hello from good old dummy plugin!'
    func dummy#Test()
      call writefile([s:ref_txt], 'XDummyOutput')
      return has("gui_running") ? '' : $':echomsg "{s:ref_txt}"'
    endfunc
  END
  call writefile(auto_sources, root . '/autoload/dummy.vim')

  " Load and check the plugin
  const ref_txt = 'Hello from good old dummy plugin!'
  let orig_packpath = &packpath
  let &packpath .= ',' . base
  let orig_runtimepath = &runtimepath
  packadd dummy
  defer execute('let &packpath = orig_runtimepath')
  defer execute('let &runtimepath = orig_runtimepath')
  messages clear
  normal dummy-test

  if !has("gui_running")
    call assert_match(ref_txt, execute('messages'), 'No vim9 plugin dummy.Test() execution')
  endif
  call assert_true(filereadable('XDummyOutput'), 'Output file was not created by legacy plugin')
  call assert_equal([ref_txt], readfile('XDummyOutput'))
  call delete('XDummyOutput')

  " Create a session file
  mksession! XDummySession.vim
  defer delete('XDummySession.vim')
  call assert_true(filereadable('XDummySession.vim'), 'Session file was not created')

  " Check the session file mappings are operational
  let test_sources =<< trim END
    " load session
    source XDummySession.vim
    " execute legacy vimscript expression mapping
    normal dummy-test
    " on my way
    cq
  END
  call writefile(test_sources, 'XTest.vim', 'D')
  " spawn a new Vim instance to load the session and execute the mapping
  call system(GetVimCommand('XTest.vim'))
  defer delete('XDummyOutput')
  call assert_true(filereadable('XDummyOutput'),
        \ 'Expected output file was not created by legacy vim plugin')
  call assert_equal([ref_txt], readfile('XDummyOutput'))

endfunc

" Test sessions cursor position management
func Test_mksession_cursor_position()

  " Set windows test scenario
  let files = []
  for i in range(10)
      let file = $'Xfile{i}'
      exe $"{i ? 'split' : 'edit'} {file}"
      call append(0, $"Session file cursor position testing {i}")
      " Force cursor position restoring commands
      setlocal nowrap
      normal dd29zl
      " Check expected position
      call assert_equal([0, 1, 30, 0], getpos('.'), $"Fail to set cursor position for {file}")
      write!
      let files += [file]
  endfor

  " Save session
  mksession! Xtest_curpos

  " Test restoring session
  %bwipe!
  try
      source Xtest_curpos
  catch
      call assert_report("Failure sourcing session file")
  endtry

  " Check cursor position
  for file in files
      exe $"drop {file}"
      call assert_equal([0, 1, 30, 0], getpos('.'), $"Cursor position not restored correctly for {file}")
  endfor

  " Clean up
  call delete('Xtest_curpos')
  for file in files
      call delete(file)
  endfor

  %bwipe
endfunc

" Test sessions global and local mappings
func Test_mksession_localmappings()

  " Create sessions. Mapping execution is tested running a file
  let valid_sessions = [] " keep map info
  let invalid_sessions = [] " do not keep map info
  " localoptions requires a buffer
  setlocal noswapfile
  silent write XDummy
  defer delete('XDummy')

  for option in ["&", "=options", "=localoptions"]
    for global in [0, 1]

      try
        " select options
        exe "set sessionoptions" .. option

        " mapping
        exe "nnoremap" . (global ? " " : " <buffer> ")
              \ . "dummy-test <Cmd>silent write XDummyOutput<CR>"
        let case = $"mapping_{global ? "global" : "local"}_{option}"

        " test mapping
        normal dummy-test
        call assert_true(filereadable("XDummyOutput"), $"Output file was not created by {case}")

        " session
        let sessionfile = "XSession_" . case
        exe $"mksession {sessionfile}"

        if global && option =~ "localoptions"
          let invalid_sessions += [sessionfile]
        else
          let valid_sessions += [sessionfile]
        endif

      finally
        call delete("XDummyOutput")

        " clear mappings
        nmapclear
        nmapclear <buffer>
        set sessionoptions&
      endtry

    endfor
  endfor

  " Check the session files are operational
  for session in valid_sessions

    let test_sources =<< trim eval END
      " load session
      silent source {session}
      " execute legacy vimscript expression mapping
      normal dummy-test
      " on my way
      cq
    END

    call writefile(test_sources, 'XTest.vim')
    call system(GetVimCommand('XTest.vim'))
    call assert_true(filereadable('XDummyOutput'),
          \ $"Expected map not defined in session file {session}")
    call delete('XDummyOutput')
    call delete(session)

  endfor

  for session in invalid_sessions

    let test_sources =<< trim eval END
      " load session
      silent source {session}
      " execute legacy vimscript expression mapping
      normal dummy-test
      " on my way
      cq
    END

    call writefile(test_sources, 'XTest.vim')
    call system(GetVimCommand('XTest.vim'))
    call assert_false(filereadable('XDummyOutput'),
          \ $"Unexpected map defined in session file {session}")
    if filereadable('XDummyOutput')
      call delete('XDummyOutput')
    endif
    call delete(session)
  endfor

endfunc

" Test vim9 script relative auto imports (issue #12641)
func Test_mksession_vim9_relative_auto_import()

  " Dummy vim9 script
  let script_sources =<< trim END
    vim9script
    import autoload './XAuto.vim'
    nnoremap dummy-test <ScriptCmd>XAuto.Test()<CR>
  END
  call writefile(script_sources, 'XScript.vim', 'D')

  let auto_sources =<< trim END
    vim9script
    const ref_txt = 'Hello from vim9 dummy relative auto script!'
    export def Test()
      if !has("gui_running")
        exe $"echomsg '{ref_txt}'"
      endif
      writefile([ref_txt], 'XDummyOutput')
    enddef
  END
  call writefile(auto_sources, 'XAuto.vim', 'D')

  " Source the script
  const ref_txt = 'Hello from vim9 dummy relative auto script!'
  source XScript.vim

  " Execute mapping
  normal dummy-test

  if !has('gui_running')
    call assert_match(ref_txt, execute('messages'), 'No vim9 auto script XAuto.Test() execution')
  endif
  call assert_true(filereadable('XDummyOutput'), 'Output file was not created by Vim9 auto script')
  call assert_equal([ref_txt], readfile('XDummyOutput'))
  call delete('XDummyOutput')

  " Create a session file
  mksession! XDummySession.vim
  defer delete('XDummySession.vim')
  call assert_true(filereadable('XDummySession.vim'), 'Session file was not created')

  " Check the session file mappings are operational
  let test_sources =<< trim END
    " Load session
    source XDummySession.vim
    " Execute mapping
    normal dummy-test
    " on my way
    cq
  END
  call writefile(test_sources, 'XTest.vim', 'D')
  " spawn a new Vim instance to load the session and execute the mapping
  call system(GetVimCommand('XTest.vim'))
  call assert_true(filereadable('XDummyOutput'),
        \ 'Expected output file was not created by Vim9 auto script mapping')
  defer delete('XDummyOutput')
  call assert_equal([ref_txt], readfile('XDummyOutput'))

endfunc

" Test vim9 script avoid homonimous auto imports
func Test_mksession_vim9_duplicate_import()

  " Auto script
  let auto_sources =<< trim END
    vim9script
    const ref_txt = 'Hello from a duplicated vim9 script!'
    export def Test()
      writefile([ref_txt], 'XDummyOutput')
    enddef
  END

  " Dummy vim9 script
  let script_sources =<< trim END
    vim9script
    import autoload './XAuto.vim'
    nnoremap dummy-test <ScriptCmd>XAuto.Test()<CR>
  END

  for i in range(1, 5)
    let dir = $'XDir{i}'
    call mkdir(dir, 'p')
    defer delete(dir, 'rf')

    let autofile = dir . '/XAuto.vim'
    call writefile(auto_sources, autofile, 'D')

    let scriptfile = dir . '/XScript.vim'
    call writefile(script_sources, scriptfile, 'D')
    exe "source " . scriptfile
  endfor

  " Create a session file
  mksession! XDummySession.vim
  defer delete('XDummySession.vim')
  call assert_true(filereadable('XDummySession.vim'), 'Session file was not created')

  " Check there are commented imports in the session file
  let commented_imports = filter(readfile('XDummySession.vim'),
  \ {_, v -> v =~ '^# import autoload'})
  call assert_equal(4, len(commented_imports),
  \ 'Session file does not contain the expected number of commented imports')

  " Check the session file mappings are operational
  const ref_txt = 'Hello from a duplicated vim9 script!'
  let test_sources =<< trim END
    " Load session
    source XDummySession.vim
    " Execute mapping
    normal dummy-test
    " on my way
    cq
  END
  call writefile(test_sources, 'XTest.vim', 'D')
  " spawn a new Vim instance to load the session and execute the mapping
  call system(GetVimCommand('XTest.vim'))
  call assert_true(filereadable('XDummyOutput'),
        \ 'Expected output file was not created by Vim9 auto script mapping')
  defer delete('XDummyOutput')
  call assert_equal([ref_txt], readfile('XDummyOutput'))

endfunc

" Make sure options are marked as legacy in the session file when they are set
" by the user from the command execution interface.  Only applies to string
" options.
func Test_mksession_preserve_option_script_version_set_manually()

  let orig_includeexpr = &includeexpr
  defer execute('let &includeexpr = orig_includeexpr')

  " Using "execute()" to simulate input from the user, rather than the option
  " being set directly by the test script itself.
  execute('set includeexpr=FooDefault(v:fname)')

  " Try a few options, to make sure they all work correctly.
  new
  execute('legacy set includeexpr=FooLegacy(v:fname)')

  new
  execute('vim9 set includeexpr=FooVim9(v:fname)')

  mksession! XDummySession.vim
  defer delete('XDummySession.vim')

  let session_content = readfile('XDummySession.vim')
  " We expect that without an explicit script version specification the command
  " line input is treated as legacy input.
  call assert_notequal(-1, index(session_content,
        \ 'legacy setlocal includeexpr=FooDefault(v:fname)'))
  call assert_notequal(-1, index(session_content,
        \ 'legacy setlocal includeexpr=FooLegacy(v:fname)'))
  call assert_notequal(-1, index(session_content,
        \ 'setlocal includeexpr=FooVim9(v:fname)'))
endfunc

" Make sure options preserve legacy/Vim9 script version when written into the
" session file based on the original script that set those options.  Only
" applies to string options.
func Test_mksession_preserve_option_script_version_set_from_script()

  CheckFeature packages

  const base = getcwd() . '/rtdir'
  " clean up later
  defer delete(base, 'rf')
  let orig_packpath = &packpath
  let &packpath .= ',' . base
  defer execute('let &packpath = orig_packpath')

  " Is used to disable file type plugins.
  let g:Global_run_ftplugins = 1
  defer execute('unlet g:Global_run_ftplugins')

  " We are going to create two simple file type plugins - one Vim9 and one
  " legacy.  Each will just set the "includeexpr" locally in the buffer to some
  " value.
  const root = base . '/pack/test/opt/test_option_script_version'
  call mkdir(root . '/ftplugin', 'p')

  let vim9_ftplugin_sources =<< trim END
    vim9script

    if !get(g:, 'Global_run_ftplugins')
      finish
    endif

    &l:include = 'vim9include'
    &l:includeexpr = 'vim9fn(v:fname, 1)'
  END
  call writefile(vim9_ftplugin_sources, root . '/ftplugin/vim9test_lang.vim')

  let legacy_ftplugin_sources =<< trim END
    if !get(g:, 'Global_run_ftplugins')
      finish
    endif

    setlocal include=legacyimport
    setlocal includeexpr=legacyfn(v:fname,\".\")
  END
  call writefile(legacy_ftplugin_sources, root . '/ftplugin/legacytest_lang.vim')

  let orig_runtimepath = &runtimepath
  packadd test_option_script_version
  defer execute('let &runtimepath = orig_runtimepath')

  " Next we will create two buffers, and set "filetype" in each to match our two
  " file type plugin names.  The expectation is that "includeexpr" will be set
  " to the correct value and a ":legacy" prefix will be used only when the
  " original value was set by a legacy script.
  filetype plugin on
  set filetype=vim9test_lang

  new
  set filetype=legacytest_lang

  mksession! XDummySession1.vim
  defer delete('XDummySession1.vim')

  " Check that the session file indeed contains correctly set "includeexpr"
  " values.
  let session1_content = readfile('XDummySession1.vim')
  call assert_notequal(-1, index(session1_content,
        \ 'setlocal include=vim9include'))
  call assert_notequal(-1, index(session1_content,
        \ 'setlocal includeexpr=vim9fn(v:fname,\ 1)'))
  call assert_notequal(-1, index(session1_content,
        \ 'legacy setlocal include=legacyimport'))
  call assert_notequal(-1, index(session1_content,
        \ 'legacy setlocal includeexpr=legacyfn(v:fname,\".\")'))

  " Now disable our filetype plugins, restore the session file, save session
  " again, and make sure that the options are still stored with correct Vim
  " script version prefixes.

  let g:Global_run_ftplugins = 0
  %bwipe!

  source XDummySession1.vim

  mksession! XDummySession2.vim
  defer delete('XDummySession2.vim')

  " Check that the session file indeed contains correctly set "includeexpr"
  " values.
  let session2_content = readfile('XDummySession2.vim')
  call assert_notequal(-1, index(session2_content,
        \ 'setlocal include=vim9include'))
  call assert_notequal(-1, index(session2_content,
        \ 'setlocal includeexpr=vim9fn(v:fname,\ 1)'))
  call assert_notequal(-1, index(session2_content,
        \ 'legacy setlocal include=legacyimport'))
  call assert_notequal(-1, index(session2_content,
        \ 'legacy setlocal includeexpr=legacyfn(v:fname,\".\")'))
endfunc

" 'winminwidth' restore must not fail when the session's saved 'winwidth' is
" smaller than the sourcing context's 'winminwidth'.
func Test_mksession_winminwidth()
  set winminheight& winminwidth& winheight=2 winwidth=1 sessionoptions-=options
  split
  mksession! Xtest_mks.out
  defer delete('Xtest_mks.out')
  only
  set winheight=2 winminheight=2 winwidth=2 winminwidth=2
  defer execute('set winheight& winwidth& winminheight& winminwidth& sessionoptions&')
  source Xtest_mks.out
  call assert_equal([2, 2], [&winminheight, &winminwidth])
  only
endfunc

" vim: shiftwidth=2 sts=2 expandtab
