auto-side-windows
auto-side-windows copied to clipboard
Inspired by: https://github.com/othneildrew/Best-README-Template
#+OPTIONS: toc:nil
[[https://github.com/MArpogaus/auto-side-windows/graphs/contributors][https://img.shields.io/github/contributors/MArpogaus/auto-side-windows.svg?style=flat-square]] [[https://github.com/MArpogaus/auto-side-windows/network/members][https://img.shields.io/github/forks/MArpogaus/auto-side-windows.svg?style=flat-square]] [[https://github.com/MArpogaus/auto-side-windows/stargazers][https://img.shields.io/github/stars/MArpogaus/auto-side-windows.svg?style=flat-square]] [[https://github.com/MArpogaus/auto-side-windows/issues][https://img.shields.io/github/issues/MArpogaus/auto-side-windows.svg?style=flat-square]] [[https://github.com/MArpogaus/auto-side-windows/blob/main/LICENSE][https://img.shields.io/github/license/MArpogaus/auto-side-windows.svg?style=flat-square]] [[https://linkedin.com/in/MArpogaus][https://img.shields.io/badge/-LinkedIn-black.svg?style=flat-square&logo=linkedin&colorB=555]]
- auto-side-windows :TOC_3_gh:noexport:
- [[#about-the-project][About The Project]]
- [[#motivation][Motivation]]
- [[#layout-concept][Layout Concept]]
- [[#inspirations][Inspirations]]
- [[#getting-started][Getting Started]]
- [[#prerequisites][Prerequisites]]
- [[#example-configuration][Example Configuration]]
- [[#usage][Usage]]
- [[#customization][Customization]]
- [[#commands][Commands]]
- [[#integration-with-other-packages][Integration with Other Packages]]
- [[#popper-integration][Popper Integration]]
- [[#ace-window-integration][Ace Window Integration]]
- [[#enhancing-magit-and-org-mode-compatibility][Enhancing =magit= and =org-mode= compatibility]]
- [[#comparison-with-other-packages][Comparison with Other Packages]]
- [[#shackle][Shackle]]
- [[#popper][Popper]]
- [[#core-emacs-display-buffer-alist][Core Emacs (=display-buffer-alist=)]]
- [[#bonus-boxes-around-top-windows][Bonus: Boxes around top windows]]
- [[#contributions][Contributions]]
- [[#license][License]]
- [[#contact][Contact]]
- [[#about-the-project][About The Project]]
** About The Project
[[file:screenshot.png]]
*** Motivation
I found the default behavior of =display-buffer= often unpredictable, leading to new buffers appearing in unexpected windows. Digging into Emacs' window management led me to realize the power of =display-buffer-alist=, but configuring it directly can be complicated and confusing especially for new users.
=auto-side-windows= simplifies this process. It provides a straightforward way to define rules based on buffer names or major modes, ensuring that specific buffers consistently open in designated side windows (top, bottom, left, or right). The goal is to achieve a predictable, organized workspace, similar to layouts often found in IDEs, keeping the main editing area clear while providing easy access to complementary information.
*** Layout Concept
The package helps achieve layouts where side windows display non-primary information, keeping the central area focused on editing. A conceptual layout might look like this:
#+begin_src _________________________________________________ | top: compilation, grep, occur | || | left: | | right: | | - dired | | - Help | | - treemacs | | - Info | | - outline | Main Window Area | - Magit | | | (Editing) | | | | | | | | | | ||___________|| | bottom: REPLs, shells | || | Echo Area | |_____________________________________| #+end_src
This allows buffers like help, compilation logs, shells, or project trees to be accessible without disrupting the primary editing flow.
*** Inspirations
This package builds upon the excellent work and explanations of others in the Emacs community:
- [[https://karthinks.com/software/emacs-window-management-almanac/][Emacs Window Management Almanac]] by [[https://www.reddit.com/user/karthinks/][u/karthinks]]
- [[https://www.masteringemacs.org/article/demystifying-emacs-window-manager][Demystifying Emacs's Window Manager]] by [[https://www.reddit.com/user/mickeyp/][u/mickeyp]]
- [[https://github.com/karthink/.emacs.d/blob/25a0aec771c38e340789d7c304f3e39ff23aee3e/lisp/setup-windows.el#L164][Karthink's setup-windows.el]] for a practical examples. Thank you all!
** Getting Started
*** Prerequisites
- Emacs 30.1 or later.
*** Example Configuration
Here’s an example configuration using =use-package=:
#+begin_src emacs-lisp (use-package auto-side-windows :load-path "/path/to/auto-side-windows/" :custom ;; Respects display actions when switching buffers (switch-to-buffer-obey-display-actions t)
;; Top side window configurations
(auto-side-windows-top-buffer-names
'("^\\*Backtrace\\*$"
"^\\*Async-native-compile-log\\*$"
"^\\*Compile-Log\\*$"
"^\\*Multiple Choice Help\\*$"
"^\\*Quick Help\\*$"
"^\\*TeX Help\\*$"
"^\\*TeX errors\\*$"
"^\\*Warnings\\*$"
"^\\*Process List\\*$"))
(auto-side-windows-top-buffer-modes
'(flymake-diagnostics-buffer-mode
locate-mode
occur-mode
grep-mode
xref--xref-buffer-mode))
;; Bottom side window configurations
(auto-side-windows-bottom-buffer-names
'("^\\*eshell\\*$"
"^\\*shell\\*$"
"^\\*term\\*$"))
(auto-side-windows-bottom-buffer-modes
'(eshell-mode
shell-mode
term-mode
comint-mode
debugger-mode))
;; Right side window configurations
(auto-side-windows-right-buffer-names
'("^\\*eldoc.*\\*$"
"^\\*info\\*$"
"^\\*Metahelp\\*$"))
(auto-side-windows-right-buffer-modes
'(Info-mode
TeX-output-mode
eldoc-mode
help-mode
helpful-mode
shortdoc-mode))
;; Example: Custom parameters for top windows (e.g., fit height to buffer)
;; (auto-side-windows-top-alist '((window-height . fit-window-to-buffer)))
;; (auto-side-windows-top-window-parameters '((mode-line-format . ...))) ;; Adjust mode-line
;; Maximum number of side windows on the left, top, right and bottom
(window-sides-slots '(1 1 1 1)) ; Example: Allow one window per side
;; Force left and right side windows to occupy full frame height
(window-sides-vertical t)
;; Make changes to tab-/header- and mode-line-format persistent when toggling windows visibility
(window-persistent-parameters
(append window-persistent-parameters
'((tab-line-format . t)
(header-line-format . t)
(mode-line-format . t))))
:bind ;; Example keybindings (adjust prefix as needed)
(:map global-map ; Or your preferred keymap prefix
("C-c w t" . auto-side-windows-display-buffer-top)
("C-c w b" . auto-side-windows-display-buffer-bottom)
("C-c w l" . auto-side-windows-display-buffer-left)
("C-c w r" . auto-side-windows-display-buffer-right)
("C-c w w" . auto-side-windows-switch-to-buffer)
("C-c w t" . window-toggle-side-windows) ; Toggle all side windows
("C-c w T" . auto-side-windows-toggle-side-window)) ; Toggle current buffer in/out of side window
:hook
(after-init . auto-side-windows-mode))
#+end_src
** Usage
Once =auto-side-windows-mode= is enabled and configured, buffers matching your defined rules (by name regexp or major mode) will automatically be displayed in the specified side window.
** Customization
You can customize the behavior through the following variables (accessible via M-x customize-group RET auto-side-windows RET):
| Option | Type | Description |
|-------------------------------------------------------------+---------------+----------------------------------------------------------------------------------------|
| =auto-side-windows-{top,bottom,left,right}-buffer-names= | list (string) | Regexps matching buffer names for each side. |
| =auto-side-windows-{top,bottom,left,right}-buffer-modes= | list (symbol) | Major modes for buffers for each side. |
| =auto-side-windows-{top,bottom,left,right}-extra-conditions= | list (sexp) | Additional conditions (e.g., (category . ...) for =display-buffer=) for matching. |
| =auto-side-windows-{top,bottom,left,right}-window-parameters= | alist | Window parameters (see =window-parameters=) applied to windows on this side. |
| =auto-side-windows-{top,bottom,left,right}-alist= | alist | Action alist properties (e.g., =window-height=) applied for this side. |
| =auto-side-windows-common-window-parameters= | alist | Window parameters applied to all side windows created by this package. |
| =auto-side-windows-common-alist= | alist | Action alist properties (e.g., (dedicated . t)) applied for all side windows. |
| =auto-side-windows-reuse-mode-window= | alist | Specify sides (e.g., ((right . t))) where windows can be reused for same major mode. |
| =auto-side-windows-{before,after}-display-hook= | hook | Hooks run before/after displaying a buffer in a side window. |
| =auto-side-windows-{before,after}-toggle-hook= | hook | Hooks run before/after toggling a buffer as a side window. |
** Commands
The package provides the following commands:
- =auto-side-windows-mode=: Global minor mode. Enable this to activate the automatic side window management based on your rules.
- =auto-side-windows-toggle-side-window=: If the current buffer is in a side window created by this package, move it to a normal window. If it was just moved out, move it back to its side window configuration. Useful for temporarily maximizing a side window buffer.
- =auto-side-windows-display-buffer-{top,bottom,left,right}=: Manually force the current buffer to be displayed in a side window on the specified side, overriding normal rules.
- =auto-side-windows-display-buffer-on-side=: Manually force the current buffer to be displayed in a side window on the specified side, overriding normal rules. Compared to the command above the user is prompted side in the minibuffer.
- =auto-side-windows-switch-to-buffer=: Switch to a side buffer. The option =switch-to-buffer-obey-display-actions= should be customized to a non-nil value to respect the display buffer actions defined by this package.
** Integration with Other Packages
=auto-side-windows= plays well with other common window and buffer management packages.
*** Popper Integration
You can configure [[https://github.com/karthink/popper][Popper]] to manage the side windows created by =auto-side-windows=. Define Popper's reference buffers using the same lists you use for =auto-side-windows=:
#+begin_src emacs-lisp (use-package popper :after auto-side-windows ; Ensure auto-side-windows variables are defined :hook (auto-side-windows-mode . popper-mode) ; Activate popper alongside :custom ;; Tell Popper to consider buffers matching auto-side-windows rules as popups (popper-reference-buffers (append auto-side-windows-top-buffer-names auto-side-windows-top-buffer-modes auto-side-windows-left-buffer-names auto-side-windows-left-buffer-modes auto-side-windows-right-buffer-names auto-side-windows-right-buffer-modes auto-side-windows-bottom-buffer-names auto-side-windows-bottom-buffer-modes)) ;; Optional: Don't let Popper decide where to display, auto-side-windows handles that (popper-display-control nil) ; Or 'user if you prefer popper commands for display :config (popper-mode +1) ; Enable popper-mode (popper-echo-mode +1) ; Optional: echo area notifications :bind ;; Example bindings (:map your-prefix-map ;; e.g. my/toggle-map ("p" . popper-toggle) ; Toggle last popup ("P" . popper-toggle-type) ; Toggle popups of specific type ("C-p" . popper-cycle))) ; Cycle through visible popups #+end_src
With this setup, you can use Popper commands (like =popper-toggle= or =popper-cycle=) to quickly hide/show/cycle through the side windows managed by =auto-side-windows=.
Additionally, the following custom command allows you to switch to arbitrary buried popup buffers:
#+begin_src emacs-lisp (defun my/popper-switch-to-buried-buffer (buffer) "Switch to buried popup BUFFER." (interactive (list (when-let ((buried-popups (progn (popper--find-buried-popups) (mapcar #'cdr (alist-get (funcall popper-group-function) popper-buried-popup-alist nil nil 'equal)))) (pred (lambda (b) (if (consp b) (setq b (car b))) (setq b (get-buffer b)) (member b buried-popups)))) (read-buffer "Switch to popup: " nil t pred)))) (if buffer (display-buffer buffer) (message "No buried popups."))) #+end_src
Just add it to the =:preface= section of the above =use-package= declaration and optionally bind it to a key.
*** Ace Window Integration
[[https://github.com/abo-abo/ace-window][Ace Window]] can serve as a fallback mechanism for selecting a window when =display-buffer= doesn't find a suitable existing window or rule (including those from =auto-side-windows=).
Modify =display-buffer-base-action= to include =ace-display-buffer=. This tells Emacs to use Ace Window to ask you where to put a buffer if other methods fail.
#+begin_src emacs-lisp (use-package ace-window :custom (display-buffer-base-action '(display-buffer-reuse-mode-window display-buffer-in-previous-window ace-display-buffer))) ; Ask user via ace-window #+end_src
Now, if a command tries to display a buffer and no =auto-side-windows= rule applies and standard reuse fails, =ace-display-buffer= will trigger, letting you pick the window.
*** Enhancing =magit= and =org-mode= compatibility
To further enhance the Emacs experience with =auto-side-windows=, consider adding these configurations:
#+begin_src emacs-lisp ;; Org mode: Ensure agenda buffers open in the top side window for easy access (setopt org-src-window-setup 'plain) (setopt auto-side-windows-top-buffer-names '("^\Org Agenda\$" "^\Org Src.\*" "^\Org-Babel Error Output\" ...)))
;; Magit: Display Magit diff/status in the right side window, edit commit msg on top (setopt magit-display-buffer-function #'display-buffer magit-commit-diff-inhibit-same-window t) (setopt auto-side-windows-right-buffer-names '("^magit-diff:.$" "^magit-process:.$" ...)) (setopt auto-side-windows-right-buffer-modes '(magit-status-mode magit-log-mode magit-diff-mode magit-process-mode ...)) (setopt auto-side-windows-top-buffer-names '("^COMMIT_EDITMSG$" ...)) #+end_src
These configurations integrate well with =auto-side-windows= and enhance buffer management for specific use cases in Emacs.
** Comparison with Other Packages
While several packages address window management, =auto-side-windows= has a specific focus:
*** Shackle [[https://depp.brause.cc/shackle/][Shackle]] is a powerful package for finely controlling how and where popup windows appear, including options for creating new frames or specifying precise window parameters. It's very flexible and can involve more complex rule definitions.
Difference: =auto-side-windows= focuses specifically on simplifying the common use case of assigning buffers to side windows within the current frame based on simple name/mode matching, acting as a high-level interface to =display-buffer-alist= for this purpose.
*** Popper [[https://github.com/karthink/popper][Popper]] excels at managing buffers that are already considered "popups". It allows you to quickly toggle, cycle through, and manage the visibility of these popup windows.
Difference: =auto-side-windows= defines which buffers should become side windows in the first place. Popper can then be used to manage these side windows created by =auto-side-windows= rules (see [[#popper-integration][Popper Integration]]), by cycling through them or separating them by project. They work well together.
*** Core Emacs (=display-buffer-alist=) Emacs' built-in =display-buffer-alist= is the underlying mechanism for controlling buffer display. It's extremely powerful but requires writing potentially complex alist structures.
Difference: =auto-side-windows= acts as a configuration layer on top of =display-buffer-alist=. It generates the necessary alist entries for side window rules based on user-friendly customization variables, reducing the need to write the raw alist entries manually for this common pattern.
** Bonus: Boxes around top windows
Inspired by [[https://www.reddit.com/r/emacs/comments/1hoehaa/boxes_everywhere/][u/Nicolas-Rougier's post]], you can add customized header lines to side windows for better visual distinction. To resemble the example shown in the screenshot add this to your configuration:
#+begin_src emacs-lisp
(use-package auto-side-windows
:ensure (:host github :repo "MArpogaus/auto-side-windows")
:preface
(defun my/get-header-line-icon-for-buffer (buffer)
(with-current-buffer buffer
(unless (boundp 'header-line-icon)
(setq-local header-line-icon
(cond
((buffer-match-p "Warning" buffer) '(" ! " . warning))
((buffer-match-p '(or "^\Backtrace\$" ".[Ee]rror.") buffer) '(" ! " . error))
((buffer-match-p '(or "^COMMIT_EDITMSG$" "^\diff-hl\$") buffer) '(" " . success))
((buffer-match-p "^\Org Src.\" buffer) '(" " . mode-line-emphasis))
((buffer-match-p "^\Org Agenda\$" buffer) '(" " . mode-line-emphasis))
(t '(" ? " . mode-line-emphasis)))))
header-line-icon))
(defun my/install-top-side-window-face-remaps (buffer foreground background)
(with-current-buffer buffer
(unless (bound-and-true-p top-side-window-face-remaps-cookies)
(setq-local top-side-window-face-remaps-cookies
(list
(face-remap-add-relative 'header-line
(:box nil :underline nil :overline ,background)) (face-remap-add-relative 'fringe (:background ,background))
(face-remap-add-relative 'mode-line-active
(:overline ,background :underline nil :height 0)) (face-remap-add-relative 'mode-line-inactive (:overline ,background :underline nil :height 0))
)))))
(defvar my/header-line-format-top
'(:eval
(let
((buffer (current-buffer))
(prefix-and-face (my/get-header-line-icon-for-buffer buffer))
(prefix (car prefix-and-face))
(background (face-foreground (cdr prefix-and-face)))
(foreground (face-background (cdr prefix-and-face) nil 'default))
(prefix-face (list :inherit 'bold :background background :foreground foreground))
(buffer-face (list :inherit 'bold :foreground background)))
(set-window-fringes nil 1 1 t)
(my/install-top-side-window-face-remaps buffer foreground background)
(list
(propertize prefix 'face prefix-face 'display '(space-width 0.7))
(propertize (format-mode-line " %b ") 'face buffer-face)
(propertize " " 'display (space :align-to right)) (propertize " " 'face prefix-face 'display '(space-width 1)))))) :custom ;; Draw boxes around top side windows (auto-side-windows-top-window-parameters ((mode-line-format . t)
(header-line-format . ,my/header-line-format-top)))
(auto-side-windows-before-display-hook '((lambda (buffer)
(with-current-buffer buffer
(when (bound-and-true-p top-side-window-face-remaps-cookies)
(dolist (cookie top-side-window-face-remaps-cookies)
(face-remap-remove-relative cookie))
(kill-local-variable 'top-side-window-face-remaps-cookies))))))
(auto-side-windows-before-toggle-hook auto-side-windows-before-display-hook))
#+end_src
Note: Complex face remapping in header lines might have side effects (e.g., affecting Corfu popup fringes).
** Contributions
Contributions to the =auto-side-windows= package are greatly appreciated! Feel free to check the [[https://github.com/MArpogaus/auto-side-windows/issues][issues page]] or submit a [[https://github.com/MArpogaus/auto-side-windows/pulls][pull request]].
** License
Distributed under the [[file:COPYING][GPLv3]] License.
** Contact
[[https://github.com/MArpogaus/][Marcel Arpogaus]] - [[mailto:[email protected]][[email protected]]] (encrypted with ROT13)
Project Link: [[https://github.com/MArpogaus/auto-side-windows]]