summaryrefslogtreecommitdiff
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
parent822e857b51fc78d63e0f5b60aa6530d6c9d01b58 (diff)
downloademacs-819e0ab1cb942dc6b80a5141723bad04ee2dfa61.tar.gz
md: 2026-08-11T01:07:20+02:00
-rw-r--r--config.org160
-rw-r--r--lisp/my-mode-line.el184
2 files changed, 290 insertions, 54 deletions
diff --git a/config.org b/config.org
index bf2a3f9..bd3d5af 100644
--- a/config.org
+++ b/config.org
@@ -435,7 +435,7 @@ Selects the compilation window so the cursor lands in it."
(setq erc-fill-wrap-align-prompt nil)
(setq erc-fill-static-center 14)
- ;; track -- activity NOT in modeline (channels list clutters doom-modeline)
+ ;; track -- activity NOT in mode-line (channel list would clutter it)
(setq erc-track-position-in-mode-line nil)
;; keep large buffer for soju history replay
@@ -610,6 +610,15 @@ Temporarily disables notifications during the fetch."
pass-otp-uri-copy
pass-view-copy-password
pass-view-copy-token))
+ (advice-add cmd :after #'my/elfeed-yank-to-clipboard)))
+ (with-eval-after-load 'passage
+ (dolist (cmd '(passage-copy
+ passage-copy-field
+ passage-copy-username
+ passage-copy-url
+ passage-otp-token-copy
+ passage-otp-uri-copy
+ passage-view-copy-password))
(advice-add cmd :after #'my/elfeed-yank-to-clipboard))))
#+end_src
@@ -1083,6 +1092,96 @@ automatically encrypted before save and can be decrypted in-place with
(add-hook 'org-mode-hook (lambda () (setq-local auto-save-default nil))))
#+end_src
+** age
+
+#+begin_src emacs-lisp
+(use-package age
+ :ensure t
+ :demand t
+ :custom
+ (age-program "rage")
+ (age-default-identity "~/.config/passage/identities")
+ (age-default-recipient
+ '("age188928acysrh9zpc3rk6q4lqmf3qlpjx3s26m6k3p6kah63gcws7sx3jeej"
+ "age1mhvdwh6cvd70hm5dcptc4xca2xnkf383g6706ful6kmrudwdysvq0eytym"))
+ :config
+ (setenv "PINENTRY_PROGRAM" "pinentry-gnome3")
+ (setenv "PASSAGE_DIR" (expand-file-name "~/pass"))
+ (age-file-enable))
+#+end_src
+
+#+begin_src emacs-lisp
+(defun my/org-crypt-migrate-to-age (title)
+ "Migrate all :crypt: headings in the current buffer to a new .org.age file.
+TITLE names the new denote-style file in ~/org."
+ (interactive "sTitle for new age file: ")
+ (unless (derived-mode-p 'org-mode)
+ (user-error "Not an org buffer"))
+ (save-excursion
+ (org-decrypt-entries)
+ (let ((collected (generate-new-buffer " *crypt-migration*"))
+ (count 0))
+ (org-map-entries
+ (lambda ()
+ (let ((beg (org-entry-beginning-position))
+ (end (org-entry-end-position)))
+ (with-current-buffer collected
+ (insert (buffer-substring-no-properties beg end) "\n"))
+ (setq count (1+ count))))
+ "crypt")
+ (if (zerop count)
+ (progn (kill-buffer collected)
+ (user-error "No :crypt: headings found"))
+ (let ((target (expand-file-name
+ (format "%s--%s__crypt.org.age"
+ (format-time-string "%Y%m%dT%H%M%S")
+ (replace-regexp-in-string "[^a-z0-9]+" "-" (downcase title)))
+ "~/org")))
+ (with-current-buffer collected
+ (org-mode)
+ (write-file target)
+ (save-buffer))
+ (goto-char (point-max))
+ (org-map-entries
+ (lambda ()
+ (delete-region (org-entry-beginning-position) (org-entry-end-position)))
+ "crypt")
+ (save-buffer)
+ (message "Migrated %d crypt headings to %s" count target))))))
+#+end_src
+
+** passage
+
+#+begin_src emacs-lisp :tangle yes
+(use-package passage
+ :ensure (:host github :repo "anticomputer/passage.el")
+ :after age
+ :custom
+ (auth-source-passage-filename "~/pass")
+ :config
+ (with-eval-after-load 'evil
+ (evil-define-key 'normal passage-mode-map
+ "y" #'passage-copy
+ "yn" #'passage-copy-username
+ "yu" #'passage-copy-url
+ "yf" #'passage-copy-field
+ "J" #'passage-goto-entry
+ "U" #'passage-browse-url
+ "a" #'passage-insert
+ "G" #'passage-insert-generated
+ "R" #'passage-rename
+ "x" #'passage-kill
+ "E" #'passage-edit
+ "j" #'passage-next-entry
+ "k" #'passage-prev-entry
+ "gr" #'passage-update-buffer
+ "o" #'passage-otp-options
+ "]]" #'passage-next-directory
+ "[[" #'passage-prev-directory
+ (kbd "RET") #'passage-view
+ "q" #'passage-quit)))
+#+end_src
+
** org-appear
#+begin_src emacs-lisp
@@ -3471,61 +3570,14 @@ Native =undo-redo= (via =evil-undo-system='undo-redo=); just raise the undo limi
(display-time-mode 1)
#+end_src
-** doom-modeline
+** mode-line-maker
#+begin_src emacs-lisp
-(defvar-local my/doom-modeline--buffer-title nil)
-
-(defun my/doom-modeline--update-title-cache ()
- "Update the cached #+title for the current buffer."
- (setq-local my/doom-modeline--buffer-title
- ;; `org-collect-keywords' runs the org element parser, which
- ;; errors (rx range error) outside org-mode buffers.
- (and (derived-mode-p 'org-mode)
- (cadar (org-collect-keywords '("TITLE"))))))
-
-(defun my/doom-modeline--buffer-title ()
- "Return the cached #+title, computing it if necessary."
- (unless (local-variable-p 'my/doom-modeline--buffer-title)
- (my/doom-modeline--update-title-cache))
- my/doom-modeline--buffer-title)
-
-(defun my/doom-modeline-set-buffer-title (&rest _)
- "Override doom-modeline's cached file name with the Org #+title.
-If no TITLE keyword is found, leave doom-modeline's default name."
- (when-let* ((title (my/doom-modeline--buffer-title)))
- (setq doom-modeline--buffer-file-name
- (propertize title
- 'face 'doom-modeline-buffer-file
- '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))))
-
-(defun my/doom-modeline--invalidate-title-cache ()
- "Invalidate the cached title so it gets recomputed on next update."
- (kill-local-variable 'my/doom-modeline--buffer-title))
-
-(use-package doom-modeline
- :ensure t
- :init (doom-modeline-mode 1)
+(use-package mode-line-maker
+ :ensure (:host github :repo "rougier/mode-line-maker")
:config
- (setq doom-modeline-hud t
- doom-modeline-irc t
- doom-modeline-irc-buffers nil
- doom-modeline-mu4e t
- doom-modeline-buffer-file-name-style 'relative-to-project
- doom-modeline-buffer-encoding nil
- doom-modeline-time t
- doom-modeline-time-live-icon t)
-
- ;; Replace the channel-list segment with the icon-only segment in ERC buffers.
- (doom-modeline-def-modeline 'special
- '(eldoc bar window-state window-number modals matches buffer-info remote-host buffer-position word-count parrot selection-info)
- '(compilation objed-state misc-info battery irc debug minor-modes input-method indent-info buffer-encoding major-mode process time))
-
- (advice-add #'doom-modeline-update-buffer-file-name :after #'my/doom-modeline-set-buffer-title)
- (add-hook 'before-save-hook #'my/doom-modeline--invalidate-title-cache))
+ (require 'my-mode-line)
+ (my/mode-line-install))
#+end_src
* soph
@@ -5237,7 +5289,7 @@ opening another file in same project does not re-notify."
:commands (zfs))
#+end_src
-* org-tree-slide
+* COMMENT org-tree-slide
#+begin_src emacs-lisp
(use-package org-tree-slide
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