feat(startup): provide v:argf for file arguments #35889

Problem:
- `:args` and `argv()` can change after startup.
- `v:arg` includes options/commands, not just files.
- Plugins (e.g. Oil) may rewrite directory args.

Solution:
- New read-only var `v:argf`: snapshot of file/dir args at startup.
- Unaffected by `:args` or plugins.
- Unlike `v:argv`, excludes options/commands.
- Paths are resolved to absolute paths when possible

Example:

    nvim file1.txt dir1 file2.txt
    :echo v:argf
    " ['/home/user/project/file1.txt', '/home/user/project/dir1', '/home/user/project/file2.txt']
This commit is contained in:
Sanzhar Kuandyk
2026-02-25 13:38:08 +05:00
committed by GitHub
parent 6d73bf4886
commit cf874cee33
7 changed files with 152 additions and 0 deletions

View File

@@ -11,6 +11,21 @@ be mentioned at the variable description below. The type cannot be changed.
Type |gO| to see the table of contents.
*v:argf* *argf-variable*
v:argf
The list of file arguments passed on the command line at startup.
Each filename is expanded to an absolute path, so that v:argf
remains valid even if the current working directory changes later.
Unlike |v:argv|, this does not include option arguments
such as `-u`, `--cmd`, or `+cmd`. Unlike |argv()|, it is not
affected by later |:args|, |:argadd|, or plugin modifications.
It also handles the `--` separator correctly, including only
files specified after it.
This is a read-only snapshot of the original startup file arguments.
*v:argv* *argv-variable*
v:argv
The command line arguments Vim was invoked with. This is a

View File

@@ -6,6 +6,21 @@ error('Cannot require a meta file')
--- @class vim.v
vim.v = ...
--- The list of file arguments passed on the command line at startup.
---
--- Each filename is expanded to an absolute path, so that v:argf
--- remains valid even if the current working directory changes later.
---
--- Unlike `v:argv`, this does not include option arguments
--- such as `-u`, `--cmd`, or `+cmd`. Unlike `argv()`, it is not
--- affected by later `:args`, `:argadd`, or plugin modifications.
--- It also handles the `--` separator correctly, including only
--- files specified after it.
---
--- This is a read-only snapshot of the original startup file arguments.
--- @type string[]
vim.v.argf = ...
--- The command line arguments Vim was invoked with. This is a
--- list of strings. The first item is the Vim command.
--- See `v:progpath` for the command with full path.

View File

@@ -198,6 +198,7 @@ static struct vimvar {
VV(VV_EVENT, "event", VAR_DICT, VV_RO),
VV(VV_VERSIONLONG, "versionlong", VAR_NUMBER, VV_RO),
VV(VV_ECHOSPACE, "echospace", VAR_NUMBER, VV_RO),
VV(VV_ARGF, "argf", VAR_LIST, VV_RO),
VV(VV_ARGV, "argv", VAR_LIST, VV_RO),
VV(VV_COLLATE, "collate", VAR_STRING, VV_RO),
VV(VV_EXITING, "exiting", VAR_NUMBER, VV_RO),

View File

@@ -118,6 +118,7 @@ typedef enum {
VV_EVENT,
VV_VERSIONLONG,
VV_ECHOSPACE,
VV_ARGF,
VV_ARGV,
VV_COLLATE,
VV_EXITING,

View File

@@ -301,6 +301,8 @@ int main(int argc, char **argv)
// argument list "global_alist".
command_line_scan(&params);
set_argf_var();
nlua_init(argv, argc, params.lua_arg0);
TIME_MSG("init lua interpreter");
@@ -1506,6 +1508,22 @@ scripterror:
TIME_MSG("parsing arguments");
}
static void set_argf_var(void)
{
list_T *list = tv_list_alloc(kListLenMayKnow);
for (int i = 0; i < GARGCOUNT; i++) {
char *fname = alist_name(&GARGLIST[i]);
if (fname != NULL) {
(void)vim_FullName(fname, NameBuff, sizeof(NameBuff), false);
tv_list_append_string(list, NameBuff, -1);
}
}
tv_list_set_lock(list, VAR_FIXED);
set_vim_var_list(VV_ARGF, list);
}
// Many variables are in "params" so that we can pass them to invoked
// functions without a lot of arguments. "argc" and "argv" are also
// copied, so that they can be changed.

View File

@@ -1,6 +1,23 @@
local M = {}
M.vars = {
argf = {
type = 'string[]',
desc = [=[
The list of file arguments passed on the command line at startup.
Each filename is expanded to an absolute path, so that v:argf
remains valid even if the current working directory changes later.
Unlike |v:argv|, this does not include option arguments
such as `-u`, `--cmd`, or `+cmd`. Unlike |argv()|, it is not
affected by later |:args|, |:argadd|, or plugin modifications.
It also handles the `--` separator correctly, including only
files specified after it.
This is a read-only snapshot of the original startup file arguments.
]=],
},
argv = {
type = 'string[]',
desc = [=[

View File

@@ -1806,3 +1806,88 @@ describe('inccommand on ex mode', function()
]])
end)
end)
describe('v:argf', function()
local files = {}
before_each(function()
clear()
files = {}
for _, f in ipairs({
'Xargf_file1',
'Xargf_file2',
'Xargf_sep1',
'Xargf_sep2',
'Xargf_sep3',
}) do
os.remove(f)
end
end)
after_each(function()
for _, f in ipairs(files) do
os.remove(f)
end
end)
it('stores full path of file args', function()
local file1, file2 = 'Xargf_file1', 'Xargf_file2'
write_file(file1, '')
write_file(file2, '')
files = { file1, file2 }
local abs1 = fn.fnamemodify(file1, ':p')
local abs2 = fn.fnamemodify(file2, ':p')
local p = n.spawn_wait('--cmd', 'echo v:argf', '-c', 'qall', file1, file2)
eq(0, p.status)
matches(pesc(abs1), p.stderr)
matches(pesc(abs2), p.stderr)
end)
it('argadd does not affect v:argf', function()
local file1, file2 = 'Xargf_file1', 'Xargf_file2'
write_file(file1, '')
write_file(file2, '')
files = { file1, file2 }
local p = n.spawn_wait(
'--cmd',
'argadd extrafile.txt',
'--cmd',
'echo v:argf',
'-c',
'qall',
file1,
file2
)
eq(0, p.status)
eq(nil, string.find(p.stderr, 'extrafile.txt'))
end)
it('handles -- separator correctly', function()
local file1, file2, file3 = 'Xargf_sep1', 'Xargf_sep2', 'Xargf_sep3'
write_file(file1, '')
write_file(file2, '')
write_file(file3, '')
files = { file1, file2, file3 }
local abs1 = fn.fnamemodify(file1, ':p')
local abs2 = fn.fnamemodify(file2, ':p')
local abs3 = fn.fnamemodify(file3, ':p')
local p = n.spawn_wait(file1, '--cmd', 'echo v:argf', '-c', 'qall', '--', file2, file3)
eq(0, p.status)
matches(pesc(abs1), p.stderr)
matches(pesc(abs2), p.stderr)
matches(pesc(abs3), p.stderr)
end)
it('is read-only', function()
matches('E46', t.pcall_err(command, "let v:argf = ['foo']"))
end)
end)