diff --git a/runtime/doc/vvars.txt b/runtime/doc/vvars.txt index 5d7db6ce1f..9f0e7a8c37 100644 --- a/runtime/doc/vvars.txt +++ b/runtime/doc/vvars.txt @@ -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 diff --git a/runtime/lua/vim/_meta/vvars.lua b/runtime/lua/vim/_meta/vvars.lua index e5f28808e1..58cdddce48 100644 --- a/runtime/lua/vim/_meta/vvars.lua +++ b/runtime/lua/vim/_meta/vvars.lua @@ -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. diff --git a/src/nvim/eval/vars.c b/src/nvim/eval/vars.c index 579dde5b07..801b49c47c 100644 --- a/src/nvim/eval/vars.c +++ b/src/nvim/eval/vars.c @@ -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), diff --git a/src/nvim/eval_defs.h b/src/nvim/eval_defs.h index ce66d07699..365bddb00b 100644 --- a/src/nvim/eval_defs.h +++ b/src/nvim/eval_defs.h @@ -118,6 +118,7 @@ typedef enum { VV_EVENT, VV_VERSIONLONG, VV_ECHOSPACE, + VV_ARGF, VV_ARGV, VV_COLLATE, VV_EXITING, diff --git a/src/nvim/main.c b/src/nvim/main.c index 7087dd47d2..9c3ae2e5b5 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -301,6 +301,8 @@ int main(int argc, char **argv) // argument list "global_alist". command_line_scan(¶ms); + 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. diff --git a/src/nvim/vvars.lua b/src/nvim/vvars.lua index d6f8d715fe..6428627389 100644 --- a/src/nvim/vvars.lua +++ b/src/nvim/vvars.lua @@ -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 = [=[ diff --git a/test/functional/core/startup_spec.lua b/test/functional/core/startup_spec.lua index c8e7ec6612..34995af7fe 100644 --- a/test/functional/core/startup_spec.lua +++ b/test/functional/core/startup_spec.lua @@ -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)