fix(path): normalize path slashes on Windows #37729

Problem:
On Windows, path separators may become inconsistent for various reasons,
which makes normalization quite painful.

Solution:
Normalize paths to `/` at the entry boundaries and always use it
internally, converting back only in rare cases where `\` is really
needed (e.g. cmd.exe/bat scripts?).

This is the first commit in a series of incremental steps.

Note:
* some funcs won't respect shellslash. e.g. `expand/fnamemodify`
* some funcs still respect shellslash, but will be updated in a follow
  PR. e.g. `ex_pwd/f_chdir/f_getcwd`
* uv's built-in funcs always return `\`. e.g. `uv.cwd/uv.exepath`

Co-authored-by: Justin M. Keyes <justinkz@gmail.com>
This commit is contained in:
taoandJustin M. Keyes authored and GitHub committed 2026-04-24 13:20:25 -04:00
1 parent 27191e0f4f
commit f130922744
41 files changed
+203 -216

No files matched your search

-3
View File
@@ -216,9 +216,6 @@ void alist_add(alist_T *al, char *fname, int set_fnum)
arglist_locked = true;
wp->w_locked++;
#ifdef BACKSLASH_IN_FILENAME
slash_adjust(fname);
#endif
AARGLIST(al)[al->al_ga.ga_len].ae_fname = fname;
if (set_fnum > 0) {
AARGLIST(al)[al->al_ga.ga_len].ae_fnum =
+2 -7
View File
@@ -75,13 +75,8 @@
#define Ctrl__ 31
// Character that separates dir names in a path.
#ifdef BACKSLASH_IN_FILENAME
# define PATHSEP psepc
# define PATHSEPSTR pseps
#else
# define PATHSEP '/'
# define PATHSEPSTR "/"
#endif
#define PATHSEP '/'
#define PATHSEPSTR "/"
/// Checks if `c` is a space or tab character.
///
+4 -6
View File
@@ -316,6 +316,7 @@ int nextwild(expand_T *xp, int type, int options, bool escape)
}
// Translate string into pattern and expand it.
const int use_options = (options
| WILD_USE_COMPLETESLASH
| WILD_HOME_REPLACE
| WILD_ADD_SLASH
| WILD_SILENT
@@ -2725,16 +2726,13 @@ static int expand_files_and_dirs(expand_T *xp, char *pat, char ***matches, int *
xfree(pat);
}
#ifdef BACKSLASH_IN_FILENAME
if (p_csl[0] != NUL && (options & WILD_IGNORE_COMPLETESLASH) == 0) {
if ((options & WILD_USE_COMPLETESLASH) && ((p_csl[0] == NUL && !p_ssl) || p_csl[0] == 'b')) {
for (int j = 0; j < *numMatches; j++) {
char *ptr = (*matches)[j];
while (*ptr != NUL) {
if (p_csl[0] == 's' && *ptr == '\\') {
*ptr = '/';
} else if (p_csl[0] == 'b' && *ptr == '/') {
for (; *ptr; ptr++) {
if (*ptr == '/') {
*ptr = '\\';
}
ptr += utfc_ptr2len(ptr);
}
}
}
+1 -1
View File
@@ -36,7 +36,7 @@ enum {
WILD_ESCAPE = 0x80,
WILD_ICASE = 0x100,
WILD_ALLLINKS = 0x200,
WILD_IGNORE_COMPLETESLASH = 0x400,
WILD_USE_COMPLETESLASH = 0x400,
WILD_NOERROR = 0x800, ///< sets EW_NOERROR
WILD_BUFLASTUSED = 0x1000,
BUF_DIFF_FILTER = 0x2000,
+1 -7
View File
@@ -462,12 +462,6 @@ void f_exepath(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
os_can_exe(tv_get_string(&argvars[0]), &path, true);
#ifdef BACKSLASH_IN_FILENAME
if (path != NULL) {
slash_adjust(path);
}
#endif
rettv->v_type = VAR_STRING;
rettv->vval.v_string = path;
}
@@ -878,7 +872,7 @@ void f_glob(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
/// "globpath()" function
void f_globpath(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
int flags = WILD_IGNORE_COMPLETESLASH; // Flags for globpath.
int flags = 0; // Flags for globpath.
bool error = false;
// Return a string, or a list if the optional third argument is non-zero.
+3
View File
@@ -11,6 +11,7 @@
#include "nvim/log.h"
#include "nvim/os/os.h"
#include "nvim/os/os_defs.h"
#include "nvim/path.h"
#include "nvim/types_defs.h"
#include "nvim/ui_client.h"
@@ -29,6 +30,8 @@ int libuv_proc_spawn(LibuvProc *uvproc)
// expects a different syntax (must be prepared by the caller before now).
if (os_shell_is_cmdexe(proc->argv[0])) {
uvproc->uvopts.flags |= UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS;
// cmd.exe compatibility: backslashes required for path.
TO_BACKSLASH(proc->argv[0]);
}
if (proc->detach) {
uvproc->uvopts.flags |= UV_PROCESS_DETACHED;
+4 -2
View File
@@ -4145,6 +4145,8 @@ int expand_filename(exarg_T *eap, char **cmdlinep, const char **errormsgp)
}
repl_cmdline(eap, eap->arg, strlen(eap->arg), p, cmdlinep);
xfree(p);
} else {
TO_SLASH(eap->arg);
}
}
return OK;
@@ -5673,7 +5675,7 @@ int expand_findfunc(char *pat, char ***files, int *numMatches)
int idx = 0;
TV_LIST_ITER_CONST(l, li, {
if (TV_LIST_ITEM_TV(li)->v_type == VAR_STRING) {
(*files)[idx] = xstrdup(TV_LIST_ITEM_TV(li)->vval.v_string);
(*files)[idx] = TO_SLASH_SAVE(TV_LIST_ITEM_TV(li)->vval.v_string);
idx++;
}
});
@@ -5704,7 +5706,7 @@ static char *findfunc_find_file(char *findarg, size_t findarg_len, int count)
} else {
listitem_T *li = tv_list_find(fname_list, count - 1);
if (li != NULL && TV_LIST_ITEM_TV(li)->v_type == VAR_STRING) {
ret_fname = xstrdup(TV_LIST_ITEM_TV(li)->vval.v_string);
ret_fname = TO_SLASH_SAVE(TV_LIST_ITEM_TV(li)->vval.v_string);
}
}
}
+6 -2
View File
@@ -1593,6 +1593,7 @@ theend:
char *grab_file_name(int count, linenr_T *file_lnum)
{
int options = FNAME_MESS | FNAME_EXP | FNAME_REL | FNAME_UNESC;
char *fname;
if (VIsual_active) {
size_t len;
char *ptr;
@@ -1605,9 +1606,12 @@ char *grab_file_name(int count, linenr_T *file_lnum)
*file_lnum = getdigits_int32(&p, false, 0);
}
return find_file_name_in_path(ptr, len, options, count, curbuf->b_ffname);
fname = find_file_name_in_path(ptr, len, options, count, curbuf->b_ffname);
} else {
fname = file_name_at_cursor(options | FNAME_HYP, count, file_lnum);
}
return file_name_at_cursor(options | FNAME_HYP, count, file_lnum);
TO_SLASH(fname);
return fname;
}
/// Return the file name under or after the cursor.
+5 -30
View File
@@ -4038,30 +4038,9 @@ static void get_next_filename_completion(void)
int max_score = 0;
Direction dir = compl_direction;
#ifdef BACKSLASH_IN_FILENAME
char pathsep = (curbuf->b_p_csl[0] == 's')
? '/' : (curbuf->b_p_csl[0] == 'b') ? '\\' : PATHSEP;
#else
char pathsep = PATHSEP;
#endif
if (in_fuzzy_collect) {
#ifdef BACKSLASH_IN_FILENAME
if (curbuf->b_p_csl[0] == 's') {
for (size_t i = 0; i < leader_len; i++) {
if (leader[i] == '\\') {
leader[i] = '/';
}
}
} else if (curbuf->b_p_csl[0] == 'b') {
for (size_t i = 0; i < leader_len; i++) {
if (leader[i] == '/') {
leader[i] = '\\';
}
}
}
#endif
char *last_sep = strrchr(leader, pathsep);
TO_SLASH(leader);
char *last_sep = strrchr(leader, PATHSEP);
if (last_sep == NULL) {
// No path separator or separator is the last character,
// fuzzy match the whole leader
@@ -4093,16 +4072,12 @@ static void get_next_filename_completion(void)
// May change home directory back to "~".
tilde_replace(compl_pattern.data, num_matches, matches);
#ifdef BACKSLASH_IN_FILENAME
if (curbuf->b_p_csl[0] != NUL) {
if ((curbuf->b_p_csl[0] == NUL && !p_ssl) || curbuf->b_p_csl[0] == 'b') {
for (int i = 0; i < num_matches; i++) {
char *ptr = matches[i];
while (*ptr != NUL) {
if (curbuf->b_p_csl[0] == 's' && *ptr == '\\') {
*ptr = '/';
} else if (curbuf->b_p_csl[0] == 'b' && *ptr == '/') {
for (char *ptr = matches[i]; *ptr; ptr++) {
if (*ptr == '/') {
*ptr = '\\';
}
ptr += utfc_ptr2len(ptr);
}
}
}
+3
View File
@@ -256,6 +256,7 @@ int main(int argc, char **argv)
#endif
{
argv0 = argv[0];
TO_SLASH(argv0);
if (!appname_is_valid()) {
fprintf(stderr, "$NVIM_APPNAME must be a name or relative path.\n");
@@ -1461,6 +1462,7 @@ scripterror:
break;
case 'u': // "-u {vimrc}" vim inits file
parmp->use_vimrc = argv[0];
TO_SLASH(argv[0]);
break;
case 'U': // "-U {gvimrc}" gvim inits file
break;
@@ -1505,6 +1507,7 @@ scripterror:
xfree(p);
p = tilde_expanded;
}
TO_SLASH(p);
#endif
if (parmp->diff_mode && os_isdir(p) && GARGCOUNT > 0
+2
View File
@@ -913,6 +913,7 @@ void ml_recover(bool checkext)
// If .swp file name given directly, use name from swapfile for buffer.
if (directly) {
TO_SLASH(b0p->b0_fname);
expand_env(b0p->b0_fname, NameBuff, MAXPATHL);
if (setfname(curbuf, NameBuff, NULL, true) == FAIL) {
goto theend;
@@ -3510,6 +3511,7 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_
fd = os_open(fname, O_RDONLY, 0);
if (fd >= 0) {
if (read_eintr(fd, &b0, sizeof(b0)) == sizeof(b0)) {
TO_SLASH(b0.b0_fname);
proc_running = swapfile_proc_running(&b0, fname);
// If the swapfile has the same directory as the
+30 -4
View File
@@ -365,7 +365,7 @@ void set_init_1(bool clean_arg)
memmove(backupdir + 2, backupdir, backupdir_len + 1);
memmove(backupdir, ".,", 2);
set_string_default(kOptBackupdir, backupdir, true);
set_string_default(kOptViewdir, stdpaths_user_state_subpath("view", 2, true),
set_string_default(kOptViewdir, stdpaths_user_state_subpath("view", 2, false),
true);
set_string_default(kOptDirectory, stdpaths_user_state_subpath("swap", 2, true),
true);
@@ -2650,6 +2650,8 @@ static const char *did_set_scrollbind(optset_T *args)
#ifdef BACKSLASH_IN_FILENAME
/// Process the updated 'shellslash' option value.
/// TODO(ntdiary): Remove this once we're confident that the `shellslash`
/// option is no longer needed.
static const char *did_set_shellslash(optset_T *args FUNC_ATTR_UNUSED)
{
if (p_ssl) {
@@ -2662,10 +2664,11 @@ static const char *did_set_shellslash(optset_T *args FUNC_ATTR_UNUSED)
pseps[0] = '\\';
}
// TODO(ntdiary): Remove these in the follow PR.
// need to adjust the file name arguments and buffer names.
buflist_slash_adjust();
alist_slash_adjust();
scriptnames_slash_adjust();
// buflist_slash_adjust();
// alist_slash_adjust();
// scriptnames_slash_adjust();
return NULL;
}
#endif
@@ -3903,6 +3906,29 @@ static const char *set_option(const OptIndex opt_idx, OptVal value, int opt_flag
}
}
#ifdef BACKSLASH_IN_FILENAME
// Ensure "/" slashes in various options.
uint32_t flags = options[opt_idx].flags;
if ((flags & kOptFlagExpand)
&& opt_idx != kOptEqualprg
&& opt_idx != kOptFormatprg
&& opt_idx != kOptGrepprg
&& opt_idx != kOptKeywordprg
&& opt_idx != kOptMakeprg
&& opt_idx != kOptShell) {
bool allow_comma = flags & kOptFlagComma;
bool allow_space = (opt_idx == kOptCdpath || opt_idx == kOptPath || opt_idx == kOptTags);
for (char *p = value.data.string.data; *p; p++) {
if (*p != '\\'
|| (p[1] == ',' && allow_comma)
|| (p[1] == ' ' && allow_space)) {
continue;
}
*p = '/';
}
}
#endif
vimoption_T *opt = &options[opt_idx];
const bool scope_local = opt_flags & OPT_LOCAL;
const bool scope_global = opt_flags & OPT_GLOBAL;
+2 -15
View File
@@ -618,6 +618,7 @@ size_t expand_env_esc(const char *restrict srcp, char *restrict dst, int dstlen,
#endif
*var = NUL;
var = vim_getenv(dst);
TO_SLASH(var);
mustfree = true;
#ifdef UNIX
}
@@ -661,21 +662,6 @@ size_t expand_env_esc(const char *restrict srcp, char *restrict dst, int dstlen,
#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 *p = xstrdup(var);
if (mustfree) {
xfree(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 && strpbrk(var, " \t") != NULL) {
@@ -1034,6 +1020,7 @@ size_t home_replace(const buf_T *const buf, const char *src, char *const dst, si
homedir_env = os_getenv("USERPROFILE");
}
#endif
TO_SLASH(homedir_env);
char *homedir_env_mod = homedir_env;
bool must_free = false;
+7 -1
View File
@@ -113,6 +113,7 @@ int os_dirname(char *buf, size_t len)
xstrlcpy(buf, uv_strerror(error_number), len);
return FAIL;
}
TO_SLASH(buf);
return OK;
}
@@ -224,7 +225,9 @@ int os_nodetype(const char *name)
int os_exepath(char *buffer, size_t *size)
FUNC_ATTR_NONNULL_ALL
{
return uv_exepath(buffer, size);
int r = uv_exepath(buffer, size);
TO_SLASH(buffer);
return r;
}
/// Checks if the file `name` is executable.
@@ -1112,6 +1115,7 @@ int os_mkdtemp(const char *templ, char *path)
int result = uv_fs_mkdtemp(NULL, &request, templ, NULL);
if (result == kLibuvSuccess) {
xstrlcpy(path, request.path, TEMP_FILE_PATH_MAXLEN);
TO_SLASH(path);
}
uv_fs_req_cleanup(&request);
return result;
@@ -1345,6 +1349,7 @@ char *os_realpath(const char *name, char *buf, size_t len)
buf = xmalloc(len);
}
xstrlcpy(buf, request.ptr, len);
TO_SLASH(buf);
}
uv_fs_req_cleanup(&request);
return result == kLibuvSuccess ? buf : NULL;
@@ -1430,6 +1435,7 @@ shortcut_end:
}
CoUninitialize();
TO_SLASH(rfname);
return rfname;
}
+4
View File
@@ -11,6 +11,7 @@
#include "nvim/os/os.h"
#include "nvim/os/pty_conpty_win.h"
#include "nvim/os/pty_proc_win.h"
#include "nvim/path.h"
#include "os/pty_proc_win.c.generated.h"
@@ -261,6 +262,9 @@ static int build_cmd_line(char **argv, wchar_t **cmd_line, bool is_cmdexe)
arg_node->arg = xmalloc(buf_len);
if (is_cmdexe) {
xstrlcpy(arg_node->arg, *argv, buf_len);
if (argc == 0) {
TO_BACKSLASH(arg_node->arg);
}
} else {
quote_cmd_arg(arg_node->arg, buf_len, *argv);
}
+17 -19
View File
@@ -43,10 +43,10 @@ static const char *const xdg_defaults_env_vars[] = {
/// Used in case environment variables contain nothing. Need to be expanded.
static const char *const xdg_defaults[] = {
#ifdef MSWIN
[kXDGConfigHome] = "~\\AppData\\Local",
[kXDGDataHome] = "~\\AppData\\Local",
[kXDGCacheHome] = "~\\AppData\\Local\\Temp",
[kXDGStateHome] = "~\\AppData\\Local",
[kXDGConfigHome] = "~/AppData/Local",
[kXDGDataHome] = "~/AppData/Local",
[kXDGCacheHome] = "~/AppData/Local/Temp",
[kXDGStateHome] = "~/AppData/Local",
[kXDGRuntimeDir] = NULL, // Decided by vim_mktempdir().
[kXDGConfigDirs] = NULL,
[kXDGDataDirs] = NULL,
@@ -166,6 +166,7 @@ char *stdpaths_get_xdg_var(const XDGVarType idx)
env_val = xstrdup("");
}
#endif
TO_SLASH(env_val);
char *ret = NULL;
if (env_val != NULL) {
@@ -213,10 +214,6 @@ char *get_xdg_home(const XDGVarType idx)
}
#endif
dir = concat_fnames_realloc(dir, IObuff, true);
#ifdef BACKSLASH_IN_FILENAME
slash_adjust(dir);
#endif
}
return dir;
}
@@ -266,21 +263,22 @@ char *stdpaths_user_state_subpath(const char *fname, const size_t trailing_paths
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
{
char *ret = concat_fnames_realloc(get_xdg_home(kXDGStateHome), fname, true);
const size_t len = strlen(ret);
size_t len = strlen(ret);
const size_t numcommas = (escape_commas ? memcnt(ret, ',', len) : 0);
if (numcommas || trailing_pathseps) {
ret = xrealloc(ret, len + trailing_pathseps + numcommas + 1);
for (size_t i = 0; i < len + numcommas; i++) {
if (ret[i] == ',') {
memmove(ret + i + 1, ret + i, len - i + numcommas);
ret[i] = '\\';
i++;
size_t newlen = len + numcommas + trailing_pathseps;
ret = xrealloc(ret, newlen + 1);
ret[newlen] = NUL;
memset(ret + len + numcommas, PATHSEP, trailing_pathseps);
newlen -= trailing_pathseps;
while (numcommas && len > 0) {
ret[--newlen] = ret[--len];
if (ret[newlen] == ',') {
ret[--newlen] = '\\';
}
}
if (trailing_pathseps) {
memset(ret + len + numcommas, PATHSEP, trailing_pathseps);
}
ret[len + trailing_pathseps + numcommas] = NUL;
}
return ret;
}
+31 -12
View File
@@ -537,7 +537,7 @@ char *FullName_save(const char *fname, bool force)
char *buf = xmalloc(MAXPATHL);
if (vim_FullName(fname, buf, MAXPATHL, force) == FAIL) {
xfree(buf);
return xstrdup(fname);
return TO_SLASH_SAVE(fname);
}
return buf;
}
@@ -551,7 +551,7 @@ char *save_abs_path(const char *name)
if (!path_is_absolute(name)) {
return FullName_save(name, true);
}
return xstrdup(name);
return TO_SLASH_SAVE(name);
}
/// Checks if a path has a wildcard character including '~', unless at the end.
@@ -1516,6 +1516,30 @@ void slash_adjust(char *p)
}
#endif
/// Convert all slashes to backslashes in-place.
char *path_to_backslash(char *p)
{
if (p != NULL) {
strchrsub(p, PATHSEP, '\\');
}
return p;
}
/// Convert all backslashes to forward slashes in-place.
char *path_to_slash(char *p)
{
if (p != NULL) {
strchrsub(p, '\\', PATHSEP);
}
return p;
}
/// Get an allocated copy of path to convert backslashes.
char *path_to_slash_save(const char *p)
{
return p == NULL ? NULL : path_to_slash(xstrdup(p));
}
/// Add a file to a file list. Accepted flags:
/// EW_DIR add directories
/// EW_FILE add files
@@ -1561,9 +1585,7 @@ void addfile(garray_T *gap, char *f, int flags)
char *p = xmalloc(strlen(f) + 1 + isdir);
STRCPY(p, f);
#ifdef BACKSLASH_IN_FILENAME
slash_adjust(p);
#endif
TO_SLASH(p);
// Append a slash or backslash after directory names if none is present.
if (isdir && (flags & EW_ADDSLASH)) {
add_pathsep(p);
@@ -1849,9 +1871,7 @@ int vim_FullName(const char *fname, char *buf, size_t len, bool force)
if (strlen(fname) > (len - 1)) {
xstrlcpy(buf, fname, len); // truncate
#ifdef MSWIN
slash_adjust(buf);
#endif
TO_SLASH(buf);
return FAIL;
}
@@ -1864,9 +1884,7 @@ int vim_FullName(const char *fname, char *buf, size_t len, bool force)
if (rv == FAIL) {
xstrlcpy(buf, fname, len); // something failed; use the filename
}
#ifdef MSWIN
slash_adjust(buf);
#endif
TO_SLASH(buf);
return rv;
}
@@ -1899,7 +1917,7 @@ char *fix_fname(const char *fname)
return FullName_save(fname, false);
}
fname = xstrdup(fname);
fname = TO_SLASH_SAVE(fname);
# ifdef CASE_INSENSITIVE_FILENAME
path_fix_case((char *)fname); // set correct case for file name
@@ -2476,6 +2494,7 @@ void path_guess_exepath(const char *argv0, char *buf, size_t bufsize)
xstrlcat(NameBuff, argv0, sizeof(NameBuff));
if (os_can_exe(NameBuff, NULL, false)) {
xstrlcpy(buf, NameBuff, bufsize);
TO_SLASH(buf);
return;
}
} while (iter != NULL);
+10
View File
@@ -41,4 +41,14 @@ typedef enum file_comparison {
kEqualFileNames = 7, ///< Both don't exist and file names are same.
} FileComparison;
#ifdef BACKSLASH_IN_FILENAME
# define TO_SLASH(p) path_to_slash(p)
# define TO_SLASH_SAVE(p) path_to_slash_save(p)
# define TO_BACKSLASH(p) path_to_backslash(p)
#else
# define TO_SLASH(...)
# define TO_SLASH_SAVE(p) xstrdup(p)
# define TO_BACKSLASH(...)
#endif
#include "path.h.generated.h"
+1 -6
View File
@@ -2314,12 +2314,6 @@ static int qf_get_fnum(qf_list_T *qfl, char *directory, char *fname)
return 0;
}
#ifdef BACKSLASH_IN_FILENAME
if (directory != NULL) {
slash_adjust(directory);
}
slash_adjust(fname);
#endif
String fname_str = cstr_as_string(fname);
if (directory != NULL && !vim_isAbsName(fname)) {
ptr = concat_fnames(cstr_as_string(directory), fname_str, true);
@@ -2341,6 +2335,7 @@ static int qf_get_fnum(qf_list_T *qfl, char *directory, char *fname)
} else {
bufname = fname_str;
}
TO_SLASH(bufname.data);
if (qf_last_bufname != NULL && strcmp(bufname.data, qf_last_bufname) == 0
&& bufref_valid(&qf_last_bufref)) {
+4 -1
View File
@@ -710,6 +710,7 @@ static ArrayOf(String) runtime_get_named_common(bool lua, Array pat, bool all,
item->path, pat_item.data.string.data);
if (size < buf_len) {
if (os_file_is_readable(buf)) {
TO_SLASH(buf);
ADD_C(rv, CSTR_TO_ARENA_OBJ(arena, buf));
if (!all) {
goto done;
@@ -1843,10 +1844,11 @@ char *runtimepath_default(bool clean_arg)
char *const config_home = clean_arg
? NULL
: stdpaths_get_xdg_var(kXDGConfigHome);
char *const vimruntime = vim_getenv("VIMRUNTIME");
char *const libdir = get_lib_dir();
char *const data_dirs = stdpaths_get_xdg_var(kXDGDataDirs);
char *const config_dirs = stdpaths_get_xdg_var(kXDGConfigDirs);
char *vimruntime = vim_getenv("VIMRUNTIME");
TO_SLASH(vimruntime);
#define SITE_SIZE (sizeof("site") - 1)
#define AFTER_SIZE (sizeof("after") - 1)
size_t data_len = 0;
@@ -2537,6 +2539,7 @@ void ex_scriptnames(exarg_T *eap)
} else {
expand_env(eap->arg, NameBuff, MAXPATHL);
eap->arg = NameBuff;
TO_SLASH(eap->arg);
}
do_exedit(eap, NULL);
}
+2
View File
@@ -3272,6 +3272,7 @@ shada_read_next_item_start:
}
if (HAS_KEY(&it, _shada_mark, f)) {
entry->data.filemark.fname = xmemdupz(it.f.data, it.f.size);
TO_SLASH(entry->data.filemark.fname);
}
if (entry->data.filemark.fname == NULL) {
@@ -3458,6 +3459,7 @@ shada_read_next_item_start:
}
if (HAS_KEY(&it, _shada_buflist_item, f)) {
e->fname = xmemdupz(it.f.data, it.f.size);
TO_SLASH(e->fname);
}
if (e->pos.lnum <= 0) {
+2
View File
@@ -3985,6 +3985,8 @@ static void syn_cmd_include(exarg_T *eap, int syncing)
}
return;
}
} else {
TO_SLASH(eap->arg);
}
// Save and restore the existing top-level grouplist id and ":syn
+4 -12
View File
@@ -2017,11 +2017,6 @@ static void findtags_add_match(findtags_state_T *st, tagptrs_T *tagpp, findtags_
char *p = mfp;
p[0] = (char)(mtt + 1);
STRCPY(p + 1, st->tag_fname);
#ifdef BACKSLASH_IN_FILENAME
// Ignore differences in slashes, avoid adding
// both path/file and path\file.
slash_adjust(p + 1);
#endif
p[tag_fname_len + 1] = TAG_SEP;
char *s = p + 1 + tag_fname_len + 1;
STRCPY(s, st->lbuf);
@@ -2438,9 +2433,6 @@ static bool found_tagfile_cb(int num_fnames, char **fnames, bool all, void *cook
for (int i = 0; i < num_fnames; i++) {
char *const tag_fname = xstrdup(fnames[i]);
#ifdef BACKSLASH_IN_FILENAME
slash_adjust(tag_fname);
#endif
simplify_filename(tag_fname);
GA_APPEND(char *, &tag_fnames, tag_fname);
@@ -2502,9 +2494,6 @@ int get_tagfname(tagname_T *tnp, int first, char *buf)
tnp->tn_hf_idx++;
xstrlcpy(buf, p_hf, MAXPATHL - STRLEN_LITERAL("tags"));
STRCPY(path_tail(buf), "tags");
#ifdef BACKSLASH_IN_FILENAME
slash_adjust(buf);
#endif
simplify_filename(buf);
for (int i = 0; i < tag_fnames.ga_len; i++) {
@@ -3075,6 +3064,8 @@ static char *expand_tag_fname(char *fname, char *const tag_fname, const bool exp
char *expanded_fname = NULL;
expand_T xpc;
fname = TO_SLASH_SAVE(fname);
// Expand file name (for environment variables) when needed.
// Disallow backticks, they could execute arbitrary shell
// commands. This is not needed for tag filenames.
@@ -3084,6 +3075,7 @@ static char *expand_tag_fname(char *fname, char *const tag_fname, const bool exp
expanded_fname = ExpandOne(&xpc, fname, NULL,
WILD_LIST_NOTFOUND|WILD_SILENT, WILD_EXPAND_FREE);
if (expanded_fname != NULL) {
xfree(fname);
fname = expanded_fname;
}
}
@@ -3101,7 +3093,7 @@ static char *expand_tag_fname(char *fname, char *const tag_fname, const bool exp
retval = xstrdup(fname);
}
xfree(expanded_fname);
xfree(fname);
return retval;
}
+1 -1
View File
@@ -5395,7 +5395,7 @@ describe('API', function()
end, { nargs = 1 })
]])
eq(
uv.cwd(),
t.fix_slashes(assert(uv.cwd())),
api.nvim_cmd(
{ cmd = 'Foo', args = { '%:p:h' }, magic = { file = true } },
{ output = true }
+1 -2
View File
@@ -14,8 +14,7 @@ local rmdir = n.rmdir
local write_file = t.write_file
local function join_path(...)
local pathsep = (is_os('win') and '\\' or '/')
return table.concat({ ... }, pathsep)
return table.concat({ ... }, '/')
end
describe('path collapse', function()
+1 -1
View File
@@ -1628,7 +1628,7 @@ end)
describe('runtime:', function()
local xhome = 'Xhome'
local pathsep = n.get_pathsep()
local pathsep = '/'
local xconfig = xhome .. pathsep .. 'Xconfig'
local xdata = xhome .. pathsep .. 'Xdata'
local xenv = { XDG_CONFIG_HOME = xconfig, XDG_DATA_HOME = xdata }
+3 -3
View File
@@ -145,7 +145,7 @@ describe(':mksession', function()
it('restores buffers with tab-local CWD', function()
local tmpfile_base = file_prefix .. '-tmpfile'
local cwd_dir = fn.getcwd()
local cwd_dir = t.fix_slashes(fn.getcwd())
local session_path = cwd_dir .. get_pathsep() .. session_file
command('edit ' .. tmpfile_base .. '1')
@@ -161,9 +161,9 @@ describe(':mksession', function()
-- Use :silent to avoid hit-enter prompt due to long path
command('silent source ' .. session_path)
command('tabnext 1')
eq(cwd_dir .. get_pathsep() .. tmpfile_base .. '1', fn.expand('%:p'))
eq(cwd_dir .. '/' .. tmpfile_base .. '1', fn.expand('%:p'))
command('tabnext 2')
eq(cwd_dir .. get_pathsep() .. tmpfile_base .. '2', fn.expand('%:p'))
eq(cwd_dir .. '/' .. tmpfile_base .. '2', fn.expand('%:p'))
end)
it('restores CWD for :terminal buffers #11288', function()
+7 -8
View File
@@ -47,7 +47,7 @@ describe(':source', function()
os.remove(test_file)
end)
it("changing 'shellslash' changes the result of expand()", function()
it("changing 'shellslash' doesn't affect the result of expand()", function()
t.skip(not is_os('win'), "N/A: 'shellslash' only works on Windows")
api.nvim_set_option_value('shellslash', false, {})
@@ -66,9 +66,9 @@ describe(':source', function()
for _ = 1, 2 do
command([[source Xshellslash/Xstack.vim]])
matches([[Xshellslash\Xstack%.vim]], api.nvim_get_var('stack1'))
matches([[Xshellslash/Xstack%.vim]], api.nvim_get_var('stack1'))
matches([[Xshellslash/Xstack%.vim]], api.nvim_get_var('stack2'))
matches([[Xshellslash\Xstack%.vim]], api.nvim_get_var('stack3'))
matches([[Xshellslash/Xstack%.vim]], api.nvim_get_var('stack3'))
end
write_file(
@@ -84,9 +84,9 @@ describe(':source', function()
for _ = 1, 2 do
command([[source Xshellslash/Xstack.lua]])
matches([[Xshellslash\Xstack%.lua]], api.nvim_get_var('stack1'))
matches([[Xshellslash/Xstack%.lua]], api.nvim_get_var('stack1'))
matches([[Xshellslash/Xstack%.lua]], api.nvim_get_var('stack2'))
matches([[Xshellslash\Xstack%.lua]], api.nvim_get_var('stack3'))
matches([[Xshellslash/Xstack%.lua]], api.nvim_get_var('stack3'))
end
rmdir('Xshellslash')
@@ -326,14 +326,13 @@ describe(':source', function()
end)
it('$HOME is not shortened in filepath in v:stacktrace from sourced file', function()
local sep = n.get_pathsep()
local xhome = table.concat({ vim.uv.cwd(), 'Xhome' }, sep)
local xhome = t.fix_slashes(assert(vim.uv.cwd())) .. '/Xhome'
mkdir(xhome)
clear({ env = { HOME = xhome } })
finally(function()
rmdir(xhome)
end)
local filepath = table.concat({ xhome, 'Xstacktrace.vim' }, sep)
local filepath = xhome .. '/Xstacktrace.vim'
local script = [[
func Xfunc()
throw 'Exception from Xfunc'
+4 -4
View File
@@ -17,9 +17,9 @@ local function last_set_lua_verbose_tests(cmd, v1)
setup(function()
clear(v1 and { args = { '-V1' } } or nil)
script_file = 'test_verbose.lua'
local current_dir = fn.getcwd()
local current_dir = assert(t.fix_slashes(fn.getcwd()))
current_dir = fn.fnamemodify(current_dir, ':~')
script_location = table.concat({ current_dir, n.get_pathsep(), script_file })
script_location = current_dir .. '/' .. script_file
write_file(
script_file,
@@ -326,9 +326,9 @@ describe(':verbose when using API from Vimscript', function()
setup(function()
clear()
script_file = 'test_verbose.vim'
local current_dir = fn.getcwd()
local current_dir = assert(t.fix_slashes(fn.getcwd()))
current_dir = fn.fnamemodify(current_dir, ':~')
script_location = table.concat({ current_dir, n.get_pathsep(), script_file })
script_location = current_dir .. '/' .. script_file
write_file(
script_file,
+9 -18
View File
@@ -41,15 +41,6 @@ describe('glob() and globpath()', function()
command([[$put =string(globpath('sautest\autoload', '*.vim'))]])
command([[$put =string(globpath('sautest\autoload', '*.vim', 0, 1))]])
expect([=[
Xxx{
Xxx$
'sautest\autoload\Test104.vim
sautest\autoload\footest.vim'
['sautest\autoload\Test104.vim', 'sautest\autoload\footest.vim']]=])
else
command([[$put =glob('Xxx\{')]])
command([[$put =glob('Xxx\$')]])
@@ -61,16 +52,16 @@ describe('glob() and globpath()', function()
command("$put =string(globpath('sautest/autoload', '*.vim'))")
command("$put =string(globpath('sautest/autoload', '*.vim', 0, 1))")
expect([=[
Xxx{
Xxx$
'sautest/autoload/Test104.vim
sautest/autoload/footest.vim'
['sautest/autoload/Test104.vim', 'sautest/autoload/footest.vim']]=])
end
expect([=[
Xxx{
Xxx$
'sautest/autoload/Test104.vim
sautest/autoload/footest.vim'
['sautest/autoload/Test104.vim', 'sautest/autoload/footest.vim']]=])
end)
teardown(function()
+4 -16
View File
@@ -291,10 +291,8 @@ describe('XDG defaults', function()
clear()
local rtp = eval('split(&runtimepath, ",")')
local rv = {}
local expected = (
is_os('win') and { [[\nvim-data\site]], [[\nvim-data\site\after]] }
or { '/nvim/site', '/nvim/site/after' }
)
local data_dir = is_os('win') and '/nvim-data' or '/nvim'
local expected = { data_dir .. '/site', data_dir .. '/site/after' }
for _, v in ipairs(rtp) do
local m = string.match(v, [=[[/\]nvim[^/\]*[/\]site.*$]=])
@@ -695,7 +693,7 @@ describe('XDG defaults', function()
it('are escaped properly', function()
local vimruntime, libdir = vimruntime_and_libdir()
local path_sep = is_os('win') and '\\' or '/'
local path_sep = '/'
eq(
(
'\\, \\, \\,'
@@ -869,17 +867,7 @@ describe('XDG defaults', function()
.. (path_sep):rep(2),
api.nvim_get_option_value('undodir', {})
)
eq(
'\\,=\\,=\\,'
.. path_sep
.. ''
.. state_dir
.. ''
.. path_sep
.. 'view'
.. (path_sep):rep(2),
api.nvim_get_option_value('viewdir', {})
)
eq(',=,=,/' .. state_dir .. '/view//', api.nvim_get_option_value('viewdir', {}))
end)
end)
end)
@@ -314,11 +314,7 @@ local function test_terminal_with_fake_shell(backslash)
eq('term://', string.match(eval('bufname("%")'), '^term://'))
feed([[<C-\><C-N>]])
command([[find */Xuniquefile]])
if is_os('win') then
eq('Xsomedir\\Xuniquefile', eval('bufname("%")'))
else
eq('Xsomedir/Xuniquefile', eval('bufname("%")'))
end
eq('Xsomedir/Xuniquefile', eval('bufname("%")'))
end)
it('works with gf', function()
+6 -6
View File
@@ -21,7 +21,7 @@ describe('title', function()
end)
it('defaults to "No Name" and the PWD in the title if the buffer is unnamed', function()
local expected = (is_os('win') and '[No Name] (C:\\) - Nvim' or '[No Name] (/) - Nvim')
local expected = (is_os('win') and '[No Name] (C:/) - Nvim' or '[No Name] (/) - Nvim')
command(is_os('win') and 'cd C:\\' or 'cd /')
command('set title')
screen:expect(function()
@@ -32,7 +32,7 @@ describe('title', function()
it(
'defaults to the filename and its directory in the title if the buffer is named as a path outside the PWD',
function()
local expected = (is_os('win') and 'myfile (C:\\mydir) - Nvim' or 'myfile (/mydir) - Nvim')
local expected = (is_os('win') and 'myfile (C:/mydir) - Nvim' or 'myfile (/mydir) - Nvim')
command('set title')
command(is_os('win') and 'file C:\\mydir\\myfile' or 'file /mydir/myfile')
screen:expect(function()
@@ -44,7 +44,7 @@ describe('title', function()
it(
'defaults to the filename and the PWD in the title if the buffer is a file in the PWD',
function()
local expected = (is_os('win') and 'myfile (C:\\) - Nvim' or 'myfile (/) - Nvim')
local expected = (is_os('win') and 'myfile (C:/) - Nvim' or 'myfile (/) - Nvim')
command('set title')
command(is_os('win') and 'cd C:\\' or 'cd /')
command('file myfile')
@@ -58,12 +58,12 @@ describe('title', function()
command(is_os('win') and 'cd C:\\' or 'cd /')
api.nvim_set_option_value('title', true, {})
screen:expect(function()
local expected = (is_os('win') and '[No Name] (C:\\) - Nvim' or '[No Name] (/) - Nvim')
local expected = (is_os('win') and '[No Name] (C:/) - Nvim' or '[No Name] (/) - Nvim')
eq(expected, screen.title)
end)
feed('ifoo')
screen:expect(function()
local expected = (is_os('win') and '[No Name] + (C:\\) - Nvim' or '[No Name] + (/) - Nvim')
local expected = (is_os('win') and '[No Name] + (C:/) - Nvim' or '[No Name] + (/) - Nvim')
eq(expected, screen.title)
end)
feed('<Esc>')
@@ -126,7 +126,7 @@ describe('title', function()
describe('is not changed by', function()
local file1 = is_os('win') and 'C:\\mydir\\myfile1' or '/mydir/myfile1'
local file2 = is_os('win') and 'C:\\mydir\\myfile2' or '/mydir/myfile2'
local expected = (is_os('win') and 'myfile1 (C:\\mydir) - Nvim' or 'myfile1 (/mydir) - Nvim')
local expected = (is_os('win') and 'myfile1 (C:/mydir) - Nvim' or 'myfile1 (/mydir) - Nvim')
local buf2
before_each(function()
@@ -6,7 +6,6 @@ local clear = n.clear
local fn = n.fn
local api = n.api
local command = n.command
local get_pathsep = n.get_pathsep
local rmdir = n.rmdir
local pcall_err = t.pcall_err
local mkdir = t.mkdir
@@ -81,8 +80,8 @@ describe('bufname() function', function()
it('returns expected buffer name', function()
eq('', fn.bufname('%')) -- Buffer has no name yet
command('file ' .. fname)
local wd = vim.uv.cwd()
local sep = get_pathsep()
local wd = assert(t.fix_slashes(assert(vim.uv.cwd())))
local sep = '/'
local curdirname = fn.fnamemodify(wd, ':t')
for _, arg in ipairs({ '%', 1, 'X', wd }) do
eq(fname, fn.bufname(arg))
+1 -1
View File
@@ -85,7 +85,7 @@ describe('backtick expansion', function()
eq({ 'file2' }, eval('argv()'))
if t.is_os('win') then
command(':silent args `dir /s/b *4`')
eq({ 'subdir\\file4' }, eval('map(argv(), \'fnamemodify(v:val, ":.")\')'))
eq({ 'subdir/file4' }, eval('map(argv(), \'fnamemodify(v:val, ":.")\')'))
else
command(':silent args `echo */*4`')
eq({ 'subdir/file4' }, eval('argv()'))
@@ -19,13 +19,13 @@ describe('executable()', function()
end)
if is_os('win') then
it('exepath respects shellslash', function()
it('exepath returns consistent slashes #13787', function()
-- test/ cannot be a symlink in this test.
n.api.nvim_set_current_dir(t.paths.test_source_path)
command('let $PATH = fnamemodify("./test/functional/fixtures/bin", ":p")')
eq(
[[test\functional\fixtures\bin\null.CMD]],
[[test/functional/fixtures/bin/null.CMD]],
call('fnamemodify', call('exepath', 'null'), ':.')
)
command('set shellslash')
@@ -35,12 +35,12 @@ describe('executable()', function()
)
end)
it('stdpath respects shellslash', function()
it('stdpath returns consistent slashes #13787', function()
-- Needs to check paths relative to repo root dir.
n.api.nvim_set_current_dir(t.paths.test_source_path)
t.matches(
[[build\Xtest_xdg[%w_]*\share\nvim%-data]],
[[build/Xtest_xdg[%w_]*/share/nvim%-data]],
call('fnamemodify', call('stdpath', 'data'), ':.')
)
command('set shellslash')
@@ -29,21 +29,20 @@ describe('fnamemodify()', function()
end)
it('handles the root path', function()
local root = n.pathroot()
local root = assert(t.fix_slashes(n.pathroot()))
eq(root, fnamemodify([[/]], ':p:h'))
eq(root, fnamemodify([[/]], ':p'))
if is_os('win') then
eq(root, fnamemodify([[\]], ':p:h'))
eq(root, fnamemodify([[\]], ':p'))
command('set shellslash')
root = string.sub(root, 1, -2) .. '/'
eq(root, fnamemodify([[\]], ':p:h'))
eq(root, fnamemodify([[\]], ':p'))
eq(root, fnamemodify([[/]], ':p:h'))
eq(root, fnamemodify([[/]], ':p'))
local letter_colon = root:sub(1, 2)
local old_dir = getcwd() .. '/'
local old_dir = t.fix_slashes(getcwd()) .. '/'
local foo_dir = old_dir .. 'foo/'
eq(old_dir, fnamemodify(letter_colon, ':p'))
eq(old_dir, fnamemodify(letter_colon .. '.', ':p'))
+2 -2
View File
@@ -72,12 +72,12 @@ func Test_expand_sfile_and_stack()
let g:stack3 = expand('<stack>')
END
call writefile(lines, 'Xshellslash/Xstack')
" Test that changing 'shellslash' always affects the result of expand()
" Test that changing 'shellslash' doesn't affect the result of expand()
" when sourcing a script multiple times.
for i in range(2)
source Xshellslash/Xstack
call assert_match('\<Xshellslash/Xstack\[1\]$', g:stack1)
call assert_match('\<Xshellslash\\Xstack\[3\]$', g:stack2)
call assert_match('\<Xshellslash/Xstack\[3\]$', g:stack2)
call assert_match('\<Xshellslash/Xstack\[5\]$', g:stack3)
unlet g:stack1
unlet g:stack2
+3 -2
View File
@@ -3671,9 +3671,10 @@ func Test_glob2()
call assert_equal([], (glob('abc[glob]def\*', 0, 1)))
call assert_equal([], (glob('\[XglobDir]\*', 0, 1)))
call assert_equal([], (glob('abc\[glob]def\*', 0, 1)))
" Test that changing 'shellslash' doesn't affect the result of glob()
set noshellslash
call assert_equal(['[XglobDir]\Xglob'], (glob('[[]XglobDir]/*', 0, 1)))
call assert_equal(['abc[glob]def\Xglob'], (glob('abc[[]glob]def/*', 0, 1)))
call assert_equal(['[XglobDir]/Xglob'], (glob('[[]XglobDir]/*', 0, 1)))
call assert_equal(['abc[glob]def/Xglob'], (glob('abc[[]glob]def/*', 0, 1)))
set shellslash
call assert_equal(['[XglobDir]/Xglob'], (glob('[[]XglobDir]/*', 0, 1)))
call assert_equal(['abc[glob]def/Xglob'], (glob('abc[[]glob]def/*', 0, 1)))
+6 -8
View File
@@ -940,9 +940,10 @@ func Test_ins_completeslash()
%bw!
call delete('Xdir', 'rf')
" globpath() should always return forward slash separator
set noshellslash
set completeslash=slash
call assert_true(stridx(globpath(&rtp, 'syntax/*.vim', 1, 1)[0], '\') != -1)
set completeslash=backslash
call assert_true(stridx(globpath(&rtp, 'syntax/*.vim', 1, 1)[0], '\') == -1)
let &shellslash = orig_shellslash
set completeslash=
@@ -1553,13 +1554,10 @@ endfunc
func Test_issue_7021()
CheckMSWindows
let orig_shellslash = &shellslash
set noshellslash
" Test that 'completeslash' doesn't affect the result of expand()
set completeslash=backslash
call assert_false(expand('~') =~ '\\')
set completeslash=slash
call assert_false(expand('~') =~ '/')
let &shellslash = orig_shellslash
set completeslash=
endfunc
+1 -1
View File
@@ -38,7 +38,7 @@ endfunc
func CheckHome(exp, ...)
call assert_equal(a:exp, $HOME)
call assert_equal(a:exp, expand('~', ':p'))
call assert_equal(substitute(a:exp, '\\', '/', 'g'), expand('~', ':p'))
if !a:0
call CheckHomeIsMissingFromSubprocessEnvironment()
else