block by tmcw 5abaddf7d9fa1aa38d486a929a0cc8fe

Mediocre vim code intelligence with ctags

Though I love vim, it has one pretty big flaw: it doesn’t know JavaScript. There are systems that know JavaScript pretty well, like all of Visual Studio Code’s code intelligence features. I’ve tried all of them: languageserver, tern, typescript server… and they’ve all broken my heart. And then I went back to the opposite extreme: no autocomplete, no fancy stuff, not even the supertab plugin. Now I’ve settled in the middle, and am loving mediocre vim. The combination is:

ctags

Ctags is a beautifully ugly system, with extreme performance and simplicity. Just enough intelligence to pluck out the exports and declarations in your files to make jump to definition work mostly. It’s basically a set of regular expressions that tries to find keywords and then puts them in a file that your editor can use as an index.

brew install ctags-exuberant

Next, you want to generate those tags. Doing it manually is annoying. There’s a great plugin, gutentags, that does it automatically, and just works:

In your vim-plug dependencies:

Plug 'ludovicchabant/vim-gutentags'

Next, you want to make those tags better, so configure ctags. This means a ~/.ctags file to configure. Here’s my configuration:

--exclude=.git
--exclude=log
--exclude=tmp
--exclude=node_modules

--languages=-javascript
--langdef=js
--langmap=js:.js

--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-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]*\{/\1/f,function/
--regex-js=/^[ \t]*(export)?[ \t]*function[ \t]+([a-zA-Z0-9_]+)/\2/f,functions/
--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/
--regex-js=/^[ \t]*(export)?[ \t]*class[ \t]+([a-zA-Z0-9_]+)/\2/c,classes/
--regex-js=/^[ \t]*(export)?[ \t]*module[ \t]+([a-zA-Z0-9_]+)/\2/n,modules/
--regex-js=/^[ \t]*export[ \t]+(var|let|const)[ \t]+([a-zA-Z0-9_]+)/\2/v,variables/
--regex-js=/^[ \t]*(var|let|const)[ \t]+([a-zA-Z0-9_]+)[ \t]*=[ \t]*function[ \t]*\(\)/\2/v,varlambdas/
--regex-js=/^[ \t]*(export)?[ \t]*(public|private)[ \t]+(static)?[ \t]*([a-zA-Z0-9_]+)/\4/m,members/
--regex-js=/^[ \t]*(export)?[ \t]*interface[ \t]+([a-zA-Z0-9_]+)/\2/i,interfaces/
--regex-js=/^[ \t]*(export)?[ \t]*enum[ \t]+([a-zA-Z0-9_]+)/\2/e,enums/

Then, you can navigate tags by putting your cursor over something and hitting Ctrl-] to navigate to its definition.

Then, you can navigate to any tag, just like you’d navigate to any file, using Fzf:

In your vim-plug dependencies:

Plug 'junegunn/fzf', { 'dir': '~/.fzf', 'do': './install --all' }
Plug 'junegunn/fzf.vim'

In vim configuration:

nnoremap <Leader>t :Tags<CR>

Voila! A simple, debuggable, non-magic setup but it’s practical as all hell. I adapted the .ctags configuration from a modern ctags configuration. I dropped some of the typescript bits and added better ES6 support.