@theia/core 1.50.1 → 1.51.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 (137) hide show
  1. package/README.md +6 -6
  2. package/i18n/nls.cs.json +0 -7
  3. package/i18n/nls.de.json +0 -7
  4. package/i18n/nls.es.json +0 -7
  5. package/i18n/nls.fr.json +0 -7
  6. package/i18n/nls.hu.json +0 -7
  7. package/i18n/nls.it.json +0 -7
  8. package/i18n/nls.ja.json +0 -7
  9. package/i18n/nls.json +0 -7
  10. package/i18n/nls.pl.json +0 -7
  11. package/i18n/nls.pt-br.json +0 -7
  12. package/i18n/nls.pt-pt.json +0 -7
  13. package/i18n/nls.ru.json +0 -7
  14. package/i18n/nls.zh-cn.json +0 -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 +9 -0
  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/tab-bars.d.ts +5 -4
  61. package/lib/browser/shell/tab-bars.d.ts.map +1 -1
  62. package/lib/browser/shell/tab-bars.js +40 -51
  63. package/lib/browser/shell/tab-bars.js.map +1 -1
  64. package/lib/browser/shell/theia-dock-panel.d.ts +7 -1
  65. package/lib/browser/shell/theia-dock-panel.d.ts.map +1 -1
  66. package/lib/browser/shell/theia-dock-panel.js +5 -1
  67. package/lib/browser/shell/theia-dock-panel.js.map +1 -1
  68. package/lib/browser/widgets/react-renderer.d.ts.map +1 -1
  69. package/lib/browser/widgets/react-renderer.js +4 -1
  70. package/lib/browser/widgets/react-renderer.js.map +1 -1
  71. package/lib/browser-only/frontend-only-application-module.d.ts.map +1 -1
  72. package/lib/browser-only/frontend-only-application-module.js +1 -0
  73. package/lib/browser-only/frontend-only-application-module.js.map +1 -1
  74. package/lib/common/application-protocol.d.ts +1 -0
  75. package/lib/common/application-protocol.d.ts.map +1 -1
  76. package/lib/common/menu/menu-model-registry.d.ts +6 -0
  77. package/lib/common/menu/menu-model-registry.d.ts.map +1 -1
  78. package/lib/common/menu/menu-model-registry.js +31 -5
  79. package/lib/common/menu/menu-model-registry.js.map +1 -1
  80. package/lib/common/preferences/preference-schema.d.ts +1 -0
  81. package/lib/common/preferences/preference-schema.d.ts.map +1 -1
  82. package/lib/common/preferences/preference-schema.js.map +1 -1
  83. package/lib/electron-browser/menu/electron-main-menu-factory.d.ts.map +1 -1
  84. package/lib/electron-browser/menu/electron-main-menu-factory.js +3 -0
  85. package/lib/electron-browser/menu/electron-main-menu-factory.js.map +1 -1
  86. package/lib/electron-browser/preload.js +2 -2
  87. package/lib/electron-browser/preload.js.map +1 -1
  88. package/lib/electron-browser/window/electron-window-module.d.ts.map +1 -1
  89. package/lib/electron-browser/window/electron-window-module.js +11 -7
  90. package/lib/electron-browser/window/electron-window-module.js.map +1 -1
  91. package/lib/electron-browser/window/external-app-open-handler.d.ts +12 -0
  92. package/lib/electron-browser/window/external-app-open-handler.d.ts.map +1 -0
  93. package/lib/electron-browser/window/external-app-open-handler.js +42 -0
  94. package/lib/electron-browser/window/external-app-open-handler.js.map +1 -0
  95. package/lib/electron-common/electron-api.d.ts +4 -1
  96. package/lib/electron-common/electron-api.d.ts.map +1 -1
  97. package/lib/electron-common/electron-api.js.map +1 -1
  98. package/lib/electron-main/electron-main-application.d.ts +1 -2
  99. package/lib/electron-main/electron-main-application.d.ts.map +1 -1
  100. package/lib/electron-main/electron-main-application.js +62 -32
  101. package/lib/electron-main/electron-main-application.js.map +1 -1
  102. package/lib/electron-main/electron-main-constants.d.ts +1 -0
  103. package/lib/electron-main/electron-main-constants.d.ts.map +1 -1
  104. package/lib/node/application-server.d.ts +1 -0
  105. package/lib/node/application-server.d.ts.map +1 -1
  106. package/lib/node/application-server.js +3 -0
  107. package/lib/node/application-server.js.map +1 -1
  108. package/package.json +6 -6
  109. package/src/browser/authentication-service.ts +16 -4
  110. package/src/browser/browser.ts +6 -1
  111. package/src/browser/common-frontend-contribution.ts +9 -7
  112. package/src/browser/frontend-application-module.ts +6 -5
  113. package/src/browser/http-open-handler.ts +3 -1
  114. package/src/browser/menu/browser-menu-plugin.ts +27 -20
  115. package/src/browser/messaging/service-connection-provider.ts +15 -1
  116. package/src/browser/saveable.ts +17 -1
  117. package/src/browser/shell/additional-views-menu-widget.tsx +5 -5
  118. package/src/browser/shell/application-shell.ts +16 -3
  119. package/src/browser/shell/side-panel-handler.ts +2 -1
  120. package/src/browser/shell/sidebar-menu-widget.tsx +63 -20
  121. package/src/browser/shell/tab-bars.ts +39 -56
  122. package/src/browser/shell/theia-dock-panel.ts +13 -3
  123. package/src/browser/style/sidepanel.css +6 -3
  124. package/src/browser/style/tabs.css +1 -1
  125. package/src/browser/widgets/react-renderer.tsx +4 -1
  126. package/src/browser-only/frontend-only-application-module.ts +1 -0
  127. package/src/common/application-protocol.ts +1 -0
  128. package/src/common/menu/menu-model-registry.ts +36 -5
  129. package/src/common/preferences/preference-schema.ts +1 -0
  130. package/src/electron-browser/menu/electron-main-menu-factory.ts +3 -0
  131. package/src/electron-browser/preload.ts +2 -2
  132. package/src/electron-browser/window/electron-window-module.ts +11 -7
  133. package/src/electron-browser/window/external-app-open-handler.ts +42 -0
  134. package/src/electron-common/electron-api.ts +5 -1
  135. package/src/electron-main/electron-main-application.ts +70 -35
  136. package/src/electron-main/electron-main-constants.ts +4 -3
  137. package/src/node/application-server.ts +4 -0
