I've spent thousands of hours editing text inside a terminal and more time than I am proud to admit, configuring Vim. Then NeoVim. Then across to Doom Emacs, and back to Vim again. It was never a surprise when my development workflow raised eyebrows and someone asked "What is that text editor?".

My initial attempts to switch to VSCode were about as successful as trying to build a castle on a swamp, but now I've been using it full time for over a year, and people still ask "What is that text editor?".

VSCode without all the visual bells and whistles

VSCode may have something of a reputation for being a resource hungry, battery draining piece of software in a why-would-you-implement-a-text-editor-inside-a-browser-inside-a-native-app kind of a way, but it also has the best out of the box experience for working with TypeScript, which is most of the work I do now.

If you open the files above with a fresh install of VSCode, you'll see a UI optimised for discovery. All possible features are turned on so that the first time user can find and understand them.

The visual war for your attention is tough, but as a design decision I think it helps flatten the learning curve. It's much easier to turn something you can see off, than to discover a feature you can't see. For some reason, not everyone wants to spend their evenings reading :help quickfix.

Lots of people seem to get excited about emulating VSCode inside Vim, which is fine if you want to build your own editor from a small tower of plugins. There are impressive efforts like SpaceVim, AstroNvim, and Nvchad which manage this complexity as standalone projects. I say they're coming at the challenge from the wrong direction. Make VSCode work like Vim!

Vim

On this noblest of quests, VSCodeVim is the main character. Editing text without modes and motions is like having to look at your keyboard to type. This extension implements a good amount of Vim's keybindings and commands [1]. I occasionally run into edge cases, but not often enough that I've ever considered embedding Neovim inside VSCode for accuracy or performance.

Theme

I prefer using a light editor theme, but I use adaptive dark mode in the mornings and evenings to reduce some strain on my eyes. I want my editor to change with the operating system. No manual restarts, or keyboard shortcuts please. GitHub Theme has (subjectively) great dark and light themes.

// settings.json
"workbench.colorTheme": "GitHub Dark Default",
"workbench.preferredLightColorTheme": "GitHub Light Default",
"workbench.preferredDarkColorTheme": "GitHub Dark Default",
"window.autoDetectColorScheme": true,

Minimap

The worst offender for misallocated screen space is the minimap. Who uses this? What do they use it for? I have no idea of the visual shape of my code, and seeing it in small doesn't help me to do anything.

// settings.json
"editor.minimap.enabled": false,

Activity Bar

I also hide the activity bar (the thing on the left that opens the file explorer and source control panels). The only things I use from sidebar are the extensions panel (which opens with ⌘ shift x) and project wide search/replace (which open with ⌘ shift f and ⌘ shift h). If I used these more often, I'd probably give them dedicated normal mode bindings with my leader key.

// settings.json
"workbench.activityBar.visible": false,

Tabs

I don't use tabs.

// settings.json
"workbench.editor.showTabs": false,

Tabs are an opt-in feature in Vim, that I never opted-in to. Instead, I prefer working with Vim's buffer model. Every file opens in a buffer that lives in memory. Vim can show the contents of a buffer in a split, but if you close that split, the buffer doesn't disappear.

Running the :buffers command will list the currently active buffers and you can use commands like :b to jump to a buffer by name or index. Commands like :bnext and :bprev navigate backwards and forwards through buffers. There's even extra fancy stuff like :bufdo for running a Vim command in parallel on every open buffer.

I haven't been able to emulate a proper buffer based workflow in VSCode, but for now I'm comfortable using a one-buffer-per-split model.

Status Bar

Vim's default status bar doesn't even show up until you have multiple splits open. Here's the info it contains.

There's a lot more going on in the status bar in VSCode.

Of course there's someone out there who needs this stuff, but for me, most of this information is superfluous. I'm reminded of tmux/i3 configs with a current weather icon in the status bar. Try working near a window, or going outside once in a while!

I remove everything from this bar other than the line/column number.

Line Numbers

Line numbers are one of the primary ways to interface with other people and compilers or stack traces.

Ideally, the compiler in question integrates with the editor to show inline errors. I use custom normal mode bindings (copied from vim-unimpaired) to bounce backwards and forwards between "problems".

// settings.json
"vim.normalModeKeyBindings": [
{
"before": ["]", "q"],
"commands": ["editor.action.marker.nextInFiles"]
},
{
"before": ["[", "q"],
"commands": ["editor.action.marker.prevInFiles"]
},
]

When it comes to following a stack trace, or navigating directly to specific line, I find it much easier to type 123G than to use the mouse to scroll there.

Having the current selection visible in my status bar means I can always see the line I'm on for casual reference, but most of the time if I'm sharing that information with someone else, I use an extension called "Open in GitHub" to open or copy a GitHub link to the file and selection that I have open.

I think line numbers are mostly a tool for mouse-heavy workflows. Vim doesn't enable them by default and neither do I. If you use a lot of vertical text motions, counting lines can be useful, but even then, you'll likely want relative line numbering.

