Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow tabs to wrap to multi-line #106448

Merged
merged 54 commits into from
Jan 4, 2021
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
a64e0f6
Allow tabs to wrap to multi-line
jmannanc Sep 9, 2020
b52a17a
Merge branch 'master' into wrap-tabs
bpasero Sep 11, 2020
8139024
Address feedback
jmannanc Sep 11, 2020
34dd6cf
Add hidden space after last tab
jmannanc Sep 15, 2020
717ab2d
Merge branch 'master' into wrap-tabs
bpasero Sep 16, 2020
417e97b
some polish for multi-line wrap css class
bpasero Sep 16, 2020
c290d7c
some more polish
bpasero Sep 16, 2020
f0fed35
Merge branch 'master' into wrap-tabs
jmannanc Sep 16, 2020
377a74a
Address feedback
jmannanc Sep 16, 2020
f85e5db
Merge branch 'master' into wrap-tabs
bpasero Sep 18, 2020
e6f1288
some adjustments to move forward
bpasero Sep 18, 2020
318f298
add clarifying comment to tabs layout
bpasero Sep 18, 2020
e6974fd
Fix editor container height
jmannanc Sep 18, 2020
3a3d7f0
WIP - overflowing tabs
jmannanc Sep 18, 2020
4dd752a
Merge branch 'master' into wrap-tabs
bpasero Sep 21, 2020
7df82ec
Merge branch 'master' into wrap-tabs
bpasero Sep 21, 2020
35fdc27
fix getPreferredHeight()
bpasero Sep 21, 2020
c1905a3
Merge remote-tracking branch 'upstream/master' into wrap-tabs
bpasero Sep 22, 2020
4cb7e49
Fix editor drop target for multi-line tabs
jmannanc Sep 22, 2020
8b34eca
Add comments and remove !important
jmannanc Sep 22, 2020
ae05859
fix dnd offset
bpasero Oct 4, 2020
ac0a024
Rework layout algorithm
jmannanc Oct 4, 2020
b5967e5
Make layout return a Dimension
jmannanc Oct 6, 2020
5f7a1aa
WIP - set maxDimensions
jmannanc Oct 7, 2020
df0d4a4
Layout multi-line tabs synchronously
jmannanc Oct 7, 2020
5979d0a
Merge branch 'master' into wrap-tabs
bpasero Oct 13, 2020
9147044
make sure dimensions are always defined and passed down to where needed
bpasero Oct 13, 2020
5835dfa
Merge branch 'master' into wrap-tabs
jmannanc Oct 26, 2020
2be28a8
Rework group.relayout and store lastComputedHeight
jmannanc Nov 2, 2020
8e07e4e
Merge branch 'master' into wrap-tabs
bpasero Dec 4, 2020
8acace5
fix breadcrumbs causing editor to disappear
bpasero Dec 5, 2020
69d6785
Merge branch 'master' into wrap-tabs
bpasero Dec 8, 2020
7222eaa
Merge branch 'master' into wrap-tabs
bpasero Dec 8, 2020
d2893f9
consolidate css rules
bpasero Dec 9, 2020
927dba7
rename setting
bpasero Dec 9, 2020
6e8618b
simplify classes
bpasero Dec 9, 2020
a88d9f1
streamline relayout
bpasero Dec 9, 2020
4bf1319
wrapTabs => experimentalWrapTabs
bpasero Dec 9, 2020
ba683f1
tweak layout
bpasero Dec 9, 2020
a1d21eb
Limit wrapped tabs to 3 rows
jmannanc Dec 15, 2020
d9fc730
Merge branch 'master' into wrap-tabs
bpasero Dec 16, 2020
11ea179
Only use flex-grow for `tabSizing: fit`
jmannanc Dec 16, 2020
d595a75
Merge branch 'master' into wrap-tabs
bpasero Dec 18, 2020
fe75f6c
fix scrollbar reveal to work properly
bpasero Dec 18, 2020
e69e7c3
tabs - get rid of sync layout
bpasero Dec 18, 2020
4585b70
WIP: Move editor actions to the bottom right
jmannanc Dec 20, 2020
b4c809d
some tweaks
bpasero Dec 27, 2020
a53fe3c
introduce css variable for margin-right trick
bpasero Dec 27, 2020
91c6333
add border to separate tabs when wrapping
bpasero Dec 27, 2020
46bdb34
:lipstick:
bpasero Dec 27, 2020
c853f2b
Merge branch 'master' into wrap-tabs
bpasero Dec 27, 2020
4f948dc
Merge branch 'master' into wrap-tabs
bpasero Jan 3, 2021
ff98891
rename setting
bpasero Jan 4, 2021
e6ee5f6
:lipstick: layout method
bpasero Jan 4, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/vs/workbench/browser/parts/editor/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ export interface IEditorGroupView extends IDisposable, ISerializableView, IEdito
readonly whenRestored: Promise<void>;

