" Test 'winfixbuf'

" Find the number of open windows in the current tab
func s:get_windows_count()
  return tabpagewinnr(tabpagenr(), '$')
endfunc

" Create some unnamed buffers.
func s:make_buffers_list()
  enew
  file first
  let l:first = bufnr()

  enew
  file middle
  let l:middle = bufnr()

  enew
  file last
  let l:last = bufnr()

  set winfixbuf

  return [l:first, l:last]
endfunc

" Create some unnamed buffers and add them to an args list
func s:make_args_list()
  let [l:first, l:last] = s:make_buffers_list()

  args! first middle last

  return [l:first, l:last]
endfunc

" Create two buffers and then set the window to 'winfixbuf'
func s:make_buffer_pairs(...)
  let l:reversed = get(a:, 1, 0)

  if l:reversed == 1
    enew
    file original

    set winfixbuf

    enew!
    file other
    let l:other = bufnr()

    return l:other
  endif

  enew
  file other
  let l:other = bufnr()

  enew
  file current

  set winfixbuf

  return l:other
endfunc

" Create 3 quick buffers and set the window to 'winfixbuf'
func s:make_buffer_trio()
  edit first
  let l:first = bufnr()
  edit second
  let l:second = bufnr()

  set winfixbuf

  edit! third
  let l:third = bufnr()

  exe $":buffer! {l:second}"

  return [l:first, l:second, l:third]
endfunc

" Create a location list with at least 2 entries + a 'winfixbuf' window.
func s:make_simple_location_list()
  enew
  file middle
  let l:middle = bufnr()
  call append(0, ["winfix search-term", "another line"])

  enew!
  file first
  let l:first = bufnr()
  call append(0, "first search-term")

  enew!
  file last
  let l:last = bufnr()
  call append(0, "last search-term")

  call setloclist(
  \  0,
  \  [
  \    {
  \      "filename": "first",
  \      "bufnr": l:first,
  \      "lnum": 1,
  \    },
  \    {
  \      "filename": "middle",
  \      "bufnr": l:middle,
  \      "lnum": 1,
  \    },
  \    {
  \      "filename": "middle",
  \      "bufnr": l:middle,
  \      "lnum": 2,
  \    },
  \    {
  \      "filename": "last",
  \      "bufnr": l:last,
  \      "lnum": 1,
  \    },
  \  ]
  \)

  set winfixbuf

  return [l:first, l:middle, l:last]
endfunc

" Create a quickfix with at least 2 entries that are in the current 'winfixbuf' window.
func s:make_simple_quickfix()
  enew
  file current
  let l:current = bufnr()
  call append(0, ["winfix search-term", "another line"])

  enew!
  file first
  let l:first = bufnr()
  call append(0, "first search-term")

  enew!
  file last
  let l:last = bufnr()
  call append(0, "last search-term")

  call setqflist(
  \  [
  \    {
  \      "filename": "first",
  \      "bufnr": l:first,
  \      "lnum": 1,
  \    },
  \    {
  \      "filename": "current",
  \      "bufnr": l:current,
  \      "lnum": 1,
  \    },
  \    {
  \      "filename": "current",
  \      "bufnr": l:current,
  \      "lnum": 2,
  \    },
  \    {
  \      "filename": "last",
  \      "bufnr": l:last,
  \      "lnum": 1,
  \    },
  \  ]
  \)

  set winfixbuf

  return [l:current, l:last]
endfunc

" Create a quickfix with at least 2 entries that are in the current 'winfixbuf' window.
func s:make_quickfix_windows()
  let [l:current, _] = s:make_simple_quickfix()
  exe $"buffer! {l:current}"

  split
  let l:first_window = win_getid()
  exe "normal \<C-w>j"
  let l:winfix_window = win_getid()

  " Open the quickfix in a separate split and go to it
  copen
  let l:quickfix_window = win_getid()

  return [l:first_window, l:winfix_window, l:quickfix_window]
endfunc

" Revert all changes that occurred in any past test
func s:reset_all_buffers()
  %bwipeout!
  set nowinfixbuf

  call setqflist([])
  call setloclist(0, [], 'f')

  delmarks A-Z0-9
endfunc

" Find and set the first quickfix entry that points to `buffer`
func s:set_quickfix_by_buffer(buffer)
  let l:index = 1  " quickfix indices start at 1
  for l:entry in getqflist()
    if l:entry["bufnr"] == a:buffer
      exe $"{l:index} cc"

      return
    endif

    let l:index += 1
  endfor

  echoerr $'No quickfix entry matching {a:buffer} could be found.'
endfunc

" Fail to call :Next on a 'winfixbuf' window unless :Next! is used.
func Test_Next()
  call s:reset_all_buffers()

  let [l:first, _] = s:make_args_list()
  next!

  call assert_fails("Next", "E1513:")
  call assert_notequal(l:first, bufnr())

  Next!
  call assert_equal(l:first, bufnr())
endfunc

" Call :argdo and choose the next available 'nowinfixbuf' window.
func Test_argdo_choose_available_window()
  call s:reset_all_buffers()

  let [_, l:last] = s:make_args_list()

  " Make a split window that is 'nowinfixbuf' but make it the second-to-last
  " window so that :argdo will first try the 'winfixbuf' window, pass over it,
  " and prefer the other 'nowinfixbuf' window, instead.
  "
  " +-------------------+
  " |   'nowinfixbuf'   |
  " +-------------------+
  " |    'winfixbuf'    |  <-- Cursor is here
  " +-------------------+
  split
  let l:nowinfixbuf_window = win_getid()
  " Move to the 'winfixbuf' window now
  exe "normal \<C-w>j"
  let l:winfixbuf_window = win_getid()
  let l:expected_windows = s:get_windows_count()

  argdo echo ''
  call assert_equal(l:nowinfixbuf_window, win_getid())
  call assert_equal(l:last, bufnr())
  call assert_equal(l:expected_windows, s:get_windows_count())
endfunc

" Call :argdo and create a new split window if all available windows are 'winfixbuf'.
func Test_argdo_make_new_window()
  call s:reset_all_buffers()

  let [l:first, l:last] = s:make_args_list()
  let l:current = win_getid()
  let l:current_windows = s:get_windows_count()

  argdo echo ''
  call assert_notequal(l:current, win_getid())
  call assert_equal(l:last, bufnr())
  exe "normal \<C-w>j"
  call assert_equal(l:first, bufnr())
  call assert_equal(l:current_windows + 1, s:get_windows_count())
endfunc

" Fail :argedit but :argedit! is allowed
func Test_argedit()
  call s:reset_all_buffers()

  args! first middle last
  enew
  file first
  let l:first = bufnr()

  enew
  file middle
  let l:middle = bufnr()

  enew
  file last
  let l:last = bufnr()

  set winfixbuf

  let l:current = bufnr()
  call assert_fails("argedit first middle last", "E1513:")
  call assert_equal(l:current, bufnr())

  argedit! first middle last
  call assert_equal(l:first, bufnr())
endfunc

" Fail :arglocal but :arglocal! is allowed
func Test_arglocal()
  call s:reset_all_buffers()

  let l:other = s:make_buffer_pairs()
  let l:current = bufnr()
  argglobal! other
  exe $"buffer! {l:current}"

  call assert_fails("arglocal other", "E1513:")
  call assert_equal(l:current, bufnr())

  arglocal! other
  call assert_equal(l:other, bufnr())
endfunc

" Fail :argglobal but :argglobal! is allowed
func Test_argglobal()
  call s:reset_all_buffers()

  let l:other = s:make_buffer_pairs()
  let l:current = bufnr()

  call assert_fails("argglobal other", "E1513:")
  call assert_equal(l:current, bufnr())

  argglobal! other
  call assert_equal(l:other, bufnr())
endfunc

" Fail :args but :args! is allowed
func Test_args()
  call s:reset_all_buffers()

  let [l:first, _] = s:make_buffers_list()
  let l:current = bufnr()

  call assert_fails("args first middle last", "E1513:")
  call assert_equal(l:current, bufnr())

  args! first middle last
  call assert_equal(l:first, bufnr())
endfunc

" Fail :bNext but :bNext! is allowed
func Test_bNext()
  call s:reset_all_buffers()

  let l:other = s:make_buffer_pairs()
  call assert_fails("bNext", "E1513:")
  let l:current = bufnr()

  call assert_equal(l:current, bufnr())

  bNext!
  call assert_equal(l:other, bufnr())
endfunc

" Allow :badd because it doesn't actually change the current window's buffer
func Test_badd()
  call s:reset_all_buffers()

  call s:make_buffer_pairs()
  let l:current = bufnr()

  badd other
  call assert_equal(l:current, bufnr())
endfunc

" Allow :balt because it doesn't actually change the current window's buffer
func Test_balt()
  call s:reset_all_buffers()

  call s:make_buffer_pairs()
  let l:current = bufnr()

  balt other
  call assert_equal(l:current, bufnr())
endfunc

" Fail :bfirst but :bfirst! is allowed
func Test_bfirst()
  call s:reset_all_buffers()

  let l:other = s:make_buffer_pairs()
  let l:current = bufnr()

  call assert_fails("bfirst", "E1513:")
  call assert_equal(l:current, bufnr())

  bfirst!
  call assert_equal(l:other, bufnr())
endfunc

" Fail :blast but :blast! is allowed
func Test_blast()
  call s:reset_all_buffers()

  let l:other = s:make_buffer_pairs(1)
  bfirst!
  let l:current = bufnr()

  call assert_fails("blast", "E1513:")
  call assert_equal(l:current, bufnr())

  blast!
  call assert_equal(l:other, bufnr())
endfunc

" Fail :bmodified but :bmodified! is allowed
func Test_bmodified()
  call s:reset_all_buffers()

  let l:other = s:make_buffer_pairs()
  let l:current = bufnr()

  exe $"buffer! {l:other}"
  set modified
  exe $"buffer! {l:current}"

  call assert_fails("bmodified", "E1513:")
  call assert_equal(l:current, bufnr())

  bmodified!
  call assert_equal(l:other, bufnr())
endfunc

" Fail :bnext but :bnext! is allowed
func Test_bnext()
  call s:reset_all_buffers()

  let l:other = s:make_buffer_pairs()
  let l:current = bufnr()

  call assert_fails("bnext", "E1513:")
  call assert_equal(l:current, bufnr())

  bnext!
  call assert_equal(l:other, bufnr())
endfunc

" Fail :bprevious but :bprevious! is allowed
func Test_bprevious()
  call s:reset_all_buffers()

  let l:other = s:make_buffer_pairs()
  let l:current = bufnr()

  call assert_fails("bprevious", "E1513:")
  call assert_equal(l:current, bufnr())

  bprevious!
  call assert_equal(l:other, bufnr())
endfunc

" Fail :brewind but :brewind! is allowed
func Test_brewind()
  call s:reset_all_buffers()

  let l:other = s:make_buffer_pairs()
  let l:current = bufnr()

  call assert_fails("brewind", "E1513:")
  call assert_equal(l:current, bufnr())

  brewind!
  call assert_equal(l:other, bufnr())
endfunc

" Fail :browse edit but :browse edit! is allowed
func Test_browse_edit_fail()
  " A GUI dialog may stall the test.
  CheckNotGui

  call s:reset_all_buffers()

  let l:other = s:make_buffer_pairs()
  let l:current = bufnr()

  call assert_fails("browse edit other", "E1513:")
  call assert_equal(l:current, bufnr())

  try
    browse edit! other
    call assert_equal(l:other, bufnr())
  catch /^Vim\%((\a\+)\)\=:E338:/
    " Ignore E338, which occurs if console Vim is built with +browse.
    " Console Vim without +browse will treat this as a regular :edit.
  endtry
