Merge pull request #2372 from mickeynp/feature/improve-window-buffer-handling

Multiple buffer support and standardised window and buffer handling
This commit is contained in:
Dirk-Jan C. Binnema 2022-12-10 19:46:40 +02:00 committed by GitHub
commit a39d61bb58
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 704 additions and 422 deletions

View File

@ -52,7 +52,8 @@ mu4e_srcs=[
'mu4e-speedbar.el', 'mu4e-speedbar.el',
'mu4e-update.el', 'mu4e-update.el',
'mu4e-vars.el', 'mu4e-vars.el',
'mu4e-view.el' 'mu4e-view.el',
'mu4e-window.el'
] ]
# note, we cannot compile mu4e-config.el without incurring # note, we cannot compile mu4e-config.el without incurring

View File

@ -76,7 +76,7 @@
(require 'mu4e-message) (require 'mu4e-message)
(require 'mu4e-draft) (require 'mu4e-draft)
(require 'mu4e-context) (require 'mu4e-context)
(require 'mu4e-window)
;;; Configuration ;;; Configuration
;; see mu4e-drafts.el ;; see mu4e-drafts.el
@ -443,9 +443,9 @@ buffers; lets remap its faces so it uses the ones for mu4e."
(subj (unless (and subj (string-match "^[:blank:]*$" subj)) subj)) (subj (unless (and subj (string-match "^[:blank:]*$" subj)) subj))
(str (or subj (str (or subj
(pcase compose-type (pcase compose-type
('reply "*reply*") ('reply "*mu4e-reply*")
('forward "*forward*") ('forward "*mu4e-forward*")
(_ "*draft*"))))) (_ "*mu4e-draft*")))))
(rename-buffer (generate-new-buffer-name (rename-buffer (generate-new-buffer-name
(truncate-string-to-width (truncate-string-to-width
str mu4e~compose-buffer-max-name-length) str mu4e~compose-buffer-max-name-length)
@ -502,8 +502,7 @@ See `mu4e-compose-crypto-policy' for more details."
(sign (mml-secure-message-sign)) (sign (mml-secure-message-sign))
(encrypt (mml-secure-message-encrypt))))) (encrypt (mml-secure-message-encrypt)))))
(cl-defun mu4e~compose-handler (compose-type &optional original-msg includes (cl-defun mu4e~compose-handler (compose-type &optional original-msg includes)
switch-function)
"Create a new draft message, or open an existing one. "Create a new draft message, or open an existing one.
COMPOSE-TYPE determines the kind of message to compose and is a COMPOSE-TYPE determines the kind of message to compose and is a
@ -539,79 +538,81 @@ are optional."
mu4e-compose-context-policy) mu4e-compose-context-policy)
(run-hooks 'mu4e-compose-pre-hook) (run-hooks 'mu4e-compose-pre-hook)
;; this opens (or re-opens) a message with all the basic headers set. ;; this opens (or re-opens) a message with all the basic headers set.
(let ((winconf (current-window-configuration))) (let ((draft-buffer))
(condition-case nil (let ((winconf (current-window-configuration)))
(mu4e-draft-open compose-type original-msg switch-function) (condition-case nil
(quit (set-window-configuration winconf) (setq draft-buffer (mu4e-draft-open compose-type original-msg))
(mu4e-message "Operation aborted") (quit (set-window-configuration winconf)
(cl-return-from mu4e~compose-handler)))) (mu4e-message "Operation aborted")
;; insert mail-header-separator, which is needed by message mode to separate (cl-return-from mu4e~compose-handler))))
;; headers and body. will be removed before saving to disk (set-buffer draft-buffer)
(mu4e~draft-insert-mail-header-separator) ;; insert mail-header-separator, which is needed by message mode to separate
;; headers and body. will be removed before saving to disk
(mu4e~draft-insert-mail-header-separator)
;; maybe encrypt/sign replies ;; maybe encrypt/sign replies
(mu4e-compose-crypto-message original-msg compose-type) (mu4e-compose-crypto-message original-msg compose-type)
;; include files -- e.g. when inline forwarding a message with ;; include files -- e.g. when inline forwarding a message with
;; attachments, we take those from the original. ;; attachments, we take those from the original.
(save-excursion (save-excursion
(goto-char (point-max)) ;; put attachments at the end (goto-char (point-max)) ;; put attachments at the end
(if (and (eq compose-type 'forward) mu4e-compose-forward-as-attachment) (if (and (eq compose-type 'forward) mu4e-compose-forward-as-attachment)
(mu4e-compose-attach-message original-msg) (mu4e-compose-attach-message original-msg)
(dolist (att includes) (dolist (att includes)
(let ((file-name (plist-get att :file-name)) (let ((file-name (plist-get att :file-name))
(mime (plist-get att :mime-type)) (mime (plist-get att :mime-type))
(description (plist-get att :description)) (description (plist-get att :description))
(disposition (plist-get att :disposition))) (disposition (plist-get att :disposition)))
(if file-name (if file-name
(mml-attach-file file-name mime description disposition) (mml-attach-file file-name mime description disposition)
(mml-attach-buffer (plist-get att :buffer-name) (mml-attach-buffer (plist-get att :buffer-name)
mime description disposition)))))) mime description disposition))))))
(mu4e~compose-set-friendly-buffer-name compose-type) (mu4e~compose-set-friendly-buffer-name compose-type)
;; bind to `mu4e-compose-parent-message' of compose buffer ;; bind to `mu4e-compose-parent-message' of compose buffer
(set (make-local-variable 'mu4e-compose-parent-message) original-msg) (set (make-local-variable 'mu4e-compose-parent-message) original-msg)
(put 'mu4e-compose-parent-message 'permanent-local t) (put 'mu4e-compose-parent-message 'permanent-local t)
;; set mu4e-compose-type once more for this buffer, ;; set mu4e-compose-type once more for this buffer,
(set (make-local-variable 'mu4e-compose-type) compose-type) (set (make-local-variable 'mu4e-compose-type) compose-type)
(put 'mu4e-compose-type 'permanent-local t) (put 'mu4e-compose-type 'permanent-local t)
;; hide some headers ;; hide some headers
(mu4e~compose-hide-headers) (mu4e~compose-hide-headers)
;; switch on the mode ;; switch on the mode
(mu4e-compose-mode) (mu4e-compose-mode)
;; now jump to some useful positions, and start writing that mail! ;; now jump to some useful positions, and start writing that mail!
(if (member compose-type '(new forward)) (if (member compose-type '(new forward))
(message-goto-to) (message-goto-to)
;; otherwise, it depends... ;; otherwise, it depends...
(pcase message-cite-reply-position (pcase message-cite-reply-position
((or 'above 'traditional) (message-goto-body)) ((or 'above 'traditional) (message-goto-body))
(_ (when (message-goto-signature) (forward-line -2))))) (_ (when (message-goto-signature) (forward-line -2)))))
;; don't allow undoing anything before this. ;; don't allow undoing anything before this.
(setq buffer-undo-list nil) (setq buffer-undo-list nil)
(when mu4e-compose-in-new-frame (when mu4e-compose-in-new-frame
;; make sure to close the frame when we're done with the message these are ;; make sure to close the frame when we're done with the message these are
;; all buffer-local; ;; all buffer-local;
(push 'delete-frame message-exit-actions) (push 'delete-frame message-exit-actions)
(push 'delete-frame message-postpone-actions)) (push 'delete-frame message-postpone-actions))
;; buffer is not user-modified yet ;; buffer is not user-modified yet
(set-buffer-modified-p nil)) (set-buffer-modified-p nil)
(mu4e-display-buffer draft-buffer t)))
(defun mu4e~switch-back-to-mu4e-buffer () (defun mu4e~switch-back-to-mu4e-buffer ()
"Try to go back to some previous buffer, in the order view->headers->main." "Try to go back to some previous buffer, in the order view->headers->main."
(unless (eq mu4e-split-view 'single-window) (if (buffer-live-p (mu4e-get-view-buffer))
(if (buffer-live-p (mu4e-get-view-buffer)) (mu4e-display-buffer (mu4e-get-view-buffer) t)
(switch-to-buffer (mu4e-get-view-buffer)) (if (buffer-live-p (mu4e-get-headers-buffer))
(if (buffer-live-p (mu4e-get-headers-buffer)) (mu4e-display-buffer (mu4e-get-headers-buffer) t)
(switch-to-buffer (mu4e-get-headers-buffer)) ;; if all else fails, back to the main view
;; if all else fails, back to the main view (when (fboundp 'mu4e) (mu4e)))))
(when (fboundp 'mu4e) (mu4e))))))
(defun mu4e-compose-context-switch (&optional force name) (defun mu4e-compose-context-switch (&optional force name)
"Change the context for the current draft message. "Change the context for the current draft message.
@ -755,10 +756,7 @@ Symbol `edit' is only allowed for draft messages."
;; composing a new message, so that one will be replaced by the compose ;; composing a new message, so that one will be replaced by the compose
;; window. The 10-or-so line headers buffer is not a good place to write ;; window. The 10-or-so line headers buffer is not a good place to write
;; it... ;; it...
(unless (eq mu4e-split-view 'single-window) ;; (mu4e-display-buffer (mu4e-get-view-buffer))
(let ((viewwin (get-buffer-window (mu4e-get-view-buffer))))
(when (window-live-p viewwin)
(select-window viewwin))))
;; talk to the backend ;; talk to the backend
(mu4e--server-compose compose-type decrypt docid))))) (mu4e--server-compose compose-type decrypt docid)))))
@ -798,8 +796,7 @@ draft message."
;;;###autoload ;;;###autoload
(defun mu4e~compose-mail (&optional to subject other-headers _continue (defun mu4e~compose-mail (&optional to subject other-headers _continue
switch-function yank-action yank-action _send-actions _return-action)
_send-actions _return-action)
"This is mu4e's implementation of `compose-mail'. "This is mu4e's implementation of `compose-mail'.
Quoting its docstring: Quoting its docstring:
@ -815,9 +812,6 @@ HEADER and VALUE are strings.
CONTINUE, if non-nil, says to continue editing a message already CONTINUE, if non-nil, says to continue editing a message already
being composed. Interactively, CONTINUE is the prefix argument. being composed. Interactively, CONTINUE is the prefix argument.
SWITCH-FUNCTION, if non-nil, is a function to use to
switch to and display the buffer used for mail composition.
YANK-ACTION, if non-nil, is an action to perform, if and when YANK-ACTION, if non-nil, is an action to perform, if and when
necessary, to insert the raw text of the message being replied necessary, to insert the raw text of the message being replied
to. It has the form (FUNCTION . ARGS). The user agent will apply to. It has the form (FUNCTION . ARGS). The user agent will apply
@ -834,11 +828,11 @@ called after the mail has been sent or put aside, and the mail
buffer buried." buffer buried."
(unless (mu4e-running-p) (unless (mu4e-running-p)
(mu4e)) (mu4e))
;; create a new draft message 'resetting' (as below) is not actually needed in ;; create a new draft message 'resetting' (as below) is not actually needed in
;; this case, but let's prepare for the re-edit case as well ;; this case, but let's prepare for the re-edit case as well
(mu4e~compose-handler 'new nil nil switch-function) (mu4e~compose-handler 'new nil nil)
(when (message-goto-to) ;; reset to-address, if needed (when (message-goto-to) ;; reset to-address, if needed
(message-delete-line)) (message-delete-line))

View File

@ -231,11 +231,6 @@ mu4e-specific version of `message-signature'."
:type 'boolean :type 'boolean
:group 'mu4e-compose) :group 'mu4e-compose)
(defcustom mu4e-compose-in-new-frame nil
"Whether to compose messages in a new frame."
:type 'boolean
:group 'mu4e-compose)
(defvar mu4e-user-agent-string (defvar mu4e-user-agent-string
(format "mu4e %s; emacs %s" mu4e-mu-version emacs-version) (format "mu4e %s; emacs %s" mu4e-mu-version emacs-version)
"The User-Agent string for mu4e, or nil.") "The User-Agent string for mu4e, or nil.")
@ -665,14 +660,9 @@ will be the same as in the original."
"The drafts-folder for this compose buffer. "The drafts-folder for this compose buffer.
This is based on `mu4e-drafts-folder', which is evaluated once.") This is based on `mu4e-drafts-folder', which is evaluated once.")
(defun mu4e~draft-open-file (path switch-function) (defun mu4e~draft-open-file (path)
"Open the the draft file at PATH." "Open the the draft file at PATH."
(let ((buf (find-file-noselect path))) (find-file-noselect path))
(funcall (or
switch-function
(and mu4e-compose-in-new-frame 'switch-to-buffer-other-frame)
'switch-to-buffer)
buf)))
(defun mu4e~draft-determine-path (draft-dir) (defun mu4e~draft-determine-path (draft-dir)
@ -681,7 +671,7 @@ This is based on `mu4e-drafts-folder', which is evaluated once.")
(mu4e-root-maildir) draft-dir (mu4e~draft-message-filename-construct "DS"))) (mu4e-root-maildir) draft-dir (mu4e~draft-message-filename-construct "DS")))
(defun mu4e-draft-open (compose-type &optional msg switch-function) (defun mu4e-draft-open (compose-type &optional msg)
"Open a draft file for a message MSG. "Open a draft file for a message MSG.
In case of a new message (when COMPOSE-TYPE is `reply', `forward' In case of a new message (when COMPOSE-TYPE is `reply', `forward'
or `new'), open an existing draft (when COMPOSE-TYPE is `edit'), or `new'), open an existing draft (when COMPOSE-TYPE is `edit'),
@ -692,8 +682,11 @@ concatenation of `(mu4e-root-maildir)' and `mu4e-drafts-folder' (the
latter will be evaluated). The message file name is a unique name latter will be evaluated). The message file name is a unique name
determined by `mu4e-send-draft-file-name'. The initial contents determined by `mu4e-send-draft-file-name'. The initial contents
will be created from either `mu4e~draft-reply-construct', or will be created from either `mu4e~draft-reply-construct', or
`mu4e~draft-forward-construct' or `mu4e~draft-newmsg-construct'." `mu4e~draft-forward-construct' or `mu4e~draft-newmsg-construct'.
(let ((draft-dir nil))
Returns the newly-created draft buffer."
(let ((draft-dir nil)
(draft-buffer))
(cl-case compose-type (cl-case compose-type
(edit (edit
@ -701,7 +694,7 @@ will be created from either `mu4e~draft-reply-construct', or
;; full path, but we cannot really know 'drafts folder'... we make a ;; full path, but we cannot really know 'drafts folder'... we make a
;; guess ;; guess
(setq draft-dir (mu4e--guess-maildir (mu4e-message-field msg :path))) (setq draft-dir (mu4e--guess-maildir (mu4e-message-field msg :path)))
(mu4e~draft-open-file (mu4e-message-field msg :path) switch-function)) (setq draft-buffer (mu4e~draft-open-file (mu4e-message-field msg :path))))
(resend (resend
;; case-2: copy some exisisting message to a draft message, then edit ;; case-2: copy some exisisting message to a draft message, then edit
@ -709,7 +702,7 @@ will be created from either `mu4e~draft-reply-construct', or
(setq draft-dir (mu4e--guess-maildir (mu4e-message-field msg :path))) (setq draft-dir (mu4e--guess-maildir (mu4e-message-field msg :path)))
(let ((draft-path (mu4e~draft-determine-path draft-dir))) (let ((draft-path (mu4e~draft-determine-path draft-dir)))
(copy-file (mu4e-message-field msg :path) draft-path) (copy-file (mu4e-message-field msg :path) draft-path)
(mu4e~draft-open-file draft-path switch-function))) (setq draft-buffer (mu4e~draft-open-file draft-path))))
((reply forward new) ((reply forward new)
;; case-3: creating a new message; in this case, we can determine ;; case-3: creating a new message; in this case, we can determine
@ -721,7 +714,8 @@ will be created from either `mu4e~draft-reply-construct', or
(reply (mu4e~draft-reply-construct msg)) (reply (mu4e~draft-reply-construct msg))
(forward (mu4e~draft-forward-construct msg)) (forward (mu4e~draft-forward-construct msg))
(new (mu4e~draft-newmsg-construct))))) (new (mu4e~draft-newmsg-construct)))))
(mu4e~draft-open-file draft-path switch-function) (setq draft-buffer (mu4e~draft-open-file draft-path))
(set-buffer draft-buffer)
(insert initial-contents) (insert initial-contents)
(newline) (newline)
;; include the message signature (if it's set) ;; include the message signature (if it's set)
@ -739,7 +733,9 @@ will be created from either `mu4e~draft-reply-construct', or
(set (make-local-variable 'mu4e~draft-drafts-folder) draft-dir) (set (make-local-variable 'mu4e~draft-drafts-folder) draft-dir)
(put 'mu4e~draft-drafts-folder 'permanent-local t) (put 'mu4e~draft-drafts-folder 'permanent-local t)
(unless mu4e~draft-drafts-folder (unless mu4e~draft-drafts-folder
(mu4e-error "Failed to determine drafts folder")))) (mu4e-error "Failed to determine drafts folder"))
;; return the name of the draft buffer
draft-buffer))
;;; _ ;;; _
(provide 'mu4e-draft) (provide 'mu4e-draft)

View File

@ -104,18 +104,7 @@ In the format of `format-time-string'."
:type 'string :type 'string
:group 'mu4e-headers) :group 'mu4e-headers)
(defcustom mu4e-headers-visible-lines 10
"Number of lines to display in the header view when using the
horizontal split-view. This includes the header-line at the top,
and the mode-line."
:type 'integer
:group 'mu4e-headers)
(defcustom mu4e-headers-visible-columns 30
"Number of columns to display for the header view when using the
vertical split-view."
:type 'integer
:group 'mu4e-headers)
(defcustom mu4e-headers-precise-alignment nil (defcustom mu4e-headers-precise-alignment nil
"When set, use precise (but relatively slow) alignment for columns. "When set, use precise (but relatively slow) alignment for columns.
@ -727,9 +716,7 @@ docid is not found."
(defun mu4e~headers-view-this-message-p (docid) (defun mu4e~headers-view-this-message-p (docid)
"Is DOCID currently being viewed?" "Is DOCID currently being viewed?"
(when (buffer-live-p (mu4e-get-view-buffer)) (mu4e-get-view-buffers (lambda (_) (eq docid (plist-get mu4e~view-message :docid)))))
(with-current-buffer (mu4e-get-view-buffer)
(eq docid (plist-get mu4e~view-message :docid)))))
;; note: this function is very performance-sensitive ;; note: this function is very performance-sensitive
(defun mu4e~headers-append-handler (msglst) (defun mu4e~headers-append-handler (msglst)
@ -811,10 +798,10 @@ present, don't do anything."
;; if we were viewing this message, close it now. ;; if we were viewing this message, close it now.
(when (and (mu4e~headers-view-this-message-p docid) (when (and (mu4e~headers-view-this-message-p docid)
(buffer-live-p (mu4e-get-view-buffer))) (buffer-live-p (mu4e-get-view-buffer)))
(unless (eq mu4e-split-view 'single-window) (let ((buf (mu4e-get-view-buffer)))
(mapc #'delete-window (get-buffer-window-list (mapc #'delete-window (get-buffer-window-list
(mu4e-get-view-buffer) nil t))) buf nil t))
(kill-buffer (mu4e-get-view-buffer)))) (kill-buffer buf))))
@ -828,7 +815,7 @@ present, don't do anything."
Switch to the output buffer for the results. If IGNORE-HISTORY is Switch to the output buffer for the results. If IGNORE-HISTORY is
true, do *not* update the query history stack." true, do *not* update the query history stack."
(let* ((buf (get-buffer-create mu4e-headers-buffer-name)) (let* ((buf (mu4e-get-headers-buffer nil t))
(inhibit-read-only t) (inhibit-read-only t)
(rewritten-expr (funcall mu4e-query-rewrite-function expr)) (rewritten-expr (funcall mu4e-query-rewrite-function expr))
(maxnum (unless mu4e-search-full mu4e-search-results-limit))) (maxnum (unless mu4e-search-full mu4e-search-results-limit)))
@ -845,7 +832,7 @@ true, do *not* update the query history stack."
;; when the buffer is already visible, select it; otherwise, ;; when the buffer is already visible, select it; otherwise,
;; switch to it. ;; switch to it.
(unless (get-buffer-window buf 0) (unless (get-buffer-window buf 0)
(switch-to-buffer buf)) (mu4e-display-buffer buf t))
(run-hook-with-args 'mu4e-search-hook expr) (run-hook-with-args 'mu4e-search-hook expr)
(mu4e~headers-clear mu4e~search-message) (mu4e~headers-clear mu4e~search-message)
(setq mu4e~headers-search-start (float-time)) (setq mu4e~headers-search-start (float-time))
@ -1158,7 +1145,8 @@ no user-interaction ongoing."
(when (and mu4e-headers-auto-update ;; must be set (when (and mu4e-headers-auto-update ;; must be set
mu4e-index-update-status mu4e-index-update-status
(not (zerop (plist-get mu4e-index-update-status :updated))) (not (zerop (plist-get mu4e-index-update-status :updated)))
(zerop (mu4e-mark-marks-num)) ;; non active marks ;; NOTE: `mu4e-mark-marks-num' can return nil. Is that intended?
(zerop (or (mu4e-mark-marks-num) 0)) ;; non active marks
(not (active-minibuffer-window))) ;; no user input only (not (active-minibuffer-window))) ;; no user input only
;; rerun search if there's a live window with search results; ;; rerun search if there's a live window with search results;
;; otherwise we'd trigger a headers view from out of nowhere. ;; otherwise we'd trigger a headers view from out of nowhere.
@ -1332,32 +1320,6 @@ message plist, or nil if not found."
"")))))) ""))))))
(defun mu4e~headers-redraw-get-view-window ()
"Close all windows, redraw the headers buffer based on the value
of `mu4e-split-view', and return a window for the message view."
(if (eq mu4e-split-view 'single-window)
(or (and (buffer-live-p (mu4e-get-view-buffer))
(get-buffer-window (mu4e-get-view-buffer)))
(selected-window))
(mu4e-hide-other-mu4e-buffers)
(unless (buffer-live-p (mu4e-get-headers-buffer))
(mu4e-error "No headers buffer available"))
(switch-to-buffer (mu4e-get-headers-buffer))
;; kill the existing view buffer
(when (buffer-live-p (mu4e-get-view-buffer))
(kill-buffer (mu4e-get-view-buffer)))
;; get a new view window
(setq mu4e~headers-view-win
(with-demoted-errors "Unable to split window: %S"
(cond
((eq mu4e-split-view 'horizontal) ;; split horizontally
(split-window-vertically mu4e-headers-visible-lines))
((eq mu4e-split-view 'vertical) ;; split vertically
(split-window-horizontally mu4e-headers-visible-columns))
((functionp mu4e-split-view)
(funcall mu4e-split-view))
(t ;; no splitting; just use the currently selected one
(selected-window)))))))
;;; Search-based marking ;;; Search-based marking
@ -1627,41 +1589,24 @@ _not_ refresh the last search with the new setting for threading."
(mu4e~headers-toggle "Skip-duplicates" (mu4e~headers-toggle "Skip-duplicates"
'mu4e-headers-skip-duplicates dont-refresh)) 'mu4e-headers-skip-duplicates dont-refresh))
(defvar mu4e~headers-loading-buf nil
"A buffer for loading a message view.")
(defun mu4e-headers-view-message () (defun mu4e-headers-view-message ()
"View message at point . "View message at point."
If there's an existing window for the view, re-use that one . If
not, create a new one, depending on the value of
`mu4e-split-view': if it's a symbol `horizontal' or `vertical',
split the window accordingly; if it is nil, replace the current
window . "
(interactive) (interactive)
(unless (eq major-mode 'mu4e-headers-mode) (unless (eq major-mode 'mu4e-headers-mode)
(mu4e-error "Must be in mu4e-headers-mode (%S)" major-mode)) (mu4e-error "Must be in mu4e-headers-mode (%S)" major-mode))
(let* ((msg (mu4e-message-at-point)) (let* ((msg (mu4e-message-at-point))
(path (mu4e-message-field msg :path)) (path (mu4e-message-field msg :path))
(_exists (or (file-readable-p path) (_exists (or (file-readable-p path)
(mu4e-warn "No message at %s" path))) (mu4e-warn "No message at %s" path)))
(docid (or (mu4e-message-field msg :docid) (docid (or (mu4e-message-field msg :docid)
(mu4e-warn "No message at point"))) (mu4e-warn "No message at point")))
(mark-as-read (mark-as-read
(if (functionp mu4e-view-auto-mark-as-read) (if (functionp mu4e-view-auto-mark-as-read)
(funcall mu4e-view-auto-mark-as-read msg) (funcall mu4e-view-auto-mark-as-read msg)
mu4e-view-auto-mark-as-read)) mu4e-view-auto-mark-as-read)))
(viewwin (mu4e~headers-redraw-get-view-window))) (when-let ((buf (mu4e-get-view-buffer (current-buffer) nil)))
(unless (window-live-p viewwin) (with-current-buffer buf
(mu4e-error "Cannot get a message view")) (mu4e-loading-mode 1)))
(select-window viewwin)
;; show some 'loading...' buffer
(unless (buffer-live-p mu4e~headers-loading-buf)
(setq mu4e~headers-loading-buf (get-buffer-create " *mu4e-loading*"))
(with-current-buffer mu4e~headers-loading-buf
(mu4e-loading-mode)))
(switch-to-buffer mu4e~headers-loading-buf)
(mu4e--server-view docid mark-as-read))) (mu4e--server-view docid mark-as-read)))
@ -1692,15 +1637,15 @@ return nil."
;; update all windows showing the headers buffer ;; update all windows showing the headers buffer
(walk-windows (walk-windows
(lambda (win) (lambda (win)
(when (eq (window-buffer win) (mu4e-get-headers-buffer)) (when (eq (window-buffer win) (mu4e-get-headers-buffer (buffer-name)))
(set-window-point win (point)))) (set-window-point win (point))))
nil t) nil t)
(if (eq mu4e-split-view 'single-window) ;; If the assigned (and buffer-local) `mu4e~headers-view-win'
(when (eq (window-buffer) (mu4e-get-view-buffer)) ;; is not live then that is indicates the user does not want
(mu4e-headers-view-message)) ;; to pop up the view when they navigate in the headers
;; update message view if it was already showing ;; buffer.
(when (and mu4e-split-view (window-live-p mu4e~headers-view-win)) (when (window-live-p mu4e~headers-view-win)
(mu4e-headers-view-message))) (mu4e-headers-view-message))
;; attempt to highlight the new line, display the message ;; attempt to highlight the new line, display the message
(mu4e~headers-highlight docid) (mu4e~headers-highlight docid)
(if succeeded (if succeeded
@ -1811,58 +1756,42 @@ region if there is a region, then move to the next message."
This is a rather complex function, to ensure we don't disturb This is a rather complex function, to ensure we don't disturb
other windows." other windows."
(interactive) (interactive)
(if (eq mu4e-split-view 'single-window) (mu4e-mark-handle-when-leaving)
(progn (mu4e-mark-handle-when-leaving) (quit-window t)
(kill-buffer)) (mu4e--main-view 'refresh))
(unless (eq major-mode 'mu4e-headers-mode)
(mu4e-error "Must be in mu4e-headers-mode (%S)" major-mode))
(mu4e-mark-handle-when-leaving)
(let ((curbuf (current-buffer))
(curwin (selected-window)))
(walk-windows
(lambda (win)
(with-selected-window win
;; if we the view window connected to this one, kill it
(when (and (not (one-window-p win)) (eq mu4e~headers-view-win win))
(delete-window win)
(setq mu4e~headers-view-win nil)))
;; and kill any _other_ (non-selected) window that shows the current
;; buffer
(when (and
(eq curbuf (window-buffer win)) ;; does win show curbuf?
(not (eq curwin win)) ;; it's not the curwin?
(not (one-window-p))) ;; and not the last one?
(delete-window win)))) ;; delete it!
;; now, all *other* windows should be gone. kill ourselves, and return
;; to the main view
(kill-buffer)
(mu4e--main-view 'refresh))))
;;; Loading messages ;;; Loading messages
;; ;;
(defvar mu4e-loading-mode-map
(let ((map (make-sparse-keymap)))
(define-key map "n" #'ignore)
(define-key map "p" #'ignore)
(define-key map "q" #'bury-buffer)
map)
"Keymap for *mu4e-loading* buffers.")
(define-derived-mode mu4e-loading-mode special-mode
"mu4e:loading"
(use-local-map mu4e-loading-mode-map)
(let ((inhibit-read-only t))
(erase-buffer)
(insert (propertize "Loading message..."
'face 'mu4e-system-face 'intangible t))))
(defun mu4e~loading-close () (defvar-local mu4e--loading-overlay-bg nil
"Bury the mu4e Loading... buffer, if any." "Internal variable that holds the loading overlay for the background.")
(let* ((buf mu4e~headers-loading-buf)
(win (and (buffer-live-p buf) (get-buffer-window buf t)))) (defvar-local mu4e--loading-overlay-text nil
(when (window-live-p win) "Internal variable that holds the loading overlay for the text.")
(delete-window win))))
(define-minor-mode mu4e-loading-mode
"Minor mode for buffers awaiting data from mu"
:init-value nil :lighter " Loading" :keymap nil
(if mu4e-loading-mode
(progn
(when mu4e-dim-when-loading
(setq mu4e--loading-overlay-bg
(let ((overlay (make-overlay (point-min) (point-max))))
(overlay-put overlay 'face `(:foreground "gray22" :background
,(face-attribute 'default :background)))
(overlay-put overlay 'priority 9998)
overlay)))
(setq mu4e--loading-overlay-text
(let ((overlay (make-overlay (point-min) (point-min))))
(overlay-put overlay 'priority 9999)
(overlay-put overlay 'before-string (propertize "Loading…\n" 'face 'mu4e-header-title-face))
overlay)))
(when mu4e--loading-overlay-bg
(delete-overlay mu4e--loading-overlay-bg))
(when mu4e--loading-overlay-text
(delete-overlay mu4e--loading-overlay-text))))
(provide 'mu4e-headers) (provide 'mu4e-headers)
;;; mu4e-headers.el ends here ;;; mu4e-headers.el ends here

View File

@ -32,6 +32,7 @@
(require 'cl-lib) (require 'cl-lib)
(require 'bookmark) (require 'bookmark)
(require 'mu4e-window)
(require 'mu4e-config) (require 'mu4e-config)
;;; Customization ;;; Customization
@ -86,79 +87,26 @@ marked as read-only, or non-nil otherwise."
:group 'mu4e-view) :group 'mu4e-view)
(defcustom mu4e-split-view 'horizontal
"How to show messages / headers.
A symbol which is either:
* `horizontal': split horizontally (headers on top)
* `vertical': split vertically (headers on the left).
* `single-window': view and headers in one window (mu4e will try not to
touch your window layout), main view in minibuffer
* a function: the function is responsible to return some window for
the view.
* anything else: don't split (show either headers or messages,
not both).
Also see `mu4e-headers-visible-lines'
and `mu4e-headers-visible-columns'."
:type '(choice (const :tag "Split horizontally" horizontal)
(const :tag "Split vertically" vertical)
(const :tag "Single window" single-window)
(const :tag "Don't split" nil))
:group 'mu4e-headers)
;;; Buffers
(defconst mu4e-main-buffer-name " *mu4e-main*"
"Name of the mu4e main buffer.
The default name starts with SPC and therefore is not visible in
buffer list.")
(defconst mu4e-headers-buffer-name "*mu4e-headers*"
"Name of the buffer for message headers.")
(defconst mu4e-embedded-buffer-name " *mu4e-embedded*"
"Name for the embedded message view buffer.")
(defconst mu4e-view-buffer-name "*Article*"
"Name of the view buffer.")
(defun mu4e-get-headers-buffer ()
"Get the buffer object from `mu4e-headers-buffer-name'."
(get-buffer mu4e-headers-buffer-name))
(defun mu4e-get-view-buffer ()
"Get the buffer object from `mu4e-view-buffer-name'."
(get-buffer mu4e-view-buffer-name))
(defun mu4e-select-other-view () (defun mu4e-select-other-view ()
"Switch between headers view and message view." "Switch between headers view and message view."
(interactive) (interactive)
(let* ((other-buf (let* ((other-buf
(cond (cond
((eq major-mode 'mu4e-headers-mode) ((mu4e-current-buffer-type-p 'view)
(mu4e-get-headers-buffer))
((mu4e-current-buffer-type-p 'headers)
(mu4e-get-view-buffer)) (mu4e-get-view-buffer))
((eq major-mode 'mu4e-view-mode) (t (mu4e-error "This window is neither the headers nor the view window."))))
(mu4e-get-headers-buffer))))
(other-win (and other-buf (get-buffer-window other-buf)))) (other-win (and other-buf (get-buffer-window other-buf))))
(if (window-live-p other-win) (if (window-live-p other-win)
(select-window other-win) (select-window other-win)
(mu4e-message "No window to switch to")))) (mu4e-message "No window to switch to"))))
;;; Windows
(defun mu4e-hide-other-mu4e-buffers ()
"Bury mu4e buffers.
Hide (main, headers, view) (and delete all windows displaying
it). Do _not_ bury the current buffer, though."
(interactive)
(unless (eq mu4e-split-view 'single-window)
(let ((curbuf (current-buffer)))
;; note: 'walk-windows' does not seem to work correctly when modifying
;; windows; therefore, the doloops here
(dolist (frame (frame-list))
(dolist (win (window-list frame nil))
(with-current-buffer (window-buffer win)
(unless (eq curbuf (current-buffer))
(when (member major-mode '(mu4e-headers-mode mu4e-view-mode))
(when (eq t (window-deletable-p win))
(delete-window win))))))) t)))
;;; Modeline ;;; Modeline
(defun mu4e-quote-for-modeline (str) (defun mu4e-quote-for-modeline (str)
@ -199,8 +147,6 @@ Create [mu4e]-prefixed error based on format FRM and ARGS. Does a
local-exit and does not return, and raises a local-exit and does not return, and raises a
debuggable (backtrace) error." debuggable (backtrace) error."
(mu4e-log 'error (apply 'mu4e-format frm args)) (mu4e-log 'error (apply 'mu4e-format frm args))
;; opportunistically close the "loading" window.
(mu4e~loading-close)
(error "%s" (apply 'mu4e-format frm args))) (error "%s" (apply 'mu4e-format frm args)))
(defun mu4e-warn (frm &rest args) (defun mu4e-warn (frm &rest args)
@ -406,7 +352,7 @@ log-buffer. See `mu4e-show-log'."
(let ((buf (get-buffer mu4e--log-buffer-name))) (let ((buf (get-buffer mu4e--log-buffer-name)))
(unless (buffer-live-p buf) (unless (buffer-live-p buf)
(mu4e-warn "No debug log available")) (mu4e-warn "No debug log available"))
(switch-to-buffer buf))) (display-buffer buf)))

View File

@ -191,7 +191,7 @@
(funcall action docid original-msg target)) (funcall action docid original-msg target))
(when (and (mu4e~headers-view-this-message-p docid) (when (and (mu4e~headers-view-this-message-p docid)
(buffer-live-p (mu4e-get-view-buffer))) (buffer-live-p (mu4e-get-view-buffer)))
(switch-to-buffer (mu4e-get-view-buffer)) (mu4e-display-buffer (mu4e-get-view-buffer))
(or (mu4e-view-headers-next) (or (mu4e-view-headers-next)
(kill-buffer-and-window)))))) (kill-buffer-and-window))))))

View File

@ -33,6 +33,7 @@
(require 'mu4e-contacts) (require 'mu4e-contacts)
(require 'mu4e-search) (require 'mu4e-search)
(require 'mu4e-vars) ;; mu-wide variables (require 'mu4e-vars) ;; mu-wide variables
(require 'mu4e-window)
(declare-function mu4e-compose-new "mu4e-compose") (declare-function mu4e-compose-new "mu4e-compose")
(declare-function mu4e~headers-jump-to-maildir "mu4e-headers") (declare-function mu4e~headers-jump-to-maildir "mu4e-headers")
@ -371,17 +372,13 @@ When REFRESH is non nil refresh infos from server."
When REFRESH is non nil refresh infos from server." When REFRESH is non nil refresh infos from server."
(let ((buf (get-buffer-create mu4e-main-buffer-name))) (let ((buf (get-buffer-create mu4e-main-buffer-name)))
(if (eq mu4e-split-view 'single-window) ;; `mu4e--main-view' is called from `mu4e--start', so don't call it
(if (buffer-live-p (mu4e-get-headers-buffer)) ;; a second time here i.e. do not refresh unless specified
(switch-to-buffer (mu4e-get-headers-buffer)) ;; explicitly with REFRESH arg.
(mu4e--main-menu)) (mu4e-display-buffer buf t)
;; `mu4e--main-view' is called from `mu4e--start', so don't call it (with-current-buffer buf
;; a second time here i.e. do not refresh unless specified (mu4e--main-view-real-1 refresh))
;; explicitly with REFRESH arg. (goto-char (point-min))))
(switch-to-buffer buf)
(with-current-buffer buf
(mu4e--main-view-real-1 refresh))
(goto-char (point-min)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Interactive functions ;; Interactive functions
@ -420,8 +417,9 @@ When REFRESH is non nil refresh infos from server."
(defun mu4e--main-update-after-index () (defun mu4e--main-update-after-index ()
"Update the main view buffer after indexing." "Update the main view buffer after indexing."
(with-current-buffer mu4e-main-buffer-name (when (buffer-live-p mu4e-main-buffer-name)
(revert-buffer))) (with-current-buffer mu4e-main-buffer-name
(revert-buffer))))
(provide 'mu4e-main) (provide 'mu4e-main)
;;; mu4e-main.el ends here ;;; mu4e-main.el ends here

View File

@ -98,31 +98,23 @@ is the target directory (for \"move\")")
(defun mu4e--mark-find-headers-buffer () (defun mu4e--mark-find-headers-buffer ()
"Find the headers buffer, if any." "Find the headers buffer, if any."
(seq-find (lambda (b) (seq-find (lambda (_)
(with-current-buffer b (mu4e-current-buffer-type-p 'headers))
(eq major-mode 'mu4e-headers-mode)))
(buffer-list))) (buffer-list)))
(defmacro mu4e--mark-in-context (&rest body) (defmacro mu4e--mark-in-context (&rest body)
"Evaluate BODY in the context of the headers buffer. "Evaluate BODY in the context of the headers buffer.
The current buffer must be either a headers or view buffer." The current buffer must be either a headers or view buffer."
`(cond `(cond
((eq major-mode 'mu4e-headers-mode) ,@body) ((mu4e-current-buffer-type-p 'headers) ,@body)
((eq major-mode 'mu4e-view-mode) ((mu4e-current-buffer-type-p 'view)
(when (buffer-live-p (mu4e-get-headers-buffer)) (when (buffer-live-p (mu4e-get-headers-buffer))
(let* ((msg (mu4e-message-at-point)) (let* ((msg (mu4e-message-at-point))
(docid (mu4e-message-field msg :docid))) (docid (mu4e-message-field msg :docid)))
(with-current-buffer (mu4e-get-headers-buffer) (with-current-buffer (mu4e-get-headers-buffer)
(if (mu4e~headers-goto-docid docid) (when (mu4e~headers-goto-docid docid)
,@body ,@body
(mu4e-error "Cannot find message in headers buffer")))))) )))))))
(t
;; even in other modes (e.g. mu4e-main-mode we try to find
;; the headers buffer
(let ((hbuf (mu4e--mark-find-headers-buffer)))
(if (buffer-live-p hbuf)
(with-current-buffer hbuf ,@body)
,@body)))))
(defconst mu4e-marks (defconst mu4e-marks
'((refile '((refile

View File

@ -28,6 +28,7 @@
(require 'mu4e-vars) (require 'mu4e-vars)
(require 'mu4e-contacts) (require 'mu4e-contacts)
(require 'mu4e-window)
(require 'flow-fill) (require 'flow-fill)
(require 'shr) (require 'shr)
(require 'pp) (require 'pp)
@ -206,18 +207,20 @@ If MSG is nil, use `mu4e-message-at-point'."
(kill-new path) (kill-new path)
(mu4e-message "Saved '%s' to kill-ring" path))) (mu4e-message "Saved '%s' to kill-ring" path)))
(defconst mu4e--sexp-buffer-name " *mu4e-sexp-at-point"
"Buffer name for sexp buffers.")
(defun mu4e-sexp-at-point () (defun mu4e-sexp-at-point ()
"Show or hide the s-expression for the message-at-point, if any." "Show or hide the s-expression for the message-at-point, if any."
(interactive) (interactive)
(if-let ((win (get-buffer-window mu4e--sexp-buffer-name))) (if-let ((win (get-buffer-window mu4e--sexp-buffer-name)))
(delete-window win) (delete-window win)
(when-let ((msg (mu4e-message-at-point 'noerror))) (when-let ((msg (mu4e-message-at-point 'noerror)))
(with-current-buffer-window mu4e--sexp-buffer-name nil nil (when (buffer-live-p mu4e--sexp-buffer-name)
;; the "pretty-printing" is not very pretty... (kill-buffer mu4e--sexp-buffer-name))
(insert (pp-to-string msg)))))) (with-current-buffer-window (get-buffer-create mu4e--sexp-buffer-name) nil nil
(lisp-data-mode)
(insert (pp-to-string msg))
(font-lock-ensure)
;; add basic `quit-window' bindings
(view-mode 1)))))
;;; ;;;
(provide 'mu4e-message) (provide 'mu4e-message)

View File

@ -211,21 +211,22 @@ if you otherwise want to use `mu4e-index-lazy-check'."
(defvar mu4e--update-buffer nil (defvar mu4e--update-buffer nil
"The buffer of the update process when updating.") "The buffer of the update process when updating.")
(define-derived-mode mu4e--update-mail-mode (define-derived-mode mu4e--update-mail-mode special-mode "mu4e:update"
special-mode "mu4e:update"
"Major mode used for retrieving new e-mail messages in `mu4e'.") "Major mode used for retrieving new e-mail messages in `mu4e'.")
(define-key mu4e--update-mail-mode-map (kbd "q") 'mu4e-kill-update-mail) (define-key mu4e--update-mail-mode-map (kbd "q") 'mu4e-kill-update-mail)
(defun mu4e--temp-window (buf height) (defun mu4e--temp-window (buf height)
"Create a temporary window with HEIGHT at the bottom BUF." "Create a temporary window with HEIGHT at the bottom BUF.
(let ((win
(split-window This function uses `display-buffer' with a default preset.
(frame-root-window)
(- (window-height (frame-root-window)) height)))) To override this behavior, customize `display-buffer-alist'."
(set-window-buffer win buf) (display-buffer buf `(display-buffer-at-bottom
(set-window-dedicated-p win t) (preserve-size . (nil . t))
win)) (height . ,height)
(window-height . fit-window-to-buffer)))
(set-window-buffer (get-buffer-window buf) buf))
(defun mu4e--update-sentinel-func (proc _msg) (defun mu4e--update-sentinel-func (proc _msg)
"Sentinel function for the update process PROC." "Sentinel function for the update process PROC."
@ -244,8 +245,7 @@ special-mode "mu4e:update"
(mu4e-update-index))) (mu4e-update-index)))
(mu4e-update-index)) (mu4e-update-index))
(when (buffer-live-p mu4e--update-buffer) (when (buffer-live-p mu4e--update-buffer)
(unless (eq mu4e-split-view 'single-window) (delete-windows-on mu4e--update-buffer t)
(mapc #'delete-window (get-buffer-window-list mu4e--update-buffer)))
(kill-buffer mu4e--update-buffer))) (kill-buffer mu4e--update-buffer)))
;; complicated function, as it: ;; complicated function, as it:
@ -268,8 +268,6 @@ run in the background; otherwise, pop up a window."
(setq mu4e--update-buffer buf) (setq mu4e--update-buffer buf)
(when (window-live-p win) (when (window-live-p win)
(with-selected-window win (with-selected-window win
;; ;;(switch-to-buffer buf)
;; (set-window-dedicated-p win t)
(erase-buffer) (erase-buffer)
(insert "\n") ;; FIXME -- needed so output starts (insert "\n") ;; FIXME -- needed so output starts
(mu4e--update-mail-mode))) (mu4e--update-mail-mode)))

View File

@ -56,6 +56,12 @@ Follows the format of `format-time-string'."
:type 'string :type 'string
:group 'mu4e) :group 'mu4e)
(defcustom mu4e-dim-when-loading t
"Dim buffer text when loading new data.
If non-nil, dim some buffers during data retrieval and rendering."
:type 'boolean
:group 'mu4e)
;;; Faces ;;; Faces
@ -355,7 +361,7 @@ header-view, not including, for instance, the message body.")
;;; Internals ;;; Internals
(defvar mu4e~headers-view-win nil (defvar-local mu4e~headers-view-win nil
"The view window connected to this headers view.") "The view window connected to this headers view.")
;; It's useful to have the current view message available to ;; It's useful to have the current view message available to
@ -363,7 +369,7 @@ header-view, not including, for instance, the message body.")
;; before calling `mu4e-view-mode'. However, changing the major mode ;; before calling `mu4e-view-mode'. However, changing the major mode
;; clobbers any local variables. Work around that by declaring the ;; clobbers any local variables. Work around that by declaring the
;; variable permanent-local. ;; variable permanent-local.
(defvar mu4e~view-message nil "The message being viewed in view mode.") (defvar-local mu4e~view-message nil "The message being viewed in view mode.")
(put 'mu4e~view-message 'permanent-local t) (put 'mu4e~view-message 'permanent-local t)
;;; _ ;;; _
(provide 'mu4e-vars) (provide 'mu4e-vars)

View File

@ -112,6 +112,10 @@ specified a function as viewer."
:type 'integer :type 'integer
:group 'mu4e-view) :group 'mu4e-view)
(defcustom mu4e-after-view-message-hook '(mu4e-resize-linked-headers-window)
"Hook run by `mu4e-view' after a message is rendered."
:type 'hook
:group 'mu4e-view)
@ -122,42 +126,7 @@ specified a function as viewer."
This is a rather complex function, to ensure we don't disturb This is a rather complex function, to ensure we don't disturb
other windows." other windows."
(interactive) (interactive)
(if (eq mu4e-split-view 'single-window) (quit-window t))
(when (buffer-live-p (mu4e-get-view-buffer))
(kill-buffer (mu4e-get-view-buffer)))
(unless (eq major-mode 'mu4e-view-mode)
(mu4e-error "Must be in mu4e-view-mode (%S)" major-mode))
(let ((curbuf (current-buffer))
(curwin (selected-window))
(headers-win))
(walk-windows
(lambda (win)
;; check whether the headers buffer window is visible
(when (eq (mu4e-get-headers-buffer) (window-buffer win))
(setq headers-win win))
;; and kill any _other_ (non-selected) window that shows the current
;; buffer
(when
(and
(eq curbuf (window-buffer win)) ;; does win show curbuf?
(not (eq curwin win)) ;; but it's not the curwin?
(not (one-window-p))) ;; and not the last one on the frame?
(delete-window win)))) ;; delete it!
;; now, all *other* windows should be gone.
;; if the headers view is also visible, kill ourselves + window; otherwise
;; switch to the headers view
(if (window-live-p headers-win)
;; headers are visible
(progn
(kill-buffer-and-window) ;; kill the view win
(setq mu4e~headers-view-win nil)
(select-window headers-win)) ;; and switch to the headers win...
;; headers are not visible...
(progn
(kill-buffer)
(setq mu4e~headers-view-win nil)
(when (buffer-live-p (mu4e-get-headers-buffer))
(switch-to-buffer (mu4e-get-headers-buffer))))))))
(defconst mu4e~view-raw-buffer-name " *mu4e-raw-view*" (defconst mu4e~view-raw-buffer-name " *mu4e-raw-view*"
@ -171,10 +140,10 @@ other windows."
(with-current-buffer buf (with-current-buffer buf
(let ((inhibit-read-only t)) (let ((inhibit-read-only t))
(erase-buffer) (erase-buffer)
(insert-file-contents path) (mu4e-raw-view-mode)
(view-mode) (insert-file-contents path)
(goto-char (point-min)))) (goto-char (point-min))))
(switch-to-buffer buf))) (mu4e-display-buffer buf t)))
(defun mu4e-view-pipe (cmd) (defun mu4e-view-pipe (cmd)
"Pipe the message at point through shell command CMD. "Pipe the message at point through shell command CMD.
@ -186,17 +155,36 @@ Then, display the results."
(defmacro mu4e~view-in-headers-context (&rest body) (defmacro mu4e~view-in-headers-context (&rest body)
"Evaluate BODY in the context of the headers buffer." "Evaluate BODY in the context of the headers buffer."
`(progn `(progn
(unless (buffer-live-p (mu4e-get-headers-buffer))
(mu4e-error "No headers buffer connected"))
(let* ((msg (mu4e-message-at-point)) (let* ((msg (mu4e-message-at-point))
(buffer (cond
;; are we already inside a headers buffer?
((mu4e-current-buffer-type-p 'headers) (current-buffer))
;; if not, are we inside a view buffer, and does
;; it have linked headers buffer?
((mu4e-current-buffer-type-p 'view)
(when (mu4e--view-detached-p (current-buffer))
(mu4e-error "You cannot navigate in a detached view buffer."))
(mu4e-get-headers-buffer))
;; fallback; but what would trigger this?
(t (mu4e-get-headers-buffer))))
(docid (mu4e-message-field msg :docid))) (docid (mu4e-message-field msg :docid)))
(unless docid (unless docid
(mu4e-error "Message without docid: action is not possible")) (mu4e-error "Message without docid: action is not possible"))
(with-current-buffer (mu4e-get-headers-buffer) (with-current-buffer buffer
(unless (eq mu4e-split-view 'single-window) (mu4e-display-buffer buffer)
(when (get-buffer-window) (if (or (mu4e~headers-goto-docid docid)
(select-window (get-buffer-window)))) ;; TODO: Is this the best way to find another
(if (mu4e~headers-goto-docid docid) ;; relevant docid for a view buffer?
;;
;; If you attach a view buffer to another headers
;; buffer that does not contain the current docid
;; then `mu4e~headers-goto-docid' returns nil and we
;; get an error. This "hack" instead gets its
;; now-changed headers buffer's current message as a
;; docid
(mu4e~headers-goto-docid
(with-current-buffer buffer
(mu4e-message-field (mu4e-message-at-point) :docid))))
,@body ,@body
(mu4e-error "Cannot find message in headers buffer")))))) (mu4e-error "Cannot find message in headers buffer"))))))
@ -226,12 +214,8 @@ message view. If this succeeds, return the new docid. Otherwise,
return nil." return nil."
(mu4e~view-in-headers-context (mu4e~view-in-headers-context
(mu4e~headers-prev-or-next-unread backwards)) (mu4e~headers-prev-or-next-unread backwards))
(if (eq mu4e-split-view 'single-window) (mu4e-select-other-view)
(when (eq (window-buffer) (mu4e-get-view-buffer)) (mu4e-headers-view-message))
(with-current-buffer (mu4e-get-headers-buffer)
(mu4e-headers-view-message)))
(mu4e-select-other-view)
(mu4e-headers-view-message)))
(defun mu4e-view-headers-prev-unread () (defun mu4e-view-headers-prev-unread ()
"Move point to the previous unread message header. "Move point to the previous unread message header.
@ -336,6 +320,31 @@ Add this function to `mu4e-view-mode-hook' to enable this feature."
"Return t if we're in split-view, nil otherwise." "Return t if we're in split-view, nil otherwise."
(member mu4e-split-view '(horizontal vertical))) (member mu4e-split-view '(horizontal vertical)))
(defun mu4e-view-detach ()
"Detach the view buffer from its headers buffer."
(interactive)
(unless mu4e-linked-headers-buffer
(mu4e-error "This view buffer is already detached."))
(mu4e-message "Detached view buffer from %s"
(prog1 mu4e-linked-headers-buffer
(with-current-buffer mu4e-linked-headers-buffer
(when (eq (selected-window) mu4e~headers-view-win)
(setq mu4e~headers-view-win nil)))
(setq mu4e-linked-headers-buffer nil))))
(defun mu4e-view-attach (headers-buffer)
"Attaches a view buffer to a headers buffer."
(interactive
(list (get-buffer (read-buffer
"Select a headers buffer to attach to: " nil t
(lambda (buf) (with-current-buffer (car buf)
(mu4e-current-buffer-type-p 'headers)))))))
(mu4e-message "Attached view buffer to %s" headers-buffer)
(setq mu4e-linked-headers-buffer headers-buffer)
(with-current-buffer headers-buffer
(setq mu4e~headers-view-win (selected-window))))
;;; Scroll commands ;;; Scroll commands
(defun mu4e-view-scroll-up-or-next () (defun mu4e-view-scroll-up-or-next ()
@ -599,27 +608,54 @@ in the the message view affects HDRSBUF, as does marking etc.
As a side-effect, a message that is being viewed loses its As a side-effect, a message that is being viewed loses its
`unread' marking if it still had that." `unread' marking if it still had that."
;; update headers, if necessary.
(mu4e~headers-update-handler msg nil nil) ;; update headers, if necessary. (mu4e~headers-update-handler msg nil nil)
;; Create a new view buffer (if needed) as it is not
(when (bufferp gnus-article-buffer) ;; feasible to recycle an existing buffer due to buffer-specific
(kill-buffer gnus-article-buffer)) ;; state (buttons, etc.) that can interfere with message rendering
(setq gnus-article-buffer mu4e-view-buffer-name) ;; in gnus.
(with-current-buffer (get-buffer-create gnus-article-buffer) ;;
(let ((inhibit-read-only t)) ;; Unfortunately that does create its own issues: namely ensuring
(remove-overlays (point-min)(point-max) 'mu4e-overlay t) ;; buffer-local state that *must* survive is correctly copied
(erase-buffer) ;; across.
(insert-file-contents-literally (let ((linked-headers-buffer))
(mu4e-message-readable-path msg) nil nil nil t))) (when-let ((existing-buffer (mu4e-get-view-buffer nil nil)))
(switch-to-buffer gnus-article-buffer) ;; required; this state must carry over from the killed buffer
(setq mu4e~view-message msg) ;; to the new one.
(mu4e~view-render-buffer msg)) (setq linked-headers-buffer mu4e-linked-headers-buffer)
(delete-windows-on existing-buffer t)
(kill-buffer existing-buffer))
(setq gnus-article-buffer (mu4e-get-view-buffer nil t))
(with-current-buffer gnus-article-buffer
(when linked-headers-buffer
(setq mu4e-linked-headers-buffer linked-headers-buffer))
(let ((inhibit-read-only t))
(remove-overlays (point-min)(point-max) 'mu4e-overlay t)
(erase-buffer)
(insert-file-contents-literally
(mu4e-message-readable-path msg) nil nil nil t)
(setq-local mu4e~view-message msg)
(mu4e~view-render-buffer msg))
(mu4e-loading-mode 0)))
(unless (mu4e--view-detached-p gnus-article-buffer)
(with-current-buffer mu4e-linked-headers-buffer
;; We need this here as we want to avoid displaying the buffer until
;; the last possible moment --- after the message is rendered in the
;; view buffer.
;;
;; Otherwise, `mu4e-display-buffer' may adjust the view buffer's
;; window height based on a buffer that has no text in it yet!
(setq-local mu4e~headers-view-win (mu4e-display-buffer gnus-article-buffer nil))
(unless (window-live-p mu4e~headers-view-win)
(mu4e-error "Cannot get a message view"))
(select-window mu4e~headers-view-win)))
(with-current-buffer gnus-article-buffer
(run-hooks 'mu4e-after-view-message-hook)))
(defun mu4e-view-message-text (msg) (defun mu4e-view-message-text (msg)
"Return the pristine MSG as a string." "Return the pristine MSG as a string."
;; we need this for replying/forwarding, since the mu4e-compose ;; we need this for replying/forwarding, since the mu4e-compose
;; wants it that way. ;; wants it that way.
(with-temp-buffer (with-temp-buffer
(insert-file-contents-literally (insert-file-contents-literally
(mu4e-message-readable-path msg) nil nil nil t) (mu4e-message-readable-path msg) nil nil nil t)
@ -686,7 +722,6 @@ determine which browser function to use."
(condition-case err (condition-case err
(progn (progn
(mm-enable-multibyte) (mm-enable-multibyte)
(mu4e-view-mode)
(run-hooks 'gnus-article-decode-hook) (run-hooks 'gnus-article-decode-hook)
(gnus-article-prepare-display) (gnus-article-prepare-display)
(mu4e~view-activate-urls) (mu4e~view-activate-urls)
@ -826,9 +861,8 @@ This is useful for advising some Gnus-functionality that does not work in mu4e."
(define-key map "q" #'mu4e~view-quit-buffer) (define-key map "q" #'mu4e~view-quit-buffer)
;; note, 'z' is by-default bound to 'bury-buffer' (define-key map "z" #'mu4e-view-detach)
;; but that's not very useful in this case (define-key map "Z" #'mu4e-view-attach)
(define-key map "z" #'ignore)
(define-key map "%" #'mu4e-view-mark-pattern) (define-key map "%" #'mu4e-view-mark-pattern)
(define-key map "t" #'mu4e-view-mark-subthread) (define-key map "t" #'mu4e-view-mark-subthread)
@ -985,12 +1019,21 @@ This is useful for advising some Gnus-functionality that does not work in mu4e."
(set-keymap-parent mu4e-view-mode-map button-buffer-map) (set-keymap-parent mu4e-view-mode-map button-buffer-map)
(defcustom mu4e-raw-view-mode-hook nil
"Hook run when entering \\[mu4e-raw-view] mode."
:options '()
:type 'hook
:group 'mu4e-view)
(defcustom mu4e-view-mode-hook nil (defcustom mu4e-view-mode-hook nil
"Hook run when entering Mu4e-View mode." "Hook run when entering \\[mu4e-view] mode."
:options '(turn-on-visual-line-mode) :options '(turn-on-visual-line-mode)
:type 'hook :type 'hook
:group 'mu4e-view) :group 'mu4e-view)
(define-derived-mode mu4e-raw-view-mode fundamental-mode "mu4e:raw-view"
(view-mode))
;; "Define the major-mode for the mu4e-view." ;; "Define the major-mode for the mu4e-view."
(define-derived-mode mu4e-view-mode gnus-article-mode "mu4e:view" (define-derived-mode mu4e-view-mode gnus-article-mode "mu4e:view"
"Major mode for viewing an e-mail message in mu4e. "Major mode for viewing an e-mail message in mu4e.
@ -1150,11 +1193,11 @@ containing commas."
(:name "raw" :handler (lambda (str) (:name "raw" :handler (lambda (str)
(let ((tmpbuf (let ((tmpbuf
(get-buffer-create " *mu4e-raw-mime*"))) (get-buffer-create " *mu4e-raw-mime*")))
(with-current-buffer tmpbuf (with-current-buffer tmpbuf
(insert str) (insert str)
(view-mode) (view-mode)
(goto-char (point-min))) (goto-char (point-min)))
(switch-to-buffer tmpbuf))) :receives pipe)) (display-buffer tmpbuf))) :receives pipe))
"Specifies actions for MIME-parts. "Specifies actions for MIME-parts.
@ -1274,7 +1317,7 @@ the third MIME-part."
(erase-buffer) (erase-buffer)
(call-process-shell-command pipecmd path t t) (call-process-shell-command pipecmd path t t)
(view-mode))) (view-mode)))
(switch-to-buffer buf))) (display-buffer buf)))
;;; Bug Reference mode support ;;; Bug Reference mode support

308
mu4e/mu4e-window.el Normal file
View File

@ -0,0 +1,308 @@
;;; mu4e-window.el --- window management for mu4e -*- lexical-binding: t; -*-
;; Copyright (C) 2022 Mickey Petersen
;; Author: Mickey Petersen <mickey@masteringemacs.org>
;; Keywords: mail
;; 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:
;;; Code:
;;; Buffer names for internal use
(defconst mu4e--sexp-buffer-name "*mu4e-sexp-at-point*"
"Buffer name for sexp buffers.")
(defvar mu4e-main-buffer-name " *mu4e-main*"
"Name of the mu4e main buffer.
The default name starts with SPC and therefore is not visible in
buffer list.")
(defvar mu4e-embedded-buffer-name " *mu4e-embedded*"
"Name for the embedded message view buffer.")
;; Buffer names for public use
(defvar mu4e-headers-buffer-name "*mu4e-headers*"
"Name of the buffer for message headers.")
(defvar mu4e-view-buffer-name "*mu4e-article*"
"Name of the view buffer.")
(defvar mu4e-headers-buffer-name-func nil
"Function used to name the headers buffers.")
(defvar mu4e-view-buffer-name-func nil
"Function used to name the view buffers.
The function is given one argument, the headers buffer it is
linked to.")
(defvar-local mu4e-linked-headers-buffer nil
"Holds the headers buffer object that ties it to a view.")
(defcustom mu4e-split-view 'horizontal
"How to show messages / headers.
A symbol which is either:
* `horizontal': split horizontally (headers on top)
* `vertical': split vertically (headers on the left).
* `single-window': view and headers in one window (mu4e will try not to
touch your window layout), main view in minibuffer
* a function: the function is responsible to return some window for
the view.
* anything else: don't split (show either headers or messages,
not both).
Also see `mu4e-headers-visible-lines'
and `mu4e-headers-visible-columns'."
:type '(choice (const :tag "Split horizontally" horizontal)
(const :tag "Split vertically" vertical)
(const :tag "Single window" single-window)
(const :tag "Don't split" nil))
:group 'mu4e-headers)
(defcustom mu4e-compose-in-new-frame nil
"Whether to compose messages in a new frame."
:type 'boolean
:group 'mu4e-compose)
(defcustom mu4e-headers-visible-lines 10
"Number of lines to display in the header view when using the
horizontal split-view. This includes the header-line at the top,
and the mode-line."
:type 'integer
:group 'mu4e-headers)
(defcustom mu4e-headers-visible-columns 30
"Number of columns to display for the header view when using the
vertical split-view."
:type 'integer
:group 'mu4e-headers)
(declare-function mu4e-view-mode "mu4e-view")
(defun mu4e-get-headers-buffer (&optional buffer-name create)
"Return a related headers buffer optionally named BUFFER-NAME.
If CREATE is non-nil, the headers buffer is created if the
generated name does not already exist."
(let* ((buffer-name
(or
;; buffer name generator func. If a user wants
;; to supply its own naming scheme, we use that
;; in lieu of our own heuristic.
(and mu4e-headers-buffer-name-func
(funcall mu4e-headers-buffer-name-func))
;; if we're supplied a buffer name for a
;; headers buffer then try to use that one.
buffer-name
;; if we're asking for a headers buffer from a
;; view, then we get our linked buffer. If
;; there is no such linked buffer -- it is
;; detached -- raise an error.
(and (mu4e-current-buffer-type-p 'view)
mu4e-linked-headers-buffer)
;; if we're already in a headers buffer then
;; that is the one we use.
(and (mu4e-current-buffer-type-p 'headers)
(current-buffer))
;; default name to use if all other checks fail.
mu4e-headers-buffer-name))
(buffer (get-buffer buffer-name)))
(when (and (not (buffer-live-p buffer)) create)
(setq buffer (get-buffer-create buffer-name)))
;; This may conceivably return a non-existent buffer if `create'
;; and `buffer-live-p' are nil.
;;
;; This is seemingly "OK" as various parts of the code check for
;; buffer liveness themselves.
buffer))
(defun mu4e-get-view-buffers (pred)
"Filter all known view buffers and keep those where PRED return non-nil.
The PRED function is called from inside the buffer that is being
tested."
(seq-filter
(lambda (buf)
(with-current-buffer buf
(and (mu4e-current-buffer-type-p 'view)
(and pred (funcall pred buf)))))
(buffer-list)))
(defun mu4e--view-detached-p (buffer)
"Return non-nil if BUFFER is a detached view buffer."
(with-current-buffer buffer
(unless (mu4e-current-buffer-type-p 'view)
(error "Buffer `%s' is not a valid mu4e view buffer" buffer))
(null mu4e-linked-headers-buffer)))
(defun mu4e--get-current-buffer-type ()
"Return an internal symbol that corresponds to each mu4e major mode."
(cond ((derived-mode-p 'mu4e-view-mode 'mu4e-raw-view-mode) 'view)
((derived-mode-p 'mu4e-headers-mode) 'headers)
((derived-mode-p 'mu4e-compose-mode) 'compose)
((derived-mode-p 'mu4e-main-mode) 'main)
(t 'unknown)))
(defun mu4e-current-buffer-type-p (type)
"Return non-nil if the current buffer is a mu4e buffer of TYPE.
Where TYPE is `view', `headers', `compose', `main' or `unknown'.
Checks are performed using `derived-mode-p' and the current
buffer's major mode."
(eq (mu4e--get-current-buffer-type) type))
(defun mu4e-get-view-buffer (&optional headers-buffer create)
"Return a view buffer belonging optionally to HEADERS-BUFFER.
If HEADERS-BUFFER is nil, the most likely (and available) headers
buffer is used.
Detached view buffers are ignored; that may result in a new view buffer
being created if CREATE is non-nil."
;; If `headers-buffer' is nil, then the caller does not have a
;; headers buffer preference.
;;
;; In that case, we request the most plausible headers buffer from
;; `mu4e-get-headers-buffer'.
(when (setq headers-buffer (or headers-buffer (mu4e-get-headers-buffer)))
(let ((buffer)
;; If `mu4e-view-buffer-name-func' is non-nil, then use that
;; to source the name of the view buffer to create or re-use.
(buffer-name (or (and mu4e-view-buffer-name-func
(funcall mu4e-view-buffer-name-func headers-buffer))
;; If the variable is nil, use the default
;; name
mu4e-view-buffer-name))
;; Search all view buffers and return those that are linked to
;; `headers-buffer'.
(linked-buffer (mu4e-get-view-buffers
(lambda (buf) (and (buffer-local-boundp 'mu4e-linked-headers-buffer buf)
(eq mu4e-linked-headers-buffer headers-buffer))))))
;; If such a linked buffer exists and its buffer is live, we use that buffer.
(if (and linked-buffer (buffer-live-p (car linked-buffer)))
;; NOTE: It's possible for there to be more than one linked view buffer.
;;
;; What, if anything, should the heuristic be to pick the
;; one to use? Presently `car' is used, but there are better
;; ways, no doubt. Perhaps preferring those with live windows?
(setq buffer (car linked-buffer))
(setq buffer (get-buffer buffer-name))
;; check if `buffer' is already live *and* detached. If it is,
;; we'll generate a new, unique name.
(when (and (buffer-live-p buffer) (mu4e--view-detached-p buffer))
(setq buffer (generate-new-buffer-name buffer-name)))
(when (or (not (buffer-live-p buffer)) create)
(setq buffer (get-buffer-create (or buffer buffer-name)))
(with-current-buffer buffer
(mu4e-view-mode))))
(when (and buffer (buffer-live-p buffer))
;; Required. Callers expect the view buffer to be set.
(set-buffer buffer)
;; Required. The call chain of `mu4e-view-mode' ends up
;; calling `kill-all-local-variables', which destroys the
;; local binding.
(set (make-local-variable 'mu4e-linked-headers-buffer) headers-buffer))
buffer)))
(defun mu4e-display-buffer (buffer-or-name &optional select)
"Display BUFFER-OR-NAME as per `mu4e-split-view'.
If SELECT is non-nil, the final window (and thus BUFFER-OR-NAME)
is selected.
This function internally uses `display-buffer' (or
`pop-to-buffer' if SELECT is non-nil).
It is therefore possible to change the display behavior by
modifying `display-buffer-alist'.
If `mu4e-split-view' is a function, then it must return a live window
for BUFFER-OR-NAME to be displayed in."
(if (functionp mu4e-split-view)
(set-window-buffer (funcall mu4e-split-view) buffer-or-name)
(let* ((buffer-name (or (get-buffer buffer-or-name)
(error "Buffer `%s' does not exist" buffer-or-name)))
(buffer-type (with-current-buffer buffer-name (mu4e--get-current-buffer-type)))
(direction (cons 'direction
(pcase (cons buffer-type mu4e-split-view)
;; views or headers can display
;; horz/vert depending on the value of
;; `mu4e-split-view'
(`(,(or 'view 'headers) . horizontal) 'below)
(`(,(or 'view 'headers) . vertical) 'right)
(`(,_ . t) nil))))
(window-size
(pcase (cons buffer-type mu4e-split-view)
;; views or headers can display
;; horz/vert depending on the value of
;; `mu4e-split-view'
('(view . horizontal) '((window-height . shrink-window-if-larger-than-buffer)))
('(view . vertical) '((window-min-width . fit-window-to-buffer)))
(`(,_ . t) nil)))
(window-action (cond
((and (eq buffer-type 'compose) mu4e-compose-in-new-frame)
'(display-buffer-pop-up-frame))
((memq buffer-type '(headers compose))
'(display-buffer-reuse-mode-window display-buffer-same-window))
((memq mu4e-split-view '(horizontal vertical))
'(display-buffer-in-direction))
((memq mu4e-split-view '(single-window))
'(display-buffer-same-window))
;; I cannot discern a difference between
;; `single-window' and "anything else" in
;; `mu4e-split-view'.
(t '(display-buffer-same-window))))
(arg `((display-buffer-reuse-window
display-buffer-reuse-mode-window
,@window-action)
,@window-size
,direction
)))
(funcall (if select #'pop-to-buffer #'display-buffer)
buffer-name
arg))))
(defun mu4e-resize-linked-headers-window ()
"Resizes the linked headers window belonging to a view.
Resizes the current headers view according to `mu4e-split-view'
and `mu4e-headers-visible-lines' or
`mu4e-headers-visible-columns'.
This function is best called from the hook
`mu4e-after-view-message-hook'."
(unless (mu4e-current-buffer-type-p 'view)
(error "Cannot resize as this is not a valid view buffer."))
(when-let (win (and mu4e-linked-headers-buffer
(get-buffer-window mu4e-linked-headers-buffer)))
;; This can fail for any number of reasons. If it does, we do
;; nothing. If the user has customized the window display we may
;; find it impossible to resize the window, and that should not be
;; cause for error.
(ignore-errors
(cond ((eq mu4e-split-view 'vertical)
(window-resize win (- mu4e-headers-visible-columns (window-width win nil))
t t nil))
((eq mu4e-split-view 'horizontal)
(set-window-text-height win mu4e-headers-visible-lines))))))
(provide 'mu4e-window)
;;; mu4e-window.el ends here

View File

@ -26,6 +26,7 @@
;;; Code: ;;; Code:
(require 'mu4e-vars) (require 'mu4e-vars)
(require 'mu4e-window)
(require 'mu4e-obsolete) (require 'mu4e-obsolete)
(require 'mu4e-helpers) (require 'mu4e-helpers)
(require 'mu4e-folders) (require 'mu4e-folders)

View File

@ -837,7 +837,7 @@ major-mode for the headers view is @code{mu4e-headers-mode}.
* Sorting and threading::Influencing the display * Sorting and threading::Influencing the display
* Custom headers: HV Custom headers. Adding your own headers * Custom headers: HV Custom headers. Adding your own headers
* Actions: HV Actions. Defining and using actions * Actions: HV Actions. Defining and using actions
* Split view::Seeing both headers and messages * Controlling and Displaying Buffers:: How and where the buffers are displayed
@end menu @end menu
@node HV Overview @node HV Overview
@ -1117,14 +1117,24 @@ attachment, using @code{mu4e-compose-attach-captured-message}. See
@file{mu4e-actions.el} in the @t{mu4e} source distribution for more example @file{mu4e-actions.el} in the @t{mu4e} source distribution for more example
actions. actions.
@node Split view @node Controlling and Displaying Buffers
@section Split view @section Display
Using the @emph{Split view}, we can see the @ref{Headers view} and the By default, @t{mu4e} will attempt to manage the display of its own
@ref{Message view} next to each other, with the message selected in the buffers. To do this, the variable @code{mu4e-split-view} is used to determine
former, visible in the latter. You can influence the way the splitting how, or where, windows and buffers are placed.
is done by customizing the variable @code{mu4e-split-view}. Possible
values are: However, @t{mu4e}'s display rules are provisional; you can override them
easily by customizing @code{display-buffer-alist}, which governs how Emacs --
and thus @t{mu4e} -- must display your buffers.
That means you can instruct @t{mu4e} to place message views in separate tabs
or frames, if you so desire.
@section Split view
You can control how @t{mu4e} displays its buffers, including the @ref{Headers
view} and the @ref{Message view}, by customizing
@code{mu4e-split-view}. There are several options available:
@itemize @itemize
@item @t{horizontal} (this is the default): display the message view below the @item @t{horizontal} (this is the default): display the message view below the
@ -1138,7 +1148,9 @@ minimize mu4e window operations (opening, killing, resizing, etc) and
buffer changes, while still retaining the view and headers buffers. In buffer changes, while still retaining the view and headers buffers. In
addition, it replaces mu4e main view with a minibuffer prompt containing addition, it replaces mu4e main view with a minibuffer prompt containing
the same information. the same information.
@item anything else: don't do any splitting @item @t{function}: a function that takes a buffer name and returns a
window to display the buffer in.
@item anything else: prefer reusing the same window, if possible.
@end itemize @end itemize
@noindent @noindent
@ -1151,6 +1163,26 @@ headers-view to the message-view and vice-versa with
@code{mu4e-select-other-view}, bound to @key{y} @code{mu4e-select-other-view}, bound to @key{y}
@end itemize @end itemize
@section Display Buffer Example
Here are a couple of examples that override @t{mu4e}'s default buffer
placement. You do not need to configure @code{mu4e-split-view} for this to
work. In the absence of explicit rules to the contrary, @t{mu4e} will fall
back on the value you have set in @code{mu4e-split-view} @emph{unless} you
have assigned your own custom window function.
Here is an example that displays the headers buffer in a side window to the
right. It occupies half of the width of the frame.
@lisp
(add-to-list 'display-buffer-alist
'(("\\*mu4e-headers\\*" display-buffer-in-side-window
(side . right)
(window-width . 0.5)))
@end lisp
You can type @key{C-x w s} to toggle the side windows to hide or show them at
will.
@node Message view @node Message view
@chapter The message view @chapter The message view
@ -1170,6 +1202,7 @@ from @t{gnus-article-mode}.
* Rich-text and images: MSGV Rich-text and images. Reading rich-text messages * Rich-text and images: MSGV Rich-text and images. Reading rich-text messages
* Custom headers: MSGV Custom headers. Your very own headers * Custom headers: MSGV Custom headers. Your very own headers
* Actions: MSGV Actions. Defining and using actions. * Actions: MSGV Actions. Defining and using actions.
* Detaching and Reattaching messages: MSGV Detaching and reattaching. Multiple message views.
@end menu @end menu
@node MSGV Overview @node MSGV Overview
@ -1286,6 +1319,7 @@ A execute some custom action on the message's MIME-parts
misc misc
---- ----
z, Z detach (or reattach) a message view to a headers buffer
. show the raw message view. 'q' takes you back. . show the raw message view. 'q' takes you back.
C-+,C-- increase / decrease the number of headers shown C-+,C-- increase / decrease the number of headers shown
H get help H get help
@ -1398,6 +1432,35 @@ as attachments. For now, these actions are defined and documented in
@code{mu4e-view-mime-part-actions}. @code{mu4e-view-mime-part-actions}.
@node MSGV Detaching and reattaching
@section Detaching and reattaching messages
You can have multiple message views, but you must rename the view
buffer and detach it to stop @t{mu4e} from reusing it when you
navigate up or down in the headers buffer. If you have several view
buffers attached to a headers view, then @t{mu4e} may pick one at
random when it has to choose which one to display a message in.
To detach the message view from its linked headers buffer, type
@key{z}. A message will appear saying it is detached (or warn you if
it is already detached.)
Detached buffers are static; they cannot change the displayed message,
and no headers buffer will use a detached buffer to display its
messages. You can reattach a buffer to an live headers buffer by
typing @key{Z}.
You can freely rename a message view buffer -- such as with @key{C-x x
r} -- if you want multiple, open messages.
Detached messages are often useful for workflows involving lots of
simultaneous messages.
You can @emph{tear off} the window a message is in and place it in a
new tab by typing @key{C-x w ^ f}. You can also detach a window and
put it in its own tab with @key{C-x w ^ t}.
@node Editor view @node Editor view
@chapter The editor view @chapter The editor view
@ -4022,8 +4085,12 @@ Sending...done
The first and final messages are the most important, and there may be The first and final messages are the most important, and there may be
considerable time between them, depending on the size of the message. considerable time between them, depending on the size of the message.
@subsection Is it possible to compose messages in a separate frame? @subsection Is it possible to view headers and messages, or compose new ones, in a separate frame?
Yes --- set the variable @code{mu4e-compose-in-new-frame} to @code{t}. Yes. There is builtin support for composing messages in a new
frame. Set the variable @code{mu4e-compose-in-new-frame} to @code{t}.
However, if you want to personalize the display of all of @t{mu4e}'s
buffers, you can do so by customizing @code{display-buffer-alist}.
@subsection How can I apply format=flowed to my outgoing messages? @subsection How can I apply format=flowed to my outgoing messages?
This enables receiving clients that support this feature to reflow This enables receiving clients that support this feature to reflow