Improving Dired in Emacs Solo: Git Status and File Icons



⚠️ WARNING: EMOJI HEAVY CONTENT AHEAD ⚠️
🚨📢 Pro Tip: Need more emoji in your life? Emacs C-x 8 RET
is
magic 🪄✨
There are many ways to enhance Dired in Emacs. From full-featured
packages like diff-hl
, all-the-icons-dired
and nerd-icons-dired
to built-in solutions. There's absolutely nothing wrong with using
well-maintained packages (I do for many things!), but sometimes you
might want something more minimal or tailored to your workflow.
These are my personal implementations from Emacs Solo that follow a specific philosophy:
🛠️ No dependencies: beyond Emacs itself
📋 Copy/paste installable: into any config
🧰 Hacky but practical: they solve my needs first and small imperfections are ok
🔧 Easily modifiable: if you know basic Elisp
Some screenshots of what were are talking about:
What These Do
1️⃣ Git Status Gutter: Left-margin indicators for file status (modified/added/deleted)
2️⃣ File Type Icons: Unicode symbols instead of heavy icon fonts
Note: These aren't trying to replace full packages. They're alternatives for those who prefer lightweight solutions.
1. File Icons Using Unicode
A font-free alternative to icon packages:
;;; EMACS-SOLO-DIRED-ICONS
;;
(use-package emacs-solo-dired-icons
:ensure nil
:no-require t
:defer t
:init
(defvar emacs-solo/dired-icons-file-icons
'(("el" . "📜") ("rb" . "💎") ("js" . "⚙️") ("ts" . "⚙️")
("json" . "🗂️") ("md" . "📝") ("txt" . "📝") ("html" . "🌐")
("css" . "🎨") ("scss" . "🎨") ("png" . "🖼️") ("jpg" . "🖼️")
("jpeg" . "🖼️") ("gif" . "🖼️") ("svg" . "🖼️") ("pdf" . "📄")
("zip" . "📦") ("tar" . "📦") ("gz" . "📦") ("bz2" . "📦")
("7z" . "📦") ("org" . "📝") ("sh" . "💻") ("c" . "🔧")
("h" . "📘") ("cpp" . "➕") ("hpp" . "📘") ("py" . "🐍")
("java" . "☕") ("go" . "🌍") ("rs" . "💨") ("php" . "🐘")
("pl" . "🐍") ("lua" . "🎮") ("ps1" . "🔧") ("exe" . "⚡")
("dll" . "🔌") ("bat" . "⚡") ("yaml" . "⚙️") ("toml" . "⚙️")
("ini" . "⚙️") ("csv" . "📊") ("xls" . "📊") ("xlsx" . "📊")
("sql" . "🗄️") ("log" . "📝") ("apk" . "📱") ("dmg" . "💻")
("iso" . "💿") ("torrent" . "⏳") ("bak" . "🗃️") ("tmp" . "⚠️")
("desktop" . "🖥️") ("md5" . "🔐") ("sha256" . "🔐") ("pem" . "🔐")
("sqlite" . "🗄️") ("db" . "🗄️")
("mp3" . "🎶") ("wav" . "🎶") ("flac" . "🎶")
("ogg" . "🎶") ("m4a" . "🎶") ("mp4" . "🎬") ("avi" . "🎬")
("mov" . "🎬") ("mkv" . "🎬") ("webm" . "🎬") ("flv" . "🎬")
("ico" . "🖼️") ("ttf" . "🔠") ("otf" . "🔠") ("eot" . "🔠")
("woff" . "🔠") ("woff2" . "🔠") ("epub" . "📚") ("mobi" . "📚")
("azw3" . "📚") ("fb2" . "📚") ("chm" . "📚") ("tex" . "📚")
("bib" . "📚") ("apk" . "📱") ("rar" . "📦") ("xz" . "📦")
("zst" . "📦") ("tar.xz" . "📦") ("tar.zst" . "📦") ("tar.gz" . "📦")
("tgz" . "📦") ("bz2" . "📦") ("mpg" . "🎬") ("webp" . "🖼️")
("flv" . "🎬") ("3gp" . "🎬") ("ogv" . "🎬") ("srt" . "🔠")
("vtt" . "🔠") ("cue" . "📀"))
"Icons for specific file extensions in Dired.")
(defun emacs-solo/dired-icons-icon-for-file (file)
(if (file-directory-p file)
"📁"
(let* ((ext (file-name-extension file))
(icon (and ext (assoc-default (downcase ext) emacs-solo/dired-icons-file-icons))))
(or icon "📄"))))
(defun emacs-solo/dired-icons-icons-regexp ()
"Return a regexp that matches any icon we use."
(let ((icons (mapcar #'cdr emacs-solo/dired-icons-file-icons)))
(concat "^\\(" (regexp-opt (cons "📁" icons)) "\\) ")))
(defun emacs-solo/dired-icons-add-icons ()
"Add icons to filenames in Dired buffer."
(when (derived-mode-p 'dired-mode)
(let ((inhibit-read-only t)
(icon-regex (emacs-solo/dired-icons-icons-regexp)))
(save-excursion
(goto-char (point-min))
(while (not (eobp))
(condition-case nil
(when-let ((file (dired-get-filename nil t)))
(dired-move-to-filename)
(unless (looking-at-p icon-regex)
(insert (concat (emacs-solo/dired-icons-icon-for-file file) " "))))
(error nil)) ;; gracefully skip invalid lines
(forward-line 1))))))
(add-hook 'dired-after-readin-hook #'emacs-solo/dired-icons-add-icons))
Tradeoffs:
✔️ Works everywhere (no special fonts)
✔️ Adds negligible overhead
❌ Less pretty than proper icons
❌ Manual extension mapping
2. Git Status Gutter for Dired
A "good enough" implementation showing Git status without external packages:
;;; EMACS-SOLO-DIRED-GUTTER
;;
(use-package emacs-solo-dired-gutter
:ensure nil
:no-require t
:defer t
:init
(setq emacs-solo-dired-gutter-enabled t)
(defvar emacs-solo/dired-git-status-overlays nil
"List of active overlays in Dired for Git status.")
(defun emacs-solo/dired--git-status-face (code)
"Return a cons cell (STATUS . FACE) for a given Git porcelain CODE."
(let* ((git-status-untracked "??")
(git-status-modified " M")
(git-status-modified-alt "M ")
(git-status-deleted "D ")
(git-status-added "A ")
(git-status-renamed "R ")
(git-status-copied "C ")
(git-status-ignored "!!")
(status (cond
((string-match-p "\\?\\?" code) git-status-untracked)
((string-match-p "^ M" code) git-status-modified)
((string-match-p "^M " code) git-status-modified-alt)
((string-match-p "^D" code) git-status-deleted)
((string-match-p "^A" code) git-status-added)
((string-match-p "^R" code) git-status-renamed)
((string-match-p "^C" code) git-status-copied)
((string-match-p "\\!\\!" code) git-status-ignored)
(t " ")))
(face (cond
((string= status git-status-ignored) 'shadow)
((string= status git-status-untracked) 'warning)
((string= status git-status-modified) 'font-lock-function-name-face)
((string= status git-status-modified-alt) 'font-lock-function-name-face)
((string= status git-status-deleted) 'error)
((string= status git-status-added) 'success)
(t 'font-lock-keyword-face))))
(cons status face)))
(defun emacs-solo/dired-git-status-overlay ()
"Overlay Git status indicators on the first column in Dired."
(interactive)
(require 'vc-git)
(let ((git-root (ignore-errors (vc-git-root default-directory))))
(when (and git-root
(not (file-remote-p default-directory))
emacs-solo-dired-gutter-enabled)
(setq git-root (expand-file-name git-root))
(let* ((git-status (vc-git--run-command-string nil "status" "--porcelain" "--ignored" "--untracked-files=normal"))
(status-map (make-hash-table :test 'equal)))
(mapc #'delete-overlay emacs-solo/dired-git-status-overlays)
(setq emacs-solo/dired-git-status-overlays nil)
(dolist (line (split-string git-status "\n" t))
(when (string-match "^\\(..\\) \\(.+\\)$" line)
(let* ((code (match-string 1 line))
(file (match-string 2 line))
(fullpath (expand-file-name file git-root))
(status-face (emacs-solo/dired--git-status-face code)))
(puthash fullpath status-face status-map))))
(save-excursion
(goto-char (point-min))
(while (not (eobp))
(let* ((file (ignore-errors (expand-file-name (dired-get-filename nil t)))))
(when file
(setq file (if (file-directory-p file) (concat file "/") file))
(let* ((status-face (gethash file status-map (cons " " 'font-lock-keyword-face)))
(status (car status-face))
(face (cdr status-face))
(status-str (propertize (format " %s " status) 'face face))
(ov (make-overlay (line-beginning-position) (1+ (line-beginning-position)))))
(overlay-put ov 'before-string status-str)
(push ov emacs-solo/dired-git-status-overlays))))
(forward-line 1)))))))
(add-hook 'dired-after-readin-hook #'emacs-solo/dired-git-status-overlay))
Tradeoffs:
✔️ Native Emacs speed
✔️ Visual feedback without clutter
✔️ No extra packages
❌ Less detailed than diff-hl-dired-mode
❌ Local files only
❌ Requires manual refresh (with g
)
Philosophy Behind These Hacks
🎯 Solve immediate needs: These do exactly what I need. Your mileage may vary, and that's ok!
🔍 Stay editable: No abstractions hiding the core logic.
✨ Embrace imperfection: The icons don't cover every file type, and that's okay.
And AGAIN: These aren't polished packages. They're starter code for your own tweaks. 😊
When To Use These vs Packages
| Topic | My Version | Full Packages |
| ------------- | ------------- | ------------------- |
| Installation | Copy/paste | Package-install |
| Customization | Edit directly | Read package docs |
| Appearance | Functional | Polished |
| Maintenance | You own it | Community-supported |
Choose these if: You value simplicity over features. You want to deal with things on your own. You want to learn while creating your own tools.
Choose packages if: You want battle-tested solutions
🚀 Try It Out
- 📋 Copy either/both snippets to your config
- 🎨 Tweak the icons or status symbols
- ⚙️ Adapt to your workflow
Or grab the latest from: Emacs Solo.
Happy hacking! 💻