Disabling emacs window management
April 6, 2018 (programming, emacs, i3)
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.
I override 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.
Org mode
(setq org-agenda-window-setup 'current-window)
Gdb
(setq gdb-display-io-nopopup t)
Compile. 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.