@theia/plugin-ext 1.28.0-next.7 → 1.28.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 (155) hide show
  1. package/lib/common/plugin-api-rpc-model.d.ts +1 -0
  2. package/lib/common/plugin-api-rpc-model.d.ts.map +1 -1
  3. package/lib/common/plugin-api-rpc-model.js.map +1 -1
  4. package/lib/common/plugin-api-rpc.d.ts +3 -0
  5. package/lib/common/plugin-api-rpc.d.ts.map +1 -1
  6. package/lib/common/plugin-api-rpc.js.map +1 -1
  7. package/lib/common/plugin-protocol.d.ts +4 -0
  8. package/lib/common/plugin-protocol.d.ts.map +1 -1
  9. package/lib/common/plugin-protocol.js.map +1 -1
  10. package/lib/hosted/browser/hosted-plugin.d.ts.map +1 -1
  11. package/lib/hosted/browser/hosted-plugin.js +4 -2
  12. package/lib/hosted/browser/hosted-plugin.js.map +1 -1
  13. package/lib/hosted/browser/worker/worker-env-ext.d.ts +1 -0
  14. package/lib/hosted/browser/worker/worker-env-ext.d.ts.map +1 -1
  15. package/lib/hosted/browser/worker/worker-env-ext.js +3 -0
  16. package/lib/hosted/browser/worker/worker-env-ext.js.map +1 -1
  17. package/lib/hosted/node/scanners/scanner-theia.d.ts +5 -1
  18. package/lib/hosted/node/scanners/scanner-theia.d.ts.map +1 -1
  19. package/lib/hosted/node/scanners/scanner-theia.js +22 -15
  20. package/lib/hosted/node/scanners/scanner-theia.js.map +1 -1
  21. package/lib/main/browser/comments/comment-thread-widget.js +5 -5
  22. package/lib/main/browser/comments/comment-thread-widget.js.map +1 -1
  23. package/lib/main/browser/debug/debug-main.d.ts +1 -0
  24. package/lib/main/browser/debug/debug-main.d.ts.map +1 -1
  25. package/lib/main/browser/debug/debug-main.js +10 -0
  26. package/lib/main/browser/debug/debug-main.js.map +1 -1
  27. package/lib/main/browser/languages-main.d.ts.map +1 -1
  28. package/lib/main/browser/languages-main.js.map +1 -1
  29. package/lib/main/browser/menus/menus-contribution-handler.d.ts +24 -88
  30. package/lib/main/browser/menus/menus-contribution-handler.d.ts.map +1 -1
  31. package/lib/main/browser/menus/menus-contribution-handler.js +108 -518
  32. package/lib/main/browser/menus/menus-contribution-handler.js.map +1 -1
  33. package/lib/main/browser/menus/plugin-menu-command-adapter.d.ts +44 -0
  34. package/lib/main/browser/menus/plugin-menu-command-adapter.d.ts.map +1 -0
  35. package/lib/main/browser/menus/plugin-menu-command-adapter.js +274 -0
  36. package/lib/main/browser/menus/plugin-menu-command-adapter.js.map +1 -0
  37. package/lib/main/browser/menus/vscode-theia-menu-mappings.d.ts +18 -0
  38. package/lib/main/browser/menus/vscode-theia-menu-mappings.d.ts.map +1 -0
  39. package/lib/main/browser/menus/vscode-theia-menu-mappings.js +88 -0
  40. package/lib/main/browser/menus/vscode-theia-menu-mappings.js.map +1 -0
  41. package/lib/main/browser/plugin-contribution-handler.d.ts.map +1 -1
  42. package/lib/main/browser/plugin-contribution-handler.js +9 -4
  43. package/lib/main/browser/plugin-contribution-handler.js.map +1 -1
  44. package/lib/main/browser/plugin-ext-frontend-module.d.ts.map +1 -1
  45. package/lib/main/browser/plugin-ext-frontend-module.js +4 -3
  46. package/lib/main/browser/plugin-ext-frontend-module.js.map +1 -1
  47. package/lib/main/browser/plugin-frontend-contribution.d.ts +0 -2
  48. package/lib/main/browser/plugin-frontend-contribution.d.ts.map +1 -1
  49. package/lib/main/browser/plugin-frontend-contribution.js +0 -8
  50. package/lib/main/browser/plugin-frontend-contribution.js.map +1 -1
  51. package/lib/main/browser/plugin-shared-style.d.ts.map +1 -1
  52. package/lib/main/browser/plugin-shared-style.js +0 -1
  53. package/lib/main/browser/plugin-shared-style.js.map +1 -1
  54. package/lib/main/browser/scm-main.d.ts +1 -0
  55. package/lib/main/browser/scm-main.d.ts.map +1 -1
  56. package/lib/main/browser/scm-main.js +7 -0
  57. package/lib/main/browser/scm-main.js.map +1 -1
  58. package/lib/main/browser/view/tree-view-widget.js +2 -2
  59. package/lib/main/browser/view/tree-view-widget.js.map +1 -1
  60. package/lib/main/node/handlers/plugin-theia-file-handler.d.ts +0 -4
  61. package/lib/main/node/handlers/plugin-theia-file-handler.d.ts.map +1 -1
  62. package/lib/main/node/handlers/plugin-theia-file-handler.js +0 -26
  63. package/lib/main/node/handlers/plugin-theia-file-handler.js.map +1 -1
  64. package/lib/main/node/plugin-ext-backend-module.d.ts.map +1 -1
  65. package/lib/main/node/plugin-ext-backend-module.js +0 -2
  66. package/lib/main/node/plugin-ext-backend-module.js.map +1 -1
  67. package/lib/main/node/resolvers/local-plugin-deployer-resolver.d.ts.map +1 -1
  68. package/lib/main/node/resolvers/local-plugin-deployer-resolver.js.map +1 -1
  69. package/lib/plugin/debug/debug-ext.d.ts +3 -1
  70. package/lib/plugin/debug/debug-ext.d.ts.map +1 -1
  71. package/lib/plugin/debug/debug-ext.js +26 -4
  72. package/lib/plugin/debug/debug-ext.js.map +1 -1
  73. package/lib/plugin/debug/plugin-debug-adapter-session.d.ts +1 -0
  74. package/lib/plugin/debug/plugin-debug-adapter-session.d.ts.map +1 -1
  75. package/lib/plugin/debug/plugin-debug-adapter-session.js +3 -0
  76. package/lib/plugin/debug/plugin-debug-adapter-session.js.map +1 -1
  77. package/lib/plugin/env.d.ts +11 -0
  78. package/lib/plugin/env.d.ts.map +1 -1
  79. package/lib/plugin/env.js +20 -0
  80. package/lib/plugin/env.js.map +1 -1
  81. package/lib/plugin/languages/signature.d.ts.map +1 -1
  82. package/lib/plugin/languages/signature.js +5 -1
  83. package/lib/plugin/languages/signature.js.map +1 -1
  84. package/lib/plugin/node/debug/debug.spec.d.ts +2 -0
  85. package/lib/plugin/node/debug/debug.spec.d.ts.map +1 -0
  86. package/lib/plugin/node/debug/debug.spec.js +68 -0
  87. package/lib/plugin/node/debug/debug.spec.js.map +1 -0
  88. package/lib/plugin/node/env-node-ext.d.ts +3 -0
  89. package/lib/plugin/node/env-node-ext.d.ts.map +1 -1
  90. package/lib/plugin/node/env-node-ext.js +11 -0
  91. package/lib/plugin/node/env-node-ext.js.map +1 -1
  92. package/lib/plugin/plugin-context.d.ts.map +1 -1
  93. package/lib/plugin/plugin-context.js +10 -0
  94. package/lib/plugin/plugin-context.js.map +1 -1
  95. package/lib/plugin/plugin-manager.d.ts.map +1 -1
  96. package/lib/plugin/plugin-manager.js +1 -0
  97. package/lib/plugin/plugin-manager.js.map +1 -1
  98. package/lib/plugin/quick-open.d.ts.map +1 -1
  99. package/lib/plugin/quick-open.js +1 -0
  100. package/lib/plugin/quick-open.js.map +1 -1
  101. package/lib/plugin/scm.d.ts +3 -0
  102. package/lib/plugin/scm.d.ts.map +1 -1
  103. package/lib/plugin/scm.js +8 -0
  104. package/lib/plugin/scm.js.map +1 -1
  105. package/lib/plugin/type-converters.d.ts.map +1 -1
  106. package/lib/plugin/type-converters.js +4 -2
  107. package/lib/plugin/type-converters.js.map +1 -1
  108. package/lib/plugin/types-impl.d.ts +4 -3
  109. package/lib/plugin/types-impl.d.ts.map +1 -1
  110. package/lib/plugin/types-impl.js +9 -8
  111. package/lib/plugin/types-impl.js.map +1 -1
  112. package/package.json +27 -27
  113. package/src/common/plugin-api-rpc-model.ts +1 -0
  114. package/src/common/plugin-api-rpc.ts +3 -0
  115. package/src/common/plugin-protocol.ts +4 -0
  116. package/src/hosted/browser/hosted-plugin.ts +4 -2
  117. package/src/hosted/browser/worker/worker-env-ext.ts +4 -0
  118. package/src/hosted/node/scanners/scanner-theia.ts +21 -15
  119. package/src/main/browser/comments/comment-thread-widget.tsx +5 -5
  120. package/src/main/browser/debug/debug-main.ts +9 -0
  121. package/src/main/browser/languages-main.ts +5 -0
  122. package/src/main/browser/menus/menus-contribution-handler.ts +104 -578
  123. package/src/main/browser/menus/plugin-menu-command-adapter.ts +259 -0
  124. package/src/main/browser/menus/vscode-theia-menu-mappings.ts +85 -0
  125. package/src/main/browser/plugin-contribution-handler.ts +3 -1
  126. package/src/main/browser/plugin-ext-frontend-module.ts +4 -3
  127. package/src/main/browser/plugin-frontend-contribution.ts +0 -9
  128. package/src/main/browser/plugin-shared-style.ts +0 -1
  129. package/src/main/browser/scm-main.ts +10 -0
  130. package/src/main/browser/view/tree-view-widget.tsx +2 -2
  131. package/src/main/node/handlers/plugin-theia-file-handler.ts +0 -26
  132. package/src/main/node/plugin-ext-backend-module.ts +0 -2
  133. package/src/main/node/resolvers/local-plugin-deployer-resolver.ts +4 -8
  134. package/src/plugin/debug/debug-ext.ts +30 -5
  135. package/src/plugin/debug/plugin-debug-adapter-session.ts +4 -0
  136. package/src/plugin/env.ts +30 -0
  137. package/src/plugin/languages/signature.ts +3 -1
  138. package/src/plugin/node/debug/debug.spec.ts +94 -0
  139. package/src/plugin/node/env-node-ext.ts +13 -1
  140. package/src/plugin/plugin-context.ts +10 -0
  141. package/src/plugin/plugin-manager.ts +1 -0
  142. package/src/plugin/quick-open.ts +1 -0
  143. package/src/plugin/scm.ts +11 -0
  144. package/src/plugin/type-converters.ts +4 -2
  145. package/src/plugin/types-impl.ts +7 -5
  146. package/lib/main/browser/plugin-ext-deploy-command.d.ts +0 -10
  147. package/lib/main/browser/plugin-ext-deploy-command.d.ts.map +0 -1
  148. package/lib/main/browser/plugin-ext-deploy-command.js +0 -64
  149. package/lib/main/browser/plugin-ext-deploy-command.js.map +0 -1
  150. package/lib/main/node/resolvers/local-file-plugin-deployer-resolver.d.ts +0 -8
  151. package/lib/main/node/resolvers/local-file-plugin-deployer-resolver.d.ts.map +0 -1
  152. package/lib/main/node/resolvers/local-file-plugin-deployer-resolver.js +0 -43
  153. package/lib/main/node/resolvers/local-file-plugin-deployer-resolver.js.map +0 -1
  154. package/src/main/browser/plugin-ext-deploy-command.ts +0 -50
  155. package/src/main/node/resolvers/local-file-plugin-deployer-resolver.ts +0 -34
