summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcashmere <cashmere@autistici.org>2026-08-24 18:46:04 +0200
committercashmere <cashmere@autistici.org>2026-08-24 18:46:04 +0200
commit353cf218845182b7e0a274977089103f4da98606 (patch)
treea2d6b6cbecfd7966763fd47f9834597765e39403
parent5aa60edd4386a4930d0c44b03a626f7d9263ac48 (diff)
downloademacs-353cf218845182b7e0a274977089103f4da98606.tar.gz
2026-08-24T18:46:06+02:00
-rw-r--r--config.el44
-rw-r--r--config.org46
-rw-r--r--lisp/my-header-line.el281
-rw-r--r--lisp/my-mode-line.el322
4 files changed, 363 insertions, 330 deletions
diff --git a/config.el b/config.el
index a533040..0691272 100644
--- a/config.el
+++ b/config.el
@@ -2766,11 +2766,34 @@ so the working-tree diff stays visible until the user explicitly stages."
(display-time-mode 1)
+(defvar my/mode-line--format
+ (or (default-value 'mode-line-format)
+ (eval (car (get 'mode-line-format 'standard-value)) t))
+ "The format restored when the bottom mode line is visible.")
+
+(define-minor-mode my/global-mode-line-mode
+ "Show the traditional bottom mode line in regular buffers."
+ :global t
+ :init-value t
+ :group 'mode-line
+ (setq-default mode-line-format
+ (and my/global-mode-line-mode my/mode-line--format))
+ (force-mode-line-update t))
+
+(defun my/hide-mode-line ()
+ "Toggle the traditional bottom mode line globally."
+ (interactive)
+ (my/global-mode-line-mode (if my/global-mode-line-mode -1 1)))
+
+;; The custom header line already carries the status information, so keep the
+;; traditional bottom mode line hidden on startup (including `emacs -nw').
+(my/global-mode-line-mode -1)
+
(use-package mode-line-maker
:ensure (:host github :repo "rougier/mode-line-maker")
:config
- (require 'my-mode-line)
- (my/mode-line-install))
+ (require 'my-header-line)
+ (my/header-line-install))
(use-package adaptive-wrap
:ensure t
@@ -2911,6 +2934,16 @@ so the working-tree diff stays visible until the user explicitly stages."
:after textui
:commands (fossil-ui fossil-ui-status))
+(use-package bbs-ui
+ :ensure nil
+ :after textui
+ :commands (bbs bbs-open)
+ :custom
+ (bbs-ui-repository-file
+ (expand-file-name "bbs-sandbox.fossil" "/home/cashmere/bbs/"))
+ (bbs-ui-web-base-url "https://bbs.copland.systems")
+ (bbs-ui-pull-on-open nil))
+
(use-package yggdrasil-ui
:ensure nil
:after textui
@@ -4135,7 +4168,7 @@ reset is unnecessary, so do the handler resolution ourselves and skip
:ensure t
:after mu4e
:custom
- (mu4e-alert-modeline-formatter #'my/mode-line-mu4e-formatter)
+ (mu4e-alert-modeline-formatter #'my/header-line-mu4e-formatter)
:config
;; Use libnotify for desktop notifications (notify-send / swaync)
(mu4e-alert-set-default-style 'libnotify)
@@ -4611,4 +4644,9 @@ opening another file in same project does not re-notify."
:files ("*.el"))
:commands (org-other-agenda))
+(use-package org-clock-reminder
+ :ensure (:host github :repo "inickey/org-clock-reminder")
+ :config
+ (org-clock-reminder-mode))
+
(provide 'init)
diff --git a/config.org b/config.org
index 06e3c8f..7c53507 100644
--- a/config.org
+++ b/config.org
@@ -3341,21 +3341,48 @@ Native =undo-redo= (via =evil-undo-system='undo-redo=); just raise the undo limi
(dirvish-directory-view-mode . diredfl-mode)))
#+end_src
-* modelines
+* header and mode lines
** display-time
#+begin_src emacs-lisp
(display-time-mode 1)
#+end_src
-** mode-line-maker
+** mode-line
+
+#+begin_src emacs-lisp
+(defvar my/mode-line--format
+ (or (default-value 'mode-line-format)
+ (eval (car (get 'mode-line-format 'standard-value)) t))
+ "The format restored when the bottom mode line is visible.")
+
+(define-minor-mode my/global-mode-line-mode
+ "Show the traditional bottom mode line in regular buffers."
+ :global t
+ :init-value t
+ :group 'mode-line
+ (setq-default mode-line-format
+ (and my/global-mode-line-mode my/mode-line--format))
+ (force-mode-line-update t))
+
+(defun my/hide-mode-line ()
+ "Toggle the traditional bottom mode line globally."
+ (interactive)
+ (my/global-mode-line-mode (if my/global-mode-line-mode -1 1)))
+
+;; The custom header line already carries the status information, so keep the
+;; traditional bottom mode line hidden on startup (including `emacs -nw').
+(my/global-mode-line-mode -1)
+#+end_src
+
+** header-line
#+begin_src emacs-lisp
(use-package mode-line-maker
:ensure (:host github :repo "rougier/mode-line-maker")
:config
- (require 'my-mode-line)
- (my/mode-line-install))
+ (require 'my-header-line)
+ (my/header-line-install))
#+end_src
* soph
@@ -4982,7 +5009,7 @@ reset is unnecessary, so do the handler resolution ourselves and skip
:ensure t
:after mu4e
:custom
- (mu4e-alert-modeline-formatter #'my/mode-line-mu4e-formatter)
+ (mu4e-alert-modeline-formatter #'my/header-line-mu4e-formatter)
:config
;; Use libnotify for desktop notifications (notify-send / swaync)
(mu4e-alert-set-default-style 'libnotify)
@@ -5538,6 +5565,15 @@ opening another file in same project does not re-notify."
:commands (org-other-agenda))
#+end_src
+* org-clock-reminder
+
+#+begin_src emacs-lisp
+(use-package org-clock-reminder
+ :ensure (:host github :repo "inickey/org-clock-reminder")
+ :config
+ (org-clock-reminder-mode))
+#+end_src
+
* provide init
#+begin_src emacs-lisp
diff --git a/lisp/my-header-line.el b/lisp/my-header-line.el
new file mode 100644
index 0000000..0b0bc75
--- /dev/null
+++ b/lisp/my-header-line.el
@@ -0,0 +1,281 @@
+;;; my-header-line.el --- Custom header line built with mode-line-maker -*- lexical-binding: t; -*-
+
+(require 'mode-line-maker)
+
+(defface my/header-line-evil-normal
+ '((t (:inherit font-lock-keyword-face :weight bold)))
+ "Face for the evil normal state indicator.")
+
+(defface my/header-line-evil-insert
+ '((t (:inherit success :weight bold)))
+ "Face for the evil insert state indicator.")
+
+(defface my/header-line-evil-visual
+ '((t (:inherit warning :weight bold)))
+ "Face for the evil visual state indicator.")
+
+(defface my/header-line-evil-replace
+ '((t (:inherit error :weight bold)))
+ "Face for the evil replace state indicator.")
+
+(defface my/header-line-evil-motion
+ '((t (:inherit font-lock-constant-face :weight bold)))
+ "Face for the evil motion state indicator.")
+
+(defface my/header-line-evil-emacs
+ '((t (:inherit font-lock-builtin-face :weight bold)))
+ "Face for the evil Emacs state indicator.")
+
+(defface my/header-line-buffer-icon-face
+ '((t (:inherit mode-line-buffer-id)))
+ "Face for the current buffer's file or mode icon.")
+
+(defface my/header-line-irc-face
+ '((t (:inherit font-lock-builtin-face :weight bold)))
+ "Face for the IRC activity indicator.")
+
+(defface my/header-line-clatter-face
+ '((t (:inherit font-lock-builtin-face :weight bold)))
+ "Face for the Clatter joined-channel indicator.")
+
+(defface my/header-line-mu4e-face
+ '((t (:inherit warning :weight bold)))
+ "Face for the unread mail indicator.")
+
+(defface my/header-line-time-face
+ '((t (:inherit font-lock-constant-face)))
+ "Face for the time segment.")
+
+(defface my/header-line-state-saved-face
+ '((t (:inherit success)))
+ "Face for the saved buffer state icon.")
+
+(defface my/header-line-state-modified-face
+ '((t (:inherit warning :weight bold)))
+ "Face for the modified buffer state icon.")
+
+(defface my/header-line-state-read-only-face
+ '((t (:inherit error)))
+ "Face for the read-only buffer state icon.")
+
+(defvar-local my/header-line--buffer-title nil)
+
+(defconst my/header-line--missing (make-symbol "missing")
+ "Sentinel used when checking membership in package hash tables.")
+
+(defvar my/header-line--format nil
+ "The installed global header-line format.")
+
+(defun my/header-line--update-title-cache ()
+ "Update the cached #+title for the current buffer."
+ (setq-local my/header-line--buffer-title
+ (and (derived-mode-p 'org-mode)
+ (cadar (org-collect-keywords '("TITLE"))))))
+
+(defun my/header-line--buffer-title ()
+ "Return the cached #+title, computing it if necessary."
+ (unless (local-variable-p 'my/header-line--buffer-title)
+ (my/header-line--update-title-cache))
+ my/header-line--buffer-title)
+
+(defun my/header-line--invalidate-title-cache ()
+ "Invalidate the cached title so it gets recomputed on next update."
+ (kill-local-variable 'my/header-line--buffer-title))
+
+(defun my/header-line-buffer-name ()
+ "Buffer name for the header 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/header-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/header-line--nerd-icons-p ()
+ "Return non-nil when Nerd Icons are enabled and available."
+ (and (bound-and-true-p ek-use-nerd-fonts)
+ (require 'nerd-icons nil t)))
+
+(defun my/header-line--icon (name face &optional fallback)
+ "Render Nerd icon NAME with FACE, or FALLBACK when unavailable."
+ (if (my/header-line--nerd-icons-p)
+ (propertize (nerd-icons-mdicon name) 'face face)
+ (propertize (or fallback "") 'face face)))
+
+(defun my/header-line-buffer-icon ()
+ "Nerd icon for the current buffer, or nil."
+ (when (my/header-line--nerd-icons-p)
+ (or (and buffer-file-name
+ (ignore-errors
+ (nerd-icons-icon-for-file
+ buffer-file-name :face 'my/header-line-buffer-icon-face)))
+ (ignore-errors
+ (nerd-icons-icon-for-mode
+ major-mode :face 'my/header-line-buffer-icon-face)))))
+
+(defconst my/header-line--evil-states
+ '((normal "N" my/header-line-evil-normal)
+ (insert "I" my/header-line-evil-insert)
+ (visual "V" my/header-line-evil-visual)
+ (replace "R" my/header-line-evil-replace)
+ (motion "M" my/header-line-evil-motion)
+ (emacs "E" my/header-line-evil-emacs))
+ "Display labels and theme-derived faces for Evil states.")
+
+(defun my/header-line-evil-state ()
+ "Compact, theme-aware indicator for the current Evil state."
+ (when (bound-and-true-p evil-state)
+ (let* ((entry (assq evil-state my/header-line--evil-states))
+ (tag (or (nth 1 entry)
+ (upcase (substring (symbol-name evil-state) 0 1))))
+ (face (or (nth 2 entry) 'my/header-line-evil-normal)))
+ (propertize (format " %s " tag) 'face face))))
+
+(defun my/header-line-buffer-state ()
+ "Render a clean lock, edit, or saved indicator for the current buffer."
+ (cond
+ (buffer-read-only
+ (my/header-line--icon "nf-md-lock"
+ 'my/header-line-state-read-only-face "RO"))
+ ((buffer-modified-p)
+ (my/header-line--icon "nf-md-pencil"
+ 'my/header-line-state-modified-face "*"))
+ (t
+ (my/header-line--icon "nf-md-check"
+ 'my/header-line-state-saved-face "="))))
+
+(defun my/header-line-remote ()
+ "Show a remote indicator only for an actual remote directory.
+Unlike the built-in `mode-line-remote', local buffers render nothing instead
+of a literal dash."
+ (when (file-remote-p default-directory)
+ (my/header-line--icon "nf-md-cloud_outline"
+ 'font-lock-constant-face "@")))
+
+(defun my/header-line-irc ()
+ "IRC activity indicator: icon plus number of buffers with unread activity."
+ (when (bound-and-true-p erc-modified-channels-alist)
+ (let ((icon (my/header-line--icon "nf-md-forum_outline"
+ 'my/header-line-irc-face "irc")))
+ (propertize (format "%s %d" icon
+ (length erc-modified-channels-alist))
+ 'face 'my/header-line-irc-face))))
+
+(defun my/header-line-clatter ()
+ "Clatter indicator showing the number of joined channels."
+ (when (featurep 'clatter)
+ (let ((count 0))
+ (dolist (buffer (clatter-all-buffers))
+ (when (with-current-buffer buffer
+ (and (eq clatter--buffer-type 'channel)
+ (hash-table-p clatter--nick-list)
+ (when-let* ((connection
+ (clatter-get-connection clatter--network))
+ ((eq (clatter-connection-state connection)
+ :connected))
+ (nick (clatter-connection-nick connection)))
+ (not (eq (gethash (downcase nick) clatter--nick-list
+ my/header-line--missing)
+ my/header-line--missing)))))
+ (setq count (1+ count))))
+ (when (> count 0)
+ (let ((icon (my/header-line--icon "nf-md-forum_outline"
+ 'my/header-line-clatter-face "irc")))
+ (propertize (format "%s %d" icon count)
+ 'face 'my/header-line-clatter-face))))))
+
+(defun my/header-line-mu4e-formatter (mail-count)
+ "Format MAIL-COUNT for the custom header line without an extra icon."
+ (unless (zerop mail-count)
+ (number-to-string mail-count)))
+
+(defun my/header-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 (my/header-line--icon "nf-md-email_outline"
+ 'my/header-line-mu4e-face "mail")))
+ (propertize (format "%s %s" icon
+ (string-trim mu4e-alert-mode-line))
+ 'face 'my/header-line-mu4e-face))))
+
+(defvar my/header-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/header-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/header-line--nerd-icons-p)
+ (nerd-icons-mdicon (aref my/header-line--clock-icons hour))))
+ (time (string-trim display-time-string)))
+ (propertize (if icon (concat icon " " time) time)
+ 'face 'my/header-line-time-face))))
+
+(defun my/header-line-sync-theme (&optional _theme)
+ "Make helper padding inherit the live header-line theme face.
+Optional THEME is ignored so this function also fits
+`enable-theme-functions'."
+ (set-face-attribute 'mode-line-maker-padding-face nil
+ :inherit 'header-line
+ :foreground 'unspecified
+ :background 'unspecified
+ :box nil
+ :overline nil
+ :underline nil
+ :inverse-video nil
+ :strike-through nil))
+
+(define-minor-mode my/global-header-line-mode
+ "Show the custom header line globally.
+
+When disabled, local informational headers, such as Elfeed's column
+headings, remain visible."
+ :global t
+ :init-value t
+ :group 'mode-line
+ (setq-default header-line-format
+ (and my/global-header-line-mode my/header-line--format))
+ (force-mode-line-update t))
+
+(defun my/toggle-header-line ()
+ "Toggle the custom header line globally."
+ (interactive)
+ (my/global-header-line-mode (if my/global-header-line-mode -1 1)))
+
+(defun my/header-line-install ()
+ "Install the custom header line built with mode-line-maker."
+ (setq global-mode-string (delq 'display-time-string global-mode-string))
+ (my/header-line-sync-theme)
+ (add-hook 'enable-theme-functions #'my/header-line-sync-theme)
+ (setq my/header-line--format
+ (mode-line-maker
+ '((:eval (my/header-line-evil-state)) " "
+ (:eval (my/header-line-buffer-state))
+ (:eval (my/header-line-remote)) " "
+ (:eval (my/header-line-buffer-icon)) " "
+ (:eval (propertize (my/header-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/header-line-irc)) " "
+ (:eval (my/header-line-clatter)) " "
+ (:eval (my/header-line-mu4e)) " "
+ mode-line-process " "
+ (:eval (my/header-line-time)) " "
+ mode-name " ")))
+ ;; The header line is visible on every startup, regardless of its previous
+ ;; interactive state in a long-running session.
+ (my/global-header-line-mode 1)
+ (add-hook 'before-save-hook #'my/header-line--invalidate-title-cache))
+
+(provide 'my-header-line)
+;;; my-header-line.el ends here
diff --git a/lisp/my-mode-line.el b/lisp/my-mode-line.el
deleted file mode 100644
index ac14cb1..0000000
--- a/lisp/my-mode-line.el
+++ /dev/null
@@ -1,322 +0,0 @@
-;;; 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 (:inherit font-lock-keyword-face :weight bold)))
- "Face for the evil normal state indicator.")
-
-(defface my/mode-line-evil-insert
- '((t (:inherit success :weight bold)))
- "Face for the evil insert state indicator.")
-
-(defface my/mode-line-evil-visual
- '((t (:inherit warning :weight bold)))
- "Face for the evil visual state indicator.")
-
-(defface my/mode-line-evil-replace
- '((t (:inherit error :weight bold)))
- "Face for the evil replace state indicator.")
-
-(defface my/mode-line-evil-motion
- '((t (:inherit font-lock-constant-face :weight bold)))
- "Face for the evil motion state indicator.")
-
-(defface my/mode-line-evil-emacs
- '((t (:inherit font-lock-builtin-face :weight bold)))
- "Face for the evil Emacs state indicator.")
-
-(defface my/mode-line-buffer-icon-face
- '((t (:inherit mode-line-buffer-id)))
- "Face for the current buffer's file or mode icon.")
-
-(defface my/mode-line-irc-face
- '((t (:inherit font-lock-builtin-face :weight bold)))
- "Face for the IRC activity indicator.")
-
-(defface my/mode-line-clatter-face
- '((t (:inherit font-lock-builtin-face :weight bold)))
- "Face for the Clatter joined-channel indicator.")
-
-(defface my/mode-line-mu4e-face
- '((t (:inherit warning :weight bold)))
- "Face for the unread mail indicator.")
-
-(defface my/mode-line-time-face
- '((t (:inherit font-lock-constant-face)))
- "Face for the time segment.")
-
-(defface my/mode-line-state-saved-face
- '((t (:inherit success)))
- "Face for the saved buffer state icon.")
-
-(defface my/mode-line-state-modified-face
- '((t (:inherit warning :weight bold)))
- "Face for the modified buffer state icon.")
-
-(defface my/mode-line-state-read-only-face
- '((t (:inherit error)))
- "Face for the read-only buffer state icon.")
-
-(defvar-local my/mode-line--buffer-title nil)
-
-(defconst my/mode-line--missing (make-symbol "missing")
- "Sentinel used when checking membership in package hash tables.")
-
-(defvar my/mode-line--format nil
- "The installed global header-line mode-line format.")
-
-(defvar-local my/mode-line--saved-mode-line-format nil
- "Clatter's mode-line format saved while the global mode line is hidden.")
-
-(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 ()
- "Return non-nil when Nerd Icons are enabled and available."
- (and (bound-and-true-p ek-use-nerd-fonts)
- (require 'nerd-icons nil t)))
-
-(defun my/mode-line--icon (name face &optional fallback)
- "Render Nerd icon NAME with FACE, or FALLBACK when unavailable."
- (if (my/mode-line--nerd-icons-p)
- (propertize (nerd-icons-mdicon name) 'face face)
- (propertize (or fallback "") 'face face)))
-
-(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 :face 'my/mode-line-buffer-icon-face)))
- (ignore-errors
- (nerd-icons-icon-for-mode
- major-mode :face 'my/mode-line-buffer-icon-face)))))
-
-(defconst my/mode-line--evil-states
- '((normal "N" my/mode-line-evil-normal)
- (insert "I" my/mode-line-evil-insert)
- (visual "V" my/mode-line-evil-visual)
- (replace "R" my/mode-line-evil-replace)
- (motion "M" my/mode-line-evil-motion)
- (emacs "E" my/mode-line-evil-emacs))
- "Display labels and theme-derived faces for Evil states.")
-
-(defun my/mode-line-evil-state ()
- "Compact, theme-aware indicator for the current Evil state."
- (when (bound-and-true-p evil-state)
- (let* ((entry (assq evil-state my/mode-line--evil-states))
- (tag (or (nth 1 entry)
- (upcase (substring (symbol-name evil-state) 0 1))))
- (face (or (nth 2 entry) 'my/mode-line-evil-normal)))
- (propertize (format " %s " tag) 'face face))))
-
-(defun my/mode-line-buffer-state ()
- "Render a clean lock, edit, or saved indicator for the current buffer."
- (cond
- (buffer-read-only
- (my/mode-line--icon "nf-md-lock"
- 'my/mode-line-state-read-only-face "RO"))
- ((buffer-modified-p)
- (my/mode-line--icon "nf-md-pencil"
- 'my/mode-line-state-modified-face "*"))
- (t
- (my/mode-line--icon "nf-md-check"
- 'my/mode-line-state-saved-face "="))))
-
-(defun my/mode-line-remote ()
- "Show a remote indicator only for an actual remote directory.
-Unlike the built-in `mode-line-remote', local buffers render nothing instead
-of a literal dash."
- (when (file-remote-p default-directory)
- (my/mode-line--icon "nf-md-cloud_outline"
- 'font-lock-constant-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 (my/mode-line--icon "nf-md-forum_outline"
- 'my/mode-line-irc-face "irc")))
- (propertize (format "%s %d" icon
- (length erc-modified-channels-alist))
- 'face 'my/mode-line-irc-face))))
-
-(defun my/mode-line-clatter ()
- "Clatter indicator showing the number of joined channels."
- (when (featurep 'clatter)
- (let ((count 0))
- (dolist (buffer (clatter-all-buffers))
- (when (with-current-buffer buffer
- (and (eq clatter--buffer-type 'channel)
- (hash-table-p clatter--nick-list)
- (when-let* ((connection
- (clatter-get-connection clatter--network))
- ((eq (clatter-connection-state connection)
- :connected))
- (nick (clatter-connection-nick connection)))
- (not (eq (gethash (downcase nick) clatter--nick-list
- my/mode-line--missing)
- my/mode-line--missing)))))
- (setq count (1+ count))))
- (when (> count 0)
- (let ((icon (my/mode-line--icon "nf-md-forum_outline"
- 'my/mode-line-clatter-face "irc")))
- (propertize (format "%s %d" icon count)
- 'face 'my/mode-line-clatter-face))))))
-
-(defun my/mode-line-mu4e-formatter (mail-count)
- "Format MAIL-COUNT for the custom mode line without an extra icon."
- (unless (zerop mail-count)
- (number-to-string mail-count)))
-
-(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 (my/mode-line--icon "nf-md-email_outline"
- 'my/mode-line-mu4e-face "mail")))
- (propertize (format "%s %s" icon
- (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-sync-theme (&optional _theme)
- "Make helper padding inherit the live header-line theme face.
-Optional THEME is ignored so this function also fits
-`enable-theme-functions'."
- (set-face-attribute 'mode-line-maker-padding-face nil
- :inherit 'header-line
- :foreground 'unspecified
- :background 'unspecified
- :box nil
- :overline nil
- :underline nil
- :inverse-video nil
- :strike-through nil))
-
-(defun my/mode-line--hide-clatter-buffers (&rest _)
- "Hide Clatter's local mode lines while the global mode line is disabled."
- (unless my/global-mode-line-mode
- (dolist (buffer (buffer-list))
- (with-current-buffer buffer
- (when (derived-mode-p 'clatter-mode)
- (unless (local-variable-p 'my/mode-line--saved-mode-line-format)
- (setq-local my/mode-line--saved-mode-line-format mode-line-format))
- (setq-local mode-line-format nil))))))
-
-(defun my/mode-line--restore-package-mode-lines ()
- "Restore package-local mode lines hidden by the global toggle."
- (dolist (buffer (buffer-list))
- (with-current-buffer buffer
- (when (local-variable-p 'my/mode-line--saved-mode-line-format)
- (setq-local mode-line-format my/mode-line--saved-mode-line-format)
- (kill-local-variable 'my/mode-line--saved-mode-line-format)))))
-
-(define-minor-mode my/global-mode-line-mode
- "Show the custom global mode line.
-
-When disabled, the header-based global mode line and Clatter's package-local
-bottom mode lines are hidden. Local informational headers, such as Elfeed's
-column headings, remain visible."
- :global t
- :init-value t
- :group 'mode-line
- (if my/global-mode-line-mode
- (progn
- (setq-default header-line-format my/mode-line--format)
- (my/mode-line--restore-package-mode-lines))
- (setq-default header-line-format nil)
- (my/mode-line--hide-clatter-buffers))
- (force-mode-line-update t))
-
-(defun my/toggle-mode-line ()
- "Toggle the custom mode line globally."
- (interactive)
- (my/global-mode-line-mode (if my/global-mode-line-mode -1 1)))
-
-(with-eval-after-load 'clatter-ui
- (unless (advice-member-p #'my/mode-line--hide-clatter-buffers
- 'clatter-ui-setup-buffer)
- (advice-add 'clatter-ui-setup-buffer :after
- #'my/mode-line--hide-clatter-buffers)))
-
-(with-eval-after-load 'clatter-track
- (unless (advice-member-p #'my/mode-line--hide-clatter-buffers
- 'clatter-track--refresh-mode-lines)
- (advice-add 'clatter-track--refresh-mode-lines :after
- #'my/mode-line--hide-clatter-buffers)))
-
-(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))
- (my/mode-line-sync-theme)
- (add-hook 'enable-theme-functions #'my/mode-line-sync-theme)
- (setq-default mode-line-format nil)
- (setq my/mode-line--format
- (mode-line-maker
- '((:eval (my/mode-line-evil-state)) " "
- (:eval (my/mode-line-buffer-state))
- (:eval (my/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-clatter)) " "
- (:eval (my/mode-line-mu4e)) " "
- mode-line-process " "
- (:eval (my/mode-line-time)) " "
- mode-name " ")))
- ;; The mode line is visible on every startup, regardless of its previous
- ;; interactive state in a long-running session.
- (my/global-mode-line-mode 1)
- (add-hook 'before-save-hook #'my/mode-line--invalidate-title-cache))
-
-(provide 'my-mode-line)
-;;; my-mode-line.el ends here