summaryrefslogtreecommitdiffhomepage
path: root/attic
diff options
context:
space:
mode:
Diffstat (limited to 'attic')
-rw-r--r--attic/elisp/book-mode.el760
-rw-r--r--attic/elisp/jao-ednc.el143
-rw-r--r--attic/elisp/jao-maildir.el4
-rw-r--r--attic/elisp/jao-multisession.el341
-rw-r--r--attic/elisp/jao-notmuch-gnus.el16
-rw-r--r--attic/elisp/jao-proton-utils.el141
-rw-r--r--attic/elisp/jao-spt.el148
-rw-r--r--attic/elisp/jao-vterm-repl.el130
-rw-r--r--attic/elisp/misc.el378
-rw-r--r--attic/elisp/nnnm.el16
10 files changed, 2052 insertions, 25 deletions
diff --git a/attic/elisp/book-mode.el b/attic/elisp/book-mode.el
new file mode 100644
index 0000000..eb3bfd0
--- /dev/null
+++ b/attic/elisp/book-mode.el
@@ -0,0 +1,760 @@
+;;; book-mode.el --- Book mode -*- lexical-binding: t -*-
+
+;; Copyright (C) 2022 Nicolas P. Rougier
+
+;; Maintainer: Nicolas P. Rougier <Nicolas.Rougier@inria.fr>
+;; URL: https://github.com/rougier/book-mode
+;; Version: 0.1.0
+;; Package-Requires: ((emacs "27.1") (nano-theme))
+;; Keywords: convenience, mode-line, header-line
+
+;; This file is not part of GNU Emacs.
+
+;; This file is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 3, or (at your option)
+;; any later version.
+
+;; This file is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; For a full copy of the GNU General Public License
+;; see <https://www.gnu.org/licenses/>.
+
+
+;;; Code:
+(require 'seq)
+(require 'nano-theme)
+;; (require 'nano-command)
+
+(defgroup book-mode nil
+ "Book mode"
+ :group 'convenience)
+
+(defcustom book-mode-left-margin 8
+ "Left margin size, measured in characters"
+ :type 'int
+ :group 'book-mode)
+
+(defcustom book-mode-right-margin 6
+ "Right margin size, measured in characters"
+ :type 'int
+ :group 'book-mode)
+
+(defcustom book-mode-top-margin 2.25
+ "Top margin size, measured in characters"
+ :type 'float
+ :group 'book-mode)
+
+(defcustom book-mode-top-padding 0.25
+ "Bottom margin size, measured in characters"
+ :type 'float
+ :group 'book-mode)
+
+(defcustom book-mode-bottom-margin 1.45
+ "Bottom margin size, measured in characters"
+ :type 'float
+ :group 'book-mode)
+
+(defcustom book-mode-bottom-padding 0.00
+ "Bottom margin size, measured in characters"
+ :type 'float
+ :group 'book-mode)
+
+(defcustom book-mode-frame-size '(81 . 51)
+ "Frame size"
+ :type '(cons (integer :tag "width")
+ (integer :tag "height"))
+ :group 'book-mode)
+
+(defcustom book-mode-frame-border-color nano-dark-background
+ "Frame border color"
+ :type 'color
+ :group 'book-mode)
+
+(defcustom book-mode-hl-line-extend t
+ "Whether to extend hl-line to margins"
+ :type 'boolean
+ :group 'book-mode)
+
+
+(defun book-mode--log (format-string &rest args)
+ "Log a message into the *Messages* buffer if message-log-max is
+non-nil. Return the message."
+
+ (with-current-buffer (get-buffer-create "*Messages*")
+ (let ((inhibit-read-only t)
+ (msg (apply 'format-message format-string args)))
+ (when (and msg message-log-max)
+ (goto-char (point-max))
+ (insert (concat "\n" msg)))
+ msg)))
+
+
+(defun book-mode--message (&rest args)
+ "Message function advice that override the message function and
+saves the message in a buffer local variable."
+
+ ;; Log message
+ (when (car args)
+ (apply #'book-mode--log args))
+
+ ;; Save message in buffer local book-mode--last-message variable
+ ;; and register a timer to clear it after a delay.
+ (let* ((msg (if (and (car args) (stringp (car args)))
+ (apply 'format-message args)))
+ (msg (if (stringp msg)
+ (replace-regexp-in-string "%" "%%" msg))))
+ (setq book-mode--last-message (or msg ""))
+ (when (and (boundp 'book-mode--message-timer) book-mode--message-timer)
+ (cancel-timer book-mode--message-timer))
+ (unless isearch-mode
+ (setq book-mode--message-timer
+ ;; (run-at-time minibuffer-message-timeout nil
+ ;; #'book-mode--message-clear))
+ (run-at-time 2.0 nil #'book-mode--message-clear)))
+ (force-mode-line-update)))
+
+
+(defun book-mode--message-cancel-clear ()
+ (when (and (boundp 'book-mode--message-timer) book-mode--message-timer)
+ (cancel-timer book-mode--message-timer)))
+
+
+(defun book-mode--message-clear ()
+ "Clear last message"
+
+ (setq book-mode--last-message nil)
+ (force-mode-line-update))
+
+
+(defun book-mode--command-error-function (data context caller)
+ "This command-error function intercepts some message from the C API."
+
+ (if (not (memq (car data) '(buffer-read-only
+ text-read-only
+ beginning-of-buffer
+ end-of-buffer
+ quit)))
+ (command-error-default-function data context caller)
+ (book-mode--message (format "%s" data))))
+
+
+(defun book-mode--header (&rest args)
+ ""
+ (apply #'book-mode--build
+ (nconc `(:overline nil
+ :underline ,(face-foreground 'default)
+ :margin ,(or (plist-get args ':margin)
+ (- book-mode-top-margin))
+ :padding ,(or (plist-get args ':padding)
+ (- book-mode-top-padding)))
+ args)))
+
+
+(defun book-mode--footer (&rest args)
+ ""
+ (apply #'book-mode--build
+ (nconc `(:overline ,(face-foreground 'default)
+ :underline nil
+ :margin ,(or (plist-get args ':margin)
+ book-mode-bottom-margin)
+ :padding ,(or (plist-get args ':padding)
+ book-mode-bottom-padding))
+ args)))
+
+
+;; ----------------------------------------------------------------------------
+(defun book-mode--build (&rest args)
+ ""
+
+ (let* ((overline (or (plist-get args ':overline) nil))
+ (underline (or (plist-get args ':underline) nil))
+ (left (or (plist-get args ':left) #'book-mode-element-empty))
+ (right (or (plist-get args ':right) #'book-mode-element-empty))
+ (center (or (plist-get args ':center) #'book-mode-element-empty))
+ (prefix (or (plist-get args ':prefix) #'book-mode-element-empty))
+ (suffix (or (plist-get args ':suffix) #'book-mode-element-empty))
+ (margin (or (plist-get args ':margin) 0.0))
+ (padding (or (plist-get args ':padding) 0.0)))
+
+ `(:eval
+ (let* ((left (if (stringp (quote ,left))
+ ,left
+ (funcall (quote ,left))))
+ (right (if (stringp (quote ,right))
+ ,right
+ (funcall (quote ,right))))
+ (prefix (if (stringp (quote ,prefix))
+ ,prefix
+ (funcall (quote ,prefix))))
+ (suffix (if (stringp (quote ,suffix))
+ ,suffix
+ (funcall (quote ,suffix))))
+ (overline ,overline)
+ (underline ,underline)
+ (width (window-width))
+ (margin ,margin)
+ (padding ,padding)
+ (left-margin (or (car (window-margins)) 0))
+ (right-margin (or (cdr (window-margins)) 0))
+ (left (truncate-string-to-width left (- width (length right) 1) nil nil "…"))
+ (prefix-filler (make-string (max 0 (- left-margin (length prefix))) 32))
+ (suffix-filler (make-string (max 0 (- right-margin (length suffix))) 32)))
+
+ (add-face-text-property 0 (length left)
+ `(:underline ,underline :overline ,overline) nil left)
+ (add-face-text-property 0 (length right)
+ `(:underline ,underline :overline ,overline) nil right)
+
+ (concat
+ (propertize prefix-filler 'display `(raise ,(+ margin padding)))
+ (propertize prefix 'display `((raise ,margin)
+ ,(get-text-property 0 'display prefix)))
+ (propertize left 'display `((raise ,margin)
+ ,(get-text-property 0 'display left)))
+ (propertize " " 'display `((raise ,(+ margin))
+ (space :align-to (- right ,(length right) 0)))
+ 'face `(:box nil :underline ,underline :overline ,overline))
+ (propertize right 'display `((raise ,margin)
+ ,(get-text-property 0 'display right)))
+ (propertize suffix 'display `((raise ,margin)
+ ,(get-text-property 0 'display suffix)))
+
+ (propertize suffix-filler 'display `(raise ,(+ margin padding))))))))
+
+
+(defun book-mode-element-frame-count ()
+ (let* ((frames (seq-filter (lambda (frame)
+ (not (frame-parent frame)))
+ (frame-list)))
+ (index (length (member (selected-frame) frames))))
+ (propertize (format " %d "index)
+ 'face `(:inherit (nano-subtle nano-strong)
+ :foreground ,(face-foreground 'nano-default)))))
+
+(defun book-mode-element-frame-count-icon (&optional icon)
+ "Prefix element displaying frame count and an icon."
+
+ (concat
+ (book-mode-element-frame-count)
+ " "
+ (cond (icon
+ (propertize (format "%s " icon) 'face 'nano-default))
+ ((or (derived-mode-p 'elfeed-show-mode)
+ (derived-mode-p 'elfeed-search-mode))
+ (propertize " " 'face 'nano-default))
+ ((derived-mode-p 'org-agenda-mode)
+ (propertize " " 'face 'nano-default))
+ ((derived-mode-p 'mastodon-mode)
+ (propertize " " 'face 'nano-default))
+ ((derived-mode-p 'mu4e-headers-mode)
+ (propertize " " 'face 'nano-default))
+ ((derived-mode-p 'mu4e-view-mode)
+ (propertize " " 'face 'nano-default))
+ (view-mode
+ (propertize " " 'face 'nano-faded))
+ ((derived-mode-p 'helpful-mode)
+ (propertize " " 'face 'nano-default))
+ (buffer-read-only
+ (propertize " " 'face 'nano-faded))
+ ((buffer-modified-p)
+ (propertize " " 'face 'nano-popout))
+ (t
+ (propertize " " 'face 'nano-subtle-i)))))
+
+(defun book-mode-element-prefix-elfeed ()
+ (book-mode-element-frame-count-icon ""))
+
+(defun book-mode-element-prefix-agenda ()
+ (book-mode-element-frame-count-icon ""))
+
+(defun book-mode-element-prefix-mastodon ()
+ (book-mode-element-frame-count-icon ""))
+
+(defun book-mode-element-prefix-mu4e-headers ()
+ (book-mode-element-frame-count-icon ""))
+
+(defun book-mode-element-prefix-mu4e-view ()
+ (book-mode-element-frame-count-icon ""))
+
+(defun book-mode-element-prefix-helpful ()
+ (book-mode-element-frame-count-icon ""))
+
+(defun book-mode-element-prefix-view ()
+ (book-mode-element-frame-count-icon ""))
+
+(defun book-mode-element-prefix-generic ()
+ (book-mode-element-frame-count-icon
+ (cond (buffer-read-only
+ (propertize "" 'face 'nano-faded))
+ ((buffer-modified-p)
+ (propertize "" 'face 'nano-popout))
+ (t
+ (propertize "" 'face 'nano-subtle-i)))))
+
+(defun book-mode-element-elfeed-feed-name ()
+ (plist-get (elfeed-feed-meta
+ (elfeed-entry-feed elfeed-show-entry)) :title))
+
+(defun book-mode-element-elfeed-search-filter ()
+ elfeed-search-filter)
+
+(defun book-mode-element-agenda-name ()
+ (save-excursion
+ (goto-char (point-min))
+ (buffer-substring-no-properties (line-beginning-position))))
+
+(defun book-mode-element-mu4e-context ()
+ (if (> (length (mu4e-context-label)) 0)
+ (substring-no-properties (mu4e-context-label) 1 -1)
+ ""))
+
+(defun book-mode-element-mu4e-search-query ()
+ (mu4e-last-query))
+
+(defun book-mode-element-mu4e-message-subject ()
+ "")
+
+(defun book-mode-element-mu4e-message-sender ()
+ "")
+
+(defun book-mode-element-mu4e-message-date ()
+ "")
+
+(defun book-mode-element-buffer-name ()
+ (buffer-name))
+
+(defun book-mode-element-name ()
+
+ (let ((name (cond ;; Elfeed show mode
+ ((derived-mode-p 'elfeed-show-mode)
+ (plist-get (elfeed-feed-meta
+ (elfeed-entry-feed elfeed-show-entry)) :title))
+ ;; Elfeed show mode
+ ((derived-mode-p 'elfeed-search-mode)
+ (concat "" elfeed-search-filter))
+
+ ;; Org agenda mode
+ ((derived-mode-p 'org-agenda-mode)
+ (save-excursion
+ (let ((inhibit-read-only t))
+ (goto-char (point-min))
+ ;; (set-text-properties (line-beginning-position)
+ ;; (+ (line-end-position) 1)
+ ;; '(invisible t))
+ (buffer-substring-no-properties (line-beginning-position)
+ (- (line-end-position) 1)))))
+
+ ;; Mu4e headers mode
+ ((derived-mode-p 'mu4e-headers-mode)
+ (concat "" (mu4e-last-query)))
+ ;; Mu4e view mode
+ ((derived-mode-p 'mu4e-view-mode)
+ "Message")
+ ;; Default
+ (t (buffer-name)))))
+ (propertize name 'face '(:inherit nano-strong))))
+
+
+(defun book-mode-element-word-count ()
+
+ (let* ((beg (if (use-region-p) (region-beginning) (point-min)))
+ (end (if (use-region-p) (region-end) (point-max)))
+ (word-count (count-words beg end))
+ (char-count (- end beg)))
+ (propertize (format "%d words / %d chars" word-count char-count)
+ 'face 'nano-faded)))
+
+(defun book-mode-element-word-target ()
+
+ (let* ((word-count (count-words (point-min) (point-max)))
+ (word-total 300)
+ (ratio (/ (float word-count) (float word-total))))
+ (propertize " " 'display (svg-lib-progress-pie ratio nil))))
+
+(defun book-mode-element-dedicated ()
+ (propertize " " 'face (if (window-dedicated-p)
+ '(:inherit nano-default)
+ '(:inherit nano-subtle-i))
+ 'display '(raise 0)))
+
+(defun book-mode-element-empty ()
+ "")
+
+(defun book-mode-element-message ()
+ (if (boundp 'book-mode--last-message)
+ (or book-mode--last-message "")
+ ""))
+
+(defun book-mode-element-line/total ()
+ (propertize (format "%s/%s"(format-mode-line "%l")
+ (save-excursion
+ (goto-char (point-max))
+ (format-mode-line "%l")))))
+
+(defun book-mode-element-mu4e-query ()
+ (mu4e-last-query))
+
+(defun book-mode-element-mu4e-context ()
+ (substring-no-properties (mu4e-context-label)))
+
+(defun book-mode-element-mu4e-total ()
+ (format "%d" (plist-get mu4e--server-props :doccount)))
+
+
+;; ----------------------------------------------------------------------------
+(defun overlay-extend-to-margin (overlay &optional face left right)
+ (let* ((face (or face 'nano-popout-i))
+ (left-width (or (car (window-margins)) 0))
+ (right-width (or (cdr (window-margins)) 0))
+ (left (or left (make-string left-width ?\ )))
+ (right (or right (make-string right-width ?\ )))
+ (line-prefix (get-text-property (line-beginning-position) 'display))
+ (line-prefix (if (and (seqp line-prefix)
+ (equal (car line-prefix) '(margin left-margin)))
+ (cadr line-prefix)))
+ (left (if (stringp line-prefix) line-prefix left)))
+ (overlay-put overlay 'line-prefix
+ (concat
+ (propertize " "
+ 'display `((margin right-margin)
+ ,(propertize right 'face face)))
+ (propertize " "
+ 'display `((margin left-margin)
+ ,(propertize left 'face face)))))
+
+ (overlay-put overlay 'wrap-prefix
+ (concat
+ (propertize " "
+ 'display `((margin right-margin)
+ ,(propertize right 'face face)))
+ (propertize " "
+ 'display `((margin left-margin)
+ ,(propertize left 'face face)))))))
+
+;; (defun region-activate ()
+;; (when (boundp 'region-overlay)
+;; (overlay-extend-to-margin region-overlay 'region)
+;; (region-update)
+;; (add-hook #'post-command-hook #'region-update)))
+
+;; (defun region-deactivate ()
+;; (remove-hook #'post-command-hook #'region-update)
+;; (when (boundp 'region-overlay)
+;; (move-overlay region-overlay (point-min) (point-min))))
+
+;; (defun region-update ()
+;; (when (and (boundp 'region-overlay) (use-region-p))
+;; (move-overlay region-overlay (region-beginning) (region-end))))
+
+;; (add-hook 'activate-mark-hook #'region-activate)
+;; (add-hook 'deactivate-mark-hook #'region-deactivate)
+;; (remove-hook 'activate-mark-hook #'region-activate)
+;; (remove-hook 'deactivate-mark-hook #'region-deactivate)
+
+
+(defun book-mode-hl-line-range-function ()
+ (overlay-extend-to-margin
+ (or hl-line-overlay global-hl-line-overlay)
+ (if (derived-mode-p 'mu4e-headers-mode)
+ 'mu4e-header-highlight-face
+ 'hl-line))
+ (cons (line-beginning-position) (line-beginning-position 2)))
+
+
+;; ----------------------------------------------------------------------------
+;; (defun book-mode-old (&optional global)
+
+;; (interactive)
+;; (let ((left-margin 8)
+;; (right-margin 6)
+;; (top-margin 2.25)
+;; (top-padding 0.25)
+;; (bottom-margin 1.45)
+;; (bottom-padding 0.00))
+;; (if global
+;; (setq-default left-margin-width left-margin
+;; right-margin-width right-margin))
+;; (set-window-margins (selected-window) left-margin right-margin)
+;; (setq left-margin-width left-margin)
+;; (setq right-margin-width right-margin)
+
+;; (set-frame-parameter (selected-frame) 'internal-border-width 1)
+;; (set-frame-parameter (selected-frame) 'width (+ 81
+;; left-margin
+;; right-margin))
+;; (set-frame-parameter (selected-frame) 'height 50)
+;; (set-face-background 'internal-border nano-dark-background
+;; (selected-frame))
+
+;; (setq-local book-mode--message-timer nil)
+;; (setq-local book-mode--message-last nil)
+
+;; (setq line-spacing 1)
+;; (fringe-mode '(0 . 0))
+;; (set (make-local-variable 'region-overlay)
+;; (make-overlay (point-min) (point-min)))
+
+;; (if global
+;; (progn
+;; (set-face-attribute 'header-line nil
+;; :background (face-background 'default))
+;; (set-face-attribute 'mode-line nil
+;; :foreground (face-foreground 'default)
+;; :background (face-background 'default)
+;; :height (face-attribute 'default :height))
+;; (set-face-attribute 'mode-line-inactive nil
+;; :foreground (face-foreground 'default)
+;; :background (face-background 'default)
+;; :height (face-attribute 'default :height))
+;; (set-face-attribute 'region nil
+;; :background "#e0e0ff")
+;; (set-face-attribute 'hl-line nil
+;; :background "#f5f5ff"))
+;; (progn
+;; (face-remap-add-relative 'header-line
+;; :background (face-background 'default))
+;; (face-remap-add-relative 'mode-line
+;; :foreground (face-foreground 'default)
+;; :background (face-background 'default)
+;; :height (face-attribute 'default :height))
+;; (face-remap-add-relative 'mode-line-inactive
+;; :foreground (face-foreground 'default)
+;; :background (face-background 'default)
+;; :height (face-attribute 'default :height))
+;; (face-remap-add-relative 'region :background "#e0e0ff")
+;; (face-remap-add-relative 'hl-line :background "#f5f5ff")))
+
+;; (setq-local header-line-format
+;; (book-mode--header :prefix #'book-mode-element-prefix
+;; :left #'book-mode-element-name
+;; :suffix #'book-mode-element-dedicated
+;; :margin (- top-margin)
+;; :padding (- top-padding))
+;; mode-line-format
+;; (book-mode--footer :left #'book-mode-element-message
+;; :right #'book-mode-element-line/total
+;; :margin bottom-margin
+;; :padding bottom-padding))
+;; (when global
+;; (setq-default header-line-format header-line-format)
+;; (setq-default mode-line-format mode-line-format))
+
+;; (when (derived-mode-p 'org-mode)
+;; (add-to-list 'font-lock-extra-managed-props 'display)
+;; (let ((margin-format (format "%%%ds" left-margin)))
+;; (font-lock-add-keywords nil
+;; `(
+;; ("^\\(\\- \\)\\(.*\\)$"
+;; 1 '(face nano-default display ((margin left-margin)
+;; ,(propertize (format margin-format "• ")
+;; 'face '(:inherit nano-default :weight light)) append)))
+
+;; ("^\\(\\*\\{1\\} \\)\\(.*\\)$"
+;; 1 '(face nano-faded display ((margin left-margin)
+;; ,(propertize (format margin-format "# ")
+;; 'face '(:inherit nano-faded :weight light)) append))
+;; 2 '(face bold append))
+
+;; ("^\\(\\*\\{2\\} \\)\\(.*\\)$"
+;; 1 '(face nano-faded display ((margin left-margin)
+;; ,(propertize (format margin-format "## ")
+;; 'face '(:inherit nano-faded :weight light)) append))
+;; 2 '(face bold append))
+
+;; ("^\\(\\*\\{3\\} \\)\\(.*\\)$"
+;; 1 '(face nano-faded display ((margin left-margin)
+;; ,(propertize (format margin-format "### ")
+;; 'face '(:inherit nano-faded :weight light)) append))
+;; 2 '(face bold append))
+
+;; ("^\\*\\{4\\} .*?\\(\n\\)"
+;; 1 '(face nil display " - "))
+
+;; ("^\\(\\*\\{4\\} \\)\\(.*?\\)$"
+;; 1 '(face nano-faded display ((margin left-margin)
+;; ,(propertize (format margin-format "§ ")
+;; 'face '(:inherit nano-faded :weight light)) append))
+;; 2 '(face bold append))))))
+;; )
+
+;; (book-mode--message-clear)
+;; (advice-add 'message :override #'book-mode--message)
+;; (when (derived-mode-p 'org-mode)
+;; (font-lock-fontify-buffer)
+;; (visual-line-mode))
+;; (set (make-local-variable 'region-overlay)
+;; (make-overlay (point-min) (point-min)))
+;; (setq hl-line-range-function #'book-mode-hl-line-range-function)
+;; )
+
+
+;;;###autoload
+(defun book-mode ()
+
+ (interactive)
+ (setq linum-format
+ (format (format "%%%ds" (- book-mode-left-margin 2)) "%4d"))
+ (set-window-margins (selected-window) book-mode-left-margin
+ book-mode-right-margin)
+ (setq left-margin-width book-mode-left-margin)
+ (setq right-margin-width book-mode-right-margin)
+
+ (set-frame-parameter (selected-frame) 'internal-border-width 1)
+ (set-frame-parameter (selected-frame) 'width (+ (car book-mode-frame-size)
+ book-mode-left-margin
+ book-mode-right-margin))
+ (set-frame-parameter (selected-frame) 'height (cdr book-mode-frame-size))
+ (set-face-background 'internal-border book-mode-frame-border-color
+ (selected-frame))
+ (setq-local book-mode--message-timer nil)
+ (make-local-variable 'book-mode--message-timer)
+
+ (setq-local book-mode--message-last nil)
+ (make-local-variable 'book-mode--last-message)
+
+ (setq line-spacing 1)
+ (set-frame-parameter (selected-frame) 'right-divider-width 1)
+
+ (fringe-mode '(0 . 0))
+ (set (make-local-variable 'region-overlay)
+ (make-overlay (point-min) (point-min)))
+ (face-remap-add-relative 'header-line
+ :background (face-background 'default))
+ (face-remap-add-relative 'window-divider
+ :foreground (face-foreground 'default))
+
+ (face-remap-set-base 'mode-line nil)
+ (face-remap-set-base 'mode-line-inactive nil)
+
+ (face-remap-add-relative 'mode-line
+ :foreground (face-foreground 'default)
+ :background (face-background 'default)
+ :height (face-attribute 'default :height))
+ (face-remap-add-relative 'mode-line-inactive
+ :foreground (face-foreground 'default)
+ :background (face-background 'default)
+ :height (face-attribute 'default :height))
+ ;; (face-remap-add-relative 'region :background "#e0e0ff")
+ ;; (face-remap-add-relative 'hl-line :background "#f5f5ff")
+ (setq-local header-line-format
+ (book-mode--header :prefix #'book-mode-element-frame-count-icon
+ :left #'book-mode-element-name
+ :suffix #'book-mode-element-dedicated))
+ (setq-local mode-line-format
+ (book-mode--footer :left #'book-mode-element-message
+ :right #'book-mode-element-line/total))
+
+ (add-hook 'isearch-mode-hook
+ (lambda ()
+ (setq-local mode-line-format
+ (book-mode--footer
+ :prefix " "
+ :left #'book-mode-element-message
+ :right #'book-mode-element-line/total))
+ (book-mode--message-cancel-clear)))
+
+ (add-hook 'isearch-mode-end-hook
+ (lambda ()
+ (setq-local mode-line-format
+ (book-mode--footer
+ :left #'book-mode-element-message
+ :right #'book-mode-element-line/total))
+ (book-mode--message-clear)))
+
+ (book-mode--message-clear)
+ (advice-add 'message :override #'book-mode--message)
+ (set (make-local-variable 'region-overlay)
+ (make-overlay (point-min) (point-min)))
+ (setq hl-line-range-function #'book-mode-hl-line-range-function))
+
+(defun my/hide-cursor ()
+ (setq cursor-type nil))
+
+(defun my/hide-org-agenda-header-line ()
+ (save-excursion
+ (let ((inhibit-read-only t))
+ (goto-char (point-min))
+ (set-text-properties (line-beginning-position)
+ (+ (line-end-position) 1)
+ '(invisible t)))))
+
+(add-hook 'elfeed-search-mode-hook 'book-mode)
+(add-hook 'elfeed-show-mode-hook 'book-mode)
+(add-hook 'org-agenda-mode-hook 'book-mode)
+(add-hook 'org-agenda-finalize-hook 'my/hide-org-agenda-header-line)
+(add-hook 'mu4e-headers-mode-hook 'book-mode)
+(add-hook 'mu4e-headers-mode-hook 'my/hide-cursor)
+(add-hook 'mu4e-loading-mode-hook 'book-mode)
+(add-hook 'mu4e-view-mode-hook 'book-mode)
+(add-hook 'mu4e-compose-mode-hook 'book-mode)
+(add-hook 'help-mode-hook 'book-mode)
+(add-hook 'helpful-mode-hook 'book-mode)
+(add-hook 'mastodon-mode-hook 'book-mode)
+(add-hook 'python-mode-hook 'book-mode)
+(add-hook 'emacs-lisp-mode-hook 'book-mode)
+
+(provide 'book-mode)
+;;; book-mode.el ends here
+
+
+;; (defun book-command--update (prompt text)
+;; (let* ((prompt (string-trim (substring-no-properties prompt)))
+;; (prompt (propertize prompt 'face 'nano-strong )))
+;; (setq-local book-mode--last-message
+;; (concat prompt " " text))))
+
+;; (defun book-command (prompt &optional hook icon)
+;; (advice-add 'nano-command--update :override #'book-command--update)
+;; (setq-local mode-line-format
+;; (book-mode--footer :prefix (or icon "")
+;; :left #'book-mode-element-message
+;; :right #'book-mode-element-line/total
+;; :margin 0.0
+;; :padding 0.0))
+;; (let ((command (nano-command prompt hook
+;; (propertize "\n" 'face '(:height 1.2)))))
+;; (setq-local mode-line-format
+;; (book-mode--footer :left #'book-mode-element-message
+;; :right #'book-mode-element-line/total))
+;; (advice-remove 'nano-command--update #'book-command--update)
+;; (setq-local book-mode--last-message "")
+;; (force-mode-line-update)
+;; command))
+
+;; (bind-key "H-&" #'(lambda ()
+;; (interactive)
+;; (let ((command (book-command "SHELL:" nil " ")))
+;; (when command
+;; (async-shell-command command)))))
+
+
+;; (defun org-quick-meeting-hook ()
+;; (insert (format-time-string (concat " "
+;; (cdr org-time-stamp-formats))))
+;; (goto-char (point-min))
+;; (org-mode))
+
+;; (defun org-quick-meeting-refile (file headline)
+;; (let ((pos (save-excursion
+;; (find-file file)
+;; (org-find-exact-headline-in-buffer headline))))
+;; (org-refile nil nil (list headline file nil pos))))
+
+;; (defun org-quick-meeting ()
+;; "This function allows to register a meeting (org)"
+
+;; (interactive)
+;; (let ((capture (book-command "MEETING " #'org-quick-meeting-hook " ")))
+;; (when capture
+;; (let ((current-buffer (current-buffer)))
+;; (with-temp-buffer
+;; (insert (format "** %s\n" capture))
+;; (goto-char (point-min))
+;; (org-quick-meeting-refile "~/Documents/org/agenda.org" "Future"))
+;; (switch-to-buffer current-buffer)))))
+
+;; (bind-key "H-m" #'org-quick-meeting)
diff --git a/attic/elisp/jao-ednc.el b/attic/elisp/jao-ednc.el
new file mode 100644
index 0000000..92ee21f
--- /dev/null
+++ b/attic/elisp/jao-ednc.el
@@ -0,0 +1,143 @@
+;;; jao-ednc.el --- Minibuffer notifications using EDNC -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2020, 2021, 2024 jao
+
+;; Author: jao <mail@jao.io>
+;; Keywords: tools, abbrev
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Use the ednc package to provide a notification daemon that uses
+;; the minibuffer to display them.
+
+;;; Code:
+
+(require 'ednc)
+(require 'jao-minibuffer)
+
+(declare-function tracking-add-buffer "tracking")
+(declare-function tracking-remove-buffer "tracking")
+
+(defvar jao-ednc--count-format " {%d} ")
+(defvar jao-ednc--notifications ())
+(defvar jao-ednc--handlers ())
+
+(defvar jao-ednc-use-tracking nil)
+
+(defface jao-ednc-tracking '((t :inherit warning))
+ "Tracking notifications face"
+ :group 'jao-ednc)
+
+(defun jao-ednc--last-notification () (car jao-ednc--notifications))
+
+(defun jao-ednc--format-last ()
+ (when (jao-ednc--last-notification)
+ (let ((s (ednc-format-notification (jao-ednc--last-notification) t)))
+ (replace-regexp-in-string "\n" " " (substring-no-properties s)))))
+
+(defun jao-ednc--count ()
+ (let ((no (length jao-ednc--notifications)))
+ (if (> no 0)
+ (propertize (format jao-ednc--count-format no) 'face 'warning)
+ "")))
+
+(defun jao-ednc-add-handler (app handler)
+ (add-to-list 'jao-ednc--handlers (cons app handler)))
+
+(defun jao-ednc-ignore-app (app)
+ (jao-ednc-add-handler app
+ (lambda (not _)
+ (ignore-errors (ednc-dismiss-notification not)))))
+
+(defun jao-ednc--clean (&optional notification)
+ (tracking-remove-buffer (get-buffer ednc-log-name))
+ (if notification
+ (remove notification jao-ednc--notifications)
+ (pop jao-ednc--notifications))
+ (jao-minibuffer-refresh))
+
+(defun jao-ednc--show-last ()
+ (message (jao-ednc--format-last)))
+
+(defun jao-ednc--default-handler (notification newp)
+ (if (not newp)
+ (jao-ednc--clean notification)
+ (when jao-ednc-use-tracking
+ (tracking-add-buffer (get-buffer ednc-log-name) '(jao-ednc-tracking)))
+ (push notification jao-ednc--notifications)
+ (jao-ednc--show-last)))
+
+(defun jao-ednc--handler (notification)
+ (alist-get (ednc-notification-app-name notification)
+ jao-ednc--handlers
+ #'jao-ednc--default-handler
+ nil
+ 'string=))
+
+(defun jao-ednc--on-notify (old new)
+ (when old (funcall (jao-ednc--handler old) old nil))
+ (when new (funcall (jao-ednc--handler new) new t)))
+
+(defun jao-ednc-setup (minibuffer-order)
+ (setq jao-notify-use-messages t)
+ (with-eval-after-load "tracking"
+ (when jao-ednc-use-tracking
+ (add-to-list 'tracking-faces-priorities 'jao-ednc-tracking)
+ (when (listp tracking-shorten-modes)
+ (add-to-list 'tracking-shorten-modes 'ednc-view-mode))))
+ (when minibuffer-order
+ (jao-minibuffer-add-variable '(jao-ednc--count) minibuffer-order))
+ (add-hook 'ednc-notification-presentation-functions #'jao-ednc--on-notify)
+ (ednc-mode))
+
+(defun jao-ednc-pop ()
+ (interactive)
+ (pop-to-buffer-same-window ednc-log-name))
+
+(defun jao-ednc-show ()
+ (interactive)
+ (if (not (jao-ednc--last-notification))
+ (jao-ednc-pop)
+ (jao-ednc--show-last)))
+
+(defun jao-ednc-invoke-last-action ()
+ (interactive)
+ (if (jao-ednc--last-notification)
+ (ednc-invoke-action (jao-ednc--last-notification))
+ (message "No active notifications"))
+ (jao-ednc--clean))
+
+(defun jao-ednc-dismiss ()
+ (interactive)
+ (when (jao-ednc--last-notification)
+ (ignore-errors
+ (with-current-buffer ednc-log-name
+ (ednc-dismiss-notification (jao-ednc--last-notification)))))
+ (jao-ednc--clean))
+
+(defun jao-ednc-dismiss-and-show ()
+ (interactive)
+ (let ((m (jao-ednc--format-last)))
+ (jao-ednc-dismiss)
+ (when m (message m))))
+
+(defun jao-ednc-dismiss-all ()
+ (interactive)
+ (while (jao-ednc--last-notification)
+ (jao-ednc-dismiss)))
+
+(provide 'jao-ednc)
+;;; jao-ednc.el ends here
diff --git a/attic/elisp/jao-maildir.el b/attic/elisp/jao-maildir.el
index 18a1725..3078a95 100644
--- a/attic/elisp/jao-maildir.el
+++ b/attic/elisp/jao-maildir.el
@@ -90,8 +90,8 @@
0))
(defun jao-maildir--update-track-string (mbox)
- (when-let ((track (seq-find (lambda (td) (string-match-p (car td) mbox))
- jao-maildir--trackers)))
+ (when-let* ((track (seq-find (lambda (td) (string-match-p (car td) mbox))
+ jao-maildir--trackers)))
(let* ((label (cadr track))
(other (assoc-delete-all label jao-maildir--track-strings))
(cnt (jao-maildir--tracked-count track)))
diff --git a/attic/elisp/jao-multisession.el b/attic/elisp/jao-multisession.el
new file mode 100644
index 0000000..7c06183
--- /dev/null
+++ b/attic/elisp/jao-multisession.el
@@ -0,0 +1,341 @@
+;;; multisession.el --- Multisession storage for variables -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2021-2026 Free Software Foundation, Inc.
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This library provides multisession variables for Emacs Lisp, to
+;; make them persist between sessions.
+;;
+;; Use `define-multisession-variable' to define a multisession
+;; variable, and `multisession-value' to read its value. Use
+;; `list-multisession-values' to list multisession variables.
+;;
+;; Users might want to customize `multisession-storage' and
+;; `multisession-directory'.
+;;
+;; See Info node `(elisp) Multisession Variables' for more
+;; information.
+
+;;; Code:
+
+(require 'cl-lib)
+(require 'eieio)
+(require 'tabulated-list)
+
+(defcustom multisession-storage 'files
+ "Storage method for multisession variables.
+Valid methods are `sqlite' and `files'."
+ :type '(choice (const :tag "SQLite" sqlite)
+ (const :tag "Files" files))
+ :version "29.1"
+ :group 'files)
+
+(defcustom multisession-directory (expand-file-name "multisession/"
+ user-emacs-directory)
+ "Directory to store multisession variables."
+ :type 'file
+ :version "29.1"
+ :group 'files)
+
+;;;###autoload
+(defmacro define-multisession-variable (name initial-value &optional doc
+ &rest args)
+ "Make NAME into a multisession variable initialized from INITIAL-VALUE.
+DOC should be a doc string, and ARGS are keywords as applicable to
+`make-multisession'."
+ (declare (indent defun))
+ (unless (plist-get args :package)
+ (setq args (nconc (list :package
+ (replace-regexp-in-string "-.*" ""
+ (symbol-name name)))
+ args)))
+ `(defvar ,name
+ (make-multisession :key ,(symbol-name name)
+ :initial-value ,initial-value
+ ,@args)
+ ,@(list doc)))
+
+(defconst multisession--unbound (make-symbol "unbound"))
+
+(cl-defstruct (multisession
+ (:constructor nil)
+ (:constructor multisession--create)
+ (:conc-name multisession--))
+ "A persistent variable that will live across Emacs invocations."
+ key
+ (initial-value nil)
+ package
+ (storage multisession-storage)
+ (synchronized nil)
+ (cached-value multisession--unbound)
+ (cached-sequence 0))
+
+(cl-defun make-multisession (&key key initial-value package synchronized
+ storage)
+ "Create a multisession object."
+ (unless package
+ (error "No package for the multisession object"))
+ (unless key
+ (error "No key for the multisession object"))
+ (unless (stringp package)
+ (error "The package has to be a string"))
+ (unless (stringp key)
+ (error "The key has to be a string"))
+ (multisession--create
+ :key key
+ :synchronized synchronized
+ :initial-value initial-value
+ :package package
+ :storage (or storage multisession-storage)))
+
+(defun multisession-value (object)
+ "Return the value of the multisession OBJECT."
+ (if (null user-init-file)
+ ;; If we don't have storage, then just return the value from the
+ ;; object.
+ (if (eq (multisession--cached-value object) multisession--unbound)
+ (multisession--initial-value object)
+ (multisession--cached-value object))
+ ;; We have storage, so we update from storage.
+ (multisession-backend-value (multisession--storage object) object)))
+
+(defun multisession--set-value (object value)
+ "Set the stored value of OBJECT to VALUE."
+ (if (null user-init-file)
+ ;; We have no backend, so just store the value.
+ (setf (multisession--cached-value object) value)
+ ;; We have a backend.
+ (multisession--backend-set-value (multisession--storage object)
+ object value)))
+
+(defun multisession-delete (object)
+ "Delete OBJECT from the backend storage."
+ (multisession--backend-delete (multisession--storage object) object))
+
+(gv-define-simple-setter multisession-value multisession--set-value)
+
+;; Files Backend
+
+(defun multisession--encode-file-name (name)
+ (url-hexify-string name))
+
+(defun multisession--read-file-value (file object)
+ (catch 'done
+ (let ((i 0)
+ last-error)
+ (while (< i 10)
+ (condition-case err
+ (throw 'done
+ (with-temp-buffer
+ (let* ((time (file-attribute-modification-time
+ (file-attributes file)))
+ (coding-system-for-read 'utf-8-emacs-unix))
+ (insert-file-contents file)
+ (let ((stored (read (current-buffer))))
+ (setf (multisession--cached-value object) stored
+ (multisession--cached-sequence object) time)
+ stored))))
+ ;; Windows uses OS-level file locking that may preclude
+ ;; reading the file in some circumstances. In addition,
+ ;; rename-file is not an atomic operation on MS-Windows,
+ ;; when the target file already exists, so there could be a
+ ;; small race window when the file to read doesn't yet
+ ;; exist. So when these problems happen, wait a bit and retry.
+ ((permission-denied file-missing)
+ (setq i (1+ i)
+ last-error err)
+ (sleep-for (+ 0.1 (/ (float (random 10)) 10))))))
+ (signal (car last-error) (cdr last-error)))))
+
+(defun multisession--object-file-name (object)
+ (expand-file-name
+ (concat "files/"
+ (multisession--encode-file-name (multisession--package object))
+ "/"
+ (multisession--encode-file-name (multisession--key object))
+ ".value")
+ multisession-directory))
+
+(cl-defmethod multisession-backend-value ((_type (eql 'files)) object)
+ (let ((file (multisession--object-file-name object)))
+ (cond
+ ;; We have no value yet; see whether it's stored.
+ ((eq (multisession--cached-value object) multisession--unbound)
+ (if (file-exists-p file)
+ (multisession--read-file-value file object)
+ ;; Nope; return the initial value.
+ (multisession--initial-value object)))
+ ;; We have a value, but we want to update in case some other
+ ;; Emacs instance has updated.
+ ((multisession--synchronized object)
+ (if (and (file-exists-p file)
+ (time-less-p (multisession--cached-sequence object)
+ (file-attribute-modification-time
+ (file-attributes file))))
+ (multisession--read-file-value file object)
+ ;; Nothing, return the cached value.
+ (multisession--cached-value object)))
+ ;; Just return the cached value.
+ (t
+ (multisession--cached-value object)))))
+
+(cl-defmethod multisession--backend-set-value ((_type (eql 'files))
+ object value)
+ (let ((file (multisession--object-file-name object))
+ (time (current-time)))
+ ;; Ensure that the directory exists.
+ (let ((dir (file-name-directory file)))
+ (unless (file-exists-p dir)
+ (make-directory dir t)))
+ (with-temp-buffer
+ (let ((print-length nil)
+ (print-circle t)
+ (print-level nil))
+ (prin1 value (current-buffer)))
+ (goto-char (point-min))
+ (condition-case nil
+ (read (current-buffer))
+ (error (error "Unable to store unreadable value: %s" (buffer-string))))
+ ;; Write to a temp file in the same directory and rename to the
+ ;; file for somewhat better atomicity.
+ (let ((coding-system-for-write 'utf-8-emacs-unix)
+ (create-lockfiles nil)
+ (temp (make-temp-name file))
+ (write-region-inhibit-fsync nil))
+ (write-region (point-min) (point-max) temp nil 'silent)
+ (set-file-times temp time)
+ (rename-file temp file t)))
+ (setf (multisession--cached-sequence object) time
+ (multisession--cached-value object) value)))
+
+(cl-defmethod multisession--backend-values ((_type (eql 'files)))
+ (mapcar (lambda (file)
+ (let ((bits (file-name-split file)))
+ (list (url-unhex-string (car (last bits 2)))
+ (url-unhex-string
+ (file-name-sans-extension (car (last bits))))
+ (with-temp-buffer
+ (let ((coding-system-for-read 'utf-8-emacs-unix))
+ (insert-file-contents file)
+ (read (current-buffer)))))))
+ (directory-files-recursively
+ (expand-file-name "files" multisession-directory)
+ "\\.value\\'")))
+
+(cl-defmethod multisession--backend-delete ((_type (eql 'files)) object)
+ (let ((file (multisession--object-file-name object)))
+ (when (file-exists-p file)
+ (delete-file file))))
+
+;; Mode for editing.
+
+(defvar-keymap multisession-edit-mode-map
+ :parent tabulated-list-mode-map
+ "d" #'multisession-delete-value
+ "e" #'multisession-edit-value)
+
+(define-derived-mode multisession-edit-mode special-mode "Multisession"
+ "This mode lists all elements in the \"multisession\" database."
+ :interactive nil
+ (buffer-disable-undo)
+ (setq-local buffer-read-only t
+ truncate-lines t)
+ (setq tabulated-list-format
+ [("Package" 10)
+ ("Key" 30)
+ ("Value" 30)])
+ (setq-local revert-buffer-function #'multisession-edit-mode--revert))
+
+;;;###autoload
+(defun list-multisession-values (&optional choose-storage)
+ "List all values in the \"multisession\" database.
+If CHOOSE-STORAGE (interactively, the prefix), query for the
+storage method to list."
+ (interactive "P")
+ (let ((storage
+ (if choose-storage
+ (intern (completing-read "Storage method: " '(sqlite files) nil t))
+ multisession-storage)))
+ (pop-to-buffer (get-buffer-create (format "*Multisession %s*" storage)))
+ (multisession-edit-mode)
+ (setq-local multisession-storage storage)
+ (multisession-edit-mode--revert)
+ (goto-char (point-min))))
+
+(defun multisession-edit-mode--revert (&rest _)
+ (let ((inhibit-read-only t)
+ (id (get-text-property (point) 'tabulated-list-id)))
+ (erase-buffer)
+ (tabulated-list-init-header)
+ (setq tabulated-list-entries
+ (mapcar (lambda (elem)
+ (list
+ (cons (car elem) (cadr elem))
+ (vector (car elem) (cadr elem)
+ (string-replace "\n" "\\n"
+ (format "%s" (caddr elem))))))
+ (multisession--backend-values multisession-storage)))
+ (tabulated-list-print t)
+ (goto-char (point-min))
+ (when id
+ (when-let* ((match
+ (text-property-search-forward 'tabulated-list-id id t)))
+ (goto-char (prop-match-beginning match))))))
+
+(defun multisession-delete-value (id)
+ "Delete the value at point."
+ (interactive (list (get-text-property (point) 'tabulated-list-id))
+ multisession-edit-mode)
+ (unless id
+ (error "No value on the current line"))
+ (unless (yes-or-no-p "Really delete this item? ")
+ (user-error "Not deleting"))
+ (multisession--backend-delete multisession-storage
+ (make-multisession :package (car id)
+ :key (cdr id)))
+ (let ((inhibit-read-only t))
+ (beginning-of-line)
+ (delete-region (point) (progn (forward-line 1) (point)))))
+
+(defun multisession-edit-value (id)
+ "Edit the value at point."
+ (interactive (list (get-text-property (point) 'tabulated-list-id))
+ multisession-edit-mode)
+ (unless id
+ (error "No value on the current line"))
+ (let* ((object (or
+ ;; If the multisession variable already exists, use
+ ;; it (so that we update it).
+ (if-let* ((sym (intern-soft (cdr id))))
+ (and (boundp sym) (symbol-value sym))
+ nil)
+ ;; Create a new object.
+ (make-multisession
+ :package (car id)
+ :key (cdr id)
+ :storage multisession-storage)))
+ (value (multisession-value object)))
+ (setf (multisession-value object)
+ (car (read-from-string
+ (read-string "New value: " (prin1-to-string value))))))
+ (multisession-edit-mode--revert))
+
+(provide 'jao-multisession)
+
+;;; multisession.el ends here
diff --git a/attic/elisp/jao-notmuch-gnus.el b/attic/elisp/jao-notmuch-gnus.el
index 1576964..20defba 100644
--- a/attic/elisp/jao-notmuch-gnus.el
+++ b/attic/elisp/jao-notmuch-gnus.el
@@ -62,7 +62,7 @@
(defun jao-notmuch-gnus-show-tags ()
"Display in the echo area the tags of the current message."
(interactive)
- (when-let (id (jao-notmuch-gnus-message-id))
+ (when-let* ((id (jao-notmuch-gnus-message-id)))
(message "%s" (string-join (jao-notmuch-gnus-message-tags id) " "))))
(defun jao-notmuch-gnus-toggle-tags (tags &optional id current)
@@ -77,7 +77,7 @@
(defun jao-notmuch-gnus-tag-mark ()
"Remove the new tag for an article when it's marked as seen by Gnus."
- (when-let (id (jao-notmuch-gnus-message-id t))
+ (when-let* ((id (jao-notmuch-gnus-message-id t)))
(jao-notmuch-gnus-tag-message id '("-new") t)))
(add-hook 'gnus-mark-article-hook #'jao-notmuch-gnus-tag-mark)
@@ -189,12 +189,12 @@ Example:
(org-gnus-follow-link group id)))
(defun jao-notmuch-gnus-org-store ()
- (when-let (d (or (when (derived-mode-p 'notmuch-show-mode 'notmuch-tree-mode)
- (cons (notmuch-show-get-message-id)
- (notmuch-show-get-subject)))
- (when (derived-mode-p 'gnus-summary-mode 'gnus-article-mode)
- (cons (jao-notmuch-gnus-message-id)
- (gnus-summary-article-subject)))))
+ (when-let* ((d (or (when (derived-mode-p 'notmuch-show-mode 'notmuch-tree-mode)
+ (cons (notmuch-show-get-message-id)
+ (notmuch-show-get-subject)))
+ (when (derived-mode-p 'gnus-summary-mode 'gnus-article-mode)
+ (cons (jao-notmuch-gnus-message-id)
+ (gnus-summary-article-subject))))))
(org-link-store-props :type "mail"
:link (concat "mail:" (car d))
:description (concat "Mail: " (cdr d)))))
diff --git a/attic/elisp/jao-proton-utils.el b/attic/elisp/jao-proton-utils.el
new file mode 100644
index 0000000..a8f13eb
--- /dev/null
+++ b/attic/elisp/jao-proton-utils.el
@@ -0,0 +1,141 @@
+;; jao-proton-utils.el -- simple interaction with Proton mail and vpn -*- lexical-binding: t; -*-
+
+;; Copyright (c) 2018, 2019, 2020, 2023, 2026 Jose Antonio Ortega Ruiz
+
+;; This file is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 3, or (at your option)
+;; any later version.
+
+;; This file is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING. If not, write to
+;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;; Author: Jose Antonio Ortega Ruiz <mail@jao.io>
+;; Start date: Fri Dec 21, 2018 23:56
+
+;;; Comentary:
+
+;; This is a very simple comint-derived mode to run the CLI version
+;; of PM's Bridge within the comfort of emacs.
+
+;;; Code:
+
+(define-derived-mode proton-bridge-mode comint-mode "proton-bridge"
+ "A very simple comint-based mode to run ProtonMail's bridge"
+ (setq comint-prompt-read-only t)
+ (setq comint-prompt-regexp "^>>> "))
+
+;;;###autoload
+(defun run-proton-bridge ()
+ "Run or switch to an existing bridge process, using its CLI"
+ (interactive)
+ (pop-to-buffer (make-comint "proton-bridge" "protonmail-bridge" nil "-c"))
+ (unless (eq major-mode 'proton-bridge-mode)
+ (proton-bridge-mode)))
+
+;;;###autoload
+(defun proton-bridge-sendmail-setup ()
+ "Configure message sending for local proton bridge."
+ (setq send-mail-function #'smtpmail-send-it)
+ (setq message-send-mail-function #'smtpmail-send-it)
+ (setq smtpmail-servers-requiring-authorization
+ (regexp-opt '("localhost" "127.0.0.1")))
+ (setq smtpmail-auth-supported '(plain login))
+ (setq smtpmail-smtp-user "mail@jao.io")
+ (setq smtpmail-smtp-server "localhost")
+ (setq smtpmail-smtp-service 1025))
+
+(defvar jao-proton-vpn-font-lock-keywords '("\\[.+\\]"))
+
+(defvar proton-vpn-mode-map
+ (let ((map (make-keymap)))
+ (suppress-keymap map)
+ (define-key map [?q] 'bury-buffer)
+ (define-key map [?n] 'next-line)
+ (define-key map [?p] 'previous-line)
+ (define-key map [?g] 'proton-vpn-status)
+ (define-key map [?r] 'proton-vpn-reconnect)
+ (define-key map [?d] (lambda ()
+ (interactive)
+ (when (y-or-n-p "Disconnect?")
+ (proton-vpn-disconnect))))
+ (define-key map [?c] 'proton-vpn-connect)
+ map))
+
+
+;;;###autoload
+(defun proton-vpn-mode ()
+ "A very simple mode to show the output of ProtonVPN commands"
+ (interactive)
+ (kill-all-local-variables)
+ (buffer-disable-undo)
+ (use-local-map proton-vpn-mode-map)
+ (setq-local font-lock-defaults '(jao-proton-vpn-font-lock-keywords))
+ (setq-local truncate-lines t)
+ (setq-local next-line-add-newlines nil)
+ (setq major-mode 'proton-vpn-mode)
+ (setq mode-name "proton-vpn")
+ (read-only-mode 1))
+
+(defvar jao-proton-vpn--buffer "*pvpn*")
+
+(defun jao-proton-vpn--do (things)
+ (let ((b (pop-to-buffer (get-buffer-create jao-proton-vpn--buffer))))
+ (let ((inhibit-read-only t)
+ (cmd (format "protonvpn-cli %s" things)))
+ (delete-region (point-min) (point-max))
+ (message "Running: %s ...." cmd)
+ (shell-command cmd b)
+ (message ""))
+ (proton-vpn-mode)))
+
+;;;###autoload
+(defun proton-vpn-status ()
+ (interactive)
+ (jao-proton-vpn--do "s"))
+
+(defun proton-vpn--get-status ()
+ (or (when-let* ((b (get-buffer jao-proton-vpn--buffer)))
+ (with-current-buffer b
+ (goto-char (point-min))
+ (if (re-search-forward "^Status: *\\(.+\\)$" nil t)
+ (match-string-no-properties 1)
+ (when (re-search-forward "^Connected!$")
+ "Connected"))))
+ "Disconnected"))
+
+;;;###autoload
+(defun proton-vpn-connect (cc)
+ (interactive "P")
+ (let ((cc (when cc (read-string "Country code: "))))
+ (jao-proton-vpn--do (if cc (format "c --cc %s" cc) "c --sc"))
+ (proton-vpn-status)))
+
+(defun proton-vpn-reconnect ()
+ (interactive)
+ (jao-proton-vpn--do "r"))
+
+(setenv "PVPN_WAIT" "300")
+
+;;;###autoload
+(defun proton-vpn-maybe-reconnect ()
+ (interactive)
+ (when (string= "Connected" (proton-vpn--get-status))
+ (jao-proton-vpn--do "d")
+ (sit-for 5)
+ (jao-proton-vpn--do "r")))
+
+;;;###autoload
+(defun proton-vpn-disconnect ()
+ (interactive)
+ (jao-proton-vpn--do "d"))
+
+(provide 'jao-proton-utils)
+;;; jao-proton.el ends here
diff --git a/attic/elisp/jao-spt.el b/attic/elisp/jao-spt.el
new file mode 100644
index 0000000..ba5d104
--- /dev/null
+++ b/attic/elisp/jao-spt.el
@@ -0,0 +1,148 @@
+;;; jao-spt.el --- Access to the spotify-tui CLI -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2021, 2022, 2024 jao
+
+;; Author: jao <mail@jao.io>
+;; Keywords: multimedia
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Simple spotifyd controls via the spt executable.
+
+;;; Code:
+
+(require 'jao-minibuffer)
+(require 'jao-notify)
+
+(defvar jao-spt-bin "spt")
+(defvar jao-spt-format "'%s %t - %a [%r] %f'")
+(defvar jao-spt-device nil)
+
+(defun jao-spt--exec-async (&rest args)
+ (let ((display-buffer-alist `((".*spt commands.*" display-buffer-no-window)))
+ (buff (get-buffer-create "* spt commands *")))
+ (apply #'start-process "spt" buff jao-spt-bin args)))
+
+(defvar jao-spt--status-str "")
+
+(defun jao-spt--pb (&rest args)
+ (let* ((args (mapconcat #'identity args " "))
+ (dev (if jao-spt-device (format "-d '%s'" jao-spt-device) ""))
+ (cmd (format "%s pb %s -f %s %s" jao-spt-bin args jao-spt-format dev))
+ (st (string-trim (shell-command-to-string cmd))))
+ (setq jao-spt--status-str (when (string-prefix-p "▶" st) st))
+ (jao-minibuffer-refresh)
+ st))
+
+(defun jao-spt--pb* (&rest args)
+ (message "%s" (apply 'jao-spt--pb args)))
+
+;;;###autoload
+(defun jao-spt-play-uri (uri)
+ (jao-spt--exec-async "play" "--uri" uri))
+
+;;;###autoload
+(defun jao-spt-update-status ()
+ (interactive)
+ (jao-spt--pb))
+
+;;;###autoload
+(defun jao-spt-toggle ()
+ (interactive)
+ (jao-spt--pb* "-t"))
+
+;;;###autoload
+(defun jao-spt-next ()
+ (interactive)
+ (jao-spt--pb* "-n"))
+
+;;;###autoload
+(defun jao-spt-previous ()
+ (interactive)
+ (jao-spt--pb* "-p"))
+
+;;;###autoload
+(defun jao-spt-like ()
+ (interactive)
+ (jao-spt--pb* "--like"))
+
+;;;###autoload
+(defun jao-spt-dislike ()
+ (interactive)
+ (jao-spt--pb* "--dislike"))
+
+;;;###autoload
+(defun jao-spt-toggle-shuffle ()
+ (interactive)
+ (jao-spt--pb* "--shuffle"))
+
+;;;###autoload
+(defun jao-spt-seek (&optional secs)
+ (interactive "p")
+ (let ((secs (if (zerop (or secs 0)) 10 secs)))
+ (jao-spt--pb* "--seek" (format "%d" secs))))
+
+;;;###autoload
+(defun jao-spt-seek-back (&optional secs)
+ (interactive "p")
+ (jao-spt-seek (- secs)))
+
+(defun jao-spt--get-vol (delta)
+ (let* ((jao-spt-format "%v")
+ (v (string-to-number (jao-spt--pb))))
+ (number-to-string (max 0 (+ delta v)))))
+
+;;;###autoload
+(defun jao-spt-vol (&optional n)
+ (interactive "p")
+ (let ((n (or n 10)))
+ (jao-spt--pb* "--volume" (jao-spt--get-vol (if (zerop n) 10 n)))))
+
+;;;###autoload
+(defun jao-spt-vol-down (&optional n)
+ (interactive "p")
+ (jao-spt-vol (- (or n 10))))
+
+;;;###autoload
+(defun jao-spt-echo-current ()
+ (interactive)
+ (let ((jao-notify-use-messages t))
+ (jao-notify (jao-spt-update-status))))
+
+;;;###autoload
+(defun jao-spt-toggle-shuffle ()
+ (interactive)
+ (jao-spt--pb* "--shuffle"))
+
+;;;###autoload
+(defun jao-spt-set-up ()
+ (jao-minibuffer-add-msg-variable 'jao-spt--status-str))
+
+(defun jao-spt-lyrics-info ()
+ (let* ((jao-spt-format "%a~~~%t")
+ (s (jao-spt--pb))
+ (at (split-string s "~~~")))
+ (cons (car at) (cadr at))))
+
+(declare jao-show-lyrics "jao-lyrics")
+
+;;;###autoload
+(defun jao-spt-show-lyrics (force)
+ (interactive "P")
+ (jao-show-lyrics force #'jao-spt-lyrics-info))
+
+(provide 'jao-spt)
+;;; jao-spt.el ends here
diff --git a/attic/elisp/jao-vterm-repl.el b/attic/elisp/jao-vterm-repl.el
new file mode 100644
index 0000000..699ff39
--- /dev/null
+++ b/attic/elisp/jao-vterm-repl.el
@@ -0,0 +1,130 @@
+;;; jao-vterm-repl.el --- vterm-based repls -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2020, 2021 jao
+
+;; Author: jao <mail@jao.io>
+;; Keywords: terminals
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Helpers to launch reply things such as erlang shells inside a vterm.
+;; For instance, to declare an erl repl for rebar projects, one would call:
+;;
+;; (jao-vterm-repl-register "rebar.config" "rebar3 shell" "^[0-9]+> ")
+
+;;; Code:
+
+(require 'jao-compilation)
+
+(declare-function 'vterm-copy-mode "vterm")
+(declare-function 'vterm-send-string "vterm")
+(declare-function 'vterm-send-return "vterm")
+
+(defun jao-vterm-repl--buffer-name (&optional dir)
+ (format "*vterm -- repl - %s*" (or dir (jao-compilation-root))))
+
+(defvar jao-vterm-repl-repls nil)
+(defvar jao-vterm-repl-prompts nil)
+(defvar-local jao-vterm-repl--name nil)
+(defvar-local jao-vterm-repl--last-buffer nil)
+(defvar-local jao-vterm-repl--prompt-rx "^[0-9]+> ")
+
+(setq vterm-buffer-name-string nil)
+
+(defun jao-vterm-repl--exec (cmd &optional name)
+ (vterm name)
+ (when name
+ (vterm-send-string "unset PROMPT_COMMAND\n\n"))
+ (vterm-send-string cmd)
+ (vterm-send-return)
+ (when name (rename-buffer name t)))
+
+;;;###autoload
+(defun jao-vterm-repl-previous-prompt ()
+ (interactive)
+ (when (derived-mode-p 'vterm-mode)
+ (vterm-copy-mode 1)
+ (forward-line 0)
+ (when (re-search-backward jao-vterm-repl--prompt-rx nil t)
+ (goto-char (match-end 0)))))
+
+;;;###autoload
+(defun jao-vterm-repl-next-prompt ()
+ (interactive)
+ (when (derived-mode-p 'vterm-mode)
+ (vterm-copy-mode 1)
+ (or (re-search-forward jao-vterm-repl--prompt-rx nil t)
+ (vterm-copy-mode -1))
+ (unless (save-excursion
+ (re-search-forward jao-vterm-repl--prompt-rx nil t))
+ (vterm-copy-mode -1))))
+
+;;;###autoload
+(define-minor-mode jao-vterm-repl-mode "repl-aware vterm" nil nil
+ '(("\C-c\C-p" . jao-vterm-repl-previous-prompt)
+ ("\C-c\C-n" . jao-vterm-repl-next-prompt)
+ ("\C-c\C-z" . jao-vterm-repl-pop-to-src)))
+
+;;;###autoload
+(defun jao-vterm-repl ()
+ (let* ((dir (jao-compilation-root))
+ (vname (jao-vterm-repl--buffer-name dir))
+ (root-name (jao-compilation-root-file))
+ (buffer (seq-find `(lambda (b)
+ (string=
+ (buffer-local-value 'jao-vterm-repl--name
+ b)
+ ,vname))
+ (buffer-list))))
+ (or buffer
+ (let ((default-directory dir)
+ (prompt (cdr (assoc root-name jao-vterm-repl-prompts)))
+ (cmd (or (cdr (assoc root-name jao-vterm-repl-repls))
+ (read-string "REPL command: ")))
+ (bname (format "* vrepl - %s/%s *"
+ (file-name-base (string-remove-suffix "/" dir))
+ root-name)))
+ (jao-vterm-repl--exec cmd bname)
+ (jao-vterm-repl-mode)
+ (setq-local jao-vterm-repl--name vname)
+ (when prompt (setq-local jao-vterm-repl--prompt-rx prompt))
+ (current-buffer)))))
+
+;;;###autoload
+(defun jao-vterm-repl-register (build-file repl-cmd prompt-rx)
+ (jao-compilation-add-dominating build-file)
+ (add-to-list 'jao-vterm-repl-repls (cons build-file repl-cmd))
+ (add-to-list 'jao-vterm-repl-prompts (cons build-file prompt-rx)))
+
+;;;###autoload
+(defun jao-vterm-repl-pop-to-repl ()
+ (interactive)
+ (let ((bn (current-buffer)))
+ (pop-to-buffer (jao-vterm-repl))
+ (setq-local jao-vterm-repl--last-buffer bn)))
+
+;;;###autoload
+(defun jao-vterm-repl-pop-to-src ()
+ (interactive)
+ (when (buffer-live-p jao-vterm-repl--last-buffer)
+ (pop-to-buffer jao-vterm-repl--last-buffer)))
+
+;;;###autoload
+(defun jao-vterm-repl-send (cmd)
+ (with-current-buffer (jao-vterm-repl) (vterm-send-string cmd)))
+
+(provide 'jao-vterm-repl)
+;;; jao-vterm-repl.el ends here
diff --git a/attic/elisp/misc.el b/attic/elisp/misc.el
index 6484310..c2a6bd3 100644
--- a/attic/elisp/misc.el
+++ b/attic/elisp/misc.el
@@ -600,7 +600,7 @@
;;; eldoc for magit status/log buffers
(defun jao-magit-eldoc-for-commit (_callback)
- (when-let ((commit (magit-commit-at-point)))
+ (when-let* ((commit (magit-commit-at-point)))
(with-temp-buffer
(magit-git-insert "show"
"--format=format:%an <%ae>, %ar"
@@ -813,11 +813,6 @@
:diminish ((disable-mouse-global-mode . "")))
(global-disable-mouse-mode)
-;;; tmr
-(use-package tmr
- :ensure t
- :init
- (setq tmr-sound-file "/usr/share/sounds/freedesktop/stereo/message.oga"))
;;; pdf-tools
(use-package pdf-tools
:ensure t
@@ -892,6 +887,12 @@
"^\\*Slack - .*? : \\(MPIM: \\)?\\([^ ]+\\)\\( \\(T\\)\\)?.*" "\\2\\4")
(jao-define-attached-buffer "\\*Slack .+ Edit Message [0-9].+" 20))
+;;; alert
+(use-package alert
+ :ensure t
+ :init
+ (setq alert-default-style 'message ;; 'libnotify
+ alert-hide-all-notifications nil))
;;; snippets
(defun jao-org-notes-open-tags ()
"Search for a note file, matching all tags with completion."
@@ -902,7 +903,7 @@
(res (funcall fn)))
(while (and res tags) (setq res (seq-intersection res (funcall fn))))
(unless res (user-error "No notes found"))
- (when-let (f (completing-read "Select file: " (mapcar #'car res)))
+ (when-let* ((f (completing-read "Select file: " (mapcar #'car res))))
(find-file (cadr (assoc f res))))))
(defun jao-sway-run-or-focus-tidal ()
@@ -949,3 +950,366 @@
'notmuch-tree-match-author-face
'notmuch-tree-no-match-author-face)))
(propertize auth 'face face)))
+
+;; winttr
+
+(defun jao-weather (&optional wide)
+ (interactive "P")
+ (if (not wide)
+ (message "%s"
+ (jao-shell-string "curl -s"
+ "https://wttr.in/?format=%l++%m++%C+%c+%t+%w++%p"))
+ (jao-afio-goto-scratch)
+ (if-let ((b (get-buffer "*wttr*")))
+ (progn (pop-to-buffer b)
+ (term-send-string (get-buffer-process nil) "clear;curl wttr.in\n"))
+ (jao-exec-in-term "curl wttr.in" "*wttr*"))))
+(global-set-key (kbd "<f5>") #'jao-weather)
+
+;; so-long
+(setq large-file-warning-threshold (* 200 1024 1024))
+
+(use-package so-long
+ :ensure t
+ :diminish)
+(global-so-long-mode 1)
+
+;;;; code reviews
+(use-package code-review
+ :disabled t
+ :ensure t
+ :after forge
+ :bind (:map magit-status-mode-map
+ ("C-c C-r" . code-review-forge-pr-at-point)))
+
+;;;; jenkins
+(use-package jenkins
+ :ensure t
+ :init
+ ;; one also needs jenkins-api-token, jenkins-username and jenkins-url
+ ;; optionally: jenkins-colwidth-id, jenkins-colwidth-last-status
+ (setq jenkins-colwidth-name 35)
+ :config
+ (defun jao-jenkins-first-job (&rest _)
+ (interactive)
+ (goto-char (point-min))
+ (when (re-search-forward "^- Job" nil t)
+ (goto-char (match-beginning 0))))
+ (add-hook 'jenkins-job-view-mode-hook #'jao-jenkins-first-job)
+ (advice-add 'jenkins-job-render :after #'jao-jenkins-first-job)
+
+ (defun jenkins-refresh-console-output ()
+ (interactive)
+ (let ((n (buffer-name)))
+ (when (string-match "\\*jenkins-console-\\([^-]+\\)-\\(.+\\)\\*$" n)
+ (jenkins-get-console-output (match-string 1 n) (match-string 2 n))
+ (goto-char (point-max)))))
+
+ :bind (:map jenkins-job-view-mode-map
+ (("n" . next-line)
+ ("p" . previous-line)
+ ("f" . jao-jenkins-first-job)
+ ("RET" . jenkins--show-console-output-from-job-screen))
+ :map jenkins-console-output-mode-map
+ (("n" . next-line)
+ ("p" . previous-line)
+ ("g" . jenkins-refresh-console-output))))
+
+;;; doric themes
+(use-package doric-themes
+ :if (jao-is-darwin)
+ :ensure t
+ :demand t
+ :config
+ ;; These are the default values.
+ (setq doric-themes-to-toggle '(doric-light doric-marble))
+ (setq doric-themes-to-rotate doric-themes-collection)
+
+ (doric-themes-select 'doric-marble)
+
+ (set-face-attribute 'default nil :family "Triplicate T4c" :height 120)
+ ;; (set-face-attribute 'default nil :family "0xProto" :height 110)
+ ;; (set-face-attribute 'default nil :family "Rec Mono Casual" :height 120)
+ ;; (set-face-attribute 'default nil :family "Rec Mono Linear" :height 120)
+ ;; (set-face-attribute 'default nil :family "Rec Mono Duotone" :height 120)
+ ;; (set-face-attribute 'default nil :family "Victor Mono" :height 120)
+ ;; (set-face-attribute 'variable-pitch nil :family "Aporetic Sans" :height 1.0)
+ ;; (set-face-attribute 'fixed-pitch nil :family "Aporetic Sans Mono" :height 1.0)
+
+ :bind
+ (("<f5>" . doric-themes-toggle)
+ ("C-<f5>" . doric-themes-select)
+ ("M-<f5>" . doric-themes-rotate)))
+
+;;; gnuplot
+(use-package gnuplot
+ :disabled t
+ :ensure t
+ :commands (gnuplot-mode gnuplot-make-buffer)
+ :init (add-to-list 'auto-mode-alist '("\\.gp$" . gnuplot-mode)))
+
+;;; rdrview
+;; https://jiewawa.me/2024/04/another-way-of-integrating-mozilla-readability-in-emacs-eww/
+(define-minor-mode eww-rdrview-mode
+ "Toggle whether to use `rdrview' to make eww buffers more readable."
+ :lighter " R"
+ (if eww-rdrview-mode
+ (progn
+ (setq eww-retrieve-command '("rdrview" "-T" "title,sitename,body" "-H"))
+ (add-hook 'eww-after-render-hook #'eww-rdrview-update-title))
+ (progn
+ (setq eww-retrieve-command nil)
+ (remove-hook 'eww-after-render-hook #'eww-rdrview-update-title))))
+
+(defun eww-rdrview-update-title ()
+ "Change title key in `eww-data' with first line of buffer.
+It should be the title of the web page as returned by `rdrview'"
+ (save-excursion
+ (goto-char (point-min))
+ (plist-put eww-data :title (string-trim (thing-at-point 'line t))))
+ (eww--after-page-change))
+
+(defun eww-rdrview-toggle-and-reload ()
+ "Toggle `eww-rdrview-mode' and reload page in current eww buffer."
+ (interactive)
+ (if eww-rdrview-mode (eww-rdrview-mode -1)
+ (eww-rdrview-mode 1))
+ (eww-reload))
+
+(defun jao-eww-readable (rdrview)
+ (interactive "P" eww-mode)
+ (if rdrview
+ (eww-rdrview-toggle-and-reload)
+ (eww-readable)))
+
+;;; spotify
+(jao-load-path "espotify")
+
+(use-package espotify
+ :demand t
+ :init (setq espotify-service-name "mopidy"))
+
+(use-package consult-spotify :demand t)
+
+(defalias 'jao-streaming-album #'consult-spotify-album)
+(defalias 'jao-streaming-track #'consult-spotify-track)
+(defalias 'jao-streaming-artist #'consult-spotify-artist)
+(defalias 'jao-streaming-playlist #'consult-spotify-playlist)
+
+(jao-def-exec-in-term "ncmpcpp" "ncmpcpp" (jao-afio-goto-scratch))
+
+;;; spt
+(use-package jao-spt
+ :demand t
+ :config
+ (defun jao-spt-setup-aliases ()
+ (setq espotify-play-uri-function #'jao-spt-play-uri)
+ (defalias 'jao-streaming-list #'jao-term-spt)
+ (defalias 'jao-streaming-lyrics #'jao-spt-show-lyrics)
+ (defalias 'jao-streaming-toggle #'jao-spt-toggle)
+ (defalias 'jao-streaming-next #'jao-spt-next)
+ (defalias 'jao-streaming-prev #'jao-spt-previous)
+ (defalias 'jao-streaming-current #'jao-spt-echo-current)
+ (defalias 'jao-streaming-seek #'jao-spt-seek)
+ (defalias 'jao-streaming-seek-back #'jao-spt-seek-back)
+ (defalias 'jao-streaming-volume #'jao-spt-vol)
+ (defalias 'jao-streaming-volume-down #'jao-spt-vol-down)
+ (defalias 'jao-streaming-like #'jao-spt-like)
+ (defalias 'jao-streaming-dislike #'jao-spt-dislike)
+ (defalias 'jao-streaming-toggle-shuffle #'jao-spt-toggle-shuffle)))
+
+(jao-def-exec-in-term "spt" "spt" (jao-afio-goto-scratch))
+
+(defvar jao-spt-on t)
+
+(defun jao-streaming-toggle-player ()
+ (interactive)
+ (if jao-spt-on
+ (progn (setq jao-mpris-player "playerctld")
+ (require 'jao-mpris)
+ (jao-mpris-setup-aliases))
+ (jao-spt-setup-aliases)
+ (setq jao-mpris-player "spt"))
+ (setq jao-spt-on (not jao-spt-on))
+ (message "%s activated " jao-mpris-player))
+
+(jao-streaming-toggle-player)
+
+;;; mpd + mopidy
+(use-package jao-mpc
+ :demand t
+ :commands jao-mpc-setup)
+
+(defvar jao-mopidy-port 6669)
+(defvar jao-mpc-last-port jao-mpc-port)
+
+(defun jao-mpc-toggle-port ()
+ (interactive)
+ (setq jao-mpc-port
+ (if (equal jao-mpc-port jao-mopidy-port) 6600 jao-mopidy-port)
+ jao-mpc-last-port jao-mpc-port))
+
+(defsubst jao-mpc-mopidy-p () (equal jao-mpc-last-port jao-mopidy-port))
+
+(jao-mpc-setup jao-mopidy-port 70)
+
+(defun jao-mpc-pport (&optional mop)
+ (cond ((or mop (jao-mpc-playing-p jao-mopidy-port)) jao-mopidy-port)
+ ((jao-mpc-playing-p) 6600)
+ (t jao-mpc-last-port)))
+
+(defmacro jao-defun-play (name &optional mpc-name)
+ (let ((arg (gensym)))
+ `(defun ,(intern (format "jao-player-%s" name)) (&optional ,arg)
+ (interactive "P")
+ (,(intern (format "jao-mpc-%s" (or mpc-name name)))
+ (setq jao-mpc-last-port (jao-mpc-pport ,arg))))))
+
+(jao-defun-play toggle)
+(jao-defun-play next)
+(jao-defun-play previous)
+(jao-defun-play stop)
+(jao-defun-play echo echo-current-times)
+(jao-defun-play list show-playlist)
+(jao-defun-play info lyrics-track-data)
+(jao-defun-play browse show-albums)
+(jao-defun-play select-album)
+
+(defun jao-player-seek (delta) (jao-mpc-seek delta (jao-mpc-pport)))
+
+(defalias 'jao-player-connect 'jao-mpc-connect)
+(defalias 'jao-player-play 'jao-mpc-play)
+
+;;; org-modern
+(use-package org-modern
+ :ensure t
+ :init
+ (setq org-modern-fold-stars
+ '(("▶" . "▼") ("▷" . "▽") ("▶" . "▼") ("▹" . "▿") ("▸" . "▾")))
+
+ (define-derived-mode jao-org-inbox-mode org-mode
+ "Org inbox"
+ (org-indent-mode)
+ (org-modern-mode))
+
+ (add-to-list 'auto-mode-alist '("inbox\\.org\\'" . jao-org-inbox-mode))
+ (add-hook 'org-agenda-finalize-hook #'org-modern-agenda))
+
+;;; Gnus notify
+
+(defun jao-gnus--notify-strs ()
+ (let* ((all (jao-gnus--unread-counts))
+ (counts (cdr all))
+ (labels (seq-keep (lambda (args)
+ (apply 'jao-gnus--unread-label counts args))
+ jao-gnus-tracked-groups)))
+ (jao-when-darwin (jao-gnus--xbar-echo labels))
+ labels))
+
+(defvar jao-gnus-tracked-groups
+ (let ((feeds (thread-first
+ (directory-files mail-source-directory nil "feeds\\.[^e]")
+ (seq-difference
+ '("feeds.trove" "feeds.emacs" "feeds.emacs-devel")))))
+ `(("nnml:jao\\.bigml" "B" jao-themes-f00)
+ ("nnml:jao\\.\\(inbox\\|trove\\)" "I" jao-themes-f01)
+ ("nnml:jao.write" "W" jao-themes-warning)
+ ("nnml:jao.[^ithwb]" "J" jao-themes-dimm)
+ ("nnml:jao.hacking" "H" jao-themes-dimm)
+ ;; (,(format "^nnml:%s" (regexp-opt feeds)) "F" jao-themes-dimm)
+ ;; ("feeds\\.emacs" "E" jao-themes-dimm)
+ ("nnml:feeds\\." "F" jao-themes-dimm)
+ ("nnml:local" "l" jao-themes-dimm)
+ ("nnrss:.*" "R" jao-themes-dimm)
+ ("^\\(gwene\\|gmane\\)\\." "N" jao-themes-dimm))))
+
+(defun jao-gnus--unread-counts ()
+ (seq-reduce (lambda (r g)
+ (let ((n (gnus-group-unread (car g))))
+ (if (and (numberp n) (> n 0))
+ (cons (+ n (car r))
+ (cons (cons (car g) n) (cdr r)))
+ r)))
+ gnus-newsrc-alist
+ '(0)))
+
+(defun jao-gnus-unread-count ()
+ (seq-reduce (lambda (c g) (+ c (or (gnus-group-unread (car g)) 0)))
+ gnus-newsrc-alist
+ 0))
+
+(defun jao-gnus--unread-label (counts rx label face)
+ (let ((n (seq-reduce (lambda (n c)
+ (if (string-match-p rx (car c)) (+ n (cdr c)) n))
+ counts
+ 0)))
+ (when (> n 0) `(:propertize ,(format "%s%d " label n) face ,face))))
+
+;;; ement
+(use-package ement
+ :disabled t
+ :ensure t
+ :init (setq ement-save-sessions t
+ ement-sessions-file (locate-user-emacs-file "cache/ement.el")
+ ement-room-avatars nil
+ ement-notify-dbus-p nil
+ ement-room-left-margin-width 0
+ ement-room-right-margin-width 11
+ ement-room-timestamp-format "%H:%M"
+ ement-room-timestamp-header-format "­­--------")
+
+ :custom ((ement-room-message-format-spec "(%S) %B%r%R %t"))
+
+ :config
+ (defun jao-ement-track (event room session)
+ (when (ement-notify--room-unread-p event room session)
+ (when-let* ((n (ement-room--buffer-name room))
+ (b (get-buffer n)))
+ (tracking-add-buffer b))))
+
+ (add-hook 'ement-event-hook #'jao-ement-track)
+ (jao-shorten-modes 'ement-room-mode)
+ (jao-tracking-cleaner "^\\*Ement Room: \\(.+\\)\\*" "@\\1"))
+
+;;;; lsp
+(use-package lsp-mode
+ :disabled t
+ ;; :ensure t
+ :commands lsp
+ :custom
+ ;; what to use when checking on-save. "check" is default, I prefer clippy
+ (lsp-rust-analyzer-cargo-watch-command "clippy")
+ (lsp-eldoc-render-all t)
+ (lsp-idle-delay 0.6)
+ ;; enable / disable the hints as you prefer:
+ (lsp-inlay-hint-enable t)
+ ;; These are optional configurations. See https://emacs-lsp.github.io/lsp-mode/page/lsp-rust-analyzer/#lsp-rust-analyzer-display-chaining-hints for a full list
+ (lsp-rust-analyzer-display-lifetime-elision-hints-enable "skip_trivial")
+ (lsp-rust-analyzer-display-chaining-hints nil)
+ (lsp-rust-analyzer-display-lifetime-elision-hints-use-parameter-names nil)
+ (lsp-rust-analyzer-display-closure-return-type-hints t)
+ (lsp-rust-analyzer-display-parameter-hints nil)
+ (lsp-rust-analyzer-display-reborrow-hints nil)
+ :config
+ (add-hook 'lsp-mode-hook 'lsp-ui-mode))
+
+(use-package lsp-ui
+ :disabled t
+ ;; :ensure t
+ :commands lsp-ui-mode
+ :custom
+ (lsp-ui-peek-always-show t)
+ (lsp-ui-sideline-show-hover t)
+ (lsp-ui-doc-enable t))
+;;; xbar
+(defvar jao-notmuch-xbar-queries
+ `((:name "" :query "tag:new and not tag:draft" :face jao-themes-f00)
+ (:name "i" :query "tag:new and tag:/inbox|write|drivel/"
+ :face jao-themes-warning)))
+
+(defvar jao-notmuch-mac-xbar t)
+
+(defun jao-notmuch-xbar ()
+ (let ((cnts (notmuch-hello-query-counts jao-notmuch-xbar-queries)))
+ (jao-shell-exec (format "echo '%s | color=#8b3626 | size=11' >/tmp/xbar"
+ (mapconcat 'jao-notmuch--qstr cnts " ")))))
diff --git a/attic/elisp/nnnm.el b/attic/elisp/nnnm.el
index 552e95c..561c09e 100644
--- a/attic/elisp/nnnm.el
+++ b/attic/elisp/nnnm.el
@@ -1,6 +1,6 @@
;;; nnnm.el --- Gnus backend for notmuch -*- lexical-binding: t; -*-
-;; Copyright (C) 2021 jao
+;; Copyright (C) 2021, 2026 jao
;; Author: jao <mail@jao.io>
;; Keywords: mail
@@ -47,8 +47,8 @@
(defun nnnm--find-query (name)
- (when-let (s (seq-find (lambda (s) (string= (plist-get s :name) name))
- nnnm-saved-searches))
+ (when-let* ((s (seq-find (lambda (s) (string= (plist-get s :name) name))
+ nnnm-saved-searches)))
(plist-get s :query)))
(defun nnnm--find-message-file (id)
@@ -60,11 +60,11 @@
(defun nnnm--article-data (article group)
(cond ((stringp article) (list article))
((numberp article)
- (when-let (data (nnnm--group-data group))
+ (when-let* ((data (nnnm--group-data group)))
(elt data (1- article))))))
(defun nnnm-article-to-file (article group)
- (when-let (d (nnnm--article-data article group))
+ (when-let* ((d (nnnm--article-data article group)))
(or (cadr d) (nnnm--find-message-file (car d)))))
(defun nnnm--count (query &optional context)
@@ -117,7 +117,7 @@
(gnus-set-active (nnnm--prefixed group server) (cons 1 n)))
(defun nnnm--update-group-data (group &optional server)
- (when-let (query (nnnm--find-query group))
+ (when-let* ((query (nnnm--find-query group)))
(let* ((data (or (nnnm--group-data group)
(mapcar #'list (nnnm--search query "NOT tag:new"))))
(ids (nnnm--search query "tag:new"))
@@ -158,7 +158,7 @@
(with-current-buffer nntp-server-buffer
(erase-buffer)
(dolist (s nnnm-saved-searches)
- (when-let (query (plist-get s :query))
+ (when-let* ((query (plist-get s :query)))
(let ((name (plist-get s :name))
(total (nnnm--count query)))
(insert (format "%s %d 1 y\n" name total))))))
@@ -173,7 +173,7 @@
(if (stringp (car sequence))
'headers
(dolist (article sequence)
- (when-let (file (nnnm-article-to-file article group))
+ (when-let* ((file (nnnm-article-to-file article group)))
(insert (format "221 %d Article retrieved.\n" article))
(save-excursion (nnheader-insert-head file))
(if (re-search-forward "\n\r?\n" nil t)