aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/tag.c
diff options
context:
space:
mode:
authorEliseo Martínez <eliseomarmol@gmail.com>2014-05-12 02:25:17 +0200
committerEliseo Martínez <eliseomarmol@gmail.com>2014-05-15 20:46:01 +0200
commitda51dc9cf202772f60bd2da975dbef257bd9237c (patch)
tree5c16b93238a153f55634e9323077f30c8133970c /src/nvim/tag.c
parentffe61e5ba1721340ca51d56bae3ddaca415fb5bc (diff)
downloadrneovim-da51dc9cf202772f60bd2da975dbef257bd9237c.tar.gz
rneovim-da51dc9cf202772f60bd2da975dbef257bd9237c.tar.bz2
rneovim-da51dc9cf202772f60bd2da975dbef257bd9237c.zip
Introduce nvim namespace: Move files.
Move files from src/ to src/nvim/. - src/nvim/ becomes the new root dir for nvim executable sources. - src/libnvim/ is planned to become root dir of the neovim library.
Diffstat (limited to 'src/nvim/tag.c')
-rw-r--r--src/nvim/tag.c2933
1 files changed, 2933 insertions, 0 deletions
diff --git a/src/nvim/tag.c b/src/nvim/tag.c
new file mode 100644
index 0000000000..bfef5366d9
--- /dev/null
+++ b/src/nvim/tag.c
@@ -0,0 +1,2933 @@
+/*
+ * 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 <string.h>
+
+#include "vim.h"
+#include "tag.h"
+#include "buffer.h"
+#include "charset.h"
+#include "edit.h"
+#include "eval.h"
+#include "ex_cmds.h"
+#include "ex_cmds2.h"
+#include "ex_docmd.h"
+#include "ex_getln.h"
+#include "fileio.h"
+#include "fold.h"
+#include "if_cscope.h"
+#include "mark.h"
+#include "mbyte.h"
+#include "message.h"
+#include "misc1.h"
+#include "misc2.h"
+#include "file_search.h"
+#include "garray.h"
+#include "memory.h"
+#include "move.h"
+#include "option.h"
+#include "os_unix.h"
+#include "path.h"
+#include "quickfix.h"
+#include "regexp.h"
+#include "screen.h"
+#include "search.h"
+#include "term.h"
+#include "ui.h"
+#include "window.h"
+#include "os/os.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 */
+ char_u *tagkind; /* "kind:" value */
+ char_u *tagkind_end; /* end of tagkind */
+} tagptrs_T;
+
+/*
+ * 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(int l);
+
+static int jumpto_tag(char_u *lbuf, int forceit, int keep_help);
+static int parse_tag_line(char_u *lbuf, tagptrs_T *tagp);
+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);
+static int test_for_current(char_u *, char_u *, char_u *, char_u *);
+static int find_extra(char_u **pp);
+
+static char_u *bottommsg = (char_u *)N_("E555: at bottom of tag stack");
+static char_u *topmsg = (char_u *)N_("E556: at top of tag stack");
+
+static char_u *tagmatchname = NULL; /* name of last used tag */
+
+/*
+ * We use ftello() here, if available. It returns off_t instead of long,
+ * which helps if long is 32 bit and off_t is 64 bit.
+ * We assume that when fseeko() is available then ftello() is too.
+ */
+#ifdef HAVE_FSEEKO
+# define ftell ftello
+#endif
+
+/*
+ * Tag for preview window is remembered separately, to avoid messing up the
+ * normal tagstack.
+ */
+static taggy_T ptag_entry = {NULL, {INIT_POS_T(0, 0, 0), 0}, 0, 0};
+
+/*
+ * 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 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;
+ fmark_T saved_fmark;
+ int taglen;
+ int jumped_to_tag = FALSE;
+ tagptrs_T tagp, tagp2;
+ int new_num_matches;
+ char_u **new_matches;
+ int attr;
+ int use_tagstack;
+ int skip_msg = FALSE;
+ char_u *buf_ffname = curbuf->b_ffname; /* name to use for
+ priority computation */
+
+ /* 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 EXITFREE
+ if (type == DT_FREE) {
+ /* remove the list of matches */
+ FreeWild(num_matches, matches);
+ cs_free_tags();
+ num_matches = 0;
+ return FALSE;
+ }
+#endif
+
+ if (type == DT_HELP) {
+ type = DT_TAG;
+ no_regexp = TRUE;
+ }
+
+ prev_num_matches = num_matches;
+ free_string_option(nofile_fname);
+ nofile_fname = NULL;
+
+ clearpos(&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;
+ } else {
+ if (g_do_tagpreview != 0)
+ use_tagstack = FALSE;
+ else
+ use_tagstack = TRUE;
+
+ /* new pattern, add to the tag stack */
+ if (*tag != NUL
+ && (type == DT_TAG || type == DT_SELECT || type == DT_JUMP
+ || type == DT_LTAG
+ || type == DT_CSCOPE
+ )) {
+ 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 {
+ free(ptag_entry.tagname);
+ if ((ptag_entry.tagname = vim_strsave(tag)) == NULL)
+ goto end_do_tag;
+ }
+ } else {
+ /*
+ * If the last used entry is not at the top, delete all tag
+ * stack entries above it.
+ */
+ while (tagstackidx < tagstacklen)
+ free(tagstack[--tagstacklen].tagname);
+
+ /* if the tagstack is full: remove oldest entry */
+ if (++tagstacklen > TAGSTACKSIZE) {
+ tagstacklen = TAGSTACKSIZE;
+ 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) {
+ 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 (
+ g_do_tagpreview != 0 ? ptag_entry.tagname == NULL :
+ tagstacklen == 0) {
+ /* empty stack */
+ EMSG(_(e_tagstack));
+ goto end_do_tag;
+ }
+
+ if (type == DT_POP) { /* go to older position */
+ int old_KeyTyped = KeyTyped;
+ 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;
+ }
+
+ /* 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();
+ if ((fdo_flags & FDO_TAG) && old_KeyTyped)
+ foldOpenCursor();
+
+ /* remove the old list of matches */
+ FreeWild(num_matches, matches);
+ cs_free_tags();
+ num_matches = 0;
+ tag_freematch();
+ goto end_do_tag;
+ }
+
+ if (type == DT_TAG
+ || type == DT_LTAG
+ ) {
+ if (g_do_tagpreview != 0) {
+ cur_match = ptag_entry.cur_match;
+ cur_fnum = ptag_entry.cur_fnum;
+ } else {
+ /* ":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(_(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;
+ 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 (g_do_tagpreview != 0) {
+ cur_match = ptag_entry.cur_match;
+ cur_fnum = ptag_entry.cur_fnum;
+ } else {
+ 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:
+ case DT_CSCOPE:
+ 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(_("E425: Cannot go before first matching tag"));
+ skip_msg = TRUE;
+ cur_match = 0;
+ cur_fnum = curbuf->b_fnum;
+ }
+ }
+ }
+
+ 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 {
+ /*
+ * 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 (;; ) {
+ /*
+ * When desired match not found yet, try to find it (and others).
+ */
+ if (use_tagstack)
+ name = tagstack[tagstackidx].tagname;
+ else if (g_do_tagpreview != 0)
+ name = ptag_entry.tagname;
+ 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) {
+ free(tagmatchname);
+ tagmatchname = vim_strsave(name);
+ }
+
+ /*
+ * If a count is supplied to the ":tag <name>" command, then
+ * jump to count'th matching tag.
+ */
+ if (type == DT_TAG && *tag != NUL && count > 0)
+ cur_match = count - 1;
+
+ if (type == DT_SELECT || type == DT_JUMP
+ || type == DT_LTAG
+ )
+ 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;
+
+ if (type == DT_CSCOPE)
+ flags = TAG_CSCOPE;
+ if (verbose)
+ flags |= TAG_VERBOSE;
+ 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. */
+
+ /* 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)
+ EMSG2(_("E426: tag not found: %s"), name);
+ g_do_tagpreview = 0;
+ } else {
+ int ask_for_selection = FALSE;
+
+ if (type == DT_CSCOPE && num_matches > 1) {
+ cs_print_tags();
+ ask_for_selection = TRUE;
+ } else 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 = (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 && (
+ (g_do_tagpreview != 0
+ && i == ptag_entry.cur_match) ||
+ (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(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_puts_long_attr(p, hl_attr(HLF_D));
+ 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((char_u *)" ", 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 */
+ ask_for_selection = TRUE;
+ } else if (type == DT_LTAG) {
+ list_T *list;
+ char_u tag_name[128 + 1];
+ char_u *fname;
+ char_u *cmd;
+
+ /*
+ * Add the matching tags to the location list for the current
+ * window.
+ */
+
+ fname = alloc(MAXPATHL + 1);
+ cmd = alloc(CMDBUFFSIZE + 1);
+ list = list_alloc();
+
+ 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);
+ free(p);
+
+ /*
+ * Get the line number or the search pattern used to locate
+ * the tag.
+ */
+ lnum = 0;
+ if (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;
+ list_append_dict(list, dict);
+
+ dict_add_nr_str(dict, "text", 0L, tag_name);
+ dict_add_nr_str(dict, "filename", 0L, fname);
+ dict_add_nr_str(dict, "lnum", lnum, NULL);
+ if (lnum == 0)
+ dict_add_nr_str(dict, "pattern", 0L, cmd);
+ }
+
+ vim_snprintf((char *)IObuff, IOSIZE, "ltag %s", tag);
+ set_errorlist(curwin, list, ' ', IObuff);
+
+ list_free(list, TRUE);
+ free(fname);
+ free(cmd);
+
+ cur_match = 0; /* Jump to the first tag */
+ }
+
+ 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;
+ }
+ cs_free_tags();
+ jumped_to_tag = TRUE;
+ 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(_("E427: There is only one matching tag"));
+ else
+ EMSG(_("E428: Cannot go beyond last matching tag"));
+ skip_msg = TRUE;
+ }
+ cur_match = num_matches - 1;
+ }
+ if (use_tagstack) {
+ tagstack[tagstackidx].cur_match = cur_match;
+ tagstack[tagstackidx].cur_fnum = cur_fnum;
+ ++tagstackidx;
+ } else if (g_do_tagpreview != 0) {
+ ptag_entry.cur_match = cur_match;
+ ptag_entry.cur_fnum = cur_fnum;
+ }
+
+ /*
+ * 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((char_u *)_("File \"%s\" does not exist"), nofile_fname);
+
+
+ ic = (matches[cur_match][0] & MT_IC_OFF);
+ if (type != DT_SELECT && type != DT_JUMP
+ && type != DT_CSCOPE
+ && (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(IObuff, hl_attr(HLF_W));
+ else
+ msg(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(1000L, TRUE);
+ }
+ }
+
+ /* 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);
+
+ /*
+ * Jump to the desired match.
+ */
+ i = jumpto_tag(matches[cur_match], forceit, type != DT_CSCOPE);
+
+ set_vim_var_string(VV_SWAPCOMMAND, NULL, -1);
+
+ 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;
+ }
+ EMSG2(_("E429: File \"%s\" 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;
+ jumped_to_tag = TRUE;
+ }
+ }
+ 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 */
+
+ return jumped_to_tag;
+}
+
+/*
+ * Free cached tags.
+ */
+void tag_freematch(void)
+{
+ free(tagmatchname);
+ tagmatchname = NULL;
+}
+
+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)
+{
+ 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');
+ sprintf((char *)IObuff, "%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);
+ free(name);
+ }
+ out_flush(); /* show one line at a time */
+ }
+ if (tagstackidx == tagstacklen) /* idx at top of stack */
+ MSG_PUTS("\n>");
+}
+
+/* When not using a CR for line separator, use vim_fgets() to read tag lines.
+ * For the Mac use tag_fgets(). It can handle any line separator, but is much
+ * slower than vim_fgets().
+ */
+#ifndef USE_CR
+# define tag_fgets vim_fgets
+#endif
+
+static int tag_strnicmp(char_u *s1, char_u *s2, size_t len);
+
+/*
+ * 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[] */
+ regmat