neovim/test/functional/core/startup_spec.lua
Gregory Anders 0dd933265f
feat(terminal)!: cursor shape and blink (#31562)
When a terminal application running inside the terminal emulator sets
the cursor shape or blink status of the cursor, update the cursor in the
parent terminal to match.

This removes the "virtual cursor" that has been in use by the terminal
emulator since the beginning. The original rationale for using the
virtual cursor was to avoid having to support additional UI methods to
change the cursor color for other (non-TUI) UIs, instead relying on the
TermCursor and TermCursorNC highlight groups.

The TermCursor highlight group is now used in the default 'guicursor'
value, which has a new entry for Terminal mode. However, the
TermCursorNC highlight group is no longer supported: since terminal
windows now use the real cursor, when the window is not focused there is
no cursor displayed in the window at all, so there is nothing to
highlight. Users can still use the StatusLineTermNC highlight group to
differentiate non-focused terminal windows.

BREAKING CHANGE: The TermCursorNC highlight group is no longer supported.
2024-12-17 07:11:41 -06:00

1452 lines
41 KiB
Lua

local t = require('test.testutil')
local n = require('test.functional.testnvim')()
local Screen = require('test.functional.ui.screen')
local assert_alive = n.assert_alive
local assert_log = t.assert_log
local clear = n.clear
local command = n.command
local ok = t.ok
local eq = t.eq
local matches = t.matches
local eval = n.eval
local exec = n.exec
local exec_capture = n.exec_capture
local exec_lua = n.exec_lua
local feed = n.feed
local fn = n.fn
local pesc = vim.pesc
local mkdir = t.mkdir
local mkdir_p = n.mkdir_p
local nvim_prog = n.nvim_prog
local nvim_set = n.nvim_set
local read_file = t.read_file
local retry = t.retry
local rmdir = n.rmdir
local sleep = vim.uv.sleep
local startswith = vim.startswith
local write_file = t.write_file
local api = n.api
local is_os = t.is_os
local dedent = t.dedent
local tbl_map = vim.tbl_map
local tbl_filter = vim.tbl_filter
local endswith = vim.endswith
local check_close = n.check_close
local testlog = 'Xtest-startupspec-log'
describe('startup', function()
it('--clean', function()
clear()
matches(
vim.pesc(t.fix_slashes(fn.stdpath('config'))),
t.fix_slashes(api.nvim_get_option_value('runtimepath', {}))
)
clear('--clean')
ok(
not t.fix_slashes(api.nvim_get_option_value('runtimepath', {}))
:match(vim.pesc(t.fix_slashes(fn.stdpath('config'))))
)
end)
it('prevents remote UI infinite loop', function()
clear()
local screen
screen = Screen.new(84, 3)
fn.termopen({ nvim_prog, '-u', 'NONE', '--server', eval('v:servername'), '--remote-ui' })
screen:expect([[
^Cannot attach UI of :terminal child to its parent. (Unset $NVIM to skip this check) |
|*2
]])
end)
it('--startuptime', function()
local testfile = 'Xtest_startuptime'
finally(function()
os.remove(testfile)
end)
clear({ args = { '--startuptime', testfile } })
assert_log('Embedded', testfile, 100)
assert_log('sourcing', testfile, 100)
assert_log("require%('vim%._editor'%)", testfile, 100)
end)
it('--startuptime does not crash on error #31125', function()
eq(
"E484: Can't open file .",
fn.system({
nvim_prog,
'-u',
'NONE',
'-i',
'NONE',
'--headless',
'--startuptime',
'.',
'-c',
'42cquit',
})
)
eq(42, api.nvim_get_vvar('shell_error'))
end)
it('-D does not hang #12647', function()
clear()
local screen
screen = Screen.new(60, 7)
-- not the same colors on windows for some reason
screen._default_attr_ids = nil
local id = fn.termopen({
nvim_prog,
'-u',
'NONE',
'-i',
'NONE',
'--cmd',
'set noruler',
'-D',
}, {
env = {
VIMRUNTIME = os.getenv('VIMRUNTIME'),
},
})
screen:expect({ any = pesc('Entering Debug mode. Type "cont" to continue.') })
fn.chansend(id, 'cont\n')
screen:expect([[
^ |
~ |*3
[No Name] |
|*2
]])
end)
it(':filetype detect enables filetype detection with -u NONE', function()
clear()
eq('filetype detection:OFF plugin:OFF indent:OFF', exec_capture('filetype'))
command('filetype detect')
eq('filetype detection:ON plugin:OFF indent:OFF', exec_capture('filetype'))
end)
end)
describe('startup', function()
before_each(clear)
after_each(function()
check_close()
os.remove(testlog)
end)
describe('-l Lua', function()
local function assert_l_out(expected, nvim_args, lua_args, script, input)
local args = { nvim_prog }
vim.list_extend(args, nvim_args or {})
vim.list_extend(args, { '-l', (script or 'test/functional/fixtures/startup.lua') })
vim.list_extend(args, lua_args or {})
local out = fn.system(args, input):gsub('\r\n', '\n')
return eq(dedent(expected), out)
end
it('failure modes', function()
-- nvim -l <empty>
matches('nvim%.?e?x?e?: Argument missing after: "%-l"', fn.system({ nvim_prog, '-l' }))
eq(1, eval('v:shell_error'))
end)
it('os.exit() sets Nvim exitcode', function()
-- tricky: LeakSanitizer triggers on os.exit() and disrupts the return value, disable it
exec_lua [[
local asan_options = os.getenv('ASAN_OPTIONS') or ''
if asan_options ~= '' then
asan_options = asan_options .. ':'
end
vim.uv.os_setenv('ASAN_OPTIONS', asan_options .. ':detect_leaks=0')
]]
-- nvim -l foo.lua -arg1 -- a b c
assert_l_out(
[[
bufs:
nvim args: 7
lua args: { "-arg1", "--exitcode", "73", "--arg2",
[0] = "test/functional/fixtures/startup.lua"
}]],
{},
{ '-arg1', '--exitcode', '73', '--arg2' }
)
eq(73, eval('v:shell_error'))
end)
it('Lua-error sets Nvim exitcode', function()
eq(0, eval('v:shell_error'))
matches(
'E5113: .* my pearls!!',
fn.system({ nvim_prog, '-l', 'test/functional/fixtures/startup-fail.lua' })
)
eq(1, eval('v:shell_error'))
matches(
'E5113: .* %[string "error%("whoa"%)"%]:1: whoa',
fn.system({ nvim_prog, '-l', '-' }, 'error("whoa")')
)
eq(1, eval('v:shell_error'))
end)
it('executes stdin "-"', function()
assert_l_out(
'arg0=- args=2 whoa\n',
nil,
{ 'arg1', 'arg 2' },
'-',
"print(('arg0=%s args=%d %s'):format(_G.arg[0], #_G.arg, 'whoa'))"
)
assert_l_out(
'biiig input: 1000042\n',
nil,
nil,
'-',
('print("biiig input: "..("%s"):len())'):format(string.rep('x', (1000 * 1000) + 42))
)
eq(0, eval('v:shell_error'))
end)
it('does not truncate long print() message', function()
assert_l_out(('k'):rep(1234) .. '\n', nil, nil, '-', "print(('k'):rep(1234))")
end)
it('does not add newline when unnecessary', function()
assert_l_out('', nil, nil, '-', '')
assert_l_out('foobar\n', nil, nil, '-', [[print('foobar\n')]])
end)
it('sets _G.arg', function()
-- nvim -l foo.lua
assert_l_out(
[[
bufs:
nvim args: 3
lua args: {
[0] = "test/functional/fixtures/startup.lua"
}
]],
{},
{}
)
eq(0, eval('v:shell_error'))
-- nvim -l foo.lua [args]
assert_l_out(
[[
bufs:
nvim args: 7
lua args: { "-arg1", "--arg2", "--", "arg3",
[0] = "test/functional/fixtures/startup.lua"
}
]],
{},
{ '-arg1', '--arg2', '--', 'arg3' }
)
eq(0, eval('v:shell_error'))
-- nvim file1 file2 -l foo.lua -arg1 -- file3 file4
assert_l_out(
[[
bufs: file1 file2
nvim args: 10
lua args: { "-arg1", "arg 2", "--", "file3", "file4",
[0] = "test/functional/fixtures/startup.lua"
}
]],
{ 'file1', 'file2' },
{ '-arg1', 'arg 2', '--', 'file3', 'file4' }
)
eq(0, eval('v:shell_error'))
-- nvim -l foo.lua <vim args>
assert_l_out(
[[
bufs:
nvim args: 5
lua args: { "-c", "set wrap?",
[0] = "test/functional/fixtures/startup.lua"
}
]],
{},
{ '-c', 'set wrap?' }
)
eq(0, eval('v:shell_error'))
-- nvim <vim args> -l foo.lua <vim args>
assert_l_out(
[[
wrap
bufs:
nvim args: 7
lua args: { "-c", "set wrap?",
[0] = "test/functional/fixtures/startup.lua"
}
]],
{ '-c', 'set wrap?' },
{ '-c', 'set wrap?' }
)
eq(0, eval('v:shell_error'))
end)
it('disables swapfile/shada/config/plugins', function()
assert_l_out(
'updatecount=0 shadafile=NONE loadplugins=false scripts=1\n',
nil,
nil,
'-',
[[print(('updatecount=%d shadafile=%s loadplugins=%s scripts=%d'):format(
vim.o.updatecount, vim.o.shadafile, tostring(vim.o.loadplugins), math.max(1, #vim.fn.getscriptinfo())))]]
)
end)
end)
it('--cmd/-c/+ do not truncate long Lua print() message with --headless', function()
local out = fn.system({
nvim_prog,
'-u',
'NONE',
'-i',
'NONE',
'--headless',
'--cmd',
'lua print(("A"):rep(1234))',
'-c',
'lua print(("B"):rep(1234))',
'+lua print(("C"):rep(1234))',
'+q',
})
eq(('A'):rep(1234) .. '\r\n' .. ('B'):rep(1234) .. '\r\n' .. ('C'):rep(1234), out)
end)
it('pipe at both ends: has("ttyin")==0 has("ttyout")==0', function()
-- system() puts a pipe at both ends.
local out = fn.system({
nvim_prog,
'-u',
'NONE',
'-i',
'NONE',
'--headless',
'--cmd',
nvim_set,
'-c',
[[echo has('ttyin') has('ttyout')]],
'+q',
})
eq('0 0', out)
end)
it('with --embed: has("ttyin")==0 has("ttyout")==0', function()
local screen = Screen.new(25, 3)
-- Remote UI connected by --embed.
-- TODO: a lot of tests in this file already use the new default color scheme.
-- once we do the batch update of tests to use it, remove this workaround
screen._default_attr_ids = nil
command([[echo has('ttyin') has('ttyout')]])
screen:expect([[
^ |
~ |
0 0 |
]])
end)
it('in a TTY: has("ttyin")==1 has("ttyout")==1', function()
local screen = Screen.new(25, 4)
screen._default_attr_ids = nil
if is_os('win') then
command([[set shellcmdflag=/s\ /c shellxquote=\"]])
end
-- Running in :terminal
fn.termopen({
nvim_prog,
'-u',
'NONE',
'-i',
'NONE',
'--cmd',
nvim_set,
'-c',
'echo has("ttyin") has("ttyout")',
}, {
env = {
VIMRUNTIME = os.getenv('VIMRUNTIME'),
},
})
screen:expect([[
^ |
~ |
1 1 |
|
]])
end)
it('output to pipe: has("ttyin")==1 has("ttyout")==0', function()
clear({ env = { NVIM_LOG_FILE = testlog } })
if is_os('win') then
command([[set shellcmdflag=/s\ /c shellxquote=\"]])
end
os.remove('Xtest_startup_ttyout')
finally(function()
os.remove('Xtest_startup_ttyout')
end)
-- Running in :terminal
fn.termopen(
(
[["%s" -u NONE -i NONE --cmd "%s"]]
.. [[ -c "call writefile([has('ttyin'), has('ttyout')], 'Xtest_startup_ttyout')"]]
.. [[ -c q | cat -v]]
):format(nvim_prog, nvim_set),
{
env = {
VIMRUNTIME = os.getenv('VIMRUNTIME'),
},
}
)
retry(nil, 3000, function()
sleep(1)
eq(
'1\n0\n', -- stdin is a TTY, stdout is a pipe
read_file('Xtest_startup_ttyout')
)
end)
end)
it('input from pipe: has("ttyin")==0 has("ttyout")==1', function()
clear({ env = { NVIM_LOG_FILE = testlog } })
if is_os('win') then
command([[set shellcmdflag=/s\ /c shellxquote=\"]])
end
os.remove('Xtest_startup_ttyout')
finally(function()
os.remove('Xtest_startup_ttyout')
end)
-- Running in :terminal
fn.termopen(
(
[[echo foo | ]] -- Input from a pipe.
.. [["%s" -u NONE -i NONE --cmd "%s"]]
.. [[ -c "call writefile([has('ttyin'), has('ttyout')], 'Xtest_startup_ttyout')"]]
.. [[ -c q -- -]]
):format(nvim_prog, nvim_set),
{
env = {
VIMRUNTIME = os.getenv('VIMRUNTIME'),
},
}
)
retry(nil, 3000, function()
sleep(1)
eq(
'0\n1\n', -- stdin is a pipe, stdout is a TTY
read_file('Xtest_startup_ttyout')
)
end)
end)
it('input from pipe (implicit) #7679', function()
clear({ env = { NVIM_LOG_FILE = testlog } })
local screen = Screen.new(25, 4)
screen._default_attr_ids = nil
if is_os('win') then
command([[set shellcmdflag=/s\ /c shellxquote=\"]])
end
-- Running in :terminal
fn.termopen(
(
[[echo foo | ]]
.. [["%s" -u NONE -i NONE --cmd "%s"]]
.. [[ -c "echo has('ttyin') has('ttyout')"]]
):format(nvim_prog, nvim_set),
{
env = {
VIMRUNTIME = os.getenv('VIMRUNTIME'),
},
}
)
screen:expect([[
^foo |
~ |
0 1 |
|
]])
if not is_os('win') then
assert_log('Failed to get flags on descriptor 3: Bad file descriptor', testlog, 100)
end
end)
it('input from pipe + file args #7679', function()
eq(
'ohyeah\r\n0 0 bufs=3',
fn.system({
nvim_prog,
'-n',
'-u',
'NONE',
'-i',
'NONE',
'--headless',
'+.print',
"+echo has('ttyin') has('ttyout') 'bufs='.bufnr('$')",
'+qall!',
'-',
'test/functional/fixtures/tty-test.c',
'test/functional/fixtures/shell-test.c',
}, { 'ohyeah', '' })
)
end)
it('if stdin is empty: selects buffer 2, deletes buffer 1 #8561', function()
eq(
'\r\n 2 %a "file1" line 0\r\n 3 "file2" line 0',
fn.system({
nvim_prog,
'-n',
'-u',
'NONE',
'-i',
'NONE',
'--headless',
'+ls!',
'+qall!',
'-',
'file1',
'file2',
}, { '' })
)
end)
it('stdin with -es/-Es #7679', function()
local input = { 'append', 'line1', 'line2', '.', '%print', '' }
local inputstr = table.concat(input, '\n')
--
-- -Es: read stdin as text
--
eq(
'partylikeits1999\n',
fn.system({
nvim_prog,
'-n',
'-u',
'NONE',
'-i',
'NONE',
'-Es',
'+.print',
'test/functional/fixtures/tty-test.c',
}, { 'partylikeits1999', '' })
)
eq(inputstr, fn.system({ nvim_prog, '-i', 'NONE', '-Es', '+%print', '-' }, input))
-- with `-u NORC`
eq(
'thepartycontinues\n',
fn.system({ nvim_prog, '-n', '-u', 'NORC', '-Es', '+.print' }, { 'thepartycontinues', '' })
)
-- without `-u`
eq(
'thepartycontinues\n',
fn.system({ nvim_prog, '-n', '-Es', '+.print' }, { 'thepartycontinues', '' })
)
--
-- -es: read stdin as ex-commands
--
eq(
' encoding=utf-8\n',
fn.system({
nvim_prog,
'-n',
'-u',
'NONE',
'-i',
'NONE',
'-es',
'test/functional/fixtures/tty-test.c',
}, { 'set encoding', '' })
)
eq('line1\nline2\n', fn.system({ nvim_prog, '-i', 'NONE', '-es', '-' }, input))
-- with `-u NORC`
eq(
' encoding=utf-8\n',
fn.system({ nvim_prog, '-n', '-u', 'NORC', '-es' }, { 'set encoding', '' })
)
-- without `-u`
eq(' encoding=utf-8\n', fn.system({ nvim_prog, '-n', '-es' }, { 'set encoding', '' }))
end)
it('-es/-Es disables swapfile, user config #8540', function()
for _, arg in ipairs({ '-es', '-Es' }) do
local out = fn.system({
nvim_prog,
arg,
'+set swapfile? updatecount? shadafile?',
'+put =map(getscriptinfo(), {-> v:val.name})',
'+%print',
})
local line1 = string.match(out, '^.-\n')
-- updatecount=0 means swapfile was disabled.
eq(' swapfile updatecount=0 shadafile=\n', line1)
-- Standard plugins were loaded, but not user config.
ok(string.find(out, 'man.lua') ~= nil)
ok(string.find(out, 'init.vim') == nil)
end
end)
it('fails on --embed with -es/-Es/-l', function()
matches(
'nvim[.exe]*: %-%-embed conflicts with %-es/%-Es/%-l',
fn.system({ nvim_prog, '--embed', '-es' })
)
matches(
'nvim[.exe]*: %-%-embed conflicts with %-es/%-Es/%-l',
fn.system({ nvim_prog, '--embed', '-Es' })
)
matches(
'nvim[.exe]*: %-%-embed conflicts with %-es/%-Es/%-l',
fn.system({ nvim_prog, '--embed', '-l', 'foo.lua' })
)
end)
it('ENTER dismisses early message #7967', function()
local screen
screen = Screen.new(60, 6)
screen._default_attr_ids = nil
local id = fn.termopen({
nvim_prog,
'-u',
'NONE',
'-i',
'NONE',
'--cmd',
'set noruler',
'--cmd',
'let g:foo = g:bar',
}, {
env = {
VIMRUNTIME = os.getenv('VIMRUNTIME'),
},
})
screen:expect([[
^ |
|
Error detected while processing pre-vimrc command line: |
E121: Undefined variable: g:bar |
Press ENTER or type command to continue |
|
]])
fn.chansend(id, '\n')
screen:expect([[
^ |
~ |*2
[No Name] |
|*2
]])
end)
it('-r works without --headless in PTY #23294', function()
exec([[
func Normalize(data) abort
" Windows: remove ^M and term escape sequences
return map(a:data, 'substitute(substitute(v:val, "\r", "", "g"), "\x1b\\%(\\]\\d\\+;.\\{-}\x07\\|\\[.\\{-}[\x40-\x7E]\\)", "", "g")')
endfunc
func OnOutput(id, data, event) dict
let g:stdout = Normalize(a:data)
endfunc
call jobstart([v:progpath, '-u', 'NONE', '-i', 'NONE', '-r'], {
\ 'pty': v:true,
\ 'stdout_buffered': v:true,
\ 'on_stdout': function('OnOutput'),
\ })
]])
retry(nil, nil, function()
eq('Swap files found:', eval('g:stdout[0]'))
end)
end)
it('fixed hang issue with --headless (#11386)', function()
local expected = ''
local period = 100
for i = 1, period - 1 do
expected = expected .. i .. '\r\n'
end
expected = expected .. period
eq(
expected,
-- FIXME(codehex): We should really set a timeout for the system function.
-- If this test fails, there will be a waiting input state.
fn.system({
nvim_prog,
'-u',
'NONE',
'-c',
'for i in range(1, 100) | echo i | endfor | quit',
'--headless',
})
)
end)
it('get command line arguments from v:argv', function()
local out = fn.system({
nvim_prog,
'-u',
'NONE',
'-i',
'NONE',
'--headless',
'--cmd',
nvim_set,
'-c',
[[echo v:argv[-1:] len(v:argv) > 1]],
'+q',
})
eq("['+q'] 1", out)
end)
end)
describe('startup', function()
it('-e/-E interactive #7679', function()
clear('-e')
local screen = Screen.new(25, 3)
feed("put ='from -e'<CR>")
screen:expect([[
:put ='from -e' |
from -e |
:^ |
]])
clear('-E')
screen = Screen.new(25, 3)
feed("put ='from -E'<CR>")
screen:expect([[
:put ='from -E' |
from -E |
:^ |
]])
end)
it('-e sets ex mode', function()
clear('-e')
local screen = Screen.new(25, 3)
-- Verify we set the proper mode both before and after :vi.
feed('put =mode(1)<CR>vi<CR>:put =mode(1)<CR>')
screen:expect([[
cv |
^n |
:put =mode(1) |
]])
eq('cv\n', fn.system({ nvim_prog, '-n', '-es' }, { 'put =mode(1)', 'print', '' }))
end)
it('-d does not diff non-arglist windows #13720 #21289', function()
write_file(
'Xdiff.vim',
[[
let bufnr = nvim_create_buf(0, 1)
let config = {
\ 'relative': 'editor',
\ 'focusable': v:false,
\ 'width': 1,
\ 'height': 1,
\ 'row': 3,
\ 'col': 3
\ }
autocmd WinEnter * call nvim_open_win(bufnr, v:false, config)]]
)
finally(function()
os.remove('Xdiff.vim')
end)
clear { args = { '-u', 'Xdiff.vim', '-d', 'Xdiff.vim', 'Xdiff.vim' } }
eq(true, api.nvim_get_option_value('diff', { win = fn.win_getid(1) }))
eq(true, api.nvim_get_option_value('diff', { win = fn.win_getid(2) }))
local float_win = fn.win_getid(3)
eq('editor', api.nvim_win_get_config(float_win).relative)
eq(false, api.nvim_get_option_value('diff', { win = float_win }))
end)
it('does not crash if --embed is given twice', function()
clear { args = { '--embed' } }
assert_alive()
end)
it('does not crash when expanding cdpath during early_init', function()
clear { env = { CDPATH = '~doesnotexist' } }
assert_alive()
eq(',~doesnotexist', eval('&cdpath'))
end)
it("sets 'shortmess' when loading other tabs", function()
clear({ args = { '-p', 'a', 'b', 'c' } })
local screen = Screen.new(25, 4)
screen:expect({
grid = [[
{1: a }{2: b c }{3: }{2:X}|
^ |
{4:~ }|
|
]],
attr_ids = {
[1] = { bold = true },
[2] = { background = Screen.colors.LightGrey, underline = true },
[3] = { reverse = true },
[4] = { bold = true, foreground = Screen.colors.Blue1 },
},
})
end)
end)
describe('startup', function()
local function pack_clear(cmd)
-- add packages after config dir in rtp but before config/after
clear {
args = {
'--cmd',
'set packpath=test/functional/fixtures',
'--cmd',
'let paths=split(&rtp, ",")',
'--cmd',
'let &rtp = paths[0]..",test/functional/fixtures,test/functional/fixtures/middle,"..join(paths[1:],",")',
'--cmd',
cmd,
},
env = { XDG_CONFIG_HOME = 'test/functional/fixtures/' },
args_rm = { 'runtimepath' },
}
end
it('handles &packpath during startup', function()
pack_clear [[
let g:x = bar#test()
let g:y = leftpad#pad("heyya")
]]
eq(-3, eval 'g:x')
eq(' heyya', eval 'g:y')
pack_clear [[ lua _G.y = require'bar'.doit() _G.z = require'leftpad''howdy' ]]
eq({ 9003, '\thowdy' }, exec_lua [[ return { _G.y, _G.z } ]])
end)
it('handles require from &packpath in an async handler', function()
-- NO! you cannot just speed things up by calling async functions during startup!
-- It doesn't make anything actually faster! NOOOO!
pack_clear [[ lua require'async_leftpad'('brrrr', 'async_res') ]]
-- haha, async leftpad go brrrrr
eq('\tbrrrr', exec_lua [[ return _G.async_res ]])
end)
it('handles :packadd during startup', function()
-- control group: opt/bonus is not available by default
pack_clear [[
try
let g:x = bonus#secret()
catch
let g:err = v:exception
endtry
]]
eq('Vim(let):E117: Unknown function: bonus#secret', eval 'g:err')
pack_clear [[ lua _G.test = {pcall(function() require'bonus'.launch() end)} ]]
eq(
{ false, [[[string ":lua"]:1: module 'bonus' not found:]] },
exec_lua [[ _G.test[2] = string.gsub(_G.test[2], '[\r\n].*', '') return _G.test ]]
)
-- ok, time to launch the nukes:
pack_clear [[ packadd! bonus | let g:x = bonus#secret() ]]
eq('halloj', eval 'g:x')
pack_clear [[ packadd! bonus | lua _G.y = require'bonus'.launch() ]]
eq('CPE 1704 TKS', exec_lua [[ return _G.y ]])
end)
it('handles the correct order with start packages and after/', function()
pack_clear [[ lua _G.test_loadorder = {} vim.cmd "runtime! filen.lua" ]]
eq(
{ 'ordinary', 'FANCY', 'mittel', 'FANCY after', 'ordinary after' },
exec_lua [[ return _G.test_loadorder ]]
)
end)
it('handles the correct order with start packages and after/ after startup', function()
pack_clear [[ lua _G.test_loadorder = {} ]]
command [[ runtime! filen.lua ]]
eq(
{ 'ordinary', 'FANCY', 'mittel', 'FANCY after', 'ordinary after' },
exec_lua [[ return _G.test_loadorder ]]
)
end)
it('handles the correct order with globpath(&rtp, ...)', function()
pack_clear [[ set loadplugins | lua _G.test_loadorder = {} ]]
command [[
for x in globpath(&rtp, "filen.lua",1,1)
call v:lua.dofile(x)
endfor
]]
eq(
{ 'ordinary', 'FANCY', 'mittel', 'FANCY after', 'ordinary after' },
exec_lua [[ return _G.test_loadorder ]]
)
local rtp = api.nvim_get_option_value('rtp', {})
ok(
startswith(
rtp,
'test/functional/fixtures/nvim,test/functional/fixtures/pack/*/start/*,test/functional/fixtures/start/*,test/functional/fixtures,test/functional/fixtures/middle,'
),
'startswith(…)',
'rtp=' .. rtp
)
end)
it('handles the correct order with opt packages and after/', function()
pack_clear [[ lua _G.test_loadorder = {} vim.cmd "packadd! superspecial\nruntime! filen.lua" ]]
eq({
'ordinary',
'SuperSpecial',
'FANCY',
'mittel',
'FANCY after',
'SuperSpecial after',
'ordinary after',
}, exec_lua [[ return _G.test_loadorder ]])
end)
it('handles the correct order with opt packages and after/ after startup', function()
pack_clear [[ lua _G.test_loadorder = {} ]]
command [[
packadd! superspecial
runtime! filen.lua
]]
eq({
'ordinary',
'SuperSpecial',
'FANCY',
'mittel',
'FANCY after',
'SuperSpecial after',
'ordinary after',
}, exec_lua [[ return _G.test_loadorder ]])
end)
it('handles the correct order with opt packages and globpath(&rtp, ...)', function()
pack_clear [[ set loadplugins | lua _G.test_loadorder = {} ]]
command [[
packadd! superspecial
for x in globpath(&rtp, "filen.lua",1,1)
call v:lua.dofile(x)
endfor
]]
eq({
'ordinary',
'SuperSpecial',
'FANCY',
'mittel',
'SuperSpecial after',
'FANCY after',
'ordinary after',
}, exec_lua [[ return _G.test_loadorder ]])
end)
it('handles the correct order with a package that changes packpath', function()
pack_clear [[ lua _G.test_loadorder = {} vim.cmd "packadd! funky\nruntime! filen.lua" ]]
eq(
{ 'ordinary', 'funky!', 'FANCY', 'mittel', 'FANCY after', 'ordinary after' },
exec_lua [[ return _G.test_loadorder ]]
)
eq({ 'ordinary', 'funky!', 'mittel', 'ordinary after' }, exec_lua [[ return _G.nested_order ]])
end)
it('handles the correct order when prepending packpath', function()
clear {
args = {
'--cmd',
'set packpath^=test/functional/fixtures',
'--cmd',
[[ lua _G.test_loadorder = {} vim.cmd "runtime! filen.lua" ]],
},
env = { XDG_CONFIG_HOME = 'test/functional/fixtures/' },
}
eq(
{ 'ordinary', 'FANCY', 'FANCY after', 'ordinary after' },
exec_lua [[ return _G.test_loadorder ]]
)
end)
it('window widths are correct when modelines set &columns with tabpages', function()
write_file('Xtab1.noft', 'vim: columns=81')
write_file('Xtab2.noft', 'vim: columns=81')
finally(function()
os.remove('Xtab1.noft')
os.remove('Xtab2.noft')
end)
clear({ args = { '-p', 'Xtab1.noft', 'Xtab2.noft' } })
eq(81, api.nvim_win_get_width(0))
command('tabnext')
eq(81, api.nvim_win_get_width(0))
end)
end)
describe('sysinit', function()
local xdgdir = 'Xxdg'
local vimdir = 'Xvim'
local xhome = 'Xhome'
local pathsep = n.get_pathsep()
before_each(function()
rmdir(xdgdir)
rmdir(vimdir)
rmdir(xhome)
mkdir(xdgdir)
mkdir(xdgdir .. pathsep .. 'nvim')
write_file(
table.concat({ xdgdir, 'nvim', 'sysinit.vim' }, pathsep),
[[
let g:loaded = get(g:, "loaded", 0) + 1
let g:xdg = 1
]]
)
mkdir(vimdir)
write_file(
table.concat({ vimdir, 'sysinit.vim' }, pathsep),
[[
let g:loaded = get(g:, "loaded", 0) + 1
let g:vim = 1
]]
)
mkdir(xhome)
end)
after_each(function()
rmdir(xdgdir)
rmdir(vimdir)
rmdir(xhome)
end)
it('prefers XDG_CONFIG_DIRS over VIM', function()
clear {
args = { '--cmd', 'set nomore undodir=. directory=. belloff=' },
args_rm = { '-u', '--cmd' },
env = { HOME = xhome, XDG_CONFIG_DIRS = xdgdir, VIM = vimdir },
}
eq(
'loaded 1 xdg 1 vim 0',
eval('printf("loaded %d xdg %d vim %d", g:loaded, get(g:, "xdg", 0), get(g:, "vim", 0))')
)
end)
it('uses VIM if XDG_CONFIG_DIRS unset', function()
clear {
args = { '--cmd', 'set nomore undodir=. directory=. belloff=' },
args_rm = { '-u', '--cmd' },
env = { HOME = xhome, XDG_CONFIG_DIRS = '', VIM = vimdir },
}
eq(
'loaded 1 xdg 0 vim 1',
eval('printf("loaded %d xdg %d vim %d", g:loaded, get(g:, "xdg", 0), get(g:, "vim", 0))')
)
end)
end)
describe('user config init', function()
local xhome = 'Xhome'
local pathsep = n.get_pathsep()
local xconfig = xhome .. pathsep .. 'Xconfig'
local xdata = xhome .. pathsep .. 'Xdata'
local init_lua_path = table.concat({ xconfig, 'nvim', 'init.lua' }, pathsep)
local xenv = { XDG_CONFIG_HOME = xconfig, XDG_DATA_HOME = xdata }
before_each(function()
rmdir(xhome)
mkdir_p(xconfig .. pathsep .. 'nvim')
mkdir_p(xdata)
write_file(
init_lua_path,
[[
vim.g.lua_rc = 1
]]
)
end)
after_each(function()
rmdir(xhome)
end)
it('loads init.lua from XDG config home by default', function()
clear { args_rm = { '-u' }, env = xenv }
eq(1, eval('g:lua_rc'))
eq(fn.fnamemodify(init_lua_path, ':p'), eval('$MYVIMRC'))
end)
describe('loads existing', function()
local exrc_path = '.exrc'
local xstate = 'Xstate'
local xstateenv = { XDG_CONFIG_HOME = xconfig, XDG_DATA_HOME = xdata, XDG_STATE_HOME = xstate }
local function setup_exrc_file(filename)
exrc_path = filename
if string.find(exrc_path, '%.lua$') then
write_file(
exrc_path,
string.format(
[[
vim.g.exrc_file = "%s"
]],
exrc_path
)
)
else
write_file(
exrc_path,
string.format(
[[
let g:exrc_file = "%s"
]],
exrc_path
)
)
end
end
before_each(function()
write_file(
init_lua_path,
[[
vim.o.exrc = true
vim.g.exrc_file = '---'
]]
)
mkdir_p(xstate .. pathsep .. (is_os('win') and 'nvim-data' or 'nvim'))
end)
after_each(function()
os.remove(exrc_path)
rmdir(xstate)
end)
for _, filename in ipairs({ '.exrc', '.nvimrc', '.nvim.lua' }) do
it(filename .. ' in cwd', function()
setup_exrc_file(filename)
clear { args_rm = { '-u' }, env = xstateenv }
-- The 'exrc' file is not trusted, and the prompt is skipped because there is no UI.
eq('---', eval('g:exrc_file'))
local screen = Screen.new(50, 8)
screen._default_attr_ids = nil
fn.termopen({ nvim_prog }, {
env = {
VIMRUNTIME = os.getenv('VIMRUNTIME'),
},
})
screen:expect({ any = pesc('[i]gnore, (v)iew, (d)eny, (a)llow:') })
-- `i` to enter Terminal mode, `a` to allow
feed('ia')
screen:expect([[
^ |
~ |*4
[No Name] 0,0-1 All|
|
-- TERMINAL -- |
]])
feed(':echo g:exrc_file<CR>')
screen:expect(string.format(
[[
^ |
~ |*4
[No Name] 0,0-1 All|
%s%s|
-- TERMINAL -- |
]],
filename,
string.rep(' ', 50 - #filename)
))
clear { args_rm = { '-u' }, env = xstateenv }
-- The 'exrc' file is now trusted.
eq(filename, eval('g:exrc_file'))
end)
end
end)
describe('with explicitly provided config', function()
local custom_lua_path = table.concat({ xhome, 'custom.lua' }, pathsep)
before_each(function()
write_file(
custom_lua_path,
[[
vim.g.custom_lua_rc = 1
]]
)
end)
it('loads custom lua config and does not set $MYVIMRC', function()
clear { args = { '-u', custom_lua_path }, env = xenv }
eq(1, eval('g:custom_lua_rc'))
eq('', eval('$MYVIMRC'))
end)
end)
describe('VIMRC also exists', function()
before_each(function()
write_file(
table.concat({ xconfig, 'nvim', 'init.vim' }, pathsep),
[[
let g:vim_rc = 1
]]
)
end)
it('loads default lua config, but shows an error', function()
clear { args_rm = { '-u' }, env = xenv }
feed('<cr><c-c>') -- Dismiss "Conflicting config …" message.
eq(1, eval('g:lua_rc'))
matches('^E5422: Conflicting configs', exec_capture('messages'))
end)
end)
end)
describe('runtime:', function()
local xhome = 'Xhome'
local pathsep = n.get_pathsep()
local xconfig = xhome .. pathsep .. 'Xconfig'
local xdata = xhome .. pathsep .. 'Xdata'
local xenv = { XDG_CONFIG_HOME = xconfig, XDG_DATA_HOME = xdata }
setup(function()
rmdir(xhome)
mkdir_p(xconfig .. pathsep .. 'nvim')
mkdir_p(xdata)
end)
teardown(function()
rmdir(xhome)
end)
it('loads plugin/*.lua from XDG config home', function()
local plugin_folder_path = table.concat({ xconfig, 'nvim', 'plugin' }, pathsep)
local plugin_file_path = table.concat({ plugin_folder_path, 'plugin.lua' }, pathsep)
mkdir_p(plugin_folder_path)
finally(function()
rmdir(plugin_folder_path)
end)
write_file(plugin_file_path, [[ vim.g.lua_plugin = 1 ]])
clear { args_rm = { '-u' }, env = xenv }
eq(1, eval('g:lua_plugin'))
end)
it('loads plugin/*.lua from start packages', function()
local plugin_path =
table.concat({ xconfig, 'nvim', 'pack', 'category', 'start', 'test_plugin' }, pathsep)
local plugin_folder_path = table.concat({ plugin_path, 'plugin' }, pathsep)
local plugin_file_path = table.concat({ plugin_folder_path, 'plugin.lua' }, pathsep)
local profiler_file = 'test_startuptime.log'
mkdir_p(plugin_folder_path)
finally(function()
os.remove(profiler_file)
rmdir(plugin_path)
end)
write_file(plugin_file_path, [[vim.g.lua_plugin = 2]])
clear { args_rm = { '-u' }, args = { '--startuptime', profiler_file }, env = xenv }
eq(2, eval('g:lua_plugin'))
-- Check if plugin_file_path is listed in getscriptinfo()
local scripts = tbl_map(function(s)
return s.name
end, fn.getscriptinfo())
ok(#tbl_filter(function(s)
return endswith(s, plugin_file_path)
end, scripts) > 0)
-- Check if plugin_file_path is listed in startup profile
local profile_reader = io.open(profiler_file, 'r')
local profile_log = profile_reader:read('*a')
profile_reader:close()
ok(profile_log:find(plugin_file_path) ~= nil)
end)
it('loads plugin/*.lua from site packages', function()
local nvimdata = is_os('win') and 'nvim-data' or 'nvim'
local plugin_path =
table.concat({ xdata, nvimdata, 'site', 'pack', 'xa', 'start', 'yb' }, pathsep)
local plugin_folder_path = table.concat({ plugin_path, 'plugin' }, pathsep)
local plugin_after_path = table.concat({ plugin_path, 'after', 'plugin' }, pathsep)
local plugin_file_path = table.concat({ plugin_folder_path, 'plugin.lua' }, pathsep)
local plugin_after_file_path = table.concat({ plugin_after_path, 'helloo.lua' }, pathsep)
mkdir_p(plugin_folder_path)
mkdir_p(plugin_after_path)
finally(function()
rmdir(plugin_path)
end)
write_file(plugin_file_path, [[table.insert(_G.lista, "unos")]])
write_file(plugin_after_file_path, [[table.insert(_G.lista, "dos")]])
clear { args_rm = { '-u' }, args = { '--cmd', 'lua _G.lista = {}' }, env = xenv }
eq({ 'unos', 'dos' }, exec_lua 'return _G.lista')
end)
it('no crash setting &rtp in plugins with :packloadall called before #18315', function()
local plugin_folder_path = table.concat({ xconfig, 'nvim', 'plugin' }, pathsep)
mkdir_p(plugin_folder_path)
finally(function()
rmdir(plugin_folder_path)
end)
write_file(
table.concat({ plugin_folder_path, 'plugin.vim' }, pathsep),
[[
let &runtimepath = &runtimepath
let g:vim_plugin = 1
]]
)
write_file(
table.concat({ plugin_folder_path, 'plugin.lua' }, pathsep),
[[
vim.o.runtimepath = vim.o.runtimepath
vim.g.lua_plugin = 1
]]
)
clear { args_rm = { '-u' }, args = { '--cmd', 'packloadall' }, env = xenv }
eq(1, eval('g:vim_plugin'))
eq(1, eval('g:lua_plugin'))
end)
it("loads ftdetect/*.{vim,lua} respecting 'rtp' order", function()
local rtp_folder = table.concat({ xconfig, 'nvim' }, pathsep)
local after_rtp_folder = table.concat({ rtp_folder, 'after' }, pathsep)
local ftdetect_folder = table.concat({ rtp_folder, 'ftdetect' }, pathsep)
local after_ftdetect_folder = table.concat({ after_rtp_folder, 'ftdetect' }, pathsep)
mkdir_p(ftdetect_folder)
mkdir_p(after_ftdetect_folder)
finally(function()
rmdir(ftdetect_folder)
rmdir(after_ftdetect_folder)
end)
write_file(table.concat({ rtp_folder, 'scripts.vim' }, pathsep), [[let g:aseq ..= 'S']])
write_file(table.concat({ after_rtp_folder, 'scripts.vim' }, pathsep), [[let g:aseq ..= 's']])
-- A .lua file is loaded after a .vim file if they only differ in extension.
-- All files in after/ftdetect/ are loaded after all files in ftdetect/.
write_file(
table.concat({ ftdetect_folder, 'new-ft.vim' }, pathsep),
[[
let g:seq ..= 'A'
autocmd BufRead,BufNewFile FTDETECT let g:aseq ..= 'A'
]]
)
write_file(
table.concat({ ftdetect_folder, 'new-ft.lua' }, pathsep),
[[
vim.g.seq = vim.g.seq .. 'B'
vim.api.nvim_create_autocmd({ 'BufRead', 'BufNewFile' }, {
pattern = 'FTDETECT',
command = "let g:aseq ..= 'B'",
})
]]
)
write_file(
table.concat({ after_ftdetect_folder, 'new-ft.vim' }, pathsep),
[[
let g:seq ..= 'a'
autocmd BufRead,BufNewFile FTDETECT let g:aseq ..= 'a'
]]
)
write_file(
table.concat({ after_ftdetect_folder, 'new-ft.lua' }, pathsep),
[[
vim.g.seq = vim.g.seq .. 'b'
vim.api.nvim_create_autocmd({ 'BufRead', 'BufNewFile' }, {
pattern = 'FTDETECT',
command = "let g:aseq ..= 'b'",
})
]]
)
clear { args_rm = { '-u' }, args = { '--cmd', 'let g:seq = ""' }, env = xenv }
eq('ABab', eval('g:seq'))
command('let g:aseq = ""')
command('edit FTDETECT')
eq('SsABab', eval('g:aseq'))
end)
end)
describe('user session', function()
local xhome = 'Xhome'
local pathsep = n.get_pathsep()
local session_file = table.concat({ xhome, 'session.lua' }, pathsep)
before_each(function()
rmdir(xhome)
mkdir(xhome)
write_file(
session_file,
[[
vim.g.lua_session = 1
]]
)
end)
after_each(function()
rmdir(xhome)
end)
it('loads session from the provided lua file', function()
clear { args = { '-S', session_file }, env = { HOME = xhome } }
eq(1, eval('g:lua_session'))
end)
end)
describe('inccommand on ex mode', function()
it('should not preview', function()
clear()
local screen
screen = Screen.new(60, 10)
local id = fn.termopen({
nvim_prog,
'-u',
'NONE',
'-i',
'NONE',
'-c',
'set termguicolors background=dark',
'-E',
'test/README.md',
}, {
env = { VIMRUNTIME = os.getenv('VIMRUNTIME') },
})
fn.chansend(id, '%s/N')
screen:expect {
grid = [[
{1:^ }|
{1: }|*6
{1:Entering Ex mode. Type "visual" to go to Normal mode. }|
{1::%s/N }|
|
]],
attr_ids = {
[1] = {
background = Screen.colors.NvimDarkGrey2,
foreground = Screen.colors.NvimLightGrey2,
},
},
}
end)
end)