endfunc

" Allow :browse w because it doesn't change the buffer in the current file
func Test_browse_edit_pass()
  " A GUI dialog may stall the test.
  CheckNotGui

  call s:reset_all_buffers()

  let l:other = s:make_buffer_pairs()
  let l:current = bufnr()

  try
    browse write other
  catch /^Vim\%((\a\+)\)\=:E338:/
    " Ignore E338, which occurs if console Vim is built with +browse.
    " Console Vim without +browse will treat this as a regular :write.
  endtry

  call delete("other")
endfunc

" Call :bufdo and choose the next available 'nowinfixbuf' window.
func Test_bufdo_choose_available_window()
  call s:reset_all_buffers()

  let l:other = s:make_buffer_pairs()

  " Make a split window that is 'nowinfixbuf' but make it the second-to-last
  " window so that :bufdo will first try the 'winfixbuf' window, pass over it,
  " and prefer the other 'nowinfixbuf' window, instead.
  "
  " +-------------------+
  " |   'nowinfixbuf'   |
  " +-------------------+
  " |    'winfixbuf'    |  <-- Cursor is here
  " +-------------------+
  split
  let l:nowinfixbuf_window = win_getid()
  " Move to the 'winfixbuf' window now
  exe "normal \<C-w>j"
  let l:winfixbuf_window = win_getid()

  let l:current = bufnr()
  let l:expected_windows = s:get_windows_count()

  call assert_notequal(l:current, l:other)

  bufdo echo ''
  call assert_equal(l:nowinfixbuf_window, win_getid())
  call assert_notequal(l:other, bufnr())
  call assert_equal(l:expected_windows, s:get_windows_count())
endfunc

" Call :bufdo and create a new split window if all available windows are 'winfixbuf'.
func Test_bufdo_make_new_window()
  call s:reset_all_buffers()

  let [l:first, l:last] = s:make_buffers_list()
  exe $"buffer! {l:first}"
  let l:current = win_getid()
  let l:current_windows = s:get_windows_count()

  bufdo echo ''
  call assert_notequal(l:current, win_getid())
  call assert_equal(l:last, bufnr())
  exe "normal \<C-w>j"
  call assert_equal(l:first, bufnr())
  call assert_equal(l:current_windows + 1, s:get_windows_count())
endfunc

" Fail :buffer but :buffer! is allowed
func Test_buffer()
  call s:reset_all_buffers()

  let l:other = s:make_buffer_pairs()
  let l:current = bufnr()

  call assert_fails($"buffer {l:other}", "E1513:")
  call assert_equal(l:current, bufnr())

  exe $"buffer! {l:other}"
  call assert_equal(l:other, bufnr())
endfunc

" Allow :buffer on a 'winfixbuf' window if there is no change in buffer
func Test_buffer_same_buffer()
  call s:reset_all_buffers()

  call s:make_buffer_pairs()
  let l:current = bufnr()

  exe $"buffer {l:current}"
  call assert_equal(l:current, bufnr())

  exe $"buffer! {l:current}"
  call assert_equal(l:current, bufnr())
endfunc

" Allow :cNext but the 'nowinfixbuf' window is selected, instead
func Test_cNext()
  CheckFeature quickfix
  call s:reset_all_buffers()

  let [l:first_window, l:winfix_window, l:quickfix_window] = s:make_quickfix_windows()

  " The call to `:cNext` succeeds but it selects the window with 'nowinfixbuf' instead
  call s:set_quickfix_by_buffer(winbufnr(l:winfix_window))

  " Make sure the previous window has 'winfixbuf' so we can test that our
  " "skip 'winfixbuf' window" logic works.
  call win_gotoid(l:winfix_window)
  call win_gotoid(l:quickfix_window)

  cNext
  call assert_equal(l:first_window, win_getid())
endfunc

" Allow :cNfile but the 'nowinfixbuf' window is selected, instead
func Test_cNfile()
  CheckFeature quickfix
  call s:reset_all_buffers()

  let [l:first_window, l:winfix_window, l:quickfix_window] = s:make_quickfix_windows()

  " The call to `:cNfile` succeeds but it selects the window with 'nowinfixbuf' instead
  call s:set_quickfix_by_buffer(winbufnr(l:winfix_window))
  cnext!

  " Make sure the previous window has 'winfixbuf' so we can test that our
  " "skip 'winfixbuf' window" logic works.
  call win_gotoid(l:winfix_window)
  call win_gotoid(l:quickfix_window)

  cNfile
  call assert_equal(l:first_window, win_getid())
endfunc

" Allow :caddexpr because it doesn't change the current buffer
func Test_caddexpr()
  CheckFeature quickfix
  call s:reset_all_buffers()

  let l:file_path = tempname()
  call writefile(["Error - bad-thing-found"], l:file_path, 'D')
  exe $"edit {l:file_path}"
  let l:file_buffer = bufnr()
  let l:current = bufnr()

  edit first.unittest
  call append(0, ["some-search-term bad-thing-found"])

  edit! other.unittest

  set winfixbuf

  exe $"buffer! {l:file_buffer}"

  exe 'caddexpr expand("%") .. ":" .. line(".") .. ":" .. getline(".")'
  call assert_equal(l:current, bufnr())
endfunc

" Fail :cbuffer but :cbuffer! is allowed
func Test_cbuffer()
  CheckFeature quickfix
  call s:reset_all_buffers()

  let l:file_path = tempname()
  call writefile(["first.unittest:1:Error - bad-thing-found"], l:file_path, 'D')
  exe $"edit {l:file_path}"
  let l:file_buffer = bufnr()
  let l:current = bufnr()

  edit first.unittest
  call append(0, ["some-search-term bad-thing-found"])

  edit! other.unittest

  set winfixbuf

  exe $"buffer! {file_buffer}"

  call assert_fails($"cbuffer {file_buffer}", "E1513: Cannot switch buffer. 'winfixbuf' is enabled")
  call assert_equal(current, bufnr())

  exe $"cbuffer! {file_buffer}"
  call assert_equal("first.unittest", expand("%:t"))
endfunc

" Allow :cc but the 'nowinfixbuf' window is selected, instead
func Test_cc()
  CheckFeature quickfix
  call s:reset_all_buffers()

  let [l:first_window, l:winfix_window, l:quickfix_window] = s:make_quickfix_windows()

  " The call to `:cnext` succeeds but it selects the window with 'nowinfixbuf' instead
  call s:set_quickfix_by_buffer(winbufnr(l:winfix_window))

  " Make sure the previous window has 'winfixbuf' so we can test that our
  " "skip 'winfixbuf' window" logic works.
  call win_gotoid(l:winfix_window)
  call win_gotoid(l:quickfix_window)
  " Go up one line in the quickfix window to an quickfix entry that doesn't
  " point to a winfixbuf buffer
  normal k
  " Attempt to make the previous window, winfixbuf buffer, to go to the
  " non-winfixbuf quickfix entry
  .cc

  " Confirm that :.cc did not change the winfixbuf-enabled window
  call assert_equal(l:first_window, win_getid())
endfunc

" Call :cdo and choose the next available 'nowinfixbuf' window.
func Test_cdo_choose_available_window()
  CheckFeature quickfix
  call s:reset_all_buffers()

  let [l:current, l:last] = s:make_simple_quickfix()
  exe $"buffer! {l:current}"

  " Make a split window that is 'nowinfixbuf' but make it the second-to-last
  " window so that :cdo will first try the 'winfixbuf' window, pass over it,
  " and prefer the other 'nowinfixbuf' window, instead.
  "
  " +-------------------+
  " |   'nowinfixbuf'   |
  " +-------------------+
  " |    'winfixbuf'    |  <-- Cursor is here
  " +-------------------+
  split
  let l:nowinfixbuf_window = win_getid()
  " Move to the 'winfixbuf' window now
  exe "normal \<C-w>j"
  let l:winfixbuf_window = win_getid()
  let l:expected_windows = s:get_windows_count()

  cdo echo ''

  call assert_equal(l:nowinfixbuf_window, win_getid())
  call assert_equal(l:last, bufnr())
  exe "normal \<C-w>j"
  call assert_equal(l:current, bufnr())
  call assert_equal(l:expected_windows, s:get_windows_count())
endfunc

" Call :cdo and create a new split window if all available windows are 'winfixbuf'.
func Test_cdo_make_new_window()
  CheckFeature quickfix
  call s:reset_all_buffers()

  let [l:current_buffer, l:last] = s:make_simple_quickfix()
  exe $"buffer! {l:current_buffer}"

  let l:current_window = win_getid()
  let l:current_windows = s:get_windows_count()

  cdo echo ''
  call assert_notequal(l:current_window, win_getid())
  call assert_equal(l:last, bufnr())
  exe "normal \<C-w>j"
  call assert_equal(l:current_buffer, bufnr())
  call assert_equal(l:current_windows + 1, s:get_windows_count())
endfunc

" Fail :cexpr but :cexpr! is allowed
func Test_cexpr()
  CheckFeature quickfix
  call s:reset_all_buffers()

  let file = tempname()
  let entry = $'["{file}:1:bar"]'
  let current = bufnr()

  set winfixbuf

  call assert_fails($"cexpr {entry}", "E1513: Cannot switch buffer. 'winfixbuf' is enabled")
  call assert_equal(current, bufnr())

  exe $"cexpr! {entry}"
  call assert_equal(fnamemodify(file, ":t"), expand("%:t"))
endfunc

" Call :cfdo and choose the next available 'nowinfixbuf' window.
func Test_cfdo_choose_available_window()
  CheckFeature quickfix
  call s:reset_all_buffers()

  let [l:current, l:last] = s:make_simple_quickfix()
  exe $"buffer! {l:current}"

  " Make a split window that is 'nowinfixbuf' but make it the second-to-last
  " window so that :cfdo will first try the 'winfixbuf' window, pass over it,
  " and prefer the other 'nowinfixbuf' window, instead.
  "
  " +-------------------+
  " |   'nowinfixbuf'   |
  " +-------------------+
  " |    'winfixbuf'    |  <-- Cursor is here
  " +-------------------+
  split
  let l:nowinfixbuf_window = win_getid()
  " Move to the 'winfixbuf' window now
  exe "normal \<C-w>j"
  let l:winfixbuf_window = win_getid()
  let l:expected_windows = s:get_windows_count()

  cfdo echo ''

  call assert_equal(l:nowinfixbuf_window, win_getid())
  call assert_equal(l:last, bufnr())
  exe "normal \<C-w>j"
  call assert_equal(l:current, bufnr())
  call assert_equal(l:expected_windows, s:get_windows_count())
endfunc

" Call :cfdo and create a new split window if all available windows are 'winfixbuf'.
func Test_cfdo_make_new_window()
  CheckFeature quickfix
  call s:reset_all_buffers()

  let [l:current_buffer, l:last] = s:make_simple_quickfix()
  exe $"buffer! {l:current_buffer}"

  let l:current_window = win_getid()
  let l:current_windows = s:get_windows_count()

  cfdo echo ''
  call assert_notequal(l:current_window, win_getid())
  call assert_equal(l:last, bufnr())
  exe "normal \<C-w>j"
  call assert_equal(l:current_buffer, bufnr())
  call assert_equal(l:current_windows + 1, s:get_windows_count())
endfunc

