mirror of
https://github.com/Microsoft/vscode
synced 2024-08-27 13:00:58 +00:00
Share profiles (#166898)
* Share profiles #159891 - Share profile in GitHub - Profile resource quick pick - Import profile from vscode link * remove duplicate code
This commit is contained in:
parent
ebb77a7dfd
commit
e43bf31ab1
|
@ -13,7 +13,9 @@
|
|||
"Other"
|
||||
],
|
||||
"activationEvents": [
|
||||
"*"
|
||||
"*",
|
||||
"onProfile",
|
||||
"onProfile:github"
|
||||
],
|
||||
"extensionDependencies": [
|
||||
"vscode.git-base"
|
||||
|
@ -27,7 +29,8 @@
|
|||
},
|
||||
"enabledApiProposals": [
|
||||
"contribShareMenu",
|
||||
"contribEditSessions"
|
||||
"contribEditSessions",
|
||||
"profileContentHandlers"
|
||||
],
|
||||
"contributes": {
|
||||
"commands": [
|
||||
|
|
|
@ -12,6 +12,7 @@ import { DisposableStore, repositoryHasGitHubRemote } from './util';
|
|||
import { GithubPushErrorHandler } from './pushErrorHandler';
|
||||
import { GitBaseExtension } from './typings/git-base';
|
||||
import { GithubRemoteSourcePublisher } from './remoteSourcePublisher';
|
||||
import './importExportProfiles';
|
||||
|
||||
export function activate(context: ExtensionContext): void {
|
||||
context.subscriptions.push(initializeGitBaseExtension());
|
||||
|
|
80
extensions/github/src/importExportProfiles.ts
Normal file
80
extensions/github/src/importExportProfiles.ts
Normal file
|
@ -0,0 +1,80 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Octokit } from '@octokit/rest';
|
||||
import * as vscode from 'vscode';
|
||||
import { httpsOverHttp } from 'tunnel';
|
||||
import { Agent, globalAgent } from 'https';
|
||||
import { basename } from 'path';
|
||||
import { URL } from 'url';
|
||||
|
||||
class GitHubGistProfileContentHandler implements vscode.ProfileContentHandler {
|
||||
|
||||
readonly name = vscode.l10n.t('GitHub');
|
||||
|
||||
private _octokit: Promise<Octokit> | undefined;
|
||||
private getOctokit(): Promise<Octokit> {
|
||||
if (!this._octokit) {
|
||||
this._octokit = (async () => {
|
||||
const session = await vscode.authentication.getSession('github', ['gist', 'user:email'], { createIfNone: true });
|
||||
const token = session.accessToken;
|
||||
const agent = this.getAgent();
|
||||
|
||||
const { Octokit } = await import('@octokit/rest');
|
||||
|
||||
return new Octokit({
|
||||
request: { agent },
|
||||
userAgent: 'GitHub VSCode',
|
||||
auth: `token ${token}`
|
||||
});
|
||||
})();
|
||||
}
|
||||
return this._octokit;
|
||||
}
|
||||
|
||||
private getAgent(url: string | undefined = process.env.HTTPS_PROXY): Agent {
|
||||
if (!url) {
|
||||
return globalAgent;
|
||||
}
|
||||
try {
|
||||
const { hostname, port, username, password } = new URL(url);
|
||||
const auth = username && password && `${username}:${password}`;
|
||||
return httpsOverHttp({ proxy: { host: hostname, port, proxyAuth: auth } });
|
||||
} catch (e) {
|
||||
vscode.window.showErrorMessage(`HTTPS_PROXY environment variable ignored: ${e.message}`);
|
||||
return globalAgent;
|
||||
}
|
||||
}
|
||||
|
||||
async saveProfile(name: string, content: string): Promise<vscode.Uri | null> {
|
||||
const octokit = await this.getOctokit();
|
||||
const result = await octokit.gists.create({
|
||||
public: true,
|
||||
files: {
|
||||
[name]: {
|
||||
content
|
||||
}
|
||||
}
|
||||
});
|
||||
return result.data.html_url ? vscode.Uri.parse(result.data.html_url) : null;
|
||||
}
|
||||
|
||||
async readProfile(uri: vscode.Uri): Promise<string | null> {
|
||||
const gist_id = basename(uri.path);
|
||||
const octokit = await this.getOctokit();
|
||||
try {
|
||||
const gist = await octokit.gists.get({ gist_id });
|
||||
if (gist.data.files) {
|
||||
return gist.data.files[Object.keys(gist.data.files)[0]]?.content ?? null;
|
||||
}
|
||||
} catch (error) {
|
||||
// ignore
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
vscode.window.registerProfileContentHandler('github', new GitHubGistProfileContentHandler());
|
|
@ -9,6 +9,7 @@
|
|||
},
|
||||
"include": [
|
||||
"src/**/*",
|
||||
"../../src/vscode-dts/vscode.d.ts"
|
||||
"../../src/vscode-dts/vscode.d.ts",
|
||||
"../../src/vscode-dts/vscode.proposed.profileContentHandlers.d.ts",
|
||||
]
|
||||
}
|
||||
|
|
|
@ -75,6 +75,7 @@ import './mainThreadAuthentication';
|
|||
import './mainThreadTimeline';
|
||||
import './mainThreadTesting';
|
||||
import './mainThreadSecretState';
|
||||
import './mainThreadProfilContentHandlers';
|
||||
|
||||
export class ExtensionPoints implements IWorkbenchContribution {
|
||||
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { Disposable, toDisposable } 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';
|
||||
import { IUserDataProfileImportExportService } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadProfileContentHandlers)
|
||||
export class MainThreadProfileContentHandlers extends Disposable implements MainThreadProfileContentHandlersShape {
|
||||
|
||||
private readonly proxy: ExtHostProfileContentHandlersShape;
|
||||
|
||||
private readonly registeredHandlers = new Set<string>();
|
||||
|
||||
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, {
|
||||
name,
|
||||
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;
|
||||
},
|
||||
readProfile: async (uri: URI, token: CancellationToken) => {
|
||||
return this.proxy.$readProfile(id, uri, token);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async $unregisterProfileContentHandler(id: string): Promise<void> {
|
||||
this.userDataProfileImportExportService.unregisterProfileContentHandler(id);
|
||||
}
|
||||
|
||||
}
|
|
@ -95,6 +95,7 @@ import { checkProposedApiEnabled, ExtensionIdentifierSet, isProposedApiEnabled }
|
|||
import { DebugConfigurationProviderTriggerKind } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { IExtHostLocalizationService } from 'vs/workbench/api/common/extHostLocalizationService';
|
||||
import { EditSessionIdentityMatch } from 'vs/platform/workspace/common/editSessions';
|
||||
import { ExtHostProfileContentHandlers } from 'vs/workbench/api/common/extHostProfileContentHandler';
|
||||
|
||||
export interface IExtensionRegistries {
|
||||
mine: ExtensionDescriptionRegistry;
|
||||
|
@ -186,6 +187,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
|||
const extHostWebviewViews = rpcProtocol.set(ExtHostContext.ExtHostWebviewViews, new ExtHostWebviewViews(rpcProtocol, extHostWebviews));
|
||||
const extHostTesting = rpcProtocol.set(ExtHostContext.ExtHostTesting, new ExtHostTesting(rpcProtocol, extHostCommands, extHostDocumentsAndEditors));
|
||||
const extHostUriOpeners = rpcProtocol.set(ExtHostContext.ExtHostUriOpeners, new ExtHostUriOpeners(rpcProtocol));
|
||||
const extHostProfileContentHandlers = rpcProtocol.set(ExtHostContext.ExtHostProfileContentHandlers, new ExtHostProfileContentHandlers(rpcProtocol));
|
||||
rpcProtocol.set(ExtHostContext.ExtHostInteractive, new ExtHostInteractive(rpcProtocol, extHostNotebook, extHostDocumentsAndEditors, extHostCommands, extHostLogService));
|
||||
|
||||
// Check that no named customers are missing
|
||||
|
@ -792,6 +794,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
|||
checkProposedApiEnabled(extension, 'externalUriOpener');
|
||||
return extHostUriOpeners.registerExternalUriOpener(extension.identifier, id, opener, metadata);
|
||||
},
|
||||
registerProfileContentHandler(id: string, handler: vscode.ProfileContentHandler) {
|
||||
checkProposedApiEnabled(extension, 'profileContentHandlers');
|
||||
return extHostProfileContentHandlers.registrProfileContentHandler(extension, id, handler);
|
||||
},
|
||||
get tabGroups(): vscode.TabGroups {
|
||||
return extHostEditorTabs.tabGroups;
|
||||
}
|
||||
|
|
|
@ -1079,6 +1079,16 @@ export interface ExtHostUriOpenersShape {
|
|||
$openUri(id: string, context: { resolvedUri: UriComponents; sourceUri: UriComponents }, token: CancellationToken): Promise<void>;
|
||||
}
|
||||
|
||||
export interface MainThreadProfileContentHandlersShape {
|
||||
$registerProfileContentHandler(id: string, name: string, extensionId: string): Promise<void>;
|
||||
$unregisterProfileContentHandler(id: string): Promise<void>;
|
||||
}
|
||||
|
||||
export interface ExtHostProfileContentHandlersShape {
|
||||
$saveProfile(id: string, name: string, content: string, token: CancellationToken): Promise<UriComponents | null>;
|
||||
$readProfile(id: string, uri: UriComponents, token: CancellationToken): Promise<string | null>;
|
||||
}
|
||||
|
||||
export interface ITextSearchComplete {
|
||||
limitHit?: boolean;
|
||||
}
|
||||
|
@ -2326,6 +2336,7 @@ export const MainContext = {
|
|||
MainThreadCustomEditors: createProxyIdentifier<MainThreadCustomEditorsShape>('MainThreadCustomEditors'),
|
||||
MainThreadUrls: createProxyIdentifier<MainThreadUrlsShape>('MainThreadUrls'),
|
||||
MainThreadUriOpeners: createProxyIdentifier<MainThreadUriOpenersShape>('MainThreadUriOpeners'),
|
||||
MainThreadProfileContentHandlers: createProxyIdentifier<MainThreadProfileContentHandlersShape>('MainThreadProfileContentHandlers'),
|
||||
MainThreadWorkspace: createProxyIdentifier<MainThreadWorkspaceShape>('MainThreadWorkspace'),
|
||||
MainThreadFileSystem: createProxyIdentifier<MainThreadFileSystemShape>('MainThreadFileSystem'),
|
||||
MainThreadExtensionService: createProxyIdentifier<MainThreadExtensionServiceShape>('MainThreadExtensionService'),
|
||||
|
@ -2385,6 +2396,7 @@ export const ExtHostContext = {
|
|||
ExtHostStorage: createProxyIdentifier<ExtHostStorageShape>('ExtHostStorage'),
|
||||
ExtHostUrls: createProxyIdentifier<ExtHostUrlsShape>('ExtHostUrls'),
|
||||
ExtHostUriOpeners: createProxyIdentifier<ExtHostUriOpenersShape>('ExtHostUriOpeners'),
|
||||
ExtHostProfileContentHandlers: createProxyIdentifier<ExtHostProfileContentHandlersShape>('ExtHostProfileContentHandlers'),
|
||||
ExtHostOutputService: createProxyIdentifier<ExtHostOutputServiceShape>('ExtHostOutputService'),
|
||||
ExtHosLabelService: createProxyIdentifier<ExtHostLabelServiceShape>('ExtHostLabelService'),
|
||||
ExtHostNotebook: createProxyIdentifier<ExtHostNotebookShape>('ExtHostNotebook'),
|
||||
|
|
63
src/vs/workbench/api/common/extHostProfileContentHandler.ts
Normal file
63
src/vs/workbench/api/common/extHostProfileContentHandler.ts
Normal file
|
@ -0,0 +1,63 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { toDisposable } from 'vs/base/common/lifecycle';
|
||||
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 type * as vscode from 'vscode';
|
||||
import { ExtHostProfileContentHandlersShape, IMainContext, MainContext, MainThreadProfileContentHandlersShape } from './extHost.protocol';
|
||||
|
||||
|
||||
export class ExtHostProfileContentHandlers implements ExtHostProfileContentHandlersShape {
|
||||
|
||||
private readonly proxy: MainThreadProfileContentHandlersShape;
|
||||
|
||||
private readonly handlers = new Map<string, vscode.ProfileContentHandler>();
|
||||
|
||||
constructor(
|
||||
mainContext: IMainContext,
|
||||
) {
|
||||
this.proxy = mainContext.getProxy(MainContext.MainThreadProfileContentHandlers);
|
||||
}
|
||||
|
||||
registrProfileContentHandler(
|
||||
extension: IExtensionDescription,
|
||||
id: string,
|
||||
handler: vscode.ProfileContentHandler,
|
||||
): vscode.Disposable {
|
||||
checkProposedApiEnabled(extension, 'profileContentHandlers');
|
||||
if (this.handlers.has(id)) {
|
||||
throw new Error(`Handler with id '${id}' already registered`);
|
||||
}
|
||||
|
||||
this.handlers.set(id, handler);
|
||||
this.proxy.$registerProfileContentHandler(id, handler.name, extension.identifier.value);
|
||||
|
||||
return toDisposable(() => {
|
||||
this.handlers.delete(id);
|
||||
this.proxy.$unregisterProfileContentHandler(id);
|
||||
});
|
||||
}
|
||||
|
||||
async $saveProfile(id: string, name: string, content: string, token: CancellationToken): Promise<UriComponents | null> {
|
||||
const handler = this.handlers.get(id);
|
||||
if (!handler) {
|
||||
throw new Error(`Unknown handler with id: ${id}`);
|
||||
}
|
||||
|
||||
return handler.saveProfile(name, content, token);
|
||||
}
|
||||
|
||||
async $readProfile(id: string, uri: UriComponents, token: CancellationToken): Promise<string | null> {
|
||||
const handler = this.handlers.get(id);
|
||||
if (!handler) {
|
||||
throw new Error(`Unknown handler with id: ${id}`);
|
||||
}
|
||||
|
||||
return handler.readProfile(URI.revive(uri), token);
|
||||
}
|
||||
}
|
|
@ -50,6 +50,7 @@ export const allApiProposals = Object.freeze({
|
|||
notebookMessaging: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookMessaging.d.ts',
|
||||
notebookMime: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookMime.d.ts',
|
||||
portsAttributes: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.portsAttributes.d.ts',
|
||||
profileContentHandlers: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.profileContentHandlers.d.ts',
|
||||
quickPickSortByLabel: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.quickPickSortByLabel.d.ts',
|
||||
resolvers: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.resolvers.d.ts',
|
||||
scmActionButton: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.scmActionButton.d.ts',
|
||||
|
|
|
@ -17,7 +17,7 @@ import { IStorageService } from 'vs/platform/storage/common/storage';
|
|||
import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile';
|
||||
import { IUserDataProfileStorageService } from 'vs/platform/userDataProfile/common/userDataProfileStorageService';
|
||||
import { ITreeItemCheckboxState, TreeItemCollapsibleState } from 'vs/workbench/common/views';
|
||||
import { IProfileResource, IProfileResourceChildTreeItem, IProfileResourceTreeItem } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
|
||||
import { IProfileResource, IProfileResourceChildTreeItem, IProfileResourceTreeItem, ProfileResourceType } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
|
||||
|
||||
interface IProfileExtension {
|
||||
identifier: IExtensionIdentifier;
|
||||
|
@ -39,6 +39,10 @@ export class ExtensionsResource implements IProfileResource {
|
|||
|
||||
async getContent(profile: IUserDataProfile, exclude?: string[]): Promise<string> {
|
||||
const extensions = await this.getLocalExtensions(profile);
|
||||
return this.toContent(extensions, exclude);
|
||||
}
|
||||
|
||||
toContent(extensions: IProfileExtension[], exclude?: string[]): string {
|
||||
return JSON.stringify(exclude?.length ? extensions.filter(e => !exclude.includes(e.identifier.id.toLowerCase())) : extensions);
|
||||
}
|
||||
|
||||
|
@ -140,22 +144,18 @@ export class ExtensionsResource implements IProfileResource {
|
|||
}
|
||||
}
|
||||
|
||||
export class ExtensionsResourceExportTreeItem implements IProfileResourceTreeItem {
|
||||
abstract class ExtensionsResourceTreeItem implements IProfileResourceTreeItem {
|
||||
|
||||
readonly handle = this.profile.extensionsResource.toString();
|
||||
readonly type = ProfileResourceType.Extensions;
|
||||
readonly handle = ProfileResourceType.Extensions;
|
||||
readonly label = { label: localize('extensions', "Extensions") };
|
||||
readonly collapsibleState = TreeItemCollapsibleState.Expanded;
|
||||
checkbox: ITreeItemCheckboxState = { isChecked: true };
|
||||
|
||||
private readonly excludedExtensions = new Set<string>();
|
||||
|
||||
constructor(
|
||||
private readonly profile: IUserDataProfile,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
) { }
|
||||
protected readonly excludedExtensions = new Set<string>();
|
||||
|
||||
async getChildren(): Promise<IProfileResourceChildTreeItem[]> {
|
||||
const extensions = await this.instantiationService.createInstance(ExtensionsResource).getLocalExtensions(this.profile);
|
||||
const extensions = await this.getExtensions();
|
||||
const that = this;
|
||||
return extensions.map<IProfileResourceChildTreeItem>(e => ({
|
||||
handle: e.identifier.id.toLowerCase(),
|
||||
|
@ -182,48 +182,51 @@ export class ExtensionsResourceExportTreeItem implements IProfileResourceTreeIte
|
|||
}
|
||||
|
||||
async hasContent(): Promise<boolean> {
|
||||
const extensions = await this.instantiationService.createInstance(ExtensionsResource).getLocalExtensions(this.profile);
|
||||
const extensions = await this.getExtensions();
|
||||
return extensions.length > 0;
|
||||
}
|
||||
|
||||
abstract getContent(): Promise<string>;
|
||||
protected abstract getExtensions(): Promise<IProfileExtension[]>;
|
||||
|
||||
}
|
||||
|
||||
export class ExtensionsResourceExportTreeItem extends ExtensionsResourceTreeItem {
|
||||
|
||||
constructor(
|
||||
private readonly profile: IUserDataProfile,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected getExtensions(): Promise<IProfileExtension[]> {
|
||||
return this.instantiationService.createInstance(ExtensionsResource).getLocalExtensions(this.profile);
|
||||
}
|
||||
|
||||
async getContent(): Promise<string> {
|
||||
return this.instantiationService.createInstance(ExtensionsResource).getContent(this.profile, [...this.excludedExtensions.values()]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class ExtensionsResourceImportTreeItem implements IProfileResourceTreeItem {
|
||||
|
||||
readonly handle = 'extensions';
|
||||
readonly label = { label: localize('extensions', "Extensions") };
|
||||
readonly collapsibleState = TreeItemCollapsibleState.Expanded;
|
||||
export class ExtensionsResourceImportTreeItem extends ExtensionsResourceTreeItem {
|
||||
|
||||
constructor(
|
||||
private readonly content: string,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
) { }
|
||||
|
||||
async getChildren(): Promise<IProfileResourceChildTreeItem[]> {
|
||||
const extensions = await this.instantiationService.createInstance(ExtensionsResource).getProfileExtensions(this.content);
|
||||
return extensions.map<IProfileResourceChildTreeItem>(e => ({
|
||||
handle: e.identifier.id.toLowerCase(),
|
||||
parent: this,
|
||||
label: { label: e.displayName || e.identifier.id },
|
||||
description: e.disabled ? localize('disabled', "Disabled") : undefined,
|
||||
collapsibleState: TreeItemCollapsibleState.None,
|
||||
command: {
|
||||
id: 'extension.open',
|
||||
title: '',
|
||||
arguments: [e.identifier.id, undefined, true]
|
||||
}
|
||||
}));
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async hasContent(): Promise<boolean> {
|
||||
const extensions = await this.instantiationService.createInstance(ExtensionsResource).getProfileExtensions(this.content);
|
||||
return extensions.length > 0;
|
||||
protected getExtensions(): Promise<IProfileExtension[]> {
|
||||
return this.instantiationService.createInstance(ExtensionsResource).getProfileExtensions(this.content);
|
||||
}
|
||||
|
||||
async getContent(): Promise<string> {
|
||||
const extensionsResource = this.instantiationService.createInstance(ExtensionsResource);
|
||||
const extensions = await extensionsResource.getProfileExtensions(this.content);
|
||||
return extensionsResource.toContent(extensions, [...this.excludedExtensions.values()]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataPro
|
|||
import { IUserDataProfileStorageService } from 'vs/platform/userDataProfile/common/userDataProfileStorageService';
|
||||
import { API_OPEN_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands';
|
||||
import { ITreeItemCheckboxState, TreeItemCollapsibleState } from 'vs/workbench/common/views';
|
||||
import { IProfileResource, IProfileResourceTreeItem } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
|
||||
import { IProfileResource, IProfileResourceTreeItem, ProfileResourceType } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
|
||||
|
||||
interface IGlobalState {
|
||||
storage: IStringDictionary<string>;
|
||||
|
@ -71,9 +71,10 @@ export class GlobalStateResource implements IProfileResource {
|
|||
}
|
||||
}
|
||||
|
||||
export class GlobalStateResourceExportTreeItem implements IProfileResourceTreeItem {
|
||||
abstract class GlobalStateResourceTreeItem implements IProfileResourceTreeItem {
|
||||
|
||||
readonly handle = this.profile.globalStorageHome.toString();
|
||||
readonly type = ProfileResourceType.GlobalState;
|
||||
readonly handle = 'globalState';
|
||||
readonly label = { label: localize('globalState', "UI State") };
|
||||
readonly collapsibleState = TreeItemCollapsibleState.None;
|
||||
checkbox: ITreeItemCheckboxState = { isChecked: true };
|
||||
|
@ -83,14 +84,23 @@ export class GlobalStateResourceExportTreeItem implements IProfileResourceTreeIt
|
|||
arguments: [this.resource, undefined, undefined]
|
||||
};
|
||||
|
||||
constructor(
|
||||
private readonly profile: IUserDataProfile,
|
||||
private readonly resource: URI,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService
|
||||
) { }
|
||||
constructor(private readonly resource: URI) { }
|
||||
|
||||
async getChildren(): Promise<undefined> { return undefined; }
|
||||
|
||||
abstract getContent(): Promise<string>;
|
||||
}
|
||||
|
||||
export class GlobalStateResourceExportTreeItem extends GlobalStateResourceTreeItem {
|
||||
|
||||
constructor(
|
||||
private readonly profile: IUserDataProfile,
|
||||
resource: URI,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService
|
||||
) {
|
||||
super(resource);
|
||||
}
|
||||
|
||||
async hasContent(): Promise<boolean> {
|
||||
const globalState = await this.instantiationService.createInstance(GlobalStateResource).getGlobalState(this.profile);
|
||||
return Object.keys(globalState.storage).length > 0;
|
||||
|
@ -102,21 +112,17 @@ export class GlobalStateResourceExportTreeItem implements IProfileResourceTreeIt
|
|||
|
||||
}
|
||||
|
||||
export class GlobalStateResourceImportTreeItem implements IProfileResourceTreeItem {
|
||||
export class GlobalStateResourceImportTreeItem extends GlobalStateResourceTreeItem {
|
||||
|
||||
readonly handle = 'globalState';
|
||||
readonly label = { label: localize('globalState', "UI State") };
|
||||
readonly collapsibleState = TreeItemCollapsibleState.None;
|
||||
readonly command = {
|
||||
id: API_OPEN_EDITOR_COMMAND_ID,
|
||||
title: '',
|
||||
arguments: [this.resource, undefined, undefined]
|
||||
};
|
||||
constructor(
|
||||
private readonly content: string,
|
||||
resource: URI,
|
||||
) {
|
||||
super(resource);
|
||||
}
|
||||
|
||||
constructor(private readonly resource: URI) { }
|
||||
async getContent(): Promise<string> {
|
||||
return this.content;
|
||||
}
|
||||
|
||||
async getChildren(): Promise<undefined> { return undefined; }
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IProfileResource, IProfileResourceTreeItem } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
|
||||
import { IProfileResource, IProfileResourceTreeItem, ProfileResourceType } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
|
||||
import { platform, Platform } from 'vs/base/common/platform';
|
||||
import { ITreeItemCheckboxState, TreeItemCollapsibleState } from 'vs/workbench/common/views';
|
||||
import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile';
|
||||
|
@ -64,10 +64,11 @@ export class KeybindingsResource implements IProfileResource {
|
|||
|
||||
export class KeybindingsResourceTreeItem implements IProfileResourceTreeItem {
|
||||
|
||||
readonly type = ProfileResourceType.Keybindings;
|
||||
readonly handle = this.profile.keybindingsResource.toString();
|
||||
readonly label = { label: localize('keybindings', "Keyboard Shortcuts") };
|
||||
readonly collapsibleState = TreeItemCollapsibleState.None;
|
||||
checkbox: ITreeItemCheckboxState | undefined = { isChecked: true };
|
||||
checkbox: ITreeItemCheckboxState = { isChecked: true };
|
||||
readonly command = {
|
||||
id: API_OPEN_EDITOR_COMMAND_ID,
|
||||
title: '',
|
||||
|
|
|
@ -8,7 +8,7 @@ import { ConfigurationScope, Extensions, IConfigurationRegistry } from 'vs/platf
|
|||
import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IProfileResource, IProfileResourceTreeItem } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
|
||||
import { IProfileResource, IProfileResourceTreeItem, ProfileResourceType } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
|
||||
import { updateIgnoredSettings } from 'vs/platform/userDataSync/common/settingsMerge';
|
||||
import { IUserDataSyncUtilService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { ITreeItemCheckboxState, TreeItemCollapsibleState } from 'vs/workbench/common/views';
|
||||
|
@ -83,10 +83,11 @@ export class SettingsResource implements IProfileResource {
|
|||
|
||||
export class SettingsResourceTreeItem implements IProfileResourceTreeItem {
|
||||
|
||||
readonly type = ProfileResourceType.Settings;
|
||||
readonly handle = this.profile.settingsResource.toString();
|
||||
readonly label = { label: localize('settings', "Settings") };
|
||||
readonly collapsibleState = TreeItemCollapsibleState.None;
|
||||
checkbox: ITreeItemCheckboxState | undefined = { isChecked: true };
|
||||
checkbox: ITreeItemCheckboxState = { isChecked: true };
|
||||
readonly command = {
|
||||
id: API_OPEN_EDITOR_COMMAND_ID,
|
||||
title: '',
|
||||
|
|
|
@ -14,7 +14,7 @@ import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'
|
|||
import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile';
|
||||
import { API_OPEN_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands';
|
||||
import { ITreeItemCheckboxState, TreeItemCollapsibleState } from 'vs/workbench/common/views';
|
||||
import { IProfileResource, IProfileResourceChildTreeItem, IProfileResourceTreeItem } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
|
||||
import { IProfileResource, IProfileResourceChildTreeItem, IProfileResourceTreeItem, ProfileResourceType } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
|
||||
|
||||
interface ISnippetsContent {
|
||||
snippets: IStringDictionary<string>;
|
||||
|
@ -80,10 +80,11 @@ export class SnippetsResource implements IProfileResource {
|
|||
|
||||
export class SnippetsResourceTreeItem implements IProfileResourceTreeItem {
|
||||
|
||||
readonly type = ProfileResourceType.Snippets;
|
||||
readonly handle = this.profile.snippetsHome.toString();
|
||||
readonly label = { label: localize('snippets', "Snippets") };
|
||||
readonly collapsibleState = TreeItemCollapsibleState.Collapsed;
|
||||
checkbox: ITreeItemCheckboxState | undefined = { isChecked: true };
|
||||
checkbox: ITreeItemCheckboxState = { isChecked: true };
|
||||
|
||||
private readonly excludedSnippets = new ResourceSet();
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ import { ILogService } from 'vs/platform/log/common/log';
|
|||
import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile';
|
||||
import { API_OPEN_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands';
|
||||
import { ITreeItemCheckboxState, TreeItemCollapsibleState } from 'vs/workbench/common/views';
|
||||
import { IProfileResource, IProfileResourceTreeItem } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
|
||||
import { IProfileResource, IProfileResourceTreeItem, ProfileResourceType } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
|
||||
|
||||
interface ITasksResourceContent {
|
||||
tasks: string | null;
|
||||
|
@ -62,10 +62,11 @@ export class TasksResource implements IProfileResource {
|
|||
|
||||
export class TasksResourceTreeItem implements IProfileResourceTreeItem {
|
||||
|
||||
readonly type = ProfileResourceType.Tasks;
|
||||
readonly handle = this.profile.tasksResource.toString();
|
||||
readonly label = { label: localize('tasks', "User Tasks") };
|
||||
readonly collapsibleState = TreeItemCollapsibleState.None;
|
||||
checkbox: ITreeItemCheckboxState | undefined = { isChecked: true };
|
||||
checkbox: ITreeItemCheckboxState = { isChecked: true };
|
||||
readonly command = {
|
||||
id: API_OPEN_EDITOR_COMMAND_ID,
|
||||
title: '',
|
||||
|
|
|
@ -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_EXPORT_IN_PROGRESS_CONTEXT, PROFILES_TTILE, defaultUserDataProfileIcon, IUserDataProfileService, IProfileResourceTreeItem, IProfileResourceChildTreeItem, PROFILES_CATEGORY, isUserDataProfileTemplate, IUserDataProfileManagementService } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
|
||||
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 { IDialogService, IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
|
||||
|
@ -49,6 +49,13 @@ import { IQuickInputService } 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';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
|
||||
import { IURLHandler, IURLService } from 'vs/platform/url/common/url';
|
||||
import { asText, IRequestService } from 'vs/platform/request/common/request';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
|
||||
interface IUserDataProfileTemplate {
|
||||
readonly name: string;
|
||||
|
@ -61,7 +68,9 @@ interface IUserDataProfileTemplate {
|
|||
readonly extensions?: string;
|
||||
}
|
||||
|
||||
export class UserDataProfileImportExportService extends Disposable implements IUserDataProfileImportExportService {
|
||||
export class UserDataProfileImportExportService extends Disposable implements IUserDataProfileImportExportService, IURLHandler {
|
||||
|
||||
private static readonly PROFILE_URL_AUTHORITY_PREFIX = 'profile-';
|
||||
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
|
@ -85,10 +94,15 @@ export class UserDataProfileImportExportService extends Disposable implements IU
|
|||
@INotificationService private readonly notificationService: INotificationService,
|
||||
@IProgressService private readonly progressService: IProgressService,
|
||||
@IDialogService private readonly dialogService: IDialogService,
|
||||
@IClipboardService private readonly clipboardService: IClipboardService,
|
||||
@IOpenerService private readonly openerService: IOpenerService,
|
||||
@IRequestService private readonly requestService: IRequestService,
|
||||
@IURLService urlService: IURLService,
|
||||
@IProductService private readonly productService: IProductService,
|
||||
@ILogService private readonly logService: ILogService,
|
||||
) {
|
||||
super();
|
||||
this.registerProfileContentHandler(this.fileUserDataProfileContentHandler = instantiationService.createInstance(FileUserDataProfileContentHandler));
|
||||
this.registerProfileContentHandler(Schemas.file, this.fileUserDataProfileContentHandler = instantiationService.createInstance(FileUserDataProfileContentHandler));
|
||||
this.isProfileImportExportInProgressContextKey = IS_PROFILE_IMPORT_EXPORT_IN_PROGRESS_CONTEXT.bindTo(contextKeyService);
|
||||
|
||||
this.viewContainer = Registry.as<IViewContainersRegistry>(Extensions.ViewContainersRegistry).registerViewContainer(
|
||||
|
@ -102,13 +116,31 @@ export class UserDataProfileImportExportService extends Disposable implements IU
|
|||
icon: defaultUserDataProfileIcon,
|
||||
hideIfEmpty: true,
|
||||
}, ViewContainerLocation.Sidebar);
|
||||
|
||||
urlService.registerHandler(this);
|
||||
}
|
||||
|
||||
registerProfileContentHandler(profileContentHandler: IUserDataProfileContentHandler): void {
|
||||
if (this.profileContentHandlers.has(profileContentHandler.id)) {
|
||||
throw new Error(`Profile content handler with id '${profileContentHandler.id}' already registered.`);
|
||||
private isProfileURL(uri: URI): boolean {
|
||||
return new RegExp(`^${UserDataProfileImportExportService.PROFILE_URL_AUTHORITY_PREFIX}`).test(uri.authority);
|
||||
}
|
||||
|
||||
async handleURL(uri: URI): Promise<boolean> {
|
||||
if (this.isProfileURL(uri)) {
|
||||
await this.importProfile(uri);
|
||||
return true;
|
||||
}
|
||||
this.profileContentHandlers.set(profileContentHandler.id, profileContentHandler);
|
||||
return false;
|
||||
}
|
||||
|
||||
registerProfileContentHandler(id: string, profileContentHandler: IUserDataProfileContentHandler): void {
|
||||
if (this.profileContentHandlers.has(id)) {
|
||||
throw new Error(`Profile content handler with id '${id}' already registered.`);
|
||||
}
|
||||
this.profileContentHandlers.set(id, profileContentHandler);
|
||||
}
|
||||
|
||||
unregisterProfileContentHandler(id: string): void {
|
||||
this.profileContentHandlers.delete(id);
|
||||
}
|
||||
|
||||
async exportProfile(): Promise<void> {
|
||||
|
@ -122,16 +154,54 @@ export class UserDataProfileImportExportService extends Disposable implements IU
|
|||
|
||||
try {
|
||||
disposables.add(toDisposable(() => this.isProfileImportExportInProgressContextKey.set(false)));
|
||||
const userDataProfilesData = disposables.add(this.instantiationService.createInstance(UserDataProfileExportData, this.userDataProfileService.currentProfile));
|
||||
const exportProfile = await this.showProfilePreviewView(`workbench.views.profiles.export.preview`, localize('export profile preview', "Export"), userDataProfilesData);
|
||||
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")
|
||||
);
|
||||
|
||||
if (exportProfile === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!exportProfile) {
|
||||
exportProfile = await this.showProfilePreviewView(`workbench.views.profiles.export.preview`, title, userDataProfilesExportState);
|
||||
}
|
||||
|
||||
if (!exportProfile) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (exportProfile) {
|
||||
const profile = await userDataProfilesData.getProfileToExport();
|
||||
const profile = await userDataProfilesExportState.getProfileToExport();
|
||||
if (!profile) {
|
||||
return;
|
||||
}
|
||||
const resource = await this.saveProfileContent(profile.name, JSON.stringify(profile));
|
||||
if (resource) {
|
||||
this.notificationService.info(localize('export success', "{0}: Exported successfully.", PROFILES_CATEGORY.value));
|
||||
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 result = await this.dialogService.show(
|
||||
Severity.Info,
|
||||
localize('export success', "Profile '{0}' is exported successfully.", profile.name),
|
||||
buttons
|
||||
);
|
||||
switch (result.choice) {
|
||||
case 0:
|
||||
await this.clipboardService.writeText(
|
||||
URI.from({
|
||||
scheme: this.productService.urlProtocol,
|
||||
authority: `${UserDataProfileImportExportService.PROFILE_URL_AUTHORITY_PREFIX}${saveResult.id}`,
|
||||
path: `/${saveResult.resource.toString()}`
|
||||
}).toString());
|
||||
break;
|
||||
case 1:
|
||||
await this.openerService.open(saveResult.resource.toString());
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
|
@ -141,7 +211,7 @@ export class UserDataProfileImportExportService extends Disposable implements IU
|
|||
|
||||
async importProfile(uri: URI): Promise<void> {
|
||||
if (this.isProfileImportExportInProgressContextKey.get()) {
|
||||
this.logService.warn('Profile import/export already in progress.');
|
||||
this.notificationService.warn('Profile import/export already in progress.');
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -154,16 +224,34 @@ export class UserDataProfileImportExportService extends Disposable implements IU
|
|||
if (profileContent === null) {
|
||||
return;
|
||||
}
|
||||
const profileTemplate: IUserDataProfileTemplate = JSON.parse(profileContent);
|
||||
|
||||
let profileTemplate: IUserDataProfileTemplate = JSON.parse(profileContent);
|
||||
if (!isUserDataProfileTemplate(profileTemplate)) {
|
||||
this.notificationService.error('Invalid profile content.');
|
||||
return;
|
||||
}
|
||||
const userDataProfilesData = disposables.add(this.instantiationService.createInstance(UserDataProfileImportData, profileTemplate));
|
||||
const importProfile = await this.showProfilePreviewView(`workbench.views.profiles.import.preview`, localize('import profile preview', "Import"), userDataProfilesData);
|
||||
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")
|
||||
);
|
||||
|
||||
if (importProfile === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!importProfile) {
|
||||
importProfile = await this.showProfilePreviewView(`workbench.views.profiles.import.preview`, title, userDataProfileImportState);
|
||||
}
|
||||
|
||||
if (!importProfile) {
|
||||
return;
|
||||
}
|
||||
|
||||
profileTemplate = await userDataProfileImportState.getProfileTemplateToImport();
|
||||
const profile = await this.getProfileToImport(profileTemplate);
|
||||
if (!profile) {
|
||||
return;
|
||||
|
@ -199,44 +287,77 @@ export class UserDataProfileImportExportService extends Disposable implements IU
|
|||
}
|
||||
}
|
||||
|
||||
private async saveProfileContent(name: string, content: string): Promise<URI | null> {
|
||||
const profileContentHandler = await this.pickProfileContentHandler();
|
||||
private async saveProfileContent(name: string, content: string): Promise<{ resource: URI; id: string } | null> {
|
||||
const id = await this.pickProfileContentHandler();
|
||||
if (!id) {
|
||||
return null;
|
||||
}
|
||||
const profileContentHandler = this.profileContentHandlers.get(id);
|
||||
if (!profileContentHandler) {
|
||||
return null;
|
||||
}
|
||||
const resource = await profileContentHandler.saveProfile(name, content);
|
||||
return resource;
|
||||
const resource = await profileContentHandler.saveProfile(name, content, CancellationToken.None);
|
||||
return resource ? { resource, id } : null;
|
||||
}
|
||||
|
||||
private async resolveProfileContent(resource: URI): Promise<string | null> {
|
||||
if (await this.fileService.canHandleResource(resource)) {
|
||||
return this.fileUserDataProfileContentHandler.readProfile(resource);
|
||||
return this.fileUserDataProfileContentHandler.readProfile(resource, CancellationToken.None);
|
||||
}
|
||||
await this.extensionService.activateByEvent(`onProfile:import:${resource.authority}`);
|
||||
const profileContentHandler = this.profileContentHandlers.get(resource.authority);
|
||||
return profileContentHandler?.readProfile(resource) ?? null;
|
||||
|
||||
if (this.isProfileURL(resource)) {
|
||||
const handlerId = resource.authority.substring(UserDataProfileImportExportService.PROFILE_URL_AUTHORITY_PREFIX.length);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
await this.extensionService.activateByEvent('onProfile');
|
||||
for (const profileContentHandler of this.profileContentHandlers.values()) {
|
||||
const content = await profileContentHandler.readProfile(resource, CancellationToken.None);
|
||||
if (content !== null) {
|
||||
return content;
|
||||
}
|
||||
}
|
||||
|
||||
const context = await this.requestService.request({ type: 'GET', url: resource.toString(true) }, CancellationToken.None);
|
||||
if (context.res.statusCode === 200) {
|
||||
return await asText(context);
|
||||
} else {
|
||||
const message = await asText(context);
|
||||
this.logService.info(`Failed to get profile from URL: ${resource.toString()}. Status code: ${context.res.statusCode}. Message: ${message}`);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private async pickProfileContentHandler(): Promise<IUserDataProfileContentHandler | undefined> {
|
||||
private async pickProfileContentHandler(): Promise<string | undefined> {
|
||||
await this.extensionService.activateByEvent('onProfile');
|
||||
if (this.profileContentHandlers.size === 1) {
|
||||
return this.profileContentHandlers.values().next().value;
|
||||
}
|
||||
await this.extensionService.activateByEvent('onProfile:export');
|
||||
return undefined;
|
||||
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") });
|
||||
return result?.id;
|
||||
}
|
||||
|
||||
private async getProfileToImport(profileTemplate: IUserDataProfileTemplate): Promise<IUserDataProfile | undefined> {
|
||||
const profile = this.userDataProfilesService.profiles.find(p => p.name === profileTemplate.name);
|
||||
if (profile) {
|
||||
const confirmation = await this.dialogService.confirm({
|
||||
type: 'info',
|
||||
message: localize('profile already exists', "Profile with name '{0}' already exists. Do you want to overwrite it?", profileTemplate.name),
|
||||
primaryButton: localize('overwrite', "Overwrite"),
|
||||
secondaryButton: localize('create new', "Create New Profile"),
|
||||
});
|
||||
if (confirmation.confirmed) {
|
||||
return profile;
|
||||
const result = await this.dialogService.show(
|
||||
Severity.Info,
|
||||
localize('profile already exists', "Profile with name '{0}' already exists. Do you want to overwrite it?", profileTemplate.name),
|
||||
[localize('overwrite', "Overwrite"), localize('create new', "Create New Profile"), localize('cancel', "Cancel")],
|
||||
{ cancelId: 2 }
|
||||
);
|
||||
switch (result.choice) {
|
||||
case 0: return profile;
|
||||
case 2: return undefined;
|
||||
}
|
||||
|
||||
// Create new profile
|
||||
const nameRegEx = new RegExp(`${escapeRegExpCharacters(profileTemplate.name)}\\s(\\d+)`);
|
||||
let nameIndex = 0;
|
||||
for (const profile of this.userDataProfilesService.profiles) {
|
||||
|
@ -264,7 +385,64 @@ export class UserDataProfileImportExportService extends Disposable implements IU
|
|||
}
|
||||
}
|
||||
|
||||
private async showProfilePreviewView(id: string, name: string, userDataProfilesData: UserDataProfileTreeViewData): Promise<boolean> {
|
||||
private async selectProfileResources(profileImportExportState: UserDataProfileImportExportState, title: string, description: string): Promise<boolean | undefined> {
|
||||
type ProfileResourceQuickItem = { item: IProfileResourceTreeItem; label: string };
|
||||
const disposables: DisposableStore = new DisposableStore();
|
||||
const quickPick = this.quickInputService.createQuickPick<ProfileResourceQuickItem>();
|
||||
disposables.add(quickPick);
|
||||
quickPick.title = title;
|
||||
quickPick.ok = 'default';
|
||||
quickPick.customButton = true;
|
||||
quickPick.customLabel = localize('show contents', "Show Contents");
|
||||
quickPick.description = description;
|
||||
quickPick.canSelectMany = true;
|
||||
quickPick.ignoreFocusOut = true;
|
||||
quickPick.hideInput = true;
|
||||
quickPick.hideCheckAll = true;
|
||||
quickPick.busy = true;
|
||||
|
||||
let accepted: boolean = false;
|
||||
let preview: boolean = false;
|
||||
disposables.add(quickPick.onDidAccept(() => {
|
||||
accepted = true;
|
||||
quickPick.hide();
|
||||
}));
|
||||
disposables.add(quickPick.onDidCustom(() => {
|
||||
preview = true;
|
||||
quickPick.hide();
|
||||
}));
|
||||
|
||||
const promise = new Promise<boolean | undefined>((c, e) => {
|
||||
disposables.add(quickPick.onDidHide(() => {
|
||||
try {
|
||||
if (accepted || preview) {
|
||||
for (const root of roots) {
|
||||
root.checkbox.isChecked = quickPick.selectedItems.some(({ item }) => item === root);
|
||||
}
|
||||
c(accepted);
|
||||
} else {
|
||||
c(undefined);
|
||||
}
|
||||
} catch (error) {
|
||||
e(error);
|
||||
} finally {
|
||||
disposables.dispose();
|
||||
}
|
||||
}));
|
||||
});
|
||||
quickPick.show();
|
||||
|
||||
const roots = await profileImportExportState.getRoots();
|
||||
quickPick.busy = false;
|
||||
|
||||
const items = roots.map<ProfileResourceQuickItem>(item => ({ item, label: item.label?.label ?? item.type }));
|
||||
quickPick.items = items;
|
||||
quickPick.selectedItems = items.filter(({ item }) => item.checkbox?.isChecked);
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
private async showProfilePreviewView(id: string, name: string, userDataProfilesData: UserDataProfileImportExportState): Promise<boolean> {
|
||||
const disposables = new DisposableStore();
|
||||
const viewsRegistry = Registry.as<IViewsRegistry>(Extensions.ViewsRegistry);
|
||||
const treeView = disposables.add(this.instantiationService.createInstance(TreeView, id, name));
|
||||
|
@ -324,8 +502,7 @@ export class UserDataProfileImportExportService extends Disposable implements IU
|
|||
|
||||
class FileUserDataProfileContentHandler implements IUserDataProfileContentHandler {
|
||||
|
||||
readonly id = 'file';
|
||||
readonly name = localize('file', "File");
|
||||
readonly name = localize('file', "Local");
|
||||
|
||||
constructor(
|
||||
@IFileDialogService private readonly fileDialogService: IFileDialogService,
|
||||
|
@ -334,7 +511,7 @@ class FileUserDataProfileContentHandler implements IUserDataProfileContentHandle
|
|||
@ITextFileService private readonly textFileService: ITextFileService,
|
||||
) { }
|
||||
|
||||
async saveProfile(name: string, content: string): Promise<URI | null> {
|
||||
async saveProfile(name: string, content: string, token: CancellationToken): Promise<URI | null> {
|
||||
const profileLocation = await this.fileDialogService.showSaveDialog({
|
||||
title: localize('export profile dialog', "Save Profile"),
|
||||
filters: PROFILE_FILTER,
|
||||
|
@ -347,8 +524,11 @@ class FileUserDataProfileContentHandler implements IUserDataProfileContentHandle
|
|||
return profileLocation;
|
||||
}
|
||||
|
||||
async readProfile(uri: URI): Promise<string> {
|
||||
return (await this.fileService.readFile(uri)).value.toString();
|
||||
async readProfile(uri: URI, token: CancellationToken): Promise<string | null> {
|
||||
if (await this.fileService.canHandleResource(uri)) {
|
||||
return (await this.fileService.readFile(uri, undefined, token)).value.toString();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async selectProfile(): Promise<URI | null> {
|
||||
|
@ -374,7 +554,7 @@ class UserDataProfileExportViewPane extends TreeViewPane {
|
|||
private totalTreeItemsCount: number = 0;
|
||||
|
||||
constructor(
|
||||
private readonly userDataProfileData: UserDataProfileTreeViewData,
|
||||
private readonly userDataProfileData: UserDataProfileImportExportState,
|
||||
private readonly confirmLabel: string,
|
||||
private readonly onConfirm: () => void,
|
||||
private readonly onCancel: () => void,
|
||||
|
@ -450,43 +630,10 @@ class UserDataProfileExportViewPane extends TreeViewPane {
|
|||
const USER_DATA_PROFILE_IMPORT_EXPORT_SCHEME = 'userdataprofileimportexport';
|
||||
const USER_DATA_PROFILE_IMPORT_EXPORT_PREVIEW_SCHEME = 'userdataprofileimportexportpreview';
|
||||
|
||||
abstract class UserDataProfileTreeViewData extends Disposable implements ITreeViewDataProvider {
|
||||
|
||||
async getChildren(element?: ITreeItem): Promise<ITreeItem[] | undefined> {
|
||||
if (element) {
|
||||
return (<IProfileResourceTreeItem>element).getChildren();
|
||||
} else {
|
||||
this.rootsPromise = undefined;
|
||||
return this.getRoots();
|
||||
}
|
||||
}
|
||||
|
||||
private roots: IProfileResourceTreeItem[] = [];
|
||||
private rootsPromise: Promise<IProfileResourceTreeItem[]> | undefined;
|
||||
getRoots(): Promise<IProfileResourceTreeItem[]> {
|
||||
if (!this.rootsPromise) {
|
||||
this.rootsPromise = this.fetchRoots().then(roots => this.roots = roots);
|
||||
}
|
||||
return this.rootsPromise;
|
||||
}
|
||||
|
||||
isEnabled(): boolean {
|
||||
return this.roots.some(root => root.checkbox?.isChecked ?? true);
|
||||
}
|
||||
|
||||
abstract onDidChangeCheckboxState(items: ITreeItem[]): ITreeItem[];
|
||||
protected abstract fetchRoots(): Promise<IProfileResourceTreeItem[]>;
|
||||
}
|
||||
|
||||
class UserDataProfileExportData extends UserDataProfileTreeViewData implements ITreeViewDataProvider {
|
||||
|
||||
private readonly disposables = this._register(new DisposableStore());
|
||||
abstract class UserDataProfileImportExportState extends Disposable implements ITreeViewDataProvider {
|
||||
|
||||
constructor(
|
||||
private readonly profile: IUserDataProfile,
|
||||
@IQuickInputService private readonly quickInputService: IQuickInputService,
|
||||
@IFileService private readonly fileService: IFileService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService
|
||||
@IQuickInputService protected readonly quickInputService: IQuickInputService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
@ -512,6 +659,89 @@ class UserDataProfileExportData extends UserDataProfileTreeViewData implements I
|
|||
return items;
|
||||
}
|
||||
|
||||
async getChildren(element?: ITreeItem): Promise<ITreeItem[] | undefined> {
|
||||
if (element) {
|
||||
return (<IProfileResourceTreeItem>element).getChildren();
|
||||
} else {
|
||||
this.rootsPromise = undefined;
|
||||
return this.getRoots();
|
||||
}
|
||||
}
|
||||
|
||||
private roots: IProfileResourceTreeItem[] = [];
|
||||
private rootsPromise: Promise<IProfileResourceTreeItem[]> | undefined;
|
||||
getRoots(): Promise<IProfileResourceTreeItem[]> {
|
||||
if (!this.rootsPromise) {
|
||||
this.rootsPromise = (async () => {
|
||||
this.roots = await this.fetchRoots();
|
||||
return this.roots;
|
||||
})();
|
||||
}
|
||||
return this.rootsPromise;
|
||||
}
|
||||
|
||||
isEnabled(resourceType?: ProfileResourceType): boolean {
|
||||
if (resourceType !== undefined) {
|
||||
return this.roots.some(root => root.type === resourceType && root.checkbox?.isChecked);
|
||||
}
|
||||
return this.roots.some(root => root.checkbox?.isChecked ?? true);
|
||||
}
|
||||
|
||||
protected async getProfileTemplate(name: string, shortName: string | undefined): Promise<IUserDataProfileTemplate> {
|
||||
const roots = await this.getRoots();
|
||||
let settings: string | undefined;
|
||||
let keybindings: string | undefined;
|
||||
let tasks: string | undefined;
|
||||
let snippets: string | undefined;
|
||||
let extensions: string | undefined;
|
||||
let globalState: string | undefined;
|
||||
for (const root of roots) {
|
||||
if (!root.checkbox?.isChecked) {
|
||||
continue;
|
||||
}
|
||||
if (root instanceof SettingsResourceTreeItem) {
|
||||
settings = await root.getContent();
|
||||
} else if (root instanceof KeybindingsResourceTreeItem) {
|
||||
keybindings = await root.getContent();
|
||||
} else if (root instanceof TasksResourceTreeItem) {
|
||||
tasks = await root.getContent();
|
||||
} else if (root instanceof SnippetsResourceTreeItem) {
|
||||
snippets = await root.getContent();
|
||||
} else if (root instanceof ExtensionsResourceExportTreeItem) {
|
||||
extensions = await root.getContent();
|
||||
} else if (root instanceof GlobalStateResourceExportTreeItem) {
|
||||
globalState = await root.getContent();
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
name,
|
||||
shortName,
|
||||
settings,
|
||||
keybindings,
|
||||
tasks,
|
||||
snippets,
|
||||
extensions,
|
||||
globalState
|
||||
};
|
||||
}
|
||||
|
||||
protected abstract fetchRoots(): Promise<IProfileResourceTreeItem[]>;
|
||||
}
|
||||
|
||||
class UserDataProfileExportState extends UserDataProfileImportExportState {
|
||||
|
||||
private readonly disposables = this._register(new DisposableStore());
|
||||
|
||||
constructor(
|
||||
private readonly profile: IUserDataProfile,
|
||||
@IQuickInputService quickInputService: IQuickInputService,
|
||||
@IFileService private readonly fileService: IFileService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService
|
||||
) {
|
||||
super(quickInputService);
|
||||
}
|
||||
|
||||
protected async fetchRoots(): Promise<IProfileResourceTreeItem[]> {
|
||||
this.disposables.clear();
|
||||
this.disposables.add(this.fileService.registerProvider(USER_DATA_PROFILE_IMPORT_EXPORT_SCHEME, this._register(new InMemoryFileSystemProvider())));
|
||||
|
@ -600,60 +830,22 @@ class UserDataProfileExportData extends UserDataProfileTreeViewData implements I
|
|||
}
|
||||
}
|
||||
|
||||
const roots = await this.getRoots();
|
||||
let settings: string | undefined;
|
||||
let keybindings: string | undefined;
|
||||
let tasks: string | undefined;
|
||||
let snippets: string | undefined;
|
||||
let extensions: string | undefined;
|
||||
let globalState: string | undefined;
|
||||
for (const root of roots) {
|
||||
if (!root.checkbox?.isChecked) {
|
||||
continue;
|
||||
}
|
||||
if (root instanceof SettingsResourceTreeItem) {
|
||||
settings = await root.getContent();
|
||||
} else if (root instanceof KeybindingsResourceTreeItem) {
|
||||
keybindings = await root.getContent();
|
||||
} else if (root instanceof TasksResourceTreeItem) {
|
||||
tasks = await root.getContent();
|
||||
} else if (root instanceof SnippetsResourceTreeItem) {
|
||||
snippets = await root.getContent();
|
||||
} else if (root instanceof ExtensionsResourceExportTreeItem) {
|
||||
extensions = await root.getContent();
|
||||
} else if (root instanceof GlobalStateResourceExportTreeItem) {
|
||||
globalState = await root.getContent();
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
name,
|
||||
shortName: this.profile.shortName,
|
||||
settings,
|
||||
keybindings,
|
||||
tasks,
|
||||
snippets,
|
||||
extensions,
|
||||
globalState
|
||||
};
|
||||
return super.getProfileTemplate(name, this.profile.shortName);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class UserDataProfileImportData extends UserDataProfileTreeViewData implements ITreeViewDataProvider {
|
||||
class UserDataProfileImportState extends UserDataProfileImportExportState {
|
||||
|
||||
private readonly disposables = this._register(new DisposableStore());
|
||||
|
||||
constructor(
|
||||
private readonly profile: IUserDataProfileTemplate,
|
||||
@IFileService private readonly fileService: IFileService,
|
||||
@IQuickInputService quickInputService: IQuickInputService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
onDidChangeCheckboxState(items: ITreeItem[]): ITreeItem[] {
|
||||
return items;
|
||||
super(quickInputService);
|
||||
}
|
||||
|
||||
protected async fetchRoots(): Promise<IProfileResourceTreeItem[]> {
|
||||
|
@ -668,7 +860,6 @@ class UserDataProfileImportData extends UserDataProfileTreeViewData implements I
|
|||
const settingsResource = this.instantiationService.createInstance(SettingsResource);
|
||||
await settingsResource.apply(this.profile.settings, importPreviewProfle);
|
||||
const settingsResourceTreeItem = this.instantiationService.createInstance(SettingsResourceTreeItem, importPreviewProfle);
|
||||
settingsResourceTreeItem.checkbox = undefined;
|
||||
if (await settingsResourceTreeItem.hasContent()) {
|
||||
roots.push(settingsResourceTreeItem);
|
||||
}
|
||||
|
@ -678,7 +869,6 @@ class UserDataProfileImportData extends UserDataProfileTreeViewData implements I
|
|||
const keybindingsResource = this.instantiationService.createInstance(KeybindingsResource);
|
||||
await keybindingsResource.apply(this.profile.keybindings, importPreviewProfle);
|
||||
const keybindingsResourceTreeItem = this.instantiationService.createInstance(KeybindingsResourceTreeItem, importPreviewProfle);
|
||||
keybindingsResourceTreeItem.checkbox = undefined;
|
||||
if (await keybindingsResourceTreeItem.hasContent()) {
|
||||
roots.push(keybindingsResourceTreeItem);
|
||||
}
|
||||
|
@ -688,7 +878,6 @@ class UserDataProfileImportData extends UserDataProfileTreeViewData implements I
|
|||
const tasksResource = this.instantiationService.createInstance(TasksResource);
|
||||
await tasksResource.apply(this.profile.tasks, importPreviewProfle);
|
||||
const tasksResourceTreeItem = this.instantiationService.createInstance(TasksResourceTreeItem, importPreviewProfle);
|
||||
tasksResourceTreeItem.checkbox = undefined;
|
||||
if (await tasksResourceTreeItem.hasContent()) {
|
||||
roots.push(tasksResourceTreeItem);
|
||||
}
|
||||
|
@ -698,7 +887,6 @@ class UserDataProfileImportData extends UserDataProfileTreeViewData implements I
|
|||
const snippetsResource = this.instantiationService.createInstance(SnippetsResource);
|
||||
await snippetsResource.apply(this.profile.snippets, importPreviewProfle);
|
||||
const snippetsResourceTreeItem = this.instantiationService.createInstance(SnippetsResourceTreeItem, importPreviewProfle);
|
||||
snippetsResourceTreeItem.checkbox = undefined;
|
||||
if (await snippetsResourceTreeItem.hasContent()) {
|
||||
roots.push(snippetsResourceTreeItem);
|
||||
}
|
||||
|
@ -709,7 +897,7 @@ class UserDataProfileImportData extends UserDataProfileTreeViewData implements I
|
|||
const content = VSBuffer.fromString(JSON.stringify(JSON.parse(this.profile.globalState), null, '\t'));
|
||||
if (content) {
|
||||
await this.fileService.writeFile(globalStateResource, content);
|
||||
roots.push(this.instantiationService.createInstance(GlobalStateResourceImportTreeItem, globalStateResource));
|
||||
roots.push(this.instantiationService.createInstance(GlobalStateResourceImportTreeItem, this.profile.globalState, globalStateResource));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -725,6 +913,10 @@ class UserDataProfileImportData extends UserDataProfileTreeViewData implements I
|
|||
return roots;
|
||||
}
|
||||
|
||||
async getProfileTemplateToImport(): Promise<IUserDataProfileTemplate> {
|
||||
return this.getProfileTemplate(this.profile.name, this.profile.shortName);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
registerSingleton(IUserDataProfileImportExportService, UserDataProfileImportExportService, InstantiationType.Delayed);
|
||||
|
|
|
@ -13,7 +13,8 @@ import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
|||
import { URI } from 'vs/base/common/uri';
|
||||
import { registerIcon } from 'vs/platform/theme/common/iconRegistry';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { ITreeItem } from 'vs/workbench/common/views';
|
||||
import { ITreeItem, ITreeItemCheckboxState, ITreeItemLabel } from 'vs/workbench/common/views';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
|
||||
export interface DidChangeUserDataProfileEvent {
|
||||
readonly preserveData: boolean;
|
||||
|
@ -68,20 +69,34 @@ export const IUserDataProfileImportExportService = createDecorator<IUserDataProf
|
|||
export interface IUserDataProfileImportExportService {
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
registerProfileContentHandler(profileContentHandler: IUserDataProfileContentHandler): void;
|
||||
registerProfileContentHandler(id: string, profileContentHandler: IUserDataProfileContentHandler): void;
|
||||
unregisterProfileContentHandler(id: string): void;
|
||||
|
||||
exportProfile(): Promise<void>;
|
||||
importProfile(uri: URI): Promise<void>;
|
||||
setProfile(profile: IUserDataProfileTemplate): Promise<void>;
|
||||
}
|
||||
|
||||
export const enum ProfileResourceType {
|
||||
Settings = 'settings',
|
||||
Keybindings = 'keybindings',
|
||||
Snippets = 'snippets',
|
||||
Tasks = 'tasks',
|
||||
Extensions = 'extensions',
|
||||
GlobalState = 'globalState',
|
||||
}
|
||||
|
||||
export interface IProfileResource {
|
||||
getContent(profile: IUserDataProfile): Promise<string>;
|
||||
apply(content: string, profile: IUserDataProfile): Promise<void>;
|
||||
}
|
||||
|
||||
export interface IProfileResourceTreeItem extends ITreeItem {
|
||||
readonly type: ProfileResourceType;
|
||||
checkbox: ITreeItemCheckboxState;
|
||||
readonly label: ITreeItemLabel;
|
||||
getChildren(): Promise<IProfileResourceChildTreeItem[] | undefined>;
|
||||
getContent(): Promise<string>;
|
||||
}
|
||||
|
||||
export interface IProfileResourceChildTreeItem extends ITreeItem {
|
||||
|
@ -89,11 +104,11 @@ export interface IProfileResourceChildTreeItem extends ITreeItem {
|
|||
}
|
||||
|
||||
export interface IUserDataProfileContentHandler {
|
||||
readonly id: string;
|
||||
readonly name: string;
|
||||
readonly description?: string;
|
||||
saveProfile(name: string, content: string): Promise<URI | null>;
|
||||
readProfile(uri: URI): Promise<string>;
|
||||
readonly extensionId?: string;
|
||||
saveProfile(name: string, content: string, token: CancellationToken): Promise<URI | null>;
|
||||
readProfile(uri: URI, token: CancellationToken): Promise<string | null>;
|
||||
}
|
||||
|
||||
export const defaultUserDataProfileIcon = registerIcon('defaultProfile-icon', Codicon.settings, localize('defaultProfileIcon', 'Icon for Default Profile.'));
|
||||
|
|
19
src/vscode-dts/vscode.proposed.profileContentHandlers.d.ts
vendored
Normal file
19
src/vscode-dts/vscode.proposed.profileContentHandlers.d.ts
vendored
Normal file
|
@ -0,0 +1,19 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
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>;
|
||||
}
|
||||
|
||||
export namespace window {
|
||||
export function registerProfileContentHandler(id: string, profileContentHandler: ProfileContentHandler): Disposable;
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue