@singapore-editor/diff 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.
Files changed (60) hide show
  1. package/README.md +158 -0
  2. package/dist/diffGutter.d.ts +37 -0
  3. package/dist/diffGutter.d.ts.map +1 -0
  4. package/dist/diffGutter.js +189 -0
  5. package/dist/diffGutter.js.map +1 -0
  6. package/dist/diffRows.d.ts +5 -0
  7. package/dist/diffRows.d.ts.map +1 -0
  8. package/dist/diffRows.js +40 -0
  9. package/dist/diffRows.js.map +1 -0
  10. package/dist/diffSyntax.d.ts +124 -0
  11. package/dist/diffSyntax.d.ts.map +1 -0
  12. package/dist/diffSyntax.js +491 -0
  13. package/dist/diffSyntax.js.map +1 -0
  14. package/dist/editorDiffPlugin.d.ts +74 -0
  15. package/dist/editorDiffPlugin.d.ts.map +1 -0
  16. package/dist/editorDiffPlugin.js +546 -0
  17. package/dist/editorDiffPlugin.js.map +1 -0
  18. package/dist/editorOptions.d.ts +15 -0
  19. package/dist/editorOptions.d.ts.map +1 -0
  20. package/dist/editorOptions.js +33 -0
  21. package/dist/editorOptions.js.map +1 -0
  22. package/dist/gutters.d.ts +53 -0
  23. package/dist/gutters.d.ts.map +1 -0
  24. package/dist/gutters.js +122 -0
  25. package/dist/gutters.js.map +1 -0
  26. package/dist/index.d.ts +18 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +11 -0
  29. package/dist/inline.d.ts +3 -0
  30. package/dist/inline.d.ts.map +1 -0
  31. package/dist/inline.js +72 -0
  32. package/dist/inline.js.map +1 -0
  33. package/dist/lines.d.ts +8 -0
  34. package/dist/lines.d.ts.map +1 -0
  35. package/dist/lines.js +49 -0
  36. package/dist/lines.js.map +1 -0
  37. package/dist/liveProjection.d.ts +11 -0
  38. package/dist/liveProjection.d.ts.map +1 -0
  39. package/dist/liveProjection.js +152 -0
  40. package/dist/liveProjection.js.map +1 -0
  41. package/dist/model.d.ts +4 -0
  42. package/dist/model.d.ts.map +1 -0
  43. package/dist/model.js +247 -0
  44. package/dist/model.js.map +1 -0
  45. package/dist/projection.d.ts +18 -0
  46. package/dist/projection.d.ts.map +1 -0
  47. package/dist/projection.js +281 -0
  48. package/dist/projection.js.map +1 -0
  49. package/dist/regions.d.ts +28 -0
  50. package/dist/regions.d.ts.map +1 -0
  51. package/dist/regions.js +0 -0
  52. package/dist/regions.js.map +1 -0
  53. package/dist/style.css +145 -0
  54. package/dist/theme.d.ts +2 -0
  55. package/dist/theme.d.ts.map +1 -0
  56. package/dist/theme.js +43 -0
  57. package/dist/theme.js.map +1 -0
  58. package/dist/types.d.ts +78 -0
  59. package/dist/types.d.ts.map +1 -0
  60. package/package.json +44 -0