@@ -16,623 +16,149 @@
16
16
 
17
17
  /* eslint-disable @typescript-eslint/no-explicit-any */
18
18
 
19
- import { URI as CodeUri } from '@theia/core/shared/vscode-uri';
20
- import { injectable, inject, optional } from '@theia/core/shared/inversify';
21
- import { MenuPath, ILogger, CommandRegistry, Command, Mutable, MenuAction, SelectionService, CommandHandler, Disposable, DisposableCollection } from '@theia/core';
22
- import { EDITOR_CONTEXT_MENU, EditorWidget } from '@theia/editor/lib/browser';
19
+ import { inject, injectable, optional } from '@theia/core/shared/inversify';
20
+ import { MenuPath, CommandRegistry, Disposable, DisposableCollection, ActionMenuNode, MenuCommandAdapterRegistry, Emitter, CompoundMenuNodeRole } from '@theia/core';
23
21
  import { MenuModelRegistry } from '@theia/core/lib/common';
24
- import { Emitter, Event } from '@theia/core/lib/common/event';
25
- import { TabBarToolbarRegistry, TabBarToolbarItem } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
26
- import { NAVIGATOR_CONTEXT_MENU } from '@theia/navigator/lib/browser/navigator-contribution';
27
- import { VIEW_ITEM_CONTEXT_MENU, TreeViewWidget, VIEW_ITEM_INLINE_MENU } from '../view/tree-view-widget';
28
- import { DeployedPlugin, Menu, ScmCommandArg, TimelineCommandArg, TreeViewSelection } from '../../../common';
29
- import { DebugStackFramesWidget } from '@theia/debug/lib/browser/view/debug-stack-frames-widget';
30
- import { DebugThreadsWidget } from '@theia/debug/lib/browser/view/debug-threads-widget';
31
- import { TreeWidgetSelection } from '@theia/core/lib/browser/tree/tree-widget-selection';
22
+ import { TabBarToolbarRegistry } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
23
+ import { DeployedPlugin, IconUrl, Menu } from '../../../common';
32
24
  import { ScmWidget } from '@theia/scm/lib/browser/scm-widget';
