From 69a17bfcb93b4ca8ee59c546f14a3e921202690c Mon Sep 17 00:00:00 2001 From: Mickey Petersen Date: Fri, 18 Nov 2022 12:54:27 +0000 Subject: [PATCH] Multiple buffer support and standardised window and buffer handling --- mu4e/mu4e-compose.el | 134 ++++++++++----------- mu4e/mu4e-draft.el | 25 ++-- mu4e/mu4e-headers.el | 120 ++++--------------- mu4e/mu4e-helpers.el | 49 ++------ mu4e/mu4e-icalendar.el | 2 +- mu4e/mu4e-main.el | 23 ++-- mu4e/mu4e-mark.el | 16 +-- mu4e/mu4e-message.el | 14 ++- mu4e/mu4e-update.el | 26 ++--- mu4e/mu4e-vars.el | 4 +- mu4e/mu4e-view.el | 162 ++++++++++++++------------ mu4e/mu4e-window.el | 256 +++++++++++++++++++++++++++++++++++++++++ mu4e/mu4e.el | 1 + 13 files changed, 494 insertions(+), 338 deletions(-) create mode 100644 mu4e/mu4e-window.el diff --git a/mu4e/mu4e-compose.el b/mu4e/mu4e-compose.el index 66f9c49c..a4b73edb 100644 --- a/mu4e/mu4e-compose.el +++ b/mu4e/mu4e-compose.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)) (str (or subj (pcase compose-type - ('reply "*reply*") - ('forward "*forward*") - (_ "*draft*"))))) + ('reply "*mu4e-reply*") + ('forward "*mu4e-forward*") + (_ "*mu4e-draft*"))))) (rename-buffer (generate-new-buffer-name (truncate-string-to-width str mu4e~compose-buffer-max-name-length) @@ -539,79 +539,82 @@ are optional." mu4e-compose-context-policy) (run-hooks 'mu4e-compose-pre-hook) ;; this opens (or re-opens) a message with all the basic headers set. - (let ((winconf (current-window-configuration))) - (condition-case nil - (mu4e-draft-open compose-type original-msg switch-function) - (quit (set-window-configuration winconf) - (mu4e-message "Operation aborted") - (cl-return-from mu4e~compose-handler)))) - ;; 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) + (let ((draft-buffer)) + (let ((winconf (current-window-configuration))) + (condition-case nil + (setq draft-buffer (mu4e-draft-open compose-type original-msg switch-function)) + (quit (set-window-configuration winconf) + (mu4e-message "Operation aborted") + (cl-return-from mu4e~compose-handler)))) + (set-buffer draft-buffer) + ;; 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 - (mu4e-compose-crypto-message original-msg compose-type) + ;; maybe encrypt/sign replies + (mu4e-compose-crypto-message original-msg compose-type) - ;; include files -- e.g. when inline forwarding a message with - ;; attachments, we take those from the original. - (save-excursion - (goto-char (point-max)) ;; put attachments at the end + ;; include files -- e.g. when inline forwarding a message with + ;; attachments, we take those from the original. + (save-excursion + (goto-char (point-max)) ;; put attachments at the end - (if (and (eq compose-type 'forward) mu4e-compose-forward-as-attachment) - (mu4e-compose-attach-message original-msg) - (dolist (att includes) - (let ((file-name (plist-get att :file-name)) - (mime (plist-get att :mime-type)) - (description (plist-get att :description)) - (disposition (plist-get att :disposition))) - (if file-name - (mml-attach-file file-name mime description disposition) - (mml-attach-buffer (plist-get att :buffer-name) - mime description disposition)))))) + (if (and (eq compose-type 'forward) mu4e-compose-forward-as-attachment) + (mu4e-compose-attach-message original-msg) + (dolist (att includes) + (let ((file-name (plist-get att :file-name)) + (mime (plist-get att :mime-type)) + (description (plist-get att :description)) + (disposition (plist-get att :disposition))) + (if file-name + (mml-attach-file file-name mime description disposition) + (mml-attach-buffer (plist-get att :buffer-name) + 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 - (set (make-local-variable 'mu4e-compose-parent-message) original-msg) - (put 'mu4e-compose-parent-message 'permanent-local t) - ;; set mu4e-compose-type once more for this buffer, - (set (make-local-variable 'mu4e-compose-type) compose-type) - (put 'mu4e-compose-type 'permanent-local t) + ;; bind to `mu4e-compose-parent-message' of compose buffer + (set (make-local-variable 'mu4e-compose-parent-message) original-msg) + (put 'mu4e-compose-parent-message 'permanent-local t) + ;; set mu4e-compose-type once more for this buffer, + (set (make-local-variable 'mu4e-compose-type) compose-type) + (put 'mu4e-compose-type 'permanent-local t) - ;; hide some headers - (mu4e~compose-hide-headers) - ;; switch on the mode - (mu4e-compose-mode) + ;; hide some headers + (mu4e~compose-hide-headers) + ;; switch on the mode + (mu4e-compose-mode) - ;; now jump to some useful positions, and start writing that mail! - (if (member compose-type '(new forward)) - (message-goto-to) - ;; otherwise, it depends... - (pcase message-cite-reply-position - ((or 'above 'traditional) (message-goto-body)) - (_ (when (message-goto-signature) (forward-line -2))))) + ;; now jump to some useful positions, and start writing that mail! + (if (member compose-type '(new forward)) + (message-goto-to) + ;; otherwise, it depends... + (pcase message-cite-reply-position + ((or 'above 'traditional) (message-goto-body)) + (_ (when (message-goto-signature) (forward-line -2))))) - ;; don't allow undoing anything before this. - (setq buffer-undo-list nil) + ;; don't allow undoing anything before this. + (setq buffer-undo-list nil) - (when mu4e-compose-in-new-frame - ;; make sure to close the frame when we're done with the message these are - ;; all buffer-local; - (push 'delete-frame message-exit-actions) - (push 'delete-frame message-postpone-actions)) + ;; TODO: replace this + (when mu4e-compose-in-new-frame + ;; make sure to close the frame when we're done with the message these are + ;; all buffer-local; + (push 'delete-frame message-exit-actions) + (push 'delete-frame message-postpone-actions)) - ;; buffer is not user-modified yet - (set-buffer-modified-p nil)) + ;; buffer is not user-modified yet + (set-buffer-modified-p nil) + (mu4e-display-buffer draft-buffer t))) (defun mu4e~switch-back-to-mu4e-buffer () "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)) - (switch-to-buffer (mu4e-get-view-buffer)) - (if (buffer-live-p (mu4e-get-headers-buffer)) - (switch-to-buffer (mu4e-get-headers-buffer)) - ;; if all else fails, back to the main view - (when (fboundp 'mu4e) (mu4e)))))) + (if (buffer-live-p (mu4e-get-view-buffer)) + (mu4e-display-buffer (mu4e-get-view-buffer) t) + (if (buffer-live-p (mu4e-get-headers-buffer)) + (mu4e-display-buffer (mu4e-get-headers-buffer) t) + ;; if all else fails, back to the main view + (when (fboundp 'mu4e) (mu4e))))) (defun mu4e-compose-context-switch (&optional force name) "Change the context for the current draft message. @@ -755,10 +758,7 @@ Symbol `edit' is only allowed for draft messages." ;; 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 ;; it... - (unless (eq mu4e-split-view 'single-window) - (let ((viewwin (get-buffer-window (mu4e-get-view-buffer)))) - (when (window-live-p viewwin) - (select-window viewwin)))) + ;; (mu4e-display-buffer (mu4e-get-view-buffer)) ;; talk to the backend (mu4e--server-compose compose-type decrypt docid))))) diff --git a/mu4e/mu4e-draft.el b/mu4e/mu4e-draft.el index 1a40ea4f..3168b2a9 100644 --- a/mu4e/mu4e-draft.el +++ b/mu4e/mu4e-draft.el @@ -667,12 +667,7 @@ This is based on `mu4e-drafts-folder', which is evaluated once.") (defun mu4e~draft-open-file (path switch-function) "Open the the draft file at PATH." - (let ((buf (find-file-noselect path))) - (funcall (or - switch-function - (and mu4e-compose-in-new-frame 'switch-to-buffer-other-frame) - 'switch-to-buffer) - buf))) + (find-file-noselect path)) (defun mu4e~draft-determine-path (draft-dir) @@ -692,8 +687,11 @@ concatenation of `(mu4e-root-maildir)' and `mu4e-drafts-folder' (the latter will be evaluated). The message file name is a unique name determined by `mu4e-send-draft-file-name'. The initial contents will be created from either `mu4e~draft-reply-construct', or -`mu4e~draft-forward-construct' or `mu4e~draft-newmsg-construct'." - (let ((draft-dir nil)) +`mu4e~draft-forward-construct' or `mu4e~draft-newmsg-construct'. + +Returns the newly-created draft buffer." + (let ((draft-dir nil) + (draft-buffer)) (cl-case compose-type (edit @@ -701,7 +699,7 @@ will be created from either `mu4e~draft-reply-construct', or ;; full path, but we cannot really know 'drafts folder'... we make a ;; guess (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) switch-function))) (resend ;; case-2: copy some exisisting message to a draft message, then edit @@ -709,7 +707,7 @@ will be created from either `mu4e~draft-reply-construct', or (setq draft-dir (mu4e--guess-maildir (mu4e-message-field msg :path))) (let ((draft-path (mu4e~draft-determine-path draft-dir))) (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 switch-function)))) ((reply forward new) ;; case-3: creating a new message; in this case, we can determine @@ -721,7 +719,8 @@ will be created from either `mu4e~draft-reply-construct', or (reply (mu4e~draft-reply-construct msg)) (forward (mu4e~draft-forward-construct msg)) (new (mu4e~draft-newmsg-construct))))) - (mu4e~draft-open-file draft-path switch-function) + (setq draft-buffer (mu4e~draft-open-file draft-path switch-function)) + (set-buffer draft-buffer) (insert initial-contents) (newline) ;; include the message signature (if it's set) @@ -739,7 +738,9 @@ will be created from either `mu4e~draft-reply-construct', or (set (make-local-variable 'mu4e~draft-drafts-folder) draft-dir) (put 'mu4e~draft-drafts-folder 'permanent-local t) (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) diff --git a/mu4e/mu4e-headers.el b/mu4e/mu4e-headers.el index f39effc8..4389dda9 100644 --- a/mu4e/mu4e-headers.el +++ b/mu4e/mu4e-headers.el @@ -724,9 +724,7 @@ docid is not found." (defun mu4e~headers-view-this-message-p (docid) "Is DOCID currently being viewed?" - (when (buffer-live-p (mu4e-get-view-buffer)) - (with-current-buffer (mu4e-get-view-buffer) - (eq docid (plist-get mu4e~view-message :docid))))) + (mu4e-get-view-buffers (lambda (buf) (eq docid (plist-get mu4e~view-message :docid))))) ;; note: this function is very performance-sensitive (defun mu4e~headers-append-handler (msglst) @@ -808,10 +806,10 @@ present, don't do anything." ;; if we were viewing this message, close it now. (when (and (mu4e~headers-view-this-message-p docid) (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 - (mu4e-get-view-buffer) nil t))) - (kill-buffer (mu4e-get-view-buffer)))) + buf nil t)) + (kill-buffer buf)))) @@ -825,7 +823,7 @@ present, don't do anything." Switch to the output buffer for the results. If IGNORE-HISTORY is 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) (rewritten-expr (funcall mu4e-query-rewrite-function expr)) (maxnum (unless mu4e-search-full mu4e-search-results-limit))) @@ -842,7 +840,7 @@ true, do *not* update the query history stack." ;; when the buffer is already visible, select it; otherwise, ;; switch to it. (unless (get-buffer-window buf 0) - (switch-to-buffer buf)) + (mu4e-display-buffer buf t)) (run-hook-with-args 'mu4e-search-hook expr) (mu4e~headers-clear mu4e~search-message) (setq mu4e~headers-search-start (float-time)) @@ -1155,7 +1153,8 @@ no user-interaction ongoing." (when (and mu4e-headers-auto-update ;; must be set mu4e-index-update-status (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 ;; rerun search if there's a live window with search results; ;; otherwise we'd trigger a headers view from out of nowhere. @@ -1329,32 +1328,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 @@ -1623,41 +1596,21 @@ _not_ refresh the last search with the new setting for threading." (mu4e~headers-toggle "Skip-duplicates" 'mu4e-headers-skip-duplicates dont-refresh)) -(defvar mu4e~headers-loading-buf nil - "A buffer for loading a message view.") - (defun mu4e-headers-view-message () - "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 . " + "View message at point." (interactive) (unless (eq major-mode 'mu4e-headers-mode) (mu4e-error "Must be in mu4e-headers-mode (%S)" major-mode)) (let* ((msg (mu4e-message-at-point)) (path (mu4e-message-field msg :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) (mu4e-warn "No message at point"))) (mark-as-read (if (functionp mu4e-view-auto-mark-as-read) (funcall mu4e-view-auto-mark-as-read msg) - mu4e-view-auto-mark-as-read)) - (viewwin (mu4e~headers-redraw-get-view-window))) - (unless (window-live-p viewwin) - (mu4e-error "Cannot get a message view")) - (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-view-auto-mark-as-read))) (mu4e--server-view docid mark-as-read))) @@ -1688,15 +1641,15 @@ return nil." ;; update all windows showing the headers buffer (walk-windows (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)))) nil t) - (if (eq mu4e-split-view 'single-window) - (when (eq (window-buffer) (mu4e-get-view-buffer)) - (mu4e-headers-view-message)) - ;; update message view if it was already showing - (when (and mu4e-split-view (window-live-p mu4e~headers-view-win)) - (mu4e-headers-view-message))) + ;; If the assigned (and buffer-local) `mu4e~headers-view-win' + ;; is not live then that is indicates the user does not want + ;; to pop up the view when they navigate in the headers + ;; buffer. + (when (window-live-p mu4e~headers-view-win) + (mu4e-headers-view-message)) ;; attempt to highlight the new line, display the message (mu4e~headers-highlight docid) (if succeeded @@ -1807,42 +1760,19 @@ region if there is a region, then move to the next message." This is a rather complex function, to ensure we don't disturb other windows." (interactive) - (if (eq mu4e-split-view 'single-window) - (progn (mu4e-mark-handle-when-leaving) - (kill-buffer)) - (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)))) + (mu4e-mark-handle-when-leaving) + (quit-window t) + (mu4e--main-view 'refresh)) ;;; 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) + (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 diff --git a/mu4e/mu4e-helpers.el b/mu4e/mu4e-helpers.el index 1da31197..ef41b209 100644 --- a/mu4e/mu4e-helpers.el +++ b/mu4e/mu4e-helpers.el @@ -105,60 +105,25 @@ and `mu4e-headers-visible-columns'." (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)) +;;; TODO: rewrite select other window (defun mu4e-select-other-view () "Switch between headers view and message view." (interactive) (let* ((other-buf (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)) - ((eq major-mode 'mu4e-view-mode) - (mu4e-get-headers-buffer)))) + (t (mu4e-error "This window is neither the headers nor the view window.")))) (other-win (and other-buf (get-buffer-window other-buf)))) (if (window-live-p other-win) (select-window other-win) (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 (defun mu4e-quote-for-modeline (str) @@ -406,7 +371,7 @@ log-buffer. See `mu4e-show-log'." (let ((buf (get-buffer mu4e--log-buffer-name))) (unless (buffer-live-p buf) (mu4e-warn "No debug log available")) - (switch-to-buffer buf))) + (display-buffer buf))) diff --git a/mu4e/mu4e-icalendar.el b/mu4e/mu4e-icalendar.el index 2bc4ce03..435e86e2 100644 --- a/mu4e/mu4e-icalendar.el +++ b/mu4e/mu4e-icalendar.el @@ -191,7 +191,7 @@ (funcall action docid original-msg target)) (when (and (mu4e~headers-view-this-message-p docid) (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) (kill-buffer-and-window)))))) diff --git a/mu4e/mu4e-main.el b/mu4e/mu4e-main.el index 31cf4baf..f00c0c43 100644 --- a/mu4e/mu4e-main.el +++ b/mu4e/mu4e-main.el @@ -371,17 +371,13 @@ 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))) - (if (eq mu4e-split-view 'single-window) - (if (buffer-live-p (mu4e-get-headers-buffer)) - (switch-to-buffer (mu4e-get-headers-buffer)) - (mu4e--main-menu)) - ;; `mu4e--main-view' is called from `mu4e--start', so don't call it - ;; a second time here i.e. do not refresh unless specified - ;; explicitly with REFRESH arg. - (switch-to-buffer buf) - (with-current-buffer buf - (mu4e--main-view-real-1 refresh)) - (goto-char (point-min))))) + ;; `mu4e--main-view' is called from `mu4e--start', so don't call it + ;; a second time here i.e. do not refresh unless specified + ;; explicitly with REFRESH arg. + (mu4e-display-buffer buf t) + (with-current-buffer buf + (mu4e--main-view-real-1 refresh)) + (goto-char (point-min)))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Interactive functions @@ -420,8 +416,9 @@ When REFRESH is non nil refresh infos from server." (defun mu4e--main-update-after-index () "Update the main view buffer after indexing." - (with-current-buffer mu4e-main-buffer-name - (revert-buffer))) + (when (buffer-live-p mu4e-main-buffer-name) + (with-current-buffer mu4e-main-buffer-name + (revert-buffer)))) (provide 'mu4e-main) ;;; mu4e-main.el ends here diff --git a/mu4e/mu4e-mark.el b/mu4e/mu4e-mark.el index e575ea46..fb1513a1 100644 --- a/mu4e/mu4e-mark.el +++ b/mu4e/mu4e-mark.el @@ -99,30 +99,22 @@ is the target directory (for \"move\")") (defun mu4e--mark-find-headers-buffer () "Find the headers buffer, if any." (seq-find (lambda (b) - (with-current-buffer b - (eq major-mode 'mu4e-headers-mode))) + (mu4e-current-buffer-type-p 'headers)) (buffer-list))) (defmacro mu4e--mark-in-context (&rest body) "Evaluate BODY in the context of the headers buffer. The current buffer must be either a headers or view buffer." `(cond - ((eq major-mode 'mu4e-headers-mode) ,@body) - ((eq major-mode 'mu4e-view-mode) + ((mu4e-current-buffer-type-p 'headers) ,@body) + ((mu4e-current-buffer-type-p 'view) (when (buffer-live-p (mu4e-get-headers-buffer)) (let* ((msg (mu4e-message-at-point)) (docid (mu4e-message-field msg :docid))) (with-current-buffer (mu4e-get-headers-buffer) (if (mu4e~headers-goto-docid docid) ,@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))))) + (mu4e-error "Cannot find message in headers buffer")))))))) (defconst mu4e-marks '((refile diff --git a/mu4e/mu4e-message.el b/mu4e/mu4e-message.el index 6b423c73..bf5ef4d0 100644 --- a/mu4e/mu4e-message.el +++ b/mu4e/mu4e-message.el @@ -206,18 +206,20 @@ If MSG is nil, use `mu4e-message-at-point'." (kill-new 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 () "Show or hide the s-expression for the message-at-point, if any." (interactive) (if-let ((win (get-buffer-window mu4e--sexp-buffer-name))) (delete-window win) (when-let ((msg (mu4e-message-at-point 'noerror))) - (with-current-buffer-window mu4e--sexp-buffer-name nil nil - ;; the "pretty-printing" is not very pretty... - (insert (pp-to-string msg)))))) + (when (buffer-live-p mu4e--sexp-buffer-name) + (kill-buffer mu4e--sexp-buffer-name)) + (with-current-buffer-window (get-buffer-create mu4e--sexp-buffer-name) nil nil + (lisp-data-mode) + (insert (pp-to-string msg)) + (font-lock-fontify-buffer) + ;; add basic `quit-window' bindings + (view-mode 1))))) ;;; (provide 'mu4e-message) diff --git a/mu4e/mu4e-update.el b/mu4e/mu4e-update.el index 0e92f5c4..02d61be8 100644 --- a/mu4e/mu4e-update.el +++ b/mu4e/mu4e-update.el @@ -211,21 +211,22 @@ if you otherwise want to use `mu4e-index-lazy-check'." (defvar mu4e--update-buffer nil "The buffer of the update process when updating.") -(define-derived-mode mu4e--update-mail-mode -special-mode "mu4e:update" +(define-derived-mode mu4e--update-mail-mode special-mode "mu4e:update" "Major mode used for retrieving new e-mail messages in `mu4e'.") (define-key mu4e--update-mail-mode-map (kbd "q") 'mu4e-kill-update-mail) (defun mu4e--temp-window (buf height) - "Create a temporary window with HEIGHT at the bottom BUF." - (let ((win - (split-window - (frame-root-window) - (- (window-height (frame-root-window)) height)))) - (set-window-buffer win buf) - (set-window-dedicated-p win t) - win)) + "Create a temporary window with HEIGHT at the bottom BUF. + +This function uses `display-buffer' with a default preset. + +To override this behavior, customize `display-buffer-alist'." + (let ((win (display-buffer buf `(display-buffer-at-bottom + (preserve-size . (nil . t)) + (height . ,height) + (window-height . fit-window-to-buffer))))) + (set-window-buffer (get-buffer-window buf) buf))) (defun mu4e--update-sentinel-func (proc _msg) "Sentinel function for the update process PROC." @@ -244,8 +245,7 @@ special-mode "mu4e:update" (mu4e-update-index))) (mu4e-update-index)) (when (buffer-live-p mu4e--update-buffer) - (unless (eq mu4e-split-view 'single-window) - (mapc #'delete-window (get-buffer-window-list mu4e--update-buffer))) + (delete-windows-on mu4e--update-buffer t) (kill-buffer mu4e--update-buffer))) ;; complicated function, as it: @@ -268,8 +268,6 @@ run in the background; otherwise, pop up a window." (setq mu4e--update-buffer buf) (when (window-live-p win) (with-selected-window win - ;; ;;(switch-to-buffer buf) - ;; (set-window-dedicated-p win t) (erase-buffer) (insert "\n") ;; FIXME -- needed so output starts (mu4e--update-mail-mode))) diff --git a/mu4e/mu4e-vars.el b/mu4e/mu4e-vars.el index beaf1edf..1a79d9ab 100644 --- a/mu4e/mu4e-vars.el +++ b/mu4e/mu4e-vars.el @@ -355,7 +355,7 @@ header-view, not including, for instance, the message body.") ;;; Internals -(defvar mu4e~headers-view-win nil +(defvar-local mu4e~headers-view-win nil "The view window connected to this headers view.") ;; It's useful to have the current view message available to @@ -363,7 +363,7 @@ header-view, not including, for instance, the message body.") ;; before calling `mu4e-view-mode'. However, changing the major mode ;; clobbers any local variables. Work around that by declaring the ;; 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) ;;; _ (provide 'mu4e-vars) diff --git a/mu4e/mu4e-view.el b/mu4e/mu4e-view.el index b2904693..de5e9bc6 100644 --- a/mu4e/mu4e-view.el +++ b/mu4e/mu4e-view.el @@ -122,42 +122,7 @@ specified a function as viewer." This is a rather complex function, to ensure we don't disturb other windows." (interactive) - (if (eq mu4e-split-view 'single-window) - (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)))))))) + (quit-window t)) (defconst mu4e~view-raw-buffer-name " *mu4e-raw-view*" @@ -171,10 +136,10 @@ other windows." (with-current-buffer buf (let ((inhibit-read-only t)) (erase-buffer) - (insert-file-contents path) - (view-mode) + (mu4e-raw-view-mode) + (insert-file-contents path) (goto-char (point-min)))) - (switch-to-buffer buf))) + (mu4e-display-buffer buf t))) (defun mu4e-view-pipe (cmd) "Pipe the message at point through shell command CMD. @@ -186,17 +151,31 @@ Then, display the results." (defmacro mu4e~view-in-headers-context (&rest body) "Evaluate BODY in the context of the headers buffer." `(progn - (unless (buffer-live-p (mu4e-get-headers-buffer)) - (mu4e-error "No headers buffer connected")) (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) (mu4e-get-headers-buffer)) + ;; fallback; but what would trigger this? + (t (mu4e-get-headers-buffer)))) (docid (mu4e-message-field msg :docid))) (unless docid (mu4e-error "Message without docid: action is not possible")) - (with-current-buffer (mu4e-get-headers-buffer) - (unless (eq mu4e-split-view 'single-window) - (when (get-buffer-window) - (select-window (get-buffer-window)))) - (if (mu4e~headers-goto-docid docid) + (with-current-buffer buffer + (mu4e-display-buffer buffer) + (if (or (mu4e~headers-goto-docid docid) + ;; TODO: Is this the best way to find another + ;; 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 (mu4e-get-headers-buffer) + (mu4e-message-field (mu4e-message-at-point) :docid)))) ,@body (mu4e-error "Cannot find message in headers buffer")))))) @@ -226,12 +205,8 @@ message view. If this succeeds, return the new docid. Otherwise, return nil." (mu4e~view-in-headers-context (mu4e~headers-prev-or-next-unread backwards)) - (if (eq mu4e-split-view 'single-window) - (when (eq (window-buffer) (mu4e-get-view-buffer)) - (with-current-buffer (mu4e-get-headers-buffer) - (mu4e-headers-view-message))) - (mu4e-select-other-view) - (mu4e-headers-view-message))) + (mu4e-select-other-view) + (mu4e-headers-view-message)) (defun mu4e-view-headers-prev-unread () "Move point to the previous unread message header. @@ -336,6 +311,31 @@ Add this function to `mu4e-view-mode-hook' to enable this feature." "Return t if we're in split-view, nil otherwise." (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 (defun mu4e-view-scroll-up-or-next () @@ -599,27 +599,34 @@ in the the message view affects HDRSBUF, as does marking etc. As a side-effect, a message that is being viewed loses its `unread' marking if it still had that." - (mu4e~headers-update-handler msg nil nil) ;; update headers, if necessary. - - (when (bufferp gnus-article-buffer) - (kill-buffer gnus-article-buffer)) - (setq gnus-article-buffer mu4e-view-buffer-name) - (with-current-buffer (get-buffer-create gnus-article-buffer) + ;; create a new view buffer (if needed) + (setq gnus-article-buffer (mu4e-get-view-buffer nil t)) + (with-current-buffer gnus-article-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))) - (switch-to-buffer gnus-article-buffer) - (setq mu4e~view-message msg) - (mu4e~view-render-buffer msg)) + (mu4e-message-readable-path msg) nil nil nil t) + (setq-local mu4e~view-message msg))) + (mu4e~view-render-buffer msg) + (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)))) (defun mu4e-view-message-text (msg) "Return the pristine MSG as a string." ;; we need this for replying/forwarding, since the mu4e-compose ;; wants it that way. - (with-temp-buffer (insert-file-contents-literally (mu4e-message-readable-path msg) nil nil nil t) @@ -686,7 +693,6 @@ determine which browser function to use." (condition-case err (progn (mm-enable-multibyte) - (mu4e-view-mode) (run-hooks 'gnus-article-decode-hook) (gnus-article-prepare-display) (mu4e~view-activate-urls) @@ -826,9 +832,8 @@ This is useful for advising some Gnus-functionality that does not work in mu4e." (define-key map "q" #'mu4e~view-quit-buffer) - ;; note, 'z' is by-default bound to 'bury-buffer' - ;; but that's not very useful in this case - (define-key map "z" #'ignore) + (define-key map "z" #'mu4e-view-detach) + (define-key map "Z" #'mu4e-view-attach) (define-key map "%" #'mu4e-view-mark-pattern) (define-key map "t" #'mu4e-view-mark-subthread) @@ -985,12 +990,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) +(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 - "Hook run when entering Mu4e-View mode." + "Hook run when entering \\[mu4e-view] mode." :options '(turn-on-visual-line-mode) :type 'hook :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-derived-mode mu4e-view-mode gnus-article-mode "mu4e:view" "Major mode for viewing an e-mail message in mu4e. @@ -1150,11 +1164,11 @@ containing commas." (:name "raw" :handler (lambda (str) (let ((tmpbuf (get-buffer-create " *mu4e-raw-mime*"))) - (with-current-buffer tmpbuf - (insert str) - (view-mode) - (goto-char (point-min))) - (switch-to-buffer tmpbuf))) :receives pipe)) + (with-current-buffer tmpbuf + (insert str) + (view-mode) + (goto-char (point-min))) + (display-buffer tmpbuf))) :receives pipe)) "Specifies actions for MIME-parts. @@ -1274,7 +1288,7 @@ the third MIME-part." (erase-buffer) (call-process-shell-command pipecmd path t t) (view-mode))) - (switch-to-buffer buf))) + (display-buffer buf))) ;;; Bug Reference mode support diff --git a/mu4e/mu4e-window.el b/mu4e/mu4e-window.el new file mode 100644 index 00000000..6a60361c --- /dev/null +++ b/mu4e/mu4e-window.el @@ -0,0 +1,256 @@ +;;; mu4e-window.el --- window management for mu4e -*- lexical-binding: t; -*- + +;; Copyright (C) 2022 Mickey Petersen + +;; Author: Mickey Petersen +;; 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 . + +;;; Commentary: + +;;; Code: + +;; TODO: write function to get headers buffer (and return an error if it is missing) +;; TODO: write a function that displays the headers/view buffer(s) +;; TODO: write a function that returns buffer livenness for headers/view +;; TODO: write a function that returns whether a buffer is visible +;; TODO: write a function that selects the headers/view buffer if it exists or is visible. + +;;; Buffer names for internal use + +(defvar mu4e~headers-loading-buf nil + "A buffer for loading a message view.") + +(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") + +(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) + (or mu4e-linked-headers-buffer + (error "This view buffer is detached"))) + ;; 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 returns 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) + "Returns 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 () + "Returns 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-loading-mode) 'loading) + ((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) + "Returns non-nil if the current buffer is a mu4e buffer of TYPE. + +Where TYPE is `view', `loading', `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 one 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 (and (not (buffer-live-p buffer)) create) + (setq buffer (get-buffer-create (or buffer buffer-name))) + (with-current-buffer buffer + (mu4e-view-mode)))) + (when 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 + ((memq buffer-type '(loader)) '(display-buffer-same-window)) + ((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)))) + +(provide 'mu4e-window) +;;; mu4e-window.el ends here diff --git a/mu4e/mu4e.el b/mu4e/mu4e.el index 6737e3bc..add93b68 100644 --- a/mu4e/mu4e.el +++ b/mu4e/mu4e.el @@ -26,6 +26,7 @@ ;;; Code: (require 'mu4e-vars) +(require 'mu4e-window) (require 'mu4e-obsolete) (require 'mu4e-helpers) (require 'mu4e-folders)