To: vim_dev@googlegroups.com Subject: Patch 8.1.1228 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 8.1.1228 Problem: Not possible to process tags with a function. Solution: Add tagfunc() (Christian Brabandt, Andy Massimino, closes #4010) Files: runtime/doc/options.txt, runtime/doc/tagsrch.txt, runtime/optwin.vim, src/buffer.c, src/dict.c, src/ex_cmds.c, src/globals.h, src/insexpand.c, src/normal.c, src/option.c, src/option.h, src/proto/dict.pro, src/structs.h, src/tag.c, src/testdir/Make_all.mak, src/testdir/test_alot.vim, src/testdir/test_tagfunc.vim, src/vim.h, src/window.c *** ../vim-8.1.1227/runtime/doc/options.txt 2019-04-28 16:00:05.367613425 +0200 --- runtime/doc/options.txt 2019-04-28 16:53:38.066933372 +0200 *************** *** 7458,7463 **** --- 7458,7473 ---- NOTE: This option is set to the Vi default value when 'compatible' is set and to the Vim default value when 'compatible' is reset. + *'tagfunc'* *'tfu'* + 'tagfunc' 'tfu' string (default: empty) + local to buffer + {not available when compiled without the |+eval| + feature} + This option specifies a function to be used to perform tag searches. + The function gets the tag pattern and should return a List of matching + tags. See |tag-function| for an explanation of how to write the + function and an example. + *'taglength'* *'tl'* 'taglength' 'tl' number (default 0) global *** ../vim-8.1.1227/runtime/doc/tagsrch.txt 2019-03-30 21:19:16.426170240 +0100 --- runtime/doc/tagsrch.txt 2019-04-28 17:43:27.976705157 +0200 *************** *** 14,19 **** --- 14,20 ---- 4. Tags details |tag-details| 5. Tags file format |tags-file-format| 6. Include file searches |include-search| + 7. Using 'tagfunc' |tag-function| ============================================================================== 1. Jump to a tag *tag-commands* *************** *** 179,186 **** 1 1 main 1 harddisk2:text/vim/test 2 1 FuncB 59 harddisk2:text/vim/src/main.c ! The gettagstack() function returns the tag stack of a specified window. The ! settagstack() function modifies the tag stack of a window. *E73* When you try to use the tag stack while it doesn't contain anything you will --- 180,187 ---- 1 1 main 1 harddisk2:text/vim/test 2 1 FuncB 59 harddisk2:text/vim/src/main.c ! The |gettagstack()| function returns the tag stack of a specified window. The ! |settagstack()| function modifies the tag stack of a window. *E73* When you try to use the tag stack while it doesn't contain anything you will *************** *** 570,576 **** the bar) and ;" is used to have Vi ignore the rest of the line. Example: APP file.c call cursor(3, 4)|;" v ! {field} .. A list of optional fields. Each field has the form: {fieldname}:{value} --- 571,577 ---- the bar) and ;" is used to have Vi ignore the rest of the line. Example: APP file.c call cursor(3, 4)|;" v ! {field} .. A list of optional fields. Each field has the form: {fieldname}:{value} *************** *** 591,596 **** --- 592,598 ---- The only other field currently recognized by Vim is "file:" (with an empty value). It is used for a static tag. + The first lines in the tags file can contain lines that start with !_TAG_ These are sorted to the first lines, only rare tags that start with "!" can *************** *** 870,873 **** < For a ":djump", ":dsplit", ":dlist" and ":dsearch" command the pattern is used as a literal string, not as a search pattern. ! vim:tw=78:ts=8:ft=help:norl: --- 872,941 ---- < For a ":djump", ":dsplit", ":dlist" and ":dsearch" command the pattern is used as a literal string, not as a search pattern. ! ============================================================================== ! 7. Using 'tagfunc' *tag-function* ! ! It is possible to provide Vim with a function which will generate a list of ! tags used for commands like |:tag|, |:tselect| and Normal mode tag commands ! like |CTRL-]|. ! ! The function used for generating the taglist is specified by setting the ! 'tagfunc' option. The function will be called with three arguments: ! a:pattern The tag identifier used during the tag search. ! a:flags List of flags to control the function behavior. ! a:info Dict containing the following entries: ! buf_ffname Full filename which can be used for priority. ! user_data Custom data String, if stored in the tag ! stack previously by tagfunc. ! ! Currently two flags may be passed to the tag function: ! 'c' The function was invoked by a normal command being processed ! (mnemonic: the tag function may use the context around the ! cursor to perform a better job of generating the tag list.) ! 'i' In Insert mode, the user was completing a tag (with ! |i_CTRL-X_CTRL-]|). ! ! Note that when 'tagfunc' is set, the priority of the tags described in ! |tag-priority| does not apply. Instead, the priority is exactly as the ! ordering of the elements in the list returned by the function. ! *E987* ! The function should return a List of Dict entries. Each Dict must at least ! include the following entries and each value must be a string: ! name Name of the tag. ! filename Name of the file where the tag is defined. It is ! either relative to the current directory or a full path. ! cmd Ex command used to locate the tag in the file. This ! can be either an Ex search pattern or a line number. ! Note that the format is similar to that of |taglist()|, which makes it possible ! to use its output to generate the result. ! The following fields are optional: ! kind Type of the tag. ! user_data String of custom data stored in the tag stack which ! can be used to disambiguate tags between operations. ! ! If the function returns |v:null| instead of a List, a standard tag lookup will ! be performed instead. ! ! It is not allowed to change the tagstack from inside 'tagfunc'. *E986* ! ! The following is a hypothetical example of a function used for 'tagfunc'. It ! uses the output of |taglist()| to generate the result: a list of tags in the ! inverse order of file names. ! > ! function! TagFunc(pattern, flags, info) ! function! CompareFilenames(item1, item2) ! let f1 = a:item1['filename'] ! let f2 = a:item2['filename'] ! return f1 >=# f2 ? ! \ -1 : f1 <=# f2 ? 1 : 0 ! endfunction ! ! let result = taglist(a:pattern) ! call sort(result, "CompareFilenames") ! ! return result ! endfunc ! set tagfunc=TagFunc ! < ! ! vim:tw=78:ts=8:noet:ft=help:norl: *** ../vim-8.1.1227/runtime/optwin.vim 2019-02-17 17:53:46.681219289 +0100 --- runtime/optwin.vim 2019-04-28 16:47:48.252732139 +0200 *************** *** 300,305 **** --- 300,310 ---- call BinOptionG("tgst", &tgst) call append("$", "showfulltag\twhen completing tags in Insert mode show more info") call BinOptionG("sft", &sft) + if has("eval") + call append("$", "tagfunc\ta function to be used to perform tag searches") + call append("$", "\t(local to buffer)") + call OptionL("tfu") + endif if has("cscope") call append("$", "cscopeprg\tcommand for executing cscope") call OptionG("csprg", &csprg) *** ../vim-8.1.1227/src/buffer.c 2019-04-27 13:03:20.000715982 +0200 --- src/buffer.c 2019-04-28 16:47:48.252732139 +0200 *************** *** 2219,2224 **** --- 2219,2227 ---- clear_string_option(&buf->b_p_path); clear_string_option(&buf->b_p_tags); clear_string_option(&buf->b_p_tc); + #ifdef FEAT_EVAL + clear_string_option(&buf->b_p_tfu); + #endif #ifdef FEAT_INS_EXPAND clear_string_option(&buf->b_p_dict); clear_string_option(&buf->b_p_tsr); *** ../vim-8.1.1227/src/dict.c 2019-04-08 18:15:36.464223229 +0200 --- src/dict.c 2019-04-28 17:48:48.959179176 +0200 *************** *** 449,454 **** --- 449,503 ---- } /* + * Initializes "iter" for iterating over dictionary items with + * dict_iterate_next(). + * If "var" is not a Dict or an empty Dict then there will be nothing to + * iterate over, no error is given. + * NOTE: The dictionary must not change until iterating is finished! + */ + void + dict_iterate_start(typval_T *var, dict_iterator_T *iter) + { + if (var->v_type != VAR_DICT || var->vval.v_dict == NULL) + iter->dit_todo = 0; + else + { + dict_T *d = var->vval.v_dict; + + iter->dit_todo = d->dv_hashtab.ht_used; + iter->dit_hi = d->dv_hashtab.ht_array; + } + } + + /* + * Iterate over the items referred to by "iter". It should be initialized with + * dict_iterate_start(). + * Returns a pointer to the key. + * "*tv_result" is set to point to the value for that key. + * If there are no more items, NULL is returned. + */ + char_u * + dict_iterate_next(dict_iterator_T *iter, typval_T **tv_result) + { + dictitem_T *di; + char_u *result; + + if (iter->dit_todo == 0) + return NULL; + + while (HASHITEM_EMPTY(iter->dit_hi)) + ++iter->dit_hi; + + di = HI2DI(iter->dit_hi); + result = di->di_key; + *tv_result = &di->di_tv; + + --iter->dit_todo; + ++iter->dit_hi; + return result; + } + + /* * Add a dict entry to dictionary "d". * Returns FAIL when out of memory and when key already exists. */ *** ../vim-8.1.1227/src/ex_cmds.c 2019-03-30 18:46:57.344077426 +0100 --- src/ex_cmds.c 2019-04-28 17:16:35.452393443 +0200 *************** *** 6813,6819 **** *matches = (char_u **)""; *num_matches = 0; ! flags = TAG_HELP | TAG_REGEXP | TAG_NAMES | TAG_VERBOSE; if (keep_lang) flags |= TAG_KEEP_LANG; if (find_tags(IObuff, num_matches, matches, flags, (int)MAXCOL, NULL) == OK --- 6813,6819 ---- *matches = (char_u **)""; *num_matches = 0; ! flags = TAG_HELP | TAG_REGEXP | TAG_NAMES | TAG_VERBOSE | TAG_NO_TAGFUNC; if (keep_lang) flags |= TAG_KEEP_LANG; if (find_tags(IObuff, num_matches, matches, flags, (int)MAXCOL, NULL) == OK *** ../vim-8.1.1227/src/globals.h 2019-04-20 23:38:02.189504258 +0200 --- src/globals.h 2019-04-28 17:21:49.430854857 +0200 *************** *** 1067,1075 **** EXTERN int postponed_split_flags INIT(= 0); /* args for win_split() */ EXTERN int postponed_split_tab INIT(= 0); /* cmdmod.tab */ #ifdef FEAT_QUICKFIX ! EXTERN int g_do_tagpreview INIT(= 0); /* for tag preview commands: ! height of preview window */ #endif EXTERN int replace_offset INIT(= 0); /* offset for replace_push() */ EXTERN char_u *escape_chars INIT(= (char_u *)" \t\\\"|"); --- 1067,1079 ---- EXTERN int postponed_split_flags INIT(= 0); /* args for win_split() */ EXTERN int postponed_split_tab INIT(= 0); /* cmdmod.tab */ #ifdef FEAT_QUICKFIX ! EXTERN int g_do_tagpreview INIT(= 0); // for tag preview commands: ! // height of preview window #endif + 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\\\"|"); *** ../vim-8.1.1227/src/insexpand.c 2019-04-08 18:15:36.464223229 +0200 --- src/insexpand.c 2019-04-28 17:21:29.878951043 +0200 *************** *** 2654,2664 **** --- 2654,2666 ---- // 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 | (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; *** ../vim-8.1.1227/src/normal.c 2019-03-30 18:46:57.356077354 +0100 --- src/normal.c 2019-04-28 17:21:37.030915863 +0200 *************** *** 5724,5730 **** --- 5724,5734 ---- (void)normal_search(cap, cmdchar == '*' ? '/' : '?', buf, 0); } else + { + g_tag_at_cursor = TRUE; do_cmdline_cmd(buf); + g_tag_at_cursor = FALSE; + } vim_free(buf); } *** ../vim-8.1.1227/src/option.c 2019-04-27 22:06:33.348200718 +0200 --- src/option.c 2019-04-28 16:47:48.256732118 +0200 *************** *** 167,172 **** --- 167,175 ---- #endif #define PV_SW OPT_BUF(BV_SW) #define PV_SWF OPT_BUF(BV_SWF) + #ifdef FEAT_EVAL + # define PV_TFU OPT_BUF(BV_TFU) + #endif #define PV_TAGS OPT_BOTH(OPT_BUF(BV_TAGS)) #define PV_TC OPT_BOTH(OPT_BUF(BV_TC)) #define PV_TS OPT_BUF(BV_TS) *************** *** 303,308 **** --- 306,314 ---- static char_u *p_cfu; static char_u *p_ofu; #endif + #ifdef FEAT_EVAL + static char_u *p_tfu; + #endif static int p_eol; static int p_fixeol; static int p_et; *************** *** 2642,2647 **** --- 2648,2662 ---- {"tagcase", "tc", P_STRING|P_VIM, (char_u *)&p_tc, PV_TC, {(char_u *)"followic", (char_u *)"followic"} SCTX_INIT}, + {"tagfunc", "tfu", P_STRING|P_ALLOCED|P_VI_DEF|P_SECURE, + #ifdef FEAT_EVAL + (char_u *)&p_tfu, PV_TFU, + {(char_u *)"", (char_u *)0L} + #else + (char_u *)NULL, PV_NONE, + {(char_u *)0L, (char_u *)0L} + #endif + SCTX_INIT}, {"taglength", "tl", P_NUM|P_VI_DEF, (char_u *)&p_tl, PV_NONE, {(char_u *)0L, (char_u *)0L} SCTX_INIT}, *************** *** 5689,5694 **** --- 5704,5712 ---- check_string_option(&buf->b_p_cfu); check_string_option(&buf->b_p_ofu); #endif + #ifdef FEAT_EVAL + check_string_option(&buf->b_p_tfu); + #endif #ifdef FEAT_KEYMAP check_string_option(&buf->b_p_keymap); #endif *************** *** 10944,10949 **** --- 10962,10970 ---- case PV_CFU: return (char_u *)&(curbuf->b_p_cfu); case PV_OFU: return (char_u *)&(curbuf->b_p_ofu); #endif + #ifdef FEAT_EVAL + case PV_TFU: return (char_u *)&(curbuf->b_p_tfu); + #endif case PV_EOL: return (char_u *)&(curbuf->b_p_eol); case PV_FIXEOL: return (char_u *)&(curbuf->b_p_fixeol); case PV_ET: return (char_u *)&(curbuf->b_p_et); *************** *** 11332,11337 **** --- 11353,11361 ---- buf->b_p_cfu = vim_strsave(p_cfu); buf->b_p_ofu = vim_strsave(p_ofu); #endif + #ifdef FEAT_EVAL + buf->b_p_tfu = vim_strsave(p_tfu); + #endif buf->b_p_sts = p_sts; buf->b_p_sts_nopaste = p_sts_nopaste; #ifdef FEAT_VARTABS *** ../vim-8.1.1227/src/option.h 2019-03-02 10:13:36.796974835 +0100 --- src/option.h 2019-04-28 16:47:48.256732118 +0200 *************** *** 1068,1073 **** --- 1068,1076 ---- #endif , BV_SW , BV_SWF + #ifdef FEAT_EVAL + , BV_TFU + #endif , BV_TAGS , BV_TC , BV_TS *** ../vim-8.1.1227/src/proto/dict.pro 2019-04-08 18:15:36.472223190 +0200 --- src/proto/dict.pro 2019-04-28 17:11:15.121943721 +0200 *************** *** 18,23 **** --- 18,25 ---- int dict_add_string(dict_T *d, char *key, char_u *str); int dict_add_string_len(dict_T *d, char *key, char_u *str, int len); int dict_add_list(dict_T *d, char *key, list_T *list); + void dict_iterate_start(typval_T *var, dict_iterator_T *iter); + char_u *dict_iterate_next(dict_iterator_T *iter, typval_T **tv_result); int dict_add_dict(dict_T *d, char *key, dict_T *dict); long dict_len(dict_T *d); dictitem_T *dict_find(dict_T *d, char_u *key, int len); *** ../vim-8.1.1227/src/structs.h 2019-04-27 20:36:52.534303564 +0200 --- src/structs.h 2019-04-28 17:31:05.364108835 +0200 *************** *** 147,156 **** */ 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 */ } taggy_T; /* --- 147,157 ---- */ 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 *user_data; // used with tagfunc } taggy_T; /* *************** *** 1885,1890 **** --- 1886,1901 ---- struct list_stack_S *prev; } list_stack_T; + /* + * Structure used for iterating over dictionary items. + * Initialize with dict_iterate_start(). + */ + typedef struct + { + long_u dit_todo; + hashitem_T *dit_hi; + } dict_iterator_T; + /* values for b_syn_spell: what to do with toplevel text */ #define SYNSPL_DEFAULT 0 /* spell check if @Spell not defined */ #define SYNSPL_TOP 1 /* spell check toplevel text */ *************** *** 2245,2250 **** --- 2256,2264 ---- char_u *b_p_cfu; /* 'completefunc' */ char_u *b_p_ofu; /* 'omnifunc' */ #endif + #ifdef FEAT_EVAL + char_u *b_p_tfu; /* 'tagfunc' */ + #endif int b_p_eol; /* 'endofline' */ int b_p_fixeol; /* 'fixendofline' */ int b_p_et; /* 'expandtab' */ *** ../vim-8.1.1227/src/tag.c 2019-04-21 00:00:07.942354840 +0200 --- src/tag.c 2019-04-28 17:44:56.956284972 +0200 *************** *** 18,37 **** */ 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 */ #ifdef FEAT_EMACS_TAGS ! int is_etag; /* TRUE for emacs tag */ #endif ! char_u *tagkind; /* "kind:" value */ ! char_u *tagkind_end; /* end of tagkind */ } tagptrs_T; /* --- 18,40 ---- */ 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. This is used ! // when 'tr' is set. #ifdef FEAT_EMACS_TAGS ! int is_etag; // TRUE for emacs tag #endif ! 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,86 **** --- 81,94 ---- #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,97 **** * 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}; #endif /* * Jump to tag; handling of tag commands and tag stack * --- 97,112 ---- * 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, 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,149 **** --- 159,165 ---- 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,156 **** --- 167,180 ---- 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,173 **** --- 192,198 ---- { type = DT_TAG; no_regexp = TRUE; + use_tfu = 0; } prev_num_matches = num_matches; *************** *** 187,193 **** #if defined(FEAT_QUICKFIX) if (g_do_tagpreview != 0) { ! vim_free(ptag_entry.tagname); if ((ptag_entry.tagname = vim_strsave(tag)) == NULL) goto end_do_tag; } --- 212,218 ---- #if defined(FEAT_QUICKFIX) if (g_do_tagpreview != 0) { ! tagstack_clear_entry(&ptag_entry); if ((ptag_entry.tagname = vim_strsave(tag)) == NULL) goto end_do_tag; } *************** *** 226,232 **** } else { ! vim_free(ptag_entry.tagname); if ((ptag_entry.tagname = vim_strsave(tag)) == NULL) goto end_do_tag; } --- 251,257 ---- } else { ! tagstack_clear_entry(&ptag_entry); if ((ptag_entry.tagname = vim_strsave(tag)) == NULL) goto end_do_tag; } *************** *** 239,251 **** * stack entries above it. */ while (tagstackidx < tagstacklen) ! vim_free(tagstack[--tagstacklen].tagname); /* if the tagstack is full: remove oldest entry */ if (++tagstacklen > TAGSTACKSIZE) { tagstacklen = TAGSTACKSIZE; ! vim_free(tagstack[0].tagname); for (i = 1; i < tagstacklen; ++i) tagstack[i - 1] = tagstack[i]; --tagstackidx; --- 264,276 ---- * stack entries above it. */ while (tagstackidx < tagstacklen) ! tagstack_clear_entry(&tagstack[--tagstacklen]); /* if the tagstack is full: remove oldest entry */ if (++tagstacklen > TAGSTACKSIZE) { tagstacklen = TAGSTACKSIZE; ! tagstack_clear_entry(&tagstack[0]); for (i = 1; i < tagstacklen; ++i) tagstack[i - 1] = tagstack[i]; --tagstackidx; *************** *** 529,534 **** --- 554,563 ---- #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,654 **** --- 676,695 ---- } 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,1248 **** --- 1284,1520 ---- 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,1273 **** --- 1540,1546 ---- * 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,1390 **** --- 1658,1666 ---- 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,1485 **** --- 1756,1773 ---- 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,2314 **** } else { - #define TAG_SEP 0x02 size_t tag_fname_len = STRLEN(tag_fname); #ifdef FEAT_EMACS_TAGS size_t ebuf_len = 0; --- 2596,2601 ---- *************** *** 2577,2584 **** tag_freematch(); # if defined(FEAT_QUICKFIX) ! if (ptag_entry.tagname) ! VIM_CLEAR(ptag_entry.tagname); # endif } #endif --- 2864,2870 ---- tag_freematch(); # if defined(FEAT_QUICKFIX) ! tagstack_clear_entry(&ptag_entry); # endif } #endif *************** *** 2940,2945 **** --- 3226,3232 ---- tagp); tagp->tagkind = NULL; + tagp->user_data = NULL; tagp->command_end = NULL; if (retval == OK) *************** *** 2957,2973 **** while (ASCII_ISALPHA(*p)) { if (STRNCMP(p, "kind:", 5) == 0) - { tagp->tagkind = p + 5; 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; --- 3244,3258 ---- 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; if (pt == NULL) break; p = pt + 1; *************** *** 2980,2985 **** --- 3265,3277 ---- ; 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,3552 **** --- 3839,3854 ---- 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,3578 **** tagnmflag = 0; if (pat[0] == '/') ret = find_tags(pat + 1, num_file, file, ! TAG_REGEXP | tagnmflag | TAG_VERBOSE, 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); if (ret == OK && !tagnames) { --- 3870,3880 ---- tagnmflag = 0; if (pat[0] == '/') ret = find_tags(pat + 1, num_file, file, ! 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_NO_TAGFUNC | TAG_NOIC, TAG_MANY, curbuf->b_ffname); if (ret == OK && !tagnames) { *************** *** 3753,3758 **** --- 4055,4062 ---- 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,3811 **** // Free the current tag stack for (i = 0; i < wp->w_tagstacklen; ++i) ! vim_free(wp->w_tagstack[i].tagname); wp->w_tagstacklen = 0; wp->w_tagstackidx = 0; } --- 4109,4115 ---- // Free the current tag stack for (i = 0; i < wp->w_tagstacklen; ++i) ! tagstack_clear_entry(&wp->w_tagstack[i]); wp->w_tagstacklen = 0; wp->w_tagstackidx = 0; } *************** *** 3820,3826 **** taggy_T *tagstack = wp->w_tagstack; int i; ! vim_free(tagstack[0].tagname); for (i = 1; i < wp->w_tagstacklen; ++i) tagstack[i - 1] = tagstack[i]; wp->w_tagstacklen--; --- 4124,4130 ---- taggy_T *tagstack = wp->w_tagstack; int i; ! tagstack_clear_entry(&tagstack[0]); for (i = 1; i < wp->w_tagstacklen; ++i) tagstack[i - 1] = tagstack[i]; wp->w_tagstacklen--; *************** *** 3836,3842 **** int cur_fnum, int cur_match, pos_T mark, ! int fnum) { taggy_T *tagstack = wp->w_tagstack; int idx = wp->w_tagstacklen; // top of the stack --- 4140,4147 ---- int cur_fnum, int cur_match, pos_T mark, ! int fnum, ! char_u *user_data) { taggy_T *tagstack = wp->w_tagstack; int idx = wp->w_tagstacklen; // top of the stack *************** *** 3856,3861 **** --- 4161,4167 ---- tagstack[idx].cur_match = 0; tagstack[idx].fmark.mark = mark; tagstack[idx].fmark.fnum = fnum; + tagstack[idx].user_data = user_data; } /* *************** *** 3892,3898 **** tagstack_push_item(wp, tagname, (int)dict_get_number(itemdict, (char_u *)"bufnr"), (int)dict_get_number(itemdict, (char_u *)"matchnr") - 1, ! mark, fnum); } } --- 4198,4205 ---- tagstack_push_item(wp, tagname, (int)dict_get_number(itemdict, (char_u *)"bufnr"), (int)dict_get_number(itemdict, (char_u *)"matchnr") - 1, ! mark, fnum, ! dict_get_string(itemdict, (char_u *)"user_data", TRUE)); } } *************** *** 3920,3925 **** --- 4227,4241 ---- 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) *** ../vim-8.1.1227/src/testdir/Make_all.mak 2019-04-27 18:00:29.851064563 +0200 --- src/testdir/Make_all.mak 2019-04-28 16:47:48.260732097 +0200 *************** *** 244,249 **** --- 244,250 ---- test_tabline \ test_tabpage \ test_tagcase \ + test_tagfunc \ test_tagjump \ test_taglist \ test_tcl \ *** ../vim-8.1.1227/src/testdir/test_alot.vim 2019-03-02 06:41:34.345330494 +0100 --- src/testdir/test_alot.vim 2019-04-28 16:47:48.260732097 +0200 *************** *** 60,65 **** --- 60,66 ---- 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_timers.vim *** ../vim-8.1.1227/src/testdir/test_tagfunc.vim 2019-04-28 18:02:57.627069396 +0200 --- src/testdir/test_tagfunc.vim 2019-04-28 17:48:10.655362588 +0200 *************** *** 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! \" + 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\\" + 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 *** ../vim-8.1.1227/src/vim.h 2019-04-08 18:15:36.472223190 +0200 --- src/vim.h 2019-04-28 17:18:43.131769441 +0200 *************** *** 1133,1151 **** /* * 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 */ #ifdef FEAT_CSCOPE ! # define TAG_CSCOPE 16 /* cscope tag */ #endif ! #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_MANY 300 /* When finding many tags (for completion), ! find up to this many tags */ /* * Types of dialogs passed to do_vim_dialog(). --- 1133,1152 ---- /* * 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 #ifdef FEAT_CSCOPE ! # define TAG_CSCOPE 16 // cscope tag #endif ! #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 /* * Types of dialogs passed to do_vim_dialog(). *** ../vim-8.1.1227/src/window.c 2019-04-27 20:36:52.534303564 +0200 --- src/window.c 2019-04-28 16:47:48.260732097 +0200 *************** *** 1326,1335 **** /* 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); } newp->w_tagstackidx = oldp->w_tagstackidx; newp->w_tagstacklen = oldp->w_tagstacklen; --- 1326,1337 ---- /* copy tagstack and folds */ for (i = 0; i < oldp->w_tagstacklen; i++) { ! 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; *** ../vim-8.1.1227/src/version.c 2019-04-28 16:08:26.813234002 +0200 --- src/version.c 2019-04-28 18:03:04.703034923 +0200 *************** *** 769,770 **** --- 769,772 ---- { /* Add new patch number below this line */ + /**/ + 1228, /**/ -- Courtroom Quote #19: Q: Doctor, how many autopsies have you performed on dead people? A: All my autopsies have been performed on dead people. /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ \\\ \\\ an exciting new programming language -- http://www.Zimbu.org /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///