Skip to main content

Diff rendering

Pipeline overview

  1. EditTools or FileTools compute diffs and statistics with diff.
  2. Tool responses include diff and stats objects.
  3. ToolResultPresenter formats the diff block and change metrics.

Diff utilities

src/utils/diff.ts
// Generate unified diff format
export const computeDiff = (original: string, updated: string, filePath: string): string =>
Diff.createPatch(filePath, original, updated)

// Calculate change statistics
export const computeStats = (original: string, updated: string): DiffStats => {
const parts = Diff.diffLines(original, updated)
let added = 0
let removed = 0

for (const part of parts) {
if (part.added) added += part.count ?? 0
else if (part.removed) removed += part.count ?? 0
}

return {
linesAdded: added,
linesRemoved: removed,
totalChanges: added + removed,
}
}
  • computeDiff produces a unified diff string consumed by the terminal presenter.
  • DiffStats summarises change size for gating logic and UI display.

Tool responses

src/tools/EditTools.ts
const stats = computeStats(content, updated)
const hasChanges = stats.totalChanges > 0
const diff = hasChanges ? computeDiff(content, updated, filePath) : undefined

return {
success: true,
path: relPath,
size: updated.length,
diff, // only present when changes detected
stats: hasChanges ? stats : undefined,
validation,
}
  • Preview responses reuse the same gating logic but set success: false and include a recommendation.
  • FileTools.writeFile performs identical gating. When includeDiff is false or there are no changes it omits both diff and stats:
src/tools/FileTools.ts
if (includeDiff) {
const computedStats = computeStats(original, content)
const hasChanges = computedStats.totalChanges > 0
stats = hasChanges ? computedStats : undefined
diff = hasChanges ? computeDiff(original, content, relPath) : undefined
}

This avoids noisy empty patches and keeps responses compact while still providing rich context when modifications occur.

Terminal presentation

src/chat/ui/ToolResultPresenter.ts
export const displayEditDiff = (result: ToolResult, toolName: string): void => {
if (toolName !== 'editFile' || !result?.diff) return

console.log(
'\n' +
UI.Colors.BORDER(' ') +
UI.Colors.TOOL(`${UI.Icons.CHANGES} `) +
UI.Colors.MUTED('Changes'),
)
renderDiffBlock(result.diff)
displayChangeStats(result.stats as DiffStats)
}
  • renderDiffBlock applies syntax colouring and indentation to the unified diff.
  • displayChangeStats prints +added, -removed, and total change counts.
  • Similar helpers exist for writeFile previews and tool validations.

Rendering helper

src/chat/ui/presenters/DiffRenderer.ts
export const renderDiffBlock = (diff: string) => {
const lines = diff.split('\n')
for (const line of lines) {
if (line.startsWith('+')) console.log(UI.Colors.DIFF_ADD(` ${line}`))
else if (line.startsWith('-')) console.log(UI.Colors.DIFF_REMOVE(` ${line}`))
else console.log(UI.Colors.DIFF_CONTEXT(` ${line}`))
}
}

The renderer colours additions/removals and keeps context lines muted so the diff stays legible in the terminal.

Usage tips

  • Use previews (previewEdit) to show diffs without touching the filesystem.
  • Watch the stats to decide whether to prompt the assistant for confirmation.
  • Custom tools can reuse computeDiff/computeStats to provide consistent UI.

Source

  • src/utils/diff.ts
  • src/tools/EditTools.ts
  • src/tools/FileTools.ts
  • src/chat/ui/presenters/DiffRenderer.ts
  • src/chat/ui/ToolResultPresenter.ts