torresjrjr(7) 0xF7959E95

My nifty vimrc

· Byron Torres

To resuscitate this blog and my writing skills, today I’d like to go through my vimrc and all-around vim configuration. Perhaps it might spark inspiration for you. You can see it in full here:

Mirror:
https://git.torresjrjr.com/.vim/files.html
SourceHut:
https://git.sr.ht/~torresjrjr/.vim/tree

This is a git repository of the ~/.vim directory, which live at my $HOME and holds everything. In here we have:

.gitignore
README.md
bin/setup
gvimrc
vimrc

Let’s go through a typical set up of this config. This walkthrough will assume that you’ll used the :help command if you see something you don’t understand. Take a look at my bin/setup file, the first thing I run after cloning.

#!/bin/sh -eu
echo >&2 'local.vim ...'
grep -sq 'VimrcLocal' after/plugin/local.vim || {
	mkdir -p after/plugin
	printf 'VimrcLocal " %s\n' "$(id -un)@$(uname -n)" \
		>> after/plugin/local.vim
}

echo >&2 'vim-plug ...'
curl -sSfL -o autoload/plug.vim --create-dirs \
	https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim

It’s a shell script which when executed from within ~/.vim does two things:

Junegunn’s vim-plug is a minimalist vim plugin manager. I would actually prefer to use something more minimal which uses vim 8.0’s packages feature, but sometimes I come across an old machine, and vim-plug does just fine for now.

:help packages

I immediately use vim-plug next. I open vim and I don’t run the :PlugUpgrade command, since I’ve already just installed the latest version of vim-plug. I do run :PlugUpdate, which updates my plugins, which are listed at the end of my vimrc. Take a look at it:

