Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions packages/tui/src/component/dialog-workspace-file-changes.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { TextAttributes } from "@opentui/core"
import { useKeyboard } from "@opentui/solid"
import { useKeyboard, useTerminalDimensions } from "@opentui/solid"
import type { VcsFileStatus } from "@opencode-ai/sdk/v2"
import { createMemo, For } from "solid-js"
import { createStore } from "solid-js/store"
import { Locale } from "../util/locale"
import { FilePath } from "../ui/file-path"
import { useTheme } from "../context/theme"
import { useTuiConfig } from "../config"
import { useDialog, type DialogContext } from "../ui/dialog"
Expand Down Expand Up @@ -33,10 +33,13 @@ export function DialogWorkspaceFileChanges(props: {
const dialog = useDialog()
const { theme } = useTheme()
const tuiConfig = useTuiConfig()
const dimensions = useTerminalDimensions()
const scrollAcceleration = createMemo(() => getScrollAcceleration(tuiConfig))
const [store, setStore] = createStore({ active: "yes" as WorkspaceFileChangesChoice })
const height = createMemo(() => Math.min(props.files.length, 8))
const fileNameWidth = createMemo(() => 48 - Math.max(Math.max(7, ...props.files.map(changeCountWidth)) - 7, 0))
const fileNameWidth = createMemo(
() => Math.max(2, Math.min(60, dimensions().width - 2) - 6 - Math.max(7, ...props.files.map(changeCountWidth))),
)

function confirm() {
props.onSelect(store.active)
Expand Down Expand Up @@ -93,9 +96,7 @@ export function DialogWorkspaceFileChanges(props: {
<box width={2} flexShrink={0}>
<text fg={theme.textMuted}>{statusLabel(item.status)}</text>
</box>
<text fg={theme.textMuted} wrapMode="none">
{Locale.truncateLeft(item.file, fileNameWidth())}
</text>
<FilePath value={item.file} maxWidth={fileNameWidth()} fg={theme.textMuted} />
</box>
<box flexDirection="row" gap={1} minWidth={7} flexShrink={0} justifyContent="flex-end">
<text>
Expand Down
43 changes: 37 additions & 6 deletions packages/tui/src/feature-plugins/home/footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,45 @@ import { createMemo, Match, Show, Switch } from "solid-js"
import { abbreviateHome } from "../../runtime"
import { useTuiPaths } from "../../context/runtime"
import { useHomeSessionDestination } from "../../routes/home/session-destination"
import { FilePath } from "../../ui/file-path"
import { useTerminalDimensions } from "@opentui/solid"

const id = "internal:home-footer"

function Directory(props: { api: TuiPluginApi }) {
function Directory(props: { api: TuiPluginApi; maxWidth: number }) {
const theme = () => props.api.theme.current
const destination = useHomeSessionDestination()
const paths = useTuiPaths()
const dir = createMemo(() => {
const selected = destination?.destination()
if (!selected || selected.type === "new") return
const out = abbreviateHome(selected.directory, paths.home)
const branch =
selected.directory === (props.api.state.path.directory || paths.cwd) ? props.api.state.vcs?.branch : undefined
if (branch) return out + ":" + branch
return out
return { path: abbreviateHome(selected.directory, paths.home), branch }
})

return <Show when={dir()}>{(value) => <text fg={theme().textMuted}>{value()}</text>}</Show>
return (
<Show when={dir()}>
{(value) => {
const suffix = () => (value().branch ? `:${value().branch}` : "")
const suffixWidth = () => Math.min(Bun.stringWidth(suffix()), Math.max(0, props.maxWidth - 2))
return (
<box flexDirection="row" minWidth={0}>
<FilePath
value={value().path}
maxWidth={Math.max(2, props.maxWidth - suffixWidth())}
fg={theme().textMuted}
/>
<Show when={suffix()}>
<text width={suffixWidth()} wrapMode="none" truncate fg={theme().textMuted}>
{suffix()}
</text>
</Show>
</box>
)
}}
</Show>
)
}

function Mcp(props: { api: TuiPluginApi }) {
Expand Down Expand Up @@ -62,6 +83,16 @@ function Version(props: { api: TuiPluginApi }) {
}

function View(props: { api: TuiPluginApi }) {
const dimensions = useTerminalDimensions()
const mcpWidth = createMemo(() => {
const list = props.api.state.mcp()
if (list.length === 0) return 0
const count = list.filter((item) => item.status === "connected").length
return Bun.stringWidth(`⊙ ${count} MCP /status`) + 2
})
const directoryWidth = createMemo(() =>
Math.max(2, dimensions().width - 8 - Bun.stringWidth(props.api.app.version) - mcpWidth()),
)
return (
<box
width="100%"
Expand All @@ -73,7 +104,7 @@ function View(props: { api: TuiPluginApi }) {
flexShrink={0}
gap={2}
>
<Directory api={props.api} />
<Directory api={props.api} maxWidth={directoryWidth()} />
<Mcp api={props.api} />
<box flexGrow={1} />
<Version api={props.api} />
Expand Down
10 changes: 6 additions & 4 deletions packages/tui/src/feature-plugins/sidebar/files.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { TuiPlugin, TuiPluginApi } from "@opencode-ai/plugin/tui"
import type { BuiltinTuiPlugin } from "../builtins"
import { createMemo, For, Show, createSignal } from "solid-js"
import { Locale } from "../../util/locale"
import { FilePath } from "../../ui/file-path"

const id = "internal:sidebar-files"

Expand Down Expand Up @@ -31,9 +31,11 @@ function View(props: { api: TuiPluginApi; session_id: string }) {
<For each={list()}>
{(item) => (
<box flexDirection="row" gap={1} justifyContent="space-between">
<text fg={theme().textMuted} wrapMode="none">
{Locale.truncateLeft(item.file, Math.max(2, 36 - changeCountWidth(item)))}
</text>
<FilePath
value={item.file}
maxWidth={Math.max(2, 36 - changeCountWidth(item))}
fg={theme().textMuted}
/>
<box flexDirection="row" gap={1} flexShrink={0}>
<Show when={item.additions}>
<text fg={theme().diffAdded}>+{item.additions}</text>
Expand Down
30 changes: 18 additions & 12 deletions packages/tui/src/feature-plugins/sidebar/footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { BuiltinTuiPlugin } from "../builtins"
import { createMemo, Show } from "solid-js"
import { abbreviateHome } from "../../runtime"
import { useTuiPaths } from "../../context/runtime"
import { FilePath } from "../../ui/file-path"

const id = "internal:sidebar-footer"

Expand All @@ -16,16 +17,12 @@ function View(props: { api: TuiPluginApi; directory: string }) {
)
const done = createMemo(() => props.api.kv.get("dismissed_getting_started", false))
const show = createMemo(() => !has() && !done())
const path = createMemo(() => {
const out = abbreviateHome(props.directory, paths.home)
const location = createMemo(() => {
const branch = props.directory === props.api.state.path.directory ? props.api.state.vcs?.branch : undefined
const text = branch ? out + ":" + branch : out
const list = text.split("/")
return {
parent: list.slice(0, -1).join("/"),
name: list.at(-1) ?? "",
}
return { path: abbreviateHome(props.directory, paths.home), branch }
})
const suffix = createMemo(() => (location().branch ? `:${location().branch}` : ""))
const suffixWidth = createMemo(() => Math.min(Bun.stringWidth(suffix()), 36))

return (
<box gap={1}>
Expand Down Expand Up @@ -62,10 +59,19 @@ function View(props: { api: TuiPluginApi; directory: string }) {
</box>
</box>
</Show>
<text>
<span style={{ fg: theme().textMuted }}>{path().parent}/</span>
<span style={{ fg: theme().text }}>{path().name}</span>
</text>
<box flexDirection="row" minWidth={0}>
<FilePath
value={location().path}
maxWidth={Math.max(2, 38 - suffixWidth())}
fg={theme().textMuted}
basenameFg={theme().text}
/>
<Show when={suffix()}>
<text width={suffixWidth()} wrapMode="none" truncate fg={theme().textMuted}>
{suffix()}
</text>
</Show>
</box>
<text fg={theme().textMuted}>
<span style={{ fg: theme().success }}>•</span> <b>Open</b>
<span style={{ fg: theme().text }}>
Expand Down
110 changes: 85 additions & 25 deletions packages/tui/src/routes/session/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import type {
} from "@opencode-ai/sdk/v2"
import { useLocal } from "../../context/local"
import { Locale } from "../../util/locale"
import { FilePath } from "../../ui/file-path"
import { webSearchProviderLabel } from "../../util/tool-display"
import { useRenderer, useTerminalDimensions, type JSX } from "@opentui/solid"
import { useSDK } from "../../context/sdk"
Expand Down Expand Up @@ -1393,6 +1394,7 @@ function RevertMessage(props: {
readonly deletions: number
}>
}) {
const ctx = use()
const { theme } = useTheme()
const route = useRouteData("session")
const sdk = useSDK()
Expand Down Expand Up @@ -1435,9 +1437,17 @@ function RevertMessage(props: {
{(file) => (
<box flexDirection="row" gap={1} flexShrink={0}>
<text fg={theme.textMuted}>{statusLabel(file.status)}</text>
<text fg={theme.text} wrapMode="none">
{Locale.truncateLeft(file.file, 60)}
</text>
<FilePath
value={file.file}
maxWidth={Math.max(
2,
ctx.width -
5 -
(file.additions > 0 ? Bun.stringWidth(`+${file.additions}`) + 1 : 0) -
(file.deletions > 0 ? Bun.stringWidth(`-${file.deletions}`) + 1 : 0),
)}
fg={theme.text}
/>
<Show when={file.additions > 0}>
<text fg={theme.diffAdded}>+{file.additions}</text>
</Show>
Expand Down Expand Up @@ -2129,6 +2139,7 @@ export function InlineToolRow(props: {

function BlockTool(props: {
title?: string
path?: { label: string; value: string }
children?: JSX.Element
onClick?: () => void
part?: SessionMessageAssistantTool
Expand Down Expand Up @@ -2162,18 +2173,45 @@ function BlockTool(props: {
props.onClick?.()
}}
>
<Show when={props.title}>
{(title) => (
<Show
when={props.spinner}
fallback={
<text paddingLeft={3} fg={permission() ? theme.warning : theme.textMuted}>
{title()}
</text>
}
>
<Spinner color={permission() ? theme.warning : theme.textMuted}>{title().replace(/^# /, "")}</Spinner>
<Show
when={props.path}
fallback={
<Show when={props.title}>
{(title) => (
<Show
when={props.spinner}
fallback={
<text fg={permission() ? theme.warning : theme.textMuted}>
{title()}
</text>
}
>
<Spinner color={permission() ? theme.warning : theme.textMuted}>{title().replace(/^# /, "")}</Spinner>
</Show>
)}
</Show>
}
>
{(path) => (
<box flexDirection="row" gap={1} minWidth={0}>
<Show
when={props.spinner}
fallback={
<text flexShrink={0} fg={permission() ? theme.warning : theme.textMuted}>
{path().label}
</text>
}
>
<Spinner color={permission() ? theme.warning : theme.textMuted}>
{path().label.replace(/^# /, "")}
</Spinner>
</Show>
<FilePath
value={path().value}
maxWidth={Math.max(2, ctx.width - 4 - Bun.stringWidth(path().label) - (props.spinner ? 2 : 0))}
fg={permission() ? theme.warning : theme.textMuted}
/>
</box>
)}
</Show>
{props.children}
Expand Down Expand Up @@ -2268,7 +2306,10 @@ function Write(props: ToolProps) {
return (
<Switch>
<Match when={props.metadata.diagnostics !== undefined}>
<BlockTool title={"# Wrote " + pathFormatter.format(stringValue(props.input.path))} part={props.part}>
<BlockTool
path={{ label: "# Wrote", value: pathFormatter.format(stringValue(props.input.path)) }}
part={props.part}
>
<line_number fg={theme.textMuted} minWidth={3} paddingRight={1}>
<code
conceal={false}
Expand Down Expand Up @@ -2494,7 +2535,7 @@ function Edit(props: ToolProps) {
<Switch>
<Match when={file()}>
{(item) => (
<BlockTool title={"← Edit " + pathFormatter.format(path())} part={props.part}>
<BlockTool path={{ label: "← Edit", value: pathFormatter.format(path()) }} part={props.part}>
<box paddingLeft={1}>
<diff
diff={item().patch}
Expand Down Expand Up @@ -2522,11 +2563,12 @@ function Edit(props: ToolProps) {
</Match>
<Match when={true}>
<BlockTool
title={
path={
stringValue(props.input.path)
? "← Edit " + pathFormatter.format(stringValue(props.input.path))
: "# Preparing edit..."
? { label: "← Edit", value: pathFormatter.format(stringValue(props.input.path)) }
: undefined
}
title={stringValue(props.input.path) ? undefined : "# Preparing edit..."}
part={props.part}
spinner={props.part.state.status === "streaming"}
/>
Expand Down Expand Up @@ -2567,7 +2609,10 @@ function ApplyPatch(props: ToolProps) {
<For each={files()}>
{(file) => (
<BlockTool
title={`${file.type === "add" ? "# Created" : file.type === "delete" ? "# Deleted" : "← Patched"} ${pathFormatter.format(file.relativePath)}`}
path={{
label: file.type === "add" ? "# Created" : file.type === "delete" ? "# Deleted" : "← Patched",
value: pathFormatter.format(file.relativePath),
}}
part={props.part}
>
<box paddingLeft={1}>
Expand Down Expand Up @@ -2601,26 +2646,41 @@ function ApplyPatch(props: ToolProps) {
<For each={applied()}>
{(file) => (
<BlockTool
title={`${file.type === "add" ? "# Created" : file.type === "delete" ? "# Deleted" : "← Patched"} ${pathFormatter.format(file.resource)}`}
path={{
label: file.type === "add" ? "# Created" : file.type === "delete" ? "# Deleted" : "← Patched",
value: pathFormatter.format(file.resource),
}}
part={props.part}
>
<text fg={file.type === "delete" ? theme.diffRemoved : theme.textMuted}>{file.resource}</text>
<FilePath
value={file.resource}
maxWidth={Math.max(2, ctx.width - 3)}
fg={file.type === "delete" ? theme.diffRemoved : theme.textMuted}
/>
</BlockTool>
)}
</For>
</box>
</Match>
<Match when={true}>
<BlockTool
path={
targets().length === 1
? {
label: props.part.state.status === "error" ? "# Patch failed" : "Patching",
value: pathFormatter.format(targets()[0]),
}
: undefined
}
title={
targets().length === 1
? `${props.part.state.status === "error" ? "# Patch failed" : "# Preparing patch"} ${pathFormatter.format(targets()[0])}`
? undefined
: props.part.state.status === "error"
? "# Patch failed"
: "# Preparing patch..."
: "Patching"
}
part={props.part}
spinner={props.part.state.status === "streaming"}
spinner={props.part.state.status === "streaming" || props.part.state.status === "running"}
/>
</Match>
</Switch>
Expand Down
Loading
Loading