| 111 | } |
| 112 | |
| 113 | function UnifiedDiff({ lines, lang: _lang }: UnifiedDiffProps) { |
| 114 | const [expandedHunks, setExpandedHunks] = useState<Set<number>>(new Set()); |
| 115 | |
| 116 | // Identify collapsed regions (equal lines away from changes) |
| 117 | const visible = useMemo(() => { |
| 118 | const changed = new Set<number>(); |
| 119 | lines.forEach((l, i) => { |
| 120 | if (l.type !== "equal") { |
| 121 | for ( |
| 122 | let k = Math.max(0, i - CONTEXT_LINES); |
| 123 | k <= Math.min(lines.length - 1, i + CONTEXT_LINES); |
| 124 | k++ |
| 125 | ) { |
| 126 | changed.add(k); |
| 127 | } |
| 128 | } |
| 129 | }); |
| 130 | return changed; |
| 131 | }, [lines]); |
| 132 | |
| 133 | const items: Array< |
| 134 | | { kind: "line"; line: DiffLine; idx: number } |
| 135 | | { kind: "hunk"; start: number; end: number; count: number } |
| 136 | > = useMemo(() => { |
| 137 | const result: typeof items = []; |
| 138 | let i = 0; |
| 139 | while (i < lines.length) { |
| 140 | if (visible.has(i) || expandedHunks.has(i)) { |
| 141 | result.push({ kind: "line", line: lines[i], idx: i }); |
| 142 | i++; |
| 143 | } else { |
| 144 | // Find the extent of the collapsed hunk |
| 145 | let end = i; |
| 146 | while (end < lines.length && !visible.has(end) && !expandedHunks.has(end)) { |
| 147 | end++; |
| 148 | } |
| 149 | result.push({ kind: "hunk", start: i, end, count: end - i }); |
| 150 | i = end; |
| 151 | } |
| 152 | } |
| 153 | return result; |
| 154 | }, [lines, visible, expandedHunks]); |
| 155 | |
| 156 | return ( |
| 157 | <div className="font-mono text-xs leading-5 overflow-x-auto"> |
| 158 | <table className="w-full border-collapse"> |
| 159 | <tbody> |
| 160 | {items.map((item, idx) => { |
| 161 | if (item.kind === "hunk") { |
| 162 | return ( |
| 163 | <tr key={`hunk-${idx}`}> |
| 164 | <td colSpan={3} className="bg-surface-800/50 text-center py-0.5"> |
| 165 | <button |
| 166 | onClick={() => { |
| 167 | setExpandedHunks((prev) => { |
| 168 | const next = new Set(prev); |
| 169 | for (let k = item.start; k < item.end; k++) next.add(k); |
| 170 | return next; |