diff --git a/modules/config/default/+evil-bindings.el b/modules/config/default/+evil-bindings.el index 4e0d1f3dd..e44e3beb3 100644 --- a/modules/config/default/+evil-bindings.el +++ b/modules/config/default/+evil-bindings.el @@ -374,6 +374,7 @@ :desc "New named workspace" "N" #'+workspaces/new-named :desc "Restore last session" "R" #'+workspaces/restore-last-session :desc "Save session" "s" #'+workspaces/save-session + :desc "Open project workspace" "p" #'+workspaces/open-in-project :desc "Kill this workspace" "k" #'+workspaces/kill :desc "Kill other workspaces" "K" #'+workspaces/kill-others :desc "Kill session" "X" #'+workspaces/kill-session diff --git a/modules/ui/workspaces/autoload/workspaces.el b/modules/ui/workspaces/autoload/workspaces.el index b7e42fbae..d984f160b 100644 --- a/modules/ui/workspaces/autoload/workspaces.el +++ b/modules/ui/workspaces/autoload/workspaces.el @@ -233,4 +233,71 @@ its associated frame, if one exists) and move to the next." (call-interactively #'+workspaces/kill)) ((user-error "Can't delete last workspace")))) +;;;###autoload +(defun +workspaces/open-in-project (project &optional force?) + "Open an existing or new workspace for PROJECT. + +Afterwards, executes `+workspaces-switch-project-function', if set. + +If FORCE?, always create a workspace, even if it already exists." + (interactive + (list (if-let* ((projects (projectile-relevant-known-projects))) + (projectile-completing-read "Switch to project: " projects) + (user-error "There are no known projects")) + current-prefix-arg)) + (let* ((project (expand-file-name project)) + (existing-tab-names (tabspaces--list-tabspaces)) + (original-tab-name (or (cdr (assoc project tabspaces-project-tab-map)) + (tabspaces-generate-descriptive-tab-name project existing-tab-names))) + (tab-name original-tab-name) + (session (tabspaces--get-project-session-file-for-restore project)) + (project-directory project) ; Use the full path as the project directory + (project-exists (cl-member project projectile-known-projects :test #'file-equal-p)) + (create-new-tab (or force? (not (member tab-name existing-tab-names))))) + + (with-current-buffer (doom-fallback-buffer) + (setq-local default-directory project-directory)) + + (cond + ;; If there is no tab nor project, create both + ((not project-exists) + (message "Creating new workspace for project...") + (tab-bar-new-tab) + (tab-bar-rename-tab tab-name) + (projectile-add-known-project project-directory) + (switch-to-buffer (doom-fallback-buffer))) + + ;; If project and tab exist, but we want a new tab + ((and project-exists + (member tab-name existing-tab-names) + create-new-tab) + (message "Creating new workspace for known project...") + (let ((new-tab-name (generate-unique-numbered-tab-name tab-name existing-tab-names))) + (tab-bar-new-tab) + (tab-bar-rename-tab new-tab-name) + (setq tab-name new-tab-name) + (switch-to-buffer (doom-fallback-buffer)))) + + ;; If project and tab exist, switch to it + ((and project-exists + (member tab-name existing-tab-names)) + (message "Switching to existing project workspace...") + (tab-bar-switch-to-tab tab-name)) + + ;; If project exists, but no corresponding tab, open a new tab + (project-exists + (message "Creating new workspace for existing project...") + (tab-bar-new-tab) + (tab-bar-rename-tab tab-name) + (if (file-exists-p session) + (tabspaces-restore-session session) + (switch-to-buffer (doom-fallback-buffer)))) + + ((user-error "Failed to find the project or create a workspace"))) + + ;; Update tabspaces-project-tab-map (only for the main tab, not numbered duplicates) + (unless (string-match-p "<[0-9]+>$" tab-name) + (setf (alist-get project-directory tabspaces-project-tab-map nil nil #'equal) + tab-name)))) + ;;; workspaces.el ends here diff --git a/modules/ui/workspaces/config.el b/modules/ui/workspaces/config.el index c41b0448a..12ab06000 100644 --- a/modules/ui/workspaces/config.el +++ b/modules/ui/workspaces/config.el @@ -38,4 +38,26 @@ ;; Per-workspace `winner-mode' history - (add-to-list 'window-persistent-parameters '(winner-ring . t))) + (add-to-list 'window-persistent-parameters '(winner-ring . t)) + + ;; HACK: Tabspaces is hardcoded to equate the root of a version controlled + ;; project as the only kind of project root, and lacks integration with + ;; project.el or projectile. This fixes that. + ;; REVIEW: PR this upstream! + (defadvice! +workspaces--project-root-a (fn &rest args) + :around #'tabspaces-save-current-project-session + :around #'tabspaces--get-project-session-file + (letf! ((#'vc-root-dir #'doom-project-root)) + (apply fn args))) + (defadvice! +workspaces--project-name-a (fn &rest args) + :around #'tabspaces--project-name + (or (doom-project-root) + (apply fn args))) + + ;; HACK: `tabspaces-open-or-create-project-and-workspace' has *so* much + ;; hardcoded and opinionated behavior, so I replace it with a version that + ;; cooperates with projectile/project, the dashboard, our custom + ;; `tabspaces-session-project-session-store', and fixes its bugs. + ;; REVIEW: Issues should be raised upstream! + (advice-add #'tabspaces-open-or-create-project-and-workspace + :override #'+workspaces/open-in-project))