118

Vim After 15 Years | Ian Langworth’s Things of Variable Interest

 6 years ago
source link: https://statico.github.io/vim3.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Vim After 15 Years

17 October 2017

My earlier posts (1, 2) about using Vim were well received and it’s about time for an update. Vim 8 added a lot of much-needed functionality, and new community sites like VimAwesome have made plugin discovery and evaluation easier. I’ve been doing a lot more work with Vim lately and have spent some time configuring my workflow for peak efficiency, so here’s a snapshot of my current state.

TL;DR:

  • fzf and fzf.vim for finding files
  • ack.vim and ag for searching files
  • Vim + tmux is the key to victory 🔑
  • ALE is the new Syntastic because it’s asynchronous
  • …and lots more. Keep reading.

vim3.png

A recent Vim session

As always, my dotfiles and vimrc are available publicly. I also have a separate install script for updating and installing Vim plugins.

TextMate and Sublime Text showed us that the fastest way to find a file is by fuzzy finding, which means typing parts of a filename or path or tag or whatever you’re looking for, sometimes even if the characters aren’t adjacent or you making a spelling error. Fuzzy-finding is so useful that it’s become a standard feature on modern text editors.

For years Ctrl-P has been the reigning fuzzy-finding champ, but a new tool, fzf, is faster and more forgiving when trying to find one file or tag among thousands. Ctrl-P used to do okay on a 30,000-file codebase on my 2013-era MacBook Pro but started to slow down during a search on an enormous tags file to the point of being unusable. fzf, however, shows no speed difference between files or tags – it’s blazingly fast either way.

</video>

Getting started with fzf is easy. Simply follow the installation instructions (basically brew install fzf on macOS with Homebrew) and install the additional fzf.vim plugin for badass lightspeed functionality.

fzf comes with a basic Vim plugin but its functionality is minimal, so fzf.vim was created to provide all of the functionality you would expect. The most useful commands are :Buffers, :Files, and :Tags, which I’ve bound to ; and ,t and ,r respectively:

nmap ; :Buffers<CR>
nmap <Leader>t :Files<CR>
nmap <Leader>r :Tags<CR>

Binding ; is important because I live and breathe buffers. I practically never use tabs – more on that later – so it’s important that I can switch my focus to something I’m thinking of with as little friction as possible.

When using fzf, make sure to tell it to use ag, a grep/ack replacement called the Silver Searcher. ag will in respect your .gitignore and your .agignore files so you no longer need to keep a giant wildignore string in your vimrc.

fzf works in the shell as well and comes with bindings for Zsh, Bash, and the Fish shell. In Zsh, I can hit Ctrl-t to instantly fuzzy-find any file in the current directory. And since I’ve configured fzf to use ag, it’ll ignore anything excluded by .gitignore. It’s glorious.

Here’s the snippet from my .zshrc. The FZF environment variables are also used when fzf is called from within Vim:

# fzf via Homebrew
if [ -e /usr/local/opt/fzf/shell/completion.zsh ]; then
  source /usr/local/opt/fzf/shell/key-bindings.zsh
  source /usr/local/opt/fzf/shell/completion.zsh
fi

# fzf via local installation
if [ -e ~/.fzf ]; then
  _append_to_path ~/.fzf/bin
  source ~/.fzf/shell/key-bindings.zsh
  source ~/.fzf/shell/completion.zsh
fi

# fzf + ag configuration
if _has fzf && _has ag; then
  export FZF_DEFAULT_COMMAND='ag --nocolor -g ""'
  export FZF_CTRL_T_COMMAND="$FZF_DEFAULT_COMMAND"
  export FZF_ALT_C_COMMAND="$FZF_DEFAULT_COMMAND"
  export FZF_DEFAULT_OPTS='
  --color fg:242,bg:236,hl:65,fg+:15,bg+:239,hl+:108
  --color info:108,prompt:109,spinner:108,pointer:168,marker:168
  '
fi

I was going to write about how the one big drawback to fzf is that it’s an external command and doesn’t work with MacVim, but now it does! Support has been recently added by using the new native terminals in Vim 8. It works well but is much slower than the terminal in large codebases (~1m files).

An aside on fuzzy-finding

While FZF and Ctrl-P and other editors support fuzzy searching for pathnames, I’m really hoping that someone will create a first-character search for Vim. In IntelliJ, for example, if you want to open the class FooFactoryGeneratorBean you hit Cmd-o and type FFGBEnter to open it (the first letter of each part of the class name). This would be great for searching tags since class names are often camel no matter which language you’re writing. Maybe it could treat characters before underscores as the first character so typing something like fbbq would highlight a file like foo_bar_baz_quux.js.

