@@ -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