diff options
| author | Eliseo Martínez <eliseomarmol@gmail.com> | 2014-05-12 02:25:17 +0200 |
|---|---|---|
| committer | Eliseo Martínez <eliseomarmol@gmail.com> | 2014-05-15 20:46:01 +0200 |
| commit | da51dc9cf202772f60bd2da975dbef257bd9237c (patch) | |
| tree | 5c16b93238a153f55634e9323077f30c8133970c /src/nvim/tag.c | |
| parent | ffe61e5ba1721340ca51d56bae3ddaca415fb5bc (diff) | |
| download | rneovim-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.c | 2933 |
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 |