My vim setup – with some rust specifities

So, I’ve got this thing called a sick leave due ti my depression. It means I have a lot of time to do whatever I want.
It includes writing stuff here, making the garden and wait for plant to grow, spend countless hours on Crusader Kings 2 et Europa Universalis 4 (my life is now gone), and to do computer related things that I wanted to do.
So, among things such as setup a pam-ldap configuration and documentations for a reset project, I’ve started learning Rust. Mainly because it is interesting. It’s nice and good, but it’s a bit hard, lots of concepts are different form my habits with Python. Anyway, I wanted to have a little help provided by context in my vim7 setup.

My previous config did worked quite well for python, but there was a lot of heavy duty plugins on opening files, which led me to have to wait some seconds when initially starting vim. And this is where my quest began. I wanted to do some things from vim (I never really bothered on compiling tasks or ctags for instance, since python or bash do not require them). I spent most of my last week to understanding vim, reading vimscript, finding plugins etc.

The first step was to get a vim version which would allow for background jobs. Either vim8 or neovim then. I switched over to neovim. Because you have to pick one first, also because there’s a lot of things who looks promising (I mean, running a tmux-session in a pane next to your code editor is kind of cool. I think.). Installing in the latest debian is straightforward. I did take the time to use an alternatives for vim, to replace all the vim/vi invocation by nvim (and without messing with the package manager)

# update-alternatives --display vim
vim - manual mode
  link best version is /usr/bin/vim.nox
  link currently points to /usr/bin/nvim
  link vim is /usr/bin/vim
/usr/bin/nvim - priority 30
/usr/bin/vim.basic - priority 30
/usr/bin/vim.nox - priority 40

