refactor(tui): consolidate settings in cli config
This commit is contained in:
@@ -35,9 +35,23 @@ test("migrates tui and kv config into cli.json", async () => {
|
|||||||
path.join(directory, "kv.json"),
|
path.join(directory, "kv.json"),
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
theme_mode_lock: "light",
|
theme_mode_lock: "light",
|
||||||
|
attention_sound_pack: "custom.pack",
|
||||||
|
diff_wrap_mode: "none",
|
||||||
|
diff_viewer_show_file_tree: false,
|
||||||
|
diff_viewer_single_patch: true,
|
||||||
|
diff_viewer_view: "split",
|
||||||
|
terminal_title_enabled: false,
|
||||||
|
file_context_enabled: false,
|
||||||
paste_summary_enabled: false,
|
paste_summary_enabled: false,
|
||||||
|
sidebar: "hide",
|
||||||
|
scrollbar_visible: true,
|
||||||
|
thinking_mode: "show",
|
||||||
exploration_grouping: false,
|
exploration_grouping: false,
|
||||||
tips_hidden: true,
|
tips_hidden: true,
|
||||||
|
dismissed_getting_started: true,
|
||||||
|
animations_enabled: false,
|
||||||
|
skipped_version: "9.9.9",
|
||||||
|
which_key_layout: "overlay",
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -56,12 +70,17 @@ test("migrates tui and kv config into cli.json", async () => {
|
|||||||
plugins: [{ package: "example", options: { mode: "safe" } }, "-disabled"],
|
plugins: [{ package: "example", options: { mode: "safe" } }, "-disabled"],
|
||||||
leader: { timeout: 500 },
|
leader: { timeout: 500 },
|
||||||
scroll: { speed: 2, acceleration: true },
|
scroll: { speed: 2, acceleration: true },
|
||||||
diffs: { view: "unified" },
|
attention: { sound_pack: "custom.pack" },
|
||||||
prompt: { paste: "full" },
|
diffs: { wrap: "none", tree: false, single: true, view: "split" },
|
||||||
session: { grouping: "none" },
|
terminal: { title: false },
|
||||||
hints: { tips: false },
|
prompt: { editor: false, paste: "full" },
|
||||||
|
session: { sidebar: "hide", scrollbar: true, thinking: "show", grouping: "none" },
|
||||||
|
hints: { tips: false, onboarding: false },
|
||||||
|
animations: false,
|
||||||
mouse: false,
|
mouse: false,
|
||||||
})
|
})
|
||||||
|
expect(config).not.toHaveProperty("skipped_version")
|
||||||
|
expect(config).not.toHaveProperty("which_key")
|
||||||
expect((await Bun.file(path.join(directory, "cli.json")).json()).keybinds).toEqual({ leader: "ctrl+o" })
|
expect((await Bun.file(path.join(directory, "cli.json")).json()).keybinds).toEqual({ leader: "ctrl+o" })
|
||||||
expect(await Bun.file(path.join(directory, "cli.json")).exists()).toBe(true)
|
expect(await Bun.file(path.join(directory, "cli.json")).exists()).toBe(true)
|
||||||
expect(await Bun.file(path.join(directory, "tui.json")).exists()).toBe(true)
|
expect(await Bun.file(path.join(directory, "tui.json")).exists()).toBe(true)
|
||||||
|
|||||||
@@ -358,12 +358,6 @@ export type TuiTheme = {
|
|||||||
readonly ready: boolean
|
readonly ready: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TuiKV = {
|
|
||||||
get: <Value = unknown>(key: string, fallback?: Value) => Value
|
|
||||||
set: (key: string, value: unknown) => void
|
|
||||||
readonly ready: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export type TuiState = {
|
export type TuiState = {
|
||||||
readonly ready: boolean
|
readonly ready: boolean
|
||||||
readonly config: SdkConfig
|
readonly config: SdkConfig
|
||||||
@@ -434,6 +428,7 @@ type TuiConfigView = {
|
|||||||
sidebar?: "auto" | "hide"
|
sidebar?: "auto" | "hide"
|
||||||
scrollbar?: boolean
|
scrollbar?: boolean
|
||||||
thinking?: "show" | "hide"
|
thinking?: "show" | "hide"
|
||||||
|
markdown?: "source" | "rendered"
|
||||||
grouping?: "auto" | "none"
|
grouping?: "auto" | "none"
|
||||||
}
|
}
|
||||||
hints?: { tips?: boolean; onboarding?: boolean }
|
hints?: { tips?: boolean; onboarding?: boolean }
|
||||||
@@ -628,7 +623,6 @@ export type TuiPluginApi = {
|
|||||||
dialog: TuiDialogStack
|
dialog: TuiDialogStack
|
||||||
}
|
}
|
||||||
readonly tuiConfig: Frozen<TuiConfigView>
|
readonly tuiConfig: Frozen<TuiConfigView>
|
||||||
kv: TuiKV
|
|
||||||
state: TuiState
|
state: TuiState
|
||||||
theme: TuiTheme
|
theme: TuiTheme
|
||||||
client: OpencodeClient
|
client: OpencodeClient
|
||||||
|
|||||||
@@ -19,7 +19,6 @@
|
|||||||
"./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",
|
||||||
"./context/kv": "./src/context/kv.tsx",
|
|
||||||
"./context/log": "./src/context/log.tsx",
|
"./context/log": "./src/context/log.tsx",
|
||||||
"./context/project": "./src/context/project.tsx",
|
"./context/project": "./src/context/project.tsx",
|
||||||
"./context/runtime": "./src/context/runtime.tsx",
|
"./context/runtime": "./src/context/runtime.tsx",
|
||||||
|
|||||||
+102
-112
@@ -71,7 +71,6 @@ import { DialogAlert } from "./ui/dialog-alert"
|
|||||||
import { DialogConfirm } from "./ui/dialog-confirm"
|
import { DialogConfirm } from "./ui/dialog-confirm"
|
||||||
import { ToastProvider, useToast } from "./ui/toast"
|
import { ToastProvider, useToast } from "./ui/toast"
|
||||||
import { isDefaultTitle } from "./util/session"
|
import { isDefaultTitle } from "./util/session"
|
||||||
import { KVProvider, useKV } from "./context/kv"
|
|
||||||
import * as Model from "./util/model"
|
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"
|
||||||
@@ -181,23 +180,6 @@ function errorMessage(error: unknown) {
|
|||||||
return error instanceof Error ? error.message : String(error)
|
return error instanceof Error ? error.message : String(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
function isVersionGreater(left: string, right: string) {
|
|
||||||
const parse = (value: string) => {
|
|
||||||
const [core, prerelease] = value.replace(/^v/, "").split("-", 2)
|
|
||||||
return { core: core.split(".").map((part) => Number.parseInt(part, 10) || 0), prerelease }
|
|
||||||
}
|
|
||||||
const a = parse(left)
|
|
||||||
const b = parse(right)
|
|
||||||
for (let index = 0; index < Math.max(a.core.length, b.core.length); index++) {
|
|
||||||
const difference = (a.core[index] ?? 0) - (b.core[index] ?? 0)
|
|
||||||
if (difference) return difference > 0
|
|
||||||
}
|
|
||||||
if (a.prerelease === b.prerelease) return false
|
|
||||||
if (!a.prerelease) return true
|
|
||||||
if (!b.prerelease) return false
|
|
||||||
return a.prerelease.localeCompare(b.prerelease, undefined, { numeric: true }) > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
export const run = Effect.fn("Tui.run")(function* (input: TuiInput) {
|
export const run = Effect.fn("Tui.run")(function* (input: TuiInput) {
|
||||||
const log = input.log ?? (() => {})
|
const log = input.log ?? (() => {})
|
||||||
const global = yield* Global.Service
|
const global = yield* Global.Service
|
||||||
@@ -343,67 +325,65 @@ export const run = Effect.fn("Tui.run")(function* (input: TuiInput) {
|
|||||||
service={input.config}
|
service={input.config}
|
||||||
options={{ terminalSuspend: process.platform !== "win32" }}
|
options={{ terminalSuspend: process.platform !== "win32" }}
|
||||||
>
|
>
|
||||||
<KVProvider>
|
<ToastProvider>
|
||||||
<ToastProvider>
|
<RouteProvider
|
||||||
<RouteProvider
|
initialRoute={
|
||||||
initialRoute={
|
input.args.continue
|
||||||
input.args.continue
|
? {
|
||||||
? {
|
type: "session",
|
||||||
type: "session",
|
sessionID: "dummy",
|
||||||
sessionID: "dummy",
|
}
|
||||||
}
|
: undefined
|
||||||
: undefined
|
}
|
||||||
}
|
>
|
||||||
>
|
<PluginRuntimeProvider value={pluginRuntime}>
|
||||||
<PluginRuntimeProvider value={pluginRuntime}>
|
<SDKProvider
|
||||||
<SDKProvider
|
client={createOpencodeClient({ ...options, directory })}
|
||||||
client={createOpencodeClient({ ...options, directory })}
|
api={api}
|
||||||
api={api}
|
reconnect={reconnect}
|
||||||
reconnect={reconnect}
|
reload={input.server.reload}
|
||||||
reload={input.server.reload}
|
>
|
||||||
>
|
<PermissionProvider>
|
||||||
<PermissionProvider>
|
<ProjectProvider>
|
||||||
<ProjectProvider>
|
<SyncProvider>
|
||||||
<SyncProvider>
|
<DataProvider>
|
||||||
<DataProvider>
|
<ThemeProvider mode={mode}>
|
||||||
<ThemeProvider mode={mode}>
|
<LocalProvider>
|
||||||
<LocalProvider>
|
<PromptStashProvider>
|
||||||
<PromptStashProvider>
|
<DialogProvider>
|
||||||
<DialogProvider>
|
<FrecencyProvider>
|
||||||
<FrecencyProvider>
|
<PromptHistoryProvider>
|
||||||
<PromptHistoryProvider>
|
<PromptRefProvider>
|
||||||
<PromptRefProvider>
|
<EditorContextProvider>
|
||||||
<EditorContextProvider>
|
<LocationProvider>
|
||||||
<LocationProvider>
|
<App
|
||||||
<App
|
pluginHost={input.pluginHost}
|
||||||
pluginHost={input.pluginHost}
|
pair={
|
||||||
pair={
|
input.server.endpoint.auth
|
||||||
input.server.endpoint.auth
|
? input.server.endpoint.auth
|
||||||
? input.server.endpoint.auth
|
: {
|
||||||
: {
|
username: "opencode",
|
||||||
username: "opencode",
|
password: "",
|
||||||
password: "",
|
}
|
||||||
}
|
}
|
||||||
}
|
/>
|
||||||
/>
|
</LocationProvider>
|
||||||
</LocationProvider>
|
</EditorContextProvider>
|
||||||
</EditorContextProvider>
|
</PromptRefProvider>
|
||||||
</PromptRefProvider>
|
</PromptHistoryProvider>
|
||||||
</PromptHistoryProvider>
|
</FrecencyProvider>
|
||||||
</FrecencyProvider>
|
</DialogProvider>
|
||||||
</DialogProvider>
|
</PromptStashProvider>
|
||||||
</PromptStashProvider>
|
</LocalProvider>
|
||||||
</LocalProvider>
|
</ThemeProvider>
|
||||||
</ThemeProvider>
|
</DataProvider>
|
||||||
</DataProvider>
|
</SyncProvider>
|
||||||
</SyncProvider>
|
</ProjectProvider>
|
||||||
</ProjectProvider>
|
</PermissionProvider>
|
||||||
</PermissionProvider>
|
</SDKProvider>
|
||||||
</SDKProvider>
|
</PluginRuntimeProvider>
|
||||||
</PluginRuntimeProvider>
|
</RouteProvider>
|
||||||
</RouteProvider>
|
</ToastProvider>
|
||||||
</ToastProvider>
|
|
||||||
</KVProvider>
|
|
||||||
</ConfigProvider>
|
</ConfigProvider>
|
||||||
</ArgsProvider>
|
</ArgsProvider>
|
||||||
</OpencodeKeymapProvider>
|
</OpencodeKeymapProvider>
|
||||||
@@ -440,13 +420,13 @@ function App(props: {
|
|||||||
}) {
|
}) {
|
||||||
const log = useLog({ component: "app" })
|
const log = useLog({ component: "app" })
|
||||||
const startup = useTuiStartup()
|
const startup = useTuiStartup()
|
||||||
const config = useConfig().data
|
const configState = useConfig()
|
||||||
|
const config = configState.data
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const dimensions = useTerminalDimensions()
|
const dimensions = useTerminalDimensions()
|
||||||
const renderer = useRenderer()
|
const renderer = useRenderer()
|
||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
const local = useLocal()
|
const local = useLocal()
|
||||||
const kv = useKV()
|
|
||||||
const keymap = useOpencodeKeymap()
|
const keymap = useOpencodeKeymap()
|
||||||
const event = useEvent()
|
const event = useEvent()
|
||||||
const sdk = useSDK()
|
const sdk = useSDK()
|
||||||
@@ -459,7 +439,7 @@ function App(props: {
|
|||||||
const exit = useExit()
|
const exit = useExit()
|
||||||
const promptRef = usePromptRef()
|
const promptRef = usePromptRef()
|
||||||
const pluginRuntime = usePluginRuntime()
|
const pluginRuntime = usePluginRuntime()
|
||||||
const attention = createTuiAttention({ renderer, config, kv })
|
const attention = createTuiAttention({ renderer, config, update: configState.update })
|
||||||
const clipboard = useClipboard()
|
const clipboard = useClipboard()
|
||||||
|
|
||||||
// Toast once when an MCP server enters a failed or needs-auth state so the user knows to act,
|
// Toast once when an MCP server enters a failed or needs-auth state so the user knows to act,
|
||||||
@@ -496,7 +476,6 @@ function App(props: {
|
|||||||
tuiConfig: config,
|
tuiConfig: config,
|
||||||
dialog,
|
dialog,
|
||||||
keymap,
|
keymap,
|
||||||
kv,
|
|
||||||
route,
|
route,
|
||||||
routes: pluginRuntime.routes,
|
routes: pluginRuntime.routes,
|
||||||
event,
|
event,
|
||||||
@@ -549,8 +528,8 @@ function App(props: {
|
|||||||
|
|
||||||
renderer.clearSelection()
|
renderer.clearSelection()
|
||||||
}
|
}
|
||||||
const [terminalTitleEnabled, setTerminalTitleEnabled] = kv.signal("terminal_title_enabled", true)
|
const terminalTitleEnabled = () => config.terminal?.title ?? true
|
||||||
const [pasteSummaryEnabled, setPasteSummaryEnabled] = kv.signal("paste_summary_enabled", true)
|
const pasteSummaryEnabled = () => config.prompt?.paste !== "full"
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
renderer.useMouse = !Flag.OPENCODE_DISABLE_MOUSE && config.mouse
|
renderer.useMouse = !Flag.OPENCODE_DISABLE_MOUSE && config.mouse
|
||||||
@@ -880,6 +859,7 @@ function App(props: {
|
|||||||
{
|
{
|
||||||
name: "theme.switch_mode",
|
name: "theme.switch_mode",
|
||||||
title: mode() === "dark" ? "Switch to light mode" : "Switch to dark mode",
|
title: mode() === "dark" ? "Switch to light mode" : "Switch to dark mode",
|
||||||
|
hidden: true,
|
||||||
run: () => {
|
run: () => {
|
||||||
setMode(mode() === "dark" ? "light" : "dark")
|
setMode(mode() === "dark" ? "light" : "dark")
|
||||||
dialog.clear()
|
dialog.clear()
|
||||||
@@ -889,6 +869,7 @@ function App(props: {
|
|||||||
{
|
{
|
||||||
name: "theme.mode.lock",
|
name: "theme.mode.lock",
|
||||||
title: locked() ? "Unlock theme mode" : "Lock theme mode",
|
title: locked() ? "Unlock theme mode" : "Lock theme mode",
|
||||||
|
hidden: true,
|
||||||
run: () => {
|
run: () => {
|
||||||
if (locked()) unlock()
|
if (locked()) unlock()
|
||||||
else lock()
|
else lock()
|
||||||
@@ -956,41 +937,57 @@ function App(props: {
|
|||||||
name: "terminal.title.toggle",
|
name: "terminal.title.toggle",
|
||||||
title: terminalTitleEnabled() ? "Disable terminal title" : "Enable terminal title",
|
title: terminalTitleEnabled() ? "Disable terminal title" : "Enable terminal title",
|
||||||
category: "System",
|
category: "System",
|
||||||
|
hidden: true,
|
||||||
run: () => {
|
run: () => {
|
||||||
setTerminalTitleEnabled((prev) => {
|
const next = !terminalTitleEnabled()
|
||||||
const next = !prev
|
if (!next) renderer.setTerminalTitle("")
|
||||||
kv.set("terminal_title_enabled", next)
|
void configState
|
||||||
if (!next) renderer.setTerminalTitle("")
|
.update((draft) => {
|
||||||
return next
|
draft.terminal = { ...draft.terminal, title: next }
|
||||||
})
|
})
|
||||||
|
.catch(toast.error)
|
||||||
dialog.clear()
|
dialog.clear()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "app.toggle.animations",
|
name: "app.toggle.animations",
|
||||||
title: kv.get("animations_enabled", true) ? "Disable animations" : "Enable animations",
|
title: (config.animations ?? true) ? "Disable animations" : "Enable animations",
|
||||||
category: "System",
|
category: "System",
|
||||||
|
hidden: true,
|
||||||
run: () => {
|
run: () => {
|
||||||
kv.set("animations_enabled", !kv.get("animations_enabled", true))
|
void configState
|
||||||
|
.update((draft) => {
|
||||||
|
draft.animations = !(config.animations ?? true)
|
||||||
|
})
|
||||||
|
.catch(toast.error)
|
||||||
dialog.clear()
|
dialog.clear()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "app.toggle.file_context",
|
name: "app.toggle.file_context",
|
||||||
title: kv.get("file_context_enabled", true) ? "Disable file context" : "Enable file context",
|
title: (config.prompt?.editor ?? true) ? "Disable file context" : "Enable file context",
|
||||||
category: "System",
|
category: "System",
|
||||||
|
hidden: true,
|
||||||
run: () => {
|
run: () => {
|
||||||
kv.set("file_context_enabled", !kv.get("file_context_enabled", true))
|
void configState
|
||||||
|
.update((draft) => {
|
||||||
|
draft.prompt = { ...draft.prompt, editor: !(config.prompt?.editor ?? true) }
|
||||||
|
})
|
||||||
|
.catch(toast.error)
|
||||||
dialog.clear()
|
dialog.clear()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "app.toggle.diffwrap",
|
name: "app.toggle.diffwrap",
|
||||||
title: kv.get("diff_wrap_mode", "word") === "word" ? "Disable diff wrapping" : "Enable diff wrapping",
|
title: (config.diffs?.wrap ?? "word") === "word" ? "Disable diff wrapping" : "Enable diff wrapping",
|
||||||
category: "System",
|
category: "System",
|
||||||
|
hidden: true,
|
||||||
run: () => {
|
run: () => {
|
||||||
const current = kv.get("diff_wrap_mode", "word")
|
void configState
|
||||||
kv.set("diff_wrap_mode", current === "word" ? "none" : "word")
|
.update((draft) => {
|
||||||
|
draft.diffs = { ...draft.diffs, wrap: (config.diffs?.wrap ?? "word") === "word" ? "none" : "word" }
|
||||||
|
})
|
||||||
|
.catch(toast.error)
|
||||||
dialog.clear()
|
dialog.clear()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -998,12 +995,13 @@ function App(props: {
|
|||||||
name: "app.toggle.paste_summary",
|
name: "app.toggle.paste_summary",
|
||||||
title: pasteSummaryEnabled() ? "Disable paste summary" : "Enable paste summary",
|
title: pasteSummaryEnabled() ? "Disable paste summary" : "Enable paste summary",
|
||||||
category: "System",
|
category: "System",
|
||||||
|
hidden: true,
|
||||||
run: () => {
|
run: () => {
|
||||||
setPasteSummaryEnabled((prev) => {
|
void configState
|
||||||
const next = !prev
|
.update((draft) => {
|
||||||
kv.set("paste_summary_enabled", next)
|
draft.prompt = { ...draft.prompt, paste: pasteSummaryEnabled() ? "full" : "compact" }
|
||||||
return next
|
})
|
||||||
})
|
.catch(toast.error)
|
||||||
dialog.clear()
|
dialog.clear()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -1101,21 +1099,13 @@ function App(props: {
|
|||||||
event.on("installation.update-available", async (evt) => {
|
event.on("installation.update-available", async (evt) => {
|
||||||
const version = evt.data.version
|
const version = evt.data.version
|
||||||
|
|
||||||
const skipped = kv.get("skipped_version")
|
|
||||||
if (skipped && !isVersionGreater(version, skipped)) return
|
|
||||||
|
|
||||||
const choice = await DialogConfirm.show(
|
const choice = await DialogConfirm.show(
|
||||||
dialog,
|
dialog,
|
||||||
`Update Available`,
|
`Update Available`,
|
||||||
`A new release v${version} is available. Would you like to update now?`,
|
`A new release v${version} is available. Would you like to update now?`,
|
||||||
"skip",
|
"later",
|
||||||
)
|
)
|
||||||
|
|
||||||
if (choice === false) {
|
|
||||||
kv.set("skipped_version", version)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (choice !== true) return
|
if (choice !== true) return
|
||||||
|
|
||||||
toast.show({
|
toast.show({
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import type {
|
|||||||
TuiAttentionNotifyResult,
|
TuiAttentionNotifyResult,
|
||||||
TuiAttentionNotifySkipReason,
|
TuiAttentionNotifySkipReason,
|
||||||
TuiAttentionWhen,
|
TuiAttentionWhen,
|
||||||
TuiKV,
|
|
||||||
TuiAttentionSoundName,
|
TuiAttentionSoundName,
|
||||||
TuiAttentionSoundPack,
|
TuiAttentionSoundPack,
|
||||||
TuiAttentionSoundPackInfo,
|
TuiAttentionSoundPackInfo,
|
||||||
@@ -40,7 +39,6 @@ type TuiAttentionHost = TuiAttention & {
|
|||||||
|
|
||||||
const DEFAULT_TITLE = "opencode"
|
const DEFAULT_TITLE = "opencode"
|
||||||
const DEFAULT_PACK_ID = "opencode.default"
|
const DEFAULT_PACK_ID = "opencode.default"
|
||||||
const KV_SOUND_PACK = "attention_sound_pack"
|
|
||||||
const TITLE_LIMIT = 80
|
const TITLE_LIMIT = 80
|
||||||
const MESSAGE_LIMIT = 240
|
const MESSAGE_LIMIT = 240
|
||||||
const BUILTIN_PACK: RegisteredSoundPack = {
|
const BUILTIN_PACK: RegisteredSoundPack = {
|
||||||
@@ -114,7 +112,7 @@ function focusSkip(when: TuiAttentionWhen, focus: FocusState) {
|
|||||||
export function createTuiAttention(input: {
|
export function createTuiAttention(input: {
|
||||||
renderer: AttentionRenderer
|
renderer: AttentionRenderer
|
||||||
config: Pick<Config.Resolved, "attention">
|
config: Pick<Config.Resolved, "attention">
|
||||||
kv?: TuiKV
|
update?: Config.Interface["update"]
|
||||||
audio?: Pick<typeof TuiAudio, "loadSoundFile" | "play">
|
audio?: Pick<typeof TuiAudio, "loadSoundFile" | "play">
|
||||||
}): TuiAttentionHost {
|
}): TuiAttentionHost {
|
||||||
let focus: FocusState = "unknown"
|
let focus: FocusState = "unknown"
|
||||||
@@ -134,8 +132,7 @@ export function createTuiAttention(input: {
|
|||||||
input.renderer.on("blur", onBlur)
|
input.renderer.on("blur", onBlur)
|
||||||
|
|
||||||
function configuredPackID() {
|
function configuredPackID() {
|
||||||
const stored = input.kv?.get<string | undefined>(KV_SOUND_PACK, undefined)
|
return activePackID ?? input.config.attention.sound_pack
|
||||||
return activePackID ?? stored ?? input.config.attention.sound_pack
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function currentPack() {
|
function currentPack() {
|
||||||
@@ -234,7 +231,12 @@ export function createTuiAttention(input: {
|
|||||||
const pack = packs.get(id)
|
const pack = packs.get(id)
|
||||||
if (!pack) return false
|
if (!pack) return false
|
||||||
activePackID = pack.id
|
activePackID = pack.id
|
||||||
if (options?.persist) input.kv?.set(KV_SOUND_PACK, pack.id)
|
if (options?.persist)
|
||||||
|
void input
|
||||||
|
.update?.((draft) => {
|
||||||
|
draft.attention = { ...draft.attention, sound_pack: pack.id }
|
||||||
|
})
|
||||||
|
.catch(() => {})
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
current() {
|
current() {
|
||||||
|
|||||||
@@ -77,6 +77,13 @@ const settings: Setting[] = [
|
|||||||
default: "hide",
|
default: "hide",
|
||||||
values: ["hide", "show"],
|
values: ["hide", "show"],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "Markdown",
|
||||||
|
category: "Session",
|
||||||
|
path: ["session", "markdown"],
|
||||||
|
default: "rendered",
|
||||||
|
values: ["source", "rendered"],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: "Grouping",
|
title: "Grouping",
|
||||||
category: "Session",
|
category: "Session",
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ import { useDialog } from "../../ui/dialog"
|
|||||||
import { DialogIntegration } from "../dialog-integration"
|
import { DialogIntegration } from "../dialog-integration"
|
||||||
import { useConnected } from "../use-connected"
|
import { useConnected } from "../use-connected"
|
||||||
import { useToast } from "../../ui/toast"
|
import { useToast } from "../../ui/toast"
|
||||||
import { useKV } from "../../context/kv"
|
|
||||||
import { createFadeIn } from "../../util/signal"
|
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"
|
||||||
@@ -177,11 +176,10 @@ export function Prompt(props: PromptProps) {
|
|||||||
const exit = useExit()
|
const exit = useExit()
|
||||||
const dimensions = useTerminalDimensions()
|
const dimensions = useTerminalDimensions()
|
||||||
const { theme, syntax } = useTheme()
|
const { theme, syntax } = useTheme()
|
||||||
const kv = useKV()
|
const animationsEnabled = createMemo(() => config.animations ?? true)
|
||||||
const animationsEnabled = createMemo(() => kv.get("animations_enabled", true))
|
|
||||||
const list = createMemo(() => props.placeholders?.normal ?? [])
|
const list = createMemo(() => props.placeholders?.normal ?? [])
|
||||||
const shell = createMemo(() => props.placeholders?.shell ?? [])
|
const shell = createMemo(() => props.placeholders?.shell ?? [])
|
||||||
const fileContextEnabled = createMemo(() => kv.get("file_context_enabled", true))
|
const fileContextEnabled = createMemo(() => config.prompt?.editor ?? true)
|
||||||
const [dismissedEditorSelectionKey, setDismissedEditorSelectionKey] = createSignal<string>()
|
const [dismissedEditorSelectionKey, setDismissedEditorSelectionKey] = createSignal<string>()
|
||||||
const editorContext = createMemo(() => {
|
const editorContext = createMemo(() => {
|
||||||
const selection = fileContextEnabled() ? editor.selection() : undefined
|
const selection = fileContextEnabled() ? editor.selection() : undefined
|
||||||
@@ -1191,7 +1189,7 @@ export function Prompt(props: PromptProps) {
|
|||||||
const lineCount = (pastedContent.match(/\n/g)?.length ?? 0) + 1
|
const lineCount = (pastedContent.match(/\n/g)?.length ?? 0) + 1
|
||||||
if (
|
if (
|
||||||
(lineCount >= 3 || pastedContent.length > 150) &&
|
(lineCount >= 3 || pastedContent.length > 150) &&
|
||||||
kv.get("paste_summary_enabled", true)
|
config.prompt?.paste !== "full"
|
||||||
) {
|
) {
|
||||||
pasteText(pastedContent, `[Pasted ~${lineCount} lines]`)
|
pasteText(pastedContent, `[Pasted ~${lineCount} lines]`)
|
||||||
return
|
return
|
||||||
@@ -1495,7 +1493,7 @@ export function Prompt(props: PromptProps) {
|
|||||||
<Match when={status() === "running"}>
|
<Match when={status() === "running"}>
|
||||||
<box flexDirection="row" gap={1} flexGrow={1} justifyContent="flex-start">
|
<box flexDirection="row" gap={1} flexGrow={1} justifyContent="flex-start">
|
||||||
<box marginLeft={1}>
|
<box marginLeft={1}>
|
||||||
<Show when={kv.get("animations_enabled", true)} fallback={<text fg={theme.textMuted}>[⋯]</text>}>
|
<Show when={config.animations ?? true} fallback={<text fg={theme.textMuted}>[⋯]</text>}>
|
||||||
<spinner color={spinnerDef().color} frames={spinnerDef().frames} interval={40} />
|
<spinner color={spinnerDef().color} frames={spinnerDef().frames} interval={40} />
|
||||||
</Show>
|
</Show>
|
||||||
</box>
|
</box>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Show } from "solid-js"
|
import { Show } from "solid-js"
|
||||||
import { useTheme } from "../context/theme"
|
import { useTheme } from "../context/theme"
|
||||||
import { useKV } from "../context/kv"
|
import { useConfig } from "../config"
|
||||||
import type { JSX } from "@opentui/solid"
|
import type { JSX } from "@opentui/solid"
|
||||||
import type { RGBA } from "@opentui/core"
|
import type { RGBA } from "@opentui/core"
|
||||||
import { registerOpencodeSpinner } from "./register-spinner"
|
import { registerOpencodeSpinner } from "./register-spinner"
|
||||||
@@ -11,10 +11,10 @@ export const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦",
|
|||||||
|
|
||||||
export function Spinner(props: { children?: JSX.Element; color?: RGBA }) {
|
export function Spinner(props: { children?: JSX.Element; color?: RGBA }) {
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const kv = useKV()
|
const config = useConfig().data
|
||||||
const color = () => props.color ?? theme.textMuted
|
const color = () => props.color ?? theme.textMuted
|
||||||
return (
|
return (
|
||||||
<Show when={kv.get("animations_enabled", true)} fallback={<text fg={color()}>⋯ {props.children}</text>}>
|
<Show when={config.animations ?? true} fallback={<text fg={color()}>⋯ {props.children}</text>}>
|
||||||
<box flexDirection="row" gap={1}>
|
<box flexDirection="row" gap={1}>
|
||||||
<spinner frames={SPINNER_FRAMES} interval={80} color={color()} />
|
<spinner frames={SPINNER_FRAMES} interval={80} color={color()} />
|
||||||
<Show when={props.children}>
|
<Show when={props.children}>
|
||||||
|
|||||||
@@ -118,6 +118,9 @@ export const Info = Schema.Struct({
|
|||||||
grouping: Schema.optional(Schema.Literals(["auto", "none"])).annotate({
|
grouping: Schema.optional(Schema.Literals(["auto", "none"])).annotate({
|
||||||
description: "Group related transcript items automatically or render each item separately",
|
description: "Group related transcript items automatically or render each item separately",
|
||||||
}),
|
}),
|
||||||
|
markdown: Schema.optional(Schema.Literals(["source", "rendered"])).annotate({
|
||||||
|
description: "Show Markdown syntax markers or conceal them in rendered transcript content",
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
).annotate({ description: "Session transcript presentation settings" }),
|
).annotate({ description: "Session transcript presentation settings" }),
|
||||||
hints: Schema.optional(
|
hints: Schema.optional(
|
||||||
|
|||||||
@@ -1,107 +0,0 @@
|
|||||||
import { createEffect, createSignal, type Setter } from "solid-js"
|
|
||||||
import { createStore, unwrap } from "solid-js/store"
|
|
||||||
import { createSimpleContext } from "./helper"
|
|
||||||
import { Flock } from "@opencode-ai/core/util/flock"
|
|
||||||
import { Global } from "@opencode-ai/core/global"
|
|
||||||
import { readJson, writeJsonAtomic } from "../util/persistence"
|
|
||||||
import { useTuiPaths } from "./runtime"
|
|
||||||
import path from "path"
|
|
||||||
import { useConfigOptional, type Config } from "../config"
|
|
||||||
|
|
||||||
export const { use: useKV, provider: KVProvider } = createSimpleContext({
|
|
||||||
name: "KV",
|
|
||||||
init: (props: { config?: Config.Info }) => {
|
|
||||||
const config = props.config ?? useConfigOptional()?.data
|
|
||||||
const paths = useTuiPaths()
|
|
||||||
void Global.Path.state
|
|
||||||
const file = path.join(paths.state, "kv.json")
|
|
||||||
const lock = `tui-kv:${file}`
|
|
||||||
const [ready, setReady] = createSignal(false)
|
|
||||||
const [store, setStore] = createStore<Record<string, any>>()
|
|
||||||
// Queue same-process writes so rapid updates persist in order.
|
|
||||||
let write = Promise.resolve()
|
|
||||||
|
|
||||||
Flock.withLock(lock, () => readJson<Record<string, unknown>>(file))
|
|
||||||
.then((x) => {
|
|
||||||
const values: Record<string, any> = { ...x }
|
|
||||||
Object.entries(configValues(config ?? {})).forEach(([key, value]) => {
|
|
||||||
if (value === undefined) delete values[key]
|
|
||||||
else values[key] = value
|
|
||||||
})
|
|
||||||
setStore(values)
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error("Failed to read KV state", { error })
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
setReady(true)
|
|
||||||
})
|
|
||||||
|
|
||||||
createEffect(() => {
|
|
||||||
if (!ready() || !config) return
|
|
||||||
Object.entries(configValues(config)).forEach(([key, value]) => {
|
|
||||||
if (value === undefined) setStore(key, undefined)
|
|
||||||
else setStore(key, value)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
const result = {
|
|
||||||
get ready() {
|
|
||||||
return ready()
|
|
||||||
},
|
|
||||||
get store() {
|
|
||||||
return store
|
|
||||||
},
|
|
||||||
signal<T>(name: string, defaultValue: T) {
|
|
||||||
if (store[name] === undefined) setStore(name, defaultValue)
|
|
||||||
return [
|
|
||||||
function () {
|
|
||||||
return result.get(name)
|
|
||||||
},
|
|
||||||
function setter(next: Setter<T>) {
|
|
||||||
result.set(name, next)
|
|
||||||
},
|
|
||||||
] as const
|
|
||||||
},
|
|
||||||
get(key: string, defaultValue?: any) {
|
|
||||||
return store[key] ?? defaultValue
|
|
||||||
},
|
|
||||||
set(key: string, value: any) {
|
|
||||||
setStore(key, value)
|
|
||||||
const snapshot = structuredClone(unwrap(store))
|
|
||||||
write = write
|
|
||||||
.then(() => Flock.withLock(lock, () => writeJsonAtomic(file, snapshot)))
|
|
||||||
.catch((error) => {
|
|
||||||
console.error("Failed to write KV state", { error })
|
|
||||||
})
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
function configValues(config: Config.Info) {
|
|
||||||
const values: Record<string, any> = {}
|
|
||||||
if (config.theme?.name !== undefined) values.theme = config.theme.name
|
|
||||||
if (config.theme?.mode !== undefined) {
|
|
||||||
values.theme_mode_lock = config.theme.mode === "system" ? undefined : config.theme.mode
|
|
||||||
values.theme_mode = undefined
|
|
||||||
}
|
|
||||||
if (config.attention?.sound_pack !== undefined) values.attention_sound_pack = config.attention.sound_pack
|
|
||||||
if (config.diffs?.wrap !== undefined) values.diff_wrap_mode = config.diffs.wrap
|
|
||||||
if (config.diffs?.tree !== undefined) values.diff_viewer_show_file_tree = config.diffs.tree
|
|
||||||
if (config.diffs?.single !== undefined) values.diff_viewer_single_patch = config.diffs.single
|
|
||||||
if (config.diffs?.view !== undefined)
|
|
||||||
values.diff_viewer_view = config.diffs.view === "auto" ? undefined : config.diffs.view
|
|
||||||
if (config.terminal?.title !== undefined) values.terminal_title_enabled = config.terminal.title
|
|
||||||
if (config.prompt?.editor !== undefined) values.file_context_enabled = config.prompt.editor
|
|
||||||
if (config.prompt?.paste !== undefined) values.paste_summary_enabled = config.prompt.paste === "compact"
|
|
||||||
if (config.session?.sidebar !== undefined) values.sidebar = config.session.sidebar
|
|
||||||
if (config.session?.scrollbar !== undefined) values.scrollbar_visible = config.session.scrollbar
|
|
||||||
if (config.session?.thinking !== undefined) values.thinking_mode = config.session.thinking
|
|
||||||
if (config.session?.grouping !== undefined) values.exploration_grouping = config.session.grouping === "auto"
|
|
||||||
if (config.hints?.tips !== undefined) values.tips_hidden = !config.hints.tips
|
|
||||||
if (config.hints?.onboarding !== undefined) values.dismissed_getting_started = !config.hints.onboarding
|
|
||||||
if (config.animations !== undefined) values.animations_enabled = config.animations
|
|
||||||
return values
|
|
||||||
}
|
|
||||||
@@ -22,7 +22,6 @@ import {
|
|||||||
import { createEffect, createMemo, onCleanup, onMount } from "solid-js"
|
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 { useConfig } from "../config"
|
import { useConfig } from "../config"
|
||||||
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"
|
||||||
@@ -103,8 +102,8 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
|
|||||||
name: "Theme",
|
name: "Theme",
|
||||||
init: (props: { mode: "dark" | "light"; source?: ThemeSource }) => {
|
init: (props: { mode: "dark" | "light"; source?: ThemeSource }) => {
|
||||||
const renderer = useRenderer()
|
const renderer = useRenderer()
|
||||||
const config = useConfig().data
|
const configState = useConfig()
|
||||||
const kv = useKV()
|
const config = configState.data
|
||||||
const themes = props.source ?? themeSource
|
const themes = props.source ?? themeSource
|
||||||
const pick = (value: unknown) => {
|
const pick = (value: unknown) => {
|
||||||
if (value === "dark" || value === "light") return value
|
if (value === "dark" || value === "light") return value
|
||||||
@@ -113,12 +112,11 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
|
|||||||
|
|
||||||
setStore(
|
setStore(
|
||||||
produce((draft) => {
|
produce((draft) => {
|
||||||
const lock = pick(kv.get("theme_mode_lock"))
|
const lock = pick(config.theme?.mode)
|
||||||
const mode = lock ?? pick(renderer.themeMode) ?? props.mode
|
const mode = lock ?? pick(renderer.themeMode) ?? props.mode
|
||||||
if (!lock && pick(kv.get("theme_mode")) !== undefined) kv.set("theme_mode", undefined)
|
|
||||||
draft.mode = mode
|
draft.mode = mode
|
||||||
draft.lock = lock
|
draft.lock = lock
|
||||||
const active = config.theme?.name ?? kv.get("theme", "opencode")
|
const active = config.theme?.name ?? "opencode"
|
||||||
draft.active = typeof active === "string" ? active : "opencode"
|
draft.active = typeof active === "string" ? active : "opencode"
|
||||||
draft.ready = false
|
draft.ready = false
|
||||||
}),
|
}),
|
||||||
@@ -132,10 +130,10 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
|
|||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const mode = config.theme?.mode
|
const mode = config.theme?.mode
|
||||||
if (mode === "dark" || mode === "light") {
|
if (mode === "dark" || mode === "light") {
|
||||||
pin(mode)
|
pin(mode, false)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (mode === "system" && store.lock !== undefined) free()
|
if (mode === "system" && store.lock !== undefined) free(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
function syncCustomThemes() {
|
function syncCustomThemes() {
|
||||||
@@ -209,23 +207,31 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function apply(mode: "dark" | "light") {
|
function apply(mode: "dark" | "light") {
|
||||||
if (store.lock !== undefined) kv.set("theme_mode", mode)
|
|
||||||
if (store.mode === mode) return
|
if (store.mode === mode) return
|
||||||
setStore("mode", mode)
|
setStore("mode", mode)
|
||||||
refreshSystemTheme(mode)
|
refreshSystemTheme(mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
function pin(mode: "dark" | "light" = store.mode) {
|
function pin(mode: "dark" | "light" = store.mode, persist = true) {
|
||||||
setStore("lock", mode)
|
setStore("lock", mode)
|
||||||
kv.set("theme_mode_lock", mode)
|
|
||||||
apply(mode)
|
apply(mode)
|
||||||
|
if (!persist) return
|
||||||
|
void configState
|
||||||
|
.update((draft) => {
|
||||||
|
draft.theme = { ...draft.theme, mode }
|
||||||
|
})
|
||||||
|
.catch(() => {})
|
||||||
}
|
}
|
||||||
|
|
||||||
function free() {
|
function free(persist = true) {
|
||||||
setStore("lock", undefined)
|
setStore("lock", undefined)
|
||||||
kv.set("theme_mode_lock", undefined)
|
|
||||||
kv.set("theme_mode", undefined)
|
|
||||||
refreshSystemTheme(renderer.themeMode ?? store.mode)
|
refreshSystemTheme(renderer.themeMode ?? store.mode)
|
||||||
|
if (!persist) return
|
||||||
|
void configState
|
||||||
|
.update((draft) => {
|
||||||
|
draft.theme = { ...draft.theme, mode: "system" }
|
||||||
|
})
|
||||||
|
.catch(() => {})
|
||||||
}
|
}
|
||||||
|
|
||||||
const handle = (mode: "dark" | "light") => {
|
const handle = (mode: "dark" | "light") => {
|
||||||
@@ -265,13 +271,6 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
|
|||||||
const values = createMemo(() => {
|
const values = createMemo(() => {
|
||||||
const active = store.themes[store.active]
|
const active = store.themes[store.active]
|
||||||
if (active) return resolveTheme(active, store.mode)
|
if (active) return resolveTheme(active, store.mode)
|
||||||
|
|
||||||
const saved = kv.get("theme")
|
|
||||||
if (typeof saved === "string") {
|
|
||||||
const theme = store.themes[saved]
|
|
||||||
if (theme) return resolveTheme(theme, store.mode)
|
|
||||||
}
|
|
||||||
|
|
||||||
return resolveTheme(store.themes.opencode, store.mode)
|
return resolveTheme(store.themes.opencode, store.mode)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -302,7 +301,11 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
|
|||||||
set(theme: string) {
|
set(theme: string) {
|
||||||
if (!hasTheme(theme)) return false
|
if (!hasTheme(theme)) return false
|
||||||
setStore("active", theme)
|
setStore("active", theme)
|
||||||
kv.set("theme", theme)
|
void configState
|
||||||
|
.update((draft) => {
|
||||||
|
draft.theme = { ...draft.theme, name: theme }
|
||||||
|
})
|
||||||
|
.catch(() => {})
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
get ready() {
|
get ready() {
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
import { createMemo, type Setter } from "solid-js"
|
|
||||||
import { useKV } from "./kv"
|
|
||||||
|
|
||||||
export type ThinkingMode = "show" | "hide"
|
export type ThinkingMode = "show" | "hide"
|
||||||
|
|
||||||
const MODES: readonly ThinkingMode[] = ["show", "hide"] as const
|
const MODES: readonly ThinkingMode[] = ["show", "hide"] as const
|
||||||
@@ -16,52 +13,8 @@ export function reasoningSummary(text: string) {
|
|||||||
return { title: match[1].trim(), body: content.slice(match[0].length).trimEnd() }
|
return { title: match[1].trim(), body: content.slice(match[0].length).trimEnd() }
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isThinkingMode(value: unknown): value is ThinkingMode {
|
|
||||||
return typeof value === "string" && (MODES as readonly string[]).includes(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cycle order matches the slash command: show → hide → show.
|
// Cycle order matches the slash command: show → hide → show.
|
||||||
export function nextThinkingMode(current: ThinkingMode): ThinkingMode {
|
export function nextThinkingMode(current: ThinkingMode): ThinkingMode {
|
||||||
const idx = MODES.indexOf(current)
|
const idx = MODES.indexOf(current)
|
||||||
return MODES[(idx + 1) % MODES.length] ?? "show"
|
return MODES[(idx + 1) % MODES.length] ?? "show"
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useThinkingMode() {
|
|
||||||
const kv = useKV()
|
|
||||||
// Capture pre-state before `kv.signal` seeds a default, so we can detect
|
|
||||||
// first-time users with a legacy `thinking_visibility` boolean and migrate.
|
|
||||||
// The KVProvider only renders children once kv.ready, so reads here are safe.
|
|
||||||
const hadStored = kv.get("thinking_mode") !== undefined
|
|
||||||
const legacy = kv.get("thinking_visibility")
|
|
||||||
const [stored, setStored] = kv.signal<ThinkingMode>("thinking_mode", "hide")
|
|
||||||
|
|
||||||
// The kv signal exposes its setter typed as `Setter<T>` which carries Solid's
|
|
||||||
// overload set; passing an updater fn through a property access loses the
|
|
||||||
// bivariance trick the existing `setX((prev) => ...)` callsites rely on.
|
|
||||||
// Wrap it in a sane shape so consumers can just call `set(next)` or pass
|
|
||||||
// an updater.
|
|
||||||
const set = (next: ThinkingMode | ((prev: ThinkingMode) => ThinkingMode)) => {
|
|
||||||
if (typeof next === "function") setStored(next as Setter<ThinkingMode>)
|
|
||||||
else setStored(() => next)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Preserve previous experience for users who had explicitly toggled the
|
|
||||||
// legacy `thinking_visibility` boolean. First-time users (no legacy key)
|
|
||||||
// get the new "hide" default (collapsed thinking).
|
|
||||||
if (!hadStored) {
|
|
||||||
if (legacy === true) set("show")
|
|
||||||
else if (legacy === false) set("hide")
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((stored() as string) === "minimal") set("hide")
|
|
||||||
|
|
||||||
const mode = createMemo<ThinkingMode>(() => {
|
|
||||||
const value = stored()
|
|
||||||
return isThinkingMode(value) ? value : "hide"
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
mode,
|
|
||||||
set,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -5,10 +5,12 @@ import { Tips } from "./tips-view"
|
|||||||
import { useBindings } from "../../keymap"
|
import { useBindings } from "../../keymap"
|
||||||
import { useData } from "../../context/data"
|
import { useData } from "../../context/data"
|
||||||
import { hasConnectedProvider } from "../../util/connected-provider"
|
import { hasConnectedProvider } from "../../util/connected-provider"
|
||||||
|
import { useConfig } from "../../config"
|
||||||
|
|
||||||
const id = "internal:home-tips"
|
const id = "internal:home-tips"
|
||||||
|
|
||||||
function View(props: { api: TuiPluginApi; hidden: boolean; show: boolean; connected: boolean }) {
|
function View(props: { api: TuiPluginApi; hidden: boolean; show: boolean; connected: boolean }) {
|
||||||
|
const config = useConfig()
|
||||||
useBindings(() => ({
|
useBindings(() => ({
|
||||||
commands: [
|
commands: [
|
||||||
{
|
{
|
||||||
@@ -16,8 +18,13 @@ function View(props: { api: TuiPluginApi; hidden: boolean; show: boolean; connec
|
|||||||
title: props.hidden ? "Show tips" : "Hide tips",
|
title: props.hidden ? "Show tips" : "Hide tips",
|
||||||
category: "System",
|
category: "System",
|
||||||
namespace: "palette",
|
namespace: "palette",
|
||||||
|
hidden: true,
|
||||||
run() {
|
run() {
|
||||||
props.api.kv.set("tips_hidden", !props.api.kv.get("tips_hidden", false))
|
void config
|
||||||
|
.update((draft) => {
|
||||||
|
draft.hints = { ...draft.hints, tips: props.hidden }
|
||||||
|
})
|
||||||
|
.catch(() => {})
|
||||||
props.api.ui.dialog.clear()
|
props.api.ui.dialog.clear()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -40,7 +47,8 @@ const tui: TuiPlugin = async (api) => {
|
|||||||
slots: {
|
slots: {
|
||||||
home_bottom() {
|
home_bottom() {
|
||||||
const data = useData()
|
const data = useData()
|
||||||
const hidden = createMemo(() => api.kv.get("tips_hidden", false))
|
const config = useConfig().data
|
||||||
|
const hidden = createMemo(() => !(config.hints?.tips ?? true))
|
||||||
const first = createMemo(() => api.state.session.count() === 0)
|
const first = createMemo(() => api.state.session.count() === 0)
|
||||||
const connected = createMemo(() => hasConnectedProvider(data.location.integration.list() ?? []))
|
const connected = createMemo(() => hasConnectedProvider(data.location.integration.list() ?? []))
|
||||||
const show = createMemo(() => (!first() || !connected()) && !hidden())
|
const show = createMemo(() => (!first() || !connected()) && !hidden())
|
||||||
|
|||||||
@@ -4,18 +4,20 @@ import { createMemo, Show } from "solid-js"
|
|||||||
import { abbreviateHome } from "../../runtime"
|
import { abbreviateHome } from "../../runtime"
|
||||||
import { useTuiPaths } from "../../context/runtime"
|
import { useTuiPaths } from "../../context/runtime"
|
||||||
import { FilePath } from "../../ui/file-path"
|
import { FilePath } from "../../ui/file-path"
|
||||||
|
import { useConfig } from "../../config"
|
||||||
|
|
||||||
const id = "internal:sidebar-footer"
|
const id = "internal:sidebar-footer"
|
||||||
|
|
||||||
function View(props: { api: TuiPluginApi; directory: string }) {
|
function View(props: { api: TuiPluginApi; directory: string }) {
|
||||||
const paths = useTuiPaths()
|
const paths = useTuiPaths()
|
||||||
|
const config = useConfig()
|
||||||
const theme = () => props.api.theme.current
|
const theme = () => props.api.theme.current
|
||||||
const has = createMemo(() =>
|
const has = createMemo(() =>
|
||||||
props.api.state.provider.some(
|
props.api.state.provider.some(
|
||||||
(item) => item.id !== "opencode" || Object.values(item.models).some((model) => model.cost?.input !== 0),
|
(item) => item.id !== "opencode" || Object.values(item.models).some((model) => model.cost?.input !== 0),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
const done = createMemo(() => props.api.kv.get("dismissed_getting_started", false))
|
const done = createMemo(() => !(config.data.hints?.onboarding ?? true))
|
||||||
const show = createMemo(() => !has() && !done())
|
const show = createMemo(() => !has() && !done())
|
||||||
const location = createMemo(() => {
|
const location = createMemo(() => {
|
||||||
const branch = props.directory === props.api.state.path.directory ? props.api.state.vcs?.branch : undefined
|
const branch = props.directory === props.api.state.path.directory ? props.api.state.vcs?.branch : undefined
|
||||||
@@ -44,7 +46,16 @@ function View(props: { api: TuiPluginApi; directory: string }) {
|
|||||||
<text fg={theme().text}>
|
<text fg={theme().text}>
|
||||||
<b>Getting started</b>
|
<b>Getting started</b>
|
||||||
</text>
|
</text>
|
||||||
<text fg={theme().textMuted} onMouseDown={() => props.api.kv.set("dismissed_getting_started", true)}>
|
<text
|
||||||
|
fg={theme().textMuted}
|
||||||
|
onMouseDown={() =>
|
||||||
|
void config
|
||||||
|
.update((draft) => {
|
||||||
|
draft.hints = { ...draft.hints, onboarding: false }
|
||||||
|
})
|
||||||
|
.catch(() => {})
|
||||||
|
}
|
||||||
|
>
|
||||||
✕
|
✕
|
||||||
</text>
|
</text>
|
||||||
</box>
|
</box>
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import { DiffViewerFileTree } from "./diff-viewer-file-tree"
|
|||||||
import { Panel, PanelGroup, Separator } from "./diff-viewer-ui"
|
import { Panel, PanelGroup, Separator } from "./diff-viewer-ui"
|
||||||
import { DialogSelect } from "../../ui/dialog-select"
|
import { DialogSelect } from "../../ui/dialog-select"
|
||||||
import { getScrollAcceleration } from "../../util/scroll"
|
import { getScrollAcceleration } from "../../util/scroll"
|
||||||
|
import { useConfig } from "../../config"
|
||||||
import {
|
import {
|
||||||
allExpandedFileTreeDirectories,
|
allExpandedFileTreeDirectories,
|
||||||
buildFileTree,
|
buildFileTree,
|
||||||
@@ -41,9 +42,6 @@ const MIN_SPLIT_WIDTH = 100
|
|||||||
const FILE_TREE_WIDTH = 32
|
const FILE_TREE_WIDTH = 32
|
||||||
const PLAIN_TEXT_FILETYPE = "opencode-plain-text"
|
const PLAIN_TEXT_FILETYPE = "opencode-plain-text"
|
||||||
const VCS_DIFF_CONTEXT_LINES = 12
|
const VCS_DIFF_CONTEXT_LINES = 12
|
||||||
const KV_SHOW_FILE_TREE = "diff_viewer_show_file_tree"
|
|
||||||
const KV_SINGLE_PATCH = "diff_viewer_single_patch"
|
|
||||||
const KV_VIEW = "diff_viewer_view"
|
|
||||||
type DiffMode = "working" | "branch" | "last-turn"
|
type DiffMode = "working" | "branch" | "last-turn"
|
||||||
type DiffViewerFocus = "patches" | "files"
|
type DiffViewerFocus = "patches" | "files"
|
||||||
type DiffView = "split" | "unified"
|
type DiffView = "split" | "unified"
|
||||||
@@ -92,6 +90,7 @@ function diffSourceLabel(mode: DiffMode) {
|
|||||||
function DiffViewer(props: { api: TuiPluginApi }) {
|
function DiffViewer(props: { api: TuiPluginApi }) {
|
||||||
const dimensions = useTerminalDimensions()
|
const dimensions = useTerminalDimensions()
|
||||||
const sdk = useSDK()
|
const sdk = useSDK()
|
||||||
|
const config = useConfig()
|
||||||
const themeState = useTheme()
|
const themeState = useTheme()
|
||||||
const theme = () => props.api.theme.current
|
const theme = () => props.api.theme.current
|
||||||
const params = () =>
|
const params = () =>
|
||||||
@@ -135,11 +134,9 @@ function DiffViewer(props: { api: TuiPluginApi }) {
|
|||||||
})
|
})
|
||||||
const files = createMemo(() => diff() ?? [])
|
const files = createMemo(() => diff() ?? [])
|
||||||
const [focus, setFocus] = createSignal<DiffViewerFocus>("patches")
|
const [focus, setFocus] = createSignal<DiffViewerFocus>("patches")
|
||||||
const [fileTreeEnabled, setFileTreeEnabled] = createSignal(
|
const [fileTreeEnabled, setFileTreeEnabled] = createSignal(config.data.diffs?.tree ?? true)
|
||||||
props.api.kv.get<boolean>(KV_SHOW_FILE_TREE, true) !== false,
|
|
||||||
)
|
|
||||||
const showFileTree = createMemo(() => showDiffViewerFileTree(fileTreeEnabled(), files().length))
|
const showFileTree = createMemo(() => showDiffViewerFileTree(fileTreeEnabled(), files().length))
|
||||||
const [singlePatch, setSinglePatch] = createSignal(props.api.kv.get<boolean>(KV_SINGLE_PATCH, false) === true)
|
const [singlePatch, setSinglePatch] = createSignal(config.data.diffs?.single ?? false)
|
||||||
const patchPaneWidth = createMemo(() => dimensions().width - (showFileTree() ? 33 : 0) - 4)
|
const patchPaneWidth = createMemo(() => dimensions().width - (showFileTree() ? 33 : 0) - 4)
|
||||||
const patchLeftBorder = createMemo<BorderSides[]>(() => (showFileTree() ? ["left"] : []))
|
const patchLeftBorder = createMemo<BorderSides[]>(() => (showFileTree() ? ["left"] : []))
|
||||||
const splitAvailable = createMemo(() => patchPaneWidth() >= MIN_SPLIT_WIDTH)
|
const splitAvailable = createMemo(() => patchPaneWidth() >= MIN_SPLIT_WIDTH)
|
||||||
@@ -148,7 +145,7 @@ function DiffViewer(props: { api: TuiPluginApi }) {
|
|||||||
if (props.api.tuiConfig.diffs?.view === "split") return "split"
|
if (props.api.tuiConfig.diffs?.view === "split") return "split"
|
||||||
return splitAvailable() ? "split" : "unified"
|
return splitAvailable() ? "split" : "unified"
|
||||||
})
|
})
|
||||||
const [viewOverride, setViewOverride] = createSignal<DiffView | undefined>(storedView(props.api.kv.get(KV_VIEW)))
|
const [viewOverride, setViewOverride] = createSignal<DiffView | undefined>(storedView(config.data.diffs?.view))
|
||||||
const view = createMemo(() => (splitAvailable() ? (viewOverride() ?? defaultView()) : "unified"))
|
const view = createMemo(() => (splitAvailable() ? (viewOverride() ?? defaultView()) : "unified"))
|
||||||
const fileTree = createMemo(() => buildFileTree(files()))
|
const fileTree = createMemo(() => buildFileTree(files()))
|
||||||
const [expandedFileNodes, setExpandedFileNodes] = createSignal<ReadonlySet<number>>(new Set())
|
const [expandedFileNodes, setExpandedFileNodes] = createSignal<ReadonlySet<number>>(new Set())
|
||||||
@@ -623,23 +620,33 @@ function DiffViewer(props: { api: TuiPluginApi }) {
|
|||||||
name: "diff.toggle_file_tree",
|
name: "diff.toggle_file_tree",
|
||||||
title: "Toggle diff viewer file tree",
|
title: "Toggle diff viewer file tree",
|
||||||
category: "VCS",
|
category: "VCS",
|
||||||
|
hidden: true,
|
||||||
run() {
|
run() {
|
||||||
const next = !fileTreeEnabled()
|
const next = !fileTreeEnabled()
|
||||||
if (!next) setFocus("patches")
|
if (!next) setFocus("patches")
|
||||||
setFileTreeEnabled(next)
|
setFileTreeEnabled(next)
|
||||||
props.api.kv.set(KV_SHOW_FILE_TREE, next)
|
void config
|
||||||
|
.update((draft) => {
|
||||||
|
draft.diffs = { ...draft.diffs, tree: next }
|
||||||
|
})
|
||||||
|
.catch(() => {})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "diff.single_patch",
|
name: "diff.single_patch",
|
||||||
title: "Toggle single patch view",
|
title: "Toggle single patch view",
|
||||||
category: "VCS",
|
category: "VCS",
|
||||||
|
hidden: true,
|
||||||
run() {
|
run() {
|
||||||
setSelectedHunk(undefined)
|
setSelectedHunk(undefined)
|
||||||
if (!singlePatch()) {
|
if (!singlePatch()) {
|
||||||
ensureHighlightedPatchFile()
|
ensureHighlightedPatchFile()
|
||||||
setSinglePatch(true)
|
setSinglePatch(true)
|
||||||
props.api.kv.set(KV_SINGLE_PATCH, true)
|
void config
|
||||||
|
.update((draft) => {
|
||||||
|
draft.diffs = { ...draft.diffs, single: true }
|
||||||
|
})
|
||||||
|
.catch(() => {})
|
||||||
scrollSinglePatchToTop()
|
scrollSinglePatchToTop()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -653,7 +660,11 @@ function DiffViewer(props: { api: TuiPluginApi }) {
|
|||||||
)
|
)
|
||||||
if (fileIndex !== undefined) selectPatchFile(fileIndex)
|
if (fileIndex !== undefined) selectPatchFile(fileIndex)
|
||||||
setSinglePatch(false)
|
setSinglePatch(false)
|
||||||
props.api.kv.set(KV_SINGLE_PATCH, false)
|
void config
|
||||||
|
.update((draft) => {
|
||||||
|
draft.diffs = { ...draft.diffs, single: false }
|
||||||
|
})
|
||||||
|
.catch(() => {})
|
||||||
if (fileIndex !== undefined) scrollToPatchFileIndexAfterRender(fileIndex)
|
if (fileIndex !== undefined) scrollToPatchFileIndexAfterRender(fileIndex)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -669,12 +680,17 @@ function DiffViewer(props: { api: TuiPluginApi }) {
|
|||||||
name: "diff.toggle_view",
|
name: "diff.toggle_view",
|
||||||
title: "Toggle diff viewer split or unified view",
|
title: "Toggle diff viewer split or unified view",
|
||||||
category: "VCS",
|
category: "VCS",
|
||||||
|
hidden: true,
|
||||||
run() {
|
run() {
|
||||||
if (!splitAvailable()) return
|
if (!splitAvailable()) return
|
||||||
setSelectedHunk(undefined)
|
setSelectedHunk(undefined)
|
||||||
const next = view() === "split" ? "unified" : "split"
|
const next = view() === "split" ? "unified" : "split"
|
||||||
setViewOverride(next)
|
setViewOverride(next)
|
||||||
props.api.kv.set(KV_VIEW, next)
|
void config
|
||||||
|
.update((draft) => {
|
||||||
|
draft.diffs = { ...draft.diffs, view: next }
|
||||||
|
})
|
||||||
|
.catch(() => {})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -22,8 +22,6 @@ const command = {
|
|||||||
} as const
|
} as const
|
||||||
|
|
||||||
const LAYER_PRIORITY = 900
|
const LAYER_PRIORITY = 900
|
||||||
const KV_LAYOUT = "which_key_layout"
|
|
||||||
const KV_PENDING_PREVIEW = "which_key_pending_preview"
|
|
||||||
const toggleCommands = [command.toggle, command.toggleLayout, command.togglePending] as const
|
const toggleCommands = [command.toggle, command.toggleLayout, command.togglePending] as const
|
||||||
const scrollCommands = [
|
const scrollCommands = [
|
||||||
command.scrollUp,
|
command.scrollUp,
|
||||||
@@ -531,8 +529,8 @@ function WhichKeyPanel(props: {
|
|||||||
|
|
||||||
const tui: TuiPlugin = async (api) => {
|
const tui: TuiPlugin = async (api) => {
|
||||||
const [pinned, setPinned] = createSignal(false)
|
const [pinned, setPinned] = createSignal(false)
|
||||||
const [mode, setMode] = createSignal(layout(api.kv.get(KV_LAYOUT, "dock")))
|
const [mode, setMode] = createSignal(layout("dock"))
|
||||||
const [pendingPreview, setPendingPreview] = createSignal(api.kv.get(KV_PENDING_PREVIEW, false))
|
const [pendingPreview, setPendingPreview] = createSignal(false)
|
||||||
|
|
||||||
api.keymap.registerLayer({
|
api.keymap.registerLayer({
|
||||||
priority: LAYER_PRIORITY,
|
priority: LAYER_PRIORITY,
|
||||||
@@ -554,7 +552,6 @@ const tui: TuiPlugin = async (api) => {
|
|||||||
run() {
|
run() {
|
||||||
setMode((value) => {
|
setMode((value) => {
|
||||||
const next = value === "dock" ? "overlay" : "dock"
|
const next = value === "dock" ? "overlay" : "dock"
|
||||||
api.kv.set(KV_LAYOUT, next)
|
|
||||||
return next
|
return next
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@@ -566,7 +563,6 @@ const tui: TuiPlugin = async (api) => {
|
|||||||
category: "System",
|
category: "System",
|
||||||
run() {
|
run() {
|
||||||
setPendingPreview((value) => {
|
setPendingPreview((value) => {
|
||||||
api.kv.set(KV_PENDING_PREVIEW, !value)
|
|
||||||
return !value
|
return !value
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import type { useData } from "../context/data"
|
|||||||
import type { useTheme } from "../context/theme"
|
import type { useTheme } from "../context/theme"
|
||||||
import { Dialog as DialogUI, type useDialog } from "../ui/dialog"
|
import { Dialog as DialogUI, type useDialog } from "../ui/dialog"
|
||||||
import type { useOpencodeKeymap } from "../keymap"
|
import type { useOpencodeKeymap } from "../keymap"
|
||||||
import type { useKV } from "../context/kv"
|
|
||||||
import { DialogAlert } from "../ui/dialog-alert"
|
import { DialogAlert } from "../ui/dialog-alert"
|
||||||
import { DialogConfirm } from "../ui/dialog-confirm"
|
import { DialogConfirm } from "../ui/dialog-confirm"
|
||||||
import { DialogPrompt } from "../ui/dialog-prompt"
|
import { DialogPrompt } from "../ui/dialog-prompt"
|
||||||
@@ -26,7 +25,6 @@ type Input = {
|
|||||||
tuiConfig: Config.Resolved
|
tuiConfig: Config.Resolved
|
||||||
dialog: ReturnType<typeof useDialog>
|
dialog: ReturnType<typeof useDialog>
|
||||||
keymap: ReturnType<typeof useOpencodeKeymap>
|
keymap: ReturnType<typeof useOpencodeKeymap>
|
||||||
kv: ReturnType<typeof useKV>
|
|
||||||
route: ReturnType<typeof useRoute>
|
route: ReturnType<typeof useRoute>
|
||||||
routes: PluginRoutes
|
routes: PluginRoutes
|
||||||
event: ReturnType<typeof useEvent>
|
event: ReturnType<typeof useEvent>
|
||||||
@@ -291,17 +289,6 @@ export function createTuiApiAdapters(input: Input): Omit<TuiPluginApi, "lifecycl
|
|||||||
get tuiConfig() {
|
get tuiConfig() {
|
||||||
return input.tuiConfig
|
return input.tuiConfig
|
||||||
},
|
},
|
||||||
kv: {
|
|
||||||
get(key, fallback) {
|
|
||||||
return input.kv.get(key, fallback)
|
|
||||||
},
|
|
||||||
set(key, value) {
|
|
||||||
input.kv.set(key, value)
|
|
||||||
},
|
|
||||||
get ready() {
|
|
||||||
return input.kv.ready
|
|
||||||
},
|
|
||||||
},
|
|
||||||
state: stateApi(input.sync, input.data),
|
state: stateApi(input.sync, input.data),
|
||||||
get client() {
|
get client() {
|
||||||
return input.sdk.client
|
return input.sdk.client
|
||||||
|
|||||||
@@ -54,7 +54,6 @@ import { filetype } from "../../util/filetype"
|
|||||||
import parsers from "../../parsers-config"
|
import parsers from "../../parsers-config"
|
||||||
import { errorMessage } from "../../util/error"
|
import { errorMessage } from "../../util/error"
|
||||||
import { Toast, useToast } from "../../ui/toast"
|
import { Toast, useToast } from "../../ui/toast"
|
||||||
import { useKV } from "../../context/kv.tsx"
|
|
||||||
import stripAnsi from "strip-ansi"
|
import stripAnsi from "strip-ansi"
|
||||||
import { usePromptRef } from "../../context/prompt"
|
import { usePromptRef } from "../../context/prompt"
|
||||||
import { useEpilogue } from "../../context/epilogue"
|
import { useEpilogue } from "../../context/epilogue"
|
||||||
@@ -66,7 +65,7 @@ import { DialogExportResult } from "../../ui/dialog-export-result"
|
|||||||
import { sessionEpilogue } from "../../util/presentation"
|
import { sessionEpilogue } from "../../util/presentation"
|
||||||
import { useConfig } from "../../config"
|
import { useConfig } from "../../config"
|
||||||
import { useClipboard } from "../../context/clipboard"
|
import { useClipboard } from "../../context/clipboard"
|
||||||
import { nextThinkingMode, reasoningSummary, useThinkingMode, type ThinkingMode } from "../../context/thinking"
|
import { nextThinkingMode, reasoningSummary, type ThinkingMode } from "../../context/thinking"
|
||||||
import { getScrollAcceleration } from "../../util/scroll"
|
import { getScrollAcceleration } from "../../util/scroll"
|
||||||
import { collapseToolOutput } from "../../util/collapse-tool-output"
|
import { collapseToolOutput } from "../../util/collapse-tool-output"
|
||||||
import { usePluginRuntime } from "../../plugin/runtime"
|
import { usePluginRuntime } from "../../plugin/runtime"
|
||||||
@@ -122,6 +121,7 @@ const context = createContext<{
|
|||||||
sessionID: string
|
sessionID: string
|
||||||
thinkingMode: () => ThinkingMode
|
thinkingMode: () => ThinkingMode
|
||||||
showThinking: () => boolean
|
showThinking: () => boolean
|
||||||
|
markdownMode: () => "source" | "rendered"
|
||||||
groupExploration: () => boolean
|
groupExploration: () => boolean
|
||||||
diffWrapMode: () => "word" | "none"
|
diffWrapMode: () => "word" | "none"
|
||||||
models: () => ModelInfo[]
|
models: () => ModelInfo[]
|
||||||
@@ -147,8 +147,8 @@ export function Session() {
|
|||||||
const data = useData()
|
const data = useData()
|
||||||
const project = useProject()
|
const project = useProject()
|
||||||
const paths = useTuiPaths()
|
const paths = useTuiPaths()
|
||||||
const config = useConfig().data
|
const configState = useConfig()
|
||||||
const kv = useKV()
|
const config = configState.data
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const promptRef = usePromptRef()
|
const promptRef = usePromptRef()
|
||||||
const session = createMemo(() => data.session.get(route.sessionID))
|
const session = createMemo(() => data.session.get(route.sessionID))
|
||||||
@@ -194,15 +194,14 @@ export function Session() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const dimensions = useTerminalDimensions()
|
const dimensions = useTerminalDimensions()
|
||||||
const [sidebar, setSidebar] = kv.signal<"auto" | "hide">("sidebar", "auto")
|
const sidebar = createMemo(() => config.session?.sidebar ?? "auto")
|
||||||
const [sidebarOpen, setSidebarOpen] = createSignal(false)
|
const [sidebarOpen, setSidebarOpen] = createSignal(false)
|
||||||
const thinking = useThinkingMode()
|
const thinkingMode = createMemo<ThinkingMode>(() => config.session?.thinking ?? "hide")
|
||||||
const thinkingMode = thinking.mode
|
|
||||||
const showThinking = createMemo(() => true)
|
const showThinking = createMemo(() => true)
|
||||||
const [showScrollbar, setShowScrollbar] = kv.signal("scrollbar_visible", false)
|
const showScrollbar = createMemo(() => config.session?.scrollbar ?? false)
|
||||||
const [diffWrapMode] = kv.signal<"word" | "none">("diff_wrap_mode", "word")
|
const markdownMode = createMemo(() => config.session?.markdown ?? "rendered")
|
||||||
const [_animationsEnabled, _setAnimationsEnabled] = kv.signal("animations_enabled", true)
|
const diffWrapMode = createMemo(() => config.diffs?.wrap ?? "word")
|
||||||
const [groupExploration, setGroupExploration] = kv.signal("exploration_grouping", true)
|
const groupExploration = createMemo(() => config.session?.grouping !== "none")
|
||||||
|
|
||||||
const wide = createMemo(() => dimensions().width > 120)
|
const wide = createMemo(() => dimensions().width > 120)
|
||||||
const sidebarVisible = createMemo(() => {
|
const sidebarVisible = createMemo(() => {
|
||||||
@@ -462,7 +461,11 @@ export function Session() {
|
|||||||
run: () => {
|
run: () => {
|
||||||
batch(() => {
|
batch(() => {
|
||||||
const isVisible = sidebarVisible()
|
const isVisible = sidebarVisible()
|
||||||
setSidebar(() => (isVisible ? "hide" : "auto"))
|
void configState
|
||||||
|
.update((draft) => {
|
||||||
|
draft.session = { ...draft.session, sidebar: isVisible ? "hide" : "auto" }
|
||||||
|
})
|
||||||
|
.catch(toast.error)
|
||||||
setSidebarOpen(!isVisible)
|
setSidebarOpen(!isVisible)
|
||||||
})
|
})
|
||||||
dialog.clear()
|
dialog.clear()
|
||||||
@@ -476,12 +479,17 @@ export function Session() {
|
|||||||
})(),
|
})(),
|
||||||
value: "session.toggle.thinking",
|
value: "session.toggle.thinking",
|
||||||
category: "Session",
|
category: "Session",
|
||||||
|
hidden: true,
|
||||||
slash: {
|
slash: {
|
||||||
name: "thinking",
|
name: "thinking",
|
||||||
aliases: ["toggle-thinking"],
|
aliases: ["toggle-thinking"],
|
||||||
},
|
},
|
||||||
run: () => {
|
run: () => {
|
||||||
thinking.set(nextThinkingMode(thinkingMode()))
|
void configState
|
||||||
|
.update((draft) => {
|
||||||
|
draft.session = { ...draft.session, thinking: nextThinkingMode(thinkingMode()) }
|
||||||
|
})
|
||||||
|
.catch(toast.error)
|
||||||
dialog.clear()
|
dialog.clear()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -489,8 +497,13 @@ export function Session() {
|
|||||||
title: "Toggle session scrollbar",
|
title: "Toggle session scrollbar",
|
||||||
value: "session.toggle.scrollbar",
|
value: "session.toggle.scrollbar",
|
||||||
category: "Session",
|
category: "Session",
|
||||||
|
hidden: true,
|
||||||
run: () => {
|
run: () => {
|
||||||
setShowScrollbar((prev) => !prev)
|
void configState
|
||||||
|
.update((draft) => {
|
||||||
|
draft.session = { ...draft.session, scrollbar: !showScrollbar() }
|
||||||
|
})
|
||||||
|
.catch(toast.error)
|
||||||
dialog.clear()
|
dialog.clear()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -498,8 +511,13 @@ export function Session() {
|
|||||||
title: groupExploration() ? "Show tool calls individually" : "Group related tool calls",
|
title: groupExploration() ? "Show tool calls individually" : "Group related tool calls",
|
||||||
value: "session.toggle.exploration_grouping",
|
value: "session.toggle.exploration_grouping",
|
||||||
category: "Session",
|
category: "Session",
|
||||||
|
hidden: true,
|
||||||
run: () => {
|
run: () => {
|
||||||
setGroupExploration((prev) => !prev)
|
void configState
|
||||||
|
.update((draft) => {
|
||||||
|
draft.session = { ...draft.session, grouping: groupExploration() ? "none" : "auto" }
|
||||||
|
})
|
||||||
|
.catch(toast.error)
|
||||||
dialog.clear()
|
dialog.clear()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -855,6 +873,7 @@ export function Session() {
|
|||||||
sessionID: route.sessionID,
|
sessionID: route.sessionID,
|
||||||
thinkingMode,
|
thinkingMode,
|
||||||
showThinking,
|
showThinking,
|
||||||
|
markdownMode,
|
||||||
groupExploration,
|
groupExploration,
|
||||||
diffWrapMode,
|
diffWrapMode,
|
||||||
models,
|
models,
|
||||||
@@ -1287,7 +1306,6 @@ function SessionSkillMessage(props: { message: Extract<SessionMessageInfo, { typ
|
|||||||
|
|
||||||
function CompactionMessage(props: { message: Extract<SessionMessageInfo, { type: "compaction" }> }) {
|
function CompactionMessage(props: { message: Extract<SessionMessageInfo, { type: "compaction" }> }) {
|
||||||
const ctx = use()
|
const ctx = use()
|
||||||
const kv = useKV()
|
|
||||||
const { theme, syntax } = useTheme()
|
const { theme, syntax } = useTheme()
|
||||||
const status = () => props.message.status
|
const status = () => props.message.status
|
||||||
const text = () => (props.message.status === "failed" ? props.message.error.message : props.message.summary)
|
const text = () => (props.message.status === "failed" ? props.message.error.message : props.message.summary)
|
||||||
@@ -1300,7 +1318,7 @@ function CompactionMessage(props: { message: Extract<SessionMessageInfo, { type:
|
|||||||
<box flexDirection="row" gap={1} paddingLeft={1} paddingRight={1}>
|
<box flexDirection="row" gap={1} paddingLeft={1} paddingRight={1}>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Match when={status() === "running"}>
|
<Match when={status() === "running"}>
|
||||||
<Show when={kv.get("animations_enabled", true)} fallback={<text fg={color()}>⋯</text>}>
|
<Show when={ctx.config.animations ?? true} fallback={<text fg={color()}>⋯</text>}>
|
||||||
<spinner frames={SPINNER_FRAMES} interval={80} color={color()} />
|
<spinner frames={SPINNER_FRAMES} interval={80} color={color()} />
|
||||||
</Show>
|
</Show>
|
||||||
</Match>
|
</Match>
|
||||||
@@ -1320,7 +1338,7 @@ function CompactionMessage(props: { message: Extract<SessionMessageInfo, { type:
|
|||||||
internalBlockMode="top-level"
|
internalBlockMode="top-level"
|
||||||
content={content()}
|
content={content()}
|
||||||
tableOptions={{ style: "grid" }}
|
tableOptions={{ style: "grid" }}
|
||||||
conceal={false}
|
conceal={ctx.markdownMode() === "rendered"}
|
||||||
fg={theme.markdownText}
|
fg={theme.markdownText}
|
||||||
bg={theme.background}
|
bg={theme.background}
|
||||||
/>
|
/>
|
||||||
@@ -1745,7 +1763,7 @@ function ReasoningPart(props: {
|
|||||||
streaming={true}
|
streaming={true}
|
||||||
syntaxStyle={syntax()}
|
syntaxStyle={syntax()}
|
||||||
content={summary().body}
|
content={summary().body}
|
||||||
conceal={false}
|
conceal={ctx.markdownMode() === "rendered"}
|
||||||
fg={theme.textMuted}
|
fg={theme.textMuted}
|
||||||
/>
|
/>
|
||||||
</box>
|
</box>
|
||||||
@@ -1811,7 +1829,7 @@ function TextPart(props: { last: boolean; part: SessionMessageAssistantText }) {
|
|||||||
internalBlockMode="top-level"
|
internalBlockMode="top-level"
|
||||||
content={props.part.text.trim()}
|
content={props.part.text.trim()}
|
||||||
tableOptions={{ style: "grid" }}
|
tableOptions={{ style: "grid" }}
|
||||||
conceal={false}
|
conceal={ctx.markdownMode() === "rendered"}
|
||||||
fg={theme.markdownText}
|
fg={theme.markdownText}
|
||||||
bg={theme.background}
|
bg={theme.background}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
import { testRender } from "@opentui/solid"
|
import { testRender } from "@opentui/solid"
|
||||||
import { onMount } from "solid-js"
|
import { onMount } from "solid-js"
|
||||||
import { ArgsProvider } from "../../../../src/context/args"
|
import { ArgsProvider } from "../../../../src/context/args"
|
||||||
import { KVProvider, useKV } from "../../../../src/context/kv"
|
|
||||||
import { ProjectProvider, useProject } from "../../../../src/context/project"
|
import { ProjectProvider, useProject } from "../../../../src/context/project"
|
||||||
import { SDKProvider } from "../../../../src/context/sdk"
|
import { SDKProvider } from "../../../../src/context/sdk"
|
||||||
import { SyncProvider, useSync } from "../../../../src/context/sync"
|
import { SyncProvider, useSync } from "../../../../src/context/sync"
|
||||||
@@ -20,25 +19,23 @@ export async function wait(fn: () => boolean, timeout = 2000) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Ctx = { kv: ReturnType<typeof useKV>; project: ReturnType<typeof useProject>; sync: ReturnType<typeof useSync> }
|
type Ctx = { project: ReturnType<typeof useProject>; sync: ReturnType<typeof useSync> }
|
||||||
|
|
||||||
export async function mount(override?: FetchHandler, state?: string) {
|
export async function mount(override?: FetchHandler, state?: string) {
|
||||||
const events = createEventStream()
|
const events = createEventStream()
|
||||||
const calls = createFetch(override, events)
|
const calls = createFetch(override, events)
|
||||||
let sync!: ReturnType<typeof useSync>
|
let sync!: ReturnType<typeof useSync>
|
||||||
let project!: ReturnType<typeof useProject>
|
let project!: ReturnType<typeof useProject>
|
||||||
let kv!: ReturnType<typeof useKV>
|
|
||||||
let done!: () => void
|
let done!: () => void
|
||||||
const ready = new Promise<void>((resolve) => {
|
const ready = new Promise<void>((resolve) => {
|
||||||
done = resolve
|
done = resolve
|
||||||
})
|
})
|
||||||
|
|
||||||
function Probe() {
|
function Probe() {
|
||||||
const ctx: Ctx = { kv: useKV(), project: useProject(), sync: useSync() }
|
const ctx: Ctx = { project: useProject(), sync: useSync() }
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
sync = ctx.sync
|
sync = ctx.sync
|
||||||
project = ctx.project
|
project = ctx.project
|
||||||
kv = ctx.kv
|
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
return <box />
|
return <box />
|
||||||
@@ -47,24 +44,22 @@ export async function mount(override?: FetchHandler, state?: string) {
|
|||||||
const app = await testRender(() => (
|
const app = await testRender(() => (
|
||||||
<TestTuiContexts paths={state ? { state } : undefined}>
|
<TestTuiContexts paths={state ? { state } : undefined}>
|
||||||
<ArgsProvider>
|
<ArgsProvider>
|
||||||
<KVProvider>
|
<SDKProvider client={createClient(calls.fetch)} api={createApi(calls.fetch)}>
|
||||||
<SDKProvider client={createClient(calls.fetch)} api={createApi(calls.fetch)}>
|
<PermissionProvider>
|
||||||
<PermissionProvider>
|
<ProjectProvider>
|
||||||
<ProjectProvider>
|
<ExitProvider exit={() => {}}>
|
||||||
<ExitProvider exit={() => {}}>
|
<SyncProvider>
|
||||||
<SyncProvider>
|
<Probe />
|
||||||
<Probe />
|
</SyncProvider>
|
||||||
</SyncProvider>
|
</ExitProvider>
|
||||||
</ExitProvider>
|
</ProjectProvider>
|
||||||
</ProjectProvider>
|
</PermissionProvider>
|
||||||
</PermissionProvider>
|
</SDKProvider>
|
||||||
</SDKProvider>
|
|
||||||
</KVProvider>
|
|
||||||
</ArgsProvider>
|
</ArgsProvider>
|
||||||
</TestTuiContexts>
|
</TestTuiContexts>
|
||||||
))
|
))
|
||||||
|
|
||||||
await ready
|
await ready
|
||||||
await wait(() => sync.status === "complete")
|
await wait(() => sync.status === "complete")
|
||||||
return { app, emit: events.emit, kv, project, sync, session: calls.session }
|
return { app, emit: events.emit, project, sync, session: calls.session }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,12 +26,10 @@ async function mountPrompt(input: {
|
|||||||
}) {
|
}) {
|
||||||
const state = path.join(input.root, "state")
|
const state = path.join(input.root, "state")
|
||||||
await mkdir(state, { recursive: true })
|
await mkdir(state, { recursive: true })
|
||||||
await Bun.write(path.join(state, "kv.json"), "{}")
|
|
||||||
|
|
||||||
const [
|
const [
|
||||||
{ DialogProvider },
|
{ DialogProvider },
|
||||||
{ DialogPrompt },
|
{ DialogPrompt },
|
||||||
{ KVProvider },
|
|
||||||
{ ThemeProvider },
|
{ ThemeProvider },
|
||||||
{ ConfigProvider },
|
{ ConfigProvider },
|
||||||
{ ToastProvider },
|
{ ToastProvider },
|
||||||
@@ -39,7 +37,6 @@ async function mountPrompt(input: {
|
|||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
import("../../../src/ui/dialog"),
|
import("../../../src/ui/dialog"),
|
||||||
import("../../../src/ui/dialog-prompt"),
|
import("../../../src/ui/dialog-prompt"),
|
||||||
import("../../../src/context/kv"),
|
|
||||||
import("../../../src/context/theme"),
|
import("../../../src/context/theme"),
|
||||||
import("../../../src/config"),
|
import("../../../src/config"),
|
||||||
import("../../../src/ui/toast"),
|
import("../../../src/ui/toast"),
|
||||||
@@ -67,15 +64,13 @@ async function mountPrompt(input: {
|
|||||||
>
|
>
|
||||||
<OpencodeKeymapProvider keymap={keymap}>
|
<OpencodeKeymapProvider keymap={keymap}>
|
||||||
<ConfigProvider config={resolvedConfig}>
|
<ConfigProvider config={resolvedConfig}>
|
||||||
<KVProvider>
|
<ThemeProvider mode="dark">
|
||||||
<ThemeProvider mode="dark">
|
<ToastProvider>
|
||||||
<ToastProvider>
|
<DialogProvider>
|
||||||
<DialogProvider>
|
<DialogPrompt title="Rename Session" value="draft" onConfirm={input.onConfirm} />
|
||||||
<DialogPrompt title="Rename Session" value="draft" onConfirm={input.onConfirm} />
|
</DialogProvider>
|
||||||
</DialogProvider>
|
</ToastProvider>
|
||||||
</ToastProvider>
|
</ThemeProvider>
|
||||||
</ThemeProvider>
|
|
||||||
</KVProvider>
|
|
||||||
</ConfigProvider>
|
</ConfigProvider>
|
||||||
</OpencodeKeymapProvider>
|
</OpencodeKeymapProvider>
|
||||||
</TestTuiContexts>
|
</TestTuiContexts>
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { RGBA } from "@opentui/core"
|
|||||||
import { testRender } from "@opentui/solid"
|
import { testRender } from "@opentui/solid"
|
||||||
import type { JSX } from "solid-js"
|
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 { ThemeProvider } from "../../../src/context/theme"
|
import { ThemeProvider } from "../../../src/context/theme"
|
||||||
import { ConfigProvider } from "../../../src/config"
|
import { ConfigProvider } from "../../../src/config"
|
||||||
import { DiffViewerFileTree } from "../../../src/feature-plugins/system/diff-viewer-file-tree"
|
import { DiffViewerFileTree } from "../../../src/feature-plugins/system/diff-viewer-file-tree"
|
||||||
@@ -182,9 +181,7 @@ function withTheme(component: () => JSX.Element) {
|
|||||||
return (
|
return (
|
||||||
<TestTuiContexts>
|
<TestTuiContexts>
|
||||||
<ConfigProvider config={createTuiResolvedConfig()}>
|
<ConfigProvider config={createTuiResolvedConfig()}>
|
||||||
<KVProvider>
|
<ThemeProvider mode="dark">{component()}</ThemeProvider>
|
||||||
<ThemeProvider mode="dark">{component()}</ThemeProvider>
|
|
||||||
</KVProvider>
|
|
||||||
</ConfigProvider>
|
</ConfigProvider>
|
||||||
</TestTuiContexts>
|
</TestTuiContexts>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { DiffRenderable, type Renderable, ScrollBoxRenderable } from "@opentui/c
|
|||||||
import { testRender, useRenderer } from "@opentui/solid"
|
import { testRender, useRenderer } from "@opentui/solid"
|
||||||
import type { TuiPluginApi, TuiPluginMeta, TuiRouteCurrent, TuiRouteDefinition } from "@opencode-ai/plugin/tui"
|
import type { TuiPluginApi, TuiPluginMeta, TuiRouteCurrent, TuiRouteDefinition } from "@opencode-ai/plugin/tui"
|
||||||
import type { Session } from "@opencode-ai/sdk/v2"
|
import type { Session } from "@opencode-ai/sdk/v2"
|
||||||
import { KVProvider } from "../../../src/context/kv"
|
|
||||||
import { ThemeProvider } from "../../../src/context/theme"
|
import { ThemeProvider } from "../../../src/context/theme"
|
||||||
import { ConfigProvider } from "../../../src/config"
|
import { ConfigProvider } from "../../../src/config"
|
||||||
import { SDKProvider } from "../../../src/context/sdk"
|
import { SDKProvider } from "../../../src/context/sdk"
|
||||||
@@ -174,11 +173,9 @@ async function renderDiffViewer(vcsDiff: unknown[], height = 20, initialRoute?:
|
|||||||
<SDKProvider client={createClient(transport.fetch)} api={createApi(transport.fetch)}>
|
<SDKProvider client={createClient(transport.fetch)} api={createApi(transport.fetch)}>
|
||||||
<OpencodeKeymapProvider keymap={keymap}>
|
<OpencodeKeymapProvider keymap={keymap}>
|
||||||
<ConfigProvider config={config}>
|
<ConfigProvider config={config}>
|
||||||
<KVProvider>
|
<ThemeProvider mode="dark">
|
||||||
<ThemeProvider mode="dark">
|
{renderDiff?.({ params: "params" in current ? current.params : undefined })}
|
||||||
{renderDiff?.({ params: "params" in current ? current.params : undefined })}
|
</ThemeProvider>
|
||||||
</ThemeProvider>
|
|
||||||
</KVProvider>
|
|
||||||
</ConfigProvider>
|
</ConfigProvider>
|
||||||
</OpencodeKeymapProvider>
|
</OpencodeKeymapProvider>
|
||||||
</SDKProvider>
|
</SDKProvider>
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import path from "node:path"
|
|||||||
import { onCleanup } from "solid-js"
|
import { onCleanup } from "solid-js"
|
||||||
import { ClipboardProvider } from "../../../src/context/clipboard"
|
import { ClipboardProvider } from "../../../src/context/clipboard"
|
||||||
import type { FormWithLocation } from "../../../src/context/data"
|
import type { FormWithLocation } from "../../../src/context/data"
|
||||||
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 { ConfigProvider } from "../../../src/config"
|
import { ConfigProvider } from "../../../src/config"
|
||||||
@@ -21,7 +20,6 @@ import { createApi, createClient, createEventStream, createFetch } from "../../f
|
|||||||
async function mountForm(root: string, width = 80) {
|
async function mountForm(root: string, width = 80) {
|
||||||
const state = path.join(root, "state")
|
const state = path.join(root, "state")
|
||||||
await mkdir(state, { recursive: true })
|
await mkdir(state, { recursive: true })
|
||||||
await Bun.write(path.join(state, "kv.json"), "{}")
|
|
||||||
|
|
||||||
const replies: unknown[] = []
|
const replies: unknown[] = []
|
||||||
const copied: string[] = []
|
const copied: string[] = []
|
||||||
@@ -78,13 +76,11 @@ async function mountForm(root: string, width = 80) {
|
|||||||
<OpencodeKeymapProvider keymap={keymap}>
|
<OpencodeKeymapProvider keymap={keymap}>
|
||||||
<ConfigProvider config={config}>
|
<ConfigProvider config={config}>
|
||||||
<SDKProvider client={createClient(transport.fetch)} api={createApi(transport.fetch)}>
|
<SDKProvider client={createClient(transport.fetch)} api={createApi(transport.fetch)}>
|
||||||
<KVProvider>
|
<ThemeProvider mode="dark" source={{ discover: () => Promise.resolve({}) }}>
|
||||||
<ThemeProvider mode="dark" source={{ discover: () => Promise.resolve({}) }}>
|
<ToastProvider>
|
||||||
<ToastProvider>
|
<FormPrompt form={form} />
|
||||||
<FormPrompt form={form} />
|
</ToastProvider>
|
||||||
</ToastProvider>
|
</ThemeProvider>
|
||||||
</ThemeProvider>
|
|
||||||
</KVProvider>
|
|
||||||
</SDKProvider>
|
</SDKProvider>
|
||||||
</ConfigProvider>
|
</ConfigProvider>
|
||||||
</OpencodeKeymapProvider>
|
</OpencodeKeymapProvider>
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ type Opts = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function createTuiPluginApi(opts: Opts = {}) {
|
export function createTuiPluginApi(opts: Opts = {}) {
|
||||||
const values = new Map<string, unknown>()
|
|
||||||
const color = RGBA.fromInts(200, 200, 200)
|
const color = RGBA.fromInts(200, 200, 200)
|
||||||
const dialog = { clear() {}, replace() {}, setSize() {}, size: "medium" as const, depth: 0, open: false }
|
const dialog = { clear() {}, replace() {}, setSize() {}, size: "medium" as const, depth: 0, open: false }
|
||||||
return {
|
return {
|
||||||
@@ -19,15 +18,6 @@ export function createTuiPluginApi(opts: Opts = {}) {
|
|||||||
client: opts.client,
|
client: opts.client,
|
||||||
event: opts.event,
|
event: opts.event,
|
||||||
keymap: opts.keymap,
|
keymap: opts.keymap,
|
||||||
kv: {
|
|
||||||
get(name: string, fallback?: unknown) {
|
|
||||||
return values.has(name) ? values.get(name) : fallback
|
|
||||||
},
|
|
||||||
set(name: string, value: unknown) {
|
|
||||||
values.set(name, value)
|
|
||||||
},
|
|
||||||
ready: true,
|
|
||||||
},
|
|
||||||
state: { session: { get: () => undefined, ...opts.state?.session } },
|
state: { session: { get: () => undefined, ...opts.state?.session } },
|
||||||
theme: { current: new Proxy({}, { get: () => color }) },
|
theme: { current: new Proxy({}, { get: () => color }) },
|
||||||
tuiConfig: createTuiResolvedConfig(),
|
tuiConfig: createTuiResolvedConfig(),
|
||||||
|
|||||||
Reference in New Issue
Block a user