A Brief History of Text Editors: from Vi to Visual Studio Code
Despite the proclamations from visual software design tool advocates in the ’90s, simple text has remained the mainstay of code manipulation for software engineers. The humble text editor has accordingly retained its status as one of the key tools for a modern technologist, ranking alongside the anachronistic terminal and the not-so-humble web browser.
Historically, line-based editors such as
ed
allowed basic interactive
text manipulation. They were finicky enough that “real-time” editors were
invented, a phrase still used by Emacs to
advertise itself today. Modern developers take for granted the ability to modify
a document in real time as it is displayed in front of them, which is a leap
forward in usability from what came before.
Throughout the late ’90s and early Noughties basic UI conventions became more consistent, such as the keyboard shortcuts for copying and pasting text. Integrated Development Environments (IDEs) became more popular, normalising tools that had a greater in-built semantic understanding of code and also of larger-scale structure in sprawling codebases.
The last decade has blurred the line between basic text editors and IDEs. IDE UIs have become more lightweight. They have more immediate flexibility. Traditional editors have accrued both built-in and optional additions that move them closer to IDE functionality. Meanwhile, the definition of IDE has evolved: while basic auto-complete and syntax highlighting sufficed in the early Noughties, deep semantic understanding of various languages and extensive automated refactoring support became table stakes due to the likes of IntelliJ.
Let’s detour into the earliest editors still in use today and compare them to contemporary offerings.
Early Real-Time Editors
Vi and Emacs predate the era of more standardised keyboard shortcuts and UI fundamentals. Where is Control-C or Command-C for copying text? What is Vi’s “insert mode” and why is it necessary? Why does Emacs talk about “kill rings” and “the meta key”?
Vi and Emacs represent a fork in the road of computing evolution that other tools didn’t take. That means both needlessly different conventions from what developers are used to, but also a gold mine of text-editing concepts that were left untapped by modern editors.
Archaic yet Composable Keybindings
Standardising on the likes of Control-C for copying and Control-Z for undoing was a boon for UI consistency but cemented artificial constraints; if we allocate Control-X for cutting the current selection, how do we add new “nouns” to that “verb” without violating the user’s UI expectations? We might want to cut whole paragraphs or just inside the current pair of HTML tags. Users don’t expect additional keystrokes before the Control-X, nor do they expect a delay afterwards in which they use a follow up key to select a “noun”.
Vi and Emacs both have composability of keybindings that goes beyond most
other editors. At the start of a line, Emacs can delete the line with Control-k.
Or rather, to use Emacs parlance, a user can kill the line with C-k. However,
the user can prefix it with Alt-4 to delete the rest of the current line and
three more lines ahead too. Vi refines such key composability into an art form:
3dd
to delete three lines; c}
to delete until the end of the paragraph and
activate “insert mode”; and y2i(
to copy all text inside the set of
parentheses outside of the current set.
Vi and Emacs don’t throw text into the void upon deletion. They “cut” text by
default and move it onto a growing stack of deleted text fragments that can be
summoned back at will. Emacs calls it a “kill ring”, vim the “delete registers”.
Keystrokes such as "7p
can then paste the 8th last cut item (counting from 0)
back into the current document. Control-V can’t work for this as it has no
standardised way of selecting which old cut selection the user wants to bring
back, never mind “named registers” by which vi and Emacs can cut text into any
storage location with a dedicated name.
There are half-hearted attempts to better support more standard shortcuts in the
older editors, such as Emacs’s cua-mode
or Vim’s “easy mode”, but they’ve
never been made default or embraced in later versions.
Extension Languages: Emacs Lisp & VimL
Apart from composable yet now-unfamiliar keyboard shortcuts, another feature stands out in Emacs: an immediately accessible and powerful extension language from day one, at least in the case of GNU Emacs. Vi didn’t have this but one of its descendants, Vim, added one: VimL (aka VimScript).
Early editors usually didn’t provide means of extension from within beyond modifying its own source code and recompiling. Emacs and Vim were somewhat unique in that regard. Later editors did provide similar capabilities: Sublime text allows extensions in Python, and IDEs such as Eclipse provide plugin systems.
Even with the growing popularity of such IDE plugin systems, Vim and Emacs stood
out for the immediacy of their extension languages. Eclipse would need a
Java-based plugin to be compiled and loaded into the IDE, whereas Emacs would
allow custom Lisp to be fed into the current environment with nothing more than
an Alt-:
. IDEs often bifurcate capabilities into powerful built-in features
and limited features exposed to plugins; meanwhile, Emacs allowed any
user-defined Lisp to modify and “advise” even the deepest ELisp code in the
bowels of the system.
VimL doesn’t go so deep. More of the C core is off-limits to VimL than ELisp in Emacs, which is partially due to more of Emacs being written in ELisp to start with. However, VimScript still possesses that immediacy.
The Modern Contender: Visual Studio Code
Modern editors edge closer to Vim and Emacs, carrying more accessible extension languages. Atom jump-started the popularity of Electron, formally “atom-shell”, popularised the idea of running editors on web technology, and allowed extensions in JavaScript with fewer formalities.
Visual Studio Code took Atom’s concepts and packaged them up in a more complete user interface with fewer performance problems. While Atom eventually addressed some of its biggest performance woes, the perception had already been set in stone; Visual Studio Code emerged as the successor of the two.
Code, as Visual Studio Code is more idiomatically known, does have more
bureaucracy for loading in custom code. It needs declared extensions rather than
something as immediate as Emac’s Alt-:
. However, it got several things right
that Vim and Emacs did not: it uses a mainstream language for extensions; it
isolates extensions in separate processes to force asynchronous operations and
to enforce isolation from the editor core; and it has a culture of pressuring
extension authors to make their extensions “just work” rather than requiring
snippets of code to be pasted into a configuration file.
Its extensions don’t block the UI because they mostly cannot: they’re not running in the same thread as the UI. There are perks of letting extensions, or “modes” in Emacs parlance, modify everything in the editor. Yet, Emacs extensions can perform dangerous tasks like tampering with core editor functions on a whim.
The culture surrounding Vim and Emacs has normalised the dropping of random snippets of ELisp or VimL into editor configuration files to set up extensions. This raises the bar beyond Code, which has aggressively sought to “auto-configure” extensions as much as possible.
A developer of the last decade, even a senior, is more likely to be familiar with JavaScript and TypeScript than ELisp or VimL. Even if they are willing to learn a new language, it’s not an effort they’re spending solely on their editor: JavaScript is ubiquitous outside of Code.
While ELisp is a well-designed language – it is a Lisp dialect, after all – VimL isn’t so much. It has organically grown from vi’s command mode and accrued some questionable characteristics en route.
Choosing an Editor
With myriad editors to choose, how does a technologist decide? While I can’t speak for others, I can detail my decision-making process that led to the editor I use today.
Emacs’s Problems
If ELisp is a decent language, why not use Emacs and call it a day? After all,
it has powerful modes such as magit
and
org-mode
. ELisp now has native compilation and lexical
scoping. The package manager has been around for a while and is well supported.
Lisp macros are a sublime programming tool, which ELisp supports via its
MacLisp/CL-style unhygienic
defmacro
.
Yet there are problems:-
- The keybindings require holding too many modifier keys, increasing strain on the wrists.
- The keybindings don’t use modern standards such as Visual Studio Code’s, but also aren’t encoded in ubiquitous standards like Vi keybindings being in the POSIX standard.
- An insufficiently composable vocabulary for dealing with text. Basic
composability exists:
Ctrl-U
can be used as a “universal argument” to shift a keybinding into an alternative mode, and numbers such asAlt-10
can be used to repeat operations several times. Yet it simply has nothing on Vim which possesses an entire verb/noun vocabulary. Do you know the noun for inside a HTML tag (it
)? What about the verb for deletion (d
)? Then you already know how to delete everything inside the current tag (dit
). - ELisp is not used anywhere outside of Emacs. That means accumulated knowledge about ELisp is inapplicable anywhere else.
- There’s no architectural distinction between the editor core and UIs. New GUIs essentially must be created in the main Emacs repository itself.
evil-mode
emulates the better keybindings from Vim, but integrating it into every mode you use is an endless game of whack-a-mole. Perhaps there areevil
packages for each mode you use. Maybe something likeevil-everywhere
covers it. However, these approaches never have 100% coverage; you need to be conscious of switching back to Emacs keybindings when you encounter an area that was missed.- Unlike Code, extensions often don’t work out of the box and need too much ELisp configuration to get basic features working. This is more of a cultural problem than a technical one.
- Antiquated terminology that increases the barrier for new developers: yanking rather than copying; killing rather than deleting; meta rather than alt; and keybinding rather than keyboard shortcut. Unlike Vi, these terms are not codified in an influential standard like POSIX with which operation types should already be familiar.
Visual Studio Code’s Problems
An editor with modern conventions, bold technical choices such as isolating extensions properly, and bolstered by a lively ecosystem. Why isn’t it the obvious choice?
- It’s entirely dependent on web technology. The modern web stack is such a behemoth that no individual can stay abreast of its architecture. Its implementations became a monoculture, with the demise of Edge’s own rendering engine and the decreasing market share of Firefox. I wouldn’t base a program as focused as a text editor on it. By contrast, Vim and Emacs only need a terminal, but also support GUIs if desired. Being able to run those editors over SSH or in a container with an attached TTY is useful.
- A lack of immediacy for its extensions. In Emacs, one can insert arbitrary
ELisp code with
Alt-:
. WithAlt-X ielm Enter
, one can jump straight into a REPL and start hacking away at the editor. This is a far cry from Code, whose extension need more ceremony. - JavaScript. Unlike many developers, I don’t meme about how much I dislike JavaScript. I actually like it as a result of reading Douglas Crockford’s works over the years. However, it is becoming a design-by-committee language that is piling in feature after feature. It’s so far from the minimal macro-less Scheme-in-ALGOL-clothing that ECMAScript 3rd and 5th edition got close to. Keeping up with JavaScript is now a long-term commitment.
Vim’s Problems
It has ergonomic keybindings, supports running within just a terminal, and has stood the test of time. However, it has its own insurmountable problems.
- Like Emacs, its keybindings aren’t standard. Unlike Emacs, they’re offset by both ergonomics and being encoded in the POSIX vi standard, meaning operation types should be familiar with them due to ubiquity.
- VimL is baroque, aged, and haphazard. Even if it were a language that evolved elegantly like ELisp, one can still not transfer gained knowledge about it outside of the editor.
- The future direction is making an incompatible Vim 9 dialect of VimL rather than embedding a proper language. The problem will get worse, not better.
- A weak separation between the UI and the editor core. It comes with various GUIs in the form of GVim. They’re built in and there isn’t a well-established culture of using a standard protocol between the editor core and the UI. As such, there are fewer third party GUIs that can evolve independently.
- A general lack of boldness in designing new features. Vim added asynchronous operations, channels to communicate between them, and an embedded terminal. But this was only done after Neovim had already beaten Vim to it.
- Similar to Emacs, it has a culture of extensions needing configuration snippets to get the basics working, rather than just working out of the box.
- Questionable technical decisions being made by leadership. See this GitHub thread in which they justify keeping dodgy crypto in the editor core.
Settling on Neovim
Neovim is a fork of Vim that appeared in 2015. Recently it released version 0.5.
Put succinctly, Neovim 0.5 fixes enough flaws in Vim to make it the most compelling choice for me in 2021. Unlike Vim, it uses a well-designed language that is usable outside of its ecosystem: Lua. It has created a decent separation between its core and UIs, creating a blossoming ecosystem of third party GUIs such as nvim-gtk. It has shown boldness in pushing things forward, such as building in LSP support and supporting full configuration with a non-VimL language.
Another development of the 0.5 release was the ability to configure even more
with Lua. Lua can now be used for all configuration – bar a few inline Lua
strings containing VimL needed for some holdouts such as autogroup
s. LSP is
built in, avoiding frameworks like coc
that want to bring an
entire Node.js runtime into the picture.
For a while I stuck with plugins that were compatible with both Vim 8 and Neovim. Now I’ve now gone all in on Neovim with a Lua configuration and a collection of Lua-only plugins.
The Future of Text Editors
Putting aside my own text editor decisions of the present, what does the future of text editing look like?
Text editors will likely gain more awareness of isolated development environments, to aid both security of development environments and general project environment reproducibility. Visual Studio Code is leading the way here; its team aren’t the first to think of such an approach, but they’re making it accessible.
Emerging libre text editors continue to explore the perks of modal editing, including Kakoune, Helix, and vis. While Vim’s modal editing is ergonomic, it’s ultimately rooted in ’70s keybindings from Vi. Can technologists refine the modal editing paradigm for the 21st century?
The gap between local and remote editing will blur, likely driven by the ability to easily run web-based technologies both locally and on a remote server. Again, Code leads here: Microsoft is pushing remote Code instances along their GitHub product. Such environments are less hassle than configuring a local environment from scratch. They let a company more easily apply mandatory security controls and Data Loss Prevention. Sadly, this will likely lead to a reduction in software engineers choosing local tools according to personal taste. How can new editors and subsequent text-editing paradigms emerge in a world when companies force pre-configured remote text editors onto engineers?
There has been a growth in tools that inspect languages via ASTs. Auto-formatters that reparse the AST to emit normalised versions are now the norm. Language ecosystems increasingly provide tools such as .NET’s Roslyn or tree-sitter that understand programming languages as nodes to rearrange rather than as opaque blob of UTF-8 text. Editors will follow this approach. Perhaps the Smalltalkers’ dream of moving beyond plaintext representation for code will occur gradually, with the underlying text increasingly becoming just a serialisation layer for text editors to load before providing a more semantic view of the structure.
Here’s hoping that Neovim’s Lua plugin ecosystem can keep up with these seismic changes in text editing over the next decade, otherwise I’ll have to waste time acclimating to yet another new editor.