Skip to content

11111000000/shaoline

Repository files navigation

Shaoline – The Modeline of Nothingness

“The mode-line that can be seen is not the eternal mode-line.” — Lao-Tse, Emacs 27+ edition

screenshot-shaoline.png

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

Quick Start

(use-package shaoline
  :ensure t
  :init
  (require 'shaoline-impure)
  :config
  (shaoline-mode 1))

Ready? Ignore the rest. Curious? Let insight flow further.

Features — Reflections of Emptiness

  1. Echo-area only — no child frames, windows, overlays.
  2. Functional core — all segments are pure functions: input → string.
  3. Zero timers unless truly needed — time or battery segments trigger timers, nothing else.
  4. Debounced updates — multiple triggers collapse into a single, smooth repaint.
  5. Optional mode-line hiding — see the void if you wish.
  6. Zero dependencies required — icons, battery, project, et al., only if chosen.
  7. Unit-test friendly — core composer is pure and side-effect free.
  8. Persistent center — the last non-empty message remains until a new one comes.
  9. Chan humor — a hint of Zen trickster may appear.
  10. Always-visible option — anchor under your code, even during (message …).

Student: “Master, how many dependencies does Shaoline have?” Master: “Mu.” — classic koan

Installation

GNU ELPA / MELPA

M-x package-install RET shaoline RET

Other Ways (Straight/Quelpa/Git)

Clone and add to load-path, or use your favorite package manager. Requires Emacs 27.1 or later.

Philosophy ⇒ Configuration

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.

VariableDefaultDescription
shaoline-enable-dynamic-segmentstDisable to drop timers & external segments (e.g. time, battery)
shaoline-timer-interval1Seconds between periodic refreshes (timer starts only if needed)
shaoline-always-visibletKeep modeline visible, even during (message …)
shaoline-enable-hookstIf nil, disables all update hooks — only manual refreshes happen
shaoline-attach-advicestIf nil, never adds advices (for message/warning interception)
shaoline-attach-hookstIf nil, never adds/removes hooks automatically
shaoline-message-timeout10Seconds to wait before shaoline redraw after a message
shaoline-project-name-ttl2TTL (sec) for caching project name (in shaoline-caching group)
shaoline-battery-ttl5TTL (sec) for caching battery status (in shaoline-caching group)
shaoline-minor-modes-cache-ttl0.5TTL (sec) for minor-modes cache (in shaoline-caching group)
shaoline-autohide-modelinetHide the traditional mode-line while Shaoline is active
shaoline-exclude-modeslistModes in which to not hide classic mode-line
shaoline-update-hookslistHooks which trigger repaint
shaoline-right-padding0Extra spaces added at the right
shaoline-debugnilEnable logs in //shaoline-logs//
shaoline-dynamic-segmentslistSegments that require timers (e.g., clock, battery)
shaoline-day-date-with-yearnilInclude 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)

TTL (Time-To-Live) Cache for Heavier Segments

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)

Standard Segments

SymbolDescriptionRequires
shaoline-segment-positionLine and column
shaoline-segment-modified“*” if buffer is modified
shaoline-segment-echo-messageLast non-empty message (center)
shaoline-segment-minor-modesMinor-mode icons or abbreviations (expanded mappings for LSP, Flycheck, etc.)
shaoline-segment-project-nameProject name (TTL-cached)projectile (optional)
shaoline-segment-git-branchCurrent Git branchvc-git
shaoline-segment-vcs-stateGit status indicator (+/!/✗)vc-git
shaoline-segment-batteryBattery percent/status (async, TTL-cached)battery.el, async.el
shaoline-segment-input-methodActive input method
shaoline-segment-digital-clock24h clock (e.g. “21:43”)calendar.el
shaoline-segment-day-dateLocalized day/date (optional year)calendar.el
shaoline-segment-moon-phaseMoon phase (icon or ASCII)calendar.el
shaoline-segment-encodingFile encoding and EOL type
shaoline-segment-flycheckFlycheck/Flymake errors/warningsflycheck or flymake
shaoline-segment-major-modeMajor mode with optional iconall-the-icons (opt.)
shaoline-segment-buffer-nameBuffer name only
shaoline-segment-major-mode-iconMajor-mode icon onlyall-the-icons (opt.)

Remove what doesn’t serve you. True enlightenment is lighter than a feather.

Integrations & Custom Segments

Shaoline works seamlessly with Flycheck, Evil, and more. Add your own segments as you wish.

Evil-mode

(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)))

Flycheck

(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.

Message Persistence & “Always-visible” Option

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.

Write Your Own Segment

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.

FAQ — Frequently Asked Koans

  1. Where did my old mode-line go? Set shaoline-autohide-modeline to nil to restore it.
  2. Why does Shaoline vanish when I run M-x? Minibuffer speaks, Shaoline waits silently.
  3. Can I use Doom icons? Yes: all-the-icons is used if installed.
  4. High CPU? Usually another package is spamming messages, or too many dynamic segments on a slow machine. Enable shaoline-debug to check shaoline-logs.
  5. Does it work in TTY? Yes—icons vanish, moon becomes ASCII, tranquility stays.
  6. Center message persists! Until a new non-empty arrives; clear with (message nil).
  7. Multi-line messages? First line + [more] indicator; full content shown briefly in the echo area.
  8. How to force Shaoline always visible? Set shaoline-always-visible = t.

Troubleshooting

SymptomPossible CauseSolution / Test
FlickerAnother package messaging rapidly(setq shaoline-debug t) – see shaoline-logs
No right segmentWindow too narrowWiden or adjust shaoline-right-padding
Battery “N/A”No battery detectedHide segment or accept impermanence
Center doesn’t updateNo new non-empty messages(message nil) to clear
Modeline not visibleshaoline-always-visible = nil, waiting for timeoutSet shaoline-always-visible = t

Segment Index and Cookbook

Input-method indicator

(push 'shaoline-segment-input-method
      (alist-get :right shaoline-segments))

— Displays “EN” if no input-method; otherwise, its title.

Cookbook Examples

  1. 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))
        
  2. 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))
        
  3. 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))
        

Disabling All Dynamic Segments

(setq shaoline-enable-dynamic-segments nil)

Perfect for minimalism, TTY, underpowered machines, or reproducible benchmarks.

Zen of Performance

  • 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)

Migration

ChangeNew incantation
Time+Moon togetherUse shaoline-segment-digital-clock and shaoline-segment-moon-phase
Minor-mode icons missingAdd shaoline-segment-minor-modes
Center message bugFixed

Compatibility

EmacsGUITTYNative CompWindowsmacOSLinux
27.1✔︎✔︎✔︎✔︎✔︎
28.x✔︎✔︎✔︎✔︎✔︎
29.x✔︎✔︎✔︎✔︎✔︎✔︎

Further Reading / Scrolls of Emptiness

  • 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.

Contributions

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

License

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.

About

shaoline-mode

Resources

Stars

Watchers

Forks

Packages

No packages published

Contributors 2

  •  
  •