diff options
| author | Constantine Vetoshev <vetoshev@gmail.com> | 2026-03-29 08:27:34 -0700 |
|---|---|---|
| committer | Constantine Vetoshev <vetoshev@gmail.com> | 2026-03-29 08:27:34 -0700 |
| commit | 5c5f1e2b986f58266c2774573d189f87d58cb55e (patch) | |
| tree | 58dd93a716a6a029651236373fc8b52d5f5841a6 | |
| parent | 0e632bb8482f94f45a7b1209bb2bd38aadf84003 (diff) | |
Add opt-in switching for other-perspective buffers.
| -rw-r--r-- | CHANGELOG.md | 5 | ||||
| -rw-r--r-- | README.md | 4 | ||||
| -rw-r--r-- | perspective.el | 48 | ||||
| -rw-r--r-- | test/test-perspective.el | 63 |
4 files changed, 111 insertions, 9 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index a69daa0..2720880 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Temporary `switch-to-buffer` displays with non-nil `norecord` no longer add the displayed buffer to the current perspective. This fixes `consult-buffer` preview importing previewed buffers ([#225](https://github.com/nex3/perspective-el/issues/225)). +### Added + +- `persp-switch-to-buffer-behavior`: configure whether `persp-switch-to-buffer*` imports buffers from other perspectives into the current perspective or switches to an owning perspective instead. + + ## [2.21] — 2026-02-10 ### Fixed @@ -383,6 +383,10 @@ Both these functions behave like the built-ins, but use `completing-read` directly. When called normally, they list buffers filtered by the current perspective. With a prefix argument, they list buffers in all perspectives. +`persp-switch-to-buffer*` imports a selected buffer into the current +perspective by default; set `persp-switch-to-buffer-behavior` to `switch` if +you prefer it to jump to an existing owning perspective instead. + The following sample `use-package` invocation changes Emacs default key bindings to use the replacements: diff --git a/perspective.el b/perspective.el index 04fdbc0..7a8bcdb 100644 --- a/perspective.el +++ b/perspective.el @@ -145,6 +145,20 @@ TODO: Eventually eliminate this setting?" (defalias 'persp-avoid-killing-last-buffer-in-perspective 'persp-feature-flag-prevent-killing-last-buffer-in-perspective) +(defcustom persp-switch-to-buffer-behavior 'import + "How Perspective-owned buffer switchers should handle other-perspective buffers. + +If the value is `import', display the selected buffer in the current +perspective, importing it if necessary. + +If the value is `switch', switch to a perspective that already contains +the selected buffer before displaying it. + +This currently affects `persp-switch-to-buffer*'." + :group 'perspective-mode + :type '(choice (const :tag "Import into current perspective" import) + (const :tag "Switch to owning perspective" switch))) + (defcustom persp-purge-initial-persp-on-save nil "When non-nil, kills all the buffers in the initial perspective upon state save. @@ -974,19 +988,35 @@ Prefers perspectives in the selected frame." (cons frame (persp-name persp))))) nil) -(defun persp-switch-to-buffer (buffer-or-name) +(cl-defun persp--show-buffer (buffer-or-name &key norecord (behavior 'import)) + "Display BUFFER-OR-NAME according to BEHAVIOR. + +If BEHAVIOR is `import', display the buffer in the current perspective. +If it is `switch', switch to a perspective containing the buffer first. + +If NORECORD is non-nil, pass it through to `switch-to-buffer' and +`persp-switch'." + (let ((buffer (window-normalize-buffer-to-switch-to buffer-or-name))) + (cl-ecase behavior + (import + (switch-to-buffer buffer norecord)) + (switch + (if (persp-is-current-buffer buffer) + (switch-to-buffer buffer norecord) + (let ((other-persp (persp-buffer-in-other-p buffer))) + (when (eq (car-safe other-persp) (selected-frame)) + (persp-switch (cdr other-persp) norecord)) + (if-let ((window (get-buffer-window buffer (selected-frame)))) + (select-window window norecord) + (switch-to-buffer buffer norecord)))))))) + +(defun persp-switch-to-buffer (buffer-or-name &optional norecord) "Like `switch-to-buffer', but switches to another perspective if necessary." (interactive (list (let ((read-buffer-function nil)) (read-buffer-to-switch "Switch to buffer: ")))) - (let ((buffer (window-normalize-buffer-to-switch-to buffer-or-name))) - (if (persp-is-current-buffer buffer) - (switch-to-buffer buffer) - (let ((other-persp (persp-buffer-in-other-p buffer))) - (when (eq (car-safe other-persp) (selected-frame)) - (persp-switch (cdr other-persp))) - (switch-to-buffer buffer))))) + (persp--show-buffer buffer-or-name :norecord norecord :behavior 'switch)) (cl-defun persp-maybe-kill-buffer () "Don't kill a buffer if it's the only buffer in a perspective. @@ -1639,7 +1669,7 @@ PERSP-SET-IDO-BUFFERS)." nil nil nil nil other))))) (let ((buffer (window-normalize-buffer-to-switch-to buffer-or-name))) - (switch-to-buffer buffer))) + (persp--show-buffer buffer :behavior persp-switch-to-buffer-behavior))) ;; Buffer killing integration: useful for frameworks which enhance the ;; built-in completing-read (e.g., Selectrum). diff --git a/test/test-perspective.el b/test/test-perspective.el index 6c55ca5..fbf2917 100644 --- a/test/test-perspective.el +++ b/test/test-perspective.el @@ -2130,6 +2130,69 @@ buffers into any perspective." (should-not (persp-is-current-buffer dummy-buffer)) (should (persp-test-buffer-in-persps dummy-buffer "A"))))) +(ert-deftest basic-persp-switch-to-buffer*-imports-by-default () + "Test that `persp-switch-to-buffer*' imports other-perspective buffers by default." + (persp-test-with-persp + (persp-test-with-temp-buffers (dummy-buffer) + (persp-switch "A") + (switch-to-buffer dummy-buffer) + (should (persp-test-buffer-in-persps dummy-buffer "A")) + (persp-switch "main") + (should-not (persp-is-current-buffer dummy-buffer)) + (let ((persp-switch-to-buffer-behavior 'import)) + (persp-switch-to-buffer* dummy-buffer)) + (should (equal (persp-current-name) "main")) + (should (eq (current-buffer) dummy-buffer)) + (should (persp-test-buffer-in-persps dummy-buffer "main" "A"))))) + +(ert-deftest basic-persp-switch-to-buffer*-switches-when-configured () + "Test that `persp-switch-to-buffer*' can switch to the owning perspective." + (persp-test-with-persp + (persp-test-with-temp-buffers (dummy-buffer) + (persp-switch "A") + (switch-to-buffer dummy-buffer) + (should (persp-test-buffer-in-persps dummy-buffer "A")) + (persp-switch "main") + (should-not (persp-is-current-buffer dummy-buffer)) + (let ((persp-switch-to-buffer-behavior 'switch)) + (persp-switch-to-buffer* dummy-buffer)) + (should (equal (persp-current-name) "A")) + (should (eq (current-buffer) dummy-buffer)) + (should (persp-test-buffer-in-persps dummy-buffer "A"))))) + +(ert-deftest basic-persp-switch-to-buffer*-selects-existing-window-when-switching () + "Test that switching prefers an existing visible window for the target buffer." + (unwind-protect + (persp-test-with-persp + (persp-test-with-files nil (A1 A2 A3 B1 B2 B3 B4) + (persp-test-make-sample-environment) + (persp-switch "A") + (persp-set-buffer A2) + (persp-switch "main") + (let ((persp-switch-to-buffer-behavior 'switch)) + (persp-switch-to-buffer* A2)) + (should (equal (persp-current-name) "A")) + (should (eq (current-buffer) A2)) + (should (eq (window-buffer (selected-window)) A2)) + (should (equal (list "A1" "A2" "A3") + (sort (mapcar #'buffer-name (mapcar #'window-buffer (window-list))) + #'string-lessp))))) + (persp-test-clean-files "A1" "A2" "A3" "B1" "B2" "B3" "B4"))) + +(ert-deftest basic-persp-switch-to-buffer-ignores-configured-import-behavior () + "Test that `persp-switch-to-buffer' keeps its switch semantics." + (persp-test-with-persp + (persp-test-with-temp-buffers (dummy-buffer) + (persp-switch "A") + (switch-to-buffer dummy-buffer) + (should (persp-test-buffer-in-persps dummy-buffer "A")) + (persp-switch "main") + (let ((persp-switch-to-buffer-behavior 'import)) + (persp-switch-to-buffer dummy-buffer)) + (should (equal (persp-current-name) "A")) + (should (eq (current-buffer) dummy-buffer)) + (should (persp-test-buffer-in-persps dummy-buffer "A"))))) + (defmacro persp-test-make-sample-environment () "Make a test environment with the following window layout: |