" Fail :cfile but :cfile! is allowed
func Test_cfile()
  CheckFeature quickfix
  call s:reset_all_buffers()

  edit first.unittest
  call append(0, ["some-search-term bad-thing-found"])
  write
  let first = bufnr()

  edit! second.unittest
  call append(0, ["some-search-term"])
  write

  let file = tempname()
  call writefile(["first.unittest:1:Error - bad-thing-found was detected"], file)

  let current = bufnr()

  set winfixbuf

  call assert_fails($":cfile {file}", "E1513: Cannot switch buffer. 'winfixbuf' is enabled")
  call assert_equal(current, bufnr())

  exe $":cfile! {file}"
  call assert_equal(first, bufnr())

  call delete(file)
  call delete("first.unittest")
  call delete("second.unittest")
endfunc

" Allow :cfirst but the 'nowinfixbuf' window is selected, instead
func Test_cfirst()
  CheckFeature quickfix
  call s:reset_all_buffers()

  let [l:first_window, l:winfix_window, l:quickfix_window] = s:make_quickfix_windows()

  " The call to `:cfirst` succeeds but it selects the window with 'nowinfixbuf' instead
  call s:set_quickfix_by_buffer(winbufnr(l:winfix_window))

  " Make sure the previous window has 'winfixbuf' so we can test that our
  " "skip 'winfixbuf' window" logic works.
  call win_gotoid(l:winfix_window)
  call win_gotoid(l:quickfix_window)

  cfirst
  call assert_equal(l:first_window, win_getid())
endfunc

" Allow :clast but the 'nowinfixbuf' window is selected, instead
func Test_clast()
  CheckFeature quickfix
  call s:reset_all_buffers()

  let [l:first_window, l:winfix_window, l:quickfix_window] = s:make_quickfix_windows()

  " The call to `:clast` succeeds but it selects the window with 'nowinfixbuf' instead
  call s:set_quickfix_by_buffer(winbufnr(l:winfix_window))

  " Make sure the previous window has 'winfixbuf' so we can test that our
  " "skip 'winfixbuf' window" logic works.
  call win_gotoid(l:winfix_window)
  call win_gotoid(l:quickfix_window)

  clast
  call assert_equal(l:first_window, win_getid())
endfunc

" Allow :cnext but the 'nowinfixbuf' window is selected, instead
" Make sure no new windows are created and previous windows are reused
func Test_cnext()
  CheckFeature quickfix
  call s:reset_all_buffers()

  let [l:first_window, l:winfix_window, l:quickfix_window] = s:make_quickfix_windows()
  let l:expected = s:get_windows_count()

  " The call to `:cnext` succeeds but it selects the window with 'nowinfixbuf' instead
  call s:set_quickfix_by_buffer(winbufnr(l:winfix_window))

  cnext!
  call assert_equal(l:expected, s:get_windows_count())

  " Make sure the previous window has 'winfixbuf' so we can test that our
  " "skip 'winfixbuf' window" logic works.
  call win_gotoid(l:winfix_window)
  call win_gotoid(l:quickfix_window)

  cnext
  call assert_equal(l:first_window, win_getid())
  call assert_equal(l:expected, s:get_windows_count())
endfunc

" Make sure :cnext creates a split window if no previous window exists
func Test_cnext_no_previous_window()
  CheckFeature quickfix
  call s:reset_all_buffers()

  let [l:current, _] = s:make_simple_quickfix()
  exe $"buffer! {l:current}"

  let l:expected = s:get_windows_count()

  " Open the quickfix in a separate split and go to it
  copen

  call assert_equal(l:expected + 1, s:get_windows_count())
endfunc

" Allow :cnext and create a 'nowinfixbuf' window if none exists
func Test_cnext_make_new_window()
  CheckFeature quickfix
  call s:reset_all_buffers()

  let [l:current, _] = s:make_simple_quickfix()
  let l:current = win_getid()

  cfirst!

  let l:windows = s:get_windows_count()
  let l:expected = l:windows + 1  " We're about to create a new split window

  cnext
  call assert_equal(l:expected, s:get_windows_count())

  cnext!
  call assert_equal(l:expected, s:get_windows_count())
endfunc

" Allow :cprevious but the 'nowinfixbuf' window is selected, instead
func Test_cprevious()
  CheckFeature quickfix
  call s:reset_all_buffers()

  let [l:first_window, l:winfix_window, l:quickfix_window] = s:make_quickfix_windows()

  " The call to `:cprevious` succeeds but it selects the window with 'nowinfixbuf' instead
  call s:set_quickfix_by_buffer(winbufnr(l:winfix_window))

  " Make sure the previous window has 'winfixbuf' so we can test that our
  " "skip 'winfixbuf' window" logic works.
  call win_gotoid(l:winfix_window)
  call win_gotoid(l:quickfix_window)

  cprevious
  call assert_equal(l:first_window, win_getid())
endfunc

" Allow :cnfile but the 'nowinfixbuf' window is selected, instead
func Test_cnfile()
  CheckFeature quickfix
  call s:reset_all_buffers()

  let [l:first_window, l:winfix_window, l:quickfix_window] = s:make_quickfix_windows()

  " The call to `:cnfile` succeeds but it selects the window with 'nowinfixbuf' instead
  call s:set_quickfix_by_buffer(winbufnr(l:winfix_window))
  cnext!

  " Make sure the previous window has 'winfixbuf' so we can test that our
  " "skip 'winfixbuf' window" logic works.
  call win_gotoid(l:winfix_window)
  call win_gotoid(l:quickfix_window)

  cnfile
  call assert_equal(l:first_window, win_getid())
endfunc

" Allow :cpfile but the 'nowinfixbuf' window is selected, instead
func Test_cpfile()
  CheckFeature quickfix
  call s:reset_all_buffers()

  let [l:first_window, l:winfix_window, l:quickfix_window] = s:make_quickfix_windows()

  " The call to `:cpfile` succeeds but it selects the window with 'nowinfixbuf' instead
  call s:set_quickfix_by_buffer(winbufnr(l:winfix_window))
  cnext!

  " Make sure the previous window has 'winfixbuf' so we can test that our
  " "skip 'winfixbuf' window" logic works.
  call win_gotoid(l:winfix_window)
  call win_gotoid(l:quickfix_window)

  cpfile
  call assert_equal(l:first_window, win_getid())
endfunc

" Allow :crewind but the 'nowinfixbuf' window is selected, instead
func Test_crewind()
  CheckFeature quickfix
  call s:reset_all_buffers()

  let [l:first_window, l:winfix_window, l:quickfix_window] = s:make_quickfix_windows()

  " The call to `:crewind` succeeds but it selects the window with 'nowinfixbuf' instead
  call s:set_quickfix_by_buffer(winbufnr(l:winfix_window))
  cnext!

  " Make sure the previous window has 'winfixbuf' so we can test that our
  " "skip 'winfixbuf' window" logic works.
  call win_gotoid(l:winfix_window)
  call win_gotoid(l:quickfix_window)

  crewind
  call assert_equal(l:first_window, win_getid())
endfunc

" Allow <C-w>f because it opens in a new split
func Test_ctrl_w_f()
  call s:reset_all_buffers()

  enew
  let l:file_name = tempname()
  call writefile([], l:file_name)
  let l:file_buffer = bufnr()

  enew
  file other
  let l:other_buffer = bufnr()

  set winfixbuf

  call setline(1, l:file_name)
  let l:current_windows = s:get_windows_count()
  exe "normal \<C-w>f"

  call assert_equal(l:current_windows + 1, s:get_windows_count())

  call delete(l:file_name)
endfunc

" Fail :djump but :djump! is allowed
func Test_djump()
  call s:reset_all_buffers()

  let l:include_file = tempname() .. ".h"
  call writefile(["min(1, 12);",
        \ $'#include "{l:include_file}"'
        \ ],
        \ "main.c")
  call writefile(["#define min(X, Y)  ((X) < (Y) ? (X) : (Y))"], l:include_file)
  edit main.c

  set winfixbuf

  let l:current = bufnr()

  call assert_fails("djump 1 /min/", "E1513:")
  call assert_equal(l:current, bufnr())

  djump! 1 /min/
  call assert_notequal(l:current, bufnr())

  call delete("main.c")
  call delete(l:include_file)
endfunc

" Fail :drop but :drop! is allowed
func Test_drop()
  call s:reset_all_buffers()

  let l:other = s:make_buffer_pairs()
  let l:current = bufnr()

  call assert_fails("drop other", "E1513:")
  call assert_equal(l:current, bufnr())

  drop! other
  call assert_equal(l:other, bufnr())
endfunc

" Fail :edit but :edit! is allowed
func Test_edit()
  call s:reset_all_buffers()

  let l:other = s:make_buffer_pairs()
  let l:current = bufnr()

  call assert_fails("edit other", "E1513:")
  call assert_equal(l:current, bufnr())

  edit! other
  call assert_equal(l:other, bufnr())
endfunc

" Fail :e when selecting a buffer from a relative path if in a different folder
"
" In this tests there's 2 buffers
"
" foo - lives on disk, in some folder. e.g. /tmp/foo
" foo - an in-memory buffer that has not been saved to disk. If saved, it
"       would live in a different folder, /other/foo.
"
" The 'winfixbuf' is looking at the in-memory buffer and trying to switch to
" the buffer on-disk (and fails, because it's a different buffer)
func Test_edit_different_buffer_on_disk_and_relative_path_to_disk()
  call s:reset_all_buffers()

  let l:file_on_disk = tempname()
  let l:directory_on_disk1 = fnamemodify(l:file_on_disk, ":p:h")
  let l:name = fnamemodify(l:file_on_disk, ":t")
  exe $"edit {l:file_on_disk}"
  write!

  let l:directory_on_disk2 = l:directory_on_disk1 .. "_something_else"

  if !isdirectory(l:directory_on_disk2)
    call mkdir(l:directory_on_disk2)
  endif

  exe $"cd {l:directory_on_disk2}"
  exe $"edit {l:name}"

  let l:current = bufnr()

  call assert_equal(l:current, bufnr())
  set winfixbuf
  call assert_fails($"edit {l:file_on_disk}", "E1513:")
  call assert_equal(l:current, bufnr())

  call delete(l:directory_on_disk1)
  call delete(l:directory_on_disk2)
endfunc

" Fail :e when selecting a buffer from a relative path if in a different folder
"
" In this tests there's 2 buffers
"
" foo - lives on disk, in some folder. e.g. /tmp/foo
" foo - an in-memory buffer that has not been saved to disk. If saved, it
"       would live in a different folder, /other/foo.
"
" The 'winfixbuf' is looking at the on-disk buffer and trying to switch to
" the in-memory buffer (and fails, because it's a different buffer)
func Test_edit_different_buffer_on_disk_and_relative_path_to_memory()
  call s:reset_all_buffers()

  let l:file_on_disk = tempname()
  let l:directory_on_disk1 = fnamemodify(l:file_on_disk, ":p:h")
  let l:name = fnamemodify(l:file_on_disk, ":t")
  exe $"edit {l:file_on_disk}"
  write!

  let l:directory_on_disk2 = l:directory_on_disk1 .. "_something_else"

  if !isdirectory(l:directory_on_disk2)
    call mkdir(l:directory_on_disk2)
  endif

  exe $"cd {l:directory_on_disk2}"
  exe $"edit {l:name}"
  exe $"cd {l:directory_on_disk1}"
  exe $"edit {l:file_on_disk}"
  exe $"cd {l:directory_on_disk2}"

  let l:current = bufnr()

  call assert_equal(l:current, bufnr())
  set winfixbuf
  call assert_fails($"edit {l:name}", "E1513:")
  call assert_equal(l:current, bufnr())

  call delete(l:directory_on_disk1)
  call delete(l:directory_on_disk2)