@@ -22,6 +22,7 @@ import { Key } from './keyboard/keys';
22
22
  import { AbstractDialog } from './dialogs';
23
23
  import { nls } from '../common/nls';
24
24
  import { DisposableCollection, isObject } from '../common';
25
+ import { BinaryBuffer } from '../common/buffer';
25
26
 
26
27
  export type AutoSaveMode = 'off' | 'afterDelay' | 'onFocusChange' | 'onWindowChange';
27
28
 
@@ -46,13 +47,17 @@ export interface Saveable {
46
47
  */
47
48
  revert?(options?: Saveable.RevertOptions): Promise<void>;
48
49
  /**
49
- * Creates a snapshot of the dirty state.
50
+ * Creates a snapshot of the dirty state. See also {@link Saveable.Snapshot}.
50
51
  */
51
52
  createSnapshot?(): Saveable.Snapshot;
52
53
  /**
53
54
  * Applies the given snapshot to the dirty state.
54
55
  */
55
56
  applySnapshot?(snapshot: object): void;
57
+ /**
58
+ * Serializes the full state of the saveable item to a binary buffer.
59
+ */
60
+ serialize?(): Promise<BinaryBuffer>;
56
61
  }
57
62
 
58
63
  export interface SaveableSource {
@@ -79,6 +84,7 @@ export class DelegatingSaveable implements Saveable {
79
84
  revert?(options?: Saveable.RevertOptions): Promise<void>;
80
85
  createSnapshot?(): Saveable.Snapshot;
81
86
  applySnapshot?(snapshot: object): void;
87
+ serialize?(): Promise<BinaryBuffer>;
82
88
 
83
89
  protected _delegate?: Saveable;
84
90
  protected toDispose = new DisposableCollection();
@@ -101,6 +107,7 @@ export class DelegatingSaveable implements Saveable {
101
107
  this.revert = delegate.revert?.bind(delegate);
102
108
  this.createSnapshot = delegate.createSnapshot?.bind(delegate);
103
109
  this.applySnapshot = delegate.applySnapshot?.bind(delegate);
110
+ this.serialize = delegate.serialize?.bind(delegate);
104
111
  }
105
112
 
106
113
  }
@@ -114,7 +121,16 @@ export namespace Saveable {
114
121
  soft?: boolean
115
122
  }
116
123
 
124
+ /**
125
+ * A snapshot of a saveable item.
126
+ * Applying a snapshot of a saveable on another (of the same type) using the `applySnapshot` should yield the state of the original saveable.
127
+ */
117
128
  export type Snapshot = { value: string } | { read(): string | null };
129
+ export namespace Snapshot {
130
+ export function read(snapshot: Snapshot): string | undefined {
131
+ return 'value' in snapshot ? snapshot.value : (snapshot.read() ?? undefined);
132
+ }
133
+ }
118
134
  export function isSource(arg: unknown): arg is SaveableSource {
119
135
  return isObject<SaveableSource>(arg) && is(arg.saveable);
120
136
  }
@@ -23,13 +23,13 @@ import { SideTabBar } from './tab-bars';
23
23
  export const AdditionalViewsMenuWidgetFactory = Symbol('AdditionalViewsMenuWidgetFactory');
24
24
  export type AdditionalViewsMenuWidgetFactory = (side: 'left' | 'right') => AdditionalViewsMenuWidget;
25
25
 
26
- export const ADDITIONAL_VIEWS_MENU_PATH: MenuPath = ['additional_views_menu'];
27
-
26
+ export const AdditionalViewsMenuPath = Symbol('AdditionalViewsMenuPath');
28
27
  @injectable()
29
28
  export class AdditionalViewsMenuWidget extends SidebarMenuWidget {
30
29
  static readonly ID = 'sidebar.additional.views';
31
30
 
32
- side: 'left' | 'right';
31
+ @inject(AdditionalViewsMenuPath)
32
+ protected menuPath: MenuPath;
33
33
 
34
34
  @inject(CommandRegistry)
35
35
  protected readonly commandRegistry: CommandRegistry;
@@ -47,7 +47,7 @@ export class AdditionalViewsMenuWidget extends SidebarMenuWidget {
47
47
  title: nls.localizeByDefault('Additional Views'),
48
48
  iconClass: codicon('ellipsis'),
49
49
  id: AdditionalViewsMenuWidget.ID,
50
- menuPath: ADDITIONAL_VIEWS_MENU_PATH,
50
+ menuPath: this.menuPath,
51
51
  order: 0
52
52
  });
53
53
  }
@@ -66,6 +66,6 @@ export class AdditionalViewsMenuWidget extends SidebarMenuWidget {
66
66
  });
67
67
  }
68
68
  }));
69
- this.menuDisposables.push(this.menuModelRegistry.registerMenuAction(ADDITIONAL_VIEWS_MENU_PATH, { commandId: command.id, order: index.toString() }));
69
+ this.menuDisposables.push(this.menuModelRegistry.registerMenuAction(this.menuPath, { commandId: command.id, order: index.toString() }));
70
70
  }
71
71
  }
@@ -960,7 +960,7 @@ export class ApplicationShell extends Widget {
960
960
  }
961
961
  }
962
962
 
963
- getInsertionOptions(options?: Readonly<ApplicationShell.WidgetOptions>): { area: string; addOptions: DockLayout.IAddOptions; } {
963
+ getInsertionOptions(options?: Readonly<ApplicationShell.WidgetOptions>): { area: string; addOptions: TheiaDockPanel.AddOptions; } {
964
964
  let ref: Widget | undefined = options?.ref;
965
965
  let area: ApplicationShell.Area = options?.area || 'main';
966
966
  if (!ref && (area === 'main' || area === 'bottom')) {
@@ -969,7 +969,7 @@ export class ApplicationShell extends Widget {
969
969
  }
970
970
  // make sure that ref belongs to area
971
971
  area = ref && this.getAreaFor(ref) || area;
972
- const addOptions: DockPanel.IAddOptions = {};
972
+ const addOptions: TheiaDockPanel.AddOptions = {};
973
973
  if (ApplicationShell.isOpenToSideMode(options?.mode)) {
974
974
  const areaPanel = area === 'main' ? this.mainPanel : area === 'bottom' ? this.bottomPanel : undefined;
975
975
  const sideRef = areaPanel && ref && (options?.mode === 'open-to-left' ?
@@ -981,6 +981,10 @@ export class ApplicationShell extends Widget {
981
981
  addOptions.ref = ref;
982
982
  addOptions.mode = options?.mode === 'open-to-left' ? 'split-left' : 'split-right';
983
983
  }
984
+ } else if (ApplicationShell.isReplaceMode(options?.mode)) {
985
+ addOptions.ref = options?.ref;
986
+ addOptions.closeRef = true;
987
+ addOptions.mode = 'tab-after';
984
988
  } else {
985
989
  addOptions.ref = ref;
986
990
  addOptions.mode = options?.mode;
@@ -2172,6 +2176,15 @@ export namespace ApplicationShell {
2172
2176
  return mode === 'open-to-left' || mode === 'open-to-right';
2173
2177
  }
2174
2178
 
2179
+ /**
2180
+ * Whether the `ref` of the options widget should be replaced.
2181
+ */
2182
+ export type ReplaceMode = 'tab-replace';
2183
+
2184
+ export function isReplaceMode(mode: unknown): mode is ReplaceMode {
2185
+ return mode === 'tab-replace';
2186
+ }
2187
+
2175
2188
  /**
2176
2189
  * Options for adding a widget to the application shell.
2177
2190
  */
@@ -2185,7 +2198,7 @@ export namespace ApplicationShell {
2185
2198
  *
2186
2199
  * The default is `'tab-after'`.
2187
2200
  */
2188
- mode?: DockLayout.InsertMode | OpenToSideMode
2201
+ mode?: DockLayout.InsertMode | OpenToSideMode | ReplaceMode
2189
2202
  /**
2190
2203
  * The reference widget for the insert location.
2191
2204
  *
@@ -211,6 +211,7 @@ export class SidePanelHandler {
211
211
  protected createAdditionalViewsWidget(): AdditionalViewsMenuWidget {
212
212
  const widget = this.additionalViewsMenuFactory(this.side);
213
213
  widget.addClass('theia-sidebar-menu');
214
+ widget.addClass('theia-additional-views-menu');
214
215
  return widget;
215
216
  }
216
217
 
@@ -653,7 +654,7 @@ export class SidePanelHandler {
653
654
  }
654
655
 
655
656
  protected onTabsOverflowChanged(sender: SideTabBar, event: { titles: Title<Widget>[], startIndex: number }): void {
656
- if (event.startIndex >= 0 && event.startIndex <= sender.currentIndex) {
657
+ if (event.startIndex > 0 && event.startIndex <= sender.currentIndex) {
657
658
  sender.revealTab(sender.currentIndex);
658
659
  } else {
659
660
  this.additionalViewsMenu.updateAdditionalViews(sender, event);
@@ -20,6 +20,7 @@ import { ReactWidget } from '../widgets';
20
20
  import { ContextMenuRenderer } from '../context-menu-renderer';
21
21
  import { MenuPath } from '../../common/menu';
22
22
  import { HoverService } from '../hover-service';
23
+ import { Event, Disposable, Emitter, DisposableCollection } from '../../common';
23
24
 
24
25
  export const SidebarTopMenuWidgetFactory = Symbol('SidebarTopMenuWidgetFactory');
25
26
  export const SidebarBottomMenuWidgetFactory = Symbol('SidebarBottomMenuWidgetFactory');
@@ -29,18 +30,54 @@ export interface SidebarMenu {
29
30
  iconClass: string;
30
31
  title: string;
31
32
  menuPath: MenuPath;
33
+ onDidBadgeChange?: Event<number>;
32
34
  /*
33
35
  * Used to sort menus. The lower the value the lower they are placed in the sidebar.
34
36
  */
35
37
  order: number;
36
38
  }
37
39
 
40
+ export class SidebarMenuItem implements Disposable {
41
+
42
+ readonly menu: SidebarMenu;
43
+ get badge(): string {
44
+ if (this._badge <= 0) {
45
+ return '';
46
+ } else if (this._badge > 99) {
47
+ return '99+';
48
+ } else {
49
+ return this._badge.toString();
50
+ }
51
+ };
52
+ protected readonly onDidBadgeChangeEmitter = new Emitter<number>();
53
+ readonly onDidBadgeChange: Event<number> = this.onDidBadgeChangeEmitter.event;
54
+ protected _badge = 0;
55
+
56
+ protected readonly toDispose = new DisposableCollection();
57
+
58
+ constructor(menu: SidebarMenu) {
59
+ this.menu = menu;
60
+ if (menu.onDidBadgeChange) {
61
+ this.toDispose.push(menu.onDidBadgeChange(value => {
62
+ this._badge = value;
63
+ this.onDidBadgeChangeEmitter.fire(value);
64
+ }));
65
+ }
66
+ }
67
+
68
+ dispose(): void {
69
+ this.toDispose.dispose();
70
+ this.onDidBadgeChangeEmitter.dispose();
71
+ }
72
+
73
+ }
74
+
38
75
  /**
39
76
  * The menu widget placed on the sidebar.
40
77
  */
41
78
  @injectable()
42
79
  export class SidebarMenuWidget extends ReactWidget {
43
- protected readonly menus: SidebarMenu[];
80
+ protected readonly items: SidebarMenuItem[];
44
81
  /**
45
82
  * The element that had focus when a menu rendered by this widget was activated.
46
83
  */
@@ -58,27 +95,27 @@ export class SidebarMenuWidget extends ReactWidget {
58
95
 
59
96
  constructor() {
60
97
  super();
61
- this.menus = [];
98
+ this.items = [];
62
99
  }
63
100
 
64
101
  addMenu(menu: SidebarMenu): void {
65
- const exists = this.menus.find(m => m.id === menu.id);
102
+ const exists = this.items.find(item => item.menu.id === menu.id);
66
103
  if (exists) {
67
104
  return;
68
105
  }
69
- this.menus.push(menu);
70
- this.menus.sort((a, b) => a.order - b.order);
106
+ const newItem = new SidebarMenuItem(menu);
107
+ newItem.onDidBadgeChange(() => this.update());
108
+ this.items.push(newItem);
109
+ this.items.sort((a, b) => a.menu.order - b.menu.order);
71
110
  this.update();
72
111
  }
73
112
 
74
113
  removeMenu(menuId: string): void {
75
- const menu = this.menus.find(m => m.id === menuId);
76
- if (menu) {
77
- const index = this.menus.indexOf(menu);
78
- if (index !== -1) {
79
- this.menus.splice(index, 1);
80
- this.update();
81
- }
114
+ const index = this.items.findIndex(m => m.menu.id === menuId);
115
+ if (index !== -1) {
116
+ this.items[index].dispose();
117
+ this.items.splice(index, 1);
118
+ this.update();
82
119
  }
83
120
  }
84
121
 
@@ -127,14 +164,20 @@ export class SidebarMenuWidget extends ReactWidget {
127
164
 
128
165
  protected render(): React.ReactNode {
129
166
  return <React.Fragment>
130
- {this.menus.map(menu => <i
131
- key={menu.id}
132
- className={menu.iconClass}
133
- onClick={e => this.onClick(e, menu.menuPath)}
134
- onMouseDown={this.onMouseDown}
135
- onMouseEnter={e => this.onMouseEnter(e, menu.title)}
136
- onMouseLeave={this.onMouseOut}
137
- />)}
167
+ {this.items.map(item => this.renderItem(item))}
138
168
  </React.Fragment>;
139
169
  }
170
+
171
+ protected renderItem(item: SidebarMenuItem): React.ReactNode {
172
+ return <div
173
+ key={item.menu.id}
174
+ className='theia-sidebar-menu-item'
175
+ onClick={e => this.onClick(e, item.menu.menuPath)}
176
+ onMouseDown={this.onMouseDown}
177
+ onMouseEnter={e => this.onMouseEnter(e, item.menu.title)}
178
+ onMouseLeave={this.onMouseOut}>
179
+ <i className={item.menu.iconClass} />
180
+ {item.badge && <div className='theia-badge-decorator-sidebar'>{item.badge}</div>}
181
+ </div>;
182
+ }
140
183
  }
@@ -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
 
@@ -305,7 +305,7 @@
305
305
  display: none !important;
306
306
  }
307
307
 
308
- .p-TabBar .theia-badge-decorator-sidebar {
308
+ .theia-badge-decorator-sidebar {
309
309
  background-color: var(--theia-activityBarBadge-background);
310
310
  border-radius: 20px;
311
311
  color: var(--theia-activityBarBadge-foreground);
@@ -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.