Skip to content

Commit dee1a4f

Browse files
authored
Merge pull request #74626 from dibarbet/vscode_source_link
Add support in DevKit for source link go to definition
2 parents 9d57657 + c1e0b26 commit dee1a4f

File tree

8 files changed

+234
-79
lines changed

8 files changed

+234
-79
lines changed

src/Features/Core/Portable/Microsoft.CodeAnalysis.Features.csproj

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@
8484
<InternalsVisibleTo Include="Microsoft.VisualStudio.LanguageServices.CodeLens" />
8585
<InternalsVisibleTo Include="Microsoft.VisualStudio.LanguageServices.CSharp" />
8686
<InternalsVisibleTo Include="Microsoft.VisualStudio.LanguageServices.CSharp.UnitTests" />
87+
<!-- This assembly is shipped and versioned alongside Roslyn in the C# extension, fine to give it full IVTs -->
88+
<InternalsVisibleTo Include="Microsoft.VisualStudio.LanguageServices.DevKit" />
8789
<InternalsVisibleTo Include="Microsoft.VisualStudio.LanguageServices.ExternalDependencyServices" WorkItem="https://github.com/dotnet/roslyn/issues/35085" />
8890
<InternalsVisibleTo Include="Microsoft.VisualStudio.LanguageServices.Implementation" />
8991
<InternalsVisibleTo Include="Microsoft.VisualStudio.LanguageServices.LiveShare" />
@@ -113,11 +115,6 @@
113115
<RestrictedInternalsVisibleTo Include="Microsoft.VisualStudio.LanguageServerClient.Razor.Test" Partner="Razor" Key="$(RazorKey)" />
114116
<RestrictedInternalsVisibleTo Include="Microsoft.VisualStudio.LanguageServices.Razor" Partner="Razor" Key="$(RazorKey)" />
115117
<RestrictedInternalsVisibleTo Include="Microsoft.VisualStudio.LanguageServices.Razor.Test" Partner="Razor" Key="$(RazorKey)" />
116-
<!--
117-
Only allow C# DevKit to use types from Contracts namespace. The contracts should not introduce breaking changes between versions,
118-
because the versions of C# DevKit and C# Extension might not be aligned.
119-
-->
120-
<RestrictedInternalsVisibleTo Include="Microsoft.VisualStudio.LanguageServices.DevKit" Namespace="Microsoft.CodeAnalysis.Contracts" />
121118
<RestrictedInternalsVisibleTo Include="Microsoft.VisualStudio.TestWindow.Core" Partner="UnitTesting" Key="$(UnitTestingKey)" />
122119
</ItemGroup>
123120
<ItemGroup>

src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/BrokeredServices/Services/Descriptors.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ internal class Descriptors
4949
{ BrokeredServiceDescriptors.HotReloadOptionService.Moniker, new ServiceRegistration(ServiceAudience.Local, null, allowGuestClients: false) },
5050
{ BrokeredServiceDescriptors.HotReloadLoggerService.Moniker, new ServiceRegistration(ServiceAudience.Local, null, allowGuestClients: false) },
5151
{ BrokeredServiceDescriptors.MauiLaunchCustomizerService.Moniker, new ServiceRegistration(ServiceAudience.Local, null, allowGuestClients: false) },
52+
{ BrokeredServiceDescriptors.DebuggerSymbolLocatorService.Moniker, new ServiceRegistration(ServiceAudience.Local, null, allowGuestClients: false) },
53+
{ BrokeredServiceDescriptors.DebuggerSourceLinkService.Moniker, new ServiceRegistration(ServiceAudience.Local, null, allowGuestClients: false) },
5254
}.ToImmutableDictionary();
5355

