mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-11 10:38:31 -05:00
Merge from vscode 2e5312cd61ff99c570299ecc122c52584265eda2
This commit is contained in:
committed by
Anthony Dresser
parent
3603f55d97
commit
7f1d8fc32f
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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()));
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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'); }
|
||||
|
||||
@@ -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}}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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).'),
|
||||
|
||||
@@ -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"));
|
||||
|
||||
@@ -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(); });
|
||||
|
||||
@@ -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));
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 () {
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
23
src/vs/workbench/services/path/browser/pathService.ts
Normal file
23
src/vs/workbench/services/path/browser/pathService.ts
Normal 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);
|
||||
124
src/vs/workbench/services/path/common/pathService.ts
Normal file
124
src/vs/workbench/services/path/common/pathService.ts
Normal 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: ''
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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') {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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> {
|
||||
|
||||
654
src/vs/workbench/services/views/common/viewContainerModel.ts
Normal file
654
src/vs/workbench/services/views/common/viewContainerModel.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
});
|
||||
|
||||
});
|
||||
@@ -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');
|
||||
});
|
||||
|
||||
});
|
||||
Reference in New Issue
Block a user