I've been a Linux user, on and off, for the better part of 15 years -- full time since about late 2023. I've been both a pure user (using Linux for regular desktop things) and a developer (using Linux as my platform for software development). I may not be an expert, but I've at least had enough experience at this point to know what I want out of the tools I use, and more importantly, how I want those tools to function in of themselves. I've found myself falling in love with tools, only to later be steered away by the tool getting in my way. Why is that?
Let's construct a framework together to analyze some tools in this context.
What is a tool?
A tool is some kind of object (in our case, a virtual object) that helps its user accomplish a specific task or a range of tasks. One of the simplest examples of a tool: a hammer. While there are many types of hammers throughout history, we're going to look at the colloquial ideal of a hammer, something that looks like this:
This is technically called the "claw hammer" -- named likely for the claw sticking out opposite the blunt head, but we're just going to call it a hammer for simplicity. This hammer's primary design intent is to deal with nails in carpentry -- driving nails into objects, and taking them out of objects.
What is a feature?
A feature is an element of a tool that is used in the act of doing the tasks it's designed to do. A hammer features a blunt, flat faced, narrow head -- perfect for slamming into a flat faced, even narrower head of a nail. A hammer also often features a set of claws, which allow the user to pull nails out of the wood they're driven into.
What is development?
Development is the act of adding features. Note that we're not talking about reinterpretations of a tool -- the claw hammer may be designed for carpentry use, but in an abstract sense it's just a blunt, heavy head on the end of a handle, and can be used outside of carpentry without fundamentally modifying the tool.
Imagine, if you will, bludgeoning a man with this hammer. While you have reinterpreted the tool as a tool of causing harm, it required nothing more than applying the tool in a different manner.
Adding a feature to a hammer would be like including a leveling bubble in the head of the hammer. This is not a reinterpretation of existing features: instead, it allows the same hammer you use to drive nails into wood to also be used to check whether that wood you're driving nails into is horizontally or vertically level. This is not in the original design scope of a hammer, but still potentially a useful feature.
What is configuration?
Configuration would then be the act of adjusting how a tool presents its features to the user. Hammers often come in different weights -- lower weights being suitable for smaller nails and weaker wrists, and higher weights for larger nails and stronger wrists. Some hammers have contoured grips, some hammers have wider or narrower heads, some have longer handles, and so on. None of these fundamentally change what the hammer is designed to do -- drive nails and take them out -- but instead adapt the form of the tool to different flavors of the same application, and to the hands of its users.
How does the line between configuration and development get blurred?
The physical world is often far more defined than the software one. It's far more obvious when you're adding features to a physical object, as you usually have to transform the shape or appearance of that object to accommodate the new feature. Adding a claw shape to a hammer head is obvious -- but in the software world, it can be less obvious.
Web browsers
All usable modern web browsers have some concept of "extensions", where the user can add new features to their browser with just the click of a button. Commonly, users will add ad blockers and password managers, but some users even go as far as to add extensions like Tridactyl which fundamentally change how one navigates the web browser. In our framework we've established, this would be an act of development -- but it doesn't really feel like one. This is because, ultimately, most web extensions don't fundamentally alter what a web browser is designed to do. They could instead could simply be read as reinterpretations of existing browser features.
A modern web browser is a HTML viewer with a built-in Javascript interpreter. In practice, this ends up being a very wide scope for a tool to have. Web browsers are the portal to blogs, social media websites, media streaming, and entire applications in of themselves -- this is what allows web extensions to feel so "native" despite being objectively foreign in origin.
An ad blocker just automates the removal of certain elements of a web page by leveraging the same features a web browser uses to add and remove content from the user's view. Password managers are basically just embedded web apps that get pulled out of a drawer or otherwise summoned on command. These extensions may ultimately add features from the end user perspective, but they don't have to fundamentally alter the web browser much at all to do so.
Even Tridactyl, a very heavy extension which allows a user to browse the web in a way that feels more like using Vim, doesn't really alter the web browser on a fundamental level. Adding new shortcuts and methods of looking at tabs doesn't really change what the web browser does, it changes how a web browser presents what it does.
The line gets blurrier, though, when we look at developer tools.
Tools that become sandboxes
This is where we wander into a more loosely defined, opinionated territory. Our framework is simple, but not a totality, and you may disagree with me in how I interpret my own framework, especially if I end up saying some less than nice things about tools you like. Let me be clear here by saying that I'm not making any statements of "suckitude", merely expressing what I personally dislike in tools I have used previously.
The way I would put it is that some tools get turned into a sandbox -- a poorly integrated development environment (PIDE) where the user must now make development decisions, to figure out what features they need, verify their development decisions work well with one another, and get stuck debugging their tool when they want to just get some work done. Tools that fall into this end up distracting their users, forcing them to make development decisions about their tooling environment before they can even start developing the things they actually want to develop.
Vim and Neovim
Vim comes from an era of computing that is well in our past. It is one of the oldest programs still in common use, and for a good reason: it works, it's small enough to be included anywhere, and it's flexible. It's a damn good text editor, and if you're handy with regular expressions and take time to learn its macro system, it is more capable than you'd expect.
The problem is that the world of today is not the world that Vim was born into. Vim is a text editor, but many that use it seek to turn it into a development environment. This is facilitated by Vim's plugin ecosystem and its internal Vimscript language which allows users to do... practically anything to their Vim environment. There's some genuinely nice plugins that I used to use, such as X which helps you jump around a text file with just a few, contextual letter presses. That isn't the limit though: Vim plugins can be used to add file trees, a whole shell, a Git helper application, LSP support. Vim is a text editor with a scripting language you can extend it with, but that's about it. Wrangling plugins together is something you must do on your own. You can download plugin source files manually and plug them into your Vim config, but that's a pain in the ass. There are plugin managers, sure -- many of them, with their own slightly different syntaxes, and differences that are insignificant to the end result, but very important to implement without error.
Neovim does a good degree to help with this. Alongside Vimscript, it has first-class support for Lua. It has built in LSP support (something that is practically a necessity for modern developers). Overall, Neovim made Vim something that was less a nice text editor, and more something that I could use for serious development... and I did!
" automated vim-plug install
let data_dir = has('nvim') ? stdpath('data') . '/site' : '~/.vim'
if empty(glob(data_dir . '/autoload/plug.vim'))
silent execute '!curl -fLo '.data_dir.'/autoload/plug.vim --create-dirs https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim'
autocmd VimEnter * PlugInstall --sync | source $MYVIMRC
endif
" Conditional plugin loading
function! Cond(Cond, ...)
let opts = get(a:000, 0, {})
return a:Cond ? opts : extend(opts, { 'on': [], 'for': [] })
endfunction
" vim-plug
call plug#begin('~/.vim/plugged')
" plugins go here
if !exists('g:vscode') "
Plug 'sheerun/vim-polyglot'
Plug 'preservim/nerdtree' |
\ Plug 'Xuyuanp/nerdtree-git-plugin'
Plug 'neoclide/coc.nvim', {'branch': 'release'}
"Plug 'junegunn/fzf', { 'do': { -> fzf#install() } }
Plug 'airblade/vim-gitgutter'
Plug 'tpope/vim-fugitive'
Plug 'RRethy/vim-hexokinase', { 'do': 'make hexokinase' }
Plug 'rust-lang/rust.vim'
endif
" truncated plugin set, compatible with vscode-neovim
Plug 'jiangmiao/auto-pairs'
Plug 'dracula/vim',{ 'as': 'dracula' }
Plug 'vim-airline/vim-airline'
Plug 'vim-airline/vim-airline-themes'
Plug 'edkolev/promptline.vim'
Plug 'edkolev/tmuxline.vim'
Plug 'easymotion/vim-easymotion', Cond(!exists('g:vscode'))
Plug 'asvetliakov/vim-easymotion', Cond(exists('g:vscode'), {'as': 'vsc-easymotion'})
call plug#end()
" text stuff
set encoding=utf-8
colorscheme dracula
let g:airline_powerline_fonts=1
let g:airline_theme='dracula'
let g:airline#extensions#tabline#enabled = 1
let g:Hexokinase_highlighters = ['backgroundfull']
syntax enable
set relativenumber
set number
let &t_8f = "\<Esc>[38;2;%lu;%lu;%lum"
let &t_8b = "\<Esc>[48;2;%lu;%lu;%lum"
set termguicolors
filetype plugin indent on
set cmdheight=1
" tab settings
set expandtab
set tabstop=4
set softtabstop=4
set shiftwidth=4
" tmux title
if exists('$TMUX')
autocmd BufEnter * call system("tmux rename-window " . expand("%:t"))
autocmd VimLeavePre * call system("tmux setw automatic-rename")
autocmd BufEnter * let &titlestring = ' ' . expand("%:t")
set title
endif
" no auto comment
autocmd FileType * setlocal formatoptions-=c formatoptions-=r formatoptions-=o
" custom binds
cmap w!! w !sudo tee > /dev/null %
" tmuxline settings
" let g:airline#extensions#tmuxline#enabled = 0
This was the core of my Vim and Neovim configuration -- it programmatically enabled certain plugins depending on whether it was in a VSCode embedded context or a standalone context, configured plugins that I added, installed vim-plug if it was not already installed, and eventually pointed to some Lua files for Neovim-specific configuration (mainly for COC.nvim).
Over time, I kept finding myself getting frustrated and defaulting back to VSCode when I found that I had to do some research and find out what plugin would work for my needs. There are different Git plugins, different statusline plugins, different everything for each little thing I wanted in my text editor. There were plugins that accomplished the same shape of goal in fundamentally incompatible ways. The process of finding out which plugins actually fit my needs got tiring after a while. I eventually pared down my Neovim configuration to just a few plugins, embedded my Neovim into VScode, and later moved on to using Zed and Helix, both of which offered me more with less development.
GNOME and its Extension ecosystem
Let's move away from a developer focused example and back to a more general user example: comparing GNOME to web browsers. The web browser renders HTML and interprets JavaScript. What GNOME "is" can be a very contentious definition, so I'm going to define what I am referring to when I refer to GNOME:
The canvas where applications can be rendered onto (think window manager)
The environment manager that allows the user to orient their apps (think virtual desktops)
The translator between user intent and system configuration values (think System Settings)
A collection of widgets that translate user intent to outcomes (closing/opening windows, enabling WiFi, turning off the computer).
If we were to distill this to a single sentence: GNOME exposes software functionality in a human readable context. It is famously opinionated about how this is displayed to the user -- much text has been written about GNOME being "user hostile" or "lacking basic features" by many, and whether or not you agree with how GNOME handles the translation between user intent and software outcomes, I'm sure we can all agree that this is what GNOME does. The opinions GNOME comes with are critical for the kind of application it is -- what separates GNOME from Plasma, COSMIC, Xfce, Lxde, is the choice of abstractions it uses to provide an interface for users to use their computer. If GNOME had few opinions about how it should present software to its user, it stops being a defined desktop environment.
GNOME extensions by their nature allow the user to change GNOME's opinions about what a user needs to do daily desktop needs. This is not inherently a bad thing: not everyone needs Tailscale integrated into their DE, but Tailscale/GNOME users certainly appreciate being able to toggle Tailscale on and off with a Tailscale extension for GNOME. The GNOME extension ecosystem, while on the surface it can feel very similar to web browser extensions, is actually significantly different in scope. Web extensions are embedded web applications -- they can only add features in the context of what the web browser can already do (render HTML and interpret JavaScript). GNOME extensions, on the other hand, can radically change the user experience. Extensions can add docks, start menus, even entirely different window management paradigms. Extensions can funnel social media notifications into your system notification bar. They can turn GNOME from just one, well defined desktop experience into many, diverging desktop experiences: a sandbox, if you will.
This isn't inherently bad, of course. A desktop environment with only strongly held and overly specific opinions is likely one that would only be used by its creator. GNOME is one of the two "default" desktop experiences in the Linux world, and one you've likely used before if you've ever used Linux on a desktop or laptop. Extensions on GNOME are more capable of fundamentally redefining how you use it than web extensions -- partially because a web browser is already a very broadly defined tool, and partially because GNOME extensions are basically just arbitrary JavaScript code written in the GJS ecosystem that have very little in the way of modifying the underlying system.
This introduces conflicts -- while I rarely worry about my web extensions breaking in a browser update, GNOME extensions regularly need updates from their developers on new releases, even if it ends up being just a version bump in metadata. GNOME extensions have very little preventing them from interacting with system internals, whereas web extensions can basically only embed web apps and change web pages. A GNOME extension shares intimate space with critical system functions, and a crashed extension can crash your entire desktop environment.
In practice, I've ended up tending towards very few extensions. My NixOS laptop has no extensions currently, and my desktop has only a few that I actually use: PaperWM (an admittedly major extension), the Tailscale extension, and a system monitoring tool. I don't want to think about whether my desktop environment is going to keep working the way I expect it to, so I prefer to stick to the defaults unless there's something really compelling that justifies the headache. This is not to say that my way of using GNOME is correct -- if you have a preference, it's not really wrong to want that preference respected -- but it is a way of using GNOME that avoids a lot of headache.
Software which avoids the blurred lines: Helix
Helix avoids the blurred lines by making commonly added features in (neo)vim first class features. Fuzzy find? Integrated. Language servers, auto formatters, documentation hints? Integrated, and minimal configuration needed. Auto bracketing, surround editing, all integrated.
This does not mean Helix has everything everyone would ever want. Helix only recently added a basic file tree. Helix, while having some Git integrations, does not present the user an interface for managing commits and branches. Helix is not trying to be an integrated development environment -- it's just trying to be a very useful text editor. You won't find yourself wrangling plugins: it either has what you need, or it doesn't. It fits the niche it's trying to be well, and encourages you to seek other, purpose built tools that work alongisde it. Helix works well with tools like Tmux or Zellij: combine terminal multiplexers with your favorite TUI applications for file management, VCS management, a debugger interface (which Helix does have, but in an experimental state), maybe Claude Code, and you've built yourself an IDE with purpose built tools that don't have to graft themselves onto a piece of software that was never designed for them in the first place. You may still end up wanting a proper IDE -- I personally find that if I need more than just the text editor, I want to open up Zed rather than wrangle my own collection of TUI applications -- but the beauty comes from each tool having its own well defined purpose.
Software which avoids the blurred lines: NixOS
NixOS and the broader Nix ecosystem avoids the blurry lines by making configuration and development one in the same. I'll avoid going into a deep dive (though if you'd like to learn more, this beginners guide is a solid read on what makes Nix unique), but in essence: Nix is a programming language, by which NixOS and the Nixpkgs package suite is implemented with.
Your system is configured with the Nix programming language. Instead of giving you a highly abstracted low-code interface, it gives you a declarative, programming interface -- one you can choose to stay at a higher level with...
# enables GNOME and GDM
services.displayManager.gdm.enable = true;
services.desktopManager = {
gnome.enable = true;
};... or get deeper into the weeds with.
# This Nix module:
# - allows the user to enable the Stylix theme module in NixOS
# - configures my favorite monospace font and base16 theme
# - triggers the enabling of the Home Manager Stylix module
{
config,
lib,
pkgs,
...
}:
with lib;
let
cfg = config.freyjaModules.stylix;
in
{
options.freyjaModules.stylix = {
enable = mkEnableOption "Enable stylix with Rose Pine Dawn";
};
config = mkIf cfg.enable {
stylix = {
enable = true;
base16Scheme = "${pkgs.base16-schemes}/share/themes/rose-pine-dawn.yaml";
fonts = {
monospace = {
package = pkgs.nerd-fonts.comic-shanns-mono;
name = "ComicShannsMono Nerd Font Mono";
};
};
};
home-manager.sharedModules = [
{
config.freyjaHome.stylix.enable = true;
}
];
};
}NixOS feels good to develop, because it's designed to be developed. I've found it fun building out my NixOS flake, because it feels like developing, not configuring. Because it's a language, you can do fun things like:
Build test cases to ensure your system works as expected
Use CI/CD pipelines to automate testing and deployment
Build systems programmatically, with modules you designed for reuse
Track it all in your favorite VCS
Nix allows you to treat the systems you use the same way you treat the tools you build, and that's not just insanely powerful, but also congruent with the actual task you are doing. You're not wrangling YAML files together: you're developing a system.
What do we make of all this?
I'm not exactly sure what the "call to action" of this would be. Perhaps this is a call for more opinionated developer tools: instead of 7 different package managers, maybe one should be considered the supported package manager. Maybe it's instead a call to recontextualize highly extensible systems -- instead of trying to abstract extensions into pluggable modules, what if we embrace the code they're written in, and embrace developing the tool?
Personally, I don't want to develop my tools, at least not at this moment. I use Helix because once I get it configured, I don't have to think about it much. I like the Rust ecosystem, because I don't need to select what Cargo fork to use before I even start making architectural or implementation decisions about what I am building with Rust.
I like NixOS, because it recognizes the reality that creating an operating system that's specifically useful for what I need from it is a process of development and should be done with a programming language. I've never used an operating system that worked exactly in the way I'd want it to, so if I'm going to be developing anyways... I want to develop properly.
Maybe the synthesis here is that we just need more effort paid to the experience of using our tools, rather than just what our tools can do. Whether that means very opinionated tools, or controlled extensibility that doesn't get in your way, it only helps us do our work better. There's nothing you can do for the fundamentally incurious, but there is a lot we can do in the way of making our tools easy to mold to our hands, rather than just "easy to use". Sometimes that looks like a text editor with strong opinions and a well-defined focus, and sometimes that looks like a programming language that builds systems to your specifications.
You should not have to fight your tools.