Skip to content
This repository was archived by the owner on Apr 1, 2026. It is now read-only.

Commit 2fe7d13

Browse files
authored
Add formatter status display to TUI status dialog (anomalyco#3701)
1 parent 1bc3c98 commit 2fe7d13

6 files changed

Lines changed: 128 additions & 1 deletion

File tree

packages/opencode/src/cli/cmd/tui/component/dialog-status.tsx

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
import { TextAttributes } from "@opentui/core"
22
import { useTheme } from "../context/theme"
33
import { useSync } from "@tui/context/sync"
4-
import { For, Match, Switch, Show } from "solid-js"
4+
import { For, Match, Switch, Show, createMemo } from "solid-js"
55

66
export type DialogStatusProps = {}
77

88
export function DialogStatus() {
99
const sync = useSync()
1010
const { theme } = useTheme()
1111

12+
const enabledFormatters = createMemo(() => sync.data.formatter.filter((f) => f.enabled))
13+
1214
return (
1315
<box paddingLeft={2} paddingRight={2} gap={1} paddingBottom={1}>
1416
<box flexDirection="row" justifyContent="space-between">
@@ -73,6 +75,28 @@ export function DialogStatus() {
7375
</For>
7476
</box>
7577
)}
78+
<Show when={enabledFormatters().length > 0} fallback={<text>No Formatters</text>}>
79+
<box>
80+
<text>{enabledFormatters().length} Formatters</text>
81+
<For each={enabledFormatters()}>
82+
{(item) => (
83+
<box flexDirection="row" gap={1}>
84+
<text
85+
flexShrink={0}
86+
style={{
87+
fg: theme.success,
88+
}}
89+
>
90+
91+
</text>
92+
<text wrapMode="word">
93+
<b>{item.name}</b>
94+
</text>
95+
</box>
96+
)}
97+
</For>
98+
</box>
99+
</Show>
76100
</box>
77101
)
78102
}

packages/opencode/src/cli/cmd/tui/context/sync.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import type {
1010
Permission,
1111
LspStatus,
1212
McpStatus,
13+
FormatterStatus,
1314
} from "@opencode-ai/sdk"
1415
import { createStore, produce, reconcile } from "solid-js/store"
1516
import { useSDK } from "@tui/context/sdk"
@@ -42,6 +43,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
4243
mcp: {
4344
[key: string]: McpStatus
4445
}
46+
formatter: FormatterStatus[]
4547
}>({
4648
config: {},
4749
ready: false,
@@ -55,6 +57,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
5557
part: {},
5658
lsp: [],
5759
mcp: {},
60+
formatter: [],
5861
})
5962

6063
const sdk = useSDK()
@@ -220,6 +223,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
220223
sdk.client.command.list().then((x) => setStore("command", x.data ?? [])),
221224
sdk.client.lsp.status().then((x) => setStore("lsp", x.data!)),
222225
sdk.client.mcp.status().then((x) => setStore("mcp", x.data!)),
226+
sdk.client.formatter.status().then((x) => setStore("formatter", x.data!)),
223227
])
224228

225229
const result = {

packages/opencode/src/format/index.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Bus } from "../bus"
22
import { File } from "../file"
33
import { Log } from "../util/log"
44
import path from "path"
5+
import z from "zod"
56

