1
0
forked from aniani/vim
vim/src/tag.c
Keith Thompson 184f71cc68
patch 9.1.0006: is*() and to*() function may be unsafe
Problem:  is*() and to*() function may be unsafe
Solution: Add SAFE_* macros and start using those instead
          (Keith Thompson)

Use SAFE_() macros for is*() and to*() functions

The standard is*() and to*() functions declared in <ctype.h> have
undefined behavior for negative arguments other than EOF.  If plain char
is signed, passing an unchecked value from argv for from user input
to one of these functions has undefined behavior.

Solution: Add SAFE_*() macros that cast the argument to unsigned char.

Most implementations behave sanely for negative arguments, and most
character values in practice are non-negative, but it's still best
to avoid undefined behavior.

The change from #13347 has been omitted, as this has already been
separately fixed in commit ac709e2fc0db6d31abb7da96f743c40956b60c3a
(v9.0.2054)

fixes: #13332
closes: #13347

Signed-off-by: Keith Thompson <Keith.S.Thompson@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
2024-01-04 21:19:04 +01:00

4694 lines
114 KiB
C

/* vi:set ts=8 sts=4 sw=4 noet:
*
* VIM - Vi IMproved by Bram Moolenaar
*
* Do ":help uganda" in Vim to read copying and usage conditions.
* Do ":help credits" in Vim to see a list of people who contributed.
* See README.txt for an overview of the Vim source code.
*/
/*
* Code to handle tags and the tag stack
*/
#include "vim.h"
/*
* Structure to hold pointers to various items in a tag line.
*/
typedef struct tag_pointers
{
// filled in by parse_tag_line():
char_u *tagname; // start of tag name (skip "file:")
char_u *tagname_end; // char after tag name
char_u *fname; // first char of file name
char_u *fname_end; // char after file name
char_u *command; // first char of command
// filled in by parse_match():
char_u *command_end; // first char after command
char_u *tag_fname; // file name of the tags file. This is used
// when 'tr' is set.
#ifdef FEAT_EMACS_TAGS
int is_etag; // TRUE for emacs tag
#endif
char_u *tagkind; // "kind:" value
char_u *tagkind_end; // end of tagkind
char_u *user_data; // user_data string
char_u *user_data_end; // end of user_data
linenr_T tagline; // "line:" value
} tagptrs_T;
/*
* Return values used when reading lines from a tags file.
*/
typedef enum
{
TAGS_READ_SUCCESS = 1,
TAGS_READ_EOF,
TAGS_READ_IGNORE,
} tags_read_status_T;
/*
* States used during a tags search
*/
typedef enum
{
TS_START, // at start of file
TS_LINEAR, // linear searching forward, till EOF
TS_BINARY, // binary searching
TS_SKIP_BACK, // skipping backwards
TS_STEP_FORWARD // stepping forwards
} tagsearch_state_T; // Current search state
/*
* Binary search file offsets in a tags file
*/
typedef struct
{
off_T low_offset; // offset for first char of first line that
// could match
off_T high_offset; // offset of char after last line that could
// match
off_T curr_offset; // Current file offset in search range
off_T curr_offset_used; // curr_offset used when skipping back
off_T match_offset; // Where the binary search found a tag
int low_char; // first char at low_offset
int high_char; // first char at high_offset
} tagsearch_info_T;
/*
* Return values used when matching tags against a pattern.
*/
typedef enum
{
TAG_MATCH_SUCCESS = 1,
TAG_MATCH_FAIL,
TAG_MATCH_STOP,
TAG_MATCH_NEXT
} tagmatch_status_T;
/*
* Arguments used for matching tags read from a tags file against a pattern.
*/
typedef struct
{
int matchoff; // tag match offset
int match_re; // TRUE if the tag matches a regexp
int match_no_ic; // TRUE if the tag matches with case
int has_re; // regular expression used
int sortic; // tags file sorted ignoring case (foldcase)
int sort_error; // tags file not sorted
} findtags_match_args_T;
/*
* The matching tags are first stored in one of the hash tables. In
* which one depends on the priority of the match.
* ht_match[] is used to find duplicates, ga_match[] to keep them in sequence.
* At the end, all the matches from ga_match[] are concatenated, to make a list
* sorted on priority.
*/
#define MT_ST_CUR 0 // static match in current file
#define MT_GL_CUR 1 // global match in current file
#define MT_GL_OTH 2 // global match in other file
#define MT_ST_OTH 3 // static match in other file
#define MT_IC_OFF 4 // add for icase match
#define MT_RE_OFF 8 // add for regexp match
#define MT_MASK 7 // mask for printing priority
#define MT_COUNT 16
static char *mt_names[MT_COUNT/2] =
{"FSC", "F C", "F ", "FS ", " SC", " C", " ", " S "};
#define NOTAGFILE 99 // return value for jumpto_tag
static char_u *nofile_fname = NULL; // fname for NOTAGFILE error
static void taglen_advance(int l);
static int jumpto_tag(char_u *lbuf, int forceit, int keep_help);
#ifdef FEAT_EMACS_TAGS
static int parse_tag_line(char_u *lbuf, int is_etag, tagptrs_T *tagp);
#else
static int parse_tag_line(char_u *lbuf, tagptrs_T *tagp);
#endif
static int test_for_static(tagptrs_T *);
static int parse_match(char_u *lbuf, tagptrs_T *tagp);
static char_u *tag_full_fname(tagptrs_T *tagp);
static char_u *expand_tag_fname(char_u *fname, char_u *tag_fname, int expand);
#ifdef FEAT_EMACS_TAGS
static int test_for_current(int, char_u *, char_u *, char_u *, char_u *);
#else
static int test_for_current(char_u *, char_u *, char_u *, char_u *);
#endif
static int find_extra(char_u **pp);
static void print_tag_list(int new_tag, int use_tagstack, int num_matches, char_u **matches);
#if defined(FEAT_QUICKFIX) && defined(FEAT_EVAL)
static int add_llist_tags(char_u *tag, int num_matches, char_u **matches);
#endif
static void tagstack_clear_entry(taggy_T *item);
static char_u *tagmatchname = NULL; // name of last used tag
#if defined(FEAT_QUICKFIX)
/*
* Tag for preview window is remembered separately, to avoid messing up the
* normal tagstack.
*/
static taggy_T ptag_entry = {NULL, {{0, 0, 0}, 0}, 0, 0, NULL};
#endif
#ifdef FEAT_EVAL
static int tfu_in_use = FALSE; // disallow recursive call of tagfunc
static callback_T tfu_cb; // 'tagfunc' callback function
#endif
// Used instead of NUL to separate tag fields in the growarrays.
#define TAG_SEP 0x02
#if defined(FEAT_EVAL) || defined(PROTO)
/*
* Reads the 'tagfunc' option value and convert that to a callback value.
* Invoked when the 'tagfunc' option is set. The option value can be a name of
* a function (string), or function(<name>) or funcref(<name>) or a lambda.
*/
char *
did_set_tagfunc(optset_T *args UNUSED)
{
#ifdef FEAT_EVAL
free_callback(&tfu_cb);
free_callback(&curbuf->b_tfu_cb);
if (*curbuf->b_p_tfu == NUL)
return NULL;
if (option_set_callback_func(curbuf->b_p_tfu, &tfu_cb) == FAIL)
return e_invalid_argument;
copy_callback(&curbuf->b_tfu_cb, &tfu_cb);
#endif
return NULL;
}
#endif
# if defined(EXITFREE) || defined(PROTO)
void
free_tagfunc_option(void)
{
# ifdef FEAT_EVAL
free_callback(&tfu_cb);
# endif
}
# endif
#if defined(FEAT_EVAL) || defined(PROTO)
/*
* Mark the global 'tagfunc' callback with "copyID" so that it is not garbage
* collected.
*/
int
set_ref_in_tagfunc(int copyID UNUSED)
{
int abort = FALSE;
abort = set_ref_in_callback(&tfu_cb, copyID);
return abort;
}
/*
* Copy the global 'tagfunc' callback function to the buffer-local 'tagfunc'
* callback for 'buf'.
*/
void
set_buflocal_tfu_callback(buf_T *buf UNUSED)
{
free_callback(&buf->b_tfu_cb);
if (tfu_cb.cb_name != NULL && *tfu_cb.cb_name != NUL)
copy_callback(&buf->b_tfu_cb, &tfu_cb);
}
#endif
/*
* Jump to tag; handling of tag commands and tag stack
*
* *tag != NUL: ":tag {tag}", jump to new tag, add to tag stack
*
* type == DT_TAG: ":tag [tag]", jump to newer position or same tag again
* type == DT_HELP: like DT_TAG, but don't use regexp.
* type == DT_POP: ":pop" or CTRL-T, jump to old position
* type == DT_NEXT: jump to next match of same tag
* type == DT_PREV: jump to previous match of same tag
* type == DT_FIRST: jump to first match of same tag
* type == DT_LAST: jump to last match of same tag
* type == DT_SELECT: ":tselect [tag]", select tag from a list of all matches
* type == DT_JUMP: ":tjump [tag]", jump to tag or select tag from a list
* type == DT_CSCOPE: use cscope to find the tag
* type == DT_LTAG: use location list for displaying tag matches
* type == DT_FREE: free cached matches
*
* for cscope, returns TRUE if we jumped to tag or aborted, FALSE otherwise
*/
int
do_tag(
char_u *tag, // tag (pattern) to jump to
int type,
int count,
int forceit, // :ta with !
int verbose) // print "tag not found" message
{
taggy_T *tagstack = curwin->w_tagstack;
int tagstackidx = curwin->w_tagstackidx;
int tagstacklen = curwin->w_tagstacklen;
int cur_match = 0;
int cur_fnum = curbuf->b_fnum;
int oldtagstackidx = tagstackidx;
int prevtagstackidx = tagstackidx;
int prev_num_matches;
int new_tag = FALSE;
int i;
int ic;
int no_regexp = FALSE;
int error_cur_match = 0;
int save_pos = FALSE;
fmark_T saved_fmark;
#ifdef FEAT_CSCOPE
int jumped_to_tag = FALSE;
#endif
int new_num_matches;
char_u **new_matches;
int use_tagstack;
int skip_msg = FALSE;
char_u *buf_ffname = curbuf->b_ffname; // name to use for
// priority computation
int use_tfu = 1;
char_u *tofree = NULL;
// remember the matches for the last used tag
static int num_matches = 0;
static int max_num_matches = 0; // limit used for match search
static char_u **matches = NULL;
static int flags;
#ifdef FEAT_EVAL
if (tfu_in_use)
{
emsg(_(e_cannot_modify_tag_stack_within_tagfunc));
return FALSE;
}
#endif
#ifdef EXITFREE
if (type == DT_FREE)
{
// remove the list of matches
FreeWild(num_matches, matches);
# ifdef FEAT_CSCOPE
cs_free_tags();
# endif
num_matches = 0;
return FALSE;
}
#endif
if (type == DT_HELP)
{
type = DT_TAG;
no_regexp = TRUE;
use_tfu = 0;
}
prev_num_matches = num_matches;
free_string_option(nofile_fname);
nofile_fname = NULL;
CLEAR_POS(&saved_fmark.mark); // shutup gcc 4.0
saved_fmark.fnum = 0;
/*
* Don't add a tag to the tagstack if 'tagstack' has been reset.
*/
if ((!p_tgst && *tag != NUL))
{
use_tagstack = FALSE;
new_tag = TRUE;
#if defined(FEAT_QUICKFIX)
if (g_do_tagpreview != 0)
{
tagstack_clear_entry(&ptag_entry);
if ((ptag_entry.tagname = vim_strsave(tag)) == NULL)
goto end_do_tag;
}
#endif
}
else
{
#if defined(FEAT_QUICKFIX)
if (g_do_tagpreview != 0)
use_tagstack = FALSE;
else
#endif
use_tagstack = TRUE;
// new pattern, add to the tag stack
if (*tag != NUL
&& (type == DT_TAG || type == DT_SELECT || type == DT_JUMP
#ifdef FEAT_QUICKFIX
|| type == DT_LTAG
#endif
#ifdef FEAT_CSCOPE
|| type == DT_CSCOPE
#endif
))
{
#if defined(FEAT_QUICKFIX)
if (g_do_tagpreview != 0)
{
if (ptag_entry.tagname != NULL
&& STRCMP(ptag_entry.tagname, tag) == 0)
{
// Jumping to same tag: keep the current match, so that
// the CursorHold autocommand example works.
cur_match = ptag_entry.cur_match;
cur_fnum = ptag_entry.cur_fnum;
}
else
{
tagstack_clear_entry(&ptag_entry);
if ((ptag_entry.tagname = vim_strsave(tag)) == NULL)
goto end_do_tag;
}
}
else
#endif
{
/*
* If the last used entry is not at the top, delete all tag
* stack entries above it.
*/
while (tagstackidx < tagstacklen)
tagstack_clear_entry(&tagstack[--tagstacklen]);
// if the tagstack is full: remove oldest entry
if (++tagstacklen > TAGSTACKSIZE)
{
tagstacklen = TAGSTACKSIZE;
tagstack_clear_entry(&tagstack[0]);
for (i = 1; i < tagstacklen; ++i)
tagstack[i - 1] = tagstack[i];
--tagstackidx;
}
/*
* put the tag name in the tag stack
*/
if ((tagstack[tagstackidx].tagname = vim_strsave(tag)) == NULL)
{
curwin->w_tagstacklen = tagstacklen - 1;
goto end_do_tag;
}
curwin->w_tagstacklen = tagstacklen;
save_pos = TRUE; // save the cursor position below
}
new_tag = TRUE;
}
else
{
if (
#if defined(FEAT_QUICKFIX)
g_do_tagpreview != 0 ? ptag_entry.tagname == NULL :
#endif
tagstacklen == 0)
{
// empty stack
emsg(_(e_tag_stack_empty));
goto end_do_tag;
}
if (type == DT_POP) // go to older position
{
#ifdef FEAT_FOLDING
int old_KeyTyped = KeyTyped;
#endif
if ((tagstackidx -= count) < 0)
{
emsg(_(e_at_bottom_of_tag_stack));
if (tagstackidx + count == 0)
{
// We did [num]^T from the bottom of the stack
tagstackidx = 0;
goto end_do_tag;
}
// We weren't at the bottom of the stack, so jump all the
// way to the bottom now.
tagstackidx = 0;
}
else if (tagstackidx >= tagstacklen) // count == 0?
{
emsg(_(e_at_top_of_tag_stack));
goto end_do_tag;
}
// Make a copy of the fmark, autocommands may invalidate the
// tagstack before it's used.
saved_fmark = tagstack[tagstackidx].fmark;
if (saved_fmark.fnum != curbuf->b_fnum)
{
/*
* Jump to other file. If this fails (e.g. because the
* file was changed) keep original position in tag stack.
*/
if (buflist_getfile(saved_fmark.fnum, saved_fmark.mark.lnum,
GETF_SETMARK, forceit) == FAIL)
{
tagstackidx = oldtagstackidx; // back to old posn
goto end_do_tag;
}
// An BufReadPost autocommand may jump to the '" mark, but
// we don't what that here.
curwin->w_cursor.lnum = saved_fmark.mark.lnum;
}
else
{
setpcmark();
curwin->w_cursor.lnum = saved_fmark.mark.lnum;
}
curwin->w_cursor.col = saved_fmark.mark.col;
curwin->w_set_curswant = TRUE;
check_cursor();
#ifdef FEAT_FOLDING
if ((fdo_flags & FDO_TAG) && old_KeyTyped)
foldOpenCursor();
#endif
// remove the old list of matches
FreeWild(num_matches, matches);
#ifdef FEAT_CSCOPE
cs_free_tags();
#endif
num_matches = 0;
tag_freematch();
goto end_do_tag;
}
if (type == DT_TAG
#if defined(FEAT_QUICKFIX)
|| type == DT_LTAG
#endif
)
{
#if defined(FEAT_QUICKFIX)
if (g_do_tagpreview != 0)
{
cur_match = ptag_entry.cur_match;
cur_fnum = ptag_entry.cur_fnum;
}
else
#endif
{
// ":tag" (no argument): go to newer pattern
save_pos = TRUE; // save the cursor position below
if ((tagstackidx += count - 1) >= tagstacklen)
{
/*
* Beyond the last one, just give an error message and
* go to the last one. Don't store the cursor
* position.
*/
tagstackidx = tagstacklen - 1;
emsg(_(e_at_top_of_tag_stack));
save_pos = FALSE;
}
else if (tagstackidx < 0) // must have been count == 0
{
emsg(_(e_at_bottom_of_tag_stack));
tagstackidx = 0;
goto end_do_tag;
}
cur_match = tagstack[tagstackidx].cur_match;
cur_fnum = tagstack[tagstackidx].cur_fnum;
}
new_tag = TRUE;
}
else // go to other matching tag
{
// Save index for when selection is cancelled.
prevtagstackidx = tagstackidx;
#if defined(FEAT_QUICKFIX)
if (g_do_tagpreview != 0)
{
cur_match = ptag_entry.cur_match;
cur_fnum = ptag_entry.cur_fnum;
}
else
#endif
{
if (--tagstackidx < 0)
tagstackidx = 0;
cur_match = tagstack[tagstackidx].cur_match;
cur_fnum = tagstack[tagstackidx].cur_fnum;
}
switch (type)
{
case DT_FIRST: cur_match = count - 1; break;
case DT_SELECT:
case DT_JUMP:
#ifdef FEAT_CSCOPE
case DT_CSCOPE:
#endif
case DT_LAST: cur_match = MAXCOL - 1; break;
case DT_NEXT: cur_match += count; break;
case DT_PREV: cur_match -= count; break;
}
if (cur_match >= MAXCOL)
cur_match = MAXCOL - 1;
else if (cur_match < 0)
{
emsg(_(e_cannot_go_before_first_matching_tag));
skip_msg = TRUE;
cur_match = 0;
cur_fnum = curbuf->b_fnum;
}
}
}
#if defined(FEAT_QUICKFIX)
if (g_do_tagpreview != 0)
{
if (type != DT_SELECT && type != DT_JUMP)
{
ptag_entry.cur_match = cur_match;
ptag_entry.cur_fnum = cur_fnum;
}
}
else
#endif
{
/*
* For ":tag [arg]" or ":tselect" remember position before the jump.
*/
saved_fmark = tagstack[tagstackidx].fmark;
if (save_pos)
{
tagstack[tagstackidx].fmark.mark = curwin->w_cursor;
tagstack[tagstackidx].fmark.fnum = curbuf->b_fnum;
}
// Curwin will change in the call to jumpto_tag() if ":stag" was
// used or an autocommand jumps to another window; store value of
// tagstackidx now.
curwin->w_tagstackidx = tagstackidx;
if (type != DT_SELECT && type != DT_JUMP)
{
curwin->w_tagstack[tagstackidx].cur_match = cur_match;
curwin->w_tagstack[tagstackidx].cur_fnum = cur_fnum;
}
}
}
// When not using the current buffer get the name of buffer "cur_fnum".
// Makes sure that the tag order doesn't change when using a remembered
// position for "cur_match".
if (cur_fnum != curbuf->b_fnum)
{
buf_T *buf = buflist_findnr(cur_fnum);
if (buf != NULL)
buf_ffname = buf->b_ffname;
}
/*
* Repeat searching for tags, when a file has not been found.
*/
for (;;)
{
int other_name;
char_u *name;
/*
* When desired match not found yet, try to find it (and others).
*/
if (use_tagstack)
{
// make a copy, the tagstack may change in 'tagfunc'
name = vim_strsave(tagstack[tagstackidx].tagname);
vim_free(tofree);
tofree = name;
}
#if defined(FEAT_QUICKFIX)
else if (g_do_tagpreview != 0)
name = ptag_entry.tagname;
#endif
else
name = tag;
other_name = (tagmatchname == NULL || STRCMP(tagmatchname, name) != 0);
if (new_tag
|| (cur_match >= num_matches && max_num_matches != MAXCOL)
|| other_name)
{
if (other_name)
{
vim_free(tagmatchname);
tagmatchname = vim_strsave(name);
}
if (type == DT_SELECT || type == DT_JUMP
#if defined(FEAT_QUICKFIX)
|| type == DT_LTAG
#endif
)
cur_match = MAXCOL - 1;
if (type == DT_TAG)
max_num_matches = MAXCOL;
else
max_num_matches = cur_match + 1;
// when the argument starts with '/', use it as a regexp
if (!no_regexp && *name == '/')
{
flags = TAG_REGEXP;
++name;
}
else
flags = TAG_NOIC;
#ifdef FEAT_CSCOPE
if (type == DT_CSCOPE)
flags = TAG_CSCOPE;
#endif
if (verbose)
flags |= TAG_VERBOSE;
if (!use_tfu)
flags |= TAG_NO_TAGFUNC;
if (find_tags(name, &new_num_matches, &new_matches, flags,
max_num_matches, buf_ffname) == OK
&& new_num_matches < max_num_matches)
max_num_matches = MAXCOL; // If less than max_num_matches
// found: all matches found.
// A tag function may do anything, which may cause various
// information to become invalid. At least check for the tagstack
// to still be the same.
if (tagstack != curwin->w_tagstack)
{
emsg(_(e_window_unexpectedly_close_while_searching_for_tags));
FreeWild(new_num_matches, new_matches);
break;
}
// If there already were some matches for the same name, move them
// to the start. Avoids that the order changes when using
// ":tnext" and jumping to another file.
if (!new_tag && !other_name)
{
int j, k;
int idx = 0;
tagptrs_T tagp, tagp2;
// Find the position of each old match in the new list. Need
// to use parse_match() to find the tag line.
for (j = 0; j < num_matches; ++j)
{
parse_match(matches[j], &tagp);
for (i = idx; i < new_num_matches; ++i)
{
parse_match(new_matches[i], &tagp2);
if (STRCMP(tagp.tagname, tagp2.tagname) == 0)
{
char_u *p = new_matches[i];
for (k = i; k > idx; --k)
new_matches[k] = new_matches[k - 1];
new_matches[idx++] = p;
break;
}
}
}
}
FreeWild(num_matches, matches);
num_matches = new_num_matches;
matches = new_matches;
}
if (num_matches <= 0)
{
if (verbose)
semsg(_(e_tag_not_found_str), name);
#if defined(FEAT_QUICKFIX)
g_do_tagpreview = 0;
#endif
}
else
{
int ask_for_selection = FALSE;
#ifdef FEAT_CSCOPE
if (type == DT_CSCOPE && num_matches > 1)
{
cs_print_tags();
ask_for_selection = TRUE;
}
else
#endif
if (type == DT_TAG && *tag != NUL)
// If a count is supplied to the ":tag <name>" command, then
// jump to count'th matching tag.
cur_match = count > 0 ? count - 1 : 0;
else if (type == DT_SELECT || (type == DT_JUMP && num_matches > 1))
{
print_tag_list(new_tag, use_tagstack, num_matches, matches);
ask_for_selection = TRUE;
}
#if defined(FEAT_QUICKFIX) && defined(FEAT_EVAL)
else if (type == DT_LTAG)
{
if (add_llist_tags(tag, num_matches, matches) == FAIL)
goto end_do_tag;
cur_match = 0; // Jump to the first tag
}
#endif
if (ask_for_selection == TRUE)
{
/*
* Ask to select a tag from the list.
*/
i = prompt_for_number(NULL);
if (i <= 0 || i > num_matches || got_int)
{
// no valid choice: don't change anything
if (use_tagstack)
{
tagstack[tagstackidx].fmark = saved_fmark;
tagstackidx = prevtagstackidx;
}
#ifdef FEAT_CSCOPE
cs_free_tags();
jumped_to_tag = TRUE;
#endif
break;
}
cur_match = i - 1;
}
if (cur_match >= num_matches)
{
// Avoid giving this error when a file wasn't found and we're
// looking for a match in another file, which wasn't found.
// There will be an emsg("file doesn't exist") below then.
if ((type == DT_NEXT || type == DT_FIRST)
&& nofile_fname == NULL)
{
if (num_matches == 1)
emsg(_(e_there_is_only_one_matching_tag));
else
emsg(_(e_cannot_go_beyond_last_matching_tag));
skip_msg = TRUE;
}
cur_match = num_matches - 1;
}
if (use_tagstack)
{
tagptrs_T tagp;
tagstack[tagstackidx].cur_match = cur_match;
tagstack[tagstackidx].cur_fnum = cur_fnum;
// store user-provided data originating from tagfunc
if (use_tfu && parse_match(matches[cur_match], &tagp) == OK
&& tagp.user_data)
{
VIM_CLEAR(tagstack[tagstackidx].user_data);
tagstack[tagstackidx].user_data = vim_strnsave(
tagp.user_data, tagp.user_data_end - tagp.user_data);
}
++tagstackidx;
}
#if defined(FEAT_QUICKFIX)
else if (g_do_tagpreview != 0)
{
ptag_entry.cur_match = cur_match;
ptag_entry.cur_fnum = cur_fnum;
}
#endif
/*
* Only when going to try the next match, report that the previous
* file didn't exist. Otherwise an emsg() is given below.
*/
if (nofile_fname != NULL && error_cur_match != cur_match)
smsg(_("File \"%s\" does not exist"), nofile_fname);
ic = (matches[cur_match][0] & MT_IC_OFF);
if (type != DT_TAG && type != DT_SELECT && type != DT_JUMP
#ifdef FEAT_CSCOPE
&& type != DT_CSCOPE
#endif
&& (num_matches > 1 || ic)
&& !skip_msg)
{
// Give an indication of the number of matching tags
sprintf((char *)IObuff, _("tag %d of %d%s"),
cur_match + 1,
num_matches,
max_num_matches != MAXCOL ? _(" or more") : "");
if (ic)
STRCAT(IObuff, _(" Using tag with different case!"));
if ((num_matches > prev_num_matches || new_tag)
&& num_matches > 1)
{
if (ic)
msg_attr((char *)IObuff, HL_ATTR(HLF_W));
else
msg((char *)IObuff);
msg_scroll = TRUE; // don't overwrite this message
}
else
give_warning(IObuff, ic);
if (ic && !msg_scrolled && msg_silent == 0)
{
out_flush();
ui_delay(1007L, TRUE);
}
}
#if defined(FEAT_EVAL)
// Let the SwapExists event know what tag we are jumping to.
vim_snprintf((char *)IObuff, IOSIZE, ":ta %s\r", name);
set_vim_var_string(VV_SWAPCOMMAND, IObuff, -1);
#endif
/*
* Jump to the desired match.
*/
i = jumpto_tag(matches[cur_match], forceit, type != DT_CSCOPE);
#if defined(FEAT_EVAL)
set_vim_var_string(VV_SWAPCOMMAND, NULL, -1);
#endif
if (i == NOTAGFILE)
{
// File not found: try again with another matching tag
if ((type == DT_PREV && cur_match > 0)
|| ((type == DT_TAG || type == DT_NEXT
|| type == DT_FIRST)
&& (max_num_matches != MAXCOL
|| cur_match < num_matches - 1)))
{
error_cur_match = cur_match;
if (use_tagstack)
--tagstackidx;
if (type == DT_PREV)
--cur_match;
else
{
type = DT_NEXT;
++cur_match;
}
continue;
}
semsg(_(e_file_str_does_not_exist), nofile_fname);
}
else
{
// We may have jumped to another window, check that
// tagstackidx is still valid.
if (use_tagstack && tagstackidx > curwin->w_tagstacklen)
tagstackidx = curwin->w_tagstackidx;
#ifdef FEAT_CSCOPE
jumped_to_tag = TRUE;
#endif
}
}
break;
}
end_do_tag:
// Only store the new index when using the tagstack and it's valid.
if (use_tagstack && tagstackidx <= curwin->w_tagstacklen)
curwin->w_tagstackidx = tagstackidx;
postponed_split = 0; // don't split next time
# ifdef FEAT_QUICKFIX
g_do_tagpreview = 0; // don't do tag preview next time
# endif
vim_free(tofree);
#ifdef FEAT_CSCOPE
return jumped_to_tag;
#else
return FALSE;
#endif
}
/*
* List all the matching tags.
*/
static void
print_tag_list(
int new_tag,
int use_tagstack,
int num_matches,
char_u **matches)
{
taggy_T *tagstack = curwin->w_tagstack;
int tagstackidx = curwin->w_tagstackidx;
int i;
char_u *p;
char_u *command_end;
tagptrs_T tagp;
int taglen;
int attr;
/*
* Assume that the first match indicates how long the tags can
* be, and align the file names to that.
*/
parse_match(matches[0], &tagp);
taglen = (int)(tagp.tagname_end - tagp.tagname + 2);
if (taglen < 18)
taglen = 18;
if (taglen > Columns - 25)
taglen = MAXCOL;
if (msg_col == 0)
msg_didout = FALSE; // overwrite previous message
msg_start();
msg_puts_attr(_(" # pri kind tag"), HL_ATTR(HLF_T));
msg_clr_eos();
taglen_advance(taglen);
msg_puts_attr(_("file\n"), HL_ATTR(HLF_T));
for (i = 0; i < num_matches && !got_int; ++i)
{
parse_match(matches[i], &tagp);
if (!new_tag && (
#if defined(FEAT_QUICKFIX)
(g_do_tagpreview != 0
&& i == ptag_entry.cur_match) ||
#endif
(use_tagstack
&& i == tagstack[tagstackidx].cur_match)))
*IObuff = '>';
else
*IObuff = ' ';
vim_snprintf((char *)IObuff + 1, IOSIZE - 1,
"%2d %s ", i + 1,
mt_names[matches[i][0] & MT_MASK]);
msg_puts((char *)IObuff);
if (tagp.tagkind != NULL)
msg_outtrans_len(tagp.tagkind,
(int)(tagp.tagkind_end - tagp.tagkind));
msg_advance(13);
msg_outtrans_len_attr(tagp.tagname,
(int)(tagp.tagname_end - tagp.tagname),
HL_ATTR(HLF_T));
msg_putchar(' ');
taglen_advance(taglen);
// Find out the actual file name. If it is long, truncate
// it and put "..." in the middle
p = tag_full_fname(&tagp);
if (p != NULL)
{
msg_outtrans_long_attr(p, HL_ATTR(HLF_D));
vim_free(p);
}
if (msg_col > 0)
msg_putchar('\n');
if (got_int)
break;
msg_advance(15);
// print any extra fields
command_end = tagp.command_end;
if (command_end != NULL)
{
p = command_end + 3;
while (*p && *p != '\r' && *p != '\n')
{
while (*p == TAB)
++p;
// skip "file:" without a value (static tag)
if (STRNCMP(p, "file:", 5) == 0
&& vim_isspace(p[5]))
{
p += 5;
continue;
}
// skip "kind:<kind>" and "<kind>"
if (p == tagp.tagkind
|| (p + 5 == tagp.tagkind
&& STRNCMP(p, "kind:", 5) == 0))
{
p = tagp.tagkind_end;
continue;
}
// print all other extra fields
attr = HL_ATTR(HLF_CM);
while (*p && *p != '\r' && *p != '\n')
{
if (msg_col + ptr2cells(p) >= Columns)
{
msg_putchar('\n');
if (got_int)
break;
msg_advance(15);
}
p = msg_outtrans_one(p, attr);
if (*p == TAB)
{
msg_puts_attr(" ", attr);
break;
}
if (*p == ':')
attr = 0;
}
}
if (msg_col > 15)
{
msg_putchar('\n');
if (got_int)
break;
msg_advance(15);
}
}
else
{
for (p = tagp.command;
*p && *p != '\r' && *p != '\n'; ++p)
;
command_end = p;
}
// Put the info (in several lines) at column 15.
// Don't display "/^" and "?^".
p = tagp.command;
if (*p == '/' || *p == '?')
{
++p;
if (*p == '^')
++p;
}
// Remove leading whitespace from pattern
while (p != command_end && vim_isspace(*p))
++p;
while (p != command_end)
{
if (msg_col + (*p == TAB ? 1 : ptr2cells(p)) > Columns)
msg_putchar('\n');
if (got_int)
break;
msg_advance(15);
// skip backslash used for escaping a command char or
// a backslash
if (*p == '\\' && (*(p + 1) == *tagp.command
|| *(p + 1) == '\\'))
++p;
if (*p == TAB)
{
msg_putchar(' ');
++p;
}
else
p = msg_outtrans_one(p, 0);
// don't display the "$/;\"" and "$?;\""
if (p == command_end - 2 && *p == '$'
&& *(p + 1) == *tagp.command)
break;
// don't display matching '/' or '?'
if (p == command_end - 1 && *p == *tagp.command
&& (*p == '/' || *p == '?'))
break;
}
if (msg_col)
msg_putchar('\n');
ui_breakcheck();
}
if (got_int)
got_int = FALSE; // only stop the listing
}
#if defined(FEAT_QUICKFIX) && defined(FEAT_EVAL)
/*
* Add the matching tags to the location list for the current
* window.
*/
static int
add_llist_tags(
char_u *tag,
int num_matches,
char_u **matches)
{
list_T *list;
char_u tag_name[128 + 1];
char_u *fname;
char_u *cmd;
int i;
char_u *p;
tagptrs_T tagp;
fname = alloc(MAXPATHL + 1);
cmd = alloc(CMDBUFFSIZE + 1);
list = list_alloc();
if (list == NULL || fname == NULL || cmd == NULL)
{
vim_free(cmd);
vim_free(fname);
if (list != NULL)
list_free(list);
return FAIL;
}
for (i = 0; i < num_matches; ++i)
{
int len, cmd_len;
long lnum;
dict_T *dict;
parse_match(matches[i], &tagp);
// Save the tag name
len = (int)(tagp.tagname_end - tagp.tagname);
if (len > 128)
len = 128;
vim_strncpy(tag_name, tagp.tagname, len);
tag_name[len] = NUL;
// Save the tag file name
p = tag_full_fname(&tagp);
if (p == NULL)
continue;
vim_strncpy(fname, p, MAXPATHL);
vim_free(p);
// Get the line number or the search pattern used to locate
// the tag.
lnum = 0;
if (SAFE_isdigit(*tagp.command))
// Line number is used to locate the tag
lnum = atol((char *)tagp.command);
else
{
char_u *cmd_start, *cmd_end;
// Search pattern is used to locate the tag
// Locate the end of the command
cmd_start = tagp.command;
cmd_end = tagp.command_end;
if (cmd_end == NULL)
{
for (p = tagp.command;
*p && *p != '\r' && *p != '\n'; ++p)
;
cmd_end = p;
}
// Now, cmd_end points to the character after the
// command. Adjust it to point to the last
// character of the command.
cmd_end--;
// Skip the '/' and '?' characters at the
// beginning and end of the search pattern.
if (*cmd_start == '/' || *cmd_start == '?')
cmd_start++;
if (*cmd_end == '/' || *cmd_end == '?')
cmd_end--;
len = 0;
cmd[0] = NUL;
// If "^" is present in the tag search pattern, then
// copy it first.
if (*cmd_start == '^')
{
STRCPY(cmd, "^");
cmd_start++;
len++;
}
// Precede the tag pattern with \V to make it very
// nomagic.
STRCAT(cmd, "\\V");
len += 2;
cmd_len = (int)(cmd_end - cmd_start + 1);
if (cmd_len > (CMDBUFFSIZE - 5))
cmd_len = CMDBUFFSIZE - 5;
STRNCAT(cmd, cmd_start, cmd_len);
len += cmd_len;
if (cmd[len - 1] == '$')
{
// Replace '$' at the end of the search pattern
// with '\$'
cmd[len - 1] = '\\';
cmd[len] = '$';
len++;
}
cmd[len] = NUL;
}
if ((dict = dict_alloc()) == NULL)
continue;
if (list_append_dict(list, dict) == FAIL)
{
vim_free(dict);
continue;
}
dict_add_string(dict, "text", tag_name);
dict_add_string(dict, "filename", fname);
dict_add_number(dict, "lnum", lnum);
if (lnum == 0)
dict_add_string(dict, "pattern", cmd);
}
vim_snprintf((char *)IObuff, IOSIZE, "ltag %s", tag);
set_errorlist(curwin, list, ' ', IObuff, NULL);
list_free(list);
vim_free(fname);
vim_free(cmd);
return OK;
}
#endif
/*
* Free cached tags.
*/
void
tag_freematch(void)
{
VIM_CLEAR(tagmatchname);
}
static void
taglen_advance(int l)
{
if (l == MAXCOL)
{
msg_putchar('\n');
msg_advance(24);
}
else
msg_advance(13 + l);
}
/*
* Print the tag stack
*/
void
do_tags(exarg_T *eap UNUSED)
{
int i;
char_u *name;
taggy_T *tagstack = curwin->w_tagstack;
int tagstackidx = curwin->w_tagstackidx;
int tagstacklen = curwin->w_tagstacklen;
// Highlight title
msg_puts_title(_("\n # TO tag FROM line in file/text"));
for (i = 0; i < tagstacklen; ++i)
{
if (tagstack[i].tagname != NULL)
{
name = fm_getname(&(tagstack[i].fmark), 30);
if (name == NULL) // file name not available
continue;
msg_putchar('\n');
vim_snprintf((char *)IObuff, IOSIZE, "%c%2d %2d %-15s %5ld ",
i == tagstackidx ? '>' : ' ',
i + 1,
tagstack[i].cur_match + 1,
tagstack[i].tagname,
tagstack[i].fmark.mark.lnum);
msg_outtrans(IObuff);
msg_outtrans_attr(name, tagstack[i].fmark.fnum == curbuf->b_fnum
? HL_ATTR(HLF_D) : 0);
vim_free(name);
}
out_flush(); // show one line at a time
}
if (tagstackidx == tagstacklen) // idx at top of stack
msg_puts("\n>");
}
/*
* Compare two strings, for length "len", ignoring case the ASCII way.
* return 0 for match, < 0 for smaller, > 0 for bigger
* Make sure case is folded to uppercase in comparison (like for 'sort -f')
*/
static int
tag_strnicmp(char_u *s1, char_u *s2, size_t len)
{
int i;
while (len > 0)
{
i = (int)TOUPPER_ASC(*s1) - (int)TOUPPER_ASC(*s2);
if (i != 0)
return i; // this character different
if (*s1 == NUL)
break; // strings match until NUL
++s1;
++s2;
--len;
}
return 0; // strings match
}
/*
* Structure to hold info about the tag pattern being used.
*/
typedef struct
{
char_u *pat; // the pattern
int len; // length of pat[]
char_u *head; // start of pattern head
int headlen; // length of head[]
regmatch_T regmatch; // regexp program, may be NULL
} pat_T;
/*
* Extract info from the tag search pattern "pats->pat".
*/
static void
prepare_pats(pat_T *pats, int has_re)
{
pats->head = pats->pat;
pats->headlen = pats->len;
if (has_re)
{
// When the pattern starts with '^' or "\\<", binary searching can be
// used (much faster).
if (pats->pat[0] == '^')
pats->head = pats->pat + 1;
else if (pats->pat[0] == '\\' && pats->pat[1] == '<')
pats->head = pats->pat + 2;
if (pats->head == pats->pat)
pats->headlen = 0;
else
for (pats->headlen = 0; pats->head[pats->headlen] != NUL;
++pats->headlen)
if (vim_strchr((char_u *)(magic_isset() ? ".[~*\\$" : "\\$"),
pats->head[pats->headlen]) != NULL)
break;
if (p_tl != 0 && pats->headlen > p_tl) // adjust for 'taglength'
pats->headlen = p_tl;
}
if (has_re)
pats->regmatch.regprog = vim_regcomp(pats->pat,
magic_isset() ? RE_MAGIC : 0);
else
pats->regmatch.regprog = NULL;
}
#ifdef FEAT_EVAL
/*
* Call the user-defined function to generate a list of tags used by
* find_tags().
*
* Return OK if at least 1 tag has been successfully found,
* NOTDONE if the function returns v:null, and FAIL otherwise.
*/
static int
find_tagfunc_tags(
char_u *pat, // pattern supplied to the user-defined function
garray_T *ga, // the tags will be placed here
int *match_count, // here the number of tags found will be placed
int flags, // flags from find_tags (TAG_*)
char_u *buf_ffname) // name of buffer for priority
{
pos_T save_pos;
list_T *taglist;
listitem_T *item;
int ntags = 0;
int result = FAIL;
typval_T args[4];
typval_T rettv;
char_u flagString[4];
dict_T *d;
taggy_T *tag = &curwin->w_tagstack[curwin->w_tagstackidx];
if (*curbuf->b_p_tfu == NUL || curbuf->b_tfu_cb.cb_name == NULL
|| *curbuf->b_tfu_cb.cb_name == NUL)
return FAIL;
args[0].v_type = VAR_STRING;
args[0].vval.v_string = pat;
args[1].v_type = VAR_STRING;
args[1].vval.v_string = flagString;
// create 'info' dict argument
if ((d = dict_alloc_lock(VAR_FIXED)) == NULL)
return FAIL;
if (tag->user_data != NULL)
dict_add_string(d, "user_data", tag->user_data);
if (buf_ffname != NULL)
dict_add_string(d, "buf_ffname", buf_ffname);
++d->dv_refcount;
args[2].v_type = VAR_DICT;
args[2].vval.v_dict = d;
args[3].v_type = VAR_UNKNOWN;
vim_snprintf((char *)flagString, sizeof(flagString),
"%s%s%s",
g_tag_at_cursor ? "c": "",
flags & TAG_INS_COMP ? "i": "",
flags & TAG_REGEXP ? "r": "");
save_pos = curwin->w_cursor;
result = call_callback(&curbuf->b_tfu_cb, 0, &rettv, 3, args);
curwin->w_cursor = save_pos; // restore the cursor position
--d->dv_refcount;
if (result == FAIL)
return FAIL;
if (rettv.v_type == VAR_SPECIAL && rettv.vval.v_number == VVAL_NULL)
{
clear_tv(&rettv);
return NOTDONE;
}
if (rettv.v_type != VAR_LIST || !rettv.vval.v_list)
{
clear_tv(&rettv);
emsg(_(e_invalid_return_value_from_tagfunc));
return FAIL;
}
taglist = rettv.vval.v_list;
FOR_ALL_LIST_ITEMS(taglist, item)
{
char_u *mfp;
char_u *res_name, *res_fname, *res_cmd, *res_kind;
int len;
dict_iterator_T iter;
char_u *dict_key;
typval_T *tv;
int has_extra = 0;
int name_only = flags & TAG_NAMES;
if (item->li_tv.v_type != VAR_DICT)
{
emsg(_(e_invalid_return_value_from_tagfunc));
break;
}
#ifdef FEAT_EMACS_TAGS
len = 3;
#else
len = 2;
#endif
res_name = NULL;
res_fname = NULL;
res_cmd = NULL;
res_kind = NULL;
dict_iterate_start(&item->li_tv, &iter);
while (NULL != (dict_key = dict_iterate_next(&iter, &tv)))
{
if (tv->v_type != VAR_STRING || tv->vval.v_string == NULL)
continue;
len += (int)STRLEN(tv->vval.v_string) + 1; // Space for "\tVALUE"
if (!STRCMP(dict_key, "name"))
{
res_name = tv->vval.v_string;
continue;
}
if (!STRCMP(dict_key, "filename"))
{
res_fname = tv->vval.v_string;
continue;
}
if (!STRCMP(dict_key, "cmd"))
{
res_cmd = tv->vval.v_string;
continue;
}
has_extra = 1;
if (!STRCMP(dict_key, "kind"))
{
res_kind = tv->vval.v_string;
continue;
}
// Other elements will be stored as "\tKEY:VALUE"
// Allocate space for the key and the colon
len += (int)STRLEN(dict_key) + 1;
}
if (has_extra)
len += 2; // need space for ;"
if (!res_name || !res_fname || !res_cmd)
{
emsg(_(e_invalid_return_value_from_tagfunc));
break;
}
if (name_only)
mfp = vim_strsave(res_name);
else
mfp = alloc(sizeof(char_u) + len + 1);
if (mfp == NULL)
continue;
if (!name_only)
{
char_u *p = mfp;
*p++ = MT_GL_OTH + 1; // mtt
*p++ = TAG_SEP; // no tag file name
#ifdef FEAT_EMACS_TAGS
*p++ = TAG_SEP;
#endif
STRCPY(p, res_name);
p += STRLEN(p);
*p++ = TAB;
STRCPY(p, res_fname);
p += STRLEN(p);
*p++ = TAB;
STRCPY(p, res_cmd);
p += STRLEN(p);
if (has_extra)
{
STRCPY(p, ";\"");
p += STRLEN(p);
if (res_kind)
{
*p++ = TAB;
STRCPY(p, res_kind);
p += STRLEN(p);
}
dict_iterate_start(&item->li_tv, &iter);
while (NULL != (dict_key = dict_iterate_next(&iter, &tv)))
{
if (tv->v_type != VAR_STRING || tv->vval.v_string == NULL)
continue;
if (!STRCMP(dict_key, "name"))
continue;
if (!STRCMP(dict_key, "filename"))
continue;
if (!STRCMP(dict_key, "cmd"))
continue;
if (!STRCMP(dict_key, "kind"))
continue;
*p++ = TAB;
STRCPY(p, dict_key);
p += STRLEN(p);
STRCPY(p, ":");
p += STRLEN(p);
STRCPY(p, tv->vval.v_string);
p += STRLEN(p);
}
}
}
// Add all matches because tagfunc should do filtering.
if (ga_grow(ga, 1) == OK)
{
((char_u **)(ga->ga_data))[ga->ga_len++] = mfp;
++ntags;
result = OK;
}
else
{
vim_free(mfp);
break;
}
}
clear_tv(&rettv);
*match_count = ntags;
return result;
}
#endif
/*
* State information used during a tag search
*/
typedef struct
{
tagsearch_state_T state; // tag search state
int stop_searching; // stop when match found or error
pat_T *orgpat; // holds unconverted pattern info
char_u *lbuf; // line buffer
int lbuf_size; // length of lbuf
char_u *tag_fname; // name of the tag file
FILE *fp; // current tags file pointer
int flags; // flags used for tag search
int tag_file_sorted; // !_TAG_FILE_SORTED value
int get_searchpat; // used for 'showfulltag'
int help_only; // only search for help tags
int did_open; // did open a tag file
int mincount; // MAXCOL: find all matches
// other: minimal number of matches
int linear; // do a linear search
vimconv_T vimconv;
#ifdef FEAT_EMACS_TAGS
int is_etag; // current file is emacs style
char_u *ebuf; // additional buffer for etag fname
#endif
#ifdef FEAT_MULTI_LANG
char_u help_lang[3]; // lang of current tags file
int help_pri; // help language priority
char_u *help_lang_find; // lang to be found
int is_txt; // flag of file extension
#endif
int match_count; // number of matches found
garray_T ga_match[MT_COUNT]; // stores matches in sequence
hashtab_T ht_match[MT_COUNT]; // stores matches by key
} findtags_state_T;
/*
* Initialize the state used by find_tags().
* Returns OK on success and FAIL on memory allocation failure.
*/
static int
findtags_state_init(
findtags_state_T *st,
char_u *pat,
int flags,
int mincount)
{
int mtt;
st->tag_fname = alloc(MAXPATHL + 1);
st->fp = NULL;
st->orgpat = ALLOC_ONE(pat_T);
st->orgpat->pat = pat;
st->orgpat->len = (int)STRLEN(pat);
st->orgpat->regmatch.regprog = NULL;
st->flags = flags;
st->tag_file_sorted = NUL;
st->help_only = (flags & TAG_HELP);
st->get_searchpat = FALSE;
#ifdef FEAT_MULTI_LANG
st->help_lang[0] = NUL;
st->help_pri = 0;
st->help_lang_find = NULL;
st->is_txt = FALSE;
#endif
st->did_open = FALSE;
st->mincount = mincount;
st->lbuf_size = LSIZE;
st->lbuf = alloc(st->lbuf_size);
#ifdef FEAT_EMACS_TAGS
st->ebuf = alloc(LSIZE);
#endif
st->match_count = 0;
st->stop_searching = FALSE;
for (mtt = 0; mtt < MT_COUNT; ++mtt)
{
ga_init2(&st->ga_match[mtt], sizeof(char_u *), 100);
hash_init(&st->ht_match[mtt]);
}
// check for out of memory situation
if (st->tag_fname == NULL
|| st->lbuf == NULL
#ifdef FEAT_EMACS_TAGS
|| st->ebuf == NULL
#endif
)
return FAIL;
return OK;
}
/*
* Free the state used by find_tags()
*/
static void
findtags_state_free(findtags_state_T *st)
{
vim_free(st->tag_fname);
vim_free(st->lbuf);
vim_regfree(st->orgpat->regmatch.regprog);
vim_free(st->orgpat);
#ifdef FEAT_EMACS_TAGS
vim_free(st->ebuf);
#endif
}
#ifdef FEAT_MULTI_LANG
/*
* Initialize the language and priority used for searching tags in a Vim help
* file.
* Returns TRUE to process the help file for tags and FALSE to skip the file.
*/
static int
findtags_in_help_init(findtags_state_T *st)
{
int i;
char_u *s;
// Keep "en" as the language if the file extension is ".txt"
if (st->is_txt)
STRCPY(st->help_lang, "en");
else
{
// Prefer help tags according to 'helplang'. Put the two-letter
// language name in help_lang[].
i = (int)STRLEN(st->tag_fname);
if (i > 3 && st->tag_fname[i - 3] == '-')
vim_strncpy(st->help_lang, st->tag_fname + i - 2, 2);
else
STRCPY(st->help_lang, "en");
}
// When searching for a specific language skip tags files for other
// languages.
if (st->help_lang_find != NULL
&& STRICMP(st->help_lang, st->help_lang_find) != 0)
return FALSE;
// For CTRL-] in a help file prefer a match with the same language.
if ((st->flags & TAG_KEEP_LANG)
&& st->help_lang_find == NULL
&& curbuf->b_fname != NULL
&& (i = (int)STRLEN(curbuf->b_fname)) > 4
&& curbuf->b_fname[i - 1] == 'x'
&& curbuf->b_fname[i - 4] == '.'
&& STRNICMP(curbuf->b_fname + i - 3, st->help_lang, 2) == 0)
st->help_pri = 0;
else
{
// search for the language in 'helplang'
st->help_pri = 1;
for (s = p_hlg; *s != NUL; ++s)
{
if (STRNICMP(s, st->help_lang, 2) == 0)
break;
++st->help_pri;
if ((s = vim_strchr(s, ',')) == NULL)
break;
}
if (s == NULL || *s == NUL)
{
// Language not in 'helplang': use last, prefer English, unless
// found already.
++st->help_pri;
if (STRICMP(st->help_lang, "en") != 0)
++st->help_pri;
}
}
return TRUE;
}
#endif
#ifdef FEAT_EVAL
/*
* Use the function set in 'tagfunc' (if configured and enabled) to get the
* tags.
* Return OK if at least 1 tag has been successfully found, NOTDONE if the
* 'tagfunc' is not used or the 'tagfunc' returns v:null and FAIL otherwise.
*/
static int
findtags_apply_tfu(findtags_state_T *st, char_u *pat, char_u *buf_ffname)
{
int use_tfu = ((st->flags & TAG_NO_TAGFUNC) == 0);
int retval;
if (!use_tfu || tfu_in_use || *curbuf->b_p_tfu == NUL)
return NOTDONE;
tfu_in_use = TRUE;
retval = find_tagfunc_tags(pat, st->ga_match, &st->match_count,
st->flags, buf_ffname);
tfu_in_use = FALSE;
return retval;
}
#endif
#ifdef FEAT_EMACS_TAGS
/*
* Stack for included emacs-tags file.
* It has a fixed size, to truncate cyclic includes. jw
*/
# define INCSTACK_SIZE 42
static struct
{
FILE *fp;
char_u *etag_fname;
} incstack[INCSTACK_SIZE];
static int incstack_idx = 0; // index in incstack
/*
* Free the emacs include tags file stack.
*/
static void
emacs_tags_incstack_free(void)
{
while (incstack_idx)
{
--incstack_idx;
fclose(incstack[incstack_idx].fp);
incstack[incstack_idx].fp = NULL;
VIM_CLEAR(incstack[incstack_idx].etag_fname);
}
}
/*
* Emacs tags line with CTRL-L: New file name on next line.
* The file name is followed by a ','. Remember etag file name in ebuf.
* The FILE pointer to the tags file is stored in "st->fp". If another tags
* file is included, then the FILE pointer to the new tags file is stored in
* "st->fp". The old file pointer is saved in incstack.
*/
static void
emacs_tags_new_filename(findtags_state_T *st)
{
char_u *p;
char_u *fullpath_ebuf;
if (vim_fgets(st->ebuf, LSIZE, st->fp))
return;
for (p = st->ebuf; *p && *p != ','; p++)
;
*p = NUL;
// check for an included tags file.
// atoi(p+1) is the number of bytes before the next ^L unless it is an
// include statement. Skip the included tags file if it exceeds the
// maximum.
if (STRNCMP(p + 1, "include", 7) != 0 || incstack_idx >= INCSTACK_SIZE)
return;
// Save current "fp" and "tag_fname" in the stack.
incstack[incstack_idx].etag_fname = vim_strsave(st->tag_fname);
if (incstack[incstack_idx].etag_fname == NULL)
return;
incstack[incstack_idx].fp = st->fp;
st->fp = NULL;
// Figure out "tag_fname" and "fp" to use for
// included file.
fullpath_ebuf = expand_tag_fname(st->ebuf, st->tag_fname, FALSE);
if (fullpath_ebuf != NULL)
{
st->fp = mch_fopen((char *)fullpath_ebuf, "r");
if (st->fp != NULL)
{
if (STRLEN(fullpath_ebuf) > LSIZE)
semsg(_(e_tag_file_path_truncated_for_str), st->ebuf);
vim_strncpy(st->tag_fname, fullpath_ebuf, MAXPATHL);
++incstack_idx;
st->is_etag = FALSE; // we can include anything
}
vim_free(fullpath_ebuf);
}
if (st->fp == NULL)
{
// Can't open the included file, skip it and
// restore old value of "fp".
st->fp = incstack[incstack_idx].fp;
vim_free(incstack[incstack_idx].etag_fname);
}
}
/*
* Reached the end of an emacs-style tags file. If this is an included tags
* file, then pop it from the incstack and continue processing the parent tags
* file. Otherwise, processed all the tags.
* Returns TRUE if an included tags file is popped and processing should
* continue with the parent tags file. Returns FALSE to stop processing tags.
*/
static int
emacs_tags_file_eof(findtags_state_T *st)
{
if (!incstack_idx) // reached end of file. stop processing.
return FALSE;
// reached the end of an included tags file. pop it.
--incstack_idx;
fclose(st->fp); // end of this file ...
st->fp = incstack[incstack_idx].fp;
STRCPY(st->tag_fname, incstack[incstack_idx].etag_fname);
vim_free(incstack[incstack_idx].etag_fname);
st->is_etag = TRUE; // (only etags can include)
return TRUE;
}
/*
* Parse a line from an emacs-style tags file.
* Returns OK if the line is parsed successfully, returns FAIL if the line is
* not terminated by a newline.
*/
static int
emacs_tags_parse_line(char_u *lbuf, tagptrs_T *tagp)
{
char_u *p_7f;
char_u *p;
// There are two formats for an emacs tag line:
// 1: struct EnvBase ^?EnvBase^A139,4627
// 2: #define ARPB_WILD_WORLD ^?153,5194
p_7f = vim_strchr(lbuf, 0x7f);
if (p_7f == NULL)
{
etag_fail:
if (vim_strchr(lbuf, '\n') != NULL)
return FAIL;
// Truncated line. Ignore it.
if (p_verbose >= 5)
{
verbose_enter();
msg(_("Ignoring long line in tags file"));
verbose_leave();
}
tagp->command = lbuf;
tagp->tagname = lbuf;
tagp->tagname_end = lbuf;
return OK;
}
// Find ^A. If not found the line number is after the 0x7f
p = vim_strchr(p_7f, Ctrl_A);
if (p == NULL)
p = p_7f + 1;
else
++p;
if (!VIM_ISDIGIT(*p)) // check for start of line number
goto etag_fail;
tagp->command = p;
if (p[-1] == Ctrl_A) // first format: explicit tagname given
{
tagp->tagname = p_7f + 1;
tagp->tagname_end = p - 1;
}
else // second format: isolate tagname
{
// find end of tagname
for (p = p_7f - 1; !vim_iswordc(*p); --p)
if (p == lbuf)
goto etag_fail;
tagp->tagname_end = p + 1;
while (p >= lbuf && vim_iswordc(*p))
--p;
tagp->tagname = p + 1;
}
return OK;
}
#endif
/*
* Read the next line from a tags file.
* Returns TAGS_READ_SUCCESS if a tags line is successfully read and should be
* processed.
* Returns TAGS_READ_EOF if the end of file is reached.
* Returns TAGS_READ_IGNORE if the current line should be ignored (used when
* reached end of a emacs included tags file)
*/
static tags_read_status_T
findtags_get_next_line(findtags_state_T *st, tagsearch_info_T *sinfo_p)
{
int eof;
off_T offset;
// For binary search: compute the next offset to use.
if (st->state == TS_BINARY)
{
offset = sinfo_p->low_offset + ((sinfo_p->high_offset
- sinfo_p->low_offset) / 2);
if (offset == sinfo_p->curr_offset)
return TAGS_READ_EOF; // End the binary search without a match.
else
sinfo_p->curr_offset = offset;
}
// Skipping back (after a match during binary search).
else if (st->state == TS_SKIP_BACK)
{
sinfo_p->curr_offset -= st->lbuf_size * 2;
if (sinfo_p->curr_offset < 0)
{
sinfo_p->curr_offset = 0;
rewind(st->fp);
st->state = TS_STEP_FORWARD;
}
}
// When jumping around in the file, first read a line to find the
// start of the next line.
if (st->state == TS_BINARY || st->state == TS_SKIP_BACK)
{
// Adjust the search file offset to the correct position
sinfo_p->curr_offset_used = sinfo_p->curr_offset;
vim_ignored = vim_fseek(st->fp, sinfo_p->curr_offset, SEEK_SET);
eof = vim_fgets(st->lbuf, st->lbuf_size, st->fp);
if (!eof && sinfo_p->curr_offset != 0)
{
sinfo_p->curr_offset = vim_ftell(st->fp);
if (sinfo_p->curr_offset == sinfo_p->high_offset)
{
// oops, gone a bit too far; try from low offset
vim_ignored = vim_fseek(st->fp, sinfo_p->low_offset, SEEK_SET);
sinfo_p->curr_offset = sinfo_p->low_offset;
}
eof = vim_fgets(st->lbuf, st->lbuf_size, st->fp);
}
// skip empty and blank lines
while (!eof && vim_isblankline(st->lbuf))
{
sinfo_p->curr_offset = vim_ftell(st->fp);
eof = vim_fgets(st->lbuf, st->lbuf_size, st->fp);
}
if (eof)
{
// Hit end of file. Skip backwards.
st->state = TS_SKIP_BACK;
sinfo_p->match_offset = vim_ftell(st->fp);
sinfo_p->curr_offset = sinfo_p->curr_offset_used;
return TAGS_READ_IGNORE;
}
}
// Not jumping around in the file: Read the next line.
else
{
// skip empty and blank lines
do
{
#ifdef FEAT_CSCOPE
if (st->flags & TAG_CSCOPE)
eof = cs_fgets(st->lbuf, st->lbuf_size);
else
#endif
eof = vim_fgets(st->lbuf, st->lbuf_size, st->fp);
} while (!eof && vim_isblankline(st->lbuf));
if (eof)
{
#ifdef FEAT_EMACS_TAGS
if (emacs_tags_file_eof(st) == TRUE)
// an included tags file. Continue processing the parent
// tags file.
return TAGS_READ_IGNORE;
#endif
return TAGS_READ_EOF;
}
}
return TAGS_READ_SUCCESS;
}
/*
* Parse a tags file header line in "st->lbuf".
* Returns TRUE if the current line in st->lbuf is not a tags header line and
* should be parsed as a regular tag line. Returns FALSE if the line is a
* header line and the next header line should be read.
*/
static int
findtags_hdr_parse(findtags_state_T *st)
{
char_u *p;
// Header lines in a tags file start with "!_TAG_"
if (STRNCMP(st->lbuf, "!_TAG_", 6) != 0)
// Non-header item before the header, e.g. "!" itself.
return TRUE;
// Process the header line.
if (STRNCMP(st->lbuf, "!_TAG_FILE_SORTED\t", 18) == 0)
st->tag_file_sorted = st->lbuf[18];
if (STRNCMP(st->lbuf, "!_TAG_FILE_ENCODING\t", 20) == 0)
{
// Prepare to convert every line from the specified encoding to
// 'encoding'.
for (p = st->lbuf + 20; *p > ' ' && *p < 127; ++p)
;
*p = NUL;
convert_setup(&st->vimconv, st->lbuf + 20, p_enc);
}
// Read the next line. Unrecognized flags are ignored.
return FALSE;
}
/*
* Handler to initialize the state when starting to process a new tags file.
* Called in the TS_START state when finding tags from a tags file.
* Returns TRUE if the line read from the tags file should be parsed and
* FALSE if the line should be ignored.
*/
static int
findtags_start_state_handler(
findtags_state_T *st,
int *sortic,
tagsearch_info_T *sinfo_p)
{
#ifdef FEAT_CSCOPE
int use_cscope = (st->flags & TAG_CSCOPE);
#endif
int noic = (st->flags & TAG_NOIC);
off_T filesize;
// The header ends when the line sorts below "!_TAG_". When case is
// folded lower case letters sort before "_".
if (STRNCMP(st->lbuf, "!_TAG_", 6) <= 0
|| (st->lbuf[0] == '!' && ASCII_ISLOWER(st->lbuf[1])))
return findtags_hdr_parse(st);
// Headers ends.
// When there is no tag head, or ignoring case, need to do a
// linear search.
// When no "!_TAG_" is found, default to binary search. If
// the tag file isn't sorted, the second loop will find it.
// When "!_TAG_FILE_SORTED" found: start binary search if
// flag set.
// For cscope, it's always linear.
# ifdef FEAT_CSCOPE
if (st->linear || use_cscope)
# else
if (st->linear)
# endif
st->state = TS_LINEAR;
else if (st->tag_file_sorted == NUL)
st->state = TS_BINARY;
else if (st->tag_file_sorted == '1')
st->state = TS_BINARY;
else if (st->tag_file_sorted == '2')
{
st->state = TS_BINARY;
*sortic = TRUE;
st->orgpat->regmatch.rm_ic = (p_ic || !noic);
}
else
st->state = TS_LINEAR;
if (st->state == TS_BINARY && st->orgpat->regmatch.rm_ic && !*sortic)
{
// Binary search won't work for ignoring case, use linear
// search.
st->linear = TRUE;
st->state = TS_LINEAR;
}
// When starting a binary search, get the size of the file and
// compute the first offset.
if (st->state == TS_BINARY)
{
if (vim_fseek(st->fp, 0L, SEEK_END) != 0)
// can't seek, don't use binary search
st->state = TS_LINEAR;
else
{
// Get the tag file size (don't use mch_fstat(), it's
// not portable). Don't use lseek(), it doesn't work
// properly on MacOS Catalina.
filesize = vim_ftell(st->fp);
vim_ignored = vim_fseek(st->fp, 0L, SEEK_SET);
// Calculate the first read offset in the file. Start
// the search in the middle of the file.
sinfo_p->low_offset = 0;
sinfo_p->low_char = 0;
sinfo_p->high_offset = filesize;
sinfo_p->curr_offset = 0;
sinfo_p->high_char = 0xff;
}
return FALSE;
}
return TRUE;
}
/*
* Parse a tag line read from a tags file.
* Also compares the tag name in "tagpp->tagname" with a search pattern in
* "st->orgpat->head" as a quick check if the tag may match.
* Returns:
* - TAG_MATCH_SUCCESS if the tag may match
* - TAG_MATCH_FAIL if the tag doesn't match
* - TAG_MATCH_NEXT to look for the next matching tag (used in a binary search)
* - TAG_MATCH_STOP if all the tags are processed without a match. Uses the
* values in "margs" for doing the comparison.
*/
static tagmatch_status_T
findtags_parse_line(
findtags_state_T *st,
tagptrs_T *tagpp,
findtags_match_args_T *margs,
tagsearch_info_T *sinfo_p)
{
int status;
int i;
int cmplen;
int tagcmp;
// Figure out where the different strings are in this line.
// For "normal" tags: Do a quick check if the tag matches.
// This speeds up tag searching a lot!
if (st->orgpat->headlen
#ifdef FEAT_EMACS_TAGS
&& !st->is_etag
#endif
)
{
CLEAR_FIELD(*tagpp);
tagpp->tagname = st->lbuf;
tagpp->tagname_end = vim_strchr(st->lbuf, TAB);
if (tagpp->tagname_end == NULL)
// Corrupted tag line.
return TAG_MATCH_FAIL;
// Skip this line if the length of the tag is different and
// there is no regexp, or the tag is too short.
cmplen = (int)(tagpp->tagname_end - tagpp->tagname);
if (p_tl != 0 && cmplen > p_tl) // adjust for 'taglength'
cmplen = p_tl;
if ((st->flags & TAG_REGEXP) && st->orgpat->headlen < cmplen)
cmplen = st->orgpat->headlen;
else if (st->state == TS_LINEAR && st->orgpat->headlen != cmplen)
return TAG_MATCH_NEXT;
if (st->state == TS_BINARY)
{
// Simplistic check for unsorted tags file.
i = (int)tagpp->tagname[0];
if (margs->sortic)
i = (int)TOUPPER_ASC(tagpp->tagname[0]);
if (i < sinfo_p->low_char || i > sinfo_p->high_char)
margs->sort_error = TRUE;
// Compare the current tag with the searched tag.
if (margs->sortic)
tagcmp = tag_strnicmp(tagpp->tagname, st->orgpat->head,
(size_t)cmplen);
else
tagcmp = STRNCMP(tagpp->tagname, st->orgpat->head, cmplen);
// A match with a shorter tag means to search forward.
// A match with a longer tag means to search backward.
if (tagcmp == 0)
{
if (cmplen < st->orgpat->headlen)
tagcmp = -1;
else if (cmplen > st->orgpat->headlen)
tagcmp = 1;
}
if (tagcmp == 0)
{
// We've located the tag, now skip back and search
// forward until the first matching tag is found.
st->state = TS_SKIP_BACK;
sinfo_p->match_offset = sinfo_p->curr_offset;
return TAG_MATCH_NEXT;
}
if (tagcmp < 0)
{
sinfo_p->curr_offset = vim_ftell(st->fp);
if (sinfo_p->curr_offset < sinfo_p->high_offset)
{
sinfo_p->low_offset = sinfo_p->curr_offset;
if (margs->sortic)
sinfo_p->low_char = TOUPPER_ASC(tagpp->tagname[0]);
else
sinfo_p->low_char = tagpp->tagname[0];
return TAG_MATCH_NEXT;
}
}
if (tagcmp > 0 && sinfo_p->curr_offset != sinfo_p->high_offset)
{
sinfo_p->high_offset = sinfo_p->curr_offset;
if (margs->sortic)
sinfo_p->high_char = TOUPPER_ASC(tagpp->tagname[0]);
else
sinfo_p->high_char = tagpp->tagname[0];
return TAG_MATCH_NEXT;
}
// No match yet and are at the end of the binary search.
return TAG_MATCH_STOP;
}
else if (st->state == TS_SKIP_BACK)
{
if (MB_STRNICMP(tagpp->tagname, st->orgpat->head, cmplen) != 0)
st->state = TS_STEP_FORWARD;
else
// Have to skip back more. Restore the curr_offset
// used, otherwise we get stuck at a long line.
sinfo_p->curr_offset = sinfo_p->curr_offset_used;
return TAG_MATCH_NEXT;
}
else if (st->state == TS_STEP_FORWARD)
{
if (MB_STRNICMP(tagpp->tagname, st->orgpat->head, cmplen) != 0)
{
if ((off_T)vim_ftell(st->fp) > sinfo_p->match_offset)
return TAG_MATCH_STOP; // past last match
else
return TAG_MATCH_NEXT; // before first match
}
}
else
// skip this match if it can't match
if (MB_STRNICMP(tagpp->tagname, st->orgpat->head, cmplen) != 0)
return TAG_MATCH_NEXT;
// Can be a matching tag, isolate the file name and command.
tagpp->fname = tagpp->tagname_end + 1;
tagpp->fname_end = vim_strchr(tagpp->fname, TAB);
if (tagpp->fname_end == NULL)
status = FAIL;
else
{
tagpp->command = tagpp->fname_end + 1;
status = OK;
}
}
else
status = parse_tag_line(st->lbuf,
#ifdef FEAT_EMACS_TAGS
st->is_etag,
#endif
tagpp);
if (status == FAIL)
return TAG_MATCH_FAIL;
#ifdef FEAT_EMACS_TAGS
if (st->is_etag)
tagpp->fname = st->ebuf;
#endif
return TAG_MATCH_SUCCESS;
}
/*
* Initialize the structure used for tag matching.
*/
static void
findtags_matchargs_init(findtags_match_args_T *margs, int flags)
{
margs->matchoff = 0; // match offset
margs->match_re = FALSE; // match with regexp
margs->match_no_ic = FALSE; // matches with case
margs->has_re = (flags & TAG_REGEXP); // regexp used
margs->sortic = FALSE; // tag file sorted in nocase
margs->sort_error = FALSE; // tags file not sorted
}
/*
* Compares the tag name in "tagpp->tagname" with a search pattern in
* "st->orgpat->pat".
* Returns TRUE if the tag matches, FALSE if the tag doesn't match.
* Uses the values in "margs" for doing the comparison.
*/
static int
findtags_match_tag(
findtags_state_T *st,
tagptrs_T *tagpp,
findtags_match_args_T *margs)
{
int match = FALSE;
int cmplen;
// First try matching with the pattern literally (also when it is
// a regexp).
cmplen = (int)(tagpp->tagname_end - tagpp->tagname);
if (p_tl != 0 && cmplen > p_tl) // adjust for 'taglength'
cmplen = p_tl;
// if tag length does not match, don't try comparing
if (st->orgpat->len != cmplen)
match = FALSE;
else
{
if (st->orgpat->regmatch.rm_ic)
{
match =
(MB_STRNICMP(tagpp->tagname, st->orgpat->pat, cmplen) == 0);
if (match)
margs->match_no_ic =
(STRNCMP(tagpp->tagname, st->orgpat->pat, cmplen) == 0);
}
else
match = (STRNCMP(tagpp->tagname, st->orgpat->pat, cmplen) == 0);
}
// Has a regexp: Also find tags matching regexp.
margs->match_re = FALSE;
if (!match && st->orgpat->regmatch.regprog != NULL)
{
int cc;
cc = *tagpp->tagname_end;
*tagpp->tagname_end = NUL;
match = vim_regexec(&st->orgpat->regmatch, tagpp->tagname, (colnr_T)0);
if (match)
{
margs->matchoff = (int)(st->orgpat->regmatch.startp[0] -
tagpp->tagname);
if (st->orgpat->regmatch.rm_ic)
{
st->orgpat->regmatch.rm_ic = FALSE;
margs->match_no_ic = vim_regexec(&st->orgpat->regmatch,
tagpp->tagname, (colnr_T)0);
st->orgpat->regmatch.rm_ic = TRUE;
}
}
*tagpp->tagname_end = cc;
margs->match_re = TRUE;
}
return match;
}
/*
* Convert the encoding of a line read from a tags file in "st->lbuf".
* Converting the pattern from 'enc' to the tags file encoding doesn't work,
* because characters are not recognized. The converted line is saved in
* st->lbuf.
*/
static void
findtags_string_convert(findtags_state_T *st)
{
char_u *conv_line;
int len;
conv_line = string_convert(&st->vimconv, st->lbuf, NULL);
if (conv_line == NULL)
return;
// Copy or swap lbuf and conv_line.
len = (int)STRLEN(conv_line) + 1;
if (len > st->lbuf_size)
{
vim_free(st->lbuf);
st->lbuf = conv_line;
st->lbuf_size = len;
}
else
{
STRCPY(st->lbuf, conv_line);
vim_free(conv_line);
}
}
/*
* Add a matching tag found in a tags file to st->ht_match and st->ga_match.
* Returns OK if successfully added the match and FAIL on memory allocation
* failure.
*/
static int
findtags_add_match(
findtags_state_T *st,
tagptrs_T *tagpp,
findtags_match_args_T *margs,
char_u *buf_ffname,
hash_T *hash)
{
#ifdef FEAT_CSCOPE
int use_cscope = (st->flags & TAG_CSCOPE);
#endif
int name_only = (st->flags & TAG_NAMES);
int mtt;
int len = 0;
int is_current; // file name matches
int is_static; // current tag line is static
char_u *mfp;
char_u *p;
char_u *s;
#ifdef FEAT_CSCOPE
if (use_cscope)
{
// Don't change the ordering, always use the same table.
mtt = MT_GL_OTH;
}
else
#endif
{
// Decide in which array to store this match.
is_current = test_for_current(
#ifdef FEAT_EMACS_TAGS
st->is_etag,
#endif
tagpp->fname, tagpp->fname_end, st->tag_fname, buf_ffname);
#ifdef FEAT_EMACS_TAGS
is_static = FALSE;
if (!st->is_etag) // emacs tags are never static
#endif
is_static = test_for_static(tagpp);
// decide in which of the sixteen tables to store this
// match
if (is_static)
{
if (is_current)
mtt = MT_ST_CUR;
else
mtt = MT_ST_OTH;
}
else
{
if (is_current)
mtt = MT_GL_CUR;
else
mtt = MT_GL_OTH;
}
if (st->orgpat->regmatch.rm_ic && !margs->match_no_ic)
mtt += MT_IC_OFF;
if (margs->match_re)
mtt += MT_RE_OFF;
}
// Add the found match in ht_match[mtt] and ga_match[mtt].
// Store the info we need later, which depends on the kind of
// tags we are dealing with.
if (st->help_only)
{
#ifdef FEAT_MULTI_LANG
# define ML_EXTRA 3
#else
# define ML_EXTRA 0
#endif
// Append the help-heuristic number after the tagname, for
// sorting it later. The heuristic is ignored for
// detecting duplicates.
// The format is {tagname}@{lang}NUL{heuristic}NUL
*tagpp->tagname_end = NUL;
len = (int)(tagpp->tagname_end - tagpp->tagname);
mfp = alloc(sizeof(char_u) + len + 10 + ML_EXTRA + 1);
if (mfp != NULL)
{
int heuristic;
p = mfp;
STRCPY(p, tagpp->tagname);
#ifdef FEAT_MULTI_LANG
p[len] = '@';
STRCPY(p + len + 1, st->help_lang);
#endif
heuristic = help_heuristic(tagpp->tagname,
margs->match_re ? margs->matchoff : 0,
!margs->match_no_ic);
#ifdef FEAT_MULTI_LANG
heuristic += st->help_pri;
#endif
sprintf((char *)p + len + 1 + ML_EXTRA, "%06d",
heuristic);
}
*tagpp->tagname_end = TAB;
}
else if (name_only)
{
if (st->get_searchpat)
{
char_u *temp_end = tagpp->command;
if (*temp_end == '/')
while (*temp_end && *temp_end != '\r'
&& *temp_end != '\n'
&& *temp_end != '$')
temp_end++;
if (tagpp->command + 2 < temp_end)
{
len = (int)(temp_end - tagpp->command - 2);
mfp = alloc(len + 2);
if (mfp != NULL)
vim_strncpy(mfp, tagpp->command + 2, len);
}
else
mfp = NULL;
st->get_searchpat = FALSE;
}
else
{
len = (int)(tagpp->tagname_end - tagpp->tagname);
mfp = alloc(sizeof(char_u) + len + 1);
if (mfp != NULL)
vim_strncpy(mfp, tagpp->tagname, len);
// if wanted, re-read line to get long form too
if (State & MODE_INSERT)
st->get_searchpat = p_sft;
}
}
else
{
size_t tag_fname_len = STRLEN(st->tag_fname);
#ifdef FEAT_EMACS_TAGS
size_t ebuf_len = 0;
#endif
// Save the tag in a buffer.
// Use 0x02 to separate fields (Can't use NUL because the
// hash key is terminated by NUL, or Ctrl_A because that is
// part of some Emacs tag files -- see parse_tag_line).
// Emacs tag: <mtt><tag_fname><0x02><ebuf><0x02><lbuf><NUL>
// other tag: <mtt><tag_fname><0x02><0x02><lbuf><NUL>
// without Emacs tags: <mtt><tag_fname><0x02><lbuf><NUL>
// Here <mtt> is the "mtt" value plus 1 to avoid NUL.
len = (int)tag_fname_len + (int)STRLEN(st->lbuf) + 3;
#ifdef FEAT_EMACS_TAGS
if (st->is_etag)
{
ebuf_len = STRLEN(st->ebuf);
len += (int)ebuf_len + 1;
}
else
++len;
#endif
mfp = alloc(sizeof(char_u) + len + 1);
if (mfp != NULL)
{
p = mfp;
p[0] = mtt + 1;
STRCPY(p + 1, st->tag_fname);
#ifdef BACKSLASH_IN_FILENAME
// Ignore differences in slashes, avoid adding
// both path/file and path\file.
slash_adjust(p + 1);
#endif
p[tag_fname_len + 1] = TAG_SEP;
s = p + 1 + tag_fname_len + 1;
#ifdef FEAT_EMACS_TAGS
if (st->is_etag)
{
STRCPY(s, st->ebuf);
s[ebuf_len] = TAG_SEP;
s += ebuf_len + 1;
}
else
*s++ = TAG_SEP;
#endif
STRCPY(s, st->lbuf);
}
}
if (mfp != NULL)
{
hashitem_T *hi;
// Don't add identical matches.
// Add all cscope tags, because they are all listed.
// "mfp" is used as a hash key, there is a NUL byte to end
// the part that matters for comparing, more bytes may
// follow after it. E.g. help tags store the priority
// after the NUL.
#ifdef FEAT_CSCOPE
if (use_cscope)
++*hash;
else
#endif
*hash = hash_hash(mfp);
hi = hash_lookup(&st->ht_match[mtt], mfp, *hash);
if (HASHITEM_EMPTY(hi))
{
if (hash_add_item(&st->ht_match[mtt], hi, mfp, *hash) == FAIL
|| ga_grow(&st->ga_match[mtt], 1) == FAIL)
{
// Out of memory! Just forget about the rest.
st->stop_searching = TRUE;
return FAIL;
}
((char_u **)(st->ga_match[mtt].ga_data))
[st->ga_match[mtt].ga_len++] = mfp;
st->match_count++;
}
else
// duplicate tag, drop it
vim_free(mfp);
}
return OK;
}
/*
* Read and get all the tags from file st->tag_fname.
* Sets "st->stop_searching" to TRUE to stop searching for additional tags.
*/
static void
findtags_get_all_tags(
findtags_state_T *st,
findtags_match_args_T *margs,
char_u *buf_ffname)
{
tagptrs_T tagp;
tagsearch_info_T search_info;
int retval;
#ifdef FEAT_CSCOPE
int use_cscope = (st->flags & TAG_CSCOPE);
#endif
hash_T hash = 0;
// This is only to avoid a compiler warning for using search_info
// uninitialised.
CLEAR_FIELD(search_info);
// Read and parse the lines in the file one by one
for (;;)
{
// check for CTRL-C typed, more often when jumping around
if (st->state == TS_BINARY || st->state == TS_SKIP_BACK)
line_breakcheck();
else
fast_breakcheck();
if ((st->flags & TAG_INS_COMP)) // Double brackets for gcc
ins_compl_check_keys(30, FALSE);
if (got_int || ins_compl_interrupted())
{
st->stop_searching = TRUE;
break;
}
// When mincount is TAG_MANY, stop when enough matches have been
// found (for completion).
if (st->mincount == TAG_MANY && st->match_count >= TAG_MANY)
{
st->stop_searching = TRUE;
break;
}
if (st->get_searchpat)
goto line_read_in;
retval = findtags_get_next_line(st, &search_info);
if (retval == TAGS_READ_IGNORE)
continue;
if (retval == TAGS_READ_EOF)
break;
line_read_in:
if (st->vimconv.vc_type != CONV_NONE)
findtags_string_convert(st);
#ifdef FEAT_EMACS_TAGS
// Emacs tags line with CTRL-L: New file name on next line.
// The file name is followed by a ','.
// Remember etag file name in ebuf.
if (*st->lbuf == Ctrl_L
# ifdef FEAT_CSCOPE
&& !use_cscope
# endif
)
{
st->is_etag = TRUE; // in case at the start
st->state = TS_LINEAR;
emacs_tags_new_filename(st);
continue;
}
#endif
// When still at the start of the file, check for Emacs tags file
// format, and for "not sorted" flag.
if (st->state == TS_START)
{
if (findtags_start_state_handler(st, &margs->sortic, &search_info) == FALSE)
continue;
}
// When the line is too long the NUL will not be in the
// last-but-one byte (see vim_fgets()).
// Has been reported for Mozilla JS with extremely long names.
// In that case we need to increase lbuf_size.
if (st->lbuf[st->lbuf_size - 2] != NUL
#ifdef FEAT_CSCOPE
&& !use_cscope
#endif
)
{
st->lbuf_size *= 2;
vim_free(st->lbuf);
st->lbuf = alloc(st->lbuf_size);
if (st->lbuf == NULL)
{
if (st->fp != NULL)
fclose(st->fp);
st->fp = NULL;
st->stop_searching = TRUE;
return;
}
if (st->state == TS_STEP_FORWARD || st->state == TS_LINEAR)
// Seek to the same position to read the same line again
vim_ignored = vim_fseek(st->fp, search_info.curr_offset,
SEEK_SET);
// this will try the same thing again, make sure the offset is
// different
search_info.curr_offset = 0;
continue;
}
retval = findtags_parse_line(st, &tagp, margs, &search_info);
if (retval == TAG_MATCH_NEXT)
continue;
if (retval == TAG_MATCH_STOP)
break;
if (retval == TAG_MATCH_FAIL)
{
semsg(_(e_format_error_in_tags_file_str), st->tag_fname);
#ifdef FEAT_CSCOPE
if (!use_cscope)
#endif
semsg(_("Before byte %ld"), (long)vim_ftell(st->fp));
st->stop_searching = TRUE;
return;
}
// If a match is found, add it to ht_match[] and ga_match[].
if (findtags_match_tag(st, &tagp, margs))
{
if (findtags_add_match(st, &tagp, margs, buf_ffname, &hash)
== FAIL)
break;
}
} // forever
}
/*
* Search for tags matching "st->orgpat->pat" in the "st->tag_fname" tags file.
* Information needed to search for the tags is in the "st" state structure.
* The matching tags are returned in "st". If an error is encountered, then
* "st->stop_searching" is set to TRUE.
*/
static void
findtags_in_file(findtags_state_T *st, char_u *buf_ffname)
{
findtags_match_args_T margs;
#ifdef FEAT_CSCOPE
int use_cscope = (st->flags & TAG_CSCOPE);
#endif
st->vimconv.vc_type = CONV_NONE;
st->tag_file_sorted = NUL;
st->fp = NULL;
findtags_matchargs_init(&margs, st->flags);
// A file that doesn't exist is silently ignored. Only when not a
// single file is found, an error message is given (further on).
#ifdef FEAT_CSCOPE
if (use_cscope)
st->fp = NULL; // avoid GCC warning
else
#endif
{
#ifdef FEAT_MULTI_LANG
if (curbuf->b_help)
{
if (!findtags_in_help_init(st))
return;
}
#endif
st->fp = mch_fopen((char *)st->tag_fname, "r");
if (st->fp == NULL)
return;
if (p_verbose >= 5)
{
verbose_enter();
smsg(_("Searching tags file %s"), st->tag_fname);
verbose_leave();
}
}
st->did_open = TRUE; // remember that we found at least one file
st->state = TS_START; // we're at the start of the file
#ifdef FEAT_EMACS_TAGS
st->is_etag = FALSE; // default is: not emacs style
#endif
// Read and parse the lines in the file one by one
findtags_get_all_tags(st, &margs, buf_ffname);
if (st->fp != NULL)
{
fclose(st->fp);
st->fp = NULL;
}
#ifdef FEAT_EMACS_TAGS
emacs_tags_incstack_free();
#endif
if (st->vimconv.vc_type != CONV_NONE)
convert_setup(&st->vimconv, NULL, NULL);
if (margs.sort_error)
semsg(_(e_tags_file_not_sorted_str), st->tag_fname);
// Stop searching if sufficient tags have been found.
if (st->match_count >= st->mincount)
st->stop_searching = TRUE;
}
/*
* Copy the tags found by find_tags() to "matchesp".
* Returns the number of matches copied.
*/
static int
findtags_copy_matches(findtags_state_T *st, char_u ***matchesp)
{
int name_only = (st->flags & TAG_NAMES);
char_u **matches;
int mtt;
int i;
char_u *mfp;
char_u *p;
if (st->match_count > 0)
matches = ALLOC_MULT(char_u *, st->match_count);
else
matches = NULL;
st->match_count = 0;
for (mtt = 0; mtt < MT_COUNT; ++mtt)
{
for (i = 0; i < st->ga_match[mtt].ga_len; ++i)
{
mfp = ((char_u **)(st->ga_match[mtt].ga_data))[i];
if (matches == NULL)
vim_free(mfp);
else
{
if (!name_only)
{
// Change mtt back to zero-based.
*mfp = *mfp - 1;
// change the TAG_SEP back to NUL
for (p = mfp + 1; *p != NUL; ++p)
if (*p == TAG_SEP)
*p = NUL;
}
matches[st->match_count++] = mfp;
}
}
ga_clear(&st->ga_match[mtt]);
hash_clear(&st->ht_match[mtt]);
}
*matchesp = matches;
return st->match_count;
}
/*
* find_tags() - search for tags in tags files
*
* Return FAIL if search completely failed (*num_matches will be 0, *matchesp
* will be NULL), OK otherwise.
*
* Priority depending on which type of tag is recognized:
* 6. A static or global tag with a full matching tag for the current file.
* 5. A global tag with a full matching tag for another file.
* 4. A static tag with a full matching tag for another file.
* 3. A static or global tag with an ignore-case matching tag for the
* current file.
* 2. A global tag with an ignore-case matching tag for another file.
* 1. A static tag with an ignore-case matching tag for another file.
*
* Tags in an emacs-style tags file are always global.
*
* flags:
* TAG_HELP only search for help tags
* TAG_NAMES only return name of tag
* TAG_REGEXP use "pat" as a regexp
* TAG_NOIC don't always ignore case
* TAG_KEEP_LANG keep language
* TAG_CSCOPE use cscope results for tags
* TAG_NO_TAGFUNC do not call the 'tagfunc' function
*/
int
find_tags(
char_u *pat, // pattern to search for
int *num_matches, // return: number of matches found
char_u ***matchesp, // return: array of matches found
int flags,
int mincount, // MAXCOL: find all matches
// other: minimal number of matches
char_u *buf_ffname) // name of buffer for priority
{
findtags_state_T st;
tagname_T tn; // info for get_tagfname()
int first_file; // trying first tag file
int retval = FAIL; // return value
int round;
int save_emsg_off;
int help_save;
#ifdef FEAT_MULTI_LANG
int i;
char_u *saved_pat = NULL; // copy of pat[]
#endif
int findall = (mincount == MAXCOL || mincount == TAG_MANY);
// find all matching tags
int has_re = (flags & TAG_REGEXP); // regexp used
int noic = (flags & TAG_NOIC);
#ifdef FEAT_CSCOPE
int use_cscope = (flags & TAG_CSCOPE);
#endif
int verbose = (flags & TAG_VERBOSE);
int save_p_ic = p_ic;
/*
* Change the value of 'ignorecase' according to 'tagcase' for the
* duration of this function.
*/
switch (curbuf->b_tc_flags ? curbuf->b_tc_flags : tc_flags)
{
case TC_FOLLOWIC: break;
case TC_IGNORE: p_ic = TRUE; break;
case TC_MATCH: p_ic = FALSE; break;
case TC_FOLLOWSCS: p_ic = ignorecase(pat); break;
case TC_SMART: p_ic = ignorecase_opt(pat, TRUE, TRUE); break;
}
help_save = curbuf->b_help;
if (findtags_state_init(&st, pat, flags, mincount) == FAIL)
goto findtag_end;
#ifdef FEAT_CSCOPE
STRCPY(st.tag_fname, "from cscope"); // for error messages
#endif
/*
* Initialize a few variables
*/
if (st.help_only) // want tags from help file
curbuf->b_help = TRUE; // will be restored later
#ifdef FEAT_CSCOPE
else if (use_cscope)
{
// Make sure we don't mix help and cscope, confuses Coverity.
st.help_only = FALSE;
curbuf->b_help = FALSE;
}
#endif
#ifdef FEAT_MULTI_LANG
if (curbuf->b_help)
{
// When "@ab" is specified use only the "ab" language, otherwise
// search all languages.
if (st.orgpat->len > 3 && pat[st.orgpat->len - 3] == '@'
&& ASCII_ISALPHA(pat[st.orgpat->len - 2])
&& ASCII_ISALPHA(pat[st.orgpat->len - 1]))
{
saved_pat = vim_strnsave(pat, st.orgpat->len - 3);
if (saved_pat != NULL)
{
st.help_lang_find = &pat[st.orgpat->len - 2];
st.orgpat->pat = saved_pat;
st.orgpat->len -= 3;
}
}
}
#endif
if (p_tl != 0 && st.orgpat->len > p_tl) // adjust for 'taglength'
st.orgpat->len = p_tl;
save_emsg_off = emsg_off;
emsg_off = TRUE; // don't want error for invalid RE here
prepare_pats(st.orgpat, has_re);
emsg_off = save_emsg_off;
if (has_re && st.orgpat->regmatch.regprog == NULL)
goto findtag_end;
#ifdef FEAT_EVAL
retval = findtags_apply_tfu(&st, pat, buf_ffname);
if (retval != NOTDONE)
goto findtag_end;
// re-initialize the default return value
retval = FAIL;
#endif
#ifdef FEAT_MULTI_LANG
// Set a flag if the file extension is .txt
if ((flags & TAG_KEEP_LANG)
&& st.help_lang_find == NULL
&& curbuf->b_fname != NULL
&& (i = (int)STRLEN(curbuf->b_fname)) > 4
&& STRICMP(curbuf->b_fname + i - 4, ".txt") == 0)
st.is_txt = TRUE;
#endif
/*
* When finding a specified number of matches, first try with matching
* case, so binary search can be used, and try ignore-case matches in a
* second loop.
* When finding all matches, 'tagbsearch' is off, or there is no fixed
* string to look for, ignore case right away to avoid going though the
* tags files twice.
* When the tag file is case-fold sorted, it is either one or the other.
* Only ignore case when TAG_NOIC not used or 'ignorecase' set.
*/
st.orgpat->regmatch.rm_ic = ((p_ic || !noic)
&& (findall || st.orgpat->headlen == 0 || !p_tbs));
for (round = 1; round <= 2; ++round)
{
st.linear = (st.orgpat->headlen == 0 || !p_tbs || round == 2);
/*
* Try tag file names from tags option one by one.
*/
for (first_file = TRUE;
#ifdef FEAT_CSCOPE
use_cscope ||
#endif
get_tagfname(&tn, first_file, st.tag_fname) == OK;
first_file = FALSE)
{
findtags_in_file(&st, buf_ffname);
if (st.stop_searching
#ifdef FEAT_CSCOPE
|| use_cscope
#endif
)
{
retval = OK;
break;
}
} // end of for-each-file loop
#ifdef FEAT_CSCOPE
if (!use_cscope)
#endif
tagname_free(&tn);
// stop searching when already did a linear search, or when TAG_NOIC
// used, and 'ignorecase' not set or already did case-ignore search
if (st.stop_searching || st.linear || (!p_ic && noic) ||
st.orgpat->regmatch.rm_ic)
break;
# ifdef FEAT_CSCOPE
if (use_cscope)
break;
# endif
// try another time while ignoring case
st.orgpat->regmatch.rm_ic = TRUE;
}
if (!st.stop_searching)
{
if (!st.did_open && verbose) // never opened any tags file
emsg(_(e_no_tags_file));
retval = OK; // It's OK even when no tag found
}
findtag_end:
findtags_state_free(&st);
/*
* Move the matches from the ga_match[] arrays into one list of
* matches. When retval == FAIL, free the matches.
*/
if (retval == FAIL)
st.match_count = 0;
*num_matches = findtags_copy_matches(&st, matchesp);
curbuf->b_help = help_save;
#ifdef FEAT_MULTI_LANG
vim_free(saved_pat);
#endif
p_ic = save_p_ic;
return retval;
}
static garray_T tag_fnames = GA_EMPTY;
/*
* Callback function for finding all "tags" and "tags-??" files in
* 'runtimepath' doc directories.
*/
static void
found_tagfile_cb(char_u *fname, void *cookie UNUSED)
{
if (ga_grow(&tag_fnames, 1) == FAIL)
return;
char_u *tag_fname = vim_strsave(fname);
#ifdef BACKSLASH_IN_FILENAME
slash_adjust(tag_fname);
#endif
simplify_filename(tag_fname);
((char_u **)(tag_fnames.ga_data))[tag_fnames.ga_len++] = tag_fname;
}
#if defined(EXITFREE) || defined(PROTO)
void
free_tag_stuff(void)
{
ga_clear_strings(&tag_fnames);
if (curwin != NULL)
do_tag(NULL, DT_FREE, 0, 0, 0);
tag_freematch();
# if defined(FEAT_QUICKFIX)
tagstack_clear_entry(&ptag_entry);
# endif
}
#endif
/*
* Get the next name of a tag file from the tag file list.
* For help files, use "tags" file only.
*
* Return FAIL if no more tag file names, OK otherwise.
*/
int
get_tagfname(
tagname_T *tnp, // holds status info
int first, // TRUE when first file name is wanted
char_u *buf) // pointer to buffer of MAXPATHL chars
{
char_u *fname = NULL;
char_u *r_ptr;
int i;
if (first)
CLEAR_POINTER(tnp);
if (curbuf->b_help)
{
/*
* For help files it's done in a completely different way:
* Find "doc/tags" and "doc/tags-??" in all directories in
* 'runtimepath'.
*/
if (first)
{
ga_clear_strings(&tag_fnames);
ga_init2(&tag_fnames, sizeof(char_u *), 10);
do_in_runtimepath((char_u *)
#ifdef FEAT_MULTI_LANG
# ifdef VMS
// Functions decc$to_vms() and decc$translate_vms() crash
// on some VMS systems with wildcards "??". Seems ECO
// patches do fix the problem in C RTL, but we can't use
// an #ifdef for that.
"doc/tags doc/tags-*"
# else
"doc/tags doc/tags-??"
# endif
#else
"doc/tags"
#endif
, DIP_ALL, found_tagfile_cb, NULL);
}
if (tnp->tn_hf_idx >= tag_fnames.ga_len)
{
// Not found in 'runtimepath', use 'helpfile', if it exists and
// wasn't used yet, replacing "help.txt" with "tags".
if (tnp->tn_hf_idx > tag_fnames.ga_len || *p_hf == NUL)
return FAIL;
++tnp->tn_hf_idx;
STRCPY(buf, p_hf);
STRCPY(gettail(buf), "tags");
#ifdef BACKSLASH_IN_FILENAME
slash_adjust(buf);
#endif
simplify_filename(buf);
for (i = 0; i < tag_fnames.ga_len; ++i)
if (STRCMP(buf, ((char_u **)(tag_fnames.ga_data))[i]) == 0)
return FAIL; // avoid duplicate file names
}
else
vim_strncpy(buf, ((char_u **)(tag_fnames.ga_data))[
tnp->tn_hf_idx++], MAXPATHL - 1);
return OK;
}
if (first)
{
// Init. We make a copy of 'tags', because autocommands may change
// the value without notifying us.
tnp->tn_tags = vim_strsave((*curbuf->b_p_tags != NUL)
? curbuf->b_p_tags : p_tags);
if (tnp->tn_tags == NULL)
return FAIL;
tnp->tn_np = tnp->tn_tags;
}
/*
* Loop until we have found a file name that can be used.
* There are two states:
* tnp->tn_did_filefind_init == FALSE: setup for next part in 'tags'.
* tnp->tn_did_filefind_init == TRUE: find next file in this part.
*/
for (;;)
{
if (tnp->tn_did_filefind_init)
{
fname = vim_findfile(tnp->tn_search_ctx);
if (fname != NULL)
break;
tnp->tn_did_filefind_init = FALSE;
}
else
{
char_u *filename = NULL;
// Stop when used all parts of 'tags'.
if (*tnp->tn_np == NUL)
{
vim_findfile_cleanup(tnp->tn_search_ctx);
tnp->tn_search_ctx = NULL;
return FAIL;
}
/*
* Copy next file name into buf.
*/
buf[0] = NUL;
(void)copy_option_part(&tnp->tn_np, buf, MAXPATHL - 1, " ,");
r_ptr = vim_findfile_stopdir(buf);
// move the filename one char forward and truncate the
// filepath with a NUL
filename = gettail(buf);
STRMOVE(filename + 1, filename);
*filename++ = NUL;
tnp->tn_search_ctx = vim_findfile_init(buf, filename,
r_ptr, 100,
FALSE, // don't free visited list
FINDFILE_FILE, // we search for a file
tnp->tn_search_ctx, TRUE, curbuf->b_ffname);
if (tnp->tn_search_ctx != NULL)
tnp->tn_did_filefind_init = TRUE;
}
}
STRCPY(buf, fname);
vim_free(fname);
return OK;
}
/*
* Free the contents of a tagname_T that was filled by get_tagfname().
*/
void
tagname_free(tagname_T *tnp)
{
vim_free(tnp->tn_tags);
vim_findfile_cleanup(tnp->tn_search_ctx);
tnp->tn_search_ctx = NULL;
ga_clear_strings(&tag_fnames);
}
/*
* Parse one line from the tags file. Find start/end of tag name, start/end of
* file name and start of search pattern.
*
* If is_etag is TRUE, tagp->fname and tagp->fname_end are not set.
*
* Return FAIL if there is a format error in this line, OK otherwise.
*/
static int
parse_tag_line(
char_u *lbuf, // line to be parsed
#ifdef FEAT_EMACS_TAGS
int is_etag,
#endif
tagptrs_T *tagp)
{
char_u *p;
#ifdef FEAT_EMACS_TAGS
if (is_etag)
// emacs-style tag file
return emacs_tags_parse_line(lbuf, tagp);
#endif
// Isolate the tagname, from lbuf up to the first white
tagp->tagname = lbuf;
p = vim_strchr(lbuf, TAB);
if (p == NULL)
return FAIL;
tagp->tagname_end = p;
// Isolate file name, from first to second white space
if (*p != NUL)
++p;
tagp->fname = p;
p = vim_strchr(p, TAB);
if (p == NULL)
return FAIL;
tagp->fname_end = p;
// find start of search command, after second white space
if (*p != NUL)
++p;
if (*p == NUL)
return FAIL;
tagp->command = p;
return OK;
}
/*
* Check if tagname is a static tag
*
* Static tags produced by the older ctags program have the format:
* 'file:tag file /pattern'.
* This is only recognized when both occurrence of 'file' are the same, to
* avoid recognizing "string::string" or ":exit".
*
* Static tags produced by the new ctags program have the format:
* 'tag file /pattern/;"<Tab>file:' "
*
* Return TRUE if it is a static tag and adjust *tagname to the real tag.
* Return FALSE if it is not a static tag.
*/
static int
test_for_static(tagptrs_T *tagp)
{
char_u *p;
/*
* Check for new style static tag ":...<Tab>file:[<Tab>...]"
*/
p = tagp->command;
while ((p = vim_strchr(p, '\t')) != NULL)
{
++p;
if (STRNCMP(p, "file:", 5) == 0)
return TRUE;
}
return FALSE;
}
/*
* Returns the length of a matching tag line.
*/
static size_t
matching_line_len(char_u *lbuf)
{
char_u *p = lbuf + 1;
// does the same thing as parse_match()
p += STRLEN(p) + 1;
#ifdef FEAT_EMACS_TAGS
p += STRLEN(p) + 1;
#endif
return (p - lbuf) + STRLEN(p);
}
/*
* Parse a line from a matching tag. Does not change the line itself.
*
* The line that we get looks like this:
* Emacs tag: <mtt><tag_fname><NUL><ebuf><NUL><lbuf>
* other tag: <mtt><tag_fname><NUL><NUL><lbuf>
* without Emacs tags: <mtt><tag_fname><NUL><lbuf>
*
* Return OK or FAIL.
*/
static int
parse_match(
char_u *lbuf, // input: matching line
tagptrs_T *tagp) // output: pointers into the line
{
int retval;
char_u *p;
char_u *pc, *pt;
tagp->tag_fname = lbuf + 1;
lbuf += STRLEN(tagp->tag_fname) + 2;
#ifdef FEAT_EMACS_TAGS
if (*lbuf)
{
tagp->is_etag = TRUE;
tagp->fname = lbuf;
lbuf += STRLEN(lbuf);
tagp->fname_end = lbuf++;
}
else
{
tagp->is_etag = FALSE;
++lbuf;
}
#endif
// Find search pattern and the file name for non-etags.
retval = parse_tag_line(lbuf,
#ifdef FEAT_EMACS_TAGS
tagp->is_etag,
#endif
tagp);
tagp->tagkind = NULL;
tagp->user_data = NULL;
tagp->tagline = 0;
tagp->command_end = NULL;
if (retval != OK)
return retval;
// Try to find a kind field: "kind:<kind>" or just "<kind>"
p = tagp->command;
if (find_extra(&p) == OK)
{
if (p > tagp->command && p[-1] == '|')
tagp->command_end = p - 1; // drop trailing bar
else
tagp->command_end = p;
p += 2; // skip ";\""
if (*p++ == TAB)
// Accept ASCII alphabetic kind characters and any multi-byte
// character.
while (ASCII_ISALPHA(*p) || mb_ptr2len(p) > 1)
{
if (STRNCMP(p, "kind:", 5) == 0)
tagp->tagkind = p + 5;
else if (STRNCMP(p, "user_data:", 10) == 0)
tagp->user_data = p + 10;
else if (STRNCMP(p, "line:", 5) == 0)
tagp->tagline = atoi((char *)p + 5);
if (tagp->tagkind != NULL && tagp->user_data != NULL)
break;
pc = vim_strchr(p, ':');
pt = vim_strchr(p, '\t');
if (pc == NULL || (pt != NULL && pc > pt))
tagp->tagkind = p;
if (pt == NULL)
break;
p = pt;
MB_PTR_ADV(p);
}
}
if (tagp->tagkind != NULL)
{
for (p = tagp->tagkind;
*p && *p != '\t' && *p != '\r' && *p != '\n'; MB_PTR_ADV(p))
;
tagp->tagkind_end = p;
}
if (tagp->user_data != NULL)
{
for (p = tagp->user_data;
*p && *p != '\t' && *p != '\r' && *p != '\n'; MB_PTR_ADV(p))
;
tagp->user_data_end = p;
}
return retval;
}
/*
* Find out the actual file name of a tag. Concatenate the tags file name
* with the matching tag file name.
* Returns an allocated string or NULL (out of memory).
*/
static char_u *
tag_full_fname(tagptrs_T *tagp)
{
char_u *fullname;
int c;
#ifdef FEAT_EMACS_TAGS
if (tagp->is_etag)
c = 0; // to shut up GCC
else
#endif
{
c = *tagp->fname_end;
*tagp->fname_end = NUL;
}
fullname = expand_tag_fname(tagp->fname, tagp->tag_fname, FALSE);
#ifdef FEAT_EMACS_TAGS
if (!tagp->is_etag)
#endif
*tagp->fname_end = c;
return fullname;
}
/*
* Jump to a tag that has been found in one of the tag files
*
* returns OK for success, NOTAGFILE when file not found, FAIL otherwise.
*/
static int
jumpto_tag(
char_u *lbuf_arg, // line from the tags file for this tag
int forceit, // :ta with !
int keep_help) // keep help flag (FALSE for cscope)
{
optmagic_T save_magic_overruled;
int save_p_ws, save_p_scs, save_p_ic;
linenr_T save_lnum;
char_u *str;
char_u *pbuf; // search pattern buffer
char_u *pbuf_end;
char_u *tofree_fname = NULL;
char_u *fname;
tagptrs_T tagp;
int retval = FAIL;
int getfile_result = GETFILE_UNUSED;
int search_options;
#ifdef FEAT_SEARCH_EXTRA
int save_no_hlsearch;
#endif
#if defined(FEAT_QUICKFIX)
win_T *curwin_save = NULL;
#endif
char_u *full_fname = NULL;
#ifdef FEAT_FOLDING
int old_KeyTyped = KeyTyped; // getting the file may reset it
#endif
size_t len;
char_u *lbuf;
// Make a copy of the line, it can become invalid when an autocommand calls
// back here recursively.
len = matching_line_len(lbuf_arg) + 1;
lbuf = alloc(len);
if (lbuf != NULL)
mch_memmove(lbuf, lbuf_arg, len);
pbuf = alloc(LSIZE);
// parse the match line into the tagp structure
if (pbuf == NULL || lbuf == NULL || parse_match(lbuf, &tagp) == FAIL)
{
tagp.fname_end = NULL;
goto erret;
}
// truncate the file name, so it can be used as a string
*tagp.fname_end = NUL;
fname = tagp.fname;
// copy the command to pbuf[], remove trailing CR/NL
str = tagp.command;
for (pbuf_end = pbuf; *str && *str != '\n' && *str != '\r'; )
{
#ifdef FEAT_EMACS_TAGS
if (tagp.is_etag && *str == ',')// stop at ',' after line number
break;
#endif
*pbuf_end++ = *str++;
if (pbuf_end - pbuf + 1 >= LSIZE)
break;
}
*pbuf_end = NUL;
#ifdef FEAT_EMACS_TAGS
if (!tagp.is_etag)
#endif
{
/*
* Remove the "<Tab>fieldname:value" stuff; we don't need it here.
*/
str = pbuf;
if (find_extra(&str) == OK)
{
pbuf_end = str;
*pbuf_end = NUL;
}
}
/*
* Expand file name, when needed (for environment variables).
* If 'tagrelative' option set, may change file name.
*/
fname = expand_tag_fname(fname, tagp.tag_fname, TRUE);
if (fname == NULL)
goto erret;
tofree_fname = fname; // free() it later
/*
* Check if the file with the tag exists before abandoning the current
* file. Also accept a file name for which there is a matching BufReadCmd
* autocommand event (e.g., http://sys/file).
*/
if (mch_getperm(fname) < 0 && !has_autocmd(EVENT_BUFREADCMD, fname, NULL))
{
retval = NOTAGFILE;
vim_free(nofile_fname);
nofile_fname = vim_strsave(fname);
if (nofile_fname == NULL)
nofile_fname = empty_option;
goto erret;
}
++RedrawingDisabled;
#ifdef FEAT_GUI
need_mouse_correct = TRUE;
#endif
#if defined(FEAT_QUICKFIX)
if (g_do_tagpreview != 0)
{
postponed_split = 0; // don't split again below
curwin_save = curwin; // Save current window
/*
* If we are reusing a window, we may change dir when
* entering it (autocommands) so turn the tag filename
* into a fullpath
*/
if (!curwin->w_p_pvw)
{
full_fname = FullName_save(fname, FALSE);
fname = full_fname;
/*
* Make the preview window the current window.
* Open a preview window when needed.
*/
prepare_tagpreview(TRUE, TRUE, FALSE);
}
}
// If it was a CTRL-W CTRL-] command split window now. For ":tab tag"
// open a new tab page.
if (postponed_split && (swb_flags & (SWB_USEOPEN | SWB_USETAB)))
{
buf_T *existing_buf = buflist_findname_exp(fname);
if (existing_buf != NULL)
{
// If 'switchbuf' is set jump to the window containing "buf".
if (swbuf_goto_win_with_buf(existing_buf) != NULL)
// We've switched to the buffer, the usual loading of the file
// must be skipped.
getfile_result = GETFILE_SAME_FILE;
}
}
if (getfile_result == GETFILE_UNUSED
&& (postponed_split || cmdmod.cmod_tab != 0))
{
if (win_split(postponed_split > 0 ? postponed_split : 0,
postponed_split_flags) == FAIL)
{
if (RedrawingDisabled > 0)
--RedrawingDisabled;
goto erret;
}
RESET_BINDING(curwin);
}
#endif
if (keep_help)
{
// A :ta from a help file will keep the b_help flag set. For ":ptag"
// we need to use the flag from the window where we came from.
#if defined(FEAT_QUICKFIX)
if (g_do_tagpreview != 0)
keep_help_flag = bt_help(curwin_save->w_buffer);
else
#endif
keep_help_flag = curbuf->b_help;
}
if (getfile_result == GETFILE_UNUSED)
// Careful: getfile() may trigger autocommands and call jumpto_tag()
// recursively.
getfile_result = getfile(0, fname, NULL, TRUE, (linenr_T)0, forceit);
keep_help_flag = FALSE;
if (GETFILE_SUCCESS(getfile_result)) // got to the right file
{
curwin->w_set_curswant = TRUE;
postponed_split = 0;
save_magic_overruled = magic_overruled;
magic_overruled = OPTION_MAGIC_OFF; // always execute with 'nomagic'
#ifdef FEAT_SEARCH_EXTRA
// Save value of no_hlsearch, jumping to a tag is not a real search
save_no_hlsearch = no_hlsearch;
#endif
#if defined(FEAT_PROP_POPUP) && defined(FEAT_QUICKFIX)
// getfile() may have cleared options, apply 'previewpopup' again.
if (g_do_tagpreview != 0 && *p_pvp != NUL)
parse_previewpopup(curwin);
#endif
/*
* If 'cpoptions' contains 't', store the search pattern for the "n"
* command. If 'cpoptions' does not contain 't', the search pattern
* is not stored.
*/
if (vim_strchr(p_cpo, CPO_TAGPAT) != NULL)
search_options = 0;
else
search_options = SEARCH_KEEP;
/*
* If the command is a search, try here.
*
* Reset 'smartcase' for the search, since the search pattern was not
* typed by the user.
* Only use do_search() when there is a full search command, without
* anything following.
*/
str = pbuf;
if (pbuf[0] == '/' || pbuf[0] == '?')
str = skip_regexp(pbuf + 1, pbuf[0], FALSE) + 1;
if (str > pbuf_end - 1) // search command with nothing following
{
save_p_ws = p_ws;
save_p_ic = p_ic;
save_p_scs = p_scs;
p_ws = TRUE; // need 'wrapscan' for backward searches
p_ic = FALSE; // don't ignore case now
p_scs = FALSE;
save_lnum = curwin->w_cursor.lnum;
if (tagp.tagline > 0)
// start search before line from "line:" field
curwin->w_cursor.lnum = tagp.tagline - 1;
else
// start search before first line
curwin->w_cursor.lnum = 0;
if (do_search(NULL, pbuf[0], pbuf[0], pbuf + 1, (long)1,
search_options, NULL))
retval = OK;
else
{
int found = 1;
int cc;
/*
* try again, ignore case now
*/
p_ic = TRUE;
if (!do_search(NULL, pbuf[0], pbuf[0], pbuf + 1, (long)1,
search_options, NULL))
{
/*
* Failed to find pattern, take a guess: "^func ("
*/
found = 2;
(void)test_for_static(&tagp);
cc = *tagp.tagname_end;
*tagp.tagname_end = NUL;
sprintf((char *)pbuf, "^%s\\s\\*(", tagp.tagname);
if (!do_search(NULL, '/', '/', pbuf, (long)1,
search_options, NULL))
{
// Guess again: "^char * \<func ("
sprintf((char *)pbuf, "^\\[#a-zA-Z_]\\.\\*\\<%s\\s\\*(",
tagp.tagname);
if (!do_search(NULL, '/', '/', pbuf, (long)1,
search_options, NULL))
found = 0;
}
*tagp.tagname_end = cc;
}
if (found == 0)
{
emsg(_(e_cannot_find_tag_pattern));
curwin->w_cursor.lnum = save_lnum;
}
else
{
/*
* Only give a message when really guessed, not when 'ic'
* is set and match found while ignoring case.
*/
if (found == 2 || !save_p_ic)
{
msg(_(e_couldnt_find_tag_just_guessing));
if (!msg_scrolled && msg_silent == 0)
{
out_flush();
ui_delay(1010L, TRUE);
}
}
retval = OK;
}
}
p_ws = save_p_ws;
p_ic = save_p_ic;
p_scs = save_p_scs;
// A search command may have positioned the cursor beyond the end
// of the line. May need to correct that here.
check_cursor();
}
else
{
int save_secure = secure;
// Setup the sandbox for executing the command from the tags file.
secure = 1;
#ifdef HAVE_SANDBOX
++sandbox;
#endif
curwin->w_cursor.lnum = 1; // start command in line 1
do_cmdline_cmd(pbuf);
retval = OK;
// When the command has done something that is not allowed make
// sure the error message can be seen.
if (secure == 2)
wait_return(TRUE);
secure = save_secure;
#ifdef HAVE_SANDBOX
--sandbox;
#endif
}
magic_overruled = save_magic_overruled;
#ifdef FEAT_SEARCH_EXTRA
// restore no_hlsearch when keeping the old search pattern
if (search_options)
set_no_hlsearch(save_no_hlsearch);
#endif
// Return OK if jumped to another file (at least we found the file!).
if (getfile_result == GETFILE_OPEN_OTHER)
retval = OK;
if (retval == OK)
{
/*
* For a help buffer: Put the cursor line at the top of the window,
* the help subject will be below it.
*/
if (curbuf->b_help)
set_topline(curwin, curwin->w_cursor.lnum);
#ifdef FEAT_FOLDING
if ((fdo_flags & FDO_TAG) && old_KeyTyped)
foldOpenCursor();
#endif
}
#if defined(FEAT_QUICKFIX)
if (g_do_tagpreview != 0
&& curwin != curwin_save && win_valid(curwin_save))
{
// Return cursor to where we were
validate_cursor();
redraw_later(UPD_VALID);
win_enter(curwin_save, TRUE);
}
#endif
if (RedrawingDisabled > 0)
--RedrawingDisabled;
}
else
{
if (RedrawingDisabled > 0)
--RedrawingDisabled;
got_int = FALSE; // don't want entering window to fail
if (postponed_split) // close the window
{
win_close(curwin, FALSE);
postponed_split = 0;
}
#if defined(FEAT_QUICKFIX) && defined(FEAT_PROP_POPUP)
else if (WIN_IS_POPUP(curwin))
{
win_T *wp = curwin;
if (win_valid(curwin_save))
win_enter(curwin_save, TRUE);
popup_close(wp->w_id, FALSE);
}
#endif
}
#if defined(FEAT_QUICKFIX) && defined(FEAT_PROP_POPUP)
if (WIN_IS_POPUP(curwin))
// something went wrong, still in popup, but it can't have focus
win_enter(firstwin, TRUE);
#endif
erret:
#if defined(FEAT_QUICKFIX)
g_do_tagpreview = 0; // For next time
#endif
vim_free(lbuf);
vim_free(pbuf);
vim_free(tofree_fname);
vim_free(full_fname);
return retval;
}
/*
* If "expand" is TRUE, expand wildcards in fname.
* If 'tagrelative' option set, change fname (name of file containing tag)
* according to tag_fname (name of tag file containing fname).
* Returns a pointer to allocated memory (or NULL when out of memory).
*/
static char_u *
expand_tag_fname(char_u *fname, char_u *tag_fname, int expand)
{
char_u *p;
char_u *retval;
char_u *expanded_fname = NULL;
expand_T xpc;
/*
* Expand file name (for environment variables) when needed.
*/
if (expand && mch_has_wildcard(fname))
{
ExpandInit(&xpc);
xpc.xp_context = EXPAND_FILES;
expanded_fname = ExpandOne(&xpc, fname, NULL,
WILD_LIST_NOTFOUND|WILD_SILENT, WILD_EXPAND_FREE);
if (expanded_fname != NULL)
fname = expanded_fname;
}
if ((p_tr || curbuf->b_help)
&& !vim_isAbsName(fname)
&& (p = gettail(tag_fname)) != tag_fname)
{
retval = alloc(MAXPATHL);
if (retval != NULL)
{
STRCPY(retval, tag_fname);
vim_strncpy(retval + (p - tag_fname), fname,
MAXPATHL - (p - tag_fname) - 1);
/*
* Translate names like "src/a/../b/file.c" into "src/b/file.c".
*/
simplify_filename(retval);
}
}
else
retval = vim_strsave(fname);
vim_free(expanded_fname);
return retval;
}
/*
* Check if we have a tag for the buffer with name "buf_ffname".
* This is a bit slow, because of the full path compare in fullpathcmp().
* Return TRUE if tag for file "fname" if tag file "tag_fname" is for current
* file.
*/
static int
test_for_current(
#ifdef FEAT_EMACS_TAGS
int is_etag,
#endif
char_u *fname,
char_u *fname_end,
char_u *tag_fname,
char_u *buf_ffname)
{
int c;
int retval = FALSE;
char_u *fullname;
if (buf_ffname != NULL) // if the buffer has a name
{
#ifdef FEAT_EMACS_TAGS
if (is_etag)
c = 0; // to shut up GCC
else
#endif
{
c = *fname_end;
*fname_end = NUL;
}
fullname = expand_tag_fname(fname, tag_fname, TRUE);
if (fullname != NULL)
{
retval = (fullpathcmp(fullname, buf_ffname, TRUE, TRUE) & FPC_SAME);
vim_free(fullname);
}
#ifdef FEAT_EMACS_TAGS
if (!is_etag)
#endif
*fname_end = c;
}
return retval;
}
/*
* Find the end of the tagaddress.
* Return OK if ";\"" is following, FAIL otherwise.
*/
static int
find_extra(char_u **pp)
{
char_u *str = *pp;
char_u first_char = **pp;
// Repeat for addresses separated with ';'
for (;;)
{
if (VIM_ISDIGIT(*str))
str = skipdigits(str + 1);
else if (*str == '/' || *str == '?')
{
str = skip_regexp(str + 1, *str, FALSE);
if (*str != first_char)
str = NULL;
else
++str;
}
else
{
// not a line number or search string, look for terminator.
str = (char_u *)strstr((char *)str, "|;\"");
if (str != NULL)
{
++str;
break;
}
}
if (str == NULL || *str != ';'
|| !(VIM_ISDIGIT(str[1]) || str[1] == '/' || str[1] == '?'))
break;
++str; // skip ';'
first_char = *str;
}
if (str != NULL && STRNCMP(str, ";\"", 2) == 0)
{
*pp = str;
return OK;
}
return FAIL;
}
/*
* Free a single entry in a tag stack
*/
static void
tagstack_clear_entry(taggy_T *item)
{
VIM_CLEAR(item->tagname);
VIM_CLEAR(item->user_data);
}
int
expand_tags(
int tagnames, // expand tag names
char_u *pat,
int *num_file,
char_u ***file)
{
int i;
int extra_flag;
char_u *name_buf;
size_t name_buf_size = 100;
tagptrs_T t_p;
int ret;
name_buf = alloc(name_buf_size);
if (name_buf == NULL)
return FAIL;
if (tagnames)
extra_flag = TAG_NAMES;
else
extra_flag = 0;
if (pat[0] == '/')
ret = find_tags(pat + 1, num_file, file,
TAG_REGEXP | extra_flag | TAG_VERBOSE | TAG_NO_TAGFUNC,
TAG_MANY, curbuf->b_ffname);
else
ret = find_tags(pat, num_file, file,
TAG_REGEXP | extra_flag | TAG_VERBOSE | TAG_NO_TAGFUNC | TAG_NOIC,
TAG_MANY, curbuf->b_ffname);
if (ret == OK && !tagnames)
{
// Reorganize the tags for display and matching as strings of:
// "<tagname>\0<kind>\0<filename>\0"
for (i = 0; i < *num_file; i++)
{
size_t len;
parse_match((*file)[i], &t_p);
len = t_p.tagname_end - t_p.tagname;
if (len > name_buf_size - 3)
{
char_u *buf;
name_buf_size = len + 3;
buf = vim_realloc(name_buf, name_buf_size);
if (buf == NULL)
{
vim_free(name_buf);
return FAIL;
}
name_buf = buf;
}
mch_memmove(name_buf, t_p.tagname, len);
name_buf[len++] = 0;
name_buf[len++] = (t_p.tagkind != NULL && *t_p.tagkind)
? *t_p.tagkind : 'f';
name_buf[len++] = 0;
mch_memmove((*file)[i] + len, t_p.fname,
t_p.fname_end - t_p.fname);
(*file)[i][len + (t_p.fname_end - t_p.fname)] = 0;
mch_memmove((*file)[i], name_buf, len);
}
}
vim_free(name_buf);
return ret;
}
#if defined(FEAT_EVAL) || defined(PROTO)
/*
* Add a tag field to the dictionary "dict".
* Return OK or FAIL.
*/
static int
add_tag_field(
dict_T *dict,
char *field_name,
char_u *start, // start of the value
char_u *end) // after the value; can be NULL
{
char_u *buf;
int len = 0;
int retval;
// check that the field name doesn't exist yet
if (dict_has_key(dict, field_name))
{
if (p_verbose > 0)
{
verbose_enter();
smsg(_("Duplicate field name: %s"), field_name);
verbose_leave();
}
return FAIL;
}
buf = alloc(MAXPATHL);
if (buf == NULL)
return FAIL;
if (start != NULL)
{
if (end == NULL)
{
end = start + STRLEN(start);
while (end > start && (end[-1] == '\r' || end[-1] == '\n'))
--end;
}
len = (int)(end - start);
if (len > MAXPATHL - 1)
len = MAXPATHL - 1;
vim_strncpy(buf, start, len);
}
buf[len] = NUL;
retval = dict_add_string(dict, field_name, buf);
vim_free(buf);
return retval;
}
/*
* Add the tags matching the specified pattern "pat" to the list "list"
* as a dictionary. Use "buf_fname" for priority, unless NULL.
*/
int
get_tags(list_T *list, char_u *pat, char_u *buf_fname)
{
int num_matches, i, ret;
char_u **matches, *p;
char_u *full_fname;
dict_T *dict;
tagptrs_T tp;
long is_static;
ret = find_tags(pat, &num_matches, &matches,
TAG_REGEXP | TAG_NOIC, (int)MAXCOL, buf_fname);
if (ret != OK || num_matches <= 0)
return ret;
for (i = 0; i < num_matches; ++i)
{
if (parse_match(matches[i], &tp) == FAIL)
{
vim_free(matches[i]);
continue;
}
is_static = test_for_static(&tp);
// Skip pseudo-tag lines.
if (STRNCMP(tp.tagname, "!_TAG_", 6) == 0)
{
vim_free(matches[i]);
continue;
}
if ((dict = dict_alloc()) == NULL)
{
ret = FAIL;
vim_free(matches[i]);
break;
}
if (list_append_dict(list, dict) == FAIL)
ret = FAIL;
full_fname = tag_full_fname(&tp);
if (add_tag_field(dict, "name", tp.tagname, tp.tagname_end) == FAIL
|| add_tag_field(dict, "filename", full_fname,
NULL) == FAIL
|| add_tag_field(dict, "cmd", tp.command,
tp.command_end) == FAIL
|| add_tag_field(dict, "kind", tp.tagkind,
tp.tagkind_end) == FAIL
|| dict_add_number(dict, "static", is_static) == FAIL)
ret = FAIL;
vim_free(full_fname);
if (tp.command_end != NULL)
{
for (p = tp.command_end + 3;
*p != NUL && *p != '\n' && *p != '\r'; MB_PTR_ADV(p))
{
if (p == tp.tagkind || (p + 5 == tp.tagkind
&& STRNCMP(p, "kind:", 5) == 0))
// skip "kind:<kind>" and "<kind>"
p = tp.tagkind_end - 1;
else if (STRNCMP(p, "file:", 5) == 0)
// skip "file:" (static tag)
p += 4;
else if (!VIM_ISWHITE(*p))
{
char_u *s, *n;
int len;
// Add extra field as a dict entry. Fields are
// separated by Tabs.
n = p;
while (*p != NUL && *p >= ' ' && *p < 127 && *p != ':')
++p;
len = (int)(p - n);
if (*p == ':' && len > 0)
{
s = ++p;
while (*p != NUL && *p >= ' ')
++p;
n[len] = NUL;
if (add_tag_field(dict, (char *)n, s, p) == FAIL)
ret = FAIL;
n[len] = ':';
}
else
// Skip field without colon.
while (*p != NUL && *p >= ' ')
++p;
if (*p == NUL)
break;
}
}
}
vim_free(matches[i]);
}
vim_free(matches);
return ret;
}
/*
* Return information about 'tag' in dict 'retdict'.
*/
static void
get_tag_details(taggy_T *tag, dict_T *retdict)
{
list_T *pos;
fmark_T *fmark;
dict_add_string(retdict, "tagname", tag->tagname);
dict_add_number(retdict, "matchnr", tag->cur_match + 1);
dict_add_number(retdict, "bufnr", tag->cur_fnum);
if (tag->user_data)
dict_add_string(retdict, "user_data", tag->user_data);
if ((pos = list_alloc_id(aid_tagstack_from)) == NULL)
return;
dict_add_list(retdict, "from", pos);
fmark = &tag->fmark;
list_append_number(pos,
(varnumber_T)(fmark->fnum != -1 ? fmark->fnum : 0));
list_append_number(pos, (varnumber_T)fmark->mark.lnum);
list_append_number(pos, (varnumber_T)(fmark->mark.col == MAXCOL ?
MAXCOL : fmark->mark.col + 1));
list_append_number(pos, (varnumber_T)fmark->mark.coladd);
}
/*
* Return the tag stack entries of the specified window 'wp' in dictionary
* 'retdict'.
*/
void
get_tagstack(win_T *wp, dict_T *retdict)
{
list_T *l;
int i;
dict_T *d;
dict_add_number(retdict, "length", wp->w_tagstacklen);
dict_add_number(retdict, "curidx", wp->w_tagstackidx + 1);
l = list_alloc_id(aid_tagstack_items);
if (l == NULL)
return;
dict_add_list(retdict, "items", l);
for (i = 0; i < wp->w_tagstacklen; i++)
{
if ((d = dict_alloc_id(aid_tagstack_details)) == NULL)
return;
list_append_dict(l, d);
get_tag_details(&wp->w_tagstack[i], d);
}
}
/*
* Free all the entries in the tag stack of the specified window
*/
static void
tagstack_clear(win_T *wp)
{
int i;
// Free the current tag stack
for (i = 0; i < wp->w_tagstacklen; ++i)
tagstack_clear_entry(&wp->w_tagstack[i]);
wp->w_tagstacklen = 0;
wp->w_tagstackidx = 0;
}
/*
* Remove the oldest entry from the tag stack and shift the rest of
* the entries to free up the top of the stack.
*/
static void
tagstack_shift(win_T *wp)
{
taggy_T *tagstack = wp->w_tagstack;
int i;
tagstack_clear_entry(&tagstack[0]);
for (i = 1; i < wp->w_tagstacklen; ++i)
tagstack[i - 1] = tagstack[i];
wp->w_tagstacklen--;
}
/*
* Push a new item to the tag stack
*/
static void
tagstack_push_item(
win_T *wp,
char_u *tagname,
int cur_fnum,
int cur_match,
pos_T mark,
int fnum,
char_u *user_data)
{
taggy_T *tagstack = wp->w_tagstack;
int idx = wp->w_tagstacklen; // top of the stack
// if the tagstack is full: remove the oldest entry
if (idx >= TAGSTACKSIZE)
{
tagstack_shift(wp);
idx = TAGSTACKSIZE - 1;
}
wp->w_tagstacklen++;
tagstack[idx].tagname = tagname;
tagstack[idx].cur_fnum = cur_fnum;
tagstack[idx].cur_match = cur_match;
if (tagstack[idx].cur_match < 0)
tagstack[idx].cur_match = 0;
tagstack[idx].fmark.mark = mark;
tagstack[idx].fmark.fnum = fnum;
tagstack[idx].user_data = user_data;
}
/*
* Add a list of items to the tag stack in the specified window
*/
static void
tagstack_push_items(win_T *wp, list_T *l)
{
listitem_T *li;
dictitem_T *di;
dict_T *itemdict;
char_u *tagname;
pos_T mark;
int fnum;
// Add one entry at a time to the tag stack
FOR_ALL_LIST_ITEMS(l, li)
{
if (li->li_tv.v_type != VAR_DICT || li->li_tv.vval.v_dict == NULL)
continue; // Skip non-dict items
itemdict = li->li_tv.vval.v_dict;
// parse 'from' for the cursor position before the tag jump
if ((di = dict_find(itemdict, (char_u *)"from", -1)) == NULL)
continue;
if (list2fpos(&di->di_tv, &mark, &fnum, NULL, FALSE) != OK)
continue;
if ((tagname = dict_get_string(itemdict, "tagname", TRUE)) == NULL)
continue;
if (mark.col > 0)
mark.col--;
tagstack_push_item(wp, tagname,
(int)dict_get_number(itemdict, "bufnr"),
(int)dict_get_number(itemdict, "matchnr") - 1,
mark, fnum,
dict_get_string(itemdict, "user_data", TRUE));
}
}
/*
* Set the current index in the tag stack. Valid values are between 0
* and the stack length (inclusive).
*/
static void
tagstack_set_curidx(win_T *wp, int curidx)
{
wp->w_tagstackidx = curidx;
if (wp->w_tagstackidx < 0) // sanity check
wp->w_tagstackidx = 0;
if (wp->w_tagstackidx > wp->w_tagstacklen)
wp->w_tagstackidx = wp->w_tagstacklen;
}
/*
* Set the tag stack entries of the specified window.
* 'action' is set to one of:
* 'a' for append
* 'r' for replace
* 't' for truncate
*/
int
set_tagstack(win_T *wp, dict_T *d, int action)
{
dictitem_T *di;
list_T *l = NULL;
#ifdef FEAT_EVAL
// not allowed to alter the tag stack entries from inside tagfunc
if (tfu_in_use)
{
emsg(_(e_cannot_modify_tag_stack_within_tagfunc));
return FAIL;
}
#endif
if ((di = dict_find(d, (char_u *)"items", -1)) != NULL)
{
if (di->di_tv.v_type != VAR_LIST)
{
emsg(_(e_list_required));
return FAIL;
}
l = di->di_tv.vval.v_list;
}
if ((di = dict_find(d, (char_u *)"curidx", -1)) != NULL)
tagstack_set_curidx(wp, (int)tv_get_number(&di->di_tv) - 1);
if (action == 't') // truncate the stack
{
taggy_T *tagstack = wp->w_tagstack;
int tagstackidx = wp->w_tagstackidx;
int tagstacklen = wp->w_tagstacklen;
// delete all the tag stack entries above the current entry
while (tagstackidx < tagstacklen)
tagstack_clear_entry(&tagstack[--tagstacklen]);
wp->w_tagstacklen = tagstacklen;
}
if (l != NULL)
{
if (action == 'r') // replace the stack
tagstack_clear(wp);
tagstack_push_items(wp, l);
// set the current index after the last entry
wp->w_tagstackidx = wp->w_tagstacklen;
}
return OK;
}
#endif