Mercurial > vim
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(®match, 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(®match, 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 } |