comparison src/misc1.c @ 15814:99ebf78686a9 v8.1.0914

patch 8.1.0914: code related to findfile() is spread out commit https://github.com/vim/vim/commit/5fd0f5052f9a312bb4cfe7b4176b1211d45127ee Author: Bram Moolenaar <Bram@vim.org> Date: Wed Feb 13 23:13:28 2019 +0100 patch 8.1.0914: code related to findfile() is spread out Problem: Code related to findfile() is spread out. Solution: Put findfile() related code into a new source file. (Yegappan Lakshmanan, closes #3934)
author Bram Moolenaar <Bram@vim.org>
date Wed, 13 Feb 2019 23:15:05 +0100
parents 2d941023bd2f
children 7fad90423bd2
comparison
equal deleted inserted replaced
15813:ad21b64216aa 15814:99ebf78686a9
18 # include <lm.h> 18 # include <lm.h>
19 #endif 19 #endif
20 20
21 static char_u *vim_version_dir(char_u *vimdir); 21 static char_u *vim_version_dir(char_u *vimdir);
22 static char_u *remove_tail(char_u *p, char_u *pend, char_u *name); 22 static char_u *remove_tail(char_u *p, char_u *pend, char_u *name);
23
24 #define URL_SLASH 1 /* path_is_url() has found "://" */
25 #define URL_BACKSLASH 2 /* path_is_url() has found ":\\" */
23 26
24 /* All user names (for ~user completion as done by shell). */ 27 /* All user names (for ~user completion as done by shell). */
25 #if defined(FEAT_CMDL_COMPL) || defined(PROTO) 28 #if defined(FEAT_CMDL_COMPL) || defined(PROTO)
26 static garray_T ga_users; 29 static garray_T ga_users;
27 #endif 30 #endif
5021 MB_PTR_ADV(p2); 5024 MB_PTR_ADV(p2);
5022 } 5025 }
5023 return p1; 5026 return p1;
5024 } 5027 }
5025 5028
5026 #if defined(FEAT_SEARCHPATH)
5027 /*
5028 * Return the end of the directory name, on the first path
5029 * separator:
5030 * "/path/file", "/path/dir/", "/path//dir", "/file"
5031 * ^ ^ ^ ^
5032 */
5033 static char_u *
5034 gettail_dir(char_u *fname)
5035 {
5036 char_u *dir_end = fname;
5037 char_u *next_dir_end = fname;
5038 int look_for_sep = TRUE;
5039 char_u *p;
5040
5041 for (p = fname; *p != NUL; )
5042 {
5043 if (vim_ispathsep(*p))
5044 {
5045 if (look_for_sep)
5046 {
5047 next_dir_end = p;
5048 look_for_sep = FALSE;
5049 }
5050 }
5051 else
5052 {
5053 if (!look_for_sep)
5054 dir_end = next_dir_end;
5055 look_for_sep = TRUE;
5056 }
5057 MB_PTR_ADV(p);
5058 }
5059 return dir_end;
5060 }
5061 #endif
5062
5063 /* 5029 /*
5064 * Get pointer to tail of "fname", including path separators. Putting a NUL 5030 * Get pointer to tail of "fname", including path separators. Putting a NUL
5065 * here leaves the directory name. Takes care of "c:/" and "//". 5031 * here leaves the directory name. Takes care of "c:/" and "//".
5066 * Always returns a valid pointer. 5032 * Always returns a valid pointer.
5067 */ 5033 */
5162 #ifdef BACKSLASH_IN_FILENAME 5128 #ifdef BACKSLASH_IN_FILENAME
5163 && c != ':' 5129 && c != ':'
5164 #endif 5130 #endif
5165 ; 5131 ;
5166 } 5132 }
5167
5168 #if defined(FEAT_SEARCHPATH) || defined(PROTO)
5169 /*
5170 * return TRUE if 'c' is a path list separator.
5171 */
5172 int
5173 vim_ispathlistsep(int c)
5174 {
5175 #ifdef UNIX
5176 return (c == ':');
5177 #else
5178 return (c == ';'); /* might not be right for every system... */
5179 #endif
5180 }
5181 #endif
5182 5133
5183 /* 5134 /*
5184 * Shorten the path of a file from "~/foo/../.bar/fname" to "~/f/../.b/fname" 5135 * Shorten the path of a file from "~/foo/../.bar/fname" to "~/f/../.b/fname"
5185 * It's done in-place. 5136 * It's done in-place.
5186 */ 5137 */
6178 matches = gap->ga_len - start_len; 6129 matches = gap->ga_len - start_len;
6179 if (matches > 0) 6130 if (matches > 0)
6180 qsort(((char_u **)gap->ga_data) + start_len, matches, 6131 qsort(((char_u **)gap->ga_data) + start_len, matches,
6181 sizeof(char_u *), pstrcmp); 6132 sizeof(char_u *), pstrcmp);
6182 return matches; 6133 return matches;
6183 }
6184 #endif
6185
6186 #if defined(FEAT_SEARCHPATH)
6187 /*
6188 * Moves "*psep" back to the previous path separator in "path".
6189 * Returns FAIL is "*psep" ends up at the beginning of "path".
6190 */
6191 static int
6192 find_previous_pathsep(char_u *path, char_u **psep)
6193 {
6194 /* skip the current separator */
6195 if (*psep > path && vim_ispathsep(**psep))
6196 --*psep;
6197
6198 /* find the previous separator */
6199 while (*psep > path)
6200 {
6201 if (vim_ispathsep(**psep))
6202 return OK;
6203 MB_PTR_BACK(path, *psep);
6204 }
6205
6206 return FAIL;
6207 }
6208
6209 /*
6210 * Returns TRUE if "maybe_unique" is unique wrt other_paths in "gap".
6211 * "maybe_unique" is the end portion of "((char_u **)gap->ga_data)[i]".
6212 */
6213 static int
6214 is_unique(char_u *maybe_unique, garray_T *gap, int i)
6215 {
6216 int j;
6217 int candidate_len;
6218 int other_path_len;
6219 char_u **other_paths = (char_u **)gap->ga_data;
6220 char_u *rival;
6221
6222 for (j = 0; j < gap->ga_len; j++)
6223 {
6224 if (j == i)
6225 continue; /* don't compare it with itself */
6226
6227 candidate_len = (int)STRLEN(maybe_unique);
6228 other_path_len = (int)STRLEN(other_paths[j]);
6229 if (other_path_len < candidate_len)
6230 continue; /* it's different when it's shorter */
6231
6232 rival = other_paths[j] + other_path_len - candidate_len;
6233 if (fnamecmp(maybe_unique, rival) == 0
6234 && (rival == other_paths[j] || vim_ispathsep(*(rival - 1))))
6235 return FALSE; /* match */
6236 }
6237
6238 return TRUE; /* no match found */
6239 }
6240
6241 /*
6242 * Split the 'path' option into an array of strings in garray_T. Relative
6243 * paths are expanded to their equivalent fullpath. This includes the "."
6244 * (relative to current buffer directory) and empty path (relative to current
6245 * directory) notations.
6246 *
6247 * TODO: handle upward search (;) and path limiter (**N) notations by
6248 * expanding each into their equivalent path(s).
6249 */
6250 static void
6251 expand_path_option(char_u *curdir, garray_T *gap)
6252 {
6253 char_u *path_option = *curbuf->b_p_path == NUL
6254 ? p_path : curbuf->b_p_path;
6255 char_u *buf;
6256 char_u *p;
6257 int len;
6258
6259 if ((buf = alloc((int)MAXPATHL)) == NULL)
6260 return;
6261
6262 while (*path_option != NUL)
6263 {
6264 copy_option_part(&path_option, buf, MAXPATHL, " ,");
6265
6266 if (buf[0] == '.' && (buf[1] == NUL || vim_ispathsep(buf[1])))
6267 {
6268 /* Relative to current buffer:
6269 * "/path/file" + "." -> "/path/"
6270 * "/path/file" + "./subdir" -> "/path/subdir" */
6271 if (curbuf->b_ffname == NULL)
6272 continue;
6273 p = gettail(curbuf->b_ffname);
6274 len = (int)(p - curbuf->b_ffname);
6275 if (len + (int)STRLEN(buf) >= MAXPATHL)
6276 continue;
6277 if (buf[1] == NUL)
6278 buf[len] = NUL;
6279 else
6280 STRMOVE(buf + len, buf + 2);
6281 mch_memmove(buf, curbuf->b_ffname, len);
6282 simplify_filename(buf);
6283 }
6284 else if (buf[0] == NUL)
6285 /* relative to current directory */
6286 STRCPY(buf, curdir);
6287 else if (path_with_url(buf))
6288 /* URL can't be used here */
6289 continue;
6290 else if (!mch_isFullName(buf))
6291 {
6292 /* Expand relative path to their full path equivalent */
6293 len = (int)STRLEN(curdir);
6294 if (len + (int)STRLEN(buf) + 3 > MAXPATHL)
6295 continue;
6296 STRMOVE(buf + len + 1, buf);
6297 STRCPY(buf, curdir);
6298 buf[len] = PATHSEP;
6299 simplify_filename(buf);
6300 }
6301
6302 if (ga_grow(gap, 1) == FAIL)
6303 break;
6304
6305 # if defined(MSWIN)
6306 /* Avoid the path ending in a backslash, it fails when a comma is
6307 * appended. */
6308 len = (int)STRLEN(buf);
6309 if (buf[len - 1] == '\\')
6310 buf[len - 1] = '/';
6311 # endif
6312
6313 p = vim_strsave(buf);
6314 if (p == NULL)
6315 break;
6316 ((char_u **)gap->ga_data)[gap->ga_len++] = p;
6317 }
6318
6319 vim_free(buf);
6320 }
6321
6322 /*
6323 * Returns a pointer to the file or directory name in "fname" that matches the
6324 * longest path in "ga"p, or NULL if there is no match. For example:
6325 *
6326 * path: /foo/bar/baz
6327 * fname: /foo/bar/baz/quux.txt
6328 * returns: ^this
6329 */
6330 static char_u *
6331 get_path_cutoff(char_u *fname, garray_T *gap)
6332 {
6333 int i;
6334 int maxlen = 0;
6335 char_u **path_part = (char_u **)gap->ga_data;
6336 char_u *cutoff = NULL;
6337
6338 for (i = 0; i < gap->ga_len; i++)
6339 {
6340 int j = 0;
6341
6342 while ((fname[j] == path_part[i][j]
6343 # if defined(MSWIN)
6344 || (vim_ispathsep(fname[j]) && vim_ispathsep(path_part[i][j]))
6345 #endif
6346 ) && fname[j] != NUL && path_part[i][j] != NUL)
6347 j++;
6348 if (j > maxlen)
6349 {
6350 maxlen = j;
6351 cutoff = &fname[j];
6352 }
6353 }
6354
6355 /* skip to the file or directory name */
6356 if (cutoff != NULL)
6357 while (vim_ispathsep(*cutoff))
6358 MB_PTR_ADV(cutoff);
6359
6360 return cutoff;
6361 }
6362
6363 /*
6364 * Sorts, removes duplicates and modifies all the fullpath names in "gap" so
6365 * that they are unique with respect to each other while conserving the part
6366 * that matches the pattern. Beware, this is at least O(n^2) wrt "gap->ga_len".
6367 */
6368 static void
6369 uniquefy_paths(garray_T *gap, char_u *pattern)
6370 {
6371 int i;
6372 int len;
6373 char_u **fnames = (char_u **)gap->ga_data;
6374 int sort_again = FALSE;
6375 char_u *pat;
6376 char_u *file_pattern;
6377 char_u *curdir;
6378 regmatch_T regmatch;
6379 garray_T path_ga;
6380 char_u **in_curdir = NULL;
6381 char_u *short_name;
6382
6383 remove_duplicates(gap);
6384 ga_init2(&path_ga, (int)sizeof(char_u *), 1);
6385
6386 /*
6387 * We need to prepend a '*' at the beginning of file_pattern so that the
6388 * regex matches anywhere in the path. FIXME: is this valid for all
6389 * possible patterns?
6390 */
6391 len = (int)STRLEN(pattern);
6392 file_pattern = alloc(len + 2);
6393 if (file_pattern == NULL)
6394 return;
6395 file_pattern[0] = '*';
6396 file_pattern[1] = NUL;
6397 STRCAT(file_pattern, pattern);
6398 pat = file_pat_to_reg_pat(file_pattern, NULL, NULL, TRUE);
6399 vim_free(file_pattern);
6400 if (pat == NULL)
6401 return;
6402
6403 regmatch.rm_ic = TRUE; /* always ignore case */
6404 regmatch.regprog = vim_regcomp(pat, RE_MAGIC + RE_STRING);
6405 vim_free(pat);
6406 if (regmatch.regprog == NULL)
6407 return;
6408
6409 if ((curdir = alloc((int)(MAXPATHL))) == NULL)
6410 goto theend;
6411 mch_dirname(curdir, MAXPATHL);
6412 expand_path_option(curdir, &path_ga);
6413
6414 in_curdir = (char_u **)alloc_clear(gap->ga_len * sizeof(char_u *));
6415 if (in_curdir == NULL)
6416 goto theend;
6417
6418 for (i = 0; i < gap->ga_len && !got_int; i++)
6419 {
6420 char_u *path = fnames[i];
6421 int is_in_curdir;
6422 char_u *dir_end = gettail_dir(path);
6423 char_u *pathsep_p;
6424 char_u *path_cutoff;
6425
6426 len = (int)STRLEN(path);
6427 is_in_curdir = fnamencmp(curdir, path, dir_end - path) == 0
6428 && curdir[dir_end - path] == NUL;
6429 if (is_in_curdir)
6430 in_curdir[i] = vim_strsave(path);
6431
6432 /* Shorten the filename while maintaining its uniqueness */
6433 path_cutoff = get_path_cutoff(path, &path_ga);
6434
6435 /* Don't assume all files can be reached without path when search
6436 * pattern starts with star star slash, so only remove path_cutoff
6437 * when possible. */
6438 if (pattern[0] == '*' && pattern[1] == '*'
6439 && vim_ispathsep_nocolon(pattern[2])
6440 && path_cutoff != NULL
6441 && vim_regexec(&regmatch, path_cutoff, (colnr_T)0)
6442 && is_unique(path_cutoff, gap, i))
6443 {
6444 sort_again = TRUE;
6445 mch_memmove(path, path_cutoff, STRLEN(path_cutoff) + 1);
6446 }
6447 else
6448 {
6449 /* Here all files can be reached without path, so get shortest
6450 * unique path. We start at the end of the path. */
6451 pathsep_p = path + len - 1;
6452
6453 while (find_previous_pathsep(path, &pathsep_p))
6454 if (vim_regexec(&regmatch, pathsep_p + 1, (colnr_T)0)
6455 && is_unique(pathsep_p + 1, gap, i)
6456 && path_cutoff != NULL && pathsep_p + 1 >= path_cutoff)
6457 {
6458 sort_again = TRUE;
6459 mch_memmove(path, pathsep_p + 1, STRLEN(pathsep_p));
6460 break;
6461 }
6462 }
6463
6464 if (mch_isFullName(path))
6465 {
6466 /*
6467 * Last resort: shorten relative to curdir if possible.
6468 * 'possible' means:
6469 * 1. It is under the current directory.
6470 * 2. The result is actually shorter than the original.
6471 *
6472 * Before curdir After
6473 * /foo/bar/file.txt /foo/bar ./file.txt
6474 * c:\foo\bar\file.txt c:\foo\bar .\file.txt
6475 * /file.txt / /file.txt
6476 * c:\file.txt c:\ .\file.txt
6477 */
6478 short_name = shorten_fname(path, curdir);
6479 if (short_name != NULL && short_name > path + 1
6480 #if defined(MSWIN)
6481 /* On windows,
6482 * shorten_fname("c:\a\a.txt", "c:\a\b")
6483 * returns "\a\a.txt", which is not really the short
6484 * name, hence: */
6485 && !vim_ispathsep(*short_name)
6486 #endif
6487 )
6488 {
6489 STRCPY(path, ".");
6490 add_pathsep(path);
6491 STRMOVE(path + STRLEN(path), short_name);
6492 }
6493 }
6494 ui_breakcheck();
6495 }
6496
6497 /* Shorten filenames in /in/current/directory/{filename} */
6498 for (i = 0; i < gap->ga_len && !got_int; i++)
6499 {
6500 char_u *rel_path;
6501 char_u *path = in_curdir[i];
6502
6503 if (path == NULL)
6504 continue;
6505
6506 /* If the {filename} is not unique, change it to ./{filename}.
6507 * Else reduce it to {filename} */
6508 short_name = shorten_fname(path, curdir);
6509 if (short_name == NULL)
6510 short_name = path;
6511 if (is_unique(short_name, gap, i))
6512 {
6513 STRCPY(fnames[i], short_name);
6514 continue;
6515 }
6516
6517 rel_path = alloc((int)(STRLEN(short_name) + STRLEN(PATHSEPSTR) + 2));
6518 if (rel_path == NULL)
6519 goto theend;
6520 STRCPY(rel_path, ".");
6521 add_pathsep(rel_path);
6522 STRCAT(rel_path, short_name);
6523
6524 vim_free(fnames[i]);
6525 fnames[i] = rel_path;
6526 sort_again = TRUE;
6527 ui_breakcheck();
6528 }
6529
6530 theend:
6531 vim_free(curdir);
6532 if (in_curdir != NULL)
6533 {
6534 for (i = 0; i < gap->ga_len; i++)
6535 vim_free(in_curdir[i]);
6536 vim_free(in_curdir);
6537 }
6538 ga_clear_strings(&path_ga);
6539 vim_regfree(regmatch.regprog);
6540
6541 if (sort_again)
6542 remove_duplicates(gap);
6543 }
6544
6545 /*
6546 * Calls globpath() with 'path' values for the given pattern and stores the
6547 * result in "gap".
6548 * Returns the total number of matches.
6549 */
6550 static int
6551 expand_in_path(
6552 garray_T *gap,
6553 char_u *pattern,
6554 int flags) /* EW_* flags */
6555 {
6556 char_u *curdir;
6557 garray_T path_ga;
6558 char_u *paths = NULL;
6559 int glob_flags = 0;
6560
6561 if ((curdir = alloc((unsigned)MAXPATHL)) == NULL)
6562 return 0;
6563 mch_dirname(curdir, MAXPATHL);
6564
6565 ga_init2(&path_ga, (int)sizeof(char_u *), 1);
6566 expand_path_option(curdir, &path_ga);
6567 vim_free(curdir);
6568 if (path_ga.ga_len == 0)
6569 return 0;
6570
6571 paths = ga_concat_strings(&path_ga, ",");
6572 ga_clear_strings(&path_ga);
6573 if (paths == NULL)
6574 return 0;
6575
6576 if (flags & EW_ICASE)
6577 glob_flags |= WILD_ICASE;
6578 if (flags & EW_ADDSLASH)
6579 glob_flags |= WILD_ADD_SLASH;
6580 globpath(paths, pattern, gap, glob_flags);
6581 vim_free(paths);
6582
6583 return gap->ga_len;
6584 } 6134 }
6585 #endif 6135 #endif
6586 6136
6587 #if defined(FEAT_SEARCHPATH) || defined(FEAT_CMDL_COMPL) || defined(PROTO) 6137 #if defined(FEAT_SEARCHPATH) || defined(FEAT_CMDL_COMPL) || defined(PROTO)
6588 /* 6138 /*
7118 p = vim_strnsave(p1, (int)(p - p1)); 6668 p = vim_strnsave(p1, (int)(p - p1));
7119 } 6669 }
7120 #endif 6670 #endif
7121 return p; 6671 return p;
7122 } 6672 }
6673
6674 /*
6675 * Check if the "://" of a URL is at the pointer, return URL_SLASH.
6676 * Also check for ":\\", which MS Internet Explorer accepts, return
6677 * URL_BACKSLASH.
6678 */
6679 int
6680 path_is_url(char_u *p)
6681 {
6682 if (STRNCMP(p, "://", (size_t)3) == 0)
6683 return URL_SLASH;
6684 else if (STRNCMP(p, ":\\\\", (size_t)3) == 0)
6685 return URL_BACKSLASH;
6686 return 0;
6687 }
6688
6689 /*
6690 * Check if "fname" starts with "name://". Return URL_SLASH if it does.
6691 * Return URL_BACKSLASH for "name:\\".
6692 * Return zero otherwise.
6693 */
6694 int
6695 path_with_url(char_u *fname)
6696 {
6697 char_u *p;
6698
6699 for (p = fname; isalpha(*p); ++p)
6700 ;
6701 return path_is_url(p);
6702 }
6703
6704 /*
6705 * Return TRUE if "name" is a full (absolute) path name or URL.
6706 */
6707 int
6708 vim_isAbsName(char_u *name)
6709 {
6710 return (path_with_url(name) != 0 || mch_isFullName(name));
6711 }
6712
6713 /*
6714 * Get absolute file name into buffer "buf[len]".
6715 *
6716 * return FAIL for failure, OK otherwise
6717 */
6718 int
6719 vim_FullName(
6720 char_u *fname,
6721 char_u *buf,
6722 int len,
6723 int force) /* force expansion even when already absolute */
6724 {
6725 int retval = OK;
6726 int url;
6727
6728 *buf = NUL;
6729 if (fname == NULL)
6730 return FAIL;
6731
6732 url = path_with_url(fname);
6733 if (!url)
6734 retval = mch_FullName(fname, buf, len, force);
6735 if (url || retval == FAIL)
6736 {
6737 /* something failed; use the file name (truncate when too long) */
6738 vim_strncpy(buf, fname, len - 1);
6739 }
6740 #if defined(MSWIN)
6741 slash_adjust(buf);
6742 #endif
6743 return retval;
6744 }