tui: let users review branch changes against main
This commit is contained in:
@@ -33,11 +33,11 @@ const ROUTE = "diff"
|
||||
const MIN_SPLIT_WIDTH = 100
|
||||
const FILE_TREE_WIDTH = 32
|
||||
const PLAIN_TEXT_FILETYPE = "opencode-plain-text"
|
||||
const WORKING_TREE_DIFF_CONTEXT_LINES = 12
|
||||
const VCS_DIFF_CONTEXT_LINES = 12
|
||||
const KV_SHOW_FILE_TREE = "diff_viewer_show_file_tree"
|
||||
const KV_SINGLE_PATCH = "diff_viewer_single_patch"
|
||||
const KV_VIEW = "diff_viewer_view"
|
||||
type DiffMode = "git" | "last-turn"
|
||||
type DiffMode = "git" | "branch" | "last-turn"
|
||||
type DiffViewerFocus = "patches" | "files"
|
||||
type DiffView = "split" | "unified"
|
||||
|
||||
@@ -75,6 +75,12 @@ function storedView(value: unknown): DiffView | undefined {
|
||||
if (value === "split" || value === "unified") return value
|
||||
}
|
||||
|
||||
function diffSourceLabel(mode: DiffMode) {
|
||||
if (mode === "last-turn") return "last turn"
|
||||
if (mode === "branch") return "main branch"
|
||||
return "working tree"
|
||||
}
|
||||
|
||||
function DiffViewer(props: { api: TuiPluginApi }) {
|
||||
const dimensions = useTerminalDimensions()
|
||||
const themeState = useTheme()
|
||||
@@ -106,7 +112,7 @@ function DiffViewer(props: { api: TuiPluginApi }) {
|
||||
}
|
||||
|
||||
const result = await props.api.client.vcs.diff(
|
||||
{ mode: "git", context: WORKING_TREE_DIFF_CONTEXT_LINES },
|
||||
{ mode: input.mode, context: VCS_DIFF_CONTEXT_LINES },
|
||||
{ throwOnError: true },
|
||||
)
|
||||
return normalizeDiffs(result.data ?? [])
|
||||
@@ -609,6 +615,11 @@ function DiffViewer(props: { api: TuiPluginApi }) {
|
||||
value: "git" as const,
|
||||
description: "Show current git changes",
|
||||
},
|
||||
{
|
||||
title: "Main branch",
|
||||
value: "branch" as const,
|
||||
description: "Show changes compared to main branch",
|
||||
},
|
||||
{
|
||||
title: "Last turn",
|
||||
value: "last-turn" as const,
|
||||
@@ -664,7 +675,7 @@ function DiffViewer(props: { api: TuiPluginApi }) {
|
||||
<PanelGroup axis="y" width="100%" height="100%">
|
||||
<Panel border="none" flexShrink={0} padding={0} paddingLeft={1}>
|
||||
<text fg={theme().text}>Diff </text>
|
||||
<text fg={theme().textMuted}>{mode() === "last-turn" ? "last turn" : "working tree"}</text>
|
||||
<text fg={theme().textMuted}>{diffSourceLabel(mode())}</text>
|
||||
<box flexGrow={1} />
|
||||
<text fg={theme().textMuted}>
|
||||
{files().length} {files().length === 1 ? "file" : "files"}
|
||||
@@ -874,7 +885,7 @@ function DiffViewerHelpDialog() {
|
||||
{
|
||||
shortcut: useCommandShortcut("diff.switch_source"),
|
||||
action: "Switch source",
|
||||
description: "Choose working tree or last-turn changes",
|
||||
description: "Choose working tree, main branch, or last-turn changes",
|
||||
},
|
||||
{
|
||||
shortcut: useCommandShortcut("diff.toggle_view"),
|
||||
|
||||
@@ -85,6 +85,66 @@ test("closing the diff viewer returns to the route it opened from", async () =>
|
||||
}
|
||||
})
|
||||
|
||||
test("branch diff source requests branch VCS diff", async () => {
|
||||
const calls: Parameters<TuiPluginApi["client"]["vcs"]["diff"]>[0][] = []
|
||||
const current: TuiRouteCurrent = { name: "diff", params: { mode: "branch" } }
|
||||
let renderDiff: TuiRouteDefinition["render"] | undefined
|
||||
await mkdir(Global.Path.state, { recursive: true })
|
||||
await Bun.write(path.join(Global.Path.state, "kv.json"), "{}")
|
||||
|
||||
function Harness() {
|
||||
const renderer = useRenderer()
|
||||
const keymap = createDefaultOpenTuiKeymap(renderer)
|
||||
const base = createTuiPluginApi({
|
||||
keymap,
|
||||
client: {
|
||||
vcs: {
|
||||
diff: async (parameters: Parameters<TuiPluginApi["client"]["vcs"]["diff"]>[0]) => {
|
||||
calls.push(parameters)
|
||||
return { data: [] }
|
||||
},
|
||||
},
|
||||
session: { diff: async () => ({ data: [] }) },
|
||||
} as unknown as TuiPluginApi["client"],
|
||||
})
|
||||
const api = {
|
||||
...base,
|
||||
route: {
|
||||
register(routes) {
|
||||
renderDiff = routes.find((route) => route.name === "diff")?.render
|
||||
return () => {}
|
||||
},
|
||||
navigate() {},
|
||||
get current() {
|
||||
return current
|
||||
},
|
||||
},
|
||||
} satisfies TuiPluginApi
|
||||
|
||||
void diffViewerPlugin.tui(api, undefined, pluginMeta)
|
||||
|
||||
return (
|
||||
<OpencodeKeymapProvider keymap={keymap}>
|
||||
<TuiConfigProvider config={createTuiResolvedConfig()}>
|
||||
<KVProvider>
|
||||
<ThemeProvider mode="dark">
|
||||
{renderDiff?.({ params: "params" in current ? current.params : undefined })}
|
||||
</ThemeProvider>
|
||||
</KVProvider>
|
||||
</TuiConfigProvider>
|
||||
</OpencodeKeymapProvider>
|
||||
)
|
||||
}
|
||||
|
||||
const app = await testRender(() => <Harness />, { width: 80, height: 20 })
|
||||
try {
|
||||
await waitForCall(app, calls)
|
||||
expect(calls[0]).toEqual({ mode: "branch", context: 12 })
|
||||
} finally {
|
||||
app.renderer.destroy()
|
||||
}
|
||||
})
|
||||
|
||||
async function waitForCommand(
|
||||
app: Awaited<ReturnType<typeof testRender>>,
|
||||
commands: Map<string, unknown>,
|
||||
@@ -97,6 +157,14 @@ async function waitForCommand(
|
||||
}
|
||||
}
|
||||
|
||||
async function waitForCall(app: Awaited<ReturnType<typeof testRender>>, calls: unknown[]) {
|
||||
for (let attempt = 0; attempt < 10; attempt++) {
|
||||
await app.renderOnce()
|
||||
if (calls.length > 0) return
|
||||
await new Promise((resolve) => setTimeout(resolve, 25))
|
||||
}
|
||||
}
|
||||
|
||||
const pluginMeta = {
|
||||
id: "diff-viewer",
|
||||
source: "internal",
|
||||
|
||||
Reference in New Issue
Block a user