Merge from vscode 2e5312cd61ff99c570299ecc122c52584265eda2

This commit is contained in:
ADS Merger
2020-04-23 02:50:35 +00:00
committed by Anthony Dresser
parent 3603f55d97
commit 7f1d8fc32f
659 changed files with 22709 additions and 12497 deletions

View File

@@ -11,12 +11,14 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { MainThreadAuthenticationProvider } from 'vs/workbench/api/browser/mainThreadAuthentication';
import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
export const IAuthenticationService = createDecorator<IAuthenticationService>('IAuthenticationService');
export interface IAuthenticationService {
_serviceBrand: undefined;
isAuthenticationProviderRegistered(id: string): boolean;
registerAuthenticationProvider(id: string, provider: MainThreadAuthenticationProvider): void;
unregisterAuthenticationProvider(id: string): void;
sessionsUpdate(providerId: string, event: AuthenticationSessionsChangeEvent): void;
@@ -34,6 +36,7 @@ export interface IAuthenticationService {
export class AuthenticationService extends Disposable implements IAuthenticationService {
_serviceBrand: undefined;
private _placeholderMenuItem: IDisposable | undefined;
private _noAccountsMenuItem: IDisposable | undefined;
private _authenticationProviders: Map<string, MainThreadAuthenticationProvider> = new Map<string, MainThreadAuthenticationProvider>();
@@ -51,19 +54,49 @@ export class AuthenticationService extends Disposable implements IAuthentication
this._placeholderMenuItem = MenuRegistry.appendMenuItem(MenuId.AccountsContext, {
command: {
id: 'noAuthenticationProviders',
title: nls.localize('loading', "Loading...")
title: nls.localize('loading', "Loading..."),
precondition: ContextKeyExpr.false()
},
});
}
isAuthenticationProviderRegistered(id: string): boolean {
return this._authenticationProviders.has(id);
}
private updateAccountsMenuItem(): void {
let hasSession = false;
this._authenticationProviders.forEach(async provider => {
hasSession = hasSession || provider.hasSessions();
});
if (hasSession && this._noAccountsMenuItem) {
this._noAccountsMenuItem.dispose();
this._noAccountsMenuItem = undefined;
}
if (!hasSession && !this._noAccountsMenuItem) {
this._noAccountsMenuItem = MenuRegistry.appendMenuItem(MenuId.AccountsContext, {
group: '0_accounts',
command: {
id: 'noAccounts',
title: nls.localize('noAccounts', "You are not signed in to any accounts"),
precondition: ContextKeyExpr.false()
},
});
}
}
registerAuthenticationProvider(id: string, authenticationProvider: MainThreadAuthenticationProvider): void {
this._authenticationProviders.set(id, authenticationProvider);
this._onDidRegisterAuthenticationProvider.fire(id);
if (authenticationProvider.dependents.length && this._placeholderMenuItem) {
if (this._placeholderMenuItem) {
this._placeholderMenuItem.dispose();
this._placeholderMenuItem = undefined;
}
this.updateAccountsMenuItem();
}
unregisterAuthenticationProvider(id: string): void {
@@ -72,23 +105,26 @@ export class AuthenticationService extends Disposable implements IAuthentication
provider.dispose();
this._authenticationProviders.delete(id);
this._onDidUnregisterAuthenticationProvider.fire(id);
this.updateAccountsMenuItem();
}
if (!this._authenticationProviders.size) {
this._placeholderMenuItem = MenuRegistry.appendMenuItem(MenuId.AccountsContext, {
command: {
id: 'noAuthenticationProviders',
title: nls.localize('loading', "Loading...")
title: nls.localize('loading', "Loading..."),
precondition: ContextKeyExpr.false()
},
});
}
}
sessionsUpdate(id: string, event: AuthenticationSessionsChangeEvent): void {
async sessionsUpdate(id: string, event: AuthenticationSessionsChangeEvent): Promise<void> {
this._onDidChangeSessions.fire({ providerId: id, event: event });
const provider = this._authenticationProviders.get(id);
if (provider) {
provider.updateSessionItems(event);
await provider.updateSessionItems(event);
this.updateAccountsMenuItem();
}
}

View File

@@ -8,15 +8,15 @@ import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedPr
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { Disposable } from 'vs/base/common/lifecycle';
import { Emitter, Event } from 'vs/base/common/event';
import { IAuthenticationTokenService } from 'vs/platform/authentication/common/authentication';
import { IAuthenticationTokenService, IUserDataSyncAuthToken } from 'vs/platform/authentication/common/authentication';
export class AuthenticationTokenService extends Disposable implements IAuthenticationTokenService {
_serviceBrand: undefined;
private readonly channel: IChannel;
private _onDidChangeToken: Emitter<string | undefined> = this._register(new Emitter<string | undefined>());
readonly onDidChangeToken: Event<string | undefined> = this._onDidChangeToken.event;
private _onDidChangeToken = this._register(new Emitter<IUserDataSyncAuthToken | undefined>());
readonly onDidChangeToken = this._onDidChangeToken.event;
private _onTokenFailed: Emitter<void> = this._register(new Emitter<void>());
readonly onTokenFailed: Event<void> = this._onTokenFailed.event;
@@ -29,11 +29,11 @@ export class AuthenticationTokenService extends Disposable implements IAuthentic
this._register(this.channel.listen<void[]>('onTokenFailed')(_ => this.sendTokenFailed()));
}
getToken(): Promise<string | undefined> {
getToken(): Promise<IUserDataSyncAuthToken | undefined> {
return this.channel.call('getToken');
}
setToken(token: string | undefined): Promise<undefined> {
setToken(token: IUserDataSyncAuthToken | undefined): Promise<undefined> {
return this.channel.call('setToken', token);
}

View File

@@ -87,10 +87,11 @@ export class WorkspaceService extends Disposable implements IConfigurationServic
}
this.workspaceConfiguration = this._register(new WorkspaceConfiguration(configurationCache, fileService));
this._register(this.workspaceConfiguration.onDidUpdateConfiguration(() => {
this.onWorkspaceConfigurationChanged();
if (this.workspaceConfiguration.loaded) {
this.releaseWorkspaceBarrier();
}
this.onWorkspaceConfigurationChanged().then(() => {
if (this.workspaceConfiguration.loaded) {
this.releaseWorkspaceBarrier();
}
});
}));
this._register(Registry.as<IConfigurationRegistry>(Extensions.Configuration).onDidSchemaChange(e => this.registerConfigurationSchemas()));

View File

@@ -218,17 +218,21 @@ export abstract class AbstractFileDialogService implements IFileDialogService {
}
private pickResource(options: IOpenDialogOptions): Promise<URI | undefined> {
const simpleFileDialog = this.instantiationService.createInstance(SimpleFileDialog);
const simpleFileDialog = this.createSimpleFileDialog();
return simpleFileDialog.showOpenDialog(options);
}
private saveRemoteResource(options: ISaveDialogOptions): Promise<URI | undefined> {
const remoteFileDialog = this.instantiationService.createInstance(SimpleFileDialog);
const remoteFileDialog = this.createSimpleFileDialog();
return remoteFileDialog.showSaveDialog(options);
}
protected createSimpleFileDialog(): SimpleFileDialog {
return this.instantiationService.createInstance(SimpleFileDialog);
}
protected getSchemeFilterForWindow(): string {
return !this.environmentService.configuration.remoteAuthority ? Schemas.file : REMOTE_HOST_SCHEME;
}

View File

@@ -35,7 +35,7 @@ import { ICommandHandler } from 'vs/platform/commands/common/commands';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { normalizeDriveLetter } from 'vs/base/common/labels';
import { SaveReason } from 'vs/workbench/common/editor';
import { IRemotePathService } from 'vs/workbench/services/path/common/remotePathService';
import { IPathService } from 'vs/workbench/services/path/common/pathService';
export namespace OpenLocalFileCommand {
export const ID = 'workbench.action.files.openLocalFile';
@@ -108,7 +108,7 @@ export class SimpleFileDialog {
private remoteAuthority: string | undefined;
private requiresTrailing: boolean = false;
private trailing: string | undefined;
private scheme: string = REMOTE_HOST_SCHEME;
protected scheme: string = REMOTE_HOST_SCHEME;
private contextKey: IContextKey<boolean>;
private userEnteredPathSegment: string = '';
private autoCompletePathSegment: string = '';
@@ -133,9 +133,9 @@ export class SimpleFileDialog {
@IFileDialogService private readonly fileDialogService: IFileDialogService,
@IModelService private readonly modelService: IModelService,
@IModeService private readonly modeService: IModeService,
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
@IWorkbenchEnvironmentService protected readonly environmentService: IWorkbenchEnvironmentService,
@IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService,
@IRemotePathService private readonly remotePathService: IRemotePathService,
@IPathService protected readonly pathService: IPathService,
@IKeybindingService private readonly keybindingService: IKeybindingService,
@IContextKeyService contextKeyService: IContextKeyService,
) {
@@ -231,11 +231,8 @@ export class SimpleFileDialog {
return this.remoteAgentEnvironment;
}
private async getUserHome(): Promise<URI> {
if (this.scheme !== Schemas.file) {
return this.remotePathService.userHome;
}
return this.environmentService.userHome!;
protected async getUserHome(): Promise<URI> {
return (await this.pathService.userHome) ?? URI.from({ scheme: this.scheme, authority: this.remoteAuthority, path: '/' });
}
private async pickResource(isSave: boolean = false): Promise<URI | undefined> {

View File

@@ -22,6 +22,8 @@ import { Schemas } from 'vs/base/common/network';
import { IModeService } from 'vs/editor/common/services/modeService';
import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces';
import { ILabelService } from 'vs/platform/label/common/label';
import { SimpleFileDialog } from 'vs/workbench/services/dialogs/browser/simpleFileDialog';
import { NativeSimpleFileDialog } from 'vs/workbench/services/dialogs/electron-browser/simpleFileDialog';
export class FileDialogService extends AbstractFileDialogService implements IFileDialogService {
@@ -190,6 +192,10 @@ export class FileDialogService extends AbstractFileDialogService implements IFil
// Don't allow untitled schema through.
return schema === Schemas.untitled ? [Schemas.file] : (schema !== Schemas.file ? [schema, Schemas.file] : [schema]);
}
protected createSimpleFileDialog(): SimpleFileDialog {
return this.instantiationService.createInstance(NativeSimpleFileDialog);
}
}
registerSingleton(IFileDialogService, FileDialogService, true);

View File

@@ -0,0 +1,49 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { URI } from 'vs/base/common/uri';
import { SimpleFileDialog } from 'vs/workbench/services/dialogs/browser/simpleFileDialog';
import { Schemas } from 'vs/base/common/network';
import { IFileService } from 'vs/platform/files/common/files';
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
import { ILabelService } from 'vs/platform/label/common/label';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IModelService } from 'vs/editor/common/services/modelService';
import { IModeService } from 'vs/editor/common/services/modeService';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { IPathService } from 'vs/workbench/services/path/common/pathService';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService';
export class NativeSimpleFileDialog extends SimpleFileDialog {
constructor(
@IFileService fileService: IFileService,
@IQuickInputService quickInputService: IQuickInputService,
@ILabelService labelService: ILabelService,
@IWorkspaceContextService workspaceContextService: IWorkspaceContextService,
@INotificationService notificationService: INotificationService,
@IFileDialogService fileDialogService: IFileDialogService,
@IModelService modelService: IModelService,
@IModeService modeService: IModeService,
@IWorkbenchEnvironmentService protected environmentService: INativeWorkbenchEnvironmentService,
@IRemoteAgentService remoteAgentService: IRemoteAgentService,
@IPathService protected pathService: IPathService,
@IKeybindingService keybindingService: IKeybindingService,
@IContextKeyService contextKeyService: IContextKeyService
) {
super(fileService, quickInputService, labelService, workspaceContextService, notificationService, fileDialogService, modelService, modeService, environmentService, remoteAgentService, pathService, keybindingService, contextKeyService);
}
protected async getUserHome(): Promise<URI> {
if (this.scheme !== Schemas.file) {
return super.getUserHome();
}
return this.environmentService.userHome;
}
}

View File

@@ -0,0 +1,76 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IJSONSchema } from 'vs/base/common/jsonSchema';
import * as nls from 'vs/nls';
import { IConfigurationNode, IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry';
import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration';
import { Registry } from 'vs/platform/registry/common/platform';
import { ICustomEditorInfo } from 'vs/workbench/services/editor/common/editorService';
export const customEditorsAssociationsSettingId = 'workbench.editorAssociations';
export const viewTypeSchamaAddition: IJSONSchema = {
type: 'string',
enum: []
};
export type CustomEditorAssociation = {
readonly viewType: string;
readonly filenamePattern?: string;
};
export type CustomEditorsAssociations = readonly CustomEditorAssociation[];
export const editorAssociationsConfigurationNode: IConfigurationNode = {
...workbenchConfigurationNodeBase,
properties: {
[customEditorsAssociationsSettingId]: {
type: 'array',
markdownDescription: nls.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: nls.localize('editor.editorAssociations.viewType', "The unique id of the editor to use."),
},
viewTypeSchamaAddition
]
},
'filenamePattern': {
type: 'string',
description: nls.localize('editor.editorAssociations.filenamePattern', "Glob pattern specifying which files the editor should be used for."),
}
}
}
}
}
};
const builtinProviderDisplayName = nls.localize('builtinProviderDisplayName', "Built-in");
export const DEFAULT_CUSTOM_EDITOR: ICustomEditorInfo = {
id: 'default',
displayName: nls.localize('promptOpenWith.defaultEditor.displayName', "Text Editor"),
providerDisplayName: builtinProviderDisplayName
};
export function updateViewTypeSchema(enumValues: string[], enumDescriptions: string[]): void {
viewTypeSchamaAddition.enum = enumValues;
viewTypeSchamaAddition.enumDescriptions = enumDescriptions;
Registry.as<IConfigurationRegistry>(Extensions.Configuration)
.notifyConfigurationSchemaUpdated(editorAssociationsConfigurationNode);
}

View File

@@ -3,6 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { IResourceEditorInput, ITextEditorOptions, IEditorOptions, EditorActivation } from 'vs/platform/editor/common/editor';
import { SideBySideEditor, IEditorInput, IEditorPane, GroupIdentifier, IFileEditorInput, IUntitledTextResourceEditorInput, IResourceDiffEditorInput, IEditorInputFactoryRegistry, Extensions as EditorExtensions, EditorInput, SideBySideEditorInput, IEditorInputWithOptions, isEditorInputWithOptions, EditorOptions, TextEditorOptions, IEditorIdentifier, IEditorCloseEvent, ITextEditorPane, ITextDiffEditorPane, IRevertOptions, SaveReason, EditorsOrder, isTextEditorPane, IWorkbenchEditorConfiguration, toResource, IVisibleEditorPane } from 'vs/workbench/common/editor';
@@ -17,7 +18,7 @@ import { URI } from 'vs/base/common/uri';
import { basename, isEqualOrParent, joinPath } from 'vs/base/common/resources';
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
import { IEditorGroupsService, IEditorGroup, GroupsOrder, IEditorReplacement, GroupChangeKind, preferredSideBySideGroupDirection } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IResourceEditorInputType, SIDE_GROUP, IResourceEditorReplacement, IOpenEditorOverrideHandler, IEditorService, SIDE_GROUP_TYPE, ACTIVE_GROUP_TYPE, ISaveEditorsOptions, ISaveAllEditorsOptions, IRevertAllEditorsOptions, IBaseSaveRevertAllEditorOptions } from 'vs/workbench/services/editor/common/editorService';
import { IResourceEditorInputType, SIDE_GROUP, IResourceEditorReplacement, IOpenEditorOverrideHandler, IEditorService, SIDE_GROUP_TYPE, ACTIVE_GROUP_TYPE, ISaveEditorsOptions, ISaveAllEditorsOptions, IRevertAllEditorsOptions, IBaseSaveRevertAllEditorOptions, IOpenEditorOverrideEntry, ICustomEditorViewTypesHandler, ICustomEditorInfo } from 'vs/workbench/services/editor/common/editorService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { Disposable, IDisposable, dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { coalesce, distinct, insert } from 'vs/base/common/arrays';
@@ -33,6 +34,8 @@ import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/u
import { timeout } from 'vs/base/common/async';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { indexOfPath } from 'vs/base/common/extpath';
import { DEFAULT_CUSTOM_EDITOR, updateViewTypeSchema, editorAssociationsConfigurationNode } from 'vs/workbench/services/editor/browser/editorAssociationsSetting';
import { Extensions as ConfigurationExtensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
type CachedEditorInput = ResourceEditorInput | IFileEditorInput | UntitledTextEditorInput;
type OpenInEditorGroup = IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE;
@@ -477,13 +480,23 @@ export class EditorService extends Disposable implements EditorServiceImpl {
return toDisposable(() => remove());
}
getEditorOverrides(editorInput: IEditorInput, options: IEditorOptions | undefined, group: IEditorGroup | undefined): [IOpenEditorOverrideHandler, IOpenEditorOverrideEntry][] {
const ret = [];
for (const handler of this.openEditorHandlers) {
const handlers = handler.getEditorOverrides ? handler.getEditorOverrides(editorInput, options, group).map(val => { return [handler, val] as [IOpenEditorOverrideHandler, IOpenEditorOverrideEntry]; }) : [];
ret.push(...handlers);
}
return ret;
}
private onGroupWillOpenEditor(group: IEditorGroup, event: IEditorOpeningEvent): void {
if (event.options && event.options.ignoreOverrides) {
return;
}
for (const handler of this.openEditorHandlers) {
const result = handler(event.editor, event.options, group);
const result = handler.open(event.editor, event.options, group);
const override = result?.override;
if (override) {
event.prevent((() => override.then(editor => withNullAsUndefined(editor))));
@@ -1078,6 +1091,48 @@ export class EditorService extends Disposable implements EditorServiceImpl {
//#endregion
//#region Custom View Type
private customEditorViewTypesHandlers = new Map<string, ICustomEditorViewTypesHandler>();
registerCustomEditorViewTypesHandler(source: string, handler: ICustomEditorViewTypesHandler): IDisposable {
if (this.customEditorViewTypesHandlers.has(source)) {
throw new Error(`Use a different name for the custom editor component, ${source} is already occupied.`);
}
this.customEditorViewTypesHandlers.set(source, handler);
this.updateSchema();
const viewTypeChangeEvent = handler.onDidChangeViewTypes(() => {
this.updateSchema();
});
return {
dispose: () => {
viewTypeChangeEvent.dispose();
this.customEditorViewTypesHandlers.delete(source);
this.updateSchema();
}
};
}
private updateSchema() {
const enumValues: string[] = [];
const enumDescriptions: string[] = [];
const infos: ICustomEditorInfo[] = [DEFAULT_CUSTOM_EDITOR];
for (const [, handler] of this.customEditorViewTypesHandlers) {
infos.push(...handler.getViewTypes());
}
infos.forEach(info => {
enumValues.push(info.id);
enumDescriptions.push(nls.localize('editorAssociations.viewType.sourceDescription', "Source: {0}", info.providerDisplayName));
});
updateViewTypeSchema(enumValues, enumDescriptions);
}
//#endregion
dispose(): void {
super.dispose();
@@ -1110,6 +1165,10 @@ export class DelegatingEditorService implements IEditorService {
@IEditorService private editorService: EditorService
) { }
getEditorOverrides(editorInput: IEditorInput, options: IEditorOptions | undefined, group: IEditorGroup | undefined) {
return [];
}
openEditor(editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions, group?: OpenInEditorGroup): Promise<IEditorPane | undefined>;
openEditor(editor: IResourceEditorInput | IUntitledTextResourceEditorInput, group?: OpenInEditorGroup): Promise<ITextEditorPane | undefined>;
openEditor(editor: IResourceDiffEditorInput, group?: OpenInEditorGroup): Promise<ITextDiffEditorPane | undefined>;
@@ -1181,7 +1240,14 @@ export class DelegatingEditorService implements IEditorService {
revert(editors: IEditorIdentifier | IEditorIdentifier[], options?: IRevertOptions): Promise<void> { return this.editorService.revert(editors, options); }
revertAll(options?: IRevertAllEditorsOptions): Promise<void> { return this.editorService.revertAll(options); }
registerCustomEditorViewTypesHandler(source: string, handler: ICustomEditorViewTypesHandler): IDisposable {
throw new Error('Method not implemented.');
}
//#endregion
}
registerSingleton(IEditorService, EditorService);
Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration)
.registerConfiguration(editorAssociationsConfigurationNode);

View File

@@ -26,8 +26,16 @@ export type ACTIVE_GROUP_TYPE = typeof ACTIVE_GROUP;
export const SIDE_GROUP = -2;
export type SIDE_GROUP_TYPE = typeof SIDE_GROUP;
export interface IOpenEditorOverrideEntry {
id: string;
label: string;
active: boolean;
detail?: string;
}
export interface IOpenEditorOverrideHandler {
(editor: IEditorInput, options: IEditorOptions | ITextEditorOptions | undefined, group: IEditorGroup): IOpenEditorOverride | undefined;
open(editor: IEditorInput, options: IEditorOptions | ITextEditorOptions | undefined, group: IEditorGroup, id?: string): IOpenEditorOverride | undefined;
getEditorOverrides?(editor: IEditorInput, options: IEditorOptions | undefined, group: IEditorGroup | undefined): IOpenEditorOverrideEntry[];
}
export interface IOpenEditorOverride {
@@ -59,6 +67,18 @@ export interface ISaveAllEditorsOptions extends ISaveEditorsOptions, IBaseSaveRe
export interface IRevertAllEditorsOptions extends IRevertOptions, IBaseSaveRevertAllEditorOptions { }
export interface ICustomEditorInfo {
readonly id: string;
readonly displayName: string;
readonly providerDisplayName: string;
}
export interface ICustomEditorViewTypesHandler {
readonly onDidChangeViewTypes: Event<void>;
getViewTypes(): ICustomEditorInfo[];
}
export interface IEditorService {
_serviceBrand: undefined;
@@ -201,6 +221,11 @@ export interface IEditorService {
isOpen(editor: IResourceEditorInput): boolean;
isOpen(editor: IEditorInput): boolean;
/**
* Get all available editor overrides for the editor input.
*/
getEditorOverrides(editorInput: IEditorInput, options: IEditorOptions | undefined, group: IEditorGroup | undefined): [IOpenEditorOverrideHandler, IOpenEditorOverrideEntry][];
/**
* Allows to override the opening of editors by installing a handler that will
* be called each time an editor is about to open allowing to override the
@@ -208,6 +233,8 @@ export interface IEditorService {
*/
overrideOpenEditor(handler: IOpenEditorOverrideHandler): IDisposable;
registerCustomEditorViewTypesHandler(source: string, handler: ICustomEditorViewTypesHandler): IDisposable;
/**
* Invoke a function in the context of the services of the active editor.
*/

View File

@@ -963,14 +963,16 @@ suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} skip suite
let overrideCalled = false;
const handler = service.overrideOpenEditor(editor => {
if (editor === input1) {
overrideCalled = true;
const handler = service.overrideOpenEditor({
open: editor => {
if (editor === input1) {
overrideCalled = true;
return { override: service.openEditor(input2, { pinned: true }) };
return { override: service.openEditor(input2, { pinned: true }) };
}
return undefined;
}
return undefined;
});
await service.openEditor(input1, { pinned: true });

View File

@@ -120,7 +120,11 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment
@memoize
get userDataSyncLogResource(): URI { return joinPath(this.options.logsPath, 'userDataSync.log'); }
get sync(): 'on' | 'off' { return 'on'; }
@memoize
get sync(): 'on' | 'off' | undefined { return undefined; }
@memoize
get enableSyncByDefault(): boolean { return !!this.options.enableSyncByDefault; }
@memoize
get keybindingsResource(): URI { return joinPath(this.userRoamingDataHome, 'keybindings.json'); }

View File

@@ -9,6 +9,7 @@ import { URI } from 'vs/base/common/uri';
import { IExtension } from 'vs/platform/extensions/common/extensions';
import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IWorkspace, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { IStringDictionary } from 'vs/base/common/collections';
export const IExtensionManagementServerService = createDecorator<IExtensionManagementServerService>('extensionManagementServerService');
@@ -84,38 +85,49 @@ export type RecommendationChangeNotification = {
};
export type DynamicRecommendation = 'dynamic';
export type ConfigRecommendation = 'config';
export type ExecutableRecommendation = 'executable';
export type CachedRecommendation = 'cached';
export type ApplicationRecommendation = 'application';
export type ExtensionRecommendationSource = IWorkspace | IWorkspaceFolder | URI | DynamicRecommendation | ExecutableRecommendation | CachedRecommendation | ApplicationRecommendation;
export type ExperimentalRecommendation = 'experimental';
export type ExtensionRecommendationSource = IWorkspace | IWorkspaceFolder | URI | DynamicRecommendation | ExecutableRecommendation | CachedRecommendation | ApplicationRecommendation | ExperimentalRecommendation | ConfigRecommendation;
export interface IExtensionRecommendation {
extensionId: string;
sources: ExtensionRecommendationSource[];
}
export const IExtensionRecommendationsService = createDecorator<IExtensionRecommendationsService>('extensionRecommendationsService');
export interface IExtensionRecommendationsService {
_serviceBrand: undefined;
getAllRecommendationsWithReason(): { [id: string]: { reasonId: ExtensionRecommendationReason, reasonText: string }; };
getFileBasedRecommendations(): IExtensionRecommendation[];
getOtherRecommendations(): Promise<IExtensionRecommendation[]>;
getWorkspaceRecommendations(): Promise<IExtensionRecommendation[]>;
getKeymapRecommendations(): IExtensionRecommendation[];
toggleIgnoredRecommendation(extensionId: string, shouldIgnore: boolean): void;
getAllIgnoredRecommendations(): { global: string[], workspace: string[] };
onRecommendationChange: Event<RecommendationChangeNotification>;
// {{SQL CARBON EDIT}}
getRecommendedExtensionsByScenario(scenarioType: string): Promise<IExtensionRecommendation[]>;
promptRecommendedExtensionsByScenario(scenarioType: string): void;
// {{SQL CARBON EDIT}} - End
}
export const enum ExtensionRecommendationReason {
Workspace,
File,
Executable,
WorkspaceConfig,
DynamicWorkspace,
Experimental
Experimental,
Application,
}
export interface IExtensionRecommendationReson {
reasonId: ExtensionRecommendationReason;
reasonText: string;
}
export const IExtensionRecommendationsService = createDecorator<IExtensionRecommendationsService>('extensionRecommendationsService');
export interface IExtensionRecommendationsService {
_serviceBrand: undefined;
getAllRecommendationsWithReason(): IStringDictionary<IExtensionRecommendationReson>;
getFileBasedRecommendations(): IExtensionRecommendation[];
getConfigBasedRecommendations(): Promise<IExtensionRecommendation[]>;
getOtherRecommendations(): Promise<IExtensionRecommendation[]>;
getWorkspaceRecommendations(): Promise<IExtensionRecommendation[]>;
getKeymapRecommendations(): IExtensionRecommendation[];
toggleIgnoredRecommendation(extensionId: string, shouldIgnore: boolean): void;
getIgnoredRecommendations(): ReadonlyArray<string>;
onRecommendationChange: Event<RecommendationChangeNotification>;
getRecommendedExtensionsByScenario(scenarioType: string): Promise<IExtensionRecommendation[]>; // {{SQL CARBON EDIT}}
promptRecommendedExtensionsByScenario(scenarioType: string): void; // {{SQL CARBON EDIT}}
}

View File

@@ -1,29 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IExtensionTipsService, IExecutableBasedExtensionTip, IWorkspaceTips } from 'vs/platform/extensionManagement/common/extensionManagement';
class WebExtensionTipsService implements IExtensionTipsService {
_serviceBrand: any;
constructor() { }
async getImportantExecutableBasedTips(): Promise<IExecutableBasedExtensionTip[]> {
return [];
}
async getOtherExecutableBasedTips(): Promise<IExecutableBasedExtensionTip[]> {
return [];
}
async getAllWorkspacesTips(): Promise<IWorkspaceTips[]> {
return [];
}
}
registerSingleton(IExtensionTipsService, WebExtensionTipsService);

View File

@@ -6,7 +6,8 @@
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService';
import { IChannel } from 'vs/base/parts/ipc/common/ipc';
import { IExtensionTipsService, IExecutableBasedExtensionTip, IWorkspaceTips } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IExtensionTipsService, IExecutableBasedExtensionTip, IWorkspaceTips, IConfigBasedExtensionTip } from 'vs/platform/extensionManagement/common/extensionManagement';
import { URI } from 'vs/base/common/uri';
class NativeExtensionTipsService implements IExtensionTipsService {
@@ -20,6 +21,10 @@ class NativeExtensionTipsService implements IExtensionTipsService {
this.channel = sharedProcessService.getChannel('extensionTipsService');
}
getConfigBasedTips(folder: URI): Promise<IConfigBasedExtensionTip[]> {
return this.channel.call<IConfigBasedExtensionTip[]>('getConfigBasedTips', [folder]);
}
getImportantExecutableBasedTips(): Promise<IExecutableBasedExtensionTip[]> {
return this.channel.call<IExecutableBasedExtensionTip[]>('getImportantExecutableBasedTips');
}

View File

@@ -418,4 +418,4 @@ export class ManageAuthorizedExtensionURIsAction extends Action {
}
const actionRegistry = Registry.as<IWorkbenchActionRegistry>(WorkbenchActionExtensions.WorkbenchActions);
actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ManageAuthorizedExtensionURIsAction, ManageAuthorizedExtensionURIsAction.ID, ManageAuthorizedExtensionURIsAction.LABEL), `Extensions: Manage Authorized Extension URIs...`, ExtensionsLabel);
actionRegistry.registerWorkbenchAction(SyncActionDescriptor.from(ManageAuthorizedExtensionURIsAction), `Extensions: Manage Authorized Extension URIs...`, ExtensionsLabel);

View File

@@ -249,6 +249,11 @@ export const schema: IJSONSchema = {
description: nls.localize('vscode.extension.activationEvents.onDebugInitialConfigurations', 'An activation event emitted whenever a "launch.json" needs to be created (and all provideDebugConfigurations methods need to be called).'),
body: 'onDebugInitialConfigurations'
},
{
label: 'onDebugDynamicConfigurations',
description: nls.localize('vscode.extension.activationEvents.onDebugDynamicConfigurations', 'An activation event emitted whenever a list of all debug configurations needs to be created (and all provideDebugConfigurations methods for the "dynamic" scope need to be called).'),
body: 'onDebugDynamicConfigurations'
},
{
label: 'onDebugResolve',
description: nls.localize('vscode.extension.activationEvents.onDebugResolve', 'An activation event emitted whenever a debug session with the specific type is about to be launched (and a corresponding resolveDebugConfiguration method needs to be called).'),

View File

@@ -619,7 +619,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten
}
} else {
// Install the Extension and reload the window to handle.
const message = nls.localize('installResolver', "Extension '{0}' is required to open the remote window.\nnOK to install?", recommendation.friendlyName);
const message = nls.localize('installResolver', "Extension '{0}' is required to open the remote window.\nOK to install?", recommendation.friendlyName);
this._notificationService.prompt(Severity.Info, message,
[{
label: nls.localize('install', 'Install and Reload'),
@@ -687,4 +687,4 @@ class RestartExtensionHostAction extends Action {
}
const registry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
registry.registerWorkbenchAction(SyncActionDescriptor.create(RestartExtensionHostAction, RestartExtensionHostAction.ID, RestartExtensionHostAction.LABEL), 'Developer: Restart Extension Host', nls.localize('developer', "Developer"));
registry.registerWorkbenchAction(SyncActionDescriptor.from(RestartExtensionHostAction), 'Developer: Restart Extension Host', nls.localize('developer', "Developer"));

View File

@@ -18,11 +18,11 @@ import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePa
import { IExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService';
import { IExtHostStorage, ExtHostStorage } from 'vs/workbench/api/common/extHostStorage';
import { ExtHostExtensionService } from 'vs/workbench/api/worker/extHostExtensionService';
import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation';
import { ILogService } from 'vs/platform/log/common/log';
import { ExtHostLogService } from 'vs/workbench/api/worker/extHostLogService';
import { IExtHostTunnelService, ExtHostTunnelService } from 'vs/workbench/api/common/extHostTunnelService';
import { IExtHostApiDeprecationService, ExtHostApiDeprecationService, } from 'vs/workbench/api/common/extHostApiDeprecationService';
import { NotImplementedProxy } from 'vs/base/common/types';
// register singleton services
registerSingleton(ILogService, ExtHostLogService);
@@ -38,22 +38,7 @@ registerSingleton(IExtHostExtensionService, ExtHostExtensionService);
registerSingleton(IExtHostSearch, ExtHostSearch);
registerSingleton(IExtHostTunnelService, ExtHostTunnelService);
// register services that only throw errors
function NotImplementedProxy<T>(name: ServiceIdentifier<T>): { new(): T } {
return <any>class {
constructor() {
return new Proxy({}, {
get(target: any, prop: PropertyKey) {
if (target[prop]) {
return target[prop];
}
throw new Error(`Not Implemented: ${name}->${String(prop)}`);
}
});
}
};
}
registerSingleton(IExtHostTerminalService, WorkerExtHostTerminalService);
// registerSingleton(IExtHostTask, WorkerExtHostTask); {{SQL CARBON EDIT}} disable
// registerSingleton(IExtHostDebugService, WorkerExtHostDebugService); {{SQL CARBON EDIT}} disable
registerSingleton(IExtensionStoragePaths, class extends NotImplementedProxy(IExtensionStoragePaths) { whenReady = Promise.resolve(); });
registerSingleton(IExtensionStoragePaths, class extends NotImplementedProxy<IExtensionStoragePaths>(String(IExtensionStoragePaths)) { whenReady = Promise.resolve(); });

View File

@@ -144,7 +144,7 @@ export class IntegrityServiceImpl implements IIntegrityService {
return new Promise<ChecksumPair>((resolve, reject) => {
fs.readFile(fileUri.fsPath, (err, buff) => {
if (err) {
return reject(err);
return resolve(IntegrityServiceImpl._createChecksumPair(fileUri, '', expected));
}
resolve(IntegrityServiceImpl._createChecksumPair(fileUri, this._computeChecksum(buff), expected));
});

View File

@@ -38,7 +38,7 @@ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editor
import { KeybindingsEditingService } from 'vs/workbench/services/keybinding/common/keybindingEditing';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { TextModelResolverService } from 'vs/workbench/services/textmodelResolver/common/textModelResolverService';
import { TestBackupFileService, TestEditorGroupsService, TestEditorService, TestLifecycleService } from 'vs/workbench/test/browser/workbenchTestServices';
import { TestBackupFileService, TestEditorGroupsService, TestEditorService, TestLifecycleService, TestPathService } from 'vs/workbench/test/browser/workbenchTestServices';
import { FileService } from 'vs/platform/files/common/fileService';
import { Schemas } from 'vs/base/common/network';
import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider';
@@ -57,6 +57,7 @@ import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService';
import { TestTextResourcePropertiesService, TestContextService, TestWorkingCopyService } from 'vs/workbench/test/common/workbenchTestServices';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService';
import { IPathService } from 'vs/workbench/services/path/common/pathService';
class TestEnvironmentService extends NativeWorkbenchEnvironmentService {
@@ -93,6 +94,7 @@ suite('KeybindingsEditing', () => {
configService.setUserConfiguration('files', { 'eol': '\n' });
instantiationService.stub(IEnvironmentService, environmentService);
instantiationService.stub(IPathService, new TestPathService());
instantiationService.stub(IConfigurationService, configService);
instantiationService.stub(IWorkspaceContextService, new TestContextService());
const lifecycleService = new TestLifecycleService();

View File

@@ -21,7 +21,7 @@ import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/exte
import { match } from 'vs/base/common/glob';
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IRemotePathService } from 'vs/workbench/services/path/common/remotePathService';
import { IPathService } from 'vs/workbench/services/path/common/pathService';
const resourceLabelFormattersExtPoint = ExtensionsRegistry.registerExtensionPoint<ResourceLabelFormatter[]>({
extensionPoint: 'resourceLabelFormatters',
@@ -102,7 +102,7 @@ export class LabelService extends Disposable implements ILabelService {
constructor(
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
@IRemotePathService private readonly remotePathService: IRemotePathService
@IPathService private readonly pathService: IPathService
) {
super();
}
@@ -135,7 +135,7 @@ export class LabelService extends Disposable implements ILabelService {
private doGetUriLabel(resource: URI, formatting?: ResourceLabelFormatting, options: { relative?: boolean, noPrefix?: boolean, endWithSeparator?: boolean } = {}): string {
if (!formatting) {
return getPathLabel(resource.path, this.environmentService, options.relative ? this.contextService : undefined);
return getPathLabel(resource.path, { userHome: this.pathService.resolvedUserHome }, options.relative ? this.contextService : undefined);
}
let label: string | undefined;
@@ -266,7 +266,7 @@ export class LabelService extends Disposable implements ILabelService {
}
if (formatting.tildify && !forceNoTildify) {
const userHome = this.remotePathService.userHomeSync;
const userHome = this.pathService.resolvedUserHome;
if (userHome) {
label = tildify(label, userHome.fsPath);
}

View File

@@ -4,11 +4,8 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { TestEnvironmentService, TestRemotePathService } from 'vs/workbench/test/browser/workbenchTestServices';
import { TestWorkspace } from 'vs/platform/workspace/test/common/testWorkspace';
import { TestEnvironmentService, TestPathService } from 'vs/workbench/test/browser/workbenchTestServices';
import { URI } from 'vs/base/common/uri';
import { sep } from 'vs/base/common/path';
import { isWindows } from 'vs/base/common/platform';
import { LabelService } from 'vs/workbench/services/label/common/labelService';
import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices';
@@ -17,28 +14,7 @@ suite('URI Label', () => {
let labelService: LabelService;
setup(() => {
labelService = new LabelService(TestEnvironmentService, new TestContextService(), new TestRemotePathService(TestEnvironmentService));
});
test('file scheme', function () {
labelService.registerFormatter({
scheme: 'file',
formatting: {
label: '${path}',
separator: sep,
tildify: !isWindows,
normalizeDriveLetter: isWindows
}
});
const uri1 = TestWorkspace.folders[0].uri.with({ path: TestWorkspace.folders[0].uri.path.concat('/a/b/c/d') });
assert.equal(labelService.getUriLabel(uri1, { relative: true }), isWindows ? 'a\\b\\c\\d' : 'a/b/c/d');
assert.equal(labelService.getUriLabel(uri1, { relative: false }), isWindows ? 'C:\\testWorkspace\\a\\b\\c\\d' : '/testWorkspace/a/b/c/d');
assert.equal(labelService.getUriBasenameLabel(uri1), 'd');
const uri2 = URI.file('c:\\1/2/3');
assert.equal(labelService.getUriLabel(uri2, { relative: false }), isWindows ? 'C:\\1\\2\\3' : '/c:\\1/2/3');
assert.equal(labelService.getUriBasenameLabel(uri2), '3');
labelService = new LabelService(TestEnvironmentService, new TestContextService(), new TestPathService());
});
test('custom scheme', function () {

View File

@@ -0,0 +1,43 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { TestWorkspace } from 'vs/platform/workspace/test/common/testWorkspace';
import { URI } from 'vs/base/common/uri';
import { sep } from 'vs/base/common/path';
import { isWindows } from 'vs/base/common/platform';
import { LabelService } from 'vs/workbench/services/label/common/labelService';
import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices';
import { TestNativePathService, TestEnvironmentService } from 'vs/workbench/test/electron-browser/workbenchTestServices';
suite('URI Label', () => {
let labelService: LabelService;
setup(() => {
labelService = new LabelService(TestEnvironmentService, new TestContextService(), new TestNativePathService(TestEnvironmentService));
});
test('file scheme', function () {
labelService.registerFormatter({
scheme: 'file',
formatting: {
label: '${path}',
separator: sep,
tildify: !isWindows,
normalizeDriveLetter: isWindows
}
});
const uri1 = TestWorkspace.folders[0].uri.with({ path: TestWorkspace.folders[0].uri.path.concat('/a/b/c/d') });
assert.equal(labelService.getUriLabel(uri1, { relative: true }), isWindows ? 'a\\b\\c\\d' : 'a/b/c/d');
assert.equal(labelService.getUriLabel(uri1, { relative: false }), isWindows ? 'C:\\testWorkspace\\a\\b\\c\\d' : '/testWorkspace/a/b/c/d');
assert.equal(labelService.getUriBasenameLabel(uri1), 'd');
const uri2 = URI.file('c:\\1/2/3');
assert.equal(labelService.getUriLabel(uri2, { relative: false }), isWindows ? 'C:\\1\\2\\3' : '/c:\\1/2/3');
assert.equal(labelService.getUriBasenameLabel(uri2), '3');
});
});

View File

@@ -93,6 +93,11 @@ export interface IWorkbenchLayoutService extends ILayoutService {
*/
hasFocus(part: Parts): boolean;
/**
* Focuses the part. If the part is not visible this is a noop.
*/
focusPart(part: Parts): void;
/**
* Returns the parts HTML element, if there is one.
*/

View File

@@ -0,0 +1,23 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { IPathService, AbstractPathService } from 'vs/workbench/services/path/common/pathService';
import { URI } from 'vs/base/common/uri';
import { Schemas } from 'vs/base/common/network';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
export class BrowserPathService extends AbstractPathService {
constructor(
@IRemoteAgentService remoteAgentService: IRemoteAgentService,
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService
) {
super(() => URI.from({ scheme: Schemas.vscodeRemote, authority: environmentService.configuration.remoteAuthority, path: '/' }), remoteAgentService);
}
}
registerSingleton(IPathService, BrowserPathService, true);

View File

@@ -0,0 +1,124 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IPath, win32, posix } from 'vs/base/common/path';
import { OperatingSystem, OS } from 'vs/base/common/platform';
import { URI } from 'vs/base/common/uri';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
export const IPathService = createDecorator<IPathService>('path');
/**
* Provides access to path related properties that will match the
* environment. If the environment is connected to a remote, the
* path properties will match that of the remotes operating system.
*/
export interface IPathService {
_serviceBrand: undefined;
/**
* The correct path library to use for the target environment. If
* the environment is connected to a remote, this will be the
* path library of the remote file system. Otherwise it will be
* the local file system's path library depending on the OS.
*/
readonly path: Promise<IPath>;
/**
* Converts the given path to a file URI to use for the target
* environment. If the environment is connected to a remote, it
* will use the path separators according to the remote file
* system. Otherwise it will use the local file system's path
* separators.
*/
fileURI(path: string): Promise<URI>;
/**
* Resolves the user-home directory for the target environment.
* If the envrionment is connected to a remote, this will be the
* remote's user home directory, otherwise the local one.
*/
readonly userHome: Promise<URI>;
/**
* Access to `userHome` in a sync fashion. This may be `undefined`
* as long as the remote environment was not resolved.
*/
readonly resolvedUserHome: URI | undefined;
}
export abstract class AbstractPathService implements IPathService {
_serviceBrand: undefined;
private remoteOS: Promise<OperatingSystem>;
private resolveUserHome: Promise<URI>;
private maybeUnresolvedUserHome: URI | undefined;
constructor(
fallbackUserHome: () => URI,
@IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService
) {
this.remoteOS = this.remoteAgentService.getEnvironment().then(env => env?.os || OS);
this.resolveUserHome = this.remoteAgentService.getEnvironment().then(env => {
const userHome = this.maybeUnresolvedUserHome = env?.userHome || fallbackUserHome();
return userHome;
});
}
get userHome(): Promise<URI> {
return this.resolveUserHome;
}
get resolvedUserHome(): URI | undefined {
return this.maybeUnresolvedUserHome;
}
get path(): Promise<IPath> {
return this.remoteOS.then(os => {
return os === OperatingSystem.Windows ?
win32 :
posix;
});
}
async fileURI(_path: string): Promise<URI> {
let authority = '';
// normalize to fwd-slashes on windows,
// on other systems bwd-slashes are valid
// filename character, eg /f\oo/ba\r.txt
if ((await this.remoteOS) === OperatingSystem.Windows) {
_path = _path.replace(/\\/g, '/');
}
// check for authority as used in UNC shares
// or use the path as given
if (_path[0] === '/' && _path[1] === '/') {
const idx = _path.indexOf('/', 2);
if (idx === -1) {
authority = _path.substring(2);
_path = '/';
} else {
authority = _path.substring(2, idx);
_path = _path.substring(idx) || '/';
}
}
// return new _URI('file', authority, path, '', '');
return URI.from({
scheme: 'file',
authority,
path: _path,
query: '',
fragment: ''
});
}
}

View File

@@ -1,122 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as path from 'vs/base/common/path';
import * as platform from 'vs/base/common/platform';
import { URI } from 'vs/base/common/uri';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
const REMOTE_PATH_SERVICE_ID = 'remotePath';
export const IRemotePathService = createDecorator<IRemotePathService>(REMOTE_PATH_SERVICE_ID);
export interface IRemotePathService {
_serviceBrand: undefined;
/**
* The path library to use for the target remote environment.
*/
readonly path: Promise<path.IPath>;
/**
* Converts the given path to a file URI in the remote environment.
*/
fileURI(path: string): Promise<URI>;
/**
* Resolves the user home of the remote environment if defined.
*/
readonly userHome: Promise<URI>;
/**
* Provides access to the user home of the remote environment
* if defined.
*/
readonly userHomeSync: URI | undefined;
}
/**
* Provides the correct IPath implementation for dealing with paths that refer to locations in the extension host
*/
export class RemotePathService implements IRemotePathService {
_serviceBrand: undefined;
private _extHostOS: Promise<platform.OperatingSystem>;
private _userHomeSync: URI | undefined;
constructor(
@IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService,
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService
) {
this._extHostOS = remoteAgentService.getEnvironment().then(remoteEnvironment => {
this._userHomeSync = remoteEnvironment?.userHome;
return remoteEnvironment ? remoteEnvironment.os : platform.OS;
});
}
get path(): Promise<path.IPath> {
return this._extHostOS.then(os => {
return os === platform.OperatingSystem.Windows ?
path.win32 :
path.posix;
});
}
async fileURI(_path: string): Promise<URI> {
let authority = '';
// normalize to fwd-slashes on windows,
// on other systems bwd-slashes are valid
// filename character, eg /f\oo/ba\r.txt
if ((await this._extHostOS) === platform.OperatingSystem.Windows) {
_path = _path.replace(/\\/g, '/');
}
// check for authority as used in UNC shares
// or use the path as given
if (_path[0] === '/' && _path[1] === '/') {
const idx = _path.indexOf('/', 2);
if (idx === -1) {
authority = _path.substring(2);
_path = '/';
} else {
authority = _path.substring(2, idx);
_path = _path.substring(idx) || '/';
}
}
// return new _URI('file', authority, path, '', '');
return URI.from({
scheme: 'file',
authority,
path: _path,
query: '',
fragment: ''
});
}
get userHome(): Promise<URI> {
return this.remoteAgentService.getEnvironment().then(env => {
// remote: use remote environment userHome
if (env) {
return env.userHome;
}
// local: use the userHome from environment
return this.environmentService.userHome!;
});
}
get userHomeSync(): URI | undefined {
return this._userHomeSync || this.environmentService.userHome;
}
}
registerSingleton(IRemotePathService, RemotePathService, true);

View File

@@ -0,0 +1,22 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IPathService, AbstractPathService } from 'vs/workbench/services/path/common/pathService';
import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService';
export class NativePathService extends AbstractPathService {
constructor(
@IRemoteAgentService remoteAgentService: IRemoteAgentService,
@IWorkbenchEnvironmentService environmentService: INativeWorkbenchEnvironmentService
) {
super(() => environmentService.userHome, remoteAgentService);
}
}
registerSingleton(IPathService, NativePathService, true);

View File

@@ -24,6 +24,7 @@ import { EditorModel } from 'vs/workbench/common/editor';
import { IFilterMetadata, IFilterResult, IGroupFilter, IKeybindingsEditorModel, ISearchResultGroup, ISetting, ISettingMatch, ISettingMatcher, ISettingsEditorModel, ISettingsGroup } from 'vs/workbench/services/preferences/common/preferences';
import { withNullAsUndefined, isArray } from 'vs/base/common/types';
import { FOLDER_SCOPES, WORKSPACE_SCOPES } from 'vs/workbench/services/configuration/common/configuration';
import { createValidator } from 'vs/workbench/services/preferences/common/preferencesValidation';
export const nullRange: IRange = { startLineNumber: -1, startColumn: -1, endLineNumber: -1, endColumn: -1 };
export function isNullRange(range: IRange): boolean { return range.startLineNumber === -1 && range.startColumn === -1 && range.endLineNumber === -1 && range.endColumn === -1; }
@@ -1028,180 +1029,6 @@ class SettingsContentBuilder {
}
}
export function createValidator(prop: IConfigurationPropertySchema): (value: any) => (string | null) {
// Only for array of string
if (prop.type === 'array' && prop.items && !isArray(prop.items) && prop.items.type === 'string') {
const propItems = prop.items;
if (propItems && !isArray(propItems) && propItems.type === 'string') {
const withQuotes = (s: string) => `'` + s + `'`;
return value => {
if (!value) {
return null;
}
let message = '';
const stringArrayValue = value as string[];
if (prop.uniqueItems) {
if (new Set(stringArrayValue).size < stringArrayValue.length) {
message += nls.localize('validations.stringArrayUniqueItems', 'Array has duplicate items');
message += '\n';
}
}
if (prop.minItems && stringArrayValue.length < prop.minItems) {
message += nls.localize('validations.stringArrayMinItem', 'Array must have at least {0} items', prop.minItems);
message += '\n';
}
if (prop.maxItems && stringArrayValue.length > prop.maxItems) {
message += nls.localize('validations.stringArrayMaxItem', 'Array must have at most {0} items', prop.maxItems);
message += '\n';
}
if (typeof propItems.pattern === 'string') {
const patternRegex = new RegExp(propItems.pattern);
stringArrayValue.forEach(v => {
if (!patternRegex.test(v)) {
message +=
propItems.patternErrorMessage ||
nls.localize(
'validations.stringArrayItemPattern',
'Value {0} must match regex {1}.',
withQuotes(v),
withQuotes(propItems.pattern!)
);
}
});
}
const propItemsEnum = propItems.enum;
if (propItemsEnum) {
stringArrayValue.forEach(v => {
if (propItemsEnum.indexOf(v) === -1) {
message += nls.localize(
'validations.stringArrayItemEnum',
'Value {0} is not one of {1}',
withQuotes(v),
'[' + propItemsEnum.map(withQuotes).join(', ') + ']'
);
message += '\n';
}
});
}
return message;
};
}
}
return value => {
let exclusiveMax: number | undefined;
let exclusiveMin: number | undefined;
if (typeof prop.exclusiveMaximum === 'boolean') {
exclusiveMax = prop.exclusiveMaximum ? prop.maximum : undefined;
} else {
exclusiveMax = prop.exclusiveMaximum;
}
if (typeof prop.exclusiveMinimum === 'boolean') {
exclusiveMin = prop.exclusiveMinimum ? prop.minimum : undefined;
} else {
exclusiveMin = prop.exclusiveMinimum;
}
let patternRegex: RegExp | undefined;
if (typeof prop.pattern === 'string') {
patternRegex = new RegExp(prop.pattern);
}
const type: (string | undefined)[] = Array.isArray(prop.type) ? prop.type : [prop.type];
const canBeType = (t: string) => type.indexOf(t) > -1;
const isNullable = canBeType('null');
const isNumeric = (canBeType('number') || canBeType('integer')) && (type.length === 1 || type.length === 2 && isNullable);
const isIntegral = (canBeType('integer')) && (type.length === 1 || type.length === 2 && isNullable);
type Validator<T> = { enabled: boolean, isValid: (value: T) => boolean; message: string };
const numericValidations: Validator<number>[] = isNumeric ? [
{
enabled: exclusiveMax !== undefined && (prop.maximum === undefined || exclusiveMax <= prop.maximum),
isValid: ((value: number) => value < exclusiveMax!),
message: nls.localize('validations.exclusiveMax', "Value must be strictly less than {0}.", exclusiveMax)
},
{
enabled: exclusiveMin !== undefined && (prop.minimum === undefined || exclusiveMin >= prop.minimum),
isValid: ((value: number) => value > exclusiveMin!),
message: nls.localize('validations.exclusiveMin', "Value must be strictly greater than {0}.", exclusiveMin)
},
{
enabled: prop.maximum !== undefined && (exclusiveMax === undefined || exclusiveMax > prop.maximum),
isValid: ((value: number) => value <= prop.maximum!),
message: nls.localize('validations.max', "Value must be less than or equal to {0}.", prop.maximum)
},
{
enabled: prop.minimum !== undefined && (exclusiveMin === undefined || exclusiveMin < prop.minimum),
isValid: ((value: number) => value >= prop.minimum!),
message: nls.localize('validations.min', "Value must be greater than or equal to {0}.", prop.minimum)
},
{
enabled: prop.multipleOf !== undefined,
isValid: ((value: number) => value % prop.multipleOf! === 0),
message: nls.localize('validations.multipleOf', "Value must be a multiple of {0}.", prop.multipleOf)
},
{
enabled: isIntegral,
isValid: ((value: number) => value % 1 === 0),
message: nls.localize('validations.expectedInteger', "Value must be an integer.")
},
].filter(validation => validation.enabled) : [];
const stringValidations: Validator<string>[] = [
{
enabled: prop.maxLength !== undefined,
isValid: ((value: { length: number; }) => value.length <= prop.maxLength!),
message: nls.localize('validations.maxLength', "Value must be {0} or fewer characters long.", prop.maxLength)
},
{
enabled: prop.minLength !== undefined,
isValid: ((value: { length: number; }) => value.length >= prop.minLength!),
message: nls.localize('validations.minLength', "Value must be {0} or more characters long.", prop.minLength)
},
{
enabled: patternRegex !== undefined,
isValid: ((value: string) => patternRegex!.test(value)),
message: prop.patternErrorMessage || nls.localize('validations.regex', "Value must match regex `{0}`.", prop.pattern)
},
].filter(validation => validation.enabled);
if (prop.type === 'string' && stringValidations.length === 0) { return null; }
if (isNullable && value === '') { return ''; }
const errors: string[] = [];
if (isNumeric) {
if (value === '' || isNaN(+value)) {
errors.push(nls.localize('validations.expectedNumeric', "Value must be a number."));
} else {
errors.push(...numericValidations.filter(validator => !validator.isValid(+value)).map(validator => validator.message));
}
}
if (prop.type === 'string') {
errors.push(...stringValidations.filter(validator => !validator.isValid('' + value)).map(validator => validator.message));
}
if (errors.length) {
return prop.errorMessage ? [prop.errorMessage, ...errors].join(' ') : errors.join(' ');
}
return '';
};
}
class RawSettingsContentBuilder extends SettingsContentBuilder {
constructor(private indent: string = '\t') {

View File

@@ -0,0 +1,237 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { JSONSchemaType } from 'vs/base/common/jsonSchema';
import { isArray } from 'vs/base/common/types';
import * as nls from 'vs/nls';
import { IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry';
type Validator<T> = { enabled: boolean, isValid: (value: T) => boolean; message: string };
function canBeType(propTypes: (string | undefined)[], ...types: JSONSchemaType[]): boolean {
return types.some(t => propTypes.includes(t));
}
export function createValidator(prop: IConfigurationPropertySchema): (value: any) => (string | null) {
const type: (string | undefined)[] = Array.isArray(prop.type) ? prop.type : [prop.type];
const isNullable = canBeType(type, 'null');
const isNumeric = (canBeType(type, 'number') || canBeType(type, 'integer')) && (type.length === 1 || type.length === 2 && isNullable);
const numericValidations = getNumericValidators(prop);
const stringValidations = getStringValidators(prop);
const stringArrayValidator = getArrayOfStringValidator(prop);
return value => {
if (prop.type === 'string' && stringValidations.length === 0) { return null; }
if (isNullable && value === '') { return ''; }
const errors: string[] = [];
if (stringArrayValidator) {
const err = stringArrayValidator(value);
if (err) {
errors.push(err);
}
}
if (isNumeric) {
if (value === '' || isNaN(+value)) {
errors.push(nls.localize('validations.expectedNumeric', "Value must be a number."));
} else {
errors.push(...numericValidations.filter(validator => !validator.isValid(+value)).map(validator => validator.message));
}
}
if (prop.type === 'string') {
errors.push(...stringValidations.filter(validator => !validator.isValid('' + value)).map(validator => validator.message));
}
if (errors.length) {
return prop.errorMessage ? [prop.errorMessage, ...errors].join(' ') : errors.join(' ');
}
return '';
};
}
export function getInvalidTypeError(value: any, type: undefined | string | string[]): string | undefined {
let typeArr = Array.isArray(type) ? type : [type];
const isNullable = canBeType(typeArr, 'null');
if (canBeType(typeArr, 'number', 'integer') && (typeArr.length === 1 || typeArr.length === 2 && isNullable)) {
if (value === '' || isNaN(+value)) {
return nls.localize('validations.expectedNumeric', "Value must be a number.");
}
}
const valueType = typeof value;
if (
(valueType === 'boolean' && !canBeType(typeArr, 'boolean')) ||
(valueType === 'object' && !canBeType(typeArr, 'object', 'null', 'array')) ||
(valueType === 'string' && !canBeType(typeArr, 'string', 'number', 'integer')) ||
(typeof parseFloat(value) === 'number' && !isNaN(parseFloat(value)) && !canBeType(typeArr, 'number', 'integer')) ||
(Array.isArray(value) && !canBeType(typeArr, 'array'))
) {
if (typeof type !== 'undefined') {
return nls.localize('invalidTypeError', "Setting has an invalid type, expected {0}. Fix in JSON.", JSON.stringify(type));
}
}
return undefined; // {{SQL CARBON EDIT}} strict-null-checks
}
function getStringValidators(prop: IConfigurationPropertySchema) {
let patternRegex: RegExp | undefined;
if (typeof prop.pattern === 'string') {
patternRegex = new RegExp(prop.pattern);
}
return [
{
enabled: prop.maxLength !== undefined,
isValid: ((value: { length: number; }) => value.length <= prop.maxLength!),
message: nls.localize('validations.maxLength', "Value must be {0} or fewer characters long.", prop.maxLength)
},
{
enabled: prop.minLength !== undefined,
isValid: ((value: { length: number; }) => value.length >= prop.minLength!),
message: nls.localize('validations.minLength', "Value must be {0} or more characters long.", prop.minLength)
},
{
enabled: patternRegex !== undefined,
isValid: ((value: string) => patternRegex!.test(value)),
message: prop.patternErrorMessage || nls.localize('validations.regex', "Value must match regex `{0}`.", prop.pattern)
},
].filter(validation => validation.enabled);
}
function getNumericValidators(prop: IConfigurationPropertySchema): Validator<number>[] {
const type: (string | undefined)[] = Array.isArray(prop.type) ? prop.type : [prop.type];
const isNullable = canBeType(type, 'null');
const isIntegral = (canBeType(type, 'integer')) && (type.length === 1 || type.length === 2 && isNullable);
const isNumeric = canBeType(type, 'number', 'integer') && (type.length === 1 || type.length === 2 && isNullable);
if (!isNumeric) {
return [];
}
let exclusiveMax: number | undefined;
let exclusiveMin: number | undefined;
if (typeof prop.exclusiveMaximum === 'boolean') {
exclusiveMax = prop.exclusiveMaximum ? prop.maximum : undefined;
} else {
exclusiveMax = prop.exclusiveMaximum;
}
if (typeof prop.exclusiveMinimum === 'boolean') {
exclusiveMin = prop.exclusiveMinimum ? prop.minimum : undefined;
} else {
exclusiveMin = prop.exclusiveMinimum;
}
return [
{
enabled: exclusiveMax !== undefined && (prop.maximum === undefined || exclusiveMax <= prop.maximum),
isValid: ((value: number) => value < exclusiveMax!),
message: nls.localize('validations.exclusiveMax', "Value must be strictly less than {0}.", exclusiveMax)
},
{
enabled: exclusiveMin !== undefined && (prop.minimum === undefined || exclusiveMin >= prop.minimum),
isValid: ((value: number) => value > exclusiveMin!),
message: nls.localize('validations.exclusiveMin', "Value must be strictly greater than {0}.", exclusiveMin)
},
{
enabled: prop.maximum !== undefined && (exclusiveMax === undefined || exclusiveMax > prop.maximum),
isValid: ((value: number) => value <= prop.maximum!),
message: nls.localize('validations.max', "Value must be less than or equal to {0}.", prop.maximum)
},
{
enabled: prop.minimum !== undefined && (exclusiveMin === undefined || exclusiveMin < prop.minimum),
isValid: ((value: number) => value >= prop.minimum!),
message: nls.localize('validations.min', "Value must be greater than or equal to {0}.", prop.minimum)
},
{
enabled: prop.multipleOf !== undefined,
isValid: ((value: number) => value % prop.multipleOf! === 0),
message: nls.localize('validations.multipleOf', "Value must be a multiple of {0}.", prop.multipleOf)
},
{
enabled: isIntegral,
isValid: ((value: number) => value % 1 === 0),
message: nls.localize('validations.expectedInteger', "Value must be an integer.")
},
].filter(validation => validation.enabled);
}
function getArrayOfStringValidator(prop: IConfigurationPropertySchema): ((value: any) => (string | null)) | null {
if (prop.type === 'array' && prop.items && !isArray(prop.items) && prop.items.type === 'string') {
const propItems = prop.items;
if (propItems && !isArray(propItems) && propItems.type === 'string') {
const withQuotes = (s: string) => `'` + s + `'`;
return value => {
if (!value) {
return null;
}
let message = '';
const stringArrayValue = value as string[];
if (prop.uniqueItems) {
if (new Set(stringArrayValue).size < stringArrayValue.length) {
message += nls.localize('validations.stringArrayUniqueItems', 'Array has duplicate items');
message += '\n';
}
}
if (prop.minItems && stringArrayValue.length < prop.minItems) {
message += nls.localize('validations.stringArrayMinItem', 'Array must have at least {0} items', prop.minItems);
message += '\n';
}
if (prop.maxItems && stringArrayValue.length > prop.maxItems) {
message += nls.localize('validations.stringArrayMaxItem', 'Array must have at most {0} items', prop.maxItems);
message += '\n';
}
if (typeof propItems.pattern === 'string') {
const patternRegex = new RegExp(propItems.pattern);
stringArrayValue.forEach(v => {
if (!patternRegex.test(v)) {
message +=
propItems.patternErrorMessage ||
nls.localize(
'validations.stringArrayItemPattern',
'Value {0} must match regex {1}.',
withQuotes(v),
withQuotes(propItems.pattern!)
);
}
});
}
const propItemsEnum = propItems.enum;
if (propItemsEnum) {
stringArrayValue.forEach(v => {
if (propItemsEnum.indexOf(v) === -1) {
message += nls.localize(
'validations.stringArrayItemEnum',
'Value {0} is not one of {1}',
withQuotes(v),
'[' + propItemsEnum.map(withQuotes).join(', ') + ']'
);
message += '\n';
}
});
}
return message;
};
}
}
return null;
}

View File

@@ -4,8 +4,8 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { createValidator } from 'vs/workbench/services/preferences/common/preferencesModels';
import { IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry';
import { createValidator } from 'vs/workbench/services/preferences/common/preferencesValidation';
suite('Preferences Model test', () => {

View File

@@ -152,6 +152,7 @@ export class ProgressService extends Disposable implements IProgressService {
const statusEntryProperties: IStatusbarEntry = {
text: `$(sync~spin) ${text}`,
ariaLabel: text,
tooltip: title,
command: progressCommand
};
@@ -419,12 +420,12 @@ export class ProgressService extends Disposable implements IProgressService {
// show in viewlet
const promise = this.withCompositeProgress(this.viewsService.getProgressIndicator(viewId), task, options);
const location = this.viewDescriptorService.getViewLocation(viewId);
const location = this.viewDescriptorService.getViewLocationById(viewId);
if (location !== ViewContainerLocation.Sidebar) {
return promise;
}
const viewletId = this.viewDescriptorService.getViewContainer(viewId)?.id;
const viewletId = this.viewDescriptorService.getViewContainerByViewId(viewId)?.id;
if (viewletId === undefined) {
return promise;
}

View File

@@ -344,8 +344,13 @@ export interface ISearchConfigurationProperties {
maintainFileSearchCache: boolean;
collapseResults: 'auto' | 'alwaysCollapse' | 'alwaysExpand';
searchOnType: boolean;
seedOnFocus: boolean;
seedWithNearestWord: boolean;
searchOnTypeDebouncePeriod: number;
searchEditor: { doubleClickBehaviour: 'selectWord' | 'goToLocation' | 'openLocationToSide' };
searchEditor: {
doubleClickBehaviour: 'selectWord' | 'goToLocation' | 'openLocationToSide',
experimental: { reusePriorSearchConfiguration: boolean }
};
sortOrder: SearchSortOrder;
}
@@ -614,9 +619,7 @@ export class QueryGlobTester {
* Guaranteed async.
*/
includedInQuery(testPath: string, basename?: string, hasSibling?: (name: string) => boolean | Promise<boolean>): Promise<boolean> {
const excludeP = this._parsedExcludeExpression ?
Promise.resolve(this._parsedExcludeExpression(testPath, basename, hasSibling)).then(result => !!result) :
Promise.resolve(false);
const excludeP = Promise.resolve(this._parsedExcludeExpression(testPath, basename, hasSibling)).then(result => !!result);
return excludeP.then(excluded => {
if (excluded) {

View File

@@ -203,10 +203,15 @@ export class SearchService extends Disposable implements ISearchService {
this.fileSearchProviders.get(scheme) :
this.textSearchProviders.get(scheme);
if (!provider && scheme === 'file') {
if (!provider && scheme === Schemas.file) {
diskSearchQueries.push(...schemeFQs);
} else {
if (!provider) {
if (scheme !== Schemas.vscodeRemote) {
console.warn(`No search provider registered for scheme: ${scheme}`);
return;
}
console.warn(`No search provider registered for scheme: ${scheme}, waiting`);
provider = await this.waitForProvider(query.type, scheme);
}

View File

@@ -28,6 +28,11 @@ export interface IStatusbarEntry {
*/
readonly text: string;
/**
* Text to be read out by the screen reader.
*/
readonly ariaLabel: string;
/**
* An optional tooltip text to show when you hover over the entry
*/

View File

@@ -31,7 +31,7 @@ import { ITextModelService, IResolvedTextEditorModel } from 'vs/editor/common/se
import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { suggestFilename } from 'vs/base/common/mime';
import { IRemotePathService } from 'vs/workbench/services/path/common/remotePathService';
import { IPathService } from 'vs/workbench/services/path/common/pathService';
import { isValidBasename } from 'vs/base/common/extpath';
import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
@@ -68,7 +68,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex
@IFilesConfigurationService protected readonly filesConfigurationService: IFilesConfigurationService,
@ITextModelService private readonly textModelService: ITextModelService,
@ICodeEditorService private readonly codeEditorService: ICodeEditorService,
@IRemotePathService private readonly remotePathService: IRemotePathService,
@IPathService private readonly pathService: IPathService,
@IWorkingCopyFileService private readonly workingCopyFileService: IWorkingCopyFileService
) {
super();
@@ -435,7 +435,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex
// Try to place where last active file was if any
// Otherwise fallback to user home
return joinPath(this.fileDialogService.defaultFilePath() || (await this.remotePathService.userHome), suggestedFilename);
return joinPath(this.fileDialogService.defaultFilePath() || (await this.pathService.userHome), suggestedFilename);
}
//#endregion

View File

@@ -36,7 +36,7 @@ import { IDialogService, IFileDialogService } from 'vs/platform/dialogs/common/d
import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { IRemotePathService } from 'vs/workbench/services/path/common/remotePathService';
import { IPathService } from 'vs/workbench/services/path/common/pathService';
import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService';
import { ILogService } from 'vs/platform/log/common/log';
@@ -57,11 +57,11 @@ export class NativeTextFileService extends AbstractTextFileService {
@IFilesConfigurationService filesConfigurationService: IFilesConfigurationService,
@ITextModelService textModelService: ITextModelService,
@ICodeEditorService codeEditorService: ICodeEditorService,
@IRemotePathService remotePathService: IRemotePathService,
@IPathService pathService: IPathService,
@IWorkingCopyFileService workingCopyFileService: IWorkingCopyFileService,
@ILogService private readonly logService: ILogService
) {
super(fileService, untitledTextEditorService, lifecycleService, instantiationService, modelService, environmentService, dialogService, fileDialogService, textResourceConfigurationService, filesConfigurationService, textModelService, codeEditorService, remotePathService, workingCopyFileService);
super(fileService, untitledTextEditorService, lifecycleService, instantiationService, modelService, environmentService, dialogService, fileDialogService, textResourceConfigurationService, filesConfigurationService, textModelService, codeEditorService, pathService, workingCopyFileService);
}
private _encoding: EncodingOracle | undefined;

View File

@@ -28,7 +28,7 @@ import { readFileSync, statSync } from 'fs';
import { detectEncodingByBOM } from 'vs/base/test/node/encoding/encoding.test';
import { workbenchInstantiationService, TestNativeTextFileServiceWithEncodingOverrides } from 'vs/workbench/test/electron-browser/workbenchTestServices';
suite('Files - TextFileService i/o', () => {
suite('Files - TextFileService i/o', function () {
const parentDir = getRandomTestPath(tmpdir(), 'vsctests', 'textfileservice');
const disposables = new DisposableStore();
@@ -36,6 +36,14 @@ suite('Files - TextFileService i/o', () => {
let service: ITextFileService;
let testDir: string;
// Given issues such as https://github.com/microsoft/vscode/issues/78602
// and https://github.com/microsoft/vscode/issues/92334 we see random test
// failures when accessing the native file system. To diagnose further, we
// retry node.js file access tests up to 3 times to rule out any random disk
// issue and increase the timeout.
this.retries(3);
this.timeout(1000 * 10);
setup(async () => {
const instantiationService = workbenchInstantiationService();

View File

@@ -121,7 +121,6 @@ export class FileIconThemeData implements IWorkbenchFileIconTheme {
case 'label':
case 'description':
case 'settingsId':
case 'extensionData':
case 'styleSheetContent':
case 'hasFileIcons':
case 'hidesExplorerArrows':
@@ -132,6 +131,9 @@ export class FileIconThemeData implements IWorkbenchFileIconTheme {
case 'location':
theme.location = URI.revive(data.location);
break;
case 'extensionData':
theme.extensionData = ExtensionData.fromJSONObject(data.extensionData);
break;
}
}
return theme;
@@ -146,11 +148,12 @@ export class FileIconThemeData implements IWorkbenchFileIconTheme {
label: this.label,
description: this.description,
settingsId: this.settingsId,
location: this.location,
location: this.location?.toJSON(),
styleSheetContent: this.styleSheetContent,
hasFileIcons: this.hasFileIcons,
hasFolderIcons: this.hasFolderIcons,
hidesExplorerArrows: this.hidesExplorerArrows,
extensionData: ExtensionData.toJSONObject(this.extensionData),
watch: this.watch
});
storageService.store(PERSISTED_FILE_ICON_THEME_STORAGE_KEY, data, StorageScope.GLOBAL);

View File

@@ -108,7 +108,6 @@ export class ProductIconThemeData implements IWorkbenchProductIconTheme {
case 'label':
case 'description':
case 'settingsId':
case 'extensionData':
case 'styleSheetContent':
case 'watch':
(theme as any)[key] = data[key];
@@ -116,6 +115,9 @@ export class ProductIconThemeData implements IWorkbenchProductIconTheme {
case 'location':
theme.location = URI.revive(data.location);
break;
case 'extensionData':
theme.extensionData = ExtensionData.fromJSONObject(data.extensionData);
break;
}
}
return theme;
@@ -130,9 +132,10 @@ export class ProductIconThemeData implements IWorkbenchProductIconTheme {
label: this.label,
description: this.description,
settingsId: this.settingsId,
location: this.location,
location: this.location?.toJSON(),
styleSheetContent: this.styleSheetContent,
watch: this.watch
watch: this.watch,
extensionData: ExtensionData.toJSONObject(this.extensionData),
});
storageService.store(PERSISTED_PRODUCT_ICON_THEME_STORAGE_KEY, data, StorageScope.GLOBAL);
}

View File

@@ -143,69 +143,8 @@ export class WorkbenchThemeService implements IWorkbenchThemeService {
this.initialize().then(undefined, errors.onUnexpectedError).then(_ => {
this.installConfigurationListener();
this.installPreferredSchemeListener();
this.installRegistryListeners();
});
let prevColorId: string | undefined = undefined;
// update settings schema setting for theme specific settings
this.colorThemeRegistry.onDidChange(async event => {
updateColorThemeConfigurationSchemas(event.themes);
if (await this.restoreColorTheme()) { // checks if theme from settings exists and is set
// restore theme
if (this.currentColorTheme.id === DEFAULT_COLOR_THEME_ID && !types.isUndefined(prevColorId) && await this.colorThemeRegistry.findThemeById(prevColorId)) {
// restore theme
this.setColorTheme(prevColorId, 'auto');
prevColorId = undefined;
} else if (event.added.some(t => t.settingsId === this.currentColorTheme.settingsId)) {
this.reloadCurrentColorTheme();
}
} else if (event.removed.some(t => t.settingsId === this.currentColorTheme.settingsId)) {
// current theme is no longer available
prevColorId = this.currentColorTheme.id;
this.setColorTheme(DEFAULT_COLOR_THEME_ID, 'auto');
}
});
let prevFileIconId: string | undefined = undefined;
this.fileIconThemeRegistry.onDidChange(async event => {
updateFileIconThemeConfigurationSchemas(event.themes);
if (await this.restoreFileIconTheme()) { // checks if theme from settings exists and is set
// restore theme
if (this.currentFileIconTheme.id === DEFAULT_FILE_ICON_THEME_ID && !types.isUndefined(prevFileIconId) && await this.fileIconThemeRegistry.findThemeById(prevFileIconId)) {
this.setFileIconTheme(prevFileIconId, 'auto');
prevFileIconId = undefined;
} else if (event.added.some(t => t.settingsId === this.currentFileIconTheme.settingsId)) {
this.reloadCurrentFileIconTheme();
}
} else if (event.removed.some(t => t.settingsId === this.currentFileIconTheme.settingsId)) {
// current theme is no longer available
prevFileIconId = this.currentFileIconTheme.id;
this.setFileIconTheme(DEFAULT_FILE_ICON_THEME_ID, 'auto');
}
});
let prevProductIconId: string | undefined = undefined;
this.productIconThemeRegistry.onDidChange(async event => {
updateProductIconThemeConfigurationSchemas(event.themes);
if (await this.restoreProductIconTheme()) { // checks if theme from settings exists and is set
// restore theme
if (this.currentProductIconTheme.id === DEFAULT_PRODUCT_ICON_THEME_ID && !types.isUndefined(prevProductIconId) && await this.productIconThemeRegistry.findThemeById(prevProductIconId)) {
this.setProductIconTheme(prevProductIconId, 'auto');
prevProductIconId = undefined;
} else if (event.added.some(t => t.settingsId === this.currentProductIconTheme.settingsId)) {
this.reloadCurrentProductIconTheme();
}
} else if (event.removed.some(t => t.settingsId === this.currentProductIconTheme.settingsId)) {
// current theme is no longer available
prevProductIconId = this.currentProductIconTheme.id;
this.setProductIconTheme(DEFAULT_PRODUCT_ICON_THEME_ID, 'auto');
}
});
}
public get onDidColorThemeChange(): Event<IWorkbenchColorTheme> {
return this.onColorThemeChange.event;
}
private initialize(): Promise<[IWorkbenchColorTheme | null, IWorkbenchFileIconTheme | null, IWorkbenchProductIconTheme | null]> {
@@ -293,6 +232,74 @@ export class WorkbenchThemeService implements IWorkbenchThemeService {
});
}
private installRegistryListeners(): Promise<any> {
let prevColorId: string | undefined = undefined;
// update settings schema setting for theme specific settings
this.colorThemeRegistry.onDidChange(async event => {
updateColorThemeConfigurationSchemas(event.themes);
if (await this.restoreColorTheme()) { // checks if theme from settings exists and is set
// restore theme
if (this.currentColorTheme.id === DEFAULT_COLOR_THEME_ID && !types.isUndefined(prevColorId) && await this.colorThemeRegistry.findThemeById(prevColorId)) {
// restore theme
this.setColorTheme(prevColorId, 'auto');
prevColorId = undefined;
} else if (event.added.some(t => t.settingsId === this.currentColorTheme.settingsId)) {
this.reloadCurrentColorTheme();
}
} else if (event.removed.some(t => t.settingsId === this.currentColorTheme.settingsId)) {
// current theme is no longer available
prevColorId = this.currentColorTheme.id;
this.setColorTheme(DEFAULT_COLOR_THEME_ID, 'auto');
}
});
let prevFileIconId: string | undefined = undefined;
this.fileIconThemeRegistry.onDidChange(async event => {
updateFileIconThemeConfigurationSchemas(event.themes);
if (await this.restoreFileIconTheme()) { // checks if theme from settings exists and is set
// restore theme
if (this.currentFileIconTheme.id === DEFAULT_FILE_ICON_THEME_ID && !types.isUndefined(prevFileIconId) && await this.fileIconThemeRegistry.findThemeById(prevFileIconId)) {
this.setFileIconTheme(prevFileIconId, 'auto');
prevFileIconId = undefined;
} else if (event.added.some(t => t.settingsId === this.currentFileIconTheme.settingsId)) {
this.reloadCurrentFileIconTheme();
}
} else if (event.removed.some(t => t.settingsId === this.currentFileIconTheme.settingsId)) {
// current theme is no longer available
prevFileIconId = this.currentFileIconTheme.id;
this.setFileIconTheme(DEFAULT_FILE_ICON_THEME_ID, 'auto');
}
});
let prevProductIconId: string | undefined = undefined;
this.productIconThemeRegistry.onDidChange(async event => {
updateProductIconThemeConfigurationSchemas(event.themes);
if (await this.restoreProductIconTheme()) { // checks if theme from settings exists and is set
// restore theme
if (this.currentProductIconTheme.id === DEFAULT_PRODUCT_ICON_THEME_ID && !types.isUndefined(prevProductIconId) && await this.productIconThemeRegistry.findThemeById(prevProductIconId)) {
this.setProductIconTheme(prevProductIconId, 'auto');
prevProductIconId = undefined;
} else if (event.added.some(t => t.settingsId === this.currentProductIconTheme.settingsId)) {
this.reloadCurrentProductIconTheme();
}
} else if (event.removed.some(t => t.settingsId === this.currentProductIconTheme.settingsId)) {
// current theme is no longer available
prevProductIconId = this.currentProductIconTheme.id;
this.setProductIconTheme(DEFAULT_PRODUCT_ICON_THEME_ID, 'auto');
}
});
return Promise.all([this.getColorThemes(), this.getFileIconThemes(), this.getProductIconThemes()]).then(([ct, fit, pit]) => {
updateColorThemeConfigurationSchemas(ct);
updateFileIconThemeConfigurationSchemas(fit);
updateProductIconThemeConfigurationSchemas(pit);
});
}
// preferred scheme handling
private installPreferredSchemeListener() {
@@ -343,6 +350,10 @@ export class WorkbenchThemeService implements IWorkbenchThemeService {
return this.colorThemeRegistry.getThemes();
}
public get onDidColorThemeChange(): Event<IWorkbenchColorTheme> {
return this.onColorThemeChange.event;
}
public setColorTheme(themeId: string | undefined, settingsTarget: ConfigurationTarget | undefined | 'auto'): Promise<IWorkbenchColorTheme | null> {
if (!themeId) {
return Promise.resolve(null);

View File

@@ -481,7 +481,8 @@ export class ColorThemeData implements IWorkbenchColorTheme {
selector: this.id.split(' ').join('.'), // to not break old clients
themeTokenColors: this.themeTokenColors,
tokenStylingRules: this.tokenStylingRules.map(TokenStylingRule.toJSONObject),
extensionData: this.extensionData,
extensionData: ExtensionData.toJSONObject(this.extensionData),
location: this.location?.toJSON(),
themeSemanticHighlighting: this.themeSemanticHighlighting,
colorMap: colorMapData,
watch: this.watch
@@ -543,7 +544,7 @@ export class ColorThemeData implements IWorkbenchColorTheme {
}
break;
case 'themeTokenColors':
case 'id': case 'label': case 'settingsId': case 'extensionData': case 'watch': case 'themeSemanticHighlighting':
case 'id': case 'label': case 'settingsId': case 'watch': case 'themeSemanticHighlighting':
(theme as any)[key] = data[key];
break;
case 'tokenStylingRules':
@@ -557,6 +558,12 @@ export class ColorThemeData implements IWorkbenchColorTheme {
}
}
break;
case 'location':
theme.location = URI.revive(data.location);
break;
case 'extensionData':
theme.extensionData = ExtensionData.fromJSONObject(data.extensionData);
break;
}
}
if (!theme.id || !theme.settingsId) {

View File

@@ -7,7 +7,7 @@ import * as nls from 'vs/nls';
import { Registry } from 'vs/platform/registry/common/platform';
import { Extensions as JSONExtensions, IJSONContributionRegistry } from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
import { IJSONSchema } from 'vs/base/common/jsonSchema';
import { getIconRegistry } from 'vs/platform/theme/common/iconRegistry';
import { iconsSchemaId } from 'vs/platform/theme/common/iconRegistry';
const schemaId = 'vscode://schemas/product-icon-theme';
@@ -67,9 +67,8 @@ const schema: IJSONSchema = {
}
},
iconDefinitions: {
type: 'object',
description: nls.localize('schema.iconDefinitions', 'Assocation of icon name to a font character.'),
properties: getIconRegistry().getIconSchema().properties,
description: nls.localize('schema.iconDefinitions', 'Association of icon name to a font character.'),
$ref: iconsSchemaId,
additionalProperties: false
}
}

View File

@@ -9,6 +9,7 @@ import { Color } from 'vs/base/common/color';
import { IColorTheme, IThemeService, IFileIconTheme } from 'vs/platform/theme/common/themeService';
import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
import { URI } from 'vs/base/common/uri';
import { isBoolean, isString } from 'vs/base/common/types';
export const IWorkbenchThemeService = createDecorator<IWorkbenchThemeService>('themeService');
@@ -119,6 +120,18 @@ export interface ExtensionData {
extensionLocation: URI;
}
export namespace ExtensionData {
export function toJSONObject(d: ExtensionData | undefined): any {
return d && { _extensionId: d.extensionId, _extensionIsBuiltin: d.extensionIsBuiltin, _extensionLocation: d.extensionLocation.toJSON(), _extensionName: d.extensionName, _extensionPublisher: d.extensionPublisher };
}
export function fromJSONObject(o: any): ExtensionData | undefined {
if (o && isString(o._extensionId) && isBoolean(o._extensionIsBuiltin) && isString(o._extensionLocation) && isString(o._extensionName) && isString(o._extensionPublisher)) {
return { extensionId: o._extensionId, extensionIsBuiltin: o._extensionIsBuiltin, extensionLocation: URI.revive(o._extensionLocation), extensionName: o._extensionName, extensionPublisher: o._extensionPublisher };
}
return undefined;
}
}
export interface IThemeExtensionPoint {
id: string;
label?: string;

View File

@@ -3,8 +3,8 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ViewContainerLocation, IViewDescriptorService, ViewContainer, IViewsRegistry, IViewContainersRegistry, IViewDescriptor, Extensions as ViewExtensions, IViewDescriptorCollection } from 'vs/workbench/common/views';
import { IContextKey, RawContextKey, IContextKeyService, IReadableSet, IContextKeyChangeEvent } from 'vs/platform/contextkey/common/contextkey';
import { ViewContainerLocation, IViewDescriptorService, ViewContainer, IViewsRegistry, IViewContainersRegistry, IViewDescriptor, Extensions as ViewExtensions } from 'vs/workbench/common/views';
import { IContextKey, RawContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IStorageService, StorageScope, IWorkspaceStorageChangeEvent } from 'vs/platform/storage/common/storage';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { Registry } from 'vs/platform/registry/common/platform';
@@ -13,167 +13,15 @@ import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneCont
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { Event, Emitter } from 'vs/base/common/event';
import { firstIndex } from 'vs/base/common/arrays';
import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
class CounterSet<T> implements IReadableSet<T> {
private map = new Map<T, number>();
add(value: T): CounterSet<T> {
this.map.set(value, (this.map.get(value) || 0) + 1);
return this;
}
delete(value: T): boolean {
let counter = this.map.get(value) || 0;
if (counter === 0) {
return false;
}
counter--;
if (counter === 0) {
this.map.delete(value);
} else {
this.map.set(value, counter);
}
return true;
}
has(value: T): boolean {
return this.map.has(value);
}
}
interface IViewItem {
viewDescriptor: IViewDescriptor;
active: boolean;
}
class ViewDescriptorCollection extends Disposable implements IViewDescriptorCollection {
private contextKeys = new CounterSet<string>();
private items: IViewItem[] = [];
private _onDidChangeViews: Emitter<{ added: IViewDescriptor[], removed: IViewDescriptor[]; }> = this._register(new Emitter<{ added: IViewDescriptor[], removed: IViewDescriptor[]; }>());
readonly onDidChangeViews: Event<{ added: IViewDescriptor[], removed: IViewDescriptor[]; }> = this._onDidChangeViews.event;
private _onDidChangeActiveViews: Emitter<{ added: IViewDescriptor[], removed: IViewDescriptor[]; }> = this._register(new Emitter<{ added: IViewDescriptor[], removed: IViewDescriptor[]; }>());
readonly onDidChangeActiveViews: Event<{ added: IViewDescriptor[], removed: IViewDescriptor[]; }> = this._onDidChangeActiveViews.event;
get activeViewDescriptors(): IViewDescriptor[] {
return this.items
.filter(i => i.active)
.map(i => i.viewDescriptor);
}
get allViewDescriptors(): IViewDescriptor[] {
return this.items.map(i => i.viewDescriptor);
}
constructor(
@IContextKeyService private readonly contextKeyService: IContextKeyService,
) {
super();
this._register(Event.filter(contextKeyService.onDidChangeContext, e => e.affectsSome(this.contextKeys))(this.onContextChanged, this));
}
addViews(viewDescriptors: IViewDescriptor[]): void {
const added: IViewDescriptor[] = [];
for (const viewDescriptor of viewDescriptors) {
const item = {
viewDescriptor,
active: this.isViewDescriptorActive(viewDescriptor) // TODO: should read from some state?
};
this.items.push(item);
if (viewDescriptor.when) {
for (const key of viewDescriptor.when.keys()) {
this.contextKeys.add(key);
}
}
if (item.active) {
added.push(viewDescriptor);
}
}
this._onDidChangeViews.fire({ added: viewDescriptors, removed: [] });
if (added.length) {
this._onDidChangeActiveViews.fire({ added, removed: [] });
}
}
removeViews(viewDescriptors: IViewDescriptor[]): void {
const removed: IViewDescriptor[] = [];
for (const viewDescriptor of viewDescriptors) {
const index = firstIndex(this.items, i => i.viewDescriptor.id === viewDescriptor.id);
if (index === -1) {
continue;
}
const item = this.items[index];
this.items.splice(index, 1);
if (viewDescriptor.when) {
for (const key of viewDescriptor.when.keys()) {
this.contextKeys.delete(key);
}
}
if (item.active) {
removed.push(viewDescriptor);
}
}
this._onDidChangeViews.fire({ added: [], removed: viewDescriptors });
if (removed.length) {
this._onDidChangeActiveViews.fire({ added: [], removed });
}
}
private onContextChanged(event: IContextKeyChangeEvent): void {
const removed: IViewDescriptor[] = [];
const added: IViewDescriptor[] = [];
for (const item of this.items) {
const active = this.isViewDescriptorActive(item.viewDescriptor);
if (item.active !== active) {
if (active) {
added.push(item.viewDescriptor);
} else {
removed.push(item.viewDescriptor);
}
}
item.active = active;
}
if (added.length || removed.length) {
this._onDidChangeActiveViews.fire({ added, removed });
}
}
private isViewDescriptorActive(viewDescriptor: IViewDescriptor): boolean {
return !viewDescriptor.when || this.contextKeyService.contextMatchesRules(viewDescriptor.when);
}
}
import { generateUuid } from 'vs/base/common/uuid';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ViewContainerModel } from 'vs/workbench/services/views/common/viewContainerModel';
interface ICachedViewContainerInfo {
containerId: string;
location?: ViewContainerLocation;
sourceViewId?: string;
}
export class ViewDescriptorService extends Disposable implements IViewDescriptorService {
@@ -189,7 +37,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor
private readonly _onDidChangeLocation: Emitter<{ views: IViewDescriptor[], from: ViewContainerLocation, to: ViewContainerLocation }> = this._register(new Emitter<{ views: IViewDescriptor[], from: ViewContainerLocation, to: ViewContainerLocation }>());
readonly onDidChangeLocation: Event<{ views: IViewDescriptor[], from: ViewContainerLocation, to: ViewContainerLocation }> = this._onDidChangeLocation.event;
private readonly viewDescriptorCollections: Map<ViewContainer, { viewDescriptorCollection: ViewDescriptorCollection, disposable: IDisposable; }>;
private readonly viewContainerModels: Map<ViewContainer, { viewContainerModel: ViewContainerModel, disposable: IDisposable; }>;
private readonly activeViewContextKeys: Map<string, IContextKey<boolean>>;
private readonly movableViewContextKeys: Map<string, IContextKey<boolean>>;
private readonly defaultViewLocationContextKeys: Map<string, IContextKey<boolean>>;
@@ -198,7 +46,6 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor
private readonly viewContainersRegistry: IViewContainersRegistry;
private cachedViewInfo: Map<string, ICachedViewContainerInfo>;
private generatedContainerSourceViewIds: Map<string, string>;
private _cachedViewPositionsValue: string | undefined;
private get cachedViewPositionsValue(): string {
@@ -217,6 +64,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor
}
constructor(
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@IStorageService private readonly storageService: IStorageService,
@IExtensionService private readonly extensionService: IExtensionService,
@@ -226,14 +74,13 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor
super();
storageKeysSyncRegistryService.registerStorageKey({ key: ViewDescriptorService.CACHED_VIEW_POSITIONS, version: 1 });
this.viewDescriptorCollections = new Map<ViewContainer, { viewDescriptorCollection: ViewDescriptorCollection, disposable: IDisposable; }>();
this.viewContainerModels = new Map<ViewContainer, { viewContainerModel: ViewContainerModel, disposable: IDisposable; }>();
this.activeViewContextKeys = new Map<string, IContextKey<boolean>>();
this.movableViewContextKeys = new Map<string, IContextKey<boolean>>();
this.defaultViewLocationContextKeys = new Map<string, IContextKey<boolean>>();
this.viewContainersRegistry = Registry.as<IViewContainersRegistry>(ViewExtensions.ViewContainersRegistry);
this.viewsRegistry = Registry.as<IViewsRegistry>(ViewExtensions.ViewsRegistry);
this.generatedContainerSourceViewIds = new Map<string, string>();
this.cachedViewInfo = this.getCachedViewPositions();
@@ -251,8 +98,8 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor
this._register(this.viewContainersRegistry.onDidRegister(({ viewContainer }) => this.onDidRegisterViewContainer(viewContainer)));
this._register(this.viewContainersRegistry.onDidDeregister(({ viewContainer }) => this.onDidDeregisterViewContainer(viewContainer)));
this._register(toDisposable(() => {
this.viewDescriptorCollections.forEach(({ disposable }) => disposable.dispose());
this.viewDescriptorCollections.clear();
this.viewContainerModels.forEach(({ disposable }) => disposable.dispose());
this.viewContainerModels.clear();
}));
this._register(this.storageService.onDidChangeStorage((e) => { this.onDidStorageChange(e); }));
@@ -267,13 +114,12 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor
const containerData = groupedViews.get(containerId)!;
// The container has not been registered yet
if (!viewContainer || !this.viewDescriptorCollections.has(viewContainer)) {
if (!viewContainer || !this.viewContainerModels.has(viewContainer)) {
if (containerData.cachedContainerInfo && this.shouldGenerateContainer(containerData.cachedContainerInfo)) {
const containerInfo = containerData.cachedContainerInfo;
const sourceViewDescriptor = this.viewsRegistry.getView(containerInfo.sourceViewId!);
if (sourceViewDescriptor && !this.viewContainersRegistry.get(containerId)) {
this.registerViewContainerForSingleView(sourceViewDescriptor, containerInfo.location!);
if (!this.viewContainersRegistry.get(containerId)) {
this.registerGeneratedViewContainer(containerInfo.location!, containerId);
}
}
@@ -290,7 +136,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor
const viewContainer = this.viewContainersRegistry.get(viewContainerId);
// The container has not been registered yet
if (!viewContainer || !this.viewDescriptorCollections.has(viewContainer)) {
if (!viewContainer || !this.viewContainerModels.has(viewContainer)) {
continue;
}
@@ -309,18 +155,14 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor
// check if we should generate this container
if (this.shouldGenerateContainer(containerInfo)) {
const sourceView = this.getViewDescriptor(containerInfo.sourceViewId!);
if (sourceView) {
this.registerViewContainerForSingleView(sourceView, containerInfo.location!);
continue;
}
this.registerGeneratedViewContainer(containerInfo.location!, containerId);
continue;
}
if (fallbackToDefault) {
// check if view has been registered to default location
const viewContainer = this.viewsRegistry.getViewContainer(viewId);
const viewDescriptor = this.getViewDescriptor(viewId);
const viewDescriptor = this.getViewDescriptorById(viewId);
if (viewContainer && viewDescriptor) {
this.addViews(viewContainer, [viewDescriptor]);
@@ -354,7 +196,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor
}
private shouldGenerateContainer(containerInfo: ICachedViewContainerInfo): boolean {
return !!containerInfo.sourceViewId && containerInfo.location !== undefined;
return containerInfo.containerId.startsWith(ViewDescriptorService.COMMON_CONTAINER_ID_PREFIX) && containerInfo.location !== undefined;
}
private onDidDeregisterViews(views: IViewDescriptor[], viewContainer: ViewContainer): void {
@@ -379,11 +221,11 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor
return ret;
}
getViewDescriptor(viewId: string): IViewDescriptor | null {
getViewDescriptorById(viewId: string): IViewDescriptor | null {
return this.viewsRegistry.getView(viewId);
}
getViewLocation(viewId: string): ViewContainerLocation | null {
getViewLocationById(viewId: string): ViewContainerLocation | null {
const cachedInfo = this.cachedViewInfo.get(viewId);
if (cachedInfo && cachedInfo.location) {
@@ -401,7 +243,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor
return this.getViewContainerLocation(container);
}
getViewContainer(viewId: string): ViewContainer | null {
getViewContainerByViewId(viewId: string): ViewContainer | null {
const containerId = this.cachedViewInfo.get(viewId)?.containerId;
return containerId ?
@@ -413,16 +255,30 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor
return this.viewContainersRegistry.getViewContainerLocation(viewContainer);
}
getDefaultContainer(viewId: string): ViewContainer | null {
getDefaultContainerById(viewId: string): ViewContainer | null {
return this.viewsRegistry.getViewContainer(viewId) ?? null;
}
getViewDescriptors(container: ViewContainer): ViewDescriptorCollection {
return this.getOrRegisterViewDescriptorCollection(container);
getViewContainerModel(container: ViewContainer): ViewContainerModel {
return this.getOrRegisterViewContainerModel(container);
}
getViewContainerById(id: string): ViewContainer | null {
return this.viewContainersRegistry.get(id) || null;
}
getViewContainersByLocation(location: ViewContainerLocation): ViewContainer[] {
return this.viewContainersRegistry.getViewContainers(location);
}
getViewContainers(): ViewContainer[] {
return this.viewContainersRegistry.all;
}
moveViewContainerToLocation(viewContainer: ViewContainer, location: ViewContainerLocation): void {
// to be implemented
}
moveViewToLocation(view: IViewDescriptor, location: ViewContainerLocation): void {
let container = this.registerViewContainerForSingleView(view, location);
let container = this.registerGeneratedViewContainer(location);
this.moveViewsToContainer([view], container);
}
@@ -431,7 +287,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor
return;
}
const from = this.getViewContainer(views[0].id);
const from = this.getViewContainerByViewId(views[0].id);
const to = viewContainer;
if (from && to && from !== to) {
@@ -494,14 +350,15 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor
}
}
private registerViewContainerForSingleView(sourceView: IViewDescriptor, location: ViewContainerLocation): ViewContainer {
const id = this.generateContainerIdFromSourceViewId(sourceView.id, location);
private registerGeneratedViewContainer(location: ViewContainerLocation, existingId?: string): ViewContainer {
const id = existingId || this.generateContainerId(location);
return this.viewContainersRegistry.registerViewContainer({
id,
ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [id, `${id}.state`, { mergeViewWithContainerWhenSingleView: true }]),
name: sourceView.name,
icon: location === ViewContainerLocation.Sidebar ? sourceView.containerIcon || 'codicon-window' : undefined,
ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [id, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]),
name: 'Custom Views', // we don't want to see this, so no need to localize
icon: location === ViewContainerLocation.Sidebar ? 'codicon-window' : undefined,
storageId: `${id}.state`,
hideIfEmpty: true
}, location);
}
@@ -527,26 +384,22 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor
const newCachedPositions = this.getCachedViewPositions();
for (let viewId of newCachedPositions.keys()) {
const viewDescriptor = this.getViewDescriptor(viewId);
const viewDescriptor = this.getViewDescriptorById(viewId);
if (!viewDescriptor) {
continue;
}
const prevViewContainer = this.getViewContainer(viewId);
const prevViewContainer = this.getViewContainerByViewId(viewId);
const newViewContainerInfo = newCachedPositions.get(viewId)!;
// Verify if we need to create the destination container
if (newViewContainerInfo.sourceViewId) {
const sourceViewDescriptor = this.getViewDescriptor(newViewContainerInfo.sourceViewId);
if (!this.viewContainersRegistry.get(newViewContainerInfo.containerId) && sourceViewDescriptor) {
this.registerViewContainerForSingleView(sourceViewDescriptor, newViewContainerInfo.location!);
}
if (!this.viewContainersRegistry.get(newViewContainerInfo.containerId)) {
this.registerGeneratedViewContainer(newViewContainerInfo.location!, newViewContainerInfo.containerId);
}
// Try moving to the new container
const newViewContainer = this.viewContainersRegistry.get(newViewContainerInfo.containerId);
if (prevViewContainer && newViewContainer && newViewContainer !== prevViewContainer) {
const viewDescriptor = this.getViewDescriptor(viewId);
const viewDescriptor = this.getViewDescriptorById(viewId);
if (viewDescriptor) {
this.moveViews([viewDescriptor], prevViewContainer, newViewContainer);
}
@@ -555,11 +408,11 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor
// If a value is not present in the cache, it must be reset to default
this.viewContainersRegistry.all.forEach(viewContainer => {
const viewDescriptorCollection = this.getViewDescriptors(viewContainer);
viewDescriptorCollection.allViewDescriptors.forEach(viewDescriptor => {
const viewContainerModel = this.getViewContainerModel(viewContainer);
viewContainerModel.allViewDescriptors.forEach(viewDescriptor => {
if (!newCachedPositions.has(viewDescriptor.id)) {
const currentContainer = this.getViewContainer(viewDescriptor.id);
const defaultContainer = this.getDefaultContainer(viewDescriptor.id);
const currentContainer = this.getViewContainerByViewId(viewDescriptor.id);
const defaultContainer = this.getDefaultContainerById(viewDescriptor.id);
if (currentContainer && defaultContainer && currentContainer !== defaultContainer) {
this.moveViews([viewDescriptor], currentContainer, defaultContainer);
}
@@ -574,11 +427,11 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor
}
// Generated Container Id Format
// {Common Prefix}.{Location}.{Uniqueness Id}
// Old Format (deprecated)
// {Common Prefix}.{Uniqueness Id}.{Source View Id}
private generateContainerIdFromSourceViewId(viewId: string, location: ViewContainerLocation): string {
const result = `${ViewDescriptorService.COMMON_CONTAINER_ID_PREFIX}.${location === ViewContainerLocation.Panel ? 'panel' : 'sidebar'}.${viewId}`;
this.generatedContainerSourceViewIds.set(result, viewId);
return result;
private generateContainerId(location: ViewContainerLocation): string {
return `${ViewDescriptorService.COMMON_CONTAINER_ID_PREFIX}.${location === ViewContainerLocation.Panel ? 'panel' : 'sidebar'}.${generateUuid()}`;
}
private getStoredCachedViewPositionsValue(): string {
@@ -591,14 +444,12 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor
private saveViewPositionsToCache(): void {
this.viewContainersRegistry.all.forEach(viewContainer => {
const viewDescriptorCollection = this.getViewDescriptors(viewContainer);
viewDescriptorCollection.allViewDescriptors.forEach(viewDescriptor => {
const sourceViewId = this.generatedContainerSourceViewIds.get(viewContainer.id);
const viewContainerModel = this.getViewContainerModel(viewContainer);
viewContainerModel.allViewDescriptors.forEach(viewDescriptor => {
const containerLocation = this.getViewContainerLocation(viewContainer);
this.cachedViewInfo.set(viewDescriptor.id, {
containerId: viewContainer.id,
location: containerLocation,
sourceViewId: sourceViewId
location: containerLocation
});
});
});
@@ -607,7 +458,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor
// so that default changes can be recognized
// https://github.com/microsoft/vscode/issues/90414
for (const [viewId, containerInfo] of this.cachedViewInfo) {
const defaultContainer = this.getDefaultContainer(viewId);
const defaultContainer = this.getDefaultContainerById(viewId);
if (defaultContainer?.id === containerInfo.containerId) {
this.cachedViewInfo.delete(viewId);
}
@@ -631,7 +482,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor
continue;
}
const viewDescriptor = this.getViewDescriptor(viewId);
const viewDescriptor = this.getViewDescriptorById(viewId);
if (viewDescriptor) {
result.push(viewDescriptor);
}
@@ -641,20 +492,20 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor
}
private onDidRegisterViewContainer(viewContainer: ViewContainer): void {
this.getOrRegisterViewDescriptorCollection(viewContainer);
this.getOrRegisterViewContainerModel(viewContainer);
}
private getOrRegisterViewDescriptorCollection(viewContainer: ViewContainer): ViewDescriptorCollection {
let viewDescriptorCollection = this.viewDescriptorCollections.get(viewContainer)?.viewDescriptorCollection;
private getOrRegisterViewContainerModel(viewContainer: ViewContainer): ViewContainerModel {
let viewContainerModel = this.viewContainerModels.get(viewContainer)?.viewContainerModel;
if (!viewDescriptorCollection) {
if (!viewContainerModel) {
const disposables = new DisposableStore();
viewDescriptorCollection = disposables.add(new ViewDescriptorCollection(this.contextKeyService));
viewContainerModel = disposables.add(this.instantiationService.createInstance(ViewContainerModel, viewContainer));
this.onDidChangeActiveViews({ added: viewDescriptorCollection.activeViewDescriptors, removed: [] });
viewDescriptorCollection.onDidChangeActiveViews(changed => this.onDidChangeActiveViews(changed), this, disposables);
this.onDidChangeActiveViews({ added: viewContainerModel.activeViewDescriptors, removed: [] });
viewContainerModel.onDidChangeActiveViewDescriptors(changed => this.onDidChangeActiveViews(changed), this, disposables);
this.viewDescriptorCollections.set(viewContainer, { viewDescriptorCollection, disposable: disposables });
this.viewContainerModels.set(viewContainer, { viewContainerModel: viewContainerModel, disposable: disposables });
const viewsToRegister = this.getViewsByContainer(viewContainer);
if (viewsToRegister.length) {
@@ -663,18 +514,18 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor
}
}
return viewDescriptorCollection;
return viewContainerModel;
}
private onDidDeregisterViewContainer(viewContainer: ViewContainer): void {
const viewDescriptorCollectionItem = this.viewDescriptorCollections.get(viewContainer);
if (viewDescriptorCollectionItem) {
viewDescriptorCollectionItem.disposable.dispose();
this.viewDescriptorCollections.delete(viewContainer);
const viewContainerModelItem = this.viewContainerModels.get(viewContainer);
if (viewContainerModelItem) {
viewContainerModelItem.disposable.dispose();
this.viewContainerModels.delete(viewContainer);
}
}
private onDidChangeActiveViews({ added, removed }: { added: IViewDescriptor[], removed: IViewDescriptor[]; }): void {
private onDidChangeActiveViews({ added, removed }: { added: ReadonlyArray<IViewDescriptor>, removed: ReadonlyArray<IViewDescriptor>; }): void {
added.forEach(viewDescriptor => this.getOrCreateActiveViewContextKey(viewDescriptor).set(true));
removed.forEach(viewDescriptor => this.getOrCreateActiveViewContextKey(viewDescriptor).set(false));
}
@@ -682,13 +533,12 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor
private addViews(container: ViewContainer, views: IViewDescriptor[]): void {
// Update in memory cache
const location = this.getViewContainerLocation(container);
const sourceViewId = this.generatedContainerSourceViewIds.get(container.id);
views.forEach(view => {
this.cachedViewInfo.set(view.id, { containerId: container.id, location, sourceViewId });
this.getOrCreateDefaultViewLocationContextKey(view).set(this.getDefaultContainer(view.id) === container);
this.cachedViewInfo.set(view.id, { containerId: container.id, location });
this.getOrCreateDefaultViewLocationContextKey(view).set(this.getDefaultContainerById(view.id) === container);
});
this.getViewDescriptors(container).addViews(views);
this.getViewContainerModel(container).add(views);
}
private removeViews(container: ViewContainer, views: IViewDescriptor[]): void {
@@ -696,7 +546,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor
views.forEach(view => this.getOrCreateDefaultViewLocationContextKey(view).set(false));
// Remove the views
this.getViewDescriptors(container).removeViews(views);
this.getViewContainerModel(container).remove(views);
}
private getOrCreateActiveViewContextKey(viewDescriptor: IViewDescriptor): IContextKey<boolean> {

View File

@@ -0,0 +1,654 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ViewContainer, IViewsRegistry, IViewDescriptor, Extensions as ViewExtensions, IViewContainerModel, IAddedViewDescriptorRef, IViewDescriptorRef } from 'vs/workbench/common/views';
import { IContextKeyService, IReadableSet } from 'vs/platform/contextkey/common/contextkey';
import { IStorageService, StorageScope, IWorkspaceStorageChangeEvent } from 'vs/platform/storage/common/storage';
import { Registry } from 'vs/platform/registry/common/platform';
import { Disposable } from 'vs/base/common/lifecycle';
import { Event, Emitter } from 'vs/base/common/event';
import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { URI } from 'vs/base/common/uri';
import { firstIndex, move } from 'vs/base/common/arrays';
import { isUndefined, isUndefinedOrNull } from 'vs/base/common/types';
import { values } from 'vs/base/common/map';
import { isEqual } from 'vs/base/common/resources';
class CounterSet<T> implements IReadableSet<T> {
private map = new Map<T, number>();
add(value: T): CounterSet<T> {
this.map.set(value, (this.map.get(value) || 0) + 1);
return this;
}
delete(value: T): boolean {
let counter = this.map.get(value) || 0;
if (counter === 0) {
return false;
}
counter--;
if (counter === 0) {
this.map.delete(value);
} else {
this.map.set(value, counter);
}
return true;
}
has(value: T): boolean {
return this.map.has(value);
}
}
interface IStoredWorkspaceViewState {
collapsed: boolean;
isHidden: boolean;
size?: number;
order?: number;
}
interface IStoredGlobalViewState {
id: string;
isHidden: boolean;
order?: number;
}
interface IViewDescriptorState {
visibleGlobal: boolean | undefined;
visibleWorkspace: boolean | undefined;
collapsed: boolean | undefined;
active: boolean
order?: number;
size?: number;
}
class ViewDescriptorsState extends Disposable {
private readonly workspaceViewsStateStorageId: string;
private readonly globalViewsStateStorageId: string;
private readonly state: Map<string, IViewDescriptorState>;
private _onDidChangeStoredState = this._register(new Emitter<{ id: string, visible: boolean }[]>());
readonly onDidChangeStoredState = this._onDidChangeStoredState.event;
constructor(
viewContainerStorageId: string,
@IStorageService private readonly storageService: IStorageService,
@IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService
) {
super();
this.globalViewsStateStorageId = `${viewContainerStorageId}.hidden`;
this.workspaceViewsStateStorageId = viewContainerStorageId;
storageKeysSyncRegistryService.registerStorageKey({ key: this.globalViewsStateStorageId, version: 1 });
this._register(this.storageService.onDidChangeStorage(e => this.onDidStorageChange(e)));
this.state = this.initialize();
}
set(id: string, state: IViewDescriptorState): void {
this.state.set(id, state);
}
get(id: string): IViewDescriptorState | undefined {
return this.state.get(id);
}
updateState(viewDescriptors: ReadonlyArray<IViewDescriptor>): void {
this.updateWorkspaceState(viewDescriptors);
this.updateGlobalState(viewDescriptors);
}
private updateWorkspaceState(viewDescriptors: ReadonlyArray<IViewDescriptor>): void {
const storedViewsStates: { [id: string]: IStoredWorkspaceViewState; } = JSON.parse(this.storageService.get(this.workspaceViewsStateStorageId, StorageScope.WORKSPACE, '{}'));
for (const viewDescriptor of viewDescriptors) {
const viewState = this.state.get(viewDescriptor.id);
if (viewState) {
storedViewsStates[viewDescriptor.id] = {
collapsed: !!viewState.collapsed,
isHidden: !viewState.visibleWorkspace,
size: viewState.size,
order: viewDescriptor.workspace && viewState ? viewState.order : undefined
};
}
}
if (Object.keys(storedViewsStates).length > 0) {
this.storageService.store(this.workspaceViewsStateStorageId, JSON.stringify(storedViewsStates), StorageScope.WORKSPACE);
} else {
this.storageService.remove(this.workspaceViewsStateStorageId, StorageScope.WORKSPACE);
}
}
private updateGlobalState(viewDescriptors: ReadonlyArray<IViewDescriptor>): void {
const storedGlobalState = this.getStoredGlobalState();
for (const viewDescriptor of viewDescriptors) {
const state = this.state.get(viewDescriptor.id);
storedGlobalState.set(viewDescriptor.id, {
id: viewDescriptor.id,
isHidden: state && viewDescriptor.canToggleVisibility ? !state.visibleGlobal : false,
order: !viewDescriptor.workspace && state ? state.order : undefined
});
}
this.setStoredGlobalState(storedGlobalState);
}
private onDidStorageChange(e: IWorkspaceStorageChangeEvent): void {
if (e.key === this.globalViewsStateStorageId && e.scope === StorageScope.GLOBAL
&& this.globalViewsStatesValue !== this.getStoredGlobalViewsStatesValue() /* This checks if current window changed the value or not */) {
this._globalViewsStatesValue = undefined;
const storedViewsVisibilityStates = this.getStoredGlobalState();
const changedStates: { id: string, visible: boolean }[] = [];
for (const [id, storedState] of storedViewsVisibilityStates) {
const state = this.state.get(id);
if (state) {
if (state.visibleGlobal !== !storedState.isHidden) {
changedStates.push({ id, visible: !storedState.isHidden });
}
}
}
if (changedStates.length) {
this._onDidChangeStoredState.fire(changedStates);
}
}
}
private initialize(): Map<string, IViewDescriptorState> {
const viewStates = new Map<string, IViewDescriptorState>();
const workspaceViewsStates = <{ [id: string]: IStoredWorkspaceViewState; }>JSON.parse(this.storageService.get(this.workspaceViewsStateStorageId, StorageScope.WORKSPACE, '{}'));
for (const id of Object.keys(workspaceViewsStates)) {
const workspaceViewState = workspaceViewsStates[id];
viewStates.set(id, {
active: false,
visibleGlobal: undefined,
visibleWorkspace: isUndefined(workspaceViewState.isHidden) ? undefined : !workspaceViewState.isHidden,
collapsed: workspaceViewState.collapsed,
order: workspaceViewState.order,
size: workspaceViewState.size,
});
}
// Migrate to `viewletStateStorageId`
const value = this.storageService.get(this.globalViewsStateStorageId, StorageScope.WORKSPACE, '[]');
const { state: workspaceVisibilityStates } = this.parseStoredGlobalState(value);
if (workspaceVisibilityStates.size > 0) {
for (const { id, isHidden } of values(workspaceVisibilityStates)) {
let viewState = viewStates.get(id);
// Not migrated to `viewletStateStorageId`
if (viewState) {
if (isUndefined(viewState.visibleWorkspace)) {
viewState.visibleWorkspace = !isHidden;
}
} else {
viewStates.set(id, {
active: false,
collapsed: undefined,
visibleGlobal: undefined,
visibleWorkspace: !isHidden,
});
}
}
this.storageService.remove(this.globalViewsStateStorageId, StorageScope.WORKSPACE);
}
const { state, hasDuplicates } = this.parseStoredGlobalState(this.globalViewsStatesValue);
if (hasDuplicates) {
this.setStoredGlobalState(state);
}
for (const { id, isHidden, order } of values(state)) {
let viewState = viewStates.get(id);
if (viewState) {
viewState.visibleGlobal = !isHidden;
if (!isUndefined(order)) {
viewState.order = order;
}
} else {
viewStates.set(id, {
active: false,
visibleGlobal: !isHidden,
order,
collapsed: undefined,
visibleWorkspace: undefined,
});
}
}
return viewStates;
}
private getStoredGlobalState(): Map<string, IStoredGlobalViewState> {
return this.parseStoredGlobalState(this.globalViewsStatesValue).state;
}
private setStoredGlobalState(storedGlobalState: Map<string, IStoredGlobalViewState>): void {
this.globalViewsStatesValue = JSON.stringify(values(storedGlobalState));
}
private parseStoredGlobalState(value: string): { state: Map<string, IStoredGlobalViewState>, hasDuplicates: boolean } {
const storedValue = <Array<string | IStoredGlobalViewState>>JSON.parse(value);
let hasDuplicates = false;
const state = storedValue.reduce((result, storedState) => {
if (typeof storedState === 'string' /* migration */) {
hasDuplicates = hasDuplicates || result.has(storedState);
result.set(storedState, { id: storedState, isHidden: true });
} else {
hasDuplicates = hasDuplicates || result.has(storedState.id);
result.set(storedState.id, storedState);
}
return result;
}, new Map<string, IStoredGlobalViewState>());
return { state, hasDuplicates };
}
private _globalViewsStatesValue: string | undefined;
private get globalViewsStatesValue(): string {
if (!this._globalViewsStatesValue) {
this._globalViewsStatesValue = this.getStoredGlobalViewsStatesValue();
}
return this._globalViewsStatesValue;
}
private set globalViewsStatesValue(globalViewsStatesValue: string) {
if (this.globalViewsStatesValue !== globalViewsStatesValue) {
this._globalViewsStatesValue = globalViewsStatesValue;
this.setStoredGlobalViewsStatesValue(globalViewsStatesValue);
}
}
private getStoredGlobalViewsStatesValue(): string {
return this.storageService.get(this.globalViewsStateStorageId, StorageScope.GLOBAL, '[]');
}
private setStoredGlobalViewsStatesValue(value: string): void {
this.storageService.store(this.globalViewsStateStorageId, value, StorageScope.GLOBAL);
}
}
interface IViewDescriptorItem {
viewDescriptor: IViewDescriptor;
state: IViewDescriptorState;
}
export class ViewContainerModel extends Disposable implements IViewContainerModel {
private readonly contextKeys = new CounterSet<string>();
private viewDescriptorItems: IViewDescriptorItem[] = [];
private viewDescriptorsState: ViewDescriptorsState;
// Container Info
private _title!: string;
get title(): string { return this._title; }
private _icon: URI | string | undefined;
get icon(): URI | string | undefined { return this._icon; }
private _onDidChangeContainerInfo = this._register(new Emitter<{ title?: boolean, icon?: boolean }>());
readonly onDidChangeContainerInfo = this._onDidChangeContainerInfo.event;
// All View Descriptors
get allViewDescriptors(): ReadonlyArray<IViewDescriptor> { return this.viewDescriptorItems.map(item => item.viewDescriptor); }
private _onDidChangeAllViewDescriptors = this._register(new Emitter<{ added: ReadonlyArray<IViewDescriptor>, removed: ReadonlyArray<IViewDescriptor> }>());
readonly onDidChangeAllViewDescriptors = this._onDidChangeAllViewDescriptors.event;
// Active View Descriptors
get activeViewDescriptors(): ReadonlyArray<IViewDescriptor> { return this.viewDescriptorItems.filter(item => item.state.active).map(item => item.viewDescriptor); }
private _onDidChangeActiveViewDescriptors = this._register(new Emitter<{ added: ReadonlyArray<IViewDescriptor>, removed: ReadonlyArray<IViewDescriptor> }>());
readonly onDidChangeActiveViewDescriptors = this._onDidChangeActiveViewDescriptors.event;
// Visible View Descriptors
get visibleViewDescriptors(): ReadonlyArray<IViewDescriptor> { return this.viewDescriptorItems.filter(item => this.isViewDescriptorVisible(item)).map(item => item.viewDescriptor); }
private _onDidAddVisibleViewDescriptors = this._register(new Emitter<IAddedViewDescriptorRef[]>());
readonly onDidAddVisibleViewDescriptors: Event<IAddedViewDescriptorRef[]> = this._onDidAddVisibleViewDescriptors.event;
private _onDidRemoveVisibleViewDescriptors = this._register(new Emitter<IViewDescriptorRef[]>());
readonly onDidRemoveVisibleViewDescriptors: Event<IViewDescriptorRef[]> = this._onDidRemoveVisibleViewDescriptors.event;
private _onDidMoveVisibleViewDescriptors = this._register(new Emitter<{ from: IViewDescriptorRef; to: IViewDescriptorRef; }>());
readonly onDidMoveVisibleViewDescriptors: Event<{ from: IViewDescriptorRef; to: IViewDescriptorRef; }> = this._onDidMoveVisibleViewDescriptors.event;
constructor(
private readonly container: ViewContainer,
@IInstantiationService instantiationService: IInstantiationService,
@IContextKeyService private readonly contextKeyService: IContextKeyService,
) {
super();
this._register(Event.filter(contextKeyService.onDidChangeContext, e => e.affectsSome(this.contextKeys))(() => this.onDidChangeContext()));
this.viewDescriptorsState = this._register(instantiationService.createInstance(ViewDescriptorsState, container.storageId || `${container.id}.state`));
this._register(this.viewDescriptorsState.onDidChangeStoredState(items => this.updateVisibility(items)));
this._register(Event.any(
this.onDidAddVisibleViewDescriptors,
this.onDidRemoveVisibleViewDescriptors,
this.onDidMoveVisibleViewDescriptors)
(() => {
this.viewDescriptorsState.updateState(this.allViewDescriptors);
this.updateContainerInfo();
}));
this.updateContainerInfo();
}
private updateContainerInfo(): void {
/* Use default container info if one of the visible view descriptors belongs to the current container by default */
const useDefaultContainerInfo = this.container.alwaysUseContainerInfo || this.visibleViewDescriptors.length === 0 || this.visibleViewDescriptors.some(v => Registry.as<IViewsRegistry>(ViewExtensions.ViewsRegistry).getViewContainer(v.id) === this.container);
const title = useDefaultContainerInfo ? this.container.name : this.visibleViewDescriptors[0]?.name || '';
let titleChanged: boolean = false;
if (this._title !== title) {
this._title = title;
titleChanged = true;
}
const icon = useDefaultContainerInfo ? this.container.icon : this.visibleViewDescriptors[0]?.containerIcon || 'codicon-window';
let iconChanged: boolean = false;
if (URI.isUri(icon) && URI.isUri(this._icon) ? isEqual(icon, this._icon) : this._icon !== icon) {
this._icon = icon;
iconChanged = true;
}
if (titleChanged || iconChanged) {
this._onDidChangeContainerInfo.fire({ title: titleChanged, icon: iconChanged });
}
}
isVisible(id: string): boolean {
const viewDescriptorItem = this.viewDescriptorItems.filter(v => v.viewDescriptor.id === id)[0];
if (!viewDescriptorItem) {
throw new Error(`Unknown view ${id}`);
}
return this.isViewDescriptorVisible(viewDescriptorItem);
}
setVisible(id: string, visible: boolean, size?: number): void {
this.updateVisibility([{ id, visible, size }]);
}
private updateVisibility(viewDescriptors: { id: string, visible: boolean, size?: number }[]): void {
const added: IAddedViewDescriptorRef[] = [];
const removed: IViewDescriptorRef[] = [];
for (const { visibleIndex, viewDescriptorItem, visible, size } of viewDescriptors.map(({ id, visible, size }) => ({ ...this.find(id), visible, size }))) {
const viewDescriptor = viewDescriptorItem.viewDescriptor;
if (!viewDescriptor.canToggleVisibility) {
throw new Error(`Can't toggle this view's visibility`);
}
if (this.isViewDescriptorVisible(viewDescriptorItem) === visible) {
return;
}
if (viewDescriptor.workspace) {
viewDescriptorItem.state.visibleWorkspace = visible;
} else {
viewDescriptorItem.state.visibleGlobal = visible;
}
if (typeof viewDescriptorItem.state.size === 'number') {
viewDescriptorItem.state.size = size;
}
if (visible) {
added.push({ index: visibleIndex, viewDescriptor, size: viewDescriptorItem.state.size, collapsed: !!viewDescriptorItem.state.collapsed });
} else {
removed.push({ index: visibleIndex, viewDescriptor });
}
}
if (added.length) {
this._onDidAddVisibleViewDescriptors.fire(added);
}
if (removed.length) {
this._onDidRemoveVisibleViewDescriptors.fire(removed);
}
}
isCollapsed(id: string): boolean {
return !!this.find(id).viewDescriptorItem.state.collapsed;
}
setCollapsed(id: string, collapsed: boolean): void {
const { viewDescriptorItem } = this.find(id);
if (viewDescriptorItem.state.collapsed !== collapsed) {
viewDescriptorItem.state.collapsed = collapsed;
}
this.viewDescriptorsState.updateState(this.allViewDescriptors);
}
getSize(id: string): number | undefined {
return this.find(id).viewDescriptorItem.state.size;
}
setSize(id: string, size: number): void {
const { viewDescriptorItem } = this.find(id);
if (viewDescriptorItem.state.size !== size) {
viewDescriptorItem.state.size = size;
}
this.viewDescriptorsState.updateState(this.allViewDescriptors);
}
move(from: string, to: string): void {
const fromIndex = firstIndex(this.viewDescriptorItems, v => v.viewDescriptor.id === from);
const toIndex = firstIndex(this.viewDescriptorItems, v => v.viewDescriptor.id === to);
const fromViewDescriptor = this.viewDescriptorItems[fromIndex];
const toViewDescriptor = this.viewDescriptorItems[toIndex];
move(this.viewDescriptorItems, fromIndex, toIndex);
for (let index = 0; index < this.viewDescriptorItems.length; index++) {
this.viewDescriptorItems[index].state.order = index;
}
this._onDidMoveVisibleViewDescriptors.fire({
from: { index: fromIndex, viewDescriptor: fromViewDescriptor.viewDescriptor },
to: { index: toIndex, viewDescriptor: toViewDescriptor.viewDescriptor }
});
}
add(viewDescriptors: IViewDescriptor[]): void {
const addedItems: IViewDescriptorItem[] = [];
const addedActiveDescriptors: IViewDescriptor[] = [];
const addedVisibleItems: { index: number, viewDescriptor: IViewDescriptor, size?: number, collapsed: boolean; }[] = [];
for (const viewDescriptor of viewDescriptors) {
if (viewDescriptor.when) {
for (const key of viewDescriptor.when.keys()) {
this.contextKeys.add(key);
}
}
let state = this.viewDescriptorsState.get(viewDescriptor.id);
if (state) {
// set defaults if not set
if (viewDescriptor.workspace) {
state.visibleWorkspace = isUndefinedOrNull(state.visibleWorkspace) ? !viewDescriptor.hideByDefault : state.visibleWorkspace;
} else {
state.visibleGlobal = isUndefinedOrNull(state.visibleGlobal) ? !viewDescriptor.hideByDefault : state.visibleGlobal;
}
state.collapsed = isUndefinedOrNull(state.collapsed) ? !!viewDescriptor.collapsed : state.collapsed;
} else {
state = {
active: false,
visibleGlobal: !viewDescriptor.hideByDefault,
visibleWorkspace: !viewDescriptor.hideByDefault,
collapsed: !!viewDescriptor.collapsed,
};
}
this.viewDescriptorsState.set(viewDescriptor.id, state);
state.active = this.contextKeyService.contextMatchesRules(viewDescriptor.when);
addedItems.push({ viewDescriptor, state });
if (state.active) {
addedActiveDescriptors.push(viewDescriptor);
}
}
this.viewDescriptorItems.push(...addedItems);
this.viewDescriptorItems.sort(this.compareViewDescriptors.bind(this));
for (const viewDescriptorItem of addedItems) {
if (this.isViewDescriptorVisible(viewDescriptorItem)) {
const { visibleIndex } = this.find(viewDescriptorItem.viewDescriptor.id);
addedVisibleItems.push({ index: visibleIndex, viewDescriptor: viewDescriptorItem.viewDescriptor, size: viewDescriptorItem.state.size, collapsed: !!viewDescriptorItem.state.collapsed });
}
}
this._onDidChangeAllViewDescriptors.fire({ added: addedItems.map(({ viewDescriptor }) => viewDescriptor), removed: [] });
if (addedActiveDescriptors.length) {
this._onDidChangeActiveViewDescriptors.fire(({ added: addedActiveDescriptors, removed: [] }));
}
if (addedVisibleItems.length) {
this._onDidAddVisibleViewDescriptors.fire(addedVisibleItems);
}
}
remove(viewDescriptors: IViewDescriptor[]): void {
const removed: IViewDescriptor[] = [];
const removedItems: IViewDescriptorItem[] = [];
const removedActiveDescriptors: IViewDescriptor[] = [];
const removedVisibleItems: { index: number, viewDescriptor: IViewDescriptor; }[] = [];
for (const viewDescriptor of viewDescriptors) {
if (viewDescriptor.when) {
for (const key of viewDescriptor.when.keys()) {
this.contextKeys.delete(key);
}
}
const index = firstIndex(this.viewDescriptorItems, i => i.viewDescriptor.id === viewDescriptor.id);
if (index !== -1) {
removed.push(viewDescriptor);
const viewDescriptorItem = this.viewDescriptorItems[index];
if (viewDescriptorItem.state.active) {
removedActiveDescriptors.push(viewDescriptorItem.viewDescriptor);
}
if (this.isViewDescriptorVisible(viewDescriptorItem)) {
const { visibleIndex } = this.find(viewDescriptorItem.viewDescriptor.id);
removedVisibleItems.push({ index: visibleIndex, viewDescriptor: viewDescriptorItem.viewDescriptor });
}
removedItems.push(viewDescriptorItem);
}
}
removedItems.forEach(item => this.viewDescriptorItems.splice(this.viewDescriptorItems.indexOf(item), 1));
this._onDidChangeAllViewDescriptors.fire({ added: [], removed });
if (removedActiveDescriptors.length) {
this._onDidChangeActiveViewDescriptors.fire(({ added: [], removed: removedActiveDescriptors }));
}
if (removedVisibleItems.length) {
this._onDidRemoveVisibleViewDescriptors.fire(removedVisibleItems);
}
}
private onDidChangeContext(): void {
const addedActiveItems: { item: IViewDescriptorItem, wasVisible: boolean }[] = [];
const removedActiveItems: { item: IViewDescriptorItem, wasVisible: boolean }[] = [];
const removedVisibleItems: { index: number, viewDescriptor: IViewDescriptor; }[] = [];
const addedVisibleItems: { index: number, viewDescriptor: IViewDescriptor, size?: number, collapsed: boolean; }[] = [];
for (const item of this.viewDescriptorItems) {
const wasActive = item.state.active;
const wasVisible = this.isViewDescriptorVisible(item);
const isActive = this.contextKeyService.contextMatchesRules(item.viewDescriptor.when);
if (wasActive !== isActive) {
if (isActive) {
addedActiveItems.push({ item, wasVisible });
} else {
removedActiveItems.push({ item, wasVisible });
}
}
}
for (const { item, wasVisible } of removedActiveItems) {
if (wasVisible) {
const { visibleIndex } = this.find(item.viewDescriptor.id);
removedVisibleItems.push({ index: visibleIndex, viewDescriptor: item.viewDescriptor });
}
}
// Update the State
removedActiveItems.forEach(({ item }) => item.state.active = false);
addedActiveItems.forEach(({ item }) => item.state.active = true);
for (const { item, wasVisible } of addedActiveItems) {
if (wasVisible !== this.isViewDescriptorVisibleWhenActive(item)) {
const { visibleIndex } = this.find(item.viewDescriptor.id);
addedVisibleItems.push({ index: visibleIndex, viewDescriptor: item.viewDescriptor, size: item.state.size, collapsed: !!item.state.collapsed });
}
}
if (addedActiveItems.length || removedActiveItems.length) {
this._onDidChangeActiveViewDescriptors.fire(({ added: addedActiveItems.map(({ item }) => item.viewDescriptor), removed: removedActiveItems.map(({ item }) => item.viewDescriptor) }));
}
if (removedVisibleItems.length) {
this._onDidRemoveVisibleViewDescriptors.fire(removedVisibleItems);
}
if (addedVisibleItems.length) {
this._onDidAddVisibleViewDescriptors.fire(addedVisibleItems);
}
}
private isViewDescriptorVisible(viewDescriptorItem: IViewDescriptorItem): boolean {
if (!viewDescriptorItem.state.active) {
return false;
}
return this.isViewDescriptorVisibleWhenActive(viewDescriptorItem);
}
private isViewDescriptorVisibleWhenActive(viewDescriptorItem: IViewDescriptorItem): boolean {
if (viewDescriptorItem.viewDescriptor.workspace) {
return !!viewDescriptorItem.state.visibleWorkspace;
}
return !!viewDescriptorItem.state.visibleGlobal;
}
private find(id: string): { index: number, visibleIndex: number, viewDescriptorItem: IViewDescriptorItem; } {
for (let i = 0, visibleIndex = 0; i < this.viewDescriptorItems.length; i++) {
const viewDescriptorItem = this.viewDescriptorItems[i];
if (viewDescriptorItem.viewDescriptor.id === id) {
return { index: i, visibleIndex, viewDescriptorItem: viewDescriptorItem };
}
if (this.isViewDescriptorVisible(viewDescriptorItem)) {
visibleIndex++;
}
}
throw new Error(`view descriptor ${id} not found`);
}
private compareViewDescriptors(a: IViewDescriptorItem, b: IViewDescriptorItem): number {
if (a.viewDescriptor.id === b.viewDescriptor.id) {
return 0;
}
return (this.getViewOrder(a) - this.getViewOrder(b)) || this.getGroupOrderResult(a.viewDescriptor, b.viewDescriptor);
}
private getViewOrder(viewDescriptorItem: IViewDescriptorItem): number {
const viewOrder = typeof viewDescriptorItem.state.order === 'number' ? viewDescriptorItem.state.order : viewDescriptorItem.viewDescriptor.order;
return typeof viewOrder === 'number' ? viewOrder : Number.MAX_VALUE;
}
private getGroupOrderResult(a: IViewDescriptor, b: IViewDescriptor) {
if (!a.group || !b.group) {
return 0;
}
if (a.group === b.group) {
return 0;
}
return a.group < b.group ? -1 : 1;
}
}

View File

@@ -0,0 +1,385 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import * as sinon from 'sinon';
import { IViewsRegistry, IViewDescriptor, IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainerLocation, IViewContainerModel, IViewDescriptorService, ViewContainer } from 'vs/workbench/common/views';
import { IDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle';
import { move } from 'vs/base/common/arrays';
import { workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices';
import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
import { ContextKeyService } from 'vs/platform/contextkey/browser/contextKeyService';
import { ViewDescriptorService } from 'vs/workbench/services/views/browser/viewDescriptorService';
import { Registry } from 'vs/platform/registry/common/platform';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
const ViewContainerRegistry = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry);
const ViewsRegistry = Registry.as<IViewsRegistry>(ViewContainerExtensions.ViewsRegistry);
class ViewDescriptorSequence {
readonly elements: IViewDescriptor[];
private disposables: IDisposable[] = [];
constructor(model: IViewContainerModel) {
this.elements = [...model.visibleViewDescriptors];
model.onDidAddVisibleViewDescriptors(added => added.forEach(({ viewDescriptor, index }) => this.elements.splice(index, 0, viewDescriptor)), null, this.disposables);
model.onDidRemoveVisibleViewDescriptors(removed => removed.sort((a, b) => b.index - a.index).forEach(({ index }) => this.elements.splice(index, 1)), null, this.disposables);
model.onDidMoveVisibleViewDescriptors(({ from, to }) => move(this.elements, from.index, to.index), null, this.disposables);
}
dispose() {
this.disposables = dispose(this.disposables);
}
}
suite('ViewContainerModel', () => {
let container: ViewContainer;
let disposableStore: DisposableStore;
let contextKeyService: IContextKeyService;
let viewDescriptorService: IViewDescriptorService;
let storageService: IStorageService;
setup(() => {
disposableStore = new DisposableStore();
const instantiationService: TestInstantiationService = <TestInstantiationService>workbenchInstantiationService();
contextKeyService = instantiationService.createInstance(ContextKeyService);
instantiationService.stub(IContextKeyService, contextKeyService);
storageService = instantiationService.get(IStorageService);
viewDescriptorService = instantiationService.createInstance(ViewDescriptorService);
});
teardown(() => {
disposableStore.dispose();
ViewsRegistry.deregisterViews(ViewsRegistry.getViews(container), container);
ViewContainerRegistry.deregisterViewContainer(container);
});
test('empty model', function () {
container = ViewContainerRegistry.registerViewContainer({ id: 'test', name: 'test', ctorDescriptor: new SyncDescriptor(<any>{}) }, ViewContainerLocation.Sidebar);
const testObject = viewDescriptorService.getViewContainerModel(container);
assert.equal(testObject.visibleViewDescriptors.length, 0);
});
test('register/unregister', () => {
container = ViewContainerRegistry.registerViewContainer({ id: 'test', name: 'test', ctorDescriptor: new SyncDescriptor(<any>{}) }, ViewContainerLocation.Sidebar);
const testObject = viewDescriptorService.getViewContainerModel(container);
const target = disposableStore.add(new ViewDescriptorSequence(testObject));
assert.equal(testObject.visibleViewDescriptors.length, 0);
assert.equal(target.elements.length, 0);
const viewDescriptor: IViewDescriptor = {
id: 'view1',
ctorDescriptor: null!,
name: 'Test View 1'
};
ViewsRegistry.registerViews([viewDescriptor], container);
assert.equal(testObject.visibleViewDescriptors.length, 1);
assert.equal(target.elements.length, 1);
assert.deepEqual(testObject.visibleViewDescriptors[0], viewDescriptor);
assert.deepEqual(target.elements[0], viewDescriptor);
ViewsRegistry.deregisterViews([viewDescriptor], container);
assert.equal(testObject.visibleViewDescriptors.length, 0);
assert.equal(target.elements.length, 0);
});
test('when contexts', async function () {
container = ViewContainerRegistry.registerViewContainer({ id: 'test', name: 'test', ctorDescriptor: new SyncDescriptor(<any>{}) }, ViewContainerLocation.Sidebar);
const testObject = viewDescriptorService.getViewContainerModel(container);
const target = disposableStore.add(new ViewDescriptorSequence(testObject));
assert.equal(testObject.visibleViewDescriptors.length, 0);
assert.equal(target.elements.length, 0);
const viewDescriptor: IViewDescriptor = {
id: 'view1',
ctorDescriptor: null!,
name: 'Test View 1',
when: ContextKeyExpr.equals('showview1', true)
};
ViewsRegistry.registerViews([viewDescriptor], container);
assert.equal(testObject.visibleViewDescriptors.length, 0, 'view should not appear since context isnt in');
assert.equal(target.elements.length, 0);
const key = contextKeyService.createKey('showview1', false);
assert.equal(testObject.visibleViewDescriptors.length, 0, 'view should still not appear since showview1 isnt true');
assert.equal(target.elements.length, 0);
key.set(true);
await new Promise(c => setTimeout(c, 30));
assert.equal(testObject.visibleViewDescriptors.length, 1, 'view should appear');
assert.equal(target.elements.length, 1);
assert.deepEqual(testObject.visibleViewDescriptors[0], viewDescriptor);
assert.equal(target.elements[0], viewDescriptor);
key.set(false);
await new Promise(c => setTimeout(c, 30));
assert.equal(testObject.visibleViewDescriptors.length, 0, 'view should disappear');
assert.equal(target.elements.length, 0);
ViewsRegistry.deregisterViews([viewDescriptor], container);
assert.equal(testObject.visibleViewDescriptors.length, 0, 'view should not be there anymore');
assert.equal(target.elements.length, 0);
key.set(true);
await new Promise(c => setTimeout(c, 30));
assert.equal(testObject.visibleViewDescriptors.length, 0, 'view should not be there anymore');
assert.equal(target.elements.length, 0);
});
test('when contexts - multiple', async function () {
container = ViewContainerRegistry.registerViewContainer({ id: 'test', name: 'test', ctorDescriptor: new SyncDescriptor(<any>{}) }, ViewContainerLocation.Sidebar);
const testObject = viewDescriptorService.getViewContainerModel(container);
const target = disposableStore.add(new ViewDescriptorSequence(testObject));
const view1: IViewDescriptor = { id: 'view1', ctorDescriptor: null!, name: 'Test View 1' };
const view2: IViewDescriptor = { id: 'view2', ctorDescriptor: null!, name: 'Test View 2', when: ContextKeyExpr.equals('showview2', true) };
ViewsRegistry.registerViews([view1, view2], container);
assert.deepEqual(testObject.visibleViewDescriptors, [view1], 'only view1 should be visible');
assert.deepEqual(target.elements, [view1], 'only view1 should be visible');
const key = contextKeyService.createKey('showview2', false);
assert.deepEqual(testObject.visibleViewDescriptors, [view1], 'still only view1 should be visible');
assert.deepEqual(target.elements, [view1], 'still only view1 should be visible');
key.set(true);
await new Promise(c => setTimeout(c, 30));
assert.deepEqual(testObject.visibleViewDescriptors, [view1, view2], 'both views should be visible');
assert.deepEqual(target.elements, [view1, view2], 'both views should be visible');
ViewsRegistry.deregisterViews([view1, view2], container);
});
test('when contexts - multiple 2', async function () {
container = ViewContainerRegistry.registerViewContainer({ id: 'test', name: 'test', ctorDescriptor: new SyncDescriptor(<any>{}) }, ViewContainerLocation.Sidebar);
const testObject = viewDescriptorService.getViewContainerModel(container);
const target = disposableStore.add(new ViewDescriptorSequence(testObject));
const view1: IViewDescriptor = { id: 'view1', ctorDescriptor: null!, name: 'Test View 1', when: ContextKeyExpr.equals('showview1', true) };
const view2: IViewDescriptor = { id: 'view2', ctorDescriptor: null!, name: 'Test View 2' };
ViewsRegistry.registerViews([view1, view2], container);
assert.deepEqual(testObject.visibleViewDescriptors, [view2], 'only view2 should be visible');
assert.deepEqual(target.elements, [view2], 'only view2 should be visible');
const key = contextKeyService.createKey('showview1', false);
assert.deepEqual(testObject.visibleViewDescriptors, [view2], 'still only view2 should be visible');
assert.deepEqual(target.elements, [view2], 'still only view2 should be visible');
key.set(true);
await new Promise(c => setTimeout(c, 30));
assert.deepEqual(testObject.visibleViewDescriptors, [view1, view2], 'both views should be visible');
assert.deepEqual(target.elements, [view1, view2], 'both views should be visible');
ViewsRegistry.deregisterViews([view1, view2], container);
});
test('setVisible', () => {
container = ViewContainerRegistry.registerViewContainer({ id: 'test', name: 'test', ctorDescriptor: new SyncDescriptor(<any>{}) }, ViewContainerLocation.Sidebar);
const testObject = viewDescriptorService.getViewContainerModel(container);
const target = disposableStore.add(new ViewDescriptorSequence(testObject));
const view1: IViewDescriptor = { id: 'view1', ctorDescriptor: null!, name: 'Test View 1', canToggleVisibility: true };
const view2: IViewDescriptor = { id: 'view2', ctorDescriptor: null!, name: 'Test View 2', canToggleVisibility: true };
const view3: IViewDescriptor = { id: 'view3', ctorDescriptor: null!, name: 'Test View 3', canToggleVisibility: true };
ViewsRegistry.registerViews([view1, view2, view3], container);
assert.deepEqual(testObject.visibleViewDescriptors, [view1, view2, view3]);
assert.deepEqual(target.elements, [view1, view2, view3]);
testObject.setVisible('view2', true);
assert.deepEqual(testObject.visibleViewDescriptors, [view1, view2, view3], 'nothing should happen');
assert.deepEqual(target.elements, [view1, view2, view3]);
testObject.setVisible('view2', false);
assert.deepEqual(testObject.visibleViewDescriptors, [view1, view3], 'view2 should hide');
assert.deepEqual(target.elements, [view1, view3]);
testObject.setVisible('view1', false);
assert.deepEqual(testObject.visibleViewDescriptors, [view3], 'view1 should hide');
assert.deepEqual(target.elements, [view3]);
testObject.setVisible('view3', false);
assert.deepEqual(testObject.visibleViewDescriptors, [], 'view3 shoud hide');
assert.deepEqual(target.elements, []);
testObject.setVisible('view1', true);
assert.deepEqual(testObject.visibleViewDescriptors, [view1], 'view1 should show');
assert.deepEqual(target.elements, [view1]);
testObject.setVisible('view3', true);
assert.deepEqual(testObject.visibleViewDescriptors, [view1, view3], 'view3 should show');
assert.deepEqual(target.elements, [view1, view3]);
testObject.setVisible('view2', true);
assert.deepEqual(testObject.visibleViewDescriptors, [view1, view2, view3], 'view2 should show');
assert.deepEqual(target.elements, [view1, view2, view3]);
ViewsRegistry.deregisterViews([view1, view2, view3], container);
assert.deepEqual(testObject.visibleViewDescriptors, []);
assert.deepEqual(target.elements, []);
});
test('move', () => {
container = ViewContainerRegistry.registerViewContainer({ id: 'test', name: 'test', ctorDescriptor: new SyncDescriptor(<any>{}) }, ViewContainerLocation.Sidebar);
const testObject = viewDescriptorService.getViewContainerModel(container);
const target = disposableStore.add(new ViewDescriptorSequence(testObject));
const view1: IViewDescriptor = { id: 'view1', ctorDescriptor: null!, name: 'Test View 1' };
const view2: IViewDescriptor = { id: 'view2', ctorDescriptor: null!, name: 'Test View 2' };
const view3: IViewDescriptor = { id: 'view3', ctorDescriptor: null!, name: 'Test View 3' };
ViewsRegistry.registerViews([view1, view2, view3], container);
assert.deepEqual(testObject.visibleViewDescriptors, [view1, view2, view3], 'model views should be OK');
assert.deepEqual(target.elements, [view1, view2, view3], 'sql views should be OK');
testObject.move('view3', 'view1');
assert.deepEqual(testObject.visibleViewDescriptors, [view3, view1, view2], 'view3 should go to the front');
assert.deepEqual(target.elements, [view3, view1, view2]);
testObject.move('view1', 'view2');
assert.deepEqual(testObject.visibleViewDescriptors, [view3, view2, view1], 'view1 should go to the end');
assert.deepEqual(target.elements, [view3, view2, view1]);
testObject.move('view1', 'view3');
assert.deepEqual(testObject.visibleViewDescriptors, [view1, view3, view2], 'view1 should go to the front');
assert.deepEqual(target.elements, [view1, view3, view2]);
testObject.move('view2', 'view3');
assert.deepEqual(testObject.visibleViewDescriptors, [view1, view2, view3], 'view2 should go to the middle');
assert.deepEqual(target.elements, [view1, view2, view3]);
});
test('view states', async function () {
storageService.store(`${container.id}.state.hidden`, JSON.stringify([{ id: 'view1', isHidden: true }]), StorageScope.GLOBAL);
container = ViewContainerRegistry.registerViewContainer({ id: 'test', name: 'test', ctorDescriptor: new SyncDescriptor(<any>{}) }, ViewContainerLocation.Sidebar);
const testObject = viewDescriptorService.getViewContainerModel(container);
const target = disposableStore.add(new ViewDescriptorSequence(testObject));
assert.equal(testObject.visibleViewDescriptors.length, 0);
assert.equal(target.elements.length, 0);
const viewDescriptor: IViewDescriptor = {
id: 'view1',
ctorDescriptor: null!,
name: 'Test View 1'
};
ViewsRegistry.registerViews([viewDescriptor], container);
assert.equal(testObject.visibleViewDescriptors.length, 0, 'view should not appear since it was set not visible in view state');
assert.equal(target.elements.length, 0);
});
test('view states and when contexts', async function () {
storageService.store(`${container.id}.state.hidden`, JSON.stringify([{ id: 'view1', isHidden: true }]), StorageScope.GLOBAL);
container = ViewContainerRegistry.registerViewContainer({ id: 'test', name: 'test', ctorDescriptor: new SyncDescriptor(<any>{}) }, ViewContainerLocation.Sidebar);
const testObject = viewDescriptorService.getViewContainerModel(container);
const target = disposableStore.add(new ViewDescriptorSequence(testObject));
assert.equal(testObject.visibleViewDescriptors.length, 0);
assert.equal(target.elements.length, 0);
const viewDescriptor: IViewDescriptor = {
id: 'view1',
ctorDescriptor: null!,
name: 'Test View 1',
when: ContextKeyExpr.equals('showview1', true)
};
ViewsRegistry.registerViews([viewDescriptor], container);
assert.equal(testObject.visibleViewDescriptors.length, 0, 'view should not appear since context isnt in');
assert.equal(target.elements.length, 0);
const key = contextKeyService.createKey('showview1', false);
assert.equal(testObject.visibleViewDescriptors.length, 0, 'view should still not appear since showview1 isnt true');
assert.equal(target.elements.length, 0);
key.set(true);
await new Promise(c => setTimeout(c, 30));
assert.equal(testObject.visibleViewDescriptors.length, 0, 'view should still not appear since it was set not visible in view state');
assert.equal(target.elements.length, 0);
});
test('view states and when contexts multiple views', async function () {
storageService.store(`${container.id}.state.hidden`, JSON.stringify([{ id: 'view1', isHidden: true }]), StorageScope.GLOBAL);
container = ViewContainerRegistry.registerViewContainer({ id: 'test', name: 'test', ctorDescriptor: new SyncDescriptor(<any>{}) }, ViewContainerLocation.Sidebar);
const testObject = viewDescriptorService.getViewContainerModel(container);
const target = disposableStore.add(new ViewDescriptorSequence(testObject));
assert.equal(testObject.visibleViewDescriptors.length, 0);
assert.equal(target.elements.length, 0);
const view1: IViewDescriptor = {
id: 'view1',
ctorDescriptor: null!,
name: 'Test View 1',
when: ContextKeyExpr.equals('showview', true)
};
const view2: IViewDescriptor = {
id: 'view2',
ctorDescriptor: null!,
name: 'Test View 2',
};
const view3: IViewDescriptor = {
id: 'view3',
ctorDescriptor: null!,
name: 'Test View 3',
when: ContextKeyExpr.equals('showview', true)
};
ViewsRegistry.registerViews([view1, view2, view3], container);
assert.deepEqual(testObject.visibleViewDescriptors, [view2], 'Only view2 should be visible');
assert.deepEqual(target.elements, [view2]);
const key = contextKeyService.createKey('showview', false);
assert.deepEqual(testObject.visibleViewDescriptors, [view2], 'Only view2 should be visible');
assert.deepEqual(target.elements, [view2]);
key.set(true);
await new Promise(c => setTimeout(c, 30));
assert.deepEqual(testObject.visibleViewDescriptors, [view2, view3], 'view3 should be visible');
assert.deepEqual(target.elements, [view2, view3]);
key.set(false);
await new Promise(c => setTimeout(c, 30));
assert.deepEqual(testObject.visibleViewDescriptors, [view2], 'Only view2 should be visible');
assert.deepEqual(target.elements, [view2]);
});
test('remove event is not triggered if view was hidden and removed', async function () {
container = ViewContainerRegistry.registerViewContainer({ id: 'test', name: 'test', ctorDescriptor: new SyncDescriptor(<any>{}) }, ViewContainerLocation.Sidebar);
const testObject = viewDescriptorService.getViewContainerModel(container);
const target = disposableStore.add(new ViewDescriptorSequence(testObject));
const viewDescriptor: IViewDescriptor = {
id: 'view1',
ctorDescriptor: null!,
name: 'Test View 1',
when: ContextKeyExpr.equals('showview1', true),
canToggleVisibility: true
};
ViewsRegistry.registerViews([viewDescriptor], container);
const key = contextKeyService.createKey('showview1', true);
await new Promise(c => setTimeout(c, 30));
assert.equal(testObject.visibleViewDescriptors.length, 1, 'view should appear after context is set');
assert.equal(target.elements.length, 1);
testObject.setVisible('view1', false);
assert.equal(testObject.visibleViewDescriptors.length, 0, 'view should disappear after setting visibility to false');
assert.equal(target.elements.length, 0);
const targetEvent = sinon.spy(testObject.onDidRemoveVisibleViewDescriptors);
key.set(false);
await new Promise(c => setTimeout(c, 30));
assert.ok(!targetEvent.called, 'remove event should not be called since it is already hidden');
});
});

View File

@@ -0,0 +1,268 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { IViewsRegistry, IViewDescriptor, IViewContainersRegistry, Extensions as ViewContainerExtensions, IViewDescriptorService, ViewContainerLocation, ViewContainer } from 'vs/workbench/common/views';
import { Registry } from 'vs/platform/registry/common/platform';
import { workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices';
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { ViewDescriptorService } from 'vs/workbench/services/views/browser/viewDescriptorService';
import { assertIsDefined } from 'vs/base/common/types';
import { ContextKeyService } from 'vs/platform/contextkey/browser/contextKeyService';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
const ViewsRegistry = Registry.as<IViewsRegistry>(ViewContainerExtensions.ViewsRegistry);
const sidebarContainer = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: 'testSidebar', name: 'test', ctorDescriptor: new SyncDescriptor(<any>{}) }, ViewContainerLocation.Sidebar);
const panelContainer = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: 'testPanel', name: 'test', ctorDescriptor: new SyncDescriptor(<any>{}) }, ViewContainerLocation.Panel);
suite('ViewDescriptorService', () => {
let viewDescriptorService: IViewDescriptorService;
setup(() => {
const instantiationService: TestInstantiationService = <TestInstantiationService>workbenchInstantiationService();
instantiationService.stub(IContextKeyService, instantiationService.createInstance(ContextKeyService));
viewDescriptorService = instantiationService.createInstance(ViewDescriptorService);
});
teardown(() => {
ViewsRegistry.deregisterViews(ViewsRegistry.getViews(sidebarContainer), sidebarContainer);
ViewsRegistry.deregisterViews(ViewsRegistry.getViews(panelContainer), panelContainer);
});
test('Empty Containers', function () {
const sidebarViews = viewDescriptorService.getViewContainerModel(sidebarContainer);
const panelViews = viewDescriptorService.getViewContainerModel(panelContainer);
assert.equal(sidebarViews.allViewDescriptors.length, 0, 'The sidebar container should have no views yet.');
assert.equal(panelViews.allViewDescriptors.length, 0, 'The panel container should have no views yet.');
});
test('Register/Deregister', () => {
const viewDescriptors: IViewDescriptor[] = [
{
id: 'view1',
ctorDescriptor: null!,
name: 'Test View 1',
canMoveView: true
},
{
id: 'view2',
ctorDescriptor: null!,
name: 'Test View 2',
canMoveView: true
},
{
id: 'view3',
ctorDescriptor: null!,
name: 'Test View 3',
canMoveView: true
}
];
ViewsRegistry.registerViews(viewDescriptors.slice(0, 2), sidebarContainer);
ViewsRegistry.registerViews(viewDescriptors.slice(2), panelContainer);
let sidebarViews = viewDescriptorService.getViewContainerModel(sidebarContainer);
let panelViews = viewDescriptorService.getViewContainerModel(panelContainer);
assert.equal(sidebarViews.activeViewDescriptors.length, 2, 'Sidebar should have 2 views');
assert.equal(panelViews.activeViewDescriptors.length, 1, 'Panel should have 1 view');
ViewsRegistry.deregisterViews(viewDescriptors.slice(0, 2), sidebarContainer);
ViewsRegistry.deregisterViews(viewDescriptors.slice(2), panelContainer);
sidebarViews = viewDescriptorService.getViewContainerModel(sidebarContainer);
panelViews = viewDescriptorService.getViewContainerModel(panelContainer);
assert.equal(sidebarViews.activeViewDescriptors.length, 0, 'Sidebar should have no views');
assert.equal(panelViews.activeViewDescriptors.length, 0, 'Panel should have no views');
});
test('move views to existing containers', async function () {
const viewDescriptors: IViewDescriptor[] = [
{
id: 'view1',
ctorDescriptor: null!,
name: 'Test View 1',
canMoveView: true
},
{
id: 'view2',
ctorDescriptor: null!,
name: 'Test View 2',
canMoveView: true
},
{
id: 'view3',
ctorDescriptor: null!,
name: 'Test View 3',
canMoveView: true
}
];
ViewsRegistry.registerViews(viewDescriptors.slice(0, 2), sidebarContainer);
ViewsRegistry.registerViews(viewDescriptors.slice(2), panelContainer);
viewDescriptorService.moveViewsToContainer(viewDescriptors.slice(2), sidebarContainer);
viewDescriptorService.moveViewsToContainer(viewDescriptors.slice(0, 2), panelContainer);
let sidebarViews = viewDescriptorService.getViewContainerModel(sidebarContainer);
let panelViews = viewDescriptorService.getViewContainerModel(panelContainer);
assert.equal(sidebarViews.activeViewDescriptors.length, 1, 'Sidebar should have 2 views');
assert.equal(panelViews.activeViewDescriptors.length, 2, 'Panel should have 1 view');
assert.notEqual(sidebarViews.activeViewDescriptors.indexOf(viewDescriptors[2]), -1, `Sidebar should have ${viewDescriptors[2].name}`);
assert.notEqual(panelViews.activeViewDescriptors.indexOf(viewDescriptors[0]), -1, `Panel should have ${viewDescriptors[0].name}`);
assert.notEqual(panelViews.activeViewDescriptors.indexOf(viewDescriptors[1]), -1, `Panel should have ${viewDescriptors[1].name}`);
});
test('move views to generated containers', async function () {
const viewDescriptors: IViewDescriptor[] = [
{
id: 'view1',
ctorDescriptor: null!,
name: 'Test View 1',
canMoveView: true
},
{
id: 'view2',
ctorDescriptor: null!,
name: 'Test View 2',
canMoveView: true
},
{
id: 'view3',
ctorDescriptor: null!,
name: 'Test View 3',
canMoveView: true
}
];
ViewsRegistry.registerViews(viewDescriptors.slice(0, 2), sidebarContainer);
ViewsRegistry.registerViews(viewDescriptors.slice(2), panelContainer);
viewDescriptorService.moveViewToLocation(viewDescriptors[0], ViewContainerLocation.Panel);
viewDescriptorService.moveViewToLocation(viewDescriptors[2], ViewContainerLocation.Sidebar);
let sidebarViews = viewDescriptorService.getViewContainerModel(sidebarContainer);
let panelViews = viewDescriptorService.getViewContainerModel(panelContainer);
assert.equal(sidebarViews.activeViewDescriptors.length, 1, 'Sidebar container should have 1 view');
assert.equal(panelViews.activeViewDescriptors.length, 0, 'Panel container should have no views');
const generatedPanel = assertIsDefined(viewDescriptorService.getViewContainerByViewId(viewDescriptors[0].id));
const generatedSidebar = assertIsDefined(viewDescriptorService.getViewContainerByViewId(viewDescriptors[2].id));
assert.equal(viewDescriptorService.getViewContainerLocation(generatedPanel), ViewContainerLocation.Panel, 'Generated Panel should be in located in the panel');
assert.equal(viewDescriptorService.getViewContainerLocation(generatedSidebar), ViewContainerLocation.Sidebar, 'Generated Sidebar should be in located in the sidebar');
assert.equal(viewDescriptorService.getViewContainerLocation(generatedPanel), viewDescriptorService.getViewLocationById(viewDescriptors[0].id), 'Panel view location and container location should match');
assert.equal(viewDescriptorService.getViewContainerLocation(generatedSidebar), viewDescriptorService.getViewLocationById(viewDescriptors[2].id), 'Sidebar view location and container location should match');
assert.equal(viewDescriptorService.getDefaultContainerById(viewDescriptors[2].id), panelContainer, `${viewDescriptors[2].name} has wrong default container`);
assert.equal(viewDescriptorService.getDefaultContainerById(viewDescriptors[0].id), sidebarContainer, `${viewDescriptors[0].name} has wrong default container`);
viewDescriptorService.moveViewToLocation(viewDescriptors[0], ViewContainerLocation.Sidebar);
viewDescriptorService.moveViewToLocation(viewDescriptors[2], ViewContainerLocation.Panel);
sidebarViews = viewDescriptorService.getViewContainerModel(sidebarContainer);
panelViews = viewDescriptorService.getViewContainerModel(panelContainer);
assert.equal(sidebarViews.activeViewDescriptors.length, 1, 'Sidebar should have 2 views');
assert.equal(panelViews.activeViewDescriptors.length, 0, 'Panel should have 1 view');
assert.equal(viewDescriptorService.getViewLocationById(viewDescriptors[0].id), ViewContainerLocation.Sidebar, 'View should be located in the sidebar');
assert.equal(viewDescriptorService.getViewLocationById(viewDescriptors[2].id), ViewContainerLocation.Panel, 'View should be located in the panel');
});
test('move view events', async function () {
const viewDescriptors: IViewDescriptor[] = [
{
id: 'view1',
ctorDescriptor: null!,
name: 'Test View 1',
canMoveView: true
},
{
id: 'view2',
ctorDescriptor: null!,
name: 'Test View 2',
canMoveView: true
},
{
id: 'view3',
ctorDescriptor: null!,
name: 'Test View 3',
canMoveView: true
}
];
let expectedSequence = '';
let actualSequence = '';
const disposables = [];
const containerMoveString = (view: IViewDescriptor, from: ViewContainer, to: ViewContainer) => {
return `Moved ${view.id} from ${from.id} to ${to.id}\n`;
};
const locationMoveString = (view: IViewDescriptor, from: ViewContainerLocation, to: ViewContainerLocation) => {
return `Moved ${view.id} from ${from === ViewContainerLocation.Sidebar ? 'Sidebar' : 'Panel'} to ${to === ViewContainerLocation.Sidebar ? 'Sidebar' : 'Panel'}\n`;
};
disposables.push(viewDescriptorService.onDidChangeContainer(({ views, from, to }) => {
views.forEach(view => {
actualSequence += containerMoveString(view, from, to);
});
}));
disposables.push(viewDescriptorService.onDidChangeLocation(({ views, from, to }) => {
views.forEach(view => {
actualSequence += locationMoveString(view, from, to);
});
}));
ViewsRegistry.registerViews(viewDescriptors.slice(0, 2), sidebarContainer);
ViewsRegistry.registerViews(viewDescriptors.slice(2), panelContainer);
expectedSequence += locationMoveString(viewDescriptors[0], ViewContainerLocation.Sidebar, ViewContainerLocation.Panel);
viewDescriptorService.moveViewToLocation(viewDescriptors[0], ViewContainerLocation.Panel);
expectedSequence += containerMoveString(viewDescriptors[0], sidebarContainer, viewDescriptorService.getViewContainerByViewId(viewDescriptors[0].id)!);
expectedSequence += locationMoveString(viewDescriptors[2], ViewContainerLocation.Panel, ViewContainerLocation.Sidebar);
viewDescriptorService.moveViewToLocation(viewDescriptors[2], ViewContainerLocation.Sidebar);
expectedSequence += containerMoveString(viewDescriptors[2], panelContainer, viewDescriptorService.getViewContainerByViewId(viewDescriptors[2].id)!);
expectedSequence += locationMoveString(viewDescriptors[0], ViewContainerLocation.Panel, ViewContainerLocation.Sidebar);
expectedSequence += containerMoveString(viewDescriptors[0], viewDescriptorService.getViewContainerByViewId(viewDescriptors[0].id)!, sidebarContainer);
viewDescriptorService.moveViewsToContainer([viewDescriptors[0]], sidebarContainer);
expectedSequence += locationMoveString(viewDescriptors[2], ViewContainerLocation.Sidebar, ViewContainerLocation.Panel);
expectedSequence += containerMoveString(viewDescriptors[2], viewDescriptorService.getViewContainerByViewId(viewDescriptors[2].id)!, panelContainer);
viewDescriptorService.moveViewsToContainer([viewDescriptors[2]], panelContainer);
expectedSequence += locationMoveString(viewDescriptors[0], ViewContainerLocation.Sidebar, ViewContainerLocation.Panel);
expectedSequence += containerMoveString(viewDescriptors[0], sidebarContainer, panelContainer);
viewDescriptorService.moveViewsToContainer([viewDescriptors[0]], panelContainer);
expectedSequence += locationMoveString(viewDescriptors[2], ViewContainerLocation.Panel, ViewContainerLocation.Sidebar);
expectedSequence += containerMoveString(viewDescriptors[2], panelContainer, sidebarContainer);
viewDescriptorService.moveViewsToContainer([viewDescriptors[2]], sidebarContainer);
expectedSequence += locationMoveString(viewDescriptors[1], ViewContainerLocation.Sidebar, ViewContainerLocation.Panel);
expectedSequence += locationMoveString(viewDescriptors[2], ViewContainerLocation.Sidebar, ViewContainerLocation.Panel);
expectedSequence += containerMoveString(viewDescriptors[1], sidebarContainer, panelContainer);
expectedSequence += containerMoveString(viewDescriptors[2], sidebarContainer, panelContainer);
viewDescriptorService.moveViewsToContainer([viewDescriptors[1], viewDescriptors[2]], panelContainer);
assert.equal(actualSequence, expectedSequence, 'Event sequence not matching expected sequence');
});
});