Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 39 additions & 13 deletions enkan-repl.el
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,16 @@ ALIASES may be a list of strings or an alist of (alias . project-name)."
(or (cdr (assoc alias project-aliases))
alias))

(defun enkan-repl--merge-target-directories (primary fallback)
"Return target directories from PRIMARY merged with FALLBACK.
Entries in PRIMARY win when both lists contain the same alias. This keeps
workspace-local imported paths authoritative while preserving the user's wider
target directory registry across workspace loads."
(append primary
(cl-remove-if (lambda (entry)
(assoc (car entry) primary))
fallback)))

(defun enkan-repl--projects-with-current-aliases (projects current-project project-aliases)
"Return PROJECTS augmented with CURRENT-PROJECT's PROJECT-ALIASES.
Merge the current workspace's aliases into CURRENT-PROJECT's project
Expand Down Expand Up @@ -460,7 +470,9 @@ This function restores workspace state from the given plist."
(setq enkan-repl-project-aliases (plist-get state :project-aliases)))
(when (plist-member state :target-directories)
(setq enkan-repl-target-directories
(plist-get state :target-directories))))
(enkan-repl--merge-target-directories
(plist-get state :target-directories)
enkan-repl-target-directories))))

(defun enkan-repl--save-workspace-state (&optional workspace-id)
"Save current globals into `enkan-repl--workspaces' under WORKSPACE-ID.
Expand Down Expand Up @@ -798,23 +810,29 @@ This ensures that directory lookups work correctly after workspace switch."
;; Find project directories from files in standard locations
(let ((dirs '()))
;; Check standard project locations
(dolist (alias enkan-repl-project-aliases)
(dolist (alias (enkan-repl--project-alias-names enkan-repl-project-aliases))
;; Try to find project directory from standard locations
(let* ((home-dir (expand-file-name "~"))
(let* ((project-name
(enkan-repl--project-name-for-alias
alias enkan-repl-project-aliases))
(home-dir (expand-file-name "~"))
(common-paths (list
(expand-file-name (format "dev/self/%s" enkan-repl--current-project) home-dir)
(expand-file-name (format "dev/%s" enkan-repl--current-project) home-dir)
(expand-file-name (format "Documents/%s" enkan-repl--current-project) home-dir)
(expand-file-name (format "projects/%s" enkan-repl--current-project) home-dir)
(expand-file-name enkan-repl--current-project default-directory))))
(expand-file-name (format "dev/self/%s" project-name) home-dir)
(expand-file-name (format "dev/%s" project-name) home-dir)
(expand-file-name (format "Documents/%s" project-name) home-dir)
(expand-file-name (format "projects/%s" project-name) home-dir)
(expand-file-name project-name default-directory))))
;; Find first existing directory
(dolist (path common-paths)
(when (and (not (assoc alias dirs))
(file-directory-p path))
(push (cons alias (cons enkan-repl--current-project path)) dirs)))))
(push (cons alias (cons project-name path)) dirs)))))
;; Update enkan-repl-target-directories if we found directories
(when dirs
(setq enkan-repl-target-directories dirs)))))
(setq enkan-repl-target-directories
(enkan-repl--merge-target-directories
(nreverse dirs)
enkan-repl-target-directories))))))

(defun enkan-repl--initialize-default-workspace ()
"Initialize default workspace '01' with first available project.
Expand Down Expand Up @@ -1509,7 +1527,8 @@ COUNTER: session counter"

(defun enkan-repl--setup-start-sessions (alias-list buffer-name)
"Start terminal sessions for each alias in ALIAS-LIST and log to BUFFER-NAME.
Includes error handling for individual session failures."
Includes error handling for individual session failures.
Returns a plist with `:success-count' and `:failure-count'."
(with-current-buffer buffer-name
(princ "🚀 Starting terminal sessions:\n"))
(let ((session-number 1)
Expand All @@ -1533,7 +1552,9 @@ Includes error handling for individual session failures."
(setq failure-count (1+ failure-count))))
(setq session-number (1+ session-number)))
(with-current-buffer buffer-name
(princ (format "\n📊 Session start summary: %d success, %d failed\n\n" success-count failure-count)))))
(princ (format "\n📊 Session start summary: %d success, %d failed\n\n" success-count failure-count)))
(list :success-count success-count
:failure-count failure-count)))

