mirror of
https://github.com/anomalyco/opencode.git
synced 2026-02-01 22:48:16 +00:00
opentui diffs
This commit is contained in:
24
bun.lock
24
bun.lock
@@ -236,8 +236,8 @@
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@opencode-ai/util": "workspace:*",
|
||||
"@openrouter/ai-sdk-provider": "1.2.8",
|
||||
"@opentui/core": "0.1.50",
|
||||
"@opentui/solid": "0.1.50",
|
||||
"@opentui/core": "0.1.51",
|
||||
"@opentui/solid": "0.1.51",
|
||||
"@parcel/watcher": "2.5.1",
|
||||
"@pierre/precision-diffs": "catalog:",
|
||||
"@solid-primitives/event-bus": "1.1.2",
|
||||
@@ -256,7 +256,7 @@
|
||||
"jsonc-parser": "3.3.1",
|
||||
"minimatch": "10.0.3",
|
||||
"open": "10.1.2",
|
||||
"opentui-spinner": "0.0.5",
|
||||
"opentui-spinner": "0.0.6",
|
||||
"partial-json": "0.1.7",
|
||||
"remeda": "catalog:",
|
||||
"solid-js": "catalog:",
|
||||
@@ -1085,21 +1085,21 @@
|
||||
|
||||
"@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
|
||||
|
||||
"@opentui/core": ["@opentui/core@0.1.50", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "jimp": "1.6.0", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.50", "@opentui/core-darwin-x64": "0.1.50", "@opentui/core-linux-arm64": "0.1.50", "@opentui/core-linux-x64": "0.1.50", "@opentui/core-win32-arm64": "0.1.50", "@opentui/core-win32-x64": "0.1.50", "bun-webgpu": "0.1.4", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-QhjwT2f8AIQj0gbL/WQ2M93sl2/qp9+Kqxyh4dOhp8z3qnTc5D7J105VrMyeWZW7/P27ubgbFAqqWXrZ4FsuLw=="],
|
||||
"@opentui/core": ["@opentui/core@0.1.51", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.51", "@opentui/core-darwin-x64": "0.1.51", "@opentui/core-linux-arm64": "0.1.51", "@opentui/core-linux-x64": "0.1.51", "@opentui/core-win32-arm64": "0.1.51", "@opentui/core-win32-x64": "0.1.51", "bun-webgpu": "0.1.4", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-9w9vg2nYC4eTKdh5en7WpBB44Nrib3uMtcPNXr2JxftjzDXU5Qmcv3vbKbxHqgzgk7FYm2Z9OFAOk7innbnplA=="],
|
||||
|
||||
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.50", "", { "os": "darwin", "cpu": "arm64" }, "sha512-FKqTDOsZl9TXF7KN2SdZKoRHQNvqKSY27AG3jhKCoiyLGdaNCAsaeBWqAmpnL4E4kMkV3aiQSCrKTrYsaevvOg=="],
|
||||
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.51", "", { "os": "darwin", "cpu": "arm64" }, "sha512-5EicEs5JQiMmr3rzKdGfHnsRXJ7cv/pxz0/C2Xcg+jKMSUmUvS7LE3Mi3HBY05GjZyB/EZ3bYyHqXB4nanYTNg=="],
|
||||
|
||||
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.50", "", { "os": "darwin", "cpu": "x64" }, "sha512-GczVNqqpM/HtsgeBB08K6zL1B7oc6Y5G2cMklo06LrYRdDkFdDtY5fNNnJR2/psZWzTrI3M+sLnKWgUGD5CxUQ=="],
|
||||
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.51", "", { "os": "darwin", "cpu": "x64" }, "sha512-mG9WlKdv0yWCIldFEAzgnm1NCyOtfiVG8zVZLiGaLhsL7Jii+f4RpOmlvFlb2sSSTxjC3HxRzWPC7WBSzk4txA=="],
|
||||
|
||||
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.50", "", { "os": "linux", "cpu": "arm64" }, "sha512-+CKMhweEXH0tLGM6qqaqk6DyCEmwrTVubTtez/pSM3GgcROSXIBui9TEZpIlPgSCVmjbotGS6eSIg4oU+p9o7w=="],
|
||||
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.51", "", { "os": "linux", "cpu": "arm64" }, "sha512-CNUx8nvkKRCsoLg/z7W8tD0hBFEUE9yEpqgyACc/ODdaaRLrUPQkBgUYWM/XGV9OnoqnpqLSKf95+DoUaTZ0lA=="],
|
||||
|
||||
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.50", "", { "os": "linux", "cpu": "x64" }, "sha512-yv5KWiMohAK9bsi1gth9DDZDpoJA1EDHexjhThsPT8EH82g13T088dnJZuJWUE9dr1OwTCQG8DyorNxX3ViEGQ=="],
|
||||
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.51", "", { "os": "linux", "cpu": "x64" }, "sha512-GpZ8vqX2dPyvBKvCRkBJp5x7zsmbixNCsCfzSVG9VIAVyT6qhylT2wrTOTGJLzDfc12ugdeL/1WUIR80YvwARA=="],
|
||||
|
||||
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.50", "", { "os": "win32", "cpu": "arm64" }, "sha512-6/6pURTRNTLFKF8IhYVi7U+T/HGMeURav9LIYw7yfcOibd0kLMthmemhS0Lzyk5dmtp0T4V4NmRmtlq/fIzyjQ=="],
|
||||
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.51", "", { "os": "win32", "cpu": "arm64" }, "sha512-eIc13B9dmoJ2x0EEsd4JWKCLzgVPBv+6Gn6vbjaSL9T/+14XRKMQuEdnQmU6+/KruAumfK+qSXlFdmjhFKrDOA=="],
|
||||
|
||||
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.50", "", { "os": "win32", "cpu": "x64" }, "sha512-EME8GBFq9uCLbH5js8fH7/xY4ZtLIZlt3bkYKT6lPiCNdaf/6ebg+F/ObPXFkJrc8VeV1ql2bXhQ6RLi7izvAA=="],
|
||||
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.51", "", { "os": "win32", "cpu": "x64" }, "sha512-ytpWgA3oNLehI5s9pxLAtg5kVO7Npq6onGPTZUikM5LlH71bX3Bm4PP8qLkD0AG9zDiO7ndp93ZFuT9yGqKPiQ=="],
|
||||
|
||||
"@opentui/solid": ["@opentui/solid@0.1.50", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.50", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.9", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.9" } }, "sha512-q778kp/eksh8UOPSQO2h8h9CGGDqepTf9u2WYTS2HYHRAI2SRtUWpN9L7Euyt3BtG9L/wpsIOHK/ufPhQH1X6A=="],
|
||||
"@opentui/solid": ["@opentui/solid@0.1.51", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.51", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.9", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.9" } }, "sha512-CBohbgFjUVG2P6/iAN52OCGaK0s5Wc2VWKyNrs7Fd9mFDMrC/IfrGAeaDeJXdQ8T0YhZ0PiUVijzFILCi2yOUw=="],
|
||||
|
||||
"@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="],
|
||||
|
||||
@@ -2969,7 +2969,7 @@
|
||||
|
||||
"openid-client": ["openid-client@5.6.4", "", { "dependencies": { "jose": "^4.15.4", "lru-cache": "^6.0.0", "object-hash": "^2.2.0", "oidc-token-hash": "^5.0.3" } }, "sha512-T1h3B10BRPKfcObdBklX639tVz+xh34O7GjofqrqiAQdm7eHsQ00ih18x6wuJ/E6FxdtS2u3FmUGPDeEcMwzNA=="],
|
||||
|
||||
"opentui-spinner": ["opentui-spinner@0.0.5", "", { "dependencies": { "cli-spinners": "^3.3.0" }, "peerDependencies": { "@opentui/core": "^0.1.49", "@opentui/react": "^0.1.49", "@opentui/solid": "^0.1.49", "typescript": "^5" }, "optionalPeers": ["@opentui/react", "@opentui/solid"] }, "sha512-abSWzWA7eyuD0PjerAWbBznLmOQn+8xRDaLGCVIs4ctETi2laNFr5KwicYnPXsHZpPc2neV7WtQm+diCEfOhLA=="],
|
||||
"opentui-spinner": ["opentui-spinner@0.0.6", "", { "dependencies": { "cli-spinners": "^3.3.0" }, "peerDependencies": { "@opentui/core": "^0.1.49", "@opentui/react": "^0.1.49", "@opentui/solid": "^0.1.49", "typescript": "^5" }, "optionalPeers": ["@opentui/react", "@opentui/solid"] }, "sha512-xupLOeVQEAXEvVJCvHkfX6fChDWmJIPHe5jyUrVb8+n4XVTX8mBNhitFfB9v2ZbkC1H2UwPab/ElePHoW37NcA=="],
|
||||
|
||||
"own-keys": ["own-keys@1.0.1", "", { "dependencies": { "get-intrinsic": "^1.2.6", "object-keys": "^1.1.1", "safe-push-apply": "^1.0.0" } }, "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg=="],
|
||||
|
||||
|
||||
@@ -62,8 +62,8 @@
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@opencode-ai/util": "workspace:*",
|
||||
"@openrouter/ai-sdk-provider": "1.2.8",
|
||||
"@opentui/core": "0.1.50",
|
||||
"@opentui/solid": "0.1.50",
|
||||
"@opentui/core": "0.1.51",
|
||||
"@opentui/solid": "0.1.51",
|
||||
"@parcel/watcher": "2.5.1",
|
||||
"@pierre/precision-diffs": "catalog:",
|
||||
"@solid-primitives/event-bus": "1.1.2",
|
||||
@@ -82,7 +82,7 @@
|
||||
"jsonc-parser": "3.3.1",
|
||||
"minimatch": "10.0.3",
|
||||
"open": "10.1.2",
|
||||
"opentui-spinner": "0.0.5",
|
||||
"opentui-spinner": "0.0.6",
|
||||
"partial-json": "0.1.7",
|
||||
"remeda": "catalog:",
|
||||
"solid-js": "catalog:",
|
||||
|
||||
@@ -81,6 +81,7 @@ const context = createContext<{
|
||||
conceal: () => boolean
|
||||
showThinking: () => boolean
|
||||
showTimestamps: () => boolean
|
||||
diffWrapMode: () => "word" | "none"
|
||||
sync: ReturnType<typeof useSync>
|
||||
}>()
|
||||
|
||||
@@ -113,6 +114,7 @@ export function Session() {
|
||||
const [conceal, setConceal] = createSignal(true)
|
||||
const [showThinking, setShowThinking] = createSignal(kv.get("thinking_visibility", true))
|
||||
const [showTimestamps, setShowTimestamps] = createSignal(kv.get("timestamps", "hide") === "show")
|
||||
const [diffWrapMode, setDiffWrapMode] = createSignal<"word" | "none">("word")
|
||||
|
||||
const wide = createMemo(() => dimensions().width > 120)
|
||||
const sidebarVisible = createMemo(() => {
|
||||
@@ -442,6 +444,15 @@ export function Session() {
|
||||
dialog.clear()
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Toggle diff wrapping",
|
||||
value: "session.toggle.diffwrap",
|
||||
category: "Session",
|
||||
onSelect: (dialog) => {
|
||||
setDiffWrapMode((prev) => (prev === "word" ? "none" : "word"))
|
||||
dialog.clear()
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Page up",
|
||||
value: "session.page.up",
|
||||
@@ -743,6 +754,7 @@ export function Session() {
|
||||
conceal,
|
||||
showThinking,
|
||||
showTimestamps,
|
||||
diffWrapMode,
|
||||
sync,
|
||||
}}
|
||||
>
|
||||
@@ -1302,21 +1314,9 @@ ToolRegistry.register<typeof WriteTool>({
|
||||
container: "block",
|
||||
render(props) {
|
||||
const { theme, syntax } = useTheme()
|
||||
const lines = createMemo(
|
||||
() => (typeof props.input.content === "string" ? props.input.content.split("\n") : []),
|
||||
[] as string[],
|
||||
)
|
||||
const code = createMemo(() => {
|
||||
if (!props.input.content) return ""
|
||||
const text = props.input.content
|
||||
return text
|
||||
})
|
||||
|
||||
const numbers = createMemo(() => {
|
||||
const pad = lines().length.toString().length
|
||||
return lines()
|
||||
.map((_, index) => index + 1)
|
||||
.map((x) => x.toString().padStart(pad, " "))
|
||||
return props.input.content
|
||||
})
|
||||
|
||||
const diagnostics = createMemo(() => props.metadata.diagnostics?.[props.input.filePath ?? ""] ?? [])
|
||||
@@ -1326,14 +1326,9 @@ ToolRegistry.register<typeof WriteTool>({
|
||||
<ToolTitle icon="←" fallback="Preparing write..." when={props.input.filePath}>
|
||||
Wrote {props.input.filePath}
|
||||
</ToolTitle>
|
||||
<box flexDirection="row">
|
||||
<box flexShrink={0}>
|
||||
<For each={numbers()}>{(value) => <text style={{ fg: theme.textMuted }}>{value}</text>}</For>
|
||||
</box>
|
||||
<box paddingLeft={1} flexGrow={1}>
|
||||
<code fg={theme.text} filetype={filetype(props.input.filePath!)} syntaxStyle={syntax()} content={code()} />
|
||||
</box>
|
||||
</box>
|
||||
<line_number fg={theme.textMuted} minWidth={3} paddingRight={1}>
|
||||
<code fg={theme.text} filetype={filetype(props.input.filePath!)} syntaxStyle={syntax()} content={code()} />
|
||||
</line_number>
|
||||
<Show when={diagnostics().length}>
|
||||
<For each={diagnostics()}>
|
||||
{(diagnostic) => (
|
||||
@@ -1475,84 +1470,17 @@ ToolRegistry.register<typeof EditTool>({
|
||||
const ctx = use()
|
||||
const { theme, syntax } = useTheme()
|
||||
|
||||
const style = createMemo(() => {
|
||||
const view = createMemo(() => {
|
||||
const diffStyle = ctx.sync.data.config.tui?.diff_style
|
||||
if (diffStyle === "stacked") return "stacked"
|
||||
if (diffStyle === "stacked") return "unified"
|
||||
// Default to "auto" behavior
|
||||
return ctx.width > 120 ? "split" : "stacked"
|
||||
})
|
||||
|
||||
const diff = createMemo(() => {
|
||||
const diff = props.metadata.diff ?? props.permission["diff"]
|
||||
if (!diff) return null
|
||||
|
||||
try {
|
||||
const patches = parsePatch(diff)
|
||||
if (patches.length === 0) return null
|
||||
|
||||
const patch = patches[0]
|
||||
const oldLines: string[] = []
|
||||
const newLines: string[] = []
|
||||
|
||||
for (const hunk of patch.hunks) {
|
||||
let i = 0
|
||||
while (i < hunk.lines.length) {
|
||||
const line = hunk.lines[i]
|
||||
|
||||
if (line.startsWith("-")) {
|
||||
const removedLines: string[] = []
|
||||
while (i < hunk.lines.length && hunk.lines[i].startsWith("-")) {
|
||||
removedLines.push("- " + hunk.lines[i].slice(1))
|
||||
i++
|
||||
}
|
||||
|
||||
const addedLines: string[] = []
|
||||
while (i < hunk.lines.length && hunk.lines[i].startsWith("+")) {
|
||||
addedLines.push("+ " + hunk.lines[i].slice(1))
|
||||
i++
|
||||
}
|
||||
|
||||
const maxLen = Math.max(removedLines.length, addedLines.length)
|
||||
for (let j = 0; j < maxLen; j++) {
|
||||
oldLines.push(removedLines[j] ?? "")
|
||||
newLines.push(addedLines[j] ?? "")
|
||||
}
|
||||
} else if (line.startsWith("+")) {
|
||||
const addedLines: string[] = []
|
||||
while (i < hunk.lines.length && hunk.lines[i].startsWith("+")) {
|
||||
addedLines.push("+ " + hunk.lines[i].slice(1))
|
||||
i++
|
||||
}
|
||||
|
||||
for (const added of addedLines) {
|
||||
oldLines.push("")
|
||||
newLines.push(added)
|
||||
}
|
||||
} else {
|
||||
oldLines.push(" " + line.slice(1))
|
||||
newLines.push(" " + line.slice(1))
|
||||
i++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
oldContent: oldLines.join("\n"),
|
||||
newContent: newLines.join("\n"),
|
||||
}
|
||||
} catch (error) {
|
||||
return null
|
||||
}
|
||||
})
|
||||
|
||||
const code = createMemo(() => {
|
||||
if (!props.metadata.diff) return ""
|
||||
const text = props.metadata.diff.split("\n").slice(5).join("\n")
|
||||
return text.trim()
|
||||
return ctx.width > 120 ? "split" : "unified"
|
||||
})
|
||||
|
||||
const ft = createMemo(() => filetype(props.input.filePath))
|
||||
|
||||
const diffContent = createMemo(() => props.metadata.diff ?? props.permission["diff"])
|
||||
|
||||
const diagnostics = createMemo(() => {
|
||||
const arr = props.metadata.diagnostics?.[props.input.filePath ?? ""] ?? []
|
||||
return arr.filter((x) => x.severity === 1).slice(0, 3)
|
||||
@@ -1566,26 +1494,28 @@ ToolRegistry.register<typeof EditTool>({
|
||||
replaceAll: props.input.replaceAll,
|
||||
})}
|
||||
</ToolTitle>
|
||||
<Switch>
|
||||
<Match when={props.permission["diff"]}>
|
||||
<text fg={theme.text}>{props.permission["diff"]?.trim()}</text>
|
||||
</Match>
|
||||
<Match when={diff() && style() === "split"}>
|
||||
<box paddingLeft={1} flexDirection="row" gap={2}>
|
||||
<box flexGrow={1} flexBasis={0}>
|
||||
<code fg={theme.text} filetype={ft()} syntaxStyle={syntax()} content={diff()!.oldContent} />
|
||||
</box>
|
||||
<box flexGrow={1} flexBasis={0}>
|
||||
<code fg={theme.text} filetype={ft()} syntaxStyle={syntax()} content={diff()!.newContent} />
|
||||
</box>
|
||||
</box>
|
||||
</Match>
|
||||
<Match when={code()}>
|
||||
<box paddingLeft={1}>
|
||||
<code fg={theme.text} filetype={ft()} syntaxStyle={syntax()} content={code()} />
|
||||
</box>
|
||||
</Match>
|
||||
</Switch>
|
||||
<Show when={diffContent()}>
|
||||
<box paddingLeft={1}>
|
||||
<diff
|
||||
diff={diffContent()}
|
||||
view={view()}
|
||||
filetype={ft()}
|
||||
syntaxStyle={syntax()}
|
||||
showLineNumbers={true}
|
||||
width="100%"
|
||||
wrapMode={ctx.diffWrapMode()}
|
||||
addedBg={theme.diffAddedBg}
|
||||
removedBg={theme.diffRemovedBg}
|
||||
contextBg={theme.diffContextBg}
|
||||
addedSignColor={theme.diffHighlightAdded}
|
||||
removedSignColor={theme.diffHighlightRemoved}
|
||||
lineNumberFg={theme.diffLineNumber}
|
||||
lineNumberBg={theme.diffContextBg}
|
||||
addedLineNumberBg={theme.diffAddedLineNumberBg}
|
||||
removedLineNumberBg={theme.diffRemovedLineNumberBg}
|
||||
/>
|
||||
</box>
|
||||
</Show>
|
||||
<Show when={diagnostics().length}>
|
||||
<box>
|
||||
<For each={diagnostics()}>
|
||||
|
||||
Reference in New Issue
Block a user