Two Years of Emacs Solo: 35 Modules, Zero External Packages, and a Full Refactor

Cover Image for Two Years of Emacs Solo: 35 Modules, Zero External Packages, and a Full Refactor
Rahul M. Juliato
Rahul M. Juliato
#emacs#config# elisp

I've been maintaining Emacs Solo for a while now, and I think it's time to talk about what happened in this latest cycle as the project reaches its two-year mark.

For those who haven't seen it before, Emacs Solo is my daily-driver Emacs configuration with one strict rule: no external packages. Everything is either built into Emacs or written from scratch by me in the lisp/ directory. No package-install, no straight.el, no use-package :ensure t pointing at ELPA or MELPA. Just Emacs and Elisp. I'm keeping this post text only, but if you'd like to check how Emacs Solo looks and feels, the repository has screenshots and more details.

Why? Partly because I wanted to understand what Emacs actually gives you out of the box. Partly because I wanted my config to survive without breakage across Emacs releases. Partly because I was tired of dealing with package repositories, mirrors going down in the middle of the workday, native compilation hiccups, and the inevitable downtime when something changed somewhere upstream and my job suddenly became debugging my very long (at the time) config instead of doing actual work. And partly, honestly, because it's a lot of fun!

This post covers the recent refactor, walks through every section of the core config, introduces all 35 self-contained extra modules I've written, and shares some thoughts on what I've learned.

Now, I'll be the first to admit: this config is long. But there's a principle behind it. I only add features when they are not already in Emacs core, and when I do, I try to build them myself. That means the code is sketchy sometimes, sure, but it's in my control. I wrote it, I understand it, and when it breaks, I know exactly where to look. The refactor I'm about to describe makes this distinction crystal clear: what is "Emacs core being tweaked" versus what is "a really hacky outsider I built in because I didn't want to live without it".


The Refactor: Core vs. Extras

The single biggest change in this cycle was architectural. Emacs Solo used to be one big init.el with everything crammed together. That worked, but it had problems:

— It was hard to navigate (even with outline-mode)

— If someone wanted just one piece, say my Eshell config or my VC extensions, they had to dig through thousands of lines

— It was difficult to tell where "configuring built-in Emacs" ended and "my own hacky reimplementations" began

The solution was clean and simple: split the config into two layers.

Layer 1: init.el (Emacs core configuration)

This file configures only built-in Emacs packages and features. Every use-package block in here has :ensure nil, because it's pointing at something that ships with Emacs. This is pure, standard Emacs customization.

The idea is that anyone can read init.el, find a section they like, and copy-paste it directly into their own config. No dependencies. No setup. It just works, because it's configuring things Emacs already has.

Layer 2: lisp/ (Self-contained extra modules)

These are my own implementations: replacements for popular external packages, reimagined as small, focused Elisp files. Each one is a proper provide/require module. They live under lisp/ and are loaded at the bottom of init.el via a simple block:

