@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
@@ -133,6 +133,7 @@ export interface AuthenticationService {
133
133
  readonly onDidUnregisterAuthenticationProvider: Event<AuthenticationProviderInformation>;
134
134
 
135
135
  readonly onDidChangeSessions: Event<{ providerId: string, label: string, event: AuthenticationProviderAuthenticationSessionsChangeEvent }>;
136
+ readonly onDidUpdateSignInCount: Event<number>;
136
137
  getSessions(providerId: string, scopes?: string[]): Promise<ReadonlyArray<AuthenticationSession>>;
137
138
  getLabel(providerId: string): string;
138
139
  supportsMultipleAccounts(providerId: string): boolean;
@@ -157,15 +158,18 @@ export class AuthenticationServiceImpl implements AuthenticationService {
157
158
 
158
159
  protected authenticationProviders: Map<string, AuthenticationProvider> = new Map<string, AuthenticationProvider>();
159
160
 
160
- private onDidRegisterAuthenticationProviderEmitter: Emitter<AuthenticationProviderInformation> = new Emitter<AuthenticationProviderInformation>();
161
+ private readonly onDidRegisterAuthenticationProviderEmitter: Emitter<AuthenticationProviderInformation> = new Emitter<AuthenticationProviderInformation>();
161
162
  readonly onDidRegisterAuthenticationProvider: Event<AuthenticationProviderInformation> = this.onDidRegisterAuthenticationProviderEmitter.event;
162
163
 
163
- private onDidUnregisterAuthenticationProviderEmitter: Emitter<AuthenticationProviderInformation> = new Emitter<AuthenticationProviderInformation>();
164
+ private readonly onDidUnregisterAuthenticationProviderEmitter: Emitter<AuthenticationProviderInformation> = new Emitter<AuthenticationProviderInformation>();
164
165
  readonly onDidUnregisterAuthenticationProvider: Event<AuthenticationProviderInformation> = this.onDidUnregisterAuthenticationProviderEmitter.event;
165
166
 
166
- private onDidChangeSessionsEmitter: Emitter<SessionChangeEvent> = new Emitter<SessionChangeEvent>();
167
+ private readonly onDidChangeSessionsEmitter: Emitter<SessionChangeEvent> = new Emitter<SessionChangeEvent>();
167
168
  readonly onDidChangeSessions: Event<SessionChangeEvent> = this.onDidChangeSessionsEmitter.event;
168
169
 
170
+ private readonly onDidChangeSignInCountEmitter: Emitter<number> = new Emitter<number>();
171
+ readonly onDidUpdateSignInCount: Event<number> = this.onDidChangeSignInCountEmitter.event;
172
+
169
173
  @inject(MenuModelRegistry) protected readonly menus: MenuModelRegistry;
170
174
  @inject(CommandRegistry) protected readonly commands: CommandRegistry;
171
175
  @inject(StorageService) protected readonly storageService: StorageService;
@@ -295,6 +299,7 @@ export class AuthenticationServiceImpl implements AuthenticationService {
295
299
  return;
296
300
  }
297
301
 
302
+ const previousSize = this.signInRequestItems.size;
298
303
  const sessions = await provider.getSessions();
299
304
  Object.keys(existingRequestsForProvider).forEach(requestedScopes => {
300
305
  if (sessions.some(session => session.scopes.slice().sort().join('') === requestedScopes)) {
@@ -311,6 +316,9 @@ export class AuthenticationServiceImpl implements AuthenticationService {
311
316
  }
312
317
  }
313
318
  });
319
+ if (previousSize !== this.signInRequestItems.size) {
320
+ this.onDidChangeSignInCountEmitter.fire(this.signInRequestItems.size);
321
+ }
314
322
  }
315
323
 
316
324
  async requestNewSession(providerId: string, scopes: string[], extensionId: string, extensionName: string): Promise<void> {
@@ -341,7 +349,7 @@ export class AuthenticationServiceImpl implements AuthenticationService {
341
349
  }
342
350
 
343
351
  const menuItem = this.menus.registerMenuAction(ACCOUNTS_SUBMENU, {
344
- label: `Sign in to use ${extensionName} (1)`,
352
+ label: nls.localizeByDefault('Sign in with {0} to use {1} (1)', provider.label, extensionName),
345
353
  order: '1',
346
354
  commandId: `${extensionId}signIn`,
347
355
  });
@@ -362,6 +370,7 @@ export class AuthenticationServiceImpl implements AuthenticationService {
362
370
  }
363
371
  });
364
372
 
373
+ const previousSize = this.signInRequestItems.size;
365
374
  if (providerRequests) {
366
375
  const existingRequest = providerRequests[scopesList] || { disposables: [], requestingExtensionIds: [] };
367
376
 
@@ -378,6 +387,9 @@ export class AuthenticationServiceImpl implements AuthenticationService {
378
387
  }
379
388
  });
380
389
  }
390
+ if (previousSize !== this.signInRequestItems.size) {
391
+ this.onDidChangeSignInCountEmitter.fire(this.signInRequestItems.size);
392
+ }
381
393
  }
382
394
  }
383
395
 
@@ -34,9 +34,14 @@ export const isSafari = (userAgent.indexOf('Chrome') === -1) && (userAgent.index
34
34
  export const isIPad = (userAgent.indexOf('iPad') >= 0);
35
35
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
36
36
  /**
37
- * @deprecated us Environment.electron.is
37
+ * @deprecated use Environment.electron.is
38
38
  */
39
39
  export const isNative = environment.electron.is();
40
+ /**
41
+ * Determines whether the backend is running in a remote environment.
42
+ * I.e. we use the browser version or connect to a remote Theia instance in Electron.
43
+ */
44
+ export const isRemote = !environment.electron.is() || new URL(location.href).searchParams.has('localPort');
40
45
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
41
46
  export const isBasicWasmSupported = typeof (window as any).WebAssembly !== 'undefined';
42
47
 
@@ -61,11 +61,12 @@ import { ConfirmDialog, confirmExit, ConfirmSaveDialog, Dialog } from './dialogs
61
61
  import { WindowService } from './window/window-service';
62
62
  import { FrontendApplicationConfigProvider } from './frontend-application-config-provider';
63
63
  import { DecorationStyle } from './decoration-style';
64
- import { isPinned, Title, togglePinned, Widget } from './widgets';
64
+ import { codicon, isPinned, Title, togglePinned, Widget } from './widgets';
65
65
  import { SaveableService } from './saveable-service';
66
66
  import { UserWorkingDirectoryProvider } from './user-working-directory-provider';
67
67
  import { UNTITLED_SCHEME, UntitledResourceResolver } from '../common';
68
68
  import { LanguageQuickPickService } from './i18n/language-quick-pick-service';
69
+ import { SidebarMenu } from './shell/sidebar-menu-widget';
69
70
 
70
71
  export namespace CommonMenus {
71
72
 
@@ -472,17 +473,18 @@ export class CommonFrontendContribution implements FrontendApplicationContributi
472
473
 
473
474
  app.shell.leftPanelHandler.addBottomMenu({
474
475
  id: 'settings-menu',
475
- iconClass: 'codicon codicon-settings-gear',
476
+ iconClass: codicon('settings-gear'),
476
477
  title: nls.localizeByDefault(CommonCommands.MANAGE_CATEGORY),
477
478
  menuPath: MANAGE_MENU,
478
- order: 1,
479
+ order: 0,
479
480
  });
480
- const accountsMenu = {
481
+ const accountsMenu: SidebarMenu = {
481
482
  id: 'accounts-menu',
482
- iconClass: 'codicon codicon-person',
483
+ iconClass: codicon('account'),
483
484
  title: nls.localizeByDefault('Accounts'),
484
485
  menuPath: ACCOUNTS_MENU,
485
- order: 0,
486
+ order: 1,
487
+ onDidBadgeChange: this.authenticationService.onDidUpdateSignInCount
486
488
  };
487
489
  this.authenticationService.onDidRegisterAuthenticationProvider(() => {
488
490
  app.shell.leftPanelHandler.addBottomMenu(accountsMenu);
@@ -530,7 +532,7 @@ export class CommonFrontendContribution implements FrontendApplicationContributi
530
532
  if (newValue === 'compact') {
531
533
  this.shell.leftPanelHandler.addTopMenu({
532
534
  id: mainMenuId,
533
- iconClass: 'codicon codicon-menu',
535
+ iconClass: `theia-compact-menu ${codicon('menu')}`,
534
536
  title: nls.localizeByDefault('Application Menu'),
535
537
  menuPath: MAIN_MENU_BAR,
536
538
  order: 0,
@@ -35,7 +35,8 @@ import {
35
35
  MenuCommandAdapterRegistry,
36
36
  MenuCommandExecutor,
37
37
  MenuCommandAdapterRegistryImpl,
38
- MenuCommandExecutorImpl
38
+ MenuCommandExecutorImpl,
39
+ MenuPath
39
40
  } from '../common';
40
41
  import { KeybindingRegistry, KeybindingContext, KeybindingContribution } from './keybinding';
41
42
  import { FrontendApplication } from './frontend-application';
@@ -137,7 +138,7 @@ import { MarkdownRenderer, MarkdownRendererFactory, MarkdownRendererImpl } from
137
138
  import { StylingParticipant, StylingService } from './styling-service';
138
139
  import { bindCommonStylingParticipants } from './common-styling-participants';
139
140
  import { HoverService } from './hover-service';
140
- import { AdditionalViewsMenuWidget, AdditionalViewsMenuWidgetFactory } from './shell/additional-views-menu-widget';
141
+ import { AdditionalViewsMenuPath, AdditionalViewsMenuWidget, AdditionalViewsMenuWidgetFactory } from './shell/additional-views-menu-widget';
141
142
  import { LanguageIconLabelProvider } from './language-icon-provider';
142
143
  import { bindTreePreferences } from './tree';
143
144
  import { OpenWithService } from './open-with-service';
@@ -177,9 +178,9 @@ export const frontendApplicationModule = new ContainerModule((bind, _unbind, _is
177
178
  bind(SidebarBottomMenuWidgetFactory).toAutoFactory(SidebarBottomMenuWidget);
178
179
  bind(AdditionalViewsMenuWidget).toSelf();
179
180
  bind(AdditionalViewsMenuWidgetFactory).toFactory(ctx => (side: 'left' | 'right') => {
180
- const widget = ctx.container.resolve(AdditionalViewsMenuWidget);
181
- widget.side = side;
182
- return widget;
181
+ const childContainer = ctx.container.createChild();
182
+ childContainer.bind<MenuPath>(AdditionalViewsMenuPath).toConstantValue(['additional_views_menu', side]);
183
+ return childContainer.resolve(AdditionalViewsMenuWidget);
183
184
  });
184
185
  bind(SplitPositionHandler).toSelf().inSingletonScope();
185
186
 
@@ -27,6 +27,8 @@ export interface HttpOpenHandlerOptions {
27
27
  @injectable()
28
28
  export class HttpOpenHandler implements OpenHandler {
29
29
 
30
+ static readonly PRIORITY: number = 500;
31
+
30
32
  readonly id = 'http';
31
33
 
32
34
  @inject(WindowService)
@@ -36,7 +38,7 @@ export class HttpOpenHandler implements OpenHandler {
36
38
  protected readonly externalUriService: ExternalUriService;
37
39
 
38
40
  canHandle(uri: URI, options?: HttpOpenHandlerOptions): number {
39
- return ((options && options.openExternal) || uri.scheme.startsWith('http') || uri.scheme.startsWith('mailto')) ? 500 : 0;
41
+ return ((options && options.openExternal) || uri.scheme.startsWith('http') || uri.scheme.startsWith('mailto')) ? HttpOpenHandler.PRIORITY : 0;
40
42
  }
41
43
 
42
44
  async open(uri: URI): Promise<undefined> {
@@ -71,25 +71,30 @@ export class BrowserMainMenuFactory implements MenuWidgetFactory {
71
71
  const menuBar = new DynamicMenuBarWidget();
72
72
  menuBar.id = 'theia:menubar';
73
73
  this.corePreferences.ready.then(() => {
74
- this.showMenuBar(menuBar, this.corePreferences.get('window.menuBarVisibility', 'classic'));
75
- });
76
- const preferenceListener = this.corePreferences.onPreferenceChanged(preference => {
77
- if (preference.preferenceName === 'window.menuBarVisibility') {
78
- this.showMenuBar(menuBar, preference.newValue);
79
- }
80
- });
81
- const keybindingListener = this.keybindingRegistry.onKeybindingsChanged(() => {
82
- const preference = this.corePreferences['window.menuBarVisibility'];
83
- this.showMenuBar(menuBar, preference);
84
- });
85
- menuBar.disposed.connect(() => {
86
- preferenceListener.dispose();
87
- keybindingListener.dispose();
74
+ this.showMenuBar(menuBar);
88
75
  });
76
+ const disposable = new DisposableCollection(
77
+ this.corePreferences.onPreferenceChanged(change => {
78
+ if (change.preferenceName === 'window.menuBarVisibility') {
79
+ this.showMenuBar(menuBar, change.newValue);
80
+ }
81
+ }),
82
+ this.keybindingRegistry.onKeybindingsChanged(() => {
83
+ this.showMenuBar(menuBar);
84
+ }),
85
+ this.menuProvider.onDidChange(() => {
86
+ this.showMenuBar(menuBar);
87
+ })
88
+ );
89
+ menuBar.disposed.connect(() => disposable.dispose());
89
90
  return menuBar;
90
91
  }
91
92
 
92
- protected showMenuBar(menuBar: DynamicMenuBarWidget, preference: string | undefined): void {
93
+ protected getMenuBarVisibility(): string {
94
+ return this.corePreferences.get('window.menuBarVisibility', 'classic');
95
+ }
96
+
97
+ protected showMenuBar(menuBar: DynamicMenuBarWidget, preference = this.getMenuBarVisibility()): void {
93
98
  if (preference && ['classic', 'visible'].includes(preference)) {
94
99
  menuBar.clearMenus();
95
100
  this.fillMenuBar(menuBar);
@@ -187,13 +192,13 @@ export class DynamicMenuBarWidget extends MenuBarWidget {
187
192
  this.openActiveMenu();
188
193
  await waitForRevealed(menu);
189
194
 
190
- const menuPath = [label];
195
+ const menuPath = [label, ...labels];
191
196
 
192
197
  let current = menu;
193
198
  for (const itemLabel of labels) {
194
199
  const item = current.items.find(i => i.label === itemLabel);
195
200
  if (!item || !item.submenu) {
196
- throw new Error(`could not find '${label}' submenu in ${menuPath.map(l => "'" + l + "'").join(' -> ')} menu`);
201
+ throw new Error(`could not find '${itemLabel}' submenu in ${menuPath.map(l => "'" + l + "'").join(' -> ')} menu`);
197
202
  }
198
203
  current.activeItem = item;
199
204
  current.triggerActiveItem();
@@ -211,7 +216,7 @@ export class DynamicMenuBarWidget extends MenuBarWidget {
211
216
  const menu = await this.activateMenu(menuPath[0], ...menuPath.slice(1));
212
217
  const item = menu.items.find(i => i.label === labels[labels.length - 1]);
213
218
  if (!item) {
214
- throw new Error(`could not find '${label}' item in ${menuPath.map(l => "'" + l + "'").join(' -> ')} menu`);
219
+ throw new Error(`could not find '${labels[labels.length - 1]}' item in ${menuPath.map(l => "'" + l + "'").join(' -> ')} menu`);
215
220
  }
216
221
  menu.activeItem = item;
217
222
  menu.triggerActiveItem();
@@ -468,9 +473,11 @@ export class MenuCommandRegistry extends PhosphorCommandRegistry {
468
473
  });
469
474
 
470
475
  const bindings = keybindingRegistry.getKeybindingsForCommand(id);
471
- // Only consider the first keybinding.
476
+ // Only consider the first active keybinding.
472
477
  if (bindings.length) {
473
- const binding = bindings[0];
478
+ const binding = bindings.length > 1 ?
479
+ bindings.find(b => !b.when || this.services.contextKeyService.match(b.when)) ?? bindings[0] :
480
+ bindings[0];
474
481
  const keys = keybindingRegistry.acceleratorFor(binding, ' ', true);
475
482
  this.addKeyBinding({
476
483
  command: id,
@@ -20,7 +20,13 @@ import { ChannelMultiplexer } from '../../common/message-rpc/channel';
20
20
  import { Deferred } from '../../common/promise-util';
21
21
  import { ConnectionSource } from './connection-source';
22
22
 
23
+ /**
24
+ * Service id for the local connection provider
25
+ */
23
26
  export const LocalConnectionProvider = Symbol('LocalConnectionProvider');
27
+ /**
28
+ * Service id for the remote connection provider
29
+ */
24
30
  export const RemoteConnectionProvider = Symbol('RemoteConnectionProvider');
25
31
 
26
32
  export namespace ServiceConnectionProvider {
@@ -28,7 +34,15 @@ export namespace ServiceConnectionProvider {
28
34
  }
29
35
 
30
36
  /**
31
- * This class manages the channels for remote services in the back end
37
+ * This class manages the channels for remote services in the back end.
38
+ *
39
+ * Since we have the ability to use a remote back end via SSH, we need to distinguish
40
+ * between two types of services: those that will be redirected to the remote back end
41
+ * and those which must remain in the local back end. For example the service that manages
42
+ * the remote ssh connections and port forwarding to the remote instance must remain local
43
+ * while e.g. the file system service will run in the remote back end. For each set
44
+ * of services, we will bind an instance of this class to {@linkcode LocalConnectionProvider}
45
+ * and {@linkcode RemoteConnectionProvider} respectively.
32
46
  */
33
47
  @injectable()
34
48
  export class ServiceConnectionProvider {
@@ -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;
@@ -1892,6 +1896,10 @@ export class ApplicationShell extends Widget {
1892
1896
  if (index < current.titles.length - 1) {
1893
1897
  return index + 1;
1894
1898
  }
1899
+ // last item in tab bar. select the previous one.
1900
+ if (index === current.titles.length - 1) {
1901
+ return index - 1;
1902
+ }
1895
1903
  return 0;
1896
1904
  }
1897
1905
 
@@ -2058,7 +2066,7 @@ export class ApplicationShell extends Widget {
2058
2066
  */
2059
2067
  async saveAll(options?: SaveOptions): Promise<void> {
2060
2068
  for (const widget of this.widgets) {
2061
- if (this.saveableService.canSaveNotSaveAs(widget)) {
2069
+ if (Saveable.isDirty(widget) && this.saveableService.canSaveNotSaveAs(widget)) {
2062
2070
  await this.saveableService.save(widget, options);
2063
2071
  }
2064
2072
  }
@@ -2172,6 +2180,15 @@ export namespace ApplicationShell {
2172
2180
  return mode === 'open-to-left' || mode === 'open-to-right';
2173
2181
  }
2174
2182
 
2183
+ /**
2184
+ * Whether the `ref` of the options widget should be replaced.
2185
+ */
2186
+ export type ReplaceMode = 'tab-replace';
2187
+
2188
+ export function isReplaceMode(mode: unknown): mode is ReplaceMode {
2189
+ return mode === 'tab-replace';
2190
+ }
2191
+
2175
2192
  /**
2176
2193
  * Options for adding a widget to the application shell.
2177
2194
  */
@@ -2185,7 +2202,7 @@ export namespace ApplicationShell {
2185
2202
  *
2186
2203
  * The default is `'tab-after'`.
2187
2204
  */
2188
- mode?: DockLayout.InsertMode | OpenToSideMode
2205
+ mode?: DockLayout.InsertMode | OpenToSideMode | ReplaceMode
2189
2206
  /**
2190
2207
  * The reference widget for the insert location.
2191
2208
  *
@@ -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
  }
@@ -91,13 +91,14 @@ export class SplitPositionHandler {
91
91
  move.resolve = resolve;
92
92
  move.reject = reject;
93
93
  if (this.splitMoves.length === 0) {
94
- window.requestAnimationFrame(this.animationFrame.bind(this));
94
+ setTimeout(this.animationFrame.bind(this), 10);
95
95
  }
96
96
  this.splitMoves.push(move);
97
97
  });
98
98
  }
99
99
 
100
- protected animationFrame(time: number): void {
100
+ protected animationFrame(): void {
101
+ const time = Date.now();
101
102
  const move = this.splitMoves[this.currentMoveIndex];
102
103
  let rejectedOrResolved = false;
103
104
  if (move.ended || move.referenceWidget && move.referenceWidget.isHidden) {
@@ -133,7 +134,7 @@ export class SplitPositionHandler {
133
134
  this.currentMoveIndex = 0;
134
135
  }
135
136
  if (this.splitMoves.length > 0) {
136
- window.requestAnimationFrame(this.animationFrame.bind(this));
137
+ setTimeout(this.animationFrame.bind(this));
137
138
  }
138
139
  }
139
140