Skip to content

Transform URIs of hover results #157448

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

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion src/vs/base/browser/markdownRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,8 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
} else {
let resolvedHref = _href(href, false);
if (markdown.baseUri) {
resolvedHref = resolveWithBaseUri(URI.from(markdown.baseUri), href);
// resolvedHref might have different URI parts in the case of being transformed during collecting
resolvedHref = resolveWithBaseUri(URI.from(markdown.baseUri), resolvedHref);
}
a.dataset.href = resolvedHref;
}
Expand Down
18 changes: 18 additions & 0 deletions src/vs/base/test/browser/markdownRenderer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,24 @@ suite('MarkdownRenderer', () => {
assert.ok(data.documentUri.toString().startsWith('file:///c%3A/'));
});

test('render collected transformed uri', () => {
// MarkdownString.uris maps hrefs into uris, which may have different scheme and authority
// should render the transformed uri to data-href
const md: IMarkdownString = JSON.parse('{"value":"[link](file:///foo/bar)","supportThemeIcons":false,"supportHtml":false,"uris":{"file:///foo/bar":{"$mid":1,"external":"vscode-remote://host/foo/bar","path":"/foo/bar","scheme":"vscode-remote","authority":"host"}}}');
const element = renderMarkdown(md).element;

const anchor = element.querySelector('a')!;
assert.ok(anchor);
assert.ok(anchor.dataset['href']);

const uri = URI.parse(anchor.dataset['href']!);

assert.ok(uri);
assert.strictEqual(uri.scheme, 'vscode-remote');
assert.strictEqual(uri.authority, 'host');
assert.strictEqual(uri.fsPath, '/foo/bar');
});

