summaryrefslogtreecommitdiff
path: root/lisp
diff options
context:
space:
mode:
authorcashmere <cashmere@autistici.org>2026-08-11 01:07:16 +0200
committercashmere <cashmere@autistici.org>2026-08-11 01:07:16 +0200
commit819e0ab1cb942dc6b80a5141723bad04ee2dfa61 (patch)
tree58b6cb87d6f5e23c7db3252ccf1d0c84cfb529cb /lisp
parent822e857b51fc78d63e0f5b60aa6530d6c9d01b58 (diff)
downloademacs-819e0ab1cb942dc6b80a5141723bad04ee2dfa61.tar.gz
md: 2026-08-11T01:07:20+02:00
Diffstat (limited to 'lisp')
-rw-r--r--lisp/my-mode-line.el184
1 files changed, 184 insertions, 0 deletions
diff --git a/lisp/my-mode-line.el b/lisp/my-mode-line.el
new file mode 100644
index 0000000..b3179fa
--- /dev/null
+++ b/lisp/my-mode-line.el
@@ -0,0 +1,184 @@
+;;; my-mode-line.el --- Custom mode-line on top of mode-line-maker -*- lexical-binding: t; -*-
+
+(require 'mode-line-maker)
+
+(defface my/mode-line-evil-normal
+ '((t (:background "#89b4fa" :foreground "#1e1e2e" :weight bold)))
+ "Face for the evil normal state indicator.")
+
+(defface my/mode-line-evil-insert
+ '((t (:background "#a6e3a1" :foreground "#1e1e2e" :weight bold)))
+ "Face for the evil insert state indicator.")
+
+(defface my/mode-line-evil-visual
+ '((t (:background "#cba6f7" :foreground "#1e1e2e" :weight bold)))
+ "Face for the evil visual state indicator.")
+
+(defface my/mode-line-evil-replace
+ '((t (:background "#f38ba8" :foreground "#1e1e2e" :weight bold)))
+ "Face for the evil replace state indicator.")
+
+(defface my/mode-line-evil-operator
+ '((t (:background "#fab387" :foreground "#1e1e2e" :weight bold)))
+ "Face for the evil operator state indicator.")
+
+(defface my/mode-line-evil-motion
+ '((t (:background "#94e2d5" :foreground "#1e1e2e" :weight bold)))
+ "Face for the evil motion state indicator.")
+
+(defface my/mode-line-evil-emacs
+ '((t (:background "#b4befe" :foreground "#1e1e2e" :weight bold)))
+ "Face for the evil emacs state indicator.")
+
+(defface my/mode-line-irc-face
+ '((t (:foreground "#94e2d5" :weight bold)))
+ "Face for the IRC activity indicator.")
+
+(defface my/mode-line-mu4e-face
+ '((t (:foreground "#f9e2af" :weight bold)))
+ "Face for the unread mail indicator.")
+
+(defface my/mode-line-time-face
+ '((t (:foreground "#89dceb")))
+ "Face for the time segment.")
+
+(defface my/mode-line-state-saved-face
+ '((t (:foreground "#a6e3a1")))
+ "Face for the saved buffer state icon.")
+
+(defface my/mode-line-state-modified-face
+ '((t (:foreground "#fab387" :weight bold)))
+ "Face for the modified buffer state icon.")
+
+(defface my/mode-line-state-read-only-face
+ '((t (:foreground "#f38ba8")))
+ "Face for the read-only buffer state icon.")
+
+(defvar-local my/mode-line--buffer-title nil)
+
+(defun my/mode-line--update-title-cache ()
+ "Update the cached #+title for the current buffer."
+ (setq-local my/mode-line--buffer-title
+ (and (derived-mode-p 'org-mode)
+ (cadar (org-collect-keywords '("TITLE"))))))
+
+(defun my/mode-line--buffer-title ()
+ "Return the cached #+title, computing it if necessary."
+ (unless (local-variable-p 'my/mode-line--buffer-title)
+ (my/mode-line--update-title-cache))
+ my/mode-line--buffer-title)
+
+(defun my/mode-line--invalidate-title-cache ()
+ "Invalidate the cached title so it gets recomputed on next update."
+ (kill-local-variable 'my/mode-line--buffer-title))
+
+(defun my/mode-line-buffer-name ()
+ "Buffer name for the mode line.
+Uses the Org #+title if present, otherwise the file path relative to
+the project root, falling back to the plain buffer name."
+ (or (my/mode-line--buffer-title)
+ (if-let* ((file buffer-file-name)
+ (project (project-current))
+ (root (project-root project)))
+ (file-relative-name file root)
+ (buffer-name))))
+
+(defun my/mode-line--nerd-icons-p ()
+ (and (bound-and-true-p ek-use-nerd-fonts)
+ (require 'nerd-icons nil t)))
+
+(defun my/mode-line-buffer-icon ()
+ "Nerd icon for the current buffer, or nil."
+ (when (my/mode-line--nerd-icons-p)
+ (or (and buffer-file-name
+ (ignore-errors (nerd-icons-icon-for-file buffer-file-name)))
+ (ignore-errors (nerd-icons-icon-for-mode major-mode)))))
+
+(defun my/mode-line-evil-state ()
+ "Colored single-letter indicator for the current evil state."
+ (when (and (boundp 'evil-state) evil-state)
+ (let* ((tag (upcase (substring (symbol-name evil-state) 0 1)))
+ (face (intern-soft (format "my/mode-line-evil-%s" evil-state))))
+ (propertize (format " %s " tag) 'face (or face 'my/mode-line-evil-normal)))))
+
+(defun my/mode-line-buffer-state ()
+ "Buffer state icon: lock (read-only), pencil-alert (modified), save (saved)."
+ (cond
+ (buffer-read-only
+ (if (my/mode-line--nerd-icons-p)
+ (propertize (nerd-icons-mdicon "nf-md-lock")
+ 'face 'my/mode-line-state-read-only-face)
+ "%%"))
+ ((buffer-modified-p)
+ (if (my/mode-line--nerd-icons-p)
+ (propertize (nerd-icons-mdicon "nf-md-content_save_alert")
+ 'face 'my/mode-line-state-modified-face)
+ "**"))
+ (t
+ (if (my/mode-line--nerd-icons-p)
+ (propertize (nerd-icons-mdicon "nf-md-content_save")
+ 'face 'my/mode-line-state-saved-face)
+ "--"))))
+
+(defun my/mode-line-irc ()
+ "IRC activity indicator: icon plus number of buffers with unread activity."
+ (when (bound-and-true-p erc-modified-channels-alist)
+ (let ((icon (and (my/mode-line--nerd-icons-p)
+ (nerd-icons-mdicon "nf-md-message_badge_outline"))))
+ (propertize (format "%s %d" (or icon "irc")
+ (length erc-modified-channels-alist))
+ 'face 'my/mode-line-irc-face))))
+
+(defun my/mode-line-mu4e ()
+ "Unread mail indicator from mu4e-alert, or nil when mu4e is not in use."
+ (when (bound-and-true-p mu4e-alert-mode-line)
+ (let ((icon (and (my/mode-line--nerd-icons-p)
+ (nerd-icons-mdicon "nf-md-email_alert_outline"))))
+ (propertize (format "%s %s" (or icon "mail")
+ (string-trim mu4e-alert-mode-line))
+ 'face 'my/mode-line-mu4e-face))))
+
+(defvar my/mode-line--clock-icons
+ ["nf-md-clock_time_twelve" "nf-md-clock_time_one" "nf-md-clock_time_two"
+ "nf-md-clock_time_three" "nf-md-clock_time_four" "nf-md-clock_time_five"
+ "nf-md-clock_time_six" "nf-md-clock_time_seven" "nf-md-clock_time_eight"
+ "nf-md-clock_time_nine" "nf-md-clock_time_ten" "nf-md-clock_time_eleven"]
+ "Nerd icon clock faces indexed by hour modulo 12.")
+
+(defun my/mode-line-time ()
+ "Time with a live clock icon showing the current hour."
+ (when (bound-and-true-p display-time-string)
+ (let* ((hour (% (string-to-number (format-time-string "%I")) 12))
+ (icon (and (my/mode-line--nerd-icons-p)
+ (nerd-icons-mdicon (aref my/mode-line--clock-icons hour))))
+ (time (string-trim display-time-string)))
+ (propertize (if icon (concat icon " " time) time)
+ 'face 'my/mode-line-time-face))))
+
+(defun my/mode-line-install ()
+ "Install the custom mode-line built with mode-line-maker.
+The mode-line lives at the top of the window via `header-line-format';
+the bottom mode-line is hidden."
+ (setq global-mode-string (delq 'display-time-string global-mode-string))
+ (setq-default mode-line-format nil)
+ (setq-default header-line-format
+ (mode-line-maker
+ '((:eval (my/mode-line-evil-state)) " "
+ (:eval (my/mode-line-buffer-state)) mode-line-remote " "
+ (:eval (my/mode-line-buffer-icon)) " "
+ (:eval (propertize (my/mode-line-buffer-name)
+ 'face 'mode-line-buffer-id
+ 'mouse-face 'mode-line-highlight
+ 'help-echo (concat (or buffer-file-truename (buffer-name))
+ "\nmouse-1: Previous buffer\nmouse-3: Next buffer")
+ 'local-map mode-line-buffer-identification-keymap)) " "
+ mode-line-position)
+ '("" (:eval (my/mode-line-irc)) " "
+ (:eval (my/mode-line-mu4e)) " "
+ mode-line-process " "
+ (:eval (my/mode-line-time)) " "
+ mode-name " ")))
+ (add-hook 'before-save-hook #'my/mode-line--invalidate-title-cache))
+
+(provide 'my-mode-line)
+;;; my-mode-line.el ends here