@adecore/editor-core 0.39.0 → 0.40.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/block-folds.js +5 -2
- package/dist/bracket-tracker.js +4 -3
- package/dist/brackets.d.ts +2 -12
- package/dist/brackets.js +3 -15
- package/dist/closer-tracker.js +0 -1
- package/dist/doc-comments.js +0 -1
- package/dist/document.d.ts +2 -0
- package/dist/document.js +33 -35
- package/dist/edit-regions.js +3 -4
- package/dist/enter.d.ts +2 -0
- package/dist/enter.js +11 -9
- package/dist/fold-roles.js +2 -2
- package/dist/lexical-folds.d.ts +0 -1
- package/dist/lexical-folds.js +1 -4
- package/dist/lexical.d.ts +11 -5
- package/dist/lexical.js +13 -17
- package/dist/line-commands.js +7 -15
- package/dist/line-text.d.ts +2 -0
- package/dist/line-text.js +4 -0
- package/dist/markup-regions.js +2 -3
- package/dist/paste.js +2 -7
- package/dist/structure.js +2 -1
- package/dist/typing-context.js +5 -15
- package/package.json +2 -2
- package/src/block-folds.ts +39 -2
- package/src/bracket-tracker.ts +55 -12
- package/src/brackets.ts +35 -42
- package/src/closer-tracker.ts +6 -1
- package/src/comments.ts +103 -0
- package/src/doc-comments.ts +19 -1
- package/src/document.ts +505 -36
- package/src/edit-regions.ts +26 -4
- package/src/enter.ts +104 -9
- package/src/fold-roles.ts +34 -2
- package/src/index.ts +2 -0
- package/src/languages.ts +11 -0
- package/src/lexical-folds.ts +21 -5
- package/src/lexical.ts +31 -23
- package/src/line-commands.ts +81 -17
- package/src/line-text.ts +7 -0
- package/src/markup-regions.ts +101 -3
- package/src/offsets.ts +10 -0
- package/src/paste.ts +20 -7
- package/src/php-markup.ts +23 -0
- package/src/preserve-case.ts +12 -0
- package/src/rope.ts +70 -0
- package/src/script-source.ts +10 -0
- package/src/search.ts +38 -0
- package/src/structure.ts +29 -1
- package/src/text-span.ts +53 -0
- package/src/typing-context.ts +65 -41
- package/src/typing-handlers.ts +28 -0
- package/src/words.ts +10 -0
package/src/preserve-case.ts
CHANGED
|
@@ -11,28 +11,36 @@ export function replaceWithCaseRespect(replacement: string, found: string): stri
|
|
|
11
11
|
if (found === '' || replacement === '') {
|
|
12
12
|
return replacement;
|
|
13
13
|
}
|
|
14
|
+
|
|
14
15
|
const [firstFound] = found;
|
|
15
16
|
const [firstReplacement] = replacement;
|
|
16
17
|
const head = upper.test(firstFound!) ? firstReplacement!.toUpperCase() : firstReplacement!.toLowerCase();
|
|
17
18
|
const replacementTail = replacement.slice(firstReplacement!.length);
|
|
19
|
+
|
|
18
20
|
if (replacementTail === '') {
|
|
19
21
|
return head;
|
|
20
22
|
}
|
|
23
|
+
|
|
21
24
|
const foundTail = found.slice(firstFound!.length);
|
|
25
|
+
|
|
22
26
|
if (foundTail === '') {
|
|
23
27
|
return head + replacementTail;
|
|
24
28
|
}
|
|
29
|
+
|
|
25
30
|
const replacementCase = letterCase(replacementTail);
|
|
26
31
|
const foundCase = letterCase(foundTail);
|
|
27
32
|
// A tail without letters, like `x2`, follows the first letter of the match.
|
|
28
33
|
const tailUpper = foundCase.hasLetters ? foundCase.upper : upper.test(firstFound!);
|
|
29
34
|
const tailLower = foundCase.hasLetters ? foundCase.lower : lower.test(firstFound!);
|
|
35
|
+
|
|
30
36
|
if (tailUpper && (replacementCase.lower || !replacementCase.upper)) {
|
|
31
37
|
return head + replacementTail.toUpperCase();
|
|
32
38
|
}
|
|
39
|
+
|
|
33
40
|
if (tailLower && (replacementCase.lower || replacementCase.upper)) {
|
|
34
41
|
return head + replacementTail.toLowerCase();
|
|
35
42
|
}
|
|
43
|
+
|
|
36
44
|
return head + replacementTail;
|
|
37
45
|
}
|
|
38
46
|
|
|
@@ -41,16 +49,20 @@ function letterCase(text: string): { upper: boolean; lower: boolean; hasLetters:
|
|
|
41
49
|
let allUpper = true;
|
|
42
50
|
let allLower = true;
|
|
43
51
|
let hasLetters = false;
|
|
52
|
+
|
|
44
53
|
for (const character of text) {
|
|
45
54
|
if (!letter.test(character)) {
|
|
46
55
|
continue;
|
|
47
56
|
}
|
|
57
|
+
|
|
48
58
|
hasLetters = true;
|
|
49
59
|
allUpper &&= upper.test(character);
|
|
50
60
|
allLower &&= lower.test(character);
|
|
61
|
+
|
|
51
62
|
if (!allUpper && !allLower) {
|
|
52
63
|
break;
|
|
53
64
|
}
|
|
54
65
|
}
|
|
66
|
+
|
|
55
67
|
return { upper: allUpper, lower: allLower, hasLetters };
|
|
56
68
|
}
|
package/src/rope.ts
CHANGED
|
@@ -16,6 +16,7 @@ type Edges = { readonly startsWithLf: boolean; readonly endsWithCr: boolean };
|
|
|
16
16
|
type Leaf = Edges & { readonly text: string; readonly breaks: readonly number[]; readonly length: number; readonly lines: number; readonly height: number };
|
|
17
17
|
type Branch = Edges & { readonly left: Node; readonly right: Node; readonly length: number; readonly lines: number; readonly height: number };
|
|
18
18
|
type Node = Leaf | Branch;
|
|
19
|
+
|
|
19
20
|
export const ROPE_CHUNK_SIZE = 2048;
|
|
20
21
|
|
|
21
22
|
const CR = 13;
|
|
@@ -30,13 +31,17 @@ function leaf(text: string): Leaf | null {
|
|
|
30
31
|
if (!text.length) {
|
|
31
32
|
return null;
|
|
32
33
|
}
|
|
34
|
+
|
|
33
35
|
const breaks: number[] = [];
|
|
36
|
+
|
|
34
37
|
for (let index = 0; index < text.length; index++) {
|
|
35
38
|
const code = text.charCodeAt(index);
|
|
39
|
+
|
|
36
40
|
if (code === LF || (code === CR && text.charCodeAt(index + 1) !== LF)) {
|
|
37
41
|
breaks.push(index);
|
|
38
42
|
}
|
|
39
43
|
}
|
|
44
|
+
|
|
40
45
|
return {
|
|
41
46
|
text,
|
|
42
47
|
breaks,
|
|
@@ -65,16 +70,22 @@ function balance(left: Node, right: Node): Node {
|
|
|
65
70
|
if (left.left.height >= left.right.height) {
|
|
66
71
|
return branch(left.left, branch(left.right, right));
|
|
67
72
|
}
|
|
73
|
+
|
|
68
74
|
const middle = left.right as Branch;
|
|
75
|
+
|
|
69
76
|
return branch(branch(left.left, middle.left), branch(middle.right, right));
|
|
70
77
|
}
|
|
78
|
+
|
|
71
79
|
if (right.height > left.height + 1 && 'left' in right) {
|
|
72
80
|
if (right.right.height >= right.left.height) {
|
|
73
81
|
return branch(branch(left, right.left), right.right);
|
|
74
82
|
}
|
|
83
|
+
|
|
75
84
|
const middle = right.left as Branch;
|
|
85
|
+
|
|
76
86
|
return branch(branch(left, middle.left), branch(middle.right, right.right));
|
|
77
87
|
}
|
|
88
|
+
|
|
78
89
|
return branch(left, right);
|
|
79
90
|
}
|
|
80
91
|
|
|
@@ -82,18 +93,23 @@ function join(left: Node | null, right: Node | null): Node | null {
|
|
|
82
93
|
if (!left) {
|
|
83
94
|
return right;
|
|
84
95
|
}
|
|
96
|
+
|
|
85
97
|
if (!right) {
|
|
86
98
|
return left;
|
|
87
99
|
}
|
|
100
|
+
|
|
88
101
|
if ('text' in left && 'text' in right && left.length + right.length <= ROPE_CHUNK_SIZE) {
|
|
89
102
|
return leaf(left.text + right.text);
|
|
90
103
|
}
|
|
104
|
+
|
|
91
105
|
if (left.height > right.height + 1 && 'left' in left) {
|
|
92
106
|
return balance(left.left, join(left.right, right)!);
|
|
93
107
|
}
|
|
108
|
+
|
|
94
109
|
if (right.height > left.height + 1 && 'left' in right) {
|
|
95
110
|
return balance(join(left, right.left)!, right.right);
|
|
96
111
|
}
|
|
112
|
+
|
|
97
113
|
return branch(left, right);
|
|
98
114
|
}
|
|
99
115
|
|
|
@@ -101,35 +117,47 @@ function split(node: Node | null, offset: number): [Node | null, Node | null] {
|
|
|
101
117
|
if (!node || offset <= 0) {
|
|
102
118
|
return [null, node];
|
|
103
119
|
}
|
|
120
|
+
|
|
104
121
|
if (offset >= node.length) {
|
|
105
122
|
return [node, null];
|
|
106
123
|
}
|
|
124
|
+
|
|
107
125
|
if ('text' in node) {
|
|
108
126
|
return [leaf(node.text.slice(0, offset)), leaf(node.text.slice(offset))];
|
|
109
127
|
}
|
|
128
|
+
|
|
110
129
|
if (offset < node.left.length) {
|
|
111
130
|
const [before, after] = split(node.left, offset);
|
|
131
|
+
|
|
112
132
|
return [before, join(after, node.right)];
|
|
113
133
|
}
|
|
134
|
+
|
|
114
135
|
const [before, after] = split(node.right, offset - node.left.length);
|
|
136
|
+
|
|
115
137
|
return [join(node.left, before), after];
|
|
116
138
|
}
|
|
117
139
|
|
|
118
140
|
function build(text: string): Node | null {
|
|
119
141
|
const leaves: Node[] = [];
|
|
142
|
+
|
|
120
143
|
for (let offset = 0; offset < text.length; offset += ROPE_CHUNK_SIZE) {
|
|
121
144
|
leaves.push(leaf(text.slice(offset, offset + ROPE_CHUNK_SIZE))!);
|
|
122
145
|
}
|
|
146
|
+
|
|
123
147
|
const tree = (from: number, to: number): Node | null => {
|
|
124
148
|
if (from === to) {
|
|
125
149
|
return null;
|
|
126
150
|
}
|
|
151
|
+
|
|
127
152
|
if (to - from === 1) {
|
|
128
153
|
return leaves[from]!;
|
|
129
154
|
}
|
|
155
|
+
|
|
130
156
|
const middle = (from + to) >>> 1;
|
|
157
|
+
|
|
131
158
|
return branch(tree(from, middle)!, tree(middle, to)!);
|
|
132
159
|
};
|
|
160
|
+
|
|
133
161
|
return tree(0, leaves.length);
|
|
134
162
|
}
|
|
135
163
|
|
|
@@ -137,13 +165,17 @@ function collect(node: Node | null, from: number, to: number, chunks: string[]):
|
|
|
137
165
|
if (!node || from >= to) {
|
|
138
166
|
return;
|
|
139
167
|
}
|
|
168
|
+
|
|
140
169
|
if ('text' in node) {
|
|
141
170
|
chunks.push(node.text.slice(from, to));
|
|
171
|
+
|
|
142
172
|
return;
|
|
143
173
|
}
|
|
174
|
+
|
|
144
175
|
if (from < node.left.length) {
|
|
145
176
|
collect(node.left, from, Math.min(to, node.left.length), chunks);
|
|
146
177
|
}
|
|
178
|
+
|
|
147
179
|
if (to > node.left.length) {
|
|
148
180
|
collect(node.right, Math.max(0, from - node.left.length), to - node.left.length, chunks);
|
|
149
181
|
}
|
|
@@ -153,13 +185,17 @@ function* chunksOf(node: Node | null, from: number, to: number): Generator<strin
|
|
|
153
185
|
if (!node || from >= to) {
|
|
154
186
|
return;
|
|
155
187
|
}
|
|
188
|
+
|
|
156
189
|
if ('text' in node) {
|
|
157
190
|
yield node.text.slice(from, to);
|
|
191
|
+
|
|
158
192
|
return;
|
|
159
193
|
}
|
|
194
|
+
|
|
160
195
|
if (from < node.left.length) {
|
|
161
196
|
yield* chunksOf(node.left, from, Math.min(to, node.left.length));
|
|
162
197
|
}
|
|
198
|
+
|
|
163
199
|
if (to > node.left.length) {
|
|
164
200
|
yield* chunksOf(node.right, Math.max(0, from - node.left.length), to - node.left.length);
|
|
165
201
|
}
|
|
@@ -181,9 +217,11 @@ export class TextRope {
|
|
|
181
217
|
static from(text: string): TextRope {
|
|
182
218
|
return new TextRope(build(text));
|
|
183
219
|
}
|
|
220
|
+
|
|
184
221
|
get length(): number {
|
|
185
222
|
return this.root?.length ?? 0;
|
|
186
223
|
}
|
|
224
|
+
|
|
187
225
|
get lineCount(): number {
|
|
188
226
|
return (this.root?.lines ?? 0) + 1;
|
|
189
227
|
}
|
|
@@ -192,7 +230,9 @@ export class TextRope {
|
|
|
192
230
|
if (offset < 0 || offset >= this.length) {
|
|
193
231
|
return '';
|
|
194
232
|
}
|
|
233
|
+
|
|
195
234
|
let node = this.root!;
|
|
235
|
+
|
|
196
236
|
while ('left' in node) {
|
|
197
237
|
if (offset < node.left.length) {
|
|
198
238
|
node = node.left;
|
|
@@ -201,18 +241,21 @@ export class TextRope {
|
|
|
201
241
|
node = node.right;
|
|
202
242
|
}
|
|
203
243
|
}
|
|
244
|
+
|
|
204
245
|
return node.text.charAt(offset);
|
|
205
246
|
}
|
|
206
247
|
|
|
207
248
|
slice(from = 0, to = this.length): string {
|
|
208
249
|
const chunks: string[] = [];
|
|
209
250
|
collect(this.root, clampInteger(from, this.length), clampInteger(to, this.length), chunks);
|
|
251
|
+
|
|
210
252
|
return chunks.join('');
|
|
211
253
|
}
|
|
212
254
|
|
|
213
255
|
replace(from: number, to: number, text: string): TextRope {
|
|
214
256
|
const [before, rest] = split(this.root, from);
|
|
215
257
|
const [, after] = split(rest, to - from);
|
|
258
|
+
|
|
216
259
|
return new TextRope(join(join(before, build(text)), after));
|
|
217
260
|
}
|
|
218
261
|
|
|
@@ -223,26 +266,33 @@ export class TextRope {
|
|
|
223
266
|
let column = 0;
|
|
224
267
|
let offset = 0;
|
|
225
268
|
const pieces: string[] = [];
|
|
269
|
+
|
|
226
270
|
const advance = (end: number, keep: boolean): void => {
|
|
227
271
|
while (offset < end && !chunk.done) {
|
|
228
272
|
const count = Math.min(end - offset, chunk.value.length - column);
|
|
273
|
+
|
|
229
274
|
if (keep) {
|
|
230
275
|
pieces.push(chunk.value.slice(column, column + count));
|
|
231
276
|
}
|
|
277
|
+
|
|
232
278
|
offset += count;
|
|
233
279
|
column += count;
|
|
280
|
+
|
|
234
281
|
if (column === chunk.value.length) {
|
|
235
282
|
chunk = chunks.next();
|
|
236
283
|
column = 0;
|
|
237
284
|
}
|
|
238
285
|
}
|
|
239
286
|
};
|
|
287
|
+
|
|
240
288
|
for (const edit of edits) {
|
|
241
289
|
advance(edit.from, true);
|
|
242
290
|
pieces.push(edit.text);
|
|
243
291
|
advance(edit.to, false);
|
|
244
292
|
}
|
|
293
|
+
|
|
245
294
|
advance(this.length, true);
|
|
295
|
+
|
|
246
296
|
return TextRope.from(pieces.join(''));
|
|
247
297
|
}
|
|
248
298
|
|
|
@@ -251,28 +301,35 @@ export class TextRope {
|
|
|
251
301
|
if (this.root === other.root) {
|
|
252
302
|
return true;
|
|
253
303
|
}
|
|
304
|
+
|
|
254
305
|
const left = chunksOf(this.root, from, to);
|
|
255
306
|
const right = chunksOf(other.root, from, to);
|
|
256
307
|
let leftChunk = left.next();
|
|
257
308
|
let rightChunk = right.next();
|
|
258
309
|
let leftAt = 0;
|
|
259
310
|
let rightAt = 0;
|
|
311
|
+
|
|
260
312
|
while (!leftChunk.done && !rightChunk.done) {
|
|
261
313
|
const length = Math.min(leftChunk.value.length - leftAt, rightChunk.value.length - rightAt);
|
|
314
|
+
|
|
262
315
|
if (leftChunk.value.slice(leftAt, leftAt + length) !== rightChunk.value.slice(rightAt, rightAt + length)) {
|
|
263
316
|
return false;
|
|
264
317
|
}
|
|
318
|
+
|
|
265
319
|
leftAt += length;
|
|
266
320
|
rightAt += length;
|
|
321
|
+
|
|
267
322
|
if (leftAt === leftChunk.value.length) {
|
|
268
323
|
leftChunk = left.next();
|
|
269
324
|
leftAt = 0;
|
|
270
325
|
}
|
|
326
|
+
|
|
271
327
|
if (rightAt === rightChunk.value.length) {
|
|
272
328
|
rightChunk = right.next();
|
|
273
329
|
rightAt = 0;
|
|
274
330
|
}
|
|
275
331
|
}
|
|
332
|
+
|
|
276
333
|
return Boolean(leftChunk.done && rightChunk.done);
|
|
277
334
|
}
|
|
278
335
|
|
|
@@ -280,9 +337,11 @@ export class TextRope {
|
|
|
280
337
|
offset = clampInteger(offset, this.length);
|
|
281
338
|
let node = this.root;
|
|
282
339
|
let count = 0;
|
|
340
|
+
|
|
283
341
|
if (!node) {
|
|
284
342
|
return count;
|
|
285
343
|
}
|
|
344
|
+
|
|
286
345
|
while ('left' in node) {
|
|
287
346
|
if (offset < node.left.length) {
|
|
288
347
|
node = node.left;
|
|
@@ -292,40 +351,50 @@ export class TextRope {
|
|
|
292
351
|
node = node.right;
|
|
293
352
|
}
|
|
294
353
|
}
|
|
354
|
+
|
|
295
355
|
let low = 0;
|
|
296
356
|
let high = node.breaks.length;
|
|
357
|
+
|
|
297
358
|
while (low < high) {
|
|
298
359
|
const middle = (low + high) >>> 1;
|
|
360
|
+
|
|
299
361
|
if (node.breaks[middle]! < offset) {
|
|
300
362
|
low = middle + 1;
|
|
301
363
|
} else {
|
|
302
364
|
high = middle;
|
|
303
365
|
}
|
|
304
366
|
}
|
|
367
|
+
|
|
305
368
|
return count + low;
|
|
306
369
|
}
|
|
307
370
|
|
|
308
371
|
lineBounds(index: number): Omit<DocumentLine, 'text'> {
|
|
309
372
|
index = clampInteger(index, this.lineCount - 1);
|
|
310
373
|
const start = index === 0 ? 0 : this.breakAt(index - 1) + 1;
|
|
374
|
+
|
|
311
375
|
if (index === this.lineCount - 1) {
|
|
312
376
|
return { start, end: this.length, next: this.length };
|
|
313
377
|
}
|
|
378
|
+
|
|
314
379
|
const newline = this.breakAt(index);
|
|
315
380
|
const crlf = this.charAt(newline) === '\n' && this.charAt(newline - 1) === '\r';
|
|
381
|
+
|
|
316
382
|
return { start, end: newline - (crlf ? 1 : 0), next: newline + 1 };
|
|
317
383
|
}
|
|
318
384
|
|
|
319
385
|
getLine(index: number): DocumentLine {
|
|
320
386
|
const bounds = this.lineBounds(index);
|
|
387
|
+
|
|
321
388
|
return { ...bounds, text: this.slice(bounds.start, bounds.end) };
|
|
322
389
|
}
|
|
323
390
|
|
|
324
391
|
private breakAt(index: number): number {
|
|
325
392
|
let node = this.root!;
|
|
326
393
|
let offset = 0;
|
|
394
|
+
|
|
327
395
|
while ('left' in node) {
|
|
328
396
|
const leftLines = node.left.lines - sharedBreak(node.left, node.right);
|
|
397
|
+
|
|
329
398
|
if (index < leftLines) {
|
|
330
399
|
node = node.left;
|
|
331
400
|
} else {
|
|
@@ -334,6 +403,7 @@ export class TextRope {
|
|
|
334
403
|
node = node.right;
|
|
335
404
|
}
|
|
336
405
|
}
|
|
406
|
+
|
|
337
407
|
return offset + node.breaks[index]!;
|
|
338
408
|
}
|
|
339
409
|
}
|
package/src/script-source.ts
CHANGED
|
@@ -5,19 +5,26 @@ export function scriptSource(text: string, start: number, complete: boolean, con
|
|
|
5
5
|
const chunks: string[] = [];
|
|
6
6
|
let cursor = 0;
|
|
7
7
|
let island: number | undefined;
|
|
8
|
+
|
|
8
9
|
const finish = (end: number): string => {
|
|
9
10
|
chunks.push(text.slice(cursor, island ?? end));
|
|
11
|
+
|
|
10
12
|
if (island !== undefined) {
|
|
11
13
|
chunks.push(' '.repeat(end - island));
|
|
12
14
|
}
|
|
15
|
+
|
|
13
16
|
return chunks.join('');
|
|
14
17
|
};
|
|
18
|
+
|
|
15
19
|
for (const match of text.matchAll(events)) {
|
|
16
20
|
const state = context(start + match.index);
|
|
21
|
+
|
|
17
22
|
if (state === null) {
|
|
18
23
|
return undefined;
|
|
19
24
|
}
|
|
25
|
+
|
|
20
26
|
const inScript = state.embedded === 'javascript' && state.embedStart === start;
|
|
27
|
+
|
|
21
28
|
if (/^<\?(?:php\b|=)/.test(match[0])) {
|
|
22
29
|
if (inScript) {
|
|
23
30
|
island = match.index;
|
|
@@ -25,11 +32,13 @@ export function scriptSource(text: string, start: number, complete: boolean, con
|
|
|
25
32
|
} else if (match[0] === '?>') {
|
|
26
33
|
if (island !== undefined && state.embed?.embedStart === start && (state.mode === 'code' || state.mode === 'line-comment')) {
|
|
27
34
|
let end = match.index + 2;
|
|
35
|
+
|
|
28
36
|
if (text.startsWith('\r\n', end)) {
|
|
29
37
|
end += 2;
|
|
30
38
|
} else if (text[end] === '\n') {
|
|
31
39
|
end++;
|
|
32
40
|
}
|
|
41
|
+
|
|
33
42
|
// Spaces preserve document offsets without treating PHP source newlines as script text.
|
|
34
43
|
chunks.push(text.slice(cursor, island), ' '.repeat(end - island));
|
|
35
44
|
cursor = end;
|
|
@@ -39,5 +48,6 @@ export function scriptSource(text: string, start: number, complete: boolean, con
|
|
|
39
48
|
return finish(match.index);
|
|
40
49
|
}
|
|
41
50
|
}
|
|
51
|
+
|
|
42
52
|
return complete ? finish(text.length) : undefined;
|
|
43
53
|
}
|
package/src/search.ts
CHANGED
|
@@ -47,33 +47,44 @@ export function findMatches(text: string, query: string, revision: number, optio
|
|
|
47
47
|
/* Validation and regex execution start when the iterator advances. */
|
|
48
48
|
export function* iterateMatches(text: string, query: string, revision: number, options: FindOptions = {}): Generator<FindMatch, void, unknown> {
|
|
49
49
|
options = { ...options };
|
|
50
|
+
|
|
50
51
|
if (!query && !options.regex) {
|
|
51
52
|
return;
|
|
52
53
|
}
|
|
54
|
+
|
|
53
55
|
const from = options.from ?? 0;
|
|
54
56
|
const to = options.to ?? text.length;
|
|
55
57
|
const maximum = options.maxResults ?? Infinity;
|
|
58
|
+
|
|
56
59
|
if (!Number.isInteger(from) || !Number.isInteger(to) || from < 0 || to < from || to > text.length) {
|
|
57
60
|
throw new RangeError('Search bounds must be ordered offsets inside the document.');
|
|
58
61
|
}
|
|
62
|
+
|
|
59
63
|
if (maximum !== Infinity && (!Number.isInteger(maximum) || maximum < 0)) {
|
|
60
64
|
throw new RangeError('maxResults must be a nonnegative integer.');
|
|
61
65
|
}
|
|
66
|
+
|
|
62
67
|
const source = options.regex ? query : query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
63
68
|
const expression = new RegExp(source, `gmu${options.caseSensitive ? '' : 'i'}`);
|
|
64
69
|
expression.lastIndex = from;
|
|
65
70
|
let count = 0;
|
|
71
|
+
|
|
66
72
|
while (count < maximum) {
|
|
67
73
|
const match = expression.exec(text);
|
|
74
|
+
|
|
68
75
|
if (!match || match.index > to) {
|
|
69
76
|
break;
|
|
70
77
|
}
|
|
78
|
+
|
|
71
79
|
const end = match.index + match[0].length;
|
|
80
|
+
|
|
72
81
|
const wholeWord =
|
|
73
82
|
!options.wholeWord ||
|
|
74
83
|
(!wordCharacter.test(characterBefore(text, match.index)) && !wordCharacter.test(String.fromCodePoint(text.codePointAt(end) ?? 0)));
|
|
84
|
+
|
|
75
85
|
if (match.index >= from && end <= to && !splitsSurrogate(text, match.index) && !splitsSurrogate(text, end) && wholeWord) {
|
|
76
86
|
count++;
|
|
87
|
+
|
|
77
88
|
yield {
|
|
78
89
|
from: match.index,
|
|
79
90
|
to: end,
|
|
@@ -84,6 +95,7 @@ export function* iterateMatches(text: string, query: string, revision: number, o
|
|
|
84
95
|
revision
|
|
85
96
|
};
|
|
86
97
|
}
|
|
98
|
+
|
|
87
99
|
if (!match[0].length) {
|
|
88
100
|
expression.lastIndex = match.index + ((text.codePointAt(match.index) ?? 0) > 0xffff ? 2 : 1);
|
|
89
101
|
}
|
|
@@ -96,12 +108,15 @@ export function findMatchFrom(text: string, query: string, revision: number, off
|
|
|
96
108
|
let first: FindMatch | null = null;
|
|
97
109
|
let last: FindMatch | null = null;
|
|
98
110
|
let previous: FindMatch | null = null;
|
|
111
|
+
|
|
99
112
|
for (const match of iterateMatches(text, query, revision, options)) {
|
|
100
113
|
first ??= match;
|
|
101
114
|
last = match;
|
|
115
|
+
|
|
102
116
|
if (!options.backwards && match.from >= position) {
|
|
103
117
|
return match;
|
|
104
118
|
}
|
|
119
|
+
|
|
105
120
|
if (options.backwards) {
|
|
106
121
|
if (match.to <= position) {
|
|
107
122
|
previous = match;
|
|
@@ -110,6 +125,7 @@ export function findMatchFrom(text: string, query: string, revision: number, off
|
|
|
110
125
|
}
|
|
111
126
|
}
|
|
112
127
|
}
|
|
128
|
+
|
|
113
129
|
return options.backwards ? (previous ?? (options.wrap === false ? null : last)) : options.wrap === false ? null : first;
|
|
114
130
|
}
|
|
115
131
|
|
|
@@ -118,59 +134,81 @@ export function replacementText(text: string, match: FindMatch, replacement: str
|
|
|
118
134
|
if (maxCharacters !== Infinity && (!Number.isSafeInteger(maxCharacters) || maxCharacters < 0)) {
|
|
119
135
|
throw new RangeError('The replacement character limit must be nonnegative.');
|
|
120
136
|
}
|
|
137
|
+
|
|
121
138
|
const expanded = literal || !match.regex ? replacement : expandPatterns(text, match, replacement, maxCharacters);
|
|
139
|
+
|
|
122
140
|
if (expanded.length > maxCharacters) {
|
|
123
141
|
throw new RangeError('Replacement output exceeds its character limit.');
|
|
124
142
|
}
|
|
143
|
+
|
|
125
144
|
const result = preserveCase ? replaceWithCaseRespect(expanded, match.text) : expanded;
|
|
145
|
+
|
|
126
146
|
if (result.length > maxCharacters) {
|
|
127
147
|
throw new RangeError('Replacement output exceeds its character limit.');
|
|
128
148
|
}
|
|
149
|
+
|
|
129
150
|
return result;
|
|
130
151
|
}
|
|
131
152
|
|
|
132
153
|
function expandPatterns(text: string, match: FindMatch, replacement: string, maxCharacters: number): string {
|
|
133
154
|
const pieces: string[] = [];
|
|
155
|
+
|
|
134
156
|
let characters = 0,
|
|
135
157
|
cursor = 0;
|
|
158
|
+
|
|
136
159
|
const append = (value: string): void => {
|
|
137
160
|
characters += value.length;
|
|
161
|
+
|
|
138
162
|
if (characters > maxCharacters) {
|
|
139
163
|
throw new RangeError('Replacement output exceeds its character limit.');
|
|
140
164
|
}
|
|
165
|
+
|
|
141
166
|
pieces.push(value);
|
|
142
167
|
};
|
|
168
|
+
|
|
143
169
|
const expand = (token: string, group: string): string => {
|
|
144
170
|
if (group === '$') {
|
|
145
171
|
return '$';
|
|
146
172
|
}
|
|
173
|
+
|
|
147
174
|
if (group === '&') {
|
|
148
175
|
return match.text;
|
|
149
176
|
}
|
|
177
|
+
|
|
150
178
|
if (group === '`') {
|
|
151
179
|
return text.slice(0, match.from);
|
|
152
180
|
}
|
|
181
|
+
|
|
153
182
|
if (group === "'") {
|
|
154
183
|
return text.slice(match.to);
|
|
155
184
|
}
|
|
185
|
+
|
|
156
186
|
if (group.startsWith('<')) {
|
|
157
187
|
return match.groups ? (match.groups[group.slice(1, -1)] ?? '') : token;
|
|
158
188
|
}
|
|
189
|
+
|
|
159
190
|
const index = Number(group);
|
|
191
|
+
|
|
160
192
|
if (index > 0 && index <= match.captures.length) {
|
|
161
193
|
return match.captures[index - 1] ?? '';
|
|
162
194
|
}
|
|
195
|
+
|
|
163
196
|
const first = Number(group[0]);
|
|
197
|
+
|
|
164
198
|
if (group.length === 2 && first > 0 && first <= match.captures.length) {
|
|
165
199
|
return (match.captures[first - 1] ?? '') + group[1];
|
|
166
200
|
}
|
|
201
|
+
|
|
167
202
|
return token;
|
|
168
203
|
};
|
|
204
|
+
|
|
169
205
|
for (const found of replacement.matchAll(/\$(\$|&|`|'|\d{1,2}|<[^>]*>)/g)) {
|
|
170
206
|
append(replacement.slice(cursor, found.index));
|
|
171
207
|
append(expand(found[0], found[1]!));
|
|
172
208
|
cursor = found.index + found[0].length;
|
|
173
209
|
}
|
|
210
|
+
|
|
174
211
|
append(replacement.slice(cursor));
|
|
212
|
+
|
|
175
213
|
return pieces.join('');
|
|
176
214
|
}
|