Просмотр исходного кода

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
Dax Raad 3 месяцев назад
Родитель
Сommit
30f9fa12d9

+ 8 - 0
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 { 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} />)
+          },
+        },
       ]}
       ]}
     />
     />
   )
   )

+ 35 - 0
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()}
+    />
+  )
+}

+ 5 - 0
packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx

@@ -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(

+ 10 - 0
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 { 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 - 0
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),
+    )
+  })
+}

+ 4 - 1
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 ?? []) {
     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)
+        }
       }
       }
     }
     }
   })
   })