mirror of
https://github.com/aljazceru/opencode.git
synced 2025-12-21 01:34:22 +01:00
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
This commit is contained in:
@@ -7,6 +7,7 @@ import { Locale } from "@/util/locale"
|
|||||||
import { Keybind } from "@/util/keybind"
|
import { Keybind } from "@/util/keybind"
|
||||||
import { useTheme } from "../context/theme"
|
import { useTheme } from "../context/theme"
|
||||||
import { useSDK } from "../context/sdk"
|
import { useSDK } from "../context/sdk"
|
||||||
|
import { DialogSessionRename } from "./dialog-session-rename"
|
||||||
|
|
||||||
export function DialogSessionList() {
|
export function DialogSessionList() {
|
||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
@@ -74,6 +75,13 @@ export function DialogSessionList() {
|
|||||||
setToDelete(option.value)
|
setToDelete(option.value)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
keybind: Keybind.parse("r")[0],
|
||||||
|
title: "rename",
|
||||||
|
onTrigger: async (option) => {
|
||||||
|
dialog.replace(() => <DialogSessionRename session={option.value} />)
|
||||||
|
},
|
||||||
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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()}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -222,6 +222,11 @@ export function Autocomplete(props: {
|
|||||||
description: "unshare a session",
|
description: "unshare a session",
|
||||||
onSelect: () => command.trigger("session.unshare"),
|
onSelect: () => command.trigger("session.unshare"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
display: "/rename",
|
||||||
|
description: "rename session",
|
||||||
|
onSelect: () => command.trigger("session.rename"),
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
results.push(
|
results.push(
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ import { Sidebar } from "./sidebar"
|
|||||||
import { LANGUAGE_EXTENSIONS } from "@/lsp/language"
|
import { LANGUAGE_EXTENSIONS } from "@/lsp/language"
|
||||||
import parsers from "../../../../../../parsers-config.ts"
|
import parsers from "../../../../../../parsers-config.ts"
|
||||||
import { Toast } from "../../ui/toast"
|
import { Toast } from "../../ui/toast"
|
||||||
|
import { DialogSessionRename } from "../../component/dialog-session-rename"
|
||||||
|
|
||||||
addDefaultParsers(parsers.parsers)
|
addDefaultParsers(parsers.parsers)
|
||||||
|
|
||||||
@@ -370,6 +371,15 @@ export function Session() {
|
|||||||
dialog.clear()
|
dialog.clear()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "Rename session",
|
||||||
|
value: "session.rename",
|
||||||
|
keybind: "session_rename",
|
||||||
|
category: "Session",
|
||||||
|
onSelect: (dialog) => {
|
||||||
|
dialog.replace(() => <DialogSessionRename session={route.sessionID} />)
|
||||||
|
},
|
||||||
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
const revert = createMemo(() => {
|
const revert = createMemo(() => {
|
||||||
|
|||||||
65
packages/opencode/src/cli/cmd/tui/ui/dialog-prompt.tsx
Normal file
65
packages/opencode/src/cli/cmd/tui/ui/dialog-prompt.tsx
Normal file
@@ -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),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -138,7 +138,10 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
|
|||||||
for (const item of props.keybind ?? []) {
|
for (const item of props.keybind ?? []) {
|
||||||
if (Keybind.match(item.keybind, keybind.parse(evt))) {
|
if (Keybind.match(item.keybind, keybind.parse(evt))) {
|
||||||
const s = selected()
|
const s = selected()
|
||||||
if (s) item.onTrigger(s)
|
if (s) {
|
||||||
|
evt.preventDefault()
|
||||||
|
item.onTrigger(s)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user