@theia/core 1.50.1 → 1.52.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 (167) hide show
  1. package/README.md +11 -7
  2. package/i18n/nls.cs.json +4 -7
  3. package/i18n/nls.de.json +4 -7
  4. package/i18n/nls.es.json +4 -7
  5. package/i18n/nls.fr.json +4 -7
  6. package/i18n/nls.hu.json +4 -7
  7. package/i18n/nls.it.json +4 -7
  8. package/i18n/nls.ja.json +4 -7
  9. package/i18n/nls.json +4 -7
  10. package/i18n/nls.pl.json +4 -7
  11. package/i18n/nls.pt-br.json +4 -7
  12. package/i18n/nls.pt-pt.json +4 -7
  13. package/i18n/nls.ru.json +4 -7
  14. package/i18n/nls.zh-cn.json +4 -7
  15. package/lib/browser/authentication-service.d.ts +6 -3
  16. package/lib/browser/authentication-service.d.ts.map +1 -1
  17. package/lib/browser/authentication-service.js +11 -1
  18. package/lib/browser/authentication-service.js.map +1 -1
  19. package/lib/browser/browser.d.ts +6 -1
  20. package/lib/browser/browser.d.ts.map +1 -1
  21. package/lib/browser/browser.js +7 -2
  22. package/lib/browser/browser.js.map +1 -1
  23. package/lib/browser/common-frontend-contribution.d.ts.map +1 -1
  24. package/lib/browser/common-frontend-contribution.js +6 -5
  25. package/lib/browser/common-frontend-contribution.js.map +1 -1
  26. package/lib/browser/frontend-application-module.d.ts.map +1 -1
  27. package/lib/browser/frontend-application-module.js +3 -3
  28. package/lib/browser/frontend-application-module.js.map +1 -1
  29. package/lib/browser/http-open-handler.d.ts +1 -0
  30. package/lib/browser/http-open-handler.d.ts.map +1 -1
  31. package/lib/browser/http-open-handler.js +5 -3
  32. package/lib/browser/http-open-handler.js.map +1 -1
  33. package/lib/browser/menu/browser-menu-plugin.d.ts +2 -1
  34. package/lib/browser/menu/browser-menu-plugin.d.ts.map +1 -1
  35. package/lib/browser/menu/browser-menu-plugin.js +22 -19
  36. package/lib/browser/menu/browser-menu-plugin.js.map +1 -1
  37. package/lib/browser/messaging/service-connection-provider.d.ts +15 -1
  38. package/lib/browser/messaging/service-connection-provider.d.ts.map +1 -1
  39. package/lib/browser/messaging/service-connection-provider.js +15 -1
  40. package/lib/browser/messaging/service-connection-provider.js.map +1 -1
  41. package/lib/browser/saveable.d.ts +14 -1
  42. package/lib/browser/saveable.d.ts.map +1 -1
  43. package/lib/browser/saveable.js +10 -1
  44. package/lib/browser/saveable.js.map +1 -1
  45. package/lib/browser/shell/additional-views-menu-widget.d.ts +2 -2
  46. package/lib/browser/shell/additional-views-menu-widget.d.ts.map +1 -1
  47. package/lib/browser/shell/additional-views-menu-widget.js +8 -4
  48. package/lib/browser/shell/additional-views-menu-widget.js.map +1 -1
  49. package/lib/browser/shell/application-shell.d.ts +7 -2
  50. package/lib/browser/shell/application-shell.d.ts.map +1 -1
  51. package/lib/browser/shell/application-shell.js +14 -1
  52. package/lib/browser/shell/application-shell.js.map +1 -1
  53. package/lib/browser/shell/side-panel-handler.d.ts.map +1 -1
  54. package/lib/browser/shell/side-panel-handler.js +2 -1
  55. package/lib/browser/shell/side-panel-handler.js.map +1 -1
  56. package/lib/browser/shell/sidebar-menu-widget.d.ts +14 -1
  57. package/lib/browser/shell/sidebar-menu-widget.d.ts.map +1 -1
  58. package/lib/browser/shell/sidebar-menu-widget.js +51 -13
  59. package/lib/browser/shell/sidebar-menu-widget.js.map +1 -1
  60. package/lib/browser/shell/split-panels.d.ts +1 -1
  61. package/lib/browser/shell/split-panels.d.ts.map +1 -1
  62. package/lib/browser/shell/split-panels.js +4 -3
  63. package/lib/browser/shell/split-panels.js.map +1 -1
  64. package/lib/browser/shell/tab-bars.d.ts +5 -4
  65. package/lib/browser/shell/tab-bars.d.ts.map +1 -1
  66. package/lib/browser/shell/tab-bars.js +41 -52
  67. package/lib/browser/shell/tab-bars.js.map +1 -1
  68. package/lib/browser/shell/theia-dock-panel.d.ts +7 -1
  69. package/lib/browser/shell/theia-dock-panel.d.ts.map +1 -1
  70. package/lib/browser/shell/theia-dock-panel.js +5 -1
  71. package/lib/browser/shell/theia-dock-panel.js.map +1 -1
  72. package/lib/browser/widget-manager.d.ts +2 -1
  73. package/lib/browser/widget-manager.d.ts.map +1 -1
  74. package/lib/browser/widget-manager.js +16 -9
  75. package/lib/browser/widget-manager.js.map +1 -1
  76. package/lib/browser/widget-open-handler.js +1 -1
  77. package/lib/browser/widget-open-handler.js.map +1 -1
  78. package/lib/browser/widgets/react-renderer.d.ts.map +1 -1
  79. package/lib/browser/widgets/react-renderer.js +4 -1
  80. package/lib/browser/widgets/react-renderer.js.map +1 -1
  81. package/lib/browser-only/frontend-only-application-module.d.ts.map +1 -1
  82. package/lib/browser-only/frontend-only-application-module.js +1 -0
  83. package/lib/browser-only/frontend-only-application-module.js.map +1 -1
  84. package/lib/common/application-protocol.d.ts +1 -0
  85. package/lib/common/application-protocol.d.ts.map +1 -1
  86. package/lib/common/menu/menu-model-registry.d.ts +6 -0
  87. package/lib/common/menu/menu-model-registry.d.ts.map +1 -1
  88. package/lib/common/menu/menu-model-registry.js +31 -5
  89. package/lib/common/menu/menu-model-registry.js.map +1 -1
  90. package/lib/common/preferences/preference-schema.d.ts +6 -0
  91. package/lib/common/preferences/preference-schema.d.ts.map +1 -1
  92. package/lib/common/preferences/preference-schema.js.map +1 -1
  93. package/lib/common/quick-pick-service.d.ts +0 -9
  94. package/lib/common/quick-pick-service.d.ts.map +1 -1
  95. package/lib/common/quick-pick-service.js.map +1 -1
  96. package/lib/electron-browser/menu/electron-main-menu-factory.d.ts.map +1 -1
  97. package/lib/electron-browser/menu/electron-main-menu-factory.js +3 -0
  98. package/lib/electron-browser/menu/electron-main-menu-factory.js.map +1 -1
  99. package/lib/electron-browser/preload.js +3 -3
  100. package/lib/electron-browser/preload.js.map +1 -1
  101. package/lib/electron-browser/window/electron-window-module.d.ts.map +1 -1
  102. package/lib/electron-browser/window/electron-window-module.js +11 -7
  103. package/lib/electron-browser/window/electron-window-module.js.map +1 -1
  104. package/lib/electron-browser/window/electron-window-service.js +1 -1
  105. package/lib/electron-browser/window/electron-window-service.js.map +1 -1
  106. package/lib/electron-browser/window/external-app-open-handler.d.ts +12 -0
  107. package/lib/electron-browser/window/external-app-open-handler.d.ts.map +1 -0
  108. package/lib/electron-browser/window/external-app-open-handler.js +42 -0
  109. package/lib/electron-browser/window/external-app-open-handler.js.map +1 -0
  110. package/lib/electron-common/electron-api.d.ts +5 -2
  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-main-application.d.ts +1 -2
  114. package/lib/electron-main/electron-main-application.d.ts.map +1 -1
  115. package/lib/electron-main/electron-main-application.js +66 -32
  116. package/lib/electron-main/electron-main-application.js.map +1 -1
  117. package/lib/electron-main/electron-main-constants.d.ts +1 -0
  118. package/lib/electron-main/electron-main-constants.d.ts.map +1 -1
  119. package/lib/electron-main/theia-electron-window.d.ts +1 -1
  120. package/lib/electron-main/theia-electron-window.d.ts.map +1 -1
  121. package/lib/electron-main/theia-electron-window.js +8 -3
  122. package/lib/electron-main/theia-electron-window.js.map +1 -1
  123. package/lib/node/application-server.d.ts +1 -0
  124. package/lib/node/application-server.d.ts.map +1 -1
  125. package/lib/node/application-server.js +3 -0
  126. package/lib/node/application-server.js.map +1 -1
  127. package/lib/node/messaging/websocket-frontend-connection-service.d.ts +1 -0
  128. package/lib/node/messaging/websocket-frontend-connection-service.d.ts.map +1 -1
  129. package/lib/node/messaging/websocket-frontend-connection-service.js +8 -1
  130. package/lib/node/messaging/websocket-frontend-connection-service.js.map +1 -1
  131. package/package.json +7 -7
  132. package/src/browser/authentication-service.ts +16 -4
  133. package/src/browser/browser.ts +6 -1
  134. package/src/browser/common-frontend-contribution.ts +9 -7
  135. package/src/browser/frontend-application-module.ts +6 -5
  136. package/src/browser/http-open-handler.ts +3 -1
  137. package/src/browser/menu/browser-menu-plugin.ts +27 -20
  138. package/src/browser/messaging/service-connection-provider.ts +15 -1
  139. package/src/browser/saveable.ts +17 -1
  140. package/src/browser/shell/additional-views-menu-widget.tsx +5 -5
  141. package/src/browser/shell/application-shell.ts +21 -4
  142. package/src/browser/shell/side-panel-handler.ts +2 -1
  143. package/src/browser/shell/sidebar-menu-widget.tsx +63 -20
  144. package/src/browser/shell/split-panels.ts +4 -3
  145. package/src/browser/shell/tab-bars.ts +40 -57
  146. package/src/browser/shell/theia-dock-panel.ts +13 -3
  147. package/src/browser/style/sidepanel.css +6 -3
  148. package/src/browser/style/tabs.css +12 -1
  149. package/src/browser/widget-manager.ts +19 -11
  150. package/src/browser/widget-open-handler.ts +3 -3
  151. package/src/browser/widgets/react-renderer.tsx +4 -1
  152. package/src/browser-only/frontend-only-application-module.ts +1 -0
  153. package/src/common/application-protocol.ts +1 -0
  154. package/src/common/menu/menu-model-registry.ts +36 -5
  155. package/src/common/preferences/preference-schema.ts +6 -0
  156. package/src/common/quick-pick-service.ts +0 -3
  157. package/src/electron-browser/menu/electron-main-menu-factory.ts +3 -0
  158. package/src/electron-browser/preload.ts +3 -3
  159. package/src/electron-browser/window/electron-window-module.ts +11 -7
  160. package/src/electron-browser/window/electron-window-service.ts +1 -1
  161. package/src/electron-browser/window/external-app-open-handler.ts +42 -0
  162. package/src/electron-common/electron-api.ts +6 -2
  163. package/src/electron-main/electron-main-application.ts +76 -35
  164. package/src/electron-main/electron-main-constants.ts +4 -3
  165. package/src/electron-main/theia-electron-window.ts +7 -3
  166. package/src/node/application-server.ts +4 -0
  167. package/src/node/messaging/websocket-frontend-connection-service.ts +11 -1
