@theia/plugin-ext 1.71.0-next.8 → 1.71.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 (98) hide show
  1. package/lib/common/plugin-api-rpc.d.ts +76 -0
  2. package/lib/common/plugin-api-rpc.d.ts.map +1 -1
  3. package/lib/common/plugin-api-rpc.js +9 -1
  4. package/lib/common/plugin-api-rpc.js.map +1 -1
  5. package/lib/hosted/browser/hosted-plugin.d.ts.map +1 -1
  6. package/lib/hosted/browser/hosted-plugin.js +13 -6
  7. package/lib/hosted/browser/hosted-plugin.js.map +1 -1
  8. package/lib/main/browser/main-context.d.ts.map +1 -1
  9. package/lib/main/browser/main-context.js +2 -6
  10. package/lib/main/browser/main-context.js.map +1 -1
  11. package/lib/main/browser/main-file-system-event-service.d.ts +10 -2
  12. package/lib/main/browser/main-file-system-event-service.d.ts.map +1 -1
  13. package/lib/main/browser/main-file-system-event-service.js +19 -2
  14. package/lib/main/browser/main-file-system-event-service.js.map +1 -1
  15. package/lib/main/browser/menus/menus-contribution-handler.d.ts.map +1 -1
  16. package/lib/main/browser/menus/menus-contribution-handler.js +2 -0
  17. package/lib/main/browser/menus/menus-contribution-handler.js.map +1 -1
  18. package/lib/main/browser/menus/plugin-menu-command-adapter.d.ts +1 -0
  19. package/lib/main/browser/menus/plugin-menu-command-adapter.d.ts.map +1 -1
  20. package/lib/main/browser/menus/plugin-menu-command-adapter.js +13 -1
  21. package/lib/main/browser/menus/plugin-menu-command-adapter.js.map +1 -1
  22. package/lib/main/browser/menus/vscode-theia-menu-mappings.d.ts +1 -1
  23. package/lib/main/browser/menus/vscode-theia-menu-mappings.d.ts.map +1 -1
  24. package/lib/main/browser/menus/vscode-theia-menu-mappings.js +9 -2
  25. package/lib/main/browser/menus/vscode-theia-menu-mappings.js.map +1 -1
  26. package/lib/main/browser/scm-main.d.ts +33 -2
  27. package/lib/main/browser/scm-main.d.ts.map +1 -1
  28. package/lib/main/browser/scm-main.js +237 -3
  29. package/lib/main/browser/scm-main.js.map +1 -1
  30. package/lib/main/browser/scm-main.spec.d.ts +2 -0
  31. package/lib/main/browser/scm-main.spec.d.ts.map +1 -0
  32. package/lib/main/browser/scm-main.spec.js +87 -0
  33. package/lib/main/browser/scm-main.spec.js.map +1 -0
  34. package/lib/main/browser/test-main.d.ts +3 -2
  35. package/lib/main/browser/test-main.d.ts.map +1 -1
  36. package/lib/main/browser/test-main.js +12 -1
  37. package/lib/main/browser/test-main.js.map +1 -1
  38. package/lib/plugin/file-system-event-service-ext-impl.d.ts +11 -5
  39. package/lib/plugin/file-system-event-service-ext-impl.d.ts.map +1 -1
  40. package/lib/plugin/file-system-event-service-ext-impl.js +28 -9
  41. package/lib/plugin/file-system-event-service-ext-impl.js.map +1 -1
  42. package/lib/plugin/plugin-context.js +4 -4
  43. package/lib/plugin/plugin-context.js.map +1 -1
  44. package/lib/plugin/scm.d.ts +8 -2
  45. package/lib/plugin/scm.d.ts.map +1 -1
  46. package/lib/plugin/scm.js +188 -5
  47. package/lib/plugin/scm.js.map +1 -1
  48. package/lib/plugin/scm.spec.d.ts +2 -0
  49. package/lib/plugin/scm.spec.d.ts.map +1 -0
  50. package/lib/plugin/scm.spec.js +461 -0
  51. package/lib/plugin/scm.spec.js.map +1 -0
  52. package/lib/plugin/terminal-ext.d.ts +13 -3
  53. package/lib/plugin/terminal-ext.d.ts.map +1 -1
  54. package/lib/plugin/terminal-ext.js +51 -10
  55. package/lib/plugin/terminal-ext.js.map +1 -1
  56. package/lib/plugin/terminal-ext.spec.d.ts +2 -0
  57. package/lib/plugin/terminal-ext.spec.d.ts.map +1 -0
  58. package/lib/plugin/terminal-ext.spec.js +285 -0
  59. package/lib/plugin/terminal-ext.spec.js.map +1 -0
  60. package/lib/plugin/test-item.d.ts.map +1 -1
  61. package/lib/plugin/test-item.js +8 -3
  62. package/lib/plugin/test-item.js.map +1 -1
  63. package/lib/plugin/tests.d.ts.map +1 -1
  64. package/lib/plugin/tests.js +15 -3
  65. package/lib/plugin/tests.js.map +1 -1
  66. package/lib/plugin/type-converters.d.ts +2 -2
  67. package/lib/plugin/type-converters.d.ts.map +1 -1
  68. package/lib/plugin/type-converters.js +3 -9
  69. package/lib/plugin/type-converters.js.map +1 -1
  70. package/lib/plugin/types-impl.d.ts +1 -1
  71. package/lib/plugin/types-impl.d.ts.map +1 -1
  72. package/lib/plugin/types-impl.js +1 -1
  73. package/lib/plugin/types-impl.js.map +1 -1
  74. package/lib/plugin/workspace.d.ts.map +1 -1
  75. package/lib/plugin/workspace.js +17 -3
  76. package/lib/plugin/workspace.js.map +1 -1
  77. package/package.json +39 -39
  78. package/src/common/plugin-api-rpc.ts +78 -0
  79. package/src/hosted/browser/hosted-plugin.ts +13 -6
  80. package/src/main/browser/main-context.ts +3 -7
  81. package/src/main/browser/main-file-system-event-service.ts +26 -6
  82. package/src/main/browser/menus/menus-contribution-handler.ts +2 -0
  83. package/src/main/browser/menus/plugin-menu-command-adapter.ts +15 -2
  84. package/src/main/browser/menus/vscode-theia-menu-mappings.ts +12 -3
  85. package/src/main/browser/scm-main.spec.ts +105 -0
  86. package/src/main/browser/scm-main.ts +272 -4
  87. package/src/main/browser/test-main.ts +13 -3
  88. package/src/plugin/file-system-event-service-ext-impl.ts +40 -14
  89. package/src/plugin/plugin-context.ts +7 -7
  90. package/src/plugin/scm.spec.ts +615 -0
  91. package/src/plugin/scm.ts +224 -6
  92. package/src/plugin/terminal-ext.spec.ts +350 -0
  93. package/src/plugin/terminal-ext.ts +58 -12
  94. package/src/plugin/test-item.ts +8 -3
  95. package/src/plugin/tests.ts +14 -3
  96. package/src/plugin/type-converters.ts +7 -13
  97. package/src/plugin/types-impl.ts +2 -2
  98. package/src/plugin/workspace.ts +17 -3
@@ -27,14 +27,24 @@ import {
27
27
  ScmMain,
28
28
  SourceControlProviderFeatures,
29
29
  ScmRawResourceSplices, ScmRawResourceGroup,
30
- ScmActionButton as RpcScmActionButton
30
+ ScmActionButton as RpcScmActionButton,
31
+ ScmHistoryItemRefDto,
32
+ ScmHistoryItemDto,
33
+ ScmHistoryItemChangeDto,
34
+ ScmHistoryOptionsDto,
35
+ ScmHistoryItemRefsChangeEventDto
31
36
  } from '../../common/plugin-api-rpc';
32
- import { ScmProvider, ScmResource, ScmResourceDecorations, ScmResourceGroup, ScmCommand, ScmActionButton } from '@theia/scm/lib/browser/scm-provider';
37
+ import {
38
+ ScmProvider, ScmResource, ScmResourceDecorations, ScmResourceGroup, ScmCommand, ScmActionButton,
39
+ ScmHistoryProvider, ScmHistoryItemRef, ScmHistoryItemRefsChangeEvent,
40
+ ScmHistoryOptions, ScmHistoryItem, ScmHistoryItemChange
41
+ } from '@theia/scm/lib/browser/scm-provider';
33
42
  import { ScmRepository } from '@theia/scm/lib/browser/scm-repository';
34
43
  import { ScmService } from '@theia/scm/lib/browser/scm-service';
35
44
  import { RPCProtocol } from '../../common/rpc-protocol';
36
45
  import { interfaces } from '@theia/core/shared/inversify';
37
46
  import { Emitter, Event } from '@theia/core/lib/common/event';
47
+ import { CancellationToken, CancellationTokenSource } from '@theia/core/lib/common/cancellation';
38
48
  import { DisposableCollection } from '@theia/core/lib/common/disposable';
39
49
  import URI from '@theia/core/lib/common/uri';
40
50
  import { URI as vscodeURI } from '@theia/core/shared/vscode-uri';
@@ -104,6 +114,198 @@ export class PluginScmResource implements ScmResource {
104
114
  }
105
115
  }
106
116
 
117
+ function historyItemRefFromDto(dto: ScmHistoryItemRefDto): ScmHistoryItemRef {
118
+ return {
119
+ id: dto.id,
120
+ name: dto.name,
121
+ description: dto.description,
122
+ revision: dto.revision,
123
+ icon: dto.icon ? (ThemeIcon.isThemeIcon(dto.icon) ? ThemeIcon.asClassName(dto.icon) :
124
+ 'light' in dto.icon ? vscodeURI.revive(dto.icon.light).toString() : vscodeURI.revive(dto.icon as UriComponents).toString()) : undefined,
125
+ category: dto.category,
126
+ };
127
+ }
128
+
129
+ function historyItemFromDto(dto: ScmHistoryItemDto): ScmHistoryItem {
130
+ return {
131
+ id: dto.id,
132
+ parentIds: dto.parentIds,
133
+ subject: dto.subject,
134
+ message: typeof dto.message === 'string' ? dto.message : dto.message?.value,
135
+ author: dto.author,
136
+ authorEmail: dto.authorEmail,
137
+ authorIcon: dto.authorIcon ? (ThemeIcon.isThemeIcon(dto.authorIcon) ? ThemeIcon.asClassName(dto.authorIcon) :
138
+ 'light' in dto.authorIcon ? vscodeURI.revive(dto.authorIcon.light).toString() : vscodeURI.revive(dto.authorIcon as UriComponents).toString()) : undefined,
139
+ displayId: dto.displayId,
140
+ timestamp: dto.timestamp,
141
+ tooltip: typeof dto.tooltip === 'string' ? dto.tooltip : dto.tooltip?.value,
142
+ statistics: dto.statistics ? {
143
+ files: dto.statistics.files,
144
+ insertions: dto.statistics.insertions,
145
+ deletions: dto.statistics.deletions,
146
+ } : undefined,
147
+ references: dto.references ? dto.references.map(historyItemRefFromDto) : undefined,
148
+ };
149
+ }
150
+
151
+ function historyItemChangeFromDto(dto: ScmHistoryItemChangeDto): ScmHistoryItemChange {
152
+ return {
153
+ uri: vscodeURI.revive(dto.uri).toString(),
154
+ originalUri: dto.originalUri ? vscodeURI.revive(dto.originalUri).toString() : undefined,
155
+ modifiedUri: dto.modifiedUri ? vscodeURI.revive(dto.modifiedUri).toString() : undefined,
156
+ renameUri: dto.renameUri ? vscodeURI.revive(dto.renameUri).toString() : undefined,
157
+ };
158
+ }
159
+
160
+ export class PluginScmHistoryProvider implements ScmHistoryProvider {
161
+
162
+ private readonly onDidChangeCurrentHistoryItemRefsEmitter = new Emitter<void>();
163
+ readonly onDidChangeCurrentHistoryItemRefs: Event<void> = this.onDidChangeCurrentHistoryItemRefsEmitter.event;
164
+
165
+ private readonly onDidChangeHistoryItemRefsEmitter = new Emitter<ScmHistoryItemRefsChangeEvent>();
166
+ readonly onDidChangeHistoryItemRefs: Event<ScmHistoryItemRefsChangeEvent> = this.onDidChangeHistoryItemRefsEmitter.event;
167
+
168
+ private _currentHistoryItemRef: ScmHistoryItemRef | undefined;
169
+ get currentHistoryItemRef(): ScmHistoryItemRef | undefined { return this._currentHistoryItemRef; }
170
+
171
+ private _currentHistoryItemRemoteRef: ScmHistoryItemRef | undefined;
172
+ get currentHistoryItemRemoteRef(): ScmHistoryItemRef | undefined { return this._currentHistoryItemRemoteRef; }
173
+
174
+ private _currentHistoryItemBaseRef: ScmHistoryItemRef | undefined;
175
+ get currentHistoryItemBaseRef(): ScmHistoryItemRef | undefined { return this._currentHistoryItemBaseRef; }
176
+
177
+ private readonly disposables = new DisposableCollection();
178
+ private pendingRequests = new Set<CancellationTokenSource>();
179
+
180
+ constructor(
181
+ private readonly proxy: ScmExt,
182
+ private readonly handle: number
183
+ ) {
184
+ this.disposables.push(this.onDidChangeCurrentHistoryItemRefsEmitter);
185
+ this.disposables.push(this.onDidChangeHistoryItemRefsEmitter);
186
+ }
187
+
188
+ updateFromFeatures(features: SourceControlProviderFeatures): void {
189
+ if (features.currentHistoryItemRef !== undefined) {
190
+ this._currentHistoryItemRef = features.currentHistoryItemRef ? historyItemRefFromDto(features.currentHistoryItemRef) : undefined;
191
+ }
192
+ if (features.currentHistoryItemRemoteRef !== undefined) {
193
+ this._currentHistoryItemRemoteRef = features.currentHistoryItemRemoteRef ? historyItemRefFromDto(features.currentHistoryItemRemoteRef) : undefined;
194
+ }
195
+ if (features.currentHistoryItemBaseRef !== undefined) {
196
+ this._currentHistoryItemBaseRef = features.currentHistoryItemBaseRef ? historyItemRefFromDto(features.currentHistoryItemBaseRef) : undefined;
197
+ }
198
+ }
199
+
200
+ fireDidChangeCurrentHistoryItemRefs(): void {
201
+ this.onDidChangeCurrentHistoryItemRefsEmitter.fire();
202
+ }
203
+
204
+ fireDidChangeHistoryItemRefs(event: ScmHistoryItemRefsChangeEventDto): void {
205
+ this.onDidChangeHistoryItemRefsEmitter.fire({
206
+ added: event.added.map(historyItemRefFromDto),
207
+ removed: event.removed.map(historyItemRefFromDto),
208
+ modified: event.modified.map(historyItemRefFromDto),
209
+ });
210
+ }
211
+
212
+ async provideHistoryItemRefs(historyItemRefs: string[] | undefined, token: CancellationToken): Promise<ScmHistoryItemRef[] | undefined> {
213
+ const cts = new CancellationTokenSource();
214
+ const listener = token.onCancellationRequested(() => cts.cancel());
215
+ this.pendingRequests.add(cts);
216
+ try {
217
+ const result = await this.proxy.$provideHistoryItemRefs(this.handle, historyItemRefs, cts.token);
218
+ if (!result) {
219
+ return undefined;
220
+ }
221
+ return result.map(historyItemRefFromDto);
222
+ } finally {
223
+ listener.dispose();
224
+ this.pendingRequests.delete(cts);
225
+ cts.dispose();
226
+ }
227
+ }
228
+
229
+ async provideHistoryItems(options: ScmHistoryOptions, token: CancellationToken): Promise<ScmHistoryItem[] | undefined> {
230
+ const dto: ScmHistoryOptionsDto = {
231
+ skip: options.skip,
232
+ limit: options.limit,
233
+ historyItemRefs: options.historyItemRefs ? [...options.historyItemRefs] : undefined,
234
+ filterText: options.filterText,
235
+ };
236
+ const cts = new CancellationTokenSource();
237
+ const listener = token.onCancellationRequested(() => cts.cancel());
238
+ this.pendingRequests.add(cts);
239
+ try {
240
+ const result = await this.proxy.$provideHistoryItems(this.handle, dto, cts.token);
241
+ if (!result) {
242
+ return undefined;
243
+ }
244
+ return result.map(historyItemFromDto);
245
+ } finally {
246
+ listener.dispose();
247
+ this.pendingRequests.delete(cts);
248
+ cts.dispose();
249
+ }
250
+ }
251
+
252
+ async provideHistoryItemChanges(historyItemId: string, historyItemParentId: string | undefined, token: CancellationToken): Promise<ScmHistoryItemChange[] | undefined> {
253
+ const cts = new CancellationTokenSource();
254
+ const listener = token.onCancellationRequested(() => cts.cancel());
255
+ this.pendingRequests.add(cts);
256
+ try {
257
+ const result = await this.proxy.$provideHistoryItemChanges(this.handle, historyItemId, historyItemParentId, cts.token);
258
+ if (!result) {
259
+ return undefined;
260
+ }
261
+ return result.map(historyItemChangeFromDto);
262
+ } finally {
263
+ listener.dispose();
264
+ this.pendingRequests.delete(cts);
265
+ cts.dispose();
266
+ }
267
+ }
268
+
269
+ async resolveHistoryItem(historyItemId: string, token: CancellationToken): Promise<ScmHistoryItem | undefined> {
270
+ const cts = new CancellationTokenSource();
271
+ const listener = token.onCancellationRequested(() => cts.cancel());
272
+ this.pendingRequests.add(cts);
273
+ try {
274
+ const result = await this.proxy.$resolveHistoryItem(this.handle, historyItemId, cts.token);
275
+ if (!result) {
276
+ return undefined;
277
+ }
278
+ return historyItemFromDto(result);
279
+ } finally {
280
+ listener.dispose();
281
+ this.pendingRequests.delete(cts);
282
+ cts.dispose();
283
+ }
284
+ }
285
+
286
+ async resolveHistoryItemRefsCommonAncestor(historyItemRefs: string[], token: CancellationToken): Promise<string | undefined> {
287
+ const cts = new CancellationTokenSource();
288
+ const listener = token.onCancellationRequested(() => cts.cancel());
289
+ this.pendingRequests.add(cts);
290
+ try {
291
+ return await this.proxy.$resolveHistoryItemRefsCommonAncestor(this.handle, historyItemRefs, cts.token) ?? undefined;
292
+ } finally {
293
+ listener.dispose();
294
+ this.pendingRequests.delete(cts);
295
+ cts.dispose();
296
+ }
297
+ }
298
+
299
+ dispose(): void {
300
+ for (const cts of this.pendingRequests) {
301
+ cts.cancel();
302
+ cts.dispose();
303
+ }
304
+ this.pendingRequests.clear();
305
+ this.disposables.dispose();
306
+ }
307
+ }
308
+
107
309
  export class PluginScmProvider implements ScmProvider {
108
310
 
109
311
  private _id = this.contextValue;
@@ -118,6 +320,11 @@ export class PluginScmProvider implements ScmProvider {
118
320
  private _actionButton: ScmActionButton | undefined;
119
321
  get actionButton(): ScmActionButton | undefined { return this._actionButton; }
120
322
 
323
+ private _historyProvider: PluginScmHistoryProvider | undefined;
324
+ get historyProvider(): ScmHistoryProvider | undefined {
325
+ return this._historyProvider;
326
+ }
327
+
121
328
  private features: SourceControlProviderFeatures = {};
122
329
 
123
330
  get providerContextValue(): string | undefined { return this.features.contextValue; }
@@ -172,8 +379,22 @@ export class PluginScmProvider implements ScmProvider {
172
379
 
173
380
  updateSourceControl(features: SourceControlProviderFeatures): void {
174
381
  this.features = { ...this.features, ...features };
382
+
383
+ if (typeof features.hasHistoryProvider !== 'undefined') {
384
+ if (features.hasHistoryProvider && !this._historyProvider) {
385
+ this._historyProvider = new PluginScmHistoryProvider(this.proxy, this.handle);
386
+ } else if (!features.hasHistoryProvider && this._historyProvider) {
387
+ this._historyProvider.dispose();
388
+ this._historyProvider = undefined;
389
+ }
390
+ }
391
+
175
392
  this.onDidChangeEmitter.fire();
176
393
 
394
+ if (this._historyProvider) {
395
+ this._historyProvider.updateFromFeatures(features);
396
+ }
397
+
177
398
  if (typeof features.commitTemplate !== 'undefined') {
178
399
  this.onDidChangeCommitTemplateEmitter.fire(this.commitTemplate!);
179
400
  }
@@ -303,7 +524,10 @@ export class PluginScmProvider implements ScmProvider {
303
524
  this.onDidChangeActionButtonEmitter.fire(actionButton);
304
525
  }
305
526
 
306
- dispose(): void { }
527
+ dispose(): void {
528
+ this._historyProvider?.dispose();
529
+ this._historyProvider = undefined;
530
+ }
307
531
  }
308
532
 
309
533
  export class ScmMainImpl implements ScmMain {
@@ -388,7 +612,31 @@ export class ScmMainImpl implements ScmMain {
388
612
  return;
389
613
  }
390
614
 
391
- this.repositoryDisposables.get(handle)!.dispose();
615
+ // Defensively cascade-dispose any children (e.g. worktrees) whose parent
616
+ // is being unregistered. The plugin API exposes onDidDisposeParent, but if
617
+ // a plugin fails to propagate it (or races with an external change like a
618
+ // worktree being removed on disk), children would otherwise be orphaned
619
+ // in the Repositories view. Children are torn down inline (not recursively)
620
+ // to keep the iteration over `this.repositories` safe and to tolerate a
621
+ // plugin-side RPC for the child arriving first.
622
+ const childHandles: number[] = [];
623
+ for (const [childHandle, childRepo] of this.repositories) {
624
+ if (childHandle !== handle && (childRepo.provider as PluginScmProvider).parentHandle === handle) {
625
+ childHandles.push(childHandle);
626
+ }
627
+ }
628
+ for (const childHandle of childHandles) {
629
+ const childRepository = this.repositories.get(childHandle);
630
+ if (!childRepository) {
631
+ continue;
632
+ }
633
+ this.repositoryDisposables.get(childHandle)?.dispose();
634
+ this.repositoryDisposables.delete(childHandle);
635
+ childRepository.dispose();
636
+ this.repositories.delete(childHandle);
637
+ }
638
+
639
+ this.repositoryDisposables.get(handle)?.dispose();
392
640
  this.repositoryDisposables.delete(handle);
393
641
 
394
642
  repository.dispose();
@@ -522,4 +770,24 @@ export class ScmMainImpl implements ScmMain {
522
770
 
523
771
  provider.updateActionButton(converted);
524
772
  }
773
+
774
+ $onDidChangeCurrentHistoryItemRefs(sourceControlHandle: number): void {
775
+ const repository = this.repositories.get(sourceControlHandle);
776
+ if (!repository) {
777
+ return;
778
+ }
779
+ const provider = repository.provider as PluginScmProvider;
780
+ const historyProvider = provider.historyProvider as PluginScmHistoryProvider | undefined;
781
+ historyProvider?.fireDidChangeCurrentHistoryItemRefs();
782
+ }
783
+
784
+ $onDidChangeHistoryItemRefs(sourceControlHandle: number, event: ScmHistoryItemRefsChangeEventDto): void {
785
+ const repository = this.repositories.get(sourceControlHandle);
786
+ if (!repository) {
787
+ return;
788
+ }
789
+ const provider = repository.provider as PluginScmProvider;
790
+ const historyProvider = provider.historyProvider as PluginScmHistoryProvider | undefined;
791
+ historyProvider?.fireDidChangeHistoryItemRefs(event);
792
+ }
525
793
  }
@@ -410,7 +410,8 @@ class TestControllerImpl implements TestController {
410
410
  private _runs = new SimpleObservableCollection<TestRunImpl>();
411
411
  readonly deltaBuilder = new AccumulatingTreeDeltaEmitter<string, TestItemImpl>(300);
412
412
  canRefresh: boolean;
413
- private canResolveChildren: boolean = false;
413
+ canResolveChildren: boolean = false;
414
+ private hasTriggeredInitialResolve: boolean = false;
414
415
  readonly items = new TestItemCollection(this, item => item.path, () => this.deltaBuilder);
415
416
 
416
417
  constructor(private readonly proxy: TestingExt, readonly id: string, public label: string) {
@@ -510,6 +511,10 @@ class TestControllerImpl implements TestController {
510
511
  }
511
512
  if ('canResolve' in change) {
512
513
  this.canResolveChildren = change.canResolve!;
514
+ if (change.canResolve && !this.hasTriggeredInitialResolve) {
515
+ this.hasTriggeredInitialResolve = true;
516
+ this.resolveChildren();
517
+ }
513
518
  }
514
519
  if ('label' in change) {
515
520
  this.label = change.label!;
@@ -543,9 +548,14 @@ class TestControllerImpl implements TestController {
543
548
  }
544
549
  onItemsChanged: Event<TreeDelta<string, TestItemImpl>[]> = this.deltaBuilder.onDidFlush;
545
550
 
546
- resolveChildren(item: TestItem): void {
551
+ resolveChildren(item?: TestItem): void {
547
552
  if (this.canResolveChildren) {
548
- this.proxy.$onResolveChildren(this.id, itemToPath(item));
553
+ if (item) {
554
+ this.proxy.$onResolveChildren(this.id, itemToPath(item));
555
+ } else {
556
+ // Root-level resolve: trigger discovery of top-level test items
557
+ this.proxy.$onResolveChildren(this.id, []);
558
+ }
549
559
  }
550
560
  }
551
561
 
@@ -18,7 +18,7 @@
18
18
  * Licensed under the MIT License. See License.txt in the project root for license information.
19
19
  *--------------------------------------------------------------------------------------------*/
20
20
  /**
21
- * **IMPORTANT** this code is running in the plugin host process and should be closed as possible to VS Code counterpart:
21
+ * **IMPORTANT** this code is running in the plugin host process and should be close as possible to VS Code counterpart:
22
22
  * https://github.com/microsoft/vscode/blob/04c36be045a94fee58e5f8992d3e3fd980294a84/src/vs/workbench/api/common/extHostFileSystemEventService.ts
23
23
  * One should be able to diff them to see differences.
24
24
  */
@@ -28,11 +28,12 @@
28
28
  /* eslint-disable @typescript-eslint/no-explicit-any */
29
29
  /* eslint-disable @typescript-eslint/tslint/config */
30
30
 
31
- import { Emitter, WaitUntilEvent, AsyncEmitter, WaitUntilData } from '@theia/core/lib/common/event';
32
- import { IRelativePattern, parse } from '@theia/core/lib/common/glob';
31
+ import { Emitter, Event as EventNamespace, WaitUntilEvent, AsyncEmitter, WaitUntilData } from '@theia/core/lib/common/event';
32
+ import { GLOB_SPLIT, GLOBSTAR, parse } from '@theia/core/lib/common/glob';
33
33
  import { UriComponents } from '../common/uri-components';
34
- import { Disposable, URI, WorkspaceEdit } from './types-impl';
34
+ import { Disposable, RelativePattern, URI, WorkspaceEdit } from './types-impl';
35
35
  import { EditorsAndDocumentsExtImpl as ExtHostDocumentsAndEditors } from './editors-and-documents';
36
+ import { WorkspaceExtImpl as ExtHostWorkspace } from './workspace';
36
37
  import type * as vscode from '@theia/plugin';
37
38
  import * as typeConverter from './type-converters';
38
39
  import { FileOperation } from '@theia/filesystem/lib/common/files';
@@ -68,8 +69,9 @@ export class FileSystemWatcher implements vscode.FileSystemWatcher {
68
69
  return Boolean(this._config & 0b100);
69
70
  }
70
71
 
71
- constructor(dispatcher: Event<FileSystemEvents>, globPattern: string | IRelativePattern,
72
- ignoreCreateEvents?: boolean, ignoreChangeEvents?: boolean, ignoreDeleteEvents?: boolean, excludes?: string[]) {
72
+ constructor(dispatcher: Event<FileSystemEvents>, globPattern: string | RelativePattern,
73
+ ignoreCreateEvents?: boolean, ignoreChangeEvents?: boolean, ignoreDeleteEvents?: boolean, excludes?: string[],
74
+ filter: (uri: URI) => boolean = () => true, disposable: { dispose(): unknown } = Disposable.from()) {
73
75
 
74
76
  this._config = 0;
75
77
  if (ignoreCreateEvents) {
@@ -89,7 +91,7 @@ export class FileSystemWatcher implements vscode.FileSystemWatcher {
89
91
  if (!ignoreCreateEvents) {
90
92
  for (const created of events.created) {
91
93
  const uri = URI.revive(created);
92
- if (parsedPattern(uri.fsPath) && !excludePatterns.some(p => p(uri.fsPath))) {
94
+ if (parsedPattern(uri.fsPath) && !excludePatterns.some(p => p(uri.fsPath)) && filter(uri)) {
93
95
  this._onDidCreate.fire(uri);
94
96
  }
95
97
  }
@@ -97,7 +99,7 @@ export class FileSystemWatcher implements vscode.FileSystemWatcher {
97
99
  if (!ignoreChangeEvents) {
98
100
  for (const changed of events.changed) {
99
101
  const uri = URI.revive(changed);
100
- if (parsedPattern(uri.fsPath) && !excludePatterns.some(p => p(uri.fsPath))) {
102
+ if (parsedPattern(uri.fsPath) && !excludePatterns.some(p => p(uri.fsPath)) && filter(uri)) {
101
103
  this._onDidChange.fire(uri);
102
104
  }
103
105
  }
@@ -105,14 +107,14 @@ export class FileSystemWatcher implements vscode.FileSystemWatcher {
105
107
  if (!ignoreDeleteEvents) {
106
108
  for (const deleted of events.deleted) {
107
109
  const uri = URI.revive(deleted);
108
- if (parsedPattern(uri.fsPath) && !excludePatterns.some(p => p(uri.fsPath))) {
110
+ if (parsedPattern(uri.fsPath) && !excludePatterns.some(p => p(uri.fsPath)) && filter(uri)) {
109
111
  this._onDidDelete.fire(uri);
110
112
  }
111
113
  }
112
114
  }
113
115
  });
114
116
 
115
- this._disposable = Disposable.from(this._onDidCreate, this._onDidChange, this._onDidDelete, subscription);
117
+ this._disposable = Disposable.from(disposable, this._onDidCreate, this._onDidChange, this._onDidDelete, subscription);
116
118
  }
117
119
 
118
120
  dispose(): void {
@@ -155,16 +157,40 @@ export class ExtHostFileSystemEventService implements ExtHostFileSystemEventServ
155
157
  constructor(
156
158
  rpc: RPCProtocol,
157
159
  private readonly _extHostDocumentsAndEditors: ExtHostDocumentsAndEditors,
158
- private readonly _mainThreadTextEditors: MainThreadTextEditorsShape = rpc.getProxy(PLUGIN_RPC_CONTEXT.TEXT_EDITORS_MAIN)
160
+ private readonly _extHostWorkspace: ExtHostWorkspace,
161
+ private readonly _mainThreadTextEditors: MainThreadTextEditorsShape = rpc.getProxy(PLUGIN_RPC_CONTEXT.TEXT_EDITORS_MAIN),
162
+ private readonly _mainThreadFileSystemEventService = rpc.getProxy(PLUGIN_RPC_CONTEXT.FILE_SYSTEM_EVENT_SERVICE_MAIN)
159
163
  ) {
160
- //
164
+ // Language services often watch every component of source trees (including dependencies),
165
+ // which can result in hundreds of watchers in large projects.
166
+ // Disable the leak warning (maxListeners 0 = unbounded) to avoid false positives.
167
+ EventNamespace.setMaxListeners(this._onFileSystemEvent.event, 0);
161
168
  }
162
169
 
163
170
  // --- file events
164
171
 
165
- createFileSystemWatcher(globPattern: string | IRelativePattern, ignoreCreateEvents?: boolean,
172
+ createFileSystemWatcher(globPattern: string | RelativePattern, ignoreCreateEvents?: boolean,
166
173
  ignoreChangeEvents?: boolean, ignoreDeleteEvents?: boolean): vscode.FileSystemWatcher {
167
- return new FileSystemWatcher(this._onFileSystemEvent.event, globPattern, ignoreCreateEvents, ignoreChangeEvents, ignoreDeleteEvents);
174
+ const filter = typeof globPattern === 'string' ? // ignore events outside the workspace when only a string pattern is provided
175
+ (uri: URI) => !!this._extHostWorkspace.getWorkspaceFolder(uri) : undefined;
176
+ return new FileSystemWatcher(this._onFileSystemEvent.event, globPattern, ignoreCreateEvents, ignoreChangeEvents, ignoreDeleteEvents,
177
+ undefined, filter, this.ensureWatching(globPattern, ignoreCreateEvents, ignoreChangeEvents, ignoreDeleteEvents));
178
+ }
179
+
180
+ private ensureWatching(globPattern: string | RelativePattern,
181
+ ignoreCreateEvents?: boolean, ignoreChangeEvents?: boolean, ignoreDeleteEvents?: boolean): Disposable | undefined {
182
+ if (typeof globPattern === 'string' || this._extHostWorkspace.getWorkspaceFolder(globPattern.baseUri)) {
183
+ return; // workspace is already watched by default, no need to watch again
184
+ }
185
+
186
+ if (ignoreChangeEvents && ignoreCreateEvents && ignoreDeleteEvents) {
187
+ return; // no need to watch if we ignore all events
188
+ }
189
+
190
+ const session = Math.random();
191
+ const recursive = globPattern.pattern.includes(GLOBSTAR) || globPattern.pattern.includes(GLOB_SPLIT); // only watch recursively if pattern indicates the need for it
192
+ this._mainThreadFileSystemEventService.$watch(session, globPattern.baseUri, { recursive, excludes: [] });
193
+ return Disposable.from({ dispose: () => this._mainThreadFileSystemEventService.$unwatch(session) });
168
194
  }
169
195
 
170
196
  $onFileEvent(events: FileSystemEvents) {
@@ -257,7 +257,7 @@ import { DocumentsExtImpl } from './documents';
257
257
  import { TextEditorCursorStyle } from '../common/editor-options';
258
258
  import { PreferenceRegistryExtImpl } from './preference-registry';
259
259
  import { OutputChannelRegistryExtImpl } from './output-channel-registry';
260
- import { TerminalServiceExtImpl, TerminalExtImpl } from './terminal-ext';
260
+ import { TerminalServiceExtImpl } from './terminal-ext';
261
261
  import { LanguagesExtImpl } from './languages';
262
262
  import { fromDocumentSelector, pluginToPluginInfo, fromGlobPattern } from './type-converters';
263
263
  import { DialogsExtImpl } from './dialogs';
@@ -348,7 +348,7 @@ export function createAPIFactory(
348
348
  const connectionExt = rpc.set(MAIN_RPC_CONTEXT.CONNECTION_EXT, new ConnectionImpl(rpc.getProxy(PLUGIN_RPC_CONTEXT.CONNECTION_MAIN)));
349
349
  const fileSystemExt = rpc.set(MAIN_RPC_CONTEXT.FILE_SYSTEM_EXT, new FileSystemExtImpl(rpc));
350
350
  const languagesExt = rpc.set(MAIN_RPC_CONTEXT.LANGUAGES_EXT, new LanguagesExtImpl(rpc, documents, commandRegistry, fileSystemExt));
351
- const extHostFileSystemEvent = rpc.set(MAIN_RPC_CONTEXT.ExtHostFileSystemEventService, new ExtHostFileSystemEventService(rpc, editorsAndDocumentsExt));
351
+ const extHostFileSystemEvent = rpc.set(MAIN_RPC_CONTEXT.ExtHostFileSystemEventService, new ExtHostFileSystemEventService(rpc, editorsAndDocumentsExt, workspaceExt));
352
352
  const scmExt = rpc.set(MAIN_RPC_CONTEXT.SCM_EXT, new ScmExtImpl(rpc, commandRegistry));
353
353
  const decorationsExt = rpc.set(MAIN_RPC_CONTEXT.DECORATIONS_EXT, new DecorationsExtImpl(rpc));
354
354
  const labelServiceExt = rpc.set(MAIN_RPC_CONTEXT.LABEL_SERVICE_EXT, new LabelServiceExtImpl(rpc));
@@ -462,7 +462,7 @@ export function createAPIFactory(
462
462
  const showErrorMessage = messageRegistryExt.showMessage.bind(messageRegistryExt, MainMessageType.Error);
463
463
  const window: typeof theia.window = {
464
464
 
465
- get activeTerminal(): TerminalExtImpl | undefined {
465
+ get activeTerminal(): theia.Terminal | undefined {
466
466
  return terminalExt.activeTerminal;
467
467
  },
468
468
  get activeTextEditor(): TextEditorExt | undefined {
@@ -471,7 +471,7 @@ export function createAPIFactory(
471
471
  get visibleTextEditors(): theia.TextEditor[] {
472
472
  return editors.getVisibleTextEditors();
473
473
  },
474
- get terminals(): TerminalExtImpl[] {
474
+ get terminals(): theia.Terminal[] {
475
475
  return terminalExt.terminals;
476
476
  },
477
477
  onDidChangeActiveTerminal,
@@ -633,7 +633,7 @@ export function createAPIFactory(
633
633
  createTerminal(nameOrOptions: theia.TerminalOptions | theia.ExtensionTerminalOptions | theia.ExtensionTerminalOptions | (string | undefined),
634
634
  shellPath?: string,
635
635
  shellArgs?: string[] | string): theia.Terminal {
636
- return createAPIObject(terminalExt.createTerminal(plugin, nameOrOptions, shellPath, shellArgs));
636
+ return terminalExt.createTerminal(plugin, nameOrOptions, shellPath, shellArgs, createAPIObject);
637
637
  },
638
638
  onDidChangeTerminalState,
639
639
  onDidCloseTerminal,
@@ -1286,8 +1286,8 @@ export function createAPIFactory(
1286
1286
  throw new Error('Input box not found!');
1287
1287
  }
1288
1288
  },
1289
- createSourceControl(id: string, label: string, rootUri?: URI, iconPath?: theia.IconPath, parent?: theia.SourceControl): theia.SourceControl {
1290
- return createAPIObject(scmExt.createSourceControl(plugin, id, label, rootUri, iconPath, parent));
1289
+ createSourceControl(id: string, label: string, rootUri?: URI, iconPath?: theia.IconPath, isHidden?: boolean, parent?: theia.SourceControl): theia.SourceControl {
1290
+ return scmExt.createSourceControl(plugin, id, label, rootUri, iconPath, isHidden, parent);
1291
1291
  }
1292
1292
  };
1293
1293