Dive into my .vimrc

Date: 2024/08/17 (Sat)

I recently got my first job, but only Windows environment at my working place. I know it's normal, but as a Linux user, it's painful to deal with Windows stuff. My current solution is vim in WSL (Arch btw!), and i've also been thinking about server side workflow without WM for a long time, so i've been updating some vim config these days.

My main concept is minimalism. We don't need to install bunch of plugins and use whatever they feed us, since for some plugins we only need a small subset of its functions. Instead, we can spend some time diving into their source code, learn how they work, and then try to make our own version with more flexibility, more customizable.

Old Workflow with bspwm

  • vim on the left, terminal simulator and file manager on the right side
    • do compile, or some shell shit in the terminal
      • but in most of the time, we are not editing in vim at the same time actually.
      • if we need to, like running a server or a slow-executing task, we can simply use tmux.
    • use vim --servername VIM and vim --remote-tab FILE to communicate between vim and ranger
    • but vim need to be compiled with clientserver flag on, or we can use gvim instead
  • we may achieve similar workflow with tmux, but i'm too lazy to look into it, and somehow i think the window navigation key bindings in tmux will messup with mine in bspwm. For now i only use the attach/detach feature of tmux, so maybe someday when i have time (?).

New Workflow with vim only

  • To open files:
    • if in a project, use self-defined command CdHere to set an anchor in the project root directory, and then we can start fuzzy finding !!
      • i was using CdHere until i found out autochdir. but i realized that it's acutally a stumbling stone to stop me from using fuzzy file finder.
      • since usually we have our .git/ in the root of a project, so we can further use fzf to search around changed git files, like when using git add.
      • (btw, another useful command SudoSave when editing some readonly files)
        command CdHere :cd %:h
        command SudoSave :w ! sudo tee %
    
    • normal file navigation with file manager, which is ranger in my case.
    • opener: tabe, i know most people use buffers, but i personally use tabs.
  • terminal
    • compile, shell stuff
    • use tmux when we need to run something in the background while we can further edit our text.

Why not Neovim ?

  • i've tried it several times, but i found nothing so critial that worth a migration for me.
    • i know i can simply move my .vimrc into ~/.config/nvim/init.vim, but i think it's just fine with vanilla vim right now. if i started from neovim back then, i would totally go for it.
    • another concern is that comparing to the single-person maintained vanilla vim, the community driven neovim may be more unstable (?) when updated, just like the problem the cutting-edge distro, Arch, has.
  • too fancy, even saw some use render engine, like gtk, to achieve cool animations, but it's a little too much for me. i just need a stable, old-fashioned (or say, retro, lol), server usable text editor, with all functions in a pure .vim/, .vimrc setup, even better not to have extra support flags needed to be compiled with.
  • a good looking is also a top priority for me. but vim8 added the fancy popup window feature right after neovim did, so im currently fine with it.
  • i'm too lazy to learn lua, but neither do i like the new vim9 script language since it looks totally different from the old one.

Plugins

