<p align="center">
<a href="https://mrwangjusttodo.github.io/git-diff-view">
<img src="icon.png" alt="logo" width="180" />
</a>
</p>
# Git Diff View
**A high-performance, feature-rich diff view component for React / Vue / Solid / Svelte / Lynx**
[](https://github.com/MrWangJustToDo/git-diff-view/actions/workflows/deploy.yml)


## ✨ Highlights
- 🎨 **Full Syntax Highlighting** - HAST AST highlighting via Lowlight / Shiki / Lezer
- ⚡ **High Performance** - Web Worker & Node Server support, template mode optimization
- 🎯 **Framework Agnostic** - React, Vue, Solid, Svelte, Lynx (ReactLynx), and CLI support
- 🔧 **Highly Customizable** - Widgets, extend data, themes, and flexible rendering
- 📦 **Multiple Modes** - Git diff or file comparison with Split/Unified views
- 🌗 **SSR/RSC Ready** - Full server-side rendering and React Server Components support
## 🎮 Live Demo
Try it online: [Git Playground](https://mrwangjusttodo.github.io/git-diff-view/?type=git-playground) · [File Playground](https://mrwangjusttodo.github.io/git-diff-view/?type=file-playground) · [GitHub Style](https://mrwangjusttodo.github.io/MrWangJustToDo.io?overlay=open&playGround=GitHub)

<details>
<summary>📸 More Screenshots</summary>





</details>
## 📦 Installation
```bash
# React
pnpm add @git-diff-view/react
# Vue
pnpm add @git-diff-view/vue
# Solid / Svelte
pnpm add @git-diff-view/solid
pnpm add @git-diff-view/svelte
# ReactLynx (Lynx)
pnpm add @git-diff-view/lynx
# CLI (terminal / Ink)
pnpm add @git-diff-view/cli
```
**[View Examples](https://github.com/MrWangJustToDo/git-diff-view/tree/main/ui)** for React / Vue / Solid / Svelte / Lynx
## 🚀 Quick Start
### React
```tsx
import { DiffView, DiffModeEnum } from "@git-diff-view/react";
import "@git-diff-view/react/styles/diff-view.css";
<DiffView
data={{
oldFile: { fileName: "old.ts", content: "..." },
newFile: { fileName: "new.ts", content: "..." },
hunks: ["..."]
}}
diffViewMode={DiffModeEnum.Split}
diffViewTheme="dark"
diffViewHighlight
/>
```
### Advanced Usage
```tsx
import { DiffFile, generateDiffFile } from "@git-diff-view/file";
// File comparison mode
const file = generateDiffFile(
"old.ts", oldContent,
"new.ts", newContent,
"typescript", "typescript"
);
file.initTheme('dark');
file.init();
file.buildSplitDiffLines();
<DiffView diffFile={file} />
```
**More examples:** [React](https://github.com/MrWangJustToDo/git-diff-view/tree/main/ui/react-example) · [Vue](https://github.com/MrWangJustToDo/git-diff-view/tree/main/ui/vue-example) · [Solid](https://github.com/MrWangJustToDo/git-diff-view/tree/main/ui/solid-example)
### CLI (Terminal)
Terminal-based diff viewer built with [Ink](https://github.com/vadimdemedes/ink). Supports Split/Unified views, syntax highlighting, and fixed-height viewport scrolling.
```bash
pnpm add @git-diff-view/cli
```
```tsx
import { DiffView, DiffModeEnum } from "@git-diff-view/cli";
<DiffView
data={{ oldFile, newFile, hunks }}
diffViewMode={DiffModeEnum.Split}
diffViewTheme="dark"
diffViewHighlight
width={120}
height={20} // viewport height in visual rows (optional)
onScrollChange={console.log}
/>
```
`CodeView` renders a single file; both components expose scroll ref methods (`scrollToTop`, `scrollDown`, `getScrollState`, etc.) when `height` is set.
**Docs:** [`packages/cli/readme.md`](./packages/cli/readme.md) · [Scroll API](./packages/cli/readme.md#scroll-api) · [Implementation notes](./packages/cli/SCROLL.md)
## 📚 Packages
**UI Frameworks**
- [`@git-diff-view/react`](https://www.npmjs.com/package/@git-diff-view/react) - React component
- [`@git-diff-view/vue`](https://www.npmjs.com/package/@git-diff-view/vue) - Vue component
- [`@git-diff-view/solid`](https://www.npmjs.com/package/@git-diff-view/solid) - Solid component
- [`@git-diff-view/svelte`](https://www.npmjs.com/package/@git-diff-view/svelte) - Svelte component
- [`@git-diff-view/lynx`](https://www.npmjs.com/package/@git-diff-view/lynx) - ReactLynx DiffView (wrap-first Split/Unified)
- [`@git-diff-view/cli`](https://www.npmjs.com/package/@git-diff-view/cli) - Terminal diff viewer (`DiffView`, `CodeView`, scroll API)
**Core Libraries**
- [`@git-diff-view/core`](https://www.npmjs.com/package/@git-diff-view/core) - Core diff engine (git diff)
- [`@git-diff-view/file`](https://www.npmjs.com/package/@git-diff-view/file) - File comparison engine
**Syntax Highlighters**
- [`@git-diff-view/lowlight`](https://www.npmjs.com/package/@git-diff-view/lowlight) - Built-in highlighter (highlight.js)
- [`@git-diff-view/shiki`](https://www.npmjs.com/package/@git-diff-view/shiki) - Shiki / TextMate (VS Code quality)
- [`@git-diff-view/lezer`](https://www.npmjs.com/package/@git-diff-view/lezer) - Lezer LR parsers (pure JS, no WASM)
| Package | Engine | Best for |
|---------|--------|----------|
| `lowlight` | highlight.js | Default, broad language coverage |
| `shiki` | TextMate / Shiki | Highest accuracy & VS Code themes |
| `lezer` | CodeMirror Lezer | Constrained runtimes (e.g. Lynx), no WASM |
```tsx
import { DiffFile } from "@git-diff-view/core";
import { getDiffViewHighlighter } from "@git-diff-view/shiki";
// or: import { highlighter } from "@git-diff-view/lezer";
const highlighter = await getDiffViewHighlighter();
diffFile.initSyntax({ registerHighlighter: highlighter });
```
Try switching engines in the [live demo](https://mrwangjusttodo.github.io/git-diff-view/) homepage example (lowlight / shiki / lezer menu).
## 🎯 Key Features
- ✅ **Split & Unified Views** - Switch between side-by-side and unified diff modes
- ✅ **Full Syntax Highlighting** - Lowlight, Shiki, or Lezer via the same HAST pipeline
- ✅ **Light & Dark Themes** - Built-in theme support with customization
- ✅ **Line Wrapping** - Toggle line wrapping for long code lines
- ✅ **Widget System** - Add custom interactive widgets to diff lines
- ✅ **Extend Data** - Attach and render custom data per line
- ✅ **Web Worker Support** - Offload processing for better performance
- ✅ **SSR & RSC** - Full server-side rendering support for React and Vue
- ✅ **Diff Match Patch** - Enhanced line-level diff with FastDiff template
- ✅ **Multiple Platforms** - React, Vue, Solid, Svelte, Lynx, and CLI
- ✅ **CLI Scroll Viewport** - Fixed-height terminal viewport with programmatic scroll (`@git-diff-view/cli`)
- ✅ **Lynx DiffView** - Wrap-first Split/Unified on ReactLynx (`@git-diff-view/lynx`)
## ⚡ Advanced Features
### FastDiff Template
Enhanced line-by-line diff visualization for better readability.
```ts
import { setEnableFastDiffTemplate } from '@git-diff-view/core';
setEnableFastDiffTemplate(true);
```
In FastDiff mode, adjacent small changes are merged by a semantic cleanup by
default, so text without word boundaries (e.g. CJK) may be highlighted as a
single block between the first and the last change. To highlight each change
separately, keep FastDiff enabled and disable the semantic cleanup:
```ts
import { setEnableFastDiffTemplate, setEnableDiffSemanticCleanup } from '@git-diff-view/core';
setEnableFastDiffTemplate(true);
setEnableDiffSemanticCleanup(false);
```
> **Note:** `setEnableDiffSemanticCleanup` only affects FastDiff mode
> (`setEnableFastDiffTemplate(true)`). It has no effect in the default
> (relative) mode, which always renders the whole changed span as one block.
>
> `setEnableDiffSemanticCleanup(false)` is **not** a substitute for
> `setEnableFastDiffTemplate(true)` — the two must be used together.
| Default | FastDiff |
|---------|----------|
|  |  |
### Range Mode
Display only a portion of the diff by specifying a line number range. This is useful when you want to focus on a specific section of a large diff.
```tsx
import { DiffFile, SplitSide } from "@git-diff-view/core";
// Create the full diff instance
const diffFile = new DiffFile(/* ... */);
diffFile.init();
diffFile.buildSplitDiffLines();
// Generate a new instance containing only lines 10-50 (based on new file line numbers)
const rangeDiffFile = diffFile.generateInstanceFromLineNumberRange(10, 50);
// Or specify which side's line numbers to use
const rangeDiffFileOld = diffFile.generateInstanceFromLineNumberRange(10, 50, SplitSide.old);
const rangeDiffFileNew = diffFile.generateInstanceFromLineNumberRange(10, 50, SplitSide.new);
<DiffView diffFile={rangeDiffFile} />
```
**API: `generateInstanceFromLineNumberRange(start, end, side?)`**
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `start` | `number` | - | Start line number (must be less than `end`) |
| `end` | `number` | - | End line number |
| `side` | `SplitSide` | `SplitSide.new` | Which file's line numbers to use (`SplitSide.old` or `SplitSide.new`) |
This method creates a new `DiffFile` instance containing only the diff lines within the specified range, making it ideal for:
- Displaying relevant portions of large diffs
- Creating focused code review experiences
- Building paginated diff views
### Template Mode
Optimized rendering mode enabled by default for better performance. [Learn more](https://github.com/MrWangJustToDo/git-diff-view/blob/main/packages/core/src/parse/template.ts)
## 📖 API Reference
### Component Props
| Prop | Type | Description |
|------|------|-------------|
| `data` | `DiffData` | Diff data with `oldFile`, `newFile`, and `hunks` |
| `diffFile` | `DiffFile` | Pre-processed diff file instance |
| `diffViewMode` | `Split \| Unified` | View mode (default: `Split`) |
| `diffViewTheme` | `light \| dark` | Theme (default: `light`) |
| `diffViewHighlight` | `boolean` | Enable syntax highlighting |
| `diffViewWrap` | `boolean` | Enable line wrapping |
| `diffViewFontSize` | `number` | Font size in pixels |
| `diffViewAddWidget` | `boolean` | Enable widget button |
| `renderWidgetLine` | `Function` | Custom widget renderer |
| `renderExtendLine` | `Function` | Custom extend data renderer |
| `extendData` | `ExtendData` | Additional data per line |
| `onAddWidgetClick` | `Function` | Widget button click handler |
### Styling
```tsx
// default style (Pure CSS)
import "@git-diff-view/react/styles/diff-view.css";
// same as diff-view.css
import "@git-diff-view/react/styles/diff-view-pure.css";
```
### Two Usage Modes
**1. Direct Data Mode** - Pass raw diff data
```tsx
<DiffView data={{ oldFile, newFile, hunks }} />
```
**2. DiffFile Instance Mode** - Pre-process for better control
```tsx
const file = new DiffFile(/* ... */);
file.init();
file.buildSplitDiffLines();
<DiffView diffFile={file} />
```
## 🛠️ Development
```bash
git clone https://github.com/MrWangJustToDo/git-diff-view.git
cd git-diff-view
pnpm install
pnpm run build:packages
pnpm run dev:react # or dev:vue, dev:solid, dev:svelte, dev:cli
```
**CLI scroll tests** (after `build:packages`):
```bash
pnpm --filter @git-diff-view/cli test:scroll
pnpm --filter @git-diff-view/cli test:scroll:interactive:diffview
```
## 📄 License
MIT © [MrWangJustToDo](https://github.com/MrWangJustToDo)