feat(tui): add semantic file path truncation (#36352)

This commit is contained in:
Kit Langton 2026-07-11 13:57:29 -04:00 committed by GitHub
commit e3f7637eb2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 312 additions and 57 deletions

View file

@ -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 }) {
@ -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%"
@ -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} />

View file

@ -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"
@ -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>

View file

@ -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"
@ -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}>
@ -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 }}>