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

Support goto defintion in Visual Studio #7668

Merged
merged 2 commits into from
Jul 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using Bicep.VSLanguageServerClient.MiddleLayerProviders;
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using OmniSharp.Extensions.LanguageServer.Protocol;
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
using OmniSharpLocation = OmniSharp.Extensions.LanguageServer.Protocol.Models.Location;
using OmniSharpPosition = OmniSharp.Extensions.LanguageServer.Protocol.Models.Position;
using OmniSharpRange = OmniSharp.Extensions.LanguageServer.Protocol.Models.Range;

namespace Bicep.VSLanguageServerClient.UnitTests.MiddleLayerProviders
{
[TestClass]
public class HandleGotoDefintionMiddleLayerTests
{
[TestMethod]
public void GetVSLocations_WithValidLocation_ShouldReturnVSLocation()
{
var handleGotoDefintionMiddleLayer = new HandleGotoDefintionMiddleLayer();
var uri = DocumentUri.From("some_path");
var omniSharpLocation = new OmniSharpLocation
{
Uri = uri,
Range = new OmniSharpRange()
{
Start = new OmniSharpPosition(5, 11),
End = new OmniSharpPosition(5, 19)
}
};
var locationOrLocationLink = new LocationOrLocationLink(omniSharpLocation);

var vsLocation = handleGotoDefintionMiddleLayer.GetVSLocation(locationOrLocationLink);

Assert.IsNotNull(vsLocation);
vsLocation.Uri.Should().Be(uri.ToUri());

var resultStart = vsLocation.Range.Start;
var resultEnd = vsLocation.Range.End;

resultStart.Line.Should().Be(5);
resultStart.Character.Should().Be(11);
resultEnd.Line.Should().Be(5);
resultEnd.Character.Should().Be(19);
}

[TestMethod]
public void GetVSLocations_WithValidLocationLink_ShouldReturnVSLocation()
{
var handleGotoDefintionMiddleLayer = new HandleGotoDefintionMiddleLayer();
var uri = DocumentUri.From("some_path");
var omniSharpLocationLink = new LocationLink
{
TargetUri = uri,
OriginSelectionRange = new OmniSharpRange()
{
Start = new OmniSharpPosition(2, 3),
End = new OmniSharpPosition(2, 6)
},
TargetRange = new OmniSharpRange()
{
Start = new OmniSharpPosition(5, 11),
End = new OmniSharpPosition(5, 20)
},
TargetSelectionRange = new OmniSharpRange()
{
Start = new OmniSharpPosition(5, 11),
End = new OmniSharpPosition(5, 30)
}
};
var locationOrLocationLink = new LocationOrLocationLink(omniSharpLocationLink);

var vsLocation = handleGotoDefintionMiddleLayer.GetVSLocation(locationOrLocationLink);

Assert.IsNotNull(vsLocation);
vsLocation.Uri.Should().Be(uri.ToUri());

var resultStart = vsLocation.Range.Start;
var resultEnd = vsLocation.Range.End;

resultStart.Line.Should().Be(5);
resultStart.Character.Should().Be(11);
resultEnd.Line.Should().Be(5);
resultEnd.Character.Should().Be(30);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ public BicepLanguageServerClient(IProcessTracker processTracker)
var setupConfiguration = new SetupConfiguration();
var handleSnippetCompletionsMiddleLayer = new HandleSnippetCompletionsMiddleLayer(setupConfiguration.GetInstanceForCurrentProcess().GetInstallationVersion());
var updateFormatSettingsMiddleLayer = new UpdateFormatSettingsMiddleLayer();
middleLayer = new AggregatingMiddleLayer(handleSnippetCompletionsMiddleLayer, updateFormatSettingsMiddleLayer);
var gotoDefintionMiddleLayer = new HandleGotoDefintionMiddleLayer();
middleLayer = new AggregatingMiddleLayer(gotoDefintionMiddleLayer, handleSnippetCompletionsMiddleLayer, updateFormatSettingsMiddleLayer);
}

public string Name => "Bicep Language Server";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.VisualStudio.LanguageServer.Client;
using Microsoft.VisualStudio.LanguageServer.Protocol;
using Newtonsoft.Json.Linq;
using OmniSharp.Extensions.LanguageServer.Protocol;
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
using OmniSharpLocation = OmniSharp.Extensions.LanguageServer.Protocol.Models.Location;
using OmniSharpRange = OmniSharp.Extensions.LanguageServer.Protocol.Models.Range;
using VSLocation = Microsoft.VisualStudio.LanguageServer.Protocol.Location;
using VSPosition = Microsoft.VisualStudio.LanguageServer.Protocol.Position;
using VSRange = Microsoft.VisualStudio.LanguageServer.Protocol.Range;

namespace Bicep.VSLanguageServerClient.MiddleLayerProviders
{
/// <summary>
/// This middle layer supports goto defintion in Visual Studio
/// VS lsp model is different from O#. We need to convert the output obtained from bicep language server to a format that is
/// serializable by VS language server client.
/// </summary>
public class HandleGotoDefintionMiddleLayer : ILanguageClientMiddleLayer
{
public bool CanHandle(string methodName)
{
if (string.IsNullOrEmpty(methodName))
{
return false;
}

return methodName.Equals(Methods.TextDocumentDefinitionName, StringComparison.Ordinal);
}

public Task HandleNotificationAsync(string methodName, JToken methodParam, Func<JToken, Task> sendNotification) => sendNotification(methodParam);

public async Task<JToken?> HandleRequestAsync(string methodName, JToken methodParam, Func<JToken, Task<JToken?>> sendRequest)
{
if (CanHandle(Methods.TextDocumentDefinitionName))
{
JToken? jToken = await sendRequest(methodParam);
var locations = new List<VSLocation>();

if (jToken is not null)
{
foreach (var child in jToken.Children())
{
var locationOrLocationLink = child.ToObject<LocationOrLocationLink>();

if (locationOrLocationLink is not null)
{
var vsLocation = GetVSLocation(locationOrLocationLink);

if (vsLocation is not null)
{
locations.Add(vsLocation);
}
}
}

return JToken.FromObject(locations);
}
}

return await sendRequest(methodParam);
}

public VSLocation? GetVSLocation(LocationOrLocationLink locationOrLocationLink)
{
if (locationOrLocationLink is not null)
{
if (locationOrLocationLink.IsLocationLink && locationOrLocationLink.LocationLink is LocationLink locationLink)
{
return GetVSLocation(locationLink.TargetUri, locationLink.TargetSelectionRange);
}
else if (locationOrLocationLink.IsLocation && locationOrLocationLink.Location is OmniSharpLocation omnisharpLocation)
{
return GetVSLocation(omnisharpLocation.Uri, omnisharpLocation.Range);
}
}

return null;
}

private VSLocation GetVSLocation(DocumentUri documentUri, OmniSharpRange omniSharpRange)
{
var start = omniSharpRange.Start;
var end = omniSharpRange.End;

var vsRange = new VSRange()
{
Start = new VSPosition(start.Line, start.Character),
End = new VSPosition(end.Line, end.Character)
};

return new VSLocation() { Uri = documentUri.ToUri(), Range = vsRange };
}
}
}