Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement import/export profiles #166517

Merged
merged 3 commits into from
Nov 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions src/vs/platform/files/common/inMemoryFilesystemProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,20 @@ export type Entry = File | Directory;

export class InMemoryFileSystemProvider extends Disposable implements IFileSystemProviderWithFileReadWriteCapability {

readonly capabilities: FileSystemProviderCapabilities =
FileSystemProviderCapabilities.FileReadWrite
| FileSystemProviderCapabilities.PathCaseSensitive;
readonly onDidChangeCapabilities: Event<void> = Event.None;
private _onDidChangeCapabilities = this._register(new Emitter<void>());
readonly onDidChangeCapabilities = this._onDidChangeCapabilities.event;

private _capabilities = FileSystemProviderCapabilities.FileReadWrite | FileSystemProviderCapabilities.PathCaseSensitive;
get capabilities(): FileSystemProviderCapabilities { return this._capabilities; }

setReadOnly(readonly: boolean) {
const isReadonly = !!(this._capabilities & FileSystemProviderCapabilities.Readonly);
if (readonly !== isReadonly) {
this._capabilities = readonly ? FileSystemProviderCapabilities.Readonly | FileSystemProviderCapabilities.PathCaseSensitive | FileSystemProviderCapabilities.FileReadWrite
: FileSystemProviderCapabilities.FileReadWrite | FileSystemProviderCapabilities.PathCaseSensitive;
this._onDidChangeCapabilities.fire();
}
}

root = new Directory('');

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { AbstractUserDataProfileStorageService, IProfileStorageChanges, IUserDat
import { isProfileUsingDefaultStorage, IStorageService } from 'vs/platform/storage/common/storage';
import { ApplicationStorageDatabaseClient, ProfileStorageDatabaseClient } from 'vs/platform/storage/common/storageIpc';
import { IUserDataProfile, IUserDataProfilesService, reviveProfile } from 'vs/platform/userDataProfile/common/userDataProfile';
import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions';

export class UserDataProfileStorageService extends AbstractUserDataProfileStorageService implements IUserDataProfileStorageService {

Expand Down Expand Up @@ -50,3 +51,5 @@ export class UserDataProfileStorageService extends AbstractUserDataProfileStorag
return isProfileUsingDefaultStorage(profile) ? new ApplicationStorageDatabaseClient(storageChannel) : new ProfileStorageDatabaseClient(storageChannel, profile);
}
}

registerSingleton(IUserDataProfileStorageService, UserDataProfileStorageService, InstantiationType.Delayed);
10 changes: 0 additions & 10 deletions src/vs/workbench/browser/parts/activitybar/activitybarPart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,6 @@ import { StringSHA1 } from 'vs/base/common/hash';
import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget';
import { GestureEvent } from 'vs/base/browser/touch';
import { IPaneCompositePart, IPaneCompositeSelectorPart } from 'vs/workbench/browser/parts/paneCompositePart';
import { Registry } from 'vs/platform/registry/common/platform';
import { Extensions, IProfileStorageRegistry } from 'vs/workbench/services/userDataProfile/common/userDataProfileStorageRegistry';
import { IUserDataProfileService, PROFILES_TTILE } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile';

Expand Down Expand Up @@ -149,14 +147,6 @@ export class ActivitybarPart extends Part implements IPaneCompositeSelectorPart

this.registerListeners();

Registry.as<IProfileStorageRegistry>(Extensions.ProfileStorageRegistry)
.registerKeys([{
key: ActivitybarPart.PINNED_VIEW_CONTAINERS,
description: localize('pinned view containers', "Activity bar entries visibility customizations")
}, {
key: AccountsActivityActionViewItem.ACCOUNTS_VISIBILITY_PREFERENCE_KEY,
description: localize('accounts visibility key', "Accounts entry visibility customization in the activity bar.")
}]);
}

