summaryrefslogtreecommitdiffhomepage
path: root/packages/app
diff options
context:
space:
mode:
authorAdam <[email protected]>2026-02-11 08:19:42 -0600
committerAdam <[email protected]>2026-02-11 08:47:26 -0600
commit7222fc0ba0e46b3ee787be608c71738aa14fe480 (patch)
tree235ac36d586ec8a15ad7a5ad85742792debbbf7d /packages/app
parent17bdb5d56a671972d7d5fad53c3a16df45f9cd20 (diff)
downloadopencode-7222fc0ba0e46b3ee787be608c71738aa14fe480.tar.gz
opencode-7222fc0ba0e46b3ee787be608c71738aa14fe480.zip
fix(app): terminal resize
Diffstat (limited to 'packages/app')
-rw-r--r--packages/app/src/components/terminal.tsx44
-rw-r--r--packages/app/src/context/terminal.tsx53
-rw-r--r--packages/app/src/pages/layout.tsx9
3 files changed, 84 insertions, 22 deletions
diff --git a/packages/app/src/components/terminal.tsx b/packages/app/src/components/terminal.tsx
index 2527c74ec..09c04db40 100644
--- a/packages/app/src/components/terminal.tsx
+++ b/packages/app/src/components/terminal.tsx
@@ -91,7 +91,7 @@ export const Terminal = (props: TerminalProps) => {
}
const getTerminalColors = (): TerminalColors => {
- const mode = theme.mode()
+ const mode = theme.mode() === "dark" ? "dark" : "light"
const fallback = DEFAULT_TERMINAL_COLORS[mode]
const currentTheme = theme.themes()[theme.themeId()]
if (!currentTheme) return fallback
@@ -186,9 +186,23 @@ export const Terminal = (props: TerminalProps) => {
}
ws = socket
+ const restore = typeof local.pty.buffer === "string" ? local.pty.buffer : ""
+ const restoreSize =
+ restore &&
+ typeof local.pty.cols === "number" &&
+ Number.isSafeInteger(local.pty.cols) &&
+ local.pty.cols > 0 &&
+ typeof local.pty.rows === "number" &&
+ Number.isSafeInteger(local.pty.rows) &&
+ local.pty.rows > 0
+ ? { cols: local.pty.cols, rows: local.pty.rows }
+ : undefined
+
const t = new mod.Terminal({
cursorBlink: true,
cursorStyle: "bar",
+ cols: restoreSize?.cols,
+ rows: restoreSize?.rows,
fontSize: 14,
fontFamily: monoFontFamily(settings.appearance.font()),
allowTransparency: false,
@@ -277,19 +291,29 @@ export const Terminal = (props: TerminalProps) => {
focusTerminal()
- fit.fit()
+ const startResize = () => {
+ fit.observeResize()
+ handleResize = () => fit.fit()
+ window.addEventListener("resize", handleResize)
+ cleanups.push(() => window.removeEventListener("resize", handleResize))
+ }
- if (local.pty.buffer) {
- t.write(local.pty.buffer, () => {
- if (local.pty.scrollY) t.scrollToLine(local.pty.scrollY)
+ if (restore && restoreSize) {
+ t.write(restore, () => {
+ fit.fit()
+ if (typeof local.pty.scrollY === "number") t.scrollToLine(local.pty.scrollY)
+ startResize()
})
+ } else {
+ fit.fit()
+ if (restore) {
+ t.write(restore, () => {
+ if (typeof local.pty.scrollY === "number") t.scrollToLine(local.pty.scrollY)
+ })
+ }
+ startResize()
}
- fit.observeResize()
- handleResize = () => fit.fit()
- window.addEventListener("resize", handleResize)
- cleanups.push(() => window.removeEventListener("resize", handleResize))
-
const onResize = t.onResize(async (size) => {
if (socket.readyState === WebSocket.OPEN) {
await sdk.client.pty
diff --git a/packages/app/src/context/terminal.tsx b/packages/app/src/context/terminal.tsx
index f0f184f8b..f6e36319b 100644
--- a/packages/app/src/context/terminal.tsx
+++ b/packages/app/src/context/terminal.tsx
@@ -3,7 +3,7 @@ import { createSimpleContext } from "@opencode-ai/ui/context"
import { batch, createEffect, createMemo, createRoot, onCleanup } from "solid-js"
import { useParams } from "@solidjs/router"
import { useSDK } from "./sdk"
-import { Persist, persisted } from "@/utils/persist"
+import { Persist, persisted, removePersisted } from "@/utils/persist"
export type LocalPTY = {
id: string
@@ -35,6 +35,28 @@ type TerminalCacheEntry = {
dispose: VoidFunction
}
+const caches = new Set<Map<string, TerminalCacheEntry>>()
+
+export function clearWorkspaceTerminals(dir: string, sessionIDs?: string[]) {
+ const key = getWorkspaceTerminalCacheKey(dir)
+ for (const cache of caches) {
+ const entry = cache.get(key)
+ entry?.value.clear()
+ }
+
+ removePersisted(Persist.workspace(dir, "terminal"))
+
+ const legacy = new Set(getLegacyTerminalStorageKeys(dir))
+ for (const id of sessionIDs ?? []) {
+ for (const key of getLegacyTerminalStorageKeys(dir, id)) {
+ legacy.add(key)
+ }
+ }
+ for (const key of legacy) {
+ removePersisted({ key })
+ }
+}
+
function createWorkspaceTerminalSession(sdk: ReturnType<typeof useSDK>, dir: string, legacySessionID?: string) {
const legacy = getLegacyTerminalStorageKeys(dir, legacySessionID)
@@ -56,7 +78,7 @@ function createWorkspaceTerminalSession(sdk: ReturnType<typeof useSDK>, dir: str
}),
)
- const unsub = sdk.event.on("pty.exited", (event) => {
+ const unsub = sdk.event.on("pty.exited", (event: { properties: { id: string } }) => {
const id = event.properties.id
if (!store.all.some((x) => x.id === id)) return
batch(() => {
@@ -96,6 +118,12 @@ function createWorkspaceTerminalSession(sdk: ReturnType<typeof useSDK>, dir: str
ready,
all: createMemo(() => Object.values(store.all)),
active: createMemo(() => store.active),
+ clear() {
+ batch(() => {
+ setStore("active", undefined)
+ setStore("all", [])
+ })
+ },
new() {
const existingTitleNumbers = new Set(
store.all.flatMap((pty) => {
@@ -114,7 +142,7 @@ function createWorkspaceTerminalSession(sdk: ReturnType<typeof useSDK>, dir: str
sdk.client.pty
.create({ title: `Terminal ${nextNumber}` })
- .then((pty) => {
+ .then((pty: { data?: { id?: string; title?: string } }) => {
const id = pty.data?.id
if (!id) return
const newTerminal = {
@@ -128,8 +156,8 @@ function createWorkspaceTerminalSession(sdk: ReturnType<typeof useSDK>, dir: str
})
setStore("active", id)
})
- .catch((e) => {
- console.error("Failed to create terminal", e)
+ .catch((error: unknown) => {
+ console.error("Failed to create terminal", error)
})
},
update(pty: Partial<LocalPTY> & { id: string }) {
@@ -143,8 +171,8 @@ function createWorkspaceTerminalSession(sdk: ReturnType<typeof useSDK>, dir: str
title: pty.title,
size: pty.cols && pty.rows ? { rows: pty.rows, cols: pty.cols } : undefined,
})
- .catch((e) => {
- console.error("Failed to update terminal", e)
+ .catch((error: unknown) => {
+ console.error("Failed to update terminal", error)
})
},
async clone(id: string) {
@@ -155,8 +183,8 @@ function createWorkspaceTerminalSession(sdk: ReturnType<typeof useSDK>, dir: str
.create({
title: pty.title,
})
- .catch((e) => {
- console.error("Failed to clone terminal", e)
+ .catch((error: unknown) => {
+ console.error("Failed to clone terminal", error)
return undefined
})
if (!clone?.data) return
@@ -200,8 +228,8 @@ function createWorkspaceTerminalSession(sdk: ReturnType<typeof useSDK>, dir: str
setStore("all", filtered)
})
- await sdk.client.pty.remove({ ptyID: id }).catch((e) => {
- console.error("Failed to close terminal", e)
+ await sdk.client.pty.remove({ ptyID: id }).catch((error: unknown) => {
+ console.error("Failed to close terminal", error)
})
},
move(id: string, to: number) {
@@ -225,6 +253,9 @@ export const { use: useTerminal, provider: TerminalProvider } = createSimpleCont
const params = useParams()
const cache = new Map<string, TerminalCacheEntry>()
+ caches.add(cache)
+ onCleanup(() => caches.delete(cache))
+
const disposeAll = () => {
for (const entry of cache.values()) {
entry.dispose()
diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx
index a18b7ef23..e8435c3f9 100644
--- a/packages/app/src/pages/layout.tsx
+++ b/packages/app/src/pages/layout.tsx
@@ -34,6 +34,7 @@ import type { DragEvent } from "@thisbeyond/solid-dnd"
import { useProviders } from "@/hooks/use-providers"
import { showToast, Toast, toaster } from "@opencode-ai/ui/toast"
import { useGlobalSDK } from "@/context/global-sdk"
+import { clearWorkspaceTerminals } from "@/context/terminal"
import { useNotification } from "@/context/notification"
import { usePermission } from "@/context/permission"
import { Binary } from "@opencode-ai/util/binary"
@@ -1221,11 +1222,17 @@ export default function Layout(props: ParentProps) {
})
const dismiss = () => toaster.dismiss(progress)
- const sessions = await globalSDK.client.session
+ const sessions: Session[] = await globalSDK.client.session
.list({ directory })
.then((x) => x.data ?? [])
.catch(() => [])
+ clearWorkspaceTerminals(
+ directory,
+ sessions.map((s) => s.id),
+ )
+ await globalSDK.client.instance.dispose({ directory }).catch(() => undefined)
+
const result = await globalSDK.client.worktree
.reset({ directory: root, worktreeResetInput: { directory } })
.then((x) => x.data)