readonly titleDimensions: IEditorGroupTitleDimensions;
readonly editorHeight: number;

readonly isEmpty: boolean;
readonly isMinimized: boolean;
Expand Down
16 changes: 9 additions & 7 deletions src/vs/workbench/browser/parts/editor/editorGroupView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -721,6 +721,10 @@ 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;
Expand Down Expand Up @@ -1698,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`;
// 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;

// Forward to controls
this.titleAreaControl.layout(new Dimension(width, titleHeight));
this.editorControl.layout(new Dimension(width, editorHeight));
// Use the new height of the title area to layout the editor
this.editorControl.layout(new Dimension(width, this.dimension.height - titleDimensions.height));
}

relayout(): void {
Expand Down
20 changes: 20 additions & 0 deletions src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,26 @@
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;
}

.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 */
}

.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 */
}

/* Tabs Container */

.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container {
Expand Down
4 changes: 3 additions & 1 deletion src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -323,9 +323,11 @@ export class NoTabsTitleControl extends TitleControl {
};
}

layout(dimension: Dimension): void {
layout(dimension: Dimension): Dimension {
if (this.breadcrumbsControl) {
this.breadcrumbsControl.layout(undefined);
}

return new Dimension(dimension.width, this.getDimensions().height);
}
}
70 changes: 60 additions & 10 deletions src/vs/workbench/browser/parts/editor/tabsTitleControl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 } from 'vs/workbench/browser/parts/editor/editor';
import { IEditorGroupsAccessor, IEditorGroupView, EditorServiceImpl, IEditorGroupTitleDimensions, 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';
Expand Down Expand Up @@ -123,7 +123,9 @@ 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');
this.tabsAndActionsContainer.classList.add('tabs-and-actions-container');
this.titleContainer.appendChild(this.tabsAndActionsContainer);
Expand Down Expand Up @@ -524,7 +526,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();
}
Expand Down Expand Up @@ -1016,6 +1019,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 {
Expand Down Expand Up @@ -1223,18 +1232,28 @@ 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 {
layout(dimension: Dimension | undefined): Dimension | undefined {
this.dimension = dimension;

const activeTabAndIndex = this.group.activeEditor ? this.getTabAndIndex(this.group.activeEditor) : undefined;
Expand All @@ -1253,6 +1272,8 @@ export class TabsTitleControl extends TitleControl {
this.layoutScheduled.clear();
});
}

return new Dimension(this.dimension.width, this.getDimensions().height);
}

private doLayout(dimension: Dimension): void {
Expand All @@ -1279,7 +1300,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
Expand Down Expand Up @@ -1334,6 +1355,35 @@ export class TabsTitleControl extends TitleControl {
tabsContainer.classList.remove('disable-sticky-tabs');
}

// Handle multi-line tabs according to setting:
// - enabled: only add class if tabs wrap
// - 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');
}

// 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 === TabsTitleControl.TAB_HEIGHT) {
tabsAndActionsContainer.classList.remove('multi-line');
}
}
} else {
tabsAndActionsContainer.classList.remove('multi-line');
}

let activeTabPosX: number | undefined;
let activeTabWidth: number | undefined;

Expand Down
2 changes: 1 addition & 1 deletion src/vs/workbench/browser/parts/editor/titleControl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ export abstract class TitleControl extends Themable {

abstract updateStyles(): void;

abstract layout(dimension: Dimension): void;
abstract layout(dimension: Dimension): Dimension | undefined;

abstract getDimensions(): IEditorGroupTitleDimensions;

Expand Down
5 changes: 5 additions & 0 deletions src/vs/workbench/browser/workbench.contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
'markdownDescription': 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. This value is ignored when `#workbench.editor.showTabs#` is `false`."),
Expand Down
1 change: 1 addition & 0 deletions src/vs/workbench/common/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1225,6 +1225,7 @@ export interface IWorkbenchEditorConfiguration {

interface IEditorPartConfiguration {
showTabs?: boolean;
multiLineTabs?: boolean;
scrollToSwitchTabs?: boolean;
highlightModifiedTabs?: boolean;
tabCloseButton?: 'left' | 'right' | 'off';
Expand Down
1 change: 1 addition & 0 deletions src/vs/workbench/test/browser/workbenchTestServices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -604,6 +604,7 @@ export class TestEditorGroupView implements IEditorGroupView {
maximumHeight!: number;

titleDimensions!: IEditorGroupTitleDimensions;
editorHeight!: number;

isEmpty = true;
isMinimized = false;
Expand Down