Skip to content

Commit 126fd69

Browse files
authored
implement import/export profiles (microsoft#166517)
* implement import/export profiles - include all resources - include all user profile storage - show import/export preview view * fix compilation
1 parent 8007c63 commit 126fd69

27 files changed

+1561
-487
lines changed

src/vs/platform/files/common/inMemoryFilesystemProvider.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,20 @@ export type Entry = File | Directory;
5252

5353
export class InMemoryFileSystemProvider extends Disposable implements IFileSystemProviderWithFileReadWriteCapability {
5454

55-
readonly capabilities: FileSystemProviderCapabilities =
56-
FileSystemProviderCapabilities.FileReadWrite
57-
| FileSystemProviderCapabilities.PathCaseSensitive;
58-
readonly onDidChangeCapabilities: Event<void> = Event.None;
55+
private _onDidChangeCapabilities = this._register(new Emitter<void>());
56+
readonly onDidChangeCapabilities = this._onDidChangeCapabilities.event;
57+
58+
private _capabilities = FileSystemProviderCapabilities.FileReadWrite | FileSystemProviderCapabilities.PathCaseSensitive;
59+
get capabilities(): FileSystemProviderCapabilities { return this._capabilities; }
60+
61+
setReadOnly(readonly: boolean) {
62+
const isReadonly = !!(this._capabilities & FileSystemProviderCapabilities.Readonly);
63+
if (readonly !== isReadonly) {
64+
this._capabilities = readonly ? FileSystemProviderCapabilities.Readonly | FileSystemProviderCapabilities.PathCaseSensitive | FileSystemProviderCapabilities.FileReadWrite
65+
: FileSystemProviderCapabilities.FileReadWrite | FileSystemProviderCapabilities.PathCaseSensitive;
66+
this._onDidChangeCapabilities.fire();
67+
}
68+
}
5969

6070
root = new Directory('');
6171

src/vs/platform/userDataProfile/electron-sandbox/userDataProfileStorageService.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { AbstractUserDataProfileStorageService, IProfileStorageChanges, IUserDat
1212
import { isProfileUsingDefaultStorage, IStorageService } from 'vs/platform/storage/common/storage';
1313
import { ApplicationStorageDatabaseClient, ProfileStorageDatabaseClient } from 'vs/platform/storage/common/storageIpc';
1414
import { IUserDataProfile, IUserDataProfilesService, reviveProfile } from 'vs/platform/userDataProfile/common/userDataProfile';
15+
import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions';
1516

1617
export class UserDataProfileStorageService extends AbstractUserDataProfileStorageService implements IUserDataProfileStorageService {
1718

@@ -50,3 +51,5 @@ export class UserDataProfileStorageService extends AbstractUserDataProfileStorag
5051
return isProfileUsingDefaultStorage(profile) ? new ApplicationStorageDatabaseClient(storageChannel) : new ProfileStorageDatabaseClient(storageChannel, profile);
5152
}
5253
}
54+
55+
registerSingleton(IUserDataProfileStorageService, UserDataProfileStorageService, InstantiationType.Delayed);

src/vs/workbench/browser/parts/activitybar/activitybarPart.ts

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,6 @@ import { StringSHA1 } from 'vs/base/common/hash';
4343
import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget';
4444
import { GestureEvent } from 'vs/base/browser/touch';
4545
import { IPaneCompositePart, IPaneCompositeSelectorPart } from 'vs/workbench/browser/parts/paneCompositePart';
46-
import { Registry } from 'vs/platform/registry/common/platform';
47-
import { Extensions, IProfileStorageRegistry } from 'vs/workbench/services/userDataProfile/common/userDataProfileStorageRegistry';
4846
import { IUserDataProfileService, PROFILES_TTILE } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
4947
import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile';
5048

@@ -149,14 +147,6 @@ export class ActivitybarPart extends Part implements IPaneCompositeSelectorPart
149147

150148
this.registerListeners();
151149

152-
Registry.as<IProfileStorageRegistry>(Extensions.ProfileStorageRegistry)
153-
.registerKeys([{
154-
key: ActivitybarPart.PINNED_VIEW_CONTAINERS,
155-
description: localize('pinned view containers', "Activity bar entries visibility customizations")
156-
}, {
157-
key: AccountsActivityActionViewItem.ACCOUNTS_VISIBILITY_PREFERENCE_KEY,
158-
description: localize('accounts visibility key', "Accounts entry visibility customization in the activity bar.")
159-
}]);
160150
}
161151

162152
private createCompositeBar() {

src/vs/workbench/browser/parts/panel/panelPart.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ import { IPaneCompositePart, IPaneCompositeSelectorPart } from 'vs/workbench/bro
4444
import { IPartOptions } from 'vs/workbench/browser/part';
4545
import { StringSHA1 } from 'vs/base/common/hash';
4646
import { URI } from 'vs/base/common/uri';
47-
import { Extensions, IProfileStorageRegistry } from 'vs/workbench/services/userDataProfile/common/userDataProfileStorageRegistry';
4847
import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar';
4948
import { ICommandService } from 'vs/platform/commands/common/commands';
5049

@@ -210,12 +209,6 @@ export abstract class BasePanelPart extends CompositePart<PaneComposite> impleme
210209
// Global Panel Actions
211210
this.globalActions = this._register(this.instantiationService.createInstance(CompositeMenuActions, partId === Parts.PANEL_PART ? MenuId.PanelTitle : MenuId.AuxiliaryBarTitle, undefined, undefined));
212211
this._register(this.globalActions.onDidChange(() => this.updateGlobalToolbarActions()));
213-
214-
Registry.as<IProfileStorageRegistry>(Extensions.ProfileStorageRegistry)
215-
.registerKeys([{
216-
key: this.pinnedPanelsKey,
217-
description: localize('pinned view containers', "Panel entries visibility customizations")
218-
}]);
219212
}
220213

221214
protected abstract getActivityHoverOptions(): IActivityHoverOptions;

src/vs/workbench/browser/parts/statusbar/statusbarModel.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,6 @@ import { isStatusbarEntryLocation, IStatusbarEntryLocation, StatusbarAlignment }
88
import { hide, show, isAncestor } from 'vs/base/browser/dom';
99
import { IStorageService, StorageScope, IStorageValueChangeEvent, StorageTarget } from 'vs/platform/storage/common/storage';
1010
import { Emitter } from 'vs/base/common/event';
11-
import { Registry } from 'vs/platform/registry/common/platform';
12-
import { Extensions, IProfileStorageRegistry } from 'vs/workbench/services/userDataProfile/common/userDataProfileStorageRegistry';
13-
import { localize } from 'vs/nls';
1411

1512
export interface IStatusbarEntryPriority {
1613

@@ -68,11 +65,6 @@ export class StatusbarViewModel extends Disposable {
6865
this.restoreState();
6966
this.registerListeners();
7067

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

7870
private restoreState(): void {

src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { workbenchInstantiationService, TestServiceAccessor, getLastResolvedFile
1111
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
1212
import { IEditorFactoryRegistry, Verbosity, EditorExtensions, EditorInputCapabilities } from 'vs/workbench/common/editor';
1313
import { EncodingMode, TextFileOperationError, TextFileOperationResult } from 'vs/workbench/services/textfile/common/textfiles';
14-
import { FileOperationResult, FileOperationError, NotModifiedSinceFileOperationError, FileSystemProviderCapabilities } from 'vs/platform/files/common/files';
14+
import { FileOperationResult, FileOperationError, NotModifiedSinceFileOperationError } from 'vs/platform/files/common/files';
1515
import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel';
1616
import { timeout } from 'vs/base/common/async';
1717
import { PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry';
@@ -127,11 +127,10 @@ suite('Files - FileEditorInput', () => {
127127

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

130-
class ReadonlyInMemoryFileSystemProvider extends InMemoryFileSystemProvider {
131-
override readonly capabilities: FileSystemProviderCapabilities = FileSystemProviderCapabilities.Readonly;
132-
}
130+
const inMemoryFilesystemProvider = new InMemoryFileSystemProvider();
131+
inMemoryFilesystemProvider.setReadOnly(true);
133132

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

src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts

Lines changed: 26 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,17 @@ import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuratio
1919
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
2020
import { RenameProfileAction } from 'vs/workbench/contrib/userDataProfile/browser/userDataProfileActions';
2121
import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
22-
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';
22+
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';
2323
import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
2424
import { INotificationService } from 'vs/platform/notification/common/notification';
2525
import { charCount } from 'vs/base/common/strings';
2626
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
27-
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
2827
import { IDialogService, IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
29-
import { joinPath } from 'vs/base/common/resources';
3028
import { Codicon } from 'vs/base/common/codicons';
3129
import { IFileService } from 'vs/platform/files/common/files';
3230
import { asJson, asText, IRequestService } from 'vs/platform/request/common/request';
3331
import { CancellationToken } from 'vs/base/common/cancellation';
32+
import { URI } from 'vs/base/common/uri';
3433

3534
export class UserDataProfilesWorkbenchContribution extends Disposable implements IWorkbenchContribution {
3635

@@ -262,6 +261,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements
262261
original: `Export (${that.userDataProfileService.currentProfile.name})...`
263262
},
264263
category: PROFILES_CATEGORY,
264+
precondition: IS_PROFILE_IMPORT_EXPORT_IN_PROGRESS_CONTEXT.toNegated(),
265265
menu: [
266266
{
267267
id: ManageProfilesSubMenu,
@@ -276,25 +276,8 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements
276276
}
277277

278278
async run(accessor: ServicesAccessor) {
279-
const textFileService = accessor.get(ITextFileService);
280-
const fileDialogService = accessor.get(IFileDialogService);
281279
const userDataProfileImportExportService = accessor.get(IUserDataProfileImportExportService);
282-
const notificationService = accessor.get(INotificationService);
283-
284-
const profileLocation = await fileDialogService.showSaveDialog({
285-
title: localize('export profile dialog', "Save Profile"),
286-
filters: PROFILE_FILTER,
287-
defaultUri: joinPath(await fileDialogService.defaultFilePath(), `profile.${PROFILE_EXTENSION}`),
288-
});
289-
290-
if (!profileLocation) {
291-
return;
292-
}
293-
294-
const profile = await userDataProfileImportExportService.exportProfile({ skipComments: true });
295-
await textFileService.create([{ resource: profileLocation, value: JSON.stringify(profile), options: { overwrite: true } }]);
296-
297-
notificationService.info(localize('export success', "{0}: Exported successfully.", PROFILES_CATEGORY.value));
280+
return userDataProfileImportExportService.exportProfile();
298281
}
299282
}));
300283
disposables.add(MenuRegistry.appendMenuItem(MenuId.MenubarShare, {
@@ -323,6 +306,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements
323306
},
324307
category: PROFILES_CATEGORY,
325308
f1: true,
309+
precondition: IS_PROFILE_IMPORT_EXPORT_IN_PROGRESS_CONTEXT.toNegated(),
326310
menu: [
327311
{
328312
id: ManageProfilesSubMenu,
@@ -358,11 +342,11 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements
358342
const disposables = new DisposableStore();
359343
const quickPick = disposables.add(quickInputService.createQuickPick());
360344
const updateQuickPickItems = (value?: string) => {
361-
const selectFromFileItem: IQuickPickItem = { label: isSettingProfilesEnabled ? localize('select from file', "Select Profile template file") : localize('import from file', "Import from profile file") };
362-
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];
345+
const selectFromFileItem: IQuickPickItem = { label: localize('import from file', "Import from profile file") };
346+
quickPick.items = value ? [{ label: localize('import from url', "Import from URL"), description: quickPick.value }, selectFromFileItem] : [selectFromFileItem];
363347
};
364-
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");
365-
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");
348+
quickPick.title = localize('import profile quick pick title', "Import Profile");
349+
quickPick.placeholder = localize('import profile placeholder', "Provide profile URL or select profile file to import");
366350
quickPick.ignoreFocusOut = true;
367351
disposables.add(quickPick.onDidChangeValue(updateQuickPickItems));
368352
updateQuickPickItems();
@@ -371,11 +355,14 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements
371355
disposables.add(quickPick.onDidAccept(async () => {
372356
try {
373357
quickPick.hide();
374-
const profile = quickPick.selectedItems[0].description ? await this.getProfileFromURL(quickPick.value, requestService) : await this.getProfileFromFileSystem(fileDialogService, fileService);
375-
if (profile) {
376-
if (isSettingProfilesEnabled) {
358+
if (isSettingProfilesEnabled) {
359+
const profile = quickPick.selectedItems[0].description ? URI.parse(quickPick.value) : await this.getProfileUriFromFileSystem(fileDialogService);
360+
if (profile) {
377361
await userDataProfileImportExportService.importProfile(profile);
378-
} else {
362+
}
363+
} else {
364+
const profile = quickPick.selectedItems[0].description ? await this.getProfileFromURL(quickPick.value, requestService) : await this.getProfileFromFileSystem(fileDialogService, fileService);
365+
if (profile) {
379366
await userDataProfileImportExportService.setProfile(profile);
380367
}
381368
}
@@ -387,7 +374,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements
387374
quickPick.show();
388375
}
389376

390-
private async getProfileFromFileSystem(fileDialogService: IFileDialogService, fileService: IFileService): Promise<IUserDataProfileTemplate | null> {
377+
private async getProfileUriFromFileSystem(fileDialogService: IFileDialogService): Promise<URI | null> {
391378
const profileLocation = await fileDialogService.showOpenDialog({
392379
canSelectFolders: false,
393380
canSelectFiles: true,
@@ -398,7 +385,15 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements
398385
if (!profileLocation) {
399386
return null;
400387
}
401-
const content = (await fileService.readFile(profileLocation[0])).value.toString();
388+
return profileLocation[0];
389+
}
390+
391+
private async getProfileFromFileSystem(fileDialogService: IFileDialogService, fileService: IFileService): Promise<IUserDataProfileTemplate | null> {
392+
const profileLocation = await this.getProfileUriFromFileSystem(fileDialogService);
393+
if (!profileLocation) {
394+
return null;
395+
}
396+
const content = (await fileService.readFile(profileLocation)).value.toString();
402397
const parsed = JSON.parse(content);
403398
return isUserDataProfileTemplate(parsed) ? parsed : null;
404399
}

src/vs/workbench/services/extensionManagement/electron-sandbox/nativeExtensionManagementService.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,22 +68,25 @@ export class NativeExtensionManagementService extends ExtensionManagementChannel
6868
override async install(vsix: URI, options?: InstallVSIXOptions): Promise<ILocalExtension> {
6969
const { location, cleanup } = await this.downloadVsix(vsix);
7070
try {
71-
return await super.install(location, { ...options, profileLocation: this.userDataProfileService.currentProfile.extensionsResource });
71+
options = options?.profileLocation ? options : { ...options, profileLocation: this.userDataProfileService.currentProfile.extensionsResource };
72+
return await super.install(location, options);
7273
} finally {
7374
await cleanup();
7475
}
7576
}
7677

7778
override installFromGallery(extension: IGalleryExtension, installOptions?: InstallOptions): Promise<ILocalExtension> {
78-
return super.installFromGallery(extension, { ...installOptions, profileLocation: this.userDataProfileService.currentProfile.extensionsResource });
79+
installOptions = installOptions?.profileLocation ? installOptions : { ...installOptions, profileLocation: this.userDataProfileService.currentProfile.extensionsResource };
80+
return super.installFromGallery(extension, installOptions);
7981
}
8082

8183
override uninstall(extension: ILocalExtension, options?: UninstallOptions): Promise<void> {
82-
return super.uninstall(extension, { ...options, profileLocation: this.userDataProfileService.currentProfile.extensionsResource });
84+
options = options?.profileLocation ? options : { ...options, profileLocation: this.userDataProfileService.currentProfile.extensionsResource };
85+
return super.uninstall(extension, options);
8386
}
8487

85-
override getInstalled(type: ExtensionType | null = null): Promise<ILocalExtension[]> {
86-
return super.getInstalled(type, this.userDataProfileService.currentProfile.extensionsResource);
88+
override getInstalled(type: ExtensionType | null = null, profileLocation: URI = this.userDataProfileService.currentProfile.extensionsResource): Promise<ILocalExtension[]> {
89+
return super.getInstalled(type, profileLocation);
8790
}
8891

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

0 commit comments

Comments
 (0)