I use Emacs, which is mostly great. However I also use i3, a tiling window manager, and there’s a little bit of overlap. Emacs can split frames into multiple windows and shuffle things around - but so can i3, and in fact that’s specifically what i3 is built for.
I resolve this tension pretty heavy-handedly. I use only i3 for placing windows. Emacs is stripped of all power to split frames.
Here’s how I get emacs to behave (annotated from my init.el.
The main part is configuring
display-buffer-alist, which controls what happens when emacs wants to display a buffer.
(setq display-buffer-alist '(("shell.*" (display-buffer-same-window) ()) (".*" (display-buffer-reuse-window display-buffer-same-window display-buffer-pop-up-frame) (reusable-frames . t))))
This tells emacs to always raise an existing window that’s already displaying the buffer, if one exists, and otherwise to display it in the current window. There’s a slight exception for shells - when I switch to them I want them to always display in the current window, even if there’s another frame somewhere already displaying them.
display-buffer-pop-up-window, which is used by some code that really wants to split the window (for example displaying grep results), to instead use the whole thing.
(defun anders/same-window-instead (orig-fun buffer alist) (display-buffer-same-window buffer nil)) (advice-add 'display-buffer-pop-up-window :around 'anders/same-window-instead)
I’ve forgotten exactly what this does - but I think it tells emacs to switch to the frame that’s already displaying whatever I want to look at, if such a frame exists.
(defun anders/do-select-frame (orig-fun buffer &rest args) (let* ((old-frame (selected-frame)) (window (apply orig-fun buffer args)) (frame (window-frame window))) (unless (eq frame old-frame) (select-frame-set-input-focus frame)) (select-window window) window)) (advice-add 'display-buffer :around 'anders/do-select-frame)
I make transient frames (I have certain utilities that pop up a temporary frame) go away entirely when they’re done.
(setq frame-auto-hide-function 'delete-frame)
I completely neuter dedicated window functionality. I don’t need it, since I’m explicitly controlling all window placement anyways, and dedicated windows override a bunch of behavior and break all the other customizations.
(advice-add 'set-window-dedicated-p :around (lambda (orig-fun &rest args) nil))
There are also some package-specific fixes.
(setq org-agenda-window-setup 'current-window)
(setq gdb-display-io-nopopup t)
compilation-goto-locus (for jumping to compile errors or grep results) is a huge offender here. It overrides
display-buffer-alist with a
let binding deep inside it’s internals. There’s nothing for it but to override the whole function with a stripped-down version (still pretty long but look at the original - it’s a real beast).
(defun compilation-goto-locus (msg mk end-mk) "Jump to an error corresponding to MSG at MK. All arguments are markers. If END-MK is non-nil, mark is set there and overlay is highlighted between MK and END-MK." (display-buffer (marker-buffer msg) '(nil (allow-no-window . t))) (with-current-buffer (marker-buffer msg) (goto-char (marker-position msg)) (and w (compilation-set-window w msg))) (display-buffer (marker-buffer mk)) (with-current-buffer (marker-buffer mk) (unless (eq (goto-char mk) (point)) ;; If narrowing gets in the way of going to the right place, widen. (widen) (if next-error-move-function (funcall next-error-move-function msg mk) (goto-char mk))) (if end-mk (push-mark end-mk t) (if mark-active (setq mark-active))) ;; If hideshow got in the way of ;; seeing the right place, open permanently. (dolist (ov (overlays-at (point))) (when (eq 'hs (overlay-get ov 'invisible)) (delete-overlay ov) (goto-char mk))))))
And as a complement to that, rust-mode tries to get fancy with jumping to compile errors. Undo that too.
(eval-after-load 'compile (remove-hook 'next-error-hook 'rustc-scroll-down-after-next-error)))
There’s probably more interactions I’ll discover with various packages, but on the whole this has been working for me quite well.