Add formatter status display to TUI status dialog (#3701)

This commit is contained in:
Yuku Kotani
2025-11-02 00:14:39 +09:00
committed by GitHub
parent 1bc3c98ae7
commit 2fe7d13e69
6 changed files with 128 additions and 1 deletions

View File

@@ -1,7 +1,7 @@
import { TextAttributes } from "@opentui/core" import { TextAttributes } from "@opentui/core"
import { useTheme } from "../context/theme" import { useTheme } from "../context/theme"
import { useSync } from "@tui/context/sync" import { useSync } from "@tui/context/sync"
import { For, Match, Switch, Show } from "solid-js" import { For, Match, Switch, Show, createMemo } from "solid-js"
export type DialogStatusProps = {} export type DialogStatusProps = {}
@@ -9,6 +9,8 @@ export function DialogStatus() {
const sync = useSync() const sync = useSync()
const { theme } = useTheme() const { theme } = useTheme()
const enabledFormatters = createMemo(() => sync.data.formatter.filter((f) => f.enabled))
return ( return (
<box paddingLeft={2} paddingRight={2} gap={1} paddingBottom={1}> <box paddingLeft={2} paddingRight={2} gap={1} paddingBottom={1}>
<box flexDirection="row" justifyContent="space-between"> <box flexDirection="row" justifyContent="space-between">
@@ -73,6 +75,28 @@ export function DialogStatus() {
</For> </For>
</box> </box>
)} )}
<Show when={enabledFormatters().length > 0} fallback={<text>No Formatters</text>}>
<box>
<text>{enabledFormatters().length} Formatters</text>
<For each={enabledFormatters()}>
{(item) => (
<box flexDirection="row" gap={1}>
<text
flexShrink={0}
style={{
fg: theme.success,
}}
>
</text>
<text wrapMode="word">
<b>{item.name}</b>
</text>
</box>
)}
</For>
</box>
</Show>
</box> </box>
) )
} }

View File

@@ -10,6 +10,7 @@ import type {
Permission, Permission,
LspStatus, LspStatus,
McpStatus, McpStatus,
FormatterStatus,
} from "@opencode-ai/sdk" } from "@opencode-ai/sdk"
import { createStore, produce, reconcile } from "solid-js/store" import { createStore, produce, reconcile } from "solid-js/store"
import { useSDK } from "@tui/context/sdk" import { useSDK } from "@tui/context/sdk"
@@ -42,6 +43,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
mcp: { mcp: {
[key: string]: McpStatus [key: string]: McpStatus
} }
formatter: FormatterStatus[]
}>({ }>({
config: {}, config: {},
ready: false, ready: false,
@@ -55,6 +57,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
part: {}, part: {},
lsp: [], lsp: [],
mcp: {}, mcp: {},
formatter: [],
}) })
const sdk = useSDK() const sdk = useSDK()
@@ -220,6 +223,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
sdk.client.command.list().then((x) => setStore("command", x.data ?? [])), sdk.client.command.list().then((x) => setStore("command", x.data ?? [])),
sdk.client.lsp.status().then((x) => setStore("lsp", x.data!)), sdk.client.lsp.status().then((x) => setStore("lsp", x.data!)),
sdk.client.mcp.status().then((x) => setStore("mcp", x.data!)), sdk.client.mcp.status().then((x) => setStore("mcp", x.data!)),
sdk.client.formatter.status().then((x) => setStore("formatter", x.data!)),
]) ])
const result = { const result = {

View File

@@ -2,6 +2,7 @@ import { Bus } from "../bus"
import { File } from "../file" import { File } from "../file"
import { Log } from "../util/log" import { Log } from "../util/log"
import path from "path" import path from "path"
import z from "zod"
import * as Formatter from "./formatter" import * as Formatter from "./formatter"
import { Config } from "../config/config" import { Config } from "../config/config"
@@ -11,6 +12,17 @@ import { Instance } from "../project/instance"
export namespace Format { export namespace Format {
const log = Log.create({ service: "format" }) const log = Log.create({ service: "format" })
export const Status = z
.object({
name: z.string(),
extensions: z.string().array(),
enabled: z.boolean(),
})
.meta({
ref: "FormatterStatus",
})
export type Status = z.infer<typeof Status>
const state = Instance.state(async () => { const state = Instance.state(async () => {
const enabled: Record<string, boolean> = {} const enabled: Record<string, boolean> = {}
const cfg = await Config.get() const cfg = await Config.get()
@@ -62,6 +74,20 @@ export namespace Format {
return result return result
} }
export async function status() {
const s = await state()
const result: Status[] = []
for (const formatter of Object.values(s.formatters)) {
const enabled = await isEnabled(formatter)
result.push({
name: formatter.name,
extensions: formatter.extensions,
enabled,
})
}
return result
}
export function init() { export function init() {
log.info("init") log.info("init")
Bus.subscribe(File.Event.Edited, async (payload) => { Bus.subscribe(File.Event.Edited, async (payload) => {

View File

@@ -20,6 +20,7 @@ import { Ripgrep } from "../file/ripgrep"
import { Config } from "../config/config" import { Config } from "../config/config"
import { File } from "../file" import { File } from "../file"
import { LSP } from "../lsp" import { LSP } from "../lsp"
import { Format } from "../format"
import { MessageV2 } from "../session/message-v2" import { MessageV2 } from "../session/message-v2"
import { TuiRoute } from "./tui" import { TuiRoute } from "./tui"
import { Permission } from "../permission" import { Permission } from "../permission"
@@ -1336,6 +1337,26 @@ export namespace Server {
return c.json(await LSP.status()) return c.json(await LSP.status())
}, },
) )
.get(
"/formatter",
describeRoute({
description: "Get formatter status",
operationId: "formatter.status",
responses: {
200: {
description: "Formatter status",
content: {
"application/json": {
schema: resolver(Format.Status.array()),
},
},
},
},
}),
async (c) => {
return c.json(await Format.status())
},
)
.post( .post(
"/tui/append-prompt", "/tui/append-prompt",
describeRoute({ describeRoute({

View File

@@ -107,6 +107,8 @@ import type {
McpStatusResponses, McpStatusResponses,
LspStatusData, LspStatusData,
LspStatusResponses, LspStatusResponses,
FormatterStatusData,
FormatterStatusResponses,
TuiAppendPromptData, TuiAppendPromptData,
TuiAppendPromptResponses, TuiAppendPromptResponses,
TuiAppendPromptErrors, TuiAppendPromptErrors,
@@ -773,6 +775,20 @@ class Lsp extends _HeyApiClient {
} }
} }
class Formatter extends _HeyApiClient {
/**
* Get formatter status
*/
public status<ThrowOnError extends boolean = false>(
options?: Options<FormatterStatusData, ThrowOnError>,
) {
return (options?.client ?? this._client).get<FormatterStatusResponses, unknown, ThrowOnError>({
url: "/formatter",
...options,
})
}
}
class Control extends _HeyApiClient { class Control extends _HeyApiClient {
/** /**
* Get the next TUI request from the queue * Get the next TUI request from the queue
@@ -1023,6 +1039,7 @@ export class OpencodeClient extends _HeyApiClient {
app = new App({ client: this._client }) app = new App({ client: this._client })
mcp = new Mcp({ client: this._client }) mcp = new Mcp({ client: this._client })
lsp = new Lsp({ client: this._client }) lsp = new Lsp({ client: this._client })
formatter = new Formatter({ client: this._client })
tui = new Tui({ client: this._client }) tui = new Tui({ client: this._client })
auth = new Auth({ client: this._client }) auth = new Auth({ client: this._client })
event = new Event({ client: this._client }) event = new Event({ client: this._client })

View File

@@ -1070,6 +1070,12 @@ export type LspStatus = {
status: "connected" | "error" status: "connected" | "error"
} }
export type FormatterStatus = {
name: string
extensions: Array<string>
enabled: boolean
}
export type EventTuiPromptAppend = { export type EventTuiPromptAppend = {
type: "tui.prompt.append" type: "tui.prompt.append"
properties: { properties: {
@@ -1248,6 +1254,16 @@ export type EventTodoUpdated = {
} }
} }
export type EventCommandExecuted = {
type: "command.executed"
properties: {
name: string
sessionID: string
arguments: string
messageID: string
}
}
export type EventSessionIdle = { export type EventSessionIdle = {
type: "session.idle" type: "session.idle"
properties: { properties: {
@@ -1310,6 +1326,7 @@ export type Event =
| EventFileEdited | EventFileEdited
| EventFileWatcherUpdated | EventFileWatcherUpdated
| EventTodoUpdated | EventTodoUpdated
| EventCommandExecuted
| EventSessionIdle | EventSessionIdle
| EventSessionCreated | EventSessionCreated
| EventSessionUpdated | EventSessionUpdated
@@ -2511,6 +2528,24 @@ export type LspStatusResponses = {
export type LspStatusResponse = LspStatusResponses[keyof LspStatusResponses] export type LspStatusResponse = LspStatusResponses[keyof LspStatusResponses]
export type FormatterStatusData = {
body?: never
path?: never
query?: {
directory?: string
}
url: "/formatter"
}
export type FormatterStatusResponses = {
/**
* Formatter status
*/
200: Array<FormatterStatus>
}
export type FormatterStatusResponse = FormatterStatusResponses[keyof FormatterStatusResponses]
export type TuiAppendPromptData = { export type TuiAppendPromptData = {
body?: { body?: {
text: string text: string