The next step is the plugin manager to use, and to find some goals to achieve for my setup.

    Things to keep in mind for my personal use

  1. I’m not using tabs. Mostly because I’m not using the mouse in vim (and I use it exclusively from inside a tmux-session or from a tty, not from the gvim functionnality)
  2. I tend to <C-z> a lot while editing files. It’s a bad habit, but when I try to fix a system with not a lot of config, I must restart/reload, have several man pages, logs and lots of buffers opened. The least I need to and do something from inside vim, the better.
  3. I want access to inline documentations of a function, without needing to open a browser or anexternal program, ideally it should not go over my windows
  4. Autocomplete for the win. But it must be configurable and not to hard on the system. Also, async. And must be easy to add sources of completion.
  5. Autopair and jump are good (I want to be able to reach end if or else quickly for instance, or to close all parenthesis I open
  6. I will not install more code engine than what I already have or need to work with my code, so I want most of my plugins to work straight from neovimscripts
  7. It must stay out of my way, but give me feedback on the state of my code, test and git repository.
  8. I do not care about compatibility mode, I do not think I’ll touch a strict vi during what’s left of my life

So, let’s roll.

Plugin managers

You have to pick one plugin manager or maintain by hand your own stack of dependencies. You probably don’t want to do that. I used Vundle in the past, but I found vim-plug much more interesting, especially with all the conditions for plugins to load, keeping the overhead smaller. All you need to install it is on their homepage, so, I assume that there’s no issue for you since you’re editing you nvim config.

Small enhancements

Let’s create a bit of structures in the config directory. Also, it is in sync on a git repo.
Mostly, an init.vim file, a plugged directory and a ftplugin one

$ ls -l
drwxr-xr-x    - okhin  2 août  19:14 ftplugin
.rw-r--r-- 5,3k okhin  2 août  22:24 init.vim
drwxr-xr-x    - okhin  2 août   1:27 plugged
.rw-r--r-- 3,2k okhin 28 juil.  1:49

$ vim .config/nvim/init.vim

Black background, save before making, support hidden buffers:

" I'm using a dark background, so go for it
set background=dark
" Hidden buffers please
set hidden
" Let's shorten messages a bit
set shortmess+=c
" Unicode
set encoding=utf-8
" Numbers
set nu

I do use a lot of splits. And when you add the preview one, the quickfix and location ones as well as the help, you’re moving a lot between windows. <C-W> tend to be not great when I’m going back and forth, so I mapped some shorter stroke to go between windows.

" Shorter split moves
nnoremap <C-j> <C-w><C-j>
nnoremap <C-k> <C-w><C-k>
nnoremap <C-h> <C-w><C-h>
nnoremap <C-l> <C-w><C-l>

And then, indent and tabulations rules. Those are the one I use for python, but I’m quite happy with them, So I do standardize them. If I need exceptions, I can overrides them in a ftplugin file.

" Default indent
set tabstop=4
set softtabstop=4
set shiftwidth=4
set expandtab
set autoindent
set fileformat=unix

Since I’m going to do a lot of autocomplete, I might as well setup some options. Preview window are nice, especially with Snippets. And since I want to type less, let’s complete with the longest part possible.

" I want preview window for autocomplete and a menu even for
" just one match
set completeopt+=preview
set completeopt+=menuone
set completeopt+=longest

I tend to quit with :q. Which is nice, but, in the case some special windows exists vim won’t quit and I’ll need to close them manually. I can still use :qa, but old habits die hard, so I found a way to close those windows.

The first one catch the ##QuitPre event and silently close location and quickfix if they’re the only one (with a filetype of qf). The preview window is closed after completion if the menu is not visible.

" Let's close the location and the quickfix window if it's the last one
if exists('##QuitPre')
    autocmd QuitPre * if &filetype != 'qf' | silent! lclose | silent! cclose | endif
autocmd! CompleteDone * if pumvisible() == 0 | pclose | endif

And I’m having a small binding to open a file whose name is under the cursor in a split. It is interesting when you edit configuration which includes other config files, and it saves you some keystrokes.

" Open file under the cursor in a vsplit
nnoremap gf :rightbelow wincmd f<CR>

The wincmd is a subset of commands made available by pressing <C-w>. It allows to call such commands from a function or from the command prompt.

And now, first batch of plugins.

Assistive plugins

Those are plugins which will ease typing. Things that close opened parenthesis or logical jump into parts of if/then/else. As well as maintaining a statusbar which can provide more information. Those plugins must be in a section delimited by plug#begin and plug#end (and only one of such section). I’ll display this section just once hre, but all lines starting by Plug must be between those two lines.

" Let's store the plugins somewhere
call plug#begin('~/.config/nvim/plugged')

Plug 'adelarsq/vim-matchit'
Plug 'jiangmiao/auto-pairs'
Plug 'vim-airline/vim-airline'
call plug#end()

So, MatchIt enhance the % move. juming from if to else to endif for instance. Straightforward does not requires configuration. It also allows to jump, for instance, between opening and closing of html markups.

Next one is AutoPairs. It is wonderful to not forgetting to close a {} block or parenthesis or “. It allow to jump and wrap text while you type. Again, no configuration and quite transparent.

The last one is vim-airline. Which is like powerline, but not like powerline. It does not requires an external server to function (even if I’m using it because I have powerline for both bash and tmux), and it integrates smoothly with a bunch of other plugins, some of them I’ll use.

With that, let’s run a first batch of install. Either source yourself, or quit and come back. Installing plugins is done asynchronously, in the background and provides a status window.


For reference, :PlugClean allow for removing plugins you’ve removed from your init.vim, :PlugUpdate update all of them and :PlugUpgrade upgrade plug-vim

Airline specific configurations

The default of airline suits me well enough, I did change something however As said I do not use tabs (instead I split and navigate with buffers). And for the sake of seamless integrations,and since I’m using powerline elsewhere, I’m using the poweerline fonts.

let g:airline_powerline_fonts = 1
let g:airline#extensions#tabline#enabled = 1

And that’s the first batch of plugins.

File browser, ftp/scp client, editor over ssh

One of the most interesting features of vim is netrw. It’s a browser usable for a lot of things. First, when you edit a directory, you can browse it, create files and subdirectories, or rename things. Or edit a file. But you can also use netrw to edit over ssh or ftp some files. The :help netrw client is dense but there’s a lot of things in it.

I’m not adding a lot of mappings to it, but I,m mostly use it in place of NERDTree and others plugins. The project drawer pattern is’nt that interesting in a tiled environment, and most of the time it eat a lot of precious space.

I’m just using it as a file opener. :e . open the browser for $CWD in the current buffer and :sp . and :vsp . open it in a split and a vsplit respectively

Code versionning

Git is great. Commits for the Git God.

Working with a git directory from your editor is nice. You can commit, merge, push, pull, branch, whatever without going back to a terminal. I’ve chosen MaGit which provide a splited window to work on a git directory, stage chunk or reject them, etc.

Git-gutter put signs in the gutter (column between the edge of a window and the numbers), so I know if those line have been committed or not, I can preview changes (with <Leader>hp) and stage or undo them using <Leader>hs and <Leader>hu respectively.

Again the defaults for those are fine by me, but have a look at :help Magit and :help gitgutter if you want more.

Installation is done like this

Plug 'airblade/vim-gitgutter'
Plug 'jreybert/vimagit'
" you can use what you want as a grep tool for gitgutter. I'm switching to ag those days.
let g:gitgutter_grep = 'ag'

CTags management

To allow for completions and definitions jump, you need to have ctags built. Gutentags is a great plugins (and the author – even if he’s partial) sums up why you should use this plugin. It requires some specific form of options for your ctags generator, but exuberant is doing the job quite well.
To avoid polluting all my repository with a tag file, I’m merging them in a cache directory. Since it invokes ctags, your custom options can be place in a .ctags in your home or in your project. A little bit of configuration is needed to help gutentags found where’s the projects root and where to store the tags. Some bit of it will be detailled later for filetype specific configs
First, let’s get exuberant-ctags apt install exuberant-ctags
And now for some config and install.

Plug 'ludovicchabant/vim-gutentags'
" let's store the tags in a nice global places to avoid clutter in the
" projects
let g:gutentags_cache_dir = '~/.cache/gutentags'

By default, it will create tags if none exists, and will keep them updated. You’ll notice that airline displays something when gutentags generate the ctags (or the closest ctags once done).

And since having a small bar to navigate your file with tags, I’m adding simple TagBar to the mix. And I Toggle it on <F8%gt;.

nmap <F8> :TagbarToggle<CR>

Ag, Grep

Grepper is a tool which allow you to grep files at scale, and to navigate through a list of matches. Liek so many other plugins (CtrlP, fzf, fzy) it helps as a fuzzy searcher. And since I want to use ag (it is faaaaaaaast) I can tell Grepper to use it (but need to insert another plugin)

" Ag plugin
Plug 'rking/ag.vim', { 'on': 'Ag' }
" And async grep
Plug 'mhinz/vim-grepper', { 'on': ['Grepper', '&lt;Plug&gt;(GrepperOperator)'] }
" Use the word under the cursor to grep through a file list
nnoremap &lt;Leader&gt;* :Grepper -tool ag -cword -noprompt&lt;CR&gt;

Note the conditional activation of those plugins. No need to load what I don’t use today, but what I’ll use later.

Auto make

I want to make my code when I save, and to run tests if needed. Ideally, in async background jobs, but they must open a quicklist/location window with the errors and warning in them.

I’ve picked up neomake for that. It do what I want, it can hold several makers and chain them, or run just one. It also do some linting, ans syntax checking. And you even have more signs in the gutter to indicates the status of a line. You can tweak the makers in an insane way.

I’m also working on folding the quickfix and location window, to have some context for the error saved.

Plug 'neomake/neomake'
" compile when I open a file, change text, leave insert mode, etc ...
call neomake#configure#automake({
    \ 'TextChanged': {},
    \ 'InsertLeave': {},
    \ 'BufWritePost': {},
    \ 'BufWinEnter': {}
\ })
" When compilation is done, open the Location list or quickfix list
let g:neomake_open_list = 2

