Skip to content

Commit

Permalink
Add contribEditSessions proposed API (#153165)
Browse files Browse the repository at this point in the history
  • Loading branch information
joyceerhl committed Jun 24, 2022
1 parent 64305a7 commit 1b57024
Show file tree
Hide file tree
Showing 3 changed files with 182 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,21 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { UserDataSyncErrorCode, UserDataSyncStoreError } from 'vs/platform/userDataSync/common/userDataSync';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IDialogService, IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
import { ILogService } from 'vs/platform/log/common/log';
import { IProductService } from 'vs/platform/product/common/productService';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration';
import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry';
import { ContextKeyExpr, ContextKeyExpression, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { getVirtualWorkspaceLocation } from 'vs/platform/workspace/common/virtualWorkspace';
import { Schemas } from 'vs/base/common/network';
import { IsWebContext } from 'vs/platform/contextkey/common/contextkeys';
import { isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions';

registerSingleton(ISessionSyncWorkbenchService, SessionSyncWorkbenchService);

Expand All @@ -44,13 +52,18 @@ const storeCurrentCommand = {
};
const continueEditSessionCommand = {
id: '_workbench.experimental.editSessions.actions.continueEditSession',
title: localize('continue edit session', "{0}: Continue Edit Session", EDIT_SESSION_SYNC_TITLE),
title: localize('continue edit session', "Continue Edit Session..."),
};
const openLocalFolderCommand = {
id: '_workbench.experimental.editSessions.actions.continueEditSession.openLocalFolder',
title: localize('continue edit session in local folder', "Open In Local Folder"),
};
const queryParamName = 'editSessionId';

export class SessionSyncContribution extends Disposable implements IWorkbenchContribution {

private registered = false;
private continueEditSessionOptions: ContinueEditSessionItem[] = [];

constructor(
@ISessionSyncWorkbenchService private readonly sessionSyncWorkbenchService: ISessionSyncWorkbenchService,
Expand All @@ -66,6 +79,10 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon
@IProductService private readonly productService: IProductService,
@IConfigurationService private configurationService: IConfigurationService,
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
@IQuickInputService private readonly quickInputService: IQuickInputService,
@ICommandService private commandService: ICommandService,
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@IFileDialogService private readonly fileDialogService: IFileDialogService
) {
super();

Expand All @@ -80,6 +97,33 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon
});

this.registerActions();

continueEditSessionExtPoint.setHandler(extensions => {
const continueEditSessionOptions: ContinueEditSessionItem[] = [];
for (const extension of extensions) {
if (!isProposedApiEnabled(extension.description, 'contribEditSessions')) {
continue;
}
if (!Array.isArray(extension.value)) {
continue;
}
const commands = new Map((extension.description.contributes?.commands ?? []).map(c => [c.command, c]));
for (const contribution of extension.value) {
if (!contribution.command || !contribution.group || !contribution.when) {
continue;
}
const fullCommand = commands.get(contribution.command);
if (!fullCommand) { return; }

continueEditSessionOptions.push(new ContinueEditSessionItem(
fullCommand.title,
fullCommand.command,
ContextKeyExpr.deserialize(contribution.when)
));
}
}
this.continueEditSessionOptions = continueEditSessionOptions;
});
}

private registerActions() {
Expand All @@ -92,6 +136,8 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon
this.registerApplyLatestEditSessionAction();
this.registerStoreLatestEditSessionAction();

this.registerContinueInLocalFolderAction();

this.registered = true;
}

Expand All @@ -101,27 +147,31 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon
constructor() {
super({
id: continueEditSessionCommand.id,
title: continueEditSessionCommand.title
title: continueEditSessionCommand.title,
f1: true
});
}

async run(accessor: ServicesAccessor, workspaceUri: URI): Promise<void> {
async run(accessor: ServicesAccessor, workspaceUri: URI | undefined): Promise<void> {
let uri = workspaceUri ?? await that.pickContinueEditSessionDestination();
if (uri === undefined) { return; }

// Run the store action to get back a ref
const ref = await that.storeEditSession();

// Append the ref to the URI
if (ref !== undefined) {
const encodedRef = encodeURIComponent(ref);
workspaceUri = workspaceUri.with({
query: workspaceUri.query.length > 0 ? (workspaceUri + `&${queryParamName}=${encodedRef}`) : `${queryParamName}=${encodedRef}`
uri = uri.with({
query: uri.query.length > 0 ? (uri + `&${queryParamName}=${encodedRef}`) : `${queryParamName}=${encodedRef}`
});
} else {
that.logService.warn(`Edit Sessions: Failed to store edit session when invoking ${continueEditSessionCommand.id}.`);
}

// Open the URI
that.logService.info(`Edit Sessions: opening ${workspaceUri.toString()}`);
await that.openerService.open(workspaceUri, { openExternal: true });
that.logService.info(`Edit Sessions: opening ${uri.toString()}`);
await that.openerService.open(uri, { openExternal: true });
}
}));
}
Expand Down Expand Up @@ -324,8 +374,125 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon

return [...trackedUris];
}

//#region Continue Edit Session extension contribution point

private registerContinueInLocalFolderAction(): void {
const that = this;
this._register(registerAction2(class ContinueInLocalFolderAction extends Action2 {
constructor() {
super({
id: openLocalFolderCommand.id,
title: openLocalFolderCommand.title,
precondition: IsWebContext
});
}

async run(accessor: ServicesAccessor): Promise<URI | undefined> {
const selection = await that.fileDialogService.showOpenDialog({
title: localize('continueEditSession.openLocalFolder.title', 'Select a local folder to continue your edit session in'),
canSelectFolders: true,
canSelectMany: false,
canSelectFiles: false,
availableFileSystems: [Schemas.file]
});

return selection?.length !== 1 ? undefined : URI.from({
scheme: that.productService.urlProtocol,
authority: Schemas.file,
path: selection[0].path
});
}
}));
}

private async pickContinueEditSessionDestination(): Promise<URI | undefined> {
const quickPick = this.quickInputService.createQuickPick<ContinueEditSessionItem>();

quickPick.title = localize('continueEditSessionPick.title', 'Continue Edit Session...');
quickPick.placeholder = localize('continueEditSessionPick.placeholder', 'Choose how you would like to continue working');
quickPick.items = this.createPickItems();

const command = await new Promise<string | undefined>((resolve, reject) => {
quickPick.onDidHide(() => resolve(undefined));

quickPick.onDidAccept((e) => {
const selection = quickPick.activeItems[0].command;
resolve(selection);
quickPick.hide();
});

quickPick.show();
});

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

try {
const uri = await this.commandService.executeCommand(command);
return URI.isUri(uri) ? uri : undefined;
} catch (ex) {
return undefined;
}
}

private createPickItems(): ContinueEditSessionItem[] {
const items = [...this.continueEditSessionOptions].filter((option) => option.when === undefined || this.contextKeyService.contextMatchesRules(option.when));

if (getVirtualWorkspaceLocation(this.contextService.getWorkspace()) !== undefined) {
items.push(new ContinueEditSessionItem(
localize('continueEditSessionItem.openInLocalFolder', 'Open In Local Folder'),
openLocalFolderCommand.id,
));
}

return items;
}
}

class ContinueEditSessionItem implements IQuickPickItem {
constructor(
public readonly label: string,
public readonly command: string,
public readonly when?: ContextKeyExpression,
) { }
}

interface ICommand {
command: string;
group: string;
when: string;
}

const continueEditSessionExtPoint = ExtensionsRegistry.registerExtensionPoint<ICommand[]>({
extensionPoint: 'continueEditSession',
jsonSchema: {
description: localize('continueEditSessionExtPoint', 'Contributes options for continuing the current edit session in a different environment'),
type: 'array',
items: {
type: 'object',
properties: {
command: {
description: localize('continueEditSessionExtPoint.command', 'Identifier of the command to execute. The command must be declared in the \'commands\'-section and return a URI representing a different environment where the current edit session can be continued.'),
type: 'string'
},
group: {
description: localize('continueEditSessionExtPoint.group', 'Group into which this item belongs.'),
type: 'string'
},
when: {
description: localize('continueEditSessionExtPoint.when', 'Condition which must be true to show this item.'),
type: 'string'
}
},
required: ['command']
}
}
});

//#endregion

const workbenchRegistry = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench);
workbenchRegistry.registerWorkbenchContribution(SessionSyncContribution, LifecyclePhase.Restored);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export const allApiProposals = Object.freeze({
authSession: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.authSession.d.ts',
badges: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.badges.d.ts',
commentsResolvedState: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.commentsResolvedState.d.ts',
contribEditSessions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribEditSessions.d.ts',
contribLabelFormatterWorkspaceTooltip: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribLabelFormatterWorkspaceTooltip.d.ts',
contribMenuBarHome: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribMenuBarHome.d.ts',
contribMergeEditorToolbar: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribMergeEditorToolbar.d.ts',
Expand Down
6 changes: 6 additions & 0 deletions src/vscode-dts/vscode.proposed.contribEditSessions.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

// empty placeholder declaration for the `contribEditSessions`-contribution point

0 comments on commit 1b57024

Please sign in to comment.