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; diff --git a/src/nvim/file_search.h b/src/nvim/file_search.h index 2450472681..f21d2b8468 100644 --- a/src/nvim/file_search.h +++ b/src/nvim/file_search.h @@ -2,6 +2,7 @@ #include // IWYU pragma: keep +#include "nvim/pos_defs.h" // IWYU pragma: keep #include "nvim/types_defs.h" // IWYU pragma: keep #include "nvim/vim_defs.h" // IWYU pragma: keep @@ -12,6 +13,17 @@ enum { FINDFILE_BOTH = 2, ///< files and directories }; +/// Values for file_name_in_line() +enum { + FNAME_MESS = 1, ///< give error message + FNAME_EXP = 2, ///< expand to path + FNAME_HYP = 4, ///< check for hypertext link + FNAME_INCL = 8, ///< apply 'includeexpr' + FNAME_REL = 16, ///< ".." and "./" are relative to the (current) + ///< file instead of the current directory + FNAME_UNESC = 32, ///< remove backslashes used for escaping +}; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "file_search.h.generated.h" #endif diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 3931ae3ee9..9e17fe234d 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -35,6 +35,7 @@ #include "nvim/ex_docmd.h" #include "nvim/ex_eval.h" #include "nvim/ex_getln.h" +#include "nvim/file_search.h" #include "nvim/fileio.h" #include "nvim/fold.h" #include "nvim/getchar.h" diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 05b9db474e..4b3f69a378 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -31,6 +31,7 @@ #include "nvim/ex_cmds_defs.h" #include "nvim/ex_getln.h" #include "nvim/extmark.h" +#include "nvim/file_search.h" #include "nvim/fold.h" #include "nvim/garray.h" #include "nvim/garray_defs.h" diff --git a/src/nvim/path.c b/src/nvim/path.c index 67acc6e51b..c9bfd5228c 100644 --- a/src/nvim/path.c +++ b/src/nvim/path.c @@ -8,23 +8,17 @@ #include "auto/config.h" #include "nvim/ascii_defs.h" -#include "nvim/buffer_defs.h" #include "nvim/charset.h" #include "nvim/cmdexpand.h" #include "nvim/eval.h" -#include "nvim/eval/typval_defs.h" #include "nvim/ex_docmd.h" -#include "nvim/file_search.h" #include "nvim/fileio.h" #include "nvim/garray.h" -#include "nvim/gettext_defs.h" #include "nvim/globals.h" #include "nvim/macros_defs.h" #include "nvim/mbyte.h" #include "nvim/memory.h" -#include "nvim/message.h" #include "nvim/option.h" -#include "nvim/option_defs.h" #include "nvim/option_vars.h" #include "nvim/os/fs.h" #include "nvim/os/fs_defs.h" @@ -37,7 +31,6 @@ #include "nvim/regexp_defs.h" #include "nvim/strings.h" #include "nvim/vim_defs.h" -#include "nvim/window.h" enum { URL_SLASH = 1, // path_is_url() has found ":/" @@ -1687,87 +1680,6 @@ void simplify_filename(char *filename) } while (*p != NUL); } -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; -} - /// Checks for a Windows drive letter ("C:/") at the start of the path. /// /// @see https://url.spec.whatwg.org/#start-with-a-windows-drive-letter diff --git a/src/nvim/search.c b/src/nvim/search.c index d1cb336905..9e00664d86 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -26,6 +26,7 @@ #include "nvim/ex_cmds_defs.h" #include "nvim/ex_docmd.h" #include "nvim/ex_getln.h" +#include "nvim/file_search.h" #include "nvim/fileio.h" #include "nvim/fold.h" #include "nvim/garray.h" diff --git a/src/nvim/window.c b/src/nvim/window.c index 4fde200a01..d959c5377f 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -6837,142 +6837,6 @@ static void frame_add_height(frame_T *frp, int n) } } -// 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); -} - /// Add or remove a status line from window(s), according to the /// value of 'laststatus'. /// diff --git a/src/nvim/window.h b/src/nvim/window.h index d20b799e20..9618ff1c2a 100644 --- a/src/nvim/window.h +++ b/src/nvim/window.h @@ -8,17 +8,6 @@ #include "nvim/option_defs.h" // IWYU pragma: keep #include "nvim/types_defs.h" // IWYU pragma: keep -/// Values for file_name_in_line() -enum { - FNAME_MESS = 1, ///< give error message - FNAME_EXP = 2, ///< expand to path - FNAME_HYP = 4, ///< check for hypertext link - FNAME_INCL = 8, ///< apply 'includeexpr' - FNAME_REL = 16, ///< ".." and "./" are relative to the (current) - ///< file instead of the current directory - FNAME_UNESC = 32, ///< remove backslashes used for escaping -}; - /// arguments for win_split() enum { WSP_ROOM = 0x01, ///< require enough room