There’s mostly two command you need to know for neomake.
:Neomake Which allow you to compile the file displayed in your current buffer and :Neomake! which work in project mode. The maker can be omitted, there’s generally one default for the filemode, and if there’s no default for project mode, it’ll use makeprg.

In single file mode, neomake will open a location list which will be bonded to the filebuffer you run the makers from, while inproject mode it will use a quickfix one to regroup all error on all file. :help quickfix provides you with some command, but those are the most used I think:

:cope "Open the quickfix window
:lope "Open the location window
:cc "Go to the error currently selected in quickfix
:ll " same for the location window
:cn / :cp " Next and previous item in QF
:ln / :lp " Same for location

You can of course load any error format in a quickfix window (:caddf) and even run something on all the lines there (:cdo). :grep and :vimgrep (and Grepper/Ag) use the location window to list relevant files, so it’s a bit magic but the Quickfix of vim is really awesome (also, you can start vim in QF mode if you feed him an error log, which allow you to get the error from your buildbot and quickfix your project really fast.

You can add custom makers too, nut beware, you need to parse your output with errorformat. And it’s some kind of regexp engine, with special words and a lot of needed escaping (:help errorformat)

Also, the highlights used by neomake didn’t went well on my terminal, so I changed them a bit.

" Some more readable highlights for neomake
highlight NeomakeError cterm=bold ctermbg=4 ctermfg=14
highlight NeomakeCap cterm=bold ctermbg=5 ctermfg=15

" Let's have some fun with the QuickFix window
autocmd BufReadPost quickfix setlocal foldmethod=expr
autocmd BufReadPost quickfix setlocal foldexpr=or(getline(v:lnum)[0:1]=='\|\|')
autocmd BufReadPost quickfix setlocal foldlevel=0

There’s probably way for improvement on this bloc of Autocmd, but it works fine. They’re run on BufReadPost (after reading the Buffer) on quickfix buffers (which also include Location window), it defines some foldexpr to use (I fold all lines starting with ||) ad then set the foldlevel to 0, folding everything that can be done on this buffer.

Language specific plugins

Before going into the wild lands of autocompletion, I’m adding some filetypes specific plugins, which usually provides a lot of syntax, tags and others helps for a specific tool. Since I’m doing rust these days, it’s the only language for which I have plugins. But I’ll probably need to add more for python for instance.

" Rust lang
Plug 'rust-lang/rust.vim', { 'for': 'rust' }
" Let's use racer
Plug 'racer-rust/vim-racer', { 'for': 'rust' }

The rust-lang package, developed by the rust community, provides lots of helpers (including things for ctags and all, but I’ll detail those a bit later) while racer provides a lot of syntaxic help, including snippets.

So. It wasn’t so hard until here, right? Let’s go for the things that will helps to code faster, witjout waiting seconds for a word to complete, and without needing to use a combination to complete things.

Automagic Completion


h3>With a lot of async plugins, sources and config



And a lot of swearing and headaches non reproduced below

I generally try to keep things small and to use most of the inner system provided by something. I do not like developing framework for framework for instance, or having to compile some binary just to add one more functionality. I like to reduce plugin number. (I mean, I do have some, but it’s not like some monstrous 100 headed hecatoncheire that seems to exist at some point, teh whole purpose here is to reduce frictions, sources of frustration, and CPU cycle).

So I dug into omnifunc and completefunc. They work great, you can even write your own functions and vim provides descent default. However, if I want to add more completion sources (for instance, using the Language Server Protocol) it tends to slow down. And you have to wait for the complete function to complete to continue typing.

I then took up the road to try implementing a plugin/functions using async job for neovim to rpovide a function for omnifunc to be async. Which is something that shouldn’t requires too much work (fool that I was …). But then, I want to complete as I type. So I need to add an Autocomand which would be fired on some Insert events. And to find what is the word, before that.

So, I stopped there. Before writing yet another autocomplete plugin and I found something which interested me. asyncomplete. Lightweight, async, in vimscript (I mean, YCM seems nice, but having to compile python code to run a server that would be then used by vim sounds overkill)(also, LanguageServerProtocol is promising).

Asyncomplete doesn’t provides any sources, but the community did provide a lot of those. For complete from where you want (yes, even from you tmux session it seems). One jsut need to register the nedded plugins, with a bit of configuration (I should work on priorizing some of them) and using whitelists (for filetypes) you can have something extremely usefull.

Add to the mix some snippets engine and a tags system which allow you to extract full function definition, including params, as an autocomplete and you have something cool.

So,let’s start with the long plugin list (yeah, just said I don’t like bloated hecatoncheires but it’s something modular, pick what you want.

" Snippets for code
Plug 'sirver/UltiSnips'
Plug 'honza/vim-snippets'

" Completion manager allow to mix various sources for completion
" and its async.
" the async.vim is required to unify vim8 and neovim async systems
Plug 'prabirshrestha/async.vim'
" LSP allows vim to use Language Server Protocol
Plug 'prabirshrestha/vim-lsp'
" The main engine
Plug 'prabirshrestha/asyncomplete.vim'
" Let's complete with what's in your buffers
Plug 'prabirshrestha/asyncomplete-buffer.vim'
" And your files
Plug 'prabirshrestha/asyncomplete-file.vim'
" Racer is a nice addition, but only for rust
Plug 'keremc/asyncomplete-racer.vim', { 'for': 'rust' }
" Let's use snipps, since I've installed them
Plug 'prabirshrestha/asyncomplete-ultisnips.vim'
" And (guten)tags
Plug 'prabirshrestha/asyncomplete-tags.vim'
" Finally, a LSP client for asyncomplete
Plug 'prabirshrestha/asyncomplete-lsp.vim'

With this I can basically complete with a lot of things. Some might be redundants, I need to check this in details. We will need to remap things, es[ecially teh trigger for ultisnips, since he’s usually tied to Tab.

" Tab navigation in the popupmenu
inoremap <expr> <Tab> pumvisible() ? "\<C-n>" : "\<Tab>"
inoremap <expr> <S-Tab> pumvisible() ? "\<C-p>" : "\<S-Tab>"
inoremap <expr> <CR> pumvisible() ? "\<C-y>" : "\<CR>"

" refresh the completion list
imap <C-space> <Plug>(asyncomplete_force_refresh)

let g:UltiSnipExpandTrigger="<C-e>"

Next is registering all of our engines. I’ll detail one, and you’ll find the other on my framagit.

Hopefully, Asyncomplete provides event for autocommand, so we’re going to use it to register our servers. The set of parameters are self-explicit, and :help asyncomplete-plugin will help in case of doubt.

" Let's register ultisnips sources for completion
autocmd User asyncomplete_setup call asyncomplete#register_source(
        \ asyncomplete#sources#ultisnips#get_source_options({
            \ 'name': 'ultisnips',
            \ 'whitelist': ['*'],
            \ 'completor': function('asyncomplete#sources#ultisnips#completor')
    \ })
\ )

