Skip to content

Commit e316b14

Browse files
authored
Merge pull request #5094 from jtschuster/gototypedef
Add GoToTypeDefinition provider
2 parents adcd932 + e7f7e55 commit e316b14

File tree

9 files changed

+231
-55
lines changed

9 files changed

+231
-55
lines changed

src/features/definitionProvider.ts

Lines changed: 67 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,16 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import * as serverUtils from '../omnisharp/utils';
7-
import { CancellationToken, DefinitionProvider, Location, Position, TextDocument, Uri } from 'vscode';
8-
import { MetadataRequest, MetadataSource, V2 } from '../omnisharp/protocol';
7+
import { CancellationToken, TypeDefinitionProvider, DefinitionProvider, Location, Position, TextDocument, Uri } from 'vscode';
8+
import { GoToTypeDefinitionRequest, GoToTypeDefinitionResponse, MetadataRequest, MetadataSource, V2 } from '../omnisharp/protocol';
99
import { createRequest, toRange3, toVscodeLocation } from '../omnisharp/typeConversion';
1010
import AbstractSupport from './abstractProvider';
1111
import DefinitionMetadataOrSourceGeneratedDocumentProvider from './definitionMetadataDocumentProvider';
1212
import { OmniSharpServer } from '../omnisharp/server';
1313
import { LanguageMiddlewareFeature } from '../omnisharp/LanguageMiddlewareFeature';
1414
import SourceGeneratedDocumentProvider from './sourceGeneratedDocumentProvider';
1515

