diff options
Diffstat (limited to 'src/nvim/runtime.c')
-rw-r--r-- | src/nvim/runtime.c | 376 |
1 files changed, 318 insertions, 58 deletions
diff --git a/src/nvim/runtime.c b/src/nvim/runtime.c index c3cd210538..7b72efce23 100644 --- a/src/nvim/runtime.c +++ b/src/nvim/runtime.c @@ -5,21 +5,25 @@ /// /// Management of runtime files (including packages) -#include "nvim/vim.h" #include "nvim/ascii.h" +#include "nvim/api/private/helpers.h" #include "nvim/charset.h" #include "nvim/eval.h" -#include "nvim/option.h" #include "nvim/ex_cmds.h" #include "nvim/ex_cmds2.h" #include "nvim/misc1.h" +#include "nvim/option.h" #include "nvim/os/os.h" #include "nvim/runtime.h" +#include "nvim/vim.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "runtime.c.generated.h" #endif +static bool runtime_search_path_valid = false; +static int *runtime_search_path_ref = NULL; +static RuntimeSearchPath runtime_search_path; /// ":runtime [what] {name}" void ex_runtime(exarg_T *eap) @@ -60,12 +64,11 @@ static void source_callback(char_u *fname, void *cookie) /// When "flags" has DIP_ERR: give an error message if there is no match. /// /// return FAIL when no file could be sourced, OK otherwise. -int do_in_path(char_u *path, char_u *name, int flags, - DoInRuntimepathCB callback, void *cookie) +int do_in_path(char_u *path, char_u *name, int flags, DoInRuntimepathCB callback, void *cookie) { - char_u *tail; + char_u *tail; int num_files; - char_u **files; + char_u **files; int i; bool did_one = false; @@ -90,8 +93,7 @@ int do_in_path(char_u *path, char_u *name, int flags, // Skip after or non-after directories. if (flags & (DIP_NOAFTER | DIP_AFTER)) { - bool is_after = buflen >= 5 - && STRCMP(buf + buflen - 5, "after") == 0; + bool is_after = path_is_after(buf, buflen); if ((is_after && (flags & DIP_NOAFTER)) || (!is_after && (flags & DIP_AFTER))) { @@ -100,10 +102,8 @@ int do_in_path(char_u *path, char_u *name, int flags, } if (name == NULL) { - (*callback)(buf, (void *)&cookie); - if (!did_one) { - did_one = (cookie == NULL); - } + (*callback)(buf, cookie); + did_one = true; } else if (buflen + STRLEN(name) + 2 < MAXPATHL) { add_pathsep((char *)buf); tail = buf + STRLEN(buf); @@ -122,10 +122,11 @@ int do_in_path(char_u *path, char_u *name, int flags, verbose_leave(); } + int ew_flags = ((flags & DIP_DIR) ? EW_DIR : EW_FILE) + | (flags & DIP_DIRFILE) ? (EW_DIR|EW_FILE) : 0; + // Expand wildcards, invoke the callback for each match. - if (gen_expand_wildcards(1, &buf, &num_files, &files, - (flags & DIP_DIR) ? EW_DIR : EW_FILE) - == OK) { + if (gen_expand_wildcards(1, &buf, &num_files, &files, ew_flags) == OK) { for (i = 0; i < num_files; i++) { (*callback)(files[i], cookie); did_one = true; @@ -157,6 +158,115 @@ int do_in_path(char_u *path, char_u *name, int flags, return did_one ? OK : FAIL; } +/// Find the file "name" in all directories in "path" and invoke +/// "callback(fname, cookie)". +/// "name" can contain wildcards. +/// When "flags" has DIP_ALL: source all files, otherwise only the first one. +/// When "flags" has DIP_DIR: find directories instead of files. +/// When "flags" has DIP_ERR: give an error message if there is no match. +/// +/// return FAIL when no file could be sourced, OK otherwise. +int do_in_cached_path(char_u *name, int flags, DoInRuntimepathCB callback, void *cookie) +{ + runtime_search_path_validate(); + char_u *tail; + int num_files; + char_u **files; + int i; + bool did_one = false; + + char_u buf[MAXPATHL]; + + if (p_verbose > 10 && name != NULL) { + verbose_enter(); + smsg(_("Searching for \"%s\" in runtime path"), (char *)name); + verbose_leave(); + } + + RuntimeSearchPath path = runtime_search_path; + int ref = 0; + if (runtime_search_path_ref == NULL) { + // cached path was unreferenced. keep a ref to + // prevent runtime_search_path() to freeing it too early + ref++; + runtime_search_path_ref = &ref; + } + + // Loop over all entries in cached path + for (size_t j = 0; j < kv_size(path); j++) { + SearchPathItem item = kv_A(path, j); + size_t buflen = strlen(item.path); + + // Skip after or non-after directories. + if (flags & (DIP_NOAFTER | DIP_AFTER)) { + if ((item.after && (flags & DIP_NOAFTER)) + || (!item.after && (flags & DIP_AFTER))) { + continue; + } + } + + if (name == NULL) { + (*callback)((char_u *)item.path, cookie); + did_one = true; + } else if (buflen + STRLEN(name) + 2 < MAXPATHL) { + STRCPY(buf, item.path); + add_pathsep((char *)buf); + tail = buf + STRLEN(buf); + + // Loop over all patterns in "name" + char_u *np = name; + while (*np != NUL && ((flags & DIP_ALL) || !did_one)) { + // Append the pattern from "name" to buf[]. + assert(MAXPATHL >= (tail - buf)); + copy_option_part(&np, tail, (size_t)(MAXPATHL - (tail - buf)), + "\t "); + + if (p_verbose > 10) { + verbose_enter(); + smsg(_("Searching for \"%s\""), buf); + verbose_leave(); + } + + int ew_flags = ((flags & DIP_DIR) ? EW_DIR : EW_FILE) + | (flags & DIP_DIRFILE) ? (EW_DIR|EW_FILE) : 0; + + // Expand wildcards, invoke the callback for each match. + char_u *(pat[]) = { buf }; + if (gen_expand_wildcards(1, pat, &num_files, &files, ew_flags) == OK) { + for (i = 0; i < num_files; i++) { + (*callback)(files[i], cookie); + did_one = true; + if (!(flags & DIP_ALL)) { + break; + } + } + FreeWild(num_files, files); + } + } + } + } + + if (!did_one && name != NULL) { + if (flags & DIP_ERR) { + EMSG3(_(e_dirnotf), "runtime path", name); + } else if (p_verbose > 0) { + verbose_enter(); + smsg(_("not found in runtime path: \"%s\""), name); + verbose_leave(); + } + } + + if (ref) { + if (runtime_search_path_ref == &ref) { + runtime_search_path_ref = NULL; + } else { + runtime_search_path_free(path); + } + } + + + return did_one ? OK : FAIL; +} /// Find "name" in "path". When found, invoke the callback function for /// it: callback(fname, "cookie") /// When "flags" has DIP_ALL repeat for all matches, otherwise only the first @@ -165,32 +275,33 @@ int do_in_path(char_u *path, char_u *name, int flags, /// If "name" is NULL calls callback for each entry in "path". Cookie is /// passed by reference in this case, setting it to NULL indicates that callback /// has done its job. -int do_in_path_and_pp(char_u *path, char_u *name, int flags, - DoInRuntimepathCB callback, void *cookie) +int do_in_path_and_pp(char_u *path, char_u *name, int flags, DoInRuntimepathCB callback, + void *cookie) { int done = FAIL; if ((flags & DIP_NORTP) == 0) { - done = do_in_path(path, name, flags, callback, cookie); + done |= do_in_path(path, (name && !*name) ? NULL : name, flags, callback, cookie); } if ((done == FAIL || (flags & DIP_ALL)) && (flags & DIP_START)) { - char *start_dir = "pack/*/start/*/%s"; // NOLINT - size_t len = STRLEN(start_dir) + STRLEN(name); - char_u *s = xmallocz(len); + char *start_dir = "pack/*/start/*/%s%s"; // NOLINT + size_t len = STRLEN(start_dir) + STRLEN(name) + 6; + char_u *s = xmallocz(len); // TODO(bfredl): get rid of random allocations + char *suffix = (flags & DIP_AFTER) ? "after/" : ""; - vim_snprintf((char *)s, len, start_dir, name); - done = do_in_path(p_pp, s, flags, callback, cookie); + vim_snprintf((char *)s, len, start_dir, suffix, name); + done |= do_in_path(p_pp, s, flags & ~DIP_AFTER, callback, cookie); xfree(s); - if (done == FAIL|| (flags & DIP_ALL)) { - start_dir = "start/*/%s"; // NOLINT - len = STRLEN(start_dir) + STRLEN(name); + if (done == FAIL || (flags & DIP_ALL)) { + start_dir = "start/*/%s%s"; // NOLINT + len = STRLEN(start_dir) + STRLEN(name) + 6; s = xmallocz(len); - vim_snprintf((char *)s, len, start_dir, name); - done = do_in_path(p_pp, s, flags, callback, cookie); + vim_snprintf((char *)s, len, start_dir, suffix, name); + done |= do_in_path(p_pp, s, flags & ~DIP_AFTER, callback, cookie); xfree(s); } @@ -202,7 +313,7 @@ int do_in_path_and_pp(char_u *path, char_u *name, int flags, char_u *s = xmallocz(len); vim_snprintf((char *)s, len, opt_dir, name); - done = do_in_path(p_pp, s, flags, callback, cookie); + done |= do_in_path(p_pp, s, flags, callback, cookie); xfree(s); @@ -212,7 +323,7 @@ int do_in_path_and_pp(char_u *path, char_u *name, int flags, s = xmallocz(len); vim_snprintf((char *)s, len, opt_dir, name); - done = do_in_path(p_pp, s, flags, callback, cookie); + done |= do_in_path(p_pp, s, flags, callback, cookie); xfree(s); } @@ -221,11 +332,161 @@ int do_in_path_and_pp(char_u *path, char_u *name, int flags, return done; } +static void push_path(RuntimeSearchPath *search_path, char *entry, bool after) +{ + kv_push(*search_path, ((SearchPathItem){ entry, after })); +} + +static void expand_pack_entry(RuntimeSearchPath *search_path, CharVec *after_path, + char_u *pack_entry) +{ + static char_u buf[MAXPATHL], buf2[MAXPATHL]; + char *start_dir = "/pack/*/start/*"; // NOLINT + if (STRLEN(pack_entry) + STRLEN(start_dir) + 1 < MAXPATHL) { + xstrlcpy((char *)buf, (char *)pack_entry, MAXPATHL); + xstrlcpy((char *)buf2, (char *)pack_entry, MAXPATHL); + xstrlcat((char *)buf, start_dir, sizeof buf); + xstrlcat((char *)buf2, "/start/*", sizeof buf); // NOLINT + int num_files; + char_u **files; + + char_u *(pat[]) = { buf, buf2 }; + if (gen_expand_wildcards(2, pat, &num_files, &files, EW_DIR) == OK) { + for (int i = 0; i < num_files; i++) { + push_path(search_path, xstrdup((char *)files[i]), false); + size_t after_size = STRLEN(files[i])+7; + char *after = xmallocz(after_size); + xstrlcpy(after, (char *)files[i], after_size); + xstrlcat(after, "/after", after_size); + if (os_isdir((char_u *)after)) { + kv_push(*after_path, after); + } else { + xfree(after); + } + } + FreeWild(num_files, files); + } + } +} + +static bool path_is_after(char_u *buf, size_t buflen) +{ + // NOTE: we only consider dirs exactly matching "after" to be an AFTER dir. + // vim8 considers all dirs like "foo/bar_after", "Xafter" etc, as an + // "after" dir in SOME codepaths not not in ALL codepaths. + return buflen >= 5 + && (!(buflen >= 6) || vim_ispathsep(buf[buflen-6])) + && STRCMP(buf + buflen - 5, "after") == 0; +} + +RuntimeSearchPath runtime_search_path_build(void) +{ + kvec_t(String) pack_entries = KV_INITIAL_VALUE; + Map(String, handle_T) pack_used = MAP_INIT; + // TODO(bfredl): add a set of existing rtp entries to not duplicate those + RuntimeSearchPath search_path = KV_INITIAL_VALUE; + CharVec after_path = KV_INITIAL_VALUE; + + static char_u buf[MAXPATHL]; + for (char *entry = (char *)p_pp; *entry != NUL; ) { + char *cur_entry = entry; + copy_option_part((char_u **)&entry, buf, MAXPATHL, ","); + + String the_entry = { .data = cur_entry, .size = STRLEN(buf) }; + + kv_push(pack_entries, the_entry); + map_put(String, handle_T)(&pack_used, the_entry, 0); + } + + + char *rtp_entry; + for (rtp_entry = (char *)p_rtp; *rtp_entry != NUL; ) { + char *cur_entry = rtp_entry; + copy_option_part((char_u **)&rtp_entry, buf, MAXPATHL, ","); + size_t buflen = STRLEN(buf); + + if (path_is_after(buf, buflen)) { + rtp_entry = cur_entry; + break; + } + + push_path(&search_path, xstrdup((char *)buf), false); + + handle_T *h = map_ref(String, handle_T)(&pack_used, cstr_as_string((char *)buf), false); + if (h) { + (*h)++; + expand_pack_entry(&search_path, &after_path, buf); + } + } + + for (size_t i = 0; i < kv_size(pack_entries); i++) { + handle_T h = map_get(String, handle_T)(&pack_used, kv_A(pack_entries, i)); + if (h == 0) { + expand_pack_entry(&search_path, &after_path, (char_u *)kv_A(pack_entries, i).data); + } + } + + // "after" packages + for (size_t i = 0; i < kv_size(after_path); i++) { + push_path(&search_path, kv_A(after_path, i), true); + } + + // "after" dirs in rtp + for (; *rtp_entry != NUL;) { + copy_option_part((char_u **)&rtp_entry, buf, MAXPATHL, ","); + push_path(&search_path, xstrdup((char *)buf), path_is_after(buf, STRLEN(buf))); + } + + // strings are not owned + kv_destroy(pack_entries); + kv_destroy(after_path); + map_destroy(String, handle_T)(&pack_used); + + return search_path; +} + +void runtime_search_path_invalidate(void) +{ + runtime_search_path_valid = false; +} + +void runtime_search_path_free(RuntimeSearchPath path) +{ + for (size_t j = 0; j < kv_size(path); j++) { + SearchPathItem item = kv_A(path, j); + xfree(item.path); + } + kv_destroy(path); +} + +void runtime_search_path_validate(void) +{ + if (!runtime_search_path_valid) { + if (!runtime_search_path_ref) { + runtime_search_path_free(runtime_search_path); + } + runtime_search_path = runtime_search_path_build(); + runtime_search_path_valid = true; + runtime_search_path_ref = NULL; // initially unowned + } +} + + + /// Just like do_in_path_and_pp(), using 'runtimepath' for "path". -int do_in_runtimepath(char_u *name, int flags, DoInRuntimepathCB callback, - void *cookie) +int do_in_runtimepath(char_u *name, int flags, DoInRuntimepathCB callback, void *cookie) { - return do_in_path_and_pp(p_rtp, name, flags, callback, cookie); + int success = FAIL; + if (!(flags & DIP_NORTP)) { + success |= do_in_cached_path((name && !*name) ? NULL : name, flags, callback, cookie); + flags = (flags & ~DIP_START) | DIP_NORTP; + } + // TODO(bfredl): we could integrate disabled OPT dirs into the cached path + // which would effectivize ":packadd myoptpack" as well + if ((flags & (DIP_START|DIP_OPT)) && (success == FAIL || (flags & DIP_ALL))) { + success |= do_in_path_and_pp(p_rtp, name, flags, callback, cookie); + } + return success; } /// Source the file "name" from all directories in 'runtimepath'. @@ -235,8 +496,7 @@ int do_in_runtimepath(char_u *name, int flags, DoInRuntimepathCB callback, /// return FAIL when no file could be sourced, OK otherwise. int source_runtime(char_u *name, int flags) { - flags |= (flags & DIP_NORTP) ? 0 : DIP_START; - return source_in_path(p_rtp, name, flags); + return do_in_runtimepath(name, flags, source_callback, NULL); } /// Just like source_runtime(), but use "path" instead of 'runtimepath'. @@ -403,8 +663,10 @@ theend: return retval; } -/// Load scripts in "plugin" and "ftdetect" directories of the package. -static int load_pack_plugin(char_u *fname) +/// Load scripts in "plugin" directory of the package. +/// For opt packages, also load scripts in "ftdetect" (start packages already +/// load these from filetype.vim) +static int load_pack_plugin(bool opt, char_u *fname) { static const char *ftpat = "%s/ftdetect/*.vim"; // NOLINT @@ -421,7 +683,7 @@ static int load_pack_plugin(char_u *fname) // If runtime/filetype.vim wasn't loaded yet, the scripts will be // found when it loads. - if (eval_to_number(cmd) > 0) { + if (opt && eval_to_number(cmd) > 0) { do_cmdline_cmd("augroup filetypedetect"); vim_snprintf((char *)pat, len, ftpat, ffname); source_all_matches(pat); @@ -441,7 +703,7 @@ static int APP_ADD_DIR; static int APP_LOAD; static int APP_BOTH; -static void add_pack_plugin(char_u *fname, void *cookie) +static void add_pack_plugin(bool opt, char_u *fname, void *cookie) { if (cookie != &APP_LOAD) { char *buf = xmalloc(MAXPATHL); @@ -465,17 +727,18 @@ static void add_pack_plugin(char_u *fname, void *cookie) } if (cookie != &APP_ADD_DIR) { - load_pack_plugin(fname); + load_pack_plugin(opt, fname); } } -/// Add all packages in the "start" directory to 'runtimepath'. -void add_pack_start_dirs(void) +static void add_start_pack_plugin(char_u *fname, void *cookie) { - do_in_path(p_pp, (char_u *)"pack/*/start/*", DIP_ALL + DIP_DIR, // NOLINT - add_pack_plugin, &APP_ADD_DIR); - do_in_path(p_pp, (char_u *)"start/*", DIP_ALL + DIP_DIR, // NOLINT - add_pack_plugin, &APP_ADD_DIR); + add_pack_plugin(false, fname, cookie); +} + +static void add_opt_pack_plugin(char_u *fname, void *cookie) +{ + add_pack_plugin(true, fname, cookie); } /// Load plugins from all packages in the "start" directory. @@ -483,9 +746,9 @@ void load_start_packages(void) { did_source_packages = true; do_in_path(p_pp, (char_u *)"pack/*/start/*", DIP_ALL + DIP_DIR, // NOLINT - add_pack_plugin, &APP_LOAD); + add_start_pack_plugin, &APP_LOAD); do_in_path(p_pp, (char_u *)"start/*", DIP_ALL + DIP_DIR, // NOLINT - add_pack_plugin, &APP_LOAD); + add_start_pack_plugin, &APP_LOAD); } // ":packloadall" @@ -496,7 +759,6 @@ void ex_packloadall(exarg_T *eap) // First do a round to add all directories to 'runtimepath', then load // the plugins. This allows for plugins to use an autoload directory // of another plugin. - add_pack_start_dirs(); load_start_packages(); } } @@ -522,7 +784,8 @@ void ex_packadd(exarg_T *eap) res = do_in_path(p_pp, (char_u *)pat, DIP_ALL + DIP_DIR + (round == 2 && res == FAIL ? DIP_ERR : 0), - add_pack_plugin, eap->forceit ? &APP_ADD_DIR : &APP_BOTH); + round == 1 ? add_start_pack_plugin : add_opt_pack_plugin, + eap->forceit ? &APP_ADD_DIR : &APP_BOTH); xfree(pat); } } @@ -555,8 +818,7 @@ static char *strcpy_comma_escaped(char *dest, const char *src, const size_t len) /// (common_suf is present after each new item, single_suf is present /// after half of the new items) and with commas after each item, commas /// inside the values are escaped. -static inline size_t compute_double_env_sep_len(const char *const val, - const size_t common_suf_len, +static inline size_t compute_double_env_sep_len(const char *const val, const size_t common_suf_len, const size_t single_suf_len) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE { @@ -599,9 +861,8 @@ static inline size_t compute_double_env_sep_len(const char *const val, /// Otherwise in reverse. /// /// @return (dest + appended_characters_length) -static inline char *add_env_sep_dirs(char *dest, const char *const val, - const char *const suf1, const size_t len1, - const char *const suf2, const size_t len2, +static inline char *add_env_sep_dirs(char *dest, const char *const val, const char *const suf1, + const size_t len1, const char *const suf2, const size_t len2, const bool forward) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_RET FUNC_ATTR_NONNULL_ARG(1) { @@ -660,9 +921,8 @@ static inline char *add_env_sep_dirs(char *dest, const char *const val, /// Otherwise in reverse. /// /// @return (dest + appended_characters_length) -static inline char *add_dir(char *dest, const char *const dir, - const size_t dir_len, const XDGVarType type, - const char *const suf1, const size_t len1, +static inline char *add_dir(char *dest, const char *const dir, const size_t dir_len, + const XDGVarType type, const char *const suf1, const size_t len1, const char *const suf2, const size_t len2) FUNC_ATTR_NONNULL_RET FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_WARN_UNUSED_RESULT { |