From 5c51b45a8223035f1aa178eeb0f008b2eb818655 Mon Sep 17 00:00:00 2001 From: Jesse van der Pluijm Date: Thu, 15 Jan 2026 15:27:10 +0100 Subject: [PATCH] fix(startup): respect $NVIM_APPNAME in $XDG_CONFIG_DIRS paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Problem: $NVIM_APPNAME was not respected when searching $XDG_CONFIG_DIRS for config files. Nvim hardcoded "nvim" when constructing paths like `$XDG_CONFIG_DIRS/nvim/init.lua`, ignoring the $NVIM_APPNAME environment variable. This meant that config files like `$XDG_CONFIG_DIRS/myapp/init.lua` were not loaded, even though $NVIM_APPNAME was set to "myapp". Solution: Use `get_appname()` instead of hardcoded "nvim" for $XDG_CONFIG_DIRS paths in `do_system_initialization()` and `do_user_initialization()`. This makes $XDG_CONFIG_DIRS behave consistently with $XDG_CONFIG_HOME, which already respected $NVIM_APPNAME. As documented in `runtime/doc/starting.txt` (L1440-L1441): "In the help wherever `$XDG_CONFIG_…/nvim` is mentioned it is understood as `$XDG_CONFIG_…/$NVIM_APPNAME`." See: https://github.com/neovim/neovim/blob/43339dee40aa7731cc644315c15080e30aa1a67c/runtime/doc/starting.txt#L1440-L1441 Relates to #37405 --- src/nvim/main.c | 59 +++++++++++++++------------ test/functional/core/startup_spec.lua | 39 ++++++++++++++++++ 2 files changed, 72 insertions(+), 26 deletions(-) diff --git a/src/nvim/main.c b/src/nvim/main.c index 5faf64a2be..c76eaa4ee0 100644 --- 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 config file ($XDG_CONFIG_HOME/nvim/init.lua or init.vim). -/// 3. Sourcing other config files ($XDG_CONFIG_DIRS[1]/nvim/init.lua or 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 @@ -2072,10 +2076,8 @@ static bool do_user_initialization(void) char *const config_dirs = stdpaths_get_xdg_var(kXDGConfigDirs); if (config_dirs != NULL) { - const char vim_path_tail[] = { 'n', 'v', 'i', 'm', PATHSEP, - 'i', 'n', 'i', 't', '.', 'v', 'i', 'm', NUL }; - const char lua_path_tail[] = { 'n', 'v', 'i', 'm', PATHSEP, - 'i', 'n', 'i', 't', '.', 'l', 'u', 'a', NUL }; + const char *appname = get_appname(false); + size_t appname_len = strlen(appname); const void *iter = NULL; do { @@ -2087,24 +2089,30 @@ static bool do_user_initialization(void) } // Build: //init.lua - char *init_lua = xmalloc(dir_len + sizeof(lua_path_tail) + 1); - memmove(init_lua, dir, dir_len); + 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; - memmove(init_lua + dir_len + 1, lua_path_tail, sizeof(lua_path_tail)); + 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: //init.vim - char *vimrc = xmalloc(dir_len + sizeof(vim_path_tail) + 1); - memmove(vimrc, dir, dir_len); - vimrc[dir_len] = PATHSEP; - memmove(vimrc + dir_len + 1, vim_path_tail, sizeof(vim_path_tail)); + 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(vimrc)) { - semsg(e_conflicting_configs, init_lua, vimrc); + if (os_path_exists(init_vim)) { + semsg(e_conflicting_configs, init_lua, init_vim); } - xfree(vimrc); + xfree(init_vim); xfree(init_lua); xfree(config_dirs); do_exrc = p_exrc; @@ -2112,17 +2120,16 @@ static bool do_user_initialization(void) } xfree(init_lua); - // init.vim - if (do_source(vimrc, true, DOSO_VIMRC, NULL) != FAIL) { + 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 index c1085f624a..cecb03a3c0 100644 --- 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() @@ -1472,6 +1488,29 @@ describe('user config init', function() } 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)