📄 tag.c
字号:
/* vi:set ts=8 sts=4 sw=4:
*
* 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.
*/
/*
* Code to handle tags and the tag stack
*/
#if defined MSDOS || defined WIN32
# include <io.h> /* for lseek(), must be before vim.h */
#endif
#include "vim.h"
#ifdef HAVE_FCNTL_H
# include <fcntl.h> /* for lseek() */
#endif
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 of command */
char_u *tag_fname; /* file name of the tags file */
#ifdef EMACS_TAGS
int is_etag; /* TRUE for emacs tag */
#endif
char_u *tagkind; /* "kind:" value */
char_u *tagkind_end; /* end of tagkind */
};
/*
* The matching tags are first stored in ga_match[]. In which one depends on
* the priority of the match.
* At the end, 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_ST_CUR 4 /* icase static match in current file */
#define MT_IC_GL_CUR 5 /* icase global match in current file */
#define MT_IC_GL_OTH 6 /* icase global match in other file */
#define MT_IC_ST_OTH 7 /* icase 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 __ARGS((int l));
static int get_tagfname __ARGS((int first, char_u *buf));
static int jumpto_tag __ARGS((char_u *lbuf, int forceit));
#ifdef EMACS_TAGS
static int parse_tag_line __ARGS((char_u *lbuf, int is_etag, struct tag_pointers *tagp));
#else
static int parse_tag_line __ARGS((char_u *lbuf, struct tag_pointers *tagp));
#endif
static int test_for_static __ARGS((struct tag_pointers *));
static int parse_match __ARGS((char_u *lbuf, struct tag_pointers *tagp));
static char_u *expand_rel_name __ARGS((char_u *fname, char_u *tag_fname));
#ifdef EMACS_TAGS
static int test_for_current __ARGS((int, char_u *, char_u *, char_u *));
#else
static int test_for_current __ARGS((char_u *, char_u *, char_u *));
#endif
static int find_extra __ARGS((char_u **pp));
static char_u *bottommsg = (char_u *)"at bottom of tag stack";
static char_u *topmsg = (char_u *)"at top of tag stack";
/*
* 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
*
* for cscope, returns TRUE if we jumped to tag or aborted, FALSE otherwise
*/
int
do_tag(tag, type, count, forceit, verbose)
char_u *tag; /* tag (pattern) to jump to */
int type;
int count;
int forceit; /* :ta with ! */
int verbose; /* print "tag not found" message */
{
struct taggy *tagstack = curwin->w_tagstack;
int tagstackidx = curwin->w_tagstackidx;
int tagstacklen = curwin->w_tagstacklen;
int cur_match = 0;
int oldtagstackidx = tagstackidx;
int prev_num_matches;
int new_tag = FALSE;
int other_name;
int i, j, k;
int idx;
int ic;
char_u *p;
char_u *name;
int no_regexp = FALSE;
int error_cur_match = 0;
char_u *command_end;
int save_pos = FALSE;
struct filemark saved_fmark;
int taglen;
#ifdef USE_CSCOPE
int jumped_to_tag = FALSE;
#endif
struct tag_pointers tagp, tagp2;
int new_num_matches;
char_u **new_matches;
/* 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;
static char_u *matchname = NULL;
if (type == DT_HELP)
{
type = DT_TAG;
no_regexp = TRUE;
}
prev_num_matches = num_matches;
nofile_fname = NULL;
/* new pattern, add to the tag stack */
#ifdef USE_CSCOPE
if ((type == DT_TAG || type == DT_SELECT || type == DT_JUMP ||
type == DT_CSCOPE) && *tag)
#else
if ((type == DT_TAG || type == DT_SELECT || type == DT_JUMP) && *tag)
#endif
{
/*
* If the last used entry is not at the top, delete all tag stack
* entries above it.
*/
while (tagstackidx < tagstacklen)
vim_free(tagstack[--tagstacklen].tagname);
/* if the tagstack is full: remove oldest entry */
if (++tagstacklen > TAGSTACKSIZE)
{
tagstacklen = TAGSTACKSIZE;
vim_free(tagstack[0].tagname);
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)
{
--tagstacklen;
goto end_do_tag;
}
new_tag = TRUE;
save_pos = TRUE; /* save the cursor position below */
}
else
{
if (tagstacklen == 0) /* empty stack */
{
EMSG(e_tagstack);
goto end_do_tag;
}
if (type == DT_POP) /* go to older position */
{
if ((tagstackidx -= count) < 0)
{
emsg(bottommsg);
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(topmsg);
goto end_do_tag;
}
if (tagstack[tagstackidx].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(tagstack[tagstackidx].fmark.fnum,
tagstack[tagstackidx].fmark.mark.lnum,
GETF_SETMARK, forceit) == FAIL)
{
tagstackidx = oldtagstackidx; /* back to old posn */
goto end_do_tag;
}
}
else
curwin->w_cursor.lnum = tagstack[tagstackidx].fmark.mark.lnum;
curwin->w_cursor.col = tagstack[tagstackidx].fmark.mark.col;
curwin->w_set_curswant = TRUE;
/* remove the old list of matches */
FreeWild(num_matches, matches);
#ifdef USE_CSCOPE
cs_free_tags();
#endif
num_matches = 0;
goto end_do_tag;
}
if (type == DT_TAG) /* 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 postition.
*/
tagstackidx = tagstacklen - 1;
emsg(topmsg);
save_pos = FALSE;
}
else if (tagstackidx < 0) /* must have been count == 0 */
{
emsg(bottommsg);
tagstackidx = 0;
goto end_do_tag;
}
cur_match = tagstack[tagstackidx].cur_match;
new_tag = TRUE;
}
else /* go to other matching tag */
{
if (--tagstackidx < 0)
tagstackidx = 0;
cur_match = tagstack[tagstackidx].cur_match;
switch (type)
{
case DT_FIRST: cur_match = count - 1; break;
case DT_SELECT:
case DT_JUMP:
#ifdef USE_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)
cur_match = 0;
}
}
/*
* 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 */
curwin->w_tagstackidx = tagstackidx;
curwin->w_tagstacklen = tagstacklen;
if (type != DT_SELECT && type != DT_JUMP)
curwin->w_tagstack[tagstackidx].cur_match = cur_match;
/*
* Repeat searching for tags, when a file has not been found.
*/
for (;;)
{
/*
* When desired match not found yet, try to find it (and others).
*/
name = tagstack[tagstackidx].tagname;
other_name = (matchname == NULL || STRCMP(matchname, name) != 0);
if (new_tag
|| (cur_match >= num_matches && max_num_matches != MAXCOL)
|| other_name)
{
if (other_name)
{
vim_free(matchname);
matchname = vim_strsave(name);
}
if (type == DT_SELECT || type == DT_JUMP)
cur_match = MAXCOL - 1;
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 USE_CSCOPE
if (type == DT_CSCOPE)
flags = TAG_CSCOPE;
#endif
if (verbose)
flags |= TAG_VERBOSE;
if (find_tags(name, &new_num_matches, &new_matches, flags,
max_num_matches) == OK
&& num_matches < max_num_matches)
max_num_matches = MAXCOL; /* If less than max_num_matches
found: all matches found. */
/* 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)
{
/* Find the position of each old match in the new list. Need
* to use parse_match() to find the tag line. */
idx = 0;
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)
{
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)
EMSG("tag not found");
}
else
{
int ask_for_selection = FALSE;
#ifdef USE_CSCOPE
if (type == DT_CSCOPE && num_matches > 1)
{
cs_print_tags();
ask_for_selection = TRUE;
}
else
#endif
if (type == DT_SELECT || (type == DT_JUMP && num_matches > 1))
{
/*
* List all the matching tags.
* 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 = tagp.tagname_end - tagp.tagname + 2;
if (taglen < 18)
taglen = 18;
if (taglen > Columns - 25)
taglen = MAXCOL;
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; ++i)
{
parse_match(matches[i], &tagp);
if (!new_tag && i == tagstack[tagstackidx].cur_match)
*IObuff = '>';
else
*IObuff = ' ';
sprintf((char *)IObuff + 1, "%2d %s ", i + 1,
mt_names[matches[i][0] & MT_MASK]);
msg_puts(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_D));
msg_putchar(' ');
taglen_advance(taglen);
/* If the file name is long, truncate it and put "..." in
* the middle */
msg_puts_long_len_attr(tagp.fname,
(int)(tagp.fname_end - tagp.fname), hl_attr(HLF_D));
if (msg_col > 0)
msg_putchar('\n');
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 */
while (*p && *p != '\r' && *p != '\n')
{
if (msg_col + charsize(*p) >= Columns)
{
msg_putchar('\n');
msg_advance(15);
}
msg_puts_attr(transchar(*p), hl_attr(HLF_CM));
++p;
if (*p == TAB)
{
msg_puts_attr((char_u *)" ",
hl_attr(HLF_CM));
break;
}
}
}
if (msg_col > 15)
{
msg_putchar('\n');
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 == '?')
{
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -