[tab] src/utils/diff.ts (typescript)
import * as vscode from 'vscode';

/**
 * Represents a segment of a diff result.
 */
interface DiffSegment {
    type: 'equal' | 'insert' | 'delete';
    value: string;
}

/**
 * Simple character-level diff implementation.
 * Uses a basic approach suitable for small text comparisons.
 * For larger texts, consider using the 'diff' npm package.
 */
export function diffChars(oldText: string, newText: string): DiffSegment[] {
    const segments: DiffSegment[] = [];
    
    // Use longest common subsequence approach for character diff
    const lcs = longestCommonSubsequence(oldText, newText);
    
    let oldIndex = 0;
    let newIndex = 0;
    let lcsIndex = 0;
    
    while (oldIndex < oldText.length || newIndex < newText.length) {
        // Handle deletions (chars in old but not in LCS)
        let deletedChars = '';
        while (oldIndex < oldText.length && 
               (lcsIndex >= lcs.length || oldText[oldIndex] !== lcs[lcsIndex])) {
            deletedChars += oldText[oldIndex];
            oldIndex++;
        }
        if (deletedChars) {
            segments.push({ type: 'delete', value: deletedChars });
        }
        
        // Handle insertions (chars in new but not in LCS)
        let insertedChars = '';
        while (newIndex < newText.length && 
               (lcsIndex >= lcs.length || newText[newIndex] !== lcs[lcsIndex])) {
            insertedChars += newText[newIndex];
            newIndex++;
        }
        if (insertedChars) {
            segments.push({ type: 'insert', value: insertedChars });
        }
        
        // Handle equal chars (from LCS)
        let equalChars = '';
        while (lcsIndex < lcs.length && 
               oldIndex < oldText.length && 
               newIndex < newText.length &&
               oldText[oldIndex] === lcs[lcsIndex] && 
               newText[newIndex] === lcs[lcsIndex]) {
            equalChars += lcs[lcsIndex];
            oldIndex++;
            newIndex++;
            lcsIndex++;
        }
        if (equalChars) {
            segments.push({ type: 'equal', value: equalChars });
        }
    }
    
    return segments;
}

/**
 * Compute the longest common subsequence of two strings.
 */
function longestCommonSubsequence(str1: string, str2: string): string {
    const m = str1.length;
    const n = str2.length;
    
    // Create DP table
    const dp: number[][] = Array(m + 1).fill(null).map(() => Array(n + 1).fill(0));
    
    // Fill DP table
    for (let i = 1; i <= m; i++) {
        for (let j = 1; j <= n; j++) {
            if (str1[i - 1] === str2[j - 1]) {
                dp[i][j] = dp[i - 1][j - 1] + 1;
            } else {
                dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
            }
        }
    }
    
    // Backtrack to find LCS
    let lcs = '';
    let i = m;
    let j = n;
    while (i > 0 && j > 0) {
        if (str1[i - 1] === str2[j - 1]) {
            lcs = str1[i - 1] + lcs;
            i--;
            j--;
        } else if (dp[i - 1][j] > dp[i][j - 1]) {
            i--;
        } else {
            j--;
        }
    }
    
    return lcs;
}

/**
 * Compute VS Code ranges for characters that will be deleted in a replacement.
 * These are characters in the old text that don't appear in the new text.
 */
export function computeDeletionRanges(
    doc: vscode.TextDocument,
    range: vscode.Range,
    newText: string
): vscode.Range[] {
    const oldText = doc.getText(range);
    const diffs = diffChars(oldText, newText);
    
    const deletions: vscode.Range[] = [];
    let offset = doc.offsetAt(range.start);
    
    for (const segment of diffs) {
        if (segment.type === 'delete') {
            const startPos = doc.positionAt(offset);
            const endPos = doc.positionAt(offset + segment.value.length);
            deletions.push(new vscode.Range(startPos, endPos));
        }
        // Only advance offset for non-inserted parts (delete and equal)
        if (segment.type !== 'insert') {
            offset += segment.value.length;
        }
    }
    
    return deletions;
}

/**
 * Check if two texts have meaningful differences.
 * Returns false if texts are identical or only differ in whitespace.
 */
export function hasSignificantDiff(oldText: string, newText: string): boolean {
    if (oldText === newText) {
        return false;
    }
    // Normalize whitespace and compare
    const normalizedOld = oldText.replace(/\s+/g, ' ').trim();
    const normalizedNew = newText.replace(/\s+/g, ' ').trim();
    return normalizedOld !== normalizedNew;
}