From 19c999bd19e56fc2ec9b1b968927a487917fd8d8 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 8 Sep 2020 10:10:42 +0200 Subject: [PATCH] pinned tabs - swap action for pinned tabs unless compact --- src/vs/base/browser/ui/actionbar/actionbar.ts | 26 +++-- src/vs/base/test/browser/actionbar.test.ts | 35 ++++++- .../browser/parts/editor/editorActions.ts | 20 +++- .../parts/editor/media/tabstitlecontrol.css | 86 ++++++++-------- .../browser/parts/editor/tabsTitleControl.ts | 97 +++++++++++-------- .../electron-sandbox/accessibilityService.ts | 2 +- 6 files changed, 174 insertions(+), 92 deletions(-) diff --git a/src/vs/base/browser/ui/actionbar/actionbar.ts b/src/vs/base/browser/ui/actionbar/actionbar.ts index aef1d1c1e1d..2b991523182 100644 --- a/src/vs/base/browser/ui/actionbar/actionbar.ts +++ b/src/vs/base/browser/ui/actionbar/actionbar.ts @@ -10,7 +10,7 @@ import * as DOM from 'vs/base/browser/dom'; import * as types from 'vs/base/common/types'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { Event, Emitter } from 'vs/base/common/event'; +import { Emitter } from 'vs/base/common/event'; import { IActionViewItemOptions, ActionViewItem, BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; export const enum ActionsOrientation { @@ -47,8 +47,9 @@ export class ActionBar extends Disposable implements IActionRunner { private _actionRunner: IActionRunner; private _context: unknown; - private _orientation: ActionsOrientation; - private _triggerKeys: ActionTrigger; + private readonly _orientation: ActionsOrientation; + private readonly _triggerKeys: ActionTrigger; + private _actionIds: string[]; // View Items viewItems: IActionViewItem[]; @@ -60,16 +61,16 @@ export class ActionBar extends Disposable implements IActionRunner { protected actionsList: HTMLElement; private _onDidBlur = this._register(new Emitter()); - readonly onDidBlur: Event = this._onDidBlur.event; + readonly onDidBlur = this._onDidBlur.event; private _onDidCancel = this._register(new Emitter()); - readonly onDidCancel: Event = this._onDidCancel.event; + readonly onDidCancel = this._onDidCancel.event; private _onDidRun = this._register(new Emitter()); - readonly onDidRun: Event = this._onDidRun.event; + readonly onDidRun = this._onDidRun.event; private _onDidBeforeRun = this._register(new Emitter()); - readonly onDidBeforeRun: Event = this._onDidBeforeRun.event; + readonly onDidBeforeRun = this._onDidBeforeRun.event; constructor(container: HTMLElement, options: IActionBarOptions = {}) { super(); @@ -92,6 +93,7 @@ export class ActionBar extends Disposable implements IActionRunner { this._register(this._actionRunner.onDidRun(e => this._onDidRun.fire(e))); this._register(this._actionRunner.onDidBeforeRun(e => this._onDidBeforeRun.fire(e))); + this._actionIds = []; this.viewItems = []; this.focusedItem = undefined; @@ -245,6 +247,10 @@ export class ActionBar extends Disposable implements IActionRunner { return this.domNode; } + hasAction(action: IAction): boolean { + return this._actionIds.includes(action.id); + } + push(arg: IAction | ReadonlyArray, options: IActionOptions = {}): void { const actions: ReadonlyArray = Array.isArray(arg) ? arg : [arg]; @@ -279,9 +285,11 @@ export class ActionBar extends Disposable implements IActionRunner { if (index === null || index < 0 || index >= this.actionsList.children.length) { this.actionsList.appendChild(actionViewItemElement); this.viewItems.push(item); + this._actionIds.push(action.id); } else { this.actionsList.insertBefore(actionViewItemElement, this.actionsList.children[index]); this.viewItems.splice(index, 0, item); + this._actionIds.splice(index, 0, action.id); index++; } }); @@ -317,12 +325,14 @@ export class ActionBar extends Disposable implements IActionRunner { if (index >= 0 && index < this.viewItems.length) { this.actionsList.removeChild(this.actionsList.childNodes[index]); dispose(this.viewItems.splice(index, 1)); + this._actionIds.splice(index, 1); } } clear(): void { dispose(this.viewItems); this.viewItems = []; + this._actionIds = []; DOM.clearNode(this.actionsList); } @@ -463,6 +473,8 @@ export class ActionBar extends Disposable implements IActionRunner { dispose(this.viewItems); this.viewItems = []; + this._actionIds = []; + DOM.removeNode(this.getContainer()); super.dispose(); diff --git a/src/vs/base/test/browser/actionbar.test.ts b/src/vs/base/test/browser/actionbar.test.ts index 8915318f99a..049c4d48bb3 100644 --- a/src/vs/base/test/browser/actionbar.test.ts +++ b/src/vs/base/test/browser/actionbar.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { prepareActions } from 'vs/base/browser/ui/actionbar/actionbar'; +import { ActionBar, prepareActions } from 'vs/base/browser/ui/actionbar/actionbar'; import { Action, Separator } from 'vs/base/common/actions'; suite('Actionbar', () => { @@ -24,4 +24,37 @@ suite('Actionbar', () => { assert(actions[1] === a5); assert(actions[2] === a6); }); + + test('hasAction()', function () { + const container = document.createElement('div'); + const actionbar = new ActionBar(container); + + let a1 = new Action('a1'); + let a2 = new Action('a2'); + + actionbar.push(a1); + assert.equal(actionbar.hasAction(a1), true); + assert.equal(actionbar.hasAction(a2), false); + + actionbar.pull(0); + assert.equal(actionbar.hasAction(a1), false); + + actionbar.push(a1, { index: 1 }); + actionbar.push(a2, { index: 0 }); + assert.equal(actionbar.hasAction(a1), true); + assert.equal(actionbar.hasAction(a2), true); + + actionbar.pull(0); + assert.equal(actionbar.hasAction(a1), true); + assert.equal(actionbar.hasAction(a2), false); + + actionbar.pull(0); + assert.equal(actionbar.hasAction(a1), false); + assert.equal(actionbar.hasAction(a2), false); + + actionbar.push(a1); + assert.equal(actionbar.hasAction(a1), true); + actionbar.clear(); + assert.equal(actionbar.hasAction(a1), false); + }); }); diff --git a/src/vs/workbench/browser/parts/editor/editorActions.ts b/src/vs/workbench/browser/parts/editor/editorActions.ts index b149f3a788f..d524a4e7329 100644 --- a/src/vs/workbench/browser/parts/editor/editorActions.ts +++ b/src/vs/workbench/browser/parts/editor/editorActions.ts @@ -10,7 +10,7 @@ import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/la import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { CLOSE_EDITOR_COMMAND_ID, MOVE_ACTIVE_EDITOR_COMMAND_ID, ActiveEditorMoveArguments, SPLIT_EDITOR_LEFT, SPLIT_EDITOR_RIGHT, SPLIT_EDITOR_UP, SPLIT_EDITOR_DOWN, splitEditor, LAYOUT_EDITOR_GROUPS_COMMAND_ID, mergeAllGroups } from 'vs/workbench/browser/parts/editor/editorCommands'; +import { CLOSE_EDITOR_COMMAND_ID, MOVE_ACTIVE_EDITOR_COMMAND_ID, ActiveEditorMoveArguments, SPLIT_EDITOR_LEFT, SPLIT_EDITOR_RIGHT, SPLIT_EDITOR_UP, SPLIT_EDITOR_DOWN, splitEditor, LAYOUT_EDITOR_GROUPS_COMMAND_ID, mergeAllGroups, UNPIN_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; import { IEditorGroupsService, IEditorGroup, GroupsArrangement, GroupLocation, GroupDirection, preferredSideBySideGroupDirection, IFindGroupScope, GroupOrientation, EditorGroupLayout, GroupsOrder, OpenEditorContext } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -406,6 +406,24 @@ export class CloseEditorAction extends Action { } } +export class UnpinEditorAction extends Action { + + static readonly ID = 'workbench.action.unpinActiveEditor'; + static readonly LABEL = nls.localize('unpinEditor', "Unpin Editor"); + + constructor( + id: string, + label: string, + @ICommandService private readonly commandService: ICommandService + ) { + super(id, label, Codicon.pin.classNames); + } + + run(context?: IEditorCommandsContext): Promise { + return this.commandService.executeCommand(UNPIN_EDITOR_COMMAND_ID, undefined, context); + } +} + export class CloseOneEditorAction extends Action { static readonly ID = 'workbench.action.closeActiveEditor'; diff --git a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css b/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css index 095ab967a79..ab80861ebd5 100644 --- a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css @@ -62,9 +62,9 @@ padding-left: 10px; } -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.has-icon.close-button-right, -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.has-icon.close-button-off:not(.sticky-compact) { - padding-left: 5px; /* reduce padding when we show icons and are in shrinking mode and tab close button is not left (unless sticky-compact) */ +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.has-icon.tab-actions-right, +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.has-icon.tab-actions-off:not(.sticky-compact) { + padding-left: 5px; /* reduce padding when we show icons and are in shrinking mode and tab actions is not left (unless sticky-compact) */ } .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-fit { @@ -123,19 +123,19 @@ position: static; } -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-left .action-label { +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.tab-actions-left .action-label { margin-right: 4px !important; } -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.close-button-left::after, -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.close-button-off::after { +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.tab-actions-left::after, +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.tab-actions-off::after { content: ''; display: flex; flex: 0; width: 5px; /* Reserve space to hide tab fade when close button is left or off (fixes https://github.com/Microsoft/vscode/issues/45728) */ } -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.close-button-left { +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.tab-actions-left { min-width: 80px; /* make more room for close button when it shows to the left */ padding-right: 5px; /* we need less room when sizing is shrink */ } @@ -148,7 +148,7 @@ pointer-events: none; /* prevents cursor flickering (fixes https://github.com/Microsoft/vscode/issues/38753) */ } -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-left { +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.tab-actions-left { flex-direction: row-reverse; padding-left: 0; padding-right: 10px; @@ -236,57 +236,57 @@ height: 16px; /* tweak the icon size of the editor labels when icons are enabled */ } -/* Tab Close */ +/* Tab Actions */ -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab > .tab-close { +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab > .tab-actions { margin-top: auto; margin-bottom: auto; width: 28px; } -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-right.sizing-shrink > .tab-close { +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.tab-actions-right.sizing-shrink > .tab-actions { flex: 0; - overflow: hidden; /* let the close button be pushed out of view when sizing is set to shrink to make more room... */ + overflow: hidden; /* let the tab actions be pushed out of view when sizing is set to shrink to make more room... */ } -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty.close-button-right.sizing-shrink > .tab-close, -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-right.sizing-shrink:hover > .tab-close, -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-right.sizing-shrink > .tab-close:focus-within { - overflow: visible; /* ...but still show the close button on hover, focus and when dirty */ +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty.tab-actions-right.sizing-shrink > .tab-actions, +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.tab-actions-right.sizing-shrink:hover > .tab-actions, +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.tab-actions-right.sizing-shrink > .tab-actions:focus-within { + overflow: visible; /* ...but still show the tab actions on hover, focus and when dirty */ } -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-off:not(.dirty) > .tab-close, -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-off.sticky-compact > .tab-close { - display: none; /* hide the close action bar when we are configured to hide it (unless dirty, but always when sticky-compact) */ +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.tab-actions-off:not(.dirty) > .tab-actions, +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.tab-actions-off.sticky-compact > .tab-actions { + display: none; /* hide the tab actions when we are configured to hide it (unless dirty, but always when sticky-compact) */ } -.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab.active > .tab-close .action-label, /* always show it for active tab */ -.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab > .tab-close .action-label:focus, /* always show it on focus */ -.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab:hover > .tab-close .action-label, /* always show it on hover */ -.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab.active:hover > .tab-close .action-label, /* always show it on hover */ -.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab.dirty > .tab-close .action-label { /* always show it for dirty tabs */ +.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab.active > .tab-actions .action-label, /* always show tab actions for active tab */ +.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab > .tab-actions .action-label:focus, /* always show tab actions on focus */ +.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab:hover > .tab-actions .action-label, /* always show tab actions on hover */ +.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab.active:hover > .tab-actions .action-label, /* always show tab actions on hover */ +.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab.dirty > .tab-actions .action-label { /* always show tab actions for dirty tabs */ opacity: 1; } -.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab > .tab-close .action-label.codicon { +.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab > .tab-actions .action-label.codicon { color: inherit; font-size: 16px; } -/* change close icon to dirty state icon */ -.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab.dirty > .tab-close .action-label:not(:hover)::before, -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty > .tab-close .action-label:not(:hover)::before { +/* change tab actions icon to dirty state icon if tab dirty */ +.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab.dirty > .tab-actions .action-label:not(:hover)::before, +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty > .tab-actions .action-label:not(:hover)::before { content: "\ea71"; /* use `circle-filled` icon unicode */ } -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active > .tab-close .action-label, /* show dimmed for inactive group */ -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active:hover > .tab-close .action-label, /* show dimmed for inactive group */ -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty > .tab-close .action-label, /* show dimmed for inactive group */ -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab:hover > .tab-close .action-label { /* show dimmed for inactive group */ +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active > .tab-actions .action-label, /* show tab actions dimmed for inactive group */ +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active:hover > .tab-actions .action-label, /* show tab actions dimmed for inactive group */ +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty > .tab-actions .action-label, /* show tab actions dimmed for inactive group */ +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab:hover > .tab-actions .action-label { /* show tab actions dimmed for inactive group */ opacity: 0.5; } -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab > .tab-close .action-label { +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab > .tab-actions .action-label { opacity: 0; display: block; height: 16px; @@ -297,26 +297,26 @@ margin-right: 0.5em; } -/* No Tab Close Button */ +/* No Tab Actions */ -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-off { - padding-right: 10px; /* give a little bit more room if close button is off */ +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.tab-actions-off { + padding-right: 10px; /* give a little bit more room if tab actions is off */ } -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.close-button-off:not(.sticky-compact) { +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.tab-actions-off:not(.sticky-compact) { padding-right: 5px; /* we need less room when sizing is shrink (unless tab is sticky-compact) */ } -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-off.dirty-border-top > .tab-close { - display: none; /* hide dirty state when highlightModifiedTabs is enabled and when running without close button */ +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.tab-actions-off.dirty-border-top > .tab-actions { + display: none; /* hide dirty state when highlightModifiedTabs is enabled and when running without tab actions */ } -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-off.dirty:not(.dirty-border-top):not(.sticky-compact) { - padding-right: 0; /* remove extra padding when we are running without close button (unless tab is sticky-compact) */ +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.tab-actions-off.dirty:not(.dirty-border-top):not(.sticky-compact) { + padding-right: 0; /* remove extra padding when we are running without tab actions (unless tab is sticky-compact) */ } -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-off > .tab-close { - pointer-events: none; /* don't allow dirty state/close button to be clicked when running without close button */ +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.tab-actions-off > .tab-actions { + pointer-events: none; /* don't allow tab actions to be clicked when running without tab actions */ } /* Editor Actions */ diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index 77983df375c..5a09d06df28 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -20,7 +20,7 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IMenuService } from 'vs/platform/actions/common/actions'; import { TitleControl } from 'vs/workbench/browser/parts/editor/titleControl'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; -import { IDisposable, dispose, DisposableStore, combinedDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, DisposableStore, combinedDisposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import { getOrSet } from 'vs/base/common/map'; @@ -35,7 +35,7 @@ import { MergeGroupMode, IMergeGroupOptions, GroupsArrangement, IEditorGroupsSer import { addClass, addDisposableListener, hasClass, EventType, EventHelper, removeClass, Dimension, scheduleAtNextAnimationFrame, findParentWithClass, clearNode } from 'vs/base/browser/dom'; import { localize } from 'vs/nls'; import { IEditorGroupsAccessor, IEditorGroupView, EditorServiceImpl, EDITOR_TITLE_HEIGHT } from 'vs/workbench/browser/parts/editor/editor'; -import { CloseOneEditorAction } from 'vs/workbench/browser/parts/editor/editorActions'; +import { CloseOneEditorAction, UnpinEditorAction } from 'vs/workbench/browser/parts/editor/editorActions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { BreadcrumbsControl } from 'vs/workbench/browser/parts/editor/breadcrumbsControl'; import { IFileService } from 'vs/platform/files/common/files'; @@ -45,6 +45,7 @@ import { basenameOrAuthority } from 'vs/base/common/resources'; import { RunOnceScheduler } from 'vs/base/common/async'; import { IPathService } from 'vs/workbench/services/path/common/pathService'; import { IPath, win32, posix } from 'vs/base/common/path'; +import { insert } from 'vs/base/common/arrays'; interface IEditorInputLabel { name?: string; @@ -74,10 +75,12 @@ export class TabsTitleControl extends TitleControl { private editorToolbarContainer: HTMLElement | undefined; private tabsScrollbar: ScrollableElement | undefined; - private closeOneEditorAction: CloseOneEditorAction; + private readonly closeEditorAction = this._register(this.instantiationService.createInstance(CloseOneEditorAction, CloseOneEditorAction.ID, CloseOneEditorAction.LABEL)); + private readonly unpinEditorAction = this._register(this.instantiationService.createInstance(UnpinEditorAction, UnpinEditorAction.ID, UnpinEditorAction.LABEL)); - private tabResourceLabels: ResourceLabels; + private readonly tabResourceLabels = this._register(this.instantiationService.createInstance(ResourceLabels, DEFAULT_LABELS_CONTAINER)); private tabLabels: IEditorInputLabel[] = []; + private tabActionBars: ActionBar[] = []; private tabDisposables: IDisposable[] = []; private dimension: Dimension | undefined; @@ -108,9 +111,6 @@ export class TabsTitleControl extends TitleControl { ) { super(parent, accessor, group, contextMenuService, instantiationService, contextKeyService, keybindingService, telemetryService, notificationService, menuService, quickInputService, themeService, extensionService, configurationService, fileService); - this.tabResourceLabels = this._register(this.instantiationService.createInstance(ResourceLabels, DEFAULT_LABELS_CONTAINER)); - this.closeOneEditorAction = this._register(this.instantiationService.createInstance(CloseOneEditorAction, CloseOneEditorAction.ID, CloseOneEditorAction.LABEL)); - // Resolve the correct path library for the OS we are on // If we are connected to remote, this accounts for the // remote OS. @@ -426,6 +426,7 @@ export class TabsTitleControl extends TitleControl { this.tabDisposables = dispose(this.tabDisposables); this.tabResourceLabels.clear(); this.tabLabels = []; + this.tabActionBars = []; this.clearEditorActionsToolbar(); } @@ -439,8 +440,8 @@ export class TabsTitleControl extends TitleControl { this.tabLabels.splice(targetIndex, 0, editorLabel); // As such we need to redraw each tab - this.forEachTab((editor, index, tabContainer, tabLabelWidget, tabLabel) => { - this.redrawTab(editor, index, tabContainer, tabLabelWidget, tabLabel); + this.forEachTab((editor, index, tabContainer, tabLabelWidget, tabLabel, tabActionBar) => { + this.redrawTab(editor, index, tabContainer, tabLabelWidget, tabLabel, tabActionBar); }); // Moving an editor requires a layout to keep the active editor visible @@ -462,7 +463,7 @@ export class TabsTitleControl extends TitleControl { private doHandleStickyEditorChange(editor: IEditorInput): void { // Update tab - this.withTab(editor, (editor, index, tabContainer, tabLabelWidget, tabLabel) => this.redrawTab(editor, index, tabContainer, tabLabelWidget, tabLabel)); + this.withTab(editor, (editor, index, tabContainer, tabLabelWidget, tabLabel, tabActionBar) => this.redrawTab(editor, index, tabContainer, tabLabelWidget, tabLabel, tabActionBar)); // A change to the sticky state requires a layout to keep the active editor visible this.layout(this.dimension); @@ -535,23 +536,24 @@ export class TabsTitleControl extends TitleControl { this.redraw(); } - private forEachTab(fn: (editor: IEditorInput, index: number, tabContainer: HTMLElement, tabLabelWidget: IResourceLabel, tabLabel: IEditorInputLabel) => void): void { + private forEachTab(fn: (editor: IEditorInput, index: number, tabContainer: HTMLElement, tabLabelWidget: IResourceLabel, tabLabel: IEditorInputLabel, tabActionBar: ActionBar) => void): void { this.group.editors.forEach((editor, index) => { this.doWithTab(index, editor, fn); }); } - private withTab(editor: IEditorInput, fn: (editor: IEditorInput, index: number, tabContainer: HTMLElement, tabLabelWidget: IResourceLabel, tabLabel: IEditorInputLabel) => void): void { + private withTab(editor: IEditorInput, fn: (editor: IEditorInput, index: number, tabContainer: HTMLElement, tabLabelWidget: IResourceLabel, tabLabel: IEditorInputLabel, tabActionBar: ActionBar) => void): void { this.doWithTab(this.group.getIndexOfEditor(editor), editor, fn); } - private doWithTab(index: number, editor: IEditorInput, fn: (editor: IEditorInput, index: number, tabContainer: HTMLElement, tabLabelWidget: IResourceLabel, tabLabel: IEditorInputLabel) => void): void { + private doWithTab(index: number, editor: IEditorInput, fn: (editor: IEditorInput, index: number, tabContainer: HTMLElement, tabLabelWidget: IResourceLabel, tabLabel: IEditorInputLabel, tabActionBar: ActionBar) => void): void { const tabsContainer = assertIsDefined(this.tabsContainer); const tabContainer = tabsContainer.children[index] as HTMLElement; const tabResourceLabel = this.tabResourceLabels.get(index); const tabLabel = this.tabLabels[index]; + const tabActionBar = this.tabActionBars[index]; if (tabContainer && tabResourceLabel && tabLabel) { - fn(editor, index, tabContainer, tabResourceLabel, tabLabel); + fn(editor, index, tabContainer, tabResourceLabel, tabLabel, tabActionBar); } } @@ -575,26 +577,35 @@ export class TabsTitleControl extends TitleControl { // Tab Editor Label const editorLabel = this.tabResourceLabels.create(tabContainer); - // Tab Close Button - const tabCloseContainer = document.createElement('div'); - addClass(tabCloseContainer, 'tab-close'); - tabContainer.appendChild(tabCloseContainer); + // Tab Actions + const tabActionsContainer = document.createElement('div'); + addClass(tabActionsContainer, 'tab-actions'); + tabContainer.appendChild(tabActionsContainer); + + const tabActionRunner = new EditorCommandsContextActionRunner({ groupId: this.group.id, editorIndex: index }); + + const tabActionBar = new ActionBar(tabActionsContainer, { + ariaLabel: localize('ariaLabelTabActions', "Tab actions"), + actionRunner: tabActionRunner, + + }); + tabActionBar.onDidBeforeRun(e => { + if (e.action.id === this.closeEditorAction.id) { + this.blockRevealActiveTabOnce(); + } + }); + + const tabActionBarDisposable = combinedDisposable(tabActionBar, toDisposable(insert(this.tabActionBars, tabActionBar))); // Tab Border Bottom const tabBorderBottomContainer = document.createElement('div'); addClass(tabBorderBottomContainer, 'tab-border-bottom-container'); tabContainer.appendChild(tabBorderBottomContainer); - const tabActionRunner = new EditorCommandsContextActionRunner({ groupId: this.group.id, editorIndex: index }); - - const tabActionBar = new ActionBar(tabCloseContainer, { ariaLabel: localize('araLabelTabActions', "Tab actions"), actionRunner: tabActionRunner }); - tabActionBar.push(this.closeOneEditorAction, { icon: true, label: false, keybinding: this.getKeybindingLabel(this.closeOneEditorAction) }); - tabActionBar.onDidBeforeRun(() => this.blockRevealActiveTabOnce()); - // Eventing const eventsDisposable = this.registerTabListeners(tabContainer, index, tabsContainer, tabsScrollbar); - this.tabDisposables.push(combinedDisposable(eventsDisposable, tabActionBar, tabActionRunner, editorLabel)); + this.tabDisposables.push(combinedDisposable(eventsDisposable, tabActionBarDisposable, tabActionRunner, editorLabel)); return tabContainer; } @@ -657,7 +668,7 @@ export class TabsTitleControl extends TitleControl { EventHelper.stop(e, true /* for https://github.com/Microsoft/vscode/issues/56715 */); this.blockRevealActiveTabOnce(); - this.closeOneEditorAction.run({ groupId: this.group.id, editorIndex: index }); + this.closeEditorAction.run({ groupId: this.group.id, editorIndex: index }); } })); @@ -1002,8 +1013,8 @@ export class TabsTitleControl extends TitleControl { } // For each tab - this.forEachTab((editor, index, tabContainer, tabLabelWidget, tabLabel) => { - this.redrawTab(editor, index, tabContainer, tabLabelWidget, tabLabel); + this.forEachTab((editor, index, tabContainer, tabLabelWidget, tabLabel, tabActionBar) => { + this.redrawTab(editor, index, tabContainer, tabLabelWidget, tabLabel, tabActionBar); }); // Update Editor Actions Toolbar @@ -1013,24 +1024,32 @@ export class TabsTitleControl extends TitleControl { this.layout(this.dimension); } - private redrawTab(editor: IEditorInput, index: number, tabContainer: HTMLElement, tabLabelWidget: IResourceLabel, tabLabel: IEditorInputLabel): void { + private redrawTab(editor: IEditorInput, index: number, tabContainer: HTMLElement, tabLabelWidget: IResourceLabel, tabLabel: IEditorInputLabel, tabActionBar: ActionBar): void { + const isTabSticky = this.group.isSticky(index); + const options = this.accessor.partOptions; // Label this.redrawLabel(editor, index, tabContainer, tabLabelWidget, tabLabel); + // Action + const tabAction = isTabSticky ? this.unpinEditorAction : this.closeEditorAction; + if (!tabActionBar.hasAction(tabAction)) { + if (!tabActionBar.isEmpty()) { + tabActionBar.clear(); + } + tabActionBar.push(tabAction, { icon: true, label: false, keybinding: this.getKeybindingLabel(tabAction) }); + } + // Borders / Outline const borderRightColor = (this.getColor(TAB_BORDER) || this.getColor(contrastBorder)); tabContainer.style.borderRight = borderRightColor ? `1px solid ${borderRightColor}` : ''; tabContainer.style.outlineColor = this.getColor(activeContrastBorder) || ''; // Settings - const isTabSticky = this.group.isSticky(index); - const options = this.accessor.partOptions; - - const tabCloseButton = isTabSticky && options.pinnedTabSizing === 'compact' ? 'off' /* treat sticky compact tabs as tabCloseButton: 'off' */ : options.tabCloseButton; + const tabActionsVisibility = isTabSticky && options.pinnedTabSizing === 'compact' ? 'off' /* treat sticky compact tabs as tabCloseButton: 'off' */ : options.tabCloseButton; ['off', 'left', 'right'].forEach(option => { - const domAction = tabCloseButton === option ? addClass : removeClass; - domAction(tabContainer, `close-button-${option}`); + const domAction = tabActionsVisibility === option ? addClass : removeClass; + domAction(tabContainer, `tab-actions-${option}`); }); const tabSizing = isTabSticky && options.pinnedTabSizing === 'shrink' ? 'shrink' /* treat sticky shrink tabs as tabSizing: 'shrink' */ : options.tabSizing; @@ -1523,10 +1542,10 @@ registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) = outline-offset: -5px; } - .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active > .tab-close .action-label, - .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active:hover > .tab-close .action-label, - .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty > .tab-close .action-label, - .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab:hover > .tab-close .action-label { + .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active > .tab-actions .action-label, + .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active:hover > .tab-actions .action-label, + .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty > .tab-actions .action-label, + .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab:hover > .tab-actions .action-label { opacity: 1 !important; } `); diff --git a/src/vs/workbench/services/accessibility/electron-sandbox/accessibilityService.ts b/src/vs/workbench/services/accessibility/electron-sandbox/accessibilityService.ts index e6f6419e512..1cdeadaf906 100644 --- a/src/vs/workbench/services/accessibility/electron-sandbox/accessibilityService.ts +++ b/src/vs/workbench/services/accessibility/electron-sandbox/accessibilityService.ts @@ -73,7 +73,7 @@ registerSingleton(IAccessibilityService, NativeAccessibilityService, true); class LinuxAccessibilityContribution implements IWorkbenchContribution { constructor( @IJSONEditingService jsonEditingService: IJSONEditingService, - @IAccessibilityService accessibilityService: AccessibilityService, + @IAccessibilityService accessibilityService: IAccessibilityService, @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService ) { const forceRendererAccessibility = () => { -- GitLab