16-
export default class CSharpDefinitionProvider extends AbstractSupport implements DefinitionProvider {
16+
export default class CSharpDefinitionProvider extends AbstractSupport implements DefinitionProvider, TypeDefinitionProvider {
1717
constructor(
1818
server: OmniSharpServer,
1919
private definitionMetadataDocumentProvider: DefinitionMetadataOrSourceGeneratedDocumentProvider,
@@ -23,76 +23,89 @@ export default class CSharpDefinitionProvider extends AbstractSupport implements
2323
}
2424

2525
public async provideDefinition(document: TextDocument, position: Position, token: CancellationToken): Promise<Location[]> {
26-
2726
let req = <V2.GoToDefinitionRequest>createRequest(document, position);
2827
req.WantMetadata = true;
2928

30-
const locations: Location[] = [];
3129
try {
3230
const gotoDefinitionResponse = await serverUtils.goToDefinition(this._server, req, token);
33-
// the defintion is in source
34-
if (gotoDefinitionResponse && gotoDefinitionResponse.Definitions) {
35-
36-
for (const definition of gotoDefinitionResponse.Definitions) {
37-
if (definition.MetadataSource) {
38-
// the definition is in metadata
39-
const metadataSource: MetadataSource = definition.MetadataSource;
31+
return await this.GetLocationsFromResponse(gotoDefinitionResponse, token);
32+
}
33+
catch (error) {
34+
return [];
35+
}
36+
}
4037

41-
// Do we already have a document for this metadata reference?
42-
if (definition.Location.FileName.startsWith("$metadata$") &&
43-
this.definitionMetadataDocumentProvider.hasMetadataDocument(definition.Location.FileName)) {
38+
public async provideTypeDefinition(document: TextDocument, position: Position, token: CancellationToken): Promise<Location[]> {
39+
let req = <GoToTypeDefinitionRequest>createRequest(document, position);
40+
req.WantMetadata = true;
4441

45-
// if it is part of an already used metadata file, retrieve its uri instead of going to the physical file
46-
const uri = this.definitionMetadataDocumentProvider.getExistingMetadataResponseUri(definition.Location.FileName);
47-
const vscodeRange = toRange3(definition.Location.Range);
48-
locations.push(new Location(uri, vscodeRange));
49-
continue;
50-
}
42+
try {
43+
const goToTypeDefinitionResponse = await serverUtils.goToTypeDefinition(this._server, req, token);
44+
return await this.GetLocationsFromResponse(goToTypeDefinitionResponse, token);
45+
}
46+
catch (error) {
47+
return [];
48+
}
49+
}
5150

52-
// We need to go to the metadata endpoint for more information
53-
const metadataResponse = await serverUtils.getMetadata(this._server, <MetadataRequest>{
54-
Timeout: 5000,
55-
AssemblyName: metadataSource.AssemblyName,
56-
VersionNumber: metadataSource.VersionNumber,
57-
ProjectName: metadataSource.ProjectName,
58-
Language: metadataSource.Language,
59-
TypeName: metadataSource.TypeName
60-
});
51+
private async GetLocationsFromResponse<TReponse> (response: GoToTypeDefinitionResponse | V2.GoToDefinitionResponse, token: CancellationToken): Promise<Location[]>
52+
{
53+
let locations: Location[] = [];
54+
if (response && response.Definitions) {
55+
for (const definition of response.Definitions) {
56+
if (definition.MetadataSource) {
57+
// the definition is in metadata
58+
const metadataSource: MetadataSource = definition.MetadataSource;
6159

62-
if (!metadataResponse || !metadataResponse.Source || !metadataResponse.SourceName) {
63-
continue;
64-
}
60+
// Do we already have a document for this metadata reference?
61+
if (definition.Location.FileName.startsWith("$metadata$") &&
62+
this.definitionMetadataDocumentProvider.hasMetadataDocument(definition.Location.FileName)) {
6563

66-
const uri: Uri = this.definitionMetadataDocumentProvider.addMetadataResponse(metadataResponse);
64+
// if it is part of an already used metadata file, retrieve its uri instead of going to the physical file
65+
const uri = this.definitionMetadataDocumentProvider.getExistingMetadataResponseUri(definition.Location.FileName);
6766
const vscodeRange = toRange3(definition.Location.Range);
6867
locations.push(new Location(uri, vscodeRange));
69-
} else if (definition.SourceGeneratedFileInfo) {
70-
// File is source generated
71-
let uri = this.sourceGeneratedDocumentProvider.tryGetExistingSourceGeneratedFile(definition.SourceGeneratedFileInfo);
72-
if (!uri) {
73-
const sourceGeneratedFileResponse = await serverUtils.getSourceGeneratedFile(this._server, definition.SourceGeneratedFileInfo, token);
68+
continue;
69+
}
70+
71+
// We need to go to the metadata endpoint for more information
72+
const metadataResponse = await serverUtils.getMetadata(this._server, <MetadataRequest>{
73+
Timeout: 5000,
74+
AssemblyName: metadataSource.AssemblyName,
75+
VersionNumber: metadataSource.VersionNumber,
76+
ProjectName: metadataSource.ProjectName,
77+
Language: metadataSource.Language,
78+
TypeName: metadataSource.TypeName
79+
});
80+
81+
if (!metadataResponse || !metadataResponse.Source || !metadataResponse.SourceName) {
82+
continue;
83+
}
7484

75-
if (!sourceGeneratedFileResponse || !sourceGeneratedFileResponse.Source || !sourceGeneratedFileResponse.SourceName) {
76-
continue;
77-
}
85+
const uri: Uri = this.definitionMetadataDocumentProvider.addMetadataResponse(metadataResponse);
86+
const vscodeRange = toRange3(definition.Location.Range);
87+
locations.push(new Location(uri, vscodeRange));
88+
} else if (definition.SourceGeneratedFileInfo) {
89+
// File is source generated
90+
let uri = this.sourceGeneratedDocumentProvider.tryGetExistingSourceGeneratedFile(definition.SourceGeneratedFileInfo);
91+
if (!uri) {
92+
const sourceGeneratedFileResponse = await serverUtils.getSourceGeneratedFile(this._server, definition.SourceGeneratedFileInfo, token);
7893

79-
uri = this.sourceGeneratedDocumentProvider.addSourceGeneratedFile(definition.SourceGeneratedFileInfo, sourceGeneratedFileResponse);
94+
if (!sourceGeneratedFileResponse || !sourceGeneratedFileResponse.Source || !sourceGeneratedFileResponse.SourceName) {
95+
continue;
8096
}
8197

82-
locations.push(new Location(uri, toRange3(definition.Location.Range)));
83-
} else {
84-
// if it is a normal source definition, convert the response to a location
85-
locations.push(toVscodeLocation(definition.Location));
98+
uri = this.sourceGeneratedDocumentProvider.addSourceGeneratedFile(definition.SourceGeneratedFileInfo, sourceGeneratedFileResponse);
8699
}
100+
101+
locations.push(new Location(uri, toRange3(definition.Location.Range)));
102+
} else {
103+
// if it is a normal source definition, convert the response to a location
104+
locations.push(toVscodeLocation(definition.Location));
87105
}
88106
}
89-
90-
// Allow language middlewares to re-map its edits if necessary.
91-
const result = await this._languageMiddlewareFeature.remap("remapLocations", locations, token);
92-
return result;
93-
}
94-
catch (error) {
95-
return [];
96107
}
108+
// Allow language middlewares to re-map its edits if necessary.
109+
return await this._languageMiddlewareFeature.remap("remapLocations", locations, token);
97110
}
98111
}

src/omnisharp/extension.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ export async function activate(context: vscode.ExtensionContext, packageJSON: an
8383
const definitionProvider = new DefinitionProvider(server, definitionMetadataDocumentProvider, sourceGeneratedDocumentProvider, languageMiddlewareFeature);
8484
localDisposables.add(vscode.languages.registerDefinitionProvider(documentSelector, definitionProvider));
8585
localDisposables.add(vscode.languages.registerDefinitionProvider({ scheme: definitionMetadataDocumentProvider.scheme }, definitionProvider));
86+
localDisposables.add(vscode.languages.registerTypeDefinitionProvider(documentSelector, definitionProvider));
87+
localDisposables.add(vscode.languages.registerTypeDefinitionProvider({ scheme: definitionMetadataDocumentProvider.scheme }, definitionProvider));
8688
localDisposables.add(vscode.languages.registerImplementationProvider(documentSelector, new ImplementationProvider(server, languageMiddlewareFeature)));
8789
localDisposables.add(vscode.languages.registerCodeLensProvider(documentSelector, new CodeLensProvider(server, testManager, optionProvider, languageMiddlewareFeature)));
8890
localDisposables.add(vscode.languages.registerDocumentHighlightProvider(documentSelector, new DocumentHighlightProvider(server, languageMiddlewareFeature)));

src/omnisharp/prioritization.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ const normalCommands = [
2222
protocol.V2.Requests.GoToDefinition,
2323
protocol.Requests.RunCodeAction,
2424
protocol.Requests.SignatureHelp,
25-
protocol.Requests.TypeLookup
25+
protocol.Requests.TypeLookup,
26+
protocol.Requests.GoToTypeDefinition
2627
];
2728

2829
const prioritySet = new Set<string>(priorityCommands);

src/omnisharp/protocol.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export module Requests {
1717
export const FormatAfterKeystroke = '/formatAfterKeystroke';
1818
export const FormatRange = '/formatRange';
1919
export const GetCodeActions = '/getcodeactions';
20+
export const GoToTypeDefinition = '/gototypedefinition';
2021
export const FindImplementations = '/findimplementations';
2122
export const Project = '/project';
2223
export const Projects = '/projects';
@@ -580,6 +581,21 @@ export enum UpdateType {
580581
export interface SourceGeneratedFileClosedRequest extends SourceGeneratedFileInfo {
581582
}
582583

584+
export interface Definition {
585+
Location: V2.Location;
586+
MetadataSource?: MetadataSource;
587+
SourceGeneratedFileInfo?: SourceGeneratedFileInfo;
588+
}
589+
590+
export interface GoToTypeDefinitionRequest extends Request {
591+
WantMetadata?: boolean;
592+
}
593+
594+
export interface GoToTypeDefinitionResponse {
595+
Definitions?: Definition[];
596+
}
597+
598+
583599
export namespace V2 {
584600

585601
export module Requests {

src/omnisharp/utils.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ export async function goToDefinition(server: OmniSharpServer, request: protocol.
6767
return server.makeRequest<protocol.V2.GoToDefinitionResponse>(protocol.V2.Requests.GoToDefinition, request, token);
6868
}
6969

70+
export async function goToTypeDefinition(server: OmniSharpServer, request: protocol.GoToTypeDefinitionRequest, token: vscode.CancellationToken) {
71+
return server.makeRequest<protocol.GoToTypeDefinitionResponse>(protocol.Requests.GoToTypeDefinition, request, token);
72+
}
73+
7074
export async function getSourceGeneratedFile(server: OmniSharpServer, request: protocol.SourceGeneratedFileRequest, token: vscode.CancellationToken) {
7175
return server.makeRequest<protocol.SourceGeneratedFileResponse>(protocol.Requests.SourceGeneratedFile, request, token);
7276
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
2+
using System;
3+
4+
namespace Test
5+
{
6+
public class LinkedList
7+
{
8+
public void MyMethod()
9+
{
10+
var linked = new LinkedList();
11+
var str = "test string";
12+
var part = new PartialClass();
13+
Console.WriteLine(str);
14+
}
15+
}
16+
17+
public partial class PartialClass {
18+
public string Foo {get; set;};
19+
}
20+
21+
public partial class PartialClass {
22+
public int Bar {get; set;}
23+
}
24+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
2+
using System;
3+
4+
namespace Test
5+
{
6+
public class LinkedList
7+
{
8+
public void MyMethod()
9+
{
10+
var linked = new LinkedList();
11+
var str = "test string";
12+
var part = new PartialClass();
13+
Console.WriteLine(str);
14+
}
15+
}
16+
17+
public partial class PartialClass {
18+
public string Foo {get; set;};
19+
}
20+
21+
public partial class PartialClass {
22+
public int Bar {get; set;}
23+
}
24+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
2+
using System;
3+
4+
namespace Test
5+
{
6+
public class LinkedList
7+
{
8+
public void MyMethod()
9+
{
10+
var linked = new LinkedList();
11+
var str = "test string";
12+
var part = new PartialClass();
13+
Console.WriteLine(str);
14+
}
15+
}
16+
17+
public partial class PartialClass {
18+
public string Foo {get; set;};
19+
}
20+
21+
public partial class PartialClass {
22+
public int Bar {get; set;}
23+
}
24+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import * as vscode from "vscode";
7+
import CSharpDefinitionProvider from "../../src/features/definitionProvider";
8+
import * as path from "path";
9+
import testAssetWorkspace from "./testAssets/testAssetWorkspace";
10+
import { expect, should } from "chai";
11+
import { activateCSharpExtension, isRazorWorkspace, isSlnWithGenerator, restartOmniSharpServer } from './integrationHelpers';
12+
13+
suite(`${CSharpDefinitionProvider.name}: ${testAssetWorkspace.description}`, () => {
14+
let fileUri: vscode.Uri;
15+
16+
suiteSetup(async function () {
17+
should();
18+
19+
if (isRazorWorkspace(vscode.workspace) || isSlnWithGenerator(vscode.workspace)) {
20+
this.skip();
21+
}
22+
23+
const activation = await activateCSharpExtension();
24+
await testAssetWorkspace.restore();
25+
26+
const fileName = 'typeDefinition.cs';
27+
const projectDirectory = testAssetWorkspace.projects[0].projectDirectoryPath;
28+
fileUri = vscode.Uri.file(path.join(projectDirectory, fileName));
29+
await vscode.commands.executeCommand("vscode.open", fileUri);
30+
31+
await testAssetWorkspace.waitForIdle(activation.eventStream);
32+
});
33+
34+
suiteTeardown(async () => {
35+
await testAssetWorkspace.cleanupWorkspace();
36+
});
37+
38+
test("Returns the type definition", async () => {
39+
const definitionList = <vscode.Location[]>(await vscode.commands.executeCommand("vscode.executeTypeDefinitionProvider", fileUri, new vscode.Position(9, 18)));
40+
expect(definitionList.length).to.be.equal(1);
41+
expect(definitionList[0]).to.exist;
42+
expect(definitionList[0].uri.path).to.contain("typeDefinition.cs");
43+
});
44+
45+
test("Returns the definition from Metadata", async () => {
46+
const omnisharpConfig = vscode.workspace.getConfiguration('omnisharp');
47+
await omnisharpConfig.update('enableDecompilationSupport', false, vscode.ConfigurationTarget.Global);
48+
await restartOmniSharpServer();
49+
50+
const definitionList = <vscode.Location[]>(await vscode.commands.executeCommand("vscode.executeTypeDefinitionProvider", fileUri, new vscode.Position(10, 18)));
51+
expect(definitionList.length).to.be.equal(1);
52+
expect(definitionList[0]).to.exist;
53+
expect(definitionList[0].uri.path).to.contain("[metadata] String.cs");
54+
});
55+
56+
test("Returns multiple definitions for partial types", async () => {
57+
const definitionList = <vscode.Location[]>(await vscode.commands.executeCommand("vscode.executeTypeDefinitionProvider", fileUri, new vscode.Position(11, 18)));
58+
expect(definitionList.length).eq(2);
59+
expect(definitionList[0]).to.exist;
60+
expect(definitionList[0].uri.path).to.contain("typeDefinition.cs");
61+
expect(definitionList[1]).to.exist;
62+
expect(definitionList[1].uri.path).to.contain("typeDefinition.cs");
63+
});
64+
65+
suiteTeardown(async () => {
66+
await testAssetWorkspace.cleanupWorkspace();
67+
});
68+
});

0 commit comments

Comments
 (0)