@adecore/editor 0.22.1 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/dist/attribution.d.ts +9 -8
  2. package/dist/attribution.js +44 -9
  3. package/dist/bidi.d.ts +44 -0
  4. package/dist/bidi.js +26 -0
  5. package/dist/column-selection.js +2 -1
  6. package/dist/controller.d.ts +6 -1
  7. package/dist/controller.js +93 -3
  8. package/dist/editor.css +81 -1
  9. package/dist/engine.js +184 -41
  10. package/dist/fake.d.ts +43 -5
  11. package/dist/fake.js +117 -13
  12. package/dist/find-search.d.ts +26 -0
  13. package/dist/find-search.js +130 -0
  14. package/dist/find.d.ts +50 -2
  15. package/dist/find.js +575 -25
  16. package/dist/geometry-stops.d.ts +6 -0
  17. package/dist/geometry-stops.js +47 -0
  18. package/dist/grammar-preparation.d.ts +29 -0
  19. package/dist/grammar-preparation.js +147 -0
  20. package/dist/index.d.ts +3 -1
  21. package/dist/index.js +2 -0
  22. package/dist/keymap-table.d.ts +10 -1
  23. package/dist/keymap-table.js +37 -1
  24. package/dist/keymap.d.ts +1 -0
  25. package/dist/keymap.js +4 -1
  26. package/dist/layout-changes.d.ts +6 -0
  27. package/dist/layout-changes.js +27 -0
  28. package/dist/layout.d.ts +45 -2
  29. package/dist/layout.js +202 -58
  30. package/dist/offsets.d.ts +1 -0
  31. package/dist/offsets.js +23 -0
  32. package/dist/outline.js +5 -4
  33. package/dist/paint.d.ts +11 -3
  34. package/dist/paint.js +180 -34
  35. package/dist/search-client.d.ts +27 -0
  36. package/dist/search-client.js +210 -0
  37. package/dist/search-protocol.d.ts +103 -0
  38. package/dist/search-protocol.js +3 -0
  39. package/dist/search-worker.d.ts +11 -0
  40. package/dist/search-worker.js +137 -0
  41. package/dist/semantic.d.ts +2 -1
  42. package/dist/semantic.js +6 -1
  43. package/dist/shiki-document.d.ts +8 -0
  44. package/dist/shiki-document.js +93 -0
  45. package/dist/shiki-worker.d.ts +65 -0
  46. package/dist/shiki-worker.js +265 -0
  47. package/dist/shiki.d.ts +1 -0
  48. package/dist/shiki.js +96 -33
  49. package/dist/testing.d.ts +1 -0
  50. package/dist/tokens.d.ts +2 -0
  51. package/dist/tokens.js +37 -7
  52. package/dist/touch.d.ts +27 -0
  53. package/dist/touch.js +100 -0
  54. package/dist/types.d.ts +84 -2
  55. package/dist/uniform-geometry.d.ts +7 -0
  56. package/dist/uniform-geometry.js +49 -0
  57. package/dist/view.d.ts +68 -17
  58. package/dist/view.js +424 -97
  59. package/dist/worker-client.d.ts +37 -0
  60. package/dist/worker-client.js +195 -0
  61. package/package.json +17 -2
  62. package/src/attribution.ts +50 -14
  63. package/src/bidi.ts +32 -0
  64. package/src/column-selection.ts +2 -1
  65. package/src/controller.ts +97 -3
  66. package/src/editor.css +81 -1
  67. package/src/engine.ts +216 -42
  68. package/src/fake.ts +152 -14
  69. package/src/find-search.ts +165 -0
  70. package/src/find.ts +622 -21
  71. package/src/geometry-stops.ts +51 -0
  72. package/src/grammar-preparation.ts +158 -0
  73. package/src/index.ts +10 -0
  74. package/src/keymap-table.ts +44 -2
  75. package/src/keymap.ts +5 -1
  76. package/src/layout-changes.ts +33 -0
  77. package/src/layout.ts +262 -63
  78. package/src/offsets.ts +24 -0
  79. package/src/outline.ts +5 -4
  80. package/src/paint.ts +202 -36
  81. package/src/search-client.ts +306 -0
  82. package/src/search-protocol.ts +100 -0
  83. package/src/search-worker.ts +171 -0
  84. package/src/semantic.ts +8 -2
  85. package/src/shiki-document.ts +143 -0
  86. package/src/shiki-worker.ts +344 -0
  87. package/src/shiki.ts +103 -33
  88. package/src/testing.ts +2 -0
  89. package/src/tokens.ts +36 -7
  90. package/src/touch.ts +134 -0
  91. package/src/types.ts +174 -8
  92. package/src/uniform-geometry.ts +57 -0
  93. package/src/view.ts +500 -102
  94. package/src/worker-client.ts +212 -0
