Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add V2 version of GotoDefinitionService #2168

Merged
merged 7 commits into from May 31, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -10,6 +10,6 @@ public interface ICodeActionRequest
int Column { get; }
string Buffer { get; }
string FileName { get; }
Range Selection { get; }
Range Selection { get; set; }
}
}
@@ -0,0 +1,11 @@
using OmniSharp.Mef;

namespace OmniSharp.Models.V2.GotoDefinition
{
[OmniSharpEndpoint(OmniSharpEndpoints.V2.GotoDefinition, typeof(GotoDefinitionRequest), typeof(GotoDefinitionResponse))]
public class GotoDefinitionRequest : Request
{
public int Timeout { get; init; } = 10000;
public bool WantMetadata { get; init; }
}
}
@@ -0,0 +1,18 @@
#nullable enable

using OmniSharp.Models.Metadata;
using System.Collections.Generic;

namespace OmniSharp.Models.V2.GotoDefinition
{
public record GotoDefinitionResponse
{
public List<Definition>? Definitions { get; init; }
}

public record Definition
{
public Location Location { get; init; } = null!;
public MetadataSource? MetadataSource { get; init; }
}
}
10 changes: 10 additions & 0 deletions src/OmniSharp.Abstractions/Models/v2/Location.cs
@@ -0,0 +1,10 @@
#nullable enable

namespace OmniSharp.Models.V2
{
public record Location
{
public string FileName { get; init; } = null!;
public Range Range { get; init; } = null!;
}
}
32 changes: 3 additions & 29 deletions src/OmniSharp.Abstractions/Models/v2/Point.cs
@@ -1,39 +1,13 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;

namespace OmniSharp.Models.V2
{
public class Point : IEquatable<Point>
public record Point : IEquatable<Point>
{
[JsonConverter(typeof(ZeroBasedIndexConverter))]
public int Line { get; set; }
public int Line { get; init; }
[JsonConverter(typeof(ZeroBasedIndexConverter))]
public int Column { get; set; }

public override bool Equals(object obj)
=> Equals(obj as Point);

public bool Equals(Point other)
=> other != null
&& Line == other.Line
&& Column == other.Column;

public override int GetHashCode()
{
var hashCode = -1456208474;
hashCode = hashCode * -1521134295 + Line.GetHashCode();
hashCode = hashCode * -1521134295 + Column.GetHashCode();
return hashCode;
}

public override string ToString()
=> $"Line = {Line}, Column = {Column}";

public static bool operator ==(Point point1, Point point2)
=> EqualityComparer<Point>.Default.Equals(point1, point2);

public static bool operator !=(Point point1, Point point2)
=> !(point1 == point2);
public int Column { get; init; }
}
}
34 changes: 3 additions & 31 deletions src/OmniSharp.Abstractions/Models/v2/Range.cs
@@ -1,12 +1,9 @@
using System;
using System.Collections.Generic;

namespace OmniSharp.Models.V2
{
public class Range : IEquatable<Range>
public record Range
{
public Point Start { get; set; }
public Point End { get; set; }
public Point Start { get; init; }
public Point End { get; init; }

public bool Contains(int line, int column)
{
Expand All @@ -29,30 +26,5 @@ public bool Contains(int line, int column)
}

public bool IsValid() => Start != null && Start.Line > -1 && Start.Column > -1 && End != null && End.Line > -1 && End.Column > -1;

public override bool Equals(object obj)
=> Equals(obj as Range);

public bool Equals(Range other)
=> other != null
&& EqualityComparer<Point>.Default.Equals(Start, other.Start)
&& EqualityComparer<Point>.Default.Equals(End, other.End);

public override int GetHashCode()
333fred marked this conversation as resolved.
Show resolved Hide resolved
{
var hashCode = -1676728671;
hashCode = hashCode * -1521134295 + EqualityComparer<Point>.Default.GetHashCode(Start);
hashCode = hashCode * -1521134295 + EqualityComparer<Point>.Default.GetHashCode(End);
return hashCode;
}

public override string ToString()
=> $"Start = {{{Start}}}, End = {{{End}}}";

public static bool operator ==(Range range1, Range range2)
=> EqualityComparer<Range>.Default.Equals(range1, range2);

public static bool operator !=(Range range1, Range range2)
=> !(range1 == range2);
}
}
1 change: 1 addition & 0 deletions src/OmniSharp.Abstractions/OmniSharpEndpoints.cs
Expand Up @@ -72,6 +72,7 @@ public static class V2

