@input/pen-search 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +21 -0
- package/README.md +63 -0
- package/dist/index.cjs +886 -0
- package/dist/index.d.cts +60 -0
- package/dist/index.d.ts +60 -0
- package/dist/index.mjs +858 -0
- package/package.json +63 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,886 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
DEFAULT_SEARCH_LOCALE: () => DEFAULT_SEARCH_LOCALE,
|
|
24
|
+
DEFAULT_SEARCH_OPTIONS: () => DEFAULT_SEARCH_OPTIONS,
|
|
25
|
+
SEARCH_BUDGET_EXCEEDED_CODE: () => SEARCH_BUDGET_EXCEEDED_CODE,
|
|
26
|
+
SEARCH_EXECUTION_BUDGET_MS: () => SEARCH_EXECUTION_BUDGET_MS,
|
|
27
|
+
SEARCH_EXTENSION_NAME: () => SEARCH_EXTENSION_NAME,
|
|
28
|
+
SEARCH_INVALID_PATTERN_CODE: () => SEARCH_INVALID_PATTERN_CODE,
|
|
29
|
+
SEARCH_QUERY_MAX_LENGTH: () => SEARCH_QUERY_MAX_LENGTH,
|
|
30
|
+
SEARCH_REGEX_SEGMENT_MAX_CODE_UNITS: () => SEARCH_REGEX_SEGMENT_MAX_CODE_UNITS,
|
|
31
|
+
buildReplaceAllOps: () => buildReplaceAllOps,
|
|
32
|
+
buildReplaceOps: () => buildReplaceOps,
|
|
33
|
+
buildSearchRegex: () => buildSearchRegex,
|
|
34
|
+
findDocumentMatches: () => findDocumentMatches,
|
|
35
|
+
getSearchController: () => getSearchController,
|
|
36
|
+
searchExtension: () => searchExtension
|
|
37
|
+
});
|
|
38
|
+
module.exports = __toCommonJS(index_exports);
|
|
39
|
+
|
|
40
|
+
// src/extension.ts
|
|
41
|
+
var import_pen_types = require("@input/pen-types");
|
|
42
|
+
var import_pen_core3 = require("@input/pen-core");
|
|
43
|
+
|
|
44
|
+
// src/controller.ts
|
|
45
|
+
var import_pen_core2 = require("@input/pen-core");
|
|
46
|
+
|
|
47
|
+
// src/search.ts
|
|
48
|
+
var import_pen_core = require("@input/pen-core");
|
|
49
|
+
var SEARCH_QUERY_MAX_LENGTH = 1024;
|
|
50
|
+
var SEARCH_EXECUTION_BUDGET_MS = 50;
|
|
51
|
+
var SEARCH_REGEX_SEGMENT_MAX_CODE_UNITS = 64 * 1024;
|
|
52
|
+
var SEARCH_BUDGET_EXCEEDED_CODE = "search-budget-exceeded";
|
|
53
|
+
var SEARCH_INVALID_PATTERN_CODE = "search-invalid-pattern";
|
|
54
|
+
var DEFAULT_SEARCH_LOCALE = "en";
|
|
55
|
+
var DEFAULT_SEARCH_OPTIONS = {
|
|
56
|
+
caseSensitive: false,
|
|
57
|
+
regex: false,
|
|
58
|
+
wholeWord: false
|
|
59
|
+
};
|
|
60
|
+
function createInitialSearchState() {
|
|
61
|
+
return {
|
|
62
|
+
open: false,
|
|
63
|
+
query: "",
|
|
64
|
+
replaceText: "",
|
|
65
|
+
matches: [],
|
|
66
|
+
activeIndex: -1,
|
|
67
|
+
options: DEFAULT_SEARCH_OPTIONS
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function findDocumentMatches(editor, query, options) {
|
|
71
|
+
if (!query) {
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
if (query.length > SEARCH_QUERY_MAX_LENGTH) {
|
|
75
|
+
emitSearchDiagnostic(
|
|
76
|
+
editor,
|
|
77
|
+
SEARCH_INVALID_PATTERN_CODE,
|
|
78
|
+
`Search query exceeds the ${SEARCH_QUERY_MAX_LENGTH}-character limit.`
|
|
79
|
+
);
|
|
80
|
+
return [];
|
|
81
|
+
}
|
|
82
|
+
const locale = resolveSearchLocale(editor, options);
|
|
83
|
+
const regex = options.regex ? buildSearchRegex(query, options) : null;
|
|
84
|
+
if (options.regex && !regex) {
|
|
85
|
+
emitSearchDiagnostic(
|
|
86
|
+
editor,
|
|
87
|
+
SEARCH_INVALID_PATTERN_CODE,
|
|
88
|
+
"Search pattern is invalid."
|
|
89
|
+
);
|
|
90
|
+
return [];
|
|
91
|
+
}
|
|
92
|
+
const matches = [];
|
|
93
|
+
const deadline = options.regex ? performance.now() + SEARCH_EXECUTION_BUDGET_MS : null;
|
|
94
|
+
const execution = {
|
|
95
|
+
query,
|
|
96
|
+
options,
|
|
97
|
+
locale,
|
|
98
|
+
regex,
|
|
99
|
+
deadline
|
|
100
|
+
};
|
|
101
|
+
for (const handle of editor.documentState.allBlocks()) {
|
|
102
|
+
if (hasExceededDeadline(deadline)) {
|
|
103
|
+
emitBudgetExceeded(editor);
|
|
104
|
+
return matches;
|
|
105
|
+
}
|
|
106
|
+
const blockResult = findMatchesInBlock(handle, execution, matches.length);
|
|
107
|
+
matches.push(...blockResult.matches);
|
|
108
|
+
if (blockResult.exceeded) {
|
|
109
|
+
emitBudgetExceeded(editor);
|
|
110
|
+
return matches;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return matches;
|
|
114
|
+
}
|
|
115
|
+
function buildSearchRegex(query, options) {
|
|
116
|
+
if (!query || query.length > SEARCH_QUERY_MAX_LENGTH) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
const pattern = options.regex ? query : escapeRegExp(query);
|
|
120
|
+
const flags = options.caseSensitive ? "gu" : "giu";
|
|
121
|
+
try {
|
|
122
|
+
return new RegExp(pattern, flags);
|
|
123
|
+
} catch {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
function normalizeActiveIndex(activeIndex, matchCount) {
|
|
128
|
+
if (matchCount === 0) {
|
|
129
|
+
return -1;
|
|
130
|
+
}
|
|
131
|
+
if (activeIndex < 0) {
|
|
132
|
+
return 0;
|
|
133
|
+
}
|
|
134
|
+
if (activeIndex >= matchCount) {
|
|
135
|
+
return matchCount - 1;
|
|
136
|
+
}
|
|
137
|
+
return activeIndex;
|
|
138
|
+
}
|
|
139
|
+
function getNextActiveIndex(activeIndex, matchCount) {
|
|
140
|
+
if (matchCount === 0) {
|
|
141
|
+
return -1;
|
|
142
|
+
}
|
|
143
|
+
if (activeIndex < 0) {
|
|
144
|
+
return 0;
|
|
145
|
+
}
|
|
146
|
+
return (activeIndex + 1) % matchCount;
|
|
147
|
+
}
|
|
148
|
+
function getPreviousActiveIndex(activeIndex, matchCount) {
|
|
149
|
+
if (matchCount === 0) {
|
|
150
|
+
return -1;
|
|
151
|
+
}
|
|
152
|
+
if (activeIndex <= 0) {
|
|
153
|
+
return matchCount - 1;
|
|
154
|
+
}
|
|
155
|
+
return activeIndex - 1;
|
|
156
|
+
}
|
|
157
|
+
function buildReplaceOps(match, replaceText) {
|
|
158
|
+
if (!match) {
|
|
159
|
+
return [];
|
|
160
|
+
}
|
|
161
|
+
if (match.kind === "block") {
|
|
162
|
+
return [
|
|
163
|
+
{
|
|
164
|
+
type: "splice-text",
|
|
165
|
+
blockId: match.blockId,
|
|
166
|
+
from: match.from,
|
|
167
|
+
to: match.from + match.to - match.from,
|
|
168
|
+
insert: ""
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
type: "splice-text",
|
|
172
|
+
blockId: match.blockId,
|
|
173
|
+
from: match.from,
|
|
174
|
+
to: match.from,
|
|
175
|
+
insert: replaceText
|
|
176
|
+
}
|
|
177
|
+
];
|
|
178
|
+
}
|
|
179
|
+
if (match.kind === "table-cell") {
|
|
180
|
+
return [
|
|
181
|
+
{
|
|
182
|
+
type: "splice-text",
|
|
183
|
+
blockId: match.blockId,
|
|
184
|
+
cell: { row: match.row ?? 0, col: match.col ?? 0 },
|
|
185
|
+
from: match.from,
|
|
186
|
+
to: match.to,
|
|
187
|
+
insert: ""
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
type: "splice-text",
|
|
191
|
+
blockId: match.blockId,
|
|
192
|
+
cell: { row: match.row ?? 0, col: match.col ?? 0 },
|
|
193
|
+
from: match.from,
|
|
194
|
+
to: match.from,
|
|
195
|
+
insert: replaceText
|
|
196
|
+
}
|
|
197
|
+
];
|
|
198
|
+
}
|
|
199
|
+
return [];
|
|
200
|
+
}
|
|
201
|
+
function buildReplaceAllOps(matches, replaceText) {
|
|
202
|
+
const matchesByTarget = /* @__PURE__ */ new Map();
|
|
203
|
+
for (const match of matches) {
|
|
204
|
+
const targetMatches = matchesByTarget.get(getMatchTargetKey(match)) ?? [];
|
|
205
|
+
targetMatches.push(match);
|
|
206
|
+
matchesByTarget.set(getMatchTargetKey(match), targetMatches);
|
|
207
|
+
}
|
|
208
|
+
const ops = [];
|
|
209
|
+
for (const [, blockMatches] of matchesByTarget) {
|
|
210
|
+
const sortedMatches = [...blockMatches].sort((left, right) => {
|
|
211
|
+
return right.from - left.from;
|
|
212
|
+
});
|
|
213
|
+
const firstMatch = sortedMatches[0];
|
|
214
|
+
if (!firstMatch) {
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
for (const match of sortedMatches) {
|
|
218
|
+
if (match.kind === "block") {
|
|
219
|
+
ops.push(
|
|
220
|
+
{
|
|
221
|
+
type: "splice-text",
|
|
222
|
+
blockId: match.blockId,
|
|
223
|
+
from: match.from,
|
|
224
|
+
to: match.from + match.to - match.from,
|
|
225
|
+
insert: ""
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
type: "splice-text",
|
|
229
|
+
blockId: match.blockId,
|
|
230
|
+
from: match.from,
|
|
231
|
+
to: match.from,
|
|
232
|
+
insert: replaceText
|
|
233
|
+
}
|
|
234
|
+
);
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
ops.push(
|
|
238
|
+
{
|
|
239
|
+
type: "splice-text",
|
|
240
|
+
blockId: match.blockId,
|
|
241
|
+
cell: { row: match.row ?? 0, col: match.col ?? 0 },
|
|
242
|
+
from: match.from,
|
|
243
|
+
to: match.to,
|
|
244
|
+
insert: ""
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
type: "splice-text",
|
|
248
|
+
blockId: match.blockId,
|
|
249
|
+
cell: { row: match.row ?? 0, col: match.col ?? 0 },
|
|
250
|
+
from: match.from,
|
|
251
|
+
to: match.from,
|
|
252
|
+
insert: replaceText
|
|
253
|
+
}
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return ops;
|
|
258
|
+
}
|
|
259
|
+
function revealActiveMatch(editor, match) {
|
|
260
|
+
if (!match) {
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
if (match.kind === "block") {
|
|
264
|
+
editor.selectText(match.blockId, match.from, match.to);
|
|
265
|
+
} else {
|
|
266
|
+
const row = match.row ?? 0;
|
|
267
|
+
const col = match.col ?? 0;
|
|
268
|
+
editor.selectCellRange(
|
|
269
|
+
match.blockId,
|
|
270
|
+
{ row, col },
|
|
271
|
+
{ row, col }
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
editor.scrollToBlock?.(match.blockId);
|
|
275
|
+
}
|
|
276
|
+
function findMatchesInBlock(handle, execution, startIndex) {
|
|
277
|
+
const matches = [];
|
|
278
|
+
const text = handle.textContent();
|
|
279
|
+
if (text) {
|
|
280
|
+
const textResult = collectMatchesInText(
|
|
281
|
+
text,
|
|
282
|
+
execution,
|
|
283
|
+
startIndex,
|
|
284
|
+
(from, to, matchedText, index) => ({
|
|
285
|
+
kind: "block",
|
|
286
|
+
blockId: handle.id,
|
|
287
|
+
from,
|
|
288
|
+
to,
|
|
289
|
+
text: matchedText,
|
|
290
|
+
index
|
|
291
|
+
})
|
|
292
|
+
);
|
|
293
|
+
matches.push(...textResult.matches);
|
|
294
|
+
if (textResult.exceeded) {
|
|
295
|
+
return { matches, exceeded: true };
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
const tableResult = findMatchesInGridCells(
|
|
299
|
+
handle,
|
|
300
|
+
execution,
|
|
301
|
+
startIndex + matches.length
|
|
302
|
+
);
|
|
303
|
+
matches.push(...tableResult.matches);
|
|
304
|
+
if (tableResult.exceeded) {
|
|
305
|
+
return { matches, exceeded: true };
|
|
306
|
+
}
|
|
307
|
+
return { matches, exceeded: false };
|
|
308
|
+
}
|
|
309
|
+
function findMatchesInGridCells(handle, execution, startIndex) {
|
|
310
|
+
const table = handle.as("table");
|
|
311
|
+
if (!table) {
|
|
312
|
+
return { matches: [], exceeded: false };
|
|
313
|
+
}
|
|
314
|
+
const matches = [];
|
|
315
|
+
const rowCount = table.tableRowCount();
|
|
316
|
+
const columnCount = table.tableColumnCount();
|
|
317
|
+
for (let row = 0; row < rowCount; row += 1) {
|
|
318
|
+
for (let col = 0; col < columnCount; col += 1) {
|
|
319
|
+
const cell = table.tableCell(row, col);
|
|
320
|
+
const cellText = cell?.textContent() ?? "";
|
|
321
|
+
if (!cellText) {
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
const cellResult = collectMatchesInText(
|
|
325
|
+
cellText,
|
|
326
|
+
execution,
|
|
327
|
+
startIndex + matches.length,
|
|
328
|
+
(from, to, matchedText, index) => ({
|
|
329
|
+
kind: "table-cell",
|
|
330
|
+
blockId: handle.id,
|
|
331
|
+
row,
|
|
332
|
+
col,
|
|
333
|
+
from,
|
|
334
|
+
to,
|
|
335
|
+
text: matchedText,
|
|
336
|
+
index,
|
|
337
|
+
cellText
|
|
338
|
+
})
|
|
339
|
+
);
|
|
340
|
+
matches.push(...cellResult.matches);
|
|
341
|
+
if (cellResult.exceeded) {
|
|
342
|
+
return { matches, exceeded: true };
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
return { matches, exceeded: false };
|
|
347
|
+
}
|
|
348
|
+
function collectMatchesInText(text, execution, startIndex, createMatch) {
|
|
349
|
+
if (execution.regex) {
|
|
350
|
+
return collectSegmentedMatches(
|
|
351
|
+
text,
|
|
352
|
+
execution,
|
|
353
|
+
startIndex,
|
|
354
|
+
createMatch
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
return {
|
|
358
|
+
matches: collectLiteralMatches(text, execution, startIndex, createMatch),
|
|
359
|
+
exceeded: false
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
function collectSegmentedMatches(text, execution, startIndex, createMatch) {
|
|
363
|
+
const matches = [];
|
|
364
|
+
const regex = execution.regex;
|
|
365
|
+
if (!regex) {
|
|
366
|
+
return { matches, exceeded: false };
|
|
367
|
+
}
|
|
368
|
+
for (let segmentStart = 0; segmentStart < text.length; segmentStart += SEARCH_REGEX_SEGMENT_MAX_CODE_UNITS) {
|
|
369
|
+
if (hasExceededDeadline(execution.deadline)) {
|
|
370
|
+
return { matches, exceeded: true };
|
|
371
|
+
}
|
|
372
|
+
const segment = text.slice(
|
|
373
|
+
segmentStart,
|
|
374
|
+
segmentStart + SEARCH_REGEX_SEGMENT_MAX_CODE_UNITS
|
|
375
|
+
);
|
|
376
|
+
matches.push(
|
|
377
|
+
...collectTextMatches(
|
|
378
|
+
text,
|
|
379
|
+
segment,
|
|
380
|
+
segmentStart,
|
|
381
|
+
regex,
|
|
382
|
+
execution,
|
|
383
|
+
startIndex + matches.length,
|
|
384
|
+
createMatch
|
|
385
|
+
)
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
return { matches, exceeded: false };
|
|
389
|
+
}
|
|
390
|
+
function collectTextMatches(text, segment, segmentStart, regex, execution, startIndex, createMatch) {
|
|
391
|
+
const matches = [];
|
|
392
|
+
const localRegex = new RegExp(regex.source, regex.flags);
|
|
393
|
+
let match;
|
|
394
|
+
while ((match = localRegex.exec(segment)) !== null) {
|
|
395
|
+
const from = segmentStart + match.index;
|
|
396
|
+
const to = from + match[0].length;
|
|
397
|
+
if (!execution.options.wholeWord || isWholeWordMatch(text, from, to, execution.locale)) {
|
|
398
|
+
matches.push(
|
|
399
|
+
createMatch(from, to, match[0], startIndex + matches.length)
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
if (!localRegex.global) {
|
|
403
|
+
break;
|
|
404
|
+
}
|
|
405
|
+
if (match[0].length === 0) {
|
|
406
|
+
localRegex.lastIndex += 1;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
return matches;
|
|
410
|
+
}
|
|
411
|
+
function collectLiteralMatches(text, execution, startIndex, createMatch) {
|
|
412
|
+
const matches = [];
|
|
413
|
+
const ranges = execution.options.caseSensitive ? findExactRanges(text, execution.query) : findFoldedRanges(text, execution.query, execution.locale);
|
|
414
|
+
for (const range of ranges) {
|
|
415
|
+
if (execution.options.wholeWord && !isWholeWordMatch(text, range.from, range.to, execution.locale)) {
|
|
416
|
+
continue;
|
|
417
|
+
}
|
|
418
|
+
matches.push(
|
|
419
|
+
createMatch(
|
|
420
|
+
range.from,
|
|
421
|
+
range.to,
|
|
422
|
+
text.slice(range.from, range.to),
|
|
423
|
+
startIndex + matches.length
|
|
424
|
+
)
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
return matches;
|
|
428
|
+
}
|
|
429
|
+
function getMatchTargetKey(match) {
|
|
430
|
+
if (match.kind === "block") {
|
|
431
|
+
return `block:${match.blockId}`;
|
|
432
|
+
}
|
|
433
|
+
return `table:${match.blockId}:${match.row ?? -1}:${match.col ?? -1}`;
|
|
434
|
+
}
|
|
435
|
+
function resolveSearchLocale(editor, options) {
|
|
436
|
+
return options.locale ?? editor.facet(import_pen_core.localeFacet);
|
|
437
|
+
}
|
|
438
|
+
function isWordSegmentBoundary(text, offset, locale) {
|
|
439
|
+
if (offset <= 0 || offset >= text.length) {
|
|
440
|
+
return true;
|
|
441
|
+
}
|
|
442
|
+
const range = (0, import_pen_core.wordRangeAt)(text, offset, locale);
|
|
443
|
+
if (!range) {
|
|
444
|
+
return true;
|
|
445
|
+
}
|
|
446
|
+
return offset === range.start || offset === range.end;
|
|
447
|
+
}
|
|
448
|
+
function isWholeWordMatch(text, from, to, locale) {
|
|
449
|
+
return isWordSegmentBoundary(text, from, locale) && isWordSegmentBoundary(text, to, locale);
|
|
450
|
+
}
|
|
451
|
+
function findExactRanges(text, query) {
|
|
452
|
+
const ranges = [];
|
|
453
|
+
let searchFrom = 0;
|
|
454
|
+
while (searchFrom <= text.length - query.length) {
|
|
455
|
+
const index = text.indexOf(query, searchFrom);
|
|
456
|
+
if (index === -1) {
|
|
457
|
+
break;
|
|
458
|
+
}
|
|
459
|
+
ranges.push({ from: index, to: index + query.length });
|
|
460
|
+
searchFrom = index + query.length;
|
|
461
|
+
}
|
|
462
|
+
return ranges;
|
|
463
|
+
}
|
|
464
|
+
function findFoldedRanges(text, query, locale) {
|
|
465
|
+
const foldedQuery = (0, import_pen_core.foldAndNormalize)(query, locale);
|
|
466
|
+
if (!foldedQuery) {
|
|
467
|
+
return [];
|
|
468
|
+
}
|
|
469
|
+
const { folded, originAt } = foldTextWithOriginMap(text, locale);
|
|
470
|
+
const ranges = [];
|
|
471
|
+
let searchFrom = 0;
|
|
472
|
+
while (searchFrom <= folded.length - foldedQuery.length) {
|
|
473
|
+
const index = folded.indexOf(foldedQuery, searchFrom);
|
|
474
|
+
if (index === -1) {
|
|
475
|
+
break;
|
|
476
|
+
}
|
|
477
|
+
const from = originAt[index] ?? 0;
|
|
478
|
+
const to = originAt[index + foldedQuery.length] ?? text.length;
|
|
479
|
+
if (to > from) {
|
|
480
|
+
ranges.push({ from, to });
|
|
481
|
+
searchFrom = index + foldedQuery.length;
|
|
482
|
+
continue;
|
|
483
|
+
}
|
|
484
|
+
searchFrom = index + 1;
|
|
485
|
+
}
|
|
486
|
+
return ranges;
|
|
487
|
+
}
|
|
488
|
+
function foldTextWithOriginMap(text, locale) {
|
|
489
|
+
const foldedWhole = (0, import_pen_core.foldAndNormalize)(text, locale);
|
|
490
|
+
if (foldedWhole.length === text.length) {
|
|
491
|
+
const originAt2 = new Array(foldedWhole.length + 1);
|
|
492
|
+
for (let index = 0; index <= foldedWhole.length; index += 1) {
|
|
493
|
+
originAt2[index] = index;
|
|
494
|
+
}
|
|
495
|
+
return { folded: foldedWhole, originAt: originAt2 };
|
|
496
|
+
}
|
|
497
|
+
let folded = "";
|
|
498
|
+
const originAt = [];
|
|
499
|
+
let offset = 0;
|
|
500
|
+
while (offset < text.length) {
|
|
501
|
+
const next = (0, import_pen_core.nextGraphemeBoundary)(text, offset, locale);
|
|
502
|
+
const piece = (0, import_pen_core.foldAndNormalize)(text.slice(offset, next), locale);
|
|
503
|
+
for (let index = 0; index < piece.length; index += 1) {
|
|
504
|
+
originAt.push(offset);
|
|
505
|
+
}
|
|
506
|
+
folded += piece;
|
|
507
|
+
offset = next;
|
|
508
|
+
}
|
|
509
|
+
originAt.push(text.length);
|
|
510
|
+
return { folded, originAt };
|
|
511
|
+
}
|
|
512
|
+
function escapeRegExp(value) {
|
|
513
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
514
|
+
}
|
|
515
|
+
function hasExceededDeadline(deadline) {
|
|
516
|
+
return deadline !== null && performance.now() >= deadline;
|
|
517
|
+
}
|
|
518
|
+
function emitBudgetExceeded(editor) {
|
|
519
|
+
emitSearchDiagnostic(
|
|
520
|
+
editor,
|
|
521
|
+
SEARCH_BUDGET_EXCEEDED_CODE,
|
|
522
|
+
"Search stopped after the execution budget was exceeded."
|
|
523
|
+
);
|
|
524
|
+
}
|
|
525
|
+
function emitSearchDiagnostic(editor, code, message) {
|
|
526
|
+
const event = {
|
|
527
|
+
code,
|
|
528
|
+
level: "warn",
|
|
529
|
+
source: "search",
|
|
530
|
+
extension: "search",
|
|
531
|
+
message
|
|
532
|
+
};
|
|
533
|
+
editor.internals.emit("diagnostic", event);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// src/controller.ts
|
|
537
|
+
var SearchControllerImpl = class {
|
|
538
|
+
editor;
|
|
539
|
+
state;
|
|
540
|
+
listeners = /* @__PURE__ */ new Set();
|
|
541
|
+
constructor(editor) {
|
|
542
|
+
this.editor = editor;
|
|
543
|
+
this.state = createInitialSearchState();
|
|
544
|
+
}
|
|
545
|
+
getState() {
|
|
546
|
+
return this.state;
|
|
547
|
+
}
|
|
548
|
+
subscribe(listener) {
|
|
549
|
+
this.listeners.add(listener);
|
|
550
|
+
return () => {
|
|
551
|
+
this.listeners.delete(listener);
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
open() {
|
|
555
|
+
this.updateState({
|
|
556
|
+
...this.state,
|
|
557
|
+
open: true
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
close() {
|
|
561
|
+
this.updateState({
|
|
562
|
+
...this.state,
|
|
563
|
+
open: false
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
toggleOpen() {
|
|
567
|
+
this.updateState({
|
|
568
|
+
...this.state,
|
|
569
|
+
open: !this.state.open
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
setQuery(query) {
|
|
573
|
+
const nextState = {
|
|
574
|
+
...this.state,
|
|
575
|
+
query,
|
|
576
|
+
activeIndex: query ? this.state.activeIndex : -1
|
|
577
|
+
};
|
|
578
|
+
if (!this.updateState(nextState)) {
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
this.recompute();
|
|
582
|
+
}
|
|
583
|
+
setReplaceText(replaceText) {
|
|
584
|
+
this.updateState({
|
|
585
|
+
...this.state,
|
|
586
|
+
replaceText
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
setOptions(options) {
|
|
590
|
+
const nextState = {
|
|
591
|
+
...this.state,
|
|
592
|
+
options: {
|
|
593
|
+
...this.state.options,
|
|
594
|
+
...options
|
|
595
|
+
}
|
|
596
|
+
};
|
|
597
|
+
if (!this.updateState(nextState)) {
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
this.recompute();
|
|
601
|
+
}
|
|
602
|
+
next() {
|
|
603
|
+
const nextIndex = getNextActiveIndex(
|
|
604
|
+
this.state.activeIndex,
|
|
605
|
+
this.state.matches.length
|
|
606
|
+
);
|
|
607
|
+
if (nextIndex === this.state.activeIndex && this.state.matches.length === 0) {
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
this.state = {
|
|
611
|
+
...this.state,
|
|
612
|
+
activeIndex: nextIndex
|
|
613
|
+
};
|
|
614
|
+
revealActiveMatch(this.editor, this.getActiveMatch());
|
|
615
|
+
this.notify();
|
|
616
|
+
}
|
|
617
|
+
previous() {
|
|
618
|
+
const previousIndex = getPreviousActiveIndex(
|
|
619
|
+
this.state.activeIndex,
|
|
620
|
+
this.state.matches.length
|
|
621
|
+
);
|
|
622
|
+
if (previousIndex === this.state.activeIndex && this.state.matches.length === 0) {
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
this.state = {
|
|
626
|
+
...this.state,
|
|
627
|
+
activeIndex: previousIndex
|
|
628
|
+
};
|
|
629
|
+
revealActiveMatch(this.editor, this.getActiveMatch());
|
|
630
|
+
this.notify();
|
|
631
|
+
}
|
|
632
|
+
replace() {
|
|
633
|
+
const activeMatch = this.getActiveMatch();
|
|
634
|
+
const ops = buildReplaceOps(activeMatch, this.state.replaceText);
|
|
635
|
+
if (ops.length === 0) {
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
this.editor.apply(ops, {
|
|
639
|
+
origin: "user",
|
|
640
|
+
undoGroup: true
|
|
641
|
+
});
|
|
642
|
+
this.recompute();
|
|
643
|
+
revealActiveMatch(this.editor, this.getActiveMatch());
|
|
644
|
+
}
|
|
645
|
+
replaceAll() {
|
|
646
|
+
const ops = buildReplaceAllOps(this.state.matches, this.state.replaceText);
|
|
647
|
+
if (ops.length === 0) {
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
this.editor.apply(ops, {
|
|
651
|
+
origin: "user",
|
|
652
|
+
undoGroup: true
|
|
653
|
+
});
|
|
654
|
+
this.recompute();
|
|
655
|
+
}
|
|
656
|
+
recompute() {
|
|
657
|
+
const previousCount = this.state.matches.length;
|
|
658
|
+
const matches = findDocumentMatches(
|
|
659
|
+
this.editor,
|
|
660
|
+
this.state.query,
|
|
661
|
+
this.state.options
|
|
662
|
+
);
|
|
663
|
+
this.updateState({
|
|
664
|
+
...this.state,
|
|
665
|
+
matches,
|
|
666
|
+
activeIndex: normalizeActiveIndex(this.state.activeIndex, matches.length)
|
|
667
|
+
});
|
|
668
|
+
if (this.state.query.length > 0 && matches.length !== previousCount) {
|
|
669
|
+
(0, import_pen_core2.announceEditorA11y)(this.editor, "findMatches", {
|
|
670
|
+
count: matches.length
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
getActiveMatch() {
|
|
675
|
+
return this.state.matches[this.state.activeIndex] ?? null;
|
|
676
|
+
}
|
|
677
|
+
notify() {
|
|
678
|
+
for (const listener of this.listeners) {
|
|
679
|
+
listener();
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
updateState(nextState) {
|
|
683
|
+
if (searchStatesEqual(this.state, nextState)) {
|
|
684
|
+
return false;
|
|
685
|
+
}
|
|
686
|
+
this.state = nextState;
|
|
687
|
+
this.notify();
|
|
688
|
+
return true;
|
|
689
|
+
}
|
|
690
|
+
};
|
|
691
|
+
function searchStatesEqual(left, right) {
|
|
692
|
+
return left.open === right.open && left.query === right.query && left.replaceText === right.replaceText && left.activeIndex === right.activeIndex && left.options.caseSensitive === right.options.caseSensitive && left.options.regex === right.options.regex && left.options.wholeWord === right.options.wholeWord && left.options.locale === right.options.locale && searchMatchesEqual(left.matches, right.matches);
|
|
693
|
+
}
|
|
694
|
+
function searchMatchesEqual(left, right) {
|
|
695
|
+
if (left.length !== right.length) {
|
|
696
|
+
return false;
|
|
697
|
+
}
|
|
698
|
+
for (let index = 0; index < left.length; index += 1) {
|
|
699
|
+
const leftMatch = left[index];
|
|
700
|
+
const rightMatch = right[index];
|
|
701
|
+
if (leftMatch?.kind !== rightMatch?.kind || leftMatch?.blockId !== rightMatch?.blockId || leftMatch?.row !== rightMatch?.row || leftMatch?.col !== rightMatch?.col || leftMatch?.cellText !== rightMatch?.cellText || leftMatch?.from !== rightMatch?.from || leftMatch?.to !== rightMatch?.to || leftMatch?.text !== rightMatch?.text || leftMatch?.index !== rightMatch?.index) {
|
|
702
|
+
return false;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
return true;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
// src/decorations.ts
|
|
709
|
+
function buildSearchDecorations(state) {
|
|
710
|
+
if (!state.open || state.matches.length === 0) {
|
|
711
|
+
return [];
|
|
712
|
+
}
|
|
713
|
+
return state.matches.flatMap((match, index) => {
|
|
714
|
+
if (match.kind !== "block") {
|
|
715
|
+
return [];
|
|
716
|
+
}
|
|
717
|
+
const isActive = index === state.activeIndex;
|
|
718
|
+
return [{
|
|
719
|
+
type: "inline",
|
|
720
|
+
blockId: match.blockId,
|
|
721
|
+
from: match.from,
|
|
722
|
+
to: match.to,
|
|
723
|
+
attributes: {
|
|
724
|
+
class: isActive ? "pen-search-match pen-search-match-active" : "pen-search-match",
|
|
725
|
+
"data-pen-search-match": "",
|
|
726
|
+
"data-search-match-index": String(match.index),
|
|
727
|
+
"data-search-match-active": String(isActive)
|
|
728
|
+
}
|
|
729
|
+
}];
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
// src/extension.ts
|
|
734
|
+
var SEARCH_EXTENSION_NAME = "search";
|
|
735
|
+
var SEARCH_KEY_BINDINGS = [
|
|
736
|
+
{
|
|
737
|
+
key: "Mod-f",
|
|
738
|
+
description: "Open search",
|
|
739
|
+
handler: (editor, event) => {
|
|
740
|
+
const controller = getSearchController(editor);
|
|
741
|
+
if (!controller) {
|
|
742
|
+
return false;
|
|
743
|
+
}
|
|
744
|
+
event.preventDefault();
|
|
745
|
+
controller.open();
|
|
746
|
+
return true;
|
|
747
|
+
}
|
|
748
|
+
},
|
|
749
|
+
{
|
|
750
|
+
key: "Mod-g",
|
|
751
|
+
description: "Next search match",
|
|
752
|
+
handler: (editor, event) => {
|
|
753
|
+
const controller = getSearchController(editor);
|
|
754
|
+
const state = controller?.getState();
|
|
755
|
+
if (!controller || !state?.open || state.query.length === 0) {
|
|
756
|
+
return false;
|
|
757
|
+
}
|
|
758
|
+
event.preventDefault();
|
|
759
|
+
controller.next();
|
|
760
|
+
return true;
|
|
761
|
+
}
|
|
762
|
+
},
|
|
763
|
+
{
|
|
764
|
+
key: "Shift-Mod-g",
|
|
765
|
+
description: "Previous search match",
|
|
766
|
+
handler: (editor, event) => {
|
|
767
|
+
const controller = getSearchController(editor);
|
|
768
|
+
const state = controller?.getState();
|
|
769
|
+
if (!controller || !state?.open || state.query.length === 0) {
|
|
770
|
+
return false;
|
|
771
|
+
}
|
|
772
|
+
event.preventDefault();
|
|
773
|
+
controller.previous();
|
|
774
|
+
return true;
|
|
775
|
+
}
|
|
776
|
+
},
|
|
777
|
+
{
|
|
778
|
+
key: "Enter",
|
|
779
|
+
description: "Next search match",
|
|
780
|
+
handler: (editor, event) => {
|
|
781
|
+
const controller = getSearchController(editor);
|
|
782
|
+
const state = controller?.getState();
|
|
783
|
+
if (!controller || !state?.open || state.query.length === 0) {
|
|
784
|
+
return false;
|
|
785
|
+
}
|
|
786
|
+
event.preventDefault();
|
|
787
|
+
controller.next();
|
|
788
|
+
return true;
|
|
789
|
+
}
|
|
790
|
+
},
|
|
791
|
+
{
|
|
792
|
+
key: "Shift-Enter",
|
|
793
|
+
description: "Previous search match",
|
|
794
|
+
handler: (editor, event) => {
|
|
795
|
+
const controller = getSearchController(editor);
|
|
796
|
+
const state = controller?.getState();
|
|
797
|
+
if (!controller || !state?.open || state.query.length === 0) {
|
|
798
|
+
return false;
|
|
799
|
+
}
|
|
800
|
+
event.preventDefault();
|
|
801
|
+
controller.previous();
|
|
802
|
+
return true;
|
|
803
|
+
}
|
|
804
|
+
},
|
|
805
|
+
{
|
|
806
|
+
key: "Escape",
|
|
807
|
+
description: "Close search",
|
|
808
|
+
handler: (editor, event) => {
|
|
809
|
+
const controller = getSearchController(editor);
|
|
810
|
+
const state = controller?.getState();
|
|
811
|
+
if (!controller || !state?.open) {
|
|
812
|
+
return false;
|
|
813
|
+
}
|
|
814
|
+
event.preventDefault();
|
|
815
|
+
controller.close();
|
|
816
|
+
return true;
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
];
|
|
820
|
+
function searchExtension() {
|
|
821
|
+
let activeEditor = null;
|
|
822
|
+
let controller = null;
|
|
823
|
+
let unsubscribeCommit = null;
|
|
824
|
+
let unsubscribeController = null;
|
|
825
|
+
return (0, import_pen_core3.defineExtension)({
|
|
826
|
+
name: SEARCH_EXTENSION_NAME,
|
|
827
|
+
facets: [
|
|
828
|
+
...searchKeymapProviders(SEARCH_KEY_BINDINGS),
|
|
829
|
+
import_pen_core3.decorationsFacet.of(() => {
|
|
830
|
+
const state = controller?.getState();
|
|
831
|
+
if (!state || state.matches.length === 0) {
|
|
832
|
+
return (0, import_pen_core3.createDecorationSet)([]);
|
|
833
|
+
}
|
|
834
|
+
return (0, import_pen_core3.createDecorationSet)(buildSearchDecorations(state));
|
|
835
|
+
})
|
|
836
|
+
],
|
|
837
|
+
activateClient: async ({ editor }) => {
|
|
838
|
+
activeEditor = editor;
|
|
839
|
+
controller = new SearchControllerImpl(editor);
|
|
840
|
+
editor.internals.assignSlot(import_pen_types.SEARCH_CONTROLLER_SLOT, controller);
|
|
841
|
+
unsubscribeCommit = editor.on("commit", () => {
|
|
842
|
+
controller?.recompute();
|
|
843
|
+
});
|
|
844
|
+
unsubscribeController = controller.subscribe(() => {
|
|
845
|
+
activeEditor?.requestDecorationUpdate();
|
|
846
|
+
});
|
|
847
|
+
},
|
|
848
|
+
deactivateClient: async () => {
|
|
849
|
+
unsubscribeCommit?.();
|
|
850
|
+
unsubscribeCommit = null;
|
|
851
|
+
unsubscribeController?.();
|
|
852
|
+
unsubscribeController = null;
|
|
853
|
+
activeEditor?.internals.assignSlot(import_pen_types.SEARCH_CONTROLLER_SLOT, null);
|
|
854
|
+
controller = null;
|
|
855
|
+
activeEditor = null;
|
|
856
|
+
}
|
|
857
|
+
});
|
|
858
|
+
}
|
|
859
|
+
function getSearchController(editor) {
|
|
860
|
+
return editor.facet(import_pen_core3.searchControllerFacet) ?? null;
|
|
861
|
+
}
|
|
862
|
+
function searchKeymapProviders(bindings) {
|
|
863
|
+
return bindings.map(
|
|
864
|
+
(binding) => import_pen_core3.keymapFacet.of(
|
|
865
|
+
[binding],
|
|
866
|
+
(0, import_pen_core3.keyBindingPriorityToPrecedence)(binding.priority ?? 300)
|
|
867
|
+
)
|
|
868
|
+
);
|
|
869
|
+
}
|
|
870
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
871
|
+
0 && (module.exports = {
|
|
872
|
+
DEFAULT_SEARCH_LOCALE,
|
|
873
|
+
DEFAULT_SEARCH_OPTIONS,
|
|
874
|
+
SEARCH_BUDGET_EXCEEDED_CODE,
|
|
875
|
+
SEARCH_EXECUTION_BUDGET_MS,
|
|
876
|
+
SEARCH_EXTENSION_NAME,
|
|
877
|
+
SEARCH_INVALID_PATTERN_CODE,
|
|
878
|
+
SEARCH_QUERY_MAX_LENGTH,
|
|
879
|
+
SEARCH_REGEX_SEGMENT_MAX_CODE_UNITS,
|
|
880
|
+
buildReplaceAllOps,
|
|
881
|
+
buildReplaceOps,
|
|
882
|
+
buildSearchRegex,
|
|
883
|
+
findDocumentMatches,
|
|
884
|
+
getSearchController,
|
|
885
|
+
searchExtension
|
|
886
|
+
});
|