Searching & the QuickFix window

ag is the new ack, which was the new grep. The best way to use ag from within Vim seems to be ack.vim, which is misleading since ag.vim is deprecated, but ack.vim supports both ack and ag.

ack.vim gives you an :Ack command, which takes arguments in the same way as running ag from the command line, except that it opens the QuickFix window with the list of search results:

</video>

Note that :Ack will jump to the first result in the QuickFix list by default. If you dislike this, use :Ack!, or reverse the functionality of the two commands per the docs.

(Somewhat confusingly, fzf.vim adds a :Ag command, which uses fzf to search with ag interactively. I bound it to ,a to try it out. I haven’t found it very useful, but it’s kind of cool.)

Once results are in the QuickFix window, the most straightforward way to use it is by moving the cursor there and hitting Enter to open a result. There are also the :cnext and :cprev commands to move and up down the results list, and I tried to find a cross-platform keybinding I liked for these for a while, but failed. Then I discovered vim-unimpaired which adds useful bindings like [q and ]q for :cprev and :cnext. vim-unimpaired actually has a lot more bindings for next/previous pairs, like navigating compiler/linter errors and toggling common options like line numbers, that I’d argue should be built into Vim.

Using the QuickFix window for search results so useful that I wrote a few bindings which search for the current word under the cursor. As much as exhuberant-ctags tries to find tags in Ruby and CoffeeScript, sometimes you just need to search for the word that you’re staring at:

nmap <M-k>    :Ack! "\b<cword>\b" <CR>
nmap <Esc>k   :Ack! "\b<cword>\b" <CR>
nmap <M-S-k>  :Ggrep! "\b<cword>\b" <CR>
nmap <Esc>K   :Ggrep! "\b<cword>\b" <CR>

Finally, after I’m done searching and navigating, I usually hit \x (bound to :cclose) to close the QuickFix window. I’ll probably need to navigate back to the file I was looking at before starting the search so I usually hit Ctrl-o a few times, which jumps backward in the jump list and is kind of like hitting the Back button in a browser. Other times I’ll use ; to bring up the buffer list and find the original file there. But now that I’m thinking about it, maybe I’ll modify the binding set a global mark in my Meta-k binding, like o, so that 'O will always take me back to where I started.

Terminals, panes, and multiplexing

I mentioned before that I’m not a frequent gvim/MacVim user. I strongly prefer to work in a terminal, but there are some good reasons to use a standalone Vim application:

  1. It’s more responsive than Vim inside tmux inside a terminal
  2. It’s a better default application than TextEdit to open .txt files on macOS and Windows
  3. It doesn’t have a problem clicking past the 220th column in wide editor windows
  4. If you’re writing a long blog post with lots of spelling errors and terminal Vim won’t show underlines, or maybe you prefer the wiggly “undercurl” style lines
  5. You demand to use the true Solarized color scheme instead of the blasphemous scheme created when Solarized is quantized to 256 colors

Other than the proximity to a command line, a big reason to use Vim in a terminal is tmux which is popular for remote development but just as useful for local development. As of now, tmux is my daily fullscreen working environment, and Vim usually takes up one of the tmux panes. This lets me use Vim while keeping a few other shells open – usually a server and one or two other utility panes. Sometimes I’ll make Vim temporarily fullscreen with the zoom keybinding.

The killer feature of tmux is the ability to send keys to tmux panes from anywhere. I use tmux and Vim like an IDE – I can edit in one pane, execute commands in another, and I can keep the server log visible in case there are errors. For example, if I’m working on a REST endpoint, I can re-test the endpoint with curl and view the output with jq using a few keystrokes, like this:

</video>

The regular way to do this would be to make a change in Vim, hit :wEnter to save, then <prefix>h to move to the left pane (where <prefix> is the tmux prefix keystroke, usually Ctrl-a), then UpEnter to repeat the command, and <prefix>l to go back to Vim. But it’s much faster to rig up a Vim keybinding to do all of this:

nmap \r :!tmux send-keys -t 0:0.1 C-p C-j <CR><CR>

The above runs tmux send-keys, which tells it to send keys to the session, window and pane 0:0.1 where I had run curl previously. It then sends Ctrl-p, which is equivalent to hitting Up, which pulls the previous command from history, and then Enter to execute it. I bound it to \r as in “run” or “repeat.” You can read more about using send-keys here and here.