" Plugin Manager: junegunn/vim-plug
if !filereadable(expand('~/.vim/autoload/plug.vim')) | finish | endif
call plug#begin('~/.vim/plugged')
Plug 'https://github.com/junegunn/fzf.git', { 'do': { -> fzf#install() } }
Plug 'https://github.com/junegunn/fzf.vim.git'
Plug 'https://github.com/tpope/vim-surround.git'
call plug#end()
let g:plug_window='0tabnew'

People say you should put your plugins up first so they don’t mess with the rest of your config. I say, don’t install crap plugins which mess with the rest of your config.

I first check if vim-plug is installed (it might not be if I’m another user like root, or some other weird case), and if so, I declare 3 primary plugins. I also let g:plug_window='0tabnew' so that those vim-plug commands run in a separate tab at index 0, to keep things tidy.

I have the vim-surround plugin installed, which allows me to modify and delete pairs of parentheses, brackets, braces, quotes, etc. easily. Such basic functionality feels like it’s missing from vim, so there I have it. I’ve also heard good things about vim-sandwich.

The fzf and fzf.vim plugins come as a couple. They give me superpowers. fzf is an excellent interactive fuzzy finder. I use it everywhere in my shell scripts and in tmux.

A demo of fzf, yanked from junegunn.kr

Note, it is possible to get similar but less versatile functionality with plain vim and globing, using the find command, tab completion, and setting path+=**. See:

:help cmdline-completion
:help starstar
:help 'path'

What about the inferred secondary plugins? Where do they go? In ~/.vim/after/plugin/local.vim of course. This path is specified in my .gitignore file, so the local.vim file can be modified in peace. This filepath leverages vim’s initialisation process, which sources certain default filepaths, specified by the runtimepath. See:

:help 'runtimepath'
:help afer-directory

This means the local.vim file is sourced after my vimrc file, so I can add or override anything there. I can have per-machine config. You can get very creative with the runtimepath if used well, so use it! Take a peak at my .gitignore file for a clue for other interesting paths.

But I’ve also got another trick up my sleeve. Take a look at around the middle of my vimrc.

command VV  <mods>  new $MYVIMRC
command VL  <mods>  new $MYVIMRCLOCAL
command VimrcLocal  let $MYVIMRCLOCAL = expand('<sfile>:p:~')

Remember :VimrcLocal from the setup script? This was placed in the local.vim file. When vim initialises, it runs that command, which sets $MYVIMRCLOCAL to the filepath of the file where that command is written. That’s what expanding <sfile> (“source file”) provides. I also create the short and handy :VL command, which opens that file using that variable. Wanna write a quick vim script hack? :VL. Boom. Cool, eh?

In local.vim files scattered across multiple machines, I have a few of these hacks. Usually, they contain an override list of plugins, some mappings, and a function or two. Just some bits and bobs which are subject to change, or which fix a quirk of the local machine. Here’s one of my local.vim files with some annotations.

VimrcLocal " b@ace

set visualbell

" update website
nnoremap <Leader>uu  :0tab term ++close ../bin/update<CR><C-W>g<Tab>
nnoremap <Leader>uU  :0tab term ++open  ../bin/update<CR>
nnoremap <Leader>ua  :0tab term ++close ../bin/update all<CR><C-W>g<Tab>
nnoremap <Leader>uA  :0tab term ++open  ../bin/update all<CR>

" hare run (https://harelang.org)
nnoremap <leader>h  <C-W>j:close <bar> w <bar> term ++rows=15 ++open hare run<CR>
nnoremap <leader>H  <C-W>l:close <bar> w <bar> vert term ++open hare run<CR>

nnoremap <leader>m  :w <bar> make<CR>

" email scissor snip annotation (for editing emails in aerc(1))
vnoremap <Leader>s  s-%<-<esc>

" source $HOME/code/projects/vim/birck.vim/plugin/birck.vim

" local plugins (relisting the primary plugins first)
call plug#begin('~/.vim/plugged')
Plug 'https://github.com/junegunn/fzf.git', #{ do:{ -> fzf#install() } }
Plug 'https://github.com/junegunn/fzf.vim.git'
Plug 'https://github.com/machakann/vim-sandwich.git'

" additions:
Plug 'https://github.com/fatih/vim-go', #{ do:':GoUpdateBinaries' }

" this was dumb
Plug 'https://git.sr.ht/~torresjrjr/birck.vim', #{ on:'Birck' }

Plug 'https://git.sr.ht/~sircmpwn/hare.vim'
Plug 'https://git.sr.ht/~torresjrjr/gemini.vim'
Plug 'https://github.com/kalekundert/vim-nestedtext'
call plug#end()

" refresh logfile periodically
command -bang LogMode  :call FnLogMode("<bang>")
function FnLogMode(bang)
	if a:bang != '!'
		set cursorline autoread
		let g:logticker = timer_start(
		\	5 * 1000,
		\	{ -> execute('checktime | norm zz') },
		\	#{repeat: -1}
		\ )
	else
		call timer_stop(g:logticker)
		set nocursorline
	endif
endfunction

au FileType help  :set nu
au FileType help  :exe "norm <C-W>T"

" ...

As for my actual vimrc file, I’ll talk only about the interesting things which deserve an explanation or which can’t be explained with :help.

au BufReadPost *  exe "norm! g`\""

This is an autocommand, a command which runs everytime a buffer is read into memory (i.e. when you open a file). The command runs a normal mode key sequence which brings the cursor to the last known position the last time the file was open. This line actually comes as part of defaults.vim, which I probably should use, but don’t. Meh.

:help g`
:help defaults.vim

Here are some nifty one-liners. This one makes opening the command-line window easier. Get used to editing commands on the fly.

noremap Q       q:

This one overrides the refresh command and causes annoying search highlights to temporarily turn off.

noremap <C-L>  :nohls<CR><C-L>

These two are just feel like obvious missing functionality.

noremap <C-J>   :bnext<CR>
noremap <C-K>   :bprev<CR>

These two in combination with the :vimgrep and :copen commands make detective work in a big project fun.

noremap <C-N>   :cnext<CR>
noremap <C-P>   :cprev<CR>

:help :vimgrep
:help quickfix-window

These “mapleader” mappings are sort of alien at first look.

let mapleader="\<space>"
noremap <leader><tab>  :.term sh<CR>
noremap <leader>q      :.!sh<CR>
noremap <leader>r      :.r !
noremap <leader>w      :.w !

The <leader>q mapping in particular is very versatile. The way it’s used, in the current buffer or in a spare one, you write shell commands. Maybe something like:

date -I
mv -v file1 convoluted/dir/path/file1
mv -v file2 convoluted/dir/path/file2

Then, visually select what you want to execute (vip), press <leader>q, and the output of those commands replace the text. Using vim as part of a cohesive system works wonders. You can imagine similar examples with the other mappings.

These two come from fzf.vim, and create a fuzzy menu for files and buffers. Great for navigation.

noremap <leader>f      :Files<CR>
noremap <leader>b      :Buffers<CR>

Oh, and the sacred escape mapping. The one and only true way.

noremap! kj  <esc>

Which can also be configured in your shell, by the way. Either using an .inputrc file, or a few of these commands according to your shell:

# bash
set -o vi   # vi mode
bind '"kj":vi-movement-mode'

# zsh
bindkey -v  # vi mode
bindkey "kj" vi-cmd-mode

The great thing about kj compared to other sequences like jj or jk is:

And that’s it. I hope you’re learned something which can apply to you.