endfunc

" Fail to call `:e first` if called from a starting, in-memory buffer
func Test_edit_first_buffer()
  call s:reset_all_buffers()

  set winfixbuf
  let l:current = bufnr()

  call assert_fails("edit first", "E1513:")
  call assert_equal(l:current, bufnr())

  edit! first
  call assert_equal(l:current, bufnr())
  edit! somewhere_else
  call assert_notequal(l:current, bufnr())
endfunc

" Allow reloading a buffer using :e
func Test_edit_no_arguments()
  call s:reset_all_buffers()

  let l:current = bufnr()
  file some_buffer

  call assert_equal(l:current, bufnr())
  set winfixbuf
  edit
  call assert_equal(l:current, bufnr())
endfunc

" Allow :e selecting the current buffer
func Test_edit_same_buffer_in_memory()
  call s:reset_all_buffers()

  let current = bufnr()
  file same_buffer

  call assert_equal(current, bufnr())
  set winfixbuf
  edit same_buffer
  call assert_equal(current, bufnr())
  set nowinfixbuf
endfunc

" Allow :e selecting the current buffer as a full path
func Test_edit_same_buffer_on_disk_absolute_path()
  call s:reset_all_buffers()

  let file = tempname()
  " file must exist for expansion of 8.3 paths to succeed
  call writefile([], file, 'D')
  let file = fnamemodify(file, ':p')
  let current = bufnr()
  exe $"edit {file}"
  write!

  call assert_equal(current, bufnr())
  set winfixbuf
  exe $"edit {file}"
  call assert_equal(current, bufnr())

  set nowinfixbuf
endfunc

" Fail :enew but :enew! is allowed
func Test_enew()
  call s:reset_all_buffers()

  let l:other = s:make_buffer_pairs()
  let l:current = bufnr()

  call assert_fails("enew", "E1513:")
  call assert_equal(l:current, bufnr())

  enew!
  call assert_notequal(l:other, bufnr())
  call assert_notequal(3, bufnr())
endfunc

" Fail :ex but :ex! is allowed
func Test_ex()
  call s:reset_all_buffers()

  let l:other = s:make_buffer_pairs()
  let l:current = bufnr()

  call assert_fails("ex other", "E1513:")
  call assert_equal(l:current, bufnr())

  ex! other
  call assert_equal(l:other, bufnr())
endfunc

" Fail :find but :find! is allowed
func Test_find()
  call s:reset_all_buffers()

  let l:current = bufnr()
  let l:file = tempname()
  call writefile([], l:file, 'D')
  let l:file = fnamemodify(l:file, ':p')  " In case it's Windows 8.3-style.
  let l:directory = fnamemodify(l:file, ":p:h")
  let l:name = fnamemodify(l:file, ":p:t")

  let l:original_path = &path
  exe $"set path={l:directory}"

  set winfixbuf

  call assert_fails($"exe 'find {l:name}'", "E1513:")
  call assert_equal(l:current, bufnr())

  exe $"find! {l:name}"
  call assert_equal(l:file, expand("%:p"))

  exe $"set path={l:original_path}"
endfunc

" Fail :first but :first! is allowed
func Test_first()
  call s:reset_all_buffers()

  let [l:first, _] = s:make_args_list()
  next!

  call assert_fails("first", "E1513:")
  call assert_notequal(l:first, bufnr())

  first!
  call assert_equal(l:first, bufnr())
endfunc

" Fail :grep but :grep! is allowed
func Test_grep()
  CheckFeature quickfix
  call s:reset_all_buffers()

  edit first.unittest
  call append(0, ["some-search-term"])
  write
  let l:first = bufnr()

  edit current.unittest
  call append(0, ["some-search-term"])
  write
  let l:current = bufnr()

  edit! last.unittest
  call append(0, ["some-search-term"])
  write
  let l:last = bufnr()

  set winfixbuf

  buffer! current.unittest

  call assert_fails("silent! grep some-search-term *.unittest", "E1513:")
  call assert_equal(l:current, bufnr())
  exe $"edit! {l:first}"

  silent! grep! some-search-term *.unittest
  call assert_notequal(l:first, bufnr())

  call delete("first.unittest")
  call delete("current.unittest")
  call delete("last.unittest")
endfunc

" Fail :ijump but :ijump! is allowed
func Test_ijump()
  call s:reset_all_buffers()

  let l:include_file = tempname() .. ".h"
  call writefile([
        \ $'#include "{l:include_file}"'
        \ ],
        \ "main.c", 'D')
  call writefile(["#define min(X, Y)  ((X) < (Y) ? (X) : (Y))"], l:include_file, 'D')
  edit main.c

  set winfixbuf

  let l:current = bufnr()

  set define=^\\s*#\\s*define
  set include=^\\s*#\\s*include
  set path=.,/usr/include,,

  call assert_fails("ijump /min/", "E1513:")
  call assert_equal(l:current, bufnr())

  set nowinfixbuf

  ijump! /min/
  call assert_notequal(l:current, bufnr())

  set define&
  set include&
  set path&
endfunc

