@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/README.md +28 -0
- package/dist/findController.d.ts +175 -0
- package/dist/findController.d.ts.map +1 -0
- package/dist/findController.js +681 -0
- package/dist/findController.js.map +1 -0
- package/dist/findIcons.d.ts +49 -0
- package/dist/findIcons.d.ts.map +1 -0
- package/dist/findIcons.js +65 -0
- package/dist/findIcons.js.map +1 -0
- package/dist/findWidget.d.ts +45 -0
- package/dist/findWidget.d.ts.map +1 -0
- package/dist/findWidget.js +194 -0
- package/dist/findWidget.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/plugin.d.ts +14 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +408 -0
- package/dist/plugin.js.map +1 -0
- package/dist/replacePattern.d.ts +18 -0
- package/dist/replacePattern.d.ts.map +1 -0
- package/dist/replacePattern.js +212 -0
- package/dist/replacePattern.js.map +1 -0
- package/dist/search.d.ts +83 -0
- package/dist/search.d.ts.map +1 -0
- package/dist/search.js +470 -0
- package/dist/search.js.map +1 -0
- package/dist/style.css +198 -0
- package/dist/types.d.ts +8 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +48 -0
|
@@ -0,0 +1,681 @@
|
|
|
1
|
+
import { FIND_MATCHES_LIMIT, FIND_REPLACE_ALL_LIMIT, countMatches, escapeRegExpCharacters, findLineRange, findMatchIndex, findMatches, findNextMatchFrom, findPreviousMatchFrom, findTextSourceFromSnapshot } from "./search.js";
|
|
2
|
+
import { ReplacePattern, parseReplaceString } from "./replacePattern.js";
|
|
3
|
+
import { wordRangeAtOffset } from "@singapore-editor/core/document";
|
|
4
|
+
import { EditorSecondaryViewScheduler } from "@singapore-editor/core/secondary-views";
|
|
5
|
+
//#region src/findController.ts
|
|
6
|
+
var FIND_HIGHLIGHT_Z_INDEX = {
|
|
7
|
+
scope: 4,
|
|
8
|
+
match: 5,
|
|
9
|
+
current: 6
|
|
10
|
+
};
|
|
11
|
+
var FIND_MATCH_STYLE = {
|
|
12
|
+
dimmable: false,
|
|
13
|
+
backgroundColor: "rgba(234, 179, 8, 0.34)",
|
|
14
|
+
zIndex: FIND_HIGHLIGHT_Z_INDEX.match
|
|
15
|
+
};
|
|
16
|
+
var FIND_CURRENT_STYLE = {
|
|
17
|
+
dimmable: false,
|
|
18
|
+
backgroundColor: "rgba(245, 158, 11, 0.72)",
|
|
19
|
+
color: "#111827",
|
|
20
|
+
zIndex: FIND_HIGHLIGHT_Z_INDEX.current
|
|
21
|
+
};
|
|
22
|
+
var FIND_SCOPE_STYLE = {
|
|
23
|
+
dimmable: false,
|
|
24
|
+
backgroundColor: "rgba(59, 130, 246, 0.22)",
|
|
25
|
+
zIndex: FIND_HIGHLIGHT_Z_INDEX.scope
|
|
26
|
+
};
|
|
27
|
+
function findReveal(match, committed = true) {
|
|
28
|
+
return {
|
|
29
|
+
...committed ? { jumpCause: "find" } : {},
|
|
30
|
+
revealOffset: match.end,
|
|
31
|
+
revealBlock: "center-if-outside"
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
var SEARCH_STRING_MAX_LENGTH = 524288;
|
|
35
|
+
var FIND_RESEARCH_KEY = "find.research";
|
|
36
|
+
var FIND_RESEARCH_DELAY_MS = 100;
|
|
37
|
+
var FIND_RESEARCH_MAX_DELAY_MS = 400;
|
|
38
|
+
var EditorFindController = class {
|
|
39
|
+
options;
|
|
40
|
+
listeners = /* @__PURE__ */ new Set();
|
|
41
|
+
host = null;
|
|
42
|
+
editHost = null;
|
|
43
|
+
matchHighlightName = "";
|
|
44
|
+
currentHighlightName = "";
|
|
45
|
+
scopeHighlightName = "";
|
|
46
|
+
state = {
|
|
47
|
+
searchString: "",
|
|
48
|
+
replaceString: "",
|
|
49
|
+
isRegex: false,
|
|
50
|
+
matchCase: false,
|
|
51
|
+
wholeWord: false,
|
|
52
|
+
preserveCase: false,
|
|
53
|
+
revealed: false,
|
|
54
|
+
replaceRevealed: false,
|
|
55
|
+
inSelection: false
|
|
56
|
+
};
|
|
57
|
+
scheduler = new EditorSecondaryViewScheduler();
|
|
58
|
+
matches = [];
|
|
59
|
+
highlightsTruncated = false;
|
|
60
|
+
totalMatches = 0;
|
|
61
|
+
scope = null;
|
|
62
|
+
trackedMatches = null;
|
|
63
|
+
currentMatch = null;
|
|
64
|
+
replacing = false;
|
|
65
|
+
pendingResearch = false;
|
|
66
|
+
constructor(options = {}) {
|
|
67
|
+
this.options = resolveFindOptions(options);
|
|
68
|
+
}
|
|
69
|
+
attachHost(host, highlightPrefix) {
|
|
70
|
+
if (this.host && this.host !== host) this.clearHighlights();
|
|
71
|
+
this.host = host;
|
|
72
|
+
this.matchHighlightName = `${highlightPrefix}-find-match`;
|
|
73
|
+
this.currentHighlightName = `${highlightPrefix}-find-current`;
|
|
74
|
+
this.scopeHighlightName = `${highlightPrefix}-find-scope`;
|
|
75
|
+
return { dispose: () => this.detachHost(host) };
|
|
76
|
+
}
|
|
77
|
+
attachEditHost(host) {
|
|
78
|
+
this.editHost = host;
|
|
79
|
+
return { dispose: () => this.detachEditHost(host) };
|
|
80
|
+
}
|
|
81
|
+
subscribe(listener) {
|
|
82
|
+
this.listeners.add(listener);
|
|
83
|
+
return { dispose: () => this.listeners.delete(listener) };
|
|
84
|
+
}
|
|
85
|
+
dispose() {
|
|
86
|
+
this.pendingResearch = false;
|
|
87
|
+
this.scheduler.dispose();
|
|
88
|
+
this.clearHighlights();
|
|
89
|
+
this.listeners.clear();
|
|
90
|
+
this.host = null;
|
|
91
|
+
this.editHost = null;
|
|
92
|
+
}
|
|
93
|
+
isVisible() {
|
|
94
|
+
return this.state.revealed;
|
|
95
|
+
}
|
|
96
|
+
openFind() {
|
|
97
|
+
return this.open({
|
|
98
|
+
replace: false,
|
|
99
|
+
focus: "find"
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
toggleFind() {
|
|
103
|
+
if (this.state.revealed) return this.close();
|
|
104
|
+
return this.openFind();
|
|
105
|
+
}
|
|
106
|
+
openFindReplace() {
|
|
107
|
+
return this.open({
|
|
108
|
+
replace: true,
|
|
109
|
+
focus: "replace"
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
close() {
|
|
113
|
+
const host = this.host;
|
|
114
|
+
if (!this.state.revealed || !host) return false;
|
|
115
|
+
this.pendingResearch = false;
|
|
116
|
+
this.state = {
|
|
117
|
+
...this.state,
|
|
118
|
+
revealed: false,
|
|
119
|
+
inSelection: false
|
|
120
|
+
};
|
|
121
|
+
this.scope = null;
|
|
122
|
+
this.trackedMatches = null;
|
|
123
|
+
this.currentMatch = null;
|
|
124
|
+
this.scheduler.cancel(FIND_RESEARCH_KEY);
|
|
125
|
+
this.clearHighlights();
|
|
126
|
+
this.emit({ type: "hide" });
|
|
127
|
+
host.focusEditor();
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
findNext() {
|
|
131
|
+
if (!this.ensureFindReady("none")) return false;
|
|
132
|
+
const startOffset = this.primarySelection()?.endOffset ?? 0;
|
|
133
|
+
const match = this.nextMatchAt(startOffset, true);
|
|
134
|
+
if (match && match.start < startOffset) this.host?.announce("Wrapped to the first match");
|
|
135
|
+
return this.selectMatch(match);
|
|
136
|
+
}
|
|
137
|
+
findPrevious() {
|
|
138
|
+
if (!this.ensureFindReady("none")) return false;
|
|
139
|
+
const startOffset = this.primarySelection()?.startOffset ?? 0;
|
|
140
|
+
const match = this.searchFrom(findPreviousMatchFrom, startOffset, { escapeEmptyMatchAtOffset: true });
|
|
141
|
+
if (match && match.start > startOffset) this.host?.announce("Wrapped to the last match");
|
|
142
|
+
return this.selectMatch(match);
|
|
143
|
+
}
|
|
144
|
+
replaceOne() {
|
|
145
|
+
if (!this.ensureFindReady("replace")) return false;
|
|
146
|
+
if (!this.editHost) return false;
|
|
147
|
+
const selection = this.primarySelection();
|
|
148
|
+
if (!selection) return false;
|
|
149
|
+
const search = this.commandSearchTarget();
|
|
150
|
+
if (!search) return false;
|
|
151
|
+
const match = findNextMatchFrom(search.source, this.state, selection.startOffset, search.scopes, {
|
|
152
|
+
captureMatches: true,
|
|
153
|
+
loop: this.options.loop
|
|
154
|
+
});
|
|
155
|
+
if (!match) return false;
|
|
156
|
+
if (match.start !== selection.startOffset || match.end !== selection.endOffset) return this.selectMatch(match);
|
|
157
|
+
const replaceText = this.replacePattern().buildReplaceString(match.matches, this.state.preserveCase);
|
|
158
|
+
this.applyReplacement([{
|
|
159
|
+
from: match.start,
|
|
160
|
+
to: match.end,
|
|
161
|
+
text: replaceText
|
|
162
|
+
}], "input.findReplaceOne", {
|
|
163
|
+
anchor: match.start + replaceText.length,
|
|
164
|
+
head: match.start + replaceText.length
|
|
165
|
+
});
|
|
166
|
+
this.selectNextReplacementMatch();
|
|
167
|
+
this.research(false);
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
replaceAll() {
|
|
171
|
+
if (!this.ensureFindReady("replace")) return false;
|
|
172
|
+
if (!this.editHost) return false;
|
|
173
|
+
const search = this.commandSearchTarget();
|
|
174
|
+
if (!search) return false;
|
|
175
|
+
const pattern = this.replacePattern();
|
|
176
|
+
const matches = findMatches(search.source, this.state, search.scopes, pattern.hasReplacementPatterns || this.state.preserveCase, FIND_REPLACE_ALL_LIMIT);
|
|
177
|
+
if (matches.length === 0) return false;
|
|
178
|
+
const edits = mergeAdjacentReplaceEdits(matches.map((match) => ({
|
|
179
|
+
from: match.start,
|
|
180
|
+
to: match.end,
|
|
181
|
+
text: pattern.buildReplaceString(match.matches, this.state.preserveCase)
|
|
182
|
+
})));
|
|
183
|
+
this.applyReplacement(edits, "input.findReplaceAll");
|
|
184
|
+
this.research(false);
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
selectAllMatches() {
|
|
188
|
+
const host = this.host;
|
|
189
|
+
if (!host) return false;
|
|
190
|
+
if (!this.ensureFindReady("none")) return false;
|
|
191
|
+
const search = this.commandSearchTarget();
|
|
192
|
+
if (!search) return false;
|
|
193
|
+
const matches = findMatches(search.source, this.state, search.scopes, false, FIND_REPLACE_ALL_LIMIT);
|
|
194
|
+
if (matches.length === 0) return false;
|
|
195
|
+
const selections = orderedMatchSelections(matches, this.primarySelection());
|
|
196
|
+
host.setSelections(selections, "input.findSelectAll", selections[0]?.head);
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
toggleMatchCase() {
|
|
200
|
+
this.state = {
|
|
201
|
+
...this.state,
|
|
202
|
+
matchCase: !this.state.matchCase
|
|
203
|
+
};
|
|
204
|
+
this.research(this.options.cursorMoveOnType);
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
toggleWholeWord() {
|
|
208
|
+
this.state = {
|
|
209
|
+
...this.state,
|
|
210
|
+
wholeWord: !this.state.wholeWord
|
|
211
|
+
};
|
|
212
|
+
this.research(this.options.cursorMoveOnType);
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
toggleRegex() {
|
|
216
|
+
this.state = {
|
|
217
|
+
...this.state,
|
|
218
|
+
isRegex: !this.state.isRegex
|
|
219
|
+
};
|
|
220
|
+
this.research(this.options.cursorMoveOnType);
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
togglePreserveCase() {
|
|
224
|
+
this.state = {
|
|
225
|
+
...this.state,
|
|
226
|
+
preserveCase: !this.state.preserveCase
|
|
227
|
+
};
|
|
228
|
+
this.updateWidget();
|
|
229
|
+
return true;
|
|
230
|
+
}
|
|
231
|
+
toggleReplace() {
|
|
232
|
+
this.state = {
|
|
233
|
+
...this.state,
|
|
234
|
+
replaceRevealed: !this.state.replaceRevealed
|
|
235
|
+
};
|
|
236
|
+
this.updateWidget();
|
|
237
|
+
return true;
|
|
238
|
+
}
|
|
239
|
+
toggleFindInSelection() {
|
|
240
|
+
if (this.state.inSelection) {
|
|
241
|
+
this.state = {
|
|
242
|
+
...this.state,
|
|
243
|
+
inSelection: false
|
|
244
|
+
};
|
|
245
|
+
this.scope = null;
|
|
246
|
+
this.research(this.options.cursorMoveOnType);
|
|
247
|
+
return true;
|
|
248
|
+
}
|
|
249
|
+
const host = this.host;
|
|
250
|
+
if (!host) return false;
|
|
251
|
+
const scopes = nonEmptySelectionRanges(this.commandSelections());
|
|
252
|
+
if (scopes.length === 0) return false;
|
|
253
|
+
this.state = {
|
|
254
|
+
...this.state,
|
|
255
|
+
inSelection: true
|
|
256
|
+
};
|
|
257
|
+
this.scope = host.trackRanges(scopes);
|
|
258
|
+
this.research(this.options.cursorMoveOnType);
|
|
259
|
+
return true;
|
|
260
|
+
}
|
|
261
|
+
setSearchString(value) {
|
|
262
|
+
this.state = {
|
|
263
|
+
...this.state,
|
|
264
|
+
searchString: value
|
|
265
|
+
};
|
|
266
|
+
if (this.options.findOnType) this.research(this.options.cursorMoveOnType);
|
|
267
|
+
}
|
|
268
|
+
setReplaceString(value) {
|
|
269
|
+
this.state = {
|
|
270
|
+
...this.state,
|
|
271
|
+
replaceString: value
|
|
272
|
+
};
|
|
273
|
+
this.updateWidget();
|
|
274
|
+
}
|
|
275
|
+
handleViewUpdate(kind, change) {
|
|
276
|
+
if (!this.state.revealed) return;
|
|
277
|
+
if (!isFindDocumentUpdate(kind)) return;
|
|
278
|
+
if (change?.kind === "selection" || change?.kind === "none") return;
|
|
279
|
+
if (kind === "clear") {
|
|
280
|
+
this.close();
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
if (this.replacing) return;
|
|
284
|
+
if (kind === "document") {
|
|
285
|
+
this.pendingResearch = false;
|
|
286
|
+
this.scope = null;
|
|
287
|
+
this.state = {
|
|
288
|
+
...this.state,
|
|
289
|
+
inSelection: false
|
|
290
|
+
};
|
|
291
|
+
this.research(false);
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
if (this.pendingResearch) {
|
|
295
|
+
this.research(false);
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
this.scheduleResearch();
|
|
299
|
+
}
|
|
300
|
+
detachHost(host) {
|
|
301
|
+
if (this.host !== host) return;
|
|
302
|
+
this.pendingResearch = false;
|
|
303
|
+
this.clearHighlights();
|
|
304
|
+
this.host = null;
|
|
305
|
+
this.matchHighlightName = "";
|
|
306
|
+
this.currentHighlightName = "";
|
|
307
|
+
this.scopeHighlightName = "";
|
|
308
|
+
this.state = {
|
|
309
|
+
...this.state,
|
|
310
|
+
revealed: false,
|
|
311
|
+
inSelection: false
|
|
312
|
+
};
|
|
313
|
+
this.matches = [];
|
|
314
|
+
this.highlightsTruncated = false;
|
|
315
|
+
this.totalMatches = 0;
|
|
316
|
+
this.scope = null;
|
|
317
|
+
this.trackedMatches = null;
|
|
318
|
+
this.currentMatch = null;
|
|
319
|
+
this.scheduler.cancel(FIND_RESEARCH_KEY);
|
|
320
|
+
}
|
|
321
|
+
detachEditHost(host) {
|
|
322
|
+
if (this.editHost !== host) return;
|
|
323
|
+
this.pendingResearch = false;
|
|
324
|
+
this.editHost = null;
|
|
325
|
+
}
|
|
326
|
+
open(options) {
|
|
327
|
+
const host = this.host;
|
|
328
|
+
if (!host || !host.hasDocument()) return false;
|
|
329
|
+
const searchString = this.seedSearchString();
|
|
330
|
+
this.state = {
|
|
331
|
+
...this.state,
|
|
332
|
+
searchString: searchString || this.state.searchString,
|
|
333
|
+
revealed: true,
|
|
334
|
+
replaceRevealed: options.replace || this.state.replaceRevealed
|
|
335
|
+
};
|
|
336
|
+
this.applyAutoFindInSelection(host);
|
|
337
|
+
this.emit({
|
|
338
|
+
type: "show",
|
|
339
|
+
replaceVisible: this.state.replaceRevealed
|
|
340
|
+
});
|
|
341
|
+
this.research(false);
|
|
342
|
+
this.focusWidget(options.focus);
|
|
343
|
+
return true;
|
|
344
|
+
}
|
|
345
|
+
ensureFindReady(focus) {
|
|
346
|
+
if (!this.state.revealed) return this.open({
|
|
347
|
+
replace: focus === "replace",
|
|
348
|
+
focus
|
|
349
|
+
});
|
|
350
|
+
if (this.matches.length === 0) this.research(false);
|
|
351
|
+
return this.state.searchString.length > 0;
|
|
352
|
+
}
|
|
353
|
+
research(moveCursor) {
|
|
354
|
+
if (!this.host) return;
|
|
355
|
+
if (!this.canPaintCurrentDocument()) {
|
|
356
|
+
this.pendingResearch = true;
|
|
357
|
+
if (moveCursor) this.selectFirstMatchFromSelection();
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
this.pendingResearch = false;
|
|
361
|
+
this.scheduler.cancel(FIND_RESEARCH_KEY);
|
|
362
|
+
const found = this.findAll(false, FIND_MATCHES_LIMIT + 1);
|
|
363
|
+
this.highlightsTruncated = found.length > FIND_MATCHES_LIMIT;
|
|
364
|
+
this.totalMatches = this.highlightsTruncated ? this.countAll() : found.length;
|
|
365
|
+
this.adoptMatches(this.highlightsTruncated ? found.slice(0, FIND_MATCHES_LIMIT) : found);
|
|
366
|
+
this.currentMatch = matchAtSelection(this.matches, this.primarySelection());
|
|
367
|
+
this.updateHighlights();
|
|
368
|
+
this.updateWidget();
|
|
369
|
+
if (moveCursor && found.length > 0) this.selectFirstMatchFromSelection();
|
|
370
|
+
}
|
|
371
|
+
scheduleResearch() {
|
|
372
|
+
this.followPendingMatches();
|
|
373
|
+
this.scheduler.schedule({
|
|
374
|
+
key: FIND_RESEARCH_KEY,
|
|
375
|
+
taskClass: "background-derived",
|
|
376
|
+
delayMs: FIND_RESEARCH_DELAY_MS,
|
|
377
|
+
maxDelayMs: FIND_RESEARCH_MAX_DELAY_MS,
|
|
378
|
+
run: () => this.research(false)
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Adopts a match set and hands it to the host to follow as far as it is painted.
|
|
383
|
+
*
|
|
384
|
+
* Handed over now rather than when an edit arrives: find is told about an edit
|
|
385
|
+
* once the text has already moved, and a range recorded against the document
|
|
386
|
+
* as it is by then is a range that will never shift.
|
|
387
|
+
*
|
|
388
|
+
* What the reader can see is the bound. A set stopping at the paint cap holds
|
|
389
|
+
* twenty thousand matches, and asking the host to carry all of them costs more
|
|
390
|
+
* per keystroke than the search this deferral exists to keep off it.
|
|
391
|
+
*/
|
|
392
|
+
adoptMatches(matches) {
|
|
393
|
+
this.matches = matches;
|
|
394
|
+
this.trackedMatches = this.host?.trackPaintedRanges(matches) ?? null;
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Carries the painted matches through the edit that deferred their re-search.
|
|
398
|
+
*
|
|
399
|
+
* Until it lands they are what the reader sees, and the offsets they were found
|
|
400
|
+
* at address text that has since moved — so they are read back from where that
|
|
401
|
+
* text went. A match whose text is gone resolves to nothing and stops being
|
|
402
|
+
* painted.
|
|
403
|
+
*/
|
|
404
|
+
/** Takes the offsets again from what the host is following, after the line it follows to moved. */
|
|
405
|
+
refreshTrackedMatches() {
|
|
406
|
+
this.followPendingMatches();
|
|
407
|
+
}
|
|
408
|
+
followPendingMatches() {
|
|
409
|
+
if (!this.canPaintCurrentDocument()) return;
|
|
410
|
+
const tracked = this.trackedMatches;
|
|
411
|
+
if (!tracked || this.matches.length === 0) return;
|
|
412
|
+
this.matches = tracked.resolve().map((range) => ({
|
|
413
|
+
...range,
|
|
414
|
+
matches: null
|
|
415
|
+
}));
|
|
416
|
+
this.currentMatch = matchAtSelection(this.matches, this.primarySelection());
|
|
417
|
+
this.updateHighlights();
|
|
418
|
+
this.updateWidget();
|
|
419
|
+
}
|
|
420
|
+
applyReplacement(edits, timingName, selection) {
|
|
421
|
+
const editHost = this.editHost;
|
|
422
|
+
if (!editHost) return;
|
|
423
|
+
this.replacing = true;
|
|
424
|
+
try {
|
|
425
|
+
editHost.applyEdits(edits, timingName, selection);
|
|
426
|
+
} finally {
|
|
427
|
+
this.replacing = false;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
selectNextReplacementMatch() {
|
|
431
|
+
const offset = this.primarySelection()?.endOffset ?? 0;
|
|
432
|
+
const match = this.searchFrom(findNextMatchFrom, offset, {});
|
|
433
|
+
if (!match) return;
|
|
434
|
+
this.host?.setSelection(match.start, match.end, "input.findNavigate", findReveal(match));
|
|
435
|
+
}
|
|
436
|
+
canPaintCurrentDocument() {
|
|
437
|
+
if (!this.editHost) return true;
|
|
438
|
+
const snapshot = this.editHost.textSnapshot();
|
|
439
|
+
return snapshot !== null && this.host?.hasTextSnapshot(snapshot) === true;
|
|
440
|
+
}
|
|
441
|
+
commandTextSource() {
|
|
442
|
+
if (!this.editHost) return this.host?.textSource();
|
|
443
|
+
const snapshot = this.editHost.textSnapshot();
|
|
444
|
+
return snapshot ? findTextSourceFromSnapshot(snapshot) : void 0;
|
|
445
|
+
}
|
|
446
|
+
commandSelections() {
|
|
447
|
+
return this.editHost?.getSelections() ?? this.host?.getSelections() ?? [];
|
|
448
|
+
}
|
|
449
|
+
commandSearchTarget() {
|
|
450
|
+
const source = this.commandTextSource();
|
|
451
|
+
return source ? this.searchTarget(source) : null;
|
|
452
|
+
}
|
|
453
|
+
findAll(captureMatches, limit = FIND_MATCHES_LIMIT) {
|
|
454
|
+
const search = this.searchTarget();
|
|
455
|
+
if (!search) return [];
|
|
456
|
+
return findMatches(search.source, this.state, search.scopes, captureMatches, limit);
|
|
457
|
+
}
|
|
458
|
+
countAll(before) {
|
|
459
|
+
const search = this.searchTarget();
|
|
460
|
+
return search ? countMatches(search.source, this.state, search.scopes, before) : 0;
|
|
461
|
+
}
|
|
462
|
+
searchFrom(find, offset, options) {
|
|
463
|
+
const search = this.commandSearchTarget();
|
|
464
|
+
if (!search) return null;
|
|
465
|
+
return find(search.source, this.state, offset, search.scopes, {
|
|
466
|
+
loop: this.options.loop,
|
|
467
|
+
...options
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
searchTarget(source = this.host?.textSource()) {
|
|
471
|
+
if (!source) return null;
|
|
472
|
+
const scopes = this.scopeRanges();
|
|
473
|
+
if (scopes && scopes.length === 0) return null;
|
|
474
|
+
return {
|
|
475
|
+
source,
|
|
476
|
+
scopes
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
scopeRanges() {
|
|
480
|
+
return this.scope?.resolve() ?? null;
|
|
481
|
+
}
|
|
482
|
+
selectFirstMatchFromSelection() {
|
|
483
|
+
const offset = this.primarySelection()?.endOffset ?? 0;
|
|
484
|
+
this.selectMatch(this.nextMatchAt(offset, false), false);
|
|
485
|
+
}
|
|
486
|
+
nextMatchAt(offset, escapeEmptyMatchAtOffset) {
|
|
487
|
+
return this.searchFrom(findNextMatchFrom, offset, { escapeEmptyMatchAtOffset });
|
|
488
|
+
}
|
|
489
|
+
selectMatch(match, committed = true) {
|
|
490
|
+
const host = this.host;
|
|
491
|
+
if (!match || !host) return false;
|
|
492
|
+
if (this.canPaintCurrentDocument()) this.currentMatch = match;
|
|
493
|
+
host.setSelection(match.start, match.end, "input.findNavigate", findReveal(match, committed));
|
|
494
|
+
if (!this.canPaintCurrentDocument()) return true;
|
|
495
|
+
this.updateHighlights();
|
|
496
|
+
this.updateWidget();
|
|
497
|
+
return true;
|
|
498
|
+
}
|
|
499
|
+
updateHighlights() {
|
|
500
|
+
if (!this.canPaintCurrentDocument()) return;
|
|
501
|
+
const host = this.host;
|
|
502
|
+
if (!host) return;
|
|
503
|
+
host.setRangeHighlight(this.matchHighlightName, this.matches, FIND_MATCH_STYLE);
|
|
504
|
+
host.setRangeHighlight(this.currentHighlightName, this.currentMatchRanges(), FIND_CURRENT_STYLE);
|
|
505
|
+
const scopes = this.scopeRanges();
|
|
506
|
+
if (scopes) {
|
|
507
|
+
host.setRangeHighlight(this.scopeHighlightName, scopes, FIND_SCOPE_STYLE);
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
host.clearRangeHighlight(this.scopeHighlightName);
|
|
511
|
+
}
|
|
512
|
+
currentMatchRanges() {
|
|
513
|
+
return this.currentMatch ? [this.currentMatch] : [];
|
|
514
|
+
}
|
|
515
|
+
clearHighlights() {
|
|
516
|
+
const host = this.host;
|
|
517
|
+
if (!host || !this.matchHighlightName) return;
|
|
518
|
+
host.clearRangeHighlight(this.matchHighlightName);
|
|
519
|
+
host.clearRangeHighlight(this.currentHighlightName);
|
|
520
|
+
host.clearRangeHighlight(this.scopeHighlightName);
|
|
521
|
+
}
|
|
522
|
+
updateWidget() {
|
|
523
|
+
this.emit({
|
|
524
|
+
type: "update",
|
|
525
|
+
state: this.widgetState()
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
widgetState() {
|
|
529
|
+
return {
|
|
530
|
+
...this.state,
|
|
531
|
+
matchesCount: this.highlightsTruncated ? this.totalMatches : this.matches.length,
|
|
532
|
+
matchesPosition: this.currentMatchPosition(),
|
|
533
|
+
highlightsTruncated: this.highlightsTruncated
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
currentMatchPosition() {
|
|
537
|
+
if (!this.currentMatch) return 0;
|
|
538
|
+
const painted = findMatchIndex(this.matches, this.currentMatch);
|
|
539
|
+
if (painted >= 0 || !this.highlightsTruncated) return painted + 1;
|
|
540
|
+
return this.countAll(this.currentMatch.start) + 1;
|
|
541
|
+
}
|
|
542
|
+
focusWidget(focus) {
|
|
543
|
+
if (focus === "find") this.emit({
|
|
544
|
+
type: "focus",
|
|
545
|
+
target: "find"
|
|
546
|
+
});
|
|
547
|
+
if (focus === "replace") this.emit({
|
|
548
|
+
type: "focus",
|
|
549
|
+
target: "replace"
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
seedSearchString() {
|
|
553
|
+
if (this.options.seedSearchStringFromSelection === "never") return "";
|
|
554
|
+
const selection = this.primarySelection();
|
|
555
|
+
if (!selection) return "";
|
|
556
|
+
const source = this.commandTextSource();
|
|
557
|
+
if (!source) return "";
|
|
558
|
+
if (!selection.collapsed) return this.seedFromLiteralText(selectedSingleLineText(source, selection));
|
|
559
|
+
if (this.options.seedSearchStringFromSelection === "selection") return "";
|
|
560
|
+
return this.seedFromLiteralText(wordTextAtOffset(source, selection.headOffset));
|
|
561
|
+
}
|
|
562
|
+
seedFromLiteralText(value) {
|
|
563
|
+
return this.state.isRegex ? escapeRegExpCharacters(value) : value;
|
|
564
|
+
}
|
|
565
|
+
applyAutoFindInSelection(host) {
|
|
566
|
+
const scopes = nonEmptySelectionRanges(this.commandSelections());
|
|
567
|
+
if (scopes.length === 0) return;
|
|
568
|
+
if (this.options.autoFindInSelection === "never") return;
|
|
569
|
+
if (this.options.autoFindInSelection === "always") {
|
|
570
|
+
this.state = {
|
|
571
|
+
...this.state,
|
|
572
|
+
inSelection: true
|
|
573
|
+
};
|
|
574
|
+
this.scope = host.trackRanges(scopes);
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
const source = this.commandTextSource();
|
|
578
|
+
if (!source || !hasMultilineScope(source, scopes)) return;
|
|
579
|
+
this.state = {
|
|
580
|
+
...this.state,
|
|
581
|
+
inSelection: true
|
|
582
|
+
};
|
|
583
|
+
this.scope = host.trackRanges(scopes);
|
|
584
|
+
}
|
|
585
|
+
replacePattern() {
|
|
586
|
+
if (this.state.isRegex) return parseReplaceString(this.state.replaceString);
|
|
587
|
+
return ReplacePattern.fromStaticValue(this.state.replaceString);
|
|
588
|
+
}
|
|
589
|
+
primarySelection() {
|
|
590
|
+
return this.commandSelections()[0] ?? null;
|
|
591
|
+
}
|
|
592
|
+
emit(event) {
|
|
593
|
+
for (const listener of this.listeners) listener(event);
|
|
594
|
+
}
|
|
595
|
+
};
|
|
596
|
+
function resolveFindOptions(options) {
|
|
597
|
+
return {
|
|
598
|
+
loop: options.loop ?? true,
|
|
599
|
+
seedSearchStringFromSelection: options.seedSearchStringFromSelection ?? "always",
|
|
600
|
+
findOnType: options.findOnType ?? true,
|
|
601
|
+
cursorMoveOnType: options.cursorMoveOnType ?? true,
|
|
602
|
+
autoFindInSelection: options.autoFindInSelection ?? "never"
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
function isFindDocumentUpdate(kind) {
|
|
606
|
+
return kind === "document" || kind === "content" || kind === "clear";
|
|
607
|
+
}
|
|
608
|
+
function selectedSingleLineText(source, selection) {
|
|
609
|
+
if (selection.endOffset - selection.startOffset >= SEARCH_STRING_MAX_LENGTH) return "";
|
|
610
|
+
if (spansLines(source, selection.startOffset, selection.endOffset)) return "";
|
|
611
|
+
return source.readRange(selection.startOffset, selection.endOffset);
|
|
612
|
+
}
|
|
613
|
+
function wordTextAtOffset(source, offset) {
|
|
614
|
+
const line = findLineRange(source, offset);
|
|
615
|
+
const text = source.readRange(line.start, line.end);
|
|
616
|
+
const word = wordRangeAtOffset(text, offset - line.start);
|
|
617
|
+
return text.slice(word.start, word.end);
|
|
618
|
+
}
|
|
619
|
+
function nonEmptySelectionRanges(selections) {
|
|
620
|
+
return selections.filter((selection) => !selection.collapsed).map((selection) => ({
|
|
621
|
+
start: selection.startOffset,
|
|
622
|
+
end: selection.endOffset
|
|
623
|
+
}));
|
|
624
|
+
}
|
|
625
|
+
function hasMultilineScope(source, scopes) {
|
|
626
|
+
return scopes.some((scope) => spansLines(source, scope.start, scope.end));
|
|
627
|
+
}
|
|
628
|
+
function spansLines(source, start, end) {
|
|
629
|
+
const lineStartsView = source.lineStartsView;
|
|
630
|
+
return lineStartsView.indexForOffset(start) !== lineStartsView.indexForOffset(end);
|
|
631
|
+
}
|
|
632
|
+
function matchAtSelection(matches, selection) {
|
|
633
|
+
if (!selection) return null;
|
|
634
|
+
return matches.find((match) => match.start === selection.startOffset && match.end === selection.endOffset) ?? null;
|
|
635
|
+
}
|
|
636
|
+
function orderedMatchSelections(matches, currentSelection) {
|
|
637
|
+
const selections = matches.map(selectionForMatch);
|
|
638
|
+
const currentMatch = matchAtSelection(matches, currentSelection);
|
|
639
|
+
const currentIndex = currentMatch ? findMatchIndex(matches, currentMatch) : -1;
|
|
640
|
+
if (!currentSelection || currentIndex < 0) return selections;
|
|
641
|
+
const current = {
|
|
642
|
+
anchor: currentSelection.anchorOffset,
|
|
643
|
+
head: currentSelection.headOffset,
|
|
644
|
+
affinity: currentSelection.affinity
|
|
645
|
+
};
|
|
646
|
+
if (currentIndex === 0) return [current, ...selections.slice(1)];
|
|
647
|
+
return [
|
|
648
|
+
current,
|
|
649
|
+
...selections.slice(0, currentIndex),
|
|
650
|
+
...selections.slice(currentIndex + 1)
|
|
651
|
+
];
|
|
652
|
+
}
|
|
653
|
+
function selectionForMatch(match) {
|
|
654
|
+
return {
|
|
655
|
+
anchor: match.start,
|
|
656
|
+
head: match.end,
|
|
657
|
+
affinity: "after"
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
function mergeAdjacentReplaceEdits(edits) {
|
|
661
|
+
const sorted = edits.toSorted((left, right) => left.from - right.from || left.to - right.to);
|
|
662
|
+
const merged = [];
|
|
663
|
+
for (const edit of sorted) mergeReplaceEdit(merged, edit);
|
|
664
|
+
return merged;
|
|
665
|
+
}
|
|
666
|
+
function mergeReplaceEdit(merged, edit) {
|
|
667
|
+
const previous = merged.at(-1);
|
|
668
|
+
if (!previous || previous.to !== edit.from) {
|
|
669
|
+
merged.push({ ...edit });
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
merged[merged.length - 1] = {
|
|
673
|
+
from: previous.from,
|
|
674
|
+
to: edit.to,
|
|
675
|
+
text: previous.text + edit.text
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
//#endregion
|
|
679
|
+
export { EditorFindController, FIND_HIGHLIGHT_Z_INDEX };
|
|
680
|
+
|
|
681
|
+
//# sourceMappingURL=findController.js.map
|