mirror of https://github.com/microsoft/vscode.git
Implement export/import profiles
- Introduce workbench profile service - Implement settings, global state, extension profiles - Implement import/export profile actions
This commit is contained in:
parent
b7774c843e
commit
5b242ed4ff
|
@ -449,6 +449,10 @@
|
|||
{
|
||||
"name": "vs/workbench/services/host",
|
||||
"project": "vscode-workbench"
|
||||
},
|
||||
{
|
||||
"name": "vs/workbench/contrib/profiles",
|
||||
"project": "vscode-profiles"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -54,6 +54,11 @@
|
|||
"filenamePatterns": [
|
||||
"**/User/snippets/*.json"
|
||||
]
|
||||
}, {
|
||||
"id": "json",
|
||||
"extensions": [
|
||||
".code-profile"
|
||||
]
|
||||
}
|
||||
],
|
||||
"jsonValidation": [
|
||||
|
|
|
@ -57,6 +57,16 @@ function getIgnoredSettingsFromContent(settingsContent: string): string[] {
|
|||
return parsed ? parsed['settingsSync.ignoredSettings'] || parsed['sync.ignoredSettings'] || [] : [];
|
||||
}
|
||||
|
||||
export function removeComments(content: string, formattingOptions: FormattingOptions): string {
|
||||
const source = parse(content) || {};
|
||||
let result = '{}';
|
||||
for (const key of Object.keys(source)) {
|
||||
const edits = setProperty(result, [key], source[key], formattingOptions);
|
||||
result = applyEdits(result, edits);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function updateIgnoredSettings(targetContent: string, sourceContent: string, ignoredSettings: string[], formattingOptions: FormattingOptions): string {
|
||||
if (ignoredSettings.length) {
|
||||
const sourceTree = parseSettings(sourceContent);
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import './profilesActions';
|
|
@ -0,0 +1,10 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { localize } from 'vs/nls';
|
||||
|
||||
export const PROFILES_CATEGORY = localize('profiles', "Profiles");
|
||||
export const PROFILE_EXTENSION = 'code-profile';
|
||||
export const PROFILE_FILTER = [{ name: localize('profile', "Code Profile"), extensions: [PROFILE_EXTENSION] }];
|
|
@ -0,0 +1,137 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { joinPath } from 'vs/base/common/resources';
|
||||
import { localize } from 'vs/nls';
|
||||
import { Action2, registerAction2 } from 'vs/platform/actions/common/actions';
|
||||
import { IDialogService, IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { asJson, asText, IRequestService } from 'vs/platform/request/common/request';
|
||||
import { PROFILES_CATEGORY, PROFILE_EXTENSION, PROFILE_FILTER } from 'vs/workbench/contrib/profiles/common/profiles';
|
||||
import { IProfile, isProfile, IWorkbenchProfileService } from 'vs/workbench/services/profiles/common/profile';
|
||||
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
|
||||
registerAction2(class ExportProfileAction extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: 'workbench.profiles.actions.exportProfile',
|
||||
title: {
|
||||
value: localize('export profile', "Export customizations as a Profile..."),
|
||||
original: 'Export customizations as a Profile...'
|
||||
},
|
||||
category: PROFILES_CATEGORY,
|
||||
f1: true
|
||||
});
|
||||
}
|
||||
|
||||
async run(accessor: ServicesAccessor) {
|
||||
const textFileService = accessor.get(ITextFileService);
|
||||
const fileDialogService = accessor.get(IFileDialogService);
|
||||
const profileService = accessor.get(IWorkbenchProfileService);
|
||||
const notificationService = accessor.get(INotificationService);
|
||||
|
||||
const profileLocation = await fileDialogService.showSaveDialog({
|
||||
title: localize('export profile dialog', "Save Profile"),
|
||||
filters: PROFILE_FILTER,
|
||||
defaultUri: joinPath(await fileDialogService.defaultFilePath(undefined), `profile.${PROFILE_EXTENSION}`),
|
||||
});
|
||||
|
||||
if (!profileLocation) {
|
||||
return;
|
||||
}
|
||||
|
||||
const profile = await profileService.createProfile({ skipComments: true });
|
||||
await textFileService.create([{ resource: profileLocation, value: JSON.stringify(profile), options: { overwrite: true } }]);
|
||||
|
||||
notificationService.info(localize('export success', "Profile successfully exported."));
|
||||
}
|
||||
});
|
||||
|
||||
registerAction2(class ImportProfileAction extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: 'workbench.profiles.actions.importProfile',
|
||||
title: {
|
||||
value: localize('import profile', "Import customizations from a Profile..."),
|
||||
original: 'Import customizations from a Profile...'
|
||||
},
|
||||
category: PROFILES_CATEGORY,
|
||||
f1: true
|
||||
});
|
||||
}
|
||||
|
||||
async run(accessor: ServicesAccessor) {
|
||||
const fileDialogService = accessor.get(IFileDialogService);
|
||||
const quickInputService = accessor.get(IQuickInputService);
|
||||
const fileService = accessor.get(IFileService);
|
||||
const requestService = accessor.get(IRequestService);
|
||||
const profileService = accessor.get(IWorkbenchProfileService);
|
||||
const dialogService = accessor.get(IDialogService);
|
||||
|
||||
if (!(await dialogService.confirm({
|
||||
title: localize('import profile title', "Import customizations from a Profile"),
|
||||
message: localize('confiirmation message', "This will replace your current customizations. Are you sure you want to continue?"),
|
||||
})).confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
const disposables = new DisposableStore();
|
||||
const quickPick = disposables.add(quickInputService.createQuickPick());
|
||||
const updateQuickPickItems = (value?: string) => {
|
||||
const selectFromFileItem: IQuickPickItem = { label: localize('select from file', "Import from profile file") };
|
||||
quickPick.items = value ? [{ label: localize('select from url', "Import from URL"), description: quickPick.value }, selectFromFileItem] : [selectFromFileItem];
|
||||
};
|
||||
quickPick.title = localize('import profile quick pick title', "Import customizations from a Profile");
|
||||
quickPick.placeholder = localize('import profile placeholder', "Provide profile URL or select profile file to import");
|
||||
quickPick.ignoreFocusOut = true;
|
||||
disposables.add(quickPick.onDidChangeValue(updateQuickPickItems));
|
||||
updateQuickPickItems();
|
||||
quickPick.matchOnLabel = false;
|
||||
quickPick.matchOnDescription = false;
|
||||
disposables.add(quickPick.onDidAccept(async () => {
|
||||
quickPick.hide();
|
||||
const profile = quickPick.selectedItems[0].description ? await this.getProfileFromURL(quickPick.value, requestService) : await this.getProfileFromFileSystem(fileDialogService, fileService);
|
||||
if (profile) {
|
||||
await profileService.setProfile(profile);
|
||||
}
|
||||
}));
|
||||
disposables.add(quickPick.onDidHide(() => disposables.dispose()));
|
||||
quickPick.show();
|
||||
}
|
||||
|
||||
private async getProfileFromFileSystem(fileDialogService: IFileDialogService, fileService: IFileService): Promise<IProfile | null> {
|
||||
const profileLocation = await fileDialogService.showOpenDialog({
|
||||
canSelectFolders: false,
|
||||
canSelectFiles: true,
|
||||
canSelectMany: false,
|
||||
filters: PROFILE_FILTER,
|
||||
title: localize('import profile dialog', "Import Profile"),
|
||||
});
|
||||
if (!profileLocation) {
|
||||
return null;
|
||||
}
|
||||
const content = (await fileService.readFile(profileLocation[0])).value.toString();
|
||||
const parsed = JSON.parse(content);
|
||||
return isProfile(parsed) ? parsed : null;
|
||||
}
|
||||
|
||||
private async getProfileFromURL(url: string, requestService: IRequestService): Promise<IProfile | null> {
|
||||
const options = { type: 'GET', url };
|
||||
const context = await requestService.request(options, CancellationToken.None);
|
||||
if (context.res.statusCode === 200) {
|
||||
const result = await asJson(context);
|
||||
return isProfile(result) ? result : null;
|
||||
} else {
|
||||
const message = await asText(context);
|
||||
throw new Error(`Expected 200, got back ${context.res.statusCode} instead.\n\n${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
|
@ -0,0 +1,102 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { IExtensionGalleryService, IExtensionIdentifier, IExtensionManagementService, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { ExtensionType } from 'vs/platform/extensions/common/extensions';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { EnablementState, IWorkbenchExtensionEnablementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
||||
import { IResourceProfile } from 'vs/workbench/services/profiles/common/profile';
|
||||
|
||||
interface IProfileExtension {
|
||||
identifier: IExtensionIdentifier;
|
||||
preRelease?: boolean;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export class ExtensionsProfile implements IResourceProfile {
|
||||
|
||||
constructor(
|
||||
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
|
||||
@IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService,
|
||||
@IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService,
|
||||
@ILogService private readonly logService: ILogService,
|
||||
) {
|
||||
}
|
||||
|
||||
async getProfileContent(): Promise<string> {
|
||||
const extensions = await this.getLocalExtensions();
|
||||
return JSON.stringify(extensions);
|
||||
}
|
||||
|
||||
async applyProfile(content: string): Promise<void> {
|
||||
const profileExtensions: IProfileExtension[] = JSON.parse(content);
|
||||
const installedExtensions = await this.extensionManagementService.getInstalled();
|
||||
const extensionsToEnableOrDisable: { extension: ILocalExtension; enablementState: EnablementState }[] = [];
|
||||
const extensionsToInstall: IProfileExtension[] = [];
|
||||
for (const e of profileExtensions) {
|
||||
const installedExtension = installedExtensions.find(installed => areSameExtensions(installed.identifier, e.identifier));
|
||||
if (!installedExtension || installedExtension.preRelease !== e.preRelease) {
|
||||
extensionsToInstall.push(e);
|
||||
}
|
||||
if (installedExtension && this.extensionEnablementService.isEnabled(installedExtension) !== !e.disabled) {
|
||||
extensionsToEnableOrDisable.push({ extension: installedExtension, enablementState: e.disabled ? EnablementState.DisabledGlobally : EnablementState.EnabledGlobally });
|
||||
}
|
||||
}
|
||||
const extensionsToUninstall: ILocalExtension[] = installedExtensions.filter(extension => extension.type === ExtensionType.User && !profileExtensions.some(({ identifier }) => areSameExtensions(identifier, extension.identifier)));
|
||||
for (const { extension, enablementState } of extensionsToEnableOrDisable) {
|
||||
this.logService.trace(`Profile: Updating extension enablement...`, extension.identifier.id);
|
||||
await this.extensionEnablementService.setEnablement([extension], enablementState);
|
||||
this.logService.info(`Profile: Updated extension enablement`, extension.identifier.id);
|
||||
}
|
||||
if (extensionsToInstall.length) {
|
||||
const galleryExtensions = await this.extensionGalleryService.getExtensions(extensionsToInstall.map(e => ({ ...e.identifier, hasPreRelease: e.preRelease })), CancellationToken.None);
|
||||
await Promise.all(extensionsToInstall.map(async e => {
|
||||
const extension = galleryExtensions.find(galleryExtension => areSameExtensions(galleryExtension.identifier, e.identifier));
|
||||
if (!extension) {
|
||||
return;
|
||||
}
|
||||
if (await this.extensionManagementService.canInstall(extension)) {
|
||||
this.logService.trace(`Profile: Installing extension...`, e.identifier.id, extension.version);
|
||||
await this.extensionManagementService.installFromGallery(extension, { isMachineScoped: false, donotIncludePackAndDependencies: true, installPreReleaseVersion: e.preRelease } /* set isMachineScoped value to prevent install and sync dialog in web */);
|
||||
this.logService.info(`Profile: Installed extension.`, e.identifier.id, extension.version);
|
||||
} else {
|
||||
this.logService.info(`Profile: Skipped installing extension because it cannot be installed.`, extension.displayName || extension.identifier.id);
|
||||
}
|
||||
}));
|
||||
}
|
||||
if (extensionsToUninstall.length) {
|
||||
await Promise.all(extensionsToUninstall.map(e => this.extensionManagementService.uninstall(e)));
|
||||
}
|
||||
}
|
||||
|
||||
private async getLocalExtensions(): Promise<IProfileExtension[]> {
|
||||
const result: IProfileExtension[] = [];
|
||||
const installedExtensions = await this.extensionManagementService.getInstalled(undefined, true);
|
||||
for (const extension of installedExtensions) {
|
||||
const { identifier, preRelease } = extension;
|
||||
const enablementState = this.extensionEnablementService.getEnablementState(extension);
|
||||
const disabled = !this.extensionEnablementService.isEnabledEnablementState(enablementState);
|
||||
if (!disabled && extension.type === ExtensionType.System) {
|
||||
// skip enabled system extensions
|
||||
continue;
|
||||
}
|
||||
if (disabled && enablementState !== EnablementState.DisabledGlobally && enablementState !== EnablementState.DisabledWorkspace) {
|
||||
//skip extensions that are not disabled by user
|
||||
continue;
|
||||
}
|
||||
const profileExtension: IProfileExtension = { identifier };
|
||||
if (disabled) {
|
||||
profileExtension.disabled = true;
|
||||
}
|
||||
if (preRelease) {
|
||||
profileExtension.preRelease = true;
|
||||
}
|
||||
result.push(profileExtension);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IStringDictionary } from 'vs/base/common/collections';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
import { IResourceProfile } from 'vs/workbench/services/profiles/common/profile';
|
||||
|
||||
interface IGlobalState {
|
||||
storage: IStringDictionary<string>;
|
||||
}
|
||||
|
||||
export class GlobalStateProfile implements IResourceProfile {
|
||||
|
||||
constructor(
|
||||
@IStorageService private readonly storageService: IStorageService,
|
||||
@ILogService private readonly logService: ILogService,
|
||||
) {
|
||||
}
|
||||
|
||||
async getProfileContent(): Promise<string> {
|
||||
const globalState = await this.getLocalGlobalState();
|
||||
return JSON.stringify(globalState);
|
||||
}
|
||||
|
||||
async applyProfile(content: string): Promise<void> {
|
||||
const globalState: IGlobalState = JSON.parse(content);
|
||||
await this.writeLocalGlobalState(globalState);
|
||||
}
|
||||
|
||||
private async getLocalGlobalState(): Promise<IGlobalState> {
|
||||
const storage: IStringDictionary<string> = {};
|
||||
for (const key of this.storageService.keys(StorageScope.GLOBAL, StorageTarget.PROFILE)) {
|
||||
const value = this.storageService.get(key, StorageScope.GLOBAL);
|
||||
if (value) {
|
||||
storage[key] = value;
|
||||
}
|
||||
}
|
||||
return { storage };
|
||||
}
|
||||
|
||||
private async writeLocalGlobalState(globalState: IGlobalState): Promise<void> {
|
||||
const profileKeys: string[] = Object.keys(globalState.storage);
|
||||
const updatedStorage: IStringDictionary<any> = globalState.storage;
|
||||
for (const key of this.storageService.keys(StorageScope.GLOBAL, StorageTarget.PROFILE)) {
|
||||
if (!profileKeys.includes(key)) {
|
||||
// Remove the key if it does not exist in the profile
|
||||
updatedStorage[key] = undefined;
|
||||
}
|
||||
}
|
||||
const updatedStorageKeys: string[] = Object.keys(updatedStorage);
|
||||
if (updatedStorageKeys.length) {
|
||||
this.logService.trace(`Profile: Updating global state...`);
|
||||
for (const key of updatedStorageKeys) {
|
||||
this.storageService.store(key, globalState.storage[key], StorageScope.GLOBAL, StorageTarget.PROFILE);
|
||||
}
|
||||
this.logService.info(`Profile: Updated global state`, updatedStorageKeys);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { isUndefined } from 'vs/base/common/types';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
export interface IProfile {
|
||||
readonly id: string;
|
||||
readonly name?: string;
|
||||
readonly settings?: string;
|
||||
readonly globalState?: string;
|
||||
readonly extensions?: string;
|
||||
}
|
||||
|
||||
export function isProfile(thing: any): thing is IProfile {
|
||||
const candidate = thing as IProfile | undefined;
|
||||
|
||||
return !!(candidate && typeof candidate === 'object'
|
||||
&& typeof candidate.id === 'string'
|
||||
&& (isUndefined(candidate.name) || typeof candidate.name === 'string')
|
||||
&& (isUndefined(candidate.settings) || typeof candidate.settings === 'string')
|
||||
&& (isUndefined(candidate.globalState) || typeof candidate.globalState === 'string')
|
||||
&& (isUndefined(candidate.extensions) || typeof candidate.extensions === 'string'));
|
||||
}
|
||||
|
||||
export type ProfileCreationOptions = { readonly skipComments: boolean };
|
||||
|
||||
export const IWorkbenchProfileService = createDecorator<IWorkbenchProfileService>('IWorkbenchProfileService');
|
||||
export interface IWorkbenchProfileService {
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
createProfile(options?: ProfileCreationOptions): Promise<IProfile>;
|
||||
setProfile(profile: IProfile): Promise<void>;
|
||||
}
|
||||
|
||||
export interface IResourceProfile {
|
||||
getProfileContent(): Promise<string>;
|
||||
applyProfile(content: string): Promise<void>;
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ExtensionsProfile } from 'vs/workbench/services/profiles/common/extensionsProfile';
|
||||
import { GlobalStateProfile } from 'vs/workbench/services/profiles/common/globalStateProfile';
|
||||
import { IProfile, IWorkbenchProfileService } from 'vs/workbench/services/profiles/common/profile';
|
||||
import { SettingsProfile } from 'vs/workbench/services/profiles/common/settingsProfile';
|
||||
|
||||
export class WorkbenchProfileService implements IWorkbenchProfileService {
|
||||
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
private readonly settingsProfile: SettingsProfile;
|
||||
private readonly globalStateProfile: GlobalStateProfile;
|
||||
private readonly extensionsProfile: ExtensionsProfile;
|
||||
|
||||
constructor(
|
||||
@IInstantiationService instantiationService: IInstantiationService
|
||||
) {
|
||||
this.settingsProfile = instantiationService.createInstance(SettingsProfile);
|
||||
this.globalStateProfile = instantiationService.createInstance(GlobalStateProfile);
|
||||
this.extensionsProfile = instantiationService.createInstance(ExtensionsProfile);
|
||||
}
|
||||
|
||||
async createProfile(options?: { skipComments: boolean }): Promise<IProfile> {
|
||||
const settings = await this.settingsProfile.getProfileContent(options);
|
||||
const globalState = await this.globalStateProfile.getProfileContent();
|
||||
const extensions = await this.extensionsProfile.getProfileContent();
|
||||
return {
|
||||
id: generateUuid(),
|
||||
settings,
|
||||
globalState,
|
||||
extensions
|
||||
};
|
||||
}
|
||||
|
||||
async setProfile(profile: IProfile): Promise<void> {
|
||||
if (profile.settings) {
|
||||
await this.settingsProfile.applyProfile(profile.settings);
|
||||
}
|
||||
if (profile.globalState) {
|
||||
await this.globalStateProfile.applyProfile(profile.globalState);
|
||||
}
|
||||
if (profile.extensions) {
|
||||
await this.extensionsProfile.applyProfile(profile.extensions);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
registerSingleton(IWorkbenchProfileService, WorkbenchProfileService);
|
|
@ -0,0 +1,69 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { ConfigurationScope, Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { removeComments, updateIgnoredSettings } from 'vs/platform/userDataSync/common/settingsMerge';
|
||||
import { IUserDataSyncUtilService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { IResourceProfile, ProfileCreationOptions } from 'vs/workbench/services/profiles/common/profile';
|
||||
|
||||
interface ISettingsContent {
|
||||
settings: string;
|
||||
}
|
||||
|
||||
export class SettingsProfile implements IResourceProfile {
|
||||
|
||||
constructor(
|
||||
@IFileService private readonly fileService: IFileService,
|
||||
@IEnvironmentService private readonly environmentService: IEnvironmentService,
|
||||
@IUserDataSyncUtilService private readonly userDataSyncUtilService: IUserDataSyncUtilService,
|
||||
@ILogService private readonly logService: ILogService,
|
||||
) {
|
||||
}
|
||||
|
||||
async getProfileContent(options?: ProfileCreationOptions): Promise<string> {
|
||||
const ignoredSettings = this.getIgnoredSettings();
|
||||
const formattingOptions = await this.userDataSyncUtilService.resolveFormattingOptions(this.environmentService.settingsResource);
|
||||
const localContent = await this.getLocalFileContent();
|
||||
let settingsProfileContent = updateIgnoredSettings(localContent || '{}', '{}', ignoredSettings, formattingOptions);
|
||||
if (options?.skipComments) {
|
||||
settingsProfileContent = removeComments(settingsProfileContent, formattingOptions);
|
||||
}
|
||||
const settingsContent: ISettingsContent = {
|
||||
settings: settingsProfileContent
|
||||
};
|
||||
return JSON.stringify(settingsContent);
|
||||
}
|
||||
|
||||
async applyProfile(content: string): Promise<void> {
|
||||
const settingsContent: ISettingsContent = JSON.parse(content);
|
||||
this.logService.trace(`Profile: Applying settings...`);
|
||||
const localSettingsContent = await this.getLocalFileContent();
|
||||
const formattingOptions = await this.userDataSyncUtilService.resolveFormattingOptions(this.environmentService.settingsResource);
|
||||
const contentToUpdate = updateIgnoredSettings(settingsContent.settings, localSettingsContent || '{}', this.getIgnoredSettings(), formattingOptions);
|
||||
await this.fileService.writeFile(this.environmentService.settingsResource, VSBuffer.fromString(contentToUpdate));
|
||||
this.logService.info(`Profile: Applied settings`);
|
||||
}
|
||||
|
||||
private getIgnoredSettings(): string[] {
|
||||
const allSettings = Registry.as<IConfigurationRegistry>(Extensions.Configuration).getConfigurationProperties();
|
||||
const ignoredSettings = Object.keys(allSettings).filter(key => allSettings[key]?.scope === ConfigurationScope.MACHINE || allSettings[key]?.scope === ConfigurationScope.MACHINE_OVERRIDABLE);
|
||||
return ignoredSettings;
|
||||
}
|
||||
|
||||
private async getLocalFileContent(): Promise<string | null> {
|
||||
try {
|
||||
const content = await this.fileService.readFile(this.environmentService.settingsResource);
|
||||
return content.value.toString();
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -83,6 +83,7 @@ import 'vs/workbench/services/extensionRecommendations/common/extensionIgnoredRe
|
|||
import 'vs/workbench/services/extensionRecommendations/common/workspaceExtensionsConfig';
|
||||
import 'vs/workbench/services/notification/common/notificationService';
|
||||
import 'vs/workbench/services/userDataSync/common/userDataSyncUtil';
|
||||
import 'vs/workbench/services/profiles/common/profileService';
|
||||
import 'vs/workbench/services/remote/common/remoteExplorerService';
|
||||
import 'vs/workbench/services/workingCopy/common/workingCopyService';
|
||||
import 'vs/workbench/services/workingCopy/common/workingCopyFileService';
|
||||
|
@ -315,6 +316,9 @@ import 'vs/workbench/contrib/feedback/browser/feedback.contribution';
|
|||
// User Data Sync
|
||||
import 'vs/workbench/contrib/userDataSync/browser/userDataSync.contribution';
|
||||
|
||||
// Profiles
|
||||
import 'vs/workbench/contrib/profiles/common/profiles.contribution';
|
||||
|
||||
// Code Actions
|
||||
import 'vs/workbench/contrib/codeActions/browser/codeActions.contribution';
|
||||
|
||||
|
|
Loading…
Reference in New Issue