33
- import { ScmTreeWidget } from '@theia/scm/lib/browser/scm-tree-widget';
34
- import { ScmService } from '@theia/scm/lib/browser/scm-service';
35
- import { ScmRepository } from '@theia/scm/lib/browser/scm-repository';
36
- import { PluginScmProvider, PluginScmResourceGroup, PluginScmResource } from '../scm-main';
37
- import { ResourceContextKey } from '@theia/core/lib/browser/resource-context-key';
38
25
  import { PluginViewWidget } from '../view/plugin-view-widget';
39
- import { ViewContextKeyService } from '../view/view-context-key-service';
40
- import { WebviewWidget } from '../webview/webview';
41
- import { Navigatable } from '@theia/core/lib/browser/navigatable';
42
- import { ContextKeyService } from '@theia/core/lib/browser/context-key-service';
43
- import { TIMELINE_ITEM_CONTEXT_MENU } from '@theia/timeline/lib/browser/timeline-tree-widget';
44
- import { TimelineItem } from '@theia/timeline/lib/common/timeline-model';
45
- import { COMMENT_CONTEXT, COMMENT_THREAD_CONTEXT, COMMENT_TITLE } from '../comments/comment-thread-widget';
46
26
  import { QuickCommandService } from '@theia/core/lib/browser';
47
-
48
- type CodeEditorWidget = EditorWidget | WebviewWidget;
49
- @injectable()
50
- export class CodeEditorWidgetUtil {
51
- is(arg: any): arg is CodeEditorWidget {
52
- return arg instanceof EditorWidget || arg instanceof WebviewWidget;
53
- }
54
- getResourceUri(editor: CodeEditorWidget): CodeUri | undefined {
55
- const resourceUri = Navigatable.is(editor) && editor.getResourceUri();
56
- return resourceUri ? resourceUri['codeUri'] : undefined;
57
- }
58
- }
27
+ import {
28
+ CodeEditorWidgetUtil, codeToTheiaMappings, ContributionPoint, implementedVSCodeContributionPoints,
29
+ PLUGIN_EDITOR_TITLE_MENU, PLUGIN_SCM_TITLE_MENU, PLUGIN_VIEW_TITLE_MENU
30
+ } from './vscode-theia-menu-mappings';
31
+ import { PluginMenuCommandAdapter, ReferenceCountingSet } from './plugin-menu-command-adapter';
32
+ import { ContextKeyExpr } from '@theia/monaco-editor-core/esm/vs/platform/contextkey/common/contextkey';
33
+ import { ContextKeyService } from '@theia/core/lib/browser/context-key-service';
34
+ import { PluginSharedStyle } from '../plugin-shared-style';
35
+ import { ThemeIcon } from '@theia/monaco-editor-core/esm/vs/platform/theme/common/themeService';
59
36
 
60
37
  @injectable()
