| /* 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. |
| * 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 */ |
| #ifdef FEAT_EMACS_TAGS |
| int is_etag; /* TRUE for emacs tag */ |
| #endif |
| 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 __ARGS((int l)); |
| |
| static int jumpto_tag __ARGS((char_u *lbuf, int forceit, int keep_help)); |
| #ifdef FEAT_EMACS_TAGS |
| static int parse_tag_line __ARGS((char_u *lbuf, int is_etag, tagptrs_T *tagp)); |
| #else |
| static int parse_tag_line __ARGS((char_u *lbuf, tagptrs_T *tagp)); |
| #endif |
| static int test_for_static __ARGS((tagptrs_T *)); |
| static int parse_match __ARGS((char_u *lbuf, tagptrs_T *tagp)); |
| static char_u *tag_full_fname __ARGS((tagptrs_T *tagp)); |
| static char_u *expand_tag_fname __ARGS((char_u *fname, char_u *tag_fname, int expand)); |
| #ifdef FEAT_EMACS_TAGS |
| static int test_for_current __ARGS((int, char_u *, char_u *, char_u *, char_u *)); |
| #else |
| static int test_for_current __ARGS((char_u *, char_u *, char_u *, char_u *)); |
| #endif |
| static int find_extra __ARGS((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 |
| |
| #if defined(FEAT_WINDOWS) && defined(FEAT_QUICKFIX) |
| /* |
| * 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}; |
| #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(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 */ |
| { |
| 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; |
| #ifdef FEAT_CSCOPE |
| int jumped_to_tag = FALSE; |
| #endif |
| 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); |
| # ifdef FEAT_CSCOPE |
| cs_free_tags(); |
| # endif |
| 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 defined(FEAT_WINDOWS) && 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_WINDOWS) && 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 |
| { |
| vim_free(ptag_entry.tagname); |
| 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) |
| 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) |
| { |
| 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_WINDOWS) && defined(FEAT_QUICKFIX) |
| g_do_tagpreview != 0 ? ptag_entry.tagname == NULL : |
| #endif |
| tagstacklen == 0) |
| { |
| /* empty stack */ |
| EMSG(_(e_tagstack)); |
| 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(_(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(); |
| #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_WINDOWS) && 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(_(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 defined(FEAT_WINDOWS) && 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(_("E425: Cannot go before first matching tag")); |
| skip_msg = TRUE; |
| cur_match = 0; |
| cur_fnum = curbuf->b_fnum; |
| } |
| } |
| } |
| |
| #if defined(FEAT_WINDOWS) && 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 (;;) |
| { |
| /* |
| * When desired match not found yet, try to find it (and others). |
| */ |
| if (use_tagstack) |
| name = tagstack[tagstackidx].tagname; |
| #if defined(FEAT_WINDOWS) && 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 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 |
| #if defined(FEAT_QUICKFIX) |
| || type == DT_LTAG |
| #endif |
| ) |
| 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 FEAT_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, 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); |
| #if defined(FEAT_WINDOWS) && 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_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 && ( |
| #if defined(FEAT_WINDOWS) && 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(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)); |
| 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((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; |
| } |
| #if defined(FEAT_QUICKFIX) && defined(FEAT_EVAL) |
| 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(); |
| if (list == NULL || fname == NULL || cmd == NULL) |
| { |
| vim_free(cmd); |
| vim_free(fname); |
| if (list != NULL) |
| list_free(list, TRUE); |
| goto end_do_tag; |
| } |
| |
| 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 (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_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); |
| vim_free(fname); |
| vim_free(cmd); |
| |
| 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(_("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; |
| } |
| #if defined(FEAT_WINDOWS) && 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((char_u *)_("File \"%s\" does not exist"), nofile_fname); |
| |
| |
| ic = (matches[cur_match][0] & MT_IC_OFF); |
| if (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(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); |
| } |
| } |
| |
| #ifdef FEAT_AUTOCMD |
| /* 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); |
| |
| #ifdef FEAT_AUTOCMD |
| 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; |
| } |
| 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; |
| #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; |
| #ifdef FEAT_WINDOWS |
| postponed_split = 0; /* don't split next time */ |
| #endif |
| |
| #ifdef FEAT_CSCOPE |
| return jumped_to_tag; |
| #else |
| return FALSE; |
| #endif |
| } |
| |
| /* |
| * Free cached tags. |
| */ |
| void |
| tag_freematch() |
| { |
| vim_free(tagmatchname); |
| tagmatchname = NULL; |
| } |
| |
| static void |
| taglen_advance(l) |
| int l; |
| { |
| if (l == MAXCOL) |
| { |
| msg_putchar('\n'); |
| msg_advance(24); |
| } |
| else |
| msg_advance(13 + l); |
| } |
| |
| /* |
| * Print the tag stack |
| */ |
| void |
| do_tags(eap) |
| 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'); |
| 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); |
| vim_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 |
| |
| #ifdef FEAT_TAG_BINS |
| static int tag_strnicmp __ARGS((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(s1, s2, len) |
| 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 */ |
| } |
| #endif |
| |
| /* |
| * 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; |
| |
| static void prepare_pats __ARGS((pat_T *pats, int has_re)); |
| |
| /* |
| * Extract info from the tag search pattern "pats->pat". |
| */ |
| static void |
| prepare_pats(pats, has_re) |
| 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 *)(p_magic ? ".[~*\\$" : "\\$"), |
| 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, p_magic ? RE_MAGIC : 0); |
| else |
| pats->regmatch.regprog = NULL; |
| } |
| |
| /* |
| * 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. |
| * |
| * There is a priority in 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 |
| */ |
| int |
| find_tags(pat, num_matches, matchesp, flags, mincount, buf_ffname) |
| 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 */ |
| { |
| FILE *fp; |
| char_u *lbuf; /* line buffer */ |
| int lbuf_size = LSIZE; /* length of lbuf */ |
| char_u *tag_fname; /* name of tag file */ |
| tagname_T tn; /* info for get_tagfname() */ |
| int first_file; /* trying first tag file */ |
| tagptrs_T tagp; |
| int did_open = FALSE; /* did open a tag file */ |
| int stop_searching = FALSE; /* stop when match found or error */ |
| int retval = FAIL; /* return value */ |
| int is_static; /* current tag line is static */ |
| int is_current; /* file name matches */ |
| int eof = FALSE; /* found end-of-file */ |
| char_u *p; |
| char_u *s; |
| int i; |
| #ifdef FEAT_TAG_BINS |
| int tag_file_sorted = NUL; /* !_TAG_FILE_SORTED value */ |
| struct tag_search_info /* Binary search file offsets */ |
| { |
| 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 */ |
| } search_info; |
| off_t filesize; |
| int tagcmp; |
| off_t offset; |
| int round; |
| #endif |
| enum |
| { |
| TS_START, /* at start of file */ |
| TS_LINEAR /* linear searching forward, till EOF */ |
| #ifdef FEAT_TAG_BINS |
| , TS_BINARY, /* binary searching */ |
| TS_SKIP_BACK, /* skipping backwards */ |
| TS_STEP_FORWARD /* stepping forwards */ |
| #endif |
| } state; /* Current search state */ |
| |
| int cmplen; |
| int match; /* matches */ |
| int match_no_ic = 0;/* matches with rm_ic == FALSE */ |
| int match_re; /* match with regexp */ |
| int matchoff = 0; |
| int save_emsg_off; |
| |
| #ifdef FEAT_EMACS_TAGS |
| /* |
| * Stack for included emacs-tags file. |
| * It has a fixed size, to truncate cyclic includes. jw |
| */ |
| # define INCSTACK_SIZE 42 |
| struct |
| { |
| FILE *fp; |
| char_u *etag_fname; |
| } incstack[INCSTACK_SIZE]; |
| |
| int incstack_idx = 0; /* index in incstack */ |
| char_u *ebuf; /* additional buffer for etag fname */ |
| int is_etag; /* current file is emaces style */ |
| #endif |
| |
| struct match_found |
| { |
| int len; /* nr of chars of match[] to be compared */ |
| char_u match[1]; /* actually longer */ |
| } *mfp, *mfp2; |
| garray_T ga_match[MT_COUNT]; |
| int match_count = 0; /* number of matches found */ |
| char_u **matches; |
| int mtt; |
| int help_save; |
| #ifdef FEAT_MULTI_LANG |
| int help_pri = 0; |
| char_u *help_lang_find = NULL; /* lang to be found */ |
| char_u help_lang[3]; /* lang of current tags file */ |
| char_u *saved_pat = NULL; /* copy of pat[] */ |
| #endif |
| |
| pat_T orgpat; /* holds unconverted pattern info */ |
| #ifdef FEAT_MBYTE |
| vimconv_T vimconv; |
| #endif |
| |
| #ifdef FEAT_TAG_BINS |
| int findall = (mincount == MAXCOL || mincount == TAG_MANY); |
| /* find all matching tags */ |
| int sort_error = FALSE; /* tags file not sorted */ |
| int linear; /* do a linear search */ |
| int sortic = FALSE; /* tag file sorted in nocase */ |
| #endif |
| int line_error = FALSE; /* syntax error */ |
| int has_re = (flags & TAG_REGEXP); /* regexp used */ |
| int help_only = (flags & TAG_HELP); |
| int name_only = (flags & TAG_NAMES); |
| int noic = (flags & TAG_NOIC); |
| int get_it_again = FALSE; |
| #ifdef FEAT_CSCOPE |
| int use_cscope = (flags & TAG_CSCOPE); |
| #endif |
| int verbose = (flags & TAG_VERBOSE); |
| |
| help_save = curbuf->b_help; |
| orgpat.pat = pat; |
| #ifdef FEAT_MBYTE |
| vimconv.vc_type = CONV_NONE; |
| #endif |
| |
| /* |
| * Allocate memory for the buffers that are used |
| */ |
| lbuf = alloc(lbuf_size); |
| tag_fname = alloc(MAXPATHL + 1); |
| #ifdef FEAT_EMACS_TAGS |
| ebuf = alloc(LSIZE); |
| #endif |
| for (mtt = 0; mtt < MT_COUNT; ++mtt) |
| ga_init2(&ga_match[mtt], (int)sizeof(struct match_found *), 100); |
| |
| /* check for out of memory situation */ |
| if (lbuf == NULL || tag_fname == NULL |
| #ifdef FEAT_EMACS_TAGS |
| || ebuf == NULL |
| #endif |
| ) |
| goto findtag_end; |
| |
| #ifdef FEAT_CSCOPE |
| STRCPY(tag_fname, "from cscope"); /* for error messages */ |
| #endif |
| |
| /* |
| * Initialize a few variables |
| */ |
| if (help_only) /* want tags from help file */ |
| curbuf->b_help = TRUE; /* will be restored later */ |
| |
| orgpat.len = (int)STRLEN(pat); |
| #ifdef FEAT_MULTI_LANG |
| if (curbuf->b_help) |
| { |
| /* When "@ab" is specified use only the "ab" language, otherwise |
| * search all languages. */ |
| if (orgpat.len > 3 && pat[orgpat.len - 3] == '@' |
| && ASCII_ISALPHA(pat[orgpat.len - 2]) |
| && ASCII_ISALPHA(pat[orgpat.len - 1])) |
| { |
| saved_pat = vim_strnsave(pat, orgpat.len - 3); |
| if (saved_pat != NULL) |
| { |
| help_lang_find = &pat[orgpat.len - 2]; |
| orgpat.pat = saved_pat; |
| orgpat.len -= 3; |
| } |
| } |
| } |
| #endif |
| if (p_tl != 0 && orgpat.len > p_tl) /* adjust for 'taglength' */ |
| orgpat.len = p_tl; |
| |
| save_emsg_off = emsg_off; |
| emsg_off = TRUE; /* don't want error for invalid RE here */ |
| prepare_pats(&orgpat, has_re); |
| emsg_off = save_emsg_off; |
| if (has_re && orgpat.regmatch.regprog == NULL) |
| goto findtag_end; |
| |
| #ifdef FEAT_TAG_BINS |
| /* This is only to avoid a compiler warning for using search_info |
| * uninitialised. */ |
| vim_memset(&search_info, 0, (size_t)1); |
| #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. |
| */ |
| #ifdef FEAT_TAG_BINS |
| orgpat.regmatch.rm_ic = ((p_ic || !noic) |
| && (findall || orgpat.headlen == 0 || !p_tbs)); |
| for (round = 1; round <= 2; ++round) |
| { |
| linear = (orgpat.headlen == 0 || !p_tbs || round == 2); |
| #else |
| orgpat.regmatch.rm_ic = (p_ic || !noic); |
| #endif |
| |
| /* |
| * 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, tag_fname) == OK; |
| first_file = FALSE) |
| { |
| /* |
| * 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) |
| fp = NULL; /* avoid GCC warning */ |
| else |
| #endif |
| { |
| #ifdef FEAT_MULTI_LANG |
| if (curbuf->b_help) |
| { |
| /* Prefer help tags according to 'helplang'. Put the |
| * two-letter language name in help_lang[]. */ |
| i = (int)STRLEN(tag_fname); |
| if (i > 3 && tag_fname[i - 3] == '-') |
| STRCPY(help_lang, tag_fname + i - 2); |
| else |
| STRCPY(help_lang, "en"); |
| |
| /* When searching for a specific language skip tags files |
| * for other languages. */ |
| if (help_lang_find != NULL |
| && STRICMP(help_lang, help_lang_find) != 0) |
| continue; |
| |
| /* For CTRL-] in a help file prefer a match with the same |
| * language. */ |
| if ((flags & TAG_KEEP_LANG) |
| && 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, help_lang, 2) == 0) |
| help_pri = 0; |
| else |
| { |
| help_pri = 1; |
| for (s = p_hlg; *s != NUL; ++s) |
| { |
| if (STRNICMP(s, help_lang, 2) == 0) |
| break; |
| ++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. */ |
| ++help_pri; |
| if (STRICMP(help_lang, "en") != 0) |
| ++help_pri; |
| } |
| } |
| } |
| #endif |
| |
| if ((fp = mch_fopen((char *)tag_fname, "r")) == NULL) |
| continue; |
| |
| if (p_verbose >= 5) |
| { |
| verbose_enter(); |
| smsg((char_u *)_("Searching tags file %s"), tag_fname); |
| verbose_leave(); |
| } |
| } |
| did_open = TRUE; /* remember that we found at least one file */ |
| |
| state = TS_START; /* we're at the start of the file */ |
| #ifdef FEAT_EMACS_TAGS |
| is_etag = 0; /* default is: not emacs style */ |
| #endif |
| |
| /* |
| * Read and parse the lines in the file one by one |
| */ |
| for (;;) |
| { |
| line_breakcheck(); /* check for CTRL-C typed */ |
| #ifdef FEAT_INS_EXPAND |
| if ((flags & TAG_INS_COMP)) /* Double brackets for gcc */ |
| ins_compl_check_keys(30); |
| if (got_int || compl_interrupted) |
| #else |
| if (got_int) |
| #endif |
| { |
| stop_searching = TRUE; |
| break; |
| } |
| /* When mincount is TAG_MANY, stop when enough matches have been |
| * found (for completion). */ |
| if (mincount == TAG_MANY && match_count >= TAG_MANY) |
| { |
| stop_searching = TRUE; |
| retval = OK; |
| break; |
| } |
| if (get_it_again) |
| goto line_read_in; |
| #ifdef FEAT_TAG_BINS |
| /* |
| * For binary search: compute the next offset to use. |
| */ |
| if (state == TS_BINARY) |
| { |
| offset = search_info.low_offset + ((search_info.high_offset |
| - search_info.low_offset) / 2); |
| if (offset == search_info.curr_offset) |
| break; /* End the binary search without a match. */ |
| else |
| search_info.curr_offset = offset; |
| } |
| |
| /* |
| * Skipping back (after a match during binary search). |
| */ |
| else if (state == TS_SKIP_BACK) |
| { |
| search_info.curr_offset -= LSIZE * 2; |
| if (search_info.curr_offset < 0) |
| { |
| search_info.curr_offset = 0; |
| rewind(fp); |
| state = TS_STEP_FORWARD; |
| } |
| } |
| |
| /* |
| * When jumping around in the file, first read a line to find the |
| * start of the next line. |
| */ |
| if (state == TS_BINARY || state == TS_SKIP_BACK) |
| { |
| /* Adjust the search file offset to the correct position */ |
| search_info.curr_offset_used = search_info.curr_offset; |
| #ifdef HAVE_FSEEKO |
| fseeko(fp, search_info.curr_offset, SEEK_SET); |
| #else |
| fseek(fp, (long)search_info.curr_offset, SEEK_SET); |
| #endif |
| eof = tag_fgets(lbuf, LSIZE, fp); |
| if (!eof && search_info.curr_offset != 0) |
| { |
| /* The explicit cast is to work around a bug in gcc 3.4.2 |
| * (repeated below). */ |
| search_info.curr_offset = ftell(fp); |
| if (search_info.curr_offset == search_info.high_offset) |
| { |
| /* oops, gone a bit too far; try from low offset */ |
| #ifdef HAVE_FSEEKO |
| fseeko(fp, search_info.low_offset, SEEK_SET); |
| #else |
| fseek(fp, (long)search_info.low_offset, SEEK_SET); |
| #endif |
| search_info.curr_offset = search_info.low_offset; |
| } |
| eof = tag_fgets(lbuf, LSIZE, fp); |
| } |
| /* skip empty and blank lines */ |
| while (!eof && vim_isblankline(lbuf)) |
| { |
| search_info.curr_offset = ftell(fp); |
| eof = tag_fgets(lbuf, LSIZE, fp); |
| } |
| if (eof) |
| { |
| /* Hit end of file. Skip backwards. */ |
| state = TS_SKIP_BACK; |
| search_info.match_offset = ftell(fp); |
| search_info.curr_offset = search_info.curr_offset_used; |
| continue; |
| } |
| } |
| |
| /* |
| * Not jumping around in the file: Read the next line. |
| */ |
| else |
| #endif |
| { |
| /* skip empty and blank lines */ |
| do |
| { |
| #ifdef FEAT_CSCOPE |
| if (use_cscope) |
| eof = cs_fgets(lbuf, LSIZE); |
| else |
| #endif |
| eof = tag_fgets(lbuf, LSIZE, fp); |
| } while (!eof && vim_isblankline(lbuf)); |
| |
| if (eof) |
| { |
| #ifdef FEAT_EMACS_TAGS |
| if (incstack_idx) /* this was an included file */ |
| { |
| --incstack_idx; |
| fclose(fp); /* end of this file ... */ |
| fp = incstack[incstack_idx].fp; |
| STRCPY(tag_fname, incstack[incstack_idx].etag_fname); |
| vim_free(incstack[incstack_idx].etag_fname); |
| is_etag = 1; /* (only etags can include) */ |
| continue; /* ... continue with parent file */ |
| } |
| else |
| #endif |
| break; /* end of file */ |
| } |
| } |
| line_read_in: |
| |
| #ifdef FEAT_MBYTE |
| if (vimconv.vc_type != CONV_NONE) |
| { |
| char_u *conv_line; |
| int len; |
| |
| /* Convert every line. Converting the pattern from 'enc' to |
| * the tags file encoding doesn't work, because characters are |
| * not recognized. */ |
| conv_line = string_convert(&vimconv, lbuf, NULL); |
| if (conv_line != NULL) |
| { |
| /* Copy or swap lbuf and conv_line. */ |
| len = (int)STRLEN(conv_line) + 1; |
| if (len > lbuf_size) |
| { |
| vim_free(lbuf); |
| lbuf = conv_line; |
| lbuf_size = len; |
| } |
| else |
| { |
| STRCPY(lbuf, conv_line); |
| vim_free(conv_line); |
| } |
| } |
| } |
| #endif |
| |
| |
| #ifdef FEAT_EMACS_TAGS |
| /* |
| * Emacs tags line with CTRL-L: New file name on next line. |
| * The file name is followed by a ','. |
| */ |
| if (*lbuf == Ctrl_L) /* remember etag file name in ebuf */ |
| { |
| is_etag = 1; /* in case at the start */ |
| state = TS_LINEAR; |
| if (!tag_fgets(ebuf, LSIZE, fp)) |
| { |
| for (p = ebuf; *p && *p != ','; p++) |
| ; |
| *p = NUL; |
| |
| /* |
| * atoi(p+1) is the number of bytes before the next ^L |
| * unless it is an include statement. |
| */ |
| if (STRNCMP(p + 1, "include", 7) == 0 |
| && incstack_idx < INCSTACK_SIZE) |
| { |
| /* Save current "fp" and "tag_fname" in the stack. */ |
| if ((incstack[incstack_idx].etag_fname = |
| vim_strsave(tag_fname)) != NULL) |
| { |
| char_u *fullpath_ebuf; |
| |
| incstack[incstack_idx].fp = fp; |
| fp = NULL; |
| |
| /* Figure out "tag_fname" and "fp" to use for |
| * included file. */ |
| fullpath_ebuf = expand_tag_fname(ebuf, |
| tag_fname, FALSE); |
| if (fullpath_ebuf != NULL) |
| { |
| fp = mch_fopen((char *)fullpath_ebuf, "r"); |
| if (fp != NULL) |
| { |
| if (STRLEN(fullpath_ebuf) > LSIZE) |
| EMSG2(_("E430: Tag file path truncated for %s\n"), ebuf); |
| vim_strncpy(tag_fname, fullpath_ebuf, |
| MAXPATHL); |
| ++incstack_idx; |
| is_etag = 0; /* we can include anything */ |
| } |
| vim_free(fullpath_ebuf); |
| } |
| if (fp == NULL) |
| { |
| /* Can't open the included file, skip it and |
| * restore old value of "fp". */ |
| fp = incstack[incstack_idx].fp; |
| vim_free(incstack[incstack_idx].etag_fname); |
| } |
| } |
| } |
| } |
| continue; |
| } |
| #endif |
| |
| /* |
| * When still at the start of the file, check for Emacs tags file |
| * format, and for "not sorted" flag. |
| */ |
| if (state == TS_START) |
| { |
| /* The header ends when the line sorts below "!_TAG_". When |
| * case is folded lower case letters sort before "_". */ |
| if (STRNCMP(lbuf, "!_TAG_", 6) <= 0 |
| || (lbuf[0] == '!' && ASCII_ISLOWER(lbuf[1]))) |
| { |
| if (STRNCMP(lbuf, "!_TAG_", 6) != 0) |
| /* Non-header item before the header, e.g. "!" itself. |
| */ |
| goto parse_line; |
| |
| /* |
| * Read header line. |
| */ |
| #ifdef FEAT_TAG_BINS |
| if (STRNCMP(lbuf, "!_TAG_FILE_SORTED\t", 18) == 0) |
| tag_file_sorted = lbuf[18]; |
| #endif |
| #ifdef FEAT_MBYTE |
| if (STRNCMP(lbuf, "!_TAG_FILE_ENCODING\t", 20) == 0) |
| { |
| /* Prepare to convert every line from the specified |
| * encoding to 'encoding'. */ |
| for (p = lbuf + 20; *p > ' ' && *p < 127; ++p) |
| ; |
| *p = NUL; |
| convert_setup(&vimconv, lbuf + 20, p_enc); |
| } |
| #endif |
| |
| /* Read the next line. Unrecognized flags are ignored. */ |
| continue; |
| } |
| |
| /* Headers ends. */ |
| |
| #ifdef FEAT_TAG_BINS |
| /* |
| * 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 (linear || use_cscope) |
| # else |
| if (linear) |
| # endif |
| state = TS_LINEAR; |
| else if (tag_file_sorted == NUL) |
| state = TS_BINARY; |
| else if (tag_file_sorted == '1') |
| state = TS_BINARY; |
| else if (tag_file_sorted == '2') |
| { |
| state = TS_BINARY; |
| sortic = TRUE; |
| orgpat.regmatch.rm_ic = (p_ic || !noic); |
| } |
| else |
| state = TS_LINEAR; |
| |
| if (state == TS_BINARY && orgpat.regmatch.rm_ic && !sortic) |
| { |
| /* Binary search won't work for ignoring case, use linear |
| * search. */ |
| linear = TRUE; |
| state = TS_LINEAR; |
| } |
| #else |
| state = TS_LINEAR; |
| #endif |
| |
| #ifdef FEAT_TAG_BINS |
| /* |
| * When starting a binary search, get the size of the file and |
| * compute the first offset. |
| */ |
| if (state == TS_BINARY) |
| { |
| /* Get the tag file size (don't use mch_fstat(), it's not |
| * portable). */ |
| if ((filesize = lseek(fileno(fp), |
| (off_t)0L, SEEK_END)) <= 0) |
| state = TS_LINEAR; |
| else |
| { |
| lseek(fileno(fp), (off_t)0L, SEEK_SET); |
| |
| /* Calculate the first read offset in the file. Start |
| * the search in the middle of the file. */ |
| search_info.low_offset = 0; |
| search_info.low_char = 0; |
| search_info.high_offset = filesize; |
| search_info.curr_offset = 0; |
| search_info.high_char = 0xff; |
| } |
| continue; |
| } |
| #endif |
| } |
| |
| parse_line: |
| /* |
| * 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 (orgpat.headlen |
| #ifdef FEAT_EMACS_TAGS |
| && !is_etag |
| #endif |
| ) |
| { |
| tagp.tagname = lbuf; |
| #ifdef FEAT_TAG_ANYWHITE |
| tagp.tagname_end = skiptowhite(lbuf); |
| if (*tagp.tagname_end == NUL) |
| #else |
| tagp.tagname_end = vim_strchr(lbuf, TAB); |
| if (tagp.tagname_end == NULL) |
| #endif |
| { |
| if (vim_strchr(lbuf, NL) == NULL) |
| { |
| /* Truncated line, ignore it. Has been reported for |
| * Mozilla JS with extremely long names. */ |
| if (p_verbose >= 5) |
| { |
| verbose_enter(); |
| MSG(_("Ignoring long line in tags file")); |
| verbose_leave(); |
| } |
| #ifdef FEAT_TAG_BINS |
| if (state != TS_LINEAR) |
| { |
| /* Avoid getting stuck. */ |
| linear = TRUE; |
| state = TS_LINEAR; |
| # ifdef HAVE_FSEEKO |
| fseeko(fp, search_info.low_offset, SEEK_SET); |
| # else |
| fseek(fp, (long)search_info.low_offset, SEEK_SET); |
| # endif |
| } |
| #endif |
| continue; |
| } |
| |
| /* Corrupted tag line. */ |
| line_error = TRUE; |
| break; |
| } |
| |
| #ifdef FEAT_TAG_OLDSTATIC |
| /* |
| * Check for old style static tag: "file:tag file .." |
| */ |
| tagp.fname = NULL; |
| for (p = lbuf; p < tagp.tagname_end; ++p) |
| { |
| if (*p == ':') |
| { |
| if (tagp.fname == NULL) |
| #ifdef FEAT_TAG_ANYWHITE |
| tagp.fname = skipwhite(tagp.tagname_end); |
| #else |
| tagp.fname = tagp.tagname_end + 1; |
| #endif |
| if ( fnamencmp(lbuf, tagp.fname, p - lbuf) == 0 |
| #ifdef FEAT_TAG_ANYWHITE |
| && vim_iswhite(tagp.fname[p - lbuf]) |
| #else |
| && tagp.fname[p - lbuf] == TAB |
| #endif |
| ) |
| { |
| /* found one */ |
| tagp.tagname = p + 1; |
| break; |
| } |
| } |
| } |
| #endif |
| |
| /* |
| * Skip this line if the length of the tag is different and |
| * there is no regexp, or the tag is too short. |
| */ |
| cmplen = (int)(tagp.tagname_end - tagp.tagname); |
| if (p_tl != 0 && cmplen > p_tl) /* adjust for 'taglength' */ |
| cmplen = p_tl; |
| if (has_re && orgpat.headlen < cmplen) |
| cmplen = orgpat.headlen; |
| else if (state == TS_LINEAR && orgpat.headlen != cmplen) |
| continue; |
| |
| #ifdef FEAT_TAG_BINS |
| if (state == TS_BINARY) |
| { |
| /* |
| * Simplistic check for unsorted tags file. |
| */ |
| i = (int)tagp.tagname[0]; |
| if (sortic) |
| i = (int)TOUPPER_ASC(tagp.tagname[0]); |
| if (i < search_info.low_char || i > search_info.high_char) |
| sort_error = TRUE; |
| |
| /* |
| * Compare the current tag with the searched tag. |
| */ |
| if (sortic) |
| tagcmp = tag_strnicmp(tagp.tagname, orgpat.head, |
| (size_t)cmplen); |
| else |
| tagcmp = STRNCMP(tagp.tagname, 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 < orgpat.headlen) |
| tagcmp = -1; |
| else if (cmplen > 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. |
| */ |
| state = TS_SKIP_BACK; |
| search_info.match_offset = search_info.curr_offset; |
| continue; |
| } |
| if (tagcmp < 0) |
| { |
| search_info.curr_offset = ftell(fp); |
| if (search_info.curr_offset < search_info.high_offset) |
| { |
| search_info.low_offset = search_info.curr_offset; |
| if (sortic) |
| search_info.low_char = |
| TOUPPER_ASC(tagp.tagname[0]); |
| else |
| search_info.low_char = tagp.tagname[0]; |
| continue; |
| } |
| } |
| if (tagcmp > 0 |
| && search_info.curr_offset != search_info.high_offset) |
| { |
| search_info.high_offset = search_info.curr_offset; |
| if (sortic) |
| search_info.high_char = |
| TOUPPER_ASC(tagp.tagname[0]); |
| else |
| search_info.high_char = tagp.tagname[0]; |
| continue; |
| } |
| |
| /* No match yet and are at the end of the binary search. */ |
| break; |
| } |
| else if (state == TS_SKIP_BACK) |
| { |
| if (MB_STRNICMP(tagp.tagname, orgpat.head, cmplen) != 0) |
| state = TS_STEP_FORWARD; |
| else |
| /* Have to skip back more. Restore the curr_offset |
| * used, otherwise we get stuck at a long line. */ |
| search_info.curr_offset = search_info.curr_offset_used; |
| continue; |
| } |
| else if (state == TS_STEP_FORWARD) |
| { |
| if (MB_STRNICMP(tagp.tagname, orgpat.head, cmplen) != 0) |
| { |
| if ((off_t)ftell(fp) > search_info.match_offset) |
| break; /* past last match */ |
| else |
| continue; /* before first match */ |
| } |
| } |
| else |
| #endif |
| /* skip this match if it can't match */ |
| if (MB_STRNICMP(tagp.tagname, orgpat.head, cmplen) != 0) |
| continue; |
| |
| /* |
| * Can be a matching tag, isolate the file name and command. |
| */ |
| #ifdef FEAT_TAG_OLDSTATIC |
| if (tagp.fname == NULL) |
| #endif |
| #ifdef FEAT_TAG_ANYWHITE |
| tagp.fname = skipwhite(tagp.tagname_end); |
| #else |
| tagp.fname = tagp.tagname_end + 1; |
| #endif |
| #ifdef FEAT_TAG_ANYWHITE |
| tagp.fname_end = skiptowhite(tagp.fname); |
| tagp.command = skipwhite(tagp.fname_end); |
| if (*tagp.command == NUL) |
| #else |
| tagp.fname_end = vim_strchr(tagp.fname, TAB); |
| tagp.command = tagp.fname_end + 1; |
| if (tagp.fname_end == NULL) |
| #endif |
| i = FAIL; |
| else |
| i = OK; |
| } |
| else |
| i = parse_tag_line(lbuf, |
| #ifdef FEAT_EMACS_TAGS |
| is_etag, |
| #endif |
| &tagp); |
| if (i == FAIL) |
| { |
| line_error = TRUE; |
| break; |
| } |
| |
| #ifdef FEAT_EMACS_TAGS |
| if (is_etag) |
| tagp.fname = ebuf; |
| #endif |
| /* |
| * First try matching with the pattern literally (also when it is |
| * a regexp). |
| */ |
| cmplen = (int)(tagp.tagname_end - tagp.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 (orgpat.len != cmplen) |
| match = FALSE; |
| else |
| { |
| if (orgpat.regmatch.rm_ic) |
| { |
| match = (MB_STRNICMP(tagp.tagname, orgpat.pat, cmplen) == 0); |
| if (match) |
| match_no_ic = (STRNCMP(tagp.tagname, orgpat.pat, |
| cmplen) == 0); |
| } |
| else |
| match = (STRNCMP(tagp.tagname, orgpat.pat, cmplen) == 0); |
| } |
| |
| /* |
| * Has a regexp: Also find tags matching regexp. |
| */ |
| match_re = FALSE; |
| if (!match && orgpat.regmatch.regprog != NULL) |
| { |
| int cc; |
| |
| cc = *tagp.tagname_end; |
| *tagp.tagname_end = NUL; |
| match = vim_regexec(&orgpat.regmatch, tagp.tagname, (colnr_T)0); |
| if (match) |
| { |
| matchoff = (int)(orgpat.regmatch.startp[0] - tagp.tagname); |
| if (orgpat.regmatch.rm_ic) |
| { |
| orgpat.regmatch.rm_ic = FALSE; |
| match_no_ic = vim_regexec(&orgpat.regmatch, tagp.tagname, |
| (colnr_T)0); |
| orgpat.regmatch.rm_ic = TRUE; |
| } |
| } |
| *tagp.tagname_end = cc; |
| match_re = TRUE; |
| } |
| |
| /* |
| * If a match is found, add it to ga_match[]. |
| */ |
| if (match) |
| { |
| #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 |
| is_etag, |
| #endif |
| tagp.fname, tagp.fname_end, tag_fname, |
| buf_ffname); |
| #ifdef FEAT_EMACS_TAGS |
| is_static = FALSE; |
| if (!is_etag) /* emacs tags are never static */ |
| #endif |
| { |
| #ifdef FEAT_TAG_OLDSTATIC |
| if (tagp.tagname != lbuf) |
| is_static = TRUE; /* detected static tag before */ |
| else |
| #endif |
| is_static = test_for_static(&tagp); |
| } |
| |
| /* 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 (orgpat.regmatch.rm_ic && !match_no_ic) |
| mtt += MT_IC_OFF; |
| if (match_re) |
| mtt += MT_RE_OFF; |
| } |
| |
| /* |
| * Add the found match in ga_match[mtt], avoiding duplicates. |
| * Store the info we need later, which depends on the kind of |
| * tags we are dealing with. |
| */ |
| if (ga_grow(&ga_match[mtt], 1) == OK) |
| { |
| int len; |
| |
| if (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. |
| */ |
| *tagp.tagname_end = NUL; |
| len = (int)(tagp.tagname_end - tagp.tagname); |
| mfp = (struct match_found *) |
| alloc((int)sizeof(struct match_found) + len |
| + 10 + ML_EXTRA); |
| if (mfp != NULL) |
| { |
| /* "len" includes the language and the NUL, but |
| * not the priority. */ |
| mfp->len = len + ML_EXTRA + 1; |
| #define ML_HELP_LEN 6 |
| p = mfp->match; |
| STRCPY(p, tagp.tagname); |
| #ifdef FEAT_MULTI_LANG |
| p[len] = '@'; |
| STRCPY(p + len + 1, help_lang); |
| #endif |
| sprintf((char *)p + len + 1 + ML_EXTRA, "%06d", |
| help_heuristic(tagp.tagname, |
| match_re ? matchoff : 0, !match_no_ic) |
| #ifdef FEAT_MULTI_LANG |
| + help_pri |
| #endif |
| ); |
| } |
| *tagp.tagname_end = TAB; |
| } |
| else if (name_only) |
| { |
| if (get_it_again) |
| { |
| char_u *temp_end = tagp.command; |
| |
| if (*temp_end == '/') |
| while (*temp_end && *temp_end != '\r' |
| && *temp_end != '\n' |
| && *temp_end != '$') |
| temp_end++; |
| |
| if (tagp.command + 2 < temp_end) |
| { |
| len = (int)(temp_end - tagp.command - 2); |
| mfp = (struct match_found *)alloc( |
| (int)sizeof(struct match_found) + len); |
| if (mfp != NULL) |
| { |
| mfp->len = len + 1; /* include the NUL */ |
| p = mfp->match; |
| vim_strncpy(p, tagp.command + 2, len); |
| } |
| } |
| else |
| mfp = NULL; |
| get_it_again = FALSE; |
| } |
| else |
| { |
| len = (int)(tagp.tagname_end - tagp.tagname); |
| mfp = (struct match_found *)alloc( |
| (int)sizeof(struct match_found) + len); |
| if (mfp != NULL) |
| { |
| mfp->len = len + 1; /* include the NUL */ |
| p = mfp->match; |
| vim_strncpy(p, tagp.tagname, len); |
| } |
| |
| /* if wanted, re-read line to get long form too */ |
| if (State & INSERT) |
| get_it_again = p_sft; |
| } |
| } |
| else |
| { |
| /* Save the tag in a buffer. |
| * 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> |
| */ |
| len = (int)STRLEN(tag_fname) |
| + (int)STRLEN(lbuf) + 3; |
| #ifdef FEAT_EMACS_TAGS |
| if (is_etag) |
| len += (int)STRLEN(ebuf) + 1; |
| else |
| ++len; |
| #endif |
| mfp = (struct match_found *)alloc( |
| (int)sizeof(struct match_found) + len); |
| if (mfp != NULL) |
| { |
| mfp->len = len; |
| p = mfp->match; |
| p[0] = mtt; |
| STRCPY(p + 1, tag_fname); |
| #ifdef BACKSLASH_IN_FILENAME |
| /* Ignore differences in slashes, avoid adding |
| * both path/file and path\file. */ |
| slash_adjust(p + 1); |
| #endif |
| s = p + 1 + STRLEN(tag_fname) + 1; |
| #ifdef FEAT_EMACS_TAGS |
| if (is_etag) |
| { |
| STRCPY(s, ebuf); |
| s += STRLEN(ebuf) + 1; |
| } |
| else |
| *s++ = NUL; |
| #endif |
| STRCPY(s, lbuf); |
| } |
| } |
| |
| if (mfp != NULL) |
| { |
| /* |
| * Don't add identical matches. |
| * This can take a lot of time when finding many |
| * matches, check for CTRL-C now and then. |
| * Add all cscope tags, because they are all listed. |
| */ |
| #ifdef FEAT_CSCOPE |
| if (use_cscope) |
| i = -1; |
| else |
| #endif |
| for (i = ga_match[mtt].ga_len; --i >= 0 && !got_int; ) |
| { |
| mfp2 = ((struct match_found **) |
| (ga_match[mtt].ga_data))[i]; |
| if (mfp2->len == mfp->len |
| && vim_memcmp(mfp2->match, mfp->match, |
| (size_t)mfp->len) == 0) |
| break; |
| line_breakcheck(); |
| } |
| if (i < 0) |
| { |
| ((struct match_found **)(ga_match[mtt].ga_data)) |
| [ga_match[mtt].ga_len++] = mfp; |
| ++match_count; |
| } |
| else |
| vim_free(mfp); |
| } |
| } |
| else /* Out of memory! Just forget about the rest. */ |
| { |
| retval = OK; |
| stop_searching = TRUE; |
| break; |
| } |
| } |
| #ifdef FEAT_CSCOPE |
| if (use_cscope && eof) |
| break; |
| #endif |
| } /* forever */ |
| |
| if (line_error) |
| { |
| EMSG2(_("E431: Format error in tags file \"%s\""), tag_fname); |
| #ifdef FEAT_CSCOPE |
| if (!use_cscope) |
| #endif |
| EMSGN(_("Before byte %ld"), (long)ftell(fp)); |
| stop_searching = TRUE; |
| line_error = FALSE; |
| } |
| |
| #ifdef FEAT_CSCOPE |
| if (!use_cscope) |
| #endif |
| fclose(fp); |
| #ifdef FEAT_EMACS_TAGS |
| while (incstack_idx) |
| { |
| --incstack_idx; |
| fclose(incstack[incstack_idx].fp); |
| vim_free(incstack[incstack_idx].etag_fname); |
| } |
| #endif |
| #ifdef FEAT_MBYTE |
| if (vimconv.vc_type != CONV_NONE) |
| convert_setup(&vimconv, NULL, NULL); |
| #endif |
| |
| #ifdef FEAT_TAG_BINS |
| tag_file_sorted = NUL; |
| if (sort_error) |
| { |
| EMSG2(_("E432: Tags file not sorted: %s"), tag_fname); |
| sort_error = FALSE; |
| } |
| #endif |
| |
| /* |
| * Stop searching if sufficient tags have been found. |
| */ |
| if (match_count >= mincount) |
| { |
| retval = OK; |
| stop_searching = TRUE; |
| } |
| |
| #ifdef FEAT_CSCOPE |
| if (stop_searching || use_cscope) |
| #else |
| if (stop_searching) |
| #endif |
| break; |
| |
| } /* end of for-each-file loop */ |
| |
| #ifdef FEAT_CSCOPE |
| if (!use_cscope) |
| #endif |
| tagname_free(&tn); |
| |
| #ifdef FEAT_TAG_BINS |
| /* stop searching when already did a linear search, or when TAG_NOIC |
| * used, and 'ignorecase' not set or already did case-ignore search */ |
| if (stop_searching || linear || (!p_ic && noic) || orgpat.regmatch.rm_ic) |
| break; |
| # ifdef FEAT_CSCOPE |
| if (use_cscope) |
| break; |
| # endif |
| orgpat.regmatch.rm_ic = TRUE; /* try another time while ignoring case */ |
| } |
| #endif |
| |
| if (!stop_searching) |
| { |
| if (!did_open && verbose) /* never opened any tags file */ |
| EMSG(_("E433: No tags file")); |
| retval = OK; /* It's OK even when no tag found */ |
| } |
| |
| findtag_end: |
| vim_free(lbuf); |
| vim_regfree(orgpat.regmatch.regprog); |
| vim_free(tag_fname); |
| #ifdef FEAT_EMACS_TAGS |
| vim_free(ebuf); |
| #endif |
| |
| /* |
| * Move the matches from the ga_match[] arrays into one list of |
| * matches. When retval == FAIL, free the matches. |
| */ |
| if (retval == FAIL) |
| match_count = 0; |
| |
| if (match_count > 0) |
| matches = (char_u **)lalloc((long_u)(match_count * sizeof(char_u *)), |
| TRUE); |
| else |
| matches = NULL; |
| match_count = 0; |
| for (mtt = 0; mtt < MT_COUNT; ++mtt) |
| { |
| for (i = 0; i < ga_match[mtt].ga_len; ++i) |
| { |
| mfp = ((struct match_found **)(ga_match[mtt].ga_data))[i]; |
| if (matches == NULL) |
| vim_free(mfp); |
| else |
| { |
| /* To avoid allocating memory again we turn the struct |
| * match_found into a string. For help the priority was not |
| * included in the length. */ |
| mch_memmove(mfp, mfp->match, |
| (size_t)(mfp->len + (help_only ? ML_HELP_LEN : 0))); |
| matches[match_count++] = (char_u *)mfp; |
| } |
| } |
| ga_clear(&ga_match[mtt]); |
| } |
| |
| *matchesp = matches; |
| *num_matches = match_count; |
| |
| curbuf->b_help = help_save; |
| #ifdef FEAT_MULTI_LANG |
| vim_free(saved_pat); |
| #endif |
| |
| return retval; |
| } |
| |
| static garray_T tag_fnames = GA_EMPTY; |
| static void found_tagfile_cb __ARGS((char_u *fname, void *cookie)); |
| |
| /* |
| * Callback function for finding all "tags" and "tags-??" files in |
| * 'runtimepath' doc directories. |
| */ |
| static void |
| found_tagfile_cb(fname, cookie) |
| char_u *fname; |
| void *cookie UNUSED; |
| { |
| if (ga_grow(&tag_fnames, 1) == OK) |
| ((char_u **)(tag_fnames.ga_data))[tag_fnames.ga_len++] = |
| vim_strsave(fname); |
| } |
| |
| #if defined(EXITFREE) || defined(PROTO) |
| void |
| free_tag_stuff() |
| { |
| ga_clear_strings(&tag_fnames); |
| do_tag(NULL, DT_FREE, 0, 0, 0); |
| tag_freematch(); |
| |
| # if defined(FEAT_WINDOWS) && defined(FEAT_QUICKFIX) |
| if (ptag_entry.tagname) |
| { |
| vim_free(ptag_entry.tagname); |
| ptag_entry.tagname = NULL; |
| } |
| # 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(tnp, first, buf) |
| 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; |
| |
| if (first) |
| vim_memset(tnp, 0, sizeof(tagname_T)); |
| |
| 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, (int)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 |
| , TRUE, 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"); |
| } |
| 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, " ,"); |
| |
| #ifdef FEAT_PATH_EXTRA |
| r_ptr = vim_findfile_stopdir(buf); |
| #else |
| r_ptr = NULL; |
| #endif |
| /* 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(tnp) |
| 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(lbuf, |
| #ifdef FEAT_EMACS_TAGS |
| is_etag, |
| #endif |
| tagp) |
| 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 |
| char_u *p_7f; |
| |
| if (is_etag) |
| { |
| /* |
| * 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) |
| { |
| /* 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; |
| } |
| return FAIL; |
| } |
| |
| /* 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; |
| } |
| } |
| else /* not an Emacs tag */ |
| { |
| #endif |
| /* Isolate the tagname, from lbuf up to the first white */ |
| tagp->tagname = lbuf; |
| #ifdef FEAT_TAG_ANYWHITE |
| p = skiptowhite(lbuf); |
| #else |
| p = vim_strchr(lbuf, TAB); |
| if (p == NULL) |
| return FAIL; |
| #endif |
| tagp->tagname_end = p; |
| |
| /* Isolate file name, from first to second white space */ |
| #ifdef FEAT_TAG_ANYWHITE |
| p = skipwhite(p); |
| #else |
| if (*p != NUL) |
| ++p; |
| #endif |
| tagp->fname = p; |
| #ifdef FEAT_TAG_ANYWHITE |
| p = skiptowhite(p); |
| #else |
| p = vim_strchr(p, TAB); |
| if (p == NULL) |
| return FAIL; |
| #endif |
| tagp->fname_end = p; |
| |
| /* find start of search command, after second white space */ |
| #ifdef FEAT_TAG_ANYWHITE |
| p = skipwhite(p); |
| #else |
| if (*p != NUL) |
| ++p; |
| #endif |
| if (*p == NUL) |
| return FAIL; |
| tagp->command = p; |
| #ifdef FEAT_EMACS_TAGS |
| } |
| #endif |
| |
| 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(tagp) |
| tagptrs_T *tagp; |
| { |
| char_u *p; |
| |
| #ifdef FEAT_TAG_OLDSTATIC |
| int len; |
| |
| /* |
| * Check for old style static tag: "file:tag file .." |
| */ |
| len = (int)(tagp->fname_end - tagp->fname); |
| p = tagp->tagname + len; |
| if ( p < tagp->tagname_end |
| && *p == ':' |
| && fnamencmp(tagp->tagname, tagp->fname, len) == 0) |
| { |
| tagp->tagname = p + 1; |
| return TRUE; |
| } |
| #endif |
| |
| /* |
| * 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; |
| } |
| |
| /* |
| * 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(lbuf, tagp) |
| 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->command_end = NULL; |
| |
| if (retval == OK) |
| { |
| /* Try to find a kind field: "kind:<kind>" or just "<kind>"*/ |
| p = tagp->command; |
| if (find_extra(&p) == OK) |
| { |
| tagp->command_end = p; |
| p += 2; /* skip ";\"" */ |
| if (*p++ == TAB) |
| while (ASCII_ISALPHA(*p)) |
| { |
| if (STRNCMP(p, "kind:", 5) == 0) |
| { |
| tagp->tagkind = p + 5; |
| break; |
| } |
| pc = vim_strchr(p, ':'); |
| pt = vim_strchr(p, '\t'); |
| if (pc == NULL || (pt != NULL && pc > pt)) |
| { |
| tagp->tagkind = p; |
| break; |
| } |
| if (pt == NULL) |
| break; |
| p = pt + 1; |
| } |
| } |
| if (tagp->tagkind != NULL) |
| { |
| for (p = tagp->tagkind; |
| *p && *p != '\t' && *p != '\r' && *p != '\n'; ++p) |
| ; |
| tagp->tagkind_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(tagp) |
| 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(lbuf, forceit, keep_help) |
| char_u *lbuf; /* line from the tags file for this tag */ |
| int forceit; /* :ta with ! */ |
| int keep_help; /* keep help flag (FALSE for cscope) */ |
| { |
| int save_secure; |
| int save_magic; |
| int save_p_ws, save_p_scs, save_p_ic; |
| linenr_T save_lnum; |
| int csave = 0; |
| 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; |
| int search_options; |
| #ifdef FEAT_SEARCH_EXTRA |
| int save_no_hlsearch; |
| #endif |
| #if defined(FEAT_WINDOWS) && 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 |
| |
| pbuf = alloc(LSIZE); |
| |
| /* parse the match line into the tagp structure */ |
| if (pbuf == NULL || parse_match(lbuf, &tagp) == FAIL) |
| { |
| tagp.fname_end = NULL; |
| goto erret; |
| } |
| |
| /* truncate the file name, so it can be used as a string */ |
| csave = *tagp.fname_end; |
| *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++; |
| } |
| *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 |
| #ifdef FEAT_AUTOCMD |
| && !has_autocmd(EVENT_BUFREADCMD, fname, NULL) |
| #endif |
| ) |
| { |
| 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_WINDOWS) && 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); |
| } |
| } |
| |
| /* If it was a CTRL-W CTRL-] command split window now. For ":tab tag" |
| * open a new tab page. */ |
| if (postponed_split || cmdmod.tab != 0) |
| { |
| win_split(postponed_split > 0 ? postponed_split : 0, |
| postponed_split_flags); |
| 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_WINDOWS) && defined(FEAT_QUICKFIX) |
| if (g_do_tagpreview != 0) |
| keep_help_flag = curwin_save->w_buffer->b_help; |
| else |
| #endif |
| keep_help_flag = curbuf->b_help; |
| } |
| getfile_result = getfile(0, fname, NULL, TRUE, (linenr_T)0, forceit); |
| keep_help_flag = FALSE; |
| |
| if (getfile_result <= 0) /* got to the right file */ |
| { |
| curwin->w_set_curswant = TRUE; |
| #ifdef FEAT_WINDOWS |
| postponed_split = 0; |
| #endif |
| |
| save_secure = secure; |
| secure = 1; |
| #ifdef HAVE_SANDBOX |
| ++sandbox; |
| #endif |
| save_magic = p_magic; |
| p_magic = FALSE; /* 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 '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, NULL) + 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; |
| #if 0 /* disabled for now */ |
| #ifdef FEAT_CMDHIST |
| /* put pattern in search history */ |
| add_to_history(HIST_SEARCH, pbuf + 1, TRUE, pbuf[0]); |
| #endif |
| #endif |
| save_lnum = curwin->w_cursor.lnum; |
| curwin->w_cursor.lnum = 0; /* start search before first line */ |
| if (do_search(NULL, 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 + 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(_("E434: Can't 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(_("E435: Couldn't find tag, just guessing!")); |
| if (!msg_scrolled && msg_silent == 0) |
| { |
| out_flush(); |
| ui_delay(1000L, 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 |
| { |
| 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; |
| p_magic = save_magic; |
| #ifdef HAVE_SANDBOX |
| --sandbox; |
| #endif |
| #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 == -1) |
| 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_WINDOWS) && 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(VALID); |
| win_enter(curwin_save, TRUE); |
| } |
| #endif |
| |
| --RedrawingDisabled; |
| } |
| else |
| { |
| --RedrawingDisabled; |
| #ifdef FEAT_WINDOWS |
| if (postponed_split) /* close the window */ |
| { |
| win_close(curwin, FALSE); |
| postponed_split = 0; |
| } |
| #endif |
| } |
| |
| erret: |
| #if defined(FEAT_WINDOWS) && defined(FEAT_QUICKFIX) |
| g_do_tagpreview = 0; /* For next time */ |
| #endif |
| if (tagp.fname_end != NULL) |
| *tagp.fname_end = csave; |
| 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(fname, tag_fname, expand) |
| 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, (char_u *)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; |
| } |
| |
| /* |
| * Converts a file name into a canonical form. It simplifies a file name into |
| * its simplest form by stripping out unneeded components, if any. The |
| * resulting file name is simplified in place and will either be the same |
| * length as that supplied, or shorter. |
| */ |
| void |
| simplify_filename(filename) |
| char_u *filename; |
| { |
| #ifndef AMIGA /* Amiga doesn't have "..", it uses "/" */ |
| int components = 0; |
| char_u *p, *tail, *start; |
| int stripping_disabled = FALSE; |
| int relative = TRUE; |
| |
| p = filename; |
| #ifdef BACKSLASH_IN_FILENAME |
| if (p[1] == ':') /* skip "x:" */ |
| p += 2; |
| #endif |
| |
| if (vim_ispathsep(*p)) |
| { |
| relative = FALSE; |
| do |
| ++p; |
| while (vim_ispathsep(*p)); |
| } |
| start = p; /* remember start after "c:/" or "/" or "///" */ |
| |
| do |
| { |
| /* At this point "p" is pointing to the char following a single "/" |
| * or "p" is at the "start" of the (absolute or relative) path name. */ |
| #ifdef VMS |
| /* VMS allows device:[path] - don't strip the [ in directory */ |
| if ((*p == '[' || *p == '<') && p > filename && p[-1] == ':') |
| { |
| /* :[ or :< composition: vms directory component */ |
| ++components; |
| p = getnextcomp(p + 1); |
| } |
| /* allow remote calls as host"user passwd"::device:[path] */ |
| else if (p[0] == ':' && p[1] == ':' && p > filename && p[-1] == '"' ) |
| { |
| /* ":: composition: vms host/passwd component */ |
| ++components; |
| p = getnextcomp(p + 2); |
| } |
| else |
| #endif |
| if (vim_ispathsep(*p)) |
| STRMOVE(p, p + 1); /* remove duplicate "/" */ |
| else if (p[0] == '.' && (vim_ispathsep(p[1]) || p[1] == NUL)) |
| { |
| if (p == start && relative) |
| p += 1 + (p[1] != NUL); /* keep single "." or leading "./" */ |
| else |
| { |
| /* Strip "./" or ".///". If we are at the end of the file name |
| * and there is no trailing path separator, either strip "/." if |
| * we are after "start", or strip "." if we are at the beginning |
| * of an absolute path name . */ |
| tail = p + 1; |
| if (p[1] != NUL) |
| while (vim_ispathsep(*tail)) |
| mb_ptr_adv(tail); |
| else if (p > start) |
| --p; /* strip preceding path separator */ |
| STRMOVE(p, tail); |
| } |
| } |
| else if (p[0] == '.' && p[1] == '.' && |
| (vim_ispathsep(p[2]) || p[2] == NUL)) |
| { |
| /* Skip to after ".." or "../" or "..///". */ |
| tail = p + 2; |
| while (vim_ispathsep(*tail)) |
| mb_ptr_adv(tail); |
| |
| if (components > 0) /* strip one preceding component */ |
| { |
| int do_strip = FALSE; |
| char_u saved_char; |
| struct stat st; |
| |
| /* Don't strip for an erroneous file name. */ |
| if (!stripping_disabled) |
| { |
| /* If the preceding component does not exist in the file |
| * system, we strip it. On Unix, we don't accept a symbolic |
| * link that refers to a non-existent file. */ |
| saved_char = p[-1]; |
| p[-1] = NUL; |
| #ifdef UNIX |
| if (mch_lstat((char *)filename, &st) < 0) |
| #else |
| if (mch_stat((char *)filename, &st) < 0) |
| #endif |
| do_strip = TRUE; |
| p[-1] = saved_char; |
| |
| --p; |
| /* Skip back to after previous '/'. */ |
| while (p > start && !after_pathsep(start, p)) |
| mb_ptr_back(start, p); |
| |
| if (!do_strip) |
| { |
| /* If the component exists in the file system, check |
| * that stripping it won't change the meaning of the |
| * file name. First get information about the |
| * unstripped file name. This may fail if the component |
| * to strip is not a searchable directory (but a regular |
| * file, for instance), since the trailing "/.." cannot |
| * be applied then. We don't strip it then since we |
| * don't want to replace an erroneous file name by |
| * a valid one, and we disable stripping of later |
| * components. */ |
| saved_char = *tail; |
| *tail = NUL; |
| if (mch_stat((char *)filename, &st) >= 0) |
| do_strip = TRUE; |
| else |
| stripping_disabled = TRUE; |
| *tail = saved_char; |
| #ifdef UNIX |
| if (do_strip) |
| { |
| struct stat new_st; |
| |
| /* On Unix, the check for the unstripped file name |
| * above works also for a symbolic link pointing to |
| * a searchable directory. But then the parent of |
| * the directory pointed to by the link must be the |
| * same as the stripped file name. (The latter |
| * exists in the file system since it is the |
| * component's parent directory.) */ |
| if (p == start && relative) |
| (void)mch_stat(".", &new_st); |
| else |
| { |
| saved_char = *p; |
| *p = NUL; |
| (void)mch_stat((char *)filename, &new_st); |
| *p = saved_char; |
| } |
| |
| if (new_st.st_ino != st.st_ino || |
| new_st.st_dev != st.st_dev) |
| { |
| do_strip = FALSE; |
| /* We don't disable stripping of later |
| * components since the unstripped path name is |
| * still valid. */ |
| } |
| } |
| #endif |
| } |
| } |
| |
| if (!do_strip) |
| { |
| /* Skip the ".." or "../" and reset the counter for the |
| * components that might be stripped later on. */ |
| p = tail; |
| components = 0; |
| } |
| else |
| { |
| /* Strip previous component. If the result would get empty |
| * and there is no trailing path separator, leave a single |
| * "." instead. If we are at the end of the file name and |
| * there is no trailing path separator and a preceding |
| * component is left after stripping, strip its trailing |
| * path separator as well. */ |
| if (p == start && relative && tail[-1] == '.') |
| { |
| *p++ = '.'; |
| *p = NUL; |
| } |
| else |
| { |
| if (p > start && tail[-1] == '.') |
| --p; |
| STRMOVE(p, tail); /* strip previous component */ |
| } |
| |
| --components; |
| } |
| } |
| else if (p == start && !relative) /* leading "/.." or "/../" */ |
| STRMOVE(p, tail); /* strip ".." or "../" */ |
| else |
| { |
| if (p == start + 2 && p[-2] == '.') /* leading "./../" */ |
| { |
| STRMOVE(p - 2, p); /* strip leading "./" */ |
| tail -= 2; |
| } |
| p = tail; /* skip to char after ".." or "../" */ |
| } |
| } |
| else |
| { |
| ++components; /* simple path component */ |
| p = getnextcomp(p); |
| } |
| } while (*p != NUL); |
| #endif /* !AMIGA */ |
| } |
| |
| /* |
| * 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 |
| #ifdef FEAT_EMACS_TAGS |
| test_for_current(is_etag, fname, fname_end, tag_fname, buf_ffname) |
| int is_etag; |
| #else |
| test_for_current(fname, fname_end, tag_fname, buf_ffname) |
| #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) & 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(pp) |
| char_u **pp; |
| { |
| char_u *str = *pp; |
| |
| /* Repeat for addresses separated with ';' */ |
| for (;;) |
| { |
| if (VIM_ISDIGIT(*str)) |
| str = skipdigits(str); |
| else if (*str == '/' || *str == '?') |
| { |
| str = skip_regexp(str + 1, *str, FALSE, NULL); |
| if (*str != **pp) |
| str = NULL; |
| else |
| ++str; |
| } |
| else |
| str = NULL; |
| if (str == NULL || *str != ';' |
| || !(VIM_ISDIGIT(str[1]) || str[1] == '/' || str[1] == '?')) |
| break; |
| ++str; /* skip ';' */ |
| } |
| |
| if (str != NULL && STRNCMP(str, ";\"", 2) == 0) |
| { |
| *pp = str; |
| return OK; |
| } |
| return FAIL; |
| } |
| |
| #if defined(FEAT_CMDL_COMPL) || defined(PROTO) |
| int |
| expand_tags(tagnames, pat, num_file, file) |
| int tagnames; /* expand tag names */ |
| char_u *pat; |
| int *num_file; |
| char_u ***file; |
| { |
| int i; |
| int c; |
| int tagnmflag; |
| char_u tagnm[100]; |
| tagptrs_T t_p; |
| int ret; |
| |
| if (tagnames) |
| tagnmflag = TAG_NAMES; |
| else |
| tagnmflag = 0; |
| if (pat[0] == '/') |
| ret = find_tags(pat + 1, num_file, file, |
| TAG_REGEXP | tagnmflag | TAG_VERBOSE, |
| TAG_MANY, curbuf->b_ffname); |
| else |
| ret = find_tags(pat, num_file, file, |
| TAG_REGEXP | tagnmflag | TAG_VERBOSE | 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++) |
| { |
| parse_match((*file)[i], &t_p); |
| c = (int)(t_p.tagname_end - t_p.tagname); |
| mch_memmove(tagnm, t_p.tagname, (size_t)c); |
| tagnm[c++] = 0; |
| tagnm[c++] = (t_p.tagkind != NULL && *t_p.tagkind) |
| ? *t_p.tagkind : 'f'; |
| tagnm[c++] = 0; |
| mch_memmove((*file)[i] + c, t_p.fname, t_p.fname_end - t_p.fname); |
| (*file)[i][c + (t_p.fname_end - t_p.fname)] = 0; |
| mch_memmove((*file)[i], tagnm, (size_t)c); |
| } |
| } |
| return ret; |
| } |
| #endif |
| |
| #if defined(FEAT_EVAL) || defined(PROTO) |
| static int add_tag_field __ARGS((dict_T *dict, char *field_name, char_u *start, char_u *end)); |
| |
| /* |
| * Add a tag field to the dictionary "dict". |
| * Return OK or FAIL. |
| */ |
| static int |
| add_tag_field(dict, field_name, start, end) |
| 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_find(dict, (char_u *)field_name, -1) != NULL) |
| { |
| if (p_verbose > 0) |
| { |
| verbose_enter(); |
| smsg((char_u *)_("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_nr_str(dict, field_name, 0L, buf); |
| vim_free(buf); |
| return retval; |
| } |
| |
| /* |
| * Add the tags matching the specified pattern to the list "list" |
| * as a dictionary |
| */ |
| int |
| get_tags(list, pat) |
| list_T *list; |
| char_u *pat; |
| { |
| 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, NULL); |
| if (ret == OK && num_matches > 0) |
| { |
| for (i = 0; i < num_matches; ++i) |
| { |
| parse_match(matches[i], &tp); |
| is_static = test_for_static(&tp); |
| |
| /* Skip pseudo-tag lines. */ |
| if (STRNCMP(tp.tagname, "!_TAG_", 6) == 0) |
| continue; |
| |
| if ((dict = dict_alloc()) == NULL) |
| ret = FAIL; |
| 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_nr_str(dict, "static", is_static, NULL) == FAIL) |
| ret = FAIL; |
| |
| vim_free(full_fname); |
| |
| if (tp.command_end != NULL) |
| { |
| for (p = tp.command_end + 3; |
| *p != NUL && *p != '\n' && *p != '\r'; ++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; |
| } |
| #endif |