(defun enkan-repl--setup-project-session (alias)
"Setup project session for given ALIAS.
Expand Down Expand Up @@ -1600,7 +1621,12 @@ Category: Session Controller"
(error "Project '%s' not found" project-name))
(enkan-repl--setup-set-project-aliases project-name alias-list buffer-name)
;; Start sessions
(enkan-repl--setup-start-sessions alias-list buffer-name))
(let ((start-result
(enkan-repl--setup-start-sessions alias-list buffer-name)))
(when (> (plist-get start-result :failure-count) 0)
(error "Failed to start %d of %d terminal session(s)"
(plist-get start-result :failure-count)
(length alias-list)))))
;; Set final project configuration
(enkan-repl--ws-set-current-project project-name)
(princ (format "\n✅ Setup completed for project: %s\n" project-name))
Expand Down
20 changes: 19 additions & 1 deletion test/enkan-repl-workspace-create-test.el
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,24 @@
(should (string= (plist-get ws-state :current-project) "MyProject"))
(should (equal (plist-get ws-state :project-aliases) '("alias1"))))))

(ert-deftest test-enkan-repl--setup-start-sessions-reports-failures ()
"Session setup should report failures instead of looking successful."
(let ((buffer-name "*enkan-repl-test-setup-start*")
(enkan-repl-target-directories nil))
(unwind-protect
(with-current-buffer (get-buffer-create buffer-name)
(erase-buffer)
(let* ((standard-output (current-buffer))
(result (enkan-repl--setup-start-sessions
'("junk")
buffer-name)))
(should (= 0 (plist-get result :success-count)))
(should (= 1 (plist-get result :failure-count)))
(should (string-match-p "Project alias .junk. not found"
(buffer-string)))))
(when (get-buffer buffer-name)
(kill-buffer buffer-name)))))

(ert-deftest test-enkan-repl--setup-window-terminal-buffer-pure-workspace-context ()
"Test that window eat buffer name includes correct workspace ID."
;; Load window-layouts if exists
Expand Down Expand Up @@ -149,4 +167,4 @@
(should (string-match-p "\\*ws:02 " (cdr result))))))))

(provide 'enkan-repl-workspace-create-test)
;;; enkan-repl-workspace-create-test.el ends here
;;; enkan-repl-workspace-create-test.el ends here
34 changes: 34 additions & 0 deletions test/enkan-repl-workspace-delete-deletion-test.el
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,39 @@
(should (equal 1 enkan-repl--session-counter))
(should (equal "other" enkan-repl--current-project)))))

(ert-deftest test-workspace-delete-preserves-target-registry-for-new-setup ()
"Deleting a workspace must not make unrelated project aliases unusable."
(let ((enkan-repl--workspaces
'(("01" . (:current-project "junk"
:session-list ((1 . "junk"))
:session-counter 1
:project-aliases (("junk" . "junk"))
:target-directories
(("junk" . ("junk" . "/repo/junk")))))
("02" . (:current-project "er"
:session-list ((1 . "enkan-repl"))
:session-counter 1
:project-aliases (("er" . "enkan-repl"))
:target-directories
(("er" . ("enkan-repl" . "/repo/enkan-repl")))))))
(enkan-repl--current-workspace "01")
(enkan-repl-session-list '((1 . "junk")))
(enkan-repl--session-counter 1)
(enkan-repl--current-project "junk")
(enkan-repl-project-aliases '(("junk" . "junk")))
(enkan-repl-target-directories
'(("junk" . ("junk" . "/repo/junk"))
("er" . ("enkan-repl" . "/repo/enkan-repl")))))
(cl-letf (((symbol-function 'enkan-repl--stop-workspace-terminals)
(lambda (_workspace-id)
(list :buffers-killed 0 :tmux-killed nil))))
(enkan-repl--delete-workspace-completely "01")
(should (equal "02" enkan-repl--current-workspace))
(should (equal '(("er" . ("enkan-repl" . "/repo/enkan-repl"))
("junk" . ("junk" . "/repo/junk")))
enkan-repl-target-directories))
(should (equal '("junk" . "/repo/junk")
(enkan-repl--setup-project-session "junk"))))))

