Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master' into OptionsExternalAccess
Browse files Browse the repository at this point in the history
  • Loading branch information
JoeRobich committed Mar 8, 2022
2 parents 43ca318 + 6aace68 commit 9a8b09f
Show file tree
Hide file tree
Showing 14 changed files with 1,093 additions and 14 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using OmniSharp.Mef;

namespace OmniSharp.Models.GotoTypeDefinition
{
[OmniSharpEndpoint(OmniSharpEndpoints.GotoTypeDefinition, typeof(GotoTypeDefinitionRequest), typeof(GotoTypeDefinitionResponse))]
public class GotoTypeDefinitionRequest : Request
{
public int Timeout { get; init; } = 10000;
public bool WantMetadata { get; init; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#nullable enable

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

namespace OmniSharp.Models.GotoTypeDefinition
{
public record GotoTypeDefinitionResponse
{
public List<TypeDefinition>? Definitions { get; init; }
}

public record TypeDefinition
{
public V2.Location Location { get; init; } = null!;
public MetadataSource? MetadataSource { get; init; }
public SourceGeneratedFileInfo? SourceGeneratedFileInfo { get; init; }
}
}
1 change: 1 addition & 0 deletions src/OmniSharp.Abstractions/OmniSharpEndpoints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ namespace OmniSharp
public static class OmniSharpEndpoints
{
public const string GotoDefinition = "/gotodefinition";
public const string GotoTypeDefinition = "/gototypedefinition";
public const string FindSymbols = "/findsymbols";
public const string UpdateBuffer = "/updatebuffer";
public const string ChangeBuffer = "/changebuffer";
Expand Down
1 change: 1 addition & 0 deletions src/OmniSharp.Cake/OmniSharp.Cake.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

<ItemGroup>
<ProjectReference Include="..\OmniSharp.Abstractions\OmniSharp.Abstractions.csproj" />
<ProjectReference Include="..\OmniSharp.Roslyn.CSharp\OmniSharp.Roslyn.CSharp.csproj" />
<ProjectReference Include="..\OmniSharp.Roslyn\OmniSharp.Roslyn.csproj" />
<ProjectReference Include="..\OmniSharp.Shared\OmniSharp.Shared.csproj" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
using System;
using System.Collections.Generic;
using System.Composition;
using System.Linq;
using System.Threading.Tasks;
using OmniSharp.Extensions;
using OmniSharp.Mef;
using OmniSharp.Models.GotoTypeDefinition;
using OmniSharp.Models.Metadata;
using OmniSharp.Models.v1.SourceGeneratedFile;
using OmniSharp.Models.V2;
using OmniSharp.Roslyn;
using OmniSharp.Utilities;
using Location = OmniSharp.Models.V2.Location;
using Range = OmniSharp.Models.V2.Range;
using OmniSharp.Roslyn.CSharp.Services;
using OmniSharp.Options;

namespace OmniSharp.Cake.Services.RequestHandlers.Navigation
{
[OmniSharpHandler(OmniSharpEndpoints.GotoTypeDefinition, Constants.LanguageNames.Cake), Shared]
public class GotoTypeDefinitionHandler : CakeRequestHandler<GotoTypeDefinitionRequest, GotoTypeDefinitionResponse>
{
private readonly IExternalSourceService _externalSourceService;

[ImportingConstructor]
public GotoTypeDefinitionHandler(
OmniSharpWorkspace workspace,
ExternalSourceServiceFactory externalSourceServiceFactory,
OmniSharpOptions omniSharpOptions)
: base(workspace)
{
_externalSourceService = externalSourceServiceFactory?.Create(omniSharpOptions) ?? throw new ArgumentNullException(nameof(externalSourceServiceFactory));
}

protected override async Task<GotoTypeDefinitionResponse> TranslateResponse(GotoTypeDefinitionResponse response, GotoTypeDefinitionRequest request)
{
var definitions = new List<TypeDefinition>();
foreach (var definition in response.Definitions ?? Enumerable.Empty<TypeDefinition>())
{
var file = definition.Location.FileName;

if (string.IsNullOrEmpty(file) || !file.Equals(Constants.Paths.Generated))
{
if (PlatformHelper.IsWindows && !string.IsNullOrEmpty(file))
{
file = file.Replace('/', '\\');
}

definitions.Add(new TypeDefinition
{
MetadataSource = definition.MetadataSource,
SourceGeneratedFileInfo = definition.SourceGeneratedFileInfo,
Location = new Location
{
FileName = file,
Range = definition.Location.Range
}
});

continue;
}

if (!request.WantMetadata)
{
continue;
}

var aliasLocations = await GotoTypeDefinitionHandlerHelper.GetAliasFromExternalSourceAsync(
Workspace,
request.FileName,
definition.Location.Range.End.Line,
request.Timeout,
_externalSourceService
);

definitions.AddRange(
aliasLocations.Select(loc =>
new TypeDefinition
{
Location = new Location
{
FileName = loc.MetadataDocument.FilePath ?? loc.MetadataDocument.Name,
Range = new Range
{
Start = new Point
{
Column = loc.LineSpan.StartLinePosition.Character,
Line = loc.LineSpan.StartLinePosition.Line
},
End = new Point
{
Column = loc.LineSpan.EndLinePosition.Character,
Line = loc.LineSpan.EndLinePosition.Line
},
}
},
MetadataSource = new MetadataSource
{
AssemblyName = loc.Symbol.ContainingAssembly.Name,
ProjectName = loc.Document.Project.Name,
TypeName = loc.Symbol.GetSymbolName()
},
SourceGeneratedFileInfo = new SourceGeneratedFileInfo
{
DocumentGuid = loc.Document.Id.Id,
ProjectGuid = loc.Document.Id.ProjectId.Id
}
})
.ToList());
}

return new GotoTypeDefinitionResponse
{
Definitions = definitions
};
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.Text;
using OmniSharp.Roslyn;
using OmniSharp.Roslyn.CSharp.Services;

namespace OmniSharp.Cake.Services.RequestHandlers.Navigation
{
public static class GotoTypeDefinitionHandlerHelper
{
private const int MethodLineOffset = 3;
private const int PropertyLineOffset = 7;

internal static async Task<IEnumerable<Alias>> GetAliasFromExternalSourceAsync(
OmniSharpWorkspace workspace,
string fileName,
int line,
int timeout,
IExternalSourceService externalSourceService)
{
var document = workspace.GetDocument(fileName);
var lineIndex = line + MethodLineOffset;
int column;

if (document == null)
{
return Enumerable.Empty<Alias>();
}

var semanticModel = await document.GetSemanticModelAsync();
var sourceText = await document.GetTextAsync();
var sourceLine = sourceText.Lines[lineIndex].ToString();
if (sourceLine.Contains("(Context"))
{
column = sourceLine.IndexOf("(Context", StringComparison.Ordinal);
}
else
{
lineIndex = line + PropertyLineOffset;
sourceLine = sourceText.Lines[lineIndex].ToString();
if (sourceLine.Contains("(Context"))
{
column = sourceLine.IndexOf("(Context", StringComparison.Ordinal);
}
else
{
return Enumerable.Empty<Alias>();
}
}

if (column > 0 && sourceLine[column - 1] == '>')
{
column = sourceLine.LastIndexOf("<", column, StringComparison.Ordinal);
}

var position = sourceText.Lines.GetPosition(new LinePosition(lineIndex, column));
var symbol = await SymbolFinder.FindSymbolAtPositionAsync(semanticModel, position, workspace);

if (symbol == null || symbol is INamespaceSymbol)
{
return Enumerable.Empty<Alias>();
}
if (symbol is IMethodSymbol method)
{
symbol = method.PartialImplementationPart ?? symbol;
}

var typeSymbol = symbol switch
{
ILocalSymbol localSymbol => localSymbol.Type,
IFieldSymbol fieldSymbol => fieldSymbol.Type,
IPropertySymbol propertySymbol => propertySymbol.Type,
IParameterSymbol parameterSymbol => parameterSymbol.Type,
_ => null
};

if (typeSymbol == null)
return Enumerable.Empty<Alias>();

var result = new List<Alias>();
foreach (var location in typeSymbol.Locations)
{
if (!location.IsInMetadata)
{
continue;
}

var cancellationSource = new CancellationTokenSource(TimeSpan.FromMilliseconds(timeout));
var (metadataDocument, _) = await externalSourceService.GetAndAddExternalSymbolDocument(document.Project, typeSymbol, cancellationSource.Token);
if (metadataDocument == null)
{
continue;
}

cancellationSource = new CancellationTokenSource(TimeSpan.FromMilliseconds(timeout));
var metadataLocation = await externalSourceService.GetExternalSymbolLocation(typeSymbol, metadataDocument, cancellationSource.Token);
var lineSpan = metadataLocation.GetMappedLineSpan();

result.Add(new Alias
{
Document = document,
MetadataDocument = metadataDocument,
Symbol = typeSymbol,
Location = location,
LineSpan = lineSpan
});
}

return result;
}

internal class Alias
{
public Document Document { get; set; }
public ISymbol Symbol { get; set; }
public Location Location { get; set; }
public FileLinePositionSpan LineSpan { get; set; }
public Document MetadataDocument { get; set; }
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#nullable enable

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.Operations;
using OmniSharp.Extensions;
using OmniSharp.Models.v1.SourceGeneratedFile;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;

namespace OmniSharp.Roslyn.CSharp.Services.Navigation
{
internal static class GotoTypeDefinitionHelpers
{
internal static async Task<ITypeSymbol?> GetTypeOfSymbol(Document document, int line, int column, CancellationToken cancellationToken)
{
var sourceText = await document.GetTextAsync(cancellationToken);
var position = sourceText.GetPositionFromLineAndOffset(line, column);
var symbol = await SymbolFinder.FindSymbolAtPositionAsync(document, position, cancellationToken);

return symbol switch
{
ILocalSymbol localSymbol => localSymbol.Type,
IFieldSymbol fieldSymbol => fieldSymbol.Type,
IPropertySymbol propertySymbol => propertySymbol.Type,
IParameterSymbol parameterSymbol => parameterSymbol.Type,
_ => null
};
}

internal static async Task<FileLinePositionSpan?> GetMetadataMappedSpan(
Document document,
ISymbol symbol,
IExternalSourceService externalSourceService,
CancellationToken cancellationToken)
{
var (metadataDocument, _) = await externalSourceService.GetAndAddExternalSymbolDocument(document.Project, symbol, cancellationToken);
if (metadataDocument != null)
{
var metadataLocation = await externalSourceService.GetExternalSymbolLocation(symbol, metadataDocument, cancellationToken);
return metadataLocation.GetMappedLineSpan();
}

return null;
}

internal static SourceGeneratedFileInfo? GetSourceGeneratedFileInfo(OmniSharpWorkspace workspace, Location location)
{
Debug.Assert(location.IsInSource);
var document = workspace.CurrentSolution.GetDocument(location.SourceTree);
if (document is not SourceGeneratedDocument)
{
return null;
}

return new SourceGeneratedFileInfo
{
ProjectGuid = document.Project.Id.Id,
DocumentGuid = document.Id.Id
};
}
}
}
Loading

0 comments on commit 9a8b09f

Please sign in to comment.