diff --git a/extensions/typescript-language-features/src/commands/index.ts b/extensions/typescript-language-features/src/commands/index.ts index 919b1b749c5..7130f201975 100644 --- a/extensions/typescript-language-features/src/commands/index.ts +++ b/extensions/typescript-language-features/src/commands/index.ts @@ -3,14 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { PluginManager } from '../tsServer/plugins'; import TypeScriptServiceClientHost from '../typeScriptServiceClientHost'; import { ActiveJsTsEditorTracker } from '../ui/activeJsTsEditorTracker'; import { Lazy } from '../utils/lazy'; -import { PluginManager } from '../tsServer/plugins'; import { CommandManager } from './commandManager'; import { ConfigurePluginCommand } from './configurePlugin'; import { JavaScriptGoToProjectConfigCommand, TypeScriptGoToProjectConfigCommand } from './goToProjectConfiguration'; import { LearnMoreAboutRefactoringsCommand } from './learnMoreAboutRefactorings'; +import { OpenJsDocLinkCommand } from './openJsDocLink'; import { OpenTsServerLogCommand } from './openTsServerLog'; import { ReloadJavaScriptProjectsCommand, ReloadTypeScriptProjectsCommand } from './reloadProject'; import { RestartTsServerCommand } from './restartTsServer'; @@ -33,4 +34,5 @@ export function registerBaseCommands( commandManager.register(new ConfigurePluginCommand(pluginManager)); commandManager.register(new LearnMoreAboutRefactoringsCommand()); commandManager.register(new TSServerRequestCommand(lazyClientHost)); + commandManager.register(new OpenJsDocLinkCommand()); } diff --git a/extensions/typescript-language-features/src/commands/openJsDocLink.ts b/extensions/typescript-language-features/src/commands/openJsDocLink.ts new file mode 100644 index 00000000000..10a480a4bd3 --- /dev/null +++ b/extensions/typescript-language-features/src/commands/openJsDocLink.ts @@ -0,0 +1,28 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import { Command } from './commandManager'; + +export interface OpenJsDocLinkCommand_Args { + readonly file: vscode.Uri; + readonly position: vscode.Position; +} + +/** + * Proxy command for opening links in jsdoc comments. + * + * This is needed to avoid incorrectly rewriting uris. + */ +export class OpenJsDocLinkCommand implements Command { + public static readonly id = '_typescript.openJsDocLink'; + public readonly id = OpenJsDocLinkCommand.id; + + public async execute(args: OpenJsDocLinkCommand_Args): Promise { + await vscode.commands.executeCommand('vscode.open', vscode.Uri.from(args.file), { + selection: new vscode.Range(args.position, args.position), + }); + } +} diff --git a/extensions/typescript-language-features/src/languageFeatures/util/textRendering.ts b/extensions/typescript-language-features/src/languageFeatures/util/textRendering.ts index f94eca76818..4eef8c1b963 100644 --- a/extensions/typescript-language-features/src/languageFeatures/util/textRendering.ts +++ b/extensions/typescript-language-features/src/languageFeatures/util/textRendering.ts @@ -4,7 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; +import { OpenJsDocLinkCommand, OpenJsDocLinkCommand_Args } from '../../commands/openJsDocLink'; import type * as Proto from '../../tsServer/protocol/protocol'; +import * as typeConverters from '../../typeConverters'; export interface IFilePathToResourceConverter { /** @@ -160,13 +162,15 @@ function convertLinkTags( case 'link': if (currentLink) { if (currentLink.target) { - const link = filePathConverter.toResource(currentLink.target.file) - .with({ - fragment: `L${currentLink.target.start.line},${currentLink.target.start.offset}` - }); + const file = filePathConverter.toResource(currentLink.target.file); + const args: OpenJsDocLinkCommand_Args = { + file: { ...file.toJSON(), $mid: undefined }, // Prevent VS Code from trying to transform the uri, + position: typeConverters.Position.fromLocation(currentLink.target.start) + }; + const command = `command:${OpenJsDocLinkCommand.id}?${encodeURIComponent(JSON.stringify([args]))}`; const linkText = currentLink.text ? currentLink.text : escapeMarkdownSyntaxTokensForCode(currentLink.name ?? ''); - out.push(`[${currentLink.linkcode ? '`' + linkText + '`' : linkText}](${link.toString()})`); + out.push(`[${currentLink.linkcode ? '`' + linkText + '`' : linkText}](${command})`); } else { const text = currentLink.text ?? currentLink.name; if (text) { @@ -232,6 +236,7 @@ export function documentationToMarkdown( const out = new vscode.MarkdownString(); appendDocumentationAsMarkdown(out, documentation, tags, filePathConverter); out.baseUri = baseUri; + out.isTrusted = { enabledCommands: [OpenJsDocLinkCommand.id] }; return out; } @@ -251,5 +256,8 @@ export function appendDocumentationAsMarkdown( out.appendMarkdown('\n\n' + tagsPreview); } } + + out.isTrusted = { enabledCommands: [OpenJsDocLinkCommand.id] }; + return out; } diff --git a/extensions/typescript-language-features/src/test/unit/textRendering.test.ts b/extensions/typescript-language-features/src/test/unit/textRendering.test.ts index cf6bdfb07ed..2354bb7f589 100644 --- a/extensions/typescript-language-features/src/test/unit/textRendering.test.ts +++ b/extensions/typescript-language-features/src/test/unit/textRendering.test.ts @@ -152,7 +152,7 @@ suite('typescript.previewer', () => { { "text": "}", "kind": "link" }, { "text": " b", "kind": "text" } ], noopToResource), - 'a [`dog`](file:///path/file.ts#L7%2C5) b'); + 'a [`dog`](command:_typescript.openJsDocLink?%5B%7B%22file%22%3A%7B%22path%22%3A%22%2Fpath%2Ffile.ts%22%2C%22scheme%22%3A%22file%22%7D%2C%22position%22%3A%7B%22line%22%3A6%2C%22character%22%3A4%7D%7D%5D) b'); }); test('Should render @linkcode text as code', async () => { @@ -173,6 +173,6 @@ suite('typescript.previewer', () => { { "text": "}", "kind": "link" }, { "text": " b", "kind": "text" } ], noopToResource), - 'a [`husky`](file:///path/file.ts#L7%2C5) b'); + 'a [`husky`](command:_typescript.openJsDocLink?%5B%7B%22file%22%3A%7B%22path%22%3A%22%2Fpath%2Ffile.ts%22%2C%22scheme%22%3A%22file%22%7D%2C%22position%22%3A%7B%22line%22%3A6%2C%22character%22%3A4%7D%7D%5D) b'); }); }); diff --git a/src/vs/base/common/htmlContent.ts b/src/vs/base/common/htmlContent.ts index 107ee2c4feb..c3b79a8e2e0 100644 --- a/src/vs/base/common/htmlContent.ts +++ b/src/vs/base/common/htmlContent.ts @@ -118,7 +118,7 @@ export function isMarkdownString(thing: any): thing is IMarkdownString { return true; } else if (thing && typeof thing === 'object') { return typeof (thing).value === 'string' - && (typeof (thing).isTrusted === 'boolean' || (thing).isTrusted === undefined) + && (typeof (thing).isTrusted === 'boolean' || typeof (thing).isTrusted === 'object' || (thing).isTrusted === undefined) && (typeof (thing).supportThemeIcons === 'boolean' || (thing).supportThemeIcons === undefined); } return false;