“The mode-line that can be seen is not the eternal mode-line.” — Lao-Tse,
Emacs 27+ edition
There was an age when every buffer wore a heavy belt of glyphs, numbers, and blinking widgets. Then, a simple Lisp script shaved its head, took its place in the echo area, and simply was. That file is Shaoline.
Shaoline replaces the traditional mode-line with a minimalist, highly functional string presented only in the echo area. It disappears the moment Emacs needs to speak, or remains if you wish. It does just enough and then nothing more. To walk its Dao, do nothing; all gets done.
“When nothing is done, nothing remains undone.” — Tao Te Ching, §48
(use-package shaoline
:ensure t
:init
(require 'shaoline-impure)
:config
(shaoline-mode 1))
Ready? Ignore the rest. Curious? Let insight flow further.
- Echo-area only — no child frames, windows, overlays.
- Functional core — all segments are pure functions: input → string.
- Zero timers unless truly needed — time or battery segments trigger timers, nothing else.
- Debounced updates — multiple triggers collapse into a single, smooth repaint.
- Optional mode-line hiding — see the void if you wish.
- Zero dependencies required — icons, battery, project, et al., only if chosen.
- Unit-test friendly — core composer is pure and side-effect free.
- Persistent center — the last non-empty message remains until a new one comes.
- Chan humor — a hint of Zen trickster may appear.
- Always-visible option — anchor under your code, even during (message …).
Student: “Master, how many dependencies does Shaoline have?” Master: “Mu.” — classic koan
M-x package-install RET shaoline RET
Clone and add to load-path
, or use your favorite package manager.
Requires Emacs 27.1 or later.
Shaoline’s default setup fits most needs, but the Dao is personal — customize your segments as your path suggests.
(setq shaoline-segments
'((:left shaoline-segment-major-mode-icon
shaoline-segment-buffer-name
shaoline-segment-modified
shaoline-segment-minor-modes)
(:center shaoline-segment-echo-message)
(:right shaoline-segment-position
shaoline-segment-project-name
shaoline-segment-git-branch
shaoline-segment-battery
shaoline-segment-input-method
shaoline-segment-digital-clock
(shaoline-segment-day-date :with-year nil)
shaoline-segment-moon-phase)))
Each section (:left, :center, :right) is fully customizable.
Variable | Default | Description |
---|---|---|
shaoline-enable-dynamic-segments | t | Disable to drop timers & external segments (e.g. time, battery) |
shaoline-timer-interval | 1 | Seconds between periodic refreshes (timer starts only if needed) |
shaoline-always-visible | t | Keep modeline visible, even during (message …) |
shaoline-enable-hooks | t | If nil, disables all update hooks — only manual refreshes happen |
shaoline-attach-advices | t | If nil, never adds advices (for message/warning interception) |
shaoline-attach-hooks | t | If nil, never adds/removes hooks automatically |
shaoline-message-timeout | 10 | Seconds to wait before shaoline redraw after a message |
shaoline-project-name-ttl | 2 | TTL (sec) for caching project name (in shaoline-caching group) |
shaoline-battery-ttl | 5 | TTL (sec) for caching battery status (in shaoline-caching group) |
shaoline-minor-modes-cache-ttl | 0.5 | TTL (sec) for minor-modes cache (in shaoline-caching group) |
shaoline-autohide-modeline | t | Hide the traditional mode-line while Shaoline is active |
shaoline-exclude-modes | list | Modes in which to not hide classic mode-line |
shaoline-update-hooks | list | Hooks which trigger repaint |
shaoline-right-padding | 0 | Extra spaces added at the right |
shaoline-debug | nil | Enable logs in //shaoline-logs// |
shaoline-dynamic-segments | list | Segments that require timers (e.g., clock, battery) |
shaoline-day-date-with-year | nil | Include year in day/date segment |
Customize interactively:
M-x customize-group RET shaoline RET
Total Control: “Wu Wei” (do not, let flow) For complete purity, disable everything side-effectful:
(setq shaoline-enable-hooks nil
shaoline-always-visible nil
shaoline-enable-dynamic-segments nil
shaoline-attach-advices nil
shaoline-attach-hooks nil)
(shaoline-mode 1)
(global-set-key (kbd "<f8>") #'shaoline--update)
To remove all traces after experimenting:
(shaoline-cleanup)
Segments like the project name and battery level may be slow to compute, so they’re TTL-cached (defaults: 2s and 5s). The battery segment additionally uses asynchronous computation (`async-start`) for non-blocking updates, showing “Batt…” as a placeholder while loading.
- Customize via
M-x customize-group RET shaoline-caching RET
- To get an immediate uncached value for testing:
(shaoline--segment-project-name-raw)
(shaoline--segment-battery-raw)
Symbol | Description | Requires |
---|---|---|
shaoline-segment-position | Line and column | — |
shaoline-segment-modified | “*” if buffer is modified | — |
shaoline-segment-echo-message | Last non-empty message (center) | — |
shaoline-segment-minor-modes | Minor-mode icons or abbreviations (expanded mappings for LSP, Flycheck, etc.) | — |
shaoline-segment-project-name | Project name (TTL-cached) | projectile (optional) |
shaoline-segment-git-branch | Current Git branch | vc-git |
shaoline-segment-vcs-state | Git status indicator (+/!/✗) | vc-git |
shaoline-segment-battery | Battery percent/status (async, TTL-cached) | battery.el, async.el |
shaoline-segment-input-method | Active input method | — |
shaoline-segment-digital-clock | 24h clock (e.g. “21:43”) | calendar.el |
shaoline-segment-day-date | Localized day/date (optional year) | calendar.el |
shaoline-segment-moon-phase | Moon phase (icon or ASCII) | calendar.el |
shaoline-segment-encoding | File encoding and EOL type | — |
shaoline-segment-flycheck | Flycheck/Flymake errors/warnings | flycheck or flymake |
shaoline-segment-major-mode | Major mode with optional icon | all-the-icons (opt.) |
shaoline-segment-buffer-name | Buffer name only | — |
shaoline-segment-major-mode-icon | Major-mode icon only | all-the-icons (opt.) |
Remove what doesn’t serve you. True enlightenment is lighter than a feather.
Shaoline works seamlessly with Flycheck, Evil, and more. Add your own segments as you wish.
(shaoline-define-simple-segment shaoline-segment-evil-state
"Show current evil state."
(when (bound-and-true-p evil-mode)
(propertize evil-state 'face 'shaoline-mode-face)))
(shaoline-define-simple-segment shaoline-segment-flycheck
"Show Flycheck errors/warnings."
(when (bound-and-true-p flycheck-mode)
(let ((err (flycheck-count-errors flycheck-current-errors)))
(propertize
(format "E:%d W:%d"
(or (cdr (assq 'error err)) 0)
(or (cdr (assq 'warning err)) 0))
'face 'shaoline-modified-face))))
More examples are in examples/custom-segments.el
.
By default, the center segment shows the latest non-empty user message, even if message
is called.
To have classic behavior (Shaoline disappears for other messages):
(setq shaoline-always-visible nil)
User messages persist until a new non-empty arrives or (message nil)
is issued.
A segment is simply a function returning a string (without side-effects):
(shaoline-define-segment shaoline-segment-buffer-size (buffer)
"Return buffer size in KiB."
(format "%.1f KiB" (/ (buffer-size buffer) 1024.0)))
(push 'shaoline-segment-buffer-size (alist-get :right shaoline-segments))
Side-effects are karmic debt; avoid them.
- Where did my old mode-line go? Set shaoline-autohide-modeline to nil to restore it.
- Why does Shaoline vanish when I run M-x? Minibuffer speaks, Shaoline waits silently.
- Can I use Doom icons?
Yes:
all-the-icons
is used if installed. - High CPU? Usually another package is spamming messages, or too many dynamic segments on a slow machine. Enable shaoline-debug to check shaoline-logs.
- Does it work in TTY? Yes—icons vanish, moon becomes ASCII, tranquility stays.
- Center message persists! Until a new non-empty arrives; clear with (message nil).
- Multi-line messages? First line + [more] indicator; full content shown briefly in the echo area.
- How to force Shaoline always visible? Set shaoline-always-visible = t.
Symptom | Possible Cause | Solution / Test |
---|---|---|
Flicker | Another package messaging rapidly | (setq shaoline-debug t) – see shaoline-logs |
No right segment | Window too narrow | Widen or adjust shaoline-right-padding |
Battery “N/A” | No battery detected | Hide segment or accept impermanence |
Center doesn’t update | No new non-empty messages | (message nil) to clear |
Modeline not visible | shaoline-always-visible = nil, waiting for timeout | Set shaoline-always-visible = t |
(push 'shaoline-segment-input-method
(alist-get :right shaoline-segments))
— Displays “EN” if no input-method; otherwise, its title.
- Org-clock in center:
(shaoline-define-simple-segment shaoline-segment-org-clock "Show current Org-clock if any." (when (and (fboundp 'org-clocking-p) (org-clocking-p)) (concat "🕑 " (org-clock-get-clock-string)))) (push 'shaoline-segment-org-clock (alist-get :center shaoline-segments))
- Tree-sitter language (Emacs 29+):
(shaoline-define-simple-segment shaoline-segment-ts-lang "Tree-sitter language name." (when (boundp 'treesit-language-at) (format "%s" (treesit-language-at (point))))) (push 'shaoline-segment-ts-lang (alist-get :left shaoline-segments))
- TRAMP remote host:
(shaoline-define-simple-segment shaoline-segment-tramp-host "Show user@host if running under TRAMP." (when (file-remote-p default-directory) (tramp-file-name-host (tramp-dissect-file-name default-directory)))) (push 'shaoline-segment-tramp-host (alist-get :right shaoline-segments))
(setq shaoline-enable-dynamic-segments nil)
Perfect for minimalism, TTY, underpowered machines, or reproducible benchmarks.
- Core string composition: <0.15 ms
- Six segments: <0.25 ms
- Timer only runs if genuinely needed
- Noise can be silenced: (setq message-log-max nil)
Change | New incantation |
---|---|
Time+Moon together | Use shaoline-segment-digital-clock and shaoline-segment-moon-phase |
Minor-mode icons missing | Add shaoline-segment-minor-modes |
Center message bug | Fixed |
Emacs | GUI | TTY | Native Comp | Windows | macOS | Linux |
---|---|---|---|---|---|---|
27.1 | ✔︎ | ✔︎ | – | ✔︎ | ✔︎ | ✔︎ |
28.x | ✔︎ | ✔︎ | – | ✔︎ | ✔︎ | ✔︎ |
29.x | ✔︎ | ✔︎ | ✔︎ | ✔︎ | ✔︎ | ✔︎ |
- Quick Zen:
README-QUICKZEN.org
- FAQ:
README-FAQ.org
- Change history:
CHANGELOG.org
Documentation is a finger pointing at the moon; Shaoline reveals both the moon and its phase.
Pull requests, issues, poems, haiku are welcome at: https://github.com/11111000000/shaoline
“If you meet the maintainer on the road, invite him for noodles.” — Zen proverb
MIT. Copy it, fork it, attach it to a spring kite and let it fly.
—
End of scroll. Close this buffer, breathe in, return to code.