aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/file_search.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/nvim/file_search.c')
-rw-r--r--src/nvim/file_search.c224
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;