commit cd9d8469b22cedeb5ccad3def6e3cfee2877577f
parent 5ad01184f3c3cb4f100bbdcebee3dd699a69b0da
Author: Gregory Anders <greg@gpanders.com>
Date: Tue, 20 May 2025 12:06:08 -0500
Merge pull request #34090 from yochem/exrc-search-parent
feat(exrc): unset 'exrc' to stop searching parent directories
Diffstat:
6 files changed, 47 insertions(+), 26 deletions(-)
diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt
@@ -123,6 +123,7 @@ DEFAULTS
• 'statusline' default is exposed as a statusline expression (previously it
was implemented as an internal C routine).
• Project-local configuration ('exrc') is also loaded from parent directories.
+ Unset 'exrc' to stop further search.
DIAGNOSTICS
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
@@ -2416,6 +2416,9 @@ A jump table for the options with a short description can be found at |Q_op|.
directories (ordered upwards), if the files are in the |trust| list.
Use |:trust| to manage trusted files. See also |vim.secure.read()|.
+ Unset 'exrc' to stop further searching of 'exrc' files in parent
+ directories, similar to |editorconfig.root|.
+
Compare 'exrc' to |editorconfig|:
- 'exrc' can execute any code; editorconfig only specifies settings.
- 'exrc' is Nvim-specific; editorconfig works in other editors.
diff --git a/runtime/lua/vim/_defaults.lua b/runtime/lua/vim/_defaults.lua
@@ -928,23 +928,31 @@ do
vim.api.nvim_create_autocmd('VimEnter', {
group = vim.api.nvim_create_augroup('nvim.find_exrc', {}),
- desc = 'Find project-local configuration',
+ desc = 'Find exrc files in parent directories',
callback = function()
- if vim.o.exrc then
- local files = vim.fs.find(
- { '.nvim.lua', '.nvimrc', '.exrc' },
- { type = 'file', upward = true, limit = math.huge }
- )
- for _, file in ipairs(files) do
- local trusted = vim.secure.read(file) --[[@as string|nil]]
- if trusted then
- if vim.endswith(file, '.lua') then
- loadstring(trusted)()
- else
- vim.api.nvim_exec2(trusted, {})
- end
+ if not vim.o.exrc then
+ return
+ end
+ local files = vim.fs.find({ '.nvim.lua', '.nvimrc', '.exrc' }, {
+ type = 'file',
+ upward = true,
+ limit = math.huge,
+ -- exrc in cwd already handled from C, thus start in parent directory.
+ path = vim.fs.dirname(vim.uv.cwd()),
+ })
+ for _, file in ipairs(files) do
+ local trusted = vim.secure.read(file) --[[@as string|nil]]
+ if trusted then
+ if vim.endswith(file, '.lua') then
+ loadstring(trusted)()
+ else
+ vim.api.nvim_exec2(trusted, {})
end
end
+ -- If the user unset 'exrc' in the current exrc then stop searching
+ if not vim.o.exrc then
+ return
+ end
end
end,
})
diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua
@@ -2080,6 +2080,9 @@ vim.bo.et = vim.bo.expandtab
--- directories (ordered upwards), if the files are in the `trust` list.
--- Use `:trust` to manage trusted files. See also `vim.secure.read()`.
---
+--- Unset 'exrc' to stop further searching of 'exrc' files in parent
+--- directories, similar to `editorconfig.root`.
+---
--- Compare 'exrc' to `editorconfig`:
--- - 'exrc' can execute any code; editorconfig only specifies settings.
--- - 'exrc' is Nvim-specific; editorconfig works in other editors.
diff --git a/src/nvim/options.lua b/src/nvim/options.lua
@@ -2749,6 +2749,9 @@ local options = {
directories (ordered upwards), if the files are in the |trust| list.
Use |:trust| to manage trusted files. See also |vim.secure.read()|.
+ Unset 'exrc' to stop further searching of 'exrc' files in parent
+ directories, similar to |editorconfig.root|.
+
Compare 'exrc' to |editorconfig|:
- 'exrc' can execute any code; editorconfig only specifies settings.
- 'exrc' is Nvim-specific; editorconfig works in other editors.
diff --git a/test/functional/core/startup_spec.lua b/test/functional/core/startup_spec.lua
@@ -1128,6 +1128,10 @@ describe('user config init', function()
end
before_each(function()
+ for _, file in ipairs({ '.exrc', '.nvimrc', '.nvim.lua' }) do
+ os.remove('../' .. file)
+ os.remove(file)
+ end
write_file(
init_lua_path,
[[
@@ -1139,7 +1143,10 @@ describe('user config init', function()
end)
after_each(function()
- os.remove(exrc_path)
+ for _, file in ipairs({ '.exrc', '.nvimrc', '.nvim.lua' }) do
+ os.remove('../' .. file)
+ os.remove(file)
+ end
rmdir(xstate)
end)
@@ -1188,13 +1195,9 @@ describe('user config init', function()
end)
end
- it('exrc from all parent directories', function()
- -- make sure that there are not any exrc files left from previous tests
- for _, file in ipairs({ '.exrc', '.nvimrc', '.nvim.lua', '../.nvim.lua', '../.nvimrc' }) do
- os.remove(file)
- end
- setup_exrc_file('../.exrc')
+ it('exrc from parent directories', function()
setup_exrc_file('.nvim.lua')
+ setup_exrc_file('../.exrc')
clear { args_rm = { '-u' }, env = xstateenv }
local screen = Screen.new(50, 8)
screen._default_attr_ids = nil
@@ -1206,16 +1209,16 @@ describe('user config init', function()
})
-- current directory exrc is found first
screen:expect({ any = '.nvim.lua' })
- screen:expect({ any = pesc('[i]gnore, (v)iew, (d)eny, (a)llow:') })
+ screen:expect({ any = pesc('[i]gnore, (v)iew, (d)eny, (a)llow:'), unchanged = true })
feed('ia')
-- after that the exrc in the parent directory
screen:expect({ any = '.exrc' })
- screen:expect({ any = pesc('[i]gnore, (v)iew, (d)eny, (a)llow:') })
- feed('ia')
- clear { args_rm = { '-u' }, env = xstateenv }
+ screen:expect({ any = pesc('[i]gnore, (v)iew, (d)eny, (a)llow:'), unchanged = true })
+ feed('a')
-- a total of 2 exrc files are executed
- eq(2, eval('g:exrc_count'))
+ feed(':echo g:exrc_count<CR>')
+ screen:expect({ any = '2' })
end)
end)