From 297120dc6cd3b07e007fa124f6b6cda40735f187 Mon Sep 17 00:00:00 2001 From: Marcelo Henrique Cerri Date: Sun, 22 Apr 2018 02:18:57 -0300 Subject: [PATCH 1/3] lib: add last_child flag to thread information With that flag it's possible to reconstruct the entire thread tree structure in mu4e. --- lib/mu-container.c | 8 ++++++-- lib/mu-msg-iter.h | 7 ++++--- lib/mu-msg-sexp.c | 4 +++- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/lib/mu-container.c b/lib/mu-container.c index 7722b64a..05127218 100644 --- a/lib/mu-container.c +++ b/lib/mu-container.c @@ -571,7 +571,8 @@ count_colons (const char *str) static MuMsgIterThreadInfo* thread_info_new (gchar *threadpath, gboolean root, gboolean first_child, - gboolean empty_parent, gboolean has_child, gboolean is_dup) + gboolean last_child, gboolean empty_parent, + gboolean has_child, gboolean is_dup) { MuMsgIterThreadInfo *ti; @@ -582,6 +583,7 @@ thread_info_new (gchar *threadpath, gboolean root, gboolean first_child, ti->prop = MU_MSG_ITER_THREAD_PROP_NONE; ti->prop |= root ? MU_MSG_ITER_THREAD_PROP_ROOT : 0; ti->prop |= first_child ? MU_MSG_ITER_THREAD_PROP_FIRST_CHILD : 0; + ti->prop |= last_child ? MU_MSG_ITER_THREAD_PROP_LAST_CHILD : 0; ti->prop |= empty_parent ? MU_MSG_ITER_THREAD_PROP_EMPTY_PARENT : 0; ti->prop |= is_dup ? MU_MSG_ITER_THREAD_PROP_DUP : 0; ti->prop |= has_child ? MU_MSG_ITER_THREAD_PROP_HAS_CHILD : 0; @@ -610,12 +612,13 @@ static void add_to_thread_info_hash (GHashTable *thread_info_hash, MuContainer *c, char *threadpath) { - gboolean is_root, first_child, empty_parent, is_dup, has_child; + gboolean is_root, first_child, last_child, empty_parent, is_dup, has_child; /* 'root' means we're a child of the dummy root-container */ is_root = (c->parent == NULL); first_child = is_root ? FALSE : (c->parent->child == c); + last_child = is_root ? FALSE : (c->next == NULL); empty_parent = is_root ? FALSE : (!c->parent->msg); is_dup = c->flags & MU_CONTAINER_FLAG_DUP; has_child = c->child ? TRUE : FALSE; @@ -625,6 +628,7 @@ add_to_thread_info_hash (GHashTable *thread_info_hash, MuContainer *c, thread_info_new (threadpath, is_root, first_child, + last_child, empty_parent, has_child, is_dup)); diff --git a/lib/mu-msg-iter.h b/lib/mu-msg-iter.h index 49d35d95..95a79ca2 100644 --- a/lib/mu-msg-iter.h +++ b/lib/mu-msg-iter.h @@ -163,9 +163,10 @@ enum _MuMsgIterThreadProp { MU_MSG_ITER_THREAD_PROP_ROOT = 1 << 0, MU_MSG_ITER_THREAD_PROP_FIRST_CHILD = 1 << 1, - MU_MSG_ITER_THREAD_PROP_EMPTY_PARENT = 1 << 2, - MU_MSG_ITER_THREAD_PROP_DUP = 1 << 3, - MU_MSG_ITER_THREAD_PROP_HAS_CHILD = 1 << 4 + MU_MSG_ITER_THREAD_PROP_LAST_CHILD = 1 << 2, + MU_MSG_ITER_THREAD_PROP_EMPTY_PARENT = 1 << 3, + MU_MSG_ITER_THREAD_PROP_DUP = 1 << 4, + MU_MSG_ITER_THREAD_PROP_HAS_CHILD = 1 << 5 }; typedef guint8 MuMsgIterThreadProp; diff --git a/lib/mu-msg-sexp.c b/lib/mu-msg-sexp.c index ccbde7c0..cf5f5be8 100644 --- a/lib/mu-msg-sexp.c +++ b/lib/mu-msg-sexp.c @@ -434,11 +434,13 @@ static void append_sexp_thread_info (GString *gstr, const MuMsgIterThreadInfo *ti) { g_string_append_printf - (gstr, "\t:thread (:path \"%s\" :level %u%s%s%s%s)\n", + (gstr, "\t:thread (:path \"%s\" :level %u%s%s%s%s%s)\n", ti->threadpath, ti->level, ti->prop & MU_MSG_ITER_THREAD_PROP_FIRST_CHILD ? " :first-child t" : "", + ti->prop & MU_MSG_ITER_THREAD_PROP_LAST_CHILD ? + " :last-child t" : "", ti->prop & MU_MSG_ITER_THREAD_PROP_EMPTY_PARENT ? " :empty-parent t" : "", ti->prop & MU_MSG_ITER_THREAD_PROP_DUP ? From 0b382105497cb40030bfa2730b3bf1f2485a051f Mon Sep 17 00:00:00 2001 From: Marcelo Henrique Cerri Date: Mon, 23 Apr 2018 01:07:58 -0300 Subject: [PATCH 2/3] mu4e: add support for mutt-like thread tree prefix --- mu4e/mu4e-headers.el | 124 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 102 insertions(+), 22 deletions(-) diff --git a/mu4e/mu4e-headers.el b/mu4e/mu4e-headers.el index 851a602a..be120ae8 100644 --- a/mu4e/mu4e-headers.el +++ b/mu4e/mu4e-headers.el @@ -232,11 +232,39 @@ one of: `:date', `:subject', `:size', `:prio', `:from', `:to.', (defvar mu4e-headers-unread-mark '("u" . "⎕") "Unread.") ;; thread prefix marks -(defvar mu4e-headers-has-child-prefix '("+" . "◼ ") "Parent.") -(defvar mu4e-headers-empty-parent-prefix '("-" . "◽ ") "Orphan.") -(defvar mu4e-headers-first-child-prefix '("\\" . "┗▶") "First child.") -(defvar mu4e-headers-duplicate-prefix '("=" . "≡ ") "Duplicate.") -(defvar mu4e-headers-default-prefix '("|" . "│ ") "Default.") +(defvar mu4e-headers-thread-child-prefix '("├>" . "┣▶ ") + "Prefix for messages in sub threads that do have a following sibling. + +This variable is only used when mu4e-headers-new-thread-style is non-nil.") + +(defvar mu4e-headers-thread-last-child-prefix '("└>" . "┗▶ ") + "Prefix for messages in sub threads that do not have a following sibling. + +This variable is only used when mu4e-headers-new-thread-style is non-nil.") + +(defvar mu4e-headers-thread-connection-prefix '("│" . "┃ ") + "Prefix to connect sibling messages that do not follow each other. + +This prefix should have the same length as `mu4e-headers-thread-blank-prefix'. + +This variable is only used when mu4e-headers-new-thread-style is non-nil.") + +(defvar mu4e-headers-thread-blank-prefix '(" " . " ") + "Prefix to separate non connected messages. + +This prefix should have the same length as `mu4e-headers-thread-connection-prefix'. + +This variable is only used when mu4e-headers-new-thread-style is non-nil.") + +(defvar mu4e-headers-thread-orphan-prefix '("" . "") + "Prefix for orphan messages. + +This variable is only used when mu4e-headers-new-thread-style is non-nil.") + +(defvar mu4e-headers-thread-duplicate-prefix '("=" . "≡ ") + "Prefix for duplicate messages. + +This variable is only used when mu4e-headers-new-thread-style is non-nil.") (defvar mu4e-headers-actions '( ("capture message" . mu4e-action-capture-message) @@ -411,26 +439,78 @@ into a string." (or name email "?"))) contacts ", ")) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defsubst mu4e~headers-thread-prefix-map (type) + "Return the thread prefix based on the symbol TYPE." + (let ((get-prefix + (lambda (cell) + (if mu4e-use-fancy-chars (cdr cell) (car cell))))) + (case type + ('child (funcall get-prefix mu4e-headers-thread-child-prefix)) + ('last-child (funcall get-prefix mu4e-headers-thread-last-child-prefix)) + ('connection (funcall get-prefix mu4e-headers-thread-connection-prefix)) + ('blank (funcall get-prefix mu4e-headers-thread-blank-prefix)) + ('orphan (funcall get-prefix mu4e-headers-thread-orphan-prefix)) + ('duplicate (funcall get-prefix mu4e-headers-thread-duplicate-prefix)) + (t "?")))) + +;; In order to print a thread tree with all the message connections, +;; it's necessary to keep track of all sub levels that still have +;; following messages. For each level, mu4e~headers-thread-state keeps +;; the value t for a connection or nil otherwise. +(defvar-local mu4e~headers-thread-state '()) + (defsubst mu4e~headers-thread-prefix (thread) "Calculate the thread prefix based on thread info THREAD." (when thread - (let ((get-prefix - (lambda (cell) (if mu4e-use-fancy-chars (cdr cell) (car cell))))) - (concat - (make-string (* (if (plist-get thread :empty-parent) 0 1) - (plist-get thread :level)) ?\s) - (cond - ((plist-get thread :has-child) - (funcall get-prefix mu4e-headers-has-child-prefix)) - ((plist-get thread :empty-parent) - (funcall get-prefix mu4e-headers-empty-parent-prefix)) - ((plist-get thread :first-child) - (funcall get-prefix mu4e-headers-first-child-prefix)) - ((plist-get thread :duplicate) - (funcall get-prefix mu4e-headers-duplicate-prefix)) - (t - (funcall get-prefix mu4e-headers-default-prefix))) - " ")))) + (let ((prefix "") + (level (plist-get thread :level)) + (has-child (plist-get thread :has-child)) + (empty-parent (plist-get thread :empty-parent)) + (first-child (plist-get thread :first-child)) + (last-child (plist-get thread :last-child)) + (duplicate (plist-get thread :duplicate))) + ;; Do not prefix root messages. + (if (or (= level 0) empty-parent) + (setq mu4e~headers-thread-state '())) + (if (> level 0) + (let* ((length (length mu4e~headers-thread-state)) + (padding (make-list (max 0 (- level length)) nil))) + ;; Trim and pad the state to ensure a message will + ;; always be shown with the correct identation, even if + ;; a broken thread is returned. It's trimmed to level-1 + ;; because the current level has always an connection + ;; and it used a special formatting. + (setq mu4e~headers-thread-state + (subseq (append mu4e~headers-thread-state padding) + 0 (- level 1))) + ;; Prepare the thread prefix. + (setq prefix + (concat + ;; Current mu4e~headers-thread-state, composed by + ;; connections or blanks. + (mapconcat + (lambda (s) + (if s (mu4e~headers-thread-prefix-map 'connection) + (mu4e~headers-thread-prefix-map 'blank))) + mu4e~headers-thread-state "") + ;; Current entry. + (if last-child (mu4e~headers-thread-prefix-map 'last-child) + (mu4e~headers-thread-prefix-map 'child)))))) + ;; If a new sub-thread will follow (has-child) and the current + ;; one is still not done (not last-child), then a new + ;; connection needs to be added to the tree-state. It's not + ;; necessary to a blank (nil), because padding will handle + ;; that. + (if (and has-child (not last-child)) + (setq mu4e~headers-thread-state + (append mu4e~headers-thread-state '(t)))) + ;; Return the thread prefix. + (format "%s%s%s" + prefix + (if empty-parent + (mu4e~headers-thread-prefix-map 'orphan) "") + (if duplicate + (mu4e~headers-thread-prefix-map 'duplicate) ""))))) (defsubst mu4e~headers-flags-str (flags) "Get a display string for the flags. From adc56249da0b349158b1e31137f8f40f66db7d6a Mon Sep 17 00:00:00 2001 From: Marcelo Henrique Cerri Date: Tue, 24 Apr 2018 22:17:38 -0300 Subject: [PATCH 3/3] mu4e.texi: update the header view example with the new thread prefix --- mu4e/mu4e.texi | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/mu4e/mu4e.texi b/mu4e/mu4e.texi index c832cf5e..8aff1fe4 100644 --- a/mu4e/mu4e.texi +++ b/mu4e/mu4e.texi @@ -787,15 +787,16 @@ An example headers view: @cartouche @verbatim Date V Flgs From/To List Subject -06:32 Nu To Edmund Dantès GstDev + Re: Gstreamer-V4L... -15:08 Nu Abbé Busoni GstDev + Re: Gstreamer-V... -18:20 Nu Pierre Morrel GstDev \ Re: Gstreamer... -2013-03-18 S Jacopo EmacsUsr + emacs server on win... -2013-03-18 S Mercédès EmacsUsr \ RE: emacs server ... -2013-03-18 S Beachamp EmacsUsr + Re: Copying a whole... -22:07 Nu Albert de Moncerf EmacsUsr \ Re: Copying a who... -2013-03-18 S Gaspard Caderousse GstDev | Issue with GESSimpl... -2013-03-18 Ss Baron Danglars GuileUsr | Guile-SDL 0.4.2 ava... +06:32 Nu To Edmund Dantès GstDev Gstreamer-V4L2SINK ... +15:08 Nu Abbé Busoni GstDev ├> ... +18:20 Nu Pierre Morrel GstDev │└> ... +07:48 Nu To Edmund Dantès GstDev └> ... +2013-03-18 S Jacopo EmacsUsr emacs server on win... +2013-03-18 S Mercédès EmacsUsr └> ... +2013-03-18 S Beachamp EmacsUsr Re: Copying a whole... +22:07 Nu Albert de Moncerf EmacsUsr └> ... +2013-03-18 S Gaspard Caderousse GstDev Issue with GESSimpl... +2013-03-18 Ss Baron Danglars GuileUsr Guile-SDL 0.4.2 ava... End of search results @end verbatim @end cartouche