@@ -0,0 +1,491 @@
1
+ import { languageIdForPath } from "./lines.js";
2
+ import { createDocumentTextSnapshot, createPieceTableSnapshot } from "@singapore-editor/core/document";
3
+ import { createEmptySyntaxResult, createSyntaxLanguageConfiguration, createSyntaxSnapshotTag, toEditorTokenStore } from "@singapore-editor/core/syntax";
4
+ import { EditorSecondaryViewScheduler } from "@singapore-editor/core/secondary-views";
5
+ //#region src/diffSyntax.ts
6
+ var nextSyntaxControllerId = 0;
7
+ /**
8
+ * One source side's parsed token stream and the session that recolours it on a theme change.
9
+ * Exactly one holder owns it at a time — a host's store, or the controller that adopted it — and
10
+ * that holder disposes it.
11
+ */
12
+ var PreparedDiffSyntaxSource = class {
13
+ side;
14
+ lineStarts;
15
+ listeners = /* @__PURE__ */ new Set();
16
+ disposables = [];
17
+ current = toEditorTokenStore([]);
18
+ disposed = false;
19
+ constructor(side, lineStarts) {
20
+ this.side = side;
21
+ this.lineStarts = lineStarts;
22
+ }
23
+ get tokens() {
24
+ return this.current;
25
+ }
26
+ /** Fires when a theme change recolours the stream. */
27
+ onDidChangeTokens(listener) {
28
+ this.listeners.add(listener);
29
+ return () => this.listeners.delete(listener);
30
+ }
31
+ dispose() {
32
+ if (this.disposed) return;
33
+ this.disposed = true;
34
+ this.listeners.clear();
35
+ disposeMutableSessions(this.disposables);
36
+ }
37
+ /** @internal */
38
+ own(disposable) {
39
+ this.disposables.push(disposable);
40
+ }
41
+ /** @internal */
42
+ setTokens(tokens) {
43
+ this.current = toEditorTokenStore(tokens);
44
+ }
45
+ /** @internal */
46
+ async recolor(session) {
47
+ try {
48
+ const result = await session.refresh();
49
+ if (this.disposed) return;
50
+ this.setTokens(result.tokens);
51
+ for (const listener of this.listeners) listener();
52
+ } catch {}
53
+ }
54
+ };
55
+ /**
56
+ * Parses a diff's sources ahead of the view that will show them, so `setFile(file, rows, prepared)`
57
+ * paints coloured on its first frame. The caller owns the result; an aborted preparation resolves
58
+ * empty.
59
+ */
60
+ async function prepareDiffSyntax(file, options = {}) {
61
+ const created = [];
62
+ const isCurrent = () => options.signal?.aborted !== true;
63
+ try {
64
+ const sources = await loadSyntaxSources(file, options.side ?? "stacked", options.backend, isCurrent, created);
65
+ if (sources) return sources;
66
+ } catch (error) {
67
+ disposeMutableSessions(created);
68
+ throw error;
69
+ }
70
+ disposeMutableSessions(created);
71
+ return [];
72
+ }
73
+ /**
74
+ * The syntax half of the old `DiffView`, with one behavioural change.
75
+ *
76
+ * `DiffView` re-ran the whole pipeline — new sessions, new parse — on every row change, including
77
+ * every expansion toggle (`updatePaneRows` → `refreshSyntaxHighlighting`, DiffView.ts:564). The
78
+ * parse result does not depend on which regions are expanded: the sources are the two *full files*,
79
+ * and expansion only changes which of their lines the projection shows. So the per-side token
80
+ * streams are cached against the file and re-projected synchronously when rows change, which is
81
+ * what lets a toggle repaint without an uncoloured frame (§C10).
82
+ */
83
+ var DiffSyntaxController = class {
84
+ options;
85
+ scheduler = new EditorSecondaryViewScheduler();
86
+ key = `diff.syntax.${nextSyntaxControllerId++}`;
87
+ sources = [];
88
+ sourceSubscriptions = [];
89
+ indexed = [];
90
+ sourcesFile = null;
91
+ file = null;
92
+ rows = [];
93
+ tokens = [];
94
+ disposed = false;
95
+ ready = true;
96
+ constructor(options) {
97
+ this.options = options;
98
+ }
99
+ /** A disposed controller is terminal — its scheduler never runs another task. */
100
+ isDisposed() {
101
+ return this.disposed;
102
+ }
103
+ isReady() {
104
+ return this.ready;
105
+ }
106
+ getTokens() {
107
+ return this.tokens;
108
+ }
109
+ /**
110
+ * New file: drop the cached streams, then adopt `prepared` when it covers this side, or reparse.
111
+ * A preparation still running is awaited instead of parsing the same file twice. Takes ownership
112
+ * of `prepared` either way.
113
+ */
114
+ setFile(file, rows, prepared = []) {
115
+ this.file = file;
116
+ this.ready = !file || this.options.enabled === false;
117
+ this.rows = rows;
118
+ this.tokens = [];
119
+ this.disposeSources();
120
+ this.scheduler.cancel(this.key);
121
+ if (!file || this.ready) {
122
+ Promise.resolve(prepared).then(disposePrepared, () => void 0);
123
+ return;
124
+ }
125
+ if (isPromiseLike(prepared)) {
126
+ const settle = (sources) => {
127
+ if (this.settlePrepared(file, sources)) this.options.onDidChangeTokens();
128
+ };
129
+ prepared.then(settle, () => settle([]));
130
+ return;
131
+ }
132
+ this.settlePrepared(file, prepared);
133
+ }
134
+ /** Whether `prepared` was adopted; otherwise it is disposed, and a current file reparses. */
135
+ settlePrepared(file, prepared) {
136
+ if (this.disposed || this.file !== file || this.ready) {
137
+ disposePrepared(prepared);
138
+ return false;
139
+ }
140
+ if (!coversSide(prepared, this.options.side)) {
141
+ disposePrepared(prepared);
142
+ this.load(file);
143
+ return false;
144
+ }
145
+ this.adoptSources(file, prepared);
146
+ return true;
147
+ }
148
+ /** Same file, different rows — an expansion toggle. Re-project what is already parsed. */
149
+ setRows(rows) {
150
+ this.rows = rows;
151
+ this.reproject();
152
+ }
153
+ /**
154
+ * Hands the parsed streams of the current file to the caller, which then owns them; empty while
155
+ * a parse is still running. The controller keeps projecting their current tokens, and stops
156
+ * following their recolouring.
157
+ */
158
+ release() {
159
+ if (!this.ready || this.sourcesFile !== this.file) return [];
160
+ const sources = this.sources;
161
+ this.unsubscribeSources();
162
+ this.sources = [];
163
+ return sources;
164
+ }
165
+ dispose() {
166
+ this.disposed = true;
167
+ this.scheduler.cancel(this.key);
168
+ this.scheduler.dispose();
169
+ this.disposeSources();
170
+ this.tokens = [];
171
+ }
172
+ reproject() {
173
+ if (this.indexed.length === 0 || this.sourcesFile !== this.file) {
174
+ this.tokens = [];
175
+ return;
176
+ }
177
+ this.tokens = projectIndexedTokens(this.rows, this.options.side, this.indexed);
178
+ }
179
+ load(file) {
180
+ if (this.options.enabled === false) return;
181
+ const created = [];
182
+ this.scheduler.schedule({
183
+ key: this.key,
184
+ taskClass: "background-derived",
185
+ priority: "low",
186
+ tags: {
187
+ configuration: "syntax",
188
+ viewport: this.options.side
189
+ },
190
+ run: (context) => loadSyntaxSources(file, this.options.side, this.options.backend, () => context.isCurrent(), created),
191
+ apply: (sources) => this.applySources(file, sources, created),
192
+ fail: () => {
193
+ disposeMutableSessions(created);
194
+ if (this.file !== file) return;
195
+ this.ready = true;
196
+ this.options.onDidChangeTokens();
197
+ },
198
+ cancel: () => disposeMutableSessions(created)
199
+ });
200
+ }
201
+ applySources(file, sources, created) {
202
+ if (this.file !== file) {
203
+ disposeMutableSessions(created);
204
+ return;
205
+ }
206
+ created.length = 0;
207
+ this.adoptSources(file, sources ?? []);
208
+ this.options.onDidChangeTokens();
209
+ }
210
+ adoptSources(file, sources) {
211
+ this.disposeSources();
212
+ this.sources = sources;
213
+ this.sourceSubscriptions = sources.map((source) => source.onDidChangeTokens(() => this.recolor(file)));
214
+ this.indexed = indexPreparedSources(sources);
215
+ this.ready = true;
216
+ this.sourcesFile = file;
217
+ this.reproject();
218
+ }
219
+ recolor(file) {
220
+ if (this.disposed || this.file !== file) return;
221
+ this.indexed = indexPreparedSources(this.sources);
222
+ this.reproject();
223
+ this.options.onDidChangeTokens();
224
+ }
225
+ unsubscribeSources() {
226
+ for (const unsubscribe of this.sourceSubscriptions) unsubscribe();
227
+ this.sourceSubscriptions = [];
228
+ }
229
+ disposeSources() {
230
+ this.unsubscribeSources();
231
+ disposeMutableSessions([...this.sources]);
232
+ this.sources = [];
233
+ this.indexed = [];
234
+ this.sourcesFile = null;
235
+ }
236
+ };
237
+ async function loadSyntaxSources(file, side, backend, isCurrent, created) {
238
+ const service = diffSyntaxService(diffSyntaxBackend(backend));
239
+ if (!service) return null;
240
+ const stale = () => {
241
+ disposeMutableSessions(created);
242
+ return null;
243
+ };
244
+ const sources = [];
245
+ for (const document of syntaxDocumentsForFile(file, side)) {
246
+ if (!isCurrent()) return stale();
247
+ const session = await service.createSession(document);
248
+ if (!session) continue;
249
+ const source = new PreparedDiffSyntaxSource(document.side, document.lineStarts);
250
+ created.push(source);
251
+ source.own(session);
252
+ if (!isCurrent()) return stale();
253
+ const unsubscribe = session.onDidChangeTheme?.(() => void source.recolor(session));
254
+ if (unsubscribe) source.own({ dispose: unsubscribe });
255
+ const result = await session.refresh();
256
+ if (!isCurrent()) return stale();
257
+ source.setTokens(result.tokens);
258
+ sources.push(source);
259
+ }
260
+ return sources;
261
+ }
262
+ function disposePrepared(sources) {
263
+ for (const source of sources) source.dispose();
264
+ }
265
+ function isPromiseLike(value) {
266
+ return "then" in value;
267
+ }
268
+ function coversSide(sources, side) {
269
+ const sides = new Set(sources.map((source) => source.side));
270
+ if (side === "stacked") return sides.size === 2 && sources.length === 2;
271
+ return sides.has(side) && sources.length === 1;
272
+ }
273
+ function indexPreparedSources(sources) {
274
+ return sources.map((source) => ({
275
+ lineStarts: source.lineStarts,
276
+ side: source.side,
277
+ tokens: source.tokens
278
+ }));
279
+ }
280
+ function diffSyntaxBackend(backend) {
281
+ return backend ?? { kind: "tree-sitter" };
282
+ }
283
+ function projectDiffSyntaxTokens({ rows, side, sources }) {
284
+ return projectIndexedTokens(rows, side, indexTokenSources(sources));
285
+ }
286
+ function projectIndexedTokens(rows, side, sources) {
287
+ const projectedTokens = [];
288
+ let rowOffset = 0;
289
+ for (const row of rows) {
290
+ const source = tokenSourceForRow(sources, row, side);
291
+ if (source) appendRowSyntaxTokens(projectedTokens, {
292
+ lineStarts: source.lineStarts,
293
+ row,
294
+ rowOffset,
295
+ side: source.side,
296
+ tokens: source.tokens
297
+ });
298
+ rowOffset += row.text.length + 1;
299
+ }
300
+ return projectedTokens;
301
+ }
302
+ function diffSyntaxService(backend) {
303
+ if (backend.kind === "tree-sitter") return treeSitterDiffSyntaxService(backend.provider ?? null);
304
+ return highlighterDiffSyntaxService(backend.provider ?? null);
305
+ }
306
+ function highlighterDiffSyntaxService(provider) {
307
+ if (!provider) return null;
308
+ return { createSession: async (document) => highlighterDiffSyntaxSession(provider, document) };
309
+ }
310
+ function highlighterDiffSyntaxSession(provider, document) {
311
+ const session = provider.createSession(highlighterSessionOptions(document));
312
+ if (!session) return null;
313
+ return tokenHighlighterDiffSyntaxSession(document, session);
314
+ }
315
+ function treeSitterDiffSyntaxService(provider) {
316
+ if (!provider) return null;
317
+ return { createSession: async (document) => treeSitterDiffSyntaxSession(provider, document) };
318
+ }
319
+ function treeSitterDiffSyntaxSession(provider, document) {
320
+ const session = provider.createSession(syntaxSessionOptions(document));
321
+ if (!session) return null;
322
+ return {
323
+ dispose: () => session.dispose(),
324
+ refresh: () => session.refresh(document.textSnapshot)
325
+ };
326
+ }
327
+ function tokenHighlighterDiffSyntaxSession(document, session) {
328
+ return {
329
+ onDidChangeTheme: session.onDidChangeTheme,
330
+ dispose: () => session.dispose(),
331
+ refresh: async () => {
332
+ const result = await session.refresh(document.textSnapshot);
333
+ return syntaxResultFromTokens(document.request, result.tokens);
334
+ }
335
+ };
336
+ }
337
+ function syntaxSessionOptions(document) {
338
+ return {
339
+ documentId: document.documentId,
340
+ includeCaptures: document.request.language.includeCaptures,
341
+ includeHighlights: document.request.language.includeHighlights,
342
+ languageId: document.languageId,
343
+ snapshot: document.request.snapshot,
344
+ syntaxMode: document.request.language.mode === "range" ? "range" : "full",
345
+ textSnapshot: document.textSnapshot
346
+ };
347
+ }
348
+ function highlighterSessionOptions(document) {
349
+ return {
350
+ documentId: document.documentId,
351
+ languageId: document.languageId,
352
+ snapshot: document.request.snapshot,
353
+ textSnapshot: document.textSnapshot
354
+ };
355
+ }
356
+ function syntaxResultFromTokens(request, tokens) {
357
+ return {
358
+ ...createEmptySyntaxResult({
359
+ language: request.language,
360
+ requestedRanges: request.requestedRanges,
361
+ snapshot: request.snapshotTag
362
+ }),
363
+ tokens
364
+ };
365
+ }
366
+ /**
367
+ * The two full files, parsed whole.
368
+ *
369
+ * This is the language the diff is highlighted in, and §C11 is the reason it lives here rather than
370
+ * on the editor: the editor's own document is the *interleaved* buffer, and giving that a language
371
+ * feeds a garbage parse into folds, brackets and injections.
372
+ */
373
+ function syntaxDocumentsForFile(file, side) {
374
+ return syntaxSourcesForSide(file, side).map((source) => syntaxDocument(file, source));
375
+ }
376
+ function syntaxSourcesForSide(file, side) {
377
+ if (side === "stacked") return [syntaxSource(file.oldLines, "old"), syntaxSource(file.newLines, "new")];
378
+ return [syntaxSource(side === "old" ? file.oldLines : file.newLines, side)];
379
+ }
380
+ function syntaxSource(lines, side) {
381
+ const text = lines.join("\n");
382
+ return {
383
+ lineStarts: lineStartsForLines(lines),
384
+ side,
385
+ text
386
+ };
387
+ }
388
+ function syntaxDocument(file, source) {
389
+ const snapshot = createPieceTableSnapshot(source.text);
390
+ const textSnapshot = createDocumentTextSnapshot(snapshot, source.text);
391
+ const documentId = `${file.path}#diff-${source.side}`;
392
+ const languageId = diffSyntaxLanguageId(file);
393
+ const request = {
394
+ editSummary: null,
395
+ language: createSyntaxLanguageConfiguration({
396
+ includeCaptures: true,
397
+ includeHighlights: true,
398
+ languageId,
399
+ mode: "full"
400
+ }),
401
+ requestedRanges: [{
402
+ startIndex: 0,
403
+ endIndex: snapshot.length
404
+ }],
405
+ snapshot,
406
+ snapshotTag: createSyntaxSnapshotTag({
407
+ documentId,
408
+ length: snapshot.length,
409
+ version: 0
410
+ }),
411
+ textSnapshot
412
+ };
413
+ return {
414
+ ...source,
415
+ documentId,
416
+ languageId,
417
+ request,
418
+ textSnapshot
419
+ };
420
+ }
421
+ function diffSyntaxLanguageId(file) {
422
+ return file.languageId ?? languageIdForPath(file.path);
423
+ }
424
+ /**
425
+ * A row reads its line's tokens by bisection, and rows do not visit source lines in order once
426
+ * expanded regions interleave. The store is sorted whichever producer made the tokens.
427
+ */
428
+ function indexTokenSources(sources) {
429
+ return sources.map((source) => ({
430
+ lineStarts: source.lineStarts,
431
+ side: source.side,
432
+ tokens: toEditorTokenStore(source.tokens)
433
+ }));
434
+ }
435
+ function tokenSourceForRow(sources, row, side) {
436
+ const sourceSide = sourceSideForRow(row, side);
437
+ return sources.find((source) => source.side === sourceSide) ?? null;
438
+ }
439
+ function appendRowSyntaxTokens(projectedTokens, { lineStarts, row, rowOffset, side, tokens }) {
440
+ const lineNumber = sourceLineNumberForRow(row, side);
441
+ if (lineNumber === void 0) return;
442
+ const lineStart = lineStarts[lineNumber - 1];
443
+ const nextLineStart = lineStarts[lineNumber];
444
+ if (lineStart === void 0) return;
445
+ const lineEnd = Math.min(nextLineStart === void 0 ? Number.POSITIVE_INFINITY : nextLineStart - 1, lineStart + row.text.length);
446
+ const last = tokens.firstStartingAtOrAfter(lineEnd);
447
+ tokens.forEachInRange(tokens.firstEndingAfter(lineStart, last), last, (start, end, styleId) => {
448
+ appendProjectedToken(projectedTokens, {
449
+ start,
450
+ end,
451
+ style: tokens.styles[styleId]
452
+ }, lineStart, lineEnd, rowOffset);
453
+ });
454
+ }
455
+ function appendProjectedToken(projectedTokens, token, lineStart, lineEnd, rowOffset) {
456
+ if (token.end <= lineStart) return;
457
+ if (token.start >= lineEnd) return;
458
+ const start = Math.max(token.start, lineStart);
459
+ const end = Math.min(token.end, lineEnd);
460
+ if (end <= start) return;
461
+ projectedTokens.push({
462
+ end: rowOffset + end - lineStart,
463
+ start: rowOffset + start - lineStart,
464
+ style: token.style
465
+ });
466
+ }
467
+ function sourceLineNumberForRow(row, side) {
468
+ if (side === "old") return row.oldLineNumber;
469
+ return row.newLineNumber;
470
+ }
471
+ function sourceSideForRow(row, side) {
472
+ if (side === "old" || side === "new") return side;
473
+ if (row.type === "deletion") return "old";
474
+ return "new";
475
+ }
476
+ function lineStartsForLines(lines) {
477
+ const starts = [];
478
+ let offset = 0;
479
+ for (const line of lines) {
480
+ starts.push(offset);
481
+ offset += line.length + 1;
482
+ }
483
+ return starts;
484
+ }
485
+ function disposeMutableSessions(sessions) {
486
+ while (sessions.length > 0) sessions.pop()?.dispose();
487
+ }
488
+ //#endregion
489
+ export { DiffSyntaxController, PreparedDiffSyntaxSource, diffSyntaxBackend, prepareDiffSyntax, projectDiffSyntaxTokens };
490
+
491
+ //# sourceMappingURL=diffSyntax.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diffSyntax.js","names":[],"sources":["../src/diffSyntax.ts"],"sourcesContent":["import {\n createDocumentTextSnapshot,\n type DocumentTextSnapshot,\n createPieceTableSnapshot,\n} from '@singapore-editor/core/document'\nimport type {\n EditorHighlighterProvider,\n EditorHighlighterSession,\n} from '@singapore-editor/core/extensions'\nimport {\n createEmptySyntaxResult,\n createSyntaxLanguageConfiguration,\n createSyntaxSnapshotTag,\n type EditorSyntaxProvider,\n type EditorSyntaxResult,\n type EditorSyntaxServiceRequest,\n type EditorSyntaxSessionOptions,\n type EditorToken,\n type EditorTokenInput,\n type EditorTokenStore,\n toEditorTokenStore,\n} from '@singapore-editor/core/syntax'\nimport { EditorSecondaryViewScheduler } from '@singapore-editor/core/secondary-views'\nimport { languageIdForPath } from './lines'\nimport type { DiffFile, DiffRenderRow, DiffSyntaxBackend } from './types'\n\ntype DiffSyntaxSide = 'old' | 'new' | 'stacked'\ntype DiffSyntaxSourceSide = 'old' | 'new'\n\nlet nextSyntaxControllerId = 0\n\nexport type DiffSyntaxTokenSource = {\n readonly lineStarts: readonly number[]\n readonly side: DiffSyntaxSourceSide\n readonly tokens: EditorTokenInput\n}\n\nexport type ProjectDiffSyntaxTokensOptions = {\n readonly rows: readonly DiffRenderRow[]\n readonly side: DiffSyntaxSide\n readonly sources: readonly DiffSyntaxTokenSource[]\n}\n\nexport type DiffSyntaxControllerOptions = {\n readonly side: DiffSyntaxSide\n readonly backend?: DiffSyntaxBackend\n readonly enabled?: boolean\n /** Fires when a *newly parsed* token stream lands; row-driven re-projection is synchronous. */\n readonly onDidChangeTokens: () => void\n}\n\n/** Streams from `prepareDiffSyntax`, or a preparation of them still running. */\nexport type PreparedDiffSyntaxInput =\n | readonly PreparedDiffSyntaxSource[]\n | Promise<readonly PreparedDiffSyntaxSource[]>\n\nexport type PrepareDiffSyntaxOptions = {\n readonly backend?: DiffSyntaxBackend\n /** Which pane the sources are for. `stacked` prepares both source sides. */\n readonly side?: DiffSyntaxSide\n readonly signal?: AbortSignal\n}\n\n/**\n * One source side's parsed token stream and the session that recolours it on a theme change.\n * Exactly one holder owns it at a time — a host's store, or the controller that adopted it — and\n * that holder disposes it.\n */\nexport class PreparedDiffSyntaxSource {\n private readonly listeners = new Set<() => void>()\n private readonly disposables: { dispose(): void }[] = []\n private current: EditorTokenStore = toEditorTokenStore([])\n private disposed = false\n\n constructor(\n readonly side: DiffSyntaxSourceSide,\n readonly lineStarts: readonly number[],\n ) {}\n\n get tokens(): EditorTokenStore {\n return this.current\n }\n\n /** Fires when a theme change recolours the stream. */\n onDidChangeTokens(listener: () => void): () => void {\n this.listeners.add(listener)\n return () => this.listeners.delete(listener)\n }\n\n dispose(): void {\n if (this.disposed) return\n\n this.disposed = true\n this.listeners.clear()\n disposeMutableSessions(this.disposables)\n }\n\n /** @internal */\n own(disposable: { dispose(): void }): void {\n this.disposables.push(disposable)\n }\n\n /** @internal */\n setTokens(tokens: EditorTokenInput): void {\n this.current = toEditorTokenStore(tokens)\n }\n\n /** @internal */\n async recolor(session: DiffSyntaxServiceSession): Promise<void> {\n try {\n const result = await session.refresh()\n if (this.disposed) return\n\n this.setTokens(result.tokens)\n for (const listener of this.listeners) listener()\n } catch {\n // Keep the last colors if the worker fails; a subsequent change can retry.\n }\n }\n}\n\n/**\n * Parses a diff's sources ahead of the view that will show them, so `setFile(file, rows, prepared)`\n * paints coloured on its first frame. The caller owns the result; an aborted preparation resolves\n * empty.\n */\nexport async function prepareDiffSyntax(\n file: DiffFile,\n options: PrepareDiffSyntaxOptions = {},\n): Promise<readonly PreparedDiffSyntaxSource[]> {\n const created: PreparedDiffSyntaxSource[] = []\n const isCurrent = () => options.signal?.aborted !== true\n try {\n const sources = await loadSyntaxSources(\n file,\n options.side ?? 'stacked',\n options.backend,\n isCurrent,\n created,\n )\n if (sources) return sources\n } catch (error) {\n disposeMutableSessions(created)\n throw error\n }\n\n disposeMutableSessions(created)\n return []\n}\n\n/**\n * The syntax half of the old `DiffView`, with one behavioural change.\n *\n * `DiffView` re-ran the whole pipeline — new sessions, new parse — on every row change, including\n * every expansion toggle (`updatePaneRows` → `refreshSyntaxHighlighting`, DiffView.ts:564). The\n * parse result does not depend on which regions are expanded: the sources are the two *full files*,\n * and expansion only changes which of their lines the projection shows. So the per-side token\n * streams are cached against the file and re-projected synchronously when rows change, which is\n * what lets a toggle repaint without an uncoloured frame (§C10).\n */\nexport class DiffSyntaxController {\n private readonly scheduler = new EditorSecondaryViewScheduler()\n private readonly key = `diff.syntax.${nextSyntaxControllerId++}`\n private sources: readonly PreparedDiffSyntaxSource[] = []\n private sourceSubscriptions: (() => void)[] = []\n private indexed: readonly IndexedTokenSource[] = []\n private sourcesFile: DiffFile | null = null\n private file: DiffFile | null = null\n private rows: readonly DiffRenderRow[] = []\n private tokens: readonly EditorToken[] = []\n private disposed = false\n private ready = true\n\n constructor(private readonly options: DiffSyntaxControllerOptions) {}\n\n /** A disposed controller is terminal — its scheduler never runs another task. */\n isDisposed(): boolean {\n return this.disposed\n }\n\n isReady(): boolean {\n return this.ready\n }\n\n getTokens(): readonly EditorToken[] {\n return this.tokens\n }\n\n /**\n * New file: drop the cached streams, then adopt `prepared` when it covers this side, or reparse.\n * A preparation still running is awaited instead of parsing the same file twice. Takes ownership\n * of `prepared` either way.\n */\n setFile(\n file: DiffFile | null,\n rows: readonly DiffRenderRow[],\n prepared: PreparedDiffSyntaxInput = [],\n ): void {\n this.file = file\n this.ready = !file || this.options.enabled === false\n this.rows = rows\n this.tokens = []\n this.disposeSources()\n this.scheduler.cancel(this.key)\n if (!file || this.ready) {\n void Promise.resolve(prepared).then(disposePrepared, () => undefined)\n return\n }\n if (isPromiseLike(prepared)) {\n const settle = (sources: readonly PreparedDiffSyntaxSource[]) => {\n if (this.settlePrepared(file, sources)) this.options.onDidChangeTokens()\n }\n void prepared.then(settle, () => settle([]))\n return\n }\n this.settlePrepared(file, prepared)\n }\n\n /** Whether `prepared` was adopted; otherwise it is disposed, and a current file reparses. */\n private settlePrepared(file: DiffFile, prepared: readonly PreparedDiffSyntaxSource[]): boolean {\n if (this.disposed || this.file !== file || this.ready) {\n disposePrepared(prepared)\n return false\n }\n if (!coversSide(prepared, this.options.side)) {\n disposePrepared(prepared)\n this.load(file)\n return false\n }\n\n this.adoptSources(file, prepared)\n return true\n }\n\n /** Same file, different rows — an expansion toggle. Re-project what is already parsed. */\n setRows(rows: readonly DiffRenderRow[]): void {\n this.rows = rows\n this.reproject()\n }\n\n /**\n * Hands the parsed streams of the current file to the caller, which then owns them; empty while\n * a parse is still running. The controller keeps projecting their current tokens, and stops\n * following their recolouring.\n */\n release(): readonly PreparedDiffSyntaxSource[] {\n if (!this.ready || this.sourcesFile !== this.file) return []\n\n const sources = this.sources\n this.unsubscribeSources()\n this.sources = []\n return sources\n }\n\n dispose(): void {\n this.disposed = true\n this.scheduler.cancel(this.key)\n this.scheduler.dispose()\n this.disposeSources()\n this.tokens = []\n }\n\n private reproject(): void {\n if (this.indexed.length === 0 || this.sourcesFile !== this.file) {\n this.tokens = []\n return\n }\n\n this.tokens = projectIndexedTokens(this.rows, this.options.side, this.indexed)\n }\n\n private load(file: DiffFile): void {\n if (this.options.enabled === false) return\n\n const created: PreparedDiffSyntaxSource[] = []\n this.scheduler.schedule({\n key: this.key,\n taskClass: 'background-derived',\n priority: 'low',\n tags: { configuration: 'syntax', viewport: this.options.side },\n run: (context) =>\n loadSyntaxSources(\n file,\n this.options.side,\n this.options.backend,\n () => context.isCurrent(),\n created,\n ),\n apply: (sources) => this.applySources(file, sources, created),\n fail: () => {\n disposeMutableSessions(created)\n if (this.file !== file) return\n this.ready = true\n this.options.onDidChangeTokens()\n },\n cancel: () => disposeMutableSessions(created),\n })\n }\n\n private applySources(\n file: DiffFile,\n sources: readonly PreparedDiffSyntaxSource[] | null,\n created: PreparedDiffSyntaxSource[],\n ): void {\n if (this.file !== file) {\n disposeMutableSessions(created)\n return\n }\n\n // Adopted: the task's cancel path must no longer reach them.\n created.length = 0\n this.adoptSources(file, sources ?? [])\n this.options.onDidChangeTokens()\n }\n\n private adoptSources(file: DiffFile, sources: readonly PreparedDiffSyntaxSource[]): void {\n this.disposeSources()\n this.sources = sources\n this.sourceSubscriptions = sources.map((source) =>\n source.onDidChangeTokens(() => this.recolor(file)),\n )\n // Indexed here, once per parse. The index depends only on the token streams, and expansion\n // does not change those — so rebuilding it inside every re-projection would be repeated work\n // plus a fresh Map and N arrays of garbage on each toggle.\n this.indexed = indexPreparedSources(sources)\n this.ready = true\n this.sourcesFile = file\n this.reproject()\n }\n\n private recolor(file: DiffFile): void {\n if (this.disposed || this.file !== file) return\n\n this.indexed = indexPreparedSources(this.sources)\n this.reproject()\n this.options.onDidChangeTokens()\n }\n\n private unsubscribeSources(): void {\n for (const unsubscribe of this.sourceSubscriptions) unsubscribe()\n this.sourceSubscriptions = []\n }\n\n private disposeSources(): void {\n this.unsubscribeSources()\n disposeMutableSessions([...this.sources])\n this.sources = []\n this.indexed = []\n this.sourcesFile = null\n }\n}\n\nasync function loadSyntaxSources(\n file: DiffFile,\n side: DiffSyntaxSide,\n backend: DiffSyntaxBackend | undefined,\n isCurrent: () => boolean,\n created: PreparedDiffSyntaxSource[],\n): Promise<readonly PreparedDiffSyntaxSource[] | null> {\n const service = diffSyntaxService(diffSyntaxBackend(backend))\n if (!service) return null\n\n // A task cancelled mid-load has already run its cancel callback, so a stale load disposes\n // whatever it created itself: nothing else will.\n const stale = () => {\n disposeMutableSessions(created)\n return null\n }\n const sources: PreparedDiffSyntaxSource[] = []\n for (const document of syntaxDocumentsForFile(file, side)) {\n if (!isCurrent()) return stale()\n\n const session = await service.createSession(document)\n if (!session) continue\n\n const source = new PreparedDiffSyntaxSource(document.side, document.lineStarts)\n created.push(source)\n source.own(session)\n if (!isCurrent()) return stale()\n const unsubscribe = session.onDidChangeTheme?.(() => void source.recolor(session))\n if (unsubscribe) source.own({ dispose: unsubscribe })\n const result = await session.refresh()\n if (!isCurrent()) return stale()\n\n source.setTokens(result.tokens)\n sources.push(source)\n }\n\n return sources\n}\n\nfunction disposePrepared(sources: readonly PreparedDiffSyntaxSource[]): void {\n for (const source of sources) source.dispose()\n}\n\nfunction isPromiseLike(\n value: PreparedDiffSyntaxInput,\n): value is Promise<readonly PreparedDiffSyntaxSource[]> {\n return 'then' in value\n}\n\nfunction coversSide(sources: readonly PreparedDiffSyntaxSource[], side: DiffSyntaxSide): boolean {\n const sides = new Set(sources.map((source) => source.side))\n if (side === 'stacked') return sides.size === 2 && sources.length === 2\n return sides.has(side) && sources.length === 1\n}\n\nfunction indexPreparedSources(\n sources: readonly PreparedDiffSyntaxSource[],\n): readonly IndexedTokenSource[] {\n return sources.map((source) => ({\n lineStarts: source.lineStarts,\n side: source.side,\n tokens: source.tokens,\n }))\n}\n\nexport function diffSyntaxBackend(backend: DiffSyntaxBackend | undefined): DiffSyntaxBackend {\n return backend ?? { kind: 'tree-sitter' }\n}\n\nexport function projectDiffSyntaxTokens({\n rows,\n side,\n sources,\n}: ProjectDiffSyntaxTokensOptions): readonly EditorToken[] {\n return projectIndexedTokens(rows, side, indexTokenSources(sources))\n}\n\nfunction projectIndexedTokens(\n rows: readonly DiffRenderRow[],\n side: DiffSyntaxSide,\n sources: readonly IndexedTokenSource[],\n): readonly EditorToken[] {\n const projectedTokens: EditorToken[] = []\n let rowOffset = 0\n\n for (const row of rows) {\n const source = tokenSourceForRow(sources, row, side)\n if (source) {\n appendRowSyntaxTokens(projectedTokens, {\n lineStarts: source.lineStarts,\n row,\n rowOffset,\n side: source.side,\n tokens: source.tokens,\n })\n }\n rowOffset += row.text.length + 1\n }\n\n return projectedTokens\n}\n\ntype DiffSyntaxSource = {\n readonly lineStarts: readonly number[]\n readonly side: DiffSyntaxSourceSide\n readonly text: string\n}\n\ntype DiffSyntaxDocument = DiffSyntaxSource & {\n readonly documentId: string\n readonly languageId: string | null\n readonly request: EditorSyntaxServiceRequest\n readonly textSnapshot: DocumentTextSnapshot\n}\n\ntype DiffSyntaxService = {\n createSession(document: DiffSyntaxDocument): Promise<DiffSyntaxServiceSession | null>\n}\n\ntype DiffSyntaxServiceSession = {\n onDidChangeTheme?: EditorHighlighterSession['onDidChangeTheme']\n refresh(): Promise<EditorSyntaxResult>\n dispose(): void\n}\n\nfunction diffSyntaxService(backend: DiffSyntaxBackend): DiffSyntaxService | null {\n if (backend.kind === 'tree-sitter') return treeSitterDiffSyntaxService(backend.provider ?? null)\n return highlighterDiffSyntaxService(backend.provider ?? null)\n}\n\nfunction highlighterDiffSyntaxService(\n provider: EditorHighlighterProvider | null,\n): DiffSyntaxService | null {\n if (!provider) return null\n\n return {\n createSession: async (document) => highlighterDiffSyntaxSession(provider, document),\n }\n}\n\nfunction highlighterDiffSyntaxSession(\n provider: EditorHighlighterProvider,\n document: DiffSyntaxDocument,\n): DiffSyntaxServiceSession | null {\n const session = provider.createSession(highlighterSessionOptions(document))\n if (!session) return null\n\n return tokenHighlighterDiffSyntaxSession(document, session)\n}\n\nfunction treeSitterDiffSyntaxService(\n provider: EditorSyntaxProvider | null,\n): DiffSyntaxService | null {\n if (!provider) return null\n\n return {\n createSession: async (document) => treeSitterDiffSyntaxSession(provider, document),\n }\n}\n\nfunction treeSitterDiffSyntaxSession(\n provider: EditorSyntaxProvider,\n document: DiffSyntaxDocument,\n): DiffSyntaxServiceSession | null {\n const session = provider.createSession(syntaxSessionOptions(document))\n if (!session) return null\n\n return {\n dispose: () => session.dispose(),\n refresh: () => session.refresh(document.textSnapshot),\n }\n}\n\nfunction tokenHighlighterDiffSyntaxSession(\n document: DiffSyntaxDocument,\n session: EditorHighlighterSession,\n): DiffSyntaxServiceSession {\n return {\n onDidChangeTheme: session.onDidChangeTheme,\n dispose: () => session.dispose(),\n refresh: async () => {\n const result = await session.refresh(document.textSnapshot)\n return syntaxResultFromTokens(document.request, result.tokens)\n },\n }\n}\n\nfunction syntaxSessionOptions(document: DiffSyntaxDocument): EditorSyntaxSessionOptions {\n return {\n documentId: document.documentId,\n includeCaptures: document.request.language.includeCaptures,\n includeHighlights: document.request.language.includeHighlights,\n languageId: document.languageId,\n snapshot: document.request.snapshot,\n syntaxMode: document.request.language.mode === 'range' ? 'range' : 'full',\n textSnapshot: document.textSnapshot,\n }\n}\n\nfunction highlighterSessionOptions(\n document: DiffSyntaxDocument,\n): Omit<EditorSyntaxSessionOptions, 'includeCaptures' | 'includeHighlights' | 'syntaxMode'> {\n return {\n documentId: document.documentId,\n languageId: document.languageId,\n snapshot: document.request.snapshot,\n textSnapshot: document.textSnapshot,\n }\n}\n\nfunction syntaxResultFromTokens(\n request: EditorSyntaxServiceRequest,\n tokens: EditorTokenInput,\n): EditorSyntaxResult {\n return {\n ...createEmptySyntaxResult({\n language: request.language,\n requestedRanges: request.requestedRanges,\n snapshot: request.snapshotTag,\n }),\n tokens,\n }\n}\n\n/**\n * The two full files, parsed whole.\n *\n * This is the language the diff is highlighted in, and §C11 is the reason it lives here rather than\n * on the editor: the editor's own document is the *interleaved* buffer, and giving that a language\n * feeds a garbage parse into folds, brackets and injections.\n */\nfunction syntaxDocumentsForFile(\n file: DiffFile,\n side: DiffSyntaxSide,\n): readonly DiffSyntaxDocument[] {\n return syntaxSourcesForSide(file, side).map((source) => syntaxDocument(file, source))\n}\n\nfunction syntaxSourcesForSide(file: DiffFile, side: DiffSyntaxSide): readonly DiffSyntaxSource[] {\n if (side === 'stacked') {\n return [syntaxSource(file.oldLines, 'old'), syntaxSource(file.newLines, 'new')]\n }\n\n return [syntaxSource(side === 'old' ? file.oldLines : file.newLines, side)]\n}\n\nfunction syntaxSource(lines: readonly string[], side: DiffSyntaxSourceSide): DiffSyntaxSource {\n const text = lines.join('\\n')\n return {\n lineStarts: lineStartsForLines(lines),\n side,\n text,\n }\n}\n\nfunction syntaxDocument(file: DiffFile, source: DiffSyntaxSource): DiffSyntaxDocument {\n const snapshot = createPieceTableSnapshot(source.text)\n const textSnapshot = createDocumentTextSnapshot(snapshot, source.text)\n const documentId = `${file.path}#diff-${source.side}`\n const languageId = diffSyntaxLanguageId(file)\n const request: EditorSyntaxServiceRequest = {\n editSummary: null,\n language: createSyntaxLanguageConfiguration({\n includeCaptures: true,\n includeHighlights: true,\n languageId,\n mode: 'full',\n }),\n requestedRanges: [{ startIndex: 0, endIndex: snapshot.length }],\n snapshot,\n snapshotTag: createSyntaxSnapshotTag({\n documentId,\n length: snapshot.length,\n version: 0,\n }),\n textSnapshot,\n }\n return { ...source, documentId, languageId, request, textSnapshot }\n}\n\nfunction diffSyntaxLanguageId(file: DiffFile): string | null {\n return file.languageId ?? languageIdForPath(file.path)\n}\n\ntype IndexedTokenSource = {\n readonly lineStarts: readonly number[]\n readonly side: DiffSyntaxSourceSide\n readonly tokens: EditorTokenStore\n}\n\n/**\n * A row reads its line's tokens by bisection, and rows do not visit source lines in order once\n * expanded regions interleave. The store is sorted whichever producer made the tokens.\n */\nfunction indexTokenSources(\n sources: readonly DiffSyntaxTokenSource[],\n): readonly IndexedTokenSource[] {\n return sources.map((source) => ({\n lineStarts: source.lineStarts,\n side: source.side,\n tokens: toEditorTokenStore(source.tokens),\n }))\n}\n\nfunction tokenSourceForRow(\n sources: readonly IndexedTokenSource[],\n row: DiffRenderRow,\n side: DiffSyntaxSide,\n): IndexedTokenSource | null {\n const sourceSide = sourceSideForRow(row, side)\n return sources.find((source) => source.side === sourceSide) ?? null\n}\n\nfunction appendRowSyntaxTokens(\n projectedTokens: EditorToken[],\n {\n lineStarts,\n row,\n rowOffset,\n side,\n tokens,\n }: {\n readonly lineStarts: readonly number[]\n readonly row: DiffRenderRow\n readonly rowOffset: number\n readonly side: DiffSyntaxSourceSide\n readonly tokens: EditorTokenStore\n },\n): void {\n const lineNumber = sourceLineNumberForRow(row, side)\n if (lineNumber === undefined) return\n\n const lineStart = lineStarts[lineNumber - 1]\n const nextLineStart = lineStarts[lineNumber]\n if (lineStart === undefined) return\n\n const lineEnd = Math.min(\n nextLineStart === undefined ? Number.POSITIVE_INFINITY : nextLineStart - 1,\n lineStart + row.text.length,\n )\n\n const last = tokens.firstStartingAtOrAfter(lineEnd)\n tokens.forEachInRange(tokens.firstEndingAfter(lineStart, last), last, (start, end, styleId) => {\n const token = { start, end, style: tokens.styles[styleId]! }\n appendProjectedToken(projectedTokens, token, lineStart, lineEnd, rowOffset)\n })\n}\n\nfunction appendProjectedToken(\n projectedTokens: EditorToken[],\n token: EditorToken,\n lineStart: number,\n lineEnd: number,\n rowOffset: number,\n): void {\n if (token.end <= lineStart) return\n if (token.start >= lineEnd) return\n\n const start = Math.max(token.start, lineStart)\n const end = Math.min(token.end, lineEnd)\n if (end <= start) return\n\n projectedTokens.push({\n end: rowOffset + end - lineStart,\n start: rowOffset + start - lineStart,\n style: token.style,\n })\n}\n\nfunction sourceLineNumberForRow(\n row: DiffRenderRow,\n side: DiffSyntaxSourceSide,\n): number | undefined {\n if (side === 'old') return row.oldLineNumber\n return row.newLineNumber\n}\n\nfunction sourceSideForRow(row: DiffRenderRow, side: DiffSyntaxSide): DiffSyntaxSourceSide {\n if (side === 'old' || side === 'new') return side\n if (row.type === 'deletion') return 'old'\n\n return 'new'\n}\n\nfunction lineStartsForLines(lines: readonly string[]): readonly number[] {\n const starts: number[] = []\n let offset = 0\n for (const line of lines) {\n starts.push(offset)\n offset += line.length + 1\n }\n return starts\n}\n\nfunction disposeMutableSessions(sessions: { dispose(): void }[]): void {\n while (sessions.length > 0) sessions.pop()?.dispose()\n}\n"],"mappings":"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"}
@@ -0,0 +1,74 @@
1
+ import type { EditorDisposable, EditorPlugin } from '@singapore-editor/core/extensions';
2
+ import type { EditorToken } from '@singapore-editor/core/syntax';
3
+ import { type PreparedDiffSyntaxInput, type PreparedDiffSyntaxSource } from './diffSyntax';
4
+ import { type DiffGutterSide } from './gutters';
5
+ import { type DiffRegionStore } from './regions';
6
+ import type { DiffFile, DiffRenderRow, DiffSyntaxBackend, DiffTextFile } from './types';
7
+ export type DiffPluginMode = 'document' | 'overlay';
8
+ export type DiffPluginOptions = {
9
+ /**
10
+ * §C1. `document` is the parity path; `overlay` is explicitly non-parity — see §C2.
11
+ *
12
+ * Required, with no default. The factory this replaced took no arguments and was always overlay,
13
+ * so the natural migration — rename the call, keep the empty argument list — would otherwise
14
+ * produce a plugin that mounts, registers everything, renders nothing and reports no error,
15
+ * because `setBaseFile` and `setEnabled` both early-return outside overlay mode.
16
+ */
17
+ readonly mode: DiffPluginMode;
18
+ readonly side?: DiffGutterSide;
19
+ /**
20
+ * Expansion state, shared when a host drives two sides of one diff. Omit it and the plugin keeps
21
+ * a private store; pass one store to both split plugins and a toggle on either side moves both.
22
+ * See regions.ts — this is what holds §C7 together.
23
+ */
24
+ readonly regions?: DiffRegionStore;
25
+ readonly syntaxBackend?: DiffSyntaxBackend;
26
+ readonly syntaxHighlight?: boolean;
27
+ };
28
+ export type DiffPlugin = EditorPlugin & {
29
+ /**
30
+ * `document` mode: the file to project. The host owns the editor's text — §C3. `prepared`
31
+ * streams from `prepareDiffSyntax` for this file paint with the first rows, and a preparation
32
+ * still running is awaited in place of a second parse; the plugin owns them.
33
+ */
34
+ setFile(file: DiffFile | null, prepared?: PreparedDiffSyntaxInput): void;
35
+ /** The current file's parsed streams, handed to the caller; empty while a parse is running. */
36
+ releasePreparedSyntax(): readonly PreparedDiffSyntaxSource[];
37
+ getRows(): readonly DiffRenderRow[];
38
+ /** Both sides in row order. Cached until the file or expansion state changes. */
39
+ getStackedRows(): readonly DiffRenderRow[];
40
+ /**
41
+ * Projected syntax tokens for the current rows. The host passes them with the rows' text,
42
+ * `setText(text, { tokens })`, or an expansion toggle repaints uncoloured (§C10).
43
+ */
44
+ isSyntaxReady(): boolean;
45
+ getTokens(): readonly EditorToken[];
46
+ onDidChangeRows(listener: () => void): EditorDisposable;
47
+ onDidChangeTokens(listener: () => void): EditorDisposable;
48
+ /** §C5. Hosts must not mirror this. */
49
+ getExpandedRegions(): ReadonlySet<string>;
50
+ toggleRegion(key: string): void;
51
+ /** `overlay` mode: the buffer the live editor text is diffed against. */
52
+ setBaseFile(file: DiffTextFile | null): void;
53
+ setEnabled(enabled: boolean): void;
54
+ };
55
+ /** The diff row under a pointer: which pane, that pane's rows, and the index into them. */
56
+ export type DiffRowHit = {
57
+ readonly side: DiffGutterSide;
58
+ readonly rowIndex: number;
59
+ readonly rows: readonly DiffRenderRow[];
60
+ };
61
+ /** `null` outside a `document`-mode diff pane, or over something that is not a diff row. */
62
+ export declare function diffRowAtEvent(event: MouseEvent): DiffRowHit | null;
63
+ /**
64
+ * The one diff plugin (§C1), carrying two internal row-delivery modes.
65
+ *
66
+ * `document` — the editor holds a synthetic buffer of the projected rows and the plugin publishes
67
+ * them for the host to push in. Everything works because the rows are real document lines.
68
+ *
69
+ * `overlay` — the editor holds the host's live, editable buffer and deletions arrive as injected
70
+ * rows. Non-parity by construction; see §C2 and test/overlayModeLimits.test.ts before reaching for
71
+ * it.
72
+ */
73
+ export declare function createDiffPlugin(options: DiffPluginOptions): DiffPlugin;
74
+ //# sourceMappingURL=editorDiffPlugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"editorDiffPlugin.d.ts","sourceRoot":"","sources":["../src/editorDiffPlugin.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAIV,gBAAgB,EAGhB,YAAY,EAOb,MAAM,mCAAmC,CAAA;AAE1C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAA;AAGhE,OAAO,EAEL,KAAK,uBAAuB,EAC5B,KAAK,wBAAwB,EAC9B,MAAM,cAAc,CAAA;AACrB,OAAO,EAIL,KAAK,cAAc,EACpB,MAAM,WAAW,CAAA;AAIlB,OAAO,EAAyB,KAAK,eAAe,EAAE,MAAM,WAAW,CAAA;AACvE,OAAO,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAEvF,MAAM,MAAM,cAAc,GAAG,UAAU,GAAG,SAAS,CAAA;AAEnD,MAAM,MAAM,iBAAiB,GAAG;IAC9B;;;;;;;OAOG;IACH,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAA;IAC7B,QAAQ,CAAC,IAAI,CAAC,EAAE,cAAc,CAAA;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,eAAe,CAAA;IAClC,QAAQ,CAAC,aAAa,CAAC,EAAE,iBAAiB,CAAA;IAC1C,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,CAAA;CACnC,CAAA;AAED,MAAM,MAAM,UAAU,GAAG,YAAY,GAAG;IACtC;;;;OAIG;IACH,OAAO,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,EAAE,QAAQ,CAAC,EAAE,uBAAuB,GAAG,IAAI,CAAA;IACxE,+FAA+F;IAC/F,qBAAqB,IAAI,SAAS,wBAAwB,EAAE,CAAA;IAC5D,OAAO,IAAI,SAAS,aAAa,EAAE,CAAA;IACnC,iFAAiF;IACjF,cAAc,IAAI,SAAS,aAAa,EAAE,CAAA;IAC1C;;;OAGG;IACH,aAAa,IAAI,OAAO,CAAA;IACxB,SAAS,IAAI,SAAS,WAAW,EAAE,CAAA;IACnC,eAAe,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,gBAAgB,CAAA;IACvD,iBAAiB,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,gBAAgB,CAAA;IACzD,uCAAuC;IACvC,kBAAkB,IAAI,WAAW,CAAC,MAAM,CAAC,CAAA;IACzC,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAE/B,yEAAyE;IACzE,WAAW,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,GAAG,IAAI,CAAA;IAC5C,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAA;CACnC,CAAA;AAED,2FAA2F;AAC3F,MAAM,MAAM,UAAU,GAAG;IACvB,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAA;IAC7B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,IAAI,EAAE,SAAS,aAAa,EAAE,CAAA;CACxC,CAAA;AAMD,4FAA4F;AAC5F,wBAAgB,cAAc,CAAC,KAAK,EAAE,UAAU,GAAG,UAAU,GAAG,IAAI,CAQnE;AAaD;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,iBAAiB,GAAG,UAAU,CAsBvE"}