Skip to content

Commit 5e48576

Browse files
authored
Merge pull request #4581 from 333fred/gotodef-v2
Adds support for the V2 GotoDefinition endpoint
2 parents 69711c2 + 75eb800 commit 5e48576

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+715
-149
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@ jobs:
1717
- name: Setup virtual display
1818
run: /usr/bin/Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 &
1919

20-
- name: Install .NET Core 3.1 SDK
20+
- name: Install .NET Core 5.0 SDK
2121
uses: actions/[email protected]
2222
with:
23-
dotnet-version: 3.1.x
23+
dotnet-version: 5.0.x
2424

2525
- name: Install Node.js 15.x
2626
uses: actions/setup-node@v1

.vscode/launch.json

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,31 @@
153153
],
154154
"preLaunchTask": "buildDev"
155155
},
156+
{
157+
"name": "Launch slnWithGenerator Workspace Tests",
158+
"type": "extensionHost",
159+
"request": "launch",
160+
"runtimeExecutable": "${execPath}",
161+
"args": [
162+
"--disable-extensions",
163+
"${workspaceRoot}/test/integrationTests/testAssets/slnWithGenerator",
164+
"--extensionDevelopmentPath=${workspaceRoot}",
165+
"--extensionTestsPath=${workspaceRoot}/out/test/integrationTests"
166+
],
167+
"env": {
168+
"CODE_WORKSPACE_ROOT": "${workspaceRoot}",
169+
"CODE_TESTS_PATH": "${workspaceRoot}/out/test/integrationTests",
170+
"CODE_TESTS_WORKSPACE": "${workspaceRoot}/test/integrationTests/testAssets/slnWithGenerator",
171+
"CODE_EXTENSIONS_PATH": "${workspaceRoot}",
172+
"OSVC_SUITE": "slnWithGenerator"
173+
},
174+
"stopOnEntry": false,
175+
"sourceMaps": true,
176+
"outFiles": [
177+
"${workspaceRoot}/dist/*.js"
178+
],
179+
"preLaunchTask": "buildDev"
180+
},
156181
{
157182
"type": "node",
158183
"request": "launch",
@@ -169,4 +194,4 @@
169194
"cwd": "${workspaceFolder}"
170195
}
171196
]
172-
}
197+
}

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
* As a workaround, make an edit within the file before using Rename Symbol.
1313

