@theia/core 1.20.0 → 1.21.0-next.14

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 (49) hide show
  1. package/README.md +3 -3
  2. package/lib/browser/common-frontend-contribution.d.ts +1 -13
  3. package/lib/browser/common-frontend-contribution.d.ts.map +1 -1
  4. package/lib/browser/common-frontend-contribution.js +86 -127
  5. package/lib/browser/common-frontend-contribution.js.map +1 -1
  6. package/lib/browser/frontend-application-module.d.ts.map +1 -1
  7. package/lib/browser/frontend-application-module.js +6 -5
  8. package/lib/browser/frontend-application-module.js.map +1 -1
  9. package/lib/browser/preferences/preference-provider.d.ts +0 -2
  10. package/lib/browser/preferences/preference-provider.d.ts.map +1 -1
  11. package/lib/browser/preferences/preference-provider.js +1 -5
  12. package/lib/browser/preferences/preference-provider.js.map +1 -1
  13. package/lib/browser/preferences/preference-service.spec.js +0 -3
  14. package/lib/browser/preferences/preference-service.spec.js.map +1 -1
  15. package/lib/browser/shell/application-shell.d.ts +7 -7
  16. package/lib/browser/shell/application-shell.d.ts.map +1 -1
  17. package/lib/browser/shell/application-shell.js +10 -17
  18. package/lib/browser/shell/application-shell.js.map +1 -1
  19. package/lib/browser/shell/current-widget-command-adapter.d.ts +39 -0
  20. package/lib/browser/shell/current-widget-command-adapter.d.ts.map +1 -0
  21. package/lib/browser/shell/current-widget-command-adapter.js +42 -0
  22. package/lib/browser/shell/current-widget-command-adapter.js.map +1 -0
  23. package/lib/browser/shell/tab-bars.d.ts +8 -2
  24. package/lib/browser/shell/tab-bars.d.ts.map +1 -1
  25. package/lib/browser/shell/tab-bars.js +30 -3
  26. package/lib/browser/shell/tab-bars.js.map +1 -1
  27. package/lib/common/promise-util.d.ts +3 -0
  28. package/lib/common/promise-util.d.ts.map +1 -1
  29. package/lib/common/promise-util.js +16 -1
  30. package/lib/common/promise-util.js.map +1 -1
  31. package/lib/common/promise-util.spec.d.ts +2 -0
  32. package/lib/common/promise-util.spec.d.ts.map +1 -0
  33. package/lib/common/promise-util.spec.js +42 -0
  34. package/lib/common/promise-util.spec.js.map +1 -0
  35. package/lib/electron-main/electron-main-application.d.ts +10 -0
  36. package/lib/electron-main/electron-main-application.d.ts.map +1 -1
  37. package/lib/electron-main/electron-main-application.js +13 -3
  38. package/lib/electron-main/electron-main-application.js.map +1 -1
  39. package/package.json +3 -3
  40. package/src/browser/common-frontend-contribution.ts +75 -132
  41. package/src/browser/frontend-application-module.ts +6 -5
  42. package/src/browser/preferences/preference-provider.ts +1 -5
  43. package/src/browser/preferences/preference-service.spec.ts +0 -3
  44. package/src/browser/shell/application-shell.ts +17 -22
  45. package/src/browser/shell/current-widget-command-adapter.ts +57 -0
  46. package/src/browser/shell/tab-bars.ts +30 -5
  47. package/src/common/promise-util.spec.ts +41 -0
  48. package/src/common/promise-util.ts +20 -1
  49. package/src/electron-main/electron-main-application.ts +24 -5
@@ -18,7 +18,6 @@
18
18
 
19
19
  import debounce = require('lodash.debounce');
20
20
  import { injectable, inject, optional } from 'inversify';
21
- import { TabBar, Widget } from '@phosphor/widgets';
22
21
  import { MAIN_MENU_BAR, SETTINGS_MENU, MenuContribution, MenuModelRegistry, ACCOUNTS_MENU } from '../common/menu';
23
22
  import { KeybindingContribution, KeybindingRegistry } from './keybinding';
24
23
  import { FrontendApplication, FrontendApplicationContribution } from './frontend-application';
@@ -28,7 +27,7 @@ import { SelectionService } from '../common/selection-service';
28
27
  import { MessageService } from '../common/message-service';
29
28
  import { OpenerService, open } from '../browser/opener-service';
30
29
  import { ApplicationShell } from './shell/application-shell';
31
- import { SHELL_TABBAR_CONTEXT_MENU } from './shell/tab-bars';
30
+ import { SHELL_TABBAR_CONTEXT_CLOSE, SHELL_TABBAR_CONTEXT_COPY, SHELL_TABBAR_CONTEXT_SPLIT } from './shell/tab-bars';
32
31
  import { AboutDialog } from './about-dialog';
33
32
  import * as browser from './browser';
34
33
  import URI from '../common/uri';
@@ -51,10 +50,11 @@ import { EncodingRegistry } from './encoding-registry';
51
50
  import { UTF8 } from '../common/encodings';