5456
public static ServiceJsonRpcDescriptor CreateDescriptor(ServiceMoniker serviceMoniker) => new(
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System.IO;
6+
using System.Reflection.PortableExecutable;
7+
using System.Threading;
8+
using System.Threading.Tasks;
9+
using Microsoft.CodeAnalysis.PdbSourceDocument;
10+
using Microsoft.CodeAnalysis.PooledObjects;
11+
using Microsoft.VisualStudio.Debugger.Contracts.SourceLink;
12+
using Microsoft.VisualStudio.Debugger.Contracts.SymbolLocator;
13+
14+
namespace Microsoft.VisualStudio.LanguageServices.PdbSourceDocument;
15+
16+
internal abstract class AbstractSourceLinkService : ISourceLinkService
17+
{
18+
public async Task<PdbFilePathResult?> GetPdbFilePathAsync(string dllPath, PEReader peReader, bool useDefaultSymbolServers, CancellationToken cancellationToken)
19+
{
20+
var hasCodeViewEntry = false;
21+
uint timeStamp = 0;
22+
CodeViewDebugDirectoryData codeViewEntry = default;
23+
using var _ = ArrayBuilder<PdbChecksum>.GetInstance(out var checksums);
24+
foreach (var entry in peReader.ReadDebugDirectory())
25+
{
26+
if (entry.Type == DebugDirectoryEntryType.PdbChecksum)
27+
{
28+
var checksum = peReader.ReadPdbChecksumDebugDirectoryData(entry);
29+
checksums.Add(new PdbChecksum(checksum.AlgorithmName, checksum.Checksum));
30+
}
31+
else if (entry.Type == DebugDirectoryEntryType.CodeView && entry.IsPortableCodeView)
32+
{
33+
hasCodeViewEntry = true;
34+
timeStamp = entry.Stamp;
35+
codeViewEntry = peReader.ReadCodeViewDebugDirectoryData(entry);
36+
}
37+
}
38+
39+
if (!hasCodeViewEntry)
40+
return null;
41+
42+
var pdbInfo = new SymbolLocatorPdbInfo(
43+
Path.GetFileName(codeViewEntry.Path),
44+
codeViewEntry.Guid,
45+
(uint)codeViewEntry.Age,
46+
timeStamp,
47+
checksums.ToImmutable(),
48+
dllPath,
49+
codeViewEntry.Path);
50+
51+
var flags = useDefaultSymbolServers
52+
? SymbolLocatorSearchFlags.ForceNuGetSymbolServer | SymbolLocatorSearchFlags.ForceMsftSymbolServer
53+
: SymbolLocatorSearchFlags.None;
54+
var result = await LocateSymbolFileAsync(pdbInfo, flags, cancellationToken).ConfigureAwait(false);
55+
if (result is null)
56+
{
57+
Logger?.Log($"{nameof(LocateSymbolFileAsync)} returned null");
58+
return null;
59+
}
60+
61+
if (result.Value.Found && result.Value.SymbolFilePath is not null)
62+
{
63+
return new PdbFilePathResult(result.Value.SymbolFilePath);
64+
}
65+
else if (Logger is not null)
66+
{
67+
// We log specific info from the debugger if there is a failure, but the caller will log general failure
68+
// information otherwise
69+
Logger.Log(result.Value.Status);
70+
Logger.Log(result.Value.Log);
71+
}
72+
73+
return null;
74+
}
75+
76+
public async Task<SourceFilePathResult?> GetSourceFilePathAsync(string url, string relativePath, CancellationToken cancellationToken)
77+
{
78+
var result = await GetSourceLinkAsync(url, relativePath, cancellationToken).ConfigureAwait(false);
79+
if (result is null)
80+
{
81+
Logger?.Log($"{nameof(GetSourceLinkAsync)} returned null");
82+
return null;
83+
}
84+
85+
if (result.Value.Status == SourceLinkResultStatus.Succeeded && result.Value.Path is not null)
86+
{
87+
return new SourceFilePathResult(result.Value.Path);
88+
}
89+
else if (Logger is not null && result.Value.Log is not null)
90+
{
91+
// We log specific info from the debugger if there is a failure, but the caller will log general failure
92+
// information otherwise.
93+
Logger.Log(result.Value.Log);
94+
}
95+
96+
return null;
97+
}
98+
99+
protected abstract Task<SymbolLocatorResult?> LocateSymbolFileAsync(SymbolLocatorPdbInfo pdbInfo, SymbolLocatorSearchFlags flags, CancellationToken cancellationToken);
100+
101+
protected abstract Task<SourceLinkResult?> GetSourceLinkAsync(string url, string relativePath, CancellationToken cancellationToken);
102+
103+
protected abstract IPdbSourceDocumentLogger? Logger { get; }
104+
}

src/VisualStudio/Core/Def/PdbSourceDocument/SourceLinkService.cs

Lines changed: 7 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,17 @@
44

55
using System;
66
using System.Composition;
7-
using System.IO;
8-
using System.Reflection.PortableExecutable;
97
using System.Threading;
108
using System.Threading.Tasks;
119
using Microsoft.CodeAnalysis.Host.Mef;
1210
using Microsoft.CodeAnalysis.PdbSourceDocument;
13-
using Microsoft.CodeAnalysis.PooledObjects;
1411
using Microsoft.VisualStudio.Debugger.Contracts.SourceLink;
1512
using Microsoft.VisualStudio.Debugger.Contracts.SymbolLocator;
1613

1714
namespace Microsoft.VisualStudio.LanguageServices.PdbSourceDocument;
1815

1916
[Export(typeof(ISourceLinkService)), Shared]
20-
internal class SourceLinkService : ISourceLinkService
17+
internal class SourceLinkService : AbstractSourceLinkService
2118
{
2219
private readonly IDebuggerSymbolLocatorService _debuggerSymbolLocatorService;
2320
private readonly IDebuggerSourceLinkService _debuggerSourceLinkService;
@@ -35,74 +32,15 @@ public SourceLinkService(
3532
_logger = logger;
3633
}
3734

38-
public async Task<PdbFilePathResult?> GetPdbFilePathAsync(string dllPath, PEReader peReader, bool useDefaultSymbolServers, CancellationToken cancellationToken)
35+
protected override async Task<SymbolLocatorResult?> LocateSymbolFileAsync(SymbolLocatorPdbInfo pdbInfo, SymbolLocatorSearchFlags flags, CancellationToken cancellationToken)
3936
{
40-
var hasCodeViewEntry = false;
41-
uint timeStamp = 0;
42-
CodeViewDebugDirectoryData codeViewEntry = default;
43-
using var _ = ArrayBuilder<PdbChecksum>.GetInstance(out var checksums);
44-
foreach (var entry in peReader.ReadDebugDirectory())
45-
{
46-
if (entry.Type == DebugDirectoryEntryType.PdbChecksum)
47-
{
48-
var checksum = peReader.ReadPdbChecksumDebugDirectoryData(entry);
49-
checksums.Add(new PdbChecksum(checksum.AlgorithmName, checksum.Checksum));
50-
}
51-
else if (entry.Type == DebugDirectoryEntryType.CodeView && entry.IsPortableCodeView)
52-
{
53-
hasCodeViewEntry = true;
54-
timeStamp = entry.Stamp;
55-
codeViewEntry = peReader.ReadCodeViewDebugDirectoryData(entry);
56-
}
57-
}
58-
59-
if (!hasCodeViewEntry)
60-
return null;
61-
62-
var pdbInfo = new SymbolLocatorPdbInfo(
63-
Path.GetFileName(codeViewEntry.Path),
64-
codeViewEntry.Guid,
65-
(uint)codeViewEntry.Age,
66-
timeStamp,
67-
checksums.ToImmutable(),
68-
dllPath,
69-
codeViewEntry.Path);
70-
71-
var flags = useDefaultSymbolServers
72-
? SymbolLocatorSearchFlags.ForceNuGetSymbolServer | SymbolLocatorSearchFlags.ForceMsftSymbolServer
73-
: SymbolLocatorSearchFlags.None;
74-
var result = await _debuggerSymbolLocatorService.LocateSymbolFileAsync(pdbInfo, flags, progress: null, cancellationToken).ConfigureAwait(false);
75-
76-
if (result.Found && result.SymbolFilePath is not null)
77-
{
78-
return new PdbFilePathResult(result.SymbolFilePath);
79-
}
80-
else if (_logger is not null)
81-
{
82-
// We log specific info from the debugger if there is a failure, but the caller will log general failure
83-
// information otherwise
84-
_logger.Log(result.Status);
85-
_logger.Log(result.Log);
86-
}
87-
88-
return null;
37+
return await _debuggerSymbolLocatorService.LocateSymbolFileAsync(pdbInfo, flags, progress: null, cancellationToken).ConfigureAwait(false);
8938
}
9039

91-
public async Task<SourceFilePathResult?> GetSourceFilePathAsync(string url, string relativePath, CancellationToken cancellationToken)
40+
protected override async Task<SourceLinkResult?> GetSourceLinkAsync(string url, string relativePath, CancellationToken cancellationToken)
9241
{
93-
var result = await _debuggerSourceLinkService.GetSourceLinkAsync(url, relativePath, allowInteractiveLogin: false, cancellationToken).ConfigureAwait(false);
94-
95-
if (result.Status == SourceLinkResultStatus.Succeeded && result.Path is not null)
96-
{
97-
return new SourceFilePathResult(result.Path);
98-
}
99-
else if (_logger is not null && result.Log is not null)
100-
{
101-
// We log specific info from the debugger if there is a failure, but the caller will log general failure
102-
// information otherwise.
103-
_logger.Log(result.Log);
104-
}
105-
106-
return null;
42+
return await _debuggerSourceLinkService.GetSourceLinkAsync(url, relativePath, allowInteractiveLogin: false, cancellationToken).ConfigureAwait(false);
10743
}
44+
45+
protected override IPdbSourceDocumentLogger? Logger => _logger;
10846
}

src/VisualStudio/DevKit/Impl/Microsoft.VisualStudio.LanguageServices.DevKit.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
<Compile Include="..\..\..\EditorFeatures\Core\EditAndContinue\Contracts\ContractWrappers.cs" Link="EditAndContinue\ContractWrappers.cs" />
1818
<Compile Include="..\..\..\EditorFeatures\Core\EditAndContinue\Contracts\ManagedHotReloadServiceBridge.cs" Link="EditAndContinue\ManagedHotReloadServiceBridge.cs" />
1919
<Compile Include="..\..\..\VisualStudio\Core\Def\Telemetry\Shared\*.cs" LinkBase="Logging" />
20+
<Compile Include="..\..\..\VisualStudio\Core\Def\PdbSourceDocument\AbstractSourceLinkService.cs" />
2021
</ItemGroup>
2122

2223
<ItemGroup>
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
6+
using System.Composition;
7+
using Microsoft.CodeAnalysis.Host.Mef;
8+
using Microsoft.CodeAnalysis.PdbSourceDocument;
9+
using Microsoft.Extensions.Logging;
10+
11+
namespace Microsoft.CodeAnalysis.LanguageServer.Services.SourceLink;
12+
13+
[Export(typeof(IPdbSourceDocumentLogger)), Shared]
14+
[method: ImportingConstructor]
15+
[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
16+
internal class VSCodePdbSourceDocumentLogger(ILoggerFactory loggerFactory) : IPdbSourceDocumentLogger
17+
{
18+
private readonly ILogger _logger = loggerFactory.CreateLogger("SourceLink");
19+
20+
public void Clear()
21+
{
22+
// Do nothing, we just leave all the logs up.
23+
return;
24+
}
25+
26+
public void Log(string message)
27+
{
28+
_logger.LogTrace(message);
29+
}
30+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
6+
using System.Composition;
7+
using System.Threading;
8+
using System.Threading.Tasks;
9+
using Microsoft.CodeAnalysis.BrokeredServices;
10+
using Microsoft.CodeAnalysis.Host.Mef;
11+
using Microsoft.CodeAnalysis.PdbSourceDocument;
12+
using Microsoft.ServiceHub.Framework;
13+
using Microsoft.VisualStudio.Debugger.Contracts.SourceLink;
14+
using Microsoft.VisualStudio.Debugger.Contracts.SymbolLocator;
15+
using Microsoft.VisualStudio.LanguageServices.PdbSourceDocument;
16+
17+
namespace Microsoft.CodeAnalysis.LanguageServer.Services.SourceLink;
18+
19+
[Export(typeof(ISourceLinkService)), Shared]
20+
[method: ImportingConstructor]
21+
[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
22+
internal class VSCodeSourceLinkService(IServiceBrokerProvider serviceBrokerProvider, IPdbSourceDocumentLogger logger) : AbstractSourceLinkService
23+
{
24+
private readonly IServiceBroker _serviceBroker = serviceBrokerProvider.ServiceBroker;
25+
26+
protected override async Task<SymbolLocatorResult?> LocateSymbolFileAsync(SymbolLocatorPdbInfo pdbInfo, SymbolLocatorSearchFlags flags, CancellationToken cancellationToken)
27+
{
28+
var proxy = await _serviceBroker.GetProxyAsync<IDebuggerSymbolLocatorService>(BrokeredServiceDescriptors.DebuggerSymbolLocatorService, cancellationToken).ConfigureAwait(false);
29+
using ((IDisposable?)proxy)
30+
{
31+
if (proxy is null)
32+
{
33+
return null;
34+
}
35+
36+
try
37+
{
38+
var result = await proxy.LocateSymbolFileAsync(pdbInfo, flags, progress: null, cancellationToken).ConfigureAwait(false);
39+
return result;
40+
}
41+
catch (StreamJsonRpc.RemoteMethodNotFoundException)
42+
{
43+
// Older versions of DevKit use an invalid service descriptor - calling it will throw a RemoteMethodNotFoundException.
44+
// Just return null as there isn't a valid service available.
45+
return null;
46+
}
47+
}
48+
}
49+
50+
protected override async Task<SourceLinkResult?> GetSourceLinkAsync(string url, string relativePath, CancellationToken cancellationToken)
51+
{
52+
var proxy = await _serviceBroker.GetProxyAsync<IDebuggerSourceLinkService>(BrokeredServiceDescriptors.DebuggerSourceLinkService, cancellationToken).ConfigureAwait(false);
53+
using ((IDisposable?)proxy)
54+
{
55+
if (proxy is null)
56+
{
57+
return null;
58+
}
59+
60+
try
61+
{
62+
var result = await proxy.GetSourceLinkAsync(url, relativePath, allowInteractiveLogin: false, cancellationToken).ConfigureAwait(false);
63+
return result;
64+
}
65+
catch (StreamJsonRpc.RemoteMethodNotFoundException)
66+
{
67+
// Older versions of DevKit use an invalid service descriptor - calling it will throw a RemoteMethodNotFoundException.
68+
// Just return null as there isn't a valid service available.
69+
return null;
70+
}
71+
}
72+
}
73+
74+
protected override IPdbSourceDocumentLogger? Logger => logger;
75+
}

src/Workspaces/Remote/Core/BrokeredServiceDescriptors.cs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ protected override JsonRpcConnection CreateConnection(JsonRpc jsonRpc)
7777
public static readonly ServiceRpcDescriptor GenericHotReloadAgentManagerService = CreateDebuggerServiceDescriptor("GenericHotReloadAgentManagerService", new Version(0, 1));
7878
public static readonly ServiceRpcDescriptor HotReloadOptionService = CreateDebuggerClientServiceDescriptor("HotReloadOptionService", new Version(0, 1));
7979
public static readonly ServiceRpcDescriptor MauiLaunchCustomizerService = CreateMauiServiceDescriptor("MauiLaunchCustomizerService", new Version(0, 1));
80+
public static readonly ServiceRpcDescriptor DebuggerSymbolLocatorService =
81+
CreateDebuggerServiceDescriptor("SymbolLocatorService", new Version(0, 1), new MultiplexingStream.Options { ProtocolMajorVersion = 3 });
82+
public static readonly ServiceRpcDescriptor DebuggerSourceLinkService =
83+
CreateDebuggerServiceDescriptor("SourceLinkService", new Version(0, 1), new MultiplexingStream.Options { ProtocolMajorVersion = 3 });
8084

8185
public static ServiceMoniker CreateMoniker(string namespaceName, string componentName, string serviceName, Version? version)
8286
=> new(namespaceName + "." + componentName + "." + serviceName, version);
@@ -97,8 +101,8 @@ public static ServiceJsonRpcDescriptor CreateServerServiceDescriptor(string serv
97101
/// <summary>
98102
/// Descriptor for services proferred by the debugger server (implemented in C#).
99103
/// </summary>
100-
public static ServiceJsonRpcDescriptor CreateDebuggerServiceDescriptor(string serviceName, Version? version = null)
101-
=> CreateDescriptor(CreateMoniker(VisualStudioComponentNamespace, DebuggerComponentName, serviceName, version));
104+
public static ServiceJsonRpcDescriptor CreateDebuggerServiceDescriptor(string serviceName, Version? version = null, MultiplexingStream.Options? streamOptions = null)
105+
=> CreateDescriptor(CreateMoniker(VisualStudioComponentNamespace, DebuggerComponentName, serviceName, version), streamOptions);
102106

103107
/// <summary>
104108
/// Descriptor for services proferred by the debugger server (implemented in TypeScript).
@@ -116,7 +120,11 @@ public static ServiceJsonRpcDescriptor CreateMauiServiceDescriptor(string servic
116120
new MultiplexingStream.Options { ProtocolMajorVersion = 3 })
117121
.WithExceptionStrategy(ExceptionProcessing.ISerializable);
118122

119-
private static ServiceJsonRpcDescriptor CreateDescriptor(ServiceMoniker moniker)
120-
=> new ServiceJsonRpcDescriptor(moniker, Formatters.MessagePack, MessageDelimiters.BigEndianInt32LengthHeader)
121-
.WithExceptionStrategy(ExceptionProcessing.ISerializable);
123+
private static ServiceJsonRpcDescriptor CreateDescriptor(ServiceMoniker moniker, MultiplexingStream.Options? streamOptions = null)
124+
{
125+
var descriptor = streamOptions is not null
126+
? new ServiceJsonRpcDescriptor(moniker, clientInterface: null, Formatters.MessagePack, MessageDelimiters.BigEndianInt32LengthHeader, streamOptions)
127+
: new ServiceJsonRpcDescriptor(moniker, Formatters.MessagePack, MessageDelimiters.BigEndianInt32LengthHeader);
128+
return descriptor.WithExceptionStrategy(ExceptionProcessing.ISerializable);
129+
}
122130
}

0 commit comments

Comments
 (0)