How do I force org-mode's capture buffer to open in a new window? I tried
(setq special-display-regexps
'("^\\*Capture\\*$"))
but it did not work - I see a new window momentarily and then org-mode makes two vertical splits (I'm using 3 vertical splits), and put the capture buffer into the right split. When I'm done by either C-c C-c
or C-c C-k
, the original split setting is restored.
I, too, like to use many side-by-side splits (usually 4 -- I'm spread across multiple monitors), so org-capture
's behavior of turning 4 regular windows into 2 really wide ones makes my head explode every time -- which tends to knock me out of my flow.
+---+---+---+---+ +-------+-------+
| 1 | 2 | 3 | 4 | --> | 1 |capture| = head explosion
+---+---+---+---+ +-------+-------+
So here's a way to prevent org-capture
from modifying your window configuration.
After some searching, it does not look like there is an easy way to customize this behavior (or at least not an obvious one). Tracing the function calls in the source code brings us to org-capture-place-template
, which saves your original window configuration, then deletes the other windows, then gives you the two-window split. You get your window configuration back later when you finalize the capture, of course, but it sure would be nice to get rid of that "let's change your window layout without your say-so" step.
Turns out it's pretty simple. Just re-evaluate org-capture-place-template
after commenting out the single line calling (delete-other-windows)
:
(defun org-capture-place-template ()
"Insert the template at the target location, and display the buffer."
(org-capture-put :return-to-wconf (current-window-configuration))
;; (delete-other-windows) ; this is the culprit!
(org-switch-to-buffer-other-window
(org-capture-get-indirect-buffer (org-capture-get :buffer) "CAPTURE"))
(widen)
(show-all)
(goto-char (org-capture-get :pos))
(org-set-local 'org-capture-target-marker
(point-marker))
(org-set-local 'outline-level 'org-outline-level)
(let* ((template (org-capture-get :template))
(type (org-capture-get :type)))
(case type
((nil entry) (org-capture-place-entry))
(table-line (org-capture-place-table-line))
(plain (org-capture-place-plain-text))
(item (org-capture-place-item))
(checkitem (org-capture-place-item))))
(org-capture-mode 1)
(org-set-local 'org-capture-current-plist org-capture-plist))
Aaaah. It was like org-capture
was punching me in the face every time I used it, but now it's stopped.
(Edited: the following is for a newer version of org-mode)
(defun org-capture-place-template (&optional inhibit-wconf-store)
"Insert the template at the target location, and display the buffer.
When `inhibit-wconf-store', don't store the window configuration, as it
may have been stored before."
(unless inhibit-wconf-store
(org-capture-put :return-to-wconf (current-window-configuration)))
;(delete-other-windows)
(org-switch-to-buffer-other-window
(org-capture-get-indirect-buffer (org-capture-get :buffer) "CAPTURE"))
(widen)
(show-all)
(goto-char (org-capture-get :pos))
(org-set-local 'org-capture-target-marker
(point-marker))
(org-set-local 'outline-level 'org-outline-level)
(let* ((template (org-capture-get :template))
(type (org-capture-get :type)))
(case type
((nil entry) (org-capture-place-entry))
(table-line (org-capture-place-table-line))
(plain (org-capture-place-plain-text))
(item (org-capture-place-item))
(checkitem (org-capture-place-item))))
(org-capture-mode 1)
(org-set-local 'org-capture-current-plist org-capture-plist))
Here's the solution I came up with. This causes the entire capture process to occur in a single pop-out frame.
First, a couple of helper functions.
(defun my/get-frame-by-name (fname)
"If there is a frame with named FNAME, return it, else nil."
(require 'dash) ; For `-some'
(-some (lambda (frame)
(when (equal fname (frame-parameter frame 'name))
frame))
(frame-list)))
(defun my/display-buffer-in-named-frame (buffer alist)
"Display BUFFER in frame with specific name.
The name to use is the value associated with the 'named-frame key
in ALIST. If a frame with that name already exists, use it.
Otherwise, call `display-buffer-in-pop-up-frame' to create it.
If ALIST does not contain the key 'named-frame, use the name of BUFFER."
(let* ((fname (or (cdr (assq 'named-frame alist))
(buffer-name buffer)))
(frame (my/get-frame-by-name fname)))
(if frame
(window--display-buffer buffer
(frame-selected-window frame)
'reuse)
(display-buffer-pop-up-frame
buffer
(add-to-list 'alist `(pop-up-frame-parameters
(name . ,fname)))))))
Next, a macro to install temporary advice. If you prefer, you could inline this pretty easily.
(defmacro my/with-advice (adlist &rest body)
"Execute BODY with temporary advice in ADLIST.
Each element of ADLIST should be a list of the form
(SYMBOL WHERE FUNCTION [PROPS])
suitable for passing to `advice-add'. The BODY is wrapped in an
`unwind-protect' form, so the advice will be removed even in the
event of an error or nonlocal exit."
(declare (debug ((&rest (&rest form)) body))
(indent 1))
`(progn
,@(mapcar (lambda (adform)
(cons 'advice-add adform))
adlist)
(unwind-protect (progn ,@body)
,@(mapcar (lambda (adform)
`(advice-remove ,(car adform) ,(nth 2 adform)))
adlist))))
Here's the main function. The idea is to temporarily override everything that Org-mode is doing that we don't want. This includes the delete-other-windows
call mentioned in Dan's answer, and also two calls to org-switch-to-buffer-other-window
, which explicitly blocks pop-out frames.
(defun my/org-capture-in-popout-frame (&optional goto keys)
"As `org-capture', but do all work in a new frame.
This function by itself doesn't clean up the frame following
capture. To do that, add `my/org-capture-delete-capture-frame'
to `org-capture-after-finalize-hook'."
(interactive "P")
(if goto
(org-capture goto keys)
(let ((override '("\\*Org Select\\*\\|\\*Capture\\*\\|CAPTURE-.*"
my/display-buffer-in-named-frame
(named-frame . "Capture"))))
;; Force all relevant buffers to open in a specific capture frame.
(add-to-list 'display-buffer-alist override)
(my/with-advice
(;; Make Org-mode respect `display-buffer-alist'.
(#'org-switch-to-buffer-other-window :override #'pop-to-buffer)
;; And stop Org-mode from messing with our window configuration.
(#'delete-other-windows :override #'ignore))
(unwind-protect (condition-case err
(org-capture goto keys)
(error (my/org-capture-delete-capture-frame)
(signal (car err) (cdr err))))
(setq display-buffer-alist
(delete override display-buffer-alist)))))))
To get rid of the frame after capture is complete, use this function:
(defun my/org-capture-delete-capture-frame ()
"Delete a frame named \"Capture\".
For use in `org-capture-after-finalize-hook' to clean up after
`my/org-capture-in-popout-frame'."
(let ((frame (my/get-frame-by-name "Capture")))
(when frame (delete-frame frame))))
This is already called by my/capture-in-popout-frame
in the event of an early abort (e.g., typing q
in the template selection buffer or C-g
in response to a prompt in a capture template). For normal completion (including refiling) or a late abort (C-c C-k
from the capture template), you need to add it to org-capture-after-finalize-hook
.
(add-hook 'org-capture-after-finalize-hook
#'my/org-capture-delete-capture-frame)
Note that none of these functions modify the way independent calls to org-capture
work (unless for some reason you have an extra frame named "Capture" lying around), so you can still get the default behavior if you want it for a particular capture.
One caveat: This won't support multiple simultaneous capture processes. I don't have a need for that personally, but if you do, I don't think it should be too hard to add that capability.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With