Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement and adopt edit session identifier API proposal #157733

Merged
merged 22 commits into from
Aug 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
59df856
Add canonical workspace identifier proposed API
joyceerhl Aug 10, 2022
5f481f7
Use canonical id to store and resume edit sessions
joyceerhl Aug 11, 2022
436e4b4
Add git extension workspace identity provider
joyceerhl Aug 11, 2022
c97e9ff
Fix warning incorrectly showing up
joyceerhl Aug 11, 2022
ed49263
Make auto resume behavior opt in
joyceerhl Aug 11, 2022
af4d7cf
* Create a separate service
joyceerhl Aug 13, 2022
7b76b49
Merge branch 'main' of https://github.com/microsoft/vscode into dev/j…
joyceerhl Aug 13, 2022
8e4ebf9
Make edit session restores resilient to provider registration races
joyceerhl Aug 15, 2022
db4fff1
`CanonicalWorkspaceIdentity` -> `EditSessionIdentity`
joyceerhl Aug 15, 2022
e6257cf
Show progress while resuming edit session
joyceerhl Aug 15, 2022
4a2459a
Store edit session even if extension will take care of opening target…
joyceerhl Aug 15, 2022
7122838
Address most of PR feedback
joyceerhl Aug 16, 2022
c09865d
`IEditSessionsWorkbenchService` -> `IEditSessionsStorageService`
joyceerhl Aug 17, 2022
c4146fd
Merge branch 'main' of https://github.com/microsoft/vscode into dev/j…
joyceerhl Aug 17, 2022
e267aa5
Unregister provider in renderer
joyceerhl Aug 17, 2022
21e1db5
Split out proposal into new `editSessionIdentityProvider.d.ts`
joyceerhl Aug 17, 2022
32c3f0f
Merge branch 'main' of https://github.com/microsoft/vscode into dev/j…
joyceerhl Aug 18, 2022
cd79406
Fix bad merge
joyceerhl Aug 18, 2022
b9ab02b
Merge branch 'main' of https://github.com/microsoft/vscode into dev/j…
joyceerhl Aug 18, 2022
c392831
Always show progress in window
joyceerhl Aug 18, 2022
d56129c
Convert URI schemes
joyceerhl Aug 19, 2022
eb80989
Merge branch 'main' of https://github.com/microsoft/vscode into dev/j…
joyceerhl Aug 19, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions extensions/git/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"diffCommand",
"contribMergeEditorToolbar",
"contribViewsWelcome",
"editSessionIdentityProvider",
"scmActionButton",
"scmSelectedProvider",
"scmValidation",
Expand All @@ -24,6 +25,7 @@
],
"activationEvents": [
"*",
"onEditSession:file",
"onFileSystem:git",
"onFileSystem:git-show"
],
Expand Down
38 changes: 38 additions & 0 deletions extensions/git/src/editSessionIdentityProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import * as path from 'path';
import * as vscode from 'vscode';
import { Model } from './model';

export class GitEditSessionIdentityProvider implements vscode.EditSessionIdentityProvider, vscode.Disposable {

private providerRegistration: vscode.Disposable;

constructor(private model: Model) {
this.providerRegistration = vscode.workspace.registerEditSessionIdentityProvider('file', this);
}

dispose() {
this.providerRegistration.dispose();
}

async provideEditSessionIdentity(workspaceFolder: vscode.WorkspaceFolder, _token: vscode.CancellationToken): Promise<string | undefined> {
await this.model.openRepository(path.dirname(workspaceFolder.uri.fsPath));

const repository = this.model.getRepository(workspaceFolder.uri);
await repository?.status();

if (!repository || !repository?.HEAD?.upstream) {
return undefined;
}

return JSON.stringify({
remote: repository.remotes.find((remote) => remote.name === repository.HEAD?.upstream?.remote)?.pushUrl ?? null,
ref: repository.HEAD?.name ?? null,
sha: repository.HEAD?.commit ?? null,
});
}
}
4 changes: 3 additions & 1 deletion extensions/git/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { OutputChannelLogger } from './log';
import { createIPCServer, IPCServer } from './ipc/ipcServer';
import { GitEditor } from './gitEditor';
import { GitPostCommitCommandsProvider } from './postCommitCommands';
import { GitEditSessionIdentityProvider } from './editSessionIdentityProvider';

const deactivateTasks: { (): Promise<any> }[] = [];

Expand Down Expand Up @@ -115,7 +116,8 @@ async function createModel(context: ExtensionContext, outputChannelLogger: Outpu
new GitFileSystemProvider(model),
new GitDecorations(model),
new GitProtocolHandler(),
new GitTimelineProvider(model, cc)
new GitTimelineProvider(model, cc),
new GitEditSessionIdentityProvider(model)
);

