Modern JavaScript ctags configuration
The other day I was surfing around for a solution for this problem and came across Ray Grasso’s excellent post from April of 2015. I recommend reading the post for his solution; I’ve just modified it a bit. He also links to his dotfiles which include updates since the content of the post.
About ctags
Many years ago I became familiar with the basic ctags
functionality in vim
. But ctags
itself is much more flexible and can be consumed by most editors. I highly encourage y’all to check out the ctags
website, as well as plugins for popular editors such as vscode, atom, and sublime text.
Essentially, ctags
will scan all files you ask it to and look for what it understands to be symbol definitions. These are classes, functions, variable names, and so on. ctags
has a large list of known languages, but the JavaScript support hasn’t yet been the greatest.
To start, make sure you have exuberant-ctags
installed, as opposed to regular ctags. You can brew install ctags
on a macOS machine, for example, which will install the latest version of exuberant-ctags
.
Inside vim
Generating ctags
I use ctags
to jump to the definition of a symbol in a given project. There are generally two ways I achieve this: from a cursor over the top of a symbol and using normal-mode commands to search the list of defined tags.
Jumping to a definition under the cursor
While reading code and the cursor is on top of a known symbol, pressing Ctrl+]
will jump to that definition.
Searching the available tags
If you know the tag you want but are nowhere near it, you can :tag <tagname>
to jump to it’s definition. The :tag
command supports tab completion; I often use :tag
searches to jump to class or constant definitions.
Navigating through multiple matches
Either of these ways of jumping to definition may result in multiple tag matches. You can navigate the tag stack with :tnext
(briefly, as :tn
) or :tprevious
(:tp
). If you’d like to see a list of definitions found for any given tag, you can :tselect
(:tsel
).
Including other packages in your generated tags file
Although it can take a prohibitively long time based on the size of your dependency graph, I have found it useful in some cases to include my node_modules
directory in my tags
file. Whereas I usually just include basic directories like app
, config
, and lib
, there are times when I’ll want to inspect the source of a module in the same session as my application’s code. One example would be when I’m using a library module for the first time and I’m unsure how things are working under the covers, or why what I expect to happen just isn’t.
Your new .ctags file
After a bit of pruning and studying, this is the JavaScript-specific portion of my ~/.ctags
file looks like this:
--exclude=node_modules
--exclude=gulp--languages=-javascript--langdef=js
--langmap=js:.js
--langmap=js:+.jsx--regex-js=/[ \t.]([A-Z][A-Z0-9._$]+)[ \t]*[=:][ \t]*([0-9"'\[\{]|null)/\1/n,constant/--regex-js=/\.([A-Za-z0-9._$]+)[ \t]*=[ \t]*\{/\1/o,object/
--regex-js=/['"]*([A-Za-z0-9_$]+)['"]*[ \t]*:[ \t]*\{/\1/o,object/
--regex-js=/([A-Za-z0-9._$]+)\[["']([A-Za-z0-9_$]+)["']\][ \t]*=[ \t]*\{/\1\.\2/o,object/--regex-js=/([A-Za-z0-9._$]+)[ \t]*=[ \t]*\(function\(\)/\1/c,class/
--regex-js=/['"]*([A-Za-z0-9_$]+)['"]*:[ \t]*\(function\(\)/\1/c,class/
--regex-js=/class[ \t]+([A-Za-z0-9._$]+)[ \t]*/\1/c,class/
--regex-js=/([A-Za-z$][A-Za-z0-9_$()]+)[ \t]*=[ \t]*[Rr]eact.createClass[ \t]*\(/\1/c,class/
--regex-js=/([A-Z][A-Za-z0-9_$]+)[ \t]*=[ \t]*[A-Za-z0-9_$]*[ \t]*[{(]/\1/c,class/
--regex-js=/([A-Z][A-Za-z0-9_$]+)[ \t]*:[ \t]*[A-Za-z0-9_$]*[ \t]*[{(]/\1/c,class/--regex-js=/([A-Za-z$][A-Za-z0-9_$]+)[ \t]*=[ \t]*function[ \t]*\(/\1/f,function/--regex-js=/(function)*[ \t]*([A-Za-z$_][A-Za-z0-9_$]+)[ \t]*\([^)]*\)[ \t]*\{/\2/f,function/
--regex-js=/['"]*([A-Za-z$][A-Za-z0-9_$]+)['"]*:[ \t]*function[ \t]*\(/\1/m,method/
--regex-js=/([A-Za-z0-9_$]+)\[["']([A-Za-z0-9_$]+)["']\][ \t]*=[ \t]*function[ \t]*\(/\2/m,method/--langdef=typescript
--langmap=typescript:.ts
--regex-typescript=/^[ \t]*(export)?[ \t]*class[ \t]+([a-zA-Z0-9_]+)/\2/c,classes/
--regex-typescript=/^[ \t]*(export)?[ \t]*module[ \t]+([a-zA-Z0-9_]+)/\2/n,modules/
--regex-typescript=/^[ \t]*(export)?[ \t]*function[ \t]+([a-zA-Z0-9_]+)/\2/f,functions/
--regex-typescript=/^[ \t]*export[ \t]+var[ \t]+([a-zA-Z0-9_]+)/\1/v,variables/
--regex-typescript=/^[ \t]*var[ \t]+([a-zA-Z0-9_]+)[ \t]*=[ \t]*function[ \t]*\(\)/\1/v,varlambdas/
--regex-typescript=/^[ \t]*(export)?[ \t]*(public|private)[ \t]+(static)?[ \t]*([a-zA-Z0-9_]+)/\4/m,members/
--regex-typescript=/^[ \t]*(export)?[ \t]*interface[ \t]+([a-zA-Z0-9_]+)/\2/i,interfaces/
--regex-typescript=/^[ \t]*(export)?[ \t]*enum[ \t]+([a-zA-Z0-9_]+)/\2/e,enums/
--regex-typescript=/^[ \t]*import[ \t]+([a-zA-Z0-9_]+)/\1/I,imports/
Wrapping it up into an alias
Finally, and following Mr. Grasso’s example, let’s get rid of some over-greedy matching and wrap it up into a nice alias:
alias jtags=”ctags -R app config lib && sed -i ‘’ -E ‘/^(if|switch|function|module\.exports|it|describe).+language:js$/d’ tags”
Happy vimming!