import { Box, Text } from 'ink'
import * as React from 'react'
import { Hunk } from 'diff'
import { getTheme, ThemeNames } from '../utils/theme'
import { useMemo } from 'react'
import { wrapText } from '../utils/format'
type Props = {
patch: Hunk
dim: boolean
width: number
overrideTheme?: ThemeNames // custom theme for previews
key?: React.Key
}
export function StructuredDiff({
patch,
dim,
width,
overrideTheme,
}: Props): React.ReactNode {
const diff = useMemo(
() => formatDiff(patch.lines, patch.oldStart, width, dim, overrideTheme),
[patch.lines, patch.oldStart, width, dim, overrideTheme],
)
return diff.map((_, i) => {_})
}
function formatDiff(
lines: string[],
startingLineNumber: number,
width: number,
dim: boolean,
overrideTheme?: ThemeNames,
): React.ReactNode[] {
const theme = getTheme(overrideTheme)
const ls = numberDiffLines(
lines.map(code => {
if (code.startsWith('+')) {
return {
code: ' ' + code.slice(1),
i: 0,
type: 'add',
}
}
if (code.startsWith('-')) {
return {
code: ' ' + code.slice(1),
i: 0,
type: 'remove',
}
}
return { code, i: 0, type: 'nochange' }
}),
startingLineNumber,
)
const maxLineNumber = Math.max(...ls.map(({ i }) => i))
const maxWidth = maxLineNumber.toString().length
return ls.flatMap(({ type, code, i }) => {
const wrappedLines = wrapText(code, width - maxWidth)
return wrappedLines.map((line, lineIndex) => {
const key = `${type}-${i}-${lineIndex}`
switch (type) {
case 'add':
return (
{line}
)
case 'remove':
return (
{line}
)
case 'nochange':
return (
{line}
)
}
})
})
}
function LineNumber({
i,
width,
}: {
i: number | undefined
width: number
}): React.ReactNode {
return (
{i !== undefined ? i.toString().padStart(width) : ' '.repeat(width)}{' '}
)
}
function numberDiffLines(
diff: { code: string; type: string }[],
startLine: number,
): { code: string; type: string; i: number }[] {
let i = startLine
const result: { code: string; type: string; i: number }[] = []
const queue = [...diff]
while (queue.length > 0) {
const { code, type } = queue.shift()!
const line = {
code: code,
type,
i,
}
// Update counters based on change type
switch (type) {
case 'nochange':
i++
result.push(line)
break
case 'add':
i++
result.push(line)
break
case 'remove': {
result.push(line)
let numRemoved = 0
while (queue[0]?.type === 'remove') {
i++
const { code, type } = queue.shift()!
const line = {
code: code,
type,
i,
}
result.push(line)
numRemoved++
}
i -= numRemoved
break
}
}
}
return result
}