mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-24 16:02:55 +08:00
Merge pull request #8754 from shssoichiro/issue-7861
ux(cli): visually show when applied patch skips sections
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@kilocode/cli": patch
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Fix TUI diff rendering when header-like content lines appear inside a unified diff hunk.
|
||||
@@ -83,6 +83,8 @@ import * as Model from "../../util/model"
|
||||
import { formatTranscript } from "../../util/transcript"
|
||||
import { UI } from "@/cli/ui.ts"
|
||||
import { useTuiConfig } from "../../context/tui-config"
|
||||
import { splitDiffHunks } from "@/kilocode/tui/diff" // kilocode_change
|
||||
|
||||
import { formatMarkdownTables } from "../../util/markdown" // kilocode_change
|
||||
import { bell } from "@/kilocode/bell" // kilocode_change
|
||||
import { SessionIndexing } from "@/kilocode/components/session-indexing" // kilocode_change
|
||||
@@ -2228,32 +2230,46 @@ function Edit(props: ToolProps<typeof EditTool>) {
|
||||
const ft = createMemo(() => filetype(props.input.filePath))
|
||||
|
||||
const diffContent = createMemo(() => props.metadata.diff)
|
||||
const hunks = createMemo(() => splitDiffHunks(diffContent() ?? "")) // kilocode_change
|
||||
|
||||
return (
|
||||
<Switch>
|
||||
<Match when={props.metadata.diff !== undefined}>
|
||||
<BlockTool title={"← Edit " + normalizePath(props.input.filePath!)} part={props.part}>
|
||||
<box paddingLeft={1}>
|
||||
<diff
|
||||
diff={diffContent()}
|
||||
view={view()}
|
||||
filetype={ft()}
|
||||
syntaxStyle={syntax()}
|
||||
showLineNumbers={true}
|
||||
width="100%"
|
||||
wrapMode={ctx.diffWrapMode()}
|
||||
fg={theme.text}
|
||||
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}
|
||||
/>
|
||||
{/* kilocode_change start */}
|
||||
<box paddingLeft={1} flexDirection="column">
|
||||
<For each={hunks()}>
|
||||
{(hunk, i) => (
|
||||
<>
|
||||
<Show when={i() > 0}>
|
||||
<text fg={theme.textMuted} alignSelf="center" height={2}>
|
||||
...
|
||||
</text>
|
||||
</Show>
|
||||
<diff
|
||||
diff={hunk}
|
||||
view={view()}
|
||||
filetype={ft()}
|
||||
syntaxStyle={syntax()}
|
||||
showLineNumbers={true}
|
||||
width="100%"
|
||||
wrapMode={ctx.diffWrapMode()}
|
||||
fg={theme.text}
|
||||
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}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</For>
|
||||
</box>
|
||||
{/* kilocode_change end */}
|
||||
<Diagnostics diagnostics={props.metadata.diagnostics} filePath={props.input.filePath ?? ""} />
|
||||
</BlockTool>
|
||||
</Match>
|
||||
@@ -2279,29 +2295,43 @@ function ApplyPatch(props: ToolProps<typeof ApplyPatchTool>) {
|
||||
})
|
||||
|
||||
function Diff(p: { diff: string; filePath: string }) {
|
||||
// kilocode_change start
|
||||
const hunks = createMemo(() => splitDiffHunks(p.diff))
|
||||
return (
|
||||
<box paddingLeft={1}>
|
||||
<diff
|
||||
diff={p.diff}
|
||||
view={view()}
|
||||
filetype={filetype(p.filePath)}
|
||||
syntaxStyle={syntax()}
|
||||
showLineNumbers={true}
|
||||
width="100%"
|
||||
wrapMode={ctx.diffWrapMode()}
|
||||
fg={theme.text}
|
||||
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 paddingLeft={1} flexDirection="column">
|
||||
<For each={hunks()}>
|
||||
{(hunk, i) => (
|
||||
<>
|
||||
<Show when={i() > 0}>
|
||||
<text fg={theme.textMuted} alignSelf="center" height={2}>
|
||||
...
|
||||
</text>
|
||||
</Show>
|
||||
<diff
|
||||
diff={hunk}
|
||||
view={view()}
|
||||
filetype={filetype(p.filePath)}
|
||||
syntaxStyle={syntax()}
|
||||
showLineNumbers={true}
|
||||
width="100%"
|
||||
wrapMode={ctx.diffWrapMode()}
|
||||
fg={theme.text}
|
||||
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}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</For>
|
||||
</box>
|
||||
)
|
||||
// kilocode_change end
|
||||
}
|
||||
|
||||
function title(file: { type: string; relativePath: string; filePath: string; deletions: number }) {
|
||||
|
||||
@@ -19,6 +19,7 @@ import { useDialog } from "../../ui/dialog"
|
||||
import { getScrollAcceleration } from "../../util/scroll"
|
||||
import { useTuiConfig } from "../../context/tui-config"
|
||||
import { ConfigProtection } from "@/kilocode/permission/config-paths" // kilocode_change
|
||||
import { splitDiffHunks } from "@/kilocode/tui/diff" // kilocode_change
|
||||
import { normalizeUrls } from "@/kilocode/util/url" // kilocode_change
|
||||
|
||||
type PermissionStage = "permission" | "always" | "reject"
|
||||
@@ -67,6 +68,7 @@ function EditBody(props: { request: PermissionRequest }) {
|
||||
|
||||
const ft = createMemo(() => filetype(filepath()))
|
||||
const scrollAcceleration = createMemo(() => getScrollAcceleration(config))
|
||||
const hunks = createMemo(() => splitDiffHunks(diff())) // kilocode_change
|
||||
|
||||
return (
|
||||
<box flexDirection="column" gap={1}>
|
||||
@@ -81,25 +83,38 @@ function EditBody(props: { request: PermissionRequest }) {
|
||||
},
|
||||
}}
|
||||
>
|
||||
<diff
|
||||
diff={diff()}
|
||||
view={view()}
|
||||
filetype={ft()}
|
||||
syntaxStyle={syntax()}
|
||||
showLineNumbers={true}
|
||||
width="100%"
|
||||
wrapMode="word"
|
||||
fg={theme.text}
|
||||
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}
|
||||
/>
|
||||
{/* kilocode_change start */}
|
||||
<box flexDirection="column">
|
||||
<For each={hunks()}>
|
||||
{(hunk, i) => (
|
||||
<>
|
||||
<Show when={i() > 0}>
|
||||
<text fg={theme.textMuted}>...</text>
|
||||
</Show>
|
||||
<diff
|
||||
diff={hunk}
|
||||
view={view()}
|
||||
filetype={ft()}
|
||||
syntaxStyle={syntax()}
|
||||
showLineNumbers={true}
|
||||
width="100%"
|
||||
wrapMode="word"
|
||||
fg={theme.text}
|
||||
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}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</For>
|
||||
</box>
|
||||
{/* kilocode_change end */}
|
||||
</scrollbox>
|
||||
</Show>
|
||||
<Show when={!diff()}>
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
export function splitDiffHunks(diff: string): string[] {
|
||||
const parse = (line: string) => {
|
||||
const match = line.match(/^@@ -\d+(?:,(\d+))? \+\d+(?:,(\d+))? @@/)
|
||||
if (!match) return
|
||||
return {
|
||||
old: Number(match[1] ?? "1"),
|
||||
next: Number(match[2] ?? "1"),
|
||||
}
|
||||
}
|
||||
|
||||
const split = (section: string[]) => {
|
||||
const start = section.findIndex((line) => line.startsWith("@@"))
|
||||
if (start === -1) return [section.join("\n")]
|
||||
|
||||
const prefix = section.slice(0, start)
|
||||
const hunks = section.slice(start).reduce((acc, line) => {
|
||||
if (line.startsWith("@@")) return [...acc, [line]]
|
||||
if (acc.length === 0) return [[line]]
|
||||
return [...acc.slice(0, -1), [...acc.at(-1)!, line]]
|
||||
}, [] as string[][])
|
||||
|
||||
const head = prefix.join("\n")
|
||||
return hunks.map((hunk) => [head, ...hunk].join("\n"))
|
||||
}
|
||||
|
||||
const lines = diff.split("\n")
|
||||
const files = lines.reduce(
|
||||
(acc, line, index) => {
|
||||
const hunk = parse(line)
|
||||
if (hunk) {
|
||||
return { files: acc.files, old: hunk.old, next: hunk.next }
|
||||
}
|
||||
|
||||
if (
|
||||
acc.old !== 0 &&
|
||||
acc.next !== 0 &&
|
||||
line.startsWith("--- ") &&
|
||||
lines[index + 1]?.startsWith("+++ ") &&
|
||||
lines[index + 2]?.startsWith("@@")
|
||||
) {
|
||||
return { files: [...acc.files, index], old: 0, next: 0 }
|
||||
}
|
||||
|
||||
if (acc.old === 0 && acc.next === 0) {
|
||||
if (line.startsWith("--- ") && lines[index + 1]?.startsWith("+++ ")) {
|
||||
return { files: [...acc.files, index], old: 0, next: 0 }
|
||||
}
|
||||
return acc
|
||||
}
|
||||
|
||||
if (line.startsWith("\\ ")) return acc
|
||||
if (line.startsWith("+")) {
|
||||
return { files: acc.files, old: acc.old, next: acc.next - 1 }
|
||||
}
|
||||
if (line.startsWith("-")) {
|
||||
return { files: acc.files, old: acc.old - 1, next: acc.next }
|
||||
}
|
||||
return { files: acc.files, old: acc.old - 1, next: acc.next - 1 }
|
||||
},
|
||||
{ files: [] as number[], old: 0, next: 0 },
|
||||
).files
|
||||
|
||||
if (files.length === 0) {
|
||||
const hunks = split(lines)
|
||||
if (hunks.length <= 1) return [diff]
|
||||
return hunks
|
||||
}
|
||||
|
||||
const hunks = files
|
||||
.map((line, index) => {
|
||||
const start = index === 0 ? 0 : line
|
||||
const end = files[index + 1] ?? lines.length
|
||||
return split(lines.slice(start, end))
|
||||
})
|
||||
.flat()
|
||||
|
||||
if (hunks.length <= 1) return [diff]
|
||||
return hunks
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import { splitDiffHunks } from "../../src/kilocode/tui/diff"
|
||||
|
||||
describe("splitDiffHunks", () => {
|
||||
test("returns original diff when there are no hunks", () => {
|
||||
const diff = "--- a/file.ts\n+++ b/file.ts"
|
||||
expect(splitDiffHunks(diff)).toEqual([diff])
|
||||
})
|
||||
|
||||
test("returns original diff when there is one hunk", () => {
|
||||
const diff = ["--- a/file.ts", "+++ b/file.ts", "@@ -1,2 +1,2 @@", "-one", "+ONE", " two"].join("\n")
|
||||
|
||||
expect(splitDiffHunks(diff)).toEqual([diff])
|
||||
})
|
||||
|
||||
test("ignores header-like content lines inside a hunk", () => {
|
||||
const diff = [
|
||||
"--- a/file.ts",
|
||||
"+++ b/file.ts",
|
||||
"@@ -1,1 +1,1 @@",
|
||||
"--- not-a-header",
|
||||
"+++ still-not-a-header",
|
||||
].join("\n")
|
||||
|
||||
expect(splitDiffHunks(diff)).toEqual([diff])
|
||||
})
|
||||
|
||||
test("splits multi-hunk diff and preserves headers", () => {
|
||||
const diff = [
|
||||
"--- a/file.ts",
|
||||
"+++ b/file.ts",
|
||||
"@@ -1,2 +1,2 @@",
|
||||
"-one",
|
||||
"+ONE",
|
||||
" two",
|
||||
"@@ -10,2 +10,2 @@",
|
||||
"-ten",
|
||||
"+TEN",
|
||||
" eleven",
|
||||
].join("\n")
|
||||
|
||||
expect(splitDiffHunks(diff)).toEqual([
|
||||
["--- a/file.ts", "+++ b/file.ts", "@@ -1,2 +1,2 @@", "-one", "+ONE", " two"].join("\n"),
|
||||
["--- a/file.ts", "+++ b/file.ts", "@@ -10,2 +10,2 @@", "-ten", "+TEN", " eleven"].join("\n"),
|
||||
])
|
||||
})
|
||||
|
||||
test("splits concatenated multi-file diff with per-file headers", () => {
|
||||
const diff = [
|
||||
"--- a/one.ts",
|
||||
"+++ b/one.ts",
|
||||
"@@ -1,2 +1,2 @@",
|
||||
"-one",
|
||||
"+ONE",
|
||||
"--- a/two.ts",
|
||||
"+++ b/two.ts",
|
||||
"@@ -1,2 +1,2 @@",
|
||||
"-two",
|
||||
"+TWO",
|
||||
].join("\n")
|
||||
|
||||
expect(splitDiffHunks(diff)).toEqual([
|
||||
["--- a/one.ts", "+++ b/one.ts", "@@ -1,2 +1,2 @@", "-one", "+ONE"].join("\n"),
|
||||
["--- a/two.ts", "+++ b/two.ts", "@@ -1,2 +1,2 @@", "-two", "+TWO"].join("\n"),
|
||||
])
|
||||
})
|
||||
|
||||
test("splits multi-file diff when first file has multiple hunks", () => {
|
||||
const diff = [
|
||||
"--- a/one.ts",
|
||||
"+++ b/one.ts",
|
||||
"@@ -1,2 +1,2 @@",
|
||||
"-one",
|
||||
"+ONE",
|
||||
"@@ -10,2 +10,2 @@",
|
||||
"-ten",
|
||||
"+TEN",
|
||||
"--- a/two.ts",
|
||||
"+++ b/two.ts",
|
||||
"@@ -3,2 +3,2 @@",
|
||||
"-two",
|
||||
"+TWO",
|
||||
].join("\n")
|
||||
|
||||
expect(splitDiffHunks(diff)).toEqual([
|
||||
["--- a/one.ts", "+++ b/one.ts", "@@ -1,2 +1,2 @@", "-one", "+ONE"].join("\n"),
|
||||
["--- a/one.ts", "+++ b/one.ts", "@@ -10,2 +10,2 @@", "-ten", "+TEN"].join("\n"),
|
||||
["--- a/two.ts", "+++ b/two.ts", "@@ -3,2 +3,2 @@", "-two", "+TWO"].join("\n"),
|
||||
])
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user