// Adapted from jalcoui (MIT) — github.com/jal-co/ui 'use client'; import * as React from 'react'; import type { Language, PrismTheme } from 'prism-react-renderer'; import { Virtuoso } from 'react-virtuoso'; import { cn } from '@djangocfg/ui-core/lib'; import { DIFF_TONE_CLASSES, type DiffLine } from '../types'; import { DiffLineContent } from './DiffLineContent'; interface SplitViewProps { lines: DiffLine[]; numWidth: number; language: Language | null; prismTheme: PrismTheme; } // See UnifiedView for context — same threshold + viewport size keeps both // layouts behaviorally aligned. const VIRTUALIZE_THRESHOLD = 100; const VIRTUAL_VIEWPORT_HEIGHT = 480; /** * Align consecutive `removed` / `added` runs into a pair of columns. * Pure-add rows leave the left column empty; pure-remove rows leave the * right column empty. Context rows duplicate on both sides. * * The pairing is the same shape jalcoui ships — we keep it so downstream * consumers' visual baseline doesn't move. */ function pairLines(lines: DiffLine[]): Array<{ left: DiffLine | null; right: DiffLine | null; }> { const pairs: Array<{ left: DiffLine | null; right: DiffLine | null }> = []; let i = 0; while (i < lines.length) { const line = lines[i]; if (line.type === 'context') { pairs.push({ left: line, right: line }); i++; continue; } if (line.type === 'removed') { const removed: DiffLine[] = []; while (i < lines.length && lines[i].type === 'removed') { removed.push(lines[i]); i++; } const added: DiffLine[] = []; while (i < lines.length && lines[i].type === 'added') { added.push(lines[i]); i++; } const maxLen = Math.max(removed.length, added.length); for (let j = 0; j < maxLen; j++) { pairs.push({ left: j < removed.length ? removed[j] : null, right: j < added.length ? added[j] : null, }); } continue; } // line.type === 'added' with no preceding removed run pairs.push({ left: null, right: line }); i++; } return pairs; } function sidePrefix(side: 'old' | 'new', type: DiffLine['type'] | undefined) { if (!type || type === 'context') return ' '; if (side === 'old' && type === 'removed') return '-'; if (side === 'new' && type === 'added') return '+'; return ' '; } interface HalfProps { line: DiffLine | null; side: 'old' | 'new'; numWidth: number; language: Language | null; prismTheme: PrismTheme; } function Half({ line, side, numWidth, language, prismTheme }: HalfProps) { const tone = line ? DIFF_TONE_CLASSES[line.type] : DIFF_TONE_CLASSES.context; const gutterCh = `calc(${numWidth}ch + 1rem)`; return (