refactor: split some more functions from misc1.c

This commit pulls the some environment-variable handling functions out of
misc1.c and in to os/env.c. Previously submited as #1231, this is the start of
a patch series that does that work based on a more up-to-date master branch.

Major tasks accomplished:
  - move functions and fix includes
  - fix clint/clang analysis warnings
  - correct documentation comments
This commit is contained in:
bobtwinkles 2015-03-13 23:27:54 -04:00 committed by Justin M. Keyes
parent ed1070bb24
commit 03d47965c0
13 changed files with 607 additions and 616 deletions

View File

@ -1744,7 +1744,7 @@ ex_let_one (
name[len] = NUL;
p = get_tv_string_chk(tv);
if (p != NULL && op != NULL && *op == '.') {
int mustfree = FALSE;
bool mustfree = false;
char_u *s = vim_getenv(name, &mustfree);
if (s != NULL) {
@ -6300,7 +6300,7 @@ static int get_env_tv(char_u **arg, typval_T *rettv, int evaluate)
{
char_u *name;
char_u *string = NULL;
int mustfree = FALSE;
bool mustfree = false;
int len;
int cc;

View File

@ -5059,7 +5059,7 @@ void fix_help_buffer(void)
char_u *fname;
char_u *p;
char_u *rt;
int mustfree;
bool mustfree;
/* set filetype to "help". */
set_option_value((char_u *)"ft", 0L, (char_u *)"help", OPT_LOCAL);

View File

@ -3808,7 +3808,7 @@ expand_shellcmd (
char_u *pat;
int i;
char_u *path;
int mustfree = FALSE;
bool mustfree = false;
garray_T ga;
char_u *buf = xmalloc(MAXPATHL);
size_t l;

View File

@ -51,6 +51,7 @@
#include "nvim/undo.h"
#include "nvim/os/event.h"
#include "nvim/os/input.h"
#include "nvim/os/os.h"
/*
* These buffers are used for storing:

View File

@ -32,7 +32,6 @@
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/misc1.h"
#include "nvim/misc2.h"
#include "nvim/garray.h"
#include "nvim/option.h"

View File

@ -20,7 +20,6 @@
#include "nvim/eval.h"
#include "nvim/fileio.h"
#include "nvim/message.h"
#include "nvim/misc1.h"
#include "nvim/misc2.h"
#include "nvim/memory.h"
#include "nvim/os/time.h"

View File

@ -8,7 +8,6 @@
#include <unistd.h>
#include "nvim/log.h"
#include "nvim/misc1.h"
#include "nvim/types.h"
#include "nvim/os/os.h"
#include "nvim/os/time.h"

View File

@ -818,7 +818,7 @@ static void init_locale(void)
# endif
{
int mustfree = FALSE;
bool mustfree = false;
char_u *p;
/* expand_env() doesn't work yet, because chartab[] is not initialized

View File

@ -30,7 +30,6 @@
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/misc1.h"
#include "nvim/misc2.h"
#include "nvim/normal.h"
#include "nvim/option.h"

View File

@ -56,7 +56,6 @@
#include "nvim/fileio.h"
#include "nvim/memline.h"
#include "nvim/message.h"
#include "nvim/misc1.h"
#include "nvim/misc2.h"
#include "nvim/memory.h"
#include "nvim/os_unix.h"

View File

@ -47,7 +47,6 @@
#include "nvim/mouse.h"
#include "nvim/option.h"
#include "nvim/os_unix.h"
#include "nvim/path.h"
#include "nvim/quickfix.h"
#include "nvim/regexp.h"
#include "nvim/screen.h"
@ -2608,54 +2607,7 @@ void vim_beep(void)
}
}
/*
* To get the "real" home directory:
* - get value of $HOME
* For Unix:
* - go to that directory
* - do os_dirname() to get the real name of that directory.
* This also works with mounts and links.
* Don't do this for MS-DOS, it will change the "current dir" for a drive.
*/
static char_u *homedir = NULL;
void init_homedir(void)
{
char_u *var;
/* In case we are called a second time (when 'encoding' changes). */
free(homedir);
homedir = NULL;
var = (char_u *)os_getenv("HOME");
if (var != NULL && *var == NUL) /* empty is same as not set */
var = NULL;
if (var != NULL) {
#ifdef UNIX
/*
* Change to the directory and get the actual path. This resolves
* links. Don't do it when we can't return.
*/
if (os_dirname(NameBuff, MAXPATHL) == OK
&& os_chdir((char *)NameBuff) == 0) {
if (!os_chdir((char *)var) && os_dirname(IObuff, IOSIZE) == OK)
var = IObuff;
if (os_chdir((char *)NameBuff) != 0)
EMSG(_(e_prev_dir));
}
#endif
homedir = vim_strsave(var);
}
}
#if defined(EXITFREE)
void free_homedir(void)
{
free(homedir);
}
void free_users(void)
{
@ -2664,430 +2616,6 @@ void free_users(void)
#endif
/*
* Call expand_env() and store the result in an allocated string.
* This is not very memory efficient, this expects the result to be freed
* again soon.
*/
char_u *expand_env_save(char_u *src)
{
return expand_env_save_opt(src, FALSE);
}
/*
* Idem, but when "one" is TRUE handle the string as one file name, only
* expand "~" at the start.
*/
char_u *expand_env_save_opt(char_u *src, int one)
{
char_u *p = xmalloc(MAXPATHL);
expand_env_esc(src, p, MAXPATHL, FALSE, one, NULL);
return p;
}
/*
* Expand environment variable with path name.
* "~/" is also expanded, using $HOME. For Unix "~user/" is expanded.
* Skips over "\ ", "\~" and "\$" (not for Win32 though).
* If anything fails no expansion is done and dst equals src.
*/
void
expand_env (
char_u *src, /* input string e.g. "$HOME/vim.hlp" */
char_u *dst, /* where to put the result */
int dstlen /* maximum length of the result */
)
{
expand_env_esc(src, dst, dstlen, FALSE, FALSE, NULL);
}
void
expand_env_esc (
char_u *srcp, /* input string e.g. "$HOME/vim.hlp" */
char_u *dst, /* where to put the result */
int dstlen, /* maximum length of the result */
int esc, /* escape spaces in expanded variables */
int one, /* "srcp" is one file name */
char_u *startstr /* start again after this (can be NULL) */
)
{
char_u *src;
char_u *tail;
int c;
char_u *var;
int copy_char;
int mustfree; /* var was allocated, need to free it later */
int at_start = TRUE; /* at start of a name */
int startstr_len = 0;
if (startstr != NULL)
startstr_len = (int)STRLEN(startstr);
src = skipwhite(srcp);
--dstlen; /* leave one char space for "\," */
while (*src && dstlen > 0) {
copy_char = TRUE;
if ((*src == '$'
)
|| (*src == '~' && at_start)) {
mustfree = FALSE;
/*
* The variable name is copied into dst temporarily, because it may
* be a string in read-only memory and a NUL needs to be appended.
*/
if (*src != '~') { /* environment var */
tail = src + 1;
var = dst;
c = dstlen - 1;
#ifdef UNIX
/* Unix has ${var-name} type environment vars */
if (*tail == '{' && !vim_isIDc('{')) {
tail++; /* ignore '{' */
while (c-- > 0 && *tail && *tail != '}')
*var++ = *tail++;
} else
#endif
{
while (c-- > 0 && *tail != NUL && ((vim_isIDc(*tail))
)) {
*var++ = *tail++;
}
}
#if defined(MSWIN) || defined(UNIX)
# ifdef UNIX
if (src[1] == '{' && *tail != '}')
# else
if (*src == '%' && *tail != '%')
# endif
var = NULL;
else {
# ifdef UNIX
if (src[1] == '{')
# else
if (*src == '%')
#endif
++tail;
#endif
*var = NUL;
var = vim_getenv(dst, &mustfree);
#if defined(MSWIN) || defined(UNIX)
}
#endif
}
/* home directory */
else if ( src[1] == NUL
|| vim_ispathsep(src[1])
|| vim_strchr((char_u *)" ,\t\n", src[1]) != NULL) {
var = homedir;
tail = src + 1;
} else { /* user directory */
#if defined(UNIX)
/*
* Copy ~user to dst[], so we can put a NUL after it.
*/
tail = src;
var = dst;
c = dstlen - 1;
while ( c-- > 0
&& *tail
&& vim_isfilec(*tail)
&& !vim_ispathsep(*tail))
*var++ = *tail++;
*var = NUL;
/*
* Use os_get_user_directory() to get the user directory.
* If this function fails, the shell is used to
* expand ~user. This is slower and may fail if the shell
* does not support ~user (old versions of /bin/sh).
*/
var = (char_u *)os_get_user_directory((char *)dst + 1);
mustfree = TRUE;
if (var == NULL)
{
expand_T xpc;
ExpandInit(&xpc);
xpc.xp_context = EXPAND_FILES;
var = ExpandOne(&xpc, dst, NULL,
WILD_ADD_SLASH|WILD_SILENT, WILD_EXPAND_FREE);
mustfree = TRUE;
}
#else
/* cannot expand user's home directory, so don't try */
var = NULL;
tail = (char_u *)""; /* for gcc */
#endif /* UNIX */
}
#ifdef BACKSLASH_IN_FILENAME
/* If 'shellslash' is set change backslashes to forward slashes.
* Can't use slash_adjust(), p_ssl may be set temporarily. */
if (p_ssl && var != NULL && vim_strchr(var, '\\') != NULL) {
char_u *p = vim_strsave(var);
if (mustfree) {
free(var);
}
var = p;
mustfree = TRUE;
forward_slash(var);
}
#endif
/* If "var" contains white space, escape it with a backslash.
* Required for ":e ~/tt" when $HOME includes a space. */
if (esc && var != NULL && vim_strpbrk(var, (char_u *)" \t") != NULL) {
char_u *p = vim_strsave_escaped(var, (char_u *)" \t");
if (mustfree)
free(var);
var = p;
mustfree = TRUE;
}
if (var != NULL && *var != NUL
&& (STRLEN(var) + STRLEN(tail) + 1 < (unsigned)dstlen)) {
STRCPY(dst, var);
dstlen -= (int)STRLEN(var);
c = (int)STRLEN(var);
/* if var[] ends in a path separator and tail[] starts
* with it, skip a character */
if (*var != NUL && after_pathsep(dst, dst + c)
#if defined(BACKSLASH_IN_FILENAME)
&& dst[-1] != ':'
#endif
&& vim_ispathsep(*tail))
++tail;
dst += c;
src = tail;
copy_char = FALSE;
}
if (mustfree)
free(var);
}
if (copy_char) { /* copy at least one char */
/*
* Recognize the start of a new name, for '~'.
* Don't do this when "one" is TRUE, to avoid expanding "~" in
* ":edit foo ~ foo".
*/
at_start = FALSE;
if (src[0] == '\\' && src[1] != NUL) {
*dst++ = *src++;
--dstlen;
} else if ((src[0] == ' ' || src[0] == ',') && !one)
at_start = TRUE;
*dst++ = *src++;
--dstlen;
if (startstr != NULL && src - startstr_len >= srcp
&& STRNCMP(src - startstr_len, startstr, startstr_len) == 0)
at_start = TRUE;
}
}
*dst = NUL;
}
/*
* Vim's version of getenv().
* Special handling of $HOME, $VIM and $VIMRUNTIME.
* Also does ACP to 'enc' conversion for Win32.
* "mustfree" is set to TRUE when returned is allocated, it must be
* initialized to FALSE by the caller.
*/
char_u *vim_getenv(char_u *name, int *mustfree)
{
char_u *p;
char_u *pend;
int vimruntime;
p = (char_u *)os_getenv((char *)name);
if (p != NULL && *p == NUL) /* empty is the same as not set */
p = NULL;
if (p != NULL) {
return p;
}
vimruntime = (STRCMP(name, "VIMRUNTIME") == 0);
if (!vimruntime && STRCMP(name, "VIM") != 0)
return NULL;
/*
* When expanding $VIMRUNTIME fails, try using $VIM/vim<version> or $VIM.
* Don't do this when default_vimruntime_dir is non-empty.
*/
if (vimruntime
#ifdef HAVE_PATHDEF
&& *default_vimruntime_dir == NUL
#endif
) {
p = (char_u *)os_getenv("VIM");
if (p != NULL && *p == NUL) /* empty is the same as not set */
p = NULL;
if (p != NULL) {
p = vim_version_dir(p);
if (p != NULL)
*mustfree = TRUE;
else
p = (char_u *)os_getenv("VIM");
}
}
/*
* When expanding $VIM or $VIMRUNTIME fails, try using:
* - the directory name from 'helpfile' (unless it contains '$')
* - the executable name from argv[0]
*/
if (p == NULL) {
if (p_hf != NULL && vim_strchr(p_hf, '$') == NULL)
p = p_hf;
if (p != NULL) {
/* remove the file name */
pend = path_tail(p);
/* remove "doc/" from 'helpfile', if present */
if (p == p_hf)
pend = remove_tail(p, pend, (char_u *)"doc");
/* for $VIM, remove "runtime/" or "vim54/", if present */
if (!vimruntime) {
pend = remove_tail(p, pend, (char_u *)RUNTIME_DIRNAME);
pend = remove_tail(p, pend, (char_u *)VIM_VERSION_NODOT);
}
/* remove trailing path separator */
/* With MacOS path (with colons) the final colon is required */
/* to avoid confusion between absolute and relative path */
if (pend > p && after_pathsep(p, pend))
--pend;
/* check that the result is a directory name */
p = vim_strnsave(p, (int)(pend - p));
if (!os_isdir(p)) {
free(p);
p = NULL;
} else {
*mustfree = TRUE;
}
}
}
#ifdef HAVE_PATHDEF
/* When there is a pathdef.c file we can use default_vim_dir and
* default_vimruntime_dir */
if (p == NULL) {
/* Only use default_vimruntime_dir when it is not empty */
if (vimruntime && *default_vimruntime_dir != NUL) {
p = default_vimruntime_dir;
*mustfree = FALSE;
} else if (*default_vim_dir != NUL) {
if (vimruntime && (p = vim_version_dir(default_vim_dir)) != NULL)
*mustfree = TRUE;
else {
p = default_vim_dir;
*mustfree = FALSE;
}
}
}
#endif
/*
* Set the environment variable, so that the new value can be found fast
* next time, and others can also use it (e.g. Perl).
*/
if (p != NULL) {
if (vimruntime) {
vim_setenv((char_u *)"VIMRUNTIME", p);
didset_vimruntime = TRUE;
} else {
vim_setenv((char_u *)"VIM", p);
didset_vim = TRUE;
}
}
return p;
}
/*
* Check if the directory "vimdir/<version>" or "vimdir/runtime" exists.
* Return NULL if not, return its name in allocated memory otherwise.
*/
static char_u *vim_version_dir(char_u *vimdir)
{
char_u *p;
if (vimdir == NULL || *vimdir == NUL)
return NULL;
p = concat_fnames(vimdir, (char_u *)VIM_VERSION_NODOT, TRUE);
if (os_isdir(p))
return p;
free(p);
p = concat_fnames(vimdir, (char_u *)RUNTIME_DIRNAME, TRUE);
if (os_isdir(p))
return p;
free(p);
return NULL;
}
/*
* If the string between "p" and "pend" ends in "name/", return "pend" minus
* the length of "name/". Otherwise return "pend".
*/
static char_u *remove_tail(char_u *p, char_u *pend, char_u *name)
{
int len = (int)STRLEN(name) + 1;
char_u *newend = pend - len;
if (newend >= p
&& fnamencmp(newend, name, len - 1) == 0
&& (newend == p || after_pathsep(p, newend)))
return newend;
return pend;
}
/*
* Our portable version of setenv.
*/
void vim_setenv(char_u *name, char_u *val)
{
os_setenv((char *)name, (char *)val, 1);
/*
* When setting $VIMRUNTIME adjust the directory to find message
* translations to $VIMRUNTIME/lang.
*/
if (*val != NUL && STRICMP(name, "VIMRUNTIME") == 0) {
char_u *buf = concat_str(val, (char_u *)"/lang");
bindtextdomain(VIMPACKAGE, (char *)buf);
free(buf);
}
}
/*
* Function given to ExpandGeneric() to obtain an environment variable name.
*/
char_u *get_env_name(expand_T *xp, int idx)
{
# define ENVNAMELEN 100
// this static buffer is needed to avoid a memory leak in ExpandGeneric
static char_u name[ENVNAMELEN];
char *envname = os_getenvname_at_index(idx);
if (envname) {
STRLCPY(name, envname, ENVNAMELEN);
free(envname);
return name;
} else {
return NULL;
}
}
/*
* Find all user names for user completion.
* Done only once and then cached.
@ -3137,137 +2665,6 @@ int match_user(char_u *name)
return result;
}
/*
* Replace home directory by "~" in each space or comma separated file name in
* 'src'.
* If anything fails (except when out of space) dst equals src.
*/
void
home_replace (
buf_T *buf, /* when not NULL, check for help files */
char_u *src, /* input file name */
char_u *dst, /* where to put the result */
int dstlen, /* maximum length of the result */
int one /* if TRUE, only replace one file name, include
spaces and commas in the file name. */
)
{
size_t dirlen = 0, envlen = 0;
size_t len;
char_u *homedir_env, *homedir_env_orig;
char_u *p;
if (src == NULL) {
*dst = NUL;
return;
}
/*
* If the file is a help file, remove the path completely.
*/
if (buf != NULL && buf->b_help) {
STRCPY(dst, path_tail(src));
return;
}
/*
* We check both the value of the $HOME environment variable and the
* "real" home directory.
*/
if (homedir != NULL)
dirlen = STRLEN(homedir);
homedir_env_orig = homedir_env = (char_u *)os_getenv("HOME");
/* Empty is the same as not set. */
if (homedir_env != NULL && *homedir_env == NUL)
homedir_env = NULL;
if (homedir_env != NULL && vim_strchr(homedir_env, '~') != NULL) {
int usedlen = 0;
int flen;
char_u *fbuf = NULL;
flen = (int)STRLEN(homedir_env);
(void)modify_fname((char_u *)":p", &usedlen,
&homedir_env, &fbuf, &flen);
flen = (int)STRLEN(homedir_env);
if (flen > 0 && vim_ispathsep(homedir_env[flen - 1]))
/* Remove the trailing / that is added to a directory. */
homedir_env[flen - 1] = NUL;
}
if (homedir_env != NULL)
envlen = STRLEN(homedir_env);
if (!one)
src = skipwhite(src);
while (*src && dstlen > 0) {
/*
* Here we are at the beginning of a file name.
* First, check to see if the beginning of the file name matches
* $HOME or the "real" home directory. Check that there is a '/'
* after the match (so that if e.g. the file is "/home/pieter/bla",
* and the home directory is "/home/piet", the file does not end up
* as "~er/bla" (which would seem to indicate the file "bla" in user
* er's home directory)).
*/
p = homedir;
len = dirlen;
for (;; ) {
if ( len
&& fnamencmp(src, p, len) == 0
&& (vim_ispathsep(src[len])
|| (!one && (src[len] == ',' || src[len] == ' '))
|| src[len] == NUL)) {
src += len;
if (--dstlen > 0)
*dst++ = '~';
/*
* If it's just the home directory, add "/".
*/
if (!vim_ispathsep(src[0]) && --dstlen > 0)
*dst++ = '/';
break;
}
if (p == homedir_env)
break;
p = homedir_env;
len = envlen;
}
/* if (!one) skip to separator: space or comma */
while (*src && (one || (*src != ',' && *src != ' ')) && --dstlen > 0)
*dst++ = *src++;
/* skip separator */
while ((*src == ' ' || *src == ',') && --dstlen > 0)
*dst++ = *src++;
}
/* if (dstlen == 0) out of space, what to do??? */
*dst = NUL;
if (homedir_env != homedir_env_orig)
free(homedir_env);
}
/*
* Like home_replace, store the replaced string in allocated memory.
*/
char_u *
home_replace_save (
buf_T *buf, /* when not NULL, check for help files */
char_u *src /* input file name */
) FUNC_ATTR_NONNULL_RET
{
size_t len = 3; /* space for "~/" and trailing NUL */
if (src != NULL) /* just in case */
len += STRLEN(src);
char_u *dst = xmalloc(len);
home_replace(buf, src, dst, (int)len, TRUE);
return dst;
}
/*
* Preserve files and exit.
* When called IObuff must contain a message.

View File

@ -1802,7 +1802,7 @@ void set_init_1(void)
# endif
int len;
garray_T ga;
int mustfree;
bool mustfree;
ga_init(&ga, 1, 100);
for (size_t n = 0; n < ARRAY_SIZE(names); ++n) {
@ -1859,7 +1859,7 @@ void set_init_1(void)
char_u *buf;
int i;
int j;
int mustfree = FALSE;
bool mustfree = false;
/* Initialize the 'cdpath' option's default value. */
cdpath = vim_getenv((char_u *)"CDPATH", &mustfree);
@ -7426,7 +7426,7 @@ static void paste_option_changed(void)
*/
void vimrc_found(char_u *fname, char_u *envname)
{
int dofree = FALSE;
bool dofree = false;
char_u *p;
if (fname != NULL) {

View File

@ -1,11 +1,23 @@
// env.c -- environment variable access
#include <assert.h>
#include <uv.h>
// vim.h must be included before charset.h (and possibly others) or things
// blow up
#include "nvim/vim.h"
#include "nvim/ascii.h"
#include "nvim/charset.h"
#include "nvim/os/os.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/misc2.h"
#include "nvim/path.h"
#include "nvim/strings.h"
#include "nvim/eval.h"
#include "nvim/ex_getln.h"
#include "nvim/version.h"
#ifdef HAVE__NSGETENVIRON
#include <crt_externs.h>
@ -87,3 +99,589 @@ void os_get_hostname(char *hostname, size_t len)
#endif
}
/// To get the "real" home directory:
/// - get value of $HOME
/// For Unix:
/// - go to that directory
/// - do os_dirname() to get the real name of that directory.
/// This also works with mounts and links.
/// Don't do this for MS-DOS, it will change the "current dir" for a drive.
static char_u *homedir = NULL;
void init_homedir(void)
{
char_u *var;
/* In case we are called a second time (when 'encoding' changes). */
free(homedir);
homedir = NULL;
var = (char_u *)os_getenv("HOME");
if (var != NULL && *var == NUL) /* empty is same as not set */
var = NULL;
if (var != NULL) {
#ifdef UNIX
/*
* Change to the directory and get the actual path. This resolves
* links. Don't do it when we can't return.
*/
if (os_dirname(NameBuff, MAXPATHL) == OK
&& os_chdir((char *)NameBuff) == 0) {
if (!os_chdir((char *)var) && os_dirname(IObuff, IOSIZE) == OK)
var = IObuff;
if (os_chdir((char *)NameBuff) != 0)
EMSG(_(e_prev_dir));
}
#endif
homedir = vim_strsave(var);
}
}
#if defined(EXITFREE)
void free_homedir(void)
{
free(homedir);
}
#endif
/// Call expand_env() and store the result in an allocated string.
/// This is not very memory efficient, this expects the result to be freed
/// again soon.
/// @param src String containing environment variables to expand
/// @see {expand_env}
char_u *expand_env_save(char_u *src)
{
return expand_env_save_opt(src, false);
}
/// Similar to expand_env_save() but when "one" is `true` handle the string as
/// one file name, i.e. only expand "~" at the start.
/// @param src String containing environment variables to expand
/// @param one Should treat as only one file name
/// @see {expand_env}
char_u *expand_env_save_opt(char_u *src, bool one)
{
char_u *p = xmalloc(MAXPATHL);
expand_env_esc(src, p, MAXPATHL, false, one, NULL);
return p;
}
/// Expand environment variable with path name.
/// "~/" is also expanded, using $HOME. For Unix "~user/" is expanded.
/// Skips over "\ ", "\~" and "\$" (not for Win32 though).
/// If anything fails no expansion is done and dst equals src.
/// @param src Input string e.g. "$HOME/vim.hlp"
/// @param dst Where to put the result
/// @param dstlen Maximum length of the result
void expand_env(char_u *src, char_u *dst, int dstlen)
{
expand_env_esc(src, dst, dstlen, false, false, NULL);
}
/// Expand environment variable with path name and escaping.
/// "~/" is also expanded, using $HOME. For Unix "~user/" is expanded.
/// Skips over "\ ", "\~" and "\$" (not for Win32 though).
/// If anything fails no expansion is done and dst equals src.
/// startstr recognize the start of a new name, for '~' expansion.
/// @param srcp Input string e.g. "$HOME/vim.hlp"
/// @param dst Where to put the result
/// @param dstlen Maximum length of the result
/// @param esc Should we escape spaces in expanded variables?
/// @param one Should we expand more than one '~'?
/// @param startstr Common prefix for paths, can be NULL
void expand_env_esc(char_u *srcp, char_u *dst, int dstlen, bool esc, bool one,
char_u *startstr)
{
char_u *src;
char_u *tail;
int c;
char_u *var;
bool copy_char;
bool mustfree; /* var was allocated, need to free it later */
bool at_start = true; /* at start of a name */
int startstr_len = 0;
if (startstr != NULL)
startstr_len = (int)STRLEN(startstr);
src = skipwhite(srcp);
--dstlen; /* leave one char space for "\," */
while (*src && dstlen > 0) {
copy_char = true;
if ((*src == '$') || (*src == '~' && at_start)) {
mustfree = false;
/*
* The variable name is copied into dst temporarily, because it may
* be a string in read-only memory and a NUL needs to be appended.
*/
if (*src != '~') { /* environment var */
tail = src + 1;
var = dst;
c = dstlen - 1;
#ifdef UNIX
/* Unix has ${var-name} type environment vars */
if (*tail == '{' && !vim_isIDc('{')) {
tail++; /* ignore '{' */
while (c-- > 0 && *tail && *tail != '}')
*var++ = *tail++;
} else // NOLINT
#endif
{
while (c-- > 0 && *tail != NUL && vim_isIDc(*tail)) {
*var++ = *tail++;
}
}
#if defined(UNIX)
// Verify that we have found the end of a UNIX ${VAR} style variable
if (src[1] == '{' && *tail != '}') {
var = NULL;
} else if (src[1] == '{') {
++tail;
}
#elif defined(MSWIN)
// Verify that we have found the end of a Windows %VAR% style variable
if (src[0] == '%' && *tail != '%') {
var = NULL;
} else if (src[0] == '%') {
++tail;
}
#endif
*var = NUL;
var = vim_getenv(dst, &mustfree);
} else if ( src[1] == NUL /* home directory */
|| vim_ispathsep(src[1])
|| vim_strchr((char_u *)" ,\t\n", src[1]) != NULL) {
var = homedir;
tail = src + 1;
} else { /* user directory */
#if defined(UNIX)
/*
* Copy ~user to dst[], so we can put a NUL after it.
*/
tail = src;
var = dst;
c = dstlen - 1;
while ( c-- > 0
&& *tail
&& vim_isfilec(*tail)
&& !vim_ispathsep(*tail))
*var++ = *tail++;
*var = NUL;
/*
* Use os_get_user_directory() to get the user directory.
* If this function fails, the shell is used to
* expand ~user. This is slower and may fail if the shell
* does not support ~user (old versions of /bin/sh).
*/
var = (char_u *)os_get_user_directory((char *)dst + 1);
mustfree = true;
if (var == NULL)
{
expand_T xpc;
ExpandInit(&xpc);
xpc.xp_context = EXPAND_FILES;
var = ExpandOne(&xpc, dst, NULL,
WILD_ADD_SLASH|WILD_SILENT, WILD_EXPAND_FREE);
mustfree = true;
}
#else
/* cannot expand user's home directory, so don't try */
var = NULL;
tail = (char_u *)""; /* for gcc */
#endif /* UNIX */
}
#ifdef BACKSLASH_IN_FILENAME
/* If 'shellslash' is set change backslashes to forward slashes.
* Can't use slash_adjust(), p_ssl may be set temporarily. */
if (p_ssl && var != NULL && vim_strchr(var, '\\') != NULL) {
char_u *p = vim_strsave(var);
if (mustfree) {
free(var);
}
var = p;
mustfree = true;
forward_slash(var);
}
#endif
/* If "var" contains white space, escape it with a backslash.
* Required for ":e ~/tt" when $HOME includes a space. */
if (esc && var != NULL && vim_strpbrk(var, (char_u *)" \t") != NULL) {
char_u *p = vim_strsave_escaped(var, (char_u *)" \t");
if (mustfree)
free(var);
var = p;
mustfree = true;
}
if (var != NULL && *var != NUL
&& (STRLEN(var) + STRLEN(tail) + 1 < (unsigned)dstlen)) {
STRCPY(dst, var);
dstlen -= (int)STRLEN(var);
c = (int)STRLEN(var);
/* if var[] ends in a path separator and tail[] starts
* with it, skip a character */
if (*var != NUL && after_pathsep(dst, dst + c)
#if defined(BACKSLASH_IN_FILENAME)
&& dst[-1] != ':'
#endif
&& vim_ispathsep(*tail))
++tail;
dst += c;
src = tail;
copy_char = false;
}
if (mustfree)
free(var);
}
if (copy_char) { /* copy at least one char */
/*
* Recognize the start of a new name, for '~'.
* Don't do this when "one" is true, to avoid expanding "~" in
* ":edit foo ~ foo".
*/
at_start = false;
if (src[0] == '\\' && src[1] != NUL) {
*dst++ = *src++;
--dstlen;
} else if ((src[0] == ' ' || src[0] == ',') && !one) {
at_start = true;
}
*dst++ = *src++;
--dstlen;
if (startstr != NULL && src - startstr_len >= srcp
&& STRNCMP(src - startstr_len, startstr, startstr_len) == 0)
at_start = true;
}
}
*dst = NUL;
}
/// Check if the directory "vimdir/<version>" or "vimdir/runtime" exists.
/// Return NULL if not, return its name in allocated memory otherwise.
/// @param vimdir directory to test
static char_u *vim_version_dir(char_u *vimdir)
{
char_u *p;
if (vimdir == NULL || *vimdir == NUL)
return NULL;
p = concat_fnames(vimdir, (char_u *)VIM_VERSION_NODOT, true);
if (os_isdir(p))
return p;
free(p);
p = concat_fnames(vimdir, (char_u *)RUNTIME_DIRNAME, true);
if (os_isdir(p))
return p;
free(p);
return NULL;
}
/// If the string between "p" and "pend" ends in "name/", return "pend" minus
/// the length of "name/". Otherwise return "pend".
static char_u *remove_tail(char_u *p, char_u *pend, char_u *name)
{
int len = (int)STRLEN(name) + 1;
char_u *newend = pend - len;
if (newend >= p
&& fnamencmp(newend, name, len - 1) == 0
&& (newend == p || after_pathsep(p, newend)))
return newend;
return pend;
}
/// Vim's version of getenv().
/// Special handling of $HOME, $VIM and $VIMRUNTIME, allowing the user to
/// override the vim runtime directory at runtime. Also does ACP to 'enc'
/// conversion for Win32.
/// @param name Name of environment variable to expand
/// @param[out] mustfree Ouput parameter for the caller to determine if they are
/// responsible for releasing memory. Must be initialized to false
/// by the caller.
char_u *vim_getenv(char_u *name, bool *mustfree)
{
char_u *p;
char_u *pend;
int vimruntime;
p = (char_u *)os_getenv((char *)name);
if (p != NULL && *p == NUL) /* empty is the same as not set */
p = NULL;
if (p != NULL) {
return p;
}
vimruntime = (STRCMP(name, "VIMRUNTIME") == 0);
if (!vimruntime && STRCMP(name, "VIM") != 0)
return NULL;
/*
* When expanding $VIMRUNTIME fails, try using $VIM/vim<version> or $VIM.
* Don't do this when default_vimruntime_dir is non-empty.
*/
if (vimruntime
#ifdef HAVE_PATHDEF
&& *default_vimruntime_dir == NUL
#endif
) {
p = (char_u *)os_getenv("VIM");
if (p != NULL && *p == NUL) /* empty is the same as not set */
p = NULL;
if (p != NULL) {
p = vim_version_dir(p);
if (p != NULL)
*mustfree = true;
else
p = (char_u *)os_getenv("VIM");
}
}
/*
* When expanding $VIM or $VIMRUNTIME fails, try using:
* - the directory name from 'helpfile' (unless it contains '$')
* - the executable name from argv[0]
*/
if (p == NULL) {
if (p_hf != NULL && vim_strchr(p_hf, '$') == NULL)
p = p_hf;
if (p != NULL) {
/* remove the file name */
pend = path_tail(p);
/* remove "doc/" from 'helpfile', if present */
if (p == p_hf)
pend = remove_tail(p, pend, (char_u *)"doc");
/* for $VIM, remove "runtime/" or "vim54/", if present */
if (!vimruntime) {
pend = remove_tail(p, pend, (char_u *)RUNTIME_DIRNAME);
pend = remove_tail(p, pend, (char_u *)VIM_VERSION_NODOT);
}
/* remove trailing path separator */
if (pend > p && after_pathsep(p, pend))
--pend;
// check that the result is a directory name
assert(pend >= p);
p = vim_strnsave(p, (size_t)(pend - p));
if (!os_isdir(p)) {
free(p);
p = NULL;
} else {
*mustfree = true;
}
}
}
#ifdef HAVE_PATHDEF
/* When there is a pathdef.c file we can use default_vim_dir and
* default_vimruntime_dir */
if (p == NULL) {
/* Only use default_vimruntime_dir when it is not empty */
if (vimruntime && *default_vimruntime_dir != NUL) {
p = default_vimruntime_dir;
*mustfree = false;
} else if (*default_vim_dir != NUL) {
if (vimruntime && (p = vim_version_dir(default_vim_dir)) != NULL) {
*mustfree = true;
} else {
p = default_vim_dir;
*mustfree = false;
}
}
}
#endif
/*
* Set the environment variable, so that the new value can be found fast
* next time, and others can also use it (e.g. Perl).
*/
if (p != NULL) {
if (vimruntime) {
vim_setenv((char_u *)"VIMRUNTIME", p);
didset_vimruntime = true;
} else {
vim_setenv((char_u *)"VIM", p);
didset_vim = true;
}
}
return p;
}
/// Replace home directory by "~" in each space or comma separated file name in
/// 'src'.
/// If anything fails (except when out of space) dst equals src.
/// @param buf When not NULL, check for help files
/// @param src Input file name
/// @param dst Where to put the result
/// @param dstlen Maximum length of the result
/// @param one If true, only replace one file name, including spaces and commas
/// in the file name
void home_replace(buf_T *buf, char_u *src, char_u *dst, int dstlen, bool one)
{
size_t dirlen = 0, envlen = 0;
size_t len;
char_u *homedir_env, *homedir_env_orig;
char_u *p;
if (src == NULL) {
*dst = NUL;
return;
}
/*
* If the file is a help file, remove the path completely.
*/
if (buf != NULL && buf->b_help) {
STRCPY(dst, path_tail(src));
return;
}
/*
* We check both the value of the $HOME environment variable and the
* "real" home directory.
*/
if (homedir != NULL)
dirlen = STRLEN(homedir);
homedir_env_orig = homedir_env = (char_u *)os_getenv("HOME");
/* Empty is the same as not set. */
if (homedir_env != NULL && *homedir_env == NUL)
homedir_env = NULL;
if (homedir_env != NULL && vim_strchr(homedir_env, '~') != NULL) {
int usedlen = 0;
int flen;
char_u *fbuf = NULL;
flen = (int)STRLEN(homedir_env);
(void)modify_fname((char_u *)":p", &usedlen,
&homedir_env, &fbuf, &flen);
flen = (int)STRLEN(homedir_env);
if (flen > 0 && vim_ispathsep(homedir_env[flen - 1]))
/* Remove the trailing / that is added to a directory. */
homedir_env[flen - 1] = NUL;
}
if (homedir_env != NULL)
envlen = STRLEN(homedir_env);
if (!one)
src = skipwhite(src);
while (*src && dstlen > 0) {
/*
* Here we are at the beginning of a file name.
* First, check to see if the beginning of the file name matches
* $HOME or the "real" home directory. Check that there is a '/'
* after the match (so that if e.g. the file is "/home/pieter/bla",
* and the home directory is "/home/piet", the file does not end up
* as "~er/bla" (which would seem to indicate the file "bla" in user
* er's home directory)).
*/
p = homedir;
len = dirlen;
for (;; ) {
if ( len
&& fnamencmp(src, p, len) == 0
&& (vim_ispathsep(src[len])
|| (!one && (src[len] == ',' || src[len] == ' '))
|| src[len] == NUL)) {
src += len;
if (--dstlen > 0)
*dst++ = '~';
/*
* If it's just the home directory, add "/".
*/
if (!vim_ispathsep(src[0]) && --dstlen > 0)
*dst++ = '/';
break;
}
if (p == homedir_env)
break;
p = homedir_env;
len = envlen;
}
/* if (!one) skip to separator: space or comma */
while (*src && (one || (*src != ',' && *src != ' ')) && --dstlen > 0)
*dst++ = *src++;
/* skip separator */
while ((*src == ' ' || *src == ',') && --dstlen > 0)
*dst++ = *src++;
}
/* if (dstlen == 0) out of space, what to do??? */
*dst = NUL;
if (homedir_env != homedir_env_orig)
free(homedir_env);
}
/// Like home_replace, store the replaced string in allocated memory.
/// @param buf When not NULL, check for help files
/// @param src Input file name
char_u * home_replace_save(buf_T *buf, char_u *src) FUNC_ATTR_NONNULL_RET
{
size_t len = 3; /* space for "~/" and trailing NUL */
if (src != NULL) /* just in case */
len += STRLEN(src);
char_u *dst = xmalloc(len);
home_replace(buf, src, dst, (int)len, true);
return dst;
}
/// Our portable version of setenv.
/// Has special handling for $VIMRUNTIME to keep the localization machinery
/// sane.
void vim_setenv(char_u *name, char_u *val)
{
os_setenv((char *)name, (char *)val, 1);
/*
* When setting $VIMRUNTIME adjust the directory to find message
* translations to $VIMRUNTIME/lang.
*/
if (*val != NUL && STRICMP(name, "VIMRUNTIME") == 0) {
char_u *buf = concat_str(val, (char_u *)"/lang");
bindtextdomain(VIMPACKAGE, (char *)buf);
free(buf);
}
}
/// Function given to ExpandGeneric() to obtain an environment variable name.
char_u *get_env_name(expand_T *xp, int idx)
{
# define ENVNAMELEN 100
// this static buffer is needed to avoid a memory leak in ExpandGeneric
static char_u name[ENVNAMELEN];
assert(idx >= 0);
char *envname = os_getenvname_at_index((size_t)idx);
if (envname) {
STRLCPY(name, envname, ENVNAMELEN);
free(envname);
return name;
} else {
return NULL;
}
}