On 20-Apr-2010 Lech Lorens <[email protected]> wrote:
> On 18-Apr-2010 Robert Webb <[email protected]> wrote:
> > Hi guys,
> [...]
> > If anyone tries my script, let me know how it goes! :-)
> > Rob.
>
> I was about to get to implementing the 'tagfunc' option so I will gladly
> volunteer for this task. But first, I'll adjust your script to make it
> work for C as well.
Here's an implementation of the 'tagfunc' option together with some
documentation. Additionally, attached is a script showing how a (very
unuseful) function used in 'tagfunc' can be implemented.
Two open questions:
- left a FIXME in the code stating that duplicate tags should be removed
from the list returned by the user-defined function. Not quite sure,
though, whether it wouldn't be better to leave it to the user.
- maybe it would make some sense to let taglist() return a result
returned by the 'tagfunc' function? taglist() could take an optional
argument which would state whether recursion is allowed and an upper
limit on the recursion level could be set. I can at least imagine
a use case for such a feature (admittedly, awkward a bit).
Any comments welcome.
--
Cheers,
Lech
--
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
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index e178613..e92deee 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -6691,6 +6691,22 @@ A jump table for the options with a short description
can be found at |Q_op|.
command-line completion and ":help").
{Vi: always uses binary search in some versions}
+ *'tagfunc'* *'tfu'*
+'tagfunc' 'tfu' string (default: empty)
+ local to buffer
+ {not in Vi}
+ {not available when compiled without the +eval
+ or +insert_expand feature}
+ This option specifies a function to be used to perform tag searches.
+ The function should take two parameters, the first of which is the
+ pattern to be searched; while the second is:
+ - 0 - if the command causing the function to be evaluated comes from
+ the command line,
+ - 1 - if the function is evaluated because a normal-mode command was
+ executed.
+ See |tag-function| for an explanation of what the function should
+ return and an example of such a function.
+
*'taglength'* *'tl'*
'taglength' 'tl' number (default 0)
global
diff --git a/runtime/doc/tagsrch.txt b/runtime/doc/tagsrch.txt
index 15ebbd4..2415ef5 100644
--- a/runtime/doc/tagsrch.txt
+++ b/runtime/doc/tagsrch.txt
@@ -14,6 +14,7 @@ See section |29.1| of the user manual for an introduction.
4. Tags details |tag-details|
5. Tags file format |tags-file-format|
6. Include file searches |include-search|
+7. Programmable tag search |tag-function|
==============================================================================
1. Jump to a tag *tag-commands*
@@ -834,4 +835,60 @@ Common arguments for the commands above:
< For a ":djump", ":dsplit", ":dlist" and ":dsearch" command the pattern
is used as a literal string, not as a search pattern.
+==============================================================================
+7. Programmable tag search *tag-function*
+
+It is possible to provide Vim with a script which will generate a list of tags
+used for commands like |:tag|, |:tselect| and normal mode commands like
+|CTRL-]|. The Vim script function used for generating the taglist is specified
+by setting the 'tagfunc' option. The function will be called with two
+arguments:
+ a:pattern - the tag identifier used during the tag search,
+ a:flags - a list of flags to control the function behaviour.
+
+Currently the only flag that may be passed to the tag function is 'c' which
+indicates that the function was invoked due to 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.
+
+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.
+
+The function should return a list of dictionaries. Each of the dictionaries
+must at least include the following entries:
+ 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, a line number or
+ a line number followed by a byte number.
+Note that the format of the result is similar to that of |taglist()|,
+which makes it possible to use its output to generate the result.
+
+
+The following is a hypothetical example of a function used for 'tagfunc'. It
+uses the output of |taglist()| to generate the result: each of the tags that
+uses a line number as the ex command is shifted by an offset of 3 lines from
+its actual location.
+
+>
+ function! TagFunc(pattern, at_cursor)
+ let result = taglist('\<' . a:pattern . '\>')
+ for i in range(len(result))
+ if result[i]['cmd'] =~ '^\d\+'
+ " Create an irritating offset of 3 lines from the actual location
+ " of the tag:
+ let lnum = substitute(result[i]['cmd'], '^\(\d\+\).*', '\1', '')
+ let rest = substitute(result[i]['cmd'], '^\d\+\(.*\)', '\1', '')
+ let result[i]['cmd'] = (lnum + 3) . rest
+ endif
+ endfor
+
+ return result
+ endfunc
+ set tagfunc=TagFunc
+<
+
vim:tw=78:ts=8:ft=help:norl:
diff --git a/src/buffer.c b/src/buffer.c
index 0569f16..e86899e 100644
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -1801,6 +1801,7 @@ free_buf_options(buf, free_p_ff)
#ifdef FEAT_COMPL_FUNC
clear_string_option(&buf->b_p_cfu);
clear_string_option(&buf->b_p_ofu);
+ clear_string_option(&buf->b_p_tfu);
#endif
#ifdef FEAT_QUICKFIX
clear_string_option(&buf->b_p_gp);
diff --git a/src/edit.c b/src/edit.c
index 33e580f..8cb7bc0 100644
--- a/src/edit.c
+++ b/src/edit.c
@@ -4050,6 +4050,7 @@ ins_compl_get_exp(ini)
/* Find up to TAG_MANY matches. Avoids that an enormous number
* of matches is found when compl_pattern is empty */
+ g_tag_at_cursor = 1;
if (find_tags(compl_pattern, &num_matches, &matches,
TAG_REGEXP | TAG_NAMES | TAG_NOIC |
TAG_INS_COMP | (ctrl_x_mode ? TAG_VERBOSE : 0),
@@ -4057,6 +4058,7 @@ ins_compl_get_exp(ini)
{
ins_compl_add_matches(num_matches, matches, p_ic);
}
+ g_tag_at_cursor = 0;
p_ic = save_p_ic;
break;
diff --git a/src/ex_cmds.c b/src/ex_cmds.c
index 2296c33..b847fd5 100644
--- a/src/ex_cmds.c
+++ b/src/ex_cmds.c
@@ -5926,7 +5926,7 @@ find_help_tags(arg, num_matches, matches, keep_lang)
*matches = (char_u **)"";
*num_matches = 0;
- flags = TAG_HELP | TAG_REGEXP | TAG_NAMES | TAG_VERBOSE;
+ flags = TAG_HELP | TAG_REGEXP | TAG_NAMES | TAG_VERBOSE | TAG_DONT_USE_TFU;
if (keep_lang)
flags |= TAG_KEEP_LANG;
if (find_tags(IObuff, num_matches, matches, flags, (int)MAXCOL, NULL) == OK
diff --git a/src/globals.h b/src/globals.h
index bfe48ca..d137d73 100644
--- a/src/globals.h
+++ b/src/globals.h
@@ -1066,6 +1066,9 @@ EXTERN int g_do_tagpreview INIT(= 0); /* for tag
preview commands:
height of preview window */
# endif
#endif
+EXTERN int g_tag_at_cursor INIT(= 0); /* 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\\\"|");
diff --git a/src/normal.c b/src/normal.c
index 8b9fea7..5eb19c2 100644
--- a/src/normal.c
+++ b/src/normal.c
@@ -5616,7 +5616,11 @@ nv_ident(cap)
normal_search(cap, cmdchar == '*' ? '/' : '?', buf, 0);
}
else
+ {
+ g_tag_at_cursor = 1;
do_cmdline_cmd(buf);
+ g_tag_at_cursor = 0;
+ }
vim_free(buf);
}
diff --git a/src/option.c b/src/option.c
index ba17c11..cfa2383 100644
--- a/src/option.c
+++ b/src/option.c
@@ -172,6 +172,9 @@
#endif
#define PV_SW OPT_BUF(BV_SW)
#define PV_SWF OPT_BUF(BV_SWF)
+#ifdef FEAT_COMPL_FUNC
+# define PV_TFU OPT_BUF(BV_TFU)
+#endif
#define PV_TAGS OPT_BOTH(OPT_BUF(BV_TAGS))
#define PV_TS OPT_BUF(BV_TS)
#define PV_TW OPT_BUF(BV_TW)
@@ -288,6 +291,7 @@ static char_u *p_cpt;
#ifdef FEAT_COMPL_FUNC
static char_u *p_cfu;
static char_u *p_ofu;
+static char_u *p_tfu;
#endif
static int p_eol;
static int p_et;
@@ -2432,6 +2436,15 @@ static struct vimoption
{(char_u *)TRUE, (char_u *)0L}
#endif
SCRIPTID_INIT},
+ {"tagfunc", "tfu", P_STRING|P_ALLOCED|P_VI_DEF|P_SECURE,
+#ifdef FEAT_COMPL_FUNC
+ (char_u *)&p_tfu, PV_TFU,
+ {(char_u *)"", (char_u *)0L}
+#else
+ (char_u *)NULL, PV_NONE,
+ {(char_u *)0L, (char_u *)0L}
+#endif
+ SCRIPTID_INIT},
{"taglength", "tl", P_NUM|P_VI_DEF,
(char_u *)&p_tl, PV_NONE,
{(char_u *)0L, (char_u *)0L} SCRIPTID_INIT},
@@ -5174,6 +5187,7 @@ check_buf_options(buf)
#ifdef FEAT_COMPL_FUNC
check_string_option(&buf->b_p_cfu);
check_string_option(&buf->b_p_ofu);
+ check_string_option(&buf->b_p_tfu);
#endif
#ifdef FEAT_KEYMAP
check_string_option(&buf->b_p_keymap);
@@ -9265,6 +9279,7 @@ get_varp(p)
#ifdef FEAT_COMPL_FUNC
case PV_CFU: return (char_u *)&(curbuf->b_p_cfu);
case PV_OFU: return (char_u *)&(curbuf->b_p_ofu);
+ case PV_TFU: return (char_u *)&(curbuf->b_p_tfu);
#endif
case PV_EOL: return (char_u *)&(curbuf->b_p_eol);
case PV_ET: return (char_u *)&(curbuf->b_p_et);
@@ -9604,6 +9619,7 @@ buf_copy_options(buf, flags)
#ifdef FEAT_COMPL_FUNC
buf->b_p_cfu = vim_strsave(p_cfu);
buf->b_p_ofu = vim_strsave(p_ofu);
+ buf->b_p_tfu = vim_strsave(p_tfu);
#endif
buf->b_p_sts = p_sts;
buf->b_p_sts_nopaste = p_sts_nopaste;
diff --git a/src/option.h b/src/option.h
index cfa7692..793febc 100644
--- a/src/option.h
+++ b/src/option.h
@@ -999,6 +999,9 @@ enum
#endif
, BV_SW
, BV_SWF
+#ifdef FEAT_COMPL_FUNC
+ , BV_TFU
+#endif
, BV_TAGS
, BV_TS
, BV_TW
diff --git a/src/structs.h b/src/structs.h
index 99afecf..1688202 100644
--- a/src/structs.h
+++ b/src/structs.h
@@ -1359,6 +1359,7 @@ struct file_buffer
#ifdef FEAT_COMPL_FUNC
char_u *b_p_cfu; /* 'completefunc' */
char_u *b_p_ofu; /* 'omnifunc' */
+ char_u *b_p_tfu; /* 'tagfunc' */
#endif
int b_p_eol; /* 'endofline' */
int b_p_et; /* 'expandtab' */
diff --git a/src/tag.c b/src/tag.c
index ba36de7..88d2739 100644
--- a/src/tag.c
+++ b/src/tag.c
@@ -30,7 +30,8 @@ typedef struct tag_pointers
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 */
+ 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
@@ -164,6 +165,7 @@ do_tag(tag, type, count, forceit, verbose)
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;
@@ -188,6 +190,7 @@ do_tag(tag, type, count, forceit, verbose)
{
type = DT_TAG;
no_regexp = TRUE;
+ use_tfu = 0;
}
prev_num_matches = num_matches;
@@ -542,6 +545,9 @@ do_tag(tag, type, count, forceit, verbose)
#endif
if (verbose)
flags |= TAG_VERBOSE;
+ if (!use_tfu)
+ flags |= TAG_DONT_USE_TFU;
+
if (find_tags(name, &new_num_matches, &new_matches, flags,
max_num_matches, buf_ffname) == OK
&& new_num_matches < max_num_matches)
@@ -1233,6 +1239,125 @@ prepare_pats(pats, has_re)
pats->regmatch.regprog = NULL;
}
+struct match_found
+{
+ int len; /* nr of chars of match[] to be
compared */
+ char_u match[1]; /* actually longer */
+};
+
+/*
+ * find_tfu_tags() - 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, FAIL otherwise.
+ * pat - used as the pattern supplied to the user-defined function,
+ * ga - the tags will be placed here,
+ * match_count - here the number of tags found will be placed.
+ */
+ static int
+find_tfu_tags(char_u *pat, garray_T *ga, int *match_count)
+{
+ pos_T pos;
+ list_T *taglist;
+ listitem_T *item;
+ int ntags = 0;
+ int result = FAIL;
+ char_u *args[2];
+
+ static int call_level = 0;
+
+ args[0] = pat;
+ args[1] = (char_u *) (g_tag_at_cursor? "c": "");
+
+ /* Prevent endless loop: */
+ ++call_level;
+ if (call_level > 1)
+ goto done;
+
+ if (*curbuf->b_p_tfu == NUL)
+ goto done;
+
+ pos = curwin->w_cursor;
+ taglist = call_func_retlist(curbuf->b_p_tfu, 2, args, FALSE);
+ curwin->w_cursor = pos; /* restore the cursor position */
+
+ if (taglist == NULL)
+ goto done;
+
+ for (item = taglist->lv_first; item != NULL; item = item->li_next)
+ {
+ struct match_found *mfp;
+ char_u *res_name, *res_fname, *res_cmd;
+ int len;
+
+ if (item->li_tv.v_type != VAR_DICT)
+ continue;
+
+ res_name = get_dict_string(item->li_tv.vval.v_dict,
+ (char_u *)"name", FALSE);
+ res_fname = get_dict_string(item->li_tv.vval.v_dict,
+ (char_u *)"filename", FALSE);
+ res_cmd = get_dict_string(item->li_tv.vval.v_dict,
+ (char_u *)"cmd", FALSE);
+
+ if (!res_name || !res_fname || !res_cmd)
+ continue;
+
+#ifdef FEAT_EMACS_TAGS
+ len = 3;
+#else
+ len = 2;
+#endif
+
+ len += STRLEN(res_name);
+ len += STRLEN(res_fname);
+ len += STRLEN(res_cmd);
+
+ len += 3; /* we only use 3 fields from the dictionary - we need space
+ * for two gaps and a terminating NUL. */
+
+ mfp = (struct match_found *)alloc(
+ (int)sizeof(struct match_found) + len);
+ if (mfp != NULL)
+ {
+ char_u *p;
+ mfp->len = len;
+ p = mfp->match;
+ p[0] = 0; /* mtt */
+ p[1] = NUL; /* no tag file name */
+ p = p + 2;
+#ifdef FEAT_EMACS_TAGS
+ *p = NUL;
+ ++p;
+#endif
+
+ STRCPY(p, res_name);
+ p += STRLEN(p);
+ *p++ = TAB;
+ STRCPY(p, res_fname);
+ p += STRLEN(p);
+ *p++ = TAB;
+ STRCPY(p, res_cmd);
+
+ /* FIXME:2010-04-24:llorens: Don't add identical matches. */
+ if (ga_grow(ga, 1) == OK)
+ {
+ ((struct match_found **)(ga->ga_data)) [ga->ga_len++] = mfp;
+ ++ntags;
+ result = OK;
+ }
+ else
+ vim_free(mfp);
+ }
+ }
+
+ list_free(taglist, TRUE);
+done:
+ --call_level;
+ *match_count = ntags;
+ return result;
+}
+
/*
* find_tags() - search for tags in tags files
*
@@ -1252,11 +1377,12 @@ prepare_pats(pats, has_re)
* Tags in an emacs-style tags file are always global.
*
* flags:
- * TAG_HELP only search for help tags
- * TAG_NAMES only return name of tag
- * TAG_REGEXP use "pat" as a regexp
- * TAG_NOIC don't always ignore case
- * TAG_KEEP_LANG keep language
+ * TAG_HELP only search for help tags
+ * TAG_NAMES only return name of tag
+ * TAG_REGEXP use "pat" as a regexp
+ * TAG_NOIC don't always ignore case
+ * TAG_KEEP_LANG keep language
+ * TAG_DONT_USE_TFU do not invoke the 'tagfunc' command
*/
int
find_tags(pat, num_matches, matchesp, flags, mincount, buf_ffname)
@@ -1335,11 +1461,7 @@ find_tags(pat, num_matches, matchesp, flags, mincount,
buf_ffname)
int is_etag; /* current file is emaces style
*/
#endif
- struct match_found
- {
- int len; /* nr of chars of match[] to be compared */
- char_u match[1]; /* actually longer */
- } *mfp, *mfp2;
+ struct match_found *mfp, *mfp2;
garray_T ga_match[MT_COUNT];
int match_count = 0; /* number of matches
found */
char_u **matches;
@@ -1380,6 +1502,7 @@ find_tags(pat, num_matches, matchesp, flags, mincount,
buf_ffname)
int use_cscope = (flags & TAG_CSCOPE);
#endif
int verbose = (flags & TAG_VERBOSE);
+ int use_tfu = ((flags & TAG_DONT_USE_TFU) == 0);
help_save = curbuf->b_help;
orgpat.pat = pat;
@@ -1448,6 +1571,12 @@ find_tags(pat, num_matches, matchesp, flags, mincount,
buf_ffname)
vim_memset(&search_info, 0, (size_t)1);
#endif
+ if (use_tfu)
+ {
+ retval = find_tfu_tags(pat, &ga_match[0], &match_count);
+ goto findtag_end;
+ }
+
/*
* 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
@@ -3738,11 +3867,11 @@ expand_tags(tagnames, pat, num_file, file)
tagnmflag = 0;
if (pat[0] == '/')
ret = find_tags(pat + 1, num_file, file,
- TAG_REGEXP | tagnmflag | TAG_VERBOSE,
+ TAG_REGEXP | tagnmflag | TAG_VERBOSE | TAG_DONT_USE_TFU,
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_DONT_USE_TFU |
TAG_NOIC,
TAG_MANY, curbuf->b_ffname);
if (ret == OK && !tagnames)
{
@@ -3817,7 +3946,8 @@ get_tags(list, pat)
long is_static;
ret = find_tags(pat, &num_matches, &matches,
- TAG_REGEXP | TAG_NOIC, (int)MAXCOL, NULL);
+ TAG_REGEXP | TAG_NOIC | TAG_DONT_USE_TFU,
+ (int)MAXCOL, NULL);
if (ret == OK && num_matches > 0)
{
for (i = 0; i < num_matches; ++i)
diff --git a/src/vim.h b/src/vim.h
index 9a36b9b..6882e91 100644
--- a/src/vim.h
+++ b/src/vim.h
@@ -1045,19 +1045,20 @@ extern char *(*dyn_libintl_textdomain)(const char
*domainname);
/*
* 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 */
+#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 */
+# 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_VERBOSE 32 /* message verbosity */
+#define TAG_INS_COMP 64 /* Currently doing insert completion */
+#define TAG_KEEP_LANG 128 /* keep current language */
+#define TAG_DONT_USE_TFU 256 /* whether 'tagfunc' should be
evaluated */
-#define TAG_MANY 300 /* When finding many tags (for completion),
- find up to this many tags */
+#define TAG_MANY 300 /* When finding many tags (for
completion),
+ find up to this many tags */
/*
* Types of dialogs passed to do_vim_dialog().
" This function basically shows that it is possible to control the tags used
" by Vim and shows what the format of the result should be.
function! TagFunc1(pattern, flags)
let result = [
\ {'name': 'blah', 'filename': 'vim.h', 'cmd': '1'},
\ {'name': 'bleh', 'filename': 'vim.h', 'cmd': '2'},
\ {'name': 'blih', 'filename': 'vim.h', 'cmd': '3'}
\]
echomsg 'Searching for pattern "' . a:pattern . '".'
if a:flags =~? 'c'
echomsg 'Tag command executed at line ' . line('.') . '.'
else
echomsg 'Tag command executed from command line.'
endif
return result
endfunc
" This demonstrates how the output of taglist() can be used to generate the
" output of the tag function:
function! TagFunc2(pattern, flags)
let result = taglist('\<' . a:pattern . '\>')
for i in range(len(result))
if result[i]['cmd'] =~ '^\d\+'
" Create an irritating offset of 3 lines from the
actual location
" of the tag:
let lnum = substitute(result[i]['cmd'], '^\(\d\+\).*',
'\1', '')
let rest = substitute(result[i]['cmd'], '^\d\+\(.*\)',
'\1', '')
let result[i]['cmd'] = (lnum + 3) . rest
endif
endfor
return result
endfunc
set tagfunc=TagFunc2
set nocscopetag
" vim: tabindent
" vim: ts=4 sta noet