diff --git a/modules/feature/workspaces/autoload/workspaces.el b/modules/feature/workspaces/autoload/workspaces.el index f72fc182f..de2a4984d 100644 --- a/modules/feature/workspaces/autoload/workspaces.el +++ b/modules/feature/workspaces/autoload/workspaces.el @@ -187,6 +187,13 @@ buffers." +workspaces-main))) (persp-frame-switch name)) +;;;###autoload +(defun +workspace-on-new-frame (frame &optional _new-frame-p) + "Spawn a perspective for each new frame." + (select-frame frame) + (+workspace/new) + (set-frame-parameter frame 'assoc-persp (+workspace-current-name))) + ;; ;; Interactive commands @@ -473,12 +480,10 @@ the workspace and move to the next." (interactive) (message "%s" (+workspace--tabline))) -;;;###autoload -(defun +workspace-on-new-frame (frame &optional _new-frame-p) - "Spawn a perspective for each new frame." - (select-frame frame) - (+workspace/new) - (set-frame-parameter frame 'assoc-persp (+workspace-current-name))) + +;; +;; Hooks +;; ;;;###autoload (defun +workspaces|delete-associated-workspace-maybe (frame) @@ -489,6 +494,32 @@ the workspace and move to the next." (not (equal frame-persp +workspaces-main))) (+workspace/delete frame-persp))))) +;;;###autoload +(defun +workspaces|per-project (&optional root) + "Open a new workspace when switching to another project. + +Ensures the scratch (or dashboard) buffers are CDed into the project's root." + (when persp-mode + (let ((cwd default-directory)) + (+workspace-switch (projectile-project-name) t) + (switch-to-buffer (doom-fallback-buffer)) + (setq default-directory cwd) + (+workspace-message + (format "Switched to '%s' in new workspace" (+workspace-current-name)) + 'success)))) + +;;;###autoload +(defun +workspaces|cleanup-unassociated-buffers () + (cl-loop for buf in (buffer-list) + unless (persp--buffer-in-persps buf) + if (kill-buffer buf) + do (cl-incf n))) + + +;; +;; Advice +;; + ;;;###autoload (defun +workspaces*autosave-real-buffers (orig-fn &rest args) "Don't autosave if no real buffers are open." diff --git a/modules/feature/workspaces/config.el b/modules/feature/workspaces/config.el index a6365e7f9..c66148228 100644 --- a/modules/feature/workspaces/config.el +++ b/modules/feature/workspaces/config.el @@ -42,56 +42,51 @@ renamed.") ;; Bootstrap (add-hook 'doom-post-init-hook #'+workspaces|init) (add-hook 'after-make-frame-functions #'+workspaces|init) - + (add-hook 'persp-mode-hook #'+workspaces|init-persp-mode) + ;; only auto-save when real buffers are present + (advice-add #'persp-asave-on-exit :around #'+workspaces*autosave-real-buffers) + ;; Modify `delete-window' to close the workspace if used on the last window (define-key persp-mode-map [remap delete-window] #'+workspace/close-window-or-workspace) + ;; For `doom/cleanup-session' + (add-hook 'doom-cleanup-hook #'+workspaces|cleanup-unassociated-buffers) - ;; per-frame and per-project workspaces + ;; per-frame workspaces (setq persp-init-new-frame-behaviour-override nil persp-interactive-init-frame-behaviour-override #'+workspace-on-new-frame) (add-hook 'delete-frame-functions #'+workspaces|delete-associated-workspace-maybe) - - (defun +workspaces|per-project (&optional root) - "Open a new workspace when switching to another project. - -Ensures the scratch (or dashboard) buffers are CDed into the project's root." - (when persp-mode - (let ((cwd default-directory)) - (+workspace-switch (projectile-project-name) t) - (switch-to-buffer (doom-fallback-buffer)) - (setq default-directory cwd) - (+workspace-message - (format "Switched to '%s' in new workspace" (+workspace-current-name)) - 'success)))) + ;; Per-project workspaces (setq projectile-switch-project-action #'+workspaces|per-project) - ;; only auto-save when real buffers are present - (advice-add #'persp-asave-on-exit :around #'+workspaces*autosave-real-buffers) - - (defun +workspaces|on-persp-mode () - ;; Remap `buffer-list' to current workspace's buffers in `doom-buffer-list' - (if persp-mode - (advice-add #'doom-buffer-list :override #'+workspace-buffer-list) - (advice-remove #'doom-buffer-list #'+workspace-buffer-list))) - (add-hook 'persp-mode-hook #'+workspaces|on-persp-mode) - + ;; (defun +workspaces|init (&optional frame) (unless persp-mode - (persp-mode +1) - ;; Ensure `persp-kill-buffer-query-function' is last in kill-buffer-query-functions - (remove-hook 'kill-buffer-query-functions 'persp-kill-buffer-query-function) - (add-hook 'kill-buffer-query-functions 'persp-kill-buffer-query-function t)) - (let ((frame (or frame (selected-frame)))) - (unless noninteractive - ;; The default perspective persp-mode makes (defined by - ;; `persp-nil-name') is special and doesn't actually represent a real - ;; persp object, so buffers can't really be assigned to it, among other - ;; quirks. We create a *real* main workspace to fill this role. - (unless (persp-with-name-exists-p +workspaces-main) - (persp-add-new +workspaces-main)) - ;; Switch to it if we aren't auto-loading the last session - (when (and (equal (safe-persp-name (get-current-persp)) persp-nil-name) - (= persp-auto-resume-time -1)) - (persp-frame-switch +workspaces-main frame))))) + (persp-mode +1)) + (unless noninteractive + ;; The default perspective persp-mode makes (defined by + ;; `persp-nil-name') is special and doesn't actually represent a real + ;; persp object, so buffers can't really be assigned to it, among other + ;; quirks. We create a *real* main workspace to fill this role. + (unless (persp-with-name-exists-p +workspaces-main) + (persp-add-new +workspaces-main)) + ;; Switch to it if we aren't auto-loading the last session + (when (and (equal (safe-persp-name (get-current-persp)) persp-nil-name) + (= persp-auto-resume-time -1)) + (persp-frame-switch +workspaces-main (or frame (selected-frame)))))) + + (defun +workspaces|init-persp-mode () + ;; Remap `buffer-list' to current workspace's buffers in `doom-buffer-list' + (cond (persp-mode + ;; Ensure `persp-kill-buffer-query-function' is last in kill-buffer-query-functions + (remove-hook 'kill-buffer-query-functions 'persp-kill-buffer-query-function) + (add-hook 'kill-buffer-query-functions 'persp-kill-buffer-query-function t) + + (advice-add #'switch-to-buffer :after #'+workspaces*auto-add-buffer) + (advice-add #'display-buffer :after #'+workspaces*auto-add-buffer) + (advice-add #'doom-buffer-list :override #'+workspace-buffer-list)) + (t + (advice-remove #'switch-to-buffer #'+workspaces*auto-add-buffer) + (advice-remove #'display-buffer #'+workspaces*auto-add-buffer) + (advice-remove #'doom-buffer-list #'+workspace-buffer-list)))) (defun +workspaces*auto-add-buffer (buffer &rest _) "Auto-associate buffers with perspectives upon opening them. @@ -101,7 +96,5 @@ Allows a perspective-specific buffer list via `+workspaces-buffer-list'." (not persp-temporarily-display-buffer) (doom-real-buffer-p buffer)) (persp-add-buffer buffer (get-current-persp) nil) - (force-mode-line-update t))) - (advice-add #'switch-to-buffer :after #'+workspaces*auto-add-buffer) - (advice-add #'display-buffer :after #'+workspaces*auto-add-buffer)) + (force-mode-line-update t)))) diff --git a/modules/feature/workspaces/test/autoload-workspaces.el b/modules/feature/workspaces/test/autoload-workspaces.el index ff69bfb67..25701711b 100644 --- a/modules/feature/workspaces/test/autoload-workspaces.el +++ b/modules/feature/workspaces/test/autoload-workspaces.el @@ -1,6 +1,7 @@ ;; -*- no-byte-compile: t; -*- ;;; feature/workspaces/test/autoload-workspaces.el +(load "persp-mode.el" nil t) (require! :feature workspaces) (defmacro with-workspace!! (buffer-args &rest body) @@ -8,28 +9,30 @@ (let ((buffers (cl-loop for bsym in buffer-args collect `(,bsym (get-buffer-create ,(symbol-name bsym)))))) - `(let (noninteractive) + `(let ((persp-auto-resume-time -1) + (persp-auto-save-opt 0) + persp-autokill-buffer-on-remove + persp-names-cache + noninteractive) (+workspaces|init) - (save-window-excursion - (let* (,@buffers) - (cl-loop with persp = (get-current-persp) - for buf in (list ,@(mapcar #'car buffers)) - do (persp-add-buffer buf persp) - do (with-current-buffer buf - (setq buffer-file-name (make-temp-file "workspaces-test-")))) - ,@body - (dolist (buf (list ,@(mapcar #'car buffers))) - (persp-remove-buffer buf) - (kill-buffer buf)))) - (persp-mode -1) - (setq *persp-hash* nil - persp-buffer-props-hash nil)))) + (let* (,@buffers) + (cl-loop with persp = (get-current-persp) + for buf in (list ,@(mapcar #'car buffers)) + do (persp-add-buffer buf persp) + do (with-current-buffer buf + (setq buffer-file-name (make-temp-file "workspaces-test-")))) + ,@body + (+workspace-delete +workspaces-main) + (let (kill-buffer-query-functions kill-buffer-hook) + (mapc #'kill-buffer (list ,@(mapcar #'car buffers))))) + (persp-mode -1)))) -;; +;; `+workspaces|init' (def-test! init (with-workspace!! () (should (equal (+workspace-current-name) +workspaces-main)))) +;; `+workspaces*auto-add-buffer' (def-test! auto-add-buffer-to-persp (let ((a (generate-new-buffer "a"))) (doom-set-buffer-real a t) @@ -38,6 +41,8 @@ (switch-to-buffer a) (should (+workspace-contains-buffer-p a))))) +;; `+workspace-current' +;; `+workspace-current-name' (def-test! current (with-workspace!! () (should (equal (+workspace-current-name) +workspaces-main)) @@ -49,6 +54,8 @@ (should (+workspace-p current-workspace)) (should (equal workspace current-workspace))))) +;; `+workspace-list' +;; `+workspace-list-names' (def-test! workspace-list (with-workspace!! () (should (equal (+workspace-list-names) @@ -56,6 +63,9 @@ (should (equal (+workspace-list) (list (+workspace-current)))))) +;; `+workspace-new' +;; `+workspace-rename' +;; `+workspace-delete' (def-test! workspace-crud "Creating, reading, updating and deleting workspaces." (with-workspace!! () @@ -71,6 +81,7 @@ (+workspace-delete renamed-workspace-name) (should (= (length (+workspace-list-names)) 1))))) +;; `+workspace-switch' (def-test! workspace-switch (with-workspace!! () (let ((new-workspace-name "*new-test*")) @@ -78,6 +89,8 @@ (should (+workspace-switch new-workspace-name t)) (should (equal (+workspace-current-name) new-workspace-name))))) +;; `+workspace-buffer-list' +;; `+workspace-contains-buffer-p' (def-test! buffer-list (with-workspace!! (a b) (let ((c (get-buffer-create "c")) @@ -93,3 +106,18 @@ (switch-to-buffer "d") (should-not (+workspace-contains-buffer-p d))))) +;; `+workspace/close-window-or-workspace' +(def-test! close-window-or-workspace + (with-workspace!! (a b) + (let ((ws (+workspace-current-name)) + (inhibit-message t)) + (+workspace-switch "test" t) + (split-window) + (should (equal (+workspace-current-name) "test")) + (should (= (length (doom-visible-windows)) 2)) + ;; kill window if more than one + (+workspace/close-window-or-workspace) + (should (= (length (doom-visible-windows)) 1)) + ;; kill workspace on last window + (+workspace/close-window-or-workspace) + (should (equal (+workspace-current-name) "main")))))