A multi-panel text diff viewer and editor built with React 18 and TypeScript. Features a custom Myers diff algorithm implementation with character-level precision, three professional themes, and a complete editing toolkit -- all without external diff libraries.
- Overview
- Features
- Architecture
- Tech Stack
- Getting Started
- Keyboard Shortcuts
- Testing
- Diff Algorithm
- Contributing
- License
Text Diff Editor is a browser-based application for comparing and editing text side by side. It supports 1-, 2-, and 3-panel split views with real-time diff computation, character-level inline highlighting, and a full-featured editor with undo/redo, search and replace, zoom, and pan.
The diff engine is written entirely in TypeScript with no external dependencies, implementing the Myers shortest edit script algorithm for line-level diffs and LCS (Longest Common Subsequence) for character-level inline diffs.
- Myers diff algorithm -- Line-level diff with O(D(N+M)) time complexity
- Character-level inline diffs -- LCS-based highlighting within modified lines
- Real-time computation -- Diffs update as you type
- Async diff support -- Non-blocking computation for large files
- Result caching -- 20-item LRU cache for repeated comparisons
- Multi-panel views -- Switch between 1, 2, or 3 pane split layouts
- Resizable panels -- Drag splitters to adjust panel widths
- 1000-level undo/redo -- Deep operation history per panel
- Line numbers -- Toggleable gutter display
- Word wrap -- Configurable text wrapping
- Current line highlighting -- Visual indicator for active line
- Full-text search -- Find across editor content with match navigation
- Regular expression support -- Regex pattern matching with case sensitivity toggle
- Replace and replace all -- Single or bulk replacements
- Zoom -- Cmd/Ctrl + scroll wheel for scaling
- Pan -- Alt + drag with momentum-based inertia scrolling
- Synchronized scrolling -- Lock scroll position across panels
- Diff navigation -- Jump to next/previous change with F7/Shift+F7
- File System Access API -- Native file open/save on supported browsers (Chrome/Edge)
- Fallback file handling -- Download-based save for unsupported browsers
- Auto-save -- Configurable timer-based automatic saving
- Recent files -- Track recently opened files
- Light theme -- Clean parchment-toned design
- Dark theme -- Low-glare ink-themed interface
- High Contrast theme -- WCAG AA compliant with enhanced color ratios
- CSS Custom Properties -- Theme switching via CSS variables with zero re-renders
- Keyboard-accessible -- Full operation via keyboard shortcuts
src/
|
|-- engine/ # Pure TypeScript diff engine
| |-- DiffEngine.ts # Myers algorithm + LCS inline diffs
| +-- README.md # Algorithm documentation
|
|-- types/
| +-- index.ts # All type definitions (185 lines)
|
|-- context/ # 7 React Context providers
| |-- ThemeContext.tsx # Theme switching (light/dark/high-contrast)
| |-- SettingsContext.tsx # Persisted user preferences
| |-- EditorContext.tsx # Panel content and cursor state
| |-- SplitContext.tsx # Split view configuration
| |-- DiffContext.tsx # Diff computation results
| |-- FileContext.tsx # File operations
| |-- ToastContext.tsx # Notification system
| +-- index.ts # Barrel export
|
|-- hooks/ # 12 custom hooks
| |-- useEditor.ts # Core editor state management
| |-- useUndoRedo.ts # 1000-level undo/redo history
| |-- useSearch.ts # Search and replace logic
| |-- useDiffComputation.ts # Debounced diff computation
| |-- useZoom.ts # Cmd+scroll zoom handler
| |-- usePan.ts # Alt+drag with momentum inertia
| |-- useScrollSync.ts # Synchronized panel scrolling
| |-- useSplitter.ts # Drag-to-resize panels
| |-- useAutoSave.ts # Timer-based auto-save
| |-- useKeyboardShortcuts.ts # Global shortcut registration
| |-- useDebounce.ts # Debounce utility hook
| +-- useWelcomeMessage.ts # First-run welcome message
|
|-- components/
| |-- EditorApp.tsx # Root application component
| |-- toolbar/
| | +-- Toolbar.tsx # File, view, font, and zoom controls
| |-- editor/
| | |-- EditorContainer.tsx # Multi-panel layout orchestrator
| | |-- EditorPanel.tsx # Individual editor panel
| | |-- TextArea.tsx # Controlled textarea with diff overlay
| | |-- LineNumbers.tsx # Gutter line number display
| | |-- PanelHeader.tsx # Panel title and file info
| | +-- Splitter.tsx # Draggable panel divider
| |-- search/
| | +-- SearchBar.tsx # Search/replace floating bar
| |-- status/
| | +-- StatusBar.tsx # Cursor position, diff stats, theme
| +-- ui/
| |-- Toast.tsx # Individual toast notification
| |-- ToastContainer.tsx # Toast stack manager
| +-- ErrorBoundary.tsx # React error boundary
|
|-- utils/ # Helper functions
|-- styles/
| +-- index.css # All styles with CSS Custom Properties
|
+-- __tests__/ # 66 tests across 4 test suites
|-- DiffEngine.test.ts # Diff algorithm unit tests
|-- hooks.test.ts # Custom hook tests
|-- utils.test.ts # Utility function tests
+-- components.test.tsx # Component rendering tests
| Category | Technology |
|---|---|
| Framework | React 18.3.1 |
| Language | TypeScript 5.7.2 (strict mode) |
| Build Tool | Vite 6.0.5 |
| Test Framework | Vitest 2.1.8 |
| Test Utilities | React Testing Library 16.1, jsdom 25 |
| Styling | CSS Custom Properties (zero-runtime themes) |
| CI/CD | GitHub Actions (test, build, deploy) |
| Hosting | GitHub Pages |
| Diff Engine | Custom Myers algorithm (pure TypeScript) |
| File Handling | File System Access API + fallback |
Zero external diff dependencies. The entire diff engine is implemented from scratch in TypeScript.
- Node.js 20 or later
- npm 9 or later
git clone https://github.com/sohei-t/text-diff-editor.git
cd text-diff-editor
npm installnpm run dev # Start development server (Vite HMR)Open http://localhost:5173/text-diff-editor/ in your browser.
npm run build # Type-check and build for production
npm run preview # Preview the production build locallynpm test # Run all 66 tests
npm run test:watch # Run tests in watch mode| Shortcut | Action |
|---|---|
Cmd/Ctrl + N |
New file |
Cmd/Ctrl + O |
Open file |
Cmd/Ctrl + S |
Save file |
Cmd/Ctrl + W |
Close panel |
Cmd/Ctrl + F |
Find |
Cmd/Ctrl + H |
Find and replace |
Cmd/Ctrl + \ |
Toggle split view |
Cmd/Ctrl + Z |
Undo |
Cmd/Ctrl + Shift + Z |
Redo |
F7 |
Jump to next diff |
Shift + F7 |
Jump to previous diff |
Cmd/Ctrl + Scroll |
Zoom in/out |
Alt + Drag |
Pan viewport (with momentum) |
The test suite contains 66 tests organized into four test files covering the core layers of the application:
| Test File | Scope | Tests |
|---|---|---|
DiffEngine.test.ts |
Myers algorithm, inline diffs, caching | Core |
hooks.test.ts |
Custom hook behavior and state management | Hooks |
utils.test.ts |
Utility and helper function correctness | Utils |
components.test.tsx |
Component rendering and user interactions | UI |
Run the full suite:
npm testThe CI pipeline runs on every push and pull request to main, executing type checking, all tests, and a production build before deploying to GitHub Pages.
The diff engine implements two complementary algorithms:
The Myers diff algorithm (1986) finds the shortest edit script between two sequences. It operates in O(D(N+M)) time where D is the edit distance, making it efficient for texts with few changes.
Implementation details:
- Forward pass -- Explores the edit graph along diagonals, tracking the furthest-reaching point for each diagonal at each edit distance
d - Trace recording -- Stores the state vector at each step for backtracking
- Backtracking -- Reconstructs the optimal edit path from the trace, producing a sequence of equal/add/delete operations
- Change building -- Converts raw edit operations into structured
DiffChangeobjects, pairing adjacent delete+add operations as modifications
For lines classified as "modified" (a paired delete and add), the engine computes character-level diffs using a dynamic programming LCS (Longest Common Subsequence) approach:
- Build an (m+1) x (n+1) DP table for the two character arrays
- Backtrack through the table to extract the LCS
- Walk both strings against the LCS to emit equal/add/delete segments
This produces precise, character-by-character highlighting of what changed within a line.
Results are stored in a 20-item LRU cache keyed by text length and a 64-character prefix. Full-text equality is verified on cache hits to prevent false matches.
Contributions are welcome. Please follow these steps:
- Fork the repository
- Create a feature branch (
git checkout -b feat/your-feature) - Make your changes with tests
- Ensure all 66 tests pass (
npm test) - Ensure TypeScript compiles without errors (
npx tsc --noEmit) - Submit a pull request
- TypeScript strict mode is enforced
- All types are defined in
src/types/index.ts - Context providers follow the pattern in
src/context/ - Custom hooks are in
src/hooks/with barrel exports
MIT -- Copyright (c) 2026 sohei-t