const postCommitCommandsProvider = new GitPostCommitCommandsProvider();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*---------------------------------------------------------------------------------------------
* 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' {

// https://github.com/microsoft/vscode/issues/157734

export namespace workspace {
/**
*
* @param scheme The URI scheme that this provider can provide edit session identities for.
* @param provider A provider which can convert URIs for workspace folders of scheme @param scheme to
* an edit session identifier which is stable across machines. This enables edit sessions to be resolved.
*/
export function registerEditSessionIdentityProvider(scheme: string, provider: EditSessionIdentityProvider): Disposable;
}

export interface EditSessionIdentityProvider {
/**
*
* @param workspaceFolder The workspace folder to provide an edit session identity for.
* @param token A cancellation token for the request.
* @returns An string representing the edit session identity for the requested workspace folder.
*/
provideEditSessionIdentity(workspaceFolder: WorkspaceFolder, token: CancellationToken): ProviderResult<string>;
}
}
23 changes: 23 additions & 0 deletions src/vs/platform/workspace/common/editSessions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';

export interface IEditSessionIdentityProvider {
joyceerhl marked this conversation as resolved.
Show resolved Hide resolved
readonly scheme: string;
getEditSessionIdentifier(workspaceFolder: IWorkspaceFolder, token: CancellationToken): Promise<string | undefined>;
}

export const IEditSessionIdentityService = createDecorator<IEditSessionIdentityService>('editSessionIdentityService');

export interface IEditSessionIdentityService {
readonly _serviceBrand: undefined;

registerEditSessionIdentityProvider(provider: IEditSessionIdentityProvider): void;
unregisterEditSessionIdentityProvider(scheme: string): void;
getEditSessionIdentifier(workspaceFolder: IWorkspaceFolder, cancellationTokenSource: CancellationTokenSource): Promise<string | undefined>;
}
18 changes: 17 additions & 1 deletion src/vs/workbench/api/browser/mainThreadWorkspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,15 @@ import { ILabelService } from 'vs/platform/label/common/label';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IRequestService } from 'vs/platform/request/common/request';
import { WorkspaceTrustRequestOptions, IWorkspaceTrustManagementService, IWorkspaceTrustRequestService } from 'vs/platform/workspace/common/workspaceTrust';
import { IWorkspace, IWorkspaceContextService, WorkbenchState, isUntitledWorkspace } from 'vs/platform/workspace/common/workspace';
import { IWorkspace, IWorkspaceContextService, WorkbenchState, isUntitledWorkspace, WorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers';
import { checkGlobFileExists } from 'vs/workbench/services/extensions/common/workspaceContains';
import { ITextQueryBuilderOptions, QueryBuilder } from 'vs/workbench/services/search/common/queryBuilder';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IFileMatch, IPatternInfo, ISearchProgressItem, ISearchService } from 'vs/workbench/services/search/common/search';
import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing';
import { ExtHostContext, ExtHostWorkspaceShape, ITextSearchComplete, IWorkspaceData, MainContext, MainThreadWorkspaceShape } from '../common/extHost.protocol';
import { IEditSessionIdentityService } from 'vs/platform/workspace/common/editSessions';

@extHostNamedCustomer(MainContext.MainThreadWorkspace)
export class MainThreadWorkspace implements MainThreadWorkspaceShape {
Expand All @@ -38,6 +39,7 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape {
extHostContext: IExtHostContext,
@ISearchService private readonly _searchService: ISearchService,
@IWorkspaceContextService private readonly _contextService: IWorkspaceContextService,
@IEditSessionIdentityService private readonly _editSessionIdentityService: IEditSessionIdentityService,
@IEditorService private readonly _editorService: IEditorService,
@IWorkspaceEditingService private readonly _workspaceEditingService: IWorkspaceEditingService,
@INotificationService private readonly _notificationService: INotificationService,
Expand Down Expand Up @@ -220,4 +222,18 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape {
private _onDidGrantWorkspaceTrust(): void {
this._proxy.$onDidGrantWorkspaceTrust();
}

// --- edit sessions ---
$registerEditSessionIdentityProvider(scheme: string) {
this._editSessionIdentityService.registerEditSessionIdentityProvider({
scheme: scheme,
getEditSessionIdentifier: async (workspaceFolder: WorkspaceFolder, token: CancellationToken) => {
return this._proxy.$getEditSessionIdentifier(workspaceFolder.uri, token);
}
});
}

$unregisterEditSessionIdentityProvider(scheme: string) {
this._editSessionIdentityService.unregisterEditSessionIdentityProvider(scheme);
}
joyceerhl marked this conversation as resolved.
Show resolved Hide resolved
}
4 changes: 4 additions & 0 deletions src/vs/workbench/api/common/extHost.api.impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1043,6 +1043,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
},
onDidGrantWorkspaceTrust: (listener, thisArgs?, disposables?) => {
return extHostWorkspace.onDidGrantWorkspaceTrust(listener, thisArgs, disposables);
},
registerEditSessionIdentityProvider: (scheme: string, provider: vscode.EditSessionIdentityProvider) => {
checkProposedApiEnabled(extension, 'editSessionIdentityProvider');
return extHostWorkspace.registerEditSessionIdentityProvider(scheme, provider);
}
};