(provide 'enkan-repl-workspace-delete-deletion-test)
;;; enkan-repl-workspace-delete-deletion-test.el ends here
63 changes: 63 additions & 0 deletions test/enkan-repl-workspace-state-test.el
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,69 @@
(should (equal enkan-repl--session-counter 2))
(should (equal enkan-repl-project-aliases '(("p3" . "proj3") ("p4" . "proj4")))))))

(ert-deftest test-enkan-repl--load-workspace-state-preserves-target-registry ()
"Loading a workspace should not discard unrelated configured targets."
(let ((enkan-repl--workspaces
'(("01" . (:current-project "er"
:session-list ((1 . "enkan-repl"))
:session-counter 1
:project-aliases (("er" . "enkan-repl"))
:target-directories
(("er" . ("enkan-repl" . "/repo/enkan-repl")))))))
(enkan-repl--current-workspace "01")
(enkan-repl--current-project nil)
(enkan-repl-session-list nil)
(enkan-repl--session-counter 0)
(enkan-repl-project-aliases nil)
(enkan-repl-target-directories
'(("er" . ("enkan-repl" . "/repo/enkan-repl"))
("junk" . ("junk" . "/repo/junk")))))
(enkan-repl--load-workspace-state "01")
(should (equal '(("er" . ("enkan-repl" . "/repo/enkan-repl"))
("junk" . ("junk" . "/repo/junk")))
enkan-repl-target-directories))
(should (equal '("junk" . "/repo/junk")
(enkan-repl--setup-project-session "junk")))))

(ert-deftest test-enkan-repl--load-legacy-state-merges-discovered-targets ()
"Loading old states without target dirs should preserve the target registry."
(let* ((project-name "enkan-repl-legacy-project")
(temp-root (file-name-as-directory
(make-temp-file "enkan-repl-legacy-state-" t)))
(project-dir (expand-file-name project-name temp-root))
(default-directory temp-root)
(enkan-repl--workspaces
`(("01" . (:current-project ,project-name
:session-list ((1 . ,project-name))
:session-counter 1
:project-aliases (("legacy" . ,project-name))))))
(enkan-repl--current-workspace "01")
(enkan-repl--current-project nil)
(enkan-repl-session-list nil)
(enkan-repl--session-counter 0)
(enkan-repl-project-aliases nil)
(enkan-repl-target-directories
'(("junk" . ("junk" . "/repo/junk")))))
(unwind-protect
(progn
(make-directory project-dir)
(enkan-repl--load-workspace-state "01")
(should (equal `(("legacy" . (,project-name . ,project-dir))
("junk" . ("junk" . "/repo/junk")))
enkan-repl-target-directories))
(should (equal '("junk" . "/repo/junk")
(enkan-repl--setup-project-session "junk"))))
(delete-directory temp-root t))))

(ert-deftest test-enkan-repl--merge-target-directories-primary-wins ()
"Workspace target directories should win over preserved registry entries."
(should (equal '(("er" . ("enkan-repl" . "/state/enkan-repl"))
("junk" . ("junk" . "/repo/junk")))
(enkan-repl--merge-target-directories
'(("er" . ("enkan-repl" . "/state/enkan-repl")))
'(("er" . ("enkan-repl" . "/config/enkan-repl"))
("junk" . ("junk" . "/repo/junk")))))))

(ert-deftest test-enkan-repl--save-workspace-state ()
"Test saving workspace state."
(let ((enkan-repl--workspaces nil)
Expand Down