Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 63 additions & 16 deletions src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,9 @@ export class MultiEditorTabsControl extends EditorTabsControl {
private readonly unpinEditorAction = this._register(this.instantiationService.createInstance(UnpinEditorAction, UnpinEditorAction.ID, UnpinEditorAction.LABEL));
private readonly closeOtherEditorTabsInGroupAction = this._register(this.instantiationService.createInstance(CloseOtherEditorTabsInGroupAction, CloseOtherEditorTabsInGroupAction.ID, CloseOtherEditorTabsInGroupAction.LABEL));

// Alt-hold alternative to a tab's close action (JetBrains-style); see updateTabActionsForAltState().
private wantsCloseOthersAction: boolean;
// Alt-hold alternative to a tab's close action (JetBrains-style), applied only
// to the currently hovered tab; see updateTabActionForHoveredTab().
private hoveredTabIndex: number | undefined;

private readonly tabResourceLabels = this._register(this.instantiationService.createInstance(ResourceLabels, DEFAULT_LABELS_CONTAINER));
private tabLabels: IEditorInputLabel[] = [];
Expand Down Expand Up @@ -175,24 +176,47 @@ export class MultiEditorTabsControl extends EditorTabsControl {
// React to decorations changing for our resource labels
this._register(this.tabResourceLabels.onDidChangeDecorations(() => this.doHandleDecorationsChange()));

// React to Alt being held/released to swap in the "Close Others" tab action. Initialize
// from the current state too, in case this control is created mid-hold.
this.wantsCloseOthersAction = modifierKeyEmitter.keyStatus.altKey;
this._register(modifierKeyEmitter.event(() => this.updateTabActionsForAltState()));
// React to Alt being held/released to swap in the "Close Others" tab action
// for the currently hovered tab only (if any).
this._register(modifierKeyEmitter.event(() => this.updateTabActionForHoveredTab()));
}

private updateTabActionsForAltState(): void {
const wantsCloseOthersAction = modifierKeyEmitter.keyStatus.altKey;
if (wantsCloseOthersAction === this.wantsCloseOthersAction) {
private updateTabActionForHoveredTab(): void {
if (typeof this.hoveredTabIndex !== 'number') {
return; // no tab hovered, nothing to update
}

this.redrawTabActionAtIndex(this.hoveredTabIndex);
}

private redrawTabActionAtIndex(tabIndex: number): void {
const editor = this.tabsModel.getEditorByIndex(tabIndex);
if (editor) {
this.doWithTab(tabIndex, editor, (editor, tabIndex, tabContainer, tabLabelWidget, tabLabel, tabActionBar) => this.redrawTabAction(editor, tabIndex, tabContainer, tabActionBar));
}
}

// Tracks which tab (if any) the mouse is currently over so the Alt-hold "Close
// Others" swap (see redrawTabAction()) applies only to that single tab.
private setHoveredTab(tabIndex: number | undefined): void {
if (this.hoveredTabIndex === tabIndex) {
return;
}

this.wantsCloseOthersAction = wantsCloseOthersAction;
const previousHoveredTabIndex = this.hoveredTabIndex;
this.hoveredTabIndex = tabIndex;

// Only the action items need to change here, not labels/decorations/toolbar/layout.
this.forEachTab((editor, tabIndex, tabContainer, tabLabelWidget, tabLabel, tabActionBar) => {
this.redrawTabAction(editor, tabIndex, tabContainer, tabActionBar);
});
if (!modifierKeyEmitter.keyStatus.altKey) {
return; // Alt is not held, no action swap in effect to redraw
}

if (typeof previousHoveredTabIndex === 'number') {
this.redrawTabActionAtIndex(previousHoveredTabIndex);
}

if (typeof tabIndex === 'number') {
this.redrawTabActionAtIndex(tabIndex);
}
}

protected override create(parent: HTMLElement): HTMLElement {
Expand Down Expand Up @@ -397,6 +421,11 @@ export class MultiEditorTabsControl extends EditorTabsControl {
}
}));

// Clear the hovered tab once the mouse leaves the tabs container entirely
this._register(addDisposableListener(tabsContainer, EventType.MOUSE_LEAVE, () => {
this.setHoveredTab(undefined);
}));

// Prevent auto-pasting (https://github.com/microsoft/vscode/issues/201696)
if (isLinux) {
this._register(addDisposableListener(tabsContainer, EventType.MOUSE_UP, e => {
Expand Down Expand Up @@ -666,6 +695,12 @@ export class MultiEditorTabsControl extends EditorTabsControl {

private handleClosedEditors(): void {

// A stale hovered tab index could otherwise leave a tab
// showing "Close Others" after the tabs it pointed past got removed
if (typeof this.hoveredTabIndex === 'number' && this.hoveredTabIndex >= this.tabsModel.count) {
this.setHoveredTab(undefined);
}

// There are tabs to show
if (this.tabsModel.count) {

Expand Down Expand Up @@ -1023,6 +1058,17 @@ export class MultiEditorTabsControl extends EditorTabsControl {
disposables.add(addDisposableListener(tab, EventType.MOUSE_DOWN, e => handleClickOrTouch(e, false)));
disposables.add(addDisposableListener(tab, TouchEventType.Tap, (e: GestureEvent) => handleClickOrTouch(e, true))); // Preserve focus on touch #125470

// Track hover so the Alt-hold "Close Others" action swap (see redrawTabAction())
// only applies to the tab the mouse is currently over.
disposables.add(addDisposableListener(tab, EventType.MOUSE_ENTER, () => {
this.setHoveredTab(tabIndex);
}));
disposables.add(addDisposableListener(tab, EventType.MOUSE_LEAVE, () => {
if (this.hoveredTabIndex === tabIndex) {
this.setHoveredTab(undefined);
}
}));

// Touch Scroll Support
disposables.add(addDisposableListener(tab, TouchEventType.Change, (e: GestureEvent) => {
tabsScrollbar.setScrollPosition({ scrollLeft: tabsScrollbar.getScrollPosition().scrollLeft - e.translationX });
Expand Down Expand Up @@ -1622,8 +1668,9 @@ export class MultiEditorTabsControl extends EditorTabsControl {
const hasCloseAction = isCloseable && !hasUnpinAction && options.tabActionCloseVisibility;
const hasAction = hasUnpinAction || hasCloseAction;

// Alt swaps a visible Close action to Close Others; Unpin is unaffected.
const wantsCloseOthersAction = hasCloseAction && this.wantsCloseOthersAction;
// Alt swaps a visible Close action to Close Others, but only for the
// currently hovered tab; Unpin is unaffected.
const wantsCloseOthersAction = hasCloseAction && modifierKeyEmitter.keyStatus.altKey && tabIndex === this.hoveredTabIndex;
this.closeOtherEditorTabsInGroupAction.enabled = this.groupView.count > 1;

let tabAction;
Expand Down
Loading