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 <mtt> is the "mtt" value plus 1 to avoid NUL.
len = (int)tag_fname_len + (int)STRLEN(st->lbuf) + 3;
#ifdef FEAT_EMACS_TAGS
! if (is_etag)
{
ebuf_len = STRLEN(st->ebuf);
len += (int)ebuf_len + 1;
--- 2671,2677 ----
// Here <mtt> is the "mtt" value plus 1 to avoid NUL.
len = (int)tag_fname_len + (int)STRLEN(st->lbuf) + 3;
#ifdef FEAT_EMACS_TAGS
! if (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 -- [email protected] -- http://www.Moolenaar.net \\\
/// \\\
\\\ sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ ///
\\\ help me help AIDS victims -- http://ICCF-Holland.org ///
--
--
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php
---
You received this message because you are subscribed to the Google Groups
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/vim_dev/20220310183727.6AD7C1C0DCA%40moolenaar.net.