" Fail :lNext but :lNext! is allowed
func Test_lNext()
  CheckFeature quickfix
  call s:reset_all_buffers()

  let [l:first, l:middle, _] = s:make_simple_location_list()
  call assert_equal(1, getloclist(0, #{idx: 0}).idx)

  lnext!
  call assert_equal(2, getloclist(0, #{idx: 0}).idx)
  call assert_equal(l:middle, bufnr())

  call assert_fails("lNext", "E1513:")
  " Ensure the entry didn't change.
  call assert_equal(2, getloclist(0, #{idx: 0}).idx)
  call assert_equal(l:middle, bufnr())

  lnext!
  call assert_equal(3, getloclist(0, #{idx: 0}).idx)
  call assert_equal(l:middle, bufnr())

  lNext!
  call assert_equal(2, getloclist(0, #{idx: 0}).idx)
  call assert_equal(l:middle, bufnr())

  lNext!
  call assert_equal(1, getloclist(0, #{idx: 0}).idx)
  call assert_equal(l:first, bufnr())
endfunc

" Fail :lNfile but :lNfile! is allowed
func Test_lNfile()
  CheckFeature quickfix
  call s:reset_all_buffers()

  let [l:first, l:current, _] = s:make_simple_location_list()
  call assert_equal(1, getloclist(0, #{idx: 0}).idx)

  lnext!
  call assert_equal(2, getloclist(0, #{idx: 0}).idx)
  call assert_equal(l:current, bufnr())

  call assert_fails("lNfile", "E1513:")
  " Ensure the entry didn't change.
  call assert_equal(2, getloclist(0, #{idx: 0}).idx)
  call assert_equal(l:current, bufnr())

  lnext!
  call assert_equal(3, getloclist(0, #{idx: 0}).idx)
  call assert_equal(l:current, bufnr())

  lNfile!
  call assert_equal(1, getloclist(0, #{idx: 0}).idx)
  call assert_equal(l:first, bufnr())
endfunc

" Allow :laddexpr because it doesn't change the current buffer
func Test_laddexpr()
  CheckFeature quickfix
  call s:reset_all_buffers()

  let l:file_path = tempname()
  call writefile(["Error - bad-thing-found"], l:file_path, 'D')
  exe $"edit {l:file_path}"
  let l:file_buffer = bufnr()
  let l:current = bufnr()

  edit first.unittest
  call append(0, ["some-search-term bad-thing-found"])

  edit! other.unittest

  set winfixbuf

  exe $"buffer! {l:file_buffer}"

  exe 'laddexpr expand("%") .. ":" .. line(".") .. ":" .. getline(".")'
  call assert_equal(l:current, bufnr())
endfunc

" Fail :last but :last! is allowed
func Test_last()
  call s:reset_all_buffers()

  let [_, l:last] = s:make_args_list()
  next!

  call assert_fails("last", "E1513:")
  call assert_notequal(l:last, bufnr())

  last!
  call assert_equal(l:last, bufnr())
endfunc

" Fail :lbuffer but :lbuffer! is allowed
func Test_lbuffer()
  CheckFeature quickfix
  call s:reset_all_buffers()

  let l:file_path = tempname()
  call writefile(["first.unittest:1:Error - bad-thing-found"], l:file_path, 'D')
  exe $"edit {l:file_path}"
  let l:file_buffer = bufnr()
  let l:current = bufnr()

  edit first.unittest
  call append(0, ["some-search-term bad-thing-found"])

  edit! other.unittest

  set winfixbuf

  exe $"buffer! {file_buffer}"

  call assert_fails($"lbuffer {file_buffer}", "E1513: Cannot switch buffer. 'winfixbuf' is enabled")
  call assert_equal(current, bufnr())

  exe $"lbuffer! {file_buffer}"
  call assert_equal("first.unittest", expand("%:t"))
endfunc

" Fail :ldo but :ldo! is allowed
func Test_ldo()
  CheckFeature quickfix
  call s:reset_all_buffers()

  let [l:first, l:middle, l:last] = s:make_simple_location_list()
  lnext!

  call assert_fails($'exe "ldo buffer {l:first}"', "E1513:")
  call assert_equal(l:middle, bufnr())
  exe $"ldo! buffer {l:first}"
  call assert_notequal(l:last, bufnr())
endfunc

" Fail :lfdo but :lfdo! is allowed
func Test_lexpr()
  CheckFeature quickfix
  call s:reset_all_buffers()

  let file = tempname()
  let entry = $'["{file}:1:bar"]'
  let current = bufnr()

  set winfixbuf

  call assert_fails($"lexpr {entry}", "E1513: Cannot switch buffer. 'winfixbuf' is enabled")
  call assert_equal(current, bufnr())

  exe $"lexpr! {entry}"
  call assert_equal(fnamemodify(file, ":t"), expand("%:t"))
endfunc

" Fail :lfdo but :lfdo! is allowed
func Test_lfdo()
  CheckFeature quickfix
  call s:reset_all_buffers()

  let [first, middle, last] = s:make_simple_location_list()
  lnext!

  call assert_fails('exe "lfdo buffer ' .. first .. '"', "E1513:")
  call assert_equal(middle, bufnr())
  exe $"lfdo! buffer {first}"
  call assert_notequal(last, bufnr())
endfunc

" Fail :lfile but :lfile! is allowed
func Test_lfile()
  CheckFeature quickfix
  call s:reset_all_buffers()

  edit first.unittest
  call append(0, ["some-search-term bad-thing-found"])
  write
  let l:first = bufnr()

  edit! second.unittest
  call append(0, ["some-search-term"])
  write

  let l:file = tempname()
  call writefile(["first.unittest:1:Error - bad-thing-found was detected"], l:file, 'D')

  let l:current = bufnr()

  set winfixbuf

  call assert_fails($":lfile {l:file}", "E1513: Cannot switch buffer. 'winfixbuf' is enabled")
  call assert_equal(l:current, bufnr())

  exe $":lfile! {l:file}"
  call assert_equal(l:first, bufnr())

  call delete("first.unittest")
  call delete("second.unittest")
endfunc

" Fail :ll but :ll! is allowed
func Test_ll()
  CheckFeature quickfix
  call s:reset_all_buffers()

  let [l:first, l:middle, l:last] = s:make_simple_location_list()
  lopen
  lfirst!
  exe "normal \<C-w>j"
  normal j

  call assert_fails(".ll", "E1513:")
  exe "normal \<C-w>k"
  call assert_equal(l:first, bufnr())
  exe "normal \<C-w>j"
  .ll!
  exe "normal \<C-w>k"
  call assert_equal(l:middle, bufnr())
endfunc

" Fail :llast but :llast! is allowed
func Test_llast()
  CheckFeature quickfix
  call s:reset_all_buffers()

  let [l:first, _, l:last] = s:make_simple_location_list()
  lfirst!

  call assert_fails("llast", "E1513:")
  call assert_equal(l:first, bufnr())

  llast!
  call assert_equal(l:last, bufnr())
endfunc

" Fail :lnext but :lnext! is allowed
func Test_lnext()
  CheckFeature quickfix
  call s:reset_all_buffers()

  let [l:first, l:middle, l:last] = s:make_simple_location_list()
  ll!

  call assert_fails("lnext", "E1513:")
  call assert_equal(l:first, bufnr())

  lnext!
  call assert_equal(l:middle, bufnr())
endfunc

" Fail :lnfile but :lnfile! is allowed
func Test_lnfile()
  CheckFeature quickfix
  call s:reset_all_buffers()

  let [_, l:current, l:last] = s:make_simple_location_list()
  call assert_equal(1, getloclist(0, #{idx: 0}).idx)

  lnext!
  call assert_equal(2, getloclist(0, #{idx: 0}).idx)
  call assert_equal(l:current, bufnr())

  call assert_fails("lnfile", "E1513:")
  " Ensure the entry didn't change.
  call assert_equal(2, getloclist(0, #{idx: 0}).idx)
  call assert_equal(l:current, bufnr())

  lnfile!
  call assert_equal(4, getloclist(0, #{idx: 0}).idx)
  call assert_equal(l:last, bufnr())
endfunc

" Fail :lpfile but :lpfile! is allowed
func Test_lpfile()
  CheckFeature quickfix
  call s:reset_all_buffers()

  let [l:first, l:current, _] = s:make_simple_location_list()
  lnext!

  call assert_fails("lpfile", "E1513:")
  call assert_equal(l:current, bufnr())

  lnext!  " Reset for the next test call

  lpfile!
  call assert_equal(l:first, bufnr())
endfunc

" Fail :lprevious but :lprevious! is allowed
func Test_lprevious()
  CheckFeature quickfix
  call s:reset_all_buffers()

  let [l:first, l:middle, _] = s:make_simple_location_list()
  call assert_equal(1, getloclist(0, #{idx: 0}).idx)

  lnext!
  call assert_equal(2, getloclist(0, #{idx: 0}).idx)
  call assert_equal(l:middle, bufnr())

  call assert_fails("lprevious", "E1513:")
  " Ensure the entry didn't change.
  call assert_equal(2, getloclist(0, #{idx: 0}).idx)
  call assert_equal(l:middle, bufnr())

  lprevious!
  call assert_equal(1, getloclist(0, #{idx: 0}).idx)
  call assert_equal(l:first, bufnr())
endfunc

" Fail :lrewind but :lrewind! is allowed
func Test_lrewind()
  CheckFeature quickfix
  call s:reset_all_buffers()

  let [l:first, l:middle, _] = s:make_simple_location_list()
  lnext!

  call assert_fails("lrewind", "E1513:")
  call assert_equal(l:middle, bufnr())

  lrewind!
  call assert_equal(l:first, bufnr())
endfunc

" Fail :ltag but :ltag! is allowed
func Test_ltag()
  call s:reset_all_buffers()

  set tags=Xtags
  call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
        \ "one\tXfile\t1",
        \ "three\tXfile\t3",
        \ "two\tXfile\t2"],
        \ "Xtags", 'D')
  call writefile(["one", "two", "three"], "Xfile", 'D')
  call writefile(["one"], "Xother", 'D')
  edit Xother
  exe "normal \<C-]>"

  set winfixbuf

  let l:current = bufnr()

  call assert_fails("ltag one", "E1513:")

  ltag! one

  set tags&
endfunc

" Fail vim.command if we try to change buffers while 'winfixbuf' is set
func Test_lua_command()
  CheckFeature lua
  call s:reset_all_buffers()

  enew
  file first
  let l:previous = bufnr()

  enew
  file second
  let l:current = bufnr()

  set winfixbuf

  call assert_fails($'lua vim.command("buffer " .. {l:previous})')
  call assert_equal(l:current, bufnr())

  exe $'lua vim.command("buffer! " .. {l:previous})'
  call assert_equal(l:previous, bufnr())
endfunc

" Fail :lvimgrep but :lvimgrep! is allowed
func Test_lvimgrep()
  CheckFeature quickfix
  call s:reset_all_buffers()

  edit first.unittest
  call append(0, ["some-search-term"])
  write

  edit winfix.unittest
  call append(0, ["some-search-term"])
  write
  let l:current = bufnr()

  set winfixbuf

  edit! last.unittest
  call append(0, ["some-search-term"])
  write
  let l:last = bufnr()

  buffer! winfix.unittest

  call assert_fails("lvimgrep /some-search-term/ *.unittest", "E1513:")
  call assert_equal(l:current, bufnr())

  lvimgrep! /some-search-term/ *.unittest
  call assert_notequal(l:current, bufnr())

  call delete("first.unittest")
  call delete("winfix.unittest")
  call delete("last.unittest")
endfunc

" Fail :lvimgrepadd but :lvimgrepadd! is allowed
func Test_lvimgrepadd()
  CheckFeature quickfix
  call s:reset_all_buffers()

  edit first.unittest
  call append(0, ["some-search-term"])
  write

  edit winfix.unittest
  call append(0, ["some-search-term"])
  write
  let l:current = bufnr()

  set winfixbuf

  edit! last.unittest
  call append(0, ["some-search-term"])
  write
  let l:last = bufnr()

  buffer! winfix.unittest

  call assert_fails("lvimgrepadd /some-search-term/ *.unittest", "E1513: Cannot switch buffer. 'winfixbuf' is enabled")
  call assert_equal(l:current, bufnr())

  lvimgrepadd! /some-search-term/ *.unittest
  call assert_notequal(l:current, bufnr())

  call delete("first.unittest")
  call delete("winfix.unittest")
  call delete("last.unittest")
endfunc

" Don't allow global marks to change the current 'winfixbuf' window
func Test_marks_mappings_fail()
  call s:reset_all_buffers()

  let l:other = s:make_buffer_pairs()
  let l:current = bufnr()
  exe $"buffer! {l:other}"
  normal mA
  exe $"buffer! {l:current}"
  normal mB

  call assert_fails("normal `A", "E1513:")
  call assert_equal(l:current, bufnr())

  call assert_fails("normal 'A", "E1513:")
  call assert_equal(l:current, bufnr())

  set nowinfixbuf

  normal `A
  call assert_equal(l:other, bufnr())
endfunc

" Allow global marks in a 'winfixbuf' window if the jump is the same buffer
func Test_marks_mappings_pass_intra_move()
  call s:reset_all_buffers()

  let l:current = bufnr()
  call append(0, ["some line", "another line"])
  normal mA
  normal j
  normal mB

  set winfixbuf

  normal `A
  call assert_equal(l:current, bufnr())
endfunc

" Fail :next but :next! is allowed
func Test_next()
  call s:reset_all_buffers()

  let [l:first, _] = s:make_args_list()
  first!

  call assert_fails("next", "E1513:")
  call assert_equal(l:first, bufnr())

  next!
  call assert_notequal(l:first, bufnr())
endfunc

" Ensure :mksession saves 'winfixbuf' details
func Test_mksession()
  CheckFeature mksession
  call s:reset_all_buffers()

  set sessionoptions+=options
  set winfixbuf

  mksession test_winfixbuf_Test_mksession.vim

  call s:reset_all_buffers()
  let l:winfixbuf = &winfixbuf
  call assert_equal(0, l:winfixbuf)

  source test_winfixbuf_Test_mksession.vim

  let l:winfixbuf = &winfixbuf
  call assert_equal(1, l:winfixbuf)

  set sessionoptions&
  call delete("test_winfixbuf_Test_mksession.vim")
endfunc

" Allow :next if the next index is the same as the current buffer
func Test_next_same_buffer()
  call s:reset_all_buffers()

  enew
  file foo
  enew
  file bar
  enew
  file fizz
  enew
  file buzz
  args foo foo bar fizz buzz

  edit foo
  set winfixbuf
  let l:current = bufnr()

  " Allow :next because the args list is `[foo] foo bar fizz buzz
  next
  call assert_equal(l:current, bufnr())

  " Fail :next because the args list is `foo [foo] bar fizz buzz
  " and the next buffer would be bar, which is a different buffer
  call assert_fails("next", "E1513:")
  call assert_equal(l:current, bufnr())
endfunc

" Fail to jump to a tag with g<C-]> if 'winfixbuf' is enabled
func Test_normal_g_ctrl_square_bracket_right()
  call s:reset_all_buffers()

  set tags=Xtags
  call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
        \ "one\tXfile\t1",
        \ "three\tXfile\t3",
        \ "two\tXfile\t2"],
        \ "Xtags", 'D')
  call writefile(["one", "two", "three"], "Xfile", 'D')
  call writefile(["one"], "Xother", 'D')
  edit Xother

  set winfixbuf

  let l:current = bufnr()

  call assert_fails("normal g\<C-]>", "E1513:")
  call assert_equal(l:current, bufnr())

  set tags&
endfunc

" Fail to jump to a tag with g<RightMouse> if 'winfixbuf' is enabled
func Test_normal_g_rightmouse()
  call s:reset_all_buffers()
  set mouse=n

  set tags=Xtags
  call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
        \ "one\tXfile\t1",
        \ "three\tXfile\t3",
        \ "two\tXfile\t2"],
        \ "Xtags", 'D')
  call writefile(["one", "two", "three"], "Xfile", 'D')
  call writefile(["one"], "Xother", 'D')
  edit Xother
  exe "normal \<C-]>"

  set winfixbuf

  let l:current = bufnr()

  call assert_fails("normal g\<RightMouse>", "E1513:")
  call assert_equal(l:current, bufnr())

  set tags&
  set mouse&
endfunc

" Fail to jump to a tag with g] if 'winfixbuf' is enabled
func Test_normal_g_square_bracket_right()
  call s:reset_all_buffers()

  set tags=Xtags
  call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
        \ "one\tXfile\t1",
        \ "three\tXfile\t3",
        \ "two\tXfile\t2"],
        \ "Xtags", 'D')
  call writefile(["one", "two", "three"], "Xfile", 'D')
  call writefile(["one"], "Xother", 'D')
  edit Xother

  set winfixbuf

  let l:current = bufnr()

  call assert_fails("normal g]", "E1513:")
  call assert_equal(l:current, bufnr())

  set tags&
endfunc

" Fail to jump to a tag with <C-RightMouse> if 'winfixbuf' is enabled
func Test_normal_ctrl_rightmouse()
  call s:reset_all_buffers()
  set mouse=n

  set tags=Xtags
  call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
        \ "one\tXfile\t1",
        \ "three\tXfile\t3",
        \ "two\tXfile\t2"],
        \ "Xtags", 'D')
  call writefile(["one", "two", "three"], "Xfile", 'D')
  call writefile(["one"], "Xother", 'D')
  edit Xother
  exe "normal \<C-]>"

  set winfixbuf

  let l:current = bufnr()

  call assert_fails("normal \<C-RightMouse>", "E1513:")
  call assert_equal(l:current, bufnr())

  set tags&
  set mouse&
endfunc

" Fail to jump to a tag with <C-t> if 'winfixbuf' is enabled
func Test_normal_ctrl_t()
  call s:reset_all_buffers()

  set tags=Xtags
  call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
        \ "one\tXfile\t1",
        \ "three\tXfile\t3",
        \ "two\tXfile\t2"],
        \ "Xtags", 'D')
  call writefile(["one", "two", "three"], "Xfile", 'D')
  call writefile(["one"], "Xother", 'D')
  edit Xother
  exe "normal \<C-]>"

  set winfixbuf

  let l:current = bufnr()

  call assert_fails("normal \<C-t>", "E1513:")
  call assert_equal(l:current, bufnr())

  set tags&
endfunc

" Disallow <C-^> in 'winfixbuf' windows
func Test_normal_ctrl_hat()
  call s:reset_all_buffers()
  clearjumps

  enew
  file first
  let l:first = bufnr()

  enew
  file current
  let l:current = bufnr()

  set winfixbuf

  call assert_fails("normal \<C-^>", "E1513:")
  call assert_equal(l:current, bufnr())
endfunc

" Allow <C-i> in 'winfixbuf' windows if the movement stays within the buffer
func Test_normal_ctrl_i_pass()
  call s:reset_all_buffers()
  clearjumps

  enew
  file first
  let l:first = bufnr()

  enew!
  file current
  let l:current = bufnr()
  " Add some lines so we can populate a jumplist"
  call append(0, ["some line", "another line"])
  " Add an entry to the jump list
  " Go up another line
  normal m`
  normal k
  exe "normal \<C-o>"

  set winfixbuf

  let l:line = getcurpos()[1]
  exe "normal 1\<C-i>"
  call assert_notequal(l:line, getcurpos()[1])
endfunc

" Disallow <C-o> in 'winfixbuf' windows if it would cause the buffer to switch
func Test_normal_ctrl_o_fail()
  call s:reset_all_buffers()
  clearjumps

  enew
  file first
  let l:first = bufnr()

  enew
  file current
  let l:current = bufnr()

  set winfixbuf

  call assert_fails("normal \<C-o>", "E1513:")
  call assert_equal(l:current, bufnr())
endfunc

" Allow <C-o> in 'winfixbuf' windows if the movement stays within the buffer
func Test_normal_ctrl_o_pass()
  call s:reset_all_buffers()
  clearjumps

  enew
  file first
  let l:first = bufnr()

  enew!
  file current
  let l:current = bufnr()
  " Add some lines so we can populate a jumplist
  call append(0, ["some line", "another line"])
  " Add an entry to the jump list
  " Go up another line
  normal m`
  normal k

  set winfixbuf

  exe "normal \<C-o>"
  call assert_equal(l:current, bufnr())
endfunc

" Fail to jump to a tag with <C-]> if 'winfixbuf' is enabled
func Test_normal_ctrl_square_bracket_right()
  call s:reset_all_buffers()

  set tags=Xtags
  call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
        \ "one\tXfile\t1",
        \ "three\tXfile\t3",
        \ "two\tXfile\t2"],
        \ "Xtags", 'D')
  call writefile(["one", "two", "three"], "Xfile", 'D')
  call writefile(["one"], "Xother", 'D')
  edit Xother

  set winfixbuf

  let l:current = bufnr()

  call assert_fails("normal \<C-]>", "E1513:")
  call assert_equal(l:current, bufnr())

  set tags&
endfunc

" Allow <C-w><C-]> with 'winfixbuf' enabled because it runs in a new, split window
func Test_normal_ctrl_w_ctrl_square_bracket_right()
  call s:reset_all_buffers()

  set tags=Xtags
  call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
        \ "one\tXfile\t1",
        \ "three\tXfile\t3",
        \ "two\tXfile\t2"],
        \ "Xtags", 'D')
  call writefile(["one", "two", "three"], "Xfile", 'D')
  call writefile(["one"], "Xother", 'D')
  edit Xother

  set winfixbuf

  let l:current_windows = s:get_windows_count()
  exe "normal \<C-w>\<C-]>"
  call assert_equal(l:current_windows + 1, s:get_windows_count())

  set tags&
endfunc

" Allow <C-w>g<C-]> with 'winfixbuf' enabled because it runs in a new, split window
func Test_normal_ctrl_w_g_ctrl_square_bracket_right()
  call s:reset_all_buffers()

  set tags=Xtags
  call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
        \ "one\tXfile\t1",
        \ "three\tXfile\t3",
        \ "two\tXfile\t2"],
        \ "Xtags", 'D')
  call writefile(["one", "two", "three"], "Xfile", 'D')
  call writefile(["one"], "Xother", 'D')
  edit Xother

  set winfixbuf

  let l:current_windows = s:get_windows_count()
  exe "normal \<C-w>g\<C-]>"
  call assert_equal(l:current_windows + 1, s:get_windows_count())

  set tags&
endfunc

" Fail to jump to a tag with <C-]> if 'winfixbuf' is enabled
func Test_normal_gt()
  call s:reset_all_buffers()

  set tags=Xtags
  call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
        \ "one\tXfile\t1",
        \ "three\tXfile\t3",
        \ "two\tXfile\t2"],
        \ "Xtags", 'D')
  call writefile(["one", "two", "three"], "Xfile", 'D')
  call writefile(["one", "two", "three"], "Xother", 'D')
  edit Xother

  set winfixbuf

  let l:current = bufnr()

  call assert_fails("normal \<C-]>", "E1513:")
  call assert_equal(l:current, bufnr())

  set tags&
endfunc

" Prevent gF from switching a 'winfixbuf' window's buffer
func Test_normal_gF()
  call s:reset_all_buffers()

  let l:file = tempname()
  call append(0, [l:file])
  call writefile([], l:file, 'D')
  " Place the cursor onto the line that has `l:file`
  normal gg
  " Prevent Vim from erroring with "No write since last change @ command
  " line" when we try to call gF, later.
  set hidden

  set winfixbuf

  let l:buffer = bufnr()

  call assert_fails("normal gF", "E1513:")
  call assert_equal(l:buffer, bufnr())

  set nowinfixbuf

  normal gF
  call assert_notequal(l:buffer, bufnr())

  set nohidden
endfunc

" Prevent gf from switching a 'winfixbuf' window's buffer
func Test_normal_gf()
  call s:reset_all_buffers()

  let l:file = tempname()
  call append(0, [l:file])
  call writefile([], l:file, 'D')
  " Place the cursor onto the line that has `l:file`
  normal gg
  " Prevent Vim from erroring with "No write since last change @ command
  " line" when we try to call gf, later.
  set hidden

  set winfixbuf

  let l:buffer = bufnr()

  call assert_fails("normal gf", "E1513:")
  call assert_equal(l:buffer, bufnr())

  set nowinfixbuf

  normal gf
  call assert_notequal(l:buffer, bufnr())

  set nohidden
endfunc

" Fail "goto file under the cursor" (using [f, which is the same as `:normal gf`)
func Test_normal_square_bracket_left_f()
  call s:reset_all_buffers()

  let l:file = tempname()
  call append(0, [l:file])
  call writefile([], l:file, 'D')
  " Place the cursor onto the line that has `l:file`
  normal gg
  " Prevent Vim from erroring with "No write since last change @ command
  " line" when we try to call gf, later.
  set hidden

  set winfixbuf

  let l:buffer = bufnr()

  call assert_fails("normal [f", "E1513:")
  call assert_equal(l:buffer, bufnr())

  set nowinfixbuf

  normal [f
  call assert_notequal(l:buffer, bufnr())

  set nohidden
endfunc

" Fail to go to a C macro with [<C-d> if 'winfixbuf' is enabled
func Test_normal_square_bracket_left_ctrl_d()
  call s:reset_all_buffers()

  let l:include_file = tempname() .. ".h"
  call writefile(["min(1, 12);",
        \ $'#include "{l:include_file}"'
        \ ],
        \ "main.c", 'D')
  call writefile(["#define min(X, Y)  ((X) < (Y) ? (X) : (Y))"], l:include_file, 'D')
  edit main.c
  normal ]\<C-d>

  set winfixbuf

  let l:current = bufnr()

  call assert_fails("normal [\<C-d>", "E1513:")
  call assert_equal(l:current, bufnr())

  set nowinfixbuf

  exe "normal [\<C-d>"
  call assert_notequal(l:current, bufnr())
endfunc

" Fail to go to a C macro with ]<C-d> if 'winfixbuf' is enabled
func Test_normal_square_bracket_right_ctrl_d()
  call s:reset_all_buffers()

  let l:include_file = tempname() .. ".h"
  call writefile(["min(1, 12);",
        \ $'#include "{l:include_file}"'
        \ ],
        \ "main.c", 'D')
  call writefile(["#define min(X, Y)  ((X) < (Y) ? (X) : (Y))"], l:include_file, 'D')
  edit main.c

  set winfixbuf

  let l:current = bufnr()

  call assert_fails("normal ]\<C-d>", "E1513:")
  call assert_equal(l:current, bufnr())

  set nowinfixbuf

  exe "normal ]\<C-d>"
  call assert_notequal(l:current, bufnr())
endfunc

" Fail to go to a C macro with [<C-i> if 'winfixbuf' is enabled
func Test_normal_square_bracket_left_ctrl_i()
  call s:reset_all_buffers()

  let l:include_file = tempname() .. ".h"
  call writefile([$'#include "{l:include_file}"',
        \ "min(1, 12);",
        \ ],
        \ "main.c", 'D')
  call writefile(["#define min(X, Y)  ((X) < (Y) ? (X) : (Y))"], l:include_file, 'D')
  edit main.c
  " Move to the line with `min(1, 12);` on it"
  normal j

  set define=^\\s*#\\s*define
  set include=^\\s*#\\s*include
  set path=.,/usr/include,,

  let l:current = bufnr()

  set winfixbuf

  call assert_fails("normal [\<C-i>", "E1513:")

  set nowinfixbuf

  exe "normal [\<C-i>"
  call assert_notequal(l:current, bufnr())

  set define&
  set include&
  set path&
endfunc

" Fail to go to a C macro with ]<C-i> if 'winfixbuf' is enabled
func Test_normal_square_bracket_right_ctrl_i()
  call s:reset_all_buffers()

  let l:include_file = tempname() .. ".h"
  call writefile(["min(1, 12);",
        \ $'#include "{l:include_file}"'
        \ ],
        \ "main.c", 'D')
  call writefile(["#define min(X, Y)  ((X) < (Y) ? (X) : (Y))"], l:include_file, 'D')
  edit main.c

  set winfixbuf

  set define=^\\s*#\\s*define
  set include=^\\s*#\\s*include
  set path=.,/usr/include,,

  let l:current = bufnr()

  call assert_fails("normal ]\<C-i>", "E1513:")
  call assert_equal(l:current, bufnr())

  set nowinfixbuf

  exe "normal ]\<C-i>"
  call assert_notequal(l:current, bufnr())

  set define&
  set include&
  set path&
endfunc

" Fail "goto file under the cursor" (using ]f, which is the same as `:normal gf`)
func Test_normal_square_bracket_right_f()
  call s:reset_all_buffers()

  let l:file = tempname()
  call append(0, [l:file])
  call writefile([], l:file, 'D')
  " Place the cursor onto the line that has `l:file`
  normal gg
  " Prevent Vim from erroring with "No write since last change @ command
  " line" when we try to call gf, later.
  set hidden

  set winfixbuf

  let l:buffer = bufnr()

  call assert_fails("normal ]f", "E1513:")
  call assert_equal(l:buffer, bufnr())

  set nowinfixbuf

  normal ]f
  call assert_notequal(l:buffer, bufnr())

  set nohidden
endfunc

" Fail to jump to a tag with v<C-]> if 'winfixbuf' is enabled
func Test_normal_v_ctrl_square_bracket_right()
  call s:reset_all_buffers()

  set tags=Xtags
  call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
        \ "one\tXfile\t1",
        \ "three\tXfile\t3",
        \ "two\tXfile\t2"],
        \ "Xtags", 'D')
  call writefile(["one", "two", "three"], "Xfile", 'D')
  call writefile(["one"], "Xother", 'D')
  edit Xother

  set winfixbuf

  let l:current = bufnr()

  call assert_fails("normal v\<C-]>", "E1513:")
  call assert_equal(l:current, bufnr())

  set tags&
endfunc

" Fail to jump to a tag with vg<C-]> if 'winfixbuf' is enabled
func Test_normal_v_g_ctrl_square_bracket_right()
  call s:reset_all_buffers()

  set tags=Xtags
  call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
        \ "one\tXfile\t1",
        \ "three\tXfile\t3",
        \ "two\tXfile\t2"],
        \ "Xtags", 'D')
  call writefile(["one", "two", "three"], "Xfile", 'D')
  call writefile(["one"], "Xother", 'D')
  edit Xother

  set winfixbuf

  let l:current = bufnr()

  call assert_fails("normal vg\<C-]>", "E1513:")
  call assert_equal(l:current, bufnr())

  set tags&
endfunc

" Allow :pedit because, unlike :edit, it uses a separate window
func Test_pedit()
  call s:reset_all_buffers()

  let l:other = s:make_buffer_pairs()

  pedit other

  exe "normal \<C-w>w"
  call assert_equal(l:other, bufnr())
endfunc

" Allow :pbuffer because, unlike :buffer, it uses a separate window
func Test_pbuffer()
  call s:reset_all_buffers()

  let l:other = s:make_buffer_pairs()

  exe $'pbuffer {l:other}'

  exe "normal \<C-w>w"
  call assert_equal(l:other, bufnr())
endfunc

" Fail :pop but :pop! is allowed
func Test_pop()
  call s:reset_all_buffers()

  set tags=Xtags
  call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
        \ "thesame\tXfile\t1;\"\td\tfile:",
        \ "thesame\tXfile\t2;\"\td\tfile:",
        \ "thesame\tXfile\t3;\"\td\tfile:",
        \ ],
        \ "Xtags", 'D')
  call writefile(["thesame one", "thesame two", "thesame three"], "Xfile", 'D')
  call writefile(["thesame one"], "Xother", 'D')
  edit Xother

  tag thesame

  set winfixbuf

  let l:current = bufnr()

  call assert_fails("pop", "E1513:")
  call assert_equal(l:current, bufnr())

  pop!
  call assert_notequal(l:current, bufnr())

  set tags&
endfunc

" Fail :previous but :previous! is allowed
func Test_previous()
  call s:reset_all_buffers()

  let [l:first, _] = s:make_args_list()
  next!

  call assert_fails("previous", "E1513:")
  call assert_notequal(l:first, bufnr())

  previous!
  call assert_equal(l:first, bufnr())
endfunc

" Fail pyxdo if it changes a window with 'winfixbuf' is set
func Test_pythonx_pyxdo()
  CheckFeature pythonx
  call s:reset_all_buffers()

  enew
  file first
  let g:_previous_buffer = bufnr()

  enew
  file second

  set winfixbuf

  pythonx << EOF
import vim

def test_winfixbuf_Test_pythonx_pyxdo_set_buffer():
    buffer = vim.vars['_previous_buffer']
    vim.current.buffer = vim.buffers[buffer]
EOF

  try
    pyxdo test_winfixbuf_Test_pythonx_pyxdo_set_buffer()
  catch /Vim\%((\a\+)\)\=:E1513:/
    let l:caught = 1
  endtry

  call assert_equal(1, l:caught)

  unlet g:_previous_buffer
endfunc

" Fail pyxfile if it changes a window with 'winfixbuf' is set
func Test_pythonx_pyxfile()
  CheckFeature pythonx
  call s:reset_all_buffers()

  enew
  file first
  let g:_previous_buffer = bufnr()

  enew
  file second

  set winfixbuf

  call writefile(["import vim",
        \ "buffer = vim.vars['_previous_buffer']",
        \ "vim.current.buffer = vim.buffers[buffer]",
        \ ],
        \ "file.py", 'D')

  try
    pyxfile file.py
  catch /Vim\%((\a\+)\)\=:E1513:/
    let l:caught = 1
  endtry

  call assert_equal(1, l:caught)

  unlet g:_previous_buffer
endfunc

" Fail vim.current.buffer if 'winfixbuf' is set
func Test_pythonx_vim_current_buffer()
  CheckFeature pythonx
  call s:reset_all_buffers()

  enew
  file first
  let g:_previous_buffer = bufnr()

  enew
  file second

  let l:caught = 0

  set winfixbuf

  try
    pythonx << EOF
import vim

buffer = vim.vars["_previous_buffer"]
vim.current.buffer = vim.buffers[buffer]
EOF
  catch /Vim\%((\a\+)\)\=:E1513:/
    let l:caught = 1
  endtry

  call assert_equal(1, l:caught)
  unlet g:_previous_buffer
endfunc

" Ensure remapping to a disabled action still triggers failures
func Test_remap_key_fail()
  call s:reset_all_buffers()

  enew
  file first
  let l:first = bufnr()

  enew
  file current
  let l:current = bufnr()

  set winfixbuf

  nnoremap g <C-^>

  call assert_fails("normal g", "E1513:")
  call assert_equal(l:current, bufnr())

  nunmap g
endfunc

" Ensure remapping a disabled key to something valid does trigger any failures
func Test_remap_key_pass()
  call s:reset_all_buffers()

  enew
  file first
  let l:first = bufnr()

  enew
  file current
  let l:current = bufnr()

  set winfixbuf

  call assert_fails("normal \<C-^>", "E1513:")
  call assert_equal(l:current, bufnr())

  " Disallow <C-^> by default but allow it if the command does something else
  nnoremap <C-^> :echo "hello!"

  exe "normal \<C-^>"
  call assert_equal(l:current, bufnr())

  nunmap <C-^>
endfunc

" Fail :rewind but :rewind! is allowed
func Test_rewind()
  call s:reset_all_buffers()

  let [l:first, _] = s:make_args_list()
  next!

  call assert_fails("rewind", "E1513:")
  call assert_notequal(l:first, bufnr())

  rewind!
  call assert_equal(l:first, bufnr())
endfunc

" Allow :sblast because it opens the buffer in a new, split window
func Test_sblast()
  call s:reset_all_buffers()

  let l:other = s:make_buffer_pairs(1)
  bfirst!
  let l:current = bufnr()

  sblast
  call assert_equal(l:other, bufnr())
endfunc

" Fail :sbprevious but :sbprevious! is allowed
func Test_sbprevious()
  call s:reset_all_buffers()

  let l:other = s:make_buffer_pairs()
  let l:current = bufnr()

  sbprevious
  call assert_equal(l:other, bufnr())
endfunc

" Make sure 'winfixbuf' can be set using 'winfixbuf' or 'wfb'
func Test_short_option()
  call s:reset_all_buffers()

  call s:make_buffer_pairs()

  set winfixbuf
  call assert_fails("edit something_else", "E1513:")

  set nowinfixbuf
  set wfb
  call assert_fails("edit another_place", "E1513:")

  set nowfb
  edit last_place
endfunc

" Allow :snext because it makes a new window
func Test_snext()
  call s:reset_all_buffers()

  let [l:first, _] = s:make_args_list()
  first!

  let l:current_window = win_getid()

  snext
  call assert_notequal(l:current_window, win_getid())
  call assert_notequal(l:first, bufnr())
endfunc

" Ensure the first has 'winfixbuf' and a new split window is 'nowinfixbuf'
func Test_split_window()
  call s:reset_all_buffers()

  split
  exe "normal \<C-w>j"

  set winfixbuf

  let l:winfix_window_1 = win_getid()
  vsplit
  let l:winfix_window_2 = win_getid()

  call assert_equal(1, getwinvar(l:winfix_window_1, "&winfixbuf"))
  call assert_equal(0, getwinvar(l:winfix_window_2, "&winfixbuf"))
endfunc

" Fail :tNext but :tNext! is allowed
func Test_tNext()
  call s:reset_all_buffers()

  set tags=Xtags
  call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
        \ "thesame\tXfile\t1;\"\td\tfile:",
        \ "thesame\tXfile\t2;\"\td\tfile:",
        \ "thesame\tXfile\t3;\"\td\tfile:",
        \ ],
        \ "Xtags", 'D')
  call writefile(["thesame one", "thesame two", "thesame three"], "Xfile", 'D')
  call writefile(["thesame one"], "Xother", 'D')
  edit Xother

  tag thesame
  exe "normal \<C-^>"
  tnext!

  set winfixbuf

  let l:current = bufnr()

  call assert_fails("tNext", "E1513:")
  call assert_equal(l:current, bufnr())

  tNext!

  set tags&
endfunc

" Call :tabdo and choose the next available 'nowinfixbuf' window.
func Test_tabdo_choose_available_window()
  call s:reset_all_buffers()

  let [l:first, _] = s:make_args_list()

  " Make a split window that is 'nowinfixbuf' but make it the second-to-last
  " window so that :tabdo will first try the 'winfixbuf' window, pass over it,
  " and prefer the other 'nowinfixbuf' window, instead.
  "
  " +-------------------+
  " |   'nowinfixbuf'   |
  " +-------------------+
  " |    'winfixbuf'    |  <-- Cursor is here
  " +-------------------+
  split
  let l:nowinfixbuf_window = win_getid()
  " Move to the 'winfixbuf' window now
  exe "normal \<C-w>j"
  let l:winfixbuf_window = win_getid()

  let l:expected_windows = s:get_windows_count()
  tabdo echo ''
  call assert_equal(l:nowinfixbuf_window, win_getid())
  call assert_equal(l:first, bufnr())
  call assert_equal(l:expected_windows, s:get_windows_count())
endfunc

" Call :tabdo and create a new split window if all available windows are 'winfixbuf'.
func Test_tabdo_make_new_window()
  call s:reset_all_buffers()

  let [l:first, _] = s:make_buffers_list()
  exe $"buffer! {l:first}"

  let l:current = win_getid()
  let l:current_windows = s:get_windows_count()

  tabdo echo ''
  call assert_notequal(l:current, win_getid())
  call assert_equal(l:first, bufnr())
  exe "normal \<C-w>j"
  call assert_equal(l:first, bufnr())
  call assert_equal(l:current_windows + 1, s:get_windows_count())
endfunc

" Fail :tag but :tag! is allowed
func Test_tag()
  call s:reset_all_buffers()

  set tags=Xtags
  call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
        \ "one\tXfile\t1",
        \ "three\tXfile\t3",
        \ "two\tXfile\t2"],
        \ "Xtags", 'D')
  call writefile(["one", "two", "three"], "Xfile", 'D')
  call writefile(["one"], "Xother", 'D')
  edit Xother

  set winfixbuf

  let l:current = bufnr()

  call assert_fails("tag one", "E1513:")
  call assert_equal(l:current, bufnr())

  tag! one
  call assert_notequal(l:current, bufnr())

  set tags&
endfunc


" Fail :tfirst but :tfirst! is allowed
func Test_tfirst()
  call s:reset_all_buffers()

  set tags=Xtags
  call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
        \ "one\tXfile\t1",
        \ "three\tXfile\t3",
        \ "two\tXfile\t2"],
        \ "Xtags", 'D')
  call writefile(["one", "two", "three"], "Xfile", 'D')
  call writefile(["one"], "Xother", 'D')
  tag one
  edit Xother

  set winfixbuf

  let l:current = bufnr()

  call assert_fails("tfirst", "E1513:")
  call assert_equal(l:current, bufnr())

  tfirst!
  call assert_notequal(l:current, bufnr())

  set tags&
endfunc

" Fail :tjump but :tjump! is allowed
func Test_tjump()
  call s:reset_all_buffers()

  set tags=Xtags
  call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
        \ "one\tXfile\t1",
        \ "three\tXfile\t3",
        \ "two\tXfile\t2"],
        \ "Xtags", 'D')
  call writefile(["one", "two", "three"], "Xfile", 'D')
  call writefile(["one"], "Xother", 'D')
  edit Xother

  set winfixbuf

  let l:current = bufnr()

  call assert_fails("tjump one", "E1513:")
  call assert_equal(l:current, bufnr())

  tjump! one
  call assert_notequal(l:current, bufnr())

  set tags&
endfunc

" Fail :tlast but :tlast! is allowed
func Test_tlast()
  call s:reset_all_buffers()

  set tags=Xtags
  call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
        \ "one\tXfile\t1",
        \ "three\tXfile\t3",
        \ "two\tXfile\t2"],
        \ "Xtags", 'D')
  call writefile(["one", "two", "three"], "Xfile", 'D')
  edit Xfile
  tjump one
  edit Xfile

  set winfixbuf

  let l:current = bufnr()

  call assert_fails("tlast", "E1513:")
  call assert_equal(l:current, bufnr())

  tlast!
  call assert_equal(l:current, bufnr())

  set tags&
endfunc

" Fail :tnext but :tnext! is allowed
func Test_tnext()
  call s:reset_all_buffers()

  set tags=Xtags
  call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
        \ "thesame\tXfile\t1;\"\td\tfile:",
        \ "thesame\tXfile\t2;\"\td\tfile:",
        \ "thesame\tXfile\t3;\"\td\tfile:",
        \ ],
        \ "Xtags", 'D')
  call writefile(["thesame one", "thesame two", "thesame three"], "Xfile", 'D')
  call writefile(["thesame one"], "Xother", 'D')
  edit Xother

  tag thesame
  exe "normal \<C-^>"

  set winfixbuf

  let l:current = bufnr()

  call assert_fails("tnext", "E1513:")
  call assert_equal(l:current, bufnr())

  tnext!
  call assert_notequal(l:current, bufnr())

  set tags&
