Background
This weekend I thought I’d try my hand at Rust. As always, first came some yak shaving,
specifically in the form of Vim configuration. This turned out to be relatively straightforward
right up until I tried to automatically generate ctags using Gutentags, like I do for all my,
other projects.
Initial Configuration
Based on some snippets I found in this GitHub issue I ended up with the following config in my .vimrc
:
" Setup gutentags to use rusty-tags
if !exists("g:gutentags_project_info")
let g:gutentags_project_info = []
endif
call add(g:gutentags_project_info, {'type': 'rust', 'file': 'Cargo.toml'})
let g:gutentags_ctags_executable_rust = $HOME.'/.vim/shims/rusttags.sh'
Where $HOME.'/.vim/shims/rusttags.sh
contained:
#!/bin/bash
rusty-tags vi && mv rusty-tags.vi tags.temp
Running this script manually would successfully create a tags.temp
file in my Rust project root.
The Problem
I kept getting the following error in my Vim command line:
gutentags: ctags job failed, returned: 1
I ran the following in Vim to enable Gutentags tracing and trigger an update:
:call gutentags#toggletrace()
:GutentagsUpdate
This resulted in the following trace being dumped to the Vim command line:
From this, I could see that the command Gutentags was using to generate my tags was:
/home/tl/src/rcfiles/vim/.vim/bundle/vim-gutentags/plat/unix/update_tags.sh \
-e '/home/tl/.vim/shims/rusttags.sh' \
-t '/home/tl/.cache/gutentags/home-tl-src-foo-tags' \
-p '/home/tl/src/foo' \
-L 'rg --files --hidden --follow --glob "\!.git/*"' \
-x '@/home/tl/.cache/gutentags/_wildignore.options' \
-l '/home/tl/.cache/gutentags/home-tl-src-foo-tags.log'
Running this command in my shell directly yielded the following output:
Locking tags file...
Running file list command, patching for absolute paths
eval rg --files --hidden --follow --glob "\!.git/*"
Running ctags on whole project
/home/tl/.vim/shims/rusttags.sh -f "/home/tl/.cache/gutentags/home-tl-src-foo-tags.temp" --exclude=@/home/tl/.cache/gutentags/_wildignore.options -L /home/tl/.cache/gutentags/home-tl-src-foo-tags.files
Fetching source and metadata ...
Creating tags for: ["foo"] ...
Replacing tags file
mv -f "/home/tl/.cache/gutentags/home-tl-src-foo-tags.temp" "/home/tl/.cache/gutentags/home-tl-src-foo-tags"
mv: cannot stat '/home/tl/.cache/gutentags/home-tl-src-foo-tags.temp': No such file or directory
After some digging I found that this was due to g:gutentags_cache_dir
being set to
$HOME.'/.cache/gutentags'
in my .vimrc
, while the script to generate the tags was
assuming a default Gutentags config which generates tags within the current working directory.
The Solution
Make ~/.vim/shims/rusttags.sh
smarter:
#!/bin/sh
_tmp=`getopt -o f: --long options:,exclude: -n "rusttags" -- "$@"`
eval set -- "$_tmp"
while true; do
case "$1" in
-f ) _output_file="${2}"; shift 2 ;;
--options ) shift 2 ;;
--exclude ) shift 2 ;;
* ) break ;;
esac
done
if [ ! -n "${_output_file}" ]; then
echo "-f option not provided"
exit 1
fi
echo "-f set to: ${_output_file}"
rusty-tags --output "${_output_file}" vi
This makes the script accept -f
, storing the value in $_output_file
.
Running the script again correctly generates takes in g:gutentags_cache_dir
:
Locking tags file...
Running ctags on whole project
/home/tl/.vim/shims/rusttags.sh -f "/home/tl/.cache/gutentags/home-tl-src-foo-tags.temp" --options=/home/tl/src/rcfiles/vim/.vim/bundle/vim-gutentags/res/ctags_recursive.options --exclude=@/home/tl/.cache/gutentags/_wildignore.options /home/tl/srcfoo
-f set to: /home/tl/.cache/gutentags/home-tl-src-foo-tags.temp
Fetching source and metadata ...
Creating tags for: ["foo"] ...
Replacing tags file
mv -f "/home/tl/.cache/gutentags/home-tl-src-foo-tags.temp" "/home/tl/.cache/gutentags/home-tl-src-foo-tags"
Unlocking tags file...
Done.
Caveats
This depends on the ordering that Gutentags currently sends these options in. This order is almost
certainly not guaranteed, and if it changes (or adds a new option that rusttags.sh
doesn’t
support, the script could hit the * ) break ;;
case before -f
is handled, leaving you with
broken tag generation.
This also requires GNU getopt
, which if you’re running a Mac you will have to install manually.