Add RemoteAuthorityResolver.getCanonicalURI

This commit is contained in:
Alex Dima 2021-05-21 16:47:57 +02:00
parent c650993dd3
commit 85f518b255
No known key found for this signature in database
GPG key ID: 6E58D7B045760DA0
9 changed files with 139 additions and 28 deletions

View file

@ -216,6 +216,9 @@ export function activate(context: vscode.ExtensionContext) {
}
const authorityResolverDisposable = vscode.workspace.registerRemoteAuthorityResolver('test', {
async getCanonicalURI(uri: vscode.Uri): Promise<vscode.Uri> {
return vscode.Uri.file(uri.path);
},
resolve(_authority: string): Thenable<vscode.ResolvedAuthority> {
return vscode.window.withProgress({
location: vscode.ProgressLocation.Notification,

View file

@ -40,6 +40,10 @@ export class RemoteAuthorityResolverService extends Disposable implements IRemot
return this._cache.get(authority)!;
}
async getCanonicalURI(uri: URI): Promise<URI> {
return uri;
}
getConnectionData(authority: string): IRemoteConnectionData | null {
if (!this._cache.has(authority)) {
return null;
@ -76,4 +80,7 @@ export class RemoteAuthorityResolverService extends Disposable implements IRemot
RemoteAuthorities.setConnectionToken(authority, connectionToken);
this._onDidChangeConnectionData.fire();
}
_setCanonicalURIProvider(provider: (uri: URI) => Promise<URI>): void {
}
}

View file

@ -5,6 +5,7 @@
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { Event } from 'vs/base/common/event';
import { URI } from 'vs/base/common/uri';
export const IRemoteAuthorityResolverService = createDecorator<IRemoteAuthorityResolverService>('remoteAuthorityResolverService');
@ -92,9 +93,18 @@ export interface IRemoteAuthorityResolverService {
resolveAuthority(authority: string): Promise<ResolverResult>;
getConnectionData(authority: string): IRemoteConnectionData | null;
/**
* Get the canonical URI for a `vscode-remote://` URI.
*
* **NOTE**: This can throw e.g. in cases where there is no resolver installed for the specific remote authority.
*
* @param uri The `vscode-remote://` URI
*/
getCanonicalURI(uri: URI): Promise<URI>;
_clearResolvedAuthority(authority: string): void;
_setResolvedAuthority(resolvedAuthority: ResolvedAuthority, resolvedOptions?: ResolvedOptions): void;
_setResolvedAuthorityError(authority: string, err: any): void;
_setAuthorityConnectionToken(authority: string, connectionToken: string): void;
_setCanonicalURIProvider(provider: (uri: URI) => Promise<URI>): void;
}

View file

@ -8,22 +8,27 @@ import * as errors from 'vs/base/common/errors';
import { RemoteAuthorities } from 'vs/base/common/network';
import { Disposable } from 'vs/base/common/lifecycle';
import { Emitter } from 'vs/base/common/event';
import { URI } from 'vs/base/common/uri';
class PendingResolveAuthorityRequest {
class PendingPromise<I, R> {
public readonly promise: Promise<R>;
public readonly input: I;
public result: R | null;
private _resolve!: (value: R) => void;
private _reject!: (err: any) => void;
public value: ResolverResult | null;
constructor(
private readonly _resolve: (value: ResolverResult) => void,
private readonly _reject: (err: any) => void,
public readonly promise: Promise<ResolverResult>,
) {
this.value = null;
constructor(request: I) {
this.input = request;
this.promise = new Promise<R>((resolve, reject) => {
this._resolve = resolve;
this._reject = reject;
});
this.result = null;
}
resolve(value: ResolverResult): void {
this.value = value;
this._resolve(this.value);
resolve(result: R): void {
this.result = result;
this._resolve(this.result);
}
reject(err: any): void {
@ -38,40 +43,50 @@ export class RemoteAuthorityResolverService extends Disposable implements IRemot
private readonly _onDidChangeConnectionData = this._register(new Emitter<void>());
public readonly onDidChangeConnectionData = this._onDidChangeConnectionData.event;
private readonly _resolveAuthorityRequests: Map<string, PendingResolveAuthorityRequest>;
private readonly _resolveAuthorityRequests: Map<string, PendingPromise<string, ResolverResult>>;
private readonly _connectionTokens: Map<string, string>;
private readonly _canonicalURIRequests: Map<string, PendingPromise<URI, URI>>;
private _canonicalURIProvider: ((uri: URI) => Promise<URI>) | null;
constructor() {
super();
this._resolveAuthorityRequests = new Map<string, PendingResolveAuthorityRequest>();
this._resolveAuthorityRequests = new Map<string, PendingPromise<string, ResolverResult>>();
this._connectionTokens = new Map<string, string>();
this._canonicalURIRequests = new Map<string, PendingPromise<URI, URI>>();
this._canonicalURIProvider = null;
}
resolveAuthority(authority: string): Promise<ResolverResult> {
if (!this._resolveAuthorityRequests.has(authority)) {
let resolve: (value: ResolverResult) => void;
let reject: (err: any) => void;
const promise = new Promise<ResolverResult>((_resolve, _reject) => {
resolve = _resolve;
reject = _reject;
});
this._resolveAuthorityRequests.set(authority, new PendingResolveAuthorityRequest(resolve!, reject!, promise));
this._resolveAuthorityRequests.set(authority, new PendingPromise<string, ResolverResult>(authority));
}
return this._resolveAuthorityRequests.get(authority)!.promise;
}
async getCanonicalURI(uri: URI): Promise<URI> {
const key = uri.toString();
if (!this._canonicalURIRequests.has(key)) {
const request = new PendingPromise<URI, URI>(uri);
if (this._canonicalURIProvider) {
this._canonicalURIProvider(request.input).then((uri) => request.resolve(uri), (err) => request.reject(err));
}
this._canonicalURIRequests.set(key, request);
}
return this._canonicalURIRequests.get(key)!.promise;
}
getConnectionData(authority: string): IRemoteConnectionData | null {
if (!this._resolveAuthorityRequests.has(authority)) {
return null;
}
const request = this._resolveAuthorityRequests.get(authority)!;
if (!request.value) {
if (!request.result) {
return null;
}
const connectionToken = this._connectionTokens.get(authority);
return {
host: request.value.authority.host,
port: request.value.authority.port,
host: request.result.authority.host,
port: request.result.authority.port,
connectionToken: connectionToken
};
}
@ -107,4 +122,11 @@ export class RemoteAuthorityResolverService extends Disposable implements IRemot
RemoteAuthorities.setConnectionToken(authority, connectionToken);
this._onDidChangeConnectionData.fire();
}
_setCanonicalURIProvider(provider: (uri: URI) => Promise<URI>): void {
this._canonicalURIProvider = provider;
this._canonicalURIRequests.forEach((value) => {
this._canonicalURIProvider!(value.input).then((uri) => value.resolve(uri), (err) => value.reject(err));
});
}
}

View file

@ -144,7 +144,24 @@ declare module 'vscode' {
}
export interface RemoteAuthorityResolver {
/**
* Resolve the authority part of the current opened `vscode-remote://` URI.
*
* This method will be invoked once during the startup of VS Code and again each time
* VS Code detects a disconnection.
*
* @param authority The authority part of the current opened `vscode-remote://` URI.
* @param context A context indicating if this is the first call or a subsequent call.
*/
resolve(authority: string, context: RemoteAuthorityResolverContext): ResolverResult | Thenable<ResolverResult>;
/**
* Get the canonical URI (if applicable) for a `vscode-remote://` URI.
*
* @returns The canonical URI or undefined if the uri is already canonical.
*/
getCanonicalURI?(uri: Uri): ProviderResult<Uri>;
/**
* Can be optionally implemented if the extension can forward ports better than the core.
* When not implemented, the core will use its default forwarding logic.

View file

@ -1297,6 +1297,7 @@ export type IResolveAuthorityResult = IResolveAuthorityErrorResult | IResolveAut
export interface ExtHostExtensionServiceShape {
$resolveAuthority(remoteAuthority: string, resolveAttempt: number): Promise<IResolveAuthorityResult>;
$getCanonicalURI(remoteAuthority: string, uri: UriComponents): Promise<UriComponents>;
$startExtensionHost(enabledExtensionIds: ExtensionIdentifier[]): Promise<void>;
$extensionTestsExecute(): Promise<number>;
$extensionTestsExit(code: number): Promise<void>;

View file

@ -7,10 +7,10 @@ import * as nls from 'vs/nls';
import * as path from 'vs/base/common/path';
import * as performance from 'vs/base/common/performance';
import { originalFSPath, joinPath } from 'vs/base/common/resources';
import { Barrier, timeout } from 'vs/base/common/async';
import { asPromise, Barrier, timeout } from 'vs/base/common/async';
import { dispose, toDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle';
import { TernarySearchTree } from 'vs/base/common/map';
import { URI } from 'vs/base/common/uri';
import { URI, UriComponents } from 'vs/base/common/uri';
import { ILogService } from 'vs/platform/log/common/log';
import { ExtHostExtensionServiceShape, IInitData, MainContext, MainThreadExtensionServiceShape, MainThreadTelemetryShape, MainThreadWorkspaceShape, IResolveAuthorityResult } from 'vs/workbench/api/common/extHost.protocol';
import { ExtHostConfiguration, IExtHostConfiguration } from 'vs/workbench/api/common/extHostConfiguration';
@ -631,7 +631,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
// -- called by main thread
public async $resolveAuthority(remoteAuthority: string, resolveAttempt: number): Promise<IResolveAuthorityResult> {
private async _activateAndGetResolver(remoteAuthority: string): Promise<{ authorityPrefix: string; resolver: vscode.RemoteAuthorityResolver | undefined; }> {
const authorityPlusIndex = remoteAuthority.indexOf('+');
if (authorityPlusIndex === -1) {
throw new Error(`Not an authority that can be resolved!`);
@ -641,7 +641,12 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
await this._almostReadyToRunExtensions.wait();
await this._activateByEvent(`onResolveRemoteAuthority:${authorityPrefix}`, false);
const resolver = this._resolvers[authorityPrefix];
return { authorityPrefix, resolver: this._resolvers[authorityPrefix] };
}
public async $resolveAuthority(remoteAuthority: string, resolveAttempt: number): Promise<IResolveAuthorityResult> {
const { authorityPrefix, resolver } = await this._activateAndGetResolver(remoteAuthority);
if (!resolver) {
return {
type: 'error',
@ -695,6 +700,28 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
}
}
public async $getCanonicalURI(remoteAuthority: string, uriComponents: UriComponents): Promise<UriComponents> {
const { authorityPrefix, resolver } = await this._activateAndGetResolver(remoteAuthority);
if (!resolver) {
throw new Error(`Cannot get canonical URI because no remote extension is installed to resolve ${authorityPrefix}`);
}
const uri = URI.revive(uriComponents);
if (typeof resolver.getCanonicalURI === 'undefined') {
// resolver cannot compute canonical URI
return uri;
}
const result = await asPromise(() => resolver.getCanonicalURI!(uri));
if (!result) {
return uri;
}
return result;
}
public $startExtensionHost(enabledExtensionIds: ExtensionIdentifier[]): Promise<void> {
this._registry.keepOnly(enabledExtensionIds);
return this._startExtensionHost();

View file

@ -24,6 +24,7 @@ import { IExtensionHost, ExtensionHostKind, ActivationKind } from 'vs/workbench/
import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtensionActivator';
import { CATEGORIES } from 'vs/workbench/common/actions';
import { timeout } from 'vs/base/common/async';
import { URI } from 'vs/base/common/uri';
// Enable to see detailed message communication between window and extension host
const LOG_EXTENSION_HOST_COMMUNICATION = false;
@ -285,6 +286,15 @@ export class ExtensionHostManager extends Disposable {
}
}
public async getCanonicalURI(remoteAuthority: string, uri: URI): Promise<URI> {
const proxy = await this._getProxy();
if (!proxy) {
throw new Error(`Cannot resolve canonical URI`);
}
const result = await proxy.$getCanonicalURI(remoteAuthority, uri);
return URI.revive(result);
}
public async start(enabledExtensionIds: ExtensionIdentifier[]): Promise<void> {
const proxy = await this._getProxy();
if (!proxy) {

View file

@ -343,6 +343,16 @@ export class ExtensionService extends AbstractExtensionService implements IExten
let remoteExtensions: IExtensionDescription[] = [];
if (remoteAuthority) {
this._remoteAuthorityResolverService._setCanonicalURIProvider(async (uri) => {
if (uri.authority !== remoteAuthority) {
// The current remote authority resolver cannot give the canonical URI for this URI
return uri;
}
const localProcessExtensionHost = this._getExtensionHostManager(ExtensionHostKind.LocalProcess)!;
return localProcessExtensionHost.getCanonicalURI(remoteAuthority, uri);
});
let resolverResult: ResolverResult;
try {
@ -397,6 +407,10 @@ export class ExtensionService extends AbstractExtensionService implements IExten
}
updateProxyConfigurationsScope(remoteEnv.useHostProxy ? ConfigurationScope.APPLICATION : ConfigurationScope.MACHINE);
} else {
this._remoteAuthorityResolverService._setCanonicalURIProvider(async (uri) => uri);
}
await this._startLocalExtensionHost(localExtensions, remoteAuthority, remoteEnv, remoteExtensions);