diff options
Diffstat (limited to 'src/nvim/tag.c')
-rw-r--r-- | src/nvim/tag.c | 2719 |
1 files changed, 1426 insertions, 1293 deletions
diff --git a/src/nvim/tag.c b/src/nvim/tag.c index 5270412382..73aa6eb9ca 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -1,34 +1,41 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com -/* - * Code to handle tags and the tag stack - */ +// Code to handle tags and the tag stack #include <assert.h> +#include <ctype.h> #include <inttypes.h> #include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> #include <string.h> #include "nvim/ascii.h" +#include "nvim/autocmd.h" #include "nvim/buffer.h" #include "nvim/charset.h" #include "nvim/cmdexpand.h" #include "nvim/cursor.h" #include "nvim/drawscreen.h" -#include "nvim/edit.h" #include "nvim/eval.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/ex_cmds.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/ex_docmd.h" -#include "nvim/ex_getln.h" #include "nvim/file_search.h" #include "nvim/fileio.h" #include "nvim/fold.h" #include "nvim/garray.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/hashtab.h" #include "nvim/help.h" -#include "nvim/if_cscope.h" +#include "nvim/highlight_defs.h" #include "nvim/input.h" #include "nvim/insexpand.h" +#include "nvim/macros.h" #include "nvim/mark.h" #include "nvim/mbyte.h" #include "nvim/memory.h" @@ -38,50 +45,47 @@ #include "nvim/optionstr.h" #include "nvim/os/input.h" #include "nvim/os/os.h" +#include "nvim/os/os_defs.h" #include "nvim/os/time.h" -#include "nvim/os_unix.h" #include "nvim/path.h" +#include "nvim/pos.h" #include "nvim/quickfix.h" #include "nvim/regexp.h" #include "nvim/runtime.h" #include "nvim/search.h" #include "nvim/strings.h" #include "nvim/tag.h" +#include "nvim/types.h" #include "nvim/ui.h" #include "nvim/vim.h" #include "nvim/window.h" -/* - * Structure to hold pointers to various items in a tag line. - */ +// Structure to hold pointers to various items in a tag line. typedef struct tag_pointers { // filled in by parse_tag_line(): - char_u *tagname; // start of tag name (skip "file:") - // - char_u *tagname_end; // char after tag name - char_u *fname; // first char of file name - char_u *fname_end; // char after file name - char_u *command; // first char of command + char *tagname; // start of tag name (skip "file:") + char *tagname_end; // char after tag name + char *fname; // first char of file name + char *fname_end; // char after file name + char *command; // first char of command // filled in by parse_match(): - char_u *command_end; // first char after command - char_u *tag_fname; // file name of the tags file. This is used + char *command_end; // first char after command + char *tag_fname; // file name of the tags file. This is used // when 'tr' is set. - char_u *tagkind; // "kind:" value - char_u *tagkind_end; // end of tagkind - char_u *user_data; // user_data string - char_u *user_data_end; // end of user_data - linenr_T tagline; // "line:" value + char *tagkind; // "kind:" value + char *tagkind_end; // end of tagkind + char *user_data; // user_data string + char *user_data_end; // end of user_data + linenr_T tagline; // "line:" value } tagptrs_T; -/* - * Structure to hold info about the tag pattern being used. - */ +// Structure to hold info about the tag pattern being used. typedef struct { - char_u *pat; // the pattern - int len; // length of pat[] - char_u *head; // start of pattern head - int headlen; // length of head[] - regmatch_T regmatch; // regexp program, may be NULL + char *pat; // the pattern + int len; // length of pat[] + char *head; // start of pattern head + int headlen; // length of head[] + regmatch_T regmatch; // regexp program, may be NULL } pat_T; // The matching tags are first stored in one of the hash tables. In @@ -89,45 +93,165 @@ typedef struct { // ht_match[] is used to find duplicates, ga_match[] to keep them in sequence. // At the end, the matches from ga_match[] are concatenated, to make a list // sorted on priority. -#define MT_ST_CUR 0 // static match in current file -#define MT_GL_CUR 1 // global match in current file -#define MT_GL_OTH 2 // global match in other file -#define MT_ST_OTH 3 // static match in other file -#define MT_IC_OFF 4 // add for icase match -#define MT_RE_OFF 8 // add for regexp match -#define MT_MASK 7 // mask for printing priority -#define MT_COUNT 16 +enum { + MT_ST_CUR = 0, // static match in current file + MT_GL_CUR = 1, // global match in current file + MT_GL_OTH = 2, // global match in other file + MT_ST_OTH = 3, // static match in other file + MT_IC_OFF = 4, // add for icase match + MT_RE_OFF = 8, // add for regexp match + MT_MASK = 7, // mask for printing priority + MT_COUNT = 16, +}; static char *mt_names[MT_COUNT/2] = { "FSC", "F C", "F ", "FS ", " SC", " C", " ", " S " }; #define NOTAGFILE 99 // return value for jumpto_tag -static char_u *nofile_fname = NULL; // fname for NOTAGFILE error +static char *nofile_fname = NULL; // fname for NOTAGFILE error + +/// 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; + +/// 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 + bool match_re; ///< true if the tag matches a regexp + bool match_no_ic; ///< true if the tag matches with case + bool has_re; ///< regular expression used + bool sortic; ///< tags file sorted ignoring case (foldcase) + bool sort_error; ///< tags file not sorted +} findtags_match_args_T; + +/// State information used during a tag search +typedef struct { + tagsearch_state_T state; ///< tag search state + bool stop_searching; ///< stop when match found or error + pat_T *orgpat; ///< holds unconverted pattern info + char *lbuf; ///< line buffer + int lbuf_size; ///< length of lbuf + char *tag_fname; ///< name of the tag file + FILE *fp; ///< current tags file pointer + int flags; ///< flags used for tag search + int tag_file_sorted; ///< !_TAG_FILE_SORTED value + bool get_searchpat; ///< used for 'showfulltag' + bool help_only; ///< only search for help tags + bool did_open; ///< did open a tag file + int mincount; ///< MAXCOL: find all matches + ///< other: minimal number of matches + bool linear; ///< do a linear search + vimconv_T vimconv; + char help_lang[3]; ///< lang of current tags file + int help_pri; ///< help language priority + char *help_lang_find; ///< lang to be found + bool is_txt; ///< flag of file extension + int match_count; ///< number of matches found + garray_T ga_match[MT_COUNT]; ///< stores matches in sequence + hashtab_T ht_match[MT_COUNT]; ///< stores matches by key +} findtags_state_T; #ifdef INCLUDE_GENERATED_DECLARATIONS # include "tag.c.generated.h" #endif -static char_u *bottommsg = (char_u *)N_("E555: at bottom of tag stack"); -static char_u *topmsg = (char_u *)N_("E556: at top of tag stack"); -static char_u *recurmsg - = (char_u *)N_("E986: cannot modify the tag stack within tagfunc"); -static char_u *tfu_inv_ret_msg - = (char_u *)N_("E987: invalid return value from tagfunc"); +static char *bottommsg = N_("E555: at bottom of tag stack"); +static char *topmsg = N_("E556: at top of tag stack"); +static char *recurmsg = N_("E986: cannot modify the tag stack within tagfunc"); +static char *tfu_inv_ret_msg = N_("E987: invalid return value from tagfunc"); +static char e_window_unexpectedly_close_while_searching_for_tags[] + = N_("E1299: Window unexpectedly closed while searching for tags"); -static char_u *tagmatchname = NULL; // name of last used tag +static char *tagmatchname = NULL; // name of last used tag -/* - * Tag for preview window is remembered separately, to avoid messing up the - * normal tagstack. - */ +// Tag for preview window is remembered separately, to avoid messing up the +// normal tagstack. static taggy_T ptag_entry = { NULL, INIT_FMARK, 0, 0, NULL }; static int tfu_in_use = false; // disallow recursive call of tagfunc +static Callback tfu_cb; // 'tagfunc' callback function // Used instead of NUL to separate tag fields in the growarrays. #define TAG_SEP 0x02 +/// Reads the 'tagfunc' option value and convert that to a callback value. +/// Invoked when the 'tagfunc' option is set. The option value can be a name of +/// a function (string), or function(<name>) or funcref(<name>) or a lambda. +int set_tagfunc_option(void) +{ + callback_free(&tfu_cb); + callback_free(&curbuf->b_tfu_cb); + + if (*curbuf->b_p_tfu == NUL) { + return OK; + } + + if (option_set_callback_func(curbuf->b_p_tfu, &tfu_cb) == FAIL) { + return FAIL; + } + + callback_copy(&curbuf->b_tfu_cb, &tfu_cb); + + return OK; +} + +#if defined(EXITFREE) +void free_tagfunc_option(void) +{ + callback_free(&tfu_cb); +} +#endif + +/// Mark the global 'tagfunc' callback with "copyID" so that it is not garbage +/// collected. +bool set_ref_in_tagfunc(int copyID) +{ + return set_ref_in_callback(&tfu_cb, copyID, NULL, NULL); +} + +/// Copy the global 'tagfunc' callback function to the buffer-local 'tagfunc' +/// callback for 'buf'. +void set_buflocal_tfu_callback(buf_T *buf) +{ + callback_free(&buf->b_tfu_cb); + if (tfu_cb.type != kCallbackNone) { + callback_copy(&buf->b_tfu_cb, &tfu_cb); + } +} + /// Jump to tag; handling of tag commands and tag stack /// /// *tag != NUL: ":tag {tag}", jump to new tag, add to tag stack @@ -141,16 +265,13 @@ static int tfu_in_use = false; // disallow recursive call of tagfunc /// type == DT_LAST: jump to last match of same tag /// type == DT_SELECT: ":tselect [tag]", select tag from a list of all matches /// type == DT_JUMP: ":tjump [tag]", jump to tag or select tag from a list -/// type == DT_CSCOPE: use cscope to find the tag /// type == DT_LTAG: use location list for displaying tag matches /// type == DT_FREE: free cached matches /// -/// for cscope, returns true if we jumped to tag or aborted, false otherwise -/// /// @param tag tag (pattern) to jump to /// @param forceit :ta with ! /// @param verbose print "tag not found" message -bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) +void do_tag(char *tag, int type, int count, int forceit, int verbose) { taggy_T *tagstack = curwin->w_tagstack; int tagstackidx = curwin->w_tagstackidx; @@ -167,13 +288,13 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) int error_cur_match = 0; int save_pos = false; fmark_T saved_fmark; - bool jumped_to_tag = false; int new_num_matches; char **new_matches; int use_tagstack; int skip_msg = false; - char_u *buf_ffname = (char_u *)curbuf->b_ffname; // name for priority computation + char *buf_ffname = curbuf->b_ffname; // name for priority computation int use_tfu = 1; + char *tofree = NULL; // remember the matches for the last used tag static int num_matches = 0; @@ -183,16 +304,15 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) if (tfu_in_use) { emsg(_(recurmsg)); - return false; + return; } #ifdef EXITFREE if (type == DT_FREE) { // remove the list of matches FreeWild(num_matches, matches); - cs_free_tags(); num_matches = 0; - return false; + return; } #endif @@ -203,7 +323,7 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) } prev_num_matches = num_matches; - free_string_option((char *)nofile_fname); + free_string_option(nofile_fname); nofile_fname = NULL; clearpos(&saved_fmark.mark); // shutup gcc 4.0 @@ -216,7 +336,7 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) new_tag = true; if (g_do_tagpreview != 0) { tagstack_clear_entry(&ptag_entry); - ptag_entry.tagname = (char *)vim_strsave(tag); + ptag_entry.tagname = xstrdup(tag); } } else { if (g_do_tagpreview != 0) { @@ -228,25 +348,21 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) // new pattern, add to the tag stack if (*tag != NUL && (type == DT_TAG || type == DT_SELECT || type == DT_JUMP - || type == DT_LTAG - || type == DT_CSCOPE - )) { + || type == DT_LTAG)) { if (g_do_tagpreview != 0) { if (ptag_entry.tagname != NULL - && STRCMP(ptag_entry.tagname, tag) == 0) { + && strcmp(ptag_entry.tagname, tag) == 0) { // Jumping to same tag: keep the current match, so that // the CursorHold autocommand example works. cur_match = ptag_entry.cur_match; cur_fnum = ptag_entry.cur_fnum; } else { tagstack_clear_entry(&ptag_entry); - ptag_entry.tagname = (char *)vim_strsave(tag); + ptag_entry.tagname = xstrdup(tag); } } else { - /* - * If the last used entry is not at the top, delete all tag - * stack entries above it. - */ + // If the last used entry is not at the top, delete all tag + // stack entries above it. while (tagstackidx < tagstacklen) { tagstack_clear_entry(&tagstack[--tagstacklen]); } @@ -262,7 +378,7 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) } // put the tag name in the tag stack - tagstack[tagstackidx].tagname = (char *)vim_strsave(tag); + tagstack[tagstackidx].tagname = xstrdup(tag); curwin->w_tagstacklen = tagstacklen; @@ -271,8 +387,7 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) new_tag = true; } else { - if ( - g_do_tagpreview != 0 ? ptag_entry.tagname == NULL : + if (g_do_tagpreview != 0 ? ptag_entry.tagname == NULL : tagstacklen == 0) { // empty stack emsg(_(e_tagstack)); @@ -300,10 +415,8 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) // tagstack before it's used. saved_fmark = tagstack[tagstackidx].fmark; if (saved_fmark.fnum != curbuf->b_fnum) { - /* - * Jump to other file. If this fails (e.g. because the - * file was changed) keep original position in tag stack. - */ + // Jump to other file. If this fails (e.g. because the + // file was changed) keep original position in tag stack. if (buflist_getfile(saved_fmark.fnum, saved_fmark.mark.lnum, GETF_SETMARK, forceit) == FAIL) { tagstackidx = oldtagstackidx; // back to old posn @@ -325,7 +438,6 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) // remove the old list of matches FreeWild(num_matches, matches); - cs_free_tags(); num_matches = 0; tag_freematch(); goto end_do_tag; @@ -340,11 +452,9 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) // ":tag" (no argument): go to newer pattern save_pos = true; // save the cursor position below if ((tagstackidx += count - 1) >= tagstacklen) { - /* - * Beyond the last one, just give an error message and - * go to the last one. Don't store the cursor - * position. - */ + // Beyond the last one, just give an error message and + // go to the last one. Don't store the cursor + // position. tagstackidx = tagstacklen - 1; emsg(_(topmsg)); save_pos = false; @@ -376,7 +486,6 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) cur_match = count - 1; break; case DT_SELECT: case DT_JUMP: - case DT_CSCOPE: case DT_LAST: cur_match = MAXCOL - 1; break; case DT_NEXT: @@ -401,9 +510,7 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) ptag_entry.cur_fnum = cur_fnum; } } else { - /* - * For ":tag [arg]" or ":tselect" remember position before the jump. - */ + // For ":tag [arg]" or ":tselect" remember position before the jump. saved_fmark = tagstack[tagstackidx].fmark; if (save_pos) { tagstack[tagstackidx].fmark.mark = curwin->w_cursor; @@ -428,32 +535,33 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) buf_T *buf = buflist_findnr(cur_fnum); if (buf != NULL) { - buf_ffname = (char_u *)buf->b_ffname; + buf_ffname = buf->b_ffname; } } - /* - * Repeat searching for tags, when a file has not been found. - */ + // Repeat searching for tags, when a file has not been found. for (;;) { int other_name; - char_u *name; + char *name; // When desired match not found yet, try to find it (and others). if (use_tagstack) { - name = (char_u *)tagstack[tagstackidx].tagname; + // make a copy, the tagstack may change in 'tagfunc' + name = xstrdup(tagstack[tagstackidx].tagname); + xfree(tofree); + tofree = name; } else if (g_do_tagpreview != 0) { - name = (char_u *)ptag_entry.tagname; + name = ptag_entry.tagname; } else { name = tag; } - other_name = (tagmatchname == NULL || STRCMP(tagmatchname, name) != 0); + other_name = (tagmatchname == NULL || strcmp(tagmatchname, name) != 0); if (new_tag || (cur_match >= num_matches && max_num_matches != MAXCOL) || other_name) { if (other_name) { xfree(tagmatchname); - tagmatchname = vim_strsave(name); + tagmatchname = xstrdup(name); } if (type == DT_SELECT || type == DT_JUMP @@ -474,9 +582,6 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) flags = TAG_NOIC; } - if (type == DT_CSCOPE) { - flags = TAG_CSCOPE; - } if (verbose) { flags |= TAG_VERBOSE; } @@ -484,13 +589,22 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) flags |= TAG_NO_TAGFUNC; } - if (find_tags((char *)name, &new_num_matches, &new_matches, flags, - max_num_matches, (char *)buf_ffname) == OK + if (find_tags(name, &new_num_matches, &new_matches, flags, + max_num_matches, buf_ffname) == OK && new_num_matches < max_num_matches) { max_num_matches = MAXCOL; // If less than max_num_matches // found: all matches found. } + // A tag function may do anything, which may cause various + // information to become invalid. At least check for the tagstack + // to still be the same. + if (tagstack != curwin->w_tagstack) { + emsg(_(e_window_unexpectedly_close_while_searching_for_tags)); + FreeWild(new_num_matches, new_matches); + break; + } + // If there already were some matches for the same name, move them // to the start. Avoids that the order changes when using // ":tnext" and jumping to another file. @@ -502,15 +616,15 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) // Find the position of each old match in the new list. Need // to use parse_match() to find the tag line. for (j = 0; j < num_matches; j++) { - parse_match((char_u *)matches[j], &tagp); + parse_match(matches[j], &tagp); for (i = idx; i < new_num_matches; i++) { - parse_match((char_u *)new_matches[i], &tagp2); - if (STRCMP(tagp.tagname, tagp2.tagname) == 0) { - char_u *p = (char_u *)new_matches[i]; + parse_match(new_matches[i], &tagp2); + if (strcmp(tagp.tagname, tagp2.tagname) == 0) { + char *p = new_matches[i]; for (k = i; k > idx; k--) { new_matches[k] = new_matches[k - 1]; } - new_matches[idx++] = (char *)p; + new_matches[idx++] = p; break; } } @@ -529,10 +643,7 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) } else { bool ask_for_selection = false; - if (type == DT_CSCOPE && num_matches > 1) { - cs_print_tags(); - ask_for_selection = true; - } else if (type == DT_TAG && *tag != NUL) { + if (type == DT_TAG && *tag != NUL) { // If a count is supplied to the ":tag <name>" command, then // jump to count'th matching tag. cur_match = count > 0 ? count - 1 : 0; @@ -556,8 +667,6 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) tagstack[tagstackidx].fmark = saved_fmark; tagstackidx = prevtagstackidx; } - cs_free_tags(); - jumped_to_tag = true; break; } cur_match = i - 1; @@ -585,12 +694,11 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) tagstack[tagstackidx].cur_fnum = cur_fnum; // store user-provided data originating from tagfunc - if (use_tfu && parse_match((char_u *)matches[cur_match], &tagp2) == OK + if (use_tfu && parse_match(matches[cur_match], &tagp2) == OK && tagp2.user_data) { XFREE_CLEAR(tagstack[tagstackidx].user_data); - tagstack[tagstackidx].user_data = (char *)vim_strnsave(tagp2.user_data, - (size_t)(tagp2.user_data_end - - tagp2.user_data)); + tagstack[tagstackidx].user_data = + xstrnsave(tagp2.user_data, (size_t)(tagp2.user_data_end - tagp2.user_data)); } tagstackidx++; @@ -599,21 +707,18 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) ptag_entry.cur_fnum = cur_fnum; } - /* - * Only when going to try the next match, report that the previous - * file didn't exist. Otherwise an emsg() is given below. - */ + // Only when going to try the next match, report that the previous + // file didn't exist. Otherwise an emsg() is given below. if (nofile_fname != NULL && error_cur_match != cur_match) { smsg(_("File \"%s\" does not exist"), nofile_fname); } ic = (matches[cur_match][0] & MT_IC_OFF); if (type != DT_TAG && type != DT_SELECT && type != DT_JUMP - && type != DT_CSCOPE && (num_matches > 1 || ic) && !skip_msg) { // Give an indication of the number of matching tags - snprintf((char *)IObuff, sizeof(IObuff), _("tag %d of %d%s"), + snprintf(IObuff, sizeof(IObuff), _("tag %d of %d%s"), cur_match + 1, num_matches, max_num_matches != MAXCOL ? _(" or more") : ""); @@ -625,11 +730,11 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) if (ic) { msg_attr((const char *)IObuff, HL_ATTR(HLF_W)); } else { - msg((char *)IObuff); + msg(IObuff); } msg_scroll = true; // Don't overwrite this message. } else { - give_warning((char *)IObuff, ic); + give_warning(IObuff, ic); } if (ic && !msg_scrolled && msg_silent == 0) { ui_flush(); @@ -638,13 +743,11 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) } // Let the SwapExists event know what tag we are jumping to. - vim_snprintf((char *)IObuff, IOSIZE, ":ta %s\r", name); - set_vim_var_string(VV_SWAPCOMMAND, (char *)IObuff, -1); + vim_snprintf(IObuff, IOSIZE, ":ta %s\r", name); + set_vim_var_string(VV_SWAPCOMMAND, IObuff, -1); - /* - * Jump to the desired match. - */ - i = jumpto_tag((char_u *)matches[cur_match], forceit, type != DT_CSCOPE); + // Jump to the desired match. + i = jumpto_tag(matches[cur_match], forceit, true); set_vim_var_string(VV_SWAPCOMMAND, NULL, -1); @@ -674,7 +777,6 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) if (use_tagstack && tagstackidx > curwin->w_tagstacklen) { tagstackidx = curwin->w_tagstackidx; } - jumped_to_tag = true; } } break; @@ -687,8 +789,7 @@ end_do_tag: } postponed_split = 0; // don't split next time g_do_tagpreview = 0; // don't do tag preview next time - - return jumped_to_tag; + xfree(tofree); } // List all the matching tags. @@ -697,15 +798,15 @@ static void print_tag_list(int new_tag, int use_tagstack, int num_matches, char taggy_T *tagstack = curwin->w_tagstack; int tagstackidx = curwin->w_tagstackidx; int i; - char_u *p; - char_u *command_end; + char *p; + char *command_end; tagptrs_T tagp; int taglen; int attr; // Assume that the first match indicates how long the tags can // be, and align the file names to that. - parse_match((char_u *)matches[0], &tagp); + parse_match(matches[0], &tagp); taglen = (int)(tagp.tagname_end - tagp.tagname + 2); if (taglen < 18) { taglen = 18; @@ -723,7 +824,7 @@ static void print_tag_list(int new_tag, int use_tagstack, int num_matches, char msg_puts_attr(_("file\n"), HL_ATTR(HLF_T)); for (i = 0; i < num_matches && !got_int; i++) { - parse_match((char_u *)matches[i], &tagp); + parse_match(matches[i], &tagp); if (!new_tag && ( (g_do_tagpreview != 0 && i == ptag_entry.cur_match) @@ -733,12 +834,12 @@ static void print_tag_list(int new_tag, int use_tagstack, int num_matches, char } else { *IObuff = ' '; } - vim_snprintf((char *)IObuff + 1, IOSIZE - 1, + vim_snprintf(IObuff + 1, IOSIZE - 1, "%2d %s ", i + 1, mt_names[matches[i][0] & MT_MASK]); - msg_puts((char *)IObuff); + msg_puts(IObuff); if (tagp.tagkind != NULL) { - msg_outtrans_len((char *)tagp.tagkind, + msg_outtrans_len(tagp.tagkind, (int)(tagp.tagkind_end - tagp.tagkind)); } msg_advance(13); @@ -752,7 +853,7 @@ static void print_tag_list(int new_tag, int use_tagstack, int num_matches, char // it and put "..." in the middle p = tag_full_fname(&tagp); if (p != NULL) { - msg_outtrans_attr((char *)p, HL_ATTR(HLF_D)); + msg_outtrans_attr(p, HL_ATTR(HLF_D)); XFREE_CLEAR(p); } if (msg_col > 0) { @@ -773,28 +874,28 @@ static void print_tag_list(int new_tag, int use_tagstack, int num_matches, char } // skip "file:" without a value (static tag) - if (STRNCMP(p, "file:", 5) == 0 && ascii_isspace(p[5])) { + if (strncmp(p, "file:", 5) == 0 && ascii_isspace(p[5])) { p += 5; continue; } // skip "kind:<kind>" and "<kind>" if (p == tagp.tagkind || (p + 5 == tagp.tagkind - && STRNCMP(p, "kind:", 5) == 0)) { + && strncmp(p, "kind:", 5) == 0)) { p = tagp.tagkind_end; continue; } // print all other extra fields attr = HL_ATTR(HLF_CM); while (*p && *p != '\r' && *p != '\n') { - if (msg_col + ptr2cells((char *)p) >= Columns) { + if (msg_col + ptr2cells(p) >= Columns) { msg_putchar('\n'); if (got_int) { break; } msg_advance(15); } - p = (char_u *)msg_outtrans_one((char *)p, attr); + p = msg_outtrans_one(p, attr); if (*p == TAB) { msg_puts_attr(" ", attr); break; @@ -833,7 +934,7 @@ static void print_tag_list(int new_tag, int use_tagstack, int num_matches, char } while (p != command_end) { - if (msg_col + (*p == TAB ? 1 : ptr2cells((char *)p)) > Columns) { + if (msg_col + (*p == TAB ? 1 : ptr2cells(p)) > Columns) { msg_putchar('\n'); } if (got_int) { @@ -852,7 +953,7 @@ static void print_tag_list(int new_tag, int use_tagstack, int num_matches, char msg_putchar(' '); p++; } else { - p = (char_u *)msg_outtrans_one((char *)p, 0); + p = msg_outtrans_one(p, 0); } // don't display the "$/;\"" and "$?;\"" @@ -878,14 +979,14 @@ static void print_tag_list(int new_tag, int use_tagstack, int num_matches, char /// Add the matching tags to the location list for the current /// window. -static int add_llist_tags(char_u *tag, int num_matches, char **matches) +static int add_llist_tags(char *tag, int num_matches, char **matches) { list_T *list; - char_u tag_name[128 + 1]; - char_u *fname; - char_u *cmd; + char tag_name[128 + 1]; + char *fname; + char *cmd; int i; - char_u *p; + char *p; tagptrs_T tagp; fname = xmalloc(MAXPATHL + 1); @@ -897,14 +998,14 @@ static int add_llist_tags(char_u *tag, int num_matches, char **matches) long lnum; dict_T *dict; - parse_match((char_u *)matches[i], &tagp); + parse_match(matches[i], &tagp); // Save the tag name len = (int)(tagp.tagname_end - tagp.tagname); if (len > 128) { len = 128; } - STRLCPY(tag_name, tagp.tagname, len + 1); + xstrlcpy(tag_name, tagp.tagname, (size_t)len + 1); tag_name[len] = NUL; // Save the tag file name @@ -912,7 +1013,7 @@ static int add_llist_tags(char_u *tag, int num_matches, char **matches) if (p == NULL) { continue; } - STRLCPY(fname, p, MAXPATHL); + xstrlcpy(fname, p, MAXPATHL); XFREE_CLEAR(p); // Get the line number or the search pattern used to locate @@ -920,9 +1021,9 @@ static int add_llist_tags(char_u *tag, int num_matches, char **matches) lnum = 0; if (isdigit(*tagp.command)) { // Line number is used to locate the tag - lnum = atol((char *)tagp.command); + lnum = atol(tagp.command); } else { - char_u *cmd_start, *cmd_end; + char *cmd_start, *cmd_end; // Search pattern is used to locate the tag @@ -970,7 +1071,7 @@ static int add_llist_tags(char_u *tag, int num_matches, char **matches) if (cmd_len > (CMDBUFFSIZE - 5)) { cmd_len = CMDBUFFSIZE - 5; } - snprintf((char *)cmd + len, (size_t)(CMDBUFFSIZE + 1 - len), + snprintf(cmd + len, (size_t)(CMDBUFFSIZE + 1 - len), "%.*s", cmd_len, cmd_start); len += cmd_len; @@ -988,16 +1089,16 @@ static int add_llist_tags(char_u *tag, int num_matches, char **matches) dict = tv_dict_alloc(); tv_list_append_dict(list, dict); - tv_dict_add_str(dict, S_LEN("text"), (const char *)tag_name); + tv_dict_add_str(dict, S_LEN("text"), tag_name); tv_dict_add_str(dict, S_LEN("filename"), (const char *)fname); tv_dict_add_nr(dict, S_LEN("lnum"), lnum); if (lnum == 0) { - tv_dict_add_str(dict, S_LEN("pattern"), (const char *)cmd); + tv_dict_add_str(dict, S_LEN("pattern"), cmd); } } - vim_snprintf((char *)IObuff, IOSIZE, "ltag %s", tag); - set_errorlist(curwin, list, ' ', (char *)IObuff, NULL); + vim_snprintf(IObuff, IOSIZE, "ltag %s", tag); + set_errorlist(curwin, list, ' ', IObuff, NULL); tv_list_free(list); XFREE_CLEAR(fname); @@ -1006,9 +1107,7 @@ static int add_llist_tags(char_u *tag, int num_matches, char **matches) return OK; } -/* - * Free cached tags. - */ +// Free cached tags. void tag_freematch(void) { XFREE_CLEAR(tagmatchname); @@ -1024,13 +1123,11 @@ static void taglen_advance(int l) } } -/* - * Print the tag stack - */ +// Print the tag stack void do_tags(exarg_T *eap) { int i; - char_u *name; + char *name; taggy_T *tagstack = curwin->w_tagstack; int tagstackidx = curwin->w_tagstackidx; int tagstacklen = curwin->w_tagstacklen; @@ -1045,35 +1142,32 @@ void do_tags(exarg_T *eap) } msg_putchar('\n'); - vim_snprintf((char *)IObuff, IOSIZE, "%c%2d %2d %-15s %5" PRIdLINENR " ", + vim_snprintf(IObuff, IOSIZE, "%c%2d %2d %-15s %5" PRIdLINENR " ", i == tagstackidx ? '>' : ' ', i + 1, tagstack[i].cur_match + 1, tagstack[i].tagname, tagstack[i].fmark.mark.lnum); - msg_outtrans((char *)IObuff); - msg_outtrans_attr((char *)name, tagstack[i].fmark.fnum == curbuf->b_fnum + msg_outtrans(IObuff); + msg_outtrans_attr(name, tagstack[i].fmark.fnum == curbuf->b_fnum ? HL_ATTR(HLF_D) : 0); xfree(name); } - ui_flush(); // show one line at a time } if (tagstackidx == tagstacklen) { // idx at top of stack msg_puts("\n>"); } } -/* - * Compare two strings, for length "len", ignoring case the ASCII way. - * return 0 for match, < 0 for smaller, > 0 for bigger - * Make sure case is folded to uppercase in comparison (like for 'sort -f') - */ -static int tag_strnicmp(char_u *s1, char_u *s2, size_t len) +// Compare two strings, for length "len", ignoring case the ASCII way. +// return 0 for match, < 0 for smaller, > 0 for bigger +// Make sure case is folded to uppercase in comparison (like for 'sort -f') +static int tag_strnicmp(char *s1, char *s2, size_t len) { int i; while (len > 0) { - i = TOUPPER_ASC(*s1) - TOUPPER_ASC(*s2); + i = TOUPPER_ASC((uint8_t)(*s1)) - TOUPPER_ASC((uint8_t)(*s2)); if (i != 0) { return i; // this character different } @@ -1087,9 +1181,7 @@ static int tag_strnicmp(char_u *s1, char_u *s2, size_t len) return 0; // strings match } -/* - * Extract info from the tag search pattern "pats->pat". - */ +// Extract info from the tag search pattern "pats->pat". static void prepare_pats(pat_T *pats, int has_re) { pats->head = pats->pat; @@ -1106,7 +1198,8 @@ static void prepare_pats(pat_T *pats, int has_re) pats->headlen = 0; } else { for (pats->headlen = 0; pats->head[pats->headlen] != NUL; pats->headlen++) { - if (vim_strchr((p_magic ? ".[~*\\$" : "\\$"), pats->head[pats->headlen]) != NULL) { + if (vim_strchr(magic_isset() ? ".[~*\\$" : "\\$", + pats->head[pats->headlen]) != NULL) { break; } } @@ -1117,7 +1210,7 @@ static void prepare_pats(pat_T *pats, int has_re) } if (has_re) { - pats->regmatch.regprog = vim_regcomp((char *)pats->pat, p_magic ? RE_MAGIC : 0); + pats->regmatch.regprog = vim_regcomp(pats->pat, magic_isset() ? RE_MAGIC : 0); } else { pats->regmatch.regprog = NULL; } @@ -1134,8 +1227,7 @@ static void prepare_pats(pat_T *pats, int has_re) /// @param match_count here the number of tags found will be placed /// @param flags flags from find_tags (TAG_*) /// @param buf_ffname name of buffer for priority -static int find_tagfunc_tags(char_u *pat, garray_T *ga, int *match_count, int flags, - char_u *buf_ffname) +static int find_tagfunc_tags(char *pat, garray_T *ga, int *match_count, int flags, char *buf_ffname) { pos_T save_pos; list_T *taglist; @@ -1143,7 +1235,7 @@ static int find_tagfunc_tags(char_u *pat, garray_T *ga, int *match_count, int fl int result = FAIL; typval_T args[4]; typval_T rettv; - char_u flagString[4]; + char flagString[4]; taggy_T *tag = NULL; if (curwin->w_tagstacklen > 0) { @@ -1154,14 +1246,14 @@ static int find_tagfunc_tags(char_u *pat, garray_T *ga, int *match_count, int fl } } - if (*curbuf->b_p_tfu == NUL) { + if (*curbuf->b_p_tfu == NUL || curbuf->b_tfu_cb.type == kCallbackNone) { return FAIL; } args[0].v_type = VAR_STRING; - args[0].vval.v_string = (char *)pat; + args[0].vval.v_string = pat; args[1].v_type = VAR_STRING; - args[1].vval.v_string = (char *)flagString; + args[1].vval.v_string = flagString; // create 'info' dict argument dict_T *const d = tv_dict_alloc_lock(VAR_FIXED); @@ -1178,14 +1270,14 @@ static int find_tagfunc_tags(char_u *pat, garray_T *ga, int *match_count, int fl args[3].v_type = VAR_UNKNOWN; - vim_snprintf((char *)flagString, sizeof(flagString), + vim_snprintf(flagString, sizeof(flagString), "%s%s%s", g_tag_at_cursor ? "c": "", flags & TAG_INS_COMP ? "i": "", flags & TAG_REGEXP ? "r": ""); save_pos = curwin->w_cursor; - result = call_vim_function(curbuf->b_p_tfu, 3, args, &rettv); + result = callback_call(&curbuf->b_tfu_cb, 3, args, &rettv); curwin->w_cursor = save_pos; // restore the cursor position d->dv_refcount--; @@ -1204,10 +1296,10 @@ static int find_tagfunc_tags(char_u *pat, garray_T *ga, int *match_count, int fl taglist = rettv.vval.v_list; TV_LIST_ITER_CONST(taglist, li, { - char_u *res_name; - char_u *res_fname; - char_u *res_cmd; - char_u *res_kind; + char *res_name; + char *res_fname; + char *res_cmd; + char *res_kind; int has_extra = 0; int name_only = flags & TAG_NAMES; @@ -1223,34 +1315,34 @@ static int find_tagfunc_tags(char_u *pat, garray_T *ga, int *match_count, int fl res_kind = NULL; TV_DICT_ITER(TV_LIST_ITEM_TV(li)->vval.v_dict, di, { - const char_u *dict_key = di->di_key; + const char *dict_key = (char *)di->di_key; typval_T *tv = &di->di_tv; if (tv->v_type != VAR_STRING || tv->vval.v_string == NULL) { continue; } - len += STRLEN(tv->vval.v_string) + 1; // Space for "\tVALUE" - if (!STRCMP(dict_key, "name")) { - res_name = (char_u *)tv->vval.v_string; + len += strlen(tv->vval.v_string) + 1; // Space for "\tVALUE" + if (!strcmp(dict_key, "name")) { + res_name = tv->vval.v_string; continue; } - if (!STRCMP(dict_key, "filename")) { - res_fname = (char_u *)tv->vval.v_string; + if (!strcmp(dict_key, "filename")) { + res_fname = tv->vval.v_string; continue; } - if (!STRCMP(dict_key, "cmd")) { - res_cmd = (char_u *)tv->vval.v_string; + if (!strcmp(dict_key, "cmd")) { + res_cmd = tv->vval.v_string; continue; } has_extra = 1; - if (!STRCMP(dict_key, "kind")) { - res_kind = (char_u *)tv->vval.v_string; + if (!strcmp(dict_key, "kind")) { + res_kind = tv->vval.v_string; continue; } // Other elements will be stored as "\tKEY:VALUE" // Allocate space for the key and the colon - len += STRLEN(dict_key) + 1; + len += strlen(dict_key) + 1; }); if (has_extra) { @@ -1262,62 +1354,62 @@ static int find_tagfunc_tags(char_u *pat, garray_T *ga, int *match_count, int fl break; } - char_u *const mfp = name_only ? vim_strsave(res_name) : xmalloc(len + 2); + char *const mfp = name_only ? xstrdup(res_name) : xmalloc(len + 2); if (!name_only) { - char_u *p = mfp; + char *p = mfp; *p++ = MT_GL_OTH + 1; // mtt *p++ = TAG_SEP; // no tag file name STRCPY(p, res_name); - p += STRLEN(p); + p += strlen(p); *p++ = TAB; STRCPY(p, res_fname); - p += STRLEN(p); + p += strlen(p); *p++ = TAB; STRCPY(p, res_cmd); - p += STRLEN(p); + p += strlen(p); if (has_extra) { STRCPY(p, ";\""); - p += STRLEN(p); + p += strlen(p); if (res_kind) { *p++ = TAB; STRCPY(p, res_kind); - p += STRLEN(p); + p += strlen(p); } TV_DICT_ITER(TV_LIST_ITEM_TV(li)->vval.v_dict, di, { - const char_u *dict_key = di->di_key; + const char *dict_key = (char *)di->di_key; typval_T *tv = &di->di_tv; if (tv->v_type != VAR_STRING || tv->vval.v_string == NULL) { continue; } - if (!STRCMP(dict_key, "name")) { + if (!strcmp(dict_key, "name")) { continue; } - if (!STRCMP(dict_key, "filename")) { + if (!strcmp(dict_key, "filename")) { continue; } - if (!STRCMP(dict_key, "cmd")) { + if (!strcmp(dict_key, "cmd")) { continue; } - if (!STRCMP(dict_key, "kind")) { + if (!strcmp(dict_key, "kind")) { continue; } *p++ = TAB; STRCPY(p, dict_key); - p += STRLEN(p); + p += strlen(p); STRCPY(p, ":"); - p += STRLEN(p); + p += strlen(p); STRCPY(p, tv->vval.v_string); - p += STRLEN(p); + p += strlen(p); }); } } @@ -1335,6 +1427,873 @@ static int find_tagfunc_tags(char_u *pat, garray_T *ga, int *match_count, int fl return result; } +/// Initialize the state used by find_tags() +static void findtags_state_init(findtags_state_T *st, char *pat, int flags, int mincount) +{ + st->tag_fname = xmalloc(MAXPATHL + 1); + st->fp = NULL; + st->orgpat = xmalloc(sizeof(pat_T)); + 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_lang_find = NULL; + st->is_txt = false; + st->did_open = false; + st->help_only = (flags & TAG_HELP); + st->get_searchpat = false; + st->help_lang[0] = NUL; + st->help_pri = 0; + st->mincount = mincount; + st->lbuf_size = LSIZE; + st->lbuf = xmalloc((size_t)st->lbuf_size); + st->match_count = 0; + st->stop_searching = false; + + for (int mtt = 0; mtt < MT_COUNT; mtt++) { + ga_init(&st->ga_match[mtt], sizeof(char *), 100); + hash_init(&st->ht_match[mtt]); + } +} + +/// Free the state used by find_tags() +static void findtags_state_free(findtags_state_T *st) +{ + xfree(st->tag_fname); + xfree(st->lbuf); + vim_regfree(st->orgpat->regmatch.regprog); + xfree(st->orgpat); +} + +/// Initialize the language and priority used for searching tags in a Vim help +/// file. +/// Returns true to process the help file for tags and false to skip the file. +static bool findtags_in_help_init(findtags_state_T *st) +{ + int i; + + // 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] == '-') { + xstrlcpy(st->help_lang, st->tag_fname + i - 2, 3); + } 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 { + st->help_pri = 1; + char *s; + 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++; + } + } + } + + return true; +} + +/// 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 *pat, char *buf_ffname) +{ + const bool use_tfu = ((st->flags & TAG_NO_TAGFUNC) == 0); + + if (!use_tfu || tfu_in_use || *curbuf->b_p_tfu == NUL) { + return NOTDONE; + } + + tfu_in_use = true; + int retval = find_tagfunc_tags(pat, st->ga_match, &st->match_count, + st->flags, buf_ffname); + tfu_in_use = false; + + return retval; +} + +/// 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; + } + } else if (st->state == TS_SKIP_BACK) { + // Skipping back (after a match during binary search). + 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_ignored = 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_ignored = 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; + } + } else { + // Not jumping around in the file: Read the next line. + + // skip empty and blank lines + do { + eof = vim_fgets(st->lbuf, st->lbuf_size, st->fp); + } while (!eof && vim_isblankline(st->lbuf)); + + if (eof) { + 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 bool findtags_hdr_parse(findtags_state_T *st) +{ + // 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 = (uint8_t)st->lbuf[18]; + } + if (strncmp(st->lbuf, "!_TAG_FILE_ENCODING\t", 20) == 0) { + // Prepare to convert every line from the specified encoding to + // 'encoding'. + char *p; + 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 bool findtags_start_state_handler(findtags_state_T *st, bool *sortic, + tagsearch_info_T *sinfo_p) +{ + const bool noic = (st->flags & TAG_NOIC); + + // 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. + if (st->linear) { + 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, 0, SEEK_END) != 0) { + // can't seek, don't use binary search + st->state = TS_LINEAR; + } else { + // Get the tag file size. + // Don't use lseek(), it doesn't work + // properly on MacOS Catalina. + const off_T filesize = vim_ftell(st->fp); + vim_ignored = vim_fseek(st->fp, 0, 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. +/// Also compares the tag name in "tagpp->tagname" with a search pattern in +/// "st->orgpat->head" as a quick check if the tag may match. +/// Returns: +/// - TAG_MATCH_SUCCESS if the tag may match +/// - TAG_MATCH_FAIL if the tag doesn't match +/// - TAG_MATCH_NEXT to look for the next matching tag (used in a binary search) +/// - 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_parse_line(findtags_state_T *st, tagptrs_T *tagpp, + findtags_match_args_T *margs, + tagsearch_info_T *sinfo_p) +{ + int status; + int i; + int cmplen; + int tagcmp; + + // 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) { + CLEAR_FIELD(*tagpp); + tagpp->tagname = st->lbuf; + tagpp->tagname_end = vim_strchr(st->lbuf, TAB); + if (tagpp->tagname_end == NULL) { + // Corrupted tag line. + return TAG_MATCH_FAIL; + } + + // Skip this line if the length of the tag is different and + // there is no regexp, or the tag is too short. + cmplen = (int)(tagpp->tagname_end - tagpp->tagname); + if (p_tl != 0 && cmplen > p_tl) { // adjust for 'taglength' + cmplen = (int)p_tl; + } + if ((st->flags & TAG_REGEXP) && st->orgpat->headlen < cmplen) { + cmplen = st->orgpat->headlen; + } else if (st->state == TS_LINEAR && st->orgpat->headlen != cmplen) { + return TAG_MATCH_NEXT; + } + + if (st->state == TS_BINARY) { + // Simplistic check for unsorted tags file. + i = (int)tagpp->tagname[0]; + if (margs->sortic) { + i = 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, (size_t)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 = (uint8_t)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 = (uint8_t)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) { + assert(cmplen >= 0); + if (mb_strnicmp(tagpp->tagname, st->orgpat->head, (size_t)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) { + assert(cmplen >= 0); + if (mb_strnicmp(tagpp->tagname, st->orgpat->head, (size_t)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 + assert(cmplen >= 0); + if (mb_strnicmp(tagpp->tagname, st->orgpat->head, (size_t)cmplen) != 0) { + return TAG_MATCH_NEXT; + } + } + + // 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, tagpp); + } + + if (status == FAIL) { + return TAG_MATCH_FAIL; + } + + return TAG_MATCH_SUCCESS; +} + +/// 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->pat". +/// Returns true if the tag matches, false if the tag doesn't match. +/// Uses the values in "margs" for doing the comparison. +static bool findtags_match_tag(findtags_state_T *st, tagptrs_T *tagpp, findtags_match_args_T *margs) +{ + bool match = false; + + // First try matching with the pattern literally (also when it is + // a regexp). + int cmplen = (int)(tagpp->tagname_end - tagpp->tagname); + if (p_tl != 0 && cmplen > p_tl) { // adjust for 'taglength' + cmplen = (int)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) { + assert(cmplen >= 0); + match = mb_strnicmp(tagpp->tagname, st->orgpat->pat, (size_t)cmplen) == 0; + if (match) { + margs->match_no_ic = strncmp(tagpp->tagname, st->orgpat->pat, (size_t)cmplen) == 0; + } + } else { + match = strncmp(tagpp->tagname, st->orgpat->pat, (size_t)cmplen) == 0; + } + } + + // Has a regexp: Also find tags matching regexp. + margs->match_re = false; + if (!match && st->orgpat->regmatch.regprog != NULL) { + char 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; +} + +/// 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 *conv_line = string_convert(&st->vimconv, st->lbuf, NULL); + if (conv_line == NULL) { + return; + } + + // Copy or swap lbuf and conv_line. + int len = (int)strlen(conv_line) + 1; + if (len > st->lbuf_size) { + xfree(st->lbuf); + st->lbuf = conv_line; + st->lbuf_size = len; + } else { + STRCPY(st->lbuf, conv_line); + xfree(conv_line); + } +} + +/// Add a matching tag found in a tags file to st->ht_match and st->ga_match. +static void findtags_add_match(findtags_state_T *st, tagptrs_T *tagpp, findtags_match_args_T *margs, + char *buf_ffname, hash_T *hash) +{ + const bool name_only = (st->flags & TAG_NAMES); + int mtt; + size_t len = 0; + bool is_current; // file name matches + bool is_static; // current tag line is static + char *mfp; + char *p; + char *s; + + // Decide in which array to store this match. + is_current = test_for_current(tagpp->fname, tagpp->fname_end, + st->tag_fname, buf_ffname); + is_static = test_for_static(tagpp); + + // Decide in which of the sixteen tables to store this match. + if (is_static) { + if (is_current) { + mtt = MT_ST_CUR; + } else { + mtt = MT_ST_OTH; + } + } else { + if (is_current) { + mtt = MT_GL_CUR; + } else { + mtt = MT_GL_OTH; + } + } + if (st->orgpat->regmatch.rm_ic && !margs->match_no_ic) { + mtt += MT_IC_OFF; + } + if (margs->match_re) { + mtt += MT_RE_OFF; + } + + // Add the found match in ht_match[mtt] and ga_match[mtt]. + // Store the info we need later, which depends on the kind of + // tags we are dealing with. + if (st->help_only) { +#define ML_EXTRA 3 + // Append the help-heuristic number after the tagname, for + // sorting it later. The heuristic is ignored for + // detecting duplicates. + // The format is {tagname}@{lang}NUL{heuristic}NUL + *tagpp->tagname_end = NUL; + len = (size_t)(tagpp->tagname_end - tagpp->tagname); + mfp = xmalloc(sizeof(char) + len + 10 + ML_EXTRA + 1); + + p = mfp; + STRCPY(p, tagpp->tagname); + p[len] = '@'; + STRCPY(p + len + 1, st->help_lang); + snprintf(p + len + 1 + ML_EXTRA, strlen(p) + len + 1 + ML_EXTRA, "%06d", + help_heuristic(tagpp->tagname, + margs->match_re ? margs->matchoff : 0, + !margs->match_no_ic) + st->help_pri); + + *tagpp->tagname_end = TAB; + } else if (name_only) { + if (st->get_searchpat) { + char *temp_end = tagpp->command; + + if (*temp_end == '/') { + while (*temp_end && *temp_end != '\r' + && *temp_end != '\n' + && *temp_end != '$') { + temp_end++; + } + } + + if (tagpp->command + 2 < temp_end) { + len = (size_t)(temp_end - tagpp->command - 2); + mfp = xmalloc(len + 2); + xstrlcpy(mfp, tagpp->command + 2, len + 1); + } else { + mfp = NULL; + } + st->get_searchpat = false; + } else { + len = (size_t)(tagpp->tagname_end - tagpp->tagname); + mfp = xmalloc(sizeof(char) + len + 1); + xstrlcpy(mfp, tagpp->tagname, len + 1); + + // if wanted, re-read line to get long form too + if (State & MODE_INSERT) { + st->get_searchpat = p_sft; + } + } + } else { + size_t tag_fname_len = strlen(st->tag_fname); + // Save the tag in a buffer. + // Use 0x02 to separate fields (Can't use NUL, because the + // hash key is terminated by NUL). + // Emacs tag: <mtt><tag_fname><0x02><ebuf><0x02><lbuf><NUL> + // other tag: <mtt><tag_fname><0x02><0x02><lbuf><NUL> + // without Emacs tags: <mtt><tag_fname><0x02><lbuf><NUL> + // Here <mtt> is the "mtt" value plus 1 to avoid NUL. + len = tag_fname_len + strlen(st->lbuf) + 3; + mfp = xmalloc(sizeof(char) + len + 1); + p = mfp; + p[0] = (char)(mtt + 1); + STRCPY(p + 1, st->tag_fname); +#ifdef BACKSLASH_IN_FILENAME + // Ignore differences in slashes, avoid adding + // both path/file and path\file. + slash_adjust(p + 1); +#endif + p[tag_fname_len + 1] = TAG_SEP; + s = p + 1 + tag_fname_len + 1; + STRCPY(s, st->lbuf); + } + + if (mfp != NULL) { + hashitem_T *hi; + + // Don't add identical matches. + // "mfp" is used as a hash key, there is a NUL byte to end + // the part that matters for comparing, more bytes may + // follow after it. E.g. help tags store the priority + // after the NUL. + *hash = hash_hash((char_u *)mfp); + hi = hash_lookup(&st->ht_match[mtt], (const char *)mfp, strlen(mfp), *hash); + if (HASHITEM_EMPTY(hi)) { + hash_add_item(&st->ht_match[mtt], hi, (char_u *)mfp, *hash); + GA_APPEND(char *, &st->ga_match[mtt], mfp); + st->match_count++; + } else { + // duplicate tag, drop it + xfree(mfp); + } + } +} + +/// Read and get all the tags from file st->tag_fname. +/// Sets "st->stop_searching" to true to stop searching for additional tags. +static void findtags_get_all_tags(findtags_state_T *st, findtags_match_args_T *margs, + char *buf_ffname) +{ + tagptrs_T tagp; + tagsearch_info_T search_info; + int retval; + 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()) { + st->stop_searching = true; + break; + } + // When mincount is TAG_MANY, stop when enough matches have been + // found (for completion). + if (st->mincount == TAG_MANY && st->match_count >= TAG_MANY) { + st->stop_searching = true; + break; + } + if (st->get_searchpat) { + goto line_read_in; + } + + retval = (int)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); + } + + // 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)) { + 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. + // In that case we need to increase lbuf_size. + if (st->lbuf[st->lbuf_size - 2] != NUL) { + st->lbuf_size *= 2; + xfree(st->lbuf); + st->lbuf = xmalloc((size_t)st->lbuf_size); + + if (st->state == TS_STEP_FORWARD || st->state == TS_LINEAR) { + // Seek to the same position to read the same line again + vim_ignored = 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; + } + + retval = (int)findtags_parse_line(st, &tagp, margs, &search_info); + if (retval == TAG_MATCH_NEXT) { + continue; + } + if (retval == TAG_MATCH_STOP) { + break; + } + if (retval == TAG_MATCH_FAIL) { + semsg(_("E431: Format error in tags file \"%s\""), st->tag_fname); + semsg(_("Before byte %" PRId64), (int64_t)vim_ftell(st->fp)); + st->stop_searching = true; + return; + } + + // If a match is found, add it to ht_match[] and ga_match[]. + if (findtags_match_tag(st, &tagp, margs)) { + findtags_add_match(st, &tagp, margs, buf_ffname, &hash); + } + } // forever +} + +/// 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". If an error is encountered, then +/// "st->stop_searching" is set to true. +static void findtags_in_file(findtags_state_T *st, int flags, char *buf_ffname) +{ + findtags_match_args_T margs; + + 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). + if (curbuf->b_help) { + if (!findtags_in_help_init(st)) { + return; + } + } + + st->fp = os_fopen(st->tag_fname, "r"); + if (st->fp == NULL) { + return; + } + + 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 + + // Read and parse the lines in the file one by one + findtags_get_all_tags(st, &margs, buf_ffname); + + if (st->fp != NULL) { + fclose(st->fp); + st->fp = NULL; + } + if (st->vimconv.vc_type != CONV_NONE) { + convert_setup(&st->vimconv, NULL, NULL); + } + + if (margs.sort_error) { + semsg(_("E432: Tags file not sorted: %s"), st->tag_fname); + } + + // Stop searching if sufficient tags have been found. + if (st->match_count >= st->mincount) { + st->stop_searching = true; + } +} + +/// 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 ***matchesp) +{ + const bool name_only = (st->flags & TAG_NAMES); + char **matches; + int mtt; + int i; + char *mfp; + char *p; + + if (st->match_count > 0) { + matches = xmalloc((size_t)st->match_count * sizeof(char *)); + } else { + matches = NULL; + } + st->match_count = 0; + for (mtt = 0; mtt < MT_COUNT; mtt++) { + for (i = 0; i < st->ga_match[mtt].ga_len; i++) { + mfp = ((char **)(st->ga_match[mtt].ga_data))[i]; + if (matches == NULL) { + xfree(mfp); + } else { + if (!name_only) { + // Change mtt back to zero-based. + *mfp = (char)(*mfp - 1); + + // change the TAG_SEP back to NUL + for (p = mfp + 1; *p != NUL; p++) { + if (*p == TAG_SEP) { + *p = NUL; + } + } + } + matches[st->match_count++] = mfp; + } + } + + ga_clear(&st->ga_match[mtt]); + hash_clear(&st->ht_match[mtt]); + } + + *matchesp = matches; + return st->match_count; +} + /// find_tags() - search for tags in tags files /// /// Return FAIL if search completely failed (*num_matches will be 0, *matchesp @@ -1358,94 +2317,33 @@ static int find_tagfunc_tags(char_u *pat, garray_T *ga, int *match_count, int fl /// TAG_REGEXP use "pat" as a regexp /// TAG_NOIC don't always ignore case /// TAG_KEEP_LANG keep language -/// TAG_CSCOPE use cscope results for tags /// TAG_NO_TAGFUNC do not call the 'tagfunc' function /// /// @param pat pattern to search for /// @param num_matches return: number of matches found /// @param matchesp return: array of matches found -/// @param mincount MAXCOL: find all matches other: minimal number of matches */ +/// @param mincount MAXCOL: find all matches +/// other: minimal number of matches /// @param buf_ffname name of buffer for priority int find_tags(char *pat, int *num_matches, char ***matchesp, int flags, int mincount, char *buf_ffname) { - FILE *fp; - char_u *lbuf; // line buffer - int lbuf_size = LSIZE; // length of lbuf - char_u *tag_fname; // name of tag file + findtags_state_T st; tagname_T tn; // info for get_tagfname() int first_file; // trying first tag file - tagptrs_T tagp; - bool did_open = false; // did open a tag file - bool stop_searching = false; // stop when match found or error int retval = FAIL; // return value - int is_static; // current tag line is static - int is_current; // file name matches - bool eof = false; // found end-of-file - char_u *p; - char_u *s; - int i; - int tag_file_sorted = NUL; // !_TAG_FILE_SORTED value - struct tag_search_info { // Binary search file offsets - off_T low_offset; // offset for first char of first line that - // could match - off_T high_offset; // offset of char after last line that could - // match - off_T curr_offset; // Current file offset in search range - off_T curr_offset_used; // curr_offset used when skipping back - off_T match_offset; // Where the binary search found a tag - int low_char; // first char at low_offset - int high_char; // first char at high_offset - } search_info; - int tagcmp; - off_T offset; int round; - 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 - int cmplen; - int match; // matches - int match_no_ic = 0; // matches with rm_ic == false - int match_re; // match with regexp - int matchoff = 0; int save_emsg_off; - char *mfp; - garray_T ga_match[MT_COUNT]; // stores matches in sequence - hashtab_T ht_match[MT_COUNT]; // stores matches by key - hash_T hash = 0; - int match_count = 0; // number of matches found - char **matches; - int mtt; int help_save; - int help_pri = 0; - char_u *help_lang_find = NULL; // lang to be found - char_u help_lang[3]; // lang of current tags file - char_u *saved_pat = NULL; // copy of pat[] - bool is_txt = false; - - pat_T orgpat; // holds unconverted pattern info - vimconv_T vimconv; + int i; + char *saved_pat = NULL; // copy of pat[] - int findall = (mincount == MAXCOL || mincount == TAG_MANY); - // find all matching tags - bool sort_error = false; // tags file not sorted - int linear; // do a linear search - bool sortic = false; // tag file sorted in nocase - bool line_error = false; // syntax error + int findall = (mincount == MAXCOL || mincount == TAG_MANY); // find all matching tags int has_re = (flags & TAG_REGEXP); // regexp used - int help_only = (flags & TAG_HELP); - int name_only = (flags & TAG_NAMES); int noic = (flags & TAG_NOIC); - int get_it_again = false; - int use_cscope = (flags & TAG_CSCOPE); int verbose = (flags & TAG_VERBOSE); - int use_tfu = ((flags & TAG_NO_TAGFUNC) == 0); int save_p_ic = p_ic; // Change the value of 'ignorecase' according to 'tagcase' for the @@ -1460,840 +2358,119 @@ int find_tags(char *pat, int *num_matches, char ***matchesp, int flags, int minc p_ic = false; break; case TC_FOLLOWSCS: - p_ic = ignorecase((char_u *)pat); + p_ic = ignorecase(pat); break; case TC_SMART: - p_ic = ignorecase_opt((char_u *)pat, true, true); + p_ic = ignorecase_opt(pat, true, true); break; default: abort(); } help_save = curbuf->b_help; - orgpat.pat = (char_u *)pat; - orgpat.regmatch.regprog = NULL; - vimconv.vc_type = CONV_NONE; - - /* - * Allocate memory for the buffers that are used - */ - lbuf = xmalloc((size_t)lbuf_size); - tag_fname = xmalloc(MAXPATHL + 1); - for (mtt = 0; mtt < MT_COUNT; mtt++) { - ga_init(&ga_match[mtt], sizeof(char *), 100); - hash_init(&ht_match[mtt]); - } - STRCPY(tag_fname, "from cscope"); // for error messages + findtags_state_init(&st, pat, flags, mincount); - /* - * Initialize a few variables - */ - if (help_only) { // want tags from help file + // Initialize a few variables + if (st.help_only) { // want tags from help file curbuf->b_help = true; // will be restored later - } else if (use_cscope) { - // Make sure we don't mix help and cscope, confuses Coverity. - help_only = false; - curbuf->b_help = false; } - orgpat.len = (int)STRLEN(pat); if (curbuf->b_help) { // When "@ab" is specified use only the "ab" language, otherwise // search all languages. - if (orgpat.len > 3 && pat[orgpat.len - 3] == '@' - && ASCII_ISALPHA(pat[orgpat.len - 2]) - && ASCII_ISALPHA(pat[orgpat.len - 1])) { - saved_pat = vim_strnsave((char_u *)pat, (size_t)orgpat.len - 3); - help_lang_find = (char_u *)&pat[orgpat.len - 2]; - orgpat.pat = saved_pat; - orgpat.len -= 3; + if (st.orgpat->len > 3 && pat[st.orgpat->len - 3] == '@' + && ASCII_ISALPHA(pat[st.orgpat->len - 2]) + && ASCII_ISALPHA(pat[st.orgpat->len - 1])) { + saved_pat = xstrnsave(pat, (size_t)st.orgpat->len - 3); + st.help_lang_find = &pat[st.orgpat->len - 2]; + st.orgpat->pat = saved_pat; + st.orgpat->len -= 3; } } - if (p_tl != 0 && orgpat.len > p_tl) { // adjust for 'taglength' - orgpat.len = (int)p_tl; + if (p_tl != 0 && st.orgpat->len > p_tl) { // adjust for 'taglength' + st.orgpat->len = (int)p_tl; } save_emsg_off = emsg_off; emsg_off = true; // don't want error for invalid RE here - prepare_pats(&orgpat, has_re); + prepare_pats(st.orgpat, has_re); emsg_off = save_emsg_off; - if (has_re && orgpat.regmatch.regprog == NULL) { + if (has_re && st.orgpat->regmatch.regprog == NULL) { goto findtag_end; } - // This is only to avoid a compiler warning for using search_info - // uninitialised. - CLEAR_FIELD(search_info); + retval = findtags_apply_tfu(&st, pat, buf_ffname); + if (retval != NOTDONE) { + goto findtag_end; + } + + // re-initialize the default return value + retval = FAIL; - if (*curbuf->b_p_tfu != NUL && use_tfu && !tfu_in_use) { - tfu_in_use = true; - retval = find_tagfunc_tags((char_u *)pat, &ga_match[0], &match_count, flags, - (char_u *)buf_ffname); - tfu_in_use = false; - if (retval != NOTDONE) { - goto findtag_end; - } - } - - /* - * When finding a specified number of matches, first try with matching - * case, so binary search can be used, and try ignore-case matches in a - * second loop. - * When finding all matches, 'tagbsearch' is off, or there is no fixed - * string to look for, ignore case right away to avoid going though the - * tags files twice. - * When the tag file is case-fold sorted, it is either one or the other. - * Only ignore case when TAG_NOIC not used or 'ignorecase' set. - */ // Set a flag if the file extension is .txt if ((flags & TAG_KEEP_LANG) - && help_lang_find == NULL + && st.help_lang_find == NULL && curbuf->b_fname != NULL - && (i = (int)STRLEN(curbuf->b_fname)) > 4 + && (i = (int)strlen(curbuf->b_fname)) > 4 && STRICMP(curbuf->b_fname + i - 4, ".txt") == 0) { - is_txt = true; - } - orgpat.regmatch.rm_ic = ((p_ic || !noic) - && (findall || orgpat.headlen == 0 || !p_tbs)); + st.is_txt = true; + } + + // When finding a specified number of matches, first try with matching + // case, so binary search can be used, and try ignore-case matches in a + // second loop. + // When finding all matches, 'tagbsearch' is off, or there is no fixed + // string to look for, ignore case right away to avoid going though the + // tags files twice. + // When the tag file is case-fold sorted, it is either one or the other. + // Only ignore case when TAG_NOIC not used or 'ignorecase' set. + st.orgpat->regmatch.rm_ic = ((p_ic || !noic) + && (findall || st.orgpat->headlen == 0 || !p_tbs)); for (round = 1; round <= 2; round++) { - linear = (orgpat.headlen == 0 || !p_tbs || round == 2); + st.linear = (st.orgpat->headlen == 0 || !p_tbs || round == 2); // Try tag file names from tags option one by one. for (first_file = true; - use_cscope || get_tagfname(&tn, first_file, (char *)tag_fname) == OK; + get_tagfname(&tn, first_file, st.tag_fname) == OK; first_file = false) { - // A file that doesn't exist is silently ignored. Only when not a - // single file is found, an error message is given (further on). - if (use_cscope) { - fp = NULL; // avoid GCC warning - } else { - if (curbuf->b_help) { - // Keep en if the file extension is .txt - if (is_txt) { - STRCPY(help_lang, "en"); - } else { - // Prefer help tags according to 'helplang'. Put the - // two-letter language name in help_lang[]. - i = (int)STRLEN(tag_fname); - if (i > 3 && tag_fname[i - 3] == '-') { - STRCPY(help_lang, tag_fname + i - 2); - } else { - STRCPY(help_lang, "en"); - } - } - - // When searching for a specific language skip tags files - // for other languages. - if (help_lang_find != NULL - && STRICMP(help_lang, help_lang_find) != 0) { - continue; - } - - // For CTRL-] in a help file prefer a match with the same - // language. - if ((flags & TAG_KEEP_LANG) - && help_lang_find == NULL - && curbuf->b_fname != NULL - && (i = (int)STRLEN(curbuf->b_fname)) > 4 - && curbuf->b_fname[i - 1] == 'x' - && curbuf->b_fname[i - 4] == '.' - && STRNICMP(curbuf->b_fname + i - 3, help_lang, 2) == 0) { - help_pri = 0; - } else { - help_pri = 1; - for (s = p_hlg; *s != NUL; s++) { - if (STRNICMP(s, help_lang, 2) == 0) { - break; - } - help_pri++; - if ((s = (char_u *)vim_strchr((char *)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++; - } - } - } - } - - if ((fp = os_fopen((char *)tag_fname, "r")) == NULL) { - continue; - } - - if (p_verbose >= 5) { - verbose_enter(); - smsg(_("Searching tags file %s"), tag_fname); - verbose_leave(); - } - } - did_open = true; // remember that we found at least one file - - state = TS_START; // we're at the start of the file - - /* - * 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()) { - stop_searching = true; - break; - } - // When mincount is TAG_MANY, stop when enough matches have been - // found (for completion). - if (mincount == TAG_MANY && match_count >= TAG_MANY) { - stop_searching = true; - retval = OK; - break; - } - if (get_it_again) { - goto line_read_in; - } - /* - * 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; - } - } else if (state == TS_SKIP_BACK) { - // Skipping back (after a match during binary search). - search_info.curr_offset -= lbuf_size * 2; - if (search_info.curr_offset < 0) { - search_info.curr_offset = 0; - rewind(fp); - state = TS_STEP_FORWARD; - } - } - - /* - * When jumping around in the file, first read a line to find the - * start of the next line. - */ - if (state == TS_BINARY || state == TS_SKIP_BACK) { - // Adjust the search file offset to the correct position - search_info.curr_offset_used = search_info.curr_offset; - vim_fseek(fp, search_info.curr_offset, SEEK_SET); - eof = vim_fgets(lbuf, lbuf_size, fp); - if (!eof && search_info.curr_offset != 0) { - // The explicit cast is to work around a bug in gcc 3.4.2 - // (repeated below). - search_info.curr_offset = vim_ftell(fp); - if (search_info.curr_offset == search_info.high_offset) { - // oops, gone a bit too far; try from low offset - vim_fseek(fp, search_info.low_offset, SEEK_SET); - search_info.curr_offset = search_info.low_offset; - } - eof = vim_fgets(lbuf, lbuf_size, fp); - } - // skip empty and blank lines - while (!eof && vim_isblankline((char *)lbuf)) { - search_info.curr_offset = vim_ftell(fp); - eof = vim_fgets(lbuf, lbuf_size, fp); - } - if (eof) { - // Hit end of file. Skip backwards. - state = TS_SKIP_BACK; - search_info.match_offset = vim_ftell(fp); - search_info.curr_offset = search_info.curr_offset_used; - continue; - } - } - /* - * Not jumping around in the file: Read the next line. - */ - else { - // skip empty and blank lines - do { - eof = use_cscope - ? cs_fgets(lbuf, lbuf_size) - : vim_fgets(lbuf, lbuf_size, fp); - } while (!eof && vim_isblankline((char *)lbuf)); - - if (eof) { - break; // end of file - } - } -line_read_in: - - if (vimconv.vc_type != CONV_NONE) { - char_u *conv_line; - int len; - - // Convert every line. Converting the pattern from 'enc' to - // the tags file encoding doesn't work, because characters are - // not recognized. - conv_line = (char_u *)string_convert(&vimconv, (char *)lbuf, NULL); - if (conv_line != NULL) { - // Copy or swap lbuf and conv_line. - len = (int)STRLEN(conv_line) + 1; - if (len > lbuf_size) { - xfree(lbuf); - lbuf = conv_line; - lbuf_size = len; - } else { - STRCPY(lbuf, conv_line); - xfree(conv_line); - } - } - } - - /* - * When still at the start of the file, check for Emacs tags file - * format, and for "not sorted" flag. - */ - if (state == TS_START) { - // The header ends when the line sorts below "!_TAG_". When - // case is folded lower case letters sort before "_". - if (STRNCMP(lbuf, "!_TAG_", 6) <= 0 - || (lbuf[0] == '!' && ASCII_ISLOWER(lbuf[1]))) { - if (STRNCMP(lbuf, "!_TAG_", 6) != 0) { - // Non-header item before the header, e.g. "!" itself. - goto parse_line; - } - - /* - * Read header line. - */ - if (STRNCMP(lbuf, "!_TAG_FILE_SORTED\t", 18) == 0) { - tag_file_sorted = lbuf[18]; - } - if (STRNCMP(lbuf, "!_TAG_FILE_ENCODING\t", 20) == 0) { - // Prepare to convert every line from the specified - // encoding to 'encoding'. - for (p = lbuf + 20; *p > ' ' && *p < 127; p++) {} - *p = NUL; - convert_setup(&vimconv, (char *)lbuf + 20, p_enc); - } - - // Read the next line. Unrecognized flags are ignored. - continue; - } - - // 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. - */ - if (linear || use_cscope) { - state = TS_LINEAR; - } else if (tag_file_sorted == NUL) { - state = TS_BINARY; - } else if (tag_file_sorted == '1') { - state = TS_BINARY; - } else if (tag_file_sorted == '2') { - state = TS_BINARY; - sortic = true; - orgpat.regmatch.rm_ic = (p_ic || !noic); - } else { - state = TS_LINEAR; - } - - if (state == TS_BINARY && orgpat.regmatch.rm_ic && !sortic) { - // Binary search won't work for ignoring case, use linear - // search. - linear = true; - state = TS_LINEAR; - } - - // When starting a binary search, get the size of the file and - // compute the first offset. - if (state == TS_BINARY) { - if (vim_fseek(fp, 0, SEEK_END) != 0) { - // can't seek, don't use binary search - state = TS_LINEAR; - } else { - // Get the tag file size. - // Don't use lseek(), it doesn't work - // properly on MacOS Catalina. - const off_T filesize = vim_ftell(fp); - vim_fseek(fp, 0, 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. - // In that case we need to increase lbuf_size. - if (lbuf[lbuf_size - 2] != NUL && !use_cscope) { - lbuf_size *= 2; - xfree(lbuf); - lbuf = xmalloc((size_t)lbuf_size); - // 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 (orgpat.headlen) { - CLEAR_FIELD(tagp); - tagp.tagname = lbuf; - tagp.tagname_end = (char_u *)vim_strchr((char *)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 = (int)p_tl; - } - if (has_re && orgpat.headlen < cmplen) { - cmplen = orgpat.headlen; - } else if (state == TS_LINEAR && orgpat.headlen != cmplen) { - continue; - } - - if (state == TS_BINARY) { - /* - * Simplistic check for unsorted tags file. - */ - i = (int)tagp.tagname[0]; - if (sortic) { - i = TOUPPER_ASC(tagp.tagname[0]); - } - if (i < search_info.low_char || i > search_info.high_char) { - sort_error = true; - } - - /* - * Compare the current tag with the searched tag. - */ - if (sortic) { - tagcmp = tag_strnicmp(tagp.tagname, orgpat.head, - (size_t)cmplen); - } else { - tagcmp = STRNCMP(tagp.tagname, orgpat.head, cmplen); - } - - /* - * A match with a shorter tag means to search forward. - * A match with a longer tag means to search backward. - */ - if (tagcmp == 0) { - if (cmplen < orgpat.headlen) { - tagcmp = -1; - } else if (cmplen > orgpat.headlen) { - tagcmp = 1; - } - } - - if (tagcmp == 0) { - // We've located the tag, now skip back and search - // forward until the first matching tag is found. - state = TS_SKIP_BACK; - search_info.match_offset = search_info.curr_offset; - continue; - } - if (tagcmp < 0) { - search_info.curr_offset = vim_ftell(fp); - if (search_info.curr_offset < search_info.high_offset) { - search_info.low_offset = search_info.curr_offset; - if (sortic) { - search_info.low_char = - TOUPPER_ASC(tagp.tagname[0]); - } else { - search_info.low_char = tagp.tagname[0]; - } - continue; - } - } - if (tagcmp > 0 - && search_info.curr_offset != search_info.high_offset) { - search_info.high_offset = search_info.curr_offset; - if (sortic) { - search_info.high_char = - TOUPPER_ASC(tagp.tagname[0]); - } else { - search_info.high_char = tagp.tagname[0]; - } - continue; - } - - // No match yet and are at the end of the binary search. - break; - } else if (state == TS_SKIP_BACK) { - assert(cmplen >= 0); - if (mb_strnicmp(tagp.tagname, orgpat.head, (size_t)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) { - assert(cmplen >= 0); - if (mb_strnicmp(tagp.tagname, orgpat.head, (size_t)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 - assert(cmplen >= 0); - } - if (mb_strnicmp(tagp.tagname, orgpat.head, (size_t)cmplen) != 0) { - continue; - } - - // Can be a matching tag, isolate the file name and command. - tagp.fname = tagp.tagname_end + 1; - tagp.fname_end = (char_u *)vim_strchr((char *)tagp.fname, TAB); - tagp.command = tagp.fname_end + 1; - if (tagp.fname_end == NULL) { - i = FAIL; - } else { - i = OK; - } - } else { - i = parse_tag_line(lbuf, - &tagp); - } - if (i == FAIL) { - line_error = true; - break; - } - - /* - * 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 = (int)p_tl; - } - // if tag length does not match, don't try comparing - if (orgpat.len != cmplen) { - match = false; - } else { - if (orgpat.regmatch.rm_ic) { - assert(cmplen >= 0); - match = mb_strnicmp(tagp.tagname, orgpat.pat, (size_t)cmplen) == 0; - if (match) { - match_no_ic = (STRNCMP(tagp.tagname, orgpat.pat, - cmplen) == 0); - } - } else { - match = (STRNCMP(tagp.tagname, orgpat.pat, cmplen) == 0); - } - } - - /* - * Has a regexp: Also find tags matching regexp. - */ - match_re = false; - if (!match && orgpat.regmatch.regprog != NULL) { - int cc; - - cc = *tagp.tagname_end; - *tagp.tagname_end = NUL; - match = vim_regexec(&orgpat.regmatch, (char *)tagp.tagname, (colnr_T)0); - if (match) { - matchoff = (int)(orgpat.regmatch.startp[0] - tagp.tagname); - if (orgpat.regmatch.rm_ic) { - orgpat.regmatch.rm_ic = false; - match_no_ic = vim_regexec(&orgpat.regmatch, (char *)tagp.tagname, (colnr_T)0); - orgpat.regmatch.rm_ic = true; - } - } - *tagp.tagname_end = (char_u)cc; - match_re = true; - } - - // If a match is found, add it to ht_match[] and ga_match[]. - if (match) { - size_t len = 0; - - if (use_cscope) { - // Don't change the ordering, always use the same table. - mtt = MT_GL_OTH; - } else { - // Decide in which array to store this match. - is_current = test_for_current((char *)tagp.fname, (char *)tagp.fname_end, - (char *)tag_fname, - buf_ffname); - is_static = test_for_static(&tagp); - - // Decide in which of the sixteen tables to store this match. - if (is_static) { - if (is_current) { - mtt = MT_ST_CUR; - } else { - mtt = MT_ST_OTH; - } - } else { - if (is_current) { - mtt = MT_GL_CUR; - } else { - mtt = MT_GL_OTH; - } - } - if (orgpat.regmatch.rm_ic && !match_no_ic) { - mtt += MT_IC_OFF; - } - if (match_re) { - mtt += MT_RE_OFF; - } - } - - // Add the found match in ht_match[mtt] and ga_match[mtt]. - // Store the info we need later, which depends on the kind of - // tags we are dealing with. - if (help_only) { -#define ML_EXTRA 3 - // Append the help-heuristic number after the tagname, for - // sorting it later. The heuristic is ignored for - // detecting duplicates. - // The format is {tagname}@{lang}NUL{heuristic}NUL - *tagp.tagname_end = NUL; - len = (size_t)(tagp.tagname_end - tagp.tagname); - mfp = xmalloc(sizeof(char) + len + 10 + ML_EXTRA + 1); - - p = (char_u *)mfp; - STRCPY(p, tagp.tagname); - p[len] = '@'; - STRCPY(p + len + 1, help_lang); - snprintf((char *)p + len + 1 + ML_EXTRA, STRLEN(p) + len + 1 + ML_EXTRA, "%06d", - help_heuristic((char *)tagp.tagname, - match_re ? matchoff : 0, !match_no_ic) - + help_pri); - - *tagp.tagname_end = TAB; - } else if (name_only) { - if (get_it_again) { - char_u *temp_end = tagp.command; - - if (*temp_end == '/') { - while (*temp_end && *temp_end != '\r' - && *temp_end != '\n' - && *temp_end != '$') { - temp_end++; - } - } - - if (tagp.command + 2 < temp_end) { - len = (size_t)(temp_end - tagp.command - 2); - mfp = xmalloc(len + 2); - STRLCPY(mfp, tagp.command + 2, len + 1); - } else { - mfp = NULL; - } - get_it_again = false; - } else { - len = (size_t)(tagp.tagname_end - tagp.tagname); - mfp = xmalloc(sizeof(char) + len + 1); - STRLCPY(mfp, tagp.tagname, len + 1); - - // if wanted, re-read line to get long form too - if (State & MODE_INSERT) { - get_it_again = p_sft; - } - } - } else { - size_t tag_fname_len = STRLEN(tag_fname); - // Save the tag in a buffer. - // Use 0x02 to separate fields (Can't use NUL, because the - // hash key is terminated by NUL). - // Emacs tag: <mtt><tag_fname><0x02><ebuf><0x02><lbuf><NUL> - // other tag: <mtt><tag_fname><0x02><0x02><lbuf><NUL> - // without Emacs tags: <mtt><tag_fname><0x02><lbuf><NUL> - // Here <mtt> is the "mtt" value plus 1 to avoid NUL. - len = tag_fname_len + STRLEN(lbuf) + 3; - mfp = xmalloc(sizeof(char) + len + 1); - p = (char_u *)mfp; - p[0] = (char_u)(mtt + 1); - STRCPY(p + 1, tag_fname); -#ifdef BACKSLASH_IN_FILENAME - // Ignore differences in slashes, avoid adding - // both path/file and path\file. - slash_adjust(p + 1); -#endif - p[tag_fname_len + 1] = TAG_SEP; - s = p + 1 + tag_fname_len + 1; - STRCPY(s, lbuf); - } - - if (mfp != NULL) { - hashitem_T *hi; - - // Don't add identical matches. - // Add all cscope tags, because they are all listed. - // "mfp" is used as a hash key, there is a NUL byte to end - // the part that matters for comparing, more bytes may - // follow after it. E.g. help tags store the priority - // after the NUL. - if (use_cscope) { - hash++; - } else { - hash = hash_hash((char_u *)mfp); - } - hi = hash_lookup(&ht_match[mtt], (const char *)mfp, - STRLEN(mfp), hash); - if (HASHITEM_EMPTY(hi)) { - hash_add_item(&ht_match[mtt], hi, (char_u *)mfp, hash); - ga_grow(&ga_match[mtt], 1); - ((char **)(ga_match[mtt].ga_data))[ga_match[mtt].ga_len++] = mfp; - match_count++; - } else { - // duplicate tag, drop it - xfree(mfp); - } - } - } - if (use_cscope && eof) { - break; - } - } // forever - - if (line_error) { - semsg(_("E431: Format error in tags file \"%s\""), tag_fname); - if (!use_cscope) { - semsg(_("Before byte %" PRId64), (int64_t)vim_ftell(fp)); - } - stop_searching = true; - line_error = false; - } - - if (!use_cscope) { - fclose(fp); - } - if (vimconv.vc_type != CONV_NONE) { - convert_setup(&vimconv, NULL, NULL); - } - - tag_file_sorted = NUL; - if (sort_error) { - semsg(_("E432: Tags file not sorted: %s"), tag_fname); - sort_error = false; - } - - /* - * Stop searching if sufficient tags have been found. - */ - if (match_count >= mincount) { + findtags_in_file(&st, flags, buf_ffname); + if (st.stop_searching) { retval = OK; - stop_searching = true; - } - - if (stop_searching || use_cscope) { break; } } // end of for-each-file loop - if (!use_cscope) { - tagname_free(&tn); - } + tagname_free(&tn); // stop searching when already did a linear search, or when TAG_NOIC // used, and 'ignorecase' not set or already did case-ignore search - if (stop_searching || linear || (!p_ic && noic) || orgpat.regmatch.rm_ic) { + if (st.stop_searching || st.linear || (!p_ic && noic) + || st.orgpat->regmatch.rm_ic) { break; } - if (use_cscope) { - break; - } - orgpat.regmatch.rm_ic = true; // try another time while ignoring case + + // try another time while ignoring case + st.orgpat->regmatch.rm_ic = true; } - if (!stop_searching) { - if (!did_open && verbose) { // never opened any tags file + if (!st.stop_searching) { + if (!st.did_open && verbose) { // never opened any tags file emsg(_("E433: No tags file")); } retval = OK; // It's OK even when no tag found } findtag_end: - xfree(lbuf); - vim_regfree(orgpat.regmatch.regprog); - xfree(tag_fname); + findtags_state_free(&st); - /* - * Move the matches from the ga_match[] arrays into one list of - * matches. When retval == FAIL, free the matches. - */ + // Move the matches from the ga_match[] arrays into one list of + // matches. When retval == FAIL, free the matches. if (retval == FAIL) { - match_count = 0; + st.match_count = 0; } - if (match_count > 0) { - matches = xmalloc((size_t)match_count * sizeof(char *)); - } else { - matches = NULL; - } - match_count = 0; - for (mtt = 0; mtt < MT_COUNT; mtt++) { - for (i = 0; i < ga_match[mtt].ga_len; i++) { - mfp = ((char **)(ga_match[mtt].ga_data))[i]; - if (matches == NULL) { - xfree(mfp); - } else { - if (!name_only) { - // Change mtt back to zero-based. - *mfp = (char)(*mfp - 1); - - // change the TAG_SEP back to NUL - for (p = (char_u *)mfp + 1; *p != NUL; p++) { - if (*p == TAG_SEP) { - *p = NUL; - } - } - } - matches[match_count++] = mfp; - } - } - - ga_clear(&ga_match[mtt]); - hash_clear(&ht_match[mtt]); - } - - *matchesp = matches; - *num_matches = match_count; + *num_matches = findtags_copy_matches(&st, matchesp); curbuf->b_help = help_save; xfree(saved_pat); @@ -2305,19 +2482,17 @@ findtag_end: static garray_T tag_fnames = GA_EMPTY_INIT_VALUE; -/* - * Callback function for finding all "tags" and "tags-??" files in - * 'runtimepath' doc directories. - */ +// Callback function for finding all "tags" and "tags-??" files in +// 'runtimepath' doc directories. static void found_tagfile_cb(char *fname, void *cookie) { - char_u *const tag_fname = vim_strsave((char_u *)fname); + char *const tag_fname = xstrdup(fname); #ifdef BACKSLASH_IN_FILENAME slash_adjust(tag_fname); #endif simplify_filename(tag_fname); - GA_APPEND(char_u *, &tag_fnames, tag_fname); + GA_APPEND(char *, &tag_fnames, tag_fname); } #if defined(EXITFREE) @@ -2350,11 +2525,9 @@ int get_tagfname(tagname_T *tnp, int first, char *buf) } if (curbuf->b_help) { - /* - * For help files it's done in a completely different way: - * Find "doc/tags" and "doc/tags-??" in all directories in - * 'runtimepath'. - */ + // For help files it's done in a completely different way: + // Find "doc/tags" and "doc/tags-??" in all directories in + // 'runtimepath'. if (first) { ga_clear_strings(&tag_fnames); ga_init(&tag_fnames, (int)sizeof(char *), 10); @@ -2374,15 +2547,15 @@ int get_tagfname(tagname_T *tnp, int first, char *buf) #ifdef BACKSLASH_IN_FILENAME slash_adjust(buf); #endif - simplify_filename((char_u *)buf); + simplify_filename(buf); for (int i = 0; i < tag_fnames.ga_len; i++) { - if (STRCMP(buf, ((char **)(tag_fnames.ga_data))[i]) == 0) { + if (strcmp(buf, ((char **)(tag_fnames.ga_data))[i]) == 0) { return FAIL; // avoid duplicate file names } } } else { - STRLCPY(buf, ((char **)(tag_fnames.ga_data))[tnp->tn_hf_idx++], MAXPATHL); + xstrlcpy(buf, ((char **)(tag_fnames.ga_data))[tnp->tn_hf_idx++], MAXPATHL); } return OK; } @@ -2390,16 +2563,14 @@ int get_tagfname(tagname_T *tnp, int first, char *buf) if (first) { // Init. We make a copy of 'tags', because autocommands may change // the value without notifying us. - tnp->tn_tags = vim_strsave((*curbuf->b_p_tags != NUL) ? (char_u *)curbuf->b_p_tags : p_tags); - tnp->tn_np = (char *)tnp->tn_tags; + tnp->tn_tags = xstrdup((*curbuf->b_p_tags != NUL) ? curbuf->b_p_tags : (char *)p_tags); + tnp->tn_np = tnp->tn_tags; } - /* - * Loop until we have found a file name that can be used. - * There are two states: - * tnp->tn_did_filefind_init == false: setup for next part in 'tags'. - * tnp->tn_did_filefind_init == true: find next file in this part. - */ + // Loop until we have found a file name that can be used. + // There are two states: + // tnp->tn_did_filefind_init == false: setup for next part in 'tags'. + // tnp->tn_did_filefind_init == true: find next file in this part. for (;;) { if (tnp->tn_did_filefind_init) { fname = (char *)vim_findfile(tnp->tn_search_ctx); @@ -2409,7 +2580,7 @@ int get_tagfname(tagname_T *tnp, int first, char *buf) tnp->tn_did_filefind_init = false; } else { - char_u *filename = NULL; + char *filename = NULL; // Stop when used all parts of 'tags'. if (*tnp->tn_np == NUL) { @@ -2418,24 +2589,22 @@ int get_tagfname(tagname_T *tnp, int first, char *buf) return FAIL; } - /* - * Copy next file name into buf. - */ + // Copy next file name into buf. buf[0] = NUL; (void)copy_option_part(&tnp->tn_np, buf, MAXPATHL - 1, " ,"); - r_ptr = (char *)vim_findfile_stopdir((char_u *)buf); + r_ptr = (char *)vim_findfile_stopdir(buf); // move the filename one char forward and truncate the // filepath with a NUL - filename = (char_u *)path_tail(buf); + filename = path_tail(buf); STRMOVE(filename + 1, filename); *filename++ = NUL; - tnp->tn_search_ctx = vim_findfile_init((char_u *)buf, filename, - (char_u *)r_ptr, 100, + tnp->tn_search_ctx = vim_findfile_init(buf, filename, + r_ptr, 100, false, // don't free visited list FINDFILE_FILE, // we search for a file - tnp->tn_search_ctx, true, (char_u *)curbuf->b_ffname); + tnp->tn_search_ctx, true, curbuf->b_ffname); if (tnp->tn_search_ctx != NULL) { tnp->tn_did_filefind_init = true; } @@ -2447,9 +2616,7 @@ int get_tagfname(tagname_T *tnp, int first, char *buf) return OK; } -/* - * Free the contents of a tagname_T that was filled by get_tagfname(). - */ +// Free the contents of a tagname_T that was filled by get_tagfname(). void tagname_free(tagname_T *tnp) { xfree(tnp->tn_tags); @@ -2466,13 +2633,13 @@ void tagname_free(tagname_T *tnp) /// @param lbuf line to be parsed /// /// @return FAIL if there is a format error in this line, OK otherwise. -static int parse_tag_line(char_u *lbuf, tagptrs_T *tagp) +static int parse_tag_line(char *lbuf, tagptrs_T *tagp) { - char_u *p; + char *p; // Isolate the tagname, from lbuf up to the first white tagp->tagname = lbuf; - p = (char_u *)vim_strchr((char *)lbuf, TAB); + p = vim_strchr(lbuf, TAB); if (p == NULL) { return FAIL; } @@ -2483,7 +2650,7 @@ static int parse_tag_line(char_u *lbuf, tagptrs_T *tagp) p++; } tagp->fname = p; - p = (char_u *)vim_strchr((char *)p, TAB); + p = vim_strchr(p, TAB); if (p == NULL) { return FAIL; } @@ -2501,29 +2668,27 @@ static int parse_tag_line(char_u *lbuf, tagptrs_T *tagp) return OK; } -/* - * Check if tagname is a static tag - * - * Static tags produced by the older ctags program have the format: - * 'file:tag file /pattern'. - * This is only recognized when both occurrence of 'file' are the same, to - * avoid recognizing "string::string" or ":exit". - * - * Static tags produced by the new ctags program have the format: - * 'tag file /pattern/;"<Tab>file:' " - * - * Return true if it is a static tag and adjust *tagname to the real tag. - * Return false if it is not a static tag. - */ +// Check if tagname is a static tag +// +// Static tags produced by the older ctags program have the format: +// 'file:tag file /pattern'. +// This is only recognized when both occurrence of 'file' are the same, to +// avoid recognizing "string::string" or ":exit". +// +// Static tags produced by the new ctags program have the format: +// 'tag file /pattern/;"<Tab>file:' " +// +// Return true if it is a static tag and adjust *tagname to the real tag. +// Return false if it is not a static tag. static bool test_for_static(tagptrs_T *tagp) { - char_u *p; + char *p; // Check for new style static tag ":...<Tab>file:[<Tab>...]" p = tagp->command; - while ((p = (char_u *)vim_strchr((char *)p, '\t')) != NULL) { + while ((p = vim_strchr(p, '\t')) != NULL) { p++; - if (STRNCMP(p, "file:", 5) == 0) { + if (strncmp(p, "file:", 5) == 0) { return true; } } @@ -2531,14 +2696,14 @@ static bool test_for_static(tagptrs_T *tagp) return false; } -// Returns the length of a matching tag line. -static size_t matching_line_len(const char_u *const lbuf) +/// @return the length of a matching tag line. +static size_t matching_line_len(const char *const lbuf) { - const char_u *p = lbuf + 1; + const char *p = lbuf + 1; // does the same thing as parse_match() - p += STRLEN(p) + 1; - return (size_t)(p - lbuf) + STRLEN(p); + p += strlen(p) + 1; + return (size_t)(p - lbuf) + strlen(p); } /// Parse a line from a matching tag. Does not change the line itself. @@ -2552,18 +2717,17 @@ static size_t matching_line_len(const char_u *const lbuf) /// @param tagp output: pointers into the line /// /// @return OK or FAIL. -static int parse_match(char_u *lbuf, tagptrs_T *tagp) +static int parse_match(char *lbuf, tagptrs_T *tagp) { int retval; - char_u *p; - char_u *pc, *pt; + char *p; + char *pc, *pt; tagp->tag_fname = lbuf + 1; - lbuf += STRLEN(tagp->tag_fname) + 2; + lbuf += strlen(tagp->tag_fname) + 2; // Find search pattern and the file name for non-etags. - retval = parse_tag_line(lbuf, - tagp); + retval = parse_tag_line(lbuf, tagp); tagp->tagkind = NULL; tagp->user_data = NULL; @@ -2582,20 +2746,20 @@ static int parse_match(char_u *lbuf, tagptrs_T *tagp) if (*p++ == TAB) { // Accept ASCII alphabetic kind characters and any multi-byte // character. - while (ASCII_ISALPHA(*p) || utfc_ptr2len((char *)p) > 1) { - if (STRNCMP(p, "kind:", 5) == 0) { + while (ASCII_ISALPHA(*p) || utfc_ptr2len(p) > 1) { + if (strncmp(p, "kind:", 5) == 0) { tagp->tagkind = p + 5; - } else if (STRNCMP(p, "user_data:", 10) == 0) { + } else if (strncmp(p, "user_data:", 10) == 0) { tagp->user_data = p + 10; - } else if (STRNCMP(p, "line:", 5) == 0) { - tagp->tagline = atoi((char *)p + 5); + } else if (strncmp(p, "line:", 5) == 0) { + tagp->tagline = atoi(p + 5); } if (tagp->tagkind != NULL && tagp->user_data != NULL) { break; } - pc = (char_u *)vim_strchr((char *)p, ':'); - pt = (char_u *)vim_strchr((char *)p, '\t'); + pc = vim_strchr(p, ':'); + pt = vim_strchr(p, '\t'); if (pc == NULL || (pt != NULL && pc > pt)) { tagp->tagkind = p; } @@ -2623,17 +2787,15 @@ static int parse_match(char_u *lbuf, tagptrs_T *tagp) return retval; } -/* - * Find out the actual file name of a tag. Concatenate the tags file name - * with the matching tag file name. - * Returns an allocated string. - */ -static char_u *tag_full_fname(tagptrs_T *tagp) +// Find out the actual file name of a tag. Concatenate the tags file name +// with the matching tag file name. +// Returns an allocated string. +static char *tag_full_fname(tagptrs_T *tagp) { - int c = *tagp->fname_end; + char c = *tagp->fname_end; *tagp->fname_end = NUL; - char_u *fullname = expand_tag_fname(tagp->fname, tagp->tag_fname, false); - *tagp->fname_end = (char_u)c; + char *fullname = expand_tag_fname(tagp->fname, tagp->tag_fname, false); + *tagp->fname_end = c; return fullname; } @@ -2642,30 +2804,29 @@ static char_u *tag_full_fname(tagptrs_T *tagp) /// /// @param lbuf_arg line from the tags file for this tag /// @param forceit :ta with ! -/// @param keep_help keep help flag (false for cscope) +/// @param keep_help keep help flag /// /// @return OK for success, NOTAGFILE when file not found, FAIL otherwise. -static int jumpto_tag(const char_u *lbuf_arg, int forceit, int keep_help) +static int jumpto_tag(const char *lbuf_arg, int forceit, int keep_help) { - int save_magic; bool save_p_ws; int save_p_scs, save_p_ic; linenr_T save_lnum; - char_u *str; - char_u *pbuf; // search pattern buffer - char_u *pbuf_end; - char_u *tofree_fname = NULL; - char_u *fname; + char *str; + char *pbuf; // search pattern buffer + char *pbuf_end; + char *tofree_fname = NULL; + char *fname; tagptrs_T tagp; int retval = FAIL; int getfile_result = GETFILE_UNUSED; int search_options; win_T *curwin_save = NULL; - char_u *full_fname = NULL; + char *full_fname = NULL; const bool old_KeyTyped = KeyTyped; // getting the file may reset it const int l_g_do_tagpreview = g_do_tagpreview; const size_t len = matching_line_len(lbuf_arg) + 1; - char_u *lbuf = xmalloc(len); + char *lbuf = xmalloc(len); memmove(lbuf, lbuf_arg, len); pbuf = xmalloc(LSIZE); @@ -2691,9 +2852,7 @@ static int jumpto_tag(const char_u *lbuf_arg, int forceit, int keep_help) *pbuf_end = NUL; { - /* - * Remove the "<Tab>fieldname:value" stuff; we don't need it here. - */ + // Remove the "<Tab>fieldname:value" stuff; we don't need it here. str = pbuf; if (find_extra(&str) == OK) { pbuf_end = str; @@ -2701,24 +2860,19 @@ static int jumpto_tag(const char_u *lbuf_arg, int forceit, int keep_help) } } - /* - * Expand file name, when needed (for environment variables). - * If 'tagrelative' option set, may change file name. - */ + // Expand file name, when needed (for environment variables). + // If 'tagrelative' option set, may change file name. fname = expand_tag_fname(fname, tagp.tag_fname, true); tofree_fname = fname; // free() it later - /* - * Check if the file with the tag exists before abandoning the current - * file. Also accept a file name for which there is a matching BufReadCmd - * autocommand event (e.g., http://sys/file). - */ + // Check if the file with the tag exists before abandoning the current + // file. Also accept a file name for which there is a matching BufReadCmd + // autocommand event (e.g., http://sys/file). if (!os_path_exists(fname) - && !has_autocmd(EVENT_BUFREADCMD, (char *)fname, - NULL)) { + && !has_autocmd(EVENT_BUFREADCMD, fname, NULL)) { retval = NOTAGFILE; xfree(nofile_fname); - nofile_fname = vim_strsave(fname); + nofile_fname = xstrdup(fname); goto erret; } @@ -2728,19 +2882,15 @@ static int jumpto_tag(const char_u *lbuf_arg, int forceit, int keep_help) postponed_split = 0; // don't split again below curwin_save = curwin; // Save current window - /* - * If we are reusing a window, we may change dir when - * entering it (autocommands) so turn the tag filename - * into a fullpath - */ + // If we are reusing a window, we may change dir when + // entering it (autocommands) so turn the tag filename + // into a fullpath if (!curwin->w_p_pvw) { - full_fname = (char_u *)FullName_save((char *)fname, false); + full_fname = FullName_save(fname, false); fname = full_fname; - /* - * Make the preview window the current window. - * Open a preview window when needed. - */ + // Make the preview window the current window. + // Open a preview window when needed. prepare_tagpreview(true); } } @@ -2748,7 +2898,7 @@ static int jumpto_tag(const char_u *lbuf_arg, int forceit, int keep_help) // If it was a CTRL-W CTRL-] command split window now. For ":tab tag" // open a new tab page. if (postponed_split && (swb_flags & (SWB_USEOPEN | SWB_USETAB))) { - buf_T *const existing_buf = buflist_findname_exp((char *)fname); + buf_T *const existing_buf = buflist_findname_exp(fname); if (existing_buf != NULL) { const win_T *wp = NULL; @@ -2793,7 +2943,7 @@ static int jumpto_tag(const char_u *lbuf_arg, int forceit, int keep_help) if (getfile_result == GETFILE_UNUSED) { // Careful: getfile() may trigger autocommands and call jumpto_tag() // recursively. - getfile_result = getfile(0, (char *)fname, NULL, true, (linenr_T)0, forceit); + getfile_result = getfile(0, fname, NULL, true, (linenr_T)0, forceit); } keep_help_flag = false; @@ -2801,33 +2951,29 @@ static int jumpto_tag(const char_u *lbuf_arg, int forceit, int keep_help) curwin->w_set_curswant = true; postponed_split = 0; - save_magic = p_magic; - p_magic = false; // always execute with 'nomagic' + const optmagic_T save_magic_overruled = magic_overruled; + magic_overruled = OPTION_MAGIC_OFF; // always execute with 'nomagic' // Save value of no_hlsearch, jumping to a tag is not a real search const bool save_no_hlsearch = no_hlsearch; - /* - * If 'cpoptions' contains 't', store the search pattern for the "n" - * command. If 'cpoptions' does not contain 't', the search pattern - * is not stored. - */ + // If 'cpoptions' contains 't', store the search pattern for the "n" + // command. If 'cpoptions' does not contain 't', the search pattern + // is not stored. if (vim_strchr(p_cpo, CPO_TAGPAT) != NULL) { search_options = 0; } else { search_options = SEARCH_KEEP; } - /* - * If the command is a search, try here. - * - * Reset 'smartcase' for the search, since the search pattern was not - * typed by the user. - * Only use do_search() when there is a full search command, without - * anything following. - */ + // If the command is a search, try here. + // + // Reset 'smartcase' for the search, since the search pattern was not + // typed by the user. + // Only use do_search() when there is a full search command, without + // anything following. str = pbuf; if (pbuf[0] == '/' || pbuf[0] == '?') { - str = (char_u *)skip_regexp((char *)pbuf + 1, pbuf[0], false, NULL) + 1; + str = skip_regexp(pbuf + 1, pbuf[0], false) + 1; } if (str > pbuf_end - 1) { // search command with nothing following save_p_ws = p_ws; @@ -2849,11 +2995,9 @@ static int jumpto_tag(const char_u *lbuf_arg, int forceit, int keep_help) retval = OK; } else { int found = 1; - int cc; + char cc; - /* - * try again, ignore case now - */ + // try again, ignore case now p_ic = true; if (!do_search(NULL, pbuf[0], pbuf[0], pbuf + 1, (long)1, search_options, NULL)) { @@ -2862,26 +3006,23 @@ static int jumpto_tag(const char_u *lbuf_arg, int forceit, int keep_help) (void)test_for_static(&tagp); cc = *tagp.tagname_end; *tagp.tagname_end = NUL; - snprintf((char *)pbuf, LSIZE, "^%s\\s\\*(", tagp.tagname); + snprintf(pbuf, LSIZE, "^%s\\s\\*(", tagp.tagname); if (!do_search(NULL, '/', '/', pbuf, (long)1, search_options, NULL)) { // Guess again: "^char * \<func (" - snprintf((char *)pbuf, LSIZE, "^\\[#a-zA-Z_]\\.\\*\\<%s\\s\\*(", + snprintf(pbuf, LSIZE, "^\\[#a-zA-Z_]\\.\\*\\<%s\\s\\*(", tagp.tagname); - if (!do_search(NULL, '/', '/', pbuf, (long)1, - search_options, NULL)) { + if (!do_search(NULL, '/', '/', pbuf, (long)1, search_options, NULL)) { found = 0; } } - *tagp.tagname_end = (char_u)cc; + *tagp.tagname_end = cc; } if (found == 0) { emsg(_("E434: Can't find tag pattern")); curwin->w_cursor.lnum = save_lnum; } else { - /* - * Only give a message when really guessed, not when 'ic' - * is set and match found while ignoring case. - */ + // Only give a message when really guessed, not when 'ic' + // is set and match found while ignoring case. if (found == 2 || !save_p_ic) { msg(_("E435: Couldn't find tag, just guessing!")); if (!msg_scrolled && msg_silent == 0) { @@ -2906,7 +3047,7 @@ static int jumpto_tag(const char_u *lbuf_arg, int forceit, int keep_help) secure = 1; sandbox++; curwin->w_cursor.lnum = 1; // start command in line 1 - do_cmdline_cmd((char *)pbuf); + do_cmdline_cmd(pbuf); retval = OK; // When the command has done something that is not allowed make sure @@ -2918,7 +3059,7 @@ static int jumpto_tag(const char_u *lbuf_arg, int forceit, int keep_help) sandbox--; } - p_magic = save_magic; + magic_overruled = save_magic_overruled; // restore no_hlsearch when keeping the old search pattern if (search_options) { set_no_hlsearch(save_no_hlsearch); @@ -2930,10 +3071,8 @@ static int jumpto_tag(const char_u *lbuf_arg, int forceit, int keep_help) } if (retval == OK) { - /* - * For a help buffer: Put the cursor line at the top of the window, - * the help subject will be below it. - */ + // For a help buffer: Put the cursor line at the top of the window, + // the help subject will be below it. if (curbuf->b_help) { set_topline(curwin, curwin->w_cursor.lnum); } @@ -2969,19 +3108,18 @@ erret: return retval; } -// If "expand" is true, expand wildcards in fname. -// If 'tagrelative' option set, change fname (name of file containing tag) -// according to tag_fname (name of tag file containing fname). -// Returns a pointer to allocated memory. -static char_u *expand_tag_fname(char_u *fname, char_u *const tag_fname, const bool expand) +/// If "expand" is true, expand wildcards in fname. +/// If 'tagrelative' option set, change fname (name of file containing tag) +/// according to tag_fname (name of tag file containing fname). +/// +/// @return a pointer to allocated memory. +static char *expand_tag_fname(char *fname, char *const tag_fname, const bool expand) { - char_u *p; - char_u *expanded_fname = NULL; + char *p; + char *expanded_fname = NULL; expand_T xpc; - /* - * Expand file name (for environment variables) when needed. - */ + // Expand file name (for environment variables) when needed. if (expand && path_has_wildcard(fname)) { ExpandInit(&xpc); xpc.xp_context = EXPAND_FILES; @@ -2992,20 +3130,17 @@ static char_u *expand_tag_fname(char_u *fname, char_u *const tag_fname, const bo } } - char_u *retval; + char *retval; if ((p_tr || curbuf->b_help) && !vim_isAbsName(fname) - && (p = (char_u *)path_tail((char *)tag_fname)) != tag_fname) { + && (p = path_tail(tag_fname)) != tag_fname) { retval = xmalloc(MAXPATHL); STRCPY(retval, tag_fname); - STRLCPY(retval + (p - tag_fname), fname, - MAXPATHL - (p - tag_fname)); - /* - * Translate names like "src/a/../b/file.c" into "src/b/file.c". - */ + xstrlcpy(retval + (p - tag_fname), fname, (size_t)(MAXPATHL - (p - tag_fname))); + // Translate names like "src/a/../b/file.c" into "src/b/file.c". simplify_filename(retval); } else { - retval = vim_strsave(fname); + retval = xstrdup(fname); } xfree(expanded_fname); @@ -3020,38 +3155,36 @@ static char_u *expand_tag_fname(char_u *fname, char_u *const tag_fname, const bo /// file. static int test_for_current(char *fname, char *fname_end, char *tag_fname, char *buf_ffname) { - int c; + char c; int retval = false; if (buf_ffname != NULL) { // if the buffer has a name { - c = (unsigned char)(*fname_end); + c = *fname_end; *fname_end = NUL; } - char *fullname = (char *)expand_tag_fname((char_u *)fname, (char_u *)tag_fname, true); + char *fullname = expand_tag_fname(fname, tag_fname, true); retval = (path_full_compare(fullname, buf_ffname, true, true) & kEqualFiles); xfree(fullname); - *fname_end = (char)c; + *fname_end = c; } return retval; } -/* - * Find the end of the tagaddress. - * Return OK if ";\"" is following, FAIL otherwise. - */ -static int find_extra(char_u **pp) +// Find the end of the tagaddress. +// Return OK if ";\"" is following, FAIL otherwise. +static int find_extra(char **pp) { - char_u *str = *pp; - char_u first_char = **pp; + char *str = *pp; + char first_char = **pp; // Repeat for addresses separated with ';' for (;;) { if (ascii_isdigit(*str)) { - str = (char_u *)skipdigits((char *)str + 1); + str = skipdigits(str + 1); } else if (*str == '/' || *str == '?') { - str = (char_u *)skip_regexp((char *)str + 1, *str, false, NULL); + str = skip_regexp(str + 1, *str, false); if (*str != first_char) { str = NULL; } else { @@ -3059,7 +3192,7 @@ static int find_extra(char_u **pp) } } else { // not a line number or search string, look for terminator. - str = (char_u *)strstr((char *)str, "|;\""); + str = strstr(str, "|;\""); if (str != NULL) { str++; break; @@ -3073,7 +3206,7 @@ static int find_extra(char_u **pp) first_char = *str; } - if (str != NULL && STRNCMP(str, ";\"", 2) == 0) { + if (str != NULL && strncmp(str, ";\"", 2) == 0) { *pp = str; return OK; } @@ -3090,11 +3223,11 @@ static void tagstack_clear_entry(taggy_T *item) } /// @param tagnames expand tag names -int expand_tags(int tagnames, char_u *pat, int *num_file, char ***file) +int expand_tags(int tagnames, char *pat, int *num_file, char ***file) { int i; int extra_flag; - char_u *name_buf; + char *name_buf; size_t name_buf_size = 100; tagptrs_T t_p; int ret; @@ -3107,11 +3240,11 @@ int expand_tags(int tagnames, char_u *pat, int *num_file, char ***file) extra_flag = 0; } if (pat[0] == '/') { - ret = find_tags((char *)pat + 1, num_file, file, + ret = find_tags(pat + 1, num_file, file, TAG_REGEXP | extra_flag | TAG_VERBOSE | TAG_NO_TAGFUNC, TAG_MANY, curbuf->b_ffname); } else { - ret = find_tags((char *)pat, num_file, file, + ret = find_tags(pat, num_file, file, TAG_REGEXP | extra_flag | TAG_VERBOSE | TAG_NO_TAGFUNC | TAG_NOIC, TAG_MANY, curbuf->b_ffname); } @@ -3121,10 +3254,10 @@ int expand_tags(int tagnames, char_u *pat, int *num_file, char ***file) for (i = 0; i < *num_file; i++) { size_t len; - parse_match((char_u *)(*file)[i], &t_p); + parse_match((*file)[i], &t_p); len = (size_t)(t_p.tagname_end - t_p.tagname); if (len > name_buf_size - 3) { - char_u *buf; + char *buf; name_buf_size = len + 3; buf = xrealloc(name_buf, name_buf_size); @@ -3165,10 +3298,10 @@ static int add_tag_field(dict_T *dict, const char *field_name, const char *start } return FAIL; } - char_u *buf = xmalloc(MAXPATHL); + char *buf = xmalloc(MAXPATHL); if (start != NULL) { if (end == NULL) { - end = start + STRLEN(start); + end = start + strlen(start); while (end > start && (end[-1] == '\r' || end[-1] == '\n')) { end--; } @@ -3177,31 +3310,30 @@ static int add_tag_field(dict_T *dict, const char *field_name, const char *start if (len > MAXPATHL - 1) { len = MAXPATHL - 1; } - STRLCPY(buf, start, len + 1); + xstrlcpy(buf, start, (size_t)len + 1); } buf[len] = NUL; - retval = tv_dict_add_str(dict, field_name, STRLEN(field_name), - (const char *)buf); + retval = tv_dict_add_str(dict, field_name, strlen(field_name), buf); xfree(buf); return retval; } /// Add the tags matching the specified pattern "pat" to the list "list" /// as a dictionary. Use "buf_fname" for priority, unless NULL. -int get_tags(list_T *list, char_u *pat, char_u *buf_fname) +int get_tags(list_T *list, char *pat, char *buf_fname) { int num_matches, i, ret; char **matches; - char_u *full_fname; + char *full_fname; dict_T *dict; tagptrs_T tp; bool is_static; - ret = find_tags((char *)pat, &num_matches, &matches, - TAG_REGEXP | TAG_NOIC, MAXCOL, (char *)buf_fname); + ret = find_tags(pat, &num_matches, &matches, + TAG_REGEXP | TAG_NOIC, MAXCOL, buf_fname); if (ret == OK && num_matches > 0) { for (i = 0; i < num_matches; i++) { - int parse_result = parse_match((char_u *)matches[i], &tp); + int parse_result = parse_match(matches[i], &tp); // Avoid an unused variable warning in release builds. (void)parse_result; @@ -3210,7 +3342,7 @@ int get_tags(list_T *list, char_u *pat, char_u *buf_fname) is_static = test_for_static(&tp); // Skip pseudo-tag lines. - if (STRNCMP(tp.tagname, "!_TAG_", 6) == 0) { + if (strncmp(tp.tagname, "!_TAG_", 6) == 0) { xfree(matches[i]); continue; } @@ -3219,11 +3351,11 @@ int get_tags(list_T *list, char_u *pat, char_u *buf_fname) tv_list_append_dict(list, dict); full_fname = tag_full_fname(&tp); - if (add_tag_field(dict, "name", (char *)tp.tagname, (char *)tp.tagname_end) == FAIL - || add_tag_field(dict, "filename", (char *)full_fname, NULL) == FAIL - || add_tag_field(dict, "cmd", (char *)tp.command, (char *)tp.command_end) == FAIL - || add_tag_field(dict, "kind", (char *)tp.tagkind, - tp.tagkind ? (char *)tp.tagkind_end : NULL) == FAIL + if (add_tag_field(dict, "name", tp.tagname, tp.tagname_end) == FAIL + || add_tag_field(dict, "filename", full_fname, NULL) == FAIL + || add_tag_field(dict, "cmd", tp.command, tp.command_end) == FAIL + || add_tag_field(dict, "kind", tp.tagkind, + tp.tagkind ? tp.tagkind_end : NULL) == FAIL || tv_dict_add_nr(dict, S_LEN("static"), is_static) == FAIL) { ret = FAIL; } @@ -3231,18 +3363,18 @@ int get_tags(list_T *list, char_u *pat, char_u *buf_fname) xfree(full_fname); if (tp.command_end != NULL) { - for (char_u *p = tp.command_end + 3; + for (char *p = tp.command_end + 3; *p != NUL && *p != '\n' && *p != '\r'; MB_PTR_ADV(p)) { if (p == tp.tagkind - || (p + 5 == tp.tagkind && STRNCMP(p, "kind:", 5) == 0)) { + || (p + 5 == tp.tagkind && strncmp(p, "kind:", 5) == 0)) { // skip "kind:<kind>" and "<kind>" p = tp.tagkind_end - 1; - } else if (STRNCMP(p, "file:", 5) == 0) { + } else if (strncmp(p, "file:", 5) == 0) { // skip "file:" (static tag) p += 4; } else if (!ascii_iswhite(*p)) { - char_u *s, *n; + char *s, *n; int len; // Add extra field as a dict entry. Fields are @@ -3254,17 +3386,17 @@ int get_tags(list_T *list, char_u *pat, char_u *buf_fname) len = (int)(p - n); if (*p == ':' && len > 0) { s = ++p; - while (*p != NUL && *p >= ' ') { + while (*p != NUL && (uint8_t)(*p) >= ' ') { p++; } n[len] = NUL; - if (add_tag_field(dict, (char *)n, (char *)s, (char *)p) == FAIL) { + if (add_tag_field(dict, n, s, p) == FAIL) { ret = FAIL; } n[len] = ':'; } else { // Skip field without colon. - while (*p != NUL && *p >= ' ') { + while (*p != NUL && (uint8_t)(*p) >= ' ') { p++; } } @@ -3350,9 +3482,9 @@ static void tagstack_shift(win_T *wp) wp->w_tagstacklen--; } -// Push a new item to the tag stack -static void tagstack_push_item(win_T *wp, char_u *tagname, int cur_fnum, int cur_match, pos_T mark, - int fnum, char_u *user_data) +/// Push a new item to the tag stack +static void tagstack_push_item(win_T *wp, char *tagname, int cur_fnum, int cur_match, pos_T mark, + int fnum, char *user_data) { taggy_T *tagstack = wp->w_tagstack; int idx = wp->w_tagstacklen; // top of the stack @@ -3364,7 +3496,7 @@ static void tagstack_push_item(win_T *wp, char_u *tagname, int cur_fnum, int cur } wp->w_tagstacklen++; - tagstack[idx].tagname = (char *)tagname; + tagstack[idx].tagname = tagname; tagstack[idx].cur_fnum = cur_fnum; tagstack[idx].cur_match = cur_match; if (tagstack[idx].cur_match < 0) { @@ -3372,16 +3504,16 @@ static void tagstack_push_item(win_T *wp, char_u *tagname, int cur_fnum, int cur } tagstack[idx].fmark.mark = mark; tagstack[idx].fmark.fnum = fnum; - tagstack[idx].user_data = (char *)user_data; + tagstack[idx].user_data = user_data; } -// Add a list of items to the tag stack in the specified window +/// Add a list of items to the tag stack in the specified window static void tagstack_push_items(win_T *wp, list_T *l) { listitem_T *li; dictitem_T *di; dict_T *itemdict; - char_u *tagname; + char *tagname; pos_T mark; int fnum; @@ -3400,8 +3532,7 @@ static void tagstack_push_items(win_T *wp, list_T *l) if (list2fpos(&di->di_tv, &mark, &fnum, NULL, false) != OK) { continue; } - if ((tagname = (char_u *)tv_dict_get_string(itemdict, "tagname", true)) - == NULL) { + if ((tagname = tv_dict_get_string(itemdict, "tagname", true)) == NULL) { continue; } @@ -3413,7 +3544,7 @@ static void tagstack_push_items(win_T *wp, list_T *l) (int)tv_dict_get_number(itemdict, "bufnr"), (int)tv_dict_get_number(itemdict, "matchnr") - 1, mark, fnum, - (char_u *)tv_dict_get_string(itemdict, "user_data", true)); + tv_dict_get_string(itemdict, "user_data", true)); } } @@ -3458,10 +3589,12 @@ int set_tagstack(win_T *wp, const dict_T *d, int action) if ((di = tv_dict_find(d, "curidx", -1)) != NULL) { tagstack_set_curidx(wp, (int)tv_get_number(&di->di_tv) - 1); } + if (action == 't') { // truncate the stack taggy_T *const tagstack = wp->w_tagstack; const int tagstackidx = wp->w_tagstackidx; int tagstacklen = wp->w_tagstacklen; + // delete all the tag stack entries above the current entry while (tagstackidx < tagstacklen) { tagstack_clear_entry(&tagstack[--tagstacklen]); |