To: vim_dev@googlegroups.com Subject: Patch 8.2.4494 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 8.2.4494 Problem: The find_tags() function is much too long. Solution: Refactor the function. (Yegappan Lakshmanan, closes #9869) Files: src/quickfix.c, src/tag.c, src/testdir/test_tagjump.vim *** ../vim-8.2.4493/src/quickfix.c 2022-02-26 10:31:24.699882028 +0000 --- src/quickfix.c 2022-03-02 20:23:32.511180681 +0000 *************** *** 5013,5018 **** --- 5013,5020 ---- int res; char_u *au_name = NULL; int_u save_qfid; + char_u *errorformat = p_efm; + int newlist = TRUE; // Redirect ":grep" to ":vimgrep" if 'grepprg' is "internal". if (grep_internal(eap->cmdidx)) *************** *** 5059,5069 **** incr_quickfix_busy(); ! res = qf_init(wp, fname, (eap->cmdidx != CMD_make ! && eap->cmdidx != CMD_lmake) ? p_gefm : p_efm, ! (eap->cmdidx != CMD_grepadd ! && eap->cmdidx != CMD_lgrepadd), ! qf_cmdtitle(*eap->cmdlinep), enc); if (wp != NULL) { qi = GET_LOC_LIST(wp); --- 5061,5073 ---- incr_quickfix_busy(); ! if (eap->cmdidx != CMD_make && eap->cmdidx != CMD_lmake) ! errorformat = p_gefm; ! if (eap->cmdidx == CMD_grepadd || eap->cmdidx == CMD_lgrepadd) ! newlist = FALSE; ! ! res = qf_init(wp, fname, errorformat, newlist, qf_cmdtitle(*eap->cmdlinep), ! enc); if (wp != NULL) { qi = GET_LOC_LIST(wp); *** ../vim-8.2.4493/src/tag.c 2022-01-28 15:28:00.212927659 +0000 --- src/tag.c 2022-03-02 20:29:21.794140317 +0000 *************** *** 1577,1652 **** #endif /* ! * find_tags() - search for tags in tags files ! * ! * Return FAIL if search completely failed (*num_matches will be 0, *matchesp ! * will be NULL), OK otherwise. ! * ! * There is a priority in which type of tag is recognized. ! * ! * 6. A static or global tag with a full matching tag for the current file. ! * 5. A global tag with a full matching tag for another file. ! * 4. A static tag with a full matching tag for another file. ! * 3. A static or global tag with an ignore-case matching tag for the ! * current file. ! * 2. A global tag with an ignore-case matching tag for another file. ! * 1. A static tag with an ignore-case matching tag for another file. ! * ! * Tags in an emacs-style tags file are always global. ! * ! * flags: ! * TAG_HELP only search for help tags ! * TAG_NAMES only return name of tag ! * TAG_REGEXP use "pat" as a regexp ! * TAG_NOIC don't always ignore case ! * TAG_KEEP_LANG keep language ! * TAG_CSCOPE use cscope results for tags ! * TAG_NO_TAGFUNC do not call the 'tagfunc' function */ ! int ! find_tags( ! char_u *pat, // pattern to search for ! int *num_matches, // return: number of matches found ! char_u ***matchesp, // return: array of matches found ! int flags, ! int mincount, // MAXCOL: find all matches // other: minimal number of matches ! char_u *buf_ffname) // name of buffer for priority { FILE *fp; - char_u *lbuf; // line buffer - int lbuf_size = LSIZE; // length of lbuf - char_u *tag_fname; // name of tag file - tagname_T tn; // info for get_tagfname() - int first_file; // trying first tag file tagptrs_T tagp; - int did_open = FALSE; // did open a tag file - int stop_searching = FALSE; // stop when match found or error - int retval = FAIL; // return value int is_static; // current tag line is static int is_current; // file name matches int eof = FALSE; // found end-of-file char_u *p; char_u *s; int i; #ifdef FEAT_TAG_BINS int tag_file_sorted = NUL; // !_TAG_FILE_SORTED value - struct tag_search_info // Binary search file offsets - { - off_T low_offset; // offset for first char of first line that - // could match - off_T high_offset; // offset of char after last line that could - // match - off_T curr_offset; // Current file offset in search range - off_T curr_offset_used; // curr_offset used when skipping back - off_T match_offset; // Where the binary search found a tag - int low_char; // first char at low_offset - int high_char; // first char at high_offset - } search_info; off_T filesize; int tagcmp; off_T offset; - int round; #endif enum { --- 1577,1680 ---- #endif /* ! * State information used during a tag search */ ! typedef struct { ! pat_T orgpat; // holds unconverted pattern info ! #ifdef FEAT_MULTI_LANG ! char_u *help_lang_find; // lang to be found ! int is_txt; // flag of file extension ! #endif ! int did_open; // did open a tag file ! int mincount; // MAXCOL: find all matches // other: minimal number of matches ! #ifdef FEAT_TAG_BINS ! int linear; // do a linear search ! #endif ! char_u *lbuf; // line buffer ! int lbuf_size; // length of lbuf ! #ifdef FEAT_EMACS_TAGS ! char_u *ebuf; // additional buffer for etag fname ! #endif ! int match_count; // number of matches found ! garray_T ga_match[MT_COUNT]; // stores matches in sequence ! hashtab_T ht_match[MT_COUNT]; // stores matches by key ! int stop_searching; // stop when match found or error ! } findtags_state_T; ! ! /* ! * Initialize the state used by find_tags() ! */ ! static int ! findtags_state_init(findtags_state_T *st, char_u *pat, int mincount) ! { ! int mtt; ! ! st->orgpat.pat = pat; ! st->orgpat.len = (int)STRLEN(pat); ! st->orgpat.regmatch.regprog = NULL; ! #ifdef FEAT_MULTI_LANG ! st->help_lang_find = NULL; ! st->is_txt = FALSE; ! #endif ! st->did_open = FALSE; ! st->mincount = mincount; ! st->lbuf_size = LSIZE; ! st->lbuf = alloc(st->lbuf_size); ! #ifdef FEAT_EMACS_TAGS ! st->ebuf = alloc(LSIZE); ! #endif ! st->match_count = 0; ! st->stop_searching = FALSE; ! ! for (mtt = 0; mtt < MT_COUNT; ++mtt) ! { ! ga_init2(&st->ga_match[mtt], sizeof(char_u *), 100); ! hash_init(&st->ht_match[mtt]); ! } ! ! // check for out of memory situation ! if (st->lbuf == NULL ! #ifdef FEAT_EMACS_TAGS ! || st->ebuf == NULL ! #endif ! ) ! return FAIL; ! ! return OK; ! } ! ! /* ! * Search for tags in the 'tag_fname' tags file. ! * Information needed to search for the tags is in the 'st' state structure. ! * The matching tags are returned in 'st'. ! * Returns OK if successfully processed the file and FAIL on memory allocation ! * failure. ! */ ! static int ! find_tags_in_file( ! char_u *tag_fname, ! findtags_state_T *st, ! int flags, ! char_u *buf_ffname) { FILE *fp; tagptrs_T tagp; int is_static; // current tag line is static int is_current; // file name matches int eof = FALSE; // found end-of-file char_u *p; char_u *s; int i; + #ifdef FEAT_MULTI_LANG + int help_pri = 0; + char_u help_lang[3]; // lang of current tags file + #endif #ifdef FEAT_TAG_BINS int tag_file_sorted = NUL; // !_TAG_FILE_SORTED value off_T filesize; int tagcmp; off_T offset; #endif enum { *************** *** 1657,1670 **** TS_SKIP_BACK, // skipping backwards TS_STEP_FORWARD // stepping forwards #endif ! } state; // Current search state int cmplen; int match; // matches int match_no_ic = 0;// matches with rm_ic == FALSE int match_re; // match with regexp int matchoff = 0; - int save_emsg_off; #ifdef FEAT_EMACS_TAGS /* --- 1685,1711 ---- TS_SKIP_BACK, // skipping backwards TS_STEP_FORWARD // stepping forwards #endif ! } state; // Current search state ! #ifdef FEAT_TAG_BINS ! struct tag_search_info // Binary search file offsets ! { ! off_T low_offset; // offset for first char of first line that ! // could match ! off_T high_offset; // offset of char after last line that could ! // match ! off_T curr_offset; // Current file offset in search range ! off_T curr_offset_used; // curr_offset used when skipping back ! off_T match_offset; // Where the binary search found a tag ! int low_char; // first char at low_offset ! int high_char; // first char at high_offset ! } search_info; ! #endif int cmplen; int match; // matches int match_no_ic = 0;// matches with rm_ic == FALSE int match_re; // match with regexp int matchoff = 0; #ifdef FEAT_EMACS_TAGS /* *************** *** 1679,2805 **** } incstack[INCSTACK_SIZE]; int incstack_idx = 0; // index in incstack - char_u *ebuf; // additional buffer for etag fname int is_etag; // current file is emaces style #endif char_u *mfp; - garray_T ga_match[MT_COUNT]; // stores matches in sequence - hashtab_T ht_match[MT_COUNT]; // stores matches by key - hash_T hash = 0; - int match_count = 0; // number of matches found - char_u **matches; int mtt; ! int help_save; ! #ifdef FEAT_MULTI_LANG ! int help_pri = 0; ! char_u *help_lang_find = NULL; // lang to be found ! char_u help_lang[3]; // lang of current tags file ! char_u *saved_pat = NULL; // copy of pat[] ! int is_txt = FALSE; // flag of file extension ! #endif ! ! pat_T orgpat; // holds unconverted pattern info ! vimconv_T vimconv; #ifdef FEAT_TAG_BINS - int findall = (mincount == MAXCOL || mincount == TAG_MANY); - // find all matching tags int sort_error = FALSE; // tags file not sorted - int linear; // do a linear search int sortic = FALSE; // tag file sorted in nocase #endif int line_error = FALSE; // syntax error int has_re = (flags & TAG_REGEXP); // regexp used int help_only = (flags & TAG_HELP); int name_only = (flags & TAG_NAMES); - int noic = (flags & TAG_NOIC); - int get_it_again = FALSE; #ifdef FEAT_CSCOPE int use_cscope = (flags & TAG_CSCOPE); #endif ! int verbose = (flags & TAG_VERBOSE); ! #ifdef FEAT_EVAL ! int use_tfu = ((flags & TAG_NO_TAGFUNC) == 0); ! #endif ! int save_p_ic = p_ic; ! ! /* ! * Change the value of 'ignorecase' according to 'tagcase' for the ! * duration of this function. ! */ ! switch (curbuf->b_tc_flags ? curbuf->b_tc_flags : tc_flags) ! { ! case TC_FOLLOWIC: break; ! case TC_IGNORE: p_ic = TRUE; break; ! case TC_MATCH: p_ic = FALSE; break; ! case TC_FOLLOWSCS: p_ic = ignorecase(pat); break; ! case TC_SMART: p_ic = ignorecase_opt(pat, TRUE, TRUE); break; ! } - help_save = curbuf->b_help; - orgpat.pat = pat; - orgpat.regmatch.regprog = NULL; vimconv.vc_type = CONV_NONE; ! /* ! * Allocate memory for the buffers that are used ! */ ! lbuf = alloc(lbuf_size); ! tag_fname = alloc(MAXPATHL + 1); ! #ifdef FEAT_EMACS_TAGS ! ebuf = alloc(LSIZE); ! #endif ! for (mtt = 0; mtt < MT_COUNT; ++mtt) ! { ! ga_init2(&ga_match[mtt], sizeof(char_u *), 100); ! hash_init(&ht_match[mtt]); ! } ! ! // check for out of memory situation ! if (lbuf == NULL || tag_fname == NULL ! #ifdef FEAT_EMACS_TAGS ! || ebuf == NULL ! #endif ! ) ! goto findtag_end; ! ! #ifdef FEAT_CSCOPE ! STRCPY(tag_fname, "from cscope"); // for error messages #endif /* ! * Initialize a few variables */ - if (help_only) // want tags from help file - curbuf->b_help = TRUE; // will be restored later #ifdef FEAT_CSCOPE ! else if (use_cscope) ! { ! // Make sure we don't mix help and cscope, confuses Coverity. ! help_only = FALSE; ! curbuf->b_help = FALSE; ! } #endif - - orgpat.len = (int)STRLEN(pat); - #ifdef FEAT_MULTI_LANG - if (curbuf->b_help) { ! // When "@ab" is specified use only the "ab" language, otherwise ! // search all languages. ! if (orgpat.len > 3 && pat[orgpat.len - 3] == '@' ! && ASCII_ISALPHA(pat[orgpat.len - 2]) ! && ASCII_ISALPHA(pat[orgpat.len - 1])) { ! saved_pat = vim_strnsave(pat, orgpat.len - 3); ! if (saved_pat != NULL) { ! help_lang_find = &pat[orgpat.len - 2]; ! orgpat.pat = saved_pat; ! orgpat.len -= 3; } } - } #endif - if (p_tl != 0 && orgpat.len > p_tl) // adjust for 'taglength' - orgpat.len = p_tl; ! save_emsg_off = emsg_off; ! emsg_off = TRUE; // don't want error for invalid RE here ! prepare_pats(&orgpat, has_re); ! emsg_off = save_emsg_off; ! if (has_re && orgpat.regmatch.regprog == NULL) ! goto findtag_end; ! ! #ifdef FEAT_TAG_BINS ! // This is only to avoid a compiler warning for using search_info ! // uninitialised. ! CLEAR_FIELD(search_info); ! #endif ! #ifdef FEAT_EVAL ! if (*curbuf->b_p_tfu != NUL && use_tfu && !tfu_in_use) ! { ! tfu_in_use = TRUE; ! retval = find_tagfunc_tags(pat, &ga_match[0], &match_count, ! flags, buf_ffname); ! tfu_in_use = FALSE; ! if (retval != NOTDONE) ! goto findtag_end; } #endif /* ! * When finding a specified number of matches, first try with matching ! * case, so binary search can be used, and try ignore-case matches in a ! * second loop. ! * When finding all matches, 'tagbsearch' is off, or there is no fixed ! * string to look for, ignore case right away to avoid going though the ! * tags files twice. ! * When the tag file is case-fold sorted, it is either one or the other. ! * Only ignore case when TAG_NOIC not used or 'ignorecase' set. */ ! #ifdef FEAT_MULTI_LANG ! // Set a flag if the file extension is .txt ! if ((flags & TAG_KEEP_LANG) ! && help_lang_find == NULL ! && curbuf->b_fname != NULL ! && (i = (int)STRLEN(curbuf->b_fname)) > 4 ! && STRICMP(curbuf->b_fname + i - 4, ".txt") == 0) ! is_txt = TRUE; ! #endif ! #ifdef FEAT_TAG_BINS ! orgpat.regmatch.rm_ic = ((p_ic || !noic) ! && (findall || orgpat.headlen == 0 || !p_tbs)); ! for (round = 1; round <= 2; ++round) { ! linear = (orgpat.headlen == 0 || !p_tbs || round == 2); ! #else ! orgpat.regmatch.rm_ic = (p_ic || !noic); ! #endif ! ! /* ! * Try tag file names from tags option one by one. ! */ ! for (first_file = TRUE; ! #ifdef FEAT_CSCOPE ! use_cscope || #endif ! get_tagfname(&tn, first_file, tag_fname) == OK; ! first_file = FALSE) ! { /* ! * A file that doesn't exist is silently ignored. Only when not a ! * single file is found, an error message is given (further on). */ ! #ifdef FEAT_CSCOPE ! if (use_cscope) ! fp = NULL; // avoid GCC warning ! else ! #endif { ! #ifdef FEAT_MULTI_LANG ! if (curbuf->b_help) ! { ! // Keep en if the file extension is .txt ! if (is_txt) ! STRCPY(help_lang, "en"); ! else ! { ! // Prefer help tags according to 'helplang'. Put the ! // two-letter language name in help_lang[]. ! i = (int)STRLEN(tag_fname); ! if (i > 3 && tag_fname[i - 3] == '-') ! STRCPY(help_lang, tag_fname + i - 2); ! else ! STRCPY(help_lang, "en"); ! } ! // When searching for a specific language skip tags files ! // for other languages. ! if (help_lang_find != NULL ! && STRICMP(help_lang, help_lang_find) != 0) ! continue; ! ! // For CTRL-] in a help file prefer a match with the same ! // language. ! if ((flags & TAG_KEEP_LANG) ! && help_lang_find == NULL ! && curbuf->b_fname != NULL ! && (i = (int)STRLEN(curbuf->b_fname)) > 4 ! && curbuf->b_fname[i - 1] == 'x' ! && curbuf->b_fname[i - 4] == '.' ! && STRNICMP(curbuf->b_fname + i - 3, help_lang, 2) == 0) ! help_pri = 0; ! else ! { ! help_pri = 1; ! for (s = p_hlg; *s != NUL; ++s) ! { ! if (STRNICMP(s, help_lang, 2) == 0) ! break; ! ++help_pri; ! if ((s = vim_strchr(s, ',')) == NULL) ! break; ! } ! if (s == NULL || *s == NUL) ! { ! // Language not in 'helplang': use last, prefer English, ! // unless found already. ! ++help_pri; ! if (STRICMP(help_lang, "en") != 0) ! ++help_pri; ! } ! } ! } ! #endif ! ! if ((fp = mch_fopen((char *)tag_fname, "r")) == NULL) ! continue; ! if (p_verbose >= 5) { ! verbose_enter(); ! smsg(_("Searching tags file %s"), tag_fname); ! verbose_leave(); } } - did_open = TRUE; // remember that we found at least one file - - state = TS_START; // we're at the start of the file - #ifdef FEAT_EMACS_TAGS - is_etag = 0; // default is: not emacs style - #endif /* ! * Read and parse the lines in the file one by one */ ! for (;;) { ! #ifdef FEAT_TAG_BINS ! // check for CTRL-C typed, more often when jumping around ! if (state == TS_BINARY || state == TS_SKIP_BACK) ! line_breakcheck(); ! else ! #endif ! fast_breakcheck(); ! if ((flags & TAG_INS_COMP)) // Double brackets for gcc ! ins_compl_check_keys(30, FALSE); ! if (got_int || ins_compl_interrupted()) { ! stop_searching = TRUE; ! break; ! } ! // When mincount is TAG_MANY, stop when enough matches have been ! // found (for completion). ! if (mincount == TAG_MANY && match_count >= TAG_MANY) ! { ! stop_searching = TRUE; ! retval = OK; ! break; } ! if (get_it_again) ! goto line_read_in; ! #ifdef FEAT_TAG_BINS ! /* ! * For binary search: compute the next offset to use. ! */ ! if (state == TS_BINARY) { ! offset = search_info.low_offset + ((search_info.high_offset ! - search_info.low_offset) / 2); ! if (offset == search_info.curr_offset) ! break; // End the binary search without a match. ! else ! search_info.curr_offset = offset; } ! ! /* ! * Skipping back (after a match during binary search). ! */ ! else if (state == TS_SKIP_BACK) { ! search_info.curr_offset -= lbuf_size * 2; ! if (search_info.curr_offset < 0) ! { ! search_info.curr_offset = 0; ! rewind(fp); ! state = TS_STEP_FORWARD; ! } } ! /* ! * When jumping around in the file, first read a line to find the ! * start of the next line. ! */ ! if (state == TS_BINARY || state == TS_SKIP_BACK) { ! // Adjust the search file offset to the correct position ! search_info.curr_offset_used = search_info.curr_offset; ! vim_fseek(fp, search_info.curr_offset, SEEK_SET); ! eof = vim_fgets(lbuf, lbuf_size, fp); ! if (!eof && search_info.curr_offset != 0) ! { ! search_info.curr_offset = vim_ftell(fp); ! if (search_info.curr_offset == search_info.high_offset) ! { ! // oops, gone a bit too far; try from low offset ! vim_fseek(fp, search_info.low_offset, SEEK_SET); ! search_info.curr_offset = search_info.low_offset; ! } ! eof = vim_fgets(lbuf, lbuf_size, fp); ! } ! // skip empty and blank lines ! while (!eof && vim_isblankline(lbuf)) { search_info.curr_offset = vim_ftell(fp); ! eof = vim_fgets(lbuf, lbuf_size, fp); ! } ! if (eof) ! { ! // Hit end of file. Skip backwards. ! state = TS_SKIP_BACK; ! search_info.match_offset = vim_ftell(fp); ! search_info.curr_offset = search_info.curr_offset_used; ! continue; } ! } ! /* ! * Not jumping around in the file: Read the next line. ! */ ! else ! #endif { - // skip empty and blank lines - do - { - #ifdef FEAT_CSCOPE - if (use_cscope) - eof = cs_fgets(lbuf, lbuf_size); - else - #endif - { - search_info.curr_offset = vim_ftell(fp); - eof = vim_fgets(lbuf, lbuf_size, fp); - } - } while (!eof && vim_isblankline(lbuf)); - - if (eof) - { #ifdef FEAT_EMACS_TAGS ! if (incstack_idx) // this was an included file ! { ! --incstack_idx; ! fclose(fp); // end of this file ... ! fp = incstack[incstack_idx].fp; ! STRCPY(tag_fname, incstack[incstack_idx].etag_fname); ! vim_free(incstack[incstack_idx].etag_fname); ! is_etag = 1; // (only etags can include) ! continue; // ... continue with parent file ! } ! else ! #endif ! break; // end of file } } line_read_in: ! if (vimconv.vc_type != CONV_NONE) ! { ! char_u *conv_line; ! int len; ! // Convert every line. Converting the pattern from 'enc' to ! // the tags file encoding doesn't work, because characters are ! // not recognized. ! conv_line = string_convert(&vimconv, lbuf, NULL); ! if (conv_line != NULL) ! { ! // Copy or swap lbuf and conv_line. ! len = (int)STRLEN(conv_line) + 1; ! if (len > lbuf_size) ! { ! vim_free(lbuf); ! lbuf = conv_line; ! lbuf_size = len; ! } ! else ! { ! STRCPY(lbuf, conv_line); ! vim_free(conv_line); ! } } } #ifdef FEAT_EMACS_TAGS ! /* ! * Emacs tags line with CTRL-L: New file name on next line. ! * The file name is followed by a ','. ! * Remember etag file name in ebuf. ! */ ! if (*lbuf == Ctrl_L # ifdef FEAT_CSCOPE ! && !use_cscope # endif ! ) { ! is_etag = 1; // in case at the start ! state = TS_LINEAR; ! if (!vim_fgets(ebuf, LSIZE, fp)) ! { ! for (p = ebuf; *p && *p != ','; p++) ! ; ! *p = NUL; ! /* ! * atoi(p+1) is the number of bytes before the next ^L ! * unless it is an include statement. ! */ ! if (STRNCMP(p + 1, "include", 7) == 0 ! && incstack_idx < INCSTACK_SIZE) ! { ! // Save current "fp" and "tag_fname" in the stack. ! if ((incstack[incstack_idx].etag_fname = ! vim_strsave(tag_fname)) != NULL) { ! char_u *fullpath_ebuf; ! ! incstack[incstack_idx].fp = fp; ! fp = NULL; ! ! // Figure out "tag_fname" and "fp" to use for ! // included file. ! fullpath_ebuf = expand_tag_fname(ebuf, ! tag_fname, FALSE); ! if (fullpath_ebuf != NULL) ! { ! fp = mch_fopen((char *)fullpath_ebuf, "r"); ! if (fp != NULL) ! { ! if (STRLEN(fullpath_ebuf) > LSIZE) ! semsg(_(e_tag_file_path_truncated_for_str), ebuf); ! vim_strncpy(tag_fname, fullpath_ebuf, ! MAXPATHL); ! ++incstack_idx; ! is_etag = 0; // we can include anything ! } ! vim_free(fullpath_ebuf); ! } ! if (fp == NULL) { ! // Can't open the included file, skip it and ! // restore old value of "fp". ! fp = incstack[incstack_idx].fp; ! vim_free(incstack[incstack_idx].etag_fname); } } } } - continue; } #endif ! /* ! * When still at the start of the file, check for Emacs tags file ! * format, and for "not sorted" flag. ! */ ! if (state == TS_START) { ! // The header ends when the line sorts below "!_TAG_". When ! // case is folded lower case letters sort before "_". ! if (STRNCMP(lbuf, "!_TAG_", 6) <= 0 ! || (lbuf[0] == '!' && ASCII_ISLOWER(lbuf[1]))) ! { ! if (STRNCMP(lbuf, "!_TAG_", 6) != 0) ! // Non-header item before the header, e.g. "!" itself. ! goto parse_line; ! ! /* ! * Read header line. ! */ #ifdef FEAT_TAG_BINS ! if (STRNCMP(lbuf, "!_TAG_FILE_SORTED\t", 18) == 0) ! tag_file_sorted = lbuf[18]; #endif ! if (STRNCMP(lbuf, "!_TAG_FILE_ENCODING\t", 20) == 0) ! { ! // Prepare to convert every line from the specified ! // encoding to 'encoding'. ! for (p = lbuf + 20; *p > ' ' && *p < 127; ++p) ! ; ! *p = NUL; ! convert_setup(&vimconv, lbuf + 20, p_enc); ! } ! ! // Read the next line. Unrecognized flags are ignored. ! continue; } ! // Headers ends. #ifdef FEAT_TAG_BINS ! /* ! * When there is no tag head, or ignoring case, need to do a ! * linear search. ! * When no "!_TAG_" is found, default to binary search. If ! * the tag file isn't sorted, the second loop will find it. ! * When "!_TAG_FILE_SORTED" found: start binary search if ! * flag set. ! * For cscope, it's always linear. ! */ # ifdef FEAT_CSCOPE ! if (linear || use_cscope) # else ! if (linear) # endif ! state = TS_LINEAR; ! else if (tag_file_sorted == NUL) ! state = TS_BINARY; ! else if (tag_file_sorted == '1') ! state = TS_BINARY; ! else if (tag_file_sorted == '2') ! { ! state = TS_BINARY; ! sortic = TRUE; ! orgpat.regmatch.rm_ic = (p_ic || !noic); ! } ! else ! state = TS_LINEAR; ! if (state == TS_BINARY && orgpat.regmatch.rm_ic && !sortic) ! { ! // Binary search won't work for ignoring case, use linear ! // search. ! linear = TRUE; ! state = TS_LINEAR; ! } ! #else state = TS_LINEAR; #endif #ifdef FEAT_TAG_BINS ! // When starting a binary search, get the size of the file and ! // compute the first offset. ! if (state == TS_BINARY) ! { ! if (vim_fseek(fp, 0L, SEEK_END) != 0) ! // can't seek, don't use binary search ! state = TS_LINEAR; ! else ! { ! // Get the tag file size (don't use mch_fstat(), it's ! // not portable). Don't use lseek(), it doesn't work ! // properly on MacOS Catalina. ! filesize = vim_ftell(fp); ! vim_fseek(fp, 0L, SEEK_SET); ! ! // Calculate the first read offset in the file. Start ! // the search in the middle of the file. ! search_info.low_offset = 0; ! search_info.low_char = 0; ! search_info.high_offset = filesize; ! search_info.curr_offset = 0; ! search_info.high_char = 0xff; ! } ! continue; } ! #endif } parse_line: ! // When the line is too long the NUL will not be in the ! // last-but-one byte (see vim_fgets()). ! // Has been reported for Mozilla JS with extremely long names. ! // In that case we need to increase lbuf_size. ! if (lbuf[lbuf_size - 2] != NUL #ifdef FEAT_CSCOPE ! && !use_cscope #endif ! ) ! { ! lbuf_size *= 2; ! vim_free(lbuf); ! lbuf = alloc(lbuf_size); ! if (lbuf == NULL) ! goto findtag_end; ! ! if (state == TS_STEP_FORWARD) ! // Seek to the same position to read the same line again ! vim_fseek(fp, search_info.curr_offset, SEEK_SET); #ifdef FEAT_TAG_BINS ! // this will try the same thing again, make sure the offset is ! // different ! search_info.curr_offset = 0; #endif ! continue; } /* ! * Figure out where the different strings are in this line. ! * For "normal" tags: Do a quick check if the tag matches. ! * This speeds up tag searching a lot! */ ! if (orgpat.headlen ! #ifdef FEAT_EMACS_TAGS ! && !is_etag ! #endif ! ) { ! CLEAR_FIELD(tagp); ! tagp.tagname = lbuf; ! tagp.tagname_end = vim_strchr(lbuf, TAB); ! if (tagp.tagname_end == NULL) ! { ! // Corrupted tag line. ! line_error = TRUE; ! break; ! } /* ! * Skip this line if the length of the tag is different and ! * there is no regexp, or the tag is too short. */ ! cmplen = (int)(tagp.tagname_end - tagp.tagname); ! if (p_tl != 0 && cmplen > p_tl) // adjust for 'taglength' ! cmplen = p_tl; ! if (has_re && orgpat.headlen < cmplen) ! cmplen = orgpat.headlen; ! else if (state == TS_LINEAR && orgpat.headlen != cmplen) ! continue; ! #ifdef FEAT_TAG_BINS ! if (state == TS_BINARY) { ! /* ! * Simplistic check for unsorted tags file. ! */ ! i = (int)tagp.tagname[0]; ! if (sortic) ! i = (int)TOUPPER_ASC(tagp.tagname[0]); ! if (i < search_info.low_char || i > search_info.high_char) ! sort_error = TRUE; ! ! /* ! * Compare the current tag with the searched tag. ! */ ! if (sortic) ! tagcmp = tag_strnicmp(tagp.tagname, orgpat.head, ! (size_t)cmplen); ! else ! tagcmp = STRNCMP(tagp.tagname, orgpat.head, cmplen); ! ! /* ! * A match with a shorter tag means to search forward. ! * A match with a longer tag means to search backward. ! */ ! if (tagcmp == 0) ! { ! if (cmplen < orgpat.headlen) ! tagcmp = -1; ! else if (cmplen > orgpat.headlen) ! tagcmp = 1; ! } ! if (tagcmp == 0) ! { ! // We've located the tag, now skip back and search ! // forward until the first matching tag is found. ! state = TS_SKIP_BACK; ! search_info.match_offset = search_info.curr_offset; ! continue; ! } ! if (tagcmp < 0) ! { ! search_info.curr_offset = vim_ftell(fp); ! if (search_info.curr_offset < search_info.high_offset) ! { ! search_info.low_offset = search_info.curr_offset; ! if (sortic) ! search_info.low_char = ! TOUPPER_ASC(tagp.tagname[0]); ! else ! search_info.low_char = tagp.tagname[0]; ! continue; ! } ! } ! if (tagcmp > 0 ! && search_info.curr_offset != search_info.high_offset) { ! search_info.high_offset = search_info.curr_offset; if (sortic) ! search_info.high_char = ! TOUPPER_ASC(tagp.tagname[0]); else ! search_info.high_char = tagp.tagname[0]; continue; } - - // No match yet and are at the end of the binary search. - break; } ! else if (state == TS_SKIP_BACK) { ! if (MB_STRNICMP(tagp.tagname, orgpat.head, cmplen) != 0) ! state = TS_STEP_FORWARD; else ! // Have to skip back more. Restore the curr_offset ! // used, otherwise we get stuck at a long line. ! search_info.curr_offset = search_info.curr_offset_used; continue; } ! else if (state == TS_STEP_FORWARD) { ! if (MB_STRNICMP(tagp.tagname, orgpat.head, cmplen) != 0) ! { ! if ((off_T)vim_ftell(fp) > search_info.match_offset) ! break; // past last match ! else ! continue; // before first match ! } } ! else #endif ! // skip this match if it can't match ! if (MB_STRNICMP(tagp.tagname, orgpat.head, cmplen) != 0) continue; ! /* ! * Can be a matching tag, isolate the file name and command. ! */ ! tagp.fname = tagp.tagname_end + 1; ! tagp.fname_end = vim_strchr(tagp.fname, TAB); ! tagp.command = tagp.fname_end + 1; ! if (tagp.fname_end == NULL) ! i = FAIL; ! else ! i = OK; ! } else ! i = parse_tag_line(lbuf, #ifdef FEAT_EMACS_TAGS ! is_etag, #endif ! &tagp); ! if (i == FAIL) ! { ! line_error = TRUE; ! break; ! } #ifdef FEAT_EMACS_TAGS ! if (is_etag) ! tagp.fname = ebuf; #endif ! /* ! * First try matching with the pattern literally (also when it is ! * a regexp). ! */ ! cmplen = (int)(tagp.tagname_end - tagp.tagname); ! if (p_tl != 0 && cmplen > p_tl) // adjust for 'taglength' ! cmplen = p_tl; ! // if tag length does not match, don't try comparing ! if (orgpat.len != cmplen) ! match = FALSE; ! else { ! if (orgpat.regmatch.rm_ic) ! { ! match = (MB_STRNICMP(tagp.tagname, orgpat.pat, cmplen) == 0); ! if (match) ! match_no_ic = (STRNCMP(tagp.tagname, orgpat.pat, ! cmplen) == 0); ! } ! else ! match = (STRNCMP(tagp.tagname, orgpat.pat, cmplen) == 0); } ! /* ! * Has a regexp: Also find tags matching regexp. ! */ ! match_re = FALSE; ! if (!match && orgpat.regmatch.regprog != NULL) ! { ! int cc; ! cc = *tagp.tagname_end; ! *tagp.tagname_end = NUL; ! match = vim_regexec(&orgpat.regmatch, tagp.tagname, (colnr_T)0); ! if (match) { ! matchoff = (int)(orgpat.regmatch.startp[0] - tagp.tagname); ! if (orgpat.regmatch.rm_ic) ! { ! orgpat.regmatch.rm_ic = FALSE; ! match_no_ic = vim_regexec(&orgpat.regmatch, tagp.tagname, ! (colnr_T)0); ! orgpat.regmatch.rm_ic = TRUE; ! } } - *tagp.tagname_end = cc; - match_re = TRUE; } ! /* ! * If a match is found, add it to ht_match[] and ga_match[]. ! */ ! if (match) ! { ! int len = 0; #ifdef FEAT_CSCOPE ! if (use_cscope) ! { ! // Don't change the ordering, always use the same table. ! mtt = MT_GL_OTH; ! } ! else #endif ! { ! // Decide in which array to store this match. ! is_current = test_for_current( #ifdef FEAT_EMACS_TAGS ! is_etag, #endif ! tagp.fname, tagp.fname_end, tag_fname, ! buf_ffname); #ifdef FEAT_EMACS_TAGS ! is_static = FALSE; ! if (!is_etag) // emacs tags are never static #endif ! is_static = test_for_static(&tagp); ! // decide in which of the sixteen tables to store this ! // match ! if (is_static) ! { ! if (is_current) ! mtt = MT_ST_CUR; ! else ! mtt = MT_ST_OTH; ! } else ! { ! if (is_current) ! mtt = MT_GL_CUR; ! else ! mtt = MT_GL_OTH; ! } ! if (orgpat.regmatch.rm_ic && !match_no_ic) ! mtt += MT_IC_OFF; ! if (match_re) ! mtt += MT_RE_OFF; } ! ! /* ! * Add the found match in ht_match[mtt] and ga_match[mtt]. ! * Store the info we need later, which depends on the kind of ! * tags we are dealing with. ! */ ! if (help_only) { #ifdef FEAT_MULTI_LANG # define ML_EXTRA 3 #else # define ML_EXTRA 0 #endif ! /* ! * Append the help-heuristic number after the tagname, for ! * sorting it later. The heuristic is ignored for ! * detecting duplicates. ! * The format is {tagname}@{lang}NUL{heuristic}NUL ! */ ! *tagp.tagname_end = NUL; ! len = (int)(tagp.tagname_end - tagp.tagname); ! mfp = alloc(sizeof(char_u) + len + 10 + ML_EXTRA + 1); ! if (mfp != NULL) ! { ! int heuristic; ! p = mfp; ! STRCPY(p, tagp.tagname); #ifdef FEAT_MULTI_LANG ! p[len] = '@'; ! STRCPY(p + len + 1, help_lang); #endif ! heuristic = help_heuristic(tagp.tagname, ! match_re ? matchoff : 0, !match_no_ic); #ifdef FEAT_MULTI_LANG ! heuristic += help_pri; #endif ! sprintf((char *)p + len + 1 + ML_EXTRA, "%06d", ! heuristic); ! } ! *tagp.tagname_end = TAB; } ! else if (name_only) { ! if (get_it_again) ! { ! char_u *temp_end = tagp.command; ! if (*temp_end == '/') ! while (*temp_end && *temp_end != '\r' ! && *temp_end != '\n' ! && *temp_end != '$') ! temp_end++; ! if (tagp.command + 2 < temp_end) ! { ! len = (int)(temp_end - tagp.command - 2); ! mfp = alloc(len + 2); ! if (mfp != NULL) ! vim_strncpy(mfp, tagp.command + 2, len); ! } ! else ! mfp = NULL; ! get_it_again = FALSE; ! } ! else { ! len = (int)(tagp.tagname_end - tagp.tagname); ! mfp = alloc(sizeof(char_u) + len + 1); if (mfp != NULL) ! vim_strncpy(mfp, tagp.tagname, len); ! ! // if wanted, re-read line to get long form too ! if (State & INSERT) ! get_it_again = p_sft; } } else { ! size_t tag_fname_len = STRLEN(tag_fname); #ifdef FEAT_EMACS_TAGS ! size_t ebuf_len = 0; #endif ! // Save the tag in a buffer. ! // Use 0x02 to separate fields (Can't use NUL because the ! // hash key is terminated by NUL, or Ctrl_A because that is ! // part of some Emacs tag files -- see parse_tag_line). ! // Emacs tag: <0x02><0x02> ! // other tag: <0x02><0x02> ! // without Emacs tags: <0x02> ! // Here is the "mtt" value plus 1 to avoid NUL. ! len = (int)tag_fname_len + (int)STRLEN(lbuf) + 3; #ifdef FEAT_EMACS_TAGS ! if (is_etag) ! { ! ebuf_len = STRLEN(ebuf); ! len += (int)ebuf_len + 1; ! } ! else ! ++len; #endif ! mfp = alloc(sizeof(char_u) + len + 1); ! if (mfp != NULL) ! { ! p = mfp; ! p[0] = mtt + 1; ! STRCPY(p + 1, tag_fname); #ifdef BACKSLASH_IN_FILENAME ! // Ignore differences in slashes, avoid adding ! // both path/file and path\file. ! slash_adjust(p + 1); #endif ! p[tag_fname_len + 1] = TAG_SEP; ! s = p + 1 + tag_fname_len + 1; #ifdef FEAT_EMACS_TAGS ! if (is_etag) ! { ! STRCPY(s, ebuf); ! s[ebuf_len] = TAG_SEP; ! s += ebuf_len + 1; ! } ! else ! *s++ = TAG_SEP; ! #endif ! STRCPY(s, lbuf); } } ! if (mfp != NULL) ! { ! hashitem_T *hi; ! /* ! * Don't add identical matches. ! * Add all cscope tags, because they are all listed. ! * "mfp" is used as a hash key, there is a NUL byte to end ! * the part that matters for comparing, more bytes may ! * follow after it. E.g. help tags store the priority ! * after the NUL. ! */ #ifdef FEAT_CSCOPE ! if (use_cscope) ! hash++; ! else #endif ! hash = hash_hash(mfp); ! hi = hash_lookup(&ht_match[mtt], mfp, hash); ! if (HASHITEM_EMPTY(hi)) ! { ! if (hash_add_item(&ht_match[mtt], hi, mfp, hash) ! == FAIL ! || ga_grow(&ga_match[mtt], 1) != OK) ! { ! // Out of memory! Just forget about the rest. ! retval = OK; ! stop_searching = TRUE; ! break; ! } ! else ! { ! ((char_u **)(ga_match[mtt].ga_data)) ! [ga_match[mtt].ga_len++] = mfp; ! ++match_count; ! } } else ! // duplicate tag, drop it ! vim_free(mfp); } } #ifdef FEAT_CSCOPE ! if (use_cscope && eof) ! break; #endif ! } // forever ! if (line_error) ! { ! semsg(_(e_format_error_in_tags_file_str), tag_fname); #ifdef FEAT_CSCOPE ! if (!use_cscope) #endif ! semsg(_("Before byte %ld"), (long)vim_ftell(fp)); ! stop_searching = TRUE; ! line_error = FALSE; ! } #ifdef FEAT_CSCOPE ! if (!use_cscope) #endif ! fclose(fp); #ifdef FEAT_EMACS_TAGS ! while (incstack_idx) ! { ! --incstack_idx; ! fclose(incstack[incstack_idx].fp); ! vim_free(incstack[incstack_idx].etag_fname); ! } #endif ! if (vimconv.vc_type != CONV_NONE) ! convert_setup(&vimconv, NULL, NULL); #ifdef FEAT_TAG_BINS ! tag_file_sorted = NUL; ! if (sort_error) { ! semsg(_(e_tags_file_not_sorted_str), tag_fname); ! sort_error = FALSE; } #endif ! /* ! * Stop searching if sufficient tags have been found. ! */ ! if (match_count >= mincount) { ! retval = OK; ! stop_searching = TRUE; } ! #ifdef FEAT_CSCOPE ! if (stop_searching || use_cscope) #else ! if (stop_searching) #endif - break; } // end of for-each-file loop #ifdef FEAT_CSCOPE --- 1720,2937 ---- } incstack[INCSTACK_SIZE]; int incstack_idx = 0; // index in incstack int is_etag; // current file is emaces style #endif char_u *mfp; int mtt; ! hash_T hash = 0; #ifdef FEAT_TAG_BINS int sort_error = FALSE; // tags file not sorted int sortic = FALSE; // tag file sorted in nocase + int noic = (flags & TAG_NOIC); #endif int line_error = FALSE; // syntax error int has_re = (flags & TAG_REGEXP); // regexp used int help_only = (flags & TAG_HELP); int name_only = (flags & TAG_NAMES); #ifdef FEAT_CSCOPE int use_cscope = (flags & TAG_CSCOPE); #endif ! int get_it_again = FALSE; ! vimconv_T vimconv; vimconv.vc_type = CONV_NONE; ! #ifdef FEAT_TAG_BINS ! // This is only to avoid a compiler warning for using search_info ! // uninitialised. ! CLEAR_FIELD(search_info); #endif /* ! * A file that doesn't exist is silently ignored. Only when not a ! * single file is found, an error message is given (further on). */ #ifdef FEAT_CSCOPE ! if (use_cscope) ! fp = NULL; // avoid GCC warning ! else #endif { ! #ifdef FEAT_MULTI_LANG ! if (curbuf->b_help) { ! // Keep en if the file extension is .txt ! if (st->is_txt) ! STRCPY(help_lang, "en"); ! else ! { ! // Prefer help tags according to 'helplang'. Put the ! // two-letter language name in help_lang[]. ! i = (int)STRLEN(tag_fname); ! if (i > 3 && tag_fname[i - 3] == '-') ! STRCPY(help_lang, tag_fname + i - 2); ! else ! STRCPY(help_lang, "en"); ! } ! // When searching for a specific language skip tags files ! // for other languages. ! if (st->help_lang_find != NULL ! && STRICMP(help_lang, st->help_lang_find) != 0) ! return OK; ! ! // For CTRL-] in a help file prefer a match with the same ! // language. ! if ((flags & TAG_KEEP_LANG) ! && st->help_lang_find == NULL ! && curbuf->b_fname != NULL ! && (i = (int)STRLEN(curbuf->b_fname)) > 4 ! && curbuf->b_fname[i - 1] == 'x' ! && curbuf->b_fname[i - 4] == '.' ! && STRNICMP(curbuf->b_fname + i - 3, help_lang, 2) == 0) ! help_pri = 0; ! else { ! help_pri = 1; ! for (s = p_hlg; *s != NUL; ++s) ! { ! if (STRNICMP(s, help_lang, 2) == 0) ! break; ! ++help_pri; ! if ((s = vim_strchr(s, ',')) == NULL) ! break; ! } ! if (s == NULL || *s == NUL) ! { ! // Language not in 'helplang': use last, prefer English, ! // unless found already. ! ++help_pri; ! if (STRICMP(help_lang, "en") != 0) ! ++help_pri; ! } } } #endif ! if ((fp = mch_fopen((char *)tag_fname, "r")) == NULL) ! return OK; ! if (p_verbose >= 5) ! { ! verbose_enter(); ! smsg(_("Searching tags file %s"), tag_fname); ! verbose_leave(); ! } } + st->did_open = TRUE; // remember that we found at least one file + + state = TS_START; // we're at the start of the file + #ifdef FEAT_EMACS_TAGS + is_etag = 0; // default is: not emacs style #endif /* ! * Read and parse the lines in the file one by one */ ! for (;;) { ! #ifdef FEAT_TAG_BINS ! // check for CTRL-C typed, more often when jumping around ! if (state == TS_BINARY || state == TS_SKIP_BACK) ! line_breakcheck(); ! else #endif ! fast_breakcheck(); ! if ((flags & TAG_INS_COMP)) // Double brackets for gcc ! ins_compl_check_keys(30, FALSE); ! if (got_int || ins_compl_interrupted()) ! { ! st->stop_searching = TRUE; ! break; ! } ! // When mincount is TAG_MANY, stop when enough matches have been ! // found (for completion). ! if (st->mincount == TAG_MANY && st->match_count >= TAG_MANY) ! { ! st->stop_searching = TRUE; ! break; ! } ! if (get_it_again) ! goto line_read_in; ! #ifdef FEAT_TAG_BINS /* ! * For binary search: compute the next offset to use. */ ! if (state == TS_BINARY) { ! offset = search_info.low_offset + ((search_info.high_offset ! - search_info.low_offset) / 2); ! if (offset == search_info.curr_offset) ! break; // End the binary search without a match. ! else ! search_info.curr_offset = offset; ! } ! /* ! * Skipping back (after a match during binary search). ! */ ! else if (state == TS_SKIP_BACK) ! { ! search_info.curr_offset -= st->lbuf_size * 2; ! if (search_info.curr_offset < 0) { ! search_info.curr_offset = 0; ! rewind(fp); ! state = TS_STEP_FORWARD; } } /* ! * When jumping around in the file, first read a line to find the ! * start of the next line. */ ! if (state == TS_BINARY || state == TS_SKIP_BACK) { ! // Adjust the search file offset to the correct position ! search_info.curr_offset_used = search_info.curr_offset; ! vim_fseek(fp, search_info.curr_offset, SEEK_SET); ! eof = vim_fgets(st->lbuf, st->lbuf_size, fp); ! if (!eof && search_info.curr_offset != 0) { ! search_info.curr_offset = vim_ftell(fp); ! if (search_info.curr_offset == search_info.high_offset) ! { ! // oops, gone a bit too far; try from low offset ! vim_fseek(fp, search_info.low_offset, SEEK_SET); ! search_info.curr_offset = search_info.low_offset; ! } ! eof = vim_fgets(st->lbuf, st->lbuf_size, fp); } ! // skip empty and blank lines ! while (!eof && vim_isblankline(st->lbuf)) { ! search_info.curr_offset = vim_ftell(fp); ! eof = vim_fgets(st->lbuf, st->lbuf_size, fp); } ! if (eof) { ! // Hit end of file. Skip backwards. ! state = TS_SKIP_BACK; ! search_info.match_offset = vim_ftell(fp); ! search_info.curr_offset = search_info.curr_offset_used; ! continue; } + } ! /* ! * Not jumping around in the file: Read the next line. ! */ ! else ! #endif ! { ! // skip empty and blank lines ! do { ! #ifdef FEAT_CSCOPE ! if (use_cscope) ! eof = cs_fgets(st->lbuf, st->lbuf_size); ! else ! #endif { + #ifdef FEAT_TAG_BINS search_info.curr_offset = vim_ftell(fp); ! #endif ! eof = vim_fgets(st->lbuf, st->lbuf_size, fp); } ! } while (!eof && vim_isblankline(st->lbuf)); ! if (eof) { #ifdef FEAT_EMACS_TAGS ! if (incstack_idx) // this was an included file ! { ! --incstack_idx; ! fclose(fp); // end of this file ... ! fp = incstack[incstack_idx].fp; ! STRCPY(tag_fname, incstack[incstack_idx].etag_fname); ! vim_free(incstack[incstack_idx].etag_fname); ! is_etag = 1; // (only etags can include) ! continue; // ... continue with parent file } + else + #endif + break; // end of file } + } line_read_in: ! if (vimconv.vc_type != CONV_NONE) ! { ! char_u *conv_line; ! int len; ! // Convert every line. Converting the pattern from 'enc' to ! // the tags file encoding doesn't work, because characters are ! // not recognized. ! conv_line = string_convert(&vimconv, st->lbuf, NULL); ! if (conv_line != NULL) ! { ! // Copy or swap lbuf and conv_line. ! len = (int)STRLEN(conv_line) + 1; ! if (len > st->lbuf_size) ! { ! vim_free(st->lbuf); ! st->lbuf = conv_line; ! st->lbuf_size = len; ! } ! else ! { ! STRCPY(st->lbuf, conv_line); ! vim_free(conv_line); } } + } #ifdef FEAT_EMACS_TAGS ! /* ! * Emacs tags line with CTRL-L: New file name on next line. ! * The file name is followed by a ','. ! * Remember etag file name in ebuf. ! */ ! if (*st->lbuf == Ctrl_L # ifdef FEAT_CSCOPE ! && !use_cscope # endif ! ) ! { ! is_etag = 1; // in case at the start ! state = TS_LINEAR; ! if (!vim_fgets(st->ebuf, LSIZE, fp)) { ! for (p = st->ebuf; *p && *p != ','; p++) ! ; ! *p = NUL; ! /* ! * atoi(p+1) is the number of bytes before the next ^L ! * unless it is an include statement. ! */ ! if (STRNCMP(p + 1, "include", 7) == 0 ! && incstack_idx < INCSTACK_SIZE) ! { ! // Save current "fp" and "tag_fname" in the stack. ! if ((incstack[incstack_idx].etag_fname = ! vim_strsave(tag_fname)) != NULL) ! { ! char_u *fullpath_ebuf; ! ! incstack[incstack_idx].fp = fp; ! fp = NULL; ! ! // Figure out "tag_fname" and "fp" to use for ! // included file. ! fullpath_ebuf = expand_tag_fname(st->ebuf, ! tag_fname, FALSE); ! if (fullpath_ebuf != NULL) { ! fp = mch_fopen((char *)fullpath_ebuf, "r"); ! if (fp != NULL) { ! if (STRLEN(fullpath_ebuf) > LSIZE) ! semsg(_(e_tag_file_path_truncated_for_str), st->ebuf); ! vim_strncpy(tag_fname, fullpath_ebuf, ! MAXPATHL); ! ++incstack_idx; ! is_etag = 0; // we can include anything } + vim_free(fullpath_ebuf); + } + if (fp == NULL) + { + // Can't open the included file, skip it and + // restore old value of "fp". + fp = incstack[incstack_idx].fp; + vim_free(incstack[incstack_idx].etag_fname); } } } } + continue; + } #endif ! /* ! * When still at the start of the file, check for Emacs tags file ! * format, and for "not sorted" flag. ! */ ! if (state == TS_START) ! { ! // The header ends when the line sorts below "!_TAG_". When ! // case is folded lower case letters sort before "_". ! if (STRNCMP(st->lbuf, "!_TAG_", 6) <= 0 ! || (st->lbuf[0] == '!' && ASCII_ISLOWER(st->lbuf[1]))) { ! if (STRNCMP(st->lbuf, "!_TAG_", 6) != 0) ! // Non-header item before the header, e.g. "!" itself. ! goto parse_line; ! ! /* ! * Read header line. ! */ #ifdef FEAT_TAG_BINS ! if (STRNCMP(st->lbuf, "!_TAG_FILE_SORTED\t", 18) == 0) ! tag_file_sorted = st->lbuf[18]; #endif ! if (STRNCMP(st->lbuf, "!_TAG_FILE_ENCODING\t", 20) == 0) ! { ! // Prepare to convert every line from the specified ! // encoding to 'encoding'. ! for (p = st->lbuf + 20; *p > ' ' && *p < 127; ++p) ! ; ! *p = NUL; ! convert_setup(&vimconv, st->lbuf + 20, p_enc); } ! // Read the next line. Unrecognized flags are ignored. ! continue; ! } ! ! // Headers ends. #ifdef FEAT_TAG_BINS ! /* ! * When there is no tag head, or ignoring case, need to do a ! * linear search. ! * When no "!_TAG_" is found, default to binary search. If ! * the tag file isn't sorted, the second loop will find it. ! * When "!_TAG_FILE_SORTED" found: start binary search if ! * flag set. ! * For cscope, it's always linear. ! */ # ifdef FEAT_CSCOPE ! if (st->linear || use_cscope) # else ! if (st->linear) # endif ! state = TS_LINEAR; ! else if (tag_file_sorted == NUL) ! state = TS_BINARY; ! else if (tag_file_sorted == '1') ! state = TS_BINARY; ! else if (tag_file_sorted == '2') ! { ! state = TS_BINARY; ! sortic = TRUE; ! st->orgpat.regmatch.rm_ic = (p_ic || !noic); ! } ! else ! state = TS_LINEAR; ! if (state == TS_BINARY && st->orgpat.regmatch.rm_ic && !sortic) ! { ! // Binary search won't work for ignoring case, use linear ! // search. ! st->linear = TRUE; state = TS_LINEAR; + } + #else + state = TS_LINEAR; #endif #ifdef FEAT_TAG_BINS ! // When starting a binary search, get the size of the file and ! // compute the first offset. ! if (state == TS_BINARY) ! { ! if (vim_fseek(fp, 0L, SEEK_END) != 0) ! // can't seek, don't use binary search ! state = TS_LINEAR; ! else ! { ! // Get the tag file size (don't use mch_fstat(), it's ! // not portable). Don't use lseek(), it doesn't work ! // properly on MacOS Catalina. ! filesize = vim_ftell(fp); ! vim_fseek(fp, 0L, SEEK_SET); ! ! // Calculate the first read offset in the file. Start ! // the search in the middle of the file. ! search_info.low_offset = 0; ! search_info.low_char = 0; ! search_info.high_offset = filesize; ! search_info.curr_offset = 0; ! search_info.high_char = 0xff; } ! continue; } + #endif + } parse_line: ! // When the line is too long the NUL will not be in the ! // last-but-one byte (see vim_fgets()). ! // Has been reported for Mozilla JS with extremely long names. ! // In that case we need to increase lbuf_size. ! if (st->lbuf[st->lbuf_size - 2] != NUL #ifdef FEAT_CSCOPE ! && !use_cscope #endif ! ) ! { ! st->lbuf_size *= 2; ! vim_free(st->lbuf); ! st->lbuf = alloc(st->lbuf_size); ! if (st->lbuf == NULL) ! return FAIL; ! #ifdef FEAT_TAG_BINS ! if (state == TS_STEP_FORWARD) ! // Seek to the same position to read the same line again ! vim_fseek(fp, search_info.curr_offset, SEEK_SET); ! // this will try the same thing again, make sure the offset is ! // different ! search_info.curr_offset = 0; #endif ! continue; ! } ! ! /* ! * Figure out where the different strings are in this line. ! * For "normal" tags: Do a quick check if the tag matches. ! * This speeds up tag searching a lot! ! */ ! if (st->orgpat.headlen ! #ifdef FEAT_EMACS_TAGS ! && !is_etag ! #endif ! ) ! { ! CLEAR_FIELD(tagp); ! tagp.tagname = st->lbuf; ! tagp.tagname_end = vim_strchr(st->lbuf, TAB); ! if (tagp.tagname_end == NULL) ! { ! // Corrupted tag line. ! line_error = TRUE; ! break; } /* ! * Skip this line if the length of the tag is different and ! * there is no regexp, or the tag is too short. */ ! cmplen = (int)(tagp.tagname_end - tagp.tagname); ! if (p_tl != 0 && cmplen > p_tl) // adjust for 'taglength' ! cmplen = p_tl; ! if (has_re && st->orgpat.headlen < cmplen) ! cmplen = st->orgpat.headlen; ! else if (state == TS_LINEAR && st->orgpat.headlen != cmplen) ! continue; ! ! #ifdef FEAT_TAG_BINS ! if (state == TS_BINARY) { ! /* ! * Simplistic check for unsorted tags file. ! */ ! i = (int)tagp.tagname[0]; ! if (sortic) ! i = (int)TOUPPER_ASC(tagp.tagname[0]); ! if (i < search_info.low_char || i > search_info.high_char) ! sort_error = TRUE; /* ! * Compare the current tag with the searched tag. */ ! if (sortic) ! tagcmp = tag_strnicmp(tagp.tagname, st->orgpat.head, ! (size_t)cmplen); ! else ! tagcmp = STRNCMP(tagp.tagname, st->orgpat.head, cmplen); ! /* ! * A match with a shorter tag means to search forward. ! * A match with a longer tag means to search backward. ! */ ! if (tagcmp == 0) { ! if (cmplen < st->orgpat.headlen) ! tagcmp = -1; ! else if (cmplen > st->orgpat.headlen) ! tagcmp = 1; ! } ! if (tagcmp == 0) ! { ! // We've located the tag, now skip back and search ! // forward until the first matching tag is found. ! state = TS_SKIP_BACK; ! search_info.match_offset = search_info.curr_offset; ! continue; ! } ! if (tagcmp < 0) ! { ! search_info.curr_offset = vim_ftell(fp); ! if (search_info.curr_offset < search_info.high_offset) { ! search_info.low_offset = search_info.curr_offset; if (sortic) ! search_info.low_char = ! TOUPPER_ASC(tagp.tagname[0]); else ! search_info.low_char = tagp.tagname[0]; continue; } } ! if (tagcmp > 0 ! && search_info.curr_offset != search_info.high_offset) { ! search_info.high_offset = search_info.curr_offset; ! if (sortic) ! search_info.high_char = ! TOUPPER_ASC(tagp.tagname[0]); else ! search_info.high_char = tagp.tagname[0]; continue; } ! ! // No match yet and are at the end of the binary search. ! break; ! } ! else if (state == TS_SKIP_BACK) ! { ! if (MB_STRNICMP(tagp.tagname, st->orgpat.head, cmplen) != 0) ! state = TS_STEP_FORWARD; ! else ! // Have to skip back more. Restore the curr_offset ! // used, otherwise we get stuck at a long line. ! search_info.curr_offset = search_info.curr_offset_used; ! continue; ! } ! else if (state == TS_STEP_FORWARD) ! { ! if (MB_STRNICMP(tagp.tagname, st->orgpat.head, cmplen) != 0) { ! if ((off_T)vim_ftell(fp) > search_info.match_offset) ! break; // past last match ! else ! continue; // before first match } ! } ! else #endif ! // skip this match if it can't match ! if (MB_STRNICMP(tagp.tagname, st->orgpat.head, cmplen) != 0) continue; ! /* ! * Can be a matching tag, isolate the file name and command. ! */ ! tagp.fname = tagp.tagname_end + 1; ! tagp.fname_end = vim_strchr(tagp.fname, TAB); ! tagp.command = tagp.fname_end + 1; ! if (tagp.fname_end == NULL) ! i = FAIL; else ! i = OK; ! } ! else ! i = parse_tag_line(st->lbuf, #ifdef FEAT_EMACS_TAGS ! is_etag, #endif ! &tagp); ! if (i == FAIL) ! { ! line_error = TRUE; ! break; ! } #ifdef FEAT_EMACS_TAGS ! if (is_etag) ! tagp.fname = st->ebuf; #endif ! /* ! * First try matching with the pattern literally (also when it is ! * a regexp). ! */ ! cmplen = (int)(tagp.tagname_end - tagp.tagname); ! if (p_tl != 0 && cmplen > p_tl) // adjust for 'taglength' ! cmplen = p_tl; ! // if tag length does not match, don't try comparing ! if (st->orgpat.len != cmplen) ! match = FALSE; ! else ! { ! if (st->orgpat.regmatch.rm_ic) { ! match = (MB_STRNICMP(tagp.tagname, st->orgpat.pat, cmplen) == 0); ! if (match) ! match_no_ic = (STRNCMP(tagp.tagname, st->orgpat.pat, ! cmplen) == 0); } + else + match = (STRNCMP(tagp.tagname, st->orgpat.pat, cmplen) == 0); + } ! /* ! * Has a regexp: Also find tags matching regexp. ! */ ! match_re = FALSE; ! if (!match && st->orgpat.regmatch.regprog != NULL) ! { ! int cc; ! cc = *tagp.tagname_end; ! *tagp.tagname_end = NUL; ! match = vim_regexec(&st->orgpat.regmatch, tagp.tagname, (colnr_T)0); ! if (match) ! { ! matchoff = (int)(st->orgpat.regmatch.startp[0] - tagp.tagname); ! if (st->orgpat.regmatch.rm_ic) { ! st->orgpat.regmatch.rm_ic = FALSE; ! match_no_ic = vim_regexec(&st->orgpat.regmatch, tagp.tagname, ! (colnr_T)0); ! st->orgpat.regmatch.rm_ic = TRUE; } } + *tagp.tagname_end = cc; + match_re = TRUE; + } ! /* ! * If a match is found, add it to ht_match[] and ga_match[]. ! */ ! if (match) ! { ! int len = 0; #ifdef FEAT_CSCOPE ! if (use_cscope) ! { ! // Don't change the ordering, always use the same table. ! mtt = MT_GL_OTH; ! } ! else #endif ! { ! // Decide in which array to store this match. ! is_current = test_for_current( #ifdef FEAT_EMACS_TAGS ! is_etag, #endif ! tagp.fname, tagp.fname_end, tag_fname, ! buf_ffname); #ifdef FEAT_EMACS_TAGS ! is_static = FALSE; ! if (!is_etag) // emacs tags are never static #endif ! is_static = test_for_static(&tagp); ! // decide in which of the sixteen tables to store this ! // match ! if (is_static) ! { ! if (is_current) ! mtt = MT_ST_CUR; else ! mtt = MT_ST_OTH; } ! else { + if (is_current) + mtt = MT_GL_CUR; + else + mtt = MT_GL_OTH; + } + if (st->orgpat.regmatch.rm_ic && !match_no_ic) + mtt += MT_IC_OFF; + if (match_re) + mtt += MT_RE_OFF; + } + + /* + * Add the found match in ht_match[mtt] and ga_match[mtt]. + * Store the info we need later, which depends on the kind of + * tags we are dealing with. + */ + if (help_only) + { #ifdef FEAT_MULTI_LANG # define ML_EXTRA 3 #else # define ML_EXTRA 0 #endif ! /* ! * Append the help-heuristic number after the tagname, for ! * sorting it later. The heuristic is ignored for ! * detecting duplicates. ! * The format is {tagname}@{lang}NUL{heuristic}NUL ! */ ! *tagp.tagname_end = NUL; ! len = (int)(tagp.tagname_end - tagp.tagname); ! mfp = alloc(sizeof(char_u) + len + 10 + ML_EXTRA + 1); ! if (mfp != NULL) ! { ! int heuristic; ! p = mfp; ! STRCPY(p, tagp.tagname); #ifdef FEAT_MULTI_LANG ! p[len] = '@'; ! STRCPY(p + len + 1, help_lang); #endif ! heuristic = help_heuristic(tagp.tagname, ! match_re ? matchoff : 0, !match_no_ic); #ifdef FEAT_MULTI_LANG ! heuristic += help_pri; #endif ! sprintf((char *)p + len + 1 + ML_EXTRA, "%06d", ! heuristic); } ! *tagp.tagname_end = TAB; ! } ! else if (name_only) ! { ! if (get_it_again) { ! char_u *temp_end = tagp.command; ! if (*temp_end == '/') ! while (*temp_end && *temp_end != '\r' ! && *temp_end != '\n' ! && *temp_end != '$') ! temp_end++; ! if (tagp.command + 2 < temp_end) { ! len = (int)(temp_end - tagp.command - 2); ! mfp = alloc(len + 2); if (mfp != NULL) ! vim_strncpy(mfp, tagp.command + 2, len); } + else + mfp = NULL; + get_it_again = FALSE; } else { ! len = (int)(tagp.tagname_end - tagp.tagname); ! mfp = alloc(sizeof(char_u) + len + 1); ! if (mfp != NULL) ! vim_strncpy(mfp, tagp.tagname, len); ! ! // if wanted, re-read line to get long form too ! if (State & INSERT) ! get_it_again = p_sft; ! } ! } ! else ! { ! size_t tag_fname_len = STRLEN(tag_fname); #ifdef FEAT_EMACS_TAGS ! size_t ebuf_len = 0; #endif ! // Save the tag in a buffer. ! // Use 0x02 to separate fields (Can't use NUL because the ! // hash key is terminated by NUL, or Ctrl_A because that is ! // part of some Emacs tag files -- see parse_tag_line). ! // Emacs tag: <0x02><0x02> ! // other tag: <0x02><0x02> ! // without Emacs tags: <0x02> ! // Here is the "mtt" value plus 1 to avoid NUL. ! len = (int)tag_fname_len + (int)STRLEN(st->lbuf) + 3; #ifdef FEAT_EMACS_TAGS ! if (is_etag) ! { ! ebuf_len = STRLEN(st->ebuf); ! len += (int)ebuf_len + 1; ! } ! else ! ++len; #endif ! mfp = alloc(sizeof(char_u) + len + 1); ! if (mfp != NULL) ! { ! p = mfp; ! p[0] = mtt + 1; ! STRCPY(p + 1, tag_fname); #ifdef BACKSLASH_IN_FILENAME ! // Ignore differences in slashes, avoid adding ! // both path/file and path\file. ! slash_adjust(p + 1); #endif ! p[tag_fname_len + 1] = TAG_SEP; ! s = p + 1 + tag_fname_len + 1; #ifdef FEAT_EMACS_TAGS ! if (is_etag) ! { ! STRCPY(s, st->ebuf); ! s[ebuf_len] = TAG_SEP; ! s += ebuf_len + 1; } + else + *s++ = TAG_SEP; + #endif + STRCPY(s, st->lbuf); } + } ! if (mfp != NULL) ! { ! hashitem_T *hi; ! /* ! * Don't add identical matches. ! * Add all cscope tags, because they are all listed. ! * "mfp" is used as a hash key, there is a NUL byte to end ! * the part that matters for comparing, more bytes may ! * follow after it. E.g. help tags store the priority ! * after the NUL. ! */ #ifdef FEAT_CSCOPE ! if (use_cscope) ! hash++; ! else #endif ! hash = hash_hash(mfp); ! hi = hash_lookup(&st->ht_match[mtt], mfp, hash); ! if (HASHITEM_EMPTY(hi)) ! { ! if (hash_add_item(&st->ht_match[mtt], hi, mfp, hash) ! == FAIL ! || ga_grow(&st->ga_match[mtt], 1) != OK) ! { ! // Out of memory! Just forget about the rest. ! st->stop_searching = TRUE; ! break; } else ! { ! ((char_u **)(st->ga_match[mtt].ga_data)) ! [st->ga_match[mtt].ga_len++] = mfp; ! st->match_count++; ! } } + else + // duplicate tag, drop it + vim_free(mfp); } + } #ifdef FEAT_CSCOPE ! if (use_cscope && eof) ! break; #endif ! } // forever ! if (line_error) ! { ! semsg(_(e_format_error_in_tags_file_str), tag_fname); #ifdef FEAT_CSCOPE ! if (!use_cscope) #endif ! semsg(_("Before byte %ld"), (long)vim_ftell(fp)); ! st->stop_searching = TRUE; ! line_error = FALSE; ! } #ifdef FEAT_CSCOPE ! if (!use_cscope) #endif ! fclose(fp); #ifdef FEAT_EMACS_TAGS ! while (incstack_idx) ! { ! --incstack_idx; ! fclose(incstack[incstack_idx].fp); ! vim_free(incstack[incstack_idx].etag_fname); ! } #endif ! if (vimconv.vc_type != CONV_NONE) ! convert_setup(&vimconv, NULL, NULL); #ifdef FEAT_TAG_BINS ! tag_file_sorted = NUL; ! if (sort_error) ! { ! semsg(_(e_tags_file_not_sorted_str), tag_fname); ! sort_error = FALSE; ! } ! #endif ! ! /* ! * Stop searching if sufficient tags have been found. ! */ ! if (st->match_count >= st->mincount) ! st->stop_searching = TRUE; ! ! return OK; ! } ! ! /* ! * Copy the tags found by find_tags() to 'matchesp'. ! */ ! static void ! findtags_copy_matches( ! findtags_state_T *st, ! char_u ***matchesp, ! int *num_matches, ! int name_only) ! { ! char_u **matches; ! int mtt; ! int i; ! char_u *mfp; ! char_u *p; ! ! if (st->match_count > 0) ! matches = ALLOC_MULT(char_u *, st->match_count); ! else ! matches = NULL; ! st->match_count = 0; ! for (mtt = 0; mtt < MT_COUNT; ++mtt) ! { ! for (i = 0; i < st->ga_match[mtt].ga_len; ++i) { ! mfp = ((char_u **)(st->ga_match[mtt].ga_data))[i]; ! if (matches == NULL) ! vim_free(mfp); ! else ! { ! if (!name_only) ! { ! // Change mtt back to zero-based. ! *mfp = *mfp - 1; ! ! // change the TAG_SEP back to NUL ! for (p = mfp + 1; *p != NUL; ++p) ! if (*p == TAG_SEP) ! *p = NUL; ! } ! matches[st->match_count++] = mfp; ! } } + + ga_clear(&st->ga_match[mtt]); + hash_clear(&st->ht_match[mtt]); + } + + *matchesp = matches; + *num_matches = st->match_count; + } + + /* + * find_tags() - search for tags in tags files + * + * Return FAIL if search completely failed (*num_matches will be 0, *matchesp + * will be NULL), OK otherwise. + * + * Priority depending on which type of tag is recognized: + * 6. A static or global tag with a full matching tag for the current file. + * 5. A global tag with a full matching tag for another file. + * 4. A static tag with a full matching tag for another file. + * 3. A static or global tag with an ignore-case matching tag for the + * current file. + * 2. A global tag with an ignore-case matching tag for another file. + * 1. A static tag with an ignore-case matching tag for another file. + * + * Tags in an emacs-style tags file are always global. + * + * flags: + * TAG_HELP only search for help tags + * TAG_NAMES only return name of tag + * TAG_REGEXP use "pat" as a regexp + * TAG_NOIC don't always ignore case + * TAG_KEEP_LANG keep language + * TAG_CSCOPE use cscope results for tags + * TAG_NO_TAGFUNC do not call the 'tagfunc' function + */ + int + find_tags( + char_u *pat, // pattern to search for + int *num_matches, // return: number of matches found + char_u ***matchesp, // return: array of matches found + int flags, + int mincount, // MAXCOL: find all matches + // other: minimal number of matches + char_u *buf_ffname) // name of buffer for priority + { + findtags_state_T st; + char_u *tag_fname; // name of tag file + tagname_T tn; // info for get_tagfname() + int first_file; // trying first tag file + int retval = FAIL; // return value + #ifdef FEAT_TAG_BINS + int round; #endif ! int save_emsg_off; ! ! int help_save; ! #ifdef FEAT_MULTI_LANG ! int i; ! char_u *saved_pat = NULL; // copy of pat[] ! #endif ! ! #ifdef FEAT_TAG_BINS ! int findall = (mincount == MAXCOL || mincount == TAG_MANY); ! // find all matching tags ! #endif ! int has_re = (flags & TAG_REGEXP); // regexp used ! int help_only = (flags & TAG_HELP); ! int name_only = (flags & TAG_NAMES); ! int noic = (flags & TAG_NOIC); ! #ifdef FEAT_CSCOPE ! int use_cscope = (flags & TAG_CSCOPE); ! #endif ! int verbose = (flags & TAG_VERBOSE); ! #ifdef FEAT_EVAL ! int use_tfu = ((flags & TAG_NO_TAGFUNC) == 0); ! #endif ! int save_p_ic = p_ic; ! ! /* ! * Change the value of 'ignorecase' according to 'tagcase' for the ! * duration of this function. ! */ ! switch (curbuf->b_tc_flags ? curbuf->b_tc_flags : tc_flags) ! { ! case TC_FOLLOWIC: break; ! case TC_IGNORE: p_ic = TRUE; break; ! case TC_MATCH: p_ic = FALSE; break; ! case TC_FOLLOWSCS: p_ic = ignorecase(pat); break; ! case TC_SMART: p_ic = ignorecase_opt(pat, TRUE, TRUE); break; ! } ! ! help_save = curbuf->b_help; ! ! /* ! * Allocate memory for the buffers that are used ! */ ! tag_fname = alloc(MAXPATHL + 1); ! ! // check for out of memory situation ! if (tag_fname == NULL) ! goto findtag_end; ! ! if (findtags_state_init(&st, pat, mincount) == FAIL) ! goto findtag_end; ! ! #ifdef FEAT_CSCOPE ! STRCPY(tag_fname, "from cscope"); // for error messages ! #endif ! ! /* ! * Initialize a few variables ! */ ! if (help_only) // want tags from help file ! curbuf->b_help = TRUE; // will be restored later ! #ifdef FEAT_CSCOPE ! else if (use_cscope) ! { ! // Make sure we don't mix help and cscope, confuses Coverity. ! help_only = FALSE; ! curbuf->b_help = FALSE; ! } ! #endif ! ! #ifdef FEAT_MULTI_LANG ! if (curbuf->b_help) ! { ! // When "@ab" is specified use only the "ab" language, otherwise ! // search all languages. ! if (st.orgpat.len > 3 && pat[st.orgpat.len - 3] == '@' ! && ASCII_ISALPHA(pat[st.orgpat.len - 2]) ! && ASCII_ISALPHA(pat[st.orgpat.len - 1])) { ! saved_pat = vim_strnsave(pat, st.orgpat.len - 3); ! if (saved_pat != NULL) ! { ! st.help_lang_find = &pat[st.orgpat.len - 2]; ! st.orgpat.pat = saved_pat; ! st.orgpat.len -= 3; ! } } + } + #endif + if (p_tl != 0 && st.orgpat.len > p_tl) // adjust for 'taglength' + st.orgpat.len = p_tl; ! save_emsg_off = emsg_off; ! emsg_off = TRUE; // don't want error for invalid RE here ! prepare_pats(&st.orgpat, has_re); ! emsg_off = save_emsg_off; ! if (has_re && st.orgpat.regmatch.regprog == NULL) ! goto findtag_end; ! ! #ifdef FEAT_EVAL ! if (*curbuf->b_p_tfu != NUL && use_tfu && !tfu_in_use) ! { ! tfu_in_use = TRUE; ! retval = find_tagfunc_tags(pat, &st.ga_match[0], &st.match_count, ! flags, buf_ffname); ! tfu_in_use = FALSE; ! if (retval != NOTDONE) ! goto findtag_end; ! } ! #endif ! ! /* ! * When finding a specified number of matches, first try with matching ! * case, so binary search can be used, and try ignore-case matches in a ! * second loop. ! * When finding all matches, 'tagbsearch' is off, or there is no fixed ! * string to look for, ignore case right away to avoid going though the ! * tags files twice. ! * When the tag file is case-fold sorted, it is either one or the other. ! * Only ignore case when TAG_NOIC not used or 'ignorecase' set. ! */ ! #ifdef FEAT_MULTI_LANG ! // Set a flag if the file extension is .txt ! if ((flags & TAG_KEEP_LANG) ! && st.help_lang_find == NULL ! && curbuf->b_fname != NULL ! && (i = (int)STRLEN(curbuf->b_fname)) > 4 ! && STRICMP(curbuf->b_fname + i - 4, ".txt") == 0) ! st.is_txt = TRUE; ! #endif ! #ifdef FEAT_TAG_BINS ! st.orgpat.regmatch.rm_ic = ((p_ic || !noic) ! && (findall || st.orgpat.headlen == 0 || !p_tbs)); ! for (round = 1; round <= 2; ++round) ! { ! st.linear = (st.orgpat.headlen == 0 || !p_tbs || round == 2); #else ! st.orgpat.regmatch.rm_ic = (p_ic || !noic); #endif + /* + * Try tag file names from tags option one by one. + */ + for (first_file = TRUE; + #ifdef FEAT_CSCOPE + use_cscope || + #endif + get_tagfname(&tn, first_file, tag_fname) == OK; + first_file = FALSE) + { + if (find_tags_in_file(tag_fname, &st, flags, buf_ffname) == FAIL) + goto findtag_end; + if (st.stop_searching + #ifdef FEAT_CSCOPE + || use_cscope + #endif + ) + { + retval = OK; + break; + } } // end of for-each-file loop #ifdef FEAT_CSCOPE *************** *** 2808,2838 **** tagname_free(&tn); #ifdef FEAT_TAG_BINS ! // stop searching when already did a linear search, or when TAG_NOIC ! // used, and 'ignorecase' not set or already did case-ignore search ! if (stop_searching || linear || (!p_ic && noic) || orgpat.regmatch.rm_ic) ! break; # ifdef FEAT_CSCOPE ! if (use_cscope) ! break; # endif ! orgpat.regmatch.rm_ic = TRUE; // try another time while ignoring case } #endif ! if (!stop_searching) { ! if (!did_open && verbose) // never opened any tags file emsg(_(e_no_tags_file)); retval = OK; // It's OK even when no tag found } findtag_end: ! vim_free(lbuf); ! vim_regfree(orgpat.regmatch.regprog); vim_free(tag_fname); #ifdef FEAT_EMACS_TAGS ! vim_free(ebuf); #endif /* --- 2940,2973 ---- tagname_free(&tn); #ifdef FEAT_TAG_BINS ! // stop searching when already did a linear search, or when TAG_NOIC ! // used, and 'ignorecase' not set or already did case-ignore search ! if (st.stop_searching || st.linear || (!p_ic && noic) || ! st.orgpat.regmatch.rm_ic) ! break; # ifdef FEAT_CSCOPE ! if (use_cscope) ! break; # endif ! ! // try another time while ignoring case ! st.orgpat.regmatch.rm_ic = TRUE; } #endif ! if (!st.stop_searching) { ! if (!st.did_open && verbose) // never opened any tags file emsg(_(e_no_tags_file)); retval = OK; // It's OK even when no tag found } findtag_end: ! vim_free(st.lbuf); ! vim_regfree(st.orgpat.regmatch.regprog); vim_free(tag_fname); #ifdef FEAT_EMACS_TAGS ! vim_free(st.ebuf); #endif /* *************** *** 2840,2881 **** * matches. When retval == FAIL, free the matches. */ if (retval == FAIL) ! match_count = 0; ! ! if (match_count > 0) ! matches = ALLOC_MULT(char_u *, match_count); ! else ! matches = NULL; ! match_count = 0; ! for (mtt = 0; mtt < MT_COUNT; ++mtt) ! { ! for (i = 0; i < ga_match[mtt].ga_len; ++i) ! { ! mfp = ((char_u **)(ga_match[mtt].ga_data))[i]; ! if (matches == NULL) ! vim_free(mfp); ! else ! { ! if (!name_only) ! { ! // Change mtt back to zero-based. ! *mfp = *mfp - 1; ! ! // change the TAG_SEP back to NUL ! for (p = mfp + 1; *p != NUL; ++p) ! if (*p == TAG_SEP) ! *p = NUL; ! } ! matches[match_count++] = mfp; ! } ! } ! ga_clear(&ga_match[mtt]); ! hash_clear(&ht_match[mtt]); ! } ! ! *matchesp = matches; ! *num_matches = match_count; curbuf->b_help = help_save; #ifdef FEAT_MULTI_LANG --- 2975,2983 ---- * matches. When retval == FAIL, free the matches. */ if (retval == FAIL) ! st.match_count = 0; ! findtags_copy_matches(&st, matchesp, num_matches, name_only); curbuf->b_help = help_save; #ifdef FEAT_MULTI_LANG *** ../vim-8.2.4493/src/testdir/test_tagjump.vim 2021-08-21 15:21:14.662455461 +0100 --- src/testdir/test_tagjump.vim 2022-03-02 20:23:32.515180668 +0000 *************** *** 1427,1432 **** --- 1427,1437 ---- endtry call assert_equal(v:true, caught_431) + " tag name and file name are not separated by a tab + call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", + \ "foo Xfile 1"], 'Xtags') + call assert_fails('tag foo', 'E431:') + call delete('Xtags') call delete('Xfile') set tags& *** ../vim-8.2.4493/src/version.c 2022-03-02 20:11:59.289532141 +0000 --- src/version.c 2022-03-02 20:25:35.066764249 +0000 *************** *** 756,757 **** --- 756,759 ---- { /* Add new patch number below this line */ + /**/ + 4494, /**/ -- Fingers not found - Pound head on keyboard to continue. /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// \\\ \\\ sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///