Skip to content

Commit

Permalink
Merge pull request #4581 from 333fred/gotodef-v2
Browse files Browse the repository at this point in the history
Adds support for the V2 GotoDefinition endpoint
  • Loading branch information
JoeRobich authored Jul 6, 2021
2 parents 69711c2 + 75eb800 commit 5e48576
Show file tree
Hide file tree
Showing 50 changed files with 715 additions and 149 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ jobs:
- name: Setup virtual display
run: /usr/bin/Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 &

- name: Install .NET Core 3.1 SDK
- name: Install .NET Core 5.0 SDK
uses: actions/[email protected]
with:
dotnet-version: 3.1.x
dotnet-version: 5.0.x

- name: Install Node.js 15.x
uses: actions/setup-node@v1
Expand Down
27 changes: 26 additions & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,31 @@
],
"preLaunchTask": "buildDev"
},
{
"name": "Launch slnWithGenerator Workspace Tests",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": [
"--disable-extensions",
"${workspaceRoot}/test/integrationTests/testAssets/slnWithGenerator",
"--extensionDevelopmentPath=${workspaceRoot}",
"--extensionTestsPath=${workspaceRoot}/out/test/integrationTests"
],
"env": {
"CODE_WORKSPACE_ROOT": "${workspaceRoot}",
"CODE_TESTS_PATH": "${workspaceRoot}/out/test/integrationTests",
"CODE_TESTS_WORKSPACE": "${workspaceRoot}/test/integrationTests/testAssets/slnWithGenerator",
"CODE_EXTENSIONS_PATH": "${workspaceRoot}",
"OSVC_SUITE": "slnWithGenerator"
},
"stopOnEntry": false,
"sourceMaps": true,
"outFiles": [
"${workspaceRoot}/dist/*.js"
],
"preLaunchTask": "buildDev"
},
{
"type": "node",
"request": "launch",
Expand All @@ -169,4 +194,4 @@
"cwd": "${workspaceFolder}"
}
]
}
}
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
* As a workaround, make an edit within the file before using Rename Symbol.

