diff options
| author | Dax Raad <[email protected]> | 2025-10-31 18:44:27 -0400 |
|---|---|---|
| committer | Dax Raad <[email protected]> | 2025-10-31 18:44:33 -0400 |
| commit | 30f9fa12d9fa1bb08c76b282d9b4fb4f88246f3f (patch) | |
| tree | bb12ed6d3fc3a2bdc01bb0f30f27f75fdd4fd46b | |
| parent | d473d4ffc8d15e1b6cbb34f5171e6b600f8d3eaa (diff) | |
| download | opencode-30f9fa12d9fa1bb08c76b282d9b4fb4f88246f3f.tar.gz opencode-30f9fa12d9fa1bb08c76b282d9b4fb4f88246f3f.zip | |
tui: add session rename functionality with /rename command
- Add /rename command to autocomplete when a session is active
- Add rename dialog component for changing session names
- Add rename option to session list dialog with 'r' keybind
- Add session rename command to command registry
6 files changed, 127 insertions, 1 deletions
diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx index 605eb2bff..33a5d816e 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx @@ -7,6 +7,7 @@ import { Locale } from "@/util/locale" import { Keybind } from "@/util/keybind" import { useTheme } from "../context/theme" import { useSDK } from "../context/sdk" +import { DialogSessionRename } from "./dialog-session-rename" export function DialogSessionList() { const dialog = useDialog() @@ -74,6 +75,13 @@ export function DialogSessionList() { setToDelete(option.value) }, }, + { + keybind: Keybind.parse("r")[0], + title: "rename", + onTrigger: async (option) => { + dialog.replace(() => <DialogSessionRename session={option.value} />) + }, + }, ]} /> ) diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-session-rename.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-session-rename.tsx new file mode 100644 index 000000000..aaf033200 --- /dev/null +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-session-rename.tsx @@ -0,0 +1,35 @@ +import { DialogPrompt } from "@tui/ui/dialog-prompt" +import { useDialog } from "@tui/ui/dialog" +import { useSync } from "@tui/context/sync" +import { createMemo } from "solid-js" +import { useSDK } from "../context/sdk" + +interface DialogSessionRenameProps { + session: string +} + +export function DialogSessionRename(props: DialogSessionRenameProps) { + const dialog = useDialog() + const sync = useSync() + const sdk = useSDK() + const session = createMemo(() => sync.session.get(props.session)) + + return ( + <DialogPrompt + title="Rename Session" + value={session()?.title} + onConfirm={(value) => { + sdk.client.session.update({ + path: { + id: props.session, + }, + body: { + title: value, + }, + }) + dialog.clear() + }} + onCancel={() => dialog.clear()} + /> + ) +} diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx index da9caa5a2..92798b942 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx @@ -222,6 +222,11 @@ export function Autocomplete(props: { description: "unshare a session", onSelect: () => command.trigger("session.unshare"), }, + { + display: "/rename", + description: "rename session", + onSelect: () => command.trigger("session.rename"), + }, ) } results.push( diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx index baa63d370..78b1ed4ab 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx @@ -63,6 +63,7 @@ import { Sidebar } from "./sidebar" import { LANGUAGE_EXTENSIONS } from "@/lsp/language" import parsers from "../../../../../../parsers-config.ts" import { Toast } from "../../ui/toast" +import { DialogSessionRename } from "../../component/dialog-session-rename" addDefaultParsers(parsers.parsers) @@ -370,6 +371,15 @@ export function Session() { dialog.clear() }, }, + { + title: "Rename session", + value: "session.rename", + keybind: "session_rename", + category: "Session", + onSelect: (dialog) => { + dialog.replace(() => <DialogSessionRename session={route.sessionID} />) + }, + }, ]) const revert = createMemo(() => { diff --git a/packages/opencode/src/cli/cmd/tui/ui/dialog-prompt.tsx b/packages/opencode/src/cli/cmd/tui/ui/dialog-prompt.tsx new file mode 100644 index 000000000..a77727aae --- /dev/null +++ b/packages/opencode/src/cli/cmd/tui/ui/dialog-prompt.tsx @@ -0,0 +1,65 @@ +import { TextareaRenderable, TextAttributes } from "@opentui/core" +import { useTheme } from "../context/theme" +import { useDialog, type DialogContext } from "./dialog" +import { onMount } from "solid-js" + +export type DialogPromptProps = { + title: string + value?: string + onConfirm?: (value: string) => void + onCancel?: () => void +} + +export function DialogPrompt(props: DialogPromptProps) { + const dialog = useDialog() + const { theme } = useTheme() + let textarea: TextareaRenderable + + onMount(() => { + dialog.setSize("large") + setTimeout(() => { + textarea.focus() + }, 1) + textarea.gotoLineEnd() + }) + + return ( + <box paddingLeft={2} paddingRight={2} gap={1}> + <box flexDirection="row" justifyContent="space-between"> + <text attributes={TextAttributes.BOLD}>{props.title}</text> + <text fg={theme.textMuted}>esc</text> + </box> + <box> + <textarea + onSubmit={() => { + props.onConfirm?.(textarea.plainText) + dialog.clear() + }} + keyBindings={[{ name: "return", action: "submit" }]} + ref={(val: TextareaRenderable) => (textarea = val)} + initialValue={props.value} + placeholder="Enter text" + /> + </box> + <box paddingBottom={1}> + <text fg={theme.textMuted}>Press enter to confirm, esc to cancel</text> + </box> + </box> + ) +} + +DialogPrompt.show = (dialog: DialogContext, title: string, value?: string) => { + return new Promise<string | null>((resolve) => { + dialog.replace( + () => ( + <DialogPrompt + title={title} + value={value} + onConfirm={(value) => resolve(value)} + onCancel={() => resolve(null)} + /> + ), + () => resolve(null), + ) + }) +} diff --git a/packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx b/packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx index 7689b3775..3c25c4a87 100644 --- a/packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx +++ b/packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx @@ -138,7 +138,10 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) { for (const item of props.keybind ?? []) { if (Keybind.match(item.keybind, keybind.parse(evt))) { const s = selected() - if (s) item.onTrigger(s) + if (s) { + evt.preventDefault() + item.onTrigger(s) + } } } }) |
