Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: "Test"
on:
pull_request:

jobs:
test:
runs-on: ubuntu-latest

permissions:
# Required to checkout the code
contents: read
# Required to put a comment into the pull-request
pull-requests: write

steps:
- uses: actions/checkout@v4
- name: "Install Node"
uses: actions/setup-node@v4
with:
node-version: "20.x"
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 10
- name: Install dependencies
run: pnpm install
- name: "Test"
run: pnpx vitest --coverage.enabled true
- name: "Report Coverage"
# Set if: always() to also generate the report if tests are failing
# Only works if you set `reportOnFailure: true` in your vite config as specified above
if: always()
uses: davelosert/vitest-coverage-report-action@v2
14 changes: 5 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,8 @@ five`;

const docModified = doc.replace(/t/g, "T") + "\nSix";

const diffResult = linesDiffComputers
.getDefault()
.computeDiff(doc, docModified, {
computeMoves: true,
ignoreTrimWhitespace: true,
maxComputationTimeMs: 100,
});
const codiff = new Codiff();
const result = codiff.computeDiff(doc, docmodified);
```

diff result:
Expand Down Expand Up @@ -102,8 +97,9 @@ diff result:
]
}
],
"moves": [],
"hitTimeout": false
"quitEarly": false,
"identical": false,
"moves": []
}
```

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"@types/node": "^22.10.2",
"@typescript-eslint/eslint-plugin": "^8.19.0",
"@typescript-eslint/parser": "^8.19.0",
"@vitest/coverage-v8": "3.0.8",
"copyfiles": "^2.4.1",
"eslint": "^9.19.0",
"globals": "^15.14.0",
Expand Down
6 changes: 6 additions & 0 deletions packages/codiff/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# codiff

## 0.2.0

### Minor Changes

- refact to use Codiff class for diff

## 0.1.2

### Patch Changes
Expand Down
2 changes: 1 addition & 1 deletion packages/codiff/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "codiff",
"version": "0.1.2",
"version": "0.2.0",
"description": "diff algorithm of vscode",
"keywords": [
"diff",
Expand Down
130 changes: 130 additions & 0 deletions packages/codiff/src/codiff.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { LRUCache } from "./common/cache";
import { LineRange } from "./common/lineRange";
import { Range } from "./common/range";
import { splitLines } from "./common/strings";
import {
IDocumentDiff,
IDocumentDiffProviderOptions,
} from "./diff/documentDiffProvider";
import { linesDiffComputers } from "./diff/linesDiffComputers";
import { DetailedLineRangeMapping, RangeMapping } from "./diff/rangeMapping";

export type DiffAlgorithmName = "legacy" | "advanced";

export interface IDiffOptions extends IDocumentDiffProviderOptions {
diffAlgorithm: DiffAlgorithmName;
}

export interface ICodiffOptions {
diffOptions: IDiffOptions;
cacheSize: number;
}

export class Codiff {
protected readonly defaultCacheSize = 100;
protected readonly defaultDiffOption: IDiffOptions = {
ignoreTrimWhitespace: true,
maxComputationTimeMs: 1000,
computeMoves: false,
extendToSubwords: false,
diffAlgorithm: "advanced",
};
private readonly diffCache: LRUCache<string, IDocumentDiff>;
private readonly options: ICodiffOptions;

constructor(options?: Partial<ICodiffOptions>) {
this.options = {
diffOptions: this.defaultDiffOption,
cacheSize: this.defaultCacheSize,
...options,
};

this.diffCache = new LRUCache<string, IDocumentDiff>(
this.options.cacheSize
);
}

private getDiffAlgorithm(name?: DiffAlgorithmName) {
if (name === "legacy") {
return linesDiffComputers.getLegacy();
}
return linesDiffComputers.getDefault();
}

private getFullRange(lines: string[]): Range {
return new Range(
1,
1,
lines.length + 1,
lines[lines.length - 1].length + 1
);
}

protected getContentKey(content: string) {
return content;
}

private getDiffCacheKey(original: string, modified: string) {
return `${this.getContentKey(original)}-codiff-cache-key-${this.getContentKey(modified)}`;
}

computeDiff(
original: string,
modified: string,
options?: Partial<IDiffOptions>
): IDocumentDiff {
const originalLines = splitLines(original);
const modifiedLines = splitLines(modified);

// This significantly speeds up the case when the original file is empty
if (originalLines.length === 1 && originalLines[0].length === 0) {
if (modifiedLines.length === 1 && modifiedLines[0].length === 0) {
return {
changes: [],
identical: true,
quitEarly: false,
moves: [],
};
}

return {
changes: [
new DetailedLineRangeMapping(
new LineRange(1, 2),
new LineRange(1, modifiedLines.length + 1),
[
new RangeMapping(
this.getFullRange(originalLines),
this.getFullRange(modifiedLines)
),
]
),
],
identical: false,
quitEarly: false,
moves: [],
};
}

const cacheKey = this.getDiffCacheKey(original, modified);
const cachedResult = this.diffCache.get(cacheKey);
if (cachedResult) {
return cachedResult;
}
const diffOptions = { ...this.options.diffOptions, ...options };
const diffAlgorithm = this.getDiffAlgorithm(diffOptions.diffAlgorithm);
const result = diffAlgorithm.computeDiff(
originalLines,
modifiedLines,
diffOptions
);
const diffResult: IDocumentDiff = {
changes: result.changes,
quitEarly: result.hitTimeout,
identical: original === modified,
moves: result.moves,
};
this.diffCache.put(cacheKey, diffResult);
return diffResult;
}
}
55 changes: 55 additions & 0 deletions packages/codiff/src/common/cache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
export class LRUCache<Key, Value> {
private cache: Map<Key, Value>;
private readonly capacity: number;

constructor(capacity: number) {
if (capacity < 1) throw new Error("Capacity must be at least 1");
this.capacity = capacity;
this.cache = new Map();
}

/**
* Get a value from the cache and mark it as recently used
*/
get(key: Key): Value | undefined {
if (!this.cache.has(key)) return undefined;

// Remove and re-insert to mark as most recently used
const value = this.cache.get(key)!;
this.cache.delete(key);
this.cache.set(key, value);
return value;
}

/**
* Add/update a value in the cache
*/
put(key: Key, value: Value): void {
// Remove existing entry to update insertion order
if (this.cache.has(key)) {
this.cache.delete(key);
}

// Add new entry (now most recently used)
this.cache.set(key, value);

// Evict least recently used if over capacity
if (this.cache.size > this.capacity) {
const oldestKey = this.cache.keys().next().value;
if (oldestKey) {
this.cache.delete(oldestKey);
}
}
}

clear() {
this.cache.clear();
}

/**
* Get current size of the cache
*/
size(): number {
return this.cache.size;
}
}
121 changes: 0 additions & 121 deletions packages/codiff/src/diff/defaultDocumentDiffProvider.ts

This file was deleted.

Loading