Skip to content

Commit fc20a7e

Browse files
authored
Merge pull request #159 from Feiry-zZ/feature-zf-rename
feat: session name can be edited now
2 parents 70aea2b + 17e39d3 commit fc20a7e

3 files changed

Lines changed: 131 additions & 7 deletions

File tree

src/session.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1671,6 +1671,27 @@ ${skillMd}
16711671
return true;
16721672
}
16731673

1674+
/**
1675+
* Rename a session by updating its summary (display title).
1676+
* Returns true if the session was found and renamed, false otherwise.
1677+
*/
1678+
renameSession(sessionId: string, summary: string): boolean {
1679+
const trimmed = summary.trim();
1680+
if (!trimmed) {
1681+
return false;
1682+
}
1683+
const entry = this.getSession(sessionId);
1684+
if (!entry) {
1685+
return false;
1686+
}
1687+
this.updateSessionEntry(sessionId, (existing) => ({
1688+
...existing,
1689+
summary: trimmed,
1690+
updateTime: new Date().toISOString(),
1691+
}));
1692+
return true;
1693+
}
1694+
16741695
listSessionMessages(sessionId: string): SessionMessage[] {
16751696
const messagePath = this.getSessionMessagesPath(sessionId);
16761697
if (!fs.existsSync(messagePath)) {

src/ui/views/App.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -751,6 +751,14 @@ function App({ projectRoot, initialPrompt, onRestart }: AppProps): React.ReactEl
751751
onDelete={(id) => {
752752
void handleDeleteSession(id);
753753
}}
754+
onRename={(id, newName) => {
755+
if (sessionManager.renameSession(id, newName)) {
756+
refreshSessionsList();
757+
setStatusLine(`Session renamed to "${newName}".`);
758+
} else {
759+
setErrorLine("Failed to rename session.");
760+
}
761+
}}
754762
/>
755763
) : view === "undo" ? (
756764
<UndoSelector

src/ui/views/SessionList.tsx

Lines changed: 102 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ type Props = {
88
onSelect: (sessionId: string) => void;
99
onCancel: () => void;
1010
onDelete?: (sessionId: string) => void;
11+
onRename?: (sessionId: string, newName: string) => void;
1112
};
1213

1314
/**
@@ -38,10 +39,13 @@ export function filterSessions(sessions: SessionEntry[], query: string): Session
3839
});
3940
}
4041

41-
export function SessionList({ sessions, onSelect, onCancel, onDelete }: Props): React.ReactElement {
42+
export function SessionList({ sessions, onSelect, onCancel, onDelete, onRename }: Props): React.ReactElement {
4243
const [index, setIndex] = useState(0);
4344
const [searchQuery, setSearchQuery] = useState("");
4445
const [confirmDeleteSessionId, setConfirmDeleteSessionId] = useState<string | null>(null);
46+
const [renameSessionId, setRenameSessionId] = useState<string | null>(null);
47+
const [renameValue, setRenameValue] = useState("");
48+
const [renameCursor, setRenameCursor] = useState(0);
4549
const { columns, rows } = useWindowSize();
4650

4751
// Filter sessions by search query
@@ -83,6 +87,65 @@ export function SessionList({ sessions, onSelect, onCancel, onDelete }: Props):
8387
const selectedSession = filteredSessions[safeIndex];
8488

8589
useInput((input, key) => {
90+
// If in rename mode, handle rename editing
91+
if (renameSessionId) {
92+
if (key.return) {
93+
if (renameValue.trim()) {
94+
onRename?.(renameSessionId, renameValue.trim());
95+
}
96+
setRenameSessionId(null);
97+
setRenameValue("");
98+
setRenameCursor(0);
99+
return;
100+
}
101+
if (key.escape) {
102+
setRenameSessionId(null);
103+
setRenameValue("");
104+
setRenameCursor(0);
105+
return;
106+
}
107+
if (key.leftArrow) {
108+
setRenameCursor((c) => Math.max(0, c - 1));
109+
return;
110+
}
111+
if (key.rightArrow) {
112+
setRenameCursor((c) => Math.min(renameValue.length, c + 1));
113+
return;
114+
}
115+
if (key.home) {
116+
setRenameCursor(0);
117+
return;
118+
}
119+
if (key.end) {
120+
setRenameCursor(renameValue.length);
121+
return;
122+
}
123+
if (key.delete) {
124+
if (renameCursor < renameValue.length) {
125+
setRenameValue((prev) => prev.slice(0, renameCursor) + prev.slice(renameCursor + 1));
126+
// cursor stays at same position (next char shifts left)
127+
}
128+
return;
129+
}
130+
if (key.backspace) {
131+
if (renameCursor > 0) {
132+
setRenameValue((prev) => prev.slice(0, renameCursor - 1) + prev.slice(renameCursor));
133+
setRenameCursor((c) => c - 1);
134+
}
135+
return;
136+
}
137+
// Printable character: insert at cursor position
138+
if (input && input.length > 0 && !key.meta && !key.ctrl && !key.tab) {
139+
if (key.upArrow || key.downArrow) {
140+
return;
141+
}
142+
setRenameValue((prev) => prev.slice(0, renameCursor) + input + prev.slice(renameCursor));
143+
setRenameCursor((c) => c + input.length);
144+
return;
145+
}
146+
return;
147+
}
148+
86149
// If in delete confirmation mode, handle confirm/cancel
87150
if (confirmDeleteSessionId) {
88151
if (key.return) {
@@ -114,6 +177,17 @@ export function SessionList({ sessions, onSelect, onCancel, onDelete }: Props):
114177
return;
115178
}
116179

180+
// Ctrl+R: start rename on selected session
181+
if (key.ctrl && (input === "r" || input === "R")) {
182+
if (selectedSession && onRename) {
183+
const name = selectedSession.summary || "";
184+
setRenameSessionId(selectedSession.id);
185+
setRenameValue(name);
186+
setRenameCursor(name.length);
187+
return;
188+
}
189+
}
190+
117191
// Delete key: remove search character, or start delete confirmation
118192
if (key.delete || key.backspace) {
119193
if (searchQuery) {
@@ -237,19 +311,28 @@ export function SessionList({ sessions, onSelect, onCancel, onDelete }: Props):
237311
const actualIndex = scrollOffset + i;
238312
const isSelected = actualIndex === safeIndex;
239313
const isConfirming = confirmDeleteSessionId === session.id;
314+
const isRenaming = renameSessionId === session.id;
240315
return (
241316
<Box key={session.id} height={2} marginBottom={1}>
242317
<Box>
243318
<Text color="#229ac3">{isSelected ? "> " : " "}</Text>
244319
</Box>
245320
<Box flexDirection="column" flexGrow={1}>
246321
<Box width={"100%"}>
247-
<Text {...(isSelected ? { bold: true } : {})} color={isSelected ? "#229ac3" : undefined}>
248-
{formatSessionTitle(session.summary || "Untitled")}
249-
</Text>
322+
{isRenaming ? (
323+
<Text color="yellow">
324+
Rename: {renameValue.slice(0, renameCursor)}
325+
<Text bold>|</Text>
326+
{renameValue.slice(renameCursor)}
327+
</Text>
328+
) : (
329+
<Text {...(isSelected ? { bold: true } : {})} color={isSelected ? "#229ac3" : undefined}>
330+
{formatSessionTitle(session.summary || "Untitled")}
331+
</Text>
332+
)}
250333
{isConfirming ? (
251334
<Text color="yellow"> [Delete? Enter=yes, Esc=no]</Text>
252-
) : (
335+
) : isRenaming ? null : (
253336
<Text dimColor> ({formatSessionStatus(session.status)})</Text>
254337
)}
255338
</Box>
@@ -272,7 +355,19 @@ export function SessionList({ sessions, onSelect, onCancel, onDelete }: Props):
272355
</Box>
273356
{/* Footer */}
274357
<Box flexDirection="column">
275-
{confirmDeleteSessionId ? (
358+
{renameSessionId ? (
359+
<Box>
360+
<Text color="yellow">Input new session name, </Text>
361+
<Text bold color="green">
362+
Enter
363+
</Text>
364+
<Text dimColor> to save · </Text>
365+
<Text bold color="red">
366+
Esc
367+
</Text>
368+
<Text dimColor> to cancel</Text>
369+
</Box>
370+
) : confirmDeleteSessionId ? (
276371
<Box>
277372
<Text color="yellow">Delete this session? </Text>
278373
<Text bold color="green">
@@ -292,7 +387,7 @@ export function SessionList({ sessions, onSelect, onCancel, onDelete }: Props):
292387
) : (
293388
<Box>
294389
<Text dimColor>
295-
Type to search · ↑/↓ navigate · PgUp/PgDn page · Enter select · Esc cancel · Del delete
390+
Type to search · ↑/↓ navigate · PgUp/PgDn page · Enter select · Esc cancel · Del delete · Ctrl+r rename
296391
</Text>
297392
</Box>
298393
)}

0 commit comments

Comments
 (0)