Skip to content

Commit

Permalink
Debug Adapter Server / Client update (#267)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
david-driscoll authored Aug 1, 2020
1 parent 7c6bbaf commit 00dafaa
Show file tree
Hide file tree
Showing 134 changed files with 3,783 additions and 380 deletions.
15 changes: 15 additions & 0 deletions LSP.sln
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JsonRpc.Generators", "src\J
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Generation.Tests", "test\Generation.Tests\Generation.Tests.csproj", "{671FFF78-BDD2-4389-B29C-BFD183DA9120}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dap.Shared", "src\Dap.Shared\Dap.Shared.csproj", "{010D4BE7-6A92-4A04-B4EB-745FA3130DF2}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -316,6 +318,18 @@ Global
{671FFF78-BDD2-4389-B29C-BFD183DA9120}.Release|x64.Build.0 = Release|Any CPU
{671FFF78-BDD2-4389-B29C-BFD183DA9120}.Release|x86.ActiveCfg = Release|Any CPU
{671FFF78-BDD2-4389-B29C-BFD183DA9120}.Release|x86.Build.0 = Release|Any CPU
{010D4BE7-6A92-4A04-B4EB-745FA3130DF2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{010D4BE7-6A92-4A04-B4EB-745FA3130DF2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{010D4BE7-6A92-4A04-B4EB-745FA3130DF2}.Debug|x64.ActiveCfg = Debug|Any CPU
{010D4BE7-6A92-4A04-B4EB-745FA3130DF2}.Debug|x64.Build.0 = Debug|Any CPU
{010D4BE7-6A92-4A04-B4EB-745FA3130DF2}.Debug|x86.ActiveCfg = Debug|Any CPU
{010D4BE7-6A92-4A04-B4EB-745FA3130DF2}.Debug|x86.Build.0 = Debug|Any CPU
{010D4BE7-6A92-4A04-B4EB-745FA3130DF2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{010D4BE7-6A92-4A04-B4EB-745FA3130DF2}.Release|Any CPU.Build.0 = Release|Any CPU
{010D4BE7-6A92-4A04-B4EB-745FA3130DF2}.Release|x64.ActiveCfg = Release|Any CPU
{010D4BE7-6A92-4A04-B4EB-745FA3130DF2}.Release|x64.Build.0 = Release|Any CPU
{010D4BE7-6A92-4A04-B4EB-745FA3130DF2}.Release|x86.ActiveCfg = Release|Any CPU
{010D4BE7-6A92-4A04-B4EB-745FA3130DF2}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -341,6 +355,7 @@ Global
{202BA1AB-25DA-44ED-B962-FD82FCC74543} = {D764E024-3D3F-4112-B932-2DB722A1BACC}
{DE259174-73DC-4532-B641-AD218971EE29} = {D764E024-3D3F-4112-B932-2DB722A1BACC}
{671FFF78-BDD2-4389-B29C-BFD183DA9120} = {2F323ED5-EBF8-45E1-B9D3-C014561B3DDA}
{010D4BE7-6A92-4A04-B4EB-745FA3130DF2} = {D764E024-3D3F-4112-B932-2DB722A1BACC}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {D38DD0EC-D095-4BCD-B8AF-2D788AF3B9AE}
Expand Down
14 changes: 12 additions & 2 deletions src/Client/LanguageClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ public static ILanguageClient PreInit(Action<LanguageClientOptions> optionsActio

public static async Task<ILanguageClient> From(LanguageClientOptions options, CancellationToken token)
{
var server = (LanguageClient)PreInit(options);
var server = (LanguageClient) PreInit(options);
await server.Initialize(token);

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

await _startedDelegates.Select(@delegate =>
Observable.FromAsync(() => @delegate(this, serverParams, token))
)
.ToObservable()
.Merge()
.LastOrDefaultAsync()
.ToTask(token);

ServerSettings = serverParams;
if (_collection.ContainsHandler(typeof(IRegisterCapabilityHandler)))
RegistrationManager.RegisterCapabilities(serverParams.Capabilities);
Expand Down Expand Up @@ -349,6 +357,7 @@ public void Dispose()
object IServiceProvider.GetService(Type serviceType) => _serviceProvider.GetService(serviceType);
protected override IResponseRouter ResponseRouter => _responseRouter;
protected override IHandlersManager HandlersManager => _collection;

public IDisposable Register(Action<ILanguageClientRegistry> registryAction)
{
var manager = new CompositeHandlersManager(_collection);
Expand All @@ -359,7 +368,8 @@ public IDisposable Register(Action<ILanguageClientRegistry> registryAction)

class LangaugeClientRegistry : InterimLanguageProtocolRegistry<ILanguageClientRegistry>, ILanguageClientRegistry
{
public LangaugeClientRegistry(IServiceProvider serviceProvider, CompositeHandlersManager handlersManager, TextDocumentIdentifiers textDocumentIdentifiers) : base(serviceProvider, handlersManager, textDocumentIdentifiers)
public LangaugeClientRegistry(IServiceProvider serviceProvider, CompositeHandlersManager handlersManager, TextDocumentIdentifiers textDocumentIdentifiers) : base(
serviceProvider, handlersManager, textDocumentIdentifiers)
{
}
}
Expand Down
65 changes: 65 additions & 0 deletions src/Dap.Client/ClientProgressManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Threading;
using System.Threading.Tasks;
using MediatR;
using OmniSharp.Extensions.DebugAdapter.Protocol.Events;
using OmniSharp.Extensions.DebugAdapter.Protocol.Models;

namespace OmniSharp.Extensions.DebugAdapter.Client
{
public class ClientProgressManager : IProgressStartHandler, IProgressUpdateHandler, IProgressEndHandler, IClientProgressManager, IDisposable
{
private readonly IObserver<IProgressObservable> _observer;
private readonly CompositeDisposable _disposable = new CompositeDisposable();
private readonly ConcurrentDictionary<ProgressToken, ProgressObservable> _activeObservables = new ConcurrentDictionary<ProgressToken, ProgressObservable>(EqualityComparer<ProgressToken>.Default);

public ClientProgressManager()
{
var subject = new Subject<IProgressObservable>();
_disposable.Add(subject);
Progress = subject.AsObservable();
_observer = subject;
}

public IObservable<IProgressObservable> Progress { get; }

Task<Unit> IRequestHandler<ProgressStartEvent, Unit>.Handle(ProgressStartEvent request, CancellationToken cancellationToken)
{
var observable = new ProgressObservable(request.ProgressId);
_activeObservables.TryAdd(request.ProgressId, observable);
observable.OnNext(request);
_observer.OnNext(observable);

return Unit.Task;
}

Task<Unit> IRequestHandler<ProgressUpdateEvent, Unit>.Handle(ProgressUpdateEvent request, CancellationToken cancellationToken)
{
if (_activeObservables.TryGetValue(request.ProgressId, out var observable))
{
observable.OnNext(request);
}

// TODO: Add log message for unhandled?
return Unit.Task;
}

Task<Unit> IRequestHandler<ProgressEndEvent, Unit>.Handle(ProgressEndEvent request, CancellationToken cancellationToken)
{
if (_activeObservables.TryGetValue(request.ProgressId, out var observable))
{
observable.OnNext(request);
}

// TODO: Add log message for unhandled?
return Unit.Task;
}

public void Dispose() => _disposable?.Dispose();
}
}
3 changes: 1 addition & 2 deletions src/Dap.Client/Dap.Client.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\JsonRpc\JsonRpc.csproj" />
<ProjectReference Include="..\Dap.Protocol\Dap.Protocol.csproj" />
<ProjectReference Include="..\Dap.Shared\Dap.Shared.csproj" />
</ItemGroup>
</Project>
198 changes: 198 additions & 0 deletions src/Dap.Client/DebugAdapterClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Reactive.Threading.Tasks;
using System.Threading;
using System.Threading.Tasks;
using MediatR;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using OmniSharp.Extensions.DebugAdapter.Protocol;
using OmniSharp.Extensions.DebugAdapter.Protocol.Events;
using OmniSharp.Extensions.DebugAdapter.Protocol.Requests;
using OmniSharp.Extensions.DebugAdapter.Shared;
using OmniSharp.Extensions.JsonRpc;
using IOutputHandler = OmniSharp.Extensions.JsonRpc.IOutputHandler;
using OutputHandler = OmniSharp.Extensions.JsonRpc.OutputHandler;

namespace OmniSharp.Extensions.DebugAdapter.Client
{
public class DebugAdapterClient : JsonRpcServerBase, IDebugAdapterClient, IInitializedHandler
{
private readonly DebugAdapterHandlerCollection _collection;
private readonly IEnumerable<OnClientStartedDelegate> _startedDelegates;
private readonly CompositeDisposable _disposable = new CompositeDisposable();
private readonly Connection _connection;
private readonly IClientProgressManager _progressManager;
private readonly DapReceiver _receiver;
private readonly ISubject<InitializedEvent> _initializedComplete = new AsyncSubject<InitializedEvent>();

public static Task<IDebugAdapterClient> From(Action<DebugAdapterClientOptions> optionsAction)
{
return From(optionsAction, CancellationToken.None);
}

public static Task<IDebugAdapterClient> From(DebugAdapterClientOptions options)
{
return From(options, CancellationToken.None);
}

public static Task<IDebugAdapterClient> From(Action<DebugAdapterClientOptions> optionsAction, CancellationToken token)
{
var options = new DebugAdapterClientOptions();
optionsAction(options);
return From(options, token);
}

public static IDebugAdapterClient PreInit(Action<DebugAdapterClientOptions> optionsAction)
{
var options = new DebugAdapterClientOptions();
optionsAction(options);
return PreInit(options);
}

public static async Task<IDebugAdapterClient> From(DebugAdapterClientOptions options, CancellationToken token)
{
var server = (DebugAdapterClient) PreInit(options);
await server.Initialize(token);

return server;
}

/// <summary>
/// Create the server without connecting to the client
///
/// Mainly used for unit testing
/// </summary>
/// <param name="options"></param>
/// <returns></returns>
public static IDebugAdapterClient PreInit(DebugAdapterClientOptions options)
{
return new DebugAdapterClient(options);
}

internal DebugAdapterClient(DebugAdapterClientOptions options) : base(options)
{
var services = options.Services;
services.AddLogging(builder => options.LoggingBuilderAction(builder));

ClientSettings = new InitializeRequestArguments() {
Locale = options.Locale,
AdapterId = options.AdapterId,
ClientId = options.ClientId,
ClientName = options.ClientName,
PathFormat = options.PathFormat,
ColumnsStartAt1 = options.ColumnsStartAt1,
LinesStartAt1 = options.LinesStartAt1,
SupportsMemoryReferences = options.SupportsMemoryReferences,
SupportsProgressReporting = options.SupportsProgressReporting,
SupportsVariablePaging = options.SupportsVariablePaging,
SupportsVariableType = options.SupportsVariableType,
SupportsRunInTerminalRequest = options.SupportsRunInTerminalRequest,
};

var serializer = options.Serializer;
var collection = new DebugAdapterHandlerCollection();
services.AddSingleton<IHandlersManager>(collection);
_collection = collection;
// _initializeDelegates = initializeDelegates;
// _initializedDelegates = initializedDelegates;
_startedDelegates = options.StartedDelegates;

var receiver = _receiver = new DapReceiver();

services.AddSingleton<IOutputHandler>(_ =>
new OutputHandler(options.Output, options.Serializer, receiver.ShouldFilterOutput, _.GetService<ILogger<OutputHandler>>()));
services.AddSingleton(_collection);
services.AddSingleton(serializer);
services.AddSingleton(options.RequestProcessIdentifier);
services.AddSingleton(receiver);
services.AddSingleton<IDebugAdapterClient>(this);
services.AddSingleton<DebugAdapterRequestRouter>();
services.AddSingleton<IRequestRouter<IHandlerDescriptor>>(_ => _.GetRequiredService<DebugAdapterRequestRouter>());
services.AddSingleton<IResponseRouter, DapResponseRouter>();

services.AddSingleton<IClientProgressManager, ClientProgressManager>();
services.AddSingleton(_ => _.GetRequiredService<IClientProgressManager>() as IJsonRpcHandler);

EnsureAllHandlersAreRegistered();

var serviceProvider = services.BuildServiceProvider();
_disposable.Add(serviceProvider);
IServiceProvider serviceProvider1 = serviceProvider;

var responseRouter = serviceProvider1.GetRequiredService<IResponseRouter>();
ResponseRouter = responseRouter;
_progressManager = serviceProvider1.GetRequiredService<IClientProgressManager>();

_connection = new Connection(
options.Input,
serviceProvider1.GetRequiredService<IOutputHandler>(),
receiver,
options.RequestProcessIdentifier,
serviceProvider1.GetRequiredService<IRequestRouter<IHandlerDescriptor>>(),
responseRouter,
serviceProvider1.GetRequiredService<ILoggerFactory>(),
options.OnUnhandledException ?? (e => { }),
options.CreateResponseException,
options.MaximumRequestTimeout,
false,
options.Concurrency
);

var serviceHandlers = serviceProvider1.GetServices<IJsonRpcHandler>().ToArray();
_collection.Add(this);
_disposable.Add(_collection.Add(serviceHandlers));
options.AddLinks(_collection);
}

public async Task Initialize(CancellationToken token)
{
RegisterCapabilities(ClientSettings);

_connection.Open();
var serverParams = await this.RequestInitialize(ClientSettings, token);

ServerSettings = serverParams;
_receiver.Initialized();

await _initializedComplete.ToTask(token);

await _startedDelegates.Select(@delegate => Observable.FromAsync(() => @delegate(this, serverParams, token)))
.ToObservable()
.Merge()
.LastOrDefaultAsync()
.ToTask(token);
}

Task<Unit> IRequestHandler<InitializedEvent, Unit>.Handle(InitializedEvent request, CancellationToken cancellationToken)
{
_initializedComplete.OnNext(request);
_initializedComplete.OnCompleted();
return Unit.Task;
}

private void RegisterCapabilities(InitializeRequestArguments capabilities)
{
capabilities.SupportsRunInTerminalRequest ??= _collection.ContainsHandler(typeof(IRunInTerminalHandler));
capabilities.SupportsProgressReporting ??= _collection.ContainsHandler(typeof(IProgressStartHandler)) &&
_collection.ContainsHandler(typeof(IProgressUpdateHandler)) &&
_collection.ContainsHandler(typeof(IProgressEndHandler));
}

protected override IResponseRouter ResponseRouter { get; }
protected override IHandlersManager HandlersManager => _collection;
public InitializeRequestArguments ClientSettings { get; }
public InitializeResponse ServerSettings { get; private set; }
public IClientProgressManager ProgressManager => _progressManager;

public void Dispose()
{
_disposable?.Dispose();
_connection?.Dispose();
}
}
}
Loading

0 comments on commit 00dafaa

Please sign in to comment.