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-identification
flycheck
augmenting the prior to indicate errors and warnings (an equivalentflymake
augment exists)org
segment to show the current top heading and its parentbuffer-status
segment 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-alist
segment augmented byminions
project
segmentvc
segmenttab-bar
segmentanimation
segment
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
critical
essential
high
medium
andlow
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:
t
to always showcurrent
to always show for the selected windowcurrent-low
to show for current window if space allows itlow
to 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.