diff options
Diffstat (limited to 'src/nvim/file_search.c')
-rw-r--r-- | src/nvim/file_search.c | 224 |
1 files changed, 223 insertions, 1 deletions
diff --git a/src/nvim/file_search.c b/src/nvim/file_search.c index 5760fc864a..cdfd281718 100644 --- a/src/nvim/file_search.c +++ b/src/nvim/file_search.c @@ -52,7 +52,9 @@ #include "nvim/ascii_defs.h" #include "nvim/autocmd.h" #include "nvim/autocmd_defs.h" -#include "nvim/buffer_defs.h" +#include "nvim/charset.h" +#include "nvim/cursor.h" +#include "nvim/errors.h" #include "nvim/eval.h" #include "nvim/eval/typval.h" #include "nvim/eval/typval_defs.h" @@ -63,6 +65,7 @@ #include "nvim/mbyte.h" #include "nvim/memory.h" #include "nvim/message.h" +#include "nvim/normal.h" #include "nvim/option.h" #include "nvim/option_vars.h" #include "nvim/os/fs.h" @@ -1503,6 +1506,225 @@ theend: return file_name; } +/// Get the file name at the cursor. +/// If Visual mode is active, use the selected text if it's in one line. +/// Returns the name in allocated memory, NULL for failure. +char *grab_file_name(int count, linenr_T *file_lnum) +{ + int options = FNAME_MESS | FNAME_EXP | FNAME_REL | FNAME_UNESC; + if (VIsual_active) { + size_t len; + char *ptr; + if (get_visual_text(NULL, &ptr, &len) == FAIL) { + return NULL; + } + // Only recognize ":123" here + if (file_lnum != NULL && ptr[len] == ':' && isdigit((uint8_t)ptr[len + 1])) { + char *p = ptr + len + 1; + + *file_lnum = getdigits_int32(&p, false, 0); + } + return find_file_name_in_path(ptr, len, options, count, curbuf->b_ffname); + } + return file_name_at_cursor(options | FNAME_HYP, count, file_lnum); +} + +/// Return the file name under or after the cursor. +/// +/// The 'path' option is searched if the file name is not absolute. +/// The string returned has been alloc'ed and should be freed by the caller. +/// NULL is returned if the file name or file is not found. +/// +/// options: +/// FNAME_MESS give error messages +/// FNAME_EXP expand to path +/// FNAME_HYP check for hypertext link +/// FNAME_INCL apply "includeexpr" +char *file_name_at_cursor(int options, int count, linenr_T *file_lnum) +{ + return file_name_in_line(get_cursor_line_ptr(), + curwin->w_cursor.col, options, count, curbuf->b_ffname, + file_lnum); +} + +/// @param rel_fname file we are searching relative to +/// @param file_lnum line number after the file name +/// +/// @return the name of the file under or after ptr[col]. +/// +/// Otherwise like file_name_at_cursor(). +char *file_name_in_line(char *line, int col, int options, int count, char *rel_fname, + linenr_T *file_lnum) +{ + // search forward for what could be the start of a file name + char *ptr = line + col; + while (*ptr != NUL && !vim_isfilec((uint8_t)(*ptr))) { + MB_PTR_ADV(ptr); + } + if (*ptr == NUL) { // nothing found + if (options & FNAME_MESS) { + emsg(_("E446: No file name under cursor")); + } + return NULL; + } + + size_t len; + bool in_type = true; + bool is_url = false; + + // Search backward for first char of the file name. + // Go one char back to ":" before "//", or to the drive letter before ":\" (even if ":" + // is not in 'isfname'). + while (ptr > line) { + if ((len = (size_t)(utf_head_off(line, ptr - 1))) > 0) { + ptr -= len + 1; + } else if (vim_isfilec((uint8_t)ptr[-1]) || ((options & FNAME_HYP) && path_is_url(ptr - 1))) { + ptr--; + } else { + break; + } + } + + // Search forward for the last char of the file name. + // Also allow ":/" when ':' is not in 'isfname'. + len = path_has_drive_letter(ptr) ? 2 : 0; + while (vim_isfilec((uint8_t)ptr[len]) || (ptr[len] == '\\' && ptr[len + 1] == ' ') + || ((options & FNAME_HYP) && path_is_url(ptr + len)) + || (is_url && vim_strchr(":?&=", (uint8_t)ptr[len]) != NULL)) { + // After type:// we also include :, ?, & and = as valid characters, so that + // http://google.com:8080?q=this&that=ok works. + if ((ptr[len] >= 'A' && ptr[len] <= 'Z') || (ptr[len] >= 'a' && ptr[len] <= 'z')) { + if (in_type && path_is_url(ptr + len + 1)) { + is_url = true; + } + } else { + in_type = false; + } + + if (ptr[len] == '\\' && ptr[len + 1] == ' ') { + // Skip over the "\" in "\ ". + len++; + } + len += (size_t)(utfc_ptr2len(ptr + len)); + } + + // If there is trailing punctuation, remove it. + // But don't remove "..", could be a directory name. + if (len > 2 && vim_strchr(".,:;!", (uint8_t)ptr[len - 1]) != NULL + && ptr[len - 2] != '.') { + len--; + } + + if (file_lnum != NULL) { + const char *line_english = " line "; + const char *line_transl = _(line_msg); + + // Get the number after the file name and a separator character. + // Also accept " line 999" with and without the same translation as + // used in last_set_msg(). + char *p = ptr + len; + if (strncmp(p, line_english, strlen(line_english)) == 0) { + p += strlen(line_english); + } else if (strncmp(p, line_transl, strlen(line_transl)) == 0) { + p += strlen(line_transl); + } else { + p = skipwhite(p); + } + if (*p != NUL) { + if (!isdigit((uint8_t)(*p))) { + p++; // skip the separator + } + p = skipwhite(p); + if (isdigit((uint8_t)(*p))) { + *file_lnum = (linenr_T)getdigits_long(&p, false, 0); + } + } + } + + return find_file_name_in_path(ptr, len, options, count, rel_fname); +} + +static char *eval_includeexpr(const char *const ptr, const size_t len) +{ + const sctx_T save_sctx = current_sctx; + set_vim_var_string(VV_FNAME, ptr, (ptrdiff_t)len); + current_sctx = curbuf->b_p_script_ctx[BV_INEX].script_ctx; + + char *res = eval_to_string_safe(curbuf->b_p_inex, + was_set_insecurely(curwin, kOptIncludeexpr, OPT_LOCAL), + true); + + set_vim_var_string(VV_FNAME, NULL, 0); + current_sctx = save_sctx; + return res; +} + +/// Return the name of the file ptr[len] in 'path'. +/// Otherwise like file_name_at_cursor(). +/// +/// @param rel_fname file we are searching relative to +char *find_file_name_in_path(char *ptr, size_t len, int options, long count, char *rel_fname) +{ + char *file_name; + char *tofree = NULL; + + if (len == 0) { + return NULL; + } + + if ((options & FNAME_INCL) && *curbuf->b_p_inex != NUL) { + tofree = eval_includeexpr(ptr, len); + if (tofree != NULL) { + ptr = tofree; + len = strlen(ptr); + } + } + + if (options & FNAME_EXP) { + char *file_to_find = NULL; + char *search_ctx = NULL; + + file_name = find_file_in_path(ptr, len, options & ~FNAME_MESS, + true, rel_fname, &file_to_find, &search_ctx); + + // If the file could not be found in a normal way, try applying + // 'includeexpr' (unless done already). + if (file_name == NULL + && !(options & FNAME_INCL) && *curbuf->b_p_inex != NUL) { + tofree = eval_includeexpr(ptr, len); + if (tofree != NULL) { + ptr = tofree; + len = strlen(ptr); + file_name = find_file_in_path(ptr, len, options & ~FNAME_MESS, + true, rel_fname, &file_to_find, &search_ctx); + } + } + if (file_name == NULL && (options & FNAME_MESS)) { + char c = ptr[len]; + ptr[len] = NUL; + semsg(_("E447: Can't find file \"%s\" in path"), ptr); + ptr[len] = c; + } + + // Repeat finding the file "count" times. This matters when it + // appears several times in the path. + while (file_name != NULL && --count > 0) { + xfree(file_name); + file_name = find_file_in_path(ptr, len, options, false, rel_fname, + &file_to_find, &search_ctx); + } + + xfree(file_to_find); + vim_findfile_cleanup(search_ctx); + } else { + file_name = xstrnsave(ptr, len); + } + + xfree(tofree); + + return file_name; +} + void do_autocmd_dirchanged(char *new_dir, CdScope scope, CdCause cause, bool pre) { static bool recursive = false; |