summaryrefslogtreecommitdiffstats
path: root/src/tag.c
diff options
context:
space:
mode:
authorBram Moolenaar <Bram@vim.org>2019-04-28 18:05:35 +0200
committerBram Moolenaar <Bram@vim.org>2019-04-28 18:05:35 +0200
commit45e18cbdc40afd8144d20dcc07ad2d981636f4c9 (patch)
tree11762469ca5d6c4bd49ca50381160afd30dcb2f3 /src/tag.c
parent7a9df9dd00bac462a2942dc798e298f365779fd0 (diff)
patch 8.1.1228: not possible to process tags with a functionv8.1.1228
Problem: Not possible to process tags with a function. Solution: Add tagfunc() (Christian Brabandt, Andy Massimino, closes #4010)
Diffstat (limited to 'src/tag.c')
-rw-r--r--src/tag.c378
1 files changed, 347 insertions, 31 deletions
diff --git a/src/tag.c b/src/tag.c
index 1afeaa273b..5ab4eaab23 100644
--- a/src/tag.c
+++ b/src/tag.c
@@ -18,20 +18,23 @@
*/
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 */
+ // 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.
#ifdef FEAT_EMACS_TAGS
- int is_etag; /* TRUE for emacs tag */
+ int is_etag; // TRUE for emacs tag
#endif
- char_u *tagkind; /* "kind:" value */
- char_u *tagkind_end; /* end of tagkind */
+ 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;
/*
@@ -78,9 +81,14 @@ static void print_tag_list(int new_tag, int use_tagstack, int num_matches, char_
#if defined(FEAT_QUICKFIX) && defined(FEAT_EVAL)
static int add_llist_tags(char_u *tag, int num_matches, char_u **matches);
#endif
+static void tagstack_clear_entry(taggy_T *item);
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");
+#ifdef FEAT_EVAL
+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");
+#endif
static char_u *tagmatchname = NULL; /* name of last used tag */
@@ -89,9 +97,16 @@ 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, 0};
+static taggy_T ptag_entry = {NULL, {{0, 0, 0}, 0}, 0, 0, NULL};
+#endif
+
+#ifdef FEAT_EVAL
+static int tfu_in_use = FALSE; // disallow recursive call of tagfunc
#endif
+// 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
*
@@ -144,6 +159,7 @@ do_tag(
int skip_msg = FALSE;
char_u *buf_ffname = curbuf->b_ffname; /* name to use for
priority computation */
+ int use_tfu = 1;
/* remember the matches for the last used tag */
static int num_matches = 0;
@@ -151,6 +167,14 @@ do_tag(
static char_u **matches = NULL;
static int flags;
+#ifdef FEAT_EVAL
+ if (tfu_in_use)
+ {
+ emsg(_(recurmsg));
+ return FALSE;
+ }
+#endif
+
#ifdef EXITFREE
if (type == DT_FREE)
{
@@ -168,6 +192,7 @@ do_tag(
{
type = DT_TAG;
no_regexp = TRUE;
+ use_tfu = 0;
}
prev_num_matches = num_matches;
@@ -187,7 +212,7 @@ do_tag(
#if defined(FEAT_QUICKFIX)
if (g_do_tagpreview != 0)
{
- vim_free(ptag_entry.tagname);
+ tagstack_clear_entry(&ptag_entry);
if ((ptag_entry.tagname = vim_strsave(tag)) == NULL)
goto end_do_tag;
}
@@ -226,7 +251,7 @@ do_tag(
}
else
{
- vim_free(ptag_entry.tagname);
+ tagstack_clear_entry(&ptag_entry);
if ((ptag_entry.tagname = vim_strsave(tag)) == NULL)
goto end_do_tag;
}
@@ -239,13 +264,13 @@ do_tag(
* stack entries above it.
*/
while (tagstackidx < tagstacklen)
- vim_free(tagstack[--tagstacklen].tagname);
+ tagstack_clear_entry(&tagstack[--tagstacklen]);
/* if the tagstack is full: remove oldest entry */
if (++tagstacklen > TAGSTACKSIZE)
{
tagstacklen = TAGSTACKSIZE;
- vim_free(tagstack[0].tagname);
+ tagstack_clear_entry(&tagstack[0]);
for (i = 1; i < tagstacklen; ++i)
tagstack[i - 1] = tagstack[i];
--tagstackidx;
@@ -529,6 +554,10 @@ do_tag(
#endif
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)
@@ -647,8 +676,20 @@ do_tag(
}
if (use_tagstack)
{
+ tagptrs_T tagp;
+
tagstack[tagstackidx].cur_match = cur_match;
tagstack[tagstackidx].cur_fnum = cur_fnum;
+
+ // store user-provided data originating from tagfunc
+ if (use_tfu && parse_match(matches[cur_match], &tagp) == OK
+ && tagp.user_data)
+ {
+ VIM_CLEAR(tagstack[tagstackidx].user_data);
+ tagstack[tagstackidx].user_data = vim_strnsave(
+ tagp.user_data, tagp.user_data_end - tagp.user_data);
+ }
+
++tagstackidx;
}
#if defined(FEAT_QUICKFIX)
@@ -1243,6 +1284,237 @@ prepare_pats(pat_T *pats, int has_re)
pats->regmatch.regprog = NULL;
}
+#ifdef FEAT_EVAL
+/*
+ * 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;
+ listitem_T *item;
+ 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
+ if ((d = dict_alloc_lock(VAR_FIXED)) == NULL)
+ return FAIL;
+ if (tag->user_data != NULL)
+ dict_add_string(d, "user_data", tag->user_data);
+ if (buf_ffname != NULL)
+ dict_add_string(d, "buf_ffname", 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 == VVAL_NULL)
+ {
+ clear_tv(&rettv);
+ return NOTDONE;
+ }
+ if (rettv.v_type != VAR_LIST || !rettv.vval.v_list)
+ {
+ clear_tv(&rettv);
+ emsg(_(tfu_inv_ret_msg));
+ return FAIL;
+ }
+ taglist = rettv.vval.v_list;
+
+ for (item = taglist->lv_first; item != NULL; item = item->li_next)
+ {
+ char_u *mfp;
+ char_u *res_name, *res_fname, *res_cmd, *res_kind;
+ int len;
+ dict_iterator_T iter;
+ char_u *dict_key;
+ typval_T *tv;
+ int has_extra = 0;
+ int name_only = flags & TAG_NAMES;
+
+ if (item->li_tv.v_type != VAR_DICT)
+ {
+ emsg(_(tfu_inv_ret_msg));
+ break;
+ }
+
+#ifdef FEAT_EMACS_TAGS
+ len = 3;
+#else
+ len = 2;
+#endif
+ res_name = NULL;
+ res_fname = NULL;
+ res_cmd = NULL;
+ res_kind = NULL;
+
+ dict_iterate_start(&item->li_tv, &iter);
+ while (NULL != (dict_key = dict_iterate_next(&iter, &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 *)alloc((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
+#ifdef FEAT_EMACS_TAGS
+ *p++ = TAG_SEP;
+#endif
+
+ 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);
+ }
+
+ dict_iterate_start(&item->li_tv, &iter);
+ while (NULL != (dict_key = dict_iterate_next(&iter, &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.
+ if (ga_grow(ga, 1) == OK)
+ {
+ ((char_u **)(ga->ga_data))[ga->ga_len++] = mfp;
+ ++ntags;
+ result = OK;
+ }
+ else
+ {
+ vim_free(mfp);
+ break;
+ }
+ }
+
+ clear_tv(&rettv);
+
+ *match_count = ntags;
+ return result;
+}
+#endif
+
/*
* find_tags() - search for tags in tags files
*
@@ -1268,6 +1540,7 @@ 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(
@@ -1385,6 +1658,9 @@ find_tags(
int use_cscope = (flags & TAG_CSCOPE);
#endif
int verbose = (flags & TAG_VERBOSE);
+#ifdef FEAT_EVAL
+ int use_tfu = ((flags & TAG_NO_TAGFUNC) == 0);
+#endif
int save_p_ic = p_ic;
/*
@@ -1480,6 +1756,18 @@ find_tags(
vim_memset(&search_info, 0, (size_t)1);
#endif
+#ifdef FEAT_EVAL
+ 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;
+ }
+#endif
+
/*
* 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
@@ -2308,7 +2596,6 @@ parse_line:
}
else
{
-#define TAG_SEP 0x02
size_t tag_fname_len = STRLEN(tag_fname);
#ifdef FEAT_EMACS_TAGS
size_t ebuf_len = 0;
@@ -2577,8 +2864,7 @@ free_tag_stuff(void)
tag_freematch();
# if defined(FEAT_QUICKFIX)
- if (ptag_entry.tagname)
- VIM_CLEAR(ptag_entry.tagname);
+ tagstack_clear_entry(&ptag_entry);
# endif
}
#endif
@@ -2940,6 +3226,7 @@ parse_match(
tagp);
tagp->tagkind = NULL;
+ tagp->user_data = NULL;
tagp->command_end = NULL;
if (retval == OK)
@@ -2957,17 +3244,15 @@ 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;
p = pt + 1;
@@ -2980,6 +3265,13 @@ 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;
}
@@ -3547,6 +3839,16 @@ find_extra(char_u **pp)
return FAIL;
}
+/*
+ * Free a single entry in a tag stack
+ */
+ static void
+tagstack_clear_entry(taggy_T *item)
+{
+ VIM_CLEAR(item->tagname);
+ VIM_CLEAR(item->user_data);
+}
+
#if defined(FEAT_CMDL_COMPL) || defined(PROTO)
int
expand_tags(
@@ -3568,11 +3870,11 @@ expand_tags(
tagnmflag = 0;
if (pat[0] == '/')
ret = find_tags(pat + 1, num_file, file,
- TAG_REGEXP | tagnmflag | TAG_VERBOSE,
+ 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_REGEXP | tagnmflag | TAG_VERBOSE | TAG_NO_TAGFUNC | TAG_NOIC,
TAG_MANY, curbuf->b_ffname);
if (ret == OK && !tagnames)
{
@@ -3753,6 +4055,8 @@ get_tag_details(taggy_T *tag, dict_T *retdict)
dict_add_string(retdict, "tagname", tag->tagname);
dict_add_number(retdict, "matchnr", tag->cur_match + 1);
dict_add_number(retdict, "bufnr", tag->cur_fnum);
+ if (tag->user_data)
+ dict_add_string(retdict, "user_data", tag->user_data);
if ((pos = list_alloc_id(aid_tagstack_from)) == NULL)
return;
@@ -3805,7 +4109,7 @@ tagstack_clear(win_T *wp)
// Free the current tag stack
for (i = 0; i < wp->w_tagstacklen; ++i)
- vim_free(wp->w_tagstack[i].tagname);
+ tagstack_clear_entry(&wp->w_tagstack[i]);
wp->w_tagstacklen = 0;
wp->w_tagstackidx = 0;
}
@@ -3820,7 +4124,7 @@ tagstack_shift(win_T *wp)
taggy_T *tagstack = wp->w_tagstack;
int i;
- vim_free(tagstack[0].tagname);
+ tagstack_clear_entry(&tagstack[0]);
for (i = 1; i < wp->w_tagstacklen; ++i)
tagstack[i - 1] = tagstack[i];
wp->w_tagstacklen--;
@@ -3836,7 +4140,8 @@ 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
@@ -3856,6 +4161,7 @@ tagstack_push_item(
tagstack[idx].cur_match = 0;
tagstack[idx].fmark.mark = mark;
tagstack[idx].fmark.fnum = fnum;
+ tagstack[idx].user_data = user_data;
}
/*
@@ -3892,7 +4198,8 @@ tagstack_push_items(win_T *wp, list_T *l)
tagstack_push_item(wp, tagname,
(int)dict_get_number(itemdict, (char_u *)"bufnr"),
(int)dict_get_number(itemdict, (char_u *)"matchnr") - 1,
- mark, fnum);
+ mark, fnum,
+ dict_get_string(itemdict, (char_u *)"user_data", TRUE));
}
}
@@ -3920,6 +4227,15 @@ set_tagstack(win_T *wp, dict_T *d, int action)
dictitem_T *di;
list_T *l;
+#ifdef FEAT_EVAL
+ // not allowed to alter the tag stack entries from inside tagfunc
+ if (tfu_in_use)
+ {
+ emsg(_(recurmsg));
+ return FAIL;
+ }
+#endif
+
if ((di = dict_find(d, (char_u *)"items", -1)) != NULL)
{
if (di->di_tv.v_type != VAR_LIST)