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.c328
1 files changed, 280 insertions, 48 deletions
diff --git a/src/nvim/file_search.c b/src/nvim/file_search.c
index a8b0dbddee..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"
@@ -349,23 +352,24 @@ void *vim_findfile_init(char *path, char *filename, char *stopdirs, int level, i
search_ctx->ffsc_stopdirs_v = xmalloc(sizeof(char *));
do {
- char *helper;
- void *ptr;
-
- helper = walker;
- ptr = xrealloc(search_ctx->ffsc_stopdirs_v,
- (dircount + 1) * sizeof(char *));
+ char *helper = walker;
+ void *ptr = xrealloc(search_ctx->ffsc_stopdirs_v,
+ (dircount + 1) * sizeof(char *));
search_ctx->ffsc_stopdirs_v = ptr;
walker = vim_strchr(walker, ';');
+ assert(!walker || walker - helper >= 0);
+ size_t len = walker ? (size_t)(walker - helper) : strlen(helper);
+ // "" means ascent till top of directory tree.
+ if (*helper != NUL && !vim_isAbsName(helper) && len + 1 < MAXPATHL) {
+ // Make the stop dir an absolute path name.
+ xmemcpyz(ff_expand_buffer, helper, len);
+ search_ctx->ffsc_stopdirs_v[dircount - 1] = FullName_save(helper, len);
+ } else {
+ search_ctx->ffsc_stopdirs_v[dircount - 1] = xmemdupz(helper, len);
+ }
if (walker) {
- assert(walker - helper >= 0);
- search_ctx->ffsc_stopdirs_v[dircount - 1] = xstrnsave(helper, (size_t)(walker - helper));
walker++;
- } else {
- // this might be "", which means ascent till top of directory tree.
- search_ctx->ffsc_stopdirs_v[dircount - 1] = xstrdup(helper);
}
-
dircount++;
} while (walker != NULL);
search_ctx->ffsc_stopdirs_v[dircount - 1] = NULL;
@@ -451,7 +455,7 @@ void *vim_findfile_init(char *path, char *filename, char *stopdirs, int level, i
STRCPY(buf, ff_expand_buffer);
STRCPY(buf + eb_len, search_ctx->ffsc_fix_path);
if (os_isdir(buf)) {
- STRCAT(ff_expand_buffer, search_ctx->ffsc_fix_path);
+ strcat(ff_expand_buffer, search_ctx->ffsc_fix_path);
add_pathsep(ff_expand_buffer);
} else {
char *p = path_tail(search_ctx->ffsc_fix_path);
@@ -479,7 +483,7 @@ void *vim_findfile_init(char *path, char *filename, char *stopdirs, int level, i
+ strlen(search_ctx->ffsc_fix_path + len)
+ 1);
STRCPY(temp, search_ctx->ffsc_fix_path + len);
- STRCAT(temp, search_ctx->ffsc_wc_path);
+ strcat(temp, search_ctx->ffsc_wc_path);
xfree(search_ctx->ffsc_wc_path);
xfree(wc_path);
search_ctx->ffsc_wc_path = temp;
@@ -505,24 +509,36 @@ error_return:
/// @return the stopdir string. Check that ';' is not escaped.
char *vim_findfile_stopdir(char *buf)
{
- char *r_ptr = buf;
-
- while (*r_ptr != NUL && *r_ptr != ';') {
- if (r_ptr[0] == '\\' && r_ptr[1] == ';') {
- // Overwrite the escape char,
- // use strlen(r_ptr) to move the trailing '\0'.
- STRMOVE(r_ptr, r_ptr + 1);
- r_ptr++;
+ for (; *buf != NUL && *buf != ';' && (buf[0] != '\\' || buf[1] != ';'); buf++) {}
+ char *dst = buf;
+ if (*buf == ';') {
+ goto is_semicolon;
+ }
+ if (*buf == NUL) {
+ goto is_nul;
+ }
+ goto start;
+ while (*buf != NUL && *buf != ';') {
+ if (buf[0] == '\\' && buf[1] == ';') {
+start:
+ // Overwrite the escape char.
+ *dst++ = ';';
+ buf += 2;
+ } else {
+ *dst++ = *buf++;
}
- r_ptr++;
}
- if (*r_ptr == ';') {
- *r_ptr = 0;
- r_ptr++;
- } else if (*r_ptr == NUL) {
- r_ptr = NULL;
+ assert(dst < buf);
+ *dst = NUL;
+ if (*buf == ';') {
+is_semicolon:
+ *buf = NUL;
+ buf++;
+ } else { // if (*buf == NUL)
+is_nul:
+ buf = NULL;
}
- return r_ptr;
+ return buf;
}
/// Clean up the given search context. Can handle a NULL pointer.
@@ -669,7 +685,7 @@ char *vim_findfile(void *search_ctx_arg)
ff_free_stack_element(stackp);
goto fail;
}
- STRCAT(file_path, stackp->ffs_fix_path);
+ strcat(file_path, stackp->ffs_fix_path);
if (!add_pathsep(file_path)) {
ff_free_stack_element(stackp);
goto fail;
@@ -769,7 +785,7 @@ char *vim_findfile(void *search_ctx_arg)
ff_free_stack_element(stackp);
goto fail;
}
- STRCAT(file_path, search_ctx->ffsc_file_to_search);
+ strcat(file_path, search_ctx->ffsc_file_to_search);
// Try without extra suffix and then with suffixes
// from 'suffixesadd'.
@@ -880,11 +896,12 @@ char *vim_findfile(void *search_ctx_arg)
if (search_ctx->ffsc_start_dir
&& search_ctx->ffsc_stopdirs_v != NULL && !got_int) {
ff_stack_T *sptr;
+ // path_end may point to the NUL or the previous path separator
+ ptrdiff_t plen = (path_end - search_ctx->ffsc_start_dir) + (*path_end != NUL);
// is the last starting directory in the stop list?
if (ff_path_in_stoplist(search_ctx->ffsc_start_dir,
- (int)(path_end - search_ctx->ffsc_start_dir),
- search_ctx->ffsc_stopdirs_v)) {
+ (size_t)plen, search_ctx->ffsc_stopdirs_v)) {
break;
}
@@ -910,7 +927,7 @@ char *vim_findfile(void *search_ctx_arg)
if (!add_pathsep(file_path)) {
goto fail;
}
- STRCAT(file_path, search_ctx->ffsc_fix_path);
+ strcat(file_path, search_ctx->ffsc_fix_path);
// create a new stack entry
sptr = ff_create_stack_element(file_path,
@@ -1219,7 +1236,7 @@ static void ff_clear(ff_search_ctx_T *search_ctx)
/// check if the given path is in the stopdirs
///
/// @return true if yes else false
-static bool ff_path_in_stoplist(char *path, int path_len, char **stopdirs_v)
+static bool ff_path_in_stoplist(char *path, size_t path_len, char **stopdirs_v)
{
// eat up trailing path separators, except the first
while (path_len > 1 && vim_ispathsep(path[path_len - 1])) {
@@ -1232,20 +1249,16 @@ static bool ff_path_in_stoplist(char *path, int path_len, char **stopdirs_v)
}
for (int i = 0; stopdirs_v[i] != NULL; i++) {
- if ((int)strlen(stopdirs_v[i]) > path_len) {
- // match for parent directory. So '/home' also matches
- // '/home/rks'. Check for PATHSEP in stopdirs_v[i], else
- // '/home/r' would also match '/home/rks'
- if (path_fnamencmp(stopdirs_v[i], path, (size_t)path_len) == 0
- && vim_ispathsep(stopdirs_v[i][path_len])) {
- return true;
- }
- } else {
- if (path_fnamecmp(stopdirs_v[i], path) == 0) {
- return true;
- }
+ // match for parent directory. So '/home' also matches
+ // '/home/rks'. Check for PATHSEP in stopdirs_v[i], else
+ // '/home/r' would also match '/home/rks'
+ if (path_fnamencmp(stopdirs_v[i], path, path_len) == 0
+ && (strlen(stopdirs_v[i]) <= path_len
+ || vim_ispathsep(stopdirs_v[i][path_len]))) {
+ return true;
}
}
+
return false;
}
@@ -1493,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;