Merge branch 'main' into tyriar/hangul

This commit is contained in:
Daniel Imms
2023-10-23 05:21:21 -07:00
committed by GitHub
54 changed files with 1147 additions and 248 deletions
@@ -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"
]
}
}
+14 -4
View File
@@ -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<string | undefined> {
async provideCommitMessage(repository: ApiRepository, _: string[], token: CancellationToken): Promise<string | undefined> {
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
};
}
+7 -12
View File
@@ -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;
+10 -1
View File
@@ -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<Window & typeof globalThis>();
const onWillUnregisterWindow = new event.Emitter<Window & typeof globalThis>();
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<Window & typeof globalThis> {
+57 -8
View File
@@ -51,6 +51,7 @@ export interface GridLeafNode<T extends IView> {
readonly view: T;
readonly box: Box;
readonly cachedVisibleSize: number | undefined;
readonly maximized: boolean;
}
export interface GridBranchNode<T extends IView> {
@@ -288,6 +289,7 @@ export class Grid<T extends IView = IView> extends Disposable {
private didLayout = false;
readonly onDidChangeViewMaximized: Event<boolean>;
/**
* Create a new {@link Grid}. A grid must *always* have a view
* inside.
@@ -313,6 +315,7 @@ export class Grid<T extends IView = IView> 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<T extends IView = IView> 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<T extends IView = IView> 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<T extends ISerializableView> extends Grid<T> {
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 };
}
/**
+156 -9
View File
@@ -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<ILayoutContext>, 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<ILayoutContext>, IDisposable {
private readonly _onDidChange = new Emitter<number | undefined>();
readonly onDidChange: Event<number | undefined> = this._onDidChange.event;
private readonly _onDidVisibilityChange = new Emitter<boolean>();
readonly onDidVisibilityChange: Event<boolean> = this._onDidVisibilityChange.event;
private readonly childrenVisibilityChangeDisposable: DisposableStore = new DisposableStore();
private _onDidScroll = new Emitter<void>();
private onDidScrollDisposable: IDisposable = Disposable.None;
readonly onDidScroll: Event<void> = this._onDidScroll.event;
@@ -427,7 +434,7 @@ class BranchNode implements ISplitView<ILayoutContext>, 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<ILayoutContext>, 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<ILayoutContext>, 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<ILayoutContext>, 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<ILayoutContext>, 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<boolean>();
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<IViewSize>): 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[] = [];
+16 -11
View File
@@ -442,7 +442,7 @@ export class SplitView<TLayoutContext = undefined, TView extends IView<TLayoutCo
private scrollableElement: SmoothScrollableElement;
private size = 0;
private layoutContext: TLayoutContext | undefined;
private contentSize = 0;
private _contentSize = 0;
private proportions: (number | undefined)[] | undefined = undefined;
private viewItems: ViewItem<TLayoutContext, TView>[] = [];
sashItems: ISashItem[] = []; // used in tests
@@ -459,6 +459,11 @@ export class SplitView<TLayoutContext = undefined, TView extends IView<TLayoutCo
private _startSnappingEnabled = true;
private _endSnappingEnabled = true;
/**
* The sum of all views' sizes.
*/
get contentSize(): number { return this._contentSize; }
/**
* Fires whenever the user resizes a {@link Sash sash}.
*/
@@ -624,7 +629,7 @@ export class SplitView<TLayoutContext = undefined, TView extends IView<TLayoutCo
});
// Initialize content size and proportions for first layout
this.contentSize = this.viewItems.reduce((r, i) => r + i.size, 0);
this._contentSize = this.viewItems.reduce((r, i) => r + i.size, 0);
this.saveProportions();
}
}
@@ -834,7 +839,7 @@ export class SplitView<TLayoutContext = undefined, TView extends IView<TLayoutCo
* @param layoutContext An optional layout context to pass along to {@link IView views}.
*/
layout(size: number, layoutContext?: TLayoutContext): void {
const previousSize = Math.max(this.size, this.contentSize);
const previousSize = Math.max(this.size, this._contentSize);
this.size = size;
this.layoutContext = layoutContext;
@@ -862,7 +867,7 @@ export class SplitView<TLayoutContext = undefined, TView extends IView<TLayoutCo
const item = this.viewItems[i];
const proportion = this.proportions[i];
if (typeof proportion === 'number') {
if (typeof proportion === 'number' && total > 0) {
item.size = clamp(Math.round(proportion * size / total), item.minimumSize, item.maximumSize);
}
}
@@ -873,8 +878,8 @@ export class SplitView<TLayoutContext = undefined, TView extends IView<TLayoutCo
}
private saveProportions(): void {
if (this.proportionalLayout && this.contentSize > 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<TLayoutContext = undefined, TView extends IView<TLayoutCo
/**
* Returns whether all other {@link IView views} are at their minimum size.
*/
isViewSizeMaximized(index: number): boolean {
isViewExpanded(index: number): boolean {
if (index < 0 || index >= this.viewItems.length) {
return false;
}
@@ -1347,7 +1352,7 @@ export class SplitView<TLayoutContext = undefined, TView extends IView<TLayoutCo
private layoutViews(): void {
// Save new content size
this.contentSize = this.viewItems.reduce((r, i) => 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<TLayoutContext = undefined, TView extends IView<TLayoutCo
if (this.orientation === Orientation.VERTICAL) {
this.scrollableElement.setScrollDimensions({
height: this.size,
scrollHeight: this.contentSize
scrollHeight: this._contentSize
});
} else {
this.scrollableElement.setScrollDimensions({
width: this.size,
scrollWidth: this.contentSize
scrollWidth: this._contentSize
});
}
}
@@ -1411,7 +1416,7 @@ export class SplitView<TLayoutContext = undefined, TView extends IView<TLayoutCo
if (snappedBefore && collapsesUp[index] && (position > 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;
+32 -1
View File
@@ -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<T>(obj: T): T {
if (!obj || typeof obj !== 'object') {
@@ -261,3 +261,34 @@ export function createProxyObject<T extends object>(methodNames: string[], invok
}
return result;
}
export function ensureOptionalBooleanValue<T extends object>(obj: T, key: OptionalBooleanKey<T>, 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<T extends object>(obj: T, key: OptionalNumberKey<T>, 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<T extends object>(obj: T, key: OptionalStringKey<T>, 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;
}
}
@@ -231,6 +231,15 @@ export function transaction(fn: (tx: ITransaction) => void, getDebugName?: () =>
}
}
export async function asyncTransaction(fn: (tx: ITransaction) => Promise<void>, getDebugName?: () => string): Promise<void> {
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);
+12
View File
@@ -227,3 +227,15 @@ export type Mutable<T> = {
* A single object or an array of the objects.
*/
export type SingleOrMany<T> = T | T[];
export type OptionalBooleanKey<T> = {
[K in keyof T]: T[K] extends boolean | undefined ? K : never;
}[keyof T];
export type OptionalNumberKey<T> = {
[K in keyof T]: T[K] extends number | undefined ? K : never;
}[keyof T];
export type OptionalStringKey<T> = {
[K in keyof T]: T[K] extends string | undefined ? K : never;
}[keyof T];
+201 -1
View File
@@ -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 {
@@ -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 () {
+80
View File
@@ -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');
});
});
@@ -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<void> {
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);
});
}
}
@@ -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<InlineCompletionsModel | undefined>('inlineCompletionModel', undefined);
private readonly textModelVersionId = observableValue<number, VersionIdChangeReason>(this, -1);
private readonly cursorPosition = observableValue<Position>(this, new Position(1, 1));
private readonly suggestWidgetAdaptor = this._register(new SuggestWidgetAdaptor(
private readonly _textModelVersionId = observableValue<number, VersionIdChangeReason>(this, -1);
private readonly _cursorPosition = observableValue<Position>(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<boolean>('accessibleViewIsShown') || this.configurationService.getValue('editor.inlineSuggest.keepOnBlur') ||
if (this._contextKeyService.getContextKeyValue<boolean>('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<boolean>('accessibleViewIsShown');
const accessibleViewShowing = this._contextKeyService.getContextKeyValue<boolean>('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() {
@@ -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<boolean, InlineCompletionTriggerKind | void>(this, false);
private readonly _forceUpdate = observableSignal<InlineCompletionTriggerKind>('forceUpdate');
readonly _forceUpdateSignal = observableSignal<InlineCompletionTriggerKind>('forceUpdate');
// We use a semantic id to keep the same inline completion selected even if the provider reorders the completions.
private readonly _selectedInlineCompletionId = observableValue<string | undefined>(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<void> {
subtransaction(tx, tx => {
this._isActive.set(true, tx);
this._forceUpdate.trigger(tx, InlineCompletionTriggerKind.Explicit);
this._forceUpdateSignal.trigger(tx, InlineCompletionTriggerKind.Explicit);
});
await this._fetchInlineCompletions.get();
}
@@ -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');
@@ -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<T extends IQuickPickItem, O extends IPickOptions<T>>(picks: Promise<QuickPickInput<T>[]> | QuickPickInput<T>[], options: O = <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<R>((doResolve, reject) => {
@@ -489,7 +489,7 @@ export class HideEditorTabsAction extends Action2 {
});
}
override run(accessor: ServicesAccessor): Promise<void> {
run(accessor: ServicesAccessor): Promise<void> {
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<void> {
run(accessor: ServicesAccessor): Promise<void> {
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<void> {
run(accessor: ServicesAccessor): Promise<void> {
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<void> {
run(accessor: ServicesAccessor): Promise<void> {
const configurationService = accessor.get(IConfigurationService);
const oldettingValue = configurationService.getValue<string>('workbench.editor.pinnedTabsOnSeparateRow');
@@ -575,8 +573,7 @@ export class ToggleSeparatePinnedTabsAction extends Action2 {
return configurationService.updateValue('workbench.editor.pinnedTabsOnSeparateRow', newSettingValue);
}
}
registerAction2(ToggleSeparatePinnedTabsAction);
});
// --- Toggle Zen Mode
+5 -1
View File
@@ -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<boolean>;
private activeEditorGroupLocked: IContextKey<boolean>;
private multipleEditorGroupsContext: IContextKey<boolean>;
private maximizedEditorGroupContext: IContextKey<boolean>;
private editorsVisibleContext: IContextKey<boolean>;
@@ -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 }));
+2 -6
View File
@@ -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;
}
@@ -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 = {
+100 -8
View File
@@ -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<OptionalBooleanKey<IEditorPartOptions>> = [
'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<OptionalNumberKey<IEditorPartOptions>> = [
'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<IEditorPartOptions>, Array<string>]> = [
['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<OptionalBooleanKey<IEditorPartLimitConfiguration>> = [
'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<OptionalBooleanKey<IEditorPartDecorationsConfiguration>> = [
'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 {
@@ -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<void> {
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<void> {
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<void> {
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<void> {
const editorGroupService = accessor.get(IEditorGroupsService);
editorGroupService.toggleMaximizeGroup();
}
}
abstract class AbstractNavigateEditorAction extends Action2 {
override async run(accessor: ServicesAccessor): Promise<void> {
@@ -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;
@@ -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`;
@@ -101,6 +101,9 @@ export class EditorPart extends Part implements IEditorPart {
private readonly _onDidChangeGroupLocked = this._register(new Emitter<IEditorGroupView>());
readonly onDidChangeGroupLocked = this._onDidChangeGroupLocked.event;
private readonly _onDidChangeGroupMaximized = this._register(new Emitter<boolean>());
readonly onDidChangeGroupMaximized = this._onDidChangeGroupMaximized.event;
private readonly _onDidActivateGroup = this._register(new Emitter<IEditorGroupView>());
readonly onDidActivateGroup = this._onDidActivateGroup.event;
@@ -137,6 +140,7 @@ export class EditorPart extends Part implements IEditorPart {
private centeredLayoutWidget!: CenteredViewLayout;
private gridWidget!: SerializableGrid<IEditorGroupView>;
private readonly gridWidgetDisposables = this._register(new DisposableStore());
private readonly gridWidgetView = this._register(new GridWidgetView<IEditorGroupView>());
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);
}
@@ -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<IEditorGroupView>());
readonly onDidChangeGroupLocked = this._onDidChangeGroupLocked.event;
private readonly _onDidChangeGroupMaximized = this._register(new Emitter<boolean>());
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 {
@@ -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 */
}
@@ -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);
}
@@ -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);
}
@@ -322,9 +322,15 @@ const registry = Registry.as<IConfigurationRegistry>(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<IConfigurationMigrationRegistry>(Extensions.ConfigurationMigration)
Registry.as<IConfigurationMigrationRegistry>(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<IConfigurationMigrationRegistry>(Extensions.ConfigurationMigration)
.registerConfigurationMigrations([{
}, {
key: 'zenMode.hideTabs', migrateFn: (value: any) => {
const result: ConfigurationKeyValuePairs = [['zenMode.hideTabs', { value: undefined }]];
if (value === true) {
+1
View File
@@ -79,6 +79,7 @@ export const SplitEditorsVertically = new RawContextKey<boolean>('splitEditorsVe
export const EditorAreaVisibleContext = new RawContextKey<boolean>('editorAreaVisible', true, localize('editorAreaVisible', "Whether the editor area is visible"));
export const EditorTabsVisibleContext = new RawContextKey<boolean>('editorTabsVisible', true, localize('editorTabsVisible', "Whether editor tabs are visible"));
export const EditorPinnedAndUnpinnedTabsContext = new RawContextKey<boolean>('editorPinnedAndUnpinnedTabsVisible', false, true);
export const MaximizedEditorGroupContext = new RawContextKey<boolean>('maximizedEditorGroup', false, localize('editorGroupMaximized', "Editor group is maximized"));
//#endregion
+15 -11
View File
@@ -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 {
@@ -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) {
@@ -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";
}
@@ -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;
@@ -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)}`)));
})
)
);
@@ -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));
}
}
}));
});
}
});
@@ -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;
}
@@ -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<InstallExtensionResult[]> {
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<void> {
const extensions: Extensions[] = [];
if (this.localExtensions) {
@@ -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<void>;
checkForUpdates(): Promise<void>;
getExtensionStatus(extension: IExtension): IExtensionsStatus | undefined;
updateAll(): Promise<InstallExtensionResult[]>;
// Sync APIs
isExtensionIgnoredToSync(extension: IExtension): boolean;
@@ -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: {
@@ -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;
}
@@ -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(
@@ -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));
@@ -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;
@@ -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,
@@ -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<IEditorGroup>;
/**
* An event for when the maximized state of a group changes.
*/
readonly onDidChangeGroupMaximized: Event<boolean>;
/**
* 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.
@@ -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();
});
@@ -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 () {
@@ -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',
@@ -824,6 +824,7 @@ export class TestEditorGroupsService implements IEditorGroupsService {
onDidMoveGroup: Event<IEditorGroup> = Event.None;
onDidChangeGroupIndex: Event<IEditorGroup> = Event.None;
onDidChangeGroupLocked: Event<IEditorGroup> = Event.None;
onDidChangeGroupMaximized: Event<boolean> = Event.None;
onDidLayout: Event<IDimension> = 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 {
@@ -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