Expand Down
3 changes: 3 additions & 0 deletions src/vs/workbench/api/common/extHost.protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1086,6 +1086,8 @@ export interface MainThreadWorkspaceShape extends IDisposable {
$updateWorkspaceFolders(extensionName: string, index: number, deleteCount: number, workspaceFoldersToAdd: { uri: UriComponents; name?: string }[]): Promise<void>;
$resolveProxy(url: string): Promise<string | undefined>;
$requestWorkspaceTrust(options?: WorkspaceTrustRequestOptions): Promise<boolean | undefined>;
$registerEditSessionIdentityProvider(scheme: string): void;
$unregisterEditSessionIdentityProvider(scheme: string): void;
}

export interface IFileChangeDto {
Expand Down Expand Up @@ -1414,6 +1416,7 @@ export interface ExtHostWorkspaceShape {
$acceptWorkspaceData(workspace: IWorkspaceData | null): void;
$handleTextSearchResult(result: search.IRawFileMatch2, requestId: number): void;
$onDidGrantWorkspaceTrust(): void;
$getEditSessionIdentifier(folder: UriComponents, token: CancellationToken): Promise<string | undefined>;
}

export interface ExtHostFileSystemInfoShape {
Expand Down
53 changes: 52 additions & 1 deletion src/vs/workbench/api/common/extHostWorkspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ import { delta as arrayDelta, mapArrayOrNot } from 'vs/base/common/arrays';
import { Barrier } from 'vs/base/common/async';
import { CancellationToken } from 'vs/base/common/cancellation';
import { Emitter, Event } from 'vs/base/common/event';
import { toDisposable } from 'vs/base/common/lifecycle';
import { TernarySearchTree } from 'vs/base/common/map';
import { Schemas } from 'vs/base/common/network';
import { Counter } from 'vs/base/common/numbers';
import { basename, basenameOrAuthority, dirname, ExtUri, relativePath } from 'vs/base/common/resources';
import { compare } from 'vs/base/common/strings';
import { withUndefinedAsNull } from 'vs/base/common/types';
import { URI } from 'vs/base/common/uri';
import { URI, UriComponents } from 'vs/base/common/uri';
import { localize } from 'vs/nls';
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { FileSystemProviderCapabilities } from 'vs/platform/files/common/files';
Expand All @@ -26,6 +27,7 @@ import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitData
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
import { GlobPattern } from 'vs/workbench/api/common/extHostTypeConverters';
import { Range } from 'vs/workbench/api/common/extHostTypes';
import { IURITransformerService } from 'vs/workbench/api/common/extHostUriTransformerService';
import { ITextQueryBuilderOptions } from 'vs/workbench/services/search/common/queryBuilder';
import { IRawFileMatch2, resultIsMatch } from 'vs/workbench/services/search/common/search';
import * as vscode from 'vscode';
Expand Down Expand Up @@ -182,19 +184,24 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac
private readonly _proxy: MainThreadWorkspaceShape;
private readonly _messageService: MainThreadMessageServiceShape;
private readonly _extHostFileSystemInfo: IExtHostFileSystemInfo;
private readonly _uriTransformerService: IURITransformerService;

private readonly _activeSearchCallbacks: ((match: IRawFileMatch2) => any)[] = [];

private _trusted: boolean = false;

private readonly _editSessionIdentityProviders = new Map<string, vscode.EditSessionIdentityProvider>();

constructor(
@IExtHostRpcService extHostRpc: IExtHostRpcService,
@IExtHostInitDataService initData: IExtHostInitDataService,
@IExtHostFileSystemInfo extHostFileSystemInfo: IExtHostFileSystemInfo,
@ILogService logService: ILogService,
@IURITransformerService uriTransformerService: IURITransformerService,
) {
this._logService = logService;
this._extHostFileSystemInfo = extHostFileSystemInfo;
this._uriTransformerService = uriTransformerService;
this._requestIdProvider = new Counter();
this._barrier = new Barrier();

Expand Down Expand Up @@ -573,6 +580,50 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac
this._onDidGrantWorkspaceTrust.fire();
}
}

// --- edit sessions ---

// called by ext host
registerEditSessionIdentityProvider(scheme: string, provider: vscode.EditSessionIdentityProvider) {
if (this._editSessionIdentityProviders.has(scheme)) {
throw new Error(`A provider has already been registered for scheme ${scheme}`);
}

this._editSessionIdentityProviders.set(scheme, provider);
const outgoingScheme = this._uriTransformerService.transformOutgoingScheme(scheme);
this._proxy.$registerEditSessionIdentityProvider(outgoingScheme);

return toDisposable(() => {
this._editSessionIdentityProviders.delete(scheme);
this._proxy.$unregisterEditSessionIdentityProvider(outgoingScheme);
});
}

// called by main thread
async $getEditSessionIdentifier(workspaceFolder: UriComponents, cancellationToken: CancellationToken): Promise<string | undefined> {
this._logService.info('Getting edit session identifier for workspaceFolder', workspaceFolder);
const folder = await this.resolveWorkspaceFolder(URI.revive(workspaceFolder));
if (!folder) {
this._logService.warn('Unable to resolve workspace folder');
return undefined;
}

this._logService.info('Invoking #provideEditSessionIdentity for workspaceFolder', folder);

const provider = this._editSessionIdentityProviders.get(folder.uri.scheme);
this._logService.info(`Provider for scheme ${folder.uri.scheme} is defined: `, !!provider);
if (!provider) {
return undefined;
}

const result = await provider.provideEditSessionIdentity(folder, cancellationToken);
this._logService.info('Provider returned edit session identifier: ', result);
if (!result) {
return undefined;
}

return result;
}
}

