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 'delete-frame-functions #'+doom-dashboard-reload-frame-h)
;; Update `default-directory' in dashboard when switching workspaces
(add-hook 'tab-bar-tab-post-select-functions #'+doom-dashboard--tab-detect-project-fn)
;; HACK Fix #2219 where, in GUI daemon frames, the dashboard loses center
;; alignment after switching (or killing) workspaces.
(when (daemonp)
(add-hook 'tab-bar-tab-post-select-functions #'+doom-dashboard-reload-maybe-h))))
;; (add-hook 'tab-bar-tab-pre-select-functions #'+doom-dashboard--record-project-h)
(add-hook 'tab-bar-tab-post-select-functions #'+doom-dashboard--record-project-h)))
(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))
?\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.
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
the correct project (which may be different across perspective)."
(when (bound-and-true-p tabspaces-mode)
(setf (alist-get 'last-project-root from-tab)
(doom-project-root))
(when-let* ((pwd (alist-get 'last-project-root to-tab)))
(ignore-errors
(+workspaces-parameter-set (+workspaces-get-by-id (alist-get 'id from-tab))
'last-project-root (doom-project-root)))
(when-let* ((pwd (+workspaces-parameter to-tab 'last-project-root)))
(+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

View file

@ -87,6 +87,15 @@ index. If NOERROR? is omitted, throws an error if NAME doesn't exist."
(unless noerror?
(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
(defalias '+workspaces-exists-p #'tab-bar--tab-index-by-name)
@ -139,6 +148,43 @@ workspace."
(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

View file

@ -3,6 +3,8 @@
(defvar +workspaces-dir (file-name-concat doom-profile-data-dir "workspaces/")
"The directory where workspace session files are stored.")
(defvar +workspaces--parameters (make-hash-table :test 'equal))
;;
;;; Packages
@ -37,10 +39,26 @@
[remap delete-window] #'+workspaces/close-window-or-workspace
[remap evil-window-delete] #'+workspaces/close-window-or-workspace)
;; Per-workspace `winner-mode' history
(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
;; project as the only kind of project root, and lacks integration with
;; project.el or projectile. This fixes that.