test('Should not render command links by default', () => {
const md = new MarkdownString(`[command1](command:doFoo) <a href="command:doFoo">command2</a>`, {
supportHtml: true
Expand Down
5 changes: 3 additions & 2 deletions src/vs/workbench/api/common/extHostLanguageFeatures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,7 @@ class HoverAdapter {
constructor(
private readonly _documents: ExtHostDocuments,
private readonly _provider: vscode.HoverProvider,
private readonly _uriTransformer: IURITransformer,
) { }

async provideHover(resource: URI, position: IPosition, token: CancellationToken): Promise<languages.Hover | undefined> {
Expand All @@ -263,7 +264,7 @@ class HoverAdapter {
if (!value.range) {
value.range = new Range(pos, pos);
}
return typeConvert.Hover.from(value);
return typeConvert.Hover.from(value, this._uriTransformer);
}
}

Expand Down Expand Up @@ -2009,7 +2010,7 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF
// --- extra info

registerHoverProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.HoverProvider, extensionId?: ExtensionIdentifier): vscode.Disposable {
const handle = this._addNewAdapter(new HoverAdapter(this._documents, provider), extension);
const handle = this._addNewAdapter(new HoverAdapter(this._documents, provider, this._uriTransformer), extension);
this._proxy.$registerHoverProvider(handle, this._transformDocumentSelector(selector));
return this._createDisposable(handle);
}
Expand Down
19 changes: 10 additions & 9 deletions src/vs/workbench/api/common/extHostTypeConverters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -306,8 +306,8 @@ export function isDecorationOptionsArr(something: vscode.Range[] | vscode.Decora

export namespace MarkdownString {

export function fromMany(markup: (vscode.MarkdownString | vscode.MarkedString)[]): htmlContent.IMarkdownString[] {
return markup.map(MarkdownString.from);
export function fromMany(markup: (vscode.MarkdownString | vscode.MarkedString)[], uriTransformer?: IURITransformer): htmlContent.IMarkdownString[] {
return markup.map((content) => MarkdownString.from(content, uriTransformer));
}

interface Codeblock {
Expand All @@ -321,7 +321,7 @@ export namespace MarkdownString {
&& typeof (<Codeblock>thing).value === 'string';
}

export function from(markup: vscode.MarkdownString | vscode.MarkedString): htmlContent.IMarkdownString {
export function from(markup: vscode.MarkdownString | vscode.MarkedString, uriTransformer?: IURITransformer): htmlContent.IMarkdownString {
let res: htmlContent.IMarkdownString;
if (isCodeblock(markup)) {
const { language, value } = markup;
Expand All @@ -341,6 +341,7 @@ export namespace MarkdownString {
const collectUri = (href: string): string => {
try {
let uri = URI.parse(href, true);
uri = uriTransformer ? uriTransformer.transformOutgoingURI(uri) : uri;
uri = uri.with({ query: _uriMassage(uri.query, resUris) });
resUris[href] = uri;
} catch (e) {
Expand Down Expand Up @@ -389,11 +390,11 @@ export namespace MarkdownString {
return JSON.stringify(data);
}

export function to(value: htmlContent.IMarkdownString): vscode.MarkdownString {
export function to(value: htmlContent.IMarkdownString, uriTransformer?: IURITransformer): vscode.MarkdownString {
const result = new types.MarkdownString(value.value, value.supportThemeIcons);
result.isTrusted = value.isTrusted;
result.supportHtml = value.supportHtml;
result.baseUri = value.baseUri ? URI.from(value.baseUri) : undefined;
result.baseUri = value.baseUri ? uriTransformer ? URI.from(uriTransformer.transformIncoming(URI.from(value.baseUri))) : URI.from(value.baseUri) : undefined;
return result;
}

Expand Down Expand Up @@ -891,15 +892,15 @@ export namespace DefinitionLink {
}

export namespace Hover {
export function from(hover: vscode.Hover): languages.Hover {
export function from(hover: vscode.Hover, uriTransformer?: IURITransformer): languages.Hover {
return <languages.Hover>{
range: Range.from(hover.range),
contents: MarkdownString.fromMany(hover.contents)
contents: MarkdownString.fromMany(hover.contents, uriTransformer)
};
}

export function to(info: languages.Hover): types.Hover {
return new types.Hover(info.contents.map(MarkdownString.to), Range.to(info.range));
export function to(info: languages.Hover, uriTransformer?: IURITransformer): types.Hover {
return new types.Hover(info.contents.map((content) => MarkdownString.to(content, uriTransformer)), Range.to(info.range));
}
}

Expand Down
47 changes: 47 additions & 0 deletions src/vs/workbench/api/common/uriTransformer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { UriParts, IRawURITransformer } from 'vs/base/common/uriIpc';

/**
* ```
* --------------------------------
* | UI SIDE | AGENT SIDE |
* |---------------|--------------|
* | vscode-remote | file |
* | file | vscode-local |
* --------------------------------
* ```
*/
export function createRemoteRawURITransformer(remoteAuthority: string): IRawURITransformer {
return {
transformIncoming: (uri: UriParts): UriParts => {
if (uri.scheme === 'vscode-remote') {
return { scheme: 'file', path: uri.path, query: uri.query, fragment: uri.fragment };
}
if (uri.scheme === 'file') {
return { scheme: 'vscode-local', path: uri.path, query: uri.query, fragment: uri.fragment };
}
return uri;
},
transformOutgoing: (uri: UriParts): UriParts => {
if (uri.scheme === 'file') {
return { scheme: 'vscode-remote', authority: remoteAuthority, path: uri.path, query: uri.query, fragment: uri.fragment };
}
if (uri.scheme === 'vscode-local') {
return { scheme: 'file', path: uri.path, query: uri.query, fragment: uri.fragment };
}
return uri;
},
transformOutgoingScheme: (scheme: string): string => {
if (scheme === 'file') {
return 'vscode-remote';
} else if (scheme === 'vscode-local') {
return 'file';
}
return scheme;
}
};
}
45 changes: 3 additions & 42 deletions src/vs/workbench/api/node/uriTransformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,49 +3,10 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { UriParts, IRawURITransformer, URITransformer, IURITransformer } from 'vs/base/common/uriIpc';
import { URITransformer, IURITransformer } from 'vs/base/common/uriIpc';
import { createRemoteRawURITransformer } from 'vs/workbench/api/common/uriTransformer';

/**
* ```
* --------------------------------
* | UI SIDE | AGENT SIDE |
* |---------------|--------------|
* | vscode-remote | file |
* | file | vscode-local |
* --------------------------------
* ```
*/
function createRawURITransformer(remoteAuthority: string): IRawURITransformer {
return {
transformIncoming: (uri: UriParts): UriParts => {
if (uri.scheme === 'vscode-remote') {
return { scheme: 'file', path: uri.path, query: uri.query, fragment: uri.fragment };
}
if (uri.scheme === 'file') {
return { scheme: 'vscode-local', path: uri.path, query: uri.query, fragment: uri.fragment };
}
return uri;
},
transformOutgoing: (uri: UriParts): UriParts => {
if (uri.scheme === 'file') {
return { scheme: 'vscode-remote', authority: remoteAuthority, path: uri.path, query: uri.query, fragment: uri.fragment };
}
if (uri.scheme === 'vscode-local') {
return { scheme: 'file', path: uri.path, query: uri.query, fragment: uri.fragment };
}
return uri;
},
transformOutgoingScheme: (scheme: string): string => {
if (scheme === 'file') {
return 'vscode-remote';
} else if (scheme === 'vscode-local') {
return 'file';
}
return scheme;
}
};
}

export function createURITransformer(remoteAuthority: string): IURITransformer {
return new URITransformer(createRawURITransformer(remoteAuthority));
return new URITransformer(createRemoteRawURITransformer(remoteAuthority));
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ import { OutlineModel } from 'vs/editor/contrib/documentSymbols/browser/outlineM
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
import { LanguageFeaturesService } from 'vs/editor/common/services/languageFeaturesService';
import { CodeActionTriggerSource } from 'vs/editor/contrib/codeAction/browser/types';
import { IURITransformer, URITransformer } from 'vs/base/common/uriIpc';
import { createRemoteRawURITransformer } from 'vs/workbench/api/common/uriTransformer';

suite('ExtHostLanguageFeatures', function () {

Expand Down Expand Up @@ -115,7 +117,8 @@ suite('ExtHostLanguageFeatures', function () {
const diagnostics = new ExtHostDiagnostics(rpcProtocol, new NullLogService(), new class extends mock<IExtHostFileSystemInfo>() { });
rpcProtocol.set(ExtHostContext.ExtHostDiagnostics, diagnostics);

extHost = new ExtHostLanguageFeatures(rpcProtocol, new URITransformerService(null), extHostDocuments, commands, diagnostics, new NullLogService(), NullApiDeprecationService);
const uriTransformer: IURITransformer = new URITransformer(createRemoteRawURITransformer('host'));
extHost = new ExtHostLanguageFeatures(rpcProtocol, new URITransformerService(uriTransformer), extHostDocuments, commands, diagnostics, new NullLogService(), NullApiDeprecationService);
rpcProtocol.set(ExtHostContext.ExtHostLanguageFeatures, extHost);

mainThread = rpcProtocol.set(MainContext.MainThreadLanguageFeatures, inst.createInstance(MainThreadLanguageFeatures, rpcProtocol));
Expand Down Expand Up @@ -478,6 +481,29 @@ suite('ExtHostLanguageFeatures', function () {
});
});

test('HoverProvider, transform uri', async () => {
// [markdown content, transformed collected uris]
const fixtures: [string, string[]][] = [
[`![link](file:///foo/bar)`, [`vscode-remote://host/foo/bar`]],
[`[link](file:///foo/bar#L1)`, [`vscode-remote://host/foo/bar#L1`]],
[`[link](vscode-local:///foo/bar)`, [`file:///foo/bar`]],
[`[link](https://example.com/foo/bar)`, [`https://example.com/foo/bar`]],
];

disposables.push(extHost.registerHoverProvider(defaultExtension, defaultSelector, new class implements vscode.HoverProvider {
provideHover(): vscode.Hover {
return { contents: fixtures.map(([md]) => new types.MarkdownString(md)) };
}
}));

await rpcProtocol.sync();
const hovers = await getHoverPromise(languageFeaturesService.hoverProvider, model, new EditorPosition(1, 1), CancellationToken.None);
assert.strictEqual(hovers.length, 1);
hovers[0].contents.forEach((md, index) => assert.deepEqual(
Object.values((md.uris || {})).map(uri => URI.revive(uri).toString()), fixtures[index][1])
);
});

// --- occurrences

test('Occurrences, data conversion', async () => {
Expand Down