blob: 15f770f42c8196c049002b578644e67cf507aa4b [file] [log] [blame]
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001/* vi:set ts=8 sts=4 sw=4 noet:
2 *
3 * VIM - Vi IMproved by Bram Moolenaar
4 *
5 * Do ":help uganda" in Vim to read copying and usage conditions.
6 * Do ":help credits" in Vim to see a list of people who contributed.
7 * See README.txt for an overview of the Vim source code.
8 */
9
10/*
Bram Moolenaar9810cfb2019-12-11 21:23:00 +010011 * filepath.c: dealing with file names and paths.
Bram Moolenaarb005cd82019-09-04 15:54:55 +020012 */
13
14#include "vim.h"
15
16#ifdef MSWIN
17/*
18 * Functions for ":8" filename modifier: get 8.3 version of a filename.
19 */
20
21/*
22 * Get the short path (8.3) for the filename in "fnamep".
23 * Only works for a valid file name.
24 * When the path gets longer "fnamep" is changed and the allocated buffer
25 * is put in "bufp".
26 * *fnamelen is the length of "fnamep" and set to 0 for a nonexistent path.
27 * Returns OK on success, FAIL on failure.
28 */
29 static int
Yegappan Lakshmanan00d34592024-12-25 10:20:51 +010030get_short_pathname(char_u **fnamep, char_u **bufp, size_t *fnamelen)
Bram Moolenaarb005cd82019-09-04 15:54:55 +020031{
Christian Brabandt6cc30272024-12-13 17:54:33 +010032 int l, len;
Bram Moolenaar3f396972019-10-30 04:10:06 +010033 WCHAR *newbuf;
34 WCHAR *wfname;
Bram Moolenaarb005cd82019-09-04 15:54:55 +020035
Christian Brabandt6cc30272024-12-13 17:54:33 +010036 len = MAXPATHL;
37 newbuf = malloc(len * sizeof(*newbuf));
Bram Moolenaar3f396972019-10-30 04:10:06 +010038 if (newbuf == NULL)
39 return FAIL;
40
41 wfname = enc_to_utf16(*fnamep, NULL);
42 if (wfname == NULL)
43 {
44 vim_free(newbuf);
45 return FAIL;
46 }
47
Christian Brabandt6cc30272024-12-13 17:54:33 +010048 l = GetShortPathNameW(wfname, newbuf, len);
49 if (l > len - 1)
Bram Moolenaarb005cd82019-09-04 15:54:55 +020050 {
Bram Moolenaar26262f82019-09-04 20:59:15 +020051 // If that doesn't work (not enough space), then save the string
52 // and try again with a new buffer big enough.
Bram Moolenaar3f396972019-10-30 04:10:06 +010053 WCHAR *newbuf_t = newbuf;
54 newbuf = vim_realloc(newbuf, (l + 1) * sizeof(*newbuf));
Bram Moolenaarb005cd82019-09-04 15:54:55 +020055 if (newbuf == NULL)
Bram Moolenaar3f396972019-10-30 04:10:06 +010056 {
57 vim_free(wfname);
58 vim_free(newbuf_t);
Bram Moolenaarb005cd82019-09-04 15:54:55 +020059 return FAIL;
Bram Moolenaar3f396972019-10-30 04:10:06 +010060 }
Bram Moolenaar26262f82019-09-04 20:59:15 +020061 // Really should always succeed, as the buffer is big enough.
Bram Moolenaar3f396972019-10-30 04:10:06 +010062 l = GetShortPathNameW(wfname, newbuf, l+1);
Bram Moolenaarb005cd82019-09-04 15:54:55 +020063 }
Bram Moolenaar3f396972019-10-30 04:10:06 +010064 if (l != 0)
65 {
66 char_u *p = utf16_to_enc(newbuf, NULL);
Bram Moolenaarc74fbfe2020-04-06 22:56:28 +020067
Bram Moolenaar3f396972019-10-30 04:10:06 +010068 if (p != NULL)
69 {
70 vim_free(*bufp);
71 *fnamep = *bufp = p;
72 }
73 else
74 {
75 vim_free(wfname);
76 vim_free(newbuf);
77 return FAIL;
78 }
79 }
80 vim_free(wfname);
81 vim_free(newbuf);
Bram Moolenaarb005cd82019-09-04 15:54:55 +020082
Yegappan Lakshmanan00d34592024-12-25 10:20:51 +010083 *fnamelen = l == 0 ? l : STRLEN(*bufp);
Bram Moolenaarb005cd82019-09-04 15:54:55 +020084 return OK;
85}
86
87/*
88 * Get the short path (8.3) for the filename in "fname". The converted
89 * path is returned in "bufp".
90 *
91 * Some of the directories specified in "fname" may not exist. This function
92 * will shorten the existing directories at the beginning of the path and then
93 * append the remaining non-existing path.
94 *
95 * fname - Pointer to the filename to shorten. On return, contains the
96 * pointer to the shortened pathname
97 * bufp - Pointer to an allocated buffer for the filename.
98 * fnamelen - Length of the filename pointed to by fname
99 *
100 * Returns OK on success (or nothing done) and FAIL on failure (out of memory).
101 */
102 static int
103shortpath_for_invalid_fname(
104 char_u **fname,
105 char_u **bufp,
Yegappan Lakshmanan00d34592024-12-25 10:20:51 +0100106 size_t *fnamelen)
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200107{
John Marriottc0072982025-03-17 21:14:17 +0100108 char_u *save_fname;
109 char_u *pbuf_unused = NULL;
110 char_u *short_fname = NULL;
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200111 char_u *endp, *save_endp;
112 char_u ch;
Yegappan Lakshmanan00d34592024-12-25 10:20:51 +0100113 size_t old_len;
114 size_t len;
115 size_t new_len, sfx_len;
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200116 int retval = OK;
117
Bram Moolenaar26262f82019-09-04 20:59:15 +0200118 // Make a copy
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200119 old_len = *fnamelen;
120 save_fname = vim_strnsave(*fname, old_len);
John Marriottc0072982025-03-17 21:14:17 +0100121 if (save_fname == NULL)
122 {
123 retval = FAIL;
124 goto theend;
125 }
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200126
Bram Moolenaar26262f82019-09-04 20:59:15 +0200127 endp = save_fname + old_len - 1; // Find the end of the copy
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200128 save_endp = endp;
129
130 /*
131 * Try shortening the supplied path till it succeeds by removing one
132 * directory at a time from the tail of the path.
133 */
134 len = 0;
135 for (;;)
136 {
Bram Moolenaar26262f82019-09-04 20:59:15 +0200137 // go back one path-separator
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200138 while (endp > save_fname && !after_pathsep(save_fname, endp + 1))
139 --endp;
140 if (endp <= save_fname)
Bram Moolenaar26262f82019-09-04 20:59:15 +0200141 break; // processed the complete path
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200142
143 /*
144 * Replace the path separator with a NUL and try to shorten the
145 * resulting path.
146 */
147 ch = *endp;
Christian Brabandt6cc30272024-12-13 17:54:33 +0100148 *endp = 0;
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200149 short_fname = save_fname;
Yegappan Lakshmanan00d34592024-12-25 10:20:51 +0100150 len = STRLEN(short_fname) + 1;
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200151 if (get_short_pathname(&short_fname, &pbuf_unused, &len) == FAIL)
152 {
153 retval = FAIL;
154 goto theend;
155 }
Bram Moolenaar26262f82019-09-04 20:59:15 +0200156 *endp = ch; // preserve the string
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200157
158 if (len > 0)
Bram Moolenaar26262f82019-09-04 20:59:15 +0200159 break; // successfully shortened the path
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200160
Bram Moolenaar26262f82019-09-04 20:59:15 +0200161 // failed to shorten the path. Skip the path separator
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200162 --endp;
163 }
164
165 if (len > 0)
166 {
167 /*
168 * Succeeded in shortening the path. Now concatenate the shortened
169 * path with the remaining path at the tail.
170 */
171
Bram Moolenaar217e1b82019-12-01 21:41:28 +0100172 // Compute the length of the new path.
Yegappan Lakshmanan00d34592024-12-25 10:20:51 +0100173 sfx_len = (save_endp - endp) + 1;
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200174 new_len = len + sfx_len;
175
176 *fnamelen = new_len;
177 vim_free(*bufp);
178 if (new_len > old_len)
179 {
Bram Moolenaar26262f82019-09-04 20:59:15 +0200180 // There is not enough space in the currently allocated string,
181 // copy it to a buffer big enough.
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200182 *fname = *bufp = vim_strnsave(short_fname, new_len);
183 if (*fname == NULL)
184 {
185 retval = FAIL;
186 goto theend;
187 }
188 }
189 else
190 {
Bram Moolenaar26262f82019-09-04 20:59:15 +0200191 // Transfer short_fname to the main buffer (it's big enough),
192 // unless get_short_pathname() did its work in-place.
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200193 *fname = *bufp = save_fname;
194 if (short_fname != save_fname)
Christian Brabandt81da16b2022-03-10 12:24:02 +0000195 STRNCPY(save_fname, short_fname, len);
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200196 save_fname = NULL;
197 }
198
Bram Moolenaar26262f82019-09-04 20:59:15 +0200199 // concat the not-shortened part of the path
Yegappan Lakshmanana34b4462022-06-11 10:43:26 +0100200 if ((*fname + len) != endp)
201 vim_strncpy(*fname + len, endp, sfx_len);
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200202 (*fname)[new_len] = NUL;
203 }
204
205theend:
206 vim_free(pbuf_unused);
207 vim_free(save_fname);
208
209 return retval;
210}
211
212/*
213 * Get a pathname for a partial path.
214 * Returns OK for success, FAIL for failure.
215 */
216 static int
217shortpath_for_partial(
218 char_u **fnamep,
219 char_u **bufp,
Yegappan Lakshmanan00d34592024-12-25 10:20:51 +0100220 size_t *fnamelen)
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200221{
Yegappan Lakshmanan00d34592024-12-25 10:20:51 +0100222 int sepcount;
223 size_t len, tflen;
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200224 char_u *p;
225 char_u *pbuf, *tfname;
226 int hasTilde;
227
Bram Moolenaar26262f82019-09-04 20:59:15 +0200228 // Count up the path separators from the RHS.. so we know which part
229 // of the path to return.
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200230 sepcount = 0;
231 for (p = *fnamep; p < *fnamep + *fnamelen; MB_PTR_ADV(p))
232 if (vim_ispathsep(*p))
233 ++sepcount;
234
Christian Brabandt6cc30272024-12-13 17:54:33 +0100235 // Need full path first (use expand_env() to remove a "~/")
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200236 hasTilde = (**fnamep == '~');
237 if (hasTilde)
238 pbuf = tfname = expand_env_save(*fnamep);
239 else
240 pbuf = tfname = FullName_save(*fnamep, FALSE);
John Marriottc0072982025-03-17 21:14:17 +0100241 if (tfname == NULL)
242 return FAIL;
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200243
Yegappan Lakshmanan00d34592024-12-25 10:20:51 +0100244 len = tflen = STRLEN(tfname);
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200245
246 if (get_short_pathname(&tfname, &pbuf, &len) == FAIL)
247 return FAIL;
248
249 if (len == 0)
250 {
Bram Moolenaar26262f82019-09-04 20:59:15 +0200251 // Don't have a valid filename, so shorten the rest of the
252 // path if we can. This CAN give us invalid 8.3 filenames, but
253 // there's not a lot of point in guessing what it might be.
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200254 len = tflen;
255 if (shortpath_for_invalid_fname(&tfname, &pbuf, &len) == FAIL)
256 return FAIL;
257 }
258
Bram Moolenaar26262f82019-09-04 20:59:15 +0200259 // Count the paths backward to find the beginning of the desired string.
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200260 for (p = tfname + len - 1; p >= tfname; --p)
261 {
262 if (has_mbyte)
263 p -= mb_head_off(tfname, p);
264 if (vim_ispathsep(*p))
265 {
266 if (sepcount == 0 || (hasTilde && sepcount == 1))
267 break;
268 else
269 sepcount --;
270 }
271 }
272 if (hasTilde)
273 {
274 --p;
275 if (p >= tfname)
276 *p = '~';
277 else
278 return FAIL;
279 }
280 else
281 ++p;
282
Bram Moolenaar26262f82019-09-04 20:59:15 +0200283 // Copy in the string - p indexes into tfname - allocated at pbuf
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200284 vim_free(*bufp);
Yegappan Lakshmanan00d34592024-12-25 10:20:51 +0100285 *fnamelen = STRLEN(p);
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200286 *bufp = pbuf;
287 *fnamep = p;
288
289 return OK;
290}
291#endif // MSWIN
292
293/*
294 * Adjust a filename, according to a string of modifiers.
295 * *fnamep must be NUL terminated when called. When returning, the length is
296 * determined by *fnamelen.
297 * Returns VALID_ flags or -1 for failure.
298 * When there is an error, *fnamep is set to NULL.
299 */
300 int
301modify_fname(
302 char_u *src, // string with modifiers
303 int tilde_file, // "~" is a file name, not $HOME
Mike Williams51024bb2024-05-30 07:46:30 +0200304 size_t *usedlen, // characters after src that are used
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200305 char_u **fnamep, // file name so far
306 char_u **bufp, // buffer for allocated file name or NULL
Yegappan Lakshmanan00d34592024-12-25 10:20:51 +0100307 size_t *fnamelen) // length of fnamep
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200308{
309 int valid = 0;
310 char_u *tail;
311 char_u *s, *p, *pbuf;
312 char_u dirname[MAXPATHL];
313 int c;
314 int has_fullname = 0;
Bram Moolenaard816cd92020-02-04 22:23:09 +0100315 int has_homerelative = 0;
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200316#ifdef MSWIN
317 char_u *fname_start = *fnamep;
318 int has_shortname = 0;
319#endif
320
321repeat:
Bram Moolenaar26262f82019-09-04 20:59:15 +0200322 // ":p" - full path/file_name
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200323 if (src[*usedlen] == ':' && src[*usedlen + 1] == 'p')
324 {
325 has_fullname = 1;
326
327 valid |= VALID_PATH;
328 *usedlen += 2;
329
Bram Moolenaar26262f82019-09-04 20:59:15 +0200330 // Expand "~/path" for all systems and "~user/path" for Unix and VMS
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200331 if ((*fnamep)[0] == '~'
332#if !defined(UNIX) && !(defined(VMS) && defined(USER_HOME))
333 && ((*fnamep)[1] == '/'
334# ifdef BACKSLASH_IN_FILENAME
335 || (*fnamep)[1] == '\\'
336# endif
337 || (*fnamep)[1] == NUL)
338#endif
339 && !(tilde_file && (*fnamep)[1] == NUL)
340 )
341 {
342 *fnamep = expand_env_save(*fnamep);
Bram Moolenaar26262f82019-09-04 20:59:15 +0200343 vim_free(*bufp); // free any allocated file name
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200344 *bufp = *fnamep;
345 if (*fnamep == NULL)
346 return -1;
347 }
348
Bram Moolenaar26262f82019-09-04 20:59:15 +0200349 // When "/." or "/.." is used: force expansion to get rid of it.
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200350 for (p = *fnamep; *p != NUL; MB_PTR_ADV(p))
351 {
352 if (vim_ispathsep(*p)
353 && p[1] == '.'
354 && (p[2] == NUL
355 || vim_ispathsep(p[2])
356 || (p[2] == '.'
357 && (p[3] == NUL || vim_ispathsep(p[3])))))
358 break;
359 }
360
Bram Moolenaar26262f82019-09-04 20:59:15 +0200361 // FullName_save() is slow, don't use it when not needed.
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200362 if (*p != NUL || !vim_isAbsName(*fnamep))
363 {
364 *fnamep = FullName_save(*fnamep, *p != NUL);
Bram Moolenaar26262f82019-09-04 20:59:15 +0200365 vim_free(*bufp); // free any allocated file name
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200366 *bufp = *fnamep;
367 if (*fnamep == NULL)
368 return -1;
369 }
370
371#ifdef MSWIN
372# if _WIN32_WINNT >= 0x0500
373 if (vim_strchr(*fnamep, '~') != NULL)
374 {
375 // Expand 8.3 filename to full path. Needed to make sure the same
376 // file does not have two different names.
377 // Note: problem does not occur if _WIN32_WINNT < 0x0500.
378 WCHAR *wfname = enc_to_utf16(*fnamep, NULL);
379 WCHAR buf[_MAX_PATH];
380
381 if (wfname != NULL)
382 {
383 if (GetLongPathNameW(wfname, buf, _MAX_PATH))
384 {
K.Takata54119102022-02-03 13:33:03 +0000385 char_u *q = utf16_to_enc(buf, NULL);
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200386
K.Takata54119102022-02-03 13:33:03 +0000387 if (q != NULL)
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200388 {
389 vim_free(*bufp); // free any allocated file name
K.Takata54119102022-02-03 13:33:03 +0000390 *bufp = *fnamep = q;
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200391 }
392 }
393 vim_free(wfname);
394 }
395 }
396# endif
397#endif
Bram Moolenaar26262f82019-09-04 20:59:15 +0200398 // Append a path separator to a directory.
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200399 if (mch_isdir(*fnamep))
400 {
Bram Moolenaar26262f82019-09-04 20:59:15 +0200401 // Make room for one or two extra characters.
Bram Moolenaar71ccd032020-06-12 22:59:11 +0200402 *fnamep = vim_strnsave(*fnamep, STRLEN(*fnamep) + 2);
Bram Moolenaar26262f82019-09-04 20:59:15 +0200403 vim_free(*bufp); // free any allocated file name
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200404 *bufp = *fnamep;
405 if (*fnamep == NULL)
406 return -1;
407 add_pathsep(*fnamep);
408 }
409 }
410
Bram Moolenaar26262f82019-09-04 20:59:15 +0200411 // ":." - path relative to the current directory
412 // ":~" - path relative to the home directory
413 // ":8" - shortname path - postponed till after
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200414 while (src[*usedlen] == ':'
415 && ((c = src[*usedlen + 1]) == '.' || c == '~' || c == '8'))
416 {
417 *usedlen += 2;
418 if (c == '8')
419 {
420#ifdef MSWIN
Bram Moolenaar26262f82019-09-04 20:59:15 +0200421 has_shortname = 1; // Postpone this.
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200422#endif
423 continue;
424 }
425 pbuf = NULL;
Christian Brabandt6cc30272024-12-13 17:54:33 +0100426 // Need full path first (use expand_env() to remove a "~/")
Bram Moolenaard816cd92020-02-04 22:23:09 +0100427 if (!has_fullname && !has_homerelative)
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200428 {
=?UTF-8?q?Dundar=20G=C3=B6c?=78a84042022-02-09 15:20:39 +0000429 if (**fnamep == '~')
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200430 p = pbuf = expand_env_save(*fnamep);
431 else
432 p = pbuf = FullName_save(*fnamep, FALSE);
433 }
434 else
435 p = *fnamep;
436
437 has_fullname = 0;
438
439 if (p != NULL)
440 {
441 if (c == '.')
442 {
Bram Moolenaard816cd92020-02-04 22:23:09 +0100443 size_t namelen;
444
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200445 mch_dirname(dirname, MAXPATHL);
Bram Moolenaard816cd92020-02-04 22:23:09 +0100446 if (has_homerelative)
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200447 {
Bram Moolenaard816cd92020-02-04 22:23:09 +0100448 s = vim_strsave(dirname);
449 if (s != NULL)
450 {
451 home_replace(NULL, s, dirname, MAXPATHL, TRUE);
452 vim_free(s);
453 }
454 }
455 namelen = STRLEN(dirname);
456
457 // Do not call shorten_fname() here since it removes the prefix
458 // even though the path does not have a prefix.
459 if (fnamencmp(p, dirname, namelen) == 0)
460 {
461 p += namelen;
Bram Moolenaara78e9c62020-02-05 21:14:00 +0100462 if (vim_ispathsep(*p))
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200463 {
Bram Moolenaara78e9c62020-02-05 21:14:00 +0100464 while (*p && vim_ispathsep(*p))
465 ++p;
466 *fnamep = p;
467 if (pbuf != NULL)
468 {
469 // free any allocated file name
470 vim_free(*bufp);
471 *bufp = pbuf;
472 pbuf = NULL;
473 }
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200474 }
475 }
476 }
477 else
478 {
479 home_replace(NULL, p, dirname, MAXPATHL, TRUE);
Bram Moolenaar26262f82019-09-04 20:59:15 +0200480 // Only replace it when it starts with '~'
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200481 if (*dirname == '~')
482 {
483 s = vim_strsave(dirname);
484 if (s != NULL)
485 {
486 *fnamep = s;
487 vim_free(*bufp);
488 *bufp = s;
Bram Moolenaard816cd92020-02-04 22:23:09 +0100489 has_homerelative = TRUE;
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200490 }
491 }
492 }
493 vim_free(pbuf);
494 }
495 }
496
497 tail = gettail(*fnamep);
Yegappan Lakshmanan00d34592024-12-25 10:20:51 +0100498 *fnamelen = STRLEN(*fnamep);
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200499
Bram Moolenaar26262f82019-09-04 20:59:15 +0200500 // ":h" - head, remove "/file_name", can be repeated
501 // Don't remove the first "/" or "c:\"
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200502 while (src[*usedlen] == ':' && src[*usedlen + 1] == 'h')
503 {
504 valid |= VALID_HEAD;
505 *usedlen += 2;
506 s = get_past_head(*fnamep);
507 while (tail > s && after_pathsep(s, tail))
508 MB_PTR_BACK(*fnamep, tail);
Yegappan Lakshmanan00d34592024-12-25 10:20:51 +0100509 *fnamelen = tail - *fnamep;
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200510#ifdef VMS
511 if (*fnamelen > 0)
Bram Moolenaar26262f82019-09-04 20:59:15 +0200512 *fnamelen += 1; // the path separator is part of the path
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200513#endif
514 if (*fnamelen == 0)
515 {
Bram Moolenaar26262f82019-09-04 20:59:15 +0200516 // Result is empty. Turn it into "." to make ":cd %:h" work.
Christian Brabandt6cc30272024-12-13 17:54:33 +0100517 p = vim_strsave((char_u *)".");
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200518 if (p == NULL)
519 return -1;
520 vim_free(*bufp);
521 *bufp = *fnamep = tail = p;
522 *fnamelen = 1;
523 }
524 else
525 {
526 while (tail > s && !after_pathsep(s, tail))
527 MB_PTR_BACK(*fnamep, tail);
528 }
529 }
530
Bram Moolenaar26262f82019-09-04 20:59:15 +0200531 // ":8" - shortname
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200532 if (src[*usedlen] == ':' && src[*usedlen + 1] == '8')
533 {
534 *usedlen += 2;
535#ifdef MSWIN
536 has_shortname = 1;
537#endif
538 }
539
540#ifdef MSWIN
541 /*
542 * Handle ":8" after we have done 'heads' and before we do 'tails'.
543 */
544 if (has_shortname)
545 {
Bram Moolenaar26262f82019-09-04 20:59:15 +0200546 // Copy the string if it is shortened by :h and when it wasn't copied
547 // yet, because we are going to change it in place. Avoids changing
548 // the buffer name for "%:8".
Yegappan Lakshmanan00d34592024-12-25 10:20:51 +0100549 if (*fnamelen < STRLEN(*fnamep) || *fnamep == fname_start)
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200550 {
551 p = vim_strnsave(*fnamep, *fnamelen);
552 if (p == NULL)
553 return -1;
554 vim_free(*bufp);
555 *bufp = *fnamep = p;
556 }
557
Bram Moolenaar26262f82019-09-04 20:59:15 +0200558 // Split into two implementations - makes it easier. First is where
559 // there isn't a full name already, second is where there is.
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200560 if (!has_fullname && !vim_isAbsName(*fnamep))
561 {
562 if (shortpath_for_partial(fnamep, bufp, fnamelen) == FAIL)
563 return -1;
564 }
565 else
566 {
Yegappan Lakshmanan00d34592024-12-25 10:20:51 +0100567 size_t l = *fnamelen;
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200568
Bram Moolenaar26262f82019-09-04 20:59:15 +0200569 // Simple case, already have the full-name.
570 // Nearly always shorter, so try first time.
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200571 if (get_short_pathname(fnamep, bufp, &l) == FAIL)
572 return -1;
573
574 if (l == 0)
575 {
Bram Moolenaar26262f82019-09-04 20:59:15 +0200576 // Couldn't find the filename, search the paths.
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200577 l = *fnamelen;
578 if (shortpath_for_invalid_fname(fnamep, bufp, &l) == FAIL)
579 return -1;
580 }
581 *fnamelen = l;
582 }
583 }
584#endif // MSWIN
585
Bram Moolenaar26262f82019-09-04 20:59:15 +0200586 // ":t" - tail, just the basename
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200587 if (src[*usedlen] == ':' && src[*usedlen + 1] == 't')
588 {
589 *usedlen += 2;
Yegappan Lakshmanan00d34592024-12-25 10:20:51 +0100590 *fnamelen -= tail - *fnamep;
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200591 *fnamep = tail;
592 }
593
Bram Moolenaar26262f82019-09-04 20:59:15 +0200594 // ":e" - extension, can be repeated
595 // ":r" - root, without extension, can be repeated
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200596 while (src[*usedlen] == ':'
597 && (src[*usedlen + 1] == 'e' || src[*usedlen + 1] == 'r'))
598 {
Bram Moolenaar26262f82019-09-04 20:59:15 +0200599 // find a '.' in the tail:
600 // - for second :e: before the current fname
601 // - otherwise: The last '.'
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200602 if (src[*usedlen + 1] == 'e' && *fnamep > tail)
603 s = *fnamep - 2;
604 else
605 s = *fnamep + *fnamelen - 1;
606 for ( ; s > tail; --s)
607 if (s[0] == '.')
608 break;
Bram Moolenaar26262f82019-09-04 20:59:15 +0200609 if (src[*usedlen + 1] == 'e') // :e
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200610 {
611 if (s > tail)
612 {
Yegappan Lakshmanan00d34592024-12-25 10:20:51 +0100613 *fnamelen += (*fnamep - (s + 1));
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200614 *fnamep = s + 1;
615#ifdef VMS
Bram Moolenaar26262f82019-09-04 20:59:15 +0200616 // cut version from the extension
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200617 s = *fnamep + *fnamelen - 1;
618 for ( ; s > *fnamep; --s)
619 if (s[0] == ';')
620 break;
621 if (s > *fnamep)
622 *fnamelen = s - *fnamep;
623#endif
624 }
625 else if (*fnamep <= tail)
626 *fnamelen = 0;
627 }
Bram Moolenaar26262f82019-09-04 20:59:15 +0200628 else // :r
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200629 {
Bram Moolenaarb1892952019-10-08 23:26:50 +0200630 char_u *limit = *fnamep;
631
632 if (limit < tail)
633 limit = tail;
634 if (s > limit) // remove one extension
Yegappan Lakshmanan00d34592024-12-25 10:20:51 +0100635 *fnamelen = s - *fnamep;
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200636 }
637 *usedlen += 2;
638 }
639
Bram Moolenaar26262f82019-09-04 20:59:15 +0200640 // ":s?pat?foo?" - substitute
641 // ":gs?pat?foo?" - global substitute
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200642 if (src[*usedlen] == ':'
643 && (src[*usedlen + 1] == 's'
644 || (src[*usedlen + 1] == 'g' && src[*usedlen + 2] == 's')))
645 {
646 char_u *str;
647 char_u *pat;
648 char_u *sub;
649 int sep;
650 char_u *flags;
651 int didit = FALSE;
652
653 flags = (char_u *)"";
654 s = src + *usedlen + 2;
655 if (src[*usedlen + 1] == 'g')
656 {
657 flags = (char_u *)"g";
658 ++s;
659 }
660
661 sep = *s++;
662 if (sep)
663 {
Bram Moolenaar26262f82019-09-04 20:59:15 +0200664 // find end of pattern
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200665 p = vim_strchr(s, sep);
666 if (p != NULL)
667 {
Bram Moolenaar71ccd032020-06-12 22:59:11 +0200668 pat = vim_strnsave(s, p - s);
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200669 if (pat != NULL)
670 {
671 s = p + 1;
Bram Moolenaar26262f82019-09-04 20:59:15 +0200672 // find end of substitution
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200673 p = vim_strchr(s, sep);
674 if (p != NULL)
675 {
Bram Moolenaar71ccd032020-06-12 22:59:11 +0200676 sub = vim_strnsave(s, p - s);
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200677 str = vim_strnsave(*fnamep, *fnamelen);
678 if (sub != NULL && str != NULL)
679 {
John Marriottbd4614f2024-11-18 20:25:21 +0100680 size_t slen;
681
Mike Williams51024bb2024-05-30 07:46:30 +0200682 *usedlen = p + 1 - src;
John Marriottbd4614f2024-11-18 20:25:21 +0100683 s = do_string_sub(str, *fnamelen, pat, sub, NULL, flags, &slen);
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200684 if (s != NULL)
685 {
686 *fnamep = s;
Yegappan Lakshmanan00d34592024-12-25 10:20:51 +0100687 *fnamelen = slen;
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200688 vim_free(*bufp);
689 *bufp = s;
690 didit = TRUE;
691 }
692 }
693 vim_free(sub);
694 vim_free(str);
695 }
696 vim_free(pat);
697 }
698 }
Bram Moolenaar26262f82019-09-04 20:59:15 +0200699 // after using ":s", repeat all the modifiers
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200700 if (didit)
701 goto repeat;
702 }
703 }
704
705 if (src[*usedlen] == ':' && src[*usedlen + 1] == 'S')
706 {
Bram Moolenaar26262f82019-09-04 20:59:15 +0200707 // vim_strsave_shellescape() needs a NUL terminated string.
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200708 c = (*fnamep)[*fnamelen];
709 if (c != NUL)
710 (*fnamep)[*fnamelen] = NUL;
711 p = vim_strsave_shellescape(*fnamep, FALSE, FALSE);
712 if (c != NUL)
713 (*fnamep)[*fnamelen] = c;
714 if (p == NULL)
715 return -1;
716 vim_free(*bufp);
717 *bufp = *fnamep = p;
Yegappan Lakshmanan00d34592024-12-25 10:20:51 +0100718 *fnamelen = STRLEN(p);
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200719 *usedlen += 2;
720 }
721
722 return valid;
723}
724
Bram Moolenaar273af492020-09-25 23:49:01 +0200725/*
726 * Shorten the path of a file from "~/foo/../.bar/fname" to "~/f/../.b/fname"
727 * "trim_len" specifies how many characters to keep for each directory.
728 * Must be 1 or more.
729 * It's done in-place.
730 */
731 static void
732shorten_dir_len(char_u *str, int trim_len)
733{
734 char_u *tail, *s, *d;
735 int skip = FALSE;
736 int dirchunk_len = 0;
737
738 tail = gettail(str);
739 d = str;
740 for (s = str; ; ++s)
741 {
742 if (s >= tail) // copy the whole tail
743 {
744 *d++ = *s;
745 if (*s == NUL)
746 break;
747 }
748 else if (vim_ispathsep(*s)) // copy '/' and next char
749 {
750 *d++ = *s;
751 skip = FALSE;
752 dirchunk_len = 0;
753 }
754 else if (!skip)
755 {
756 *d++ = *s; // copy next char
757 if (*s != '~' && *s != '.') // and leading "~" and "."
758 {
759 ++dirchunk_len; // only count word chars for the size
760
761 // keep copying chars until we have our preferred length (or
762 // until the above if/else branches move us along)
763 if (dirchunk_len >= trim_len)
764 skip = TRUE;
765 }
766
767 if (has_mbyte)
768 {
769 int l = mb_ptr2len(s);
770
771 while (--l > 0)
772 *d++ = *++s;
773 }
774 }
775 }
776}
777
778/*
779 * Shorten the path of a file from "~/foo/../.bar/fname" to "~/f/../.b/fname"
780 * It's done in-place.
781 */
782 void
783shorten_dir(char_u *str)
784{
785 shorten_dir_len(str, 1);
786}
787
Bram Moolenaar022f9ef2022-07-02 17:36:31 +0100788/*
789 * Return TRUE if "fname" is a readable file.
790 */
791 int
792file_is_readable(char_u *fname)
793{
794 int fd;
795
796#ifndef O_NONBLOCK
797# define O_NONBLOCK 0
798#endif
799 if (*fname && !mch_isdir(fname)
800 && (fd = mch_open((char *)fname, O_RDONLY | O_NONBLOCK, 0)) >= 0)
801 {
802 close(fd);
803 return TRUE;
804 }
805 return FALSE;
806}
807
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200808#if defined(FEAT_EVAL) || defined(PROTO)
809
810/*
811 * "chdir(dir)" function
812 */
813 void
814f_chdir(typval_T *argvars, typval_T *rettv)
815{
816 char_u *cwd;
817 cdscope_T scope = CDSCOPE_GLOBAL;
818
819 rettv->v_type = VAR_STRING;
820 rettv->vval.v_string = NULL;
821
822 if (argvars[0].v_type != VAR_STRING)
Bram Moolenaarc5809432021-03-27 21:23:30 +0100823 {
Bram Moolenaard816cd92020-02-04 22:23:09 +0100824 // Returning an empty string means it failed.
Bram Moolenaar002bc792020-06-05 22:33:42 +0200825 // No error message, for historic reasons.
Bram Moolenaarc5809432021-03-27 21:23:30 +0100826 if (in_vim9script())
827 (void) check_for_string_arg(argvars, 0);
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200828 return;
Bram Moolenaarc5809432021-03-27 21:23:30 +0100829 }
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200830
831 // Return the current directory
832 cwd = alloc(MAXPATHL);
833 if (cwd != NULL)
834 {
835 if (mch_dirname(cwd, MAXPATHL) != FAIL)
836 {
837#ifdef BACKSLASH_IN_FILENAME
838 slash_adjust(cwd);
839#endif
840 rettv->vval.v_string = vim_strsave(cwd);
841 }
842 vim_free(cwd);
843 }
844
845 if (curwin->w_localdir != NULL)
846 scope = CDSCOPE_WINDOW;
847 else if (curtab->tp_localdir != NULL)
848 scope = CDSCOPE_TABPAGE;
849
850 if (!changedir_func(argvars[0].vval.v_string, TRUE, scope))
851 // Directory change failed
852 VIM_CLEAR(rettv->vval.v_string);
853}
854
855/*
856 * "delete()" function
857 */
858 void
859f_delete(typval_T *argvars, typval_T *rettv)
860{
861 char_u nbuf[NUMBUFLEN];
862 char_u *name;
863 char_u *flags;
864
865 rettv->vval.v_number = -1;
866 if (check_restricted() || check_secure())
867 return;
868
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +0200869 if (in_vim9script()
870 && (check_for_string_arg(argvars, 0) == FAIL
871 || check_for_opt_string_arg(argvars, 1) == FAIL))
872 return;
873
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200874 name = tv_get_string(&argvars[0]);
875 if (name == NULL || *name == NUL)
876 {
Bram Moolenaar436b5ad2021-12-31 22:49:24 +0000877 emsg(_(e_invalid_argument));
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200878 return;
879 }
880
881 if (argvars[1].v_type != VAR_UNKNOWN)
882 flags = tv_get_string_buf(&argvars[1], nbuf);
883 else
884 flags = (char_u *)"";
885
886 if (*flags == NUL)
Bram Moolenaar26262f82019-09-04 20:59:15 +0200887 // delete a file
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200888 rettv->vval.v_number = mch_remove(name) == 0 ? 0 : -1;
889 else if (STRCMP(flags, "d") == 0)
Bram Moolenaar26262f82019-09-04 20:59:15 +0200890 // delete an empty directory
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200891 rettv->vval.v_number = mch_rmdir(name) == 0 ? 0 : -1;
892 else if (STRCMP(flags, "rf") == 0)
Bram Moolenaar26262f82019-09-04 20:59:15 +0200893 // delete a directory recursively
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200894 rettv->vval.v_number = delete_recursive(name);
895 else
Bram Moolenaar108010a2021-06-27 22:03:33 +0200896 semsg(_(e_invalid_expression_str), flags);
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200897}
898
899/*
900 * "executable()" function
901 */
902 void
903f_executable(typval_T *argvars, typval_T *rettv)
904{
Bram Moolenaar32105ae2021-03-27 18:59:25 +0100905 if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL)
Bram Moolenaar7bb4e742020-12-09 12:41:50 +0100906 return;
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200907
Bram Moolenaar26262f82019-09-04 20:59:15 +0200908 // Check in $PATH and also check directly if there is a directory name.
Bram Moolenaar7bb4e742020-12-09 12:41:50 +0100909 rettv->vval.v_number = mch_can_exe(tv_get_string(&argvars[0]), NULL, TRUE);
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200910}
911
912/*
913 * "exepath()" function
914 */
915 void
916f_exepath(typval_T *argvars, typval_T *rettv)
917{
918 char_u *p = NULL;
919
Bram Moolenaar32105ae2021-03-27 18:59:25 +0100920 if (in_vim9script() && check_for_nonempty_string_arg(argvars, 0) == FAIL)
Bram Moolenaar7bb4e742020-12-09 12:41:50 +0100921 return;
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200922 (void)mch_can_exe(tv_get_string(&argvars[0]), &p, TRUE);
923 rettv->v_type = VAR_STRING;
924 rettv->vval.v_string = p;
925}
926
927/*
928 * "filereadable()" function
929 */
930 void
931f_filereadable(typval_T *argvars, typval_T *rettv)
932{
Bram Moolenaar32105ae2021-03-27 18:59:25 +0100933 if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL)
Bram Moolenaar7bb4e742020-12-09 12:41:50 +0100934 return;
Bram Moolenaar4dea2d92022-03-31 11:37:57 +0100935 rettv->vval.v_number = file_is_readable(tv_get_string(&argvars[0]));
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200936}
937
938/*
939 * Return 0 for not writable, 1 for writable file, 2 for a dir which we have
940 * rights to write into.
941 */
942 void
943f_filewritable(typval_T *argvars, typval_T *rettv)
944{
Bram Moolenaar32105ae2021-03-27 18:59:25 +0100945 if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL)
Bram Moolenaar7bb4e742020-12-09 12:41:50 +0100946 return;
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200947 rettv->vval.v_number = filewritable(tv_get_string(&argvars[0]));
948}
949
Bram Moolenaar840d16f2019-09-10 21:27:18 +0200950 static void
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200951findfilendir(
Yee Cheng China7767072023-04-16 20:13:12 +0100952 typval_T *argvars,
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200953 typval_T *rettv,
Yee Cheng China7767072023-04-16 20:13:12 +0100954 int find_what)
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200955{
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200956 char_u *fname;
957 char_u *fresult = NULL;
958 char_u *path = *curbuf->b_p_path == NUL ? p_path : curbuf->b_p_path;
959 char_u *p;
960 char_u pathbuf[NUMBUFLEN];
961 int count = 1;
962 int first = TRUE;
963 int error = FALSE;
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200964
965 rettv->vval.v_string = NULL;
966 rettv->v_type = VAR_STRING;
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +0200967 if (in_vim9script()
968 && (check_for_nonempty_string_arg(argvars, 0) == FAIL
969 || check_for_opt_string_arg(argvars, 1) == FAIL
970 || (argvars[1].v_type != VAR_UNKNOWN
971 && check_for_opt_number_arg(argvars, 2) == FAIL)))
Bram Moolenaar7bb4e742020-12-09 12:41:50 +0100972 return;
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200973
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200974 fname = tv_get_string(&argvars[0]);
975
976 if (argvars[1].v_type != VAR_UNKNOWN)
977 {
978 p = tv_get_string_buf_chk(&argvars[1], pathbuf);
979 if (p == NULL)
980 error = TRUE;
981 else
982 {
983 if (*p != NUL)
984 path = p;
985
986 if (argvars[2].v_type != VAR_UNKNOWN)
987 count = (int)tv_get_number_chk(&argvars[2], &error);
988 }
989 }
990
991 if (count < 0 && rettv_list_alloc(rettv) == FAIL)
992 error = TRUE;
993
994 if (*fname != NUL && !error)
995 {
Bram Moolenaar5145c9a2023-03-11 13:55:53 +0000996 char_u *file_to_find = NULL;
997 char *search_ctx = NULL;
998
Bram Moolenaarb005cd82019-09-04 15:54:55 +0200999 do
1000 {
1001 if (rettv->v_type == VAR_STRING || rettv->v_type == VAR_LIST)
1002 vim_free(fresult);
1003 fresult = find_file_in_path_option(first ? fname : NULL,
1004 first ? (int)STRLEN(fname) : 0,
1005 0, first, path,
1006 find_what,
1007 curbuf->b_ffname,
1008 find_what == FINDFILE_DIR
Bram Moolenaar5145c9a2023-03-11 13:55:53 +00001009 ? (char_u *)"" : curbuf->b_p_sua,
1010 &file_to_find, &search_ctx);
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001011 first = FALSE;
1012
1013 if (fresult != NULL && rettv->v_type == VAR_LIST)
1014 list_append_string(rettv->vval.v_list, fresult, -1);
1015
1016 } while ((rettv->v_type == VAR_LIST || --count > 0) && fresult != NULL);
Bram Moolenaar5145c9a2023-03-11 13:55:53 +00001017
1018 vim_free(file_to_find);
1019 vim_findfile_cleanup(search_ctx);
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001020 }
1021
1022 if (rettv->v_type == VAR_STRING)
1023 rettv->vval.v_string = fresult;
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001024}
1025
1026/*
1027 * "finddir({fname}[, {path}[, {count}]])" function
1028 */
1029 void
1030f_finddir(typval_T *argvars, typval_T *rettv)
1031{
1032 findfilendir(argvars, rettv, FINDFILE_DIR);
1033}
1034
1035/*
1036 * "findfile({fname}[, {path}[, {count}]])" function
1037 */
1038 void
1039f_findfile(typval_T *argvars, typval_T *rettv)
1040{
1041 findfilendir(argvars, rettv, FINDFILE_FILE);
1042}
1043
1044/*
1045 * "fnamemodify({fname}, {mods})" function
1046 */
1047 void
1048f_fnamemodify(typval_T *argvars, typval_T *rettv)
1049{
1050 char_u *fname;
1051 char_u *mods;
Mike Williams51024bb2024-05-30 07:46:30 +02001052 size_t usedlen = 0;
Yegappan Lakshmanan00d34592024-12-25 10:20:51 +01001053 size_t len = 0;
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001054 char_u *fbuf = NULL;
1055 char_u buf[NUMBUFLEN];
1056
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +02001057 if (in_vim9script()
1058 && (check_for_string_arg(argvars, 0) == FAIL
1059 || check_for_string_arg(argvars, 1) == FAIL))
Bram Moolenaar7bb4e742020-12-09 12:41:50 +01001060 return;
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +02001061
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001062 fname = tv_get_string_chk(&argvars[0]);
1063 mods = tv_get_string_buf_chk(&argvars[1], buf);
Bram Moolenaarc5308522020-12-13 12:25:35 +01001064 if (mods == NULL || fname == NULL)
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001065 fname = NULL;
Bram Moolenaarc5308522020-12-13 12:25:35 +01001066 else
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001067 {
Yegappan Lakshmanan00d34592024-12-25 10:20:51 +01001068 len = STRLEN(fname);
Bram Moolenaarc5308522020-12-13 12:25:35 +01001069 if (mods != NULL && *mods != NUL)
1070 (void)modify_fname(mods, FALSE, &usedlen, &fname, &fbuf, &len);
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001071 }
1072
1073 rettv->v_type = VAR_STRING;
1074 if (fname == NULL)
1075 rettv->vval.v_string = NULL;
1076 else
1077 rettv->vval.v_string = vim_strnsave(fname, len);
1078 vim_free(fbuf);
1079}
1080
1081/*
1082 * "getcwd()" function
1083 *
1084 * Return the current working directory of a window in a tab page.
1085 * First optional argument 'winnr' is the window number or -1 and the second
1086 * optional argument 'tabnr' is the tab page number.
1087 *
1088 * If no arguments are supplied, then return the directory of the current
1089 * window.
1090 * If only 'winnr' is specified and is not -1 or 0 then return the directory of
1091 * the specified window.
1092 * If 'winnr' is 0 then return the directory of the current window.
1093 * If both 'winnr and 'tabnr' are specified and 'winnr' is -1 then return the
1094 * directory of the specified tab page. Otherwise return the directory of the
1095 * specified window in the specified tab page.
1096 * If the window or the tab page doesn't exist then return NULL.
1097 */
1098 void
1099f_getcwd(typval_T *argvars, typval_T *rettv)
1100{
1101 win_T *wp = NULL;
1102 tabpage_T *tp = NULL;
1103 char_u *cwd;
1104 int global = FALSE;
1105
1106 rettv->v_type = VAR_STRING;
1107 rettv->vval.v_string = NULL;
1108
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +02001109 if (in_vim9script()
1110 && (check_for_opt_number_arg(argvars, 0) == FAIL
1111 || (argvars[0].v_type != VAR_UNKNOWN
1112 && check_for_opt_number_arg(argvars, 1) == FAIL)))
1113 return;
1114
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001115 if (argvars[0].v_type == VAR_NUMBER
1116 && argvars[0].vval.v_number == -1
1117 && argvars[1].v_type == VAR_UNKNOWN)
1118 global = TRUE;
1119 else
1120 wp = find_tabwin(&argvars[0], &argvars[1], &tp);
1121
Bram Moolenaar851c7a62021-11-18 20:47:31 +00001122 if (wp != NULL && wp->w_localdir != NULL
1123 && argvars[0].v_type != VAR_UNKNOWN)
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001124 rettv->vval.v_string = vim_strsave(wp->w_localdir);
Bram Moolenaar851c7a62021-11-18 20:47:31 +00001125 else if (tp != NULL && tp->tp_localdir != NULL
1126 && argvars[0].v_type != VAR_UNKNOWN)
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001127 rettv->vval.v_string = vim_strsave(tp->tp_localdir);
1128 else if (wp != NULL || tp != NULL || global)
1129 {
Bram Moolenaar851c7a62021-11-18 20:47:31 +00001130 if (globaldir != NULL && argvars[0].v_type != VAR_UNKNOWN)
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001131 rettv->vval.v_string = vim_strsave(globaldir);
1132 else
1133 {
1134 cwd = alloc(MAXPATHL);
1135 if (cwd != NULL)
1136 {
1137 if (mch_dirname(cwd, MAXPATHL) != FAIL)
1138 rettv->vval.v_string = vim_strsave(cwd);
1139 vim_free(cwd);
1140 }
1141 }
1142 }
1143#ifdef BACKSLASH_IN_FILENAME
1144 if (rettv->vval.v_string != NULL)
1145 slash_adjust(rettv->vval.v_string);
1146#endif
1147}
1148
1149/*
Bram Moolenaar6c9ba042020-06-01 16:09:41 +02001150 * Convert "st" to file permission string.
1151 */
1152 char_u *
1153getfpermst(stat_T *st, char_u *perm)
1154{
1155 char_u flags[] = "rwx";
1156 int i;
1157
1158 for (i = 0; i < 9; i++)
1159 {
1160 if (st->st_mode & (1 << (8 - i)))
1161 perm[i] = flags[i % 3];
1162 else
1163 perm[i] = '-';
1164 }
1165 return perm;
1166}
1167
1168/*
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001169 * "getfperm({fname})" function
1170 */
1171 void
1172f_getfperm(typval_T *argvars, typval_T *rettv)
1173{
1174 char_u *fname;
1175 stat_T st;
1176 char_u *perm = NULL;
Bram Moolenaar6c9ba042020-06-01 16:09:41 +02001177 char_u permbuf[] = "---------";
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001178
Bram Moolenaar32105ae2021-03-27 18:59:25 +01001179 if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL)
Bram Moolenaar7bb4e742020-12-09 12:41:50 +01001180 return;
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +02001181
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001182 fname = tv_get_string(&argvars[0]);
1183
1184 rettv->v_type = VAR_STRING;
1185 if (mch_stat((char *)fname, &st) >= 0)
Bram Moolenaar6c9ba042020-06-01 16:09:41 +02001186 perm = vim_strsave(getfpermst(&st, permbuf));
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001187 rettv->vval.v_string = perm;
1188}
1189
1190/*
1191 * "getfsize({fname})" function
1192 */
1193 void
1194f_getfsize(typval_T *argvars, typval_T *rettv)
1195{
1196 char_u *fname;
1197 stat_T st;
1198
Bram Moolenaar32105ae2021-03-27 18:59:25 +01001199 if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL)
Bram Moolenaar7bb4e742020-12-09 12:41:50 +01001200 return;
1201
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001202 fname = tv_get_string(&argvars[0]);
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001203 if (mch_stat((char *)fname, &st) >= 0)
1204 {
1205 if (mch_isdir(fname))
1206 rettv->vval.v_number = 0;
1207 else
1208 {
1209 rettv->vval.v_number = (varnumber_T)st.st_size;
1210
Bram Moolenaar26262f82019-09-04 20:59:15 +02001211 // non-perfect check for overflow
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001212 if ((off_T)rettv->vval.v_number != (off_T)st.st_size)
1213 rettv->vval.v_number = -2;
1214 }
1215 }
1216 else
1217 rettv->vval.v_number = -1;
1218}
1219
1220/*
1221 * "getftime({fname})" function
1222 */
1223 void
1224f_getftime(typval_T *argvars, typval_T *rettv)
1225{
1226 char_u *fname;
1227 stat_T st;
1228
Bram Moolenaar32105ae2021-03-27 18:59:25 +01001229 if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL)
Bram Moolenaar7bb4e742020-12-09 12:41:50 +01001230 return;
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +02001231
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001232 fname = tv_get_string(&argvars[0]);
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001233 if (mch_stat((char *)fname, &st) >= 0)
1234 rettv->vval.v_number = (varnumber_T)st.st_mtime;
1235 else
1236 rettv->vval.v_number = -1;
1237}
1238
1239/*
Bram Moolenaar6c9ba042020-06-01 16:09:41 +02001240 * Convert "st" to file type string.
1241 */
1242 char_u *
1243getftypest(stat_T *st)
1244{
1245 char *t;
1246
1247 if (S_ISREG(st->st_mode))
1248 t = "file";
1249 else if (S_ISDIR(st->st_mode))
1250 t = "dir";
1251 else if (S_ISLNK(st->st_mode))
1252 t = "link";
1253 else if (S_ISBLK(st->st_mode))
1254 t = "bdev";
1255 else if (S_ISCHR(st->st_mode))
1256 t = "cdev";
1257 else if (S_ISFIFO(st->st_mode))
1258 t = "fifo";
1259 else if (S_ISSOCK(st->st_mode))
1260 t = "socket";
1261 else
1262 t = "other";
1263 return (char_u*)t;
1264}
1265
1266/*
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001267 * "getftype({fname})" function
1268 */
1269 void
1270f_getftype(typval_T *argvars, typval_T *rettv)
1271{
1272 char_u *fname;
1273 stat_T st;
1274 char_u *type = NULL;
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001275
Bram Moolenaar32105ae2021-03-27 18:59:25 +01001276 if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL)
Bram Moolenaar7bb4e742020-12-09 12:41:50 +01001277 return;
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +02001278
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001279 fname = tv_get_string(&argvars[0]);
1280
1281 rettv->v_type = VAR_STRING;
1282 if (mch_lstat((char *)fname, &st) >= 0)
Bram Moolenaar6c9ba042020-06-01 16:09:41 +02001283 type = vim_strsave(getftypest(&st));
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001284 rettv->vval.v_string = type;
1285}
1286
1287/*
1288 * "glob()" function
1289 */
1290 void
1291f_glob(typval_T *argvars, typval_T *rettv)
1292{
1293 int options = WILD_SILENT|WILD_USE_NL;
1294 expand_T xpc;
1295 int error = FALSE;
1296
Yegappan Lakshmanan83494b42021-07-20 17:51:51 +02001297 if (in_vim9script()
1298 && (check_for_string_arg(argvars, 0) == FAIL
1299 || check_for_opt_bool_arg(argvars, 1) == FAIL
1300 || (argvars[1].v_type != VAR_UNKNOWN
1301 && (check_for_opt_bool_arg(argvars, 2) == FAIL
1302 || (argvars[2].v_type != VAR_UNKNOWN
1303 && check_for_opt_bool_arg(argvars, 3) == FAIL)))))
1304 return;
1305
Bram Moolenaar26262f82019-09-04 20:59:15 +02001306 // When the optional second argument is non-zero, don't remove matches
1307 // for 'wildignore' and don't put matches for 'suffixes' at the end.
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001308 rettv->v_type = VAR_STRING;
1309 if (argvars[1].v_type != VAR_UNKNOWN)
1310 {
Bram Moolenaar5892ea12020-09-02 21:53:11 +02001311 if (tv_get_bool_chk(&argvars[1], &error))
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001312 options |= WILD_KEEP_ALL;
1313 if (argvars[2].v_type != VAR_UNKNOWN)
1314 {
Bram Moolenaar5892ea12020-09-02 21:53:11 +02001315 if (tv_get_bool_chk(&argvars[2], &error))
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001316 rettv_list_set(rettv, NULL);
1317 if (argvars[3].v_type != VAR_UNKNOWN
Bram Moolenaar5892ea12020-09-02 21:53:11 +02001318 && tv_get_bool_chk(&argvars[3], &error))
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001319 options |= WILD_ALLLINKS;
1320 }
1321 }
1322 if (!error)
1323 {
1324 ExpandInit(&xpc);
1325 xpc.xp_context = EXPAND_FILES;
1326 if (p_wic)
1327 options += WILD_ICASE;
1328 if (rettv->v_type == VAR_STRING)
1329 rettv->vval.v_string = ExpandOne(&xpc, tv_get_string(&argvars[0]),
1330 NULL, options, WILD_ALL);
Bram Moolenaar8088ae92022-06-20 11:38:17 +01001331 else if (rettv_list_alloc(rettv) == OK)
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001332 {
1333 int i;
1334
1335 ExpandOne(&xpc, tv_get_string(&argvars[0]),
1336 NULL, options, WILD_ALL_KEEP);
1337 for (i = 0; i < xpc.xp_numfiles; i++)
1338 list_append_string(rettv->vval.v_list, xpc.xp_files[i], -1);
1339
1340 ExpandCleanup(&xpc);
1341 }
1342 }
1343 else
1344 rettv->vval.v_string = NULL;
1345}
1346
1347/*
1348 * "glob2regpat()" function
1349 */
1350 void
1351f_glob2regpat(typval_T *argvars, typval_T *rettv)
1352{
Bram Moolenaar3cfa5b12021-06-06 14:14:39 +02001353 char_u buf[NUMBUFLEN];
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +02001354 char_u *pat;
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001355
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +02001356 if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL)
1357 return;
1358
1359 pat = tv_get_string_buf_chk_strict(&argvars[0], buf, in_vim9script());
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001360 rettv->v_type = VAR_STRING;
1361 rettv->vval.v_string = (pat == NULL)
1362 ? NULL : file_pat_to_reg_pat(pat, NULL, NULL, FALSE);
1363}
1364
1365/*
1366 * "globpath()" function
1367 */
1368 void
1369f_globpath(typval_T *argvars, typval_T *rettv)
1370{
1371 int flags = WILD_IGNORE_COMPLETESLASH;
1372 char_u buf1[NUMBUFLEN];
Yegappan Lakshmanan83494b42021-07-20 17:51:51 +02001373 char_u *file;
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001374 int error = FALSE;
1375 garray_T ga;
1376 int i;
1377
Yegappan Lakshmanan83494b42021-07-20 17:51:51 +02001378 if (in_vim9script()
1379 && (check_for_string_arg(argvars, 0) == FAIL
1380 || check_for_string_arg(argvars, 1) == FAIL
1381 || check_for_opt_bool_arg(argvars, 2) == FAIL
1382 || (argvars[2].v_type != VAR_UNKNOWN
1383 && (check_for_opt_bool_arg(argvars, 3) == FAIL
1384 || (argvars[3].v_type != VAR_UNKNOWN
1385 && check_for_opt_bool_arg(argvars, 4) == FAIL)))))
1386 return;
1387
1388 file = tv_get_string_buf_chk(&argvars[1], buf1);
1389
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001390 // When the optional second argument is non-zero, don't remove matches
1391 // for 'wildignore' and don't put matches for 'suffixes' at the end.
1392 rettv->v_type = VAR_STRING;
1393 if (argvars[2].v_type != VAR_UNKNOWN)
1394 {
Bram Moolenaarf966ce52020-09-02 21:57:07 +02001395 if (tv_get_bool_chk(&argvars[2], &error))
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001396 flags |= WILD_KEEP_ALL;
1397 if (argvars[3].v_type != VAR_UNKNOWN)
1398 {
Bram Moolenaarf966ce52020-09-02 21:57:07 +02001399 if (tv_get_bool_chk(&argvars[3], &error))
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001400 rettv_list_set(rettv, NULL);
1401 if (argvars[4].v_type != VAR_UNKNOWN
Bram Moolenaarf966ce52020-09-02 21:57:07 +02001402 && tv_get_bool_chk(&argvars[4], &error))
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001403 flags |= WILD_ALLLINKS;
1404 }
1405 }
1406 if (file != NULL && !error)
1407 {
Bram Moolenaar04935fb2022-01-08 16:19:22 +00001408 ga_init2(&ga, sizeof(char_u *), 10);
zeertzjq3770f4c2023-01-22 18:38:51 +00001409 globpath(tv_get_string(&argvars[0]), file, &ga, flags, FALSE);
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001410 if (rettv->v_type == VAR_STRING)
1411 rettv->vval.v_string = ga_concat_strings(&ga, "\n");
Bram Moolenaar8088ae92022-06-20 11:38:17 +01001412 else if (rettv_list_alloc(rettv) == OK)
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001413 for (i = 0; i < ga.ga_len; ++i)
1414 list_append_string(rettv->vval.v_list,
1415 ((char_u **)(ga.ga_data))[i], -1);
1416 ga_clear_strings(&ga);
1417 }
1418 else
1419 rettv->vval.v_string = NULL;
1420}
1421
1422/*
1423 * "isdirectory()" function
1424 */
1425 void
1426f_isdirectory(typval_T *argvars, typval_T *rettv)
1427{
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +02001428 if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL)
1429 return;
1430
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001431 rettv->vval.v_number = mch_isdir(tv_get_string(&argvars[0]));
1432}
1433
1434/*
LemonBoydca1d402022-04-28 15:26:33 +01001435 * "isabsolutepath()" function
1436 */
1437 void
1438f_isabsolutepath(typval_T *argvars, typval_T *rettv)
1439{
1440 if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL)
1441 return;
1442
1443 rettv->vval.v_number = mch_isFullName(tv_get_string_strict(&argvars[0]));
1444}
1445
1446/*
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001447 * Create the directory in which "dir" is located, and higher levels when
1448 * needed.
Bram Moolenaar6f14da12022-09-07 21:30:44 +01001449 * Set "created" to the full name of the first created directory. It will be
1450 * NULL until that happens.
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001451 * Return OK or FAIL.
1452 */
1453 static int
Bram Moolenaar6f14da12022-09-07 21:30:44 +01001454mkdir_recurse(char_u *dir, int prot, char_u **created)
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001455{
1456 char_u *p;
1457 char_u *updir;
1458 int r = FAIL;
1459
Bram Moolenaar26262f82019-09-04 20:59:15 +02001460 // Get end of directory name in "dir".
1461 // We're done when it's "/" or "c:/".
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001462 p = gettail_sep(dir);
1463 if (p <= get_past_head(dir))
1464 return OK;
1465
Bram Moolenaar26262f82019-09-04 20:59:15 +02001466 // If the directory exists we're done. Otherwise: create it.
Bram Moolenaar71ccd032020-06-12 22:59:11 +02001467 updir = vim_strnsave(dir, p - dir);
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001468 if (updir == NULL)
1469 return FAIL;
1470 if (mch_isdir(updir))
1471 r = OK;
Bram Moolenaar6f14da12022-09-07 21:30:44 +01001472 else if (mkdir_recurse(updir, prot, created) == OK)
1473 {
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001474 r = vim_mkdir_emsg(updir, prot);
Bram Moolenaar6f14da12022-09-07 21:30:44 +01001475 if (r == OK && created != NULL && *created == NULL)
1476 *created = FullName_save(updir, FALSE);
1477 }
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001478 vim_free(updir);
1479 return r;
1480}
1481
1482/*
1483 * "mkdir()" function
1484 */
1485 void
1486f_mkdir(typval_T *argvars, typval_T *rettv)
1487{
1488 char_u *dir;
1489 char_u buf[NUMBUFLEN];
1490 int prot = 0755;
Bram Moolenaar6f14da12022-09-07 21:30:44 +01001491 int defer = FALSE;
1492 int defer_recurse = FALSE;
1493 char_u *created = NULL;
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001494
1495 rettv->vval.v_number = FAIL;
1496 if (check_restricted() || check_secure())
1497 return;
1498
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +02001499 if (in_vim9script()
1500 && (check_for_nonempty_string_arg(argvars, 0) == FAIL
1501 || check_for_opt_string_arg(argvars, 1) == FAIL
1502 || (argvars[1].v_type != VAR_UNKNOWN
1503 && check_for_opt_number_arg(argvars, 2) == FAIL)))
1504 return;
1505
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001506 dir = tv_get_string_buf(&argvars[0], buf);
1507 if (*dir == NUL)
1508 return;
1509
1510 if (*gettail(dir) == NUL)
Bram Moolenaar26262f82019-09-04 20:59:15 +02001511 // remove trailing slashes
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001512 *gettail_sep(dir) = NUL;
1513
1514 if (argvars[1].v_type != VAR_UNKNOWN)
1515 {
Bram Moolenaar6f14da12022-09-07 21:30:44 +01001516 char_u *arg2;
1517
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001518 if (argvars[2].v_type != VAR_UNKNOWN)
1519 {
1520 prot = (int)tv_get_number_chk(&argvars[2], NULL);
1521 if (prot == -1)
1522 return;
1523 }
Bram Moolenaar6f14da12022-09-07 21:30:44 +01001524 arg2 = tv_get_string(&argvars[1]);
1525 defer = vim_strchr(arg2, 'D') != NULL;
1526 defer_recurse = vim_strchr(arg2, 'R') != NULL;
1527 if ((defer || defer_recurse) && !can_add_defer())
1528 return;
1529
1530 if (vim_strchr(arg2, 'p') != NULL)
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001531 {
1532 if (mch_isdir(dir))
1533 {
Bram Moolenaar26262f82019-09-04 20:59:15 +02001534 // With the "p" flag it's OK if the dir already exists.
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001535 rettv->vval.v_number = OK;
1536 return;
1537 }
Bram Moolenaar6f14da12022-09-07 21:30:44 +01001538 mkdir_recurse(dir, prot, defer || defer_recurse ? &created : NULL);
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001539 }
1540 }
1541 rettv->vval.v_number = vim_mkdir_emsg(dir, prot);
Bram Moolenaar6f14da12022-09-07 21:30:44 +01001542
1543 // Handle "D" and "R": deferred deletion of the created directory.
1544 if (rettv->vval.v_number == OK
1545 && created == NULL && (defer || defer_recurse))
1546 created = FullName_save(dir, FALSE);
1547 if (created != NULL)
1548 {
1549 typval_T tv[2];
1550
1551 tv[0].v_type = VAR_STRING;
1552 tv[0].v_lock = 0;
1553 tv[0].vval.v_string = created;
1554 tv[1].v_type = VAR_STRING;
1555 tv[1].v_lock = 0;
Christian Brabandt6cc30272024-12-13 17:54:33 +01001556 tv[1].vval.v_string = vim_strsave(
1557 (char_u *)(defer_recurse ? "rf" : "d"));
Bram Moolenaar6f14da12022-09-07 21:30:44 +01001558 if (tv[0].vval.v_string == NULL || tv[1].vval.v_string == NULL
1559 || add_defer((char_u *)"delete", 2, tv) == FAIL)
1560 {
1561 vim_free(tv[0].vval.v_string);
1562 vim_free(tv[1].vval.v_string);
1563 }
1564 }
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001565}
1566
1567/*
Bram Moolenaaraf7645d2019-09-05 22:33:28 +02001568 * "pathshorten()" function
1569 */
1570 void
1571f_pathshorten(typval_T *argvars, typval_T *rettv)
1572{
1573 char_u *p;
Bram Moolenaar6a33ef02020-09-25 22:42:48 +02001574 int trim_len = 1;
1575
Yegappan Lakshmanan1a71d312021-07-15 12:49:58 +02001576 if (in_vim9script()
1577 && (check_for_string_arg(argvars, 0) == FAIL
Yegappan Lakshmanan83494b42021-07-20 17:51:51 +02001578 || check_for_opt_number_arg(argvars, 1) == FAIL))
Yegappan Lakshmanan1a71d312021-07-15 12:49:58 +02001579 return;
1580
Bram Moolenaar6a33ef02020-09-25 22:42:48 +02001581 if (argvars[1].v_type != VAR_UNKNOWN)
1582 {
1583 trim_len = (int)tv_get_number(&argvars[1]);
1584 if (trim_len < 1)
1585 trim_len = 1;
1586 }
Bram Moolenaaraf7645d2019-09-05 22:33:28 +02001587
1588 rettv->v_type = VAR_STRING;
1589 p = tv_get_string_chk(&argvars[0]);
Bram Moolenaar6a33ef02020-09-25 22:42:48 +02001590
Bram Moolenaaraf7645d2019-09-05 22:33:28 +02001591 if (p == NULL)
1592 rettv->vval.v_string = NULL;
1593 else
1594 {
1595 p = vim_strsave(p);
1596 rettv->vval.v_string = p;
1597 if (p != NULL)
Bram Moolenaar6a33ef02020-09-25 22:42:48 +02001598 shorten_dir_len(p, trim_len);
Bram Moolenaaraf7645d2019-09-05 22:33:28 +02001599 }
1600}
1601
1602/*
Bram Moolenaarf8abbf32020-08-19 16:00:06 +02001603 * Common code for readdir_checkitem() and readdirex_checkitem().
1604 * Either "name" or "dict" is NULL.
Bram Moolenaar80147dd2020-02-04 22:32:59 +01001605 */
1606 static int
Bram Moolenaarf8abbf32020-08-19 16:00:06 +02001607checkitem_common(void *context, char_u *name, dict_T *dict)
Bram Moolenaar80147dd2020-02-04 22:32:59 +01001608{
1609 typval_T *expr = (typval_T *)context;
1610 typval_T save_val;
1611 typval_T rettv;
1612 typval_T argv[2];
1613 int retval = 0;
1614 int error = FALSE;
Bram Moolenaar80147dd2020-02-04 22:32:59 +01001615
1616 prepare_vimvar(VV_VAL, &save_val);
Bram Moolenaarf8abbf32020-08-19 16:00:06 +02001617 if (name != NULL)
1618 {
1619 set_vim_var_string(VV_VAL, name, -1);
1620 argv[0].v_type = VAR_STRING;
1621 argv[0].vval.v_string = name;
1622 }
1623 else
1624 {
1625 set_vim_var_dict(VV_VAL, dict);
1626 argv[0].v_type = VAR_DICT;
1627 argv[0].vval.v_dict = dict;
1628 }
Bram Moolenaar80147dd2020-02-04 22:32:59 +01001629
zeertzjqad0c4422023-08-17 22:15:47 +02001630 if (eval_expr_typval(expr, FALSE, argv, 1, NULL, &rettv) == FAIL)
Bram Moolenaar80147dd2020-02-04 22:32:59 +01001631 goto theend;
1632
Bram Moolenaarf8abbf32020-08-19 16:00:06 +02001633 // We want to use -1, but also true/false should be allowed.
1634 if (rettv.v_type == VAR_SPECIAL || rettv.v_type == VAR_BOOL)
1635 {
1636 rettv.v_type = VAR_NUMBER;
1637 rettv.vval.v_number = rettv.vval.v_number == VVAL_TRUE;
1638 }
Bram Moolenaar80147dd2020-02-04 22:32:59 +01001639 retval = tv_get_number_chk(&rettv, &error);
1640 if (error)
1641 retval = -1;
1642 clear_tv(&rettv);
1643
1644theend:
Bram Moolenaarf8abbf32020-08-19 16:00:06 +02001645 if (name != NULL)
1646 set_vim_var_string(VV_VAL, NULL, 0);
1647 else
1648 set_vim_var_dict(VV_VAL, NULL);
Bram Moolenaar80147dd2020-02-04 22:32:59 +01001649 restore_vimvar(VV_VAL, &save_val);
1650 return retval;
1651}
1652
Bram Moolenaarf8abbf32020-08-19 16:00:06 +02001653/*
1654 * Evaluate "expr" (= "context") for readdir().
1655 */
1656 static int
1657readdir_checkitem(void *context, void *item)
1658{
1659 char_u *name = (char_u *)item;
1660
1661 return checkitem_common(context, name, NULL);
1662}
1663
Bram Moolenaard83392a2022-09-01 12:22:46 +01001664/*
1665 * Process the keys in the Dict argument to the readdir() and readdirex()
1666 * functions. Assumes the Dict argument is the 3rd argument.
1667 */
Bram Moolenaar84cf6bd2020-06-16 20:03:43 +02001668 static int
Bram Moolenaard83392a2022-09-01 12:22:46 +01001669readdirex_dict_arg(typval_T *argvars, int *cmp)
Bram Moolenaar84cf6bd2020-06-16 20:03:43 +02001670{
1671 char_u *compare;
1672
Bram Moolenaard83392a2022-09-01 12:22:46 +01001673 if (check_for_nonnull_dict_arg(argvars, 2) == FAIL)
Bram Moolenaar84cf6bd2020-06-16 20:03:43 +02001674 return FAIL;
Bram Moolenaar84cf6bd2020-06-16 20:03:43 +02001675
Bram Moolenaard83392a2022-09-01 12:22:46 +01001676 if (dict_has_key(argvars[2].vval.v_dict, "sort"))
1677 compare = dict_get_string(argvars[2].vval.v_dict, "sort", FALSE);
Bram Moolenaar84cf6bd2020-06-16 20:03:43 +02001678 else
1679 {
Bram Moolenaar460ae5d2022-01-01 14:19:49 +00001680 semsg(_(e_dictionary_key_str_required), "sort");
Bram Moolenaar84cf6bd2020-06-16 20:03:43 +02001681 return FAIL;
1682 }
1683
1684 if (STRCMP(compare, (char_u *) "none") == 0)
1685 *cmp = READDIR_SORT_NONE;
1686 else if (STRCMP(compare, (char_u *) "case") == 0)
1687 *cmp = READDIR_SORT_BYTE;
1688 else if (STRCMP(compare, (char_u *) "icase") == 0)
1689 *cmp = READDIR_SORT_IC;
1690 else if (STRCMP(compare, (char_u *) "collate") == 0)
1691 *cmp = READDIR_SORT_COLLATE;
1692 return OK;
1693}
1694
Bram Moolenaar80147dd2020-02-04 22:32:59 +01001695/*
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001696 * "readdir()" function
1697 */
1698 void
1699f_readdir(typval_T *argvars, typval_T *rettv)
1700{
1701 typval_T *expr;
1702 int ret;
1703 char_u *path;
1704 char_u *p;
1705 garray_T ga;
1706 int i;
Bram Moolenaar6ed545e2022-05-09 20:09:23 +01001707 int sort = READDIR_SORT_BYTE;
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001708
1709 if (rettv_list_alloc(rettv) == FAIL)
1710 return;
Yegappan Lakshmanan5bca9062021-07-24 21:33:26 +02001711
1712 if (in_vim9script()
1713 && (check_for_string_arg(argvars, 0) == FAIL
1714 || (argvars[1].v_type != VAR_UNKNOWN
1715 && check_for_opt_dict_arg(argvars, 2) == FAIL)))
1716 return;
1717
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001718 path = tv_get_string(&argvars[0]);
1719 expr = &argvars[1];
1720
Bram Moolenaar84cf6bd2020-06-16 20:03:43 +02001721 if (argvars[1].v_type != VAR_UNKNOWN && argvars[2].v_type != VAR_UNKNOWN &&
Bram Moolenaard83392a2022-09-01 12:22:46 +01001722 readdirex_dict_arg(argvars, &sort) == FAIL)
Bram Moolenaar84cf6bd2020-06-16 20:03:43 +02001723 return;
1724
Bram Moolenaar6c9ba042020-06-01 16:09:41 +02001725 ret = readdir_core(&ga, path, FALSE, (void *)expr,
Bram Moolenaar84cf6bd2020-06-16 20:03:43 +02001726 (expr->v_type == VAR_UNKNOWN) ? NULL : readdir_checkitem, sort);
Bram Moolenaar6c9ba042020-06-01 16:09:41 +02001727 if (ret == OK)
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001728 {
1729 for (i = 0; i < ga.ga_len; i++)
1730 {
1731 p = ((char_u **)ga.ga_data)[i];
1732 list_append_string(rettv->vval.v_list, p, -1);
1733 }
1734 }
1735 ga_clear_strings(&ga);
1736}
1737
1738/*
Bram Moolenaar6c9ba042020-06-01 16:09:41 +02001739 * Evaluate "expr" (= "context") for readdirex().
1740 */
1741 static int
1742readdirex_checkitem(void *context, void *item)
1743{
Bram Moolenaar6c9ba042020-06-01 16:09:41 +02001744 dict_T *dict = (dict_T*)item;
1745
Bram Moolenaarf8abbf32020-08-19 16:00:06 +02001746 return checkitem_common(context, NULL, dict);
Bram Moolenaar6c9ba042020-06-01 16:09:41 +02001747}
1748
1749/*
1750 * "readdirex()" function
1751 */
1752 void
1753f_readdirex(typval_T *argvars, typval_T *rettv)
1754{
1755 typval_T *expr;
1756 int ret;
1757 char_u *path;
1758 garray_T ga;
1759 int i;
Bram Moolenaar6ed545e2022-05-09 20:09:23 +01001760 int sort = READDIR_SORT_BYTE;
Bram Moolenaar6c9ba042020-06-01 16:09:41 +02001761
1762 if (rettv_list_alloc(rettv) == FAIL)
1763 return;
Yegappan Lakshmanan5bca9062021-07-24 21:33:26 +02001764
1765 if (in_vim9script()
1766 && (check_for_string_arg(argvars, 0) == FAIL
1767 || (argvars[1].v_type != VAR_UNKNOWN
1768 && check_for_opt_dict_arg(argvars, 2) == FAIL)))
1769 return;
1770
Bram Moolenaar6c9ba042020-06-01 16:09:41 +02001771 path = tv_get_string(&argvars[0]);
1772 expr = &argvars[1];
1773
Bram Moolenaar84cf6bd2020-06-16 20:03:43 +02001774 if (argvars[1].v_type != VAR_UNKNOWN && argvars[2].v_type != VAR_UNKNOWN &&
Bram Moolenaard83392a2022-09-01 12:22:46 +01001775 readdirex_dict_arg(argvars, &sort) == FAIL)
Bram Moolenaar84cf6bd2020-06-16 20:03:43 +02001776 return;
1777
Bram Moolenaar6c9ba042020-06-01 16:09:41 +02001778 ret = readdir_core(&ga, path, TRUE, (void *)expr,
Bram Moolenaar84cf6bd2020-06-16 20:03:43 +02001779 (expr->v_type == VAR_UNKNOWN) ? NULL : readdirex_checkitem, sort);
Bram Moolenaar6c9ba042020-06-01 16:09:41 +02001780 if (ret == OK)
1781 {
1782 for (i = 0; i < ga.ga_len; i++)
1783 {
1784 dict_T *dict = ((dict_T**)ga.ga_data)[i];
1785 list_append_dict(rettv->vval.v_list, dict);
1786 dict_unref(dict);
1787 }
1788 }
1789 ga_clear(&ga);
1790}
1791
1792/*
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001793 * "readfile()" function
1794 */
Bram Moolenaarc423ad72021-01-13 20:38:03 +01001795 static void
1796read_file_or_blob(typval_T *argvars, typval_T *rettv, int always_blob)
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001797{
1798 int binary = FALSE;
Bram Moolenaarc423ad72021-01-13 20:38:03 +01001799 int blob = always_blob;
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001800 int failed = FALSE;
1801 char_u *fname;
1802 FILE *fd;
Bram Moolenaar26262f82019-09-04 20:59:15 +02001803 char_u buf[(IOSIZE/256)*256]; // rounded to avoid odd + 1
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001804 int io_size = sizeof(buf);
Bram Moolenaar26262f82019-09-04 20:59:15 +02001805 int readlen; // size of last fread()
1806 char_u *prev = NULL; // previously read bytes, if any
1807 long prevlen = 0; // length of data in prev
1808 long prevsize = 0; // size of prev buffer
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001809 long maxline = MAXLNUM;
1810 long cnt = 0;
Bram Moolenaar26262f82019-09-04 20:59:15 +02001811 char_u *p; // position in buf
1812 char_u *start; // start of current line
K.Takata11df3ae2022-10-19 14:02:40 +01001813 off_T offset = 0;
1814 off_T size = -1;
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001815
1816 if (argvars[1].v_type != VAR_UNKNOWN)
1817 {
K.Takata11df3ae2022-10-19 14:02:40 +01001818 if (always_blob)
1819 {
1820 offset = (off_T)tv_get_number(&argvars[1]);
1821 if (argvars[2].v_type != VAR_UNKNOWN)
1822 size = (off_T)tv_get_number(&argvars[2]);
1823 }
1824 else
1825 {
1826 if (STRCMP(tv_get_string(&argvars[1]), "b") == 0)
1827 binary = TRUE;
1828 if (STRCMP(tv_get_string(&argvars[1]), "B") == 0)
1829 blob = TRUE;
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001830
K.Takata11df3ae2022-10-19 14:02:40 +01001831 if (argvars[2].v_type != VAR_UNKNOWN)
1832 maxline = (long)tv_get_number(&argvars[2]);
1833 }
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001834 }
1835
Bram Moolenaar15352dc2020-04-06 21:12:42 +02001836 if ((blob ? rettv_blob_alloc(rettv) : rettv_list_alloc(rettv)) == FAIL)
1837 return;
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001838
Bram Moolenaar26262f82019-09-04 20:59:15 +02001839 // Always open the file in binary mode, library functions have a mind of
1840 // their own about CR-LF conversion.
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001841 fname = tv_get_string(&argvars[0]);
Bram Moolenaar15352dc2020-04-06 21:12:42 +02001842
1843 if (mch_isdir(fname))
1844 {
Bram Moolenaar4dea2d92022-03-31 11:37:57 +01001845 semsg(_(e_str_is_directory), fname);
Bram Moolenaar15352dc2020-04-06 21:12:42 +02001846 return;
1847 }
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001848 if (*fname == NUL || (fd = mch_fopen((char *)fname, READBIN)) == NULL)
1849 {
K.Takata11df3ae2022-10-19 14:02:40 +01001850 semsg(_(e_cant_open_file_str),
1851 *fname == NUL ? (char_u *)_("<empty>") : fname);
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001852 return;
1853 }
1854
1855 if (blob)
1856 {
K.Takata11df3ae2022-10-19 14:02:40 +01001857 if (read_blob(fd, rettv, offset, size) == FAIL)
Bram Moolenaar460ae5d2022-01-01 14:19:49 +00001858 semsg(_(e_cant_read_file_str), fname);
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001859 fclose(fd);
1860 return;
1861 }
1862
1863 while (cnt < maxline || maxline < 0)
1864 {
1865 readlen = (int)fread(buf, 1, io_size, fd);
1866
Bram Moolenaar26262f82019-09-04 20:59:15 +02001867 // This for loop processes what was read, but is also entered at end
1868 // of file so that either:
1869 // - an incomplete line gets written
1870 // - a "binary" file gets an empty line at the end if it ends in a
1871 // newline.
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001872 for (p = buf, start = buf;
1873 p < buf + readlen || (readlen <= 0 && (prevlen > 0 || binary));
1874 ++p)
1875 {
Dominique Pellef5d639a2022-01-12 15:24:40 +00001876 if (readlen <= 0 || *p == '\n')
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001877 {
1878 listitem_T *li;
1879 char_u *s = NULL;
1880 long_u len = p - start;
1881
Bram Moolenaar26262f82019-09-04 20:59:15 +02001882 // Finished a line. Remove CRs before NL.
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001883 if (readlen > 0 && !binary)
1884 {
1885 while (len > 0 && start[len - 1] == '\r')
1886 --len;
Bram Moolenaar26262f82019-09-04 20:59:15 +02001887 // removal may cross back to the "prev" string
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001888 if (len == 0)
1889 while (prevlen > 0 && prev[prevlen - 1] == '\r')
1890 --prevlen;
1891 }
1892 if (prevlen == 0)
Bram Moolenaar71ccd032020-06-12 22:59:11 +02001893 s = vim_strnsave(start, len);
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001894 else
1895 {
Bram Moolenaar26262f82019-09-04 20:59:15 +02001896 // Change "prev" buffer to be the right size. This way
1897 // the bytes are only copied once, and very long lines are
1898 // allocated only once.
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001899 if ((s = vim_realloc(prev, prevlen + len + 1)) != NULL)
1900 {
1901 mch_memmove(s + prevlen, start, len);
1902 s[prevlen + len] = NUL;
Bram Moolenaar26262f82019-09-04 20:59:15 +02001903 prev = NULL; // the list will own the string
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001904 prevlen = prevsize = 0;
1905 }
1906 }
1907 if (s == NULL)
1908 {
1909 do_outofmem_msg((long_u) prevlen + len + 1);
1910 failed = TRUE;
1911 break;
1912 }
1913
1914 if ((li = listitem_alloc()) == NULL)
1915 {
1916 vim_free(s);
1917 failed = TRUE;
1918 break;
1919 }
1920 li->li_tv.v_type = VAR_STRING;
1921 li->li_tv.v_lock = 0;
1922 li->li_tv.vval.v_string = s;
1923 list_append(rettv->vval.v_list, li);
1924
Bram Moolenaar26262f82019-09-04 20:59:15 +02001925 start = p + 1; // step over newline
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001926 if ((++cnt >= maxline && maxline >= 0) || readlen <= 0)
1927 break;
1928 }
1929 else if (*p == NUL)
1930 *p = '\n';
Bram Moolenaar26262f82019-09-04 20:59:15 +02001931 // Check for utf8 "bom"; U+FEFF is encoded as EF BB BF. Do this
1932 // when finding the BF and check the previous two bytes.
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001933 else if (*p == 0xbf && enc_utf8 && !binary)
1934 {
Bram Moolenaar26262f82019-09-04 20:59:15 +02001935 // Find the two bytes before the 0xbf. If p is at buf, or buf
1936 // + 1, these may be in the "prev" string.
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001937 char_u back1 = p >= buf + 1 ? p[-1]
1938 : prevlen >= 1 ? prev[prevlen - 1] : NUL;
1939 char_u back2 = p >= buf + 2 ? p[-2]
1940 : p == buf + 1 && prevlen >= 1 ? prev[prevlen - 1]
1941 : prevlen >= 2 ? prev[prevlen - 2] : NUL;
1942
1943 if (back2 == 0xef && back1 == 0xbb)
1944 {
1945 char_u *dest = p - 2;
1946
Bram Moolenaar26262f82019-09-04 20:59:15 +02001947 // Usually a BOM is at the beginning of a file, and so at
1948 // the beginning of a line; then we can just step over it.
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001949 if (start == dest)
1950 start = p + 1;
1951 else
1952 {
Bram Moolenaar26262f82019-09-04 20:59:15 +02001953 // have to shuffle buf to close gap
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001954 int adjust_prevlen = 0;
1955
1956 if (dest < buf)
1957 {
Bram Moolenaarc423ad72021-01-13 20:38:03 +01001958 // must be 1 or 2
1959 adjust_prevlen = (int)(buf - dest);
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001960 dest = buf;
1961 }
1962 if (readlen > p - buf + 1)
1963 mch_memmove(dest, p + 1, readlen - (p - buf) - 1);
1964 readlen -= 3 - adjust_prevlen;
1965 prevlen -= adjust_prevlen;
1966 p = dest - 1;
1967 }
1968 }
1969 }
Bram Moolenaar26262f82019-09-04 20:59:15 +02001970 } // for
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001971
1972 if (failed || (cnt >= maxline && maxline >= 0) || readlen <= 0)
1973 break;
1974 if (start < p)
1975 {
Bram Moolenaar26262f82019-09-04 20:59:15 +02001976 // There's part of a line in buf, store it in "prev".
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001977 if (p - start + prevlen >= prevsize)
1978 {
Bram Moolenaar26262f82019-09-04 20:59:15 +02001979 // need bigger "prev" buffer
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001980 char_u *newprev;
1981
Bram Moolenaar26262f82019-09-04 20:59:15 +02001982 // A common use case is ordinary text files and "prev" gets a
1983 // fragment of a line, so the first allocation is made
1984 // small, to avoid repeatedly 'allocing' large and
1985 // 'reallocing' small.
Bram Moolenaarb005cd82019-09-04 15:54:55 +02001986 if (prevsize == 0)
1987 prevsize = (long)(p - start);
1988 else
1989 {
1990 long grow50pc = (prevsize * 3) / 2;
1991 long growmin = (long)((p - start) * 2 + prevlen);
1992 prevsize = grow50pc > growmin ? grow50pc : growmin;
1993 }
1994 newprev = vim_realloc(prev, prevsize);
1995 if (newprev == NULL)
1996 {
1997 do_outofmem_msg((long_u)prevsize);
1998 failed = TRUE;
1999 break;
2000 }
2001 prev = newprev;
2002 }
Bram Moolenaar26262f82019-09-04 20:59:15 +02002003 // Add the line part to end of "prev".
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002004 mch_memmove(prev + prevlen, start, p - start);
2005 prevlen += (long)(p - start);
2006 }
Bram Moolenaar26262f82019-09-04 20:59:15 +02002007 } // while
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002008
Bram Moolenaar26262f82019-09-04 20:59:15 +02002009 // For a negative line count use only the lines at the end of the file,
2010 // free the rest.
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002011 if (!failed && maxline < 0)
2012 while (cnt > -maxline)
2013 {
2014 listitem_remove(rettv->vval.v_list, rettv->vval.v_list->lv_first);
2015 --cnt;
2016 }
2017
2018 if (failed)
2019 {
2020 // an empty list is returned on error
2021 list_free(rettv->vval.v_list);
2022 rettv_list_alloc(rettv);
2023 }
2024
2025 vim_free(prev);
2026 fclose(fd);
2027}
2028
2029/*
Bram Moolenaarc423ad72021-01-13 20:38:03 +01002030 * "readblob()" function
2031 */
2032 void
2033f_readblob(typval_T *argvars, typval_T *rettv)
2034{
K.Takata11df3ae2022-10-19 14:02:40 +01002035 if (in_vim9script()
2036 && (check_for_string_arg(argvars, 0) == FAIL
2037 || check_for_opt_number_arg(argvars, 1) == FAIL
2038 || (argvars[1].v_type != VAR_UNKNOWN
2039 && check_for_opt_number_arg(argvars, 2) == FAIL)))
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +02002040 return;
2041
Bram Moolenaarc423ad72021-01-13 20:38:03 +01002042 read_file_or_blob(argvars, rettv, TRUE);
2043}
2044
2045/*
2046 * "readfile()" function
2047 */
2048 void
2049f_readfile(typval_T *argvars, typval_T *rettv)
2050{
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +02002051 if (in_vim9script()
2052 && (check_for_nonempty_string_arg(argvars, 0) == FAIL
2053 || check_for_opt_string_arg(argvars, 1) == FAIL
2054 || (argvars[1].v_type != VAR_UNKNOWN
2055 && check_for_opt_number_arg(argvars, 2) == FAIL)))
2056 return;
2057
Bram Moolenaarc423ad72021-01-13 20:38:03 +01002058 read_file_or_blob(argvars, rettv, FALSE);
2059}
2060
2061/*
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002062 * "resolve()" function
2063 */
2064 void
2065f_resolve(typval_T *argvars, typval_T *rettv)
2066{
2067 char_u *p;
2068#ifdef HAVE_READLINK
2069 char_u *buf = NULL;
2070#endif
2071
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +02002072 if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL)
2073 return;
2074
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002075 p = tv_get_string(&argvars[0]);
2076#ifdef FEAT_SHORTCUT
2077 {
2078 char_u *v = NULL;
2079
2080 v = mch_resolve_path(p, TRUE);
2081 if (v != NULL)
2082 rettv->vval.v_string = v;
2083 else
2084 rettv->vval.v_string = vim_strsave(p);
2085 }
2086#else
2087# ifdef HAVE_READLINK
2088 {
2089 char_u *cpy;
Christian Brabandt6cc30272024-12-13 17:54:33 +01002090 int len;
2091 char_u *remain = NULL;
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002092 char_u *q;
2093 int is_relative_to_current = FALSE;
2094 int has_trailing_pathsep = FALSE;
2095 int limit = 100;
2096
Christian Brabandt6cc30272024-12-13 17:54:33 +01002097 p = vim_strsave(p);
Bram Moolenaar70188f52019-12-23 18:18:52 +01002098 if (p == NULL)
2099 goto fail;
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002100 if (p[0] == '.' && (vim_ispathsep(p[1])
2101 || (p[1] == '.' && (vim_ispathsep(p[2])))))
2102 is_relative_to_current = TRUE;
2103
Christian Brabandt6cc30272024-12-13 17:54:33 +01002104 len = STRLEN(p);
2105 if (len > 1 && after_pathsep(p, p + len))
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002106 {
2107 has_trailing_pathsep = TRUE;
Christian Brabandt6cc30272024-12-13 17:54:33 +01002108 p[len - 1] = NUL; // the trailing slash breaks readlink()
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002109 }
2110
2111 q = getnextcomp(p);
2112 if (*q != NUL)
2113 {
Bram Moolenaar26262f82019-09-04 20:59:15 +02002114 // Separate the first path component in "p", and keep the
2115 // remainder (beginning with the path separator).
Christian Brabandt6cc30272024-12-13 17:54:33 +01002116 remain = vim_strsave(q - 1);
2117 q[-1] = NUL;
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002118 }
2119
2120 buf = alloc(MAXPATHL + 1);
2121 if (buf == NULL)
Christian Brabandt6cc30272024-12-13 17:54:33 +01002122 {
2123 vim_free(p);
2124 vim_free(remain);
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002125 goto fail;
Christian Brabandt6cc30272024-12-13 17:54:33 +01002126 }
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002127
2128 for (;;)
2129 {
2130 for (;;)
2131 {
Christian Brabandt6cc30272024-12-13 17:54:33 +01002132 len = readlink((char *)p, (char *)buf, MAXPATHL);
2133 if (len <= 0)
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002134 break;
Christian Brabandt6cc30272024-12-13 17:54:33 +01002135 buf[len] = NUL;
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002136
2137 if (limit-- == 0)
2138 {
Christian Brabandt6cc30272024-12-13 17:54:33 +01002139 vim_free(p);
2140 vim_free(remain);
Bram Moolenaara6f79292022-01-04 21:30:47 +00002141 emsg(_(e_too_many_symbolic_links_cycle));
Christian Brabandt6cc30272024-12-13 17:54:33 +01002142 rettv->vval.v_string = NULL;
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002143 goto fail;
2144 }
2145
Bram Moolenaar26262f82019-09-04 20:59:15 +02002146 // Ensure that the result will have a trailing path separator
2147 // if the argument has one.
Christian Brabandt6cc30272024-12-13 17:54:33 +01002148 if (remain == NULL && has_trailing_pathsep)
2149 add_pathsep(buf);
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002150
Bram Moolenaar26262f82019-09-04 20:59:15 +02002151 // Separate the first path component in the link value and
2152 // concatenate the remainders.
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002153 q = getnextcomp(vim_ispathsep(*buf) ? buf + 1 : buf);
2154 if (*q != NUL)
2155 {
2156 if (remain == NULL)
Christian Brabandt6cc30272024-12-13 17:54:33 +01002157 remain = vim_strsave(q - 1);
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002158 else
2159 {
Christian Brabandt6cc30272024-12-13 17:54:33 +01002160 cpy = concat_str(q - 1, remain);
2161 if (cpy != NULL)
2162 {
2163 vim_free(remain);
2164 remain = cpy;
2165 }
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002166 }
Christian Brabandt6cc30272024-12-13 17:54:33 +01002167 q[-1] = NUL;
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002168 }
2169
2170 q = gettail(p);
2171 if (q > p && *q == NUL)
2172 {
Bram Moolenaar26262f82019-09-04 20:59:15 +02002173 // Ignore trailing path separator.
Christian Brabandt6cc30272024-12-13 17:54:33 +01002174 p[q - p - 1] = NUL;
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002175 q = gettail(p);
2176 }
2177 if (q > p && !mch_isFullName(buf))
2178 {
Bram Moolenaar26262f82019-09-04 20:59:15 +02002179 // symlink is relative to directory of argument
Christian Brabandt6cc30272024-12-13 17:54:33 +01002180 cpy = alloc(STRLEN(p) + STRLEN(buf) + 1);
2181 if (cpy != NULL)
2182 {
2183 STRCPY(cpy, p);
2184 STRCPY(gettail(cpy), buf);
2185 vim_free(p);
2186 p = cpy;
2187 }
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002188 }
2189 else
2190 {
2191 vim_free(p);
Christian Brabandt6cc30272024-12-13 17:54:33 +01002192 p = vim_strsave(buf);
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002193 }
2194 }
2195
2196 if (remain == NULL)
2197 break;
2198
Bram Moolenaar26262f82019-09-04 20:59:15 +02002199 // Append the first path component of "remain" to "p".
Christian Brabandt6cc30272024-12-13 17:54:33 +01002200 q = getnextcomp(remain + 1);
2201 len = q - remain - (*q != NUL);
2202 cpy = vim_strnsave(p, STRLEN(p) + len);
2203 if (cpy != NULL)
2204 {
2205 STRNCAT(cpy, remain, len);
2206 vim_free(p);
2207 p = cpy;
2208 }
Bram Moolenaar26262f82019-09-04 20:59:15 +02002209 // Shorten "remain".
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002210 if (*q != NUL)
Christian Brabandt6cc30272024-12-13 17:54:33 +01002211 STRMOVE(remain, q - 1);
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002212 else
2213 VIM_CLEAR(remain);
2214 }
2215
Bram Moolenaar26262f82019-09-04 20:59:15 +02002216 // If the result is a relative path name, make it explicitly relative to
2217 // the current directory if and only if the argument had this form.
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002218 if (!vim_ispathsep(*p))
2219 {
2220 if (is_relative_to_current
2221 && *p != NUL
2222 && !(p[0] == '.'
2223 && (p[1] == NUL
2224 || vim_ispathsep(p[1])
2225 || (p[1] == '.'
2226 && (p[2] == NUL
2227 || vim_ispathsep(p[2]))))))
2228 {
Bram Moolenaar26262f82019-09-04 20:59:15 +02002229 // Prepend "./".
Christian Brabandt6cc30272024-12-13 17:54:33 +01002230 cpy = concat_str((char_u *)"./", p);
2231 if (cpy != NULL)
2232 {
2233 vim_free(p);
2234 p = cpy;
2235 }
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002236 }
2237 else if (!is_relative_to_current)
2238 {
Bram Moolenaar26262f82019-09-04 20:59:15 +02002239 // Strip leading "./".
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002240 q = p;
2241 while (q[0] == '.' && vim_ispathsep(q[1]))
2242 q += 2;
2243 if (q > p)
Christian Brabandt6cc30272024-12-13 17:54:33 +01002244 STRMOVE(p, p + 2);
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002245 }
2246 }
2247
Bram Moolenaar26262f82019-09-04 20:59:15 +02002248 // Ensure that the result will have no trailing path separator
2249 // if the argument had none. But keep "/" or "//".
Christian Brabandt6cc30272024-12-13 17:54:33 +01002250 if (!has_trailing_pathsep)
2251 {
2252 q = p + STRLEN(p);
2253 if (after_pathsep(p, q))
2254 *gettail_sep(p) = NUL;
2255 }
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002256
2257 rettv->vval.v_string = p;
2258 }
2259# else
2260 rettv->vval.v_string = vim_strsave(p);
2261# endif
2262#endif
2263
2264 simplify_filename(rettv->vval.v_string);
2265
2266#ifdef HAVE_READLINK
2267fail:
2268 vim_free(buf);
2269#endif
2270 rettv->v_type = VAR_STRING;
2271}
2272
2273/*
2274 * "tempname()" function
2275 */
2276 void
2277f_tempname(typval_T *argvars UNUSED, typval_T *rettv)
2278{
2279 static int x = 'A';
2280
2281 rettv->v_type = VAR_STRING;
2282 rettv->vval.v_string = vim_tempname(x, FALSE);
2283
Bram Moolenaar26262f82019-09-04 20:59:15 +02002284 // Advance 'x' to use A-Z and 0-9, so that there are at least 34 different
2285 // names. Skip 'I' and 'O', they are used for shell redirection.
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002286 do
2287 {
2288 if (x == 'Z')
2289 x = '0';
2290 else if (x == '9')
2291 x = 'A';
2292 else
Bram Moolenaar424bcae2022-01-31 14:59:41 +00002293 ++x;
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002294 } while (x == 'I' || x == 'O');
2295}
2296
2297/*
2298 * "writefile()" function
2299 */
2300 void
2301f_writefile(typval_T *argvars, typval_T *rettv)
2302{
2303 int binary = FALSE;
2304 int append = FALSE;
Bram Moolenaar806a2732022-09-04 15:40:36 +01002305 int defer = FALSE;
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002306#ifdef HAVE_FSYNC
2307 int do_fsync = p_fs;
2308#endif
2309 char_u *fname;
2310 FILE *fd;
2311 int ret = 0;
2312 listitem_T *li;
2313 list_T *list = NULL;
2314 blob_T *blob = NULL;
2315
2316 rettv->vval.v_number = -1;
2317 if (check_secure())
2318 return;
2319
Yegappan Lakshmanan0ad871d2021-07-23 20:37:56 +02002320 if (in_vim9script()
2321 && (check_for_list_or_blob_arg(argvars, 0) == FAIL
2322 || check_for_string_arg(argvars, 1) == FAIL
2323 || check_for_opt_string_arg(argvars, 2) == FAIL))
2324 return;
2325
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002326 if (argvars[0].v_type == VAR_LIST)
2327 {
2328 list = argvars[0].vval.v_list;
2329 if (list == NULL)
2330 return;
Bram Moolenaar7e9f3512020-05-13 22:44:22 +02002331 CHECK_LIST_MATERIALIZE(list);
Bram Moolenaaraeea7212020-04-02 18:50:46 +02002332 FOR_ALL_LIST_ITEMS(list, li)
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002333 if (tv_get_string_chk(&li->li_tv) == NULL)
2334 return;
2335 }
2336 else if (argvars[0].v_type == VAR_BLOB)
2337 {
2338 blob = argvars[0].vval.v_blob;
2339 if (blob == NULL)
2340 return;
2341 }
2342 else
2343 {
Bram Moolenaar436b5ad2021-12-31 22:49:24 +00002344 semsg(_(e_invalid_argument_str),
Bram Moolenaar18a2b872020-03-19 13:08:45 +01002345 _("writefile() first argument must be a List or a Blob"));
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002346 return;
2347 }
2348
2349 if (argvars[2].v_type != VAR_UNKNOWN)
2350 {
2351 char_u *arg2 = tv_get_string_chk(&argvars[2]);
2352
2353 if (arg2 == NULL)
2354 return;
2355 if (vim_strchr(arg2, 'b') != NULL)
2356 binary = TRUE;
2357 if (vim_strchr(arg2, 'a') != NULL)
2358 append = TRUE;
Bram Moolenaar806a2732022-09-04 15:40:36 +01002359 if (vim_strchr(arg2, 'D') != NULL)
2360 defer = TRUE;
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002361#ifdef HAVE_FSYNC
2362 if (vim_strchr(arg2, 's') != NULL)
2363 do_fsync = TRUE;
2364 else if (vim_strchr(arg2, 'S') != NULL)
2365 do_fsync = FALSE;
2366#endif
2367 }
2368
2369 fname = tv_get_string_chk(&argvars[1]);
2370 if (fname == NULL)
2371 return;
2372
Bram Moolenaar6f14da12022-09-07 21:30:44 +01002373 if (defer && !can_add_defer())
Bram Moolenaar806a2732022-09-04 15:40:36 +01002374 return;
Bram Moolenaar806a2732022-09-04 15:40:36 +01002375
Bram Moolenaar26262f82019-09-04 20:59:15 +02002376 // Always open the file in binary mode, library functions have a mind of
2377 // their own about CR-LF conversion.
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002378 if (*fname == NUL || (fd = mch_fopen((char *)fname,
2379 append ? APPENDBIN : WRITEBIN)) == NULL)
2380 {
Bram Moolenaar806a2732022-09-04 15:40:36 +01002381 semsg(_(e_cant_create_file_str),
2382 *fname == NUL ? (char_u *)_("<empty>") : fname);
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002383 ret = -1;
2384 }
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002385 else
2386 {
Bram Moolenaar806a2732022-09-04 15:40:36 +01002387 if (defer)
2388 {
2389 typval_T tv;
2390
2391 tv.v_type = VAR_STRING;
2392 tv.v_lock = 0;
Bram Moolenaar6f14da12022-09-07 21:30:44 +01002393 tv.vval.v_string = FullName_save(fname, FALSE);
Bram Moolenaar806a2732022-09-04 15:40:36 +01002394 if (tv.vval.v_string == NULL
2395 || add_defer((char_u *)"delete", 1, &tv) == FAIL)
2396 {
2397 ret = -1;
2398 fclose(fd);
2399 (void)mch_remove(fname);
2400 }
2401 }
2402
2403 if (ret == 0)
2404 {
2405 if (blob)
2406 {
2407 if (write_blob(fd, blob) == FAIL)
2408 ret = -1;
2409 }
2410 else
2411 {
2412 if (write_list(fd, list, binary) == FAIL)
2413 ret = -1;
2414 }
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002415#ifdef HAVE_FSYNC
Bram Moolenaar806a2732022-09-04 15:40:36 +01002416 if (ret == 0 && do_fsync)
2417 // Ignore the error, the user wouldn't know what to do about
2418 // it. May happen for a device.
2419 vim_ignored = vim_fsync(fileno(fd));
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002420#endif
Bram Moolenaar806a2732022-09-04 15:40:36 +01002421 fclose(fd);
2422 }
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002423 }
2424
2425 rettv->vval.v_number = ret;
2426}
2427
2428#endif // FEAT_EVAL
2429
2430#if defined(FEAT_BROWSE) || defined(PROTO)
2431/*
2432 * Generic browse function. Calls gui_mch_browse() when possible.
2433 * Later this may pop-up a non-GUI file selector (external command?).
2434 */
2435 char_u *
2436do_browse(
Bram Moolenaar26262f82019-09-04 20:59:15 +02002437 int flags, // BROWSE_SAVE and BROWSE_DIR
2438 char_u *title, // title for the window
2439 char_u *dflt, // default file name (may include directory)
2440 char_u *ext, // extension added
2441 char_u *initdir, // initial directory, NULL for current dir or
2442 // when using path from "dflt"
2443 char_u *filter, // file name filter
2444 buf_T *buf) // buffer to read/write for
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002445{
2446 char_u *fname;
Bram Moolenaar26262f82019-09-04 20:59:15 +02002447 static char_u *last_dir = NULL; // last used directory
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002448 char_u *tofree = NULL;
Bram Moolenaare1004402020-10-24 20:49:43 +02002449 int save_cmod_flags = cmdmod.cmod_flags;
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002450
Bram Moolenaar26262f82019-09-04 20:59:15 +02002451 // Must turn off browse to avoid that autocommands will get the
2452 // flag too!
Bram Moolenaare1004402020-10-24 20:49:43 +02002453 cmdmod.cmod_flags &= ~CMOD_BROWSE;
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002454
2455 if (title == NULL || *title == NUL)
2456 {
2457 if (flags & BROWSE_DIR)
2458 title = (char_u *)_("Select Directory dialog");
2459 else if (flags & BROWSE_SAVE)
2460 title = (char_u *)_("Save File dialog");
2461 else
2462 title = (char_u *)_("Open File dialog");
2463 }
2464
Bram Moolenaar26262f82019-09-04 20:59:15 +02002465 // When no directory specified, use default file name, default dir, buffer
2466 // dir, last dir or current dir
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002467 if ((initdir == NULL || *initdir == NUL) && dflt != NULL && *dflt != NUL)
2468 {
Bram Moolenaar26262f82019-09-04 20:59:15 +02002469 if (mch_isdir(dflt)) // default file name is a directory
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002470 {
2471 initdir = dflt;
2472 dflt = NULL;
2473 }
Bram Moolenaar26262f82019-09-04 20:59:15 +02002474 else if (gettail(dflt) != dflt) // default file name includes a path
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002475 {
2476 tofree = vim_strsave(dflt);
2477 if (tofree != NULL)
2478 {
2479 initdir = tofree;
2480 *gettail(initdir) = NUL;
2481 dflt = gettail(dflt);
2482 }
2483 }
2484 }
2485
2486 if (initdir == NULL || *initdir == NUL)
2487 {
Bram Moolenaar26262f82019-09-04 20:59:15 +02002488 // When 'browsedir' is a directory, use it
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002489 if (STRCMP(p_bsdir, "last") != 0
2490 && STRCMP(p_bsdir, "buffer") != 0
2491 && STRCMP(p_bsdir, "current") != 0
2492 && mch_isdir(p_bsdir))
2493 initdir = p_bsdir;
Bram Moolenaar26262f82019-09-04 20:59:15 +02002494 // When saving or 'browsedir' is "buffer", use buffer fname
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002495 else if (((flags & BROWSE_SAVE) || *p_bsdir == 'b')
2496 && buf != NULL && buf->b_ffname != NULL)
2497 {
2498 if (dflt == NULL || *dflt == NUL)
2499 dflt = gettail(curbuf->b_ffname);
2500 tofree = vim_strsave(curbuf->b_ffname);
2501 if (tofree != NULL)
2502 {
2503 initdir = tofree;
2504 *gettail(initdir) = NUL;
2505 }
2506 }
Bram Moolenaar26262f82019-09-04 20:59:15 +02002507 // When 'browsedir' is "last", use dir from last browse
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002508 else if (*p_bsdir == 'l')
2509 initdir = last_dir;
Bram Moolenaar26262f82019-09-04 20:59:15 +02002510 // When 'browsedir is "current", use current directory. This is the
2511 // default already, leave initdir empty.
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002512 }
2513
2514# ifdef FEAT_GUI
Bram Moolenaar26262f82019-09-04 20:59:15 +02002515 if (gui.in_use) // when this changes, also adjust f_has()!
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002516 {
2517 if (filter == NULL
2518# ifdef FEAT_EVAL
2519 && (filter = get_var_value((char_u *)"b:browsefilter")) == NULL
2520 && (filter = get_var_value((char_u *)"g:browsefilter")) == NULL
2521# endif
2522 )
2523 filter = BROWSE_FILTER_DEFAULT;
2524 if (flags & BROWSE_DIR)
2525 {
2526# if defined(FEAT_GUI_GTK) || defined(MSWIN)
Bram Moolenaar26262f82019-09-04 20:59:15 +02002527 // For systems that have a directory dialog.
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002528 fname = gui_mch_browsedir(title, initdir);
2529# else
Bram Moolenaar26262f82019-09-04 20:59:15 +02002530 // Generic solution for selecting a directory: select a file and
2531 // remove the file name.
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002532 fname = gui_mch_browse(0, title, dflt, ext, initdir, (char_u *)"");
2533# endif
2534# if !defined(FEAT_GUI_GTK)
Bram Moolenaar26262f82019-09-04 20:59:15 +02002535 // Win32 adds a dummy file name, others return an arbitrary file
2536 // name. GTK+ 2 returns only the directory,
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002537 if (fname != NULL && *fname != NUL && !mch_isdir(fname))
2538 {
Bram Moolenaar26262f82019-09-04 20:59:15 +02002539 // Remove the file name.
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002540 char_u *tail = gettail_sep(fname);
2541
2542 if (tail == fname)
Bram Moolenaar26262f82019-09-04 20:59:15 +02002543 *tail++ = '.'; // use current dir
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002544 *tail = NUL;
2545 }
2546# endif
2547 }
2548 else
2549 fname = gui_mch_browse(flags & BROWSE_SAVE,
2550 title, dflt, ext, initdir, (char_u *)_(filter));
2551
Bram Moolenaar26262f82019-09-04 20:59:15 +02002552 // We hang around in the dialog for a while, the user might do some
2553 // things to our files. The Win32 dialog allows deleting or renaming
2554 // a file, check timestamps.
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002555 need_check_timestamps = TRUE;
2556 did_check_timestamps = FALSE;
2557 }
2558 else
2559# endif
2560 {
Bram Moolenaar26262f82019-09-04 20:59:15 +02002561 // TODO: non-GUI file selector here
Bram Moolenaareaaac012022-01-02 17:00:40 +00002562 emsg(_(e_sorry_no_file_browser_in_console_mode));
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002563 fname = NULL;
2564 }
2565
Bram Moolenaar26262f82019-09-04 20:59:15 +02002566 // keep the directory for next time
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002567 if (fname != NULL)
2568 {
2569 vim_free(last_dir);
2570 last_dir = vim_strsave(fname);
2571 if (last_dir != NULL && !(flags & BROWSE_DIR))
2572 {
2573 *gettail(last_dir) = NUL;
2574 if (*last_dir == NUL)
2575 {
Bram Moolenaar26262f82019-09-04 20:59:15 +02002576 // filename only returned, must be in current dir
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002577 vim_free(last_dir);
2578 last_dir = alloc(MAXPATHL);
2579 if (last_dir != NULL)
2580 mch_dirname(last_dir, MAXPATHL);
2581 }
2582 }
2583 }
2584
2585 vim_free(tofree);
Bram Moolenaare1004402020-10-24 20:49:43 +02002586 cmdmod.cmod_flags = save_cmod_flags;
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002587
2588 return fname;
2589}
2590#endif
2591
2592#if defined(FEAT_EVAL) || defined(PROTO)
2593
2594/*
2595 * "browse(save, title, initdir, default)" function
2596 */
2597 void
2598f_browse(typval_T *argvars UNUSED, typval_T *rettv)
2599{
2600# ifdef FEAT_BROWSE
2601 int save;
2602 char_u *title;
2603 char_u *initdir;
2604 char_u *defname;
2605 char_u buf[NUMBUFLEN];
2606 char_u buf2[NUMBUFLEN];
2607 int error = FALSE;
2608
Bram Moolenaarf28f2ac2021-03-22 22:21:26 +01002609 if (in_vim9script()
Yegappan Lakshmanan83494b42021-07-20 17:51:51 +02002610 && (check_for_bool_arg(argvars, 0) == FAIL
2611 || check_for_string_arg(argvars, 1) == FAIL
Bram Moolenaar32105ae2021-03-27 18:59:25 +01002612 || check_for_string_arg(argvars, 2) == FAIL
2613 || check_for_string_arg(argvars, 3) == FAIL))
Bram Moolenaarf28f2ac2021-03-22 22:21:26 +01002614 return;
Yegappan Lakshmanan83494b42021-07-20 17:51:51 +02002615
Bram Moolenaar5a049842022-10-08 12:52:09 +01002616 save = (int)tv_get_bool_chk(&argvars[0], &error);
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002617 title = tv_get_string_chk(&argvars[1]);
2618 initdir = tv_get_string_buf_chk(&argvars[2], buf);
2619 defname = tv_get_string_buf_chk(&argvars[3], buf2);
2620
2621 if (error || title == NULL || initdir == NULL || defname == NULL)
2622 rettv->vval.v_string = NULL;
2623 else
2624 rettv->vval.v_string =
2625 do_browse(save ? BROWSE_SAVE : 0,
2626 title, defname, NULL, initdir, NULL, curbuf);
2627# else
2628 rettv->vval.v_string = NULL;
2629# endif
2630 rettv->v_type = VAR_STRING;
2631}
2632
2633/*
2634 * "browsedir(title, initdir)" function
2635 */
2636 void
2637f_browsedir(typval_T *argvars UNUSED, typval_T *rettv)
2638{
2639# ifdef FEAT_BROWSE
2640 char_u *title;
2641 char_u *initdir;
2642 char_u buf[NUMBUFLEN];
2643
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +02002644 if (in_vim9script()
2645 && (check_for_string_arg(argvars, 0) == FAIL
2646 || check_for_string_arg(argvars, 1) == FAIL))
2647 return;
2648
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002649 title = tv_get_string_chk(&argvars[0]);
2650 initdir = tv_get_string_buf_chk(&argvars[1], buf);
2651
2652 if (title == NULL || initdir == NULL)
2653 rettv->vval.v_string = NULL;
2654 else
2655 rettv->vval.v_string = do_browse(BROWSE_DIR,
2656 title, NULL, NULL, initdir, NULL, curbuf);
2657# else
2658 rettv->vval.v_string = NULL;
2659# endif
2660 rettv->v_type = VAR_STRING;
2661}
2662
Shougo Matsushita60c87432024-06-03 22:59:27 +02002663/*
2664 * "filecopy()" function
2665 */
2666 void
2667f_filecopy(typval_T *argvars, typval_T *rettv)
2668{
2669 char_u *from;
2670 stat_T st;
2671
2672 rettv->vval.v_number = FALSE;
2673
2674 if (check_restricted() || check_secure()
2675 || check_for_string_arg(argvars, 0) == FAIL
2676 || check_for_string_arg(argvars, 1) == FAIL)
2677 return;
2678
2679 from = tv_get_string(&argvars[0]);
2680
2681 if (mch_lstat((char *)from, &st) >= 0
2682 && (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)))
2683 rettv->vval.v_number = vim_copyfile(
2684 tv_get_string(&argvars[0]),
2685 tv_get_string(&argvars[1])) == OK ? TRUE : FALSE;
2686}
2687
Bram Moolenaarb005cd82019-09-04 15:54:55 +02002688#endif // FEAT_EVAL
Bram Moolenaar26262f82019-09-04 20:59:15 +02002689
2690/*
2691 * Replace home directory by "~" in each space or comma separated file name in
2692 * 'src'.
2693 * If anything fails (except when out of space) dst equals src.
2694 */
2695 void
2696home_replace(
2697 buf_T *buf, // when not NULL, check for help files
2698 char_u *src, // input file name
2699 char_u *dst, // where to put the result
2700 int dstlen, // maximum length of the result
2701 int one) // if TRUE, only replace one file name, include
2702 // spaces and commas in the file name.
2703{
2704 size_t dirlen = 0, envlen = 0;
2705 size_t len;
2706 char_u *homedir_env, *homedir_env_orig;
2707 char_u *p;
2708
2709 if (src == NULL)
2710 {
2711 *dst = NUL;
2712 return;
2713 }
2714
2715 /*
2716 * If the file is a help file, remove the path completely.
2717 */
2718 if (buf != NULL && buf->b_help)
2719 {
2720 vim_snprintf((char *)dst, dstlen, "%s", gettail(src));
2721 return;
2722 }
2723
2724 /*
2725 * We check both the value of the $HOME environment variable and the
2726 * "real" home directory.
2727 */
2728 if (homedir != NULL)
2729 dirlen = STRLEN(homedir);
2730
2731#ifdef VMS
2732 homedir_env_orig = homedir_env = mch_getenv((char_u *)"SYS$LOGIN");
2733#else
2734 homedir_env_orig = homedir_env = mch_getenv((char_u *)"HOME");
2735#endif
2736#ifdef MSWIN
2737 if (homedir_env == NULL)
2738 homedir_env_orig = homedir_env = mch_getenv((char_u *)"USERPROFILE");
2739#endif
2740 // Empty is the same as not set.
2741 if (homedir_env != NULL && *homedir_env == NUL)
2742 homedir_env = NULL;
2743
2744 if (homedir_env != NULL && *homedir_env == '~')
2745 {
Mike Williams51024bb2024-05-30 07:46:30 +02002746 size_t usedlen = 0;
Yegappan Lakshmanan00d34592024-12-25 10:20:51 +01002747 size_t flen;
Bram Moolenaar26262f82019-09-04 20:59:15 +02002748 char_u *fbuf = NULL;
2749
Yegappan Lakshmanan00d34592024-12-25 10:20:51 +01002750 flen = STRLEN(homedir_env);
Bram Moolenaar26262f82019-09-04 20:59:15 +02002751 (void)modify_fname((char_u *)":p", FALSE, &usedlen,
2752 &homedir_env, &fbuf, &flen);
Yegappan Lakshmanan00d34592024-12-25 10:20:51 +01002753 flen = STRLEN(homedir_env);
Bram Moolenaar26262f82019-09-04 20:59:15 +02002754 if (flen > 0 && vim_ispathsep(homedir_env[flen - 1]))
2755 // Remove the trailing / that is added to a directory.
2756 homedir_env[flen - 1] = NUL;
2757 }
2758
2759 if (homedir_env != NULL)
2760 envlen = STRLEN(homedir_env);
2761
2762 if (!one)
2763 src = skipwhite(src);
2764 while (*src && dstlen > 0)
2765 {
2766 /*
2767 * Here we are at the beginning of a file name.
2768 * First, check to see if the beginning of the file name matches
2769 * $HOME or the "real" home directory. Check that there is a '/'
2770 * after the match (so that if e.g. the file is "/home/pieter/bla",
2771 * and the home directory is "/home/piet", the file does not end up
2772 * as "~er/bla" (which would seem to indicate the file "bla" in user
2773 * er's home directory)).
2774 */
2775 p = homedir;
2776 len = dirlen;
2777 for (;;)
2778 {
2779 if ( len
2780 && fnamencmp(src, p, len) == 0
2781 && (vim_ispathsep(src[len])
2782 || (!one && (src[len] == ',' || src[len] == ' '))
2783 || src[len] == NUL))
2784 {
2785 src += len;
2786 if (--dstlen > 0)
2787 *dst++ = '~';
2788
Bram Moolenaar0e390f42020-06-10 13:12:28 +02002789 // Do not add directory separator into dst, because dst is
2790 // expected to just return the directory name without the
2791 // directory separator '/'.
Bram Moolenaar26262f82019-09-04 20:59:15 +02002792 break;
2793 }
2794 if (p == homedir_env)
2795 break;
2796 p = homedir_env;
2797 len = envlen;
2798 }
2799
2800 // if (!one) skip to separator: space or comma
2801 while (*src && (one || (*src != ',' && *src != ' ')) && --dstlen > 0)
2802 *dst++ = *src++;
2803 // skip separator
2804 while ((*src == ' ' || *src == ',') && --dstlen > 0)
2805 *dst++ = *src++;
2806 }
2807 // if (dstlen == 0) out of space, what to do???
2808
2809 *dst = NUL;
2810
2811 if (homedir_env != homedir_env_orig)
2812 vim_free(homedir_env);
2813}
2814
2815/*
2816 * Like home_replace, store the replaced string in allocated memory.
2817 * When something fails, NULL is returned.
2818 */
2819 char_u *
2820home_replace_save(
2821 buf_T *buf, // when not NULL, check for help files
2822 char_u *src) // input file name
2823{
2824 char_u *dst;
2825 unsigned len;
2826
2827 len = 3; // space for "~/" and trailing NUL
2828 if (src != NULL) // just in case
2829 len += (unsigned)STRLEN(src);
2830 dst = alloc(len);
2831 if (dst != NULL)
2832 home_replace(buf, src, dst, len, TRUE);
2833 return dst;
2834}
2835
2836/*
2837 * Compare two file names and return:
2838 * FPC_SAME if they both exist and are the same file.
2839 * FPC_SAMEX if they both don't exist and have the same file name.
2840 * FPC_DIFF if they both exist and are different files.
2841 * FPC_NOTX if they both don't exist.
2842 * FPC_DIFFX if one of them doesn't exist.
2843 * For the first name environment variables are expanded if "expandenv" is
2844 * TRUE.
2845 */
2846 int
2847fullpathcmp(
2848 char_u *s1,
2849 char_u *s2,
2850 int checkname, // when both don't exist, check file names
2851 int expandenv)
2852{
2853#ifdef UNIX
2854 char_u exp1[MAXPATHL];
2855 char_u full1[MAXPATHL];
2856 char_u full2[MAXPATHL];
2857 stat_T st1, st2;
2858 int r1, r2;
2859
2860 if (expandenv)
2861 expand_env(s1, exp1, MAXPATHL);
2862 else
2863 vim_strncpy(exp1, s1, MAXPATHL - 1);
2864 r1 = mch_stat((char *)exp1, &st1);
2865 r2 = mch_stat((char *)s2, &st2);
2866 if (r1 != 0 && r2 != 0)
2867 {
Bram Moolenaar217e1b82019-12-01 21:41:28 +01002868 // if mch_stat() doesn't work, may compare the names
Bram Moolenaar26262f82019-09-04 20:59:15 +02002869 if (checkname)
2870 {
2871 if (fnamecmp(exp1, s2) == 0)
2872 return FPC_SAMEX;
2873 r1 = vim_FullName(exp1, full1, MAXPATHL, FALSE);
2874 r2 = vim_FullName(s2, full2, MAXPATHL, FALSE);
2875 if (r1 == OK && r2 == OK && fnamecmp(full1, full2) == 0)
2876 return FPC_SAMEX;
2877 }
2878 return FPC_NOTX;
2879 }
2880 if (r1 != 0 || r2 != 0)
2881 return FPC_DIFFX;
2882 if (st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino)
2883 return FPC_SAME;
2884 return FPC_DIFF;
2885#else
2886 char_u *exp1; // expanded s1
2887 char_u *full1; // full path of s1
2888 char_u *full2; // full path of s2
2889 int retval = FPC_DIFF;
2890 int r1, r2;
2891
2892 // allocate one buffer to store three paths (alloc()/free() is slow!)
2893 if ((exp1 = alloc(MAXPATHL * 3)) != NULL)
2894 {
2895 full1 = exp1 + MAXPATHL;
2896 full2 = full1 + MAXPATHL;
2897
2898 if (expandenv)
2899 expand_env(s1, exp1, MAXPATHL);
2900 else
2901 vim_strncpy(exp1, s1, MAXPATHL - 1);
2902 r1 = vim_FullName(exp1, full1, MAXPATHL, FALSE);
2903 r2 = vim_FullName(s2, full2, MAXPATHL, FALSE);
2904
2905 // If vim_FullName() fails, the file probably doesn't exist.
2906 if (r1 != OK && r2 != OK)
2907 {
2908 if (checkname && fnamecmp(exp1, s2) == 0)
2909 retval = FPC_SAMEX;
2910 else
2911 retval = FPC_NOTX;
2912 }
2913 else if (r1 != OK || r2 != OK)
2914 retval = FPC_DIFFX;
2915 else if (fnamecmp(full1, full2))
2916 retval = FPC_DIFF;
2917 else
2918 retval = FPC_SAME;
2919 vim_free(exp1);
2920 }
2921 return retval;
2922#endif
2923}
2924
2925/*
2926 * Get the tail of a path: the file name.
2927 * When the path ends in a path separator the tail is the NUL after it.
2928 * Fail safe: never returns NULL.
2929 */
2930 char_u *
2931gettail(char_u *fname)
2932{
2933 char_u *p1, *p2;
2934
2935 if (fname == NULL)
2936 return (char_u *)"";
2937 for (p1 = p2 = get_past_head(fname); *p2; ) // find last part of path
2938 {
2939 if (vim_ispathsep_nocolon(*p2))
2940 p1 = p2 + 1;
2941 MB_PTR_ADV(p2);
2942 }
2943 return p1;
2944}
2945
2946/*
2947 * Get pointer to tail of "fname", including path separators. Putting a NUL
2948 * here leaves the directory name. Takes care of "c:/" and "//".
2949 * Always returns a valid pointer.
2950 */
2951 char_u *
2952gettail_sep(char_u *fname)
2953{
2954 char_u *p;
2955 char_u *t;
2956
2957 p = get_past_head(fname); // don't remove the '/' from "c:/file"
2958 t = gettail(fname);
2959 while (t > p && after_pathsep(fname, t))
2960 --t;
2961#ifdef VMS
2962 // path separator is part of the path
2963 ++t;
2964#endif
2965 return t;
2966}
2967
2968/*
2969 * get the next path component (just after the next path separator).
2970 */
2971 char_u *
2972getnextcomp(char_u *fname)
2973{
2974 while (*fname && !vim_ispathsep(*fname))
2975 MB_PTR_ADV(fname);
2976 if (*fname)
2977 ++fname;
2978 return fname;
2979}
2980
2981/*
2982 * Get a pointer to one character past the head of a path name.
2983 * Unix: after "/"; DOS: after "c:\"; Amiga: after "disk:/"; Mac: no head.
2984 * If there is no head, path is returned.
2985 */
2986 char_u *
2987get_past_head(char_u *path)
2988{
2989 char_u *retval;
2990
2991#if defined(MSWIN)
2992 // may skip "c:"
Keith Thompson184f71c2024-01-04 21:19:04 +01002993 if (SAFE_isalpha(path[0]) && path[1] == ':')
Bram Moolenaar26262f82019-09-04 20:59:15 +02002994 retval = path + 2;
2995 else
2996 retval = path;
2997#else
2998# if defined(AMIGA)
2999 // may skip "label:"
3000 retval = vim_strchr(path, ':');
3001 if (retval == NULL)
3002 retval = path;
3003# else // Unix
3004 retval = path;
3005# endif
3006#endif
3007
3008 while (vim_ispathsep(*retval))
3009 ++retval;
3010
3011 return retval;
3012}
3013
3014/*
3015 * Return TRUE if 'c' is a path separator.
3016 * Note that for MS-Windows this includes the colon.
3017 */
3018 int
3019vim_ispathsep(int c)
3020{
3021#ifdef UNIX
3022 return (c == '/'); // UNIX has ':' inside file names
3023#else
3024# ifdef BACKSLASH_IN_FILENAME
3025 return (c == ':' || c == '/' || c == '\\');
3026# else
3027# ifdef VMS
3028 // server"user passwd"::device:[full.path.name]fname.extension;version"
3029 return (c == ':' || c == '[' || c == ']' || c == '/'
3030 || c == '<' || c == '>' || c == '"' );
3031# else
3032 return (c == ':' || c == '/');
3033# endif // VMS
3034# endif
3035#endif
3036}
3037
3038/*
3039 * Like vim_ispathsep(c), but exclude the colon for MS-Windows.
3040 */
3041 int
3042vim_ispathsep_nocolon(int c)
3043{
3044 return vim_ispathsep(c)
3045#ifdef BACKSLASH_IN_FILENAME
3046 && c != ':'
3047#endif
3048 ;
3049}
3050
3051/*
Bram Moolenaar26262f82019-09-04 20:59:15 +02003052 * Return TRUE if the directory of "fname" exists, FALSE otherwise.
3053 * Also returns TRUE if there is no directory name.
3054 * "fname" must be writable!.
3055 */
3056 int
3057dir_of_file_exists(char_u *fname)
3058{
3059 char_u *p;
3060 int c;
3061 int retval;
3062
3063 p = gettail_sep(fname);
3064 if (p == fname)
3065 return TRUE;
3066 c = *p;
3067 *p = NUL;
3068 retval = mch_isdir(fname);
3069 *p = c;
3070 return retval;
3071}
3072
3073/*
3074 * Versions of fnamecmp() and fnamencmp() that handle '/' and '\' equally
3075 * and deal with 'fileignorecase'.
3076 */
3077 int
3078vim_fnamecmp(char_u *x, char_u *y)
3079{
3080#ifdef BACKSLASH_IN_FILENAME
3081 return vim_fnamencmp(x, y, MAXPATHL);
3082#else
3083 if (p_fic)
3084 return MB_STRICMP(x, y);
3085 return STRCMP(x, y);
3086#endif
3087}
3088
3089 int
3090vim_fnamencmp(char_u *x, char_u *y, size_t len)
3091{
3092#ifdef BACKSLASH_IN_FILENAME
3093 char_u *px = x;
3094 char_u *py = y;
3095 int cx = NUL;
3096 int cy = NUL;
3097
3098 while (len > 0)
3099 {
3100 cx = PTR2CHAR(px);
3101 cy = PTR2CHAR(py);
3102 if (cx == NUL || cy == NUL
3103 || ((p_fic ? MB_TOLOWER(cx) != MB_TOLOWER(cy) : cx != cy)
3104 && !(cx == '/' && cy == '\\')
3105 && !(cx == '\\' && cy == '/')))
3106 break;
Bram Moolenaar1614a142019-10-06 22:00:13 +02003107 len -= mb_ptr2len(px);
3108 px += mb_ptr2len(px);
3109 py += mb_ptr2len(py);
Bram Moolenaar26262f82019-09-04 20:59:15 +02003110 }
3111 if (len == 0)
3112 return 0;
3113 return (cx - cy);
3114#else
3115 if (p_fic)
3116 return MB_STRNICMP(x, y, len);
3117 return STRNCMP(x, y, len);
3118#endif
3119}
3120
3121/*
3122 * Concatenate file names fname1 and fname2 into allocated memory.
3123 * Only add a '/' or '\\' when 'sep' is TRUE and it is necessary.
3124 */
3125 char_u *
3126concat_fnames(char_u *fname1, char_u *fname2, int sep)
3127{
3128 char_u *dest;
3129
Christian Brabandt6cc30272024-12-13 17:54:33 +01003130 dest = alloc(STRLEN(fname1) + STRLEN(fname2) + 3);
Yegappan Lakshmanan1cfb14a2023-01-09 19:04:23 +00003131 if (dest == NULL)
3132 return NULL;
3133
Christian Brabandt6cc30272024-12-13 17:54:33 +01003134 STRCPY(dest, fname1);
3135 if (sep)
3136 add_pathsep(dest);
3137 STRCAT(dest, fname2);
Bram Moolenaar26262f82019-09-04 20:59:15 +02003138 return dest;
3139}
3140
3141/*
3142 * Add a path separator to a file name, unless it already ends in a path
3143 * separator.
3144 */
3145 void
3146add_pathsep(char_u *p)
3147{
Christian Brabandt6cc30272024-12-13 17:54:33 +01003148 if (*p != NUL && !after_pathsep(p, p + STRLEN(p)))
3149 STRCAT(p, PATHSEPSTR);
Bram Moolenaar26262f82019-09-04 20:59:15 +02003150}
3151
3152/*
3153 * FullName_save - Make an allocated copy of a full file name.
3154 * Returns NULL when out of memory.
3155 */
3156 char_u *
3157FullName_save(
3158 char_u *fname,
3159 int force) // force expansion, even when it already looks
3160 // like a full path name
3161{
3162 char_u *buf;
3163 char_u *new_fname = NULL;
3164
3165 if (fname == NULL)
3166 return NULL;
3167
3168 buf = alloc(MAXPATHL);
Yegappan Lakshmanan1cfb14a2023-01-09 19:04:23 +00003169 if (buf == NULL)
3170 return NULL;
3171
3172 if (vim_FullName(fname, buf, MAXPATHL, force) != FAIL)
3173 new_fname = vim_strsave(buf);
3174 else
3175 new_fname = vim_strsave(fname);
3176 vim_free(buf);
Bram Moolenaar26262f82019-09-04 20:59:15 +02003177 return new_fname;
3178}
3179
3180/*
3181 * return TRUE if "fname" exists.
3182 */
3183 int
3184vim_fexists(char_u *fname)
3185{
3186 stat_T st;
3187
3188 if (mch_stat((char *)fname, &st))
3189 return FALSE;
3190 return TRUE;
3191}
3192
3193/*
3194 * Invoke expand_wildcards() for one pattern.
3195 * Expand items like "%:h" before the expansion.
3196 * Returns OK or FAIL.
3197 */
3198 int
3199expand_wildcards_eval(
3200 char_u **pat, // pointer to input pattern
3201 int *num_file, // resulting number of files
3202 char_u ***file, // array of resulting files
3203 int flags) // EW_DIR, etc.
3204{
3205 int ret = FAIL;
3206 char_u *eval_pat = NULL;
3207 char_u *exp_pat = *pat;
Bram Moolenaarf5724372022-09-02 19:45:15 +01003208 char *ignored_msg;
Mike Williams51024bb2024-05-30 07:46:30 +02003209 size_t usedlen;
Bram Moolenaarf5724372022-09-02 19:45:15 +01003210 int is_cur_alt_file = *exp_pat == '%' || *exp_pat == '#';
3211 int star_follows = FALSE;
Bram Moolenaar26262f82019-09-04 20:59:15 +02003212
Bram Moolenaarf5724372022-09-02 19:45:15 +01003213 if (is_cur_alt_file || *exp_pat == '<')
Bram Moolenaar26262f82019-09-04 20:59:15 +02003214 {
3215 ++emsg_off;
3216 eval_pat = eval_vars(exp_pat, exp_pat, &usedlen,
Bram Moolenaara96edb72022-04-28 17:52:24 +01003217 NULL, &ignored_msg, NULL, TRUE);
Bram Moolenaar26262f82019-09-04 20:59:15 +02003218 --emsg_off;
3219 if (eval_pat != NULL)
Bram Moolenaarf5724372022-09-02 19:45:15 +01003220 {
3221 star_follows = STRCMP(exp_pat + usedlen, "*") == 0;
Bram Moolenaar26262f82019-09-04 20:59:15 +02003222 exp_pat = concat_str(eval_pat, exp_pat + usedlen);
Bram Moolenaarf5724372022-09-02 19:45:15 +01003223 }
Bram Moolenaar26262f82019-09-04 20:59:15 +02003224 }
3225
3226 if (exp_pat != NULL)
3227 ret = expand_wildcards(1, &exp_pat, num_file, file, flags);
3228
3229 if (eval_pat != NULL)
3230 {
Bram Moolenaarf5724372022-09-02 19:45:15 +01003231 if (*num_file == 0 && is_cur_alt_file && star_follows)
3232 {
3233 // Expanding "%" or "#" and the file does not exist: Add the
3234 // pattern anyway (without the star) so that this works for remote
3235 // files and non-file buffer names.
3236 *file = ALLOC_ONE(char_u *);
3237 if (*file != NULL)
3238 {
3239 **file = eval_pat;
3240 eval_pat = NULL;
3241 *num_file = 1;
3242 ret = OK;
3243 }
3244 }
Bram Moolenaar26262f82019-09-04 20:59:15 +02003245 vim_free(exp_pat);
3246 vim_free(eval_pat);
3247 }
3248
3249 return ret;
3250}
3251
3252/*
3253 * Expand wildcards. Calls gen_expand_wildcards() and removes files matching
3254 * 'wildignore'.
3255 * Returns OK or FAIL. When FAIL then "num_files" won't be set.
3256 */
3257 int
3258expand_wildcards(
3259 int num_pat, // number of input patterns
3260 char_u **pat, // array of input patterns
3261 int *num_files, // resulting number of files
3262 char_u ***files, // array of resulting files
3263 int flags) // EW_DIR, etc.
3264{
3265 int retval;
3266 int i, j;
3267 char_u *p;
3268 int non_suf_match; // number without matching suffix
3269
3270 retval = gen_expand_wildcards(num_pat, pat, num_files, files, flags);
3271
3272 // When keeping all matches, return here
3273 if ((flags & EW_KEEPALL) || retval == FAIL)
3274 return retval;
3275
Bram Moolenaar26262f82019-09-04 20:59:15 +02003276 /*
3277 * Remove names that match 'wildignore'.
3278 */
3279 if (*p_wig)
3280 {
3281 char_u *ffname;
3282
3283 // check all files in (*files)[]
3284 for (i = 0; i < *num_files; ++i)
3285 {
3286 ffname = FullName_save((*files)[i], FALSE);
3287 if (ffname == NULL) // out of memory
3288 break;
3289# ifdef VMS
3290 vms_remove_version(ffname);
3291# endif
3292 if (match_file_list(p_wig, (*files)[i], ffname))
3293 {
3294 // remove this matching file from the list
3295 vim_free((*files)[i]);
3296 for (j = i; j + 1 < *num_files; ++j)
3297 (*files)[j] = (*files)[j + 1];
3298 --*num_files;
3299 --i;
3300 }
3301 vim_free(ffname);
3302 }
3303
3304 // If the number of matches is now zero, we fail.
3305 if (*num_files == 0)
3306 {
3307 VIM_CLEAR(*files);
3308 return FAIL;
3309 }
3310 }
Bram Moolenaar26262f82019-09-04 20:59:15 +02003311
3312 /*
3313 * Move the names where 'suffixes' match to the end.
Bram Moolenaar57e95172022-08-20 19:26:14 +01003314 * Skip when interrupted, the result probably won't be used.
Bram Moolenaar26262f82019-09-04 20:59:15 +02003315 */
Bram Moolenaar57e95172022-08-20 19:26:14 +01003316 if (*num_files > 1 && !got_int)
Bram Moolenaar26262f82019-09-04 20:59:15 +02003317 {
3318 non_suf_match = 0;
3319 for (i = 0; i < *num_files; ++i)
3320 {
3321 if (!match_suffix((*files)[i]))
3322 {
3323 /*
3324 * Move the name without matching suffix to the front
3325 * of the list.
3326 */
3327 p = (*files)[i];
3328 for (j = i; j > non_suf_match; --j)
3329 (*files)[j] = (*files)[j - 1];
3330 (*files)[non_suf_match++] = p;
3331 }
3332 }
3333 }
3334
3335 return retval;
3336}
3337
3338/*
3339 * Return TRUE if "fname" matches with an entry in 'suffixes'.
3340 */
3341 int
3342match_suffix(char_u *fname)
3343{
3344 int fnamelen, setsuflen;
3345 char_u *setsuf;
3346#define MAXSUFLEN 30 // maximum length of a file suffix
3347 char_u suf_buf[MAXSUFLEN];
3348
3349 fnamelen = (int)STRLEN(fname);
3350 setsuflen = 0;
3351 for (setsuf = p_su; *setsuf; )
3352 {
3353 setsuflen = copy_option_part(&setsuf, suf_buf, MAXSUFLEN, ".,");
3354 if (setsuflen == 0)
3355 {
3356 char_u *tail = gettail(fname);
3357
3358 // empty entry: match name without a '.'
3359 if (vim_strchr(tail, '.') == NULL)
3360 {
3361 setsuflen = 1;
3362 break;
3363 }
3364 }
3365 else
3366 {
3367 if (fnamelen >= setsuflen
3368 && fnamencmp(suf_buf, fname + fnamelen - setsuflen,
3369 (size_t)setsuflen) == 0)
3370 break;
3371 setsuflen = 0;
3372 }
3373 }
3374 return (setsuflen != 0);
3375}
3376
3377#ifdef VIM_BACKTICK
3378
3379/*
3380 * Return TRUE if we can expand this backtick thing here.
3381 */
3382 static int
3383vim_backtick(char_u *p)
3384{
3385 return (*p == '`' && *(p + 1) != NUL && *(p + STRLEN(p) - 1) == '`');
3386}
3387
3388/*
3389 * Expand an item in `backticks` by executing it as a command.
3390 * Currently only works when pat[] starts and ends with a `.
3391 * Returns number of file names found, -1 if an error is encountered.
3392 */
3393 static int
3394expand_backtick(
3395 garray_T *gap,
3396 char_u *pat,
3397 int flags) // EW_* flags
3398{
3399 char_u *p;
3400 char_u *cmd;
3401 char_u *buffer;
3402 int cnt = 0;
3403 int i;
3404
3405 // Create the command: lop off the backticks.
Bram Moolenaar71ccd032020-06-12 22:59:11 +02003406 cmd = vim_strnsave(pat + 1, STRLEN(pat) - 2);
Bram Moolenaar26262f82019-09-04 20:59:15 +02003407 if (cmd == NULL)
3408 return -1;
3409
3410#ifdef FEAT_EVAL
3411 if (*cmd == '=') // `={expr}`: Expand expression
Bram Moolenaara4e0b972022-10-01 19:43:52 +01003412 buffer = eval_to_string(cmd + 1, TRUE, FALSE);
Bram Moolenaar26262f82019-09-04 20:59:15 +02003413 else
3414#endif
3415 buffer = get_cmd_output(cmd, NULL,
3416 (flags & EW_SILENT) ? SHELL_SILENT : 0, NULL);
3417 vim_free(cmd);
3418 if (buffer == NULL)
3419 return -1;
3420
3421 cmd = buffer;
3422 while (*cmd != NUL)
3423 {
3424 cmd = skipwhite(cmd); // skip over white space
3425 p = cmd;
3426 while (*p != NUL && *p != '\r' && *p != '\n') // skip over entry
3427 ++p;
3428 // add an entry if it is not empty
3429 if (p > cmd)
3430 {
3431 i = *p;
3432 *p = NUL;
3433 addfile(gap, cmd, flags);
3434 *p = i;
3435 ++cnt;
3436 }
3437 cmd = p;
3438 while (*cmd != NUL && (*cmd == '\r' || *cmd == '\n'))
3439 ++cmd;
3440 }
3441
3442 vim_free(buffer);
3443 return cnt;
3444}
3445#endif // VIM_BACKTICK
3446
Christian Brabandt6cc30272024-12-13 17:54:33 +01003447#if defined(MSWIN)
Bram Moolenaar26262f82019-09-04 20:59:15 +02003448/*
Christian Brabandt6cc30272024-12-13 17:54:33 +01003449 * File name expansion code for MS-DOS, Win16 and Win32. It's here because
Bram Moolenaar26262f82019-09-04 20:59:15 +02003450 * it's shared between these systems.
3451 */
3452
3453/*
Christian Brabandt6cc30272024-12-13 17:54:33 +01003454 * comparison function for qsort in dos_expandpath()
Bram Moolenaar26262f82019-09-04 20:59:15 +02003455 */
3456 static int
3457pstrcmp(const void *a, const void *b)
3458{
3459 return (pathcmp(*(char **)a, *(char **)b, -1));
3460}
3461
3462/*
3463 * Recursively expand one path component into all matching files and/or
3464 * directories. Adds matches to "gap". Handles "*", "?", "[a-z]", "**", etc.
3465 * Return the number of matches found.
3466 * "path" has backslashes before chars that are not to be expanded, starting
3467 * at "path[wildoff]".
3468 * Return the number of matches found.
Christian Brabandt6cc30272024-12-13 17:54:33 +01003469 * NOTE: much of this is identical to unix_expandpath(), keep in sync!
Bram Moolenaar26262f82019-09-04 20:59:15 +02003470 */
Christian Brabandt6cc30272024-12-13 17:54:33 +01003471 static int
3472dos_expandpath(
Bram Moolenaar26262f82019-09-04 20:59:15 +02003473 garray_T *gap,
Christian Brabandt6cc30272024-12-13 17:54:33 +01003474 char_u *path,
zeertzjq00a749b2025-03-15 09:53:32 +01003475 size_t wildoff,
Christian Brabandt6cc30272024-12-13 17:54:33 +01003476 int flags, // EW_* flags
3477 int didstar) // expanded "**" once already
Bram Moolenaar26262f82019-09-04 20:59:15 +02003478{
Christian Brabandt6cc30272024-12-13 17:54:33 +01003479 char_u *buf;
3480 char_u *path_end;
3481 char_u *p, *s, *e;
3482 int start_len = gap->ga_len;
3483 char_u *pat;
Bram Moolenaar26262f82019-09-04 20:59:15 +02003484 regmatch_T regmatch;
Christian Brabandt6cc30272024-12-13 17:54:33 +01003485 int starts_with_dot;
3486 int matches;
zeertzjq00a749b2025-03-15 09:53:32 +01003487 size_t buflen;
3488 size_t len;
Christian Brabandt6cc30272024-12-13 17:54:33 +01003489 int starstar = FALSE;
Bram Moolenaar26262f82019-09-04 20:59:15 +02003490 static int stardepth = 0; // depth for "**" expansion
Christian Brabandt6cc30272024-12-13 17:54:33 +01003491 HANDLE hFind = INVALID_HANDLE_VALUE;
3492 WIN32_FIND_DATAW wfb;
3493 WCHAR *wn = NULL; // UCS-2 name, NULL when not used.
3494 char_u *matchname;
3495 int ok;
3496 char_u *p_alt;
Bram Moolenaar26262f82019-09-04 20:59:15 +02003497
3498 // Expanding "**" may take a long time, check for CTRL-C.
3499 if (stardepth > 0)
3500 {
3501 ui_breakcheck();
3502 if (got_int)
3503 return 0;
3504 }
3505
zeertzjq00a749b2025-03-15 09:53:32 +01003506 // Make room for file name (a bit too much to stay on the safe side).
3507 buflen = STRLEN(path) + MAXPATHL;
3508 buf = alloc(buflen);
Bram Moolenaar26262f82019-09-04 20:59:15 +02003509 if (buf == NULL)
3510 return 0;
3511
3512 /*
3513 * Find the first part in the path name that contains a wildcard or a ~1.
Christian Brabandt6cc30272024-12-13 17:54:33 +01003514 * Copy it into buf, including the preceding characters.
Bram Moolenaar26262f82019-09-04 20:59:15 +02003515 */
3516 p = buf;
3517 s = buf;
3518 e = NULL;
3519 path_end = path;
3520 while (*path_end != NUL)
3521 {
3522 // May ignore a wildcard that has a backslash before it; it will
3523 // be removed by rem_backslash() or file_pat_to_reg_pat() below.
3524 if (path_end >= path + wildoff && rem_backslash(path_end))
3525 *p++ = *path_end++;
Christian Brabandt6cc30272024-12-13 17:54:33 +01003526 else if (*path_end == '\\' || *path_end == ':' || *path_end == '/')
Bram Moolenaar26262f82019-09-04 20:59:15 +02003527 {
3528 if (e != NULL)
3529 break;
3530 s = p + 1;
3531 }
3532 else if (path_end >= path + wildoff
Christian Brabandt6cc30272024-12-13 17:54:33 +01003533 && vim_strchr((char_u *)"*?[~", *path_end) != NULL)
Bram Moolenaar26262f82019-09-04 20:59:15 +02003534 e = p;
3535 if (has_mbyte)
3536 {
zeertzjq00a749b2025-03-15 09:53:32 +01003537 int charlen = (*mb_ptr2len)(path_end);
3538
3539 STRNCPY(p, path_end, (size_t)charlen);
3540 p += charlen;
3541 path_end += charlen;
Christian Brabandt6cc30272024-12-13 17:54:33 +01003542 }
3543 else
3544 *p++ = *path_end++;
3545 }
3546 e = p;
3547 *e = NUL;
3548
3549 // now we have one wildcard component between s and e
3550 // Remove backslashes between "wildoff" and the start of the wildcard
3551 // component.
3552 for (p = buf + wildoff; p < s; ++p)
3553 if (rem_backslash(p))
3554 {
3555 STRMOVE(p, p + 1);
3556 --e;
3557 --s;
3558 }
3559
3560 // Check for "**" between "s" and "e".
3561 for (p = s; p < e; ++p)
3562 if (p[0] == '*' && p[1] == '*')
3563 starstar = TRUE;
3564
3565 starts_with_dot = *s == '.';
3566 pat = file_pat_to_reg_pat(s, e, NULL, FALSE);
3567 if (pat == NULL)
3568 {
3569 vim_free(buf);
3570 return 0;
3571 }
3572
3573 // compile the regexp into a program
3574 if (flags & (EW_NOERROR | EW_NOTWILD))
3575 ++emsg_silent;
3576 regmatch.rm_ic = TRUE; // Always ignore case
3577 regmatch.regprog = vim_regcomp(pat, RE_MAGIC);
3578 if (flags & (EW_NOERROR | EW_NOTWILD))
3579 --emsg_silent;
3580 vim_free(pat);
3581
3582 if (regmatch.regprog == NULL && (flags & EW_NOTWILD) == 0)
3583 {
3584 vim_free(buf);
3585 return 0;
3586 }
3587
3588 // remember the pattern or file name being looked for
3589 matchname = vim_strsave(s);
3590
zeertzjq00a749b2025-03-15 09:53:32 +01003591 len = (size_t)(s - buf);
Christian Brabandt6cc30272024-12-13 17:54:33 +01003592 // If "**" is by itself, this is the first time we encounter it and more
3593 // is following then find matches without any directory.
3594 if (!didstar && stardepth < 100 && starstar && e - s == 2
3595 && *path_end == '/')
3596 {
zeertzjq00a749b2025-03-15 09:53:32 +01003597 vim_snprintf((char *)s, buflen - len, "%s", path_end + 1);
Christian Brabandt6cc30272024-12-13 17:54:33 +01003598 ++stardepth;
zeertzjq00a749b2025-03-15 09:53:32 +01003599 (void)dos_expandpath(gap, buf, len, flags, TRUE);
Christian Brabandt6cc30272024-12-13 17:54:33 +01003600 --stardepth;
3601 }
3602
3603 // Scan all files in the directory with "dir/ *.*"
John Marriott10644262025-03-16 19:06:31 +01003604 vim_snprintf((char *)s, buflen - len, "*.*");
Christian Brabandt6cc30272024-12-13 17:54:33 +01003605 wn = enc_to_utf16(buf, NULL);
3606 if (wn != NULL)
3607 hFind = FindFirstFileW(wn, &wfb);
3608 ok = (hFind != INVALID_HANDLE_VALUE);
3609
3610 while (ok)
3611 {
3612 p = utf16_to_enc(wfb.cFileName, NULL); // p is allocated here
3613
3614 if (p == NULL)
3615 break; // out of memory
3616
3617 // Do not use the alternate filename when the file name ends in '~',
3618 // because it picks up backup files: short name for "foo.vim~" is
3619 // "foo~1.vim", which matches "*.vim".
3620 if (*wfb.cAlternateFileName == NUL || p[STRLEN(p) - 1] == '~')
3621 p_alt = NULL;
3622 else
3623 p_alt = utf16_to_enc(wfb.cAlternateFileName, NULL);
3624
zeertzjq00a749b2025-03-15 09:53:32 +01003625 len = (size_t)(s - buf);
Christian Brabandt6cc30272024-12-13 17:54:33 +01003626 // Ignore entries starting with a dot, unless when asked for. Accept
3627 // all entries found with "matchname".
3628 if ((p[0] != '.' || starts_with_dot
3629 || ((flags & EW_DODOT)
3630 && p[1] != NUL && (p[1] != '.' || p[2] != NUL)))
3631 && (matchname == NULL
3632 || (regmatch.regprog != NULL
3633 && (vim_regexec(&regmatch, p, (colnr_T)0)
3634 || (p_alt != NULL
3635 && vim_regexec(&regmatch, p_alt, (colnr_T)0))))
3636 || ((flags & EW_NOTWILD)
zeertzjq00a749b2025-03-15 09:53:32 +01003637 && fnamencmp(path + len, p, e - s) == 0)))
Christian Brabandt6cc30272024-12-13 17:54:33 +01003638 {
zeertzjq00a749b2025-03-15 09:53:32 +01003639 len += vim_snprintf((char *)s, buflen - len, "%s", p);
3640 if (len + 1 < buflen)
3641 {
3642 if (starstar && stardepth < 100
Christian Brabandt6cc30272024-12-13 17:54:33 +01003643 && (wfb.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
zeertzjq00a749b2025-03-15 09:53:32 +01003644 {
3645 // For "**" in the pattern first go deeper in the tree to
3646 // find matches.
3647 vim_snprintf((char *)buf + len, buflen - len,
3648 "/**%s", path_end);
3649 ++stardepth;
3650 (void)dos_expandpath(gap, buf, len + 1, flags, TRUE);
3651 --stardepth;
3652 }
Christian Brabandt6cc30272024-12-13 17:54:33 +01003653
zeertzjq00a749b2025-03-15 09:53:32 +01003654 vim_snprintf((char *)buf + len, buflen - len, "%s", path_end);
3655 if (mch_has_exp_wildcard(path_end))
3656 {
3657 // need to expand another component of the path
3658 // remove backslashes for the remaining components only
3659 (void)dos_expandpath(gap, buf, len + 1, flags, FALSE);
3660 }
3661 else
3662 {
3663 stat_T sb;
Christian Brabandt6cc30272024-12-13 17:54:33 +01003664
zeertzjq00a749b2025-03-15 09:53:32 +01003665 // no more wildcards, check if there is a match
3666 // remove backslashes for the remaining components only
3667 if (*path_end != 0)
3668 backslash_halve(buf + len + 1);
3669 // add existing file
3670 if ((flags & EW_ALLLINKS) ? mch_lstat((char *)buf, &sb) >= 0
3671 : mch_getperm(buf) >= 0)
3672 addfile(gap, buf, flags);
3673 }
Christian Brabandt6cc30272024-12-13 17:54:33 +01003674 }
3675 }
3676
3677 vim_free(p_alt);
3678 vim_free(p);
3679 ok = FindNextFileW(hFind, &wfb);
3680 }
3681
3682 FindClose(hFind);
3683 vim_free(wn);
3684 vim_free(buf);
3685 vim_regfree(regmatch.regprog);
3686 vim_free(matchname);
3687
3688 matches = gap->ga_len - start_len;
3689 if (matches > 0)
3690 qsort(((char_u **)gap->ga_data) + start_len, (size_t)matches,
3691 sizeof(char_u *), pstrcmp);
3692 return matches;
3693}
3694
3695 int
3696mch_expandpath(
3697 garray_T *gap,
3698 char_u *path,
3699 int flags) // EW_* flags
3700{
3701 return dos_expandpath(gap, path, 0, flags, FALSE);
3702}
3703#endif // MSWIN
3704
3705#if (defined(UNIX) && !defined(VMS)) || defined(USE_UNIXFILENAME) \
3706 || defined(PROTO)
3707/*
3708 * Unix style wildcard expansion code.
3709 * It's here because it's used both for Unix and Mac.
3710 */
3711 static int
3712pstrcmp(const void *a, const void *b)
3713{
3714 return (pathcmp(*(char **)a, *(char **)b, -1));
3715}
3716
3717/*
3718 * Recursively expand one path component into all matching files and/or
3719 * directories. Adds matches to "gap". Handles "*", "?", "[a-z]", "**", etc.
3720 * "path" has backslashes before chars that are not to be expanded, starting
3721 * at "path + wildoff".
3722 * Return the number of matches found.
3723 * NOTE: much of this is identical to dos_expandpath(), keep in sync!
3724 */
3725 int
3726unix_expandpath(
3727 garray_T *gap,
3728 char_u *path,
zeertzjq00a749b2025-03-15 09:53:32 +01003729 size_t wildoff,
Christian Brabandt6cc30272024-12-13 17:54:33 +01003730 int flags, // EW_* flags
3731 int didstar) // expanded "**" once already
3732{
3733 char_u *buf;
3734 char_u *path_end;
3735 char_u *p, *s, *e;
3736 int start_len = gap->ga_len;
3737 char_u *pat;
3738 regmatch_T regmatch;
3739 int starts_with_dot;
3740 int matches;
zeertzjq00a749b2025-03-15 09:53:32 +01003741 size_t buflen;
3742 size_t len;
Christian Brabandt6cc30272024-12-13 17:54:33 +01003743 int starstar = FALSE;
3744 static int stardepth = 0; // depth for "**" expansion
3745
3746 DIR *dirp;
3747 struct dirent *dp;
3748
3749 // Expanding "**" may take a long time, check for CTRL-C.
3750 if (stardepth > 0)
3751 {
3752 ui_breakcheck();
3753 if (got_int)
3754 return 0;
3755 }
3756
zeertzjq00a749b2025-03-15 09:53:32 +01003757 // Make room for file name (a bit too much to stay on the safe side).
3758 buflen = STRLEN(path) + MAXPATHL;
Christian Brabandt6cc30272024-12-13 17:54:33 +01003759 buf = alloc(buflen);
3760 if (buf == NULL)
3761 return 0;
3762
3763 /*
3764 * Find the first part in the path name that contains a wildcard.
3765 * When EW_ICASE is set every letter is considered to be a wildcard.
3766 * Copy it into "buf", including the preceding characters.
3767 */
3768 p = buf;
3769 s = buf;
3770 e = NULL;
3771 path_end = path;
3772 while (*path_end != NUL)
3773 {
3774 // May ignore a wildcard that has a backslash before it; it will
3775 // be removed by rem_backslash() or file_pat_to_reg_pat() below.
3776 if (path_end >= path + wildoff && rem_backslash(path_end))
3777 *p++ = *path_end++;
3778 else if (*path_end == '/')
3779 {
3780 if (e != NULL)
3781 break;
3782 s = p + 1;
3783 }
3784 else if (path_end >= path + wildoff
3785 && (vim_strchr((char_u *)"*?[{~$", *path_end) != NULL
3786 || (!p_fic && (flags & EW_ICASE)
3787 && vim_isalpha(PTR2CHAR(path_end)))))
3788 e = p;
3789 if (has_mbyte)
3790 {
zeertzjq00a749b2025-03-15 09:53:32 +01003791 int charlen = (*mb_ptr2len)(path_end);
3792
3793 STRNCPY(p, path_end, (size_t)charlen);
3794 p += charlen;
3795 path_end += charlen;
Bram Moolenaar26262f82019-09-04 20:59:15 +02003796 }
3797 else
3798 *p++ = *path_end++;
3799 }
3800 e = p;
3801 *e = NUL;
3802
3803 // Now we have one wildcard component between "s" and "e".
3804 // Remove backslashes between "wildoff" and the start of the wildcard
3805 // component.
Christian Brabandt6cc30272024-12-13 17:54:33 +01003806 for (p = buf + wildoff; p < s; ++p)
3807 if (rem_backslash(p))
Bram Moolenaar26262f82019-09-04 20:59:15 +02003808 {
Christian Brabandt6cc30272024-12-13 17:54:33 +01003809 STRMOVE(p, p + 1);
3810 --e;
3811 --s;
3812 }
Bram Moolenaar26262f82019-09-04 20:59:15 +02003813
3814 // Check for "**" between "s" and "e".
3815 for (p = s; p < e; ++p)
3816 if (p[0] == '*' && p[1] == '*')
3817 starstar = TRUE;
3818
3819 // convert the file pattern to a regexp pattern
3820 starts_with_dot = *s == '.';
3821 pat = file_pat_to_reg_pat(s, e, NULL, FALSE);
3822 if (pat == NULL)
3823 {
3824 vim_free(buf);
3825 return 0;
3826 }
3827
3828 // compile the regexp into a program
3829 if (flags & EW_ICASE)
Christian Brabandt6cc30272024-12-13 17:54:33 +01003830 regmatch.rm_ic = TRUE; // 'wildignorecase' set
Bram Moolenaar26262f82019-09-04 20:59:15 +02003831 else
Christian Brabandt6cc30272024-12-13 17:54:33 +01003832 regmatch.rm_ic = p_fic; // ignore case when 'fileignorecase' is set
Bram Moolenaar26262f82019-09-04 20:59:15 +02003833 if (flags & (EW_NOERROR | EW_NOTWILD))
3834 ++emsg_silent;
3835 regmatch.regprog = vim_regcomp(pat, RE_MAGIC);
3836 if (flags & (EW_NOERROR | EW_NOTWILD))
3837 --emsg_silent;
3838 vim_free(pat);
3839
3840 if (regmatch.regprog == NULL && (flags & EW_NOTWILD) == 0)
3841 {
3842 vim_free(buf);
3843 return 0;
3844 }
3845
zeertzjq00a749b2025-03-15 09:53:32 +01003846 len = (size_t)(s - buf);
Bram Moolenaar26262f82019-09-04 20:59:15 +02003847 // If "**" is by itself, this is the first time we encounter it and more
3848 // is following then find matches without any directory.
Christian Brabandt6cc30272024-12-13 17:54:33 +01003849 if (!didstar && stardepth < 100 && starstar && e - s == 2
3850 && *path_end == '/')
Bram Moolenaar26262f82019-09-04 20:59:15 +02003851 {
zeertzjq00a749b2025-03-15 09:53:32 +01003852 vim_snprintf((char *)s, buflen - len, "%s", path_end + 1);
Bram Moolenaar26262f82019-09-04 20:59:15 +02003853 ++stardepth;
zeertzjq00a749b2025-03-15 09:53:32 +01003854 (void)unix_expandpath(gap, buf, len, flags, TRUE);
Bram Moolenaar26262f82019-09-04 20:59:15 +02003855 --stardepth;
3856 }
3857
3858 // open the directory for scanning
3859 *s = NUL;
3860 dirp = opendir(*buf == NUL ? "." : (char *)buf);
3861
3862 // Find all matching entries
Christian Brabandt6cc30272024-12-13 17:54:33 +01003863 if (dirp != NULL)
Bram Moolenaar26262f82019-09-04 20:59:15 +02003864 {
Bram Moolenaar57e95172022-08-20 19:26:14 +01003865 while (!got_int)
Bram Moolenaar26262f82019-09-04 20:59:15 +02003866 {
3867 dp = readdir(dirp);
3868 if (dp == NULL)
3869 break;
zeertzjq00a749b2025-03-15 09:53:32 +01003870 len = (size_t)(s - buf);
Christian Brabandt6cc30272024-12-13 17:54:33 +01003871 if ((dp->d_name[0] != '.' || starts_with_dot
3872 || ((flags & EW_DODOT)
3873 && dp->d_name[1] != NUL
3874 && (dp->d_name[1] != '.' || dp->d_name[2] != NUL)))
3875 && ((regmatch.regprog != NULL && vim_regexec(&regmatch,
3876 (char_u *)dp->d_name, (colnr_T)0))
John Marriotte29c8ba2024-12-13 13:58:53 +01003877 || ((flags & EW_NOTWILD)
zeertzjq00a749b2025-03-15 09:53:32 +01003878 && fnamencmp(path + len, dp->d_name, e - s) == 0)))
John Marriotte29c8ba2024-12-13 13:58:53 +01003879 {
zeertzjq00a749b2025-03-15 09:53:32 +01003880 len += vim_snprintf((char *)s, buflen - len, "%s", dp->d_name);
3881 if (len + 1 >= buflen)
3882 continue;
John Marriotte29c8ba2024-12-13 13:58:53 +01003883
Christian Brabandt6cc30272024-12-13 17:54:33 +01003884 if (starstar && stardepth < 100)
Bram Moolenaar26262f82019-09-04 20:59:15 +02003885 {
3886 // For "**" in the pattern first go deeper in the tree to
3887 // find matches.
Christian Brabandt6cc30272024-12-13 17:54:33 +01003888 vim_snprintf((char *)buf + len, buflen - len,
3889 "/**%s", path_end);
Bram Moolenaar26262f82019-09-04 20:59:15 +02003890 ++stardepth;
3891 (void)unix_expandpath(gap, buf, len + 1, flags, TRUE);
3892 --stardepth;
3893 }
3894
Christian Brabandt6cc30272024-12-13 17:54:33 +01003895 vim_snprintf((char *)buf + len, buflen - len, "%s", path_end);
Bram Moolenaar26262f82019-09-04 20:59:15 +02003896 if (mch_has_exp_wildcard(path_end)) // handle more wildcards
3897 {
3898 // need to expand another component of the path
3899 // remove backslashes for the remaining components only
3900 (void)unix_expandpath(gap, buf, len + 1, flags, FALSE);
3901 }
3902 else
3903 {
3904 stat_T sb;
3905
3906 // no more wildcards, check if there is a match
3907 // remove backslashes for the remaining components only
3908 if (*path_end != NUL)
3909 backslash_halve(buf + len + 1);
3910 // add existing file or symbolic link
3911 if ((flags & EW_ALLLINKS) ? mch_lstat((char *)buf, &sb) >= 0
Christian Brabandt6cc30272024-12-13 17:54:33 +01003912 : mch_getperm(buf) >= 0)
Bram Moolenaar26262f82019-09-04 20:59:15 +02003913 {
3914#ifdef MACOS_CONVERT
3915 size_t precomp_len = STRLEN(buf)+1;
3916 char_u *precomp_buf =
3917 mac_precompose_path(buf, precomp_len, &precomp_len);
3918
3919 if (precomp_buf)
3920 {
3921 mch_memmove(buf, precomp_buf, precomp_len);
3922 vim_free(precomp_buf);
3923 }
3924#endif
3925 addfile(gap, buf, flags);
3926 }
3927 }
3928 }
3929 }
3930
3931 closedir(dirp);
3932 }
3933
3934 vim_free(buf);
3935 vim_regfree(regmatch.regprog);
3936
Bram Moolenaar57e95172022-08-20 19:26:14 +01003937 // When interrupted the matches probably won't be used and sorting can be
3938 // slow, thus skip it.
Bram Moolenaar26262f82019-09-04 20:59:15 +02003939 matches = gap->ga_len - start_len;
Bram Moolenaar57e95172022-08-20 19:26:14 +01003940 if (matches > 0 && !got_int)
Bram Moolenaar26262f82019-09-04 20:59:15 +02003941 qsort(((char_u **)gap->ga_data) + start_len, matches,
Christian Brabandt6cc30272024-12-13 17:54:33 +01003942 sizeof(char_u *), pstrcmp);
Bram Moolenaar26262f82019-09-04 20:59:15 +02003943 return matches;
3944}
3945#endif
3946
3947/*
3948 * Return TRUE if "p" contains what looks like an environment variable.
3949 * Allowing for escaping.
3950 */
3951 static int
3952has_env_var(char_u *p)
3953{
3954 for ( ; *p; MB_PTR_ADV(p))
3955 {
3956 if (*p == '\\' && p[1] != NUL)
3957 ++p;
3958 else if (vim_strchr((char_u *)
3959#if defined(MSWIN)
3960 "$%"
3961#else
3962 "$"
3963#endif
3964 , *p) != NULL)
3965 return TRUE;
3966 }
3967 return FALSE;
3968}
3969
3970#ifdef SPECIAL_WILDCHAR
3971/*
3972 * Return TRUE if "p" contains a special wildcard character, one that Vim
3973 * cannot expand, requires using a shell.
3974 */
3975 static int
3976has_special_wildchar(char_u *p)
3977{
3978 for ( ; *p; MB_PTR_ADV(p))
3979 {
3980 // Disallow line break characters.
3981 if (*p == '\r' || *p == '\n')
3982 break;
3983 // Allow for escaping.
3984 if (*p == '\\' && p[1] != NUL && p[1] != '\r' && p[1] != '\n')
3985 ++p;
3986 else if (vim_strchr((char_u *)SPECIAL_WILDCHAR, *p) != NULL)
3987 {
3988 // A { must be followed by a matching }.
3989 if (*p == '{' && vim_strchr(p, '}') == NULL)
3990 continue;
3991 // A quote and backtick must be followed by another one.
3992 if ((*p == '`' || *p == '\'') && vim_strchr(p, *p) == NULL)
3993 continue;
3994 return TRUE;
3995 }
3996 }
3997 return FALSE;
3998}
3999#endif
4000
4001/*
4002 * Generic wildcard expansion code.
4003 *
4004 * Characters in "pat" that should not be expanded must be preceded with a
4005 * backslash. E.g., "/path\ with\ spaces/my\*star*"
4006 *
4007 * Return FAIL when no single file was found. In this case "num_file" is not
4008 * set, and "file" may contain an error message.
4009 * Return OK when some files found. "num_file" is set to the number of
4010 * matches, "file" to the array of matches. Call FreeWild() later.
4011 */
4012 int
4013gen_expand_wildcards(
4014 int num_pat, // number of input patterns
4015 char_u **pat, // array of input patterns
4016 int *num_file, // resulting number of files
4017 char_u ***file, // array of resulting files
4018 int flags) // EW_* flags
4019{
4020 int i;
4021 garray_T ga;
4022 char_u *p;
4023 static int recursive = FALSE;
4024 int add_pat;
4025 int retval = OK;
Bram Moolenaar26262f82019-09-04 20:59:15 +02004026 int did_expand_in_path = FALSE;
LemonBoya20bf692024-07-11 22:35:53 +02004027 char_u *path_option = *curbuf->b_p_path == NUL ?
4028 p_path : curbuf->b_p_path;
Bram Moolenaar26262f82019-09-04 20:59:15 +02004029
4030 /*
4031 * expand_env() is called to expand things like "~user". If this fails,
4032 * it calls ExpandOne(), which brings us back here. In this case, always
4033 * call the machine specific expansion function, if possible. Otherwise,
4034 * return FAIL.
4035 */
4036 if (recursive)
4037#ifdef SPECIAL_WILDCHAR
4038 return mch_expand_wildcards(num_pat, pat, num_file, file, flags);
4039#else
4040 return FAIL;
4041#endif
4042
4043#ifdef SPECIAL_WILDCHAR
4044 /*
4045 * If there are any special wildcard characters which we cannot handle
4046 * here, call machine specific function for all the expansion. This
4047 * avoids starting the shell for each argument separately.
4048 * For `=expr` do use the internal function.
4049 */
4050 for (i = 0; i < num_pat; i++)
4051 {
4052 if (has_special_wildchar(pat[i])
4053# ifdef VIM_BACKTICK
4054 && !(vim_backtick(pat[i]) && pat[i][1] == '=')
4055# endif
4056 )
4057 return mch_expand_wildcards(num_pat, pat, num_file, file, flags);
4058 }
4059#endif
4060
4061 recursive = TRUE;
4062
4063 /*
4064 * The matching file names are stored in a growarray. Init it empty.
4065 */
Bram Moolenaar04935fb2022-01-08 16:19:22 +00004066 ga_init2(&ga, sizeof(char_u *), 30);
Bram Moolenaar26262f82019-09-04 20:59:15 +02004067
Bram Moolenaar57e95172022-08-20 19:26:14 +01004068 for (i = 0; i < num_pat && !got_int; ++i)
Bram Moolenaar26262f82019-09-04 20:59:15 +02004069 {
4070 add_pat = -1;
4071 p = pat[i];
4072
4073#ifdef VIM_BACKTICK
4074 if (vim_backtick(p))
4075 {
4076 add_pat = expand_backtick(&ga, p, flags);
4077 if (add_pat == -1)
4078 retval = FAIL;
4079 }
4080 else
4081#endif
4082 {
4083 /*
4084 * First expand environment variables, "~/" and "~user/".
4085 */
4086 if ((has_env_var(p) && !(flags & EW_NOTENV)) || *p == '~')
4087 {
4088 p = expand_env_save_opt(p, TRUE);
4089 if (p == NULL)
4090 p = pat[i];
4091#ifdef UNIX
4092 /*
4093 * On Unix, if expand_env() can't expand an environment
4094 * variable, use the shell to do that. Discard previously
4095 * found file names and start all over again.
4096 */
4097 else if (has_env_var(p) || *p == '~')
4098 {
4099 vim_free(p);
4100 ga_clear_strings(&ga);
4101 i = mch_expand_wildcards(num_pat, pat, num_file, file,
4102 flags|EW_KEEPDOLLAR);
4103 recursive = FALSE;
4104 return i;
4105 }
4106#endif
4107 }
4108
4109 /*
LemonBoya3157a42022-04-03 11:58:31 +01004110 * If there are wildcards or case-insensitive expansion is
4111 * required: Expand file names and add each match to the list. If
4112 * there is no match, and EW_NOTFOUND is given, add the pattern.
4113 * Otherwise: Add the file name if it exists or when EW_NOTFOUND is
4114 * given.
Bram Moolenaar26262f82019-09-04 20:59:15 +02004115 */
LemonBoya3157a42022-04-03 11:58:31 +01004116 if (mch_has_exp_wildcard(p) || (flags & EW_ICASE))
Bram Moolenaar26262f82019-09-04 20:59:15 +02004117 {
LemonBoya20bf692024-07-11 22:35:53 +02004118 if ((flags & (EW_PATH | EW_CDPATH))
Bram Moolenaar26262f82019-09-04 20:59:15 +02004119 && !mch_isFullName(p)
4120 && !(p[0] == '.'
4121 && (vim_ispathsep(p[1])
4122 || (p[1] == '.' && vim_ispathsep(p[2]))))
4123 )
4124 {
4125 // :find completion where 'path' is used.
4126 // Recursiveness is OK here.
4127 recursive = FALSE;
4128 add_pat = expand_in_path(&ga, p, flags);
4129 recursive = TRUE;
4130 did_expand_in_path = TRUE;
4131 }
4132 else
Bram Moolenaar26262f82019-09-04 20:59:15 +02004133 add_pat = mch_expandpath(&ga, p, flags);
4134 }
4135 }
4136
4137 if (add_pat == -1 || (add_pat == 0 && (flags & EW_NOTFOUND)))
4138 {
4139 char_u *t = backslash_halve_save(p);
4140
4141 // When EW_NOTFOUND is used, always add files and dirs. Makes
4142 // "vim c:/" work.
4143 if (flags & EW_NOTFOUND)
4144 addfile(&ga, t, flags | EW_DIR | EW_FILE);
4145 else
4146 addfile(&ga, t, flags);
4147
4148 if (t != p)
4149 vim_free(t);
4150 }
4151
LemonBoya20bf692024-07-11 22:35:53 +02004152 if (did_expand_in_path && ga.ga_len > 0 && (flags & (EW_PATH | EW_CDPATH)))
4153 uniquefy_paths(&ga, p, path_option);
Bram Moolenaar26262f82019-09-04 20:59:15 +02004154 if (p != pat[i])
4155 vim_free(p);
4156 }
4157
Bram Moolenaar566cc8c2020-06-29 21:14:51 +02004158 // When returning FAIL the array must be freed here.
4159 if (retval == FAIL)
Yegappan Lakshmanan2b74b682022-04-03 21:30:32 +01004160 ga_clear_strings(&ga);
Bram Moolenaar566cc8c2020-06-29 21:14:51 +02004161
Bram Moolenaar26262f82019-09-04 20:59:15 +02004162 *num_file = ga.ga_len;
Bram Moolenaar566cc8c2020-06-29 21:14:51 +02004163 *file = (ga.ga_data != NULL) ? (char_u **)ga.ga_data
4164 : (char_u **)_("no matches");
Bram Moolenaar26262f82019-09-04 20:59:15 +02004165
4166 recursive = FALSE;
4167
4168 return ((flags & EW_EMPTYOK) || ga.ga_data != NULL) ? retval : FAIL;
4169}
4170
4171/*
4172 * Add a file to a file list. Accepted flags:
4173 * EW_DIR add directories
4174 * EW_FILE add files
4175 * EW_EXEC add executable files
4176 * EW_NOTFOUND add even when it doesn't exist
4177 * EW_ADDSLASH add slash after directory name
4178 * EW_ALLLINKS add symlink also when the referred file does not exist
4179 */
4180 void
4181addfile(
4182 garray_T *gap,
Bram Moolenaar217e1b82019-12-01 21:41:28 +01004183 char_u *f, // filename
Bram Moolenaar26262f82019-09-04 20:59:15 +02004184 int flags)
4185{
4186 char_u *p;
4187 int isdir;
4188 stat_T sb;
4189
4190 // if the file/dir/link doesn't exist, may not add it
4191 if (!(flags & EW_NOTFOUND) && ((flags & EW_ALLLINKS)
4192 ? mch_lstat((char *)f, &sb) < 0 : mch_getperm(f) < 0))
4193 return;
4194
4195#ifdef FNAME_ILLEGAL
4196 // if the file/dir contains illegal characters, don't add it
4197 if (vim_strpbrk(f, (char_u *)FNAME_ILLEGAL) != NULL)
4198 return;
4199#endif
4200
4201 isdir = mch_isdir(f);
4202 if ((isdir && !(flags & EW_DIR)) || (!isdir && !(flags & EW_FILE)))
4203 return;
4204
4205 // If the file isn't executable, may not add it. Do accept directories.
4206 // When invoked from expand_shellcmd() do not use $PATH.
4207 if (!isdir && (flags & EW_EXEC)
4208 && !mch_can_exe(f, NULL, !(flags & EW_SHELLCMD)))
4209 return;
4210
4211 // Make room for another item in the file list.
4212 if (ga_grow(gap, 1) == FAIL)
4213 return;
4214
4215 p = alloc(STRLEN(f) + 1 + isdir);
4216 if (p == NULL)
4217 return;
4218
4219 STRCPY(p, f);
4220#ifdef BACKSLASH_IN_FILENAME
4221 slash_adjust(p);
4222#endif
4223 /*
4224 * Append a slash or backslash after directory names if none is present.
4225 */
Bram Moolenaar26262f82019-09-04 20:59:15 +02004226 if (isdir && (flags & EW_ADDSLASH))
4227 add_pathsep(p);
Bram Moolenaar26262f82019-09-04 20:59:15 +02004228 ((char_u **)gap->ga_data)[gap->ga_len++] = p;
4229}
4230
4231/*
4232 * Free the list of files returned by expand_wildcards() or other expansion
4233 * functions.
4234 */
4235 void
4236FreeWild(int count, char_u **files)
4237{
4238 if (count <= 0 || files == NULL)
4239 return;
4240 while (count--)
4241 vim_free(files[count]);
4242 vim_free(files);
4243}
4244
4245/*
4246 * Compare path "p[]" to "q[]".
4247 * If "maxlen" >= 0 compare "p[maxlen]" to "q[maxlen]"
4248 * Return value like strcmp(p, q), but consider path separators.
4249 */
4250 int
4251pathcmp(const char *p, const char *q, int maxlen)
4252{
4253 int i, j;
4254 int c1, c2;
4255 const char *s = NULL;
4256
4257 for (i = 0, j = 0; maxlen < 0 || (i < maxlen && j < maxlen);)
4258 {
4259 c1 = PTR2CHAR((char_u *)p + i);
4260 c2 = PTR2CHAR((char_u *)q + j);
4261
4262 // End of "p": check if "q" also ends or just has a slash.
4263 if (c1 == NUL)
4264 {
4265 if (c2 == NUL) // full match
4266 return 0;
4267 s = q;
4268 i = j;
4269 break;
4270 }
4271
4272 // End of "q": check if "p" just has a slash.
4273 if (c2 == NUL)
4274 {
4275 s = p;
4276 break;
4277 }
4278
4279 if ((p_fic ? MB_TOUPPER(c1) != MB_TOUPPER(c2) : c1 != c2)
4280#ifdef BACKSLASH_IN_FILENAME
4281 // consider '/' and '\\' to be equal
4282 && !((c1 == '/' && c2 == '\\')
4283 || (c1 == '\\' && c2 == '/'))
4284#endif
4285 )
4286 {
4287 if (vim_ispathsep(c1))
4288 return -1;
4289 if (vim_ispathsep(c2))
4290 return 1;
4291 return p_fic ? MB_TOUPPER(c1) - MB_TOUPPER(c2)
4292 : c1 - c2; // no match
4293 }
4294
Bram Moolenaar1614a142019-10-06 22:00:13 +02004295 i += mb_ptr2len((char_u *)p + i);
4296 j += mb_ptr2len((char_u *)q + j);
Bram Moolenaar26262f82019-09-04 20:59:15 +02004297 }
4298 if (s == NULL) // "i" or "j" ran into "maxlen"
4299 return 0;
4300
4301 c1 = PTR2CHAR((char_u *)s + i);
Bram Moolenaar1614a142019-10-06 22:00:13 +02004302 c2 = PTR2CHAR((char_u *)s + i + mb_ptr2len((char_u *)s + i));
Bram Moolenaar26262f82019-09-04 20:59:15 +02004303 // ignore a trailing slash, but not "//" or ":/"
4304 if (c2 == NUL
4305 && i > 0
4306 && !after_pathsep((char_u *)s, (char_u *)s + i)
4307#ifdef BACKSLASH_IN_FILENAME
4308 && (c1 == '/' || c1 == '\\')
4309#else
4310 && c1 == '/'
4311#endif
4312 )
4313 return 0; // match with trailing slash
4314 if (s == q)
4315 return -1; // no match
4316 return 1;
4317}
4318
4319/*
4320 * Return TRUE if "name" is a full (absolute) path name or URL.
4321 */
4322 int
4323vim_isAbsName(char_u *name)
4324{
4325 return (path_with_url(name) != 0 || mch_isFullName(name));
4326}
4327
4328/*
4329 * Get absolute file name into buffer "buf[len]".
4330 *
4331 * return FAIL for failure, OK otherwise
4332 */
4333 int
4334vim_FullName(
4335 char_u *fname,
4336 char_u *buf,
4337 int len,
4338 int force) // force expansion even when already absolute
4339{
4340 int retval = OK;
4341 int url;
4342
4343 *buf = NUL;
4344 if (fname == NULL)
4345 return FAIL;
4346
4347 url = path_with_url(fname);
4348 if (!url)
4349 retval = mch_FullName(fname, buf, len, force);
4350 if (url || retval == FAIL)
4351 {
4352 // something failed; use the file name (truncate when too long)
4353 vim_strncpy(buf, fname, len - 1);
4354 }
4355#if defined(MSWIN)
4356 slash_adjust(buf);
4357#endif
4358 return retval;
4359}