|
@@ -81,6 +81,7 @@ const context = createContext<{
|
|
|
conceal: () => boolean
|
|
conceal: () => boolean
|
|
|
showThinking: () => boolean
|
|
showThinking: () => boolean
|
|
|
showTimestamps: () => boolean
|
|
showTimestamps: () => boolean
|
|
|
|
|
+ diffWrapMode: () => "word" | "none"
|
|
|
sync: ReturnType<typeof useSync>
|
|
sync: ReturnType<typeof useSync>
|
|
|
}>()
|
|
}>()
|
|
|
|
|
|
|
@@ -113,6 +114,7 @@ export function Session() {
|
|
|
const [conceal, setConceal] = createSignal(true)
|
|
const [conceal, setConceal] = createSignal(true)
|
|
|
const [showThinking, setShowThinking] = createSignal(kv.get("thinking_visibility", true))
|
|
const [showThinking, setShowThinking] = createSignal(kv.get("thinking_visibility", true))
|
|
|
const [showTimestamps, setShowTimestamps] = createSignal(kv.get("timestamps", "hide") === "show")
|
|
const [showTimestamps, setShowTimestamps] = createSignal(kv.get("timestamps", "hide") === "show")
|
|
|
|
|
+ const [diffWrapMode, setDiffWrapMode] = createSignal<"word" | "none">("word")
|
|
|
|
|
|
|
|
const wide = createMemo(() => dimensions().width > 120)
|
|
const wide = createMemo(() => dimensions().width > 120)
|
|
|
const sidebarVisible = createMemo(() => {
|
|
const sidebarVisible = createMemo(() => {
|
|
@@ -442,6 +444,15 @@ export function Session() {
|
|
|
dialog.clear()
|
|
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",
|
|
title: "Page up",
|
|
|
value: "session.page.up",
|
|
value: "session.page.up",
|
|
@@ -743,6 +754,7 @@ export function Session() {
|
|
|
conceal,
|
|
conceal,
|
|
|
showThinking,
|
|
showThinking,
|
|
|
showTimestamps,
|
|
showTimestamps,
|
|
|
|
|
+ diffWrapMode,
|
|
|
sync,
|
|
sync,
|
|
|
}}
|
|
}}
|
|
|
>
|
|
>
|
|
@@ -1302,21 +1314,9 @@ ToolRegistry.register<typeof WriteTool>({
|
|
|
container: "block",
|
|
container: "block",
|
|
|
render(props) {
|
|
render(props) {
|
|
|
const { theme, syntax } = useTheme()
|
|
const { theme, syntax } = useTheme()
|
|
|
- const lines = createMemo(
|
|
|
|
|
- () => (typeof props.input.content === "string" ? props.input.content.split("\n") : []),
|
|
|
|
|
- [] as string[],
|
|
|
|
|
- )
|
|
|
|
|
const code = createMemo(() => {
|
|
const code = createMemo(() => {
|
|
|
if (!props.input.content) return ""
|
|
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 ?? ""] ?? [])
|
|
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}>
|
|
<ToolTitle icon="←" fallback="Preparing write..." when={props.input.filePath}>
|
|
|
Wrote {props.input.filePath}
|
|
Wrote {props.input.filePath}
|
|
|
</ToolTitle>
|
|
</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}>
|
|
<Show when={diagnostics().length}>
|
|
|
<For each={diagnostics()}>
|
|
<For each={diagnostics()}>
|
|
|
{(diagnostic) => (
|
|
{(diagnostic) => (
|
|
@@ -1475,84 +1470,17 @@ ToolRegistry.register<typeof EditTool>({
|
|
|
const ctx = use()
|
|
const ctx = use()
|
|
|
const { theme, syntax } = useTheme()
|
|
const { theme, syntax } = useTheme()
|
|
|
|
|
|
|
|
- const style = createMemo(() => {
|
|
|
|
|
|
|
+ const view = createMemo(() => {
|
|
|
const diffStyle = ctx.sync.data.config.tui?.diff_style
|
|
const diffStyle = ctx.sync.data.config.tui?.diff_style
|
|
|
- if (diffStyle === "stacked") return "stacked"
|
|
|
|
|
|
|
+ if (diffStyle === "stacked") return "unified"
|
|
|
// Default to "auto" behavior
|
|
// 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 ft = createMemo(() => filetype(props.input.filePath))
|
|
|
|
|
|
|
|
|
|
+ const diffContent = createMemo(() => props.metadata.diff ?? props.permission["diff"])
|
|
|
|
|
+
|
|
|
const diagnostics = createMemo(() => {
|
|
const diagnostics = createMemo(() => {
|
|
|
const arr = props.metadata.diagnostics?.[props.input.filePath ?? ""] ?? []
|
|
const arr = props.metadata.diagnostics?.[props.input.filePath ?? ""] ?? []
|
|
|
return arr.filter((x) => x.severity === 1).slice(0, 3)
|
|
return arr.filter((x) => x.severity === 1).slice(0, 3)
|
|
@@ -1566,26 +1494,28 @@ ToolRegistry.register<typeof EditTool>({
|
|
|
replaceAll: props.input.replaceAll,
|
|
replaceAll: props.input.replaceAll,
|
|
|
})}
|
|
})}
|
|
|
</ToolTitle>
|
|
</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}>
|
|
<Show when={diagnostics().length}>
|
|
|
<box>
|
|
<box>
|
|
|
<For each={diagnostics()}>
|
|
<For each={diagnostics()}>
|