export const IExtHostWorkspace = createDecorator<IExtHostWorkspace>('IExtHostWorkspace');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitData
import { IExtHostFileSystemInfo } from 'vs/workbench/api/common/extHostFileSystemInfo';
import { FileSystemProviderCapabilities } from 'vs/platform/files/common/files';
import { isLinux } from 'vs/base/common/platform';
import { IURITransformerService } from 'vs/workbench/api/common/extHostUriTransformerService';

suite('ExtHostConfiguration', function () {

Expand All @@ -30,7 +31,7 @@ suite('ExtHostConfiguration', function () {
}

function createExtHostWorkspace(): ExtHostWorkspace {
return new ExtHostWorkspace(new TestRPCProtocol(), new class extends mock<IExtHostInitDataService>() { }, new class extends mock<IExtHostFileSystemInfo>() { override getCapabilities() { return isLinux ? FileSystemProviderCapabilities.PathCaseSensitive : undefined; } }, new NullLogService());
return new ExtHostWorkspace(new TestRPCProtocol(), new class extends mock<IExtHostInitDataService>() { }, new class extends mock<IExtHostFileSystemInfo>() { override getCapabilities() { return isLinux ? FileSystemProviderCapabilities.PathCaseSensitive : undefined; } }, new NullLogService(), new class extends mock<IURITransformerService>() { });
}

function createExtHostConfiguration(contents: any = Object.create(null), shape?: MainThreadConfigurationShape) {
Expand Down
2 changes: 2 additions & 0 deletions src/vs/workbench/api/test/browser/extHostWorkspace.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,15 @@ import { isLinux, isWindows } from 'vs/base/common/platform';
import { IExtHostFileSystemInfo } from 'vs/workbench/api/common/extHostFileSystemInfo';
import { FileSystemProviderCapabilities } from 'vs/platform/files/common/files';
import { nullExtensionDescription as extensionDescriptor } from 'vs/workbench/services/extensions/common/extensions';
import { IURITransformerService } from 'vs/workbench/api/common/extHostUriTransformerService';

function createExtHostWorkspace(mainContext: IMainContext, data: IWorkspaceData, logService: ILogService): ExtHostWorkspace {
const result = new ExtHostWorkspace(
new ExtHostRpcService(mainContext),
new class extends mock<IExtHostInitDataService>() { override workspace = data; },
new class extends mock<IExtHostFileSystemInfo>() { override getCapabilities() { return isLinux ? FileSystemProviderCapabilities.PathCaseSensitive : undefined; } },
logService,
new class extends mock<IURITransformerService>() { }
);
result.$initializeWorkspace(data, true);
return result;
Expand Down
Loading