New Tags Engine for JED
At work I have a lot of programs built from a large list of files distributed
almost randomly in a couple of directories. So every time I'm browsing the
sources (mainly because I'm looking for a weird bug) I use to spend a lot
of time looking where some funny-named functions are defined. This seems
like a work for ctags, but the standard ctags.sl was unable to handle
the brain-damaged output from the SCO ctags program. For a while I used
a slightly patched version, but after some time I got
Exuberant ctags and decided to
write my own version of JED tags support.
The main features of this version are:
- Support for multiple tag files.
- Support for more than one occurrence of the same tag.
- It keeps a stack of positions visited, so you can navigate c source
files a bit like lynx.
- It does a better work when trying to find the right destination (it's
less confused by prototypes), or at least I hope so!
- It uses, if available, the extended flags written by Exuberant ctags (the
default ctags program found in most Linux distributions). This allows
it to prefer function bodies instead of prototypes, to search for static
functions only in the current file, and other clever things.
- Some extra funny functions to play with (intellijed(), intellijed_insert()
,intellipointer(), whatis())
- Far more customisable, see comments in ntags.sl.
- Automatic reloading of modified tags files. Support for this feature
is provided by the bufmake.sl script.
It Has also some drawbacks:
- Doesn't support emacs style tags. (I don't use them, but should be easy
to add).
- May be slower and uses more memory.
You can download all the needed files here:
ntags.tar.gz
How to use all this.
- Place the files somewhere.
- Add that 'somewhere' to the jed library path (use
set_jed_library_path(get_jed_library_path() + "," + "/some/where/dir");
)
- Eval the autoload.sl (maybe in ~/.jedrc).
- Bind some key to find_tag() (the standard binding
is ESC+'.', note that I kept the same name of the old ctags, so it should
simply happen).
- Bind some other key to ntags_back() (I use ESC+',').
You may find useful some bindings for walk_forward() and ntags_next_tag()
(ESC+'m' and ESC+'k' are my choices). Note that ntags_next_tag()
will be called automagically if you try to follow the last followed
tag from the last found line. (simply press again ESC+'.', and you will cycle between
all the occurences of the current tag). If you set NTags_Never_Ask, you
may wish to bind ntags_ask() to a key (ESC+'a' works for me).
How it works.
The file walk.sl implements a stack of positions. It allows going
backward and forward on this stack, opening files, buffers and windows as
needed (this is like info.sl navigation, as I discovered later).
There are 2 configuration variables you can adjust:
- Walk_Size (Default = 32): The stack maximum size. I Think the default
value is more than adequate, if you happen to need a bigger value
sometimes, please let me know.
(this limit is more a safety measure than an implementation limit, it can
easily be done as a linked list)
- Walk_Use_Current_Window (Default = 1): if == 0, it uses pop2buf()
when switching files (this is what the old ctags used to do), if == 1,
it uses sw2buf(), showing the destination file in the same window
as the old file (I prefer to have only one big window, and browse through
sources more or less like lynx).
Note that this file doesn't depend on ntags, so it may be used by other
scripts as well. I use it also in followgrep.sl (see the
followgrep page)
and in my very own version of tokenlist.sl, in Others.
The behaviour of ntags.sl file may be tuned by changing the following
variables:
- Tags_Files (Default = "tags"): A comma-separated list of tags files.
- NTags_Follow_Now (Default = 1): If the tag under the cursor exists,
don't ask the user, but follow it.
- NTags_Never_Ask (Default = 1): If the tag under the cursor doesn't
exists, don't ask the user for a tag, but fail.
- NTags_Look_From_End (Default = 1): Search for the tag pattern starting
at the end of file, instead of the beginning. For some coding styles,
this may help to find the right destination, if the prototypes are
before function bodies (which seems logical).
- NTags_Case_Sensitive (Default = 1): Use case sensitive search (set
to 0 ('zero') for case insensitive, or to -1 to use the current
CASE_SEARCH value).
- NTags_Save_Pos_On_Next (Default = 0): A stupid option. If set, an entry
on the walk stack will be saved every time you search the next matching tag.
Leave it to zero (if you don't understand why, try setting it and play a
bit...)
- NTags_Add_Path_To_Tag (Default = 0): Add full path to tags files when
loaded. This slows down the tags loading a lot, so is better to use the -p
option of ctags (on ctags 5.0 the option -p was removed, you should pass
the full path on command line to get it encoded on tags file. This is a
pain on old unices with limited environment sizes).
To set these variables, and maybe do other things depending where you are,
you may use my project.sl, look at Project
handling.
When looking for a tag, it scans the tags buffer, assigning a score to every
matching line. The line with the greatest score is chosen. The score is
calculated using the extended information (if available), and comparing the
source an destination files. If you want to play with the score calculation,
take a look at the psyco_heuristics() function.
Goodies.
wordsrch.sl implements some whole-word searching routines. (some
are used by ntags.sl and cfun.sl, so it's here).
cfun.sl contains some fun functions. Try binding '(' to intellijed()
, (or, if you like the special effects, to intellijed_insert()).
These functions try to extract the function definition from the
tag line, and shows it with message() (using intellijed())
or insert it in your buffer (intellijed_insert()). All this may work
or may not, depending on how you write the code (and how the tags file is
built). Works well if you write all the function definition in one line,
as in:
int one_good_function(int x, char *str)
{
bla bla;
return 0;
}
But not if is written as:
int one_bad_function(int x,
char *str)
{
more bla;
return 1;
}
So, this should work for some jed source files, but not for gnu indentation style :))).
The intellipointer() function tries to guess the type of the variable
under the cursor, and follow the type as a tag. suppose you have written:
int func(AStructType *a)
{
int x;
x = a->
But you don't remember the field name you need here. No problem, invoke
the intellipointer() function (bind this function to a key, I use
Ctrl-N). If you are lucky, you'll be warped to the file and position where
this type is defined. This works only if the tags files cover the type
definition and the variable is either a parameter to the current function
or a local variable (and even when these conditions are met, it may not work,
but most times it will).
The whatis() function was not written by me, but by the first user
of my slang scripts, Maurizio Zanello, a friend which works with me.
It searches the definition of the variable under the cursor, and shows it
as a message().
Todo
- etags support (the code from ctags.sl, with a bit of tweaking,
should suffice)
- Something like tagrelative ViM variable (this is hard, as is retrieving
any information tags_file related, as these files are packed together while
loading).
- Add a fuzzy search when the line defining the tag has changed.
- Score calculation needs some tuning (The extended information put by
Exuberant ctags allows really nice things: for example, I may score better
structure members if the current tag is after a '->' or '.', but right now
it's only used to choose classes first, then function definitions and
prototypes last).
I was thinking about rewriting all this using associative arrays instead of
a buffer to store the tags. A more or less working version (plenty of bugs
here) is in the New Technology Tags Engine. Grab my
entire configuration directory tree, and look at ntte.sl file.
Back to home page.
Last Updated : Thu Nov 8 20:54:04 CET 2001