I’ve been using this for half a year and it’s been a massive productivity boost. However, it’s worth mentioning that Vim 8 now supports in-editor native terminals, after some basic use they seem pretty solid. While various plugins have tried to integrate terminals into Vim before with lackluster results, the new native terminals are fast, Unicode-aware, and 256 color-enabled, and there’s a new term_sendkeys() function that lets you send keystrokes like tmux. This was only added to Vim a few months ago so I need to experiment. Who knows, I might end up using MacVim splits with :terminals instead of tmux.

A note on terminals on macOS

I’ve been using iTerm2 instead of macOS’s default Terminal.app for as long as I can remember. I recently noticed, however, that typing in Vim inside iTerm2 felt sluggish, especially inside tmux. I tried using urxvt inside XQuartz to compare and it felt like lightning. Something was clearly adding latency, but I wasn’t about to make urxvt my primary terminal on macOS because of the clipboard woes, focus issues, and lack of high-DPI support on XQuartz.

A few days after noticing this I read an article that demonstrated input latency between terminals on macOS and claimed that Terminal.app is now significantly faster than iTerm2. I tried it myself and found that the keystroke latency was somewhere between urxvt and iTerm2, so I’ve switched to Terminal.app completely. I was using a fancy custom iTerm2 color theme and was pleased to find a project which has converted all the themes for Terminal.app.

I miss one thing about iTerm2 vertical splits, and that’s the occasional use case where I want different font sizes in different panes. It’s easy to do this with iTerm2, or in fact any editor environment where the editing area isn’t a single grid with fixed sized cells, but I can live without it for now.

Writing prose in Vim

Distraction-free writing is popular and for good reason – it works. There are some nice-looking native and browser-based applications that do this, but I want to do my writing in Vim, so I worked on a solution.

vim3-prose.png

A great plugin is goyo.vim, which adds lots of padding to your buffer and hides all the cruft. It recognizes airline/powerline/lightline status bars so those get hidden too – well, mostly. That plus a few other settings tweaks is something I call Prose Mode:

function! ProseMode()
  call goyo#execute(0, [])
  set spell noci nosi noai nolist noshowmode noshowcmd
  set complete+=s
  set bg=light
  if !has('gui_running')
    let g:solarized_termcolors=256
  endif
  colors solarized
endfunction

command! ProseMode call ProseMode()
nmap \p :ProseMode<CR>

This command, which I’ve bound to \p, turns on Goyo and gets rid of any funny source-code like indenting when you type parentheses. It also changes the color scheme from my regular dark theme to the light version of Solarized, which is important because it becomes a visual reminder that I’m in “writing mode” and that I shouldn’t mess around or get distracted since my goal is to produce words.

The command also makes autocompletion pull words from the thesaurus and dictionary when I hit Tab in hopes that I’ll be able to write faster. It’s still a work-in-progress, but it’s come in handy now and then.

Linting

One of best and most sorely-needed additions to Vim has been asynchronous process control. Now that Vim can finally run processes in the background, a good new plugin called ALE is gaining on Syntastic because it runs the linters asynchronously. You no longer have to wait for your linter to finish every time you write a file. I’ve been writing a lot of Ruby on JRuby lately and the linter takes a while to run, so I had turned Syntastic off because of the delays. With ALE I can now turn linting back on while I edit.

Lightline, Powerline, Airline, and status bars

I was using Powerline for the last few years and eventually converted to the lighter-weight Airline. But the information and widgets in these status bars are more distracting than useful – I don’t need to know the current file encoding or syntax type – plus I’m not excited about using hacked up fonts. I switched to Lightline and spent a little effort to make it minimal and add linter status icons:

vim3-ale.png

I don’t see the need of showing the current git branch name in the status line, especially with a terminal being a keystroke away. I also don’t like the idea of putting the git branch in the shell status because it’ll become inaccurate if you switch branches from another shell. But, clearly I’m in the minority here, so maybe I’m missing something.

If you’re using Git, a few plugins are important.

vim-gitgutter is a plugin that shows you markers for any lines that have been added, delete, or modified, like most other editors do nowadays. I changed it to show a colored dot (·) for changes instead of the default - and + characters, which I think looks cleaner.

vim3-gitgutter.png

vim-fugitive seems to be the most popular Git plugin for Vim and has lots of capability. I have tons of shell aliases for git so in Vim I rarely use anything other than :Gblame and :Gbrowse, but it’s got a lot of other nice things you’d expect from in-editor Git tools. (If your repo is hosted on GitHub you’ll need vim-rhubarb to get :Gbrowse to work.)