private createCompositeBar() {
Expand Down
7 changes: 0 additions & 7 deletions src/vs/workbench/browser/parts/panel/panelPart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ import { IPaneCompositePart, IPaneCompositeSelectorPart } from 'vs/workbench/bro
import { IPartOptions } from 'vs/workbench/browser/part';
import { StringSHA1 } from 'vs/base/common/hash';
import { URI } from 'vs/base/common/uri';
import { Extensions, IProfileStorageRegistry } from 'vs/workbench/services/userDataProfile/common/userDataProfileStorageRegistry';
import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar';
import { ICommandService } from 'vs/platform/commands/common/commands';

Expand Down Expand Up @@ -210,12 +209,6 @@ export abstract class BasePanelPart extends CompositePart<PaneComposite> impleme
// Global Panel Actions
this.globalActions = this._register(this.instantiationService.createInstance(CompositeMenuActions, partId === Parts.PANEL_PART ? MenuId.PanelTitle : MenuId.AuxiliaryBarTitle, undefined, undefined));
this._register(this.globalActions.onDidChange(() => this.updateGlobalToolbarActions()));

Registry.as<IProfileStorageRegistry>(Extensions.ProfileStorageRegistry)
.registerKeys([{
key: this.pinnedPanelsKey,
description: localize('pinned view containers', "Panel entries visibility customizations")
}]);
}

protected abstract getActivityHoverOptions(): IActivityHoverOptions;
Expand Down
8 changes: 0 additions & 8 deletions src/vs/workbench/browser/parts/statusbar/statusbarModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,6 @@ import { isStatusbarEntryLocation, IStatusbarEntryLocation, StatusbarAlignment }
import { hide, show, isAncestor } from 'vs/base/browser/dom';
import { IStorageService, StorageScope, IStorageValueChangeEvent, StorageTarget } from 'vs/platform/storage/common/storage';
import { Emitter } from 'vs/base/common/event';
import { Registry } from 'vs/platform/registry/common/platform';
import { Extensions, IProfileStorageRegistry } from 'vs/workbench/services/userDataProfile/common/userDataProfileStorageRegistry';
import { localize } from 'vs/nls';

export interface IStatusbarEntryPriority {

Expand Down Expand Up @@ -68,11 +65,6 @@ export class StatusbarViewModel extends Disposable {
this.restoreState();
this.registerListeners();

Registry.as<IProfileStorageRegistry>(Extensions.ProfileStorageRegistry)
.registerKeys([{
key: StatusbarViewModel.HIDDEN_ENTRIES_KEY,
description: localize('statusbar.hidden', "Status bar entries visibility customizations"),
}]);
}

private restoreState(): void {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { workbenchInstantiationService, TestServiceAccessor, getLastResolvedFile
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IEditorFactoryRegistry, Verbosity, EditorExtensions, EditorInputCapabilities } from 'vs/workbench/common/editor';
import { EncodingMode, TextFileOperationError, TextFileOperationResult } from 'vs/workbench/services/textfile/common/textfiles';
import { FileOperationResult, FileOperationError, NotModifiedSinceFileOperationError, FileSystemProviderCapabilities } from 'vs/platform/files/common/files';
import { FileOperationResult, FileOperationError, NotModifiedSinceFileOperationError } from 'vs/platform/files/common/files';
import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel';
import { timeout } from 'vs/base/common/async';
import { PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry';
Expand Down Expand Up @@ -127,11 +127,10 @@ suite('Files - FileEditorInput', () => {

test('reports as readonly with readonly file scheme', async function () {

class ReadonlyInMemoryFileSystemProvider extends InMemoryFileSystemProvider {
override readonly capabilities: FileSystemProviderCapabilities = FileSystemProviderCapabilities.Readonly;
}
const inMemoryFilesystemProvider = new InMemoryFileSystemProvider();
inMemoryFilesystemProvider.setReadOnly(true);

const disposable = accessor.fileService.registerProvider('someTestingReadonlyScheme', new ReadonlyInMemoryFileSystemProvider());
const disposable = accessor.fileService.registerProvider('someTestingReadonlyScheme', inMemoryFilesystemProvider);
try {
const input = createFileInput(toResource.call(this, '/foo/bar/file.js').with({ scheme: 'someTestingReadonlyScheme' }));

Expand Down
57 changes: 26 additions & 31 deletions src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,17 @@ import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuratio
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { RenameProfileAction } from 'vs/workbench/contrib/userDataProfile/browser/userDataProfileActions';
import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { CURRENT_PROFILE_CONTEXT, HAS_PROFILES_CONTEXT, isUserDataProfileTemplate, IS_CURRENT_PROFILE_TRANSIENT_CONTEXT, IUserDataProfileImportExportService, IUserDataProfileManagementService, IUserDataProfileService, IUserDataProfileTemplate, ManageProfilesSubMenu, PROFILES_CATEGORY, PROFILES_ENABLEMENT_CONTEXT, PROFILES_TTILE, PROFILE_EXTENSION, PROFILE_FILTER } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
import { CURRENT_PROFILE_CONTEXT, HAS_PROFILES_CONTEXT, isUserDataProfileTemplate, IS_CURRENT_PROFILE_TRANSIENT_CONTEXT, IS_PROFILE_IMPORT_EXPORT_IN_PROGRESS_CONTEXT, IUserDataProfileImportExportService, IUserDataProfileManagementService, IUserDataProfileService, IUserDataProfileTemplate, ManageProfilesSubMenu, PROFILES_CATEGORY, PROFILES_ENABLEMENT_CONTEXT, PROFILES_TTILE, PROFILE_FILTER } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { charCount } from 'vs/base/common/strings';
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { IDialogService, IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
import { joinPath } from 'vs/base/common/resources';
import { Codicon } from 'vs/base/common/codicons';
import { IFileService } from 'vs/platform/files/common/files';
import { asJson, asText, IRequestService } from 'vs/platform/request/common/request';
import { CancellationToken } from 'vs/base/common/cancellation';
import { URI } from 'vs/base/common/uri';

export class UserDataProfilesWorkbenchContribution extends Disposable implements IWorkbenchContribution {

Expand Down Expand Up @@ -262,6 +261,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements
original: `Export (${that.userDataProfileService.currentProfile.name})...`
},
category: PROFILES_CATEGORY,
precondition: IS_PROFILE_IMPORT_EXPORT_IN_PROGRESS_CONTEXT.toNegated(),
menu: [
{
id: ManageProfilesSubMenu,
Expand All @@ -276,25 +276,8 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements
}

async run(accessor: ServicesAccessor) {
const textFileService = accessor.get(ITextFileService);
const fileDialogService = accessor.get(IFileDialogService);
const userDataProfileImportExportService = accessor.get(IUserDataProfileImportExportService);
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(), `profile.${PROFILE_EXTENSION}`),
});

if (!profileLocation) {
return;
}

const profile = await userDataProfileImportExportService.exportProfile({ skipComments: true });
await textFileService.create([{ resource: profileLocation, value: JSON.stringify(profile), options: { overwrite: true } }]);

notificationService.info(localize('export success', "{0}: Exported successfully.", PROFILES_CATEGORY.value));
return userDataProfileImportExportService.exportProfile();
}
}));
disposables.add(MenuRegistry.appendMenuItem(MenuId.MenubarShare, {
Expand Down Expand Up @@ -323,6 +306,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements
},
category: PROFILES_CATEGORY,
f1: true,
precondition: IS_PROFILE_IMPORT_EXPORT_IN_PROGRESS_CONTEXT.toNegated(),
menu: [
{
id: ManageProfilesSubMenu,
Expand Down Expand Up @@ -358,11 +342,11 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements
const disposables = new DisposableStore();
const quickPick = disposables.add(quickInputService.createQuickPick());
const updateQuickPickItems = (value?: string) => {
const selectFromFileItem: IQuickPickItem = { label: isSettingProfilesEnabled ? localize('select from file', "Select Profile template file") : localize('import from file', "Import from profile file") };
quickPick.items = value ? [{ label: isSettingProfilesEnabled ? localize('select from url', "Create from template URL") : localize('import from url', "Import from URL"), description: quickPick.value }, selectFromFileItem] : [selectFromFileItem];
const selectFromFileItem: IQuickPickItem = { label: localize('import from file', "Import from profile file") };
quickPick.items = value ? [{ label: localize('import from url', "Import from URL"), description: quickPick.value }, selectFromFileItem] : [selectFromFileItem];
};
quickPick.title = isSettingProfilesEnabled ? localize('create from profile template quick pick title', "Create from Profile Template") : localize('import profile quick pick title', "Import Settings from a Profile");
quickPick.placeholder = isSettingProfilesEnabled ? localize('create from profile template placeholder', "Provide a template URL or Select a template file") : localize('import profile placeholder', "Provide profile URL or select profile file to import");
quickPick.title = localize('import profile quick pick title', "Import 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();
Expand All @@ -371,11 +355,14 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements
disposables.add(quickPick.onDidAccept(async () => {
try {
quickPick.hide();
const profile = quickPick.selectedItems[0].description ? await this.getProfileFromURL(quickPick.value, requestService) : await this.getProfileFromFileSystem(fileDialogService, fileService);
if (profile) {
if (isSettingProfilesEnabled) {
if (isSettingProfilesEnabled) {
const profile = quickPick.selectedItems[0].description ? URI.parse(quickPick.value) : await this.getProfileUriFromFileSystem(fileDialogService);
if (profile) {
await userDataProfileImportExportService.importProfile(profile);
} else {
}
} else {
const profile = quickPick.selectedItems[0].description ? await this.getProfileFromURL(quickPick.value, requestService) : await this.getProfileFromFileSystem(fileDialogService, fileService);
if (profile) {
await userDataProfileImportExportService.setProfile(profile);
}
}
Expand All @@ -387,7 +374,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements
quickPick.show();
}

private async getProfileFromFileSystem(fileDialogService: IFileDialogService, fileService: IFileService): Promise<IUserDataProfileTemplate | null> {
private async getProfileUriFromFileSystem(fileDialogService: IFileDialogService): Promise<URI | null> {
const profileLocation = await fileDialogService.showOpenDialog({
canSelectFolders: false,
canSelectFiles: true,
Expand All @@ -398,7 +385,15 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements
if (!profileLocation) {
return null;
}
const content = (await fileService.readFile(profileLocation[0])).value.toString();
return profileLocation[0];
}

private async getProfileFromFileSystem(fileDialogService: IFileDialogService, fileService: IFileService): Promise<IUserDataProfileTemplate | null> {
const profileLocation = await this.getProfileUriFromFileSystem(fileDialogService);
if (!profileLocation) {
return null;
}
const content = (await fileService.readFile(profileLocation)).value.toString();
const parsed = JSON.parse(content);
return isUserDataProfileTemplate(parsed) ? parsed : null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,22 +68,25 @@ export class NativeExtensionManagementService extends ExtensionManagementChannel
override async install(vsix: URI, options?: InstallVSIXOptions): Promise<ILocalExtension> {
const { location, cleanup } = await this.downloadVsix(vsix);
try {
return await super.install(location, { ...options, profileLocation: this.userDataProfileService.currentProfile.extensionsResource });
options = options?.profileLocation ? options : { ...options, profileLocation: this.userDataProfileService.currentProfile.extensionsResource };
return await super.install(location, options);
} finally {
await cleanup();
}
}

override installFromGallery(extension: IGalleryExtension, installOptions?: InstallOptions): Promise<ILocalExtension> {
return super.installFromGallery(extension, { ...installOptions, profileLocation: this.userDataProfileService.currentProfile.extensionsResource });
installOptions = installOptions?.profileLocation ? installOptions : { ...installOptions, profileLocation: this.userDataProfileService.currentProfile.extensionsResource };
return super.installFromGallery(extension, installOptions);
}

override uninstall(extension: ILocalExtension, options?: UninstallOptions): Promise<void> {
return super.uninstall(extension, { ...options, profileLocation: this.userDataProfileService.currentProfile.extensionsResource });
options = options?.profileLocation ? options : { ...options, profileLocation: this.userDataProfileService.currentProfile.extensionsResource };
return super.uninstall(extension, options);
}

override getInstalled(type: ExtensionType | null = null): Promise<ILocalExtension[]> {
return super.getInstalled(type, this.userDataProfileService.currentProfile.extensionsResource);
override getInstalled(type: ExtensionType | null = null, profileLocation: URI = this.userDataProfileService.currentProfile.extensionsResource): Promise<ILocalExtension[]> {
return super.getInstalled(type, profileLocation);
}

private async downloadVsix(vsix: URI): Promise<{ location: URI; cleanup: () => Promise<void> }> {
Expand Down
Loading