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:
<Tab>{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:
<Tab>{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 <SID>BinOptionG("tgst", &tgst)
call append("$", "showfulltag\twhen completing tags in Insert mode show more
info")
call <SID>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 <SID>OptionL("tfu")
+ endif
if has("cscope")
call append("$", "cscopeprg\tcommand for executing cscope")
call <SID>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! \<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
*** ../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 -- [email protected] -- 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 ///
--
--
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php
---
You received this message because you are subscribed to the Google Groups
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
For more options, visit https://groups.google.com/d/optout.