diff --git a/CHANGELOG.md b/CHANGELOG.md
index 84bc0093bec1c..35db2ed6e21e3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,7 @@
- Note: the updated dependency may have a [performance impact](https://github.com/eclipse-theia/theia/pull/7715#issuecomment-667434288) on the deployment of plugins.
- [[electron]](#1_5_0_electron_main_extension) Electron applications can now be configured/extended through `inversify`. Added new `electronMain` extension points to provide inversify container modules. [#8076](https://github.com/eclipse-theia/theia/pull/8076)
+- [core] added functionality for handling context menu for `tab-bars` without activating the shell tab-bar [#6965](https://github.com/eclipse-theia/theia/pull/6965)
[Breaking Changes:](#breaking_changes_1.5.0)
@@ -53,6 +54,11 @@
- [[repo]](#1_5_0_drop_node_10_support) support for `Node 10` is dropped. [#8290](https://github.com/eclipse-theia/theia/pull/8290)
- From now on, Node.js `12.x` is required when building.\
The recommended minimum version is aligned with `electron` (Node.js `12.14.1`).
+- [core] Context-menu for `tab-bars` requires an `Event` to be passed onto it to perform actions without activating the shell tab-bar [#6965](https://github.com/eclipse-theia/theia/pull/6965)
+ - Removed the logic from `handleContextMenuEvent()` that gives focus to the widget upon opening the context menu, instead functionality added to handle it without activating the widget
+ - This change triggers the context menu for a given shell tab-bar without the need to activate it
+ - While registering a command, `Event` should be passed down, if not passed, then the commands will not work correctly as they no longer rely on the activation of tab-bar
+- [core] Moved `findTitle()` and `findTabBar()` from `common-frontend-contribution.ts` to `application-shell.ts` [#6965](https://github.com/eclipse-theia/theia/pull/6965)
## v1.4.0 - 30/07/2020
diff --git a/packages/core/src/browser/common-frontend-contribution.ts b/packages/core/src/browser/common-frontend-contribution.ts
index fc16ea689b538..31cf4ef6731f6 100644
--- a/packages/core/src/browser/common-frontend-contribution.ts
+++ b/packages/core/src/browser/common-frontend-contribution.ts
@@ -18,7 +18,7 @@
import debounce = require('lodash.debounce');
import { injectable, inject } from 'inversify';
-import { TabBar, Widget, Title } from '@phosphor/widgets';
+import { TabBar, Widget } from '@phosphor/widgets';
import { MAIN_MENU_BAR, MenuContribution, MenuModelRegistry } from '../common/menu';
import { KeybindingContribution, KeybindingRegistry } from './keybinding';
import { FrontendApplicationContribution } from './frontend-application';
@@ -603,55 +603,59 @@ export class CommonFrontendContribution implements FrontendApplicationContributi
});
commandRegistry.registerCommand(CommonCommands.CLOSE_TAB, {
isEnabled: (event?: Event) => {
- const tabBar = this.findTabBar(event);
+ const tabBar = this.shell.findTabBar(event);
if (!tabBar) {
return false;
}
- const currentTitle = this.findTitle(tabBar, event);
+ const currentTitle = this.shell.findTitle(tabBar, event);
return currentTitle !== undefined && currentTitle.closable;
},
execute: (event?: Event) => {
- const tabBar = this.findTabBar(event)!;
- const currentTitle = this.findTitle(tabBar, event);
+ const tabBar = this.shell.findTabBar(event)!;
+ const currentTitle = this.shell.findTitle(tabBar, event);
this.shell.closeTabs(tabBar, title => title === currentTitle);
}
});
commandRegistry.registerCommand(CommonCommands.CLOSE_OTHER_TABS, {
isEnabled: (event?: Event) => {
- const tabBar = this.findTabBar(event);
+ const tabBar = this.shell.findTabBar(event);
if (!tabBar) {
return false;
}
- const currentTitle = this.findTitle(tabBar, event);
+ const currentTitle = this.shell.findTitle(tabBar, event);
return tabBar.titles.some(title => title !== currentTitle && title.closable);
},
execute: (event?: Event) => {
- const tabBar = this.findTabBar(event)!;
- const currentTitle = this.findTitle(tabBar, event);
+ const tabBar = this.shell.findTabBar(event)!;
+ const currentTitle = this.shell.findTitle(tabBar, event);
this.shell.closeTabs(tabBar, title => title !== currentTitle && title.closable);
}
});
commandRegistry.registerCommand(CommonCommands.CLOSE_RIGHT_TABS, {
isEnabled: (event?: Event) => {
- const tabBar = this.findTabBar(event);
- return tabBar !== undefined && tabBar.titles.some((title, index) => index > tabBar.currentIndex && title.closable);
+ const tabBar = this.shell.findTabBar(event);
+ if (!tabBar) {
+ return false;
+ }
+ const currentIndex = this.findTitleIndex(tabBar, event);
+ return tabBar.titles.some((title, index) => index > currentIndex && title.closable);
},
isVisible: (event?: Event) => {
const area = this.findTabArea(event);
return area !== undefined && area !== 'left' && area !== 'right';
},
execute: (event?: Event) => {
- const tabBar = this.findTabBar(event)!;
- const currentIndex = tabBar.currentIndex;
+ const tabBar = this.shell.findTabBar(event)!;
+ const currentIndex = this.findTitleIndex(tabBar, event);
this.shell.closeTabs(tabBar, (title, index) => index > currentIndex && title.closable);
}
});
commandRegistry.registerCommand(CommonCommands.CLOSE_ALL_TABS, {
isEnabled: (event?: Event) => {
- const tabBar = this.findTabBar(event);
+ const tabBar = this.shell.findTabBar(event);
return tabBar !== undefined && tabBar.titles.some(title => title.closable);
},
- execute: (event?: Event) => this.shell.closeTabs(this.findTabBar(event)!, title => title.closable)
+ execute: (event?: Event) => this.shell.closeTabs(this.shell.findTabBar(event)!, title => title.closable)
});
commandRegistry.registerCommand(CommonCommands.CLOSE_MAIN_TAB, {
isEnabled: () => {
@@ -698,9 +702,9 @@ export class CommonFrontendContribution implements FrontendApplicationContributi
}
});
commandRegistry.registerCommand(CommonCommands.TOGGLE_MAXIMIZED, {
- isEnabled: () => this.shell.canToggleMaximized(),
- isVisible: () => this.shell.canToggleMaximized(),
- execute: () => this.shell.toggleMaximized()
+ isEnabled: (event?: Event) => this.canToggleMaximized(event),
+ isVisible: (event?: Event) => this.canToggleMaximized(event),
+ execute: (event?: Event) => this.toggleMaximized(event)
});
commandRegistry.registerCommand(CommonCommands.SAVE, {
@@ -725,38 +729,61 @@ export class CommonFrontendContribution implements FrontendApplicationContributi
});
}
- private findTabBar(event?: Event): TabBar | undefined {
- if (event && event.target) {
- const tabBar = this.shell.findWidgetForElement(event.target as HTMLElement);
- if (tabBar instanceof TabBar) {
- return tabBar;
- }
- }
- return this.shell.currentTabBar;
- }
-
private findTabArea(event?: Event): ApplicationShell.Area | undefined {
- const tabBar = this.findTabBar(event);
+ const tabBar = this.shell.findTabBar(event);
if (tabBar) {
return this.shell.getAreaFor(tabBar);
}
return this.shell.currentTabArea;
}
- private findTitle(tabBar: TabBar, event?: Event): Title | undefined {
- if (event && event.target) {
- let tabNode: HTMLElement | null = event.target as HTMLElement;
- while (tabNode && !tabNode.classList.contains('p-TabBar-tab')) {
- tabNode = tabNode.parentElement;
+ /**
+ * Finds the index of the selected title from the tab-bar.
+ * @param tabBar: used for providing an array of titles.
+ * @returns the index of the selected title if it is available in the tab-bar, else returns the index of currently-selected title.
+ */
+ private findTitleIndex(tabBar: TabBar, event?: Event): number {
+ if (event) {
+ const targetTitle = this.shell.findTitle(tabBar, event);
+ return targetTitle ? tabBar.titles.indexOf(targetTitle) : tabBar.currentIndex;
+ }
+ return tabBar.currentIndex;
+ }
+
+ private canToggleMaximized(event?: Event): boolean {
+ if (event?.target instanceof HTMLElement) {
+ const widget = this.shell.findWidgetForElement(event.target);
+ if (widget) {
+ return this.shell.mainPanel.contains(widget) || this.shell.bottomPanel.contains(widget);
}
- if (tabNode && tabNode.title) {
- const title = tabBar.titles.find(t => t.label === tabNode!.title);
- if (title) {
- return title;
+ }
+ return this.shell.canToggleMaximized();
+ }
+
+ /**
+ * Maximize the bottom or the main dockpanel based on the widget.
+ * @param event used to find the selected widget.
+ */
+ private toggleMaximized(event?: Event): void {
+ if (event?.target instanceof HTMLElement) {
+ const widget = this.shell.findWidgetForElement(event.target);
+ if (widget) {
+ if (this.shell.mainPanel.contains(widget)) {
+ this.shell.mainPanel.toggleMaximized();
+ } else if (this.shell.bottomPanel.contains(widget)) {
+ this.shell.bottomPanel.toggleMaximized();
+ }
+ if (widget instanceof TabBar) {
+ // reveals the widget when maximized.
+ const title = this.shell.findTitle(widget, event);
+ if (title) {
+ this.shell.revealWidget(title.owner.id);
+ }
}
}
+ } else {
+ this.shell.toggleMaximized();
}
- return tabBar.currentTitle || undefined;
}
private isElectron(): boolean {
diff --git a/packages/core/src/browser/shell/application-shell.ts b/packages/core/src/browser/shell/application-shell.ts
index 89105291f7b69..86b4693ccadac 100644
--- a/packages/core/src/browser/shell/application-shell.ts
+++ b/packages/core/src/browser/shell/application-shell.ts
@@ -811,6 +811,45 @@ export class ApplicationShell extends Widget {
return result;
}
+ /**
+ * Finds the title widget from the tab-bar.
+ * @param tabBar used for providing an array of titles.
+ * @returns the selected title widget, else returns the currentTitle or undefined.
+ */
+ findTitle(tabBar: TabBar, event?: Event): Title | undefined {
+ if (event?.target instanceof HTMLElement) {
+ let tabNode: HTMLElement | null = event.target;
+ while (tabNode && !tabNode.classList.contains('p-TabBar-tab')) {
+ tabNode = tabNode.parentElement;
+ }
+ if (tabNode && tabNode.title) {
+ let title = tabBar.titles.find(t => t.caption === tabNode!.title);
+ if (title) {
+ return title;
+ }
+ title = tabBar.titles.find(t => t.label === tabNode!.title);
+ if (title) {
+ return title;
+ }
+ }
+ }
+ return tabBar.currentTitle || undefined;
+ }
+
+ /**
+ * Finds the tab-bar widget.
+ * @returns the selected tab-bar, else returns the currentTabBar.
+ */
+ findTabBar(event?: Event): TabBar | undefined {
+ if (event?.target instanceof HTMLElement) {
+ const tabBar = this.findWidgetForElement(event.target);
+ if (tabBar instanceof TabBar) {
+ return tabBar;
+ }
+ }
+ return this.currentTabBar;
+ }
+
/**
* The current widget in the application shell. The current widget is the last widget that
* was active and not yet closed. See the remarks to `activeWidget` on what _active_ means.
diff --git a/packages/core/src/browser/shell/tab-bars.ts b/packages/core/src/browser/shell/tab-bars.ts
index 82df6a7d63ac0..03ecff0f4115c 100644
--- a/packages/core/src/browser/shell/tab-bars.ts
+++ b/packages/core/src/browser/shell/tab-bars.ts
@@ -435,18 +435,6 @@ export class TabBarRenderer extends TabBar.Renderer {
if (this.contextMenuRenderer && this.contextMenuPath && event.currentTarget instanceof HTMLElement) {
event.stopPropagation();
event.preventDefault();
-
- if (this.tabBar) {
- const id = event.currentTarget.id;
- // eslint-disable-next-line no-null/no-null
- const title = this.tabBar.titles.find(t => this.createTabId(t) === id) || null;
- this.tabBar.currentTitle = title;
- this.tabBar.activate();
- if (title) {
- title.owner.activate();
- }
- }
-
this.contextMenuRenderer.render(this.contextMenuPath, event);
}
};
diff --git a/packages/navigator/src/browser/navigator-contribution.ts b/packages/navigator/src/browser/navigator-contribution.ts
index f5f2b7df7b373..da3dfc4b29d9c 100644
--- a/packages/navigator/src/browser/navigator-contribution.ts
+++ b/packages/navigator/src/browser/navigator-contribution.ts
@@ -28,7 +28,8 @@ import {
PreferenceService,
SelectableTreeNode,
SHELL_TABBAR_CONTEXT_MENU,
- Widget
+ Widget,
+ Title
} from '@theia/core/lib/browser';
import { FileDownloadCommands } from '@theia/filesystem/lib/browser/download/file-download-command-contribution';
import {
@@ -260,9 +261,18 @@ export class FileNavigatorContribution extends AbstractViewContribution this.openView({ activate: true })
});
registry.registerCommand(FileNavigatorCommands.REVEAL_IN_NAVIGATOR, {
- execute: () => this.openView({ activate: true }).then(() => this.selectWidgetFileNode(this.shell.currentWidget)),
- isEnabled: () => Navigatable.is(this.shell.currentWidget),
- isVisible: () => Navigatable.is(this.shell.currentWidget)
+ execute: (event?: Event) => {
+ const widget = this.findTargetedWidget(event);
+ this.openView({ activate: true }).then(() => this.selectWidgetFileNode(widget || this.shell.currentWidget));
+ },
+ isEnabled: (event?: Event) => {
+ const widget = this.findTargetedWidget(event);
+ return widget ? Navigatable.is(widget) : Navigatable.is(this.shell.currentWidget);
+ },
+ isVisible: (event?: Event) => {
+ const widget = this.findTargetedWidget(event);
+ return widget ? Navigatable.is(widget) : Navigatable.is(this.shell.currentWidget);
+ }
});
registry.registerCommand(FileNavigatorCommands.TOGGLE_HIDDEN_FILES, {
execute: () => {
@@ -545,6 +555,19 @@ export class FileNavigatorContribution extends AbstractViewContribution | undefined;
+ if (event) {
+ const tab = this.shell.findTabBar(event);
+ title = tab && this.shell.findTitle(tab, event);
+ }
+ return title && title.owner;
+ }
+
/**
* Reveals and selects node in the file navigator to which given widget is related.
* Does nothing if given widget undefined or doesn't have related resource.