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.

b005cd80cf

Co-authored-by: Bram Moolenaar <Bram@vim.org>
This commit is contained in:
Jan Edmund Lazo
2025-10-15 02:35:55 -04:00
committed by GitHub
parent 382963891c
commit 183f8cc59d
5 changed files with 346 additions and 343 deletions

View File

@@ -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.

View File

@@ -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);
}

View File

@@ -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)
{

View File

@@ -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"

View File

@@ -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"