Effective navigation is a critical part of managing inside a large codebase, and the combination of VSCode and Vim excels here.

VSCode has the semantic side of navigation covered.

These commands play well with Vim's jumplist. ctrl o (jump backwards) and ctrl i (jump forwards) are probably my most used navigational keys. I often combine them with gd for exploration. I'll jump to a type from a variable, then to the type of one of those properties, then use the jumplist to unwind the stack back to the original file.

I occasionally still use ctrl 6 to swap to the previous file, but more often I'll open both files in separate splits and hop from side to side instead.

With fuzzy finders for files and symbols, I don't find much need for breadcrumbs. Disabling them frees up space at the top of the screen.

// settings.json
"breadcrumbs.enabled": false,

File explorer

Sometimes navigation crosses into the unknown and becomes more about discovery. This might happen when you've forgotten the name of a file, or you're working in a new directory, without any intuition for the structure. These are the times where you need a file explorer.

Vim comes with a directory explorer called Netrw (comparable to dired in Emacs). It opens its own buffer in the active split. "Oil and vinegar" is a classic read, which compares split explorers and project drawers. I was convinced a long time ago and I haven't looked back.

VSCode doesn't have anything like Netrw. I was also surprised to see that no-one had implemented it as an extension either. I decided to build it myself.

Vsnetrw is the text-based split explorer that I made for VSCode. I use it extensively every time I use VSCode, making it my highest value personal project. It supports a subset of Netrw's features (the net component is absent), but for navigating, creating, deleting, and renaming files, it's a joy to use.

Version control

It's well established that Magit is one of the "killer apps" for Emacs. I've even heard of Vim users who open Emacs purely to interact with Git. For a long time I used vim-fugitive, and after spending a year in Emacs, the built-in VSCode source control panel just doesn't cut it.

Thankfully, for that, there's Edamagit, a Magit implementation for VSCode. A Git buffer is only ever a <leader>gg away.

From this buffer, I can manage branches, stage changes, stash, tag, push, pull, cherry-pick, rebase, commit, and lots more. Best of all, the entire UI is text based, so you can navigate through it using motions and patterns you use everywhere else.

It needs some extra keybindings to work well with VSCodeVim, and even then there are some conflicts, but this is the best version control workflow I've had outside of Emacs.

Tmux

Whilst not a feature of Vim at all, Tmux has been an integral part of managing sessions and processes in my workflow for a long time, and switching to VSCode means the editor can't be a part of that session.

For some simple projects I've stopped using Tmux altogether, in favour of having a VSCode task that starts a given process.

For example, when I sat down to write this article, I pressed ⌘ shift p (to bring up the command palette), then selected "Run task" and "npm: start" to start Eleventy's build process and server.

For more complex projects, I still use Tmux. For most of the projects at work, I'll be running a set of Docker containers, a server, a frontend build process, and at least one regular shell. You could run these tasks in separate VSCode terminals, but I like to be able to detach the session entirely, when I'm switching projects, without having multiple instances of VSCode open together.

Spot the Difference

Let's open up the original session in Vim and see how close we managed to get.

And here it is again in VSCode.

Pretty good, right? It's not going to be for everyone, but I find that I'd much rather opt-in to distractions, than have them onscreen at all time.

What Do I Prefer About VSCode?

No text editor is strictly better than any other, and there's a weird amount of energy wasted on tribal superiority that I don't want to contribute to. It all comes down to personal preferences and operational tradeoffs.

That said, there are some VSCode features that do not exist in Vim, or that have alternatives that work better for me.

What Do I Miss From Vim?

Farewell, Vim?

Vim isn't going anywhere for me. I use it on servers, I use it inside Docker containers and virtual machines, I use it for one off tasks when I can't justify starting a VSCode session.

At the moment, I'm happy in VSCode and nothing is pushing me towards changing. For all my complaints about Vimscript, I'm also not wildy excited about the current state of the Neovim ecosystem and its transition to Lua.

When you customize VSCode, you are configuring an editor. JSON works fine for this. It's not perfect, but supporting comments gets it close enough. When you customize Neovim, you tend to be building an editor. You're connecting and configuring plugins for package management, for LSP, for fuzzy finding, for searching, for snippets, for completions, for version control. I'm done with the "handcraft your own editor" thing.

At the same time, I'm not overly jazzed about the direction Vim itself is going with vim9script either. Is it a better language than Vimscript? Yes. Was it necessary to implement a new programming language to achieve that? Probably not. Vim and Neovim seem destined to diverge.

Why do some programmers habitually configure and tweak their text editors? Well, the simple answer is that it's fun. I also need to enjoy editing text to program or write effectively. If my editor is distracting, or flaky, or inconsistent, then that can interrupt my thought process.

There are lots of other tweaks I made that aren't worth mentioning here, but you can find the entire VSCode config in my dotfiles repo.


  1. VSCodeVim Roadmap ↩︎

  2. https://github.com/VSCodeVim/Vim/issues/8157 ↩︎