| 226 | } |
| 227 | |
| 228 | function SideBySideDiff({ lines }: SideBySideDiffProps) { |
| 229 | // Build paired columns: match adds to removes |
| 230 | const pairs: Array<{ |
| 231 | left: DiffLine | null; |
| 232 | right: DiffLine | null; |
| 233 | }> = useMemo(() => { |
| 234 | const result: Array<{ left: DiffLine | null; right: DiffLine | null }> = []; |
| 235 | let i = 0; |
| 236 | while (i < lines.length) { |
| 237 | const line = lines[i]; |
| 238 | if (line.type === "equal") { |
| 239 | result.push({ left: line, right: line }); |
| 240 | i++; |
| 241 | } else if (line.type === "remove") { |
| 242 | // Pair with next add if exists |
| 243 | const next = lines[i + 1]; |
| 244 | if (next?.type === "add") { |
| 245 | result.push({ left: line, right: next }); |
| 246 | i += 2; |
| 247 | } else { |
| 248 | result.push({ left: line, right: null }); |
| 249 | i++; |
| 250 | } |
| 251 | } else { |
| 252 | result.push({ left: null, right: line }); |
| 253 | i++; |
| 254 | } |
| 255 | } |
| 256 | return result; |
| 257 | }, [lines]); |
| 258 | |
| 259 | return ( |
| 260 | <div className="font-mono text-xs leading-5 overflow-x-auto"> |
| 261 | <table className="w-full border-collapse"> |
| 262 | <thead> |
| 263 | <tr className="text-surface-500 border-b border-surface-700"> |
| 264 | <th colSpan={2} className="text-left pl-3 py-1 font-normal"> |
| 265 | Before |
| 266 | </th> |
| 267 | <th colSpan={2} className="text-left pl-3 py-1 font-normal border-l border-surface-700"> |
| 268 | After |
| 269 | </th> |
| 270 | </tr> |
| 271 | </thead> |
| 272 | <tbody> |
| 273 | {pairs.map((pair, idx) => ( |
| 274 | <tr key={idx}> |
| 275 | {/* Left column */} |
| 276 | <td |
| 277 | className={cn( |
| 278 | "select-none text-right text-surface-600 pr-2 pl-3 w-10 border-r border-surface-700/50", |
| 279 | pair.left?.type === "remove" && "bg-red-950/50" |
| 280 | )} |
| 281 | > |
| 282 | {pair.left?.oldLineNo ?? ""} |
| 283 | </td> |
| 284 | <td |
| 285 | className={cn( |