diff --git a/build/lib/stylelint/vscode-known-variables.json b/build/lib/stylelint/vscode-known-variables.json index 70475768de0..5f33f35fc19 100644 --- a/build/lib/stylelint/vscode-known-variables.json +++ b/build/lib/stylelint/vscode-known-variables.json @@ -737,6 +737,7 @@ "--tab-sizing-fixed-min-width", "--tab-sizing-fixed-max-width", "--editor-group-tab-height", + "--editor-group-tabs-height", "--testMessageDecorationFontFamily", "--testMessageDecorationFontSize", "--title-border-bottom-color", @@ -783,4 +784,4 @@ "--z-index-notebook-sticky-scroll", "--zoom-factor" ] -} \ No newline at end of file +} diff --git a/extensions/git/src/commitMessageProvider.ts b/extensions/git/src/commitMessageProvider.ts index 29f2da8c8b1..c774ea5dcec 100644 --- a/extensions/git/src/commitMessageProvider.ts +++ b/extensions/git/src/commitMessageProvider.ts @@ -20,7 +20,9 @@ export class TestCommitMessageProvider implements CommitMessageProvider { readonly icon = new ThemeIcon('rocket'); readonly title = 'Generate Commit Message (Test)'; - async provideCommitMessage(_: ApiRepository, __: string[], token: CancellationToken): Promise { + async provideCommitMessage(repository: ApiRepository, _: string[], token: CancellationToken): Promise { + console.log('Repository: ', repository.rootUri.fsPath); + if (token.isCancellationRequested) { return undefined; } @@ -62,12 +64,20 @@ export class GenerateCommitMessageActionButton { return this.state.isGenerating ? { icon: new ThemeIcon('debug-stop'), - command: { title: l10n.t('Cancel'), command: 'git.generateCommitMessageCancel' }, - enabled: this.state.enabled + command: { + title: l10n.t('Cancel'), + command: 'git.generateCommitMessageCancel', + arguments: [this.repository.sourceControl] + }, + enabled: this.state.enabled, } : { icon: this.commitMessageProviderRegistry.commitMessageProvider.icon ?? new ThemeIcon('sparkle'), - command: { title: this.commitMessageProviderRegistry.commitMessageProvider.title, command: 'git.generateCommitMessage' }, + command: { + title: this.commitMessageProviderRegistry.commitMessageProvider.title, + command: 'git.generateCommitMessage', + arguments: [this.repository.sourceControl] + }, enabled: this.state.enabled }; } diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index 2a3fc098d52..17e17c8c7f9 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -267,10 +267,9 @@ export interface IGitErrorData { gitArgs?: string[]; } -export class GitError { +export class GitError extends Error { error?: Error; - message: string; stdout?: string; stderr?: string; exitCode?: number; @@ -279,15 +278,9 @@ export class GitError { gitArgs?: string[]; constructor(data: IGitErrorData) { - if (data.error) { - this.error = data.error; - this.message = data.error.message; - } else { - this.error = undefined; - this.message = ''; - } + super(data.error?.message || data.message || 'Git error'); - this.message = this.message || data.message || 'Git error'; + this.error = data.error; this.stdout = data.stdout; this.stderr = data.stderr; this.exitCode = data.exitCode; @@ -296,7 +289,7 @@ export class GitError { this.gitArgs = data.gitArgs; } - toString(): string { + override toString(): string { let result = this.message + ' ' + JSON.stringify({ exitCode: this.exitCode, gitErrorCode: this.gitErrorCode, @@ -1173,7 +1166,9 @@ export class Repository { const element = elements.filter(file => file.file.toLowerCase() === relativePathLowercase)[0]; if (!element) { - throw new GitError({ message: 'Git relative path not found.' }); + throw new GitError({ + message: `Git relative path not found. Was looking for ${relativePathLowercase} among ${JSON.stringify(elements.map(({ file }) => file), null, 2)}`, + }); } return element.file; diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index 024e9988544..46290602d90 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -17,11 +17,15 @@ import { FileAccess, RemoteAuthorities, Schemas } from 'vs/base/common/network'; import * as platform from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; -export const { registerWindow, getWindows, onDidRegisterWindow } = (function () { +export const { registerWindow, getWindows, onDidRegisterWindow, onWillUnregisterWindow, onDidUnregisterWindow } = (function () { const windows = new Set([window]); const onDidRegisterWindow = new event.Emitter<{ window: Window & typeof globalThis; disposableStore: DisposableStore }>(); + const onDidUnregisterWindow = new event.Emitter(); + const onWillUnregisterWindow = new event.Emitter(); return { onDidRegisterWindow: onDidRegisterWindow.event, + onWillUnregisterWindow: onWillUnregisterWindow.event, + onDidUnregisterWindow: onDidUnregisterWindow.event, registerWindow(window: Window & typeof globalThis): IDisposable { if (windows.has(window)) { return Disposable.None; @@ -32,10 +36,15 @@ export const { registerWindow, getWindows, onDidRegisterWindow } = (function () const disposableStore = new DisposableStore(); disposableStore.add(toDisposable(() => { windows.delete(window); + onDidUnregisterWindow.fire(window); })); onDidRegisterWindow.fire({ window, disposableStore }); + disposableStore.add(addDisposableListener(window, 'beforeunload', () => { + onWillUnregisterWindow.fire(window); + })); + return disposableStore; }, getWindows(): Iterable { 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/common/objects.ts b/src/vs/base/common/objects.ts index 897a9fd8249..6fccb6f8589 100644 --- a/src/vs/base/common/objects.ts +++ b/src/vs/base/common/objects.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { isTypedArray, isObject, isUndefinedOrNull } from 'vs/base/common/types'; +import { isTypedArray, isObject, isUndefinedOrNull, OptionalBooleanKey, OptionalNumberKey, OptionalStringKey } from 'vs/base/common/types'; export function deepClone(obj: T): T { if (!obj || typeof obj !== 'object') { @@ -261,3 +261,34 @@ export function createProxyObject(methodNames: string[], invok } return result; } + +export function ensureOptionalBooleanValue(obj: T, key: OptionalBooleanKey, defaultValue: boolean | undefined): void { + if (typeof key !== 'string') { + return; + } + + if (obj[key] !== undefined && typeof obj[key] !== 'boolean') { + obj[key] = defaultValue as any; + } +} + +export function ensureOptionalNumberValue(obj: T, key: OptionalNumberKey, defaultValue: number | undefined): void { + if (typeof key !== 'string') { + return; + } + + if (obj[key] !== undefined && typeof obj[key] !== 'number') { + obj[key] = defaultValue as any; + } +} + +export function ensureOptionalStringValue(obj: T, key: OptionalStringKey, allowed: string[], defaultValue: string | undefined): void { + if (typeof key !== 'string') { + return; + } + + const value = obj[key]; + if (value !== undefined && (typeof value !== 'string' || !allowed.includes(value))) { + obj[key] = defaultValue as any; + } +} diff --git a/src/vs/base/common/observableInternal/base.ts b/src/vs/base/common/observableInternal/base.ts index 321ec278ff2..bc7f900caed 100644 --- a/src/vs/base/common/observableInternal/base.ts +++ b/src/vs/base/common/observableInternal/base.ts @@ -231,6 +231,15 @@ export function transaction(fn: (tx: ITransaction) => void, getDebugName?: () => } } +export async function asyncTransaction(fn: (tx: ITransaction) => Promise, getDebugName?: () => string): Promise { + const tx = new TransactionImpl(fn, getDebugName); + try { + await fn(tx); + } finally { + tx.finish(); + } +} + export function subtransaction(tx: ITransaction | undefined, fn: (tx: ITransaction) => void, getDebugName?: () => string): void { if (!tx) { transaction(fn, getDebugName); diff --git a/src/vs/base/common/types.ts b/src/vs/base/common/types.ts index 529d5e4e7be..fc53cb11abb 100644 --- a/src/vs/base/common/types.ts +++ b/src/vs/base/common/types.ts @@ -227,3 +227,15 @@ export type Mutable = { * A single object or an array of the objects. */ export type SingleOrMany = T | T[]; + +export type OptionalBooleanKey = { + [K in keyof T]: T[K] extends boolean | undefined ? K : never; +}[keyof T]; + +export type OptionalNumberKey = { + [K in keyof T]: T[K] extends number | undefined ? K : never; +}[keyof T]; + +export type OptionalStringKey = { + [K in keyof T]: T[K] extends string | undefined ? K : never; +}[keyof T]; 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/base/test/common/objects.test.ts b/src/vs/base/test/common/objects.test.ts index 867615abce6..af797f47eea 100644 --- a/src/vs/base/test/common/objects.test.ts +++ b/src/vs/base/test/common/objects.test.ts @@ -227,4 +227,84 @@ suite('Objects', () => { assert.strictEqual(obj1.mIxEdCaSe, objects.getCaseInsensitive(obj1, 'MIXEDCASE')); assert.strictEqual(obj1.mIxEdCaSe, objects.getCaseInsensitive(obj1, 'mixedcase')); }); + + test('ensureOptionalBooleanValue', () => { + const obj: any = { + a: true, + b: false, + c: undefined, + d: 5, + e: 'foo' + }; + + objects.ensureOptionalBooleanValue(obj, 'a', false); + assert.strictEqual(obj.a, true); + + objects.ensureOptionalBooleanValue(obj, 'b', true); + assert.strictEqual(obj.b, false); + + objects.ensureOptionalBooleanValue(obj, 'c', true); + assert.strictEqual(obj.c, undefined); + + objects.ensureOptionalBooleanValue(obj, 'd', true); + assert.strictEqual(obj.d, true); + + objects.ensureOptionalBooleanValue(obj, 'e', true); + assert.strictEqual(obj.e, true); + }); + + test('ensureOptionalNumberValue', () => { + const obj: any = { + a: 1, + b: 0, + c: undefined, + d: true, + e: 'foo' + }; + + objects.ensureOptionalNumberValue(obj, 'a', 0); + assert.strictEqual(obj.a, 1); + + objects.ensureOptionalNumberValue(obj, 'b', 1); + assert.strictEqual(obj.b, 0); + + objects.ensureOptionalNumberValue(obj, 'c', 1); + assert.strictEqual(obj.c, undefined); + + objects.ensureOptionalNumberValue(obj, 'd', 1); + assert.strictEqual(obj.d, 1); + + objects.ensureOptionalNumberValue(obj, 'e', 1); + assert.strictEqual(obj.e, 1); + }); + + test('ensureOptionalStringValue', () => { + const obj: any = { + a: 'hello', + b: 'world', + c: undefined, + d: 'earth', + e: 5, + f: true + }; + + objects.ensureOptionalStringValue(obj, 'a', ['hello', 'world'], 'world'); + assert.strictEqual(obj.a, 'hello'); + + objects.ensureOptionalStringValue(obj, 'b', ['hello', 'world'], 'hello'); + assert.strictEqual(obj.b, 'world'); + + objects.ensureOptionalStringValue(obj, 'c', ['hello', 'world'], 'world'); + assert.strictEqual(obj.c, undefined); + + objects.ensureOptionalStringValue(obj, 'd', ['hello', 'world'], 'world'); + assert.strictEqual(obj.d, 'world'); + + objects.ensureOptionalStringValue(obj, 'e', ['hello', 'world'], 'world'); + assert.strictEqual(obj.e, 'world'); + + objects.ensureOptionalStringValue(obj, 'f', ['hello', 'world'], 'world'); + assert.strictEqual(obj.f, 'world'); + + }); }); diff --git a/src/vs/editor/contrib/inlineCompletions/browser/commands.ts b/src/vs/editor/contrib/inlineCompletions/browser/commands.ts index ac7535f9231..9727bec2359 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/commands.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/commands.ts @@ -5,6 +5,7 @@ import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { transaction } from 'vs/base/common/observable'; +import { asyncTransaction } from 'vs/base/common/observableInternal/base'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorAction, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; @@ -72,7 +73,11 @@ export class TriggerInlineSuggestionAction extends EditorAction { public async run(accessor: ServicesAccessor | undefined, editor: ICodeEditor): Promise { const controller = InlineCompletionsController.get(editor); - controller?.model.get()?.triggerExplicitly(); + await asyncTransaction(async tx => { + /** @description triggerExplicitly from command */ + await controller?.model.get()?.triggerExplicitly(tx); + controller?.playAudioCue(tx); + }); } } diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController.ts index f9697ac3232..4f977c5df2e 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController.ts @@ -6,7 +6,7 @@ import { alert } from 'vs/base/browser/ui/aria/aria'; import { Event } from 'vs/base/common/event'; import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; -import { ITransaction, autorun, constObservable, disposableObservableValue, observableFromEvent, observableValue, transaction } from 'vs/base/common/observable'; +import { ITransaction, autorun, autorunHandleChanges, constObservable, disposableObservableValue, observableFromEvent, observableSignal, observableValue, transaction } from 'vs/base/common/observable'; import { CoreEditingCommands } from 'vs/editor/browser/coreCommands'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; @@ -38,9 +38,9 @@ export class InlineCompletionsController extends Disposable { } public readonly model = disposableObservableValue('inlineCompletionModel', undefined); - private readonly textModelVersionId = observableValue(this, -1); - private readonly cursorPosition = observableValue(this, new Position(1, 1)); - private readonly suggestWidgetAdaptor = this._register(new SuggestWidgetAdaptor( + private readonly _textModelVersionId = observableValue(this, -1); + private readonly _cursorPosition = observableValue(this, new Position(1, 1)); + private readonly _suggestWidgetAdaptor = this._register(new SuggestWidgetAdaptor( this.editor, () => this.model.get()?.selectedInlineCompletion.get()?.toSingleTextEdit(undefined), (tx) => this.updateObservables(tx, VersionIdChangeReason.Other), @@ -54,32 +54,34 @@ export class InlineCompletionsController extends Disposable { )); private readonly _enabled = observableFromEvent(this.editor.onDidChangeConfiguration, () => this.editor.getOption(EditorOption.inlineSuggest).enabled); - private ghostTextWidget = this._register(this.instantiationService.createInstance(GhostTextWidget, this.editor, { + private _ghostTextWidget = this._register(this._instantiationService.createInstance(GhostTextWidget, this.editor, { ghostText: this.model.map((v, reader) => /** ghostText */ v?.ghostText.read(reader)), minReservedLineCount: constObservable(0), targetTextModel: this.model.map(v => v?.textModel), })); - private readonly _debounceValue = this.debounceService.for( - this.languageFeaturesService.inlineCompletionsProvider, + private readonly _debounceValue = this._debounceService.for( + this._languageFeaturesService.inlineCompletionsProvider, 'InlineCompletionsDebounce', { min: 50, max: 50 } ); + private readonly _playAudioCueSignal = observableSignal(this); + constructor( public readonly editor: ICodeEditor, - @IInstantiationService private readonly instantiationService: IInstantiationService, - @IContextKeyService private readonly contextKeyService: IContextKeyService, - @IConfigurationService private readonly configurationService: IConfigurationService, - @ICommandService private readonly commandService: ICommandService, - @ILanguageFeatureDebounceService private readonly debounceService: ILanguageFeatureDebounceService, - @ILanguageFeaturesService private readonly languageFeaturesService: ILanguageFeaturesService, - @IAudioCueService private readonly audioCueService: IAudioCueService, - @IKeybindingService private readonly _keybindingService: IKeybindingService + @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IContextKeyService private readonly _contextKeyService: IContextKeyService, + @IConfigurationService private readonly _configurationService: IConfigurationService, + @ICommandService private readonly _commandService: ICommandService, + @ILanguageFeatureDebounceService private readonly _debounceService: ILanguageFeatureDebounceService, + @ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService, + @IAudioCueService private readonly _audioCueService: IAudioCueService, + @IKeybindingService private readonly _keybindingService: IKeybindingService, ) { super(); - this._register(new InlineCompletionContextKeys(this.contextKeyService, this.model)); + this._register(new InlineCompletionContextKeys(this._contextKeyService, this.model)); this._register(Event.runAndSubscribe(editor.onDidChangeModel, () => transaction(tx => { /** @description onDidChangeModel */ this.model.set(undefined, tx); @@ -87,12 +89,12 @@ export class InlineCompletionsController extends Disposable { const textModel = editor.getModel(); if (textModel) { - const model = instantiationService.createInstance( + const model = _instantiationService.createInstance( InlineCompletionsModel, textModel, - this.suggestWidgetAdaptor.selectedItem, - this.cursorPosition, - this.textModelVersionId, + this._suggestWidgetAdaptor.selectedItem, + this._cursorPosition, + this._textModelVersionId, this._debounceValue, observableFromEvent(editor.onDidChangeConfiguration, () => editor.getOption(EditorOption.suggest).preview), observableFromEvent(editor.onDidChangeConfiguration, () => editor.getOption(EditorOption.suggest).previewMode), @@ -130,7 +132,7 @@ export class InlineCompletionsController extends Disposable { } }))); - this._register(this.commandService.onDidExecuteCommand((e) => { + this._register(this._commandService.onDidExecuteCommand((e) => { // These commands don't trigger onDidType. const commands = new Set([ CoreEditingCommands.Tab.id, @@ -149,7 +151,7 @@ export class InlineCompletionsController extends Disposable { this._register(this.editor.onDidBlurEditorWidget(() => { // This is a hidden setting very useful for debugging - if (this.contextKeyService.getContextKeyValue('accessibleViewIsShown') || this.configurationService.getValue('editor.inlineSuggest.keepOnBlur') || + if (this._contextKeyService.getContextKeyValue('accessibleViewIsShown') || this._configurationService.getValue('editor.inlineSuggest.keepOnBlur') || editor.getOption(EditorOption.inlineSuggest).keepOnBlur) { return; } @@ -167,19 +169,28 @@ export class InlineCompletionsController extends Disposable { const state = this.model.read(reader)?.state.read(reader); if (state?.suggestItem) { if (state.ghostText.lineCount >= 2) { - this.suggestWidgetAdaptor.forceRenderingAbove(); + this._suggestWidgetAdaptor.forceRenderingAbove(); } } else { - this.suggestWidgetAdaptor.stopForceRenderingAbove(); + this._suggestWidgetAdaptor.stopForceRenderingAbove(); } })); this._register(toDisposable(() => { - this.suggestWidgetAdaptor.stopForceRenderingAbove(); + this._suggestWidgetAdaptor.stopForceRenderingAbove(); })); let lastInlineCompletionId: string | undefined = undefined; - this._register(autorun(reader => { + this._register(autorunHandleChanges({ + handleChange: (context, changeSummary) => { + if (context.didChange(this._playAudioCueSignal)) { + lastInlineCompletionId = undefined; + } + return true; + }, + }, async reader => { /** @description play audio cue & read suggestion */ + this._playAudioCueSignal.read(reader); + const model = this.model.read(reader); const state = model?.state.read(reader); if (!model || !state || !state.inlineCompletion) { @@ -190,7 +201,7 @@ export class InlineCompletionsController extends Disposable { if (state.inlineCompletion.semanticId !== lastInlineCompletionId) { lastInlineCompletionId = state.inlineCompletion.semanticId; const lineText = model.textModel.getLineContent(state.ghostText.lineNumber); - this.audioCueService.playAudioCue(AudioCue.inlineSuggestion).then(() => { + this._audioCueService.playAudioCue(AudioCue.inlineSuggestion).then(() => { if (this.editor.getOption(EditorOption.screenReaderAnnounceInlineSuggestion)) { this.provideScreenReaderUpdate(state.ghostText.renderForScreenReader(lineText)); } @@ -198,17 +209,21 @@ export class InlineCompletionsController extends Disposable { } })); - this._register(new InlineCompletionsHintsWidget(this.editor, this.model, this.instantiationService)); - this._register(this.configurationService.onDidChangeConfiguration(e => { + this._register(new InlineCompletionsHintsWidget(this.editor, this.model, this._instantiationService)); + this._register(this._configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration('accessibility.verbosity.inlineCompletions')) { - this.editor.updateOptions({ inlineCompletionsAccessibilityVerbose: this.configurationService.getValue('accessibility.verbosity.inlineCompletions') }); + this.editor.updateOptions({ inlineCompletionsAccessibilityVerbose: this._configurationService.getValue('accessibility.verbosity.inlineCompletions') }); } })); - this.editor.updateOptions({ inlineCompletionsAccessibilityVerbose: this.configurationService.getValue('accessibility.verbosity.inlineCompletions') }); + this.editor.updateOptions({ inlineCompletionsAccessibilityVerbose: this._configurationService.getValue('accessibility.verbosity.inlineCompletions') }); + } + + public playAudioCue(tx: ITransaction) { + this._playAudioCueSignal.trigger(tx); } private provideScreenReaderUpdate(content: string): void { - const accessibleViewShowing = this.contextKeyService.getContextKeyValue('accessibleViewIsShown'); + const accessibleViewShowing = this._contextKeyService.getContextKeyValue('accessibleViewIsShown'); const accessibleViewKeybinding = this._keybindingService.lookupKeybinding('editor.action.accessibleView'); let hint: string | undefined; if (!accessibleViewShowing && accessibleViewKeybinding && this.editor.getOption(EditorOption.inlineCompletionsAccessibilityVerbose)) { @@ -224,8 +239,8 @@ export class InlineCompletionsController extends Disposable { */ private updateObservables(tx: ITransaction, changeReason: VersionIdChangeReason): void { const newModel = this.editor.getModel(); - this.textModelVersionId.set(newModel?.getVersionId() ?? -1, tx, changeReason); - this.cursorPosition.set(this.editor.getPosition() ?? new Position(1, 1), tx); + this._textModelVersionId.set(newModel?.getVersionId() ?? -1, tx, changeReason); + this._cursorPosition.set(this.editor.getPosition() ?? new Position(1, 1), tx); } public shouldShowHoverAt(range: Range) { @@ -237,7 +252,7 @@ export class InlineCompletionsController extends Disposable { } public shouldShowHoverAtViewZone(viewZoneId: string): boolean { - return this.ghostTextWidget.ownsViewZone(viewZoneId); + return this._ghostTextWidget.ownsViewZone(viewZoneId); } public hide() { diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts index 2aa8e7cd21f..1a7663d40ee 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts @@ -35,7 +35,7 @@ export enum VersionIdChangeReason { export class InlineCompletionsModel extends Disposable { private readonly _source = this._register(this._instantiationService.createInstance(InlineCompletionsSource, this.textModel, this.textModelVersionId, this._debounceValue)); private readonly _isActive = observableValue(this, false); - private readonly _forceUpdate = observableSignal('forceUpdate'); + readonly _forceUpdateSignal = observableSignal('forceUpdate'); // We use a semantic id to keep the same inline completion selected even if the provider reorders the completions. private readonly _selectedInlineCompletionId = observableValue(this, undefined); @@ -92,13 +92,13 @@ export class InlineCompletionsModel extends Disposable { /** @description fetch inline completions */ if (ctx.didChange(this.textModelVersionId) && this._preserveCurrentCompletionReasons.has(ctx.change)) { changeSummary.preserveCurrentCompletion = true; - } else if (ctx.didChange(this._forceUpdate)) { + } else if (ctx.didChange(this._forceUpdateSignal)) { changeSummary.inlineCompletionTriggerKind = ctx.change; } return true; }, }, (reader, changeSummary) => { - this._forceUpdate.read(reader); + this._forceUpdateSignal.read(reader); const shouldUpdate = (this._enabled.read(reader) && this.selectedSuggestItem.read(reader)) || this._isActive.read(reader); if (!shouldUpdate) { this._source.cancelUpdate(); @@ -140,7 +140,7 @@ export class InlineCompletionsModel extends Disposable { public async triggerExplicitly(tx?: ITransaction): Promise { subtransaction(tx, tx => { this._isActive.set(true, tx); - this._forceUpdate.trigger(tx, InlineCompletionTriggerKind.Explicit); + this._forceUpdateSignal.trigger(tx, InlineCompletionTriggerKind.Explicit); }); await this._fetchInlineCompletions.get(); } diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 2dafc74b618..081100f8528 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -110,7 +110,6 @@ export class MenuId { static readonly SCMResourceFolderContext = new MenuId('SCMResourceFolderContext'); static readonly SCMResourceGroupContext = new MenuId('SCMResourceGroupContext'); static readonly SCMSourceControl = new MenuId('SCMSourceControl'); - static readonly SCMInputBox = new MenuId('SCMInputBox'); static readonly SCMTitle = new MenuId('SCMTitle'); static readonly SearchContext = new MenuId('SearchContext'); static readonly SearchActionMenu = new MenuId('SearchActionContext'); diff --git a/src/vs/platform/quickinput/browser/quickInputController.ts b/src/vs/platform/quickinput/browser/quickInputController.ts index acf5b64feb0..f0040a3206b 100644 --- a/src/vs/platform/quickinput/browser/quickInputController.ts +++ b/src/vs/platform/quickinput/browser/quickInputController.ts @@ -59,6 +59,15 @@ export class QuickInputController extends Disposable { this.parentElement = options.container; this.styles = options.styles; this._register(Event.runAndSubscribe(dom.onDidRegisterWindow, ({ window, disposableStore }) => this.registerKeyModsListeners(window, disposableStore), { window, disposableStore: this._store })); + this._register(dom.onWillUnregisterWindow(window => { + if (this.ui && dom.getWindow(this.ui.container) === window) { + // The window this quick input is contained in is about to + // close, so we have to make sure to reparent it back to an + // existing parent to not loose functionality. + // (https://github.com/microsoft/vscode/issues/195870) + this.reparentUI(this.layoutService.container); + } + })); } private registerKeyModsListeners(window: Window, disposables: DisposableStore): void { @@ -74,11 +83,10 @@ export class QuickInputController extends Disposable { private getUI() { if (this.ui) { - // In order to support aux windows, re-parent the controller if the original event is - // from a different document + // In order to support aux windows, re-parent the controller + // if the original event is from a different document if (this.parentElement.ownerDocument !== this.layoutService.activeContainer.ownerDocument) { - this.parentElement = this.layoutService.activeContainer; - dom.append(this.parentElement, this.ui.container); + this.reparentUI(this.layoutService.activeContainer); } return this.ui; @@ -306,6 +314,13 @@ export class QuickInputController extends Disposable { return this.ui; } + private reparentUI(container: HTMLElement): void { + if (this.ui) { + this.parentElement = container; + dom.append(this.parentElement, this.ui.container); + } + } + pick>(picks: Promise[]> | QuickPickInput[], options: O = {}, token: CancellationToken = CancellationToken.None): Promise<(O extends { canPickMany: true } ? T[] : T) | undefined> { type R = (O extends { canPickMany: true } ? T[] : T) | undefined; return new Promise((doResolve, reject) => { diff --git a/src/vs/workbench/browser/actions/layoutActions.ts b/src/vs/workbench/browser/actions/layoutActions.ts index 853891af351..af6791a1ecf 100644 --- a/src/vs/workbench/browser/actions/layoutActions.ts +++ b/src/vs/workbench/browser/actions/layoutActions.ts @@ -489,7 +489,7 @@ export class HideEditorTabsAction extends Action2 { }); } - override run(accessor: ServicesAccessor): Promise { + run(accessor: ServicesAccessor): Promise { const configurationService = accessor.get(IConfigurationService); return configurationService.updateValue('workbench.editor.showTabs', 'none'); } @@ -515,7 +515,7 @@ export class ShowMultipleEditorTabsAction extends Action2 { }); } - override run(accessor: ServicesAccessor): Promise { + run(accessor: ServicesAccessor): Promise { const configurationService = accessor.get(IConfigurationService); return configurationService.updateValue('workbench.editor.showTabs', 'multiple'); } @@ -541,7 +541,7 @@ export class ShowSingleEditorTabAction extends Action2 { }); } - override run(accessor: ServicesAccessor): Promise { + run(accessor: ServicesAccessor): Promise { const configurationService = accessor.get(IConfigurationService); return configurationService.updateValue('workbench.editor.showTabs', 'single'); } @@ -550,13 +550,11 @@ registerAction2(ShowSingleEditorTabAction); // --- Toggle Pinned Tabs On Separate Row -export class ToggleSeparatePinnedTabsAction extends Action2 { - - static readonly ID = 'workbench.action.toggleSeparatePinnedEditorTabs'; +registerAction2(class extends Action2 { constructor() { super({ - id: ToggleSeparatePinnedTabsAction.ID, + id: 'workbench.action.toggleSeparatePinnedEditorTabs', title: { value: localize('toggleSeparatePinnedEditorTabs', "Separate Pinned Editor Tabs"), original: 'Separate Pinned Editor Tabs' @@ -567,7 +565,7 @@ export class ToggleSeparatePinnedTabsAction extends Action2 { }); } - override run(accessor: ServicesAccessor): Promise { + run(accessor: ServicesAccessor): Promise { const configurationService = accessor.get(IConfigurationService); const oldettingValue = configurationService.getValue('workbench.editor.pinnedTabsOnSeparateRow'); @@ -575,8 +573,7 @@ export class ToggleSeparatePinnedTabsAction extends Action2 { return configurationService.updateValue('workbench.editor.pinnedTabsOnSeparateRow', newSettingValue); } -} -registerAction2(ToggleSeparatePinnedTabsAction); +}); // --- Toggle Zen Mode 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/media/part.css b/src/vs/workbench/browser/media/part.css index a0628d68945..526b2b541de 100644 --- a/src/vs/workbench/browser/media/part.css +++ b/src/vs/workbench/browser/media/part.css @@ -87,7 +87,7 @@ } .monaco-workbench .part > .content > .monaco-progress-container, -.monaco-workbench .part.editor > .content .monaco-progress-container { +.monaco-workbench .part.editor > .content .editor-group-container > .monaco-progress-container { position: absolute; left: 0; top: 33px; /* at the bottom of the 35px height title container */ @@ -96,10 +96,6 @@ } .monaco-workbench .part > .content > .monaco-progress-container .progress-bit, -.monaco-workbench - .part.editor - > .content - .monaco-progress-container - .progress-bit { +.monaco-workbench .part.editor > .content .editor-group-container > .monaco-progress-container .progress-bit { height: 2px; } diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index 1d76000dfcf..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, EditorPinnedAndUnpinnedTabsContext + 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'; @@ -64,7 +65,7 @@ import { Codicon } from 'vs/base/common/codicons'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; import { UntitledTextEditorInputSerializer, UntitledTextEditorWorkingCopyEditorHandler } from 'vs/workbench/services/untitled/common/untitledTextEditorHandler'; import { DynamicEditorConfigurations } from 'vs/workbench/browser/parts/editor/editorConfiguration'; -import { HideEditorTabsAction, ShowMultipleEditorTabsAction, ShowSingleEditorTabAction, ToggleSeparatePinnedTabsAction } from 'vs/workbench/browser/actions/layoutActions'; +import { HideEditorTabsAction, ShowMultipleEditorTabsAction, ShowSingleEditorTabAction } from 'vs/workbench/browser/actions/layoutActions'; import product from 'vs/platform/product/common/product'; import { ICommandAction } from 'vs/platform/action/common/action'; @@ -214,6 +215,8 @@ registerAction2(NavigateBetweenGroupsAction); registerAction2(ResetGroupSizesAction); registerAction2(ToggleGroupSizesAction); registerAction2(MaximizeGroupAction); +registerAction2(UnmaximizeEditorGroupAction); +registerAction2(MaximizeGroupHideSidebarAction); registerAction2(MinimizeOtherGroupsAction); registerAction2(MoveEditorLeftInGroupAction); @@ -354,11 +357,10 @@ MenuRegistry.appendMenuItem(MenuId.EditorTabsBarContext, { command: { id: SPLIT_ MenuRegistry.appendMenuItem(MenuId.EditorTabsBarContext, { command: { id: SPLIT_EDITOR_DOWN, title: localize('splitDown', "Split Down") }, group: '2_split', order: 20 }); MenuRegistry.appendMenuItem(MenuId.EditorTabsBarContext, { command: { id: SPLIT_EDITOR_LEFT, title: localize('splitLeft', "Split Left") }, group: '2_split', order: 30 }); MenuRegistry.appendMenuItem(MenuId.EditorTabsBarContext, { command: { id: SPLIT_EDITOR_RIGHT, title: localize('splitRight', "Split Right") }, group: '2_split', order: 40 }); -MenuRegistry.appendMenuItem(MenuId.EditorTabsBarContext, { submenu: MenuId.EditorTabsBarShowTabsSubmenu, title: localize('showTabs', "Show Tabs"), group: '3_config', order: 10 }); +MenuRegistry.appendMenuItem(MenuId.EditorTabsBarContext, { submenu: MenuId.EditorTabsBarShowTabsSubmenu, title: localize('tabBar', "Tab bar"), group: '3_config', order: 10 }); MenuRegistry.appendMenuItem(MenuId.EditorTabsBarShowTabsSubmenu, { command: { id: ShowMultipleEditorTabsAction.ID, title: localize('multipleTabs', "Multiple Tabs"), toggled: ContextKeyExpr.equals('config.workbench.editor.showTabs', 'multiple') }, group: '1_config', order: 10 }); MenuRegistry.appendMenuItem(MenuId.EditorTabsBarShowTabsSubmenu, { command: { id: ShowSingleEditorTabAction.ID, title: localize('singleTab', "Single Tab"), toggled: ContextKeyExpr.equals('config.workbench.editor.showTabs', 'single') }, group: '1_config', order: 20 }); -MenuRegistry.appendMenuItem(MenuId.EditorTabsBarShowTabsSubmenu, { command: { id: HideEditorTabsAction.ID, title: localize('hideTabBar', "Hide Tab Bar"), toggled: ContextKeyExpr.equals('config.workbench.editor.showTabs', 'none') }, group: '1_config', order: 30 }); -MenuRegistry.appendMenuItem(MenuId.EditorTabsBarContext, { command: { id: ToggleSeparatePinnedTabsAction.ID, title: localize('toggleSeparatePinnedEditorTabs', "Separate Pinned Editor Tabs"), toggled: ContextKeyExpr.has('config.workbench.editor.pinnedTabsOnSeparateRow') }, when: EditorPinnedAndUnpinnedTabsContext, group: '3_config', order: 20 }); +MenuRegistry.appendMenuItem(MenuId.EditorTabsBarShowTabsSubmenu, { command: { id: HideEditorTabsAction.ID, title: localize('hideTabBar', "Hide"), toggled: ContextKeyExpr.equals('config.workbench.editor.showTabs', 'none') }, group: '1_config', order: 30 }); // Editor Title Context Menu MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: CLOSE_EDITOR_COMMAND_ID, title: localize('close', "Close") }, group: '1_close', order: 10 }); @@ -383,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 6fecf20c309..0ad637398b2 100644 --- a/src/vs/workbench/browser/parts/editor/editor.ts +++ b/src/vs/workbench/browser/parts/editor/editor.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { GroupIdentifier, IWorkbenchEditorConfiguration, IEditorIdentifier, IEditorCloseEvent, IEditorPartOptions, IEditorPartOptionsChangeEvent, SideBySideEditor, EditorCloseContext, IEditorPane } from 'vs/workbench/common/editor'; +import { GroupIdentifier, IWorkbenchEditorConfiguration, IEditorIdentifier, IEditorCloseEvent, IEditorPartOptions, IEditorPartOptionsChangeEvent, SideBySideEditor, EditorCloseContext, IEditorPane, IEditorPartLimitConfiguration, IEditorPartDecorationsConfiguration } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { IEditorGroup, GroupDirection, IMergeGroupOptions, GroupsOrder, GroupsArrangement } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IDisposable } from 'vs/base/common/lifecycle'; @@ -13,9 +13,10 @@ import { IConfigurationChangeEvent, IConfigurationService } from 'vs/platform/co import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ISerializableView } from 'vs/base/browser/ui/grid/grid'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { isObject } from 'vs/base/common/types'; +import { OptionalBooleanKey, OptionalNumberKey, OptionalStringKey, isObject } from 'vs/base/common/types'; import { IEditorOptions } from 'vs/platform/editor/common/editor'; import { IWindowsConfiguration } from 'vs/platform/window/common/window'; +import { ensureOptionalBooleanValue, ensureOptionalNumberValue, ensureOptionalStringValue } from 'vs/base/common/objects'; export interface IEditorPartCreationOptions { readonly restorePreviousState: boolean; @@ -47,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 { @@ -90,13 +91,102 @@ export function getEditorPartOptions(configurationService: IConfigurationService return options; } -function validateEditorPartOptions(options: IEditorPartOptions) { - // showTabs ensure correct enum value +function validateEditorPartOptions(options: IEditorPartOptions): void { + + // Migrate: Show tabs (config migration kicks in very late and can cause flicker otherwise) if (typeof options.showTabs === 'boolean') { - // Migration service kicks in very late and can cause a flicker otherwise options.showTabs = options.showTabs ? 'multiple' : 'single'; - } else if (options.showTabs !== 'multiple' && options.showTabs !== 'single' && options.showTabs !== 'none') { - options.showTabs = 'multiple'; + } + + // Boolean options + const booleanOptions: Array> = [ + 'wrapTabs', + 'scrollToSwitchTabs', + 'highlightModifiedTabs', + 'pinnedTabsOnSeparateRow', + 'focusRecentEditorAfterClose', + 'showIcons', + 'enablePreview', + 'enablePreviewFromQuickOpen', + 'enablePreviewFromCodeNavigation', + 'closeOnFileDelete', + 'closeEmptyGroups', + 'revealIfOpen', + 'mouseBackForwardToNavigate', + 'restoreViewState', + 'splitOnDragAndDrop', + 'centeredLayoutFixedWidth', + ]; + for (const option of booleanOptions) { + if (typeof option === 'string') { + ensureOptionalBooleanValue(options, option, Boolean(DEFAULT_EDITOR_PART_OPTIONS[option])); + } + } + + // Number options + const numberOptions: Array> = [ + 'tabSizingFixedMinWidth', + 'tabSizingFixedMaxWidth' + ]; + for (const option of numberOptions) { + if (typeof option === 'string') { + ensureOptionalNumberValue(options, option, Number(DEFAULT_EDITOR_PART_OPTIONS[option])); + } + } + + // String options + const stringOptions: Array<[OptionalStringKey, Array]> = [ + ['showTabs', ['multiple', 'single', 'none']], + ['tabCloseButton', ['left', 'right', 'off']], + ['tabSizing', ['fit', 'shrink', 'fixed']], + ['pinnedTabSizing', ['normal', 'compact', 'shrink']], + ['tabHeight', ['default', 'compact']], + ['preventPinnedEditorClose', ['keyboardAndMouse', 'keyboard', 'mouse', 'never']], + ['titleScrollbarSizing', ['default', 'large']], + ['openPositioning', ['left', 'right', 'first', 'last']], + ['openSideBySideDirection', ['right', 'down']], + ['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') { + ensureOptionalStringValue(options, option, allowed, String(DEFAULT_EDITOR_PART_OPTIONS[option])); + } + } + + // Complex options + if (options.autoLockGroups && !(options.autoLockGroups instanceof Set)) { + options.autoLockGroups = undefined; + } + if (options.limit && !isObject(options.limit)) { + options.limit = undefined; + } else if (options.limit) { + const booleanLimitOptions: Array> = [ + 'enabled', + 'excludeDirty', + 'perEditorGroup' + ]; + for (const option of booleanLimitOptions) { + if (typeof option === 'string') { + ensureOptionalBooleanValue(options.limit, option, undefined); + } + } + ensureOptionalNumberValue(options.limit, 'value', undefined); + } + if (options.decorations && !isObject(options.decorations)) { + options.decorations = undefined; + } else if (options.decorations) { + const booleanDecorationOptions: Array> = [ + 'badges', + 'colors' + ]; + for (const option of booleanDecorationOptions) { + if (typeof option === 'string') { + ensureOptionalBooleanValue(options.decorations, option, undefined); + } + } } } @@ -145,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/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index 32b7296069f..6883d55ac43 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -700,6 +700,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Title control switch between singleEditorTabs, multiEditorTabs and multiRowEditorTabs if ( event.oldPartOptions.showTabs !== event.newPartOptions.showTabs || + event.oldPartOptions.tabHeight !== event.newPartOptions.tabHeight || (event.oldPartOptions.showTabs === 'multiple' && event.oldPartOptions.pinnedTabsOnSeparateRow !== event.newPartOptions.pinnedTabsOnSeparateRow) ) { @@ -1930,6 +1931,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView { available: new Dimension(width, height - this.editorPane.minimumHeight) }); + this.element.style.setProperty('--editor-group-tabs-height', `${this.titleHeight.offset}px`); + // Pass the container width and remaining height to the editor layout const editorHeight = Math.max(0, height - titleControlSize.height); this.editorContainer.style.height = `${editorHeight}px`; 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/media/editorgroupview.css b/src/vs/workbench/browser/parts/editor/media/editorgroupview.css index 676c6e0fff8..80d7062ec80 100644 --- a/src/vs/workbench/browser/parts/editor/media/editorgroupview.css +++ b/src/vs/workbench/browser/parts/editor/media/editorgroupview.css @@ -185,3 +185,7 @@ width: 100%; height: 100%; } + +.monaco-workbench .part.editor > .content .editor-group-container > .monaco-progress-container { + top: max(calc(var(--editor-group-tabs-height) - 2px), 0px); /* Override top position of progress bar which defined in vs/workbench/browser/media/part.css */ +} 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/parts/panel/media/panelpart.css b/src/vs/workbench/browser/parts/panel/media/panelpart.css index 0cb8bea4fa7..5b3bf23fdc9 100644 --- a/src/vs/workbench/browser/parts/panel/media/panelpart.css +++ b/src/vs/workbench/browser/parts/panel/media/panelpart.css @@ -53,3 +53,19 @@ .monaco-workbench .part.panel > .title > .composite-bar-container >.composite-bar > .monaco-action-bar .action-item:focus { outline: none; } + +/* Rotate icons when panel is on right */ +.monaco-workbench .part.basepanel.right .title-actions .codicon-split-horizontal::before, +.monaco-workbench .part.basepanel.right .global-actions .codicon-panel-maximize::before, +.monaco-workbench .part.basepanel.right .global-actions .codicon-panel-restore::before { + display: inline-block; + transform: rotate(-90deg); +} + +/* Rotate icons when panel is on left */ +.monaco-workbench .part.basepanel.left .title-actions .codicon-split-horizontal::before, +.monaco-workbench .part.basepanel.left .global-actions .codicon-panel-maximize::before, +.monaco-workbench .part.basepanel.left .global-actions .codicon-panel-restore::before { + display: inline-block; + transform: rotate(90deg); +} 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 36c62f1a362..e7854dea7b5 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -1095,6 +1095,18 @@ export interface IWorkbenchEditorConfiguration { }; } +export interface IEditorPartLimitConfiguration { + enabled?: boolean; + excludeDirty?: boolean; + value?: number; + perEditorGroup?: boolean; +} + +export interface IEditorPartDecorationsConfiguration { + badges?: boolean; + colors?: boolean; +} + interface IEditorPartConfiguration { showTabs?: 'multiple' | 'single' | 'none'; wrapTabs?: boolean; @@ -1127,17 +1139,9 @@ interface IEditorPartConfiguration { splitSizing?: 'auto' | 'split' | 'distribute'; splitOnDragAndDrop?: boolean; centeredLayoutFixedWidth?: boolean; - doubleClickTabToToggleEditorGroupSizes?: boolean; - limit?: { - enabled?: boolean; - excludeDirty?: boolean; - value?: number; - perEditorGroup?: boolean; - }; - decorations?: { - badges?: boolean; - colors?: boolean; - }; + doubleClickTabToToggleEditorGroupSizes?: 'maximize' | 'expand' | 'off'; + limit?: IEditorPartLimitConfiguration; + decorations?: IEditorPartDecorationsConfiguration; } export interface IEditorPartOptions extends IEditorPartConfiguration { diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts b/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts index 51b5f469841..c787fbdaa98 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts @@ -31,7 +31,7 @@ export function getAccessibilityHelpText(accessor: ServicesAccessor, type: 'pane } else { const startChatKeybinding = keybindingService.lookupKeybinding('inlineChat.start')?.getAriaLabel(); content.push(localize('inlineChat.overview', "Inline chat occurs within a code editor and takes into account the current selection. It is useful for making changes to the current editor. For example, fixing diagnostics, documenting or refactoring code. Keep in mind that AI generated code may be incorrect.")); - content.push(localize('inlineChat.access', "It can be activated via code actions or directly using the command: Inline Chat: Start Code Chat ({0}).", startChatKeybinding)); + content.push(localize('inlineChat.access', "It can be activated via code actions or directly using the command: Inline Chat: Start Inline Chat ({0}).", startChatKeybinding)); const upHistoryKeybinding = keybindingService.lookupKeybinding('inlineChat.previousFromHistory')?.getAriaLabel(); const downHistoryKeybinding = keybindingService.lookupKeybinding('inlineChat.nextFromHistory')?.getAriaLabel(); if (upHistoryKeybinding && downHistoryKeybinding) { diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/media/voiceChatActions.css b/src/vs/workbench/contrib/chat/electron-sandbox/actions/media/voiceChatActions.css index 1f105070d1e..44a67e2efdf 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/media/voiceChatActions.css +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/media/voiceChatActions.css @@ -12,17 +12,25 @@ } /* - * Clear animation styles when hovering. + * Clear animation styles when hovering or when reduced motion is enabled. */ .monaco-workbench .interactive-input-part .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled):hover, .monaco-workbench .inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled):hover { animation: none; } +.monaco-workbench.reduce-motion .interactive-input-part .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled), +.monaco-workbench.reduce-motion .inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled) { + animation: none; +} /* - * Replace with "stop" icon when hovering. + * Replace with "stop" icon when hovering or when reduced motion is enabled. */ .monaco-workbench .interactive-input-part .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled):hover::before, .monaco-workbench .inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled):hover::before { - content: "\ead7"; /* use `debug-stop` icon unicode for hovering over running voice recording */ + content: "\ead7"; +} +.monaco-workbench.reduce-motion .interactive-input-part .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::before, +.monaco-workbench.reduce-motion .inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::before { + content: "\ead7"; } diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts index 91ac2dcef34..787443bd275 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -683,8 +683,8 @@ registerThemingParticipant((theme, collector) => { // Show a "microphone" icon when recording is in progress that glows via outline. collector.addRule(` - .monaco-workbench .interactive-input-part .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled):not(:hover), - .monaco-workbench .inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled):not(:hover) { + .monaco-workbench:not(.reduce-motion) .interactive-input-part .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled):not(:hover), + .monaco-workbench:not(.reduce-motion) .inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled):not(:hover) { color: ${activeRecordingColor}; outline: 1px solid ${activeRecordingColor}; outline-offset: -1px; diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index 9ee8986fd53..489aa98d108 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -1237,7 +1237,7 @@ export class ExtensionEditor extends EditorPane { return $('tr', undefined, $('td', undefined, $('code', undefined, key)), $('td', undefined, description), - $('td', undefined, $('code', undefined, `${isUndefined(properties[key].default) ? getDefaultValue(properties[key].type) : properties[key].default}`))); + $('td', undefined, $('code', undefined, `${isUndefined(properties[key].default) ? getDefaultValue(properties[key].type) : JSON.stringify(properties[key].default)}`))); }) ) ); diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index 77e04f41e31..3b7f3a69780 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -13,7 +13,7 @@ import { EnablementState, IExtensionManagementServerService, IWorkbenchExtension import { IExtensionIgnoredRecommendationsService, IExtensionRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -import { VIEWLET_ID, IExtensionsWorkbenchService, IExtensionsViewPaneContainer, TOGGLE_IGNORE_EXTENSION_ACTION_ID, INSTALL_EXTENSION_FROM_VSIX_COMMAND_ID, WORKSPACE_RECOMMENDATIONS_VIEW_ID, IWorkspaceRecommendedExtensionsView, AutoUpdateConfigurationKey, HasOutdatedExtensionsContext, SELECT_INSTALL_VSIX_EXTENSION_COMMAND_ID, LIST_WORKSPACE_UNSUPPORTED_EXTENSIONS_COMMAND_ID, ExtensionEditorTab, THEME_ACTIONS_GROUP, INSTALL_ACTIONS_GROUP, OUTDATED_EXTENSIONS_VIEW_ID, CONTEXT_HAS_GALLERY } from 'vs/workbench/contrib/extensions/common/extensions'; +import { VIEWLET_ID, IExtensionsWorkbenchService, IExtensionsViewPaneContainer, TOGGLE_IGNORE_EXTENSION_ACTION_ID, INSTALL_EXTENSION_FROM_VSIX_COMMAND_ID, WORKSPACE_RECOMMENDATIONS_VIEW_ID, IWorkspaceRecommendedExtensionsView, AutoUpdateConfigurationKey, HasOutdatedExtensionsContext, SELECT_INSTALL_VSIX_EXTENSION_COMMAND_ID, LIST_WORKSPACE_UNSUPPORTED_EXTENSIONS_COMMAND_ID, ExtensionEditorTab, THEME_ACTIONS_GROUP, INSTALL_ACTIONS_GROUP, OUTDATED_EXTENSIONS_VIEW_ID, CONTEXT_HAS_GALLERY, IExtension } from 'vs/workbench/contrib/extensions/common/extensions'; import { ReinstallAction, InstallSpecificVersionOfExtensionAction, ConfigureWorkspaceRecommendedExtensionsAction, ConfigureWorkspaceFolderRecommendedExtensionsAction, PromptExtensionInstallFailureAction, SearchExtensionsAction, SwitchToPreReleaseVersionAction, SwitchToReleasedVersionAction, SetColorThemeAction, SetFileIconThemeAction, SetProductIconThemeAction, ClearLanguageAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput'; import { ExtensionEditor } from 'vs/workbench/contrib/extensions/browser/extensionEditor'; @@ -672,14 +672,17 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi } ], icon: installWorkspaceRecommendedIcon, - run: () => { - return Promise.all(this.extensionsWorkbenchService.outdated.map(async extension => { - try { - await this.extensionsWorkbenchService.install(extension, extension.local?.preRelease ? { installPreReleaseVersion: true } : undefined); - } catch (err) { - runAction(this.instantiationService.createInstance(PromptExtensionInstallFailureAction, extension, extension.latestVersion, InstallOperation.Update, err)); + run: async () => { + const outdated = this.extensionsWorkbenchService.outdated; + const results = await this.extensionsWorkbenchService.updateAll(); + results.forEach((result) => { + if (result.error) { + const extension: IExtension | undefined = outdated.find((extension) => areSameExtensions(extension.identifier, result.identifier)); + if (extension) { + runAction(this.instantiationService.createInstance(PromptExtensionInstallFailureAction, extension, extension.latestVersion, InstallOperation.Update, result.error)); + } } - })); + }); } }); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts index f920b7496b6..5445e6230a1 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts @@ -82,7 +82,7 @@ export class InstallCountWidget extends ExtensionWidget { return; } - if (this.small && this.extension.state === ExtensionState.Installed) { + if (this.small && this.extension.state !== ExtensionState.Uninstalled) { return; } @@ -146,7 +146,7 @@ export class RatingsWidget extends ExtensionWidget { return; } - if (this.small && this.extension.state === ExtensionState.Installed) { + if (this.small && this.extension.state !== ExtensionState.Uninstalled) { return; } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index 154696503e3..3bb04e433a2 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -15,7 +15,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IExtensionGalleryService, ILocalExtension, IGalleryExtension, IQueryOptions, InstallExtensionEvent, DidUninstallExtensionEvent, InstallOperation, InstallOptions, WEB_EXTENSION_TAG, InstallExtensionResult, - IExtensionsControlManifest, InstallVSIXOptions, IExtensionInfo, IExtensionQueryOptions, IDeprecationInfo, isTargetPlatformCompatible + IExtensionsControlManifest, InstallVSIXOptions, IExtensionInfo, IExtensionQueryOptions, IDeprecationInfo, isTargetPlatformCompatible, InstallExtensionInfo } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer, IWorkbenchExtensionManagementService, DefaultIconPath } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData, areSameExtensions, groupByExtension, ExtensionKey, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; @@ -1364,6 +1364,23 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension } } + async updateAll(): Promise { + const toUpdate: InstallExtensionInfo[] = []; + this.outdated.forEach((extension) => { + if (extension.gallery) { + toUpdate.push({ + extension: extension.gallery, + options: { + operation: InstallOperation.Update, + installPreReleaseVersion: extension.local?.isPreReleaseVersion, + profileLocation: this.userDataProfileService.currentProfile.extensionsResource, + } + }); + } + }); + return this.extensionManagementService.installGalleryExtensions(toUpdate); + } + private async syncInstalledExtensionsWithGallery(gallery: IGalleryExtension[]): Promise { const extensions: Extensions[] = []; if (this.localExtensions) { diff --git a/src/vs/workbench/contrib/extensions/common/extensions.ts b/src/vs/workbench/contrib/extensions/common/extensions.ts index 1ce6153fb8b..5582a39fea9 100644 --- a/src/vs/workbench/contrib/extensions/common/extensions.ts +++ b/src/vs/workbench/contrib/extensions/common/extensions.ts @@ -6,7 +6,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { Event } from 'vs/base/common/event'; import { IPager } from 'vs/base/common/paging'; -import { IQueryOptions, ILocalExtension, IGalleryExtension, IExtensionIdentifier, InstallOptions, InstallVSIXOptions, IExtensionInfo, IExtensionQueryOptions, IDeprecationInfo } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IQueryOptions, ILocalExtension, IGalleryExtension, IExtensionIdentifier, InstallOptions, InstallVSIXOptions, IExtensionInfo, IExtensionQueryOptions, IDeprecationInfo, InstallExtensionResult } from 'vs/platform/extensionManagement/common/extensionManagement'; import { EnablementState, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; @@ -116,6 +116,7 @@ export interface IExtensionsWorkbenchService { open(extension: IExtension | string, options?: IExtensionEditorOptions): Promise; checkForUpdates(): Promise; getExtensionStatus(extension: IExtension): IExtensionsStatus | undefined; + updateAll(): Promise; // Sync APIs isExtensionIgnoredToSync(extension: IExtension): boolean; diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts index 2f2cb558da9..fa7769443e9 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts @@ -37,7 +37,7 @@ export class StartSessionAction extends EditorAction2 { constructor() { super({ id: 'inlineChat.start', - title: { value: localize('run', 'Start Code Chat'), original: 'Start Code Chat' }, + title: { value: localize('run', 'Start Inline Chat'), original: 'Start Inline Chat' }, category: AbstractInlineChatAction.category, f1: true, precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_HAS_PROVIDER, EditorContextKeys.writable), @@ -64,7 +64,7 @@ export class UnstashSessionAction extends EditorAction2 { constructor() { super({ id: 'inlineChat.unstash', - title: { value: localize('unstash', 'Resume Last Dismissed Code Chat'), original: 'Resume Last Dismissed Code Chat' }, + title: { value: localize('unstash', 'Resume Last Dismissed Inline Chat'), original: 'Resume Last Dismissed Inline Chat' }, category: AbstractInlineChatAction.category, precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_HAS_STASHED_SESSION, EditorContextKeys.writable), keybinding: { diff --git a/src/vs/workbench/contrib/scm/browser/media/scm.css b/src/vs/workbench/contrib/scm/browser/media/scm.css index 16274449aff..3b6417cc287 100644 --- a/src/vs/workbench/contrib/scm/browser/media/scm.css +++ b/src/vs/workbench/contrib/scm/browser/media/scm.css @@ -260,6 +260,10 @@ outline-offset: -1px; } +.scm-view .scm-input .actions .action-label.codicon.codicon-debug-stop { + color: var(--vscode-icon-foreground) !important; +} + .scm-view .scm-editor-container .monaco-editor { border-radius: 2px; } diff --git a/src/vs/workbench/contrib/scm/browser/menus.ts b/src/vs/workbench/contrib/scm/browser/menus.ts index d1bde83d780..9918414d4c0 100644 --- a/src/vs/workbench/contrib/scm/browser/menus.ts +++ b/src/vs/workbench/contrib/scm/browser/menus.ts @@ -161,16 +161,6 @@ export class SCMRepositoryMenus implements ISCMRepositoryMenus, IDisposable { return this._repositoryMenu; } - private _inputBoxMenu: IMenu | undefined; - get inputBoxMenu(): IMenu { - if (!this._inputBoxMenu) { - this._inputBoxMenu = this.menuService.createMenu(MenuId.SCMInputBox, this.contextKeyService); - this.disposables.add(this._inputBoxMenu); - } - - return this._inputBoxMenu; - } - private readonly disposables = new DisposableStore(); constructor( diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index a9cb19a3718..492d2e20979 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -1959,9 +1959,10 @@ class SCMInputWidget { updateEnablement(input.enabled); // ActionBar - this.actionBar.context = input.repository.provider; - const onDidChangeActionButton = () => { + // Update placeholder width to accommodate for the action bar + this.placeholderTextContainer.style.width = input.actionButton ? 'calc(100% - 26px)' : '100%'; + this.actionBar.clear(); if (!input.actionButton) { return; @@ -1975,9 +1976,6 @@ class SCMInputWidget { () => this.commandService.executeCommand(input.actionButton!.command.id, ...(input.actionButton!.command.arguments || []))); this.actionBar.push(action, { icon: true, label: false }); - - // Update placeholder width to accommodate for the action bar - this.placeholderTextContainer.style.width = input.actionButton ? 'calc(100% - 26px)' : '100%'; }; this.repositoryDisposables.add(input.onDidChangeActionButton(onDidChangeActionButton, this)); diff --git a/src/vs/workbench/contrib/scm/common/scm.ts b/src/vs/workbench/contrib/scm/common/scm.ts index 4327c3c83d8..3e52814ee60 100644 --- a/src/vs/workbench/contrib/scm/common/scm.ts +++ b/src/vs/workbench/contrib/scm/common/scm.ts @@ -181,7 +181,6 @@ export interface ISCMTitleMenu { export interface ISCMRepositoryMenus { readonly titleMenu: ISCMTitleMenu; readonly repositoryMenu: IMenu; - readonly inputBoxMenu: IMenu; getResourceGroupMenu(group: ISCMResourceGroup): IMenu; getResourceMenu(resource: ISCMResource): IMenu; getResourceFolderMenu(group: ISCMResourceGroup): IMenu; diff --git a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts index 56609d8331f..bb1993e9d95 100644 --- a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts +++ b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts @@ -124,12 +124,6 @@ const apiMenus: IAPIMenu[] = [ id: MenuId.SCMSourceControl, description: localize('menus.scmSourceControl', "The Source Control menu") }, - { - key: 'scm/inputBox', - id: MenuId.SCMInputBox, - description: localize('menus.scmInputBox', "The Source Control input box menu"), - proposed: 'contribSourceControlInputBoxMenu' - }, { key: 'scm/resourceState/context', id: MenuId.SCMResourceContext, 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/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index b76468a8e26..cb95f2c976f 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -31,7 +31,6 @@ export const allApiProposals = Object.freeze({ contribNotebookStaticPreloads: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribNotebookStaticPreloads.d.ts', contribRemoteHelp: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribRemoteHelp.d.ts', contribShareMenu: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribShareMenu.d.ts', - contribSourceControlInputBoxMenu: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribSourceControlInputBoxMenu.d.ts', contribStatusBarItems: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribStatusBarItems.d.ts', contribViewsRemote: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribViewsRemote.d.ts', contribViewsWelcome: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribViewsWelcome.d.ts', 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 { diff --git a/src/vscode-dts/vscode.proposed.contribSourceControlInputBoxMenu.d.ts b/src/vscode-dts/vscode.proposed.contribSourceControlInputBoxMenu.d.ts deleted file mode 100644 index 4774bf46fef..00000000000 --- a/src/vscode-dts/vscode.proposed.contribSourceControlInputBoxMenu.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -// empty placeholder declaration for the `scm/inputBox` menu contribution point -// https://github.com/microsoft/vscode/issues/195474