1 | // This is an open source non-commercial project. Dear PVS-Studio, please check |
2 | // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com |
3 | |
4 | #include <assert.h> |
5 | #include <inttypes.h> |
6 | #include <stdbool.h> |
7 | #include <stdlib.h> |
8 | |
9 | #include "nvim/vim.h" |
10 | #include "nvim/ascii.h" |
11 | #include "nvim/path.h" |
12 | #include "nvim/charset.h" |
13 | #include "nvim/eval.h" |
14 | #include "nvim/ex_docmd.h" |
15 | #include "nvim/ex_getln.h" |
16 | #include "nvim/fileio.h" |
17 | #include "nvim/file_search.h" |
18 | #include "nvim/garray.h" |
19 | #include "nvim/memfile.h" |
20 | #include "nvim/memline.h" |
21 | #include "nvim/memory.h" |
22 | #include "nvim/message.h" |
23 | #include "nvim/misc1.h" |
24 | #include "nvim/option.h" |
25 | #include "nvim/os/os.h" |
26 | #include "nvim/os/shell.h" |
27 | #include "nvim/os_unix.h" |
28 | #include "nvim/quickfix.h" |
29 | #include "nvim/regexp.h" |
30 | #include "nvim/screen.h" |
31 | #include "nvim/strings.h" |
32 | #include "nvim/tag.h" |
33 | #include "nvim/types.h" |
34 | #include "nvim/os/input.h" |
35 | #include "nvim/window.h" |
36 | |
37 | #define URL_SLASH 1 /* path_is_url() has found "://" */ |
38 | #define URL_BACKSLASH 2 /* path_is_url() has found ":\\" */ |
39 | |
40 | #ifdef gen_expand_wildcards |
41 | # undef gen_expand_wildcards |
42 | #endif |
43 | |
44 | #ifdef INCLUDE_GENERATED_DECLARATIONS |
45 | # include "path.c.generated.h" |
46 | #endif |
47 | |
48 | /// Compare two file names. |
49 | /// |
50 | /// @param s1 First file name. Environment variables in this name will be |
51 | /// expanded. |
52 | /// @param s2 Second file name. |
53 | /// @param checkname When both files don't exist, only compare their names. |
54 | /// @return Enum of type FileComparison. @see FileComparison. |
55 | FileComparison path_full_compare(char_u *const s1, char_u *const s2, |
56 | const bool checkname) |
57 | { |
58 | assert(s1 && s2); |
59 | char_u exp1[MAXPATHL]; |
60 | char_u full1[MAXPATHL]; |
61 | char_u full2[MAXPATHL]; |
62 | FileID file_id_1, file_id_2; |
63 | |
64 | expand_env(s1, exp1, MAXPATHL); |
65 | bool id_ok_1 = os_fileid((char *)exp1, &file_id_1); |
66 | bool id_ok_2 = os_fileid((char *)s2, &file_id_2); |
67 | if (!id_ok_1 && !id_ok_2) { |
68 | // If os_fileid() doesn't work, may compare the names. |
69 | if (checkname) { |
70 | vim_FullName((char *)exp1, (char *)full1, MAXPATHL, FALSE); |
71 | vim_FullName((char *)s2, (char *)full2, MAXPATHL, FALSE); |
72 | if (fnamecmp(full1, full2) == 0) { |
73 | return kEqualFileNames; |
74 | } |
75 | } |
76 | return kBothFilesMissing; |
77 | } |
78 | if (!id_ok_1 || !id_ok_2) { |
79 | return kOneFileMissing; |
80 | } |
81 | if (os_fileid_equal(&file_id_1, &file_id_2)) { |
82 | return kEqualFiles; |
83 | } |
84 | return kDifferentFiles; |
85 | } |
86 | |
87 | /// Gets the tail (i.e., the filename segment) of a path `fname`. |
88 | /// |
89 | /// @return pointer just past the last path separator (empty string, if fname |
90 | /// ends in a slash), or empty string if fname is NULL. |
91 | char_u *path_tail(const char_u *fname) |
92 | FUNC_ATTR_NONNULL_RET |
93 | { |
94 | if (fname == NULL) { |
95 | return (char_u *)"" ; |
96 | } |
97 | |
98 | const char_u *tail = get_past_head(fname); |
99 | const char_u *p = tail; |
100 | // Find last part of path. |
101 | while (*p != NUL) { |
102 | if (vim_ispathsep_nocolon(*p)) { |
103 | tail = p + 1; |
104 | } |
105 | MB_PTR_ADV(p); |
106 | } |
107 | return (char_u *)tail; |
108 | } |
109 | |
110 | /// Get pointer to tail of "fname", including path separators. |
111 | /// |
112 | /// Takes care of "c:/" and "//". That means `path_tail_with_sep("dir///file.txt")` |
113 | /// will return a pointer to `"///file.txt"`. |
114 | /// @param fname A file path. (Must be != NULL.) |
115 | /// @return |
116 | /// - Pointer to the last path separator of `fname`, if there is any. |
117 | /// - `fname` if it contains no path separator. |
118 | /// - Never NULL. |
119 | char_u *path_tail_with_sep(char_u *fname) |
120 | { |
121 | assert(fname != NULL); |
122 | |
123 | // Don't remove the '/' from "c:/file". |
124 | char_u *past_head = get_past_head(fname); |
125 | char_u *tail = path_tail(fname); |
126 | while (tail > past_head && after_pathsep((char *)fname, (char *)tail)) { |
127 | tail--; |
128 | } |
129 | return tail; |
130 | } |
131 | |
132 | /// Finds the path tail (or executable) in an invocation. |
133 | /// |
134 | /// @param[in] invocation A program invocation in the form: |
135 | /// "path/to/exe [args]". |
136 | /// @param[out] len Stores the length of the executable name. |
137 | /// |
138 | /// @post if `len` is not null, stores the length of the executable name. |
139 | /// |
140 | /// @return The position of the last path separator + 1. |
141 | const char_u *invocation_path_tail(const char_u *invocation, size_t *len) |
142 | FUNC_ATTR_NONNULL_RET FUNC_ATTR_NONNULL_ARG(1) |
143 | { |
144 | const char_u *tail = get_past_head((char_u *) invocation); |
145 | const char_u *p = tail; |
146 | while (*p != NUL && *p != ' ') { |
147 | bool was_sep = vim_ispathsep_nocolon(*p); |
148 | MB_PTR_ADV(p); |
149 | if (was_sep) { |
150 | tail = p; // Now tail points one past the separator. |
151 | } |
152 | } |
153 | |
154 | if (len != NULL) { |
155 | *len = (size_t)(p - tail); |
156 | } |
157 | |
158 | return tail; |
159 | } |
160 | |
161 | /// Get the next path component of a path name. |
162 | /// |
163 | /// @param fname A file path. (Must be != NULL.) |
164 | /// @return Pointer to first found path separator + 1. |
165 | /// An empty string, if `fname` doesn't contain a path separator, |
166 | const char *path_next_component(const char *fname) |
167 | { |
168 | assert(fname != NULL); |
169 | while (*fname != NUL && !vim_ispathsep(*fname)) { |
170 | MB_PTR_ADV(fname); |
171 | } |
172 | if (*fname != NUL) { |
173 | fname++; |
174 | } |
175 | return fname; |
176 | } |
177 | |
178 | /// Get a pointer to one character past the head of a path name. |
179 | /// Unix: after "/"; Win: after "c:\" |
180 | /// If there is no head, path is returned. |
181 | char_u *get_past_head(const char_u *path) |
182 | { |
183 | const char_u *retval = path; |
184 | |
185 | #ifdef WIN32 |
186 | // May skip "c:" |
187 | if (isalpha(path[0]) && path[1] == ':') { |
188 | retval = path + 2; |
189 | } |
190 | #endif |
191 | |
192 | while (vim_ispathsep(*retval)) { |
193 | ++retval; |
194 | } |
195 | |
196 | return (char_u *)retval; |
197 | } |
198 | |
199 | /* |
200 | * Return TRUE if 'c' is a path separator. |
201 | * Note that for MS-Windows this includes the colon. |
202 | */ |
203 | int vim_ispathsep(int c) |
204 | { |
205 | #ifdef UNIX |
206 | return c == '/'; // Unix has ':' inside file names |
207 | #else |
208 | # ifdef BACKSLASH_IN_FILENAME |
209 | return c == ':' || c == '/' || c == '\\'; |
210 | # else |
211 | return c == ':' || c == '/'; |
212 | # endif |
213 | #endif |
214 | } |
215 | |
216 | /* |
217 | * Like vim_ispathsep(c), but exclude the colon for MS-Windows. |
218 | */ |
219 | int vim_ispathsep_nocolon(int c) |
220 | { |
221 | return vim_ispathsep(c) |
222 | #ifdef BACKSLASH_IN_FILENAME |
223 | && c != ':' |
224 | #endif |
225 | ; |
226 | } |
227 | |
228 | /* |
229 | * return TRUE if 'c' is a path list separator. |
230 | */ |
231 | int vim_ispathlistsep(int c) |
232 | { |
233 | #ifdef UNIX |
234 | return c == ':'; |
235 | #else |
236 | return c == ';'; /* might not be right for every system... */ |
237 | #endif |
238 | } |
239 | |
240 | /* |
241 | * Shorten the path of a file from "~/foo/../.bar/fname" to "~/f/../.b/fname" |
242 | * It's done in-place. |
243 | */ |
244 | char_u *shorten_dir(char_u *str) |
245 | { |
246 | char_u *tail = path_tail(str); |
247 | char_u *d = str; |
248 | bool skip = false; |
249 | for (char_u *s = str;; ++s) { |
250 | if (s >= tail) { /* copy the whole tail */ |
251 | *d++ = *s; |
252 | if (*s == NUL) |
253 | break; |
254 | } else if (vim_ispathsep(*s)) { /* copy '/' and next char */ |
255 | *d++ = *s; |
256 | skip = false; |
257 | } else if (!skip) { |
258 | *d++ = *s; /* copy next char */ |
259 | if (*s != '~' && *s != '.') /* and leading "~" and "." */ |
260 | skip = true; |
261 | if (has_mbyte) { |
262 | int l = mb_ptr2len(s); |
263 | while (--l > 0) |
264 | *d++ = *++s; |
265 | } |
266 | } |
267 | } |
268 | return str; |
269 | } |
270 | |
271 | /* |
272 | * Return TRUE if the directory of "fname" exists, FALSE otherwise. |
273 | * Also returns TRUE if there is no directory name. |
274 | * "fname" must be writable!. |
275 | */ |
276 | bool dir_of_file_exists(char_u *fname) |
277 | { |
278 | char_u *p = path_tail_with_sep(fname); |
279 | if (p == fname) { |
280 | return true; |
281 | } |
282 | char_u c = *p; |
283 | *p = NUL; |
284 | bool retval = os_isdir(fname); |
285 | *p = c; |
286 | return retval; |
287 | } |
288 | |
289 | /// Compare two file names |
290 | /// |
291 | /// Handles '/' and '\\' correctly and deals with &fileignorecase option. |
292 | /// |
293 | /// @param[in] fname1 First file name. |
294 | /// @param[in] fname2 Second file name. |
295 | /// |
296 | /// @return 0 if they are equal, non-zero otherwise. |
297 | int path_fnamecmp(const char *fname1, const char *fname2) |
298 | FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT |
299 | { |
300 | #ifdef BACKSLASH_IN_FILENAME |
301 | const size_t len1 = strlen(fname1); |
302 | const size_t len2 = strlen(fname2); |
303 | return path_fnamencmp(fname1, fname2, MAX(len1, len2)); |
304 | #else |
305 | return mb_strcmp_ic((bool)p_fic, fname1, fname2); |
306 | #endif |
307 | } |
308 | |
309 | /// Compare two file names |
310 | /// |
311 | /// Handles '/' and '\\' correctly and deals with &fileignorecase option. |
312 | /// |
313 | /// @param[in] fname1 First file name. |
314 | /// @param[in] fname2 Second file name. |
315 | /// @param[in] len Compare at most len bytes. |
316 | /// |
317 | /// @return 0 if they are equal, non-zero otherwise. |
318 | int path_fnamencmp(const char *const fname1, const char *const fname2, |
319 | size_t len) |
320 | FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT |
321 | { |
322 | #ifdef BACKSLASH_IN_FILENAME |
323 | int c1 = NUL; |
324 | int c2 = NUL; |
325 | |
326 | const char *p1 = fname1; |
327 | const char *p2 = fname2; |
328 | while (len > 0) { |
329 | c1 = PTR2CHAR((const char_u *)p1); |
330 | c2 = PTR2CHAR((const char_u *)p2); |
331 | if ((c1 == NUL || c2 == NUL |
332 | || (!((c1 == '/' || c1 == '\\') && (c2 == '\\' || c2 == '/')))) |
333 | && (p_fic ? (c1 != c2 && CH_FOLD(c1) != CH_FOLD(c2)) : c1 != c2)) { |
334 | break; |
335 | } |
336 | len -= (size_t)MB_PTR2LEN((const char_u *)p1); |
337 | p1 += MB_PTR2LEN((const char_u *)p1); |
338 | p2 += MB_PTR2LEN((const char_u *)p2); |
339 | } |
340 | return c1 - c2; |
341 | #else |
342 | if (p_fic) { |
343 | return mb_strnicmp((const char_u *)fname1, (const char_u *)fname2, len); |
344 | } |
345 | return strncmp(fname1, fname2, len); |
346 | #endif |
347 | } |
348 | |
349 | /// Append fname2 to fname1 |
350 | /// |
351 | /// @param[in] fname1 First fname to append to. |
352 | /// @param[in] len1 Length of fname1. |
353 | /// @param[in] fname2 Secord part of the file name. |
354 | /// @param[in] len2 Length of fname2. |
355 | /// @param[in] sep If true and fname1 does not end with a path separator, |
356 | /// add a path separator before fname2. |
357 | /// |
358 | /// @return fname1 |
359 | static inline char *do_concat_fnames(char *fname1, const size_t len1, |
360 | const char *fname2, const size_t len2, |
361 | const bool sep) |
362 | FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET |
363 | { |
364 | if (sep && *fname1 && !after_pathsep(fname1, fname1 + len1)) { |
365 | fname1[len1] = PATHSEP; |
366 | memmove(fname1 + len1 + 1, fname2, len2 + 1); |
367 | } else { |
368 | memmove(fname1 + len1, fname2, len2 + 1); |
369 | } |
370 | |
371 | return fname1; |
372 | } |
373 | |
374 | /// Concatenate file names fname1 and fname2 into allocated memory. |
375 | /// |
376 | /// Only add a '/' or '\\' when 'sep' is true and it is necessary. |
377 | /// |
378 | /// @param fname1 is the first part of the path or filename |
379 | /// @param fname2 is the second half of the path or filename |
380 | /// @param sep is a flag to indicate a path separator should be added |
381 | /// if necessary |
382 | /// @return [allocated] Concatenation of fname1 and fname2. |
383 | char *concat_fnames(const char *fname1, const char *fname2, bool sep) |
384 | FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET |
385 | { |
386 | const size_t len1 = strlen(fname1); |
387 | const size_t len2 = strlen(fname2); |
388 | char *dest = xmalloc(len1 + len2 + 3); |
389 | memmove(dest, fname1, len1 + 1); |
390 | return do_concat_fnames(dest, len1, fname2, len2, sep); |
391 | } |
392 | |
393 | /// Concatenate file names fname1 and fname2 |
394 | /// |
395 | /// Like concat_fnames(), but in place of allocating new memory it reallocates |
396 | /// fname1. For this reason fname1 must be allocated with xmalloc, and can no |
397 | /// longer be used after running concat_fnames_realloc. |
398 | /// |
399 | /// @param fname1 is the first part of the path or filename |
400 | /// @param fname2 is the second half of the path or filename |
401 | /// @param sep is a flag to indicate a path separator should be added |
402 | /// if necessary |
403 | /// @return [allocated] Concatenation of fname1 and fname2. |
404 | char *concat_fnames_realloc(char *fname1, const char *fname2, bool sep) |
405 | FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET |
406 | { |
407 | const size_t len1 = strlen(fname1); |
408 | const size_t len2 = strlen(fname2); |
409 | return do_concat_fnames(xrealloc(fname1, len1 + len2 + 3), len1, |
410 | fname2, len2, sep); |
411 | } |
412 | |
413 | /// Adds a path separator to a filename, unless it already ends in one. |
414 | /// |
415 | /// @return `true` if the path separator was added or already existed. |
416 | /// `false` if the filename is too long. |
417 | bool add_pathsep(char *p) |
418 | FUNC_ATTR_NONNULL_ALL |
419 | { |
420 | const size_t len = strlen(p); |
421 | if (*p != NUL && !after_pathsep(p, p + len)) { |
422 | const size_t pathsep_len = sizeof(PATHSEPSTR); |
423 | if (len > MAXPATHL - pathsep_len) { |
424 | return false; |
425 | } |
426 | memcpy(p + len, PATHSEPSTR, pathsep_len); |
427 | } |
428 | return true; |
429 | } |
430 | |
431 | /// Get an allocated copy of the full path to a file. |
432 | /// |
433 | /// @param fname is the filename to save |
434 | /// @param force is a flag to expand `fname` even if it looks absolute |
435 | /// |
436 | /// @return [allocated] Copy of absolute path to `fname` or NULL when |
437 | /// `fname` is NULL. |
438 | char *FullName_save(const char *fname, bool force) |
439 | FUNC_ATTR_MALLOC |
440 | { |
441 | if (fname == NULL) { |
442 | return NULL; |
443 | } |
444 | |
445 | char *buf = xmalloc(MAXPATHL); |
446 | if (vim_FullName(fname, buf, MAXPATHL, force) == FAIL) { |
447 | xfree(buf); |
448 | return xstrdup(fname); |
449 | } |
450 | return buf; |
451 | } |
452 | |
453 | /// Saves the absolute path. |
454 | /// @param name An absolute or relative path. |
455 | /// @return The absolute path of `name`. |
456 | char *save_abs_path(const char *name) |
457 | FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_ALL |
458 | { |
459 | if (!path_is_absolute((char_u *)name)) { |
460 | return FullName_save(name, true); |
461 | } |
462 | return (char *)vim_strsave((char_u *)name); |
463 | } |
464 | |
465 | /// Checks if a path has a wildcard character including '~', unless at the end. |
466 | /// @param p The path to expand. |
467 | /// @returns Unix: True if it contains one of "?[{`'$". |
468 | /// @returns Windows: True if it contains one of "*?$[". |
469 | bool path_has_wildcard(const char_u *p) |
470 | FUNC_ATTR_NONNULL_ALL |
471 | { |
472 | for (; *p; MB_PTR_ADV(p)) { |
473 | #if defined(UNIX) |
474 | if (p[0] == '\\' && p[1] != NUL) { |
475 | p++; |
476 | continue; |
477 | } |
478 | |
479 | const char *wildcards = "*?[{`'$" ; |
480 | #else |
481 | // Windows: |
482 | const char *wildcards = "?*$[`" ; |
483 | #endif |
484 | if (vim_strchr((char_u *)wildcards, *p) != NULL |
485 | || (p[0] == '~' && p[1] != NUL)) { |
486 | return true; |
487 | } |
488 | } |
489 | return false; |
490 | } |
491 | |
492 | /* |
493 | * Unix style wildcard expansion code. |
494 | */ |
495 | static int pstrcmp(const void *a, const void *b) |
496 | { |
497 | return pathcmp(*(char **)a, *(char **)b, -1); |
498 | } |
499 | |
500 | /// Checks if a path has a character path_expand can expand. |
501 | /// @param p The path to expand. |
502 | /// @returns Unix: True if it contains one of *?[{. |
503 | /// @returns Windows: True if it contains one of *?[. |
504 | bool path_has_exp_wildcard(const char_u *p) |
505 | FUNC_ATTR_NONNULL_ALL |
506 | { |
507 | for (; *p != NUL; MB_PTR_ADV(p)) { |
508 | #if defined(UNIX) |
509 | if (p[0] == '\\' && p[1] != NUL) { |
510 | p++; |
511 | continue; |
512 | } |
513 | |
514 | const char *wildcards = "*?[{" ; |
515 | #else |
516 | const char *wildcards = "*?[" ; // Windows. |
517 | #endif |
518 | if (vim_strchr((char_u *) wildcards, *p) != NULL) { |
519 | return true; |
520 | } |
521 | } |
522 | return false; |
523 | } |
524 | |
525 | /// Recursively expands one path component into all matching files and/or |
526 | /// directories. Handles "*", "?", "[a-z]", "**", etc. |
527 | /// @remark "**" in `path` requests recursive expansion. |
528 | /// |
529 | /// @param[out] gap The matches found. |
530 | /// @param path The path to search. |
531 | /// @param flags Flags for regexp expansion. |
532 | /// - EW_ICASE: Ignore case. |
533 | /// - EW_NOERROR: Silence error messeges. |
534 | /// - EW_NOTWILD: Add matches literally. |
535 | /// @returns the number of matches found. |
536 | static size_t path_expand(garray_T *gap, const char_u *path, int flags) |
537 | FUNC_ATTR_NONNULL_ALL |
538 | { |
539 | return do_path_expand(gap, path, 0, flags, false); |
540 | } |
541 | |
542 | static const char *scandir_next_with_dots(Directory *dir) |
543 | { |
544 | static int count = 0; |
545 | if (dir == NULL) { // initialize |
546 | count = 0; |
547 | return NULL; |
548 | } |
549 | |
550 | count += 1; |
551 | if (count == 1 || count == 2) { |
552 | return (count == 1) ? "." : ".." ; |
553 | } |
554 | return os_scandir_next(dir); |
555 | } |
556 | |
557 | /// Implementation of path_expand(). |
558 | /// |
559 | /// Chars before `path + wildoff` do not get expanded. |
560 | static size_t do_path_expand(garray_T *gap, const char_u *path, |
561 | size_t wildoff, int flags, bool didstar) |
562 | FUNC_ATTR_NONNULL_ALL |
563 | { |
564 | int start_len = gap->ga_len; |
565 | size_t len; |
566 | bool starstar = false; |
567 | static int stardepth = 0; // depth for "**" expansion |
568 | |
569 | /* Expanding "**" may take a long time, check for CTRL-C. */ |
570 | if (stardepth > 0) { |
571 | os_breakcheck(); |
572 | if (got_int) |
573 | return 0; |
574 | } |
575 | |
576 | // Make room for file name. When doing encoding conversion the actual |
577 | // length may be quite a bit longer, thus use the maximum possible length. |
578 | char_u *buf = xmalloc(MAXPATHL); |
579 | |
580 | // Find the first part in the path name that contains a wildcard. |
581 | // When EW_ICASE is set every letter is considered to be a wildcard. |
582 | // Copy it into "buf", including the preceding characters. |
583 | char_u *p = buf; |
584 | char_u *s = buf; |
585 | char_u *e = NULL; |
586 | const char_u *path_end = path; |
587 | while (*path_end != NUL) { |
588 | /* May ignore a wildcard that has a backslash before it; it will |
589 | * be removed by rem_backslash() or file_pat_to_reg_pat() below. */ |
590 | if (path_end >= path + wildoff && rem_backslash(path_end)) { |
591 | *p++ = *path_end++; |
592 | } else if (vim_ispathsep_nocolon(*path_end)) { |
593 | if (e != NULL) { |
594 | break; |
595 | } |
596 | s = p + 1; |
597 | } else if (path_end >= path + wildoff |
598 | && (vim_strchr((char_u *)"*?[{~$" , *path_end) != NULL |
599 | #ifndef WIN32 |
600 | || (!p_fic && (flags & EW_ICASE) |
601 | && isalpha(PTR2CHAR(path_end))) |
602 | #endif |
603 | )) { |
604 | e = p; |
605 | } |
606 | if (has_mbyte) { |
607 | len = (size_t)(*mb_ptr2len)(path_end); |
608 | memcpy(p, path_end, len); |
609 | p += len; |
610 | path_end += len; |
611 | } else |
612 | *p++ = *path_end++; |
613 | } |
614 | e = p; |
615 | *e = NUL; |
616 | |
617 | /* Now we have one wildcard component between "s" and "e". */ |
618 | /* Remove backslashes between "wildoff" and the start of the wildcard |
619 | * component. */ |
620 | for (p = buf + wildoff; p < s; ++p) |
621 | if (rem_backslash(p)) { |
622 | STRMOVE(p, p + 1); |
623 | --e; |
624 | --s; |
625 | } |
626 | |
627 | /* Check for "**" between "s" and "e". */ |
628 | for (p = s; p < e; ++p) |
629 | if (p[0] == '*' && p[1] == '*') |
630 | starstar = true; |
631 | |
632 | // convert the file pattern to a regexp pattern |
633 | int starts_with_dot = *s == '.'; |
634 | char_u *pat = file_pat_to_reg_pat(s, e, NULL, false); |
635 | if (pat == NULL) { |
636 | xfree(buf); |
637 | return 0; |
638 | } |
639 | |
640 | // compile the regexp into a program |
641 | regmatch_T regmatch; |
642 | #if defined(UNIX) |
643 | // Ignore case if given 'wildignorecase', else respect 'fileignorecase'. |
644 | regmatch.rm_ic = (flags & EW_ICASE) || p_fic; |
645 | #else |
646 | regmatch.rm_ic = true; // Always ignore case on Windows. |
647 | #endif |
648 | if (flags & (EW_NOERROR | EW_NOTWILD)) |
649 | ++emsg_silent; |
650 | regmatch.regprog = vim_regcomp(pat, RE_MAGIC); |
651 | if (flags & (EW_NOERROR | EW_NOTWILD)) |
652 | --emsg_silent; |
653 | xfree(pat); |
654 | |
655 | if (regmatch.regprog == NULL && (flags & EW_NOTWILD) == 0) { |
656 | xfree(buf); |
657 | return 0; |
658 | } |
659 | |
660 | /* If "**" is by itself, this is the first time we encounter it and more |
661 | * is following then find matches without any directory. */ |
662 | if (!didstar && stardepth < 100 && starstar && e - s == 2 |
663 | && *path_end == '/') { |
664 | STRCPY(s, path_end + 1); |
665 | stardepth++; |
666 | (void)do_path_expand(gap, buf, (size_t)(s - buf), flags, true); |
667 | stardepth--; |
668 | } |
669 | *s = NUL; |
670 | |
671 | Directory dir; |
672 | char *dirpath = (*buf == NUL ? "." : (char *)buf); |
673 | if (os_file_is_readable(dirpath) && os_scandir(&dir, dirpath)) { |
674 | // Find all matching entries. |
675 | char_u *name; |
676 | scandir_next_with_dots(NULL); // initialize |
677 | while ((name = (char_u *)scandir_next_with_dots(&dir)) != NULL) { |
678 | if ((name[0] != '.' |
679 | || starts_with_dot |
680 | || ((flags & EW_DODOT) |
681 | && name[1] != NUL |
682 | && (name[1] != '.' || name[2] != NUL))) // -V557 |
683 | && ((regmatch.regprog != NULL && vim_regexec(®match, name, 0)) |
684 | || ((flags & EW_NOTWILD) |
685 | && fnamencmp(path + (s - buf), name, e - s) == 0))) { |
686 | STRCPY(s, name); |
687 | len = STRLEN(buf); |
688 | |
689 | if (starstar && stardepth < 100) { |
690 | /* For "**" in the pattern first go deeper in the tree to |
691 | * find matches. */ |
692 | STRCPY(buf + len, "/**" ); |
693 | STRCPY(buf + len + 3, path_end); |
694 | ++stardepth; |
695 | (void)do_path_expand(gap, buf, len + 1, flags, true); |
696 | --stardepth; |
697 | } |
698 | |
699 | STRCPY(buf + len, path_end); |
700 | if (path_has_exp_wildcard(path_end)) { /* handle more wildcards */ |
701 | /* need to expand another component of the path */ |
702 | /* remove backslashes for the remaining components only */ |
703 | (void)do_path_expand(gap, buf, len + 1, flags, false); |
704 | } else { |
705 | FileInfo file_info; |
706 | |
707 | // no more wildcards, check if there is a match |
708 | // remove backslashes for the remaining components only |
709 | if (*path_end != NUL) { |
710 | backslash_halve(buf + len + 1); |
711 | } |
712 | // add existing file or symbolic link |
713 | if ((flags & EW_ALLLINKS) ? os_fileinfo_link((char *)buf, &file_info) |
714 | : os_path_exists(buf)) { |
715 | addfile(gap, buf, flags); |
716 | } |
717 | } |
718 | } |
719 | } |
720 | os_closedir(&dir); |
721 | } |
722 | |
723 | xfree(buf); |
724 | vim_regfree(regmatch.regprog); |
725 | |
726 | size_t matches = (size_t)(gap->ga_len - start_len); |
727 | if (matches > 0) { |
728 | qsort(((char_u **)gap->ga_data) + start_len, matches, |
729 | sizeof(char_u *), pstrcmp); |
730 | } |
731 | return matches; |
732 | } |
733 | |
734 | /* |
735 | * Moves "*psep" back to the previous path separator in "path". |
736 | * Returns FAIL is "*psep" ends up at the beginning of "path". |
737 | */ |
738 | static int find_previous_pathsep(char_u *path, char_u **psep) |
739 | { |
740 | /* skip the current separator */ |
741 | if (*psep > path && vim_ispathsep(**psep)) |
742 | --*psep; |
743 | |
744 | /* find the previous separator */ |
745 | while (*psep > path) { |
746 | if (vim_ispathsep(**psep)) |
747 | return OK; |
748 | MB_PTR_BACK(path, *psep); |
749 | } |
750 | |
751 | return FAIL; |
752 | } |
753 | |
754 | /* |
755 | * Returns TRUE if "maybe_unique" is unique wrt other_paths in "gap". |
756 | * "maybe_unique" is the end portion of "((char_u **)gap->ga_data)[i]". |
757 | */ |
758 | static bool is_unique(char_u *maybe_unique, garray_T *gap, int i) |
759 | { |
760 | char_u **other_paths = (char_u **)gap->ga_data; |
761 | |
762 | for (int j = 0; j < gap->ga_len; j++) { |
763 | if (j == i) { |
764 | continue; // don't compare it with itself |
765 | } |
766 | size_t candidate_len = STRLEN(maybe_unique); |
767 | size_t other_path_len = STRLEN(other_paths[j]); |
768 | if (other_path_len < candidate_len) { |
769 | continue; // it's different when it's shorter |
770 | } |
771 | char_u *rival = other_paths[j] + other_path_len - candidate_len; |
772 | if (fnamecmp(maybe_unique, rival) == 0 |
773 | && (rival == other_paths[j] || vim_ispathsep(*(rival - 1)))) { |
774 | return false; // match |
775 | } |
776 | } |
777 | return true; // no match found |
778 | } |
779 | |
780 | /* |
781 | * Split the 'path' option into an array of strings in garray_T. Relative |
782 | * paths are expanded to their equivalent fullpath. This includes the "." |
783 | * (relative to current buffer directory) and empty path (relative to current |
784 | * directory) notations. |
785 | * |
786 | * TODO: handle upward search (;) and path limiter (**N) notations by |
787 | * expanding each into their equivalent path(s). |
788 | */ |
789 | static void expand_path_option(char_u *curdir, garray_T *gap) |
790 | { |
791 | char_u *path_option = *curbuf->b_p_path == NUL ? p_path : curbuf->b_p_path; |
792 | char_u *buf = xmalloc(MAXPATHL); |
793 | |
794 | while (*path_option != NUL) { |
795 | copy_option_part(&path_option, buf, MAXPATHL, " ," ); |
796 | |
797 | if (buf[0] == '.' && (buf[1] == NUL || vim_ispathsep(buf[1]))) { |
798 | /* Relative to current buffer: |
799 | * "/path/file" + "." -> "/path/" |
800 | * "/path/file" + "./subdir" -> "/path/subdir" */ |
801 | if (curbuf->b_ffname == NULL) |
802 | continue; |
803 | char_u *p = path_tail(curbuf->b_ffname); |
804 | size_t len = (size_t)(p - curbuf->b_ffname); |
805 | if (len + STRLEN(buf) >= MAXPATHL) { |
806 | continue; |
807 | } |
808 | if (buf[1] == NUL) { |
809 | buf[len] = NUL; |
810 | } else { |
811 | STRMOVE(buf + len, buf + 2); |
812 | } |
813 | memmove(buf, curbuf->b_ffname, len); |
814 | simplify_filename(buf); |
815 | } else if (buf[0] == NUL) { |
816 | STRCPY(buf, curdir); // relative to current directory |
817 | } else if (path_with_url((char *)buf)) { |
818 | continue; // URL can't be used here |
819 | } else if (!path_is_absolute(buf)) { |
820 | // Expand relative path to their full path equivalent |
821 | size_t len = STRLEN(curdir); |
822 | if (len + STRLEN(buf) + 3 > MAXPATHL) { |
823 | continue; |
824 | } |
825 | STRMOVE(buf + len + 1, buf); |
826 | STRCPY(buf, curdir); |
827 | buf[len] = (char_u)PATHSEP; |
828 | simplify_filename(buf); |
829 | } |
830 | |
831 | GA_APPEND(char_u *, gap, vim_strsave(buf)); |
832 | } |
833 | |
834 | xfree(buf); |
835 | } |
836 | |
837 | /* |
838 | * Returns a pointer to the file or directory name in "fname" that matches the |
839 | * longest path in "ga"p, or NULL if there is no match. For example: |
840 | * |
841 | * path: /foo/bar/baz |
842 | * fname: /foo/bar/baz/quux.txt |
843 | * returns: ^this |
844 | */ |
845 | static char_u *get_path_cutoff(char_u *fname, garray_T *gap) |
846 | { |
847 | int maxlen = 0; |
848 | char_u **path_part = (char_u **)gap->ga_data; |
849 | char_u *cutoff = NULL; |
850 | |
851 | for (int i = 0; i < gap->ga_len; i++) { |
852 | int j = 0; |
853 | |
854 | while ((fname[j] == path_part[i][j] |
855 | #ifdef WIN32 |
856 | || (vim_ispathsep(fname[j]) && vim_ispathsep(path_part[i][j])) |
857 | #endif |
858 | ) // NOLINT(whitespace/parens) |
859 | && fname[j] != NUL && path_part[i][j] != NUL) { |
860 | j++; |
861 | } |
862 | if (j > maxlen) { |
863 | maxlen = j; |
864 | cutoff = &fname[j]; |
865 | } |
866 | } |
867 | |
868 | // skip to the file or directory name |
869 | if (cutoff != NULL) { |
870 | while (vim_ispathsep(*cutoff)) { |
871 | MB_PTR_ADV(cutoff); |
872 | } |
873 | } |
874 | |
875 | return cutoff; |
876 | } |
877 | |
878 | /* |
879 | * Sorts, removes duplicates and modifies all the fullpath names in "gap" so |
880 | * that they are unique with respect to each other while conserving the part |
881 | * that matches the pattern. Beware, this is at least O(n^2) wrt "gap->ga_len". |
882 | */ |
883 | static void uniquefy_paths(garray_T *gap, char_u *pattern) |
884 | { |
885 | char_u **fnames = (char_u **)gap->ga_data; |
886 | bool sort_again = false; |
887 | regmatch_T regmatch; |
888 | garray_T path_ga; |
889 | char_u **in_curdir = NULL; |
890 | char_u *short_name; |
891 | |
892 | ga_remove_duplicate_strings(gap); |
893 | ga_init(&path_ga, (int)sizeof(char_u *), 1); |
894 | |
895 | // We need to prepend a '*' at the beginning of file_pattern so that the |
896 | // regex matches anywhere in the path. FIXME: is this valid for all |
897 | // possible patterns? |
898 | size_t len = STRLEN(pattern); |
899 | char_u *file_pattern = xmalloc(len + 2); |
900 | file_pattern[0] = '*'; |
901 | file_pattern[1] = NUL; |
902 | STRCAT(file_pattern, pattern); |
903 | char_u *pat = file_pat_to_reg_pat(file_pattern, NULL, NULL, true); |
904 | xfree(file_pattern); |
905 | if (pat == NULL) |
906 | return; |
907 | |
908 | regmatch.rm_ic = TRUE; /* always ignore case */ |
909 | regmatch.regprog = vim_regcomp(pat, RE_MAGIC + RE_STRING); |
910 | xfree(pat); |
911 | if (regmatch.regprog == NULL) |
912 | return; |
913 | |
914 | char_u *curdir = xmalloc(MAXPATHL); |
915 | os_dirname(curdir, MAXPATHL); |
916 | expand_path_option(curdir, &path_ga); |
917 | |
918 | in_curdir = xcalloc((size_t)gap->ga_len, sizeof(char_u *)); |
919 | |
920 | for (int i = 0; i < gap->ga_len && !got_int; i++) { |
921 | char_u *path = fnames[i]; |
922 | int is_in_curdir; |
923 | char_u *dir_end = (char_u *)gettail_dir((const char *)path); |
924 | char_u *pathsep_p; |
925 | char_u *path_cutoff; |
926 | |
927 | len = STRLEN(path); |
928 | is_in_curdir = fnamencmp(curdir, path, dir_end - path) == 0 |
929 | && curdir[dir_end - path] == NUL; |
930 | if (is_in_curdir) |
931 | in_curdir[i] = vim_strsave(path); |
932 | |
933 | /* Shorten the filename while maintaining its uniqueness */ |
934 | path_cutoff = get_path_cutoff(path, &path_ga); |
935 | |
936 | // Don't assume all files can be reached without path when search |
937 | // pattern starts with **/, so only remove path_cutoff |
938 | // when possible. |
939 | if (pattern[0] == '*' && pattern[1] == '*' |
940 | && vim_ispathsep_nocolon(pattern[2]) |
941 | && path_cutoff != NULL |
942 | && vim_regexec(®match, path_cutoff, (colnr_T)0) |
943 | && is_unique(path_cutoff, gap, i)) { |
944 | sort_again = true; |
945 | memmove(path, path_cutoff, STRLEN(path_cutoff) + 1); |
946 | } else { |
947 | // Here all files can be reached without path, so get shortest |
948 | // unique path. We start at the end of the path. */ |
949 | pathsep_p = path + len - 1; |
950 | while (find_previous_pathsep(path, &pathsep_p)) { |
951 | if (vim_regexec(®match, pathsep_p + 1, (colnr_T)0) |
952 | && is_unique(pathsep_p + 1, gap, i) |
953 | && path_cutoff != NULL && pathsep_p + 1 >= path_cutoff) { |
954 | sort_again = true; |
955 | memmove(path, pathsep_p + 1, STRLEN(pathsep_p)); |
956 | break; |
957 | } |
958 | } |
959 | } |
960 | |
961 | if (path_is_absolute(path)) { |
962 | // Last resort: shorten relative to curdir if possible. |
963 | // 'possible' means: |
964 | // 1. It is under the current directory. |
965 | // 2. The result is actually shorter than the original. |
966 | // |
967 | // Before curdir After |
968 | // /foo/bar/file.txt /foo/bar ./file.txt |
969 | // c:\foo\bar\file.txt c:\foo\bar .\file.txt |
970 | // /file.txt / /file.txt |
971 | // c:\file.txt c:\ .\file.txt |
972 | short_name = path_shorten_fname(path, curdir); |
973 | if (short_name != NULL && short_name > path + 1 |
974 | ) { |
975 | STRCPY(path, "." ); |
976 | add_pathsep((char *)path); |
977 | STRMOVE(path + STRLEN(path), short_name); |
978 | } |
979 | } |
980 | os_breakcheck(); |
981 | } |
982 | |
983 | /* Shorten filenames in /in/current/directory/{filename} */ |
984 | for (int i = 0; i < gap->ga_len && !got_int; i++) { |
985 | char_u *rel_path; |
986 | char_u *path = in_curdir[i]; |
987 | |
988 | if (path == NULL) |
989 | continue; |
990 | |
991 | /* If the {filename} is not unique, change it to ./{filename}. |
992 | * Else reduce it to {filename} */ |
993 | short_name = path_shorten_fname(path, curdir); |
994 | if (short_name == NULL) |
995 | short_name = path; |
996 | if (is_unique(short_name, gap, i)) { |
997 | STRCPY(fnames[i], short_name); |
998 | continue; |
999 | } |
1000 | |
1001 | rel_path = xmalloc(STRLEN(short_name) + STRLEN(PATHSEPSTR) + 2); |
1002 | STRCPY(rel_path, "." ); |
1003 | add_pathsep((char *)rel_path); |
1004 | STRCAT(rel_path, short_name); |
1005 | |
1006 | xfree(fnames[i]); |
1007 | fnames[i] = rel_path; |
1008 | sort_again = true; |
1009 | os_breakcheck(); |
1010 | } |
1011 | |
1012 | xfree(curdir); |
1013 | for (int i = 0; i < gap->ga_len; i++) |
1014 | xfree(in_curdir[i]); |
1015 | xfree(in_curdir); |
1016 | ga_clear_strings(&path_ga); |
1017 | vim_regfree(regmatch.regprog); |
1018 | |
1019 | if (sort_again) |
1020 | ga_remove_duplicate_strings(gap); |
1021 | } |
1022 | |
1023 | /// Find end of the directory name |
1024 | /// |
1025 | /// @param[in] fname File name to process. |
1026 | /// |
1027 | /// @return end of the directory name, on the first path separator: |
1028 | /// |
1029 | /// "/path/file", "/path/dir/", "/path//dir", "/file" |
1030 | /// ^ ^ ^ ^ |
1031 | const char *gettail_dir(const char *const fname) |
1032 | FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL |
1033 | { |
1034 | const char *dir_end = fname; |
1035 | const char *next_dir_end = fname; |
1036 | bool look_for_sep = true; |
1037 | |
1038 | for (const char *p = fname; *p != NUL; ) { |
1039 | if (vim_ispathsep(*p)) { |
1040 | if (look_for_sep) { |
1041 | next_dir_end = p; |
1042 | look_for_sep = false; |
1043 | } |
1044 | } else { |
1045 | if (!look_for_sep) |
1046 | dir_end = next_dir_end; |
1047 | look_for_sep = true; |
1048 | } |
1049 | MB_PTR_ADV(p); |
1050 | } |
1051 | return dir_end; |
1052 | } |
1053 | |
1054 | |
1055 | /* |
1056 | * Calls globpath() with 'path' values for the given pattern and stores the |
1057 | * result in "gap". |
1058 | * Returns the total number of matches. |
1059 | */ |
1060 | static int expand_in_path( |
1061 | garray_T *const gap, |
1062 | char_u *const pattern, |
1063 | const int flags // EW_* flags |
1064 | ) |
1065 | { |
1066 | garray_T path_ga; |
1067 | |
1068 | char_u *const curdir = xmalloc(MAXPATHL); |
1069 | os_dirname(curdir, MAXPATHL); |
1070 | |
1071 | ga_init(&path_ga, (int)sizeof(char_u *), 1); |
1072 | expand_path_option(curdir, &path_ga); |
1073 | xfree(curdir); |
1074 | if (GA_EMPTY(&path_ga)) { |
1075 | return 0; |
1076 | } |
1077 | |
1078 | char_u *const paths = ga_concat_strings(&path_ga); |
1079 | ga_clear_strings(&path_ga); |
1080 | |
1081 | int glob_flags = 0; |
1082 | if (flags & EW_ICASE) { |
1083 | glob_flags |= WILD_ICASE; |
1084 | } |
1085 | if (flags & EW_ADDSLASH) { |
1086 | glob_flags |= WILD_ADD_SLASH; |
1087 | } |
1088 | globpath(paths, pattern, gap, glob_flags); |
1089 | xfree(paths); |
1090 | |
1091 | return gap->ga_len; |
1092 | } |
1093 | |
1094 | |
1095 | /* |
1096 | * Return TRUE if "p" contains what looks like an environment variable. |
1097 | * Allowing for escaping. |
1098 | */ |
1099 | static bool has_env_var(char_u *p) |
1100 | { |
1101 | for (; *p; MB_PTR_ADV(p)) { |
1102 | if (*p == '\\' && p[1] != NUL) { |
1103 | p++; |
1104 | } else if (vim_strchr((char_u *) "$" , *p) != NULL) { |
1105 | return true; |
1106 | } |
1107 | } |
1108 | return false; |
1109 | } |
1110 | |
1111 | #ifdef SPECIAL_WILDCHAR |
1112 | |
1113 | // Return TRUE if "p" contains a special wildcard character, one that Vim |
1114 | // cannot expand, requires using a shell. |
1115 | static bool has_special_wildchar(char_u *p) |
1116 | { |
1117 | for (; *p; MB_PTR_ADV(p)) { |
1118 | // Allow for escaping |
1119 | if (*p == '\\' && p[1] != NUL) { |
1120 | p++; |
1121 | } else if (vim_strchr((char_u *)SPECIAL_WILDCHAR, *p) != NULL) { |
1122 | return true; |
1123 | } |
1124 | } |
1125 | return false; |
1126 | } |
1127 | #endif |
1128 | |
1129 | /// Generic wildcard expansion code. |
1130 | /// |
1131 | /// Characters in pat that should not be expanded must be preceded with a |
1132 | /// backslash. E.g., "/path\ with\ spaces/my\*star*". |
1133 | /// |
1134 | /// @param num_pat is number of input patterns. |
1135 | /// @param pat is an array of pointers to input patterns. |
1136 | /// @param[out] num_file is pointer to number of matched file names. |
1137 | /// @param[out] file is pointer to array of pointers to matched file names. |
1138 | /// @param flags is a combination of EW_* flags used in |
1139 | /// expand_wildcards(). |
1140 | /// |
1141 | /// @returns OK when some files were found. *num_file is set to the |
1142 | /// number of matches, *file to the allocated array of |
1143 | /// matches. Call FreeWild() later. |
1144 | /// If FAIL is returned, *num_file and *file are either |
1145 | /// unchanged or *num_file is set to 0 and *file is set |
1146 | /// to NULL or points to "". |
1147 | int gen_expand_wildcards(int num_pat, char_u **pat, int *num_file, |
1148 | char_u ***file, int flags) |
1149 | { |
1150 | garray_T ga; |
1151 | char_u *p; |
1152 | static bool recursive = false; |
1153 | int add_pat; |
1154 | bool did_expand_in_path = false; |
1155 | |
1156 | /* |
1157 | * expand_env() is called to expand things like "~user". If this fails, |
1158 | * it calls ExpandOne(), which brings us back here. In this case, always |
1159 | * call the machine specific expansion function, if possible. Otherwise, |
1160 | * return FAIL. |
1161 | */ |
1162 | if (recursive) |
1163 | #ifdef SPECIAL_WILDCHAR |
1164 | return mch_expand_wildcards(num_pat, pat, num_file, file, flags); |
1165 | #else |
1166 | return FAIL; |
1167 | #endif |
1168 | |
1169 | #ifdef SPECIAL_WILDCHAR |
1170 | /* |
1171 | * If there are any special wildcard characters which we cannot handle |
1172 | * here, call machine specific function for all the expansion. This |
1173 | * avoids starting the shell for each argument separately. |
1174 | * For `=expr` do use the internal function. |
1175 | */ |
1176 | for (int i = 0; i < num_pat; i++) { |
1177 | if (has_special_wildchar(pat[i]) |
1178 | && !(vim_backtick(pat[i]) && pat[i][1] == '=')) { |
1179 | return mch_expand_wildcards(num_pat, pat, num_file, file, flags); |
1180 | } |
1181 | } |
1182 | #endif |
1183 | |
1184 | recursive = true; |
1185 | |
1186 | /* |
1187 | * The matching file names are stored in a growarray. Init it empty. |
1188 | */ |
1189 | ga_init(&ga, (int)sizeof(char_u *), 30); |
1190 | |
1191 | for (int i = 0; i < num_pat; ++i) { |
1192 | add_pat = -1; |
1193 | p = pat[i]; |
1194 | |
1195 | if (vim_backtick(p)) { |
1196 | add_pat = expand_backtick(&ga, p, flags); |
1197 | if (add_pat == -1) { |
1198 | recursive = false; |
1199 | FreeWild(ga.ga_len, (char_u **)ga.ga_data); |
1200 | *num_file = 0; |
1201 | *file = NULL; |
1202 | return FAIL; |
1203 | } |
1204 | } else { |
1205 | // First expand environment variables, "~/" and "~user/". |
1206 | if (has_env_var(p) || *p == '~') { |
1207 | p = expand_env_save_opt(p, true); |
1208 | if (p == NULL) |
1209 | p = pat[i]; |
1210 | #ifdef UNIX |
1211 | /* |
1212 | * On Unix, if expand_env() can't expand an environment |
1213 | * variable, use the shell to do that. Discard previously |
1214 | * found file names and start all over again. |
1215 | */ |
1216 | else if (has_env_var(p) || *p == '~') { |
1217 | xfree(p); |
1218 | ga_clear_strings(&ga); |
1219 | i = mch_expand_wildcards(num_pat, pat, num_file, file, |
1220 | flags | EW_KEEPDOLLAR); |
1221 | recursive = false; |
1222 | return i; |
1223 | } |
1224 | #endif |
1225 | } |
1226 | |
1227 | /* |
1228 | * If there are wildcards: Expand file names and add each match to |
1229 | * the list. If there is no match, and EW_NOTFOUND is given, add |
1230 | * the pattern. |
1231 | * If there are no wildcards: Add the file name if it exists or |
1232 | * when EW_NOTFOUND is given. |
1233 | */ |
1234 | if (path_has_exp_wildcard(p)) { |
1235 | if ((flags & EW_PATH) |
1236 | && !path_is_absolute(p) |
1237 | && !(p[0] == '.' |
1238 | && (vim_ispathsep(p[1]) |
1239 | || (p[1] == '.' && vim_ispathsep(p[2])))) |
1240 | ) { |
1241 | /* :find completion where 'path' is used. |
1242 | * Recursiveness is OK here. */ |
1243 | recursive = false; |
1244 | add_pat = expand_in_path(&ga, p, flags); |
1245 | recursive = true; |
1246 | did_expand_in_path = true; |
1247 | } else { |
1248 | size_t tmp_add_pat = path_expand(&ga, p, flags); |
1249 | assert(tmp_add_pat <= INT_MAX); |
1250 | add_pat = (int)tmp_add_pat; |
1251 | } |
1252 | } |
1253 | } |
1254 | |
1255 | if (add_pat == -1 || (add_pat == 0 && (flags & EW_NOTFOUND))) { |
1256 | char_u *t = backslash_halve_save(p); |
1257 | |
1258 | /* When EW_NOTFOUND is used, always add files and dirs. Makes |
1259 | * "vim c:/" work. */ |
1260 | if (flags & EW_NOTFOUND) { |
1261 | addfile(&ga, t, flags | EW_DIR | EW_FILE); |
1262 | } else { |
1263 | addfile(&ga, t, flags); |
1264 | } |
1265 | |
1266 | if (t != p) { |
1267 | xfree(t); |
1268 | } |
1269 | } |
1270 | |
1271 | if (did_expand_in_path && !GA_EMPTY(&ga) && (flags & EW_PATH)) |
1272 | uniquefy_paths(&ga, p); |
1273 | if (p != pat[i]) |
1274 | xfree(p); |
1275 | } |
1276 | |
1277 | *num_file = ga.ga_len; |
1278 | *file = (ga.ga_data != NULL) ? (char_u **)ga.ga_data : NULL; |
1279 | |
1280 | recursive = false; |
1281 | |
1282 | return ((flags & EW_EMPTYOK) || ga.ga_data != NULL) ? OK : FAIL; |
1283 | } |
1284 | |
1285 | |
1286 | /* |
1287 | * Return TRUE if we can expand this backtick thing here. |
1288 | */ |
1289 | static int vim_backtick(char_u *p) |
1290 | { |
1291 | return *p == '`' && *(p + 1) != NUL && *(p + STRLEN(p) - 1) == '`'; |
1292 | } |
1293 | |
1294 | // Expand an item in `backticks` by executing it as a command. |
1295 | // Currently only works when pat[] starts and ends with a `. |
1296 | // Returns number of file names found, -1 if an error is encountered. |
1297 | static int expand_backtick( |
1298 | garray_T *gap, |
1299 | char_u *pat, |
1300 | int flags /* EW_* flags */ |
1301 | ) |
1302 | { |
1303 | char_u *p; |
1304 | char_u *buffer; |
1305 | int cnt = 0; |
1306 | |
1307 | // Create the command: lop off the backticks. |
1308 | char_u *cmd = vim_strnsave(pat + 1, STRLEN(pat) - 2); |
1309 | |
1310 | if (*cmd == '=') /* `={expr}`: Expand expression */ |
1311 | buffer = eval_to_string(cmd + 1, &p, TRUE); |
1312 | else |
1313 | buffer = get_cmd_output(cmd, NULL, |
1314 | (flags & EW_SILENT) ? kShellOptSilent : 0, NULL); |
1315 | xfree(cmd); |
1316 | if (buffer == NULL) { |
1317 | return -1; |
1318 | } |
1319 | |
1320 | cmd = buffer; |
1321 | while (*cmd != NUL) { |
1322 | cmd = skipwhite(cmd); /* skip over white space */ |
1323 | p = cmd; |
1324 | while (*p != NUL && *p != '\r' && *p != '\n') /* skip over entry */ |
1325 | ++p; |
1326 | /* add an entry if it is not empty */ |
1327 | if (p > cmd) { |
1328 | char_u i = *p; |
1329 | *p = NUL; |
1330 | addfile(gap, cmd, flags); |
1331 | *p = i; |
1332 | ++cnt; |
1333 | } |
1334 | cmd = p; |
1335 | while (*cmd != NUL && (*cmd == '\r' || *cmd == '\n')) |
1336 | ++cmd; |
1337 | } |
1338 | |
1339 | xfree(buffer); |
1340 | return cnt; |
1341 | } |
1342 | |
1343 | #ifdef BACKSLASH_IN_FILENAME |
1344 | /// Replace all slashes by backslashes. |
1345 | /// This used to be the other way around, but MS-DOS sometimes has problems |
1346 | /// with slashes (e.g. in a command name). We can't have mixed slashes and |
1347 | /// backslashes, because comparing file names will not work correctly. The |
1348 | /// commands that use a file name should try to avoid the need to type a |
1349 | /// backslash twice. |
1350 | /// When 'shellslash' set do it the other way around. |
1351 | /// When the path looks like a URL leave it unmodified. |
1352 | void slash_adjust(char_u *p) |
1353 | { |
1354 | if (path_with_url((const char *)p)) { |
1355 | return; |
1356 | } |
1357 | |
1358 | if (*p == '`') { |
1359 | // don't replace backslash in backtick quoted strings |
1360 | const size_t len = STRLEN(p); |
1361 | if (len > 2 && *(p + len - 1) == '`') { |
1362 | return; |
1363 | } |
1364 | } |
1365 | |
1366 | while (*p) { |
1367 | if (*p == (char_u)psepcN) { |
1368 | *p = (char_u)psepc; |
1369 | } |
1370 | MB_PTR_ADV(p); |
1371 | } |
1372 | } |
1373 | #endif |
1374 | |
1375 | // Add a file to a file list. Accepted flags: |
1376 | // EW_DIR add directories |
1377 | // EW_FILE add files |
1378 | // EW_EXEC add executable files |
1379 | // EW_NOTFOUND add even when it doesn't exist |
1380 | // EW_ADDSLASH add slash after directory name |
1381 | // EW_ALLLINKS add symlink also when the referred file does not exist |
1382 | void addfile( |
1383 | garray_T *gap, |
1384 | char_u *f, /* filename */ |
1385 | int flags |
1386 | ) |
1387 | { |
1388 | bool isdir; |
1389 | FileInfo file_info; |
1390 | |
1391 | // if the file/dir/link doesn't exist, may not add it |
1392 | if (!(flags & EW_NOTFOUND) |
1393 | && ((flags & EW_ALLLINKS) |
1394 | ? !os_fileinfo_link((char *)f, &file_info) |
1395 | : !os_path_exists(f))) { |
1396 | return; |
1397 | } |
1398 | |
1399 | #ifdef FNAME_ILLEGAL |
1400 | /* if the file/dir contains illegal characters, don't add it */ |
1401 | if (vim_strpbrk(f, (char_u *)FNAME_ILLEGAL) != NULL) |
1402 | return; |
1403 | #endif |
1404 | |
1405 | isdir = os_isdir(f); |
1406 | if ((isdir && !(flags & EW_DIR)) || (!isdir && !(flags & EW_FILE))) |
1407 | return; |
1408 | |
1409 | // If the file isn't executable, may not add it. Do accept directories. |
1410 | // When invoked from expand_shellcmd() do not use $PATH. |
1411 | if (!isdir && (flags & EW_EXEC) |
1412 | && !os_can_exe((char *)f, NULL, !(flags & EW_SHELLCMD))) { |
1413 | return; |
1414 | } |
1415 | |
1416 | char_u *p = xmalloc(STRLEN(f) + 1 + isdir); |
1417 | |
1418 | STRCPY(p, f); |
1419 | #ifdef BACKSLASH_IN_FILENAME |
1420 | slash_adjust(p); |
1421 | #endif |
1422 | /* |
1423 | * Append a slash or backslash after directory names if none is present. |
1424 | */ |
1425 | if (isdir && (flags & EW_ADDSLASH)) |
1426 | add_pathsep((char *)p); |
1427 | GA_APPEND(char_u *, gap, p); |
1428 | } |
1429 | |
1430 | /* |
1431 | * Converts a file name into a canonical form. It simplifies a file name into |
1432 | * its simplest form by stripping out unneeded components, if any. The |
1433 | * resulting file name is simplified in place and will either be the same |
1434 | * length as that supplied, or shorter. |
1435 | */ |
1436 | void simplify_filename(char_u *filename) |
1437 | { |
1438 | int components = 0; |
1439 | char_u *p, *tail, *start; |
1440 | bool stripping_disabled = false; |
1441 | bool relative = true; |
1442 | |
1443 | p = filename; |
1444 | #ifdef BACKSLASH_IN_FILENAME |
1445 | if (p[1] == ':') /* skip "x:" */ |
1446 | p += 2; |
1447 | #endif |
1448 | |
1449 | if (vim_ispathsep(*p)) { |
1450 | relative = false; |
1451 | do |
1452 | ++p; |
1453 | while (vim_ispathsep(*p)); |
1454 | } |
1455 | start = p; /* remember start after "c:/" or "/" or "///" */ |
1456 | |
1457 | do { |
1458 | /* At this point "p" is pointing to the char following a single "/" |
1459 | * or "p" is at the "start" of the (absolute or relative) path name. */ |
1460 | if (vim_ispathsep(*p)) |
1461 | STRMOVE(p, p + 1); /* remove duplicate "/" */ |
1462 | else if (p[0] == '.' && (vim_ispathsep(p[1]) || p[1] == NUL)) { |
1463 | if (p == start && relative) |
1464 | p += 1 + (p[1] != NUL); /* keep single "." or leading "./" */ |
1465 | else { |
1466 | /* Strip "./" or ".///". If we are at the end of the file name |
1467 | * and there is no trailing path separator, either strip "/." if |
1468 | * we are after "start", or strip "." if we are at the beginning |
1469 | * of an absolute path name . */ |
1470 | tail = p + 1; |
1471 | if (p[1] != NUL) { |
1472 | while (vim_ispathsep(*tail)) { |
1473 | MB_PTR_ADV(tail); |
1474 | } |
1475 | } else if (p > start) { |
1476 | p--; // strip preceding path separator |
1477 | } |
1478 | STRMOVE(p, tail); |
1479 | } |
1480 | } else if (p[0] == '.' && p[1] == '.' |
1481 | && (vim_ispathsep(p[2]) || p[2] == NUL)) { |
1482 | // Skip to after ".." or "../" or "..///". |
1483 | tail = p + 2; |
1484 | while (vim_ispathsep(*tail)) { |
1485 | MB_PTR_ADV(tail); |
1486 | } |
1487 | |
1488 | if (components > 0) { /* strip one preceding component */ |
1489 | bool do_strip = false; |
1490 | char_u saved_char; |
1491 | |
1492 | /* Don't strip for an erroneous file name. */ |
1493 | if (!stripping_disabled) { |
1494 | /* If the preceding component does not exist in the file |
1495 | * system, we strip it. On Unix, we don't accept a symbolic |
1496 | * link that refers to a non-existent file. */ |
1497 | saved_char = p[-1]; |
1498 | p[-1] = NUL; |
1499 | FileInfo file_info; |
1500 | if (!os_fileinfo_link((char *)filename, &file_info)) { |
1501 | do_strip = true; |
1502 | } |
1503 | p[-1] = saved_char; |
1504 | |
1505 | p--; |
1506 | // Skip back to after previous '/'. |
1507 | while (p > start && !after_pathsep((char *)start, (char *)p)) { |
1508 | MB_PTR_BACK(start, p); |
1509 | } |
1510 | |
1511 | if (!do_strip) { |
1512 | /* If the component exists in the file system, check |
1513 | * that stripping it won't change the meaning of the |
1514 | * file name. First get information about the |
1515 | * unstripped file name. This may fail if the component |
1516 | * to strip is not a searchable directory (but a regular |
1517 | * file, for instance), since the trailing "/.." cannot |
1518 | * be applied then. We don't strip it then since we |
1519 | * don't want to replace an erroneous file name by |
1520 | * a valid one, and we disable stripping of later |
1521 | * components. */ |
1522 | saved_char = *tail; |
1523 | *tail = NUL; |
1524 | if (os_fileinfo((char *)filename, &file_info)) { |
1525 | do_strip = true; |
1526 | } else { |
1527 | stripping_disabled = true; |
1528 | } |
1529 | *tail = saved_char; |
1530 | if (do_strip) { |
1531 | /* The check for the unstripped file name |
1532 | * above works also for a symbolic link pointing to |
1533 | * a searchable directory. But then the parent of |
1534 | * the directory pointed to by the link must be the |
1535 | * same as the stripped file name. (The latter |
1536 | * exists in the file system since it is the |
1537 | * component's parent directory.) */ |
1538 | FileInfo new_file_info; |
1539 | if (p == start && relative) { |
1540 | os_fileinfo("." , &new_file_info); |
1541 | } else { |
1542 | saved_char = *p; |
1543 | *p = NUL; |
1544 | os_fileinfo((char *)filename, &new_file_info); |
1545 | *p = saved_char; |
1546 | } |
1547 | |
1548 | if (!os_fileinfo_id_equal(&file_info, &new_file_info)) { |
1549 | do_strip = false; |
1550 | /* We don't disable stripping of later |
1551 | * components since the unstripped path name is |
1552 | * still valid. */ |
1553 | } |
1554 | } |
1555 | } |
1556 | } |
1557 | |
1558 | if (!do_strip) { |
1559 | /* Skip the ".." or "../" and reset the counter for the |
1560 | * components that might be stripped later on. */ |
1561 | p = tail; |
1562 | components = 0; |
1563 | } else { |
1564 | /* Strip previous component. If the result would get empty |
1565 | * and there is no trailing path separator, leave a single |
1566 | * "." instead. If we are at the end of the file name and |
1567 | * there is no trailing path separator and a preceding |
1568 | * component is left after stripping, strip its trailing |
1569 | * path separator as well. */ |
1570 | if (p == start && relative && tail[-1] == '.') { |
1571 | *p++ = '.'; |
1572 | *p = NUL; |
1573 | } else { |
1574 | if (p > start && tail[-1] == '.') |
1575 | --p; |
1576 | STRMOVE(p, tail); /* strip previous component */ |
1577 | } |
1578 | |
1579 | --components; |
1580 | } |
1581 | } else if (p == start && !relative) /* leading "/.." or "/../" */ |
1582 | STRMOVE(p, tail); /* strip ".." or "../" */ |
1583 | else { |
1584 | if (p == start + 2 && p[-2] == '.') { /* leading "./../" */ |
1585 | STRMOVE(p - 2, p); /* strip leading "./" */ |
1586 | tail -= 2; |
1587 | } |
1588 | p = tail; /* skip to char after ".." or "../" */ |
1589 | } |
1590 | } else { |
1591 | components++; // Simple path component. |
1592 | p = (char_u *)path_next_component((const char *)p); |
1593 | } |
1594 | } while (*p != NUL); |
1595 | } |
1596 | |
1597 | static char *eval_includeexpr(const char *const ptr, const size_t len) |
1598 | { |
1599 | set_vim_var_string(VV_FNAME, ptr, (ptrdiff_t) len); |
1600 | char *res = (char *) eval_to_string_safe( |
1601 | curbuf->b_p_inex, NULL, was_set_insecurely((char_u *)"includeexpr" , |
1602 | OPT_LOCAL)); |
1603 | set_vim_var_string(VV_FNAME, NULL, 0); |
1604 | return res; |
1605 | } |
1606 | |
1607 | /* |
1608 | * Return the name of the file ptr[len] in 'path'. |
1609 | * Otherwise like file_name_at_cursor(). |
1610 | */ |
1611 | char_u * |
1612 | find_file_name_in_path ( |
1613 | char_u *ptr, |
1614 | size_t len, |
1615 | int options, |
1616 | long count, |
1617 | char_u *rel_fname /* file we are searching relative to */ |
1618 | ) |
1619 | { |
1620 | char_u *file_name; |
1621 | char_u *tofree = NULL; |
1622 | |
1623 | if ((options & FNAME_INCL) && *curbuf->b_p_inex != NUL) { |
1624 | tofree = (char_u *) eval_includeexpr((char *) ptr, len); |
1625 | if (tofree != NULL) { |
1626 | ptr = tofree; |
1627 | len = STRLEN(ptr); |
1628 | } |
1629 | } |
1630 | |
1631 | if (options & FNAME_EXP) { |
1632 | file_name = find_file_in_path(ptr, len, options & ~FNAME_MESS, true, |
1633 | rel_fname); |
1634 | |
1635 | /* |
1636 | * If the file could not be found in a normal way, try applying |
1637 | * 'includeexpr' (unless done already). |
1638 | */ |
1639 | if (file_name == NULL |
1640 | && !(options & FNAME_INCL) && *curbuf->b_p_inex != NUL) { |
1641 | tofree = (char_u *) eval_includeexpr((char *) ptr, len); |
1642 | if (tofree != NULL) { |
1643 | ptr = tofree; |
1644 | len = STRLEN(ptr); |
1645 | file_name = find_file_in_path(ptr, len, options & ~FNAME_MESS, |
1646 | TRUE, rel_fname); |
1647 | } |
1648 | } |
1649 | if (file_name == NULL && (options & FNAME_MESS)) { |
1650 | char_u c = ptr[len]; |
1651 | ptr[len] = NUL; |
1652 | EMSG2(_("E447: Can't find file \"%s\" in path" ), ptr); |
1653 | ptr[len] = c; |
1654 | } |
1655 | |
1656 | /* Repeat finding the file "count" times. This matters when it |
1657 | * appears several times in the path. */ |
1658 | while (file_name != NULL && --count > 0) { |
1659 | xfree(file_name); |
1660 | file_name = find_file_in_path(ptr, len, options, FALSE, rel_fname); |
1661 | } |
1662 | } else |
1663 | file_name = vim_strnsave(ptr, len); |
1664 | |
1665 | xfree(tofree); |
1666 | |
1667 | return file_name; |
1668 | } |
1669 | |
1670 | // Check if the "://" of a URL is at the pointer, return URL_SLASH. |
1671 | // Also check for ":\\", which MS Internet Explorer accepts, return |
1672 | // URL_BACKSLASH. |
1673 | int path_is_url(const char *p) |
1674 | { |
1675 | if (strncmp(p, "://" , 3) == 0) |
1676 | return URL_SLASH; |
1677 | else if (strncmp(p, ":\\\\" , 3) == 0) |
1678 | return URL_BACKSLASH; |
1679 | return 0; |
1680 | } |
1681 | |
1682 | /// Check if "fname" starts with "name://". Return URL_SLASH if it does. |
1683 | /// |
1684 | /// @param fname is the filename to test |
1685 | /// @return URL_BACKSLASH for "name:\\", zero otherwise. |
1686 | int path_with_url(const char *fname) |
1687 | { |
1688 | const char *p; |
1689 | for (p = fname; isalpha(*p); p++) {} |
1690 | return path_is_url(p); |
1691 | } |
1692 | |
1693 | /* |
1694 | * Return TRUE if "name" is a full (absolute) path name or URL. |
1695 | */ |
1696 | bool vim_isAbsName(char_u *name) |
1697 | { |
1698 | return path_with_url((char *)name) != 0 || path_is_absolute(name); |
1699 | } |
1700 | |
1701 | /// Save absolute file name to "buf[len]". |
1702 | /// |
1703 | /// @param fname filename to evaluate |
1704 | /// @param[out] buf contains `fname` absolute path, or: |
1705 | /// - truncated `fname` if longer than `len` |
1706 | /// - unmodified `fname` if absolute path fails or is a URL |
1707 | /// @param len length of `buf` |
1708 | /// @param force flag to force expanding even if the path is absolute |
1709 | /// |
1710 | /// @return FAIL for failure, OK otherwise |
1711 | int vim_FullName(const char *fname, char *buf, size_t len, bool force) |
1712 | FUNC_ATTR_NONNULL_ARG(2) |
1713 | { |
1714 | *buf = NUL; |
1715 | if (fname == NULL) { |
1716 | return FAIL; |
1717 | } |
1718 | |
1719 | if (strlen(fname) > (len - 1)) { |
1720 | xstrlcpy(buf, fname, len); // truncate |
1721 | #ifdef WIN32 |
1722 | slash_adjust((char_u *)buf); |
1723 | #endif |
1724 | return FAIL; |
1725 | } |
1726 | |
1727 | if (path_with_url(fname)) { |
1728 | xstrlcpy(buf, fname, len); |
1729 | return OK; |
1730 | } |
1731 | |
1732 | int rv = path_to_absolute((char_u *)fname, (char_u *)buf, len, force); |
1733 | if (rv == FAIL) { |
1734 | xstrlcpy(buf, fname, len); // something failed; use the filename |
1735 | } |
1736 | #ifdef WIN32 |
1737 | slash_adjust((char_u *)buf); |
1738 | #endif |
1739 | return rv; |
1740 | } |
1741 | |
1742 | /// Get the full resolved path for `fname` |
1743 | /// |
1744 | /// Even filenames that appear to be absolute based on starting from |
1745 | /// the root may have relative paths (like dir/../subdir) or symlinks |
1746 | /// embedded, or even extra separators (//). This function addresses |
1747 | /// those possibilities, returning a resolved absolute path. |
1748 | /// For MS-Windows, this also expands names like "longna~1". |
1749 | /// |
1750 | /// @param fname is the filename to expand |
1751 | /// @return [allocated] Full path (NULL for failure). |
1752 | char *fix_fname(const char *fname) |
1753 | { |
1754 | #ifdef UNIX |
1755 | return FullName_save(fname, true); |
1756 | #else |
1757 | if (!vim_isAbsName((char_u *)fname) |
1758 | || strstr(fname, ".." ) != NULL |
1759 | || strstr(fname, "//" ) != NULL |
1760 | # ifdef BACKSLASH_IN_FILENAME |
1761 | || strstr(fname, "\\\\" ) != NULL |
1762 | # endif |
1763 | ) |
1764 | return FullName_save(fname, false); |
1765 | |
1766 | fname = xstrdup(fname); |
1767 | |
1768 | # ifdef USE_FNAME_CASE |
1769 | path_fix_case((char_u *)fname); // set correct case for file name |
1770 | # endif |
1771 | |
1772 | return (char *)fname; |
1773 | #endif |
1774 | } |
1775 | |
1776 | /// Set the case of the file name, if it already exists. This will cause the |
1777 | /// file name to remain exactly the same. |
1778 | /// Only required for file systems where case is ignored and preserved. |
1779 | // TODO(SplinterOfChaos): Could also be used when mounting case-insensitive |
1780 | // file systems. |
1781 | void path_fix_case(char_u *name) |
1782 | FUNC_ATTR_NONNULL_ALL |
1783 | { |
1784 | FileInfo file_info; |
1785 | if (!os_fileinfo_link((char *)name, &file_info)) { |
1786 | return; |
1787 | } |
1788 | |
1789 | // Open the directory where the file is located. |
1790 | char_u *slash = STRRCHR(name, '/'); |
1791 | char_u *tail; |
1792 | Directory dir; |
1793 | bool ok; |
1794 | if (slash == NULL) { |
1795 | ok = os_scandir(&dir, "." ); |
1796 | tail = name; |
1797 | } else { |
1798 | *slash = NUL; |
1799 | ok = os_scandir(&dir, (char *) name); |
1800 | *slash = '/'; |
1801 | tail = slash + 1; |
1802 | } |
1803 | |
1804 | if (!ok) { |
1805 | return; |
1806 | } |
1807 | |
1808 | char_u *entry; |
1809 | while ((entry = (char_u *) os_scandir_next(&dir))) { |
1810 | // Only accept names that differ in case and are the same byte |
1811 | // length. TODO: accept different length name. |
1812 | if (STRICMP(tail, entry) == 0 && STRLEN(tail) == STRLEN(entry)) { |
1813 | char_u newname[MAXPATHL + 1]; |
1814 | |
1815 | // Verify the inode is equal. |
1816 | STRLCPY(newname, name, MAXPATHL + 1); |
1817 | STRLCPY(newname + (tail - name), entry, |
1818 | MAXPATHL - (tail - name) + 1); |
1819 | FileInfo file_info_new; |
1820 | if (os_fileinfo_link((char *)newname, &file_info_new) |
1821 | && os_fileinfo_id_equal(&file_info, &file_info_new)) { |
1822 | STRCPY(tail, entry); |
1823 | break; |
1824 | } |
1825 | } |
1826 | } |
1827 | |
1828 | os_closedir(&dir); |
1829 | } |
1830 | |
1831 | /* |
1832 | * Return TRUE if "p" points to just after a path separator. |
1833 | * Takes care of multi-byte characters. |
1834 | * "b" must point to the start of the file name |
1835 | */ |
1836 | int after_pathsep(const char *b, const char *p) |
1837 | { |
1838 | return p > b && vim_ispathsep(p[-1]) |
1839 | && utf_head_off((char_u *)b, (char_u *)p - 1) == 0; |
1840 | } |
1841 | |
1842 | /* |
1843 | * Return TRUE if file names "f1" and "f2" are in the same directory. |
1844 | * "f1" may be a short name, "f2" must be a full path. |
1845 | */ |
1846 | bool same_directory(char_u *f1, char_u *f2) |
1847 | { |
1848 | char_u ffname[MAXPATHL]; |
1849 | char_u *t1; |
1850 | char_u *t2; |
1851 | |
1852 | /* safety check */ |
1853 | if (f1 == NULL || f2 == NULL) |
1854 | return false; |
1855 | |
1856 | (void)vim_FullName((char *)f1, (char *)ffname, MAXPATHL, FALSE); |
1857 | t1 = path_tail_with_sep(ffname); |
1858 | t2 = path_tail_with_sep(f2); |
1859 | return t1 - ffname == t2 - f2 |
1860 | && pathcmp((char *)ffname, (char *)f2, (int)(t1 - ffname)) == 0; |
1861 | } |
1862 | |
1863 | /* |
1864 | * Compare path "p[]" to "q[]". |
1865 | * If "maxlen" >= 0 compare "p[maxlen]" to "q[maxlen]" |
1866 | * Return value like strcmp(p, q), but consider path separators. |
1867 | */ |
1868 | int pathcmp(const char *p, const char *q, int maxlen) |
1869 | { |
1870 | int i, j; |
1871 | int c1, c2; |
1872 | const char *s = NULL; |
1873 | |
1874 | for (i = 0, j = 0; maxlen < 0 || (i < maxlen && j < maxlen);) { |
1875 | c1 = PTR2CHAR((char_u *)p + i); |
1876 | c2 = PTR2CHAR((char_u *)q + j); |
1877 | |
1878 | /* End of "p": check if "q" also ends or just has a slash. */ |
1879 | if (c1 == NUL) { |
1880 | if (c2 == NUL) /* full match */ |
1881 | return 0; |
1882 | s = q; |
1883 | i = j; |
1884 | break; |
1885 | } |
1886 | |
1887 | /* End of "q": check if "p" just has a slash. */ |
1888 | if (c2 == NUL) { |
1889 | s = p; |
1890 | break; |
1891 | } |
1892 | |
1893 | if ((p_fic ? mb_toupper(c1) != mb_toupper(c2) : c1 != c2) |
1894 | #ifdef BACKSLASH_IN_FILENAME |
1895 | /* consider '/' and '\\' to be equal */ |
1896 | && !((c1 == '/' && c2 == '\\') |
1897 | || (c1 == '\\' && c2 == '/')) |
1898 | #endif |
1899 | ) { |
1900 | if (vim_ispathsep(c1)) |
1901 | return -1; |
1902 | if (vim_ispathsep(c2)) |
1903 | return 1; |
1904 | return p_fic ? mb_toupper(c1) - mb_toupper(c2) |
1905 | : c1 - c2; // no match |
1906 | } |
1907 | |
1908 | i += MB_PTR2LEN((char_u *)p + i); |
1909 | j += MB_PTR2LEN((char_u *)q + j); |
1910 | } |
1911 | if (s == NULL) { // "i" or "j" ran into "maxlen" |
1912 | return 0; |
1913 | } |
1914 | |
1915 | c1 = PTR2CHAR((char_u *)s + i); |
1916 | c2 = PTR2CHAR((char_u *)s + i + MB_PTR2LEN((char_u *)s + i)); |
1917 | /* ignore a trailing slash, but not "//" or ":/" */ |
1918 | if (c2 == NUL |
1919 | && i > 0 |
1920 | && !after_pathsep((char *)s, (char *)s + i) |
1921 | #ifdef BACKSLASH_IN_FILENAME |
1922 | && (c1 == '/' || c1 == '\\') |
1923 | #else |
1924 | && c1 == '/' |
1925 | #endif |
1926 | ) |
1927 | return 0; /* match with trailing slash */ |
1928 | if (s == q) |
1929 | return -1; /* no match */ |
1930 | return 1; |
1931 | } |
1932 | |
1933 | /// Try to find a shortname by comparing the fullname with the current |
1934 | /// directory. |
1935 | /// |
1936 | /// @param full_path The full path of the file. |
1937 | /// @return |
1938 | /// - Pointer into `full_path` if shortened. |
1939 | /// - `full_path` unchanged if no shorter name is possible. |
1940 | /// - NULL if `full_path` is NULL. |
1941 | char_u *path_try_shorten_fname(char_u *full_path) |
1942 | { |
1943 | char_u *dirname = xmalloc(MAXPATHL); |
1944 | char_u *p = full_path; |
1945 | |
1946 | if (os_dirname(dirname, MAXPATHL) == OK) { |
1947 | p = path_shorten_fname(full_path, dirname); |
1948 | if (p == NULL || *p == NUL) { |
1949 | p = full_path; |
1950 | } |
1951 | } |
1952 | xfree(dirname); |
1953 | return p; |
1954 | } |
1955 | |
1956 | /// Try to find a shortname by comparing the fullname with `dir_name`. |
1957 | /// |
1958 | /// @param full_path The full path of the file. |
1959 | /// @param dir_name The directory to shorten relative to. |
1960 | /// @return |
1961 | /// - Pointer into `full_path` if shortened. |
1962 | /// - NULL if no shorter name is possible. |
1963 | char_u *path_shorten_fname(char_u *full_path, char_u *dir_name) |
1964 | { |
1965 | if (full_path == NULL) { |
1966 | return NULL; |
1967 | } |
1968 | |
1969 | assert(dir_name != NULL); |
1970 | size_t len = strlen((char *)dir_name); |
1971 | char_u *p = full_path + len; |
1972 | |
1973 | if (fnamencmp(dir_name, full_path, len) != 0 |
1974 | || !vim_ispathsep(*p)) { |
1975 | return NULL; |
1976 | } |
1977 | |
1978 | return p + 1; |
1979 | } |
1980 | |
1981 | /// Invoke expand_wildcards() for one pattern |
1982 | /// |
1983 | /// One should expand items like "%:h" before the expansion. |
1984 | /// |
1985 | /// @param[in] pat Pointer to the input pattern. |
1986 | /// @param[out] num_file Resulting number of files. |
1987 | /// @param[out] file Array of resulting files. |
1988 | /// @param[in] flags Flags passed to expand_wildcards(). |
1989 | /// |
1990 | /// @returns OK when *file is set to allocated array of matches |
1991 | /// and *num_file(can be zero) to the number of matches. |
1992 | /// If FAIL is returned, *num_file and *file are either |
1993 | /// unchanged or *num_file is set to 0 and *file is set |
1994 | /// to NULL or points to "". |
1995 | int expand_wildcards_eval(char_u **pat, int *num_file, char_u ***file, |
1996 | int flags) |
1997 | { |
1998 | int ret = FAIL; |
1999 | char_u *eval_pat = NULL; |
2000 | char_u *exp_pat = *pat; |
2001 | char_u *ignored_msg; |
2002 | size_t usedlen; |
2003 | |
2004 | if (*exp_pat == '%' || *exp_pat == '#' || *exp_pat == '<') { |
2005 | ++emsg_off; |
2006 | eval_pat = eval_vars(exp_pat, exp_pat, &usedlen, |
2007 | NULL, &ignored_msg, NULL); |
2008 | --emsg_off; |
2009 | if (eval_pat != NULL) |
2010 | exp_pat = concat_str(eval_pat, exp_pat + usedlen); |
2011 | } |
2012 | |
2013 | if (exp_pat != NULL) |
2014 | ret = expand_wildcards(1, &exp_pat, num_file, file, flags); |
2015 | |
2016 | if (eval_pat != NULL) { |
2017 | xfree(exp_pat); |
2018 | xfree(eval_pat); |
2019 | } |
2020 | |
2021 | return ret; |
2022 | } |
2023 | |
2024 | /// Expand wildcards. Calls gen_expand_wildcards() and removes files matching |
2025 | /// 'wildignore'. |
2026 | /// |
2027 | /// @param num_pat is number of input patterns. |
2028 | /// @param pat is an array of pointers to input patterns. |
2029 | /// @param[out] num_file is pointer to number of matched file names. |
2030 | /// @param[out] file is pointer to array of pointers to matched file names. |
2031 | /// @param flags is a combination of EW_* flags. |
2032 | /// |
2033 | /// @returns OK when *file is set to allocated array of matches |
2034 | /// and *num_file (can be zero) to the number of matches. |
2035 | /// If FAIL is returned, *num_file and *file are either |
2036 | /// unchanged or *num_file is set to 0 and *file is set to |
2037 | /// NULL or points to "". |
2038 | int expand_wildcards(int num_pat, char_u **pat, int *num_files, char_u ***files, |
2039 | int flags) |
2040 | { |
2041 | int retval; |
2042 | int i, j; |
2043 | char_u *p; |
2044 | int non_suf_match; /* number without matching suffix */ |
2045 | |
2046 | retval = gen_expand_wildcards(num_pat, pat, num_files, files, flags); |
2047 | |
2048 | /* When keeping all matches, return here */ |
2049 | if ((flags & EW_KEEPALL) || retval == FAIL) |
2050 | return retval; |
2051 | |
2052 | /* |
2053 | * Remove names that match 'wildignore'. |
2054 | */ |
2055 | if (*p_wig) { |
2056 | char_u *ffname; |
2057 | |
2058 | // check all files in (*files)[] |
2059 | assert(*num_files == 0 || *files != NULL); |
2060 | for (i = 0; i < *num_files; i++) { |
2061 | ffname = (char_u *)FullName_save((char *)(*files)[i], false); |
2062 | assert((*files)[i] != NULL); |
2063 | assert(ffname != NULL); |
2064 | if (match_file_list(p_wig, (*files)[i], ffname)) { |
2065 | // remove this matching file from the list |
2066 | xfree((*files)[i]); |
2067 | for (j = i; j + 1 < *num_files; j++) { |
2068 | (*files)[j] = (*files)[j + 1]; |
2069 | } |
2070 | (*num_files)--; |
2071 | i--; |
2072 | } |
2073 | xfree(ffname); |
2074 | } |
2075 | } |
2076 | |
2077 | // |
2078 | // Move the names where 'suffixes' match to the end. |
2079 | // |
2080 | assert(*num_files == 0 || *files != NULL); |
2081 | if (*num_files > 1) { |
2082 | non_suf_match = 0; |
2083 | for (i = 0; i < *num_files; i++) { |
2084 | if (!match_suffix((*files)[i])) { |
2085 | // |
2086 | // Move the name without matching suffix to the front of the list. |
2087 | // |
2088 | p = (*files)[i]; |
2089 | for (j = i; j > non_suf_match; j--) { |
2090 | (*files)[j] = (*files)[j - 1]; |
2091 | } |
2092 | (*files)[non_suf_match++] = p; |
2093 | } |
2094 | } |
2095 | } |
2096 | |
2097 | // Free empty array of matches |
2098 | if (*num_files == 0) { |
2099 | XFREE_CLEAR(*files); |
2100 | return FAIL; |
2101 | } |
2102 | |
2103 | return retval; |
2104 | } |
2105 | |
2106 | /* |
2107 | * Return TRUE if "fname" matches with an entry in 'suffixes'. |
2108 | */ |
2109 | int match_suffix(char_u *fname) |
2110 | { |
2111 | #define MAXSUFLEN 30 // maximum length of a file suffix |
2112 | char_u suf_buf[MAXSUFLEN]; |
2113 | |
2114 | size_t fnamelen = STRLEN(fname); |
2115 | size_t setsuflen = 0; |
2116 | for (char_u *setsuf = p_su; *setsuf; ) { |
2117 | setsuflen = copy_option_part(&setsuf, suf_buf, MAXSUFLEN, ".," ); |
2118 | if (setsuflen == 0) { |
2119 | char_u *tail = path_tail(fname); |
2120 | |
2121 | /* empty entry: match name without a '.' */ |
2122 | if (vim_strchr(tail, '.') == NULL) { |
2123 | setsuflen = 1; |
2124 | break; |
2125 | } |
2126 | } else { |
2127 | if (fnamelen >= setsuflen |
2128 | && fnamencmp(suf_buf, fname + fnamelen - setsuflen, setsuflen) == 0) { |
2129 | break; |
2130 | } |
2131 | setsuflen = 0; |
2132 | } |
2133 | } |
2134 | return setsuflen != 0; |
2135 | } |
2136 | |
2137 | /// Get the absolute name of the given relative directory. |
2138 | /// |
2139 | /// @param directory Directory name, relative to current directory. |
2140 | /// @return `FAIL` for failure, `OK` for success. |
2141 | int path_full_dir_name(char *directory, char *buffer, size_t len) |
2142 | { |
2143 | int SUCCESS = 0; |
2144 | int retval = OK; |
2145 | |
2146 | if (STRLEN(directory) == 0) { |
2147 | return os_dirname((char_u *) buffer, len); |
2148 | } |
2149 | |
2150 | char old_dir[MAXPATHL]; |
2151 | |
2152 | // Get current directory name. |
2153 | if (os_dirname((char_u *) old_dir, MAXPATHL) == FAIL) { |
2154 | return FAIL; |
2155 | } |
2156 | |
2157 | // We have to get back to the current dir at the end, check if that works. |
2158 | if (os_chdir(old_dir) != SUCCESS) { |
2159 | return FAIL; |
2160 | } |
2161 | |
2162 | if (os_chdir(directory) != SUCCESS) { |
2163 | // Do not return immediately since we may be in the wrong directory. |
2164 | retval = FAIL; |
2165 | } |
2166 | |
2167 | if (retval == FAIL || os_dirname((char_u *) buffer, len) == FAIL) { |
2168 | // Do not return immediately since we are in the wrong directory. |
2169 | retval = FAIL; |
2170 | } |
2171 | |
2172 | if (os_chdir(old_dir) != SUCCESS) { |
2173 | // That shouldn't happen, since we've tested if it works. |
2174 | retval = FAIL; |
2175 | EMSG(_(e_prev_dir)); |
2176 | } |
2177 | |
2178 | return retval; |
2179 | } |
2180 | |
2181 | // Append to_append to path with a slash in between. |
2182 | int append_path(char *path, const char *to_append, size_t max_len) |
2183 | { |
2184 | size_t current_length = strlen(path); |
2185 | size_t to_append_length = strlen(to_append); |
2186 | |
2187 | // Do not append empty string or a dot. |
2188 | if (to_append_length == 0 || strcmp(to_append, "." ) == 0) { |
2189 | return OK; |
2190 | } |
2191 | |
2192 | // Combine the path segments, separated by a slash. |
2193 | if (current_length > 0 && !vim_ispathsep_nocolon(path[current_length-1])) { |
2194 | current_length += 1; // Count the trailing slash. |
2195 | |
2196 | // +1 for the NUL at the end. |
2197 | if (current_length + 1 > max_len) { |
2198 | return FAIL; |
2199 | } |
2200 | |
2201 | xstrlcat(path, PATHSEPSTR, max_len); |
2202 | } |
2203 | |
2204 | // +1 for the NUL at the end. |
2205 | if (current_length + to_append_length + 1 > max_len) { |
2206 | return FAIL; |
2207 | } |
2208 | |
2209 | xstrlcat(path, to_append, max_len); |
2210 | return OK; |
2211 | } |
2212 | |
2213 | /// Expand a given file to its absolute path. |
2214 | /// |
2215 | /// @param fname filename which should be expanded. |
2216 | /// @param buf buffer to store the absolute path of "fname". |
2217 | /// @param len length of "buf". |
2218 | /// @param force also expand when "fname" is already absolute. |
2219 | /// |
2220 | /// @return FAIL for failure, OK for success. |
2221 | static int path_to_absolute(const char_u *fname, char_u *buf, size_t len, |
2222 | int force) |
2223 | { |
2224 | char_u *p; |
2225 | *buf = NUL; |
2226 | |
2227 | char *relative_directory = xmalloc(len); |
2228 | char *end_of_path = (char *) fname; |
2229 | |
2230 | // expand it if forced or not an absolute path |
2231 | if (force || !path_is_absolute(fname)) { |
2232 | p = STRRCHR(fname, '/'); |
2233 | #ifdef WIN32 |
2234 | if (p == NULL) { |
2235 | p = STRRCHR(fname, '\\'); |
2236 | } |
2237 | #endif |
2238 | if (p != NULL) { |
2239 | // relative to root |
2240 | if (p == fname) { |
2241 | // only one path component |
2242 | relative_directory[0] = PATHSEP; |
2243 | relative_directory[1] = NUL; |
2244 | } else { |
2245 | assert(p >= fname); |
2246 | memcpy(relative_directory, fname, (size_t)(p - fname)); |
2247 | relative_directory[p-fname] = NUL; |
2248 | } |
2249 | end_of_path = (char *) (p + 1); |
2250 | } else { |
2251 | relative_directory[0] = NUL; |
2252 | end_of_path = (char *) fname; |
2253 | } |
2254 | |
2255 | if (FAIL == path_full_dir_name(relative_directory, (char *) buf, len)) { |
2256 | xfree(relative_directory); |
2257 | return FAIL; |
2258 | } |
2259 | } |
2260 | xfree(relative_directory); |
2261 | return append_path((char *)buf, end_of_path, len); |
2262 | } |
2263 | |
2264 | /// Check if file `fname` is a full (absolute) path. |
2265 | /// |
2266 | /// @return `TRUE` if "fname" is absolute. |
2267 | int path_is_absolute(const char_u *fname) |
2268 | { |
2269 | #ifdef WIN32 |
2270 | // A name like "d:/foo" and "//server/share" is absolute |
2271 | return ((isalpha(fname[0]) && fname[1] == ':' |
2272 | && vim_ispathsep_nocolon(fname[2])) |
2273 | || (vim_ispathsep_nocolon(fname[0]) && fname[0] == fname[1])); |
2274 | #else |
2275 | // UNIX: This just checks if the file name starts with '/' or '~'. |
2276 | return *fname == '/' || *fname == '~'; |
2277 | #endif |
2278 | } |
2279 | |
2280 | /// Builds a full path from an invocation name `argv0`, based on heuristics. |
2281 | /// |
2282 | /// @param[in] argv0 Name by which Nvim was invoked. |
2283 | /// @param[out] buf Guessed full path to `argv0`. |
2284 | /// @param[in] bufsize Size of `buf`. |
2285 | /// |
2286 | /// @see os_exepath |
2287 | void path_guess_exepath(const char *argv0, char *buf, size_t bufsize) |
2288 | FUNC_ATTR_NONNULL_ALL |
2289 | { |
2290 | const char *path = os_getenv("PATH" ); |
2291 | |
2292 | if (path == NULL || path_is_absolute((char_u *)argv0)) { |
2293 | xstrlcpy(buf, argv0, bufsize); |
2294 | } else if (argv0[0] == '.' || strchr(argv0, PATHSEP)) { |
2295 | // Relative to CWD. |
2296 | if (os_dirname((char_u *)buf, MAXPATHL) != OK) { |
2297 | buf[0] = NUL; |
2298 | } |
2299 | xstrlcat(buf, PATHSEPSTR, bufsize); |
2300 | xstrlcat(buf, argv0, bufsize); |
2301 | } else { |
2302 | // Search $PATH for plausible location. |
2303 | const void *iter = NULL; |
2304 | do { |
2305 | const char *dir; |
2306 | size_t dir_len; |
2307 | iter = vim_env_iter(ENV_SEPCHAR, path, iter, &dir, &dir_len); |
2308 | if (dir == NULL || dir_len == 0) { |
2309 | break; |
2310 | } |
2311 | if (dir_len + 1 > sizeof(NameBuff)) { |
2312 | continue; |
2313 | } |
2314 | xstrlcpy((char *)NameBuff, dir, dir_len + 1); |
2315 | xstrlcat((char *)NameBuff, PATHSEPSTR, sizeof(NameBuff)); |
2316 | xstrlcat((char *)NameBuff, argv0, sizeof(NameBuff)); |
2317 | if (os_can_exe((char *)NameBuff, NULL, false)) { |
2318 | xstrlcpy(buf, (char *)NameBuff, bufsize); |
2319 | return; |
2320 | } |
2321 | } while (iter != NULL); |
2322 | // Not found in $PATH, fall back to argv0. |
2323 | xstrlcpy(buf, argv0, bufsize); |
2324 | } |
2325 | } |
2326 | |