singularity-forge/packages/native/src/diff/index.ts
Lex Christopherson 4c97d59536 feat: add native Rust diff engine for edit tool
Move the edit tool's hot-path diffing operations from JS to native Rust:
- `normalizeForFuzzyMatch`: single-pass Unicode normalization (smart quotes,
  dashes, special spaces, trailing whitespace)
- `fuzzyFindText`: exact-then-fuzzy substring search with UTF-16 index
  conversion for JS compatibility
- `generateDiff`: unified diff generation using the `similar` crate
  (Myers' algorithm with optimizations)

The Rust module at `native/crates/engine/src/diff.rs` exposes three napi
functions. The TypeScript wrapper at `packages/native/src/diff/` follows
the existing module pattern. `edit-diff.ts` now delegates to native
implementations while keeping line-ending handling and file I/O in JS.

18 tests covering normalization, fuzzy matching (including UTF-16 index
correctness with emoji/surrogate pairs), and diff generation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-13 14:11:40 -06:00

61 lines
1.6 KiB
TypeScript

/**
* Native fuzzy text matching and diff generation for the edit tool.
*
* Uses the `similar` Rust crate (Myers' algorithm) for O(n+d) diffing,
* and single-pass Unicode normalization for fuzzy matching.
*/
import { native } from "../native.js";
import type { DiffResult, FuzzyMatchResult } from "./types.js";
export type { DiffResult, FuzzyMatchResult };
/**
* Normalize text for fuzzy matching:
* - Strip trailing whitespace from each line
* - Smart quotes to ASCII equivalents
* - Unicode dashes/hyphens to ASCII hyphen
* - Special Unicode spaces to regular space
*/
export function normalizeForFuzzyMatch(text: string): string {
return (native as Record<string, Function>).normalizeForFuzzyMatch(
text,
) as string;
}
/**
* Find `oldText` in `content`, trying exact match first, then fuzzy match.
*
* When fuzzy matching is used, `contentForReplacement` is the normalized
* version of `content`.
*/
export function fuzzyFindText(
content: string,
oldText: string,
): FuzzyMatchResult {
return (native as Record<string, Function>).fuzzyFindText(
content,
oldText,
) as FuzzyMatchResult;
}
/**
* Generate a unified diff string with line numbers and context.
*
* Uses Myers' diff algorithm via the `similar` Rust crate.
*
* @param oldContent Original text
* @param newContent Modified text
* @param contextLines Number of context lines around changes (default: 4)
*/
export function generateDiff(
oldContent: string,
newContent: string,
contextLines?: number,
): DiffResult {
return (native as Record<string, Function>).generateDiff(
oldContent,
newContent,
contextLines,
) as DiffResult;
}