"" My |vimrc|
" TODO: Supply chain attacks are getting more common. We should use (mandate?)
" commit hashes in `GitAdd`. Also, make sure to use a command format that
" ignores tag/branch names (those can be named to look like hashes and are
" mutable).
" TODO: Do we want to try to integrate with Debian? There seems to be at least
" two ways vim-related files are installed:
" - Purely auxilliary files as part of some other package: Files are installed
" to `/usr/share/vim/addons` which are referenced by files installed to
" `/usr/share/vim/registry`. `vim-addons` from the `vim-addon-manager` can be
" used to (presumably) symlink them into a path that is listed in
" 'runtimepath'.
" - As a standalone `vim-${addon}` package: Files are installed to
" `/usr/share/vim-${addon}`, presumably symlinked into a `start` or `opt`
" 'packpath' during package installation.
" and the `dh_vim-addon` man page in
" the `dh-vim-addon` package contains some further details.
""" Formatting
" A |'textwidth'| of 79 is used throughout.
" Comments are written in Vim's help |notation|, see |help-writing|. They can
" be highlighted with
" .
" Section headings are not written in the help |notation|, but are instead
" indicated with multiple contiguous comment characters. They can form folds
" with .
""" Compatibility
" TODO: Test with `nvi`, a "bug-for-bug compatible" clone of the original BSD
" `vi`.
" TODO: Test with `VIM_POSIX=1 "$vim"` (for different `$vim`s?). See
" |$VIM_POSIX|.
" This vimrc should work with all configurations of Vim. Manually tested with:
" - Versions: TODO: Whatever some old {Debian,Ubuntu,Fedora} ships.
" - Features: small, huge (Debian `apt-get install vim-{tiny,gtk3}`)
" - Locales: C, UTF-8 (`LC_ALL=C{,.UFT-8}`)
" - Colors: 1, 2, 8, 16, 256, termguicolors (`--cmd "set {t_Co=$colors,tgc}"`)
" - UI: TUI, GUI (`{,g}vim`)
" - Terminals: GUI, VTE, Linux, tmux with the correct `Ss` `terminal-override`
" - Platforms: Unix-like
" TODO: `man setfont` says:
" > Note: if a font has more than 256 glyphs, only 8 out of 16 colors can be
" > used simultaneously. It can make console perception worse (loss of
" > intensity and even some colors).
" The default font on my system has 512 glyphs (`showconsolefont -v`).
" TODO: `man setfont` says, in section "CONSOLE MAPS":
" > Several mappings are involved in the path from user program output to
" > console display.
" Might be worth reading up on that. If I already did, document.
""" Features
" |:version| says "tiny" features are always present and are therefore used in
" favor other features whenever possible. "Tiny" features include:
" - |+multi_byte|
" - |+mouse|
" - |+tag_binary|
" - |+user_commands|
" - |+autocmd|
" - |+localmap|
" Note that trying to set (run-time, but not compile-time) unsupported options
" is silently ignored, and so can be done without checking for support first
" (which is good, since that requires |+eval|). Everything between |:if| and
" |:endif| is also silently ignored if |+eval| is not available. See
" |no-eval-feature|.
""" Encoding
" |'encoding'| is hard-coded to `utf-8`. However, only characters present in
" Code Page 437 should be used in the interface, to ensure font support on most
" terminals (in particular, the Linux console, via `CHARMAP` in
" `/etc/default/console-setup`). See
" -
" -
" - `man showconsolefont`
""" Re-sourcing
" |setglobal| is used where appropriate so that buffer/window-local options are
" not overridden when re-sourcing.
" A `vimrc` |augroup| is cleared and used for all |autocmd|s. At the time of
" writing, |augroup| messes up the highlighting of |autocmd|s within it, so the
" `vimrc` group name is added to every |autocmd| instead (the addition of the
" group of course makes the highlighting misinterpret it as the
" `vimAutoEventList` and the events instead get the erroneous
" `VimAutoCmdSfxList` highlighting... file a bug?). This can be checked with
" `:g/autocmd`.
""" Mappings
" Don't make any built-in mappings do something completely different! Another
" Vimmer should never be surprised by what happens, unless it is pleasantly
" surprised because the functionality is extended in useful and obvious ways.
" This includes mappings from popular plugins, for which the defaults should be
" kept as far as possible.
" || is hard-coded to so that mappings work without |+eval|.
" Setting |mapleader| to a different value after the mappings have been defined
" does not change the mappings, so it is not possible to "hot-swap" it for pair
" programming anyway.
"" Options
" TODO: Use |setglobal| for more things? Note that for options with a non-empty
" default local value, `setglobal` does nothing useful. Worse, for options with
" only a (buffer or window) local value, `setglobal` does nothing, literally.
" Is that a problem, should we even set local values here? Maybe identifying
" them and getting rid of them is a good idea?
" TODO: Maybe source |defaults.vim|? Probably not, but add some documentation?
" TODO: Look at |:options| and maybe follow the same grouping?
" NOTE that |OptionSet| was added in
" and may not be available.
""" Reset
setglobal all&
""" Encoding
" Force Vim's internal encoding to a Unicode encoding.
set encoding=utf-8
""" Buffers
" Allow switching away from unsaved buffers.
set hidden
""" Clipboard
" Integrate with any graphical environment's clipboard.
set clipboard^=unnamedplus
""" Editing
set backspace=indent,eol,start
set virtualedit=block
""" Modeline
set nomodeline
""" Navigation
set nostartofline
" : Cannot scroll by screen
" line if a line wraps. Add the 'smoothscroll' option.
if has('patch-9.0.0640')
set smoothscroll
endif
""" Windows
" Put newly opened windows below and to the right.
set splitbelow
set splitright
""" Interface
set showcmd
set noruler
set laststatus=1
set showtabline=1
set shortmess+=I
set guioptions=cf " console dialogs, foreground (no fork())
set numberwidth=1
""" Interface characters
" If |'t_Co'| is empty (or set to 1) the default behavior of |'fillchars'|
" `stl:`/`stlnc:` (`=`/`^`) will be used regardless of its setting. Other good
" choices for |'listchars'| `fold:` is ` ` and `tab:` is: `→ `, `――>`, `├──`,
" `──┤`, `├─┤`.
" TODO: `:help 'fillchars'` says "for `stl` and `stlnc` only single-byte values
" are supported". Test the current (non-ASCII) values against older versions of
" Vim. See also .
" TODO: If Vim cannot tell appart |hl-StatusLine| and |hl-StatusLineNC| (e.g.
" because |hl-StatusLine| is a |hl-link|), (ordinary) spaces and `fillchars`
" `stl` characters for the statusline of the focused window will be replaced
" with `^`. Note that this includes the single character under the `fillchars`
" `vert`, which cannot be set with |'statusline'|.
set linespace=-1
set list
set linebreak
set breakindent
set breakindentopt=sbr,shift:2
set showbreak=»
set fillchars=stl:─,stlnc:─,vert:│,fold:─,diff:
" set fillchars=stl:_,stlnc:_,vert:│,fold:─,diff:
set listchars=tab:├─,trail:•,extends:›,precedes:‹,nbsp:·
" : Showing two characters for
" tab is limited. Allow for a third character for "tab:" in 'listchars'.
if has('patch-8.1.0759')
" ab c
" set listchars+=tab:├\ ┤
set listchars+=tab:│\ │
endif
" TODO: `%f` still shows `[No Name]`, is there a way to remove that?
set statusline=%(%f\ %)
if has('eval')
" TODO: This works poorly with plugins that have windows with their own
" statusbar, e.g. |undotree.txt|.
" set statusline=%(%{expand('%:.s?^/??')}\ %)
" `fillchar_status` function in `src/screen.c:4649` of Vim source.
" TODO: Try to use some other non-standard space? Search for "\" in
" |digraphs|.
" set fillchars+=stl:\ ,stlnc:
set fillchars+=stl: ,stlnc:
" set fillchars+=stl:┘,stlnc:┘
" TODO: Standardize on global functions? Or |autoload|s (but that would break
" our "single file" policy)?
" function! s:statusline() abort
function! Statusline() abort
" TODO: Make this expansion relative to the window-local cwd? Also do other
" substitutions, like `$HOME` to `~`, the expanded `$VIMRUMTIME` to the
" string `$VIMRUNTIME`, etc. We used to have `s?^/??`, why?
" TODO: Harmonize with `titlestring`.
let l:title = !empty(&l:buftype) ? &l:buftype : expand('%:~:.')
if &l:buftype =~# 'help\|terminal'
let l:title .= ':' . expand('%:t')
elseif &l:buftype ==# 'quickfix' && exists('w:quickfix_title')
let l:title .= ':' . w:quickfix_title
endif
if !empty(l:title)
let l:title .= ' '
endif
" NOTE: `matchstr` looks for the _first_ match.
" TODO: This is slow. Cache the `matchstr` result?
let l:fill = matchstr(&fillchars, '\(^\|,\)stl:\zs[^,]*')
let l:pad = winwidth(0) - strwidth(l:title)
return l:title . repeat(l:fill, l:pad)
endfunction
" : Cannot easily get the
" script ID. Support expand('').
" if has('patch-8.2.1347')
" let &statusline = '%{'.expandcmd('statusline()').'}'
" else
let &statusline = '%{Statusline()}'
" function! g:Statusline() abort
" return s:statusline()
" endfunction
" endif
endif
""" Colors
" May be overritten later by Vim for terminals with |t_RB|.
set background=dark
set guioptions+=d
if &t_Co > 16
set t_Co=16
"
if has('nvim')
lua ffi = require "ffi" ffi.cdef "int t_colors" ffi.C.t_colors = 16
endif
endif
set notermguicolors
"
if empty($COLORTERM) && !has('vcon')
set notermguicolors
endif
if has('gui_running')
set termguicolors
endif
""" Bell
set belloff=error
""" Title
set title
" set titlestring=vi:%{fnamemodify(getcwd(),':~:t:s?^$?/?')}%(:%<%{pathshorten(expand('%:.:s?^/??'))}%)
" TODO: Break out into `Title()` function and harmonize with `statusline`.
set titlestring=vi:%{fnamemodify(getcwd(),':~:t:s?^$?/?')}%(:%<%{expand('%:.:s?^/??')}%)
set titleold=
""" Tabs
" When reading files written by others, assume default `tabstop=8`.
" When Writing files, use 4 space indents.
set shiftwidth=4
set shiftround
set softtabstop=-1
set expandtab
""" Mouse
set mouse=a
""" Command-line
set wildmenu
set wildmode=longest:full,full
" See also |'suffixes'|.
set wildignore+=_*
set wildignore+=.git
set wildignore+=UPSTREAMS,FORKS
" Relative to `$HOME`.
set wildignore+=*/.vim/pack,*/.config/vim/pack,*/.config/nvim/pack
set wildignore+=*/.cache,
set wildignore+=*/.mozilla/firefox
set wildignore+=*/.local/lib,*/.local/share/man
set wildignore+=*/.vagrant,*/.ansible
set wildignore+=*/.local/pipx,__pycache__,.venv,*.egg-info
set wildignore+=*/.cargo
set wildignore+=*/.npm,node_modules
set wildignore+=*/.local/share/tldr
set wildignore+=*/snap
set wildignore+=*/.wine/dosdevices,*/.wine/drive_*
" : 'wildmenu' only shows few
" matches. Add the "pum" option: use a popup menu to show the matches.
if has('patch-8.2.4325')
set wildoptions+=pum
endif
""" Search
set incsearch
set shortmess-=S
""" Timing
set lazyredraw
set notimeout
set ttimeout
set ttimeoutlen=100
""" Diffs
set diffopt+=vertical
set diffopt+=foldcolumn:0
" : Using an external diff
" program is slow and inflexible. Include the xdiff library. Use it by default.
if has('patch-8.1.0360')
set diffopt+=internal
set diffopt+=algorithm:histogram
set diffopt+=indent-heuristic
endif
""" Formatting
" See |auto-format|, |format-comments|, and |fo-table|.
set textwidth=79
" set winwidth=80
" set colorcolumn=+1
set autoindent
set nojoinspaces
" Don't autowrap text, enable comment formatting with |gq|, recognize lists,
" remove comment leader when joining.
set formatoptions-=t
" set formatoptions+=q " TODO: This is the default?
set formatoptions+=n
set formatoptions+=j
set nowrap " See also |breakindent|, |breakindentopt|, |showbreak| set above.
" A custom 'formatprg' produces nicer results but is harder to replicate for
" others and places undue burden on documentation contributors. Having simple
" format rules is more important than slightly prettier formatting.
" TODO: At least make the expressions valid (we're lacking spaces, no?).
" if executable('par')
" let &formatprg = join(['par', 'T'.&tabstop, 'w'.&textwidth, 'e'])
" let &formatprg = join(['par', 'T'.&tabstop, 'w'.&textwidth, 'e', 'j'])
" elseif executable('fmt')
" let &formatprg = 'fmt' . '-w' . &textwidth '-g' . &textwidth
" endif
""" Comments
" set commentstring=#%s
""" Folds
set foldlevelstart=999
set foldtext=substitute(getline(v:foldstart),'\\t',repeat('\ ',&ts),'g').'\ '
""" Spelling
" set spell
set spelllang=en_us
set thesaurus=~/.vim/thesaurus/en_us.txt
" TODO: We can probably do something better/cleaner for the thesaurus on our
" own. In particular, publicly available, clearly licensed, version controlled,
" code to generate the vim thesaurus from primary sources.
" The current `~/.vim/thesaurus/en_us.txt` is downloaded from the URL
" documented in |'thesaurus'|:
" . This zip file
" contains a patch to
" and the results of running the patched code on the included files, as well as
" the licenses. `MyThes-1.zip` is linked from
" and seems to be
" some version of . MyThes states in its
" readme that it is based on WordNet 2.0. However, the latest version as of
" this writing is 3.1. When generating a Vim version, it helps to have a
" visualization to guide the heuristics that will probably be needed. Searching
" for "thesaurus" gives
" several projects. looks good (try e.g. "completion").
" Another search hit,
" , states that
" "WordNet's vocabulary is smaller than other "word" databases", so we might
" want to use other sources as well, e.g. Roget's Thesaurus.
"
" WordNet
" -
" -
" Data hosted on Project Gutenberg
" - (2002) Moby Thesaurus List
" - (2004) Roget's Thesaurus of English Words and Phrases
" - (1991) Roget's Thesaurus
" - (2011) A Dictionary of English Synonymes [...]
" - (2016) A Complete Dictionary of Synonyms and Antonyms
" For Swedish, there is
" -
" -
""" Views
set viewoptions-=options
set viewoptions+=slash
set viewoptions+=unix
""" Files
set swapfile
set writebackup
set backupcopy=yes
set undofile
set directory=~/.cache/vim/swap//
set backupdir=~/.cache/vim/backup//
set undodir=~/.cache/vim/undo//
set viewdir=~/.cache/vim/view//
set viminfofile=~/.cache/vim/viminfo
if has('nvim')
set shadafile=
endif
""" Conceal
set conceallevel=2
set concealcursor=nc
""" Platform
set shellslash
""" External tools
" Not POSIX: `-H` (suggested in 'grepprg'), `-I`.
set grepprg=grep\ -nHI
""" Quickfix
set switchbuf=useopen
"" Mappings
" TODO: Categorize into map modes. Use the nonspecific |:map| (|mapmode-nvo|)
" and |:map!| (|mapmode-ic|) more often?
""" Escape
" Overwrites the default |i_CTRL-C|, |v_CTRL-C|. See
" .
inoremap
xnoremap
""" Windows
nnoremap h
nnoremap j
nnoremap k
nnoremap l
tnoremap h
tnoremap j
tnoremap k
tnoremap l
""" Clear and redraw
" is overridden above, so use a mapping.
nnoremap
""" Movement
" Up down dispaly lines (unless a count is given).
nnoremap j v:count ? 'j' : 'gj'
nnoremap k v:count ? 'k' : 'gk'
" End of pair opened at end of current line.
" TODO: These are overridden by `matchit`.
nnoremap g% :norm! $%
onoremap g% :norm! $%
xnoremap g% :norm! v$%
""" Scrolling
" Put paragraph containing cursor line at the top of the window.
" TODO: Preserve jump list somehow?
nnoremap zg {jzt
""" Visual mode repeat
" See also `vim-visualrepeat`.
xnoremap . :normal! .
""" Clipboard
" Yank file and line number of cursor.
nnoremap yy :let @+ = expand('%:p') . ':' . line('.')
""" `$MYVIMRC`
nnoremap ve :edit $MYVIMRC
nnoremap vs :source $MYVIMRC
""" Keyword
" nnoremap K :silent execute "normal! K" \| redraw!
" nnoremap K :terminal ++close =&keywordprg
" TODO: vnoremap
" TODO: `set keywordprg=:help`
""" Help
" TODO: Make this filetype dependent? Could use |:grep| or |fugitive|'s
" |:Ggrep|. On the other hand, there are other built-in mappings that does
" keyword search in a codebase.
" TODO: Here we use `g` as prefix. Do we do that in other places? Should we not
" use (i.e. )?
nnoremap gK :helpgrep \<\>
xnoremap gK y:helpgrep "
""" Run
" TODO: Standardize where the output goes.
" File (with output in pager).
nnoremap % :!%:p:S \| less -FR
nnoremap # :!#:p:S \| less -FR
" Command (with output in new window).
nnoremap ! :new \| .!
""" Quickfix
"""" Toggle Quickfix window
" TODO: Do similar for location list?
" TODO: Make the |botright| global with some |autocmd|?
nnoremap q
\ empty(filter(range(1,winnr('$')),'getwinvar(v:val,"&buftype")==#"quickfix"'))
\ ? ':botright copen'
\ : ':botright cclose'
""" Make
" TODO: |dispatch| provides default mappings, try to emulate them?
" TODO: Should these be as well?
" TODO: Use the |terminal-debugger| instead of a make target?
nnoremap m% :!make %:r:S \| less -FR
nnoremap m# :!make #:r:S \| less -FR
nnoremap mm :!make \| less -FR
nnoremap ma :!make all \| less -FR
nnoremap mt :!make test \| less -FR
nnoremap md :!make debug \| less -FR
if has('quickfix')
nnoremap m% :silent make! %:r:S \| redraw!
nnoremap m# :silent make! #:r:S \| redraw!
nnoremap mm :silent make! \| redraw!
nnoremap ma :silent make! all \| redraw!
nnoremap mt :silent make! test \| redraw!
endif
if has('terminal')
nnoremap md :terminal make debug
endif
""" Improve default mappings
"""" Disable unconditional quit from Normal mode
" As warned about in |zz| these may be typed accidentally when Caps Lock is
" enabled and lose data. Redefining them is a bit hostile towards other users,
" but deemed worth it.
nnoremap ZZ :quit
nnoremap ZQ :quit
"""" Yank to end of line, not entire line
" As suggested in |Y|. Analogous with |D| does `d$` and |C| does `c$`.
nnoremap Y y$
"""" Don't include newline in Visual mode `$`.
xnoremap $ $h
"""" Visual mode put that doesn't clobber unnamed register
" See also .
xnoremap P pgvy
""" Visual mode
"""" Visually select last pasted text
" TODO: Make a text object as well?
" TODO: I use this relatively often, but it clobbers the built in |gp|. I never
" use that, but it seems useful, so maybe I should? Find another natural
" mapping?
" Analogous with how |gv| visually selects last visually selected text. Default
" to always using ordinary Visual mode. Linewise and blockwise with |+eval|
" support.
nnoremap gp `[v`]
if has('eval')
nnoremap gp '`[' . getregtype()[0] . '`]'
endif
"""" Restrict Visual mode substitutions to the selected text
xnoremap s :s/\%V
"""" Visual movement
xnoremap hoho
xnoremap jojo
xnoremap koko
xnoremap lolo
""" Scrolling
" Arrow keys scroll. Useful when 'mouse' is empty and the terminal sends arrow
" keys when scrolling with the mouse.
noremap
noremap
noremap zh
noremap zl
""" Mouse
" As suggested in |scroll-mouse-wheel|, scroll only one line.
noremap
noremap
noremap zh
noremap zl
noremap zh
noremap zl
" Toggle folds.
noremap za
noremap <2-RightMouse> za
noremap <3-RightMouse> za
noremap <4-RightMouse> za
""" Command-line
" Take already written text into account when searching history.
cnoremap
cnoremap
""" Macros
" Repeat the macro in register `q` on current line, lines covered by visual
" selection, or line covered by text object.
nnoremap QQ @q
xnoremap Q :normal! @q
nnoremap Q :set operatorfunc=Qg@
if has('eval')
function! s:Q(...) abort
'[,']normal! @q
endfunction
endif
""" Diffs
" Current file.
" Se also |:DiffOrig|.
" TODO: Use a buffer?
"
nnoremap dd :w !
\ diff -u --color=always %:S -
\ \| tail -n +3
\ \| less -FR
\
nnoremap dw :w !
\ diff -u %:S -
\ \| tail -n +3
\ \| wdiff -d -n
\ -w "$([ "$(tput colors)" -ge 16 ] && tput setaf 9 \|\| { tput bold; tput setaf 1; })"
\ -y "$([ "$(tput colors)" -ge 16 ] && tput setaf 10 \|\| { tput bold; tput setaf 2; })"
\ -x "$([ "$(tput colors)" -ge 16 ] && tput setaf 15 \|\| { tput bold; tput setaf 7; })"
\ -z "$([ "$(tput colors)" -ge 16 ] && tput setaf 15 \|\| { tput bold; tput setaf 7; })"
\ \| less -FR
\
" Requires `vim-fugitive`.
nmap dg :Gdiffsplit
" Analogous with |dp| |do| in Normal mode.
" TODO: Is adding a `diffupdate` at the end useful?
nnoremap dpp :.diffput
nnoremap doo :.diffget
xnoremap dp :diffput
xnoremap do :diffget
nnoremap dp :set operatorfunc=diffputg@
nnoremap do :set operatorfunc=diffgetg@
if has('eval')
function! s:diffput(...) abort
'[,']diffput
endfunction
function! s:diffget(...) abort
'[,']diffget
endfunction
endif
""" Formatting
" Format comment (only). NOTE that this requires a `gc` comment text object
" plugin.
nmap gQ gqgc
nmap gW gwgc
""" Folds
" Focus current fold.
nnoremap zV zMzv
" Focus next/previous fold. Overwrites default move to start/end of
" next/previous fold.
" TODO: |c_CTRL-R_=| requires |+eval|?
nnoremap zj mgvzc'>zv
nnoremap zk zvzckVzvV:=prevnonblank(line('.'))zvzcVoVzv
" Open/close nested folds recursively. Overwrites default open/close fold under
" cursor recursively.
nnoremap zO zvzczO
nnoremap zC zvzcV:foldclose!zvzc
""" Spelling
" Correct last misspelled word with first suggestion without moving the cursor.
" See also |compl-spelling|.
nnoremap z? [s1z=
inoremap u[s1z=`]au
""" Visual mode operators
" Allows for visual selection of text objects that share a name with an
" operator.
nnoremap v :set operatorfunc=vg@
nnoremap V :set operatorfunc=Vg@
nnoremap :set operatorfunc=CVg@
if has('eval')
function! s:v(...) abort
execute 'normal!' '`[v`]'
endfunction
function! s:V(...) abort
execute 'normal!' '`[V`]'
endfunction
function! s:CV(...) abort
execute 'normal!' "`[\`]"
endfunction
endif
""" Normalize date to ISO 8601
" See |:visual_example|.
" See also |abolish-coercion|.
" This is hacky but works without |+eval|.
xnoremap crd `>a`