To: vim_dev@googlegroups.com Subject: Patch 8.2.4538 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 8.2.4538 Problem: The find_tags_in_file() function is too long. Solution: Refactor into smaller functions. (Yegappan Lakshmanan, closes #9920) Files: src/tag.c, src/testdir/test_tagjump.vim *** ../vim-8.2.4537/src/tag.c 2022-03-06 14:27:06.495895690 +0000 --- src/tag.c 2022-03-10 18:23:55.076121513 +0000 *************** *** 39,44 **** --- 39,106 ---- } tagptrs_T; /* + * Return values used when reading lines from a tags file. + */ + typedef enum + { + TAGS_READ_SUCCESS = 1, + TAGS_READ_EOF, + TAGS_READ_IGNORE, + } tags_read_status_T; + + /* + * States used during a tags search + */ + typedef enum + { + TS_START, // at start of file + TS_LINEAR, // linear searching forward, till EOF + TS_BINARY, // binary searching + TS_SKIP_BACK, // skipping backwards + TS_STEP_FORWARD // stepping forwards + } tagsearch_state_T; // Current search state + + /* + * Binary search file offsets in a tags file + */ + typedef struct + { + 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 + } tagsearch_info_T; + + /* + * Return values used when matching tags against a pattern. + */ + typedef enum + { + TAG_MATCH_SUCCESS = 1, + TAG_MATCH_FAIL, + TAG_MATCH_STOP, + TAG_MATCH_NEXT + } tagmatch_status_T; + + /* + * Arguments used for matching tags read from a tags file against a pattern. + */ + typedef struct + { + int matchoff; // tag match offset + int match_re; // TRUE if the tag matches a regexp + int match_no_ic; // TRUE if the tag matches with case + int has_re; // regular expression used + int sortic; // tags file sorted ignoring case (foldcase) + int sort_error; // tags file not sorted + } findtags_match_args_T; + + /* * The matching tags are first stored in one of the hash tables. In * which one depends on the priority of the match. * ht_match[] is used to find duplicates, ga_match[] to keep them in sequence. *************** *** 1577,1589 **** /* * State information used during a tag search */ ! typedef struct { char_u *tag_fname; // name of the tag file pat_T orgpat; // holds unconverted pattern info ! int name_only; // get only tag names ! int get_searchpat; // used for 'showfulltag' int help_only; // only search for help tags #ifdef FEAT_MULTI_LANG char_u *help_lang_find; // lang to be found int is_txt; // flag of file extension #endif --- 1639,1658 ---- /* * State information used during a tag search */ ! typedef struct ! { ! tagsearch_state_T state; // tag search state char_u *tag_fname; // name of the tag file + FILE *fp; // current tags file pointer pat_T orgpat; // holds unconverted pattern info ! int flags; // flags used for tag search ! int tag_file_sorted; // !_TAG_FILE_SORTED value ! int get_searchpat; // used for 'showfulltag' int help_only; // only search for help tags + vimconv_T vimconv; #ifdef FEAT_MULTI_LANG + char_u help_lang[3]; // lang of current tags file + int help_pri; // help language priority char_u *help_lang_find; // lang to be found int is_txt; // flag of file extension #endif *************** *** 1594,1599 **** --- 1663,1669 ---- char_u *lbuf; // line buffer int lbuf_size; // length of lbuf #ifdef FEAT_EMACS_TAGS + int is_etag; // current file is emaces style char_u *ebuf; // additional buffer for etag fname #endif int match_count; // number of matches found *************** *** 1615,1627 **** int mtt; st->tag_fname = alloc(MAXPATHL + 1); st->orgpat.pat = pat; st->orgpat.len = (int)STRLEN(pat); st->orgpat.regmatch.regprog = NULL; st->help_only = (flags & TAG_HELP); - st->name_only = (flags & TAG_NAMES); st->get_searchpat = FALSE; #ifdef FEAT_MULTI_LANG st->help_lang_find = NULL; st->is_txt = FALSE; #endif --- 1685,1701 ---- int mtt; st->tag_fname = alloc(MAXPATHL + 1); + st->fp = NULL; st->orgpat.pat = pat; st->orgpat.len = (int)STRLEN(pat); st->orgpat.regmatch.regprog = NULL; + st->flags = flags; + st->tag_file_sorted = NUL; st->help_only = (flags & TAG_HELP); st->get_searchpat = FALSE; #ifdef FEAT_MULTI_LANG + st->help_lang[0] = NUL; + st->help_pri = 0; st->help_lang_find = NULL; st->is_txt = FALSE; #endif *************** *** 1669,1734 **** #ifdef FEAT_MULTI_LANG /* ! * Initialize the state for searching tags in a Vim help file. * Returns TRUE to process the help file and FALSE to skip the file. */ static int ! findtags_in_help_init( ! findtags_state_T *st, ! int flags, ! char_u *help_lang, ! int *help_pri) { int i; char_u *s; ! // 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(st->tag_fname); if (i > 3 && st->tag_fname[i - 3] == '-') ! STRCPY(help_lang, st->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 FALSE; ! // 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; } } --- 1743,1805 ---- #ifdef FEAT_MULTI_LANG /* ! * Initialize the language and priority used for searching tags in a Vim help ! * file. * Returns TRUE to process the help file and FALSE to skip the file. */ static int ! findtags_in_help_init(findtags_state_T *st) { int i; char_u *s; ! // Keep 'en' as the language if the file extension is '.txt' if (st->is_txt) ! STRCPY(st->help_lang, "en"); else { ! // Prefer help tags according to 'helplang'. Put the two-letter ! // language name in help_lang[]. i = (int)STRLEN(st->tag_fname); if (i > 3 && st->tag_fname[i - 3] == '-') ! STRCPY(st->help_lang, st->tag_fname + i - 2); else ! STRCPY(st->help_lang, "en"); } ! // When searching for a specific language skip tags files for other ! // languages. if (st->help_lang_find != NULL ! && STRICMP(st->help_lang, st->help_lang_find) != 0) return FALSE; ! // For CTRL-] in a help file prefer a match with the same language. ! if ((st->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, st->help_lang, 2) == 0) ! st->help_pri = 0; else { ! // search for the language in 'helplang' ! st->help_pri = 1; for (s = p_hlg; *s != NUL; ++s) { ! if (STRNICMP(s, st->help_lang, 2) == 0) break; ! ++st->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. ! ++st->help_pri; ! if (STRICMP(st->help_lang, "en") != 0) ! ++st->help_pri; } } *************** *** 1738,1755 **** #ifdef FEAT_EVAL /* ! * Use the 'tagfunc' (if configured and enabled) to get the tags. * Return OK if at least 1 tag has been successfully found, NOTDONE if the * 'tagfunc' is not used or the 'tagfunc' returns v:null and FAIL otherwise. */ static int ! findtags_apply_tfu( ! char_u *pat, ! findtags_state_T *st, ! int flags, ! char_u *buf_ffname) { ! int use_tfu = ((flags & TAG_NO_TAGFUNC) == 0); int retval; if (!use_tfu || tfu_in_use || *curbuf->b_p_tfu == NUL) --- 1809,1823 ---- #ifdef FEAT_EVAL /* ! * Use the function set in 'tagfunc' (if configured and enabled) to get the ! * tags. * Return OK if at least 1 tag has been successfully found, NOTDONE if the * 'tagfunc' is not used or the 'tagfunc' returns v:null and FAIL otherwise. */ static int ! findtags_apply_tfu(findtags_state_T *st, char_u *pat, char_u *buf_ffname) { ! int use_tfu = ((st->flags & TAG_NO_TAGFUNC) == 0); int retval; if (!use_tfu || tfu_in_use || *curbuf->b_p_tfu == NUL) *************** *** 1757,1763 **** tfu_in_use = TRUE; retval = find_tagfunc_tags(pat, st->ga_match, &st->match_count, ! flags, buf_ffname); tfu_in_use = FALSE; return retval; --- 1825,1831 ---- tfu_in_use = TRUE; retval = find_tagfunc_tags(pat, st->ga_match, &st->match_count, ! st->flags, buf_ffname); tfu_in_use = FALSE; return retval; *************** *** 1799,1812 **** * then returns a pointer to the new tags file. The old file pointer is saved * in incstack. */ ! static FILE * ! emacs_tags_new_filename(findtags_state_T *st, FILE *fp, int *is_etag) { char_u *p; char_u *fullpath_ebuf; ! if (vim_fgets(st->ebuf, LSIZE, fp)) ! return fp; for (p = st->ebuf; *p && *p != ','; p++) ; --- 1867,1880 ---- * then returns a pointer to the new tags file. The old file pointer is saved * in incstack. */ ! static void ! emacs_tags_new_filename(findtags_state_T *st) { char_u *p; char_u *fullpath_ebuf; ! if (vim_fgets(st->ebuf, LSIZE, st->fp)) ! return; for (p = st->ebuf; *p && *p != ','; p++) ; *************** *** 1817,1857 **** // include statement. Skip the included tags file if it exceeds the // maximum. if (STRNCMP(p + 1, "include", 7) != 0 || incstack_idx >= INCSTACK_SIZE) ! return fp; // Save current "fp" and "tag_fname" in the stack. incstack[incstack_idx].etag_fname = vim_strsave(st->tag_fname); if (incstack[incstack_idx].etag_fname == NULL) ! return fp; ! 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, st->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(st->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); } ! return fp; } /* --- 1885,1925 ---- // include statement. Skip the included tags file if it exceeds the // maximum. if (STRNCMP(p + 1, "include", 7) != 0 || incstack_idx >= INCSTACK_SIZE) ! return; // Save current "fp" and "tag_fname" in the stack. incstack[incstack_idx].etag_fname = vim_strsave(st->tag_fname); if (incstack[incstack_idx].etag_fname == NULL) ! return; ! incstack[incstack_idx].fp = st->fp; ! st->fp = NULL; // Figure out "tag_fname" and "fp" to use for // included file. fullpath_ebuf = expand_tag_fname(st->ebuf, st->tag_fname, FALSE); if (fullpath_ebuf != NULL) { ! st->fp = mch_fopen((char *)fullpath_ebuf, "r"); ! if (st->fp != NULL) { if (STRLEN(fullpath_ebuf) > LSIZE) semsg(_(e_tag_file_path_truncated_for_str), st->ebuf); vim_strncpy(st->tag_fname, fullpath_ebuf, MAXPATHL); ++incstack_idx; ! st->is_etag = FALSE; // we can include anything } vim_free(fullpath_ebuf); } ! if (st->fp == NULL) { // Can't open the included file, skip it and // restore old value of "fp". ! st->fp = incstack[incstack_idx].fp; vim_free(incstack[incstack_idx].etag_fname); } ! return; } /* *************** *** 1862,1876 **** * continue with the parent tags file. Otherwise returns FALSE. */ static int ! emacs_tags_file_eof(findtags_state_T *st, FILE **fp) { if (!incstack_idx) // reached end of file. stop processing. return FALSE; // reached the end of an included tags file. pop it. --incstack_idx; ! fclose(*fp); // end of this file ... ! *fp = incstack[incstack_idx].fp; STRCPY(st->tag_fname, incstack[incstack_idx].etag_fname); vim_free(incstack[incstack_idx].etag_fname); --- 1930,1944 ---- * continue with the parent tags file. Otherwise returns FALSE. */ static int ! emacs_tags_file_eof(findtags_state_T *st) { if (!incstack_idx) // reached end of file. stop processing. return FALSE; // reached the end of an included tags file. pop it. --incstack_idx; ! fclose(st->fp); // end of this file ... ! st->fp = incstack[incstack_idx].fp; STRCPY(st->tag_fname, incstack[incstack_idx].etag_fname); vim_free(incstack[incstack_idx].etag_fname); *************** *** 1943,1989 **** #endif /* * Parse a tags file header line in 'st->lbuf'. ! * Returns TRUE to read the next header line and FALSE to process the line. */ static int ! tags_file_hdr_parse(findtags_state_T *st, vimconv_T *vcp, int *sorted_file) { char_u *p; if (STRNCMP(st->lbuf, "!_TAG_", 6) != 0) // Non-header item before the header, e.g. "!" itself. ! return FALSE; ! // Read header line. if (STRNCMP(st->lbuf, "!_TAG_FILE_SORTED\t", 18) == 0) ! *sorted_file = st->lbuf[18]; 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(vcp, st->lbuf + 20, p_enc); } // Read the next line. Unrecognized flags are ignored. return TRUE; } /* * Convert the encoding of a line read from a tags file in 'st->lbuf'. * Converting the pattern from 'enc' to the tags file encoding doesn't work, ! * because characters are not recognized. */ static void ! findtags_string_convert(findtags_state_T *st, vimconv_T *vcp) { char_u *conv_line; int len; ! conv_line = string_convert(vcp, st->lbuf, NULL); if (conv_line == NULL) return; --- 2011,2499 ---- #endif /* + * Read the next line from a tags file. + * Returns TAGS_READ_SUCCESS if a tags line is successfully read and should be + * processed. + * Returns TAGS_READ_EOF if the end of file is reached. + * Returns TAGS_READ_IGNORE if the current line should be ignored (used when + * reached end of a emacs included tags file) + */ + static tags_read_status_T + findtags_get_next_line(findtags_state_T *st, tagsearch_info_T *sinfo_p) + { + int eof; + off_T offset; + + // For binary search: compute the next offset to use. + if (st->state == TS_BINARY) + { + offset = sinfo_p->low_offset + ((sinfo_p->high_offset + - sinfo_p->low_offset) / 2); + if (offset == sinfo_p->curr_offset) + return TAGS_READ_EOF; // End the binary search without a match. + else + sinfo_p->curr_offset = offset; + } + + // Skipping back (after a match during binary search). + else if (st->state == TS_SKIP_BACK) + { + sinfo_p->curr_offset -= st->lbuf_size * 2; + if (sinfo_p->curr_offset < 0) + { + sinfo_p->curr_offset = 0; + rewind(st->fp); + st->state = TS_STEP_FORWARD; + } + } + + // When jumping around in the file, first read a line to find the + // start of the next line. + if (st->state == TS_BINARY || st->state == TS_SKIP_BACK) + { + // Adjust the search file offset to the correct position + sinfo_p->curr_offset_used = sinfo_p->curr_offset; + vim_fseek(st->fp, sinfo_p->curr_offset, SEEK_SET); + eof = vim_fgets(st->lbuf, st->lbuf_size, st->fp); + if (!eof && sinfo_p->curr_offset != 0) + { + sinfo_p->curr_offset = vim_ftell(st->fp); + if (sinfo_p->curr_offset == sinfo_p->high_offset) + { + // oops, gone a bit too far; try from low offset + vim_fseek(st->fp, sinfo_p->low_offset, SEEK_SET); + sinfo_p->curr_offset = sinfo_p->low_offset; + } + eof = vim_fgets(st->lbuf, st->lbuf_size, st->fp); + } + // skip empty and blank lines + while (!eof && vim_isblankline(st->lbuf)) + { + sinfo_p->curr_offset = vim_ftell(st->fp); + eof = vim_fgets(st->lbuf, st->lbuf_size, st->fp); + } + if (eof) + { + // Hit end of file. Skip backwards. + st->state = TS_SKIP_BACK; + sinfo_p->match_offset = vim_ftell(st->fp); + sinfo_p->curr_offset = sinfo_p->curr_offset_used; + return TAGS_READ_IGNORE; + } + } + // Not jumping around in the file: Read the next line. + else + { + // skip empty and blank lines + do + { + #ifdef FEAT_CSCOPE + if (st->flags & TAG_CSCOPE) + eof = cs_fgets(st->lbuf, st->lbuf_size); + else + #endif + { + sinfo_p->curr_offset = vim_ftell(st->fp); + eof = vim_fgets(st->lbuf, st->lbuf_size, st->fp); + } + } while (!eof && vim_isblankline(st->lbuf)); + + if (eof) + { + #ifdef FEAT_EMACS_TAGS + if (emacs_tags_file_eof(st) == TRUE) + { + // an included tags file. Continue processing the parent + // tags file. + st->is_etag = TRUE; // (only etags can include) + return TAGS_READ_IGNORE; + } + #endif + return TAGS_READ_EOF; + } + } + + return TAGS_READ_SUCCESS; + } + + /* * Parse a tags file header line in 'st->lbuf'. ! * Returns TRUE if the current line in st->lbuf is not a tags header line and ! * should be parsed as a regular tag line. Returns FALSE if the line is a ! * header line and the next header line should be read. */ static int ! findtags_hdr_parse(findtags_state_T *st) { char_u *p; + // Header lines in a tags file start with "!_TAG_" if (STRNCMP(st->lbuf, "!_TAG_", 6) != 0) // Non-header item before the header, e.g. "!" itself. ! return TRUE; ! // Process the header line. if (STRNCMP(st->lbuf, "!_TAG_FILE_SORTED\t", 18) == 0) ! st->tag_file_sorted = st->lbuf[18]; 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(&st->vimconv, st->lbuf + 20, p_enc); } // Read the next line. Unrecognized flags are ignored. + return FALSE; + } + + /* + * Handler to initialize the state when starting to process a new tags file. + * Called in the TS_START state when finding tags from a tags file. + * Returns TRUE if the line read from the tags file should be parsed and + * FALSE if the line should be ignored. + */ + static int + findtags_start_state_handler( + findtags_state_T *st, + int *sortic, + tagsearch_info_T *sinfo_p) + { + #ifdef FEAT_CSCOPE + int use_cscope = (st->flags & TAG_CSCOPE); + #endif + int noic = (st->flags & TAG_NOIC); + off_T filesize; + + // 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]))) + return findtags_hdr_parse(st); + + // Headers ends. + + // 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 + st->state = TS_LINEAR; + else if (st->tag_file_sorted == NUL) + st->state = TS_BINARY; + else if (st->tag_file_sorted == '1') + st->state = TS_BINARY; + else if (st->tag_file_sorted == '2') + { + st->state = TS_BINARY; + *sortic = TRUE; + st->orgpat.regmatch.rm_ic = (p_ic || !noic); + } + else + st->state = TS_LINEAR; + + if (st->state == TS_BINARY && st->orgpat.regmatch.rm_ic && !*sortic) + { + // Binary search won't work for ignoring case, use linear + // search. + st->linear = TRUE; + st->state = TS_LINEAR; + } + + // When starting a binary search, get the size of the file and + // compute the first offset. + if (st->state == TS_BINARY) + { + if (vim_fseek(st->fp, 0L, SEEK_END) != 0) + // can't seek, don't use binary search + st->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(st->fp); + vim_fseek(st->fp, 0L, SEEK_SET); + + // Calculate the first read offset in the file. Start + // the search in the middle of the file. + sinfo_p->low_offset = 0; + sinfo_p->low_char = 0; + sinfo_p->high_offset = filesize; + sinfo_p->curr_offset = 0; + sinfo_p->high_char = 0xff; + } + return FALSE; + } + return TRUE; } /* + * Parse a tag line read from a tags file. + * Returns OK if a tags line is successfully parsed. + * Returns FAIL if an error is encountered. + */ + static int + findtags_parse_line(findtags_state_T *st, tagptrs_T *tagpp) + { + int status; + + // 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 + && !st->is_etag + #endif + ) + { + CLEAR_FIELD(*tagpp); + tagpp->tagname = st->lbuf; + tagpp->tagname_end = vim_strchr(st->lbuf, TAB); + if (tagpp->tagname_end == NULL) + { + // Corrupted tag line. + return FAIL; + } + + // Can be a matching tag, isolate the file name and command. + tagpp->fname = tagpp->tagname_end + 1; + tagpp->fname_end = vim_strchr(tagpp->fname, TAB); + if (tagpp->fname_end == NULL) + status = FAIL; + else + { + tagpp->command = tagpp->fname_end + 1; + status = OK; + } + } + else + status = parse_tag_line(st->lbuf, + #ifdef FEAT_EMACS_TAGS + st->is_etag, + #endif + tagpp); + + if (status == FAIL) + return FAIL; + + #ifdef FEAT_EMACS_TAGS + if (st->is_etag) + tagpp->fname = st->ebuf; + #endif + + return OK; + } + + /* + * Initialize the structure used for tag matching. + */ + static void + findtags_matchargs_init(findtags_match_args_T *margs, int flags) + { + margs->matchoff = 0; // match offset + margs->match_re = FALSE; // match with regexp + margs->match_no_ic = FALSE; // matches with case + margs->has_re = (flags & TAG_REGEXP); // regexp used + margs->sortic = FALSE; // tag file sorted in nocase + margs->sort_error = FALSE; // tags file not sorted + } + + /* + * Compares the tag name in 'tagpp->tagname' with a search pattern in + * 'st->orgpat.head'. + * Returns TAG_MATCH_SUCCESS if the tag matches, TAG_MATCH_FAIL if the tag + * doesn't match, TAG_MATCH_NEXT to look for the next matching tag (used in a + * binary search) and TAG_MATCH_STOP if all the tags are processed without a + * match. Uses the values in 'margs' for doing the comparison. + */ + static tagmatch_status_T + findtags_match_tag( + findtags_state_T *st, + tagptrs_T *tagpp, + findtags_match_args_T *margs, + tagsearch_info_T *sinfo_p) + { + int match = FALSE; + int cmplen; + int i; + int tagcmp; + + // Skip this line if the length of the tag is different and + // there is no regexp, or the tag is too short. + if (st->orgpat.headlen + #ifdef FEAT_EMACS_TAGS + && !st->is_etag + #endif + ) + { + cmplen = (int)(tagpp->tagname_end - tagpp->tagname); + if (p_tl != 0 && cmplen > p_tl) // adjust for 'taglength' + cmplen = p_tl; + if (margs->has_re && st->orgpat.headlen < cmplen) + cmplen = st->orgpat.headlen; + else if (st->state == TS_LINEAR && st->orgpat.headlen != cmplen) + return TAG_MATCH_FAIL; + + if (st->state == TS_BINARY) + { + // Simplistic check for unsorted tags file. + i = (int)tagpp->tagname[0]; + if (margs->sortic) + i = (int)TOUPPER_ASC(tagpp->tagname[0]); + if (i < sinfo_p->low_char || i > sinfo_p->high_char) + margs->sort_error = TRUE; + + // Compare the current tag with the searched tag. + if (margs->sortic) + tagcmp = tag_strnicmp(tagpp->tagname, st->orgpat.head, + (size_t)cmplen); + else + tagcmp = STRNCMP(tagpp->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. + st->state = TS_SKIP_BACK; + sinfo_p->match_offset = sinfo_p->curr_offset; + return TAG_MATCH_NEXT; + } + if (tagcmp < 0) + { + sinfo_p->curr_offset = vim_ftell(st->fp); + if (sinfo_p->curr_offset < sinfo_p->high_offset) + { + sinfo_p->low_offset = sinfo_p->curr_offset; + if (margs->sortic) + sinfo_p->low_char = TOUPPER_ASC(tagpp->tagname[0]); + else + sinfo_p->low_char = tagpp->tagname[0]; + return TAG_MATCH_NEXT; + } + } + if (tagcmp > 0 && sinfo_p->curr_offset != sinfo_p->high_offset) + { + sinfo_p->high_offset = sinfo_p->curr_offset; + if (margs->sortic) + sinfo_p->high_char = TOUPPER_ASC(tagpp->tagname[0]); + else + sinfo_p->high_char = tagpp->tagname[0]; + return TAG_MATCH_NEXT; + } + + // No match yet and are at the end of the binary search. + return TAG_MATCH_STOP; + } + else if (st->state == TS_SKIP_BACK) + { + if (MB_STRNICMP(tagpp->tagname, st->orgpat.head, cmplen) != 0) + st->state = TS_STEP_FORWARD; + else + // Have to skip back more. Restore the curr_offset + // used, otherwise we get stuck at a long line. + sinfo_p->curr_offset = sinfo_p->curr_offset_used; + return TAG_MATCH_NEXT; + } + else if (st->state == TS_STEP_FORWARD) + { + if (MB_STRNICMP(tagpp->tagname, st->orgpat.head, cmplen) != 0) + { + if ((off_T)vim_ftell(st->fp) > sinfo_p->match_offset) + return TAG_MATCH_STOP; // past last match + else + return TAG_MATCH_NEXT; // before first match + } + } + else + // skip this match if it can't match + if (MB_STRNICMP(tagpp->tagname, st->orgpat.head, cmplen) != 0) + return TAG_MATCH_FAIL; + } + + // First try matching with the pattern literally (also when it is + // a regexp). + cmplen = (int)(tagpp->tagname_end - tagpp->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(tagpp->tagname, st->orgpat.pat, cmplen) == 0); + if (match) + margs->match_no_ic = + (STRNCMP(tagpp->tagname, st->orgpat.pat, cmplen) == 0); + } + else + match = (STRNCMP(tagpp->tagname, st->orgpat.pat, cmplen) == 0); + } + + // Has a regexp: Also find tags matching regexp. + margs->match_re = FALSE; + if (!match && st->orgpat.regmatch.regprog != NULL) + { + int cc; + + cc = *tagpp->tagname_end; + *tagpp->tagname_end = NUL; + match = vim_regexec(&st->orgpat.regmatch, tagpp->tagname, (colnr_T)0); + if (match) + { + margs->matchoff = (int)(st->orgpat.regmatch.startp[0] - + tagpp->tagname); + if (st->orgpat.regmatch.rm_ic) + { + st->orgpat.regmatch.rm_ic = FALSE; + margs->match_no_ic = vim_regexec(&st->orgpat.regmatch, + tagpp->tagname, (colnr_T)0); + st->orgpat.regmatch.rm_ic = TRUE; + } + } + *tagpp->tagname_end = cc; + margs->match_re = TRUE; + } + + return match ? TAG_MATCH_SUCCESS : TAG_MATCH_FAIL; + } + + /* * Convert the encoding of a line read from a tags file in 'st->lbuf'. * Converting the pattern from 'enc' to the tags file encoding doesn't work, ! * because characters are not recognized. The converted line is saved in ! * st->lbuf. */ static void ! findtags_string_convert(findtags_state_T *st) { char_u *conv_line; int len; ! conv_line = string_convert(&st->vimconv, st->lbuf, NULL); if (conv_line == NULL) return; *************** *** 2002,2024 **** } } static int findtags_add_match( findtags_state_T *st, ! tagptrs_T *tagp, char_u *buf_ffname, ! int flags UNUSED, ! hash_T *hash, ! int match_re, ! int match_no_ic, ! int matchoff, ! int is_etag UNUSED, ! char_u *help_lang UNUSED, ! int help_pri UNUSED) { #ifdef FEAT_CSCOPE ! int use_cscope = (flags & TAG_CSCOPE); #endif int mtt; int len = 0; int is_current; // file name matches --- 2512,2534 ---- } } + /* + * Add a matching tag found in a tags file to st->ht_match and st->ga_match. + * Returns OK if successfully added the match and FAIL on memory allocation + * failure. + */ static int findtags_add_match( findtags_state_T *st, ! tagptrs_T *tagpp, ! findtags_match_args_T *margs, char_u *buf_ffname, ! hash_T *hash) { #ifdef FEAT_CSCOPE ! int use_cscope = (st->flags & TAG_CSCOPE); #endif + int name_only = (st->flags & TAG_NAMES); int mtt; int len = 0; int is_current; // file name matches *************** *** 2039,2053 **** // 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, st->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 --- 2549,2562 ---- // Decide in which array to store this match. is_current = test_for_current( #ifdef FEAT_EMACS_TAGS ! st->is_etag, #endif ! tagpp->fname, tagpp->fname_end, st->tag_fname, buf_ffname); #ifdef FEAT_EMACS_TAGS is_static = FALSE; ! if (!st->is_etag) // emacs tags are never static #endif ! is_static = test_for_static(tagpp); // decide in which of the sixteen tables to store this // match *************** *** 2065,2073 **** 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; } --- 2574,2582 ---- else mtt = MT_GL_OTH; } ! if (st->orgpat.regmatch.rm_ic && !margs->match_no_ic) mtt += MT_IC_OFF; ! if (margs->match_re) mtt += MT_RE_OFF; } *************** *** 2085,2119 **** // 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 (st->name_only) { if (st->get_searchpat) { ! char_u *temp_end = tagp->command; if (*temp_end == '/') while (*temp_end && *temp_end != '\r' --- 2594,2629 ---- // sorting it later. The heuristic is ignored for // detecting duplicates. // The format is {tagname}@{lang}NUL{heuristic}NUL ! *tagpp->tagname_end = NUL; ! len = (int)(tagpp->tagname_end - tagpp->tagname); mfp = alloc(sizeof(char_u) + len + 10 + ML_EXTRA + 1); if (mfp != NULL) { int heuristic; p = mfp; ! STRCPY(p, tagpp->tagname); #ifdef FEAT_MULTI_LANG p[len] = '@'; ! STRCPY(p + len + 1, st->help_lang); #endif ! heuristic = help_heuristic(tagpp->tagname, ! margs->match_re ? margs->matchoff : 0, ! !margs->match_no_ic); #ifdef FEAT_MULTI_LANG ! heuristic += st->help_pri; #endif sprintf((char *)p + len + 1 + ML_EXTRA, "%06d", heuristic); } ! *tagpp->tagname_end = TAB; } ! else if (name_only) { if (st->get_searchpat) { ! char_u *temp_end = tagpp->command; if (*temp_end == '/') while (*temp_end && *temp_end != '\r' *************** *** 2121,2132 **** && *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; --- 2631,2642 ---- && *temp_end != '$') temp_end++; ! if (tagpp->command + 2 < temp_end) { ! len = (int)(temp_end - tagpp->command - 2); mfp = alloc(len + 2); if (mfp != NULL) ! vim_strncpy(mfp, tagpp->command + 2, len); } else mfp = NULL; *************** *** 2134,2143 **** } 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) --- 2644,2653 ---- } else { ! len = (int)(tagpp->tagname_end - tagpp->tagname); mfp = alloc(sizeof(char_u) + len + 1); if (mfp != NULL) ! vim_strncpy(mfp, tagpp->tagname, len); // if wanted, re-read line to get long form too if (State & INSERT) *************** *** 2161,2167 **** // 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; --- 2671,2677 ---- // 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 (st->is_etag) { ebuf_len = STRLEN(st->ebuf); len += (int)ebuf_len + 1; *************** *** 2183,2189 **** 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; --- 2693,2699 ---- p[tag_fname_len + 1] = TAG_SEP; s = p + 1 + tag_fname_len + 1; #ifdef FEAT_EMACS_TAGS ! if (st->is_etag) { STRCPY(s, st->ebuf); s[ebuf_len] = TAG_SEP; *************** *** 2222,2233 **** st->stop_searching = TRUE; return FAIL; } ! else ! { ! ((char_u **)(st->ga_match[mtt].ga_data)) ! [st->ga_match[mtt].ga_len++] = mfp; ! st->match_count++; ! } } else // duplicate tag, drop it --- 2732,2741 ---- st->stop_searching = TRUE; return FAIL; } ! ! ((char_u **)(st->ga_match[mtt].ga_data)) ! [st->ga_match[mtt].ga_len++] = mfp; ! st->match_count++; } else // duplicate tag, drop it *************** *** 2238,2354 **** } /* ! * Search for tags matching 'st->orgpat.pat' in the 'st->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( ! findtags_state_T *st, ! int flags, ! char_u *buf_ffname) ! { ! FILE *fp = NULL; ! tagptrs_T tagp; ! int eof = FALSE; // found end-of-file ! int i; ! #ifdef FEAT_MULTI_LANG ! int help_pri = 0; ! char_u help_lang[3] = ""; // lang of current tags file ! #endif ! int tag_file_sorted = NUL; // !_TAG_FILE_SORTED value ! off_T filesize; ! int tagcmp; ! off_T offset; ! enum ! { ! TS_START, // at start of file ! TS_LINEAR, // linear searching forward, till EOF ! TS_BINARY, // binary searching ! TS_SKIP_BACK, // skipping backwards ! TS_STEP_FORWARD // stepping forwards ! } state; // Current search state ! 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; ! ! 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 is_etag; // current file is emaces style ! ! hash_T hash = 0; ! ! int sort_error = FALSE; // tags file not sorted ! int sortic = FALSE; // tag file sorted in nocase ! int noic = (flags & TAG_NOIC); ! int line_error = FALSE; // syntax error ! int has_re = (flags & TAG_REGEXP); // regexp used #ifdef FEAT_CSCOPE ! int use_cscope = (flags & TAG_CSCOPE); #endif ! vimconv_T vimconv; ! ! vimconv.vc_type = CONV_NONE; // This is only to avoid a compiler warning for using search_info // uninitialised. CLEAR_FIELD(search_info); - // 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) - { - if (!findtags_in_help_init(st, flags, help_lang, &help_pri)) - return OK; - } - #endif - - if ((fp = mch_fopen((char *)st->tag_fname, "r")) == NULL) - return OK; - - if (p_verbose >= 5) - { - verbose_enter(); - smsg(_("Searching tags file %s"), st->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 - is_etag = 0; // default is: not emacs style - // Read and parse the lines in the file one by one for (;;) { // check for CTRL-C typed, more often when jumping around ! if (state == TS_BINARY || state == TS_SKIP_BACK) line_breakcheck(); else fast_breakcheck(); ! if ((flags & TAG_INS_COMP)) // Double brackets for gcc ins_compl_check_keys(30, FALSE); if (got_int || ins_compl_interrupted()) { --- 2746,2782 ---- } /* ! * Read and get all the tags from file st->tag_fname. ! * Returns OK if all the tags are processed successfully and FAIL is a tag ! * format error is encountered. */ static int ! findtags_get_all_tags( ! findtags_state_T *st, ! findtags_match_args_T *margs, ! char_u *buf_ffname) ! { ! tagptrs_T tagp; ! tagsearch_info_T search_info; ! int retval; #ifdef FEAT_CSCOPE ! int use_cscope = (st->flags & TAG_CSCOPE); #endif ! hash_T hash = 0; // This is only to avoid a compiler warning for using search_info // uninitialised. CLEAR_FIELD(search_info); // Read and parse the lines in the file one by one for (;;) { // check for CTRL-C typed, more often when jumping around ! if (st->state == TS_BINARY || st->state == TS_SKIP_BACK) line_breakcheck(); else fast_breakcheck(); ! if ((st->flags & TAG_INS_COMP)) // Double brackets for gcc ins_compl_check_keys(30, FALSE); if (got_int || ins_compl_interrupted()) { *************** *** 2364,2572 **** } if (st->get_searchpat) goto line_read_in; - // 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 ! { ! // skip empty and blank lines ! do ! { ! #ifdef FEAT_CSCOPE ! if (use_cscope) ! eof = cs_fgets(st->lbuf, st->lbuf_size); ! else ! #endif ! { ! search_info.curr_offset = vim_ftell(fp); ! eof = vim_fgets(st->lbuf, st->lbuf_size, fp); ! } ! } while (!eof && vim_isblankline(st->lbuf)); - if (eof) - { - #ifdef FEAT_EMACS_TAGS - if (emacs_tags_file_eof(st, &fp) == TRUE) - { - // an included tags file. Continue processing the parent - // tags file. - is_etag = 1; // (only etags can include) - continue; - } - #endif - break; // end of file - } - } line_read_in: ! if (vimconv.vc_type != CONV_NONE) ! findtags_string_convert(st, &vimconv); #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; ! fp = emacs_tags_new_filename(st, fp, &is_etag); 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 (tags_file_hdr_parse(st, &vimconv, &tag_file_sorted)) ! // Read the next line. Unrecognized flags are ignored. ! continue; ! ! goto parse_line; ! } ! ! // Headers ends. ! ! /* ! * 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; ! } ! ! // 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; - } } - 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. --- 2792,2834 ---- } if (st->get_searchpat) goto line_read_in; ! retval = findtags_get_next_line(st, &search_info); ! if (retval == TAGS_READ_IGNORE) ! continue; ! if (retval == TAGS_READ_EOF) ! break; line_read_in: ! if (st->vimconv.vc_type != CONV_NONE) ! findtags_string_convert(st); #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 ) { ! st->is_etag = TRUE; // in case at the start ! st->state = TS_LINEAR; ! emacs_tags_new_filename(st); continue; } #endif ! // When still at the start of the file, check for Emacs tags file ! // format, and for "not sorted" flag. ! if (st->state == TS_START) { ! if (findtags_start_state_handler(st, &margs->sortic, &search_info) == FALSE) continue; } // 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. *************** *** 2582,2825 **** st->lbuf = alloc(st->lbuf_size); if (st->lbuf == NULL) { ! if (fp != NULL) ! fclose(fp); ! return FAIL; } ! 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; 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; ! 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 ! // 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); ! if (tagp.fname_end == NULL) ! i = FAIL; ! else ! { ! tagp.command = tagp.fname_end + 1; ! 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) { ! if (findtags_add_match(st, &tagp, buf_ffname, flags, &hash, ! match_re, match_no_ic, matchoff, ! is_etag, ! #ifdef FEAT_MULTI_LANG ! help_lang, help_pri ! #else ! (char_u *)"", 0 ! #endif ! ) == FAIL) ! break; } ! #ifdef FEAT_CSCOPE ! if (use_cscope && eof) ! break; #endif ! } // forever if (line_error) { --- 2844,2943 ---- st->lbuf = alloc(st->lbuf_size); if (st->lbuf == NULL) { ! if (st->fp != NULL) ! fclose(st->fp); ! break; } ! if (st->state == TS_STEP_FORWARD) // Seek to the same position to read the same line again ! vim_fseek(st->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; continue; } ! if (findtags_parse_line(st, &tagp) == FAIL) ! return FAIL; ! retval = findtags_match_tag(st, &tagp, margs, &search_info); ! if (retval == TAG_MATCH_NEXT) ! continue; ! if (retval == TAG_MATCH_STOP) ! break; ! // If a match is found, add it to ht_match[] and ga_match[]. ! if (retval == TAG_MATCH_SUCCESS) ! { ! if (findtags_add_match(st, &tagp, margs, buf_ffname, &hash) ! == FAIL) ! break; ! } ! } // forever ! return OK; ! } ! /* ! * Search for tags matching 'st->orgpat.pat' in the 'st->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 ! findtags_in_file(findtags_state_T *st, char_u *buf_ffname) ! { ! findtags_match_args_T margs; ! int line_error = FALSE; // syntax error ! #ifdef FEAT_CSCOPE ! int use_cscope = FALSE; ! #endif ! st->vimconv.vc_type = CONV_NONE; ! st->tag_file_sorted = NUL; ! st->fp = NULL; ! findtags_matchargs_init(&margs, st->flags); ! // 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 ! use_cscope = (st->flags & TAG_CSCOPE); ! if (use_cscope) ! st->fp = NULL; // avoid GCC warning ! else #endif ! { ! #ifdef FEAT_MULTI_LANG ! if (curbuf->b_help) { ! if (!findtags_in_help_init(st)) ! return OK; } #endif ! st->fp = mch_fopen((char *)st->tag_fname, "r"); ! if (st->fp == NULL) ! return OK; ! if (p_verbose >= 5) { ! verbose_enter(); ! smsg(_("Searching tags file %s"), st->tag_fname); ! verbose_leave(); } ! } ! st->did_open = TRUE; // remember that we found at least one file ! ! st->state = TS_START; // we're at the start of the file ! #ifdef FEAT_EMACS_TAGS ! st->is_etag = FALSE; // default is: not emacs style #endif ! ! // Read and parse the lines in the file one by one ! if (findtags_get_all_tags(st, &margs, buf_ffname) == FAIL) ! line_error = TRUE; if (line_error) { *************** *** 2827,2857 **** #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 emacs_tags_incstack_free(); #endif ! if (vimconv.vc_type != CONV_NONE) ! convert_setup(&vimconv, NULL, NULL); ! tag_file_sorted = NUL; ! if (sort_error) ! { semsg(_(e_tags_file_not_sorted_str), st->tag_fname); - sort_error = FALSE; - } ! /* ! * Stop searching if sufficient tags have been found. ! */ if (st->match_count >= st->mincount) st->stop_searching = TRUE; --- 2945,2967 ---- #ifdef FEAT_CSCOPE if (!use_cscope) #endif ! semsg(_("Before byte %ld"), (long)vim_ftell(st->fp)); st->stop_searching = TRUE; line_error = FALSE; } ! if (st->fp != NULL) ! fclose(st->fp); #ifdef FEAT_EMACS_TAGS emacs_tags_incstack_free(); #endif ! if (st->vimconv.vc_type != CONV_NONE) ! convert_setup(&st->vimconv, NULL, NULL); ! if (margs.sort_error) semsg(_(e_tags_file_not_sorted_str), st->tag_fname); ! // Stop searching if sufficient tags have been found. if (st->match_count >= st->mincount) st->stop_searching = TRUE; *************** *** 2860,2872 **** /* * Copy the tags found by find_tags() to 'matchesp'. */ ! static void ! findtags_copy_matches( ! findtags_state_T *st, ! char_u ***matchesp, ! int *num_matches) { char_u **matches; int mtt; int i; --- 2970,2981 ---- /* * Copy the tags found by find_tags() to 'matchesp'. + * Returns the number of matches copied. */ ! static int ! findtags_copy_matches(findtags_state_T *st, char_u ***matchesp) { + int name_only = (st->flags & TAG_NAMES); char_u **matches; int mtt; int i; *************** *** 2887,2893 **** vim_free(mfp); else { ! if (!st->name_only) { // Change mtt back to zero-based. *mfp = *mfp - 1; --- 2996,3002 ---- vim_free(mfp); else { ! if (!name_only) { // Change mtt back to zero-based. *mfp = *mfp - 1; *************** *** 2906,2912 **** } *matchesp = matches; ! *num_matches = st->match_count; } /* --- 3015,3021 ---- } *matchesp = matches; ! return st->match_count; } /* *************** *** 2941,2947 **** 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 { --- 3050,3056 ---- 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 { *************** *** 3035,3041 **** goto findtag_end; #ifdef FEAT_EVAL ! retval = findtags_apply_tfu(pat, &st, flags, buf_ffname); if (retval != NOTDONE) goto findtag_end; --- 3144,3150 ---- goto findtag_end; #ifdef FEAT_EVAL ! retval = findtags_apply_tfu(&st, pat, buf_ffname); if (retval != NOTDONE) goto findtag_end; *************** *** 3079,3085 **** get_tagfname(&tn, first_file, st.tag_fname) == OK; first_file = FALSE) { ! if (find_tags_in_file(&st, flags, buf_ffname) == FAIL) goto findtag_end; if (st.stop_searching #ifdef FEAT_CSCOPE --- 3188,3194 ---- get_tagfname(&tn, first_file, st.tag_fname) == OK; first_file = FALSE) { ! if (findtags_in_file(&st, buf_ffname) == FAIL) goto findtag_end; if (st.stop_searching #ifdef FEAT_CSCOPE *************** *** 3128,3134 **** if (retval == FAIL) st.match_count = 0; ! findtags_copy_matches(&st, matchesp, num_matches); curbuf->b_help = help_save; #ifdef FEAT_MULTI_LANG --- 3237,3243 ---- if (retval == FAIL) st.match_count = 0; ! *num_matches = findtags_copy_matches(&st, matchesp); curbuf->b_help = help_save; #ifdef FEAT_MULTI_LANG *** ../vim-8.2.4537/src/testdir/test_tagjump.vim 2022-03-06 14:27:06.495895690 +0000 --- src/testdir/test_tagjump.vim 2022-03-10 18:23:55.076121513 +0000 *************** *** 811,821 **** " Test for an unsorted tags file func Test_tag_sort() ! call writefile([ \ "first\tXfoo\t1", \ "ten\tXfoo\t3", ! \ "six\tXfoo\t2"], ! \ 'Xtags') set tags=Xtags let code =<< trim [CODE] int first() {} --- 811,821 ---- " Test for an unsorted tags file func Test_tag_sort() ! let l = [ \ "first\tXfoo\t1", \ "ten\tXfoo\t3", ! \ "six\tXfoo\t2"] ! call writefile(l, 'Xtags') set tags=Xtags let code =<< trim [CODE] int first() {} *************** *** 826,832 **** --- 826,839 ---- call assert_fails('tag first', 'E432:') + " When multiple tag files are not sorted, then message should be displayed + " multiple times + call writefile(l, 'Xtags2') + set tags=Xtags,Xtags2 + call assert_fails('tag first', ['E432:', 'E432:']) + call delete('Xtags') + call delete('Xtags2') call delete('Xfoo') set tags& %bwipe *** ../vim-8.2.4537/src/version.c 2022-03-10 15:51:20.992688946 +0000 --- src/version.c 2022-03-10 18:32:19.243177355 +0000 *************** *** 752,753 **** --- 752,755 ---- { /* Add new patch number below this line */ + /**/ + 4538, /**/ -- hundred-and-one symptoms of being an internet addict: 224. You set up your own Web page. You set up a Web page for each of your kids... and your pets. /// 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 ///