From f2bcf4c272c9afab6cff74053fcb4aa9367163ab Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 27 Dec 2022 15:02:26 +0100 Subject: [PATCH] fix #167699 (#170108) --- extensions/github/src/importExportProfiles.ts | 14 ++-- .../mainThreadProfilContentHandlers.ts | 5 +- .../workbench/api/common/extHost.protocol.ts | 7 +- .../common/extHostProfileContentHandler.ts | 8 ++- .../userDataProfileImportExportService.ts | 70 +++++++++++-------- .../userDataProfile/common/userDataProfile.ts | 9 ++- ...scode.proposed.profileContentHandlers.d.ts | 4 +- 7 files changed, 70 insertions(+), 47 deletions(-) diff --git a/extensions/github/src/importExportProfiles.ts b/extensions/github/src/importExportProfiles.ts index 8200d546c79..a406f12423d 100644 --- a/extensions/github/src/importExportProfiles.ts +++ b/extensions/github/src/importExportProfiles.ts @@ -48,7 +48,7 @@ class GitHubGistProfileContentHandler implements vscode.ProfileContentHandler { } } - async saveProfile(name: string, content: string): Promise { + async saveProfile(name: string, content: string): Promise<{ readonly id: string; readonly link: vscode.Uri } | null> { const octokit = await this.getOctokit(); const result = await octokit.gists.create({ public: false, @@ -58,7 +58,11 @@ class GitHubGistProfileContentHandler implements vscode.ProfileContentHandler { } } }); - return result.data.html_url ? vscode.Uri.parse(result.data.html_url) : null; + if (result.data.id && result.data.html_url) { + const link = vscode.Uri.parse(result.data.html_url); + return { id: result.data.id, link }; + } + return null; } private _public_octokit: Promise | undefined; @@ -72,8 +76,10 @@ class GitHubGistProfileContentHandler implements vscode.ProfileContentHandler { return this._public_octokit; } - async readProfile(uri: vscode.Uri): Promise { - const gist_id = basename(uri.path); + async readProfile(id: string): Promise; + async readProfile(uri: vscode.Uri): Promise; + async readProfile(arg: string | vscode.Uri): Promise { + const gist_id = typeof arg === 'string' ? arg : basename(arg.path); const octokit = await this.getPublicOctokit(); try { const gist = await octokit.gists.get({ gist_id }); diff --git a/src/vs/workbench/api/browser/mainThreadProfilContentHandlers.ts b/src/vs/workbench/api/browser/mainThreadProfilContentHandlers.ts index fc1a876ac18..55cb66b3d1d 100644 --- a/src/vs/workbench/api/browser/mainThreadProfilContentHandlers.ts +++ b/src/vs/workbench/api/browser/mainThreadProfilContentHandlers.ts @@ -5,10 +5,11 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { Disposable, DisposableMap, IDisposable } from 'vs/base/common/lifecycle'; +import { revive } from 'vs/base/common/marshalling'; 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'; -import { IUserDataProfileImportExportService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { ISaveProfileResult, IUserDataProfileImportExportService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; @extHostNamedCustomer(MainContext.MainThreadProfileContentHandlers) export class MainThreadProfileContentHandlers extends Disposable implements MainThreadProfileContentHandlersShape { @@ -31,7 +32,7 @@ export class MainThreadProfileContentHandlers extends Disposable implements Main extensionId, saveProfile: async (name: string, content: string, token: CancellationToken) => { const result = await this.proxy.$saveProfile(id, name, content, token); - return result ? URI.revive(result) : null; + return result ? revive(result) : null; }, readProfile: async (uri: URI, token: CancellationToken) => { return this.proxy.$readProfile(id, uri, token); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index af66b1293d2..6f0caacc47b 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -12,7 +12,7 @@ import { IMarkdownString } from 'vs/base/common/htmlContent'; import { IDisposable } from 'vs/base/common/lifecycle'; import * as performance from 'vs/base/common/performance'; import Severity from 'vs/base/common/severity'; -import { URI, UriComponents } from 'vs/base/common/uri'; +import { URI, UriComponents, UriDto } from 'vs/base/common/uri'; import { RenderLineNumbersType, TextEditorCursorStyle } from 'vs/editor/common/config/editorOptions'; import { ISingleEditOperation } from 'vs/editor/common/core/editOperation'; import { IPosition } from 'vs/editor/common/core/position'; @@ -71,6 +71,7 @@ import { ITextQueryBuilderOptions } from 'vs/workbench/services/search/common/qu import * as search from 'vs/workbench/services/search/common/search'; import { EditSessionIdentityMatch } from 'vs/platform/workspace/common/editSessions'; import { TerminalCommandMatchResult, TerminalQuickFixCommand, TerminalQuickFixOpener } from 'vscode'; +import { ISaveProfileResult } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; export type TerminalQuickFix = TerminalQuickFixCommand | TerminalQuickFixOpener; @@ -1091,8 +1092,8 @@ export interface MainThreadProfileContentHandlersShape { } export interface ExtHostProfileContentHandlersShape { - $saveProfile(id: string, name: string, content: string, token: CancellationToken): Promise; - $readProfile(id: string, uri: UriComponents, token: CancellationToken): Promise; + $saveProfile(id: string, name: string, content: string, token: CancellationToken): Promise | null>; + $readProfile(id: string, idOrUri: string | UriComponents, token: CancellationToken): Promise; } export interface ITextSearchComplete { diff --git a/src/vs/workbench/api/common/extHostProfileContentHandler.ts b/src/vs/workbench/api/common/extHostProfileContentHandler.ts index f4c300a3bc0..e12f12c6cd8 100644 --- a/src/vs/workbench/api/common/extHostProfileContentHandler.ts +++ b/src/vs/workbench/api/common/extHostProfileContentHandler.ts @@ -5,9 +5,11 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { toDisposable } from 'vs/base/common/lifecycle'; +import { isString } from 'vs/base/common/types'; import { URI, UriComponents } from 'vs/base/common/uri'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; +import { ISaveProfileResult } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; import type * as vscode from 'vscode'; import { ExtHostProfileContentHandlersShape, IMainContext, MainContext, MainThreadProfileContentHandlersShape } from './extHost.protocol'; @@ -43,7 +45,7 @@ export class ExtHostProfileContentHandlers implements ExtHostProfileContentHandl }); } - async $saveProfile(id: string, name: string, content: string, token: CancellationToken): Promise { + async $saveProfile(id: string, name: string, content: string, token: CancellationToken): Promise { const handler = this.handlers.get(id); if (!handler) { throw new Error(`Unknown handler with id: ${id}`); @@ -52,12 +54,12 @@ export class ExtHostProfileContentHandlers implements ExtHostProfileContentHandl return handler.saveProfile(name, content, token); } - async $readProfile(id: string, uri: UriComponents, token: CancellationToken): Promise { + async $readProfile(id: string, idOrUri: string | UriComponents, token: CancellationToken): Promise { const handler = this.handlers.get(id); if (!handler) { throw new Error(`Unknown handler with id: ${id}`); } - return handler.readProfile(URI.revive(uri), token); + return handler.readProfile(isString(idOrUri) ? idOrUri : URI.revive(idOrUri), token); } } diff --git a/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts b/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts index af4849810f5..30a3e1d1e28 100644 --- a/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts +++ b/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts @@ -8,7 +8,7 @@ import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/ import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; 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_IN_PROGRESS_CONTEXT, PROFILES_TTILE, defaultUserDataProfileIcon, IUserDataProfileService, IProfileResourceTreeItem, IProfileResourceChildTreeItem, PROFILES_CATEGORY, IUserDataProfileManagementService, ProfileResourceType, IS_PROFILE_EXPORT_IN_PROGRESS_CONTEXT } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { IUserDataProfileImportExportService, PROFILE_FILTER, PROFILE_EXTENSION, IUserDataProfileContentHandler, IS_PROFILE_IMPORT_IN_PROGRESS_CONTEXT, PROFILES_TTILE, defaultUserDataProfileIcon, IUserDataProfileService, IProfileResourceTreeItem, IProfileResourceChildTreeItem, PROFILES_CATEGORY, IUserDataProfileManagementService, ProfileResourceType, IS_PROFILE_EXPORT_IN_PROGRESS_CONTEXT, ISaveProfileResult } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; 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'; @@ -87,6 +87,7 @@ const EXPORT_PROFILE_PREVIEW_VIEW = 'workbench.views.profiles.export.preview'; export class UserDataProfileImportExportService extends Disposable implements IUserDataProfileImportExportService, IURLHandler { private static readonly PROFILE_URL_AUTHORITY_PREFIX = 'profile-'; + private static readonly PROFILE_URL_AUTHORITY = 'profile'; readonly _serviceBrand: undefined; @@ -116,6 +117,7 @@ export class UserDataProfileImportExportService extends Disposable implements IU @IRequestService private readonly requestService: IRequestService, @IURLService urlService: IURLService, @IProductService private readonly productService: IProductService, + @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, @ILogService private readonly logService: ILogService, ) { super(); @@ -139,7 +141,7 @@ export class UserDataProfileImportExportService extends Disposable implements IU } private isProfileURL(uri: URI): boolean { - return new RegExp(`^${UserDataProfileImportExportService.PROFILE_URL_AUTHORITY_PREFIX}`).test(uri.authority); + return uri.authority === UserDataProfileImportExportService.PROFILE_URL_AUTHORITY || new RegExp(`^${UserDataProfileImportExportService.PROFILE_URL_AUTHORITY_PREFIX}`).test(uri.authority); } async handleURL(uri: URI): Promise { @@ -322,14 +324,24 @@ export class UserDataProfileImportExportService extends Disposable implements IU location: ProgressLocation.Window, title: localize('profiles.exporting', "{0}: Exporting...", PROFILES_CATEGORY.value), }, async progress => { - const saveResult = await this.saveProfileContent(profile.name, JSON.stringify(profile)); - if (saveResult) { - 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 id = await this.pickProfileContentHandler(profile.name); + if (!id) { + return; + } + const profileContentHandler = this.profileContentHandlers.get(id); + if (!profileContentHandler) { + return; + } + const saveResult = await profileContentHandler.saveProfile(profile.name, JSON.stringify(profile), CancellationToken.None); + if (!saveResult) { + return; + } + const message = localize('export success', "Profile '{0}' is exported successfully.", profile.name); + if (profileContentHandler.extensionId) { const result = await this.dialogService.show( Severity.Info, - localize('export success', "Profile '{0}' is exported successfully.", profile.name), - buttons, + message, + [localize('copy', "Copy Link"), localize('open', "Open in {0}", profileContentHandler.name), localize('close', "Close")], { cancelId: 2 } ); switch (result.choice) { @@ -337,15 +349,17 @@ export class UserDataProfileImportExportService extends Disposable implements IU await this.clipboardService.writeText( URI.from({ scheme: this.productService.urlProtocol, - authority: `${UserDataProfileImportExportService.PROFILE_URL_AUTHORITY_PREFIX}${saveResult.id}`, - path: `/${saveResult.resource.toString()}` + authority: `${UserDataProfileImportExportService.PROFILE_URL_AUTHORITY}`, + path: `/${id}/${saveResult.id}` }).toString()); break; case 1: - await this.openerService.open(saveResult.resource.toString()); + await this.openerService.open(saveResult.link.toString()); break; } + } else { + await this.dialogService.show(Severity.Info, message); } }); } finally { @@ -353,30 +367,24 @@ export class UserDataProfileImportExportService extends Disposable implements IU } } - private async saveProfileContent(name: string, content: string): Promise<{ resource: URI; id: string } | null> { - const id = await this.pickProfileContentHandler(name); - if (!id) { - return null; - } - const profileContentHandler = this.profileContentHandlers.get(id); - if (!profileContentHandler) { - return null; - } - const resource = await profileContentHandler.saveProfile(name, content, CancellationToken.None); - return resource ? { resource, id } : null; - } - private async resolveProfileContent(resource: URI): Promise { if (await this.fileService.canHandleResource(resource)) { return this.fileUserDataProfileContentHandler.readProfile(resource, CancellationToken.None); } if (this.isProfileURL(resource)) { - const handlerId = resource.authority.substring(UserDataProfileImportExportService.PROFILE_URL_AUTHORITY_PREFIX.length); + let handlerId: string, idOrUri: string | URI; + if (resource.authority === UserDataProfileImportExportService.PROFILE_URL_AUTHORITY) { + idOrUri = this.uriIdentityService.extUri.basename(resource); + handlerId = this.uriIdentityService.extUri.basename(this.uriIdentityService.extUri.dirname(resource)); + } else { + handlerId = resource.authority.substring(UserDataProfileImportExportService.PROFILE_URL_AUTHORITY_PREFIX.length); + idOrUri = URI.parse(resource.path.substring(1)); + } await this.extensionService.activateByEvent(`onProfile:${handlerId}`); const profileContentHandler = this.profileContentHandlers.get(handlerId); if (profileContentHandler) { - return profileContentHandler.readProfile(URI.parse(resource.path.substring(1)), CancellationToken.None); + return profileContentHandler.readProfile(idOrUri, CancellationToken.None); } } @@ -599,17 +607,17 @@ class FileUserDataProfileContentHandler implements IUserDataProfileContentHandle @ITextFileService private readonly textFileService: ITextFileService, ) { } - async saveProfile(name: string, content: string, token: CancellationToken): Promise { - const profileLocation = await this.fileDialogService.showSaveDialog({ + async saveProfile(name: string, content: string, token: CancellationToken): Promise { + const link = await this.fileDialogService.showSaveDialog({ title: localize('export profile dialog', "Save Profile"), filters: PROFILE_FILTER, defaultUri: this.uriIdentityService.extUri.joinPath(await this.fileDialogService.defaultFilePath(), `${name}.${PROFILE_EXTENSION}`), }); - if (!profileLocation) { + if (!link) { return null; } - await this.textFileService.create([{ resource: profileLocation, value: content, options: { overwrite: true } }]); - return profileLocation; + await this.textFileService.create([{ resource: link, value: content, options: { overwrite: true } }]); + return { link, id: link.toString() }; } async readProfile(uri: URI, token: CancellationToken): Promise { diff --git a/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts b/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts index 676b38ccc87..e9b4381ae7f 100644 --- a/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts +++ b/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts @@ -105,11 +105,16 @@ export interface IProfileResourceChildTreeItem extends ITreeItem { parent: IProfileResourceTreeItem; } +export interface ISaveProfileResult { + readonly id: string; + readonly link: URI; +} + export interface IUserDataProfileContentHandler { readonly name: string; readonly extensionId?: string; - saveProfile(name: string, content: string, token: CancellationToken): Promise; - readProfile(uri: URI, token: CancellationToken): Promise; + saveProfile(name: string, content: string, token: CancellationToken): Promise; + readProfile(idOrUri: string | URI, token: CancellationToken): Promise; } export const defaultUserDataProfileIcon = registerIcon('defaultProfile-icon', Codicon.settings, localize('defaultProfileIcon', 'Icon for Default Profile.')); diff --git a/src/vscode-dts/vscode.proposed.profileContentHandlers.d.ts b/src/vscode-dts/vscode.proposed.profileContentHandlers.d.ts index 9f3dd0a803d..e66bcc0f439 100644 --- a/src/vscode-dts/vscode.proposed.profileContentHandlers.d.ts +++ b/src/vscode-dts/vscode.proposed.profileContentHandlers.d.ts @@ -7,8 +7,8 @@ declare module 'vscode' { export interface ProfileContentHandler { readonly name: string; - saveProfile(name: string, content: string, token: CancellationToken): Thenable; - readProfile(uri: Uri, token: CancellationToken): Thenable; + saveProfile(name: string, content: string, token: CancellationToken): Thenable<{ readonly id: string; readonly link: Uri } | null>; + readProfile(idOrUri: string | Uri, token: CancellationToken): Thenable; } export namespace window {