package/dist/find.js CHANGED
@@ -1,6 +1,9 @@
1
1
  import { mapOffset } from './offsets.js';
2
- /* Past this a count means nothing to a reader, and marking every match would stall the editor. */
3
- export const FIND_LIMIT = 10000;
2
+ import { FindSearchQueue, scheduleFind } from './find-search.js';
3
+ import { EditorSearchError } from './search-client.js';
4
+ import { SEARCH_LIMIT, MAX_REPLACEMENT_PAGE, MAX_REPLACEMENT_CHARACTERS } from './search-protocol.js';
5
+ export const FIND_LIMIT = SEARCH_LIMIT;
6
+ const LIVE_REGEX_LIMIT = 8_000_000;
4
7
  /*
5
8
  * The find of the host's find bar: every match marked, one of them current. It searches the model on
6
9
  * its own and never touches the selection until the find ends.
@@ -8,14 +11,39 @@ export const FIND_LIMIT = 10000;
8
11
  export class FindController {
9
12
  query = null;
10
13
  marks = [];
14
+ cache;
11
15
  index = null;
16
+ outside = null;
17
+ seeking = false;
18
+ replacing = 0;
19
+ replaceEpoch = -1;
20
+ replaced;
21
+ replaceFailed;
12
22
  /* The text a search in the selection is held to, which follows the text through edits; null when nothing was selected. */
13
23
  bounds = null;
14
24
  /* Where the next refresh looks for the current match from, once: right after what a replace wrote. */
15
25
  pendingAnchor;
16
26
  model;
