diff options
author | Justin M. Keyes <justinkz@gmail.com> | 2019-10-22 20:27:35 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-10-22 20:27:35 -0700 |
commit | 9e4db2ef508b02583c7f30d12c045ccc07328f16 (patch) | |
tree | 1f239179a6cf53520290949ae9ac30be537b133c /src | |
parent | 02393a0c74d3aaeece939bafa4f658763acca965 (diff) | |
parent | 3b6b528ea98ca7bf8cd5ae1cf103203e3ca67814 (diff) | |
download | rneovim-9e4db2ef508b02583c7f30d12c045ccc07328f16.tar.gz rneovim-9e4db2ef508b02583c7f30d12c045ccc07328f16.tar.bz2 rneovim-9e4db2ef508b02583c7f30d12c045ccc07328f16.zip |
Merge #11199 from bobrippling/vim-8.1.1228
vim-patch:8.1.{1099,1228,1962} add 'tagfunc'
Diffstat (limited to 'src')
-rw-r--r-- | src/nvim/buffer.c | 1 | ||||
-rw-r--r-- | src/nvim/buffer_defs.h | 10 | ||||
-rw-r--r-- | src/nvim/edit.c | 2 | ||||
-rw-r--r-- | src/nvim/ex_cmds.c | 2 | ||||
-rw-r--r-- | src/nvim/globals.h | 6 | ||||
-rw-r--r-- | src/nvim/hashtab.h | 2 | ||||
-rw-r--r-- | src/nvim/normal.c | 2 | ||||
-rw-r--r-- | src/nvim/option.c | 5 | ||||
-rw-r--r-- | src/nvim/option_defs.h | 1 | ||||
-rw-r--r-- | src/nvim/options.lua | 8 | ||||
-rw-r--r-- | src/nvim/tag.c | 1186 | ||||
-rw-r--r-- | src/nvim/tag.h | 27 | ||||
-rw-r--r-- | src/nvim/testdir/test_alot.vim | 1 | ||||
-rw-r--r-- | src/nvim/testdir/test_tagfunc.vim | 84 | ||||
-rw-r--r-- | src/nvim/window.c | 20 |
15 files changed, 913 insertions, 444 deletions
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index b81ffd09e1..1d5aa8ba9b 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -1947,6 +1947,7 @@ void free_buf_options(buf_T *buf, int free_p_ff) clear_string_option(&buf->b_p_path); clear_string_option(&buf->b_p_tags); clear_string_option(&buf->b_p_tc); + clear_string_option(&buf->b_p_tfu); clear_string_option(&buf->b_p_dict); clear_string_option(&buf->b_p_tsr); clear_string_option(&buf->b_p_qe); diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 16c7804be0..ca740dea21 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -119,10 +119,11 @@ typedef uint16_t disptick_T; // display tick type * The taggy struct is used to store the information about a :tag command. */ typedef struct taggy { - char_u *tagname; /* tag name */ - fmark_T fmark; /* cursor position BEFORE ":tag" */ - int cur_match; /* match number */ - int cur_fnum; /* buffer number used for cur_match */ + char_u *tagname; // tag name + fmark_T fmark; // cursor position BEFORE ":tag" + int cur_match; // match number + int cur_fnum; // buffer number used for cur_match + char_u *user_data; // used with tagfunc } taggy_T; typedef struct buffblock buffblock_T; @@ -647,6 +648,7 @@ struct file_buffer { char_u *b_p_cpt; ///< 'complete' char_u *b_p_cfu; ///< 'completefunc' char_u *b_p_ofu; ///< 'omnifunc' + char_u *b_p_tfu; ///< 'tagfunc' int b_p_eol; ///< 'endofline' int b_p_fixeol; ///< 'fixendofline' int b_p_et; ///< 'expandtab' diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 49cf090962..62e4f77e6e 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -4047,12 +4047,14 @@ static int ins_compl_get_exp(pos_T *ini) // Find up to TAG_MANY matches. Avoids that an enormous number // of matches is found when compl_pattern is empty + g_tag_at_cursor = true; if (find_tags(compl_pattern, &num_matches, &matches, TAG_REGEXP | TAG_NAMES | TAG_NOIC | TAG_INS_COMP | (l_ctrl_x_mode != CTRL_X_NORMAL ? TAG_VERBOSE : 0), TAG_MANY, curbuf->b_ffname) == OK && num_matches > 0) { ins_compl_add_matches(num_matches, matches, p_ic); } + g_tag_at_cursor = false; p_ic = save_p_ic; break; diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 00de7a3d66..2e8bd79c81 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -4905,7 +4905,7 @@ int find_help_tags(const char_u *arg, int *num_matches, char_u ***matches, *matches = NULL; *num_matches = 0; - int flags = TAG_HELP | TAG_REGEXP | TAG_NAMES | TAG_VERBOSE; + int flags = TAG_HELP | TAG_REGEXP | TAG_NAMES | TAG_VERBOSE | TAG_NO_TAGFUNC; if (keep_lang) { flags |= TAG_KEEP_LANG; } diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 5237c621f9..c3d1a4d40b 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -787,7 +787,11 @@ EXTERN int postponed_split_flags INIT(= 0); /* args for win_split() */ EXTERN int postponed_split_tab INIT(= 0); /* cmdmod.tab */ EXTERN int g_do_tagpreview INIT(= 0); /* for tag preview commands: height of preview window */ -EXTERN int replace_offset INIT(= 0); /* offset for replace_push() */ +EXTERN int g_tag_at_cursor INIT(= false); // whether the tag command comes + // from the command line (0) or was + // invoked as a normal command (1) + +EXTERN int replace_offset INIT(= 0); // offset for replace_push() EXTERN char_u *escape_chars INIT(= (char_u *)" \t\\\"|"); /* need backslash in cmd line */ diff --git a/src/nvim/hashtab.h b/src/nvim/hashtab.h index a70a8bea63..19633d455f 100644 --- a/src/nvim/hashtab.h +++ b/src/nvim/hashtab.h @@ -81,10 +81,10 @@ typedef struct hashtable_S { size_t hi##todo_ = hi##ht_->ht_used; \ for (hashitem_T *hi = hi##ht_->ht_array; hi##todo_; hi++) { \ if (!HASHITEM_EMPTY(hi)) { \ + hi##todo_--; \ { \ code \ } \ - hi##todo_--; \ } \ } \ } while (0) diff --git a/src/nvim/normal.c b/src/nvim/normal.c index d051ba33b8..f6222f9d3f 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -4938,7 +4938,9 @@ static void nv_ident(cmdarg_T *cap) add_to_history(HIST_SEARCH, (char_u *)buf, true, NUL); (void)normal_search(cap, cmdchar == '*' ? '/' : '?', (char_u *)buf, 0); } else { + g_tag_at_cursor = true; do_cmdline_cmd(buf); + g_tag_at_cursor = false; } xfree(buf); diff --git a/src/nvim/option.c b/src/nvim/option.c index 22f7b85133..20351d3908 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -133,6 +133,7 @@ static char_u *p_cms; static char_u *p_cpt; static char_u *p_cfu; static char_u *p_ofu; +static char_u *p_tfu; static int p_eol; static int p_fixeol; static int p_et; @@ -2273,6 +2274,7 @@ void check_buf_options(buf_T *buf) check_string_option(&buf->b_p_ep); check_string_option(&buf->b_p_path); check_string_option(&buf->b_p_tags); + check_string_option(&buf->b_p_tfu); check_string_option(&buf->b_p_tc); check_string_option(&buf->b_p_dict); check_string_option(&buf->b_p_tsr); @@ -5590,6 +5592,7 @@ static char_u *get_varp_scope(vimoption_T *p, int opt_flags) case PV_INC: return (char_u *)&(curbuf->b_p_inc); case PV_DICT: return (char_u *)&(curbuf->b_p_dict); case PV_TSR: return (char_u *)&(curbuf->b_p_tsr); + case PV_TFU: return (char_u *)&(curbuf->b_p_tfu); case PV_STL: return (char_u *)&(curwin->w_p_stl); case PV_UL: return (char_u *)&(curbuf->b_p_ul); case PV_LW: return (char_u *)&(curbuf->b_p_lw); @@ -5742,6 +5745,7 @@ static char_u *get_varp(vimoption_T *p) case PV_SPF: return (char_u *)&(curwin->w_s->b_p_spf); case PV_SPL: return (char_u *)&(curwin->w_s->b_p_spl); case PV_SW: return (char_u *)&(curbuf->b_p_sw); + case PV_TFU: return (char_u *)&(curbuf->b_p_tfu); case PV_TS: return (char_u *)&(curbuf->b_p_ts); case PV_TW: return (char_u *)&(curbuf->b_p_tw); case PV_UDF: return (char_u *)&(curbuf->b_p_udf); @@ -6004,6 +6008,7 @@ void buf_copy_options(buf_T *buf, int flags) buf->b_p_cpt = vim_strsave(p_cpt); buf->b_p_cfu = vim_strsave(p_cfu); buf->b_p_ofu = vim_strsave(p_ofu); + buf->b_p_tfu = vim_strsave(p_tfu); buf->b_p_sts = p_sts; buf->b_p_sts_nopaste = p_sts_nopaste; buf->b_p_com = vim_strsave(p_com); diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index 67cb53ce02..e5a3c0bd95 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -780,6 +780,7 @@ enum { , BV_SUA , BV_SW , BV_SWF + , BV_TFU , BV_TAGS , BV_TC , BV_TS diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 52e788944b..e96b3f8e02 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -2389,6 +2389,14 @@ return { defaults={if_true={vi=""}} }, { + full_name='tagfunc', abbreviation='tfu', + type='string', scope={'buffer'}, + vim=true, + vi_def=true, + varname='p_tfu', + defaults={if_true={vi=""}} + }, + { full_name='tabline', abbreviation='tal', type='string', scope={'global'}, vi_def=true, diff --git a/src/nvim/tag.c b/src/nvim/tag.c index c8c9677a98..0d42deed2b 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -51,17 +51,20 @@ * 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 */ - /* filled in by parse_match(): */ - char_u *command_end; /* first char after command */ - char_u *tag_fname; /* file name of the tags file */ - char_u *tagkind; /* "kind:" value */ - char_u *tagkind_end; /* end of tagkind */ + // 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 + // 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 + // 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 } tagptrs_T; /* @@ -102,6 +105,10 @@ static char_u *nofile_fname = NULL; /* fname for NOTAGFILE error */ 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_u *tagmatchname = NULL; /* name of last used tag */ @@ -109,7 +116,12 @@ static char_u *tagmatchname = NULL; /* name of last used tag */ * Tag for preview window is remembered separately, to avoid messing up the * normal tagstack. */ -static taggy_T ptag_entry = { NULL, { { 0, 0, 0 }, 0, 0, NULL }, 0, 0 }; +static taggy_T ptag_entry = { NULL, { { 0, 0, 0 }, 0, 0, NULL }, 0, 0, NULL }; + +static int tfu_in_use = false; // disallow recursive call of tagfunc + +// Used instead of NUL to separate tag fields in the growarrays. +#define TAG_SEP 0x02 /* * Jump to tag; handling of tag commands and tag stack @@ -131,13 +143,13 @@ static taggy_T ptag_entry = { NULL, { { 0, 0, 0 }, 0, 0, NULL }, 0, 0 }; * * for cscope, returns TRUE if we jumped to tag or aborted, FALSE otherwise */ -int -do_tag ( - char_u *tag, /* tag (pattern) to jump to */ +int +do_tag( + char_u *tag, // tag (pattern) to jump to int type, int count, - int forceit, /* :ta with ! */ - int verbose /* print "tag not found" message */ + int forceit, // :ta with ! + int verbose // print "tag not found" message ) { taggy_T *tagstack = curwin->w_tagstack; @@ -148,28 +160,20 @@ do_tag ( int oldtagstackidx = tagstackidx; int prevtagstackidx = tagstackidx; int prev_num_matches; - int new_tag = FALSE; - int other_name; - int i, j, k; - int idx; + int new_tag = false; + int i; int ic; - char_u *p; - char_u *name; - int no_regexp = FALSE; + int no_regexp = false; int error_cur_match = 0; - char_u *command_end; - int save_pos = FALSE; + int save_pos = false; fmark_T saved_fmark; - int taglen; - int jumped_to_tag = FALSE; - tagptrs_T tagp, tagp2; + int jumped_to_tag = false; int new_num_matches; char_u **new_matches; - int attr; int use_tagstack; - int skip_msg = FALSE; - char_u *buf_ffname = curbuf->b_ffname; /* name to use for - priority computation */ + int skip_msg = false; + char_u *buf_ffname = curbuf->b_ffname; // name for priority computation + int use_tfu = 1; /* remember the matches for the last used tag */ static int num_matches = 0; @@ -177,19 +181,25 @@ do_tag ( static char_u **matches = NULL; static int flags; + if (tfu_in_use) { + EMSG(_(recurmsg)); + return false; + } + #ifdef EXITFREE if (type == DT_FREE) { /* remove the list of matches */ FreeWild(num_matches, matches); cs_free_tags(); num_matches = 0; - return FALSE; + return false; } #endif if (type == DT_HELP) { type = DT_TAG; - no_regexp = TRUE; + no_regexp = true; + use_tfu = 0; } prev_num_matches = num_matches; @@ -205,14 +215,15 @@ do_tag ( use_tagstack = false; new_tag = true; if (g_do_tagpreview != 0) { - xfree(ptag_entry.tagname); + tagstack_clear_entry(&ptag_entry); ptag_entry.tagname = vim_strsave(tag); } } else { - if (g_do_tagpreview != 0) - use_tagstack = FALSE; - else - use_tagstack = TRUE; + if (g_do_tagpreview != 0) { + use_tagstack = false; + } else { + use_tagstack = true; + } /* new pattern, add to the tag stack */ if (*tag != NUL @@ -228,7 +239,7 @@ do_tag ( cur_match = ptag_entry.cur_match; cur_fnum = ptag_entry.cur_fnum; } else { - xfree(ptag_entry.tagname); + tagstack_clear_entry(&ptag_entry); ptag_entry.tagname = vim_strsave(tag); } } else { @@ -236,16 +247,18 @@ do_tag ( * If the last used entry is not at the top, delete all tag * stack entries above it. */ - while (tagstackidx < tagstacklen) - xfree(tagstack[--tagstacklen].tagname); + while (tagstackidx < tagstacklen) { + tagstack_clear_entry(&tagstack[--tagstacklen]); + } /* if the tagstack is full: remove oldest entry */ if (++tagstacklen > TAGSTACKSIZE) { tagstacklen = TAGSTACKSIZE; - xfree(tagstack[0].tagname); - for (i = 1; i < tagstacklen; ++i) + tagstack_clear_entry(&tagstack[0]); + for (i = 1; i < tagstacklen; i++) { tagstack[i - 1] = tagstack[i]; - --tagstackidx; + } + tagstackidx--; } // put the tag name in the tag stack @@ -253,15 +266,15 @@ do_tag ( curwin->w_tagstacklen = tagstacklen; - save_pos = TRUE; /* save the cursor position below */ + save_pos = true; // save the cursor position below } - new_tag = TRUE; + new_tag = true; } else { if ( - g_do_tagpreview != 0 ? ptag_entry.tagname == NULL : - tagstacklen == 0) { - /* empty stack */ + g_do_tagpreview != 0 ? ptag_entry.tagname == NULL : + tagstacklen == 0) { + // empty stack EMSG(_(e_tagstack)); goto end_do_tag; } @@ -271,7 +284,7 @@ do_tag ( if ((tagstackidx -= count) < 0) { EMSG(_(bottommsg)); if (tagstackidx + count == 0) { - /* We did [num]^T from the bottom of the stack */ + // We did [num]^T from the bottom of the stack tagstackidx = 0; goto end_do_tag; } @@ -279,7 +292,7 @@ do_tag ( * way to the bottom now. */ tagstackidx = 0; - } else if (tagstackidx >= tagstacklen) { /* count == 0? */ + } else if (tagstackidx >= tagstacklen) { // count == 0? EMSG(_(topmsg)); goto end_do_tag; } @@ -293,8 +306,8 @@ do_tag ( * 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 */ + GETF_SETMARK, forceit) == FAIL) { + tagstackidx = oldtagstackidx; // back to old posn goto end_do_tag; } /* A BufReadPost autocommand may jump to the '" mark, but @@ -305,12 +318,12 @@ do_tag ( curwin->w_cursor.lnum = saved_fmark.mark.lnum; } curwin->w_cursor.col = saved_fmark.mark.col; - curwin->w_set_curswant = TRUE; + curwin->w_set_curswant = true; check_cursor(); if ((fdo_flags & FDO_TAG) && old_KeyTyped) foldOpenCursor(); - /* remove the old list of matches */ + // remove the old list of matches FreeWild(num_matches, matches); cs_free_tags(); num_matches = 0; @@ -325,8 +338,8 @@ do_tag ( cur_match = ptag_entry.cur_match; cur_fnum = ptag_entry.cur_fnum; } else { - /* ":tag" (no argument): go to newer pattern */ - save_pos = TRUE; /* save the cursor position below */ + // ":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 @@ -335,8 +348,8 @@ do_tag ( */ tagstackidx = tagstacklen - 1; EMSG(_(topmsg)); - save_pos = FALSE; - } else if (tagstackidx < 0) { /* must have been count == 0 */ + save_pos = false; + } else if (tagstackidx < 0) { // must have been count == 0 EMSG(_(bottommsg)); tagstackidx = 0; goto end_do_tag; @@ -344,9 +357,9 @@ do_tag ( cur_match = tagstack[tagstackidx].cur_match; cur_fnum = tagstack[tagstackidx].cur_fnum; } - new_tag = TRUE; - } else { /* go to other matching tag */ - /* Save index for when selection is cancelled. */ + new_tag = true; + } else { // go to other matching tag + // Save index for when selection is cancelled. prevtagstackidx = tagstackidx; if (g_do_tagpreview != 0) { @@ -371,7 +384,7 @@ do_tag ( cur_match = MAXCOL - 1; else if (cur_match < 0) { EMSG(_("E425: Cannot go before first matching tag")); - skip_msg = TRUE; + skip_msg = true; cur_match = 0; cur_fnum = curbuf->b_fnum; } @@ -418,15 +431,17 @@ do_tag ( * Repeat searching for tags, when a file has not been found. */ for (;; ) { - /* - * When desired match not found yet, try to find it (and others). - */ - if (use_tagstack) + int other_name; + char_u *name; + + // When desired match not found yet, try to find it (and others). + if (use_tagstack) { name = tagstack[tagstackidx].tagname; - else if (g_do_tagpreview != 0) + } else if (g_do_tagpreview != 0) { name = ptag_entry.tagname; - else + } else { name = tag; + } other_name = (tagmatchname == NULL || STRCMP(tagmatchname, name) != 0); if (new_tag || (cur_match >= num_matches && max_num_matches != MAXCOL) @@ -446,38 +461,49 @@ do_tag ( max_num_matches = cur_match + 1; } - /* when the argument starts with '/', use it as a regexp */ + // when the argument starts with '/', use it as a regexp if (!no_regexp && *name == '/') { flags = TAG_REGEXP; ++name; } else flags = TAG_NOIC; - if (type == DT_CSCOPE) + if (type == DT_CSCOPE) { flags = TAG_CSCOPE; - if (verbose) + } + if (verbose) { flags |= TAG_VERBOSE; + } + if (!use_tfu) { + flags |= TAG_NO_TAGFUNC; + } + 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. */ + 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. + } /* 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. */ if (!new_tag && !other_name) { - /* Find the position of each old match in the new list. Need - * to use parse_match() to find the tag line. */ - idx = 0; - for (j = 0; j < num_matches; ++j) { + int j, k; + int idx = 0; + tagptrs_T tagp, tagp2; + + // 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(matches[j], &tagp); for (i = idx; i < new_num_matches; ++i) { parse_match(new_matches[i], &tagp2); if (STRCMP(tagp.tagname, tagp2.tagname) == 0) { - p = new_matches[i]; - for (k = i; k > idx; --k) + char_u *p = new_matches[i]; + for (k = i; k > idx; k--) { new_matches[k] = new_matches[k - 1]; + } new_matches[idx++] = p; break; } @@ -504,304 +530,27 @@ do_tag ( // jump to count'th matching tag. cur_match = count > 0 ? count - 1 : 0; } else if (type == DT_SELECT || (type == DT_JUMP && num_matches > 1)) { - // List all the matching tags. - // Assume that the first match indicates how long the tags can - // be, and align the file names to that. - parse_match(matches[0], &tagp); - taglen = (int)(tagp.tagname_end - tagp.tagname + 2); - if (taglen < 18) - taglen = 18; - if (taglen > Columns - 25) - taglen = MAXCOL; - if (msg_col == 0) - msg_didout = FALSE; /* overwrite previous message */ - msg_start(); - MSG_PUTS_ATTR(_(" # pri kind tag"), HL_ATTR(HLF_T)); - msg_clr_eos(); - taglen_advance(taglen); - MSG_PUTS_ATTR(_("file\n"), HL_ATTR(HLF_T)); - - for (i = 0; i < num_matches && !got_int; i++) { - parse_match(matches[i], &tagp); - if (!new_tag && ((g_do_tagpreview != 0 && i == ptag_entry.cur_match) - || (use_tagstack - && i == tagstack[tagstackidx].cur_match))) { - *IObuff = '>'; - } else { - *IObuff = ' '; - } - vim_snprintf((char *)IObuff + 1, IOSIZE - 1, "%2d %s ", i + 1, - mt_names[matches[i][0] & MT_MASK]); - msg_puts((const char *)IObuff); - if (tagp.tagkind != NULL) { - msg_outtrans_len(tagp.tagkind, - (int)(tagp.tagkind_end - tagp.tagkind)); - } - msg_advance(13); - msg_outtrans_len_attr(tagp.tagname, - (int)(tagp.tagname_end - tagp.tagname), - HL_ATTR(HLF_T)); - msg_putchar(' '); - taglen_advance(taglen); - - /* Find out the actual file name. If it is long, truncate - * it and put "..." in the middle */ - p = tag_full_fname(&tagp); - msg_puts_long_attr(p, HL_ATTR(HLF_D)); - xfree(p); - - if (msg_col > 0) - msg_putchar('\n'); - if (got_int) - break; - msg_advance(15); - - /* print any extra fields */ - command_end = tagp.command_end; - if (command_end != NULL) { - p = command_end + 3; - while (*p && *p != '\r' && *p != '\n') { - while (*p == TAB) - ++p; - - /* skip "file:" without a value (static tag) */ - 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)) { - 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(p) >= Columns) { - msg_putchar('\n'); - if (got_int) - break; - msg_advance(15); - } - p = msg_outtrans_one(p, attr); - if (*p == TAB) { - msg_puts_attr(" ", attr); - break; - } - if (*p == ':') - attr = 0; - } - } - if (msg_col > 15) { - msg_putchar('\n'); - if (got_int) - break; - msg_advance(15); - } - } else { - for (p = tagp.command; - *p && *p != '\r' && *p != '\n'; ++p) - ; - command_end = p; - } - - /* - * Put the info (in several lines) at column 15. - * Don't display "/^" and "?^". - */ - p = tagp.command; - if (*p == '/' || *p == '?') { - ++p; - if (*p == '^') - ++p; - } - /* Remove leading whitespace from pattern */ - while (p != command_end && ascii_isspace(*p)) - ++p; - - while (p != command_end) { - if (msg_col + (*p == TAB ? 1 : ptr2cells(p)) > Columns) - msg_putchar('\n'); - if (got_int) - break; - msg_advance(15); - - // Skip backslash used for escaping a command char or a backslash. - if (*p == '\\' && (*(p + 1) == *tagp.command - || *(p + 1) == '\\')) { - ++p; - } - - if (*p == TAB) { - msg_putchar(' '); - ++p; - } else - p = msg_outtrans_one(p, 0); - - /* don't display the "$/;\"" and "$?;\"" */ - if (p == command_end - 2 && *p == '$' - && *(p + 1) == *tagp.command) - break; - /* don't display matching '/' or '?' */ - if (p == command_end - 1 && *p == *tagp.command - && (*p == '/' || *p == '?')) - break; - } - if (msg_col) - msg_putchar('\n'); - os_breakcheck(); - } - if (got_int) { - got_int = false; // only stop the listing - } + print_tag_list(new_tag, use_tagstack, num_matches, matches); ask_for_selection = true; } else if (type == DT_LTAG) { - list_T *list; - char_u tag_name[128 + 1]; - char_u *fname; - char_u *cmd; - - /* - * Add the matching tags to the location list for the current - * window. - */ - - fname = xmalloc(MAXPATHL + 1); - cmd = xmalloc(CMDBUFFSIZE + 1); - list = tv_list_alloc(num_matches); - - for (i = 0; i < num_matches; ++i) { - int len, cmd_len; - long lnum; - dict_T *dict; - - 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); - - /* Save the tag file name */ - p = tag_full_fname(&tagp); - STRLCPY(fname, p, MAXPATHL + 1); - xfree(p); - - /* - * Get the line number or the search pattern used to locate - * the tag. - */ - lnum = 0; - if (isdigit(*tagp.command)) - /* Line number is used to locate the tag */ - lnum = atol((char *)tagp.command); - else { - char_u *cmd_start, *cmd_end; - - /* Search pattern is used to locate the tag */ - - /* Locate the end of the command */ - cmd_start = tagp.command; - cmd_end = tagp.command_end; - if (cmd_end == NULL) { - for (p = tagp.command; - *p && *p != '\r' && *p != '\n'; ++p) - ; - cmd_end = p; - } - - /* - * Now, cmd_end points to the character after the - * command. Adjust it to point to the last - * character of the command. - */ - cmd_end--; - - /* - * Skip the '/' and '?' characters at the - * beginning and end of the search pattern. - */ - if (*cmd_start == '/' || *cmd_start == '?') - cmd_start++; - - if (*cmd_end == '/' || *cmd_end == '?') - cmd_end--; - - len = 0; - cmd[0] = NUL; - - /* - * If "^" is present in the tag search pattern, then - * copy it first. - */ - if (*cmd_start == '^') { - STRCPY(cmd, "^"); - cmd_start++; - len++; - } - - /* - * Precede the tag pattern with \V to make it very - * nomagic. - */ - STRCAT(cmd, "\\V"); - len += 2; - - cmd_len = (int)(cmd_end - cmd_start + 1); - if (cmd_len > (CMDBUFFSIZE - 5)) - cmd_len = CMDBUFFSIZE - 5; - STRNCAT(cmd, cmd_start, cmd_len); - len += cmd_len; - - if (cmd[len - 1] == '$') { - /* - * Replace '$' at the end of the search pattern - * with '\$' - */ - cmd[len - 1] = '\\'; - cmd[len] = '$'; - len++; - } - - cmd[len] = NUL; - } - - 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("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); - } + if (add_llist_tags(tag, num_matches, matches) == FAIL) { + goto end_do_tag; } - vim_snprintf((char *)IObuff, IOSIZE, "ltag %s", tag); - set_errorlist(curwin, list, ' ', IObuff, NULL); - - tv_list_free(list); - xfree(fname); - xfree(cmd); - - cur_match = 0; /* Jump to the first tag */ + cur_match = 0; // Jump to the first tag } if (ask_for_selection) { // Ask to select a tag from the list. i = prompt_for_number(NULL); if (i <= 0 || i > num_matches || got_int) { - /* no valid choice: don't change anything */ + // no valid choice: don't change anything if (use_tagstack) { tagstack[tagstackidx].fmark = saved_fmark; tagstackidx = prevtagstackidx; } cs_free_tags(); - jumped_to_tag = TRUE; + jumped_to_tag = true; break; } cur_match = i - 1; @@ -817,14 +566,25 @@ do_tag ( EMSG(_("E427: There is only one matching tag")); else EMSG(_("E428: Cannot go beyond last matching tag")); - skip_msg = TRUE; + skip_msg = true; } cur_match = num_matches - 1; } if (use_tagstack) { + tagptrs_T tagp2; + tagstack[tagstackidx].cur_match = cur_match; tagstack[tagstackidx].cur_fnum = cur_fnum; - ++tagstackidx; + + // store user-provided data originating from tagfunc + if (use_tfu && parse_match(matches[cur_match], &tagp2) == OK + && tagp2.user_data) { + XFREE_CLEAR(tagstack[tagstackidx].user_data); + tagstack[tagstackidx].user_data = vim_strnsave( + tagp2.user_data, tagp2.user_data_end - tagp2.user_data); + } + + tagstackidx++; } else if (g_do_tagpreview != 0) { ptag_entry.cur_match = cur_match; ptag_entry.cur_fnum = cur_fnum; @@ -843,13 +603,14 @@ do_tag ( && type != DT_CSCOPE && (num_matches > 1 || ic) && !skip_msg) { - /* Give an indication of the number of matching tags */ - sprintf((char *)IObuff, _("tag %d of %d%s"), - cur_match + 1, - num_matches, - max_num_matches != MAXCOL ? _(" or more") : ""); - if (ic) + // Give an indication of the number of matching tags + snprintf((char *)IObuff, sizeof(IObuff), _("tag %d of %d%s"), + cur_match + 1, + num_matches, + max_num_matches != MAXCOL ? _(" or more") : ""); + if (ic) { STRCAT(IObuff, _(" Using tag with different case!")); + } if ((num_matches > prev_num_matches || new_tag) && num_matches > 1) { if (ic) { @@ -867,7 +628,7 @@ do_tag ( } } - /* Let the SwapExists event know what tag we are jumping to. */ + // 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); @@ -879,7 +640,7 @@ do_tag ( set_vim_var_string(VV_SWAPCOMMAND, NULL, -1); if (i == NOTAGFILE) { - /* File not found: try again with another matching tag */ + // File not found: try again with another matching tag if ((type == DT_PREV && cur_match > 0) || ((type == DT_TAG || type == DT_NEXT || type == DT_FIRST) @@ -902,22 +663,354 @@ do_tag ( * tagstackidx is still valid. */ if (use_tagstack && tagstackidx > curwin->w_tagstacklen) tagstackidx = curwin->w_tagstackidx; - jumped_to_tag = TRUE; + jumped_to_tag = true; } } break; } end_do_tag: - /* Only store the new index when using the tagstack and it's valid. */ - if (use_tagstack && tagstackidx <= curwin->w_tagstacklen) + // Only store the new index when using the tagstack and it's valid. + if (use_tagstack && tagstackidx <= curwin->w_tagstacklen) { curwin->w_tagstackidx = tagstackidx; + } postponed_split = 0; // don't split next time g_do_tagpreview = 0; // don't do tag preview next time return jumped_to_tag; } +// +// List all the matching tags. +// +static void +print_tag_list( + int new_tag, + int use_tagstack, + int num_matches, + char_u **matches) +{ + taggy_T *tagstack = curwin->w_tagstack; + int tagstackidx = curwin->w_tagstackidx; + int i; + char_u *p; + char_u *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(matches[0], &tagp); + taglen = (int)(tagp.tagname_end - tagp.tagname + 2); + if (taglen < 18) { + taglen = 18; + } + if (taglen > Columns - 25) { + taglen = MAXCOL; + } + if (msg_col == 0) { + msg_didout = false; // overwrite previous message + } + msg_start(); + msg_puts_attr(_(" # pri kind tag"), HL_ATTR(HLF_T)); + msg_clr_eos(); + taglen_advance(taglen); + msg_puts_attr(_("file\n"), HL_ATTR(HLF_T)); + + for (i = 0; i < num_matches && !got_int; i++) { + parse_match(matches[i], &tagp); + if (!new_tag && ( + (g_do_tagpreview != 0 + && i == ptag_entry.cur_match) + || (use_tagstack + && i == tagstack[tagstackidx].cur_match))) { + *IObuff = '>'; + } else { + *IObuff = ' '; + } + vim_snprintf((char *)IObuff + 1, IOSIZE - 1, + "%2d %s ", i + 1, + mt_names[matches[i][0] & MT_MASK]); + msg_puts((char *)IObuff); + if (tagp.tagkind != NULL) { + msg_outtrans_len(tagp.tagkind, + (int)(tagp.tagkind_end - tagp.tagkind)); + } + msg_advance(13); + msg_outtrans_len_attr(tagp.tagname, + (int)(tagp.tagname_end - tagp.tagname), + HL_ATTR(HLF_T)); + msg_putchar(' '); + taglen_advance(taglen); + + // Find out the actual file name. If it is long, truncate + // it and put "..." in the middle + p = tag_full_fname(&tagp); + if (p != NULL) { + msg_outtrans_attr(p, HL_ATTR(HLF_D)); + XFREE_CLEAR(p); + } + if (msg_col > 0) { + msg_putchar('\n'); + } + if (got_int) { + break; + } + msg_advance(15); + + // print any extra fields + command_end = tagp.command_end; + if (command_end != NULL) { + p = command_end + 3; + while (*p && *p != '\r' && *p != '\n') { + while (*p == TAB) { + p++; + } + + // skip "file:" without a value (static tag) + 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)) { + 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(p) >= Columns) { + msg_putchar('\n'); + if (got_int) { + break; + } + msg_advance(15); + } + p = msg_outtrans_one(p, attr); + if (*p == TAB) { + msg_puts_attr(" ", attr); + break; + } + if (*p == ':') { + attr = 0; + } + } + } + if (msg_col > 15) { + msg_putchar('\n'); + if (got_int) { + break; + } + msg_advance(15); + } + } else { + for (p = tagp.command; + *p && *p != '\r' && *p != '\n'; + p++) { + } + command_end = p; + } + + // Put the info (in several lines) at column 15. + // Don't display "/^" and "?^". + p = tagp.command; + if (*p == '/' || *p == '?') { + p++; + if (*p == '^') { + p++; + } + } + // Remove leading whitespace from pattern + while (p != command_end && ascii_isspace(*p)) { + p++; + } + + while (p != command_end) { + if (msg_col + (*p == TAB ? 1 : ptr2cells(p)) > Columns) { + msg_putchar('\n'); + } + if (got_int) { + break; + } + msg_advance(15); + + // skip backslash used for escaping a command char or + // a backslash + if (*p == '\\' && (*(p + 1) == *tagp.command + || *(p + 1) == '\\')) { + p++; + } + + if (*p == TAB) { + msg_putchar(' '); + p++; + } else { + p = msg_outtrans_one(p, 0); + } + + // don't display the "$/;\"" and "$?;\"" + if (p == command_end - 2 && *p == '$' + && *(p + 1) == *tagp.command) { + break; + } + // don't display matching '/' or '?' + if (p == command_end - 1 && *p == *tagp.command + && (*p == '/' || *p == '?')) { + break; + } + } + if (msg_col) { + msg_putchar('\n'); + } + os_breakcheck(); + } + if (got_int) { + got_int = false; // only stop the listing + } +} + +// +// Add the matching tags to the location list for the current +// window. +// +static int +add_llist_tags( + char_u *tag, + int num_matches, + char_u **matches) +{ + list_T *list; + char_u tag_name[128 + 1]; + char_u *fname; + char_u *cmd; + int i; + char_u *p; + tagptrs_T tagp; + + fname = xmalloc(MAXPATHL + 1); + cmd = xmalloc(CMDBUFFSIZE + 1); + list = tv_list_alloc(0); + + for (i = 0; i < num_matches; i++) { + int len, cmd_len; + long lnum; + dict_T *dict; + + parse_match(matches[i], &tagp); + + // Save the tag name + len = (int)(tagp.tagname_end - tagp.tagname); + if (len > 128) { + len = 128; + } + xstrlcpy((char *)tag_name, (const char *)tagp.tagname, len); + tag_name[len] = NUL; + + // Save the tag file name + p = tag_full_fname(&tagp); + if (p == NULL) { + continue; + } + xstrlcpy((char *)fname, (const char *)p, MAXPATHL); + XFREE_CLEAR(p); + + // Get the line number or the search pattern used to locate + // the tag. + lnum = 0; + if (isdigit(*tagp.command)) { + // Line number is used to locate the tag + lnum = atol((char *)tagp.command); + } else { + char_u *cmd_start, *cmd_end; + + // Search pattern is used to locate the tag + + // Locate the end of the command + cmd_start = tagp.command; + cmd_end = tagp.command_end; + if (cmd_end == NULL) { + for (p = tagp.command; + *p && *p != '\r' && *p != '\n'; p++) { + } + cmd_end = p; + } + + // Now, cmd_end points to the character after the + // command. Adjust it to point to the last + // character of the command. + cmd_end--; + + // Skip the '/' and '?' characters at the + // beginning and end of the search pattern. + if (*cmd_start == '/' || *cmd_start == '?') { + cmd_start++; + } + + if (*cmd_end == '/' || *cmd_end == '?') { + cmd_end--; + } + + len = 0; + cmd[0] = NUL; + + // If "^" is present in the tag search pattern, then + // copy it first. + if (*cmd_start == '^') { + STRCPY(cmd, "^"); + cmd_start++; + len++; + } + + // Precede the tag pattern with \V to make it very + // nomagic. + STRCAT(cmd, "\\V"); + len += 2; + + cmd_len = (int)(cmd_end - cmd_start + 1); + if (cmd_len > (CMDBUFFSIZE - 5)) { + cmd_len = CMDBUFFSIZE - 5; + } + xstrlcat((char *)cmd, (char *)cmd_start, cmd_len); + len += cmd_len; + + if (cmd[len - 1] == '$') { + // Replace '$' at the end of the search pattern + // with '\$' + cmd[len - 1] = '\\'; + cmd[len] = '$'; + len++; + } + + cmd[len] = NUL; + } + + if ((dict = tv_dict_alloc()) == NULL) { + continue; + } + 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("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); + } + } + + vim_snprintf((char *)IObuff, IOSIZE, "ltag %s", tag); + set_errorlist(curwin, list, ' ', IObuff, NULL); + + tv_list_free(list); + XFREE_CLEAR(fname); + XFREE_CLEAR(cmd); + + return OK; +} + /* * Free cached tags. */ @@ -1029,6 +1122,220 @@ static void prepare_pats(pat_T *pats, int has_re) pats->regmatch.regprog = NULL; } +// +// Call the user-defined function to generate a list of tags used by +// find_tags(). +// +// Return OK if at least 1 tag has been successfully found, +// NOTDONE if the function returns v:null, and FAIL otherwise. +// +static int find_tagfunc_tags( + char_u *pat, // pattern supplied to the user-defined function + garray_T *ga, // the tags will be placed here + int *match_count, // here the number of tags found will be placed + int flags, // flags from find_tags (TAG_*) + char_u *buf_ffname) // name of buffer for priority +{ + pos_T save_pos; + list_T *taglist; + int ntags = 0; + int result = FAIL; + typval_T args[4]; + typval_T rettv; + char_u flagString[3]; + dict_T *d; + taggy_T *tag = &curwin->w_tagstack[curwin->w_tagstackidx]; + + if (*curbuf->b_p_tfu == NUL) { + return FAIL; + } + + args[0].v_type = VAR_STRING; + args[0].vval.v_string = pat; + args[1].v_type = VAR_STRING; + args[1].vval.v_string = flagString; + + // create 'info' dict argument + d = tv_dict_alloc(); + if (tag->user_data != NULL) { + tv_dict_add_str(d, S_LEN("user_data"), (const char *)tag->user_data); + } + if (buf_ffname != NULL) { + tv_dict_add_str(d, S_LEN("buf_ffname"), (const char *)buf_ffname); + } + + d->dv_refcount++; + args[2].v_type = VAR_DICT; + args[2].vval.v_dict = d; + + args[3].v_type = VAR_UNKNOWN; + + vim_snprintf((char *)flagString, sizeof(flagString), + "%s%s", + g_tag_at_cursor ? "c": "", + flags & TAG_INS_COMP ? "i": ""); + + save_pos = curwin->w_cursor; + result = call_vim_function(curbuf->b_p_tfu, 3, args, &rettv); + curwin->w_cursor = save_pos; // restore the cursor position + d->dv_refcount--; + + if (result == FAIL) { + return FAIL; + } + if (rettv.v_type == VAR_SPECIAL && rettv.vval.v_number == VV_NULL) { + tv_clear(&rettv); + return NOTDONE; + } + if (rettv.v_type != VAR_LIST || !rettv.vval.v_list) { + tv_clear(&rettv); + EMSG(_(tfu_inv_ret_msg)); + return FAIL; + } + taglist = rettv.vval.v_list; + + TV_LIST_ITER_CONST(taglist, li, { + char_u *mfp; + char_u *res_name; + char_u *res_fname; + char_u *res_cmd; + char_u *res_kind; + int len; + int has_extra = 0; + int name_only = flags & TAG_NAMES; + + if (TV_LIST_ITEM_TV(li)->v_type != VAR_DICT) { + EMSG(_(tfu_inv_ret_msg)); + break; + } + + len = 2; + res_name = NULL; + res_fname = NULL; + res_cmd = NULL; + res_kind = NULL; + + TV_DICT_ITER(TV_LIST_ITEM_TV(li)->vval.v_dict, di, { + const char_u *dict_key = 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 = tv->vval.v_string; + continue; + } + if (!STRCMP(dict_key, "filename")) { + res_fname = tv->vval.v_string; + continue; + } + if (!STRCMP(dict_key, "cmd")) { + res_cmd = tv->vval.v_string; + continue; + } + has_extra = 1; + 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; + }); + + if (has_extra) { + len += 2; // need space for ;" + } + + if (!res_name || !res_fname || !res_cmd) { + EMSG(_(tfu_inv_ret_msg)); + break; + } + + if (name_only) { + mfp = vim_strsave(res_name); + } else { + mfp = (char_u *)xmalloc((int)sizeof(char_u) + len + 1); + } + + if (mfp == NULL) { + continue; + } + + if (!name_only) { + char_u *p = mfp; + + *p++ = MT_GL_OTH + 1; // mtt + *p++ = TAG_SEP; // no tag file name + + STRCPY(p, res_name); + p += STRLEN(p); + + *p++ = TAB; + STRCPY(p, res_fname); + p += STRLEN(p); + + *p++ = TAB; + STRCPY(p, res_cmd); + p += STRLEN(p); + + if (has_extra) { + STRCPY(p, ";\""); + p += STRLEN(p); + + if (res_kind) { + *p++ = TAB; + STRCPY(p, res_kind); + p += STRLEN(p); + } + + TV_DICT_ITER(TV_LIST_ITEM_TV(li)->vval.v_dict, di, { + const char_u *dict_key = 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")) { + continue; + } + if (!STRCMP(dict_key, "filename")) { + continue; + } + if (!STRCMP(dict_key, "cmd")) { + continue; + } + if (!STRCMP(dict_key, "kind")) { + continue; + } + + *p++ = TAB; + STRCPY(p, dict_key); + p += STRLEN(p); + STRCPY(p, ":"); + p += STRLEN(p); + STRCPY(p, tv->vval.v_string); + p += STRLEN(p); + }); + } + } + + // Add all matches because tagfunc should do filtering. + ga_grow(ga, 1); + ((char_u **)(ga->ga_data))[ga->ga_len++] = mfp; + ntags++; + result = OK; + }); + + tv_clear(&rettv); + + *match_count = ntags; + return result; +} + /* * find_tags() - search for tags in tags files * @@ -1054,12 +1361,13 @@ static void prepare_pats(pat_T *pats, int has_re) * TAG_NOIC don't always ignore case * TAG_KEEP_LANG keep language * TAG_CSCOPE use cscope results for tags + * TAG_NO_TAGFUNC do not call the 'tagfunc' function */ -int -find_tags ( - char_u *pat, /* pattern to search for */ - int *num_matches, /* return: number of matches found */ - char_u ***matchesp, /* return: array of matches found */ +int +find_tags( + char_u *pat, // pattern to search for + int *num_matches, // return: number of matches found + char_u ***matchesp, // return: array of matches found int flags, int mincount, /* MAXCOL: find all matches other: minimal number of matches */ @@ -1143,6 +1451,7 @@ find_tags ( 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 @@ -1220,6 +1529,16 @@ find_tags ( // uninitialised. memset(&search_info, 0, 1); // -V512 + if (*curbuf->b_p_tfu != NUL && use_tfu && !tfu_in_use) { + tfu_in_use = true; + retval = find_tagfunc_tags(pat, &ga_match[0], &match_count, + flags, buf_ffname); + tfu_in_use = false; + if (retval != NOTDONE) { + goto findtag_end; + } + } + /* * 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 @@ -1803,7 +2122,6 @@ parse_line: } } } else { -#define TAG_SEP 0x02 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 @@ -1987,9 +2305,7 @@ void free_tag_stuff(void) do_tag(NULL, DT_FREE, 0, 0, 0); tag_freematch(); - if (ptag_entry.tagname) { - XFREE_CLEAR(ptag_entry.tagname); - } + tagstack_clear_entry(&ptag_entry); } #endif @@ -2000,11 +2316,11 @@ void free_tag_stuff(void) * * Return FAIL if no more tag file names, OK otherwise. */ -int -get_tagfname ( - tagname_T *tnp, /* holds status info */ - int first, /* TRUE when first file name is wanted */ - char_u *buf /* pointer to buffer of MAXPATHL chars */ +int +get_tagfname( + tagname_T *tnp, // holds status info + int first, // TRUE when first file name is wanted + char_u *buf // pointer to buffer of MAXPATHL chars ) { char_u *fname = NULL; @@ -2129,9 +2445,9 @@ void tagname_free(tagname_T *tnp) * * Return FAIL if there is a format error in this line, OK otherwise. */ -static int -parse_tag_line ( - char_u *lbuf, /* line to be parsed */ +static int +parse_tag_line( + char_u *lbuf, // line to be parsed tagptrs_T *tagp ) { @@ -2212,10 +2528,10 @@ static size_t matching_line_len(const char_u *const lbuf) * * Return OK or FAIL. */ -static int -parse_match ( - char_u *lbuf, /* input: matching line */ - tagptrs_T *tagp /* output: pointers into the line */ +static int +parse_match( + char_u *lbuf, // input: matching line + tagptrs_T *tagp // output: pointers into the line ) { int retval; @@ -2230,6 +2546,7 @@ parse_match ( tagp); tagp->tagkind = NULL; + tagp->user_data = NULL; tagp->command_end = NULL; if (retval == OK) { @@ -2247,13 +2564,17 @@ parse_match ( while (ASCII_ISALPHA(*p)) { if (STRNCMP(p, "kind:", 5) == 0) { tagp->tagkind = p + 5; + } else if (STRNCMP(p, "user_data:", 10) == 0) { + tagp->user_data = p + 10; + } + if (tagp->tagkind != NULL && tagp->user_data != NULL) { break; } + pc = vim_strchr(p, ':'); pt = vim_strchr(p, '\t'); if (pc == NULL || (pt != NULL && pc > pt)) { tagp->tagkind = p; - break; } if (pt == NULL) break; @@ -2267,6 +2588,12 @@ parse_match ( ; tagp->tagkind_end = p; } + if (tagp->user_data != NULL) { + for (p = tagp->user_data; + *p && *p != '\t' && *p != '\r' && *p != '\n'; p++) { + } + tagp->user_data_end = p; + } } return retval; } @@ -2717,6 +3044,15 @@ static int find_extra(char_u **pp) return FAIL; } +// +// Free a single entry in a tag stack +// +static void tagstack_clear_entry(taggy_T *item) +{ + XFREE_CLEAR(item->tagname); + XFREE_CLEAR(item->user_data); +} + int expand_tags ( int tagnames, /* expand tag names */ @@ -2736,14 +3072,16 @@ expand_tags ( tagnmflag = TAG_NAMES; else tagnmflag = 0; - if (pat[0] == '/') + if (pat[0] == '/') { ret = find_tags(pat + 1, num_file, file, - TAG_REGEXP | tagnmflag | TAG_VERBOSE, - TAG_MANY, curbuf->b_ffname); - else + TAG_REGEXP | tagnmflag | TAG_VERBOSE | TAG_NO_TAGFUNC, + TAG_MANY, curbuf->b_ffname); + } else { ret = find_tags(pat, num_file, file, - TAG_REGEXP | tagnmflag | TAG_VERBOSE | TAG_NOIC, - TAG_MANY, curbuf->b_ffname); + TAG_REGEXP | tagnmflag | TAG_VERBOSE + | TAG_NO_TAGFUNC | TAG_NOIC, + TAG_MANY, curbuf->b_ffname); + } if (ret == OK && !tagnames) { /* Reorganize the tags for display and matching as strings of: * "<tagname>\0<kind>\0<filename>\0" @@ -2769,8 +3107,8 @@ expand_tags ( * Add a tag field to the dictionary "dict". * Return OK or FAIL. */ -static int -add_tag_field ( +static int +add_tag_field( dict_T *dict, const char *field_name, const char_u *start, // start of the value @@ -2905,6 +3243,9 @@ static void get_tag_details(taggy_T *tag, dict_T *retdict) tv_dict_add_str(retdict, S_LEN("tagname"), (const char *)tag->tagname); tv_dict_add_nr(retdict, S_LEN("matchnr"), tag->cur_match + 1); tv_dict_add_nr(retdict, S_LEN("bufnr"), tag->cur_fnum); + if (tag->user_data) { + tv_dict_add_str(retdict, S_LEN("user_data"), (const char *)tag->user_data); + } pos = tv_list_alloc(4); tv_dict_add_list(retdict, S_LEN("from"), pos); @@ -2943,7 +3284,7 @@ static void tagstack_clear(win_T *wp) { // Free the current tag stack for (int i = 0; i < wp->w_tagstacklen; i++) { - xfree(wp->w_tagstack[i].tagname); + tagstack_clear_entry(&wp->w_tagstack[i]); } wp->w_tagstacklen = 0; wp->w_tagstackidx = 0; @@ -2954,7 +3295,7 @@ static void tagstack_clear(win_T *wp) static void tagstack_shift(win_T *wp) { taggy_T *tagstack = wp->w_tagstack; - xfree(tagstack[0].tagname); + tagstack_clear_entry(&tagstack[0]); for (int i = 1; i < wp->w_tagstacklen; i++) { tagstack[i - 1] = tagstack[i]; } @@ -2968,7 +3309,8 @@ static void tagstack_push_item( int cur_fnum, int cur_match, pos_T mark, - int fnum) + int fnum, + char_u *user_data) { taggy_T *tagstack = wp->w_tagstack; int idx = wp->w_tagstacklen; // top of the stack @@ -2988,6 +3330,7 @@ static void tagstack_push_item( } tagstack[idx].fmark.mark = mark; tagstack[idx].fmark.fnum = fnum; + tagstack[idx].user_data = user_data; } // Add a list of items to the tag stack in the specified window @@ -3023,10 +3366,13 @@ static void tagstack_push_items(win_T *wp, list_T *l) if (mark.col > 0) { mark.col--; } - tagstack_push_item(wp, tagname, - (int)tv_dict_get_number(itemdict, "bufnr"), - (int)tv_dict_get_number(itemdict, "matchnr") - 1, - mark, fnum); + tagstack_push_item( + wp, + tagname, + (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)); } } @@ -3050,6 +3396,12 @@ int set_tagstack(win_T *wp, dict_T *d, int action) dictitem_T *di; list_T *l; + // not allowed to alter the tag stack entries from inside tagfunc + if (tfu_in_use) { + EMSG(_(recurmsg)); + return FAIL; + } + if ((di = tv_dict_find(d, "items", -1)) != NULL) { if (di->di_tv.v_type != VAR_LIST) { return FAIL; diff --git a/src/nvim/tag.h b/src/nvim/tag.h index a8fddd05da..9f671043b3 100644 --- a/src/nvim/tag.h +++ b/src/nvim/tag.h @@ -20,20 +20,21 @@ #define DT_LTAG 11 /* tag using location list */ #define DT_FREE 99 /* free cached matches */ -/* - * flags for find_tags(). - */ -#define TAG_HELP 1 /* only search for help tags */ -#define TAG_NAMES 2 /* only return name of tag */ -#define TAG_REGEXP 4 /* use tag pattern as regexp */ -#define TAG_NOIC 8 /* don't always ignore case */ -#define TAG_CSCOPE 16 /* cscope tag */ -#define TAG_VERBOSE 32 /* message verbosity */ -#define TAG_INS_COMP 64 /* Currently doing insert completion */ -#define TAG_KEEP_LANG 128 /* keep current language */ +// +// flags for find_tags(). +// +#define TAG_HELP 1 // only search for help tags +#define TAG_NAMES 2 // only return name of tag +#define TAG_REGEXP 4 // use tag pattern as regexp +#define TAG_NOIC 8 // don't always ignore case +#define TAG_CSCOPE 16 // cscope tag +#define TAG_VERBOSE 32 // message verbosity +#define TAG_INS_COMP 64 // Currently doing insert completion +#define TAG_KEEP_LANG 128 // keep current language +#define TAG_NO_TAGFUNC 256 // do not use 'tagfunc' -#define TAG_MANY 300 /* When finding many tags (for completion), - find up to this many tags */ +#define TAG_MANY 300 // When finding many tags (for completion), + // find up to this many tags /* * Structure used for get_tagfname(). diff --git a/src/nvim/testdir/test_alot.vim b/src/nvim/testdir/test_alot.vim index 2c52452f6b..f1274b01c8 100644 --- a/src/nvim/testdir/test_alot.vim +++ b/src/nvim/testdir/test_alot.vim @@ -44,6 +44,7 @@ source test_syn_attr.vim source test_tabline.vim source test_tabpage.vim source test_tagcase.vim +source test_tagfunc.vim source test_tagjump.vim source test_taglist.vim source test_true_false.vim diff --git a/src/nvim/testdir/test_tagfunc.vim b/src/nvim/testdir/test_tagfunc.vim new file mode 100644 index 0000000000..242aa3a235 --- /dev/null +++ b/src/nvim/testdir/test_tagfunc.vim @@ -0,0 +1,84 @@ +" Test 'tagfunc' + +func TagFunc(pat, flag, info) + let g:tagfunc_args = [a:pat, a:flag, a:info] + let tags = [] + for num in range(1,10) + let tags += [{ + \ 'cmd': '2', 'name': 'nothing'.num, 'kind': 'm', + \ 'filename': 'Xfile1', 'user_data': 'somedata'.num, + \}] + endfor + return tags +endfunc + +func Test_tagfunc() + set tagfunc=TagFunc + new Xfile1 + call setline(1, ['empty', 'one()', 'empty']) + write + + call assert_equal({'cmd': '2', 'static': 0, + \ 'name': 'nothing2', 'user_data': 'somedata2', + \ 'kind': 'm', 'filename': 'Xfile1'}, taglist('.')[1]) + + call settagstack(win_getid(), {'items': []}) + + tag arbitrary + call assert_equal('arbitrary', g:tagfunc_args[0]) + call assert_equal('', g:tagfunc_args[1]) + call assert_equal('somedata1', gettagstack().items[0].user_data) + 5tag arbitrary + call assert_equal('arbitrary', g:tagfunc_args[0]) + call assert_equal('', g:tagfunc_args[1]) + call assert_equal('somedata5', gettagstack().items[1].user_data) + pop + tag + call assert_equal('arbitrary', g:tagfunc_args[0]) + call assert_equal('', g:tagfunc_args[1]) + call assert_equal('somedata5', gettagstack().items[1].user_data) + + let g:tagfunc_args=[] + execute "normal! \<c-]>" + call assert_equal('one', g:tagfunc_args[0]) + call assert_equal('c', g:tagfunc_args[1]) + + set cpt=t + let g:tagfunc_args=[] + execute "normal! i\<c-n>\<c-y>" + call assert_equal('ci', g:tagfunc_args[1]) + call assert_equal('nothing1', getline('.')[0:7]) + + func BadTagFunc1(...) + return 0 + endfunc + func BadTagFunc2(...) + return [1] + endfunc + func BadTagFunc3(...) + return [{'name': 'foo'}] + endfunc + + for &tagfunc in ['BadTagFunc1', 'BadTagFunc2', 'BadTagFunc3'] + try + tag nothing + call assert_false(1, 'tag command should have failed') + catch + call assert_exception('E987:') + endtry + exe 'delf' &tagfunc + endfor + + func NullTagFunc(...) + return v:null + endfunc + set tags= tfu=NullTagFunc + call assert_fails('tag nothing', 'E426') + delf NullTagFunc + + bwipe! + set tags& tfu& cpt& + call delete('Xfile1') +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/window.c b/src/nvim/window.c index 4d8eaa9dcc..ce5be8e904 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -70,8 +70,8 @@ static char *m_onlyone = N_("Already only one window"); /* * all CTRL-W window commands are handled here, called from normal_cmd(). */ -void -do_window ( +void +do_window( int nchar, long Prenum, int xchar /* extra char from ":wincmd gx" or NUL */ @@ -1537,10 +1537,14 @@ static void win_init(win_T *newp, win_T *oldp, int flags) /* copy tagstack and folds */ for (i = 0; i < oldp->w_tagstacklen; i++) { - newp->w_tagstack[i] = oldp->w_tagstack[i]; - if (newp->w_tagstack[i].tagname != NULL) - newp->w_tagstack[i].tagname = - vim_strsave(newp->w_tagstack[i].tagname); + taggy_T *tag = &newp->w_tagstack[i]; + *tag = oldp->w_tagstack[i]; + if (tag->tagname != NULL) { + tag->tagname = vim_strsave(tag->tagname); + } + if (tag->user_data != NULL) { + tag->user_data = vim_strsave(tag->user_data); + } } newp->w_tagstackidx = oldp->w_tagstackidx; newp->w_tagstacklen = oldp->w_tagstacklen; @@ -4638,8 +4642,10 @@ win_free ( xfree(wp->w_lines); - for (i = 0; i < wp->w_tagstacklen; ++i) + for (i = 0; i < wp->w_tagstacklen; i++) { xfree(wp->w_tagstack[i].tagname); + xfree(wp->w_tagstack[i].user_data); + } xfree(wp->w_localdir); |