So, here we are. This the end … For the init.vim plugin. I do need some specifics for certain filetypes, and sure, I can probably use some Autogroup and FileType match, but I found it more readable to keep each filetype in each own file.

It will avoid to conflict between completion sources for instance (and will not load jedi for rust, or racer for python among other things).

Rusting vim

So, in your $VIM_RUNTIME dir, create a ftplugin directory and open a rust.vim file there.


As said earlier, racer is a nice tool to complete rust code. There’s no linting for now (this is done by rust-clippy, but I didn’t manage to get it working yet). The project page details how to install it, but basically it’s a single cargo command: $ cargo +nightly install racer

Next we need to activate it. The plugin is already loaded from our init.vim, so it’s just some simple configuration options, notably the experimental completor.

" Racer is here
let g:racer_cmd = '/home/okhin/.cargo/bin/racer'
let g:racer_experimental_completer = 1

" Let's do some clever mapping for racer
nmap gd <Plug>(rust-def)
nmap gs <Plug>(rust-def-split)
nmap gv <Plug>(rust-def-vertical)
nmap K <Plug>(rust-doc)

The mapping are nice. gd go to the function definition, gs do the same but in a split while dv use a vsplit. K (capital k) opens up a preview window with the documentation associated to a function (which is way better than opening a browser tab and directing you to the doc, and it allows to keep track of parameters for instance.

While we’re at it, we’re going to register racer as an autocomplete source.

" have racer as a complete soure
if executable('racer')
autocmd User asyncomplete_setup call asyncomplete#register_source(
    \ asyncomplete#sources#racer#get_source_options())

Yeah, no options for racer (you can add one, for the executable path). The plugin sets up the needed defaults.


This one took me a while to understand since I’m not familiar with ctags. The weirdpart is taht the AutoTag barwas working fine, but gutentags did not find any tags. I tried to feed gutentags with rusty-tags, but rusty-tags doesn’t meet the requirement for gutentags.

So I red a lot of docs, among them the man page for ctags which says that ctags will try to find a .ctagsfile in your $HOME (among other places) which, if it exists, should consist of a list of options, one by line, for them to be added.

I found such a list in the rust.vim plugin. I did not find a way to script the conversion of a file into a list I could feed to gutentags (my vimscripts-fu isn’t really good). So, I’ve setup a plain old symbolic link to $HOME/.ctags from this plugin and it worked nicely.

The next step for gutentags, is telling him how he can recognize he’s at the root of a rust project. Since I use cargo, the presence of a Cargo.toml file is a good marker of a project root (for python, it could be, for instance, the file

let g:gutentags_project_root = ['Cargo.toml']
call add(g:gutentags_project_info, {'type': 'rust', 'file': 'Cargo.toml'})

There might be a cleaner way, but it’s what the doc tells me to do. And I ain’t sure they’re not bith equivalent, needs testing.

Completion with rls

This will complement racer with interesting completion, and especially previews. So, we first need to start a server, then to register a client. You install rls with rustup (and bring back the rust-src while you’re at it) $ rustup component add rls-preview rust-analysis rust-src

And then in $VIM_RUNTINE/ftplugins/rust.vim add the following

" Let's define completion sources for rust
if executable("rls")
    au User lsp_setup call lsp#register_server({
        \ 'name': 'rls',
        \ 'cmd': { server_info->['rls']},
        \ 'whitelist': ['rust'],
    \ })