1414
## 1.23.13 (Not yet released)
15+
* Support V2 version of GoToDefinition, which can show more than one location for partial types and show source-generated file information (PR: [#4581](https://github.com/OmniSharp/omnisharp-vscode/pull/4581))
1516
* Add command 'listRemoteDockerProcess' and variable 'pickRemoteDockerProcess' ([#4607](https://github.com/OmniSharp/omnisharp-vscode/issues/4607), PR: [#4617](https://www.github.com/OmniSharp/omnisharp-vscode/pull/4617))
1617
* Ensure we only start one instance of OmniSharp server (PR: [#4612](https://www.github.com/OmniSharp/omnisharp-vscode/pull/4612))
1718
* Update OmniSharp version to 1.37.11

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ For Windows users who have Visual Studio installed, this means you will need to
2828
For MacOS and Linux users who have Mono installed, this means you will need to set `omnisharp.useGlobalMono` to `never` until a version of Mono ships with MSBuild 16.8.
2929

3030
## What's new in 1.23.13
31+
- Support V2 version of GoToDefinition, which can show more than one location for partial types and show source-generated file information (PR: [#4581](https://github.com/OmniSharp/omnisharp-vscode/pull/4581))
3132
- Add command 'listRemoteDockerProcess' and variable 'pickRemoteDockerProcess' ([#4607](https://github.com/OmniSharp/omnisharp-vscode/issues/4607), PR: [#4617](https://www.github.com/OmniSharp/omnisharp-vscode/pull/4617))
3233
- Ensure we only start one instance of OmniSharp server (PR: [#4612](https://www.github.com/OmniSharp/omnisharp-vscode/pull/4612))
3334
- Update OmniSharp version to 1.37.11

src/common.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,9 @@ export async function execChildProcess(command: string, workingDirectory: string
7171
return new Promise<string>((resolve, reject) => {
7272
cp.exec(command, { cwd: workingDirectory, maxBuffer: 500 * 1024 }, (error, stdout, stderr) => {
7373
if (error) {
74-
reject(error);
74+
reject(`${error}
75+
${stdout}
76+
${stderr}`);
7577
}
7678
else if (stderr && !stderr.includes("screen size is bogus")) {
7779
reject(new Error(stderr));

src/features/definitionProvider.ts

Lines changed: 56 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -5,66 +5,84 @@
55

66
import * as serverUtils from '../omnisharp/utils';
77
import { CancellationToken, DefinitionProvider, Location, Position, TextDocument, Uri } from 'vscode';
8-
import { GoToDefinitionRequest, MetadataRequest, MetadataSource } from '../omnisharp/protocol';
9-
import { createRequest, toLocation, toLocationFromUri } from '../omnisharp/typeConversion';
8+
import { MetadataRequest, MetadataSource, V2 } from '../omnisharp/protocol';
9+
import { createRequest, toRange3, toVscodeLocation } from '../omnisharp/typeConversion';
1010
import AbstractSupport from './abstractProvider';
11-
import DefinitionMetadataDocumentProvider from './definitionMetadataDocumentProvider';
11+
import DefinitionMetadataOrSourceGeneratedDocumentProvider from './definitionMetadataDocumentProvider';
1212
import { OmniSharpServer } from '../omnisharp/server';
1313
import { LanguageMiddlewareFeature } from '../omnisharp/LanguageMiddlewareFeature';
14+
import SourceGeneratedDocumentProvider from './sourceGeneratedDocumentProvider';
1415

1516
export default class CSharpDefinitionProvider extends AbstractSupport implements DefinitionProvider {
16-
private _definitionMetadataDocumentProvider: DefinitionMetadataDocumentProvider;
17-
18-
constructor(server: OmniSharpServer, definitionMetadataDocumentProvider: DefinitionMetadataDocumentProvider, languageMiddlewareFeature: LanguageMiddlewareFeature) {
17+
constructor(
18+
server: OmniSharpServer,
19+
private definitionMetadataDocumentProvider: DefinitionMetadataOrSourceGeneratedDocumentProvider,
20+
private sourceGeneratedDocumentProvider: SourceGeneratedDocumentProvider,
21+
languageMiddlewareFeature: LanguageMiddlewareFeature) {
1922
super(server, languageMiddlewareFeature);
20-
21-
this._definitionMetadataDocumentProvider = definitionMetadataDocumentProvider;
2223
}
2324

2425
public async provideDefinition(document: TextDocument, position: Position, token: CancellationToken): Promise<Location[]> {
2526

26-
let req = <GoToDefinitionRequest>createRequest(document, position);
27+
let req = <V2.GoToDefinitionRequest>createRequest(document, position);
2728
req.WantMetadata = true;
2829

29-
let location: Location;
30+
const locations: Location[] = [];
3031
try {
31-
let gotoDefinitionResponse = await serverUtils.goToDefinition(this._server, req, token);
32+
const gotoDefinitionResponse = await serverUtils.goToDefinition(this._server, req, token);
3233
// the defintion is in source
33-
if (gotoDefinitionResponse && gotoDefinitionResponse.FileName) {
34+
if (gotoDefinitionResponse && gotoDefinitionResponse.Definitions) {
3435

35-
// if it is part of an already used metadata file, retrieve its uri instead of going to the physical file
36-
if (gotoDefinitionResponse.FileName.startsWith("$metadata$")) {
37-
const uri = this._definitionMetadataDocumentProvider.getExistingMetadataResponseUri(gotoDefinitionResponse.FileName);
38-
location = toLocationFromUri(uri, gotoDefinitionResponse);
39-
} else {
40-
// if it is a normal source definition, convert the response to a location
41-
location = toLocation(gotoDefinitionResponse);
42-
}
36+
for (const definition of gotoDefinitionResponse.Definitions) {
37+
if (definition.Location.FileName.startsWith("$metadata$")) {
38+
// if it is part of an already used metadata file, retrieve its uri instead of going to the physical file
39+
const uri = this.definitionMetadataDocumentProvider.getExistingMetadataResponseUri(definition.Location.FileName);
40+
const vscodeRange = toRange3(definition.Location.Range);
41+
locations.push(new Location(uri, vscodeRange));
42+
} else if (definition.MetadataSource) {
43+
// the definition is in metadata
44+
const metadataSource: MetadataSource = definition.MetadataSource;
4345

44-
// the definition is in metadata
45-
} else if (gotoDefinitionResponse.MetadataSource) {
46-
const metadataSource: MetadataSource = gotoDefinitionResponse.MetadataSource;
46+
// go to metadata endpoint for more information
47+
const metadataResponse = await serverUtils.getMetadata(this._server, <MetadataRequest>{
48+
Timeout: 5000,
49+
AssemblyName: metadataSource.AssemblyName,
50+
VersionNumber: metadataSource.VersionNumber,
51+
ProjectName: metadataSource.ProjectName,
52+
Language: metadataSource.Language,
53+
TypeName: metadataSource.TypeName
54+
});
4755

48-
// go to metadata endpoint for more information
49-
const metadataResponse = await serverUtils.getMetadata(this._server, <MetadataRequest>{
50-
Timeout: 5000,
51-
AssemblyName: metadataSource.AssemblyName,
52-
VersionNumber: metadataSource.VersionNumber,
53-
ProjectName: metadataSource.ProjectName,
54-
Language: metadataSource.Language,
55-
TypeName: metadataSource.TypeName
56-
});
56+
if (!metadataResponse || !metadataResponse.Source || !metadataResponse.SourceName) {
57+
continue;
58+
}
5759

58-
if (!metadataResponse || !metadataResponse.Source || !metadataResponse.SourceName) {
59-
return;
60-
}
60+
const uri: Uri = this.definitionMetadataDocumentProvider.addMetadataResponse(metadataResponse);
61+
const vscodeRange = toRange3(definition.Location.Range);
62+
locations.push(new Location(uri, vscodeRange));
63+
} else if (definition.SourceGeneratedFileInfo) {
64+
// File is source generated
65+
let uri = this.sourceGeneratedDocumentProvider.tryGetExistingSourceGeneratedFile(definition.SourceGeneratedFileInfo);
66+
if (!uri) {
67+
const sourceGeneratedFileResponse = await serverUtils.getSourceGeneratedFile(this._server, definition.SourceGeneratedFileInfo, token);
6168

62-
const uri: Uri = this._definitionMetadataDocumentProvider.addMetadataResponse(metadataResponse);
63-
location = new Location(uri, new Position(gotoDefinitionResponse.Line, gotoDefinitionResponse.Column));
69+
if (!sourceGeneratedFileResponse || !sourceGeneratedFileResponse.Source || !sourceGeneratedFileResponse.SourceName) {
70+
continue;
71+
}
72+
73+
uri = this.sourceGeneratedDocumentProvider.addSourceGeneratedFile(definition.SourceGeneratedFileInfo, sourceGeneratedFileResponse);
74+
}
75+
76+
locations.push(new Location(uri, toRange3(definition.Location.Range)));
77+
} else {
78+
// if it is a normal source definition, convert the response to a location
79+
locations.push(toVscodeLocation(definition.Location));
80+
}
81+
}
6482
}
6583

6684
// Allow language middlewares to re-map its edits if necessary.
67-
const result = await this._languageMiddlewareFeature.remap("remapLocations", [location], token);
85+
const result = await this._languageMiddlewareFeature.remap("remapLocations", locations, token);
6886
return result;
6987
}
7088
catch (error) {
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
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 serverUtils from '../omnisharp/utils';
7+
import { Event, EventEmitter, TextDocument, TextDocumentContentProvider, TextEditor, Uri, window, workspace } from 'vscode';
8+
import { IDisposable } from '../Disposable';
9+
import { SourceGeneratedFileInfo, SourceGeneratedFileResponse, UpdateType } from '../omnisharp/protocol';
10+
import { OmniSharpServer } from '../omnisharp/server';
11+
12+
export default class SourceGeneratedDocumentProvider implements TextDocumentContentProvider, IDisposable {
13+
readonly scheme = "omnisharp-source-generated";
14+
private _registration: IDisposable;
15+
private _documents: Map<SourceGeneratedFileInfo, SourceGeneratedFileResponse>;
16+
private _uriToDocumentInfo: Map<string, SourceGeneratedFileInfo>;
17+
private _documentClosedSubscription: IDisposable;
18+
private _visibleTextEditorsChangedSubscription: IDisposable;
19+
private _onDidChangeEmitter: EventEmitter<Uri>;
20+
public onDidChange: Event<Uri>;
21+
22+
constructor(private server: OmniSharpServer) {
23+
this._documents = new Map<SourceGeneratedFileInfo, SourceGeneratedFileResponse>();
24+
this._uriToDocumentInfo = new Map<string, SourceGeneratedFileInfo>();
25+
this._documentClosedSubscription = workspace.onDidCloseTextDocument(this.onTextDocumentClosed, this);
26+
this._visibleTextEditorsChangedSubscription = window.onDidChangeVisibleTextEditors(this.onVisibleTextEditorsChanged, this);
27+
this._onDidChangeEmitter = new EventEmitter<Uri>();
28+
this.onDidChange = this._onDidChangeEmitter.event;
29+
}
30+
31+
private async onTextDocumentClosed(document: TextDocument) {
32+
const uriString = document.uri.toString();
33+
if (this._uriToDocumentInfo.has(uriString)) {
34+
const info = this._uriToDocumentInfo.get(uriString);
35+
this._documents.delete(info);
36+
this._uriToDocumentInfo.delete(uriString);
37+
await serverUtils.sourceGeneratedFileClosed(this.server, info);
38+
}
39+
}
40+
41+
private async onVisibleTextEditorsChanged(editors?: TextEditor[]) {
42+
for (const editor of editors) {
43+
const documentUri = editor.document.uri;
44+
const uriString = documentUri.toString();
45+
if (this._uriToDocumentInfo.has(uriString)) {
46+
try {
47+
const existingInfo = this._uriToDocumentInfo.get(uriString);
48+
const existingResponse = this._documents.get(existingInfo);
49+
const update = await serverUtils.getUpdatedSourceGeneratedFile(this.server, existingInfo);
50+
if (!update) {
51+
continue;
52+
}
53+
54+
switch (update.UpdateType) {
55+
case UpdateType.Deleted:
56+
this._documents.set(existingInfo, { Source: "Document is no longer being generated.", SourceName: existingResponse.SourceName });
57+
break;
58+
case UpdateType.Modified:
59+
this._documents.set(existingInfo, { Source: update.Source, SourceName: existingResponse.SourceName });
60+
break;
61+
case UpdateType.Unchanged:
62+
continue;
63+
}
64+
65+
this._onDidChangeEmitter.fire(documentUri);
66+
} catch {
67+
continue;
68+
}
69+
}
70+
}
71+
}
72+
73+
public register(): void {
74+
this._registration = workspace.registerTextDocumentContentProvider(this.scheme, this);
75+
}
76+
77+
public dispose() {
78+
this._registration.dispose();
79+
this._documentClosedSubscription.dispose();
80+
this._visibleTextEditorsChangedSubscription.dispose();
81+
this._documents.clear();
82+
}
83+
84+
public tryGetExistingSourceGeneratedFile(fileInfo: SourceGeneratedFileInfo): Uri | undefined {
85+
if (this._documents.has(fileInfo)) {
86+
return this.getUriForName(this._documents.get(fileInfo).SourceName);
87+
}
88+
89+
return undefined;
90+
}
91+
92+
public addSourceGeneratedFile(fileInfo: SourceGeneratedFileInfo, response: SourceGeneratedFileResponse): Uri {
93+
if (this._documents.has(fileInfo)) {
94+
// Raced with something, return the existing one
95+
return this.tryGetExistingSourceGeneratedFile(fileInfo);
96+
}
97+
98+
const uri = this.getUriForName(response.SourceName);
99+
const uriString = uri.toString();
100+
101+
let triggerUpdate = false;
102+
103+
if (this._uriToDocumentInfo.has(uriString)) {
104+
// Old version of the file in the cache. Remove it, and after it's replaced trigger vscode to update the file.
105+
this._documents.delete(fileInfo);
106+
this._uriToDocumentInfo.delete(uriString);
107+
triggerUpdate = true;
108+
}
109+
110+
this._documents.set(fileInfo, response);
111+
this._uriToDocumentInfo.set(uriString, fileInfo);
112+
113+
if (triggerUpdate) {
114+
this._onDidChangeEmitter.fire(uri);
115+
}
116+
117+
return uri;
118+
}
119+
120+
public provideTextDocumentContent(uri: Uri): string {
121+
return this._documents.get(this._uriToDocumentInfo.get(uri.toString())).Source;
122+
}
123+
124+
private getUriForName(sourceName: string): Uri {
125+
return Uri.parse(this.scheme + ":///" + sourceName);
126+
}
127+
}

src/omnisharp/extension.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import { getMonoVersion } from '../utils/getMonoVersion';
4141
import { FixAllProvider } from '../features/fixAllProvider';
4242
import { LanguageMiddlewareFeature } from './LanguageMiddlewareFeature';
4343
import SemanticTokensProvider from '../features/semanticTokensProvider';
44+
import SourceGeneratedDocumentProvider from '../features/sourceGeneratedDocumentProvider';
4445

4546
export interface ActivationResult {
4647
readonly server: OmniSharpServer;
@@ -72,7 +73,10 @@ export async function activate(context: vscode.ExtensionContext, packageJSON: an
7273
const definitionMetadataDocumentProvider = new DefinitionMetadataDocumentProvider();
7374
definitionMetadataDocumentProvider.register();
7475
localDisposables.add(definitionMetadataDocumentProvider);
75-
const definitionProvider = new DefinitionProvider(server, definitionMetadataDocumentProvider, languageMiddlewareFeature);
76+
const sourceGeneratedDocumentProvider = new SourceGeneratedDocumentProvider(server);
77+
sourceGeneratedDocumentProvider.register();
78+
localDisposables.add(sourceGeneratedDocumentProvider);
79+
const definitionProvider = new DefinitionProvider(server, definitionMetadataDocumentProvider, sourceGeneratedDocumentProvider, languageMiddlewareFeature);
7680
localDisposables.add(vscode.languages.registerDefinitionProvider(documentSelector, definitionProvider));
7781
localDisposables.add(vscode.languages.registerDefinitionProvider({ scheme: definitionMetadataDocumentProvider.scheme }, definitionProvider));
7882
localDisposables.add(vscode.languages.registerImplementationProvider(documentSelector, new ImplementationProvider(server, languageMiddlewareFeature)));

src/omnisharp/prioritization.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const normalCommands = [
1919
protocol.Requests.FindSymbols,
2020
protocol.Requests.FindUsages,
2121
protocol.Requests.GetCodeActions,
22-
protocol.Requests.GoToDefinition,
22+
protocol.V2.Requests.GoToDefinition,
2323
protocol.Requests.RunCodeAction,
2424
protocol.Requests.SignatureHelp,
2525
protocol.Requests.TypeLookup
@@ -58,4 +58,4 @@ export function isDeferredCommand(command: string) {
5858

5959
deferredSet.add(command);
6060
return true;
61-
}
61+
}

0 commit comments

Comments
 (0)