Patch 8.2.4494
Problem: The find_tags() function is much too long.
Solution: Refactor the function. (Yegappan Lakshmanan, closes #9869)
Files: src/quickfix.c, src/tag.c, src/testdir/test_tagjump.vim
*** ../vim-8.2.4493/src/quickfix.c 2022-02-26 10:31:24.699882028 +0000
--- src/quickfix.c 2022-03-02 20:23:32.511180681 +0000
***************
*** 5013,5018 ****
--- 5013,5020 ----
int res;
char_u *au_name = NULL;
int_u save_qfid;
+ char_u *errorformat = p_efm;
+ int newlist = TRUE;
// Redirect ":grep" to ":vimgrep" if 'grepprg' is "internal".
if (grep_internal(eap->cmdidx))
***************
*** 5059,5069 ****
incr_quickfix_busy();
! res = qf_init(wp, fname, (eap->cmdidx != CMD_make
! && eap->cmdidx != CMD_lmake) ? p_gefm : p_efm,
! (eap->cmdidx != CMD_grepadd
! && eap->cmdidx != CMD_lgrepadd),
! qf_cmdtitle(*eap->cmdlinep), enc);
if (wp != NULL)
{
qi = GET_LOC_LIST(wp);
--- 5061,5073 ----
incr_quickfix_busy();
! if (eap->cmdidx != CMD_make && eap->cmdidx != CMD_lmake)
! errorformat = p_gefm;
! if (eap->cmdidx == CMD_grepadd || eap->cmdidx == CMD_lgrepadd)
! newlist = FALSE;
!
! res = qf_init(wp, fname, errorformat, newlist, qf_cmdtitle(*eap->cmdlinep),
! enc);
if (wp != NULL)
{
qi = GET_LOC_LIST(wp);
*** ../vim-8.2.4493/src/tag.c 2022-01-28 15:28:00.212927659 +0000
--- src/tag.c 2022-03-02 20:29:21.794140317 +0000
***************
*** 1577,1652 ****
#endif
/*
! * 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
! * TAG_CSCOPE use cscope results for tags
! * TAG_NO_TAGFUNC do not call the 'tagfunc' function
*/
! int
! find_tags(
! 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
{
--- 1577,1680 ----
#endif
/*
! * State information used during a tag search
*/
! typedef struct {
! pat_T orgpat; // holds unconverted pattern info
! #ifdef FEAT_MULTI_LANG
! char_u *help_lang_find; // lang to be found
! int is_txt; // flag of file extension
! #endif
! int did_open; // did open a tag file
! int mincount; // MAXCOL: find all matches
// other: minimal number of matches
! #ifdef FEAT_TAG_BINS
! int linear; // do a linear search
! #endif
! char_u *lbuf; // line buffer
! int lbuf_size; // length of lbuf
! #ifdef FEAT_EMACS_TAGS
! char_u *ebuf; // additional buffer for etag fname
! #endif
! int match_count; // number of matches found
! garray_T ga_match[MT_COUNT]; // stores matches in sequence
! hashtab_T ht_match[MT_COUNT]; // stores matches by key
! int stop_searching; // stop when match found or error
! } findtags_state_T;
!
! /*
! * Initialize the state used by find_tags()
! */
! static int
! findtags_state_init(findtags_state_T *st, char_u *pat, int mincount)
! {
! int mtt;
!
! st->orgpat.pat = pat;
! st->orgpat.len = (int)STRLEN(pat);
! st->orgpat.regmatch.regprog = NULL;
! #ifdef FEAT_MULTI_LANG
! st->help_lang_find = NULL;
! st->is_txt = FALSE;
! #endif
! st->did_open = FALSE;
! st->mincount = mincount;
! st->lbuf_size = LSIZE;
! st->lbuf = alloc(st->lbuf_size);
! #ifdef FEAT_EMACS_TAGS
! st->ebuf = alloc(LSIZE);
! #endif
! st->match_count = 0;
! st->stop_searching = FALSE;
!
! for (mtt = 0; mtt < MT_COUNT; ++mtt)
! {
! ga_init2(&st->ga_match[mtt], sizeof(char_u *), 100);
! hash_init(&st->ht_match[mtt]);
! }
!
! // check for out of memory situation
! if (st->lbuf == NULL
! #ifdef FEAT_EMACS_TAGS
! || st->ebuf == NULL
! #endif
! )
! return FAIL;
!
! return OK;
! }
!
! /*
! * Search for tags in the 'tag_fname' tags file.
! * Information needed to search for the tags is in the 'st' state structure.
! * The matching tags are returned in 'st'.
! * Returns OK if successfully processed the file and FAIL on memory allocation
! * failure.
! */
! static int
! find_tags_in_file(
! char_u *tag_fname,
! findtags_state_T *st,
! int flags,
! char_u *buf_ffname)
{
FILE *fp;
tagptrs_T tagp;
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_MULTI_LANG
+ int help_pri = 0;
+ char_u help_lang[3]; // lang of current tags file
+ #endif
#ifdef FEAT_TAG_BINS
int tag_file_sorted = NUL; // !_TAG_FILE_SORTED value
off_T filesize;
int tagcmp;
off_T offset;
#endif
enum
{
***************
*** 1657,1670 ****
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
/*
--- 1685,1711 ----
TS_SKIP_BACK, // skipping backwards
TS_STEP_FORWARD // stepping forwards
#endif
! } state; // Current search state
! #ifdef FEAT_TAG_BINS
! 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;
! #endif
int cmplen;
int match; // matches
int match_no_ic = 0;// matches with rm_ic == FALSE
int match_re; // match with regexp
int matchoff = 0;
#ifdef FEAT_EMACS_TAGS
/*
***************
*** 1679,2805 ****
} 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
char_u *mfp;
- garray_T ga_match[MT_COUNT]; // stores matches in sequence
- hashtab_T ht_match[MT_COUNT]; // stores matches by key
- hash_T hash = 0;
- 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[]
! int is_txt = FALSE; // flag of file extension
! #endif
!
! pat_T orgpat; // holds unconverted pattern info
! vimconv_T vimconv;
#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);
! #ifdef FEAT_EVAL
! int use_tfu = ((flags & TAG_NO_TAGFUNC) == 0);
! #endif
! int save_p_ic = p_ic;
!
! /*
! * Change the value of 'ignorecase' according to 'tagcase' for the
! * duration of this function.
! */
! switch (curbuf->b_tc_flags ? curbuf->b_tc_flags : tc_flags)
! {
! case TC_FOLLOWIC: break;
! case TC_IGNORE: p_ic = TRUE; break;
! case TC_MATCH: p_ic = FALSE; break;
! case TC_FOLLOWSCS: p_ic = ignorecase(pat); break;
! case TC_SMART: p_ic = ignorecase_opt(pat, TRUE, TRUE); break;
! }
- help_save = curbuf->b_help;
- orgpat.pat = pat;
- orgpat.regmatch.regprog = NULL;
vimconv.vc_type = CONV_NONE;
! /*
! * 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], sizeof(char_u *), 100);
! hash_init(&ht_match[mtt]);
! }
!
! // 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
#ifdef FEAT_CSCOPE
! else if (use_cscope)
! {
! // Make sure we don't mix help and cscope, confuses Coverity.
! help_only = FALSE;
! curbuf->b_help = FALSE;
! }
#endif
-
- 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.
! CLEAR_FIELD(search_info);
! #endif
! #ifdef FEAT_EVAL
! if (*curbuf->b_p_tfu != NUL && use_tfu && !tfu_in_use)
! {
! tfu_in_use = TRUE;
! retval = find_tagfunc_tags(pat, &ga_match[0], &match_count,
! flags, buf_ffname);
! tfu_in_use = FALSE;
! if (retval != NOTDONE)
! goto findtag_end;
}
#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_MULTI_LANG
! // Set a flag if the file extension is .txt
! if ((flags & TAG_KEEP_LANG)
! && help_lang_find == NULL
! && curbuf->b_fname != NULL
! && (i = (int)STRLEN(curbuf->b_fname)) > 4
! && STRICMP(curbuf->b_fname + i - 4, ".txt") == 0)
! is_txt = TRUE;
! #endif
! #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)
! {
! // Keep en if the file extension is .txt
! if (is_txt)
! STRCPY(help_lang, "en");
! else
! {
! // 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(_("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 (;;)
{
! #ifdef FEAT_TAG_BINS
! // check for CTRL-C typed, more often when jumping around
! if (state == TS_BINARY || state == TS_SKIP_BACK)
! line_breakcheck();
! else
! #endif
! fast_breakcheck();
! if ((flags & TAG_INS_COMP)) // Double brackets for gcc
! ins_compl_check_keys(30, FALSE);
! if (got_int || ins_compl_interrupted())
{
! 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 -= lbuf_size * 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;
! vim_fseek(fp, search_info.curr_offset, SEEK_SET);
! eof = vim_fgets(lbuf, lbuf_size, fp);
! if (!eof && search_info.curr_offset != 0)
! {
! search_info.curr_offset = vim_ftell(fp);
! if (search_info.curr_offset == search_info.high_offset)
! {
! // oops, gone a bit too far; try from low offset
! vim_fseek(fp, search_info.low_offset, SEEK_SET);
! search_info.curr_offset = search_info.low_offset;
! }
! eof = vim_fgets(lbuf, lbuf_size, fp);
! }
! // skip empty and blank lines
! while (!eof && vim_isblankline(lbuf))
{
search_info.curr_offset = vim_ftell(fp);
! eof = vim_fgets(lbuf, lbuf_size, fp);
! }
! if (eof)
! {
! // Hit end of file. Skip backwards.
! state = TS_SKIP_BACK;
! search_info.match_offset = vim_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, lbuf_size);
- else
- #endif
- {
- search_info.curr_offset = vim_ftell(fp);
- eof = vim_fgets(lbuf, lbuf_size, 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:
! 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);
! }
}
}
#ifdef FEAT_EMACS_TAGS
! /*
! * Emacs tags line with CTRL-L: New file name on next line.
! * The file name is followed by a ','.
! * Remember etag file name in ebuf.
! */
! if (*lbuf == Ctrl_L
# ifdef FEAT_CSCOPE
! && !use_cscope
# endif
! )
{
! is_etag = 1; // in case at the start
! state = TS_LINEAR;
! if (!vim_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)
! semsg(_(e_tag_file_path_truncated_for_str), 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
! 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);
! }
!
! // 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)
! {
! if (vim_fseek(fp, 0L, SEEK_END) != 0)
! // can't seek, don't use binary search
! state = TS_LINEAR;
! else
! {
! // Get the tag file size (don't use mch_fstat(), it's
! // not portable). Don't use lseek(), it doesn't work
! // properly on MacOS Catalina.
! filesize = vim_ftell(fp);
! vim_fseek(fp, 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:
! // When the line is too long the NUL will not be in the
! // last-but-one byte (see vim_fgets()).
! // Has been reported for Mozilla JS with extremely long names.
! // In that case we need to increase lbuf_size.
! if (lbuf[lbuf_size - 2] != NUL
#ifdef FEAT_CSCOPE
! && !use_cscope
#endif
! )
! {
! lbuf_size *= 2;
! vim_free(lbuf);
! lbuf = alloc(lbuf_size);
! if (lbuf == NULL)
! goto findtag_end;
!
! if (state == TS_STEP_FORWARD)
! // Seek to the same position to read the same line again
! vim_fseek(fp, search_info.curr_offset, SEEK_SET);
#ifdef FEAT_TAG_BINS
! // this will try the same thing again, make sure the offset is
! // different
! search_info.curr_offset = 0;
#endif
! continue;
}
/*
! * 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
! )
{
! CLEAR_FIELD(tagp);
! tagp.tagname = lbuf;
! tagp.tagname_end = vim_strchr(lbuf, TAB);
! if (tagp.tagname_end == NULL)
! {
! // Corrupted tag line.
! line_error = TRUE;
! break;
! }
/*
! * 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 = vim_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)vim_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.
! */
! tagp.fname = tagp.tagname_end + 1;
! tagp.fname_end = vim_strchr(tagp.fname, TAB);
! tagp.command = tagp.fname_end + 1;
! if (tagp.fname_end == NULL)
! 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 ht_match[] and ga_match[].
! */
! if (match)
! {
! int len = 0;
#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
! 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 ht_match[mtt] and ga_match[mtt].
! * Store the info we need later, which depends on the kind of
! * tags we are dealing with.
! */
! 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. The heuristic is ignored for
! * detecting duplicates.
! * The format is {tagname}@{lang}NUL{heuristic}NUL
! */
! *tagp.tagname_end = NUL;
! len = (int)(tagp.tagname_end - tagp.tagname);
! mfp = alloc(sizeof(char_u) + len + 10 + ML_EXTRA + 1);
! if (mfp != NULL)
! {
! int heuristic;
! p = mfp;
! STRCPY(p, tagp.tagname);
#ifdef FEAT_MULTI_LANG
! p[len] = '@';
! STRCPY(p + len + 1, help_lang);
#endif
! heuristic = help_heuristic(tagp.tagname,
! match_re ? matchoff : 0, !match_no_ic);
#ifdef FEAT_MULTI_LANG
! heuristic += help_pri;
#endif
! sprintf((char *)p + len + 1 + ML_EXTRA, "%06d",
! heuristic);
! }
! *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 = alloc(len + 2);
! if (mfp != NULL)
! vim_strncpy(mfp, tagp.command + 2, len);
! }
! else
! mfp = NULL;
! get_it_again = FALSE;
! }
! else
{
! len = (int)(tagp.tagname_end - tagp.tagname);
! mfp = alloc(sizeof(char_u) + len + 1);
if (mfp != NULL)
! vim_strncpy(mfp, tagp.tagname, len);
!
! // if wanted, re-read line to get long form too
! if (State & INSERT)
! get_it_again = p_sft;
}
}
else
{
! size_t tag_fname_len = STRLEN(tag_fname);
#ifdef FEAT_EMACS_TAGS
! size_t ebuf_len = 0;
#endif
! // Save the tag in a buffer.
! // Use 0x02 to separate fields (Can't use NUL because the
! // hash key is terminated by NUL, or Ctrl_A because that is
! // part of some Emacs tag files -- see parse_tag_line).
! // Emacs tag: <mtt><tag_fname><0x02><ebuf><0x02><lbuf><NUL>
! // other tag: <mtt><tag_fname><0x02><0x02><lbuf><NUL>
! // without Emacs tags: <mtt><tag_fname><0x02><lbuf><NUL>
! // Here <mtt> is the "mtt" value plus 1 to avoid NUL.
! len = (int)tag_fname_len + (int)STRLEN(lbuf) + 3;
#ifdef FEAT_EMACS_TAGS
! if (is_etag)
! {
! ebuf_len = STRLEN(ebuf);
! len += (int)ebuf_len + 1;
! }
! else
! ++len;
#endif
! mfp = alloc(sizeof(char_u) + len + 1);
! if (mfp != NULL)
! {
! p = mfp;
! p[0] = mtt + 1;
! 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
! p[tag_fname_len + 1] = TAG_SEP;
! s = p + 1 + tag_fname_len + 1;
#ifdef FEAT_EMACS_TAGS
! if (is_etag)
! {
! STRCPY(s, ebuf);
! s[ebuf_len] = TAG_SEP;
! s += ebuf_len + 1;
! }
! else
! *s++ = TAG_SEP;
! #endif
! STRCPY(s, lbuf);
}
}
! if (mfp != NULL)
! {
! hashitem_T *hi;
! /*
! * Don't add identical matches.
! * Add all cscope tags, because they are all listed.
! * "mfp" is used as a hash key, there is a NUL byte to end
! * the part that matters for comparing, more bytes may
! * follow after it. E.g. help tags store the priority
! * after the NUL.
! */
#ifdef FEAT_CSCOPE
! if (use_cscope)
! hash++;
! else
#endif
! hash = hash_hash(mfp);
! hi = hash_lookup(&ht_match[mtt], mfp, hash);
! if (HASHITEM_EMPTY(hi))
! {
! if (hash_add_item(&ht_match[mtt], hi, mfp, hash)
! == FAIL
! || ga_grow(&ga_match[mtt], 1) != OK)
! {
! // Out of memory! Just forget about the rest.
! retval = OK;
! stop_searching = TRUE;
! break;
! }
! else
! {
! ((char_u **)(ga_match[mtt].ga_data))
! [ga_match[mtt].ga_len++] = mfp;
! ++match_count;
! }
}
else
! // duplicate tag, drop it
! vim_free(mfp);
}
}
#ifdef FEAT_CSCOPE
! if (use_cscope && eof)
! break;
#endif
! } // forever
! if (line_error)
! {
! semsg(_(e_format_error_in_tags_file_str), tag_fname);
#ifdef FEAT_CSCOPE
! if (!use_cscope)
#endif
! semsg(_("Before byte %ld"), (long)vim_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
! if (vimconv.vc_type != CONV_NONE)
! convert_setup(&vimconv, NULL, NULL);
#ifdef FEAT_TAG_BINS
! tag_file_sorted = NUL;
! if (sort_error)
{
! semsg(_(e_tags_file_not_sorted_str), 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
--- 1720,2937 ----
} incstack[INCSTACK_SIZE];
int incstack_idx = 0; // index in incstack
int is_etag; // current file is emaces style
#endif
char_u *mfp;
int mtt;
! hash_T hash = 0;
#ifdef FEAT_TAG_BINS
int sort_error = FALSE; // tags file not sorted
int sortic = FALSE; // tag file sorted in nocase
+ int noic = (flags & TAG_NOIC);
#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);
#ifdef FEAT_CSCOPE
int use_cscope = (flags & TAG_CSCOPE);
#endif
! int get_it_again = FALSE;
! vimconv_T vimconv;
vimconv.vc_type = CONV_NONE;
! #ifdef FEAT_TAG_BINS
! // This is only to avoid a compiler warning for using search_info
! // uninitialised.
! CLEAR_FIELD(search_info);
#endif
/*
! * 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)
{
! // Keep en if the file extension is .txt
! if (st->is_txt)
! STRCPY(help_lang, "en");
! else
! {
! // 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 (st->help_lang_find != NULL
! && STRICMP(help_lang, st->help_lang_find) != 0)
! return OK;
!
! // For CTRL-] in a help file prefer a match with the same
! // language.
! if ((flags & TAG_KEEP_LANG)
! && st->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)
! return OK;
! if (p_verbose >= 5)
! {
! verbose_enter();
! smsg(_("Searching tags file %s"), tag_fname);
! verbose_leave();
! }
}
+ st->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 (;;)
{
! #ifdef FEAT_TAG_BINS
! // check for CTRL-C typed, more often when jumping around
! if (state == TS_BINARY || state == TS_SKIP_BACK)
! line_breakcheck();
! else
#endif
! fast_breakcheck();
! if ((flags & TAG_INS_COMP)) // Double brackets for gcc
! ins_compl_check_keys(30, FALSE);
! if (got_int || ins_compl_interrupted())
! {
! st->stop_searching = TRUE;
! break;
! }
! // When mincount is TAG_MANY, stop when enough matches have been
! // found (for completion).
! if (st->mincount == TAG_MANY && st->match_count >= TAG_MANY)
! {
! st->stop_searching = TRUE;
! 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 -= st->lbuf_size * 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;
! vim_fseek(fp, search_info.curr_offset, SEEK_SET);
! eof = vim_fgets(st->lbuf, st->lbuf_size, fp);
! if (!eof && search_info.curr_offset != 0)
{
! search_info.curr_offset = vim_ftell(fp);
! if (search_info.curr_offset == search_info.high_offset)
! {
! // oops, gone a bit too far; try from low offset
! vim_fseek(fp, search_info.low_offset, SEEK_SET);
! search_info.curr_offset = search_info.low_offset;
! }
! eof = vim_fgets(st->lbuf, st->lbuf_size, fp);
}
! // skip empty and blank lines
! while (!eof && vim_isblankline(st->lbuf))
{
! search_info.curr_offset = vim_ftell(fp);
! eof = vim_fgets(st->lbuf, st->lbuf_size, fp);
}
! if (eof)
{
! // Hit end of file. Skip backwards.
! state = TS_SKIP_BACK;
! search_info.match_offset = vim_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(st->lbuf, st->lbuf_size);
! else
! #endif
{
+ #ifdef FEAT_TAG_BINS
search_info.curr_offset = vim_ftell(fp);
! #endif
! eof = vim_fgets(st->lbuf, st->lbuf_size, fp);
}
! } while (!eof && vim_isblankline(st->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:
! 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, st->lbuf, NULL);
! if (conv_line != NULL)
! {
! // Copy or swap lbuf and conv_line.
! len = (int)STRLEN(conv_line) + 1;
! if (len > st->lbuf_size)
! {
! vim_free(st->lbuf);
! st->lbuf = conv_line;
! st->lbuf_size = len;
! }
! else
! {
! STRCPY(st->lbuf, conv_line);
! vim_free(conv_line);
}
}
+ }
#ifdef FEAT_EMACS_TAGS
! /*
! * Emacs tags line with CTRL-L: New file name on next line.
! * The file name is followed by a ','.
! * Remember etag file name in ebuf.
! */
! if (*st->lbuf == Ctrl_L
# ifdef FEAT_CSCOPE
! && !use_cscope
# endif
! )
! {
! is_etag = 1; // in case at the start
! state = TS_LINEAR;
! if (!vim_fgets(st->ebuf, LSIZE, fp))
{
! for (p = st->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(st->ebuf,
! tag_fname, FALSE);
! if (fullpath_ebuf != NULL)
{
! fp = mch_fopen((char *)fullpath_ebuf, "r");
! if (fp != NULL)
{
! if (STRLEN(fullpath_ebuf) > LSIZE)
! semsg(_(e_tag_file_path_truncated_for_str), st->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(st->lbuf, "!_TAG_", 6) <= 0
! || (st->lbuf[0] == '!' && ASCII_ISLOWER(st->lbuf[1])))
{
! if (STRNCMP(st->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(st->lbuf, "!_TAG_FILE_SORTED\t", 18) == 0)
! tag_file_sorted = st->lbuf[18];
#endif
! if (STRNCMP(st->lbuf, "!_TAG_FILE_ENCODING\t", 20) == 0)
! {
! // Prepare to convert every line from the specified
! // encoding to 'encoding'.
! for (p = st->lbuf + 20; *p > ' ' && *p < 127; ++p)
! ;
! *p = NUL;
! convert_setup(&vimconv, st->lbuf + 20, p_enc);
}
! // 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 (st->linear || use_cscope)
# else
! if (st->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;
! st->orgpat.regmatch.rm_ic = (p_ic || !noic);
! }
! else
! state = TS_LINEAR;
! if (state == TS_BINARY && st->orgpat.regmatch.rm_ic && !sortic)
! {
! // Binary search won't work for ignoring case, use linear
! // search.
! st->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)
! {
! if (vim_fseek(fp, 0L, SEEK_END) != 0)
! // can't seek, don't use binary search
! state = TS_LINEAR;
! else
! {
! // Get the tag file size (don't use mch_fstat(), it's
! // not portable). Don't use lseek(), it doesn't work
! // properly on MacOS Catalina.
! filesize = vim_ftell(fp);
! vim_fseek(fp, 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:
! // When the line is too long the NUL will not be in the
! // last-but-one byte (see vim_fgets()).
! // Has been reported for Mozilla JS with extremely long names.
! // In that case we need to increase lbuf_size.
! if (st->lbuf[st->lbuf_size - 2] != NUL
#ifdef FEAT_CSCOPE
! && !use_cscope
#endif
! )
! {
! st->lbuf_size *= 2;
! vim_free(st->lbuf);
! st->lbuf = alloc(st->lbuf_size);
! if (st->lbuf == NULL)
! return FAIL;
!
#ifdef FEAT_TAG_BINS
! if (state == TS_STEP_FORWARD)
! // Seek to the same position to read the same line again
! vim_fseek(fp, search_info.curr_offset, SEEK_SET);
! // this will try the same thing again, make sure the offset is
! // different
! search_info.curr_offset = 0;
#endif
! continue;
! }
!
! /*
! * 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 (st->orgpat.headlen
! #ifdef FEAT_EMACS_TAGS
! && !is_etag
! #endif
! )
! {
! CLEAR_FIELD(tagp);
! tagp.tagname = st->lbuf;
! tagp.tagname_end = vim_strchr(st->lbuf, TAB);
! if (tagp.tagname_end == NULL)
! {
! // Corrupted tag line.
! line_error = TRUE;
! break;
}
/*
! * 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 && st->orgpat.headlen < cmplen)
! cmplen = st->orgpat.headlen;
! else if (state == TS_LINEAR && st->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, st->orgpat.head,
! (size_t)cmplen);
! else
! tagcmp = STRNCMP(tagp.tagname, st->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 < st->orgpat.headlen)
! tagcmp = -1;
! else if (cmplen > st->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 = vim_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, st->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, st->orgpat.head, cmplen) != 0)
{
! if ((off_T)vim_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, st->orgpat.head, cmplen) != 0)
continue;
! /*
! * Can be a matching tag, isolate the file name and command.
! */
! tagp.fname = tagp.tagname_end + 1;
! tagp.fname_end = vim_strchr(tagp.fname, TAB);
! tagp.command = tagp.fname_end + 1;
! if (tagp.fname_end == NULL)
! i = FAIL;
else
! i = OK;
! }
! else
! i = parse_tag_line(st->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 = st->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 (st->orgpat.len != cmplen)
! match = FALSE;
! else
! {
! if (st->orgpat.regmatch.rm_ic)
{
! match = (MB_STRNICMP(tagp.tagname, st->orgpat.pat, cmplen) == 0);
! if (match)
! match_no_ic = (STRNCMP(tagp.tagname, st->orgpat.pat,
! cmplen) == 0);
}
+ else
+ match = (STRNCMP(tagp.tagname, st->orgpat.pat, cmplen) == 0);
+ }
! /*
! * Has a regexp: Also find tags matching regexp.
! */
! match_re = FALSE;
! if (!match && st->orgpat.regmatch.regprog != NULL)
! {
! int cc;
! cc = *tagp.tagname_end;
! *tagp.tagname_end = NUL;
! match = vim_regexec(&st->orgpat.regmatch, tagp.tagname, (colnr_T)0);
! if (match)
! {
! matchoff = (int)(st->orgpat.regmatch.startp[0] - tagp.tagname);
! if (st->orgpat.regmatch.rm_ic)
{
! st->orgpat.regmatch.rm_ic = FALSE;
! match_no_ic = vim_regexec(&st->orgpat.regmatch, tagp.tagname,
! (colnr_T)0);
! st->orgpat.regmatch.rm_ic = TRUE;
}
}
+ *tagp.tagname_end = cc;
+ match_re = TRUE;
+ }
! /*
! * If a match is found, add it to ht_match[] and ga_match[].
! */
! if (match)
! {
! int len = 0;
#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
! 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 (st->orgpat.regmatch.rm_ic && !match_no_ic)
+ mtt += MT_IC_OFF;
+ if (match_re)
+ mtt += MT_RE_OFF;
+ }
+
+ /*
+ * Add the found match in ht_match[mtt] and ga_match[mtt].
+ * Store the info we need later, which depends on the kind of
+ * tags we are dealing with.
+ */
+ 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. The heuristic is ignored for
! * detecting duplicates.
! * The format is {tagname}@{lang}NUL{heuristic}NUL
! */
! *tagp.tagname_end = NUL;
! len = (int)(tagp.tagname_end - tagp.tagname);
! mfp = alloc(sizeof(char_u) + len + 10 + ML_EXTRA + 1);
! if (mfp != NULL)
! {
! int heuristic;
! p = mfp;
! STRCPY(p, tagp.tagname);
#ifdef FEAT_MULTI_LANG
! p[len] = '@';
! STRCPY(p + len + 1, help_lang);
#endif
! heuristic = help_heuristic(tagp.tagname,
! match_re ? matchoff : 0, !match_no_ic);
#ifdef FEAT_MULTI_LANG
! heuristic += help_pri;
#endif
! sprintf((char *)p + len + 1 + ML_EXTRA, "%06d",
! heuristic);
}
! *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 = alloc(len + 2);
if (mfp != NULL)
! vim_strncpy(mfp, tagp.command + 2, len);
}
+ else
+ mfp = NULL;
+ get_it_again = FALSE;
}
else
{
! len = (int)(tagp.tagname_end - tagp.tagname);
! mfp = alloc(sizeof(char_u) + len + 1);
! if (mfp != NULL)
! vim_strncpy(mfp, tagp.tagname, len);
!
! // if wanted, re-read line to get long form too
! if (State & INSERT)
! get_it_again = p_sft;
! }
! }
! else
! {
! size_t tag_fname_len = STRLEN(tag_fname);
#ifdef FEAT_EMACS_TAGS
! size_t ebuf_len = 0;
#endif
! // Save the tag in a buffer.
! // Use 0x02 to separate fields (Can't use NUL because the
! // hash key is terminated by NUL, or Ctrl_A because that is
! // part of some Emacs tag files -- see parse_tag_line).
! // Emacs tag: <mtt><tag_fname><0x02><ebuf><0x02><lbuf><NUL>
! // other tag: <mtt><tag_fname><0x02><0x02><lbuf><NUL>
! // without Emacs tags: <mtt><tag_fname><0x02><lbuf><NUL>
! // Here <mtt> is the "mtt" value plus 1 to avoid NUL.
! len = (int)tag_fname_len + (int)STRLEN(st->lbuf) + 3;
#ifdef FEAT_EMACS_TAGS
! if (is_etag)
! {
! ebuf_len = STRLEN(st->ebuf);
! len += (int)ebuf_len + 1;
! }
! else
! ++len;
#endif
! mfp = alloc(sizeof(char_u) + len + 1);
! if (mfp != NULL)
! {
! p = mfp;
! p[0] = mtt + 1;
! 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
! p[tag_fname_len + 1] = TAG_SEP;
! s = p + 1 + tag_fname_len + 1;
#ifdef FEAT_EMACS_TAGS
! if (is_etag)
! {
! STRCPY(s, st->ebuf);
! s[ebuf_len] = TAG_SEP;
! s += ebuf_len + 1;
}
+ else
+ *s++ = TAG_SEP;
+ #endif
+ STRCPY(s, st->lbuf);
}
+ }
! if (mfp != NULL)
! {
! hashitem_T *hi;
! /*
! * Don't add identical matches.
! * Add all cscope tags, because they are all listed.
! * "mfp" is used as a hash key, there is a NUL byte to end
! * the part that matters for comparing, more bytes may
! * follow after it. E.g. help tags store the priority
! * after the NUL.
! */
#ifdef FEAT_CSCOPE
! if (use_cscope)
! hash++;
! else
#endif
! hash = hash_hash(mfp);
! hi = hash_lookup(&st->ht_match[mtt], mfp, hash);
! if (HASHITEM_EMPTY(hi))
! {
! if (hash_add_item(&st->ht_match[mtt], hi, mfp, hash)
! == FAIL
! || ga_grow(&st->ga_match[mtt], 1) != OK)
! {
! // Out of memory! Just forget about the rest.
! st->stop_searching = TRUE;
! break;
}
else
! {
! ((char_u **)(st->ga_match[mtt].ga_data))
! [st->ga_match[mtt].ga_len++] = mfp;
! st->match_count++;
! }
}
+ else
+ // duplicate tag, drop it
+ vim_free(mfp);
}
+ }
#ifdef FEAT_CSCOPE
! if (use_cscope && eof)
! break;
#endif
! } // forever
! if (line_error)
! {
! semsg(_(e_format_error_in_tags_file_str), tag_fname);
#ifdef FEAT_CSCOPE
! if (!use_cscope)
#endif
! semsg(_("Before byte %ld"), (long)vim_ftell(fp));
! st->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
! if (vimconv.vc_type != CONV_NONE)
! convert_setup(&vimconv, NULL, NULL);
#ifdef FEAT_TAG_BINS
! tag_file_sorted = NUL;
! if (sort_error)
! {
! semsg(_(e_tags_file_not_sorted_str), tag_fname);
! sort_error = FALSE;
! }
! #endif
!
! /*
! * Stop searching if sufficient tags have been found.
! */
! if (st->match_count >= st->mincount)
! st->stop_searching = TRUE;
!
! return OK;
! }
!
! /*
! * Copy the tags found by find_tags() to 'matchesp'.
! */
! static void
! findtags_copy_matches(
! findtags_state_T *st,
! char_u ***matchesp,
! int *num_matches,
! int name_only)
! {
! char_u **matches;
! int mtt;
! int i;
! char_u *mfp;
! char_u *p;
!
! if (st->match_count > 0)
! matches = ALLOC_MULT(char_u *, st->match_count);
! else
! matches = NULL;
! st->match_count = 0;
! for (mtt = 0; mtt < MT_COUNT; ++mtt)
! {
! for (i = 0; i < st->ga_match[mtt].ga_len; ++i)
{
! mfp = ((char_u **)(st->ga_match[mtt].ga_data))[i];
! if (matches == NULL)
! vim_free(mfp);
! else
! {
! if (!name_only)
! {
! // Change mtt back to zero-based.
! *mfp = *mfp - 1;
!
! // change the TAG_SEP back to NUL
! for (p = mfp + 1; *p != NUL; ++p)
! if (*p == TAG_SEP)
! *p = NUL;
! }
! matches[st->match_count++] = mfp;
! }
}
+
+ ga_clear(&st->ga_match[mtt]);
+ hash_clear(&st->ht_match[mtt]);
+ }
+
+ *matchesp = matches;
+ *num_matches = st->match_count;
+ }
+
+ /*
+ * 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.
+ *
+ * Priority depending on 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
+ * TAG_CSCOPE use cscope results for tags
+ * TAG_NO_TAGFUNC do not call the 'tagfunc' function
+ */
+ int
+ find_tags(
+ 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
+ {
+ findtags_state_T st;
+ char_u *tag_fname; // name of tag file
+ tagname_T tn; // info for get_tagfname()
+ int first_file; // trying first tag file
+ int retval = FAIL; // return value
+ #ifdef FEAT_TAG_BINS
+ int round;
#endif
! int save_emsg_off;
!
! int help_save;
! #ifdef FEAT_MULTI_LANG
! int i;
! char_u *saved_pat = NULL; // copy of pat[]
! #endif
!
! #ifdef FEAT_TAG_BINS
! int findall = (mincount == MAXCOL || mincount == TAG_MANY);
! // find all matching tags
! #endif
! 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);
! #ifdef FEAT_CSCOPE
! int use_cscope = (flags & TAG_CSCOPE);
! #endif
! int verbose = (flags & TAG_VERBOSE);
! #ifdef FEAT_EVAL
! int use_tfu = ((flags & TAG_NO_TAGFUNC) == 0);
! #endif
! int save_p_ic = p_ic;
!
! /*
! * Change the value of 'ignorecase' according to 'tagcase' for the
! * duration of this function.
! */
! switch (curbuf->b_tc_flags ? curbuf->b_tc_flags : tc_flags)
! {
! case TC_FOLLOWIC: break;
! case TC_IGNORE: p_ic = TRUE; break;
! case TC_MATCH: p_ic = FALSE; break;
! case TC_FOLLOWSCS: p_ic = ignorecase(pat); break;
! case TC_SMART: p_ic = ignorecase_opt(pat, TRUE, TRUE); break;
! }
!
! help_save = curbuf->b_help;
!
! /*
! * Allocate memory for the buffers that are used
! */
! tag_fname = alloc(MAXPATHL + 1);
!
! // check for out of memory situation
! if (tag_fname == NULL)
! goto findtag_end;
!
! if (findtags_state_init(&st, pat, mincount) == FAIL)
! 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
! #ifdef FEAT_CSCOPE
! else if (use_cscope)
! {
! // Make sure we don't mix help and cscope, confuses Coverity.
! help_only = FALSE;
! curbuf->b_help = FALSE;
! }
! #endif
!
! #ifdef FEAT_MULTI_LANG
! if (curbuf->b_help)
! {
! // When "@ab" is specified use only the "ab" language, otherwise
! // search all languages.
! if (st.orgpat.len > 3 && pat[st.orgpat.len - 3] == '@'
! && ASCII_ISALPHA(pat[st.orgpat.len - 2])
! && ASCII_ISALPHA(pat[st.orgpat.len - 1]))
{
! saved_pat = vim_strnsave(pat, st.orgpat.len - 3);
! if (saved_pat != NULL)
! {
! st.help_lang_find = &pat[st.orgpat.len - 2];
! st.orgpat.pat = saved_pat;
! st.orgpat.len -= 3;
! }
}
+ }
+ #endif
+ if (p_tl != 0 && st.orgpat.len > p_tl) // adjust for 'taglength'
+ st.orgpat.len = p_tl;
! save_emsg_off = emsg_off;
! emsg_off = TRUE; // don't want error for invalid RE here
! prepare_pats(&st.orgpat, has_re);
! emsg_off = save_emsg_off;
! if (has_re && st.orgpat.regmatch.regprog == NULL)
! goto findtag_end;
!
! #ifdef FEAT_EVAL
! if (*curbuf->b_p_tfu != NUL && use_tfu && !tfu_in_use)
! {
! tfu_in_use = TRUE;
! retval = find_tagfunc_tags(pat, &st.ga_match[0], &st.match_count,
! flags, buf_ffname);
! tfu_in_use = FALSE;
! if (retval != NOTDONE)
! goto findtag_end;
! }
! #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_MULTI_LANG
! // Set a flag if the file extension is .txt
! if ((flags & TAG_KEEP_LANG)
! && st.help_lang_find == NULL
! && curbuf->b_fname != NULL
! && (i = (int)STRLEN(curbuf->b_fname)) > 4
! && STRICMP(curbuf->b_fname + i - 4, ".txt") == 0)
! st.is_txt = TRUE;
! #endif
! #ifdef FEAT_TAG_BINS
! st.orgpat.regmatch.rm_ic = ((p_ic || !noic)
! && (findall || st.orgpat.headlen == 0 || !p_tbs));
! for (round = 1; round <= 2; ++round)
! {
! st.linear = (st.orgpat.headlen == 0 || !p_tbs || round == 2);
#else
! st.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)
+ {
+ if (find_tags_in_file(tag_fname, &st, flags, buf_ffname) == FAIL)
+ goto findtag_end;
+ if (st.stop_searching
+ #ifdef FEAT_CSCOPE
+ || use_cscope
+ #endif
+ )
+ {
+ retval = OK;
+ break;
+ }
} // end of for-each-file loop
#ifdef FEAT_CSCOPE
***************
*** 2808,2838 ****
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(_(e_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
/*
--- 2940,2973 ----
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 (st.stop_searching || st.linear || (!p_ic && noic) ||
! st.orgpat.regmatch.rm_ic)
! break;
# ifdef FEAT_CSCOPE
! if (use_cscope)
! break;
# endif
!
! // try another time while ignoring case
! st.orgpat.regmatch.rm_ic = TRUE;
}
#endif
! if (!st.stop_searching)
{
! if (!st.did_open && verbose) // never opened any tags file
emsg(_(e_no_tags_file));
retval = OK; // It's OK even when no tag found
}
findtag_end:
! vim_free(st.lbuf);
! vim_regfree(st.orgpat.regmatch.regprog);
vim_free(tag_fname);
#ifdef FEAT_EMACS_TAGS
! vim_free(st.ebuf);
#endif
/*
***************
*** 2840,2881 ****
* matches. When retval == FAIL, free the matches.
*/
if (retval == FAIL)
! match_count = 0;
!
! if (match_count > 0)
! matches = ALLOC_MULT(char_u *, match_count);
! else
! matches = NULL;
! match_count = 0;
! for (mtt = 0; mtt < MT_COUNT; ++mtt)
! {
! for (i = 0; i < ga_match[mtt].ga_len; ++i)
! {
! mfp = ((char_u **)(ga_match[mtt].ga_data))[i];
! if (matches == NULL)
! vim_free(mfp);
! else
! {
! if (!name_only)
! {
! // Change mtt back to zero-based.
! *mfp = *mfp - 1;
!
! // change the TAG_SEP back to NUL
! for (p = mfp + 1; *p != NUL; ++p)
! if (*p == TAG_SEP)
! *p = NUL;
! }
! matches[match_count++] = mfp;
! }
! }
! ga_clear(&ga_match[mtt]);
! hash_clear(&ht_match[mtt]);
! }
!
! *matchesp = matches;
! *num_matches = match_count;
curbuf->b_help = help_save;
#ifdef FEAT_MULTI_LANG
--- 2975,2983 ----
* matches. When retval == FAIL, free the matches.
*/
if (retval == FAIL)
! st.match_count = 0;
! findtags_copy_matches(&st, matchesp, num_matches, name_only);
curbuf->b_help = help_save;
#ifdef FEAT_MULTI_LANG
*** ../vim-8.2.4493/src/testdir/test_tagjump.vim 2021-08-21 15:21:14.662455461 +0100
--- src/testdir/test_tagjump.vim 2022-03-02 20:23:32.515180668 +0000
***************
*** 1427,1432 ****
--- 1427,1437 ----
endtry
call assert_equal(v:true, caught_431)
+ " tag name and file name are not separated by a tab
+ call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "foo Xfile 1"], 'Xtags')
+ call assert_fails('tag foo', 'E431:')
+
call delete('Xtags')
call delete('Xfile')
set tags&
*** ../vim-8.2.4493/src/version.c 2022-03-02 20:11:59.289532141 +0000
--- src/version.c 2022-03-02 20:25:35.066764249 +0000
***************
*** 756,757 ****
--- 756,759 ----
{ /* Add new patch number below this line */
+ /**/
+ 4494,
/**/
--
Fingers not found - Pound head on keyboard to continue.
/// Bram Moolenaar -- Br...@Moolenaar.net --
http://www.Moolenaar.net \\\
/// \\\
\\\ sponsor Vim, vote for features --
http://www.Vim.org/sponsor/ ///
\\\ help me help AIDS victims --
http://ICCF-Holland.org ///