Skip to content

Commit 00dafaa

Browse files
Debug Adapter Server / Client update (#267)
* Started work on DAP client and server * added missing properties, events, handlers and such. Everything compiles, but needs unit tests! * added unit tests around initialization, cancellation and progress * Added support for custom attach and launch request objectS * Updated module to allow extension data * Added support for derived Attach and Launch handlers for DAP * Added test to validate that handler collection supports handlers that implement more than one interface +semver:minor
1 parent 7c6bbaf commit 00dafaa

File tree

134 files changed

+3783
-380
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

134 files changed

+3783
-380
lines changed

LSP.sln

+15
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JsonRpc.Generators", "src\J
7272
EndProject
7373
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Generation.Tests", "test\Generation.Tests\Generation.Tests.csproj", "{671FFF78-BDD2-4389-B29C-BFD183DA9120}"
7474
EndProject
75+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dap.Shared", "src\Dap.Shared\Dap.Shared.csproj", "{010D4BE7-6A92-4A04-B4EB-745FA3130DF2}"
76+
EndProject
7577
Global
7678
GlobalSection(SolutionConfigurationPlatforms) = preSolution
7779
Debug|Any CPU = Debug|Any CPU
@@ -316,6 +318,18 @@ Global
316318
{671FFF78-BDD2-4389-B29C-BFD183DA9120}.Release|x64.Build.0 = Release|Any CPU
317319
{671FFF78-BDD2-4389-B29C-BFD183DA9120}.Release|x86.ActiveCfg = Release|Any CPU
318320
{671FFF78-BDD2-4389-B29C-BFD183DA9120}.Release|x86.Build.0 = Release|Any CPU
321+
{010D4BE7-6A92-4A04-B4EB-745FA3130DF2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
322+
{010D4BE7-6A92-4A04-B4EB-745FA3130DF2}.Debug|Any CPU.Build.0 = Debug|Any CPU
323+
{010D4BE7-6A92-4A04-B4EB-745FA3130DF2}.Debug|x64.ActiveCfg = Debug|Any CPU
324+
{010D4BE7-6A92-4A04-B4EB-745FA3130DF2}.Debug|x64.Build.0 = Debug|Any CPU
325+
{010D4BE7-6A92-4A04-B4EB-745FA3130DF2}.Debug|x86.ActiveCfg = Debug|Any CPU
326+
{010D4BE7-6A92-4A04-B4EB-745FA3130DF2}.Debug|x86.Build.0 = Debug|Any CPU
327+
{010D4BE7-6A92-4A04-B4EB-745FA3130DF2}.Release|Any CPU.ActiveCfg = Release|Any CPU
328+
{010D4BE7-6A92-4A04-B4EB-745FA3130DF2}.Release|Any CPU.Build.0 = Release|Any CPU
329+
{010D4BE7-6A92-4A04-B4EB-745FA3130DF2}.Release|x64.ActiveCfg = Release|Any CPU
330+
{010D4BE7-6A92-4A04-B4EB-745FA3130DF2}.Release|x64.Build.0 = Release|Any CPU
331+
{010D4BE7-6A92-4A04-B4EB-745FA3130DF2}.Release|x86.ActiveCfg = Release|Any CPU
332+
{010D4BE7-6A92-4A04-B4EB-745FA3130DF2}.Release|x86.Build.0 = Release|Any CPU
319333
EndGlobalSection
320334
GlobalSection(SolutionProperties) = preSolution
321335
HideSolutionNode = FALSE
@@ -341,6 +355,7 @@ Global
341355
{202BA1AB-25DA-44ED-B962-FD82FCC74543} = {D764E024-3D3F-4112-B932-2DB722A1BACC}
342356
{DE259174-73DC-4532-B641-AD218971EE29} = {D764E024-3D3F-4112-B932-2DB722A1BACC}
343357
{671FFF78-BDD2-4389-B29C-BFD183DA9120} = {2F323ED5-EBF8-45E1-B9D3-C014561B3DDA}
358+
{010D4BE7-6A92-4A04-B4EB-745FA3130DF2} = {D764E024-3D3F-4112-B932-2DB722A1BACC}
344359
EndGlobalSection
345360
GlobalSection(ExtensibilityGlobals) = postSolution
346361
SolutionGuid = {D38DD0EC-D095-4BCD-B8AF-2D788AF3B9AE}

src/Client/LanguageClient.cs

+12-2
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ public static ILanguageClient PreInit(Action<LanguageClientOptions> optionsActio
8080

8181
public static async Task<ILanguageClient> From(LanguageClientOptions options, CancellationToken token)
8282
{
83-
var server = (LanguageClient)PreInit(options);
83+
var server = (LanguageClient) PreInit(options);
8484
await server.Initialize(token);
8585

8686
return server;
@@ -238,6 +238,14 @@ public async Task Initialize(CancellationToken token)
238238
var serverParams = await this.RequestLanguageProtocolInitialize(ClientSettings, token);
239239
_receiver.Initialized();
240240

241+
await _startedDelegates.Select(@delegate =>
242+
Observable.FromAsync(() => @delegate(this, serverParams, token))
243+
)
244+
.ToObservable()
245+
.Merge()
246+
.LastOrDefaultAsync()
247+
.ToTask(token);
248+
241249
ServerSettings = serverParams;
242250
if (_collection.ContainsHandler(typeof(IRegisterCapabilityHandler)))
243251
RegistrationManager.RegisterCapabilities(serverParams.Capabilities);
@@ -349,6 +357,7 @@ public void Dispose()
349357
object IServiceProvider.GetService(Type serviceType) => _serviceProvider.GetService(serviceType);
350358
protected override IResponseRouter ResponseRouter => _responseRouter;
351359
protected override IHandlersManager HandlersManager => _collection;
360+
352361
public IDisposable Register(Action<ILanguageClientRegistry> registryAction)
353362
{
354363
var manager = new CompositeHandlersManager(_collection);
@@ -359,7 +368,8 @@ public IDisposable Register(Action<ILanguageClientRegistry> registryAction)
359368

360369
class LangaugeClientRegistry : InterimLanguageProtocolRegistry<ILanguageClientRegistry>, ILanguageClientRegistry
361370
{
362-
public LangaugeClientRegistry(IServiceProvider serviceProvider, CompositeHandlersManager handlersManager, TextDocumentIdentifiers textDocumentIdentifiers) : base(serviceProvider, handlersManager, textDocumentIdentifiers)
371+
public LangaugeClientRegistry(IServiceProvider serviceProvider, CompositeHandlersManager handlersManager, TextDocumentIdentifiers textDocumentIdentifiers) : base(
372+
serviceProvider, handlersManager, textDocumentIdentifiers)
363373
{
364374
}
365375
}
+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
using System;
2+
using System.Collections.Concurrent;
3+
using System.Collections.Generic;
4+
using System.Reactive.Disposables;
5+
using System.Reactive.Linq;
6+
using System.Reactive.Subjects;
7+
using System.Threading;
8+
using System.Threading.Tasks;
9+
using MediatR;
10+
using OmniSharp.Extensions.DebugAdapter.Protocol.Events;
11+
using OmniSharp.Extensions.DebugAdapter.Protocol.Models;
12+
13+
namespace OmniSharp.Extensions.DebugAdapter.Client
14+
{
15+
public class ClientProgressManager : IProgressStartHandler, IProgressUpdateHandler, IProgressEndHandler, IClientProgressManager, IDisposable
16+
{
17+
private readonly IObserver<IProgressObservable> _observer;
18+
private readonly CompositeDisposable _disposable = new CompositeDisposable();
19+
private readonly ConcurrentDictionary<ProgressToken, ProgressObservable> _activeObservables = new ConcurrentDictionary<ProgressToken, ProgressObservable>(EqualityComparer<ProgressToken>.Default);
20+
21+
public ClientProgressManager()
22+
{
23+
var subject = new Subject<IProgressObservable>();
24+
_disposable.Add(subject);
25+
Progress = subject.AsObservable();
26+
_observer = subject;
27+
}
28+
29+
public IObservable<IProgressObservable> Progress { get; }
30+
31+
Task<Unit> IRequestHandler<ProgressStartEvent, Unit>.Handle(ProgressStartEvent request, CancellationToken cancellationToken)
32+
{
33+
var observable = new ProgressObservable(request.ProgressId);
34+
_activeObservables.TryAdd(request.ProgressId, observable);
35+
observable.OnNext(request);
36+
_observer.OnNext(observable);
37+
38+
return Unit.Task;
39+
}
40+
41+
Task<Unit> IRequestHandler<ProgressUpdateEvent, Unit>.Handle(ProgressUpdateEvent request, CancellationToken cancellationToken)
42+
{
43+
if (_activeObservables.TryGetValue(request.ProgressId, out var observable))
44+
{
45+
observable.OnNext(request);
46+
}
47+
48+
// TODO: Add log message for unhandled?
49+
return Unit.Task;
50+
}
51+
52+
Task<Unit> IRequestHandler<ProgressEndEvent, Unit>.Handle(ProgressEndEvent request, CancellationToken cancellationToken)
53+
{
54+
if (_activeObservables.TryGetValue(request.ProgressId, out var observable))
55+
{
56+
observable.OnNext(request);
57+
}
58+
59+
// TODO: Add log message for unhandled?
60+
return Unit.Task;
61+
}
62+
63+
public void Dispose() => _disposable?.Dispose();
64+
}
65+
}

src/Dap.Client/Dap.Client.csproj

+1-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
</ItemGroup>
1414

1515
<ItemGroup>
16-
<ProjectReference Include="..\JsonRpc\JsonRpc.csproj" />
17-
<ProjectReference Include="..\Dap.Protocol\Dap.Protocol.csproj" />
16+
<ProjectReference Include="..\Dap.Shared\Dap.Shared.csproj" />
1817
</ItemGroup>
1918
</Project>

src/Dap.Client/DebugAdapterClient.cs

+198
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Reactive.Disposables;
5+
using System.Reactive.Linq;
6+
using System.Reactive.Subjects;
7+
using System.Reactive.Threading.Tasks;
8+
using System.Threading;
9+
using System.Threading.Tasks;
10+
using MediatR;
11+
using Microsoft.Extensions.DependencyInjection;
12+
using Microsoft.Extensions.Logging;
13+
using OmniSharp.Extensions.DebugAdapter.Protocol;
14+
using OmniSharp.Extensions.DebugAdapter.Protocol.Events;
15+
using OmniSharp.Extensions.DebugAdapter.Protocol.Requests;
16+
using OmniSharp.Extensions.DebugAdapter.Shared;
17+
using OmniSharp.Extensions.JsonRpc;
18+
using IOutputHandler = OmniSharp.Extensions.JsonRpc.IOutputHandler;
19+
using OutputHandler = OmniSharp.Extensions.JsonRpc.OutputHandler;
20+
21+
namespace OmniSharp.Extensions.DebugAdapter.Client
22+
{
23+
public class DebugAdapterClient : JsonRpcServerBase, IDebugAdapterClient, IInitializedHandler
24+
{
25+
private readonly DebugAdapterHandlerCollection _collection;
26+
private readonly IEnumerable<OnClientStartedDelegate> _startedDelegates;
27+
private readonly CompositeDisposable _disposable = new CompositeDisposable();
28+
private readonly Connection _connection;
29+
private readonly IClientProgressManager _progressManager;
30+
private readonly DapReceiver _receiver;
31+
private readonly ISubject<InitializedEvent> _initializedComplete = new AsyncSubject<InitializedEvent>();
32+
33+
public static Task<IDebugAdapterClient> From(Action<DebugAdapterClientOptions> optionsAction)
34+
{
35+
return From(optionsAction, CancellationToken.None);
36+
}
37+
38+
public static Task<IDebugAdapterClient> From(DebugAdapterClientOptions options)
39+
{
40+
return From(options, CancellationToken.None);
41+
}
42+
43+
public static Task<IDebugAdapterClient> From(Action<DebugAdapterClientOptions> optionsAction, CancellationToken token)
44+
{
45+
var options = new DebugAdapterClientOptions();
46+
optionsAction(options);
47+
return From(options, token);
48+
}
49+
50+
public static IDebugAdapterClient PreInit(Action<DebugAdapterClientOptions> optionsAction)
51+
{
52+
var options = new DebugAdapterClientOptions();
53+
optionsAction(options);
54+
return PreInit(options);
55+
}
56+
57+
public static async Task<IDebugAdapterClient> From(DebugAdapterClientOptions options, CancellationToken token)
58+
{
59+
var server = (DebugAdapterClient) PreInit(options);
60+
await server.Initialize(token);
61+
62+
return server;
63+
}
64+
65+
/// <summary>
66+
/// Create the server without connecting to the client
67+
///
68+
/// Mainly used for unit testing
69+
/// </summary>
70+
/// <param name="options"></param>
71+
/// <returns></returns>
72+
public static IDebugAdapterClient PreInit(DebugAdapterClientOptions options)
73+
{
74+
return new DebugAdapterClient(options);
75+
}
76+
77+
internal DebugAdapterClient(DebugAdapterClientOptions options) : base(options)
78+
{
79+
var services = options.Services;
80+
services.AddLogging(builder => options.LoggingBuilderAction(builder));
81+
82+
ClientSettings = new InitializeRequestArguments() {
83+
Locale = options.Locale,
84+
AdapterId = options.AdapterId,
85+
ClientId = options.ClientId,
86+
ClientName = options.ClientName,
87+
PathFormat = options.PathFormat,
88+
ColumnsStartAt1 = options.ColumnsStartAt1,
89+
LinesStartAt1 = options.LinesStartAt1,
90+
SupportsMemoryReferences = options.SupportsMemoryReferences,
91+
SupportsProgressReporting = options.SupportsProgressReporting,
92+
SupportsVariablePaging = options.SupportsVariablePaging,
93+
SupportsVariableType = options.SupportsVariableType,
94+
SupportsRunInTerminalRequest = options.SupportsRunInTerminalRequest,
95+
};
96+
97+
var serializer = options.Serializer;
98+
var collection = new DebugAdapterHandlerCollection();
99+
services.AddSingleton<IHandlersManager>(collection);
100+
_collection = collection;
101+
// _initializeDelegates = initializeDelegates;
102+
// _initializedDelegates = initializedDelegates;
103+
_startedDelegates = options.StartedDelegates;
104+
105+
var receiver = _receiver = new DapReceiver();
106+
107+
services.AddSingleton<IOutputHandler>(_ =>
108+
new OutputHandler(options.Output, options.Serializer, receiver.ShouldFilterOutput, _.GetService<ILogger<OutputHandler>>()));
109+
services.AddSingleton(_collection);
110+
services.AddSingleton(serializer);
111+
services.AddSingleton(options.RequestProcessIdentifier);
112+
services.AddSingleton(receiver);
113+
services.AddSingleton<IDebugAdapterClient>(this);
114+
services.AddSingleton<DebugAdapterRequestRouter>();
115+
services.AddSingleton<IRequestRouter<IHandlerDescriptor>>(_ => _.GetRequiredService<DebugAdapterRequestRouter>());
116+
services.AddSingleton<IResponseRouter, DapResponseRouter>();
117+
118+
services.AddSingleton<IClientProgressManager, ClientProgressManager>();
119+
services.AddSingleton(_ => _.GetRequiredService<IClientProgressManager>() as IJsonRpcHandler);
120+
121+
EnsureAllHandlersAreRegistered();
122+
123+
var serviceProvider = services.BuildServiceProvider();
124+
_disposable.Add(serviceProvider);
125+
IServiceProvider serviceProvider1 = serviceProvider;
126+
127+
var responseRouter = serviceProvider1.GetRequiredService<IResponseRouter>();
128+
ResponseRouter = responseRouter;
129+
_progressManager = serviceProvider1.GetRequiredService<IClientProgressManager>();
130+
131+
_connection = new Connection(
132+
options.Input,
133+
serviceProvider1.GetRequiredService<IOutputHandler>(),
134+
receiver,
135+
options.RequestProcessIdentifier,
136+
serviceProvider1.GetRequiredService<IRequestRouter<IHandlerDescriptor>>(),
137+
responseRouter,
138+
serviceProvider1.GetRequiredService<ILoggerFactory>(),
139+
options.OnUnhandledException ?? (e => { }),
140+
options.CreateResponseException,
141+
options.MaximumRequestTimeout,
142+
false,
143+
options.Concurrency
144+
);
145+
146+
var serviceHandlers = serviceProvider1.GetServices<IJsonRpcHandler>().ToArray();
147+
_collection.Add(this);
148+
_disposable.Add(_collection.Add(serviceHandlers));
149+
options.AddLinks(_collection);
150+
}
151+
152+
public async Task Initialize(CancellationToken token)
153+
{
154+
RegisterCapabilities(ClientSettings);
155+
156+
_connection.Open();
157+
var serverParams = await this.RequestInitialize(ClientSettings, token);
158+
159+
ServerSettings = serverParams;
160+
_receiver.Initialized();
161+
162+
await _initializedComplete.ToTask(token);
163+
164+
await _startedDelegates.Select(@delegate => Observable.FromAsync(() => @delegate(this, serverParams, token)))
165+
.ToObservable()
166+
.Merge()
167+
.LastOrDefaultAsync()
168+
.ToTask(token);
169+
}
170+
171+
Task<Unit> IRequestHandler<InitializedEvent, Unit>.Handle(InitializedEvent request, CancellationToken cancellationToken)
172+
{
173+
_initializedComplete.OnNext(request);
174+
_initializedComplete.OnCompleted();
175+
return Unit.Task;
176+
}
177+
178+
private void RegisterCapabilities(InitializeRequestArguments capabilities)
179+
{
180+
capabilities.SupportsRunInTerminalRequest ??= _collection.ContainsHandler(typeof(IRunInTerminalHandler));
181+
capabilities.SupportsProgressReporting ??= _collection.ContainsHandler(typeof(IProgressStartHandler)) &&
182+
_collection.ContainsHandler(typeof(IProgressUpdateHandler)) &&
183+
_collection.ContainsHandler(typeof(IProgressEndHandler));
184+
}
185+
186+
protected override IResponseRouter ResponseRouter { get; }
187+
protected override IHandlersManager HandlersManager => _collection;
188+
public InitializeRequestArguments ClientSettings { get; }
189+
public InitializeResponse ServerSettings { get; private set; }
190+
public IClientProgressManager ProgressManager => _progressManager;
191+
192+
public void Dispose()
193+
{
194+
_disposable?.Dispose();
195+
_connection?.Dispose();
196+
}
197+
}
198+
}

0 commit comments

Comments
 (0)