mirror of
https://github.com/Microsoft/vscode
synced 2024-10-06 11:26:07 +00:00
Add experimental Continue Edit Session API command (#152375)
* Implement `vscode.experimental.editSession.continue` API command * Read `editSessionId` from protocol url query params Pass it down to `environmentService` for later access Read it from `environmentService` when attempting to apply edit session * Pass `editSessionId` to environmentService in web * Set and clear edit session ID * Add logging and encode ref in query parameters * Update test
This commit is contained in:
parent
5e26d5f9b3
commit
482bc7c146
|
@ -876,6 +876,13 @@ export class CodeApplication extends Disposable {
|
|||
// or if no window is open (macOS only)
|
||||
shouldOpenInNewWindow ||= isMacintosh && windowsMainService.getWindowCount() === 0;
|
||||
|
||||
// Pass along edit session id
|
||||
if (params.get('edit-session-id') !== null) {
|
||||
environmentService.editSessionId = params.get('edit-session-id') ?? undefined;
|
||||
params.delete('edit-session-id');
|
||||
uri = uri.with({ query: params.toString() });
|
||||
}
|
||||
|
||||
// Check for URIs to open in window
|
||||
const windowOpenableFromProtocolLink = app.getWindowOpenableFromProtocolLink(uri);
|
||||
logService.trace('app#handleURL: windowOpenableFromProtocolLink = ', windowOpenableFromProtocolLink);
|
||||
|
|
|
@ -64,6 +64,9 @@ export interface IEnvironmentService {
|
|||
userDataSyncLogResource: URI;
|
||||
sync: 'on' | 'off' | undefined;
|
||||
|
||||
// --- continue edit session
|
||||
editSessionId?: string;
|
||||
|
||||
// --- extension development
|
||||
debugExtensionHost: IExtensionHostDebugParams;
|
||||
isExtensionDevelopment: boolean;
|
||||
|
|
|
@ -436,6 +436,12 @@ const newCommands: ApiCommand[] = [
|
|||
'vscode.revealTestInExplorer', '_revealTestInExplorer', 'Reveals a test instance in the explorer',
|
||||
[ApiCommandArgument.TestItem],
|
||||
ApiCommandResult.Void
|
||||
),
|
||||
// --- continue edit session
|
||||
new ApiCommand(
|
||||
'vscode.experimental.editSession.continue', '_workbench.experimental.sessionSync.actions.continueEditSession', 'Continue the current edit session in a different workspace',
|
||||
[ApiCommandArgument.Uri.with('workspaceUri', 'The target workspace to continue the current edit session in')],
|
||||
ApiCommandResult.Void
|
||||
)
|
||||
];
|
||||
|
||||
|
|
|
@ -169,6 +169,11 @@ export interface IWorkbenchConstructionOptions {
|
|||
*/
|
||||
readonly codeExchangeProxyEndpoints?: { [providerId: string]: string };
|
||||
|
||||
/**
|
||||
* The identifier of an edit session associated with the current workspace.
|
||||
*/
|
||||
readonly editSessionId?: string;
|
||||
|
||||
/**
|
||||
* [TEMPORARY]: This will be removed soon.
|
||||
* Endpoints to be used for proxying repository tarball download calls in the browser.
|
||||
|
|
|
@ -27,17 +27,24 @@ import { INotificationService } from 'vs/platform/notification/common/notificati
|
|||
import { IDialogService } 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';
|
||||
|
||||
registerSingleton(ISessionSyncWorkbenchService, SessionSyncWorkbenchService);
|
||||
|
||||
const applyLatestCommand = {
|
||||
id: 'workbench.sessionSync.actions.applyLatest',
|
||||
id: 'workbench.experimental.sessionSync.actions.applyLatest',
|
||||
title: localize('apply latest', "{0}: Apply Latest Edit Session", EDIT_SESSION_SYNC_TITLE),
|
||||
};
|
||||
const storeLatestCommand = {
|
||||
id: 'workbench.sessionSync.actions.storeLatest',
|
||||
id: 'workbench.experimental.sessionSync.actions.storeLatest',
|
||||
title: localize('store latest', "{0}: Store Latest Edit Session", EDIT_SESSION_SYNC_TITLE),
|
||||
};
|
||||
const continueEditSessionCommand = {
|
||||
id: '_workbench.experimental.sessionSync.actions.continueEditSession',
|
||||
title: localize('continue edit session', "{0}: Continue Edit Session", EDIT_SESSION_SYNC_TITLE),
|
||||
};
|
||||
const queryParamName = 'editSessionId';
|
||||
|
||||
export class SessionSyncContribution extends Disposable implements IWorkbenchContribution {
|
||||
|
||||
|
@ -47,17 +54,23 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon
|
|||
@ISessionSyncWorkbenchService private readonly sessionSyncWorkbenchService: ISessionSyncWorkbenchService,
|
||||
@IFileService private readonly fileService: IFileService,
|
||||
@IProgressService private readonly progressService: IProgressService,
|
||||
@IOpenerService private readonly openerService: IOpenerService,
|
||||
@ITelemetryService private readonly telemetryService: ITelemetryService,
|
||||
@ISCMService private readonly scmService: ISCMService,
|
||||
@INotificationService private readonly notificationService: INotificationService,
|
||||
@IDialogService private readonly dialogService: IDialogService,
|
||||
@ILogService private readonly logService: ILogService,
|
||||
@IEnvironmentService private readonly environmentService: IEnvironmentService,
|
||||
@IProductService private readonly productService: IProductService,
|
||||
@IConfigurationService private configurationService: IConfigurationService,
|
||||
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
|
||||
) {
|
||||
super();
|
||||
|
||||
if (this.environmentService.editSessionId !== undefined) {
|
||||
void this.applyEditSession(this.environmentService.editSessionId).then(() => this.environmentService.editSessionId = undefined);
|
||||
}
|
||||
|
||||
this.configurationService.onDidChangeConfiguration((e) => {
|
||||
if (e.affectsConfiguration('workbench.experimental.sessionSync.enabled')) {
|
||||
this.registerActions();
|
||||
|
@ -72,15 +85,50 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon
|
|||
return;
|
||||
}
|
||||
|
||||
this.registerApplyEditSessionAction();
|
||||
this.registerStoreEditSessionAction();
|
||||
this.registerContinueEditSessionAction();
|
||||
|
||||
this.registerApplyLatestEditSessionAction();
|
||||
this.registerStoreLatestEditSessionAction();
|
||||
|
||||
this.registered = true;
|
||||
}
|
||||
|
||||
private registerApplyEditSessionAction(): void {
|
||||
private registerContinueEditSessionAction() {
|
||||
const that = this;
|
||||
this._register(registerAction2(class ApplyEditSessionAction extends Action2 {
|
||||
this._register(registerAction2(class ContinueEditSessionAction extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: continueEditSessionCommand.id,
|
||||
title: continueEditSessionCommand.title
|
||||
});
|
||||
}
|
||||
|
||||
async run(accessor: ServicesAccessor, workspaceUri: URI): Promise<void> {
|
||||
// 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}`
|
||||
});
|
||||
|
||||
that.environmentService.editSessionId = ref;
|
||||
} 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 });
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private registerApplyLatestEditSessionAction(): void {
|
||||
const that = this;
|
||||
this._register(registerAction2(class ApplyLatestEditSessionAction extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: applyLatestCommand.id,
|
||||
|
@ -100,9 +148,9 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon
|
|||
}));
|
||||
}
|
||||
|
||||
private registerStoreEditSessionAction(): void {
|
||||
private registerStoreLatestEditSessionAction(): void {
|
||||
const that = this;
|
||||
this._register(registerAction2(class StoreEditSessionAction extends Action2 {
|
||||
this._register(registerAction2(class StoreLatestEditSessionAction extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: storeLatestCommand.id,
|
||||
|
@ -122,8 +170,12 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon
|
|||
}));
|
||||
}
|
||||
|
||||
async applyEditSession() {
|
||||
const editSession = await this.sessionSyncWorkbenchService.read(undefined);
|
||||
async applyEditSession(ref?: string): Promise<void> {
|
||||
if (ref !== undefined) {
|
||||
this.logService.info(`Edit Sessions: Applying edit session with ref ${ref}.`);
|
||||
}
|
||||
|
||||
const editSession = await this.sessionSyncWorkbenchService.read(ref);
|
||||
if (!editSession) {
|
||||
return;
|
||||
}
|
||||
|
@ -160,6 +212,7 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon
|
|||
}
|
||||
|
||||
if (hasLocalUncommittedChanges) {
|
||||
// TODO@joyceerhl Provide the option to diff files which would be overwritten by edit session contents
|
||||
const result = await this.dialogService.confirm({
|
||||
message: localize('apply edit session warning', 'Applying your edit session may overwrite your existing uncommitted changes. Do you want to proceed?'),
|
||||
type: 'warning',
|
||||
|
@ -178,12 +231,12 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon
|
|||
}
|
||||
}
|
||||
} catch (ex) {
|
||||
this.logService.error(ex);
|
||||
this.logService.error('Edit Sessions:', (ex as Error).toString());
|
||||
this.notificationService.error(localize('apply failed', "Failed to apply your edit session."));
|
||||
}
|
||||
}
|
||||
|
||||
async storeEditSession() {
|
||||
async storeEditSession(): Promise<string | undefined> {
|
||||
const folders: Folder[] = [];
|
||||
|
||||
for (const repository of this.scmService.repositories) {
|
||||
|
@ -223,7 +276,9 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon
|
|||
const data: EditSession = { folders, version: 1 };
|
||||
|
||||
try {
|
||||
await this.sessionSyncWorkbenchService.write(data);
|
||||
const ref = await this.sessionSyncWorkbenchService.write(data);
|
||||
this.logService.info(`Edit Sessions: Stored edit session with ref ${ref}.`);
|
||||
return ref;
|
||||
} catch (ex) {
|
||||
type UploadFailedEvent = { reason: string };
|
||||
type UploadFailedClassification = {
|
||||
|
@ -245,6 +300,8 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private getChangedResources(repository: ISCMRepository) {
|
||||
|
|
|
@ -26,6 +26,8 @@ import { URI } from 'vs/base/common/uri';
|
|||
import { joinPath } from 'vs/base/common/resources';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService';
|
||||
import { TestEnvironmentService } from 'vs/workbench/test/browser/workbenchTestServices';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
|
||||
const folderName = 'test-folder';
|
||||
const folderUri = URI.file(`/${folderName}`);
|
||||
|
@ -53,6 +55,7 @@ suite('Edit session sync', () => {
|
|||
instantiationService.stub(ISessionSyncWorkbenchService, new class extends mock<ISessionSyncWorkbenchService>() { });
|
||||
instantiationService.stub(IProgressService, ProgressService);
|
||||
instantiationService.stub(ISCMService, SCMService);
|
||||
instantiationService.stub(IEnvironmentService, TestEnvironmentService);
|
||||
instantiationService.stub(IConfigurationService, new TestConfigurationService({ workbench: { experimental: { sessionSync: { enabled: true } } } }));
|
||||
instantiationService.stub(IWorkspaceContextService, new class extends mock<IWorkspaceContextService>() {
|
||||
override getWorkspace() {
|
||||
|
|
|
@ -205,6 +205,9 @@ export class BrowserWorkbenchEnvironmentService implements IBrowserWorkbenchEnvi
|
|||
@memoize
|
||||
get disableWorkspaceTrust(): boolean { return !this.options.enableWorkspaceTrust; }
|
||||
|
||||
@memoize
|
||||
get editSessionId(): string | undefined { return this.options.editSessionId; }
|
||||
|
||||
private payload: Map<string, string> | undefined;
|
||||
|
||||
constructor(
|
||||
|
|
|
@ -60,14 +60,15 @@ export class SessionSyncWorkbenchService extends Disposable implements ISessionS
|
|||
/**
|
||||
*
|
||||
* @param editSession An object representing edit session state to be restored.
|
||||
* @returns The ref of the stored edit session state.
|
||||
*/
|
||||
async write(editSession: EditSession): Promise<void> {
|
||||
async write(editSession: EditSession): Promise<string> {
|
||||
this.initialized = await this.waitAndInitialize();
|
||||
if (!this.initialized) {
|
||||
throw new Error('Please sign in to store your edit session.');
|
||||
}
|
||||
|
||||
await this.storeClient?.write('editSessions', JSON.stringify(editSession), null);
|
||||
return this.storeClient!.write('editSessions', JSON.stringify(editSession), null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -13,7 +13,7 @@ export interface ISessionSyncWorkbenchService {
|
|||
_serviceBrand: undefined;
|
||||
|
||||
read(ref: string | undefined): Promise<EditSession | undefined>;
|
||||
write(editSession: EditSession): Promise<void>;
|
||||
write(editSession: EditSession): Promise<string>;
|
||||
}
|
||||
|
||||
export enum ChangeType {
|
||||
|
|
Loading…
Reference in a new issue