commit 183f8cc59d855070810e33e9c13b4d769db6e045
parent 382963891c9b7781a2a6cfa91e8e2f95f4eb586d
Author: Jan Edmund Lazo <jan.lazo@mail.utoronto.ca>
Date: Wed, 15 Oct 2025 02:35:55 -0400
vim-patch:8.1.1979: code for handling file names is spread out (#36176)
Problem: Code for handling file names is spread out.
Solution: Move code to new filepath.c file. Graduate FEAT_MODIFY_FNAME.
https://github.com/vim/vim/commit/b005cd80cfda591be95146024d9b97eef383500f
Co-authored-by: Bram Moolenaar <Bram@vim.org>
Diffstat:
5 files changed, 346 insertions(+), 343 deletions(-)
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
@@ -6919,307 +6919,6 @@ void last_set_msg(sctx_T script_ctx)
verbose_leave();
}
-/// Adjust a filename, according to a string of modifiers.
-/// *fnamep must be NUL terminated when called. When returning, the length is
-/// determined by *fnamelen.
-/// Returns VALID_ flags or -1 for failure.
-/// When there is an error, *fnamep is set to NULL.
-///
-/// @param src string with modifiers
-/// @param tilde_file "~" is a file name, not $HOME
-/// @param usedlen characters after src that are used
-/// @param fnamep file name so far
-/// @param bufp buffer for allocated file name or NULL
-/// @param fnamelen length of fnamep
-int modify_fname(char *src, bool tilde_file, size_t *usedlen, char **fnamep, char **bufp,
- size_t *fnamelen)
-{
- int valid = 0;
- char *s, *p, *pbuf;
- char dirname[MAXPATHL];
- bool has_fullname = false;
- bool has_homerelative = false;
-
-repeat:
- // ":p" - full path/file_name
- if (src[*usedlen] == ':' && src[*usedlen + 1] == 'p') {
- has_fullname = true;
-
- valid |= VALID_PATH;
- *usedlen += 2;
-
- // Expand "~/path" for all systems and "~user/path" for Unix
- if ((*fnamep)[0] == '~'
-#if !defined(UNIX)
- && ((*fnamep)[1] == '/'
-# ifdef BACKSLASH_IN_FILENAME
- || (*fnamep)[1] == '\\'
-# endif
- || (*fnamep)[1] == NUL)
-#endif
- && !(tilde_file && (*fnamep)[1] == NUL)) {
- *fnamep = expand_env_save(*fnamep);
- xfree(*bufp); // free any allocated file name
- *bufp = *fnamep;
- if (*fnamep == NULL) {
- return -1;
- }
- }
-
- // When "/." or "/.." is used: force expansion to get rid of it.
- for (p = *fnamep; *p != NUL; MB_PTR_ADV(p)) {
- if (vim_ispathsep(*p)
- && p[1] == '.'
- && (p[2] == NUL
- || vim_ispathsep(p[2])
- || (p[2] == '.'
- && (p[3] == NUL || vim_ispathsep(p[3]))))) {
- break;
- }
- }
-
- // FullName_save() is slow, don't use it when not needed.
- if (*p != NUL || !vim_isAbsName(*fnamep)) {
- *fnamep = FullName_save(*fnamep, *p != NUL);
- xfree(*bufp); // free any allocated file name
- *bufp = *fnamep;
- if (*fnamep == NULL) {
- return -1;
- }
- }
-
- // Append a path separator to a directory.
- if (os_isdir(*fnamep)) {
- // Make room for one or two extra characters.
- *fnamep = xstrnsave(*fnamep, strlen(*fnamep) + 2);
- xfree(*bufp); // free any allocated file name
- *bufp = *fnamep;
- add_pathsep(*fnamep);
- }
- }
-
- int c;
-
- // ":." - path relative to the current directory
- // ":~" - path relative to the home directory
- // ":8" - shortname path - postponed till after
- while (src[*usedlen] == ':'
- && ((c = (uint8_t)src[*usedlen + 1]) == '.' || c == '~' || c == '8')) {
- *usedlen += 2;
- if (c == '8') {
- continue;
- }
- pbuf = NULL;
- // Need full path first (use expand_env() to remove a "~/")
- if (!has_fullname && !has_homerelative) {
- if (**fnamep == '~') {
- p = pbuf = expand_env_save(*fnamep);
- } else {
- p = pbuf = FullName_save(*fnamep, false);
- }
- } else {
- p = *fnamep;
- }
-
- has_fullname = false;
-
- if (p != NULL) {
- if (c == '.') {
- os_dirname(dirname, MAXPATHL);
- if (has_homerelative) {
- s = xstrdup(dirname);
- home_replace(NULL, s, dirname, MAXPATHL, true);
- xfree(s);
- }
- size_t namelen = strlen(dirname);
-
- // Do not call shorten_fname() here since it removes the prefix
- // even though the path does not have a prefix.
- if (path_fnamencmp(p, dirname, namelen) == 0) {
- p += namelen;
- if (vim_ispathsep(*p)) {
- while (*p && vim_ispathsep(*p)) {
- p++;
- }
- *fnamep = p;
- if (pbuf != NULL) {
- // free any allocated file name
- xfree(*bufp);
- *bufp = pbuf;
- pbuf = NULL;
- }
- }
- }
- } else {
- home_replace(NULL, p, dirname, MAXPATHL, true);
- // Only replace it when it starts with '~'
- if (*dirname == '~') {
- s = xstrdup(dirname);
- assert(s != NULL); // suppress clang "Argument with 'nonnull' attribute passed null"
- *fnamep = s;
- xfree(*bufp);
- *bufp = s;
- has_homerelative = true;
- }
- }
- xfree(pbuf);
- }
- }
-
- char *tail = path_tail(*fnamep);
- *fnamelen = strlen(*fnamep);
-
- // ":h" - head, remove "/file_name", can be repeated
- // Don't remove the first "/" or "c:\"
- while (src[*usedlen] == ':' && src[*usedlen + 1] == 'h') {
- valid |= VALID_HEAD;
- *usedlen += 2;
- s = get_past_head(*fnamep);
- while (tail > s && after_pathsep(s, tail)) {
- MB_PTR_BACK(*fnamep, tail);
- }
- *fnamelen = (size_t)(tail - *fnamep);
- if (*fnamelen == 0) {
- // Result is empty. Turn it into "." to make ":cd %:h" work.
- xfree(*bufp);
- *bufp = *fnamep = tail = xstrdup(".");
- *fnamelen = 1;
- } else {
- while (tail > s && !after_pathsep(s, tail)) {
- MB_PTR_BACK(*fnamep, tail);
- }
- }
- }
-
- // ":8" - shortname
- if (src[*usedlen] == ':' && src[*usedlen + 1] == '8') {
- *usedlen += 2;
- }
-
- // ":t" - tail, just the basename
- if (src[*usedlen] == ':' && src[*usedlen + 1] == 't') {
- *usedlen += 2;
- *fnamelen -= (size_t)(tail - *fnamep);
- *fnamep = tail;
- }
-
- // ":e" - extension, can be repeated
- // ":r" - root, without extension, can be repeated
- while (src[*usedlen] == ':'
- && (src[*usedlen + 1] == 'e' || src[*usedlen + 1] == 'r')) {
- // find a '.' in the tail:
- // - for second :e: before the current fname
- // - otherwise: The last '.'
- const bool is_second_e = *fnamep > tail;
- if (src[*usedlen + 1] == 'e' && is_second_e) {
- s = (*fnamep) - 2;
- } else {
- s = (*fnamep) + *fnamelen - 1;
- }
-
- for (; s > tail; s--) {
- if (s[0] == '.') {
- break;
- }
- }
- if (src[*usedlen + 1] == 'e') {
- if (s > tail || (0 && is_second_e && s == tail)) {
- // we stopped at a '.' (so anchor to &'.' + 1)
- char *newstart = s + 1;
- size_t distance_stepped_back = (size_t)(*fnamep - newstart);
- *fnamelen += distance_stepped_back;
- *fnamep = newstart;
- } else if (*fnamep <= tail) {
- *fnamelen = 0;
- }
- } else {
- // :r - Remove one extension
- //
- // Ensure that `s` doesn't go before `*fnamep`,
- // since then we're taking too many roots:
- //
- // "path/to/this.file.ext" :e:e:r:r
- // ^ ^-------- *fnamep
- // +------------- tail
- //
- // Also ensure `s` doesn't go before `tail`,
- // since then we're taking too many roots again:
- //
- // "path/to/this.file.ext" :r:r:r
- // ^ ^------------- tail
- // +--------------------- *fnamep
- if (s > MAX(tail, *fnamep)) {
- *fnamelen = (size_t)(s - *fnamep);
- }
- }
- *usedlen += 2;
- }
-
- // ":s?pat?foo?" - substitute
- // ":gs?pat?foo?" - global substitute
- if (src[*usedlen] == ':'
- && (src[*usedlen + 1] == 's'
- || (src[*usedlen + 1] == 'g' && src[*usedlen + 2] == 's'))) {
- bool didit = false;
-
- char *flags = "";
- s = src + *usedlen + 2;
- if (src[*usedlen + 1] == 'g') {
- flags = "g";
- s++;
- }
-
- int sep = (uint8_t)(*s++);
- if (sep) {
- // find end of pattern
- p = vim_strchr(s, sep);
- if (p != NULL) {
- char *const pat = xmemdupz(s, (size_t)(p - s));
- s = p + 1;
- // find end of substitution
- p = vim_strchr(s, sep);
- if (p != NULL) {
- char *const sub = xmemdupz(s, (size_t)(p - s));
- char *const str = xmemdupz(*fnamep, *fnamelen);
- *usedlen = (size_t)(p + 1 - src);
- size_t slen;
- s = do_string_sub(str, *fnamelen, pat, sub, NULL, flags, &slen);
- *fnamep = s;
- *fnamelen = slen;
- xfree(*bufp);
- *bufp = s;
- didit = true;
- xfree(sub);
- xfree(str);
- }
- xfree(pat);
- }
- // after using ":s", repeat all the modifiers
- if (didit) {
- goto repeat;
- }
- }
- }
-
- if (src[*usedlen] == ':' && src[*usedlen + 1] == 'S') {
- // vim_strsave_shellescape() needs a NUL terminated string.
- c = (uint8_t)(*fnamep)[*fnamelen];
- if (c != NUL) {
- (*fnamep)[*fnamelen] = NUL;
- }
- p = vim_strsave_shellescape(*fnamep, false, false);
- if (c != NUL) {
- (*fnamep)[*fnamelen] = (char)c;
- }
- xfree(*bufp);
- *bufp = *fnamep = p;
- *fnamelen = strlen(p);
- *usedlen += 2;
- }
-
- return valid;
-}
-
/// Perform a substitution on "str" with pattern "pat" and substitute "sub".
/// When "sub" is NULL "expr" is used, must be a VAR_FUNC or VAR_PARTIAL.
/// "flags" can be "g" to do a global substitute.
diff --git a/src/nvim/eval/fs.c b/src/nvim/eval/fs.c
@@ -31,6 +31,7 @@
#include "nvim/gettext_defs.h"
#include "nvim/globals.h"
#include "nvim/macros_defs.h"
+#include "nvim/mbyte.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/option_vars.h"
@@ -38,7 +39,7 @@
#include "nvim/os/fileio_defs.h"
#include "nvim/os/fs.h"
#include "nvim/os/fs_defs.h"
-#include "nvim/os/os_defs.h"
+#include "nvim/os/os.h"
#include "nvim/path.h"
#include "nvim/pos_defs.h"
#include "nvim/strings.h"
@@ -50,6 +51,307 @@
static const char e_error_while_writing_str[] = N_("E80: Error while writing: %s");
+/// Adjust a filename, according to a string of modifiers.
+/// *fnamep must be NUL terminated when called. When returning, the length is
+/// determined by *fnamelen.
+/// Returns VALID_ flags or -1 for failure.
+/// When there is an error, *fnamep is set to NULL.
+///
+/// @param src string with modifiers
+/// @param tilde_file "~" is a file name, not $HOME
+/// @param usedlen characters after src that are used
+/// @param fnamep file name so far
+/// @param bufp buffer for allocated file name or NULL
+/// @param fnamelen length of fnamep
+int modify_fname(char *src, bool tilde_file, size_t *usedlen, char **fnamep, char **bufp,
+ size_t *fnamelen)
+{
+ int valid = 0;
+ char *s, *p, *pbuf;
+ char dirname[MAXPATHL];
+ bool has_fullname = false;
+ bool has_homerelative = false;
+
+repeat:
+ // ":p" - full path/file_name
+ if (src[*usedlen] == ':' && src[*usedlen + 1] == 'p') {
+ has_fullname = true;
+
+ valid |= VALID_PATH;
+ *usedlen += 2;
+
+ // Expand "~/path" for all systems and "~user/path" for Unix
+ if ((*fnamep)[0] == '~'
+#if !defined(UNIX)
+ && ((*fnamep)[1] == '/'
+# ifdef BACKSLASH_IN_FILENAME
+ || (*fnamep)[1] == '\\'
+# endif
+ || (*fnamep)[1] == NUL)
+#endif
+ && !(tilde_file && (*fnamep)[1] == NUL)) {
+ *fnamep = expand_env_save(*fnamep);
+ xfree(*bufp); // free any allocated file name
+ *bufp = *fnamep;
+ if (*fnamep == NULL) {
+ return -1;
+ }
+ }
+
+ // When "/." or "/.." is used: force expansion to get rid of it.
+ for (p = *fnamep; *p != NUL; MB_PTR_ADV(p)) {
+ if (vim_ispathsep(*p)
+ && p[1] == '.'
+ && (p[2] == NUL
+ || vim_ispathsep(p[2])
+ || (p[2] == '.'
+ && (p[3] == NUL || vim_ispathsep(p[3]))))) {
+ break;
+ }
+ }
+
+ // FullName_save() is slow, don't use it when not needed.
+ if (*p != NUL || !vim_isAbsName(*fnamep)) {
+ *fnamep = FullName_save(*fnamep, *p != NUL);
+ xfree(*bufp); // free any allocated file name
+ *bufp = *fnamep;
+ if (*fnamep == NULL) {
+ return -1;
+ }
+ }
+
+ // Append a path separator to a directory.
+ if (os_isdir(*fnamep)) {
+ // Make room for one or two extra characters.
+ *fnamep = xstrnsave(*fnamep, strlen(*fnamep) + 2);
+ xfree(*bufp); // free any allocated file name
+ *bufp = *fnamep;
+ add_pathsep(*fnamep);
+ }
+ }
+
+ int c;
+
+ // ":." - path relative to the current directory
+ // ":~" - path relative to the home directory
+ // ":8" - shortname path - postponed till after
+ while (src[*usedlen] == ':'
+ && ((c = (uint8_t)src[*usedlen + 1]) == '.' || c == '~' || c == '8')) {
+ *usedlen += 2;
+ if (c == '8') {
+ continue;
+ }
+ pbuf = NULL;
+ // Need full path first (use expand_env() to remove a "~/")
+ if (!has_fullname && !has_homerelative) {
+ if (**fnamep == '~') {
+ p = pbuf = expand_env_save(*fnamep);
+ } else {
+ p = pbuf = FullName_save(*fnamep, false);
+ }
+ } else {
+ p = *fnamep;
+ }
+
+ has_fullname = false;
+
+ if (p != NULL) {
+ if (c == '.') {
+ os_dirname(dirname, MAXPATHL);
+ if (has_homerelative) {
+ s = xstrdup(dirname);
+ home_replace(NULL, s, dirname, MAXPATHL, true);
+ xfree(s);
+ }
+ size_t namelen = strlen(dirname);
+
+ // Do not call shorten_fname() here since it removes the prefix
+ // even though the path does not have a prefix.
+ if (path_fnamencmp(p, dirname, namelen) == 0) {
+ p += namelen;
+ if (vim_ispathsep(*p)) {
+ while (*p && vim_ispathsep(*p)) {
+ p++;
+ }
+ *fnamep = p;
+ if (pbuf != NULL) {
+ // free any allocated file name
+ xfree(*bufp);
+ *bufp = pbuf;
+ pbuf = NULL;
+ }
+ }
+ }
+ } else {
+ home_replace(NULL, p, dirname, MAXPATHL, true);
+ // Only replace it when it starts with '~'
+ if (*dirname == '~') {
+ s = xstrdup(dirname);
+ assert(s != NULL); // suppress clang "Argument with 'nonnull' attribute passed null"
+ *fnamep = s;
+ xfree(*bufp);
+ *bufp = s;
+ has_homerelative = true;
+ }
+ }
+ xfree(pbuf);
+ }
+ }
+
+ char *tail = path_tail(*fnamep);
+ *fnamelen = strlen(*fnamep);
+
+ // ":h" - head, remove "/file_name", can be repeated
+ // Don't remove the first "/" or "c:\"
+ while (src[*usedlen] == ':' && src[*usedlen + 1] == 'h') {
+ valid |= VALID_HEAD;
+ *usedlen += 2;
+ s = get_past_head(*fnamep);
+ while (tail > s && after_pathsep(s, tail)) {
+ MB_PTR_BACK(*fnamep, tail);
+ }
+ *fnamelen = (size_t)(tail - *fnamep);
+ if (*fnamelen == 0) {
+ // Result is empty. Turn it into "." to make ":cd %:h" work.
+ xfree(*bufp);
+ *bufp = *fnamep = tail = xstrdup(".");
+ *fnamelen = 1;
+ } else {
+ while (tail > s && !after_pathsep(s, tail)) {
+ MB_PTR_BACK(*fnamep, tail);
+ }
+ }
+ }
+
+ // ":8" - shortname
+ if (src[*usedlen] == ':' && src[*usedlen + 1] == '8') {
+ *usedlen += 2;
+ }
+
+ // ":t" - tail, just the basename
+ if (src[*usedlen] == ':' && src[*usedlen + 1] == 't') {
+ *usedlen += 2;
+ *fnamelen -= (size_t)(tail - *fnamep);
+ *fnamep = tail;
+ }
+
+ // ":e" - extension, can be repeated
+ // ":r" - root, without extension, can be repeated
+ while (src[*usedlen] == ':'
+ && (src[*usedlen + 1] == 'e' || src[*usedlen + 1] == 'r')) {
+ // find a '.' in the tail:
+ // - for second :e: before the current fname
+ // - otherwise: The last '.'
+ const bool is_second_e = *fnamep > tail;
+ if (src[*usedlen + 1] == 'e' && is_second_e) {
+ s = (*fnamep) - 2;
+ } else {
+ s = (*fnamep) + *fnamelen - 1;
+ }
+
+ for (; s > tail; s--) {
+ if (s[0] == '.') {
+ break;
+ }
+ }
+ if (src[*usedlen + 1] == 'e') {
+ if (s > tail || (0 && is_second_e && s == tail)) {
+ // we stopped at a '.' (so anchor to &'.' + 1)
+ char *newstart = s + 1;
+ size_t distance_stepped_back = (size_t)(*fnamep - newstart);
+ *fnamelen += distance_stepped_back;
+ *fnamep = newstart;
+ } else if (*fnamep <= tail) {
+ *fnamelen = 0;
+ }
+ } else {
+ // :r - Remove one extension
+ //
+ // Ensure that `s` doesn't go before `*fnamep`,
+ // since then we're taking too many roots:
+ //
+ // "path/to/this.file.ext" :e:e:r:r
+ // ^ ^-------- *fnamep
+ // +------------- tail
+ //
+ // Also ensure `s` doesn't go before `tail`,
+ // since then we're taking too many roots again:
+ //
+ // "path/to/this.file.ext" :r:r:r
+ // ^ ^------------- tail
+ // +--------------------- *fnamep
+ if (s > MAX(tail, *fnamep)) {
+ *fnamelen = (size_t)(s - *fnamep);
+ }
+ }
+ *usedlen += 2;
+ }
+
+ // ":s?pat?foo?" - substitute
+ // ":gs?pat?foo?" - global substitute
+ if (src[*usedlen] == ':'
+ && (src[*usedlen + 1] == 's'
+ || (src[*usedlen + 1] == 'g' && src[*usedlen + 2] == 's'))) {
+ bool didit = false;
+
+ char *flags = "";
+ s = src + *usedlen + 2;
+ if (src[*usedlen + 1] == 'g') {
+ flags = "g";
+ s++;
+ }
+
+ int sep = (uint8_t)(*s++);
+ if (sep) {
+ // find end of pattern
+ p = vim_strchr(s, sep);
+ if (p != NULL) {
+ char *const pat = xmemdupz(s, (size_t)(p - s));
+ s = p + 1;
+ // find end of substitution
+ p = vim_strchr(s, sep);
+ if (p != NULL) {
+ char *const sub = xmemdupz(s, (size_t)(p - s));
+ char *const str = xmemdupz(*fnamep, *fnamelen);
+ *usedlen = (size_t)(p + 1 - src);
+ size_t slen;
+ s = do_string_sub(str, *fnamelen, pat, sub, NULL, flags, &slen);
+ *fnamep = s;
+ *fnamelen = slen;
+ xfree(*bufp);
+ *bufp = s;
+ didit = true;
+ xfree(sub);
+ xfree(str);
+ }
+ xfree(pat);
+ }
+ // after using ":s", repeat all the modifiers
+ if (didit) {
+ goto repeat;
+ }
+ }
+ }
+
+ if (src[*usedlen] == ':' && src[*usedlen + 1] == 'S') {
+ // vim_strsave_shellescape() needs a NUL terminated string.
+ c = (uint8_t)(*fnamep)[*fnamelen];
+ if (c != NUL) {
+ (*fnamep)[*fnamelen] = NUL;
+ }
+ p = vim_strsave_shellescape(*fnamep, false, false);
+ if (c != NUL) {
+ (*fnamep)[*fnamelen] = (char)c;
+ }
+ xfree(*bufp);
+ *bufp = *fnamep = p;
+ *fnamelen = strlen(p);
+ *usedlen += 2;
+ }
+
+ return valid;
+}
+
/// "chdir(dir)" function
void f_chdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
@@ -279,6 +581,34 @@ void f_findfile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
findfilendir(argvars, rettv, FINDFILE_FILE);
}
+/// "fnamemodify({fname}, {mods})" function
+void f_fnamemodify(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ char *fbuf = NULL;
+ size_t len = 0;
+ char buf[NUMBUFLEN];
+ const char *fname = tv_get_string_chk(&argvars[0]);
+ const char *const mods = tv_get_string_buf_chk(&argvars[1], buf);
+ if (mods == NULL || fname == NULL) {
+ fname = NULL;
+ } else {
+ len = strlen(fname);
+ if (*mods != NUL) {
+ size_t usedlen = 0;
+ modify_fname((char *)mods, false, &usedlen,
+ (char **)&fname, &fbuf, &len);
+ }
+ }
+
+ rettv->v_type = VAR_STRING;
+ if (fname == NULL) {
+ rettv->vval.v_string = NULL;
+ } else {
+ rettv->vval.v_string = xmemdupz(fname, len);
+ }
+ xfree(fbuf);
+}
+
/// `getcwd([{win}[, {tab}]])` function
///
/// Every scope not specified implies the currently selected scope object.
@@ -1501,3 +1831,16 @@ void f_writefile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
}
}
+
+/// "browse(save, title, initdir, default)" function
+void f_browse(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ rettv->vval.v_string = NULL;
+ rettv->v_type = VAR_STRING;
+}
+
+/// "browsedir(title, initdir)" function
+void f_browsedir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ f_browse(argvars, rettv, fptr);
+}
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
@@ -421,19 +421,6 @@ static void f_atan2(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
}
-/// "browse(save, title, initdir, default)" function
-static void f_browse(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- rettv->vval.v_string = NULL;
- rettv->v_type = VAR_STRING;
-}
-
-/// "browsedir(title, initdir)" function
-static void f_browsedir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- f_browse(argvars, rettv, fptr);
-}
-
/// Get buffer by number or pattern.
buf_T *tv_get_buf(typval_T *tv, int curtab_only)
{
@@ -1666,34 +1653,6 @@ static void f_fnameescape(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
rettv->v_type = VAR_STRING;
}
-/// "fnamemodify({fname}, {mods})" function
-static void f_fnamemodify(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- char *fbuf = NULL;
- size_t len = 0;
- char buf[NUMBUFLEN];
- const char *fname = tv_get_string_chk(&argvars[0]);
- const char *const mods = tv_get_string_buf_chk(&argvars[1], buf);
- if (mods == NULL || fname == NULL) {
- fname = NULL;
- } else {
- len = strlen(fname);
- if (*mods != NUL) {
- size_t usedlen = 0;
- modify_fname((char *)mods, false, &usedlen,
- (char **)&fname, &fbuf, &len);
- }
- }
-
- rettv->v_type = VAR_STRING;
- if (fname == NULL) {
- rettv->vval.v_string = NULL;
- } else {
- rettv->vval.v_string = xmemdupz(fname, len);
- }
- xfree(fbuf);
-}
-
/// "foreground()" function
static void f_foreground(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
@@ -35,6 +35,7 @@
#include "nvim/drawscreen.h"
#include "nvim/edit.h"
#include "nvim/errors.h"
+#include "nvim/eval/fs.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/eval/userfunc.h"
diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c
@@ -15,6 +15,7 @@
#include "nvim/cmdexpand.h"
#include "nvim/cmdexpand_defs.h"
#include "nvim/eval.h"
+#include "nvim/eval/fs.h"
#include "nvim/eval/vars.h"
#include "nvim/globals.h"
#include "nvim/log.h"