summaryrefslogtreecommitdiffhomepage
path: root/packages
diff options
context:
space:
mode:
authorDax Raad <[email protected]>2025-11-28 21:13:13 -0500
committerDax Raad <[email protected]>2025-11-28 21:13:13 -0500
commit204a31b6bbad951e1e03d985c2edc62165062a4e (patch)
treeb386540eda1ebe6bd679d12f888cf02f181c4435 /packages
parent813d287a09b3bdffecdb7e79226b39b4cb8ee211 (diff)
parent4dd9f33ebaa887f906387a2fb8700beff170648f (diff)
downloadopencode-204a31b6bbad951e1e03d985c2edc62165062a4e.tar.gz
opencode-204a31b6bbad951e1e03d985c2edc62165062a4e.zip
Merge remote-tracking branch 'origin/dev' into dev
Diffstat (limited to 'packages')
-rw-r--r--packages/desktop/index.html3
-rw-r--r--packages/enterprise/src/entry-server.tsx3
-rw-r--r--packages/opencode/package.json6
-rw-r--r--packages/opencode/src/cli/cmd/tui/routes/session/index.tsx156
-rw-r--r--packages/ui/src/components/diff.tsx2
5 files changed, 51 insertions, 119 deletions
diff --git a/packages/desktop/index.html b/packages/desktop/index.html
index 6e67e6d47..0ac3d566d 100644
--- a/packages/desktop/index.html
+++ b/packages/desktop/index.html
@@ -9,7 +9,8 @@
<link rel="shortcut icon" href="/favicon.ico" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="manifest" href="/site.webmanifest" />
- <meta name="theme-color" content="var(--background-base)" />
+ <meta name="theme-color" content="#F8F7F7" />
+ <meta name="theme-color" content="#131010" media="(prefers-color-scheme: dark)" />
<meta property="og:image" content="/social-share.png" />
<meta property="twitter:image" content="/social-share.png" />
</head>
diff --git a/packages/enterprise/src/entry-server.tsx b/packages/enterprise/src/entry-server.tsx
index 85d69e2e0..68f4325c8 100644
--- a/packages/enterprise/src/entry-server.tsx
+++ b/packages/enterprise/src/entry-server.tsx
@@ -9,7 +9,8 @@ export default createHandler(() => (
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>OpenCode</title>
- <meta name="theme-color" content="var(--background-base)" />
+ <meta name="theme-color" content="#F8F7F7" />
+ <meta name="theme-color" content="#131010" media="(prefers-color-scheme: dark)" />
<meta property="og:image" content="/social-share.png" />
<meta property="twitter:image" content="/social-share.png" />
{assets}
diff --git a/packages/opencode/package.json b/packages/opencode/package.json
index 968e9539b..e8e79f36f 100644
--- a/packages/opencode/package.json
+++ b/packages/opencode/package.json
@@ -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:",
diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx
index 3d14db149..fb0e07a86 100644
--- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx
+++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx
@@ -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(() => {
@@ -443,6 +445,15 @@ export function Session() {
},
},
{
+ 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",
keybind: "messages_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()}>
diff --git a/packages/ui/src/components/diff.tsx b/packages/ui/src/components/diff.tsx
index c895a6e29..bd2134515 100644
--- a/packages/ui/src/components/diff.tsx
+++ b/packages/ui/src/components/diff.tsx
@@ -46,7 +46,7 @@ export function Diff<T>(props: DiffProps<T>) {
})
onMount(() => {
- if (isServer) return
+ if (isServer || !props.preloadedDiff) return
fileDiffInstance = new FileDiff<T>({
...createDefaultOptions(props.diffStyle),
...others,