⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 tag.c

📁 VIM文本编辑器
💻 C
📖 第 1 页 / 共 5 页
字号:
/* 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 + -