]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 8.2.4494: the find_tags() function is much too long v8.2.4494
authorYegappan Lakshmanan <yegappan@yahoo.com>
Wed, 2 Mar 2022 20:29:35 +0000 (20:29 +0000)
committerBram Moolenaar <Bram@vim.org>
Wed, 2 Mar 2022 20:29:35 +0000 (20:29 +0000)
Problem:    The find_tags() function is much too long.
Solution:   Refactor the function. (Yegappan Lakshmanan, closes #9869)

src/quickfix.c
src/tag.c
src/testdir/test_tagjump.vim
src/version.c

index 10fc4e09f1d01ab6a57a75cc12253572b99526d4..db607171a19719471b4c314490bdcf405b323c99 100644 (file)
@@ -5013,6 +5013,8 @@ ex_make(exarg_T *eap)
     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,11 +5061,13 @@ ex_make(exarg_T *eap)
 
     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 (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);
index b405f1fe681f3e56ff24ea4de0a9dda94b961f87..0ef0a5d5aa653198529a4fdb30c60290d799b307 100644 (file)
--- a/src/tag.c
+++ b/src/tag.c
@@ -1577,76 +1577,104 @@ find_tagfunc_tags(
 #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
+ * State information used during a tag search
  */
-    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
+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
-    char_u     *buf_ffname)            // name of buffer for priority
+#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;
-    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_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
-    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
     {
@@ -1657,14 +1685,27 @@ find_tags(
        TS_SKIP_BACK,           // skipping backwards
        TS_STEP_FORWARD         // stepping forwards
 #endif
-    }  state;                  // Current search state
+    } 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;
-    int                save_emsg_off;
 
 #ifdef FEAT_EMACS_TAGS
     /*
@@ -1679,1127 +1720,1218 @@ find_tags(
     } 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;
+    hash_T     hash = 0;
 
 #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
+    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);
-    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;
-    }
+    int                get_it_again = FALSE;
+    vimconv_T  vimconv;
 
-    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
+#ifdef FEAT_TAG_BINS
+    // This is only to avoid a compiler warning for using search_info
+    // uninitialised.
+    CLEAR_FIELD(search_info);
 #endif
 
     /*
-     * Initialize a few variables
+     * A file that doesn't exist is silently ignored.  Only when not a
+     * single file is found, an error message is given (further on).
      */
-    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;
-    }
+    if (use_cscope)
+       fp = NULL;          // avoid GCC warning
+    else
 #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]))
+#ifdef FEAT_MULTI_LANG
+       if (curbuf->b_help)
        {
-           saved_pat = vim_strnsave(pat, orgpat.len - 3);
-           if (saved_pat != NULL)
+           // 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_lang_find = &pat[orgpat.len - 2];
-               orgpat.pat = saved_pat;
-               orgpat.len -= 3;
+               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 (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
+       if ((fp = mch_fopen((char *)tag_fname, "r")) == NULL)
+           return OK;
 
-#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;
+       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
 
     /*
-     * 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.
+     * Read and parse the lines in the file one by one
      */
-#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)
+    for (;;)
     {
-      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
+#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())
        {
-#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;
+           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;
+       }
 
-           if (p_verbose >= 5)
+       /*
+        * 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)
            {
-               verbose_enter();
-               smsg(_("Searching tags file %s"), tag_fname);
-               verbose_leave();
+               search_info.curr_offset = 0;
+               rewind(fp);
+               state = TS_STEP_FORWARD;
            }
        }
-       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
+        * When jumping around in the file, first read a line to find the
+        * start of the next line.
         */
-       for (;;)
+       if (state == TS_BINARY || state == TS_SKIP_BACK)
        {
-#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)
+           // 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)
            {
-               stop_searching = TRUE;
-               retval = OK;
-               break;
+               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);
            }
-           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)
+           // skip empty and blank lines
+           while (!eof && vim_isblankline(st->lbuf))
            {
-               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;
+               search_info.curr_offset = vim_ftell(fp);
+               eof = vim_fgets(st->lbuf, st->lbuf_size, fp);
            }
-
-           /*
-            * Skipping back (after a match during binary search).
-            */
-           else if (state == TS_SKIP_BACK)
+           if (eof)
            {
-               search_info.curr_offset -= lbuf_size * 2;
-               if (search_info.curr_offset < 0)
-               {
-                   search_info.curr_offset = 0;
-                   rewind(fp);
-                   state = TS_STEP_FORWARD;
-               }
+               // 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;
            }
+       }
 
-           /*
-            * 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)
+       /*
+        * Not jumping around in the file: Read the next line.
+        */
+       else
+#endif
+       {
+           // skip empty and blank lines
+           do
            {
-               // 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))
+#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);
-                   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;
+#endif
+                   eof = vim_fgets(st->lbuf, st->lbuf_size, fp);
                }
-           }
+           } while (!eof && vim_isblankline(st->lbuf));
 
