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

Cohosting Support for Go to Definition #10750

Merged
merged 8 commits into from
Aug 19, 2024
1 change: 1 addition & 0 deletions eng/targets/Services.props
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
<ServiceHubService Include="Microsoft.VisualStudio.Razor.SignatureHelp" ClassName="Microsoft.CodeAnalysis.Remote.Razor.RemoteSignatureHelpService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.Razor.DocumentHighlight" ClassName="Microsoft.CodeAnalysis.Remote.Razor.RemoteDocumentHighlightService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.Razor.InlayHint" ClassName="Microsoft.CodeAnalysis.Remote.Razor.RemoteInlayHintService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.Razor.GoToDefinition" ClassName="Microsoft.CodeAnalysis.Remote.Razor.RemoteGoToDefinitionService+Factory" />
<ServiceHubService Include="Microsoft.VisualStudio.Razor.Rename" ClassName="Microsoft.CodeAnalysis.Remote.Razor.RemoteRenameService+Factory" />
</ItemGroup>
</Project>

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Razor.DocumentMapping;
using Microsoft.CodeAnalysis.Razor.GoToDefinition;
using Microsoft.CodeAnalysis.Razor.Logging;
using Microsoft.CodeAnalysis.Razor.Workspaces;

namespace Microsoft.AspNetCore.Razor.LanguageServer.Definition;

