From d851ea5d49b0ec89b98765d9e0084c25f2ff5031 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 19 Apr 2022 19:31:58 -0700 Subject: [PATCH] Add 'go to source definition' command Fixes #147532 Requires TS 4.7+ --- .../typescript-language-features/package.json | 32 +++++ .../package.nls.json | 1 + .../src/languageFeatures/sourceDefinition.ts | 122 ++++++++++++++++++ .../src/languageProvider.ts | 1 + .../src/utils/api.ts | 1 + 5 files changed, 157 insertions(+) create mode 100644 extensions/typescript-language-features/src/languageFeatures/sourceDefinition.ts diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index db88efe88ca..932626b6a12 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -65,6 +65,7 @@ "onCommand:_typescript.configurePlugin", "onCommand:_typescript.learnMoreAboutRefactorings", "onCommand:typescript.fileReferences", + "onCommand:typescript.goToSourceDefinition", "onTaskType:typescript", "onLanguage:jsonc" ], @@ -1229,6 +1230,11 @@ "command": "typescript.findAllFileReferences", "title": "%typescript.findAllFileReferences%", "category": "TypeScript" + }, + { + "command": "typescript.goToSourceDefinition", + "title": "%typescript.goToSourceDefinition%", + "category": "TypeScript" } ], "menus": { @@ -1280,6 +1286,32 @@ { "command": "typescript.findAllFileReferences", "when": "tsSupportsFileReferences && typescript.isManagedFile" + }, + { + "command": "typescript.goToSourceDefinition", + "when": "tsSupportsSourceDefinition && typescript.isManagedFile" + } + ], + "editor/context": [ + { + "command": "typescript.goToSourceDefinition", + "when": "tsSupportsSourceDefinition && resourceLangId == typescript", + "group": "navigation@9" + }, + { + "command": "typescript.goToSourceDefinition", + "when": "tsSupportsSourceDefinition && resourceLangId == typescriptreact", + "group": "navigation@9" + }, + { + "command": "typescript.goToSourceDefinition", + "when": "tsSupportsSourceDefinition && resourceLangId == javascript", + "group": "navigation@9" + }, + { + "command": "typescript.goToSourceDefinition", + "when": "tsSupportsSourceDefinition && resourceLangId == javascriptreact", + "group": "navigation@9" } ], "explorer/context": [ diff --git a/extensions/typescript-language-features/package.nls.json b/extensions/typescript-language-features/package.nls.json index 4a7322a8432..5a91821e729 100644 --- a/extensions/typescript-language-features/package.nls.json +++ b/extensions/typescript-language-features/package.nls.json @@ -190,6 +190,7 @@ "codeActions.refactor.rewrite.property.generateAccessors.description": "Generate 'get' and 'set' accessors", "codeActions.source.organizeImports.title": "Organize imports", "typescript.findAllFileReferences": "Find File References", + "typescript.goToSourceDefinition": "Go to Source Definition", "configuration.suggest.classMemberSnippets.enabled": "Enable/disable snippet completions for class members. Requires using TypeScript 4.5+ in the workspace", "configuration.suggest.objectLiteralMethodSnippets.enabled": "Enable/disable snippet completions for methods in object literals. Requires using TypeScript 4.7+ in the workspace" } diff --git a/extensions/typescript-language-features/src/languageFeatures/sourceDefinition.ts b/extensions/typescript-language-features/src/languageFeatures/sourceDefinition.ts new file mode 100644 index 00000000000..8107d92b109 --- /dev/null +++ b/extensions/typescript-language-features/src/languageFeatures/sourceDefinition.ts @@ -0,0 +1,122 @@ +/*--------------------------------------------------------------------------------------------- + * 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 * as nls from 'vscode-nls'; +import { Command, CommandManager } from '../commands/commandManager'; +import * as Proto from '../protocol'; +import { ExecConfig, ITypeScriptServiceClient, ServerResponse } from '../typescriptService'; +import API from '../utils/api'; +import { isSupportedLanguageMode } from '../utils/languageIds'; +import * as typeConverters from '../utils/typeConverters'; + +const localize = nls.loadMessageBundle(); + +namespace ExperimentalProto { + export const enum CommandTypes { + FindSourceDefinition = 'findSourceDefinition' + } + + export interface SourceDefinitionRequestArgs extends Proto.FileLocationRequestArgs { } + + export interface SourceDefinitionRequest extends Proto.Request { + command: CommandTypes.FindSourceDefinition; + arguments: SourceDefinitionRequestArgs; + } + + export interface InlayHintsResponse extends Proto.DefinitionResponse { } + + export interface IExtendedTypeScriptServiceClient { + execute( + command: K, + args: ExtendedTsServerRequests[K][0], + token: vscode.CancellationToken, + config?: ExecConfig + ): Promise>; + } + + export interface ExtendedTsServerRequests { + 'findSourceDefinition': [SourceDefinitionRequestArgs, InlayHintsResponse]; + } +} + +class SourceDefinitionCommand implements Command { + + public static readonly context = 'tsSupportsSourceDefinition'; + public static readonly minVersion = API.v470; + + public readonly id = 'typescript.goToSourceDefinition'; + + public constructor( + private readonly client: ITypeScriptServiceClient + ) { } + + public async execute() { + if (this.client.apiVersion.lt(SourceDefinitionCommand.minVersion)) { + vscode.window.showErrorMessage(localize('error.unsupportedVersion', "Go to Source Definition failed. Requires TypeScript 4.2+.")); + return; + } + + const activeEditor = vscode.window.activeTextEditor; + if (!activeEditor) { + vscode.window.showErrorMessage(localize('error.noResource', "Go to Source Definition failed. No resource provided.")); + return; + } + + const resource = activeEditor.document.uri; + const document = await vscode.workspace.openTextDocument(resource); + if (!isSupportedLanguageMode(document)) { + vscode.window.showErrorMessage(localize('error.unsupportedLanguage', "Go to Source Definition failed. Unsupported file type.")); + return; + } + + const openedFiledPath = this.client.toOpenedFilePath(document); + if (!openedFiledPath) { + vscode.window.showErrorMessage(localize('error.unknownFile', "Go to Source Definition failed. Unknown file type.")); + return; + } + + await vscode.window.withProgress({ + location: vscode.ProgressLocation.Window, + title: localize('progress.title', "Finding source definitions") + }, async (_progress, token) => { + + const position = activeEditor.selection.anchor; + const args = typeConverters.Position.toFileLocationRequestArgs(openedFiledPath, position); + const response = await (this.client as ExperimentalProto.IExtendedTypeScriptServiceClient).execute('findSourceDefinition', args, token); + if (response.type === 'response' && response.body) { + const locations: vscode.Location[] = response.body.map(reference => + typeConverters.Location.fromTextSpan(this.client.toResource(reference.file), reference)); + + if (locations.length) { + if (locations.length === 1) { + vscode.commands.executeCommand('vscode.open', locations[0].uri.with({ + fragment: `L${locations[0].range.start.line + 1},${locations[0].range.start.character + 1}` + })); + } else { + vscode.commands.executeCommand('editor.action.showReferences', resource, position, locations); + } + return; + } + } + + vscode.window.showErrorMessage(localize('error.noReferences', "No source definitions found.")); + }); + } +} + + +export function register( + client: ITypeScriptServiceClient, + commandManager: CommandManager +) { + function updateContext() { + vscode.commands.executeCommand('setContext', SourceDefinitionCommand.context, client.apiVersion.gte(SourceDefinitionCommand.minVersion)); + } + updateContext(); + + commandManager.register(new SourceDefinitionCommand(client)); + return client.onTsServerStarted(() => updateContext()); +} diff --git a/extensions/typescript-language-features/src/languageProvider.ts b/extensions/typescript-language-features/src/languageProvider.ts index 4a3669e142d..fa2ca6563b4 100644 --- a/extensions/typescript-language-features/src/languageProvider.ts +++ b/extensions/typescript-language-features/src/languageProvider.ts @@ -82,6 +82,7 @@ export default class LanguageProvider extends Disposable { import('./languageFeatures/semanticTokens').then(provider => this._register(provider.register(selector, this.client))), import('./languageFeatures/signatureHelp').then(provider => this._register(provider.register(selector, this.client))), import('./languageFeatures/smartSelect').then(provider => this._register(provider.register(selector, this.client))), + import('./languageFeatures/sourceDefinition').then(provider => this._register(provider.register(this.client, this.commandManager))), import('./languageFeatures/tagClosing').then(provider => this._register(provider.register(selector, this.description, this.client))), import('./languageFeatures/typeDefinitions').then(provider => this._register(provider.register(selector, this.client))), import('./languageFeatures/inlayHints').then(provider => this._register(provider.register(selector, this.description, this.client, this.fileConfigurationManager))), diff --git a/extensions/typescript-language-features/src/utils/api.ts b/extensions/typescript-language-features/src/utils/api.ts index e4c6133cc15..e9e85091fa4 100644 --- a/extensions/typescript-language-features/src/utils/api.ts +++ b/extensions/typescript-language-features/src/utils/api.ts @@ -39,6 +39,7 @@ export default class API { public static readonly v430 = API.fromSimpleString('4.3.0'); public static readonly v440 = API.fromSimpleString('4.4.0'); public static readonly v460 = API.fromSimpleString('4.6.0'); + public static readonly v470 = API.fromSimpleString('4.7.0'); public static fromVersionString(versionString: string): API { let version = semver.valid(versionString);