Plugin Manager

  • vim8's native plugin manager
    • simply git clone the plugins into ~/.vim/pack/default/start/
    • but need to pull the git repo of plugins manually when update
    • this is sort of related to my arch installation flow. i may need to maintain the git directories of plugins or i have to write what plugins are installed down somewhere (like in arch's installation script). But unlike zsh, vim plugins often change, so it makes me harder to maintain my setup.
  • vim-plug
    • install/remove with one command PlugInstall/PlugClean
    • as i mentioned above, it can be easily listed and managed in .vimrc.
  • an overview of my plugins:
    " 1. vim 8 native plugin manager (git clone at ~/.vim/pack/default/start/)
    " 2. junegunn/vim-plug:
    call plug#begin()
        " Plug 'vimwiki/vimwiki'
        Plug 'lilydjwg/colorizer'
        Plug 'kovetskiy/sxhkd-vim'
        Plug 'airblade/vim-gitgutter'
        Plug 'itchyny/vim-highlighturl'
        Plug 'voldikss/vim-floaterm'

        " ===== fuzzy finder ======
        " not needed if installed from pkg manager
        " Plug 'junegunn/fzf', { 'do': { -> fzf#install() } }
        Plug 'junegunn/fzf.vim'

        " ===== auto-complete ======
        Plug 'girishji/vimcomplete'
        Plug 'girishji/autosuggest.vim'

        " ===== airline ===== (current using powerline)
        " Plug 'vim-airline/vim-airline'
        " Plug 'vim-airline/vim-airline-themes'
    call plug#end()
  • only three plugins, which are covered in this article, needs some little tweaks. Others are some useful little plugins that can be easily used after installed.
    • fzf / fzf.vim
    • vim-floaterm
    • vimcomplete / autosuggest.vim

Auto Completion

  • completor.vim
    • need vim to be compiled with python or python3 support flag on
    • to get rid of this, i just find an alternative without the need of extra flags. there are tons of auto-completion plugins out there.
  • vimcomplete / autosuggest.vim
    autocmd VimEnter * call g:AutoSuggestSetup({ 'search': { 'fuzzy': 1 }, 'cmd': { 'fuzzy': 1 } })
    autocmd VimEnter * call g:VimCompleteOptionsSet({ 'buffer': { 'completionMatcher': 'fuzzy' } })
    inoremap <expr> <S-Tab> pumvisible() ? "\<C-p>" : "\<S-Tab>"
    inoremap <expr> <Tab>   pumvisible() ? "\<C-n>" : "\<Tab>"

Fuzzy File Finder

  • i've tried several plugins so far:
    • scope.vim
      • slightly slower than fzf, and no fancy file previews
      • but with built-in live file grep search and no other dependencies needed
    • fzf and its wrapper fzf.vim
      • fzf is prettier, and really well-documented. With command piping, fzf can do a lot of tricks. apart from fuzzy finder, it can also just be a simple selector like dmenu.
      • fzf has its onw vim port, fzf#run and fzf#wrapper, while fzf.vim is a further wrapper around it. somehow if you find it hard to write your own custom script with fzf.vim, you may consider doing it directly base on fzf.
      • but the downside of fzf.vim is that the preview script is hardcoded. i have to custom it in the git directory of the plugin. there was a pull request about passing a custom preview in the wrapper in 2019, but i don't know why it's still not accepted.
  • at first, i didn't find out the :RG function of fzf.vim, so i wrote a live file grep with the tutorial from fzf. here's it works:
    1. use rg command to search given token in files (unfortunately no fuzzy search so far) as source
    2. pipe result of rg into fzf to be selected
    3. with --bind option of fzf, reload on every keystroke to update new token queries to rg
    • ofc then i went for fzf.vim, so i am not putting my dumbass script here, lol
  • tabs v.s. buffers
    • sometimes switching betwen tabs when viewing tens of thousands lines of code is quite slow (also, at my working place, PC is constantly scanned by MIS), and there doesn't seem to be a command to directly jump to specific tabs in vim, but :b can easily do the work with buffers.
    • one buffer can be opened in multiple tabs, which is not what i want: sometimes fzf grep tokens in the already opened files, and there would be duplicate tabs holding the same file.
    • :pwd after CdHere are not the same among tabs (tabs before calling CdHere won't change)
    • so i am a buffer fan boy right now, haha.
  • after using fzf.vim's :Buffers command, i want it also to be able to delete buffers with another key binding (ctrl+d here) while selecting buffers, so i customed it a little bit. Here's how it works:
    1. with a custom sink* function sink_callback(), i can do :buffer or :bdelete on selected buffers. but how do i know if i want to open them or delete them ?
    2. so i use two bindings --bind "ctrl-d:accept+execute(echo bd > tmpfile)" to save different actions to a temp file, and then read it with the callback function afterward
    3. the --bind options take event:action syntax: here pressing ctrl-d as an event triggers two actions accept and (+) execute at the same time. obviously, we do the output bd stuff in execute, and accept makes fzf output the selected items to its own communication channel (also a temp file).
    nnoremap <C-f> :call FzfCmd('File', '')<CR>
    nnoremap <C-b> :call FzfCmd('Buffer', '')<CR>
    nnoremap <C-g> :call FzfCmd('Grep', '')<CR>
    vnoremap <C-g> y/<C-r>"<CR>N:call FzfCmd('Grep', getreg('"'))<CR>

    function! FzfCmd(cmd, init_query) " {{{
        let preview_opt = '~3:+{2}+5/2'
        let fd_cmd = 'fd -HL -E ".git" -t f 2> /dev/null'
        let rg_cmd = "rg -L --hidden --column --line-number --no-heading --color=always --smart-case "

        " for Buffer: save fzf action as tmp file and read by sink callback afterward
        let s:tmp_action = '/tmp/fzf_action'
        function! s:sink_callback(lines)
            let action = readfile(s:tmp_action)[0]
            for item in a:lines[1:]
                let bufn = matchstr(split(item, ':')[1], '\[\zs[0-9]*\ze\]')
                execute action bufn
            endfor
            return delete(s:tmp_action)
        endfunction

        " dict good, if/else bad (?)
        let funcs = {
        \	'File': function('fzf#vim#files', ['', fzf#vim#with_preview({ 'source': l:fd_cmd, 'options': '--prompt "' . a:cmd . ' > " --preview-window ' . l:preview_opt }), 0]),
        \	'Grep': function('fzf#vim#grep2', [l:rg_cmd, a:init_query, fzf#vim#with_preview({ 'options': '--prompt "' . a:cmd . ' > " --preview-window ' . l:preview_opt }), 0]),
        \	'Buffer': function('fzf#vim#buffers', ['', fzf#vim#with_preview({
        \		'sink*': function('s:sink_callback'),
        \		'options': '--prompt " ' . a:cmd . ' > " --preview-window ' . l:preview_opt . ' --multi' .
        \			' --bind "ctrl-d:accept+execute(echo bd > '.s:tmp_action.')"' .
        \			' --bind  "enter:accept+execute(echo b  > '.s:tmp_action.')"' }), 0])
        \}
        call funcs[a:cmd]()
    endfunction " }}}
  • Btw, the --highlight-line and --line-range options of bat work as a file grep preview out of the box !!
  • in fzf.vim, for the purpose of scrolling preview window with mouse (i've never tried it though), they use fzf's --preview-window option to scroll to the center highlighted line of bat, which prints out the full file. it's easier to use imo, but may take a little more memory (?).

Terminal & File Manager

  • Terminal:
    • i'm not a fan of window splitting in vim so i go for the fancy popup window terminal vim-floaterm instead of :terminal.
    • vim-floaterm also has wrappers with some good tools such as fzf, ranger, etc.
  • File navigation:
    • i used to use ranger.vim and the built-in netrw as alternatives just in case.
    • but the full-sized ranger looks so laaaaame. i really want a popup one
  • So i was trying to combine the below two flows and make a floating ranger myself:
    • How ranger in vim works
      1. use ranger option --choosefile to not directly open the file but save the chosen file name in a temporary location.
      2. then cat the file and open with vim afterward
    • How popup window (since vim8 IIRC) works
      • according to vim documentation: popup_create(bufn, options)
      • (note: this is different from Pmenu or pum in vim)
      1. call :terminal to open a terminal in window, record the buffer number, and then hide the buffer.
      2. feed the buffer number and your popup window options to the popup_create() function
      3. you may need a callback to tell what to do after the terminal closed, which is well done by vim-floaterm's wrappers
  • i know the configs below seem too simple to worth any mention, cuz i removed my stupid self-written scripts right after i found out vim-floaterm's ranger wrapper lol.
    let g:floaterm_title = ''
    let g:floaterm_opener = 'tabe'
    let g:floaterm_borderchars = '─│─│╭╮╯╰'
    nnoremap <C-n> :FloatermNew<CR>
    nnoremap <C-b> :FloatermNew ranger<CR>

    if !executable('ranger')
        nnoremap <C-b> :Texplore<CR>
    endif

    " netrw configs
    let g:netrw_hide = 1
    let g:netrw_list_hide='\(^\|\s\s\)\zs\.\S\+'
    autocmd FileType netrw nmap <buffer> h -
    autocmd FileType netrw nmap <buffer> l <CR>
    autocmd FileType netrw nmap <buffer> <BS> gh
  • looking nice :)

Some other useful configs

Syntax Highlighting

  • custom syntax for C: a little regex can do the work
    " file location: ~/.vim/syntax/c.vim
    syn match cType '\<[a-zA-Z_][a-zA-Z0-9_]*_[ft]\>'	" data_type_t
    syn match cType '\<[A-Z_][A-Z0-9_]*\>'				" DATA_TYPE or MACRO
    syn match cFunction "\<\h\w*\ze\_s*("				" functions
  • before v.s. after: much more readable
  • no need of fancy tree-sitter. i know tree-sitter can do some powerful parser stuff, but if i need syntax highlighting only, it's just an overly decorated Christmas tree for me.

Folding

  • a good looking folding script stolen from stack overflow or somewhere.
  • sorry that i cant even remember where it's from lol.
    set foldenable
    set foldmethod=marker	" indent / syntax / marker / manual
    set foldtext=NeatFoldText()

    function! NeatFoldText()
        let line = ' ' . substitute(getline(v:foldstart), '\s*{{' . '{\s*', ' ', 'g') . ' '
        let lines_count = v:foldend - v:foldstart + 1
        let lines_count_text = '| ' . printf("%10s", lines_count . ' lines') . ' |'
        let foldchar = matchstr(&fillchars, 'fold:\zs.')
        let foldtextstart = strpart('+' . repeat(foldchar, v:foldlevel*2) . line, 0, (winwidth(0)*2)/3)
        let foldtextend = lines_count_text . repeat(foldchar, 8)
        let foldtextlength = strlen(substitute(foldtextstart . foldtextend, '.', 'x', 'g')) + &foldcolumn
        return foldtextstart . repeat(foldchar, winwidth(0)-foldtextlength) . foldtextend
    endfunction

Huh ? no LSP ?!

  • yeah, i haven't done too much research on it, and i'm currently fine with auto-completion and fuzzy finder. i'll definitely look into it later.

OMAKE: My Linux Hopping History

  • 2021
    • 11/12: Start with Manjaro i3 Edition
  • 2022
    • 04/07: musikcube -> cmus
    • 06/12:
      • Manjaro -> Arch
      • terminator -> termite
    • 07/01: Try Artix
    • 08/15: Sublime Text -> vim
    • 10/03: i3 -> bspwm
  • See more of my dotfiles here.