My thoughts about editors in (early) 2021

editors, productivity-platforms

2021-03-22 21:00:00 UTC, by Dimitri Sabadie — feed


For the past year, I have been moving back and forth between Neovim, my main, daily editor, and Doom Emacs, an Emacs configuration distribution focusing on bringing a super consistent, powerful and overall top-notch user experience for Emacs. This article is a direct follow-up to the previous one from fall 2020.

In this blog entry, I want to share something a bit different: instead of presenting my experiences with all those editors, I want to take some hindsight and analyze a bit more the technology and what it seriously means in terms of features and productivity. Obviously, I will reference other editors I have used a lot (I use IntelliJ a lot at work, and I am used to VS Code and Atom as well).

Current status as of March 2021: I am lost

Yeah, you read that title right. I am lost. I have been a Vim / Neovim user for something like 12 years now. I am used to the ecosystem, I have installed hundreds of plugins in the past decade, tried so many things, discovered pearls that I still use today (hello context.vim), I have been using a workflow based on tmux, Neovim and my shell that has proven very powerful and yet to be fully replaced. But — and I think it is important to make that note — I did not start coding with Vim. Today, I am 29. I started coding when I was 11, using a tool called Code::Blocks. Rapidly, I switched to Emacs – by the time, Doom Emacs did not even exist, and spent several years using it. Later, when I was between 16 and 17, I switched to Vim — I guess the modal idea got me intrigued and I felt in love with it. In 2014, I was told that Vim was getting forked for the reasons we all know and I decided to switch to the fork, called Neovim.

Since then, I have been sticking to Neovim but I do not partake in those “editor wars”. “VS Code sucks”, “how do I even exit Vim?”, “Emacs is bloated!”. I have my personal opinions on the technologies (i.e. I dislike very much the front-end ecosystem / electron, even though I think the front-end idea — i.e. running code in a browser — is an interesting idea; I just think the current technology for doing so is really bad), so obviously I will not pick something such as Atom / VS Code, but I cannot say they suck. I have tried them. I see why people like them and it would be very dishonest to say those editors suck. They really do not. Same thing applies to the products by JetBrains. In the past year, I did not understand why some people would pay money for an editor. I have to use IntelliJ at work and even though I would not pay for an editor, I start to grasp why people would actually want to pay for one. Not all people have the patience and time to tweak an editor to their taste, such as with Vim / Neovim / Emacs. For those people, editors such as VS Code, IntelliJ, Atom and others are really just superior.

Just take LSP. Configuring LSP in VS Code is a click on an install button, depending on your language. That is all. You get all the good defaults and behaviors and you can already start enjoying coding with LSP. Doing the same in an editor like Neovim is a very different experience. I am not saying it is bad; it is different. If you are, like me, someone who enjoys spending time tweaking their tools, then you should not care too much. I have a colleague at work who gave me an argument that I find very interesting: he basically wants an editor he does not have to configure, so that we knows how to use it everywhere, on any machine of any colleague. That is not a point I would make, but for someone who cares about that topic, it is a solid argument in favor of tools such as IntelliJ or VS Code.

In the previous company I was working for, I worked with @gagbo, who is an active Doom Emacs contributor, and I was surprised by the editor he was using on his laptop. He mentioned Doom and on the night home, I had a look at it, and I was really blown away. Doom Emacs (Doom for short) was a revelation to me, because it is using emacs as a productivy platform more than a simple editor. @hlissner, Doom’s author, and the Doom community, have been gathering the best of emacs and vim into a single configuration distribution. The result is so interesting and so powerful that I have been doing back and forth between Doom and Neovim. More on that below.

Lately, I have been more present in the Neovim community, contributing mostly via a plugin that I have been making: hop.nvim. That plugin is a complete rewrite from scratch of EasyMotion (I have not even looked at how they do it on their side) I made by using the Neovim Lua API directly. Obviously, it provides a much better experience than EasyMotion in Neovim, simply because it uses features such as virtual text and extended marks and doesn not touch the buffer, allowing to benefit from Neovim-0.5 crunchy features, such as the native LSP client and treesitter parsers. I also made a bunch of PRs to Neovim core, reading a bit on the C code, etc.

All that to say that… given the experience that I have with Neovim and Doom… I have no idea what to think about “editing” anymore. Neovim is growing and lots of interesting ideas are landing / will land in the next release. I do not really know whether people will stick around Hop, but I think it has found its place — at least, I spotted a missing piece and provided it. I am not a fan of Lua but I have to admit that it attracts more people doing some awesome things. Among the things that I think are going into the right direction:

So what is wrong?