:Gbrowse is wonderful – it opens the current file with optional line selection in the browser, assuming your repo is mirrored on GitHub or GitLab or whatever. It’s even more useful now that GitHub displays links to specific commits and line numbers as snippets when used in issues and pull requests. All you have to is select a few lines with Shift-v, do a :Gbrowse, copy the URL that opens, and paste it into a GitHub comment to get something like this:

vim3-github.png

I thought I was going to talk about RootIgnore and how it sets the wildignore automatically based on your .gitignore. This turned out to be a bad idea because tab-completing paths on the Vim command line doesn’t work if the path is in wildignore. Worse, the built-in expand() returns null if the path you ask it to expand is in ignored. It took me a while to figure out that this was causing my .gitignore-ed host-specific .vimlocal file to not be sourced by my checked-in .vimrc.

Buffers, buffers, buffers

I’m a staunch user of buffers. I’ve tried using tabs but never found them useful. All tabs do is create an additional way of hiding information and they require you to memorize another keybinding or command to get at them. If you’re using tmux, it’s simply easier to open Vim in another pane. And if you’re making good use of buffers, it’s easy to get at the file you’re thinking of with a few keystrokes using FZF as described earlier.

If you haven’t really used buffers they’re easy to understand: Once you start Vim, any file you open or create becomes a named buffer. You can view them using the :buffers command, and navigate to one of them using :buf <name>, where <name> is any part of the filename of the buffer, or the number shown to you with :buffers.

If you start Vim from the command line with multiple files as command line arguments then each file will already be open in a buffer for easy access. If you’ve installed vim-unimpaired you can use the [b and ]b keybindings to navigate between them easily.

As I mentioned earlier, I’ve sped this process up considerably by binding the ; key to the FZF :Buffers command so a single keystroke brings up a buffer list with fuzzy-finding. For example, if I open three files on the command line like vim foo.txt bar.txt quux.txt, getting to quux.txt is simply ;qEnter. (Yes, using :buf is close, but fzf shows you a live preview when you have many similarly-named files open.)

Sometimes I create buffers by accident, such as when trying to open a file with :e and hitting Enter too quickly. The :bd command can be used to delete the buffer and remove it from the list, but it will also close the Vim window or split if that buffer is open in it. A good solution is to use bufkill.vim, which provides :BD to delete the current buffer and keep the current window open. I use this often so I’ve bound it to Meta-w.

If you need to rename, chmod or delete a file, you can pop over to a terminal and make the change, but then the Vim buffer will be out of sync and show you an annoying “File is no longer available” warning. Instead, you can use NERDTree to highlight the current file with :NERDTreeFind, hit m to modify it, and choose an action like move or rename. The solution I prefer is to use vim-eunuch, which adds a bunch of commands: :Chmod chmods the current file, :Rename renames the file in its parent directory, :Move can move it to a new path, and :Delete will delete the file and the buffer. There are a few more commands but those are the ones I’ve used the most.

Miscellaneous other plugins

A friend turned me onto vim-polyglot, which bundles 100+ syntax plugins into a single package and makes them load only on demand. It’s kept very up to date, and the author has picked the best syntax plugins to make sure indenting and highlighting works well for the most popular languages.

Commenting out code is a common activity so it makes sense to use a plugin that is smart enough to comment lines or blocks of code in multiple languages. You can usually get away with :s/^/# if you’re writing code that uses hashes to comment out lines, but I prefer the vim-commentary plugin, which makes commenting and uncommenting in any language simple with gc.

The vim-surround plugin is so useful that it should probably be built into Vim. It adds keybindings to add, remove, and change the surrounding characters of any bit of text, such as changing single quotes to double quotes or brackets to parentheses. Unfortunately, the . key doesn’t repeat these changes by default, so you need repeat.vim to make that happen. For example, to change the quotes used for multiple strings, use the cS'" or combination once, then use . to repeat the substitution on the next string.

If you’re writing Ruby or any language with end-block keywords then you’ll be writing a lot of ends. The endwise plugin inserts them automatically, which is nice. And if you’re writing HTML or XML, you should certainly use closetag.vim plugin which closes tags automatically when you type </.

In the original Vim post I mentioned some tab and space fixing macros to deal with codebases that use different tab and space variations. However, sleuth.vim can detect these settings automatically by scanning files when they’re opened. It works 90% of the time and makes the macros I set up mostly unnecessary.

Thoughts on Plugins

