file_search.c (66691B)
1 // File searching functions for 'path', 'tags' and 'cdpath' options. 2 // 3 // External visible functions: 4 // vim_findfile_init() creates/initialises the search context 5 // vim_findfile_free_visited() free list of visited files/dirs of search 6 // context 7 // vim_findfile() find a file in the search context 8 // vim_findfile_cleanup() cleanup/free search context created by 9 // vim_findfile_init() 10 // 11 // All static functions and variables start with 'ff_' 12 // 13 // In general it works like this: 14 // First you create yourself a search context by calling vim_findfile_init(). 15 // It is possible to give a search context from a previous call to 16 // vim_findfile_init(), so it can be reused. After this you call vim_findfile() 17 // until you are satisfied with the result or it returns NULL. On every call it 18 // returns the next file which matches the conditions given to 19 // vim_findfile_init(). If it doesn't find a next file it returns NULL. 20 // 21 // It is possible to call vim_findfile_init() again to reinitialise your search 22 // with some new parameters. Don't forget to pass your old search context to 23 // it, so it can reuse it and especially reuse the list of already visited 24 // directories. If you want to delete the list of already visited directories 25 // simply call vim_findfile_free_visited(). 26 // 27 // When you are done call vim_findfile_cleanup() to free the search context. 28 // 29 // The function vim_findfile_init() has a long comment, which describes the 30 // needed parameters. 31 // 32 // 33 // 34 // ATTENTION: 35 // ========== 36 // We use an allocated search context, these functions are NOT thread-safe!!!!! 37 // 38 // To minimize parameter passing (or because I'm too lazy), only the 39 // external visible functions get a search context as a parameter. This is 40 // then assigned to a static global, which is used throughout the local 41 // functions. 42 43 #include <assert.h> 44 #include <ctype.h> 45 #include <inttypes.h> 46 #include <limits.h> 47 #include <stdbool.h> 48 #include <stddef.h> 49 #include <stdio.h> 50 #include <stdlib.h> 51 #include <string.h> 52 53 #include "nvim/api/private/defs.h" 54 #include "nvim/api/private/helpers.h" 55 #include "nvim/ascii_defs.h" 56 #include "nvim/autocmd.h" 57 #include "nvim/autocmd_defs.h" 58 #include "nvim/buffer_defs.h" 59 #include "nvim/charset.h" 60 #include "nvim/cursor.h" 61 #include "nvim/errors.h" 62 #include "nvim/eval.h" 63 #include "nvim/eval/typval.h" 64 #include "nvim/eval/typval_defs.h" 65 #include "nvim/eval/vars.h" 66 #include "nvim/file_search.h" 67 #include "nvim/gettext_defs.h" 68 #include "nvim/globals.h" 69 #include "nvim/macros_defs.h" 70 #include "nvim/mbyte.h" 71 #include "nvim/memory.h" 72 #include "nvim/message.h" 73 #include "nvim/normal.h" 74 #include "nvim/option.h" 75 #include "nvim/option_defs.h" 76 #include "nvim/option_vars.h" 77 #include "nvim/os/fs.h" 78 #include "nvim/os/fs_defs.h" 79 #include "nvim/os/input.h" 80 #include "nvim/os/os.h" 81 #include "nvim/os/os_defs.h" 82 #include "nvim/path.h" 83 #include "nvim/strings.h" 84 #include "nvim/vim_defs.h" 85 86 static String ff_expand_buffer = STRING_INIT; // used for expanding filenames 87 88 // type for the directory search stack 89 typedef struct ff_stack { 90 struct ff_stack *ffs_prev; 91 92 // the fix part (no wildcards) and the part containing the wildcards 93 // of the search path 94 String ffs_fix_path; 95 String ffs_wc_path; 96 97 // files/dirs found in the above directory, matched by the first wildcard 98 // of wc_part 99 char **ffs_filearray; 100 int ffs_filearray_size; 101 int ffs_filearray_cur; // needed for partly handled dirs 102 103 // to store status of partly handled directories 104 // 0: we work on this directory for the first time 105 // 1: this directory was partly searched in an earlier step 106 int ffs_stage; 107 108 // How deep are we in the directory tree? 109 // Counts backward from value of level parameter to vim_findfile_init 110 int ffs_level; 111 112 // Did we already expand '**' to an empty string? 113 int ffs_star_star_empty; 114 } ff_stack_T; 115 116 // type for already visited directories or files. 117 typedef struct ff_visited { 118 struct ff_visited *ffv_next; 119 120 // Visited directories are different if the wildcard string are 121 // different. So we have to save it. 122 char *ffv_wc_path; 123 124 // use FileID for comparison (needed because of links), else use filename. 125 bool file_id_valid; 126 FileID file_id; 127 // The memory for this struct is allocated according to the length of 128 // ffv_fname. 129 char ffv_fname[]; 130 } ff_visited_T; 131 132 // We might have to manage several visited lists during a search. 133 // This is especially needed for the tags option. If tags is set to: 134 // "./++/tags,./++/TAGS,++/tags" (replace + with *) 135 // So we have to do 3 searches: 136 // 1) search from the current files directory downward for the file "tags" 137 // 2) search from the current files directory downward for the file "TAGS" 138 // 3) search from Vims current directory downwards for the file "tags" 139 // As you can see, the first and the third search are for the same file, so for 140 // the third search we can use the visited list of the first search. For the 141 // second search we must start from an empty visited list. 142 // The struct ff_visited_list_hdr is used to manage a linked list of already 143 // visited lists. 144 typedef struct ff_visited_list_hdr { 145 struct ff_visited_list_hdr *ffvl_next; 146 147 // the filename the attached visited list is for 148 char *ffvl_filename; 149 150 ff_visited_T *ffvl_visited_list; 151 } ff_visited_list_hdr_T; 152 153 // '**' can be expanded to several directory levels. 154 // Set the default maximum depth. 155 #define FF_MAX_STAR_STAR_EXPAND 30 156 157 // The search context: 158 // ffsc_stack_ptr: the stack for the dirs to search 159 // ffsc_visited_list: the currently active visited list 160 // ffsc_dir_visited_list: the currently active visited list for search dirs 161 // ffsc_visited_lists_list: the list of all visited lists 162 // ffsc_dir_visited_lists_list: the list of all visited lists for search dirs 163 // ffsc_file_to_search: the file to search for 164 // ffsc_start_dir: the starting directory, if search path was relative 165 // ffsc_fix_path: the fix part of the given path (without wildcards) 166 // Needed for upward search. 167 // ffsc_wc_path: the part of the given path containing wildcards 168 // ffsc_level: how many levels of dirs to search downwards 169 // ffsc_stopdirs_v: array of stop directories for upward search 170 // ffsc_find_what: FINDFILE_BOTH, FINDFILE_DIR or FINDFILE_FILE 171 // ffsc_tagfile: searching for tags file, don't use 'suffixesadd' 172 typedef struct { 173 ff_stack_T *ffsc_stack_ptr; 174 ff_visited_list_hdr_T *ffsc_visited_list; 175 ff_visited_list_hdr_T *ffsc_dir_visited_list; 176 ff_visited_list_hdr_T *ffsc_visited_lists_list; 177 ff_visited_list_hdr_T *ffsc_dir_visited_lists_list; 178 String ffsc_file_to_search; 179 String ffsc_start_dir; 180 String ffsc_fix_path; 181 String ffsc_wc_path; 182 int ffsc_level; 183 String *ffsc_stopdirs_v; 184 int ffsc_find_what; 185 int ffsc_tagfile; 186 } ff_search_ctx_T; 187 188 // locally needed functions 189 190 #include "file_search.c.generated.h" 191 192 static const char e_path_too_long_for_completion[] 193 = N_("E854: Path too long for completion"); 194 195 /// Initialization routine for vim_findfile(). 196 /// 197 /// Returns the newly allocated search context or NULL if an error occurred. 198 /// 199 /// Don't forget to clean up by calling vim_findfile_cleanup() if you are done 200 /// with the search context. 201 /// 202 /// Find the file 'filename' in the directory 'path'. 203 /// The parameter 'path' may contain wildcards. If so only search 'level' 204 /// directories deep. The parameter 'level' is the absolute maximum and is 205 /// not related to restricts given to the '**' wildcard. If 'level' is 100 206 /// and you use '**200' vim_findfile() will stop after 100 levels. 207 /// 208 /// 'filename' cannot contain wildcards! It is used as-is, no backslashes to 209 /// escape special characters. 210 /// 211 /// If 'stopdirs' is not NULL and nothing is found downward, the search is 212 /// restarted on the next higher directory level. This is repeated until the 213 /// start-directory of a search is contained in 'stopdirs'. 'stopdirs' has the 214 /// format ";*<dirname>*\(;<dirname>\)*;\=$". 215 /// 216 /// If the 'path' is relative, the starting dir for the search is either VIM's 217 /// current dir or if the path starts with "./" the current files dir. 218 /// If the 'path' is absolute, the starting dir is that part of the path before 219 /// the first wildcard. 220 /// 221 /// Upward search is only done on the starting dir. 222 /// 223 /// If 'free_visited' is true the list of already visited files/directories is 224 /// cleared. Set this to false if you just want to search from another 225 /// directory, but want to be sure that no directory from a previous search is 226 /// searched again. This is useful if you search for a file at different places. 227 /// The list of visited files/dirs can also be cleared with the function 228 /// vim_findfile_free_visited(). 229 /// 230 /// Set the parameter 'find_what' to FINDFILE_DIR if you want to search for 231 /// directories only, FINDFILE_FILE for files only, FINDFILE_BOTH for both. 232 /// 233 /// A search context returned by a previous call to vim_findfile_init() can be 234 /// passed in the parameter "search_ctx_arg". This context is reused and 235 /// reinitialized with the new parameters. The list of already visited 236 /// directories from this context is only deleted if the parameter 237 /// "free_visited" is true. Be aware that the passed "search_ctx_arg" is freed 238 /// if the reinitialization fails. 239 /// 240 /// If you don't have a search context from a previous call "search_ctx_arg" 241 /// must be NULL. 242 /// 243 /// This function silently ignores a few errors, vim_findfile() will have 244 /// limited functionality then. 245 /// 246 /// @param tagfile expanding names of tags files 247 /// @param rel_fname file name to use for "." 248 void *vim_findfile_init(char *path, char *filename, size_t filenamelen, char *stopdirs, int level, 249 int free_visited, int find_what, void *search_ctx_arg, int tagfile, 250 char *rel_fname) 251 { 252 ff_stack_T *sptr; 253 ff_search_ctx_T *search_ctx; 254 255 // If a search context is given by the caller, reuse it, else allocate a 256 // new one. 257 if (search_ctx_arg != NULL) { 258 search_ctx = search_ctx_arg; 259 } else { 260 search_ctx = xcalloc(1, sizeof(ff_search_ctx_T)); 261 } 262 search_ctx->ffsc_find_what = find_what; 263 search_ctx->ffsc_tagfile = tagfile; 264 265 // clear the search context, but NOT the visited lists 266 ff_clear(search_ctx); 267 268 // clear visited list if wanted 269 if (free_visited == true) { 270 vim_findfile_free_visited(search_ctx); 271 } else { 272 // Reuse old visited lists. Get the visited list for the given 273 // filename. If no list for the current filename exists, creates a new 274 // one. 275 search_ctx->ffsc_visited_list 276 = ff_get_visited_list(filename, filenamelen, 277 &search_ctx->ffsc_visited_lists_list); 278 if (search_ctx->ffsc_visited_list == NULL) { 279 goto error_return; 280 } 281 search_ctx->ffsc_dir_visited_list 282 = ff_get_visited_list(filename, filenamelen, 283 &search_ctx->ffsc_dir_visited_lists_list); 284 if (search_ctx->ffsc_dir_visited_list == NULL) { 285 goto error_return; 286 } 287 } 288 289 if (ff_expand_buffer.data == NULL) { 290 ff_expand_buffer.size = 0; 291 ff_expand_buffer.data = xmalloc(MAXPATHL); 292 } 293 294 // Store information on starting dir now if path is relative. 295 // If path is absolute, we do that later. 296 if (path[0] == '.' 297 && (vim_ispathsep(path[1]) || path[1] == NUL) 298 && (!tagfile || vim_strchr(p_cpo, CPO_DOTTAG) == NULL) 299 && rel_fname != NULL) { 300 size_t len = (size_t)(path_tail(rel_fname) - rel_fname); 301 302 if (!vim_isAbsName(rel_fname) && len + 1 < MAXPATHL) { 303 // Make the start dir an absolute path name. 304 xmemcpyz(ff_expand_buffer.data, rel_fname, len); 305 ff_expand_buffer.size = len; 306 search_ctx->ffsc_start_dir = cstr_as_string(FullName_save(ff_expand_buffer.data, false)); 307 } else { 308 search_ctx->ffsc_start_dir = cbuf_to_string(rel_fname, len); 309 } 310 if (*++path != NUL) { 311 path++; 312 } 313 } else if (*path == NUL || !vim_isAbsName(path)) { 314 #ifdef BACKSLASH_IN_FILENAME 315 // "c:dir" needs "c:" to be expanded, otherwise use current dir 316 if (*path != NUL && path[1] == ':') { 317 char drive[3]; 318 319 drive[0] = path[0]; 320 drive[1] = ':'; 321 drive[2] = NUL; 322 if (vim_FullName(drive, ff_expand_buffer.data, MAXPATHL, true) == FAIL) { 323 goto error_return; 324 } 325 path += 2; 326 } else 327 #endif 328 if (os_dirname(ff_expand_buffer.data, MAXPATHL) == FAIL) { 329 goto error_return; 330 } 331 ff_expand_buffer.size = strlen(ff_expand_buffer.data); 332 333 search_ctx->ffsc_start_dir = copy_string(ff_expand_buffer, NULL); 334 } 335 336 // If stopdirs are given, split them into an array of pointers. 337 // If this fails (mem allocation), there is no upward search at all or a 338 // stop directory is not recognized -> continue silently. 339 // If stopdirs just contains a ";" or is empty, 340 // search_ctx->ffsc_stopdirs_v will only contain a NULL pointer. This 341 // is handled as unlimited upward search. See function 342 // ff_path_in_stoplist() for details. 343 if (stopdirs != NULL) { 344 char *walker = stopdirs; 345 346 while (*walker == ';') { 347 walker++; 348 } 349 350 size_t dircount = 1; 351 search_ctx->ffsc_stopdirs_v = xmalloc(sizeof(String)); 352 353 do { 354 char *helper = walker; 355 void *ptr = xrealloc(search_ctx->ffsc_stopdirs_v, 356 (dircount + 1) * sizeof(String)); 357 search_ctx->ffsc_stopdirs_v = ptr; 358 walker = vim_strchr(walker, ';'); 359 assert(!walker || walker - helper >= 0); 360 size_t len = walker ? (size_t)(walker - helper) : strlen(helper); 361 // "" means ascent till top of directory tree. 362 if (*helper != NUL && !vim_isAbsName(helper) && len + 1 < MAXPATHL) { 363 // Make the stop dir an absolute path name. 364 xmemcpyz(ff_expand_buffer.data, helper, len); 365 ff_expand_buffer.size = len; 366 search_ctx->ffsc_stopdirs_v[dircount - 1] = cstr_as_string(FullName_save(helper, len)); 367 } else { 368 search_ctx->ffsc_stopdirs_v[dircount - 1] = cbuf_to_string(helper, len); 369 } 370 if (walker) { 371 walker++; 372 } 373 dircount++; 374 } while (walker != NULL); 375 376 search_ctx->ffsc_stopdirs_v[dircount - 1] = NULL_STRING; 377 } 378 379 search_ctx->ffsc_level = level; 380 381 // split into: 382 // -fix path 383 // -wildcard_stuff (might be NULL) 384 char *wc_part = vim_strchr(path, '*'); 385 if (wc_part != NULL) { 386 int64_t llevel; 387 char *errpt; 388 389 // save the fix part of the path 390 assert(wc_part - path >= 0); 391 search_ctx->ffsc_fix_path = cbuf_to_string(path, (size_t)(wc_part - path)); 392 393 // copy wc_path and add restricts to the '**' wildcard. 394 // The octet after a '**' is used as a (binary) counter. 395 // So '**3' is transposed to '**^C' ('^C' is ASCII value 3) 396 // or '**76' is transposed to '**N'( 'N' is ASCII value 76). 397 // If no restrict is given after '**' the default is used. 398 // Due to this technique the path looks awful if you print it as a 399 // string. 400 ff_expand_buffer.size = 0; 401 while (*wc_part != NUL) { 402 if (ff_expand_buffer.size + 5 >= MAXPATHL) { 403 emsg(_(e_path_too_long_for_completion)); 404 break; 405 } 406 if (strncmp(wc_part, "**", 2) == 0) { 407 ff_expand_buffer.data[ff_expand_buffer.size++] = *wc_part++; 408 ff_expand_buffer.data[ff_expand_buffer.size++] = *wc_part++; 409 410 llevel = strtol(wc_part, &errpt, 10); 411 if (errpt != wc_part && llevel > 0 && llevel < 255) { 412 ff_expand_buffer.data[ff_expand_buffer.size++] = (char)llevel; 413 } else if (errpt != wc_part && llevel == 0) { 414 // restrict is 0 -> remove already added '**' 415 ff_expand_buffer.size -= 2; 416 } else { 417 ff_expand_buffer.data[ff_expand_buffer.size++] = FF_MAX_STAR_STAR_EXPAND; 418 } 419 wc_part = errpt; 420 if (*wc_part != NUL && !vim_ispathsep(*wc_part)) { 421 semsg(_( 422 "E343: Invalid path: '**[number]' must be at the end of the path or be followed by '%s'."), 423 PATHSEPSTR); 424 goto error_return; 425 } 426 } else { 427 ff_expand_buffer.data[ff_expand_buffer.size++] = *wc_part++; 428 } 429 } 430 ff_expand_buffer.data[ff_expand_buffer.size] = NUL; 431 search_ctx->ffsc_wc_path = copy_string(ff_expand_buffer, false); 432 } else { 433 search_ctx->ffsc_fix_path = cstr_to_string(path); 434 } 435 436 if (search_ctx->ffsc_start_dir.data == NULL) { 437 // store the fix part as startdir. 438 // This is needed if the parameter path is fully qualified. 439 search_ctx->ffsc_start_dir = copy_string(search_ctx->ffsc_fix_path, false); 440 search_ctx->ffsc_fix_path.data[0] = NUL; 441 search_ctx->ffsc_fix_path.size = 0; 442 } 443 444 // create an absolute path 445 if (search_ctx->ffsc_start_dir.size 446 + search_ctx->ffsc_fix_path.size + 3 >= MAXPATHL) { 447 emsg(_(e_path_too_long_for_completion)); 448 goto error_return; 449 } 450 451 bool add_sep = !after_pathsep(search_ctx->ffsc_start_dir.data, 452 search_ctx->ffsc_start_dir.data + search_ctx->ffsc_start_dir.size); 453 ff_expand_buffer.size = (size_t)vim_snprintf(ff_expand_buffer.data, 454 MAXPATHL, 455 "%s%s", 456 search_ctx->ffsc_start_dir.data, 457 add_sep ? PATHSEPSTR : ""); 458 assert(ff_expand_buffer.size < MAXPATHL); 459 460 { 461 size_t bufsize = ff_expand_buffer.size + search_ctx->ffsc_fix_path.size + 1; 462 char *buf = xmalloc(bufsize); 463 464 vim_snprintf(buf, 465 bufsize, 466 "%s%s", 467 ff_expand_buffer.data, 468 search_ctx->ffsc_fix_path.data); 469 if (os_isdir(buf)) { 470 if (search_ctx->ffsc_fix_path.size > 0) { 471 add_sep = !after_pathsep(search_ctx->ffsc_fix_path.data, 472 search_ctx->ffsc_fix_path.data + search_ctx->ffsc_fix_path.size); 473 ff_expand_buffer.size += (size_t)vim_snprintf(ff_expand_buffer.data + ff_expand_buffer.size, 474 MAXPATHL - ff_expand_buffer.size, 475 "%s%s", 476 search_ctx->ffsc_fix_path.data, 477 add_sep ? PATHSEPSTR : ""); 478 assert(ff_expand_buffer.size < MAXPATHL); 479 } 480 } else { 481 char *p = path_tail(search_ctx->ffsc_fix_path.data); 482 int len = (int)search_ctx->ffsc_fix_path.size; 483 484 if (p > search_ctx->ffsc_fix_path.data) { 485 // do not add '..' to the path and start upwards searching 486 len = (int)(p - search_ctx->ffsc_fix_path.data) - 1; 487 if ((len >= 2 && strncmp(search_ctx->ffsc_fix_path.data, "..", 2) == 0) 488 && (len == 2 || search_ctx->ffsc_fix_path.data[2] == PATHSEP)) { 489 xfree(buf); 490 goto error_return; 491 } 492 493 add_sep = !after_pathsep(search_ctx->ffsc_fix_path.data, 494 search_ctx->ffsc_fix_path.data + search_ctx->ffsc_fix_path.size); 495 ff_expand_buffer.size += (size_t)vim_snprintf(ff_expand_buffer.data + ff_expand_buffer.size, 496 MAXPATHL - ff_expand_buffer.size, 497 "%.*s%s", 498 len, 499 search_ctx->ffsc_fix_path.data, 500 add_sep ? PATHSEPSTR : ""); 501 assert(ff_expand_buffer.size < MAXPATHL); 502 } 503 504 if (search_ctx->ffsc_wc_path.data != NULL) { 505 size_t tempsize = (search_ctx->ffsc_fix_path.size - (size_t)len) 506 + search_ctx->ffsc_wc_path.size + 1; 507 char *temp = xmalloc(tempsize); 508 search_ctx->ffsc_wc_path.size = (size_t)vim_snprintf(temp, 509 tempsize, 510 "%s%s", 511 search_ctx->ffsc_fix_path.data + len, 512 search_ctx->ffsc_wc_path.data); 513 assert(search_ctx->ffsc_wc_path.size < tempsize); 514 xfree(search_ctx->ffsc_wc_path.data); 515 search_ctx->ffsc_wc_path.data = temp; 516 } 517 } 518 xfree(buf); 519 } 520 521 sptr = ff_create_stack_element(ff_expand_buffer.data, 522 ff_expand_buffer.size, 523 search_ctx->ffsc_wc_path.data, 524 search_ctx->ffsc_wc_path.size, 525 level, 0); 526 527 ff_push(search_ctx, sptr); 528 search_ctx->ffsc_file_to_search = cbuf_to_string(filename, filenamelen); 529 return search_ctx; 530 531 error_return: 532 // We clear the search context now! 533 // Even when the caller gave us a (perhaps valid) context we free it here, 534 // as we might have already destroyed it. 535 vim_findfile_cleanup(search_ctx); 536 return NULL; 537 } 538 539 /// @return the stopdir string. Check that ';' is not escaped. 540 char *vim_findfile_stopdir(char *buf) 541 { 542 for (; *buf != NUL && *buf != ';' && (buf[0] != '\\' || buf[1] != ';'); buf++) {} 543 char *dst = buf; 544 if (*buf == ';') { 545 goto is_semicolon; 546 } 547 if (*buf == NUL) { 548 goto is_nul; 549 } 550 goto start; 551 while (*buf != NUL && *buf != ';') { 552 if (buf[0] == '\\' && buf[1] == ';') { 553 start: 554 // Overwrite the escape char. 555 *dst++ = ';'; 556 buf += 2; 557 } else { 558 *dst++ = *buf++; 559 } 560 } 561 assert(dst < buf); 562 *dst = NUL; 563 if (*buf == ';') { 564 is_semicolon: 565 *buf = NUL; 566 buf++; 567 } else { // if (*buf == NUL) 568 is_nul: 569 buf = NULL; 570 } 571 return buf; 572 } 573 574 /// Clean up the given search context. Can handle a NULL pointer. 575 void vim_findfile_cleanup(void *ctx) 576 { 577 if (ctx == NULL) { 578 return; 579 } 580 581 vim_findfile_free_visited(ctx); 582 ff_clear(ctx); 583 xfree(ctx); 584 } 585 586 /// Find a file in a search context. 587 /// The search context was created with vim_findfile_init() above. 588 /// 589 /// To get all matching files call this function until you get NULL. 590 /// 591 /// If the passed search_context is NULL, NULL is returned. 592 /// 593 /// The search algorithm is depth first. To change this replace the 594 /// stack with a list (don't forget to leave partly searched directories on the 595 /// top of the list). 596 /// 597 /// @return a pointer to an allocated file name or, 598 /// NULL if nothing found. 599 char *vim_findfile(void *search_ctx_arg) 600 { 601 String rest_of_wildcards; 602 char *path_end = NULL; 603 ff_stack_T *stackp = NULL; 604 605 if (search_ctx_arg == NULL) { 606 return NULL; 607 } 608 609 ff_search_ctx_T *search_ctx = (ff_search_ctx_T *)search_ctx_arg; 610 611 // filepath is used as buffer for various actions and as the storage to 612 // return a found filename. 613 String file_path = { .data = xmalloc(MAXPATHL) }; 614 615 // store the end of the start dir -- needed for upward search 616 if (search_ctx->ffsc_start_dir.data != NULL) { 617 path_end = &search_ctx->ffsc_start_dir.data[search_ctx->ffsc_start_dir.size]; 618 } 619 620 // upward search loop 621 while (true) { 622 // downward search loop 623 while (true) { 624 // check if user wants to stop the search 625 os_breakcheck(); 626 if (got_int) { 627 break; 628 } 629 630 // get directory to work on from stack 631 stackp = ff_pop(search_ctx); 632 if (stackp == NULL) { 633 break; 634 } 635 636 // TODO(vim): decide if we leave this test in 637 // 638 // GOOD: don't search a directory(-tree) twice. 639 // BAD: - check linked list for every new directory entered. 640 // - check for double files also done below 641 // 642 // Here we check if we already searched this directory. 643 // We already searched a directory if: 644 // 1) The directory is the same. 645 // 2) We would use the same wildcard string. 646 // 647 // Good if you have links on same directory via several ways 648 // or you have selfreferences in directories (e.g. SuSE Linux 6.3: 649 // /etc/rc.d/init.d is linked to /etc/rc.d -> endless loop) 650 // 651 // This check is only needed for directories we work on for the 652 // first time (hence stackp->ff_filearray == NULL) 653 if (stackp->ffs_filearray == NULL 654 && ff_check_visited(&search_ctx->ffsc_dir_visited_list->ffvl_visited_list, 655 stackp->ffs_fix_path.data, stackp->ffs_fix_path.size, 656 stackp->ffs_wc_path.data, stackp->ffs_wc_path.size) == FAIL) { 657 #ifdef FF_VERBOSE 658 if (p_verbose >= 5) { 659 verbose_enter_scroll(); 660 smsg(0, "Already Searched: %s (%s)", 661 stackp->ffs_fix_path.data, stackp->ffs_wc_path.data); 662 msg_puts("\n"); // don't overwrite this either 663 verbose_leave_scroll(); 664 } 665 #endif 666 ff_free_stack_element(stackp); 667 continue; 668 #ifdef FF_VERBOSE 669 } else if (p_verbose >= 5) { 670 verbose_enter_scroll(); 671 smsg(0, "Searching: %s (%s)", 672 stackp->ffs_fix_path.data, stackp->ffs_wc_path.data); 673 msg_puts("\n"); // don't overwrite this either 674 verbose_leave_scroll(); 675 #endif 676 } 677 678 // check depth 679 if (stackp->ffs_level <= 0) { 680 ff_free_stack_element(stackp); 681 continue; 682 } 683 684 file_path.data[0] = NUL; 685 file_path.size = 0; 686 687 // If no filearray till now expand wildcards 688 // The function expand_wildcards() can handle an array of paths 689 // and all possible expands are returned in one array. We use this 690 // to handle the expansion of '**' into an empty string. 691 if (stackp->ffs_filearray == NULL) { 692 char *dirptrs[2]; 693 694 // we use filepath to build the path expand_wildcards() should expand. 695 dirptrs[0] = file_path.data; 696 dirptrs[1] = NULL; 697 698 // if we have a start dir copy it in 699 if (!vim_isAbsName(stackp->ffs_fix_path.data) 700 && search_ctx->ffsc_start_dir.data) { 701 if (search_ctx->ffsc_start_dir.size + 1 >= MAXPATHL) { 702 ff_free_stack_element(stackp); 703 goto fail; 704 } 705 bool add_sep = !after_pathsep(search_ctx->ffsc_start_dir.data, 706 search_ctx->ffsc_start_dir.data 707 + search_ctx->ffsc_start_dir.size); 708 file_path.size = (size_t)vim_snprintf(file_path.data, 709 MAXPATHL, 710 "%s%s", 711 search_ctx->ffsc_start_dir.data, 712 add_sep ? PATHSEPSTR : ""); 713 if (file_path.size >= MAXPATHL) { 714 ff_free_stack_element(stackp); 715 goto fail; 716 } 717 } 718 719 // append the fix part of the search path 720 if (file_path.size + stackp->ffs_fix_path.size + 1 >= MAXPATHL) { 721 ff_free_stack_element(stackp); 722 goto fail; 723 } 724 bool add_sep = !after_pathsep(stackp->ffs_fix_path.data, 725 stackp->ffs_fix_path.data + stackp->ffs_fix_path.size); 726 file_path.size += (size_t)vim_snprintf(file_path.data + file_path.size, 727 MAXPATHL - file_path.size, 728 "%s%s", 729 stackp->ffs_fix_path.data, 730 add_sep ? PATHSEPSTR : ""); 731 if (file_path.size >= MAXPATHL) { 732 ff_free_stack_element(stackp); 733 goto fail; 734 } 735 736 rest_of_wildcards = stackp->ffs_wc_path; 737 if (*rest_of_wildcards.data != NUL) { 738 if (strncmp(rest_of_wildcards.data, "**", 2) == 0) { 739 // pointer to the restrict byte 740 // The restrict byte is not a character! 741 char *p = rest_of_wildcards.data + 2; 742 743 if (*p > 0) { 744 (*p)--; 745 if (file_path.size + 1 >= MAXPATHL) { 746 ff_free_stack_element(stackp); 747 goto fail; 748 } 749 file_path.data[file_path.size++] = '*'; 750 } 751 752 if (*p == 0) { 753 // remove '**<numb> from wildcards 754 memmove(rest_of_wildcards.data, 755 rest_of_wildcards.data + 3, 756 (rest_of_wildcards.size - 3) + 1); // +1 for NUL 757 rest_of_wildcards.size -= 3; 758 stackp->ffs_wc_path.size = rest_of_wildcards.size; 759 } else { 760 rest_of_wildcards.data += 3; 761 rest_of_wildcards.size -= 3; 762 } 763 764 if (stackp->ffs_star_star_empty == 0) { 765 // if not done before, expand '**' to empty 766 stackp->ffs_star_star_empty = 1; 767 dirptrs[1] = stackp->ffs_fix_path.data; 768 } 769 } 770 771 // Here we copy until the next path separator or the end of 772 // the path. If we stop at a path separator, there is 773 // still something else left. This is handled below by 774 // pushing every directory returned from expand_wildcards() 775 // on the stack again for further search. 776 while (*rest_of_wildcards.data 777 && !vim_ispathsep(*rest_of_wildcards.data)) { 778 if (file_path.size + 1 >= MAXPATHL) { 779 ff_free_stack_element(stackp); 780 goto fail; 781 } 782 file_path.data[file_path.size++] = *rest_of_wildcards.data++; 783 rest_of_wildcards.size--; 784 } 785 786 file_path.data[file_path.size] = NUL; 787 if (vim_ispathsep(*rest_of_wildcards.data)) { 788 rest_of_wildcards.data++; 789 rest_of_wildcards.size--; 790 } 791 } 792 793 // Expand wildcards like "*" and "$VAR". 794 // If the path is a URL don't try this. 795 if (path_with_url(dirptrs[0])) { 796 stackp->ffs_filearray = xmalloc(sizeof(char *)); 797 stackp->ffs_filearray[0] = xmemdupz(dirptrs[0], file_path.size); 798 stackp->ffs_filearray_size = 1; 799 } else { 800 // Add EW_NOTWILD because the expanded path may contain 801 // wildcard characters that are to be taken literally. 802 // This is a bit of a hack. 803 expand_wildcards((dirptrs[1] == NULL) ? 1 : 2, dirptrs, 804 &stackp->ffs_filearray_size, 805 &stackp->ffs_filearray, 806 EW_DIR|EW_ADDSLASH|EW_SILENT|EW_NOTWILD); 807 } 808 809 stackp->ffs_filearray_cur = 0; 810 stackp->ffs_stage = 0; 811 } else { 812 rest_of_wildcards.data = &stackp->ffs_wc_path.data[stackp->ffs_wc_path.size]; 813 rest_of_wildcards.size = 0; 814 } 815 816 if (stackp->ffs_stage == 0) { 817 // this is the first time we work on this directory 818 if (*rest_of_wildcards.data == NUL) { 819 // We don't have further wildcards to expand, so we have to 820 // check for the final file now. 821 for (int i = stackp->ffs_filearray_cur; i < stackp->ffs_filearray_size; i++) { 822 if (!path_with_url(stackp->ffs_filearray[i]) 823 && !os_isdir(stackp->ffs_filearray[i])) { 824 continue; // not a directory 825 } 826 // prepare the filename to be checked for existence below 827 size_t len = strlen(stackp->ffs_filearray[i]); 828 if (len + 1 + search_ctx->ffsc_file_to_search.size >= MAXPATHL) { 829 ff_free_stack_element(stackp); 830 goto fail; 831 } 832 bool add_sep = !after_pathsep(stackp->ffs_filearray[i], 833 stackp->ffs_filearray[i] + len); 834 file_path.size = (size_t)vim_snprintf(file_path.data, 835 MAXPATHL, 836 "%s%s%s", 837 stackp->ffs_filearray[i], 838 add_sep ? PATHSEPSTR : "", 839 search_ctx->ffsc_file_to_search.data); 840 if (file_path.size >= MAXPATHL) { 841 ff_free_stack_element(stackp); 842 goto fail; 843 } 844 845 // Try without extra suffix and then with suffixes 846 // from 'suffixesadd'. 847 len = file_path.size; 848 char *suf = search_ctx->ffsc_tagfile ? "" : curbuf->b_p_sua; 849 while (true) { 850 // if file exists and we didn't already find it 851 if ((path_with_url(file_path.data) 852 || (os_path_exists(file_path.data) 853 && (search_ctx->ffsc_find_what == FINDFILE_BOTH 854 || ((search_ctx->ffsc_find_what == FINDFILE_DIR) 855 == os_isdir(file_path.data))))) 856 #ifndef FF_VERBOSE 857 && (ff_check_visited(&search_ctx->ffsc_visited_list->ffvl_visited_list, 858 file_path.data, file_path.size, "", 0) == OK) 859 #endif 860 ) { 861 #ifdef FF_VERBOSE 862 if (ff_check_visited(&search_ctx->ffsc_visited_list->ffvl_visited_list, 863 file_path.data, file_path.size, "", 0) == FAIL) { 864 if (p_verbose >= 5) { 865 verbose_enter_scroll(); 866 smsg(0, "Already: %s", file_path.data); 867 msg_puts("\n"); // don't overwrite this either 868 verbose_leave_scroll(); 869 } 870 continue; 871 } 872 #endif 873 874 // push dir to examine rest of subdirs later 875 assert(i < INT_MAX); 876 stackp->ffs_filearray_cur = i + 1; 877 ff_push(search_ctx, stackp); 878 879 if (!path_with_url(file_path.data)) { 880 file_path.size = simplify_filename(file_path.data); 881 } 882 883 if (os_dirname(ff_expand_buffer.data, MAXPATHL) == OK) { 884 ff_expand_buffer.size = strlen(ff_expand_buffer.data); 885 char *p = path_shorten_fname(file_path.data, ff_expand_buffer.data); 886 if (p != NULL) { 887 memmove(file_path.data, p, 888 (size_t)((file_path.data + file_path.size) - p) + 1); // +1 for NUL 889 file_path.size -= (size_t)(p - file_path.data); 890 } 891 } 892 #ifdef FF_VERBOSE 893 if (p_verbose >= 5) { 894 verbose_enter_scroll(); 895 smsg(0, "HIT: %s", file_path.data); 896 msg_puts("\n"); // don't overwrite this either 897 verbose_leave_scroll(); 898 } 899 #endif 900 return file_path.data; 901 } 902 903 // Not found or found already, try next suffix. 904 if (*suf == NUL) { 905 break; 906 } 907 assert(MAXPATHL >= file_path.size); 908 file_path.size = len + copy_option_part(&suf, file_path.data + len, 909 MAXPATHL - len, ","); 910 } 911 } 912 } else { 913 // still wildcards left, push the directories for further search 914 for (int i = stackp->ffs_filearray_cur; i < stackp->ffs_filearray_size; i++) { 915 if (!os_isdir(stackp->ffs_filearray[i])) { 916 continue; // not a directory 917 } 918 ff_push(search_ctx, 919 ff_create_stack_element(stackp->ffs_filearray[i], 920 strlen(stackp->ffs_filearray[i]), 921 rest_of_wildcards.data, 922 rest_of_wildcards.size, 923 stackp->ffs_level - 1, 0)); 924 } 925 } 926 stackp->ffs_filearray_cur = 0; 927 stackp->ffs_stage = 1; 928 } 929 930 // if wildcards contains '**' we have to descent till we reach the 931 // leaves of the directory tree. 932 if (strncmp(stackp->ffs_wc_path.data, "**", 2) == 0) { 933 for (int i = stackp->ffs_filearray_cur; 934 i < stackp->ffs_filearray_size; i++) { 935 if (path_fnamecmp(stackp->ffs_filearray[i], 936 stackp->ffs_fix_path.data) == 0) { 937 continue; // don't repush same directory 938 } 939 if (!os_isdir(stackp->ffs_filearray[i])) { 940 continue; // not a directory 941 } 942 ff_push(search_ctx, 943 ff_create_stack_element(stackp->ffs_filearray[i], 944 strlen(stackp->ffs_filearray[i]), 945 stackp->ffs_wc_path.data, 946 stackp->ffs_wc_path.size, 947 stackp->ffs_level - 1, 1)); 948 } 949 } 950 951 // we are done with the current directory 952 ff_free_stack_element(stackp); 953 } 954 955 // If we reached this, we didn't find anything downwards. 956 // Let's check if we should do an upward search. 957 if (search_ctx->ffsc_start_dir.data 958 && search_ctx->ffsc_stopdirs_v != NULL && !got_int) { 959 ff_stack_T *sptr; 960 // path_end may point to the NUL or the previous path separator 961 ptrdiff_t plen = (path_end - search_ctx->ffsc_start_dir.data) + (*path_end != NUL); 962 963 // is the last starting directory in the stop list? 964 if (ff_path_in_stoplist(search_ctx->ffsc_start_dir.data, 965 (size_t)plen, search_ctx->ffsc_stopdirs_v)) { 966 break; 967 } 968 969 // cut of last dir 970 while (path_end > search_ctx->ffsc_start_dir.data && vim_ispathsep(*path_end)) { 971 path_end--; 972 } 973 while (path_end > search_ctx->ffsc_start_dir.data && !vim_ispathsep(path_end[-1])) { 974 path_end--; 975 } 976 *path_end = NUL; 977 978 // we may have shortened search_ctx->ffsc_start_dir, so update it's length 979 search_ctx->ffsc_start_dir.size = (size_t)(path_end - search_ctx->ffsc_start_dir.data); 980 path_end--; 981 982 if (*search_ctx->ffsc_start_dir.data == NUL) { 983 break; 984 } 985 986 if (search_ctx->ffsc_start_dir.size + 1 987 + search_ctx->ffsc_fix_path.size >= MAXPATHL) { 988 goto fail; 989 } 990 bool add_sep = !after_pathsep(search_ctx->ffsc_start_dir.data, 991 search_ctx->ffsc_start_dir.data 992 + search_ctx->ffsc_start_dir.size); 993 file_path.size = (size_t)vim_snprintf(file_path.data, 994 MAXPATHL, 995 "%s%s%s", 996 search_ctx->ffsc_start_dir.data, 997 add_sep ? PATHSEPSTR : "", 998 search_ctx->ffsc_fix_path.data); 999 if (file_path.size >= MAXPATHL) { 1000 goto fail; 1001 } 1002 1003 // create a new stack entry 1004 sptr = ff_create_stack_element(file_path.data, 1005 file_path.size, 1006 search_ctx->ffsc_wc_path.data, 1007 search_ctx->ffsc_wc_path.size, 1008 search_ctx->ffsc_level, 0); 1009 ff_push(search_ctx, sptr); 1010 } else { 1011 break; 1012 } 1013 } 1014 1015 fail: 1016 xfree(file_path.data); 1017 return NULL; 1018 } 1019 1020 /// Free the list of lists of visited files and directories 1021 /// Can handle it if the passed search_context is NULL; 1022 static void vim_findfile_free_visited(void *search_ctx_arg) 1023 { 1024 if (search_ctx_arg == NULL) { 1025 return; 1026 } 1027 1028 ff_search_ctx_T *search_ctx = (ff_search_ctx_T *)search_ctx_arg; 1029 vim_findfile_free_visited_list(&search_ctx->ffsc_visited_lists_list); 1030 vim_findfile_free_visited_list(&search_ctx->ffsc_dir_visited_lists_list); 1031 } 1032 1033 static void vim_findfile_free_visited_list(ff_visited_list_hdr_T **list_headp) 1034 { 1035 ff_visited_list_hdr_T *vp; 1036 1037 while (*list_headp != NULL) { 1038 vp = (*list_headp)->ffvl_next; 1039 ff_free_visited_list((*list_headp)->ffvl_visited_list); 1040 1041 xfree((*list_headp)->ffvl_filename); 1042 xfree(*list_headp); 1043 *list_headp = vp; 1044 } 1045 *list_headp = NULL; 1046 } 1047 1048 static void ff_free_visited_list(ff_visited_T *vl) 1049 { 1050 ff_visited_T *vp; 1051 1052 while (vl != NULL) { 1053 vp = vl->ffv_next; 1054 xfree(vl->ffv_wc_path); 1055 xfree(vl); 1056 vl = vp; 1057 } 1058 vl = NULL; 1059 } 1060 1061 /// @return the already visited list for the given filename. If none is found it 1062 /// allocates a new one. 1063 static ff_visited_list_hdr_T *ff_get_visited_list(char *filename, size_t filenamelen, 1064 ff_visited_list_hdr_T **list_headp) 1065 { 1066 ff_visited_list_hdr_T *retptr = NULL; 1067 1068 // check if a visited list for the given filename exists 1069 if (*list_headp != NULL) { 1070 retptr = *list_headp; 1071 while (retptr != NULL) { 1072 if (path_fnamecmp(filename, retptr->ffvl_filename) == 0) { 1073 #ifdef FF_VERBOSE 1074 if (p_verbose >= 5) { 1075 verbose_enter_scroll(); 1076 smsg(0, "ff_get_visited_list: FOUND list for %s", filename); 1077 msg_puts("\n"); // don't overwrite this either 1078 verbose_leave_scroll(); 1079 } 1080 #endif 1081 return retptr; 1082 } 1083 retptr = retptr->ffvl_next; 1084 } 1085 } 1086 1087 #ifdef FF_VERBOSE 1088 if (p_verbose >= 5) { 1089 verbose_enter_scroll(); 1090 smsg(0, "ff_get_visited_list: new list for %s", filename); 1091 msg_puts("\n"); // don't overwrite this either 1092 verbose_leave_scroll(); 1093 } 1094 #endif 1095 1096 // if we reach this we didn't find a list and we have to allocate new list 1097 retptr = xmalloc(sizeof(*retptr)); 1098 1099 retptr->ffvl_visited_list = NULL; 1100 retptr->ffvl_filename = xmemdupz(filename, filenamelen); 1101 retptr->ffvl_next = *list_headp; 1102 *list_headp = retptr; 1103 1104 return retptr; 1105 } 1106 1107 /// Check if two wildcard paths are equal. 1108 /// They are equal if: 1109 /// - both paths are NULL 1110 /// - they have the same length 1111 /// - char by char comparison is OK 1112 /// - the only differences are in the counters behind a '**', so 1113 /// '**\20' is equal to '**\24' 1114 static bool ff_wc_equal(char *s1, char *s2) 1115 { 1116 int i, j; 1117 int prev1 = NUL; 1118 int prev2 = NUL; 1119 1120 if (s1 == s2) { 1121 return true; 1122 } 1123 1124 if (s1 == NULL || s2 == NULL) { 1125 return false; 1126 } 1127 1128 for (i = 0, j = 0; s1[i] != NUL && s2[j] != NUL;) { 1129 int c1 = utf_ptr2char(s1 + i); 1130 int c2 = utf_ptr2char(s2 + j); 1131 1132 if ((p_fic ? mb_tolower(c1) != mb_tolower(c2) : c1 != c2) 1133 && (prev1 != '*' || prev2 != '*')) { 1134 return false; 1135 } 1136 prev2 = prev1; 1137 prev1 = c1; 1138 1139 i += utfc_ptr2len(s1 + i); 1140 j += utfc_ptr2len(s2 + j); 1141 } 1142 return s1[i] == s2[j]; 1143 } 1144 1145 /// maintains the list of already visited files and dirs 1146 /// 1147 /// @return FAIL if the given file/dir is already in the list or, 1148 /// OK if it is newly added 1149 static int ff_check_visited(ff_visited_T **visited_list, char *fname, size_t fnamelen, 1150 char *wc_path, size_t wc_pathlen) 1151 { 1152 ff_visited_T *vp; 1153 bool url = false; 1154 1155 FileID file_id; 1156 // For a URL we only compare the name, otherwise we compare the 1157 // device/inode. 1158 if (path_with_url(fname)) { 1159 xmemcpyz(ff_expand_buffer.data, fname, fnamelen); 1160 ff_expand_buffer.size = fnamelen; 1161 url = true; 1162 } else { 1163 ff_expand_buffer.data[0] = NUL; 1164 ff_expand_buffer.size = 0; 1165 if (!os_fileid(fname, &file_id)) { 1166 return FAIL; 1167 } 1168 } 1169 1170 // check against list of already visited files 1171 for (vp = *visited_list; vp != NULL; vp = vp->ffv_next) { 1172 if ((url && path_fnamecmp(vp->ffv_fname, ff_expand_buffer.data) == 0) 1173 || (!url && vp->file_id_valid 1174 && os_fileid_equal(&(vp->file_id), &file_id))) { 1175 // are the wildcard parts equal 1176 if (ff_wc_equal(vp->ffv_wc_path, wc_path)) { 1177 // already visited 1178 return FAIL; 1179 } 1180 } 1181 } 1182 1183 // New file/dir. Add it to the list of visited files/dirs. 1184 vp = xmalloc(offsetof(ff_visited_T, ffv_fname) + ff_expand_buffer.size + 1); 1185 1186 if (!url) { 1187 vp->file_id_valid = true; 1188 vp->file_id = file_id; 1189 vp->ffv_fname[0] = NUL; 1190 } else { 1191 vp->file_id_valid = false; 1192 STRCPY(vp->ffv_fname, ff_expand_buffer.data); 1193 } 1194 1195 if (wc_path != NULL) { 1196 vp->ffv_wc_path = xmemdupz(wc_path, wc_pathlen); 1197 } else { 1198 vp->ffv_wc_path = NULL; 1199 } 1200 1201 vp->ffv_next = *visited_list; 1202 *visited_list = vp; 1203 1204 return OK; 1205 } 1206 1207 /// create stack element from given path pieces 1208 static ff_stack_T *ff_create_stack_element(char *fix_part, size_t fix_partlen, char *wc_part, 1209 size_t wc_partlen, int level, int star_star_empty) 1210 { 1211 ff_stack_T *stack = xmalloc(sizeof(ff_stack_T)); 1212 1213 stack->ffs_prev = NULL; 1214 stack->ffs_filearray = NULL; 1215 stack->ffs_filearray_size = 0; 1216 stack->ffs_filearray_cur = 0; 1217 stack->ffs_stage = 0; 1218 stack->ffs_level = level; 1219 stack->ffs_star_star_empty = star_star_empty; 1220 1221 // the following saves NULL pointer checks in vim_findfile 1222 if (fix_part == NULL) { 1223 fix_part = ""; 1224 fix_partlen = 0; 1225 } 1226 stack->ffs_fix_path = cbuf_to_string(fix_part, fix_partlen); 1227 1228 if (wc_part == NULL) { 1229 wc_part = ""; 1230 wc_partlen = 0; 1231 } 1232 stack->ffs_wc_path = cbuf_to_string(wc_part, wc_partlen); 1233 1234 return stack; 1235 } 1236 1237 /// Push a dir on the directory stack. 1238 static void ff_push(ff_search_ctx_T *search_ctx, ff_stack_T *stack_ptr) 1239 { 1240 // check for NULL pointer, not to return an error to the user, but 1241 // to prevent a crash 1242 if (stack_ptr == NULL) { 1243 return; 1244 } 1245 1246 stack_ptr->ffs_prev = search_ctx->ffsc_stack_ptr; 1247 search_ctx->ffsc_stack_ptr = stack_ptr; 1248 } 1249 1250 /// Pop a dir from the directory stack. 1251 /// 1252 /// @return NULL if stack is empty. 1253 static ff_stack_T *ff_pop(ff_search_ctx_T *search_ctx) 1254 { 1255 ff_stack_T *sptr = search_ctx->ffsc_stack_ptr; 1256 if (search_ctx->ffsc_stack_ptr != NULL) { 1257 search_ctx->ffsc_stack_ptr = search_ctx->ffsc_stack_ptr->ffs_prev; 1258 } 1259 1260 return sptr; 1261 } 1262 1263 /// free the given stack element 1264 static void ff_free_stack_element(ff_stack_T *const stack_ptr) 1265 { 1266 if (stack_ptr == NULL) { 1267 return; 1268 } 1269 1270 // API_CLEAR_STRING handles possible NULL pointers 1271 API_CLEAR_STRING(stack_ptr->ffs_fix_path); 1272 API_CLEAR_STRING(stack_ptr->ffs_wc_path); 1273 1274 if (stack_ptr->ffs_filearray != NULL) { 1275 FreeWild(stack_ptr->ffs_filearray_size, stack_ptr->ffs_filearray); 1276 } 1277 1278 xfree(stack_ptr); 1279 } 1280 1281 /// Clear the search context, but NOT the visited list. 1282 static void ff_clear(ff_search_ctx_T *search_ctx) 1283 { 1284 ff_stack_T *sptr; 1285 1286 // clear up stack 1287 while ((sptr = ff_pop(search_ctx)) != NULL) { 1288 ff_free_stack_element(sptr); 1289 } 1290 1291 if (search_ctx->ffsc_stopdirs_v != NULL) { 1292 int i = 0; 1293 1294 while (search_ctx->ffsc_stopdirs_v[i].data != NULL) { 1295 xfree(search_ctx->ffsc_stopdirs_v[i].data); 1296 i++; 1297 } 1298 XFREE_CLEAR(search_ctx->ffsc_stopdirs_v); 1299 } 1300 1301 // reset everything 1302 API_CLEAR_STRING(search_ctx->ffsc_file_to_search); 1303 API_CLEAR_STRING(search_ctx->ffsc_start_dir); 1304 API_CLEAR_STRING(search_ctx->ffsc_fix_path); 1305 API_CLEAR_STRING(search_ctx->ffsc_wc_path); 1306 search_ctx->ffsc_level = 0; 1307 } 1308 1309 /// check if the given path is in the stopdirs 1310 /// 1311 /// @return true if yes else false 1312 static bool ff_path_in_stoplist(char *path, size_t path_len, String *stopdirs_v) 1313 { 1314 // eat up trailing path separators, except the first 1315 while (path_len > 1 && vim_ispathsep(path[path_len - 1])) { 1316 path_len--; 1317 } 1318 1319 // if no path consider it as match 1320 if (path_len == 0) { 1321 return true; 1322 } 1323 1324 for (int i = 0; stopdirs_v[i].data != NULL; i++) { 1325 // match for parent directory. So '/home' also matches 1326 // '/home/rks'. Check for PATHSEP in stopdirs_v[i], else 1327 // '/home/r' would also match '/home/rks' 1328 if (path_fnamencmp(stopdirs_v[i].data, path, path_len) == 0 1329 && (stopdirs_v[i].size <= path_len 1330 || vim_ispathsep(stopdirs_v[i].data[path_len]))) { 1331 return true; 1332 } 1333 } 1334 1335 return false; 1336 } 1337 1338 /// Find the file name "ptr[len]" in the path. Also finds directory names. 1339 /// 1340 /// On the first call set the parameter 'first' to true to initialize 1341 /// the search. For repeating calls to false. 1342 /// 1343 /// Repeating calls will return other files called 'ptr[len]' from the path. 1344 /// 1345 /// Only on the first call 'ptr' and 'len' are used. For repeating calls they 1346 /// don't need valid values. 1347 /// 1348 /// If nothing found on the first call the option FNAME_MESS will issue the 1349 /// message: 1350 /// 'Can't find file "<file>" in path' 1351 /// On repeating calls: 1352 /// 'No more file "<file>" found in path' 1353 /// 1354 /// options: 1355 /// FNAME_MESS give error message when not found 1356 /// 1357 /// Uses NameBuff[]! 1358 /// 1359 /// @param ptr file name 1360 /// @param len length of file name 1361 /// @param first use count'th matching file name 1362 /// @param rel_fname file name searching relative to 1363 /// @param[in,out] file_to_find modified copy of file name 1364 /// @param[in,out] search_ctx state of the search 1365 /// 1366 /// @return an allocated string for the file name. NULL for error. 1367 char *find_file_in_path(char *ptr, size_t len, int options, int first, char *rel_fname, 1368 char **file_to_find, char **search_ctx) 1369 { 1370 return find_file_in_path_option(ptr, len, options, first, 1371 (*curbuf->b_p_path == NUL 1372 ? p_path 1373 : curbuf->b_p_path), 1374 FINDFILE_BOTH, rel_fname, curbuf->b_p_sua, 1375 file_to_find, search_ctx); 1376 } 1377 1378 #if defined(EXITFREE) 1379 void free_findfile(void) 1380 { 1381 API_CLEAR_STRING(ff_expand_buffer); 1382 } 1383 #endif 1384 1385 /// Find the directory name "ptr[len]" in the path. 1386 /// 1387 /// options: 1388 /// FNAME_MESS give error message when not found 1389 /// FNAME_UNESC unescape backslashes 1390 /// 1391 /// Uses NameBuff[]! 1392 /// 1393 /// @param ptr file name 1394 /// @param len length of file name 1395 /// @param rel_fname file name searching relative to 1396 /// @param[in,out] file_to_find modified copy of file name 1397 /// @param[in,out] search_ctx state of the search 1398 /// 1399 /// @return an allocated string for the file name. NULL for error. 1400 char *find_directory_in_path(char *ptr, size_t len, int options, char *rel_fname, 1401 char **file_to_find, char **search_ctx) 1402 { 1403 return find_file_in_path_option(ptr, len, options, true, p_cdpath, 1404 FINDFILE_DIR, rel_fname, "", 1405 file_to_find, search_ctx); 1406 } 1407 1408 /// @param ptr file name 1409 /// @param len length of file name 1410 /// @param first use count'th matching file name 1411 /// @param path_option p_path or p_cdpath 1412 /// @param find_what FINDFILE_FILE, _DIR or _BOTH 1413 /// @param rel_fname file name we are looking relative to. 1414 /// @param suffixes list of suffixes, 'suffixesadd' option 1415 /// @param[in,out] file_to_find modified copy of file name 1416 /// @param[in,out] search_ctx_arg state of the search 1417 char *find_file_in_path_option(char *ptr, size_t len, int options, int first, char *path_option, 1418 int find_what, char *rel_fname, char *suffixes, char **file_to_find, 1419 char **search_ctx_arg) 1420 { 1421 ff_search_ctx_T **search_ctx = (ff_search_ctx_T **)search_ctx_arg; 1422 static char *dir; 1423 static bool did_findfile_init = false; 1424 char *file_name = NULL; 1425 static size_t file_to_findlen = 0; 1426 1427 if (rel_fname != NULL && path_with_url(rel_fname)) { 1428 // Do not attempt to search "relative" to a URL. #6009 1429 rel_fname = NULL; 1430 } 1431 1432 if (first == true) { 1433 if (len == 0) { 1434 return NULL; 1435 } 1436 1437 // copy file name into NameBuff, expanding environment variables 1438 char save_char = ptr[len]; 1439 ptr[len] = NUL; 1440 file_to_findlen = expand_env_esc(ptr, NameBuff, MAXPATHL, false, true, NULL); 1441 ptr[len] = save_char; 1442 1443 xfree(*file_to_find); 1444 *file_to_find = xmemdupz(NameBuff, file_to_findlen); 1445 if (options & FNAME_UNESC) { 1446 // Change all "\ " to " ". 1447 for (ptr = *file_to_find; *ptr != NUL; ptr++) { 1448 if (ptr[0] == '\\' && ptr[1] == ' ') { 1449 memmove(ptr, ptr + 1, 1450 (size_t)((*file_to_find + file_to_findlen) - (ptr + 1)) + 1); 1451 file_to_findlen--; 1452 } 1453 } 1454 } 1455 } 1456 1457 bool rel_to_curdir = ((*file_to_find)[0] == '.' 1458 && ((*file_to_find)[1] == NUL 1459 || vim_ispathsep((*file_to_find)[1]) 1460 || ((*file_to_find)[1] == '.' 1461 && ((*file_to_find)[2] == NUL 1462 || vim_ispathsep((*file_to_find)[2]))))); 1463 if (vim_isAbsName(*file_to_find) 1464 // "..", "../path", "." and "./path": don't use the path_option 1465 || rel_to_curdir 1466 #if defined(MSWIN) 1467 // handle "\tmp" as absolute path 1468 || vim_ispathsep((*file_to_find)[0]) 1469 // handle "c:name" as absolute path 1470 || ((*file_to_find)[0] != NUL && (*file_to_find)[1] == ':') 1471 #endif 1472 ) { 1473 // Absolute path, no need to use "path_option". 1474 // If this is not a first call, return NULL. We already returned a 1475 // filename on the first call. 1476 if (first == true) { 1477 if (path_with_url(*file_to_find)) { 1478 file_name = xmemdupz(*file_to_find, file_to_findlen); 1479 goto theend; 1480 } 1481 1482 size_t rel_fnamelen = rel_fname != NULL ? strlen(rel_fname) : 0; 1483 1484 // When FNAME_REL flag given first use the directory of the file. 1485 // Otherwise or when this fails use the current directory. 1486 for (int run = 1; run <= 2; run++) { 1487 size_t l = file_to_findlen; 1488 if (run == 1 1489 && rel_to_curdir 1490 && (options & FNAME_REL) 1491 && rel_fname != NULL 1492 && rel_fnamelen + l < MAXPATHL) { 1493 l = (size_t)vim_snprintf(NameBuff, 1494 MAXPATHL, 1495 "%.*s%s", 1496 (int)(path_tail(rel_fname) - rel_fname), 1497 rel_fname, 1498 *file_to_find); 1499 assert(l < MAXPATHL); 1500 } else { 1501 STRCPY(NameBuff, *file_to_find); 1502 run = 2; 1503 } 1504 1505 // When the file doesn't exist, try adding parts of 'suffixesadd'. 1506 size_t NameBufflen = l; 1507 char *suffix = suffixes; 1508 while (true) { 1509 if ((os_path_exists(NameBuff) 1510 && (find_what == FINDFILE_BOTH 1511 || ((find_what == FINDFILE_DIR) == os_isdir(NameBuff))))) { 1512 file_name = xmemdupz(NameBuff, NameBufflen); 1513 goto theend; 1514 } 1515 if (*suffix == NUL) { 1516 break; 1517 } 1518 assert(MAXPATHL >= l); 1519 NameBufflen = l + copy_option_part(&suffix, NameBuff + l, MAXPATHL - l, ","); 1520 } 1521 } 1522 } 1523 } else { 1524 // Loop over all paths in the 'path' or 'cdpath' option. 1525 // When "first" is set, first setup to the start of the option. 1526 // Otherwise continue to find the next match. 1527 if (first == true) { 1528 // vim_findfile_free_visited can handle a possible NULL pointer 1529 vim_findfile_free_visited(*search_ctx); 1530 dir = path_option; 1531 did_findfile_init = false; 1532 } 1533 1534 while (true) { 1535 if (did_findfile_init) { 1536 file_name = vim_findfile(*search_ctx); 1537 if (file_name != NULL) { 1538 break; 1539 } 1540 1541 did_findfile_init = false; 1542 } else { 1543 char *r_ptr; 1544 1545 if (dir == NULL || *dir == NUL) { 1546 // We searched all paths of the option, now we can free the search context. 1547 vim_findfile_cleanup(*search_ctx); 1548 *search_ctx = NULL; 1549 break; 1550 } 1551 1552 char *buf = xmalloc(MAXPATHL); 1553 1554 // copy next path 1555 buf[0] = NUL; 1556 copy_option_part(&dir, buf, MAXPATHL, " ,"); 1557 1558 // get the stopdir string 1559 r_ptr = vim_findfile_stopdir(buf); 1560 *search_ctx = vim_findfile_init(buf, *file_to_find, file_to_findlen, 1561 r_ptr, 100, false, find_what, 1562 *search_ctx, false, rel_fname); 1563 if (*search_ctx != NULL) { 1564 did_findfile_init = true; 1565 } 1566 xfree(buf); 1567 } 1568 } 1569 } 1570 if (file_name == NULL && (options & FNAME_MESS)) { 1571 if (first == true) { 1572 if (find_what == FINDFILE_DIR) { 1573 semsg(_(e_cant_find_directory_str_in_cdpath), *file_to_find); 1574 } else { 1575 semsg(_(e_cant_find_file_str_in_path), *file_to_find); 1576 } 1577 } else { 1578 if (find_what == FINDFILE_DIR) { 1579 semsg(_(e_no_more_directory_str_found_in_cdpath), *file_to_find); 1580 } else { 1581 semsg(_(e_no_more_file_str_found_in_path), *file_to_find); 1582 } 1583 } 1584 } 1585 1586 theend: 1587 return file_name; 1588 } 1589 1590 /// Get the file name at the cursor. 1591 /// If Visual mode is active, use the selected text if it's in one line. 1592 /// Returns the name in allocated memory, NULL for failure. 1593 char *grab_file_name(int count, linenr_T *file_lnum) 1594 { 1595 int options = FNAME_MESS | FNAME_EXP | FNAME_REL | FNAME_UNESC; 1596 if (VIsual_active) { 1597 size_t len; 1598 char *ptr; 1599 if (get_visual_text(NULL, &ptr, &len) == FAIL) { 1600 return NULL; 1601 } 1602 // Only recognize ":123" here 1603 if (file_lnum != NULL && ptr[len] == ':' && isdigit((uint8_t)ptr[len + 1])) { 1604 char *p = ptr + len + 1; 1605 1606 *file_lnum = getdigits_int32(&p, false, 0); 1607 } 1608 return find_file_name_in_path(ptr, len, options, count, curbuf->b_ffname); 1609 } 1610 return file_name_at_cursor(options | FNAME_HYP, count, file_lnum); 1611 } 1612 1613 /// Return the file name under or after the cursor. 1614 /// 1615 /// The 'path' option is searched if the file name is not absolute. 1616 /// The string returned has been alloc'ed and should be freed by the caller. 1617 /// NULL is returned if the file name or file is not found. 1618 /// 1619 /// options: 1620 /// FNAME_MESS give error messages 1621 /// FNAME_EXP expand to path 1622 /// FNAME_HYP check for hypertext link 1623 /// FNAME_INCL apply "includeexpr" 1624 char *file_name_at_cursor(int options, int count, linenr_T *file_lnum) 1625 { 1626 return file_name_in_line(get_cursor_line_ptr(), 1627 curwin->w_cursor.col, options, count, curbuf->b_ffname, 1628 file_lnum); 1629 } 1630 1631 /// @param rel_fname file we are searching relative to 1632 /// @param file_lnum line number after the file name 1633 /// 1634 /// @return the name of the file under or after ptr[col]. 1635 /// 1636 /// Otherwise like file_name_at_cursor(). 1637 char *file_name_in_line(char *line, int col, int options, int count, char *rel_fname, 1638 linenr_T *file_lnum) 1639 { 1640 // search forward for what could be the start of a file name 1641 char *ptr = line + col; 1642 while (*ptr != NUL && !vim_isfilec((uint8_t)(*ptr))) { 1643 MB_PTR_ADV(ptr); 1644 } 1645 if (*ptr == NUL) { // nothing found 1646 if (options & FNAME_MESS) { 1647 emsg(_("E446: No file name under cursor")); 1648 } 1649 return NULL; 1650 } 1651 1652 size_t len; 1653 bool in_type = true; 1654 bool is_url = false; 1655 1656 // Search backward for first char of the file name. 1657 // Go one char back to ":" before "//", or to the drive letter before ":\" (even if ":" 1658 // is not in 'isfname'). 1659 while (ptr > line) { 1660 if ((len = (size_t)(utf_head_off(line, ptr - 1))) > 0) { 1661 ptr -= len + 1; 1662 } else if (vim_isfilec((uint8_t)ptr[-1]) || ((options & FNAME_HYP) && path_is_url(ptr - 1))) { 1663 ptr--; 1664 } else { 1665 break; 1666 } 1667 } 1668 1669 // Search forward for the last char of the file name. 1670 // Also allow ":/" when ':' is not in 'isfname'. 1671 len = path_has_drive_letter(ptr, strlen(ptr)) ? 2 : 0; 1672 while (vim_isfilec((uint8_t)ptr[len]) || (ptr[len] == '\\' && ptr[len + 1] == ' ') 1673 || ((options & FNAME_HYP) && path_is_url(ptr + len)) 1674 || (is_url && vim_strchr(":?&=", (uint8_t)ptr[len]) != NULL)) { 1675 // After type:// we also include :, ?, & and = as valid characters, so that 1676 // http://google.com:8080?q=this&that=ok works. 1677 if ((ptr[len] >= 'A' && ptr[len] <= 'Z') || (ptr[len] >= 'a' && ptr[len] <= 'z')) { 1678 if (in_type && path_is_url(ptr + len + 1)) { 1679 is_url = true; 1680 } 1681 } else { 1682 in_type = false; 1683 } 1684 1685 if (ptr[len] == '\\' && ptr[len + 1] == ' ') { 1686 // Skip over the "\" in "\ ". 1687 len++; 1688 } 1689 len += (size_t)(utfc_ptr2len(ptr + len)); 1690 } 1691 1692 // If there is trailing punctuation, remove it. 1693 // But don't remove "..", could be a directory name. 1694 if (len > 2 && vim_strchr(".,:;!", (uint8_t)ptr[len - 1]) != NULL 1695 && ptr[len - 2] != '.') { 1696 len--; 1697 } 1698 1699 if (file_lnum != NULL) { 1700 const char *match_text = " line "; // english 1701 size_t match_textlen = 6; 1702 1703 // Get the number after the file name and a separator character. 1704 // Also accept " line 999" with and without the same translation as 1705 // used in last_set_msg(). 1706 char *p = ptr + len; 1707 if (strncmp(p, match_text, match_textlen) == 0) { 1708 p += match_textlen; 1709 } else { 1710 // no match with english, try localized 1711 match_text = _(line_msg); 1712 match_textlen = strlen(match_text); 1713 if (strncmp(p, match_text, match_textlen) == 0) { 1714 p += match_textlen; 1715 } else { 1716 p = skipwhite(p); 1717 } 1718 } 1719 if (*p != NUL) { 1720 if (!isdigit((uint8_t)(*p))) { 1721 p++; // skip the separator 1722 } 1723 p = skipwhite(p); 1724 if (isdigit((uint8_t)(*p))) { 1725 *file_lnum = (linenr_T)getdigits_long(&p, false, 0); 1726 } 1727 } 1728 } 1729 1730 return find_file_name_in_path(ptr, len, options, count, rel_fname); 1731 } 1732 1733 static char *eval_includeexpr(const char *const ptr, const size_t len) 1734 { 1735 const sctx_T save_sctx = current_sctx; 1736 set_vim_var_string(VV_FNAME, ptr, (ptrdiff_t)len); 1737 current_sctx = curbuf->b_p_script_ctx[kBufOptIncludeexpr]; 1738 1739 char *res = eval_to_string_safe(curbuf->b_p_inex, 1740 was_set_insecurely(curwin, kOptIncludeexpr, OPT_LOCAL), 1741 true); 1742 1743 set_vim_var_string(VV_FNAME, NULL, 0); 1744 current_sctx = save_sctx; 1745 return res; 1746 } 1747 1748 /// Return the name of the file ptr[len] in 'path'. 1749 /// Otherwise like file_name_at_cursor(). 1750 /// 1751 /// @param rel_fname file we are searching relative to 1752 char *find_file_name_in_path(char *ptr, size_t len, int options, long count, char *rel_fname) 1753 { 1754 char *file_name; 1755 char *tofree = NULL; 1756 1757 if (len == 0) { 1758 return NULL; 1759 } 1760 1761 if ((options & FNAME_INCL) && *curbuf->b_p_inex != NUL) { 1762 tofree = eval_includeexpr(ptr, len); 1763 if (tofree != NULL) { 1764 ptr = tofree; 1765 len = strlen(ptr); 1766 } 1767 } 1768 1769 if (options & FNAME_EXP) { 1770 char *file_to_find = NULL; 1771 char *search_ctx = NULL; 1772 1773 file_name = find_file_in_path(ptr, len, options & ~FNAME_MESS, 1774 true, rel_fname, &file_to_find, &search_ctx); 1775 1776 // If the file could not be found in a normal way, try applying 1777 // 'includeexpr' (unless done already). 1778 if (file_name == NULL 1779 && !(options & FNAME_INCL) && *curbuf->b_p_inex != NUL) { 1780 tofree = eval_includeexpr(ptr, len); 1781 if (tofree != NULL) { 1782 ptr = tofree; 1783 len = strlen(ptr); 1784 file_name = find_file_in_path(ptr, len, options & ~FNAME_MESS, 1785 true, rel_fname, &file_to_find, &search_ctx); 1786 } 1787 } 1788 if (file_name == NULL && (options & FNAME_MESS)) { 1789 char c = ptr[len]; 1790 ptr[len] = NUL; 1791 semsg(_("E447: Can't find file \"%s\" in path"), ptr); 1792 ptr[len] = c; 1793 } 1794 1795 // Repeat finding the file "count" times. This matters when it 1796 // appears several times in the path. 1797 while (file_name != NULL && --count > 0) { 1798 xfree(file_name); 1799 file_name = find_file_in_path(ptr, len, options, false, rel_fname, 1800 &file_to_find, &search_ctx); 1801 } 1802 1803 xfree(file_to_find); 1804 vim_findfile_cleanup(search_ctx); 1805 } else { 1806 file_name = xstrnsave(ptr, len); 1807 } 1808 1809 xfree(tofree); 1810 1811 return file_name; 1812 } 1813 1814 void do_autocmd_dirchanged(char *new_dir, CdScope scope, CdCause cause, bool pre) 1815 { 1816 static bool recursive = false; 1817 1818 event_T event = pre ? EVENT_DIRCHANGEDPRE : EVENT_DIRCHANGED; 1819 1820 if (recursive || !has_event(event)) { 1821 // No autocommand was defined or we changed 1822 // the directory from this autocommand. 1823 return; 1824 } 1825 1826 recursive = true; 1827 1828 save_v_event_T save_v_event; 1829 dict_T *dict = get_v_event(&save_v_event); 1830 char buf[8]; 1831 1832 switch (scope) { 1833 case kCdScopeGlobal: 1834 snprintf(buf, sizeof(buf), "global"); 1835 break; 1836 case kCdScopeTabpage: 1837 snprintf(buf, sizeof(buf), "tabpage"); 1838 break; 1839 case kCdScopeWindow: 1840 snprintf(buf, sizeof(buf), "window"); 1841 break; 1842 case kCdScopeInvalid: 1843 // Should never happen. 1844 abort(); 1845 } 1846 1847 #ifdef BACKSLASH_IN_FILENAME 1848 char new_dir_buf[MAXPATHL]; 1849 STRCPY(new_dir_buf, new_dir); 1850 slash_adjust(new_dir_buf); 1851 new_dir = new_dir_buf; 1852 #endif 1853 1854 if (pre) { 1855 tv_dict_add_str(dict, S_LEN("directory"), new_dir); 1856 } else { 1857 tv_dict_add_str(dict, S_LEN("cwd"), new_dir); 1858 } 1859 tv_dict_add_str(dict, S_LEN("scope"), buf); 1860 tv_dict_add_bool(dict, S_LEN("changed_window"), cause == kCdCauseWindow); 1861 tv_dict_set_keys_readonly(dict); 1862 1863 switch (cause) { 1864 case kCdCauseManual: 1865 case kCdCauseWindow: 1866 break; 1867 case kCdCauseAuto: 1868 snprintf(buf, sizeof(buf), "auto"); 1869 break; 1870 case kCdCauseOther: 1871 // Should never happen. 1872 abort(); 1873 } 1874 1875 apply_autocmds(event, buf, new_dir, false, curbuf); 1876 1877 restore_v_event(dict, &save_v_event); 1878 1879 recursive = false; 1880 } 1881 1882 /// Change to a file's directory. 1883 /// Caller must call shorten_fnames()! 1884 /// 1885 /// @return OK or FAIL 1886 int vim_chdirfile(char *fname, CdCause cause) 1887 { 1888 char dir[MAXPATHL]; 1889 1890 xstrlcpy(dir, fname, MAXPATHL); 1891 *path_tail_with_sep(dir) = NUL; 1892 1893 if (os_dirname(NameBuff, sizeof(NameBuff)) != OK) { 1894 NameBuff[0] = NUL; 1895 } 1896 1897 if (pathcmp(dir, NameBuff, -1) == 0) { 1898 // nothing to do 1899 return OK; 1900 } 1901 1902 if (cause != kCdCauseOther) { 1903 do_autocmd_dirchanged(dir, kCdScopeWindow, cause, true); 1904 } 1905 1906 if (os_chdir(dir) != 0) { 1907 return FAIL; 1908 } 1909 1910 if (cause != kCdCauseOther) { 1911 do_autocmd_dirchanged(dir, kCdScopeWindow, cause, false); 1912 } 1913 1914 return OK; 1915 } 1916 1917 /// Change directory to "new_dir". Search 'cdpath' for relative directory names. 1918 int vim_chdir(char *new_dir) 1919 { 1920 char *file_to_find = NULL; 1921 char *search_ctx = NULL; 1922 char *dir_name = find_directory_in_path(new_dir, strlen(new_dir), FNAME_MESS, 1923 curbuf->b_ffname, &file_to_find, &search_ctx); 1924 xfree(file_to_find); 1925 vim_findfile_cleanup(search_ctx); 1926 if (dir_name == NULL) { 1927 return -1; 1928 } 1929 1930 int r = os_chdir(dir_name); 1931 xfree(dir_name); 1932 return r; 1933 }