@adecore/editor 0.22.1 → 0.23.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.
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 +189 -41
  10. package/dist/fake.d.ts +45 -5
  11. package/dist/fake.js +121 -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 +86 -2
  55. package/dist/uniform-geometry.d.ts +7 -0
  56. package/dist/uniform-geometry.js +49 -0
  57. package/dist/view.d.ts +69 -17
  58. package/dist/view.js +427 -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 +222 -42
  68. package/src/fake.ts +157 -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 +178 -8
  92. package/src/uniform-geometry.ts +57 -0
  93. package/src/view.ts +505 -102
  94. package/src/worker-client.ts +212 -0
package/src/fake.ts CHANGED
@@ -5,7 +5,9 @@ import type {
5
5
  EditorBlock,
6
6
  EditorCodeBlockOptions,
7
7
  EditorLineAction,
8
+ EditorReplacement,
8
9
  EditorLineHighlight,
10
+ LineToken,
9
11
  EditorChangeMark,
10
12
  EditorCodeVision,
11
13
  EditorClick,
@@ -22,9 +24,14 @@ import type {
22
24
  EditorHover,
23
25
  EditorIndentation,
24
26
  EditorGhostText,
27
+ EditorInlayClick,
28
+ EditorInlayClickHandler,
25
29
  EditorInlayHint,
30
+ EditorInlayTarget,
26
31
  EditorKeyHandler,
32
+ EditorInputPopup,
27
33
  EditorMarker,
34
+ EditorMessages,
28
35
  EditorOptions,
29
36
  EditorPosition,
30
37
  EditorRemoteCursor,
@@ -38,6 +45,7 @@ import type {
38
45
  EditorFolds,
39
46
  EditorSemanticToken,
40
47
  EditorSmartKeys,
48
+ EditorApplyEditsOptions,
41
49
  EditorTextChange,
42
50
  EditorTrackedRange,
43
51
  EditorTheme,
@@ -59,7 +67,7 @@ export class FakeEditor implements Editor {
59
67
  readonly language: string | undefined;
60
68
  readonly path: string | undefined;
61
69
  readonly column: number | null;
62
- readonly scrollTop: number | null;
70
+ scrollTop: number | null;
63
71
  wrap: boolean;
64
72
  theme: EditorTheme;
65
73
  readOnly: boolean;
@@ -130,7 +138,7 @@ export class FakeEditor implements Editor {
130
138
  }
131
139
 
132
140
  /* The text becomes `text`, and listeners of the text hear the one stretch that differs, as the real editor reports it. */
133
- private replaceText(text: string, source: EditorTextChange['source'] = 'external'): void {
141
+ private replaceText(text: string, source: EditorTextChange['source'] = 'external', selections?: readonly EditorRange[]): void {
134
142
  const span = changedSpan(this.text, text);
135
143
  const before = this.text;
136
144
  this.text = text;
@@ -139,7 +147,11 @@ export class FakeEditor implements Editor {
139
147
  }
140
148
  // As in the real editor the caret lands behind what was typed, and listeners of the caret hear it before those of the text.
141
149
  if (source !== 'external') {
142
- this.moveCaret(positionIn(text, span.start + span.text.length));
150
+ if (selections === undefined) {
151
+ this.moveCaret(positionIn(text, span.start + span.text.length));
152
+ } else {
153
+ this.setSelections(selections);
154
+ }
143
155
  }
144
156
  this.followTracked(span.start, span.end, span.text.length);
145
157
  this.ghost = null;
@@ -250,6 +262,10 @@ export class FakeEditor implements Editor {
250
262
  return this.readOnly ? 0 : this.findState.count;
251
263
  }
252
264
 
265
+ async replaceAllAsync(replacement: string, options: EditorReplaceOptions = {}): Promise<number> {
266
+ return this.replaceAll(replacement, options);
267
+ }
268
+
253
269
  setReplacePreview(replacement: string | null): void {
254
270
  this.replacePreview = replacement;
255
271
  }
@@ -288,11 +304,19 @@ export class FakeEditor implements Editor {
288
304
  blocks: readonly EditorBlock[] | null = null;
289
305
  private readonly scopes = new Set<(scope: readonly EditorBlock[]) => void>();
290
306
 
291
- /* What the host marked as changed last. */
292
- changeMarks: readonly EditorChangeMark[] = [];
307
+ /* The marks of every layer, in the order the layers last became non-empty. */
308
+ get changeMarks(): readonly EditorChangeMark[] {
309
+ return [...this.changeLayers.values()].flat();
310
+ }
311
+
312
+ private readonly changeLayers = new Map<string, readonly EditorChangeMark[]>();
293
313
 
294
- setChangeMarks(marks: readonly EditorChangeMark[]): void {
295
- this.changeMarks = marks;
314
+ setChangeMarks(marks: readonly EditorChangeMark[], layer = ''): void {
315
+ if (marks.length === 0) {
316
+ this.changeLayers.delete(layer);
317
+ } else {
318
+ this.changeLayers.set(layer, marks);
319
+ }
296
320
  }
297
321
 
298
322
  /* The tinted lines the host set last. */
@@ -305,6 +329,43 @@ export class FakeEditor implements Editor {
305
329
  /* What the host drew as code outside the document: the text and how, and into which element. */
306
330
  codeBlocks: { container: HTMLElement; text: string; options: EditorCodeBlockOptions }[] = [];
307
331
 
332
+ lineNumbers: ((line: number) => string | null) | null = null;
333
+ lineTokens: ((line: number) => readonly LineToken[] | null | undefined) | null = null;
334
+
335
+ setLineNumbers(labels: ((line: number) => string | null) | null): void {
336
+ this.lineNumbers = labels;
337
+ }
338
+
339
+ lineLabels: ((line: number) => string | null) | null = null;
340
+
341
+ setLineLabels(labels: ((line: number) => string | null) | null): void {
342
+ this.lineLabels = labels;
343
+ }
344
+
345
+ /* What the input points at, as the last call left it. */
346
+ inputPopup: EditorInputPopup | null = null;
347
+
348
+ setInputPopup(popup: EditorInputPopup | null): void {
349
+ this.inputPopup = popup;
350
+ }
351
+
352
+ /* Every sentence said, oldest first. */
353
+ announcements: string[] = [];
354
+
355
+ announce(message: string): void {
356
+ this.announcements.push(message);
357
+ }
358
+
359
+ messages: Partial<EditorMessages> = {};
360
+
361
+ setMessages(messages: Partial<EditorMessages>): void {
362
+ this.messages = { ...this.messages, ...messages };
363
+ }
364
+
365
+ setLineTokens(tokens: ((line: number) => readonly LineToken[] | null | undefined) | null): void {
366
+ this.lineTokens = tokens;
367
+ }
368
+
308
369
  renderCode(container: HTMLElement, text: string, options: EditorCodeBlockOptions = {}): void {
309
370
  this.codeBlocks.push({ container, text, options });
310
371
  }
@@ -359,12 +420,22 @@ export class FakeEditor implements Editor {
359
420
  const before = this.drawnWidgets.get(owner) ?? new Set<string>();
360
421
  for (const widget of widgets) {
361
422
  if (!before.has(widget.id)) {
362
- widget.render({} as HTMLElement);
423
+ widget.render(this.element.ownerDocument?.createElement('div') ?? ({} as HTMLElement));
363
424
  }
364
425
  }
365
426
  this.drawnWidgets.set(owner, new Set(widgets.map((widget) => widget.id)));
366
427
  }
367
428
 
429
+ readonly replacementsByOwner = new Map<string, readonly EditorReplacement[]>();
430
+
431
+ setReplacements(replacements: readonly EditorReplacement[], owner = 'default'): void {
432
+ if (replacements.length === 0) {
433
+ this.replacementsByOwner.delete(owner);
434
+ } else {
435
+ this.replacementsByOwner.set(owner, replacements);
436
+ }
437
+ }
438
+
368
439
  readonly lineActionsByOwner = new Map<string, readonly EditorLineAction[]>();
369
440
 
370
441
  setLineActions(actions: readonly EditorLineAction[], owner = 'default'): void {
@@ -438,6 +509,7 @@ export class FakeEditor implements Editor {
438
509
  smartKeys: Partial<EditorSmartKeys>;
439
510
 
440
511
  guides = true;
512
+ stickyScroll = true;
441
513
  whitespace = false;
442
514
  rightMargin: number | null = null;
443
515
 
@@ -445,6 +517,10 @@ export class FakeEditor implements Editor {
445
517
  this.guides = guides;
446
518
  }
447
519
 
520
+ setStickyScroll(sticky: boolean): void {
521
+ this.stickyScroll = sticky;
522
+ }
523
+
448
524
  setWhitespace(whitespace: boolean): void {
449
525
  this.whitespace = whitespace;
450
526
  }
@@ -487,6 +563,7 @@ export class FakeEditor implements Editor {
487
563
  charSize = { width: 8, height: 20 };
488
564
  private readonly carets = new Set<(position: EditorPosition) => void>();
489
565
  private readonly hovers = new Set<(hover: EditorHover | null) => void>();
566
+ private readonly lineHeights = new Set<Listener>();
490
567
  private readonly views = new Set<Listener>();
491
568
  private readonly keys = new Set<EditorKeyHandler>();
492
569
 
@@ -501,6 +578,35 @@ export class FakeEditor implements Editor {
501
578
  this.inlayHints = hints;
502
579
  }
503
580
 
581
+ private readonly inlayHovers = new Set<(target: EditorInlayTarget | null) => void>();
582
+ private readonly inlayHandlers = new Set<EditorInlayClickHandler>();
583
+
584
+ /* The hint, or part of one, the last `setInlayLink` drew as a link. */
585
+ inlayLink: { readonly id: string; readonly part: number | null } | null = null;
586
+
587
+ onInlayHover(listener: (target: EditorInlayTarget | null) => void): () => void {
588
+ return subscribe(this.inlayHovers, listener);
589
+ }
590
+
591
+ /* The pointer resting on a hint or a part of one, or leaving them. */
592
+ hoverInlay(id: string | null, part: number | null = null): void {
593
+ emit(this.inlayHovers, id === null ? null : { id, part, rect: { left: 0, top: 0, right: 40, bottom: 20 } });
594
+ }
595
+
596
+ onInlayClick(handler: EditorInlayClickHandler): () => void {
597
+ return subscribe(this.inlayHandlers, handler);
598
+ }
599
+
600
+ /* A press on a hint as the editor sees it first; true when a handler took it. */
601
+ clickInlay(click: Partial<EditorInlayClick> & Pick<EditorInlayClick, 'id'>): boolean {
602
+ const full: EditorInlayClick = { part: null, mod: false, alt: false, shift: false, clicks: 1, touch: false, ...click };
603
+ return [...this.inlayHandlers].some((handler) => handler(full));
604
+ }
605
+
606
+ setInlayLink(target: { readonly id: string; readonly part: number | null } | null): void {
607
+ this.inlayLink = target;
608
+ }
609
+
504
610
  /* The suggestion after the caret now, which any change of the text takes away as the real editor does. */
505
611
  ghost: EditorGhostText | null = null;
506
612
 
@@ -544,6 +650,32 @@ export class FakeEditor implements Editor {
544
650
  /* The selection a person made; without one the caret. */
545
651
  selection: EditorRange | null = null;
546
652
 
653
+ /* A test supplies the measured height; this fake has no layout. */
654
+ contentHeight = 0;
655
+
656
+ setScrollTop(top: number): void {
657
+ if (Number.isFinite(top) && this.getScrollTop() !== Math.max(0, top)) {
658
+ this.scrollTop = Math.max(0, top);
659
+ emit(this.views);
660
+ }
661
+ }
662
+
663
+ getLineHeight(line: number): number {
664
+ return Number.isInteger(line) && line >= 0 && line < this.text.split('\n').length ? this.charSize.height : 0;
665
+ }
666
+
667
+ getBaseLineHeight(): number {
668
+ return this.charSize.height;
669
+ }
670
+
671
+ onLineHeights(listener: Listener): () => void {
672
+ return subscribe(this.lineHeights, listener);
673
+ }
674
+
675
+ getContentHeight(): number {
676
+ return this.contentHeight;
677
+ }
678
+
547
679
  getSelection(): EditorRange {
548
680
  return this.selection ?? { start: this.caret, end: this.caret };
549
681
  }
@@ -675,7 +807,8 @@ export class FakeEditor implements Editor {
675
807
  return [...this.keys].some((handler) => handler(full));
676
808
  }
677
809
 
678
- applyEdits(edits: readonly EditorContentChange[]): boolean {
810
+ /* The fake keeps no undo history, so `mergeUndo` changes nothing here. It keeps no selection direction either, so a test of `mapSelections` keeping a backward selection needs the real editor. */
811
+ applyEdits(edits: readonly EditorContentChange[], options: EditorApplyEditsOptions = {}): boolean {
679
812
  if (this.readOnly || edits.length === 0) {
680
813
  return false;
681
814
  }
@@ -702,11 +835,14 @@ export class FakeEditor implements Editor {
702
835
  return offset + delta;
703
836
  };
704
837
  const moved = others.map((range) => ({ anchor: follow(range.anchor), head: follow(range.head) }));
705
- this.replaceText(text, 'command');
706
- this.otherSelections = moved.map((range) => ({
707
- start: positionIn(text, Math.min(range.anchor, range.head)),
708
- end: positionIn(text, Math.max(range.anchor, range.head))
709
- }));
838
+ const mapped = options.mapSelections && this.getSelections().map((range) => options.mapSelections!(range));
839
+ this.replaceText(text, 'command', mapped);
840
+ if (mapped === undefined) {
841
+ this.otherSelections = moved.map((range) => ({
842
+ start: positionIn(text, Math.min(range.anchor, range.head)),
843
+ end: positionIn(text, Math.max(range.anchor, range.head))
844
+ }));
845
+ }
710
846
  emit(this.changes);
711
847
  return true;
712
848
  }
@@ -724,6 +860,10 @@ export class FakeEditor implements Editor {
724
860
  this.readOnlyReason = reason;
725
861
  }
726
862
 
863
+ isReadOnly(): boolean {
864
+ return this.readOnly;
865
+ }
866
+
727
867
  /* The commands run, in order; the fake has no commands of its own to carry out. */
728
868
  readonly commands: EditorRunCommand[] = [];
729
869
 
@@ -742,8 +882,11 @@ export class FakeEditor implements Editor {
742
882
  this.textChanges.clear();
743
883
  this.carets.clear();
744
884
  this.hovers.clear();
885
+ this.inlayHovers.clear();
886
+ this.inlayHandlers.clear();
745
887
  this.attributionHovers.clear();
746
888
  this.views.clear();
889
+ this.lineHeights.clear();
747
890
  this.keys.clear();
748
891
  this.saves.clear();
749
892
  this.blurs.clear();
@@ -0,0 +1,165 @@
1
+ import { EditorSearchError, type EditorSearchClient } from './search-client.ts';
2
+
3
+ export interface FindSearchOptions {
4
+ readonly createClient: () => EditorSearchClient | null;
5
+ readonly schedule?: (callback: () => void, delay: number) => () => void;
6
+ readonly now?: () => number;
7
+ }
8
+
9
+ type SearchKind = 'live' | 'preview' | 'command';
10
+
11
+ interface SearchJob {
12
+ readonly kind: SearchKind;
13
+ readonly valid?: () => boolean;
14
+ readonly run: (client: EditorSearchClient, isCurrent: () => boolean) => Promise<unknown>;
15
+ readonly resolve: (value: unknown) => void;
16
+ readonly reject: (error: unknown) => void;
17
+ cancelled: boolean;
18
+ }
19
+
20
+ export function scheduleFind(callback: () => void, delay: number): () => void {
21
+ const timer = setTimeout(callback, delay);
22
+ return () => clearTimeout(timer);
23
+ }
24
+
25
+ function superseded(): EditorSearchError {
26
+ return new EditorSearchError('superseded', 'The find operation is no longer current.');
27
+ }
28
+
29
+ /* Commands finish atomically; short background requests reuse the worker before newer work starts. */
30
+ export class FindSearchQueue {
31
+ private client: EditorSearchClient | null | undefined;
32
+ private active: SearchJob | undefined;
33
+ private pending: SearchJob[] = [];
34
+ private cancelTimer: (() => void) | undefined;
35
+ private started = 0;
36
+ private disposed = false;
37
+ private epoch = 0;
38
+ private readonly options: FindSearchOptions;
39
+
40
+ constructor(options: FindSearchOptions) {
41
+ this.options = options;
42
+ }
43
+
44
+ get busy(): boolean {
45
+ return this.active !== undefined || this.pending.length > 0;
46
+ }
47
+
48
+ request<Result>(
49
+ run: (client: EditorSearchClient, isCurrent: () => boolean) => Promise<Result>,
50
+ kind: SearchKind = 'live',
51
+ valid?: () => boolean
52
+ ): Promise<Result> {
53
+ if (this.disposed) {
54
+ return Promise.reject(new EditorSearchError('disposed', 'The find session was disposed.'));
55
+ }
56
+ return new Promise((resolve, reject) => {
57
+ this.pending = this.pending.filter((job) => {
58
+ if (job.kind !== 'command' && (job.kind === kind || (kind !== 'preview' && job.kind === 'preview'))) {
59
+ job.cancelled = true;
60
+ job.reject(superseded());
61
+ return false;
62
+ }
63
+ return true;
64
+ });
65
+ this.pending.push({ kind, valid, run, resolve: (value) => resolve(value as Result), reject, cancelled: false });
66
+ this.pending.sort((left, right) => priority(left.kind) - priority(right.kind));
67
+ if (this.active === undefined) {
68
+ this.pump();
69
+ } else if (
70
+ (kind !== 'preview' || this.active.valid?.() === false) &&
71
+ (this.active.kind !== 'command' || this.active.valid?.() === false) &&
72
+ this.cancelTimer === undefined
73
+ ) {
74
+ this.cancelTimer = (this.options.schedule ?? scheduleFind)(
75
+ () => {
76
+ this.cancelTimer = undefined;
77
+ if (this.pending.length > 0 && this.active !== undefined && (this.active.kind !== 'command' || this.active.valid?.() === false)) {
78
+ this.active.cancelled = true;
79
+ this.active.reject(superseded());
80
+ this.client?.cancel();
81
+ }
82
+ },
83
+ Math.max(0, 200 - (this.now() - this.started))
84
+ );
85
+ }
86
+ });
87
+ }
88
+
89
+ private now(): number {
90
+ return this.options.now?.() ?? performance.now();
91
+ }
92
+
93
+ private pump(): void {
94
+ if (this.disposed || this.active !== undefined) {
95
+ return;
96
+ }
97
+ const job = this.pending.shift();
98
+ if (job === undefined) {
99
+ return;
100
+ }
101
+ this.active = job;
102
+ this.started = this.now();
103
+ const epoch = this.epoch;
104
+ const alive = (): boolean => !this.disposed && epoch === this.epoch && !job.cancelled;
105
+ const isCurrent = (): boolean => alive() && job.valid?.() !== false;
106
+ void Promise.resolve()
107
+ .then(() => {
108
+ if (!isCurrent()) {
109
+ throw superseded();
110
+ }
111
+ this.client ??= this.options.createClient();
112
+ if (this.client === null) {
113
+ throw new EditorSearchError('unavailable', 'Regex search needs a search worker.');
114
+ }
115
+ const client = this.client;
116
+ const guarded: EditorSearchClient = {
117
+ search: (...args) => (isCurrent() ? client.search(...args) : Promise.reject(superseded())),
118
+ match: (...args) => (isCurrent() ? client.match(...args) : Promise.reject(superseded())),
119
+ next: (...args) => (isCurrent() ? client.next(...args) : Promise.reject(superseded())),
120
+ replacements: (...args) => (isCurrent() ? client.replacements(...args) : Promise.reject(superseded())),
121
+ cancel: () => client.cancel(),
122
+ release: () => client.release(),
123
+ dispose: () => client.dispose()
124
+ };
125
+ return job.run(guarded, isCurrent);
126
+ })
127
+ .then((value) => {
128
+ if (alive()) {
129
+ job.resolve(value);
130
+ } else {
131
+ job.reject(superseded());
132
+ }
133
+ }, job.reject)
134
+ .finally(() => {
135
+ this.active = undefined;
136
+ this.cancelTimer?.();
137
+ this.cancelTimer = undefined;
138
+ this.pump();
139
+ });
140
+ }
141
+
142
+ release(): void {
143
+ this.epoch++;
144
+ this.active?.reject(superseded());
145
+ for (const job of this.pending) {
146
+ job.cancelled = true;
147
+ job.reject(superseded());
148
+ }
149
+ this.pending = [];
150
+ this.cancelTimer?.();
151
+ this.cancelTimer = undefined;
152
+ this.client?.release();
153
+ }
154
+
155
+ dispose(): void {
156
+ this.disposed = true;
157
+ this.release();
158
+ this.client?.dispose();
159
+ this.client = undefined;
160
+ }
161
+ }
162
+
163
+ function priority(kind: SearchKind): number {
164
+ return kind === 'command' ? 0 : kind === 'live' ? 1 : 2;
165
+ }