-           /*
-            * Not jumping around in the file: Read the next line.
-            */
-           else
-#endif
+           if (eof)
            {
-               // 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
+               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)
+       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)
            {
-               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 > st->lbuf_size)
                {
-                   // 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);
-                   }
+                   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 (*lbuf == Ctrl_L
+       /*
+        * 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
+               && !use_cscope
 # endif
-                               )
+          )
+       {
+           is_etag = 1;                // in case at the start
+           state = TS_LINEAR;
+           if (!vim_fgets(st->ebuf, LSIZE, fp))
            {
-               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;
+               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)
+               /*
+                * 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)
                    {
-                       // Save current "fp" and "tag_fname" in the stack.
-                       if ((incstack[incstack_idx].etag_fname =
-                                             vim_strsave(tag_fname)) != NULL)
-                       {
-                           char_u *fullpath_ebuf;
+                       char_u *fullpath_ebuf;
 
-                           incstack[incstack_idx].fp = fp;
-                           fp = NULL;
+                       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)
+                       // 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)
                            {
-                               // 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);
+                               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;
            }
+           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)
+       /*
+        * 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])))
            {
-               // 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;
+               if (STRNCMP(st->lbuf, "!_TAG_", 6) != 0)
+                   // Non-header item before the header, e.g. "!" itself.
+                   goto parse_line;
 
-                   /*
-                    * Read header line.
-                    */
+               /*
+                * Read header line.
+                */
 #ifdef FEAT_TAG_BINS
-                   if (STRNCMP(lbuf, "!_TAG_FILE_SORTED\t", 18) == 0)
-                       tag_file_sorted = lbuf[18];
+               if (STRNCMP(st->lbuf, "!_TAG_FILE_SORTED\t", 18) == 0)
+                   tag_file_sorted = st->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;
+               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);
                }
 
-               // Headers ends.
+               // 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.
-                */
+           /*
+            * 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)
+           if (st->linear || use_cscope)
 # else
-               if (linear)
+           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;
-                   orgpat.regmatch.rm_ic = (p_ic || !noic);
-               }
-               else
-                   state = TS_LINEAR;
+               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 && orgpat.regmatch.rm_ic && !sortic)
-               {
-                   // Binary search won't work for ignoring case, use linear
-                   // search.
-                   linear = TRUE;
-                   state = TS_LINEAR;
-               }
-#else
+           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)
+           // 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
                {
-                   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;
+                   // 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;
                }
-#endif
+               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
+       // 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
+               && !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);
+          )
+       {
+           st->lbuf_size *= 2;
+           vim_free(st->lbuf);
+           st->lbuf = alloc(st->lbuf_size);
+           if (st->lbuf == NULL)
+               return FAIL;
+
 #ifdef FEAT_TAG_BINS
-               // this will try the same thing again, make sure the offset is
-               // different
-               search_info.curr_offset = 0;
+           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;
+           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;
            }
 
            /*
-            * 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!
+            * Skip this line if the length of the tag is different and
+            * there is no regexp, or the tag is too short.
             */
-           if (orgpat.headlen
-#ifdef FEAT_EMACS_TAGS
-                           && !is_etag
-#endif
-                                       )
+           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)
            {
-               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;
-               }
+               /*
+                * 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;
 
                /*
-                * Skip this line if the length of the tag is different and
-                * there is no regexp, or the tag is too short.
+                * Compare the current tag with the searched tag.
                 */
-               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;
+               if (sortic)
+                   tagcmp = tag_strnicmp(tagp.tagname, st->orgpat.head,
+                           (size_t)cmplen);
+               else
+                   tagcmp = STRNCMP(tagp.tagname, st->orgpat.head, cmplen);
 
-#ifdef FEAT_TAG_BINS
-               if (state == TS_BINARY)
+               /*
+                * A match with a shorter tag means to search forward.
+                * A match with a longer tag means to search backward.
+                */
+               if (tagcmp == 0)
                {
-                   /*
-                    * 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);
+                   if (cmplen < st->orgpat.headlen)
+                       tagcmp = -1;
+                   else if (cmplen > st->orgpat.headlen)
+                       tagcmp = 1;
+               }
 
-                   /*
-                    * A match with a shorter tag means to search forward.
-                    * A match with a longer tag means to search backward.
-                    */
-                   if (tagcmp == 0)
+               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)
                    {
-                       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;
+                       search_info.low_offset = search_info.curr_offset;
                        if (sortic)
-                           search_info.high_char =
-                                                TOUPPER_ASC(tagp.tagname[0]);
+                           search_info.low_char =
+                               TOUPPER_ASC(tagp.tagname[0]);
                        else
-                           search_info.high_char = tagp.tagname[0];
+                           search_info.low_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 (tagcmp > 0
+                       && search_info.curr_offset != search_info.high_offset)
                {
-                   if (MB_STRNICMP(tagp.tagname, orgpat.head, cmplen) != 0)
-                       state = TS_STEP_FORWARD;
+                   search_info.high_offset = search_info.curr_offset;
+                   if (sortic)
+                       search_info.high_char =
+                           TOUPPER_ASC(tagp.tagname[0]);
                    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;
+                       search_info.high_char = tagp.tagname[0];
                    continue;
                }
-               else if (state == TS_STEP_FORWARD)
+
+               // 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 (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
-                   }
+                   if ((off_T)vim_ftell(fp) > search_info.match_offset)
+                       break;  // past last match
+                   else
+                       continue;       // before first match
                }