endfunc

" Fail :tprevious but :tprevious! is allowed
func Test_tprevious()
  call s:reset_all_buffers()

  set tags=Xtags
  call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
        \ "thesame\tXfile\t1;\"\td\tfile:",
        \ "thesame\tXfile\t2;\"\td\tfile:",
        \ "thesame\tXfile\t3;\"\td\tfile:",
        \ ],
        \ "Xtags", 'D')
  call writefile(["thesame one", "thesame two", "thesame three"], "Xfile", 'D')
  call writefile(["thesame one"], "Xother", 'D')
  edit Xother

  tag thesame
  exe "normal \<C-^>"
  tnext!

  set winfixbuf

  let l:current = bufnr()

  call assert_fails("tprevious", "E1513:")
  call assert_equal(l:current, bufnr())

  tprevious!

  set tags&
endfunc

" Fail :view but :view! is allowed
func Test_view()
  call s:reset_all_buffers()

  let l:other = s:make_buffer_pairs()
  let l:current = bufnr()

  call assert_fails("view other", "E1513:")
  call assert_equal(l:current, bufnr())

  view! other
  call assert_equal(l:other, bufnr())
endfunc

" Fail :visual but :visual! is allowed
func Test_visual()
  call s:reset_all_buffers()

  let l:other = s:make_buffer_pairs()
  let l:current = bufnr()

  call assert_fails("visual other", "E1513:")
  call assert_equal(l:current, bufnr())

  visual! other
  call assert_equal(l:other, bufnr())
