aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorRob Pilling <robpilling@gmail.com>2019-10-10 22:06:45 +0100
committerRob Pilling <robpilling@gmail.com>2019-10-22 20:13:02 +0100
commit194f7bfacea934177d524197127242947bd28471 (patch)
treed13ac87810663c783907e96c8a9a691630755ddf /src
parente284b7233fb459a7a6d4ce0f98371b34b3639d2b (diff)
downloadrneovim-194f7bfacea934177d524197127242947bd28471.tar.gz
rneovim-194f7bfacea934177d524197127242947bd28471.tar.bz2
rneovim-194f7bfacea934177d524197127242947bd28471.zip
vim-patch:8.1.1228: not possible to process tags with a function
Problem: Not possible to process tags with a function. Solution: Add tagfunc() (Christian Brabandt, Andy Massimino, closes vim/vim#4010) https://github.com/vim/vim/commit/45e18cbdc40afd8144d20dcc07ad2d981636f4c9
Diffstat (limited to 'src')
-rw-r--r--src/nvim/buffer.c1
-rw-r--r--src/nvim/buffer_defs.h10
-rw-r--r--src/nvim/edit.c2
-rw-r--r--src/nvim/ex_cmds.c2
-rw-r--r--src/nvim/globals.h6
-rw-r--r--src/nvim/normal.c2
-rw-r--r--src/nvim/option.c5
-rw-r--r--src/nvim/option_defs.h1
-rw-r--r--src/nvim/options.lua8
-rw-r--r--src/nvim/tag.c386
-rw-r--r--src/nvim/tag.h27
-rw-r--r--src/nvim/testdir/test_alot.vim1
-rw-r--r--src/nvim/testdir/test_tagfunc.vim84
-rw-r--r--src/nvim/window.c16
14 files changed, 482 insertions, 69 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 16c4882975..a4b4e0d980 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/normal.c b/src/nvim/normal.c
index e0dc9d4f23..28183ffa1d 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -4929,7 +4929,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 880c467d30..872fc0f279 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
@@ -161,6 +173,7 @@ do_tag(
int use_tagstack;
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;
@@ -168,6 +181,11 @@ 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 */
@@ -181,6 +199,7 @@ do_tag(
if (type == DT_HELP) {
type = DT_TAG;
no_regexp = true;
+ use_tfu = 0;
}
prev_num_matches = num_matches;
@@ -196,7 +215,7 @@ 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 {
@@ -220,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 {
@@ -228,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
@@ -447,15 +468,22 @@ do_tag(
} 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
@@ -543,9 +571,20 @@ do_tag(
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;
@@ -1083,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
*
@@ -1108,6 +1361,7 @@ 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(
@@ -1198,6 +1452,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
@@ -1275,6 +1530,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
@@ -1856,7 +2121,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
@@ -2040,9 +2304,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
@@ -2283,6 +2545,7 @@ parse_match(
tagp);
tagp->tagkind = NULL;
+ tagp->user_data = NULL;
tagp->command_end = NULL;
if (retval == OK) {
@@ -2300,13 +2563,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;
@@ -2320,6 +2587,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;
}
@@ -2770,6 +3043,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 */
@@ -2789,14 +3071,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"
@@ -2958,6 +3242,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);
@@ -2996,7 +3283,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;
@@ -3007,7 +3294,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];
}
@@ -3021,7 +3308,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
@@ -3041,6 +3329,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
@@ -3076,10 +3365,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));
}
}
@@ -3103,6 +3395,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 6bf2e8329c..a871924d32 100644
--- a/src/nvim/testdir/test_alot.vim
+++ b/src/nvim/testdir/test_alot.vim
@@ -45,6 +45,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..976f1d8ff0 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;