diff --git a/src/vs/base/browser/ui/grid/grid.ts b/src/vs/base/browser/ui/grid/grid.ts index 821567ccf59..4f772b198c2 100644 --- a/src/vs/base/browser/ui/grid/grid.ts +++ b/src/vs/base/browser/ui/grid/grid.ts @@ -51,6 +51,7 @@ export interface GridLeafNode { readonly view: T; readonly box: Box; readonly cachedVisibleSize: number | undefined; + readonly maximized: boolean; } export interface GridBranchNode { @@ -288,6 +289,7 @@ export class Grid extends Disposable { private didLayout = false; + readonly onDidChangeViewMaximized: Event; /** * Create a new {@link Grid}. A grid must *always* have a view * inside. @@ -313,6 +315,7 @@ export class Grid extends Disposable { this.onDidChange = this.gridview.onDidChange; this.onDidScroll = this.gridview.onDidScroll; + this.onDidChangeViewMaximized = this.gridview.onDidChangeViewMaximized; } style(styles: IGridStyles): void { @@ -545,9 +548,28 @@ export class Grid extends Disposable { * * @param view The reference {@link IView view}. */ - isViewSizeMaximized(view: T): boolean { + isViewExpanded(view: T): boolean { const location = this.getViewLocation(view); - return this.gridview.isViewSizeMaximized(location); + return this.gridview.isViewExpanded(location); + } + + /** + * Returns whether the {@link IView view} is maximized. + * + * @param view The reference {@link IView view}. + */ + isViewMaximized(view: T): boolean { + const location = this.getViewLocation(view); + return this.gridview.isViewMaximized(location); + } + + /** + * Returns whether the {@link IView view} is maximized. + * + * @param view The reference {@link IView view}. + */ + hasMaximizedView(): boolean { + return this.gridview.hasMaximizedView(); } /** @@ -577,14 +599,30 @@ export class Grid extends Disposable { } /** - * Maximize the size of a {@link IView view} by collapsing all other views + * Maximizes the specified view and hides all other views. + * @param view The view to maximize. + */ + maximizeView(view: T) { + if (this.views.size < 2) { + throw new Error('At least two views are required to maximize a view'); + } + const location = this.getViewLocation(view); + this.gridview.maximizeView(location); + } + + exitMaximizedView(): void { + this.gridview.exitMaximizedView(); + } + + /** + * Expand the size of a {@link IView view} by collapsing all other views * to their minimum sizes. * * @param view The {@link IView view}. */ - maximizeViewSize(view: T): void { + expandView(view: T): void { const location = this.getViewLocation(view); - this.gridview.maximizeViewSize(location); + this.gridview.expandView(location); } /** @@ -713,12 +751,14 @@ export interface ISerializedLeafNode { data: any; size: number; visible?: boolean; + maximized?: boolean; } export interface ISerializedBranchNode { type: 'branch'; data: ISerializedNode[]; size: number; + visible?: boolean; } export type ISerializedNode = ISerializedLeafNode | ISerializedBranchNode; @@ -739,14 +779,23 @@ export class SerializableGrid extends Grid { const size = orientation === Orientation.VERTICAL ? node.box.width : node.box.height; if (!isGridBranchNode(node)) { + const serializedLeafNode: ISerializedLeafNode = { type: 'leaf', data: node.view.toJSON(), size }; + if (typeof node.cachedVisibleSize === 'number') { - return { type: 'leaf', data: node.view.toJSON(), size: node.cachedVisibleSize, visible: false }; + serializedLeafNode.size = node.cachedVisibleSize; + serializedLeafNode.visible = false; + } else if (node.maximized) { + serializedLeafNode.maximized = true; } - return { type: 'leaf', data: node.view.toJSON(), size }; + return serializedLeafNode; } - return { type: 'branch', data: node.children.map(c => SerializableGrid.serializeNode(c, orthogonal(orientation))), size }; + const data = node.children.map(c => SerializableGrid.serializeNode(c, orthogonal(orientation))); + if (data.some(c => c.visible !== false)) { + return { type: 'branch', data: data, size }; + } + return { type: 'branch', data: data, size, visible: false }; } /** diff --git a/src/vs/base/browser/ui/grid/gridview.ts b/src/vs/base/browser/ui/grid/gridview.ts index f73e7a8ce7a..e21d06a9936 100644 --- a/src/vs/base/browser/ui/grid/gridview.ts +++ b/src/vs/base/browser/ui/grid/gridview.ts @@ -148,12 +148,14 @@ export interface ISerializedLeafNode { data: any; size: number; visible?: boolean; + maximized?: boolean; } export interface ISerializedBranchNode { type: 'branch'; data: ISerializedNode[]; size: number; + visible?: boolean; } export type ISerializedNode = ISerializedLeafNode | ISerializedBranchNode; @@ -180,6 +182,7 @@ export interface GridLeafNode { readonly view: IView; readonly box: Box; readonly cachedVisibleSize: number | undefined; + readonly maximized: boolean; } export interface GridBranchNode { @@ -284,11 +287,11 @@ class BranchNode implements ISplitView, IDisposable { } get minimumSize(): number { - return this.children.length === 0 ? 0 : Math.max(...this.children.map(c => c.minimumOrthogonalSize)); + return this.children.length === 0 ? 0 : Math.max(...this.children.map((c, index) => this.splitview.isViewVisible(index) ? c.minimumOrthogonalSize : 0)); } get maximumSize(): number { - return Math.min(...this.children.map(c => c.maximumOrthogonalSize)); + return Math.min(...this.children.map((c, index) => this.splitview.isViewVisible(index) ? c.maximumOrthogonalSize : Number.POSITIVE_INFINITY)); } get priority(): LayoutPriority { @@ -342,6 +345,10 @@ class BranchNode implements ISplitView, IDisposable { private readonly _onDidChange = new Emitter(); readonly onDidChange: Event = this._onDidChange.event; + private readonly _onDidVisibilityChange = new Emitter(); + readonly onDidVisibilityChange: Event = this._onDidVisibilityChange.event; + private readonly childrenVisibilityChangeDisposable: DisposableStore = new DisposableStore(); + private _onDidScroll = new Emitter(); private onDidScrollDisposable: IDisposable = Disposable.None; readonly onDidScroll: Event = this._onDidScroll.event; @@ -427,7 +434,7 @@ class BranchNode implements ISplitView, IDisposable { return { view: childDescriptor.node, size: childDescriptor.node.size, - visible: childDescriptor.node instanceof LeafNode && childDescriptor.visible !== undefined ? childDescriptor.visible : true + visible: childDescriptor.visible !== false }; }), size: this.orthogonalSize @@ -579,8 +586,8 @@ class BranchNode implements ISplitView, IDisposable { this.splitview.resizeView(index, size); } - isChildSizeMaximized(index: number): boolean { - return this.splitview.isViewSizeMaximized(index); + isChildExpanded(index: number): boolean { + return this.splitview.isViewExpanded(index); } distributeViewSizes(recursive = false): void { @@ -614,7 +621,15 @@ class BranchNode implements ISplitView, IDisposable { return; } + const wereAllChildrenHidden = this.splitview.contentSize === 0; this.splitview.setViewVisible(index, visible); + const areAllChildrenHidden = this.splitview.contentSize === 0; + + // If all children are hidden then the parent should hide the entire splitview + // If the entire splitview is hidden then the parent should show the splitview when a child is shown + if ((visible && wereAllChildrenHidden) || (!visible && areAllChildrenHidden)) { + this._onDidVisibilityChange.fire(visible); + } } getChildCachedVisibleSize(index: number): number | undefined { @@ -651,6 +666,15 @@ class BranchNode implements ISplitView, IDisposable { const onDidScroll = Event.any(Event.signal(this.splitview.onDidScroll), ...this.children.map(c => c.onDidScroll)); this.onDidScrollDisposable.dispose(); this.onDidScrollDisposable = onDidScroll(this._onDidScroll.fire, this._onDidScroll); + + this.childrenVisibilityChangeDisposable.clear(); + this.children.forEach((child, index) => { + if (child instanceof BranchNode) { + this.childrenVisibilityChangeDisposable.add(child.onDidVisibilityChange((visible) => { + this.setChildVisible(index, visible); + })); + } + }); } trySet2x2(other: BranchNode): IDisposable { @@ -714,7 +738,9 @@ class BranchNode implements ISplitView, IDisposable { this._onDidChange.dispose(); this._onDidSashReset.dispose(); + this._onDidVisibilityChange.dispose(); + this.childrenVisibilityChangeDisposable.dispose(); this.splitviewSashResetDisposable.dispose(); this.childrenSashResetDisposable.dispose(); this.childrenChangeDisposable.dispose(); @@ -1128,6 +1154,11 @@ export class GridView implements IDisposable { this.root.edgeSnapping = edgeSnapping; } + private maximizedNode: LeafNode | undefined = undefined; + + private readonly _onDidChangeViewMaximized = new Emitter(); + readonly onDidChangeViewMaximized = this._onDidChangeViewMaximized.event; + /** * Create a new {@link GridView} instance. * @@ -1173,6 +1204,10 @@ export class GridView implements IDisposable { * @param location The {@link GridLocation location} to insert the view on. */ addView(view: IView, size: number | Sizing, location: GridLocation): void { + if (this.hasMaximizedView()) { + this.exitMaximizedView(); + } + this.disposable2x2.dispose(); this.disposable2x2 = Disposable.None; @@ -1226,6 +1261,10 @@ export class GridView implements IDisposable { * @param sizing Whether to distribute other {@link IView view}'s sizes. */ removeView(location: GridLocation, sizing?: DistributeSizing | AutoSizing): IView { + if (this.hasMaximizedView()) { + this.exitMaximizedView(); + } + this.disposable2x2.dispose(); this.disposable2x2 = Disposable.None; @@ -1312,6 +1351,10 @@ export class GridView implements IDisposable { * @param to The index where the {@link IView view} should move to. */ moveView(parentLocation: GridLocation, from: number, to: number): void { + if (this.hasMaximizedView()) { + this.exitMaximizedView(); + } + const [, parent] = this.getNode(parentLocation); if (!(parent instanceof BranchNode)) { @@ -1330,6 +1373,10 @@ export class GridView implements IDisposable { * @param to The {@link GridLocation location} of another view. */ swapViews(from: GridLocation, to: GridLocation): void { + if (this.hasMaximizedView()) { + this.exitMaximizedView(); + } + const [fromRest, fromIndex] = tail(from); const [, fromParent] = this.getNode(fromRest); @@ -1378,6 +1425,10 @@ export class GridView implements IDisposable { * @param size The size the view should be. Optionally provide a single dimension. */ resizeView(location: GridLocation, size: Partial): void { + if (this.hasMaximizedView()) { + this.exitMaximizedView(); + } + const [rest, index] = tail(location); const [pathToParent, parent] = this.getNode(rest); @@ -1443,7 +1494,11 @@ export class GridView implements IDisposable { * * @param location The {@link GridLocation location} of the view. */ - maximizeViewSize(location: GridLocation): void { + expandView(location: GridLocation): void { + if (this.hasMaximizedView()) { + this.exitMaximizedView(); + } + const [ancestors, node] = this.getNode(location); if (!(node instanceof LeafNode)) { @@ -1460,7 +1515,12 @@ export class GridView implements IDisposable { * * @param location The {@link GridLocation location} of the view. */ - isViewSizeMaximized(location: GridLocation): boolean { + isViewExpanded(location: GridLocation): boolean { + if (this.hasMaximizedView()) { + // No view can be expanded when a view is maximized + return false; + } + const [ancestors, node] = this.getNode(location); if (!(node instanceof LeafNode)) { @@ -1468,7 +1528,7 @@ export class GridView implements IDisposable { } for (let i = 0; i < ancestors.length; i++) { - if (!ancestors[i].isChildSizeMaximized(location[i])) { + if (!ancestors[i].isChildExpanded(location[i])) { return false; } } @@ -1476,6 +1536,80 @@ export class GridView implements IDisposable { return true; } + maximizeView(location: GridLocation) { + const [, nodeToMaximize] = this.getNode(location); + if (!(nodeToMaximize instanceof LeafNode)) { + throw new Error('Location is not a LeafNode'); + } + + if (this.maximizedNode === nodeToMaximize) { + return; + } + + if (this.hasMaximizedView()) { + this.exitMaximizedView(); + } + + function hideAllViewsBut(parent: BranchNode, exclude: LeafNode): void { + for (let i = 0; i < parent.children.length; i++) { + const child = parent.children[i]; + if (child instanceof LeafNode) { + if (child !== exclude) { + parent.setChildVisible(i, false); + } + } else { + hideAllViewsBut(child, exclude); + } + } + } + + hideAllViewsBut(this.root, nodeToMaximize); + + this.maximizedNode = nodeToMaximize; + this._onDidChangeViewMaximized.fire(true); + } + + exitMaximizedView(): void { + if (!this.maximizedNode) { + return; + } + this.maximizedNode = undefined; + + // When hiding a view, it's previous size is cached. + // To restore the sizes of all views, they need to be made visible in reverse order. + function showViewsInReverseOrder(parent: BranchNode): void { + for (let index = parent.children.length - 1; index >= 0; index--) { + const child = parent.children[index]; + if (child instanceof LeafNode) { + parent.setChildVisible(index, true); + } else { + showViewsInReverseOrder(child); + } + } + } + + showViewsInReverseOrder(this.root); + + this._onDidChangeViewMaximized.fire(false); + } + + hasMaximizedView(): boolean { + return this.maximizedNode !== undefined; + } + + /** + * Returns whether the {@link IView view} is maximized. + * + * @param location The {@link GridLocation location} of the view. + */ + isViewMaximized(location: GridLocation): boolean { + const [, node] = this.getNode(location); + if (!(node instanceof LeafNode)) { + throw new Error('Location is not a LeafNode'); + } + return node === this.maximizedNode; + } + /** * Distribute the size among all {@link IView views} within the entire * grid or within a single {@link SplitView}. @@ -1486,6 +1620,10 @@ export class GridView implements IDisposable { * in the entire grid. */ distributeViewSizes(location?: GridLocation): void { + if (this.hasMaximizedView()) { + this.exitMaximizedView(); + } + if (!location) { this.root.distributeViewSizes(true); return; @@ -1523,6 +1661,11 @@ export class GridView implements IDisposable { * @param location The {@link GridLocation location} of the view. */ setViewVisible(location: GridLocation, visible: boolean): void { + if (this.hasMaximizedView()) { + this.exitMaximizedView(); + return; + } + const [rest, index] = tail(location); const [, parent] = this.getNode(rest); @@ -1596,6 +1739,10 @@ export class GridView implements IDisposable { result = new BranchNode(orientation, this.layoutController, this.styles, this.proportionalLayout, node.size, orthogonalSize, undefined, children); } else { result = new LeafNode(deserializer.fromJSON(node.data), orientation, this.layoutController, orthogonalSize, node.size); + if (node.maximized && !this.maximizedNode) { + this.maximizedNode = result; + this._onDidChangeViewMaximized.fire(true); + } } return result; @@ -1605,7 +1752,7 @@ export class GridView implements IDisposable { const box = { top: node.top, left: node.left, width: node.width, height: node.height }; if (node instanceof LeafNode) { - return { view: node.view, box, cachedVisibleSize }; + return { view: node.view, box, cachedVisibleSize, maximized: this.maximizedNode === node }; } const children: GridNode[] = []; diff --git a/src/vs/base/browser/ui/splitview/splitview.ts b/src/vs/base/browser/ui/splitview/splitview.ts index ca8a714d8b3..8f63b6039a1 100644 --- a/src/vs/base/browser/ui/splitview/splitview.ts +++ b/src/vs/base/browser/ui/splitview/splitview.ts @@ -442,7 +442,7 @@ export class SplitView[] = []; sashItems: ISashItem[] = []; // used in tests @@ -459,6 +459,11 @@ export class SplitView r + i.size, 0); + this._contentSize = this.viewItems.reduce((r, i) => r + i.size, 0); this.saveProportions(); } } @@ -834,7 +839,7 @@ export class SplitView 0) { item.size = clamp(Math.round(proportion * size / total), item.minimumSize, item.maximumSize); } } @@ -873,8 +878,8 @@ export class SplitView 0) { - this.proportions = this.viewItems.map(i => i.proportionalLayout ? i.size / this.contentSize : undefined); + if (this.proportionalLayout && this._contentSize > 0) { + this.proportions = this.viewItems.map(v => v.proportionalLayout && v.visible ? v.size / this._contentSize : undefined); } } @@ -1052,7 +1057,7 @@ export class SplitView= this.viewItems.length) { return false; } @@ -1347,7 +1352,7 @@ export class SplitView r + i.size, 0); + this._contentSize = this.viewItems.reduce((r, i) => r + i.size, 0); // Layout views let offset = 0; @@ -1367,12 +1372,12 @@ export class SplitView 0 || this.startSnappingEnabled)) { sash.state = SashState.AtMinimum; - } else if (snappedAfter && collapsesDown[index] && (position < this.contentSize || this.endSnappingEnabled)) { + } else if (snappedAfter && collapsesDown[index] && (position < this._contentSize || this.endSnappingEnabled)) { sash.state = SashState.AtMaximum; } else { sash.state = SashState.Disabled; diff --git a/src/vs/base/test/browser/ui/grid/grid.test.ts b/src/vs/base/test/browser/ui/grid/grid.test.ts index b4e5e4d6c16..781a0a407f0 100644 --- a/src/vs/base/test/browser/ui/grid/grid.test.ts +++ b/src/vs/base/test/browser/ui/grid/grid.test.ts @@ -7,7 +7,7 @@ import * as assert from 'assert'; import { createSerializedGrid, Direction, getRelativeLocation, Grid, GridNode, GridNodeDescriptor, ISerializableView, isGridBranchNode, IViewDeserializer, Orientation, sanitizeGridNodeDescriptor, SerializableGrid, Sizing } from 'vs/base/browser/ui/grid/grid'; import { Event } from 'vs/base/common/event'; import { deepClone } from 'vs/base/common/objects'; -import { nodesToArrays, TestView } from './util'; +import { nodesToArrays, TestView } from 'vs/base/test/browser/ui/grid/util'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { DisposableStore } from 'vs/base/common/lifecycle'; @@ -464,6 +464,206 @@ suite('Grid', function () { assert.deepStrictEqual(grid.getNeighborViews(view1, Direction.Right), [view2, view3]); }); + + test('hiding splitviews and restoring sizes', function () { + const view1 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); + const grid = store.add(new Grid(view1)); + container.appendChild(grid.element); + + grid.layout(800, 600); + + const view2 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); + grid.addView(view2, Sizing.Distribute, view1, Direction.Right); + + const view3 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); + grid.addView(view3, Sizing.Distribute, view2, Direction.Down); + + const view4 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); + grid.addView(view4, Sizing.Distribute, view2, Direction.Right); + + const size1 = view1.size; + const size2 = view2.size; + const size3 = view3.size; + const size4 = view4.size; + + grid.maximizeView(view1); + + // Views 2, 3, 4 are hidden + // Splitview (2,4) and ((2,4),3) are hidden + assert.deepStrictEqual(view1.size, [800, 600]); + assert.deepStrictEqual(view2.size, [0, 0]); + assert.deepStrictEqual(view3.size, [0, 0]); + assert.deepStrictEqual(view4.size, [0, 0]); + + grid.exitMaximizedView(); + + assert.deepStrictEqual(view1.size, size1); + assert.deepStrictEqual(view2.size, size2); + assert.deepStrictEqual(view3.size, size3); + assert.deepStrictEqual(view4.size, size4); + + // Views 1, 3, 4 are hidden + // All splitviews are still visible => only orthogonalsize is 0 + grid.maximizeView(view2); + + assert.deepStrictEqual(view1.size, [0, 600]); + assert.deepStrictEqual(view2.size, [800, 600]); + assert.deepStrictEqual(view3.size, [800, 0]); + assert.deepStrictEqual(view4.size, [0, 600]); + + grid.exitMaximizedView(); + + assert.deepStrictEqual(view1.size, size1); + assert.deepStrictEqual(view2.size, size2); + assert.deepStrictEqual(view3.size, size3); + assert.deepStrictEqual(view4.size, size4); + }); + + test('hasMaximizedView', function () { + const view1 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); + const grid = store.add(new Grid(view1)); + container.appendChild(grid.element); + + grid.layout(800, 600); + + const view2 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); + grid.addView(view2, Sizing.Distribute, view1, Direction.Right); + + const view3 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); + grid.addView(view3, Sizing.Distribute, view2, Direction.Down); + + const view4 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); + grid.addView(view4, Sizing.Distribute, view2, Direction.Right); + + function checkIsMaximized(view: TestView) { + grid.maximizeView(view); + + assert.deepStrictEqual(grid.hasMaximizedView(), true); + + // When a view is maximized, no view can be expanded even if it is maximized + assert.deepStrictEqual(grid.isViewExpanded(view1), false); + assert.deepStrictEqual(grid.isViewExpanded(view2), false); + assert.deepStrictEqual(grid.isViewExpanded(view3), false); + assert.deepStrictEqual(grid.isViewExpanded(view4), false); + + grid.exitMaximizedView(); + + assert.deepStrictEqual(grid.hasMaximizedView(), false); + } + + checkIsMaximized(view1); + checkIsMaximized(view2); + checkIsMaximized(view3); + checkIsMaximized(view4); + }); + + test('Changes to the grid unmaximize the view', function () { + const view1 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); + const grid = store.add(new Grid(view1)); + container.appendChild(grid.element); + + grid.layout(800, 600); + + const view2 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); + grid.addView(view2, Sizing.Distribute, view1, Direction.Right); + + const view3 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); + grid.addView(view3, Sizing.Distribute, view2, Direction.Down); + + const view4 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); + + // Adding a view unmaximizes the view + grid.maximizeView(view1); + assert.deepStrictEqual(grid.hasMaximizedView(), true); + grid.addView(view4, Sizing.Distribute, view2, Direction.Right); + + assert.deepStrictEqual(grid.hasMaximizedView(), false); + assert.deepStrictEqual(grid.isViewVisible(view1), true); + assert.deepStrictEqual(grid.isViewVisible(view2), true); + assert.deepStrictEqual(grid.isViewVisible(view3), true); + assert.deepStrictEqual(grid.isViewVisible(view4), true); + + // Removing a view unmaximizes the view + grid.maximizeView(view1); + assert.deepStrictEqual(grid.hasMaximizedView(), true); + grid.removeView(view4); + + assert.deepStrictEqual(grid.hasMaximizedView(), false); + assert.deepStrictEqual(grid.isViewVisible(view1), true); + assert.deepStrictEqual(grid.isViewVisible(view2), true); + assert.deepStrictEqual(grid.isViewVisible(view3), true); + + // Changing the visibility of any view while a view is maximized, unmaximizes the view + grid.maximizeView(view1); + assert.deepStrictEqual(grid.hasMaximizedView(), true); + grid.setViewVisible(view3, true); + + assert.deepStrictEqual(grid.hasMaximizedView(), false); + assert.deepStrictEqual(grid.isViewVisible(view1), true); + assert.deepStrictEqual(grid.isViewVisible(view2), true); + assert.deepStrictEqual(grid.isViewVisible(view3), true); + }); + + test('Changes to the grid sizing unmaximize the view', function () { + const view1 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); + const grid = store.add(new Grid(view1)); + container.appendChild(grid.element); + + grid.layout(800, 600); + + const view2 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); + grid.addView(view2, Sizing.Distribute, view1, Direction.Right); + + const view3 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); + grid.addView(view3, Sizing.Distribute, view2, Direction.Down); + + const view4 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); + grid.addView(view4, Sizing.Distribute, view2, Direction.Right); + + // Maximizing a different view unmaximizes the current one and maximizes the new one + grid.maximizeView(view1); + assert.deepStrictEqual(grid.hasMaximizedView(), true); + grid.maximizeView(view2); + + assert.deepStrictEqual(grid.hasMaximizedView(), true); + assert.deepStrictEqual(grid.isViewVisible(view1), false); + assert.deepStrictEqual(grid.isViewVisible(view2), true); + assert.deepStrictEqual(grid.isViewVisible(view3), false); + assert.deepStrictEqual(grid.isViewVisible(view4), false); + + // Distributing the size unmaximizes the view + grid.maximizeView(view1); + assert.deepStrictEqual(grid.hasMaximizedView(), true); + grid.distributeViewSizes(); + + assert.deepStrictEqual(grid.hasMaximizedView(), false); + assert.deepStrictEqual(grid.isViewVisible(view1), true); + assert.deepStrictEqual(grid.isViewVisible(view2), true); + assert.deepStrictEqual(grid.isViewVisible(view3), true); + assert.deepStrictEqual(grid.isViewVisible(view4), true); + + // Expanding a different view unmaximizes the view + grid.maximizeView(view1); + assert.deepStrictEqual(grid.hasMaximizedView(), true); + grid.expandView(view2); + + assert.deepStrictEqual(grid.hasMaximizedView(), false); + assert.deepStrictEqual(grid.isViewVisible(view1), true); + assert.deepStrictEqual(grid.isViewVisible(view2), true); + assert.deepStrictEqual(grid.isViewVisible(view3), true); + assert.deepStrictEqual(grid.isViewVisible(view4), true); + + // Expanding the maximized view unmaximizes the view + grid.maximizeView(view1); + assert.deepStrictEqual(grid.hasMaximizedView(), true); + grid.expandView(view1); + + assert.deepStrictEqual(grid.hasMaximizedView(), false); + assert.deepStrictEqual(grid.isViewVisible(view1), true); + assert.deepStrictEqual(grid.isViewVisible(view2), true); + assert.deepStrictEqual(grid.isViewVisible(view3), true); + assert.deepStrictEqual(grid.isViewVisible(view4), true); + }); }); class TestSerializableView extends TestView implements ISerializableView { diff --git a/src/vs/base/test/browser/ui/grid/gridview.test.ts b/src/vs/base/test/browser/ui/grid/gridview.test.ts index e9be1b00e16..bbc7de0716f 100644 --- a/src/vs/base/test/browser/ui/grid/gridview.test.ts +++ b/src/vs/base/test/browser/ui/grid/gridview.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import { $ } from 'vs/base/browser/dom'; import { GridView, IView, Orientation, Sizing } from 'vs/base/browser/ui/grid/gridview'; -import { nodesToArrays, TestView } from './util'; +import { nodesToArrays, TestView } from 'vs/base/test/browser/ui/grid/util'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('Gridview', function () { diff --git a/src/vs/workbench/browser/contextkeys.ts b/src/vs/workbench/browser/contextkeys.ts index fcddfd142d8..f052cd5f7a9 100644 --- a/src/vs/workbench/browser/contextkeys.ts +++ b/src/vs/workbench/browser/contextkeys.ts @@ -7,7 +7,7 @@ import { Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { IContextKeyService, IContextKey, setConstant as setConstantContextKey } from 'vs/platform/contextkey/common/contextkey'; import { InputFocusedContext, IsMacContext, IsLinuxContext, IsWindowsContext, IsWebContext, IsMacNativeContext, IsDevelopmentContext, IsIOSContext, ProductQualityContext, IsMobileContext } from 'vs/platform/contextkey/common/contextkeys'; -import { SplitEditorsVertically, InEditorZenModeContext, ActiveEditorCanRevertContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, AuxiliaryBarVisibleContext, SideBarVisibleContext, PanelAlignmentContext, PanelMaximizedContext, PanelVisibleContext, ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, EmbedderIdentifierContext, EditorTabsVisibleContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorReadonlyContext, EditorAreaVisibleContext, ActiveEditorAvailableEditorIdsContext, DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, IsFullscreenContext, OpenFolderWorkspaceSupportContext, RemoteNameContext, VirtualWorkspaceContext, WorkbenchStateContext, WorkspaceFolderCountContext, PanelPositionContext, TemporaryWorkspaceContext, ActiveEditorCanToggleReadonlyContext, applyAvailableEditorIds, TitleBarVisibleContext } from 'vs/workbench/common/contextkeys'; +import { SplitEditorsVertically, InEditorZenModeContext, ActiveEditorCanRevertContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, AuxiliaryBarVisibleContext, SideBarVisibleContext, PanelAlignmentContext, PanelMaximizedContext, PanelVisibleContext, ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, EmbedderIdentifierContext, EditorTabsVisibleContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorReadonlyContext, EditorAreaVisibleContext, ActiveEditorAvailableEditorIdsContext, DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, IsFullscreenContext, OpenFolderWorkspaceSupportContext, RemoteNameContext, VirtualWorkspaceContext, WorkbenchStateContext, WorkspaceFolderCountContext, PanelPositionContext, TemporaryWorkspaceContext, ActiveEditorCanToggleReadonlyContext, applyAvailableEditorIds, MaximizedEditorGroupContext, TitleBarVisibleContext } from 'vs/workbench/common/contextkeys'; import { TEXT_DIFF_EDITOR_ID, EditorInputCapabilities, SIDE_BY_SIDE_EDITOR_ID, EditorResourceAccessor, SideBySideEditor } from 'vs/workbench/common/editor'; import { trackFocus, addDisposableListener, EventType, onDidRegisterWindow } from 'vs/base/browser/dom'; import { preferredSideBySideGroupDirection, GroupDirection, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -44,6 +44,7 @@ export class WorkbenchContextKeysHandler extends Disposable { private activeEditorGroupLast: IContextKey; private activeEditorGroupLocked: IContextKey; private multipleEditorGroupsContext: IContextKey; + private maximizedEditorGroupContext: IContextKey; private editorsVisibleContext: IContextKey; @@ -136,6 +137,7 @@ export class WorkbenchContextKeysHandler extends Disposable { this.activeEditorGroupLast = ActiveEditorGroupLastContext.bindTo(this.contextKeyService); this.activeEditorGroupLocked = ActiveEditorGroupLockedContext.bindTo(this.contextKeyService); this.multipleEditorGroupsContext = MultipleEditorGroupsContext.bindTo(this.contextKeyService); + this.maximizedEditorGroupContext = MaximizedEditorGroupContext.bindTo(this.contextKeyService); // Working Copies this.dirtyWorkingCopiesContext = DirtyWorkingCopiesContext.bindTo(this.contextKeyService); @@ -233,6 +235,8 @@ export class WorkbenchContextKeysHandler extends Disposable { this._register(this.editorGroupService.onDidChangeActiveGroup(() => this.updateEditorGroupContextKeys())); this._register(this.editorGroupService.onDidChangeGroupLocked(() => this.updateEditorGroupContextKeys())); + this._register(this.editorGroupService.onDidChangeGroupMaximized((maximized) => this.maximizedEditorGroupContext.set(maximized))); + this._register(this.editorGroupService.onDidChangeEditorPartOptions(() => this.updateEditorAreaContextKeys())); this._register(Event.runAndSubscribe(onDidRegisterWindow, ({ window, disposableStore }) => disposableStore.add(addDisposableListener(window, EventType.FOCUS_IN, () => this.updateInputContextKeys(window.document), true)), { window, disposableStore: this._store })); diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index e0672ad76e2..43df6bf2818 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -10,7 +10,7 @@ import { IEditorFactoryRegistry, EditorExtensions } from 'vs/workbench/common/ed import { TextCompareEditorActiveContext, ActiveEditorPinnedContext, EditorGroupEditorsCountContext, ActiveEditorStickyContext, ActiveEditorAvailableEditorIdsContext, MultipleEditorGroupsContext, ActiveEditorDirtyContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, - EditorTabsVisibleContext, ActiveEditorLastInGroupContext + EditorTabsVisibleContext, ActiveEditorLastInGroupContext, MaximizedEditorGroupContext } from 'vs/workbench/common/contextkeys'; import { SideBySideEditorInput, SideBySideEditorInputSerializer } from 'vs/workbench/common/editor/sideBySideEditorInput'; import { TextResourceEditor } from 'vs/workbench/browser/parts/editor/textResourceEditor'; @@ -40,13 +40,14 @@ import { QuickAccessPreviousRecentlyUsedEditorAction, OpenPreviousRecentlyUsedEditorInGroupAction, OpenNextRecentlyUsedEditorInGroupAction, QuickAccessLeastRecentlyUsedEditorAction, QuickAccessLeastRecentlyUsedEditorInGroupAction, ReOpenInTextEditorAction, DuplicateGroupDownAction, DuplicateGroupLeftAction, DuplicateGroupRightAction, DuplicateGroupUpAction, ToggleEditorTypeAction, SplitEditorToAboveGroupAction, SplitEditorToBelowGroupAction, SplitEditorToFirstGroupAction, SplitEditorToLastGroupAction, SplitEditorToLeftGroupAction, SplitEditorToNextGroupAction, SplitEditorToPreviousGroupAction, SplitEditorToRightGroupAction, NavigateForwardInEditsAction, - NavigateBackwardsInEditsAction, NavigateForwardInNavigationsAction, NavigateBackwardsInNavigationsAction, NavigatePreviousInNavigationsAction, NavigatePreviousInEditsAction, NavigateToLastNavigationLocationAction, ExperimentalMoveEditorIntoNewWindowAction + NavigateBackwardsInEditsAction, NavigateForwardInNavigationsAction, NavigateBackwardsInNavigationsAction, NavigatePreviousInNavigationsAction, NavigatePreviousInEditsAction, NavigateToLastNavigationLocationAction, + MaximizeGroupHideSidebarAction, UnmaximizeEditorGroupAction, ExperimentalMoveEditorIntoNewWindowAction } from 'vs/workbench/browser/parts/editor/editorActions'; import { CLOSE_EDITORS_AND_GROUP_COMMAND_ID, CLOSE_EDITORS_IN_GROUP_COMMAND_ID, CLOSE_EDITORS_TO_THE_RIGHT_COMMAND_ID, CLOSE_EDITOR_COMMAND_ID, CLOSE_EDITOR_GROUP_COMMAND_ID, CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID, CLOSE_PINNED_EDITOR_COMMAND_ID, CLOSE_SAVED_EDITORS_COMMAND_ID, GOTO_NEXT_CHANGE, GOTO_PREVIOUS_CHANGE, KEEP_EDITOR_COMMAND_ID, PIN_EDITOR_COMMAND_ID, SHOW_EDITORS_IN_GROUP, SPLIT_EDITOR_DOWN, SPLIT_EDITOR_LEFT, SPLIT_EDITOR_RIGHT, SPLIT_EDITOR_UP, TOGGLE_DIFF_IGNORE_TRIM_WHITESPACE, TOGGLE_DIFF_SIDE_BY_SIDE, TOGGLE_KEEP_EDITORS_COMMAND_ID, UNPIN_EDITOR_COMMAND_ID, setup as registerEditorCommands, REOPEN_WITH_COMMAND_ID, - TOGGLE_LOCK_GROUP_COMMAND_ID, UNLOCK_GROUP_COMMAND_ID, SPLIT_EDITOR_IN_GROUP, JOIN_EDITOR_IN_GROUP, FOCUS_FIRST_SIDE_EDITOR, FOCUS_SECOND_SIDE_EDITOR, TOGGLE_SPLIT_EDITOR_IN_GROUP_LAYOUT, SPLIT_EDITOR + TOGGLE_LOCK_GROUP_COMMAND_ID, UNLOCK_GROUP_COMMAND_ID, SPLIT_EDITOR_IN_GROUP, JOIN_EDITOR_IN_GROUP, FOCUS_FIRST_SIDE_EDITOR, FOCUS_SECOND_SIDE_EDITOR, TOGGLE_SPLIT_EDITOR_IN_GROUP_LAYOUT, SPLIT_EDITOR, MAXIMIZE_EDITOR_GROUP, UNMAXIMIZE_EDITOR_GROUP } from 'vs/workbench/browser/parts/editor/editorCommands'; import { inQuickPickContext, getQuickNavigateHandler } from 'vs/workbench/browser/quickaccess'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; @@ -214,6 +215,8 @@ registerAction2(NavigateBetweenGroupsAction); registerAction2(ResetGroupSizesAction); registerAction2(ToggleGroupSizesAction); registerAction2(MaximizeGroupAction); +registerAction2(UnmaximizeEditorGroupAction); +registerAction2(MaximizeGroupHideSidebarAction); registerAction2(MinimizeOtherGroupsAction); registerAction2(MoveEditorLeftInGroupAction); @@ -382,7 +385,9 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: SHOW_EDITORS_IN MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: CLOSE_EDITORS_IN_GROUP_COMMAND_ID, title: localize('closeAll', "Close All") }, group: '5_close', order: 10 }); MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: CLOSE_SAVED_EDITORS_COMMAND_ID, title: localize('closeAllSaved', "Close Saved") }, group: '5_close', order: 20 }); MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: TOGGLE_KEEP_EDITORS_COMMAND_ID, title: localize('togglePreviewMode', "Enable Preview Editors"), toggled: ContextKeyExpr.has('config.workbench.editor.enablePreview') }, group: '7_settings', order: 10 }); -MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: TOGGLE_LOCK_GROUP_COMMAND_ID, title: localize('lockGroup', "Lock Group"), toggled: ActiveEditorGroupLockedContext }, group: '8_lock', order: 10, when: MultipleEditorGroupsContext }); +MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: MAXIMIZE_EDITOR_GROUP, title: localize('maximizeGroup', "Maximize Group") }, group: '8_group_operations', order: 5, when: ContextKeyExpr.and(MaximizedEditorGroupContext.negate(), MultipleEditorGroupsContext) }); +MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: UNMAXIMIZE_EDITOR_GROUP, title: localize('unmaximizeGroup', "Unmaximize Group") }, group: '8_group_operations', order: 5, when: MaximizedEditorGroupContext }); +MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: TOGGLE_LOCK_GROUP_COMMAND_ID, title: localize('lockGroup', "Lock Group"), toggled: ActiveEditorGroupLockedContext }, group: '8_group_operations', order: 10, when: MultipleEditorGroupsContext }); function appendEditorToolItem(primary: ICommandAction, when: ContextKeyExpression | undefined, order: number, alternative?: ICommandAction, precondition?: ContextKeyExpression | undefined): void { const item: IMenuItem = { diff --git a/src/vs/workbench/browser/parts/editor/editor.ts b/src/vs/workbench/browser/parts/editor/editor.ts index 425343b6cf3..0ad637398b2 100644 --- a/src/vs/workbench/browser/parts/editor/editor.ts +++ b/src/vs/workbench/browser/parts/editor/editor.ts @@ -48,7 +48,7 @@ export const DEFAULT_EDITOR_PART_OPTIONS: IEditorPartOptions = { splitSizing: 'auto', splitOnDragAndDrop: true, centeredLayoutFixedWidth: false, - doubleClickTabToToggleEditorGroupSizes: true, + doubleClickTabToToggleEditorGroupSizes: 'expand', }; export function impactsEditorPartOptions(event: IConfigurationChangeEvent): boolean { @@ -116,7 +116,6 @@ function validateEditorPartOptions(options: IEditorPartOptions): void { 'restoreViewState', 'splitOnDragAndDrop', 'centeredLayoutFixedWidth', - 'doubleClickTabToToggleEditorGroupSizes' ]; for (const option of booleanOptions) { if (typeof option === 'string') { @@ -149,6 +148,7 @@ function validateEditorPartOptions(options: IEditorPartOptions): void { ['labelFormat', ['default', 'short', 'medium', 'long']], ['splitInGroupLayout', ['vertical', 'horizontal']], ['splitSizing', ['distribute', 'split', 'auto']], + ['doubleClickTabToToggleEditorGroupSizes', ['maximize', 'expand', 'off']] ]; for (const [option, allowed] of stringOptions) { if (typeof option === 'string') { @@ -235,6 +235,8 @@ export interface IEditorGroupsView { removeGroup(group: IEditorGroupView | GroupIdentifier): void; arrangeGroups(arrangement: GroupsArrangement, target?: IEditorGroupView | GroupIdentifier): void; + toggleMaximizeGroup(group?: IEditorGroupView | GroupIdentifier): void; + toggleExpandGroup(group?: IEditorGroupView | GroupIdentifier): void; } export interface IEditorGroupTitleHeight { diff --git a/src/vs/workbench/browser/parts/editor/editorActions.ts b/src/vs/workbench/browser/parts/editor/editorActions.ts index 86e2ec7c78a..38800f952db 100644 --- a/src/vs/workbench/browser/parts/editor/editorActions.ts +++ b/src/vs/workbench/browser/parts/editor/editorActions.ts @@ -13,7 +13,7 @@ import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/bro import { GoFilter, IHistoryService } from 'vs/workbench/services/history/common/history'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { CLOSE_EDITOR_COMMAND_ID, MOVE_ACTIVE_EDITOR_COMMAND_ID, ActiveEditorMoveCopyArguments, SPLIT_EDITOR_LEFT, SPLIT_EDITOR_RIGHT, SPLIT_EDITOR_UP, SPLIT_EDITOR_DOWN, splitEditor, LAYOUT_EDITOR_GROUPS_COMMAND_ID, UNPIN_EDITOR_COMMAND_ID, COPY_ACTIVE_EDITOR_COMMAND_ID, SPLIT_EDITOR } from 'vs/workbench/browser/parts/editor/editorCommands'; +import { CLOSE_EDITOR_COMMAND_ID, MOVE_ACTIVE_EDITOR_COMMAND_ID, ActiveEditorMoveCopyArguments, SPLIT_EDITOR_LEFT, SPLIT_EDITOR_RIGHT, SPLIT_EDITOR_UP, SPLIT_EDITOR_DOWN, splitEditor, LAYOUT_EDITOR_GROUPS_COMMAND_ID, UNPIN_EDITOR_COMMAND_ID, COPY_ACTIVE_EDITOR_COMMAND_ID, SPLIT_EDITOR, UNMAXIMIZE_EDITOR_GROUP, MAXIMIZE_EDITOR_GROUP, resolveCommandsContext, getCommandsContext } from 'vs/workbench/browser/parts/editor/editorCommands'; import { IEditorGroupsService, IEditorGroup, GroupsArrangement, GroupLocation, GroupDirection, preferredSideBySideGroupDirection, IFindGroupScope, GroupOrientation, EditorGroupLayout, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -33,7 +33,8 @@ import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { ILogService } from 'vs/platform/log/common/log'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; -import { ActiveEditorAvailableEditorIdsContext, ActiveEditorContext, ActiveEditorGroupEmptyContext } from 'vs/workbench/common/contextkeys'; +import { ActiveEditorAvailableEditorIdsContext, ActiveEditorContext, ActiveEditorGroupEmptyContext, MaximizedEditorGroupContext, MultipleEditorGroupsContext } from 'vs/workbench/common/contextkeys'; +import { URI } from 'vs/base/common/uri'; import { getActiveDocument } from 'vs/base/browser/dom'; class ExecuteCommandAction extends Action2 { @@ -1014,7 +1015,7 @@ export class MinimizeOtherGroupsAction extends Action2 { constructor() { super({ id: 'workbench.action.minimizeOtherEditors', - title: { value: localize('minimizeOtherEditorGroups', "Maximize Editor Group"), original: 'Maximize Editor Group' }, + title: { value: localize('minimizeOtherEditorGroups', "Expand Editor Group"), original: 'Expand Editor Group' }, f1: true, category: Categories.View }); @@ -1023,7 +1024,7 @@ export class MinimizeOtherGroupsAction extends Action2 { override async run(accessor: ServicesAccessor): Promise { const editorGroupService = accessor.get(IEditorGroupsService); - editorGroupService.arrangeGroups(GroupsArrangement.MAXIMIZE); + editorGroupService.arrangeGroups(GroupsArrangement.EXPAND); } } @@ -1059,7 +1060,7 @@ export class ToggleGroupSizesAction extends Action2 { override async run(accessor: ServicesAccessor): Promise { const editorGroupService = accessor.get(IEditorGroupsService); - editorGroupService.arrangeGroups(GroupsArrangement.TOGGLE); + editorGroupService.toggleExpandGroup(); } } @@ -1067,10 +1068,34 @@ export class MaximizeGroupAction extends Action2 { constructor() { super({ - id: 'workbench.action.maximizeEditor', - title: { value: localize('maximizeEditor', "Maximize Editor Group and Hide Side Bars"), original: 'Maximize Editor Group and Hide Side Bars' }, + id: MAXIMIZE_EDITOR_GROUP, + title: { value: localize('maximizeEditor', "Maximize Editor Group"), original: 'Maximize Editor Group' }, f1: true, - category: Categories.View + category: Categories.View, + precondition: ContextKeyExpr.and(MaximizedEditorGroupContext.negate(), MultipleEditorGroupsContext), + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.KeyM), + } + }); + } + + override async run(accessor: ServicesAccessor, resourceOrContext?: URI | IEditorCommandsContext, context?: IEditorCommandsContext): Promise { + const editorsGroupService = accessor.get(IEditorGroupsService); + const { group } = resolveCommandsContext(editorsGroupService, getCommandsContext(resourceOrContext, context)); + editorsGroupService.arrangeGroups(GroupsArrangement.MAXIMIZE, group); + } +} + +export class MaximizeGroupHideSidebarAction extends Action2 { + + constructor() { + super({ + id: 'workbench.action.maximizeEditorHideSidebar', + title: { value: localize('maximizeEditorHideSidebar', "Maximize Editor Group and Hide Side Bars"), original: 'Maximize Editor Group and Hide Side Bars' }, + f1: true, + category: Categories.View, + precondition: ContextKeyExpr.and(MaximizedEditorGroupContext.negate(), MultipleEditorGroupsContext) }); } @@ -1087,6 +1112,36 @@ export class MaximizeGroupAction extends Action2 { } } +export class UnmaximizeEditorGroupAction extends Action2 { + + constructor() { + super({ + id: UNMAXIMIZE_EDITOR_GROUP, + title: { value: localize('UnmaximizeEditorGroup', "Unmaximize Editor Group"), original: 'Unmaximize Editor Group' }, + f1: true, + category: Categories.View, + precondition: MaximizedEditorGroupContext, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.KeyM), + }, + menu: { + id: MenuId.EditorTitle, + order: -10000, // towards the front + group: 'navigation', + when: MaximizedEditorGroupContext + }, + icon: Codicon.screenFull, + toggled: MaximizedEditorGroupContext, + }); + } + + override async run(accessor: ServicesAccessor): Promise { + const editorGroupService = accessor.get(IEditorGroupsService); + editorGroupService.toggleMaximizeGroup(); + } +} + abstract class AbstractNavigateEditorAction extends Action2 { override async run(accessor: ServicesAccessor): Promise { diff --git a/src/vs/workbench/browser/parts/editor/editorCommands.ts b/src/vs/workbench/browser/parts/editor/editorCommands.ts index 44e916e0629..a6e013ebf4f 100644 --- a/src/vs/workbench/browser/parts/editor/editorCommands.ts +++ b/src/vs/workbench/browser/parts/editor/editorCommands.ts @@ -80,6 +80,9 @@ export const SPLIT_EDITOR_DOWN = 'workbench.action.splitEditorDown'; export const SPLIT_EDITOR_LEFT = 'workbench.action.splitEditorLeft'; export const SPLIT_EDITOR_RIGHT = 'workbench.action.splitEditorRight'; +export const MAXIMIZE_EDITOR_GROUP = 'workbench.action.maximizeEditorGroup'; +export const UNMAXIMIZE_EDITOR_GROUP = 'workbench.action.unmaximizeEditorGroup'; + export const SPLIT_EDITOR_IN_GROUP = 'workbench.action.splitEditorInGroup'; export const TOGGLE_SPLIT_EDITOR_IN_GROUP = 'workbench.action.toggleSplitEditorInGroup'; export const JOIN_EDITOR_IN_GROUP = 'workbench.action.joinEditorInGroup'; @@ -104,7 +107,8 @@ export const EDITOR_CORE_NAVIGATION_COMMANDS = [ SPLIT_EDITOR, CLOSE_EDITOR_COMMAND_ID, UNPIN_EDITOR_COMMAND_ID, - UNLOCK_GROUP_COMMAND_ID + UNLOCK_GROUP_COMMAND_ID, + UNMAXIMIZE_EDITOR_GROUP ]; export interface ActiveEditorMoveCopyArguments { @@ -1464,7 +1468,7 @@ function getEditorsContext(accessor: ServicesAccessor, resourceOrContext?: URI | }; } -function getCommandsContext(resourceOrContext?: URI | IEditorCommandsContext, context?: IEditorCommandsContext): IEditorCommandsContext | undefined { +export function getCommandsContext(resourceOrContext?: URI | IEditorCommandsContext, context?: IEditorCommandsContext): IEditorCommandsContext | undefined { if (URI.isUri(resourceOrContext)) { return context; } @@ -1480,7 +1484,7 @@ function getCommandsContext(resourceOrContext?: URI | IEditorCommandsContext, co return undefined; } -function resolveCommandsContext(editorGroupService: IEditorGroupsService, context?: IEditorCommandsContext): { group: IEditorGroup; editor?: EditorInput } { +export function resolveCommandsContext(editorGroupService: IEditorGroupsService, context?: IEditorCommandsContext): { group: IEditorGroup; editor?: EditorInput } { // Resolve from context let group = context && typeof context.groupId === 'number' ? editorGroupService.getGroup(context.groupId) : undefined; diff --git a/src/vs/workbench/browser/parts/editor/editorPart.ts b/src/vs/workbench/browser/parts/editor/editorPart.ts index c21abb83198..28e554f316c 100644 --- a/src/vs/workbench/browser/parts/editor/editorPart.ts +++ b/src/vs/workbench/browser/parts/editor/editorPart.ts @@ -101,6 +101,9 @@ export class EditorPart extends Part implements IEditorPart { private readonly _onDidChangeGroupLocked = this._register(new Emitter()); readonly onDidChangeGroupLocked = this._onDidChangeGroupLocked.event; + private readonly _onDidChangeGroupMaximized = this._register(new Emitter()); + readonly onDidChangeGroupMaximized = this._onDidChangeGroupMaximized.event; + private readonly _onDidActivateGroup = this._register(new Emitter()); readonly onDidActivateGroup = this._onDidActivateGroup.event; @@ -137,6 +140,7 @@ export class EditorPart extends Part implements IEditorPart { private centeredLayoutWidget!: CenteredViewLayout; private gridWidget!: SerializableGrid; + private readonly gridWidgetDisposables = this._register(new DisposableStore()); private readonly gridWidgetView = this._register(new GridWidgetView()); constructor( @@ -147,7 +151,7 @@ export class EditorPart extends Part implements IEditorPart { @IThemeService themeService: IThemeService, @IConfigurationService private readonly configurationService: IConfigurationService, @IStorageService storageService: IStorageService, - @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, ) { super(id, { hasTitle: false }, themeService, storageService, layoutService); @@ -369,21 +373,48 @@ export class EditorPart extends Part implements IEditorPart { this.gridWidget.distributeViewSizes(); break; case GroupsArrangement.MAXIMIZE: - this.gridWidget.maximizeViewSize(target); - break; - case GroupsArrangement.TOGGLE: - if (this.isGroupMaximized(target)) { - this.arrangeGroups(GroupsArrangement.EVEN); - } else { - this.arrangeGroups(GroupsArrangement.MAXIMIZE); + if (this.groups.length < 2) { + return; // need at least 2 groups to be maximized } - + this.gridWidget.maximizeView(target); + this.doSetGroupActive(target); + break; + case GroupsArrangement.EXPAND: + this.gridWidget.expandView(target); break; } } - isGroupMaximized(targetGroup: IEditorGroupView): boolean { - return this.gridWidget.isViewSizeMaximized(targetGroup); + toggleMaximizeGroup(target: IEditorGroupView = this.activeGroup): void { + if (this.hasMaximizedGroup()) { + this.unmaximizeGroup(); + } else { + this.arrangeGroups(GroupsArrangement.MAXIMIZE, target); + } + } + + toggleExpandGroup(target: IEditorGroupView = this.activeGroup): void { + if (this.isGroupExpanded(this.activeGroup)) { + this.arrangeGroups(GroupsArrangement.EVEN); + } else { + this.arrangeGroups(GroupsArrangement.EXPAND, target); + } + } + + private unmaximizeGroup(): void { + this.gridWidget.exitMaximizedView(); + } + + private hasMaximizedGroup(): boolean { + return this.gridWidget.hasMaximizedView(); + } + + private isGroupMaximized(targetGroup: IEditorGroupView): boolean { + return this.gridWidget.isViewMaximized(targetGroup); + } + + isGroupExpanded(targetGroup: IEditorGroupView): boolean { + return this.gridWidget.isViewExpanded(targetGroup); } setGroupOrientation(orientation: GroupOrientation): void { @@ -524,7 +555,7 @@ export class EditorPart extends Part implements IEditorPart { if (locationView.groupsView === this) { const restoreFocus = this.shouldRestoreFocus(locationView.element); - const shouldMaximize = this.groupViews.size > 1 && this.isGroupMaximized(locationView); + const shouldExpand = this.groupViews.size > 1 && this.isGroupExpanded(locationView); newGroupView = this.doCreateGroupView(groupToCopy); // Add to grid widget @@ -544,9 +575,9 @@ export class EditorPart extends Part implements IEditorPart { // Notify group index change given a new group was added this.notifyGroupIndexChange(); - // Maximize new group, if the reference view was previously maximized - if (shouldMaximize) { - this.arrangeGroups(GroupsArrangement.MAXIMIZE, newGroupView); + // Expand new group, if the reference view was previously expanded + if (shouldExpand) { + this.arrangeGroups(GroupsArrangement.EXPAND, newGroupView); } // Restore focus if we had it previously after completing the grid @@ -642,7 +673,7 @@ export class EditorPart extends Part implements IEditorPart { // Mark group as new active group.setActive(true); - // Maximize the group if it is currently minimized + // Expand the group if it is currently minimized this.doRestoreGroup(group); // Event @@ -657,9 +688,13 @@ export class EditorPart extends Part implements IEditorPart { private doRestoreGroup(group: IEditorGroupView): void { if (this.gridWidget) { + if (this.hasMaximizedGroup() && !this.isGroupMaximized(group)) { + this.unmaximizeGroup(); + } + const viewSize = this.gridWidget.getViewSize(group); if (viewSize.width === group.minimumWidth || viewSize.height === group.minimumHeight) { - this.arrangeGroups(GroupsArrangement.MAXIMIZE, group); + this.arrangeGroups(GroupsArrangement.EXPAND, group); } } } @@ -1032,6 +1067,10 @@ export class EditorPart extends Part implements IEditorPart { } centerLayout(active: boolean): void { + if (this.hasMaximizedGroup()) { + this.unmaximizeGroup(); + } + this.centeredLayoutWidget.activate(active); this._activeGroup.focus(); @@ -1158,6 +1197,10 @@ export class EditorPart extends Part implements IEditorPart { this._onDidChangeSizeConstraints.input = gridWidget.onDidChange; this._onDidScroll.input = gridWidget.onDidScroll; + this.gridWidgetDisposables.clear(); + this.gridWidgetDisposables.add(gridWidget.onDidChangeViewMaximized(maximized => this._onDidChangeGroupMaximized.fire(maximized))); + + this._onDidChangeGroupMaximized.fire(this.hasMaximizedGroup()); this.onDidSetGridWidget.fire(undefined); } diff --git a/src/vs/workbench/browser/parts/editor/editorParts.ts b/src/vs/workbench/browser/parts/editor/editorParts.ts index eb45a93eb54..92c96fced43 100644 --- a/src/vs/workbench/browser/parts/editor/editorParts.ts +++ b/src/vs/workbench/browser/parts/editor/editorParts.ts @@ -97,6 +97,7 @@ export class EditorParts extends Disposable implements IEditorGroupsService, IEd disposables.add(part.onDidRemoveGroup(group => this._onDidRemoveGroup.fire(group))); disposables.add(part.onDidMoveGroup(group => this._onDidMoveGroup.fire(group))); disposables.add(part.onDidActivateGroup(group => this._onDidActivateGroup.fire(group))); + disposables.add(part.onDidChangeGroupMaximized(maximized => this._onDidChangeGroupMaximized.fire(maximized))); disposables.add(part.onDidChangeGroupIndex(group => this._onDidChangeGroupIndex.fire(group))); disposables.add(part.onDidChangeGroupLocked(group => this._onDidChangeGroupLocked.fire(group))); @@ -176,6 +177,9 @@ export class EditorParts extends Disposable implements IEditorGroupsService, IEd private readonly _onDidChangeGroupLocked = this._register(new Emitter()); readonly onDidChangeGroupLocked = this._onDidChangeGroupLocked.event; + private readonly _onDidChangeGroupMaximized = this._register(new Emitter()); + readonly onDidChangeGroupMaximized = this._onDidChangeGroupMaximized.event; + //#endregion //#region Editor Groups Service @@ -227,11 +231,19 @@ export class EditorParts extends Disposable implements IEditorGroupsService, IEd } setSize(group: IEditorGroupView | GroupIdentifier, size: { width: number; height: number }): void { - return this.getPart(group).setSize(group, size); + this.getPart(group).setSize(group, size); } - arrangeGroups(arrangement: GroupsArrangement): void { - return this.activePart.arrangeGroups(arrangement); + arrangeGroups(arrangement: GroupsArrangement, group?: IEditorGroupView): void { + (group !== undefined ? this.getPart(group) : this.activePart).arrangeGroups(arrangement, group); + } + + toggleMaximizeGroup(group?: IEditorGroupView): void { + (group !== undefined ? this.getPart(group) : this.activePart).toggleMaximizeGroup(group); + } + + toggleExpandGroup(group?: IEditorGroupView): void { + (group !== undefined ? this.getPart(group) : this.activePart).toggleExpandGroup(group); } restoreGroup(group: IEditorGroupView | GroupIdentifier): IEditorGroupView { @@ -239,7 +251,7 @@ export class EditorParts extends Disposable implements IEditorGroupsService, IEd } applyLayout(layout: EditorGroupLayout): void { - return this.activePart.applyLayout(layout); + this.activePart.applyLayout(layout); } getLayout(): EditorGroupLayout { @@ -247,7 +259,7 @@ export class EditorParts extends Disposable implements IEditorGroupsService, IEd } centerLayout(active: boolean): void { - return this.activePart.centerLayout(active); + this.activePart.centerLayout(active); } isLayoutCentered(): boolean { @@ -259,7 +271,7 @@ export class EditorParts extends Disposable implements IEditorGroupsService, IEd } setGroupOrientation(orientation: GroupOrientation): void { - return this.activePart.setGroupOrientation(orientation); + this.activePart.setGroupOrientation(orientation); } findGroup(scope: IFindGroupScope, source?: IEditorGroupView | GroupIdentifier, wrap?: boolean): IEditorGroupView | undefined { @@ -275,7 +287,7 @@ export class EditorParts extends Disposable implements IEditorGroupsService, IEd } removeGroup(group: IEditorGroupView | GroupIdentifier): void { - return this.getPart(group).removeGroup(group); + this.getPart(group).removeGroup(group); } moveGroup(group: IEditorGroupView | GroupIdentifier, location: IEditorGroupView | GroupIdentifier, direction: GroupDirection): IEditorGroupView { diff --git a/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts index af5579120fd..a9ed03f97c2 100644 --- a/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts @@ -31,7 +31,7 @@ import { activeContrastBorder, contrastBorder, editorBackground } from 'vs/platf import { ResourcesDropHandler, DraggedEditorIdentifier, DraggedEditorGroupIdentifier, extractTreeDropData } from 'vs/workbench/browser/dnd'; import { Color } from 'vs/base/common/color'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { MergeGroupMode, IMergeGroupOptions, GroupsArrangement, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { MergeGroupMode, IMergeGroupOptions, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { addDisposableListener, EventType, EventHelper, Dimension, scheduleAtNextAnimationFrame, findParentWithClass, clearNode, DragAndDropObserver, isMouseEvent } from 'vs/base/browser/dom'; import { localize } from 'vs/nls'; import { IEditorGroupsView, EditorServiceImpl, IEditorGroupView, IInternalEditorOpenOptions, IEditorPartsView } from 'vs/workbench/browser/parts/editor/editor'; @@ -992,9 +992,17 @@ export class MultiEditorTabsControl extends EditorTabsControl { const editor = this.tabsModel.getEditorByIndex(tabIndex); if (editor && this.tabsModel.isPinned(editor)) { - if (this.groupsView.partOptions.doubleClickTabToToggleEditorGroupSizes) { - this.groupsView.arrangeGroups(GroupsArrangement.TOGGLE, this.groupView); + switch (this.groupsView.partOptions.doubleClickTabToToggleEditorGroupSizes) { + case 'maximize': + this.groupsView.toggleMaximizeGroup(this.groupView); + break; + case 'expand': + this.groupsView.toggleExpandGroup(this.groupView); + break; + case 'off': + break; } + } else { this.groupView.pinEditor(editor); } diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index c6c287adb4d..217d2623382 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -322,9 +322,15 @@ const registry = Registry.as(ConfigurationExtensions.Con 'description': localize('centeredLayoutDynamicWidth', "Controls whether the centered layout tries to maintain constant width when the window is resized.") }, 'workbench.editor.doubleClickTabToToggleEditorGroupSizes': { - 'type': 'boolean', - 'default': true, - 'markdownDescription': localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'doubleClickTabToToggleEditorGroupSizes' }, "Controls whether to maximize/restore the editor group when double clicking on a tab. This value is ignored when `#workbench.editor.showTabs#` is not set to `multiple`.") + 'type': 'string', + 'enum': ['maximize', 'expand', 'off'], + 'default': 'expand', + 'markdownDescription': localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'doubleClickTabToToggleEditorGroupSizes' }, "Controls how the editor group is resized when double clicking on a tab. This value is ignored when `#workbench.editor.showTabs#` is not set to `multiple`."), + 'enumDescriptions': [ + localize('workbench.editor.doubleClickTabToToggleEditorGroupSizes.maximize', "All other editor groups are hidden and the current editor group is maximized to take up the entire editor area."), + localize('workbench.editor.doubleClickTabToToggleEditorGroupSizes.expand', "The editor group takes as much space as possible by making all other editor groups as small as possible."), + localize('workbench.editor.doubleClickTabToToggleEditorGroupSizes.off', "No editor group is resized when double clicking on a tab.") + ] }, 'workbench.editor.limit.enabled': { 'type': 'boolean', @@ -780,16 +786,20 @@ Registry.as(Extensions.ConfigurationMigration) Registry.as(Extensions.ConfigurationMigration) .registerConfigurationMigrations([{ + key: 'workbench.editor.doubleClickTabToToggleEditorGroupSizes', migrateFn: (value: any) => { + if (typeof value === 'boolean') { + value = value ? 'expand' : 'off'; + } + return [['workbench.editor.doubleClickTabToToggleEditorGroupSizes', { value: value }]]; + } + }, { key: 'workbench.editor.showTabs', migrateFn: (value: any) => { if (typeof value === 'boolean') { value = value ? 'multiple' : 'single'; } return [['workbench.editor.showTabs', { value: value }]]; } - }]); - -Registry.as(Extensions.ConfigurationMigration) - .registerConfigurationMigrations([{ + }, { key: 'zenMode.hideTabs', migrateFn: (value: any) => { const result: ConfigurationKeyValuePairs = [['zenMode.hideTabs', { value: undefined }]]; if (value === true) { diff --git a/src/vs/workbench/common/contextkeys.ts b/src/vs/workbench/common/contextkeys.ts index c58fb9d32f7..d58629b85f7 100644 --- a/src/vs/workbench/common/contextkeys.ts +++ b/src/vs/workbench/common/contextkeys.ts @@ -79,6 +79,7 @@ export const SplitEditorsVertically = new RawContextKey('splitEditorsVe export const EditorAreaVisibleContext = new RawContextKey('editorAreaVisible', true, localize('editorAreaVisible', "Whether the editor area is visible")); export const EditorTabsVisibleContext = new RawContextKey('editorTabsVisible', true, localize('editorTabsVisible', "Whether editor tabs are visible")); export const EditorPinnedAndUnpinnedTabsContext = new RawContextKey('editorPinnedAndUnpinnedTabsVisible', false, true); +export const MaximizedEditorGroupContext = new RawContextKey('maximizedEditorGroup', false, localize('editorGroupMaximized', "Editor group is maximized")); //#endregion diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 60e2e472be2..e7854dea7b5 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -1139,7 +1139,7 @@ interface IEditorPartConfiguration { splitSizing?: 'auto' | 'split' | 'distribute'; splitOnDragAndDrop?: boolean; centeredLayoutFixedWidth?: boolean; - doubleClickTabToToggleEditorGroupSizes?: boolean; + doubleClickTabToToggleEditorGroupSizes?: 'maximize' | 'expand' | 'off'; limit?: IEditorPartLimitConfiguration; decorations?: IEditorPartDecorationsConfiguration; } diff --git a/src/vs/workbench/services/editor/common/editorGroupsService.ts b/src/vs/workbench/services/editor/common/editorGroupsService.ts index 1b48ec30bca..ba0188a48b8 100644 --- a/src/vs/workbench/services/editor/common/editorGroupsService.ts +++ b/src/vs/workbench/services/editor/common/editorGroupsService.ts @@ -43,23 +43,22 @@ export interface IFindGroupScope { } export const enum GroupsArrangement { + /** + * Make the current active group consume the entire + * editor area. + */ + MAXIMIZE, /** * Make the current active group consume the maximum * amount of space possible. */ - MAXIMIZE, + EXPAND, /** * Size all groups evenly. */ - EVEN, - - /** - * Will behave like MINIMIZE_OTHERS if the active - * group is not already maximized and EVEN otherwise - */ - TOGGLE + EVEN } export interface GroupLayoutArgument { @@ -215,6 +214,11 @@ export interface IEditorGroupsContainer { */ readonly onDidChangeGroupLocked: Event; + /** + * An event for when the maximized state of a group changes. + */ + readonly onDidChangeGroupMaximized: Event; + /** * An active group is the default location for new editors to open. */ @@ -273,7 +277,17 @@ export interface IEditorGroupsContainer { /** * Arrange all groups in the container according to the provided arrangement. */ - arrangeGroups(arrangement: GroupsArrangement): void; + arrangeGroups(arrangement: GroupsArrangement, target?: IEditorGroup | GroupIdentifier): void; + + /** + * Toggles the target goup size to maximize/unmaximize. + */ + toggleMaximizeGroup(group?: IEditorGroup | GroupIdentifier): void; + + /** + * Toggles the target goup size to expand/distribute even. + */ + toggleExpandGroup(group?: IEditorGroup | GroupIdentifier): void; /** * Applies the provided layout by either moving existing groups or creating new groups. diff --git a/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts b/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts index 02d63db45ad..0e8a0e2b313 100644 --- a/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import { workbenchInstantiationService, registerTestEditor, TestFileEditorInput, TestEditorPart, TestServiceAccessor, createEditorPart, ITestInstantiationService, workbenchTeardown } from 'vs/workbench/test/browser/workbenchTestServices'; -import { GroupDirection, GroupsOrder, MergeGroupMode, GroupOrientation, GroupLocation, isEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { GroupDirection, GroupsOrder, MergeGroupMode, GroupOrientation, GroupLocation, isEditorGroup, IEditorGroupsService, GroupsArrangement } from 'vs/workbench/services/editor/common/editorGroupsService'; import { CloseDirection, IEditorPartOptions, EditorsOrder, EditorInputCapabilities, GroupModelChangeKind, SideBySideEditor } from 'vs/workbench/common/editor'; import { URI } from 'vs/base/common/uri'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; @@ -1718,5 +1718,44 @@ suite('EditorGroupsService', () => { assert.strictEqual(rootGroup.isLocked, false); }); + test('maximize editor group', async () => { + const instantiationService = workbenchInstantiationService(undefined, disposables); + const [part] = await createPart(instantiationService); + + const rootGroup = part.activeGroup; + const editorPartSize = part.getSize(rootGroup); + + const rightGroup = part.addGroup(rootGroup, GroupDirection.RIGHT); + const rightBottomGroup = part.addGroup(rightGroup, GroupDirection.DOWN); + + const sizeRootGroup = part.getSize(rootGroup); + const sizeRightGroup = part.getSize(rightGroup); + const sizeRightBottomGroup = part.getSize(rightBottomGroup); + + let maximizedValue; + const maxiizeGroupEventDisposable = part.onDidChangeGroupMaximized((maximized) => { + maximizedValue = maximized; + }); + + part.arrangeGroups(GroupsArrangement.MAXIMIZE, rootGroup); + + // getSize() + assert.deepStrictEqual(part.getSize(rootGroup), editorPartSize); + assert.deepStrictEqual(part.getSize(rightGroup), { width: 0, height: 0 }); + assert.deepStrictEqual(part.getSize(rightBottomGroup), { width: 0, height: 0 }); + + assert.deepStrictEqual(maximizedValue, true); + + part.toggleMaximizeGroup(); + + // Size is restored + assert.deepStrictEqual(part.getSize(rootGroup), sizeRootGroup); + assert.deepStrictEqual(part.getSize(rightGroup), sizeRightGroup); + assert.deepStrictEqual(part.getSize(rightBottomGroup), sizeRightBottomGroup); + + assert.deepStrictEqual(maximizedValue, false); + maxiizeGroupEventDisposable.dispose(); + }); + ensureNoDisposablesAreLeakedInTestSuite(); }); diff --git a/src/vs/workbench/services/editor/test/browser/editorService.test.ts b/src/vs/workbench/services/editor/test/browser/editorService.test.ts index b645db393f7..f8571e68675 100644 --- a/src/vs/workbench/services/editor/test/browser/editorService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorService.test.ts @@ -1691,7 +1691,7 @@ suite('EditorService', () => { editor = await service.openEditor(input2, { pinned: true, activation: EditorActivation.ACTIVATE }, sideGroup); assert.strictEqual(part.activeGroup, sideGroup); - part.arrangeGroups(GroupsArrangement.MAXIMIZE); + part.arrangeGroups(GroupsArrangement.EXPAND); editor = await service.openEditor(input1, { pinned: true, preserveFocus: true, activation: EditorActivation.RESTORE }, rootGroup); assert.strictEqual(part.activeGroup, sideGroup); }); @@ -1711,13 +1711,13 @@ suite('EditorService', () => { assert.strictEqual(part.activeGroup, sideGroup); assert.notStrictEqual(rootGroup, sideGroup); - part.arrangeGroups(GroupsArrangement.MAXIMIZE, part.activeGroup); + part.arrangeGroups(GroupsArrangement.EXPAND, part.activeGroup); await rootGroup.closeEditor(input2); assert.strictEqual(part.activeGroup, sideGroup); - assert(!part.isGroupMaximized(rootGroup)); - assert(part.isGroupMaximized(part.activeGroup)); + assert(!part.isGroupExpanded(rootGroup)); + assert(part.isGroupExpanded(part.activeGroup)); }); test('active editor change / visible editor change events', async function () { diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index fa0abcf5d20..8f1a7006ef3 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -824,6 +824,7 @@ export class TestEditorGroupsService implements IEditorGroupsService { onDidMoveGroup: Event = Event.None; onDidChangeGroupIndex: Event = Event.None; onDidChangeGroupLocked: Event = Event.None; + onDidChangeGroupMaximized: Event = Event.None; onDidLayout: Event = Event.None; onDidChangeEditorPartOptions = Event.None; onDidScroll = Event.None; @@ -850,6 +851,8 @@ export class TestEditorGroupsService implements IEditorGroupsService { getSize(_group: number | IEditorGroup): { width: number; height: number } { return { width: 100, height: 100 }; } setSize(_group: number | IEditorGroup, _size: { width: number; height: number }): void { } arrangeGroups(_arrangement: GroupsArrangement): void { } + toggleMaximizeGroup(): void { } + toggleExpandGroup(): void { } applyLayout(_layout: EditorGroupLayout): void { } getLayout(): EditorGroupLayout { throw new Error('not implemented'); } setGroupOrientation(_orientation: GroupOrientation): void { } @@ -966,6 +969,8 @@ export class TestEditorGroupAccessor implements IEditorGroupsView { copyGroup(group: number | IEditorGroupView, location: number | IEditorGroupView, direction: GroupDirection): IEditorGroupView { throw new Error('Method not implemented.'); } removeGroup(group: number | IEditorGroupView): void { throw new Error('Method not implemented.'); } arrangeGroups(arrangement: GroupsArrangement, target?: number | IEditorGroupView | undefined): void { throw new Error('Method not implemented.'); } + toggleMaximizeGroup(group: number | IEditorGroupView): void { throw new Error('Method not implemented.'); } + toggleExpandGroup(group: number | IEditorGroupView): void { throw new Error('Method not implemented.'); } } export class TestEditorService implements EditorServiceImpl {