mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-26 19:31:39 -04:00
fix(tui): restore terminal settings and move pane toggle to sessions (#45271)
This commit is contained in:
@@ -51,6 +51,15 @@ export const settings: Setting[] = [
|
||||
values: ["hide", "auto"],
|
||||
keywords: ["side panel"],
|
||||
},
|
||||
{
|
||||
title: "Terminal",
|
||||
category: "Session",
|
||||
path: ["session", "terminal"],
|
||||
default: false,
|
||||
values: [false, true],
|
||||
labels: ["off", "on"],
|
||||
keywords: ["pty", "shell", "terminal pane"],
|
||||
},
|
||||
{
|
||||
title: "Scrollbar",
|
||||
category: "Session",
|
||||
@@ -274,13 +283,21 @@ export const settings: Setting[] = [
|
||||
keywords: ["sound volume", "audio volume"],
|
||||
},
|
||||
{
|
||||
title: "Enabled",
|
||||
title: "Window title",
|
||||
category: "Terminal",
|
||||
path: ["terminal", "enabled"],
|
||||
default: false,
|
||||
path: ["terminal", "title"],
|
||||
default: true,
|
||||
values: [false, true],
|
||||
labels: ["off", "on"],
|
||||
keywords: ["pty", "shell", "terminal pane"],
|
||||
keywords: ["terminal title", "tab title"],
|
||||
},
|
||||
{
|
||||
title: "Copy behavior",
|
||||
category: "Terminal",
|
||||
path: ["terminal", "copy"],
|
||||
default: process.platform === "win32" ? "manual" : "select",
|
||||
values: ["manual", "select"],
|
||||
keywords: ["selection", "clipboard"],
|
||||
},
|
||||
{
|
||||
title: "Developer tools",
|
||||
|
||||
@@ -25,13 +25,13 @@ export function SessionFrame(props: { sessionID: string; verticalTabsWidth: numb
|
||||
const [restoreTerminalFocus, setRestoreTerminalFocus] = createSignal(false)
|
||||
let focusTerminal: (() => void) | undefined
|
||||
createResource(
|
||||
() => (config.data.terminal?.enabled ? props.sessionID : undefined),
|
||||
() => (config.data.session.terminal ? props.sessionID : undefined),
|
||||
(sessionID) => sessions.refresh(sessionID).catch(() => undefined),
|
||||
)
|
||||
const session = () => sessions.get(props.sessionID)
|
||||
const terminals = () => session()?.terminals ?? []
|
||||
const selectedTerminal = () => {
|
||||
if (!config.data.terminal?.enabled) return
|
||||
if (!config.data.session.terminal) return
|
||||
const value = session()
|
||||
if (value?.hidden) return
|
||||
return value?.terminals.find((terminal) => terminal.id === value.selectedTerminalID) ?? value?.terminals.at(-1)
|
||||
@@ -74,7 +74,7 @@ export function SessionFrame(props: { sessionID: string; verticalTabsWidth: numb
|
||||
prompt.current?.focus()
|
||||
})
|
||||
Keymap.createLayer(() => ({
|
||||
enabled: () => config.data.terminal?.enabled === true,
|
||||
enabled: () => config.data.session.terminal === true,
|
||||
commands: [
|
||||
{
|
||||
id: "pane.focus.left",
|
||||
|
||||
@@ -102,7 +102,6 @@ export const Info = Schema.Struct({
|
||||
).annotate({ description: "Diff presentation settings" }),
|
||||
terminal: Schema.optional(
|
||||
Schema.Struct({
|
||||
enabled: Schema.optional(Schema.Boolean).annotate({ description: "Enable persistent terminal panes" }),
|
||||
title: Schema.optional(Schema.Boolean).annotate({ description: "Update the terminal window title" }),
|
||||
copy: Schema.optional(Schema.Literals(["manual", "select"])).annotate({
|
||||
description: "Copy text manually or immediately after selecting it",
|
||||
@@ -127,6 +126,7 @@ export const Info = Schema.Struct({
|
||||
sidebar: Schema.optional(Schema.Literals(["auto", "hide"])).annotate({
|
||||
description: "Session sidebar visibility; 'auto' shows it when space permits",
|
||||
}),
|
||||
terminal: Schema.optional(Schema.Boolean).annotate({ description: "Enable persistent session terminal panes" }),
|
||||
scrollbar: Schema.optional(Schema.Boolean).annotate({ description: "Show the session transcript scrollbar" }),
|
||||
thinking: Schema.optional(Schema.Literals(["show", "hide"])).annotate({
|
||||
description: "Show or hide model reasoning by default",
|
||||
@@ -236,7 +236,7 @@ export type Resolved = Omit<Info, "attention" | "cursor" | "keybinds" | "leader"
|
||||
|
||||
export function resolve(input: Info, options: { terminalSuspend: boolean }): Resolved {
|
||||
const keybinds: TuiKeybind.KeybindOverrides = { ...input.keybinds }
|
||||
if (input.terminal?.enabled) {
|
||||
if (input.session?.terminal) {
|
||||
if (input.keybinds?.["terminal.toggle"] === undefined && input.keybinds?.["theme.switch"] === undefined) {
|
||||
keybinds["terminal.toggle"] = "<leader>t"
|
||||
keybinds["theme.switch"] = "none"
|
||||
|
||||
@@ -47,7 +47,7 @@ export const { use: useSessionTerminals, provider: SessionTerminalsProvider } =
|
||||
for (const type of ["persistent-pty.added", "persistent-pty.removed"] as const) {
|
||||
onCleanup(
|
||||
event.on(type, (evt) => {
|
||||
if (!config.terminal?.enabled || !store.sessions[evt.data.sessionID]) return
|
||||
if (!config.session.terminal || !store.sessions[evt.data.sessionID]) return
|
||||
void refresh(evt.data.sessionID).catch((error) =>
|
||||
console.error("Failed to refresh persistent terminal panes", error),
|
||||
)
|
||||
|
||||
@@ -152,7 +152,7 @@ export function Composer(props: ComposerProps) {
|
||||
</box>
|
||||
<SubagentsTab sessionID={props.sessionID} />
|
||||
<ShellTab sessionID={props.sessionID} />
|
||||
<Show when={config.terminal?.enabled}>
|
||||
<Show when={config.session.terminal}>
|
||||
<TerminalsTab sessionID={props.sessionID} visibleTerminalID={props.visibleTerminalID} />
|
||||
</Show>
|
||||
<box flexDirection="row" gap={2} paddingLeft={1} flexShrink={0}>
|
||||
|
||||
@@ -895,7 +895,7 @@ export function Session(props: {
|
||||
dialog.clear()
|
||||
},
|
||||
},
|
||||
...(config.terminal?.enabled
|
||||
...(config.session.terminal
|
||||
? [
|
||||
{
|
||||
title: props.visibleTerminalID ? "Hide terminal pane" : "Show terminal pane",
|
||||
|
||||
@@ -74,24 +74,29 @@ test("validates terminal copy behavior", () => {
|
||||
expect(decodeInfo({ terminal: { copy: "manual" } })).toEqual({ terminal: { copy: "manual" } })
|
||||
expect(decodeInfo({ terminal: { copy: "select" } })).toEqual({ terminal: { copy: "select" } })
|
||||
expect(() => decodeInfo({ terminal: { copy: "always" } })).toThrow()
|
||||
|
||||
const setting = settings.find((setting) => setting.path.join(".") === "terminal.copy")
|
||||
expect(setting?.values).toEqual(["manual", "select"])
|
||||
expect(setting?.default).toBe(process.platform === "win32" ? "manual" : "select")
|
||||
})
|
||||
|
||||
test("keeps persistent terminals disabled until explicitly enabled", () => {
|
||||
const disabled = resolve({}, { terminalSuspend: true })
|
||||
expect(disabled.terminal?.enabled ?? false).toBe(false)
|
||||
expect(disabled.session.terminal ?? false).toBe(false)
|
||||
expect(disabled.keybinds.get("theme.switch")).toMatchObject([{ key: "<leader>t" }])
|
||||
expect(disabled.keybinds.get("terminal.toggle")).toEqual([])
|
||||
expect(settings.find((setting) => setting.path.join(".") === "terminal.enabled")?.default).toBe(false)
|
||||
expect(settings.find((setting) => setting.path.join(".") === "session.terminal")?.default).toBe(false)
|
||||
expect(settings.filter((setting) => setting.category === "Terminal").map((setting) => setting.title)).toEqual([
|
||||
"Enabled",
|
||||
"Window title",
|
||||
"Copy behavior",
|
||||
])
|
||||
|
||||
const enabled = resolve({ terminal: { enabled: true } }, { terminalSuspend: true })
|
||||
const enabled = resolve({ session: { terminal: true } }, { terminalSuspend: true })
|
||||
expect(enabled.keybinds.get("terminal.toggle")).toMatchObject([{ key: "<leader>t" }])
|
||||
expect(enabled.keybinds.get("theme.switch")).toEqual([])
|
||||
|
||||
const customized = resolve(
|
||||
{ terminal: { enabled: true }, keybinds: { "theme.switch": "<leader>t", "terminal.toggle": "<leader>p" } },
|
||||
{ session: { terminal: true }, keybinds: { "theme.switch": "<leader>t", "terminal.toggle": "<leader>p" } },
|
||||
{ terminalSuspend: true },
|
||||
)
|
||||
expect(customized.keybinds.get("theme.switch")).toMatchObject([{ key: "<leader>t" }])
|
||||
|
||||
Reference in New Issue
Block a user