From 9e9d2bbfcc9cc46c4e5862ea7ff75033e5650ae6 Mon Sep 17 00:00:00 2001 From: Joe Frikker Date: Fri, 4 Sep 2026 14:36:52 -0400 Subject: [PATCH] No more right side window --- home.org | 413 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 213 insertions(+), 200 deletions(-) diff --git a/home.org b/home.org index 9fbb94c..9f20845 100644 --- a/home.org +++ b/home.org @@ -1054,229 +1054,242 @@ windows. I place windows in the bottom or on the right mostly depending on how w content to be. #+BEGIN_SRC elisp - (defun jf/window-children (window) - (let ((result nil) - (child (window-child window))) - (while child - (push child result) - (setq child (window-next-sibling child))) - (reverse result))) + (defun jf/window-children (window) + (let ((result nil) + (child (window-child window))) + (while child + (push child result) + (setq child (window-next-sibling child))) + (reverse result))) - (defun jf/enforce-right-side-heights (win) - (let ((slot-heights '((-1 . 20))) - (parent (window-parent win))) - (when parent - (let ((children (jf/window-children parent))) - (when (> (length children) 1) - (cl-loop for child in children - for slot = (window-parameter child 'window-slot) - for height = (alist-get slot slot-heights) - when height do (progn (window-resize child (- height (window-height child))) - (window-preserve-size child nil t)))))))) + (defun jf/window-main-root (&optional frame) + "Return FRAME's main (non-side) window or window subtree." + (let* ((root (frame-root-window (or frame (selected-frame)))) + (children (jf/window-children root))) + (if (seq-some (lambda (w) (window-parameter w 'window-side)) children) + (seq-find (lambda (w) (not (window-parameter w 'window-side))) children) + root))) - (defun jf/enforce-right-side-width (win) - "Resize side window WIN so its frame keeps 120 columns for the rest of the frame." - (let ((width (max 1 (- (frame-width (window-frame win)) 120)))) - (window-resize win (- width (window-width win)) t))) + (defun jf/window-leaves (window) + "Return the live leaf windows in WINDOW's subtree." + (if (window-live-p window) + (list window) + (mapcan #'jf/window-leaves (jf/window-children window)))) - (defun jf/enforce-right-side-widths (frame) - "Re-apply `jf/enforce-right-side-width' to all of FRAME's right-side windows." - (dolist (win (window-list frame 'nomini)) - (when (eq (window-parameter win 'window-side) 'right) - (jf/enforce-right-side-width win)))) + (defun jf/topright-window (windows) + "Return whichever of WINDOWS sits furthest up, then furthest right." + (car (sort (copy-sequence windows) + (lambda (a b) + (let ((top-a (nth 1 (window-edges a))) + (top-b (nth 1 (window-edges b)))) + (if (= top-a top-b) + (> (nth 0 (window-edges a)) (nth 0 (window-edges b))) + (< top-a top-b))))))) - (add-hook 'window-size-change-functions #'jf/enforce-right-side-widths) + (defun jf/display-buffer-right (buffer alist) + "Display BUFFER on the right of the main editing area. - ;; (require 'perspective) - ;; (setopt persp-mode-prefix-key (kbd "C-x t")) - ;; (setopt persp-show-modestring nil) - ;; (persp-mode) + Applies three rules, based on the shape of the frame's non-side windows: + 1. One window -> split it and show BUFFER in the new right window. + 2. Two windows side-by-side -> show BUFFER in the right one. If the + right window is selected, swap the two windows first (keeping + focus on the buffer that was active) so BUFFER doesn't evict it. + 3. Any other layout -> show BUFFER in the upper-rightmost window." + (let ((main (jf/window-main-root))) + (cond + ((window-live-p main) + (let* ((new (split-window main nil 'right)) + (width (max 1 (- (frame-width) 120))) + (delta (window-resizable new (- width (window-width new)) t))) + (window-resize new delta t) + (window--display-buffer buffer new 'window alist))) + ((and (window-left-child main) + (= (length (jf/window-children main)) 2) + (seq-every-p #'window-live-p (jf/window-children main))) + (let* ((left (window-left-child main)) + (right (window-next-sibling left))) + (when (eq (selected-window) right) + (window-swap-states left right) + (select-window left)) + (window--display-buffer buffer right 'reuse alist))) + (t + (window--display-buffer buffer (jf/topright-window (jf/window-leaves main)) 'reuse alist))))) - ;; (defun jf/popper-group-by-owning-perspective () - ;; "Return the name of the perspective that owns the current buffer. + ;; (require 'perspective) + ;; (setopt persp-mode-prefix-key (kbd "C-x t")) + ;; (setopt persp-show-modestring nil) + ;; (persp-mode) - ;; Unlike `popper-group-by-perspective', which just returns whichever - ;; perspective happens to be active on the frame right now, this looks - ;; up which perspective's buffer list the popup buffer actually belongs - ;; to. This matters because popper re-groups popups on - ;; `window-configuration-change-hook', which perspective.el triggers - ;; *after* switching the frame's active perspective -- so - ;; `popper-group-by-perspective' ends up filing a popup under the - ;; perspective you just switched *to* rather than the one it came from." - ;; (let ((buf (current-buffer))) - ;; (or (cl-loop for persp being the hash-values of (perspectives-hash (selected-frame)) - ;; if (memq buf (persp-buffers persp)) - ;; return (persp-name persp)) - ;; (persp-current-name)))) + ;; (defun jf/popper-group-by-owning-perspective () + ;; "Return the name of the perspective that owns the current buffer. - (setopt popper-reference-buffers - '("\\*Flymake diagnostics .*" - "\\*Embark Export: .*" - "\\*Embark Collect .*" - "\\*xref\\*" - "\\*compilation\\*" - "\\*pnpm:.*" - "\\*sly-mrepl *" - "\\*lispy-message\\*" - "\\*occur\\*" - ".*eshell\\*" - "\\*SQL: .*" - "magit-revision: .*" - "\\*Help\\*" - "\\*rustfmt\\*" - "\\*slack: .*" - "\\*forge: .*" - "new-pullreq" - "\\*claude:.*" - "\\*Man .*" - "\\*List .*" - "\\*eldoc\\*")) - (setopt popper-display-control nil) - ;; (setopt popper-group-function 'jf/popper-group-by-owning-perspective) + ;; Unlike `popper-group-by-perspective', which just returns whichever + ;; perspective happens to be active on the frame right now, this looks + ;; up which perspective's buffer list the popup buffer actually belongs + ;; to. This matters because popper re-groups popups on + ;; `window-configuration-change-hook', which perspective.el triggers + ;; *after* switching the frame's active perspective -- so + ;; `popper-group-by-perspective' ends up filing a popup under the + ;; perspective you just switched *to* rather than the one it came from." + ;; (let ((buf (current-buffer))) + ;; (or (cl-loop for persp being the hash-values of (perspectives-hash (selected-frame)) + ;; if (memq buf (persp-buffers persp)) + ;; return (persp-name persp)) + ;; (persp-current-name)))) - (popper-mode) - (popper-echo-mode) + (setopt popper-reference-buffers + '("\\*Flymake diagnostics .*" + "\\*Embark Export: .*" + "\\*Embark Collect .*" + "\\*xref\\*" + "\\*compilation\\*" + "\\*pnpm:.*" + "\\*sly-mrepl *" + "\\*lispy-message\\*" + "\\*occur\\*" + ".*eshell\\*" + "\\*SQL: .*" + "magit-revision: .*" + "\\*Help\\*" + "\\*rustfmt\\*" + "\\*slack: .*" + "\\*forge: .*" + "new-pullreq" + "\\*claude:.*" + "\\*Man .*" + "\\*List .*" + "\\*eldoc\\*")) + (setopt popper-display-control nil) + ;; (setopt popper-group-function 'jf/popper-group-by-owning-perspective) - (bind-keys ("C-q" . popper-toggle) - ("C-;" . popper-cycle)) + (popper-mode) + (popper-echo-mode) - (defvar jf/fullscreen-popup--param 'jf/fullscreen-saved-configuration - "Key used to stash the window configuration a fullscreen popup replaced. -Stored per-tab when `tab-bar-mode' is active, otherwise as a plain frame -parameter; see `jf/tab-or-frame-parameter'.") + (bind-keys ("C-q" . popper-toggle) + ("C-;" . popper-cycle)) - (defvar-local jf/fullscreen-popup-p nil - "Non-nil in a buffer currently being displayed as a fullscreen popup.") + (defvar jf/fullscreen-popup--param 'jf/fullscreen-saved-configuration + "Key used to stash the window configuration a fullscreen popup replaced. + Stored per-tab when `tab-bar-mode' is active, otherwise as a plain frame + parameter; see `jf/tab-or-frame-parameter'.") - (defun jf/current-tab-cell (&optional frame) - "Return FRAME's current tab's raw data cell, or nil without `tab-bar-mode'." - (and (bound-and-true-p tab-bar-mode) - (assq 'current-tab (tab-bar-tabs frame)))) + (defvar-local jf/fullscreen-popup-p nil + "Non-nil in a buffer currently being displayed as a fullscreen popup.") - (defun jf/tab-or-frame-parameter (parameter &optional frame) - "Like `frame-parameter', but scoped to FRAME's selected tab when tabs are in use. + (defun jf/current-tab-cell (&optional frame) + "Return FRAME's current tab's raw data cell, or nil without `tab-bar-mode'." + (and (bound-and-true-p tab-bar-mode) + (assq 'current-tab (tab-bar-tabs frame)))) -A plain frame parameter is shared by every tab on that frame, which is wrong -for state like `jf/fullscreen-popup--param': switching tabs swaps in an -entirely different window layout, so \"the layout to restore\" has to travel -with the tab, not the frame." - (if-let ((tab (jf/current-tab-cell frame))) - (alist-get parameter tab) - (frame-parameter frame parameter))) + (defun jf/tab-or-frame-parameter (parameter &optional frame) + "Like `frame-parameter', but scoped to FRAME's selected tab when tabs are in use. - (defun jf/set-tab-or-frame-parameter (parameter value &optional frame) - "Set PARAMETER to VALUE, scoped like `jf/tab-or-frame-parameter'." - (if-let ((tab (jf/current-tab-cell frame))) - (let ((cell (assq parameter tab))) - (if cell - (setcdr cell value) - (setcdr tab (cons (cons parameter value) (cdr tab))))) - (set-frame-parameter frame parameter value))) + A plain frame parameter is shared by every tab on that frame, which is wrong + for state like `jf/fullscreen-popup--param': switching tabs swaps in an + entirely different window layout, so \"the layout to restore\" has to travel + with the tab, not the frame." + (if-let ((tab (jf/current-tab-cell frame))) + (alist-get parameter tab) + (frame-parameter frame parameter))) - (defun jf/display-buffer-fullscreen (buffer alist) - "Display BUFFER, deleting other windows, saving the prior layout. + (defun jf/set-tab-or-frame-parameter (parameter value &optional frame) + "Set PARAMETER to VALUE, scoped like `jf/tab-or-frame-parameter'." + (if-let ((tab (jf/current-tab-cell frame))) + (let ((cell (assq parameter tab))) + (if cell + (setcdr cell value) + (setcdr tab (cons (cons parameter value) (cdr tab))))) + (set-frame-parameter frame parameter value))) -Intended for use in `display-buffer-alist' for popups (e.g. Magit -status/log) that should take over the frame while displayed, but -restore the previous window configuration once closed via either -`q' or `popper-toggle' -- both end up calling `quit-window', which -is where the restore actually happens; see -`jf/quit-window-restore-fullscreen'." - (unless (jf/tab-or-frame-parameter jf/fullscreen-popup--param) - (jf/set-tab-or-frame-parameter jf/fullscreen-popup--param - (current-window-configuration))) - (when-let ((window (or (display-buffer-reuse-window buffer alist) - (display-buffer-same-window buffer alist) - (display-buffer-pop-up-window buffer alist) - (display-buffer-use-some-window buffer alist)))) - (let ((ignore-window-parameters t)) - (delete-other-windows window)) - (with-current-buffer buffer - (setq jf/fullscreen-popup-p t)) - window)) + (defun jf/display-buffer-fullscreen (buffer alist) + "Display BUFFER, deleting other windows, saving the prior layout. - (defun jf/quit-window-restore-fullscreen (orig-fn &optional kill window) - "Restore the saved window configuration after quitting a fullscreen popup. + Intended for use in `display-buffer-alist' for popups (e.g. Magit + status/log) that should take over the frame while displayed, but + restore the previous window configuration once closed via either + `q' or `popper-toggle' -- both end up calling `quit-window', which + is where the restore actually happens; see + `jf/quit-window-restore-fullscreen'." + (unless (jf/tab-or-frame-parameter jf/fullscreen-popup--param) + (jf/set-tab-or-frame-parameter jf/fullscreen-popup--param + (current-window-configuration))) + (when-let ((window (or (display-buffer-reuse-window buffer alist) + (display-buffer-same-window buffer alist) + (display-buffer-pop-up-window buffer alist) + (display-buffer-use-some-window buffer alist)))) + (let ((ignore-window-parameters t)) + (delete-other-windows window)) + (with-current-buffer buffer + (setq jf/fullscreen-popup-p t)) + window)) -Advice around `quit-window' so both `q' (which Magit and other -special-mode buffers bind to buffer-quitting commands that call -`quit-window') and Popper's close/toggle commands (which also call -`quit-window' directly) trigger the restore. Only restores once the -last fullscreen popup on the tab (or frame, without `tab-bar-mode') -has closed, so opening one fullscreen popup from within another -falls back to the outer one first." - (let* ((window (or window (selected-window))) - (was-fullscreen (buffer-local-value 'jf/fullscreen-popup-p - (window-buffer window))) - (frame (window-frame window))) - (funcall orig-fn kill window) - (when (and was-fullscreen - (not (and (window-live-p window) - (buffer-local-value 'jf/fullscreen-popup-p - (window-buffer window)))) - (jf/tab-or-frame-parameter jf/fullscreen-popup--param frame)) - (let ((conf (jf/tab-or-frame-parameter jf/fullscreen-popup--param frame))) - (jf/set-tab-or-frame-parameter jf/fullscreen-popup--param nil frame) - (set-window-configuration conf))))) + (defun jf/quit-window-restore-fullscreen (orig-fn &optional kill window) + "Restore the saved window configuration after quitting a fullscreen popup. - (advice-add 'quit-window :around #'jf/quit-window-restore-fullscreen) + Advice around `quit-window' so both `q' (which Magit and other + special-mode buffers bind to buffer-quitting commands that call + `quit-window') and Popper's close/toggle commands (which also call + `quit-window' directly) trigger the restore. Only restores once the + last fullscreen popup on the tab (or frame, without `tab-bar-mode') + has closed, so opening one fullscreen popup from within another + falls back to the outer one first." + (let* ((window (or window (selected-window))) + (was-fullscreen (buffer-local-value 'jf/fullscreen-popup-p + (window-buffer window))) + (frame (window-frame window))) + (funcall orig-fn kill window) + (when (and was-fullscreen + (not (and (window-live-p window) + (buffer-local-value 'jf/fullscreen-popup-p + (window-buffer window)))) + (jf/tab-or-frame-parameter jf/fullscreen-popup--param frame)) + (let ((conf (jf/tab-or-frame-parameter jf/fullscreen-popup--param frame))) + (jf/set-tab-or-frame-parameter jf/fullscreen-popup--param nil frame) + (set-window-configuration conf))))) - (setq display-buffer-alist - '(((or "magit: *" - "magit-log*" - "\\*Messages\\*" - "\\*trace-output\\*" - "\\*EGLOT connections\\*") - (jf/display-buffer-fullscreen)) - ((or "\\*Flymake diagnostics .*" - "\\*Embark Export: .*" - "\\*Embark Collect .*" - "\\*xref\\*" - "\\*compilation\\*" - "\\*pnpm:.*" - "\\*sly-mrepl *" - "\\*lispy-message\\*" - "\\*occur\\*" - ".*eshell\\*" - "\\*SQL: .*") - (display-buffer-in-side-window) - (side . bottom) - (slot . 0) - (window-height . 0.33) - (window-parameters (no-delete-other-windows . t) - (no-other-window . t))) - ((or "magit-revision: .*" - "\\*Help\\*" - "\\*rustfmt\\*" - "\\*slack: .*" - "\\*forge: .*" - "new-pullreq" - "\\*claude:.*" - "\\*Man .*" - "\\*List .*") - (display-buffer-in-side-window) - (side . right) - (slot . 0) - (window-width . jf/enforce-right-side-width) - (window-height . jf/enforce-right-side-heights) - (preserve-size . (nil . nil)) - (window-parameters (no-delete-other-windows . t) - (no-other-window . t))) - ((or "\\*Embark Actions\\*" - "\\*eldoc\\*") - (display-buffer-in-side-window) - (side . right) - (slot . -1) - (window-width . jf/enforce-right-side-width) - (window-height . jf/enforce-right-side-heights) - (preserve-size . (nil . t)) - (window-parameters (no-delete-other-windows . t) - (no-other-window . t))))) + (advice-add 'quit-window :around #'jf/quit-window-restore-fullscreen) - (setopt window-persistent-parameters - (cl-list* '(no-delete-other-windows . writable) - '(no-other-window . writable) - window-persistent-parameters)) + (setq display-buffer-alist + '(((or "magit: *" + "magit-log*" + "\\*Messages\\*" + "\\*trace-output\\*" + "\\*EGLOT connections\\*") + (jf/display-buffer-fullscreen)) + ((or "\\*Flymake diagnostics .*" + "\\*Embark Export: .*" + "\\*Embark Collect .*" + "\\*xref\\*" + "\\*compilation\\*" + "\\*pnpm:.*" + "\\*sly-mrepl *" + "\\*lispy-message\\*" + "\\*occur\\*" + ".*eshell\\*" + "\\*SQL: .*") + (display-buffer-in-side-window) + (side . bottom) + (slot . 0) + (window-height . 0.33) + (window-parameters (no-delete-other-windows . t) + (no-other-window . t))) + ((or "magit-revision: .*" + "\\*Help\\*" + "\\*rustfmt\\*" + "\\*slack: .*" + "\\*forge: .*" + "new-pullreq" + "\\*claude:.*" + "\\*Man .*" + "\\*List .*" + "\\*Embark Actions\\*" + "\\*eldoc\\*") + (jf/display-buffer-right)))) + + (setopt window-persistent-parameters + (cl-list* '(no-delete-other-windows . writable) + '(no-other-window . writable) + window-persistent-parameters)) #+END_SRC