From c5e3011d2365f62695187d7190969881469647c0 Mon Sep 17 00:00:00 2001 From: "Aaron L. Zeng" Date: Tue, 17 Dec 2024 10:57:24 -0500 Subject: [PATCH] Revert "JS: Delete spacemacs-purpose layer" This reverts commit 1c8b01a144d4827197843db0fea192694b3f628e. --- layers/+distributions/spacemacs/layers.el | 1 + .../+spacemacs/spacemacs-purpose/README.org | 152 +++++++++++ layers/+spacemacs/spacemacs-purpose/funcs.el | 74 ++++++ .../spacemacs-purpose-popwin.el | 249 ++++++++++++++++++ .../+spacemacs/spacemacs-purpose/packages.el | 169 ++++++++++++ 5 files changed, 645 insertions(+) create mode 100644 layers/+spacemacs/spacemacs-purpose/README.org create mode 100644 layers/+spacemacs/spacemacs-purpose/funcs.el create mode 100644 layers/+spacemacs/spacemacs-purpose/local/spacemacs-purpose-popwin/spacemacs-purpose-popwin.el create mode 100644 layers/+spacemacs/spacemacs-purpose/packages.el diff --git a/layers/+distributions/spacemacs/layers.el b/layers/+distributions/spacemacs/layers.el index 9bfdb2a53d8d..a477f622e52f 100644 --- a/layers/+distributions/spacemacs/layers.el +++ b/layers/+distributions/spacemacs/layers.el @@ -41,5 +41,6 @@ spacemacs-navigation spacemacs-org spacemacs-project + spacemacs-purpose spacemacs-visual )) diff --git a/layers/+spacemacs/spacemacs-purpose/README.org b/layers/+spacemacs/spacemacs-purpose/README.org new file mode 100644 index 000000000000..884b92619ce5 --- /dev/null +++ b/layers/+spacemacs/spacemacs-purpose/README.org @@ -0,0 +1,152 @@ +#+TITLE: Spacemacs-purpose layer + +#+TAGS: layer|misc|spacemacs + +* Table of Contents :TOC_5_gh:noexport: +- [[#description][Description]] + - [[#features][Features:]] +- [[#purposes][Purposes]] + - [[#switch-to-buffer-and-display-buffer][switch-to-buffer and display-buffer]] + - [[#misc][misc]] +- [[#install][Install]] +- [[#usage][Usage]] + - [[#allocate-purposes-in-layers][Allocate purposes in layers]] + - [[#overwrite-purposes-in-dotfile][Overwrite purposes in dotfile]] +- [[#key-bindings][Key bindings]] +- [[#caveats][Caveats]] + - [[#popwin-and-guide-key][Popwin and guide-key]] + - [[#packages-that-do-display-management][Packages that do display management]] + +* Description +This layer enables [[https://github.com/bmag/emacs-purpose][window-purpose]], which provides an alternative, purpose-based +window manager for Emacs. With this layer, your window layout should be robust +and shouldn't change too much when opening all sorts of buffers. + +Regular [[https://github.com/m2ym/popwin-el][popwin]] is not triggered when window-purpose is enabled. However, +the window-purpose layer provides a =purpose-popwin= extension, which +brings popwin's behavior to window-purpose and solves that problem. + +** Features: +- Window layout is more robust and less likely to change unintentionally +- Dedicate window to a purpose +- User-defined purposes +- Extensible window display behavior +- Empty =purpose-mode-map=, to avoid conflicts with other key maps +- Replicate popwin behavior for purpose-mode - almost no regression in popup behavior from using window-purpose. +- Reuses popwin's settings: =popwin:special-display-config=, =popwin:popup-window-height= and =popwin:popup-window-width=. +- Difference from popwin: when several windows are open, popup window is sometimes bigger than with regular popwin in the same situation. + +* Purposes +window-purpose contains a configuration which assigns a purpose for each +buffer. Later, when Emacs needs to display a buffer in a window, its purpose +helps make a better decision of which window to use. + +For example, consider the following case: Emacs frame shows three windows - one +for code, one for a terminal and one general-purpose window. The general window +is selected and you want to open a code file. How do you ensure that the code +file will be displayed in the code window? With window-purpose you don't +need to worry about it - you open the file and window-purpose places it in +the correct window. + +Additionally, you can dedicate a window to a purpose - so that window is +reserved only for buffers that share that purpose. + +** switch-to-buffer and display-buffer +In regular Emacs, =switch-to-buffer= follows different rules than the other +switching and popping commands, because it doesn't use =display-buffer= (which +the other commands do). With window-purpose, this behavior is fixed. The +result is a better control over how buffers are displayed, since +=switch-to-buffer= doesn't ignore the user's customizations anymore. + +** misc +- specialized helm source similar to =helm-source-buffers-list= + +* Install +To use this configuration layer, add it to your =~/.spacemacs=. You will need to +add =spacemacs-purpose= to the existing =dotspacemacs-configuration-layers= list in +this file. + +* Usage +With window-purpose layer installed, =purpose-mode= and =pupo-mode= are enabled. +You can toggle =purpose-mode= (~SPC : purpose-mode~) at any time to return to +purpose-less behavior. You can toggle =pupo-mode= (~SPC : pupo-mode~) to turn +off only the purpose-popwin integration. + +If you change =popwin:special-display-config= in your =dotspacemacs/config=, you +should call =pupo/update-purpose-config= to update purpose-popwin with those +changes. + +See [[https://github.com/bmag/emacs-purpose/wiki][window-purpose wiki]] to learn more about window-purpose. + +** Allocate purposes in layers +Layers may assign purposes to buffers that have been created by their packages. +This can either be done by a simple mode mapping or according to the buffer's +name. This follows the same idea as the autocomplete and syntax-checking layers. + +This means the configuration is not centralised in this layer but +spread among the individual language layers. To ensure +that users can still overwrite these configs it is important +to declare them uniformly in Spacemacs. + +To do so copy and adjust the following code: + +#+BEGIN_SRC emacs-lisp + ;; This will only be called if `window-purpose` is listed + ;; among the layer packages. + ;; This code also takes care that the right loading + ;; order is followed so there is no need for any + ;; `with-eval-after-load` constructs. + (defun shell/post-init-window-purpose () + (purpose-set-extension-configuration + :shell-layer + (purpose-conf :mode-purposes '((vterm-mode . terminal) + (eshell-mode . terminal) + (shell-mode . terminal) + (term-mode . terminal))))) + ;; This can also be a static name allocation + ;; :name-purposes '(("*Anaconda Help*" . Help)) + ;; or a dynamic one following a regexp + ;; :regexp-purposes '(("^\\*Anaconda" . general)) + +#+END_SRC + +** Overwrite purposes in dotfile +With layers defining all kinds of purposes there will surely +come the point where one wishes to change one or two of +these allocations to match ones own personal needs. + +This can easily be achieved by adding below code in +`dotspacemacs/user-config`: + +#+BEGIN_SRC emacs-lisp + ;; This will add user allocations with a higher + ;; priority than the ones from the layers. + ;; With this allocations can be completely + ;; customised. + (purpose-add-user-purposes :names '(("*Anaconda Help*" . general)) + :regexps '(("\\.hy$" . python))) +#+END_SRC + +* Key bindings + +| Key binding | Description | +|-------------+-------------------------------------------------------------------------------------| +| ~SPC r b~ | Open a buffer. Only buffers with the same purpose as the current buffer are listed. | +| ~SPC r B~ | Open any buffer and ignore window-purpose when displaying the buffer. | +| ~SPC r d b~ | Toggle dedication of selected window to its current buffer purpose. | +| ~SPC r d w~ | Toggle dedication of selected window to its current purpose. | +| ~SPC r D~ | Delete all non-dedicated windows. | +| ~SPC r p~ | Choose a purpose and open a buffer with that purpose. | +| ~SPC r P~ | Change the purpose of the selected window. Changes the window's buffer accordingly. | + +* Caveats +** Popwin and guide-key +If a buffer is displayed in two different windows, and only one of those windows +is purpose-dedicated, then invoking guide-key will cause both windows to become +purpose-dedicated. + +** Packages that do display management +Some packages that manage how windows are displayed, such as =gdb= with +=gdb-many-windows=, might not play nicely with =window-purpose=. However, it is +usually possible to find a solution. After all, even =helm= and =popwin= work +with =window-purpose=. diff --git a/layers/+spacemacs/spacemacs-purpose/funcs.el b/layers/+spacemacs/spacemacs-purpose/funcs.el new file mode 100644 index 000000000000..028ec32cd6a4 --- /dev/null +++ b/layers/+spacemacs/spacemacs-purpose/funcs.el @@ -0,0 +1,74 @@ +;;; funcs.el --- Spacemacs Purpose Layer functions File +;; +;; Copyright (c) 2012-2024 Sylvain Benner & Contributors +;; +;; Author: Bar Magal +;; URL: https://github.com/syl20bnr/spacemacs +;; +;; This file is not part of GNU Emacs. +;; +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. +;; +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. +;; +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + + + +;; eyebrowse integration + +(defun spacemacs/window-purpose-sync-eyebrowse () + "Synchronize window-purpose layer with eyebrowse. +Set `eyebrowse-new-workspace' value depending on the state of `purpose-mode'." + (defvar spacemacs--window-purpose-eyebrowse-new-workspace + eyebrowse-new-workspace + "Internal backup of `eyebrowse-new-workspace'.") + (require 'window-purpose) + (if purpose-mode + (setq eyebrowse-new-workspace #'spacemacs//window-purpose-new-workspace) + (setq eyebrowse-new-workspace + spacemacs--window-purpose-eyebrowse-new-workspace))) + +(defun spacemacs//window-purpose-new-workspace () + "Create a new eyebrowse workspace. +Replacement for `eyebrowse-new-workspace' that handles purpose-dedicated +windows correctly." + ;; call original `eyebrowse-new-workspace' (partially copied from + ;; `eyebrowse-switch-to-window-config') + (cond + ((stringp spacemacs--window-purpose-eyebrowse-new-workspace) + (switch-to-buffer (get-buffer-create + spacemacs--window-purpose-eyebrowse-new-workspace))) + ((functionp spacemacs--window-purpose-eyebrowse-new-workspace) + (funcall spacemacs--window-purpose-eyebrowse-new-workspace)) + (t (switch-to-buffer "*scratch*"))) + + ;; in case opening the new buffer splitted the frame (e.g. + ;; `eyebrowse-switch-to-window-config' was called from a purpose-dedicated + ;; buffer) + (delete-other-windows)) + + +;; Popwin integration + +(defun spacemacs/window-purpose-sync-popwin () + "Synchronize window-purpose layer with popwin. +Enable or disable advices to popwin, according to the state of `purpose-mode'." + (require 'window-purpose) + (if purpose-mode + (progn + (advice-add #'popwin:create-popup-window + :before #'window-purpose/save-dedicated-windows) + (advice-add #'popwin:create-popup-window + :after #'window-purpose/restore-dedicated-windows)) + (advice-remove #'popwin:create-popup-window + #'window-purpose/save-dedicated-windows) + (advice-remove #'popwin:create-popup-window + #'window-purpose/restore-dedicated-windows))) diff --git a/layers/+spacemacs/spacemacs-purpose/local/spacemacs-purpose-popwin/spacemacs-purpose-popwin.el b/layers/+spacemacs/spacemacs-purpose/local/spacemacs-purpose-popwin/spacemacs-purpose-popwin.el new file mode 100644 index 000000000000..81c19282df2c --- /dev/null +++ b/layers/+spacemacs/spacemacs-purpose/local/spacemacs-purpose-popwin/spacemacs-purpose-popwin.el @@ -0,0 +1,249 @@ +;;; spacemacs-purpose-popwin.el --- Purpose extension to act like Popwin -*- lexical-binding: t -*- + +;;; Commentary: + +;;; Code: + +(require 'window-purpose) + +(defcustom pupo-split-active-window nil + "Non-nil if Pupo splits the active window. +Nil if Pupo splits the entire frame." + :type '(boolean) + :group 'pupo) + +(defconst pupo--direction-to-purpose '((left . popl) + (right . popr) + (top . popt) + (bottom . popb)) + "Mapping of popwin positions to purposes.") + +(defconst pupo--purposes + (cl-loop for (direction . purpose) in pupo--direction-to-purpose collect purpose) + "List of purposes used to present popwin positions.") + +(defvar pupo--windows nil + "List of popup windows.") +(defvar pupo--auto-windows nil + "List of popup windows that should be closed automatically.") +(defvar pupo--saved-buffers nil + "Temporary list of displayed popup buffers.") +(defvar pupo--saved-auto-buffers nil + "Temporary list of non-sticky displayed popup buffers.") + +(defun pupo//popup-function (position size) + "Generate a display function to create a popup window. +POSITION should be one of bottom, top, left and right. +SIZE should be either a positive number of nil. Size is interpreted as +width or height depending on POSITION." + (let* ((size (cl-case position + (left (purpose--normalize-width (or size + popwin:popup-window-width))) + (right (purpose--normalize-width (or size + popwin:popup-window-width))) + (top (purpose--normalize-height (or size + popwin:popup-window-height))) + (bottom (purpose--normalize-height (or size + popwin:popup-window-height))))) + (size (when size (- size))) + (side (cl-case position + (left 'left) + (right 'right) + (top 'above) + (bottom 'below)))) + (lambda (buffer alist) + (let* ((main-window (if pupo-split-active-window + (selected-window) + (frame-root-window))) + (window (ignore-errors (split-window main-window size side)))) + (when window + (purpose-change-buffer buffer window 'window alist)))))) + +(defun pupo//position-to-display-function (position width height) + "Generate a display function for creating a popup window. +POSITION defaults to bottom. +WIDTH and HEIGHT should be either a positive number or nil." + (cl-case (or position 'bottom) + ((left right) (pupo//popup-function position width)) + ((top bottom) (pupo//popup-function position height)))) + +(defun pupo//position-to-purpose (position) + "Translate POSITION to a purpose. +Direction -> purpose: +left -> popl +right -> popr +top -> popt +bottom -> popb +POSITION defaults to bottom." + (cl-case (or position 'bottom) + ;; names are short so they don't take much room in the mode-line + (left 'popl) + (right 'popr) + (top 'popt) + (bottom 'popb))) + +(defun pupo//actions (settings) + "Generate list of display functions for displaying a popup window. +SETTINGS is the settings for the popup buffer, and corresponds to what +popwin calls \"config keywords\"." + (delq nil + (list #'purpose-display-reuse-window-buffer + (unless (plist-get settings :dedicated) + #'purpose-display-reuse-window-purpose) + (pupo//position-to-display-function (plist-get settings :position) + (plist-get settings :width) + (plist-get settings :height))))) + +(defun pupo/display-condition (_purpose buffer _alist) + "A condition to be used in `purpose-special-action-sequences'. +Return non-nil if BUFFER is a popup buffer, according to the settings in +`popwin:special-display-config'. + +See `purpose-special-action-sequences' for a description of _PURPOSE, +BUFFER and _ALIST." + (popwin:match-config buffer)) + +(defun pupo/display-function (buffer alist) + "A display function to be used in `purpose-special-action-sequences'. +Display BUFFER as a popup buffer, according to the settings in +`popwin:special-display-config'. + +See `purpose-special-action-sequences' for a description of BUFFER and +ALIST." + (cl-do ((display-fns (pupo//actions (cdr (popwin:match-config buffer))) + (cdr display-fns)) + (window nil (and display-fns (funcall (car display-fns) buffer alist)))) + ((or window (null display-fns)) window))) + +(defun pupo/after-display (window) + "Additional initialization for popup windows. +Sets properties for WINDOW and updates some variables, if WINDOW is a +popup window. + +This function should be hooked to `purpose-display-buffer-functions'." + (let* ((buffer (window-buffer window)) + (config (popwin:match-config buffer)) + (settings (cdr (popwin:listify config)))) + (when config + (setq pupo--windows (delete window pupo--windows)) + (push window pupo--windows) + (when (plist-get settings :dedicated) + (set-window-dedicated-p window t)) + (unless (plist-get settings :stick) + (push window pupo--auto-windows)) + (unless (or (minibuffer-window-active-p (selected-window)) + (plist-get settings :noselect)) + ;; popwin selects window unless :noselect is t + ;; in contrast, popwin doesn't prevent selection when :noselect is nil + (select-window window)) + ;; make \\[C-g] delete last popup window + (global-set-key [remap keyboard-quit] #'pupo/close-window)))) + +(defun pupo//safe-delete-window (&optional window) + "Delete WINDOW if possible. +Return t if successful, nil otherwise. +WINDOW defaults to the selected window." + (ignore-errors (delete-window window) t)) + +(defun pupo/auto-delete-windows (window) + "Delete all non-sticky popup windows, unless WINDOW is a popup window. +This function should be hooked to `purpose-display-buffer-functions'." + (unless (member (purpose-window-purpose window) pupo--purposes) + (mapc #'pupo//safe-delete-window pupo--auto-windows) + (setq pupo--auto-windows nil))) + +(defun pupo/close-window () + "Close most recent popup window. +This command can be used repeatedly to close all popup windows." + (interactive) + (let ((searching t)) + (while (and pupo--windows searching) + (when (window-live-p (car pupo--windows)) + (pupo//safe-delete-window (car pupo--windows)) + (setq searching nil)) + (pop pupo--windows)) + (unless pupo--windows + ;; no more popup windows, revert \\[C-g] to `keyboard-quit' + (global-set-key [remap keyboard-quit] nil)))) + +(defun pupo/close-all-windows () + "Close all popup windows." + (interactive) + (dolist (purpose pupo--purposes) + (mapc #'pupo//safe-delete-window (purpose-windows-with-purpose purpose)))) + +(defun pupo/popwin-config-to-purpose-config () + "Create a purpose configuration matching current popwin's settings. +Return a `purpose-conf' object. +Popwin's settings are taken from `popwin:special-display-config'." + (let (mode-purposes name-purposes regexp-purposes) + (cl-loop for config-entry in popwin:special-display-config + for (pattern . settings) = (popwin:listify config-entry) + do + (push (cons pattern + (pupo//position-to-purpose (plist-get settings :position))) + (cond ((symbolp pattern) mode-purposes) + ((plist-get settings :regexp) regexp-purposes) + (t name-purposes)))) + (purpose-conf :mode-purposes mode-purposes + :name-purposes name-purposes + :regexp-purposes regexp-purposes))) + +(defun pupo/update-purpose-config () + "Update purpose configuration according to current popwin's settings. +Popwin's settings are taken from `popwin:special-display-config'." + (purpose-set-extension-configuration :pupo (pupo/popwin-config-to-purpose-config))) + +(define-minor-mode pupo-mode + "Minor mode for combining `purpose-mode' and `popwin-mode'." + :global t + (if pupo-mode + (progn + (pupo/update-purpose-config) + (push '(pupo/display-condition pupo/display-function) + purpose-special-action-sequences) + (add-hook 'purpose-display-buffer-functions #'pupo/after-display) + (add-hook 'purpose-display-buffer-functions #'pupo/auto-delete-windows)) + (purpose-del-extension-configuration :pupo) + (setq purpose-special-action-sequences + (delete '(pupo/display-condition pupo/display-function) + purpose-special-action-sequences)) + (remove-hook 'purpose-display-buffer-functions #'pupo/after-display) + (remove-hook 'purpose-display-buffer-functions #'pupo/auto-delete-windows))) + +(define-advice popwin:create-popup-window (:before pupo/before-popwin-create) + "Save current popup windows for later restoration. +The windows are restored in `pupo/after-popwin-create'. +Note that the windows themselves aren't saved, but some internal +variables are updated instead." + (setq pupo--saved-buffers (mapcar #'window-buffer pupo--windows)) + (setq pupo--saved-auto-buffers (mapcar #'window-buffer pupo--auto-windows))) + +(define-advice popwin:create-popup-window (:after pupo/after-popwin-create) + "Restore popup windows. +The windows were saved in `pupo/before-popwin-create'. +Note that the windows themselves aren't restored, but some internal +variables are updated instead." + (setq pupo--windows nil) + (cl-loop for buffer in pupo--saved-buffers + do (setq pupo--windows + (append pupo--windows + (get-buffer-window-list buffer)))) + (setq pupo--auto-windows nil) + (cl-loop for buffer in pupo--saved-auto-buffers + do (setq pupo--auto-windows + (append pupo--auto-windows + (get-buffer-window-list buffer))))) + +(defun pupo/sync-advices () + (if pupo-mode + (progn + (advice-add #'popwin:create-popup-window :before #'pupo/before-popwin-create) + (advice-add #'popwin:create-popup-window :after #'pupo/after-popwin-create)) + (advice-remove #'popwin:create-popup-window #'pupo/before-popwin-create) + (advice-remove #'popwin:create-popup-window #'pupo/after-popwin-create))) +(add-hook 'pupo-mode-hook #'pupo/sync-advices) + +(provide 'spacemacs-purpose-popwin) + +;;; spacemacs-purpose-popwin.el ends here diff --git a/layers/+spacemacs/spacemacs-purpose/packages.el b/layers/+spacemacs/spacemacs-purpose/packages.el new file mode 100644 index 000000000000..33a8f144b4f7 --- /dev/null +++ b/layers/+spacemacs/spacemacs-purpose/packages.el @@ -0,0 +1,169 @@ +;;; packages.el --- Spacemacs Purpose Layer packages File for Spacemacs +;; +;; Copyright (c) 2012-2024 Sylvain Benner & Contributors +;; +;; Author: Bar Magal +;; URL: https://github.com/syl20bnr/spacemacs +;; +;; This file is not part of GNU Emacs. +;; +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. +;; +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. +;; +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + + +(setq spacemacs-purpose-packages + '(eyebrowse + (helm-purpose :requires helm) + (ivy-purpose :requires ivy) + popwin + (spacemacs-purpose-popwin + :location local + :requires popwin) + window-purpose)) + +(defun spacemacs-purpose/pre-init-eyebrowse () + (spacemacs|use-package-add-hook eyebrowse + :post-config + (progn + (add-hook 'purpose-mode-hook #'spacemacs/window-purpose-sync-eyebrowse) + ;; sync with eyebrowse now + (spacemacs/window-purpose-sync-eyebrowse)))) + +(defun spacemacs-purpose/init-helm-purpose () + (use-package helm-purpose + :defer t + :init + (setq purpose-preferred-prompt 'helm) + ;; remap bindings defined with `spacemacs/set-leader-keys' + (global-set-key [remap purpose-switch-buffer-with-purpose] + #'helm-purpose-switch-buffer-with-purpose) + (global-set-key [remap switch-buffer-without-purpose] + #'helm-purpose-mini-ignore-purpose) + (global-set-key [remap purpose-switch-buffer-with-some-purpose] + #'helm-purpose-switch-buffer-with-some-purpose))) + +(defun spacemacs-purpose/init-ivy-purpose () + ;; vanilla lets `ivy' take over + (use-package ivy-purpose + :defer t + :init + (setq purpose-preferred-prompt 'vanilla) + (global-set-key [remap purpose-switch-buffer-with-purpose] + #'ivy-purpose-switch-buffer-with-purpose) + (global-set-key [remap purpose-switch-buffer-without-purpose] + #'ivy-purpose-switch-buffer-without-purpose) + (global-set-key [remap purpose-switch-buffer-with-some-purpose] + #'ivy-purpose-switch-buffer-with-some-purpose))) + +(defun spacemacs-purpose/pre-init-popwin () + ;; when popwin creates a popup window, it removes the `purpose-dedicated' + ;; window parameter from all windows, so we must save and restore it + ;; ourselves. this works well as long as no buffer is displayed in more than + ;; one window. if a buffer is displayed in several windows, and at least one + ;; of these windows is purpose-dedicated, then all these windows will become + ;; purpose-dedicated after popwin creates a popup window. + ;; there is no problem if the local spacemacs-purpose-popwin package is used, + ;; as long as the user doesn't call `popwin:create-popup-window' directly + ;; (e.g. from `helm-mini') + (spacemacs|use-package-add-hook popwin + :post-config + (progn + (defvar window-purpose--dedicated-windows nil) + (define-advice popwin:create-popup-window + (:before window-purpose/save-dedicated-windows) + (setq window-purpose--dedicated-windows + (cl-loop for window in (window-list) + if (purpose-window-purpose-dedicated-p window) + collect (window-buffer window)))) + (define-advice popwin:create-popup-window + (:after window-purpose/restore-dedicated-windows) + (cl-loop for buffer in window-purpose--dedicated-windows + do (cl-loop for window in (get-buffer-window-list buffer) + do (purpose-set-window-purpose-dedicated-p + window t)))) + (add-hook 'purpose-mode-hook #'spacemacs/window-purpose-sync-popwin) + (with-eval-after-load 'window-purpose + (spacemacs/window-purpose-sync-popwin) + ;; can't have both `purpose-mode' and `popwin-mode' active at the same + ;; time (see https://github.com/syl20bnr/spacemacs/issues/9593), but we + ;; use `popwin' for its configuration so we can't just exclude it, so + ;; current solution is to disable `popwin-mode' (which is enabled in + ;; popwin's :config block) + (popwin-mode -1))))) + +(defun spacemacs-purpose/pre-init-spacemacs-purpose-popwin () + (spacemacs|use-package-add-hook window-purpose + :post-config + (progn + ;; override popwin commands with pupo commands + (spacemacs/set-leader-keys + "wpp" #'pupo/close-window + "wpP" #'pupo/close-all-windows) + (pupo-mode)))) +(defun spacemacs-purpose/init-spacemacs-purpose-popwin () + (use-package spacemacs-purpose-popwin :commands pupo-mode)) + +(defun spacemacs-purpose/init-window-purpose () + (use-package window-purpose + :defer (spacemacs/defer) + :init + (add-hook 'emacs-startup-hook + (lambda () + (spacemacs|add-transient-hook window-configuration-change-hook + (lambda () (require 'window-purpose)) + lazy-load-window-purpose))) + ;; 'r' is for "puRpose" ('w', 'p' are crowded, 'W', 'P' aren't + ;; comfortable) + (spacemacs/set-leader-keys + "rb" 'purpose-switch-buffer-with-purpose + "rB" 'switch-buffer-without-purpose + "rdb" 'purpose-toggle-window-buffer-dedicated + "rdw" 'purpose-toggle-window-purpose-dedicated + "rD" 'purpose-delete-non-dedicated-windows + "rp" 'purpose-switch-buffer-with-some-purpose + "rP" 'purpose-set-window-purpose) + :config + (purpose-mode) + ;; fix around window-purpose not respecting -other-window requirement + ;; of clone-indirect-buffer-other-window + ;; see https://github.com/bmag/emacs-purpose/issues/122 + (defalias 'clone-indirect-buffer-other-window-without-purpose + (without-purpose-command #'clone-indirect-buffer-other-window)) + + ;; change `switch-to-buffer' display preferences according to + ;; `dotspacemacs-switch-to-buffer-prefers-purpose'. This affects actions + ;; like `spacemacs/alternate-buffer', and opening buffers from Dired + (setcdr (assq 'switch-to-buffer purpose-action-sequences) + (if dotspacemacs-switch-to-buffer-prefers-purpose + '(purpose-display-reuse-window-buffer + purpose-display-reuse-window-purpose + purpose-display-maybe-same-window + purpose-display-maybe-other-window + purpose-display-maybe-other-frame + purpose-display-maybe-pop-up-window + purpose-display-maybe-pop-up-frame) + '(purpose-display-maybe-same-window + purpose-display-reuse-window-buffer + purpose-display-reuse-window-purpose + purpose-display-maybe-other-window + purpose-display-maybe-other-frame + purpose-display-maybe-pop-up-window + purpose-display-maybe-pop-up-frame))) + ;; overriding `purpose-mode-map' with empty keymap, so it doesn't conflict + ;; with original `C-x C-f', `C-x b', etc. and `semantic' key bindings. + (setcdr purpose-mode-map nil) + (spacemacs|diminish purpose-mode) + (purpose-x-golden-ratio-setup) + + ;; Show magit-log-select and diff in two windows + (purpose-x-magit-multi-on)))