Skip to content

Commit 7fc628e

Browse files
phasetrclaude
andauthored
feat: add workspace list display command (#54)
* feat: add workspace list display command This feature will display a list of all workspaces with: - Associated project information (name and alias) - Target directories from enkan-repl-target-directories - Additional workspace metadata 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * feat: implement workspace list display command - Add enkan-repl-workspace-list command to display all workspaces - Show current workspace indicator and project information - Display target directories and session counts - Implement special mode with keybindings for workspace switching - Add tests for list display functionality Workspace list features: - RET: Switch to workspace at point - g: Refresh the list - q: Quit the buffer - Shows active/inactive status for each workspace 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: handle target-directories format correctly in workspace list The error "Wrong type argument: listp" occurred because project-info could be either a cons cell (project-name . project-path) or a string. Now properly extract the directory path from the cons cell structure. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: simplify workspace list display to show only essential information Remove unnecessary session counts and other verbose information. Display only workspace ID, project name, and directory. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: correctly handle both string and cons cell formats in target-directories Fixed the condition check to use (stringp (cdr project-info)) instead of (consp (cdr project-info)) to properly detect (project-name . path) format. Now correctly displays both string values and path values. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: distinguish between no project and project not found in directory list Show '<no project>' when no project is assigned to workspace, and '<not found>' when project is assigned but not in target-directories. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: handle both alias and project name lookups in workspace list - First try to find by alias (e.g., 'er', 'os') - If not found, try to find by project name in target-directories - Fix enkan-repl--get-project-path-from-directories to handle string values correctly - Show '<not found>' when project exists but not in target-directories 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: simplify workspace list to handle aliases correctly Remove incorrect lookup logic for 'eronly'/'ofonly' which are project config names, not aliases. The current-project should contain the actual alias like 'er' or 'os'. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: use existing enkan-repl--get-project-paths-for-current function Stop reinventing the wheel and use the existing function that properly handles project name -> aliases -> directories lookup chain. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: update tests to use correct project -> alias -> directories chain - Tests now use enkan-repl-projects for project config names - Workspace :current-project contains project config name (not alias) - Use enkan-repl--get-project-paths-for-current to resolve directories - Remove unused project-aliases variable All tests pass without warnings. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * feat: support multiple target directories display in workspace list When a project has multiple aliases (e.g., web-dev with er, pt, cc), display all target directories separated by commas. Added test case for multiple targets to ensure all paths are shown. All 6 tests pass. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: align workspace list display with consistent indentation Use fixed-width format with consistent spacing: - Active workspace: ' ▶ 01 - ...' - Inactive workspace: ' 02 - ...' This ensures all workspace IDs align properly regardless of selection status. Added tests to verify proper alignment. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * test: update tests to match user's format modification Update test expectations to match the user's preferred format: - Active workspace: '▶ 01 - ...' - Inactive workspace: ' 02 - ...' All 6 tests pass. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 33b7f51 commit 7fc628e

5 files changed

Lines changed: 590 additions & 1 deletion

File tree

enkan-repl-sessions.el

Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
;;; enkan-repl-sessions.el --- Session management functions for enkan-repl -*- lexical-binding: t; -*-
2+
3+
;; Copyright (C) 2024 Enkan Contributors
4+
5+
;; This program is free software; you can redistribute it and/or modify
6+
;; it under the terms of the GNU General Public License as published by
7+
;; the Free Software Foundation, either version 3 of the License, or
8+
;; (at your option) any later version.
9+
10+
;; This program is distributed in the hope that it will be useful,
11+
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
;; GNU General Public License for more details.
14+
15+
;; You should have received a copy of the GNU General Public License
16+
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
17+
18+
;;; Commentary:
19+
20+
;; This file contains session management functions for enkan-repl.
21+
;; Functions are pure and handle session state, formatting, and actions.
22+
23+
;;; Code:
24+
25+
(require 'cl-lib)
26+
27+
;; Forward declarations to avoid circular dependencies
28+
(declare-function enkan-repl--is-enkan-buffer-name "enkan-repl-utils")
29+
(declare-function enkan-repl--buffer-name->path "enkan-repl-utils")
30+
31+
;;; Session Information Extraction
32+
33+
(defun enkan-repl--extract-session-info (buffer-name buffer-live-p has-eat-process process-live-p)
34+
"Pure function to extract session info from buffer properties.
35+
BUFFER-NAME is the name of the buffer.
36+
BUFFER-LIVE-P indicates if buffer is live.
37+
HAS-EAT-PROCESS indicates if buffer has eat--process variable.
38+
PROCESS-LIVE-P indicates if the process is alive.
39+
Returns a plist with :name, :directory, and :status, or nil if not a session."
40+
(when (and buffer-name
41+
buffer-live-p
42+
(enkan-repl--is-enkan-buffer-name buffer-name))
43+
(let ((dir (enkan-repl--buffer-name->path buffer-name))
44+
(status (if (and has-eat-process process-live-p)
45+
'alive
46+
'dead)))
47+
(list :name buffer-name
48+
:directory dir
49+
:status status))))
50+
51+
(defun enkan-repl--collect-sessions (buffer-info-list)
52+
"Pure function to collect session info from buffer info list.
53+
BUFFER-INFO-LIST is a list of plists with buffer properties.
54+
Each plist should have :name, :live-p, :has-eat-process, and :process-live-p.
55+
Returns a list of session info plists."
56+
(let ((sessions '()))
57+
(dolist (buffer-info buffer-info-list)
58+
(let ((session-info
59+
(enkan-repl--extract-session-info
60+
(plist-get buffer-info :name)
61+
(plist-get buffer-info :live-p)
62+
(plist-get buffer-info :has-eat-process)
63+
(plist-get buffer-info :process-live-p))))
64+
(when session-info
65+
(push session-info sessions))))
66+
(nreverse sessions)))
67+
68+
;;; Session Formatting Functions
69+
70+
(defun enkan-repl--format-sessions (sessions)
71+
"Pure function to format sessions for display.
72+
SESSIONS is a list of session info plists.
73+
Returns a formatted string for display."
74+
(if (null sessions)
75+
"No active sessions found\n"
76+
(concat "Active enkan-repl sessions:\n"
77+
"Press 'd' to delete a session, 'q' to quit\n"
78+
"─────────────────────────────────────────\n\n"
79+
(mapconcat
80+
(lambda (session)
81+
(format " %s\n Directory: %s\n Status: %s\n\n"
82+
(plist-get session :name)
83+
(plist-get session :directory)
84+
(plist-get session :status)))
85+
sessions
86+
""))))
87+
88+
(defun enkan-repl--format-numbered-sessions (sessions)
89+
"Pure function to format numbered session list.
90+
SESSIONS is a list of session info plists.
91+
Returns a formatted string with numbered sessions."
92+
(let ((result "")
93+
(index 1))
94+
(dolist (session sessions)
95+
(setq result
96+
(concat result
97+
(format "%d. %s — Directory: %s, Status: %s\n"
98+
index
99+
(plist-get session :name)
100+
(plist-get session :directory)
101+
(plist-get session :status))))
102+
(setq index (1+ index)))
103+
result))
104+
105+
(defun enkan-repl--format-minibuffer-sessions (sessions)
106+
"Pure function to format sessions for minibuffer display.
107+
SESSIONS is a list of session info plists.
108+
Returns a list of formatted strings, one per session."
109+
(let ((index 1)
110+
(result '()))
111+
(dolist (session sessions)
112+
(push (format "%d. %s — Directory: %s, Status: %s"
113+
index
114+
(plist-get session :name)
115+
(plist-get session :directory)
116+
(plist-get session :status))
117+
result)
118+
(setq index (1+ index)))
119+
(nreverse result)))
120+
121+
(defun enkan-repl--prepare-session-candidates (sessions)
122+
"Pure function to prepare candidates for completing-read.
123+
SESSIONS is a list of session info plists.
124+
Returns an alist of (NAME . DESCRIPTION)."
125+
(mapcar
126+
(lambda (session)
127+
(cons (plist-get session :name)
128+
(format "Directory: %s, Status: %s"
129+
(plist-get session :directory)
130+
(plist-get session :status))))
131+
sessions))
132+
133+
;;; Session Selection and Actions
134+
135+
(defun enkan-repl--find-session-buffer (selected-name buffer-info-list)
136+
"Pure function to find buffer for selected session.
137+
SELECTED-NAME is the selected session name.
138+
BUFFER-INFO-LIST is the list of buffer info plists.
139+
Returns the buffer object or nil."
140+
(let ((buf-info (cl-find-if
141+
(lambda (info)
142+
(equal (plist-get info :name) selected-name))
143+
buffer-info-list)))
144+
(and buf-info (plist-get buf-info :buffer))))
145+
146+
(defun enkan-repl--session-action (action selected-name)
147+
"Pure function to determine session action result.
148+
ACTION is the action character (?s, ?d, ?c).
149+
SELECTED-NAME is the selected session name.
150+
Returns a plist with :type and :message."
151+
(cl-case action
152+
(?s (list :type 'switch
153+
:message (format "Switching to session: %s" selected-name)))
154+
(?d (list :type 'delete
155+
:message (format "Deleting session: %s" selected-name)))
156+
(?c (list :type 'copy
157+
:message (format "Copying session path: %s" selected-name)))
158+
(t (list :type 'unknown
159+
:message "Unknown action"))))
160+
161+
(defun enkan-repl--validate-session-selection (selection candidates)
162+
"Pure function to validate session selection.
163+
SELECTION is the user's selection.
164+
CANDIDATES is the list of valid candidates.
165+
Returns the valid selection or nil."
166+
(when (and selection
167+
(member selection candidates))
168+
selection))
169+
170+
(defun enkan-repl--format-session-list (sessions)
171+
"Pure function to format sessions for display.
172+
SESSIONS is a list of session info plists.
173+
Returns a formatted string for display."
174+
(if (null sessions)
175+
"No sessions found\n"
176+
(concat "Sessions:\n"
177+
(mapconcat
178+
(lambda (session)
179+
(format " %s (%s) - %s"
180+
(plist-get session :name)
181+
(plist-get session :directory)
182+
(plist-get session :status)))
183+
sessions
184+
"\n"))))
185+
186+
;;; Session State Information
187+
188+
(defun enkan-repl--get-current-session-state-info (current-project session-list session-counter project-aliases)
189+
"Pure function to get formatted session state information.
190+
CURRENT-PROJECT is the current project name or nil.
191+
SESSION-LIST is an alist of (session-number . project-name).
192+
SESSION-COUNTER is the next session number.
193+
PROJECT-ALIASES is a list of project aliases.
194+
Returns a plist with formatted information."
195+
(list :current-project (or current-project "none")
196+
:session-count (length session-list)
197+
:next-session-number session-counter
198+
:sessions (if session-list
199+
(mapconcat (lambda (s)
200+
(format "%d -> %s" (car s) (cdr s)))
201+
session-list ", ")
202+
"none")
203+
:aliases (if project-aliases
204+
(mapconcat #'identity project-aliases ", ")
205+
"none")))
206+
207+
(defun enkan-repl--format-session-state-display (state-info &optional prefix)
208+
"Pure function to format session state for display.
209+
STATE-INFO is a plist from `enkan-repl--get-current-session-state-info'.
210+
PREFIX is an optional string prefix for each line.
211+
Returns a formatted string."
212+
(let ((prefix (or prefix "")))
213+
(format "%sCurrent project: %s
214+
%sActive sessions: %d
215+
%sNext session #: %d
216+
%sSessions: %s
217+
%sAliases: %s"
218+
prefix (plist-get state-info :current-project)
219+
prefix (plist-get state-info :session-count)
220+
prefix (plist-get state-info :next-session-number)
221+
prefix (plist-get state-info :sessions)
222+
prefix (plist-get state-info :aliases))))
223+
224+
;;; Session Deletion Support
225+
226+
(defun enkan-repl--find-deletion-bounds (lines current-line)
227+
"Pure function to find bounds for deletion.
228+
LINES is a list of strings (buffer lines).
229+
CURRENT-LINE is the current line number (0-indexed).
230+
Returns a plist with :start-line and :end-line for deletion bounds,
231+
or nil if not on a session entry."
232+
(when (and (>= current-line 0)
233+
(< current-line (length lines)))
234+
(let ((line (nth current-line lines)))
235+
;; Check if we're on or near a session entry
236+
(cond
237+
;; On session name line
238+
((and (>= (length line) 2)
239+
(enkan-repl--is-enkan-buffer-name (substring line 2)))
240+
(list :start-line current-line
241+
:end-line (min (+ current-line 4) (length lines))))
242+
;; On directory line
243+
((and (> current-line 0)
244+
(string-match "^ Directory:" line)
245+
(let ((prev-line (nth (1- current-line) lines)))
246+
(and (>= (length prev-line) 2)
247+
(enkan-repl--is-enkan-buffer-name (substring prev-line 2)))))
248+
(list :start-line (1- current-line)
249+
:end-line (min (+ current-line 3) (length lines))))
250+
;; On status line
251+
((and (> current-line 1)
252+
(string-match "^ Status:" line)
253+
(let ((prev-prev-line (nth (- current-line 2) lines)))
254+
(and (>= (length prev-prev-line) 2)
255+
(enkan-repl--is-enkan-buffer-name (substring prev-prev-line 2)))))
256+
(list :start-line (- current-line 2)
257+
:end-line (min (+ current-line 2) (length lines))))
258+
(t nil)))))
259+
260+
(provide 'enkan-repl-sessions)
261+
;;; enkan-repl-sessions.el ends here

enkan-repl-utils.el

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -518,7 +518,9 @@ Return (project-name . project-path) or nil if not found."
518518
(defun enkan-repl--get-project-path-from-directories (project-name target-directories)
519519
"Pure function to get project path from directories by project name."
520520
(let ((project-info (cl-find-if (lambda (entry)
521-
(string= (car (cdr entry)) project-name))
521+
(let ((value (cdr entry)))
522+
(and (consp value)
523+
(string= (car value) project-name))))
522524
target-directories)))
523525
(when project-info
524526
(cdr (cdr project-info)))))

0 commit comments

Comments
 (0)