This will register a server for rls. asyncomplete-lsp is a bit different from others asyncomplete plugins since it tries to connect to a lsp_server when the server gets up. So, the configuration is done on the server (which is started here if there’s a rls executable somewhere). But nothing fancy really.

Testing code

A lot of reading docs has been done here too. Especially for the errorformat and how the quickfix works. The default neomake’s plugins for rust are awesome, they’re based on cargo (and build test using cargo test --no-run to gain time for testing since you won’t need to compile them and you and you benefit from the compiler errors while writing your tests).
But the errorformat doesn’t works when testing. cargo test does not gives you results in a json format (while cargo build does it), and the rustc errorformat does not work either.
Also, I wanted to keep the advices that cargo gives when things fails (and fold them, but that’s been done above).

For reference here’s the output of a test run (with failures).

$ cargo test --quiet

running 2 tests
test tests::calling_without_args ... ok
test tests::calling_with_unknown_subcommand ... ok

test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

running 1 test
test src/ - create_image (line 26) ... FAILED


---- src/ - create_image (line 26) stdout ----
        error[E0432]: unresolved import `self::tests`
 --> src/
5 | use self::tests::setup_database;
  |           ^^^^^ Maybe a missing `extern crate tests;`?

thread 'src/ - create_image (line 26)' panicked at 'couldn't compile the test', librustdoc/
note: Run with `RUST_BACKTRACE=1` for a backtrace.

    src/ - create_image (line 26)

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out

