@theia/core 1.76.0-next.6 → 1.76.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 (179) hide show
  1. package/README.md +8 -8
  2. package/i18n/nls.cs.json +187 -49
  3. package/i18n/nls.de.json +187 -49
  4. package/i18n/nls.es.json +187 -49
  5. package/i18n/nls.fr.json +187 -49
  6. package/i18n/nls.hu.json +187 -49
  7. package/i18n/nls.it.json +187 -49
  8. package/i18n/nls.ja.json +187 -49
  9. package/i18n/nls.json +187 -49
  10. package/i18n/nls.ko.json +187 -49
  11. package/i18n/nls.pl.json +187 -49
  12. package/i18n/nls.pt-br.json +187 -49
  13. package/i18n/nls.ru.json +187 -49
  14. package/i18n/nls.tr.json +187 -49
  15. package/i18n/nls.zh-cn.json +187 -49
  16. package/i18n/nls.zh-tw.json +187 -49
  17. package/lib/browser/catalog.json +175 -95
  18. package/lib/browser/dialogs.d.ts +2 -0
  19. package/lib/browser/dialogs.d.ts.map +1 -1
  20. package/lib/browser/dialogs.js +9 -5
  21. package/lib/browser/dialogs.js.map +1 -1
  22. package/lib/browser/frontend-application-module.d.ts.map +1 -1
  23. package/lib/browser/frontend-application-module.js +3 -0
  24. package/lib/browser/frontend-application-module.js.map +1 -1
  25. package/lib/browser/index.d.ts +1 -0
  26. package/lib/browser/index.d.ts.map +1 -1
  27. package/lib/browser/index.js +1 -0
  28. package/lib/browser/index.js.map +1 -1
  29. package/lib/browser/resource-context-key.d.ts +25 -0
  30. package/lib/browser/resource-context-key.d.ts.map +1 -1
  31. package/lib/browser/resource-context-key.js +41 -16
  32. package/lib/browser/resource-context-key.js.map +1 -1
  33. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-menu-adapters.d.ts +7 -5
  34. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-menu-adapters.d.ts.map +1 -1
  35. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-menu-adapters.js +13 -12
  36. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-menu-adapters.js.map +1 -1
  37. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-registry.d.ts +11 -1
  38. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-registry.d.ts.map +1 -1
  39. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-registry.js +38 -3
  40. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-registry.js.map +1 -1
  41. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar.d.ts +2 -1
  42. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar.d.ts.map +1 -1
  43. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar.js +4 -2
  44. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar.js.map +1 -1
  45. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar.spec.js +123 -2
  46. package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar.spec.js.map +1 -1
  47. package/lib/browser/shell/tab-bar-toolbar/tab-toolbar-item.d.ts +12 -4
  48. package/lib/browser/shell/tab-bar-toolbar/tab-toolbar-item.d.ts.map +1 -1
  49. package/lib/browser/shell/tab-bar-toolbar/tab-toolbar-item.js +2 -2
  50. package/lib/browser/shell/tab-bar-toolbar/tab-toolbar-item.js.map +1 -1
  51. package/lib/browser/tree/tree-widget.d.ts +20 -2
  52. package/lib/browser/tree/tree-widget.d.ts.map +1 -1
  53. package/lib/browser/tree/tree-widget.js +31 -13
  54. package/lib/browser/tree/tree-widget.js.map +1 -1
  55. package/lib/browser/tree/tree-widget.spec.d.ts +2 -0
  56. package/lib/browser/tree/tree-widget.spec.d.ts.map +1 -0
  57. package/lib/browser/tree/tree-widget.spec.js +192 -0
  58. package/lib/browser/tree/tree-widget.spec.js.map +1 -0
  59. package/lib/browser/view-container.d.ts.map +1 -1
  60. package/lib/browser/view-container.js +2 -2
  61. package/lib/browser/view-container.js.map +1 -1
  62. package/lib/browser/widget-context-key-contribution.d.ts +17 -0
  63. package/lib/browser/widget-context-key-contribution.d.ts.map +1 -0
  64. package/lib/browser/widget-context-key-contribution.js +20 -0
  65. package/lib/browser/widget-context-key-contribution.js.map +1 -0
  66. package/lib/browser/window/browser-window-module.d.ts.map +1 -1
  67. package/lib/browser/window/browser-window-module.js +3 -0
  68. package/lib/browser/window/browser-window-module.js.map +1 -1
  69. package/lib/browser/window/window-launch-args.d.ts +29 -0
  70. package/lib/browser/window/window-launch-args.d.ts.map +1 -0
  71. package/lib/browser/window/window-launch-args.js +39 -0
  72. package/lib/browser/window/window-launch-args.js.map +1 -0
  73. package/lib/browser-only/frontend-only-application-module.d.ts.map +1 -1
  74. package/lib/browser-only/frontend-only-application-module.js +5 -2
  75. package/lib/browser-only/frontend-only-application-module.js.map +1 -1
  76. package/lib/browser-only/messaging/frontend-only-service-connection-provider.d.ts +3 -1
  77. package/lib/browser-only/messaging/frontend-only-service-connection-provider.d.ts.map +1 -1
  78. package/lib/browser-only/messaging/frontend-only-service-connection-provider.js +8 -7
  79. package/lib/browser-only/messaging/frontend-only-service-connection-provider.js.map +1 -1
  80. package/lib/common/index.d.ts +2 -0
  81. package/lib/common/index.d.ts.map +1 -1
  82. package/lib/common/index.js +2 -0
  83. package/lib/common/index.js.map +1 -1
  84. package/lib/common/launch-arguments.d.ts +32 -0
  85. package/lib/common/launch-arguments.d.ts.map +1 -0
  86. package/lib/common/launch-arguments.js +44 -0
  87. package/lib/common/launch-arguments.js.map +1 -0
  88. package/lib/common/remote-cli-args-contribution.d.ts +22 -0
  89. package/lib/common/remote-cli-args-contribution.d.ts.map +1 -0
  90. package/lib/common/remote-cli-args-contribution.js +20 -0
  91. package/lib/common/remote-cli-args-contribution.js.map +1 -0
  92. package/lib/electron-browser/messaging/electron-ipc-connection-source.d.ts +6 -3
  93. package/lib/electron-browser/messaging/electron-ipc-connection-source.d.ts.map +1 -1
  94. package/lib/electron-browser/messaging/electron-ipc-connection-source.js +4 -3
  95. package/lib/electron-browser/messaging/electron-ipc-connection-source.js.map +1 -1
  96. package/lib/electron-browser/preload.d.ts.map +1 -1
  97. package/lib/electron-browser/preload.js +3 -1
  98. package/lib/electron-browser/preload.js.map +1 -1
  99. package/lib/electron-browser/window/electron-window-launch-args.d.ts +14 -0
  100. package/lib/electron-browser/window/electron-window-launch-args.d.ts.map +1 -0
  101. package/lib/electron-browser/window/electron-window-launch-args.js +41 -0
  102. package/lib/electron-browser/window/electron-window-launch-args.js.map +1 -0
  103. package/lib/electron-browser/window/electron-window-launch-args.spec.d.ts +2 -0
  104. package/lib/electron-browser/window/electron-window-launch-args.spec.d.ts.map +1 -0
  105. package/lib/electron-browser/window/electron-window-launch-args.spec.js +43 -0
  106. package/lib/electron-browser/window/electron-window-launch-args.spec.js.map +1 -0
  107. package/lib/electron-browser/window/electron-window-module.d.ts.map +1 -1
  108. package/lib/electron-browser/window/electron-window-module.js +7 -0
  109. package/lib/electron-browser/window/electron-window-module.js.map +1 -1
  110. package/lib/electron-common/electron-api.d.ts +10 -0
  111. package/lib/electron-common/electron-api.d.ts.map +1 -1
  112. package/lib/electron-common/electron-api.js.map +1 -1
  113. package/lib/electron-main/electron-api-main.d.ts.map +1 -1
  114. package/lib/electron-main/electron-api-main.js +7 -1
  115. package/lib/electron-main/electron-api-main.js.map +1 -1
  116. package/lib/electron-main/electron-main-application-module.d.ts.map +1 -1
  117. package/lib/electron-main/electron-main-application-module.js +2 -0
  118. package/lib/electron-main/electron-main-application-module.js.map +1 -1
  119. package/lib/electron-main/electron-main-application.d.ts +57 -2
  120. package/lib/electron-main/electron-main-application.d.ts.map +1 -1
  121. package/lib/electron-main/electron-main-application.js +68 -8
  122. package/lib/electron-main/electron-main-application.js.map +1 -1
  123. package/lib/electron-main/launch-args-store.d.ts +45 -0
  124. package/lib/electron-main/launch-args-store.d.ts.map +1 -0
  125. package/lib/electron-main/launch-args-store.js +76 -0
  126. package/lib/electron-main/launch-args-store.js.map +1 -0
  127. package/lib/electron-main/launch-args-store.spec.d.ts +2 -0
  128. package/lib/electron-main/launch-args-store.spec.d.ts.map +1 -0
  129. package/lib/electron-main/launch-args-store.spec.js +53 -0
  130. package/lib/electron-main/launch-args-store.spec.js.map +1 -0
  131. package/lib/electron-main/launch-argv-parser.d.ts +19 -0
  132. package/lib/electron-main/launch-argv-parser.d.ts.map +1 -0
  133. package/lib/electron-main/launch-argv-parser.js +71 -0
  134. package/lib/electron-main/launch-argv-parser.js.map +1 -0
  135. package/lib/electron-main/launch-argv-parser.spec.d.ts +2 -0
  136. package/lib/electron-main/launch-argv-parser.spec.d.ts.map +1 -0
  137. package/lib/electron-main/launch-argv-parser.spec.js +62 -0
  138. package/lib/electron-main/launch-argv-parser.spec.js.map +1 -0
  139. package/lib/electron-main/theia-electron-window.d.ts +2 -0
  140. package/lib/electron-main/theia-electron-window.d.ts.map +1 -1
  141. package/lib/electron-main/theia-electron-window.js +12 -0
  142. package/lib/electron-main/theia-electron-window.js.map +1 -1
  143. package/package.json +11 -11
  144. package/src/browser/dialogs.ts +10 -5
  145. package/src/browser/frontend-application-module.ts +5 -1
  146. package/src/browser/index.ts +1 -0
  147. package/src/browser/resource-context-key.ts +61 -16
  148. package/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar-menu-adapters.tsx +13 -12
  149. package/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar-registry.ts +36 -4
  150. package/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar.spec.ts +150 -3
  151. package/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar.tsx +6 -3
  152. package/src/browser/shell/tab-bar-toolbar/tab-toolbar-item.tsx +13 -5
  153. package/src/browser/style/tree.css +7 -0
  154. package/src/browser/tree/tree-widget.spec.tsx +245 -0
  155. package/src/browser/tree/tree-widget.tsx +36 -12
  156. package/src/browser/view-container.ts +3 -3
  157. package/src/browser/widget-context-key-contribution.ts +34 -0
  158. package/src/browser/window/browser-window-module.ts +3 -0
  159. package/src/browser/window/window-launch-args.ts +53 -0
  160. package/src/browser-only/frontend-only-application-module.ts +5 -2
  161. package/src/browser-only/messaging/frontend-only-service-connection-provider.ts +3 -1
  162. package/src/common/i18n/nls.metadata.json +5246 -1106
  163. package/src/common/index.ts +2 -0
  164. package/src/common/launch-arguments.ts +61 -0
  165. package/src/common/remote-cli-args-contribution.ts +39 -0
  166. package/src/electron-browser/messaging/electron-ipc-connection-source.ts +6 -3
  167. package/src/electron-browser/preload.ts +3 -1
  168. package/src/electron-browser/window/electron-window-launch-args.spec.ts +45 -0
  169. package/src/electron-browser/window/electron-window-launch-args.ts +38 -0
  170. package/src/electron-browser/window/electron-window-module.ts +7 -0
  171. package/src/electron-common/electron-api.ts +10 -0
  172. package/src/electron-main/electron-api-main.ts +7 -1
  173. package/src/electron-main/electron-main-application-module.ts +2 -0
  174. package/src/electron-main/electron-main-application.ts +101 -8
  175. package/src/electron-main/launch-args-store.spec.ts +59 -0
  176. package/src/electron-main/launch-args-store.ts +73 -0
  177. package/src/electron-main/launch-argv-parser.spec.ts +70 -0
  178. package/src/electron-main/launch-argv-parser.ts +66 -0
  179. package/src/electron-main/theia-electron-window.ts +9 -0