endfunc

" Fail :vimgrep but :vimgrep! is allowed
func Test_vimgrep()
  CheckFeature quickfix
  call s:reset_all_buffers()

  edit first.unittest
  call append(0, ["some-search-term"])
  write

  edit winfix.unittest
  call append(0, ["some-search-term"])
  write
  let l:current = bufnr()

  set winfixbuf

  edit! last.unittest
  call append(0, ["some-search-term"])
  write
  let l:last = bufnr()

  buffer! winfix.unittest

  call assert_fails("vimgrep /some-search-term/ *.unittest", "E1513: Cannot switch buffer. 'winfixbuf' is enabled")
  call assert_equal(l:current, bufnr())

  " Don't error and also do swap to the first match because ! was included
  vimgrep! /some-search-term/ *.unittest
  call assert_notequal(l:current, bufnr())

  call delete("first.unittest")
  call delete("winfix.unittest")
  call delete("last.unittest")
endfunc

" Fail :vimgrepadd but ::vimgrepadd! is allowed
func Test_vimgrepadd()
  CheckFeature quickfix
  call s:reset_all_buffers()

  edit first.unittest
  call append(0, ["some-search-term"])
  write

  edit winfix.unittest
  call append(0, ["some-search-term"])
  write
  let l:current = bufnr()

  set winfixbuf

  edit! last.unittest
  call append(0, ["some-search-term"])
  write
  let l:last = bufnr()

  buffer! winfix.unittest

  call assert_fails("vimgrepadd /some-search-term/ *.unittest", "E1513: Cannot switch buffer. 'winfixbuf' is enabled")
  call assert_equal(l:current, bufnr())

  vimgrepadd! /some-search-term/ *.unittest
  call assert_notequal(l:current, bufnr())
  call delete("first.unittest")
  call delete("winfix.unittest")
  call delete("last.unittest")
