From a64e0f619d51c990a713898466ed4875c326ef64 Mon Sep 17 00:00:00 2001 From: "sneakyfish5.sneaky@gmail.com" Date: Tue, 8 Sep 2020 21:42:26 -0500 Subject: [PATCH 01/38] Allow tabs to wrap to multi-line --- .../browser/parts/editor/editorDropTarget.ts | 5 +++++ .../parts/editor/media/tabstitlecontrol.css | 13 +++++++++++++ .../browser/parts/editor/tabsTitleControl.ts | 15 +++++++++++++++ .../workbench/browser/workbench.contribution.ts | 5 +++++ 4 files changed, 38 insertions(+) diff --git a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts index d7ae2ba76a1ff..f6cf124552504 100644 --- a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts +++ b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts @@ -33,6 +33,7 @@ interface IDropOperation { class DropOverlay extends Themable { private static readonly OVERLAY_ID = 'monaco-workbench-editor-drop-overlay'; + private static readonly TABS_SELECTOR = 'div.tabs-container[role="tablist"]'; private static readonly MAX_FILE_UPLOAD_SIZE = 100 * 1024 * 1024; // 100mb @@ -501,6 +502,10 @@ class DropOverlay extends Themable { private getOverlayOffsetHeight(): number { if (!this.groupView.isEmpty && this.accessor.partOptions.showTabs) { + const tablist = document.querySelector(DropOverlay.TABS_SELECTOR) as HTMLElement; + if (tablist) { + return tablist.offsetHeight; + } return EDITOR_TITLE_HEIGHT; // show overlay below title if group shows tabs } diff --git a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css b/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css index dbf66755d80c5..7ea9a1f05d8e1 100644 --- a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css @@ -25,6 +25,19 @@ height: 1px; } +.monaco-workbench .part.editor > .content .editor-group-container > .title.tabs> .tabs-and-actions-container.multi-line-tabs-container > .monaco-scrollable-element > .tabs-container { + height: auto !important; + flex-wrap: wrap; +} + +.monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container.multi-line-tabs-container > .monaco-scrollable-element { + height: auto !important; +} + +.monaco-workbench .part.editor > .content .editor-group-container > .title.tabs> .tabs-and-actions-container.multi-line-tabs-container > .monaco-scrollable-element > .tabs-container > .tab { + border-bottom: 1px solid rgba(128,128,128,0.2); +} + .monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container > .monaco-scrollable-element { flex: 1; } diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index f3eb689c73ad1..67a68da91d1bf 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -124,6 +124,18 @@ export class TabsTitleControl extends TitleControl { this.updateTabsScrollbarSizing(); } })); + + this._register(this.configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('workbench.editor.multiLineTabs')) { + const tabsAndActionsContainer = this.tabsAndActionsContainer as HTMLElement; + + if (this.configurationService.getValue('workbench.editor.multiLineTabs')) { + addClass(tabsAndActionsContainer, 'multi-line-tabs-container'); + } else { + removeClass(tabsAndActionsContainer, 'multi-line-tabs-container'); + } + } + })); } protected create(parent: HTMLElement): void { @@ -133,6 +145,9 @@ export class TabsTitleControl extends TitleControl { this.tabsAndActionsContainer = document.createElement('div'); addClass(this.tabsAndActionsContainer, 'tabs-and-actions-container'); this.titleContainer.appendChild(this.tabsAndActionsContainer); + if (this.configurationService.getValue('workbench.editor.multiLineTabs')) { + addClass(this.tabsAndActionsContainer, 'multi-line-tabs-container'); + } // Tabs Container this.tabsContainer = document.createElement('div'); diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index 7656a699f3cc8..86042a7346edc 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -32,6 +32,11 @@ import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuratio 'description': nls.localize('showEditorTabs', "Controls whether opened editors should show in tabs or not."), 'default': true }, + 'workbench.editor.multiLineTabs': { + 'type': 'boolean', + 'description': nls.localize('multiLineTabs', "Controls whether tabs should be wrapped to multi-line or not."), + 'default': false + }, 'workbench.editor.scrollToSwitchTabs': { 'type': 'boolean', 'description': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'scrollToSwitchTabs' }, "Controls whether scrolling over tabs will open them or not. By default tabs will only reveal upon scrolling, but not open. You can press and hold the Shift-key while scrolling to change this behaviour for that duration."), From 81390249c4936c27384dd1b0677902e2c4dc6d70 Mon Sep 17 00:00:00 2001 From: "sneakyfish5.sneaky@gmail.com" Date: Fri, 11 Sep 2020 13:05:28 -0500 Subject: [PATCH 02/38] Address feedback --- .../parts/editor/media/tabstitlecontrol.css | 2 +- .../browser/parts/editor/tabsTitleControl.ts | 35 +++++++++++-------- src/vs/workbench/common/editor.ts | 1 + 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css b/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css index cb13429044239..c45ab0dbabd57 100644 --- a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css @@ -35,7 +35,7 @@ } .monaco-workbench .part.editor > .content .editor-group-container > .title.tabs> .tabs-and-actions-container.multi-line-tabs-container > .monaco-scrollable-element > .tabs-container > .tab { - border-bottom: 1px solid rgba(128,128,128,0.2); + flex-grow: 1; } .monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container > .monaco-scrollable-element { diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index 7d7d89d9f71e4..b918e215ff719 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -124,18 +124,8 @@ export class TabsTitleControl extends TitleControl { this._register(this.accessor.onDidEditorPartOptionsChange(e => { if (e.oldPartOptions.titleScrollbarSizing !== e.newPartOptions.titleScrollbarSizing) { this.updateTabsScrollbarSizing(); - } - })); - - this._register(this.configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration('workbench.editor.multiLineTabs')) { - const tabsAndActionsContainer = this.tabsAndActionsContainer as HTMLElement; - - if (this.configurationService.getValue('workbench.editor.multiLineTabs')) { - addClass(tabsAndActionsContainer, 'multi-line-tabs-container'); - } else { - removeClass(tabsAndActionsContainer, 'multi-line-tabs-container'); - } + } else if (e.oldPartOptions.multiLineTabs !== e.newPartOptions.multiLineTabs) { + this.updateWrappedTabs(); } })); } @@ -147,8 +137,8 @@ export class TabsTitleControl extends TitleControl { this.tabsAndActionsContainer = document.createElement('div'); addClass(this.tabsAndActionsContainer, 'tabs-and-actions-container'); this.titleContainer.appendChild(this.tabsAndActionsContainer); - if (this.configurationService.getValue('workbench.editor.multiLineTabs')) { - addClass(this.tabsAndActionsContainer, 'multi-line-tabs-container'); + if (this.accessor.partOptions.multiLineTabs) { + this.tabsAndActionsContainer.classList.add('multi-line-tabs-container'); } // Tabs Container @@ -202,6 +192,16 @@ export class TabsTitleControl extends TitleControl { }); } + private updateWrappedTabs(): void { + const tabsAndActionsContainer = this.tabsAndActionsContainer as HTMLElement; + + if (this.accessor.partOptions.multiLineTabs) { + tabsAndActionsContainer.classList.add('multi-line-tabs-container'); + } else { + tabsAndActionsContainer.classList.remove('multi-line-tabs-container'); + } + } + private getTabsScrollbarSizing(): number { if (this.accessor.partOptions.titleScrollbarSizing !== 'large') { return TabsTitleControl.SCROLLBAR_SIZES.default; @@ -542,7 +542,8 @@ export class TabsTitleControl extends TitleControl { oldOptions.pinnedTabSizing !== newOptions.pinnedTabSizing || oldOptions.showIcons !== newOptions.showIcons || oldOptions.hasIcons !== newOptions.hasIcons || - oldOptions.highlightModifiedTabs !== newOptions.highlightModifiedTabs + oldOptions.highlightModifiedTabs !== newOptions.highlightModifiedTabs || + oldOptions.multiLineTabs !== newOptions.multiLineTabs ) { this.redraw(); } @@ -1057,6 +1058,10 @@ export class TabsTitleControl extends TitleControl { const borderRightColor = ((isTabLastSticky ? this.getColor(TAB_LAST_PINNED_BORDER) : undefined) || this.getColor(TAB_BORDER) || this.getColor(contrastBorder)); tabContainer.style.borderRight = borderRightColor ? `1px solid ${borderRightColor}` : ''; tabContainer.style.outlineColor = this.getColor(activeContrastBorder) || ''; + // Add a border to the bottom of the tabs if multi-line tabs is enabled + if (this.accessor.partOptions.multiLineTabs) { + tabContainer.style.borderBottom = `1px solid ${borderRightColor}`; + } // Settings const tabActionsVisibility = isTabSticky && options.pinnedTabSizing === 'compact' ? 'off' /* treat sticky compact tabs as tabCloseButton: 'off' */ : options.tabCloseButton; diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 4cf6492ee2e07..882ceeff329f5 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -1243,6 +1243,7 @@ interface IEditorPartConfiguration { value?: number; perEditorGroup?: boolean; }; + multiLineTabs?: boolean; } export interface IEditorPartOptions extends IEditorPartConfiguration { From 34dd6cf785bf8deb0da9a26ba152c209b8ae0020 Mon Sep 17 00:00:00 2001 From: "sneakyfish5.sneaky@gmail.com" Date: Tue, 15 Sep 2020 06:39:01 -0500 Subject: [PATCH 03/38] Add hidden space after last tab --- .../browser/parts/editor/media/tabstitlecontrol.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css b/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css index c45ab0dbabd57..9f70053c4205a 100644 --- a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css @@ -30,6 +30,11 @@ flex-wrap: wrap; } +.monaco-workbench .part.editor>.content .editor-group-container>.title.tabs>.tabs-and-actions-container.multi-line-tabs-container>.monaco-scrollable-element>.tabs-container::after { + content: ""; /* Add a hidden space after the last tab in multi-line tabs */ + flex: 1 0 auto; +} + .monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container.multi-line-tabs-container > .monaco-scrollable-element { height: auto !important; } From 417e97b3132bc8eb402bb1aee1ed421bc1de4a1e Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 16 Sep 2020 08:34:06 +0200 Subject: [PATCH 04/38] some polish for multi-line wrap css class --- .../parts/editor/media/tabstitlecontrol.css | 28 ++++++++-------- .../browser/parts/editor/tabsTitleControl.ts | 32 ++++++++----------- 2 files changed, 29 insertions(+), 31 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css b/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css index 9f70053c4205a..32f9bc10ad9d9 100644 --- a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css @@ -25,33 +25,35 @@ height: 1px; } -.monaco-workbench .part.editor > .content .editor-group-container > .title.tabs> .tabs-and-actions-container.multi-line-tabs-container > .monaco-scrollable-element > .tabs-container { +.monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container > .monaco-scrollable-element { + flex: 1; +} + +.monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container > .monaco-scrollable-element .scrollbar { + z-index: 3; /* on top of tabs */ + cursor: default; +} + +/* Title Container (multi-line wrapping) */ + +.monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container.multi-line > .monaco-scrollable-element > .tabs-container { height: auto !important; flex-wrap: wrap; } -.monaco-workbench .part.editor>.content .editor-group-container>.title.tabs>.tabs-and-actions-container.multi-line-tabs-container>.monaco-scrollable-element>.tabs-container::after { +.monaco-workbench .part.editor>.content .editor-group-container>.title.tabs >.tabs-and-actions-container.multi-line>.monaco-scrollable-element>.tabs-container::after { content: ""; /* Add a hidden space after the last tab in multi-line tabs */ flex: 1 0 auto; } -.monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container.multi-line-tabs-container > .monaco-scrollable-element { +.monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container.multi-line > .monaco-scrollable-element { height: auto !important; } -.monaco-workbench .part.editor > .content .editor-group-container > .title.tabs> .tabs-and-actions-container.multi-line-tabs-container > .monaco-scrollable-element > .tabs-container > .tab { +.monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container.multi-line > .monaco-scrollable-element > .tabs-container > .tab { flex-grow: 1; } -.monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container > .monaco-scrollable-element { - flex: 1; -} - -.monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container > .monaco-scrollable-element .scrollbar { - z-index: 3; /* on top of tabs */ - cursor: default; -} - /* Tabs Container */ .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container { diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index dc1a81ab82a00..9ad89dd19eaf7 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -32,7 +32,7 @@ import { Color } from 'vs/base/common/color'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { MergeGroupMode, IMergeGroupOptions, GroupsArrangement, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { addClass, addDisposableListener, hasClass, EventType, EventHelper, removeClass, Dimension, scheduleAtNextAnimationFrame, findParentWithClass, clearNode } from 'vs/base/browser/dom'; +import { addClass, addDisposableListener, hasClass, EventType, EventHelper, removeClass, Dimension, scheduleAtNextAnimationFrame, findParentWithClass, clearNode, toggleClass } 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, UnpinEditorAction } from 'vs/workbench/browser/parts/editor/editorActions'; @@ -121,13 +121,13 @@ export class TabsTitleControl extends TitleControl { protected create(parent: HTMLElement): void { this.titleContainer = parent; - // Tabs and Actions Container (are on a single row with flex side-by-side) + // Tabs and Actions Container: depending on the multi-line setting: + // off: single row with flex side-by-side + // on: multiple rows with flex side-by-side this.tabsAndActionsContainer = document.createElement('div'); addClass(this.tabsAndActionsContainer, 'tabs-and-actions-container'); + toggleClass(this.tabsAndActionsContainer, 'multi-line', this.accessor.partOptions.multiLineTabs); this.titleContainer.appendChild(this.tabsAndActionsContainer); - if (this.accessor.partOptions.multiLineTabs) { - this.tabsAndActionsContainer.classList.add('multi-line-tabs-container'); - } // Tabs Container this.tabsContainer = document.createElement('div'); @@ -514,7 +514,12 @@ export class TabsTitleControl extends TitleControl { // Update tabs multiline wrapping if (oldOptions.multiLineTabs !== newOptions.multiLineTabs) { - this.updateWrappedTabs(); + this.updateMultiLineTabs(); + } + + // Udate tabs scrollbar sizing + if (oldOptions.titleScrollbarSizing !== newOptions.titleScrollbarSizing) { + this.updateTabsScrollbarSizing(); } // Redraw tabs when other options change @@ -530,21 +535,12 @@ export class TabsTitleControl extends TitleControl { ) { this.redraw(); } - - // Udate tabs scrollbar sizing - if (oldOptions.titleScrollbarSizing !== newOptions.titleScrollbarSizing) { - this.updateTabsScrollbarSizing(); - } } - private updateWrappedTabs(): void { - const tabsAndActionsContainer = this.tabsAndActionsContainer as HTMLElement; + private updateMultiLineTabs(): void { + const tabsAndActionsContainer = assertIsDefined(this.tabsAndActionsContainer); - if (this.accessor.partOptions.multiLineTabs) { - tabsAndActionsContainer.classList.add('multi-line-tabs-container'); - } else { - tabsAndActionsContainer.classList.remove('multi-line-tabs-container'); - } + toggleClass(tabsAndActionsContainer, 'multi-line', this.accessor.partOptions.multiLineTabs); } updateStyles(): void { From c290d7ce127d2e9a14fa1509da47c73d272f1ccb Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 16 Sep 2020 08:53:13 +0200 Subject: [PATCH 05/38] some more polish --- .../browser/parts/editor/media/tabstitlecontrol.css | 2 +- src/vs/workbench/browser/parts/editor/tabsTitleControl.ts | 5 +---- src/vs/workbench/common/editor.ts | 2 +- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css b/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css index 32f9bc10ad9d9..1177c29d2e143 100644 --- a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css @@ -41,7 +41,7 @@ flex-wrap: wrap; } -.monaco-workbench .part.editor>.content .editor-group-container>.title.tabs >.tabs-and-actions-container.multi-line>.monaco-scrollable-element>.tabs-container::after { +.monaco-workbench .part.editor>.content .editor-group-container>.title.tabs >.tabs-and-actions-container.multi-line > .monaco-scrollable-element > .tabs-container::after { content: ""; /* Add a hidden space after the last tab in multi-line tabs */ flex: 1 0 auto; } diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index 9ad89dd19eaf7..df5baedeb12f3 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -1051,11 +1051,8 @@ export class TabsTitleControl extends TitleControl { // Borders / Outline const borderRightColor = ((isTabLastSticky ? this.getColor(TAB_LAST_PINNED_BORDER) : undefined) || this.getColor(TAB_BORDER) || this.getColor(contrastBorder)); tabContainer.style.borderRight = borderRightColor ? `1px solid ${borderRightColor}` : ''; + tabContainer.style.borderBottom = borderRightColor && this.accessor.partOptions.multiLineTabs ? `1px solid ${borderRightColor}` : ''; // bottom border when wrapping multi-line tabContainer.style.outlineColor = this.getColor(activeContrastBorder) || ''; - // Add a border to the bottom of the tabs if multi-line tabs is enabled - if (this.accessor.partOptions.multiLineTabs) { - tabContainer.style.borderBottom = `1px solid ${borderRightColor}`; - } // Settings const tabActionsVisibility = isTabSticky && options.pinnedTabSizing === 'compact' ? 'off' /* treat sticky compact tabs as tabCloseButton: 'off' */ : options.tabCloseButton; diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 882ceeff329f5..f6a310d4cc4f4 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -1219,6 +1219,7 @@ export interface IWorkbenchEditorConfiguration { interface IEditorPartConfiguration { showTabs?: boolean; + multiLineTabs?: boolean; scrollToSwitchTabs?: boolean; highlightModifiedTabs?: boolean; tabCloseButton?: 'left' | 'right' | 'off'; @@ -1243,7 +1244,6 @@ interface IEditorPartConfiguration { value?: number; perEditorGroup?: boolean; }; - multiLineTabs?: boolean; } export interface IEditorPartOptions extends IEditorPartConfiguration { From 377a74af56b55f3c1712a4b27a57167b86898852 Mon Sep 17 00:00:00 2001 From: "sneakyfish5.sneaky@gmail.com" Date: Wed, 16 Sep 2020 10:48:21 -0500 Subject: [PATCH 06/38] Address feedback --- .../workbench/browser/parts/editor/editor.ts | 1 + .../browser/parts/editor/editorDropTarget.ts | 6 ++-- .../browser/parts/editor/editorGroupView.ts | 4 +++ .../parts/editor/media/tabstitlecontrol.css | 4 ++- .../browser/parts/editor/tabsTitleControl.ts | 36 +++++++++++-------- .../test/browser/workbenchTestServices.ts | 1 + 6 files changed, 32 insertions(+), 20 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editor.ts b/src/vs/workbench/browser/parts/editor/editor.ts index 6452a712aefc2..97f512e865c9a 100644 --- a/src/vs/workbench/browser/parts/editor/editor.ts +++ b/src/vs/workbench/browser/parts/editor/editor.ts @@ -117,6 +117,7 @@ export interface IEditorGroupView extends IDisposable, ISerializableView, IEdito readonly isEmpty: boolean; readonly isMinimized: boolean; + readonly titleHeight: number; readonly onDidFocus: Event; readonly onWillDispose: Event; diff --git a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts index e409d2231445e..cea6a66d52421 100644 --- a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts +++ b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts @@ -33,7 +33,6 @@ interface IDropOperation { class DropOverlay extends Themable { private static readonly OVERLAY_ID = 'monaco-workbench-editor-drop-overlay'; - private static readonly TABS_SELECTOR = 'div.tabs-container[role="tablist"]'; private static readonly MAX_FILE_UPLOAD_SIZE = 100 * 1024 * 1024; // 100mb @@ -502,9 +501,8 @@ class DropOverlay extends Themable { private getOverlayOffsetHeight(): number { if (!this.groupView.isEmpty && this.accessor.partOptions.showTabs) { - const tablist = document.querySelector(DropOverlay.TABS_SELECTOR) as HTMLElement; - if (tablist) { - return tablist.offsetHeight; + if (this.accessor.partOptions.multiLineTabs) { + return this.groupView.titleHeight; } return EDITOR_TITLE_HEIGHT; // show overlay below title if group shows tabs } diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index 27403a6749a41..6e4065b80a182 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -712,6 +712,10 @@ export class EditorGroupView extends Themable implements IEditorGroupView { return this._group.count === 0; } + get titleHeight(): number { + return this.titleContainer.offsetHeight; + } + get isMinimized(): boolean { if (!this.dimension) { return false; diff --git a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css b/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css index 1177c29d2e143..cc8bf23f8a135 100644 --- a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css @@ -41,7 +41,7 @@ flex-wrap: wrap; } -.monaco-workbench .part.editor>.content .editor-group-container>.title.tabs >.tabs-and-actions-container.multi-line > .monaco-scrollable-element > .tabs-container::after { +.monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container.multi-line > .monaco-scrollable-element > .tabs-container::after { content: ""; /* Add a hidden space after the last tab in multi-line tabs */ flex: 1 0 auto; } @@ -183,6 +183,7 @@ .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active.tab-border-top > .tab-border-top-container, .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active.tab-border-bottom > .tab-border-bottom-container, +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.multi-line.tab-border-bottom > .tab-border-bottom-container, .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty-border-top > .tab-border-top-container { display: block; position: absolute; @@ -198,6 +199,7 @@ background-color: var(--tab-border-top-color); } +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.multi-line.tab-border-bottom > .tab-border-bottom-container, .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active.tab-border-bottom > .tab-border-bottom-container { bottom: 0; height: 1px; diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index 49a00c8dd20f1..283fe135f5ba8 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -126,7 +126,6 @@ export class TabsTitleControl extends TitleControl { // on: multiple rows with flex side-by-side this.tabsAndActionsContainer = document.createElement('div'); this.tabsAndActionsContainer.classList.add('tabs-and-actions-container'); - this.tabsAndActionsContainer.classList.toggle('multi-line', this.accessor.partOptions.multiLineTabs); this.titleContainer.appendChild(this.tabsAndActionsContainer); // Tabs Container @@ -512,12 +511,7 @@ export class TabsTitleControl extends TitleControl { this.computeTabLabels(); } - // Update tabs multiline wrapping - if (oldOptions.multiLineTabs !== newOptions.multiLineTabs) { - this.updateMultiLineTabs(); - } - - // Udate tabs scrollbar sizing + // Update tabs scrollbar sizing if (oldOptions.titleScrollbarSizing !== newOptions.titleScrollbarSizing) { this.updateTabsScrollbarSizing(); } @@ -537,12 +531,6 @@ export class TabsTitleControl extends TitleControl { } } - private updateMultiLineTabs(): void { - const tabsAndActionsContainer = assertIsDefined(this.tabsAndActionsContainer); - - this.tabsAndActionsContainer.classList.toggle('multi-line', this.accessor.partOptions.multiLineTabs); - } - updateStyles(): void { this.redraw(); } @@ -1051,7 +1039,6 @@ export class TabsTitleControl extends TitleControl { // Borders / Outline const borderRightColor = ((isTabLastSticky ? this.getColor(TAB_LAST_PINNED_BORDER) : undefined) || this.getColor(TAB_BORDER) || this.getColor(contrastBorder)); tabContainer.style.borderRight = borderRightColor ? `1px solid ${borderRightColor}` : ''; - tabContainer.style.borderBottom = borderRightColor && this.accessor.partOptions.multiLineTabs ? `1px solid ${borderRightColor}` : ''; // bottom border when wrapping multi-line tabContainer.style.outlineColor = this.getColor(activeContrastBorder) || ''; // Settings @@ -1190,6 +1177,17 @@ export class TabsTitleControl extends TitleControl { tabContainer.style.backgroundColor = this.getColor(isGroupActive ? TAB_INACTIVE_BACKGROUND : TAB_UNFOCUSED_INACTIVE_BACKGROUND) || ''; tabContainer.style.boxShadow = ''; + // bottom border when wrapping to multi-line tabs + if (this.accessor.partOptions.multiLineTabs) { + tabContainer.classList.add('multi-line'); + tabContainer.classList.add('tab-border-bottom'); + const borderBottom = (this.getColor(TAB_BORDER) || this.getColor(contrastBorder)) || ''; + tabContainer.style.setProperty('--tab-border-bottom-color', borderBottom.toString()); + } else { + tabContainer.classList.remove('multi-line'); + tabContainer.classList.remove('tab-border-bottom'); + } + // Label tabContainer.style.color = this.getColor(isGroupActive ? TAB_INACTIVE_FOREGROUND : TAB_UNFOCUSED_INACTIVE_FOREGROUND) || ''; } @@ -1285,7 +1283,7 @@ export class TabsTitleControl extends TitleControl { } private doLayoutTabs(activeTab: HTMLElement, activeIndex: number): void { - const [tabsContainer, tabsScrollbar] = assertAllDefined(this.tabsContainer, this.tabsScrollbar); + const [tabsContainer, tabsScrollbar, tabsAndActionsContainer] = assertAllDefined(this.tabsContainer, this.tabsScrollbar, this.tabsAndActionsContainer); // // Synopsis @@ -1305,6 +1303,7 @@ export class TabsTitleControl extends TitleControl { const visibleTabsContainerWidth = tabsContainer.offsetWidth; const allTabsWidth = tabsContainer.scrollWidth; + const allTabsHeight = tabsContainer.offsetHeight; // Compute width of sticky tabs depending on pinned tab sizing // - compact: sticky-tabs * TAB_SIZES.compact @@ -1340,6 +1339,13 @@ export class TabsTitleControl extends TitleControl { tabsContainer.classList.remove('disable-sticky-tabs'); } + // Add the multi-line tabs class after first row is filled out and wraps + if (allTabsWidth > visibleTabsContainerWidth && this.accessor.partOptions.multiLineTabs) { + tabsAndActionsContainer.classList.add('multi-line'); + } else if ((allTabsWidth === visibleTabsContainerWidth && allTabsHeight === EDITOR_TITLE_HEIGHT) || !this.accessor.partOptions.multiLineTabs) { + tabsAndActionsContainer.classList.remove('multi-line'); + } + let activeTabPosX: number | undefined; let activeTabWidth: number | undefined; diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 123dae166b96a..18d0d24174a64 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -598,6 +598,7 @@ export class TestEditorGroupView implements IEditorGroupView { maximumWidth!: number; minimumHeight!: number; maximumHeight!: number; + titleHeight!: number; isEmpty = true; isMinimized = false; From e6f128801cb23cbc3e15f87bcab8dfaf4d5abe62 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 18 Sep 2020 08:38:45 +0200 Subject: [PATCH 07/38] some adjustments to move forward --- .../workbench/browser/parts/editor/editor.ts | 17 ++++++----- .../browser/parts/editor/editorDropTarget.ts | 10 +++---- .../browser/parts/editor/editorGroupView.ts | 12 ++++++-- .../parts/editor/media/tabstitlecontrol.css | 2 -- .../browser/parts/editor/tabsTitleControl.ts | 29 +++++++++---------- 5 files changed, 38 insertions(+), 32 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editor.ts b/src/vs/workbench/browser/parts/editor/editor.ts index 97f512e865c9a..c75dfce76d85b 100644 --- a/src/vs/workbench/browser/parts/editor/editor.ts +++ b/src/vs/workbench/browser/parts/editor/editor.ts @@ -111,13 +111,6 @@ export interface IEditorGroupsAccessor { } export interface IEditorGroupView extends IDisposable, ISerializableView, IEditorGroup { - readonly group: EditorGroup; - readonly whenRestored: Promise; - readonly disposed: boolean; - - readonly isEmpty: boolean; - readonly isMinimized: boolean; - readonly titleHeight: number; readonly onDidFocus: Event; readonly onWillDispose: Event; @@ -126,6 +119,16 @@ export interface IEditorGroupView extends IDisposable, ISerializableView, IEdito readonly onWillCloseEditor: Event; readonly onDidCloseEditor: Event; + readonly group: EditorGroup; + readonly whenRestored: Promise; + + readonly titleHeight: number; + + readonly isEmpty: boolean; + readonly isMinimized: boolean; + + readonly disposed: boolean; + setActive(isActive: boolean): void; notifyIndexChanged(newIndex: number): void; diff --git a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts index 709e6753ae82f..b17d0e4650684 100644 --- a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts +++ b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts @@ -6,7 +6,7 @@ import 'vs/css!./media/editordroptarget'; import { LocalSelectionTransfer, DraggedEditorIdentifier, ResourcesDropHandler, DraggedEditorGroupIdentifier, DragAndDropObserver, containsDragType } from 'vs/workbench/browser/dnd'; import { addDisposableListener, EventType, EventHelper, isAncestor } from 'vs/base/browser/dom'; -import { IEditorGroupsAccessor, EDITOR_TITLE_HEIGHT, IEditorGroupView, getActiveTextEditorOptions } from 'vs/workbench/browser/parts/editor/editor'; +import { IEditorGroupsAccessor, IEditorGroupView, getActiveTextEditorOptions } from 'vs/workbench/browser/parts/editor/editor'; import { EDITOR_DRAG_AND_DROP_BACKGROUND } from 'vs/workbench/common/theme'; import { IThemeService, Themable } from 'vs/platform/theme/common/themeService'; import { activeContrastBorder } from 'vs/platform/theme/common/colorRegistry'; @@ -500,13 +500,13 @@ class DropOverlay extends Themable { } private getOverlayOffsetHeight(): number { + + // With tabs and opened editors: use the area below tabs as drop target if (!this.groupView.isEmpty && this.accessor.partOptions.showTabs) { - if (this.accessor.partOptions.multiLineTabs) { - return this.groupView.titleHeight; - } - return EDITOR_TITLE_HEIGHT; // show overlay below title if group shows tabs + return this.groupView.titleHeight; } + // Without tabs or empty group: use entire editor area as drop target return 0; } diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index a4949f5cbd449..818040e252068 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -30,7 +30,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { RunOnceWorker } from 'vs/base/common/async'; import { EventType as TouchEventType, GestureEvent } from 'vs/base/browser/touch'; import { TitleControl } from 'vs/workbench/browser/parts/editor/titleControl'; -import { IEditorGroupsAccessor, IEditorGroupView, getActiveTextEditorOptions, IEditorOpeningEvent, EditorServiceImpl } from 'vs/workbench/browser/parts/editor/editor'; +import { IEditorGroupsAccessor, IEditorGroupView, getActiveTextEditorOptions, IEditorOpeningEvent, EditorServiceImpl, EDITOR_TITLE_HEIGHT } from 'vs/workbench/browser/parts/editor/editor'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ActionRunner, IAction, Action } from 'vs/base/common/actions'; @@ -718,7 +718,15 @@ export class EditorGroupView extends Themable implements IEditorGroupView { } get titleHeight(): number { - return this.titleContainer.offsetHeight; + + // Multi-line: we need to ask `offsetHeight` to get + // the real height of the title area with wrapping. + if (this.accessor.partOptions.multiLineTabs) { + return this.titleContainer.offsetHeight; + } + + // Otherwise return default title height + return EDITOR_TITLE_HEIGHT; } get isMinimized(): boolean { diff --git a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css b/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css index 1aa6bf2e06aee..fad530406859a 100644 --- a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css @@ -183,7 +183,6 @@ .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active.tab-border-top > .tab-border-top-container, .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active.tab-border-bottom > .tab-border-bottom-container, -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.multi-line.tab-border-bottom > .tab-border-bottom-container, .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty-border-top > .tab-border-top-container { display: block; position: absolute; @@ -199,7 +198,6 @@ background-color: var(--tab-border-top-color); } -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.multi-line.tab-border-bottom > .tab-border-bottom-container, .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active.tab-border-bottom > .tab-border-bottom-container { bottom: 0; height: 1px; diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index 732026631b5ec..de8802f6bd713 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -1177,17 +1177,6 @@ export class TabsTitleControl extends TitleControl { tabContainer.style.backgroundColor = this.getColor(isGroupActive ? TAB_INACTIVE_BACKGROUND : TAB_UNFOCUSED_INACTIVE_BACKGROUND) || ''; tabContainer.style.boxShadow = ''; - // bottom border when wrapping to multi-line tabs - if (this.accessor.partOptions.multiLineTabs) { - tabContainer.classList.add('multi-line'); - tabContainer.classList.add('tab-border-bottom'); - const borderBottom = (this.getColor(TAB_BORDER) || this.getColor(contrastBorder)) || ''; - tabContainer.style.setProperty('--tab-border-bottom-color', borderBottom.toString()); - } else { - tabContainer.classList.remove('multi-line'); - tabContainer.classList.remove('tab-border-bottom'); - } - // Label tabContainer.style.color = this.getColor(isGroupActive ? TAB_INACTIVE_FOREGROUND : TAB_UNFOCUSED_INACTIVE_FOREGROUND) || ''; } @@ -1303,7 +1292,6 @@ export class TabsTitleControl extends TitleControl { const visibleTabsContainerWidth = tabsContainer.offsetWidth; const allTabsWidth = tabsContainer.scrollWidth; - const allTabsHeight = tabsContainer.offsetHeight; // Compute width of sticky tabs depending on pinned tab sizing // - compact: sticky-tabs * TAB_SIZES.compact @@ -1339,10 +1327,19 @@ export class TabsTitleControl extends TitleControl { tabsContainer.classList.remove('disable-sticky-tabs'); } - // Add the multi-line tabs class after first row is filled out and wraps - if (allTabsWidth > visibleTabsContainerWidth && this.accessor.partOptions.multiLineTabs) { - tabsAndActionsContainer.classList.add('multi-line'); - } else if ((allTabsWidth === visibleTabsContainerWidth && allTabsHeight === EDITOR_TITLE_HEIGHT) || !this.accessor.partOptions.multiLineTabs) { + // Handle multi-line tabs according to setting: + // - enabled: only add class if tabs wrap + // - disabled: remove class + if (this.accessor.partOptions.multiLineTabs) { + if (allTabsWidth > visibleTabsContainerWidth) { + tabsAndActionsContainer.classList.add('multi-line'); + } else if (allTabsWidth === visibleTabsContainerWidth) { + const allTabsHeight = tabsContainer.offsetHeight; + if (allTabsHeight === EDITOR_TITLE_HEIGHT) { + tabsAndActionsContainer.classList.remove('multi-line'); + } + } + } else { tabsAndActionsContainer.classList.remove('multi-line'); } From 318f2983d93a3c4b966a57340817bf7192ad5bf9 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 18 Sep 2020 17:57:14 +0200 Subject: [PATCH 08/38] add clarifying comment to tabs layout --- .../browser/parts/editor/tabsTitleControl.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index de8802f6bd713..3eb584cbdd8e6 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -1331,11 +1331,20 @@ export class TabsTitleControl extends TitleControl { // - enabled: only add class if tabs wrap // - disabled: remove class if (this.accessor.partOptions.multiLineTabs) { + + // tabs exceed the tabs container width, so we start to wrap multi-line if (allTabsWidth > visibleTabsContainerWidth) { tabsAndActionsContainer.classList.add('multi-line'); - } else if (allTabsWidth === visibleTabsContainerWidth) { - const allTabsHeight = tabsContainer.offsetHeight; - if (allTabsHeight === EDITOR_TITLE_HEIGHT) { + } + + // if we do not exceed the tabs container width, we cannot simply remove + // the multi-line class because by wrapping tabs, they reduce their size + // and we would otherwise constantly add and remove the class. As such + // we need to check if the height of the tabs container is back to normal + // and then remove the multi-line class. + else if (allTabsWidth === visibleTabsContainerWidth && tabsAndActionsContainer.classList.contains('multi-line')) { + const visibleTabsContainerHeight = tabsContainer.offsetHeight; + if (visibleTabsContainerHeight === EDITOR_TITLE_HEIGHT) { tabsAndActionsContainer.classList.remove('multi-line'); } } From e6974fdfec48de8ae798eb340088f5fb7c5fde41 Mon Sep 17 00:00:00 2001 From: "sneakyfish5.sneaky@gmail.com" Date: Fri, 18 Sep 2020 12:02:50 -0500 Subject: [PATCH 09/38] Fix editor container height --- .../workbench/browser/parts/editor/noTabsTitleControl.ts | 3 +-- src/vs/workbench/browser/parts/editor/tabsTitleControl.ts | 8 +++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts index 1ccbc86d42fed..02e2d0ed773f2 100644 --- a/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts @@ -10,7 +10,6 @@ import { ResourceLabel, IResourceLabel } from 'vs/workbench/browser/labels'; import { TAB_ACTIVE_FOREGROUND, TAB_UNFOCUSED_ACTIVE_FOREGROUND } from 'vs/workbench/common/theme'; import { EventType as TouchEventType, GestureEvent, Gesture } from 'vs/base/browser/touch'; import { addDisposableListener, EventType, EventHelper, Dimension } from 'vs/base/browser/dom'; -import { EDITOR_TITLE_HEIGHT } from 'vs/workbench/browser/parts/editor/editor'; import { IAction } from 'vs/base/common/actions'; import { CLOSE_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; import { Color } from 'vs/base/common/color'; @@ -114,7 +113,7 @@ export class NoTabsTitleControl extends TitleControl { } getPreferredHeight(): number { - return EDITOR_TITLE_HEIGHT; + return this.group.titleHeight; } openEditor(editor: IEditorInput): void { diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index 3eb584cbdd8e6..a1744dd298c6e 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -1017,6 +1017,12 @@ export class TabsTitleControl extends TitleControl { // Ensure the active tab is always revealed this.layout(this.dimension); + + // When multi-line tabs are enabled, the title height grows beyond the default + // Thus, the editor container height needs to relayout + if (this.accessor.partOptions.multiLineTabs) { + this.group.relayout(); + } } private redrawTab(editor: IEditorInput, index: number, tabContainer: HTMLElement, tabLabelWidget: IResourceLabel, tabLabel: IEditorInputLabel, tabActionBar: ActionBar): void { @@ -1224,7 +1230,7 @@ export class TabsTitleControl extends TitleControl { } getPreferredHeight(): number { - return EDITOR_TITLE_HEIGHT + (this.breadcrumbsControl && !this.breadcrumbsControl.isHidden() ? BreadcrumbsControl.HEIGHT : 0); + return this.group.titleHeight + (this.breadcrumbsControl && !this.breadcrumbsControl.isHidden() ? BreadcrumbsControl.HEIGHT : 0); } layout(dimension: Dimension | undefined): void { From 3a3d7f0bf8aedf0d849e71154c1b6c7f2d9c24f7 Mon Sep 17 00:00:00 2001 From: "sneakyfish5.sneaky@gmail.com" Date: Fri, 18 Sep 2020 16:20:05 -0500 Subject: [PATCH 10/38] WIP - overflowing tabs --- src/vs/workbench/browser/parts/editor/editor.ts | 1 + src/vs/workbench/browser/parts/editor/editorGroupView.ts | 4 ++++ src/vs/workbench/browser/parts/editor/tabsTitleControl.ts | 6 +++++- src/vs/workbench/test/browser/workbenchTestServices.ts | 1 + 4 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/editor/editor.ts b/src/vs/workbench/browser/parts/editor/editor.ts index c75dfce76d85b..2987e35e9edb1 100644 --- a/src/vs/workbench/browser/parts/editor/editor.ts +++ b/src/vs/workbench/browser/parts/editor/editor.ts @@ -123,6 +123,7 @@ export interface IEditorGroupView extends IDisposable, ISerializableView, IEdito readonly whenRestored: Promise; readonly titleHeight: number; + readonly editorHeight: number; readonly isEmpty: boolean; readonly isMinimized: boolean; diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index 818040e252068..035db2e06b9cb 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -729,6 +729,10 @@ export class EditorGroupView extends Themable implements IEditorGroupView { return EDITOR_TITLE_HEIGHT; } + get editorHeight(): number { + return (this.element.offsetHeight - this.titleHeight); + } + get isMinimized(): boolean { if (!this.dimension) { return false; diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index a1744dd298c6e..038af23ecc343 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -34,7 +34,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { MergeGroupMode, IMergeGroupOptions, GroupsArrangement, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { addDisposableListener, EventType, EventHelper, 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 { IEditorGroupsAccessor, IEditorGroupView, EditorServiceImpl, EDITOR_TITLE_HEIGHT, DEFAULT_EDITOR_MIN_DIMENSIONS } from 'vs/workbench/browser/parts/editor/editor'; 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'; @@ -1338,6 +1338,10 @@ export class TabsTitleControl extends TitleControl { // - disabled: remove class if (this.accessor.partOptions.multiLineTabs) { + if (this.group.editorHeight < DEFAULT_EDITOR_MIN_DIMENSIONS.height) { + tabsAndActionsContainer.classList.remove('multi-line'); + } + // tabs exceed the tabs container width, so we start to wrap multi-line if (allTabsWidth > visibleTabsContainerWidth) { tabsAndActionsContainer.classList.add('multi-line'); diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index bb24acb1869b9..15c6bea70f869 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -603,6 +603,7 @@ export class TestEditorGroupView implements IEditorGroupView { minimumHeight!: number; maximumHeight!: number; titleHeight!: number; + editorHeight!: number; isEmpty = true; isMinimized = false; From 35fdc271afb1bcb9cdea7e2808557c280f8c2acd Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 21 Sep 2020 09:49:50 +0200 Subject: [PATCH 11/38] fix getPreferredHeight() --- src/vs/workbench/browser/parts/editor/editor.ts | 2 +- .../browser/parts/editor/editorDropTarget.ts | 2 +- .../browser/parts/editor/editorGroupView.ts | 6 +++--- .../browser/parts/editor/tabsTitleControl.ts | 15 ++++++++++++++- .../test/browser/workbenchTestServices.ts | 2 +- 5 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editor.ts b/src/vs/workbench/browser/parts/editor/editor.ts index d660171859a52..5a7973c40f592 100644 --- a/src/vs/workbench/browser/parts/editor/editor.ts +++ b/src/vs/workbench/browser/parts/editor/editor.ts @@ -120,7 +120,7 @@ export interface IEditorGroupView extends IDisposable, ISerializableView, IEdito readonly group: EditorGroup; readonly whenRestored: Promise; - readonly titleHeight: number; + readonly preferredTitleHeight: number; readonly editorHeight: number; readonly isEmpty: boolean; diff --git a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts index b17d0e4650684..3b82e1a65e7a5 100644 --- a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts +++ b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts @@ -503,7 +503,7 @@ class DropOverlay extends Themable { // With tabs and opened editors: use the area below tabs as drop target if (!this.groupView.isEmpty && this.accessor.partOptions.showTabs) { - return this.groupView.titleHeight; + return this.groupView.preferredTitleHeight; } // Without tabs or empty group: use entire editor area as drop target diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index b01ea5a1a9e79..fceaa138c9783 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -717,12 +717,12 @@ export class EditorGroupView extends Themable implements IEditorGroupView { return this._group.count === 0; } - get titleHeight(): number { + get preferredTitleHeight(): number { return this.titleAreaControl.getPreferredHeight(); } get editorHeight(): number { - return (this.element.offsetHeight - this.titleHeight); + return (this.element.offsetHeight - this.preferredTitleHeight); } get isMinimized(): boolean { @@ -1703,7 +1703,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this.dimension = new Dimension(width, height); // Ensure editor container gets height as CSS depending on the preferred height of the title control - const titleHeight = this.titleHeight; + const titleHeight = this.preferredTitleHeight; const editorHeight = Math.max(0, height - titleHeight); this.editorContainer.style.height = `${editorHeight}px`; diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index a3309548cca5a..c09277b60b510 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -1232,7 +1232,20 @@ export class TabsTitleControl extends TitleControl { } getPreferredHeight(): number { - return this.group.titleHeight + (this.breadcrumbsControl && !this.breadcrumbsControl.isHidden() ? BreadcrumbsControl.HEIGHT : 0); + let height = TabsTitleControl.TAB_HEIGHT; + + // Multi-line: we need to ask `offsetHeight` to get + // the real height of the title area with wrapping. + if (this.accessor.partOptions.multiLineTabs) { + const tabsAndActionsContainer = assertIsDefined(this.tabsAndActionsContainer); + height = tabsAndActionsContainer.offsetHeight; + } + + if (this.breadcrumbsControl && !this.breadcrumbsControl.isHidden()) { + height += BreadcrumbsControl.HEIGHT; + } + + return height; } layout(dimension: Dimension | undefined): void { diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 15c6bea70f869..781abb93c84a2 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -602,7 +602,7 @@ export class TestEditorGroupView implements IEditorGroupView { maximumWidth!: number; minimumHeight!: number; maximumHeight!: number; - titleHeight!: number; + preferredTitleHeight!: number; editorHeight!: number; isEmpty = true; From 4cb7e4991046b00aa4fcd83525f8d99237e8b205 Mon Sep 17 00:00:00 2001 From: "sneakyfish5.sneaky@gmail.com" Date: Tue, 22 Sep 2020 08:23:17 -0500 Subject: [PATCH 12/38] Fix editor drop target for multi-line tabs --- src/vs/workbench/browser/parts/editor/editorDropTarget.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts index 9c48c54fe33ce..dcef48891ec0f 100644 --- a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts +++ b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts @@ -503,6 +503,11 @@ class DropOverlay extends Themable { // With tabs and opened editors: use the area below tabs as drop target if (!this.groupView.isEmpty && this.accessor.partOptions.showTabs) { + + if (this.accessor.partOptions.multiLineTabs) { + return this.groupView.titleDimensions.height; + } + return this.groupView.titleDimensions.offset; } From 8b34eca45106bbaa483234797c11df71f666fad3 Mon Sep 17 00:00:00 2001 From: "sneakyfish5.sneaky@gmail.com" Date: Tue, 22 Sep 2020 08:29:13 -0500 Subject: [PATCH 13/38] Add comments and remove !important --- src/vs/workbench/browser/parts/editor/editorDropTarget.ts | 1 + .../browser/parts/editor/media/tabstitlecontrol.css | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts index dcef48891ec0f..dc7dbb5c43211 100644 --- a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts +++ b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts @@ -504,6 +504,7 @@ class DropOverlay extends Themable { // With tabs and opened editors: use the area below tabs as drop target if (!this.groupView.isEmpty && this.accessor.partOptions.showTabs) { + // Return the actual height when multi-line tabs are enabled if (this.accessor.partOptions.multiLineTabs) { return this.groupView.titleDimensions.height; } diff --git a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css b/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css index fad530406859a..c6d3ab112c438 100644 --- a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css @@ -37,7 +37,7 @@ /* Title Container (multi-line wrapping) */ .monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container.multi-line > .monaco-scrollable-element > .tabs-container { - height: auto !important; + height: auto; flex-wrap: wrap; } @@ -47,11 +47,11 @@ } .monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container.multi-line > .monaco-scrollable-element { - height: auto !important; + height: auto; /* For when breadcrumbs are enabled */ } .monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container.multi-line > .monaco-scrollable-element > .tabs-container > .tab { - flex-grow: 1; + flex-grow: 1; /* Grow the tabs to fill the row */ } /* Tabs Container */ From ae05859e276955e16cbfc83391b7d796fd6e3ba7 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sun, 4 Oct 2020 08:25:52 +0200 Subject: [PATCH 14/38] fix dnd offset --- .../browser/parts/editor/editorDropTarget.ts | 6 ------ .../browser/parts/editor/tabsTitleControl.ts | 12 +++++++----- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts index dc7dbb5c43211..9c48c54fe33ce 100644 --- a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts +++ b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts @@ -503,12 +503,6 @@ class DropOverlay extends Themable { // With tabs and opened editors: use the area below tabs as drop target if (!this.groupView.isEmpty && this.accessor.partOptions.showTabs) { - - // Return the actual height when multi-line tabs are enabled - if (this.accessor.partOptions.multiLineTabs) { - return this.groupView.titleDimensions.height; - } - return this.groupView.titleDimensions.offset; } diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index 7c10400586ece..d2a1f724e31dd 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -1232,23 +1232,25 @@ export class TabsTitleControl extends TitleControl { } getDimensions(): IEditorGroupTitleDimensions { - let height = TabsTitleControl.TAB_HEIGHT; + let height: number; + let offset: number; // Multi-line: we need to ask `offsetHeight` to get // the real height of the title area with wrapping. if (this.accessor.partOptions.multiLineTabs) { const tabsAndActionsContainer = assertIsDefined(this.tabsAndActionsContainer); height = tabsAndActionsContainer.offsetHeight; + } else { + height = TabsTitleControl.TAB_HEIGHT; } + offset = height; + if (this.breadcrumbsControl && !this.breadcrumbsControl.isHidden()) { height += BreadcrumbsControl.HEIGHT; } - return { - height, - offset: TabsTitleControl.TAB_HEIGHT - }; + return { height, offset }; } layout(dimension: Dimension | undefined): void { From ac0a02482669a6b142d0d09f516f733fe2b906c5 Mon Sep 17 00:00:00 2001 From: "sneakyfish5.sneaky@gmail.com" Date: Sun, 4 Oct 2020 13:28:16 -0500 Subject: [PATCH 15/38] Rework layout algorithm --- .../browser/parts/editor/editorGroupView.ts | 14 ++++++-------- .../parts/editor/media/tabstitlecontrol.css | 2 +- .../browser/parts/editor/noTabsTitleControl.ts | 4 +++- .../browser/parts/editor/tabsTitleControl.ts | 4 +++- .../workbench/browser/parts/editor/titleControl.ts | 2 +- 5 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index fa24167a322fc..f5d37009c72b7 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -1702,14 +1702,12 @@ export class EditorGroupView extends Themable implements IEditorGroupView { layout(width: number, height: number): void { this.dimension = new Dimension(width, height); - // Ensure editor container gets height as CSS depending on the preferred height of the title control - const titleHeight = this.titleDimensions.height; - const editorHeight = Math.max(0, height - titleHeight); - this.editorContainer.style.height = `${editorHeight}px`; - - // Forward to controls - this.titleAreaControl.layout(new Dimension(width, titleHeight)); - this.editorControl.layout(new Dimension(width, editorHeight)); + // Use the initial height of the title area to layout + const initialTitleHeight = this.titleDimensions.height; + const titleHeight = this.titleAreaControl.layout(new Dimension(width, initialTitleHeight)) as number; + + // Use the new height of the title area to layout the editor + this.editorControl.layout(new Dimension(width, this.dimension.height - titleHeight)); } relayout(): void { diff --git a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css b/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css index c6d3ab112c438..8917461a2b506 100644 --- a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css @@ -47,7 +47,7 @@ } .monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container.multi-line > .monaco-scrollable-element { - height: auto; /* For when breadcrumbs are enabled */ + height: auto !important; /* For when breadcrumbs are enabled */ } .monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container.multi-line > .monaco-scrollable-element > .tabs-container > .tab { diff --git a/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts index c42ffee0434c7..3692839833b9d 100644 --- a/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts @@ -323,9 +323,11 @@ export class NoTabsTitleControl extends TitleControl { }; } - layout(dimension: Dimension): void { + layout(dimension: Dimension): number { if (this.breadcrumbsControl) { this.breadcrumbsControl.layout(undefined); } + + return this.getDimensions().height; } } diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index d2a1f724e31dd..90a881ffb8535 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -1253,7 +1253,7 @@ export class TabsTitleControl extends TitleControl { return { height, offset }; } - layout(dimension: Dimension | undefined): void { + layout(dimension: Dimension | undefined): number | undefined { this.dimension = dimension; const activeTabAndIndex = this.group.activeEditor ? this.getTabAndIndex(this.group.activeEditor) : undefined; @@ -1272,6 +1272,8 @@ export class TabsTitleControl extends TitleControl { this.layoutScheduled.clear(); }); } + + return this.getDimensions().height; } private doLayout(dimension: Dimension): void { diff --git a/src/vs/workbench/browser/parts/editor/titleControl.ts b/src/vs/workbench/browser/parts/editor/titleControl.ts index 877b9cd83fb85..b6bf483d53d1c 100644 --- a/src/vs/workbench/browser/parts/editor/titleControl.ts +++ b/src/vs/workbench/browser/parts/editor/titleControl.ts @@ -407,7 +407,7 @@ export abstract class TitleControl extends Themable { abstract updateStyles(): void; - abstract layout(dimension: Dimension): void; + abstract layout(dimension: Dimension): number | undefined; abstract getDimensions(): IEditorGroupTitleDimensions; From b5967e542a6341f277c4e3289fba2c87dc8a52c7 Mon Sep 17 00:00:00 2001 From: "sneakyfish5.sneaky@gmail.com" Date: Mon, 5 Oct 2020 23:32:08 -0500 Subject: [PATCH 16/38] Make layout return a Dimension --- src/vs/workbench/browser/parts/editor/editorGroupView.ts | 4 ++-- src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts | 4 ++-- src/vs/workbench/browser/parts/editor/tabsTitleControl.ts | 4 ++-- src/vs/workbench/browser/parts/editor/titleControl.ts | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index f5d37009c72b7..48724ec78902d 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -1704,10 +1704,10 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Use the initial height of the title area to layout const initialTitleHeight = this.titleDimensions.height; - const titleHeight = this.titleAreaControl.layout(new Dimension(width, initialTitleHeight)) as number; + const titleDimensions = this.titleAreaControl.layout(new Dimension(width, initialTitleHeight)) as Dimension; // Use the new height of the title area to layout the editor - this.editorControl.layout(new Dimension(width, this.dimension.height - titleHeight)); + this.editorControl.layout(new Dimension(width, this.dimension.height - titleDimensions.height)); } relayout(): void { diff --git a/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts index 3692839833b9d..e4ddd37da3c02 100644 --- a/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts @@ -323,11 +323,11 @@ export class NoTabsTitleControl extends TitleControl { }; } - layout(dimension: Dimension): number { + layout(dimension: Dimension): Dimension { if (this.breadcrumbsControl) { this.breadcrumbsControl.layout(undefined); } - return this.getDimensions().height; + return new Dimension(dimension.width, this.getDimensions().height); } } diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index 90a881ffb8535..2239bf76cd4b5 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -1253,7 +1253,7 @@ export class TabsTitleControl extends TitleControl { return { height, offset }; } - layout(dimension: Dimension | undefined): number | undefined { + layout(dimension: Dimension | undefined): Dimension | undefined { this.dimension = dimension; const activeTabAndIndex = this.group.activeEditor ? this.getTabAndIndex(this.group.activeEditor) : undefined; @@ -1273,7 +1273,7 @@ export class TabsTitleControl extends TitleControl { }); } - return this.getDimensions().height; + return new Dimension(this.dimension.width, this.getDimensions().height); } private doLayout(dimension: Dimension): void { diff --git a/src/vs/workbench/browser/parts/editor/titleControl.ts b/src/vs/workbench/browser/parts/editor/titleControl.ts index b6bf483d53d1c..1a697c7927029 100644 --- a/src/vs/workbench/browser/parts/editor/titleControl.ts +++ b/src/vs/workbench/browser/parts/editor/titleControl.ts @@ -407,7 +407,7 @@ export abstract class TitleControl extends Themable { abstract updateStyles(): void; - abstract layout(dimension: Dimension): number | undefined; + abstract layout(dimension: Dimension): Dimension | undefined; abstract getDimensions(): IEditorGroupTitleDimensions; From 5f7a1aa1389cf5fd753f360ce482764881d4422c Mon Sep 17 00:00:00 2001 From: "sneakyfish5.sneaky@gmail.com" Date: Tue, 6 Oct 2020 23:06:18 -0500 Subject: [PATCH 17/38] WIP - set maxDimensions --- .../browser/parts/editor/editorGroupView.ts | 4 ++-- .../browser/parts/editor/tabsTitleControl.ts | 12 +++++++----- .../workbench/browser/parts/editor/titleControl.ts | 2 +- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index 48724ec78902d..a964fe4d2639d 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -1703,8 +1703,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this.dimension = new Dimension(width, height); // Use the initial height of the title area to layout - const initialTitleHeight = this.titleDimensions.height; - const titleDimensions = this.titleAreaControl.layout(new Dimension(width, initialTitleHeight)) as Dimension; + const titleMaxDimensions = new Dimension(this.dimension.width, this.dimension.height - this.minimumHeight); + const titleDimensions = this.titleAreaControl.layout(this.dimension, titleMaxDimensions) as Dimension; // Use the new height of the title area to layout the editor this.editorControl.layout(new Dimension(width, this.dimension.height - titleDimensions.height)); diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index 2239bf76cd4b5..3506f418af536 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -34,7 +34,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { MergeGroupMode, IMergeGroupOptions, GroupsArrangement, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { addDisposableListener, EventType, EventHelper, Dimension, scheduleAtNextAnimationFrame, findParentWithClass, clearNode } from 'vs/base/browser/dom'; import { localize } from 'vs/nls'; -import { IEditorGroupsAccessor, IEditorGroupView, EditorServiceImpl, IEditorGroupTitleDimensions, DEFAULT_EDITOR_MIN_DIMENSIONS } from 'vs/workbench/browser/parts/editor/editor'; +import { IEditorGroupsAccessor, IEditorGroupView, EditorServiceImpl, IEditorGroupTitleDimensions } from 'vs/workbench/browser/parts/editor/editor'; 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'; @@ -87,6 +87,7 @@ export class TabsTitleControl extends TitleControl { private tabDisposables: IDisposable[] = []; private dimension: Dimension | undefined; + private disableMultiLineTabs: boolean | undefined; private readonly layoutScheduled = this._register(new MutableDisposable()); private blockRevealActiveTab: boolean | undefined; @@ -1253,7 +1254,7 @@ export class TabsTitleControl extends TitleControl { return { height, offset }; } - layout(dimension: Dimension | undefined): Dimension | undefined { + layout(dimension: Dimension | undefined, maxDimension?: Dimension): Dimension | undefined { this.dimension = dimension; const activeTabAndIndex = this.group.activeEditor ? this.getTabAndIndex(this.group.activeEditor) : undefined; @@ -1261,6 +1262,8 @@ export class TabsTitleControl extends TitleControl { return; } + this.disableMultiLineTabs = maxDimension && maxDimension.height > this.getDimensions().height ? true : false; + // The layout of tabs can be an expensive operation because we access DOM properties // that can result in the browser doing a full page layout to validate them. To buffer // this a little bit we try at least to schedule this work on the next animation frame. @@ -1359,8 +1362,8 @@ export class TabsTitleControl extends TitleControl { // - enabled: only add class if tabs wrap // - disabled: remove class if (this.accessor.partOptions.multiLineTabs) { - - if (this.group.editorHeight < DEFAULT_EDITOR_MIN_DIMENSIONS.height) { + const visibleTabsContainerHeight = tabsContainer.offsetHeight; + if (this.disableMultiLineTabs && tabsAndActionsContainer.classList.contains('multi-line')) { tabsAndActionsContainer.classList.remove('multi-line'); } @@ -1375,7 +1378,6 @@ export class TabsTitleControl extends TitleControl { // we need to check if the height of the tabs container is back to normal // and then remove the multi-line class. else if (allTabsWidth === visibleTabsContainerWidth && tabsAndActionsContainer.classList.contains('multi-line')) { - const visibleTabsContainerHeight = tabsContainer.offsetHeight; if (visibleTabsContainerHeight === TabsTitleControl.TAB_HEIGHT) { tabsAndActionsContainer.classList.remove('multi-line'); } diff --git a/src/vs/workbench/browser/parts/editor/titleControl.ts b/src/vs/workbench/browser/parts/editor/titleControl.ts index 1a697c7927029..f425e1f73f9fa 100644 --- a/src/vs/workbench/browser/parts/editor/titleControl.ts +++ b/src/vs/workbench/browser/parts/editor/titleControl.ts @@ -407,7 +407,7 @@ export abstract class TitleControl extends Themable { abstract updateStyles(): void; - abstract layout(dimension: Dimension): Dimension | undefined; + abstract layout(dimension: Dimension, maxDimension?: Dimension): Dimension | undefined; abstract getDimensions(): IEditorGroupTitleDimensions; From df0d4a49003ff427fbceabc5a936f8eeff37d505 Mon Sep 17 00:00:00 2001 From: "sneakyfish5.sneaky@gmail.com" Date: Wed, 7 Oct 2020 18:03:35 -0500 Subject: [PATCH 18/38] Layout multi-line tabs synchronously --- .../browser/parts/editor/editorGroupView.ts | 2 +- .../browser/parts/editor/tabsTitleControl.ts | 22 ++++++++++++++++--- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index a964fe4d2639d..4736f4df2655e 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -1703,7 +1703,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this.dimension = new Dimension(width, height); // Use the initial height of the title area to layout - const titleMaxDimensions = new Dimension(this.dimension.width, this.dimension.height - this.minimumHeight); + const titleMaxDimensions = new Dimension(this.dimension.width, this.dimension.height - this.editorControl.minimumHeight); const titleDimensions = this.titleAreaControl.layout(this.dimension, titleMaxDimensions) as Dimension; // Use the new height of the title area to layout the editor diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index 3506f418af536..6b3a869d0a042 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -1262,8 +1262,26 @@ export class TabsTitleControl extends TitleControl { return; } - this.disableMultiLineTabs = maxDimension && maxDimension.height > this.getDimensions().height ? true : false; + const maxHeight = maxDimension?.height ?? this.getDimensions().height; + this.disableMultiLineTabs = this.getDimensions().height > maxHeight ? true : false; + // Layout tabs synchronously if multi-line tabs are enabled so that + // the correct height of the title area can be returned to the title control + if (this.accessor.partOptions.multiLineTabs) { + this.layoutSync(); + } else { + this.layoutAsync(); + } + + return new Dimension(this.dimension.width, this.getDimensions().height); + } + + private layoutSync() { + const dimension = assertIsDefined(this.dimension); + this.doLayout(dimension); + } + + private layoutAsync() { // The layout of tabs can be an expensive operation because we access DOM properties // that can result in the browser doing a full page layout to validate them. To buffer // this a little bit we try at least to schedule this work on the next animation frame. @@ -1275,8 +1293,6 @@ export class TabsTitleControl extends TitleControl { this.layoutScheduled.clear(); }); } - - return new Dimension(this.dimension.width, this.getDimensions().height); } private doLayout(dimension: Dimension): void { From 9147044deab259a90be70d6247e665c2eb8efc68 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 13 Oct 2020 15:55:12 +0200 Subject: [PATCH 19/38] make sure dimensions are always defined and passed down to where needed --- src/vs/base/browser/dom.ts | 2 + src/vs/workbench/browser/part.ts | 2 +- .../workbench/browser/parts/editor/editor.ts | 1 - .../browser/parts/editor/editorGroupView.ts | 16 ++- .../parts/editor/noTabsTitleControl.ts | 6 +- .../browser/parts/editor/tabsTitleControl.ts | 109 +++++++++--------- .../browser/parts/editor/titleControl.ts | 16 ++- .../test/browser/workbenchTestServices.ts | 1 - 8 files changed, 83 insertions(+), 70 deletions(-) diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index 1192aace4f885..6db2a203d688c 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -495,6 +495,8 @@ export interface IDimension { export class Dimension implements IDimension { + static readonly None = new Dimension(0, 0); + constructor( public readonly width: number, public readonly height: number, diff --git a/src/vs/workbench/browser/part.ts b/src/vs/workbench/browser/part.ts index dfab3bc5cfc45..8e78cbccc27db 100644 --- a/src/vs/workbench/browser/part.ts +++ b/src/vs/workbench/browser/part.ts @@ -162,7 +162,7 @@ class PartLayout { if (this.options && this.options.hasTitle) { titleSize = new Dimension(width, Math.min(height, PartLayout.TITLE_HEIGHT)); } else { - titleSize = new Dimension(0, 0); + titleSize = Dimension.None; } let contentWidth = width; diff --git a/src/vs/workbench/browser/parts/editor/editor.ts b/src/vs/workbench/browser/parts/editor/editor.ts index abfaed58c9594..ef35a87e76665 100644 --- a/src/vs/workbench/browser/parts/editor/editor.ts +++ b/src/vs/workbench/browser/parts/editor/editor.ts @@ -137,7 +137,6 @@ export interface IEditorGroupView extends IDisposable, ISerializableView, IEdito readonly whenRestored: Promise; readonly titleDimensions: IEditorGroupTitleDimensions; - readonly editorHeight: number; readonly isEmpty: boolean; readonly isMinimized: boolean; diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index 71d18788ae285..faabace9eaef6 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -715,10 +715,6 @@ export class EditorGroupView extends Themable implements IEditorGroupView { return this.titleAreaControl.getDimensions(); } - get editorHeight(): number { - return (this.element.offsetHeight - this.titleDimensions.height); - } - get isMinimized(): boolean { if (!this.dimension) { return false; @@ -1696,12 +1692,14 @@ export class EditorGroupView extends Themable implements IEditorGroupView { layout(width: number, height: number): void { this.dimension = new Dimension(width, height); - // Use the initial height of the title area to layout - const titleMaxDimensions = new Dimension(this.dimension.width, this.dimension.height - this.editorControl.minimumHeight); - const titleDimensions = this.titleAreaControl.layout(this.dimension, titleMaxDimensions) as Dimension; + // Layout the title area first to receive the size it occupies + const titleAreaSize = this.titleAreaControl.layout({ + container: this.dimension, + available: new Dimension(width, height - this.editorControl.minimumHeight) + }); - // Use the new height of the title area to layout the editor - this.editorControl.layout(new Dimension(width, this.dimension.height - titleDimensions.height)); + // Pass the container width and remaining height to the editor layout + this.editorControl.layout(new Dimension(width, height - titleAreaSize.height)); } relayout(): void { diff --git a/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts index a217edeec4667..2d56db005aa16 100644 --- a/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts @@ -5,7 +5,7 @@ import 'vs/css!./media/notabstitlecontrol'; import { EditorResourceAccessor, Verbosity, IEditorInput, IEditorPartOptions, SideBySideEditor } from 'vs/workbench/common/editor'; -import { TitleControl, IToolbarActions } from 'vs/workbench/browser/parts/editor/titleControl'; +import { TitleControl, IToolbarActions, ITitleControlDimensions } from 'vs/workbench/browser/parts/editor/titleControl'; import { ResourceLabel, IResourceLabel } from 'vs/workbench/browser/labels'; import { TAB_ACTIVE_FOREGROUND, TAB_UNFOCUSED_ACTIVE_FOREGROUND } from 'vs/workbench/common/theme'; import { EventType as TouchEventType, GestureEvent, Gesture } from 'vs/base/browser/touch'; @@ -323,11 +323,11 @@ export class NoTabsTitleControl extends TitleControl { }; } - layout(dimension: Dimension): Dimension { + layout(dimensions: ITitleControlDimensions): Dimension { if (this.breadcrumbsControl) { this.breadcrumbsControl.layout(undefined); } - return new Dimension(dimension.width, this.getDimensions().height); + return new Dimension(dimensions.container.width, NoTabsTitleControl.HEIGHT); } } diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index b7f0f2153d0cf..2104ed54f7e35 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -18,7 +18,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; 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 { ITitleControlDimensions, TitleControl } from 'vs/workbench/browser/parts/editor/titleControl'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { IDisposable, dispose, DisposableStore, combinedDisposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; @@ -86,8 +86,11 @@ export class TabsTitleControl extends TitleControl { private tabActionBars: ActionBar[] = []; private tabDisposables: IDisposable[] = []; - private dimension: Dimension | undefined; - private disableMultiLineTabs: boolean | undefined; + private dimensions: ITitleControlDimensions = { + container: Dimension.None, + available: Dimension.None + }; + private readonly layoutScheduled = this._register(new MutableDisposable()); private blockRevealActiveTab: boolean | undefined; @@ -358,7 +361,7 @@ export class TabsTitleControl extends TitleControl { // Changing the actions in the toolbar can have an impact on the size of the // tab container, so we need to layout the tabs to make sure the active is visible - this.layout(this.dimension); + this.layout(this.dimensions); } openEditor(editor: IEditorInput): void { @@ -441,7 +444,7 @@ export class TabsTitleControl extends TitleControl { }); // Moving an editor requires a layout to keep the active editor visible - this.layout(this.dimension); + this.layout(this.dimensions); } pinEditor(editor: IEditorInput): void { @@ -468,7 +471,7 @@ export class TabsTitleControl extends TitleControl { }); // A change to the sticky state requires a layout to keep the active editor visible - this.layout(this.dimension); + this.layout(this.dimensions); } setActive(isGroupActive: boolean): void { @@ -480,7 +483,7 @@ export class TabsTitleControl extends TitleControl { // Activity has an impact on the toolbar, so we need to update and layout this.updateEditorActionsToolbar(); - this.layout(this.dimension); + this.layout(this.dimensions); } private updateEditorLabelAggregator = this._register(new RunOnceScheduler(() => this.updateEditorLabels(), 0)); @@ -506,7 +509,7 @@ export class TabsTitleControl extends TitleControl { }); // A change to a label requires a layout to keep the active editor visible - this.layout(this.dimension); + this.layout(this.dimensions); } updateEditorDirty(editor: IEditorInput): void { @@ -1025,13 +1028,7 @@ export class TabsTitleControl extends TitleControl { this.updateEditorActionsToolbar(); // Ensure the active tab is always revealed - this.layout(this.dimension); - - // When multi-line tabs are enabled, the title height grows beyond the default - // Thus, the editor container height needs to relayout - if (this.accessor.partOptions.multiLineTabs) { - this.group.relayout(); - } + this.layout(this.dimensions); } private redrawTab(editor: IEditorInput, index: number, tabContainer: HTMLElement, tabLabelWidget: IResourceLabel, tabLabel: IEditorInputLabel, tabActionBar: ActionBar): void { @@ -1243,20 +1240,18 @@ export class TabsTitleControl extends TitleControl { } getDimensions(): IEditorGroupTitleDimensions { - let height: number; - let offset: number; - // Multi-line: we need to ask `offsetHeight` to get // the real height of the title area with wrapping. - if (this.accessor.partOptions.multiLineTabs) { - const tabsAndActionsContainer = assertIsDefined(this.tabsAndActionsContainer); - height = tabsAndActionsContainer.offsetHeight; + let height: number; + if (this.accessor.partOptions.multiLineTabs && this.tabsAndActionsContainer && this.tabsAndActionsContainer.classList.contains('multi-line')) { + height = this.tabsAndActionsContainer.offsetHeight; } else { height = TabsTitleControl.TAB_HEIGHT; } - offset = height; + const offset = height; + // Account for breadcrumbs if visible if (this.breadcrumbsControl && !this.breadcrumbsControl.isHidden()) { height += BreadcrumbsControl.HEIGHT; } @@ -1264,31 +1259,30 @@ export class TabsTitleControl extends TitleControl { return { height, offset }; } - layout(dimension: Dimension | undefined, maxDimension?: Dimension): Dimension | undefined { - this.dimension = dimension; + layout(dimensions: ITitleControlDimensions): Dimension { + this.dimensions = dimensions; + // We need an opened editor and dimensions to layout the title + // Otherwise quickly return from the layout algorithm const activeTabAndIndex = this.group.activeEditor ? this.getTabAndIndex(this.group.activeEditor) : undefined; - if (!activeTabAndIndex || !this.dimension) { - return; + if (!activeTabAndIndex || dimensions.container === Dimension.None || dimensions.available === Dimension.None) { + return Dimension.None; } - const maxHeight = maxDimension?.height ?? this.getDimensions().height; - this.disableMultiLineTabs = this.getDimensions().height > maxHeight ? true : false; - // Layout tabs synchronously if multi-line tabs are enabled so that - // the correct height of the title area can be returned to the title control + // the correct height of the title area can be returned to the title + // control if (this.accessor.partOptions.multiLineTabs) { - this.layoutSync(); + this.layoutSync(dimensions); } else { this.layoutAsync(); } - return new Dimension(this.dimension.width, this.getDimensions().height); + return new Dimension(dimensions.container.width, this.getDimensions().height); } - private layoutSync() { - const dimension = assertIsDefined(this.dimension); - this.doLayout(dimension); + private layoutSync(dimensions: ITitleControlDimensions) { + this.doLayout(dimensions); } private layoutAsync() { @@ -1297,38 +1291,37 @@ export class TabsTitleControl extends TitleControl { // this a little bit we try at least to schedule this work on the next animation frame. if (!this.layoutScheduled.value) { this.layoutScheduled.value = scheduleAtNextAnimationFrame(() => { - const dimension = assertIsDefined(this.dimension); - this.doLayout(dimension); + this.doLayout(this.dimensions); this.layoutScheduled.clear(); }); } } - private doLayout(dimension: Dimension): void { + private doLayout(dimensions: ITitleControlDimensions): void { const activeTabAndIndex = this.group.activeEditor ? this.getTabAndIndex(this.group.activeEditor) : undefined; if (!activeTabAndIndex) { return; // nothing to do if not editor opened } // Breadcrumbs - this.doLayoutBreadcrumbs(dimension); + this.doLayoutBreadcrumbs(dimensions); // Tabs const [activeTab, activeIndex] = activeTabAndIndex; - this.doLayoutTabs(activeTab, activeIndex); + this.doLayoutTabs(activeTab, activeIndex, dimensions); } - private doLayoutBreadcrumbs(dimension: Dimension): void { + private doLayoutBreadcrumbs(dimensions: ITitleControlDimensions): void { if (this.breadcrumbsControl && !this.breadcrumbsControl.isHidden()) { const tabsScrollbar = assertIsDefined(this.tabsScrollbar); - this.breadcrumbsControl.layout({ width: dimension.width, height: BreadcrumbsControl.HEIGHT }); - tabsScrollbar.getDomNode().style.height = `${dimension.height - BreadcrumbsControl.HEIGHT}px`; + this.breadcrumbsControl.layout({ width: dimensions.container.width, height: BreadcrumbsControl.HEIGHT }); + tabsScrollbar.getDomNode().style.height = `${dimensions.container.height - BreadcrumbsControl.HEIGHT}px`; } } - private doLayoutTabs(activeTab: HTMLElement, activeIndex: number): void { + private doLayoutTabs(activeTab: HTMLElement, activeIndex: number, dimensions: ITitleControlDimensions): void { const [tabsContainer, tabsScrollbar, tabsAndActionsContainer] = assertAllDefined(this.tabsContainer, this.tabsScrollbar, this.tabsAndActionsContainer); // @@ -1391,14 +1384,20 @@ export class TabsTitleControl extends TitleControl { // - enabled: only add class if tabs wrap // - disabled: remove class if (this.accessor.partOptions.multiLineTabs) { - const visibleTabsContainerHeight = tabsContainer.offsetHeight; - if (this.disableMultiLineTabs && tabsAndActionsContainer.classList.contains('multi-line')) { - tabsAndActionsContainer.classList.remove('multi-line'); + + // Tabs wrap multiline: remove wrapping if height exceeds available height + const tabsWrapMultiLine = tabsAndActionsContainer.classList.contains('multi-line'); + if (tabsWrapMultiLine) { + if (tabsContainer.offsetHeight > dimensions.available.height) { + tabsAndActionsContainer.classList.remove('multi-line'); + } } - // tabs exceed the tabs container width, so we start to wrap multi-line - if (allTabsWidth > visibleTabsContainerWidth) { - tabsAndActionsContainer.classList.add('multi-line'); + // Tabs do not wrap multiline: add wrapping if tabs exceed the tabs container width + else { + if (allTabsWidth > visibleTabsContainerWidth) { + tabsAndActionsContainer.classList.add('multi-line'); + } } // if we do not exceed the tabs container width, we cannot simply remove @@ -1406,8 +1405,8 @@ export class TabsTitleControl extends TitleControl { // and we would otherwise constantly add and remove the class. As such // we need to check if the height of the tabs container is back to normal // and then remove the multi-line class. - else if (allTabsWidth === visibleTabsContainerWidth && tabsAndActionsContainer.classList.contains('multi-line')) { - if (visibleTabsContainerHeight === TabsTitleControl.TAB_HEIGHT) { + if (allTabsWidth === visibleTabsContainerWidth && tabsAndActionsContainer.classList.contains('multi-line')) { + if (tabsContainer.offsetHeight === TabsTitleControl.TAB_HEIGHT) { tabsAndActionsContainer.classList.remove('multi-line'); } } @@ -1495,8 +1494,10 @@ export class TabsTitleControl extends TitleControl { const editorIndex = this.group.getIndexOfEditor(editor); if (editorIndex >= 0) { const tabsContainer = assertIsDefined(this.tabsContainer); - - return [tabsContainer.children[editorIndex] as HTMLElement, editorIndex]; + const tab = tabsContainer.children[editorIndex]; + if (tab) { + return [tabsContainer.children[editorIndex] as HTMLElement, editorIndex]; + } } return undefined; diff --git a/src/vs/workbench/browser/parts/editor/titleControl.ts b/src/vs/workbench/browser/parts/editor/titleControl.ts index 4ff6bd7315948..2cb51d357e8fe 100644 --- a/src/vs/workbench/browser/parts/editor/titleControl.ts +++ b/src/vs/workbench/browser/parts/editor/titleControl.ts @@ -46,6 +46,20 @@ export interface IToolbarActions { secondary: IAction[]; } +export interface ITitleControlDimensions { + + /** + * The size of the parent container the title control is layed out in. + */ + container: Dimension; + + /** + * The maximum size the title control is allowed to consume based on + * other controls that are positioned inside the container. + */ + available: Dimension; +} + export abstract class TitleControl extends Themable { protected readonly groupTransfer = LocalSelectionTransfer.getInstance(); @@ -407,7 +421,7 @@ export abstract class TitleControl extends Themable { abstract updateStyles(): void; - abstract layout(dimension: Dimension, maxDimension?: Dimension): Dimension | undefined; + abstract layout(dimensions: ITitleControlDimensions): Dimension; abstract getDimensions(): IEditorGroupTitleDimensions; diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 227674d7a860e..3527de197e467 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -604,7 +604,6 @@ export class TestEditorGroupView implements IEditorGroupView { maximumHeight!: number; titleDimensions!: IEditorGroupTitleDimensions; - editorHeight!: number; isEmpty = true; isMinimized = false; From 2be28a88416498f160ddf8b44bea3b2863ce0a5a Mon Sep 17 00:00:00 2001 From: "sneakyfish5.sneaky@gmail.com" Date: Sun, 1 Nov 2020 22:25:07 -0600 Subject: [PATCH 20/38] Rework group.relayout and store lastComputedHeight --- src/vs/workbench/browser/parts/editor/tabsTitleControl.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index dfdd06120c2b2..488352c17fc7b 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -91,6 +91,7 @@ export class TabsTitleControl extends TitleControl { container: Dimension.None, available: Dimension.None }; + private lastComputedHeight = this.getDimensions().height; private readonly layoutScheduled = this._register(new MutableDisposable()); private blockRevealActiveTab: boolean | undefined; @@ -1275,6 +1276,11 @@ export class TabsTitleControl extends TitleControl { // control if (this.accessor.partOptions.multiLineTabs) { this.layoutSync(dimensions); + const newHeight = this.getDimensions().height; + if (this.lastComputedHeight !== newHeight) { + this.lastComputedHeight = newHeight; + this.group.relayout(); + } } else { this.layoutAsync(); } From 8acace51b4e6771d2a84e16ca7d96e40cc762f63 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sat, 5 Dec 2020 08:56:59 +0100 Subject: [PATCH 21/38] fix breadcrumbs causing editor to disappear --- src/vs/workbench/browser/parts/editor/editorGroupView.ts | 4 +++- src/vs/workbench/browser/parts/editor/tabsTitleControl.ts | 8 +++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index ef14e6ded3a54..ed48c48900754 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -1711,7 +1711,9 @@ export class EditorGroupView extends Themable implements IEditorGroupView { }); // Pass the container width and remaining height to the editor layout - this.editorControl.layout(new Dimension(width, height - titleAreaSize.height)); + const editorHeight = Math.max(0, height - titleAreaSize.height); + this.editorContainer.style.height = `${editorHeight}px`; + this.editorControl.layout(new Dimension(width, editorHeight)); } relayout(): void { diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index b2a543fdf4d07..6d0be07a63ca8 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -1245,7 +1245,7 @@ export class TabsTitleControl extends TitleControl { // Multi-line: we need to ask `offsetHeight` to get // the real height of the title area with wrapping. let height: number; - if (this.accessor.partOptions.multiLineTabs && this.tabsAndActionsContainer && this.tabsAndActionsContainer.classList.contains('multi-line')) { + if (this.accessor.partOptions.multiLineTabs && this.tabsAndActionsContainer?.classList.contains('multi-line')) { height = this.tabsAndActionsContainer.offsetHeight; } else { height = TabsTitleControl.TAB_HEIGHT; @@ -1276,6 +1276,7 @@ export class TabsTitleControl extends TitleControl { // control if (this.accessor.partOptions.multiLineTabs) { this.layoutSync(dimensions); + const newHeight = this.getDimensions().height; if (this.lastComputedHeight !== newHeight) { this.lastComputedHeight = newHeight; @@ -1321,10 +1322,7 @@ export class TabsTitleControl extends TitleControl { private doLayoutBreadcrumbs(dimensions: ITitleControlDimensions): void { if (this.breadcrumbsControl && !this.breadcrumbsControl.isHidden()) { - const tabsScrollbar = assertIsDefined(this.tabsScrollbar); - this.breadcrumbsControl.layout(new Dimension(dimensions.container.width, BreadcrumbsControl.HEIGHT)); - tabsScrollbar.getDomNode().style.height = `${dimensions.container.height - BreadcrumbsControl.HEIGHT}px`; } } @@ -1503,7 +1501,7 @@ export class TabsTitleControl extends TitleControl { const tabsContainer = assertIsDefined(this.tabsContainer); const tab = tabsContainer.children[editorIndex]; if (tab) { - return [tabsContainer.children[editorIndex] as HTMLElement, editorIndex]; + return [tab as HTMLElement, editorIndex]; } } From d2893f949bebcdab1e6b321384e9b62b6cfb7cc1 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 9 Dec 2020 09:36:18 +0100 Subject: [PATCH 22/38] consolidate css rules --- .../browser/parts/editor/media/tabstitlecontrol.css | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css b/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css index a0da9f0c39e90..92163f46c301c 100644 --- a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css @@ -69,20 +69,11 @@ .monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container.multi-line > .monaco-scrollable-element > .tabs-container { height: auto; - flex-wrap: wrap; -} - -.monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container.multi-line > .monaco-scrollable-element > .tabs-container::after { - content: ""; /* Add a hidden space after the last tab in multi-line tabs */ - flex: 1 0 auto; -} - -.monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container.multi-line > .monaco-scrollable-element { - height: auto !important; /* For when breadcrumbs are enabled */ + flex-wrap: wrap; /* Enable wrapping via flex layout */ } .monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container.multi-line > .monaco-scrollable-element > .tabs-container > .tab { - flex-grow: 1; /* Grow the tabs to fill the row */ + flex-grow: 1; /* Grow the tabs to fill each row for a more homogeneous look when tabs wrap */ } /* Tabs Container */ From 927dba768ca408fb8f7d5a4a581943e9f622b2e2 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 9 Dec 2020 09:43:32 +0100 Subject: [PATCH 23/38] rename setting --- .../parts/editor/media/tabstitlecontrol.css | 20 ++++++------ .../browser/parts/editor/tabsTitleControl.ts | 32 +++++++++---------- .../browser/workbench.contribution.ts | 4 +-- src/vs/workbench/common/editor.ts | 2 +- 4 files changed, 27 insertions(+), 31 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css b/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css index 92163f46c301c..fd99dca976e3f 100644 --- a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css @@ -65,17 +65,6 @@ cursor: default; } -/* Title Container (multi-line wrapping) */ - -.monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container.multi-line > .monaco-scrollable-element > .tabs-container { - height: auto; - flex-wrap: wrap; /* Enable wrapping via flex layout */ -} - -.monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container.multi-line > .monaco-scrollable-element > .tabs-container > .tab { - flex-grow: 1; /* Grow the tabs to fill each row for a more homogeneous look when tabs wrap */ -} - /* Tabs Container */ .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container { @@ -92,6 +81,11 @@ display: none; /* Chrome + Safari: hide scrollbar */ } +.monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container.wrap > .monaco-scrollable-element > .tabs-container { + height: auto; + flex-wrap: wrap; /* wrapping enabled */ +} + /* Tab */ .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab { @@ -104,6 +98,10 @@ padding-left: 10px; } +.monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container.wrap > .monaco-scrollable-element > .tabs-container > .tab { + flex-grow: 1; /* Grow the tabs to fill each row for a more homogeneous look when tabs wrap */ +} + .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) */ diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index ac6d0f766e783..aae4901a30581 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -134,9 +134,7 @@ export class TabsTitleControl extends TitleControl { protected create(parent: HTMLElement): void { this.titleContainer = parent; - // Tabs and Actions Container: depending on the multi-line setting: - // off: single row with flex side-by-side - // on: multiple rows with flex side-by-side + // Tabs and Actions Container (are on a single row with flex side-by-side) this.tabsAndActionsContainer = document.createElement('div'); this.tabsAndActionsContainer.classList.add('tabs-and-actions-container'); this.titleContainer.appendChild(this.tabsAndActionsContainer); @@ -563,7 +561,7 @@ export class TabsTitleControl extends TitleControl { oldOptions.showIcons !== newOptions.showIcons || oldOptions.hasIcons !== newOptions.hasIcons || oldOptions.highlightModifiedTabs !== newOptions.highlightModifiedTabs || - oldOptions.multiLineTabs !== newOptions.multiLineTabs + oldOptions.wrapTabs !== newOptions.wrapTabs ) { this.redraw(); } @@ -1269,7 +1267,7 @@ export class TabsTitleControl extends TitleControl { // Multi-line: we need to ask `offsetHeight` to get // the real height of the title area with wrapping. let height: number; - if (this.accessor.partOptions.multiLineTabs && this.tabsAndActionsContainer?.classList.contains('multi-line')) { + if (this.accessor.partOptions.wrapTabs && this.tabsAndActionsContainer?.classList.contains('wrap')) { height = this.tabsAndActionsContainer.offsetHeight; } else { height = TabsTitleControl.TAB_HEIGHT; @@ -1295,10 +1293,10 @@ export class TabsTitleControl extends TitleControl { return Dimension.None; } - // Layout tabs synchronously if multi-line tabs are enabled so that + // Layout tabs synchronously if wrapping tabs are enabled so that // the correct height of the title area can be returned to the title // control - if (this.accessor.partOptions.multiLineTabs) { + if (this.accessor.partOptions.wrapTabs) { this.layoutSync(dimensions); const newHeight = this.getDimensions().height; @@ -1409,38 +1407,38 @@ export class TabsTitleControl extends TitleControl { tabsContainer.classList.remove('disable-sticky-tabs'); } - // Handle multi-line tabs according to setting: + // Handle wrapping tabs according to setting: // - enabled: only add class if tabs wrap // - disabled: remove class - if (this.accessor.partOptions.multiLineTabs) { + if (this.accessor.partOptions.wrapTabs) { // Tabs wrap multiline: remove wrapping if height exceeds available height - const tabsWrapMultiLine = tabsAndActionsContainer.classList.contains('multi-line'); + const tabsWrapMultiLine = tabsAndActionsContainer.classList.contains('wrap'); if (tabsWrapMultiLine) { if (tabsContainer.offsetHeight > dimensions.available.height) { - tabsAndActionsContainer.classList.remove('multi-line'); + tabsAndActionsContainer.classList.remove('wrap'); } } // Tabs do not wrap multiline: add wrapping if tabs exceed the tabs container width else { if (allTabsWidth > visibleTabsContainerWidth) { - tabsAndActionsContainer.classList.add('multi-line'); + tabsAndActionsContainer.classList.add('wrap'); } } // if we do not exceed the tabs container width, we cannot simply remove - // the multi-line class because by wrapping tabs, they reduce their size + // the wrap class because by wrapping tabs, they reduce their size // and we would otherwise constantly add and remove the class. As such // we need to check if the height of the tabs container is back to normal - // and then remove the multi-line class. - if (allTabsWidth === visibleTabsContainerWidth && tabsAndActionsContainer.classList.contains('multi-line')) { + // and then remove the wrap class. + if (allTabsWidth === visibleTabsContainerWidth && tabsAndActionsContainer.classList.contains('wrap')) { if (tabsContainer.offsetHeight === TabsTitleControl.TAB_HEIGHT) { - tabsAndActionsContainer.classList.remove('multi-line'); + tabsAndActionsContainer.classList.remove('wrap'); } } } else { - tabsAndActionsContainer.classList.remove('multi-line'); + tabsAndActionsContainer.classList.remove('wrap'); } let activeTabPosX: number | undefined; diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index 6970ac41e411f..d74c55c047d18 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -33,9 +33,9 @@ import { isStandalone } from 'vs/base/browser/browser'; 'description': nls.localize('showEditorTabs', "Controls whether opened editors should show in tabs or not."), 'default': true }, - 'workbench.editor.multiLineTabs': { + 'workbench.editor.wrapTabs': { 'type': 'boolean', - 'description': nls.localize('multiLineTabs', "Controls whether tabs should be wrapped to multi-line or not."), + 'description': nls.localize('wrapTabs', "Controls whether tabs should be wrapped over multiple lines when needed or a scrollbar appears."), 'default': false }, 'workbench.editor.scrollToSwitchTabs': { diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 5a4f03adde933..fe70ee5bab7ed 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -1265,7 +1265,7 @@ export interface IWorkbenchEditorConfiguration { interface IEditorPartConfiguration { showTabs?: boolean; - multiLineTabs?: boolean; + wrapTabs?: boolean; scrollToSwitchTabs?: boolean; highlightModifiedTabs?: boolean; tabCloseButton?: 'left' | 'right' | 'off'; From 6e8618ba83732ee00965959e301e95edec85e137 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 9 Dec 2020 09:48:02 +0100 Subject: [PATCH 24/38] simplify classes --- .../parts/editor/media/tabstitlecontrol.css | 12 ++++++------ .../browser/parts/editor/tabsTitleControl.ts | 18 +++++++++--------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css b/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css index fd99dca976e3f..e576690b26c7a 100644 --- a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css @@ -77,15 +77,15 @@ overflow: scroll !important; } -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container::-webkit-scrollbar { - display: none; /* Chrome + Safari: hide scrollbar */ -} - -.monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container.wrap > .monaco-scrollable-element > .tabs-container { +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container.wrap { height: auto; flex-wrap: wrap; /* wrapping enabled */ } +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container::-webkit-scrollbar { + display: none; /* Chrome + Safari: hide scrollbar */ +} + /* Tab */ .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab { @@ -98,7 +98,7 @@ padding-left: 10px; } -.monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container.wrap > .monaco-scrollable-element > .tabs-container > .tab { +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container.wrap > .tab { flex-grow: 1; /* Grow the tabs to fill each row for a more homogeneous look when tabs wrap */ } diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index aae4901a30581..af92c3fee0fc5 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -1267,8 +1267,8 @@ export class TabsTitleControl extends TitleControl { // Multi-line: we need to ask `offsetHeight` to get // the real height of the title area with wrapping. let height: number; - if (this.accessor.partOptions.wrapTabs && this.tabsAndActionsContainer?.classList.contains('wrap')) { - height = this.tabsAndActionsContainer.offsetHeight; + if (this.accessor.partOptions.wrapTabs && this.tabsContainer?.classList.contains('wrap')) { + height = this.tabsContainer.offsetHeight; } else { height = TabsTitleControl.TAB_HEIGHT; } @@ -1349,7 +1349,7 @@ export class TabsTitleControl extends TitleControl { } private doLayoutTabs(activeTab: HTMLElement, activeIndex: number, dimensions: ITitleControlDimensions): void { - const [tabsContainer, tabsScrollbar, tabsAndActionsContainer] = assertAllDefined(this.tabsContainer, this.tabsScrollbar, this.tabsAndActionsContainer); + const [tabsContainer, tabsScrollbar] = assertAllDefined(this.tabsContainer, this.tabsScrollbar); // // Synopsis @@ -1413,17 +1413,17 @@ export class TabsTitleControl extends TitleControl { if (this.accessor.partOptions.wrapTabs) { // Tabs wrap multiline: remove wrapping if height exceeds available height - const tabsWrapMultiLine = tabsAndActionsContainer.classList.contains('wrap'); + const tabsWrapMultiLine = tabsContainer.classList.contains('wrap'); if (tabsWrapMultiLine) { if (tabsContainer.offsetHeight > dimensions.available.height) { - tabsAndActionsContainer.classList.remove('wrap'); + tabsContainer.classList.remove('wrap'); } } // Tabs do not wrap multiline: add wrapping if tabs exceed the tabs container width else { if (allTabsWidth > visibleTabsContainerWidth) { - tabsAndActionsContainer.classList.add('wrap'); + tabsContainer.classList.add('wrap'); } } @@ -1432,13 +1432,13 @@ export class TabsTitleControl extends TitleControl { // and we would otherwise constantly add and remove the class. As such // we need to check if the height of the tabs container is back to normal // and then remove the wrap class. - if (allTabsWidth === visibleTabsContainerWidth && tabsAndActionsContainer.classList.contains('wrap')) { + if (allTabsWidth === visibleTabsContainerWidth && tabsContainer.classList.contains('wrap')) { if (tabsContainer.offsetHeight === TabsTitleControl.TAB_HEIGHT) { - tabsAndActionsContainer.classList.remove('wrap'); + tabsContainer.classList.remove('wrap'); } } } else { - tabsAndActionsContainer.classList.remove('wrap'); + tabsContainer.classList.remove('wrap'); } let activeTabPosX: number | undefined; From a88d9f161fe1b52515ee85e52359ddea55b25168 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 9 Dec 2020 10:27:45 +0100 Subject: [PATCH 25/38] streamline relayout --- .../browser/parts/editor/tabsTitleControl.ts | 53 +++++++++++++------ 1 file changed, 38 insertions(+), 15 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index af92c3fee0fc5..787e35bc4104f 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -90,11 +90,10 @@ export class TabsTitleControl extends TitleControl { private tabActionBars: ActionBar[] = []; private tabDisposables: IDisposable[] = []; - private dimensions: ITitleControlDimensions = { + private dimensions: ITitleControlDimensions & { used?: Dimension } = { container: Dimension.None, available: Dimension.None }; - private lastComputedHeight = this.getDimensions().height; private readonly layoutScheduled = this._register(new MutableDisposable()); private blockRevealActiveTab: boolean | undefined; @@ -1264,7 +1263,8 @@ export class TabsTitleControl extends TitleControl { } getDimensions(): IEditorGroupTitleDimensions { - // Multi-line: we need to ask `offsetHeight` to get + + // Wrap: we need to ask `offsetHeight` to get // the real height of the title area with wrapping. let height: number; if (this.accessor.partOptions.wrapTabs && this.tabsContainer?.classList.contains('wrap')) { @@ -1284,7 +1284,18 @@ export class TabsTitleControl extends TitleControl { } layout(dimensions: ITitleControlDimensions): Dimension { - this.dimensions = dimensions; + + // We only consider to trigger relayout to outer + // container if the dimensions we receive are + // not ours already (which indicates the layout + // method was called internally) + let triggerContainerRelayoutIfNeeded: boolean; + if (this.dimensions === dimensions) { + triggerContainerRelayoutIfNeeded = true; + } else { + triggerContainerRelayoutIfNeeded = false; + Object.assign(this.dimensions, dimensions); + } // We need an opened editor and dimensions to layout the title // Otherwise quickly return from the layout algorithm @@ -1297,25 +1308,37 @@ export class TabsTitleControl extends TitleControl { // the correct height of the title area can be returned to the title // control if (this.accessor.partOptions.wrapTabs) { - this.layoutSync(dimensions); - - const newHeight = this.getDimensions().height; - if (this.lastComputedHeight !== newHeight) { - this.lastComputedHeight = newHeight; - this.group.relayout(); - } + this.doLayoutSync(dimensions); } else { - this.layoutAsync(); + this.doLayoutAsync(); + } + + // Compute new dimension of tabs title control + // and remember it for future usages + const oldDimension = this.dimensions.used; + const newDimension = this.dimensions.used = new Dimension(dimensions.container.width, this.getDimensions().height); + + // If `layout` is called internally (not from the outer container) + // and tabs are wrapping, it is possible that the height of the + // control changes without the outer container being aware of + // In this case we need to `relayout` the outer container so that + // the editor is receiving the correct new dimensions + if ( + triggerContainerRelayoutIfNeeded && + this.accessor.partOptions.wrapTabs && + oldDimension && oldDimension.height !== newDimension.height + ) { + this.group.relayout(); } - return new Dimension(dimensions.container.width, this.getDimensions().height); + return newDimension; } - private layoutSync(dimensions: ITitleControlDimensions): void { + private doLayoutSync(dimensions: ITitleControlDimensions): void { this.doLayout(dimensions); } - private layoutAsync(): void { + private doLayoutAsync(): void { // The layout of tabs can be an expensive operation because we access DOM properties // that can result in the browser doing a full page layout to validate them. To buffer // this a little bit we try at least to schedule this work on the next animation frame. From 4bf1319429d94e0897667dc3535910f2dc0fe7f3 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 9 Dec 2020 10:37:39 +0100 Subject: [PATCH 26/38] wrapTabs => experimentalWrapTabs --- .../workbench/browser/parts/editor/tabsTitleControl.ts | 10 +++++----- src/vs/workbench/browser/workbench.contribution.ts | 4 ++-- src/vs/workbench/common/editor.ts | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index 787e35bc4104f..9329f8bd0f827 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -560,7 +560,7 @@ export class TabsTitleControl extends TitleControl { oldOptions.showIcons !== newOptions.showIcons || oldOptions.hasIcons !== newOptions.hasIcons || oldOptions.highlightModifiedTabs !== newOptions.highlightModifiedTabs || - oldOptions.wrapTabs !== newOptions.wrapTabs + oldOptions.experimentalWrapTabs !== newOptions.experimentalWrapTabs ) { this.redraw(); } @@ -1267,7 +1267,7 @@ export class TabsTitleControl extends TitleControl { // Wrap: we need to ask `offsetHeight` to get // the real height of the title area with wrapping. let height: number; - if (this.accessor.partOptions.wrapTabs && this.tabsContainer?.classList.contains('wrap')) { + if (this.accessor.partOptions.experimentalWrapTabs && this.tabsContainer?.classList.contains('wrap')) { height = this.tabsContainer.offsetHeight; } else { height = TabsTitleControl.TAB_HEIGHT; @@ -1307,7 +1307,7 @@ export class TabsTitleControl extends TitleControl { // Layout tabs synchronously if wrapping tabs are enabled so that // the correct height of the title area can be returned to the title // control - if (this.accessor.partOptions.wrapTabs) { + if (this.accessor.partOptions.experimentalWrapTabs) { this.doLayoutSync(dimensions); } else { this.doLayoutAsync(); @@ -1325,7 +1325,7 @@ export class TabsTitleControl extends TitleControl { // the editor is receiving the correct new dimensions if ( triggerContainerRelayoutIfNeeded && - this.accessor.partOptions.wrapTabs && + this.accessor.partOptions.experimentalWrapTabs && oldDimension && oldDimension.height !== newDimension.height ) { this.group.relayout(); @@ -1433,7 +1433,7 @@ export class TabsTitleControl extends TitleControl { // Handle wrapping tabs according to setting: // - enabled: only add class if tabs wrap // - disabled: remove class - if (this.accessor.partOptions.wrapTabs) { + if (this.accessor.partOptions.experimentalWrapTabs) { // Tabs wrap multiline: remove wrapping if height exceeds available height const tabsWrapMultiLine = tabsContainer.classList.contains('wrap'); diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index d74c55c047d18..27c8fa7cec200 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -33,9 +33,9 @@ import { isStandalone } from 'vs/base/browser/browser'; 'description': nls.localize('showEditorTabs', "Controls whether opened editors should show in tabs or not."), 'default': true }, - 'workbench.editor.wrapTabs': { + 'workbench.editor.experimentalWrapTabs': { 'type': 'boolean', - 'description': nls.localize('wrapTabs', "Controls whether tabs should be wrapped over multiple lines when needed or a scrollbar appears."), + 'description': nls.localize('experimentalWrapTabs', "Experimental: Controls whether tabs should be wrapped over multiple lines when needed or a scrollbar appears."), 'default': false }, 'workbench.editor.scrollToSwitchTabs': { diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index fe70ee5bab7ed..c8b2fd7c04758 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -1265,7 +1265,7 @@ export interface IWorkbenchEditorConfiguration { interface IEditorPartConfiguration { showTabs?: boolean; - wrapTabs?: boolean; + experimentalWrapTabs?: boolean; scrollToSwitchTabs?: boolean; highlightModifiedTabs?: boolean; tabCloseButton?: 'left' | 'right' | 'off'; From ba683f1c65784c06f8b968220c2dee3e748da019 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 9 Dec 2020 10:51:34 +0100 Subject: [PATCH 27/38] tweak layout --- .../browser/parts/editor/tabsTitleControl.ts | 31 +++++++++---------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index 9329f8bd0f827..2031248b0b114 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -1431,34 +1431,31 @@ export class TabsTitleControl extends TitleControl { } // Handle wrapping tabs according to setting: - // - enabled: only add class if tabs wrap + // - enabled: only add class if tabs wrap and don't exceed available height // - disabled: remove class if (this.accessor.partOptions.experimentalWrapTabs) { + let tabsWrapMultiLine = tabsContainer.classList.contains('wrap'); - // Tabs wrap multiline: remove wrapping if height exceeds available height - const tabsWrapMultiLine = tabsContainer.classList.contains('wrap'); - if (tabsWrapMultiLine) { - if (tabsContainer.offsetHeight > dimensions.available.height) { - tabsContainer.classList.remove('wrap'); - } + // Tabs do not wrap multiline: add wrapping if tabs exceed the tabs container width + if (!tabsWrapMultiLine && allTabsWidth > visibleTabsContainerWidth) { + tabsContainer.classList.add('wrap'); + tabsWrapMultiLine = true; } - // Tabs do not wrap multiline: add wrapping if tabs exceed the tabs container width - else { - if (allTabsWidth > visibleTabsContainerWidth) { - tabsContainer.classList.add('wrap'); - } + // Tabs wrap multiline: remove wrapping if height exceeds available height + if (tabsWrapMultiLine && tabsContainer.offsetHeight > dimensions.available.height) { + tabsContainer.classList.remove('wrap'); + tabsWrapMultiLine = false; } - // if we do not exceed the tabs container width, we cannot simply remove + // If we do not exceed the tabs container width, we cannot simply remove // the wrap class because by wrapping tabs, they reduce their size // and we would otherwise constantly add and remove the class. As such // we need to check if the height of the tabs container is back to normal // and then remove the wrap class. - if (allTabsWidth === visibleTabsContainerWidth && tabsContainer.classList.contains('wrap')) { - if (tabsContainer.offsetHeight === TabsTitleControl.TAB_HEIGHT) { - tabsContainer.classList.remove('wrap'); - } + if (allTabsWidth === visibleTabsContainerWidth && tabsWrapMultiLine && tabsContainer.offsetHeight === TabsTitleControl.TAB_HEIGHT) { + tabsContainer.classList.remove('wrap'); + tabsWrapMultiLine = false; } } else { tabsContainer.classList.remove('wrap'); From a1d21eb97d15d0a51a76f504c900ed5a68bf0e03 Mon Sep 17 00:00:00 2001 From: Jonathan Mannancheril <32284796+SneakyFish5@users.noreply.github.com> Date: Mon, 14 Dec 2020 23:11:04 -0600 Subject: [PATCH 28/38] Limit wrapped tabs to 3 rows --- src/vs/workbench/browser/parts/editor/tabsTitleControl.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index 2031248b0b114..760d4350cde83 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -72,6 +72,7 @@ export class TabsTitleControl extends TitleControl { }; private static readonly TAB_HEIGHT = 35; + private static readonly MAX_WRAPPED_HEIGHT = TabsTitleControl.TAB_HEIGHT * 3; private static readonly MOUSE_WHEEL_EVENT_THRESHOLD = 150; private static readonly MOUSE_WHEEL_DISTANCE_THRESHOLD = 1.5; @@ -1437,13 +1438,15 @@ export class TabsTitleControl extends TitleControl { let tabsWrapMultiLine = tabsContainer.classList.contains('wrap'); // Tabs do not wrap multiline: add wrapping if tabs exceed the tabs container width - if (!tabsWrapMultiLine && allTabsWidth > visibleTabsContainerWidth) { + // and the height of the tabs container does not exceed the maximum + if (!tabsWrapMultiLine && allTabsWidth > visibleTabsContainerWidth && (tabsContainer.offsetHeight <= TabsTitleControl.MAX_WRAPPED_HEIGHT)) { tabsContainer.classList.add('wrap'); tabsWrapMultiLine = true; } // Tabs wrap multiline: remove wrapping if height exceeds available height - if (tabsWrapMultiLine && tabsContainer.offsetHeight > dimensions.available.height) { + // or the maximum allowed height + if (tabsWrapMultiLine && (tabsContainer.offsetHeight > dimensions.available.height || tabsContainer.offsetHeight > TabsTitleControl.MAX_WRAPPED_HEIGHT)) { tabsContainer.classList.remove('wrap'); tabsWrapMultiLine = false; } From 11ea1797cc918371eb40b6e32c56238ba9d9ef14 Mon Sep 17 00:00:00 2001 From: Jonathan Mannancheril <32284796+SneakyFish5@users.noreply.github.com> Date: Wed, 16 Dec 2020 09:18:20 -0600 Subject: [PATCH 29/38] Only use flex-grow for `tabSizing: fit` --- .../browser/parts/editor/media/tabstitlecontrol.css | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css b/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css index e576690b26c7a..c867f84dfab91 100644 --- a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css @@ -98,10 +98,6 @@ padding-left: 10px; } -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container.wrap > .tab { - flex-grow: 1; /* Grow the tabs to fill each row for a more homogeneous look when tabs wrap */ -} - .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) */ @@ -114,6 +110,10 @@ flex-shrink: 0; } +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container.wrap > .tab.sizing-fit { + flex-grow: 1; /* Grow the tabs to fill each row for a more homogeneous look when tabs wrap */ +} + .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink { min-width: 80px; flex-basis: 0; /* all tabs are even */ From fe75f6cfd8a61ad68c2deb6e8a449551005d8f60 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 18 Dec 2020 09:13:02 +0100 Subject: [PATCH 30/38] fix scrollbar reveal to work properly --- .../browser/parts/editor/tabsTitleControl.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index 936c722d422a7..2e0aab8d0a9ac 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -1431,6 +1431,7 @@ export class TabsTitleControl extends TitleControl { // - disabled: remove class if (this.accessor.partOptions.experimentalWrapTabs) { let tabsWrapMultiLine = tabsContainer.classList.contains('wrap'); + let updateScrollbar = false; // Tabs do not wrap multiline: add wrapping if tabs exceed the tabs container width // and the height of the tabs container does not exceed the maximum @@ -1444,6 +1445,7 @@ export class TabsTitleControl extends TitleControl { if (tabsWrapMultiLine && (tabsContainer.offsetHeight > dimensions.available.height || tabsContainer.offsetHeight > TabsTitleControl.MAX_WRAPPED_HEIGHT)) { tabsContainer.classList.remove('wrap'); tabsWrapMultiLine = false; + updateScrollbar = true; } // If we do not exceed the tabs container width, we cannot simply remove @@ -1451,9 +1453,18 @@ export class TabsTitleControl extends TitleControl { // and we would otherwise constantly add and remove the class. As such // we need to check if the height of the tabs container is back to normal // and then remove the wrap class. - if (allTabsWidth === visibleTabsContainerWidth && tabsWrapMultiLine && tabsContainer.offsetHeight === TabsTitleControl.TAB_HEIGHT) { + if (tabsWrapMultiLine && allTabsWidth === visibleTabsContainerWidth && tabsContainer.offsetHeight === TabsTitleControl.TAB_HEIGHT) { tabsContainer.classList.remove('wrap'); tabsWrapMultiLine = false; + updateScrollbar = true; + } + + // When tabs change from wrapping back to normal, we need to indicate this + // to the scrollbar so that revealing the active tab functions properly. + if (updateScrollbar) { + tabsScrollbar.setScrollPosition({ + scrollLeft: tabsContainer.scrollLeft + }); } } else { tabsContainer.classList.remove('wrap'); From e69e7c31c503a902fa6a0f85db6753687351c08a Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 18 Dec 2020 11:07:52 +0100 Subject: [PATCH 31/38] tabs - get rid of sync layout --- .../browser/parts/editor/tabsTitleControl.ts | 66 ++++++------------- 1 file changed, 20 insertions(+), 46 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index 2e0aab8d0a9ac..804d6d0a5e4ab 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -1288,53 +1288,9 @@ export class TabsTitleControl extends TitleControl { layout(dimensions: ITitleControlDimensions): Dimension { - // We only consider to trigger relayout to outer - // container if the dimensions we receive are - // not ours already (which indicates the layout - // method was called internally) - let triggerContainerRelayoutIfNeeded: boolean; - if (this.dimensions === dimensions) { - triggerContainerRelayoutIfNeeded = true; - } else { - triggerContainerRelayoutIfNeeded = false; - Object.assign(this.dimensions, dimensions); - } - - // Layout tabs synchronously if wrapping tabs are enabled so that - // the correct height of the title area can be returned to the title - // control - if (this.accessor.partOptions.experimentalWrapTabs) { - this.doLayoutSync(dimensions); - } else { - this.doLayoutAsync(); - } - - // Compute new dimension of tabs title control - // and remember it for future usages - const oldDimension = this.dimensions.used; - const newDimension = this.dimensions.used = new Dimension(dimensions.container.width, this.getDimensions().height); - - // If `layout` is called internally (not from the outer container) - // and tabs are wrapping, it is possible that the height of the - // control changes without the outer container being aware of - // In this case we need to `relayout` the outer container so that - // the editor is receiving the correct new dimensions - if ( - triggerContainerRelayoutIfNeeded && - this.accessor.partOptions.experimentalWrapTabs && - oldDimension && oldDimension.height !== newDimension.height - ) { - this.group.relayout(); - } + // Remember dimensions that we get + Object.assign(this.dimensions, dimensions); - return newDimension; - } - - private doLayoutSync(dimensions: ITitleControlDimensions): void { - this.doLayout(dimensions); - } - - private doLayoutAsync(): void { // The layout of tabs can be an expensive operation because we access DOM properties // that can result in the browser doing a full page layout to validate them. To buffer // this a little bit we try at least to schedule this work on the next animation frame. @@ -1342,9 +1298,27 @@ export class TabsTitleControl extends TitleControl { this.layoutScheduled.value = scheduleAtNextAnimationFrame(() => { this.doLayout(this.dimensions); + // Compute new dimension of tabs title control + // and remember it for future usages + const oldDimension = this.dimensions.used; + const newDimension = this.dimensions.used = new Dimension(dimensions.container.width, this.getDimensions().height); + + // In case the height of the title control changed from before + // (currently only possible if tabs are set to wrap), we need + // to signal this to the outside via a `relayout` call so that + // e.g. the editor control can be adjusted accordingly. + if ( + this.accessor.partOptions.experimentalWrapTabs && + oldDimension && oldDimension.height !== newDimension.height + ) { + this.group.relayout(); + } + this.layoutScheduled.clear(); }); } + + return new Dimension(dimensions.container.width, this.getDimensions().height); } private doLayout(dimensions: ITitleControlDimensions): void { From 4585b7042fbd87aa461c3de8201df537e54bbc09 Mon Sep 17 00:00:00 2001 From: Jonathan Mannancheril <32284796+SneakyFish5@users.noreply.github.com> Date: Sun, 20 Dec 2020 02:29:32 -0600 Subject: [PATCH 32/38] WIP: Move editor actions to the bottom right --- .../parts/editor/media/tabstitlecontrol.css | 6 +++++ .../browser/parts/editor/tabsTitleControl.ts | 27 ++++++++++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css b/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css index c867f84dfab91..414116b656538 100644 --- a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css @@ -375,6 +375,12 @@ height: 35px; } +.monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions.wrap { + /* When tabs are wrapped, position the editor actions at the end of the very last row */ + position: absolute; + right: 0; +} + /* Breadcrumbs */ .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control { diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index 804d6d0a5e4ab..ff2eef7927769 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -1342,7 +1342,7 @@ export class TabsTitleControl extends TitleControl { } private doLayoutTabs(activeTab: HTMLElement, activeIndex: number, dimensions: ITitleControlDimensions): void { - const [tabsContainer, tabsScrollbar] = assertAllDefined(this.tabsContainer, this.tabsScrollbar); + const [tabsContainer, tabsScrollbar, editorToolbarContainer] = assertAllDefined(this.tabsContainer, this.tabsScrollbar, this.editorToolbarContainer); // // Synopsis @@ -1406,11 +1406,13 @@ export class TabsTitleControl extends TitleControl { if (this.accessor.partOptions.experimentalWrapTabs) { let tabsWrapMultiLine = tabsContainer.classList.contains('wrap'); let updateScrollbar = false; + let updateEditorActions = false; // Tabs do not wrap multiline: add wrapping if tabs exceed the tabs container width // and the height of the tabs container does not exceed the maximum if (!tabsWrapMultiLine && allTabsWidth > visibleTabsContainerWidth && (tabsContainer.offsetHeight <= TabsTitleControl.MAX_WRAPPED_HEIGHT)) { tabsContainer.classList.add('wrap'); + editorToolbarContainer.classList.add('wrap'); tabsWrapMultiLine = true; } @@ -1418,8 +1420,10 @@ export class TabsTitleControl extends TitleControl { // or the maximum allowed height if (tabsWrapMultiLine && (tabsContainer.offsetHeight > dimensions.available.height || tabsContainer.offsetHeight > TabsTitleControl.MAX_WRAPPED_HEIGHT)) { tabsContainer.classList.remove('wrap'); + editorToolbarContainer.classList.remove('wrap'); tabsWrapMultiLine = false; updateScrollbar = true; + updateEditorActions = true; } // If we do not exceed the tabs container width, we cannot simply remove @@ -1429,10 +1433,30 @@ export class TabsTitleControl extends TitleControl { // and then remove the wrap class. if (tabsWrapMultiLine && allTabsWidth === visibleTabsContainerWidth && tabsContainer.offsetHeight === TabsTitleControl.TAB_HEIGHT) { tabsContainer.classList.remove('wrap'); + editorToolbarContainer.classList.remove('wrap'); tabsWrapMultiLine = false; updateScrollbar = true; } + // Move editor actions to the bottom right of the last row so that + // the rows above can fill the entire width + if (tabsWrapMultiLine || updateEditorActions) { + + // Add a right margin to the last tab equal to the width of the editor actions + // so that the editor actions always stay on the bottom right of the last row. + // Also make sure to remove the right margin from the previous sibling tab that + // it had been applied so that no empty space is left over. + (tabsContainer?.lastChild?.previousSibling as HTMLElement).style.marginRight = ''; + (tabsContainer?.lastChild as HTMLElement).style.marginRight = `${editorToolbarContainer.offsetWidth}px`; + + // Adjust the position of the editor actions if breadcrumbs are visibile + if (this.breadcrumbsControl && !this.breadcrumbsControl.isHidden()) { + editorToolbarContainer.style.bottom = `${BreadcrumbsControl.HEIGHT}px`; + } else { + editorToolbarContainer.style.bottom = '0px'; + } + } + // When tabs change from wrapping back to normal, we need to indicate this // to the scrollbar so that revealing the active tab functions properly. if (updateScrollbar) { @@ -1442,6 +1466,7 @@ export class TabsTitleControl extends TitleControl { } } else { tabsContainer.classList.remove('wrap'); + editorToolbarContainer.classList.remove('wrap'); } let activeTabPosX: number | undefined; From b4c809de8090882e9f9ed2363420676b3d0beeec Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sun, 27 Dec 2020 11:22:54 +0100 Subject: [PATCH 33/38] some tweaks --- .../parts/editor/media/tabstitlecontrol.css | 15 +++----- .../browser/parts/editor/tabsTitleControl.ts | 37 +++++++------------ 2 files changed, 19 insertions(+), 33 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css b/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css index 414116b656538..c6a79b8f53893 100644 --- a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css @@ -38,10 +38,7 @@ .monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container { display: flex; -} - -.monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container.tabs-border-bottom { - position: relative; + position: relative; /* Position tabs border bottom or editor actions (when tabs wrap) relative to this container */ } .monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container.tabs-border-bottom::after { @@ -77,7 +74,7 @@ overflow: scroll !important; } -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container.wrap { +.monaco-workbench .part.editor > .content .editor-group-container > .title > .tabs-and-actions-container.wrapping .tabs-container { height: auto; flex-wrap: wrap; /* wrapping enabled */ } @@ -110,7 +107,7 @@ flex-shrink: 0; } -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container.wrap > .tab.sizing-fit { +.monaco-workbench .part.editor > .content .editor-group-container > .title > .tabs-and-actions-container.wrapping .tabs-container > .tab.sizing-fit { flex-grow: 1; /* Grow the tabs to fill each row for a more homogeneous look when tabs wrap */ } @@ -375,9 +372,9 @@ height: 35px; } -.monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions.wrap { - /* When tabs are wrapped, position the editor actions at the end of the very last row */ - position: absolute; +.monaco-workbench .part.editor > .content .editor-group-container > .title > .tabs-and-actions-container.wrapping .editor-actions { + position: absolute; /* When tabs are wrapped, position the editor actions at the end of the very last row */ + bottom: 0; right: 0; } diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index ff2eef7927769..cb233d0b778a0 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -73,7 +73,6 @@ export class TabsTitleControl extends TitleControl { }; private static readonly TAB_HEIGHT = 35; - private static readonly MAX_WRAPPED_HEIGHT = TabsTitleControl.TAB_HEIGHT * 3; private static readonly MOUSE_WHEEL_EVENT_THRESHOLD = 150; private static readonly MOUSE_WHEEL_DISTANCE_THRESHOLD = 1.5; @@ -1270,8 +1269,8 @@ export class TabsTitleControl extends TitleControl { // Wrap: we need to ask `offsetHeight` to get // the real height of the title area with wrapping. let height: number; - if (this.accessor.partOptions.experimentalWrapTabs && this.tabsContainer?.classList.contains('wrap')) { - height = this.tabsContainer.offsetHeight; + if (this.accessor.partOptions.experimentalWrapTabs && this.tabsAndActionsContainer?.classList.contains('wrapping')) { + height = this.tabsAndActionsContainer.offsetHeight; } else { height = TabsTitleControl.TAB_HEIGHT; } @@ -1342,7 +1341,7 @@ export class TabsTitleControl extends TitleControl { } private doLayoutTabs(activeTab: HTMLElement, activeIndex: number, dimensions: ITitleControlDimensions): void { - const [tabsContainer, tabsScrollbar, editorToolbarContainer] = assertAllDefined(this.tabsContainer, this.tabsScrollbar, this.editorToolbarContainer); + const [tabsAndActionsContainer, tabsContainer, tabsScrollbar, editorToolbarContainer] = assertAllDefined(this.tabsAndActionsContainer, this.tabsContainer, this.tabsScrollbar, this.editorToolbarContainer); // // Synopsis @@ -1404,23 +1403,22 @@ export class TabsTitleControl extends TitleControl { // - enabled: only add class if tabs wrap and don't exceed available height // - disabled: remove class if (this.accessor.partOptions.experimentalWrapTabs) { - let tabsWrapMultiLine = tabsContainer.classList.contains('wrap'); + let tabsWrapMultiLine = tabsAndActionsContainer.classList.contains('wrapping'); + let updateScrollbar = false; let updateEditorActions = false; // Tabs do not wrap multiline: add wrapping if tabs exceed the tabs container width // and the height of the tabs container does not exceed the maximum - if (!tabsWrapMultiLine && allTabsWidth > visibleTabsContainerWidth && (tabsContainer.offsetHeight <= TabsTitleControl.MAX_WRAPPED_HEIGHT)) { - tabsContainer.classList.add('wrap'); - editorToolbarContainer.classList.add('wrap'); + if (!tabsWrapMultiLine && allTabsWidth > visibleTabsContainerWidth) { + tabsAndActionsContainer.classList.add('wrapping'); tabsWrapMultiLine = true; } // Tabs wrap multiline: remove wrapping if height exceeds available height // or the maximum allowed height - if (tabsWrapMultiLine && (tabsContainer.offsetHeight > dimensions.available.height || tabsContainer.offsetHeight > TabsTitleControl.MAX_WRAPPED_HEIGHT)) { - tabsContainer.classList.remove('wrap'); - editorToolbarContainer.classList.remove('wrap'); + if (tabsWrapMultiLine && tabsContainer.offsetHeight > dimensions.available.height) { + tabsAndActionsContainer.classList.remove('wrapping'); tabsWrapMultiLine = false; updateScrollbar = true; updateEditorActions = true; @@ -1432,8 +1430,7 @@ export class TabsTitleControl extends TitleControl { // we need to check if the height of the tabs container is back to normal // and then remove the wrap class. if (tabsWrapMultiLine && allTabsWidth === visibleTabsContainerWidth && tabsContainer.offsetHeight === TabsTitleControl.TAB_HEIGHT) { - tabsContainer.classList.remove('wrap'); - editorToolbarContainer.classList.remove('wrap'); + tabsAndActionsContainer.classList.remove('wrapping'); tabsWrapMultiLine = false; updateScrollbar = true; } @@ -1446,15 +1443,8 @@ export class TabsTitleControl extends TitleControl { // so that the editor actions always stay on the bottom right of the last row. // Also make sure to remove the right margin from the previous sibling tab that // it had been applied so that no empty space is left over. - (tabsContainer?.lastChild?.previousSibling as HTMLElement).style.marginRight = ''; - (tabsContainer?.lastChild as HTMLElement).style.marginRight = `${editorToolbarContainer.offsetWidth}px`; - - // Adjust the position of the editor actions if breadcrumbs are visibile - if (this.breadcrumbsControl && !this.breadcrumbsControl.isHidden()) { - editorToolbarContainer.style.bottom = `${BreadcrumbsControl.HEIGHT}px`; - } else { - editorToolbarContainer.style.bottom = '0px'; - } + (tabsContainer.lastChild?.previousSibling as HTMLElement).style.marginRight = ''; + (tabsContainer.lastChild as HTMLElement).style.marginRight = `${editorToolbarContainer.offsetWidth}px`; } // When tabs change from wrapping back to normal, we need to indicate this @@ -1465,8 +1455,7 @@ export class TabsTitleControl extends TitleControl { }); } } else { - tabsContainer.classList.remove('wrap'); - editorToolbarContainer.classList.remove('wrap'); + tabsAndActionsContainer.classList.remove('wrapping'); } let activeTabPosX: number | undefined; From a53fe3c3414f065a423a7607dcaa70b22d5b5861 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sun, 27 Dec 2020 11:50:29 +0100 Subject: [PATCH 34/38] introduce css variable for margin-right trick --- .../parts/editor/media/tabstitlecontrol.css | 20 +++++++++++-------- .../browser/parts/editor/tabsTitleControl.ts | 18 ++++------------- 2 files changed, 16 insertions(+), 22 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css b/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css index c6a79b8f53893..3a0454baecc87 100644 --- a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css @@ -38,7 +38,7 @@ .monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container { display: flex; - position: relative; /* Position tabs border bottom or editor actions (when tabs wrap) relative to this container */ + position: relative; /* position tabs border bottom or editor actions (when tabs wrap) relative to this container */ } .monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container.tabs-border-bottom::after { @@ -95,6 +95,10 @@ padding-left: 10px; } +.monaco-workbench .part.editor > .content .editor-group-container > .title > .tabs-and-actions-container.wrapping .tabs-container > .tab:last-child { + margin-right: var(--last-tab-margin-right); /* when tabs wrap, we need a margin away from the absolute positioned editor actions */ +} + .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) */ @@ -108,7 +112,7 @@ } .monaco-workbench .part.editor > .content .editor-group-container > .title > .tabs-and-actions-container.wrapping .tabs-container > .tab.sizing-fit { - flex-grow: 1; /* Grow the tabs to fill each row for a more homogeneous look when tabs wrap */ + flex-grow: 1; /* grow the tabs to fill each row for a more homogeneous look when tabs wrap */ } .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink { @@ -155,9 +159,7 @@ .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container.disable-sticky-tabs > .tab.sizing-shrink.sticky-compact, .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container.disable-sticky-tabs > .tab.sizing-fit.sticky-shrink, .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container.disable-sticky-tabs > .tab.sizing-shrink.sticky-shrink { - - /** Disable sticky positions for sticky compact/shrink tabs if the available space is too little */ - position: static; + position: static; /** disable sticky positions for sticky compact/shrink tabs if the available space is too little */ } .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.tab-actions-left .action-label { @@ -169,7 +171,7 @@ 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) */ + 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.tab-actions-left { @@ -282,7 +284,7 @@ .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 tab actions 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.tab-actions-right.sizing-shrink > .tab-actions, @@ -373,7 +375,9 @@ } .monaco-workbench .part.editor > .content .editor-group-container > .title > .tabs-and-actions-container.wrapping .editor-actions { - position: absolute; /* When tabs are wrapped, position the editor actions at the end of the very last row */ + + /* When tabs are wrapped, position the editor actions at the end of the very last row */ + position: absolute; bottom: 0; right: 0; } diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index cb233d0b778a0..1738b2a892a88 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -1404,9 +1404,7 @@ export class TabsTitleControl extends TitleControl { // - disabled: remove class if (this.accessor.partOptions.experimentalWrapTabs) { let tabsWrapMultiLine = tabsAndActionsContainer.classList.contains('wrapping'); - let updateScrollbar = false; - let updateEditorActions = false; // Tabs do not wrap multiline: add wrapping if tabs exceed the tabs container width // and the height of the tabs container does not exceed the maximum @@ -1421,7 +1419,6 @@ export class TabsTitleControl extends TitleControl { tabsAndActionsContainer.classList.remove('wrapping'); tabsWrapMultiLine = false; updateScrollbar = true; - updateEditorActions = true; } // If we do not exceed the tabs container width, we cannot simply remove @@ -1435,17 +1432,10 @@ export class TabsTitleControl extends TitleControl { updateScrollbar = true; } - // Move editor actions to the bottom right of the last row so that - // the rows above can fill the entire width - if (tabsWrapMultiLine || updateEditorActions) { - - // Add a right margin to the last tab equal to the width of the editor actions - // so that the editor actions always stay on the bottom right of the last row. - // Also make sure to remove the right margin from the previous sibling tab that - // it had been applied so that no empty space is left over. - (tabsContainer.lastChild?.previousSibling as HTMLElement).style.marginRight = ''; - (tabsContainer.lastChild as HTMLElement).style.marginRight = `${editorToolbarContainer.offsetWidth}px`; - } + // Update `last-tab-margin-right` CSS variable to account for the absolute + // positioned editor actions container when tabs wrap. The margin needs to + // be the width of the editor actions container to avoid screen cheese. + tabsContainer.style.setProperty('--last-tab-margin-right', tabsWrapMultiLine ? `${editorToolbarContainer.offsetWidth}px` : '0'); // When tabs change from wrapping back to normal, we need to indicate this // to the scrollbar so that revealing the active tab functions properly. From 91c6333fcdce8833c96ce838ad845858d3f770c3 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sun, 27 Dec 2020 13:10:50 +0100 Subject: [PATCH 35/38] add border to separate tabs when wrapping --- .../browser/parts/editor/tabsTitleControl.ts | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index 1738b2a892a88..f489813ebc7b7 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -1634,11 +1634,23 @@ registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) = // Add border between tabs and breadcrumbs in high contrast mode. if (theme.type === ColorScheme.HIGH_CONTRAST) { const borderColor = (theme.getColor(TAB_BORDER) || theme.getColor(contrastBorder)); + if (borderColor) { + collector.addRule(` + .monaco-workbench .part.editor > .content .editor-group-container > .title > .tabs-and-actions-container { + border-bottom: 1px solid ${borderColor}; + } + `); + } + } + + // Add bottom border to tabs when wrapping + const borderColor = theme.getColor(TAB_BORDER); + if (borderColor) { collector.addRule(` - .monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container { - border-bottom: 1px solid ${borderColor}; - } - `); + .monaco-workbench .part.editor > .content .editor-group-container > .title > .tabs-and-actions-container.wrapping .tabs-container > .tab { + border-bottom: 1px solid ${borderColor}; + } + `); } // Styling with Outline color (e.g. high contrast theme) From 46bdb34dd33678bc73358a3d04f5fe0e8563bfcc Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sun, 27 Dec 2020 13:19:57 +0100 Subject: [PATCH 36/38] :lipstick: --- .../workbench/browser/parts/editor/media/tabstitlecontrol.css | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css b/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css index 3a0454baecc87..47cfaf45b747b 100644 --- a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css @@ -75,8 +75,10 @@ } .monaco-workbench .part.editor > .content .editor-group-container > .title > .tabs-and-actions-container.wrapping .tabs-container { + + /* Enable wrapping via flex layout and dynamic height */ height: auto; - flex-wrap: wrap; /* wrapping enabled */ + flex-wrap: wrap; } .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container::-webkit-scrollbar { From ff98891a607056dc4895e91f9c6aeb606c45db52 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 4 Jan 2021 09:43:35 +0100 Subject: [PATCH 37/38] rename setting --- src/vs/workbench/browser/parts/editor/tabsTitleControl.ts | 8 ++++---- src/vs/workbench/browser/workbench.contribution.ts | 4 ++-- src/vs/workbench/common/editor.ts | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index a3182acaf421a..86f7446c72082 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -571,7 +571,7 @@ export class TabsTitleControl extends TitleControl { oldOptions.showIcons !== newOptions.showIcons || oldOptions.hasIcons !== newOptions.hasIcons || oldOptions.highlightModifiedTabs !== newOptions.highlightModifiedTabs || - oldOptions.experimentalWrapTabs !== newOptions.experimentalWrapTabs || + oldOptions.wrapTabs !== newOptions.wrapTabs || !equals(oldOptions.decorations, newOptions.decorations) ) { this.redraw(); @@ -1279,7 +1279,7 @@ export class TabsTitleControl extends TitleControl { // Wrap: we need to ask `offsetHeight` to get // the real height of the title area with wrapping. let height: number; - if (this.accessor.partOptions.experimentalWrapTabs && this.tabsAndActionsContainer?.classList.contains('wrapping')) { + if (this.accessor.partOptions.wrapTabs && this.tabsAndActionsContainer?.classList.contains('wrapping')) { height = this.tabsAndActionsContainer.offsetHeight; } else { height = TabsTitleControl.TAB_HEIGHT; @@ -1317,7 +1317,7 @@ export class TabsTitleControl extends TitleControl { // to signal this to the outside via a `relayout` call so that // e.g. the editor control can be adjusted accordingly. if ( - this.accessor.partOptions.experimentalWrapTabs && + this.accessor.partOptions.wrapTabs && oldDimension && oldDimension.height !== newDimension.height ) { this.group.relayout(); @@ -1412,7 +1412,7 @@ export class TabsTitleControl extends TitleControl { // Handle wrapping tabs according to setting: // - enabled: only add class if tabs wrap and don't exceed available height // - disabled: remove class - if (this.accessor.partOptions.experimentalWrapTabs) { + if (this.accessor.partOptions.wrapTabs) { let tabsWrapMultiLine = tabsAndActionsContainer.classList.contains('wrapping'); let updateScrollbar = false; diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index 258b52bdc6def..b47ccfbe06afb 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -33,9 +33,9 @@ import { isStandalone } from 'vs/base/browser/browser'; 'description': nls.localize('showEditorTabs', "Controls whether opened editors should show in tabs or not."), 'default': true }, - 'workbench.editor.experimentalWrapTabs': { + 'workbench.editor.wrapTabs': { 'type': 'boolean', - 'description': nls.localize('experimentalWrapTabs', "Experimental: Controls whether tabs should be wrapped over multiple lines when needed or a scrollbar appears."), + 'description': nls.localize('wrapTabs', "Controls whether tabs should be wrapped over multiple lines when exceeding available space or wether a scrollbar should appear instead."), 'default': false }, 'workbench.editor.scrollToSwitchTabs': { diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 72942ebc35d06..99bde65fbb8e1 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -1265,7 +1265,7 @@ export interface IWorkbenchEditorConfiguration { interface IEditorPartConfiguration { showTabs?: boolean; - experimentalWrapTabs?: boolean; + wrapTabs?: boolean; scrollToSwitchTabs?: boolean; highlightModifiedTabs?: boolean; tabCloseButton?: 'left' | 'right' | 'off'; From e6ee5f61f0df3d4dd1ec2dc9ee7d10b03895c51d Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 4 Jan 2021 09:50:24 +0100 Subject: [PATCH 38/38] :lipstick: layout method --- .../browser/parts/editor/tabsTitleControl.ts | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index 86f7446c72082..d7cfb96a48836 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -1275,10 +1275,10 @@ export class TabsTitleControl extends TitleControl { } getDimensions(): IEditorGroupTitleDimensions { + let height: number; // Wrap: we need to ask `offsetHeight` to get // the real height of the title area with wrapping. - let height: number; if (this.accessor.partOptions.wrapTabs && this.tabsAndActionsContainer?.classList.contains('wrapping')) { height = this.tabsAndActionsContainer.offsetHeight; } else { @@ -1307,22 +1307,6 @@ export class TabsTitleControl extends TitleControl { this.layoutScheduled.value = scheduleAtNextAnimationFrame(() => { this.doLayout(this.dimensions); - // Compute new dimension of tabs title control - // and remember it for future usages - const oldDimension = this.dimensions.used; - const newDimension = this.dimensions.used = new Dimension(dimensions.container.width, this.getDimensions().height); - - // In case the height of the title control changed from before - // (currently only possible if tabs are set to wrap), we need - // to signal this to the outside via a `relayout` call so that - // e.g. the editor control can be adjusted accordingly. - if ( - this.accessor.partOptions.wrapTabs && - oldDimension && oldDimension.height !== newDimension.height - ) { - this.group.relayout(); - } - this.layoutScheduled.clear(); }); } @@ -1331,17 +1315,33 @@ export class TabsTitleControl extends TitleControl { } private doLayout(dimensions: ITitleControlDimensions): void { + + // Only layout if we have valid tab index and dimensions const activeTabAndIndex = this.group.activeEditor ? this.getTabAndIndex(this.group.activeEditor) : undefined; - if (!activeTabAndIndex || dimensions.container === Dimension.None || dimensions.available === Dimension.None) { - return; // nothing to do if not editor opened or we got no dimensions yet + if (activeTabAndIndex && dimensions.container !== Dimension.None && dimensions.available !== Dimension.None) { + + // Breadcrumbs + this.doLayoutBreadcrumbs(dimensions); + + // Tabs + const [activeTab, activeIndex] = activeTabAndIndex; + this.doLayoutTabs(activeTab, activeIndex, dimensions); } - // Breadcrumbs - this.doLayoutBreadcrumbs(dimensions); + // Compute new dimension of tabs title control and remember it for future usages + const oldDimension = this.dimensions.used; + const newDimension = this.dimensions.used = new Dimension(dimensions.container.width, this.getDimensions().height); - // Tabs - const [activeTab, activeIndex] = activeTabAndIndex; - this.doLayoutTabs(activeTab, activeIndex, dimensions); + // In case the height of the title control changed from before + // (currently only possible if tabs are set to wrap), we need + // to signal this to the outside via a `relayout` call so that + // e.g. the editor control can be adjusted accordingly. + if ( + this.accessor.partOptions.wrapTabs && + oldDimension && oldDimension.height !== newDimension.height + ) { + this.group.relayout(); + } } private doLayoutBreadcrumbs(dimensions: ITitleControlDimensions): void {