@@ -619,7 +619,7 @@ export class TabBarRenderer extends TabBar.Renderer {
619
619
  cssClasses: ['extended-tab-preview'],
620
620
  visualPreview: this.corePreferences?.['window.tabbar.enhancedPreview'] === 'visual' ? width => this.renderVisualPreview(width, title) : undefined
621
621
  });
622
- } else {
622
+ } else if (title.caption) {
623
623
  this.hoverService.requestHover({
624
624
  content: title.caption,
625
625
  target: event.currentTarget,
@@ -1084,8 +1084,6 @@ export class SideTabBar extends ScrollableTabBar {
1084
1084
  startIndex: number
1085
1085
  };
1086
1086
 
1087
- protected _rowGap: number;
1088
-
1089
1087
  constructor(options?: TabBar.IOptions<Widget> & PerfectScrollbar.Options) {
1090
1088
  super(options);
1091
1089
 
@@ -1142,31 +1140,6 @@ export class SideTabBar extends ScrollableTabBar {
1142
1140
  }
1143
1141
  }
1144
1142
 
1145
- // Queries the tabRowGap value of the content node. Needed to properly compute overflowing
1146
- // tabs that should be hidden
1147
- protected get tabRowGap(): number {
1148
- // We assume that the tab row gap is static i.e. we compute it once an then cache it
1149
- if (!this._rowGap) {
1150
- this._rowGap = this.computeTabRowGap();
1151
- }
1152
- return this._rowGap;
1153
-
1154
- }
1155
-
1156
- protected computeTabRowGap(): number {
1157
- const style = window.getComputedStyle(this.contentNode);
1158
- const rowGapStyle = style.getPropertyValue('row-gap');
1159
- const numericValue = parseFloat(rowGapStyle);
1160
- const unit = rowGapStyle.match(/[a-zA-Z]+/)?.[0];
1161
-
1162
- const tempDiv = document.createElement('div');
1163
- tempDiv.style.height = '1' + unit;
1164
- document.body.appendChild(tempDiv);
1165
- const rowGapValue = numericValue * tempDiv.offsetHeight;
1166
- document.body.removeChild(tempDiv);
1167
- return rowGapValue;
1168
- }
1169
-
1170
1143
  /**
1171
1144
  * Reveal the tab with the given index by moving it into the non-overflowing tabBar section
1172
1145
  * if necessary.
@@ -1207,18 +1180,13 @@ export class SideTabBar extends ScrollableTabBar {
1207
1180
  const hiddenContent = this.hiddenContentNode;
1208
1181
  const n = hiddenContent.children.length;
1209
1182
  const renderData = new Array<Partial<SideBarRenderData>>(n);
1210
- const availableWidth = this.node.clientHeight - this.tabRowGap;
1211
- let actualWidth = 0;
1212
- let overflowStartIndex = -1;
1213
1183
  for (let i = 0; i < n; i++) {
1214
1184
  const hiddenTab = hiddenContent.children[i];
1215
- // Extract tab padding from the computed style
1185
+ // Extract tab padding, and margin from the computed style
1216
1186
  const tabStyle = window.getComputedStyle(hiddenTab);
1217
- const paddingTop = parseFloat(tabStyle.paddingTop!);
1218
- const paddingBottom = parseFloat(tabStyle.paddingBottom!);
1219
1187
  const rd: Partial<SideBarRenderData> = {
1220
- paddingTop,
1221
- paddingBottom
1188
+ paddingTop: parseFloat(tabStyle.paddingTop!),
1189
+ paddingBottom: parseFloat(tabStyle.paddingBottom!)
1222
1190
  };
1223
1191
  // Extract label size from the DOM
1224
1192
  const labelElements = hiddenTab.getElementsByClassName('p-TabBar-tabLabel');
@@ -1231,38 +1199,21 @@ export class SideTabBar extends ScrollableTabBar {
1231
1199
  if (iconElements.length === 1) {
1232
1200
  const icon = iconElements[0];
1233
1201
  rd.iconSize = { width: icon.clientWidth, height: icon.clientHeight };
1234
- actualWidth += icon.clientHeight + paddingTop + paddingBottom + this.tabRowGap;
1235
-
1236
- if (actualWidth > availableWidth && i !== 0) {
1237
- rd.visible = false;
1238
- if (overflowStartIndex === -1) {
1239
- overflowStartIndex = i;
1240
- }
1241
- }
1242
- renderData[i] = rd;
1243
1202
  }
1244
- }
1245
1203
 
1246
- // Special handling if only one element is overflowing.
1247
- if (overflowStartIndex === n - 1 && renderData[overflowStartIndex]) {
1248
- if (!this.tabsOverflowData) {
1249
- overflowStartIndex--;
1250
- renderData[overflowStartIndex].visible = false;
1251
- } else {
1252
- renderData[overflowStartIndex].visible = true;
1253
- overflowStartIndex = -1;
1254
- }
1204
+ renderData[i] = rd;
1255
1205
  }
1256
1206
  // Render into the visible node
1257
1207
  this.renderTabs(this.contentNode, renderData);
1258
- this.computeOverflowingTabsData(overflowStartIndex);
1208
+ this.computeOverflowingTabsData();
1259
1209
  });
1260
1210
  }
1261
1211
  }
1262
1212
 
1263
- protected computeOverflowingTabsData(startIndex: number): void {
1213
+ protected computeOverflowingTabsData(): void {
1264
1214
  // ensure that render tabs has completed
1265
1215
  window.requestAnimationFrame(() => {
1216
+ const startIndex = this.hideOverflowingTabs();
1266
1217
  if (startIndex === -1) {
1267
1218
  if (this.tabsOverflowData) {
1268
1219
  this.tabsOverflowData = undefined;
@@ -1286,6 +1237,38 @@ export class SideTabBar extends ScrollableTabBar {
1286
1237
  });
1287
1238
  }
1288
1239
 
1240
+ /**
1241
+ * Hide overflowing tabs and return the index of the first hidden tab.
1242
+ */
1243
+ protected hideOverflowingTabs(): number {
1244
+ const availableHeight = this.node.clientHeight;
1245
+ const invisibleClass = 'p-mod-invisible';
1246
+ let startIndex = -1;
1247
+ const n = this.contentNode.children.length;
1248
+ for (let i = 0; i < n; i++) {
1249
+ const tab = this.contentNode.children[i] as HTMLLIElement;
1250
+ if (tab.offsetTop + tab.offsetHeight >= availableHeight) {
1251
+ tab.classList.add(invisibleClass);
1252
+ if (startIndex === -1) {
1253
+ startIndex = i;
1254
+ /* If only one element is overflowing and the additional menu widget is visible (i.e. this.tabsOverflowData is set)
1255
+ * there might already be enough space to show the last tab. In this case, we need to include the size of the
1256
+ * additional menu widget and recheck if the last tab is visible */
1257
+ if (startIndex === n - 1 && this.tabsOverflowData) {
1258
+ const additionalViewsMenu = this.node.parentElement?.querySelector('.theia-additional-views-menu') as HTMLDivElement;
1259
+ if (tab.offsetTop + tab.offsetHeight < availableHeight + additionalViewsMenu.offsetHeight) {
1260
+ tab.classList.remove(invisibleClass);
1261
+ startIndex = -1;
1262
+ }
1263
+ }
1264
+ }
1265
+ } else {
1266
+ tab.classList.remove(invisibleClass);
1267
+ }
1268
+ }
1269
+ return startIndex;
1270
+ }
1271
+
1289
1272
  /**
1290
1273
  * Render the tab bar using the given DOM element as host. The optional `renderData` is forwarded
1291
1274
  * to the TabBarRenderer.
@@ -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 { find, toArray, ArrayExt } from '@phosphor/algorithm';
17
+ import { find, toArray } from '@phosphor/algorithm';
18
18
  import { TabBar, Widget, DockPanel, Title, DockLayout } from '@phosphor/widgets';
19
19
  import { Signal } from '@phosphor/signaling';
20
20
  import { Disposable, DisposableCollection } from '../../common/disposable';
@@ -103,7 +103,7 @@ export class TheiaDockPanel extends DockPanel {
103
103
  }
104
104
 
105
105
  findTabBar(title: Title<Widget>): TabBar<Widget> | undefined {
106
- return find(this.tabBars(), bar => ArrayExt.firstIndexOf(bar.titles, title) > -1);
106
+ return find(this.tabBars(), bar => bar.titles.includes(title));
107
107
  }
108
108
 
109
109
  protected readonly toDisposeOnMarkAsCurrent = new DisposableCollection();
@@ -133,11 +133,14 @@ export class TheiaDockPanel extends DockPanel {
133
133
  }
134
134
  }
135
135
 
136
- override addWidget(widget: Widget, options?: DockPanel.IAddOptions): void {
136
+ override addWidget(widget: Widget, options?: TheiaDockPanel.AddOptions): void {
137
137
  if (this.mode === 'single-document' && widget.parent === this) {
138
138
  return;
139
139
  }
140
140
  super.addWidget(widget, options);
141
+ if (options?.closeRef) {
142
+ options.ref?.close();
143
+ }
141
144
  this.widgetAdded.emit(widget);
142
145
  this.markActiveTabBar(widget.title);
143
146
  }
@@ -252,4 +255,11 @@ export namespace TheiaDockPanel {
252
255
  export interface Factory {
253
256
  (options?: DockPanel.IOptions): TheiaDockPanel;
254
257
  }
258
+
259
+ export interface AddOptions extends DockPanel.IAddOptions {
260
+ /**
261
+ * Whether to also close the widget referenced by `ref`.
262
+ */
263
+ closeRef?: boolean
264
+ }
255
265
  }
@@ -186,23 +186,26 @@
186
186
  flex-direction: column-reverse;
187
187
  }