17
- constructor(model) {
27
+ options;
28
+ search;
29
+ regexCache;
30
+ snapshotMemo;
31
+ retained = null;
32
+ epoch = 0;
33
+ searching = false;
34
+ failed;
35
+ captureFailed;
36
+ pendingStep;
37
+ cancelSearch;
38
+ cancelMatch;
39
+ preview = false;
40
+ disposed = false;
41
+ revision = -1;
42
+ cursorRequests = 0;
43
+ constructor(model, options) {
18
44
  this.model = model;
45
+ this.options = options;
46
+ this.search = options === undefined ? undefined : new FindSearchQueue(options);
19
47
  }
20
48
  get activeQuery() {
21
49
  return this.query;
@@ -30,19 +58,92 @@ export class FindController {
30
58
  return this.index;
31
59
  }
32
60
  get state() {
33
- return { count: this.marks.length, current: this.index, ...(this.query?.inSelection === true && this.bounds === null ? { noSelection: true } : {}) };
61
+ return {
62
+ count: this.marks.length,
63
+ current: this.index,
64
+ ...(this.cache?.complete === false || (this.failed === undefined && this.regexCache?.complete === false) ? { limited: true } : {}),
65
+ ...(this.searching || this.seeking || (this.replacing > 0 && this.replaceEpoch === this.epoch) ? { searching: true } : {}),
66
+ ...(this.replaced === undefined ? {} : { replaced: this.replaced }),
67
+ ...(this.replaceFailed === undefined ? {} : { replaceFailed: this.replaceFailed }),
68
+ ...(this.failed === undefined ? {} : { failed: this.failed }),
69
+ ...(this.captureFailed === undefined ? {} : { captureFailed: this.captureFailed }),
70
+ ...(this.search !== undefined && this.query?.regex && this.model.getLength() > LIVE_REGEX_LIMIT && !this.freshRegex ? { large: true } : {}),
71
+ ...(this.query?.inSelection === true && this.bounds === null ? { noSelection: true } : {})
72
+ };
34
73
  }
35
74
  /* The range a search in the selection is held to. */
36
75
  get scope() {
37
76
  return this.query?.inSelection === true ? this.bounds : null;
38
77
  }
39
78
  get currentMark() {
40
- return this.index === null ? null : (this.marks[this.index] ?? null);
79
+ return this.outside ?? (this.index === null ? null : (this.marks[this.index] ?? null));
80
+ }
81
+ matchesBetween(from, to) {
82
+ let low = 0;
83
+ let high = this.marks.length;
84
+ while (low < high) {
85
+ const middle = (low + high) >>> 1;
86
+ if (this.marks[middle].to < from) {
87
+ low = middle + 1;
88
+ }
89
+ else {
90
+ high = middle;
91
+ }
92
+ }
93
+ const first = low;
94
+ high = this.marks.length;
95
+ while (low < high) {
96
+ const middle = (low + high) >>> 1;
97
+ if (this.marks[middle].from <= to) {
98
+ low = middle + 1;
99
+ }
100
+ else {
101
+ high = middle;
102
+ }
103
+ }
104
+ return this.marks.slice(first, low);
105
+ }
106
+ get currentMatch() {
107
+ if (this.search !== undefined && this.query?.regex) {
108
+ const mark = this.currentMark;
109
+ return this.freshRegex && this.retained?.from === mark?.from && this.retained?.to === mark?.to ? this.retained : null;
110
+ }
111
+ const matches = this.cachedMatches;
112
+ if (this.outside !== null && matches !== null) {
113
+ return {
114
+ ...this.outside,
115
+ text: this.model.slice(this.outside.from, this.outside.to),
116
+ captures: [],
117
+ regex: false,
118
+ revision: this.model.getRevision()
119
+ };
120
+ }
121
+ return this.index === null || matches === null ? null : (matches[this.index] ?? null);
122
+ }
123
+ get completeMatches() {
124
+ return this.cache?.complete === true ? this.cachedMatches : null;
125
+ }
126
+ get cachedMatches() {
127
+ return this.query !== null && this.cache?.revision === this.model.getRevision() && this.cache.key === this.cacheKey() ? this.cache.matches : null;
128
+ }
129
+ cacheKey() {
130
+ const query = this.query;
131
+ const scope = this.scope;
132
+ return JSON.stringify([
133
+ query?.text,
134
+ query?.caseSensitive === true,
135
+ query?.wholeWord === true,
136
+ query?.regex === true,
137
+ query?.inSelection === true,
138
+ scope?.from,
139
+ scope?.to
140
+ ]);
41
141
  }
42
142
  /* A new query, or null to end the find. */
43
143
  set(query) {
144
+ const oldKey = this.cacheKey();
44
145
  const wasInSelection = this.query?.inSelection === true;
45
- this.query = query === null || query.text === '' ? null : query;
146
+ this.query = query === null || query.text === '' ? null : { ...query };
46
147
  if (this.query?.inSelection !== true) {
47
148
  this.bounds = null;
48
149
  }
@@ -53,10 +154,20 @@ export class FindController {
53
154
  ? null
54
155
  : { from: Math.min(selection.anchor, selection.head), to: Math.max(selection.anchor, selection.head) };
55
156
  }
157
+ if (this.cacheKey() !== oldKey) {
158
+ this.pendingAnchor = undefined;
159
+ this.pendingStep = undefined;
160
+ this.outside = null;
161
+ this.replaceFailed = undefined;
162
+ this.replaced = undefined;
163
+ }
56
164
  this.refresh();
57
165
  }
58
166
  /* The text of a search in the selection moved with an edit. */
59
167
  mapBounds(changes) {
168
+ if (this.pendingAnchor !== undefined) {
169
+ this.pendingAnchor = mapOffset(this.pendingAnchor, changes);
170
+ }
60
171
  if (this.bounds !== null) {
61
172
  this.bounds = { from: mapOffset(this.bounds.from, changes), to: mapOffset(this.bounds.to, changes) };
62
173
  }
@@ -66,43 +177,482 @@ export class FindController {
66
177
  * previous one was, or from the cursor, so each letter typed keeps the place instead of starting over.
67
178
  */
68
179
  refresh() {
180
+ if (this.revision !== this.model.getRevision()) {
181
+ this.revision = this.model.getRevision();
182
+ this.replaced = undefined;
183
+ }
69
184
  const previous = this.currentMark;
70
185
  const query = this.query;
186
+ if (this.search !== undefined && query?.regex) {
187
+ this.refreshRegex(previous);
188
+ return;
189
+ }
190
+ this.resetRegex(query === null || this.search?.busy === true);
71
191
  if (query === null) {
72
192
  this.marks = [];
193
+ this.cache = undefined;
73
194
  this.index = null;
74
195
  return;
75
196
  }
76
- let found = [];
77
- try {
78
- const inSelection = query.inSelection === true;
79
- if (!inSelection || this.bounds !== null) {
80
- found = this.model
81
- .find(query.text, {
82
- caseSensitive: query.caseSensitive,
83
- wholeWord: query.wholeWord,
84
- regex: query.regex,
85
- maxResults: FIND_LIMIT,
86
- ...(inSelection ? this.bounds : {})
87
- })
88
- .map((match) => ({ from: match.from, to: match.to }));
89
- }
90
- }
91
- catch {
92
- // A pattern that does not parse finds nothing; the find bar says it is invalid.
197
+ const revision = this.model.getRevision();
198
+ const key = this.cacheKey();
199
+ if (this.cache?.revision !== revision || this.cache.key !== key) {
200
+ let found = [];
201
+ try {
202
+ const inSelection = query.inSelection === true;
203
+ if (!inSelection || this.bounds !== null) {
204
+ found = this.model.find(query.text, {
205
+ caseSensitive: query.caseSensitive,
206
+ wholeWord: query.wholeWord,
207
+ regex: query.regex,
208
+ maxResults: FIND_LIMIT + 1,
209
+ ...(inSelection ? this.bounds : {})
210
+ });
211
+ }
212
+ }
213
+ catch {
214
+ // A pattern that does not parse finds nothing; the find bar says it is invalid.
215
+ }
216
+ this.cache = { revision, key, matches: found.slice(0, FIND_LIMIT), complete: found.length <= FIND_LIMIT };
93
217
  }
218
+ const found = this.cache.matches;
94
219
  this.marks = found;
95
220
  const selection = this.model.getPrimary();
96
221
  const anchor = this.pendingAnchor ?? previous?.from ?? Math.min(selection.anchor, selection.head);
97
222
  this.pendingAnchor = undefined;
98
- const next = found.findIndex((mark) => mark.from >= anchor);
99
- this.index = found.length === 0 ? null : Math.max(0, next);
223
+ this.chooseCurrent(anchor, 1, !this.cache.complete);
100
224
  }
101
225
  step(direction) {
226
+ this.replaced = undefined;
227
+ if (this.seeking) {
228
+ this.pendingStep = direction;
229
+ return;
230
+ }
231
+ if (this.search !== undefined && this.query?.regex && !this.freshRegex) {
232
+ this.pendingStep = direction;
233
+ if (!this.searching) {
234
+ this.startRegex(true);
235
+ }
236
+ return;
237
+ }
102
238
  const count = this.marks.length;
103
239
  if (count === 0) {
104
240
  return;
105
241
  }
242
+ const incomplete = this.regexCache?.complete === false || this.cache?.complete === false;
243
+ const current = this.currentMark;
244
+ if (this.search !== undefined &&
245
+ incomplete &&
246
+ current !== null &&
247
+ (this.outside !== null || (direction === 1 ? this.index === count - 1 : this.index === 0))) {
248
+ let offset = direction === -1 ? current.from : current.to;
249
+ if (direction === 1 && current.from === current.to) {
250
+ offset += (this.model.getText().codePointAt(offset) ?? 0) > 0xffff ? 2 : 1;
251
+ }
252
+ if (offset > this.model.getLength()) {
253
+ offset = 0;
254
+ }
255
+ this.seek(offset, direction);
256
+ return;
257
+ }
258
+ this.outside = null;
106
259
  this.index = this.index === null ? (direction === 1 ? 0 : count - 1) : (this.index + direction + count) % count;
260
+ this.captureFailed = undefined;
261
+ this.preparePreview();
262
+ }
263
+ get freshRegex() {
264
+ return (this.failed === undefined && !this.searching && this.regexCache?.revision === this.model.getRevision() && this.regexCache.key === this.cacheKey());
265
+ }
266
+ resetRegex(release) {
267
+ this.epoch++;
268
+ this.cancelSearch?.();
269
+ this.cancelSearch = undefined;
270
+ this.cancelMatch?.();
271
+ this.cancelMatch = undefined;
272
+ this.regexCache = undefined;
273
+ this.outside = null;
274
+ this.seeking = false;
275
+ this.snapshotMemo = undefined;
276
+ this.retained = null;
277
+ this.searching = false;
278
+ this.failed = undefined;
279
+ this.captureFailed = undefined;
280
+ if (release) {
281
+ this.search?.release();
282
+ }
283
+ }
284
+ refreshRegex(previous) {
285
+ if (this.freshRegex) {
286
+ return;
287
+ }
288
+ const failure = this.failed;
289
+ const retryBlocked = failure === 'timeout' || failure === 'unavailable';
290
+ const sameQuery = this.regexCache?.key === this.cacheKey();
291
+ this.resetRegex(false);
292
+ this.cache = undefined;
293
+ this.marks = [];
294
+ this.index = null;
295
+ if (retryBlocked && sameQuery) {
296
+ this.failed = failure;
297
+ this.regexCache = { revision: this.model.getRevision(), key: this.cacheKey(), complete: false };
298
+ return;
299
+ }
300
+ this.pendingAnchor ??= previous?.from;
301
+ this.startRegex(false);
302
+ }
303
+ startRegex(explicit) {
304
+ const query = this.query;
305
+ if (query === null || this.search === undefined || this.disposed) {
306
+ return;
307
+ }
308
+ if (query.inSelection && this.bounds === null) {
309
+ return;
310
+ }
311
+ if (!explicit && this.model.getLength() > LIVE_REGEX_LIMIT) {
312
+ this.pendingStep = undefined;
313
+ this.pendingAnchor = undefined;
314
+ return;
315
+ }
316
+ const epoch = this.epoch;
317
+ const key = this.cacheKey();
318
+ const revision = this.model.getRevision();
319
+ this.failed = undefined;
320
+ this.searching = true;
321
+ this.cancelSearch?.();
322
+ this.cancelSearch = (this.options?.schedule ?? scheduleFind)(() => {
323
+ this.cancelSearch = undefined;
324
+ const snapshot = this.snapshot();
325
+ void this.search.request((client) => {
326
+ if (epoch !== this.epoch || this.disposed) {
327
+ throw new EditorSearchError('superseded', 'The find query changed.');
328
+ }
329
+ return client.search(snapshot, query, { ...this.scope, maxResults: FIND_LIMIT, ...(explicit ? { deadline: 10000 } : {}) });
330
+ }, explicit ? 'command' : 'live', () => epoch === this.epoch && revision === this.model.getRevision() && !this.disposed)
331
+ .then((result) => {
332
+ if (epoch !== this.epoch || revision !== this.model.getRevision() || this.disposed) {
333
+ return;
334
+ }
335
+ this.regexCache = { revision, key, complete: !result.limited };
336
+ const marks = [];
337
+ for (let index = 0; index < result.ranges.length; index += 2) {
338
+ marks.push({ from: result.ranges[index], to: result.ranges[index + 1] });
339
+ }
340
+ this.marks = marks;
341
+ const selection = this.model.getPrimary();
342
+ const anchor = this.pendingAnchor ?? Math.min(selection.anchor, selection.head);
343
+ this.pendingAnchor = undefined;
344
+ const direction = this.pendingStep;
345
+ this.pendingStep = undefined;
346
+ this.searching = false;
347
+ this.chooseCurrent(anchor, direction ?? 1, result.limited);
348
+ this.options?.changed(true);
349
+ this.preparePreview();
350
+ })
351
+ .catch((error) => {
352
+ if (epoch !== this.epoch || this.disposed) {
353
+ return;
354
+ }
355
+ this.searching = false;
356
+ if (error instanceof EditorSearchError && error.kind === 'superseded') {
357
+ this.options?.changed();
358
+ this.startRegex(this.pendingStep !== undefined);
359
+ return;
360
+ }
361
+ this.pendingAnchor = undefined;
362
+ this.pendingStep = undefined;
363
+ this.failed =
364
+ error instanceof EditorSearchError
365
+ ? error.kind === 'syntax'
366
+ ? 'invalid'
367
+ : error.kind === 'timeout' || error.kind === 'unavailable'
368
+ ? error.kind
369
+ : 'error'
370
+ : 'error';
371
+ this.regexCache = { revision, key, complete: false };
372
+ this.options?.changed();
373
+ });
374
+ }, explicit || this.model.getLength() <= 1_000_000 ? 0 : 150);
375
+ }
376
+ snapshot() {
377
+ const revision = this.model.getRevision();
378
+ if (this.snapshotMemo?.revision !== revision) {
379
+ this.snapshotMemo = { revision, text: this.model.getText() };
380
+ }
381
+ return this.snapshotMemo;
107
382
  }
383
+ setPreview(active) {
384
+ this.preview = active;
385
+ this.preparePreview();
386
+ }
387
+ preparePreview() {
388
+ this.cancelMatch?.();
389
+ this.cancelMatch = undefined;
390
+ if (this.preview &&
391
+ this.search !== undefined &&
392
+ this.query?.regex &&
393
+ this.freshRegex &&
394
+ this.currentMatch === null &&
395
+ this.captureFailed === undefined) {
396
+ this.cancelMatch = (this.options?.schedule ?? scheduleFind)(() => {
397
+ this.cancelMatch = undefined;
398
+ void this.ensureMatch().catch(() => { });
399
+ }, 50);
400
+ }
401
+ }
402
+ async ensureMatch(command = false) {
403
+ if (this.currentMatch !== null || this.search === undefined || !this.query?.regex || !this.freshRegex) {
404
+ return this.currentMatch;
405
+ }
406
+ const mark = this.currentMark;
407
+ if (mark === null) {
408
+ return null;
409
+ }
410
+ const query = this.query;
411
+ const epoch = this.epoch;
412
+ const snapshot = this.snapshot();
413
+ if (command) {
414
+ this.captureFailed = undefined;
415
+ }
416
+ const current = () => !this.disposed &&
417
+ epoch === this.epoch &&
418
+ this.model.getRevision() === snapshot.revision &&
419
+ this.currentMark?.from === mark.from &&
420
+ this.currentMark?.to === mark.to;
421
+ try {
422
+ const found = await this.search.request((client) => {
423
+ if (!current()) {
424
+ throw new EditorSearchError('superseded', 'The current find match changed.');
425
+ }
426
+ return client.match(snapshot, query, { ...mark, deadline: 10000 });
427
+ }, command ? 'command' : 'preview');
428
+ if (!current()) {
429
+ return null;
430
+ }
431
+ this.retained = found;
432
+ this.captureFailed = undefined;
433
+ this.options?.changed();
434
+ return found;
435
+ }
436
+ catch (error) {
437
+ if (current() && !(error instanceof EditorSearchError && error.kind === 'superseded')) {
438
+ this.captureFailed = error instanceof EditorSearchError && (error.kind === 'timeout' || error.kind === 'unavailable') ? error.kind : 'error';
439
+ this.options?.changed();
440
+ }
441
+ return null;
442
+ }
443
+ }
444
+ chooseCurrent(anchor, direction, limited) {
445
+ if (this.marks.length === 0) {
446
+ this.index = null;
447
+ return;
448
+ }
449
+ const next = direction === 1 ? this.marks.findIndex((mark) => mark.from >= anchor) : this.marks.findLastIndex((mark) => mark.from < anchor);
450
+ if (limited && this.search !== undefined && (next === -1 || (direction === -1 && next === this.marks.length - 1))) {
451
+ this.index = null;
452
+ this.seek(anchor, direction);
453
+ }
454
+ else {
455
+ this.index = next === -1 ? (direction === 1 ? 0 : this.marks.length - 1) : next;
456
+ }
457
+ }
458
+ seek(offset, direction) {
459
+ const query = this.query;
460
+ if (this.search === undefined || query === null) {
461
+ return;
462
+ }
463
+ const epoch = this.epoch;
464
+ const snapshot = this.snapshot();
465
+ const scope = this.scope;
466
+ const valid = () => !this.disposed && epoch === this.epoch && snapshot.revision === this.model.getRevision();
467
+ this.seeking = true;
468
+ this.failed = undefined;
469
+ this.retained = null;
470
+ this.captureFailed = undefined;
471
+ void this.search
472
+ .request((client) => client.next(snapshot, query, offset, { ...scope, backwards: direction === -1, wrap: true, deadline: 10000 }), 'command', valid)
473
+ .then((range) => {
474
+ if (!valid()) {
475
+ return;
476
+ }
477
+ this.seeking = false;
478
+ this.outside = null;
479
+ this.index = range === null ? null : this.marks.findIndex((mark) => mark.from === range.from && mark.to === range.to);
480
+ if (this.index === -1) {
481
+ this.index = null;
482
+ this.outside = range;
483
+ }
484
+ this.options?.changed(true);
485
+ this.preparePreview();
486
+ const pending = this.pendingStep;
487
+ this.pendingStep = undefined;
488
+ if (pending !== undefined) {
489
+ this.step(pending);
490
+ }
491
+ })
492
+ .catch((error) => {
493
+ if (valid()) {
494
+ this.seeking = false;
495
+ this.failed = searchFailure(error);
496
+ this.options?.changed();
497
+ }
498
+ });
499
+ }
500
+ async nextFromCursor(query, offset, direction) {
501
+ if (this.search === undefined) {
502
+ return null;
503
+ }
504
+ const snapshot = this.snapshot();
505
+ const epoch = this.epoch;
506
+ const valid = () => !this.disposed && epoch === this.epoch && snapshot.revision === this.model.getRevision();
507
+ this.cursorRequests++;
508
+ try {
509
+ const range = await this.search.request((client) => client.next(snapshot, query, offset, { backwards: direction === -1, wrap: true, deadline: 10000 }), 'command', valid);
510
+ return valid() ? range : null;
511
+ }
512
+ catch (error) {
513
+ if (valid()) {
514
+ this.failed = searchFailure(error);
515
+ this.options?.changed();
516
+ }
517
+ return null;
518
+ }
519
+ finally {
520
+ this.cursorRequests--;
521
+ if (this.query === null && this.cursorRequests === 0) {
522
+ this.search.release();
523
+ }
524
+ }
525
+ }
526
+ reportReplaceFailed(failure) {
527
+ this.replaceFailed = failure;
528
+ this.replaced = undefined;
529
+ this.options?.changed();
530
+ }
531
+ reportReplaced(count) {
532
+ this.replaced = count;
533
+ this.replaceFailed = undefined;
534
+ this.options?.changed();
535
+ }
536
+ async replaceOne(replacement, preserveCase, canApply) {
537
+ const query = this.query, mark = this.currentMark;
538
+ if (query === null || mark === null || this.search === undefined || !canApply()) {
539
+ return false;
540
+ }
541
+ const snapshot = this.snapshot();
542
+ const epoch = this.epoch;
543
+ const valid = () => !this.disposed &&
544
+ epoch === this.epoch &&
545
+ snapshot.revision === this.model.getRevision() &&
546
+ canApply() &&
547
+ this.currentMark?.from === mark.from &&
548
+ this.currentMark?.to === mark.to;
549
+ this.replacing++;
550
+ this.replaceEpoch = epoch;
551
+ this.replaceFailed = undefined;
552
+ this.options?.changed();
553
+ try {
554
+ const page = await this.search.request((client) => client.replacements(snapshot, query, replacement, {
555
+ from: mark.from,
556
+ to: mark.to,
557
+ preserveCase,
558
+ maxEdits: 1,
559
+ maxCharacters: MAX_REPLACEMENT_CHARACTERS,
560
+ deadline: 10000
561
+ }), 'command', valid);
562
+ if (!valid() || page.ranges.length !== 3 || page.ranges[0] !== mark.from || page.ranges[1] !== mark.to || page.next !== null) {
563
+ return false;
564
+ }
565
+ this.pendingAnchor = mark.to;
566
+ const changed = this.model.applyEdits([{ from: mark.from, to: mark.to, text: page.text }], {
567
+ source: 'command',
568
+ expectedRevision: snapshot.revision
569
+ });
570
+ if (!changed) {
571
+ this.pendingAnchor = undefined;
572
+ }
573
+ return changed;
574
+ }
575
+ catch (error) {
576
+ if (valid()) {
577
+ const failure = searchFailure(error);
578
+ this.reportReplaceFailed(error instanceof EditorSearchError && error.kind === 'range' ? 'limit' : failure === 'invalid' ? 'error' : failure);
579
+ }
580
+ return false;
581
+ }
582
+ finally {
583
+ this.replacing--;
584
+ if (!this.disposed) {
585
+ this.options?.changed();
586
+ }
587
+ }
588
+ }
589
+ async replaceAll(replacement, preserveCase, canApply) {
590
+ const query = this.query;
591
+ if (query === null || this.search === undefined || this.state.noSelection || !canApply()) {
592
+ return 0;
593
+ }
594
+ const snapshot = this.snapshot();
595
+ const epoch = this.epoch;
596
+ const scope = this.scope;
597
+ const valid = () => !this.disposed && epoch === this.epoch && snapshot.revision === this.model.getRevision() && canApply();
598
+ this.replacing++;
599
+ this.replaceEpoch = epoch;
600
+ this.replaceFailed = undefined;
601
+ this.replaced = undefined;
602
+ this.options?.changed();
603
+ try {
604
+ const page = await this.search.request((client) => client.replacements(snapshot, query, replacement, {
605
+ ...scope,
606
+ preserveCase,
607
+ maxEdits: MAX_REPLACEMENT_PAGE,
608
+ maxCharacters: MAX_REPLACEMENT_CHARACTERS,
609
+ deadline: 10000
610
+ }), 'command', valid);
611
+ if (!valid()) {
612
+ return 0;
613
+ }
614
+ if (page.next !== null) {
615
+ this.replaceFailed = 'limit';
616
+ return 0;
617
+ }
618
+ const edits = [];
619
+ let position = 0;
620
+ for (let index = 0; index < page.ranges.length; index += 3) {
621
+ const length = page.ranges[index + 2];
622
+ edits.push({ from: page.ranges[index], to: page.ranges[index + 1], text: page.text.slice(position, position + length) });
623
+ position += length;
624
+ }
625
+ const changed = this.model.applyEdits(edits, { source: 'command', expectedRevision: snapshot.revision });
626
+ const count = changed ? edits.length : 0;
627
+ this.reportReplaced(count);
628
+ return count;
629
+ }
630
+ catch (error) {
631
+ if (!this.disposed && epoch === this.epoch) {
632
+ const failure = searchFailure(error);
633
+ this.replaceFailed = error instanceof EditorSearchError && error.kind === 'range' ? 'limit' : failure === 'invalid' ? 'error' : failure;
634
+ }
635
+ return 0;
636
+ }
637
+ finally {
638
+ this.replacing--;
639
+ if (!this.disposed) {
640
+ this.options?.changed();
641
+ }
642
+ }
643
+ }
644
+ dispose() {
645
+ this.disposed = true;
646
+ this.resetRegex(false);
647
+ this.search?.dispose();
648
+ }
649
+ }
650
+ function searchFailure(error) {
651
+ return error instanceof EditorSearchError
652
+ ? error.kind === 'syntax'
653
+ ? 'invalid'
654
+ : error.kind === 'timeout' || error.kind === 'unavailable'
655
+ ? error.kind
656
+ : 'error'
657
+ : 'error';
108
658
  }
@@ -0,0 +1,6 @@
1
+ import type { Affinity, LineGeometry } from './layout.ts';
2
+ export declare function stopCount(geometry: LineGeometry): number;
3
+ export declare function offsetOfStop(geometry: LineGeometry, stop: number): number;
4
+ export declare function rowOfStop(geometry: LineGeometry, stop: number): number;
5
+ export declare function xOfStop(geometry: LineGeometry, stop: number, affinity?: Affinity): number;
6
+ export declare function stopAtOffset(geometry: LineGeometry, offset: number): number;