@singapore-editor/find 0.1.1

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/dist/plugin.js ADDED
@@ -0,0 +1,408 @@
1
+ import { EditorFindController } from "./findController.js";
2
+ import { EditorFindWidget } from "./findWidget.js";
3
+ import { compareTextOffsetRanges } from "@singapore-editor/core/document";
4
+ import { EDITOR_FIND_FEATURE, EDITOR_FIND_FEATURE_ID, EDITOR_MINIMAP_FEATURE, projectDecorationRangeThroughEdits } from "@singapore-editor/core/extensions";
5
+ //#region src/plugin.ts
6
+ function createEditorFindPlugin(options = {}) {
7
+ return {
8
+ name: "editor.find",
9
+ activate(context) {
10
+ const providers = createEditorFindContributionProviders(options);
11
+ return [
12
+ context.registerViewContribution(providers.view),
13
+ context.registerCommandContribution(providers.command),
14
+ context.registerCapabilityContribution(providers.capability),
15
+ context.registerEditContribution(providers.edit)
16
+ ];
17
+ }
18
+ };
19
+ }
20
+ function createEditorFindContributionProviders(options = {}) {
21
+ const controller = new EditorFindController(options);
22
+ return {
23
+ view: { createContribution: (context) => new EditorFindViewContribution(context, controller) },
24
+ command: { createContribution: (context) => new EditorFindCommandContribution(context, controller) },
25
+ capability: { createContribution: (context) => new EditorFindCapabilityContribution(context, controller) },
26
+ edit: { createContribution: (context) => new EditorFindEditContribution(context, controller) }
27
+ };
28
+ }
29
+ var EditorFindViewContribution = class {
30
+ context;
31
+ controller;
32
+ hostRegistration;
33
+ subscription;
34
+ visibleKey;
35
+ latestSnapshot;
36
+ widget = null;
37
+ reservationListener = null;
38
+ paintedMatches = null;
39
+ constructor(context, controller) {
40
+ this.context = context;
41
+ this.controller = controller;
42
+ this.latestSnapshot = context.getSnapshot();
43
+ this.hostRegistration = controller.attachHost(createFindHost(context, () => this.latestSnapshot, this.trackPaintedRanges), context.highlightPrefix);
44
+ this.subscription = controller.subscribe(this.handleUiEvent);
45
+ this.visibleKey = context.registerKeymapContextKey("findVisible", () => controller.isVisible());
46
+ }
47
+ update(snapshot, kind, change) {
48
+ if (kind === "layout" || snapshot.viewport.clientWidth !== this.latestSnapshot.viewport.clientWidth) this.syncTrailingInset();
49
+ this.latestSnapshot = snapshot;
50
+ if (kind === "viewport" || kind === "layout") {
51
+ if (this.paintedMatches?.repartition(snapshot)) this.controller.refreshTrackedMatches();
52
+ }
53
+ this.controller.handleViewUpdate(kind, change ?? null);
54
+ }
55
+ dispose() {
56
+ this.visibleKey.dispose();
57
+ this.subscription.dispose();
58
+ this.reservationListener?.dispose();
59
+ this.reservationListener = null;
60
+ this.widget?.dispose();
61
+ this.widget = null;
62
+ this.paintedMatches = null;
63
+ this.hostRegistration.dispose();
64
+ }
65
+ trackPaintedRanges = (ranges) => {
66
+ this.paintedMatches = new PaintedFindRanges(this.context, this.latestSnapshot, ranges);
67
+ return this.paintedMatches;
68
+ };
69
+ handleUiEvent = (event) => {
70
+ if (event.type === "show") {
71
+ this.ensureWidget().show(event.replaceVisible);
72
+ return;
73
+ }
74
+ if (event.type === "hide") {
75
+ this.widget?.hide();
76
+ this.paintedMatches = null;
77
+ return;
78
+ }
79
+ if (event.type === "update") {
80
+ this.widget?.update(event.state);
81
+ return;
82
+ }
83
+ this.focusWidget(event.target);
84
+ };
85
+ focusWidget(target) {
86
+ const widget = this.widget;
87
+ if (!widget) return;
88
+ if (target === "find") widget.focusFindInput();
89
+ if (target === "replace") widget.focusReplaceInput();
90
+ }
91
+ ensureWidget() {
92
+ if (this.widget) return this.widget;
93
+ this.widget = new EditorFindWidget(this.context.container, this.context.scrollElement, this.createWidgetOptions());
94
+ this.observeReservedWidth();
95
+ this.syncTrailingInset();
96
+ return this.widget;
97
+ }
98
+ observeReservedWidth() {
99
+ this.reservationListener ??= this.context.onDidChangeReservedOverlayWidth((side) => {
100
+ if (side === "right") this.syncTrailingInset();
101
+ });
102
+ }
103
+ syncTrailingInset() {
104
+ if (!this.widget) return;
105
+ const { container, scrollElement } = this.context;
106
+ const containerRight = container.getBoundingClientRect().left + container.clientLeft + container.clientWidth;
107
+ const viewportRight = scrollElement.getBoundingClientRect().left + scrollElement.clientLeft + scrollElement.clientWidth;
108
+ const reservedWidth = this.context.getReservedOverlayWidth("right");
109
+ this.widget.setTrailingInset(reservedWidth + Math.max(0, containerRight - viewportRight));
110
+ }
111
+ createWidgetOptions() {
112
+ return {
113
+ onSearchInput: (value) => this.controller.setSearchString(value),
114
+ onReplaceInput: (value) => this.controller.setReplaceString(value),
115
+ onToggleReplace: () => this.controller.toggleReplace(),
116
+ onPrevious: () => this.controller.findPrevious(),
117
+ onNext: () => this.controller.findNext(),
118
+ onClose: () => this.controller.close(),
119
+ onToggleCase: () => this.controller.toggleMatchCase(),
120
+ onToggleWholeWord: () => this.controller.toggleWholeWord(),
121
+ onToggleRegex: () => this.controller.toggleRegex(),
122
+ onToggleScope: () => this.controller.toggleFindInSelection(),
123
+ onTogglePreserveCase: () => this.controller.togglePreserveCase(),
124
+ onReplaceOne: () => this.controller.replaceOne(),
125
+ onReplaceAll: () => this.controller.replaceAll()
126
+ };
127
+ }
128
+ };
129
+ var EditorFindCommandContribution = class {
130
+ commands;
131
+ constructor(context, controller) {
132
+ this.commands = [
133
+ context.registerCommand("find", () => controller.toggleFind()),
134
+ context.registerCommand("findReplace", () => controller.openFindReplace()),
135
+ context.registerCommand("findNext", () => controller.findNext()),
136
+ context.registerCommand("findPrevious", () => controller.findPrevious()),
137
+ context.registerCommand("closeFind", () => controller.close()),
138
+ context.registerCommand("toggleFindCaseSensitive", () => controller.toggleMatchCase()),
139
+ context.registerCommand("toggleFindWholeWord", () => controller.toggleWholeWord()),
140
+ context.registerCommand("toggleFindRegex", () => controller.toggleRegex()),
141
+ context.registerCommand("toggleFindInSelection", () => controller.toggleFindInSelection()),
142
+ context.registerCommand("togglePreserveCase", () => controller.togglePreserveCase()),
143
+ context.registerCommand("replaceOne", () => controller.replaceOne()),
144
+ context.registerCommand("replaceAll", () => controller.replaceAll()),
145
+ context.registerCommand("selectAllMatches", () => controller.selectAllMatches())
146
+ ];
147
+ }
148
+ dispose() {
149
+ disposeAll(this.commands);
150
+ }
151
+ };
152
+ var EditorFindCapabilityContribution = class {
153
+ registration;
154
+ constructor(context, controller) {
155
+ this.registration = context.registerFeature(EDITOR_FIND_FEATURE, createFindFeature(controller));
156
+ }
157
+ dispose() {
158
+ this.registration.dispose();
159
+ }
160
+ };
161
+ var EditorFindEditContribution = class {
162
+ registration;
163
+ constructor(context, controller) {
164
+ this.registration = controller.attachEditHost({
165
+ textSnapshot: () => context.getTextSnapshot(),
166
+ getSelections: () => findSelections(context.getSelections()),
167
+ applyEdits: (edits, timingName, selection) => context.applyEdits(edits, timingName, selection)
168
+ });
169
+ }
170
+ dispose() {
171
+ this.registration.dispose();
172
+ }
173
+ };
174
+ function createFindFeature(controller) {
175
+ return {
176
+ openFind: () => controller.openFind(),
177
+ toggleFind: () => controller.toggleFind(),
178
+ openFindReplace: () => controller.openFindReplace(),
179
+ closeFind: () => controller.close(),
180
+ findNext: () => controller.findNext(),
181
+ findPrevious: () => controller.findPrevious(),
182
+ replaceOne: () => controller.replaceOne(),
183
+ replaceAll: () => controller.replaceAll(),
184
+ selectAllMatches: () => controller.selectAllMatches()
185
+ };
186
+ }
187
+ function createFindHost(context, getSnapshot, trackPaintedRanges) {
188
+ const textSource = snapshotTextSource();
189
+ return {
190
+ hasDocument: () => context.hasDocument(),
191
+ textSource: () => textSource(getSnapshot()),
192
+ hasTextSnapshot: (snapshot) => getSnapshot().textSnapshot === snapshot,
193
+ trackRanges: (ranges) => context.trackRanges(ranges),
194
+ trackPaintedRanges,
195
+ getSelections: () => findSelections(getSnapshot().selections),
196
+ focusEditor: () => context.focusEditor(),
197
+ announce: (message) => context.announce(message),
198
+ setSelection: (anchor, head, timingName, options) => context.setSelection(anchor, head, timingName, options),
199
+ setSelections: (selections, timingName, revealOffset) => context.setSelections(selections, timingName, revealOffset),
200
+ setRangeHighlight: (name, ranges, style) => {
201
+ context.setRangeHighlight(name, ranges, style);
202
+ minimapFeature(context)?.setDecorations(name, minimapBands(textSource(getSnapshot()).lineStartsView, ranges, style));
203
+ },
204
+ clearRangeHighlight: (name) => {
205
+ context.clearRangeHighlight(name);
206
+ minimapFeature(context)?.clearDecorations(name);
207
+ }
208
+ };
209
+ }
210
+ function minimapFeature(context) {
211
+ return context.getFeature(EDITOR_MINIMAP_FEATURE);
212
+ }
213
+ /**
214
+ * A count says how many matches there are and nothing says where they are until
215
+ * they reach the scroll furniture, so every group find paints in the text is
216
+ * published under the name it is painted under.
217
+ *
218
+ * The band keeps its group's own colour: the minimap paints onto a canvas from a
219
+ * worker, which has no element to resolve a registered colour against and
220
+ * silently substitutes the selection colour for anything it cannot parse, so the
221
+ * literal has to travel with the group rather than be looked up there.
222
+ */
223
+ function minimapBands(lineStartsView, ranges, style) {
224
+ return ranges.map((range) => {
225
+ const rows = bandRows(lineStartsView, range);
226
+ return {
227
+ startLineNumber: rows.start,
228
+ startColumn: 1,
229
+ endLineNumber: rows.end,
230
+ endColumn: 1,
231
+ color: style.backgroundColor,
232
+ position: "inline",
233
+ zIndex: style.zIndex
234
+ };
235
+ });
236
+ }
237
+ function bandRows(lineStartsView, range) {
238
+ const startIndex = lineStartsView.indexForOffset(range.start);
239
+ const endIndex = lineStartsView.indexForOffset(range.end);
240
+ if (endIndex > startIndex && lineStartsView.at(endIndex) === range.end) return {
241
+ start: startIndex + 1,
242
+ end: endIndex
243
+ };
244
+ return {
245
+ start: startIndex + 1,
246
+ end: endIndex + 1
247
+ };
248
+ }
249
+ /**
250
+ * Follows the ranges as far as they are painted, answering for the rest with the
251
+ * offsets it was handed.
252
+ *
253
+ * A span the reader cannot see cannot be seen standing on text that moved out
254
+ * from under it, and the re-search already on its way replaces the whole set
255
+ * either way. Handing the document every match instead costs it an anchor pair
256
+ * each, on every keystroke — the bill deferring that re-search was supposed to
257
+ * take off the keystroke in the first place.
258
+ *
259
+ * Which of them those are is a fact about the rows mounted at the time and not
260
+ * about the set, so it stops being true the moment the reader scrolls. Drawn once
261
+ * and left, the line would divide the screenful they have scrolled away from,
262
+ * and every mark now in front of them would be one nobody is following.
263
+ */
264
+ var MATCH_BIAS = {
265
+ startBias: "right",
266
+ endBias: "left"
267
+ };
268
+ var PaintedFindRanges = class {
269
+ context;
270
+ span = null;
271
+ tracked = null;
272
+ elsewhere;
273
+ /** Text version the offsets in `elsewhere` describe, which is not always the document's. */
274
+ elsewhereVersion;
275
+ elsewhereSyncPoint;
276
+ constructor(context, snapshot, ranges) {
277
+ this.context = context;
278
+ this.elsewhere = ranges;
279
+ this.elsewhereVersion = snapshot.textVersion;
280
+ this.elsewhereSyncPoint = snapshot.documentSyncPoint;
281
+ this.partition(snapshot);
282
+ }
283
+ resolve() {
284
+ if (!this.tracked) return this.elsewhere;
285
+ return mergeRanges(this.tracked.resolve(), this.elsewhere);
286
+ }
287
+ /** Answers whether the line moved, so a caller only re-reads the set when there is a reason to. */
288
+ repartition(snapshot) {
289
+ return this.partition(snapshot);
290
+ }
291
+ partition(snapshot) {
292
+ const span = paintedSpan(snapshot);
293
+ if (!span || sameSpan(span, this.span)) return false;
294
+ this.carryElsewhereForward(snapshot);
295
+ const ranges = this.resolve();
296
+ const painted = ranges.filter((range) => overlapsSpan(range, span));
297
+ const tracked = painted.length === 0 ? null : this.context.trackRanges(painted, MATCH_BIAS);
298
+ this.span = span;
299
+ this.tracked = tracked;
300
+ this.elsewhere = tracked ? ranges.filter((range) => !overlapsSpan(range, span)) : ranges;
301
+ this.elsewhereVersion = snapshot.textVersion;
302
+ this.elsewhereSyncPoint = snapshot.documentSyncPoint;
303
+ return true;
304
+ }
305
+ /**
306
+ * Brings the unfollowed half up to the document before any of it is promoted.
307
+ *
308
+ * Nothing follows a match the reader cannot see, so an edit that lands while they are looking
309
+ * elsewhere leaves those offsets describing text that has moved. Scrolling then hands one of them
310
+ * to the document to be followed from where it used to be, and the mark lands beside its word —
311
+ * the defect being followed at all was supposed to prevent, arriving one scroll later. The edits
312
+ * are replayed where the host can name them, and where it cannot the offsets are dropped rather
313
+ * than promoted: no mark is the honest answer for the moment before the re-search lands, and a
314
+ * mark on the wrong word is not.
315
+ */
316
+ carryElsewhereForward(snapshot) {
317
+ if (snapshot.textVersion === this.elsewhereVersion) return;
318
+ if (this.elsewhere.length === 0) {
319
+ this.elsewhereVersion = snapshot.textVersion;
320
+ this.elsewhereSyncPoint = snapshot.documentSyncPoint;
321
+ return;
322
+ }
323
+ const edits = snapshot.changesSinceDocumentSyncPoint(this.elsewhereSyncPoint, null)?.edits ?? null;
324
+ this.elsewhere = edits ? this.elsewhere.map((range) => projectDecorationRangeThroughEdits({
325
+ ...range,
326
+ ...MATCH_BIAS
327
+ }, edits)).filter((range) => range !== null).map((range) => ({
328
+ end: range.end,
329
+ start: range.start
330
+ })) : [];
331
+ this.elsewhereVersion = snapshot.textVersion;
332
+ this.elsewhereSyncPoint = snapshot.documentSyncPoint;
333
+ }
334
+ };
335
+ function paintedSpan(snapshot) {
336
+ const first = snapshot.visibleRows[0];
337
+ const last = snapshot.visibleRows.at(-1);
338
+ if (!first || !last) return null;
339
+ return {
340
+ start: first.startOffset,
341
+ end: last.endOffset
342
+ };
343
+ }
344
+ function overlapsSpan(range, span) {
345
+ return range.end > span.start && range.start < span.end;
346
+ }
347
+ function sameSpan(span, other) {
348
+ return other !== null && span.start === other.start && span.end === other.end;
349
+ }
350
+ function mergeRanges(left, right) {
351
+ if (right.length === 0) return left;
352
+ const merged = [];
353
+ let leftIndex = 0;
354
+ let rightIndex = 0;
355
+ while (leftIndex < left.length && rightIndex < right.length) {
356
+ if (compareTextOffsetRanges(left[leftIndex], right[rightIndex]) <= 0) {
357
+ merged.push(left[leftIndex]);
358
+ leftIndex += 1;
359
+ continue;
360
+ }
361
+ merged.push(right[rightIndex]);
362
+ rightIndex += 1;
363
+ }
364
+ for (; leftIndex < left.length; leftIndex += 1) merged.push(left[leftIndex]);
365
+ for (; rightIndex < right.length; rightIndex += 1) merged.push(right[rightIndex]);
366
+ return merged;
367
+ }
368
+ /**
369
+ * The text a search reads, kept for as long as the snapshot it was taken from.
370
+ *
371
+ * Either view can be absent, and standing in for a missing one materializes what
372
+ * it exists to avoid: a copy of the document, or the whole line-start array.
373
+ * Every press in the find box searches again, so a stand-in built per search
374
+ * would hand a keystroke exactly the bill that ranged reads are here to spare it.
375
+ * Held against the snapshot itself rather than its text version, because a
376
+ * document swap can carry that version across unchanged.
377
+ */
378
+ function snapshotTextSource() {
379
+ let cached = null;
380
+ return (snapshot) => {
381
+ if (cached?.snapshot !== snapshot) cached = {
382
+ snapshot,
383
+ source: findTextSource(snapshot)
384
+ };
385
+ return cached.source;
386
+ };
387
+ }
388
+ function findTextSource(snapshot) {
389
+ const text = snapshot.textSnapshot;
390
+ return {
391
+ length: text.length,
392
+ readRange: (start, end) => text.readRange(start, end),
393
+ lineStartsView: snapshot.lineStartsView
394
+ };
395
+ }
396
+ function findSelections(selections) {
397
+ return selections.map((selection) => ({
398
+ ...selection,
399
+ collapsed: selection.startOffset === selection.endOffset
400
+ }));
401
+ }
402
+ function disposeAll(disposables) {
403
+ for (const disposable of disposables.toReversed()) disposable.dispose();
404
+ }
405
+ //#endregion
406
+ export { EDITOR_FIND_FEATURE, EDITOR_FIND_FEATURE_ID, createEditorFindContributionProviders, createEditorFindPlugin };
407
+
408
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.js","names":[],"sources":["../src/plugin.ts"],"sourcesContent":["import { compareTextOffsetRanges, type DocumentSyncPoint } from '@singapore-editor/core/document'\nimport {\n type EditorContributionChange,\n projectDecorationRangeThroughEdits,\n} from '@singapore-editor/core/extensions'\nimport type {\n EditorCapabilityContribution,\n EditorCapabilityContributionContext,\n EditorCapabilityContributionProvider,\n EditorCommandContribution,\n EditorCommandContributionContext,\n EditorCommandContributionProvider,\n EditorDisposable,\n EditorEditContribution,\n EditorEditContributionContext,\n EditorEditContributionProvider,\n EditorFindFeature,\n EditorMinimapDecoration,\n EditorMinimapFeature,\n EditorPlugin,\n EditorResolvedSelection,\n EditorViewContribution,\n EditorViewContributionContext,\n EditorViewContributionProvider,\n EditorViewContributionUpdateKind,\n EditorViewSnapshot,\n} from '@singapore-editor/core/extensions'\nimport {\n EDITOR_FIND_FEATURE,\n EDITOR_FIND_FEATURE_ID,\n EDITOR_MINIMAP_FEATURE,\n} from '@singapore-editor/core/extensions'\nimport type { VirtualizedTextHighlightStyle } from '@singapore-editor/core/rendering'\nimport {\n EditorFindController,\n type EditorFindHost,\n type EditorFindResolvedSelection,\n type EditorFindUiEvent,\n type FindTrackedRanges,\n} from './findController'\nimport { EditorFindWidget, type EditorFindWidgetOptions } from './findWidget'\nimport { type FindLineStartsView, type FindRange, type FindTextSource } from './search'\nimport type { EditorFindOptions } from './types'\n\nexport { EDITOR_FIND_FEATURE, EDITOR_FIND_FEATURE_ID }\n\nexport type EditorFindContributionProviders = {\n readonly view: EditorViewContributionProvider\n readonly command: EditorCommandContributionProvider\n readonly capability: EditorCapabilityContributionProvider\n readonly edit: EditorEditContributionProvider\n}\n\nexport function createEditorFindPlugin(options: EditorFindOptions = {}): EditorPlugin {\n return {\n name: 'editor.find',\n activate(context) {\n const providers = createEditorFindContributionProviders(options)\n return [\n context.registerViewContribution(providers.view),\n context.registerCommandContribution(providers.command),\n context.registerCapabilityContribution(providers.capability),\n context.registerEditContribution(providers.edit),\n ]\n },\n }\n}\n\nexport function createEditorFindContributionProviders(\n options: EditorFindOptions = {},\n): EditorFindContributionProviders {\n const controller = new EditorFindController(options)\n return {\n view: {\n createContribution: (context) => new EditorFindViewContribution(context, controller),\n },\n command: {\n createContribution: (context) => new EditorFindCommandContribution(context, controller),\n },\n capability: {\n createContribution: (context) => new EditorFindCapabilityContribution(context, controller),\n },\n edit: {\n createContribution: (context) => new EditorFindEditContribution(context, controller),\n },\n }\n}\n\nclass EditorFindViewContribution implements EditorViewContribution {\n private readonly hostRegistration: EditorDisposable\n private readonly subscription: EditorDisposable\n private readonly visibleKey: EditorDisposable\n private latestSnapshot: EditorViewSnapshot\n private widget: EditorFindWidget | null = null\n private reservationListener: EditorDisposable | null = null\n private paintedMatches: PaintedFindRanges | null = null\n\n public constructor(\n private readonly context: EditorViewContributionContext,\n private readonly controller: EditorFindController,\n ) {\n this.latestSnapshot = context.getSnapshot()\n this.hostRegistration = controller.attachHost(\n createFindHost(context, () => this.latestSnapshot, this.trackPaintedRanges),\n context.highlightPrefix,\n )\n this.subscription = controller.subscribe(this.handleUiEvent)\n this.visibleKey = context.registerKeymapContextKey('findVisible', () => controller.isVisible())\n }\n\n public update(\n snapshot: EditorViewSnapshot,\n kind: EditorViewContributionUpdateKind,\n change?: EditorContributionChange | null,\n ): void {\n if (\n kind === 'layout' ||\n snapshot.viewport.clientWidth !== this.latestSnapshot.viewport.clientWidth\n ) {\n this.syncTrailingInset()\n }\n this.latestSnapshot = snapshot\n // Scrolling is the one thing that moves rows under the marks without an edit\n // having happened yet, which is the only moment left to start following the\n // ones it brought in front of the reader.\n // Re-read after the line moves, not only after an edit: the set the controller is painting was\n // copied out of these ranges, so a match promoted into the mounted rows by the scroll is still\n // at the offset it was found at until the copy is taken again.\n if (kind === 'viewport' || kind === 'layout') {\n if (this.paintedMatches?.repartition(snapshot)) this.controller.refreshTrackedMatches()\n }\n this.controller.handleViewUpdate(kind, change ?? null)\n }\n\n public dispose(): void {\n this.visibleKey.dispose()\n this.subscription.dispose()\n this.reservationListener?.dispose()\n this.reservationListener = null\n this.widget?.dispose()\n this.widget = null\n this.paintedMatches = null\n this.hostRegistration.dispose()\n }\n\n private readonly trackPaintedRanges = (ranges: readonly FindRange[]): FindTrackedRanges => {\n this.paintedMatches = new PaintedFindRanges(this.context, this.latestSnapshot, ranges)\n return this.paintedMatches\n }\n\n private readonly handleUiEvent = (event: EditorFindUiEvent): void => {\n if (event.type === 'show') {\n this.ensureWidget().show(event.replaceVisible)\n return\n }\n\n if (event.type === 'hide') {\n this.widget?.hide()\n // Nothing reads the set once it is off screen, so leaving it here would\n // have every scroll from now on redraw a line across it for nobody.\n this.paintedMatches = null\n return\n }\n\n if (event.type === 'update') {\n this.widget?.update(event.state)\n return\n }\n\n this.focusWidget(event.target)\n }\n\n private focusWidget(target: 'find' | 'replace'): void {\n const widget = this.widget\n if (!widget) return\n\n if (target === 'find') widget.focusFindInput()\n if (target === 'replace') widget.focusReplaceInput()\n }\n\n private ensureWidget(): EditorFindWidget {\n if (this.widget) return this.widget\n\n this.widget = new EditorFindWidget(\n this.context.container,\n this.context.scrollElement,\n this.createWidgetOptions(),\n )\n this.observeReservedWidth()\n this.syncTrailingInset()\n return this.widget\n }\n\n private observeReservedWidth(): void {\n this.reservationListener ??= this.context.onDidChangeReservedOverlayWidth((side) => {\n if (side === 'right') this.syncTrailingInset()\n })\n }\n\n private syncTrailingInset(): void {\n if (!this.widget) return\n\n const { container, scrollElement } = this.context\n const containerRight =\n container.getBoundingClientRect().left + container.clientLeft + container.clientWidth\n const viewportRight =\n scrollElement.getBoundingClientRect().left +\n scrollElement.clientLeft +\n scrollElement.clientWidth\n const reservedWidth = this.context.getReservedOverlayWidth('right')\n this.widget.setTrailingInset(reservedWidth + Math.max(0, containerRight - viewportRight))\n }\n\n private createWidgetOptions(): EditorFindWidgetOptions {\n return {\n onSearchInput: (value) => this.controller.setSearchString(value),\n onReplaceInput: (value) => this.controller.setReplaceString(value),\n onToggleReplace: () => this.controller.toggleReplace(),\n onPrevious: () => this.controller.findPrevious(),\n onNext: () => this.controller.findNext(),\n onClose: () => this.controller.close(),\n onToggleCase: () => this.controller.toggleMatchCase(),\n onToggleWholeWord: () => this.controller.toggleWholeWord(),\n onToggleRegex: () => this.controller.toggleRegex(),\n onToggleScope: () => this.controller.toggleFindInSelection(),\n onTogglePreserveCase: () => this.controller.togglePreserveCase(),\n onReplaceOne: () => this.controller.replaceOne(),\n onReplaceAll: () => this.controller.replaceAll(),\n }\n }\n}\n\nclass EditorFindCommandContribution implements EditorCommandContribution {\n private readonly commands: readonly EditorDisposable[]\n\n public constructor(context: EditorCommandContributionContext, controller: EditorFindController) {\n this.commands = [\n context.registerCommand('find', () => controller.toggleFind()),\n context.registerCommand('findReplace', () => controller.openFindReplace()),\n context.registerCommand('findNext', () => controller.findNext()),\n context.registerCommand('findPrevious', () => controller.findPrevious()),\n context.registerCommand('closeFind', () => controller.close()),\n context.registerCommand('toggleFindCaseSensitive', () => controller.toggleMatchCase()),\n context.registerCommand('toggleFindWholeWord', () => controller.toggleWholeWord()),\n context.registerCommand('toggleFindRegex', () => controller.toggleRegex()),\n context.registerCommand('toggleFindInSelection', () => controller.toggleFindInSelection()),\n context.registerCommand('togglePreserveCase', () => controller.togglePreserveCase()),\n context.registerCommand('replaceOne', () => controller.replaceOne()),\n context.registerCommand('replaceAll', () => controller.replaceAll()),\n context.registerCommand('selectAllMatches', () => controller.selectAllMatches()),\n ]\n }\n\n public dispose(): void {\n disposeAll(this.commands)\n }\n}\n\nclass EditorFindCapabilityContribution implements EditorCapabilityContribution {\n private readonly registration: EditorDisposable\n\n public constructor(\n context: EditorCapabilityContributionContext,\n controller: EditorFindController,\n ) {\n this.registration = context.registerFeature(EDITOR_FIND_FEATURE, createFindFeature(controller))\n }\n\n public dispose(): void {\n this.registration.dispose()\n }\n}\n\nclass EditorFindEditContribution implements EditorEditContribution {\n private readonly registration: EditorDisposable\n\n public constructor(context: EditorEditContributionContext, controller: EditorFindController) {\n this.registration = controller.attachEditHost({\n textSnapshot: () => context.getTextSnapshot(),\n getSelections: () => findSelections(context.getSelections()),\n applyEdits: (edits, timingName, selection) =>\n context.applyEdits(edits, timingName, selection),\n })\n }\n\n public dispose(): void {\n this.registration.dispose()\n }\n}\n\nfunction createFindFeature(controller: EditorFindController): EditorFindFeature {\n return {\n openFind: () => controller.openFind(),\n toggleFind: () => controller.toggleFind(),\n openFindReplace: () => controller.openFindReplace(),\n closeFind: () => controller.close(),\n findNext: () => controller.findNext(),\n findPrevious: () => controller.findPrevious(),\n replaceOne: () => controller.replaceOne(),\n replaceAll: () => controller.replaceAll(),\n selectAllMatches: () => controller.selectAllMatches(),\n }\n}\n\nfunction createFindHost(\n context: EditorViewContributionContext,\n getSnapshot: () => EditorViewSnapshot,\n trackPaintedRanges: (ranges: readonly FindRange[]) => FindTrackedRanges,\n): EditorFindHost {\n const textSource = snapshotTextSource()\n return {\n hasDocument: () => context.hasDocument(),\n textSource: () => textSource(getSnapshot()),\n hasTextSnapshot: (snapshot) => getSnapshot().textSnapshot === snapshot,\n trackRanges: (ranges) => context.trackRanges(ranges),\n trackPaintedRanges,\n getSelections: () => findSelections(getSnapshot().selections),\n focusEditor: () => context.focusEditor(),\n announce: (message) => context.announce(message),\n setSelection: (anchor, head, timingName, options) =>\n context.setSelection(anchor, head, timingName, options),\n setSelections: (selections, timingName, revealOffset) =>\n context.setSelections(selections, timingName, revealOffset),\n setRangeHighlight: (name, ranges, style) => {\n context.setRangeHighlight(name, ranges, style)\n minimapFeature(context)?.setDecorations(\n name,\n minimapBands(textSource(getSnapshot()).lineStartsView, ranges, style),\n )\n },\n clearRangeHighlight: (name) => {\n context.clearRangeHighlight(name)\n minimapFeature(context)?.clearDecorations(name)\n },\n }\n}\n\nfunction minimapFeature(context: EditorViewContributionContext): EditorMinimapFeature | null {\n return context.getFeature(EDITOR_MINIMAP_FEATURE)\n}\n\n/**\n * A count says how many matches there are and nothing says where they are until\n * they reach the scroll furniture, so every group find paints in the text is\n * published under the name it is painted under.\n *\n * The band keeps its group's own colour: the minimap paints onto a canvas from a\n * worker, which has no element to resolve a registered colour against and\n * silently substitutes the selection colour for anything it cannot parse, so the\n * literal has to travel with the group rather than be looked up there.\n */\nfunction minimapBands(\n lineStartsView: FindLineStartsView,\n ranges: readonly FindRange[],\n style: VirtualizedTextHighlightStyle,\n): readonly EditorMinimapDecoration[] {\n return ranges.map((range): EditorMinimapDecoration => {\n const rows = bandRows(lineStartsView, range)\n return {\n startLineNumber: rows.start,\n startColumn: 1,\n endLineNumber: rows.end,\n endColumn: 1,\n color: style.backgroundColor,\n position: 'inline',\n zIndex: style.zIndex,\n }\n })\n}\n\n// A range ending exactly at a line start ends on the row before it: the break\n// belongs to the line it terminates, and a selection taken to the end of one row\n// should not shade the next.\nfunction bandRows(\n lineStartsView: FindLineStartsView,\n range: FindRange,\n): { readonly start: number; readonly end: number } {\n const startIndex = lineStartsView.indexForOffset(range.start)\n const endIndex = lineStartsView.indexForOffset(range.end)\n if (endIndex > startIndex && lineStartsView.at(endIndex) === range.end) {\n return { start: startIndex + 1, end: endIndex }\n }\n\n return { start: startIndex + 1, end: endIndex + 1 }\n}\n\n/**\n * Follows the ranges as far as they are painted, answering for the rest with the\n * offsets it was handed.\n *\n * A span the reader cannot see cannot be seen standing on text that moved out\n * from under it, and the re-search already on its way replaces the whole set\n * either way. Handing the document every match instead costs it an anchor pair\n * each, on every keystroke — the bill deferring that re-search was supposed to\n * take off the keystroke in the first place.\n *\n * Which of them those are is a fact about the rows mounted at the time and not\n * about the set, so it stops being true the moment the reader scrolls. Drawn once\n * and left, the line would divide the screenful they have scrolled away from,\n * and every mark now in front of them would be one nobody is following.\n */\n// A match is the text the query answered for, so typing against either edge of one is not part of\n// it — unlike a scope, which is a region the reader drew and which takes in what they add at its\n// edges. The same pair decides how an offset is carried across an edit nobody was following.\nconst MATCH_BIAS = { startBias: 'right', endBias: 'left' } as const\n\nclass PaintedFindRanges implements FindTrackedRanges {\n private span: FindRange | null = null\n private tracked: FindTrackedRanges | null = null\n private elsewhere: readonly FindRange[]\n /** Text version the offsets in `elsewhere` describe, which is not always the document's. */\n private elsewhereVersion: number\n private elsewhereSyncPoint: DocumentSyncPoint\n\n public constructor(\n private readonly context: EditorViewContributionContext,\n snapshot: EditorViewSnapshot,\n ranges: readonly FindRange[],\n ) {\n this.elsewhere = ranges\n this.elsewhereVersion = snapshot.textVersion\n this.elsewhereSyncPoint = snapshot.documentSyncPoint\n this.partition(snapshot)\n }\n\n public resolve(): readonly FindRange[] {\n if (!this.tracked) return this.elsewhere\n return mergeRanges(this.tracked.resolve(), this.elsewhere)\n }\n\n /** Answers whether the line moved, so a caller only re-reads the set when there is a reason to. */\n public repartition(snapshot: EditorViewSnapshot): boolean {\n return this.partition(snapshot)\n }\n\n private partition(snapshot: EditorViewSnapshot): boolean {\n const span = paintedSpan(snapshot)\n if (!span || sameSpan(span, this.span)) return false\n\n // Read back first: what is already followed has moved since it was handed\n // over, and re-partitioning the offsets of the search would undo that.\n this.carryElsewhereForward(snapshot)\n const ranges = this.resolve()\n const painted = ranges.filter((range) => overlapsSpan(range, span))\n const tracked = painted.length === 0 ? null : this.context.trackRanges(painted, MATCH_BIAS)\n\n this.span = span\n this.tracked = tracked\n this.elsewhere = tracked ? ranges.filter((range) => !overlapsSpan(range, span)) : ranges\n this.elsewhereVersion = snapshot.textVersion\n this.elsewhereSyncPoint = snapshot.documentSyncPoint\n return true\n }\n\n /**\n * Brings the unfollowed half up to the document before any of it is promoted.\n *\n * Nothing follows a match the reader cannot see, so an edit that lands while they are looking\n * elsewhere leaves those offsets describing text that has moved. Scrolling then hands one of them\n * to the document to be followed from where it used to be, and the mark lands beside its word —\n * the defect being followed at all was supposed to prevent, arriving one scroll later. The edits\n * are replayed where the host can name them, and where it cannot the offsets are dropped rather\n * than promoted: no mark is the honest answer for the moment before the re-search lands, and a\n * mark on the wrong word is not.\n */\n private carryElsewhereForward(snapshot: EditorViewSnapshot): void {\n if (snapshot.textVersion === this.elsewhereVersion) return\n if (this.elsewhere.length === 0) {\n this.elsewhereVersion = snapshot.textVersion\n this.elsewhereSyncPoint = snapshot.documentSyncPoint\n return\n }\n\n const edits =\n snapshot.changesSinceDocumentSyncPoint(this.elsewhereSyncPoint, null)?.edits ?? null\n this.elsewhere = edits\n ? this.elsewhere\n .map((range) => projectDecorationRangeThroughEdits({ ...range, ...MATCH_BIAS }, edits))\n .filter((range) => range !== null)\n .map((range) => ({ end: range.end, start: range.start }))\n : []\n this.elsewhereVersion = snapshot.textVersion\n this.elsewhereSyncPoint = snapshot.documentSyncPoint\n }\n}\n\n// The view adds a range highlight to the rows it has mounted and to no others.\nfunction paintedSpan(snapshot: EditorViewSnapshot): FindRange | null {\n const first = snapshot.visibleRows[0]\n const last = snapshot.visibleRows.at(-1)\n if (!first || !last) return null\n\n return { start: first.startOffset, end: last.endOffset }\n}\n\nfunction overlapsSpan(range: FindRange, span: FindRange): boolean {\n return range.end > span.start && range.start < span.end\n}\n\nfunction sameSpan(span: FindRange, other: FindRange | null): boolean {\n return other !== null && span.start === other.start && span.end === other.end\n}\n\n// Navigation steps the set rather than searching it, so an entry out of order\n// answers the next press with the wrong match.\nfunction mergeRanges(\n left: readonly FindRange[],\n right: readonly FindRange[],\n): readonly FindRange[] {\n if (right.length === 0) return left\n\n const merged: FindRange[] = []\n let leftIndex = 0\n let rightIndex = 0\n while (leftIndex < left.length && rightIndex < right.length) {\n if (compareTextOffsetRanges(left[leftIndex]!, right[rightIndex]!) <= 0) {\n merged.push(left[leftIndex]!)\n leftIndex += 1\n continue\n }\n\n merged.push(right[rightIndex]!)\n rightIndex += 1\n }\n\n for (; leftIndex < left.length; leftIndex += 1) merged.push(left[leftIndex]!)\n for (; rightIndex < right.length; rightIndex += 1) merged.push(right[rightIndex]!)\n return merged\n}\n\n/**\n * The text a search reads, kept for as long as the snapshot it was taken from.\n *\n * Either view can be absent, and standing in for a missing one materializes what\n * it exists to avoid: a copy of the document, or the whole line-start array.\n * Every press in the find box searches again, so a stand-in built per search\n * would hand a keystroke exactly the bill that ranged reads are here to spare it.\n * Held against the snapshot itself rather than its text version, because a\n * document swap can carry that version across unchanged.\n */\nfunction snapshotTextSource(): (snapshot: EditorViewSnapshot) => FindTextSource {\n let cached: { readonly snapshot: EditorViewSnapshot; readonly source: FindTextSource } | null =\n null\n return (snapshot) => {\n if (cached?.snapshot !== snapshot) cached = { snapshot, source: findTextSource(snapshot) }\n return cached.source\n }\n}\n\nfunction findTextSource(snapshot: EditorViewSnapshot): FindTextSource {\n const text = snapshot.textSnapshot\n return {\n length: text.length,\n readRange: (start, end) => text.readRange(start, end),\n lineStartsView: snapshot.lineStartsView,\n }\n}\n\nfunction findSelections(\n selections: readonly EditorResolvedSelection[],\n): readonly EditorFindResolvedSelection[] {\n return selections.map((selection) => ({\n ...selection,\n collapsed: selection.startOffset === selection.endOffset,\n }))\n}\n\nfunction disposeAll(disposables: readonly EditorDisposable[]): void {\n for (const disposable of disposables.toReversed()) disposable.dispose()\n}\n\nexport type { EditorFindFeature, EditorFindOptions }\n"],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ export declare class ReplacePattern {
2
+ private readonly pieces;
3
+ static fromStaticValue(value: string): ReplacePattern;
4
+ readonly hasReplacementPatterns: boolean;
5
+ constructor(pieces: readonly ReplacePiece[]);
6
+ buildReplaceString(matches: readonly string[] | null, preserveCase?: boolean): string;
7
+ }
8
+ type ReplacePiece = {
9
+ readonly kind: 'static';
10
+ readonly value: string;
11
+ } | {
12
+ readonly kind: 'match';
13
+ readonly index: number;
14
+ readonly caseOps: readonly string[];
15
+ };
16
+ export declare function parseReplaceString(replaceString: string): ReplacePattern;
17
+ export {};
18
+ //# sourceMappingURL=replacePattern.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replacePattern.d.ts","sourceRoot":"","sources":["../src/replacePattern.ts"],"names":[],"mappings":"AAAA,qBAAa,cAAc;IAON,OAAO,CAAC,QAAQ,CAAC,MAAM;WAN5B,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,cAAc;IAI5D,SAAgB,sBAAsB,EAAE,OAAO,CAAA;gBAEX,MAAM,EAAE,SAAS,YAAY,EAAE;IAI5D,kBAAkB,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,EAAE,YAAY,UAAQ,GAAG,MAAM;CAY3F;AAED,KAAK,YAAY,GACb;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACnD;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,CAAA;AAE3F,wBAAgB,kBAAkB,CAAC,aAAa,EAAE,MAAM,GAAG,cAAc,CAgBxE"}