refactor(tui): simplify config and tool display

This commit is contained in:
Dax Raad
2026-07-11 19:51:19 -04:00
parent 8f904c8e9a
commit ac1ddc3f83
44 changed files with 264 additions and 315 deletions
+1 -1
View File
@@ -6,7 +6,7 @@
// history ring. All are async because they read config or hit the SDK, but // history ring. All are async because they read config or hit the SDK, but
// none block each other. // none block each other.
import { Context, Effect, Layer } from "effect" import { Context, Effect, Layer } from "effect"
import { resolve } from "@opencode-ai/tui/config" import { resolve } from "@opencode-ai/tui/config/v1"
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder" import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
import { makeGlobalNode } from "@opencode-ai/core/effect/app-node" import { makeGlobalNode } from "@opencode-ai/core/effect/app-node"
import { makeRuntime } from "@opencode-ai/core/effect/runtime" import { makeRuntime } from "@opencode-ai/core/effect/runtime"
+1 -1
View File
@@ -13,7 +13,7 @@
// → OpenTUI split-footer renderer writes to terminal // → OpenTUI split-footer renderer writes to terminal
import type { OpenCodeClient, ReferenceListOutput } from "@opencode-ai/client/promise" import type { OpenCodeClient, ReferenceListOutput } from "@opencode-ai/client/promise"
import type { FilePart, PermissionRequest, QuestionRequest, ToolPart } from "@opencode-ai/sdk/v2" import type { FilePart, PermissionRequest, QuestionRequest, ToolPart } from "@opencode-ai/sdk/v2"
import type { TuiConfig } from "@opencode-ai/tui/config" import type { TuiConfig } from "@opencode-ai/tui/config/v1"
export type RunFilePart = { export type RunFilePart = {
type: "file" type: "file"
+1 -1
View File
@@ -1,7 +1,7 @@
export * as TuiConfig from "./tui-config" export * as TuiConfig from "./tui-config"
import { Global } from "@opencode-ai/core/global" import { Global } from "@opencode-ai/core/global"
import { TuiConfig } from "@opencode-ai/tui/config" import { TuiConfig } from "@opencode-ai/tui/config/v1"
import { Effect, FileSystem, Option, Schema } from "effect" import { Effect, FileSystem, Option, Schema } from "effect"
import { parse, type ParseError } from "jsonc-parser" import { parse, type ParseError } from "jsonc-parser"
import path from "path" import path from "path"
+1 -1
View File
@@ -2,7 +2,7 @@
import { createDefaultOpenTuiKeymap } from "@opentui/keymap/opentui" import { createDefaultOpenTuiKeymap } from "@opentui/keymap/opentui"
import { testRender, useRenderer } from "@opentui/solid" import { testRender, useRenderer } from "@opentui/solid"
import { OpencodeKeymapProvider, registerOpencodeKeymap } from "@opencode-ai/tui/keymap" import { OpencodeKeymapProvider, registerOpencodeKeymap } from "@opencode-ai/tui/keymap"
import { resolve } from "@opencode-ai/tui/config" import { resolve } from "@opencode-ai/tui/config/v1"
import { expect, test } from "bun:test" import { expect, test } from "bun:test"
import { createComponent, createSignal } from "solid-js" import { createComponent, createSignal } from "solid-js"
import { RunFooterView } from "../src/mini/footer.view" import { RunFooterView } from "../src/mini/footer.view"
+1 -1
View File
@@ -2,7 +2,7 @@
import { Config } from "@/config/config" import { Config } from "@/config/config"
import { ConfigV1 } from "@opencode-ai/core/v1/config/config" import { ConfigV1 } from "@opencode-ai/core/v1/config/config"
import { TuiConfig } from "@opencode-ai/tui/config" import { TuiConfig } from "@opencode-ai/tui/config/v1"
import { Schema } from "effect" import { Schema } from "effect"
type JsonSchema = Record<string, unknown> type JsonSchema = Record<string, unknown>
@@ -1,4 +1,4 @@
import { TuiConfig } from "@opencode-ai/tui/config" import { TuiConfig } from "@opencode-ai/tui/config/v1"
import { isRecord } from "@opencode-ai/tui/util/record" import { isRecord } from "@opencode-ai/tui/util/record"
import { Filesystem } from "@/util/filesystem" import { Filesystem } from "@/util/filesystem"
import { Schema } from "effect" import { Schema } from "effect"
+1 -1
View File
@@ -2,7 +2,7 @@ import path from "path"
import { type ParseError as JsoncParseError, applyEdits, modify, parse as parseJsonc } from "jsonc-parser" import { type ParseError as JsoncParseError, applyEdits, modify, parse as parseJsonc } from "jsonc-parser"
import { unique } from "remeda" import { unique } from "remeda"
import { Option, Schema } from "effect" import { Option, Schema } from "effect"
import { TuiConfig } from "@opencode-ai/tui/config" import { TuiConfig } from "@opencode-ai/tui/config/v1"
import { Flag } from "@opencode-ai/core/flag/flag" import { Flag } from "@opencode-ai/core/flag/flag"
import { Global } from "@opencode-ai/core/global" import { Global } from "@opencode-ai/core/global"
import { Filesystem } from "@/util/filesystem" import { Filesystem } from "@/util/filesystem"
+2 -2
View File
@@ -15,14 +15,14 @@ import { Global } from "@opencode-ai/core/global"
import { FSUtil } from "@opencode-ai/core/fs-util" import { FSUtil } from "@opencode-ai/core/fs-util"
import { CurrentWorkingDirectory } from "./tui-cwd" import { CurrentWorkingDirectory } from "./tui-cwd"
import { ConfigPlugin } from "@/config/plugin" import { ConfigPlugin } from "@/config/plugin"
import { TuiKeybind } from "@opencode-ai/tui/config/keybind" import { TuiKeybind } from "@opencode-ai/tui/config/v1/keybind"
import { InstallationLocal, InstallationVersion } from "@opencode-ai/core/installation/version" import { InstallationLocal, InstallationVersion } from "@opencode-ai/core/installation/version"
import { makeRuntime } from "@opencode-ai/core/effect/runtime" import { makeRuntime } from "@opencode-ai/core/effect/runtime"
import { Filesystem } from "@/util/filesystem" import { Filesystem } from "@/util/filesystem"
import { ConfigVariable } from "@/config/variable" import { ConfigVariable } from "@/config/variable"
import { Npm } from "@opencode-ai/core/npm" import { Npm } from "@opencode-ai/core/npm"
import { FormatError, FormatUnknownError } from "@/cli/error" import { FormatError, FormatUnknownError } from "@/cli/error"
import { TuiConfig } from "@opencode-ai/tui/config" import { TuiConfig } from "@opencode-ai/tui/config/v1"
export const Info = TuiConfig.Info export const Info = TuiConfig.Info
export type Info = TuiConfig.Info export type Info = TuiConfig.Info
@@ -1,7 +1,7 @@
import { describe, expect, test } from "bun:test" import { describe, expect, test } from "bun:test"
import type { AudioPlayOptions, AudioSound } from "@opentui/core" import type { AudioPlayOptions, AudioSound } from "@opentui/core"
import { createTuiAttention } from "@opencode-ai/tui/attention" import { createTuiAttention } from "@opencode-ai/tui/attention"
import type { TuiConfig } from "@opencode-ai/tui/config" import type { TuiConfig } from "@opencode-ai/tui/config/v1"
type FocusEvent = "focus" | "blur" type FocusEvent = "focus" | "blur"
@@ -1,6 +1,6 @@
import { afterEach, describe, expect, mock, spyOn, test } from "bun:test" import { afterEach, describe, expect, mock, spyOn, test } from "bun:test"
import { OpenCode } from "@opencode-ai/client/promise" import { OpenCode } from "@opencode-ai/client/promise"
import type { Resolved } from "@opencode-ai/tui/config" import type { Resolved } from "@opencode-ai/tui/config/v1"
import { resolveDiffStyle, resolveModelInfo, resolveRunTuiConfig } from "@opencode-ai/cli/mini/runtime.boot" import { resolveDiffStyle, resolveModelInfo, resolveRunTuiConfig } from "@opencode-ai/cli/mini/runtime.boot"
import { createTuiResolvedConfig } from "../../fixture/tui-runtime" import { createTuiResolvedConfig } from "../../fixture/tui-runtime"
@@ -1,8 +1,8 @@
import { spyOn } from "bun:test" import { spyOn } from "bun:test"
import path from "path" import path from "path"
import { resolve, type Info, type Resolved } from "@opencode-ai/tui/config" import { resolve, type Info, type Resolved } from "@opencode-ai/tui/config/v1"
import { TuiConfig } from "../../src/config/tui" import { TuiConfig } from "../../src/config/tui"
import { TuiKeybind } from "@opencode-ai/tui/config/keybind" import { TuiKeybind } from "@opencode-ai/tui/config/v1/keybind"
type PluginSpec = string | [string, Record<string, unknown>] type PluginSpec = string | [string, Record<string, unknown>]
type PluginOrigin = { type PluginOrigin = {
+4 -2
View File
@@ -12,7 +12,10 @@
"exports": { "exports": {
".": "./src/index.tsx", ".": "./src/index.tsx",
"./builtins": "./src/feature-plugins/builtins.ts", "./builtins": "./src/feature-plugins/builtins.ts",
"./config": "./src/config/index.tsx", "./config/v1": "./src/config/v1/index.tsx",
"./config/v1/keybind": "./src/config/v1/keybind.ts",
"./config/v2": "./src/config/v2/index.ts",
"./config/v2/keybind": "./src/config/v2/keybind.ts",
"./context/args": "./src/context/args.tsx", "./context/args": "./src/context/args.tsx",
"./context/epilogue": "./src/context/epilogue.tsx", "./context/epilogue": "./src/context/epilogue.tsx",
"./context/exit": "./src/context/exit.tsx", "./context/exit": "./src/context/exit.tsx",
@@ -30,7 +33,6 @@
"./editor-zed": "./src/editor-zed.ts", "./editor-zed": "./src/editor-zed.ts",
"./runtime": "./src/runtime.tsx", "./runtime": "./src/runtime.tsx",
"./terminal-win32": "./src/terminal-win32.ts", "./terminal-win32": "./src/terminal-win32.ts",
"./config/keybind": "./src/config/keybind.ts",
"./keymap": "./src/keymap.tsx", "./keymap": "./src/keymap.tsx",
"./prompt/content": "./src/prompt/content.ts", "./prompt/content": "./src/prompt/content.ts",
"./prompt/display": "./src/prompt/display.ts", "./prompt/display": "./src/prompt/display.ts",
+1 -1
View File
@@ -68,7 +68,7 @@ import * as Model from "./util/model"
import { ArgsProvider, useArgs, type Args } from "./context/args" import { ArgsProvider, useArgs, type Args } from "./context/args"
import open from "open" import open from "open"
import { PromptRefProvider, usePromptRef } from "./context/prompt" import { PromptRefProvider, usePromptRef } from "./context/prompt"
import { TuiConfigProvider, useTuiConfig, type TuiConfig } from "./config" import { TuiConfigProvider, useTuiConfig, type TuiConfig } from "./config/v1"
import { createTuiApiAdapters } from "./plugin/adapters" import { createTuiApiAdapters } from "./plugin/adapters"
import { createTuiApi } from "./plugin/api" import { createTuiApi } from "./plugin/api"
import { createPluginRuntime, PluginRuntimeProvider, usePluginRuntime, type TuiPluginHost } from "./plugin/runtime" import { createPluginRuntime, PluginRuntimeProvider, usePluginRuntime, type TuiPluginHost } from "./plugin/runtime"
+1 -1
View File
@@ -10,7 +10,7 @@ import type {
TuiAttentionSoundPack, TuiAttentionSoundPack,
TuiAttentionSoundPackInfo, TuiAttentionSoundPackInfo,
} from "@opencode-ai/plugin/tui" } from "@opencode-ai/plugin/tui"
import { AttentionSoundName, type TuiConfig } from "./config" import { AttentionSoundName, type TuiConfig } from "./config/v1"
import { Schema } from "effect" import { Schema } from "effect"
import stripAnsi from "strip-ansi" import stripAnsi from "strip-ansi"
import * as TuiAudio from "./audio" import * as TuiAudio from "./audio"
@@ -8,7 +8,7 @@ import {
useKeymapSelector, useKeymapSelector,
useOpencodeKeymap, useOpencodeKeymap,
} from "../keymap" } from "../keymap"
import { useTuiConfig } from "../config" import { useTuiConfig } from "../config/v1"
type PaletteCommandEntry = ReturnType<OpenTuiKeymap["getCommandEntries"]>[number] type PaletteCommandEntry = ReturnType<OpenTuiKeymap["getCommandEntries"]>[number]
+1 -1
View File
@@ -9,7 +9,7 @@ import type { McpServer } from "@opencode-ai/client"
import { useClipboard } from "../context/clipboard" import { useClipboard } from "../context/clipboard"
import { useToast } from "../ui/toast" import { useToast } from "../ui/toast"
import { useKeyboard, useTerminalDimensions } from "@opentui/solid" import { useKeyboard, useTerminalDimensions } from "@opentui/solid"
import { useTuiConfig } from "../config" import { useTuiConfig } from "../config/v1"
import { getScrollAcceleration } from "../util/scroll" import { getScrollAcceleration } from "../util/scroll"
import { useBindings } from "../keymap" import { useBindings } from "../keymap"
@@ -1,7 +1,7 @@
import { InputRenderable, TextAttributes } from "@opentui/core" import { InputRenderable, TextAttributes } from "@opentui/core"
import { Slug } from "@opencode-ai/core/util/slug" import { Slug } from "@opencode-ai/core/util/slug"
import { createSignal, onMount } from "solid-js" import { createSignal, onMount } from "solid-js"
import { useTuiConfig } from "../config" import { useTuiConfig } from "../config/v1"
import { useTheme } from "../context/theme" import { useTheme } from "../context/theme"
import { useBindings, useCommandShortcut } from "../keymap" import { useBindings, useCommandShortcut } from "../keymap"
import { useDialog, type DialogContext } from "../ui/dialog" import { useDialog, type DialogContext } from "../ui/dialog"
@@ -5,7 +5,7 @@ import { createMemo, For } from "solid-js"
import { createStore } from "solid-js/store" import { createStore } from "solid-js/store"
import { FilePath } from "../ui/file-path" import { FilePath } from "../ui/file-path"
import { useTheme } from "../context/theme" import { useTheme } from "../context/theme"
import { useTuiConfig } from "../config" import { useTuiConfig } from "../config/v1"
import { useDialog, type DialogContext } from "../ui/dialog" import { useDialog, type DialogContext } from "../ui/dialog"
import { getScrollAcceleration } from "../util/scroll" import { getScrollAcceleration } from "../util/scroll"
@@ -11,7 +11,7 @@ import { useSDK } from "../../context/sdk"
import { useData } from "../../context/data" import { useData } from "../../context/data"
import { getScrollAcceleration } from "../../util/scroll" import { getScrollAcceleration } from "../../util/scroll"
import { useTuiPaths } from "../../context/runtime" import { useTuiPaths } from "../../context/runtime"
import { useTuiConfig } from "../../config" import { useTuiConfig } from "../../config/v1"
import { useLocation } from "../../context/location" import { useLocation } from "../../context/location"
import { useTheme, selectedForeground } from "../../context/theme" import { useTheme, selectedForeground } from "../../context/theme"
import { SplitBorder } from "../../ui/border" import { SplitBorder } from "../../ui/border"
+1 -1
View File
@@ -48,7 +48,7 @@ import { createFadeIn } from "../../util/signal"
import { DialogSkill } from "../dialog-skill" import { DialogSkill } from "../dialog-skill"
import { useArgs } from "../../context/args" import { useArgs } from "../../context/args"
import { OPENCODE_BASE_MODE, useBindings, useCommandShortcut, useLeaderActive, useOpencodeKeymap } from "../../keymap" import { OPENCODE_BASE_MODE, useBindings, useCommandShortcut, useLeaderActive, useOpencodeKeymap } from "../../keymap"
import { useTuiConfig } from "../../config" import { useTuiConfig } from "../../config/v1"
import { usePromptMove } from "./move" import { usePromptMove } from "./move"
import { readLocalAttachment } from "./local-attachment" import { readLocalAttachment } from "./local-attachment"
import { useData } from "../../context/data" import { useData } from "../../context/data"
@@ -97,8 +97,6 @@ export const Definitions = {
session_interrupt: keybind("escape", "Interrupt current session"), session_interrupt: keybind("escape", "Interrupt current session"),
session_background: keybind("ctrl+b", "Background blocking session tools"), session_background: keybind("ctrl+b", "Background blocking session tools"),
session_compact: keybind("<leader>c", "Compact the session"), session_compact: keybind("<leader>c", "Compact the session"),
session_toggle_timestamps: keybind("none", "Toggle message timestamps"),
session_toggle_generic_tool_output: keybind("none", "Toggle generic tool output"),
session_queued_prompts: keybind("<leader>q", "Manage queued prompts"), session_queued_prompts: keybind("<leader>q", "Manage queued prompts"),
session_child_first: keybind("down,<leader>down", "Toggle subagent picker"), session_child_first: keybind("down,<leader>down", "Toggle subagent picker"),
session_child_cycle: keybind("right", "Go to next child session"), session_child_cycle: keybind("right", "Go to next child session"),
@@ -146,7 +144,6 @@ export const Definitions = {
messages_undo: keybind("<leader>u", "Undo message"), messages_undo: keybind("<leader>u", "Undo message"),
messages_redo: keybind("<leader>r", "Redo message"), messages_redo: keybind("<leader>r", "Redo message"),
messages_toggle_conceal: keybind("<leader>h", "Toggle code block concealment in messages"), messages_toggle_conceal: keybind("<leader>h", "Toggle code block concealment in messages"),
tool_details: keybind("none", "Toggle tool details visibility"),
display_thinking: keybind("none", "Toggle thinking blocks visibility"), display_thinking: keybind("none", "Toggle thinking blocks visibility"),
prompt_submit: keybind("none", "Submit prompt"), prompt_submit: keybind("none", "Submit prompt"),
@@ -303,8 +300,6 @@ export const CommandMap = {
session_interrupt: "session.interrupt", session_interrupt: "session.interrupt",
session_background: "session.background", session_background: "session.background",
session_compact: "session.compact", session_compact: "session.compact",
session_toggle_timestamps: "session.toggle.timestamps",
session_toggle_generic_tool_output: "session.toggle.generic_tool_output",
session_queued_prompts: "session.queued_prompts", session_queued_prompts: "session.queued_prompts",
session_child_first: "session.child.first", session_child_first: "session.child.first",
session_child_cycle: "session.child.next", session_child_cycle: "session.child.next",
@@ -350,7 +345,6 @@ export const CommandMap = {
messages_undo: "session.undo", messages_undo: "session.undo",
messages_redo: "session.redo", messages_redo: "session.redo",
messages_toggle_conceal: "session.toggle.conceal", messages_toggle_conceal: "session.toggle.conceal",
tool_details: "session.toggle.actions",
display_thinking: "session.toggle.thinking", display_thinking: "session.toggle.thinking",
prompt_submit: "prompt.submit", prompt_submit: "prompt.submit",
prompt_editor_context_clear: "prompt.editor_context.clear", prompt_editor_context_clear: "prompt.editor_context.clear",
+97
View File
@@ -0,0 +1,97 @@
export * as TuiConfigV2 from "."
import { Schema } from "effect"
import { TuiKeybind } from "./keybind"
export const Plugin = Schema.Union([
Schema.String,
Schema.Struct({
package: Schema.String,
options: Schema.optional(Schema.Record(Schema.String, Schema.Any)),
}),
])
export const Info = Schema.Struct({
theme: Schema.optional(
Schema.Struct({
name: Schema.optional(Schema.String),
mode: Schema.optional(Schema.Literals(["system", "dark", "light"])),
}),
),
keybinds: Schema.optional(TuiKeybind.KeybindOverrides),
plugins: Schema.optional(Schema.Array(Plugin)),
leader: Schema.optional(
Schema.Struct({
timeout: Schema.optional(Schema.Int.check(Schema.isGreaterThan(0))),
}),
),
scroll: Schema.optional(
Schema.Struct({
speed: Schema.optional(Schema.Number.check(Schema.isGreaterThanOrEqualTo(0.001))),
acceleration: Schema.optional(Schema.Boolean),
}),
),
attention: Schema.optional(
Schema.Struct({
enabled: Schema.optional(Schema.Boolean),
notifications: Schema.optional(Schema.Boolean),
sound: Schema.optional(Schema.Boolean),
volume: Schema.optional(Schema.Number.check(Schema.isGreaterThanOrEqualTo(0), Schema.isLessThanOrEqualTo(1))),
sound_pack: Schema.optional(Schema.String),
sounds: Schema.optional(
Schema.Record(
Schema.Literals(["default", "question", "permission", "error", "done", "subagent_done"]),
Schema.optionalKey(Schema.String),
),
),
}),
),
diffs: Schema.optional(
Schema.Struct({
wrap: Schema.optional(Schema.Literals(["word", "none"])),
tree: Schema.optional(Schema.Boolean),
single: Schema.optional(Schema.Boolean),
view: Schema.optional(Schema.Literals(["auto", "split", "unified"])),
}),
),
terminal: Schema.optional(
Schema.Struct({
title: Schema.optional(Schema.Boolean),
}),
),
composer: Schema.optional(
Schema.Struct({
file_context: Schema.optional(Schema.Boolean),
paste_summary: Schema.optional(Schema.Boolean),
}),
),
session: Schema.optional(
Schema.Struct({
sidebar: Schema.optional(Schema.Literals(["auto", "hide"])),
scrollbar: Schema.optional(Schema.Boolean),
thinking: Schema.optional(Schema.Literals(["show", "hide"])),
group_exploration: Schema.optional(Schema.Boolean),
directory_filter: Schema.optional(Schema.Boolean),
}),
),
which_key: Schema.optional(
Schema.Struct({
layout: Schema.optional(Schema.Literals(["dock", "overlay"])),
pending_preview: Schema.optional(Schema.Boolean),
}),
),
hints: Schema.optional(
Schema.Struct({
tips: Schema.optional(Schema.Boolean),
getting_started: Schema.optional(Schema.Boolean),
}),
),
updates: Schema.optional(
Schema.Struct({
skipped: Schema.optional(Schema.String),
}),
),
animations: Schema.optional(Schema.Boolean),
mouse: Schema.optional(Schema.Boolean),
})
export type Info = Schema.Schema.Type<typeof Info>
+6
View File
@@ -0,0 +1,6 @@
export * as TuiKeybind from "./keybind"
import { Schema } from "effect"
export const KeybindOverrides = Schema.Struct({})
export type KeybindOverrides = Schema.Schema.Type<typeof KeybindOverrides>
+1 -1
View File
@@ -23,7 +23,7 @@ import { createEffect, createMemo, onCleanup, onMount } from "solid-js"
import { createStore, produce } from "solid-js/store" import { createStore, produce } from "solid-js/store"
import { createSimpleContext } from "./helper" import { createSimpleContext } from "./helper"
import { useKV } from "./kv" import { useKV } from "./kv"
import { useTuiConfig } from "../config" import { useTuiConfig } from "../config/v1"
import { Global } from "@opencode-ai/core/global" import { Global } from "@opencode-ai/core/global"
import { Glob } from "@opencode-ai/core/util/glob" import { Glob } from "@opencode-ai/core/util/glob"
import { readFile } from "node:fs/promises" import { readFile } from "node:fs/promises"
+2 -2
View File
@@ -14,8 +14,8 @@ import {
} from "@opentui/keymap/extras" } from "@opentui/keymap/extras"
import { KeymapProvider, useKeymap, useKeymapSelector, useBindings } from "@opentui/keymap/solid" import { KeymapProvider, useKeymap, useKeymapSelector, useBindings } from "@opentui/keymap/solid"
import { createMemo, type Accessor } from "solid-js" import { createMemo, type Accessor } from "solid-js"
import { useTuiConfig } from "./config" import { useTuiConfig } from "./config/v1"
import { TuiKeybind } from "./config/keybind" import { TuiKeybind } from "./config/v1/keybind"
export const LEADER_TOKEN = "leader" export const LEADER_TOKEN = "leader"
export const OPENCODE_BASE_MODE = "base" export const OPENCODE_BASE_MODE = "base"
+1 -1
View File
@@ -1,5 +1,5 @@
import type { TuiDialogSelectOption, TuiPluginApi, TuiSlotProps } from "@opencode-ai/plugin/tui" import type { TuiDialogSelectOption, TuiPluginApi, TuiSlotProps } from "@opencode-ai/plugin/tui"
import type { TuiConfig } from "../config" import type { TuiConfig } from "../config/v1"
import type { useEvent } from "../context/event" import type { useEvent } from "../context/event"
import type { useRoute } from "../context/route" import type { useRoute } from "../context/route"
import type { useSDK } from "../context/sdk" import type { useSDK } from "../context/sdk"
+1 -1
View File
@@ -1,6 +1,6 @@
// Legacy `api.command` bridge for v1 plugins; remove in v2. // Legacy `api.command` bridge for v1 plugins; remove in v2.
import type { TuiCommand, TuiPluginApi } from "@opencode-ai/plugin/tui" import type { TuiCommand, TuiPluginApi } from "@opencode-ai/plugin/tui"
import { TuiKeybind } from "../config/keybind" import { TuiKeybind } from "../config/v1/keybind"
import type { DialogContext } from "../ui/dialog" import type { DialogContext } from "../ui/dialog"
const COMMAND_PALETTE_SHOW = "command.palette.show" const COMMAND_PALETTE_SHOW = "command.palette.show"
+1 -1
View File
@@ -4,7 +4,7 @@ import type {
TuiPluginInstallResult, TuiPluginInstallResult,
TuiPluginStatus, TuiPluginStatus,
} from "@opencode-ai/plugin/tui" } from "@opencode-ai/plugin/tui"
import type { TuiConfig } from "../config" import type { TuiConfig } from "../config/v1"
import { createContext, createSignal, useContext, type JSX, type ParentProps } from "solid-js" import { createContext, createSignal, useContext, type JSX, type ParentProps } from "solid-js"
import { createPluginRoutes } from "./api" import { createPluginRoutes } from "./api"
import { createSlots, type HostSlots } from "./slots" import { createSlots, type HostSlots } from "./slots"
+1 -1
View File
@@ -9,7 +9,7 @@ import { useLocal } from "../context/local"
import { usePluginRuntime } from "../plugin/runtime" import { usePluginRuntime } from "../plugin/runtime"
import { useEditorContext } from "../context/editor" import { useEditorContext } from "../context/editor"
import { useTerminalDimensions } from "@opentui/solid" import { useTerminalDimensions } from "@opentui/solid"
import { useTuiConfig } from "../config" import { useTuiConfig } from "../config/v1"
import { HomeSessionDestinationProvider } from "./home/session-destination" import { HomeSessionDestinationProvider } from "./home/session-destination"
import { useData } from "../context/data" import { useData } from "../context/data"
import { LocationProvider } from "../context/location" import { LocationProvider } from "../context/location"
+1 -1
View File
@@ -10,7 +10,7 @@ import { useSDK } from "../../context/sdk"
import { useClipboard } from "../../context/clipboard" import { useClipboard } from "../../context/clipboard"
import { SplitBorder } from "../../ui/border" import { SplitBorder } from "../../ui/border"
import { useToast } from "../../ui/toast" import { useToast } from "../../ui/toast"
import { useTuiConfig } from "../../config" import { useTuiConfig } from "../../config/v1"
import { useBindings, useOpencodeModeStack } from "../../keymap" import { useBindings, useOpencodeModeStack } from "../../keymap"
const FORM_MODE = "form" const FORM_MODE = "form"
+48 -153
View File
@@ -64,7 +64,7 @@ import { FormPrompt } from "./form"
import { DialogExportOptions } from "../../ui/dialog-export-options" import { DialogExportOptions } from "../../ui/dialog-export-options"
import { DialogExportResult } from "../../ui/dialog-export-result" import { DialogExportResult } from "../../ui/dialog-export-result"
import { sessionEpilogue } from "../../util/presentation" import { sessionEpilogue } from "../../util/presentation"
import { useTuiConfig } from "../../config" import { useTuiConfig } from "../../config/v1"
import { useClipboard } from "../../context/clipboard" import { useClipboard } from "../../context/clipboard"
import { nextThinkingMode, reasoningSummary, useThinkingMode, type ThinkingMode } from "../../context/thinking" import { nextThinkingMode, reasoningSummary, useThinkingMode, type ThinkingMode } from "../../context/thinking"
import { getScrollAcceleration } from "../../util/scroll" import { getScrollAcceleration } from "../../util/scroll"
@@ -89,11 +89,8 @@ const sessionBindingCommands = [
"session.redo", "session.redo",
"session.sidebar.toggle", "session.sidebar.toggle",
"session.toggle.conceal", "session.toggle.conceal",
"session.toggle.timestamps",
"session.toggle.thinking", "session.toggle.thinking",
"session.toggle.actions",
"session.toggle.scrollbar", "session.toggle.scrollbar",
"session.toggle.generic_tool_output",
"session.toggle.exploration_grouping", "session.toggle.exploration_grouping",
"session.first", "session.first",
"session.last", "session.last",
@@ -127,9 +124,6 @@ const context = createContext<{
conceal: () => boolean conceal: () => boolean
thinkingMode: () => ThinkingMode thinkingMode: () => ThinkingMode
showThinking: () => boolean showThinking: () => boolean
showTimestamps: () => boolean
showDetails: () => boolean
showGenericToolOutput: () => boolean
groupExploration: () => boolean groupExploration: () => boolean
diffWrapMode: () => "word" | "none" diffWrapMode: () => "word" | "none"
models: () => ModelInfo[] models: () => ModelInfo[]
@@ -208,13 +202,9 @@ export function Session() {
const thinking = useThinkingMode() const thinking = useThinkingMode()
const thinkingMode = thinking.mode const thinkingMode = thinking.mode
const showThinking = createMemo(() => true) const showThinking = createMemo(() => true)
const [timestamps, setTimestamps] = kv.signal<"hide" | "show">("timestamps", "hide")
const [showDetails, setShowDetails] = kv.signal("tool_details_visibility", true)
const [showAssistantMetadata, _setShowAssistantMetadata] = kv.signal("assistant_metadata_visibility", true)
const [showScrollbar, setShowScrollbar] = kv.signal("scrollbar_visible", false) const [showScrollbar, setShowScrollbar] = kv.signal("scrollbar_visible", false)
const [diffWrapMode] = kv.signal<"word" | "none">("diff_wrap_mode", "word") const [diffWrapMode] = kv.signal<"word" | "none">("diff_wrap_mode", "word")
const [_animationsEnabled, _setAnimationsEnabled] = kv.signal("animations_enabled", true) const [_animationsEnabled, _setAnimationsEnabled] = kv.signal("animations_enabled", true)
const [showGenericToolOutput, setShowGenericToolOutput] = kv.signal("generic_tool_output_visibility", false)
const [groupExploration, setGroupExploration] = kv.signal("exploration_grouping", true) const [groupExploration, setGroupExploration] = kv.signal("exploration_grouping", true)
const wide = createMemo(() => dimensions().width > 120) const wide = createMemo(() => dimensions().width > 120)
@@ -224,7 +214,6 @@ export function Session() {
if (sidebar() === "auto" && wide()) return true if (sidebar() === "auto" && wide()) return true
return false return false
}) })
const showTimestamps = createMemo(() => timestamps() === "show")
const contentWidth = createMemo(() => dimensions().width - (sidebarVisible() ? 42 : 0) - 4) const contentWidth = createMemo(() => dimensions().width - (sidebarVisible() ? 42 : 0) - 4)
const models = createMemo(() => data.location.model.list(location()) ?? []) const models = createMemo(() => data.location.model.list(location()) ?? [])
@@ -491,19 +480,6 @@ export function Session() {
dialog.clear() dialog.clear()
}, },
}, },
{
title: showTimestamps() ? "Hide timestamps" : "Show timestamps",
value: "session.toggle.timestamps",
category: "Session",
slash: {
name: "timestamps",
aliases: ["toggle-timestamps"],
},
run: () => {
setTimestamps((prev) => (prev === "show" ? "hide" : "show"))
dialog.clear()
},
},
{ {
title: (() => { title: (() => {
const next = nextThinkingMode(thinkingMode()) const next = nextThinkingMode(thinkingMode())
@@ -521,15 +497,6 @@ export function Session() {
dialog.clear() dialog.clear()
}, },
}, },
{
title: showDetails() ? "Hide tool details" : "Show tool details",
value: "session.toggle.actions",
category: "Session",
run: () => {
setShowDetails((prev) => !prev)
dialog.clear()
},
},
{ {
title: "Toggle session scrollbar", title: "Toggle session scrollbar",
value: "session.toggle.scrollbar", value: "session.toggle.scrollbar",
@@ -539,15 +506,6 @@ export function Session() {
dialog.clear() dialog.clear()
}, },
}, },
{
title: showGenericToolOutput() ? "Hide generic tool output" : "Show generic tool output",
value: "session.toggle.generic_tool_output",
category: "Session",
run: () => {
setShowGenericToolOutput((prev) => !prev)
dialog.clear()
},
},
{ {
title: groupExploration() ? "Show exploration tools individually" : "Group exploration tools", title: groupExploration() ? "Show exploration tools individually" : "Group exploration tools",
value: "session.toggle.exploration_grouping", value: "session.toggle.exploration_grouping",
@@ -727,13 +685,7 @@ export function Session() {
try { try {
const sessionData = session() const sessionData = session()
if (!sessionData) return if (!sessionData) return
const transcript = formatSessionTranscript( const transcript = formatSessionTranscript(sessionData, messages(), showThinking())
sessionData,
messages(),
showThinking(),
showDetails(),
showAssistantMetadata(),
)
await clipboard.write?.(transcript) await clipboard.write?.(transcript)
toast.show({ message: "Session transcript copied to clipboard!", variant: "success" }) toast.show({ message: "Session transcript copied to clipboard!", variant: "success" })
} catch { } catch {
@@ -754,19 +706,13 @@ export function Session() {
const sessionData = session() const sessionData = session()
if (!sessionData) return if (!sessionData) return
const options = await DialogExportOptions.show(dialog, showThinking(), showDetails(), showAssistantMetadata()) const options = await DialogExportOptions.show(dialog, showThinking())
if (options === null) return if (options === null) return
const content = const content =
options.format === "markdown" options.format === "markdown"
? formatSessionTranscript( ? formatSessionTranscript(sessionData, messages(), options.thinking)
sessionData,
messages(),
options.thinking,
options.toolDetails,
options.assistantMetadata,
)
: await (async () => { : await (async () => {
if (options.debug) { if (options.debug) {
const events: { readonly created: number }[] = [] const events: { readonly created: number }[] = []
@@ -922,9 +868,6 @@ export function Session() {
conceal, conceal,
thinkingMode, thinkingMode,
showThinking, showThinking,
showTimestamps,
showDetails,
showGenericToolOutput,
groupExploration, groupExploration,
diffWrapMode, diffWrapMode,
models, models,
@@ -1534,13 +1477,7 @@ function UserMessage(props: { message: SessionMessageUser }) {
> >
<text fg={theme.text}>{props.message.text}</text> <text fg={theme.text}>{props.message.text}</text>
<Show when={files().length}> <Show when={files().length}>
<box <box flexDirection="row" paddingTop={1} gap={1} flexWrap="wrap">
flexDirection="row"
paddingBottom={ctx.showTimestamps() ? 1 : 0}
paddingTop={1}
gap={1}
flexWrap="wrap"
>
<For each={files()}> <For each={files()}>
{(file) => { {(file) => {
const label = file.mime === "application/x-directory" ? "dir" : "file" const label = file.mime === "application/x-directory" ? "dir" : "file"
@@ -1557,11 +1494,6 @@ function UserMessage(props: { message: SessionMessageUser }) {
</For> </For>
</box> </box>
</Show> </Show>
<Show when={ctx.showTimestamps()}>
<text fg={theme.textMuted}>
<span style={{ fg: theme.textMuted }}>{Locale.todayTimeOrDateTime(props.message.time.created)}</span>
</text>
</Show>
</box> </box>
</box> </box>
</Show> </Show>
@@ -1865,31 +1797,7 @@ function TextPart(props: { last: boolean; part: SessionMessageAssistantText }) {
// Pending messages moved to individual tool pending functions // Pending messages moved to individual tool pending functions
function ToolPart(props: { part: SessionMessageAssistantTool }) { function ToolPart(props: { part: SessionMessageAssistantTool }) {
const ctx = use()
const data = useData()
const display = createMemo(() => toolDisplay(props.part.name)) const display = createMemo(() => toolDisplay(props.part.name))
const activeBackgroundWork = createMemo(() => {
if (props.part.state.status === "streaming") return false
if (display() === "shell") {
const shellID = stringValue(props.part.state.structured.shellID)
return Boolean(shellID && data.shell.get(shellID))
}
if (display() === "subagent") {
const sessionID =
stringValue(props.part.state.structured.sessionID) ?? stringValue(props.part.state.structured.sessionId)
return Boolean(sessionID && data.session.status(sessionID) === "running")
}
return false
})
// Hide tool if showDetails is false and tool completed successfully
const shouldHide = createMemo(() => {
if (ctx.showDetails()) return false
if (activeBackgroundWork()) return false
if (props.part.state.status !== "completed") return false
if (display() === "shell") return false
return true
})
const toolprops = { const toolprops = {
get metadata() { get metadata() {
@@ -1913,7 +1821,6 @@ function ToolPart(props: { part: SessionMessageAssistantTool }) {
} }
return ( return (
<Show when={!shouldHide()}>
<Switch> <Switch>
<Match when={display() === "shell"}> <Match when={display() === "shell"}>
<Shell {...toolprops} /> <Shell {...toolprops} />
@@ -1958,7 +1865,6 @@ function ToolPart(props: { part: SessionMessageAssistantTool }) {
<GenericTool {...toolprops} /> <GenericTool {...toolprops} />
</Match> </Match>
</Switch> </Switch>
</Show>
) )
} }
@@ -1970,40 +1876,55 @@ type ToolProps = {
part: SessionMessageAssistantTool part: SessionMessageAssistantTool
} }
function GenericTool(props: ToolProps) { function GenericTool(props: ToolProps) {
const { theme } = useTheme() const { theme, syntax } = useTheme()
const ctx = use()
const output = createMemo(() => props.output?.trim() ?? "") const output = createMemo(() => props.output?.trim() ?? "")
const args = createMemo(() => JSON.stringify(props.input, null, 2))
const [expanded, setExpanded] = createSignal(false) const [expanded, setExpanded] = createSignal(false)
const maxLines = 3 const expandable = createMemo(() => Object.keys(props.input).length > 0 || output().length > 0)
const maxChars = createMemo(() => maxLines * Math.max(20, ctx.width - 6))
const collapsed = createMemo(() => collapseToolOutput(output(), maxLines, maxChars()))
const limited = createMemo(() => {
if (expanded() || !collapsed().overflow) return output()
return collapsed().output
})
return ( return (
<Show
when={props.output && ctx.showGenericToolOutput()}
fallback={
<InlineTool icon="⚙" pending="Writing command..." complete={true} part={props.part}>
{props.tool} {input(props.input)}
</InlineTool>
}
>
<BlockTool <BlockTool
title={`# ${props.tool} ${input(props.input)}`} title={` ${props.tool}`}
part={props.part} part={props.part}
onClick={collapsed().overflow ? () => setExpanded((prev) => !prev) : undefined} spinner={props.part.state.status === "streaming" || props.part.state.status === "running"}
onClick={expandable() ? () => setExpanded((value) => !value) : undefined}
> >
<Show when={expanded()}>
<box gap={1} paddingTop={1}>
<Show when={Object.keys(props.input).length > 0}>
<box gap={1}> <box gap={1}>
<text fg={theme.text}>{limited()}</text> <text>
<Show when={collapsed().overflow}> <span style={{ bg: theme.backgroundElement, fg: theme.textMuted }}> Input </span>
<text fg={theme.textMuted}>{expanded() ? "Click to collapse" : "Click to expand"}</text> </text>
<box paddingLeft={1}>
<code
content={args()}
filetype="json"
syntaxStyle={syntax()}
conceal={false}
drawUnstyledText={false}
fg={theme.text}
/>
</box>
</box>
</Show>
<Show when={output()}>
{(value) => (
<box gap={1}>
<text>
<span style={{ bg: theme.backgroundElement, fg: theme.textMuted }}> Output </span>
</text>
<box paddingLeft={1}>
<text fg={theme.text} wrapMode="word">
{value()}
</text>
</box>
</box>
)}
</Show> </Show>
</box> </box>
</BlockTool>
</Show> </Show>
</BlockTool>
) )
} }
@@ -2106,12 +2027,7 @@ export function InlineToolRow(props: {
<Spinner color={props.color} children={props.children} /> <Spinner color={props.color} children={props.children} />
</Match> </Match>
<Match when={true}> <Match when={true}>
<Show <Show fallback={<Spinner color={props.color}>{props.pending}</Spinner>} when={props.complete || props.failed}>
fallback={
<Spinner color={props.color}>{props.pending}</Spinner>
}
when={props.complete || props.failed}
>
<box flexDirection="row"> <box flexDirection="row">
<text <text
width={INLINE_TOOL_ICON_WIDTH} width={INLINE_TOOL_ICON_WIDTH}
@@ -2183,11 +2099,7 @@ function BlockTool(props: {
{(title) => ( {(title) => (
<Show <Show
when={props.spinner} when={props.spinner}
fallback={ fallback={<text fg={permission() ? theme.warning : theme.textMuted}>{title()}</text>}
<text fg={permission() ? theme.warning : theme.textMuted}>
{title()}
</text>
}
> >
<Spinner color={permission() ? theme.warning : theme.textMuted}>{title().replace(/^# /, "")}</Spinner> <Spinner color={permission() ? theme.warning : theme.textMuted}>{title().replace(/^# /, "")}</Spinner>
</Show> </Show>
@@ -2676,11 +2588,7 @@ function ApplyPatch(props: ToolProps) {
: undefined : undefined
} }
title={ title={
targets().length === 1 targets().length === 1 ? undefined : props.part.state.status === "error" ? "# Patch failed" : "Patching"
? undefined
: props.part.state.status === "error"
? "# Patch failed"
: "Patching"
} }
part={props.part} part={props.part}
spinner={props.part.state.status === "streaming" || props.part.state.status === "running"} spinner={props.part.state.status === "streaming" || props.part.state.status === "running"}
@@ -2806,13 +2714,7 @@ function recordValue(value: unknown): Record<string, unknown> | undefined {
return value as Record<string, unknown> return value as Record<string, unknown>
} }
function formatSessionTranscript( function formatSessionTranscript(session: SessionInfo, messages: SessionMessageInfo[], thinking: boolean) {
session: SessionInfo,
messages: SessionMessageInfo[],
thinking: boolean,
toolDetails: boolean,
assistantMetadata: boolean,
) {
const body = messages.flatMap((message) => { const body = messages.flatMap((message) => {
if (message.type === "user") return [`## User\n\n${message.text}`] if (message.type === "user") return [`## User\n\n${message.text}`]
if (message.type === "shell") if (message.type === "shell")
@@ -2821,7 +2723,6 @@ function formatSessionTranscript(
const content = message.content.flatMap((item) => { const content = message.content.flatMap((item) => {
if (item.type === "text") return [item.text] if (item.type === "text") return [item.text]
if (item.type === "reasoning") return thinking ? [`_Thinking:_\n\n${item.text}`] : [] if (item.type === "reasoning") return thinking ? [`_Thinking:_\n\n${item.text}`] : []
if (!toolDetails) return [`**Tool: ${item.name}**`]
const input = typeof item.state.input === "string" ? item.state.input : JSON.stringify(item.state.input, null, 2) const input = typeof item.state.input === "string" ? item.state.input : JSON.stringify(item.state.input, null, 2)
const output = const output =
item.state.status === "error" item.state.status === "error"
@@ -2833,13 +2734,7 @@ function formatSessionTranscript(
.join("\n") .join("\n")
return [`**Tool: ${item.name}**\n\n**Input:**\n\`\`\`json\n${input}\n\`\`\`\n\n${output}`] return [`**Tool: ${item.name}**\n\n**Input:**\n\`\`\`json\n${input}\n\`\`\`\n\n${output}`]
}) })
const duration = message.time.completed return [`## Assistant\n\n${content.join("\n\n")}`]
? ` · ${((message.time.completed - message.time.created) / 1000).toFixed(1)}s`
: ""
const heading = assistantMetadata
? `## Assistant (${message.agent} · ${message.model.providerID}/${message.model.id}${duration})`
: "## Assistant"
return [`${heading}\n\n${content.join("\n\n")}`]
}) })
return `# ${session.title}\n\n**Session ID:** ${session.id}\n**Created:** ${new Date(session.time.created).toLocaleString()}\n**Updated:** ${new Date(session.time.updated).toLocaleString()}\n\n---\n\n${body.join("\n\n---\n\n")}\n` return `# ${session.title}\n\n**Session ID:** ${session.id}\n**Created:** ${new Date(session.time.created).toLocaleString()}\n**Updated:** ${new Date(session.time.updated).toLocaleString()}\n\n---\n\n${body.join("\n\n---\n\n")}\n`
} }
@@ -12,7 +12,7 @@ import { filetype } from "../../util/filetype"
import { Locale } from "../../util/locale" import { Locale } from "../../util/locale"
import { webSearchProviderLabel } from "../../util/tool-display" import { webSearchProviderLabel } from "../../util/tool-display"
import { getScrollAcceleration } from "../../util/scroll" import { getScrollAcceleration } from "../../util/scroll"
import { useTuiConfig } from "../../config" import { useTuiConfig } from "../../config/v1"
import { OPENCODE_BASE_MODE, useBindings, useCommandShortcut } from "../../keymap" import { OPENCODE_BASE_MODE, useBindings, useCommandShortcut } from "../../keymap"
import { usePathFormatter } from "../../context/path-format" import { usePathFormatter } from "../../context/path-format"
+1 -1
View File
@@ -1,7 +1,7 @@
import { useData } from "../../context/data" import { useData } from "../../context/data"
import { createMemo, Show } from "solid-js" import { createMemo, Show } from "solid-js"
import { useTheme } from "../../context/theme" import { useTheme } from "../../context/theme"
import { useTuiConfig } from "../../config" import { useTuiConfig } from "../../config/v1"
import { InstallationVersion } from "@opencode-ai/core/installation/version" import { InstallationVersion } from "@opencode-ai/core/installation/version"
import { usePluginRuntime } from "../../plugin/runtime" import { usePluginRuntime } from "../../plugin/runtime"
+13 -58
View File
@@ -9,20 +9,11 @@ export type ExportFormat = "markdown" | "json"
export type DialogExportOptionsProps = { export type DialogExportOptionsProps = {
defaultThinking: boolean defaultThinking: boolean
defaultToolDetails: boolean onConfirm?: (options: { action: "copy" | "export"; format: ExportFormat; debug: boolean; thinking: boolean }) => void
defaultAssistantMetadata: boolean
onConfirm?: (options: {
action: "copy" | "export"
format: ExportFormat
debug: boolean
thinking: boolean
toolDetails: boolean
assistantMetadata: boolean
}) => void
onCancel?: () => void onCancel?: () => void
} }
type Active = ExportFormat | "debug" | "thinking" | "toolDetails" | "assistantMetadata" | "copy" | "export" type Active = ExportFormat | "debug" | "thinking" | "copy" | "export"
export function DialogExportOptions(props: DialogExportOptionsProps) { export function DialogExportOptions(props: DialogExportOptionsProps) {
const dialog = useDialog() const dialog = useDialog()
@@ -31,8 +22,6 @@ export function DialogExportOptions(props: DialogExportOptionsProps) {
format: "markdown" as ExportFormat, format: "markdown" as ExportFormat,
debug: false, debug: false,
thinking: props.defaultThinking, thinking: props.defaultThinking,
toolDetails: props.defaultToolDetails,
assistantMetadata: props.defaultAssistantMetadata,
active: "markdown" as Active, active: "markdown" as Active,
}) })
@@ -42,8 +31,6 @@ export function DialogExportOptions(props: DialogExportOptionsProps) {
format: store.format, format: store.format,
debug: store.debug, debug: store.debug,
thinking: store.thinking, thinking: store.thinking,
toolDetails: store.toolDetails,
assistantMetadata: store.assistantMetadata,
}) })
const activate = () => { const activate = () => {
@@ -53,8 +40,6 @@ export function DialogExportOptions(props: DialogExportOptionsProps) {
} }
if (store.active === "debug") setStore("debug", !store.debug) if (store.active === "debug") setStore("debug", !store.debug)
if (store.active === "thinking") setStore("thinking", !store.thinking) if (store.active === "thinking") setStore("thinking", !store.thinking)
if (store.active === "toolDetails") setStore("toolDetails", !store.toolDetails)
if (store.active === "assistantMetadata") setStore("assistantMetadata", !store.assistantMetadata)
if (store.active === "copy" || store.active === "export") confirm(store.active) if (store.active === "copy" || store.active === "export") confirm(store.active)
} }
@@ -67,7 +52,7 @@ export function DialogExportOptions(props: DialogExportOptionsProps) {
cmd: () => { cmd: () => {
const order: Active[] = const order: Active[] =
store.format === "markdown" store.format === "markdown"
? ["markdown", "json", "thinking", "toolDetails", "assistantMetadata", "copy", "export"] ? ["markdown", "json", "thinking", "copy", "export"]
: ["markdown", "json", "debug", "copy", "export"] : ["markdown", "json", "debug", "copy", "export"]
setStore("active", order[(order.indexOf(store.active) + 1) % order.length]) setStore("active", order[(order.indexOf(store.active) + 1) % order.length])
}, },
@@ -86,11 +71,6 @@ export function DialogExportOptions(props: DialogExportOptionsProps) {
setStore("active", format) setStore("active", format)
} }
const toggle = (option: "thinking" | "toolDetails" | "assistantMetadata") => {
setStore("active", option)
setStore(option, !store[option])
}
return ( return (
<box paddingLeft={2} paddingRight={2} gap={1}> <box paddingLeft={2} paddingRight={2} gap={1}>
<box flexDirection="row" justifyContent="space-between"> <box flexDirection="row" justifyContent="space-between">
@@ -121,30 +101,19 @@ export function DialogExportOptions(props: DialogExportOptionsProps) {
</box> </box>
</box> </box>
<Show when={store.format === "markdown"}> <Show when={store.format === "markdown"}>
<box flexDirection="column">
<For
each={
[
["thinking", "Include thinking"],
["toolDetails", "Include tool details"],
["assistantMetadata", "Include assistant metadata"],
] as const
}
>
{(item) => (
<box <box
flexDirection="row" flexDirection="row"
gap={1} gap={1}
backgroundColor={store.active === item[0] ? theme.backgroundElement : undefined} backgroundColor={store.active === "thinking" ? theme.backgroundElement : undefined}
onMouseUp={() => toggle(item[0])} onMouseUp={() => {
setStore("active", "thinking")
setStore("thinking", !store.thinking)
}}
> >
<text fg={store.active === item[0] ? theme.primary : theme.textMuted}> <text fg={store.active === "thinking" ? theme.primary : theme.textMuted}>
{store[item[0]] ? "[x]" : "[ ]"} {store.thinking ? "[x]" : "[ ]"}
</text> </text>
<text fg={store.active === item[0] ? theme.primary : theme.text}>{item[1]}</text> <text fg={store.active === "thinking" ? theme.primary : theme.text}>Include thinking</text>
</box>
)}
</For>
</box> </box>
</Show> </Show>
<Show when={store.format === "json"}> <Show when={store.format === "json"}>
@@ -170,12 +139,7 @@ export function DialogExportOptions(props: DialogExportOptionsProps) {
> >
<text fg={theme.text}>Copy</text> <text fg={theme.text}>Copy</text>
</box> </box>
<box <box paddingLeft={4} paddingRight={4} backgroundColor={theme.primary} onMouseUp={() => confirm("export")}>
paddingLeft={4}
paddingRight={4}
backgroundColor={theme.primary}
onMouseUp={() => confirm("export")}
>
<text fg={theme.selectedListItemText}>Export</text> <text fg={theme.selectedListItemText}>Export</text>
</box> </box>
</box> </box>
@@ -183,26 +147,17 @@ export function DialogExportOptions(props: DialogExportOptionsProps) {
) )
} }
DialogExportOptions.show = ( DialogExportOptions.show = (dialog: DialogContext, defaultThinking: boolean) => {
dialog: DialogContext,
defaultThinking: boolean,
defaultToolDetails: boolean,
defaultAssistantMetadata: boolean,
) => {
return new Promise<{ return new Promise<{
action: "copy" | "export" action: "copy" | "export"
format: ExportFormat format: ExportFormat
debug: boolean debug: boolean
thinking: boolean thinking: boolean
toolDetails: boolean
assistantMetadata: boolean
} | null>((resolve) => { } | null>((resolve) => {
dialog.replace( dialog.replace(
() => ( () => (
<DialogExportOptions <DialogExportOptions
defaultThinking={defaultThinking} defaultThinking={defaultThinking}
defaultToolDetails={defaultToolDetails}
defaultAssistantMetadata={defaultAssistantMetadata}
onConfirm={(options) => resolve(options)} onConfirm={(options) => resolve(options)}
onCancel={() => resolve(null)} onCancel={() => resolve(null)}
/> />
+1 -1
View File
@@ -3,7 +3,7 @@ import { useTheme } from "../context/theme"
import { useDialog, type DialogContext } from "./dialog" import { useDialog, type DialogContext } from "./dialog"
import { Show, createEffect, createSignal, onMount, type JSX } from "solid-js" import { Show, createEffect, createSignal, onMount, type JSX } from "solid-js"
import { Spinner } from "../component/spinner" import { Spinner } from "../component/spinner"
import { useTuiConfig } from "../config" import { useTuiConfig } from "../config/v1"
import { useBindings, useCommandShortcut } from "../keymap" import { useBindings, useCommandShortcut } from "../keymap"
export type DialogPromptProps = { export type DialogPromptProps = {
+1 -1
View File
@@ -17,7 +17,7 @@ import { isDeepEqual } from "remeda"
import { useDialog, type DialogContext } from "./dialog" import { useDialog, type DialogContext } from "./dialog"
import { Locale } from "../util/locale" import { Locale } from "../util/locale"
import { getScrollAcceleration } from "../util/scroll" import { getScrollAcceleration } from "../util/scroll"
import { useTuiConfig } from "../config" import { useTuiConfig } from "../config/v1"
import { formatKeyBindings, useBindings, useKeymapSelector } from "../keymap" import { formatKeyBindings, useBindings, useKeymapSelector } from "../keymap"
export interface DialogSelectProps<T> { export interface DialogSelectProps<T> {
@@ -8,7 +8,7 @@ import path from "node:path"
import { onCleanup } from "solid-js" import { onCleanup } from "solid-js"
import { tmpdir } from "../../fixture/fixture" import { tmpdir } from "../../fixture/fixture"
import { createTuiResolvedConfig } from "../../fixture/tui-runtime" import { createTuiResolvedConfig } from "../../fixture/tui-runtime"
import type { TuiKeybind } from "../../../src/config/keybind" import type { TuiKeybind } from "../../../src/config/v1/keybind"
import { TestTuiContexts } from "../../fixture/tui-environment" import { TestTuiContexts } from "../../fixture/tui-environment"
async function wait(fn: () => boolean, timeout = 2000) { async function wait(fn: () => boolean, timeout = 2000) {
@@ -41,7 +41,7 @@ async function mountPrompt(input: {
import("../../../src/ui/dialog-prompt"), import("../../../src/ui/dialog-prompt"),
import("../../../src/context/kv"), import("../../../src/context/kv"),
import("../../../src/context/theme"), import("../../../src/context/theme"),
import("../../../src/config"), import("../../../src/config/v1"),
import("../../../src/ui/toast"), import("../../../src/ui/toast"),
import("../../../src/keymap"), import("../../../src/keymap"),
]) ])
@@ -6,7 +6,7 @@ import type { JSX } from "solid-js"
import { createTuiResolvedConfig } from "../../fixture/tui-runtime" import { createTuiResolvedConfig } from "../../fixture/tui-runtime"
import { KVProvider } from "../../../src/context/kv" import { KVProvider } from "../../../src/context/kv"
import { ThemeProvider } from "../../../src/context/theme" import { ThemeProvider } from "../../../src/context/theme"
import { TuiConfigProvider } from "../../../src/config" import { TuiConfigProvider } from "../../../src/config/v1"
import { DiffViewerFileTree } from "../../../src/feature-plugins/system/diff-viewer-file-tree" import { DiffViewerFileTree } from "../../../src/feature-plugins/system/diff-viewer-file-tree"
import { TestTuiContexts } from "../../fixture/tui-environment" import { TestTuiContexts } from "../../fixture/tui-environment"
import { import {
@@ -7,9 +7,9 @@ import type { TuiPluginApi, TuiPluginMeta, TuiRouteCurrent, TuiRouteDefinition }
import type { Session } from "@opencode-ai/sdk/v2" import type { Session } from "@opencode-ai/sdk/v2"
import { KVProvider } from "../../../src/context/kv" import { KVProvider } from "../../../src/context/kv"
import { ThemeProvider } from "../../../src/context/theme" import { ThemeProvider } from "../../../src/context/theme"
import { TuiConfigProvider } from "../../../src/config" import { TuiConfigProvider } from "../../../src/config/v1"
import { SDKProvider } from "../../../src/context/sdk" import { SDKProvider } from "../../../src/context/sdk"
import { TuiKeybind } from "../../../src/config/keybind" import { TuiKeybind } from "../../../src/config/v1/keybind"
import { OpencodeKeymapProvider } from "../../../src/keymap" import { OpencodeKeymapProvider } from "../../../src/keymap"
import diffViewerPlugin from "../../../src/feature-plugins/system/diff-viewer" import diffViewerPlugin from "../../../src/feature-plugins/system/diff-viewer"
import { createTuiPluginApi } from "../../fixture/tui-plugin" import { createTuiPluginApi } from "../../fixture/tui-plugin"
+1 -1
View File
@@ -10,7 +10,7 @@ import type { FormWithLocation } from "../../../src/context/data"
import { KVProvider } from "../../../src/context/kv" import { KVProvider } from "../../../src/context/kv"
import { SDKProvider } from "../../../src/context/sdk" import { SDKProvider } from "../../../src/context/sdk"
import { ThemeProvider } from "../../../src/context/theme" import { ThemeProvider } from "../../../src/context/theme"
import { TuiConfigProvider } from "../../../src/config" import { TuiConfigProvider } from "../../../src/config/v1"
import { OpencodeKeymapProvider, registerOpencodeKeymap } from "../../../src/keymap" import { OpencodeKeymapProvider, registerOpencodeKeymap } from "../../../src/keymap"
import { ToastProvider } from "../../../src/ui/toast" import { ToastProvider } from "../../../src/ui/toast"
import { tmpdir } from "../../fixture/fixture" import { tmpdir } from "../../fixture/fixture"
+1 -1
View File
@@ -11,7 +11,7 @@ import {
TuiConfigProvider, TuiConfigProvider,
type Info as TuiConfigInfo, type Info as TuiConfigInfo,
useTuiConfig, useTuiConfig,
} from "../src/config" } from "../src/config/v1"
const decodeInfo = Schema.decodeUnknownSync(Info) const decodeInfo = Schema.decodeUnknownSync(Info)
const decodePlugin = Schema.decodeUnknownSync(PluginSpec) const decodePlugin = Schema.decodeUnknownSync(PluginSpec)
+2 -2
View File
@@ -1,5 +1,5 @@
import { resolve, type Info, type Resolved } from "../../src/config" import { resolve, type Info, type Resolved } from "../../src/config/v1"
import { TuiKeybind } from "../../src/config/keybind" import { TuiKeybind } from "../../src/config/v1/keybind"
type ResolvedInput = Omit<Info, "attention" | "keybinds" | "leader_timeout"> & { type ResolvedInput = Omit<Info, "attention" | "keybinds" | "leader_timeout"> & {
attention?: Partial<Resolved["attention"]> attention?: Partial<Resolved["attention"]>
+1 -1
View File
@@ -4,7 +4,7 @@ import { createBindingLookup } from "@opentui/keymap/extras"
import { testRender, useRenderer } from "@opentui/solid" import { testRender, useRenderer } from "@opentui/solid"
import { expect, test } from "bun:test" import { expect, test } from "bun:test"
import { onCleanup } from "solid-js" import { onCleanup } from "solid-js"
import { TuiKeybind } from "../src/config/keybind" import { TuiKeybind } from "../src/config/v1/keybind"
import { import {
formatKeySequence, formatKeySequence,
getOpencodeModeStack, getOpencodeModeStack,