This package provides a highly modular mode-line. The whale-line is
a list of segments divided into two halves (left and right). Each
segment is positioned and enabled by its inclusion and positioning in
whale-line-segments (wherein | symbolizes the dividing line).
The package ships with a wide array of segments and some augments to enhance those segments.
You may also create your own segments and augments using the
whale-line-create-* macros. In fact, you could disregard the
pre-shipped segments entirely and just use this package as a library
to build your own mode-line.
This package takes inspiration from two other great custom mode-lines:
mood-line and doom-modeline.
Modeline in a split frame (theme is doom-vibrant).
On the left window, the segment son the left are
major-mode(usingall-the-icons)buffer-identificationflycheckaugmenting the prior to indicate errors and warnings (an equivalentflymakeaugment exists)orgsegment to show the current top heading and its parentbuffer-statussegment to indicate the buffer is edited
Not shown are segments for LSP and debug sessions, as well as
window-status that each show an icon when they’re active. Also not
shown is process that shows mode-line-process as well as client
showing mode-line-client.
On the right you can see
minor-mode-alistsegment augmented byminionsprojectsegmentvcsegmenttab-barsegmentanimationsegment
Not shown is the misc-info segment that shows mode-line-misc-info.
The right window shows fewer segments. That’s because many segments are defined to only show on the current window or if space isn’t tight.
If you use straight or quelpa, you know what to do.
If you’re on Emacs >29, I recommend using package-vc-install.
Alternatively, provided you have Cask, you can install the package
with make package-install.
The position and order of the segments can be controlled by
customizing whale-line-segments. You can freely re-arrange them,
leave some out, add your custom ones.
If you move, add or remove a segment after loading the package and
enabling the mode, you will need to call whale-line-rebuild to see
the change.
(use-package whale-line
:config
(whale-line-mode +1)
;; If you want to use icons.
(whale-line-iconify-mode)
:custom
(whale-line-segments '(major-mode
buffer-identification
org
buffer-status
position
selection
client
lsp
debug
window-status
process
| ; Divides into left and right.
misc-info
minor-modes
project
vc
tab-bar
animation))
;; Strategy to use when there's not enough space to render all segments.
(whale-line-segment-strategy 'tiered) ; Other options are `prioritize', `elide' and `ignore'.
(whale-line-tier-formatting-cache-max-age 2) ; Time in seconds the right hand side's length is cached.
;; Animation.
(whale-line-segments-animation-key-frames [
" "
". "
".. "
"..."
" .."
" ."
])
(whale-line-segments-animation-speed 0.4)
;; Buffer identification.
(whale-line-segments-buffer-identification-path-segments 1)
(whale-line-segments-buffer-identification-path-segments-length 'full)
;; Org.
(whale-line-segments-org-separator ">")
(whale-line-segments-org-ellispis "…")
(whale-line-segments-org-elision "*")
(whale-line-segments-org-max-count 2)
;; Window status.
(whale-line-segments-window-status-separator "·")
;; Specs for used icons.
(whale-line-iconify-specs
'((project . (:name "package" :font octicon :face whale-line-emphasis))
(vc . (:name "code-fork" :face whale-line-contrast))
(major-mode . (:function all-the-icons-icon-for-buffer))
(buffer-read-only . (:name "lock" :face whale-line-contrast :parent buffer-status))
(buffer-file-name . (:name "sticky-note-o" :face whale-line-shadow :parent buffer-status))
(buffer-modified . (:name "pencil" :face whale-line-emphasis :parent buffer-status))
(window-dedicated . (:name "link" :face whale-line-shadow :parent window-status))
(window-no-other . (:name "low-vision" :face whale-line-shadow :parent window-status))
(window-no-delete . (:name "deaf" :face whale-line-shadow :parent window-status))
(buffer-fallback . (:name "question-circle" :face whale-line-contrast :no-defaults t))
(lsp . (:name "plug" :face whale-line-contrast))
(debug . (:name "bug" :face whale-line-urgent)))
;; List of icons to disable.
(whale-line-iconify-disabled nil))If you use strategy tiered (the default) you may want to customize
the defaults.
All segments belong to a tier. Segments belonging to a lower tier will be removed if space is low. The strategy will always show the lowest possible tier. The tiers are
criticalessentialhighmediumandlow
If you’re unhappy with the default settings, you can use
whale-line-with-tiers to change them in bulk after loading the
package.
(whale-line-with-tiers
;; Lower tier of `minor-modes' and `process'.
minor-modes
process
medium
;; Make `tab-bar' critical.
tab-bar
critical)If you use strategy prioritize you may want to customize the
defaults.
All segments are created with a priority that determines on what condition the segment is shown. The possible values are:
tto always showcurrentto always show for the selected windowcurrent-lowto show for current window if space allows itlowto show if space allows it
If you’re unhappy with the default settings, you can use
whale-line-with-priorities to change them in bulk after loading the
package.
(whale-line-with-priorities
;; Make `major-mode' and `buffer-status' segment show only for
;; current window.
major-mode
buffer-status
current
;; Make `project' segment show only if space allows it.
project
low
;; Always show `lsp' segment.
lsp
t)You can use whale-line-edit to edit segment positioning and
inclusion. It will pop to a buffer where you can move, add and delete
segments. The empty line represents the divider. If your reordering is
valid (single divider, only including known segments), you can apply
the change with C-c C-c. To persist the change you need to use C-c
C-w.
You may create your own segments and augments using macros
whale-line-create-stateless-segment,
whale-line-create-stateful-segment and whale-line-create-augment.
Note however that their signature is not finalized and may change at
any time. (Be sure to add your segment to whale-line-segments at the
desired position.)
As the macro names suggest, there are three things you can create:
- Stateless segments
- Stateful segments
- Augments for either segment type
Be sure to create your segments before activating whale-line-mode.
A stateless segment is just that: a segment without a state. This simply means that the segment will be re-rendered on every mode-line update.
This is ideal for segments that are not costly to render and should be up-to-date at all times.
Stateless segments use either a variable or a function to yield their representation on a mode-line. If you’re familiar with mode-line constructs, this would be the simplest stateless segment definition.
(defvar my-stateless-segment '((:propertize "hello" face success)))
(whale-line-create-stateless-segment stateless
:var my-stateless-segment)This would create segment stateless that would render “hello”
propertized with face success on every mode-line update.
This is fine in most cases but if the construction of your segment is a bit more involved than a mode-line construct allows, you might want a function.
(defun my-stateless-getter ()
"Construct my segment."
(if (org-before-first-heading-p)
"before"
"after"))
(whale-line-create-stateless-segment stateless
:getter my-stateless-getter)Stateless segments accept :condition which should be a form that is
evaluated before the getter. If it returns nil an empty string is
returned instead.
(whale-line-create-stateless-segment conditional
:var my-stateless-segment
:condition (derived-mode-p 'text-mode))A stateful segment is a segment with a state. This means that it will return its state unchanged on every mode-line update and only re-calculate that state when it’s necessary.
This type makes sense when processing the segment takes a lot of time or resources even though the result of the processing itself only changes at certain known junctures.
There are two ways to tell such a segment to re-calculate: by providing a list of hooks, a list functions to advise or both.
The recalculation is defined as the segment’s getter.
Let’s have a look.
(defun my-stateful-fun ()
"Return the major mode."
(let ((calculated ;; Do some heavy stuff here.
))
calculated))
(whale-line-create-stateful-segment stateful
:getter my-stateful-fun
:hooks (change-major-mode-hook))This would call my-stateless-fun only on the first mode-line
update and then store it. On each subsequent update the stored value
is returned. The value is updated whenever change-major-mode-hook is
run.
You may also want to use :after to advise a list of functions after
which the state should be updated.
(whale-line-create-stateful-segment advised
:getter my-stateful-fun
:after (undo redo))Whenever undo or redo are called, my-stateful-fun would be
called afterwards (with the same arguments) to updated the state.
You can also specify your own advice combinator.
(whale-line-create-stateful-segment before-advised
:advice (:before . (undo redo)))Sometimes a segment you want already exists in a basic form but you want to enhance it when certain criteria are met. This is where augments come into play.
The definition of augments is similar to that of stateful segments. You define either hooks or functions to advise. Other than stateful segments, these hooks being run (or functions being called) do not update another segment directly, instead they just call an action.
The relationship between a segment and its augment is therefore somewhat tenuous in that you need to define how exactly the augmentation is to take place.
The easiest way here is using :after-while in combination with a
stateful segment or a stateless getter-based segment.
(defun my-augment-fun (calculated)
"Enhance CALCULATED value."
(concat calculated ":augmented"))
(defun my-augment-should-augment-p ()
"Only augment on Linux."
(eq system-type 'gnu/linux))
(whale-line-create-augment my-augment
:verify my-augment-should-augment-p
:action my-augment-fun
;; You may also provide a list.
:after-while whale-line-stateful--render
;; Or equivalent and more readable.
:wraps stateful)If you don’t set :verify always will be used for augments. More on
this below.
You can also use :after or specify your own advice combinator.
(whale-line-create-augment before-augment
;; :after (whale-line-staetful--get-segment)
:advice (:before-while . (whale-line-stateful--render)))The function whale-line-stateful--get-segment is created by previous
declaration for segment stateful. It is called when the state is
updated so our augment advises it to return an augmented value.
If the segment provides a port (see below), you can also use
:plugs-into.
(defvar slot-var nil)
(defvar slot-construct '(("fe" slot-var)))
(defun slot-port (a b)
"Concat A and B."
(setq slot-var (concat a b))
(whale-line-create-stateless-segment slot
:var slot-construct
:port slot-port)
(defun plug-action ()
"Return values to concatenate."
(list "male" "female"))
(whale-line-create-augment plug
:action plug-action
:plugs-into slot
:hooks (change-major-mode-hook))Whenever change-major-mode-hook is run, plug-action would be
called and its result passed to slot-port (with some indirection),
setting slot-var to “malefemale”. The segment would now show
“femalefemale”.
If your segment (or augment) requires a setup or teardown routine, you
can pass a lambda or function symbol to :setup and/or :teardown.
These functions will be called whenever
whale-line-{setup,teardown}-hook is run. This is the case when
whale-line-mode is enabled/disabled or when segments are re-built
using whale-line-rebuild (provided the segment was added/removed
since the previous build).
Note that whether you provide such a routine or not, there’s always a
setup and a teardown function (used for :hooks or :advice for
example).
Mostly makes sense for augments. This function is called before a
setup or teardown happens. If it yields nil, no setup/teardown will
take place. Note that for segments this function will replace the
default check of (memq '<segment> whale-line-segments).
The default priority of your segment (see section Customizing Priorities).
You can use this to disallow padding the segment. Normally, depending on its position, the segment will have padding to its left/right.
If your segment comes pre-padded (for example if you use an external
construct that already adds whitespace on the left or right), you can
pass left, right or all here. This will ensure that the segment
won’t get superfluous padding on that side or both sides, no matter
how it’s positioned.
This is a function that augments using :plug-into call with their
result. This function should set some variable used during internal
rendering for augmentation.
If you want to build something more complicated, you might need to know what functions and variables are created during macro expansion. So here’s a summary.
Stateless segments define a function whale-line-<name>--render. This
function is called to render the segment. If they use :var this
function will just return that variable’s value. If they use :getter
this function will call additionally created
whale-line-<name>--getter that in turn will finally call the passed
function. The reason for this nesting and indirection is that you may
pass either a function symbol or an anonymous function to :getter.
Stateful segments hold their state in local variable (not function!)
whale-line-<name>--render. This variable is set to symbol initial
at first to make sure the the value is set at least once during
format-mode-line. The function that does this uses pattern
whale-line-<name>--setter. It calls the passed getter to set the
variable.
Augments also create whale-line-<name>--setter to do their thing.
If you’re using :port and :plugs-into, the segment with :port
will create function whale-line-<name>--port that will be called
with the result of the augment’s action. That means the return value
of the action should match the arity of the port function.