My experience with Doom Emacs really changed the way I look at software, and more specifically at “productivity tools.” See, my current workflow is based on tmux, Neovim and my shell, which is zsh (vanilla, I completely ditched Oh My Zsh as I find it useless and I uninstalled starship.rs a few weeks ago because I realized I hated the package manager version annotations on my prompt, that I actually never needed). For people wondering, we have spent so much time trying to install cool things that we forgot how powerful raw and vanilla zsh is. This is my current prompt:

At work, I need a bit more information (i.e. Kubernetes (k8s) namespace and context / cluster I am currently connected to and AWS), but as those information are not path-dependent, I put them in my tmux statusline instead.

The thing is… I just realized by using Doom Emacs that I do not need all of these anymore. Doom Emacs provides an environment that is very similar to my tmux + neovim + shell combo. It provides even more, depending on the packages you install. And the thing is: for each tool, emacs have more opportunities, because it is not limited by a terminal implementation (i.e. cell-grid / line based application). So for instance, you can have a k8s / AWS / docker client inside Emacs that is likely to be much much better than the one you will use in CLI (I honestly dislike kubectl a lot, it has so many switches and grepping through them by piping with rg or using some switches such as -l feels weird). You can easily see a buffer containing the list of pods / containers / whatever, that you should be able to work with the tool you already use (i.e. Ivy for instance), interact dynamically with (instead of having to type one line after another to make effects). It is even more true when you consider the actual shell: you do not need zsh anymore when using Emacs, because it has its eshell implementation that is both super weird and super nice. You want to script something for your shell? Just write ELisp. You do not have to use that horrible bash script language, and you will benefit from the whole ELisp ecosystem. Completion for commands in eshell is basically the same as completing a regular buffer, which is weird at first but actually makes a lot of sense. And you have dozens of other examples. Org-mode is one of the best plugin of Emacs, allowing to take notes, make wikis, handle tasks and calendars, in a way that feels very seamless with the rest of the Emacs ecosystem. I know some people will tell me to use another tool (I do, my current tool for that is toodoux, a tool I made, similar to task-warrior). And of course, magit, which is by far my favorite Emacs plugin. As others say it, it is hard to explain why magit is so much better than the git CLI or VS Code’s integration or basically any other editor. It is just made very well, with pretty much anything actionable from the UI, with amazing workflows and even actions you do not have easily with the CLI version.

What is an editor in 2021, anymore?

Today, when I look at Doom, at Neovim, at what people are trying to do in Lua with Neovim, I feel like I need to stop trying to implement / follow what others are doing and think a bit about whether we are doing the right thing. Neovim, currently, is going towards a very exciting direction with all the effort regarding Lua, LSP, treesitter and community plugins. But I think the problem is bigger than Neovim. And I think I know that because of how I can use emacs with Doom. I think I come back to Doom so often because they understand well what it means to create a productivity platform more than an editor. It does not mean the editor is any less good, au contraire. In Doom, there is a default package that gets installed if you ask for the project module. That package is projectile, a wonderful package that provides with you project management from within emacs. If you use emacs in daemon mode, it means that you can get the same workflow as with tmux, Neovim and a shell, but with a native implementation of this project / workspace workflow, in a much much more powerful way (because it will obviously plug in things like Ivy, allowing you to do fuzzy searching of projects, etc.). The overall experience by having a consistent platform is something I had completely ignored for years and now that Doom hit me, it hit me hard. I do not know any other platform like emacs, besides composable environments such as tmux + Neovim + shell. Yes, you have VS Code / Atom / IntelliJ / etc. that will have tons of plugins to get you terminals and other kinds of integrations, but for having used them, it is clearly not as powerful as what Doom does — and it is most of the time based on electron; big no. To sum up, Doom is a bit my VS Code: it provides this amazing productivity platform, but it is fully native, modal-centric and highly composable / customizable.

Composability is really important to me, but I have to face reality: there is no way to make a workflow such as tmux + Neovim as snappy and fast as projectile. tmux basically reimplements a terminal inside a terminal, which has a lot of implications (copy-pasting with the mouse in tmux is a challenge even Chuck Norris does not want to take). And once you turn the computer off, tmux loses everything. You have plugins, such as tmux-resurrect, that will allow to save and restore the tmux layout (sessions / windows / panes), but you will obviously lose the contents of each panes. With projectile, because it is a native emacs thing, you will not lose anything.

This whole idea has been obsessing me for months. The concept of a “productivity platform.” People laugh at emacs as being bloated but… it basically implements their shell, tmux and editor in a more elegant way — and more. Why would one laugh at that? Especially when it allows more interesting kinds of interaction.

So I have been wondering: what is a productivity platform, today, in 2021? Is it this workflow I have been using since I am 16, with tmux and Vim / Neovim? A friend on IRC told me lately that if I have been using this workflow (as others do) for so long… it has proven being good so why would I want to replace it? Even though this is a valid point, I do think this is missing the point: ask some workers to dig a hole for planting trees, they will start digging the ground with their bare hands. Later, give them a shovel and they will accept it as a better tool and will enjoy it. But does it mean it is the best tool for planting trees? What if a better tool existed? This is basically the reason why I never accept something as an ultimate solution: we can always enhance our tools. And having seen what Doom Emacs do, I do think that I have found new ideas. Not necessarily a better tool, but at least, I think Neovim needs to evolve.

When I think about Neovim, I try to focus on what makes it this tool I love: modal centric, snappy / performance, integrates well with tmux, and that is pretty much all. The integration part with tmux is needed because I have to use the TUI, which is also something that I start not liking anymore. I know since Doom that we can get rid of TUIs and make great GUIs. So what I think I want to go to with Neovim from now on is this:

Something around one year ago, I chatted with core contributors, telling them that something that puzzles me a bit with the current Neovim situation is that there is no unified / consistent UI experience. Most plugins will write their own UI thing, obviously often based on TUI hacks, such as using characters as , , , etc. The current situation with plenary.nvim is another interesting topic, because I think it is not the answer to my problem. Yes, it will provide an abstraction for people needing to create bordered windows / pop-ups but… the GUI really does not care about that. Technically, Neovim already makes the distinction between “buffers” and “pop-ups” and “floats”, so GUIs are free to render them the way they want, but… what if developers start using APIs, such as plenary, where they pass border arguments? How does that make sense? If you are writing a plugin that needs to list things and requires a fuzzy interaction with the user, you should not depend on something that expects anything from the UI implementators. And for this, I think Neovim is lacking abstractions. Obviously, we can discuss that kind of problems for other topics:

I guess some of those items are already addressed, but I have yet to see a Neovim GUI going fully “native without TUI aspects.” Maybe it already exists and I need to search again. I want to invest time in things like goneovim and some other implementations, and I really do want to make my own (maybe with Gtk or even using my own 2D/3D graphics Rust stack).

Next obsession: CLI, TUI and GUI

CLI stands for Command Line Interface; TUI for Terminal User Interface and GUI for Graphics User Interface.

Among all the things that Doom changed in my mind, there is this concept of TUI. I have been a very big user (power user?) of the CLI. Pretty much anything that I do on a computer is via the CLI. For more advanced usage, I tend to use a TUI. I only use GUIs if I have to, such as firefox, blender or a video game. However, guess what: Doom hit hard here too, because I am not using the TUI version: I am using the GUI one. And do not get it twisted, I am not using my mouse, and this is a very important and interesting point about CLI / TUI / GUI.

When talking about this CLI / TUI vs. GUI thing around me, people tend to agree, weirdly, on some misconceptions:

These misconceptions are interesting because, as misconceptions, they are not real. You can find some terrible CLIs (i.e. npm is such a bad CLI — I had a big issue at work a few years ago when running not on purpose a command such as npm i --user, thinking it would install it in user space while it just completely ignored the unknown --user flag and installed the package globally), and you can find terrible GUIs too. However, and this is where I want to focus, GUIs are not necessarily mouse-focused, and since we can implement nice UX in TUI… we should be able to do the same in GUIs, right?

Aaaaaaaand… enter Doom, again. I am using the GUI version of emacs — on Archlinux, I am using this patched version which is a basically gccemacs, an emacs that recompiles all the Lisp as C! It obviously can accept mouse inputs so that you can click on things if you want to but… the most important still applies: you can use it exactly as you would use it in a terminal. However, because it is a GUI, you get access to much more possibilities:

Today, I do think that the right direction is to allow developers to build platforms around the concept of “graphics toolkits”, and not “terminal applications.” Even for CLIs. Think about git branch. My current git branch workflow, in CLI, does not use git branch only: I have an alias for each Git commands that require a branch. For instance, if I want to switch to another branch, this is what my gs alias looks like when invoked:

This is basically a fuzzy-finder version of git switch, using fzf – if you want that script, have a look at these ones. The same feature is doable with Doom / Neovim by using the right plugin, and I have to admit that I prefer doing that in magit as it feels more “well integrated.” Of course, you will always want to have a way to compose applications, and for that, CLIs are great. Something such as kubectl get pods -l service=my-service | rg whatever. However, I am trying to challenge my own conception of “program composability” because I do think we can implement this list k8s pods -> filter them inside an UI such as the ones in Doom Emacs, without having to write a specific tool. And we can actually do it in CLI / TUI as I just showed with my gs alias, so… why not providing that kind of power natively on the platform?

Final obsession: configuration ≠ scripting

And now one of the big issue I have with pretty much any editor today (but more with Neovim as I am actively contributing to it): if you want to configure your editor, you have to code your configuration and I highly dislike that. It is possible, indeed, to reduce the quantity of code to write, but you will eventually have to get your hands dirty and write some Lua (eeew). And I have a big issue with this.