@@ -57,8 +57,8 @@ abstract class AbstractToolbarMenuWrapper {
57
57
  }
58
58
  return false;
59
59
  }
60
- render(widget: Widget): React.ReactNode {
61
- return this.renderMenuItem(widget);
60
+ render(widget: Widget, contextMatcher: ContextMatcher = this.contextKeyService): React.ReactNode {
61
+ return this.renderMenuItem(widget, contextMatcher);
62
62
  }
63
63
 
64
64
  abstract toMenuNode(): MenuNode | undefined;
@@ -90,14 +90,15 @@ abstract class AbstractToolbarMenuWrapper {
90
90
  * chevron decoration that pops up a floating menu when clicked.
91
91
  *
92
92
  * @param item a toolbar item that is a menu item
93
+ * @param contextMatcher evaluates `when` clauses with the values that describe `widget`, so that the
94
+ * chevron and its popup resolve the same keys as the visibility check.
93
95
  * @returns the rendered toolbar item
94
96
  */
95
- protected renderMenuItem(widget: Widget): React.ReactNode {
97
+ protected renderMenuItem(widget: Widget, contextMatcher: ContextMatcher = this.contextKeyService): React.ReactNode {
96
98
  const icon = this.icon || 'ellipsis';
97
- const contextMatcher: ContextMatcher = this.contextKeyService;
98
99
  const className = `${icon} ${ACTION_ITEM}`;
99
100
  const itemClassName = TabBarToolbar.Styles.TAB_BAR_TOOLBAR_ITEM + ' enabled menu' + (this.isToggled(widget) ? ' toggled' : '');
100
- if (CompoundMenuNode.is(this.menuNode) && !this.menuNode.isEmpty(this.effectiveMenuPath, this.contextKeyService, widget.node, widget)) {
101
+ if (CompoundMenuNode.is(this.menuNode) && !this.menuNode.isEmpty(this.effectiveMenuPath, contextMatcher, widget.node, widget)) {
101
102
  return <div key={this.id} className={itemClassName}>
102
103
  <div id={this.id} className={className}
103
104
  title={this.tooltip || this.text}
@@ -137,8 +138,8 @@ export class SubmenuAsToolbarItemWrapper extends AbstractToolbarMenuWrapper impl
137
138
  executeCommand(widget: Widget, e: React.MouseEvent<HTMLDivElement, MouseEvent>): void {
138
139
  }
139
140
 
140
- isVisible(widget: Widget): boolean {
141
- const menuNodeVisible = this.menuNode.isVisible(this.effectiveMenuPath, this.contextKeyService, widget.node, widget);
141
+ isVisible(widget: Widget, contextMatcher: ContextMatcher): boolean {
142
+ const menuNodeVisible = this.menuNode.isVisible(this.effectiveMenuPath, contextMatcher, widget.node, widget);
142
143
  return menuNodeVisible && !MenuModelRegistry.isEmpty(this.menuNode);
143
144
  }
144
145
 
@@ -173,8 +174,8 @@ export class CommandMenuAsToolbarItemWrapper extends AbstractToolbarMenuWrapper
173
174
  return this.menuNode.when;
174
175
  }
175
176
 
176
- isVisible(widget: Widget): boolean {
177
- return this.menuNode.isVisible(this.effectiveMenuPath, this.contextKeyService, widget.node, widget);
177
+ isVisible(widget: Widget, contextMatcher: ContextMatcher): boolean {
178
+ return this.menuNode.isVisible(this.effectiveMenuPath, contextMatcher, widget.node, widget);
178
179
  }
179
180
 
180
181
  executeCommand(widget: Widget, e: React.MouseEvent<HTMLDivElement, MouseEvent>): void {
@@ -229,19 +230,19 @@ export class ToolbarActionWrapper extends AbstractToolbarMenuWrapper implements
229
230
  }
230
231
  };
231
232
 
232
- isVisible(widget: Widget): boolean {
233
+ isVisible(widget: Widget, contextMatcher: ContextMatcher): boolean {
233
234
  const menuNode = this.menuNode;
234
235
  if (this.toolbarItem.isVisible && !this.toolbarItem.isVisible(widget)) {
235
236
  return false;
236
237
  }
237
- if (!menuNode?.isVisible(this.effectiveMenuPath, this.contextKeyService, widget.node, widget)) {
238
+ if (!menuNode?.isVisible(this.effectiveMenuPath, contextMatcher, widget.node, widget)) {
238
239
  return false;
239
240
  }
240
241
  if (this.toolbarItem.command) {
241
242
  return true;
242
243
  }
243
244
  if (CompoundMenuNode.is(menuNode)) {
244
- return !menuNode.isEmpty(this.effectiveMenuPath, this.contextKeyService, widget.node, widget);
245
+ return !menuNode.isEmpty(this.effectiveMenuPath, contextMatcher, widget.node, widget);
245
246
  }
246
247
  return true;
247
248
  }
@@ -18,9 +18,12 @@ import debounce = require('lodash.debounce');
18
18
  import { inject, injectable, named } from 'inversify';
19
19
  // eslint-disable-next-line max-len
20
20
  import { CommandRegistry, ContributionProvider, Disposable, DisposableCollection, Emitter, Event, MenuModelRegistry, MenuPath, ILogger } from '../../../common';
21
- import { ContextKeyService } from '../../context-key-service';
21
+ import { ContextKeyService, ContextMatcher } from '../../context-key-service';
22
22
  import { FrontendApplicationContribution } from '../../frontend-application-contribution';
23
23
  import { Widget } from '../../widgets';
24
+ import { Navigatable } from '../../navigatable-types';
25
+ import { ResourceContextKey } from '../../resource-context-key';
26
+ import { WidgetContextKeyContribution } from '../../widget-context-key-contribution';
24
27
  import { ReactTabBarToolbarAction, RenderedToolbarAction } from './tab-bar-toolbar-types';
25
28
  import { CommandMenuAsToolbarItemWrapper, SubmenuAsToolbarItemWrapper, ToolbarActionWrapper } from './tab-bar-toolbar-menu-adapters';
26
29
  import { KeybindingRegistry } from '../../keybinding';
@@ -65,9 +68,14 @@ export class TabBarToolbarRegistry implements FrontendApplicationContribution {
65
68
  @inject(LabelParser) protected readonly labelParser: LabelParser;
66
69
  @inject(ContextMenuRenderer) protected readonly contextMenuRenderer: ContextMenuRenderer;
67
70
 
71
+ @inject(ResourceContextKey) protected readonly resourceContextKey: ResourceContextKey;
72
+
68
73
  @inject(ContributionProvider) @named(TabBarToolbarContribution)
69
74
  protected readonly contributionProvider: ContributionProvider<TabBarToolbarContribution>;
70
75
 
76
+ @inject(ContributionProvider) @named(WidgetContextKeyContribution)
77
+ protected readonly contextKeyContributionProvider: ContributionProvider<WidgetContextKeyContribution>;
78
+
71
79
  @inject(ILogger) @named('core:TabBarToolbarRegistry')
72
80
  protected readonly logger: ILogger;
73
81
 
@@ -133,8 +141,9 @@ export class TabBarToolbarRegistry implements FrontendApplicationContribution {
133
141
  return [];
134
142
  }
135
143
  const result: Array<TabBarToolbarItem> = [];
144
+ const contextMatcher = this.contextMatcherFor(widget);
136
145
  for (const item of this.items.values()) {
137
- if (item.isVisible(widget)) {
146
+ if (item.isVisible(widget, contextMatcher)) {
138
147
  result.push(item);
139
148
  }
140
149
  }
@@ -144,11 +153,11 @@ export class TabBarToolbarRegistry implements FrontendApplicationContribution {
144
153
  const menu = this.menuRegistry.getMenu(delegate.menuPath);
145
154
  if (menu) {
146
155
  for (const child of menu.children) {
147
- if (child.isVisible([...delegate.menuPath, child.id], this.contextKeyService, widget.node, widget)) {
156
+ if (child.isVisible([...delegate.menuPath, child.id], contextMatcher, widget.node, widget)) {
148
157
  if (CompoundMenuNode.is(child)) {
149
158
  for (const grandchild of child.children) {
150
159
  if (grandchild.isVisible([...delegate.menuPath, child.id, grandchild.id],
151
- this.contextKeyService, widget.node, widget) && RenderedMenuNode.is(grandchild)) {
160
+ contextMatcher, widget.node, widget) && RenderedMenuNode.is(grandchild)) {
152
161
  if (CommandMenu.is(grandchild)) {
153
162
  result.push(new CommandMenuAsToolbarItemWrapper([...delegate.menuPath, child.id, grandchild.id], this.commandRegistry,
154
163
  this.menuRegistry, this.contextKeyService, this.contextMenuRenderer, grandchild, child.id));
@@ -171,6 +180,29 @@ export class TabBarToolbarRegistry implements FrontendApplicationContribution {
171
180
  return result;
172
181
  }
173
182
 
183
+ /**
184
+ * A matcher that evaluates `when` clauses against the given widget rather than against whichever widget
185
+ * currently holds the focus. Keys that describe no aspect of the widget keep their ambient value, and the
186
+ * context scoped to the widget's DOM node is still consulted for the keys the overlay does not define.
187
+ */
188
+ contextMatcherFor(widget: Widget): ContextMatcher {
189
+ const overlay = new Map<string, unknown>();
190
+ if (Navigatable.is(widget)) {
191
+ for (const [key, value] of Object.entries(this.resourceContextKey.toValues(widget.getResourceUri()))) {
192
+ overlay.set(key, value);
193
+ }
194
+ }
195
+ for (const contribution of this.contextKeyContributionProvider.getContributions()) {
196
+ const entries = contribution.getContextKeyValues(widget);
197
+ if (entries) {
198
+ for (const [key, value] of entries) {
199
+ overlay.set(key, value);
200
+ }
201
+ }
202
+ }
203
+ return overlay.size ? this.contextKeyService.createOverlay(overlay) : this.contextKeyService;
204
+ }
205
+
174
206
  /**
175
207
  * Collects all context keys referenced by toolbar items and delegated menu nodes
176
208
  * that may participate in rendering for the given widget.
@@ -18,13 +18,18 @@ import { enableJSDOM } from '../../test/jsdom';
18
18
 
19
19
  let disableJSDOM = enableJSDOM();
20
20
  import { expect } from 'chai';
21
+ import { renderToStaticMarkup } from 'react-dom/server';
21
22
  import {
22
23
  CommandMenu, CommandRegistry, CompoundMenuNode, Group, GroupImpl, MenuAction, MenuModelRegistry, MenuNode, MenuNodeFactory, MutableCompoundMenuNode,
23
24
  Submenu, SubmenuImpl, SubMenuLink
24
25
  } from '../../../common';
25
- import { ContextKeyServiceDummyImpl } from '../../context-key-service';
26
+ import { ContextKeyServiceDummyImpl, ContextMatcher } from '../../context-key-service';
26
27
  import { ContextMenuRenderer } from '../../context-menu-renderer';
27
28
  import { Widget } from '../../widgets';
29
+ import URI from '../../../common/uri';
30
+ import { ResourceContextKey } from '../../resource-context-key';
31
+ import { WidgetContextKeyContribution } from '../../widget-context-key-contribution';
32
+ import { TabBarToolbar } from './tab-bar-toolbar';
28
33
  import { TOOLBAR_WRAPPER_ID_SUFFIX } from './tab-bar-toolbar-menu-adapters';
29
34
  import { TabBarToolbarRegistry } from './tab-bar-toolbar-registry';
30
35
  import { TAB_BAR_TOOLBAR_CONTEXT_MENU, TabBarToolbarAction } from './tab-bar-toolbar-types';
@@ -135,6 +140,106 @@ describe('tab-bar-toolbar', () => {
135
140
  testWidget.dispose();
136
141
  });
137
142
 
143
+ it('evaluates delegated items against the resource of the widget that owns the toolbar', () => {
144
+ const testWidget = new TestToolbarWidget(new URI('probe:/a.probe'));
145
+ const commands = createCommandRegistry();
146
+ commands.registerCommand({ id: TEST_COMMAND, label: 'Test Command' }, { execute: () => { } });
147
+ const menuRegistry = createMenuRegistry(commands);
148
+ menuRegistry.registerMenuAction([...TEST_MENU_PATH, 'other'], { commandId: TEST_COMMAND, when: 'resourceScheme == probe' });
149
+ const registry = createToolbarRegistry(commands, menuRegistry, new TestContextKeyService());
150
+ registry.registerMenuDelegate(TEST_MENU_PATH, TestToolbarWidget.is);
151
+
152
+ const commandItem = registry.visibleItems(testWidget).find(item => item.id === `${TEST_COMMAND}${TOOLBAR_WRAPPER_ID_SUFFIX}`);
153
+
154
+ const otherWidget = new TestToolbarWidget(new URI('file:/a.txt'));
155
+
156
+ expect(commandItem).to.exist;
157
+ expect(registry.visibleItems(otherWidget)).to.be.empty;
158
+ testWidget.dispose();
159
+ otherWidget.dispose();
160
+ });
161
+
162
+ it('evaluates registered items against the keys contributed for the widget that owns the toolbar', () => {
163
+ const testWidget = new TestToolbarWidget(new URI('probe:/a.probe'));
164
+ const otherWidget = new TestToolbarWidget(new URI('probe:/b.probe'));
165
+ const commands = createCommandRegistry();
166
+ commands.registerCommand({ id: TEST_COMMAND, label: 'Test Command' }, { execute: () => { } });
167
+ const menuRegistry = createMenuRegistry(commands);
168
+ const contribution: WidgetContextKeyContribution = {
169
+ getContextKeyValues: widget => [['activeCustomEditorId', widget === testWidget ? 'test.probeEditor' : '']]
170
+ };
171
+ const registry = createToolbarRegistry(commands, menuRegistry, new TestContextKeyService(), [contribution]);
172
+ registry.registerItem({ id: 'test-item', command: TEST_COMMAND, when: 'activeCustomEditorId == test.probeEditor' });
173
+
174
+ expect(registry.visibleItems(testWidget).map(item => item.id)).to.deep.equal(['test-item']);
175
+ expect(registry.visibleItems(otherWidget)).to.be.empty;
176
+ testWidget.dispose();
177
+ otherWidget.dispose();
178
+ });
179
+
180
+ it('renders the submenu chevron against the widget that owns the toolbar', () => {
181
+ const testWidget = new TestToolbarWidget();
182
+ const otherWidget = new TestToolbarWidget();
183
+ const commands = createCommandRegistry();
184
+ commands.registerCommand({ id: TEST_SUBMENU_COMMAND, label: 'Test Submenu Command' }, { execute: () => { } });
185
+ const menuRegistry = createMenuRegistry(commands);
186
+ menuRegistry.registerSubmenu([...TEST_MENU_PATH, 'other', 'test-submenu'], 'Test Submenu');
187
+ menuRegistry.registerMenuAction([...TEST_MENU_PATH, 'other', 'test-submenu'],
188
+ { commandId: TEST_SUBMENU_COMMAND, when: 'activeCustomEditorId == test.probeEditor' });
189
+ const contribution: WidgetContextKeyContribution = {
190
+ getContextKeyValues: widget => [['activeCustomEditorId', widget === testWidget ? 'test.probeEditor' : '']]
191
+ };
192
+ const registry = createToolbarRegistry(commands, menuRegistry, new TestContextKeyService(), [contribution]);
193
+ registry.registerMenuDelegate(TEST_MENU_PATH, TestToolbarWidget.is);
194
+ const submenuItem = registry.visibleItems(testWidget).find(item => item.id === `test-submenu${TOOLBAR_WRAPPER_ID_SUFFIX}`);
195
+
196
+ expect(submenuItem).to.exist;
197
+ expect(renderToStaticMarkup(submenuItem!.render(testWidget, registry.contextMatcherFor(testWidget)))).to.contain('chevron');
198
+ expect(renderToStaticMarkup(submenuItem!.render(testWidget, registry.contextMatcherFor(otherWidget)))).to.not.contain('chevron');
199
+ testWidget.dispose();
200
+ otherWidget.dispose();
201
+ });
202
+
203
+ it('opens the More Actions menu against the widget that owns the toolbar', () => {
204
+ const testWidget = new TestToolbarWidget();
205
+ const otherWidget = new TestToolbarWidget();
206
+ const commands = createCommandRegistry();
207
+ commands.registerCommand({ id: TEST_SUBMENU_COMMAND, label: 'Test Submenu Command' }, { execute: () => { } });
208
+ const menuRegistry = createMenuRegistry(commands);
209
+ menuRegistry.registerSubmenu([...TEST_MENU_PATH, 'other', 'test-submenu'], 'Test Submenu');
210
+ menuRegistry.registerMenuAction([...TEST_MENU_PATH, 'other', 'test-submenu'],
211
+ { commandId: TEST_SUBMENU_COMMAND, when: 'activeCustomEditorId == test.probeEditor' });
212
+ const contribution: WidgetContextKeyContribution = {
213
+ getContextKeyValues: widget => [['activeCustomEditorId', widget === testWidget ? 'test.probeEditor' : '']]
214
+ };
215
+ const ambient = new TestContextKeyService();
216
+ const registry = createToolbarRegistry(commands, menuRegistry, ambient, [contribution]);
217
+ registry.registerMenuDelegate(TEST_MENU_PATH, TestToolbarWidget.is);
218
+
219
+ let rendered: { contextKeyService?: ContextMatcher } | undefined;
220
+ const toolbar = new TabBarToolbar();
221
+ Reflect.set(toolbar, 'contextKeyService', ambient);
222
+ Reflect.set(toolbar, 'toolbarRegistry', registry);
223
+ Reflect.set(toolbar, 'contextMenuRenderer', {
224
+ render: (options: { contextKeyService?: ContextMatcher }) => {
225
+ rendered = options;
226
+ return { dispose: () => { } };
227
+ }
228
+ });
229
+ const setCurrent = Reflect.get(toolbar, 'setCurrent') as (widget: Widget) => void;
230
+
231
+ setCurrent.call(toolbar, testWidget);
232
+ toolbar.renderMoreContextMenu({ x: 0, y: 0 });
233
+ expect(rendered?.contextKeyService?.match('activeCustomEditorId == test.probeEditor')).to.be.true;
234
+
235
+ setCurrent.call(toolbar, otherWidget);
236
+ toolbar.renderMoreContextMenu({ x: 0, y: 0 });
237
+ expect(rendered?.contextKeyService?.match('activeCustomEditorId == test.probeEditor')).to.be.false;
238
+
239
+ testWidget.dispose();
240
+ otherWidget.dispose();
241
+ });
242
+
138
243
  it('preserves the widget for wrapped submenu emptiness checks', () => {
139
244
  const testWidget = new TestToolbarWidget();
140
245
  const commands = createCommandRegistry();
@@ -166,6 +271,38 @@ class TestToolbarWidget extends Widget {
166
271
  static is(candidate?: Widget): candidate is TestToolbarWidget {
167
272
  return candidate instanceof TestToolbarWidget;
168
273
  }
274
+
275
+ constructor(protected readonly resourceUri?: URI) {
276
+ super();
277
+ }
278
+
279
+ getResourceUri(): URI | undefined {
280
+ return this.resourceUri;
281
+ }
282
+
283
+ createMoveToUri(resourceUri: URI): URI | undefined {
284
+ return resourceUri;
285
+ }
286
+ }
287
+
288
+ /**
289
+ * Evaluates `key == value` clauses against the overlay alone, so that a visible item can only come from the
290
+ * values the registry attributed to the widget.
291
+ */
292
+ class TestContextKeyService extends ContextKeyServiceDummyImpl {
293
+ override match(expression: string): boolean {
294
+ return false;
295
+ }
296
+
297
+ override createOverlay(overlay: Iterable<[string, unknown]>): ContextMatcher {
298
+ const values = new Map(overlay);
299
+ return {
300
+ match: (expression: string) => {
301
+ const [key, value] = expression.split(' == ');
302
+ return values.get(key) === value;
303
+ }
304
+ };
305
+ }
169
306
  }
170
307
 
171
308
  class TestMenuNodeFactory implements MenuNodeFactory {
@@ -187,7 +324,8 @@ class TestMenuNodeFactory implements MenuNodeFactory {
187
324
 
188
325
  createCommandMenu(item: MenuAction): CommandMenu {
189
326
  return {
190
- isVisible: (_path, _contextMatcher, _context, ...args) => this.commands.isVisible(item.commandId, ...args),
327
+ isVisible: (_path, contextMatcher, context, ...args) =>
328
+ (!item.when || contextMatcher.match(item.when, context)) && this.commands.isVisible(item.commandId, ...args),
191
329
  isEnabled: (_path, ...args) => this.commands.isEnabled(item.commandId, ...args),
192
330
  isToggled: (_path, ...args) => this.commands.isToggled(item.commandId, ...args),
193
331
  id: item.commandId,
@@ -208,7 +346,12 @@ function createMenuRegistry(commands: CommandRegistry): MenuModelRegistry {
208
346
  return new MenuModelRegistry({ getContributions: () => [] }, commands, new TestMenuNodeFactory(commands));
209
347
  }
210
348
 
211
- function createToolbarRegistry(commands: CommandRegistry, menuRegistry: MenuModelRegistry, contextKeyService: ContextKeyServiceDummyImpl): TabBarToolbarRegistry {
349
+ function createToolbarRegistry(
350
+ commands: CommandRegistry,
351
+ menuRegistry: MenuModelRegistry,
352
+ contextKeyService: ContextKeyServiceDummyImpl,
353
+ contextKeyContributions: WidgetContextKeyContribution[] = []
354
+ ): TabBarToolbarRegistry {
212
355
  const registry = new TabBarToolbarRegistry();
213
356
  Reflect.set(registry, 'commandRegistry', commands);
214
357
  Reflect.set(registry, 'contextKeyService', contextKeyService);
@@ -216,5 +359,9 @@ function createToolbarRegistry(commands: CommandRegistry, menuRegistry: MenuMode
216
359
  Reflect.set(registry, 'keybindingRegistry', {});
217
360
  Reflect.set(registry, 'labelParser', {});
218
361
  Reflect.set(registry, 'contextMenuRenderer', { render: () => undefined } as unknown as ContextMenuRenderer);
362
+ const resourceContextKey = new ResourceContextKey();
363
+ Reflect.set(resourceContextKey, 'languages', { languages: [] });
364
+ Reflect.set(registry, 'resourceContextKey', resourceContextKey);
365
+ Reflect.set(registry, 'contextKeyContributionProvider', { getContributions: () => contextKeyContributions });
219
366
  return registry;
220
367
  }
@@ -17,7 +17,7 @@
17
17
  import { inject, injectable, postConstruct } from 'inversify';
18
18
  import * as React from 'react';
19
19
  import { buttonKeyboardProps, isActivationKey } from '../../keyboard/keyboard-utils';
20
- import { ContextKeyService } from '../../context-key-service';
20
+ import { ContextKeyService, ContextMatcher } from '../../context-key-service';
21
21
  import { CommandRegistry, Disposable, DisposableCollection, nls } from '../../../common';
22
22
  import { Anchor, ContextMenuAccess, ContextMenuRenderer } from '../../context-menu-renderer';
23
23
  import { LabelParser } from '../../label-parser';
@@ -48,6 +48,7 @@ export function toAnchor(event: React.MouseEvent): Anchor {
48
48
  export class TabBarToolbar extends ReactWidget {
49
49
 
50
50
  protected current: Widget | undefined;
51
+ protected contextMatcher: ContextMatcher;
51
52
  protected inline = new Map<string, TabBarToolbarItem>();
52
53
  protected more = new Map<string, TabBarToolbarItem>();
53
54
 
@@ -73,6 +74,7 @@ export class TabBarToolbar extends ReactWidget {
73
74
 
74
75
  @postConstruct()
75
76
  protected init(): void {
77
+ this.contextMatcher = this.contextKeyService;
76
78
  this.toDispose.pushAll([
77
79
  this.keybindings.onKeybindingsChanged(() => this.maybeUpdate()),
78
80
  this.contextKeyService.onDidChange(e => {
@@ -129,6 +131,7 @@ export class TabBarToolbar extends ReactWidget {
129
131
  this.toDisposeOnSetCurrent.dispose();
130
132
  this.toDispose.push(this.toDisposeOnSetCurrent);
131
133
  this.current = current;
134
+ this.contextMatcher = current ? this.toolbarRegistry.contextMatcherFor(current) : this.contextKeyService;
132
135
  if (current) {
133
136
  const resetCurrent = () => {
134
137
  this.setCurrent(undefined);
@@ -145,7 +148,7 @@ export class TabBarToolbar extends ReactWidget {
145
148
  this.keybindingContextKeys.clear();
146
149
  return <React.Fragment>
147
150
  {this.renderMore()}
148
- {[...this.inline.values()].map(item => item.render(this.current))}
151
+ {[...this.inline.values()].map(item => item.render(this.current, this.contextMatcher))}
149
152
  </React.Fragment>;
150
153
  }
151
154
 
@@ -199,7 +202,7 @@ export class TabBarToolbar extends ReactWidget {
199
202
  args: [this.current],
200
203
  anchor,
201
204
  context: this.current?.node || this.node,
202
- contextKeyService: this.contextKeyService,
205
+ contextKeyService: this.contextMatcher,
203
206
  includeAnchorArg: false,
204
207
  onHide: () => toDisposeOnHide.dispose()
205
208
  });
@@ -14,7 +14,7 @@
14
14
  // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
15
  // *****************************************************************************
16
16
 
17
- import { ContextKeyService } from '../../context-key-service';
17
+ import { ContextKeyService, ContextMatcher } from '../../context-key-service';
18
18
  import { ReactTabBarToolbarAction, RenderedToolbarAction, TabBarToolbarActionBase } from './tab-bar-toolbar-types';
19
19
  import { Widget } from '@lumino/widgets';
20
20
  import { LabelIcon, LabelParser } from '../../label-parser';
@@ -29,10 +29,18 @@ import { ActionMenuNode, GroupImpl, MenuNode } from '../../../common/menu';
29
29
  export interface TabBarToolbarItem {
30
30
  id: string;
31
31
  when?: string;
32
- isVisible(widget: Widget): boolean;
32
+ /**
33
+ * @param contextMatcher evaluates `when` clauses with the values that describe the given widget. Keys
34
+ * scoped to a DOM node keep resolving against the focused element, as no context node is passed here.
35
+ */
36
+ isVisible(widget: Widget, contextMatcher: ContextMatcher): boolean;
33
37
  isEnabled(widget: Widget): boolean;
34
38
  isToggled(widget: Widget): boolean;
35
- render(widget?: Widget): React.ReactNode;
39
+ /**
40
+ * @param contextMatcher evaluates `when` clauses with the values that describe the given widget. Defaults
41
+ * to the ambient context, for toolbars that render items with no owning widget.
42
+ */
43
+ render(widget?: Widget, contextMatcher?: ContextMatcher): React.ReactNode;
36
44
  onDidChange?: Event<void>;
37
45
  group?: string;
38
46
  priority?: number;
@@ -68,12 +76,12 @@ class AbstractToolbarItemImpl<T extends TabBarToolbarActionBase> {
68
76
  return this.action.onDidChange;
69
77
  }
70
78
 
71
- isVisible(widget: Widget): boolean {
79
+ isVisible(widget: Widget, contextMatcher: ContextMatcher): boolean {
72
80
  if (this.action.isVisible) {
73
81
  return this.action.isVisible(widget);
74
82
  }
75
83
  const actionVisible = !this.action.command || this.commandRegistry.isVisible(this.action.command, widget);
76
- const contextMatches = !this.action.when || this.contextKeyService.match(this.action.when);
84
+ const contextMatches = !this.action.when || contextMatcher.match(this.action.when);
77
85
 
78
86
  return actionVisible && contextMatches;
79
87
  }
@@ -219,10 +219,17 @@
219
219
  padding: 0 var(--theia-ui-padding);
220
220
  }
221
221
 
222
+ /* Centered as a flex line rather than by text alignment: a codicon is a 16px
223
+ inline-block on the same baseline as the 13px label, so in normal flow it
224
+ rides above the text and stretches the line box. */
222
225
  .theia-TreeContainer .theia-WelcomeView .theia-WelcomeViewButton {
223
226
  width: 100%;
224
227
  max-width: var(--theia-welcomeView-button-maxWidth);
225
228
  margin: auto;
229
+ display: flex;
230
+ align-items: center;
231
+ justify-content: center;
232
+ gap: calc(var(--theia-ui-padding) / 2);
226
233
  }
227
234
 
228
235
  .theia-TreeContainer .theia-WelcomeView .theia-WelcomeViewCommandLink {