endfunc

" Fail :wNext but :wNext! is allowed
func Test_wNext()
  call s:reset_all_buffers()

  let [l:first, _] = s:make_args_list()
  next!

  call assert_fails("wNext", "E1513:")
  call assert_notequal(l:first, bufnr())

  wNext!
  call assert_equal(l:first, bufnr())

  call delete("first")
  call delete("middle")
  call delete("last")
endfunc

" Allow :windo unless `:windo foo` would change a 'winfixbuf' window's buffer
func Test_windo()
  call s:reset_all_buffers()

  let l:current_window = win_getid()
  let l:current_buffer = bufnr()
  split
  enew
  file some_other_buffer

  set winfixbuf

  let l:current = win_getid()

  windo echo ''
  call assert_equal(l:current_window, win_getid())

  call assert_fails($'exe "windo buffer {l:current_buffer}"', "E1513:")
  call assert_equal(l:current_window, win_getid())

  exe $"windo buffer! {l:current_buffer}"
  call assert_equal(l:current_window, win_getid())
endfunc

" Fail :wnext but :wnext! is allowed
func Test_wnext()
  call s:reset_all_buffers()

  let [_, l:last] = s:make_args_list()
  next!

  call assert_fails("wnext", "E1513:")
  call assert_notequal(l:last, bufnr())

  wnext!
  call assert_equal(l:last, bufnr())

  call delete("first")
  call delete("middle")
  call delete("last")
endfunc

" Fail :wprevious but :wprevious! is allowed
func Test_wprevious()
  call s:reset_all_buffers()

  let [l:first, _] = s:make_args_list()
  next!

  call assert_fails("wprevious", "E1513:")
  call assert_notequal(l:first, bufnr())

  wprevious!
  call assert_equal(l:first, bufnr())

  call delete("first")
  call delete("middle")
  call delete("last")
endfunc

func Test_quickfix_switchbuf_invalid_prevwin()
  call s:reset_all_buffers()

  call s:make_simple_quickfix()
  call assert_equal(1, getqflist(#{idx: 0}).idx)

  set switchbuf=uselast
  split
  copen
  exe winnr('#') 'quit'
  call assert_equal(2, winnr('$'))

  cnext  " Would've triggered a null pointer member access
  call assert_equal(2, getqflist(#{idx: 0}).idx)

  set switchbuf&
endfunc

func Test_listdo_goto_prevwin()
  call s:reset_all_buffers()
  call s:make_buffers_list()

  new
  call assert_equal(0, &winfixbuf)
  wincmd p
  call assert_equal(1, &winfixbuf)
  call assert_notequal(bufnr(), bufnr('#'))

  augroup ListDoGotoPrevwin
    au!
    au BufLeave * let s:triggered = 1
          \| call assert_equal(bufnr(), winbufnr(winnr()))
  augroup END
  " Should correctly switch to the window without 'winfixbuf', and curbuf should
  " be consistent with curwin->w_buffer for autocommands.
  bufdo "
  call assert_equal(0, &winfixbuf)
  call assert_equal(1, s:triggered)
  unlet! s:triggered
  au! ListDoGotoPrevwin

  set winfixbuf
  wincmd p
  call assert_equal(2, winnr('$'))
  " Both curwin and prevwin have 'winfixbuf' set, so should split a new window
  " without it set.
  bufdo "
  call assert_equal(0, &winfixbuf)
  call assert_equal(3, winnr('$'))

  quit
  call assert_equal(2, winnr('$'))
  call assert_equal(1, &winfixbuf)
  augroup ListDoGotoPrevwin
    au!
    au WinEnter * ++once set winfixbuf
  augroup END
  " Same as before, but naughty autocommands set 'winfixbuf' for the new window.
  " :bufdo should give up in this case.
  call assert_fails('bufdo "', 'E1513:')

  au! ListDoGotoPrevwin
  augroup! ListDoGotoPrevwin
endfunc

func Test_quickfix_changed_split_failed()
  call s:reset_all_buffers()

  call s:make_simple_quickfix()
  call assert_equal(1, winnr('$'))

  " Quickfix code will open a split in an attempt to get a 'nowinfixbuf' window
  " to switch buffers in.  Interfere with things by setting 'winfixbuf' in it.
  augroup QfChanged
    au!
    au WinEnter * ++once call assert_equal(2, winnr('$'))
          \| set winfixbuf | call setqflist([], 'f')
  augroup END
  call assert_fails('cnext', ['E1513:', 'E925:'])
  " Check that the split was automatically closed.
  call assert_equal(1, winnr('$'))

  au! QfChanged
  augroup! QfChanged
endfunc

func Test_bufdo_cnext_splitwin_fails()
  call s:reset_all_buffers()
  call s:make_simple_quickfix()
  call assert_equal(1, getqflist(#{idx: 0}).idx)
  " Make sure there is not enough room to
  " split the winfixedbuf window
  let &winheight=&lines
  let &winminheight=&lines-2
  " Still want E1513, or it may not be clear why a split was attempted and why
  " it failing caused the commands to abort.
  call assert_fails(':bufdo echo 1', ['E36:', 'E1513:'])
  call assert_fails(':cnext', ['E36:', 'E1513:'])
  " Ensure the entry didn't change.
  call assert_equal(1, getqflist(#{idx: 0}).idx)
  set winminheight&vim winheight&vim
endfunc

" Test that exiting with 'winfixbuf' and EXITFREE doesn't cause an error.
func Test_exitfree_no_error()
  let lines =<< trim END
    set winfixbuf
    qall!
  END
  call writefile(lines, 'Xwfb_exitfree', 'D')
  call assert_notmatch('E1513:',
        \ system(GetVimCommandClean() .. ' --not-a-term -X -S Xwfb_exitfree'))
endfunc

" vim: shiftwidth=2 sts=2 expandtab
