@vscode/diff 0.0.2-0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +64 -0
- package/SECURITY.md +14 -0
- package/dist/diff_wasm_bg-BNcsPO7p.wasm +0 -0
- package/dist/index.d.ts +223 -0
- package/dist/index.js +2583 -0
- package/dist/index.js.map +1 -0
- package/package.json +58 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) Microsoft Corporation.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE
|
package/README.md
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# @vscode/diff
|
|
2
|
+
|
|
3
|
+
> ⚠️ **Experimental** — this package is in early development. The API may change without notice and bugs are expected. Do not depend on it for production workloads yet.
|
|
4
|
+
|
|
5
|
+
A high-performance diff library, ported from the diff algorithm used inside VS Code. Ships with two interchangeable backends:
|
|
6
|
+
|
|
7
|
+
- **TypeScript** (default) — pure JS, no native dependencies.
|
|
8
|
+
- **WebAssembly** — same algorithm compiled from Rust via `wasm-pack`. Typically ~1.5–3× faster on larger inputs.
|
|
9
|
+
|
|
10
|
+
Both produce identical results.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
npm install @vscode/diff
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import { createDiffComputer } from '@vscode/diff';
|
|
22
|
+
|
|
23
|
+
// TS backend (synchronous-feeling, no init required)
|
|
24
|
+
const ts = await createDiffComputer();
|
|
25
|
+
const result = ts.computeDiff(original, modified, {
|
|
26
|
+
ignoreTrimWhitespace: false,
|
|
27
|
+
computeMoves: false,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// WASM backend (loads the .wasm module on first call)
|
|
31
|
+
const wasm = await createDiffComputer({ useWasm: true });
|
|
32
|
+
const result2 = wasm.computeDiff(original, modified);
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
`computeDiff` returns a [`DiffResult`](./src/diff/api/types.ts) containing:
|
|
36
|
+
|
|
37
|
+
- `edits` — an `AnnotatedStringEdit` describing the text-level changes (offset-based, suitable for applying directly).
|
|
38
|
+
- `moves` — detected block moves (when `computeMoves: true`).
|
|
39
|
+
- `hitTimeout` — `true` if computation exceeded `maxComputationTimeMs`.
|
|
40
|
+
|
|
41
|
+
### Options
|
|
42
|
+
|
|
43
|
+
| Option | Default | Description |
|
|
44
|
+
|---|---|---|
|
|
45
|
+
| `maxComputationTimeMs` | `0` (no limit) | Cap the diff time; falls back to a coarser result on timeout. |
|
|
46
|
+
| `ignoreTrimWhitespace` | `false` | Treat lines as equal when they differ only in leading/trailing whitespace. |
|
|
47
|
+
| `computeMoves` | `false` | Detect moved blocks. |
|
|
48
|
+
| `extendToSubwords` | `false` | Refine inner changes to subword boundaries. |
|
|
49
|
+
|
|
50
|
+
## Performance
|
|
51
|
+
|
|
52
|
+
Across all bundled test fixtures (46 cases, total median time in ms):
|
|
53
|
+
|
|
54
|
+
| Backend | Total | vs TS | vs native Rust |
|
|
55
|
+
|---|---:|---:|---:|
|
|
56
|
+
| TS (V8) | 113.7 | 1.00× | 2.86× slower |
|
|
57
|
+
| WASM | 72.5 | 0.64× | 1.82× slower |
|
|
58
|
+
| Native Rust (reference) | 39.8 | 0.35× | 1.00× |
|
|
59
|
+
|
|
60
|
+
Run `node scripts/bench.mjs` to reproduce.
|
|
61
|
+
|
|
62
|
+
## License
|
|
63
|
+
|
|
64
|
+
MIT — see [LICENSE](./LICENSE).
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<!-- BEGIN MICROSOFT SECURITY.MD V1.0.0 BLOCK -->
|
|
2
|
+
|
|
3
|
+
## Security
|
|
4
|
+
|
|
5
|
+
Microsoft takes the security of our software products and services seriously, which
|
|
6
|
+
includes all source code repositories in our GitHub organizations.
|
|
7
|
+
|
|
8
|
+
**Please do not report security vulnerabilities through public GitHub issues.**
|
|
9
|
+
|
|
10
|
+
For security reporting information, locations, contact information, and policies,
|
|
11
|
+
please review the latest guidance for Microsoft repositories at
|
|
12
|
+
[https://aka.ms/SECURITY.md](https://aka.ms/SECURITY.md).
|
|
13
|
+
|
|
14
|
+
<!-- END MICROSOFT SECURITY.MD BLOCK -->
|
|
Binary file
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/*---------------------------------------------------------------------------------------------
|
|
2
|
+
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
4
|
+
*--------------------------------------------------------------------------------------------*/
|
|
5
|
+
/**
|
|
6
|
+
* A range of offsets (0-based).
|
|
7
|
+
*/
|
|
8
|
+
declare class OffsetRange {
|
|
9
|
+
readonly start: number;
|
|
10
|
+
readonly endExclusive: number;
|
|
11
|
+
static ofLength(length: number): OffsetRange;
|
|
12
|
+
static ofStartAndLength(start: number, length: number): OffsetRange;
|
|
13
|
+
constructor(start: number, endExclusive: number);
|
|
14
|
+
get isEmpty(): boolean;
|
|
15
|
+
get length(): number;
|
|
16
|
+
delta(offset: number): OffsetRange;
|
|
17
|
+
deltaStart(offset: number): OffsetRange;
|
|
18
|
+
deltaEnd(offset: number): OffsetRange;
|
|
19
|
+
equals(other: OffsetRange): boolean;
|
|
20
|
+
containsRange(other: OffsetRange): boolean;
|
|
21
|
+
contains(offset: number): boolean;
|
|
22
|
+
join(other: OffsetRange): OffsetRange;
|
|
23
|
+
intersect(other: OffsetRange): OffsetRange | undefined;
|
|
24
|
+
intersects(other: OffsetRange): boolean;
|
|
25
|
+
intersectsOrTouches(other: OffsetRange): boolean;
|
|
26
|
+
slice<T>(arr: readonly T[]): T[];
|
|
27
|
+
toString(): string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* A pair of ranges, one in `original`, one in `modified`.
|
|
32
|
+
*/
|
|
33
|
+
declare class OffsetRangeMapping {
|
|
34
|
+
readonly original: OffsetRange;
|
|
35
|
+
readonly modified: OffsetRange;
|
|
36
|
+
constructor(original: OffsetRange, modified: OffsetRange);
|
|
37
|
+
toString(): string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* A single atomic replacement: replace `range` in the input with `newText`.
|
|
42
|
+
*/
|
|
43
|
+
declare class StringReplacement {
|
|
44
|
+
readonly range: OffsetRange;
|
|
45
|
+
readonly newText: string;
|
|
46
|
+
constructor(range: OffsetRange, newText: string);
|
|
47
|
+
get isEmpty(): boolean;
|
|
48
|
+
toString(): string;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* A set of replacements applied in parallel to one input string.
|
|
52
|
+
*
|
|
53
|
+
* Invariants:
|
|
54
|
+
* - replacements are sorted by `range.start`
|
|
55
|
+
* - replacements do not overlap (touching is allowed)
|
|
56
|
+
* - all ranges live in the same coordinate space (the input passed to `apply`)
|
|
57
|
+
*/
|
|
58
|
+
declare class StringEdit {
|
|
59
|
+
readonly replacements: readonly StringReplacement[];
|
|
60
|
+
static readonly empty: StringEdit;
|
|
61
|
+
static single(replacement: StringReplacement): StringEdit;
|
|
62
|
+
/**
|
|
63
|
+
* Validates that replacements are sorted and non-overlapping.
|
|
64
|
+
*/
|
|
65
|
+
static of(replacements: readonly StringReplacement[]): StringEdit;
|
|
66
|
+
private constructor();
|
|
67
|
+
get isEmpty(): boolean;
|
|
68
|
+
apply(text: string): string;
|
|
69
|
+
/**
|
|
70
|
+
* Returns an edit `E` such that `E.apply(this.apply(input)) === input`.
|
|
71
|
+
*/
|
|
72
|
+
inverse(input: string): StringEdit;
|
|
73
|
+
/**
|
|
74
|
+
* Joins touching replacements and drops no-ops.
|
|
75
|
+
*/
|
|
76
|
+
normalize(): StringEdit;
|
|
77
|
+
toString(): string;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Annotation attached to a replacement. `join` decides whether two
|
|
82
|
+
* adjacent annotations can be merged during `normalize()`.
|
|
83
|
+
*/
|
|
84
|
+
interface IEditData<T> {
|
|
85
|
+
join(other: T): T | undefined;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* An atomic replacement carrying user-defined metadata.
|
|
89
|
+
*/
|
|
90
|
+
declare class AnnotatedStringReplacement<T extends IEditData<T>> {
|
|
91
|
+
readonly range: OffsetRange;
|
|
92
|
+
readonly newText: string;
|
|
93
|
+
readonly data: T;
|
|
94
|
+
constructor(range: OffsetRange, newText: string, data: T);
|
|
95
|
+
get isEmpty(): boolean;
|
|
96
|
+
toString(): string;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* A set of annotated replacements applied in parallel.
|
|
100
|
+
*
|
|
101
|
+
* Invariants: same as `StringEdit` (sorted by `range.start`, non-overlapping,
|
|
102
|
+
* shared coordinate space).
|
|
103
|
+
*/
|
|
104
|
+
declare class AnnotatedStringEdit<T extends IEditData<T>> {
|
|
105
|
+
readonly replacements: readonly AnnotatedStringReplacement<T>[];
|
|
106
|
+
static empty<T extends IEditData<T>>(): AnnotatedStringEdit<T>;
|
|
107
|
+
static single<T extends IEditData<T>>(replacement: AnnotatedStringReplacement<T>): AnnotatedStringEdit<T>;
|
|
108
|
+
static of<T extends IEditData<T>>(replacements: readonly AnnotatedStringReplacement<T>[]): AnnotatedStringEdit<T>;
|
|
109
|
+
private constructor();
|
|
110
|
+
get isEmpty(): boolean;
|
|
111
|
+
apply(text: string): string;
|
|
112
|
+
inverse(input: string, invertData: (data: T) => T): AnnotatedStringEdit<T>;
|
|
113
|
+
/**
|
|
114
|
+
* Joins touching replacements when their annotations join. Drops no-ops.
|
|
115
|
+
*/
|
|
116
|
+
normalize(): AnnotatedStringEdit<T>;
|
|
117
|
+
mapData<U extends IEditData<U>>(f: (data: T) => U): AnnotatedStringEdit<U>;
|
|
118
|
+
stripData(): StringEdit;
|
|
119
|
+
toString(): string;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Diff result annotation.
|
|
123
|
+
*
|
|
124
|
+
* Carries flags describing the nature of the replacement. For now only
|
|
125
|
+
* `isFormattingChange` is exposed; more flags (e.g. move source/target,
|
|
126
|
+
* semantic category) can be added later.
|
|
127
|
+
*/
|
|
128
|
+
declare class DiffAnnotation implements IEditData<DiffAnnotation> {
|
|
129
|
+
/** Whether the replacement is purely a formatting change (whitespace, indentation, etc.). */
|
|
130
|
+
readonly isFormattingChange: boolean;
|
|
131
|
+
static readonly change: DiffAnnotation;
|
|
132
|
+
static readonly formatting: DiffAnnotation;
|
|
133
|
+
private constructor();
|
|
134
|
+
join(other: DiffAnnotation): DiffAnnotation | undefined;
|
|
135
|
+
toString(): string;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* A block of text that was moved between two locations.
|
|
140
|
+
*
|
|
141
|
+
* `range` locates the block in `original` and in `modified` (offsets in the
|
|
142
|
+
* respective full texts). `innerEdit` describes the changes inside the moved
|
|
143
|
+
* block, with offsets local to the slice (i.e. `0` is the start of the slice).
|
|
144
|
+
*/
|
|
145
|
+
declare class Move {
|
|
146
|
+
readonly range: OffsetRangeMapping;
|
|
147
|
+
readonly innerEdit: StringEdit;
|
|
148
|
+
constructor(range: OffsetRangeMapping, innerEdit: StringEdit);
|
|
149
|
+
toString(): string;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Output of a diff computation.
|
|
154
|
+
*/
|
|
155
|
+
declare class DiffResult {
|
|
156
|
+
/**
|
|
157
|
+
* Parallel set of replacements transforming `original` into `modified`,
|
|
158
|
+
* excluding any replacements that are part of a `Move`.
|
|
159
|
+
*
|
|
160
|
+
* When no moves are computed: `edits.apply(original) === modified`.
|
|
161
|
+
*/
|
|
162
|
+
readonly edits: AnnotatedStringEdit<DiffAnnotation>;
|
|
163
|
+
/**
|
|
164
|
+
* Detected text moves. Empty unless `DiffOptions.computeMoves` is set.
|
|
165
|
+
*/
|
|
166
|
+
readonly moves: readonly Move[];
|
|
167
|
+
/**
|
|
168
|
+
* `true` if the algorithm aborted because of `maxComputationTimeMs`.
|
|
169
|
+
* The result is still valid but may be coarser than ideal.
|
|
170
|
+
*/
|
|
171
|
+
readonly hitTimeout: boolean;
|
|
172
|
+
constructor(
|
|
173
|
+
/**
|
|
174
|
+
* Parallel set of replacements transforming `original` into `modified`,
|
|
175
|
+
* excluding any replacements that are part of a `Move`.
|
|
176
|
+
*
|
|
177
|
+
* When no moves are computed: `edits.apply(original) === modified`.
|
|
178
|
+
*/
|
|
179
|
+
edits: AnnotatedStringEdit<DiffAnnotation>,
|
|
180
|
+
/**
|
|
181
|
+
* Detected text moves. Empty unless `DiffOptions.computeMoves` is set.
|
|
182
|
+
*/
|
|
183
|
+
moves: readonly Move[],
|
|
184
|
+
/**
|
|
185
|
+
* `true` if the algorithm aborted because of `maxComputationTimeMs`.
|
|
186
|
+
* The result is still valid but may be coarser than ideal.
|
|
187
|
+
*/
|
|
188
|
+
hitTimeout: boolean);
|
|
189
|
+
}
|
|
190
|
+
interface DiffOptions {
|
|
191
|
+
/**
|
|
192
|
+
* Algorithm hint. Pure-TS computer auto-selects based on input size if omitted.
|
|
193
|
+
* The WASM computer currently ignores this hint.
|
|
194
|
+
*/
|
|
195
|
+
algorithm?: 'myers' | 'dynamic-programming';
|
|
196
|
+
/** Maximum computation time in milliseconds. 0 (default) means no timeout. */
|
|
197
|
+
maxComputationTimeMs?: number;
|
|
198
|
+
/** Ignore leading and trailing whitespace differences. Default: false. */
|
|
199
|
+
ignoreTrimWhitespace?: boolean;
|
|
200
|
+
/** Detect moved blocks of text. Default: false. */
|
|
201
|
+
computeMoves?: boolean;
|
|
202
|
+
/** Extend diffs to subword boundaries. Default: false. */
|
|
203
|
+
extendToSubwords?: boolean;
|
|
204
|
+
}
|
|
205
|
+
interface IDiffComputer {
|
|
206
|
+
computeDiff(original: string, modified: string, options?: DiffOptions): DiffResult;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
interface CreateDiffComputerOptions {
|
|
210
|
+
/**
|
|
211
|
+
* Use the WASM-backed implementation. Loads the WASM module on first creation.
|
|
212
|
+
* Default: false (pure-TS implementation).
|
|
213
|
+
*/
|
|
214
|
+
useWasm?: boolean;
|
|
215
|
+
/**
|
|
216
|
+
* Algorithm hint passed to the computer.
|
|
217
|
+
*/
|
|
218
|
+
algorithm?: 'myers' | 'dynamic-programming';
|
|
219
|
+
}
|
|
220
|
+
declare function createDiffComputer(options?: CreateDiffComputerOptions): Promise<IDiffComputer>;
|
|
221
|
+
|
|
222
|
+
export { AnnotatedStringEdit, AnnotatedStringReplacement, DiffAnnotation, DiffResult, Move, OffsetRange, OffsetRangeMapping, StringEdit, StringReplacement, createDiffComputer };
|
|
223
|
+
export type { CreateDiffComputerOptions, DiffOptions, IDiffComputer, IEditData };
|