With the recent improvements to Vim and VimL, such as asynchronous process control and some indispensable types, the plugin ecosystem is thriving. A new plugin site, VimAwesome, makes finding popular plugins easy and has well-formatted documentation and install instructions.

Some of the responses to my previous posts included occasional backlashes against using Vim with lots of plugins. Part of this is understandable suspicion – any system which allows users to add unordered extensions to patch any part of itself without constraint can easily become a mess. Just look at WordPress. Or, if you were around 20 years ago, the horrors of Mac OS Classic extensions. There’s no way to declare dependencies and debugging interactions between plugins becomes the norm.

Vim plugins aren’t too bad, though. Debugging an interaction between plugins X and Y usually involves googling “vim X with Y” and I’ve only had to do it once or twice. The one time I experienced weirdness I had to binary-search my way through (like Conflict Catcher used do twenty years) ago and had to rename one plugin so it loaded before another. I’m not proud, but so far it’s been the only bad plugin interaction that I’ve encountered.

Additional resistance towards plugins seems to be some kind of purist animosity against straying away from some core set of Vim functionality. But if you’re using Vim, you’re already in a subset of people who demand that editing text be fast and efficient, so it’s like a group of savants arguing about which is the most eccentric. The set of people that use a movement plugin like EasyMotion or vim-sneak will argue that they’re more efficient than vanilla Vim users, and vanilla Vim users will argue that they’re more efficient than non-Vim users, and so on. The argument will be moot when we can control computers with our brains anyway.

I’ve also heard of practical resistance to plugins, such as when a plugin needs a version of Vim with Ruby or Python or who-knows-what compiled in, and maybe the plugin itself needs to be compiled. Vim 7 added a lot of essential language features so a lot of plugins are now pure VimL and don’t need an extra language dependency. Combined with vim-pathogen which adds everything in ~/.vim/bundle/ to Vim’s runtime path, adding plugins should be as easy as a git clone.

My opinion is as follows: If a plugin provides useful functionality that I wish were built into Vim, it’s worth installing. Otherwise, I try to keep the number of plugins at a minimum to avoid interaction problems and maintain crisp performance when starting Vim and viewing files. The plugins and configuration I list here are more about efficiency and getting stuff, but only to the point where I don’t need to completely rewire my brain.

Vim isn’t the only editor

There are a lot more interesting editors than there were four years ago. Atom and Microsoft’s Visual Studio Code have emerged now that browser-based native applications are practical. Sublime Text continues to be a great application. IntelliJ IDEA now has a free Community Edition. All of these have support for Vim-like modes and are worth trying or using in certain situations.

New programmers often ask me which editor to use, and I always suggest starting with Sublime Text. Its interface is familiar, it has a great plugin ecosystem with up-to-date syntax highlighting, and it works well on macOS, Windows, and Linux. If you’re learning programming then you don’t want to also learn Vim’s strange arcane combinations of letters and different editing modes just to move and edit text on the screen. Though some of those people have later picked up Vim and remarked at how supremely fast and powerful they feel.

The best editor for Java is probably IntelliJ IDEA. The Community Edition is available for free and has all of the features a modern Java or Kotlin developer probably wants or needs. It’s got nice Maven build support, solid Git integration, amazing refactoring support, intelligent completion, snazzy function parameter hints, smart indexing and searching that’s better than ctags, and its interactive debugger can be essential. In fact, when writing Ruby, if I need to debug anything and need more than one or two puts I’ll fire up IntelliJ and use the debugger. And if you miss Vim, the free IdeaVIM plugin gives you Vim keybindings and works reasonably well.

I haven’t tried Visual Studio Code, but I might give it a go when I start writing TypeScript, as both are developed by Microsoft and work well together. I’ve seen some GIFs and I’ve been impressed at its completion, and the things I’ve read about it are generally positive. A friend of mine claims that the YouCompleteMe Vim plugin is essential if you’re writing C or C++, and it has TypeScript support, so might try that as well.

NeoVim looks interesting, but I don’t plan on giving it a try. When it was announced it boasted asynchronous job control and native terminals, but those features have been added to the Vim core. If there’s a killer reason for using it, let me know.

Conclusion

My focus nowadays is on getting stuff done instead of mucking about with tools. But Vim has always been one of those things where a little work and research pays dividends. Browsing VimAwesome.com or reading a few lines in a help page can dramatically improve one’s effectiveness. Most of the plugin or configuration tweaks I’ve made are from running into some annoyance and thinking, “There has to be a better way to do this.”

I hope that this post has been useful. Let me know what you think in the comments.



About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK