Skip to content

Commit ec8a43f

Browse files
Copilotbenibenj
andauthored
Only swap Alt-hold close action for the hovered tab in MultiEditorTabsControl (#331772)
* Initial plan * Only swap Alt-hold close action for the hovered tab Co-authored-by: benibenj <44439583+benibenj@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: benibenj <44439583+benibenj@users.noreply.github.com>
1 parent 18a568d commit ec8a43f

1 file changed

Lines changed: 63 additions & 16 deletions

File tree

src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts

Lines changed: 63 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,9 @@ export class MultiEditorTabsControl extends EditorTabsControl {
120120
private readonly unpinEditorAction = this._register(this.instantiationService.createInstance(UnpinEditorAction, UnpinEditorAction.ID, UnpinEditorAction.LABEL));
121121
private readonly closeOtherEditorTabsInGroupAction = this._register(this.instantiationService.createInstance(CloseOtherEditorTabsInGroupAction, CloseOtherEditorTabsInGroupAction.ID, CloseOtherEditorTabsInGroupAction.LABEL));
122122

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

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

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

184-
private updateTabActionsForAltState(): void {
185-
const wantsCloseOthersAction = modifierKeyEmitter.keyStatus.altKey;
186-
if (wantsCloseOthersAction === this.wantsCloseOthersAction) {
184+
private updateTabActionForHoveredTab(): void {
185+
if (typeof this.hoveredTabIndex !== 'number') {
186+
return; // no tab hovered, nothing to update
187+
}
188+
189+
this.redrawTabActionAtIndex(this.hoveredTabIndex);
190+
}
191+
192+
private redrawTabActionAtIndex(tabIndex: number): void {
193+
const editor = this.tabsModel.getEditorByIndex(tabIndex);
194+
if (editor) {
195+
this.doWithTab(tabIndex, editor, (editor, tabIndex, tabContainer, tabLabelWidget, tabLabel, tabActionBar) => this.redrawTabAction(editor, tabIndex, tabContainer, tabActionBar));
196+
}
197+
}
198+
199+
// Tracks which tab (if any) the mouse is currently over so the Alt-hold "Close
200+
// Others" swap (see redrawTabAction()) applies only to that single tab.
201+
private setHoveredTab(tabIndex: number | undefined): void {
202+
if (this.hoveredTabIndex === tabIndex) {
187203
return;
188204
}
189205

190-
this.wantsCloseOthersAction = wantsCloseOthersAction;
206+
const previousHoveredTabIndex = this.hoveredTabIndex;
207+
this.hoveredTabIndex = tabIndex;
191208

192-
// Only the action items need to change here, not labels/decorations/toolbar/layout.
193-
this.forEachTab((editor, tabIndex, tabContainer, tabLabelWidget, tabLabel, tabActionBar) => {
194-
this.redrawTabAction(editor, tabIndex, tabContainer, tabActionBar);
195-
});
209+
if (!modifierKeyEmitter.keyStatus.altKey) {
210+
return; // Alt is not held, no action swap in effect to redraw
211+
}
212+
213+
if (typeof previousHoveredTabIndex === 'number') {
214+
this.redrawTabActionAtIndex(previousHoveredTabIndex);
215+
}
216+
217+
if (typeof tabIndex === 'number') {
218+
this.redrawTabActionAtIndex(tabIndex);
219+
}
196220
}
197221

198222
protected override create(parent: HTMLElement): HTMLElement {
@@ -397,6 +421,11 @@ export class MultiEditorTabsControl extends EditorTabsControl {
397421
}
398422
}));
399423

424+
// Clear the hovered tab once the mouse leaves the tabs container entirely
425+
this._register(addDisposableListener(tabsContainer, EventType.MOUSE_LEAVE, () => {
426+
this.setHoveredTab(undefined);
427+
}));
428+
400429
// Prevent auto-pasting (https://github.com/microsoft/vscode/issues/201696)
401430
if (isLinux) {
402431
this._register(addDisposableListener(tabsContainer, EventType.MOUSE_UP, e => {
@@ -666,6 +695,12 @@ export class MultiEditorTabsControl extends EditorTabsControl {
666695

667696
private handleClosedEditors(): void {
668697

698+
// A stale hovered tab index could otherwise leave a tab
699+
// showing "Close Others" after the tabs it pointed past got removed
700+
if (typeof this.hoveredTabIndex === 'number' && this.hoveredTabIndex >= this.tabsModel.count) {
701+
this.setHoveredTab(undefined);
702+
}
703+
669704
// There are tabs to show
670705
if (this.tabsModel.count) {
671706

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

1061+
// Track hover so the Alt-hold "Close Others" action swap (see redrawTabAction())
1062+
// only applies to the tab the mouse is currently over.
1063+
disposables.add(addDisposableListener(tab, EventType.MOUSE_ENTER, () => {
1064+
this.setHoveredTab(tabIndex);
1065+
}));
1066+
disposables.add(addDisposableListener(tab, EventType.MOUSE_LEAVE, () => {
1067+
if (this.hoveredTabIndex === tabIndex) {
1068+
this.setHoveredTab(undefined);
1069+
}
1070+
}));
1071+
10261072
// Touch Scroll Support
10271073
disposables.add(addDisposableListener(tab, TouchEventType.Change, (e: GestureEvent) => {
10281074
tabsScrollbar.setScrollPosition({ scrollLeft: tabsScrollbar.getScrollPosition().scrollLeft - e.translationX });
@@ -1622,8 +1668,9 @@ export class MultiEditorTabsControl extends EditorTabsControl {
16221668
const hasCloseAction = isCloseable && !hasUnpinAction && options.tabActionCloseVisibility;
16231669
const hasAction = hasUnpinAction || hasCloseAction;
16241670

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

16291676
let tabAction;

0 commit comments

Comments
 (0)