From d05fc112c986da275927ca472f379d802567286d Mon Sep 17 00:00:00 2001 From: AlAl Date: Tue, 25 Aug 2026 20:35:33 -0400 Subject: [PATCH 1/5] feat: make space-tree frame specific by creating get/set functions Make space-tree frame specific by replacing space-tree variables with --get and --set functions. Replace any calls to these variables with the --get and --set functions. --- space-tree.el | 200 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 134 insertions(+), 66 deletions(-) diff --git a/space-tree.el b/space-tree.el index 96db74d..f4dc628 100644 --- a/space-tree.el +++ b/space-tree.el @@ -93,45 +93,103 @@ does not affect the underlying tree structure." ;; (for keybindings, custom modeline elements, etc.). Do not mutate ;; them directly; use the commands and helpers in this file. -(defvar space-tree-tree (make-hash-table :test 'equal) - "Root hash table holding the structural layout of all spaces. + +;; (defvar space-tree-tree (make-hash-table :test 'equal) +;; "Root hash table holding the structural layout of all spaces. + +;; Each node is a hash table whose keys are integer space numbers and +;; whose values are the child node hash tables. The variable itself is +;; the root. Reset by `space-tree-init'.") + +(defun space-tree--get-tree () + "Get current frame's root hash table for spaces. + +Each node is a hash table whose keys are integer space numbers and +whose values are the child node hash tables. The variable itself is +the root. Reset by `space-tree-init'." + (frame-parameter nil 'space-tree-tree)) + +(defun space-tree--set-tree (val &optional frame) + "Set current frame's root hash table for spaces to VAL. Each node is a hash table whose keys are integer space numbers and whose values are the child node hash tables. The variable itself is -the root. Reset by `space-tree-init'.") +the root. Reset by `space-tree-init'." + (set-frame-parameter frame 'space-tree-tree val)) -(defvar space-tree-current-address nil - "Address of the currently selected space, as a list of integers. +(defun space-tree--get-current-address () + "Get address of the current space for frame, as integer list. For example, (2 1 3) refers to the third space at depth 3, nested under space 1 at depth 2, which is nested under top-level space 2. The empty list indicates no space is selected (initial state before -`space-tree-init' is called).") +`space-tree-init' is called)." + (frame-parameter nil 'space-tree-current-address)) -(defvar space-tree-address-wconf-tbl (make-hash-table :test 'equal) - "Hash table mapping each space's address to its saved window state. +(defun space-tree--set-current-address (val &optional frame) + "Set address of the current space for frame as VAL. + +For example, (2 1 3) refers to the third space at depth 3, nested +under space 1 at depth 2, which is nested under top-level space 2. +The empty list indicates no space is selected (initial state before +`space-tree-init' is called)." + (set-frame-parameter frame 'space-tree-current-address val)) + +(defun space-tree--get-address-wconf-tbl () + "Get frame's hash table with each space's window state. Keys are address lists (see `space-tree-current-address') and values are objects produced by `window-state-get'. When you switch to a space, the corresponding window state is restored via -`window-state-put'.") +`window-state-put'." + (frame-parameter nil 'space-tree-address-wconf-tbl)) -(defvar space-tree-recent-space-list nil - "List of recently visited space addresses, most recent first. +(defun space-tree--set-address-wconf-tbl (val &optional frame) + "Set frame's hash table with each space's window state to VAL. + +Keys are address lists (see `space-tree-current-address') and values +are objects produced by `window-state-get'. When you switch to a +space, the corresponding window state is restored via +`window-state-put'." + (set-frame-parameter frame 'space-tree-address-wconf-tbl val)) + +(defun space-tree--get-recent-space-list () + "Get List of recently visited space addresses, most recent first. + +Used by `space-tree-switch-or-create' to resolve a partial address +to its most recently visited descendant - e.g. asking for (2) when +\(2 1) was last visited resolves to (2 1). Adjacent duplicates and +pure prefixes of later entries are compacted by +`space-tree--process-history'." + (frame-parameter nil 'space-tree-recent-space-list)) + +(defun space-tree--set-recent-space-list (val &optional frame) + "Set list of recently visited space addresses to VAL. Used by `space-tree-switch-or-create' to resolve a partial address to its most recently visited descendant - e.g. asking for (2) when \(2 1) was last visited resolves to (2 1). Adjacent duplicates and pure prefixes of later entries are compacted by -`space-tree--process-history'.") +`space-tree--process-history'." + (set-frame-parameter frame 'space-tree-recent-space-list val)) -(defvar space-tree-space-name-tbl (make-hash-table :test 'equal) - "Hash table mapping space addresses to user-supplied display names. +(defun space-tree--get-space-name-tbl () + "Get hash table mapping space addresses to display names. Set by `space-tree-name-current-space' and `space-tree-name-space-by-digit-arg'. Named spaces appear in the modeline by name instead of number and can be reached directly with -`space-tree-switch-space-by-name'.") +`space-tree-switch-space-by-name'." + (frame-parameter nil 'space-tree-space-name-tbl)) + +(defun space-tree--set-space-name-tbl (val &optional frame) + "Set hash table mapping space addresses to VAL. + +Set by `space-tree-name-current-space' and +`space-tree-name-space-by-digit-arg'. Named spaces appear in the +modeline by name instead of number and can be reached directly with +`space-tree-switch-space-by-name'." + (set-frame-parameter frame 'space-tree-space-name-tbl val)) (defvar space-tree-copied-space nil "Window state captured by `space-tree-copy-workspace', or nil. @@ -247,22 +305,22 @@ invoked from a shallower depth than it expects)." ADDRESS is a list of integer space numbers. An empty list returns the root. The returned value is the child-hash-table at that node." - (let ((node space-tree-tree)) + (let ((node (space-tree--get-tree))) (dolist (k address node) (setq node (and node (gethash k node)))))) (defun space-tree--space-exists-p (address) "Return non-nil if a space exists at ADDRESS." - (and (gethash address space-tree-address-wconf-tbl) t)) + (and (gethash address (space-tree--get-address-wconf-tbl)) t)) (defun space-tree--current-depth () "Return the depth (number of levels) of the current space." - (length space-tree-current-address)) + (length (space-tree--get-current-address))) (defun space-tree--current-parent () "Return the address of the parent of the current space. Returns the empty list when the current space is at the top level." - (butlast space-tree-current-address)) + (butlast (space-tree--get-current-address))) (defun space-tree--siblings-of (address) "Return the sorted list of space numbers at the level of ADDRESS. @@ -271,7 +329,7 @@ That is, the keys of ADDRESS's parent node, in ascending order." (defun space-tree--number-of-spaces-current-level () "Return the count of spaces at the current address's level." - (length (space-tree--siblings-of space-tree-current-address))) + (length (space-tree--siblings-of (space-tree--get-current-address)))) (defun space-tree--recent-space-on-path (sublist) "Return the most recently visited address that starts with SUBLIST. @@ -280,7 +338,7 @@ Returns nil if no recent space has SUBLIST as a prefix. Used to resolve a partial navigation like \"go to space 1\" into the most recently visited descendant of that parent." (cl-find-if (lambda (x) (space-tree--prefix-p sublist x)) - space-tree-recent-space-list)) + (space-tree--get-recent-space-list))) ;;; State Mutation @@ -300,15 +358,15 @@ Overwrites any existing node at that address." Removes addresses that are pure prefixes of a deeper address already in the list (since the deeper address already captures the parent context) and collapses adjacent duplicates." - (setq space-tree-recent-space-list - (space-tree--remove-adjacent-duplicates - (seq-remove - (lambda (a) - (cl-some (lambda (b) - (and (> (length b) (length a)) - (space-tree--prefix-p a b))) - space-tree-recent-space-list)) - space-tree-recent-space-list)))) + (space-tree--set-recent-space-list + (space-tree--remove-adjacent-duplicates + (seq-remove + (lambda (a) + (cl-some (lambda (b) + (and (> (length b) (length a)) + (space-tree--prefix-p a b))) + (space-tree--get-recent-space-list))) + (space-tree--get-recent-space-list))))) (defun space-tree--remove (address) "Delete the space at ADDRESS from all state. @@ -317,11 +375,11 @@ Removes the tree node, the saved window state, the user-defined name (if any), and all references in the recent-space history. Refreshes the modeline." (remhash (car (last address)) (space-tree--get (butlast address))) - (remhash address space-tree-space-name-tbl) - (remhash address space-tree-address-wconf-tbl) - (setq space-tree-recent-space-list - (seq-remove (lambda (x) (equal x address)) - space-tree-recent-space-list)) + (remhash address (space-tree--get-space-name-tbl)) + (remhash address (space-tree--get-address-wconf-tbl)) + (space-tree--set-recent-space-list + (seq-remove (lambda (x) (equal x address)) + (space-tree--get-recent-space-list))) (space-tree--process-history) (force-mode-line-update)) @@ -335,9 +393,10 @@ which case the user's existing frame layout is preserved, e.g. for the very first space created by `space-tree-init')." (space-tree--select-non-side-window) (space-tree--set address) - (setq space-tree-current-address address) - (puthash address (window-state-get) space-tree-address-wconf-tbl) - (push space-tree-current-address space-tree-recent-space-list) + (space-tree--set-current-address address) + (puthash address (window-state-get) (space-tree--get-address-wconf-tbl)) + (space-tree--set-recent-space-list + (cons (space-tree--get-current-address) (space-tree--get-recent-space-list))) (space-tree--process-history) (when (> (space-tree--number-of-spaces-current-level) 1) (space-tree--delete-other-windows-and-switch-to-scratch))) @@ -346,10 +405,10 @@ the very first space created by `space-tree-init')." "Snapshot the current window state into the current space. No-op when no space is currently selected, so callers can invoke this unconditionally without first checking initialisation state." - (when space-tree-current-address - (puthash space-tree-current-address + (when (space-tree--get-current-address) + (puthash (space-tree--get-current-address) (window-state-get) - space-tree-address-wconf-tbl))) + (space-tree--get-address-wconf-tbl)))) (defun space-tree--switch (address &optional no-update-wconf) "Switch to the existing space at ADDRESS. @@ -359,11 +418,12 @@ state - used when the caller will redraw the frame separately, for example when deleting the current space and switching to its parent in one operation." (space-tree--select-non-side-window) - (let ((wconf (gethash address space-tree-address-wconf-tbl))) + (let ((wconf (gethash address (space-tree--get-address-wconf-tbl)))) (unless no-update-wconf (space-tree--window-state-put-safely wconf)) - (setq space-tree-current-address address) - (push space-tree-current-address space-tree-recent-space-list))) + (space-tree--set-current-address address) + (space-tree--set-recent-space-list + (cons (space-tree--get-current-address) (space-tree--get-recent-space-list))))) ;;; Modeline @@ -380,7 +440,7 @@ sibling nodes at this level." (mapconcat (lambda (n) (let ((label (or (gethash (append parent-address (list n)) - space-tree-space-name-tbl) + (space-tree--get-space-name-tbl)) (number-to-string n)))) (if (equal n selected-space-number) (propertize (concat label "' ") 'face 'bold) @@ -398,8 +458,8 @@ marked with a trailing apostrophe; levels are separated by `|'. Named spaces appear by name instead of number." (let (parts) (dotimes (i (space-tree--current-depth)) - (let* ((parent (seq-take space-tree-current-address i)) - (selected (nth i space-tree-current-address)) + (let* ((parent (seq-take (space-tree--get-current-address) i)) + (selected (nth i (space-tree--get-current-address))) (level-ht (space-tree--get parent))) (push (space-tree--modeline-string-for-level parent selected level-ht) parts))) @@ -408,8 +468,11 @@ Named spaces appear by name instead of number." ;;; Public API - Lifecycle +;; check if space-tree-init is in frame if not then add it +;; create space-tree-init-frame which calls space-tree-init and sets hook for frame +;; create hook in init after each make frame ;;;###autoload -(defun space-tree-init () +(defun space-tree-init (&optional frame) "Initialise space-tree, replacing any existing state. Resets the entire tree and creates a single top-level space. The @@ -420,14 +483,19 @@ WARNING: any previously created spaces and their saved window states are discarded. The internal copy/paste clipboard \(`space-tree-copied-space') is preserved." (interactive) - (setq space-tree-tree (space-tree--ht) - space-tree-current-address nil - space-tree-address-wconf-tbl (space-tree--ht) - space-tree-space-name-tbl (space-tree--ht) - space-tree-recent-space-list nil) + (space-tree--set-tree (space-tree--ht) frame) + (space-tree--set-current-address nil frame) + (space-tree--set-address-wconf-tbl (space-tree--ht) frame) + (space-tree--set-recent-space-list nil frame) + (space-tree--set-space-name-tbl (space-tree--ht) frame) (space-tree--create-space-at (list (if space-tree-start-at-0 0 1))) (force-mode-line-update)) +;;;###autoload +;; (defun space-tree-init-frames () +;; "Initialise space-tree-init after each frame." +;; (unless (member 'space-tree-init after-make-frame-functions) +;; (add-hook 'after-make-frame-functions #'space-tree-init))) ;;; Public API - Navigation @@ -447,7 +515,7 @@ including the convenience wrappers `space-tree-to-1' .. `-to-9' and `space-tree-sub-1' .. `-sub-5'. It is not directly interactive; bind a wrapper lambda or use a digit-encoded command instead." (space-tree--validate-address new-address) - (let* ((existing (gethash new-address space-tree-address-wconf-tbl)) + (let* ((existing (gethash new-address (space-tree--get-address-wconf-tbl))) (recent (space-tree--recent-space-on-path new-address))) (space-tree--save-wconf) (cond @@ -476,7 +544,7 @@ The new space is numbered one higher than the current largest top-level space. Its window layout starts as a single `*scratch*' buffer." (interactive) - (let ((next (1+ (apply #'max (hash-table-keys space-tree-tree))))) + (let ((next (1+ (apply #'max (hash-table-keys (space-tree--get-tree)))))) (space-tree--create-space-at (list next)))) ;;;###autoload @@ -492,7 +560,7 @@ an error. Deletion also removes the saved window state, the user-defined name \(if any), and all history references." - (interactive (list space-tree-current-address)) + (interactive (list (space-tree--get-current-address))) (let ((n (space-tree--number-of-spaces-current-level))) (cond ((> n 1) (space-tree-go-left) (space-tree--remove address)) ((and (= n 1) (= (space-tree--current-depth) 1)) @@ -578,7 +646,7 @@ requires a match - arbitrary input is rejected." (let* ((entries (let (acc) (maphash (lambda (addr name) (push (cons name addr) acc)) - space-tree-space-name-tbl) + (space-tree--get-space-name-tbl)) acc)) (name (completing-read "Named space: " entries nil t))) (space-tree-switch-or-create (cdr (assoc name entries))))) @@ -588,7 +656,7 @@ requires a match - arbitrary input is rejected." "Switch back to the most recently visited space. Signals a `user-error' if there is no prior space in the history." (interactive) - (if-let* ((prev (nth 1 space-tree-recent-space-list))) + (if-let* ((prev (nth 1 (space-tree--get-recent-space-list)))) (space-tree-switch-or-create prev) (user-error "No previous space"))) @@ -596,9 +664,9 @@ Signals a `user-error' if there is no prior space in the history." "Move DELTA positions among the current level's sorted siblings. Wraps around at both ends, so going right from the rightmost lands on the leftmost and vice versa." - (space-tree--validate-address space-tree-current-address) - (let* ((siblings (space-tree--siblings-of space-tree-current-address)) - (i (seq-position siblings (car (last space-tree-current-address)))) + (space-tree--validate-address (space-tree--get-current-address)) + (let* ((siblings (space-tree--siblings-of (space-tree--get-current-address))) + (i (seq-position siblings (car (last (space-tree--get-current-address))))) (j (mod (+ i delta) (length siblings)))) (space-tree-switch-current-level (nth j siblings)))) @@ -627,7 +695,7 @@ The name replaces the numeric label in the modeline lighter and can be used as a target for `space-tree-switch-space-by-name'. Names are not required to be unique." (interactive "sName: ") - (puthash space-tree-current-address name space-tree-space-name-tbl) + (puthash (space-tree--get-current-address) name (space-tree--get-space-name-tbl)) (force-mode-line-update)) ;;;###autoload @@ -640,7 +708,7 @@ prompted in the minibuffer for the name." (interactive "p") (let* ((address (space-tree--parse-digit-address (number-to-string arg))) (name (read-from-minibuffer "Name: "))) - (puthash address name space-tree-space-name-tbl)) + (puthash address name (space-tree--get-space-name-tbl))) (force-mode-line-update)) @@ -672,12 +740,12 @@ is used as the docstring of each generated command." (space-tree--def-level-commands "space-tree-sub" "Switch to second-level space %d under the current top-level." 5 - (list (nth 0 space-tree-current-address) n)) + (list (nth 0 (space-tree--get-current-address)) n)) (space-tree--def-level-commands "space-tree-sub-sub" "Switch to third-level space %d under the current path." 5 - (list (nth 0 space-tree-current-address) - (nth 1 space-tree-current-address) n)) + (list (nth 0 (space-tree--get-current-address)) + (nth 1 (space-tree--get-current-address)) n)) (provide 'space-tree) From 89075bb0dfdc75c6a18673f68d91de888178b788 Mon Sep 17 00:00:00 2001 From: AlAl Date: Sun, 30 Aug 2026 20:03:30 -0400 Subject: [PATCH 2/5] feat: initialize space-tree for each frame with hook and frame arg Create #'space-tree-init-frames to hook #'space-tree-init to 'after-make-frame-functions unless it is already in 'after-make-frame-functions. Change signature and function calls of any functions called by #'space-tree--create-space-at, and the functions they call, to pass frame parameter to our #'space-tree--get functions. --- space-tree.el | 62 +++++++++++++++++++++++++-------------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/space-tree.el b/space-tree.el index f4dc628..d6d930d 100644 --- a/space-tree.el +++ b/space-tree.el @@ -101,13 +101,13 @@ does not affect the underlying tree structure." ;; whose values are the child node hash tables. The variable itself is ;; the root. Reset by `space-tree-init'.") -(defun space-tree--get-tree () +(defun space-tree--get-tree (&optional frame) "Get current frame's root hash table for spaces. Each node is a hash table whose keys are integer space numbers and whose values are the child node hash tables. The variable itself is the root. Reset by `space-tree-init'." - (frame-parameter nil 'space-tree-tree)) + (frame-parameter frame 'space-tree-tree)) (defun space-tree--set-tree (val &optional frame) "Set current frame's root hash table for spaces to VAL. @@ -117,14 +117,14 @@ whose values are the child node hash tables. The variable itself is the root. Reset by `space-tree-init'." (set-frame-parameter frame 'space-tree-tree val)) -(defun space-tree--get-current-address () +(defun space-tree--get-current-address (&optional frame) "Get address of the current space for frame, as integer list. For example, (2 1 3) refers to the third space at depth 3, nested under space 1 at depth 2, which is nested under top-level space 2. The empty list indicates no space is selected (initial state before `space-tree-init' is called)." - (frame-parameter nil 'space-tree-current-address)) + (frame-parameter frame 'space-tree-current-address)) (defun space-tree--set-current-address (val &optional frame) "Set address of the current space for frame as VAL. @@ -135,14 +135,14 @@ The empty list indicates no space is selected (initial state before `space-tree-init' is called)." (set-frame-parameter frame 'space-tree-current-address val)) -(defun space-tree--get-address-wconf-tbl () +(defun space-tree--get-address-wconf-tbl (&optional frame) "Get frame's hash table with each space's window state. Keys are address lists (see `space-tree-current-address') and values are objects produced by `window-state-get'. When you switch to a space, the corresponding window state is restored via `window-state-put'." - (frame-parameter nil 'space-tree-address-wconf-tbl)) + (frame-parameter frame 'space-tree-address-wconf-tbl)) (defun space-tree--set-address-wconf-tbl (val &optional frame) "Set frame's hash table with each space's window state to VAL. @@ -153,7 +153,7 @@ space, the corresponding window state is restored via `window-state-put'." (set-frame-parameter frame 'space-tree-address-wconf-tbl val)) -(defun space-tree--get-recent-space-list () +(defun space-tree--get-recent-space-list (&optional frame) "Get List of recently visited space addresses, most recent first. Used by `space-tree-switch-or-create' to resolve a partial address @@ -161,7 +161,7 @@ to its most recently visited descendant - e.g. asking for (2) when \(2 1) was last visited resolves to (2 1). Adjacent duplicates and pure prefixes of later entries are compacted by `space-tree--process-history'." - (frame-parameter nil 'space-tree-recent-space-list)) + (frame-parameter frame 'space-tree-recent-space-list)) (defun space-tree--set-recent-space-list (val &optional frame) "Set list of recently visited space addresses to VAL. @@ -300,12 +300,12 @@ invoked from a shallower depth than it expects)." ;;; Analyzing State -(defun space-tree--get (address) +(defun space-tree--get (address &optional frame) "Return the tree node at ADDRESS in `space-tree-tree', or nil if absent. ADDRESS is a list of integer space numbers. An empty list returns the root. The returned value is the child-hash-table at that node." - (let ((node (space-tree--get-tree))) + (let ((node (space-tree--get-tree frame))) (dolist (k address node) (setq node (and node (gethash k node)))))) @@ -322,14 +322,14 @@ the root. The returned value is the child-hash-table at that node." Returns the empty list when the current space is at the top level." (butlast (space-tree--get-current-address))) -(defun space-tree--siblings-of (address) +(defun space-tree--siblings-of (address &optional frame) "Return the sorted list of space numbers at the level of ADDRESS. That is, the keys of ADDRESS's parent node, in ascending order." - (sort (hash-table-keys (space-tree--get (butlast address))) #'<)) + (sort (hash-table-keys (space-tree--get (butlast address) frame)) #'<)) -(defun space-tree--number-of-spaces-current-level () +(defun space-tree--number-of-spaces-current-level (&optional frame) "Return the count of spaces at the current address's level." - (length (space-tree--siblings-of (space-tree--get-current-address)))) + (length (space-tree--siblings-of (space-tree--get-current-address frame) frame))) (defun space-tree--recent-space-on-path (sublist) "Return the most recently visited address that starts with SUBLIST. @@ -343,16 +343,16 @@ recently visited descendant of that parent." ;;; State Mutation -(defun space-tree--set (address) +(defun space-tree--set (address &optional frame) "Create an empty child node at ADDRESS. ADDRESS must be non-empty and its parent must already exist. Overwrites any existing node at that address." (puthash (car (last address)) (space-tree--ht) - (space-tree--get (butlast address)))) + (space-tree--get (butlast address) frame))) -(defun space-tree--process-history () +(defun space-tree--process-history (&optional frame) "Compact `space-tree-recent-space-list'. Removes addresses that are pure prefixes of a deeper address already @@ -365,8 +365,8 @@ context) and collapses adjacent duplicates." (cl-some (lambda (b) (and (> (length b) (length a)) (space-tree--prefix-p a b))) - (space-tree--get-recent-space-list))) - (space-tree--get-recent-space-list))))) + (space-tree--get-recent-space-list frame))) + (space-tree--get-recent-space-list frame))) frame)) (defun space-tree--remove (address) "Delete the space at ADDRESS from all state. @@ -383,7 +383,7 @@ Refreshes the modeline." (space-tree--process-history) (force-mode-line-update)) -(defun space-tree--create-space-at (address) +(defun space-tree--create-space-at (address &optional frame) "Create a new space at ADDRESS and switch to it. Establishes the tree node, captures the current window state as the @@ -392,13 +392,13 @@ new space's starting layout, and clears the frame to a single which case the user's existing frame layout is preserved, e.g. for the very first space created by `space-tree-init')." (space-tree--select-non-side-window) - (space-tree--set address) - (space-tree--set-current-address address) - (puthash address (window-state-get) (space-tree--get-address-wconf-tbl)) + (space-tree--set address frame) + (space-tree--set-current-address address frame) + (puthash address (window-state-get) (space-tree--get-address-wconf-tbl frame)) (space-tree--set-recent-space-list - (cons (space-tree--get-current-address) (space-tree--get-recent-space-list))) - (space-tree--process-history) - (when (> (space-tree--number-of-spaces-current-level) 1) + (cons (space-tree--get-current-address frame) (space-tree--get-recent-space-list frame)) frame) + (space-tree--process-history frame) + (when (> (space-tree--number-of-spaces-current-level frame) 1) (space-tree--delete-other-windows-and-switch-to-scratch))) (defun space-tree--save-wconf () @@ -488,14 +488,14 @@ are discarded. The internal copy/paste clipboard (space-tree--set-address-wconf-tbl (space-tree--ht) frame) (space-tree--set-recent-space-list nil frame) (space-tree--set-space-name-tbl (space-tree--ht) frame) - (space-tree--create-space-at (list (if space-tree-start-at-0 0 1))) + (space-tree--create-space-at (list (if space-tree-start-at-0 0 1)) frame) (force-mode-line-update)) ;;;###autoload -;; (defun space-tree-init-frames () -;; "Initialise space-tree-init after each frame." -;; (unless (member 'space-tree-init after-make-frame-functions) -;; (add-hook 'after-make-frame-functions #'space-tree-init))) +(defun space-tree-init-frames () + "Initialise space-tree-init after each frame." + (unless (member 'space-tree-init after-make-frame-functions) + (add-hook 'after-make-frame-functions #'space-tree-init))) ;;; Public API - Navigation From af5c498068379be9232a0fa8176fa5213f142198 Mon Sep 17 00:00:00 2001 From: work Date: Tue, 1 Sep 2026 18:05:38 -0400 Subject: [PATCH 3/5] fix: fix tests by replacing variables with functions/frame-parameter --- test/space-tree-test.el | 94 ++++++++++++++++++++--------------------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/test/space-tree-test.el b/test/space-tree-test.el index c2eceda..05b5fd4 100644 --- a/test/space-tree-test.el +++ b/test/space-tree-test.el @@ -19,12 +19,12 @@ (defmacro space-tree-test-with-clean-state (&rest body) "Execute BODY with all space-tree global state saved and restored." (declare (indent 0) (debug t)) - `(let ((space-tree-tree (make-hash-table :test 'equal)) - (space-tree-current-address '()) - (space-tree-address-wconf-tbl (make-hash-table :test 'equal)) - (space-tree-recent-space-list '()) - (space-tree-space-name-tbl (make-hash-table :test 'equal)) - (space-tree-copied-space nil)) + `(cl-letf (((frame-parameter nil 'space-tree-tree) (make-hash-table :test 'equal)) + ((frame-parameter nil 'space-tree-current-address) '()) + ((frame-parameter nil 'space-tree-address-wconf-tbl) (make-hash-table :test 'equal)) + ((frame-parameter nil 'space-tree-recent-space-list) '()) + ((frame-parameter nil 'space-tree-space-name-tbl) (make-hash-table :test 'equal)) + ((frame-parameter nil 'space-tree-copied-space) nil)) ,@body)) (defmacro space-tree-test-with-mock-windows (&rest body) @@ -50,11 +50,11 @@ table entries, current-address (last in list), and recent-space-list." (unless (space-tree--get path) (space-tree--set path)))) ;; Store a placeholder wconf - (puthash addr (list 'mock-wconf addr) space-tree-address-wconf-tbl)) + (puthash addr (list 'mock-wconf addr) (space-tree--get-address-wconf-tbl))) ;; Current address is the last one given - (setq space-tree-current-address (car (last addresses))) + (space-tree--set-current-address (car (last addresses))) ;; Recent list is addresses in reverse order - (setq space-tree-recent-space-list (reverse addresses))) + (space-tree--set-recent-space-list (reverse addresses))) ;;; A. Pure Functions @@ -214,17 +214,17 @@ table entries, current-address (last in list), and recent-space-list." (ert-deftest space-tree-test-recent-space-on-path/found () (space-tree-test-with-clean-state - (setq space-tree-recent-space-list '((1 2 3) (1 2) (2 1))) + (space-tree--set-recent-space-list '((1 2 3) (1 2) (2 1))) (should (equal (space-tree--recent-space-on-path '(1 2)) '(1 2 3))))) (ert-deftest space-tree-test-recent-space-on-path/not-found () (space-tree-test-with-clean-state - (setq space-tree-recent-space-list '((1 2 3) (2 1))) + (space-tree--set-recent-space-list '((1 2 3) (2 1))) (should-not (space-tree--recent-space-on-path '(3))))) (ert-deftest space-tree-test-recent-space-on-path/exact-match () (space-tree-test-with-clean-state - (setq space-tree-recent-space-list '((1 2) (3 4))) + (space-tree--set-recent-space-list '((1 2) (3 4))) (should (equal (space-tree--recent-space-on-path '(1 2)) '(1 2))))) ;; space-tree--get @@ -290,13 +290,13 @@ table entries, current-address (last in list), and recent-space-list." (space-tree-test-build-tree '((1) (2))) (space-tree--remove '(2)) (should-not (space-tree--get '(2))) - (should-not (gethash '(2) space-tree-address-wconf-tbl))))) + (should-not (gethash '(2) (space-tree--get-address-wconf-tbl)))))) (ert-deftest space-tree-test-remove/nested () (space-tree-test-with-clean-state (space-tree-test-with-mock-windows (space-tree-test-build-tree '((1) (1 1) (1 2))) - (setq space-tree-current-address '(1 2)) + (space-tree--set-current-address '(1 2)) (space-tree--remove '(1 1)) (should-not (space-tree--get '(1 1))) ;; Parent still has remaining child @@ -306,38 +306,38 @@ table entries, current-address (last in list), and recent-space-list." (space-tree-test-with-clean-state (space-tree-test-with-mock-windows (space-tree-test-build-tree '((1) (2))) - (puthash '(2) "work" space-tree-space-name-tbl) + (puthash '(2) "work" (space-tree--get-space-name-tbl)) (space-tree--remove '(2)) - (should-not (gethash '(2) space-tree-space-name-tbl))))) + (should-not (gethash '(2) (space-tree--get-space-name-tbl)))))) (ert-deftest space-tree-test-remove/cleans-history () (space-tree-test-with-clean-state (space-tree-test-with-mock-windows (space-tree-test-build-tree '((1) (2))) - (setq space-tree-recent-space-list '((2) (1))) + (space-tree--set-recent-space-list '((2) (1))) (space-tree--remove '(2)) - (should-not (member '(2) space-tree-recent-space-list))))) + (should-not (member '(2) (space-tree--get-recent-space-list)))))) ;; space-tree--process-history (ert-deftest space-tree-test-process-history/removes-prefix () "Parent addresses are removed when a child is in the list." (space-tree-test-with-clean-state - (setq space-tree-recent-space-list '((1 2 3) (1 2) (1))) + (space-tree--set-recent-space-list '((1 2 3) (1 2) (1))) (space-tree--process-history) - (should (equal space-tree-recent-space-list '((1 2 3)))))) + (should (equal (space-tree--get-recent-space-list) '((1 2 3)))))) (ert-deftest space-tree-test-process-history/keeps-non-prefix () (space-tree-test-with-clean-state - (setq space-tree-recent-space-list '((1 2) (3 4))) + (space-tree--set-recent-space-list '((1 2) (3 4))) (space-tree--process-history) - (should (equal space-tree-recent-space-list '((1 2) (3 4)))))) + (should (equal (space-tree--get-recent-space-list) '((1 2) (3 4)))))) (ert-deftest space-tree-test-process-history/removes-adj-dups () (space-tree-test-with-clean-state - (setq space-tree-recent-space-list '((1 2) (1 2) (3 4))) + (space-tree--set-recent-space-list '((1 2) (1 2) (3 4))) (space-tree--process-history) - (should (equal space-tree-recent-space-list '((1 2) (3 4)))))) + (should (equal (space-tree--get-recent-space-list) '((1 2) (3 4)))))) ;; space-tree-init @@ -347,8 +347,8 @@ table entries, current-address (last in list), and recent-space-list." (space-tree-test-with-mock-windows (let ((space-tree-start-at-0 nil)) (space-tree-init) - (should (equal space-tree-current-address '(1))) - (should (gethash '(1) space-tree-address-wconf-tbl)) + (should (equal (space-tree--get-current-address) '(1))) + (should (gethash '(1) (space-tree--get-address-wconf-tbl))) (should (hash-table-p (space-tree--get '(1)))))))) (ert-deftest space-tree-test-init/start-at-0 () @@ -356,22 +356,22 @@ table entries, current-address (last in list), and recent-space-list." (space-tree-test-with-mock-windows (let ((space-tree-start-at-0 t)) (space-tree-init) - (should (equal space-tree-current-address '(0))) - (should (gethash '(0) space-tree-address-wconf-tbl)))))) + (should (equal (space-tree--get-current-address) '(0))) + (should (gethash '(0) (space-tree--get-address-wconf-tbl))))))) (ert-deftest space-tree-test-init/resets-state () "Init clears any pre-existing state." (space-tree-test-with-clean-state (space-tree-test-with-mock-windows (space-tree-test-build-tree '((1) (2) (3))) - (puthash '(1) "old" space-tree-space-name-tbl) + (puthash '(1) "old" (space-tree--get-space-name-tbl)) (let ((space-tree-start-at-0 nil)) (space-tree-init) ;; Old spaces gone - (should-not (gethash '(2) space-tree-address-wconf-tbl)) - (should-not (gethash '(3) space-tree-address-wconf-tbl)) + (should-not (gethash '(2) (space-tree--get-address-wconf-tbl))) + (should-not (gethash '(3) (space-tree--get-address-wconf-tbl))) ;; Name table cleared - (should (= (hash-table-count space-tree-space-name-tbl) 0)))))) + (should (= (hash-table-count (space-tree--get-space-name-tbl)) 0)))))) ;; space-tree--switch @@ -379,18 +379,18 @@ table entries, current-address (last in list), and recent-space-list." (space-tree-test-with-clean-state (space-tree-test-with-mock-windows (space-tree-test-build-tree '((1) (2))) - (setq space-tree-current-address '(1)) + (space-tree--set-current-address '(1)) (space-tree--switch '(2)) - (should (equal space-tree-current-address '(2)))))) + (should (equal (space-tree--get-current-address) '(2)))))) (ert-deftest space-tree-test-switch/history-updated () (space-tree-test-with-clean-state (space-tree-test-with-mock-windows (space-tree-test-build-tree '((1) (2))) - (setq space-tree-current-address '(1)) - (setq space-tree-recent-space-list '((1))) + (space-tree--set-current-address '(1)) + (space-tree--set-recent-space-list '((1))) (space-tree--switch '(2)) - (should (equal (car space-tree-recent-space-list) '(2)))))) + (should (equal (car (space-tree--get-recent-space-list)) '(2)))))) ;; space-tree-switch-or-create @@ -399,31 +399,31 @@ table entries, current-address (last in list), and recent-space-list." (space-tree-test-with-clean-state (space-tree-test-with-mock-windows (space-tree-test-build-tree '((1) (2))) - (setq space-tree-current-address '(1)) - (setq space-tree-recent-space-list '((1) (2))) + (space-tree--set-current-address '(1)) + (space-tree--set-recent-space-list '((1) (2))) (space-tree-switch-or-create '(2)) - (should (equal space-tree-current-address '(2)))))) + (should (equal (space-tree--get-current-address) '(2)))))) (ert-deftest space-tree-test-switch-or-create/new-space () "Switching to non-existent address creates it." (space-tree-test-with-clean-state (space-tree-test-with-mock-windows (space-tree-test-build-tree '((1))) - (setq space-tree-current-address '(1)) + (space-tree--set-current-address '(1)) (space-tree-switch-or-create '(2)) - (should (equal space-tree-current-address '(2))) - (should (gethash '(2) space-tree-address-wconf-tbl))))) + (should (equal (space-tree--get-current-address) '(2))) + (should (gethash '(2) (space-tree--get-address-wconf-tbl)))))) (ert-deftest space-tree-test-switch-or-create/via-recent () "Navigating to a parent address resolves via recent list." (space-tree-test-with-clean-state (space-tree-test-with-mock-windows (space-tree-test-build-tree '((1) (1 1) (1 2))) - (setq space-tree-current-address '(1)) - (setq space-tree-recent-space-list '((1 2) (1 1) (1))) + (space-tree--set-current-address '(1)) + (space-tree--set-recent-space-list '((1 2) (1 1) (1))) (space-tree-switch-or-create '(1)) ;; Should resolve to (1 2) - first recent space on path (1) - (should (equal space-tree-current-address '(1 2)))))) + (should (equal (space-tree--get-current-address) '(1 2)))))) ;;; D. Modeline Rendering @@ -464,7 +464,7 @@ table entries, current-address (last in list), and recent-space-list." (space-tree-test-with-clean-state (space-tree-test-with-mock-windows (space-tree-test-build-tree '((1) (2))) - (puthash '(2) "work" space-tree-space-name-tbl) + (puthash '(2) "work" (space-tree--get-space-name-tbl)) (let ((result (space-tree-modeline-lighter))) (should (string-match-p "work" result)))))) From a73778cfc855b7b634ba05cc8d57b75823a721c2 Mon Sep 17 00:00:00 2001 From: work Date: Tue, 1 Sep 2026 18:59:14 -0400 Subject: [PATCH 4/5] style: fix linting issues --- space-tree.el | 82 +++++++++++++++++++++++++++++---------------------- 1 file changed, 46 insertions(+), 36 deletions(-) diff --git a/space-tree.el b/space-tree.el index d6d930d..c9da0b0 100644 --- a/space-tree.el +++ b/space-tree.el @@ -102,7 +102,7 @@ does not affect the underlying tree structure." ;; the root. Reset by `space-tree-init'.") (defun space-tree--get-tree (&optional frame) - "Get current frame's root hash table for spaces. + "Get current FRAME's root hash table for spaces. Each node is a hash table whose keys are integer space numbers and whose values are the child node hash tables. The variable itself is @@ -110,7 +110,7 @@ the root. Reset by `space-tree-init'." (frame-parameter frame 'space-tree-tree)) (defun space-tree--set-tree (val &optional frame) - "Set current frame's root hash table for spaces to VAL. + "Set current FRAME's root hash table for spaces to VAL. Each node is a hash table whose keys are integer space numbers and whose values are the child node hash tables. The variable itself is @@ -118,7 +118,7 @@ the root. Reset by `space-tree-init'." (set-frame-parameter frame 'space-tree-tree val)) (defun space-tree--get-current-address (&optional frame) - "Get address of the current space for frame, as integer list. + "Get address of the current space for FRAME, as integer list. For example, (2 1 3) refers to the third space at depth 3, nested under space 1 at depth 2, which is nested under top-level space 2. @@ -127,7 +127,7 @@ The empty list indicates no space is selected (initial state before (frame-parameter frame 'space-tree-current-address)) (defun space-tree--set-current-address (val &optional frame) - "Set address of the current space for frame as VAL. + "Set address of the current space for FRAME as VAL. For example, (2 1 3) refers to the third space at depth 3, nested under space 1 at depth 2, which is nested under top-level space 2. @@ -136,25 +136,25 @@ The empty list indicates no space is selected (initial state before (set-frame-parameter frame 'space-tree-current-address val)) (defun space-tree--get-address-wconf-tbl (&optional frame) - "Get frame's hash table with each space's window state. + "Get FRAME's hash table with each space's window state. -Keys are address lists (see `space-tree-current-address') and values -are objects produced by `window-state-get'. When you switch to a -space, the corresponding window state is restored via +Keys are address lists (see `space-tree--get-current-address') and +values are objects produced by `window-state-get'. When you switch +to a space, the corresponding window state is restored via `window-state-put'." (frame-parameter frame 'space-tree-address-wconf-tbl)) (defun space-tree--set-address-wconf-tbl (val &optional frame) - "Set frame's hash table with each space's window state to VAL. + "Set FRAME's hash table with each space's window state to VAL. -Keys are address lists (see `space-tree-current-address') and values -are objects produced by `window-state-get'. When you switch to a -space, the corresponding window state is restored via +Keys are address lists (see `space-tree--get-current-address') and +values are objects produced by `window-state-get'. When you switch +to a space, the corresponding window state is restored via `window-state-put'." (set-frame-parameter frame 'space-tree-address-wconf-tbl val)) (defun space-tree--get-recent-space-list (&optional frame) - "Get List of recently visited space addresses, most recent first. + "Get FRAME's list of recently visited addresses, most recent first. Used by `space-tree-switch-or-create' to resolve a partial address to its most recently visited descendant - e.g. asking for (2) when @@ -164,7 +164,7 @@ pure prefixes of later entries are compacted by (frame-parameter frame 'space-tree-recent-space-list)) (defun space-tree--set-recent-space-list (val &optional frame) - "Set list of recently visited space addresses to VAL. + "Set FRAME's list of recently visited space addresses to VAL. Used by `space-tree-switch-or-create' to resolve a partial address to its most recently visited descendant - e.g. asking for (2) when @@ -183,7 +183,7 @@ modeline by name instead of number and can be reached directly with (frame-parameter nil 'space-tree-space-name-tbl)) (defun space-tree--set-space-name-tbl (val &optional frame) - "Set hash table mapping space addresses to VAL. + "Set FRAME's hash table mapping space addresses to VAL. Set by `space-tree-name-current-space' and `space-tree-name-space-by-digit-arg'. Named spaces appear in the @@ -301,7 +301,7 @@ invoked from a shallower depth than it expects)." ;;; Analyzing State (defun space-tree--get (address &optional frame) - "Return the tree node at ADDRESS in `space-tree-tree', or nil if absent. + "Return the FRAME's node at ADDRESS, or nil if absent. ADDRESS is a list of integer space numbers. An empty list returns the root. The returned value is the child-hash-table at that node." @@ -323,13 +323,16 @@ Returns the empty list when the current space is at the top level." (butlast (space-tree--get-current-address))) (defun space-tree--siblings-of (address &optional frame) - "Return the sorted list of space numbers at the level of ADDRESS. + "Return the FRAME's list of space numbers at the level of ADDRESS. That is, the keys of ADDRESS's parent node, in ascending order." - (sort (hash-table-keys (space-tree--get (butlast address) frame)) #'<)) + (sort (hash-table-keys + (space-tree--get (butlast address) frame)) + #'<)) (defun space-tree--number-of-spaces-current-level (&optional frame) - "Return the count of spaces at the current address's level." - (length (space-tree--siblings-of (space-tree--get-current-address frame) frame))) + "Return the FRAME's count of spaces at the current address's level." + (length (space-tree--siblings-of + (space-tree--get-current-address frame) frame))) (defun space-tree--recent-space-on-path (sublist) "Return the most recently visited address that starts with SUBLIST. @@ -344,7 +347,7 @@ recently visited descendant of that parent." ;;; State Mutation (defun space-tree--set (address &optional frame) - "Create an empty child node at ADDRESS. + "Create an empty child node at FRAME's ADDRESS. ADDRESS must be non-empty and its parent must already exist. Overwrites any existing node at that address." @@ -353,7 +356,7 @@ Overwrites any existing node at that address." (space-tree--get (butlast address) frame))) (defun space-tree--process-history (&optional frame) - "Compact `space-tree-recent-space-list'. + "Compact FRAME's `space-tree-recent-space-list'. Removes addresses that are pure prefixes of a deeper address already in the list (since the deeper address already captures the parent @@ -366,7 +369,8 @@ context) and collapses adjacent duplicates." (and (> (length b) (length a)) (space-tree--prefix-p a b))) (space-tree--get-recent-space-list frame))) - (space-tree--get-recent-space-list frame))) frame)) + (space-tree--get-recent-space-list frame))) + frame)) (defun space-tree--remove (address) "Delete the space at ADDRESS from all state. @@ -384,7 +388,7 @@ Refreshes the modeline." (force-mode-line-update)) (defun space-tree--create-space-at (address &optional frame) - "Create a new space at ADDRESS and switch to it. + "Create a new space at FRAME's ADDRESS and switch to it. Establishes the tree node, captures the current window state as the new space's starting layout, and clears the frame to a single @@ -394,9 +398,12 @@ the very first space created by `space-tree-init')." (space-tree--select-non-side-window) (space-tree--set address frame) (space-tree--set-current-address address frame) - (puthash address (window-state-get) (space-tree--get-address-wconf-tbl frame)) + (puthash address (window-state-get) + (space-tree--get-address-wconf-tbl frame)) (space-tree--set-recent-space-list - (cons (space-tree--get-current-address frame) (space-tree--get-recent-space-list frame)) frame) + (cons (space-tree--get-current-address frame) + (space-tree--get-recent-space-list frame)) + frame) (space-tree--process-history frame) (when (> (space-tree--number-of-spaces-current-level frame) 1) (space-tree--delete-other-windows-and-switch-to-scratch))) @@ -423,7 +430,8 @@ in one operation." (space-tree--window-state-put-safely wconf)) (space-tree--set-current-address address) (space-tree--set-recent-space-list - (cons (space-tree--get-current-address) (space-tree--get-recent-space-list))))) + (cons (space-tree--get-current-address) + (space-tree--get-recent-space-list))))) ;;; Modeline @@ -468,12 +476,9 @@ Named spaces appear by name instead of number." ;;; Public API - Lifecycle -;; check if space-tree-init is in frame if not then add it -;; create space-tree-init-frame which calls space-tree-init and sets hook for frame -;; create hook in init after each make frame ;;;###autoload (defun space-tree-init (&optional frame) - "Initialise space-tree, replacing any existing state. + "Initialise space-tree for FRAME, replacing any existing state. Resets the entire tree and creates a single top-level space. The first space is numbered 1 by default, or 0 if `space-tree-start-at-0' @@ -493,7 +498,7 @@ are discarded. The internal copy/paste clipboard ;;;###autoload (defun space-tree-init-frames () - "Initialise space-tree-init after each frame." + "Initialise `space-tree-init' after each frame." (unless (member 'space-tree-init after-make-frame-functions) (add-hook 'after-make-frame-functions #'space-tree-init))) @@ -515,7 +520,8 @@ including the convenience wrappers `space-tree-to-1' .. `-to-9' and `space-tree-sub-1' .. `-sub-5'. It is not directly interactive; bind a wrapper lambda or use a digit-encoded command instead." (space-tree--validate-address new-address) - (let* ((existing (gethash new-address (space-tree--get-address-wconf-tbl))) + (let* ((existing (gethash new-address + (space-tree--get-address-wconf-tbl))) (recent (space-tree--recent-space-on-path new-address))) (space-tree--save-wconf) (cond @@ -665,8 +671,10 @@ Signals a `user-error' if there is no prior space in the history." Wraps around at both ends, so going right from the rightmost lands on the leftmost and vice versa." (space-tree--validate-address (space-tree--get-current-address)) - (let* ((siblings (space-tree--siblings-of (space-tree--get-current-address))) - (i (seq-position siblings (car (last (space-tree--get-current-address))))) + (let* ((siblings (space-tree--siblings-of + (space-tree--get-current-address))) + (i (seq-position + siblings (car (last (space-tree--get-current-address))))) (j (mod (+ i delta) (length siblings)))) (space-tree-switch-current-level (nth j siblings)))) @@ -695,7 +703,9 @@ The name replaces the numeric label in the modeline lighter and can be used as a target for `space-tree-switch-space-by-name'. Names are not required to be unique." (interactive "sName: ") - (puthash (space-tree--get-current-address) name (space-tree--get-space-name-tbl)) + (puthash + (space-tree--get-current-address) name + (space-tree--get-space-name-tbl)) (force-mode-line-update)) ;;;###autoload From 331c619dd837dc44a0b526adea70f17b4946356a Mon Sep 17 00:00:00 2001 From: work Date: Tue, 1 Sep 2026 19:36:12 -0400 Subject: [PATCH 5/5] docs: add (space-tree-init-frames) to ## Installation in README --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ba31d74..67121c2 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,8 @@ space-tree is a library for managing spaces (workspaces) in Emacs. It is inspire (use-package space-tree :ensure (:host github :repo "chiply/space-tree") :config - (space-tree-init)) + (space-tree-init) + (space-tree-init-frames)) ``` ### With straight.el (use-package) @@ -32,7 +33,8 @@ space-tree is a library for managing spaces (workspaces) in Emacs. It is inspire (use-package space-tree :straight (:host github :repo "chiply/space-tree") :config - (space-tree-init)) + (space-tree-init) + (space-tree-init-frames)) ``` ### Manual @@ -43,6 +45,7 @@ Clone the repository and add it to your `load-path`: (add-to-list 'load-path "/path/to/space-tree") (require 'space-tree) (space-tree-init) +(space-tree-init-frames) ``` ## Usage