Porn, Zen, and .vimrc

A few lessons learned during my 5 year adventure in fiddling with Vim configuration files.

Porn

As a rule, new users, right after introduction to a few basic commands, start indulging in Vim configuration porn. Such porn has various different flavours. There is the “exhibitionist” variety which includes dressing Vim up in status lines, colorschemes, and font decorations in order to impress others. It often leads to Vim being displayed on videos and, nomen omen, “vim-porn” screenshots. There is an “integrated” category where people plug and stretch their Vim until it resembles an IDE. Git integration, autocompletion, language servers, refactoring, and debugging are the tools of trade in this version. And then there are a few more variants, including “fuzzy”, “packaged”, “conveniently re-mapped”, and I am sure anyone could think up some of their own.

My particular genre was “consistency” - I wanted my Vim to be intuitive and predictable. While working I would constantly keep an eye for digressions in Vim’s behaviour and then spend my time trying to adjust them. In my fantasy Vim had to feel intuitive and smooth so I tried to correct every little detail. And, despite “consistency” porn being quite vanilla, I managed to construct a vimrc file exceeding one thousand lines of code.

Consistent vimrc

No point in sharing the entire collection, but here are a few illustrative examples and the rationale behind them. Feel free to try them out, if that is your thing.

  1. Y

    As is widely known Y is not consistent with other uppercase commands. It copies the whole line while D and C operates from the cursor position to the end of line. Simple remap fixes the issue.

    " make Y consistent with C and D.
    nnoremap Y y$
    
  2. n and N

    Having the same characters always go in the same direction would be a lot easier. So this map makes n always go forwards and N always go backwards. Same for ; and ,.

    " make n always search forward and N backward
    nnoremap <expr> n 'Nn'[v:searchforward]
    nnoremap <expr> N 'nN'[v:searchforward]
    
    " make ; always "find" forward and , backward
    nnoremap <expr> ; getcharsearch().forward ? ';' : ','
    nnoremap <expr> , getcharsearch().forward ? ',' : ';'
    
  3. ; and ,

    I disliked the forward and backward f repetition being under different characters: ; and ,. This set of remaps puts them under the same key and moves away the colon command over to backtick. The original backtick behaviour is transferred to a single quote.

    " make ; and , be on the same key
    nnoremap : ,
    nnoremap ` :
    nnoremap ' `
    
  4. j, k, h, l

    This set of mappings makes the j, k, h, and l keys perform consistent direction-related action in various circumstances: scrolling, changing and creating windows, moving across paragraphs.

    " make J, K, L, and H move the cursor MORE.
    nnoremap J }
    nnoremap K {
    nnoremap L g_
    nnoremap H ^
    
    " make <c-j>, <c-k>, <c-l>, and <c-h> scroll the screen.
    nnoremap <c-j> <c-e>
    nnoremap <c-k> <c-y>
    nnoremap <c-l> zl
    nnoremap <c-h> zh
    
    " make <a-j>, <a-k>, <a-l>, and <a-h> move to window.
    nnoremap <a-j> <c-w>j
    nnoremap <a-k> <c-w>k
    nnoremap <a-l> <c-w>l
    nnoremap <a-h> <c-w>h
    
    " make <a-J>, <a-K>, <a-L>, and <a-H> create windows.
    nnoremap <a-J> <c-w>s<c-w>k
    nnoremap <a-K> <c-w>s
    nnoremap <a-H> <c-w>v
    nnoremap <a-L> <c-w>v<c-w>h
    
  5. J and K.

    If you think about how Vim moves across sentences and words you will notice that it doesn’t place the cursor on the empty space between them, but rather on the first and last characters. Similarly this bad boy makes J and K commands place the cursor on the outermost lines while moving from paragraph to paragraph. Consistent.

    " move to the edge of a paragraph.
    function! s:Move(isUp, isInVisual)
      if a:isInVisual
        normal! gv
      end
      let curpos = getcurpos()
      let firstline='\(^\s*\n\)\zs\s*\S\+'
      let lastline ='\s*\S\+\ze\n\s*$'
      let flags = 'Wn'. (a:isUp ? 'b' : '')
      " Move to first or last line of paragraph, or to the beginning/end of file
      let pat = '\('.firstline.'\|'.lastline.'\)\|\%^\|\%$'
      " make sure cursor moves and search does not get stuck on current line
      call cursor(line('.'), a:isUp ? 1 : col('$'))
      let target=search(pat, flags)
      if target > 0
        let curpos[1]=target
        let curpos[2]=curpos[4]
      end
      call setpos('.',curpos)
    endfu
    
    " H K L J for moving to paragraph edge
    nnoremap <silent> J :<c-u>call <sid>Move(0, 0)<cr>
    nnoremap <silent> K :<c-u>call <sid>Move(1, 0)<cr>
    vnoremap <silent> J :<c-u>call <sid>Move(0, 1)<cr>
    vnoremap <silent> K :<c-u>call <sid>Move(1, 1)<cr>
    onoremap J V:call <sid>Move(0, 0)<cr>
    onoremap K V:call <sid>Move(1, 0)<cr>
    
  6. ge

    To understand the inconsistency with ge consider how Vim behaves across 4 distinct commands:

    Cursor position       Command        Result
    ------------------------------------------------
    word1 word2           de             wo word2
      ^                   dw             woword2
    
    word1 word2           db             word1 rd2
            ^             dge            wordd2
    

    Note that dge is inconsistent in two ways: first, unlike db, it deletes the character under the cursor; second, unlike dw, it deletes the last character of the word it moves towards. Remap below made it more consistent.

    " make commands with ge consistent with b and w
    onoremap ge :execute "normal! " . v:count1 . "ge<space>"<cr>
    
  7. &

    Here remap makes the substitution repeat command reuse the flags of the previous invocation.

    " make substitution repeat to reuse last flags
    nnoremap & :&&<cr>
    xnoremap & :&&<cr>
    

    Let that be the end of examples. I had many more but you probably get the point by now. The simple truth is that my thousand line vimrc file didn’t give me any comfort. The feeling I had when I opened it up is best described as a kind of anxiety. I often felt a weird push to improve it, organize it better, make it more consistent. Until one day I had enough and pressed delete.

A fresh start

So, no more vimrc. It did feel sloppy, at first. But I quickly found a new way to get around.

Some of the issues with highly customized configuration files are known to everyone. You have to use a plugin to manage your plugins; have to transfer dotfiles across different servers; lose the ability to work on machines without your configuration files; others cannot use your computer without disabling your setup first. Removing the heavy configuration got rid of those downsides. But in addition I noticed a few additional less talked about benefits which are listed below.

Zen

So, after all the trouble, do I have regrets about deleting my thousand line creation? No. The simple truth was that, no matter how much energy I invested in my custom configuration, the people who built and designed Vim knew it a lot better than me. And it hit me like a cold shower: implemented defaults and design was there for a reason. Some of my perceived inconsistencies were products of my limited understanding. I made assumptions about what a particular command should do, when it should be used, and how. But those assumptions were not always warranted.

For a brief period I even went into the opposite direction and tried to make my vimrc as minimalistic as possible. This was an exercise in asceticism and it produced a similar negative effect, except in another direction. I still cared too much about my vimrc, only this time, instead of making it more consistent, I was striving to make it as light as possible. A similar paradox happens in some Zen practices where a disciple, being told he shouldn’t attach to things, starts attaching to non-attachment. The correct attitude, at least in the case of Vim, is to let go of the need for control. I learned to trust the choices of people who created Vim and to not interfere with their design. In the long run this attitude saved me from an uphill battle between me and my vim configuration. In the long run it helped me stop wasting my time.

Now, after all the struggle, my vimrc is around 50 lines in length. It only includes simple and frequently used commands, like this map to toggle search highlighting with ctrl-/ :

nnoremap <silent> <c-_> :set hlsearch!<cr>

Various boilerplate procedures that I previously couldn’t live without remain deleted. This includes things like setting relative numbers or opening a :terminal buffer below. I started to look at those commands as a sort of a ritual that I have to perform before editing. It takes a couple of seconds but it gets me in the mood for work.

Thanks to /u/-romainl-, /u/dutch_gecko, and /u/htranix from /r/vim reddit thread for corrections


  1. ◦  n and N remap answer on Vi and Vim Stack Exchange

  2. ◦  ; and , remap answer on reddit

  3. ◦  “vim–you–keep–using–that–word” plugin by Aristotle Pagaltzis on GitHub

  4. ◦  “vim–operator–replace” plugin by Kana on GitHub