diff src/tag.c @ 16447:54ffc82f38a8 v8.1.1228

patch 8.1.1228: not possible to process tags with a function commit https://github.com/vim/vim/commit/45e18cbdc40afd8144d20dcc07ad2d981636f4c9 Author: Bram Moolenaar <Bram@vim.org> Date: Sun Apr 28 18:05:35 2019 +0200 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 https://github.com/vim/vim/issues/4010)
author Bram Moolenaar <Bram@vim.org>
date Sun, 28 Apr 2019 18:15:07 +0200
parents 3d6b282e2d6e
children 826ad48af5e3
line wrap: on
line diff
--- 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, 
 #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;	/* n
  * 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 *re
     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 a
     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)