mirror of
https://github.com/anomalyco/opencode.git
synced 2026-09-05 08:54:38 +00:00
feat(tui): add copy session ID command (#47064)
Co-authored-by: thdxr <826656+thdxr@users.noreply.github.com>
This commit is contained in:
parent
0f6393dab1
commit
5d8a01dedc
3 changed files with 91 additions and 0 deletions
|
|
@ -103,6 +103,7 @@ export const Definitions = {
|
|||
|
||||
"session.export": keybind("<leader>x", "Export session to editor"),
|
||||
"session.copy": keybind("none", "Copy session transcript"),
|
||||
"session.copy.id": keybind("none", "Copy session ID"),
|
||||
"session.move": keybind("none", "Move session"),
|
||||
"session.new": keybind("<leader>n", "Create a new session"),
|
||||
"session.list": keybind("<leader>l", "List all sessions"),
|
||||
|
|
|
|||
|
|
@ -1182,6 +1182,18 @@ export function Session(props: {
|
|||
dialog.clear()
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Copy session ID",
|
||||
id: "session.copy.id",
|
||||
group: "Session",
|
||||
run: () => {
|
||||
clipboard
|
||||
.write(route.sessionID)
|
||||
.then(() => toast.show({ message: "Session ID copied to clipboard!", variant: "success" }))
|
||||
.catch(() => toast.show({ message: "Failed to copy session ID", variant: "error" }))
|
||||
dialog.clear()
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Copy session transcript",
|
||||
id: "session.copy",
|
||||
|
|
|
|||
78
packages/tui/test/copy-session-id.test.tsx
Normal file
78
packages/tui/test/copy-session-id.test.tsx
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
import { expect, spyOn, test } from "bun:test"
|
||||
import { InputRenderable, TextareaRenderable } from "@opentui/core"
|
||||
import { createTestRenderer } from "@opentui/core/testing"
|
||||
import { Effect, FileSystem } from "effect"
|
||||
import { Global } from "@opencode-ai/util/global"
|
||||
import { createEventStream, createFetch, directory, json } from "./fixture/tui-client"
|
||||
import { tmpdir } from "./fixture/fixture"
|
||||
|
||||
test.each(["success", "failure", "home"])("Copy session ID from Ctrl+P (%s)", async (mode) => {
|
||||
await using state = await tmpdir()
|
||||
const setup = await createTestRenderer({ width: 100, height: 30, useThread: false, kittyKeyboard: true })
|
||||
setup.renderer.start()
|
||||
Object.defineProperty(setup.renderer, "capabilities", { get: () => null })
|
||||
const copy = spyOn(setup.renderer, "copyToClipboardOSC52").mockReturnValue(mode === "success")
|
||||
const sessionID = "ses_copy_id"
|
||||
const events = createEventStream()
|
||||
const calls = createFetch((url) => {
|
||||
if (url.pathname === `/api/session/${sessionID}`)
|
||||
return json({
|
||||
data: {
|
||||
id: sessionID,
|
||||
projectID: "proj_test",
|
||||
title: "Copy ID fixture",
|
||||
location: { directory },
|
||||
cost: 0,
|
||||
tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
|
||||
time: { created: 0, updated: 0 },
|
||||
},
|
||||
})
|
||||
if (url.pathname === `/api/session/${sessionID}/message`) return json({ data: [], cursor: {} })
|
||||
if (url.pathname === `/api/session/${sessionID}/inbox` || url.pathname === `/api/session/${sessionID}/permission`)
|
||||
return json({ data: [] })
|
||||
return undefined
|
||||
}, events)
|
||||
const server = Bun.serve({ port: 0, idleTimeout: 0, fetch: (request) => calls.fetch(request) })
|
||||
const { run } = await import("../src/app")
|
||||
const task = Effect.runPromise(
|
||||
run({
|
||||
app: { name: "test", version: "test", channel: "test" },
|
||||
server: { endpoint: { url: server.url.toString() } },
|
||||
config: { get: async () => ({ animations: false }), update: async () => ({}) },
|
||||
packages: { prepare: async () => ({ directory: "" }) },
|
||||
terminalHandoff: async () => ({ renderer: setup.renderer, mode: "dark", complete: () => {} }),
|
||||
args: mode === "home" ? {} : { sessionID },
|
||||
log: () => {},
|
||||
}).pipe(Effect.provide(Global.layerWith({ state: state.path })), Effect.provide(FileSystem.layerNoop({}))),
|
||||
)
|
||||
try {
|
||||
await setup.waitFor(() => setup.renderer.currentFocusedEditor instanceof TextareaRenderable)
|
||||
setup.mockInput.pressKey("p", { ctrl: true })
|
||||
await setup.waitFor(() => setup.renderer.currentFocusedEditor instanceof InputRenderable)
|
||||
await setup.mockInput.typeText("Copy session ID")
|
||||
if (mode === "home") {
|
||||
await setup.waitForVisualIdle()
|
||||
expect(setup.captureCharFrame()).not.toMatch(/Copy session ID\s+Session/)
|
||||
expect(copy).not.toHaveBeenCalled()
|
||||
return
|
||||
}
|
||||
await setup.waitForFrame((frame) => /Copy session ID\s+Session/.test(frame))
|
||||
setup.mockInput.pressEnter()
|
||||
const frame = await setup.waitForFrame((frame) =>
|
||||
frame.includes(mode === "success" ? "Session ID copied to clipboard!" : "Failed to copy session ID"),
|
||||
)
|
||||
expect(copy).toHaveBeenCalledTimes(1)
|
||||
expect(copy.mock.calls[0]?.[0]).toBe(sessionID)
|
||||
expect(frame).not.toContain("Copy session ID")
|
||||
await setup.waitFor(
|
||||
() =>
|
||||
setup.renderer.currentFocusedEditor instanceof TextareaRenderable &&
|
||||
!(setup.renderer.currentFocusedEditor instanceof InputRenderable),
|
||||
)
|
||||
} finally {
|
||||
copy.mockRestore()
|
||||
setup.renderer.destroy()
|
||||
await task
|
||||
await server.stop()
|
||||
}
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue