Revert "Debt - extract Continue On picker and contrib into separate class (#163002)" (#163251)

This reverts commit a8fe1cc157.
This commit is contained in:
Joyce Er 2022-10-11 00:15:02 -07:00 committed by GitHub
parent f4c58486f4
commit 645c718a99
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 183 additions and 168 deletions

View file

@ -1,160 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { MenuRegistry } from 'vs/platform/actions/common/actions';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { ContextKeyExpr, ContextKeyExpression, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions';
import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry';
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
import { Disposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { localize } from 'vs/nls';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
export const IContinueOnPicker = createDecorator<IContinueOnPicker>('IContinueOnPicker');
export interface IContinueOnPicker {
_serviceBrand: undefined;
pick(): Promise<URI | 'noDestinationUri' | undefined>;
}
export class ContinueOnPicker extends Disposable implements IContinueOnPicker {
_serviceBrand = undefined;
private continueEditSessionOptions: ContinueEditSessionItem[] = [];
constructor(
@IQuickInputService private readonly quickInputService: IQuickInputService,
@ICommandService private commandService: ICommandService,
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
@IContextKeyService private readonly contextKeyService: IContextKeyService,
) {
super();
this.registerContributedEditSessionOptions();
}
private registerContributedEditSessionOptions() {
continueEditSessionExtPoint.setHandler(extensions => {
const continueEditSessionOptions: ContinueEditSessionItem[] = [];
for (const extension of extensions) {
if (!isProposedApiEnabled(extension.description, 'contribEditSessions')) {
continue;
}
if (!Array.isArray(extension.value)) {
continue;
}
for (const contribution of extension.value) {
const command = MenuRegistry.getCommand(contribution.command);
if (!command) {
return;
}
const icon = command.icon;
const title = typeof command.title === 'string' ? command.title : command.title.value;
continueEditSessionOptions.push(new ContinueEditSessionItem(
ThemeIcon.isThemeIcon(icon) ? `$(${icon.id}) ${title}` : title,
command.id,
command.source,
ContextKeyExpr.deserialize(contribution.when)
));
}
}
this.continueEditSessionOptions = continueEditSessionOptions;
});
}
async pick(): Promise<URI | 'noDestinationUri' | undefined> {
const quickPick = this.quickInputService.createQuickPick<ContinueEditSessionItem>();
const workspaceContext = this.contextService.getWorkbenchState() === WorkbenchState.FOLDER
? this.contextService.getWorkspace().folders[0].name
: this.contextService.getWorkspace().folders.map((folder) => folder.name).join(', ');
quickPick.title = localize('continueEditSessionPick.title', "Continue {0} on", `'${workspaceContext}'`);
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();
});
quickPick.dispose();
if (command === undefined) {
return undefined;
}
try {
const uri = await this.commandService.executeCommand(command);
// Some continue on commands do not return a URI
// to support extensions which want to be in control
// of how the destination is opened
if (uri === undefined) { return 'noDestinationUri'; }
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));
return items.sort((item1, item2) => item1.label.localeCompare(item2.label));
}
}
class ContinueEditSessionItem implements IQuickPickItem {
constructor(
public readonly label: string,
public readonly command: string,
public readonly description?: 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']
}
}
});

View file

@ -7,7 +7,7 @@ import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle';
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { Registry } from 'vs/platform/registry/common/platform';
import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { Action2, IAction2Options, registerAction2 } from 'vs/platform/actions/common/actions';
import { Action2, IAction2Options, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions';
import { ServicesAccessor } from 'vs/editor/browser/editorExtensions';
import { localize } from 'vs/nls';
import { IEditSessionsStorageService, Change, ChangeType, Folder, EditSession, FileType, EDIT_SESSION_SYNC_CATEGORY, EDIT_SESSIONS_CONTAINER_ID, EditSessionSchemaVersion, IEditSessionsLogService, EDIT_SESSIONS_VIEW_ICON, EDIT_SESSIONS_TITLE, EDIT_SESSIONS_SHOW_VIEW, EDIT_SESSIONS_DATA_VIEW_ID, decodeEditSessionFileContent } from 'vs/workbench/contrib/editSessions/common/editSessions';
@ -20,17 +20,24 @@ import { encodeBase64 } from 'vs/base/common/buffer';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress';
import { EditSessionsWorkbenchService } from 'vs/workbench/contrib/editSessions/browser/editSessionsStorageService';
import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions';
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, Severity } from 'vs/platform/notification/common/notification';
import { getFileNamesMessage, IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { getFileNamesMessage, IDialogService, IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
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 as ConfigurationExtensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry';
import { ContextKeyExpr, ContextKeyExpression, IContextKey, 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';
import { EditSessionsLogService } from 'vs/workbench/contrib/editSessions/common/editSessionsLogService';
import { IViewContainersRegistry, Extensions as ViewExtensions, ViewContainerLocation, IViewsService } from 'vs/workbench/common/views';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
@ -38,20 +45,20 @@ import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneCont
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { EditSessionsDataViews } from 'vs/workbench/contrib/editSessions/browser/editSessionsViews';
import { EditSessionsFileSystemProvider } from 'vs/workbench/contrib/editSessions/browser/editSessionsFileSystemProvider';
import { isNative } from 'vs/base/common/platform';
import { WorkspaceFolderCountContext } from 'vs/workbench/common/contextkeys';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { equals } from 'vs/base/common/objects';
import { IEditSessionIdentityService } from 'vs/platform/workspace/common/editSessions';
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
import { IOutputService } from 'vs/workbench/services/output/common/output';
import * as Constants from 'vs/workbench/contrib/logs/common/logConstants';
import { sha1Hex } from 'vs/base/browser/hash';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity';
import { ContinueOnPicker, IContinueOnPicker } from 'vs/workbench/contrib/editSessions/browser/continueOnPicker';
registerSingleton(IEditSessionsLogService, EditSessionsLogService, false);
registerSingleton(IEditSessionsStorageService, EditSessionsWorkbenchService, false);
registerSingleton(IContinueOnPicker, ContinueOnPicker, InstantiationType.Delayed);
const continueWorkingOnCommand: IAction2Options = {
id: '_workbench.editSessions.actions.continueEditSession',
@ -59,6 +66,12 @@ const continueWorkingOnCommand: IAction2Options = {
precondition: WorkspaceFolderCountContext.notEqualsTo('0'),
f1: true
};
const openLocalFolderCommand: IAction2Options = {
id: '_workbench.editSessions.actions.continueEditSession.openLocalFolder',
title: { value: localize('continue edit session in local folder', "Open In Local Folder"), original: 'Open In Local Folder' },
category: EDIT_SESSION_SYNC_CATEGORY,
precondition: IsWebContext
};
const showOutputChannelCommand: IAction2Options = {
id: 'workbench.editSessions.actions.showOutputChannel',
title: { value: localize('show log', 'Show Log'), original: 'Show Log' },
@ -73,6 +86,9 @@ const queryParamName = 'editSessionId';
const useEditSessionsWithContinueOn = 'workbench.editSessions.continueOn';
export class EditSessionsContribution extends Disposable implements IWorkbenchContribution {
private continueEditSessionOptions: ContinueEditSessionItem[] = [];
private readonly shouldShowViewsContext: IContextKey<boolean>;
private static APPLICATION_LAUNCHED_VIA_CONTINUE_ON_STORAGE_KEY = 'applicationLaunchedViaContinueOn';
@ -94,11 +110,13 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo
@IConfigurationService private configurationService: IConfigurationService,
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
@IEditSessionIdentityService private readonly editSessionIdentityService: IEditSessionIdentityService,
@IQuickInputService private readonly quickInputService: IQuickInputService,
@ICommandService private commandService: ICommandService,
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@IFileDialogService private readonly fileDialogService: IFileDialogService,
@ILifecycleService private readonly lifecycleService: ILifecycleService,
@IStorageService private readonly storageService: IStorageService,
@IActivityService private readonly activityService: IActivityService,
@IContinueOnPicker private readonly continueOnPicker: IContinueOnPicker,
) {
super();
@ -106,6 +124,7 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo
this.registerActions();
this.registerViews();
this.registerContributedEditSessionOptions();
this.shouldShowViewsContext = EDIT_SESSIONS_SHOW_VIEW.bindTo(this.contextKeyService);
@ -218,6 +237,8 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo
this.registerResumeLatestEditSessionAction();
this.registerStoreLatestEditSessionAction();
this.registerContinueInLocalFolderAction();
this.registerShowEditSessionViewAction();
this.registerShowEditSessionOutputChannelAction();
}
@ -271,7 +292,7 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo
const shouldStoreEditSession = await that.shouldContinueOnWithEditSession();
let uri = workspaceUri ?? await that.continueOnPicker.pick();
let uri = workspaceUri ?? await that.pickContinueEditSessionDestination();
if (uri === undefined) { return; }
// Run the store action to get back a ref
@ -627,8 +648,162 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo
return false;
}
//#region Continue Edit Session extension contribution point
private registerContributedEditSessionOptions() {
continueEditSessionExtPoint.setHandler(extensions => {
const continueEditSessionOptions: ContinueEditSessionItem[] = [];
for (const extension of extensions) {
if (!isProposedApiEnabled(extension.description, 'contribEditSessions')) {
continue;
}
if (!Array.isArray(extension.value)) {
continue;
}
for (const contribution of extension.value) {
const command = MenuRegistry.getCommand(contribution.command);
if (!command) {
return;
}
const icon = command.icon;
const title = typeof command.title === 'string' ? command.title : command.title.value;
continueEditSessionOptions.push(new ContinueEditSessionItem(
ThemeIcon.isThemeIcon(icon) ? `$(${icon.id}) ${title}` : title,
command.id,
command.source,
ContextKeyExpr.deserialize(contribution.when)
));
}
}
this.continueEditSessionOptions = continueEditSessionOptions;
});
}
private registerContinueInLocalFolderAction(): void {
const that = this;
this._register(registerAction2(class ContinueInLocalFolderAction extends Action2 {
constructor() {
super(openLocalFolderCommand);
}
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 | 'noDestinationUri' | undefined> {
const quickPick = this.quickInputService.createQuickPick<ContinueEditSessionItem>();
const workspaceContext = this.contextService.getWorkbenchState() === WorkbenchState.FOLDER
? this.contextService.getWorkspace().folders[0].name
: this.contextService.getWorkspace().folders.map((folder) => folder.name).join(', ');
quickPick.title = localize('continueEditSessionPick.title', "Continue {0} on", `'${workspaceContext}'`);
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();
});
quickPick.dispose();
if (command === undefined) {
return undefined;
}
try {
const uri = await this.commandService.executeCommand(command);
// Some continue on commands do not return a URI
// to support extensions which want to be in control
// of how the destination is opened
if (uri === undefined) { return 'noDestinationUri'; }
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 && isNative) {
items.push(new ContinueEditSessionItem(
'$(folder) ' + localize('continueEditSessionItem.openInLocalFolder.v2', 'Open in Local Folder'),
openLocalFolderCommand.id,
localize('continueEditSessionItem.builtin', 'Built-in')
));
}
return items.sort((item1, item2) => item1.label.localeCompare(item2.label));
}
}
class ContinueEditSessionItem implements IQuickPickItem {
constructor(
public readonly label: string,
public readonly command: string,
public readonly description?: 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