mirror of
https://github.com/aljazceru/opencode.git
synced 2025-12-20 01:04:22 +01:00
Add formatter status display to TUI status dialog (#3701)
This commit is contained in:
@@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 = {
|
||||||
|
|||||||
@@ -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) => {
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
@@ -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 })
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user