188
188
 
189
+ .p-Widget .theia-sidebar-menu-item {
190
+ cursor: pointer;
191
+ }
192
+
189
193
  .p-Widget.theia-sidebar-menu i {
190
194
  padding: var(--theia-private-sidebar-tab-padding-top-and-bottom)
191
195
  var(--theia-private-sidebar-tab-padding-left-and-right);
192
196
  display: flex;
193
197
  justify-content: center;
194
198
  align-items: center;
195
- cursor: pointer;
196
199
  color: var(--theia-activityBar-inactiveForeground);
197
200
  background-color: var(--theia-activityBar-background);
198
201
  font-size: var(--theia-private-sidebar-icon-size);
199
202
  }
200
203
 
201
- .theia-sidebar-menu i:hover {
204
+ .theia-sidebar-menu .theia-sidebar-menu-item:hover i {
202
205
  color: var(--theia-activityBar-foreground);
203
206
  }
204
207
 
205
- .theia-sidebar-menu > i.codicon-menu {
208
+ .theia-sidebar-menu i.theia-compact-menu {
206
209
  font-size: 16px;
207
210
  }
208
211
 
@@ -177,6 +177,17 @@
177
177
  padding-right: 8px;
178
178
  }
179
179
 
180
+ .p-TabBar.theia-app-centers .p-TabBar-tabIcon[class*="plugin-icon-"],
181
+ .p-TabBar-tab.p-mod-drag-image .p-TabBar-tabIcon[class*="plugin-icon-"] {
182
+ background: none;
183
+ height: var(--theia-icon-size);
184
+ }
185
+
186
+ .p-TabBar.theia-app-centers .p-TabBar-tabIcon[class*="plugin-icon-"]::before,
187
+ .p-TabBar-tab.p-mod-drag-image .p-TabBar-tabIcon[class*="plugin-icon-"]::before {
188
+ display: inline-block;
189
+ }
190
+
180
191
  /* codicons */
181
192
  .p-TabBar.theia-app-centers .p-TabBar-tabIcon.codicon,
182
193
  .p-TabBar-tab.p-mod-drag-image .p-TabBar-tabIcon.codicon {
@@ -305,7 +316,7 @@
305
316
  display: none !important;
306
317
  }
307
318
 
308
- .p-TabBar .theia-badge-decorator-sidebar {
319
+ .theia-badge-decorator-sidebar {
309
320
  background-color: var(--theia-activityBarBadge-background);
310
321
  border-radius: 20px;
311
322
  color: var(--theia-activityBarBadge-foreground);
@@ -114,7 +114,7 @@ export class WidgetManager {
114
114
 
115
115
  protected _cachedFactories: Map<string, WidgetFactory>;
116
116
  protected readonly widgets = new Map<string, Widget>();
117
- protected readonly pendingWidgetPromises = new Map<string, MaybePromise<Widget>>();
117
+ protected readonly pendingWidgetPromises = new Map<string, Promise<Widget>>();
118
118
 
119
119
  @inject(ContributionProvider) @named(WidgetFactory)
120
120
  protected readonly factoryProvider: ContributionProvider<WidgetFactory>;
@@ -207,9 +207,9 @@ export class WidgetManager {
207
207
  return widget as T;
208
208
  }
209
209
  }
210
- for (const [key, widget] of this.pendingWidgetPromises.entries()) {
210
+ for (const [key, widgetPromise] of this.pendingWidgetPromises.entries()) {
211
211
  if (this.testPredicate(key, factoryId, predicate)) {
212
- return widget as T;
212
+ return widgetPromise as Promise<T>;
213
213
  }
214
214
  }
215
215
  }
@@ -244,18 +244,26 @@ export class WidgetManager {
244
244
  if (!factory) {
245
245
  throw Error("No widget factory '" + factoryId + "' has been registered.");
246
246
  }
247
- try {
248
- const widgetPromise = factory.createWidget(options);
249
- this.pendingWidgetPromises.set(key, widgetPromise);
250
- const widget = await widgetPromise;
251
- await WaitUntilEvent.fire(this.onWillCreateWidgetEmitter, { factoryId, widget });
247
+ const widgetPromise = this.doCreateWidget<T>(factory, options).then(widget => {
252
248
  this.widgets.set(key, widget);
253
249
  widget.disposed.connect(() => this.widgets.delete(key));
254
250
  this.onDidCreateWidgetEmitter.fire({ factoryId, widget });
255
- return widget as T;
256
- } finally {
257
- this.pendingWidgetPromises.delete(key);
251
+ return widget;
252
+ }).finally(() => this.pendingWidgetPromises.delete(key));
253
+ this.pendingWidgetPromises.set(key, widgetPromise);
254
+ return widgetPromise;
255
+ }
256
+
257
+ protected async doCreateWidget<T extends Widget>(factory: WidgetFactory, options?: any): Promise<T> {
258
+ const widget = await factory.createWidget(options);
259
+ // Note: the widget creation process also includes the 'onWillCreateWidget' part, which can potentially fail
260
+ try {
261
+ await WaitUntilEvent.fire(this.onWillCreateWidgetEmitter, { factoryId: factory.id, widget });
262
+ } catch (e) {
263
+ widget.dispose();
264
+ throw e;
258
265
  }
266
+ return widget as T;
259
267
  }
260
268
 
261
269
  /**
@@ -96,7 +96,7 @@ export abstract class WidgetOpenHandler<W extends BaseWidget> implements OpenHan
96
96
  ...options
97
97
  };
98
98
  if (!widget.isAttached) {
99
- this.shell.addWidget(widget, op.widgetOptions || { area: 'main' });
99
+ await this.shell.addWidget(widget, op.widgetOptions || { area: 'main' });
100
100
  }
101
101
  if (op.mode === 'activate') {
102
102
  await this.shell.activateWidget(widget.id);
@@ -143,12 +143,12 @@ export abstract class WidgetOpenHandler<W extends BaseWidget> implements OpenHan
143
143
 
144
144
  protected getWidget(uri: URI, options?: WidgetOpenerOptions): Promise<W | undefined> {
145
145
  const widgetOptions = this.createWidgetOptions(uri, options);
146
- return this.widgetManager.getWidget<W>(this.id, widgetOptions);
146
+ return this.widgetManager.getWidget(this.id, widgetOptions);
147
147
  }
148
148
 
149
149
  protected getOrCreateWidget(uri: URI, options?: WidgetOpenerOptions): Promise<W> {
150
150
  const widgetOptions = this.createWidgetOptions(uri, options);
151
- return this.widgetManager.getOrCreateWidget<W>(this.id, widgetOptions);
151
+ return this.widgetManager.getOrCreateWidget(this.id, widgetOptions);
152
152
  }
153
153
 
154
154
  protected abstract createWidgetOptions(uri: URI, options?: WidgetOpenerOptions): Object;
@@ -41,7 +41,10 @@ export class ReactRenderer implements Disposable {
41
41
  }
42
42
 
43
43
  render(): void {
44
- this.hostRoot.render(<React.Fragment>{this.doRender()}</React.Fragment>);
44
+ // Ignore all render calls after the host element has unmounted
45
+ if (!this.toDispose.disposed) {
46
+ this.hostRoot.render(<React.Fragment>{this.doRender()}</React.Fragment>);
47
+ }
45
48
  }
46
49
 
47
50
  protected doRender(): React.ReactNode {
@@ -56,6 +56,7 @@ export const frontendOnlyApplicationModule = new ContainerModule((bind, unbind,
56
56
  getExtensionsInfos: async (): Promise<ExtensionInfo[]> => [],
57
57
  getApplicationInfo: async (): Promise<ApplicationInfo | undefined> => undefined,
58
58
  getApplicationRoot: async (): Promise<string> => '',
59
+ getApplicationPlatform: () => Promise.resolve('web'),
59
60
  getBackendOS: async (): Promise<OS.Type> => OS.Type.Linux
60
61
  };
61
62
  if (isBound(ApplicationServer)) {
@@ -24,6 +24,7 @@ export interface ApplicationServer {
24
24
  getExtensionsInfos(): Promise<ExtensionInfo[]>;
25
25
  getApplicationInfo(): Promise<ApplicationInfo | undefined>;
26
26
  getApplicationRoot(): Promise<string>;
27
+ getApplicationPlatform(): Promise<string>;
27
28
  /**
28
29
  * @deprecated since 1.25.0. Use `OS.backend.type()` instead.
29
30
  */
@@ -18,6 +18,7 @@ import { inject, injectable, named } from 'inversify';
18
18
  import { Command, CommandRegistry } from '../command';
19
19
  import { ContributionProvider } from '../contribution-provider';
20
20
  import { Disposable } from '../disposable';
21
+ import { Emitter, Event } from '../event';
21
22
  import { ActionMenuNode } from './action-menu-node';
22
23
  import { CompositeMenuNode, CompositeMenuNodeWrapper } from './composite-menu-node';
23
24
  import { CompoundMenuNode, MenuAction, MenuNode, MenuNodeMetadata, MenuPath, MutableCompoundMenuNode, SubMenuOptions } from './menu-types';
@@ -68,6 +69,14 @@ export class MenuModelRegistry {
68
69
  protected readonly root = new CompositeMenuNode('');
69
70
  protected readonly independentSubmenus = new Map<string, MutableCompoundMenuNode>();
70
71
 
72
+ protected readonly onDidChangeEmitter = new Emitter<void>();
73
+
74
+ get onDidChange(): Event<void> {
75
+ return this.onDidChangeEmitter.event;
76
+ }
77
+
78
+ protected isReady = false;
79
+
71
80
  constructor(
72
81
  @inject(ContributionProvider) @named(MenuContribution)
73
82
  protected readonly contributions: ContributionProvider<MenuContribution>,
@@ -78,6 +87,7 @@ export class MenuModelRegistry {
78
87
  for (const contrib of this.contributions.getContributions()) {
79
88
  contrib.registerMenus(this);
80
89
  }
90
+ this.isReady = true;
81
91
  }
82
92
 
83
93
  /**
@@ -97,7 +107,9 @@ export class MenuModelRegistry {
97
107
  */
98
108
  registerMenuNode(menuPath: MenuPath | string, menuNode: MenuNode, group?: string): Disposable {
99
109
  const parent = this.getMenuNode(menuPath, group);
100
- return parent.addNode(menuNode);
110
+ const disposable = parent.addNode(menuNode);
111
+ this.fireChangeEvent();
112
+ return this.changeEventOnDispose(disposable);
101
113
  }
102
114
 
103
115
  getMenuNode(menuPath: MenuPath | string, group?: string): MutableCompoundMenuNode {
@@ -137,13 +149,15 @@ export class MenuModelRegistry {
137
149
  const groupPath = index === 0 ? [] : menuPath.slice(0, index);
138
150
  const parent = this.findGroup(groupPath, options);
139
151
  let groupNode = this.findSubMenu(parent, menuId, options);
152
+ let disposable = Disposable.NULL;
140
153
  if (!groupNode) {
141
154
  groupNode = new CompositeMenuNode(menuId, label, options, parent);
142
- return parent.addNode(groupNode);
155
+ disposable = this.changeEventOnDispose(parent.addNode(groupNode));
143
156
  } else {
144
157
  groupNode.updateOptions({ ...options, label });
145
- return Disposable.NULL;
146
158
  }
159
+ this.fireChangeEvent();
160
+ return disposable;
147
161
  }
148
162
 
149
163
  registerIndependentSubmenu(id: string, label: string, options?: SubMenuOptions): Disposable {
@@ -151,7 +165,7 @@ export class MenuModelRegistry {
151
165
  console.debug(`Independent submenu with path ${id} registered, but given ID already exists.`);
152
166
  }
153
167
  this.independentSubmenus.set(id, new CompositeMenuNode(id, label, options));
154
- return { dispose: () => this.independentSubmenus.delete(id) };
168
+ return this.changeEventOnDispose(Disposable.create(() => this.independentSubmenus.delete(id)));
155
169
  }
156
170
 
157
171
  linkSubmenu(parentPath: MenuPath | string, childId: string | MenuPath, options?: SubMenuOptions, group?: string): Disposable {
@@ -175,7 +189,9 @@ export class MenuModelRegistry {
175
189
  }
176
190
 
177
191
  const wrapper = new CompositeMenuNodeWrapper(child, parent, options);
178
- return parent.addNode(wrapper);
192
+ const disposable = parent.addNode(wrapper);
193
+ this.fireChangeEvent();
194
+ return this.changeEventOnDispose(disposable);
179
195
  }
180
196
 
181
197
  /**
@@ -207,6 +223,7 @@ export class MenuModelRegistry {
207
223
  if (menuPath) {
208
224
  const parent = this.findGroup(menuPath);
209
225
  parent.removeNode(id);
226
+ this.fireChangeEvent();
210
227
  return;
211
228
  }
212
229
 
@@ -228,6 +245,7 @@ export class MenuModelRegistry {
228
245
  });
229
246
  };
230
247
  recurse(this.root);
248
+ this.fireChangeEvent();
231
249
  }
232
250
 
233
251
  /**
@@ -321,6 +339,19 @@ export class MenuModelRegistry {
321
339
  return true;
322
340
  }
323
341
 
342
+ protected changeEventOnDispose(disposable: Disposable): Disposable {
343
+ return Disposable.create(() => {
344
+ disposable.dispose();
345
+ this.fireChangeEvent();
346
+ });
347
+ }
348
+
349
+ protected fireChangeEvent(): void {
350
+ if (this.isReady) {
351
+ this.onDidChangeEmitter.fire();
352
+ }
353
+ }
354
+
324
355
  /**
325
356
  * Returns the {@link MenuPath path} at which a given menu node can be accessed from this registry, if it can be determined.
326
357
  * Returns `undefined` if the `parent` of any node in the chain is unknown.
@@ -25,6 +25,11 @@ export interface PreferenceSchema {
25
25
  [name: string]: any,
26
26
  scope?: 'application' | 'window' | 'resource' | PreferenceScope,
27
27
  overridable?: boolean;
28
+ /**
29
+ * The title of the preference schema.
30
+ * It is used in the preference UI to associate a localized group of preferences.
31
+ */
32
+ title?: string;
28
33
  properties: PreferenceSchemaProperties
29
34
  }
30
35
  export namespace PreferenceSchema {
@@ -75,6 +80,7 @@ export interface PreferenceSchemaProperty extends PreferenceItem {
75
80
  description?: string;
76
81
  markdownDescription?: string;
77
82
  scope?: 'application' | 'machine' | 'window' | 'resource' | 'language-overridable' | 'machine-overridable' | PreferenceScope;
83
+ tags?: string[];
78
84
  }
79
85
 
80
86
  export interface PreferenceDataProperty extends PreferenceItem {
@@ -18,7 +18,6 @@ import * as fuzzy from 'fuzzy';
18
18
  import { Event } from './event';
19
19
  import { KeySequence } from './keys';
20
20
  import { CancellationToken } from './cancellation';
21
- import { URI as Uri } from 'vscode-uri';
22
21
 
23
22
  export const quickPickServicePath = '/services/quickPick';
24
23
  export const QuickPickService = Symbol('QuickPickService');
@@ -53,7 +52,6 @@ export interface QuickPickItem {
53
52
  description?: string;
54
53
  detail?: string;
55
54
  keySequence?: KeySequence;
56
- iconPath?: { light?: Uri; dark: Uri };
57
55
  iconClasses?: string[];
58
56
  alwaysShow?: boolean;
59
57
  highlights?: QuickPickItemHighlights;
@@ -94,7 +92,6 @@ export interface QuickPickValue<V> extends QuickPickItem {
94
92
  }
95
93
 
96
94
  export interface QuickInputButton {
97
- iconPath?: { light?: Uri; dark: Uri };
98
95
  iconClass?: string;
99
96
  tooltip?: string;
100
97
  /**
@@ -101,6 +101,9 @@ export class ElectronMainMenuFactory extends BrowserMainMenuFactory {
101
101
  this.keybindingRegistry.onKeybindingsChanged(() => {
102
102
  this.setMenuBar();
103
103
  });
104
+ this.menuProvider.onDidChange(() => {
105
+ this.setMenuBar();
106
+ });
104
107
  }
105
108
 
106
109
  async setMenuBar(): Promise<void> {
@@ -79,8 +79,8 @@ const api: TheiaCoreAPI = {
79
79
  showItemInFolder: fsPath => {
80
80
  ipcRenderer.send(CHANNEL_SHOW_ITEM_IN_FOLDER, fsPath);
81
81
  },
82
- openWithSystemApp: fsPath => {
83
- ipcRenderer.send(CHANNEL_OPEN_WITH_SYSTEM_APP, fsPath);
82
+ openWithSystemApp: location => {
83
+ ipcRenderer.send(CHANNEL_OPEN_WITH_SYSTEM_APP, location);
84
84
  },
85
85
  attachSecurityToken: (endpoint: string) => ipcRenderer.invoke(CHANNEL_ATTACH_SECURITY_TOKEN, endpoint),
86
86
 
@@ -199,7 +199,7 @@ const api: TheiaCoreAPI = {
199
199
  ipcRenderer.send(CHANNEL_TOGGLE_FULL_SCREEN);
200
200
  },
201
201
 
202
- requestReload: () => ipcRenderer.send(CHANNEL_REQUEST_RELOAD),
202
+ requestReload: (newUrl?: string) => ipcRenderer.send(CHANNEL_REQUEST_RELOAD, newUrl),
203
203
  restart: () => ipcRenderer.send(CHANNEL_RESTART),
204
204
 
205
205
  applicationStateChanged: state => {
@@ -15,18 +15,20 @@
15
15
  // *****************************************************************************
16
16
 
17
17
  import { ContainerModule } from 'inversify';
18
- import { WindowService } from '../../browser/window/window-service';
19
- import { ElectronWindowService } from './electron-window-service';
20
- import { FrontendApplicationContribution } from '../../browser/frontend-application-contribution';
21
- import { ElectronClipboardService } from '../electron-clipboard-service';
18
+ import { OpenHandler } from '../../browser';
22
19
  import { ClipboardService } from '../../browser/clipboard-service';
20
+ import { FrontendApplicationContribution } from '../../browser/frontend-application-contribution';
21
+ import { FrontendApplicationStateService } from '../../browser/frontend-application-state';
22
+ import { SecondaryWindowService } from '../../browser/window/secondary-window-service';
23
+ import { WindowService } from '../../browser/window/window-service';
23
24
  import { ElectronMainWindowService, electronMainWindowServicePath } from '../../electron-common/electron-main-window-service';
25
+ import { ElectronClipboardService } from '../electron-clipboard-service';
24
26
  import { ElectronIpcConnectionProvider } from '../messaging/electron-ipc-connection-source';
25
- import { bindWindowPreferences } from './electron-window-preferences';
26
- import { FrontendApplicationStateService } from '../../browser/frontend-application-state';
27
27
  import { ElectronFrontendApplicationStateService } from './electron-frontend-application-state';
28
28
  import { ElectronSecondaryWindowService } from './electron-secondary-window-service';
29
- import { SecondaryWindowService } from '../../browser/window/secondary-window-service';
29
+ import { bindWindowPreferences } from './electron-window-preferences';
30
+ import { ElectronWindowService } from './electron-window-service';
31
+ import { ExternalAppOpenHandler } from './external-app-open-handler';
30
32
 
31
33
  export default new ContainerModule((bind, unbind, isBound, rebind) => {
32
34
  bind(ElectronMainWindowService).toDynamicValue(context =>
@@ -38,4 +40,6 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
38
40
  bind(ClipboardService).to(ElectronClipboardService).inSingletonScope();
39
41
  rebind(FrontendApplicationStateService).to(ElectronFrontendApplicationStateService).inSingletonScope();
40
42
  bind(SecondaryWindowService).to(ElectronSecondaryWindowService).inSingletonScope();
43
+ bind(ExternalAppOpenHandler).toSelf().inSingletonScope();
44
+ bind(OpenHandler).toService(ExternalAppOpenHandler);
41
45
  });
@@ -100,7 +100,7 @@ export class ElectronWindowService extends DefaultWindowService {
100
100
  if (params.hash) {
101
101
  newLocation.hash = '#' + params.hash;
102
102
  }
103
- location.assign(newLocation);
103
+ window.electronTheiaCore.requestReload(newLocation.toString());
104
104
  } else {
105
105
  window.electronTheiaCore.requestReload();
106
106
  }
@@ -0,0 +1,42 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2024 TypeFox and others.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+
17
+ import { injectable } from 'inversify';
18
+ import { OpenHandler } from '../../browser/opener-service';
19
+ import URI from '../../common/uri';
20
+ import { HttpOpenHandler } from '../../browser/http-open-handler';
21
+
22
+ export interface ExternalAppOpenHandlerOptions {
23
+ openExternalApp?: boolean
24
+ }
25
+
26
+ @injectable()
27
+ export class ExternalAppOpenHandler implements OpenHandler {
28
+
29
+ static readonly PRIORITY: number = HttpOpenHandler.PRIORITY + 100;
30
+ readonly id = 'external-app';
31
+
32
+ canHandle(uri: URI, options?: ExternalAppOpenHandlerOptions): number {
33
+ return (options && options.openExternalApp) ? ExternalAppOpenHandler.PRIORITY : -1;
34
+ }
35
+
36
+ async open(uri: URI): Promise<undefined> {
37
+ // For files 'file:' scheme, system accepts only the path.
38
+ // For other protocols e.g. 'vscode:' we use the full URI to propagate target app information.
39
+ window.electronTheiaCore.openWithSystemApp(uri.scheme === 'file' ? uri.path.fsPath() : uri.toString(true));
40
+ return undefined;
41
+ }
42
+ }