Photo by Dries Augustyns on Unsplash
I made Neovim my main code editor, migrating from VSCode.
I knew moving away from an “ordinary” code editor would be challenging. That is why, instead of going full cold turkey, I opted for easing the process by modelling my initial configuration around behaviours I am already comfortable with.
In this article, I am going to describe the approach I followed, explaining why I preferred a solution over another. I will provide links to content I was supported by, so that any interested reader will be able to explore further.
Although I will mainly compare to VSCode, it should be possible to adopt a similar pipeline moving from any other editor.
It is possible to find related configuration files tracked in my dotfiles repository.
Also, I gathered the most helpful resources I found in the Neovim learning path. You will find at that location most of the resources I am going to reference, and also additional ones I could not fit in this article.
Of course, why
For developers, people who spend most of their daytime reading and writing code, the editor of choice is obviously important.
It often happens that is so important one has to adhere to a faction:
The line between editors and IDE is blurred these days as editors can often offer IDE-like functionalities just by installing few plugins.
Recently, VSCode in particular became quite popular for many web development needs.
I too am a VSCode user. I am a happy VSCode user, too.
Apart from the much disputed memory hunger typical of Electron applications and the existence of grey areas behind its licensing choices, VSCode is a great software I cannot really complain about. It provides nearly everything I need to support my day-to-day development activities, without leaving the editor.
Nearly everything. Without leaving the editor. That is where my problems with VSCode start. I am providing an example explain better.
I installed a neat Microsoft extension, Docker, which
Docker is powerful and complex. Thanks to the extension, it is effectively possible to consume Docker commands inside VSCode by interacting with its UI. That allows utilising Docker without having to deal with the complexity of its CLI.
That also implies, assuming one uses the extension as primary mean to interact with Docker, the understanding of it will be limited by what the plugin offers and shaped by its pre-packaged mental model.
Although simplifying development flows is essential to achieve results without needing to focus on every cog of the engine, I want to learn more, get closer to the metal and grow conscious about what happens behind the curtains of a well refined interface. I need to use tools that nudge me into learning new things.
Besides, a good part of my daily development activities lives in the terminal already, so moving to a terminal-based editor feels like closing the gap.
Learning Vim is not difficult per se. It is challenging indeed, if one tries to approach it like they would with any other text editor. One has to learn thinking in Vim to make sense of its commands interface. Memorising by brute force a bunch of keyboard combinations won’t work very well. At least, it didn’t work for me.
When I started investigating around how to migrate to Vim I didn’t know where to start. I was never a command line aficionado and my knowledge of the editor was limited to typing
:q! to quit it.
Thus, I started tackling the challenge in discovery mode, trying to gather many resources to find which one would better suit my necessities.
Since the first readings, I was already absolutely fascinated by the concept of coding by describing the desired end result, rather than mechanically executing the steps needed to achieve it.
I think the same principle applies when comparing imperative and declaritive programming styles. Who am I to tell, though.
Was I ready to start using and learning Vim at this point? Yes. Also no.
Before starting the journey, I needed to clear one final doubt: would I use Vim or Neovim?
A bit more reading pushed me towards Neovim.
I am happy I made that decision, although I confess I was not fully understanding the difference at the time.
There is a well known quote attributed to W. Edwards Deming which goes like:
If you can't describe what you are doing as a process, you don't know what you're doing.
Whilst going through more tutorials and articles, it was becoming clear customising Vim to one’s needs is among the strongest selling points of the editor, and can become a never ending task.
I needed to formalise a definition of done which could give me boundaries.
Therefore, I compiled a list of functionalities I believed to be indispensable, based on what in VSCode I valued the most. I would follow the list and flibber-jibber the frammistan to achieve similar results following “the vim way”.
Be aware I am cheating: at the very beginning the list was just a flat, chaotic collection of items. Only after I started getting my hands dirty it was possible to wrap my head around how features interact and can be grouped together.
Anyway, here is the list:
- Status bar with contextual information
- Project explorer
- Syntax highlighting
- Indentation guides
- Split windows
- Tags autocompletion
- Brackets and quotes autocompletion
- Commenting and uncommenting lines
- Multiple cursors editing
- Text case change
- Code completion
- Code navigation
- Code diagnostics
- Code linting and formatting
- Search and replace in a file
- Search and replace in a project
- Search and replace in a folder
- Files fuzzy find
- Commands fuzzy find
- Git status
- Git diff
- Conflicts merge helper
- Hunks diff
- GitHub PRs integration
- Multiple projects management
- Todos management
- Terminal integration
The list was long and comprehensive of core VSCode functionalities alongside others, available through extensions.
I decided to aim for a MVP result, to quickly start using Neovim as my primary editor, test and improve the setup against real-world development scenarios.
That means I had to leave some cool ideas behind, waiting for a more convenient time to have fun with them.
Vim doesn’t immediately surface many of the functionalities one expects to be slapped in the face with when launching any modern editor.
I already mentioned I chose to use NeoVim over Vim without precisely knowing why, other than the fact it advertises itself to be a refactor of Vim improving its extensibility and usability while remaining compatible with most of Vim ecosystem, which I took as a win-win situation.
Only when I started customising it, I understood I made a good choice for once. Neovim can be configured using Lua, which I found myself comfortable with. I stumbled upon several good tutorials, but “Everything you need to know to configure neovim using lua” and “Getting started using Lua in Neovim” were lifesavers.
By easing the learning curve of getting acquainted to a new programming language, I could focus on structuring the configuration itself, rather than fighting against an averse syntax.
I split and organised my setup into modules exposing sets of related functionalities.
Grouping features based on their relative coupling, makes it easier to maintain them, as connected ones end up living close in the filesystem. All modules ended up behaving in a predictable way by exposing a common interface.
Do you see what I did there? My preferred approach to structuring applications is not the point. What matters is I was able to reuse my development skills to design my setup.
The feeling of not just learning to use an editor, but creating my own personal coding tool by extending Neovim was thrilling to me.
It was almost like building a kallax and using a gnabbas to store my fåtalig and my favourite eftertänka.
I began writing Lua code from something simple, figuring the addition of a few colour schemes could represent a quick-win to make the terminal’s brutalist appearance a little more approachable.
Neovim supports any Vim theme, but I focussed on exclusive extensions where sensible, like it is in the case of colour schemes.
In fact, an acclaimed addition to the 0.5 Neovim release was the support for tree-sitter, which provides advanced code parsing functionalities. That means it offers, among other mouth-filling wizardries, support for better syntax highlighting. The nvim-treesitter repository lists supported languages and compatible themes.
I found shaunsingh/nord.nvim, avarasu/onedark.nvim, sainnhe/edge and rose-pine/neovim simply irresistible.
As statusline, I chose Lualine because I was blown away by its performance when compared to other similar plugins, as advertised in its repository documentation. Or maybe because it was supported and documented by the colour schemes I had just installed. Who knows.
Vim comes out of the box with a filesystem explorer, a plugin called Netrw. It happens not to be extremely appreciated by Vim users, so I installed nvim-tree.
The plugin felt instantly familiar, because it behaves similarly to what I was already used to.
Still, I was not convinced by it. It made things somehow stiffer when compared to the otherwise unconstricted feeling of interacting with files in Vim. I couldn’t explain exactly what was out of place, until I read Oil and vinegar - split windows and the project drawer.
It was too late, though. I decided to move on and leave the replanning of my approach to projects navigation, and the reviewing of my life choices for a later stage.
I was practicing with Neovim itself to edit my configuration. I was struggling.
The editor looked better, but was lacking most utilities I would usually take for granted. I needed to make it easier on me to write code.
Indent-blankline to draw indentation guides, nvim-autopairs to automatically complete pairs of brackets and quotes (I didn’t know I couldn’t live without it), nvim-ts-autotag to autocomplete pairs of tags as well, targets.vim to target what is inside or outside the mentioned pairs and vim-surround to manage all those pairs with few keystrokes.
Kommentary to comment and uncomment lines of code, nvim-cursorline to help locate where the cursor is and nvim-colorizer because I am cheeky.
Vim-abolish is definitely an interesting one. I decided to install it because of its case coercion capabilities, but it can do much more than that.
No more plugins, please.
Among my self-imposed non-functional requirements there was using few extensions as possible, to keep things tidy and minimise clashes among them.
I was therefore happy to read about the several abstractions used by Vim to represent a file, realising no extension was needed to work on multiple files at the same time.
In fact, that is an area where Vim is way more structured than other editors.
I dropped instead the idea of multiple cursors editing.
On one side that doesn’t feel very Vim-ish, and on the other side there are multiple native alternatives to achieve similar results. If I ever wanted that functionality at any cost, there would be options nonetheless.
I quite like the Microsoft term “IntelliSense”. It gives an instinctual feeling of what the term means, despite referring to a set of complex and interrelated technical features.
As I saw my editing experience improving, I got even more excited and tried to throw in code completion, too.
For the record, Vim supports autocompletion, without the need for any configuration or plugin. The thing is, as some other of its core features, Vim’s autocompletion options are granular and powerful, but rough like a pile of rocks right out of the quarry.
Besides, Neovim introduced built-in support for LSP, with the possibility of using the editor as a client to communicate with language servers and obtain context aware suggestions, autocompletion and diagnostics. IntelliSense, as friends call it.
However, LSP requires some work to be properly setup. More than I was willing to spend whilst still trying to understand how all the pieces of the puzzle fit together.
That’s why I opted for installing coc.nvim instead, with the idea of saving on the efforts needed to reach my MVP goal.
The plugin follows very closely VSCode ways of functioning. One can count on a decent amount of extensions extending the extension, many being themselves ports of VSCode plugins. Last but not least, I was able to find plenty of documentation to set everything up.
I could quickly set things up in a familiar way and see appearing nice code diagnostics and hints. This win felt bittersweet, though.
There are some aspects of the unfortunately named extension I find confusing.
Mostly, I am bothered by COC and its environment trying to push for too many different functionalities under the same flag. Besides language completions, one can find a calculator, integration with git and GitHub gists, custom lists, preview of markdown files, a custom search implementation and more.
I was tempted by some of those features, but determined to avoid creating a big ball of mud.
I cherry-picked (almost) only code completion related COC functionalities, isolating them in their own module, and delegated to other modules the burden of dealing with separated concerns.
I would deliberate later on a possible migration to the Neovim LSP client.
Software engineers often like to think that the job is to write shiny new code.
I think recruiters know that. I couldn’t explain otherwise the number of
I am getting in touch with you because we are looking for superstar developers, willing to work with cutting edge technologies on an exciting greenfield project.
in the mail inbox, as opposed to the lack of
I am getting in touch with you because we are looking for experienced engineers to help us maintain a 10-year-old web application.
It is known, software engineers spend a relevant amount of time tweaking pre-existent codebases rather than creating from scratch.
That could easily be a reason why search and replace functionalities can be valuable, allowing developers find a needle in a haystack.
Vim is rather powerful when it comes to find and replace. While investigating around a good way to translate my workflows, I was once more surprised by realising how far one could go by just relying on its core.
I couldn’t resist however, and eventually installed an extension.
Telescope is a fuzzy finder offering filter, sort and preview capabilities. It is getting a lot of attention in the Neovim community because it is flexible to the point of allowing for the creation of customised finders.
As it comes with plenty of preconfigured pickers catering for my most of my needs, it seemed sensible to include it.
On a side note, I was not surprised instead to discover flamboyant solutions to the challenge.
Dealing with version control systems falls in a category of procedures I am willing compromise on in exchange for simplification.
For example, using a GUI client gives me visibility over the global state of the repository at any time, making my life easier when I need to perform non trivial operations. It also forces me to take distance from the code editor, adopting in regard an agnostic point if view whilst thinking about branches, conflicts, diffs and whatnot.
I make use of just a few of the available VSCode VCS integrations.
On those premises, I investigated for ways to improve using git inside Vim, with a pinch of plugins.
Vim-fugitive felt almost an obligated choice because of its huge popularity. It offers Vim commands wrapping CLI ones, improving their ergonomics. I well liked the simple effectiveness of the idea.
I installed Gitsigns.nvim to have visual hints of changes applied to the current buffer, while also being able to diff and undo them, if needed.
Finally, I added to the pile octo.nvim, for a more integrated experience working with GitHub.
Although my needs were already covered, I found neogit and diffview.nvim nonetheless interesting and decided make note of them for a later thorough integration.
By the time I got to evaluate feasible solutions for the “Extra” requirements category, I had walked a long way and learned plenty.
Still, I was unsure whether the awareness of having just scratched the surface was fuelling either a sense of bubbling excitement or profound despair in me.
Anyhow, I started clearing the last requirements standing by installing telescope-project.nvim and todo-comments.nvim. They were a convenient choice, as both extend the already mentioned Telescope plugin.
Seamless terminal integration was possibly the feature I was looking for the most. As I hoped, Neovim offers transparent terminal emulation out of the box, making to open a terminal buffer feel like a first class operation.
Was that the end of the journey? Yes. Also no.
I purposely avoided talking about a central piece of the puzzle, that is synchronising on multiple machines to reuse the setup I bled so much to put together.
I decided discuss it at the end because the challenge has less to do with Neovim that it has to do with managing dotfiles in general. This is where the choice of moving to a different tool started to nudge me out of my comfort zone, pushing me into doing something I never felt the urge for. No shame.
Among numerous feasible approaches, I eventually decided to give a try to storing my configuration as a bare repository.
I was already using Homebrew to manage system packages and it can conveniently take also care of GUI applications. I chose then just to add some custom implementation to have it maintain a dependencies file.
Zsh extensions are instead dealt with as git submodules.
I will sooner or later look into oh-my-zsh, to understand whether I could find a reason for it to exist in my setup.
I appreciate compromises.
Although they are often perceived as unsatisfying decisions, they can also be a way to work out situations difficult to solve otherwise.
If I abruptly adopted Neovim by changing all my coding habits at once, I would probably have failed and would have gone back to my comfort zone.
Instead, I can cautiously state I am successfully using Neovim for most of my coding activities, fully aware that there is still a lot for me to discover about it.
In fact, I ended up compiling a list of todos and ideas that is possibly three times longer than the one I started with.
Among all the plugins I installed, there are several I plan to replace with simple customisations on top of built-in functionalities. I will probably keep only extensions that feel more integrated with Neovim’s core behaviours. I am more a purist than an exobrain, after all.
On a negative side, if one could say that, I am noticing Vim commands are beginning to feel more natural and sensible than my old ways of editing text. Sometimes I surprise myself typing
:w to save the current state of a Notion document.
As much as one would love to use Vim superpowers in any editor, they will need to come to terms with modal editing being rarely found outside of (Neo)Vim. Without the use of workarounds and dedicated extensions, that is.
To recap, I talked about moving from an editor to another, planning the migration as an incremental process and adopting strategic choices to make the transition less taxing.
A similar approach could be applied to many situations and is not exclusive to technology contexts, though.
I collected the best resources I found in the Neovim learning path, both as personal reference and as a resource for anyone interested in taking on a similar journey. It is possible to find at that URL all the links I shared in this article and more.
There are other learning paths as well. Be sure to check them out, maybe you will find something interesting.
Top comments (2)
Thanks for sharing!
I will have a look at your repo as an additional reference. Nice also the inspiration configs listed in the readme.
NvChad is a nice one. and what I like about it is that the configuration is very readable. That means it's a nice one to loot code from, but also to learn.
I also found this one github.com/ecosse3/nvim