Skip to content

Repository files navigation

echo-bar

Display status information in the Emacs echo area instead of the mode line, saving a line of screen real estate.

./screenshots/2026-03-04-213230_1366x768_scrot.png

what’s the point

Most echo-area status packages use a timer that recomputes every segment on every tick, creates temporary buffers to measure pixel widths, and writes overlays even when nothing changed. That works, but it’s wasteful.

echo-bar takes a different approach: event-driven updates with coalescing. Each segment declares what triggers it (hooks, advice, idle timers). When a trigger fires, the segment is marked dirty and a single update is scheduled via run-at-time 0 nil. Only dirty segments recompute. Overlays are written only when the rendered string actually changes.

The result is a status bar that feels instant and doesn’t burn CPU in the background.

Install

echo-bar is not yet on MELPA. For now, clone the repo and point your config at it.

use-package + local path

(use-package echo-bar
  :ensure nil
  :load-path "~/path/to/echo-bar"
  :custom
  (echo-bar-layout
   '(:center ("buffer-position" "buffer-name" "major-mode")
     :right  ("project" "vcs" "time" "battery")))
  :config
  (echo-bar-mode 1))

straight.el

(use-package echo-bar
  :straight (:host github :repo "chenanton/echo-bar")
  :custom
  (echo-bar-layout
   '(:center ("buffer-position" "buffer-name" "major-mode")
     :right  ("project" "vcs" "time" "battery")))
  :config
  (echo-bar-mode 1))

elpaca

(use-package echo-bar
  :ensure (:host github :repo "chenanton/echo-bar")
  :custom
  (echo-bar-layout
   '(:center ("buffer-position" "buffer-name" "major-mode")
     :right  ("project" "vcs" "time" "battery")))
  :config
  (echo-bar-mode 1))

Usage

(echo-bar-mode 1)   ; enable
(echo-bar-mode -1)  ; disable

Layout

echo-bar-layout is a plist mapping zone keys to lists of segment names. Three zones are supported:

ZonePositioning
:leftFlush left
:centerCentered (clamped to avoid right)
:rightRight-aligned

You can combine them however you like:

;; Right only (simplest)
(setq echo-bar-layout
  '(:right ("buffer-name" "major-mode" "vcs" "time")))

;; Center + right
(setq echo-bar-layout
  '(:center ("buffer-position" "buffer-name" "major-mode")
    :right  ("project" "vcs" "time" "battery")))

;; Left + right
(setq echo-bar-layout
  '(:left  ("buffer-name" "buffer-position")
    :right ("major-mode" "vcs" "project" "time")))

Other options

VariableDefaultDescription
echo-bar-separator" "String between segments in a group
echo-bar-right-padding1Chars of padding before the right edge
echo-bar-center-right-gap4Min gap between center and right groups
echo-bar-time-format"%a %b %-e %-I:%M %p"Format string passed to format-time-string
echo-bar-idle-fallback-interval2.0Idle seconds before safety-net refresh

Faces

Every segment has a face you can customize. They all inherit from echo-bar-default (which inherits default) except echo-bar-dim (inherits shadow) and echo-bar-warning (inherits warning).

;; Example: make the buffer name bold
(set-face-attribute 'echo-bar-buffer-name nil :weight 'bold)

Or with use-package:

:custom-face
(echo-bar-buffer-name ((t (:inherit bold))))
(echo-bar-major-mode ((t (:inherit org-level-4))))

Segments

NameShowsScopeTrigger
buffer-nameBuffer name, [+] if dirtybufferpost-command, save
buffer-positionLine:columnbufferpost-command
selection-infoSel:3L,42C when regionbufferpost-command
narrowNarrow when narrowedbufferpost-command
macroREC during kbd macrobufferpost-command
processActive process infobufferpost-command
profilerPROF when profiler runsglobalpost-command
major-modeMode namebuffermode change
vcsBranch namebufferfile open/save, vc
projectProject namebufferfile open
blameBlame in magit-blamebufferblame mode hook
text-scaleZoom:+2 when scaledbuffertext-scale hook
repeatREPEAT during repeat-modeglobalrepeat-post-hook
timeFormatted date/timeglobalidle timer
batteryBattery stringglobalbattery-update

Segments only appear when they have something to show. nil or "" means the segment is hidden and no separator is added.

Defining custom segments

(echo-bar-defsegment my-word-count
  "Word count for the current buffer."
  :triggers (:hooks (post-command-hook))
  :fn (format "%dW" (count-words (point-min) (point-max)))
  :face echo-bar-dim)

Then add "my-word-count" to your layout.

Properties:

KeyDescription
:fnBody that returns a string (or nil to hide)
:faceFace applied to the output
:scope:buffer (default) or :global
:triggersPlist of :hooks, :advice, :timers
:setupBody run once on first activation

Trigger types:

  • :hooks – list of hook symbols (e.g. (post-command-hook after-save-hook))
  • :advice – alist of (FUNCTION . HOW) pairs (e.g. ((vc-refresh-state . :after)))
  • :timers – plist (:idle SECS :repeat BOOL) for idle-timer triggers

How it works

Traditional echo-area packages poll on a timer. echo-bar does this instead:

  1. Trigger – A hook fires or advice runs. The associated segment is marked dirty.
  2. Coalesce – run-at-time 0 nil schedules a single update. Multiple triggers in the same command cycle collapse into one call.
  3. Recompute – Only dirty segments re-evaluate their :fn. Clean segments keep their cached output.
  4. Render – Cached outputs are joined per zone and positioned with space :align-to display properties.
  5. Write – The rendered string is compared to the last write. If it’s identical, the overlay is not touched.

Messages are truncated with … to fit alongside the bar. If you need the full message, check *Messages*.

Requirements

Emacs 29.1 or later. No external dependencies.

Alternatives

  • mini-echo – Similar concept, more segments out of the box. Uses a 300ms timer for updates.
  • awesome-tray – Another echo-area status bar.
  • doom-modeline – Not an echo-area package, but a well-optimized modeline with hook-cached segments. Good reference for the approach.

License

GPL-3.0-or-later. See LICENSE.

About

Display status information in the Emacs echo area instead of the mode line, saving a line of screen real estate.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages