@theia/plugin-ext 1.71.0-next.4 → 1.71.0-next.43

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 (77) hide show
  1. package/lib/common/plugin-api-rpc.d.ts +5 -0
  2. package/lib/common/plugin-api-rpc.d.ts.map +1 -1
  3. package/lib/common/plugin-api-rpc.js +1 -0
  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/plugin-menu-command-adapter.js +1 -1
  16. package/lib/main/browser/menus/plugin-menu-command-adapter.js.map +1 -1
  17. package/lib/main/browser/menus/vscode-theia-menu-mappings.d.ts +1 -1
  18. package/lib/main/browser/menus/vscode-theia-menu-mappings.d.ts.map +1 -1
  19. package/lib/main/browser/menus/vscode-theia-menu-mappings.js +2 -2
  20. package/lib/main/browser/menus/vscode-theia-menu-mappings.js.map +1 -1
  21. package/lib/main/browser/test-main.d.ts +3 -2
  22. package/lib/main/browser/test-main.d.ts.map +1 -1
  23. package/lib/main/browser/test-main.js +12 -1
  24. package/lib/main/browser/test-main.js.map +1 -1
  25. package/lib/plugin/file-system-event-service-ext-impl.d.ts +11 -5
  26. package/lib/plugin/file-system-event-service-ext-impl.d.ts.map +1 -1
  27. package/lib/plugin/file-system-event-service-ext-impl.js +28 -9
  28. package/lib/plugin/file-system-event-service-ext-impl.js.map +1 -1
  29. package/lib/plugin/plugin-context.js +4 -4
  30. package/lib/plugin/plugin-context.js.map +1 -1
  31. package/lib/plugin/scm.d.ts +1 -1
  32. package/lib/plugin/scm.d.ts.map +1 -1
  33. package/lib/plugin/scm.js +6 -5
  34. package/lib/plugin/scm.js.map +1 -1
  35. package/lib/plugin/terminal-ext.d.ts +13 -3
  36. package/lib/plugin/terminal-ext.d.ts.map +1 -1
  37. package/lib/plugin/terminal-ext.js +51 -10
  38. package/lib/plugin/terminal-ext.js.map +1 -1
  39. package/lib/plugin/terminal-ext.spec.d.ts +2 -0
  40. package/lib/plugin/terminal-ext.spec.d.ts.map +1 -0
  41. package/lib/plugin/terminal-ext.spec.js +285 -0
  42. package/lib/plugin/terminal-ext.spec.js.map +1 -0
  43. package/lib/plugin/test-item.d.ts.map +1 -1
  44. package/lib/plugin/test-item.js +8 -3
  45. package/lib/plugin/test-item.js.map +1 -1
  46. package/lib/plugin/tests.d.ts.map +1 -1
  47. package/lib/plugin/tests.js +15 -3
  48. package/lib/plugin/tests.js.map +1 -1
  49. package/lib/plugin/type-converters.d.ts +2 -2
  50. package/lib/plugin/type-converters.d.ts.map +1 -1
  51. package/lib/plugin/type-converters.js +3 -9
  52. package/lib/plugin/type-converters.js.map +1 -1
  53. package/lib/plugin/types-impl.d.ts +1 -1
  54. package/lib/plugin/types-impl.d.ts.map +1 -1
  55. package/lib/plugin/types-impl.js +1 -1
  56. package/lib/plugin/types-impl.js.map +1 -1
  57. package/lib/plugin/workspace.d.ts.map +1 -1
  58. package/lib/plugin/workspace.js +17 -3
  59. package/lib/plugin/workspace.js.map +1 -1
  60. package/package.json +38 -38
  61. package/src/common/plugin-api-rpc.ts +6 -0
  62. package/src/hosted/browser/hosted-plugin.ts +13 -6
  63. package/src/main/browser/main-context.ts +3 -7
  64. package/src/main/browser/main-file-system-event-service.ts +26 -6
  65. package/src/main/browser/menus/plugin-menu-command-adapter.ts +1 -1
  66. package/src/main/browser/menus/vscode-theia-menu-mappings.ts +3 -3
  67. package/src/main/browser/test-main.ts +13 -3
  68. package/src/plugin/file-system-event-service-ext-impl.ts +40 -14
  69. package/src/plugin/plugin-context.ts +7 -7
  70. package/src/plugin/scm.ts +8 -4
  71. package/src/plugin/terminal-ext.spec.ts +350 -0
  72. package/src/plugin/terminal-ext.ts +58 -12
  73. package/src/plugin/test-item.ts +8 -3
  74. package/src/plugin/tests.ts +14 -3
  75. package/src/plugin/type-converters.ts +7 -13
  76. package/src/plugin/types-impl.ts +2 -2
  77. package/src/plugin/workspace.ts +17 -3
package/package.json CHANGED
@@ -1,55 +1,55 @@
1
1
  {
2
2
  "name": "@theia/plugin-ext",
3
- "version": "1.71.0-next.4+cdbc982c6",
3
+ "version": "1.71.0-next.43+e960af22f",
4
4
  "description": "Theia - Plugin Extension",
5
5
  "main": "lib/common/index.js",
6
6
  "typings": "lib/common/index.d.ts",
7
7
  "dependencies": {
8
- "@theia/ai-mcp": "1.71.0-next.4+cdbc982c6",
9
- "@theia/bulk-edit": "1.71.0-next.4+cdbc982c6",
10
- "@theia/callhierarchy": "1.71.0-next.4+cdbc982c6",
11
- "@theia/console": "1.71.0-next.4+cdbc982c6",
12
- "@theia/core": "1.71.0-next.4+cdbc982c6",
13
- "@theia/debug": "1.71.0-next.4+cdbc982c6",
14
- "@theia/editor": "1.71.0-next.4+cdbc982c6",
15
- "@theia/editor-preview": "1.71.0-next.4+cdbc982c6",
16
- "@theia/file-search": "1.71.0-next.4+cdbc982c6",
17
- "@theia/filesystem": "1.71.0-next.4+cdbc982c6",
18
- "@theia/markers": "1.71.0-next.4+cdbc982c6",
19
- "@theia/messages": "1.71.0-next.4+cdbc982c6",
20
- "@theia/monaco": "1.71.0-next.4+cdbc982c6",
8
+ "@theia/ai-mcp": "1.71.0-next.43+e960af22f",
9
+ "@theia/bulk-edit": "1.71.0-next.43+e960af22f",
10
+ "@theia/callhierarchy": "1.71.0-next.43+e960af22f",
11
+ "@theia/console": "1.71.0-next.43+e960af22f",
12
+ "@theia/core": "1.71.0-next.43+e960af22f",
13
+ "@theia/debug": "1.71.0-next.43+e960af22f",
14
+ "@theia/editor": "1.71.0-next.43+e960af22f",
15
+ "@theia/editor-preview": "1.71.0-next.43+e960af22f",
16
+ "@theia/file-search": "1.71.0-next.43+e960af22f",
17
+ "@theia/filesystem": "1.71.0-next.43+e960af22f",
18
+ "@theia/markers": "1.71.0-next.43+e960af22f",
19
+ "@theia/messages": "1.71.0-next.43+e960af22f",
20
+ "@theia/monaco": "1.71.0-next.43+e960af22f",
21
21
  "@theia/monaco-editor-core": "1.108.201",
22
- "@theia/navigator": "1.71.0-next.4+cdbc982c6",
23
- "@theia/notebook": "1.71.0-next.4+cdbc982c6",
24
- "@theia/output": "1.71.0-next.4+cdbc982c6",
25
- "@theia/plugin": "1.71.0-next.4+cdbc982c6",
26
- "@theia/preferences": "1.71.0-next.4+cdbc982c6",
27
- "@theia/scm": "1.71.0-next.4+cdbc982c6",
28
- "@theia/search-in-workspace": "1.71.0-next.4+cdbc982c6",
29
- "@theia/task": "1.71.0-next.4+cdbc982c6",
30
- "@theia/terminal": "1.71.0-next.4+cdbc982c6",
31
- "@theia/test": "1.71.0-next.4+cdbc982c6",
32
- "@theia/timeline": "1.71.0-next.4+cdbc982c6",
33
- "@theia/typehierarchy": "1.71.0-next.4+cdbc982c6",
34
- "@theia/variable-resolver": "1.71.0-next.4+cdbc982c6",
35
- "@theia/workspace": "1.71.0-next.4+cdbc982c6",
36
- "@types/mime": "^2.0.1",
37
- "@vscode/debugprotocol": "^1.51.0",
22
+ "@theia/navigator": "1.71.0-next.43+e960af22f",
23
+ "@theia/notebook": "1.71.0-next.43+e960af22f",
24
+ "@theia/output": "1.71.0-next.43+e960af22f",
25
+ "@theia/plugin": "1.71.0-next.43+e960af22f",
26
+ "@theia/preferences": "1.71.0-next.43+e960af22f",
27
+ "@theia/scm": "1.71.0-next.43+e960af22f",
28
+ "@theia/search-in-workspace": "1.71.0-next.43+e960af22f",
29
+ "@theia/task": "1.71.0-next.43+e960af22f",
30
+ "@theia/terminal": "1.71.0-next.43+e960af22f",
31
+ "@theia/test": "1.71.0-next.43+e960af22f",
32
+ "@theia/timeline": "1.71.0-next.43+e960af22f",
33
+ "@theia/typehierarchy": "1.71.0-next.43+e960af22f",
34
+ "@theia/variable-resolver": "1.71.0-next.43+e960af22f",
35
+ "@theia/workspace": "1.71.0-next.43+e960af22f",
36
+ "@types/mime": "^2.0.3",
37
+ "@vscode/debugprotocol": "^1.68.0",
38
38
  "@vscode/proxy-agent": "^0.13.2",
39
- "async-mutex": "^0.4.0",
39
+ "async-mutex": "^0.4.1",
40
40
  "decompress": "^4.2.1",
41
41
  "escape-html": "^1.0.3",
42
- "filenamify": "^4.1.0",
42
+ "filenamify": "^4.3.0",
43
43
  "is-electron": "^2.2.0",
44
- "jsonc-parser": "^2.2.0",
44
+ "jsonc-parser": "^2.3.1",
45
45
  "lodash.clonedeep": "^4.5.0",
46
- "macaddress": "^0.5.3",
47
- "mime": "^2.4.4",
46
+ "macaddress": "^0.5.4",
47
+ "mime": "^2.6.0",
48
48
  "node-pty": "1.2.0-beta.12",
49
- "semver": "^7.5.4",
49
+ "semver": "^7.7.4",
50
50
  "tslib": "^2.6.2",
51
51
  "vhost": "^3.0.2",
52
- "vscode-textmate": "^9.2.0"
52
+ "vscode-textmate": "^9.3.2"
53
53
  },
54
54
  "publishConfig": {
55
55
  "access": "public"
@@ -97,5 +97,5 @@
97
97
  "nyc": {
98
98
  "extends": "../../configs/nyc.json"
99
99
  },
100
- "gitHead": "cdbc982c6456e257ba7ed8e01b8c71aa7ac67283"
100
+ "gitHead": "e960af22fd0721f7cd4a8f7d66e6e9d10c801962"
101
101
  }
@@ -2120,6 +2120,11 @@ export interface ExtHostFileSystemEventServiceShape {
2120
2120
  $onDidRunFileOperation(operation: files.FileOperation, target: UriComponents, source: UriComponents | undefined): void;
2121
2121
  }
2122
2122
 
2123
+ export interface MainFileSystemEventServiceShape {
2124
+ $watch(session: number, resource: UriComponents, opts: files.WatchOptions): void;
2125
+ $unwatch(session: number): void;
2126
+ }
2127
+
2123
2128
  export interface ClipboardMain {
2124
2129
  $readText(): Promise<string>;
2125
2130
  $writeText(value: string): Promise<void>;
@@ -2384,6 +2389,7 @@ export const PLUGIN_RPC_CONTEXT = {
2384
2389
  TASKS_MAIN: createProxyIdentifier<TasksMain>('TasksMain'),
2385
2390
  DEBUG_MAIN: createProxyIdentifier<DebugMain>('DebugMain'),
2386
2391
  FILE_SYSTEM_MAIN: createProxyIdentifier<FileSystemMain>('FileSystemMain'),
2392
+ FILE_SYSTEM_EVENT_SERVICE_MAIN: createProxyIdentifier<MainFileSystemEventServiceShape>('FileSystemEventServiceMain'),
2387
2393
  SCM_MAIN: createProxyIdentifier<ScmMain>('ScmMain'),
2388
2394
  SECRETS_MAIN: createProxyIdentifier<SecretsMain>('SecretsMain'),
2389
2395
  DECORATIONS_MAIN: createProxyIdentifier<DecorationsMain>('DecorationsMain'),
@@ -267,10 +267,12 @@ export class HostedPluginSupport extends AbstractHostedPluginSupport<PluginManag
267
267
  }
268
268
 
269
269
  protected override async beforeLoadContributions(toDisconnect: DisposableCollection): Promise<void> {
270
- // make sure that the previous state, including plugin widgets, is restored
271
- // and core layout is initialized, i.e. explorer, scm, debug views are already added to the shell
272
- // but shell is not yet revealed
273
- await this.appState.reachedState('initialized_layout');
270
+ // Make sure the shell is attached so that registries (commands, menus, views, etc.)
271
+ // are ready to accept contributions. We intentionally do NOT wait for initialized_layout
272
+ // here, because layout restoration may depend on plugin-provided file system providers
273
+ // (e.g. git: scheme for merge editors), and those providers are registered during
274
+ // startPlugins which runs after this point. Waiting for initialized_layout would deadlock.
275
+ await this.appState.reachedState('attached_shell');
274
276
  this.workspaceTrusted = await this.workspaceTrustService.getWorkspaceTrust();
275
277
  }
276
278
 
@@ -464,12 +466,17 @@ export class HostedPluginSupport extends AbstractHostedPluginSupport<PluginManag
464
466
  }
465
467
 
466
468
  protected ensureFileSystemActivation(event: FileSystemProviderActivationEvent): void {
467
- event.waitUntil(this.activateByFileSystem(event).then(() => {
469
+ event.waitUntil((async () => {
470
+ // Wait until plugins are synced so that activation events are recorded
471
+ // and will be replayed when managers start. This does not depend on
472
+ // layout initialization, so it cannot deadlock.
473
+ await this.willStart;
474
+ await this.activateByFileSystem(event);
468
475
  if (!this.fileService.hasProvider(event.scheme)) {
469
476
  return waitForEvent(Event.filter(this.fileService.onDidChangeFileSystemProviderRegistrations,
470
477
  ({ added, scheme }) => added && scheme === event.scheme), 3000);
471
478
  }
472
- }));
479
+ })());
473
480
  }
474
481
 
475
482
  protected ensureCommandHandlerRegistration(event: WillExecuteCommandEvent): void {
@@ -171,15 +171,11 @@ export function setUpPluginApi(rpc: RPCProtocol, container: interfaces.Container
171
171
  rpc.set(PLUGIN_RPC_CONTEXT.DEBUG_MAIN, debugMain);
172
172
 
173
173
  const fs = new FileSystemMainImpl(rpc, container);
174
- const fsEventService = new MainFileSystemEventService(rpc, container);
175
- const disposeFS = fs.dispose.bind(fs);
176
- fs.dispose = () => {
177
- fsEventService.dispose();
178
- disposeFS();
179
- };
180
-
181
174
  rpc.set(PLUGIN_RPC_CONTEXT.FILE_SYSTEM_MAIN, fs);
182
175
 
176
+ const fsEventService = new MainFileSystemEventService(rpc, container);
177
+ rpc.set(PLUGIN_RPC_CONTEXT.FILE_SYSTEM_EVENT_SERVICE_MAIN, fsEventService);
178
+
183
179
  const scmMain = new ScmMainImpl(rpc, container);
184
180
  rpc.set(PLUGIN_RPC_CONTEXT.SCM_MAIN, scmMain);
185
181
 
@@ -21,21 +21,24 @@
21
21
 
22
22
  import { interfaces } from '@theia/core/shared/inversify';
23
23
  import { RPCProtocol } from '../../common/rpc-protocol';
24
- import { MAIN_RPC_CONTEXT, FileSystemEvents } from '../../common/plugin-api-rpc';
25
- import { DisposableCollection } from '@theia/core/lib/common/disposable';
24
+ import { MAIN_RPC_CONTEXT, FileSystemEvents, MainFileSystemEventServiceShape } from '../../common/plugin-api-rpc';
25
+ import { UriComponents } from '../../common/uri-components';
26
+ import { URI } from '@theia/core';
27
+ import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable';
26
28
  import { FileService } from '@theia/filesystem/lib/browser/file-service';
27
- import { FileChangeType } from '@theia/filesystem/lib/common/files';
29
+ import { FileChangeType, WatchOptions } from '@theia/filesystem/lib/common/files';
28
30
 
29
- export class MainFileSystemEventService {
31
+ export class MainFileSystemEventService implements MainFileSystemEventServiceShape {
30
32
 
31
33
  private readonly toDispose = new DisposableCollection();
34
+ private readonly watches = new Map<number, Disposable>();
32
35
 
33
36
  constructor(
34
37
  rpc: RPCProtocol,
35
- container: interfaces.Container
38
+ container: interfaces.Container,
39
+ private readonly fileService = container.get(FileService)
36
40
  ) {
37
41
  const proxy = rpc.getProxy(MAIN_RPC_CONTEXT.ExtHostFileSystemEventService);
38
- const fileService = container.get(FileService);
39
42
 
40
43
  this.toDispose.push(fileService.onDidFilesChange(event => {
41
44
  // file system events - (changes the editor and others make)
@@ -73,4 +76,21 @@ export class MainFileSystemEventService {
73
76
  dispose(): void {
74
77
  this.toDispose.dispose();
75
78
  }
79
+
80
+ $watch(session: number, resource: UriComponents, options: WatchOptions): void {
81
+ if (this.watches.has(session)) {
82
+ throw new Error(`There is already a watch request for the key ${session}`);
83
+ }
84
+ const watch = this.fileService.watch(URI.fromComponents(resource), options);
85
+ this.toDispose.push(watch);
86
+ this.watches.set(session, watch);
87
+ }
88
+
89
+ $unwatch(session: number): void {
90
+ const watch = this.watches.get(session);
91
+ if (watch) {
92
+ watch.dispose();
93
+ this.watches.delete(session);
94
+ }
95
+ }
76
96
  }
@@ -69,9 +69,9 @@ export class PluginMenuCommandAdapter {
69
69
  ['scm/resourceFolder/context', toScmArgs],
70
70
  ['scm/resourceGroup/context', toScmArgs],
71
71
  ['scm/resourceState/context', toScmArgs],
72
+ ['scm/repository', toScmArgs],
72
73
  ['scm/title', () => [this.toScmArg(this.scmService.selectedRepository)]],
73
74
  ['scm/sourceControl', toScmArgs],
74
- ['scm/sourceControl/context', toScmArgs],
75
75
  ['scm/sourceControl/title', () => [this.toScmArg(this.scmService.selectedRepository)]],
76
76
  ['testing/message/context', toTestMessageArgs],
77
77
  ['testing/profiles/context', noArgs],
@@ -27,7 +27,7 @@ import { NAVIGATOR_CONTEXT_MENU } from '@theia/navigator/lib/browser/navigator-c
27
27
  import { ScmTreeWidget } from '@theia/scm/lib/browser/scm-tree-widget';
28
28
  import { PLUGIN_SCM_CHANGE_TITLE_MENU } from '@theia/scm/lib/browser/dirty-diff/dirty-diff-widget';
29
29
  import {
30
- SCM_SOURCE_CONTROL_CONTEXT_MENU, SCM_SOURCE_CONTROL_MENU, SCM_SOURCE_CONTROL_TITLE_MENU, SCM_TITLE_MENU
30
+ SCM_REPOSITORY_MENU, SCM_SOURCE_CONTROL_MENU, SCM_SOURCE_CONTROL_TITLE_MENU, SCM_TITLE_MENU
31
31
  } from '@theia/scm/lib/browser/scm-repositories-widget';
32
32
  import { TIMELINE_ITEM_CONTEXT_MENU } from '@theia/timeline/lib/browser/timeline-tree-widget';
33
33
  import { COMMENT_CONTEXT, COMMENT_THREAD_CONTEXT, COMMENT_TITLE } from '../comments/comment-thread-widget';
@@ -62,8 +62,8 @@ export const implementedVSCodeContributionPoints = [
62
62
  'scm/resourceFolder/context',
63
63
  'scm/resourceGroup/context',
64
64
  'scm/resourceState/context',
65
+ 'scm/repository',
65
66
  'scm/sourceControl',
66
- 'scm/sourceControl/context',
67
67
  'scm/sourceControl/title',
68
68
  'scm/title',
69
69
  'timeline/item/context',
@@ -100,8 +100,8 @@ export const codeToTheiaMappings = new Map<string, MenuPath[]>([
100
100
  ['scm/resourceFolder/context', [ScmTreeWidget.RESOURCE_FOLDER_CONTEXT_MENU]],
101
101
  ['scm/resourceGroup/context', [ScmTreeWidget.RESOURCE_GROUP_CONTEXT_MENU]],
102
102
  ['scm/resourceState/context', [ScmTreeWidget.RESOURCE_CONTEXT_MENU]],
103
+ ['scm/repository', [SCM_REPOSITORY_MENU]],
103
104
  ['scm/sourceControl', [SCM_SOURCE_CONTROL_MENU]],
104
- ['scm/sourceControl/context', [SCM_SOURCE_CONTROL_CONTEXT_MENU]],
105
105
  ['scm/sourceControl/title', [SCM_SOURCE_CONTROL_TITLE_MENU]],
106
106
  ['scm/title', [SCM_TITLE_MENU]],
107
107
  ['testing/item/context', [TEST_VIEW_CONTEXT_MENU]],
@@ -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
 
package/src/plugin/scm.ts CHANGED
@@ -543,6 +543,8 @@ class SourceControlImpl implements theia.SourceControl {
543
543
  private static handlePool: number = 0;
544
544
  private groups: Map<GroupHandle, ScmResourceGroupImpl> = new Map<GroupHandle, ScmResourceGroupImpl>();
545
545
 
546
+ readonly apiObject: theia.SourceControl;
547
+
546
548
  get id(): string {
547
549
  return this._id;
548
550
  }
@@ -702,11 +704,13 @@ class SourceControlImpl implements theia.SourceControl {
702
704
  private _label: string,
703
705
  private _rootUri?: theia.Uri,
704
706
  _iconPath?: theia.IconPath,
707
+ _isHidden?: boolean,
705
708
  _parent?: SourceControlImpl
706
709
  ) {
707
710
  this.inputBox = new ScmInputBoxImpl(plugin, this.proxy, this.handle);
708
711
  this.proxy.$registerSourceControl(this.handle, _id, _label, _rootUri, _parent?.handle);
709
712
  this.onDidDisposeParent = _parent ? _parent.onDidDispose : Event.None;
713
+ this.apiObject = createAPIObject(this);
710
714
  }
711
715
 
712
716
  private createdResourceGroups = new Map<ScmResourceGroupImpl, Disposable>();
@@ -824,7 +828,7 @@ export class ScmExtImpl implements ScmExt {
824
828
  return undefined;
825
829
  }
826
830
  if (typeof arg.resourceGroupHandle !== 'number') {
827
- return sourceControl;
831
+ return sourceControl.apiObject;
828
832
  }
829
833
  const resourceGroup = sourceControl.getResourceGroup(arg.resourceGroupHandle);
830
834
  if (typeof arg.resourceStateHandle !== 'number') {
@@ -836,17 +840,17 @@ export class ScmExtImpl implements ScmExt {
836
840
  }
837
841
 
838
842
  createSourceControl(extension: Plugin, id: string, label: string, rootUri: theia.Uri | undefined,
839
- iconPath?: theia.IconPath, parent?: theia.SourceControl): theia.SourceControl {
843
+ iconPath?: theia.IconPath, isHidden?: boolean, parent?: theia.SourceControl): theia.SourceControl {
840
844
  const handle = ScmExtImpl.handlePool++;
841
845
  const parentImpl = parent ? this.findSourceControlImpl(parent) : undefined;
842
- const sourceControl = new SourceControlImpl(extension, this.proxy, this.commands, id, label, rootUri, iconPath, parentImpl);
846
+ const sourceControl = new SourceControlImpl(extension, this.proxy, this.commands, id, label, rootUri, iconPath, isHidden, parentImpl);
843
847
  this.sourceControls.set(handle, sourceControl);
844
848
 
845
849
  const sourceControls = this.sourceControlsByExtension.get(extension.model.id) || [];
846
850
  sourceControls.push(sourceControl);
847
851
  this.sourceControlsByExtension.set(extension.model.id, sourceControls);
848
852
 
849
- return sourceControl;
853
+ return sourceControl.apiObject;
850
854
  }
851
855
 
852
856
  private findSourceControlImpl(apiObject: theia.SourceControl): SourceControlImpl | undefined {