internal sealed class RazorComponentDefinitionService(
IRazorComponentSearchEngine componentSearchEngine,
IDocumentMappingService documentMappingService,
ILoggerFactory loggerFactory)
: AbstractRazorComponentDefinitionService(componentSearchEngine, documentMappingService, loggerFactory.GetOrCreateLogger<RazorComponentDefinitionService>())
{
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
using Microsoft.AspNetCore.Razor.LanguageServer.WrapWithTag;
using Microsoft.AspNetCore.Razor.Telemetry;
using Microsoft.CodeAnalysis.Razor.FoldingRanges;
using Microsoft.CodeAnalysis.Razor.GoToDefinition;
using Microsoft.CodeAnalysis.Razor.Logging;
using Microsoft.CodeAnalysis.Razor.Rename;
using Microsoft.CodeAnalysis.Razor.Workspaces;
Expand Down Expand Up @@ -178,10 +179,11 @@ static void AddHandlers(IServiceCollection services, LanguageServerFeatureOption
services.AddHandlerWithCapabilities<ImplementationEndpoint>();
services.AddHandlerWithCapabilities<OnAutoInsertEndpoint>();

services.AddHandlerWithCapabilities<DefinitionEndpoint>();

if (!featureOptions.UseRazorCohostServer)
{
services.AddSingleton<IRazorComponentDefinitionService, RazorComponentDefinitionService>();
services.AddHandlerWithCapabilities<DefinitionEndpoint>();

services.AddSingleton<IRenameService, RenameService>();
services.AddHandlerWithCapabilities<RenameEndpoint>();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.

using System;

namespace Roslyn.LanguageServer.Protocol;

internal static partial class RoslynLspExtensions
{
public static void Deconstruct(this Location position, out Uri uri, out Range range)
=> (uri, range) = (position.Uri, position.Range);
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.

using System;
using System.Diagnostics;
using Microsoft.CodeAnalysis.Text;

Expand Down Expand Up @@ -159,6 +160,18 @@ public static Range CreateSingleLineRange(LinePosition start, int length)
public static Range CreateSingleLineRange((int line, int character) start, int length)
=> CreateRange(CreatePosition(start), CreatePosition(start.line, start.character + length));

public static Location CreateLocation(Uri uri, Range range)
=> new() { Uri = uri, Range = range };

public static Location CreateLocation(Uri uri, LinePositionSpan span)
=> new() { Uri = uri, Range = CreateRange(span) };

public static DocumentLink CreateDocumentLink(Uri target, Range range)
=> new() { Target = target, Range = range };

public static DocumentLink CreateDocumentLink(Uri target, LinePositionSpan span)
=> new() { Target = target, Range = CreateRange(span) };

public static TextEdit CreateTextEdit(Range range, string newText)
=> new() { Range = range, NewText = newText };

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.

using System;

namespace Microsoft.VisualStudio.LanguageServer.Protocol;

internal static partial class VsLspExtensions
{
public static void Deconstruct(this Location position, out Uri uri, out Range range)
=> (uri, range) = (position.Uri, position.Range);
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.

using System;
using System.Diagnostics;
using Microsoft.CodeAnalysis.Text;

Expand Down Expand Up @@ -159,6 +160,18 @@ public static Range CreateSingleLineRange(LinePosition start, int length)
public static Range CreateSingleLineRange((int line, int character) start, int length)
=> CreateRange(CreatePosition(start), CreatePosition(start.line, start.character + length));

public static Location CreateLocation(string filePath, LinePositionSpan span)
=> CreateLocation(CreateFilePathUri(filePath), CreateRange(span));

public static Location CreateLocation(Uri uri, LinePositionSpan span)
=> CreateLocation(uri, CreateRange(span));

public static Location CreateLocation(string filePath, Range range)
=> CreateLocation(CreateFilePathUri(filePath), range);

public static Location CreateLocation(Uri uri, Range range)
=> new() { Uri = uri, Range = range };

public static TextEdit CreateTextEdit(Range range, string newText)
=> new() { Range = range, NewText = newText };

Expand All @@ -185,4 +198,16 @@ public static TextEdit CreateTextEdit(LinePosition position, string newText)

public static TextEdit CreateTextEdit((int line, int character) position, string newText)
=> CreateTextEdit(CreateZeroWidthRange(position), newText);

public static Uri CreateFilePathUri(string filePath)
{
var builder = new UriBuilder
{
Path = filePath,
Scheme = Uri.UriSchemeFile,
Host = string.Empty,
};

return builder.Uri;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.

using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis.Razor.DocumentMapping;
using Microsoft.CodeAnalysis.Razor.Logging;
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
using Microsoft.CodeAnalysis.Razor.Protocol;
using Microsoft.CodeAnalysis.Razor.Workspaces;
using Microsoft.VisualStudio.LanguageServer.Protocol;
using LspLocation = Microsoft.VisualStudio.LanguageServer.Protocol.Location;
using LspRange = Microsoft.VisualStudio.LanguageServer.Protocol.Range;

namespace Microsoft.CodeAnalysis.Razor.GoToDefinition;

internal abstract class AbstractRazorComponentDefinitionService(
IRazorComponentSearchEngine componentSearchEngine,
IDocumentMappingService documentMappingService,
ILogger logger) : IRazorComponentDefinitionService
{
private readonly IRazorComponentSearchEngine _componentSearchEngine = componentSearchEngine;
private readonly IDocumentMappingService _documentMappingService = documentMappingService;
private readonly ILogger _logger = logger;

public async Task<LspLocation?> GetDefinitionAsync(DocumentContext documentContext, DocumentPositionInfo positionInfo, bool ignoreAttributes, CancellationToken cancellationToken)
DustinCampbell marked this conversation as resolved.
Show resolved Hide resolved
{
// If we're in C# then there is no point checking for a component tag, because there won't be one
if (positionInfo.LanguageKind == RazorLanguageKind.CSharp)
{
return null;
}

if (!FileKinds.IsComponent(documentContext.FileKind))
{
_logger.LogInformation($"'{documentContext.FileKind}' is not a component type.");
return null;
}

var codeDocument = await documentContext.GetCodeDocumentAsync(cancellationToken).ConfigureAwait(false);

if (!RazorComponentDefinitionHelpers.TryGetBoundTagHelpers(codeDocument, positionInfo.HostDocumentIndex, ignoreAttributes, _logger, out var boundTagHelper, out var boundAttribute))
{
_logger.LogInformation($"Could not retrieve bound tag helper information.");
return null;
}

var componentDocument = await _componentSearchEngine.TryLocateComponentAsync(documentContext.Snapshot, boundTagHelper).ConfigureAwait(false);
if (componentDocument is null)
{
_logger.LogInformation($"Could not locate component document.");
return null;
}

var componentFilePath = componentDocument.FilePath.AssumeNotNull();

_logger.LogInformation($"Definition found at file path: {componentFilePath}");

var range = await GetNavigateRangeAsync(componentDocument, boundAttribute, cancellationToken).ConfigureAwait(false);

return VsLspFactory.CreateLocation(componentFilePath, range);
}

private async Task<LspRange> GetNavigateRangeAsync(IDocumentSnapshot documentSnapshot, BoundAttributeDescriptor? attributeDescriptor, CancellationToken cancellationToken)
{
if (attributeDescriptor is not null)
{
_logger.LogInformation($"Attempting to get definition from an attribute directly.");

var originCodeDocument = await documentSnapshot.GetGeneratedOutputAsync().ConfigureAwait(false);

var range = await RazorComponentDefinitionHelpers
.TryGetPropertyRangeAsync(originCodeDocument, attributeDescriptor.GetPropertyName(), _documentMappingService, _logger, cancellationToken)
.ConfigureAwait(false);

if (range is not null)
{
return range;
}
}

// When navigating from a start or end tag, we just take the user to the top of the file.
// If we were trying to navigate to a property, and we couldn't find it, we can at least take
// them to the file for the component. If the property was defined in a partial class they can
// at least then press F7 to go there.
return VsLspFactory.DefaultRange;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.

using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Razor.DocumentMapping;
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
using LspLocation = Microsoft.VisualStudio.LanguageServer.Protocol.Location;

namespace Microsoft.CodeAnalysis.Razor.GoToDefinition;

/// <summary>
/// Go to Definition support for Razor components.
/// </summary>
internal interface IRazorComponentDefinitionService
{
Task<LspLocation?> GetDefinitionAsync(DocumentContext documentContext, DocumentPositionInfo positionInfo, bool ignoreAttributes, CancellationToken cancellationToken);
}
Loading