52
51
  import { EnvVariablesServer } from '../common/env-variables';
53
52
  import { AuthenticationService } from './authentication-service';
54
- import { FormatType } from './saveable';
53
+ import { FormatType, Saveable } from './saveable';
55
54
  import { QuickInputService, QuickPick, QuickPickItem } from './quick-input';
56
55
  import { AsyncLocalizationProvider } from '../common/i18n/localization';
57
56
  import { nls } from '../common/nls';
57
+ import { CurrentWidgetCommandAdapter } from './shell/current-widget-command-adapter';
58
58
 
59
59
  export namespace CommonMenus {
60
60
 
@@ -181,6 +181,11 @@ export namespace CommonCommands {
181
181
  category: VIEW_CATEGORY,
182
182
  label: 'Close Other Tabs'
183
183
  }, 'theia/core/common/closeOthers', VIEW_CATEGORY_KEY);
184
+ export const CLOSE_SAVED_TABS = Command.toDefaultLocalizedCommand({
185
+ id: 'workbench.action.closeUnmodifiedEditors',
186
+ category: VIEW_CATEGORY,
187
+ label: 'Close Saved Editors in Group',
188
+ });
184
189
  export const CLOSE_RIGHT_TABS = Command.toLocalizedCommand({
185
190
  id: 'core.close.right.tabs',
186
191
  category: VIEW_CATEGORY,
@@ -546,35 +551,45 @@ export class CommonFrontendContribution implements FrontendApplicationContributi
546
551
  order: '2'
547
552
  });
548
553
 
549
- registry.registerMenuAction(SHELL_TABBAR_CONTEXT_MENU, {
554
+ registry.registerMenuAction(SHELL_TABBAR_CONTEXT_CLOSE, {
550
555
  commandId: CommonCommands.CLOSE_TAB.id,
551
556
  label: nls.localizeByDefault('Close'),
552
557
  order: '0'
553
558
  });
554
- registry.registerMenuAction(SHELL_TABBAR_CONTEXT_MENU, {
559
+ registry.registerMenuAction(SHELL_TABBAR_CONTEXT_CLOSE, {
555
560
  commandId: CommonCommands.CLOSE_OTHER_TABS.id,
556
561
  label: nls.localizeByDefault('Close Others'),
557
562
  order: '1'
558
563
  });
559
- registry.registerMenuAction(SHELL_TABBAR_CONTEXT_MENU, {
564
+ registry.registerMenuAction(SHELL_TABBAR_CONTEXT_CLOSE, {
560
565
  commandId: CommonCommands.CLOSE_RIGHT_TABS.id,
561
566
  label: nls.localizeByDefault('Close to the Right'),
562
567
  order: '2'
563
568
  });
564
- registry.registerMenuAction(SHELL_TABBAR_CONTEXT_MENU, {
569
+ registry.registerMenuAction(SHELL_TABBAR_CONTEXT_CLOSE, {
570
+ commandId: CommonCommands.CLOSE_SAVED_TABS.id,
571
+ label: nls.localizeByDefault('Close Saved'),
572
+ order: '3',
573
+ });
574
+ registry.registerMenuAction(SHELL_TABBAR_CONTEXT_CLOSE, {
565
575
  commandId: CommonCommands.CLOSE_ALL_TABS.id,
566
576
  label: nls.localizeByDefault('Close All'),
567
- order: '3'
577
+ order: '4'
568
578
  });
569
- registry.registerMenuAction(SHELL_TABBAR_CONTEXT_MENU, {
579
+ registry.registerMenuAction(SHELL_TABBAR_CONTEXT_SPLIT, {
570
580
  commandId: CommonCommands.COLLAPSE_PANEL.id,
571
581
  label: CommonCommands.COLLAPSE_PANEL.label,
572
- order: '4'
582
+ order: '5'
573
583
  });
574
- registry.registerMenuAction(SHELL_TABBAR_CONTEXT_MENU, {
584
+ registry.registerMenuAction(SHELL_TABBAR_CONTEXT_SPLIT, {
575
585
  commandId: CommonCommands.TOGGLE_MAXIMIZED.id,
576
586
  label: CommonCommands.TOGGLE_MAXIMIZED.label,
577
- order: '5'
587
+ order: '6'
588
+ });
589
+ registry.registerMenuAction(SHELL_TABBAR_CONTEXT_COPY, {
590
+ commandId: CommonCommands.COPY_PATH.id,
591
+ label: CommonCommands.COPY_PATH.label,
592
+ order: '1',
578
593
  });
579
594
  registry.registerMenuAction(CommonMenus.HELP, {
580
595
  commandId: CommonCommands.ABOUT_COMMAND.id,
@@ -633,6 +648,8 @@ export class CommonFrontendContribution implements FrontendApplicationContributi
633
648
  }
634
649
  });
635
650
  commandRegistry.registerCommand(CommonCommands.COPY_PATH, UriAwareCommandHandler.MultiSelect(this.selectionService, {
651
+ isVisible: uris => Array.isArray(uris) && uris.some(uri => uri instanceof URI),
652
+ isEnabled: uris => Array.isArray(uris) && uris.some(uri => uri instanceof URI),
636
653
  execute: async uris => {
637
654
  if (uris.length) {
638
655
  const lineDelimiter = isWindows ? '\r\n' : '\n';
@@ -685,62 +702,45 @@ export class CommonFrontendContribution implements FrontendApplicationContributi
685
702
  isEnabled: () => this.shell.previousTabBar() !== undefined,
686
703
  execute: () => this.shell.activatePreviousTabBar()
687
704
  });
688
- commandRegistry.registerCommand(CommonCommands.CLOSE_TAB, {
689
- isEnabled: (event?: Event) => {
690
- const tabBar = this.shell.findTabBar(event);
691
- if (!tabBar) {
692
- return false;
693
- }
694
- const currentTitle = this.shell.findTitle(tabBar, event);
695
- return currentTitle !== undefined && currentTitle.closable;
696
- },
697
- execute: (event?: Event) => {
698
- const tabBar = this.shell.findTabBar(event)!;
699
- const currentTitle = this.shell.findTitle(tabBar, event);
700
- this.shell.closeTabs(tabBar, title => title === currentTitle);
701
- }
702
- });
703
- commandRegistry.registerCommand(CommonCommands.CLOSE_OTHER_TABS, {
704
- isEnabled: (event?: Event) => {
705
- const tabBar = this.shell.findTabBar(event);
706
- if (!tabBar) {
707
- return false;
708
- }
709
- const currentTitle = this.shell.findTitle(tabBar, event);
710
- return tabBar.titles.some(title => title !== currentTitle && title.closable);
711
- },
712
- execute: (event?: Event) => {
713
- const tabBar = this.shell.findTabBar(event)!;
714
- const currentTitle = this.shell.findTitle(tabBar, event);
715
- this.shell.closeTabs(tabBar, title => title !== currentTitle && title.closable);
716
- }
717
- });
718
- commandRegistry.registerCommand(CommonCommands.CLOSE_RIGHT_TABS, {
719
- isEnabled: (event?: Event) => {
720
- const tabBar = this.shell.findTabBar(event);
721
- if (!tabBar) {
722
- return false;
723
- }
724
- const currentIndex = this.findTitleIndex(tabBar, event);
725
- return tabBar.titles.some((title, index) => index > currentIndex && title.closable);
726
- },
727
- isVisible: (event?: Event) => {
728
- const area = this.findTabArea(event);
705
+ commandRegistry.registerCommand(CommonCommands.CLOSE_TAB, new CurrentWidgetCommandAdapter(this.shell, {
706
+ isEnabled: title => Boolean(title?.closable),
707
+ execute: (title, tabBar) => tabBar && this.shell.closeTabs(tabBar, candidate => candidate === title),
708
+ }));
709
+ commandRegistry.registerCommand(CommonCommands.CLOSE_OTHER_TABS, new CurrentWidgetCommandAdapter(this.shell, {
710
+ isEnabled: (title, tabbar) => Boolean(tabbar?.titles.some(candidate => candidate !== title && candidate.closable)),
711
+ execute: (title, tabbar) => tabbar && this.shell.closeTabs(tabbar, candidate => candidate !== title && candidate.closable),
712
+ }));
713
+ commandRegistry.registerCommand(CommonCommands.CLOSE_SAVED_TABS, new CurrentWidgetCommandAdapter(this.shell, {
714
+ isEnabled: (_title, tabbar) => Boolean(tabbar?.titles.some(candidate => candidate.closable && !Saveable.isDirty(candidate.owner))),
715
+ execute: (_title, tabbar) => tabbar && this.shell.closeTabs(tabbar, candidate => candidate.closable && !Saveable.isDirty(candidate.owner)),
716
+ }));
717
+ commandRegistry.registerCommand(CommonCommands.CLOSE_RIGHT_TABS, new CurrentWidgetCommandAdapter(this.shell, {
718
+ isEnabled: (title, tabbar) => {
719
+ let targetSeen = false;
720
+ return Boolean(tabbar?.titles.some(candidate => {
721
+ if (targetSeen && candidate.closable) { return true; };
722
+ if (candidate === title) { targetSeen = true; };
723
+ }));
724
+ },
725
+ isVisible: (_title, tabbar) => {
726
+ const area = (tabbar && this.shell.getAreaFor(tabbar)) ?? this.shell.currentTabArea;
729
727
  return area !== undefined && area !== 'left' && area !== 'right';
730
728
  },
731
- execute: (event?: Event) => {
732
- const tabBar = this.shell.findTabBar(event)!;
733
- const currentIndex = this.findTitleIndex(tabBar, event);
734
- this.shell.closeTabs(tabBar, (title, index) => index > currentIndex && title.closable);
729
+ execute: (title, tabbar) => {
730
+ if (tabbar) {
731
+ let targetSeen = false;
732
+ this.shell.closeTabs(tabbar, candidate => {
733
+ if (targetSeen && candidate.closable) { return true; };
734
+ if (candidate === title) { targetSeen = true; };
735
+ return false;
736
+ });
737
+ }
735
738
  }
736
- });
737
- commandRegistry.registerCommand(CommonCommands.CLOSE_ALL_TABS, {
738
- isEnabled: (event?: Event) => {
739
- const tabBar = this.shell.findTabBar(event);
740
- return tabBar !== undefined && tabBar.titles.some(title => title.closable);
741
- },
742
- execute: (event?: Event) => this.shell.closeTabs(this.shell.findTabBar(event)!, title => title.closable)
743
- });
739
+ }));
740
+ commandRegistry.registerCommand(CommonCommands.CLOSE_ALL_TABS, new CurrentWidgetCommandAdapter(this.shell, {
741
+ isEnabled: (_title, tabbar) => Boolean(tabbar?.titles.some(title => title.closable)),
742
+ execute: (_title, tabbar) => tabbar && this.shell.closeTabs(tabbar, candidate => candidate.closable),
743
+ }));
744
744
  commandRegistry.registerCommand(CommonCommands.CLOSE_MAIN_TAB, {
745
745
  isEnabled: () => {
746
746
  const currentWidget = this.shell.getCurrentWidget('main');
@@ -763,11 +763,11 @@ export class CommonFrontendContribution implements FrontendApplicationContributi
763
763
  isEnabled: () => this.shell.mainAreaTabBars.some(tb => tb.titles.some(title => title.closable)),
764
764
  execute: () => this.shell.closeTabs('main', title => title.closable)
765
765
  });
766
- commandRegistry.registerCommand(CommonCommands.COLLAPSE_PANEL, {
767
- isEnabled: (event?: Event) => ApplicationShell.isSideArea(this.findTabArea(event)),
768
- isVisible: (event?: Event) => ApplicationShell.isSideArea(this.findTabArea(event)),
769
- execute: (event?: Event) => this.shell.collapsePanel(this.findTabArea(event)!)
770
- });
766
+ commandRegistry.registerCommand(CommonCommands.COLLAPSE_PANEL, new CurrentWidgetCommandAdapter(this.shell, {
767
+ isEnabled: (_title, tabbar) => Boolean(tabbar && ApplicationShell.isSideArea(this.shell.getAreaFor(tabbar))),
768
+ isVisible: (_title, tabbar) => Boolean(tabbar && ApplicationShell.isSideArea(this.shell.getAreaFor(tabbar))),
769
+ execute: (_title, tabbar) => tabbar && this.shell.collapsePanel(this.shell.getAreaFor(tabbar)!)
770
+ }));
771
771
  commandRegistry.registerCommand(CommonCommands.COLLAPSE_ALL_PANELS, {
772
772
  execute: () => {
773
773
  this.shell.collapsePanel('left');
@@ -788,11 +788,11 @@ export class CommonFrontendContribution implements FrontendApplicationContributi
788
788
  commandRegistry.registerCommand(CommonCommands.TOGGLE_STATUS_BAR, {
789
789
  execute: () => this.preferenceService.updateValue('workbench.statusBar.visible', !this.preferences['workbench.statusBar.visible'])
790
790
  });
791
- commandRegistry.registerCommand(CommonCommands.TOGGLE_MAXIMIZED, {
792
- isEnabled: (event?: Event) => this.canToggleMaximized(event),
793
- isVisible: (event?: Event) => this.canToggleMaximized(event),
794
- execute: (event?: Event) => this.toggleMaximized(event)
795
- });
791
+ commandRegistry.registerCommand(CommonCommands.TOGGLE_MAXIMIZED, new CurrentWidgetCommandAdapter(this.shell, {
792
+ isEnabled: title => Boolean(title?.owner && this.shell.canToggleMaximized(title?.owner)),
793
+ isVisible: title => Boolean(title?.owner && this.shell.canToggleMaximized(title?.owner)),
794
+ execute: title => title?.owner && this.shell.toggleMaximized(title?.owner),
795
+ }));
796
796
 
797
797
  commandRegistry.registerCommand(CommonCommands.SAVE, {
798
798
  execute: () => this.shell.save({ formatType: FormatType.ON })
@@ -823,63 +823,6 @@ export class CommonFrontendContribution implements FrontendApplicationContributi
823
823
  });
824
824
  }
825
825
 
826
- private findTabArea(event?: Event): ApplicationShell.Area | undefined {
827
- const tabBar = this.shell.findTabBar(event);
828
- if (tabBar) {
829
- return this.shell.getAreaFor(tabBar);
830
- }
831
- return this.shell.currentTabArea;
832
- }
833
-
834
- /**
835
- * Finds the index of the selected title from the tab-bar.
836
- * @param tabBar: used for providing an array of titles.
837
- * @returns the index of the selected title if it is available in the tab-bar, else returns the index of currently-selected title.
838
- */
839
- private findTitleIndex(tabBar: TabBar<Widget>, event?: Event): number {
840
- if (event) {
841
- const targetTitle = this.shell.findTitle(tabBar, event);
842
- return targetTitle ? tabBar.titles.indexOf(targetTitle) : tabBar.currentIndex;
843
- }
844
- return tabBar.currentIndex;
845
- }
846
-
847
- private canToggleMaximized(event?: Event): boolean {
848
- if (event?.target instanceof HTMLElement) {
849
- const widget = this.shell.findWidgetForElement(event.target);
850
- if (widget) {
851
- return this.shell.mainPanel.contains(widget) || this.shell.bottomPanel.contains(widget);
852
- }
853
- }
854
- return this.shell.canToggleMaximized();
855
- }
856
-
857
- /**
858
- * Maximize the bottom or the main dockpanel based on the widget.
859
- * @param event used to find the selected widget.
860
- */
861
- private toggleMaximized(event?: Event): void {
862
- if (event?.target instanceof HTMLElement) {
863
- const widget = this.shell.findWidgetForElement(event.target);
864
- if (widget) {
865
- if (this.shell.mainPanel.contains(widget)) {
866
- this.shell.mainPanel.toggleMaximized();
867
- } else if (this.shell.bottomPanel.contains(widget)) {
868
- this.shell.bottomPanel.toggleMaximized();
869
- }
870
- if (widget instanceof TabBar) {
871
- // reveals the widget when maximized.
872
- const title = this.shell.findTitle(widget, event);
873
- if (title) {
874
- this.shell.revealWidget(title.owner.id);
875
- }
876
- }
877
- }
878
- } else {
879
- this.shell.toggleMaximized();
880
- }
881
- }
882
-
883
826
  private isElectron(): boolean {
884
827
  return environment.electron.is();
885
828
  }
@@ -165,11 +165,12 @@ export const frontendApplicationModule = new ContainerModule((bind, unbind, isBo
165
165
 
166
166
  bind(DockPanelRendererFactory).toFactory(context => () => context.container.get(DockPanelRenderer));
167
167
  bind(DockPanelRenderer).toSelf();
168
- bind(TabBarRendererFactory).toFactory(context => () => {
169
- const contextMenuRenderer = context.container.get<ContextMenuRenderer>(ContextMenuRenderer);
170
- const decoratorService = context.container.get<TabBarDecoratorService>(TabBarDecoratorService);
171
- const iconThemeService = context.container.get<IconThemeService>(IconThemeService);
172
- return new TabBarRenderer(contextMenuRenderer, decoratorService, iconThemeService);
168
+ bind(TabBarRendererFactory).toFactory(({ container }) => () => {
169
+ const contextMenuRenderer = container.get(ContextMenuRenderer);
170
+ const tabBarDecoratorService = container.get(TabBarDecoratorService);
171
+ const iconThemeService = container.get(IconThemeService);
172
+ const selectionService = container.get(SelectionService);
173
+ return new TabBarRenderer(contextMenuRenderer, tabBarDecoratorService, iconThemeService, selectionService);
173
174
  });
174
175
 
175
176
  bindContributionProvider(bind, TabBarDecorator);
@@ -75,10 +75,6 @@ export abstract class PreferenceProvider implements Disposable {
75
75
  }
76
76
 
77
77
  protected deferredChanges: PreferenceProviderDataChanges | undefined;
78
- protected _pendingChanges: Promise<boolean> = Promise.resolve(false);
79
- get pendingChanges(): Promise<boolean> {
80
- return this._pendingChanges;
81
- }
82
78
 
83
79
  /**
84
80
  * Informs the listeners that one or more preferences of this provider are changed.
@@ -94,7 +90,7 @@ export abstract class PreferenceProvider implements Disposable {
94
90
  this.mergePreferenceProviderDataChange(changes[preferenceName]);
95
91
  }
96
92
  }
97
- return this._pendingChanges = this.fireDidPreferencesChanged();
93
+ return this.fireDidPreferencesChanged();
98
94
  }
99
95
 
100
96
  protected mergePreferenceProviderDataChange(change: PreferenceProviderDataChange): void {
@@ -193,7 +193,6 @@ describe('Preference Service', () => {
193
193
  assert.strictEqual(prefService.get('editor.insertSpaces'), undefined, 'get after');
194
194
  assert.strictEqual(prefService.get('[go].editor.insertSpaces'), undefined, 'get after overridden');
195
195
 
196
- assert.strictEqual(await prefSchema.pendingChanges, false);
197
196
  assert.deepStrictEqual([], events.map(e => ({
198
197
  preferenceName: e.preferenceName,
199
198
  newValue: e.newValue,
@@ -488,7 +487,6 @@ describe('Preference Service', () => {
488
487
 
489
488
  it('onPreferenceChanged #0', async () => {
490
489
  const { preferences, schema } = prepareServices();
491
- await schema.pendingChanges;
492
490
 
493
491
  const events: PreferenceChange[] = [];
494
492
  preferences.onPreferenceChanged(event => events.push(event));
@@ -511,7 +509,6 @@ describe('Preference Service', () => {
511
509
 
512
510
  it('onPreferenceChanged #1', async () => {
513
511
  const { preferences, schema } = prepareServices();
514
- await schema.pendingChanges;
515
512
 
516
513
  const events: PreferenceChange[] = [];
517
514
  preferences.onPreferenceChanged(event => events.push(event));
@@ -29,10 +29,10 @@ import { Saveable, SaveableWidget, SaveOptions } from '../saveable';
29
29
  import { StatusBarImpl, StatusBarEntry, StatusBarAlignment } from '../status-bar/status-bar';
30
30
  import { TheiaDockPanel, BOTTOM_AREA_ID, MAIN_AREA_ID } from './theia-dock-panel';
31
31
  import { SidePanelHandler, SidePanel, SidePanelHandlerFactory } from './side-panel-handler';
32
- import { TabBarRendererFactory, TabBarRenderer, SHELL_TABBAR_CONTEXT_MENU, ScrollableTabBar, ToolbarAwareTabBar } from './tab-bars';
32
+ import { TabBarRendererFactory, SHELL_TABBAR_CONTEXT_MENU, ScrollableTabBar, ToolbarAwareTabBar } from './tab-bars';
33
33
  import { SplitPositionHandler, SplitPositionOptions } from './split-panels';
34
34
  import { FrontendApplicationStateService } from '../frontend-application-state';
35
- import { TabBarToolbarRegistry, TabBarToolbarFactory, TabBarToolbar } from './tab-bar-toolbar';
35
+ import { TabBarToolbarRegistry, TabBarToolbarFactory } from './tab-bar-toolbar';
36
36
  import { ContextKeyService } from '../context-key-service';
37
37
  import { Emitter } from '../../common/event';
38
38
  import { waitForRevealed, waitForClosed } from '../widgets';
@@ -82,9 +82,9 @@ export class DockPanelRenderer implements DockLayout.IRenderer {
82
82
  readonly tabBarClasses: string[] = [];
83
83
 
84
84
  constructor(
85
- @inject(TabBarRendererFactory) protected readonly tabBarRendererFactory: () => TabBarRenderer,
85
+ @inject(TabBarRendererFactory) protected readonly tabBarRendererFactory: TabBarRendererFactory,
86
86
  @inject(TabBarToolbarRegistry) protected readonly tabBarToolbarRegistry: TabBarToolbarRegistry,
87
- @inject(TabBarToolbarFactory) protected readonly tabBarToolbarFactory: () => TabBarToolbar,
87
+ @inject(TabBarToolbarFactory) protected readonly tabBarToolbarFactory: TabBarToolbarFactory,
88
88
  @inject(BreadcrumbsRendererFactory) protected readonly breadcrumbsRendererFactory: BreadcrumbsRendererFactory,
89
89
  ) { }
90
90
 
@@ -847,20 +847,15 @@ export class ApplicationShell extends Widget {
847
847
  */
848
848
  findTitle(tabBar: TabBar<Widget>, event?: Event): Title<Widget> | undefined {
849
849
  if (event?.target instanceof HTMLElement) {
850
- let tabNode: HTMLElement | null = event.target;
851
- while (tabNode && !tabNode.classList.contains('p-TabBar-tab')) {
852
- tabNode = tabNode.parentElement;
853
- }
854
- if (tabNode && tabNode.title) {
855
- let title = tabBar.titles.find(t => t.caption === tabNode!.title);
856
- if (title) {
857
- return title;
858
- }
859
- title = tabBar.titles.find(t => t.label === tabNode!.title);
860
- if (title) {
861
- return title;
862
- }
850
+ const tabNode = event.target;
851
+
852
+ const titleIndex = Array.from(tabBar.contentNode.getElementsByClassName('p-TabBar-tab'))
853
+ .findIndex(node => node.contains(tabNode));
854
+
855
+ if (titleIndex !== -1) {
856
+ return tabBar.titles[titleIndex];
863
857
  }
858
+
864
859
  }
865
860
  return tabBar.currentTitle || undefined;
866
861
  }
@@ -1832,18 +1827,18 @@ export class ApplicationShell extends Widget {
1832
1827
  return undefined;
1833
1828
  }
1834
1829
 
1835
- canToggleMaximized(): boolean {
1836
- const area = this.currentWidget && this.getAreaFor(this.currentWidget);
1830
+ canToggleMaximized(widget: Widget | undefined = this.currentWidget): boolean {
1831
+ const area = widget && this.getAreaFor(widget);
1837
1832
  return area === 'main' || area === 'bottom';
1838
1833
  }
1839
1834
 
1840
- toggleMaximized(): void {
1841
- const area = this.currentWidget && this.getAreaPanelFor(this.currentWidget);
1835
+ toggleMaximized(widget: Widget | undefined = this.currentWidget): void {
1836
+ const area = widget && this.getAreaPanelFor(widget);
1842
1837
  if (area instanceof TheiaDockPanel && (area === this.mainPanel || area === this.bottomPanel)) {
1843
1838
  area.toggleMaximized();
1839
+ this.revealWidget(widget!.id);
1844
1840
  }
1845
1841
  }
1846
-
1847
1842
  }
1848
1843
 
1849
1844
  /**
@@ -0,0 +1,57 @@
1
+ /********************************************************************************
2
+ * Copyright (C) 2021 Ericsson 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 WITH Classpath-exception-2.0
15
+ ********************************************************************************/
16
+
17
+ import { CommandHandler } from '../../common';
18
+ import { TabBar, Title, Widget } from '../widgets';
19
+ import { ApplicationShell } from './application-shell';
20
+
21
+ type CurrentWidgetCommandAdapterBooleanCheck = (event: Event) => boolean;
22
+ type CurrentWidgetCommandHandlerBooleanCheck = (title: Title<Widget> | undefined, tabbar: TabBar<Widget> | undefined, event: Event) => boolean;
23
+
24
+ export interface TabBarContextMenuCommandHandler extends CommandHandler {
25
+ execute(title: Title<Widget> | undefined, tabbar: TabBar<Widget> | undefined, event: Event): unknown;
26
+ isEnabled?: CurrentWidgetCommandHandlerBooleanCheck;
27
+ isVisible?: CurrentWidgetCommandHandlerBooleanCheck;
28
+ isToggled?: CurrentWidgetCommandHandlerBooleanCheck;
29
+ }
30
+
31
+ /**
32
+ * Creates a command handler that acts on either the widget targeted by a DOM event or the current widget.
33
+ */
34
+ export class CurrentWidgetCommandAdapter implements CommandHandler {
35
+ execute: (event: Event) => unknown;
36
+ isEnabled?: CurrentWidgetCommandAdapterBooleanCheck;
37
+ isVisible?: CurrentWidgetCommandAdapterBooleanCheck;
38
+ isToggled?: CurrentWidgetCommandAdapterBooleanCheck;
39
+ constructor(shell: ApplicationShell, handler: TabBarContextMenuCommandHandler) {
40
+ this.execute = (event: Event) => handler.execute(...this.transformArguments(shell, event));
41
+ if (handler.isEnabled) {
42
+ this.isEnabled = (event: Event) => !!handler.isEnabled?.(...this.transformArguments(shell, event));
43
+ }
44
+ if (handler.isVisible) {
45
+ this.isVisible = (event: Event) => !!handler.isVisible?.(...this.transformArguments(shell, event));
46
+ }
47
+ if (handler.isToggled) {
48
+ this.isToggled = (event: Event) => !!handler.isToggled?.(...this.transformArguments(shell, event));
49
+ }
50
+ }
51
+
52
+ protected transformArguments(shell: ApplicationShell, event: Event): [Title<Widget> | undefined, TabBar<Widget> | undefined, Event] {
53
+ const tabBar = shell.findTabBar(event);
54
+ const title = tabBar && shell.findTitle(tabBar, event);
55
+ return [title, tabBar, event];
56
+ }
57
+ }
@@ -17,7 +17,7 @@
17
17
  import PerfectScrollbar from 'perfect-scrollbar';
18
18
  import { TabBar, Title, Widget } from '@phosphor/widgets';
19
19
  import { VirtualElement, h, VirtualDOM, ElementInlineStyle } from '@phosphor/virtualdom';
20
- import { Disposable, DisposableCollection, MenuPath, notEmpty } from '../../common';
20
+ import { Disposable, DisposableCollection, MenuPath, notEmpty, SelectionService } from '../../common';
21
21
  import { ContextMenuRenderer } from '../context-menu-renderer';
22
22
  import { Signal, Slot } from '@phosphor/signaling';
23
23
  import { Message, MessageLoop } from '@phosphor/messaging';
@@ -37,8 +37,14 @@ const HIDDEN_CONTENT_CLASS = 'theia-TabBar-hidden-content';
37
37
 
38
38
  /** Menu path for tab bars used throughout the application shell. */
39
39
  export const SHELL_TABBAR_CONTEXT_MENU: MenuPath = ['shell-tabbar-context-menu'];
40
+ export const SHELL_TABBAR_CONTEXT_CLOSE: MenuPath = [...SHELL_TABBAR_CONTEXT_MENU, '0_close'];
41
+ export const SHELL_TABBAR_CONTEXT_COPY: MenuPath = [...SHELL_TABBAR_CONTEXT_MENU, '1_copy'];
42
+ // Kept here in anticipation of tab pinning behavior implemented in tab-bars.ts
43
+ export const SHELL_TABBAR_CONTEXT_PIN: MenuPath = [...SHELL_TABBAR_CONTEXT_MENU, '4_pin'];
44
+ export const SHELL_TABBAR_CONTEXT_SPLIT: MenuPath = [...SHELL_TABBAR_CONTEXT_MENU, '5_split'];
40
45
 
41
46
  export const TabBarRendererFactory = Symbol('TabBarRendererFactory');
47
+ export type TabBarRendererFactory = () => TabBarRenderer;
42
48
 
43
49
  /**
44
50
  * Size information of DOM elements used for rendering tabs in side bars.
@@ -66,7 +72,6 @@ export interface SideBarRenderData extends TabBar.IRenderData<Widget> {
66
72
  * automatically.
67
73
  */
68
74
  export class TabBarRenderer extends TabBar.Renderer {
69
-
70
75
  /**
71
76
  * The menu path used to render the context menu.
72
77
  */
@@ -80,7 +85,8 @@ export class TabBarRenderer extends TabBar.Renderer {
80
85
  constructor(
81
86
  protected readonly contextMenuRenderer?: ContextMenuRenderer,
82
87
  protected readonly decoratorService?: TabBarDecoratorService,
83
- protected readonly iconThemeService?: IconThemeService
88
+ protected readonly iconThemeService?: IconThemeService,
89
+ protected readonly selectionService?: SelectionService,
84
90
  ) {
85
91
  super();
86
92
  if (this.decoratorService) {
@@ -437,7 +443,27 @@ export class TabBarRenderer extends TabBar.Renderer {
437
443
  if (this.contextMenuRenderer && this.contextMenuPath && event.currentTarget instanceof HTMLElement) {
438
444
  event.stopPropagation();
439
445
  event.preventDefault();
440
- this.contextMenuRenderer.render(this.contextMenuPath, event);
446
+ let widget: Widget | undefined = undefined;
447
+ if (this.tabBar) {
448
+ const titleIndex = Array.from(this.tabBar.contentNode.getElementsByClassName('p-TabBar-tab'))
449
+ .findIndex(node => node.contains(event.currentTarget as HTMLElement));
450
+ if (titleIndex !== -1) {
451
+ widget = this.tabBar.titles[titleIndex].owner;
452
+ }
453
+ }
454
+
455
+ const oldSelection = this.selectionService?.selection;
456
+ if (widget && this.selectionService) {
457
+ this.selectionService.selection = NavigatableWidget.is(widget) ? { uri: widget.getResourceUri() } : widget;
458
+ }
459
+
460
+ this.contextMenuRenderer.render({
461
+ menuPath: this.contextMenuPath!,
462
+ anchor: event,
463
+ args: [event],
464
+ // We'd like to wait until the command triggered by the context menu has been run, but this should let it get through the preamble, at least.
465
+ onHide: () => setTimeout(() => { if (this.selectionService) { this.selectionService.selection = oldSelection; } })
466
+ });
441
467
  }
442
468
  };
443
469
 
@@ -452,7 +478,6 @@ export class TabBarRenderer extends TabBar.Renderer {
452
478
  }
453
479
  }
454
480
  };
455
-
456
481
  }
457
482
 
458
483
  /**
@@ -0,0 +1,41 @@
1
+ /********************************************************************************
2
+ * Copyright (C) 2021 Red Hat 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 WITH Classpath-exception-2.0
15
+ ********************************************************************************/
16
+ import * as assert from 'assert';
17
+ import { waitForEvent } from './promise-util';
18
+ import { Emitter } from './event';
19
+
20
+ describe('promise-util', () => {
21
+ it('should time out', async () => {
22
+ const emitter = new Emitter<string>();
23
+ try {
24
+ await waitForEvent(emitter.event, 1000);
25
+ assert.fail('did not time out');
26
+ } catch (e) {
27
+ // OK
28
+ }
29
+ });
30
+
31
+ describe('promise-util', () => {
32
+ it('should get event', async () => {
33
+ const emitter = new Emitter<string>();
34
+ setTimeout(() => {
35
+ emitter.fire('abcd');
36
+ }, 500);
37
+ assert.strictEqual(await waitForEvent(emitter.event, 1000), 'abcd');
38
+ });
39
+ });
40
+
41
+ });
@@ -14,7 +14,9 @@
14
14
  * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15
15
  ********************************************************************************/
16
16
 
17
- import { CancellationToken, cancelled } from './cancellation';
17
+ import { Disposable } from './disposable';
18
+ import { Event } from './event';
19
+ import { CancellationToken, CancellationError, cancelled } from './cancellation';
18
20
 
19
21
  /**
20
22
  * Simple implementation of the deferred pattern.
@@ -90,3 +92,20 @@ export function delay<T>(ms: number): (value: T) => Promise<T> {
90
92
  export async function wait(ms: number): Promise<void> {
91
93
  await delay(ms)(undefined);
92
94
  }
95
+
96
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
97
+ export function waitForEvent<T>(event: Event<T>, ms: number, thisArg?: any, disposables?: Disposable[]): Promise<T> {
98
+ return new Promise<T>((resolve, reject) => {
99
+ const registration = setTimeout(() => {
100
+ listener.dispose();
101
+ reject(new CancellationError());
102
+ }, ms);
103
+
104
+ const listener = event((evt: T) => {
105
+ clearTimeout(registration);
106
+ listener.dispose();
107
+ resolve(evt);
108
+ }, thisArg, disposables);
109
+
110
+ });
111
+ }