61
38
  export class MenusContributionPointHandler {
62
39
 
63
- @inject(MenuModelRegistry)
64
- protected readonly menuRegistry: MenuModelRegistry;
65
-
66
- @inject(CommandRegistry)
67
- protected readonly commands: CommandRegistry;
68
-
69
- @inject(ILogger)
70
- protected readonly logger: ILogger;
71
-
72
- @inject(ScmService)
73
- protected readonly scmService: ScmService;
74
-
40
+ @inject(MenuModelRegistry) private readonly menuRegistry: MenuModelRegistry;
41
+ @inject(CommandRegistry) private readonly commands: CommandRegistry;
42
+ @inject(TabBarToolbarRegistry) private readonly tabBarToolbar: TabBarToolbarRegistry;
43
+ @inject(CodeEditorWidgetUtil) private readonly codeEditorWidgetUtil: CodeEditorWidgetUtil;
44
+ @inject(PluginMenuCommandAdapter) protected readonly commandAdapter: PluginMenuCommandAdapter;
45
+ @inject(MenuCommandAdapterRegistry) protected readonly commandAdapterRegistry: MenuCommandAdapterRegistry;
46
+ @inject(ContextKeyService) protected readonly contextKeyService: ContextKeyService;
47
+ @inject(PluginSharedStyle) protected readonly style: PluginSharedStyle;
75
48
  @inject(QuickCommandService) @optional()
76
- protected readonly quickCommandService: QuickCommandService;
77
-
78
- @inject(TabBarToolbarRegistry)
79
- protected readonly tabBarToolbar: TabBarToolbarRegistry;
80
-
81
- @inject(SelectionService)
82
- protected readonly selectionService: SelectionService;
83
-
84
- @inject(ResourceContextKey)
85
- protected readonly resourceContextKey: ResourceContextKey;
86
-
87
- @inject(ViewContextKeyService)
88
- protected readonly viewContextKeys: ViewContextKeyService;
89
-
90
- @inject(ContextKeyService)
91
- protected readonly contextKeyService: ContextKeyService;
92
-
93
- @inject(CodeEditorWidgetUtil)
94
- protected readonly codeEditorWidgetUtil: CodeEditorWidgetUtil;
95
-
96
- handle(plugin: DeployedPlugin): Disposable {
97
- const allMenus = plugin.contributes && plugin.contributes.menus;
98
- if (!allMenus) {
99
- return Disposable.NULL;
100
- }
101
- const toDispose = new DisposableCollection();
102
-
103
- const tree = this.getMenusTree(plugin);
104
- tree.forEach(rootMenu => {
105
-
106
- const registerMenuActions = (menus: MenuTree[], group: string | undefined, submenusOrder: string | undefined = '') => {
107
- menus.forEach(menu => {
108
- if (group) {
109
- // Adding previous group to the start of current menu group.
110
- menu.group = `${group}/${menu.group || '_'}`;
111
- }
112
- if (menu.isSubmenu) {
113
- let [submenuGroup, submenuOrder = ''] = (menu.group || '_').split('@');
114
- // Generating group in format: `<submenu group>/<submenu name>`
115
- submenuGroup = `${submenuGroup}/${menu.label}`;
116
- if (submenusOrder) {
117
- // Adding previous submenus order to the start of current submenu order
118
- // in format: `<submenu order>/<submenu order>.../<menu order>`.
119
- submenuOrder = `${submenusOrder}/${submenuOrder}`;
120
- }
121
- registerMenuActions(menu.children, submenuGroup, submenuOrder);
122
- } else {
123
- menu.submenusOrder = submenusOrder;
124
- toDispose.push(
125
- this.registerAction(plugin, rootMenu.id!, menu)
126
- );
127
- }
128
- });
129
- };
130
-
131
- registerMenuActions(rootMenu.children, undefined, undefined);
132
- });
133
-
134
- return toDispose;
135
- }
136
-
137
- /**
138
- * Transforms the structure of Menus & Submenus
139
- * into something more tree-like.
140
- */
141
- protected getMenusTree(plugin: DeployedPlugin): MenuTree[] {
142
- const allMenus = plugin.contributes && plugin.contributes.menus;
143
- if (!allMenus) {
144
- return [];
145
- }
146
- const allSubmenus = plugin.contributes && plugin.contributes.submenus;
147
- const tree: MenuTree[] = [];
148
-
149
- Object.keys(allMenus).forEach(location => {
150
- // Don't build menus tree for a submenu declaration at root.
151
- if (allSubmenus && allSubmenus.findIndex(submenu => submenu.id === location) > -1) {
152
- return;
49
+ private readonly quickCommandService: QuickCommandService;
50
+
51
+ protected readonly titleContributionContextKeys = new ReferenceCountingSet();
52
+ protected readonly onDidChangeTitleContributionEmitter = new Emitter<void>();
53
+
54
+ private initialized = false;
55
+ private initialize(): void {
56
+ this.initialized = true;
57
+ this.commandAdapterRegistry.registerAdapter(this.commandAdapter);
58
+ for (const contributionPoint of implementedVSCodeContributionPoints) {
59
+ this.menuRegistry.registerIndependentSubmenu(contributionPoint, '');
60
+ this.getMatchingMenu(contributionPoint)!.forEach(([menu, when]) => this.menuRegistry.linkSubmenu(menu, contributionPoint, { role: CompoundMenuNodeRole.Flat, when }));
61
+ }
62
+ this.tabBarToolbar.registerMenuDelegate(PLUGIN_EDITOR_TITLE_MENU, widget => this.codeEditorWidgetUtil.is(widget));
63
+ this.tabBarToolbar.registerMenuDelegate(PLUGIN_SCM_TITLE_MENU, widget => widget instanceof ScmWidget);
64
+ this.tabBarToolbar.registerMenuDelegate(PLUGIN_VIEW_TITLE_MENU, widget => widget instanceof PluginViewWidget);
65
+ this.tabBarToolbar.registerItem({ id: 'plugin-menu-contribution-title-contribution', command: '_never_', onDidChange: this.onDidChangeTitleContributionEmitter.event });
66
+ this.contextKeyService.onDidChange(event => {
67
+ if (event.affects(this.titleContributionContextKeys)) {
68
+ this.onDidChangeTitleContributionEmitter.fire();
153
69
  }
154
-
155
- /**
156
- * @param menus the menus to create a tree from.
157
- * @param submenusIds contains all the previous submenus ids in the current tree.
158
- * @returns {MenuTree[]} the trees for the given menus.
159
- */
160
- const getChildren = (menus: Menu[], submenusIds: Set<string>) => {
161
- // Contains all the submenus ids of the current parent.
162
- const parentSubmenusIds = new Set<string>();
163
-
164
- return menus.reduce((children: MenuTree[], menuItem) => {
165
- if (menuItem.submenu) {
166
- if (parentSubmenusIds.has(menuItem.submenu)) {
167
- console.warn(`Submenu ${menuItem.submenu} already registered`);
168
- } else if (submenusIds.has(menuItem.submenu)) {
169
- console.warn(`Found submenu cycle: ${menuItem.submenu}`);
170
- } else {
171
- parentSubmenusIds.add(menuItem.submenu);
172
- const submenu = allSubmenus!.find(s => s.id === menuItem.submenu)!;
173
- const menuTree = new MenuTree({ ...menuItem }, menuItem.submenu, submenu.label);
174
- menuTree.children = getChildren(allMenus[submenu.id], new Set([...submenusIds, menuItem.submenu]));
175
- children.push(menuTree);
176
- }
177
- } else {
178
- children.push(new MenuTree({ ...menuItem }));
179
- }
180
- return children;
181
- }, []);
182
- };
183
-
184
- const rootMenu = new MenuTree(undefined, location);
185
- rootMenu.children = getChildren(allMenus[location], new Set());
186
- tree.push(rootMenu);
187
70
  });
71
+ }
188
72
 
189
- return tree;
73
+ private getMatchingMenu(contributionPoint: ContributionPoint): Array<[MenuPath] | [MenuPath, string]> | undefined {
74
+ return codeToTheiaMappings.get(contributionPoint);
190
75
  }
191
76
 
192
- protected registerAction(plugin: DeployedPlugin, location: string, action: MenuTree): Disposable {
193
- const allMenus = plugin.contributes && plugin.contributes.menus;
77
+ handle(plugin: DeployedPlugin): Disposable {
78
+ const allMenus = plugin.contributes?.menus;
194
79
  if (!allMenus) {
195
80
  return Disposable.NULL;
196
81
  }
197
-
198
- switch (location) {
199
- case 'commandPalette': return this.registerCommandPaletteAction(action);
200
- case 'editor/title': return this.registerEditorTitleAction(location, action);
201
- case 'view/title': return this.registerViewTitleAction(location, action);
202
- case 'view/item/context': return this.registerViewItemContextAction(action);
203
- case 'scm/title': return this.registerScmTitleAction(location, action);
204
- case 'scm/resourceGroup/context': return this.registerScmResourceGroupAction(action);
205
- case 'scm/resourceFolder/context': return this.registerScmResourceFolderAction(action);
206
- case 'scm/resourceState/context': return this.registerScmResourceStateAction(action);
207
- case 'timeline/item/context': return this.registerTimelineItemAction(action);
208
- case 'comments/commentThread/context': return this.registerCommentThreadAction(action, plugin);
209
- case 'comments/comment/title': return this.registerCommentTitleAction(action);
210
- case 'comments/comment/context': return this.registerCommentContextAction(action);
211
- case 'debug/callstack/context': return this.registerDebugCallstackAction(action);
212
-
213
- default: if (allMenus.hasOwnProperty(location)) {
214
- return this.registerGlobalMenuAction(action, location, plugin);
215
- }
216
- return Disposable.NULL;
217
- }
218
- }
219
-
220
- protected static parseMenuPaths(value: string): MenuPath[] {
221
- switch (value) {
222
- case 'editor/context': return [EDITOR_CONTEXT_MENU];
223
- case 'explorer/context': return [NAVIGATOR_CONTEXT_MENU];
82
+ if (!this.initialized) {
83
+ this.initialize();
224
84
  }
225
- return [];
226
- }
227
-
228
- protected registerCommandPaletteAction(menu: Menu): Disposable {
229
- if (menu.command && menu.when) {
230
- return this.quickCommandService.pushCommandContext(menu.command, menu.when);
85
+ const toDispose = new DisposableCollection();
86
+ const submenus = plugin.contributes?.submenus ?? [];
87
+ for (const submenu of submenus) {
88
+ const iconClass = submenu.icon && this.toIconClass(submenu.icon, toDispose);
89
+ this.menuRegistry.registerIndependentSubmenu(submenu.id, submenu.label, iconClass ? { iconClass } : undefined);
231
90
  }
232
- return Disposable.NULL;
233
- }
234
-
235
- protected registerEditorTitleAction(location: string, action: Menu): Disposable {
236
- return this.registerTitleAction(location, action, {
237
- execute: widget => this.codeEditorWidgetUtil.is(widget) &&
238
- this.commands.executeCommand(action.command!, this.codeEditorWidgetUtil.getResourceUri(widget)),
239
- isEnabled: widget => this.codeEditorWidgetUtil.is(widget) && this.commands.isEnabled(action.command!, this.codeEditorWidgetUtil.getResourceUri(widget)),
240
- isVisible: widget => this.codeEditorWidgetUtil.is(widget) && this.commands.isVisible(action.command!, this.codeEditorWidgetUtil.getResourceUri(widget))
241
- });
242
- }
243
-
244
- protected registerViewTitleAction(location: string, action: Menu): Disposable {
245
- return this.registerTitleAction(location, action, {
246
- execute: widget => widget instanceof PluginViewWidget && this.commands.executeCommand(action.command!),
247
- isEnabled: widget => widget instanceof PluginViewWidget && this.commands.isEnabled(action.command!),
248
- isVisible: widget => widget instanceof PluginViewWidget && this.commands.isVisible(action.command!),
249
- });
250
- }
251
-
252
- protected registerViewItemContextAction(menu: MenuTree): Disposable {
253
- const inline = menu.group && /^inline/.test(menu.group) || false;
254
- const menuPath = inline ? VIEW_ITEM_INLINE_MENU : VIEW_ITEM_CONTEXT_MENU;
255
- return this.registerTreeMenuAction(menuPath, menu);
256
- }
257
-
258
- protected registerScmResourceGroupAction(menu: MenuTree): Disposable {
259
- const inline = menu.group && /^inline/.test(menu.group) || false;
260
- const menuPath = inline ? ScmTreeWidget.RESOURCE_GROUP_INLINE_MENU : ScmTreeWidget.RESOURCE_GROUP_CONTEXT_MENU;
261
- return this.registerScmMenuAction(menuPath, menu);
262
- }
263
-
264
- protected registerScmResourceFolderAction(menu: MenuTree): Disposable {
265
- const inline = menu.group && /^inline/.test(menu.group) || false;
266
- const menuPath = inline ? ScmTreeWidget.RESOURCE_FOLDER_INLINE_MENU : ScmTreeWidget.RESOURCE_FOLDER_CONTEXT_MENU;
267
- return this.registerScmMenuAction(menuPath, menu);
268
- }
269
-
270
- protected registerScmResourceStateAction(menu: MenuTree): Disposable {
271
- const inline = menu.group && /^inline/.test(menu.group) || false;
272
- const menuPath = inline ? ScmTreeWidget.RESOURCE_INLINE_MENU : ScmTreeWidget.RESOURCE_CONTEXT_MENU;
273
- return this.registerScmMenuAction(menuPath, menu);
274
- }
275
-
276
- protected registerTimelineItemAction(menu: MenuTree): Disposable {
277
- return this.registerMenuAction(TIMELINE_ITEM_CONTEXT_MENU, menu,
278
- command => ({
279
- execute: (...args) => this.commands.executeCommand(command, ...this.toTimelineArgs(...args)),
280
- isEnabled: (...args) => this.commands.isEnabled(command, ...this.toTimelineArgs(...args)),
281
- isVisible: (...args) => this.commands.isVisible(command, ...this.toTimelineArgs(...args))
282
- }));
283
- }
284
91
 
285
- protected registerCommentThreadAction(menu: MenuTree, plugin: DeployedPlugin): Disposable {
286
- return this.registerMenuAction(COMMENT_THREAD_CONTEXT, menu,
287
- command => ({
288
- execute: (...args) => this.commands.executeCommand(command, ...this.toCommentArgs(...args)),
289
- isEnabled: () => {
290
- const commandContributions = plugin.contributes?.commands;
291
- if (commandContributions) {
292
- const commandContribution = commandContributions.find(c => c.command === command);
293
- if (commandContribution && commandContribution.enablement) {
294
- return this.contextKeyService.match(commandContribution.enablement);
92
+ for (const [contributionPoint, items] of Object.entries(allMenus)) {
93
+ for (const item of items) {
94
+ try {
95
+ if (contributionPoint === 'commandPalette') {
96
+ toDispose.push(this.registerCommandPaletteAction(item));
97
+ } else {
98
+ this.checkTitleContribution(contributionPoint, item, toDispose);
99
+ if (item.submenu) {
100
+ const targets = this.getMatchingMenu(contributionPoint as ContributionPoint) ?? [contributionPoint];
101
+ const { group, order } = this.parseGroup(item.group);
102
+ targets.forEach(([target]) => toDispose.push(this.menuRegistry.linkSubmenu(target, item.submenu!, { order, when: item.when }, group)));
103
+ } else if (item.command) {
104
+ toDispose.push(this.commandAdapter.addCommand(item.command));
105
+ const { group, order } = this.parseGroup(item.group);
106
+ const node = new ActionMenuNode({
107
+ commandId: item.command,
108
+ when: item.when,
109
+ order,
110
+ }, this.commands);
111
+ const parent = this.menuRegistry.getMenuNode(contributionPoint, group);
112
+ toDispose.push(parent.addNode(node));
295
113
  }
296
114
  }
297
- return true;
298
- },
299
- isVisible: (...args) => this.commands.isVisible(command, ...this.toCommentArgs(...args))
300
- }));
301
- }
302
-
303
- protected registerCommentTitleAction(menu: MenuTree): Disposable {
304
- return this.registerMenuAction(COMMENT_TITLE, menu,
305
- command => ({
306
- execute: (...args) => this.commands.executeCommand(command, ...this.toCommentArgs(...args)),
307
- isEnabled: (...args) => this.commands.isEnabled(command, ...this.toCommentArgs(...args)),
308
- isVisible: (...args) => this.commands.isVisible(command, ...this.toCommentArgs(...args))
309
- }));
310
- }
311
-
312
- protected registerCommentContextAction(menu: MenuTree): Disposable {
313
- return this.registerMenuAction(COMMENT_CONTEXT, menu,
314
- command => ({
315
- execute: (...args) => this.commands.executeCommand(command, ...this.toCommentArgs(...args)),
316
- isEnabled: () => true,
317
- isVisible: (...args) => this.commands.isVisible(command, ...this.toCommentArgs(...args))
318
- }));
319
- }
320
-
321
- protected registerDebugCallstackAction(menu: MenuTree): Disposable {
322
- const toDispose = new DisposableCollection();
323
- [DebugStackFramesWidget.CONTEXT_MENU, DebugThreadsWidget.CONTEXT_MENU].forEach(menuPath => {
324
- toDispose.push(
325
- this.registerMenuAction(menuPath, menu, command => ({
326
- execute: (...args) => this.commands.executeCommand(command, args[0]),
327
- isEnabled: (...args) => this.commands.isEnabled(command, args[0]),
328
- isVisible: (...args) => this.commands.isVisible(command, args[0])
329
- })));
330
- });
331
- return toDispose;
332
- }
333
-
334
- protected registerTreeMenuAction(menuPath: MenuPath, menu: MenuTree): Disposable {
335
- return this.registerMenuAction(menuPath, menu, command => ({
336
- execute: (...args) => this.commands.executeCommand(command, ...this.toTreeArgs(...args)),
337
- isEnabled: (...args) => this.commands.isEnabled(command, ...this.toTreeArgs(...args)),
338
- isVisible: (...args) => this.commands.isVisible(command, ...this.toTreeArgs(...args))
339
- }));
340
- }
341
- protected toTreeArgs(...args: any[]): any[] {
342
- const treeArgs: any[] = [];
343
- for (const arg of args) {
344
- if (TreeViewSelection.is(arg)) {
345
- treeArgs.push(arg);
346
- }
347
- }
348
- return treeArgs;
349
- }
350
-
351
- protected registerTitleAction(location: string, action: Menu, handler: CommandHandler): Disposable {
352
- if (!action.command) {
353
- return Disposable.NULL;
354
- }
355
- const toDispose = new DisposableCollection();
356
- const id = this.createSyntheticCommandId(action.command, { prefix: `__plugin.${location.replace('/', '.')}.action.` });
357
- const command: Command = { id };
358
- toDispose.push(this.commands.registerCommand(command, handler));
359
-
360
- const { when } = action;
361
- const whenKeys = when && this.contextKeyService.parseKeys(when);
362
- let onDidChange: Event<void> | undefined;
363
- if (whenKeys && whenKeys.size) {
364
- const onDidChangeEmitter = new Emitter<void>();
365
- toDispose.push(onDidChangeEmitter);
366
- onDidChange = onDidChangeEmitter.event;
367
- Event.addMaxListeners(this.contextKeyService.onDidChange, 1);
368
- toDispose.push(Disposable.create(() => {
369
- Event.addMaxListeners(this.contextKeyService.onDidChange, -1);
370
- }));
371
- toDispose.push(this.contextKeyService.onDidChange(event => {
372
- if (event.affects(whenKeys)) {
373
- onDidChangeEmitter.fire(undefined);
115
+ } catch (error) {
116
+ console.warn(`Failed to register a menu item for plugin ${plugin.metadata.model.id} contributed to ${contributionPoint}`, item, error);
374
117
  }
375
- }));
376
- }
377
-
378
- // handle group and priority
379
- // if group is empty or white space is will be set to navigation
380
- // ' ' => ['navigation', 0]
381
- // 'navigation@1' => ['navigation', 1]
382
- // '1_rest-client@2' => ['1_rest-client', 2]
383
- // if priority is not a number it will be set to 0
384
- // navigation@test => ['navigation', 0]
385
- const [group, sort] = (action.group || 'navigation').split('@');
386
- const item: Mutable<TabBarToolbarItem> = { id, command: id, group: group.trim() || 'navigation', priority: ~~sort || undefined, when, onDidChange };
387
- toDispose.push(this.tabBarToolbar.registerItem(item));
388
-
389
- toDispose.push(this.onDidRegisterCommand(action.command, pluginCommand => {
390
- command.category = pluginCommand.category;
391
- item.tooltip = pluginCommand.label;
392
- if (group === 'navigation') {
393
- command.iconClass = pluginCommand.iconClass;
394
- }
395
- }));
396
- return toDispose;
397
- }
398
-
399
- protected registerScmTitleAction(location: string, action: Menu): Disposable {
400
- if (!action.command) {
401
- return Disposable.NULL;
402
- }
403
- const selectedRepository = () => this.toScmArg(this.scmService.selectedRepository);
404
- return this.registerTitleAction(location, action, {
405
- execute: widget => widget instanceof ScmWidget && this.commands.executeCommand(action.command!, selectedRepository()),
406
- isEnabled: widget => widget instanceof ScmWidget && this.commands.isEnabled(action.command!, selectedRepository()),
407
- isVisible: widget => widget instanceof ScmWidget && this.commands.isVisible(action.command!, selectedRepository())
408
- });
409
- }
410
- protected registerScmMenuAction(menuPath: MenuPath, menu: MenuTree): Disposable {
411
- return this.registerMenuAction(menuPath, menu, command => ({
412
- execute: (...args) => this.commands.executeCommand(command, ...this.toScmArgs(...args)),
413
- isEnabled: (...args) => this.commands.isEnabled(command, ...this.toScmArgs(...args)),
414
- isVisible: (...args) => this.commands.isVisible(command, ...this.toScmArgs(...args))
415
- }));
416
- }
417
- protected toScmArgs(...args: any[]): any[] {
418
- const scmArgs: any[] = [];
419
- for (const arg of args) {
420
- const scmArg = this.toScmArg(arg);
421
- if (scmArg) {
422
- scmArgs.push(scmArg);
423
118
  }
424
119
  }
425
- return scmArgs;
426
- }
427
- protected toScmArg(arg: any): ScmCommandArg | undefined {
428
- if (arg instanceof ScmRepository && arg.provider instanceof PluginScmProvider) {
429
- return {
430
- sourceControlHandle: arg.provider.handle
431
- };
432
- }
433
- if (arg instanceof PluginScmResourceGroup) {
434
- return {
435
- sourceControlHandle: arg.provider.handle,
436
- resourceGroupHandle: arg.handle
437
- };
438
- }
439
- if (arg instanceof PluginScmResource) {
440
- return {
441
- sourceControlHandle: arg.group.provider.handle,
442
- resourceGroupHandle: arg.group.handle,
443
- resourceStateHandle: arg.handle
444
- };
445
- }
446
- }
447
120
 
448
- protected toTimelineArgs(...args: any[]): any[] {
449
- const timelineArgs: any[] = [];
450
- const arg = args[0];
451
- timelineArgs.push(this.toTimelineArg(arg));
452
- timelineArgs.push(CodeUri.parse(arg.uri));
453
- timelineArgs.push('source' in arg ? arg.source : '');
454
- return timelineArgs;
455
- }
456
- protected toTimelineArg(arg: TimelineItem): TimelineCommandArg {
457
- return {
458
- timelineHandle: arg.handle,
459
- source: arg.source,
460
- uri: arg.uri
461
- };
121
+ return toDispose;
462
122
  }
463
123
 
464
- protected toCommentArgs(...args: any[]): any[] {
465
- const arg = args[0];
466
- if ('text' in arg) {
467
- if ('commentUniqueId' in arg) {
468
- return [{
469
- commentControlHandle: arg.thread.controllerHandle,
470
- commentThreadHandle: arg.thread.commentThreadHandle,
471
- text: arg.text,
472
- commentUniqueId: arg.commentUniqueId
473
- }];
474
- }
475
- return [{
476
- commentControlHandle: arg.thread.controllerHandle,
477
- commentThreadHandle: arg.thread.commentThreadHandle,
478
- text: arg.text
479
- }];
124
+ private parseGroup(rawGroup?: string): { group?: string, order?: string } {
125
+ if (!rawGroup) { return {}; }
126
+ const separatorIndex = rawGroup.lastIndexOf('@');
127
+ if (separatorIndex > -1) {
128
+ return { group: rawGroup.substring(0, separatorIndex), order: rawGroup.substring(separatorIndex + 1) || undefined };
480
129
  }
481
- return [{
482
- commentControlHandle: arg.thread.controllerHandle,
483
- commentThreadHandle: arg.thread.commentThreadHandle,
484
- commentUniqueId: arg.commentUniqueId
485
- }];
130
+ return { group: rawGroup };
486
131
  }
487
132
 
488
- protected registerGlobalMenuAction(menu: MenuTree, location: string, plugin: DeployedPlugin): Disposable {
489
- const menuPaths = MenusContributionPointHandler.parseMenuPaths(location);
490
- if (!menuPaths.length) {
491
- this.logger.warn(`'${plugin.metadata.model.id}' plugin contributes items to a menu with invalid identifier: ${location}`);
492
- return Disposable.NULL;
133
+ private registerCommandPaletteAction(menu: Menu): Disposable {
134
+ if (menu.command && menu.when) {
135
+ return this.quickCommandService.pushCommandContext(menu.command, menu.when);
493
136
  }
494
-
495
- const selectedResource = () => {
496
- const selection = this.selectionService.selection;
497
- if (TreeWidgetSelection.is(selection) && selection.source instanceof TreeViewWidget && selection[0]) {
498
- return selection.source.toTreeViewSelection(selection[0]);
499
- }
500
- const uri = this.resourceContextKey.get();
501
- return uri ? uri['codeUri'] : undefined;
502
- };
503
-
504
- const toDispose = new DisposableCollection();
505
- menuPaths.forEach(menuPath => {
506
- toDispose.push(this.registerMenuAction(menuPath, menu, command => ({
507
- execute: () => this.commands.executeCommand(command, selectedResource()),
508
- isEnabled: () => this.commands.isEnabled(command, selectedResource()),
509
- isVisible: () => this.commands.isVisible(command, selectedResource())
510
- })));
511
- });
512
- return toDispose;
137
+ return Disposable.NULL;
513
138
  }
514
139
 
515
- protected registerMenuAction(menuPath: MenuPath, menu: MenuTree, handler: (command: string) => CommandHandler): Disposable {
516
- if (!menu.command) {
517
- return Disposable.NULL;
518
- }
519
- const toDispose = new DisposableCollection();
520
- const commandId = this.createSyntheticCommandId(menu.command, { prefix: '__plugin.menu.action.' });
521
- const altId = menu.alt && this.createSyntheticCommandId(menu.alt, { prefix: '__plugin.menu.action.' });
522
-
523
- const inline = Boolean(menu.group && /^inline/.test(menu.group));
524
- const [group, order = undefined] = (menu.group || '_').split('@');
525
-
526
- const command: Command = { id: commandId };
527
- const action: MenuAction = { commandId, alt: altId, order, when: menu.when };
528
-
529
- toDispose.push(this.commands.registerCommand(command, handler(menu.command)));
530
- toDispose.push(this.quickCommandService?.pushCommandContext(commandId, 'false'));
531
- toDispose.push(this.menuRegistry.registerMenuAction(inline ? menuPath : [...menuPath, ...group.split('/')], action));
532
- toDispose.push(this.onDidRegisterCommand(menu.command, pluginCommand => {
533
- command.category = pluginCommand.category;
534
- command.label = pluginCommand.label;
535
- if (inline) {
536
- command.iconClass = pluginCommand.iconClass;
537
- }
538
- }));
539
-
540
- if (menu.alt && altId) {
541
- const alt: Command = { id: altId };
542
- toDispose.push(this.commands.registerCommand(alt, handler(menu.alt)));
543
- toDispose.push(this.quickCommandService?.pushCommandContext(altId, 'false'));
544
- toDispose.push(this.onDidRegisterCommand(menu.alt, pluginCommand => {
545
- alt.category = pluginCommand.category;
546
- alt.label = pluginCommand.label;
547
- if (inline) {
548
- alt.iconClass = pluginCommand.iconClass;
140
+ protected checkTitleContribution(contributionPoint: ContributionPoint | string, contribution: { when?: string }, toDispose: DisposableCollection): void {
141
+ if (contribution.when && contributionPoint.endsWith('title')) {
142
+ const expression = ContextKeyExpr.deserialize(contribution.when);
143
+ if (expression) {
144
+ for (const key of expression.keys()) {
145
+ this.titleContributionContextKeys.add(key);
146
+ toDispose.push(Disposable.create(() => this.titleContributionContextKeys.delete(key)));
549
147
  }
550
- }));
551
- }
552
-
553
- // Register a submenu if the group is in format `<submenu group>/<submenu name>/<submenu order>.../<menu group>`
554
- if (group.includes('/')) {
555
- const groupSplit = group.split('/');
556
- const orderSplit = (menu.submenusOrder || '').split('/');
557
- const paths: string[] = [];
558
- for (let i = 0, j = 0; i < groupSplit.length - 1; i += 2, j += 1) {
559
- const submenuGroup = groupSplit[i];
560
- const submenuLabel = groupSplit[i + 1];
561
- const submenuOrder = orderSplit[j];
562
- paths.push(submenuGroup, submenuLabel);
563
- toDispose.push(this.menuRegistry.registerSubmenu([...menuPath, ...paths], submenuLabel, { order: submenuOrder }));
148
+ toDispose.push(Disposable.create(() => this.onDidChangeTitleContributionEmitter.fire()));
564
149
  }
565
150
  }
566
-
567
- return toDispose;
568
151
  }
569
152
 
570
- protected createSyntheticCommandId(command: string, { prefix }: { prefix: string }): string {
571
- let id = prefix + command;
572
- let index = 0;
573
- while (this.commands.getCommand(id)) {
574
- id = prefix + command + ':' + index;
575
- index++;
576
- }
577
- return id;
578
- }
579
-
580
- protected onDidRegisterCommand(id: string, cb: (command: Command) => void): Disposable {
581
- const command = this.commands.getCommand(id);
582
- if (command) {
583
- cb(command);
584
- return Disposable.NULL;
585
- }
586
- const toDispose = new DisposableCollection();
587
- // Registering a menu action requires the related command to be already registered.
588
- // But Theia plugin registers the commands dynamically via the Commands API.
589
- // Let's wait for ~2 sec. It should be enough to finish registering all the contributed commands.
590
- // FIXME: remove this workaround (timer) once the https://github.com/theia-ide/theia/issues/3344 is fixed
591
- const handle = setTimeout(() => toDispose.push(this.onDidRegisterCommand(id, cb)), 2000);
592
- toDispose.push(Disposable.create(() => clearTimeout(handle)));
593
- return toDispose;
594
- }
595
-
596
- }
597
-
598
- /**
599
- * MenuTree representing a (sub)menu in the menu tree structure.
600
- */
601
- export class MenuTree implements Menu {
602
-
603
- protected _children: MenuTree[] = [];
604
- command?: string;
605
- alt?: string;
606
- group?: string;
607
- when?: string;
608
- /** The orders of the menu items which lead to the submenus */
609
- submenusOrder?: string;
610
-
611
- constructor(menu?: Menu,
612
- /** The location where the menu item will be open from. */
613
- public readonly id?: string,
614
- /** The label of the menu item which leads to the submenu. */
615
- public label?: string) {
616
- if (menu) {
617
- this.command = menu.command;
618
- this.alt = menu.alt;
619
- this.group = menu.group;
620
- this.when = menu.when;
153
+ protected toIconClass(url: IconUrl, toDispose: DisposableCollection): string | undefined {
154
+ if (typeof url === 'string') {
155
+ const asThemeIcon = ThemeIcon.fromString(url);
156
+ if (asThemeIcon) {
157
+ return ThemeIcon.asClassName(asThemeIcon);
158
+ }
621
159
  }
622
- }
623
-
624
- get children(): MenuTree[] {
625
- return this._children;
626
- }
627
- set children(items: MenuTree[]) {
628
- this._children.push(...items);
629
- }
630
-
631
- public addChild(node: MenuTree): void {
632
- this._children.push(node);
633
- }
634
-
635
- get isSubmenu(): boolean {
636
- return this.label !== undefined;
160
+ const reference = this.style.toIconClass(url);
161
+ toDispose.push(reference);
162
+ return reference.object.iconClass;
637
163
  }
638
164
  }