From 12ea9735f77b81f0fecc7abe712639f88889f7f3 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Wed, 1 Jul 2026 09:48:54 -0400 Subject: [PATCH] refactor(path): path_skip_sep() #40524 Problem: Redundant code. Solution: Add path_skip_sep() and use it. Dropping MB_PTR_ADV is safe: the loops only advance while `*p` is a one-byte separator (`/`, `\`, `:`). MB_PTR_ADV was needed in legacy Vim because it supported non-UTF-8 (DBCS) *internal* encodings. --- runtime/doc/news.txt | 2 +- src/nvim/os/fs.c | 28 ++++++++++------------------ src/nvim/path.c | 43 ++++++++++++++++++++++--------------------- 3 files changed, 33 insertions(+), 40 deletions(-) diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index a4c23d9e76..d2c953d150 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -340,6 +340,7 @@ UI VIMSCRIPT +• `fnamemodify(':h')` preserves logical roots for more path formats. • |v:exitreason| is set before |QuitPre|. • |v:startreason| indicates whether Nvim started normally or by |:restart|. • |v:starttime| is the process start time (nanoseconds since UNIX epoch). @@ -369,7 +370,6 @@ These existing features changed their behavior. • Markdown inline highlighting now conceals the backslash in backslash escapes. • Markdown inline backslash escapes and hard line breaks no longer use the `@string.escape` capture. -• `fnamemodify(':h')` preserves logical roots for more path formats. ============================================================================== REMOVED FEATURES *news-removed* diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c index 14cb6cb611..85588ed876 100644 --- a/src/nvim/os/fs.c +++ b/src/nvim/os/fs.c @@ -1202,14 +1202,6 @@ bool os_fileinfo(const char *path, FileInfo *file_info) return os_stat(path, &(file_info->stat)) == kLibuvSuccess; } -static const char *path_skip_sep(const char *path) -{ - while (*path != NUL && vim_ispathsep_nocolon(*path)) { - path++; - } - return path; -} - /// Parses `path` into a `FileInfo` structure. /// /// TODO(ntdiary): Could be extended for path.c cleanup and path normalization @@ -1223,15 +1215,15 @@ bool os_fileinfo2(const char *path, FileInfo *info) if (path_with_url(path)) { return true; } - // Preserves the leading two "/"; runs of 3+ "/" collapse to a single "/" (IEEE 1003.1). - // The same rule applies to kPathUNC and kPathGeneric on Windows, but not to kPathDevice - // or kPathDeviceUNC, where the leading "//" is significant. - const char *p = path_skip_sep(path); + // Preserves the leading two "/"; 3+ "///…" collapse to a single "/" (IEEE 1003.1). + // Same rule applies to kPathUNC/kPathGeneric on Windows, but not to kPathDevice or + // kPathDeviceUNC, where the leading "//" is significant. + const char *p = path_skip_sep(path, false); size_t leading_slashes = (size_t)(p - path); #ifdef MSWIN if (leading_slashes == 0 && ASCII_ISALPHA(p[0]) && p[1] == ':') { info->type = kPathDrive; - p = path_skip_sep(p + 2); + p = path_skip_sep(p + 2, false); info->rest_off = (size_t)(p - path); return true; } @@ -1241,10 +1233,10 @@ bool os_fileinfo2(const char *path, FileInfo *info) } info->type = kPathDevice; info->prefix_off = leading_slashes - 2; - p = path_skip_sep(p + 2); + p = path_skip_sep(p + 2, false); if (vim_strnicmp_asc(p, "unc", 3) == 0 && vim_ispathsep_nocolon(p[3])) { info->type = kPathDeviceUNC; - p = path_skip_sep(p + 4); + p = path_skip_sep(p + 4, false); info->root_off = (size_t)(p - path); goto server; } @@ -1252,15 +1244,15 @@ bool os_fileinfo2(const char *path, FileInfo *info) if (ASCII_ISALPHA(p[0]) && p[1] == ':') { p += 2; } - p = path_skip_sep(path_next_component(p)); + p = path_skip_sep(path_next_component(p), false); info->rest_off = (size_t)(p - path); return true; } if (leading_slashes == 2) { info->type = kPathUNC; server: - p = path_skip_sep(path_next_component(p)); - p = path_skip_sep(path_next_component(p)); + p = path_skip_sep(path_next_component(p), false); + p = path_skip_sep(path_next_component(p), false); info->rest_off = (size_t)(p - path); return true; } diff --git a/src/nvim/path.c b/src/nvim/path.c index 87fef36c09..184783d25c 100644 --- a/src/nvim/path.c +++ b/src/nvim/path.c @@ -188,7 +188,7 @@ const char *invocation_path_tail(const char *invocation, size_t *len) return tail; } -/// Get the next path component of a path name. +/// Get the next separator-delimited component of a path name. /// /// @param fname A file path. (Must be != NULL.) /// @return Pointer to first found path separator + 1. @@ -205,6 +205,20 @@ const char *path_next_component(const char *fname) return fname; } +/// Advances past consecutive path separators. +/// +/// @param path Position in a path. +/// @param colon Whether ':' counts as a separator on MS-Windows (see vim_ispathsep()). +/// @return Pointer to the first non-separator byte (or terminating NUL). +char *path_skip_sep(const char *path, bool colon) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET FUNC_ATTR_PURE +{ + while (colon ? vim_ispathsep(*path) : vim_ispathsep_nocolon(*path)) { + path++; + } + return (char *)path; +} + /// Returns the length of the path head on the current platform. /// @return /// - 3 on windows @@ -249,9 +263,7 @@ char *get_past_head(const char *path) } #endif - while (vim_ispathsep(*retval)) { - retval++; - } + retval = path_skip_sep(retval, true); return (char *)retval; } @@ -1006,9 +1018,7 @@ static char *get_path_cutoff(char *fname, garray_T *gap) // skip to the file or directory name if (cutoff != NULL) { - while (vim_ispathsep(*cutoff)) { - MB_PTR_ADV(cutoff); - } + cutoff = path_skip_sep(cutoff, true); } return cutoff; @@ -1642,9 +1652,7 @@ size_t simplify_filename(char *filename) if (vim_ispathsep(*p)) { relative = false; - do { - p++; - } while (vim_ispathsep(*p)); + p = path_skip_sep(p, true); } char *start = p; // remember start after "c:/" or "/" or "///" char *p_end = p + strlen(p); // point to NUL at end of string "p" @@ -1674,9 +1682,7 @@ size_t simplify_filename(char *filename) // of an absolute path name. char *tail = p + 1; if (p[1] != NUL) { - while (vim_ispathsep(*tail)) { - MB_PTR_ADV(tail); - } + tail = path_skip_sep(tail, true); } else if (p > start) { p--; // strip preceding path separator } @@ -1686,10 +1692,7 @@ size_t simplify_filename(char *filename) } else if (p[0] == '.' && p[1] == '.' && (vim_ispathsep(p[2]) || p[2] == NUL)) { // Skip to after ".." or "../" or "..///". - char *tail = p + 2; - while (vim_ispathsep(*tail)) { - MB_PTR_ADV(tail); - } + char *tail = path_skip_sep(p + 2, true); if (components > 0) { // strip one preceding component bool do_strip = false; @@ -2175,10 +2178,8 @@ char *path_shorten_fname(char *full_path, char *dir_name) return NULL; } - do { - p++; - } while (vim_ispathsep_nocolon(*p)); - return p; + // Skip the matched separator, then any following separators (but not a colon). + return path_skip_sep(p + 1, false); } /// Invoke expand_wildcards() for one pattern