Last active
October 10, 2022 18:23
-
-
Save rougier/98e83fb50e19fb73fe34a7ecc5fc1ccc to your computer and use it in GitHub Desktop.
Blazing fast mu4e thread folding
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
;; mu4e thread fast folding -*- lexical-binding: t; -*- | |
;; This file is not part of GNU Emacs. | |
;; | |
;; 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 <http://www.gnu.org/licenses/> | |
(require 'mu4e) | |
(defun mu4e-fast-folding-info (msg) | |
(let* ((thread (mu4e-message-field msg :thread)) | |
(prefix (mu4e~headers-thread-prefix thread)) | |
(unread (memq 'unread (mu4e-message-field msg :flags)))) | |
(concat | |
(if (= (length prefix) 0) " " " ") ;; Normal space vs Non-breaking space | |
(if unread "•" " ")))) ;; Specific character to later detect unread | |
(add-to-list 'mu4e-header-info-custom | |
'(:fast-folding . (:name "fast-folding" | |
:shortname "" | |
:function mu4e-fast-folding-info))) | |
(setq mu4e-headers-fields '((:fast-folding . 2) | |
(:human-date . 12) | |
(:flags . 6) | |
(:mailing-list . 10) | |
(:from . 22) | |
(:subject))) | |
(defun mu4e-fast-folding-is-unfolded-child () | |
"Check if the line at point is an unfolded thread child. | |
This is detected by the presence of non-breaking space." | |
(interactive) | |
(save-excursion | |
(beginning-of-line) | |
(and (not (mu4e-fast-folding-is-folded-children)) | |
(search-forward " " (line-end-position) t)))) | |
(defun mu4e-fast-folding-is-folded-children () | |
"Check if the line at point is a folded thread. | |
This is detected by the presence of an overlay with value 'overlay." | |
(interactive) | |
(save-excursion | |
(beginning-of-line) | |
(let ((overlays (overlays-at (point))) | |
(found nil)) | |
(while overlays | |
(if (overlay-get (car overlays) 'overlay) | |
(setq found t)) | |
(setq overlays (cdr overlays))) | |
found))) | |
(defun mu4e-fast-folding-is-root () | |
"Check if the line at point is a thread root." | |
(interactive) | |
(and (not (mu4e-fast-folding-is-unfolded-child)) | |
(not (mu4e-fast-folding-is-folded-children)))) | |
(defun mu4e-fast-folding-is-unread () | |
"Check if the line at point is an unread message." | |
(save-excursion | |
(beginning-of-line) | |
(search-forward "•" (line-end-position) t))) | |
(defun mu4e-fast-folding-thread-toggle () | |
"Toggle thread at point." | |
(interactive) | |
(save-excursion | |
(beginning-of-line) | |
(if (mu4e-fast-folding-is-root) | |
(forward-line)) | |
(cond ((mu4e-fast-folding-is-folded-children) | |
(mu4e-fast-folding-thread-unfold)) | |
((mu4e-fast-folding-is-unfolded-child) | |
(mu4e-fast-folding-thread-fold))))) | |
(defun mu4e-fast-folding-thread-unfold () | |
"Unfold thread at point." | |
(interactive) | |
(if (mu4e-fast-folding-is-root) | |
(forward-line)) | |
(let ((overlays (overlays-at (point)))) | |
(while overlays | |
(let ((overlay (car overlays))) | |
(if (overlay-get overlay 'overlay) | |
(delete-overlay (overlay-get overlay 'overlay)))) | |
(setq overlays (cdr overlays))))) | |
(defun mu4e-fast-folding-thread-fold () | |
"Fold thread at point." | |
(interactive) | |
;; Move to thread start | |
(beginning-of-line) | |
(while (and (> (point) (point-min)) | |
(mu4e-fast-folding-is-unfolded-child)) | |
(forward-line -1)) | |
(forward-line +1) | |
;; Hide all children, count them and count unread | |
(beginning-of-line) | |
(let ((start (point)) | |
(end (+ (point) 1)) | |
(unread 0) | |
(count 0)) | |
(while (and (< (point) (point-max)) | |
(mu4e-fast-folding-is-unfolded-child)) | |
;; Count unread | |
(beginning-of-line) | |
(if (mu4e-fast-folding-is-unread) | |
(setq unread (+ unread 1))) | |
;; Count thread | |
(setq count (+ count 1)) | |
;; Set new end for the overlay | |
(setq end (+ (line-end-position) 1)) | |
(forward-line +1) | |
(beginning-of-line)) | |
;; Add overlay | |
(let* ((overlay (make-overlay start (- end 1))) | |
(face (if (> unread 0) 'mu4e-unread-face 'mu4e-system-face)) | |
(text (if (> unread 0) | |
(format " --- %d hidden messages (%d unread) --- " count unread) | |
(format " --- %d hidden messages --- " count)))) | |
;; No overlay if only 1 child | |
(when (> count 1) | |
(overlay-put overlay 'display (propertize text 'face face)) | |
(overlay-put overlay 'overlay overlay))))) | |
(defun mu4e-fast-folding-thread-fold-all () | |
"Fold all threads independently of their current state." | |
(interactive) | |
(save-excursion | |
(goto-char (point-min)) | |
(while (not (eobp)) | |
(mu4e-fast-folding-thread-fold) | |
(forward-line)))) | |
(defun mu4e-fast-folding-thread-unfold-all () | |
"Unfold all threads, independently of their current state." | |
(interactive) | |
(save-excursion | |
(goto-char (point-min)) | |
(while (not (eobp)) | |
(mu4e-fast-folding-thread-unfold) | |
(forward-line)))) | |
(defvar mu4e-fast-folding-thread-folding-state nil | |
"Global folding state") | |
(defun mu4e-fast-folding-thread-toggle-all () | |
"Toggle global folding state." | |
(interactive) | |
(when mu4e-headers-include-related | |
(setq mu4e-fast-folding-thread-folding-state | |
(not mu4e-fast-folding-thread-folding-state)) | |
(mu4e-fast-folding-thread-apply-folding))) | |
(defun mu4e-fast-folding-thread-apply-folding () | |
"Apply folding according to the global folding state." | |
(interactive) | |
(if mu4e-fast-folding-thread-folding-state | |
(mu4e-fast-folding-thread-fold-all) | |
(mu4e-fast-folding-thread-unfold-all))) | |
(add-hook 'mu4e-headers-found-hook | |
#'mu4e-fast-folding-thread-apply-folding) | |
(define-key mu4e-headers-mode-map (kbd "TAB") | |
#'mu4e-fast-folding-thread-toggle) | |
(define-key mu4e-headers-mode-map (kbd "<backtab>") | |
#'mu4e-fast-folding-thread-toggle-all) |
Ok after investigating, this line (let* ((thread (mu4e-message-field msg :thread))
has to be changed to (let* ((thread (mu4e-message-field msg :meta))
for it to work. But there is an issue that you cannot forward-line if point is at the beginning of the line when threads are folded...
You're using next-line
?
Yes. Good catch - will rebind next-line
to mu4e-headers-next
. Using C-n
instead of n
was causing issues.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I have saved the .el file in my path that I load upon startup. This does not throw an error, but I also don't see threads being folded. Manually evaluating
mu4e-fast-folding.el
also does not work. I am on Emacs 29.0.50 and mu4e 1.9.0.