public const string Highlight = "/v2/highlight";

public const string GotoDefinition = "/v2/gotodefinition";
}
}
}
19 changes: 11 additions & 8 deletions src/OmniSharp.Cake/Extensions/ResponseExtensions.cs
Expand Up @@ -272,16 +272,19 @@ private static async Task<Range> TranslateAsync(this Range range, OmniSharpWorks

if (range.Start.Line == range.End.Line)
{
range.Start.Line = line;
range.End.Line = line;
return range;
return range with
{
Start = range.Start with { Line = line },
End = range.End with { Line = line }
};
}

range.Start.Line = line;
(line, _) = await LineIndexHelper.TranslateFromGenerated(request.FileName, range.End.Line, workspace, true);
range.End.Line = line;

return range;
var (endLine, _) = await LineIndexHelper.TranslateFromGenerated(request.FileName, range.End.Line, workspace, true);
return range with
{
Start = range.Start with { Line = line },
End = range.End with { Line = endLine }
};
}

private static async Task<FileMemberElement> TranslateAsync(this FileMemberElement element, OmniSharpWorkspace workspace, Request request)
Expand Down
Expand Up @@ -5,7 +5,7 @@
namespace OmniSharp.Cake.Services.RequestHandlers.Refactoring.V2
{
public abstract class BaseCodeActionsHandler<TRequest, TResponse> : CakeRequestHandler<TRequest, TResponse>
where TRequest : ICodeActionRequest
where TRequest : ICodeActionRequest, new()
{
protected BaseCodeActionsHandler(
OmniSharpWorkspace workspace)
Expand All @@ -18,10 +18,17 @@ protected override async Task<TRequest> TranslateRequestAsync(TRequest request)
if (request.Selection != null)
{
var startLine = await LineIndexHelper.TranslateToGenerated(request.FileName, request.Selection.Start.Line, Workspace);
request.Selection.End.Line = request.Selection.Start.Line != request.Selection.End.Line
var endLine = request.Selection.Start.Line != request.Selection.End.Line
? await LineIndexHelper.TranslateToGenerated(request.FileName, request.Selection.End.Line, Workspace)
: startLine;
request.Selection.Start.Line = startLine;
request = new TRequest()
{
Selection = request.Selection with
{
Start = request.Selection.Start with { Line = startLine },
End = request.Selection.End with { Line = endLine }
}
};
}

return await base.TranslateRequestAsync(request);
Expand Down
@@ -0,0 +1,51 @@
#nullable enable

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.FindSymbols;
using OmniSharp.Extensions;
using OmniSharp.Options;
using System.Threading.Tasks;

namespace OmniSharp.Roslyn.CSharp.Services.Navigation
{
internal static class GoToDefinitionHelpers
{
internal static async Task<ISymbol?> GetDefinitionSymbol(Document document, int line, int column)
{
var sourceText = await document.GetTextAsync();
var position = sourceText.GetPositionFromLineAndOffset(line, column);
333fred marked this conversation as resolved.
Show resolved Hide resolved
var symbol = await SymbolFinder.FindSymbolAtPositionAsync(document, position);

return symbol switch
{
INamespaceSymbol => null,
// Always prefer the partial implementation over the definition
IMethodSymbol { IsPartialDefinition: true, PartialImplementationPart: var impl } => impl,
// Don't return property getters/settings/initers
IMethodSymbol { AssociatedSymbol: IPropertySymbol } => null,
_ => symbol
};
}

internal static async Task<FileLinePositionSpan?> GetMetadataMappedSpan(
Document document,
ISymbol symbol,
ExternalSourceServiceFactory externalSourceServiceFactory,
IExternalSourceService externalSourceService,
OmniSharpOptions options,
int timeout)
{

var cancellationToken = externalSourceServiceFactory.CreateCancellationToken(options, timeout);
333fred marked this conversation as resolved.
Show resolved Hide resolved
var (metadataDocument, _) = await externalSourceService.GetAndAddExternalSymbolDocument(document.Project, symbol, cancellationToken);
if (metadataDocument != null)
{
cancellationToken = externalSourceServiceFactory.CreateCancellationToken(options, timeout);
var metadataLocation = await externalSourceService.GetExternalSymbolLocation(symbol, metadataDocument, cancellationToken);
return metadataLocation.GetMappedLineSpan();
}

return null;
}
}
}
@@ -1,9 +1,9 @@
#nullable enable

using System.Composition;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.Text;
using OmniSharp.Extensions;
using OmniSharp.Mef;
using OmniSharp.Models.GotoDefinition;
Expand Down Expand Up @@ -33,67 +33,46 @@ public async Task<GotoDefinitionResponse> Handle(GotoDefinitionRequest request)
var document = externalSourceService.FindDocumentInCache(request.FileName) ??
_workspace.GetDocument(request.FileName);

var response = new GotoDefinitionResponse();

if (document != null)
var symbol = await GoToDefinitionHelpers.GetDefinitionSymbol(document, request.Line, request.Column);
if (symbol == null)
{
var semanticModel = await document.GetSemanticModelAsync();
var sourceText = await document.GetTextAsync();
var position = sourceText.GetTextPosition(request);
var symbol = await SymbolFinder.FindSymbolAtPositionAsync(semanticModel, position, _workspace);

// go to definition for namespaces is not supported
if (symbol != null && !(symbol is INamespaceSymbol))
{
// for partial methods, pick the one with body
if (symbol is IMethodSymbol method)
{
// Return an empty response for property accessor symbols like get and set
if (method.AssociatedSymbol is IPropertySymbol)
return response;
return new GotoDefinitionResponse();
}

symbol = method.PartialImplementationPart ?? symbol;
}
var location = symbol.Locations.First();

var location = symbol.Locations.First();
GotoDefinitionResponse? response = null;
if (location.IsInSource)
{
var lineSpan = symbol.Locations.First().GetMappedLineSpan();
response = new GotoDefinitionResponse
{
FileName = lineSpan.Path,
Line = lineSpan.StartLinePosition.Line,
Column = lineSpan.StartLinePosition.Character
};
}
else if (location.IsInMetadata && request.WantMetadata)
{
var maybeSpan = await GoToDefinitionHelpers.GetMetadataMappedSpan(document, symbol, _externalSourceServiceFactory, externalSourceService, _omnisharpOptions, request.Timeout);

if (location.IsInSource)
{
var lineSpan = symbol.Locations.First().GetMappedLineSpan();
response = new GotoDefinitionResponse
{
FileName = lineSpan.Path,
Line = lineSpan.StartLinePosition.Line,
Column = lineSpan.StartLinePosition.Character
};
}
else if (location.IsInMetadata && request.WantMetadata)
if (maybeSpan is FileLinePositionSpan lineSpan)
{
response = new GotoDefinitionResponse
{
var cancellationToken = _externalSourceServiceFactory.CreateCancellationToken(_omnisharpOptions, request.Timeout);
var (metadataDocument, _) = await externalSourceService.GetAndAddExternalSymbolDocument(document.Project, symbol, cancellationToken);
if (metadataDocument != null)
Line = lineSpan.StartLinePosition.Line,
Column = lineSpan.StartLinePosition.Character,
MetadataSource = new MetadataSource()
{
cancellationToken = _externalSourceServiceFactory.CreateCancellationToken(_omnisharpOptions, request.Timeout);
var metadataLocation = await externalSourceService.GetExternalSymbolLocation(symbol, metadataDocument, cancellationToken);
var lineSpan = metadataLocation.GetMappedLineSpan();

response = new GotoDefinitionResponse
{
Line = lineSpan.StartLinePosition.Line,
Column = lineSpan.StartLinePosition.Character,
MetadataSource = new MetadataSource()
{
AssemblyName = symbol.ContainingAssembly.Name,
ProjectName = document.Project.Name,
TypeName = symbol.GetSymbolName()
},
};
}
}
AssemblyName = symbol.ContainingAssembly.Name,
ProjectName = document.Project.Name,
TypeName = symbol.GetSymbolName()
},
};
}
}

return response;
return response ?? new GotoDefinitionResponse();
}
}
}