67
import * as Formatter from "./formatter"
78
import { Config } from "../config/config"
@@ -11,6 +12,17 @@ import { Instance } from "../project/instance"
1112
export namespace Format {
1213
const log = Log.create({ service: "format" })
1314

15+
export const Status = z
16+
.object({
17+
name: z.string(),
18+
extensions: z.string().array(),
19+
enabled: z.boolean(),
20+
})
21+
.meta({
22+
ref: "FormatterStatus",
23+
})
24+
export type Status = z.infer<typeof Status>
25+
1426
const state = Instance.state(async () => {
1527
const enabled: Record<string, boolean> = {}
1628
const cfg = await Config.get()
@@ -62,6 +74,20 @@ export namespace Format {
6274
return result
6375
}
6476

77+
export async function status() {
78+
const s = await state()
79+
const result: Status[] = []
80+
for (const formatter of Object.values(s.formatters)) {
81+
const enabled = await isEnabled(formatter)
82+
result.push({
83+
name: formatter.name,
84+
extensions: formatter.extensions,
85+
enabled,
86+
})
87+
}
88+
return result
89+
}
90+
6591
export function init() {
6692
log.info("init")
6793
Bus.subscribe(File.Event.Edited, async (payload) => {

packages/opencode/src/server/server.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { Ripgrep } from "../file/ripgrep"
2020
import { Config } from "../config/config"
2121
import { File } from "../file"
2222
import { LSP } from "../lsp"
23+
import { Format } from "../format"
2324
import { MessageV2 } from "../session/message-v2"
2425
import { TuiRoute } from "./tui"
2526
import { Permission } from "../permission"
@@ -1336,6 +1337,26 @@ export namespace Server {
13361337
return c.json(await LSP.status())
13371338
},
13381339
)
1340+
.get(
1341+
"/formatter",
1342+
describeRoute({
1343+
description: "Get formatter status",
1344+
operationId: "formatter.status",
1345+
responses: {
1346+
200: {
1347+
description: "Formatter status",
1348+
content: {
1349+
"application/json": {
1350+
schema: resolver(Format.Status.array()),
1351+
},
1352+
},
1353+
},
1354+
},
1355+
}),
1356+
async (c) => {
1357+
return c.json(await Format.status())
1358+
},
1359+
)
13391360
.post(
13401361
"/tui/append-prompt",
13411362
describeRoute({

packages/sdk/js/src/gen/sdk.gen.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ import type {
107107
McpStatusResponses,
108108
LspStatusData,
109109
LspStatusResponses,
110+
FormatterStatusData,
111+
FormatterStatusResponses,
110112
TuiAppendPromptData,
111113
TuiAppendPromptResponses,
112114
TuiAppendPromptErrors,
@@ -773,6 +775,20 @@ class Lsp extends _HeyApiClient {
773775
}
774776
}
775777

778+
class Formatter extends _HeyApiClient {
779+
/**
780+
* Get formatter status
781+
*/
782+
public status<ThrowOnError extends boolean = false>(
783+
options?: Options<FormatterStatusData, ThrowOnError>,
784+
) {
785+
return (options?.client ?? this._client).get<FormatterStatusResponses, unknown, ThrowOnError>({
786+
url: "/formatter",
787+
...options,
788+
})
789+
}
790+
}
791+
776792
class Control extends _HeyApiClient {
777793
/**
778794
* Get the next TUI request from the queue
@@ -1023,6 +1039,7 @@ export class OpencodeClient extends _HeyApiClient {
10231039
app = new App({ client: this._client })
10241040
mcp = new Mcp({ client: this._client })
10251041
lsp = new Lsp({ client: this._client })
1042+
formatter = new Formatter({ client: this._client })
10261043
tui = new Tui({ client: this._client })
10271044
auth = new Auth({ client: this._client })
10281045
event = new Event({ client: this._client })

packages/sdk/js/src/gen/types.gen.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1070,6 +1070,12 @@ export type LspStatus = {
10701070
status: "connected" | "error"
10711071
}
10721072

1073+
export type FormatterStatus = {
1074+
name: string
1075+
extensions: Array<string>
1076+
enabled: boolean
1077+
}
1078+
10731079
export type EventTuiPromptAppend = {
10741080
type: "tui.prompt.append"
10751081
properties: {
@@ -1248,6 +1254,16 @@ export type EventTodoUpdated = {
12481254
}
12491255
}
12501256

1257+
export type EventCommandExecuted = {
1258+
type: "command.executed"
1259+
properties: {
1260+
name: string
1261+
sessionID: string
1262+
arguments: string
1263+
messageID: string
1264+
}
1265+
}
1266+
12511267
export type EventSessionIdle = {
12521268
type: "session.idle"
12531269
properties: {
@@ -1310,6 +1326,7 @@ export type Event =
13101326
| EventFileEdited
13111327
| EventFileWatcherUpdated
13121328
| EventTodoUpdated
1329+
| EventCommandExecuted
13131330
| EventSessionIdle
13141331
| EventSessionCreated
13151332
| EventSessionUpdated
@@ -2511,6 +2528,24 @@ export type LspStatusResponses = {
25112528

25122529
export type LspStatusResponse = LspStatusResponses[keyof LspStatusResponses]
25132530

2531+
export type FormatterStatusData = {
2532+
body?: never
2533+
path?: never
2534+
query?: {
2535+
directory?: string
2536+
}
2537+
url: "/formatter"
2538+
}
2539+
2540+
export type FormatterStatusResponses = {
2541+
/**
2542+
* Formatter status
2543+
*/
2544+
200: Array<FormatterStatus>
2545+
}
2546+
2547+
export type FormatterStatusResponse = FormatterStatusResponses[keyof FormatterStatusResponses]
2548+
25142549
export type TuiAppendPromptData = {
25152550
body?: {
25162551
text: string

0 commit comments

Comments
 (0)