(add-to-list 'load-path (expand-file-name "lisp" user-emacs-directory))
(require 'emacs-solo-themes)
(require 'emacs-solo-movements)
(require 'emacs-solo-formatter)
;; ... and so on

If you don't want one of them, just comment out the require line. If you want to use one in your own config, just copy the .el file into your own lisp/ directory and require it. That's it.

This separation made the whole project dramatically easier to maintain, understand, and share.


The Core: What init.el Configures

The init.el file is organized into clearly labeled sections (using outline-mode-friendly headers, so you can fold and navigate them inside Emacs). Here's every built-in package and feature it touches, and why.

General Emacs Settings

The emacs use-package block is the largest single section. It sets up sensible defaults that most people would want:

— Key rebindings: M-o for other-window, M-j for duplicate-dwim, C-x ; for comment-line, C-x C-b for ibuffer

— Window layout commands bound under C-x w (these are upcoming Emacs 31 features: window-layout-transpose, window-layout-rotate-clockwise, window-layout-flip-leftright, window-layout-flip-topdown)

— Named frames: C-x 5 l to select-frame-by-name, C-x 5 s to set-frame-name, great for multi-frame workflows

— Disabling C-z (suspend) because accidentally suspending Emacs in a terminal is never fun

— Sensible file handling: backups and auto-saves in a cache/ directory, recentf for recent files, clean buffer naming with uniquify

— Tree-sitter auto-install and auto-mode (treesit-auto-install-grammar t and treesit-enabled-modes t, both Emacs 31)

delete-pair-push-mark, kill-region-dwim, ibuffer-human-readable-size, all the small quality-of-life settings coming in Emacs 31

Abbrev

A full abbrev-mode setup with a custom placeholder system. You define abbreviations with ###1###, ###2### markers, and when the abbreviation expands, it prompts you to fill in each placeholder interactively. The ###@### marker tells it where to leave point after expansion. I wrote a whole article about it.

Auth-Source

Configures auth-source to use ~/.authinfo.gpg for credential storage. Simple but essential if you use Gnus, ERC, or any network-facing Emacs feature.

Auto-Revert

Makes buffers automatically refresh when files change on disk. Essential for any Git workflow.

Conf / Compilation

Configuration file mode settings and a compilation-mode setup with ANSI color support, so compiler output actually looks readable.

Window

Custom window management beyond the defaults, because Emacs window management out of the box is powerful but needs a little nudging.

Tab-Bar

Tab-bar configuration for workspace management. Emacs has had tabs since version 27, and they're genuinely useful once you configure them properly.

RCIRC and ERC

Two IRC clients, both built into Emacs, both configured. ERC gets the bigger treatment: logging, scrolltobottom, fill, match highlighting, and even inline image support (via one of the extra modules). The Emacs 31 cycle brought nice improvements here too, including a fix for the scrolltobottom/fill-wrap dependency issue.

Icomplete

This is where Emacs Solo's completion story lives. Instead of reaching for Vertico, Consult, or Helm, I use icomplete-vertical-mode, which is built into Emacs. With the right settings it's surprisingly capable:

(setq icomplete-delay-completions-threshold 0)
(setq icomplete-compute-delay 0)
(setq icomplete-show-matches-on-no-input t)
(setq icomplete-scroll t)

I've also been contributing patches upstream to improve icomplete's vertical rendering with prefix indicators. Some of those features are already landing in Emacs 31, which means the polyfill code I carry today will eventually become unnecessary.

Dired

A heavily customized Dired setup. Custom listing switches, human readable sizes, integration with system openers (open on macOS, xdg-open on Linux), and the dired-hide-details-hide-absolute-location option from Emacs 31.

WDired

Writable Dired, so you can rename files by editing the buffer directly.

Eshell

This one I'm particularly proud of. Emacs Solo's Eshell configuration includes:

Shared history across all Eshell buffers: Every Eshell instance reads from and writes to a merged history, so you never lose a command just because you ran it in a different buffer

Custom prompts: Multiple prompt styles you can toggle between with C-c t (full vs. minimal) and C-c T (lighter vs. heavier full prompt)

— A custom welcome banner with keybinding hints

— History size of 100,000 entries with deduplication

Isearch

Enhanced incremental search with sensible defaults.

VC (Version Control)

This is one of the largest sections and one I'm most invested in. Emacs's built-in vc is an incredible piece of software that most people overlook in favor of Magit. I'm not saying it replaces Magit entirely, but with the right configuration it covers 95% of daily Git operations:

Git add/reset from vc-dir: S to stage, U to unstage, directly in the vc-dir buffer. Admittedly, I almost never use this because I'm now used to the Emacs-style VC workflow: C-x v D or C-x v =, then killing what I don’t want, splitting what isn’t ready yet, and finishing with C-c C-c. Amending with C-c C-e is awesome. Still useful once or twice a semester.

Git reflog viewer: A custom emacs-solo/vc-git-reflog command with ANSI color rendering and navigation keybindings

Browse remote: C-x v B opens your repository on GitHub/GitLab in a browser; with a prefix argument it jumps to the current file and line

Jump to current hunk: C-x v = opens the diff buffer scrolled to the hunk containing your current line

Switch between modified files: C-x C-g lets you completing-read through all modified/untracked files in the current repo

Pull current branch: A dedicated command for git pull origin <current-branch>

— Emacs 31 settings: vc-auto-revert-mode, vc-allow-rewriting-published-history, vc-dir-hide-up-to-date-on-revert

Smerge / Diff / Ediff

Merge conflict resolution and diff viewing. Ediff configured to split windows sanely (side by side, not in a new frame).

Eldoc

Documentation at point, with eldoc-help-at-pt (Emacs 31) for showing docs automatically.

Eglot

The LSP client that ships with Emacs. Configured with:

— Auto-shutdown of unused servers

— No event buffer logging (for performance)

— Custom server programs, including rassumfrassum for multiplexing TypeScript + ESLint + Tailwind (I wrote a whole post about that)

— Keybindings under C-c l for code actions, rename, format, and inlay hints

— Automatic enabling for all prog-mode buffers except emacs-lisp-mode and lisp-mode

Flymake / Flyspell / Whitespace

Diagnostics, spell checking, and whitespace visualization. All built-in, all configured.

Gnus

The Emacs newsreader and email client. Configured for IMAP/SMTP usage.

Man

Manual page viewer settings.

Minibuffer

Fine-tuned minibuffer behavior, including completion-eager-update from Emacs 31 for faster feedback during completion.

Newsticker

RSS/Atom feed reader built into Emacs. Customized with some extras I build my self for dealing with youtube feeds: thumbnail, transcripts, sending to AI for a quick summary, and so on.

Electric-Pair / Paren

Auto-closing brackets and parenthesis highlighting.

Proced

Process manager (like top, but inside Emacs).

Org

Org-mode configuration, because of course.

Speedbar

File tree navigation in a side window. With Emacs 31, speedbar gained speedbar-window support, so it can live inside your existing frame instead of spawning a new one.

Time

World clock with multiple time zones, sorted by ISO timestamp (Emacs 31).

Uniquify

Buffer name disambiguation when you have multiple files with the same name open.

Which-Key

Key discovery. Built into Emacs since version 30.

Webjump

Quick web searches from the minibuffer. Configured with useful search engines.

Language Modes

Specific configurations for every language I work with, organized into three areas:

Common Lisp: inferior-lisp and lisp-mode with custom REPL interaction, evaluation commands, and a poor man's SLIME/SLY setup that actually works quite well for basic Common Lisp development.

Non-Tree-sitter: sass-mode for when tree-sitter grammars aren't available.

Tree-sitter modes: ruby-ts-mode, js-ts-mode, json-ts-mode, typescript-ts-mode, bash-ts-mode, rust-ts-mode, toml-ts-mode, markdown-ts-mode (Emacs 31), yaml-ts-mode, dockerfile-ts-mode, go-ts-mode. Each one configured with tree-sitter grammar sources (which Emacs 31 is starting to define internally, so those definitions will eventually become unnecessary).


The Extras: 35 Self-Contained Modules

This is where the fun really is. Each of these is a complete, standalone Elisp file that reimplements functionality you'd normally get from an external package. They're all in lisp/ and can be used independently.

I call them "hacky reimplementations" in the spirit of Emacs Solo: they're not trying to be feature-complete replacements for their MELPA counterparts. They're trying to be small, understandable, and good enough for daily use while keeping the config self-contained.

emacs-solo-themes

Custom color themes based on Modus. Provides several theme variants: Catppuccin Mocha, Crafters (the default), Matrix, and GITS. All built on top of Emacs's built-in Modus themes by overriding faces, so you get the accessibility and completeness of Modus with different aesthetics.

emacs-solo-mode-line

Custom mode-line format and configuration. A hand-crafted mode-line that shows exactly what I want: buffer state indicators, file name, major mode, Git branch, line/column, and nothing else. No doom-modeline, no telephone-line, just format strings and faces.

emacs-solo-movements

Enhanced navigation and window movement commands. Extra commands for moving between windows, resizing splits, and navigating buffers more efficiently.

emacs-solo-formatter

Configurable format-on-save with a formatter registry. You register formatters by file extension (e.g., prettier for .tsx, black for .py), and the module automatically hooks into after-save-hook to format the buffer. All controllable via a defcustom, so you can toggle it on and off globally.

emacs-solo-transparency

Frame transparency for GUI and terminal. Toggle transparency on your Emacs frame. Works on both graphical and terminal Emacs, using the appropriate mechanism for each.

emacs-solo-exec-path-from-shell

Sync shell PATH into Emacs. The classic macOS problem: GUI Emacs doesn't inherit your shell's PATH. This module solves it the same way exec-path-from-shell does, but in about 20 lines instead of a full package.

emacs-solo-rainbow-delimiters

Rainbow coloring for matching delimiters. Colorizes nested parentheses, brackets, and braces in different colors so you can visually match nesting levels. Essential for any Lisp, and helpful everywhere else.

emacs-solo-project-select

Interactive project finder and switcher. A completing-read interface for finding and switching between projects, building on Emacs's built-in project.el.

emacs-solo-viper-extensions

Vim-like keybindings and text objects for Viper. If you use Emacs's built-in viper-mode (the Vim emulation layer), this extends it with text objects and additional Vim-like commands. No Evil needed.

emacs-solo-highlight-keywords

Highlight TODO and similar keywords in comments. Makes TODO, FIXME, HACK, NOTE, and similar keywords stand out in source code comments with distinctive faces. A small thing that makes a big difference.

emacs-solo-gutter

Git diff gutter indicators in buffers. Shows added, modified, and deleted line indicators in the margin, like diff-hl or git-gutter. Pure Elisp, using vc-git under the hood.

emacs-solo-ace-window

Quick window switching with labels. When you have three or more windows, this overlays single-character labels on each window so you can jump to any one with a single keystroke. A minimal reimplementation of the popular ace-window package.

emacs-solo-olivetti

Centered document layout mode. Centers your text in the window with wide margins, like olivetti-mode. Great for prose writing, Org documents, or any time you want a distraction-free centered layout.

emacs-solo-0x0

Upload text and files to 0x0.st. Select a region or a file and upload it to the 0x0.st paste service. The URL is copied to your kill ring. Quick and useful for sharing snippets.

emacs-solo-sudo-edit

Edit files as root via TRAMP. Reopen the current file with root privileges using TRAMP's /sudo:: prefix. A reimplementation of the sudo-edit package.

emacs-solo-replace-as-diff

Multi-file regexp replace with diff preview. Perform a search-and-replace across multiple files and see the changes as a diff before applying them. This one turned out to be more useful than I expected.

emacs-solo-weather

Weather forecast from wttr.in. Fetches weather data from wttr.in and displays it in an Emacs buffer. Because checking the weather shouldn't require leaving Emacs.

emacs-solo-rate

Cryptocurrency and fiat exchange rate viewer. Query exchange rates and display them inside Emacs. For when you need to know how much a bitcoin is worth but refuse to open a browser tab.

emacs-solo-how-in

Query cheat.sh for programming answers. Ask "how do I do X in language Y?" and get an answer from cheat.sh displayed right in Emacs. Like howdoi but simpler.

emacs-solo-ai

AI assistant integration (Ollama, Gemini, Claude). Send prompts to AI models directly from Emacs. Supports multiple backends: local Ollama, Google Gemini, and Anthropic Claude. The response streams into a buffer. No gptel, no ellama, just url-retrieve and some JSON parsing.

emacs-solo-dired-gutter

Git status indicators in Dired buffers. Shows Git status (modified, added, untracked) next to file names in Dired, using colored indicators in the margin. Think diff-hl-dired-mode but self-contained.

emacs-solo-dired-mpv

Audio player for Dired using mpv. Mark audio files in Dired, hit C-c m, and play them through mpv. You get a persistent mpv session you can control from anywhere with C-c m. A mini music player that lives inside your file manager.

emacs-solo-icons

File type icon definitions for Emacs Solo. The icon registry that maps file extensions and major modes to Unicode/Nerd Font icons. This is the foundation that the next three modules build on.

emacs-solo-icons-dired

File type icons for Dired buffers. Displays file type icons next to file names in Dired. Uses Nerd Font glyphs.

emacs-solo-icons-eshell

File type icons for Eshell listings. Same as above but for Eshell's ls output.

emacs-solo-icons-ibuffer

File type icons for ibuffer. And again for the buffer list.

emacs-solo-container

Container management UI for Docker and Podman. A full tabulated-list-mode interface for managing containers: list, start, stop, restart, remove, inspect, view logs, open a shell. Works with both Docker and Podman. This one started small and grew into a genuinely useful tool.

emacs-solo-m3u

M3U playlist viewer and online radio player. Open .m3u playlist files, browse the entries, and play them with mpv. RET to play, x to stop. Great for online radio streams.

emacs-solo-clipboard

System clipboard integration for terminals. Makes copy/paste work correctly between Emacs running in a terminal and the system clipboard. Solves the eternal terminal Emacs clipboard problem.

emacs-solo-eldoc-box

Eldoc documentation in a child frame. Shows eldoc documentation in a floating child frame near point instead of the echo area. A reimplementation of the eldoc-box package.

emacs-solo-khard

Khard contacts browser. Browse and search your khard address book from inside Emacs. Niche, but if you use khard for contact management, this is handy.

emacs-solo-flymake-eslint

Flymake backend for ESLint. Runs ESLint as a Flymake checker for JavaScript/TypeScript files. Disabled by default now that LSP servers handle ESLint natively, but still available if you prefer the standalone approach.

emacs-solo-erc-image

Inline images in ERC chat buffers. When someone posts an image URL in IRC, this fetches and displays the image inline in the ERC buffer. A small luxury that makes IRC feel more modern.

emacs-solo-yt

YouTube search and playback with yt-dlp and mpv. Search YouTube from Emacs, browse results, and play videos (or just audio) through mpv. Because sometimes you need background music and YouTube is right there.

emacs-solo-gh

GitHub CLI interface with transient menu. A transient-based menu for the gh CLI tool. Browse issues, pull requests, run actions, all from a structured Emacs interface without memorizing gh subcommands.


Emacs 31: Looking Forward

Throughout the config you'll see comments tagged ; EMACS-31 marking features that are coming (or already available on the development branch). Some highlights:

Window layout commands: window-layout-transpose, window-layout-rotate-clockwise, and flip commands. Finally, first-class support for rearranging window layouts

Tree-sitter grammar sources defined in modes: No more manually specifying treesit-language-source-alist entries for every language

markdown-ts-mode: Tree-sitter powered Markdown, built-in

Icomplete improvements: In-buffer adjustment, prefix indicators, and better vertical rendering

Speedbar in-frame: speedbar-window lets the speedbar live inside your frame as a normal window

VC enhancements: vc-dir-hide-up-to-date-on-revert, vc-auto-revert-mode, vc-allow-rewriting-published-history

ERC fixes: The scrolltobottom/fill-wrap dependency is finally resolved

native-comp-async-on-battery-power: Don't waste battery on native compilation

kill-region-dwim: Smart kill-region behavior

delete-pair-push-mark: Better delete-pair with mark pushing

World clock sorting: world-clock-sort-order for sensible timezone display

I tag these not just for my own reference, but so that anyone reading the config can see exactly which parts will become cleaner or unnecessary as Emacs 31 stabilizes. Some of the polyfill code I carry today, particularly around icomplete, exists specifically because those features haven't landed in a stable release yet.


What I've Learned

This latest cycle of working on Emacs Solo taught me a few things worth sharing.

Emacs gives you more than you think. Every time I set out to "reimplement" something, I discovered that Emacs already had 70% of it built in. vc is far more capable than most people realize. icomplete-vertical-mode is genuinely good. tab-bar-mode is a real workspace manager. proced is a real process manager. The gap between "built-in Emacs" and "Emacs with 50 packages" is smaller than the community often assumes.

Writing your own packages is the best way to learn Elisp. I learned more about Emacs Lisp writing emacs-solo-gutter and emacs-solo-container than I did in years of tweaking other people's configs. When you have to implement something from scratch, you're forced to understand overlays, process filters, tabulated-list-mode, transient, child frames, and all the machinery that packages usually hide from you.

Small is beautiful. Most of the modules in lisp/ are under 200 lines. Some are under 50. They don't try to handle every edge case. They handle my edge cases, and that's enough. If someone else needs something different, the code is simple enough to fork and modify.

Contributing upstream is worth it. Some of the things I built as workarounds (like the icomplete vertical prefix indicators) turned into upstream patches. When you're deep enough in a feature to build a workaround, you're deep enough to propose a fix.


Conclusion

Emacs Solo started as a personal challenge: can I have a productive, modern Emacs setup without installing a single external package?

The answer, after this cycle, is a definitive yes.

Is it for everyone? Absolutely not. If you're happy with Doom Emacs or Spacemacs or your own carefully curated package list, that's great. Those are excellent choices.

But if you're curious about what Emacs can do on its own, if you want a config where you understand every line, if you want something you can hand to someone and say "just drop this into ~/.emacs.d/ and it works", then maybe Emacs Solo is worth a look.

The repository is here: https://github.com/LionyxML/emacs-solo

It's been a lot of fun. I learned more in this cycle than in any previous one. And if anyone out there finds even a single module or config snippet useful, I'd be happy.

That's the whole point, really. Sharing what works.


Acknowledgements

None of this exists in a vacuum, and I want to give proper thanks.

First and foremost, to the Emacs core team. The people who maintain and develop GNU Emacs are doing extraordinary work, often quietly, often thanklessly. Every built-in feature I configure in init.el is the result of decades of careful engineering. The fact that Emacs 31 keeps making things better in ways that matter (tree-sitter integration, icomplete improvements, VC enhancements, window layout commands) is a testament to how alive this project is.

While working on Emacs Solo I also had the opportunity to contribute directly to Emacs itself. I originally wrote markdown-ts-mode, which was later improved and integrated with the help and review of Emacs maintainers. I also contributed changes such as aligning icomplete candidates with point in the buffer (similar to Corfu or Company) and a few fixes to newsticker.

I'm very grateful for the help, reviews, patience, and guidance from people like Eli Zaretskii, Yuan Fu, Stéphane Marks, João Távora, and others on the mailing lists.

To the authors of every package that inspired a module in lisp/. Even though Emacs Solo doesn't install external packages, it is deeply influenced by them. diff-hl, ace-window, olivetti, doom-modeline, exec-path-from-shell, eldoc-box, rainbow-delimiters, sudo-edit, and many others showed me what was possible and set the bar for what a good Emacs experience looks like. Where specific credit is due, it's noted in the source code itself.

A special thanks to David Wilson (daviwil) and the System Crafters community. David's streams and videos were foundational for me in understanding how to build an Emacs config from scratch, and the System Crafters community has been an incredibly welcoming and knowledgeable group of people. The "Crafters" theme variant in Emacs Solo exists as a direct nod to that influence.

To Protesilaos Stavrou (Prot), whose work on Modus themes, Denote, and his thoughtful writing about Emacs philosophy has shaped how I think about software defaults, accessibility, and keeping things simple. The fact that Emacs Solo's themes are built on top of Modus is no coincidence.

And to Gopar (goparism), whose Emacs content and enthusiasm for exploring Emacs from the ground up resonated deeply with the spirit of this project. It's encouraging to see others who believe in understanding the tools we use.

To everyone who I probably forgot to mention, who has opened issues, suggested features, or just tried Emacs Solo and told me about it: thank you. Open source is a conversation, and every bit of feedback makes the project better.