feat(workspaces): implement tab-local parameters

This commit is contained in:
Henrik Lissner 2025-09-23 18:38:47 -04:00
parent 96a1b72dda
commit a88cb71a11
No known key found for this signature in database
GPG key ID: B60957CA074D39A3
3 changed files with 73 additions and 31 deletions

View file

@ -122,11 +122,8 @@ PLIST can have the following properties:
(add-hook 'doom-switch-buffer-hook #'+doom-dashboard-reload-maybe-h) (add-hook 'doom-switch-buffer-hook #'+doom-dashboard-reload-maybe-h)
(add-hook 'delete-frame-functions #'+doom-dashboard-reload-frame-h) (add-hook 'delete-frame-functions #'+doom-dashboard-reload-frame-h)
;; Update `default-directory' in dashboard when switching workspaces ;; Update `default-directory' in dashboard when switching workspaces
(add-hook 'tab-bar-tab-post-select-functions #'+doom-dashboard--tab-detect-project-fn) ;; (add-hook 'tab-bar-tab-pre-select-functions #'+doom-dashboard--record-project-h)
;; HACK Fix #2219 where, in GUI daemon frames, the dashboard loses center (add-hook 'tab-bar-tab-post-select-functions #'+doom-dashboard--record-project-h)))
;; alignment after switching (or killing) workspaces.
(when (daemonp)
(add-hook 'tab-bar-tab-post-select-functions #'+doom-dashboard-reload-maybe-h))))
(add-hook 'doom-init-ui-hook #'+doom-dashboard-init-h 'append) (add-hook 'doom-init-ui-hook #'+doom-dashboard-init-h 'append)
@ -288,38 +285,19 @@ whose dimensions may not be fully initialized by the time this is run."
(car +doom-dashboard-banner-padding)) (car +doom-dashboard-banner-padding))
?\n)))))))) ?\n))))))))
(defun +doom-dashboard--tab-detect-project-fn (from-tab to-tab &rest _) (defun +doom-dashboard--record-project-h (from-tab to-tab &rest _)
"Set dashboard's PWD to current persp's `last-project-root', if it exists. "Set dashboard's PWD to current persp's `last-project-root', if it exists.
This and `+doom-dashboard--tab-record-project-h' provides `persp-mode' This and `+doom-dashboard--persp-record-project-h' provides `persp-mode'
integration with the Doom dashboard. It ensures that the dashboard is always in integration with the Doom dashboard. It ensures that the dashboard is always in
the correct project (which may be different across perspective)." the correct project (which may be different across perspective)."
(when (bound-and-true-p tabspaces-mode) (when (bound-and-true-p tabspaces-mode)
(setf (alist-get 'last-project-root from-tab) (ignore-errors
(doom-project-root)) (+workspaces-parameter-set (+workspaces-get-by-id (alist-get 'id from-tab))
(when-let* ((pwd (alist-get 'last-project-root to-tab))) 'last-project-root (doom-project-root)))
(when-let* ((pwd (+workspaces-parameter to-tab 'last-project-root)))
(+doom-dashboard-update-pwd-h pwd)))) (+doom-dashboard-update-pwd-h pwd))))
;; (defun +doom-dashboard--persp-detect-project-h (&rest _)
;; "Set dashboard's PWD to current persp's `last-project-root', if it exists.
;; This and `+doom-dashboard--persp-record-project-h' provides `persp-mode'
;; integration with the Doom dashboard. It ensures that the dashboard is always in
;; the correct project (which may be different across perspective)."
;; (when (bound-and-true-p persp-mode)
;; (when-let (pwd (persp-parameter 'last-project-root))
;; (+doom-dashboard-update-pwd-h pwd))))
;; (defun +doom-dashboard--persp-record-project-h (&optional persp &rest _)
;; "Record the last `doom-project-root' for the current persp.
;; See `+doom-dashboard--persp-detect-project-h' for more information."
;; (when (bound-and-true-p persp-mode)
;; (set-persp-parameter
;; 'last-project-root (doom-project-root)
;; (if (persp-p persp)
;; persp
;; (get-current-persp)))))
;; ;;
;;; Library ;;; Library

View file

@ -87,6 +87,15 @@ index. If NOERROR? is omitted, throws an error if NAME doesn't exist."
(unless noerror? (unless noerror?
(user-error "No workspace found: %s" name)))) (user-error "No workspace found: %s" name))))
;;;###autoload
(defun +workspaces-get-by-id (id)
"Return the tab by unique ID."
(catch 'result
(dolist (fr (frame-list))
(dolist (tab (tab-bar-tabs fr))
(when (equal id (alist-get 'id tab))
(throw 'result tab))))))
;;;###autoload ;;;###autoload
(defalias '+workspaces-exists-p #'tab-bar--tab-index-by-name) (defalias '+workspaces-exists-p #'tab-bar--tab-index-by-name)
@ -139,6 +148,43 @@ workspace."
(tabspaces-remove-buffer buffer)) (tabspaces-remove-buffer buffer))
(tabspaces-remove-buffer buffer))) (tabspaces-remove-buffer buffer)))
;;;###autoload
(defun +workspaces-parameter (workspace param &optional default)
"Return a WORKSPACE's PARAM, otherwise DEFAULT."
(if-let* ((table (gethash (alist-get 'id (or workspace (+workspaces-current)))
+workspaces--parameters)))
(gethash param table default)
default))
;;;###autoload
(defun +workspaces-parameter-set (workspace param val)
"Set a WORKSPACE's PARAM to VAL"
(let* ((workspace (or workspace (+workspaces-current)))
(id (or (alist-get 'id workspace)
(error "Workspace has no id attribute: %S" workspace))))
(puthash param val (or (gethash id +workspaces--parameters)
(error "Invalid workspace ID: %s" id)))))
;;
;;; Hooks
;;;###autoload
(defun +workspaces-init-parameters-h (tab)
"Initialize the current workspace's parameters."
(puthash (alist-get 'id tab) (make-hash-table :test 'eq)
+workspaces--parameters))
;;;###autoload
(defun +workspaces-cleanup-parameters-h (_tab _last-tab?)
"Cleanup workspace parameters for forgotten workspaces."
(dolist (id (hash-table-keys +workspaces--parameters))
(or (cl-loop for tab in tab-bar-closed-tabs
for tid = (map-nested-elt tab '(tab id))
unless (eq (gethash tid +workspaces--parameters t) t)
return t)
(remhash id +workspaces--parameters))))
;; ;;
;;; Interactive commands ;;; Interactive commands

View file

@ -3,6 +3,8 @@
(defvar +workspaces-dir (file-name-concat doom-profile-data-dir "workspaces/") (defvar +workspaces-dir (file-name-concat doom-profile-data-dir "workspaces/")
"The directory where workspace session files are stored.") "The directory where workspace session files are stored.")
(defvar +workspaces--parameters (make-hash-table :test 'equal))
;; ;;
;;; Packages ;;; Packages
@ -37,10 +39,26 @@
[remap delete-window] #'+workspaces/close-window-or-workspace [remap delete-window] #'+workspaces/close-window-or-workspace
[remap evil-window-delete] #'+workspaces/close-window-or-workspace) [remap evil-window-delete] #'+workspaces/close-window-or-workspace)
;; Per-workspace `winner-mode' history ;; Per-workspace `winner-mode' history
(add-to-list 'window-persistent-parameters '(winner-ring . t)) (add-to-list 'window-persistent-parameters '(winner-ring . t))
;; HACK: Emacs tabs don't offer an easy way to associate data with a tab,
;; since tab names aren't unique, so these advice is dedicated to providing
;; that:
(autoload 'uuidgen-1 "uuidgen")
(defadvice! +workspaces--embed-id-a (tab)
:filter-return #'tab-bar--tab
:filter-return #'tab-bar--current-tab-make
(unless (alist-get 'id tab)
(setq tab (append tab `((id . ,(uuidgen-1))))))
tab)
(add-to-list 'window-persistent-parameters '(id . writable))
(add-hook 'tab-bar-tab-post-open-functions #'+workspaces-init-parameters-h)
(add-hook 'tab-bar-tab-pre-close-functions #'+workspaces-cleanup-parameters-h)
;; HACK: Tabspaces is hardcoded to equate the root of a version controlled ;; 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 as the only kind of project root, and lacks integration with
;; project.el or projectile. This fixes that. ;; project.el or projectile. This fixes that.