-               else
+           }
+           else
 #endif
-                   // skip this match if it can't match
-                   if (MB_STRNICMP(tagp.tagname, orgpat.head, cmplen) != 0)
+               // 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;
-           }
+           /*
+            * 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 = parse_tag_line(lbuf,
+               i = OK;
+       }
+       else
+           i = parse_tag_line(st->lbuf,
 #ifdef FEAT_EMACS_TAGS
-                                      is_etag,
+                   is_etag,
 #endif
-                                              &tagp);
-           if (i == FAIL)
-           {
-               line_error = TRUE;
-               break;
-           }
+                   &tagp);
+       if (i == FAIL)
+       {
+           line_error = TRUE;
+           break;
+       }
 
 #ifdef FEAT_EMACS_TAGS
-           if (is_etag)
-               tagp.fname = ebuf;
+       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 (orgpat.len != cmplen)
-               match = FALSE;
-           else
+       /*
+        * 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)
            {
-               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);
+               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 && orgpat.regmatch.regprog != NULL)
-           {
-               int     cc;
+       /*
+        * 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(&orgpat.regmatch, tagp.tagname, (colnr_T)0);
-               if (match)
+           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)
                {
-                   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;
-                   }
+                   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;
            }
+           *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;
+       /*
+        * 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
+           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(
+           {
+               // Decide in which array to store this match.
+               is_current = test_for_current(
 #ifdef FEAT_EMACS_TAGS
-                           is_etag,
+                       is_etag,
 #endif
-                                    tagp.fname, tagp.fname_end, tag_fname,
-                                    buf_ffname);
+                       tagp.fname, tagp.fname_end, tag_fname,
+                       buf_ffname);
 #ifdef FEAT_EMACS_TAGS
-                   is_static = FALSE;
-                   if (!is_etag)       // emacs tags are never static
+               is_static = FALSE;
+               if (!is_etag)   // emacs tags are never static
 #endif
-                       is_static = test_for_static(&tagp);
+                   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;
-                   }
+               // decide in which of the sixteen tables to store this
+               // match
+               if (is_static)
+               {
+                   if (is_current)
+                       mtt = MT_ST_CUR;
                    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;
+                       mtt = MT_ST_OTH;
                }
-
-               /*
-                * 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)
+               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;
+               /*
+                * 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);
+                   p = mfp;
+                   STRCPY(p, tagp.tagname);
 #ifdef FEAT_MULTI_LANG
-                       p[len] = '@';
-                       STRCPY(p + len + 1, help_lang);
+                   p[len] = '@';
+                   STRCPY(p + len + 1, help_lang);
 #endif
 
-                       heuristic = help_heuristic(tagp.tagname,
-                                   match_re ? matchoff : 0, !match_no_ic);
+                   heuristic = help_heuristic(tagp.tagname,
+                           match_re ? matchoff : 0, !match_no_ic);
 #ifdef FEAT_MULTI_LANG
-                       heuristic += help_pri;
+                   heuristic += help_pri;
 #endif
-                       sprintf((char *)p + len + 1 + ML_EXTRA, "%06d",
-                                                              heuristic);
-                   }
-                   *tagp.tagname_end = TAB;
+                   sprintf((char *)p + len + 1 + ML_EXTRA, "%06d",
+                           heuristic);
                }
-               else if (name_only)
+               *tagp.tagname_end = TAB;
+           }
+           else if (name_only)
+           {
+               if (get_it_again)
                {
-                   if (get_it_again)
-                   {
-                       char_u *temp_end = tagp.command;
+                   char_u *temp_end = tagp.command;
 
-                       if (*temp_end == '/')
-                           while (*temp_end && *temp_end != '\r'
-                                   && *temp_end != '\n'
-                                   && *temp_end != '$')
-                               temp_end++;
+                   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
+                   if (tagp.command + 2 < temp_end)
                    {
-                       len = (int)(tagp.tagname_end - tagp.tagname);
-                       mfp = alloc(sizeof(char_u) + len + 1);
+                       len = (int)(temp_end - tagp.command - 2);
+                       mfp = alloc(len + 2);
                        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;
+                           vim_strncpy(mfp, tagp.command + 2, len);
                    }
+                   else
+                       mfp = NULL;
+                   get_it_again = FALSE;
                }
                else
                {
-                   size_t tag_fname_len = STRLEN(tag_fname);
+                   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;
+               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(ebuf);
-                       len += (int)ebuf_len + 1;
-                   }
-                   else
-                       ++len;
+               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);
+               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);
+                   // 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;
+                   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 (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;
+           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.
-                    */
+               /*
+                * 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
+               if (use_cscope)
+                   hash++;
+               else
 #endif
-                       hash = hash_hash(mfp);
-                   hi = hash_lookup(&ht_match[mtt], mfp, hash);
-                   if (HASHITEM_EMPTY(hi))
+                   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)
                    {
-                       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;
-                       }
+                       // Out of memory! Just forget about the rest.
+                       st->stop_searching = TRUE;
+                       break;
                    }
                    else
-                       // duplicate tag, drop it
-                       vim_free(mfp);
+                   {
+                       ((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;
+       if (use_cscope && eof)
+           break;
 #endif
-       } // forever
+    } // forever
 
-       if (line_error)
-       {
-           semsg(_(e_format_error_in_tags_file_str), tag_fname);
+    if (line_error)
+    {
+       semsg(_(e_format_error_in_tags_file_str), tag_fname);
 #ifdef FEAT_CSCOPE
-           if (!use_cscope)
+       if (!use_cscope)
 #endif
-               semsg(_("Before byte %ld"), (long)vim_ftell(fp));
-           stop_searching = TRUE;
-           line_error = FALSE;
-       }
+           semsg(_("Before byte %ld"), (long)vim_ftell(fp));
+       st->stop_searching = TRUE;
+       line_error = FALSE;
+    }
 
 #ifdef FEAT_CSCOPE
-       if (!use_cscope)
+    if (!use_cscope)
 #endif
-           fclose(fp);
+       fclose(fp);
 #ifdef FEAT_EMACS_TAGS
-       while (incstack_idx)
-       {
-           --incstack_idx;
-           fclose(incstack[incstack_idx].fp);
-           vim_free(incstack[incstack_idx].etag_fname);
-       }
+    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);
+    if (vimconv.vc_type != CONV_NONE)
+       convert_setup(&vimconv, NULL, NULL);
 
 #ifdef FEAT_TAG_BINS
-       tag_file_sorted = NUL;
-       if (sort_error)
+    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)
        {
-           semsg(_(e_tags_file_not_sorted_str), tag_fname);
-           sort_error = FALSE;
+           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
 
-       /*
-        * Stop searching if sufficient tags have been found.
-        */
-       if (match_count >= mincount)
+    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]))
        {
-           retval = OK;
-           stop_searching = TRUE;
+           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;
 
-#ifdef FEAT_CSCOPE
-       if (stop_searching || use_cscope)
+    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
-       if (stop_searching)
+       st.orgpat.regmatch.rm_ic = (p_ic || !noic);
 #endif
-           break;
 
+      /*
+       * 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,31 +2940,34 @@ parse_line:
            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;
+       // 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;
+       if (use_cscope)
+           break;
 # endif
-      orgpat.regmatch.rm_ic = TRUE;    // try another time while ignoring case
+
+       // try another time while ignoring case
+       st.orgpat.regmatch.rm_ic = TRUE;
     }
 #endif
 
-    if (!stop_searching)
+    if (!st.stop_searching)
     {
-       if (!did_open && verbose)       // never opened any tags file
+       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(lbuf);
-    vim_regfree(orgpat.regmatch.regprog);
+    vim_free(st.lbuf);
+    vim_regfree(st.orgpat.regmatch.regprog);
     vim_free(tag_fname);
 #ifdef FEAT_EMACS_TAGS
-    vim_free(ebuf);
+    vim_free(st.ebuf);
 #endif
 
     /*
@@ -2840,42 +2975,9 @@ findtag_end:
      * matches.  When retval == FAIL, free the matches.
      */
     if (retval == FAIL)
-       match_count = 0;
+       st.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;
+    findtags_copy_matches(&st, matchesp, num_matches, name_only);
 
     curbuf->b_help = help_save;
 #ifdef FEAT_MULTI_LANG
index ba55159c7a4a9afa9f836cb634e52bb83926f1b4..4b479be9beb1551745b1045bbaeeec28ac1ad96e 100644 (file)
@@ -1427,6 +1427,11 @@ func Test_tagfile_errors()
   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&
index 0a832778b5f874663a3fe441a0111ef99f0da3af..341a68159c6532e22cde97796c0edf227050190e 100644 (file)
@@ -754,6 +754,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    4494,
 /**/
     4493,
 /**/