From 45e18cbdc40afd8144d20dcc07ad2d981636f4c9 Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Sun, 28 Apr 2019 18:05:35 +0200 Subject: 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 #4010) --- src/tag.c | 378 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 347 insertions(+), 31 deletions(-) (limited to 'src/tag.c') 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) -- cgit v1.2.3