There’s some whitespaces in front of some lines. What I wanted in the end is to have a quickfix containing this:

src/|28 col 11 error 432| unresolved import `self::tests`
||   |
|| 5 | use self::tests::setup_database;
||   |           ^^^^^ Maybe a missing `extern crate tests;`?

I did play a lot with quickfixes, loading content and trying to not turn totally crazy with writing errorformat rules. And I wrote a custom maker to add upstream (yes, even opened an issue, and I need to write a PR too).

I then edited the $VIM_RUNTIME/plugged/neomake/autoload/neomake/makers/ft/rust.vim to find a templates, and added this maker.

function! neomake#makers#ft#rust#cargotest() abort
    let maker = {
        \ 'exe': 'cargo',
        \ 'args': ['test', '--quiet'],
        \ 'auto_enabled': 1,
        \ 'cwd': '%:p:h',
        \ 'errorformat':
            \ '%-G,' .
            \ '%-Gtest %.%#,' .
            \ '%-Grunning %\\d%# test%.%#,' .
            \ '%-Gfailures:%.%#,' .
            \ '%-G----%.%#,' .
            \ '%E%\\s%#error[E%n]: %m,' .
            \ '%C%\\s%#-->\ %f:%l:%c,' .
            \ '%+G%\\d%# %#|%.%#,' .
            \ '%-Gthread %.%#,' .
            \ '%-Gnote:%.%#RUST_BACKTRACE%.%#,' .
            \ '%-G%\\s%\\+%.%#,',
    \ }
    let cargo_toml = neomake#utils#FindGlobFile('Cargo.toml')
    if !empty(cargo_toml)
        let maker.cwd = fnamemodify(cargo_toml, ':h')
    return maker

Which is my first fully functionnal vimscript function. And parse the output of cargo test quite well.

And with that, my vim setup is done (well, I still have python to do, but it should be simpler).

There’s certainly a lot of room for improvements, or nicer way to do things, but I’m still learning. I never dug so deep in nvim and it’s something I should have done earlier. Especially playing with register (pasting with C-R while in insert mode for instance) will save me a lot of time.
I discovered some move keys I ignored existed ( } and { allow to go super fast through a file), and learned how to browse help (they’re tags, so C-] helps).
I still haven’t wrote a single line of rust this week though … But can’t do everything.