See, configuration should be stupid and limiting. It should be key = value, period. If you need a more structured way to configure things, just add sections / objects like what you can do with TOML or JSON, but that is all. Remember that a configuration file might be read by a human, and that you must do everything possible to make it super clear what the configuration state is. Configuring should be:

There is a fundamental difference between having the power to do something and explicitly refusing that power and lacking that power. I think that configuration can be done via code, but it does not mean it should. The reason for that is mainly for cognitive complexity reasons. If you are the author of your application, you will very likely know how to script it. You will know all the quirks and all the little things to know that you will, obviously, have well documented (right?!). Your users will not have this kind of knowledge. I think it is wrong to use Lua to configure a software as it will force people to get into the documentation of the Lua API, while all they should have is a list of configuration options they can set, what setting does what and what are the possible values, and that is all. Providing more power leads to inconsistencies, broken setup and something that is pretty hard to fix without proper semantic versioning (and an actual package manager enforcing it): a slowly rotting configuration.

Rotting configuration happens when you start piling up lots of “configuration code”, using different APIs, that will eventually get deprecated or not even used anymore. If you are lucky, the application will just tell you this and that API calls are deprecated and you should switch to something else. If you are not, you will get some random crashes and failures in your applications and you will have to debug code. For configuration. And this happens a lot with Neovim.

See, blurring the line between configuration and scripting is easy, especially if you use the same language for both. Scripting is enhancing the power of an application, such as an editor or a video game, by providing new behaviors. Those behaviors (should them be native to the applications or new ones provided by plugins) are configured. If you start having to script the configuration side, you lose the distinction between both.

At work, we are using k8s a lot. In order to configure our services, we have to deploy configuration massively everywhere, and we need to generate that configuration. But see, there is a difference between “the configuration” and “generating the configuration.” The “generate part” is basically a template language. This is code, because it relies on the datacenter / cluster / environment / feature flags / etc. we want to deploy in / with. However, we do not configure the software in the template code. We pass fragments of configuration to the template engine, which generates the final configuration. And those fragments are basically exactly what I described above: key-value objects, sometimes nested to implement more complex configuration setup, but nothing else. It is basically YAML. If you do not need the complexity of the datacenter, cluster or environment, you can just run your application with a configuration file that has no code in it, just a bunch of key-value pairs.

The way Neovim treats configuration right now is via a convention, well-accepted among the plugin developers (and since I am now officially a Neovim plugin developer with hop.nvim, I obviously follow it as well). That convention is the setup function plugins expose to pass configuration (via an object often called opts for options). So people have to know whether that convention is used, they have to call that function (i.e. it is a regular Lua function call) and they have to dig in the Lua API. What I think I want to try (I think I am going to work on a plugin for that) is a way to have a central “configuration store” so that users do not have to go through this setup thing anymore — which, for the record, requires you to do something like require'the-plugin'.setup { … }, for every plugin you want to use. The way I see things would be more like this:

-- In your init.lua, for instance

require'config-store'.setup {
  telescope = {
    -- options used by telescope
  },
  hop = {
    -- options used by hop
  },
  neogit = {
    -- options used by neogit
  },
  -- etc. etc.
}

require'telescope'
require'hop'
require'neogit'

Then, each plugin, in their init.lua, could do something like this:

-- Assuming this is the plugin for hop
local config = require'config-store'.hop or require'hop.config'.defaults

This way of doing seems much more natural to me, as it limits the amount of “coding” people have to do to configure their packages, and it is not really different for package maintainers. Instead of exposing a setup function, they can just get the configuration via this config-store thing. I think I will try to experiment with this idea. Obviously, it requires plugins to adopt this config-store thing, and I do think that if people think it is an interesting idea, this should be a vanilla Lua API and not a random plugin, so that we know this config-store object is there, whatever plugins the user have installed. As discussed above with how we do that at work, I do not want to prevent people from coding / scripting their editors. But I think a clear distinction between scripting and configuring is important.

In the end

So those were my (lost) thoughts about the current situation. I might do a follow-up later in the year to see how things have evolved to me. Today, I stick around Neovim in tmux, using various scripts around fzf to make my shell a bit more interesting to use. I have to admit that I often open emacs to do three / four commits. The experience is just consistent. Navigating in projectile, Ivy and magit is unbeaten to me. The simple interactions with dired (to navigate directories a bit like with a file browser) and edit their contents is something I truly seek everywhere else, even in my shell. I recently did a quick benchmarked of Avy, the emacs implementation of EasyMotion, versus hop.nvim, and Hop is really much faster, so I do not really know what to think (I guess I should be proud of what I made 🙂). I think that Doom currently has this level of maturity that Neovim will take years to achieve, not because of talent, but because of the fact Neovim is an editor and has not been designed as being a productivity platform, while emacs was.

Keep the vibes!