## 1.23.13 (Not yet released)
* 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))
* Add command 'listRemoteDockerProcess' and variable 'pickRemoteDockerProcess' ([#4607](https://github.com/OmniSharp/omnisharp-vscode/issues/4607), PR: [#4617](https://github.com/OmniSharp/omnisharp-vscode/pull/4617))
* Ensure we only start one instance of OmniSharp server (PR: [#4612](https://github.com/OmniSharp/omnisharp-vscode/pull/4612))
* Update OmniSharp version to 1.37.11
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ For Windows users who have Visual Studio installed, this means you will need to
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.

## What's new in 1.23.13
- 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))
- Add command 'listRemoteDockerProcess' and variable 'pickRemoteDockerProcess' ([#4607](https://github.com/OmniSharp/omnisharp-vscode/issues/4607), PR: [#4617](https://github.com/OmniSharp/omnisharp-vscode/pull/4617))
- Ensure we only start one instance of OmniSharp server (PR: [#4612](https://github.com/OmniSharp/omnisharp-vscode/pull/4612))
- Update OmniSharp version to 1.37.11
Expand Down
4 changes: 3 additions & 1 deletion src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,9 @@ export async function execChildProcess(command: string, workingDirectory: string
return new Promise<string>((resolve, reject) => {
cp.exec(command, { cwd: workingDirectory, maxBuffer: 500 * 1024 }, (error, stdout, stderr) => {
if (error) {
reject(error);
reject(`${error}
${stdout}
${stderr}`);
}
else if (stderr && !stderr.includes("screen size is bogus")) {
reject(new Error(stderr));
Expand Down
94 changes: 56 additions & 38 deletions src/features/definitionProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,66 +5,84 @@

import * as serverUtils from '../omnisharp/utils';
import { CancellationToken, DefinitionProvider, Location, Position, TextDocument, Uri } from 'vscode';
import { GoToDefinitionRequest, MetadataRequest, MetadataSource } from '../omnisharp/protocol';
import { createRequest, toLocation, toLocationFromUri } from '../omnisharp/typeConversion';
import { MetadataRequest, MetadataSource, V2 } from '../omnisharp/protocol';
import { createRequest, toRange3, toVscodeLocation } from '../omnisharp/typeConversion';
import AbstractSupport from './abstractProvider';
import DefinitionMetadataDocumentProvider from './definitionMetadataDocumentProvider';
import DefinitionMetadataOrSourceGeneratedDocumentProvider from './definitionMetadataDocumentProvider';
import { OmniSharpServer } from '../omnisharp/server';
import { LanguageMiddlewareFeature } from '../omnisharp/LanguageMiddlewareFeature';
import SourceGeneratedDocumentProvider from './sourceGeneratedDocumentProvider';

export default class CSharpDefinitionProvider extends AbstractSupport implements DefinitionProvider {
private _definitionMetadataDocumentProvider: DefinitionMetadataDocumentProvider;

constructor(server: OmniSharpServer, definitionMetadataDocumentProvider: DefinitionMetadataDocumentProvider, languageMiddlewareFeature: LanguageMiddlewareFeature) {
constructor(
server: OmniSharpServer,
private definitionMetadataDocumentProvider: DefinitionMetadataOrSourceGeneratedDocumentProvider,
private sourceGeneratedDocumentProvider: SourceGeneratedDocumentProvider,
languageMiddlewareFeature: LanguageMiddlewareFeature) {
super(server, languageMiddlewareFeature);

this._definitionMetadataDocumentProvider = definitionMetadataDocumentProvider;
}

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

let req = <GoToDefinitionRequest>createRequest(document, position);
let req = <V2.GoToDefinitionRequest>createRequest(document, position);
req.WantMetadata = true;

let location: Location;
const locations: Location[] = [];
try {
let gotoDefinitionResponse = await serverUtils.goToDefinition(this._server, req, token);
const gotoDefinitionResponse = await serverUtils.goToDefinition(this._server, req, token);
// the defintion is in source
if (gotoDefinitionResponse && gotoDefinitionResponse.FileName) {
if (gotoDefinitionResponse && gotoDefinitionResponse.Definitions) {

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

// the definition is in metadata
} else if (gotoDefinitionResponse.MetadataSource) {
const metadataSource: MetadataSource = gotoDefinitionResponse.MetadataSource;
// go to metadata endpoint for more information
const metadataResponse = await serverUtils.getMetadata(this._server, <MetadataRequest>{
Timeout: 5000,
AssemblyName: metadataSource.AssemblyName,
VersionNumber: metadataSource.VersionNumber,
ProjectName: metadataSource.ProjectName,
Language: metadataSource.Language,
TypeName: metadataSource.TypeName
});

// go to metadata endpoint for more information
const metadataResponse = await serverUtils.getMetadata(this._server, <MetadataRequest>{
Timeout: 5000,
AssemblyName: metadataSource.AssemblyName,
VersionNumber: metadataSource.VersionNumber,
ProjectName: metadataSource.ProjectName,
Language: metadataSource.Language,
TypeName: metadataSource.TypeName
});
if (!metadataResponse || !metadataResponse.Source || !metadataResponse.SourceName) {
continue;
}

if (!metadataResponse || !metadataResponse.Source || !metadataResponse.SourceName) {
return;
}
const uri: Uri = this.definitionMetadataDocumentProvider.addMetadataResponse(metadataResponse);
const vscodeRange = toRange3(definition.Location.Range);
locations.push(new Location(uri, vscodeRange));
} else if (definition.SourceGeneratedFileInfo) {
// File is source generated
let uri = this.sourceGeneratedDocumentProvider.tryGetExistingSourceGeneratedFile(definition.SourceGeneratedFileInfo);
if (!uri) {
const sourceGeneratedFileResponse = await serverUtils.getSourceGeneratedFile(this._server, definition.SourceGeneratedFileInfo, token);

const uri: Uri = this._definitionMetadataDocumentProvider.addMetadataResponse(metadataResponse);
location = new Location(uri, new Position(gotoDefinitionResponse.Line, gotoDefinitionResponse.Column));
if (!sourceGeneratedFileResponse || !sourceGeneratedFileResponse.Source || !sourceGeneratedFileResponse.SourceName) {
continue;
}

uri = this.sourceGeneratedDocumentProvider.addSourceGeneratedFile(definition.SourceGeneratedFileInfo, sourceGeneratedFileResponse);
}

locations.push(new Location(uri, toRange3(definition.Location.Range)));
} else {
// if it is a normal source definition, convert the response to a location
locations.push(toVscodeLocation(definition.Location));
}
}
}

// Allow language middlewares to re-map its edits if necessary.
const result = await this._languageMiddlewareFeature.remap("remapLocations", [location], token);
const result = await this._languageMiddlewareFeature.remap("remapLocations", locations, token);
return result;
}
catch (error) {
Expand Down
127 changes: 127 additions & 0 deletions src/features/sourceGeneratedDocumentProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import * as serverUtils from '../omnisharp/utils';
import { Event, EventEmitter, TextDocument, TextDocumentContentProvider, TextEditor, Uri, window, workspace } from 'vscode';
import { IDisposable } from '../Disposable';
import { SourceGeneratedFileInfo, SourceGeneratedFileResponse, UpdateType } from '../omnisharp/protocol';
import { OmniSharpServer } from '../omnisharp/server';

export default class SourceGeneratedDocumentProvider implements TextDocumentContentProvider, IDisposable {
readonly scheme = "omnisharp-source-generated";
private _registration: IDisposable;
private _documents: Map<SourceGeneratedFileInfo, SourceGeneratedFileResponse>;
private _uriToDocumentInfo: Map<string, SourceGeneratedFileInfo>;
private _documentClosedSubscription: IDisposable;
private _visibleTextEditorsChangedSubscription: IDisposable;
private _onDidChangeEmitter: EventEmitter<Uri>;
public onDidChange: Event<Uri>;

constructor(private server: OmniSharpServer) {
this._documents = new Map<SourceGeneratedFileInfo, SourceGeneratedFileResponse>();
this._uriToDocumentInfo = new Map<string, SourceGeneratedFileInfo>();
this._documentClosedSubscription = workspace.onDidCloseTextDocument(this.onTextDocumentClosed, this);
this._visibleTextEditorsChangedSubscription = window.onDidChangeVisibleTextEditors(this.onVisibleTextEditorsChanged, this);
this._onDidChangeEmitter = new EventEmitter<Uri>();
this.onDidChange = this._onDidChangeEmitter.event;
}

private async onTextDocumentClosed(document: TextDocument) {
const uriString = document.uri.toString();
if (this._uriToDocumentInfo.has(uriString)) {
const info = this._uriToDocumentInfo.get(uriString);
this._documents.delete(info);
this._uriToDocumentInfo.delete(uriString);
await serverUtils.sourceGeneratedFileClosed(this.server, info);
}
}

private async onVisibleTextEditorsChanged(editors?: TextEditor[]) {
for (const editor of editors) {
const documentUri = editor.document.uri;
const uriString = documentUri.toString();
if (this._uriToDocumentInfo.has(uriString)) {
try {
const existingInfo = this._uriToDocumentInfo.get(uriString);
const existingResponse = this._documents.get(existingInfo);
const update = await serverUtils.getUpdatedSourceGeneratedFile(this.server, existingInfo);
if (!update) {
continue;
}

switch (update.UpdateType) {
case UpdateType.Deleted:
this._documents.set(existingInfo, { Source: "Document is no longer being generated.", SourceName: existingResponse.SourceName });
break;
case UpdateType.Modified:
this._documents.set(existingInfo, { Source: update.Source, SourceName: existingResponse.SourceName });
break;
case UpdateType.Unchanged:
continue;
}

this._onDidChangeEmitter.fire(documentUri);
} catch {
continue;
}
}
}
}

public register(): void {
this._registration = workspace.registerTextDocumentContentProvider(this.scheme, this);
}

public dispose() {
this._registration.dispose();
this._documentClosedSubscription.dispose();
this._visibleTextEditorsChangedSubscription.dispose();
this._documents.clear();
}

public tryGetExistingSourceGeneratedFile(fileInfo: SourceGeneratedFileInfo): Uri | undefined {
if (this._documents.has(fileInfo)) {
return this.getUriForName(this._documents.get(fileInfo).SourceName);
}

return undefined;
}

public addSourceGeneratedFile(fileInfo: SourceGeneratedFileInfo, response: SourceGeneratedFileResponse): Uri {
if (this._documents.has(fileInfo)) {
// Raced with something, return the existing one
return this.tryGetExistingSourceGeneratedFile(fileInfo);
}

const uri = this.getUriForName(response.SourceName);
const uriString = uri.toString();

let triggerUpdate = false;

if (this._uriToDocumentInfo.has(uriString)) {
// Old version of the file in the cache. Remove it, and after it's replaced trigger vscode to update the file.
this._documents.delete(fileInfo);
this._uriToDocumentInfo.delete(uriString);
triggerUpdate = true;
}

this._documents.set(fileInfo, response);
this._uriToDocumentInfo.set(uriString, fileInfo);

if (triggerUpdate) {
this._onDidChangeEmitter.fire(uri);
}

return uri;
}

public provideTextDocumentContent(uri: Uri): string {
return this._documents.get(this._uriToDocumentInfo.get(uri.toString())).Source;
}

private getUriForName(sourceName: string): Uri {
return Uri.parse(this.scheme + ":///" + sourceName);
}
}
6 changes: 5 additions & 1 deletion src/omnisharp/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import { getMonoVersion } from '../utils/getMonoVersion';
import { FixAllProvider } from '../features/fixAllProvider';
import { LanguageMiddlewareFeature } from './LanguageMiddlewareFeature';
import SemanticTokensProvider from '../features/semanticTokensProvider';
import SourceGeneratedDocumentProvider from '../features/sourceGeneratedDocumentProvider';

export interface ActivationResult {
readonly server: OmniSharpServer;
Expand Down Expand Up @@ -72,7 +73,10 @@ export async function activate(context: vscode.ExtensionContext, packageJSON: an
const definitionMetadataDocumentProvider = new DefinitionMetadataDocumentProvider();
definitionMetadataDocumentProvider.register();
localDisposables.add(definitionMetadataDocumentProvider);
const definitionProvider = new DefinitionProvider(server, definitionMetadataDocumentProvider, languageMiddlewareFeature);
const sourceGeneratedDocumentProvider = new SourceGeneratedDocumentProvider(server);
sourceGeneratedDocumentProvider.register();
localDisposables.add(sourceGeneratedDocumentProvider);
const definitionProvider = new DefinitionProvider(server, definitionMetadataDocumentProvider, sourceGeneratedDocumentProvider, languageMiddlewareFeature);
localDisposables.add(vscode.languages.registerDefinitionProvider(documentSelector, definitionProvider));
localDisposables.add(vscode.languages.registerDefinitionProvider({ scheme: definitionMetadataDocumentProvider.scheme }, definitionProvider));
localDisposables.add(vscode.languages.registerImplementationProvider(documentSelector, new ImplementationProvider(server, languageMiddlewareFeature)));
Expand Down
4 changes: 2 additions & 2 deletions src/omnisharp/prioritization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const normalCommands = [
protocol.Requests.FindSymbols,
protocol.Requests.FindUsages,
protocol.Requests.GetCodeActions,
protocol.Requests.GoToDefinition,
protocol.V2.Requests.GoToDefinition,
protocol.Requests.RunCodeAction,
protocol.Requests.SignatureHelp,
protocol.Requests.TypeLookup
Expand Down Expand Up @@ -58,4 +58,4 @@ export function isDeferredCommand(command: string) {

deferredSet.add(command);
return true;
}
}
Loading

0 comments on commit 5e48576

Please sign in to comment.