chore(vscode): update to 1.56.0

This commit is contained in:
Akash Satheesan
2021-04-30 20:25:17 +05:30
1749 changed files with 88014 additions and 43316 deletions

View File

@@ -28,6 +28,7 @@ import { ILogService } from 'vs/platform/log/common/log';
import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { CATEGORIES } from 'vs/workbench/common/actions';
import { IWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/common/workingCopyBackup';
class InspectContextKeysAction extends Action2 {
@@ -263,15 +264,24 @@ class LogWorkingCopiesAction extends Action2 {
});
}
run(accessor: ServicesAccessor): void {
async run(accessor: ServicesAccessor): Promise<void> {
const workingCopyService = accessor.get(IWorkingCopyService);
const workingCopyBackupService = accessor.get(IWorkingCopyBackupService);
const logService = accessor.get(ILogService);
const backups = await workingCopyBackupService.getBackups();
const msg = [
`Dirty Working Copies:`,
...workingCopyService.dirtyWorkingCopies.map(workingCopy => workingCopy.resource.toString(true)),
``,
`All Working Copies:`,
...workingCopyService.workingCopies.map(workingCopy => workingCopy.resource.toString(true)),
`[Working Copies]`,
...(workingCopyService.workingCopies.length > 0) ?
workingCopyService.workingCopies.map(workingCopy => `${workingCopy.isDirty() ? '● ' : ''}${workingCopy.resource.toString(true)} (typeId: ${workingCopy.typeId || '<no typeId>'})`) :
['<none>'],
``,
`[Backups]`,
...(backups.length > 0) ?
backups.map(backup => `${backup.resource.toString(true)} (typeId: ${backup.typeId || '<no typeId>'})`) :
['<none>'],
];
logService.info(msg.join('\n'));

View File

@@ -139,7 +139,7 @@ export class ToggleSidebarPositionAction extends Action {
super(id, label);
}
run(): Promise<void> {
override run(): Promise<void> {
const position = this.layoutService.getSideBarPosition();
const newPositionValue = (position === Position.LEFT) ? 'right' : 'left';
@@ -244,7 +244,7 @@ export class ToggleEditorVisibilityAction extends Action {
super(id, label);
}
async run(): Promise<void> {
override async run(): Promise<void> {
this.layoutService.toggleMaximizedPanel();
}
}
@@ -339,7 +339,7 @@ export class ToggleStatusbarVisibilityAction extends Action {
super(id, label);
}
run(): Promise<void> {
override run(): Promise<void> {
const visibility = this.layoutService.isVisible(Parts.STATUSBAR_PART);
const newVisibilityValue = !visibility;
@@ -376,7 +376,7 @@ class ToggleTabsVisibilityAction extends Action {
super(id, label);
}
run(): Promise<void> {
override run(): Promise<void> {
const visibility = this.configurationService.getValue<string>(ToggleTabsVisibilityAction.tabsVisibleKey);
const newVisibilityValue = !visibility;
@@ -405,7 +405,7 @@ class ToggleZenMode extends Action {
super(id, label);
}
async run(): Promise<void> {
override async run(): Promise<void> {
this.layoutService.toggleZenMode();
}
}
@@ -448,7 +448,7 @@ export class ToggleMenuBarAction extends Action {
super(id, label);
}
async run(): Promise<void> {
override async run(): Promise<void> {
this.layoutService.toggleMenuBar();
}
}
@@ -482,7 +482,7 @@ export class ResetViewLocationsAction extends Action {
super(id, label);
}
async run(): Promise<void> {
override async run(): Promise<void> {
this.viewDescriptorService.reset();
}
}
@@ -585,7 +585,7 @@ export class MoveViewAction extends Action {
});
}
async run(): Promise<void> {
override async run(): Promise<void> {
const focusedViewId = FocusedViewContext.getValue(this.contextKeyService);
let viewId: string;
@@ -624,7 +624,7 @@ export class MoveFocusedViewAction extends Action {
super(id, label);
}
async run(viewId: string): Promise<void> {
override async run(viewId: string): Promise<void> {
const focusedViewId = viewId || FocusedViewContext.getValue(this.contextKeyService);
if (focusedViewId === undefined || focusedViewId.trim() === '') {
@@ -744,7 +744,7 @@ export class ResetFocusedViewLocationAction extends Action {
super(id, label);
}
async run(): Promise<void> {
override async run(): Promise<void> {
const focusedViewId = FocusedViewContext.getValue(this.contextKeyService);
let viewDescriptor: IViewDescriptor | null = null;

View File

@@ -32,7 +32,7 @@ abstract class BaseNavigationAction extends Action {
super(id, label);
}
async run(): Promise<boolean | IViewlet | IPanel> {
override async run(): Promise<void> {
const isEditorFocus = this.layoutService.hasFocus(Parts.EDITOR_PART);
const isPanelFocus = this.layoutService.hasFocus(Parts.PANEL_PART);
const isSidebarFocus = this.layoutService.hasFocus(Parts.SIDEBAR_PART);
@@ -41,7 +41,7 @@ abstract class BaseNavigationAction extends Action {
if (isEditorFocus) {
const didNavigate = this.navigateAcrossEditorGroup(this.toGroupDirection(this.direction));
if (didNavigate) {
return true;
return;
}
neighborPart = this.layoutService.getVisibleNeighborPart(Parts.EDITOR_PART, this.direction);
@@ -56,18 +56,12 @@ abstract class BaseNavigationAction extends Action {
}
if (neighborPart === Parts.EDITOR_PART) {
return this.navigateToEditorGroup(this.direction === Direction.Right ? GroupLocation.FIRST : GroupLocation.LAST);
this.navigateToEditorGroup(this.direction === Direction.Right ? GroupLocation.FIRST : GroupLocation.LAST);
} else if (neighborPart === Parts.SIDEBAR_PART) {
this.navigateToSidebar();
} else if (neighborPart === Parts.PANEL_PART) {
this.navigateToPanel();
}
if (neighborPart === Parts.SIDEBAR_PART) {
return this.navigateToSidebar();
}
if (neighborPart === Parts.PANEL_PART) {
return this.navigateToPanel();
}
return false;
}
private async navigateToPanel(): Promise<IPanel | boolean> {
@@ -240,7 +234,7 @@ export class FocusNextPart extends Action {
super(id, label);
}
async run(): Promise<void> {
override async run(): Promise<void> {
focusNextOrPreviousPart(this.layoutService, this.editorService, true);
}
}
@@ -258,7 +252,7 @@ export class FocusPreviousPart extends Action {
super(id, label);
}
async run(): Promise<void> {
override async run(): Promise<void> {
focusNextOrPreviousPart(this.layoutService, this.editorService, false);
}
}

View File

@@ -42,7 +42,7 @@ export class TextInputActionsProvider extends Disposable implements IWorkbenchCo
// Cut / Copy / Paste
new Action('editor.action.clipboardCutAction', localize('cut', "Cut"), undefined, true, async () => document.execCommand('cut')),
new Action('editor.action.clipboardCopyAction', localize('copy', "Copy"), undefined, true, async () => document.execCommand('copy')),
new Action('editor.action.clipboardPasteAction', localize('paste', "Paste"), undefined, true, async (element: HTMLInputElement | HTMLTextAreaElement) => {
new Action('editor.action.clipboardPasteAction', localize('paste', "Paste"), undefined, true, async element => {
// Native: paste is supported
if (isNative) {

View File

@@ -78,7 +78,7 @@ abstract class BaseOpenRecentAction extends Action {
protected abstract isQuickNavigate(): boolean;
async run(): Promise<void> {
override async run(): Promise<void> {
const recentlyOpened = await this.workspacesService.getRecentlyOpened();
const dirtyWorkspacesAndFolders = await this.workspacesService.getDirtyWorkspaces();
@@ -286,7 +286,7 @@ class ToggleFullScreenAction extends Action {
super(id, label);
}
run(): Promise<void> {
override run(): Promise<void> {
return this.hostService.toggleFullScreen();
}
}
@@ -304,10 +304,8 @@ export class ReloadWindowAction extends Action {
super(id, label);
}
async run(): Promise<boolean> {
override async run(): Promise<void> {
await this.hostService.reload();
return true;
}
}
@@ -324,7 +322,7 @@ class ShowAboutDialogAction extends Action {
super(id, label);
}
run(): Promise<void> {
override run(): Promise<void> {
return this.dialogService.about();
}
}
@@ -342,7 +340,7 @@ export class NewWindowAction extends Action {
super(id, label);
}
run(): Promise<void> {
override run(): Promise<void> {
return this.hostService.openWindow({ remoteAuthority: null });
}
}

View File

@@ -24,6 +24,7 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IWorkspacesService, hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces';
import { WORKSPACE_TRUST_ENABLED } from 'vs/workbench/services/workspaces/common/workspaceTrust';
import { IsWebContext } from 'vs/platform/contextkey/common/contextkeys';
export class OpenFileAction extends Action {
@@ -38,7 +39,7 @@ export class OpenFileAction extends Action {
super(id, label);
}
run(event?: unknown, data?: ITelemetryData): Promise<void> {
override run(event?: unknown, data?: ITelemetryData): Promise<void> {
return this.dialogService.pickFileAndOpen({ forceNewWindow: false, telemetryExtraData: data });
}
}
@@ -56,7 +57,7 @@ export class OpenFolderAction extends Action {
super(id, label);
}
run(event?: unknown, data?: ITelemetryData): Promise<void> {
override run(event?: unknown, data?: ITelemetryData): Promise<void> {
return this.dialogService.pickFolderAndOpen({ forceNewWindow: false, telemetryExtraData: data });
}
}
@@ -74,7 +75,7 @@ export class OpenFileFolderAction extends Action {
super(id, label);
}
run(event?: unknown, data?: ITelemetryData): Promise<void> {
override run(event?: unknown, data?: ITelemetryData): Promise<void> {
return this.dialogService.pickFileFolderAndOpen({ forceNewWindow: false, telemetryExtraData: data });
}
}
@@ -92,7 +93,7 @@ export class OpenWorkspaceAction extends Action {
super(id, label);
}
run(event?: unknown, data?: ITelemetryData): Promise<void> {
override run(event?: unknown, data?: ITelemetryData): Promise<void> {
return this.dialogService.pickWorkspaceAndOpen({ telemetryExtraData: data });
}
}
@@ -113,7 +114,7 @@ export class CloseWorkspaceAction extends Action {
super(id, label);
}
async run(): Promise<void> {
override async run(): Promise<void> {
if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) {
this.notificationService.info(localize('noWorkspaceOrFolderOpened', "There is currently no workspace or folder opened in this instance to close."));
return;
@@ -139,7 +140,7 @@ export class OpenWorkspaceConfigFileAction extends Action {
this.enabled = !!this.workspaceContextService.getWorkspace().configuration;
}
async run(): Promise<void> {
override async run(): Promise<void> {
const configuration = this.workspaceContextService.getWorkspace().configuration;
if (configuration) {
await this.editorService.openEditor({ resource: configuration, options: { pinned: true } });
@@ -160,7 +161,7 @@ export class AddRootFolderAction extends Action {
super(id, label);
}
run(): Promise<void> {
override run(): Promise<void> {
return this.commandService.executeCommand(ADD_ROOT_FOLDER_COMMAND_ID);
}
}
@@ -180,7 +181,7 @@ export class GlobalRemoveRootFolderAction extends Action {
super(id, label);
}
async run(): Promise<void> {
override async run(): Promise<void> {
const state = this.contextService.getWorkbenchState();
// Workspace / Folder
@@ -208,7 +209,7 @@ export class SaveWorkspaceAsAction extends Action {
super(id, label);
}
async run(): Promise<void> {
override async run(): Promise<void> {
const configPathUri = await this.workspaceEditingService.pickNewWorkspacePath();
if (configPathUri && hasWorkspaceFileExtension(configPathUri)) {
switch (this.contextService.getWorkbenchState()) {
@@ -240,7 +241,7 @@ export class DuplicateWorkspaceInNewWindowAction extends Action {
super(id, label);
}
async run(): Promise<void> {
override async run(): Promise<void> {
const folders = this.workspaceContextService.getWorkspace().folders;
const remoteAuthority = this.environmentService.remoteAuthority;
@@ -256,7 +257,7 @@ class WorkspaceTrustManageAction extends Action2 {
super({
id: 'workbench.action.manageTrust',
title: { value: localize('manageTrustAction', "Manage Workspace Trust"), original: 'Manage Workspace Trust' },
precondition: ContextKeyExpr.equals(`config.${WORKSPACE_TRUST_ENABLED}`, true),
precondition: ContextKeyExpr.and(IsWebContext.negate(), ContextKeyExpr.equals(`config.${WORKSPACE_TRUST_ENABLED}`, true)),
category: localize('workspacesCategory', "Workspaces"),
f1: true
});

View File

@@ -17,11 +17,12 @@ import { IQuickInputService, IPickOptions, IQuickPickItem } from 'vs/platform/qu
import { getIconClasses } from 'vs/editor/common/services/getIconClasses';
import { IModelService } from 'vs/editor/common/services/modelService';
import { IModeService } from 'vs/editor/common/services/modeService';
import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IFileDialogService, IPickAndOpenOptions } from 'vs/platform/dialogs/common/dialogs';
import { URI } from 'vs/base/common/uri';
import { Schemas } from 'vs/base/common/network';
import { IOpenWindowOptions, IWindowOpenable } from 'vs/platform/windows/common/windows';
import { hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces';
import { IPathService } from 'vs/workbench/services/path/common/pathService';
export const ADD_ROOT_FOLDER_COMMAND_ID = 'addRootFolder';
export const ADD_ROOT_FOLDER_LABEL = localize('addFolderToWorkspace', "Add Folder to Workspace...");
@@ -60,12 +61,14 @@ CommandsRegistry.registerCommand({
handler: async (accessor) => {
const workspaceEditingService = accessor.get(IWorkspaceEditingService);
const dialogsService = accessor.get(IFileDialogService);
const pathService = accessor.get(IPathService);
const folders = await dialogsService.showOpenDialog({
openLabel: mnemonicButtonLabel(localize({ key: 'add', comment: ['&& denotes a mnemonic'] }, "&&Add")),
title: localize('addFolderToWorkspaceTitle', "Add Folder to Workspace"),
canSelectFolders: true,
canSelectMany: true,
defaultUri: await dialogsService.defaultFolderPath()
defaultUri: await dialogsService.defaultFolderPath(),
availableFileSystems: [pathService.defaultUriScheme]
});
if (!folders || !folders.length) {
@@ -127,21 +130,28 @@ interface IOpenFolderAPICommandOptions {
forceNewWindow?: boolean;
forceReuseWindow?: boolean;
noRecentEntry?: boolean;
forceLocalWindow?: boolean;
}
CommandsRegistry.registerCommand({
id: 'vscode.openFolder',
handler: (accessor: ServicesAccessor, uri?: URI, arg?: boolean | IOpenFolderAPICommandOptions) => {
const commandService = accessor.get(ICommandService);
// Be compatible to previous args by converting to options
if (typeof arg === 'boolean') {
arg = { forceNewWindow: arg };
}
// Without URI, ask to pick a folder or workpsace to open
// Without URI, ask to pick a folder or workspace to open
if (!uri) {
return commandService.executeCommand('_files.pickFolderAndOpen', { forceNewWindow: arg?.forceNewWindow });
const options: IPickAndOpenOptions = {
forceNewWindow: arg?.forceNewWindow
};
if (arg?.forceLocalWindow) {
options.remoteAuthority = null;
options.availableFileSystems = ['file'];
}
return commandService.executeCommand('_files.pickFolderAndOpen', options);
}
uri = URI.revive(uri);
@@ -149,17 +159,28 @@ CommandsRegistry.registerCommand({
const options: IOpenWindowOptions = {
forceNewWindow: arg?.forceNewWindow,
forceReuseWindow: arg?.forceReuseWindow,
noRecentEntry: arg?.noRecentEntry
noRecentEntry: arg?.noRecentEntry,
remoteAuthority: arg?.forceLocalWindow ? null : undefined
};
const uriToOpen: IWindowOpenable = (hasWorkspaceFileExtension(uri) || uri.scheme === Schemas.untitled) ? { workspaceUri: uri } : { folderUri: uri };
return commandService.executeCommand('_files.windowOpen', [uriToOpen], options);
},
description: {
description: 'Open a folder or workspace in the current window or new window depending on the newWindow argument. Note that opening in the same window will shutdown the current extension host process and start a new one on the given folder/workspace unless the newWindow parameter is set to true.',
args: [
{ name: 'uri', description: '(optional) Uri of the folder or workspace file to open. If not provided, a native dialog will ask the user for the folder', constraint: (value: any) => value === undefined || value instanceof URI },
{ name: 'options', description: '(optional) Options. Object with the following properties: `forceNewWindow `: Whether to open the folder/workspace in a new window or the same. Defaults to opening in the same window. `noRecentEntry`: Whether the opened URI will appear in the \'Open Recent\' list. Defaults to true. Note, for backward compatibility, options can also be of type boolean, representing the `forceNewWindow` setting.', constraint: (value: any) => value === undefined || typeof value === 'object' || typeof value === 'boolean' }
{
name: 'uri', description: '(optional) Uri of the folder or workspace file to open. If not provided, a native dialog will ask the user for the folder',
constraint: (value: any) => value === undefined || value === null || value instanceof URI
},
{
name: 'options',
description: '(optional) Options. Object with the following properties: ' +
'`forceNewWindow`: Whether to open the folder/workspace in a new window or the same. Defaults to opening in the same window. ' +
'`forceReuseWindow`: Whether to force opening the folder/workspace in the same window. Defaults to false. ' +
'`noRecentEntry`: Whether the opened URI will appear in the \'Open Recent\' list. Defaults to false. ' +
'Note, for backward compatibility, options can also be of type boolean, representing the `forceNewWindow` setting.',
constraint: (value: any) => value === undefined || typeof value === 'object' || typeof value === 'boolean'
}
]
}
});

View File

@@ -76,10 +76,9 @@ export class RangeHighlightDecorations extends Disposable {
}
private getEditor(resourceRange: IRangeHighlightDecoration): ICodeEditor | undefined {
const activeEditor = this.editorService.activeEditor;
const resource = activeEditor?.resource;
if (resource && isEqual(resource, resourceRange.resource)) {
return this.editorService.activeTextEditorControl as ICodeEditor;
const resource = this.editorService.activeEditor?.resource;
if (resource && isEqual(resource, resourceRange.resource) && isCodeEditor(this.editorService.activeTextEditorControl)) {
return this.editorService.activeTextEditorControl;
}
return undefined;
@@ -122,7 +121,7 @@ export class RangeHighlightDecorations extends Disposable {
return (isWholeLine ? RangeHighlightDecorations._WHOLE_LINE_RANGE_HIGHLIGHT : RangeHighlightDecorations._RANGE_HIGHLIGHT);
}
dispose() {
override dispose() {
super.dispose();
if (this.editor?.getModel()) {
@@ -201,7 +200,7 @@ export class FloatingClickWidget extends Widget implements IOverlayWidget {
this.editor.addOverlayWidget(this);
}
dispose(): void {
override dispose(): void {
this.editor.removeOverlayWidget(this);
super.dispose();
@@ -292,7 +291,7 @@ export class OpenWorkspaceButtonContribution extends Disposable implements IEdit
this.openWorkspaceButton = undefined;
}
dispose(): void {
override dispose(): void {
this.disposeOpenWorkspaceWidgetRenderer();
super.dispose();

View File

@@ -118,7 +118,7 @@ export abstract class Composite extends Component implements IComposite {
this.parent = parent;
}
updateStyles(): void {
override updateStyles(): void {
super.updateStyles();
}
@@ -161,7 +161,7 @@ export abstract class Composite extends Component implements IComposite {
/**
* Returns an array of actions to show in the action bar of the composite.
*/
getActions(): ReadonlyArray<IAction> {
getActions(): readonly IAction[] {
return [];
}
@@ -169,14 +169,14 @@ export abstract class Composite extends Component implements IComposite {
* Returns an array of actions to show in the action bar of the composite
* in a less prominent way then action from getActions.
*/
getSecondaryActions(): ReadonlyArray<IAction> {
getSecondaryActions(): readonly IAction[] {
return [];
}
/**
* Returns an array of actions to show in the context menu of the composite
*/
getContextMenuActions(): ReadonlyArray<IAction> {
getContextMenuActions(): readonly IAction[] {
return [];
}

View File

@@ -19,7 +19,7 @@ import { SideBarVisibleContext } from 'vs/workbench/common/viewlet';
import { IWorkbenchLayoutService, Parts, positionToString } from 'vs/workbench/services/layout/browser/layoutService';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { PanelMaximizedContext, PanelPositionContext, PanelVisibleContext } from 'vs/workbench/common/panel';
import { getRemoteName } from 'vs/platform/remote/common/remoteHosts';
import { getRemoteName, getVirtualWorkspaceScheme } from 'vs/platform/remote/common/remoteHosts';
import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService';
import { isNative } from 'vs/base/common/platform';
@@ -27,9 +27,10 @@ export const WorkbenchStateContext = new RawContextKey<string>('workbenchState',
export const WorkspaceFolderCountContext = new RawContextKey<number>('workspaceFolderCount', 0, localize('workspaceFolderCount', "The number of root folders in the workspace"));
export const EmptyWorkspaceSupportContext = new RawContextKey<boolean>('emptyWorkspaceSupport', true, true);
export const DirtyWorkingCopiesContext = new RawContextKey<boolean>('dirtyWorkingCopies', false, localize('dirtyWorkingCopies', "Wether there are any dirty working copies"));
export const DirtyWorkingCopiesContext = new RawContextKey<boolean>('dirtyWorkingCopies', false, localize('dirtyWorkingCopies', "Whether there are any dirty working copies"));
export const RemoteNameContext = new RawContextKey<string>('remoteName', '', localize('remoteName', "The name of the remote the window is connected to or an empty string if not connected to any remote"));
export const VirtualWorkspaceContext = new RawContextKey<string>('virtualWorkspace', '', localize('virtualWorkspace', "The scheme of the current workspace if is from a virtual file system or an empty string."));
export const IsFullscreenContext = new RawContextKey<boolean>('isFullscreen', false, localize('isFullscreen', "Whether the window is in fullscreen mode"));
@@ -68,6 +69,8 @@ export class WorkbenchContextKeysHandler extends Disposable {
private panelVisibleContext: IContextKey<boolean>;
private panelMaximizedContext: IContextKey<boolean>;
private vitualWorkspaceContext: IContextKey<string>;
constructor(
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
@@ -91,6 +94,9 @@ export class WorkbenchContextKeysHandler extends Disposable {
RemoteNameContext.bindTo(this.contextKeyService).set(getRemoteName(this.environmentService.remoteAuthority) || '');
this.vitualWorkspaceContext = VirtualWorkspaceContext.bindTo(this.contextKeyService);
this.updateVirtualWorkspaceContextKey();
// Capabilities
HasWebFileSystemAccess.bindTo(this.contextKeyService).set(WebFileSystemAccess.supported(window));
@@ -161,7 +167,7 @@ export class WorkbenchContextKeysHandler extends Disposable {
}
private registerListeners(): void {
this.editorGroupService.whenRestored.then(() => this.updateEditorContextKeys());
this.editorGroupService.whenReady.then(() => this.updateEditorContextKeys());
this._register(this.editorService.onDidActiveEditorChange(() => this.updateEditorContextKeys()));
this._register(this.editorService.onDidVisibleEditorsChange(() => this.updateEditorContextKeys()));
@@ -173,7 +179,10 @@ export class WorkbenchContextKeysHandler extends Disposable {
this._register(addDisposableListener(window, EventType.FOCUS_IN, () => this.updateInputContextKeys(), true));
this._register(this.contextService.onDidChangeWorkbenchState(() => this.updateWorkbenchStateContextKey()));
this._register(this.contextService.onDidChangeWorkspaceFolders(() => this.updateWorkspaceFolderCountContextKey()));
this._register(this.contextService.onDidChangeWorkspaceFolders(() => {
this.updateWorkspaceFolderCountContextKey();
this.updateVirtualWorkspaceContextKey();
}));
this._register(this.configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration('workbench.editor.openSideBySideDirection')) {
@@ -285,4 +294,8 @@ export class WorkbenchContextKeysHandler extends Disposable {
private updateSideBarContextKeys(): void {
this.sideBarVisibleContext.set(this.layoutService.isVisible(Parts.SIDEBAR_PART));
}
private updateVirtualWorkspaceContextKey(): void {
this.vitualWorkspaceContext.set(getVirtualWorkspaceScheme(this.contextService.getWorkspace()) || '');
}
}

View File

@@ -3,13 +3,15 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Registry } from 'vs/platform/registry/common/platform';
import { hasWorkspaceFileExtension, IWorkspaceFolderCreationData, IRecentFile, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces';
import { normalize } from 'vs/base/common/path';
import { basename, isEqual } from 'vs/base/common/resources';
import { IFileService } from 'vs/platform/files/common/files';
import { IWindowOpenable } from 'vs/platform/windows/common/windows';
import { URI } from 'vs/base/common/uri';
import { ITextFileService, stringToSnapshot } from 'vs/workbench/services/textfile/common/textfiles';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { bufferToReadable, VSBuffer } from 'vs/base/common/buffer';
import { FileAccess, Schemas } from 'vs/base/common/network';
import { ITextEditorOptions } from 'vs/platform/editor/common/editor';
import { DataTransfers, IDragAndDropData } from 'vs/base/browser/dnd';
@@ -19,7 +21,7 @@ import { MIME_BINARY } from 'vs/base/common/mime';
import { isWindows } from 'vs/base/common/platform';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { isCodeEditor } from 'vs/editor/browser/editorBrowser';
import { IEditorIdentifier, GroupIdentifier } from 'vs/workbench/common/editor';
import { IEditorIdentifier, GroupIdentifier, IEditorInputFactoryRegistry, EditorExtensions } from 'vs/workbench/common/editor';
import { IEditorService, IResourceEditorInputType } from 'vs/workbench/services/editor/common/editorService';
import { Disposable, IDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { addDisposableListener, EventType } from 'vs/base/browser/dom';
@@ -27,8 +29,9 @@ import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsSe
import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing';
import { withNullAsUndefined } from 'vs/base/common/types';
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
import { IWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/common/workingCopyBackup';
import { Emitter } from 'vs/base/common/event';
import { NO_TYPE_ID } from 'vs/workbench/services/workingCopy/common/workingCopy';
export interface IDraggedResource {
resource: URI;
@@ -110,7 +113,7 @@ export function extractResources(e: DragEvent, externalOnly?: boolean): Array<ID
if (e.dataTransfer && e.dataTransfer.files) {
for (let i = 0; i < e.dataTransfer.files.length; i++) {
const file = e.dataTransfer.files[i];
if (file?.path /* Electron only */ && !resources.some(r => r.resource.fsPath === file.path) /* prevent duplicates */) {
if (file?.path /* Electron only */ && !resources.some(resource => resource.resource.fsPath === file.path) /* prevent duplicates */) {
try {
resources.push({ resource: URI.file(file.path), isExternal: true });
} catch (error) {
@@ -126,7 +129,7 @@ export function extractResources(e: DragEvent, externalOnly?: boolean): Array<ID
try {
const codeFiles: string[] = JSON.parse(rawCodeFiles);
codeFiles.forEach(codeFile => {
if (!resources.some(r => r.resource.fsPath === codeFile) /* prevent duplicates */) {
if (!resources.some(resource => resource.resource.fsPath === codeFile) /* prevent duplicates */) {
resources.push({ resource: URI.file(codeFile), isExternal: true });
}
});
@@ -159,7 +162,7 @@ export class ResourcesDropHandler {
@IFileService private readonly fileService: IFileService,
@IWorkspacesService private readonly workspacesService: IWorkspacesService,
@ITextFileService private readonly textFileService: ITextFileService,
@IBackupFileService private readonly backupFileService: IBackupFileService,
@IWorkingCopyBackupService private readonly workingCopyBackupService: IWorkingCopyBackupService,
@IEditorService private readonly editorService: IEditorService,
@IWorkspaceEditingService private readonly workspaceEditingService: IWorkspaceEditingService,
@IHostService private readonly hostService: IHostService
@@ -167,7 +170,7 @@ export class ResourcesDropHandler {
}
async handleDrop(event: DragEvent, resolveTargetGroup: () => IEditorGroup | undefined, afterDrop: (targetGroup: IEditorGroup | undefined) => void, targetIndex?: number): Promise<void> {
const untitledOrFileResources = extractResources(event).filter(r => this.fileService.canHandleResource(r.resource) || r.resource.scheme === Schemas.untitled);
const untitledOrFileResources = extractResources(event).filter(resource => this.fileService.canHandleResource(resource.resource) || resource.resource.scheme === Schemas.untitled);
if (!untitledOrFileResources.length) {
return;
}
@@ -227,6 +230,7 @@ export class ResourcesDropHandler {
}
private async handleDirtyEditorDrop(droppedDirtyEditor: IDraggedEditor): Promise<boolean> {
const fileEditorFactory = Registry.as<IEditorInputFactoryRegistry>(EditorExtensions.EditorInputFactories).getFileEditorInputFactory();
// Untitled: always ensure that we open a new untitled editor for each file we drop
if (droppedDirtyEditor.resource.scheme === Schemas.untitled) {
@@ -237,7 +241,7 @@ export class ResourcesDropHandler {
}
// File: ensure the file is not dirty or opened already
else if (this.textFileService.isDirty(droppedDirtyEditor.resource) || this.editorService.isOpen({ resource: droppedDirtyEditor.resource })) {
else if (this.textFileService.isDirty(droppedDirtyEditor.resource) || this.editorService.isOpened({ resource: droppedDirtyEditor.resource, typeId: fileEditorFactory.typeId })) {
return false;
}
@@ -245,7 +249,7 @@ export class ResourcesDropHandler {
// content and turn it into a backup so that it loads the contents
if (typeof droppedDirtyEditor.dirtyContent === 'string') {
try {
await this.backupFileService.backup(droppedDirtyEditor.resource, stringToSnapshot(droppedDirtyEditor.dirtyContent));
await this.workingCopyBackupService.backup({ resource: droppedDirtyEditor.resource, typeId: NO_TYPE_ID }, bufferToReadable(VSBuffer.fromString(droppedDirtyEditor.dirtyContent)));
} catch (e) {
// Ignore error
}

View File

@@ -4,22 +4,19 @@
*--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls';
import { Event } from 'vs/base/common/event';
import { EditorInput } from 'vs/workbench/common/editor';
import { EditorInput, EditorResourceAccessor, IEditorInput, EditorExtensions, SideBySideEditor } from 'vs/workbench/common/editor';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { Registry } from 'vs/platform/registry/common/platform';
import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane';
import { IConstructorSignature0, IInstantiationService, BrandedService } from 'vs/platform/instantiation/common/instantiation';
import { IConstructorSignature0, IInstantiationService, BrandedService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { insert } from 'vs/base/common/arrays';
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { IJSONSchema } from 'vs/base/common/jsonSchema';
import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration';
import { Extensions as ConfigurationExtensions, IConfigurationNode, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
export const Extensions = {
Editors: 'workbench.contributions.editors',
Associations: 'workbench.editors.associations'
};
import { Promises } from 'vs/base/common/async';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity';
import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService';
import { URI } from 'vs/workbench/workbench.web.api';
import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService';
//#region Editors Registry
@@ -161,7 +158,7 @@ class EditorRegistry implements IEditorRegistry {
if (descriptors.length > 0) {
// Ask the input for its preferred Editor
const preferredEditorId = input.getPreferredEditorId(descriptors.map(d => d.getId()));
const preferredEditorId = input.getPreferredEditorId(descriptors.map(descriptor => descriptor.getId()));
if (preferredEditorId) {
return this.getEditorById(preferredEditorId);
}
@@ -194,137 +191,91 @@ class EditorRegistry implements IEditorRegistry {
}
}
Registry.add(Extensions.Editors, new EditorRegistry());
Registry.add(EditorExtensions.Editors, new EditorRegistry());
//#endregion
//#region Editor Close Tracker
//#region Editor Associations
export function whenEditorClosed(accessor: ServicesAccessor, resources: URI[]): Promise<void> {
const editorService = accessor.get(IEditorService);
const uriIdentityService = accessor.get(IUriIdentityService);
const workingCopyService = accessor.get(IWorkingCopyService);
export const editorsAssociationsSettingId = 'workbench.editorAssociations';
return new Promise(resolve => {
let remainingResources = [...resources];
export const DEFAULT_EDITOR_ASSOCIATION: IEditorType = {
id: 'default',
displayName: localize('promptOpenWith.defaultEditor.displayName', "Text Editor"),
providerDisplayName: localize('builtinProviderDisplayName', "Built-in")
};
// Observe any editor closing from this moment on
const listener = editorService.onDidCloseEditor(async event => {
const primaryResource = EditorResourceAccessor.getOriginalUri(event.editor, { supportSideBySide: SideBySideEditor.PRIMARY });
const secondaryResource = EditorResourceAccessor.getOriginalUri(event.editor, { supportSideBySide: SideBySideEditor.SECONDARY });
export type EditorAssociation = {
readonly viewType: string;
readonly filenamePattern?: string;
};
export type EditorsAssociations = readonly EditorAssociation[];
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
const editorTypeSchemaAddition: IJSONSchema = {
type: 'string',
enum: []
};
const editorAssociationsConfigurationNode: IConfigurationNode = {
...workbenchConfigurationNodeBase,
properties: {
'workbench.editorAssociations': {
type: 'array',
markdownDescription: localize('editor.editorAssociations', "Configure which editor to use for specific file types."),
items: {
type: 'object',
defaultSnippets: [{
body: {
'viewType': '$1',
'filenamePattern': '$2'
}
}],
properties: {
'viewType': {
anyOf: [
{
type: 'string',
description: localize('editor.editorAssociations.viewType', "The unique id of the editor to use."),
},
editorTypeSchemaAddition
]
},
'filenamePattern': {
type: 'string',
description: localize('editor.editorAssociations.filenamePattern', "Glob pattern specifying which files the editor should be used for."),
}
// Remove from resources to wait for being closed based on the
// resources from editors that got closed
remainingResources = remainingResources.filter(resource => {
if (uriIdentityService.extUri.isEqual(resource, primaryResource) || uriIdentityService.extUri.isEqual(resource, secondaryResource)) {
return false; // remove - the closing editor matches this resource
}
return true; // keep - not yet closed
});
// All resources to wait for being closed are closed
if (remainingResources.length === 0) {
// If auto save is configured with the default delay (1s) it is possible
// to close the editor while the save still continues in the background. As such
// we have to also check if the editors to track for are dirty and if so wait
// for them to get saved.
const dirtyResources = resources.filter(resource => workingCopyService.isDirty(resource));
if (dirtyResources.length > 0) {
await Promises.settled(dirtyResources.map(async resource => await new Promise<void>(resolve => {
if (!workingCopyService.isDirty(resource)) {
return resolve(); // return early if resource is not dirty
}
// Otherwise resolve promise when resource is saved
const listener = workingCopyService.onDidChangeDirty(workingCopy => {
if (!workingCopy.isDirty() && uriIdentityService.extUri.isEqual(resource, workingCopy.resource)) {
listener.dispose();
return resolve();
}
});
})));
}
listener.dispose();
return resolve();
}
}
}
};
export interface IEditorType {
readonly id: string;
readonly displayName: string;
readonly providerDisplayName: string;
}
export interface IEditorTypesHandler {
readonly onDidChangeEditorTypes: Event<void>;
getEditorTypes(): IEditorType[];
}
export interface IEditorAssociationsRegistry {
/**
* Register handlers for editor types
*/
registerEditorTypesHandler(id: string, handler: IEditorTypesHandler): IDisposable;
}
class EditorAssociationsRegistry implements IEditorAssociationsRegistry {
private readonly editorTypesHandlers = new Map<string, IEditorTypesHandler>();
registerEditorTypesHandler(id: string, handler: IEditorTypesHandler): IDisposable {
if (this.editorTypesHandlers.has(id)) {
throw new Error(`An editor type handler with ${id} was already registered.`);
}
this.editorTypesHandlers.set(id, handler);
this.updateEditorAssociationsSchema();
const editorTypeChangeEvent = handler.onDidChangeEditorTypes(() => {
this.updateEditorAssociationsSchema();
});
return {
dispose: () => {
editorTypeChangeEvent.dispose();
this.editorTypesHandlers.delete(id);
this.updateEditorAssociationsSchema();
}
};
}
private updateEditorAssociationsSchema() {
const enumValues: string[] = [];
const enumDescriptions: string[] = [];
const editorTypes: IEditorType[] = [DEFAULT_EDITOR_ASSOCIATION];
for (const [, handler] of this.editorTypesHandlers) {
editorTypes.push(...handler.getEditorTypes());
}
for (const { id, providerDisplayName } of editorTypes) {
enumValues.push(id);
enumDescriptions.push(localize('editorAssociations.viewType.sourceDescription', "Source: {0}", providerDisplayName));
}
editorTypeSchemaAddition.enum = enumValues;
editorTypeSchemaAddition.enumDescriptions = enumDescriptions;
configurationRegistry.notifyConfigurationSchemaUpdated(editorAssociationsConfigurationNode);
}
});
}
//#endregion
//#region ARIA
export function computeEditorAriaLabel(input: IEditorInput, index: number | undefined, group: IEditorGroup | undefined, groupCount: number): string {
let ariaLabel = input.getAriaLabel();
if (group && !group.isPinned(input)) {
ariaLabel = localize('preview', "{0}, preview", ariaLabel);
}
if (group?.isSticky(index ?? input)) {
ariaLabel = localize('pinned', "{0}, pinned", ariaLabel);
}
// Apply group information to help identify in
// which group we are (only if more than one group
// is actually opened)
if (group && groupCount > 1) {
ariaLabel = `${ariaLabel}, ${group.ariaLabel}`;
}
return ariaLabel;
}
Registry.add(Extensions.Associations, new EditorAssociationsRegistry());
configurationRegistry.registerConfiguration(editorAssociationsConfigurationNode);
//#endregion

View File

@@ -227,7 +227,7 @@ export class ResourceLabels extends Disposable {
this.labels = [];
}
dispose(): void {
override dispose(): void {
super.dispose();
this.clear();
@@ -569,6 +569,7 @@ class ResourceLabelWidget extends IconLabel {
if (this.options.fileDecorations.badges) {
iconLabelOptions.extraClasses.push(deco.badgeClassName);
iconLabelOptions.extraClasses.push(deco.iconClassName);
}
}
}
@@ -580,7 +581,7 @@ class ResourceLabelWidget extends IconLabel {
return true;
}
dispose(): void {
override dispose(): void {
super.dispose();
this.label = undefined;

View File

@@ -7,7 +7,7 @@ import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { Event, Emitter } from 'vs/base/common/event';
import { EventType, addDisposableListener, getClientArea, Dimension, position, size, IDimension, isAncestorUsingFlowTo } from 'vs/base/browser/dom';
import { onDidChangeFullscreen, isFullscreen } from 'vs/base/browser/browser';
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
import { IWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/common/workingCopyBackup';
import { Registry } from 'vs/platform/registry/common/platform';
import { isWindows, isLinux, isMacintosh, isWeb, isNative } from 'vs/base/common/platform';
import { pathsToEditors, SideBySideEditorInput } from 'vs/workbench/common/editor';
@@ -22,7 +22,7 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
import { ITitleService } from 'vs/workbench/services/title/common/titleService';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { LifecyclePhase, StartupKind, ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { StartupKind, ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { MenuBarVisibility, getTitleBarStyle, getMenuBarVisibility, IPath } from 'vs/platform/windows/common/windows';
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { IEditor } from 'vs/editor/common/editorCommon';
@@ -159,7 +159,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
private environmentService!: IWorkbenchEnvironmentService;
private extensionService!: IExtensionService;
private configurationService!: IConfigurationService;
private lifecycleService!: ILifecycleService;
private storageService!: IStorageService;
private hostService!: IHostService;
private editorService!: IEditorService;
@@ -169,7 +168,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
private viewletService!: IViewletService;
private viewDescriptorService!: IViewDescriptorService;
private contextService!: IWorkspaceContextService;
private backupFileService!: IBackupFileService;
private workingCopyBackupService!: IWorkingCopyBackupService;
private notificationService!: INotificationService;
private themeService!: IThemeService;
private activityBarService!: IActivityBarService;
@@ -232,7 +231,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
wasPanelVisible: false,
transitionDisposables: new DisposableStore(),
setNotificationsFilter: false,
editorWidgetSet: new Set<IEditor>()
}
};
@@ -247,11 +245,10 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
// Services
this.environmentService = accessor.get(IWorkbenchEnvironmentService);
this.configurationService = accessor.get(IConfigurationService);
this.lifecycleService = accessor.get(ILifecycleService);
this.hostService = accessor.get(IHostService);
this.contextService = accessor.get(IWorkspaceContextService);
this.storageService = accessor.get(IStorageService);
this.backupFileService = accessor.get(IBackupFileService);
this.workingCopyBackupService = accessor.get(IWorkingCopyBackupService);
this.themeService = accessor.get(IThemeService);
this.extensionService = accessor.get(IExtensionService);
this.logService = accessor.get(ILogService);
@@ -314,7 +311,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
}
// Theme changes
this._register(this.themeService.onDidColorThemeChange(theme => this.updateStyles()));
this._register(this.themeService.onDidColorThemeChange(() => this.updateStyles()));
// Window focus changes
this._register(this.hostService.onDidChangeFocus(e => this.onWindowFocusChanged(e)));
@@ -582,7 +579,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
const { views } = defaultLayout;
if (views?.length) {
this.state.views.defaults = views.map(v => v.id);
this.state.views.defaults = views.map(view => view.id);
}
}
@@ -599,7 +596,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
// Files to diff is exclusive
return pathsToEditors(initialFilesToOpen.filesToDiff, fileService).then(filesToDiff => {
if (filesToDiff?.length === 2) {
if (filesToDiff.length === 2) {
return [{
leftResource: filesToDiff[0].resource,
rightResource: filesToDiff[1].resource,
@@ -613,13 +610,13 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
});
}
// Empty workbench
// Empty workbench configured to open untitled file if empty
else if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY && this.configurationService.getValue('workbench.startupEditor') === 'newUntitledFile') {
if (this.editorGroupService.willRestoreEditors) {
return []; // do not open any empty untitled file if we restored editors from previous session
if (this.editorGroupService.hasRestorableState) {
return []; // do not open any empty untitled file if we restored groups/editors from previous session
}
return this.backupFileService.hasBackups().then(hasBackups => {
return this.workingCopyBackupService.hasBackups().then(hasBackups => {
if (hasBackups) {
return []; // do not open any empty untitled file if we have backups to restore
}
@@ -635,6 +632,8 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
get openedDefaultEditors() { return this._openedDefaultEditors; }
private getInitialFilesToOpen(): { filesToOpenOrCreate?: IPath[], filesToDiff?: IPath[]; } | undefined {
// Check for editors from `defaultLayout` options first
const defaultLayout = this.environmentService.options?.defaultLayout;
if (defaultLayout?.editors?.length && (defaultLayout.force || this.storageService.isNew(StorageScope.WORKSPACE))) {
this._openedDefaultEditors = true;
@@ -646,6 +645,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
};
}
// Then check for files to open, create or diff from main side
const { filesToOpenOrCreate, filesToDiff } = this.environmentService.configuration;
if (filesToOpenOrCreate || filesToDiff) {
return { filesToOpenOrCreate, filesToDiff };
@@ -654,17 +654,40 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
return undefined;
}
protected async restoreWorkbenchLayout(): Promise<void> {
const restorePromises: Promise<void>[] = [];
private whenReadyResolve: (() => void) | undefined;
protected readonly whenReady = new Promise<void>(resolve => (this.whenReadyResolve = resolve));
private whenRestoredResolve: (() => void) | undefined;
readonly whenRestored = new Promise<void>(resolve => (this.whenRestoredResolve = resolve));
private restored = false;
isRestored(): boolean {
return this.restored;
}
protected restoreParts(): void {
// distinguish long running restore operations that
// are required for the layout to be ready from those
// that are needed to signal restoring is done
const layoutReadyPromises: Promise<unknown>[] = [];
const layoutRestoredPromises: Promise<unknown>[] = [];
// Restore editors
restorePromises.push((async () => {
layoutReadyPromises.push((async () => {
mark('code/willRestoreEditors');
// first ensure the editor part is restored
await this.editorGroupService.whenRestored;
// first ensure the editor part is ready
await this.editorGroupService.whenReady;
// then see for editors to open as instructed
// it is important that we trigger this from
// the overall restore flow to reduce possible
// flicker on startup: we want any editor to
// open to get a chance to open first before
// signaling that layout is restored, but we do
// not need to await the editors from having
// fully loaded.
let editors: IResourceEditorInputType[];
if (Array.isArray(this.state.editor.editorsToOpen)) {
editors = this.state.editor.editorsToOpen;
@@ -672,19 +695,32 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
editors = await this.state.editor.editorsToOpen;
}
let openEditorsPromise: Promise<unknown> | undefined = undefined;
if (editors.length) {
await this.editorService.openEditors(editors);
openEditorsPromise = this.editorService.openEditors(editors);
}
mark('code/didRestoreEditors');
// do not block the overall layout ready flow from potentially
// slow editors to resolve on startup
layoutRestoredPromises.push(
Promise.all([
openEditorsPromise,
this.editorGroupService.whenRestored
]).finally(() => {
// the `code/didRestoreEditors` perf mark is specifically
// for when visible editors have resolved, so we only mark
// if when editor group service has restored.
mark('code/didRestoreEditors');
})
);
})());
// Restore default views
// Restore default views (only when `IDefaultLayout` is provided)
const restoreDefaultViewsPromise = (async () => {
if (this.state.views.defaults?.length) {
mark('code/willOpenDefaultViews');
let locationsRestored: { id: string; order: number; }[] = [];
const locationsRestored: { id: string; order: number; }[] = [];
const tryOpenView = (view: { id: string; order: number; }): boolean => {
const location = this.viewDescriptorService.getViewLocationById(view.id);
@@ -742,10 +778,10 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
mark('code/didOpenDefaultViews');
}
})();
restorePromises.push(restoreDefaultViewsPromise);
layoutReadyPromises.push(restoreDefaultViewsPromise);
// Restore Sidebar
restorePromises.push((async () => {
layoutReadyPromises.push((async () => {
// Restoring views could mean that sidebar already
// restored, as such we need to test again
@@ -765,7 +801,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
})());
// Restore Panel
restorePromises.push((async () => {
layoutReadyPromises.push((async () => {
// Restoring views could mean that panel already
// restored, as such we need to test again
@@ -776,7 +812,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
mark('code/willRestorePanel');
const panel = await this.panelService.openPanel(this.state.panel.panelToRestore!);
const panel = await this.panelService.openPanel(this.state.panel.panelToRestore);
if (!panel) {
await this.panelService.openPanel(Registry.as<PanelRegistry>(PanelExtensions.Panels).getDefaultPanelId()); // fallback to default panel as needed
}
@@ -794,8 +830,16 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
this.centerEditorLayout(true, true);
}
// Await restore to be done
await Promises.settled(restorePromises);
// Await for promises that we recorded to update
// our ready and restored states properly.
Promises.settled(layoutReadyPromises).finally(() => {
this.whenReadyResolve?.();
Promises.settled(layoutRestoredPromises).finally(() => {
this.restored = true;
this.whenRestoredResolve?.();
});
});
}
private updatePanelPosition() {
@@ -822,10 +866,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
this._register(delegate.onDidChangeNotificationsVisibility(visible => this._onDidChangeNotificationsVisibility.fire(visible)));
}
isRestored(): boolean {
return this.lifecycleService.phase >= LifecyclePhase.Restored;
}
hasFocus(part: Parts): boolean {
const activeElement = document.activeElement;
if (!activeElement) {
@@ -976,22 +1016,13 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
editor.updateOptions({ lineNumbers });
};
const editorControlSet = this.state.zenMode.editorWidgetSet;
if (!lineNumbers) {
// Reset line numbers on all editors visible and non-visible
for (const editor of editorControlSet) {
setEditorLineNumbers(editor);
for (const editorControl of this.editorService.visibleTextEditorControls) {
setEditorLineNumbers(editorControl);
}
editorControlSet.clear();
} else {
for (const editorControl of this.editorService.visibleTextEditorControls) {
if (!editorControlSet.has(editorControl)) {
editorControlSet.add(editorControl);
this.state.zenMode.transitionDisposables.add(editorControl.onDidDispose(() => {
editorControlSet.delete(editorControl);
}));
}
setEditorLineNumbers(editorControl);
}
}
@@ -1044,9 +1075,9 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
if (config.silentNotifications) {
this.notificationService.setFilter(NotificationsFilter.ERROR);
}
this.state.zenMode.transitionDisposables.add(this.configurationService.onDidChangeConfiguration(c => {
this.state.zenMode.transitionDisposables.add(this.configurationService.onDidChangeConfiguration(e => {
const silentNotificationsKey = 'zenMode.silentNotifications';
if (c.affectsConfiguration(silentNotificationsKey)) {
if (e.affectsConfiguration(silentNotificationsKey)) {
const filter = this.configurationService.getValue(silentNotificationsKey) ? NotificationsFilter.ERROR : NotificationsFilter.OFF;
this.notificationService.setFilter(filter);
}
@@ -1780,7 +1811,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
return result;
}
dispose(): void {
override dispose(): void {
super.dispose();
this.disposed = true;

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
.monaco-workbench .part {
box-sizing: border-box;
box-sizing: border-box;
overflow: hidden;
}
@@ -29,7 +29,7 @@
.monaco-workbench .part > .title {
height: 35px;
display: flex;
box-sizing: border-box;
box-sizing: border-box;
overflow: hidden;
}
@@ -73,9 +73,6 @@
.monaco-workbench .part > .title > .title-actions .action-label {
display: block;
height: 35px;
line-height: 35px;
min-width: 28px;
background-size: 16px;
background-position: center center;
background-repeat: no-repeat;
@@ -103,6 +100,10 @@
}
.monaco-workbench .part > .content > .monaco-progress-container .progress-bit,
.monaco-workbench .part.editor > .content .monaco-progress-container .progress-bit {
.monaco-workbench
.part.editor
> .content
.monaco-progress-container
.progress-bit {
height: 2px;
}

View File

@@ -36,13 +36,13 @@ export abstract class PaneComposite extends Composite implements IPaneComposite
super(id, telemetryService, themeService, storageService);
}
create(parent: HTMLElement): void {
override create(parent: HTMLElement): void {
this.viewPaneContainer = this._register(this.createViewPaneContainer(parent));
this._register(this.viewPaneContainer.onTitleAreaUpdate(() => this.updateTitleArea()));
this.viewPaneContainer.create(parent);
}
setVisible(visible: boolean): void {
override setVisible(visible: boolean): void {
super.setVisible(visible);
this.viewPaneContainer?.setVisible(visible);
}
@@ -63,15 +63,15 @@ export abstract class PaneComposite extends Composite implements IPaneComposite
return this.viewPaneContainer;
}
getActionsContext(): unknown {
override getActionsContext(): unknown {
return this.getViewPaneContainer()?.getActionsContext();
}
getContextMenuActions(): ReadonlyArray<IAction> {
override getContextMenuActions(): readonly IAction[] {
return this.viewPaneContainer?.menuActions?.getContextMenuActions() ?? [];
}
getActions(): ReadonlyArray<IAction> {
override getActions(): readonly IAction[] {
const result = [];
if (this.viewPaneContainer?.menuActions) {
result.push(...this.viewPaneContainer.menuActions.getPrimaryActions());
@@ -82,7 +82,7 @@ export abstract class PaneComposite extends Composite implements IPaneComposite
return result;
}
getSecondaryActions(): ReadonlyArray<IAction> {
override getSecondaryActions(): readonly IAction[] {
if (!this.viewPaneContainer?.menuActions) {
return [];
}
@@ -116,19 +116,19 @@ export abstract class PaneComposite extends Composite implements IPaneComposite
return menuActions.length ? menuActions : viewPaneActions;
}
getActionViewItem(action: IAction): IActionViewItem | undefined {
override getActionViewItem(action: IAction): IActionViewItem | undefined {
return this.viewPaneContainer?.getActionViewItem(action);
}
getTitle(): string {
override getTitle(): string {
return this.viewPaneContainer?.getTitle() ?? '';
}
saveState(): void {
override saveState(): void {
super.saveState();
}
focus(): void {
override focus(): void {
this.viewPaneContainer?.focus();
}

View File

@@ -37,19 +37,19 @@ export abstract class Panel extends PaneComposite implements IPanel {
this._register(this.panelActions.onDidChange(() => this.updateTitleArea()));
}
getActions(): ReadonlyArray<IAction> {
override getActions(): readonly IAction[] {
return [...super.getActions(), ...this.panelActions.getPrimaryActions()];
}
getSecondaryActions(): ReadonlyArray<IAction> {
override getSecondaryActions(): readonly IAction[] {
return this.mergeSecondaryActions(super.getSecondaryActions(), this.panelActions.getSecondaryActions());
}
getContextMenuActions(): ReadonlyArray<IAction> {
override getContextMenuActions(): readonly IAction[] {
return this.mergeSecondaryActions(super.getContextMenuActions(), this.panelActions.getContextMenuActions());
}
private mergeSecondaryActions(actions: ReadonlyArray<IAction>, panelActions: IAction[]): ReadonlyArray<IAction> {
private mergeSecondaryActions(actions: readonly IAction[], panelActions: IAction[]): readonly IAction[] {
if (panelActions.length && actions.length) {
return [
...actions,

View File

@@ -52,7 +52,7 @@ export abstract class Part extends Component implements ISerializableView {
layoutService.registerPart(this);
}
protected onThemeChange(theme: IColorTheme): void {
protected override onThemeChange(theme: IColorTheme): void {
// only call if our create() method has been called
if (this.parent) {
@@ -60,7 +60,7 @@ export abstract class Part extends Component implements ISerializableView {
}
}
updateStyles(): void {
override updateStyles(): void {
super.updateStyles();
}

View File

@@ -16,7 +16,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { activeContrastBorder, focusBorder } from 'vs/platform/theme/common/colorRegistry';
import { IColorTheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { ActivityAction, ActivityActionViewItem, ICompositeBar, ICompositeBarColors, ToggleCompositePinnedAction } from 'vs/workbench/browser/parts/compositeBarActions';
import { ActivityAction, ActivityActionViewItem, IActivityHoverOptions, ICompositeBar, ICompositeBarColors, ToggleCompositePinnedAction } from 'vs/workbench/browser/parts/compositeBarActions';
import { CATEGORIES } from 'vs/workbench/common/actions';
import { IActivity } from 'vs/workbench/common/activity';
import { ACTIVITY_BAR_FOREGROUND, ACTIVITY_BAR_ACTIVE_BORDER, ACTIVITY_BAR_ACTIVE_FOCUS_BORDER, ACTIVITY_BAR_ACTIVE_BACKGROUND } from 'vs/workbench/common/theme';
@@ -35,6 +35,8 @@ import { AnchorAlignment, AnchorAxisAlignment } from 'vs/base/browser/ui/context
import { getTitleBarStyle } from 'vs/platform/windows/common/windows';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { IHoverService } from 'vs/workbench/services/hover/browser/hover';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
export class ViewContainerActivityAction extends ActivityAction {
@@ -56,7 +58,7 @@ export class ViewContainerActivityAction extends ActivityAction {
this.activity = activity;
}
async run(event: unknown): Promise<void> {
override async run(event: unknown): Promise<void> {
if (event instanceof MouseEvent && event.button === 2) {
return; // do not run on right click
}
@@ -111,17 +113,20 @@ class MenuActivityActionViewItem extends ActivityActionViewItem {
action: ActivityAction,
private contextMenuActionsProvider: () => IAction[],
colors: (theme: IColorTheme) => ICompositeBarColors,
hoverOptions: IActivityHoverOptions,
@IThemeService themeService: IThemeService,
@IHoverService hoverService: IHoverService,
@IMenuService protected readonly menuService: IMenuService,
@IContextMenuService protected readonly contextMenuService: IContextMenuService,
@IContextKeyService protected readonly contextKeyService: IContextKeyService,
@IConfigurationService protected readonly configurationService: IConfigurationService,
@IWorkbenchEnvironmentService protected readonly environmentService: IWorkbenchEnvironmentService
@IConfigurationService configurationService: IConfigurationService,
@IWorkbenchEnvironmentService protected readonly environmentService: IWorkbenchEnvironmentService,
@IKeybindingService keybindingService: IKeybindingService,
) {
super(action, { draggable: false, colors, icon: true, hasPopup: true }, themeService);
super(action, { draggable: false, colors, icon: true, hasPopup: true, hoverOptions }, themeService, hoverService, configurationService, keybindingService);
}
render(container: HTMLElement): void {
override render(container: HTMLElement): void {
super.render(container);
// Context menus are triggered on mouse down so that an item can be picked
@@ -190,7 +195,9 @@ export class AccountsActivityActionViewItem extends MenuActivityActionViewItem {
action: ActivityAction,
contextMenuActionsProvider: () => IAction[],
colors: (theme: IColorTheme) => ICompositeBarColors,
activityHoverOptions: IActivityHoverOptions,
@IThemeService themeService: IThemeService,
@IHoverService hoverService: IHoverService,
@IContextMenuService contextMenuService: IContextMenuService,
@IMenuService menuService: IMenuService,
@IContextKeyService contextKeyService: IContextKeyService,
@@ -198,12 +205,13 @@ export class AccountsActivityActionViewItem extends MenuActivityActionViewItem {
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
@IProductService private readonly productService: IProductService,
@IConfigurationService configurationService: IConfigurationService,
@IStorageService private readonly storageService: IStorageService
@IStorageService private readonly storageService: IStorageService,
@IKeybindingService keybindingService: IKeybindingService,
) {
super(MenuId.AccountsContext, action, contextMenuActionsProvider, colors, themeService, menuService, contextMenuService, contextKeyService, configurationService, environmentService);
super(MenuId.AccountsContext, action, contextMenuActionsProvider, colors, activityHoverOptions, themeService, hoverService, menuService, contextMenuService, contextKeyService, configurationService, environmentService, keybindingService);
}
protected async resolveMainMenuActions(accountsMenu: IMenu, disposables: DisposableStore): Promise<IAction[]> {
protected override async resolveMainMenuActions(accountsMenu: IMenu, disposables: DisposableStore): Promise<IAction[]> {
await super.resolveMainMenuActions(accountsMenu, disposables);
const otherCommands = accountsMenu.getActions();
@@ -279,7 +287,7 @@ export class AccountsActivityActionViewItem extends MenuActivityActionViewItem {
return menus;
}
protected async resolveContextMenuActions(disposables: DisposableStore): Promise<IAction[]> {
protected override async resolveContextMenuActions(disposables: DisposableStore): Promise<IAction[]> {
const actions = await super.resolveContextMenuActions(disposables);
actions.unshift(...[
@@ -297,14 +305,17 @@ export class GlobalActivityActionViewItem extends MenuActivityActionViewItem {
action: ActivityAction,
contextMenuActionsProvider: () => IAction[],
colors: (theme: IColorTheme) => ICompositeBarColors,
activityHoverOptions: IActivityHoverOptions,
@IThemeService themeService: IThemeService,
@IHoverService hoverService: IHoverService,
@IMenuService menuService: IMenuService,
@IContextMenuService contextMenuService: IContextMenuService,
@IContextKeyService contextKeyService: IContextKeyService,
@IConfigurationService configurationService: IConfigurationService,
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
@IKeybindingService keybindingService: IKeybindingService,
) {
super(MenuId.GlobalActivity, action, contextMenuActionsProvider, colors, themeService, menuService, contextMenuService, contextKeyService, configurationService, environmentService);
super(MenuId.GlobalActivity, action, contextMenuActionsProvider, colors, activityHoverOptions, themeService, hoverService, menuService, contextMenuService, contextKeyService, configurationService, environmentService, keybindingService);
}
}

View File

@@ -10,7 +10,7 @@ import { GLOBAL_ACTIVITY_ID, IActivity, ACCOUNTS_ACTIVITY_ID } from 'vs/workbenc
import { Part } from 'vs/workbench/browser/part';
import { GlobalActivityActionViewItem, ViewContainerActivityAction, PlaceHolderToggleCompositePinnedAction, PlaceHolderViewContainerActivityAction, AccountsActivityActionViewItem } from 'vs/workbench/browser/parts/activitybar/activitybarActions';
import { IBadge, NumberBadge } from 'vs/workbench/services/activity/common/activity';
import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService';
import { IWorkbenchLayoutService, Parts, Position } from 'vs/workbench/services/layout/browser/layoutService';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { IDisposable, toDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle';
import { ToggleActivityBarVisibilityAction, ToggleSidebarPositionAction } from 'vs/workbench/browser/actions/layoutActions';
@@ -22,7 +22,7 @@ import { Dimension, createCSSRule, asCSSUrl, addDisposableListener, EventType }
import { IStorageService, StorageScope, IStorageValueChangeEvent, StorageTarget } from 'vs/platform/storage/common/storage';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { URI, UriComponents } from 'vs/base/common/uri';
import { ToggleCompositePinnedAction, ICompositeBarColors, ActivityAction, ICompositeActivity } from 'vs/workbench/browser/parts/compositeBarActions';
import { ToggleCompositePinnedAction, ICompositeBarColors, ActivityAction, ICompositeActivity, IActivityHoverOptions } from 'vs/workbench/browser/parts/compositeBarActions';
import { IViewDescriptorService, ViewContainer, IViewContainerModel, ViewContainerLocation, IViewsService, getEnabledViewContainerContextKey } from 'vs/workbench/common/views';
import { IContextKeyService, ContextKeyExpr, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { assertIsDefined, isString } from 'vs/base/common/types';
@@ -43,6 +43,7 @@ import { Action2, registerAction2 } from 'vs/platform/actions/common/actions';
import { CATEGORIES } from 'vs/workbench/common/actions';
import { registerIcon } from 'vs/platform/theme/common/iconRegistry';
import { StringSHA1 } from 'vs/base/common/hash';
import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget';
interface IPlaceholderViewContainer {
readonly id: string;
@@ -156,6 +157,7 @@ export class ActivitybarPart extends Part implements IActivityBarService {
return this._register(this.instantiationService.createInstance(CompositeBar, cachedItems, {
icon: true,
orientation: ActionsOrientation.VERTICAL,
activityHoverOptions: this.getActivityHoverOptions(),
preventLoopNavigation: true,
openComposite: compositeId => this.viewsService.openViewContainer(compositeId, true),
getActivityAction: compositeId => this.getCompositeActions(compositeId).activityAction,
@@ -218,6 +220,13 @@ export class ActivitybarPart extends Part implements IActivityBarService {
}));
}
private getActivityHoverOptions(): IActivityHoverOptions {
return {
position: () => this.layoutService.getSideBarPosition() === Position.LEFT ? HoverPosition.RIGHT : HoverPosition.LEFT,
delay: () => 0
};
}
private getContextMenuActionsForComposite(compositeId: string): IAction[] {
const actions: IAction[] = [];
@@ -269,7 +278,7 @@ export class ActivitybarPart extends Part implements IActivityBarService {
}));
}
private onDidChangeViewContainers(added: ReadonlyArray<{ container: ViewContainer, location: ViewContainerLocation; }>, removed: ReadonlyArray<{ container: ViewContainer, location: ViewContainerLocation; }>) {
private onDidChangeViewContainers(added: readonly { container: ViewContainer, location: ViewContainerLocation; }[], removed: readonly { container: ViewContainer, location: ViewContainerLocation; }[]) {
removed.filter(({ location }) => location === ViewContainerLocation.Sidebar).forEach(({ container }) => this.onDidDeregisterViewContainer(container));
this.onDidRegisterViewContainers(added.filter(({ location }) => location === ViewContainerLocation.Sidebar).map(({ container }) => container));
}
@@ -447,7 +456,7 @@ export class ActivitybarPart extends Part implements IActivityBarService {
this.registerKeyboardNavigationListeners();
}
createContentArea(parent: HTMLElement): HTMLElement {
override createContentArea(parent: HTMLElement): HTMLElement {
this.element = parent;
this.content = document.createElement('div');
@@ -522,11 +531,11 @@ export class ActivitybarPart extends Part implements IActivityBarService {
this.globalActivityActionBar = this._register(new ActionBar(container, {
actionViewItemProvider: action => {
if (action.id === 'workbench.actions.manage') {
return this.instantiationService.createInstance(GlobalActivityActionViewItem, action as ActivityAction, () => this.compositeBar.getContextMenuActions(), (theme: IColorTheme) => this.getActivitybarItemColors(theme));
return this.instantiationService.createInstance(GlobalActivityActionViewItem, action as ActivityAction, () => this.compositeBar.getContextMenuActions(), (theme: IColorTheme) => this.getActivitybarItemColors(theme), this.getActivityHoverOptions());
}
if (action.id === 'workbench.actions.accounts') {
return this.instantiationService.createInstance(AccountsActivityActionViewItem, action as ActivityAction, () => this.compositeBar.getContextMenuActions(), (theme: IColorTheme) => this.getActivitybarItemColors(theme));
return this.instantiationService.createInstance(AccountsActivityActionViewItem, action as ActivityAction, () => this.compositeBar.getContextMenuActions(), (theme: IColorTheme) => this.getActivitybarItemColors(theme), this.getActivityHoverOptions());
}
throw new Error(`No view item for action '${action.id}'`);
@@ -598,7 +607,7 @@ export class ActivitybarPart extends Part implements IActivityBarService {
return compositeActions;
}
private onDidRegisterViewContainers(viewContainers: ReadonlyArray<ViewContainer>): void {
private onDidRegisterViewContainers(viewContainers: readonly ViewContainer[]): void {
for (const viewContainer of viewContainers) {
this.addComposite(viewContainer);
@@ -765,7 +774,7 @@ export class ActivitybarPart extends Part implements IActivityBarService {
this.compositeBar.focus();
}
updateStyles(): void {
override updateStyles(): void {
super.updateStyles();
const container = assertIsDefined(this.getContainer());
@@ -790,7 +799,7 @@ export class ActivitybarPart extends Part implements IActivityBarService {
};
}
layout(width: number, height: number): void {
override layout(width: number, height: number): void {
if (!this.layoutService.isVisible(Parts.ACTIVITYBAR_PART)) {
return;
}
@@ -814,7 +823,7 @@ export class ActivitybarPart extends Part implements IActivityBarService {
return viewContainer && this.viewDescriptorService.getViewContainerLocation(viewContainer) === this.location ? viewContainer : undefined;
}
private getViewContainers(): ReadonlyArray<ViewContainer> {
private getViewContainers(): readonly ViewContainer[] {
return this.viewDescriptorService.getViewContainersByLocation(this.location);
}

View File

@@ -57,6 +57,7 @@
z-index: 1;
display: flex;
overflow: hidden;
width: 48px;
height: 48px;
margin-right: 0;
box-sizing: border-box;
@@ -154,7 +155,8 @@
.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .badge .codicon.badge-content {
font-size: 12px;
font-weight: unset;
padding: 0 2px;
padding: 0;
justify-content: center;
}
.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .badge .codicon.badge-content::before {

View File

@@ -10,7 +10,7 @@ import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { IBadge } from 'vs/workbench/services/activity/common/activity';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ActionBar, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
import { CompositeActionViewItem, CompositeOverflowActivityAction, ICompositeActivity, CompositeOverflowActivityActionViewItem, ActivityAction, ICompositeBar, ICompositeBarColors } from 'vs/workbench/browser/parts/compositeBarActions';
import { CompositeActionViewItem, CompositeOverflowActivityAction, ICompositeActivity, CompositeOverflowActivityActionViewItem, ActivityAction, ICompositeBar, ICompositeBarColors, IActivityHoverOptions } from 'vs/workbench/browser/parts/compositeBarActions';
import { Dimension, $, addDisposableListener, EventType, EventHelper, isAncestor } from 'vs/base/browser/dom';
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
@@ -145,6 +145,7 @@ export interface ICompositeBarOptions {
readonly compositeSize: number;
readonly overflowActionSize: number;
readonly dndHandler: ICompositeDragAndDrop;
readonly activityHoverOptions: IActivityHoverOptions;
readonly preventLoopNavigation?: boolean;
getActivityAction: (compositeId: string) => ActivityAction;
@@ -213,11 +214,12 @@ export class CompositeBar extends Widget implements ICompositeBar {
}
const item = this.model.findItem(action.id);
return item && this.instantiationService.createInstance(
CompositeActionViewItem, action as ActivityAction, item.pinnedAction,
CompositeActionViewItem,
{ draggable: true, colors: this.options.colors, icon: this.options.icon, hoverOptions: this.options.activityHoverOptions },
action as ActivityAction,
item.pinnedAction,
compositeId => this.options.getContextMenuActionsForComposite(compositeId),
() => this.getContextMenuActions(),
this.options.colors,
this.options.icon,
this.options.dndHandler,
this
);
@@ -595,7 +597,8 @@ export class CompositeBar extends Widget implements ICompositeBar {
return item?.activity[0]?.badge;
},
this.options.getOnCompositeClickAction,
this.options.colors
this.options.colors,
this.options.activityHoverOptions
);
compositeSwitcherBar.push(this.compositeOverflowAction, { label: false, icon: true });

View File

@@ -7,7 +7,7 @@ import { localize } from 'vs/nls';
import { Action, IAction, Separator } from 'vs/base/common/actions';
import { $, addDisposableListener, append, clearNode, EventHelper, EventType, getDomNodePagePosition, hide, show } from 'vs/base/browser/dom';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { dispose, toDisposable, MutableDisposable } from 'vs/base/common/lifecycle';
import { dispose, toDisposable, MutableDisposable, IDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IThemeService, IColorTheme, ThemeIcon } from 'vs/platform/theme/common/themeService';
import { TextBadge, NumberBadge, IBadge, IconBadge, ProgressBadge } from 'vs/workbench/services/activity/common/activity';
@@ -21,6 +21,11 @@ import { CompositeDragAndDropObserver, ICompositeDragAndDrop, Before2D, toggleDr
import { Color } from 'vs/base/common/color';
import { IBaseActionViewItemOptions, BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems';
import { Codicon } from 'vs/base/common/codicons';
import { IHoverService } from 'vs/workbench/services/hover/browser/hover';
import { domEvent } from 'vs/base/browser/event';
import { RunOnceScheduler } from 'vs/base/common/async';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget';
export interface ICompositeActivity {
badge: IBadge;
@@ -101,7 +106,7 @@ export class ActivityAction extends Action {
this._onDidChangeBadge.fire(this);
}
dispose(): void {
override dispose(): void {
this._onDidChangeActivity.dispose();
this._onDidChangeBadge.dispose();
@@ -122,9 +127,15 @@ export interface ICompositeBarColors {
dragAndDropBorder?: Color;
}
export interface IActivityHoverOptions {
position: () => HoverPosition;
delay: () => number;
}
export interface IActivityActionViewItemOptions extends IBaseActionViewItemOptions {
icon?: boolean;
colors: (theme: IColorTheme) => ICompositeBarColors;
hoverOptions: IActivityHoverOptions;
hasPopup?: boolean;
}
@@ -132,22 +143,35 @@ export class ActivityActionViewItem extends BaseActionViewItem {
protected container!: HTMLElement;
protected label!: HTMLElement;
protected badge!: HTMLElement;
protected options!: IActivityActionViewItemOptions;
protected override readonly options: IActivityActionViewItemOptions;
private badgeContent: HTMLElement | undefined;
private readonly badgeDisposable = this._register(new MutableDisposable());
private mouseUpTimeout: any;
private keybindingLabel: string | undefined | null;
private readonly hoverDisposables = this._register(new DisposableStore());
private readonly hover = this._register(new MutableDisposable<IDisposable>());
private readonly showHoverScheduler = new RunOnceScheduler(() => this.showHover(), 0);
constructor(
action: ActivityAction,
options: IActivityActionViewItemOptions,
@IThemeService protected readonly themeService: IThemeService
@IThemeService protected readonly themeService: IThemeService,
@IHoverService private readonly hoverService: IHoverService,
@IConfigurationService protected readonly configurationService: IConfigurationService,
@IKeybindingService protected readonly keybindingService: IKeybindingService,
) {
super(null, action, options);
this.options = options;
this._register(this.themeService.onDidColorThemeChange(this.onThemeChange, this));
this._register(action.onDidChangeActivity(this.updateActivity, this));
this._register(Event.filter(keybindingService.onDidUpdateKeybindings, () => this.keybindingLabel !== this.computeKeybindingLabel())(() => this.updateTitle()));
this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('workbench.experimental.useCustomHover'))(() => this.updateHover()));
this._register(action.onDidChangeBadge(this.updateBadge, this));
this._register(toDisposable(() => this.showHoverScheduler.cancel()));
}
protected get activity(): IActivity {
@@ -195,10 +219,13 @@ export class ActivityActionViewItem extends BaseActionViewItem {
}
}
render(container: HTMLElement): void {
override render(container: HTMLElement): void {
super.render(container);
this.container = container;
if (this.options.icon) {
this.container.classList.add('icon');
}
if (this.options.hasPopup) {
this.container.setAttribute('role', 'button');
@@ -239,6 +266,7 @@ export class ActivityActionViewItem extends BaseActionViewItem {
this.updateActivity();
this.updateStyles();
this.updateHover();
}
private onThemeChange(theme: IColorTheme): void {
@@ -247,7 +275,7 @@ export class ActivityActionViewItem extends BaseActionViewItem {
protected updateActivity(): void {
this.updateLabel();
this.updateTitle(this.activity.name);
this.updateTitle();
this.updateBadge();
this.updateStyles();
}
@@ -311,22 +339,10 @@ export class ActivityActionViewItem extends BaseActionViewItem {
}
}
// Title
let title: string;
if (badge?.getDescription()) {
if (this.activity.name) {
title = localize('badgeTitle', "{0} - {1}", this.activity.name, badge.getDescription());
} else {
title = badge.getDescription();
}
} else {
title = this.activity.name;
}
this.updateTitle(title);
this.updateTitle();
}
protected updateLabel(): void {
protected override updateLabel(): void {
this.label.className = 'action-label';
if (this.activity.cssClass) {
@@ -343,16 +359,77 @@ export class ActivityActionViewItem extends BaseActionViewItem {
}
}
private updateTitle(title: string): void {
private updateTitle(): void {
// Title
const title = this.computeTitle();
[this.label, this.badge, this.container].forEach(element => {
if (element) {
element.setAttribute('aria-label', title);
element.title = title;
if (this.useCustomHover) {
element.setAttribute('title', '');
element.removeAttribute('title');
} else {
element.setAttribute('title', title);
}
}
});
}
dispose(): void {
private computeTitle(): string {
this.keybindingLabel = this.computeKeybindingLabel();
let title = this.keybindingLabel ? localize('titleKeybinding', "{0} ({1})", this.activity.name, this.keybindingLabel) : this.activity.name;
const badge = (this.getAction() as ActivityAction).getBadge();
if (badge?.getDescription()) {
title = localize('badgeTitle', "{0} - {1}", title, badge.getDescription());
}
return title;
}
private computeKeybindingLabel(): string | undefined | null {
const keybinding = this.activity.keybindingId ? this.keybindingService.lookupKeybinding(this.activity.keybindingId) : null;
return keybinding?.getLabel();
}
private updateHover(): void {
this.hoverDisposables.clear();
this.updateTitle();
if (this.useCustomHover) {
this.hoverDisposables.add(domEvent(this.container, EventType.MOUSE_OVER, true)(() => {
if (!this.showHoverScheduler.isScheduled()) {
this.showHoverScheduler.schedule(this.options.hoverOptions!.delay() || 150);
}
}));
this.hoverDisposables.add(domEvent(this.container, EventType.MOUSE_LEAVE, true)(() => {
this.hover.value = undefined;
this.showHoverScheduler.cancel();
}));
this.hoverDisposables.add(toDisposable(() => {
this.hover.value = undefined;
this.showHoverScheduler.cancel();
}));
}
}
private showHover(): void {
if (this.hover.value) {
return;
}
const hoverPosition = this.options.hoverOptions!.position();
this.hover.value = this.hoverService.showHover({
target: this.container,
hoverPosition,
text: this.computeTitle(),
showPointer: true,
compact: true
});
}
private get useCustomHover(): boolean {
return !!this.configurationService.getValue<boolean>('workbench.experimental.useCustomHover');
}
override dispose(): void {
super.dispose();
if (this.mouseUpTimeout) {
@@ -375,7 +452,7 @@ export class CompositeOverflowActivityAction extends ActivityAction {
});
}
async run(): Promise<void> {
override async run(): Promise<void> {
this.showMenu();
}
}
@@ -390,10 +467,14 @@ export class CompositeOverflowActivityActionViewItem extends ActivityActionViewI
private getBadge: (compositeId: string) => IBadge,
private getCompositeOpenAction: (compositeId: string) => IAction,
colors: (theme: IColorTheme) => ICompositeBarColors,
hoverOptions: IActivityHoverOptions,
@IContextMenuService private readonly contextMenuService: IContextMenuService,
@IThemeService themeService: IThemeService
@IThemeService themeService: IThemeService,
@IHoverService hoverService: IHoverService,
@IConfigurationService configurationService: IConfigurationService,
@IKeybindingService keybindingService: IKeybindingService,
) {
super(action, { icon: true, colors, hasPopup: true }, themeService);
super(action, { icon: true, colors, hasPopup: true, hoverOptions }, themeService, hoverService, configurationService, keybindingService);
}
showMenu(): void {
@@ -434,7 +515,7 @@ export class CompositeOverflowActivityActionViewItem extends ActivityActionViewI
});
}
dispose(): void {
override dispose(): void {
super.dispose();
if (this.actions) {
@@ -451,7 +532,7 @@ class ManageExtensionAction extends Action {
super('activitybar.manage.extension', localize('manageExtension', "Manage Extension"));
}
run(id: string): Promise<void> {
override run(id: string): Promise<void> {
return this.commandService.executeCommand('_extensions.manage', id);
}
}
@@ -460,64 +541,29 @@ export class CompositeActionViewItem extends ActivityActionViewItem {
private static manageExtensionAction: ManageExtensionAction;
private compositeActivity: IActivity | undefined;
constructor(
private compositeActivityAction: ActivityAction,
private toggleCompositePinnedAction: IAction,
private compositeContextMenuActionsProvider: (compositeId: string) => IAction[],
private contextMenuActionsProvider: () => IAction[],
colors: (theme: IColorTheme) => ICompositeBarColors,
icon: boolean,
private dndHandler: ICompositeDragAndDrop,
private compositeBar: ICompositeBar,
options: IActivityActionViewItemOptions,
private readonly compositeActivityAction: ActivityAction,
private readonly toggleCompositePinnedAction: IAction,
private readonly compositeContextMenuActionsProvider: (compositeId: string) => IAction[],
private readonly contextMenuActionsProvider: () => IAction[],
private readonly dndHandler: ICompositeDragAndDrop,
private readonly compositeBar: ICompositeBar,
@IContextMenuService private readonly contextMenuService: IContextMenuService,
@IKeybindingService private readonly keybindingService: IKeybindingService,
@IKeybindingService keybindingService: IKeybindingService,
@IInstantiationService instantiationService: IInstantiationService,
@IThemeService themeService: IThemeService
@IThemeService themeService: IThemeService,
@IHoverService hoverService: IHoverService,
@IConfigurationService configurationService: IConfigurationService,
) {
super(compositeActivityAction, { draggable: true, colors, icon }, themeService);
super(compositeActivityAction, options, themeService, hoverService, configurationService, keybindingService);
if (!CompositeActionViewItem.manageExtensionAction) {
CompositeActionViewItem.manageExtensionAction = instantiationService.createInstance(ManageExtensionAction);
}
this._register(compositeActivityAction.onDidChangeActivity(() => {
this.compositeActivity = undefined;
this.updateActivity();
}, this));
this._register(Event.any(
compositeActivityAction.onDidChangeActivity,
Event.filter(keybindingService.onDidUpdateKeybindings, () => this.compositeActivity!.name !== this.getActivtyName())
)(() => {
if (this.compositeActivity && this.compositeActivity.name !== this.getActivtyName()) {
this.compositeActivity = undefined;
this.updateActivity();
}
}));
}
protected get activity(): IActivity {
if (!this.compositeActivity) {
this.compositeActivity = {
...this.compositeActivityAction.activity,
... { name: this.getActivtyName() }
};
}
return this.compositeActivity;
}
private getActivtyName(skipKeybinding = false): string {
let name = this.compositeActivityAction.activity.name;
if (skipKeybinding) {
return name;
}
const keybinding = this.compositeActivityAction.activity.keybindingId ? this.keybindingService.lookupKeybinding(this.compositeActivityAction.activity.keybindingId) : null;
return keybinding ? localize('titleKeybinding', "{0} ({1})", name, keybinding.getLabel()) : name;
}
render(container: HTMLElement): void {
override render(container: HTMLElement): void {
super.render(container);
this.updateChecked();
@@ -627,10 +673,10 @@ export class CompositeActionViewItem extends ActivityActionViewItem {
const isPinned = this.compositeBar.isPinned(this.activity.id);
if (isPinned) {
this.toggleCompositePinnedAction.label = localize('hide', "Hide '{0}'", this.getActivtyName(true));
this.toggleCompositePinnedAction.label = localize('hide', "Hide '{0}'", this.activity.name);
this.toggleCompositePinnedAction.checked = false;
} else {
this.toggleCompositePinnedAction.label = localize('keep', "Keep '{0}'", this.getActivtyName(true));
this.toggleCompositePinnedAction.label = localize('keep', "Keep '{0}'", this.activity.name);
}
const otherActions = this.contextMenuActionsProvider();
@@ -652,7 +698,7 @@ export class CompositeActionViewItem extends ActivityActionViewItem {
});
}
protected updateChecked(): void {
protected override updateChecked(): void {
if (this.getAction().checked) {
this.container.classList.add('checked');
this.container.setAttribute('aria-label', this.container.title);
@@ -667,7 +713,7 @@ export class CompositeActionViewItem extends ActivityActionViewItem {
this.updateStyles();
}
protected updateEnabled(): void {
protected override updateEnabled(): void {
if (!this.element) {
return;
}
@@ -679,7 +725,7 @@ export class CompositeActionViewItem extends ActivityActionViewItem {
}
}
dispose(): void {
override dispose(): void {
super.dispose();
this.label.remove();
}
@@ -696,7 +742,7 @@ export class ToggleCompositePinnedAction extends Action {
this.checked = !!this.activity && this.compositeBar.isPinned(this.activity.id);
}
async run(context: string): Promise<void> {
override async run(context: string): Promise<void> {
const id = this.activity ? this.activity.id : context;
if (this.compositeBar.isPinned(id)) {

View File

@@ -75,7 +75,7 @@ export abstract class CompositePart<T extends Composite> extends Part {
protected readonly storageService: IStorageService,
private readonly telemetryService: ITelemetryService,
protected readonly contextMenuService: IContextMenuService,
protected readonly layoutService: IWorkbenchLayoutService,
layoutService: IWorkbenchLayoutService,
protected readonly keybindingService: IKeybindingService,
protected readonly instantiationService: IInstantiationService,
themeService: IThemeService,
@@ -263,9 +263,7 @@ export abstract class CompositePart<T extends Composite> extends Part {
}
// Log in telemetry
if (this.telemetryService) {
this.telemetryService.publicLog2<WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification>('workbenchActionExecuted', { id: e.action.id, from: this.nameForTelemetry });
}
this.telemetryService.publicLog2<WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification>('workbenchActionExecuted', { id: e.action.id, from: this.nameForTelemetry });
});
// Indicate to composite that it is now visible
@@ -376,7 +374,7 @@ export abstract class CompositePart<T extends Composite> extends Part {
return composite;
}
createTitleArea(parent: HTMLElement): HTMLElement {
override createTitleArea(parent: HTMLElement): HTMLElement {
// Title Area Container
const titleArea = append(parent, $('.composite'));
@@ -423,7 +421,7 @@ export abstract class CompositePart<T extends Composite> extends Part {
};
}
updateStyles(): void {
override updateStyles(): void {
super.updateStyles();
// Forward to title label
@@ -451,7 +449,7 @@ export abstract class CompositePart<T extends Composite> extends Part {
return null;
}
createContentArea(parent: HTMLElement): HTMLElement {
override createContentArea(parent: HTMLElement): HTMLElement {
const contentContainer = append(parent, $('.content'));
this.progressBar = this._register(new ProgressBar(contentContainer));
@@ -471,7 +469,7 @@ export abstract class CompositePart<T extends Composite> extends Part {
return AnchorAlignment.RIGHT;
}
layout(width: number, height: number): void {
override layout(width: number, height: number): void {
super.layout(width, height);
// Layout contents
@@ -500,7 +498,7 @@ export abstract class CompositePart<T extends Composite> extends Part {
return true;
}
dispose(): void {
override dispose(): void {
this.mapCompositeToCompositeContainer.clear();
this.mapActionsBindingToComposite.clear();

View File

@@ -17,6 +17,7 @@ import { BrowserDialogHandler } from 'vs/workbench/browser/parts/dialogs/dialogH
import { DialogService } from 'vs/workbench/services/dialogs/common/dialogService';
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { Disposable } from 'vs/base/common/lifecycle';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
export class DialogHandlerContribution extends Disposable implements IWorkbenchContribution {
private readonly model: IDialogsModel;
@@ -30,12 +31,13 @@ export class DialogHandlerContribution extends Disposable implements IWorkbenchC
@ILayoutService layoutService: ILayoutService,
@IThemeService themeService: IThemeService,
@IKeybindingService keybindingService: IKeybindingService,
@IInstantiationService instantiationService: IInstantiationService,
@IProductService productService: IProductService,
@IClipboardService clipboardService: IClipboardService
) {
super();
this.impl = new BrowserDialogHandler(logService, layoutService, themeService, keybindingService, productService, clipboardService);
this.impl = new BrowserDialogHandler(logService, layoutService, themeService, keybindingService, instantiationService, productService, clipboardService);
this.model = (this.dialogService as DialogService).model;

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls';
import { IDialogOptions, IConfirmation, IConfirmationResult, DialogType, IShowResult, IInputResult, ICheckbox, IInput, IDialogHandler } from 'vs/platform/dialogs/common/dialogs';
import { IDialogOptions, IConfirmation, IConfirmationResult, DialogType, IShowResult, IInputResult, ICheckbox, IInput, IDialogHandler, ICustomDialogOptions } from 'vs/platform/dialogs/common/dialogs';
import { ILayoutService } from 'vs/platform/layout/browser/layoutService';
import { ILogService } from 'vs/platform/log/common/log';
import Severity from 'vs/base/common/severity';
@@ -18,6 +18,8 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IProductService } from 'vs/platform/product/common/productService';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { fromNow } from 'vs/base/common/date';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { MarkdownRenderer } from 'vs/editor/browser/core/markdownRenderer';
export class BrowserDialogHandler implements IDialogHandler {
@@ -30,14 +32,19 @@ export class BrowserDialogHandler implements IDialogHandler {
'editor.action.clipboardPasteAction'
];
private readonly markdownRenderer: MarkdownRenderer;
constructor(
@ILogService private readonly logService: ILogService,
@ILayoutService private readonly layoutService: ILayoutService,
@IThemeService private readonly themeService: IThemeService,
@IKeybindingService private readonly keybindingService: IKeybindingService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IProductService private readonly productService: IProductService,
@IClipboardService private readonly clipboardService: IClipboardService
) { }
) {
this.markdownRenderer = this.instantiationService.createInstance(MarkdownRenderer, {});
}
async confirm(confirmation: IConfirmation): Promise<IConfirmationResult> {
this.logService.trace('DialogService#confirm', confirmation.message);
@@ -67,7 +74,7 @@ export class BrowserDialogHandler implements IDialogHandler {
async show(severity: Severity, message: string, buttons: string[], options?: IDialogOptions): Promise<IShowResult> {
this.logService.trace('DialogService#show', message);
const result = await this.doShow(this.getDialogType(severity), message, buttons, options?.detail, options?.cancelId, options?.checkbox);
const result = await this.doShow(this.getDialogType(severity), message, buttons, options?.detail, options?.cancelId, options?.checkbox, undefined, typeof options?.custom === 'object' ? options.custom : undefined);
return {
choice: result.button,
@@ -75,8 +82,19 @@ export class BrowserDialogHandler implements IDialogHandler {
};
}
private async doShow(type: 'none' | 'info' | 'error' | 'question' | 'warning' | 'pending' | undefined, message: string, buttons: string[], detail?: string, cancelId?: number, checkbox?: ICheckbox, inputs?: IInput[]): Promise<IDialogResult> {
private async doShow(type: 'none' | 'info' | 'error' | 'question' | 'warning' | 'pending' | undefined, message: string, buttons: string[], detail?: string, cancelId?: number, checkbox?: ICheckbox, inputs?: IInput[], customOptions?: ICustomDialogOptions): Promise<IDialogResult> {
const dialogDisposables = new DisposableStore();
const renderBody = customOptions ? (parent: HTMLElement) => {
parent.classList.add(...(customOptions.classes || []));
(customOptions.markdownDetails || []).forEach(markdownDetail => {
const result = this.markdownRenderer.render(markdownDetail.markdown);
parent.appendChild(result.element);
result.element.classList.add(...(markdownDetail.classes || []));
dialogDisposables.add(result);
});
} : undefined;
const dialog = new Dialog(
this.layoutService.container,
message,
@@ -93,6 +111,10 @@ export class BrowserDialogHandler implements IDialogHandler {
}
}
},
renderBody,
icon: customOptions?.icon,
disableCloseAction: customOptions?.disableCloseAction,
buttonDetails: customOptions?.buttonDetails,
checkboxLabel: checkbox?.label,
checkboxChecked: checkbox?.checked,
inputs

View File

@@ -17,7 +17,7 @@ import { IStorageService } from 'vs/platform/storage/common/storage';
*/
export class BinaryResourceDiffEditor extends SideBySideEditor {
static readonly ID = BINARY_DIFF_EDITOR_ID;
static override readonly ID = BINARY_DIFF_EDITOR_ID;
constructor(
@ITelemetryService telemetryService: ITelemetryService,

View File

@@ -13,18 +13,15 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { URI } from 'vs/base/common/uri';
import { Dimension, size, clearNode, append, addDisposableListener, EventType, $ } from 'vs/base/browser/dom';
import { CancellationToken } from 'vs/base/common/cancellation';
import { dispose, IDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { DisposableStore, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { assertIsDefined, assertAllDefined } from 'vs/base/common/types';
import { ByteSize } from 'vs/platform/files/common/files';
export interface IOpenCallbacks {
openInternal: (input: EditorInput, options: EditorOptions | undefined) => Promise<void>;
openExternal: (uri: URI) => void;
}
/*
@@ -42,22 +39,21 @@ export abstract class BaseBinaryResourceEditor extends EditorPane {
private metadata: string | undefined;
private binaryContainer: HTMLElement | undefined;
private scrollbar: DomScrollableElement | undefined;
private resourceViewerContext: ResourceViewerContext | undefined;
private inputDisposable = this._register(new MutableDisposable());
constructor(
id: string,
callbacks: IOpenCallbacks,
telemetryService: ITelemetryService,
themeService: IThemeService,
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
@IStorageService storageService: IStorageService,
@IStorageService storageService: IStorageService
) {
super(id, telemetryService, themeService, storageService);
this.callbacks = callbacks;
}
getTitle(): string {
override getTitle(): string {
return this.input ? this.input.getName() : localize('binaryEditor', "Binary Viewer");
}
@@ -65,7 +61,7 @@ export abstract class BaseBinaryResourceEditor extends EditorPane {
// Container for Binary
this.binaryContainer = document.createElement('div');
this.binaryContainer.className = 'binary-container';
this.binaryContainer.className = 'monaco-binary-resource-editor';
this.binaryContainer.style.outline = 'none';
this.binaryContainer.tabIndex = 0; // enable focus support from the editor part (do not remove)
@@ -74,7 +70,7 @@ export abstract class BaseBinaryResourceEditor extends EditorPane {
parent.appendChild(this.scrollbar.getDomNode());
}
async setInput(input: EditorInput, options: EditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise<void> {
override async setInput(input: EditorInput, options: EditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise<void> {
await super.setInput(input, options, context, token);
const model = await input.resolve();
@@ -89,23 +85,38 @@ export abstract class BaseBinaryResourceEditor extends EditorPane {
}
// Render Input
if (this.resourceViewerContext) {
this.resourceViewerContext.dispose();
}
const [binaryContainer, scrollbar] = assertAllDefined(this.binaryContainer, this.scrollbar);
this.resourceViewerContext = ResourceViewer.show({ name: model.getName(), resource: model.resource, size: model.getSize(), etag: model.getETag(), mime: model.getMime() }, binaryContainer, scrollbar, {
openInternalClb: () => this.handleOpenInternalCallback(input, options),
openExternalClb: this.environmentService.remoteAuthority ? undefined : resource => this.callbacks.openExternal(resource),
metadataClb: meta => this.handleMetadataChanged(meta)
});
this.inputDisposable.value = this.renderInput(input, options, model);
}
private async handleOpenInternalCallback(input: EditorInput, options: EditorOptions | undefined): Promise<void> {
await this.callbacks.openInternal(input, options);
private renderInput(input: EditorInput, options: EditorOptions | undefined, model: BinaryEditorModel): IDisposable {
const [binaryContainer, scrollbar] = assertAllDefined(this.binaryContainer, this.scrollbar);
// Signal to listeners that the binary editor has been opened in-place
this._onDidOpenInPlace.fire();
clearNode(binaryContainer);
const disposables = new DisposableStore();
const label = document.createElement('p');
label.textContent = localize('nativeBinaryError', "The file is not displayed in the editor because it is either binary or uses an unsupported text encoding.");
binaryContainer.appendChild(label);
const link = append(label, $('a.embedded-link'));
link.setAttribute('role', 'button');
link.textContent = localize('openAsText', "Do you want to open it anyway?");
disposables.add(addDisposableListener(link, EventType.CLICK, async () => {
await this.callbacks.openInternal(input, options);
// Signal to listeners that the binary editor has been opened in-place
this._onDidOpenInPlace.fire();
}));
scrollbar.scanDomNode();
// Update metadata
const size = model.getSize();
this.handleMetadataChanged(typeof size === 'number' ? ByteSize.formatSize(size) : '');
return disposables;
}
private handleMetadataChanged(meta: string | undefined): void {
@@ -118,7 +129,7 @@ export abstract class BaseBinaryResourceEditor extends EditorPane {
return this.metadata;
}
clearInput(): void {
override clearInput(): void {
// Clear Meta
this.handleMetadataChanged(undefined);
@@ -127,8 +138,7 @@ export abstract class BaseBinaryResourceEditor extends EditorPane {
if (this.binaryContainer) {
clearNode(this.binaryContainer);
}
dispose(this.resourceViewerContext);
this.resourceViewerContext = undefined;
this.inputDisposable.clear();
super.clearInput();
}
@@ -139,118 +149,17 @@ export abstract class BaseBinaryResourceEditor extends EditorPane {
const [binaryContainer, scrollbar] = assertAllDefined(this.binaryContainer, this.scrollbar);
size(binaryContainer, dimension.width, dimension.height);
scrollbar.scanDomNode();
if (typeof this.resourceViewerContext?.layout === 'function') {
this.resourceViewerContext.layout(dimension);
}
}
focus(): void {
override focus(): void {
const binaryContainer = assertIsDefined(this.binaryContainer);
binaryContainer.focus();
}
dispose(): void {
if (this.binaryContainer) {
this.binaryContainer.remove();
}
dispose(this.resourceViewerContext);
this.resourceViewerContext = undefined;
override dispose(): void {
this.binaryContainer?.remove();
super.dispose();
}
}
export interface IResourceDescriptor {
readonly resource: URI;
readonly name: string;
readonly size?: number;
readonly etag?: string;
readonly mime: string;
}
interface ResourceViewerContext extends IDisposable {
layout?(dimension: Dimension): void;
}
interface ResourceViewerDelegate {
openInternalClb(uri: URI): void;
openExternalClb?(uri: URI): void;
metadataClb(meta: string): void;
}
class ResourceViewer {
private static readonly MAX_OPEN_INTERNAL_SIZE = ByteSize.MB * 200; // max size until we offer an action to open internally
static show(
descriptor: IResourceDescriptor,
container: HTMLElement,
scrollbar: DomScrollableElement,
delegate: ResourceViewerDelegate,
): ResourceViewerContext {
// Ensure CSS class
container.className = 'monaco-binary-resource-editor';
// Large Files
if (typeof descriptor.size === 'number' && descriptor.size > ResourceViewer.MAX_OPEN_INTERNAL_SIZE) {
return FileTooLargeFileView.create(container, descriptor.size, scrollbar, delegate);
}
// Seemingly Binary Files
return FileSeemsBinaryFileView.create(container, descriptor, scrollbar, delegate);
}
}
class FileTooLargeFileView {
static create(
container: HTMLElement,
descriptorSize: number,
scrollbar: DomScrollableElement,
delegate: ResourceViewerDelegate
) {
const size = ByteSize.formatSize(descriptorSize);
delegate.metadataClb(size);
clearNode(container);
const label = document.createElement('span');
label.textContent = localize('nativeFileTooLargeError', "The file is not displayed in the editor because it is too large ({0}).", size);
container.appendChild(label);
scrollbar.scanDomNode();
return Disposable.None;
}
}
class FileSeemsBinaryFileView {
static create(
container: HTMLElement,
descriptor: IResourceDescriptor,
scrollbar: DomScrollableElement,
delegate: ResourceViewerDelegate
) {
delegate.metadataClb(typeof descriptor.size === 'number' ? ByteSize.formatSize(descriptor.size) : '');
clearNode(container);
const disposables = new DisposableStore();
const label = document.createElement('p');
label.textContent = localize('nativeBinaryError', "The file is not displayed in the editor because it is either binary or uses an unsupported text encoding.");
container.appendChild(label);
const link = append(label, $('a.embedded-link'));
link.setAttribute('role', 'button');
link.textContent = localize('openAsText', "Do you want to open it anyway?");
disposables.add(addDisposableListener(link, EventType.CLICK, () => delegate.openInternalClb(descriptor.resource)));
scrollbar.scanDomNode();
return disposables;
}
}

View File

@@ -54,7 +54,7 @@ class OutlineItem extends BreadcrumbsItem {
super();
}
dispose(): void {
override dispose(): void {
this._disposables.dispose();
}
@@ -114,7 +114,7 @@ class FileItem extends BreadcrumbsItem {
super();
}
dispose(): void {
override dispose(): void {
this._disposables.dispose();
}
@@ -147,6 +147,7 @@ export interface IBreadcrumbsControlOptions {
showSymbolIcons: boolean;
showDecorationColors: boolean;
breadcrumbsBackground: ColorIdentifier | ColorFunction;
showPlaceholder: boolean;
}
export class BreadcrumbsControl {
@@ -288,8 +289,21 @@ export class BreadcrumbsControl {
showSymbolIcons: this._options.showSymbolIcons && showIcons
};
const items = model.getElements().map(element => element instanceof FileElement ? new FileItem(model, element, options, this._instantiationService) : new OutlineItem(model, element, options));
this._widget.setItems(items);
this._widget.reveal(items[items.length - 1]);
if (items.length === 0) {
this._widget.setEnabled(false);
this._widget.setItems([new class extends BreadcrumbsItem {
render(container: HTMLElement): void {
container.innerText = localize('empty', "no elements");
}
equals(other: BreadcrumbsItem): boolean {
return other === this;
}
}]);
} else {
this._widget.setEnabled(true);
this._widget.setItems(items);
this._widget.reveal(items[items.length - 1]);
}
};
const listener = model.onDidUpdate(updateBreadcrumbs);
const configListener = this._cfShowIcons.onDidChange(updateBreadcrumbs);

View File

@@ -348,7 +348,7 @@ export class BreadcrumbsFilePicker extends BreadcrumbsPicker {
constructor(
parent: HTMLElement,
protected resource: URI,
resource: URI,
@IInstantiationService instantiationService: IInstantiationService,
@IThemeService themeService: IThemeService,
@IConfigurationService configService: IConfigurationService,

View File

@@ -6,8 +6,8 @@
import { Registry } from 'vs/platform/registry/common/platform';
import { localize } from 'vs/nls';
import { URI, UriComponents } from 'vs/base/common/uri';
import { IEditorRegistry, EditorDescriptor, Extensions as EditorExtensions } from 'vs/workbench/browser/editor';
import { EditorInput, IEditorInputFactory, SideBySideEditorInput, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions, TextCompareEditorActiveContext, ActiveEditorPinnedContext, EditorGroupEditorsCountContext, ActiveEditorStickyContext, ActiveEditorAvailableEditorIdsContext, MultipleEditorGroupsContext, ActiveEditorDirtyContext } from 'vs/workbench/common/editor';
import { IEditorRegistry, EditorDescriptor } from 'vs/workbench/browser/editor';
import { EditorInput, IEditorInputSerializer, SideBySideEditorInput, IEditorInputFactoryRegistry, TextCompareEditorActiveContext, ActiveEditorPinnedContext, EditorGroupEditorsCountContext, ActiveEditorStickyContext, ActiveEditorAvailableEditorIdsContext, MultipleEditorGroupsContext, ActiveEditorDirtyContext, EditorExtensions } from 'vs/workbench/common/editor';
import { TextResourceEditor } from 'vs/workbench/browser/parts/editor/textResourceEditor';
import { SideBySideEditor } from 'vs/workbench/browser/parts/editor/sideBySideEditor';
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
@@ -118,8 +118,8 @@ interface ISerializedUntitledTextEditorInput {
encoding: string | undefined;
}
// Register Editor Input Factory
class UntitledTextEditorInputFactory implements IEditorInputFactory {
// Register Editor Input Serializer
class UntitledTextEditorInputSerializer implements IEditorInputSerializer {
constructor(
@IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService,
@@ -136,7 +136,7 @@ class UntitledTextEditorInputFactory implements IEditorInputFactory {
return undefined;
}
const untitledTextEditorInput = <UntitledTextEditorInput>editorInput;
const untitledTextEditorInput = editorInput as UntitledTextEditorInput;
let resource = untitledTextEditorInput.resource;
if (untitledTextEditorInput.model.hasAssociatedFilePath) {
@@ -165,7 +165,7 @@ class UntitledTextEditorInputFactory implements IEditorInputFactory {
}
deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): UntitledTextEditorInput {
return instantiationService.invokeFunction<UntitledTextEditorInput>(accessor => {
return instantiationService.invokeFunction(accessor => {
const deserialized: ISerializedUntitledTextEditorInput = JSON.parse(serializedEditorInput);
const resource = URI.revive(deserialized.resourceJSON);
const mode = deserialized.modeId;
@@ -176,9 +176,9 @@ class UntitledTextEditorInputFactory implements IEditorInputFactory {
}
}
Registry.as<IEditorInputFactoryRegistry>(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory(UntitledTextEditorInput.ID, UntitledTextEditorInputFactory);
Registry.as<IEditorInputFactoryRegistry>(EditorExtensions.EditorInputFactories).registerEditorInputSerializer(UntitledTextEditorInput.ID, UntitledTextEditorInputSerializer);
// Register SideBySide/DiffEditor Input Factory
// Register SideBySide/DiffEditor Input Serializer
interface ISerializedSideBySideEditorInput {
name: string;
description: string | undefined;
@@ -190,21 +190,21 @@ interface ISerializedSideBySideEditorInput {
secondaryTypeId: string;
}
export abstract class AbstractSideBySideEditorInputFactory implements IEditorInputFactory {
export abstract class AbstractSideBySideEditorInputSerializer implements IEditorInputSerializer {
private getInputFactories(secondaryId: string, primaryId: string): [IEditorInputFactory | undefined, IEditorInputFactory | undefined] {
const registry = Registry.as<IEditorInputFactoryRegistry>(EditorInputExtensions.EditorInputFactories);
private getInputSerializers(secondaryEditorInputTypeId: string, primaryEditorInputTypeId: string): [IEditorInputSerializer | undefined, IEditorInputSerializer | undefined] {
const registry = Registry.as<IEditorInputFactoryRegistry>(EditorExtensions.EditorInputFactories);
return [registry.getEditorInputFactory(secondaryId), registry.getEditorInputFactory(primaryId)];
return [registry.getEditorInputSerializer(secondaryEditorInputTypeId), registry.getEditorInputSerializer(primaryEditorInputTypeId)];
}
canSerialize(editorInput: EditorInput): boolean {
const input = editorInput as SideBySideEditorInput | DiffEditorInput;
if (input.primary && input.secondary) {
const [secondaryInputFactory, primaryInputFactory] = this.getInputFactories(input.secondary.getTypeId(), input.primary.getTypeId());
const [secondaryInputSerializer, primaryInputSerializer] = this.getInputSerializers(input.secondary.typeId, input.primary.typeId);
return !!(secondaryInputFactory?.canSerialize(input.secondary) && primaryInputFactory?.canSerialize(input.primary));
return !!(secondaryInputSerializer?.canSerialize(input.secondary) && primaryInputSerializer?.canSerialize(input.primary));
}
return false;
@@ -214,10 +214,10 @@ export abstract class AbstractSideBySideEditorInputFactory implements IEditorInp
const input = editorInput as SideBySideEditorInput | DiffEditorInput;
if (input.primary && input.secondary) {
const [secondaryInputFactory, primaryInputFactory] = this.getInputFactories(input.secondary.getTypeId(), input.primary.getTypeId());
if (primaryInputFactory && secondaryInputFactory) {
const primarySerialized = primaryInputFactory.serialize(input.primary);
const secondarySerialized = secondaryInputFactory.serialize(input.secondary);
const [secondaryInputSerializer, primaryInputSerializer] = this.getInputSerializers(input.secondary.typeId, input.primary.typeId);
if (primaryInputSerializer && secondaryInputSerializer) {
const primarySerialized = primaryInputSerializer.serialize(input.primary);
const secondarySerialized = secondaryInputSerializer.serialize(input.secondary);
if (primarySerialized && secondarySerialized) {
const serializedEditorInput: ISerializedSideBySideEditorInput = {
@@ -225,8 +225,8 @@ export abstract class AbstractSideBySideEditorInputFactory implements IEditorInp
description: input.getDescription(),
primarySerialized: primarySerialized,
secondarySerialized: secondarySerialized,
primaryTypeId: input.primary.getTypeId(),
secondaryTypeId: input.secondary.getTypeId()
primaryTypeId: input.primary.typeId,
secondaryTypeId: input.secondary.typeId
};
return JSON.stringify(serializedEditorInput);
@@ -240,10 +240,10 @@ export abstract class AbstractSideBySideEditorInputFactory implements IEditorInp
deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): EditorInput | undefined {
const deserialized: ISerializedSideBySideEditorInput = JSON.parse(serializedEditorInput);
const [secondaryInputFactory, primaryInputFactory] = this.getInputFactories(deserialized.secondaryTypeId, deserialized.primaryTypeId);
if (primaryInputFactory && secondaryInputFactory) {
const primaryInput = primaryInputFactory.deserialize(instantiationService, deserialized.primarySerialized);
const secondaryInput = secondaryInputFactory.deserialize(instantiationService, deserialized.secondarySerialized);
const [secondaryInputSerializer, primaryInputSerializer] = this.getInputSerializers(deserialized.secondaryTypeId, deserialized.primaryTypeId);
if (primaryInputSerializer && secondaryInputSerializer) {
const primaryInput = primaryInputSerializer.deserialize(instantiationService, deserialized.primarySerialized);
const secondaryInput = secondaryInputSerializer.deserialize(instantiationService, deserialized.secondarySerialized);
if (primaryInput && secondaryInput) {
return this.createEditorInput(instantiationService, deserialized.name, deserialized.description, secondaryInput, primaryInput);
@@ -256,22 +256,22 @@ export abstract class AbstractSideBySideEditorInputFactory implements IEditorInp
protected abstract createEditorInput(instantiationService: IInstantiationService, name: string, description: string | undefined, secondaryInput: EditorInput, primaryInput: EditorInput): EditorInput;
}
class SideBySideEditorInputFactory extends AbstractSideBySideEditorInputFactory {
class SideBySideEditorInputSerializer extends AbstractSideBySideEditorInputSerializer {
protected createEditorInput(instantiationService: IInstantiationService, name: string, description: string | undefined, secondaryInput: EditorInput, primaryInput: EditorInput): EditorInput {
return new SideBySideEditorInput(name, description, secondaryInput, primaryInput);
}
}
class DiffEditorInputFactory extends AbstractSideBySideEditorInputFactory {
class DiffEditorInputSerializer extends AbstractSideBySideEditorInputSerializer {
protected createEditorInput(instantiationService: IInstantiationService, name: string, description: string | undefined, secondaryInput: EditorInput, primaryInput: EditorInput): EditorInput {
return instantiationService.createInstance(DiffEditorInput, name, description, secondaryInput, primaryInput, undefined);
}
}
Registry.as<IEditorInputFactoryRegistry>(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory(SideBySideEditorInput.ID, SideBySideEditorInputFactory);
Registry.as<IEditorInputFactoryRegistry>(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory(DiffEditorInput.ID, DiffEditorInputFactory);
Registry.as<IEditorInputFactoryRegistry>(EditorExtensions.EditorInputFactories).registerEditorInputSerializer(SideBySideEditorInput.ID, SideBySideEditorInputSerializer);
Registry.as<IEditorInputFactoryRegistry>(EditorExtensions.EditorInputFactories).registerEditorInputSerializer(DiffEditorInput.ID, DiffEditorInputSerializer);
// Register Editor Contributions
registerEditorContribution(OpenWorkspaceButtonContribution.ID, OpenWorkspaceButtonContribution);

View File

@@ -3,9 +3,8 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { GroupIdentifier, IWorkbenchEditorConfiguration, EditorOptions, TextEditorOptions, IEditorInput, IEditorIdentifier, IEditorCloseEvent, IEditorPane, IEditorPartOptions, IEditorPartOptionsChangeEvent, EditorInput } from 'vs/workbench/common/editor';
import { EditorGroup } from 'vs/workbench/common/editor/editorGroup';
import { IEditorGroup, GroupDirection, IAddGroupOptions, IMergeGroupOptions, GroupsOrder, GroupsArrangement, OpenEditorContext } from 'vs/workbench/services/editor/common/editorGroupsService';
import { GroupIdentifier, IWorkbenchEditorConfiguration, EditorOptions, TextEditorOptions, IEditorInput, IEditorIdentifier, IEditorCloseEvent, IEditorPartOptions, IEditorPartOptionsChangeEvent, EditorInput } from 'vs/workbench/common/editor';
import { IEditorGroup, GroupDirection, IAddGroupOptions, IMergeGroupOptions, GroupsOrder, GroupsArrangement } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IDisposable } from 'vs/base/common/lifecycle';
import { Dimension } from 'vs/base/browser/dom';
import { Event } from 'vs/base/common/event';
@@ -13,7 +12,6 @@ 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 { getIEditor } from 'vs/editor/browser/editorBrowser';
import { IEditorOptions } from 'vs/platform/editor/common/editor';
import { IEditorService, IResourceEditorInputType } from 'vs/workbench/services/editor/common/editorService';
export interface IEditorPartCreationOptions {
@@ -60,28 +58,6 @@ export function getEditorPartOptions(configurationService: IConfigurationService
return options;
}
export interface IEditorOpeningEvent extends IEditorIdentifier {
/**
* The options used when opening the editor.
*/
readonly options?: IEditorOptions;
/**
* Context indicates how the editor open event is initialized.
*/
readonly context?: OpenEditorContext;
/**
* Allows to prevent the opening of an editor by providing a callback
* that will be executed instead. By returning another editor promise
* it is possible to override the opening with another editor. It is ok
* to return a promise that resolves to `undefined` to prevent the opening
* alltogether.
*/
prevent(callback: () => Promise<IEditorPane | undefined> | undefined): void;
}
export interface IEditorGroupsAccessor {
readonly groups: IEditorGroupView[];
@@ -128,18 +104,20 @@ export interface IEditorGroupTitleHeight {
export interface IEditorGroupView extends IDisposable, ISerializableView, IEditorGroup {
readonly onDidFocus: Event<void>;
readonly onWillDispose: Event<void>;
readonly onWillOpenEditor: Event<IEditorOpeningEvent>;
readonly onDidOpenEditorFail: Event<IEditorInput>;
readonly onWillCloseEditor: Event<IEditorCloseEvent>;
readonly onDidCloseEditor: Event<IEditorCloseEvent>;
readonly group: EditorGroup;
/**
* A promise that resolves when the group has been restored.
*
* For a group with active editor, the promise will resolve
* when the active editor has finished to resolve.
*/
readonly whenRestored: Promise<void>;
readonly titleHeight: IEditorGroupTitleHeight;
readonly isEmpty: boolean;
readonly isMinimized: boolean;
readonly disposed: boolean;

View File

@@ -11,7 +11,7 @@ import { 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, ActiveEditorMoveArguments, SPLIT_EDITOR_LEFT, SPLIT_EDITOR_RIGHT, SPLIT_EDITOR_UP, SPLIT_EDITOR_DOWN, splitEditor, LAYOUT_EDITOR_GROUPS_COMMAND_ID, UNPIN_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands';
import { IEditorGroupsService, IEditorGroup, GroupsArrangement, GroupLocation, GroupDirection, preferredSideBySideGroupDirection, IFindGroupScope, GroupOrientation, EditorGroupLayout, GroupsOrder, OpenEditorContext } from 'vs/workbench/services/editor/common/editorGroupsService';
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';
import { DisposableStore } from 'vs/base/common/lifecycle';
@@ -23,6 +23,7 @@ import { AllEditorsByMostRecentlyUsedQuickAccess, ActiveGroupEditorsByMostRecent
import { Codicon } from 'vs/base/common/codicons';
import { IFilesConfigurationService, AutoSaveMode } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService';
import { EditorOverride } from 'vs/platform/editor/common/editor';
import { Schemas } from 'vs/base/common/network';
export class ExecuteCommandAction extends Action {
@@ -36,7 +37,7 @@ export class ExecuteCommandAction extends Action {
super(id, label);
}
run(): Promise<void> {
override run(): Promise<void> {
return this.commandService.executeCommand(this.commandId, this.commandArgs);
}
}
@@ -70,7 +71,7 @@ export class BaseSplitEditorAction extends Action {
}));
}
async run(context?: IEditorIdentifier): Promise<void> {
override async run(context?: IEditorIdentifier): Promise<void> {
splitEditor(this.editorGroupService, this.direction, context);
}
}
@@ -104,7 +105,7 @@ export class SplitEditorOrthogonalAction extends BaseSplitEditorAction {
super(id, label, editorGroupService, configurationService);
}
protected getDirection(): GroupDirection {
protected override getDirection(): GroupDirection {
const direction = preferredSideBySideGroupDirection(this.configurationService);
return direction === GroupDirection.RIGHT ? GroupDirection.DOWN : GroupDirection.RIGHT;
@@ -180,7 +181,7 @@ export class JoinTwoGroupsAction extends Action {
super(id, label);
}
async run(context?: IEditorIdentifier): Promise<void> {
override async run(context?: IEditorIdentifier): Promise<void> {
let sourceGroup: IEditorGroup | undefined;
if (context && typeof context.groupId === 'number') {
sourceGroup = this.editorGroupService.getGroup(context.groupId);
@@ -215,7 +216,7 @@ export class JoinAllGroupsAction extends Action {
super(id, label);
}
async run(): Promise<void> {
override async run(): Promise<void> {
this.editorGroupService.mergeAllGroups();
}
}
@@ -233,7 +234,7 @@ export class NavigateBetweenGroupsAction extends Action {
super(id, label);
}
async run(): Promise<void> {
override async run(): Promise<void> {
const nextGroup = this.editorGroupService.findGroup({ location: GroupLocation.NEXT }, this.editorGroupService.activeGroup, true);
nextGroup.focus();
}
@@ -252,7 +253,7 @@ export class FocusActiveGroupAction extends Action {
super(id, label);
}
async run(): Promise<void> {
override async run(): Promise<void> {
this.editorGroupService.activeGroup.focus();
}
}
@@ -268,7 +269,7 @@ export abstract class BaseFocusGroupAction extends Action {
super(id, label);
}
async run(): Promise<void> {
override async run(): Promise<void> {
const group = this.editorGroupService.findGroup(this.scope, this.editorGroupService.activeGroup, true);
if (group) {
group.focus();
@@ -401,7 +402,7 @@ export class CloseEditorAction extends Action {
super(id, label, Codicon.close.classNames);
}
run(context?: IEditorCommandsContext): Promise<void> {
override run(context?: IEditorCommandsContext): Promise<void> {
return this.commandService.executeCommand(CLOSE_EDITOR_COMMAND_ID, undefined, context);
}
}
@@ -419,7 +420,7 @@ export class UnpinEditorAction extends Action {
super(id, label, Codicon.pinned.classNames);
}
run(context?: IEditorCommandsContext): Promise<void> {
override run(context?: IEditorCommandsContext): Promise<void> {
return this.commandService.executeCommand(UNPIN_EDITOR_COMMAND_ID, undefined, context);
}
}
@@ -437,7 +438,7 @@ export class CloseOneEditorAction extends Action {
super(id, label, Codicon.close.classNames);
}
async run(context?: IEditorCommandsContext): Promise<void> {
override async run(context?: IEditorCommandsContext): Promise<void> {
let group: IEditorGroup | undefined;
let editorIndex: number | undefined;
if (context) {
@@ -480,7 +481,7 @@ export class RevertAndCloseEditorAction extends Action {
super(id, label);
}
async run(): Promise<void> {
override async run(): Promise<void> {
const activeEditorPane = this.editorService.activeEditorPane;
if (activeEditorPane) {
const editor = activeEditorPane.input;
@@ -515,7 +516,7 @@ export class CloseLeftEditorsInGroupAction extends Action {
super(id, label);
}
async run(context?: IEditorIdentifier): Promise<void> {
override async run(context?: IEditorIdentifier): Promise<void> {
const { group, editor } = this.getTarget(context);
if (group && editor) {
return group.closeEditors({ direction: CloseDirection.LEFT, except: editor, excludeSticky: true });
@@ -561,7 +562,7 @@ abstract class BaseCloseAllAction extends Action {
return groupsToClose;
}
async run(): Promise<void> {
override async run(): Promise<void> {
// Just close all if there are no dirty editors
if (!this.workingCopyService.hasDirty) {
@@ -714,7 +715,7 @@ export class CloseEditorsInOtherGroupsAction extends Action {
super(id, label);
}
async run(context?: IEditorIdentifier): Promise<void> {
override async run(context?: IEditorIdentifier): Promise<void> {
const groupToSkip = context ? this.editorGroupService.getGroup(context.groupId) : this.editorGroupService.activeGroup;
await Promise.all(this.editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE).map(async group => {
if (groupToSkip && group.id === groupToSkip.id) {
@@ -740,7 +741,7 @@ export class CloseEditorInAllGroupsAction extends Action {
super(id, label);
}
async run(): Promise<void> {
override async run(): Promise<void> {
const activeEditor = this.editorService.activeEditor;
if (activeEditor) {
await Promise.all(this.editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE).map(group => group.closeEditor(activeEditor)));
@@ -760,7 +761,7 @@ class BaseMoveCopyGroupAction extends Action {
super(id, label);
}
async run(context?: IEditorIdentifier): Promise<void> {
override async run(context?: IEditorIdentifier): Promise<void> {
let sourceGroup: IEditorGroup | undefined;
if (context && typeof context.groupId === 'number') {
sourceGroup = this.editorGroupService.getGroup(context.groupId);
@@ -958,7 +959,7 @@ export class MinimizeOtherGroupsAction extends Action {
super(id, label);
}
async run(): Promise<void> {
override async run(): Promise<void> {
this.editorGroupService.arrangeGroups(GroupsArrangement.MINIMIZE_OTHERS);
}
}
@@ -972,7 +973,7 @@ export class ResetGroupSizesAction extends Action {
super(id, label);
}
async run(): Promise<void> {
override async run(): Promise<void> {
this.editorGroupService.arrangeGroups(GroupsArrangement.EVEN);
}
}
@@ -986,7 +987,7 @@ export class ToggleGroupSizesAction extends Action {
super(id, label);
}
async run(): Promise<void> {
override async run(): Promise<void> {
this.editorGroupService.arrangeGroups(GroupsArrangement.TOGGLE);
}
}
@@ -1006,7 +1007,7 @@ export class MaximizeGroupAction extends Action {
super(id, label);
}
async run(): Promise<void> {
override async run(): Promise<void> {
if (this.editorService.activeEditor) {
this.editorGroupService.arrangeGroups(GroupsArrangement.MINIMIZE_OTHERS);
this.layoutService.setSideBarHidden(true);
@@ -1025,7 +1026,7 @@ export abstract class BaseNavigateEditorAction extends Action {
super(id, label);
}
async run(): Promise<void> {
override async run(): Promise<void> {
const result = this.navigate();
if (!result) {
return;
@@ -1214,7 +1215,7 @@ export class NavigateForwardAction extends Action {
super(id, label);
}
async run(): Promise<void> {
override async run(): Promise<void> {
this.historyService.forward();
}
}
@@ -1228,7 +1229,7 @@ export class NavigateBackwardsAction extends Action {
super(id, label);
}
async run(): Promise<void> {
override async run(): Promise<void> {
this.historyService.back();
}
}
@@ -1242,7 +1243,7 @@ export class NavigateToLastEditLocationAction extends Action {
super(id, label);
}
async run(): Promise<void> {
override async run(): Promise<void> {
this.historyService.openLastEditLocation();
}
}
@@ -1256,7 +1257,7 @@ export class NavigateLastAction extends Action {
super(id, label);
}
async run(): Promise<void> {
override async run(): Promise<void> {
this.historyService.last();
}
}
@@ -1274,7 +1275,7 @@ export class ReopenClosedEditorAction extends Action {
super(id, label);
}
async run(): Promise<void> {
override async run(): Promise<void> {
this.historyService.reopenLastClosedEditor();
}
}
@@ -1293,7 +1294,7 @@ export class ClearRecentFilesAction extends Action {
super(id, label);
}
async run(): Promise<void> {
override async run(): Promise<void> {
// Clear global recently opened
this.workspacesService.clearRecentlyOpened();
@@ -1316,7 +1317,7 @@ export class ShowEditorsInActiveGroupByMostRecentlyUsedAction extends Action {
super(id, label);
}
async run(): Promise<void> {
override async run(): Promise<void> {
this.quickInputService.quickAccess.show(ActiveGroupEditorsByMostRecentlyUsedQuickAccess.PREFIX);
}
}
@@ -1334,7 +1335,7 @@ export class ShowAllEditorsByAppearanceAction extends Action {
super(id, label);
}
async run(): Promise<void> {
override async run(): Promise<void> {
this.quickInputService.quickAccess.show(AllEditorsByAppearanceQuickAccess.PREFIX);
}
}
@@ -1352,7 +1353,7 @@ export class ShowAllEditorsByMostRecentlyUsedAction extends Action {
super(id, label);
}
async run(): Promise<void> {
override async run(): Promise<void> {
this.quickInputService.quickAccess.show(AllEditorsByMostRecentlyUsedQuickAccess.PREFIX);
}
}
@@ -1370,7 +1371,7 @@ export class BaseQuickAccessEditorAction extends Action {
super(id, label);
}
async run(): Promise<void> {
override async run(): Promise<void> {
const keybindings = this.keybindingService.lookupKeybindings(this.id);
this.quickInputService.quickAccess.show(this.prefix, {
@@ -1455,7 +1456,7 @@ export class QuickAccessPreviousEditorFromHistoryAction extends Action {
super(id, label);
}
async run(): Promise<void> {
override async run(): Promise<void> {
const keybindings = this.keybindingService.lookupKeybindings(this.id);
// Enforce to activate the first item in quick access if
@@ -1482,7 +1483,7 @@ export class OpenNextRecentlyUsedEditorAction extends Action {
super(id, label);
}
async run(): Promise<void> {
override async run(): Promise<void> {
this.historyService.openNextRecentlyUsedEditor();
}
}
@@ -1500,7 +1501,7 @@ export class OpenPreviousRecentlyUsedEditorAction extends Action {
super(id, label);
}
async run(): Promise<void> {
override async run(): Promise<void> {
this.historyService.openPreviouslyUsedEditor();
}
}
@@ -1519,7 +1520,7 @@ export class OpenNextRecentlyUsedEditorInGroupAction extends Action {
super(id, label);
}
async run(): Promise<void> {
override async run(): Promise<void> {
this.historyService.openNextRecentlyUsedEditor(this.editorGroupsService.activeGroup.id);
}
}
@@ -1538,7 +1539,7 @@ export class OpenPreviousRecentlyUsedEditorInGroupAction extends Action {
super(id, label);
}
async run(): Promise<void> {
override async run(): Promise<void> {
this.historyService.openPreviouslyUsedEditor(this.editorGroupsService.activeGroup.id);
}
}
@@ -1556,7 +1557,7 @@ export class ClearEditorHistoryAction extends Action {
super(id, label);
}
async run(): Promise<void> {
override async run(): Promise<void> {
// Editor history
this.historyService.clear();
@@ -1826,7 +1827,7 @@ export class BaseCreateEditorGroupAction extends Action {
super(id, label);
}
async run(): Promise<void> {
override async run(): Promise<void> {
this.editorGroupService.addGroup(this.editorGroupService.activeGroup, this.direction, { activate: true });
}
}
@@ -1900,7 +1901,7 @@ export class ReopenResourcesAction extends Action {
super(id, label);
}
async run(): Promise<void> {
override async run(): Promise<void> {
const activeInput = this.editorService.activeEditor;
if (!activeInput) {
return;
@@ -1913,7 +1914,14 @@ export class ReopenResourcesAction extends Action {
const options = activeEditorPane.options;
const group = activeEditorPane.group;
await this.editorService.openEditor(activeInput, { ...options, override: EditorOverride.PICK }, group);
await this.editorService.replaceEditors([
{
editor: activeInput,
replacement: activeInput,
forceReplaceDirty: activeInput.resource?.scheme === Schemas.untitled,
options: { ...options, override: EditorOverride.PICK }
}
], group);
}
}
@@ -1930,7 +1938,7 @@ export class ToggleEditorTypeAction extends Action {
super(id, label);
}
async run(): Promise<void> {
override async run(): Promise<void> {
const activeEditorPane = this.editorService.activeEditorPane;
if (!activeEditorPane) {
return;
@@ -1950,6 +1958,6 @@ export class ToggleEditorTypeAction extends Action {
return;
}
await firstNonActiveOverride[0].open(activeEditorPane.input, { ...options, override: firstNonActiveOverride[1].id }, group, OpenEditorContext.NEW_EDITOR)?.override;
await firstNonActiveOverride[0].open(activeEditorPane.input, { ...options, override: firstNonActiveOverride[1].id }, group)?.override;
}
}

View File

@@ -11,7 +11,8 @@ import { SaveReason, IEditorIdentifier, IEditorInput, GroupIdentifier, ISaveOpti
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { withNullAsUndefined } from 'vs/base/common/types';
import { IWorkingCopyService, IWorkingCopy, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService';
import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService';
import { IWorkingCopy, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopy';
import { ILogService } from 'vs/platform/log/common/log';
export class EditorAutoSave extends Disposable implements IWorkbenchContribution {
@@ -184,7 +185,7 @@ export class EditorAutoSave extends Disposable implements IWorkbenchContribution
// Clear any running auto save operation
this.discardAutoSave(workingCopy);
this.logService.trace(`[editor auto save] scheduling auto save after ${this.autoSaveAfterDelay}ms`, workingCopy.resource.toString());
this.logService.trace(`[editor auto save] scheduling auto save after ${this.autoSaveAfterDelay}ms`, workingCopy.resource.toString(true), workingCopy.typeId);
// Schedule new auto save
const handle = setTimeout(() => {
@@ -194,7 +195,7 @@ export class EditorAutoSave extends Disposable implements IWorkbenchContribution
// Save if dirty
if (workingCopy.isDirty()) {
this.logService.trace(`[editor auto save] running auto save`, workingCopy.resource.toString());
this.logService.trace(`[editor auto save] running auto save`, workingCopy.resource.toString(true), workingCopy.typeId);
workingCopy.save({ reason: SaveReason.AUTO });
}
@@ -202,7 +203,7 @@ export class EditorAutoSave extends Disposable implements IWorkbenchContribution
// Keep in map for disposal as needed
this.pendingAutoSavesAfterDelay.set(workingCopy, toDisposable(() => {
this.logService.trace(`[editor auto save] clearing pending auto save`, workingCopy.resource.toString());
this.logService.trace(`[editor auto save] clearing pending auto save`, workingCopy.resource.toString(true), workingCopy.typeId);
clearTimeout(handle);
}));

View File

@@ -487,8 +487,7 @@ function registerOpenEditorAPICommands(): void {
group = editorGroupsService.getGroup(viewColumnToEditorGroup(editorGroupsService, columnArg)) ?? editorGroupsService.activeGroup;
}
const input = editorService.createEditorInput({ resource: URI.revive(resource) });
return editorService.openEditor(input, { ...optionsArg, override: id }, group);
return editorService.openEditor({ resource: URI.revive(resource), options: { ...optionsArg, override: id } }, group);
});
}
@@ -632,12 +631,13 @@ export function splitEditor(editorGroupService: IEditorGroupsService, direction:
editorToCopy = withNullAsUndefined(sourceGroup.activeEditor);
}
if (editorToCopy && (editorToCopy as EditorInput).supportsSplitEditor()) {
// Copy the editor to the new group, else move the editor to the new group
if (editorToCopy && (editorToCopy as EditorInput).canSplit()) {
sourceGroup.copyEditor(editorToCopy, newGroup);
// Focus
newGroup.focus();
}
// Focus
newGroup.focus();
}
function registerSplitEditorCommands() {

View File

@@ -4,10 +4,10 @@
*--------------------------------------------------------------------------------------------*/
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { EditorInput, EditorOptions, IEditorOpenContext, IVisibleEditorPane } from 'vs/workbench/common/editor';
import { EditorExtensions, EditorInput, EditorOptions, IEditorOpenContext, IVisibleEditorPane } from 'vs/workbench/common/editor';
import { Dimension, show, hide } from 'vs/base/browser/dom';
import { Registry } from 'vs/platform/registry/common/platform';
import { IEditorRegistry, Extensions as EditorExtensions, IEditorDescriptor } from 'vs/workbench/browser/editor';
import { IEditorRegistry, IEditorDescriptor } from 'vs/workbench/browser/editor';
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@@ -58,7 +58,7 @@ export class EditorControl extends Disposable {
// Editor pane
const descriptor = Registry.as<IEditorRegistry>(EditorExtensions.Editors).getEditor(editor);
if (!descriptor) {
throw new Error(`No editor descriptor found for input id ${editor.getTypeId()}`);
throw new Error(`No editor descriptor found for input id ${editor.typeId}`);
}
const editorPane = this.doShowEditorPane(descriptor);

View File

@@ -12,7 +12,7 @@ import { IThemeService, Themable } from 'vs/platform/theme/common/themeService';
import { activeContrastBorder } from 'vs/platform/theme/common/colorRegistry';
import { IEditorIdentifier, EditorInput, EditorOptions } from 'vs/workbench/common/editor';
import { isMacintosh, isWeb } from 'vs/base/common/platform';
import { GroupDirection, IEditorGroupsService, MergeGroupMode, OpenEditorContext } from 'vs/workbench/services/editor/common/editorGroupsService';
import { GroupDirection, IEditorGroupsService, MergeGroupMode } from 'vs/workbench/services/editor/common/editorGroupsService';
import { toDisposable } from 'vs/base/common/lifecycle';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { RunOnceScheduler } from 'vs/base/common/async';
@@ -26,7 +26,6 @@ import { assertIsDefined, assertAllDefined } from 'vs/base/common/types';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { localize } from 'vs/nls';
import { ByteSize } from 'vs/platform/files/common/files';
import { EditorOverride } from 'vs/platform/editor/common/editor';
interface IDropOperation {
splitDirection?: GroupDirection;
@@ -98,7 +97,7 @@ class DropOverlay extends Themable {
this.updateStyles();
}
protected updateStyles(): void {
protected override updateStyles(): void {
const overlay = assertIsDefined(this.overlay);
// Overlay drop background
@@ -285,18 +284,17 @@ class DropOverlay extends Themable {
const options = getActiveTextEditorOptions(sourceGroup, draggedEditor.editor, EditorOptions.create({
pinned: true, // always pin dropped editor
sticky: sourceGroup.isSticky(draggedEditor.editor), // preserve sticky state
override: EditorOverride.DISABLED // preserve editor type
}));
const copyEditor = this.isCopyOperation(event, draggedEditor);
targetGroup.openEditor(draggedEditor.editor, options, copyEditor ? OpenEditorContext.COPY_EDITOR : OpenEditorContext.MOVE_EDITOR);
if (!copyEditor) {
sourceGroup.moveEditor(draggedEditor.editor, targetGroup, options);
} else {
sourceGroup.copyEditor(draggedEditor.editor, targetGroup, options);
}
// Ensure target has focus
targetGroup.focus();
// Close in source group unless we copy
if (!copyEditor) {
sourceGroup.closeEditor(draggedEditor.editor);
}
}
this.editorTransfer.clearData(DraggedEditorIdentifier.prototype);
@@ -365,7 +363,7 @@ class DropOverlay extends Themable {
}
private isCopyOperation(e: DragEvent, draggedEditor?: IEditorIdentifier): boolean {
if (draggedEditor?.editor instanceof EditorInput && !draggedEditor.editor.supportsSplitEditor()) {
if (draggedEditor?.editor instanceof EditorInput && !draggedEditor.editor.canSplit()) {
return false;
}
@@ -545,7 +543,7 @@ class DropOverlay extends Themable {
return element === this.container || element === this.overlay;
}
dispose(): void {
override dispose(): void {
super.dispose();
this._disposed = true;
@@ -654,7 +652,7 @@ export class EditorDropTarget extends Themable {
this.container.classList.toggle('dragged-over', isDraggedOver);
}
dispose(): void {
override dispose(): void {
super.dispose();
this.disposeOverlay();

View File

@@ -4,8 +4,8 @@
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/editorgroupview';
import { EditorGroup, IEditorOpenOptions, EditorCloseEvent, ISerializedEditorGroup, isSerializedEditorGroup } from 'vs/workbench/common/editor/editorGroup';
import { EditorInput, EditorOptions, GroupIdentifier, SideBySideEditorInput, CloseDirection, IEditorCloseEvent, ActiveEditorDirtyContext, IEditorPane, EditorGroupEditorsCountContext, SaveReason, IEditorPartOptionsChangeEvent, EditorsOrder, IVisibleEditorPane, ActiveEditorStickyContext, ActiveEditorPinnedContext, EditorResourceAccessor } from 'vs/workbench/common/editor';
import { EditorGroupModel, IEditorOpenOptions, EditorCloseEvent, ISerializedEditorGroupModel, isSerializedEditorGroupModel } from 'vs/workbench/common/editor/editorGroupModel';
import { EditorInput, EditorOptions, GroupIdentifier, SideBySideEditorInput, CloseDirection, IEditorCloseEvent, ActiveEditorDirtyContext, IEditorPane, EditorGroupEditorsCountContext, SaveReason, IEditorPartOptionsChangeEvent, EditorsOrder, IVisibleEditorPane, ActiveEditorStickyContext, ActiveEditorPinnedContext, EditorResourceAccessor, IEditorMoveEvent } from 'vs/workbench/common/editor';
import { Event, Emitter, Relay } from 'vs/base/common/event';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { Dimension, trackFocus, addDisposableListener, EventType, EventHelper, findParentWithClass, clearNode, isAncestor, asCSSUrl } from 'vs/base/browser/dom';
@@ -16,7 +16,7 @@ import { attachProgressBarStyler } from 'vs/platform/theme/common/styler';
import { IThemeService, registerThemingParticipant, Themable } from 'vs/platform/theme/common/themeService';
import { editorBackground, contrastBorder } from 'vs/platform/theme/common/colorRegistry';
import { EDITOR_GROUP_HEADER_TABS_BACKGROUND, EDITOR_GROUP_HEADER_NO_TABS_BACKGROUND, EDITOR_GROUP_EMPTY_BACKGROUND, EDITOR_GROUP_FOCUSED_EMPTY_BORDER, EDITOR_GROUP_HEADER_BORDER } from 'vs/workbench/common/theme';
import { IMoveEditorOptions, ICopyEditorOptions, ICloseEditorsFilter, IGroupChangeEvent, GroupChangeKind, GroupsOrder, ICloseEditorOptions, ICloseAllEditorsOptions, OpenEditorContext } from 'vs/workbench/services/editor/common/editorGroupsService';
import { ICloseEditorsFilter, IGroupChangeEvent, GroupChangeKind, GroupsOrder, ICloseEditorOptions, ICloseAllEditorsOptions, IEditorReplacement } from 'vs/workbench/services/editor/common/editorGroupsService';
import { TabsTitleControl } from 'vs/workbench/browser/parts/editor/tabsTitleControl';
import { EditorControl } from 'vs/workbench/browser/parts/editor/editorControl';
import { IEditorProgressService } from 'vs/platform/progress/common/progress';
@@ -30,7 +30,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { Promises, RunOnceWorker } from 'vs/base/common/async';
import { EventType as TouchEventType, GestureEvent } from 'vs/base/browser/touch';
import { TitleControl } from 'vs/workbench/browser/parts/editor/titleControl';
import { IEditorGroupsAccessor, IEditorGroupView, getActiveTextEditorOptions, IEditorOpeningEvent, EditorServiceImpl, IEditorGroupTitleHeight } from 'vs/workbench/browser/parts/editor/editor';
import { IEditorGroupsAccessor, IEditorGroupView, getActiveTextEditorOptions, EditorServiceImpl, IEditorGroupTitleHeight } from 'vs/workbench/browser/parts/editor/editor';
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { ActionRunner, IAction, Action } from 'vs/base/common/actions';
@@ -41,16 +41,18 @@ import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { withNullAsUndefined, withUndefinedAsNull } from 'vs/base/common/types';
import { hash } from 'vs/base/common/hash';
import { guessMimeTypes } from 'vs/base/common/mime';
import { extname } from 'vs/base/common/resources';
import { extname, isEqual } from 'vs/base/common/resources';
import { FileAccess, Schemas } from 'vs/base/common/network';
import { EditorActivation, EditorOpenContext } from 'vs/platform/editor/common/editor';
import { IDialogService, IFileDialogService, ConfirmResult } from 'vs/platform/dialogs/common/dialogs';
import { ILogService } from 'vs/platform/log/common/log';
import { Codicon } from 'vs/base/common/codicons';
import { IFilesConfigurationService, AutoSaveMode } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService';
import { withNullAsUndefined } from 'vs/base/common/types';
import { URI } from 'vs/base/common/uri';
import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity';
export class EditorGroupView extends Themable implements IEditorGroupView {
@@ -60,7 +62,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
return instantiationService.createInstance(EditorGroupView, accessor, null, index);
}
static createFromSerialized(serialized: ISerializedEditorGroup, accessor: IEditorGroupsAccessor, index: number, instantiationService: IInstantiationService): IEditorGroupView {
static createFromSerialized(serialized: ISerializedEditorGroupModel, accessor: IEditorGroupsAccessor, index: number, instantiationService: IInstantiationService): IEditorGroupView {
return instantiationService.createInstance(EditorGroupView, accessor, serialized, index);
}
@@ -86,9 +88,6 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
private readonly _onDidGroupChange = this._register(new Emitter<IGroupChangeEvent>());
readonly onDidGroupChange = this._onDidGroupChange.event;
private readonly _onWillOpenEditor = this._register(new Emitter<IEditorOpeningEvent>());
readonly onWillOpenEditor = this._onWillOpenEditor.event;
private readonly _onDidOpenEditorFail = this._register(new Emitter<EditorInput>());
readonly onDidOpenEditorFail = this._onDidOpenEditorFail.event;
@@ -98,16 +97,16 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
private readonly _onDidCloseEditor = this._register(new Emitter<IEditorCloseEvent>());
readonly onDidCloseEditor = this._onDidCloseEditor.event;
private readonly _onWillMoveEditor = this._register(new Emitter<IEditorMoveEvent>());
readonly onWillMoveEditor = this._onWillMoveEditor.event;
//#endregion
private readonly _group: EditorGroup;
private readonly model: EditorGroupModel;
private active: boolean | undefined;
private dimension: Dimension | undefined;
private readonly _whenRestored: Promise<void>;
private isRestored = false;
private readonly scopedInstantiationService: IInstantiationService;
private readonly titleContainer: HTMLElement;
@@ -122,9 +121,13 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
private readonly mapEditorToPendingConfirmation = new Map<EditorInput, Promise<boolean>>();
private whenRestoredResolve: (() => void) | undefined;
readonly whenRestored = new Promise<void>(resolve => (this.whenRestoredResolve = resolve));
private isRestored = false;
constructor(
private accessor: IEditorGroupsAccessor,
from: IEditorGroupView | ISerializedEditorGroup | null,
from: IEditorGroupView | ISerializedEditorGroupModel | null,
private _index: number,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@@ -138,16 +141,17 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
@IFileDialogService private readonly fileDialogService: IFileDialogService,
@ILogService private readonly logService: ILogService,
@IEditorService private readonly editorService: EditorServiceImpl,
@IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService
@IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService,
@IUriIdentityService private readonly uriIdentityService: IUriIdentityService
) {
super(themeService);
if (from instanceof EditorGroupView) {
this._group = this._register(from.group.clone());
} else if (isSerializedEditorGroup(from)) {
this._group = this._register(instantiationService.createInstance(EditorGroup, from));
this.model = this._register(from.model.clone());
} else if (isSerializedEditorGroupModel(from)) {
this.model = this._register(instantiationService.createInstance(EditorGroupModel, from));
} else {
this._group = this._register(instantiationService.createInstance(EditorGroup, undefined));
this.model = this._register(instantiationService.createInstance(EditorGroupModel, undefined));
}
//#region create()
@@ -213,9 +217,16 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
}
//#endregion
this._whenRestored = this.restoreEditors(from);
this._whenRestored.then(() => this.isRestored = true);
// Restore editors if provided
const restoreEditorsPromise = this.restoreEditors(from) ?? Promise.resolve();
// Signal restored once editors have restored
restoreEditorsPromise.finally(() => {
this.isRestored = true;
this.whenRestoredResolve?.();
});
// Register Listeners
this.registerListeners();
}
@@ -230,7 +241,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
const observeActiveEditor = () => {
activeEditorListener.clear();
const activeEditor = this._group.activeEditor;
const activeEditor = this.model.activeEditor;
if (activeEditor) {
groupActiveEditorDirtyContext.set(activeEditor.isDirty() && !activeEditor.isSaving());
activeEditorListener.value = activeEditor.onDidChangeDirty(() => {
@@ -250,13 +261,13 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
observeActiveEditor();
break;
case GroupChangeKind.EDITOR_PIN:
if (e.editor && e.editor === this._group.activeEditor) {
groupActiveEditorPinnedContext.set(this._group.isPinned(this._group.activeEditor));
if (e.editor && e.editor === this.model.activeEditor) {
groupActiveEditorPinnedContext.set(this.model.isPinned(this.model.activeEditor));
}
break;
case GroupChangeKind.EDITOR_STICKY:
if (e.editor && e.editor === this._group.activeEditor) {
groupActiveEditorStickyContext.set(this._group.isSticky(this._group.activeEditor));
if (e.editor && e.editor === this.model.activeEditor) {
groupActiveEditorStickyContext.set(this.model.isSticky(this.model.activeEditor));
}
break;
}
@@ -297,11 +308,11 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
this.element.appendChild(toolbarContainer);
// Toolbar
const groupId = this._group.id;
const groupId = this.model.id;
const containerToolbar = this._register(new ActionBar(toolbarContainer, {
ariaLabel: localize('ariaLabelGroupActions', "Editor group actions"), actionRunner: this._register(new class extends ActionRunner {
run(action: IAction) {
return action.run(groupId);
override async run(action: IAction) {
await action.run(groupId);
}
})
}));
@@ -439,8 +450,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
return this.titleAreaControl;
}
private async restoreEditors(from: IEditorGroupView | ISerializedEditorGroup | null): Promise<void> {
if (this._group.count === 0) {
private restoreEditors(from: IEditorGroupView | ISerializedEditorGroupModel | null): Promise<void> | undefined {
if (this.model.count === 0) {
return; // nothing to show
}
@@ -452,27 +463,29 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
options = new EditorOptions();
}
const activeEditor = this._group.activeEditor;
const activeEditor = this.model.activeEditor;
if (!activeEditor) {
return;
}
options.pinned = this._group.isPinned(activeEditor); // preserve pinned state
options.sticky = this._group.isSticky(activeEditor); // preserve sticky state
options.pinned = this.model.isPinned(activeEditor); // preserve pinned state
options.sticky = this.model.isSticky(activeEditor); // preserve sticky state
options.preserveFocus = true; // handle focus after editor is opened
const activeElement = document.activeElement;
// Show active editor
await this.doShowEditor(activeEditor, { active: true, isNew: false /* restored */ }, options);
// Show active editor (intentionally not using async to keep
// `restoreEditors` from executing in same stack)
return this.doShowEditor(activeEditor, { active: true, isNew: false /* restored */ }, options).then(() => {
// Set focused now if this is the active group and focus has
// not changed meanwhile. This prevents focus from being
// stolen accidentally on startup when the user already
// clicked somewhere.
if (this.accessor.activeGroup === this && activeElement === document.activeElement) {
this.focus();
}
// Set focused now if this is the active group and focus has
// not changed meanwhile. This prevents focus from being
// stolen accidentally on startup when the user already
// clicked somewhere.
if (this.accessor.activeGroup === this && activeElement === document.activeElement) {
this.focus();
}
});
}
//#region event handling
@@ -480,13 +493,13 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
private registerListeners(): void {
// Model Events
this._register(this._group.onDidChangeEditorPinned(editor => this.onDidChangeEditorPinned(editor)));
this._register(this._group.onDidChangeEditorSticky(editor => this.onDidChangeEditorSticky(editor)));
this._register(this._group.onDidOpenEditor(editor => this.onDidOpenEditor(editor)));
this._register(this._group.onDidCloseEditor(editor => this.handleOnDidCloseEditor(editor)));
this._register(this._group.onDidDisposeEditor(editor => this.onDidDisposeEditor(editor)));
this._register(this._group.onDidChangeEditorDirty(editor => this.onDidChangeEditorDirty(editor)));
this._register(this._group.onDidEditorLabelChange(editor => this.onDidEditorLabelChange(editor)));
this._register(this.model.onDidChangeEditorPinned(editor => this.onDidChangeEditorPinned(editor)));
this._register(this.model.onDidChangeEditorSticky(editor => this.onDidChangeEditorSticky(editor)));
this._register(this.model.onDidOpenEditor(editor => this.onDidOpenEditor(editor)));
this._register(this.model.onDidCloseEditor(editor => this.handleOnDidCloseEditor(editor)));
this._register(this.model.onWillDisposeEditor(editor => this.onWillDisposeEditor(editor)));
this._register(this.model.onDidChangeEditorDirty(editor => this.onDidChangeEditorDirty(editor)));
this._register(this.model.onDidEditorLabelChange(editor => this.onDidEditorLabelChange(editor)));
// Option Changes
this._register(this.accessor.onDidChangeEditorPartOptions(e => this.onDidChangeEditorPartOptions(e)));
@@ -540,10 +553,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
// (including being visible in side by side / diff editors) and as such we
// only dispose when they are not opened elsewhere.
for (const editor of editorsToClose) {
if (!this.accessor.groups.some(groupView => groupView.group.contains(editor, {
strictEquals: true, // only if this input is not shared across editor groups
supportSideBySide: true // include side by side editor primary & secondary
}))) {
if (this.canDispose(editor)) {
editor.dispose();
}
}
@@ -565,6 +575,19 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
this._onDidGroupChange.fire({ kind: GroupChangeKind.EDITOR_CLOSE, editor, editorIndex: event.index });
}
private canDispose(editor: EditorInput): boolean {
for (const groupView of this.accessor.groups) {
if (groupView instanceof EditorGroupView && groupView.model.contains(editor, {
strictEquals: true, // only if this input is not shared across editor groups
supportSideBySide: true // include side by side editor primary & secondary
})) {
return false;
}
}
return true;
}
private toEditorTelemetryDescriptor(editor: EditorInput): object {
const descriptor = editor.getTelemetryDescriptor();
@@ -584,7 +607,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
return descriptor;
}
private onDidDisposeEditor(editor: EditorInput): void {
private onWillDisposeEditor(editor: EditorInput): void {
// To prevent race conditions, we handle disposed editors in our worker with a timeout
// because it can happen that an input is being disposed with the intent to replace
@@ -598,9 +621,9 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
let activeEditor: EditorInput | undefined;
const inactiveEditors: EditorInput[] = [];
for (const editor of editors) {
if (this._group.isActive(editor)) {
if (this.model.isActive(editor)) {
activeEditor = editor;
} else if (this._group.contains(editor)) {
} else if (this.model.contains(editor)) {
inactiveEditors.push(editor);
}
}
@@ -631,8 +654,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
this.relayout();
// Ensure to show active editor if any
if (this._group.activeEditor) {
this.titleAreaControl.openEditor(this._group.activeEditor);
if (this.model.activeEditor) {
this.titleAreaControl.openEditor(this.model.activeEditor);
}
}
@@ -646,8 +669,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
// Pin preview editor once user disables preview
if (event.oldPartOptions.enablePreview && !event.newPartOptions.enablePreview) {
if (this._group.previewEditor) {
this.pinEditor(this._group.previewEditor);
if (this.model.previewEditor) {
this.pinEditor(this.model.previewEditor);
}
}
}
@@ -681,11 +704,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
//#endregion
//region IEditorGroupView
get group(): EditorGroup {
return this._group;
}
//#region IEditorGroupView
get index(): number {
return this._index;
@@ -704,12 +723,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
return this._disposed;
}
get whenRestored(): Promise<void> {
return this._whenRestored;
}
get isEmpty(): boolean {
return this._group.count === 0;
return this.model.count === 0;
}
get titleHeight(): IEditorGroupTitleHeight {
@@ -755,19 +770,19 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
//#region basics()
get id(): GroupIdentifier {
return this._group.id;
return this.model.id;
}
get editors(): EditorInput[] {
return this._group.getEditors(EditorsOrder.SEQUENTIAL);
return this.model.getEditors(EditorsOrder.SEQUENTIAL);
}
get count(): number {
return this._group.count;
return this.model.count;
}
get stickyCount(): number {
return this._group.stickyCount;
return this.model.stickyCount;
}
get activeEditorPane(): IVisibleEditorPane | undefined {
@@ -775,39 +790,46 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
}
get activeEditor(): EditorInput | null {
return this._group.activeEditor;
return this.model.activeEditor;
}
get previewEditor(): EditorInput | null {
return this._group.previewEditor;
return this.model.previewEditor;
}
isPinned(editor: EditorInput): boolean {
return this._group.isPinned(editor);
return this.model.isPinned(editor);
}
isSticky(editorOrIndex: EditorInput | number): boolean {
return this._group.isSticky(editorOrIndex);
return this.model.isSticky(editorOrIndex);
}
isActive(editor: EditorInput): boolean {
return this._group.isActive(editor);
return this.model.isActive(editor);
}
contains(candidate: EditorInput): boolean {
return this.model.contains(candidate);
}
getEditors(order: EditorsOrder, options?: { excludeSticky?: boolean }): EditorInput[] {
return this._group.getEditors(order, options);
return this.model.getEditors(order, options);
}
findEditors(resource: URI): EditorInput[] {
const canonicalResource = this.uriIdentityService.asCanonicalUri(resource);
return this.getEditors(EditorsOrder.SEQUENTIAL).filter(editor => {
return editor.resource && isEqual(editor.resource, canonicalResource);
});
}
getEditorByIndex(index: number): EditorInput | undefined {
return this._group.getEditorByIndex(index);
return this.model.getEditorByIndex(index);
}
getIndexOfEditor(editor: EditorInput): number {
return this._group.indexOf(editor);
}
isOpened(editor: EditorInput): boolean {
return this._group.contains(editor);
return this.model.indexOf(editor);
}
focus(): void {
@@ -824,10 +846,10 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
}
pinEditor(candidate: EditorInput | undefined = this.activeEditor || undefined): void {
if (candidate && !this._group.isPinned(candidate)) {
if (candidate && !this.model.isPinned(candidate)) {
// Update model
const editor = this._group.pin(candidate);
const editor = this.model.pin(candidate);
// Forward to title control
if (editor) {
@@ -845,11 +867,11 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
}
private doStickEditor(candidate: EditorInput | undefined, sticky: boolean): void {
if (candidate && this._group.isSticky(candidate) !== sticky) {
if (candidate && this.model.isSticky(candidate) !== sticky) {
const oldIndexOfEditor = this.getIndexOfEditor(candidate);
// Update model
const editor = sticky ? this._group.stick(candidate) : this._group.unstick(candidate);
const editor = sticky ? this.model.stick(candidate) : this.model.unstick(candidate);
if (!editor) {
return;
}
@@ -877,23 +899,15 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
//#region openEditor()
async openEditor(editor: EditorInput, options?: EditorOptions, context?: OpenEditorContext): Promise<IEditorPane | null> {
async openEditor(editor: EditorInput, options?: EditorOptions): Promise<IEditorPane | undefined> {
// Guard against invalid inputs
if (!editor) {
return null;
}
// Editor opening event allows for prevention
const event = new EditorOpeningEvent(this._group.id, editor, options, context);
this._onWillOpenEditor.fire(event);
const prevented = event.isPrevented();
if (prevented) {
return withUndefinedAsNull(await prevented());
return undefined;
}
// Proceed with opening
return withUndefinedAsNull(await this.doOpenEditor(editor, options));
return this.doOpenEditor(editor, options);
}
private async doOpenEditor(editor: EditorInput, options?: EditorOptions): Promise<IEditorPane | undefined> {
@@ -908,19 +922,19 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
// Determine options
const openEditorOptions: IEditorOpenOptions = {
index: options ? options.index : undefined,
pinned: options?.sticky || !this.accessor.partOptions.enablePreview || editor.isDirty() || (options?.pinned ?? typeof options?.index === 'number' /* unless specified, prefer to pin when opening with index */) || (typeof options?.index === 'number' && this._group.isSticky(options.index)),
sticky: options?.sticky || (typeof options?.index === 'number' && this._group.isSticky(options.index)),
active: this._group.count === 0 || !options || !options.inactive
pinned: options?.sticky || !this.accessor.partOptions.enablePreview || editor.isDirty() || (options?.pinned ?? typeof options?.index === 'number' /* unless specified, prefer to pin when opening with index */) || (typeof options?.index === 'number' && this.model.isSticky(options.index)),
sticky: options?.sticky || (typeof options?.index === 'number' && this.model.isSticky(options.index)),
active: this.model.count === 0 || !options || !options.inactive
};
if (options?.sticky && typeof options?.index === 'number' && !this._group.isSticky(options.index)) {
if (options?.sticky && typeof options?.index === 'number' && !this.model.isSticky(options.index)) {
// Special case: we are to open an editor sticky but at an index that is not sticky
// In that case we prefer to open the editor at the index but not sticky. This enables
// to drag a sticky editor to an index that is not sticky to unstick it.
openEditorOptions.sticky = false;
}
if (!openEditorOptions.active && !openEditorOptions.pinned && this._group.activeEditor && !this._group.isPinned(this._group.activeEditor)) {
if (!openEditorOptions.active && !openEditorOptions.pinned && this.model.activeEditor && !this.model.isPinned(this.model.activeEditor)) {
// Special case: we are to open an editor inactive and not pinned, but the current active
// editor is also not pinned, which means it will get replaced with this one. As such,
// the editor can only be active.
@@ -953,7 +967,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
// out that the editor is already opened at a different index. This
// ensures the right set of events are fired to the outside.
if (typeof openEditorOptions.index === 'number') {
const indexOfEditor = this._group.indexOf(editor);
const indexOfEditor = this.model.indexOf(editor);
if (indexOfEditor !== -1 && indexOfEditor !== openEditorOptions.index) {
this.doMoveEditorInsideGroup(editor, openEditorOptions);
}
@@ -962,7 +976,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
// Update model and make sure to continue to use the editor we get from
// the model. It is possible that the editor was already opened and we
// want to ensure that we use the existing instance in that case.
const { editor: openedEditor, isNew } = this._group.openEditor(editor, openEditorOptions);
const { editor: openedEditor, isNew } = this.model.openEditor(editor, openEditorOptions);
// Show editor
const showEditorResult = this.doShowEditor(openedEditor, { active: !!openEditorOptions.active, isNew }, options);
@@ -977,10 +991,10 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
return showEditorResult;
}
private async doShowEditor(editor: EditorInput, context: { active: boolean, isNew: boolean }, options?: EditorOptions): Promise<IEditorPane | undefined> {
private doShowEditor(editor: EditorInput, context: { active: boolean, isNew: boolean }, options?: EditorOptions): Promise<IEditorPane | undefined> {
// Show in editor control if the active editor changed
let openEditorPromise: Promise<IEditorPane | undefined> | undefined;
let openEditorPromise: Promise<IEditorPane | undefined>;
if (context.active) {
openEditorPromise = (async () => {
try {
@@ -1001,7 +1015,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
}
})();
} else {
openEditorPromise = undefined; // inactive: return undefined as result to signal this
openEditorPromise = Promise.resolve(undefined); // inactive: return undefined as result to signal this
}
// Show in title control after editor control because some actions depend on it
@@ -1021,7 +1035,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
if (this.isRestored) {
// Extract possible error actions from the error
let errorActions: ReadonlyArray<IAction> | undefined = undefined;
let errorActions: readonly IAction[] | undefined = undefined;
if (isErrorWithActions(error)) {
errorActions = error.actions;
}
@@ -1070,6 +1084,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
}
const handle = this.notificationService.notify({
id: `${hash(editor.resource?.toString())}`, // unique per editor
severity: Severity.Error,
message: localize('editorOpenError', "Unable to open '{0}': {1}.", editor.getName(), toErrorMessage(error)),
actions
@@ -1132,7 +1147,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
//#region moveEditor()
moveEditor(editor: EditorInput, target: IEditorGroupView, options?: IMoveEditorOptions): void {
moveEditor(editor: EditorInput, target: IEditorGroupView, options?: EditorOptions): void {
// Move within same group
if (this === target) {
@@ -1145,13 +1160,13 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
}
}
private doMoveEditorInsideGroup(candidate: EditorInput, moveOptions?: IMoveEditorOptions): void {
const moveToIndex = moveOptions ? moveOptions.index : undefined;
private doMoveEditorInsideGroup(candidate: EditorInput, options?: IEditorOpenOptions): void {
const moveToIndex = options ? options.index : undefined;
if (typeof moveToIndex !== 'number') {
return; // do nothing if we move into same group without index
}
const currentIndex = this._group.indexOf(candidate);
const currentIndex = this.model.indexOf(candidate);
if (currentIndex === -1 || currentIndex === moveToIndex) {
return; // do nothing if editor unknown in model or is already at the given index
}
@@ -1159,14 +1174,14 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
// Update model and make sure to continue to use the editor we get from
// the model. It is possible that the editor was already opened and we
// want to ensure that we use the existing instance in that case.
const editor = this._group.getEditorByIndex(currentIndex);
const editor = this.model.getEditorByIndex(currentIndex);
if (!editor) {
return;
}
// Update model
this._group.moveEditor(editor, moveToIndex);
this._group.pin(editor);
this.model.moveEditor(editor, moveToIndex);
this.model.pin(editor);
// Forward to title area
this.titleAreaControl.moveEditor(editor, currentIndex, moveToIndex);
@@ -1176,19 +1191,28 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
this._onDidGroupChange.fire({ kind: GroupChangeKind.EDITOR_MOVE, editor });
}
private doMoveOrCopyEditorAcrossGroups(editor: EditorInput, target: IEditorGroupView, moveOptions: IMoveEditorOptions = Object.create(null), keepCopy?: boolean): void {
private doMoveOrCopyEditorAcrossGroups(editor: EditorInput, target: IEditorGroupView, openOptions?: IEditorOpenOptions, keepCopy?: boolean): void {
// When moving/copying an editor, try to preserve as much view state as possible
// by checking for the editor to be a text editor and creating the options accordingly
// if so
const options = getActiveTextEditorOptions(this, editor, EditorOptions.create({
...moveOptions,
...openOptions,
pinned: true, // always pin moved editor
sticky: !keepCopy && this._group.isSticky(editor) // preserve sticky state only if editor is moved (https://github.com/microsoft/vscode/issues/99035)
sticky: !keepCopy && this.model.isSticky(editor) // preserve sticky state only if editor is moved (https://github.com/microsoft/vscode/issues/99035)
}));
// Indicate will move event
if (!keepCopy) {
this._onWillMoveEditor.fire({
groupId: this.id,
editor,
target: target.id,
});
}
// A move to another group is an open first...
target.openEditor(editor, options, keepCopy ? OpenEditorContext.COPY_EDITOR : OpenEditorContext.MOVE_EDITOR);
target.openEditor(keepCopy ? editor.copy() : editor, options);
// ...and a close afterwards (unless we copy)
if (!keepCopy) {
@@ -1200,7 +1224,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
//#region copyEditor()
copyEditor(editor: EditorInput, target: IEditorGroupView, options?: ICopyEditorOptions): void {
copyEditor(editor: EditorInput, target: IEditorGroupView, options?: EditorOptions): void {
// Move within same group because we do not support to show the same editor
// multiple times in the same group
@@ -1242,7 +1266,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
private doCloseEditor(editor: EditorInput, focusNext = (this.accessor.activeGroup === this), fromError?: boolean): void {
// Closing the active editor of the group is a bit more work
if (this._group.isActive(editor)) {
if (this.model.isActive(editor)) {
this.doCloseActiveEditor(focusNext, fromError);
}
@@ -1267,7 +1291,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
// event because it became empty, only to then trigger another one when the next
// group gets active.
const closeEmptyGroup = this.accessor.partOptions.closeEmptyGroups;
if (closeEmptyGroup && this.active && this._group.count === 1) {
if (closeEmptyGroup && this.active && this.model.count === 1) {
const mostRecentlyActiveGroups = this.accessor.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE);
const nextActiveGroup = mostRecentlyActiveGroups[1]; // [0] will be the current one, so take [1]
if (nextActiveGroup) {
@@ -1281,11 +1305,11 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
// Update model
if (editorToClose) {
this._group.closeEditor(editorToClose);
this.model.closeEditor(editorToClose);
}
// Open next active if there are more to show
const nextActiveEditor = this._group.activeEditor;
const nextActiveEditor = this.model.activeEditor;
if (nextActiveEditor) {
const preserveFocus = !focusNext;
@@ -1349,7 +1373,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
private doCloseInactiveEditor(editor: EditorInput) {
// Update model
this._group.closeEditor(editor);
this.model.closeEditor(editor);
}
private async handleDirtyClosing(editors: EditorInput[]): Promise<boolean /* veto */> {
@@ -1388,7 +1412,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
return false; // editor must be dirty and not saving
}
if (editor instanceof SideBySideEditorInput && this._group.contains(editor.primary)) {
if (editor instanceof SideBySideEditorInput && this.model.contains(editor.primary)) {
return false; // primary-side of editor is still opened somewhere else
}
@@ -1402,7 +1426,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
return false; // skip this group to avoid false assumptions about the editor being opened still
}
const otherGroup = groupView.group;
const otherGroup = groupView;
if (otherGroup.contains(editor)) {
return true; // exact editor still opened
}
@@ -1517,7 +1541,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
const filter = args;
const hasDirection = typeof filter.direction === 'number';
let editorsToClose = this._group.getEditors(hasDirection ? EditorsOrder.SEQUENTIAL : EditorsOrder.MOST_RECENTLY_ACTIVE, filter); // in MRU order only if direction is not specified
let editorsToClose = this.model.getEditors(hasDirection ? EditorsOrder.SEQUENTIAL : EditorsOrder.MOST_RECENTLY_ACTIVE, filter); // in MRU order only if direction is not specified
// Filter: saved or saving only
if (filter.savedOnly) {
@@ -1527,8 +1551,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
// Filter: direction (left / right)
else if (hasDirection && filter.except) {
editorsToClose = (filter.direction === CloseDirection.LEFT) ?
editorsToClose.slice(0, this._group.indexOf(filter.except, editorsToClose)) :
editorsToClose.slice(this._group.indexOf(filter.except, editorsToClose) + 1);
editorsToClose.slice(0, this.model.indexOf(filter.except, editorsToClose)) :
editorsToClose.slice(this.model.indexOf(filter.except, editorsToClose) + 1);
}
// Filter: except
@@ -1580,7 +1604,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
}
// Check for dirty and veto
const veto = await this.handleDirtyClosing(this._group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE, options));
const veto = await this.handleDirtyClosing(this.model.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE, options));
if (veto) {
return;
}
@@ -1593,7 +1617,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
// Close all inactive editors first
const editorsToClose: EditorInput[] = [];
for (const editor of this._group.getEditors(EditorsOrder.SEQUENTIAL, options)) {
for (const editor of this.model.getEditors(EditorsOrder.SEQUENTIAL, options)) {
if (!this.isActive(editor)) {
this.doCloseInactiveEditor(editor);
}
@@ -1689,7 +1713,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
//#region Themable
protected updateStyles(): void {
protected override updateStyles(): void {
const isEmpty = this.isEmpty;
// Container
@@ -1752,13 +1776,13 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
}
}
toJSON(): ISerializedEditorGroup {
return this._group.serialize();
toJSON(): ISerializedEditorGroupModel {
return this.model.serialize();
}
//#endregion
dispose(): void {
override dispose(): void {
this._disposed = true;
this._onWillDispose.fire();
@@ -1769,36 +1793,9 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
}
}
class EditorOpeningEvent implements IEditorOpeningEvent {
private override: (() => Promise<IEditorPane | undefined>) | undefined = undefined;
constructor(
public readonly groupId: GroupIdentifier,
public readonly editor: EditorInput,
private _options: EditorOptions | undefined,
public readonly context: OpenEditorContext | undefined
) {
}
get options(): EditorOptions | undefined {
return this._options;
}
prevent(callback: () => Promise<IEditorPane | undefined>): void {
this.override = callback;
}
isPrevented(): (() => Promise<IEditorPane | undefined>) | undefined {
return this.override;
}
}
export interface EditorReplacement {
export interface EditorReplacement extends IEditorReplacement {
readonly editor: EditorInput;
readonly replacement: EditorInput;
/** Skips asking the user for confirmation and doesn't save the document. Only use this if you really need to! */
readonly forceReplaceDirty?: boolean;
readonly options?: EditorOptions;
}

View File

@@ -76,7 +76,7 @@ export abstract class EditorPane extends Composite implements IEditorPane {
super(id, telemetryService, themeService, storageService);
}
create(parent: HTMLElement): void {
override create(parent: HTMLElement): void {
super.create(parent);
// Create Editor
@@ -133,7 +133,7 @@ export abstract class EditorPane extends Composite implements IEditorPane {
this._options = options;
}
setVisible(visible: boolean, group?: IEditorGroup): void {
override setVisible(visible: boolean, group?: IEditorGroup): void {
super.setVisible(visible);
// Propagate to Editor
@@ -163,7 +163,7 @@ export abstract class EditorPane extends Composite implements IEditorPane {
return editorMemento;
}
protected saveState(): void {
protected override saveState(): void {
// Save all editor memento for this editor type
for (const [, editorMemento] of EditorPane.EDITOR_MEMENTOS) {
@@ -175,7 +175,7 @@ export abstract class EditorPane extends Composite implements IEditorPane {
super.saveState();
}
dispose(): void {
override dispose(): void {
this._input = undefined;
this._options = undefined;
@@ -274,7 +274,7 @@ export class EditorMemento<T> implements IEditorMemento<T> {
}
if (!this.editorDisposables.has(editor)) {
this.editorDisposables.set(editor, Event.once(editor.onDispose)(() => {
this.editorDisposables.set(editor, Event.once(editor.onWillDispose)(() => {
this.clearEditorState(resource);
this.editorDisposables?.delete(editor);
}));

View File

@@ -19,7 +19,7 @@ import { EditorGroupView } from 'vs/workbench/browser/parts/editor/editorGroupVi
import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
import { IDisposable, dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { ISerializedEditorGroup, isSerializedEditorGroup } from 'vs/workbench/common/editor/editorGroup';
import { ISerializedEditorGroupModel, isSerializedEditorGroupModel } from 'vs/workbench/common/editor/editorGroupModel';
import { EditorDropTarget, IEditorDropTargetDelegate } from 'vs/workbench/browser/parts/editor/editorDropTarget';
import { IEditorDropService } from 'vs/workbench/services/editor/browser/editorDropService';
import { Color } from 'vs/base/common/color';
@@ -27,7 +27,6 @@ import { CenteredViewLayout } from 'vs/base/browser/ui/centered/centeredViewLayo
import { onUnexpectedError } from 'vs/base/common/errors';
import { Parts, IWorkbenchLayoutService, Position } from 'vs/workbench/services/layout/browser/layoutService';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { MementoObject } from 'vs/workbench/common/memento';
import { assertIsDefined } from 'vs/base/common/types';
import { IBoundarySashes } from 'vs/base/browser/ui/grid/gridview';
import { CompositeDragAndDropObserver } from 'vs/workbench/browser/dnd';
@@ -121,8 +120,8 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro
//#endregion
private readonly workspaceMemento: MementoObject;
private readonly globalMemento: MementoObject;
private readonly workspaceMemento = this.getMemento(StorageScope.WORKSPACE, StorageTarget.MACHINE);
private readonly globalMemento = this.getMemento(StorageScope.GLOBAL, StorageTarget.MACHINE);
private readonly groupViews = new Map<GroupIdentifier, IEditorGroupView>();
private mostRecentActiveGroups: GroupIdentifier[] = [];
@@ -132,10 +131,7 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro
private centeredLayoutWidget!: CenteredViewLayout;
private gridWidget!: SerializableGrid<IEditorGroupView>;
private gridWidgetView: GridWidgetView<IEditorGroupView>;
private _whenRestored: Promise<void>;
private whenRestoredResolve: (() => void) | undefined;
private readonly gridWidgetView = this._register(new GridWidgetView<IEditorGroupView>());
constructor(
@IInstantiationService private readonly instantiationService: IInstantiationService,
@@ -146,13 +142,6 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro
) {
super(Parts.EDITOR_PART, { hasTitle: false }, themeService, storageService, layoutService);
this.gridWidgetView = new GridWidgetView<IEditorGroupView>();
this.workspaceMemento = this.getMemento(StorageScope.WORKSPACE, StorageTarget.MACHINE);
this.globalMemento = this.getMemento(StorageScope.GLOBAL, StorageTarget.MACHINE);
this._whenRestored = new Promise(resolve => (this.whenRestoredResolve = resolve));
this.registerListeners();
}
@@ -217,11 +206,18 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro
return (this.gridWidget && this.gridWidget.orientation === Orientation.VERTICAL) ? GroupOrientation.VERTICAL : GroupOrientation.HORIZONTAL;
}
get whenRestored(): Promise<void> {
return this._whenRestored;
private whenReadyResolve: (() => void) | undefined;
readonly whenReady = new Promise<void>(resolve => (this.whenReadyResolve = resolve));
private whenRestoredResolve: (() => void) | undefined;
readonly whenRestored = new Promise<void>(resolve => (this.whenRestoredResolve = resolve));
private restored = false;
isRestored(): boolean {
return this.restored;
}
get willRestoreEditors(): boolean {
get hasRestorableState(): boolean {
return !!this.workspaceMemento[EditorPart.EDITOR_PART_UI_STATE_STORAGE_KEY];
}
@@ -522,13 +518,13 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro
return this._partOptions.splitSizing === 'split' ? Sizing.Split : Sizing.Distribute;
}
private doCreateGroupView(from?: IEditorGroupView | ISerializedEditorGroup | null): IEditorGroupView {
private doCreateGroupView(from?: IEditorGroupView | ISerializedEditorGroupModel | null): IEditorGroupView {
// Create group view
let groupView: IEditorGroupView;
if (from instanceof EditorGroupView) {
groupView = EditorGroupView.createCopy(from, this, this.count, this.instantiationService);
} else if (isSerializedEditorGroup(from)) {
} else if (isSerializedEditorGroupModel(from)) {
groupView = EditorGroupView.createFromSerialized(from, this, this.count, this.instantiationService);
} else {
groupView = EditorGroupView.createNew(this, this.count, this.instantiationService);
@@ -811,14 +807,14 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro
readonly snap = true;
get onDidChange(): Event<IViewSize | undefined> { return Event.any(this.centeredLayoutWidget.onDidChange, this.onDidSetGridWidget.event); }
override get onDidChange(): Event<IViewSize | undefined> { return Event.any(this.centeredLayoutWidget.onDidChange, this.onDidSetGridWidget.event); }
readonly priority: LayoutPriority = LayoutPriority.High;
private get gridSeparatorBorder(): Color {
return this.theme.getColor(EDITOR_GROUP_BORDER) || this.theme.getColor(contrastBorder) || Color.transparent;
}
updateStyles(): void {
override updateStyles(): void {
const container = assertIsDefined(this.container);
container.style.backgroundColor = this.getColor(editorBackground) || '';
@@ -827,7 +823,7 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro
this.centeredLayoutWidget.styles(separatorBorderStyle);
}
createContentArea(parent: HTMLElement, options?: IEditorPartCreationOptions): HTMLElement {
override createContentArea(parent: HTMLElement, options?: IEditorPartCreationOptions): HTMLElement {
// Container
this.element = parent;
@@ -835,13 +831,31 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro
this.container.classList.add('content');
parent.appendChild(this.container);
// Grid control with center layout
// Grid control
this.doCreateGridControl(options);
// Centered layout widget
this.centeredLayoutWidget = this._register(new CenteredViewLayout(this.container, this.gridWidgetView, this.globalMemento[EditorPart.EDITOR_PART_CENTERED_VIEW_STORAGE_KEY]));
// Drop support
this._register(this.createEditorDropTarget(this.container, Object.create(null)));
// Drag & Drop support
this.setupDragAndDropSupport(parent, this.container);
// Signal ready
this.whenReadyResolve?.();
// Signal restored
Promises.settled(this.groups.map(group => group.whenRestored)).finally(() => {
this.restored = true;
this.whenRestoredResolve?.();
});
return this.container;
}
private setupDragAndDropSupport(parent: HTMLElement, container: HTMLElement): void {
// Editor drop target
this._register(this.createEditorDropTarget(container, Object.create(null)));
// No drop in the editor
const overlay = document.createElement('div');
@@ -849,17 +863,11 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro
parent.appendChild(overlay);
// Hide the block if a mouse down event occurs #99065
this._register(addDisposableGenericMouseDownListner(overlay, () => {
overlay.classList.remove('visible');
}));
this._register(addDisposableGenericMouseDownListner(overlay, () => overlay.classList.remove('visible')));
this._register(CompositeDragAndDropObserver.INSTANCE.registerTarget(this.element, {
onDragStart: e => {
overlay.classList.add('visible');
},
onDragEnd: e => {
overlay.classList.remove('visible');
}
onDragStart: e => overlay.classList.add('visible'),
onDragEnd: e => overlay.classList.remove('visible')
}));
let panelOpenerTimeout: any;
@@ -920,8 +928,6 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro
}
}
}));
return this.container;
}
centerLayout(active: boolean): void {
@@ -955,13 +961,6 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro
this.doSetGroupActive(initialGroup);
}
// Signal restored
Promises.settled(this.groups.map(group => group.whenRestored)).finally(() => {
if (this.whenRestoredResolve) {
this.whenRestoredResolve();
}
});
// Update container
this.updateContainer();
@@ -1012,7 +1011,7 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro
// Create new
const groupViews: IEditorGroupView[] = [];
const gridWidget = SerializableGrid.deserialize(serializedGrid, {
fromJSON: (serializedEditorGroup: ISerializedEditorGroup | null) => {
fromJSON: (serializedEditorGroup: ISerializedEditorGroupModel | null) => {
let groupView: IEditorGroupView;
if (reuseGroupViews.length > 0) {
groupView = reuseGroupViews.shift()!;
@@ -1081,7 +1080,7 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro
this.centeredLayoutWidget.boundarySashes = sashes;
}
layout(width: number, height: number): void {
override layout(width: number, height: number): void {
// Layout contents
const contentAreaSize = super.layoutContents(width, height).contentSize;
@@ -1100,7 +1099,7 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro
this._onDidLayout.fire(dimension);
}
protected saveState(): void {
protected override saveState(): void {
// Persist grid UI state
if (this.gridWidget) {
@@ -1136,16 +1135,14 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro
};
}
dispose(): void {
override dispose(): void {
// Forward to all groups
this.groupViews.forEach(group => group.dispose());
this.groupViews.clear();
// Grid widget
if (this.gridWidget) {
this.gridWidget.dispose();
}
this.gridWidget?.dispose();
super.dispose();
}

View File

@@ -59,7 +59,7 @@ export abstract class BaseEditorQuickAccessProvider extends PickerQuickAccessPro
);
}
provide(picker: IQuickPick<IEditorQuickPickItem>, token: CancellationToken): IDisposable {
override provide(picker: IQuickPick<IEditorQuickPickItem>, token: CancellationToken): IDisposable {
// Reset the pick state for this run
this.pickState.reset(!!picker.quickNavigate);
@@ -172,7 +172,7 @@ export abstract class BaseEditorQuickAccessProvider extends PickerQuickAccessPro
if (group) {
await group.closeEditor(editor, { preserveFocus: true });
if (!group.isOpened(editor)) {
if (!group.contains(editor)) {
return TriggerAction.REMOVE_ITEM;
}
}

View File

@@ -13,7 +13,7 @@ import { URI } from 'vs/base/common/uri';
import { Action, WorkbenchActionExecutedClassification, WorkbenchActionExecutedEvent } from 'vs/base/common/actions';
import { Language } from 'vs/base/common/platform';
import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput';
import { IFileEditorInput, EncodingMode, IEncodingSupport, EditorResourceAccessor, SideBySideEditorInput, IEditorPane, IEditorInput, SideBySideEditor, IModeSupport } from 'vs/workbench/common/editor';
import { IFileEditorInput, EditorResourceAccessor, SideBySideEditorInput, IEditorPane, IEditorInput, SideBySideEditor } from 'vs/workbench/common/editor';
import { Disposable, MutableDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { IEditorAction } from 'vs/editor/common/editorCommon';
import { EndOfLineSequence } from 'vs/editor/common/model';
@@ -31,7 +31,7 @@ import { Selection } from 'vs/editor/common/core/selection';
import { TabFocus } from 'vs/editor/common/config/commonEditorConfig';
import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands';
import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { EncodingMode, IEncodingSupport, IModeSupport, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { SUPPORTED_ENCODINGS } from 'vs/workbench/services/textfile/common/encoding';
import { ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents';
import { ConfigurationChangedEvent, IEditorOptions, EditorOption } from 'vs/editor/common/config/editorOptions';
@@ -43,7 +43,7 @@ import { Schemas } from 'vs/base/common/network';
import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences';
import { IQuickInputService, IQuickPickItem, QuickPickInput } from 'vs/platform/quickinput/common/quickInput';
import { getIconClassesForModeId } from 'vs/editor/common/services/getIconClasses';
import { timeout } from 'vs/base/common/async';
import { Promises, timeout } from 'vs/base/common/async';
import { INotificationHandle, INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { Event } from 'vs/base/common/event';
import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility';
@@ -61,8 +61,8 @@ class SideBySideEditorEncodingSupport implements IEncodingSupport {
return this.primary.getEncoding(); // always report from modified (right hand) side
}
setEncoding(encoding: string, mode: EncodingMode): void {
[this.primary, this.secondary].forEach(editor => editor.setEncoding(encoding, mode));
async setEncoding(encoding: string, mode: EncodingMode): Promise<void> {
await Promises.settled([this.primary, this.secondary].map(editor => editor.setEncoding(encoding, mode)));
}
}
@@ -976,7 +976,7 @@ class ShowCurrentMarkerInStatusbarContribution extends Disposable {
return this.markers.find(marker => Range.containsPosition(marker, position)) || null;
}
private onMarkerChanged(changedResources: ReadonlyArray<URI>): void {
private onMarkerChanged(changedResources: readonly URI[]): void {
if (!this.editor) {
return;
}
@@ -1044,7 +1044,7 @@ export class ShowLanguageExtensionsAction extends Action {
this.enabled = galleryService.isEnabled();
}
async run(): Promise<void> {
override async run(): Promise<void> {
await this.commandService.executeCommand('workbench.extensions.action.showExtensionsForLanguage', this.fileExtension);
}
}
@@ -1069,7 +1069,7 @@ export class ChangeModeAction extends Action {
super(actionId, actionLabel);
}
async run(event: any, data: ITelemetryData): Promise<void> {
override async run(event: unknown, data?: ITelemetryData): Promise<void> {
const activeTextEditorControl = getCodeEditor(this.editorService.activeTextEditorControl);
if (!activeTextEditorControl) {
await this.quickInputService.pick([{ label: localize('noEditor', "No text editor active at this time") }]);
@@ -1094,7 +1094,7 @@ export class ChangeModeAction extends Action {
// All languages are valid picks
const languages = this.modeService.getRegisteredLanguageNames();
const picks: QuickPickInput[] = languages.sort().map((lang, index) => {
const picks: QuickPickInput[] = languages.sort().map(lang => {
const modeId = this.modeService.getModeIdForLanguageName(lang.toLowerCase()) || 'unknown';
const extensions = this.modeService.getExtensions(lang).join(' ');
let description: string;
@@ -1263,7 +1263,7 @@ export class ChangeEOLAction extends Action {
super(actionId, actionLabel);
}
async run(): Promise<void> {
override async run(): Promise<void> {
const activeTextEditorControl = getCodeEditor(this.editorService.activeTextEditorControl);
if (!activeTextEditorControl) {
await this.quickInputService.pick([{ label: localize('noEditor', "No text editor active at this time") }]);
@@ -1316,7 +1316,7 @@ export class ChangeEncodingAction extends Action {
super(actionId, actionLabel);
}
async run(): Promise<void> {
override async run(): Promise<void> {
const activeTextEditorControl = getCodeEditor(this.editorService.activeTextEditorControl);
if (!activeTextEditorControl) {
await this.quickInputService.pick([{ label: localize('noEditor', "No text editor active at this time") }]);
@@ -1434,7 +1434,7 @@ export class ChangeEncodingAction extends Action {
const activeEncodingSupport = toEditorWithEncodingSupport(this.editorService.activeEditorPane.input);
if (typeof encoding.id !== 'undefined' && activeEncodingSupport && activeEncodingSupport.getEncoding() !== encoding.id) {
activeEncodingSupport.setEncoding(encoding.id, isReopenWithEncoding ? EncodingMode.Decode : EncodingMode.Encode); // Set new encoding
await activeEncodingSupport.setEncoding(encoding.id, isReopenWithEncoding ? EncodingMode.Decode : EncodingMode.Encode); // Set new encoding
}
activeTextEditorControl.focus();

View File

@@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IEditorInput, IEditorInputFactoryRegistry, IEditorIdentifier, GroupIdentifier, Extensions, IEditorPartOptionsChangeEvent, EditorsOrder, EditorResourceAccessor, SideBySideEditor } from 'vs/workbench/common/editor';
import { IEditorInput, IEditorInputFactoryRegistry, IEditorIdentifier, GroupIdentifier, EditorExtensions, IEditorPartOptionsChangeEvent, EditorsOrder, SideBySideEditorInput } from 'vs/workbench/common/editor';
import { dispose, Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { Registry } from 'vs/platform/registry/common/platform';
@@ -12,6 +12,7 @@ import { IEditorGroupsService, IEditorGroup, GroupChangeKind, GroupsOrder } from
import { coalesce } from 'vs/base/common/arrays';
import { LinkedMap, Touch, ResourceMap } from 'vs/base/common/map';
import { equals } from 'vs/base/common/objects';
import { IResourceEditorInputIdentifier } from 'vs/platform/editor/common/editor';
import { URI } from 'vs/base/common/uri';
interface ISerializedEditorsList {
@@ -38,7 +39,7 @@ export class EditorsObserver extends Disposable {
private readonly keyMap = new Map<GroupIdentifier, Map<IEditorInput, IEditorIdentifier>>();
private readonly mostRecentEditorsMap = new LinkedMap<IEditorIdentifier, IEditorIdentifier>();
private readonly editorResourcesMap = new ResourceMap<number>();
private readonly editorsPerResourceCounter = new ResourceMap<Map<string /* type ID */, number /* counter */>>();
private readonly _onDidMostRecentlyActiveEditorsChange = this._register(new Emitter<void>());
readonly onDidMostRecentlyActiveEditorsChange = this._onDidMostRecentlyActiveEditorsChange.event;
@@ -51,8 +52,14 @@ export class EditorsObserver extends Disposable {
return [...this.mostRecentEditorsMap.values()];
}
hasEditor(resource: URI): boolean {
return this.editorResourcesMap.has(resource);
hasEditor(editor: IResourceEditorInputIdentifier): boolean {
const editors = this.editorsPerResourceCounter.get(editor.resource);
return editors?.has(editor.typeId) ?? false;
}
hasEditors(resource: URI): boolean {
return this.editorsPerResourceCounter.has(resource);
}
constructor(
@@ -69,7 +76,7 @@ export class EditorsObserver extends Disposable {
this._register(this.editorGroupsService.onDidAddGroup(group => this.onGroupAdded(group)));
this._register(this.editorGroupsService.onDidChangeEditorPartOptions(e => this.onDidChangeEditorPartOptions(e)));
this.editorGroupsService.whenRestored.then(() => this.loadState());
this.editorGroupsService.whenReady.then(() => this.loadState());
}
private onGroupAdded(group: IEditorGroup): void {
@@ -185,19 +192,48 @@ export class EditorsObserver extends Disposable {
}
private updateEditorResourcesMap(editor: IEditorInput, add: boolean): void {
const resource = EditorResourceAccessor.getCanonicalUri(editor, { supportSideBySide: SideBySideEditor.PRIMARY });
// Distill the editor resource and type id with support
// for side by side editor's primary side too.
let resource: URI | undefined = undefined;
let typeId: string | undefined = undefined;
if (editor instanceof SideBySideEditorInput) {
resource = editor.primary.resource;
typeId = editor.primary.typeId;
} else {
resource = editor.resource;
typeId = editor.typeId;
}
if (!resource) {
return; // require a resource
}
// Add entry
if (add) {
this.editorResourcesMap.set(resource, (this.editorResourcesMap.get(resource) ?? 0) + 1);
} else {
const counter = this.editorResourcesMap.get(resource) ?? 0;
if (counter > 1) {
this.editorResourcesMap.set(resource, counter - 1);
} else {
this.editorResourcesMap.delete(resource);
let editorsPerResource = this.editorsPerResourceCounter.get(resource);
if (!editorsPerResource) {
editorsPerResource = new Map<string, number>();
this.editorsPerResourceCounter.set(resource, editorsPerResource);
}
editorsPerResource.set(typeId, (editorsPerResource.get(typeId) ?? 0) + 1);
}
// Remove entry
else {
const editorsPerResource = this.editorsPerResourceCounter.get(resource);
if (editorsPerResource) {
const counter = editorsPerResource.get(typeId) ?? 0;
if (counter > 1) {
editorsPerResource.set(typeId, counter - 1);
} else {
editorsPerResource.delete(typeId);
if (editorsPerResource.size === 0) {
this.editorsPerResourceCounter.delete(resource);
}
}
}
}
}
@@ -344,7 +380,7 @@ export class EditorsObserver extends Disposable {
}
private serialize(): ISerializedEditorsList {
const registry = Registry.as<IEditorInputFactoryRegistry>(Extensions.EditorInputFactories);
const registry = Registry.as<IEditorInputFactoryRegistry>(EditorExtensions.EditorInputFactories);
const entries = [...this.mostRecentEditorsMap.values()];
const mapGroupToSerializableEditorsOfGroup = new Map<IEditorGroup, IEditorInput[]>();
@@ -362,9 +398,9 @@ export class EditorsObserver extends Disposable {
let serializableEditorsOfGroup = mapGroupToSerializableEditorsOfGroup.get(group);
if (!serializableEditorsOfGroup) {
serializableEditorsOfGroup = group.getEditors(EditorsOrder.SEQUENTIAL).filter(editor => {
const factory = registry.getEditorInputFactory(editor.getTypeId());
const editorSerializer = registry.getEditorInputSerializer(editor);
return factory?.canSerialize(editor);
return editorSerializer?.canSerialize(editor);
});
mapGroupToSerializableEditorsOfGroup.set(group, serializableEditorsOfGroup);
}
@@ -384,10 +420,8 @@ export class EditorsObserver extends Disposable {
private loadState(): void {
const serialized = this.storageService.get(EditorsObserver.STORAGE_KEY, StorageScope.WORKSPACE);
// Previous state:
// Previous state: Load editors map from persisted state
if (serialized) {
// Load editors map from persisted state
this.deserialize(JSON.parse(serialized));
}

View File

@@ -73,20 +73,19 @@
.monaco-workbench .part.editor > .content .editor-group-container > .editor-group-container-toolbar {
display: none;
height: 35px;
}
.monaco-workbench .part.editor > .content:not(.empty) .editor-group-container.empty > .editor-group-container-toolbar {
display: block;
}
.monaco-workbench .part.editor > .content .editor-group-container > .editor-group-container-toolbar .action-label {
display: block;
height: 35px;
line-height: 35px;
min-width: 28px;
background-size: 16px;
background-position: center center;
background-repeat: no-repeat;
.monaco-workbench .part.editor > .content .editor-group-container > .editor-group-container-toolbar .actions-container {
justify-content: flex-end;
}
.monaco-workbench .part.editor > .content .editor-group-container > .editor-group-container-toolbar .action-item {
margin-right: 6px;
}
/* Editor */

View File

@@ -97,6 +97,10 @@
height: 35px;
}
.monaco-workbench .part.editor > .content .editor-group-container > .title > .title-actions .action-item {
margin-right: 4px;
}
.monaco-workbench .part.editor > .content .editor-group-container.active > .title > .title-actions {
opacity: 1;
}

View File

@@ -281,6 +281,7 @@
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink > .monaco-icon-label > .monaco-icon-label-container {
text-overflow: clip;
flex: none;
}
.monaco-workbench.hc-black .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink > .monaco-icon-label > .monaco-icon-label-container {
@@ -295,6 +296,10 @@
width: 28px;
}
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab > .tab-actions > .monaco-action-bar {
width: 28px;
}
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.tab-actions-right.sizing-shrink > .tab-actions {
flex: 0;
overflow: hidden; /* let the tab actions be pushed out of view when sizing is set to shrink to make more room */
@@ -321,9 +326,16 @@
opacity: 1;
}
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab > .tab-actions .actions-container {
justify-content: center;
}
.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab > .tab-actions .action-label.codicon {
color: inherit;
font-size: 16px;
padding: 2px;
width: 16px;
height: 16px;
}
.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab.sticky.dirty > .tab-actions .action-label:not(:hover)::before,
@@ -346,13 +358,6 @@
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab > .tab-actions .action-label {
opacity: 0;
display: block;
height: 16px;
width: 16px;
background-size: 16px;
background-position: center center;
background-repeat: no-repeat;
margin-right: 0.5em;
}
/* Tab Actions: Off */
@@ -430,6 +435,10 @@
height: 35px;
}
.monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-item {
margin-right: 4px;
}
.monaco-workbench .part.editor > .content .editor-group-container > .title > .tabs-and-actions-container.wrapping .editor-actions {
/* When tabs are wrapped, position the editor actions at the end of the very last row */

View File

@@ -35,39 +35,6 @@
padding-right: 0; /* by default the icon label has a padding right and this isn't wanted when not showing tabs and not showing breadcrumbs */
}
/* Title Actions */
.monaco-workbench .part.editor > .content .editor-group-container > .title .title-actions .action-label:not(span),
.monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label:not(span) {
display: flex;
height: 35px;
min-width: 28px;
align-items: center;
justify-content: center;
background-size: 16px;
background-position: center center;
background-repeat: no-repeat;
}
.monaco-workbench.hc-black .part.editor > .content .editor-group-container > .title .title-actions .action-label,
.monaco-workbench.hc-black .part.editor > .content .editor-group-container > .title .editor-actions .action-label:not(.codicon) {
line-height: initial;
}
.monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label .label,
.monaco-workbench .part.editor > .content .editor-group-container > .title .title-actions .action-label .label {
display: none;
}
.monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label.codicon {
color: inherit;
}
.monaco-workbench .part.editor > .content .editor-group-container > .title .title-actions .action-label.disabled,
.monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label.disabled {
opacity: 0.4;
}
/* Drag and Drop */
.monaco-workbench .part.editor > .content .editor-group-container > .title {

View File

@@ -50,7 +50,7 @@ export class NoTabsTitleControl extends TitleControl {
this._register(addDisposableListener(this.editorLabel.element, EventType.CLICK, e => this.onTitleLabelClick(e)));
// Breadcrumbs
this.createBreadcrumbsControl(labelContainer, { showFileIcons: false, showSymbolIcons: true, showDecorationColors: false, breadcrumbsBackground: () => Color.transparent });
this.createBreadcrumbsControl(labelContainer, { showFileIcons: false, showSymbolIcons: true, showDecorationColors: false, breadcrumbsBackground: () => Color.transparent, showPlaceholder: false });
titleContainer.classList.toggle('breadcrumbs', Boolean(this.breadcrumbsControl));
this._register(toDisposable(() => titleContainer.classList.remove('breadcrumbs'))); // important to remove because the container is a shared dom node
@@ -195,7 +195,7 @@ export class NoTabsTitleControl extends TitleControl {
}
}
updateStyles(): void {
override updateStyles(): void {
this.redraw();
}
@@ -321,7 +321,7 @@ export class NoTabsTitleControl extends TitleControl {
}
}
protected prepareEditorActions(editorActions: IToolbarActions): { primaryEditorActions: IAction[], secondaryEditorActions: IAction[] } {
protected override prepareEditorActions(editorActions: IToolbarActions): { primaryEditorActions: IAction[], secondaryEditorActions: IAction[] } {
const isGroupActive = this.accessor.activeGroup === this.group;
// Group active: show all actions

View File

@@ -5,13 +5,13 @@
import { Dimension, $, clearNode } from 'vs/base/browser/dom';
import { Registry } from 'vs/platform/registry/common/platform';
import { EditorInput, EditorOptions, SideBySideEditorInput, IEditorControl, IEditorPane, IEditorOpenContext } from 'vs/workbench/common/editor';
import { EditorInput, EditorOptions, SideBySideEditorInput, IEditorControl, IEditorPane, IEditorOpenContext, EditorExtensions } from 'vs/workbench/common/editor';
import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { scrollbarShadow } from 'vs/platform/theme/common/colorRegistry';
import { IEditorRegistry, Extensions as EditorExtensions } from 'vs/workbench/browser/editor';
import { IEditorRegistry } from 'vs/workbench/browser/editor';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService';
import { SplitView, Sizing, Orientation } from 'vs/base/browser/ui/splitview/splitview';
@@ -34,15 +34,15 @@ export class SideBySideEditor extends EditorPane {
private get maximumSecondaryHeight() { return this.secondaryEditorPane ? this.secondaryEditorPane.maximumHeight : Number.POSITIVE_INFINITY; }
// these setters need to exist because this extends from EditorPane
set minimumWidth(value: number) { /* noop */ }
set maximumWidth(value: number) { /* noop */ }
set minimumHeight(value: number) { /* noop */ }
set maximumHeight(value: number) { /* noop */ }
override set minimumWidth(value: number) { /* noop */ }
override set maximumWidth(value: number) { /* noop */ }
override set minimumHeight(value: number) { /* noop */ }
override set maximumHeight(value: number) { /* noop */ }
get minimumWidth() { return this.minimumPrimaryWidth + this.minimumSecondaryWidth; }
get maximumWidth() { return this.maximumPrimaryWidth + this.maximumSecondaryWidth; }
get minimumHeight() { return this.minimumPrimaryHeight + this.minimumSecondaryHeight; }
get maximumHeight() { return this.maximumPrimaryHeight + this.maximumSecondaryHeight; }
override get minimumWidth() { return this.minimumPrimaryWidth + this.minimumSecondaryWidth; }
override get maximumWidth() { return this.maximumPrimaryWidth + this.maximumSecondaryWidth; }
override get minimumHeight() { return this.minimumPrimaryHeight + this.minimumSecondaryHeight; }
override get maximumHeight() { return this.maximumPrimaryHeight + this.maximumSecondaryHeight; }
protected primaryEditorPane?: EditorPane;
protected secondaryEditorPane?: EditorPane;
@@ -56,7 +56,7 @@ export class SideBySideEditor extends EditorPane {
private onDidCreateEditors = this._register(new Emitter<{ width: number; height: number; } | undefined>());
private _onDidChangeSizeConstraints = this._register(new Relay<{ width: number; height: number; } | undefined>());
readonly onDidChangeSizeConstraints = Event.any(this.onDidCreateEditors.event, this._onDidChangeSizeConstraints.event);
override readonly onDidChangeSizeConstraints = Event.any(this.onDidCreateEditors.event, this._onDidChangeSizeConstraints.event);
constructor(
@ITelemetryService telemetryService: ITelemetryService,
@@ -94,20 +94,20 @@ export class SideBySideEditor extends EditorPane {
this.updateStyles();
}
async setInput(newInput: EditorInput, options: EditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise<void> {
override async setInput(newInput: EditorInput, options: EditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise<void> {
const oldInput = this.input as SideBySideEditorInput;
await super.setInput(newInput, options, context, token);
return this.updateInput(oldInput, (newInput as SideBySideEditorInput), options, context, token);
}
setOptions(options: EditorOptions | undefined): void {
override setOptions(options: EditorOptions | undefined): void {
if (this.primaryEditorPane) {
this.primaryEditorPane.setOptions(options);
}
}
protected setEditorVisible(visible: boolean, group: IEditorGroup | undefined): void {
protected override setEditorVisible(visible: boolean, group: IEditorGroup | undefined): void {
if (this.primaryEditorPane) {
this.primaryEditorPane.setVisible(visible, group);
}
@@ -119,7 +119,7 @@ export class SideBySideEditor extends EditorPane {
super.setEditorVisible(visible, group);
}
clearInput(): void {
override clearInput(): void {
if (this.primaryEditorPane) {
this.primaryEditorPane.clearInput();
}
@@ -133,7 +133,7 @@ export class SideBySideEditor extends EditorPane {
super.clearInput();
}
focus(): void {
override focus(): void {
if (this.primaryEditorPane) {
this.primaryEditorPane.focus();
}
@@ -146,7 +146,7 @@ export class SideBySideEditor extends EditorPane {
splitview.layout(dimension.width);
}
getControl(): IEditorControl | undefined {
override getControl(): IEditorControl | undefined {
if (this.primaryEditorPane) {
return this.primaryEditorPane.getControl();
}
@@ -218,7 +218,7 @@ export class SideBySideEditor extends EditorPane {
);
}
updateStyles(): void {
override updateStyles(): void {
super.updateStyles();
if (this.primaryEditorContainer) {
@@ -246,7 +246,7 @@ export class SideBySideEditor extends EditorPane {
}
}
dispose(): void {
override dispose(): void {
this.disposeEditors();
super.dispose();

View File

@@ -6,7 +6,8 @@
import 'vs/css!./media/tabstitlecontrol';
import { isMacintosh, isWindows } from 'vs/base/common/platform';
import { shorten } from 'vs/base/common/labels';
import { EditorResourceAccessor, GroupIdentifier, IEditorInput, Verbosity, EditorCommandsContextActionRunner, IEditorPartOptions, SideBySideEditor, computeEditorAriaLabel } from 'vs/workbench/common/editor';
import { EditorResourceAccessor, GroupIdentifier, IEditorInput, Verbosity, EditorCommandsContextActionRunner, IEditorPartOptions, SideBySideEditor } from 'vs/workbench/common/editor';
import { computeEditorAriaLabel } from 'vs/workbench/browser/editor';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { EventType as TouchEventType, GestureEvent, Gesture } from 'vs/base/browser/touch';
import { KeyCode } from 'vs/base/common/keyCodes';
@@ -166,7 +167,7 @@ export class TabsTitleControl extends TitleControl {
const breadcrumbsContainer = document.createElement('div');
breadcrumbsContainer.classList.add('tabs-breadcrumbs');
this.titleContainer.appendChild(breadcrumbsContainer);
this.createBreadcrumbsControl(breadcrumbsContainer, { showFileIcons: true, showSymbolIcons: true, showDecorationColors: false, breadcrumbsBackground: breadcrumbsBackground });
this.createBreadcrumbsControl(breadcrumbsContainer, { showFileIcons: true, showSymbolIcons: true, showDecorationColors: false, showPlaceholder: true, breadcrumbsBackground: breadcrumbsBackground });
}
private createTabsScrollbar(scrollable: HTMLElement): ScrollableElement {
@@ -378,7 +379,7 @@ export class TabsTitleControl extends TitleControl {
this.layout(this.dimensions);
}
protected updateEditorActionsToolbar(): void {
protected override updateEditorActionsToolbar(): void {
super.updateEditorActionsToolbar();
// Changing the actions in the toolbar can have an impact on the size of the
@@ -570,7 +571,7 @@ export class TabsTitleControl extends TitleControl {
}
}
updateStyles(): void {
override updateStyles(): void {
this.redraw();
}
@@ -1771,7 +1772,7 @@ export class TabsTitleControl extends TitleControl {
return !isCopy || source === this.group.id;
}
dispose(): void {
override dispose(): void {
super.dispose();
this.tabDisposables = dispose(this.tabDisposables);

View File

@@ -9,7 +9,7 @@ import { isFunction, isObject, isArray, assertIsDefined, withUndefinedAsNull } f
import { IDiffEditor } from 'vs/editor/browser/editorBrowser';
import { IDiffEditorOptions, IEditorOptions as ICodeEditorOptions } from 'vs/editor/common/config/editorOptions';
import { BaseTextEditor, IEditorConfiguration } from 'vs/workbench/browser/parts/editor/textEditor';
import { TextEditorOptions, EditorInput, EditorOptions, TEXT_DIFF_EDITOR_ID, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions, ITextDiffEditorPane, IEditorInput, IEditorOpenContext } from 'vs/workbench/common/editor';
import { TextEditorOptions, EditorInput, EditorOptions, TEXT_DIFF_EDITOR_ID, IEditorInputFactoryRegistry, EditorExtensions, ITextDiffEditorPane, IEditorInput, IEditorOpenContext } from 'vs/workbench/common/editor';
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
import { DiffNavigator } from 'vs/editor/browser/widget/diffNavigator';
import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditorWidget';
@@ -43,7 +43,7 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditorPan
private diffNavigator: DiffNavigator | undefined;
private readonly diffNavigatorDisposables = this._register(new DisposableStore());
get scopedContextKeyService(): IContextKeyService | undefined {
override get scopedContextKeyService(): IContextKeyService | undefined {
const control = this.getControl();
if (!control) {
return undefined;
@@ -86,7 +86,7 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditorPan
}
}
protected onWillCloseEditorInGroup(editor: IEditorInput): void {
protected override onWillCloseEditorInGroup(editor: IEditorInput): void {
// React to editors closing to preserve or clear view state. This needs to happen
// in the onWillCloseEditor because at that time the editor has not yet
@@ -94,7 +94,7 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditorPan
this.doSaveOrClearTextDiffEditorViewState(editor);
}
getTitle(): string {
override getTitle(): string {
if (this.input) {
return this.input.getName();
}
@@ -102,11 +102,11 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditorPan
return localize('textDiffEditor', "Text Diff Editor");
}
createEditorControl(parent: HTMLElement, configuration: ICodeEditorOptions): IDiffEditor {
override createEditorControl(parent: HTMLElement, configuration: ICodeEditorOptions): IDiffEditor {
return this.instantiationService.createInstance(DiffEditorWidget, parent, configuration, {});
}
async setInput(input: EditorInput, options: EditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise<void> {
override async setInput(input: EditorInput, options: EditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise<void> {
// Dispose previous diff navigator
this.diffNavigatorDisposables.clear();
@@ -197,7 +197,7 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditorPan
const binaryDiffInput = this.instantiationService.createInstance(DiffEditorInput, input.getName(), input.getDescription(), originalInput, modifiedInput, true);
// Forward binary flag to input if supported
const fileEditorInputFactory = Registry.as<IEditorInputFactoryRegistry>(EditorInputExtensions.EditorInputFactories).getFileEditorInputFactory();
const fileEditorInputFactory = Registry.as<IEditorInputFactoryRegistry>(EditorExtensions.EditorInputFactories).getFileEditorInputFactory();
if (fileEditorInputFactory.isFileEditorInput(originalInput)) {
originalInput.setForceOpenAsBinary();
}
@@ -231,7 +231,7 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditorPan
return false;
}
protected computeConfiguration(configuration: IEditorConfiguration): ICodeEditorOptions {
protected override computeConfiguration(configuration: IEditorConfiguration): ICodeEditorOptions {
const editorConfiguration = super.computeConfiguration(configuration);
// Handle diff editor specially by merging in diffEditor configuration
@@ -252,7 +252,7 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditorPan
return editorConfiguration;
}
protected getConfigurationOverrides(): ICodeEditorOptions {
protected override getConfigurationOverrides(): ICodeEditorOptions {
const options: IDiffEditorOptions = super.getConfigurationOverrides();
options.readOnly = this.input instanceof DiffEditorInput && this.input.modifiedInput.isReadonly();
@@ -274,7 +274,7 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditorPan
return (<TextFileOperationError>error).textFileOperationResult === TextFileOperationResult.FILE_IS_BINARY;
}
clearInput(): void {
override clearInput(): void {
// Dispose previous diff navigator
this.diffNavigatorDisposables.clear();
@@ -296,15 +296,15 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditorPan
return this.diffNavigator;
}
getControl(): IDiffEditor | undefined {
override getControl(): IDiffEditor | undefined {
return super.getControl() as IDiffEditor | undefined;
}
protected loadTextEditorViewState(resource: URI): IDiffEditorViewState {
protected override loadTextEditorViewState(resource: URI): IDiffEditorViewState {
return super.loadTextEditorViewState(resource) as IDiffEditorViewState; // overridden for text diff editor support
}
protected saveState(): void {
protected override saveState(): void {
// Update/clear editor view State
this.doSaveOrClearTextDiffEditorViewState(this.input);
@@ -323,7 +323,7 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditorPan
}
// Clear view state if input is disposed or we are configured to not storing any state
if (input.isDisposed() || (!this.shouldRestoreTextEditorViewState(input) && (!this.group || !this.group.isOpened(input)))) {
if (input.isDisposed() || (!this.shouldRestoreTextEditorViewState(input) && (!this.group || !this.group.contains(input)))) {
super.clearTextEditorViewState([resource], this.group);
}
@@ -333,7 +333,7 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditorPan
}
}
protected retrieveTextEditorViewState(resource: URI): IDiffEditorViewState | null {
protected override retrieveTextEditorViewState(resource: URI): IDiffEditorViewState | null {
return this.retrieveTextDiffEditorViewState(resource); // overridden for text diff editor support
}

View File

@@ -10,7 +10,8 @@ import { Event } from 'vs/base/common/event';
import { isObject, assertIsDefined, withNullAsUndefined, isFunction } from 'vs/base/common/types';
import { Dimension } from 'vs/base/browser/dom';
import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';
import { EditorInput, EditorOptions, IEditorMemento, ITextEditorPane, TextEditorOptions, IEditorCloseEvent, IEditorInput, computeEditorAriaLabel, IEditorOpenContext, EditorResourceAccessor, SideBySideEditor } from 'vs/workbench/common/editor';
import { EditorInput, EditorOptions, IEditorMemento, ITextEditorPane, TextEditorOptions, IEditorCloseEvent, IEditorInput, IEditorOpenContext, EditorResourceAccessor, SideBySideEditor } from 'vs/workbench/common/editor';
import { computeEditorAriaLabel } from 'vs/workbench/browser/editor';
import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane';
import { IEditorViewState, IEditor, ScrollType } from 'vs/editor/common/editorCommon';
import { IStorageService } from 'vs/platform/storage/common/storage';
@@ -52,7 +53,7 @@ export abstract class BaseTextEditor extends EditorPane implements ITextEditorPa
protected get instantiationService(): IInstantiationService { return this._instantiationService; }
protected set instantiationService(value: IInstantiationService) { this._instantiationService = value; }
get scopedContextKeyService(): IContextKeyService | undefined {
override get scopedContextKeyService(): IContextKeyService | undefined {
return isCodeEditor(this.editorControl) ? this.editorControl.invokeWithinContext(accessor => accessor.get(IContextKeyService)) : undefined;
}
@@ -62,7 +63,7 @@ export abstract class BaseTextEditor extends EditorPane implements ITextEditorPa
@IInstantiationService instantiationService: IInstantiationService,
@IStorageService storageService: IStorageService,
@ITextResourceConfigurationService protected readonly textResourceConfigurationService: ITextResourceConfigurationService,
@IThemeService protected themeService: IThemeService,
@IThemeService themeService: IThemeService,
@IEditorService protected editorService: IEditorService,
@IEditorGroupsService protected editorGroupService: IEditorGroupsService
) {
@@ -158,7 +159,7 @@ export abstract class BaseTextEditor extends EditorPane implements ITextEditorPa
return this.instantiationService.createInstance(CodeEditorWidget, parent, configuration, {});
}
async setInput(input: EditorInput, options: EditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise<void> {
override async setInput(input: EditorInput, options: EditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise<void> {
await super.setInput(input, options, context, token);
// Update editor options after having set the input. We do this because there can be
@@ -170,7 +171,7 @@ export abstract class BaseTextEditor extends EditorPane implements ITextEditorPa
editorContainer.setAttribute('aria-label', this.computeAriaLabel());
}
setOptions(options: EditorOptions | undefined): void {
override setOptions(options: EditorOptions | undefined): void {
const textOptions = options as TextEditorOptions;
if (textOptions && isFunction(textOptions.apply)) {
const textEditor = assertIsDefined(this.getControl());
@@ -178,7 +179,7 @@ export abstract class BaseTextEditor extends EditorPane implements ITextEditorPa
}
}
protected setEditorVisible(visible: boolean, group: IEditorGroup | undefined): void {
protected override setEditorVisible(visible: boolean, group: IEditorGroup | undefined): void {
// Pass on to Editor
const editorControl = assertIsDefined(this.editorControl);
@@ -206,7 +207,7 @@ export abstract class BaseTextEditor extends EditorPane implements ITextEditorPa
// Subclasses can override
}
focus(): void {
override focus(): void {
// Pass on to Editor
const editorControl = assertIsDefined(this.editorControl);
@@ -220,7 +221,7 @@ export abstract class BaseTextEditor extends EditorPane implements ITextEditorPa
editorControl.layout(dimension);
}
getControl(): IEditor | undefined {
override getControl(): IEditor | undefined {
return this.editorControl;
}
@@ -338,7 +339,7 @@ export abstract class BaseTextEditor extends EditorPane implements ITextEditorPa
return undefined;
}
dispose(): void {
override dispose(): void {
this.lastAppliedEditorOptions = undefined;
super.dispose();

View File

@@ -45,7 +45,7 @@ export class AbstractTextResourceEditor extends BaseTextEditor {
super(id, telemetryService, instantiationService, storageService, textResourceConfigurationService, themeService, editorService, editorGroupService);
}
getTitle(): string | undefined {
override getTitle(): string | undefined {
if (this.input) {
return this.input.getName();
}
@@ -53,7 +53,7 @@ export class AbstractTextResourceEditor extends BaseTextEditor {
return localize('textEditor', "Text Editor");
}
async setInput(input: EditorInput, options: EditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise<void> {
override async setInput(input: EditorInput, options: EditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise<void> {
// Remember view settings if input changes
this.saveTextResourceEditorViewState(this.input);
@@ -119,7 +119,7 @@ export class AbstractTextResourceEditor extends BaseTextEditor {
}
}
clearInput(): void {
override clearInput(): void {
// Keep editor view state in settings to restore when coming back
this.saveTextResourceEditorViewState(this.input);
@@ -133,7 +133,7 @@ export class AbstractTextResourceEditor extends BaseTextEditor {
super.clearInput();
}
protected saveState(): void {
protected override saveState(): void {
// Save View State (only for untitled)
if (this.input instanceof UntitledTextEditorInput) {
@@ -180,7 +180,7 @@ export class TextResourceEditor extends AbstractTextResourceEditor {
super(TextResourceEditor.ID, telemetryService, instantiationService, storageService, textResourceConfigurationService, themeService, editorGroupService, editorService);
}
protected createEditorControl(parent: HTMLElement, configuration: IEditorOptions): IEditor {
protected override createEditorControl(parent: HTMLElement, configuration: IEditorOptions): IEditor {
const control = super.createEditorControl(parent, configuration);
// Install a listener for paste to update this editors

View File

@@ -38,6 +38,7 @@ import { IFileService } from 'vs/platform/files/common/files';
import { withNullAsUndefined, withUndefinedAsNull, assertIsDefined } from 'vs/base/common/types';
import { isFirefox } from 'vs/base/browser/browser';
import { ITextEditorOptions } from 'vs/platform/editor/common/editor';
import { isPromiseCanceledError } from 'vs/base/common/errors';
export interface IToolbarActions {
primary: IAction[];
@@ -151,12 +152,12 @@ export abstract class TitleControl extends Themable {
this._register(this.editorActionsToolbar.actionRunner.onDidRun(e => {
// Notify for Error
this.notificationService.error(e.error);
if (e.error && !isPromiseCanceledError(e.error)) {
this.notificationService.error(e.error);
}
// Log in telemetry
if (this.telemetryService) {
this.telemetryService.publicLog2<WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification>('workbenchActionExecuted', { id: e.action.id, from: 'editorPart' });
}
this.telemetryService.publicLog2<WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification>('workbenchActionExecuted', { id: e.action.id, from: 'editorPart' });
}));
}
@@ -385,13 +386,11 @@ export abstract class TitleControl extends Themable {
abstract updateOptions(oldOptions: IEditorPartOptions, newOptions: IEditorPartOptions): void;
abstract updateStyles(): void;
abstract layout(dimensions: ITitleControlDimensions): Dimension;
abstract getHeight(): IEditorGroupTitleHeight;
dispose(): void {
override dispose(): void {
dispose(this.breadcrumbsControl);
this.breadcrumbsControl = undefined;

View File

@@ -7,18 +7,16 @@ import * as dom from 'vs/base/browser/dom';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser';
import { localize } from 'vs/nls';
import { DEFAULT_FONT_FAMILY } from 'vs/workbench/browser/style';
import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { inputPlaceholderForeground } from 'vs/platform/theme/common/colorRegistry';
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { inputPlaceholderForeground, textLinkForeground } from 'vs/platform/theme/common/colorRegistry';
import { ChangeModeAction } from 'vs/workbench/browser/parts/editor/editorStatus';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry';
import { IEditorContribution } from 'vs/editor/common/editorCommon';
import { Schemas } from 'vs/base/common/network';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { FloatingClickWidget } from 'vs/workbench/browser/codeeditor';
import { ITASExperimentService } from 'vs/workbench/services/experiment/common/experimentService';
import { ConfigurationChangedEvent, EditorOption } from 'vs/editor/common/config/editorOptions';
const $ = dom.$;
const untitledHintSetting = 'workbench.editor.untitled.hint';
@@ -28,16 +26,15 @@ export class UntitledHintContribution implements IEditorContribution {
private toDispose: IDisposable[];
private untitledHintContentWidget: UntitledHintContentWidget | undefined;
private button: FloatingClickWidget | undefined;
private experimentTreatment: 'text' | 'button' | 'hidden' | undefined;
private experimentTreatment: 'text' | 'hidden' | undefined;
constructor(
private editor: ICodeEditor,
@ICommandService private readonly commandService: ICommandService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IKeybindingService private readonly keybindingService: IKeybindingService,
@IThemeService private readonly themeService: IThemeService,
@ITASExperimentService private readonly experimentService: ITASExperimentService
) {
this.toDispose = [];
this.toDispose.push(this.editor.onDidChangeModel(() => this.update()));
@@ -47,7 +44,7 @@ export class UntitledHintContribution implements IEditorContribution {
this.update();
}
}));
this.experimentService.getTreatment<'text' | 'button'>('untitledhint').then(treatment => {
this.experimentService.getTreatment<'text' | 'hidden'>('untitledhint').then(treatment => {
this.experimentTreatment = treatment;
this.update();
});
@@ -55,25 +52,13 @@ export class UntitledHintContribution implements IEditorContribution {
private update(): void {
this.untitledHintContentWidget?.dispose();
this.button?.dispose();
const configValue = this.configurationService.getValue<'text' | 'button' | 'hidden' | 'default'>(untitledHintSetting);
const untitledHintMode = configValue === 'default' ? (this.experimentTreatment || 'hidden') : configValue;
const configValue = this.configurationService.getValue<'text' | 'hidden' | 'default'>(untitledHintSetting);
const untitledHintMode = configValue === 'default' ? (this.experimentTreatment || 'text') : configValue;
const model = this.editor.getModel();
if (model && model.uri.scheme === Schemas.untitled && model.getModeId() === PLAINTEXT_MODE_ID) {
if (untitledHintMode === 'text') {
this.untitledHintContentWidget = new UntitledHintContentWidget(this.editor, this.commandService, this.configurationService, this.keybindingService);
}
if (untitledHintMode === 'button') {
this.button = new FloatingClickWidget(this.editor, localize('selectALanguage', "Select a Language"), ChangeModeAction.ID, this.keybindingService, this.themeService);
this.toDispose.push(this.button.onClick(async () => {
// Need to focus editor before so current editor becomes active and the command is properly executed
this.editor.focus();
await this.commandService.executeCommand(ChangeModeAction.ID, { from: 'button' });
this.editor.focus();
}));
this.button.render();
}
if (model && model.uri.scheme === Schemas.untitled && model.getModeId() === PLAINTEXT_MODE_ID && untitledHintMode === 'text') {
this.untitledHintContentWidget = new UntitledHintContentWidget(this.editor, this.commandService, this.configurationService);
}
}
@@ -94,10 +79,14 @@ class UntitledHintContentWidget implements IContentWidget {
private readonly editor: ICodeEditor,
private readonly commandService: ICommandService,
private readonly configurationService: IConfigurationService,
private readonly keybindingService: IKeybindingService
) {
this.toDispose = [];
this.toDispose.push(editor.onDidChangeModelContent(() => this.onDidChangeModelContent()));
this.toDispose.push(this.editor.onDidChangeConfiguration((e: ConfigurationChangedEvent) => {
if (this.domNode && e.hasChanged(EditorOption.fontInfo)) {
this.editor.applyFontInfo(this.domNode);
}
}));
this.onDidChangeModelContent();
}
@@ -118,17 +107,16 @@ class UntitledHintContentWidget implements IContentWidget {
if (!this.domNode) {
this.domNode = $('.untitled-hint');
this.domNode.style.width = 'max-content';
const language = $('span.language-mode.detected-link-active');
const keybinding = this.keybindingService.lookupKeybinding(ChangeModeAction.ID);
const keybindingLabel = keybinding?.getLabel();
const keybindingWithBrackets = keybindingLabel ? `(${keybindingLabel})` : '';
language.innerText = localize('selectAlanguage', "Select a language {0}", keybindingWithBrackets);
const language = $('a.language-mode');
language.style.cursor = 'pointer';
language.innerText = localize('selectAlanguage', "Select a language");
this.domNode.appendChild(language);
const toGetStarted = $('span');
toGetStarted.innerText = localize('toGetStarted', " to get started. Start typing to dismiss, or ",);
this.domNode.appendChild(toGetStarted);
const dontShow = $('span.detected-link-active');
const dontShow = $('a');
dontShow.style.cursor = 'pointer';
dontShow.innerText = localize('dontshow', "don't show");
this.domNode.appendChild(dontShow);
@@ -154,9 +142,9 @@ class UntitledHintContentWidget implements IContentWidget {
this.editor.focus();
}));
this.domNode.style.fontFamily = DEFAULT_FONT_FAMILY;
this.domNode.style.fontStyle = 'italic';
this.domNode.style.paddingLeft = '4px';
this.editor.applyFontInfo(this.domNode);
}
return this.domNode;
@@ -180,4 +168,8 @@ registerThemingParticipant((theme, collector) => {
if (inputPlaceholderForegroundColor) {
collector.addRule(`.monaco-editor .contentWidgets .untitled-hint { color: ${inputPlaceholderForegroundColor}; }`);
}
const textLinkForegroundColor = theme.getColor(textLinkForeground);
if (textLinkForegroundColor) {
collector.addRule(`.monaco-editor .contentWidgets .untitled-hint a { color: ${textLinkForegroundColor}; }`);
}
});

View File

@@ -3,15 +3,12 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-toolbar-container .action-label,
.monaco-workbench > .notifications-center > .notifications-center-header > .notifications-center-header-toolbar .action-label {
display: flex;
align-items: center;
width: 16px;
height: 22px;
.monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-toolbar-container .action-item,
.monaco-workbench > .notifications-center > .notifications-center-header > .notifications-center-header-toolbar .action-item {
margin-right: 4px;
margin-left: 4px;
color: inherit;
background-position: center;
background-repeat: no-repeat;
}
.monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-toolbar-container .action-item:first-child,
.monaco-workbench > .notifications-center > .notifications-center-header > .notifications-center-header-toolbar .action-item:first-child {
margin-left: 4px;
}

View File

@@ -37,4 +37,8 @@
.monaco-workbench > .notifications-center > .notifications-center-header > .notifications-center-header-toolbar {
flex: 1;
}
}
.monaco-workbench > .notifications-center > .notifications-center-header > .notifications-center-header-toolbar .actions-container {
justify-content: flex-end;
}

View File

@@ -37,7 +37,7 @@ export class ClearNotificationAction extends Action {
super(id, label, ThemeIcon.asClassName(clearIcon));
}
async run(notification: INotificationViewItem): Promise<void> {
override async run(notification: INotificationViewItem): Promise<void> {
this.commandService.executeCommand(CLEAR_NOTIFICATION, notification);
}
}
@@ -55,7 +55,7 @@ export class ClearAllNotificationsAction extends Action {
super(id, label, ThemeIcon.asClassName(clearAllIcon));
}
async run(): Promise<void> {
override async run(): Promise<void> {
this.commandService.executeCommand(CLEAR_ALL_NOTIFICATIONS);
}
}
@@ -73,7 +73,7 @@ export class HideNotificationsCenterAction extends Action {
super(id, label, ThemeIcon.asClassName(hideIcon));
}
async run(): Promise<void> {
override async run(): Promise<void> {
this.commandService.executeCommand(HIDE_NOTIFICATIONS_CENTER);
}
}
@@ -91,7 +91,7 @@ export class ExpandNotificationAction extends Action {
super(id, label, ThemeIcon.asClassName(expandIcon));
}
async run(notification: INotificationViewItem): Promise<void> {
override async run(notification: INotificationViewItem): Promise<void> {
this.commandService.executeCommand(EXPAND_NOTIFICATION, notification);
}
}
@@ -109,7 +109,7 @@ export class CollapseNotificationAction extends Action {
super(id, label, ThemeIcon.asClassName(collapseIcon));
}
async run(notification: INotificationViewItem): Promise<void> {
override async run(notification: INotificationViewItem): Promise<void> {
this.commandService.executeCommand(COLLAPSE_NOTIFICATION, notification);
}
}
@@ -122,7 +122,7 @@ export class ConfigureNotificationAction extends Action {
constructor(
id: string,
label: string,
public readonly configurationActions: ReadonlyArray<IAction>
public readonly configurationActions: readonly IAction[]
) {
super(id, label, ThemeIcon.asClassName(configureIcon));
}
@@ -141,7 +141,7 @@ export class CopyNotificationMessageAction extends Action {
super(id, label);
}
run(notification: INotificationViewItem): Promise<void> {
override run(notification: INotificationViewItem): Promise<void> {
return this.clipboardService.writeText(notification.message.raw);
}
}
@@ -149,12 +149,15 @@ export class CopyNotificationMessageAction extends Action {
interface NotificationActionMetrics {
id: string;
actionLabel: string;
source: string | undefined;
source: string;
silent: boolean;
}
type NotificationActionMetricsClassification = {
id: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
actionLabel: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
source: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
silent: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
};
export class NotificationActionRunner extends ActionRunner {
@@ -166,11 +169,11 @@ export class NotificationActionRunner extends ActionRunner {
super();
}
protected async runAction(action: IAction, context: INotificationViewItem | undefined): Promise<void> {
protected override async runAction(action: IAction, context: INotificationViewItem | undefined): Promise<void> {
this.telemetryService.publicLog2<WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification>('workbenchActionExecuted', { id: action.id, from: 'message' });
if (context) {
// If the context is not present it is a "global" notification action. Will be captured by other events
this.telemetryService.publicLog2<NotificationActionMetrics, NotificationActionMetricsClassification>('notification:actionExecuted', { id: hash(context.message.original.toString()).toString(), actionLabel: action.label, source: context.sourceId });
this.telemetryService.publicLog2<NotificationActionMetrics, NotificationActionMetricsClassification>('notification:actionExecuted', { id: hash(context.message.original.toString()).toString(), actionLabel: action.label, source: context.sourceId || 'core', silent: context.silent });
}
// Run and make sure to notify on any error again

View File

@@ -248,7 +248,7 @@ export class NotificationsCenter extends Themable implements INotificationsCente
}
}
protected updateStyles(): void {
protected override updateStyles(): void {
if (this.notificationsCenterContainer && this.notificationsCenterHeader) {
const widgetShadowColor = this.getColor(widgetShadow);
this.notificationsCenterContainer.style.boxShadow = widgetShadowColor ? `0 0 8px 2px ${widgetShadowColor}` : '';

View File

@@ -246,7 +246,7 @@ export class NotificationsList extends Themable {
return isAncestor(document.activeElement, this.listContainer);
}
protected updateStyles(): void {
protected override updateStyles(): void {
if (this.listContainer) {
const foreground = this.getColor(NOTIFICATIONS_FOREGROUND);
this.listContainer.style.color = foreground ? foreground : '';
@@ -271,7 +271,7 @@ export class NotificationsList extends Themable {
}
}
dispose(): void {
override dispose(): void {
this.hide();
super.dispose();

View File

@@ -21,7 +21,7 @@ import { Severity, NotificationsFilter } from 'vs/platform/notification/common/n
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { IntervalCounter, timeout } from 'vs/base/common/async';
import { IntervalCounter } from 'vs/base/common/async';
import { assertIsDefined } from 'vs/base/common/types';
interface INotificationToast {
@@ -93,8 +93,9 @@ export class NotificationsToasts extends Themable implements INotificationsToast
// Layout
this._register(this.layoutService.onDidLayout(dimension => this.layout(Dimension.lift(dimension))));
// Delay some tasks until after we can show notifications
this.onCanShowNotifications().then(() => {
// Delay some tasks until after we have restored
// to reduce UI pressure from the startup phase
this.lifecycleService.when(LifecyclePhase.Restored).then(() => {
// Show toast for initial notifications if any
this.model.notifications.forEach(notification => this.addToast(notification));
@@ -111,19 +112,6 @@ export class NotificationsToasts extends Themable implements INotificationsToast
}));
}
private async onCanShowNotifications(): Promise<void> {
// Wait for the running phase to ensure we can draw notifications properly
await this.lifecycleService.when(LifecyclePhase.Ready);
// Push notificiations out until either workbench is restored
// or some time has ellapsed to reduce pressure on the startup
return Promise.race([
this.lifecycleService.when(LifecyclePhase.Restored),
timeout(2000)
]);
}
private onDidChangeNotification(e: INotificationChangeEvent): void {
switch (e.kind) {
case NotificationChangeType.ADD:
@@ -476,7 +464,7 @@ export class NotificationsToasts extends Themable implements INotificationsToast
}
}
protected updateStyles(): void {
protected override updateStyles(): void {
this.mapNotificationToToast.forEach(({ toast }) => {
const backgroundColor = this.getColor(NOTIFICATIONS_BACKGROUND);
toast.style.background = backgroundColor ? backgroundColor : '';

View File

@@ -85,7 +85,7 @@ export class NotificationsListDelegate implements IListVirtualDelegate<INotifica
if (isNonEmptyArray(notification.actions && notification.actions.secondary)) {
actions++; // secondary actions
}
this.offsetHelper.style.width = `${450 /* notifications container width */ - (10 /* padding */ + 26 /* severity icon */ + (actions * 24) /* 24px per action */)}px`;
this.offsetHelper.style.width = `${450 /* notifications container width */ - (10 /* padding */ + 26 /* severity icon */ + (actions * (24 + 8)) /* 24px (+8px padding) per action */ - 4 /* 4px less padding for last action */)}px`;
// Render message into offset helper
const renderedMessage = NotificationMessageRenderer.render(notification.message);
@@ -371,7 +371,7 @@ export class NotificationTemplateRenderer extends Disposable {
private renderMessage(notification: INotificationViewItem): boolean {
clearNode(this.template.message);
this.template.message.appendChild(NotificationMessageRenderer.render(notification.message, {
callback: link => this.openerService.open(URI.parse(link)),
callback: link => this.openerService.open(URI.parse(link), { allowCommands: true }),
toDispose: this.inputDisposables
}));
@@ -444,7 +444,7 @@ export class NotificationTemplateRenderer extends Disposable {
if (notification.expanded && isNonEmptyArray(primaryActions)) {
const that = this;
const actionRunner: IActionRunner = new class extends ActionRunner {
protected async runAction(action: IAction): Promise<void> {
protected override async runAction(action: IAction): Promise<void> {
// Run action
that.actionRunner.run(action, notification);

View File

@@ -46,6 +46,14 @@
border-right-width: 0; /* no border when editor area is hiden */
}
.monaco-workbench .part.panel > .composite.title > .title-actions .monaco-action-bar .actions-container {
justify-content: flex-end;
}
.monaco-workbench .part.panel > .composite.title > .title-actions .monaco-action-bar .action-item {
margin-right: 4px;
}
.monaco-workbench .part.panel > .composite.title > .title-actions .monaco-action-bar .action-item .action-label {
outline-offset: -2px;
}
@@ -144,14 +152,24 @@
opacity: 1;
}
.monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item .action-label{
.monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item .action-label {
margin-right: 0;
padding: 2px;
}
.monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item .action-label:not(.codicon) {
background: none !important;
border-radius: 0;
}
.monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item:last-child {
padding-right: 10px;
}
.monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item:not(.checked) .action-label {
margin-bottom: 1px;
}
.monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item.checked .action-label {
border-bottom: 1px solid;
margin-right: 0;

View File

@@ -38,7 +38,7 @@ export class TogglePanelAction extends Action {
super(id, name, layoutService.isVisible(Parts.PANEL_PART) ? 'panel expanded' : 'panel');
}
async run(): Promise<void> {
override async run(): Promise<void> {
this.layoutService.setPanelHidden(this.layoutService.isVisible(Parts.PANEL_PART));
}
}
@@ -57,7 +57,7 @@ class FocusPanelAction extends Action {
super(id, label);
}
async run(): Promise<void> {
override async run(): Promise<void> {
// Show panel
if (!this.layoutService.isVisible(Parts.PANEL_PART)) {
@@ -113,7 +113,7 @@ export class SetPanelPositionAction extends Action {
super(id, label);
}
async run(): Promise<void> {
override async run(): Promise<void> {
const position = positionByActionId.get(this.id);
this.layoutService.setPanelPosition(position === undefined ? Position.BOTTOM : position);
}
@@ -128,7 +128,7 @@ export class PanelActivityAction extends ActivityAction {
super(activity);
}
async run(): Promise<void> {
override async run(): Promise<void> {
await this.panelService.openPanel(this.activity.id, true);
this.activate();
}
@@ -169,7 +169,7 @@ export class SwitchPanelViewAction extends Action {
super(id, name);
}
async run(offset: number): Promise<void> {
override async run(offset: number): Promise<void> {
const pinnedPanels = this.panelService.getPinnedPanels();
const activePanel = this.panelService.getActivePanel();
if (!activePanel) {
@@ -201,7 +201,7 @@ export class PreviousPanelViewAction extends SwitchPanelViewAction {
super(id, name, panelService);
}
run(): Promise<void> {
override run(): Promise<void> {
return super.run(-1);
}
}
@@ -219,7 +219,7 @@ export class NextPanelViewAction extends SwitchPanelViewAction {
super(id, name, panelService);
}
run(): Promise<void> {
override run(): Promise<void> {
return super.run(1);
}
}

View File

@@ -37,6 +37,7 @@ import { ViewContainer, IViewDescriptorService, IViewContainerModel, ViewContain
import { IPaneComposite } from 'vs/workbench/common/panecomposite';
import { Before2D, CompositeDragAndDropObserver, ICompositeDragAndDrop, toggleDropEffect } from 'vs/workbench/browser/dnd';
import { IActivity } from 'vs/workbench/common/activity';
import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget';
interface ICachedPanel {
id: string;
@@ -148,6 +149,10 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
this.compositeBar = this._register(this.instantiationService.createInstance(CompositeBar, this.getCachedPanels(), {
icon: false,
orientation: ActionsOrientation.HORIZONTAL,
activityHoverOptions: {
position: () => this.layoutService.getPanelPosition() === Position.BOTTOM && !this.layoutService.isPanelMaximized() ? HoverPosition.ABOVE : HoverPosition.BELOW,
delay: () => 0
},
openComposite: compositeId => this.openPanel(compositeId, true).then(panel => panel || null),
getActivityAction: compositeId => this.getCompositeActions(compositeId).activityAction,
getCompositePinnedAction: compositeId => this.getCompositeActions(compositeId).pinnedAction,
@@ -415,7 +420,7 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
this.layoutEmptyMessage();
}
create(parent: HTMLElement): void {
override create(parent: HTMLElement): void {
this.element = parent;
super.create(parent);
@@ -468,7 +473,7 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
}));
}
updateStyles(): void {
override updateStyles(): void {
super.updateStyles();
const container = assertIsDefined(this.getContainer());
@@ -562,7 +567,7 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
this.hideActiveComposite();
}
protected createTitleLabel(parent: HTMLElement): ICompositeTitleLabel {
protected override createTitleLabel(parent: HTMLElement): ICompositeTitleLabel {
const titleArea = this.compositeBar.create(parent);
titleArea.classList.add('panel-switcher-container');
@@ -579,7 +584,7 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
};
}
layout(width: number, height: number): void {
override layout(width: number, height: number): void {
if (!this.layoutService.isVisible(Parts.PANEL_PART)) {
return;
}
@@ -646,7 +651,7 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
return compositeActions;
}
protected removeComposite(compositeId: string): boolean {
protected override removeComposite(compositeId: string): boolean {
if (super.removeComposite(compositeId)) {
this.compositeBar.removeComposite(compositeId);
const compositeActions = this.compositeActions.get(compositeId);
@@ -857,16 +862,15 @@ registerThemingParticipant((theme, collector) => {
if (outline) {
collector.addRule(`
.monaco-workbench .part.panel > .title > .panel-switcher-container > .monaco-action-bar .action-item.checked .action-label,
.monaco-workbench .part.panel > .title > .panel-switcher-container > .monaco-action-bar .action-item .action-label:hover {
.monaco-workbench .part.panel > .title > .panel-switcher-container > .monaco-action-bar .action-item:hover .action-label {
outline-color: ${outline};
outline-width: 1px;
outline-style: solid;
border-bottom: none;
padding-bottom: 0;
outline-offset: 1px;
outline-offset: -2px;
}
.monaco-workbench .part.panel > .title > .panel-switcher-container > .monaco-action-bar .action-item:not(.checked) .action-label:hover {
.monaco-workbench .part.panel > .title > .panel-switcher-container > .monaco-action-bar .action-item:not(.checked):hover .action-label {
outline-style: dashed;
}
`);

View File

@@ -8,6 +8,14 @@
visibility: hidden !important;
}
.monaco-workbench .part.sidebar .title-actions .actions-container {
justify-content: flex-end;
}
.monaco-workbench .part.sidebar .title-actions .action-item {
margin-right: 4px;
}
.monaco-workbench .part.sidebar > .title > .title-label h2 {
text-transform: uppercase;
}

View File

@@ -155,7 +155,7 @@ export class SidebarPart extends CompositePart<Viewlet> implements IViewletServi
}));
}
create(parent: HTMLElement): void {
override create(parent: HTMLElement): void {
this.element = parent;
super.create(parent);
@@ -165,7 +165,7 @@ export class SidebarPart extends CompositePart<Viewlet> implements IViewletServi
this._register(focusTracker.onDidBlur(() => this.sideBarFocusContextKey.set(false)));
}
createTitleArea(parent: HTMLElement): HTMLElement {
override createTitleArea(parent: HTMLElement): HTMLElement {
const titleArea = super.createTitleArea(parent);
this._register(addDisposableListener(titleArea, EventType.CONTEXT_MENU, e => {
@@ -183,7 +183,7 @@ export class SidebarPart extends CompositePart<Viewlet> implements IViewletServi
return titleArea;
}
updateStyles(): void {
override updateStyles(): void {
super.updateStyles();
// Part container
@@ -203,7 +203,7 @@ export class SidebarPart extends CompositePart<Viewlet> implements IViewletServi
container.style.outlineColor = this.getColor(SIDE_BAR_DRAG_AND_DROP_BACKGROUND) ?? '';
}
layout(width: number, height: number): void {
override layout(width: number, height: number): void {
if (!this.layoutService.isVisible(Parts.SIDEBAR_PART)) {
return;
}
@@ -275,7 +275,7 @@ export class SidebarPart extends CompositePart<Viewlet> implements IViewletServi
return this.openComposite(id, focus) as Viewlet;
}
protected getTitleAreaDropDownAnchorAlignment(): AnchorAlignment {
protected override getTitleAreaDropDownAnchorAlignment(): AnchorAlignment {
return this.layoutService.getSideBarPosition() === SideBarPosition.LEFT ? AnchorAlignment.LEFT : AnchorAlignment.RIGHT;
}

View File

@@ -39,6 +39,9 @@ import { RawContextKey, IContextKeyService } from 'vs/platform/contextkey/common
import { ColorScheme } from 'vs/platform/theme/common/theme';
import { renderIcon, renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels';
import { syncing } from 'vs/platform/theme/common/iconRegistry';
import { Action2, registerAction2 } from 'vs/platform/actions/common/actions';
import { CATEGORIES } from 'vs/workbench/common/actions';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
interface IPendingStatusbarEntry {
id: string;
@@ -208,6 +211,11 @@ class StatusbarViewModel extends Disposable {
this.focusEntry(-1, this.entries.length - 1);
}
isEntryFocused(): boolean {
const focused = this._entries.find(entry => isAncestor(document.activeElement, entry.container));
return !!focused;
}
private focusEntry(delta: number, restartPosition: number): void {
const getVisibleEntry = (start: number) => {
let indexToFocus = start;
@@ -352,7 +360,7 @@ class ToggleStatusbarEntryVisibilityAction extends Action {
this.checked = !model.isHidden(id);
}
async run(): Promise<void> {
override async run(): Promise<void> {
if (this.model.isHidden(this.id)) {
this.model.show(this.id);
} else {
@@ -367,7 +375,7 @@ class HideStatusbarEntryAction extends Action {
super(id, localize('hide', "Hide '{0}'", name), undefined, true);
}
async run(): Promise<void> {
override async run(): Promise<void> {
this.model.hide(this.id);
}
}
@@ -496,6 +504,10 @@ export class StatusbarPart extends Part implements IStatusbarService {
this.viewModel.focusPreviousEntry();
}
isEntryFocused(): boolean {
return this.viewModel.isEntryFocused();
}
focus(preserveEntryFocus = true): void {
this.getContainer()?.focus();
const lastFocusedEntry = this.viewModel.lastFocusedEntry;
@@ -505,7 +517,7 @@ export class StatusbarPart extends Part implements IStatusbarService {
}
}
createContentArea(parent: HTMLElement): HTMLElement {
override createContentArea(parent: HTMLElement): HTMLElement {
this.element = parent;
// Track focus within container
@@ -516,7 +528,7 @@ export class StatusbarPart extends Part implements IStatusbarService {
this.leftItemsContainer = document.createElement('div');
this.leftItemsContainer.classList.add('left-items', 'items-container');
this.element.appendChild(this.leftItemsContainer);
this.element.tabIndex = -1;
this.element.tabIndex = 0;
// Right items container
this.rightItemsContainer = document.createElement('div');
@@ -646,7 +658,7 @@ export class StatusbarPart extends Part implements IStatusbarService {
return actions;
}
updateStyles(): void {
override updateStyles(): void {
super.updateStyles();
const container = assertIsDefined(this.getContainer());
@@ -692,7 +704,7 @@ export class StatusbarPart extends Part implements IStatusbarService {
return itemContainer;
}
layout(width: number, height: number): void {
override layout(width: number, height: number): void {
super.layout(width, height);
super.layoutContents(width, height);
}
@@ -724,7 +736,7 @@ class StatusBarCodiconLabel extends SimpleIconLabel {
}
}
set text(text: string) {
override set text(text: string) {
// Progress: insert progress codicon as first element as needed
// but keep it stable so that the animation does not reset
@@ -928,7 +940,7 @@ class StatusbarEntryItem extends Disposable {
}
}
dispose(): void {
override dispose(): void {
super.dispose();
dispose(this.foregroundListener);
@@ -1042,6 +1054,30 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
when: CONTEXT_STATUS_BAR_FOCUSED,
handler: (accessor: ServicesAccessor) => {
const statusBarService = accessor.get(IStatusbarService);
statusBarService.focus(false);
const editorService = accessor.get(IEditorService);
if (statusBarService.isEntryFocused()) {
statusBarService.focus(false);
} else if (editorService.activeEditorPane) {
editorService.activeEditorPane.focus();
}
}
});
class FocusStatusBarAction extends Action2 {
constructor() {
super({
id: 'workbench.action.focusStatusBar',
title: { value: localize('focusStatusBar', "Focus Status Bar"), original: 'Focus Status Bar' },
category: CATEGORIES.View,
f1: true
});
}
async run(accessor: ServicesAccessor): Promise<void> {
const layoutService = accessor.get(IWorkbenchLayoutService);
layoutService.focusPart(Parts.STATUSBAR_PART);
}
}
registerAction2(FocusStatusBarAction);

View File

@@ -253,11 +253,12 @@ export abstract class MenubarControl extends Disposable {
openable = { fileUri: uri };
}
const ret: IAction = new Action(commandId, unmnemonicLabel(label), undefined, undefined, (event) => {
const openInNewWindow = event && ((!isMacintosh && (event.ctrlKey || event.shiftKey)) || (isMacintosh && (event.metaKey || event.altKey)));
const ret: IAction = new Action(commandId, unmnemonicLabel(label), undefined, undefined, event => {
const browserEvent = event as KeyboardEvent;
const openInNewWindow = event && ((!isMacintosh && (browserEvent.ctrlKey || browserEvent.shiftKey)) || (isMacintosh && (browserEvent.metaKey || browserEvent.altKey)));
return this.hostService.openWindow([openable], {
forceNewWindow: openInNewWindow,
forceNewWindow: !!openInNewWindow,
remoteAuthority
});
});
@@ -313,13 +314,18 @@ export class CustomMenubarControl extends MenubarControl {
@IStorageService storageService: IStorageService,
@INotificationService notificationService: INotificationService,
@IPreferencesService preferencesService: IPreferencesService,
@IWorkbenchEnvironmentService protected readonly environmentService: IWorkbenchEnvironmentService,
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
@IAccessibilityService accessibilityService: IAccessibilityService,
@IThemeService private readonly themeService: IThemeService,
@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService,
<<<<<<< HEAD
@IHostService protected readonly hostService: IHostService,
@ICommandService commandService: ICommandService,
@ILogService private readonly logService: ILogService
=======
@IHostService hostService: IHostService,
@ICommandService commandService: ICommandService
>>>>>>> 58ce849223667f77dc0d6d7658870ca3f815e17f
) {
super(menuService, workspacesService, contextKeyService, keybindingService, configurationService, labelService, updateService, storageService, notificationService, preferencesService, environmentService, accessibilityService, hostService, commandService);
@@ -466,7 +472,7 @@ export class CustomMenubarControl extends MenubarControl {
case StateType.Idle:
return new Action('update.check', localize({ key: 'checkForUpdates', comment: ['&& denotes a mnemonic'] }, "Check for &&Updates..."), undefined, true, () =>
this.updateService.checkForUpdates(this.environmentService.sessionId));
this.updateService.checkForUpdates(true));
case StateType.CheckingForUpdates:
return new Action('update.checking', localize('checkingForUpdates', "Checking for Updates..."), undefined, false);
@@ -764,7 +770,7 @@ export class CustomMenubarControl extends MenubarControl {
};
}
protected onDidChangeWindowFocus(hasFocus: boolean): void {
protected override onDidChangeWindowFocus(hasFocus: boolean): void {
if (!this.visible) {
return;
}
@@ -783,7 +789,7 @@ export class CustomMenubarControl extends MenubarControl {
}
}
protected onUpdateStateChange(): void {
protected override onUpdateStateChange(): void {
if (!this.visible) {
return;
}
@@ -791,7 +797,7 @@ export class CustomMenubarControl extends MenubarControl {
super.onUpdateStateChange();
}
protected onDidChangeRecentlyOpened(): void {
protected override onDidChangeRecentlyOpened(): void {
if (!this.visible) {
return;
}
@@ -799,7 +805,7 @@ export class CustomMenubarControl extends MenubarControl {
super.onDidChangeRecentlyOpened();
}
protected onUpdateKeybindings(): void {
protected override onUpdateKeybindings(): void {
if (!this.visible) {
return;
}
@@ -807,7 +813,7 @@ export class CustomMenubarControl extends MenubarControl {
super.onUpdateKeybindings();
}
protected registerListeners(): void {
protected override registerListeners(): void {
super.registerListeners();
this._register(addDisposableListener(window, EventType.RESIZE, () => {

View File

@@ -42,6 +42,7 @@ import { IProductService } from 'vs/platform/product/common/productService';
import { Schemas } from 'vs/base/common/network';
import { withNullAsUndefined } from 'vs/base/common/types';
import { Codicon, iconRegistry } from 'vs/base/common/codicons';
import { getVirtualWorkspaceLocation } from 'vs/platform/remote/common/remoteHosts';
export class TitlebarPart extends Part implements ITitleService {
@@ -278,6 +279,19 @@ export class TitlebarPart extends Part implements ITitleService {
folder = withNullAsUndefined(this.contextService.getWorkspaceFolder(editorResource));
}
// Compute remote
// vscode-remtoe: use as is
// otherwise figure out if we have a virtual folder opened
let remoteName: string | undefined = undefined;
if (this.environmentService.remoteAuthority) {
remoteName = this.labelService.getHostLabel(Schemas.vscodeRemote, this.environmentService.remoteAuthority);
} else {
const virtualWorkspaceLocation = getVirtualWorkspaceLocation(workspace);
if (virtualWorkspaceLocation) {
remoteName = this.labelService.getHostLabel(virtualWorkspaceLocation.scheme, virtualWorkspaceLocation.authority);
}
}
// Variables
const activeEditorShort = editor ? editor.getTitle(Verbosity.SHORT) : '';
const activeEditorMedium = editor ? editor.getTitle(Verbosity.MEDIUM) : activeEditorShort;
@@ -291,7 +305,6 @@ export class TitlebarPart extends Part implements ITitleService {
const folderPath = folder ? this.labelService.getUriLabel(folder.uri) : '';
const dirty = editor?.isDirty() && !editor.isSaving() ? TitlebarPart.TITLE_DIRTY : '';
const appName = this.productService.nameLong;
const remoteName = this.labelService.getHostLabel(Schemas.vscodeRemote, this.environmentService.remoteAuthority);
const separator = this.configurationService.getValue<string>('window.titleSeparator');
const titleTemplate = this.configurationService.getValue<string>('window.title');
@@ -341,7 +354,7 @@ export class TitlebarPart extends Part implements ITitleService {
this._register(this.customMenubar.onVisibilityChange(e => this.onMenubarVisibilityChanged(e)));
}
createContentArea(parent: HTMLElement): HTMLElement {
override createContentArea(parent: HTMLElement): HTMLElement {
this.element = parent;
// App Icon (Native Windows/Linux and Web)
@@ -413,7 +426,7 @@ export class TitlebarPart extends Part implements ITitleService {
return this.element;
}
updateStyles(): void {
override updateStyles(): void {
super.updateStyles();
// Part container
@@ -514,7 +527,7 @@ export class TitlebarPart extends Part implements ITitleService {
}
}
layout(width: number, height: number): void {
override layout(width: number, height: number): void {
this.updateLayout(new Dimension(width, height));
super.layoutContents(width, height);

View File

@@ -18,13 +18,14 @@
.monaco-pane-view .pane > .pane-header > .actions.show {
display: initial;
}
.monaco-pane-view .pane > .pane-header .icon {
.monaco-pane-view .pane > .pane-header > .icon {
display: none;
width: 16px;
height: 16px;
}
.monaco-pane-view .pane.pane.horizontal:not(.expanded) > .pane-header .icon {
.monaco-pane-view .pane.pane.horizontal:not(.expanded) > .pane-header > .icon {
display: inline;
margin-top: 4px;
}

View File

@@ -131,6 +131,11 @@
overflow: hidden;
}
.customview-tree .monaco-list .monaco-list-row .custom-view-tree-node-item .monaco-icon-label-container::after {
content: '';
display: block;
}
.customview-tree .monaco-list .monaco-list-row .custom-view-tree-node-item > .custom-view-tree-node-item-icon {
background-size: 16px;
background-position: left center;
@@ -162,25 +167,12 @@
display: none;
}
.customview-tree .monaco-list .monaco-list-row .custom-view-tree-node-item .actions .action-label {
padding: 2px;
}
.customview-tree .monaco-list .monaco-list-row:hover .custom-view-tree-node-item .actions,
.customview-tree .monaco-list .monaco-list-row.selected .custom-view-tree-node-item .actions,
.customview-tree .monaco-list .monaco-list-row.focused .custom-view-tree-node-item .actions {
display: block;
}
.customview-tree .monaco-list .custom-view-tree-node-item .actions .action-label {
width: 16px;
height: 100%;
background-size: 16px;
background-position: 50% 50%;
background-repeat: no-repeat;
}
.customview-tree .monaco-list .custom-view-tree-node-item .actions .action-label.codicon {
line-height: 22px;
height: 22px;
}
.customview-tree .monaco-list .custom-view-tree-node-item .actions .action-label.codicon::before {
vertical-align: middle;
}

View File

@@ -54,6 +54,7 @@ import { API_OPEN_DIFF_EDITOR_COMMAND_ID, API_OPEN_EDITOR_COMMAND_ID } from 'vs/
import { Codicon } from 'vs/base/common/codicons';
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
import { Command } from 'vs/editor/common/modes';
import { isPromiseCanceledError } from 'vs/base/common/errors';
export class TreeViewPane extends ViewPane {
@@ -71,7 +72,7 @@ export class TreeViewPane extends ViewPane {
@IThemeService themeService: IThemeService,
@ITelemetryService telemetryService: ITelemetryService,
) {
super({ ...(options as IViewPaneOptions), titleMenuId: MenuId.ViewTitle }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService);
super({ ...(options as IViewPaneOptions), titleMenuId: MenuId.ViewTitle, donotForwardArgs: true }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService);
const { treeView } = (<ITreeViewDescriptor>Registry.as<IViewsRegistry>(Extensions.ViewsRegistry).getView(options.id));
this.treeView = treeView;
this._register(this.treeView.onDidChangeActions(() => this.updateActions(), this));
@@ -89,26 +90,26 @@ export class TreeViewPane extends ViewPane {
this.updateTreeVisibility();
}
focus(): void {
override focus(): void {
super.focus();
this.treeView.focus();
}
renderBody(container: HTMLElement): void {
override renderBody(container: HTMLElement): void {
super.renderBody(container);
this.renderTreeView(container);
}
shouldShowWelcome(): boolean {
override shouldShowWelcome(): boolean {
return ((this.treeView.dataProvider === undefined) || !!this.treeView.dataProvider.isTreeEmpty) && (this.treeView.message === undefined);
}
layoutBody(height: number, width: number): void {
override layoutBody(height: number, width: number): void {
super.layoutBody(height, width);
this.layoutTreeView(height, width);
}
getOptimalWidth(): number {
override getOptimalWidth(): number {
return this.treeView.getOptimalWidth();
}
@@ -1071,13 +1072,13 @@ class MultipleSelectionActionRunner extends ActionRunner {
constructor(notificationService: INotificationService, private getSelectedResources: (() => ITreeItem[])) {
super();
this._register(this.onDidRun(e => {
if (e.error) {
if (e.error && !isPromiseCanceledError(e.error)) {
notificationService.error(localize('command-error', 'Error running command {1}: {0}. This is likely caused by the extension that contributes {1}.', e.error.message, e.action.id));
}
}));
}
runAction(action: IAction, context: TreeViewItemHandleArg): Promise<void> {
override async runAction(action: IAction, context: TreeViewItemHandleArg): Promise<void> {
const selection = this.getSelectedResources();
let selectionHandleArgs: TreeViewItemHandleArg[] | undefined = undefined;
let actionInSelected: boolean = false;
@@ -1094,7 +1095,7 @@ class MultipleSelectionActionRunner extends ActionRunner {
selectionHandleArgs = undefined;
}
return action.run(...[context, selectionHandleArgs]);
await action.run(...[context, selectionHandleArgs]);
}
}
@@ -1164,7 +1165,7 @@ export class CustomTreeView extends TreeView {
super(id, title, themeService, instantiationService, commandService, configurationService, progressService, contextMenuService, keybindingService, notificationService, viewDescriptorService, hoverService, contextKeyService);
}
setVisibility(isVisible: boolean): void {
override setVisibility(isVisible: boolean): void {
super.setVisibility(isVisible);
if (this.visible) {
this.activate();

View File

@@ -46,6 +46,7 @@ export interface IViewPaneOptions extends IPaneOptions {
id: string;
showActionsAlways?: boolean;
titleMenuId?: MenuId;
donotForwardArgs?: boolean;
}
type WelcomeActionClassification = {
@@ -142,6 +143,7 @@ class ViewMenuActions extends CompositeMenuActions {
viewId: string,
menuId: MenuId,
contextMenuId: MenuId,
donotForwardArgs: boolean,
@IContextKeyService contextKeyService: IContextKeyService,
@IMenuService menuService: IMenuService,
@IViewDescriptorService viewDescriptorService: IViewDescriptorService,
@@ -149,7 +151,7 @@ class ViewMenuActions extends CompositeMenuActions {
const scopedContextKeyService = contextKeyService.createScoped(element);
scopedContextKeyService.createKey('view', viewId);
const viewLocationKey = scopedContextKeyService.createKey('viewLocation', ViewContainerLocationToString(viewDescriptorService.getViewLocationById(viewId)!));
super(menuId, contextMenuId, { shouldForwardArgs: true }, scopedContextKeyService, menuService);
super(menuId, contextMenuId, { shouldForwardArgs: !donotForwardArgs }, scopedContextKeyService, menuService);
this._register(scopedContextKeyService);
this._register(Event.filter(viewDescriptorService.onDidChangeLocation, e => e.views.some(view => view.id === viewId))(() => viewLocationKey.set(ViewContainerLocationToString(viewDescriptorService.getViewLocationById(viewId)!))));
}
@@ -225,17 +227,17 @@ export abstract class ViewPane extends Pane implements IView {
this._titleDescription = options.titleDescription;
this.showActionsAlways = !!options.showActionsAlways;
this.menuActions = this._register(this.instantiationService.createInstance(ViewMenuActions, this.element, this.id, options.titleMenuId || MenuId.ViewTitle, MenuId.ViewTitleContext));
this.menuActions = this._register(this.instantiationService.createInstance(ViewMenuActions, this.element, this.id, options.titleMenuId || MenuId.ViewTitle, MenuId.ViewTitleContext, !!options.donotForwardArgs));
this._register(this.menuActions.onDidChange(() => this.updateActions()));
this.viewWelcomeController = new ViewWelcomeController(this.id, contextKeyService);
}
get headerVisible(): boolean {
override get headerVisible(): boolean {
return super.headerVisible;
}
set headerVisible(visible: boolean) {
override set headerVisible(visible: boolean) {
super.headerVisible = visible;
this.element.classList.toggle('merged-header', !visible);
}
@@ -258,7 +260,7 @@ export abstract class ViewPane extends Pane implements IView {
return this._isVisible && this.isExpanded();
}
setExpanded(expanded: boolean): boolean {
override setExpanded(expanded: boolean): boolean {
const changed = super.setExpanded(expanded);
if (changed) {
this._onDidChangeBodyVisibility.fire(expanded);
@@ -270,7 +272,7 @@ export abstract class ViewPane extends Pane implements IView {
return changed;
}
render(): void {
override render(): void {
super.render();
const focusTracker = trackFocus(this.element);
@@ -314,7 +316,7 @@ export abstract class ViewPane extends Pane implements IView {
return expanded ? viewPaneContainerExpandedIcon : viewPaneContainerCollapsedIcon;
}
style(styles: IPaneStyles): void {
override style(styles: IPaneStyles): void {
super.style(styles);
const icon = this.getIcon();
@@ -552,7 +554,7 @@ export abstract class ViewPane extends Pane implements IView {
button.label = node.label;
button.onDidClick(_ => {
this.telemetryService.publicLog2<{ viewId: string, uri: string }, WelcomeActionClassification>('views.welcomeAction', { viewId: this.id, uri: node.href });
this.openerService.open(node.href);
this.openerService.open(node.href, { allowCommands: true });
}, null, disposables);
disposables.add(button);
disposables.add(attachButtonStyler(button, this.themeService));
@@ -603,8 +605,10 @@ export abstract class ViewPane extends Pane implements IView {
}
export abstract class ViewAction<T extends IView> extends Action2 {
constructor(readonly desc: Readonly<IAction2Options> & { viewId: string }) {
override readonly desc: Readonly<IAction2Options> & { viewId: string };
constructor(desc: Readonly<IAction2Options> & { viewId: string }) {
super(desc);
this.desc = desc;
}
run(accessor: ServicesAccessor, ...args: any[]) {

View File

@@ -96,7 +96,7 @@ class ViewPaneDropOverlay extends Themable {
private orientation: Orientation | undefined,
private bounds: BoundingRect | undefined,
protected location: ViewContainerLocation,
protected themeService: IThemeService,
themeService: IThemeService,
) {
super(themeService);
this.cleanupOverlayScheduler = this._register(new RunOnceScheduler(() => this.dispose(), 300));
@@ -134,7 +134,7 @@ class ViewPaneDropOverlay extends Themable {
this.updateStyles();
}
protected updateStyles(): void {
protected override updateStyles(): void {
// Overlay drop background
this.overlay.style.backgroundColor = this.getColor(this.location === ViewContainerLocation.Panel ? PANEL_SECTION_DRAG_AND_DROP_BACKGROUND : SIDE_BAR_DRAG_AND_DROP_BACKGROUND) || '';
@@ -287,7 +287,7 @@ class ViewPaneDropOverlay extends Themable {
return element === this.container || element === this.overlay;
}
dispose(): void {
override dispose(): void {
super.dispose();
this._disposed = true;
@@ -381,7 +381,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
@IContextMenuService protected contextMenuService: IContextMenuService,
@ITelemetryService protected telemetryService: ITelemetryService,
@IExtensionService protected extensionService: IExtensionService,
@IThemeService protected themeService: IThemeService,
@IThemeService themeService: IThemeService,
@IStorageService protected storageService: IStorageService,
@IWorkspaceContextService protected contextService: IWorkspaceContextService,
@IViewDescriptorService protected viewDescriptorService: IViewDescriptorService,
@@ -720,7 +720,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
return sizes;
}
saveState(): void {
override saveState(): void {
this.panes.forEach((view) => view.saveState());
this.storageService.store(this.visibleViewsStorageId, this.length, StorageScope.WORKSPACE, StorageTarget.USER);
}
@@ -1064,7 +1064,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
return true;
}
dispose(): void {
override dispose(): void {
super.dispose();
this.paneItems.forEach(i => i.disposable.dispose());
if (this.paneview) {
@@ -1074,8 +1074,10 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
}
export abstract class ViewPaneContainerAction<T extends IViewPaneContainer> extends Action2 {
constructor(readonly desc: Readonly<IAction2Options> & { viewPaneContainerId: string }) {
override readonly desc: Readonly<IAction2Options> & { viewPaneContainerId: string };
constructor(desc: Readonly<IAction2Options> & { viewPaneContainerId: string }) {
super(desc);
this.desc = desc;
}
run(accessor: ServicesAccessor, ...args: any[]) {

View File

@@ -121,7 +121,7 @@ export abstract class FilterViewPaneContainer extends ViewPaneContainer {
return views;
}
onDidAddViewDescriptors(added: IAddedViewDescriptorRef[]): ViewPane[] {
override onDidAddViewDescriptors(added: IAddedViewDescriptorRef[]): ViewPane[] {
const panes: ViewPane[] = super.onDidAddViewDescriptors(added);
for (let i = 0; i < added.length; i++) {
if (this.constantViewDescriptors.has(added[i].viewDescriptor.id)) {
@@ -135,6 +135,6 @@ export abstract class FilterViewPaneContainer extends ViewPaneContainer {
return panes;
}
abstract getTitle(): string;
abstract override getTitle(): string;
}

View File

@@ -5,7 +5,7 @@
import 'vs/css!./media/style';
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { iconForeground, foreground, selectionBackground, focusBorder, scrollbarShadow, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, listHighlightForeground, inputPlaceholderForeground } from 'vs/platform/theme/common/colorRegistry';
import { iconForeground, foreground, selectionBackground, focusBorder, scrollbarShadow, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, listHighlightForeground, inputPlaceholderForeground, toolbarHoverBackground, toolbarActiveBackground, toolbarHoverOutline } from 'vs/platform/theme/common/colorRegistry';
import { WORKBENCH_BACKGROUND, TITLE_BAR_ACTIVE_BACKGROUND } from 'vs/workbench/common/theme';
import { isWeb, isIOS, isMacintosh, isWindows } from 'vs/base/common/platform';
import { createMetaElement } from 'vs/base/browser/dom';
@@ -182,6 +182,39 @@ registerThemingParticipant((theme, collector) => {
if (isIOS && isStandalone) {
collector.addRule(`body { background-color: ${workbenchBackground}; }`);
}
// Action bars
const toolbarHoverBackgroundColor = theme.getColor(toolbarHoverBackground);
if (toolbarHoverBackgroundColor) {
collector.addRule(`
.monaco-action-bar:not(.vertical) .action-label:not(.disabled):hover {
background-color: ${toolbarHoverBackgroundColor};
}
.monaco-action-bar:not(.vertical) .monaco-dropdown-with-primary:not(.disabled):hover {
background-color: ${toolbarHoverBackgroundColor};
}
`);
}
const toolbarActiveBackgroundColor = theme.getColor(toolbarActiveBackground);
if (toolbarActiveBackgroundColor) {
collector.addRule(`
.monaco-action-bar:not(.vertical) .action-item.active .action-label:not(.disabled),
.monaco-action-bar:not(.vertical) .monaco-dropdown.active .action-label:not(.disabled) {
background-color: ${toolbarActiveBackgroundColor};
}
`);
}
const toolbarHoverOutlineColor = theme.getColor(toolbarHoverOutline);
if (toolbarHoverOutlineColor) {
collector.addRule(`
.monaco-action-bar:not(.vertical) .action-item .action-label:hover:not(.disabled) {
outline: 1px dashed ${toolbarHoverOutlineColor};
outline-offset: -1px;
}
`);
}
});
/**

View File

@@ -22,12 +22,12 @@ export abstract class Viewlet extends PaneComposite implements IViewlet {
constructor(id: string,
@ITelemetryService telemetryService: ITelemetryService,
@IStorageService protected storageService: IStorageService,
@IInstantiationService protected instantiationService: IInstantiationService,
@IStorageService storageService: IStorageService,
@IInstantiationService instantiationService: IInstantiationService,
@IThemeService themeService: IThemeService,
@IContextMenuService protected contextMenuService: IContextMenuService,
@IExtensionService protected extensionService: IExtensionService,
@IWorkspaceContextService protected contextService: IWorkspaceContextService,
@IContextMenuService contextMenuService: IContextMenuService,
@IExtensionService extensionService: IExtensionService,
@IWorkspaceContextService contextService: IWorkspaceContextService,
@IWorkbenchLayoutService protected layoutService: IWorkbenchLayoutService,
@IConfigurationService protected configurationService: IConfigurationService
) {

View File

@@ -61,6 +61,9 @@ import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/ur
import { UriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentityService';
import { BrowserWindow } from 'vs/workbench/browser/window';
import { ITimerService } from 'vs/workbench/services/timer/browser/timerService';
import { WorkspaceTrustManagementService } from 'vs/workbench/services/workspaces/common/workspaceTrust';
import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust';
import { HTMLFileSystemProvider } from 'vs/platform/files/browser/htmlFileSystemProvider';
class BrowserMain extends Disposable {
@@ -80,10 +83,9 @@ class BrowserMain extends Disposable {
}
async open(): Promise<IWorkbench> {
const services = await this.initServices();
await domContentLoaded();
mark('code/willStartWorkbench');
// Init services and wait for DOM to be ready in parallel
const [services] = await Promise.all([this.initServices(), domContentLoaded()]);
// Create Workbench
const workbench = new Workbench(this.domElement, services.serviceCollection, services.logService);
@@ -132,7 +134,7 @@ class BrowserMain extends Disposable {
}
}));
this._register(workbench.onWillShutdown(() => storageService.close()));
this._register(workbench.onShutdown(() => this.dispose()));
this._register(workbench.onDidShutdown(() => this.dispose()));
}
private async initServices(): Promise<{ serviceCollection: ServiceCollection, configurationService: IWorkbenchConfigurationService, logService: ILogService, storageService: BrowserStorageService }> {
@@ -202,6 +204,14 @@ class BrowserMain extends Disposable {
})
]);
// Workspace Trust Service
const workspaceTrustManagementService = new WorkspaceTrustManagementService(configurationService, environmentService, storageService, uriIdentityService, configurationService);
serviceCollection.set(IWorkspaceTrustManagementService, workspaceTrustManagementService);
// Update workspace trust so that configuration is updated accordingly
configurationService.updateWorkspaceTrust(workspaceTrustManagementService.isWorkpaceTrusted());
this._register(workspaceTrustManagementService.onDidChangeTrust(() => configurationService.updateWorkspaceTrust(workspaceTrustManagementService.isWorkpaceTrusted())));
// Request Service
const requestService = new BrowserRequestService(remoteAgentService, configurationService, logService);
serviceCollection.set(IRequestService, requestService);
@@ -271,7 +281,16 @@ class BrowserMain extends Disposable {
} catch (error) {
onUnexpectedError(error);
}
fileService.registerProvider(Schemas.userData, indexedDBUserDataProvider || new InMemoryFileSystemProvider());
let userDataProvider: IFileSystemProvider | undefined;
if (indexedDBUserDataProvider) {
userDataProvider = indexedDBUserDataProvider;
} else {
logService.info('using in-memory user data provider');
userDataProvider = new InMemoryFileSystemProvider();
}
fileService.registerProvider(Schemas.userData, userDataProvider);
if (indexedDBUserDataProvider) {
registerAction2(class ResetUserDataAction extends Action2 {
@@ -301,6 +320,9 @@ class BrowserMain extends Disposable {
}
});
}
fileService.registerProvider(Schemas.file, new HTMLFileSystemProvider());
fileService.registerProvider(Schemas.tmp, new InMemoryFileSystemProvider());
}
private async createStorageService(payload: IWorkspaceInitializationPayload, environmentService: IWorkbenchEnvironmentService, fileService: IFileService, logService: ILogService): Promise<BrowserStorageService> {

View File

@@ -12,6 +12,7 @@ import { Disposable } from 'vs/base/common/lifecycle';
import { Schemas } from 'vs/base/common/network';
import { isIOS, isMacintosh } from 'vs/base/common/platform';
import Severity from 'vs/base/common/severity';
import { URI } from 'vs/base/common/uri';
import { localize } from 'vs/nls';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { registerWindowDriver } from 'vs/platform/driver/browser/driver';
@@ -115,7 +116,7 @@ export class BrowserWindow extends Disposable {
private create(): void {
// Driver
if (this.environmentService.options?.driver) {
if (this.environmentService.options?.developmentOptions?.enableSmokeTestDriver) {
(async () => this._register(await registerWindowDriver()))();
}
@@ -138,7 +139,17 @@ export class BrowserWindow extends Disposable {
this.openerService.setDefaultExternalOpener({
openExternal: async (href: string) => {
if (matchesScheme(href, Schemas.http) || matchesScheme(href, Schemas.https)) {
windowOpenNoOpener(href);
const opened = windowOpenNoOpener(href);
if (!opened) {
const showResult = await this.dialogService.show(Severity.Warning, localize('unableToOpenExternal', "The browser interrupted the opening of a new tab or window. Press 'Open' to open it anyway."),
[localize('open', "Open"), localize('learnMore', "Learn More"), localize('cancel', "Cancel")], { cancelId: 2, detail: href });
if (showResult.choice === 0) {
windowOpenNoOpener(href);
}
if (showResult.choice === 1) {
await this.openerService.open(URI.parse('https://aka.ms/allow-vscode-popup'));
}
}
} else {
this.lifecycleService.withExpectedUnload(() => window.location.href = href);
}

View File

@@ -9,10 +9,14 @@ import { IConfigurationRegistry, Extensions as ConfigurationExtensions, Configur
import { isMacintosh, isWindows, isLinux, isWeb, isNative } from 'vs/base/common/platform';
import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration';
import { isStandalone } from 'vs/base/browser/browser';
import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
import { ITASExperimentService } from 'vs/workbench/services/experiment/common/experimentService';
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
const registry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
// Configuration
(function registerConfiguration(): void {
const registry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
// Workbench
registry.registerConfiguration({
@@ -88,7 +92,7 @@ import { isStandalone } from 'vs/base/browser/browser';
},
'workbench.editor.untitled.hint': {
'type': 'string',
'enum': ['text', 'button', 'hidden', 'default'],
'enum': ['text', 'hidden', 'default'],
'default': 'default',
'markdownDescription': localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'untitledHint' }, "Controls if the untitled hint should be inline text in the editor or a floating button or hidden.")
},
@@ -511,3 +515,23 @@ import { isStandalone } from 'vs/base/browser/browser';
}
});
})();
class ExperimentalCustomHoverConfigContribution implements IWorkbenchContribution {
constructor(@ITASExperimentService tasExperimentService: ITASExperimentService) {
tasExperimentService.getTreatment<boolean>('customHovers').then(useCustomHoversAsDefault => {
registry.registerConfiguration({
...workbenchConfigurationNodeBase,
'properties': {
'workbench.experimental.useCustomHover': {
'type': 'boolean',
'description': localize('workbench.experimental.useCustomHover', "Enable/disable custom hovers on Activity Bar & Panel. Note this configuration is experimental and subjected to be removed at any time."),
'default': !!useCustomHoversAsDefault
}
}
});
});
}
}
const workbenchRegistry = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench);
workbenchRegistry.registerWorkbenchContribution(ExperimentalCustomHoverConfigContribution, LifecyclePhase.Starting);

View File

@@ -6,14 +6,14 @@
import 'vs/workbench/browser/style';
import { localize } from 'vs/nls';
import { Event, Emitter, setGlobalLeakWarningThreshold } from 'vs/base/common/event';
import { runWhenIdle } from 'vs/base/common/async';
import { RunOnceScheduler, runWhenIdle, timeout } from 'vs/base/common/async';
import { getZoomLevel, isFirefox, isSafari, isChrome, getPixelRatio } from 'vs/base/browser/browser';
import { mark } from 'vs/base/common/performance';
import { onUnexpectedError, setUnexpectedErrorHandler } from 'vs/base/common/errors';
import { Registry } from 'vs/platform/registry/common/platform';
import { isWindows, isLinux, isWeb, isNative, isMacintosh } from 'vs/base/common/platform';
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
import { IEditorInputFactoryRegistry, Extensions as EditorExtensions } from 'vs/workbench/common/editor';
import { IEditorInputFactoryRegistry, EditorExtensions } from 'vs/workbench/common/editor';
import { getSingletonServiceDescriptors } from 'vs/platform/instantiation/common/extensions';
import { Position, Parts, IWorkbenchLayoutService, positionToString } from 'vs/workbench/services/layout/browser/layoutService';
import { IStorageService, WillSaveStateReason, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
@@ -48,8 +48,8 @@ export class Workbench extends Layout {
private readonly _onWillShutdown = this._register(new Emitter<WillShutdownEvent>());
readonly onWillShutdown = this._onWillShutdown.event;
private readonly _onShutdown = this._register(new Emitter<void>());
readonly onShutdown = this._onShutdown.event;
private readonly _onDidShutdown = this._register(new Emitter<void>());
readonly onDidShutdown = this._onDidShutdown.event;
constructor(
parent: HTMLElement,
@@ -58,6 +58,9 @@ export class Workbench extends Layout {
) {
super(parent);
// Perf: measure workbench startup time
mark('code/willStartWorkbench');
this.registerErrorHandler(logService);
}
@@ -128,7 +131,7 @@ export class Workbench extends Layout {
// Services
const instantiationService = this.initServices(this.serviceCollection);
instantiationService.invokeFunction(async accessor => {
instantiationService.invokeFunction(accessor => {
const lifecycleService = accessor.get(ILifecycleService);
const storageService = accessor.get(IStorageService);
const configurationService = accessor.get(IConfigurationService);
@@ -157,11 +160,7 @@ export class Workbench extends Layout {
this.layout();
// Restore
try {
await this.restoreWorkbench(accessor.get(ILogService), lifecycleService);
} catch (error) {
onUnexpectedError(error);
}
this.restore(lifecycleService);
});
return instantiationService;
@@ -216,12 +215,7 @@ export class Workbench extends Layout {
return instantiationService;
}
private registerListeners(
lifecycleService: ILifecycleService,
storageService: IStorageService,
configurationService: IConfigurationService,
hostService: IHostService
): void {
private registerListeners(lifecycleService: ILifecycleService, storageService: IStorageService, configurationService: IConfigurationService, hostService: IHostService): void {
// Configuration changes
this._register(configurationService.onDidChangeConfiguration(() => this.setFontAliasing(configurationService)));
@@ -240,8 +234,8 @@ export class Workbench extends Layout {
// Lifecycle
this._register(lifecycleService.onBeforeShutdown(event => this._onBeforeShutdown.fire(event)));
this._register(lifecycleService.onWillShutdown(event => this._onWillShutdown.fire(event)));
this._register(lifecycleService.onShutdown(() => {
this._onShutdown.fire();
this._register(lifecycleService.onDidShutdown(() => {
this._onDidShutdown.fire();
this.dispose();
}));
@@ -250,7 +244,11 @@ export class Workbench extends Layout {
// the chance of loosing any state.
// The window loosing focus is a good indication that the user has stopped working
// in that window so we pick that at a time to collect state.
this._register(hostService.onDidChangeFocus(focus => { if (!focus) { storageService.flush(); } }));
this._register(hostService.onDidChangeFocus(focus => {
if (!focus) {
storageService.flush();
}
}));
}
private fontAliasing: 'default' | 'antialiased' | 'none' | 'auto' | undefined;
@@ -361,7 +359,7 @@ export class Workbench extends Layout {
}
private createPart(id: string, role: string, classes: string[]): HTMLElement {
const part = document.createElement(role === 'status' ? 'footer' : 'div'); // Use footer element for status bar #98376
const part = document.createElement(role === 'status' ? 'footer' /* Use footer element for status bar #98376 */ : 'div');
part.classList.add('part', ...classes);
part.id = id;
part.setAttribute('role', role);
@@ -400,35 +398,54 @@ export class Workbench extends Layout {
});
}
private async restoreWorkbench(
logService: ILogService,
lifecycleService: ILifecycleService
): Promise<void> {
// Emit a warning after 10s if restore does not complete
const restoreTimeoutHandle = setTimeout(() => logService.warn('Workbench did not finish loading in 10 seconds, that might be a problem that should be reported.'), 10000);
private restore(lifecycleService: ILifecycleService): void {
// Ask each part to restore
try {
await super.restoreWorkbenchLayout();
clearTimeout(restoreTimeoutHandle);
this.restoreParts();
} catch (error) {
onUnexpectedError(error);
} finally {
// Set lifecycle phase to `Restored`
lifecycleService.phase = LifecyclePhase.Restored;
// Set lifecycle phase to `Eventually` after a short delay and when idle (min 2.5sec, max 5sec)
setTimeout(() => {
this._register(runWhenIdle(() => lifecycleService.phase = LifecyclePhase.Eventually, 2500));
}, 2500);
// Telemetry: startup metrics
mark('code/didStartWorkbench');
// Perf reporting (devtools)
performance.measure('perf: workbench create & restore', 'code/didLoadWorkbenchMain', 'code/didStartWorkbench');
}
// Transition into restored phase after layout has restored
// but do not wait indefinitly on this to account for slow
// editors restoring. Since the workbench is fully functional
// even when the visible editors have not resolved, we still
// want contributions on the `Restored` phase to work before
// slow editors have resolved. But we also do not want fast
// editors to resolve slow when too many contributions get
// instantiated, so we find a middle ground solution via
// `Promise.race`
this.whenReady.finally(() =>
Promise.race([
this.whenRestored,
timeout(2000)
]).finally(() => {
// Set lifecycle phase to `Restored`
lifecycleService.phase = LifecyclePhase.Restored;
// Set lifecycle phase to `Eventually` after a short delay and when idle (min 2.5sec, max 5sec)
const eventuallyPhaseScheduler = this._register(new RunOnceScheduler(() => {
this._register(runWhenIdle(() => lifecycleService.phase = LifecyclePhase.Eventually, 2500));
}, 2500));
eventuallyPhaseScheduler.schedule();
// Update perf marks only when the layout is fully
// restored. We want the time it takes to restore
// editors to be included in these numbers
function markDidStartWorkbench() {
mark('code/didStartWorkbench');
performance.measure('perf: workbench create & restore', 'code/didLoadWorkbenchMain', 'code/didStartWorkbench');
}
if (this.isRestored()) {
markDidStartWorkbench();
} else {
this.whenRestored.finally(() => markDidStartWorkbench());
}
})
);
}
}