neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

startup_spec.lua (56309B)


      1 local t = require('test.testutil')
      2 local n = require('test.functional.testnvim')()
      3 local Screen = require('test.functional.ui.screen')
      4 
      5 local assert_alive = n.assert_alive
      6 local assert_log = t.assert_log
      7 local clear = n.clear
      8 local command = n.command
      9 local ok = t.ok
     10 local eq = t.eq
     11 local matches = t.matches
     12 local eval = n.eval
     13 local exec = n.exec
     14 local exec_capture = n.exec_capture
     15 local exec_lua = n.exec_lua
     16 local feed = n.feed
     17 local fn = n.fn
     18 local pesc = vim.pesc
     19 local mkdir = t.mkdir
     20 local mkdir_p = n.mkdir_p
     21 local nvim_prog = n.nvim_prog
     22 local nvim_set = n.nvim_set
     23 local read_file = t.read_file
     24 local retry = t.retry
     25 local rmdir = n.rmdir
     26 local sleep = vim.uv.sleep
     27 local startswith = vim.startswith
     28 local write_file = t.write_file
     29 local api = n.api
     30 local is_os = t.is_os
     31 local dedent = t.dedent
     32 local tbl_map = vim.tbl_map
     33 local tbl_filter = vim.tbl_filter
     34 local endswith = vim.endswith
     35 local check_close = n.check_close
     36 
     37 local testlog = 'Xtest-startupspec-log'
     38 
     39 describe('startup', function()
     40  it('--clean', function()
     41    clear()
     42    matches(
     43      vim.pesc(t.fix_slashes(fn.stdpath('config'))),
     44      t.fix_slashes(api.nvim_get_option_value('runtimepath', {}))
     45    )
     46 
     47    clear('--clean')
     48    ok(
     49      not t.fix_slashes(api.nvim_get_option_value('runtimepath', {}))
     50        :match(vim.pesc(t.fix_slashes(fn.stdpath('config'))))
     51    )
     52  end)
     53 
     54  it('prevents remote UI infinite loop', function()
     55    clear()
     56    local screen = Screen.new(84, 3)
     57    fn.jobstart(
     58      { nvim_prog, '-u', 'NONE', '--server', eval('v:servername'), '--remote-ui' },
     59      { term = true }
     60    )
     61    screen:expect([[
     62      ^Cannot attach UI of :terminal child to its parent. (Unset $NVIM to skip this check) |
     63                                                                                          |*2
     64    ]])
     65  end)
     66 
     67  it('--startuptime', function()
     68    local testfile = 'Xtest_startuptime'
     69    finally(function()
     70      os.remove(testfile)
     71    end)
     72    clear({ args = { '--startuptime', testfile } })
     73    assert_log('Embedded', testfile, 100)
     74    assert_log('sourcing', testfile, 100)
     75    assert_log("require%('vim%._core.editor'%)", testfile, 100)
     76  end)
     77 
     78  it('--startuptime does not crash on error #31125', function()
     79    local p = n.spawn_wait('--startuptime', '.', '-c', '42cquit')
     80    eq("E484: Can't open file .", p.stderr)
     81    eq(42, p.status)
     82  end)
     83 
     84  it('-D does not hang #12647', function()
     85    clear()
     86    local screen = Screen.new(60, 7)
     87    -- not the same colors on windows for some reason
     88    screen._default_attr_ids = nil
     89    local id = fn.jobstart({
     90      nvim_prog,
     91      '-u',
     92      'NONE',
     93      '-i',
     94      'NONE',
     95      '--cmd',
     96      'set noruler',
     97      '-D',
     98    }, {
     99      term = true,
    100      env = {
    101        VIMRUNTIME = os.getenv('VIMRUNTIME'),
    102      },
    103    })
    104    screen:expect({ any = pesc('Entering Debug mode.  Type "cont" to continue.') })
    105    fn.chansend(id, 'cont\n')
    106    screen:expect([[
    107      ^                                                            |
    108      ~                                                           |*3
    109      [No Name]                                                   |
    110                                                                  |*2
    111    ]])
    112  end)
    113 
    114  it(':filetype detect enables filetype detection with -u NONE', function()
    115    clear()
    116    eq('filetype detection:OFF  plugin:OFF  indent:OFF', exec_capture('filetype'))
    117    command('filetype detect')
    118    eq('filetype detection:ON  plugin:OFF  indent:OFF', exec_capture('filetype'))
    119  end)
    120 end)
    121 
    122 describe('startup', function()
    123  before_each(clear)
    124 
    125  after_each(function()
    126    check_close()
    127    os.remove(testlog)
    128  end)
    129 
    130  describe('-l Lua', function()
    131    local function assert_l_out(expected, nvim_args, lua_args, script, input)
    132      local args = { nvim_prog }
    133      vim.list_extend(args, nvim_args or {})
    134      vim.list_extend(args, { '-l', (script or 'test/functional/fixtures/startup.lua') })
    135      vim.list_extend(args, lua_args or {})
    136      local out = fn.system(args, input):gsub('\r\n', '\n')
    137      if type(expected) == 'function' then
    138        return expected(out)
    139      else
    140        return eq(dedent(expected), out)
    141      end
    142    end
    143 
    144    it('failure modes', function()
    145      -- nvim -l <empty>
    146      local proc = n.spawn_wait('-l')
    147      matches('nvim%.?e?x?e?: Argument missing after: "%-l"', proc.stderr)
    148      eq(1, proc.status)
    149    end)
    150 
    151    it('os.exit() sets Nvim exitcode', function()
    152      -- tricky: LeakSanitizer triggers on os.exit() and disrupts the return value, disable it
    153      exec_lua [[
    154        local asan_options = os.getenv('ASAN_OPTIONS') or ''
    155        if asan_options ~= '' then
    156          asan_options = asan_options .. ':'
    157        end
    158        vim.uv.os_setenv('ASAN_OPTIONS', asan_options .. ':detect_leaks=0')
    159      ]]
    160      -- nvim -l foo.lua -arg1 -- a b c
    161      assert_l_out(
    162        [[
    163          bufs:
    164          nvim args: 7
    165          lua args: { "-arg1", "--exitcode", "73", "--arg2",
    166            [0] = "test/functional/fixtures/startup.lua"
    167          }]],
    168        {},
    169        { '-arg1', '--exitcode', '73', '--arg2' }
    170      )
    171      eq(73, eval('v:shell_error'))
    172    end)
    173 
    174    it('Lua-error sets Nvim exitcode', function()
    175      local proc = n.spawn_wait('-l', 'test/functional/fixtures/startup-fail.lua')
    176      matches('E5113: .* my pearls!!', proc:output())
    177      eq(0, proc.signal)
    178      eq(1, proc.status)
    179 
    180      eq(0, eval('v:shell_error'))
    181      matches(
    182        'E5113: .* %[string "error%("whoa"%)"%]:1: whoa',
    183        fn.system({ nvim_prog, '-l', '-' }, 'error("whoa")')
    184      )
    185      eq(1, eval('v:shell_error'))
    186    end)
    187 
    188    it('executes stdin "-"', function()
    189      assert_l_out(
    190        'arg0=- args=2 whoa\n',
    191        nil,
    192        { 'arg1', 'arg 2' },
    193        '-',
    194        "print(('arg0=%s args=%d %s'):format(_G.arg[0], #_G.arg, 'whoa'))"
    195      )
    196      assert_l_out(
    197        'biiig input: 1000042\n',
    198        nil,
    199        nil,
    200        '-',
    201        ('print("biiig input: "..("%s"):len())'):format(string.rep('x', (1000 * 1000) + 42))
    202      )
    203      eq(0, eval('v:shell_error'))
    204    end)
    205 
    206    it('does not truncate long print() message', function()
    207      assert_l_out(('k'):rep(1234) .. '\n', nil, nil, '-', "print(('k'):rep(1234))")
    208    end)
    209 
    210    it('does not add newline when unnecessary', function()
    211      assert_l_out('', nil, nil, '-', '')
    212      assert_l_out('foobar\n', nil, nil, '-', [[print('foobar\n')]])
    213    end)
    214 
    215    it('sets _G.arg', function()
    216      -- nvim -l foo.lua
    217      assert_l_out(
    218        [[
    219          bufs:
    220          nvim args: 3
    221          lua args: {
    222            [0] = "test/functional/fixtures/startup.lua"
    223          }
    224          ]],
    225        {},
    226        {}
    227      )
    228      eq(0, eval('v:shell_error'))
    229 
    230      -- nvim -l foo.lua [args]
    231      assert_l_out(
    232        [[
    233          bufs:
    234          nvim args: 7
    235          lua args: { "-arg1", "--arg2", "--", "arg3",
    236            [0] = "test/functional/fixtures/startup.lua"
    237          }
    238          ]],
    239        {},
    240        { '-arg1', '--arg2', '--', 'arg3' }
    241      )
    242      eq(0, eval('v:shell_error'))
    243 
    244      -- nvim file1 file2 -l foo.lua -arg1 -- file3 file4
    245      assert_l_out(
    246        [[
    247          bufs: file1 file2
    248          nvim args: 10
    249          lua args: { "-arg1", "arg 2", "--", "file3", "file4",
    250            [0] = "test/functional/fixtures/startup.lua"
    251          }
    252          ]],
    253        { 'file1', 'file2' },
    254        { '-arg1', 'arg 2', '--', 'file3', 'file4' }
    255      )
    256      eq(0, eval('v:shell_error'))
    257 
    258      -- nvim -l foo.lua <vim args>
    259      assert_l_out(
    260        [[
    261          bufs:
    262          nvim args: 5
    263          lua args: { "-c", "set wrap?",
    264            [0] = "test/functional/fixtures/startup.lua"
    265          }
    266          ]],
    267        {},
    268        { '-c', 'set wrap?' }
    269      )
    270      eq(0, eval('v:shell_error'))
    271 
    272      -- nvim <vim args> -l foo.lua <vim args>
    273      assert_l_out(
    274        [[
    275            wrap
    276          bufs:
    277          nvim args: 7
    278          lua args: { "-c", "set wrap?",
    279            [0] = "test/functional/fixtures/startup.lua"
    280          }
    281          ]],
    282        { '-c', 'set wrap?' },
    283        { '-c', 'set wrap?' }
    284      )
    285      eq(0, eval('v:shell_error'))
    286    end)
    287 
    288    it('disables swapfile/shada/config/plugins unless overridden', function()
    289      local script = [[print(('updatecount=%d shadafile=%s loadplugins=%s scripts=%d'):format(
    290                       vim.o.updatecount, vim.o.shadafile, tostring(vim.o.loadplugins), math.max(1, #vim.fn.getscriptinfo())))]]
    291      finally(function()
    292        os.remove('Xtest_shada')
    293      end)
    294 
    295      assert_l_out(
    296        'updatecount=0 shadafile=NONE loadplugins=false scripts=1\n',
    297        nil,
    298        nil,
    299        '-',
    300        script
    301      )
    302 
    303      -- User can override.
    304      assert_l_out(
    305        function(out)
    306          return matches('updatecount=99 shadafile=Xtest_shada loadplugins=true scripts=2%d\n', out)
    307        end,
    308        { '+set updatecount=99', '-i', 'Xtest_shada', '+set loadplugins', '-u', 'NORC' },
    309        nil,
    310        '-',
    311        script
    312      )
    313    end)
    314  end)
    315 
    316  it('--cmd/-c/+ do not truncate long Lua print() message with --headless', function()
    317    local out = fn.system({
    318      nvim_prog,
    319      '-u',
    320      'NONE',
    321      '-i',
    322      'NONE',
    323      '--headless',
    324      '--cmd',
    325      'lua print(("A"):rep(1234))',
    326      '-c',
    327      'lua print(("B"):rep(1234))',
    328      '+lua print(("C"):rep(1234))',
    329      '+q',
    330    })
    331    eq(('A'):rep(1234) .. '\r\n' .. ('B'):rep(1234) .. '\r\n' .. ('C'):rep(1234), out)
    332  end)
    333 
    334  it('pipe at both ends: has("ttyin")==0 has("ttyout")==0', function()
    335    -- system() puts a pipe at both ends.
    336    local out = fn.system({
    337      nvim_prog,
    338      '-u',
    339      'NONE',
    340      '-i',
    341      'NONE',
    342      '--headless',
    343      '--cmd',
    344      nvim_set,
    345      '-c',
    346      [[echo has('ttyin') has('ttyout')]],
    347      '+q',
    348    })
    349    eq('0 0', out)
    350  end)
    351 
    352  it('with --embed: has("ttyin")==0 has("ttyout")==0', function()
    353    local screen = Screen.new(25, 3)
    354    -- Remote UI connected by --embed.
    355    -- TODO: a lot of tests in this file already use the new default color scheme.
    356    -- once we do the batch update of tests to use it, remove this workaround
    357    screen._default_attr_ids = nil
    358    command([[echo has('ttyin') has('ttyout')]])
    359    screen:expect([[
    360      ^                         |
    361      ~                        |
    362      0 0                      |
    363    ]])
    364  end)
    365 
    366  it('in a TTY: has("ttyin")==1 has("ttyout")==1', function()
    367    local screen = Screen.new(25, 4)
    368    screen._default_attr_ids = nil
    369    if is_os('win') then
    370      command([[set shellcmdflag=/s\ /c shellxquote=\"]])
    371    end
    372    -- Running in :terminal
    373    fn.jobstart({
    374      nvim_prog,
    375      '-u',
    376      'NONE',
    377      '-i',
    378      'NONE',
    379      '--cmd',
    380      nvim_set,
    381      '-c',
    382      'echo has("ttyin") has("ttyout")',
    383    }, {
    384      term = true,
    385      env = {
    386        VIMRUNTIME = os.getenv('VIMRUNTIME'),
    387      },
    388    })
    389    screen:expect([[
    390      ^                         |
    391      ~                        |
    392      1 1                      |
    393                               |
    394    ]])
    395  end)
    396 
    397  it('output to pipe: has("ttyin")==1 has("ttyout")==0', function()
    398    clear({ env = { NVIM_LOG_FILE = testlog } })
    399    if is_os('win') then
    400      command([[set shellcmdflag=/s\ /c shellxquote=\"]])
    401    end
    402    os.remove('Xtest_startup_ttyout')
    403    finally(function()
    404      os.remove('Xtest_startup_ttyout')
    405    end)
    406    -- Running in :terminal
    407    fn.jobstart(
    408      (
    409        [["%s" -u NONE -i NONE --cmd "%s"]]
    410        .. [[ -c "call writefile([has('ttyin'), has('ttyout')], 'Xtest_startup_ttyout')"]]
    411        .. [[ -c q | cat -v]]
    412      ):format(nvim_prog, nvim_set),
    413      {
    414        term = true,
    415        env = {
    416          VIMRUNTIME = os.getenv('VIMRUNTIME'),
    417        },
    418      }
    419    )
    420    retry(nil, 3000, function()
    421      sleep(1)
    422      eq(
    423        '1\n0\n', -- stdin is a TTY, stdout is a pipe
    424        read_file('Xtest_startup_ttyout')
    425      )
    426    end)
    427  end)
    428 
    429  it('input from pipe: has("ttyin")==0 has("ttyout")==1', function()
    430    clear({ env = { NVIM_LOG_FILE = testlog } })
    431    if is_os('win') then
    432      command([[set shellcmdflag=/s\ /c shellxquote=\"]])
    433    end
    434    os.remove('Xtest_startup_ttyout')
    435    finally(function()
    436      os.remove('Xtest_startup_ttyout')
    437    end)
    438    -- Running in :terminal
    439    fn.jobstart(
    440      (
    441        [[echo foo | ]] -- Input from a pipe.
    442        .. [["%s" -u NONE -i NONE --cmd "%s"]]
    443        .. [[ -c "call writefile([has('ttyin'), has('ttyout')], 'Xtest_startup_ttyout')"]]
    444        .. [[ -c q -- -]]
    445      ):format(nvim_prog, nvim_set),
    446      {
    447        term = true,
    448        env = {
    449          VIMRUNTIME = os.getenv('VIMRUNTIME'),
    450        },
    451      }
    452    )
    453    retry(nil, 3000, function()
    454      sleep(1)
    455      eq(
    456        '0\n1\n', -- stdin is a pipe, stdout is a TTY
    457        read_file('Xtest_startup_ttyout')
    458      )
    459    end)
    460  end)
    461 
    462  it('input from pipe (implicit) #7679', function()
    463    clear({ env = { NVIM_LOG_FILE = testlog } })
    464    local screen = Screen.new(25, 4)
    465    screen._default_attr_ids = nil
    466    if is_os('win') then
    467      command([[set shellcmdflag=/s\ /c shellxquote=\"]])
    468    end
    469    -- Running in :terminal
    470    fn.jobstart(
    471      (
    472        [[echo foo | ]]
    473        .. [["%s" -u NONE -i NONE --cmd "%s"]]
    474        .. [[ -c "echo has('ttyin') has('ttyout')"]]
    475      ):format(nvim_prog, nvim_set),
    476      {
    477        term = true,
    478        env = {
    479          VIMRUNTIME = os.getenv('VIMRUNTIME'),
    480        },
    481      }
    482    )
    483    screen:expect([[
    484      ^foo                      |
    485      ~                        |
    486      0 1                      |
    487                               |
    488    ]])
    489    if not is_os('win') then
    490      assert_log('Failed to get flags on descriptor 3: Bad file descriptor', testlog, 100)
    491    end
    492  end)
    493 
    494  it('input from pipe + file args #7679', function()
    495    eq(
    496      'ohyeah\r\n0 0 bufs=3',
    497      fn.system({
    498        nvim_prog,
    499        '-n',
    500        '-u',
    501        'NONE',
    502        '-i',
    503        'NONE',
    504        '--headless',
    505        '+.print',
    506        "+echo has('ttyin') has('ttyout') 'bufs='.bufnr('$')",
    507        '+qall!',
    508        '-',
    509        'test/functional/fixtures/tty-test.c',
    510        'test/functional/fixtures/shell-test.c',
    511      }, { 'ohyeah', '' })
    512    )
    513  end)
    514 
    515  it('if stdin is empty: selects buffer 2, deletes buffer 1 #8561', function()
    516    eq(
    517      '\r\n  2 %a   "file1"                        line 0\r\n  3      "file2"                        line 0',
    518      fn.system({
    519        nvim_prog,
    520        '-n',
    521        '-u',
    522        'NONE',
    523        '-i',
    524        'NONE',
    525        '--headless',
    526        '+ls!',
    527        '+qall!',
    528        '-',
    529        'file1',
    530        'file2',
    531      }, { '' })
    532    )
    533  end)
    534 
    535  it('if stdin is empty and - is last: selects buffer 1, deletes buffer 3 #35269', function()
    536    eq(
    537      '\r\n  1 %a   "file1"                        line 0\r\n  2      "file2"                        line 0',
    538      fn.system({
    539        nvim_prog,
    540        '-n',
    541        '-u',
    542        'NONE',
    543        '-i',
    544        'NONE',
    545        '--headless',
    546        '+ls!',
    547        '+qall!',
    548        'file1',
    549        'file2',
    550        '-',
    551      }, { '' })
    552    )
    553  end)
    554 
    555  it("empty stdin with terminal split doesn't crash #35681", function()
    556    eq(
    557      'nocrash',
    558      fn.system({
    559        nvim_prog,
    560        '-n',
    561        '-u',
    562        'NONE',
    563        '-i',
    564        'NONE',
    565        '--headless',
    566        '--cmd',
    567        'term',
    568        '+split',
    569        '+quit!',
    570        '+bw!',
    571        '+bw!',
    572        '+echo "nocrash"',
    573        "+call timer_start(1, { -> execute('qa') })", -- need to let event handling happen
    574        '-',
    575      }, { '' })
    576    )
    577  end)
    578 
    579  it('stdin with -es/-Es #7679', function()
    580    local input = { 'append', 'line1', 'line2', '.', '%print', '' }
    581    local inputstr = table.concat(input, '\n')
    582 
    583    --
    584    -- -Es: read stdin as text
    585    --
    586    eq(
    587      'partylikeits1999\n',
    588      fn.system({
    589        nvim_prog,
    590        '-n',
    591        '-u',
    592        'NONE',
    593        '-i',
    594        'NONE',
    595        '-Es',
    596        '+.print',
    597        'test/functional/fixtures/tty-test.c',
    598      }, { 'partylikeits1999', '' })
    599    )
    600    eq(inputstr, fn.system({ nvim_prog, '-i', 'NONE', '-Es', '+%print', '-' }, input))
    601    -- with `-u NORC`
    602    eq(
    603      'thepartycontinues\n',
    604      fn.system({ nvim_prog, '-n', '-u', 'NORC', '-Es', '+.print' }, { 'thepartycontinues', '' })
    605    )
    606    -- without `-u`
    607    eq(
    608      'thepartycontinues\n',
    609      fn.system({ nvim_prog, '-n', '-Es', '+.print' }, { 'thepartycontinues', '' })
    610    )
    611 
    612    --
    613    -- -es: read stdin as ex-commands
    614    --
    615    eq(
    616      '  encoding=utf-8\n',
    617      fn.system({
    618        nvim_prog,
    619        '-n',
    620        '-u',
    621        'NONE',
    622        '-i',
    623        'NONE',
    624        '-es',
    625        'test/functional/fixtures/tty-test.c',
    626      }, { 'set encoding', '' })
    627    )
    628    eq('line1\nline2\n', fn.system({ nvim_prog, '-i', 'NONE', '-es', '-' }, input))
    629    -- with `-u NORC`
    630    eq(
    631      '  encoding=utf-8\n',
    632      fn.system({ nvim_prog, '-n', '-u', 'NORC', '-es' }, { 'set encoding', '' })
    633    )
    634    -- without `-u`
    635    eq('  encoding=utf-8\n', fn.system({ nvim_prog, '-n', '-es' }, { 'set encoding', '' }))
    636  end)
    637 
    638  it('-es/-Es disables swapfile/shada/config #8540', function()
    639    for _, arg in ipairs({ '-es', '-Es' }) do
    640      local out = fn.system({
    641        nvim_prog,
    642        arg,
    643        '+set updatecount? shadafile? loadplugins?',
    644        '+put =map(getscriptinfo(), {-> v:val.name})',
    645        '+%print',
    646      })
    647      local line1 = string.match(out, '^.-\n')
    648      -- updatecount=0 means swapfile was disabled.
    649      eq('  updatecount=0  shadafile=NONE  loadplugins\n', line1)
    650      -- Standard plugins were loaded, but not user config. #31878
    651      local nrlines = #vim.split(out, '\n')
    652      ok(nrlines > 20, '>20', nrlines)
    653      ok(string.find(out, 'man.lua') ~= nil)
    654      ok(string.find(out, 'init.vim') == nil)
    655    end
    656  end)
    657 
    658  it('fails on --embed with -es/-Es/-l', function()
    659    matches(
    660      'nvim[.exe]*: %-%-embed conflicts with %-es/%-Es/%-l',
    661      n.spawn_wait('--embed', '-es').stderr
    662    )
    663    matches(
    664      'nvim[.exe]*: %-%-embed conflicts with %-es/%-Es/%-l',
    665      n.spawn_wait('--embed', '-Es').stderr
    666    )
    667    matches(
    668      'nvim[.exe]*: %-%-embed conflicts with %-es/%-Es/%-l',
    669      n.spawn_wait('--embed', '-l', 'foo.lua').stderr
    670    )
    671  end)
    672 
    673  it('-es does not exit early with closed stdin', function()
    674    write_file('Xinput.txt', 'line1\nline2\nline3\nline4\n')
    675    write_file('Xoutput.txt', 'OUT\n')
    676    finally(function()
    677      os.remove('Xinput.txt')
    678      os.remove('Xoutput.txt')
    679    end)
    680    -- Use system() without input so that stdin is closed.
    681    fn.system({
    682      nvim_prog,
    683      '--clean',
    684      '-es',
    685      '-c',
    686      -- 'showcmd' leads to a char_avail() call just after the 'Q' (no more input).
    687      [[set showcmd | exe "g/^/vi|Vgg:w>>Xoutput.txt\rgQ"]],
    688      'Xinput.txt',
    689    })
    690    eq(
    691      'OUT\nline1\nline1\nline2\nline1\nline2\nline3\nline1\nline2\nline3\nline4\n',
    692      read_file('Xoutput.txt')
    693    )
    694  end)
    695 
    696  it('ENTER dismisses early message #7967', function()
    697    local screen
    698    screen = Screen.new(60, 6)
    699    screen._default_attr_ids = nil
    700    local id = fn.jobstart({
    701      nvim_prog,
    702      '-u',
    703      'NONE',
    704      '-i',
    705      'NONE',
    706      '--cmd',
    707      'set noruler',
    708      '--cmd',
    709      'let g:foo = g:bar',
    710    }, {
    711      term = true,
    712      env = {
    713        VIMRUNTIME = os.getenv('VIMRUNTIME'),
    714      },
    715    })
    716    screen:expect([[
    717      ^                                                            |
    718                                                                  |
    719      Error in pre-vimrc command line:                            |
    720      E121: Undefined variable: g:bar                             |
    721      Press ENTER or type command to continue                     |
    722                                                                  |
    723    ]])
    724    fn.chansend(id, '\n')
    725    screen:expect([[
    726      ^                                                            |
    727      ~                                                           |*2
    728      [No Name]                                                   |
    729                                                                  |*2
    730    ]])
    731  end)
    732 
    733  it('-r works without --headless in PTY #23294', function()
    734    exec([[
    735      func Normalize(data) abort
    736        " Windows: remove ^M and term escape sequences
    737        return mapnew(a:data, 'substitute(substitute(v:val, "\r", "", "g"), "\x1b\\%(\\]\\d\\+;.\\{-}\x07\\|\\[.\\{-}[\x40-\x7E]\\)", "", "g")')
    738      endfunc
    739      func OnOutput(id, data, event) dict
    740        let g:stdout = Normalize(a:data)
    741      endfunc
    742      call jobstart([v:progpath, '-u', 'NONE', '-i', 'NONE', '-r'], {
    743      \ 'pty': v:true,
    744      \ 'stdout_buffered': v:true,
    745      \ 'on_stdout': function('OnOutput'),
    746      \ })
    747    ]])
    748    retry(nil, nil, function()
    749      eq('Swap files found:', eval('g:stdout[0]'))
    750    end)
    751  end)
    752 
    753  it('fixed hang issue with --headless (#11386)', function()
    754    local expected = ''
    755    local period = 100
    756    for i = 1, period - 1 do
    757      expected = expected .. i .. '\r\n'
    758    end
    759    expected = expected .. period
    760    eq(
    761      expected,
    762      -- FIXME(codehex): We should really set a timeout for the system function.
    763      -- If this test fails, there will be a waiting input state.
    764      fn.system({
    765        nvim_prog,
    766        '-u',
    767        'NONE',
    768        '-c',
    769        'for i in range(1, 100) | echo i | endfor | quit',
    770        '--headless',
    771      })
    772    )
    773  end)
    774 
    775  it('get command line arguments from v:argv', function()
    776    local p = n.spawn_wait('--cmd', nvim_set, '-c', [[echo v:argv[-1:] len(v:argv) > 1]], '+q')
    777    eq("['+q'] 1", p.stderr)
    778  end)
    779 end)
    780 
    781 describe('startup', function()
    782  it('-e/-E interactive #7679', function()
    783    clear('-e')
    784    local screen = Screen.new(25, 3)
    785    feed("put ='from -e'<CR>")
    786    screen:expect([[
    787      :put ='from -e'          |
    788      from -e                  |
    789      :^                        |
    790    ]])
    791 
    792    clear('-E')
    793    screen = Screen.new(25, 3)
    794    feed("put ='from -E'<CR>")
    795    screen:expect([[
    796      :put ='from -E'          |
    797      from -E                  |
    798      :^                        |
    799    ]])
    800  end)
    801 
    802  it('-e sets ex mode', function()
    803    clear('-e')
    804    local screen = Screen.new(25, 3)
    805    -- Verify we set the proper mode both before and after :vi.
    806    feed('put =mode(1)<CR>vi<CR>:put =mode(1)<CR>')
    807    screen:expect([[
    808      cv                       |
    809      ^n                        |
    810      :put =mode(1)            |
    811    ]])
    812 
    813    eq('cv\n', fn.system({ nvim_prog, '-n', '-es' }, { 'put =mode(1)', 'print', '' }))
    814  end)
    815 
    816  it('-d does not diff non-arglist windows #13720 #21289', function()
    817    write_file(
    818      'Xdiff.vim',
    819      [[
    820      let bufnr = nvim_create_buf(0, 1)
    821      let config = {
    822            \   'relative': 'editor',
    823            \   'focusable': v:false,
    824            \   'width': 1,
    825            \   'height': 1,
    826            \   'row': 3,
    827            \   'col': 3
    828            \ }
    829      autocmd WinEnter * let g:float_win = nvim_open_win(bufnr, v:false, config)
    830    ]]
    831    )
    832    finally(function()
    833      os.remove('Xdiff.vim')
    834    end)
    835    clear { args = { '-u', 'Xdiff.vim', '-d', 'Xdiff.vim', 'Xdiff.vim' } }
    836    eq(true, api.nvim_get_option_value('diff', { win = fn.win_getid(1) }))
    837    eq(true, api.nvim_get_option_value('diff', { win = fn.win_getid(2) }))
    838    local float_win = eval('g:float_win')
    839    eq('editor', api.nvim_win_get_config(float_win).relative)
    840    eq(false, api.nvim_get_option_value('diff', { win = float_win }))
    841  end)
    842 
    843  it('still opens arglist windows if init only opens floating windows', function()
    844    write_file(
    845      'Xfloat.vim',
    846      [[
    847      let config = {
    848            \   'relative': 'editor',
    849            \   'focusable': v:false,
    850            \   'width': 1,
    851            \   'height': 1,
    852            \   'row': 1,
    853            \   'col': 1
    854            \ }
    855      call nvim_open_win(0, v:false, config)
    856    ]]
    857    )
    858    finally(function()
    859      os.remove('Xfloat.vim')
    860    end)
    861    clear { args = { '-u', 'Xfloat.vim', '-d', 'Xfloat.vim', 'foo' } }
    862    local screen = Screen.new(40, 5)
    863    screen:add_extra_attr_ids({
    864      [101] = {
    865        bold = true,
    866        background = Screen.colors.LightBlue,
    867        foreground = Screen.colors.Brown,
    868      },
    869      [102] = { foreground = Screen.colors.SlateBlue, background = Screen.colors.LightBlue },
    870      [103] = { foreground = Screen.colors.Magenta, background = Screen.colors.LightBlue },
    871      [104] = { foreground = Screen.colors.DarkCyan, background = Screen.colors.LightBlue },
    872    })
    873    screen:expect([[
    874      {7:  }{101:^let}{22: config }{101:=}{22: }{102:{}{22:                      }│{23:-}|
    875      {7: }{101:l}{22:      }{102:\}{22:   }{103:'relative'}{22:: }{103:'editor'}{22:,     }│{23:-}|
    876      {7:  }{22:      }{102:\}{22:   }{103:'focusable'}{22:: }{104:v:}{22:false,     }│ |
    877      {3:Xfloat.vim                             }{2:<}|
    878                                              |
    879    ]])
    880  end)
    881 
    882  it("default 'diffopt' is applied with -d", function()
    883    clear({
    884      args = {
    885        '-d',
    886        'test/functional/fixtures/diff/startup_old.txt',
    887        'test/functional/fixtures/diff/startup_new.txt',
    888        '--cmd',
    889        'set laststatus=0',
    890      },
    891    })
    892    local screen = Screen.new(80, 24)
    893    screen:expect([[
    894      {7:+ }{13:^+-- 15 lines: package main············}│{7:+ }{13:+-- 15 lines: package main···········}|
    895      {7:  }                                      │{7:  }                                     |
    896      {7:  }func printCharacters(str string) strin│{7:  }func printCharacters(str string) stri|
    897      {7:  }        return str                    │{7:  }        return str                   |
    898      {7:  }}                                     │{7:  }}                                    |
    899      {7:  }                                      │{7:  }                                     |
    900      {7:  }func main() {                         │{7:  }func main() {                        |
    901      {7:  }{23:--------------------------------------}│{7:  }{22:        hello := "Hello, World!"     }|
    902      {7:  }{23:--------------------------------------}│{7:  }{22:                                     }|
    903      {7:  }{4:        fmt.Print}{27:Ln}{4:(compressString("aa}│{7:  }{4:        fmt.Print(compressString("aaa}|
    904      {7:  }{4:        fmt.Print}{27:Ln}{4:(compressString("go}│{7:  }{4:        fmt.Print(compressString("gol}|
    905      {7:  }{4:        fmt.Print}{27:Ln}{4:(removeDuplicate("a}│{7:  }{4:        fmt.Print(removeDuplicate("aa}|
    906      {7:  }{4:        fmt.Print}{27:Ln}{4:(reverseAndDouble("}│{7:  }{4:        fmt.Print(reverseAndDouble("a}|
    907      {7:  }{4:        fmt.Print}{27:Ln}{4:(printCharacters("g}│{7:  }{4:        fmt.Print(printCharacters("go}|
    908      {7:  }{23:--------------------------------------}│{7:  }{22:                                     }|
    909      {7:  }{23:--------------------------------------}│{7:  }{22:        fmt.Println(hello)           }|
    910      {7:  }}                                     │{7:  }}                                    |
    911      {1:~                                       }│{1:~                                      }|*6
    912                                                                                      |
    913    ]])
    914    command('let &diffopt = &diffopt')
    915    screen:expect_unchanged()
    916  end)
    917 
    918  it('does not crash if --embed is given twice', function()
    919    clear { args = { '--embed' } }
    920    assert_alive()
    921  end)
    922 
    923  it('does not crash when expanding cdpath during early_init', function()
    924    clear { env = { CDPATH = '~doesnotexist' } }
    925    assert_alive()
    926    eq(',~doesnotexist', eval('&cdpath'))
    927  end)
    928 
    929  it("sets 'shortmess' when loading other tabs", function()
    930    clear({ args = { '-p', 'a', 'b', 'c' } })
    931    local screen = Screen.new(25, 4)
    932    screen:expect([[
    933      {5: a }{24: b  c }{2:               }{24:X}|
    934      ^                         |
    935      {1:~                        }|
    936                               |
    937    ]])
    938  end)
    939 
    940  describe('opening a terminal before buffers are loaded #30765', function()
    941    local lines = {} --- @type string[]
    942    for i = 1, 50 do
    943      lines[#lines + 1] = ('line%d'):format(i)
    944    end
    945 
    946    setup(function()
    947      write_file('Xsomefile', table.concat(lines, '\n') .. '\n')
    948    end)
    949 
    950    teardown(function()
    951      os.remove('Xsomefile')
    952    end)
    953 
    954    it('sends buffer content to terminal with nvim_open_term()', function()
    955      clear({
    956        args_rm = { '--headless' },
    957        args = {
    958          'Xsomefile',
    959          '--cmd',
    960          'let g:chan = nvim_open_term(0, {}) | startinsert',
    961          '--cmd',
    962          'call chansend(g:chan, "new_line1\nnew_line2\nnew_line3")',
    963        },
    964      })
    965      local screen = Screen.new(50, 7)
    966      screen:expect([[
    967        line48                                            |
    968        line49                                            |
    969        line50                                            |
    970        new_line1                                         |
    971        new_line2                                         |
    972        new_line3^                                         |
    973        {5:-- TERMINAL --}                                    |
    974      ]])
    975      eq(lines, api.nvim_buf_get_lines(0, 0, #lines, true))
    976    end)
    977 
    978    it('does not error with jobstart(…,{term=true})', function()
    979      clear({
    980        args_rm = { '--headless' },
    981        args = {
    982          'Xsomefile',
    983          '--cmd',
    984          ('lua vim.fn.jobstart({%q}, {term = true})'):format(n.testprg('tty-test')),
    985        },
    986      })
    987      local screen = Screen.new(50, 7)
    988      screen:expect([[
    989        ^tty ready                                         |
    990                                                          |*6
    991      ]])
    992    end)
    993  end)
    994 end)
    995 
    996 describe('startup', function()
    997  local function pack_clear(cmd)
    998    -- add packages after config dir in rtp but before config/after
    999    clear {
   1000      args = {
   1001        '--cmd',
   1002        'set packpath=test/functional/fixtures',
   1003        '--cmd',
   1004        'let paths=split(&rtp, ",")',
   1005        '--cmd',
   1006        'let &rtp = paths[0]..",test/functional/fixtures,test/functional/fixtures/middle,"..join(paths[1:],",")',
   1007        '--cmd',
   1008        cmd,
   1009      },
   1010      env = { XDG_CONFIG_HOME = 'test/functional/fixtures/' },
   1011      args_rm = { 'runtimepath' },
   1012    }
   1013  end
   1014 
   1015  it('handles &packpath during startup', function()
   1016    pack_clear [[
   1017      let g:x = bar#test()
   1018      let g:y = leftpad#pad("heyya")
   1019    ]]
   1020    eq(-3, eval 'g:x')
   1021    eq('  heyya', eval 'g:y')
   1022 
   1023    pack_clear [[ lua _G.y = require'bar'.doit() _G.z = require'leftpad''howdy' ]]
   1024    eq({ 9003, '\thowdy' }, exec_lua [[ return { _G.y, _G.z } ]])
   1025  end)
   1026 
   1027  it('handles require from &packpath in an async handler', function()
   1028    -- NO! you cannot just speed things up by calling async functions during startup!
   1029    -- It doesn't make anything actually faster! NOOOO!
   1030    pack_clear [[ lua require'async_leftpad'('brrrr', 'async_res') ]]
   1031 
   1032    -- haha, async leftpad go brrrrr
   1033    eq('\tbrrrr', exec_lua [[ return _G.async_res ]])
   1034  end)
   1035 
   1036  it('handles :packadd during startup', function()
   1037    -- control group: opt/bonus is not available by default
   1038    pack_clear [[
   1039      try
   1040        let g:x = bonus#secret()
   1041      catch
   1042        let g:err = v:exception
   1043      endtry
   1044    ]]
   1045    eq('Vim(let):E117: Unknown function: bonus#secret', eval 'g:err')
   1046 
   1047    pack_clear [[ lua _G.test = {pcall(function() require'bonus'.launch() end)} ]]
   1048    eq(
   1049      { false, [[[string ":lua"]:1: module 'bonus' not found:]] },
   1050      exec_lua [[ _G.test[2] = string.gsub(_G.test[2], '[\r\n].*', '') return _G.test ]]
   1051    )
   1052 
   1053    -- ok, time to launch the nukes:
   1054    pack_clear [[ packadd! bonus | let g:x = bonus#secret() ]]
   1055    eq('halloj', eval 'g:x')
   1056 
   1057    pack_clear [[ packadd! bonus | lua _G.y = require'bonus'.launch() ]]
   1058    eq('CPE 1704 TKS', exec_lua [[ return _G.y ]])
   1059  end)
   1060 
   1061  it('handles the correct order with start packages and after/', function()
   1062    pack_clear [[ lua _G.test_loadorder = {} vim.cmd "runtime! filen.lua" ]]
   1063    eq(
   1064      { 'ordinary', 'FANCY', 'mittel', 'FANCY after', 'ordinary after' },
   1065      exec_lua [[ return _G.test_loadorder ]]
   1066    )
   1067  end)
   1068 
   1069  it('handles the correct order with start packages and after/ after startup', function()
   1070    pack_clear [[ lua _G.test_loadorder = {} ]]
   1071    command [[ runtime! filen.lua ]]
   1072    eq(
   1073      { 'ordinary', 'FANCY', 'mittel', 'FANCY after', 'ordinary after' },
   1074      exec_lua [[ return _G.test_loadorder ]]
   1075    )
   1076  end)
   1077 
   1078  it('handles the correct order with globpath(&rtp, ...)', function()
   1079    pack_clear [[ set loadplugins | lua _G.test_loadorder = {} ]]
   1080    command [[
   1081      for x in globpath(&rtp, "filen.lua",1,1)
   1082        call v:lua.dofile(x)
   1083      endfor
   1084    ]]
   1085    eq(
   1086      { 'ordinary', 'FANCY', 'mittel', 'FANCY after', 'ordinary after' },
   1087      exec_lua [[ return _G.test_loadorder ]]
   1088    )
   1089 
   1090    local rtp = api.nvim_get_option_value('rtp', {})
   1091    ok(
   1092      startswith(
   1093        rtp,
   1094        'test/functional/fixtures/nvim,test/functional/fixtures/pack/*/start/*,test/functional/fixtures/start/*,test/functional/fixtures,test/functional/fixtures/middle,'
   1095      ),
   1096      'startswith(…)',
   1097      'rtp=' .. rtp
   1098    )
   1099  end)
   1100 
   1101  it('handles the correct order with opt packages and after/', function()
   1102    pack_clear [[ lua _G.test_loadorder = {} vim.cmd "packadd! superspecial\nruntime! filen.lua" ]]
   1103    eq({
   1104      'ordinary',
   1105      'SuperSpecial',
   1106      'FANCY',
   1107      'mittel',
   1108      'FANCY after',
   1109      'SuperSpecial after',
   1110      'ordinary after',
   1111    }, exec_lua [[ return _G.test_loadorder ]])
   1112  end)
   1113 
   1114  it('handles the correct order with opt packages and after/ after startup', function()
   1115    pack_clear [[ lua _G.test_loadorder = {} ]]
   1116    command [[
   1117      packadd! superspecial
   1118      runtime! filen.lua
   1119    ]]
   1120    eq({
   1121      'ordinary',
   1122      'SuperSpecial',
   1123      'FANCY',
   1124      'mittel',
   1125      'FANCY after',
   1126      'SuperSpecial after',
   1127      'ordinary after',
   1128    }, exec_lua [[ return _G.test_loadorder ]])
   1129  end)
   1130 
   1131  it('does an incremental update for packadd', function()
   1132    pack_clear [[ lua _G.test_loadorder = {} ]]
   1133    command [[
   1134      " need to use the runtime to make the initial cache:
   1135      runtime! non_exist_ent
   1136      " this should now incrementally update it:
   1137      packadd! superspecial
   1138    ]]
   1139 
   1140    local check = api.nvim__runtime_inspect()
   1141    local check_copy = vim.deepcopy(check)
   1142    local any_incremental = false
   1143    for _, item in ipairs(check_copy) do
   1144      any_incremental = any_incremental or item.pack_inserted
   1145      item.pack_inserted = nil
   1146    end
   1147    eq(true, any_incremental, 'no pack_inserted in ' .. vim.inspect(check))
   1148 
   1149    command [[
   1150      let &rtp = &rtp
   1151      runtime! phantom_ghost
   1152    ]]
   1153 
   1154    local new_check = api.nvim__runtime_inspect()
   1155    eq(check_copy, new_check)
   1156 
   1157    command [[ runtime! filen.lua ]]
   1158    eq({
   1159      'ordinary',
   1160      'SuperSpecial',
   1161      'FANCY',
   1162      'mittel',
   1163      'FANCY after',
   1164      'SuperSpecial after',
   1165      'ordinary after',
   1166    }, exec_lua [[ return _G.test_loadorder ]])
   1167  end)
   1168 
   1169  it('handles the correct order with opt packages and globpath(&rtp, ...)', function()
   1170    pack_clear [[ set loadplugins | lua _G.test_loadorder = {} ]]
   1171    command [[
   1172      packadd! superspecial
   1173      for x in globpath(&rtp, "filen.lua",1,1)
   1174        call v:lua.dofile(x)
   1175      endfor
   1176    ]]
   1177    eq({
   1178      'ordinary',
   1179      'SuperSpecial',
   1180      'FANCY',
   1181      'mittel',
   1182      'SuperSpecial after',
   1183      'FANCY after',
   1184      'ordinary after',
   1185    }, exec_lua [[ return _G.test_loadorder ]])
   1186  end)
   1187 
   1188  it('handles the correct order with a package that changes packpath', function()
   1189    pack_clear [[ lua _G.test_loadorder = {} vim.cmd "packadd! funky\nruntime! filen.lua" ]]
   1190    eq(
   1191      { 'ordinary', 'funky!', 'FANCY', 'mittel', 'FANCY after', 'ordinary after' },
   1192      exec_lua [[ return _G.test_loadorder ]]
   1193    )
   1194    eq({ 'ordinary', 'funky!', 'mittel', 'ordinary after' }, exec_lua [[ return _G.nested_order ]])
   1195  end)
   1196 
   1197  it('handles the correct order when prepending packpath', function()
   1198    clear {
   1199      args = {
   1200        '--cmd',
   1201        'set packpath^=test/functional/fixtures',
   1202        '--cmd',
   1203        [[ lua _G.test_loadorder = {} vim.cmd "runtime! filen.lua" ]],
   1204      },
   1205      env = { XDG_CONFIG_HOME = 'test/functional/fixtures/' },
   1206    }
   1207    eq(
   1208      { 'ordinary', 'FANCY', 'FANCY after', 'ordinary after' },
   1209      exec_lua [[ return _G.test_loadorder ]]
   1210    )
   1211  end)
   1212 
   1213  it('window widths are correct when modelines set &columns with tabpages', function()
   1214    write_file('Xtab1.noft', 'vim: columns=81')
   1215    write_file('Xtab2.noft', 'vim: columns=81')
   1216    finally(function()
   1217      os.remove('Xtab1.noft')
   1218      os.remove('Xtab2.noft')
   1219    end)
   1220    clear({ args = { '-p', 'Xtab1.noft', 'Xtab2.noft' } })
   1221    eq(81, api.nvim_win_get_width(0))
   1222    command('tabnext')
   1223    eq(81, api.nvim_win_get_width(0))
   1224  end)
   1225 end)
   1226 
   1227 describe('sysinit', function()
   1228  local xdgdir = 'Xxdg'
   1229  local vimdir = 'Xvim'
   1230  local xhome = 'Xhome'
   1231  local pathsep = n.get_pathsep()
   1232 
   1233  before_each(function()
   1234    rmdir(xdgdir)
   1235    rmdir(vimdir)
   1236    rmdir(xhome)
   1237 
   1238    mkdir(xdgdir)
   1239    mkdir(xdgdir .. pathsep .. 'nvim')
   1240    write_file(
   1241      table.concat({ xdgdir, 'nvim', 'sysinit.vim' }, pathsep),
   1242      [[
   1243      let g:loaded = get(g:, "loaded", 0) + 1
   1244      let g:xdg = 1
   1245    ]]
   1246    )
   1247 
   1248    mkdir(vimdir)
   1249    write_file(
   1250      table.concat({ vimdir, 'sysinit.vim' }, pathsep),
   1251      [[
   1252      let g:loaded = get(g:, "loaded", 0) + 1
   1253      let g:vim = 1
   1254    ]]
   1255    )
   1256 
   1257    mkdir(xhome)
   1258  end)
   1259  after_each(function()
   1260    rmdir(xdgdir)
   1261    rmdir(vimdir)
   1262    rmdir(xhome)
   1263  end)
   1264 
   1265  it('prefers XDG_CONFIG_DIRS over VIM', function()
   1266    clear {
   1267      args = { '--cmd', 'set nomore undodir=. directory=. belloff=' },
   1268      args_rm = { '-u', '--cmd' },
   1269      env = { HOME = xhome, XDG_CONFIG_DIRS = xdgdir, VIM = vimdir },
   1270    }
   1271    eq(
   1272      'loaded 1 xdg 1 vim 0',
   1273      eval('printf("loaded %d xdg %d vim %d", g:loaded, get(g:, "xdg", 0), get(g:, "vim", 0))')
   1274    )
   1275  end)
   1276 
   1277  it('uses VIM if XDG_CONFIG_DIRS unset', function()
   1278    clear {
   1279      args = { '--cmd', 'set nomore undodir=. directory=. belloff=' },
   1280      args_rm = { '-u', '--cmd' },
   1281      env = { HOME = xhome, XDG_CONFIG_DIRS = '', VIM = vimdir },
   1282    }
   1283    eq(
   1284      'loaded 1 xdg 0 vim 1',
   1285      eval('printf("loaded %d xdg %d vim %d", g:loaded, get(g:, "xdg", 0), get(g:, "vim", 0))')
   1286    )
   1287  end)
   1288 
   1289  it('respects NVIM_APPNAME in XDG_CONFIG_DIRS', function()
   1290    local appname = 'mysysinitapp'
   1291    mkdir(xdgdir .. pathsep .. appname)
   1292    write_file(
   1293      table.concat({ xdgdir, appname, 'sysinit.vim' }, pathsep),
   1294      [[let g:appname_sysinit = 1]]
   1295    )
   1296    clear {
   1297      args_rm = { '-u' },
   1298      env = { HOME = xhome, XDG_CONFIG_DIRS = xdgdir, NVIM_APPNAME = appname },
   1299    }
   1300    eq(1, eval('g:appname_sysinit'))
   1301    -- Should not load from nvim/ subdir (which has the default sysinit.vim from before_each)
   1302    eq(0, eval('get(g:, "xdg", 0)'))
   1303  end)
   1304 end)
   1305 
   1306 describe('user config init', function()
   1307  local xhome = 'Xhome'
   1308  local pathsep = n.get_pathsep()
   1309  local xconfig = xhome .. pathsep .. 'Xconfig'
   1310  local xdata = xhome .. pathsep .. 'Xdata'
   1311  local init_lua_path = table.concat({ xconfig, 'nvim', 'init.lua' }, pathsep)
   1312  local xenv = { XDG_CONFIG_HOME = xconfig, XDG_DATA_HOME = xdata }
   1313 
   1314  before_each(function()
   1315    rmdir(xhome)
   1316 
   1317    mkdir_p(xconfig .. pathsep .. 'nvim')
   1318    mkdir_p(xdata)
   1319 
   1320    write_file(init_lua_path, [[vim.g.lua_rc = 1]])
   1321  end)
   1322 
   1323  after_each(function()
   1324    rmdir(xhome)
   1325  end)
   1326 
   1327  it('loads init.lua from XDG config home by default', function()
   1328    clear { args_rm = { '-u' }, env = xenv }
   1329 
   1330    eq(1, eval('g:lua_rc'))
   1331    eq(fn.fnamemodify(init_lua_path, ':p'), eval('$MYVIMRC'))
   1332  end)
   1333 
   1334  describe('loads existing', function()
   1335    local exrc_path = '.exrc'
   1336    local xstate = 'Xstate'
   1337    local xstateenv = { XDG_CONFIG_HOME = xconfig, XDG_DATA_HOME = xdata, XDG_STATE_HOME = xstate }
   1338 
   1339    local function setup_exrc_file(filename)
   1340      exrc_path = filename
   1341 
   1342      if string.find(exrc_path, '%.lua$') then
   1343        write_file(
   1344          exrc_path,
   1345          string.format(
   1346            [[
   1347              vim.g.exrc_file = "%s"
   1348              vim.g.exrc_path = debug.getinfo(1, 'S').source:gsub('^@', '')
   1349              vim.g.exrc_count = (vim.g.exrc_count or 0) + 1
   1350            ]],
   1351            exrc_path
   1352          )
   1353        )
   1354      else
   1355        write_file(
   1356          exrc_path,
   1357          string.format(
   1358            [[
   1359              let g:exrc_file = "%s"
   1360              " let g:exrc_path = ??
   1361              let g:exrc_count = get(g:, 'exrc_count', 0) + 1
   1362            ]],
   1363            exrc_path
   1364          )
   1365        )
   1366      end
   1367    end
   1368 
   1369    before_each(function()
   1370      for _, file in ipairs({ '.exrc', '.nvimrc', '.nvim.lua' }) do
   1371        os.remove('../' .. file)
   1372        os.remove(file)
   1373      end
   1374      write_file(
   1375        init_lua_path,
   1376        [[
   1377          vim.o.exrc = true
   1378          vim.g.exrc_file = '---'
   1379        ]]
   1380      )
   1381      mkdir_p(xstate .. pathsep .. (is_os('win') and 'nvim-data' or 'nvim'))
   1382    end)
   1383 
   1384    after_each(function()
   1385      for _, file in ipairs({ '.exrc', '.nvimrc', '.nvim.lua' }) do
   1386        os.remove('../' .. file)
   1387        os.remove(file)
   1388      end
   1389      rmdir(xstate)
   1390    end)
   1391 
   1392    for _, filename in ipairs({ '.exrc', '.nvimrc', '.nvim.lua', '../.nvim.lua', '../.nvimrc' }) do
   1393      it(filename .. ' from cwd', function()
   1394        setup_exrc_file(filename)
   1395 
   1396        clear { args_rm = { '-u' }, env = xstateenv }
   1397        -- The 'exrc' file is not trusted, and the prompt is skipped because there is no UI.
   1398        eq('---', eval('g:exrc_file'))
   1399 
   1400        local screen = Screen.new(50, 8)
   1401        screen._default_attr_ids = nil
   1402        fn.jobstart({ nvim_prog }, {
   1403          term = true,
   1404          env = {
   1405            VIMRUNTIME = os.getenv('VIMRUNTIME'),
   1406          },
   1407        })
   1408        screen:expect({ any = pesc('[i]gnore, (v)iew, (d)eny:') })
   1409        -- `i` to enter Terminal mode, `v` to view then `:trust`
   1410        feed('iv')
   1411        feed(':trust<CR>')
   1412        feed(':q<CR>')
   1413        screen:expect([[
   1414          ^                                                  |
   1415          ~                                                 |*4
   1416          [No Name]                       0,0-1          All|
   1417                                                            |
   1418          -- TERMINAL --                                    |
   1419        ]])
   1420        feed(':echo g:exrc_file<CR>')
   1421        screen:expect(string.format(
   1422          [[
   1423          ^                                                  |
   1424          ~                                                 |*4
   1425          [No Name]                       0,0-1          All|
   1426          %s%s|
   1427          -- TERMINAL --                                    |
   1428        ]],
   1429          '---',
   1430          string.rep(' ', 50 - #'---')
   1431        ))
   1432 
   1433        clear { args_rm = { '-u' }, env = xstateenv }
   1434        if string.find(exrc_path, '%.lua$') then
   1435          eq(
   1436            vim.fs.normalize(vim.fs.abspath(filename)),
   1437            vim.fs.normalize(vim.fs.abspath(eval('g:exrc_path')))
   1438          )
   1439        end
   1440        -- The 'exrc' file is now trusted.
   1441        eq(filename, eval('g:exrc_file'))
   1442      end)
   1443    end
   1444 
   1445    it('exrc from parent directories', function()
   1446      setup_exrc_file('.nvim.lua')
   1447      setup_exrc_file('../.exrc')
   1448      clear { args_rm = { '-u' }, env = xstateenv }
   1449      -- use a screen wide width to avoid wrapping the word `.exrc`, `.nvim.lua` below.
   1450      local screen = Screen.new(500, 8)
   1451      screen._default_attr_ids = nil
   1452      fn.jobstart({ nvim_prog }, {
   1453        term = true,
   1454        env = {
   1455          VIMRUNTIME = os.getenv('VIMRUNTIME'),
   1456        },
   1457      })
   1458      -- current directory exrc is found first
   1459      screen:expect({ any = '.nvim.lua' })
   1460      screen:expect({ any = pesc('[i]gnore, (v)iew, (d)eny:'), unchanged = true })
   1461      feed('iv')
   1462 
   1463      -- after that the exrc in the parent directory
   1464      screen:expect({ any = '.exrc', unchanged = true })
   1465      screen:expect({ any = pesc('[i]gnore, (v)iew, (d)eny:'), unchanged = true })
   1466      feed('v')
   1467 
   1468      -- trust .exrc
   1469      feed(':trust<CR>')
   1470      screen:expect({ any = 'Allowed in trust database: ".*' .. pathsep .. '%.exrc"' })
   1471      feed(':q<CR>')
   1472      -- trust .nvim.lua
   1473      feed(':trust<CR>')
   1474      screen:expect({ any = 'Allowed in trust database: ".*' .. pathsep .. '%.nvim%.lua"' })
   1475      feed(':q<CR>')
   1476      -- no exrc file is executed
   1477      feed(':echo g:exrc_count<CR>')
   1478      screen:expect({ any = 'E121: Undefined variable: g:exrc_count' })
   1479 
   1480      -- restart nvim
   1481      feed(':restart<CR>')
   1482      screen:expect([[
   1483        ^{MATCH: +}|
   1484        ~{MATCH: +}|*4
   1485        [No Name]{MATCH: +}0,0-1{MATCH: +}All|
   1486        {MATCH: +}|
   1487        -- TERMINAL --{MATCH: +}|
   1488      ]])
   1489 
   1490      -- a total of 2 exrc files are executed
   1491      feed(':echo g:exrc_count<CR>')
   1492      screen:expect({ any = '2' })
   1493    end)
   1494  end)
   1495 
   1496  describe('with explicitly provided config', function()
   1497    local custom_lua_path = table.concat({ xhome, 'custom.lua' }, pathsep)
   1498    before_each(function()
   1499      write_file(
   1500        custom_lua_path,
   1501        [[
   1502          vim.g.custom_lua_rc = 1
   1503        ]]
   1504      )
   1505    end)
   1506 
   1507    it('loads custom lua config and does not set $MYVIMRC', function()
   1508      clear { args = { '-u', custom_lua_path }, env = xenv }
   1509      eq(1, eval('g:custom_lua_rc'))
   1510      eq('', eval('$MYVIMRC'))
   1511    end)
   1512  end)
   1513 
   1514  describe('VIMRC also exists', function()
   1515    before_each(function()
   1516      write_file(
   1517        table.concat({ xconfig, 'nvim', 'init.vim' }, pathsep),
   1518        [[
   1519          let g:vim_rc = 1
   1520        ]]
   1521      )
   1522    end)
   1523 
   1524    it('loads default lua config, but shows an error', function()
   1525      clear { args_rm = { '-u' }, env = xenv }
   1526      eq(1, eval('g:lua_rc'))
   1527      t.matches(
   1528        'E5422: Conflicting configs: "Xhome.Xconfig.nvim.init.lua" "Xhome.Xconfig.nvim.init.vim"',
   1529        eval('v:errmsg')
   1530      )
   1531    end)
   1532  end)
   1533 
   1534  describe('from XDG_CONFIG_DIRS', function()
   1535    local xdgdir = 'Xxdgconfigdirs'
   1536 
   1537    before_each(function()
   1538      -- Remove init.lua from XDG_CONFIG_HOME so nvim falls back to XDG_CONFIG_DIRS
   1539      os.remove(init_lua_path)
   1540      rmdir(xdgdir)
   1541      mkdir_p(xdgdir .. pathsep .. 'nvim')
   1542    end)
   1543 
   1544    after_each(function()
   1545      rmdir(xdgdir)
   1546    end)
   1547 
   1548    it('loads init.lua from XDG_CONFIG_DIRS when no config in XDG_CONFIG_HOME', function()
   1549      write_file(
   1550        table.concat({ xdgdir, 'nvim', 'init.lua' }, pathsep),
   1551        [[vim.g.xdg_config_dirs_lua = 1]]
   1552      )
   1553      clear {
   1554        args_rm = { '-u' },
   1555        env = { XDG_CONFIG_HOME = xconfig, XDG_DATA_HOME = xdata, XDG_CONFIG_DIRS = xdgdir },
   1556      }
   1557      eq(1, eval('g:xdg_config_dirs_lua'))
   1558      eq(
   1559        fn.fnamemodify(table.concat({ xdgdir, 'nvim', 'init.lua' }, pathsep), ':p'),
   1560        eval('$MYVIMRC')
   1561      )
   1562    end)
   1563 
   1564    it('prefers init.lua over init.vim, shows E5422', function()
   1565      write_file(table.concat({ xdgdir, 'nvim', 'init.lua' }, pathsep), [[vim.g.xdg_lua = 1]])
   1566      write_file(table.concat({ xdgdir, 'nvim', 'init.vim' }, pathsep), [[let g:xdg_vim = 1]])
   1567      clear {
   1568        args_rm = { '-u' },
   1569        env = { XDG_CONFIG_HOME = xconfig, XDG_DATA_HOME = xdata, XDG_CONFIG_DIRS = xdgdir },
   1570      }
   1571      eq(1, eval('g:xdg_lua'))
   1572      eq(0, eval('get(g:, "xdg_vim", 0)'))
   1573      t.matches('E5422: Conflicting configs:', eval('v:errmsg'))
   1574    end)
   1575 
   1576    it('falls back to init.vim when no init.lua', function()
   1577      write_file(table.concat({ xdgdir, 'nvim', 'init.vim' }, pathsep), [[let g:xdg_vim = 1]])
   1578      clear {
   1579        args_rm = { '-u' },
   1580        env = { XDG_CONFIG_HOME = xconfig, XDG_DATA_HOME = xdata, XDG_CONFIG_DIRS = xdgdir },
   1581      }
   1582      eq(1, eval('g:xdg_vim'))
   1583    end)
   1584 
   1585    it('respects NVIM_APPNAME', function()
   1586      local appname = 'mytestapp'
   1587      mkdir_p(xdgdir .. pathsep .. appname)
   1588      -- Also create nvim/ with a config that should NOT be loaded
   1589      write_file(table.concat({ xdgdir, 'nvim', 'init.lua' }, pathsep), [[vim.g.wrong = 1]])
   1590      write_file(table.concat({ xdgdir, appname, 'init.lua' }, pathsep), [[vim.g.appname_lua = 1]])
   1591      clear {
   1592        args_rm = { '-u' },
   1593        env = {
   1594          XDG_CONFIG_HOME = xconfig,
   1595          XDG_DATA_HOME = xdata,
   1596          XDG_CONFIG_DIRS = xdgdir,
   1597          NVIM_APPNAME = appname,
   1598        },
   1599      }
   1600      eq(1, eval('g:appname_lua'))
   1601      eq(0, eval('get(g:, "wrong", 0)'))
   1602      eq(
   1603        fn.fnamemodify(table.concat({ xdgdir, appname, 'init.lua' }, pathsep), ':p'),
   1604        eval('$MYVIMRC')
   1605      )
   1606    end)
   1607  end)
   1608 end)
   1609 
   1610 describe('runtime:', function()
   1611  local xhome = 'Xhome'
   1612  local pathsep = n.get_pathsep()
   1613  local xconfig = xhome .. pathsep .. 'Xconfig'
   1614  local xdata = xhome .. pathsep .. 'Xdata'
   1615  local xenv = { XDG_CONFIG_HOME = xconfig, XDG_DATA_HOME = xdata }
   1616 
   1617  setup(function()
   1618    rmdir(xhome)
   1619    mkdir_p(xconfig .. pathsep .. 'nvim')
   1620    mkdir_p(xdata)
   1621  end)
   1622 
   1623  teardown(function()
   1624    rmdir(xhome)
   1625  end)
   1626 
   1627  it('loads plugin/*.lua from XDG config home', function()
   1628    local plugin_folder_path = table.concat({ xconfig, 'nvim', 'plugin' }, pathsep)
   1629    local plugin_file_path = table.concat({ plugin_folder_path, 'plugin.lua' }, pathsep)
   1630    mkdir_p(plugin_folder_path)
   1631    finally(function()
   1632      rmdir(plugin_folder_path)
   1633    end)
   1634    write_file(plugin_file_path, [[ vim.g.lua_plugin = 1 ]])
   1635 
   1636    clear { args_rm = { '-u' }, env = xenv }
   1637 
   1638    eq(1, eval('g:lua_plugin'))
   1639  end)
   1640 
   1641  it('loads plugin/*.lua from start packages', function()
   1642    local plugin_path =
   1643      table.concat({ xconfig, 'nvim', 'pack', 'category', 'start', 'test_plugin' }, pathsep)
   1644    local plugin_folder_path = table.concat({ plugin_path, 'plugin' }, pathsep)
   1645    local plugin_file_path = table.concat({ plugin_folder_path, 'plugin.lua' }, pathsep)
   1646    local profiler_file = 'test_startuptime.log'
   1647    mkdir_p(plugin_folder_path)
   1648    finally(function()
   1649      os.remove(profiler_file)
   1650      rmdir(plugin_path)
   1651    end)
   1652 
   1653    write_file(plugin_file_path, [[vim.g.lua_plugin = 2]])
   1654 
   1655    clear { args_rm = { '-u' }, args = { '--startuptime', profiler_file }, env = xenv }
   1656 
   1657    eq(2, eval('g:lua_plugin'))
   1658    -- Check if plugin_file_path is listed in getscriptinfo()
   1659    local scripts = tbl_map(function(s)
   1660      return s.name
   1661    end, fn.getscriptinfo())
   1662    ok(#tbl_filter(function(s)
   1663      return endswith(s, plugin_file_path)
   1664    end, scripts) > 0)
   1665 
   1666    -- Check if plugin_file_path is listed in startup profile
   1667    local profile_reader = io.open(profiler_file, 'r')
   1668    local profile_log = profile_reader:read('*a')
   1669    profile_reader:close()
   1670    ok(profile_log:find(plugin_file_path) ~= nil)
   1671  end)
   1672 
   1673  it('loads plugin/*.lua from site packages', function()
   1674    local nvimdata = is_os('win') and 'nvim-data' or 'nvim'
   1675    local plugin_path =
   1676      table.concat({ xdata, nvimdata, 'site', 'pack', 'xa', 'start', 'yb' }, pathsep)
   1677    local plugin_folder_path = table.concat({ plugin_path, 'plugin' }, pathsep)
   1678    local plugin_after_path = table.concat({ plugin_path, 'after', 'plugin' }, pathsep)
   1679    local plugin_file_path = table.concat({ plugin_folder_path, 'plugin.lua' }, pathsep)
   1680    local plugin_after_file_path = table.concat({ plugin_after_path, 'helloo.lua' }, pathsep)
   1681    mkdir_p(plugin_folder_path)
   1682    mkdir_p(plugin_after_path)
   1683    finally(function()
   1684      rmdir(plugin_path)
   1685    end)
   1686 
   1687    write_file(plugin_file_path, [[table.insert(_G.lista, "unos")]])
   1688    write_file(plugin_after_file_path, [[table.insert(_G.lista, "dos")]])
   1689 
   1690    clear { args_rm = { '-u' }, args = { '--cmd', 'lua _G.lista = {}' }, env = xenv }
   1691 
   1692    eq({ 'unos', 'dos' }, exec_lua 'return _G.lista')
   1693  end)
   1694 
   1695  it('no crash setting &rtp in plugins with :packloadall called before #18315', function()
   1696    local plugin_folder_path = table.concat({ xconfig, 'nvim', 'plugin' }, pathsep)
   1697    mkdir_p(plugin_folder_path)
   1698    finally(function()
   1699      rmdir(plugin_folder_path)
   1700    end)
   1701 
   1702    write_file(
   1703      table.concat({ plugin_folder_path, 'plugin.vim' }, pathsep),
   1704      [[
   1705        let &runtimepath = &runtimepath
   1706        let g:vim_plugin = 1
   1707      ]]
   1708    )
   1709    write_file(
   1710      table.concat({ plugin_folder_path, 'plugin.lua' }, pathsep),
   1711      [[
   1712        vim.o.runtimepath = vim.o.runtimepath
   1713        vim.g.lua_plugin = 1
   1714      ]]
   1715    )
   1716 
   1717    clear { args_rm = { '-u' }, args = { '--cmd', 'packloadall' }, env = xenv }
   1718 
   1719    eq(1, eval('g:vim_plugin'))
   1720    eq(1, eval('g:lua_plugin'))
   1721  end)
   1722 
   1723  it("loads ftdetect/*.{vim,lua} respecting 'rtp' order", function()
   1724    local rtp_folder = table.concat({ xconfig, 'nvim' }, pathsep)
   1725    local after_rtp_folder = table.concat({ rtp_folder, 'after' }, pathsep)
   1726    local ftdetect_folder = table.concat({ rtp_folder, 'ftdetect' }, pathsep)
   1727    local after_ftdetect_folder = table.concat({ after_rtp_folder, 'ftdetect' }, pathsep)
   1728    mkdir_p(ftdetect_folder)
   1729    mkdir_p(after_ftdetect_folder)
   1730    finally(function()
   1731      rmdir(ftdetect_folder)
   1732      rmdir(after_ftdetect_folder)
   1733    end)
   1734    write_file(table.concat({ rtp_folder, 'scripts.vim' }, pathsep), [[let g:aseq ..= 'S']])
   1735    write_file(table.concat({ after_rtp_folder, 'scripts.vim' }, pathsep), [[let g:aseq ..= 's']])
   1736    -- A .lua file is loaded after a .vim file if they only differ in extension.
   1737    -- All files in after/ftdetect/ are loaded after all files in ftdetect/.
   1738    write_file(
   1739      table.concat({ ftdetect_folder, 'new-ft.vim' }, pathsep),
   1740      [[
   1741        let g:seq ..= 'A'
   1742        autocmd BufRead,BufNewFile FTDETECT let g:aseq ..= 'A'
   1743      ]]
   1744    )
   1745    write_file(
   1746      table.concat({ ftdetect_folder, 'new-ft.lua' }, pathsep),
   1747      [[
   1748        vim.g.seq = vim.g.seq .. 'B'
   1749        vim.api.nvim_create_autocmd({ 'BufRead', 'BufNewFile' }, {
   1750          pattern = 'FTDETECT',
   1751          command = "let g:aseq ..= 'B'",
   1752        })
   1753      ]]
   1754    )
   1755    write_file(
   1756      table.concat({ after_ftdetect_folder, 'new-ft.vim' }, pathsep),
   1757      [[
   1758        let g:seq ..= 'a'
   1759        autocmd BufRead,BufNewFile FTDETECT let g:aseq ..= 'a'
   1760      ]]
   1761    )
   1762    write_file(
   1763      table.concat({ after_ftdetect_folder, 'new-ft.lua' }, pathsep),
   1764      [[
   1765        vim.g.seq = vim.g.seq .. 'b'
   1766        vim.api.nvim_create_autocmd({ 'BufRead', 'BufNewFile' }, {
   1767          pattern = 'FTDETECT',
   1768          command = "let g:aseq ..= 'b'",
   1769        })
   1770      ]]
   1771    )
   1772    clear { args_rm = { '-u' }, args = { '--cmd', 'let g:seq = ""' }, env = xenv }
   1773    eq('ABab', eval('g:seq'))
   1774    command('let g:aseq = ""')
   1775    command('edit FTDETECT')
   1776    eq('SsABab', eval('g:aseq'))
   1777  end)
   1778 end)
   1779 
   1780 describe('user session', function()
   1781  local xhome = 'Xhome'
   1782  local pathsep = n.get_pathsep()
   1783  local session_file = table.concat({ xhome, 'session.lua' }, pathsep)
   1784 
   1785  before_each(function()
   1786    rmdir(xhome)
   1787 
   1788    mkdir(xhome)
   1789    write_file(
   1790      session_file,
   1791      [[
   1792        vim.g.lua_session = 1
   1793      ]]
   1794    )
   1795  end)
   1796 
   1797  after_each(function()
   1798    rmdir(xhome)
   1799  end)
   1800 
   1801  it('loads session from the provided lua file', function()
   1802    clear { args = { '-S', session_file }, env = { HOME = xhome } }
   1803    eq(1, eval('g:lua_session'))
   1804  end)
   1805 end)
   1806 
   1807 describe('inccommand on ex mode', function()
   1808  it('should not preview', function()
   1809    clear()
   1810    local screen
   1811    screen = Screen.new(60, 10)
   1812    local id = fn.jobstart({
   1813      nvim_prog,
   1814      '-u',
   1815      'NONE',
   1816      '-i',
   1817      'NONE',
   1818      '-c',
   1819      'set termguicolors background=dark',
   1820      '-E',
   1821      'README.md',
   1822    }, {
   1823      term = true,
   1824      env = { VIMRUNTIME = os.getenv('VIMRUNTIME') },
   1825    })
   1826    fn.chansend(id, '%s/N')
   1827    screen:add_extra_attr_ids({
   1828      [101] = {
   1829        background = Screen.colors.NvimDarkGrey4,
   1830        foreground = Screen.colors.NvimLightGray2,
   1831      },
   1832      [102] = {
   1833        background = Screen.colors.NvimDarkGray2,
   1834        foreground = Screen.colors.NvimLightGray2,
   1835      },
   1836    })
   1837    screen:expect([[
   1838      {102:^                                                            }|
   1839      {102:                                                            }|*5
   1840      {101:                                                            }|
   1841      {102:Entering Ex mode.  Type "visual" to go to Normal mode.      }|
   1842      {102::%s/N                                                       }|
   1843                                                                  |
   1844    ]])
   1845  end)
   1846 end)