From d34c64e342dfba9248d1055e702d02620a1b31a8 Mon Sep 17 00:00:00 2001 From: Ghjuvan Lacambre Date: Thu, 16 Feb 2023 13:15:02 +0100 Subject: [PATCH] feat: $NVIM_APPNAME #22128 This commit implements the ability to control all of the XDG paths Neovim should use. This is done by setting an environment variable named NVIM_APPNAME. For example, setting $NVIM_APPNAME makes Neovim look for its configuration directory in $XDG_CONFIG_HOME/$NVIM_APPNAME instead of $XDG_CONFIG_HOME/nvim. If NVIM_APPNAME is not set or is an empty string, "nvim" will be used as default. The usecase for this feature is to enable an easy way to switch from configuration to configuration. One might argue that the various $XDG environment variables can already be used for this usecase. However, setting $XDG environment variables also affects tools spawned by Neovim. For example, while setting $XDG_CONFIG_HOME will enable Neovim to use a different configuration directory, it will also prevent Git from finding its "default" configuration. Closes https://github.com/neovim/neovim/issues/21691 --- runtime/doc/news.txt | 4 +++ runtime/doc/starting.txt | 12 +++++++ src/nvim/eval.c | 3 +- src/nvim/fileio.c | 5 ++- src/nvim/msgpack_rpc/server.c | 5 +-- src/nvim/os/stdpaths.c | 39 ++++++++++++++------- src/nvim/runtime.c | 41 +++++++++++++---------- test/functional/options/defaults_spec.lua | 26 ++++++++++++-- 8 files changed, 99 insertions(+), 36 deletions(-) diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 2afb22bb43..ae21bc47ca 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -55,6 +55,10 @@ NEW FEATURES *news-features* The following new APIs or features were added. +• A new environment variable named NVIM_APPNAME enables configuring the + directories where Neovim should find its configuration and state files. See + `:help $NVIM_APPNAME` . + • |nvim_open_win()| now accepts a relative `mouse` option to open a floating win relative to the mouse. Note that the mouse doesn't update frequently without setting `vim.o.mousemoveevent = true` diff --git a/runtime/doc/starting.txt b/runtime/doc/starting.txt index 5e0718c3bb..67a88913fb 100644 --- a/runtime/doc/starting.txt +++ b/runtime/doc/starting.txt @@ -1385,6 +1385,18 @@ STATE DIRECTORY (DEFAULT) ~ Note: Throughout the user manual these defaults are used as placeholders, e.g. "~/.config" is understood to mean "$XDG_CONFIG_HOME or ~/.config". +NVIM_APPNAME *$NVIM_APPNAME* +The XDG directories used by Nvim can be further configured by setting the +`$NVIM_APPNAME` environment variable. This variable controls the directory +Neovim will look for (and auto-create) in the various XDG parent directories. +For example, setting `$NVIM_APPNAME` to "neovim" before running Neovim will +result in Neovim looking for configuration files in `$XDG_CONFIG_HOME/neovim` +instead of `$XDG_CONFIG_HOME/nvim`. + +Note: Similarly to the $XDG environment variables, when +`$XDG_CONFIG_HOME/nvim` is mentionned, it should be understood as +`$XDG_CONFIG_HOME/$NVIM_APPNAME`. + LOG FILE *$NVIM_LOG_FILE* *E5430* Besides 'debug' and 'verbose', Nvim keeps a general log file for internal debugging, plugins and RPC clients. > diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 92a8ffd646..ccea4f4951 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -5368,13 +5368,14 @@ void get_xdg_var_list(const XDGVarType xdg, typval_T *rettv) return; } const void *iter = NULL; + const char *appname = get_appname(); do { size_t dir_len; const char *dir; iter = vim_env_iter(ENV_SEPCHAR, dirs, iter, &dir, &dir_len); if (dir != NULL && dir_len > 0) { char *dir_with_nvim = xmemdupz(dir, dir_len); - dir_with_nvim = concat_fnames_realloc(dir_with_nvim, "nvim", true); + dir_with_nvim = concat_fnames_realloc(dir_with_nvim, appname, true); tv_list_append_string(list, dir_with_nvim, (ssize_t)strlen(dir_with_nvim)); xfree(dir_with_nvim); } diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index a3cdd2bb23..755b8e0834 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -5181,7 +5181,10 @@ static void vim_mktempdir(void) // "/tmp/" exists, now try to create "/tmp/nvim./". add_pathsep(tmp); - xstrlcat(tmp, "nvim.", sizeof(tmp)); + + const char *appname = get_appname(); + xstrlcat(tmp, appname, sizeof(tmp)); + xstrlcat(tmp, ".", sizeof(tmp)); xstrlcat(tmp, user, sizeof(tmp)); (void)os_mkdir(tmp, 0700); // Always create, to avoid a race. bool owned = os_file_owned(tmp); diff --git a/src/nvim/msgpack_rpc/server.c b/src/nvim/msgpack_rpc/server.c index 1d75c208be..c6a0a4d8cc 100644 --- a/src/nvim/msgpack_rpc/server.c +++ b/src/nvim/msgpack_rpc/server.c @@ -91,13 +91,14 @@ char *server_address_new(const char *name) { static uint32_t count = 0; char fmt[ADDRESS_MAX_SIZE]; + const char *appname = get_appname(); #ifdef MSWIN int r = snprintf(fmt, sizeof(fmt), "\\\\.\\pipe\\%s.%" PRIu64 ".%" PRIu32, - name ? name : "nvim", os_get_pid(), count++); + name ? name : appname, os_get_pid(), count++); #else char *dir = stdpaths_get_xdg_var(kXDGRuntimeDir); int r = snprintf(fmt, sizeof(fmt), "%s/%s.%" PRIu64 ".%" PRIu32, - dir, name ? name : "nvim", os_get_pid(), count++); + dir, name ? name : appname, os_get_pid(), count++); xfree(dir); #endif if ((size_t)r >= sizeof(fmt)) { diff --git a/src/nvim/os/stdpaths.c b/src/nvim/os/stdpaths.c index 6b07b6ef70..5235828f7a 100644 --- a/src/nvim/os/stdpaths.c +++ b/src/nvim/os/stdpaths.c @@ -57,6 +57,18 @@ static const char *const xdg_defaults[] = { #endif }; +/// Get the value of $NVIM_APPNAME or "nvim" if not set. +/// +/// @return $NVIM_APPNAME value +const char *get_appname(void) +{ + const char *env_val = os_getenv("NVIM_APPNAME"); + if (env_val == NULL || *env_val == '\0') { + env_val = "nvim"; + } + return env_val; +} + /// Return XDG variable value /// /// @param[in] idx XDG variable to use. @@ -100,25 +112,28 @@ char *stdpaths_get_xdg_var(const XDGVarType idx) /// Return Nvim-specific XDG directory subpath. /// -/// Windows: Uses "…/nvim-data" for kXDGDataHome to avoid storing +/// Windows: Uses "…/$NVIM_APPNAME-data" for kXDGDataHome to avoid storing /// configuration and data files in the same path. #4403 /// /// @param[in] idx XDG directory to use. /// -/// @return [allocated] "{xdg_directory}/nvim" +/// @return [allocated] "{xdg_directory}/$NVIM_APPNAME" char *get_xdg_home(const XDGVarType idx) FUNC_ATTR_WARN_UNUSED_RESULT { char *dir = stdpaths_get_xdg_var(idx); + const char *appname = get_appname(); + size_t appname_len = strlen(appname); + assert(appname_len < (IOSIZE - sizeof("-data"))); + if (dir) { + xstrlcpy(IObuff, appname, appname_len + 1); #if defined(MSWIN) - dir = concat_fnames_realloc(dir, - ((idx == kXDGDataHome - || idx == kXDGStateHome) ? "nvim-data" : "nvim"), - true); -#else - dir = concat_fnames_realloc(dir, "nvim", true); + if (idx == kXDGDataHome || idx == kXDGStateHome) { + STRCAT(IObuff, "-data"); + } #endif + dir = concat_fnames_realloc(dir, IObuff, true); #ifdef BACKSLASH_IN_FILENAME slash_adjust(dir); @@ -131,7 +146,7 @@ char *get_xdg_home(const XDGVarType idx) /// /// @param[in] fname New component of the path. /// -/// @return [allocated] `$XDG_CACHE_HOME/nvim/{fname}` +/// @return [allocated] `$XDG_CACHE_HOME/$NVIM_APPNAME/{fname}` char *stdpaths_user_cache_subpath(const char *fname) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET { @@ -142,7 +157,7 @@ char *stdpaths_user_cache_subpath(const char *fname) /// /// @param[in] fname New component of the path. /// -/// @return [allocated] `$XDG_CONFIG_HOME/nvim/{fname}` +/// @return [allocated] `$XDG_CONFIG_HOME/$NVIM_APPNAME/{fname}` char *stdpaths_user_conf_subpath(const char *fname) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET { @@ -153,7 +168,7 @@ char *stdpaths_user_conf_subpath(const char *fname) /// /// @param[in] fname New component of the path. /// -/// @return [allocated] `$XDG_DATA_HOME/nvim/{fname}` +/// @return [allocated] `$XDG_DATA_HOME/$NVIM_APPNAME/{fname}` char *stdpaths_user_data_subpath(const char *fname) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET { @@ -166,7 +181,7 @@ char *stdpaths_user_data_subpath(const char *fname) /// @param[in] trailing_pathseps Amount of trailing path separators to add. /// @param[in] escape_commas If true, all commas will be escaped. /// -/// @return [allocated] `$XDG_STATE_HOME/nvim/{fname}`. +/// @return [allocated] `$XDG_STATE_HOME/$NVIM_APPNAME/{fname}`. char *stdpaths_user_state_subpath(const char *fname, const size_t trailing_pathseps, const bool escape_commas) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET diff --git a/src/nvim/runtime.c b/src/nvim/runtime.c index 9dc213d718..3487a8d7a2 100644 --- a/src/nvim/runtime.c +++ b/src/nvim/runtime.c @@ -1435,7 +1435,6 @@ static inline size_t compute_double_env_sep_len(const char *const val, const siz return ret; } -#define NVIM_SIZE (sizeof("nvim") - 1) /// Add directories to a ENV_SEPCHAR-separated array from a colon-separated one /// /// Commas are escaped in process. To each item PATHSEP "nvim" is appended in @@ -1464,6 +1463,8 @@ static inline char *add_env_sep_dirs(char *dest, const char *const val, const ch return dest; } const void *iter = NULL; + const char *appname = get_appname(); + const size_t appname_len = strlen(appname); do { size_t dir_len; const char *dir; @@ -1474,8 +1475,8 @@ static inline char *add_env_sep_dirs(char *dest, const char *const val, const ch if (!after_pathsep(dest - 1, dest)) { *dest++ = PATHSEP; } - memmove(dest, "nvim", NVIM_SIZE); - dest += NVIM_SIZE; + memmove(dest, appname, appname_len); + dest += appname_len; if (suf1 != NULL) { *dest++ = PATHSEP; memmove(dest, suf1, len1); @@ -1529,14 +1530,18 @@ static inline char *add_dir(char *dest, const char *const dir, const size_t dir_ if (!after_pathsep(dest - 1, dest)) { *dest++ = PATHSEP; } + const char *appname = get_appname(); + size_t appname_len = strlen(appname); + assert(appname_len < (IOSIZE - sizeof("-data"))); + xstrlcpy(IObuff, appname, appname_len + 1); #if defined(MSWIN) - size_t size = (type == kXDGDataHome ? sizeof("nvim-data") - 1 : NVIM_SIZE); - memmove(dest, (type == kXDGDataHome ? "nvim-data" : "nvim"), size); - dest += size; -#else - memmove(dest, "nvim", NVIM_SIZE); - dest += NVIM_SIZE; + if (type == kXDGDataHome || type == kXDGStateHome) { + STRCAT(IObuff, "-data"); + appname_len += 5; + } #endif + xstrlcpy(dest, IObuff, appname_len + 1); + dest += appname_len; if (suf1 != NULL) { *dest++ = PATHSEP; memmove(dest, suf1, len1); @@ -1596,16 +1601,17 @@ char *runtimepath_default(bool clean_arg) size_t config_len = 0; size_t vimruntime_len = 0; size_t libdir_len = 0; + const char *appname = get_appname(); + size_t appname_len = strlen(appname); if (data_home != NULL) { data_len = strlen(data_home); - if (data_len != 0) { + size_t nvim_data_size = appname_len; #if defined(MSWIN) - size_t nvim_size = (sizeof("nvim-data") - 1); -#else - size_t nvim_size = NVIM_SIZE; + nvim_data_size += sizeof("-data"); #endif + if (data_len != 0) { rtp_size += ((data_len + memcnt(data_home, ',', data_len) - + nvim_size + 1 + SITE_SIZE + 1 + + nvim_data_size + 1 + SITE_SIZE + 1 + !after_pathsep(data_home, data_home + data_len)) * 2 + AFTER_SIZE + 1); } @@ -1614,7 +1620,7 @@ char *runtimepath_default(bool clean_arg) config_len = strlen(config_home); if (config_len != 0) { rtp_size += ((config_len + memcnt(config_home, ',', config_len) - + NVIM_SIZE + 1 + + appname_len + 1 + !after_pathsep(config_home, config_home + config_len)) * 2 + AFTER_SIZE + 1); } @@ -1632,9 +1638,9 @@ char *runtimepath_default(bool clean_arg) } } rtp_size += compute_double_env_sep_len(data_dirs, - NVIM_SIZE + 1 + SITE_SIZE + 1, + appname_len + 1 + SITE_SIZE + 1, AFTER_SIZE + 1); - rtp_size += compute_double_env_sep_len(config_dirs, NVIM_SIZE + 1, + rtp_size += compute_double_env_sep_len(config_dirs, appname_len + 1, AFTER_SIZE + 1); char *rtp = NULL; if (rtp_size == 0) { @@ -1675,7 +1681,6 @@ freeall: return rtp; } -#undef NVIM_SIZE static void cmd_source(char *fname, exarg_T *eap) { diff --git a/test/functional/options/defaults_spec.lua b/test/functional/options/defaults_spec.lua index 84ec43f4cb..4242b6e493 100644 --- a/test/functional/options/defaults_spec.lua +++ b/test/functional/options/defaults_spec.lua @@ -566,8 +566,12 @@ end) describe('stdpath()', function() -- Windows appends 'nvim-data' instead of just 'nvim' to prevent collisions -- due to XDG_CONFIG_HOME, XDG_DATA_HOME and XDG_STATE_HOME being the same. - local datadir = is_os('win') and 'nvim-data' or 'nvim' - local statedir = is_os('win') and 'nvim-data' or 'nvim' + local function maybe_data(name) + return is_os('win') and name .. '-data' or name + end + + local datadir = maybe_data('nvim') + local statedir = maybe_data('nvim') local env_sep = is_os('win') and ';' or ':' it('acceptance', function() @@ -583,6 +587,24 @@ describe('stdpath()', function() assert_alive() -- Check for crash. #8393 end) + it('reacts to #NVIM_APPNAME', function() + local appname = "NVIM_APPNAME_TEST____________________________________" .. + "______________________________________________________________________" + clear({env={ NVIM_APPNAME=appname }}) + eq(appname, funcs.fnamemodify(funcs.stdpath('config'), ':t')) + eq(appname, funcs.fnamemodify(funcs.stdpath('cache'), ':t')) + eq(maybe_data(appname), funcs.fnamemodify(funcs.stdpath('log'), ':t')) + eq(maybe_data(appname), funcs.fnamemodify(funcs.stdpath('data'), ':t')) + eq(maybe_data(appname), funcs.fnamemodify(funcs.stdpath('state'), ':t')) + -- config_dirs and data_dirs are empty on windows, so don't check them on + -- that platform + if not is_os('win') then + eq(appname, funcs.fnamemodify(funcs.stdpath('config_dirs')[1], ':t')) + eq(appname, funcs.fnamemodify(funcs.stdpath('data_dirs')[1], ':t')) + end + assert_alive() -- Check for crash. #8393 + end) + context('returns a String', function() describe('with "config"' , function ()