Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
* Fix microsoft#166940

* fix label
  • Loading branch information
sandy081 committed Nov 22, 2022
1 parent 7165673 commit 4d4ca20
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 80 deletions.
16 changes: 5 additions & 11 deletions src/vs/workbench/api/browser/mainThreadProfilContentHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/

import { CancellationToken } from 'vs/base/common/cancellation';
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
import { Disposable, DisposableMap, IDisposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { ExtHostContext, ExtHostProfileContentHandlersShape, MainContext, MainThreadProfileContentHandlersShape } from 'vs/workbench/api/common/extHost.protocol';
import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers';
Expand All @@ -15,24 +15,18 @@ export class MainThreadProfileContentHandlers extends Disposable implements Main

private readonly proxy: ExtHostProfileContentHandlersShape;

private readonly registeredHandlers = new Set<string>();
private readonly registeredHandlers = this._register(new DisposableMap<string, IDisposable>());

constructor(
context: IExtHostContext,
@IUserDataProfileImportExportService private readonly userDataProfileImportExportService: IUserDataProfileImportExportService,
) {
super();
this.proxy = context.getProxy(ExtHostContext.ExtHostProfileContentHandlers);
this._register(toDisposable(() => {
for (const id of this.registeredHandlers) {
this.userDataProfileImportExportService.unregisterProfileContentHandler(id);
}
this.registeredHandlers.clear();
}));
}

async $registerProfileContentHandler(id: string, name: string, extensionId: string): Promise<void> {
this.userDataProfileImportExportService.registerProfileContentHandler(id, {
this.registeredHandlers.set(id, this.userDataProfileImportExportService.registerProfileContentHandler(id, {
name,
extensionId,
saveProfile: async (name: string, content: string, token: CancellationToken) => {
Expand All @@ -42,11 +36,11 @@ export class MainThreadProfileContentHandlers extends Disposable implements Main
readProfile: async (uri: URI, token: CancellationToken) => {
return this.proxy.$readProfile(id, uri, token);
},
});
}));
}

async $unregisterProfileContentHandler(id: string): Promise<void> {
this.userDataProfileImportExportService.unregisterProfileContentHandler(id);
this.registeredHandlers.deleteAndDispose(id);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { INotificationService } from 'vs/platform/notification/common/notification';
import * as DOM from 'vs/base/browser/dom';
import { IUserDataProfileImportExportService, PROFILE_FILTER, PROFILE_EXTENSION, IUserDataProfileContentHandler, IS_PROFILE_IMPORT_EXPORT_IN_PROGRESS_CONTEXT, PROFILES_TTILE, defaultUserDataProfileIcon, IUserDataProfileService, IProfileResourceTreeItem, IProfileResourceChildTreeItem, PROFILES_CATEGORY, isUserDataProfileTemplate, IUserDataProfileManagementService, ProfileResourceType } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { IDialogService, IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
Expand Down Expand Up @@ -45,7 +45,7 @@ import { EditorsOrder } from 'vs/workbench/common/editor';
import { onUnexpectedError } from 'vs/base/common/errors';
import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
import { IQuickInputService, QuickPickItem } from 'vs/platform/quickinput/common/quickInput';
import { VSBuffer } from 'vs/base/common/buffer';
import { joinPath } from 'vs/base/common/resources';
import { escapeRegExpCharacters } from 'vs/base/common/strings';
Expand Down Expand Up @@ -132,11 +132,12 @@ export class UserDataProfileImportExportService extends Disposable implements IU
return false;
}

registerProfileContentHandler(id: string, profileContentHandler: IUserDataProfileContentHandler): void {
registerProfileContentHandler(id: string, profileContentHandler: IUserDataProfileContentHandler): IDisposable {
if (this.profileContentHandlers.has(id)) {
throw new Error(`Profile content handler with id '${id}' already registered.`);
}
this.profileContentHandlers.set(id, profileContentHandler);
return toDisposable(() => this.unregisterProfileContentHandler(id));
}

unregisterProfileContentHandler(id: string): void {
Expand All @@ -153,40 +154,46 @@ export class UserDataProfileImportExportService extends Disposable implements IU
const disposables = new DisposableStore();

try {
disposables.add(toDisposable(() => this.isProfileImportExportInProgressContextKey.set(false)));
const userDataProfilesExportState = disposables.add(this.instantiationService.createInstance(UserDataProfileExportState, this.userDataProfileService.currentProfile));

const title = localize('export profile preview', "Export");
let exportProfile = await this.selectProfileResources(
userDataProfilesExportState,
localize('export title', "{0}: {1} ({2})", PROFILES_CATEGORY.value, title, this.userDataProfileService.currentProfile.name),
localize('export description', "Select data to export")
);
await this.progressService.withProgress({
location: ProgressLocation.Window,
title: localize('profiles.exporting', "{0}: Exporting...", PROFILES_CATEGORY.value),
}, async progress => {
disposables.add(toDisposable(() => this.isProfileImportExportInProgressContextKey.set(false)));
const userDataProfilesExportState = disposables.add(this.instantiationService.createInstance(UserDataProfileExportState, this.userDataProfileService.currentProfile));

if (exportProfile === undefined) {
return;
}
const title = localize('export profile preview', "Export");
let exportProfile = await this.selectProfileResources(
userDataProfilesExportState,
localize('export title', "{0}: {1} ({2})", PROFILES_CATEGORY.value, title, this.userDataProfileService.currentProfile.name),
localize('export description', "Chose what to export")
);

if (!exportProfile) {
exportProfile = await this.showProfilePreviewView(`workbench.views.profiles.export.preview`, title, userDataProfilesExportState);
}
if (exportProfile === undefined) {
return;
}

if (!exportProfile) {
return;
}
if (!exportProfile) {
exportProfile = await this.showProfilePreviewView(`workbench.views.profiles.export.preview`, title, userDataProfilesExportState);
}

if (!exportProfile) {
return;
}

if (exportProfile) {
const profile = await userDataProfilesExportState.getProfileToExport();
if (!profile) {
return;
}

const saveResult = await this.saveProfileContent(profile.name, JSON.stringify(profile));
if (saveResult) {
const buttons = saveResult.id === Schemas.file ? undefined : [localize('copy', "Copy Link"), localize('open', "Open in {0}", this.profileContentHandlers.get(saveResult.id)?.name)];
const profileHandler = this.profileContentHandlers.get(saveResult.id);
const buttons = profileHandler?.extensionId ? [localize('copy', "Copy Link"), localize('open', "Open in {0}", profileHandler?.name), localize('close', "Close")] : undefined;
const result = await this.dialogService.show(
Severity.Info,
localize('export success', "Profile '{0}' is exported successfully.", profile.name),
buttons
buttons,
{ cancelId: 2 }
);
switch (result.choice) {
case 0:
Expand All @@ -203,7 +210,7 @@ export class UserDataProfileImportExportService extends Disposable implements IU

}
}
}
});
} finally {
disposables.dispose();
}
Expand All @@ -220,46 +227,47 @@ export class UserDataProfileImportExportService extends Disposable implements IU
disposables.add(toDisposable(() => this.isProfileImportExportInProgressContextKey.set(false)));

try {
const profileContent = await this.resolveProfileContent(uri);
if (profileContent === null) {
return;
}
await this.progressService.withProgress({
location: ProgressLocation.Window,
title: localize('profiles.importing', "{0}: Importing...", PROFILES_CATEGORY.value),
}, async progress => {
const profileContent = await this.resolveProfileContent(uri);
if (profileContent === null) {
return;
}

let profileTemplate: IUserDataProfileTemplate = JSON.parse(profileContent);
if (!isUserDataProfileTemplate(profileTemplate)) {
this.notificationService.error('Invalid profile content.');
return;
}
const userDataProfileImportState = disposables.add(this.instantiationService.createInstance(UserDataProfileImportState, profileTemplate));
let profileTemplate: IUserDataProfileTemplate = JSON.parse(profileContent);
if (!isUserDataProfileTemplate(profileTemplate)) {
this.notificationService.error('Invalid profile content.');
return;
}
const userDataProfileImportState = disposables.add(this.instantiationService.createInstance(UserDataProfileImportState, profileTemplate));

const title = localize('import profile preview', "Import");
let importProfile = await this.selectProfileResources(
userDataProfileImportState,
localize('import title', "{0}: {1} ({2})", PROFILES_CATEGORY.value, title, profileTemplate.name),
localize('import description', "Select data to import")
);
const title = localize('import profile preview', "Import");
let importProfile = await this.selectProfileResources(
userDataProfileImportState,
localize('import title', "{0}: {1} ({2})", PROFILES_CATEGORY.value, title, profileTemplate.name),
localize('import description', "Chose what to import")
);

if (importProfile === undefined) {
return;
}
if (importProfile === undefined) {
return;
}

if (!importProfile) {
importProfile = await this.showProfilePreviewView(`workbench.views.profiles.import.preview`, title, userDataProfileImportState);
}
if (!importProfile) {
importProfile = await this.showProfilePreviewView(`workbench.views.profiles.import.preview`, title, userDataProfileImportState);
}

if (!importProfile) {
return;
}
if (!importProfile) {
return;
}

profileTemplate = await userDataProfileImportState.getProfileTemplateToImport();
const profile = await this.getProfileToImport(profileTemplate);
if (!profile) {
return;
}

profileTemplate = await userDataProfileImportState.getProfileTemplateToImport();
const profile = await this.getProfileToImport(profileTemplate);
if (!profile) {
return;
}
await this.progressService.withProgress({
location: ProgressLocation.Notification,
title: localize('profiles.importing', "{0}: Importing...", PROFILES_CATEGORY.value),
}, async progress => {
if (profileTemplate.settings) {
await this.instantiationService.createInstance(SettingsResource).apply(profileTemplate.settings, profile);
}
Expand All @@ -279,16 +287,16 @@ export class UserDataProfileImportExportService extends Disposable implements IU
await this.instantiationService.createInstance(ExtensionsResource).apply(profileTemplate.extensions, profile);
}
await this.userDataProfileManagementService.switchProfile(profile);
});

this.notificationService.info(localize('imported profile', "{0}: Imported successfully.", PROFILES_CATEGORY.value));
this.notificationService.info(localize('imported profile', "Profile '{0}' is imported successfully.", profile.name));
});
} finally {
disposables.dispose();
}
}

private async saveProfileContent(name: string, content: string): Promise<{ resource: URI; id: string } | null> {
const id = await this.pickProfileContentHandler();
const id = await this.pickProfileContentHandler(name);
if (!id) {
return null;
}
Expand Down Expand Up @@ -333,13 +341,34 @@ export class UserDataProfileImportExportService extends Disposable implements IU
return null;
}

private async pickProfileContentHandler(): Promise<string | undefined> {
private async pickProfileContentHandler(name: string): Promise<string | undefined> {
await this.extensionService.activateByEvent('onProfile');
if (this.profileContentHandlers.size === 1) {
return this.profileContentHandlers.values().next().value;
}
const result = await this.quickInputService.pick([...this.profileContentHandlers.entries()].map(([id, handler]) => ({ label: handler.name, id })),
{ placeHolder: localize('select profile content handler', "Select the location where to export the profile") });
const linkHandlers: { id: string; label: string }[] = [];
const fileHandlers: { id: string; label: string }[] = [];
for (const [id, profileContentHandler] of this.profileContentHandlers) {
if (profileContentHandler.extensionId) {
linkHandlers.push({ id, label: profileContentHandler.name });
} else {
fileHandlers.push({ id, label: profileContentHandler.name });
}
}
const options: QuickPickItem[] = [];
if (linkHandlers.length) {
options.push({ label: localize('link', "link"), type: 'separator' });
options.push(...linkHandlers);
}
if (fileHandlers.length) {
options.push({ label: localize('file', "file"), type: 'separator' });
options.push(...fileHandlers);
}
const result = await this.quickInputService.pick(options,
{
title: localize('select profile content handler', "Export '{0}' profile as...", name),
hideInput: true
});
return result?.id;
}

Expand Down Expand Up @@ -502,7 +531,7 @@ export class UserDataProfileImportExportService extends Disposable implements IU

class FileUserDataProfileContentHandler implements IUserDataProfileContentHandler {

readonly name = localize('file', "Local");
readonly name = localize('local', "Local");

constructor(
@IFileDialogService private readonly fileDialogService: IFileDialogService,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { registerIcon } from 'vs/platform/theme/common/iconRegistry';
import { Codicon } from 'vs/base/common/codicons';
import { ITreeItem, ITreeItemCheckboxState, ITreeItemLabel } from 'vs/workbench/common/views';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IDisposable } from 'vs/base/common/lifecycle';

export interface DidChangeUserDataProfileEvent {
readonly preserveData: boolean;
Expand Down Expand Up @@ -69,7 +70,7 @@ export const IUserDataProfileImportExportService = createDecorator<IUserDataProf
export interface IUserDataProfileImportExportService {
readonly _serviceBrand: undefined;

registerProfileContentHandler(id: string, profileContentHandler: IUserDataProfileContentHandler): void;
registerProfileContentHandler(id: string, profileContentHandler: IUserDataProfileContentHandler): IDisposable;
unregisterProfileContentHandler(id: string): void;

exportProfile(): Promise<void>;
Expand Down Expand Up @@ -105,7 +106,6 @@ export interface IProfileResourceChildTreeItem extends ITreeItem {

export interface IUserDataProfileContentHandler {
readonly name: string;
readonly description?: string;
readonly extensionId?: string;
saveProfile(name: string, content: string, token: CancellationToken): Promise<URI | null>;
readProfile(uri: URI, token: CancellationToken): Promise<string | null>;
Expand Down
1 change: 0 additions & 1 deletion src/vscode-dts/vscode.proposed.profileContentHandlers.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ declare module 'vscode' {

export interface ProfileContentHandler {
readonly name: string;
readonly description?: string;
saveProfile(name: string, content: string, token: CancellationToken): Thenable<Uri | null>;
readProfile(uri: Uri, token: CancellationToken): Thenable<string | null>;
}
Expand Down

0 comments on commit 4d4ca20

Please sign in to comment.