neovim

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

commit 7720e52a0b4aa973540934ab6dcf4703dbde3b9f
parent ba6440c106f99b5d68980784805e8a60a295eafe
Author: Justin M. Keyes <justinkz@gmail.com>
Date:   Fri,  6 Feb 2026 13:28:43 -0500

Merge #37424 $XDG_CONFIG_DIRS with init.lua, $NVIM_APPNAME


Diffstat:
Msrc/nvim/errors.h | 2++
Msrc/nvim/main.c | 71++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------
Mtest/functional/core/startup_spec.lua | 91+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 145 insertions(+), 19 deletions(-)

diff --git a/src/nvim/errors.h b/src/nvim/errors.h @@ -216,6 +216,8 @@ EXTERN const char e_diff_anchors_with_hidden_windows[] INIT( = N_("E1562: Diff a EXTERN const char e_trustfile[] INIT(= N_("E5570: Cannot update trust file: %s")); EXTERN const char e_cannot_read_from_str_2[] INIT(= N_("E282: Cannot read from \"%s\"")); +EXTERN const char e_conflicting_configs[] INIT(= N_("E5422: Conflicting configs: \"%s\" \"%s\"")); + EXTERN const char e_unknown_option2[] INIT(= N_("E355: Unknown option: %s")); EXTERN const char top_bot_msg[] INIT(= N_("search hit TOP, continuing at BOTTOM")); diff --git a/src/nvim/main.c b/src/nvim/main.c @@ -1981,15 +1981,17 @@ static void exe_commands(mparm_T *parmp) /// /// Does one of the following things, stops after whichever succeeds: /// -/// 1. Source system vimrc file from $XDG_CONFIG_DIRS/nvim/sysinit.vim +/// 1. Source system vimrc file from $XDG_CONFIG_DIRS/$NVIM_APPNAME/sysinit.vim /// 2. Source system vimrc file from $VIM static void do_system_initialization(void) { char *const config_dirs = stdpaths_get_xdg_var(kXDGConfigDirs); if (config_dirs != NULL) { const void *iter = NULL; - const char path_tail[] = { - 'n', 'v', 'i', 'm', PATHSEP, + const char *appname = get_appname(false); + size_t appname_len = strlen(appname); + const char sysinit_suffix[] = { + PATHSEP, 's', 'y', 's', 'i', 'n', 'i', 't', '.', 'v', 'i', 'm', NUL }; do { @@ -1999,13 +2001,15 @@ static void do_system_initialization(void) if (dir == NULL || dir_len == 0) { break; } - char *vimrc = xmalloc(dir_len + sizeof(path_tail) + 1); + size_t path_len = dir_len + 1 + appname_len + sizeof(sysinit_suffix); + char *vimrc = xmalloc(path_len); memcpy(vimrc, dir, dir_len); if (vimrc[dir_len - 1] != PATHSEP) { vimrc[dir_len] = PATHSEP; dir_len += 1; } - memcpy(vimrc + dir_len, path_tail, sizeof(path_tail)); + memcpy(vimrc + dir_len, appname, appname_len); + memcpy(vimrc + dir_len + appname_len, sysinit_suffix, sizeof(sysinit_suffix)); if (do_source(vimrc, false, DOSO_NONE, NULL) != FAIL) { xfree(vimrc); xfree(config_dirs); @@ -2027,8 +2031,8 @@ static void do_system_initialization(void) /// Does one of the following things, stops after whichever succeeds: /// /// 1. Execution of VIMINIT environment variable. -/// 2. Sourcing user vimrc file ($XDG_CONFIG_HOME/nvim/init.vim). -/// 3. Sourcing other vimrc files ($XDG_CONFIG_DIRS[1]/nvim/init.vim, …). +/// 2. Sourcing user config file ($XDG_CONFIG_HOME/$NVIM_APPNAME/init.lua or init.vim). +/// 3. Sourcing other config files ($XDG_CONFIG_DIRS[1]/$NVIM_APPNAME/init.lua or init.vim, …). /// 4. Execution of EXINIT environment variable. /// /// @return True if it is needed to attempt to source exrc file according to @@ -2049,8 +2053,7 @@ static bool do_user_initialization(void) if (os_path_exists(init_lua_path) && do_source(init_lua_path, true, DOSO_VIMRC, NULL)) { if (os_path_exists(user_vimrc)) { - semsg(_("E5422: Conflicting configs: \"%s\" \"%s\""), init_lua_path, - user_vimrc); + semsg(e_conflicting_configs, init_lua_path, user_vimrc); } xfree(user_vimrc); @@ -2073,6 +2076,9 @@ static bool do_user_initialization(void) char *const config_dirs = stdpaths_get_xdg_var(kXDGConfigDirs); if (config_dirs != NULL) { + const char *appname = get_appname(false); + size_t appname_len = strlen(appname); + const void *iter = NULL; do { const char *dir; @@ -2081,22 +2087,49 @@ static bool do_user_initialization(void) if (dir == NULL || dir_len == 0) { break; } - const char path_tail[] = { 'n', 'v', 'i', 'm', PATHSEP, - 'i', 'n', 'i', 't', '.', 'v', 'i', 'm', NUL }; - char *vimrc = xmalloc(dir_len + sizeof(path_tail) + 1); - memmove(vimrc, dir, dir_len); - vimrc[dir_len] = PATHSEP; - memmove(vimrc + dir_len + 1, path_tail, sizeof(path_tail)); - if (do_source(vimrc, true, DOSO_VIMRC, NULL) != FAIL) { + + // Build: <xdg_dir>/<appname>/init.lua + const char init_lua_suffix[] = { PATHSEP, 'i', 'n', 'i', 't', '.', 'l', 'u', 'a', NUL }; + size_t init_lua_len = dir_len + 1 + appname_len + sizeof(init_lua_suffix); + char *init_lua = xmalloc(init_lua_len); + memcpy(init_lua, dir, dir_len); + init_lua[dir_len] = PATHSEP; + memcpy(init_lua + dir_len + 1, appname, appname_len); + memcpy(init_lua + dir_len + 1 + appname_len, init_lua_suffix, sizeof(init_lua_suffix)); + + // Build: <xdg_dir>/<appname>/init.vim + const char init_vim_suffix[] = { PATHSEP, 'i', 'n', 'i', 't', '.', 'v', 'i', 'm', NUL }; + size_t init_vim_len = dir_len + 1 + appname_len + sizeof(init_vim_suffix); + char *init_vim = xmalloc(init_vim_len); + memcpy(init_vim, dir, dir_len); + init_vim[dir_len] = PATHSEP; + memcpy(init_vim + dir_len + 1, appname, appname_len); + memcpy(init_vim + dir_len + 1 + appname_len, init_vim_suffix, sizeof(init_vim_suffix)); + + if (os_path_exists(init_lua) + && do_source(init_lua, true, DOSO_VIMRC, NULL)) { + if (os_path_exists(init_vim)) { + semsg(e_conflicting_configs, init_lua, init_vim); + } + + xfree(init_vim); + xfree(init_lua); + xfree(config_dirs); + do_exrc = p_exrc; + return do_exrc; + } + xfree(init_lua); + + if (do_source(init_vim, true, DOSO_VIMRC, NULL) != FAIL) { do_exrc = p_exrc; if (do_exrc) { - do_exrc = (path_full_compare(VIMRC_FILE, vimrc, false, true) != kEqualFiles); + do_exrc = (path_full_compare(VIMRC_FILE, init_vim, false, true) != kEqualFiles); } - xfree(vimrc); + xfree(init_vim); xfree(config_dirs); return do_exrc; } - xfree(vimrc); + xfree(init_vim); } while (iter != NULL); xfree(config_dirs); } diff --git a/test/functional/core/startup_spec.lua b/test/functional/core/startup_spec.lua @@ -1192,6 +1192,22 @@ describe('sysinit', function() eval('printf("loaded %d xdg %d vim %d", g:loaded, get(g:, "xdg", 0), get(g:, "vim", 0))') ) end) + + it('respects NVIM_APPNAME in XDG_CONFIG_DIRS', function() + local appname = 'mysysinitapp' + mkdir(xdgdir .. pathsep .. appname) + write_file( + table.concat({ xdgdir, appname, 'sysinit.vim' }, pathsep), + [[let g:appname_sysinit = 1]] + ) + clear { + args_rm = { '-u' }, + env = { HOME = xhome, XDG_CONFIG_DIRS = xdgdir, NVIM_APPNAME = appname }, + } + eq(1, eval('g:appname_sysinit')) + -- Should not load from nvim/ subdir (which has the default sysinit.vim from before_each) + eq(0, eval('get(g:, "xdg", 0)')) + end) end) describe('user config init', function() @@ -1421,6 +1437,81 @@ describe('user config init', function() ) end) end) + + describe('from XDG_CONFIG_DIRS', function() + local xdgdir = 'Xxdgconfigdirs' + + before_each(function() + -- Remove init.lua from XDG_CONFIG_HOME so nvim falls back to XDG_CONFIG_DIRS + os.remove(init_lua_path) + rmdir(xdgdir) + mkdir_p(xdgdir .. pathsep .. 'nvim') + end) + + after_each(function() + rmdir(xdgdir) + end) + + it('loads init.lua from XDG_CONFIG_DIRS when no config in XDG_CONFIG_HOME', function() + write_file( + table.concat({ xdgdir, 'nvim', 'init.lua' }, pathsep), + [[vim.g.xdg_config_dirs_lua = 1]] + ) + clear { + args_rm = { '-u' }, + env = { XDG_CONFIG_HOME = xconfig, XDG_DATA_HOME = xdata, XDG_CONFIG_DIRS = xdgdir }, + } + eq(1, eval('g:xdg_config_dirs_lua')) + eq( + fn.fnamemodify(table.concat({ xdgdir, 'nvim', 'init.lua' }, pathsep), ':p'), + eval('$MYVIMRC') + ) + end) + + it('prefers init.lua over init.vim, shows E5422', function() + write_file(table.concat({ xdgdir, 'nvim', 'init.lua' }, pathsep), [[vim.g.xdg_lua = 1]]) + write_file(table.concat({ xdgdir, 'nvim', 'init.vim' }, pathsep), [[let g:xdg_vim = 1]]) + clear { + args_rm = { '-u' }, + env = { XDG_CONFIG_HOME = xconfig, XDG_DATA_HOME = xdata, XDG_CONFIG_DIRS = xdgdir }, + } + eq(1, eval('g:xdg_lua')) + eq(0, eval('get(g:, "xdg_vim", 0)')) + t.matches('E5422: Conflicting configs:', eval('v:errmsg')) + end) + + it('falls back to init.vim when no init.lua', function() + write_file(table.concat({ xdgdir, 'nvim', 'init.vim' }, pathsep), [[let g:xdg_vim = 1]]) + clear { + args_rm = { '-u' }, + env = { XDG_CONFIG_HOME = xconfig, XDG_DATA_HOME = xdata, XDG_CONFIG_DIRS = xdgdir }, + } + eq(1, eval('g:xdg_vim')) + end) + + it('respects NVIM_APPNAME', function() + local appname = 'mytestapp' + mkdir_p(xdgdir .. pathsep .. appname) + -- Also create nvim/ with a config that should NOT be loaded + write_file(table.concat({ xdgdir, 'nvim', 'init.lua' }, pathsep), [[vim.g.wrong = 1]]) + write_file(table.concat({ xdgdir, appname, 'init.lua' }, pathsep), [[vim.g.appname_lua = 1]]) + clear { + args_rm = { '-u' }, + env = { + XDG_CONFIG_HOME = xconfig, + XDG_DATA_HOME = xdata, + XDG_CONFIG_DIRS = xdgdir, + NVIM_APPNAME = appname, + }, + } + eq(1, eval('g:appname_lua')) + eq(0, eval('get(g:, "wrong", 0)')) + eq( + fn.fnamemodify(table.concat({ xdgdir, appname, 'init.lua' }, pathsep), ':p'), + eval('$MYVIMRC') + ) + end) + end) end) describe('runtime:', function()