From c6cea42d6c370c509d2564a30337d91e2a6d7745 Mon Sep 17 00:00:00 2001 From: David Driscoll Date: Sun, 2 Aug 2020 01:28:12 -0400 Subject: [PATCH] Fixed issues with codelens, documentlink and completion item data (#272) --- .../DebugAdapterHandlerCollection.cs | 27 ++- src/JsonRpc/HandlerCollection.cs | 34 +-- src/Protocol/Constants.cs | 8 +- src/Protocol/Document/ICodeLensHandler.cs | 32 +-- src/Protocol/Document/ICompletionHandler.cs | 32 +-- src/Protocol/Document/IDocumentLinkHandler.cs | 32 +-- src/Protocol/Models/CodeLens.cs | 64 +++--- src/Protocol/Models/CodeLensContainer.cs | 24 +-- src/Protocol/Models/CompletionItem.cs | 204 ++++++++++++++---- src/Protocol/Models/CompletionList.cs | 29 +-- src/Protocol/Models/DocumentLink.cs | 79 ++++--- src/Protocol/Models/DocumentLinkContainer.cs | 26 +-- src/Protocol/Models/HandlerIdentity.cs | 11 + src/Server/Matchers/ResolveCommandMatcher.cs | 6 +- .../Pipelines/ResolveCommandPipeline.cs | 14 +- src/Shared/SharedHandlerCollection.cs | 50 +++-- test/JsonRpc.Tests/HandlerResolverTests.cs | 2 +- test/Lsp.Tests/HandlerResolverTests.cs | 49 ++--- .../Integration/TypedCodeLensTests.cs | 6 +- .../Integration/TypedCompletionTests.cs | 8 +- .../Integration/TypedDocumentLinkTests.cs | 6 +- .../Matchers/ResolveCommandMatcherTests.cs | 12 +- 22 files changed, 437 insertions(+), 318 deletions(-) create mode 100644 src/Protocol/Models/HandlerIdentity.cs diff --git a/src/Dap.Shared/DebugAdapterHandlerCollection.cs b/src/Dap.Shared/DebugAdapterHandlerCollection.cs index 71feb9fd6..6e76eb4c1 100644 --- a/src/Dap.Shared/DebugAdapterHandlerCollection.cs +++ b/src/Dap.Shared/DebugAdapterHandlerCollection.cs @@ -1,9 +1,11 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using System.Reactive.Disposables; using System.Reflection; +using System.Threading; using MediatR; using OmniSharp.Extensions.JsonRpc; @@ -11,11 +13,11 @@ namespace OmniSharp.Extensions.DebugAdapter.Shared { class DebugAdapterHandlerCollection : IEnumerable, IHandlersManager { - internal readonly HashSet _handlers = new HashSet(); + private ImmutableHashSet _descriptors = ImmutableHashSet.Empty; public IEnumerator GetEnumerator() { - return _handlers.GetEnumerator(); + return _descriptors.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() @@ -29,7 +31,7 @@ IEnumerator IEnumerable.GetEnumerator() IDisposable IHandlersManager.AddLink(string sourceMethod, string destinationMethod) { - var source = _handlers.First(z => z.Method == sourceMethod); + var source = _descriptors.First(z => z.Method == sourceMethod); HandlerDescriptor descriptor = null; descriptor = GetDescriptor( destinationMethod, @@ -38,7 +40,7 @@ IDisposable IHandlersManager.AddLink(string sourceMethod, string destinationMeth source.RequestProcessType.HasValue ? new JsonRpcHandlerOptions() {RequestProcessType = source.RequestProcessType.Value} : null, source.TypeDescriptor, source.HandlerType); - _handlers.Add(descriptor); + Interlocked.Exchange(ref _descriptors, _descriptors.Add(descriptor)); return descriptor; } @@ -46,7 +48,7 @@ IDisposable IHandlersManager.AddLink(string sourceMethod, string destinationMeth public IDisposable Add(string method, IJsonRpcHandler handler, JsonRpcHandlerOptions options) { var descriptor = GetDescriptor(method, handler.GetType(), handler, options); - _handlers.Add(descriptor); + Interlocked.Exchange(ref _descriptors, _descriptors.Add(descriptor)); return new CompositeDisposable {descriptor}; } @@ -66,7 +68,7 @@ public IDisposable Add(params IJsonRpcHandler[] handlers) { var descriptor = GetDescriptor(method, implementedInterface, handler, null); cd.Add(descriptor); - _handlers.Add(descriptor); + Interlocked.Exchange(ref _descriptors, _descriptors.Add(descriptor)); } } @@ -100,7 +102,7 @@ private IDisposable Add(IJsonRpcHandler[] handlers, JsonRpcHandlerOptions option { var descriptor = GetDescriptor(method, implementedInterface, handler, options); cd.Add(descriptor); - _handlers.Add(descriptor); + Interlocked.Exchange(ref _descriptors, _descriptors.Add(descriptor)); } } @@ -148,7 +150,16 @@ private HandlerDescriptor GetDescriptor(string method, Type handlerType, IJsonRp @params, response, requestProcessType, - () => { _handlers.RemoveWhere(d => d.Handler == handler); }); + () => { + var descriptors = _descriptors.ToBuilder(); + foreach (var descriptor in _descriptors) + { + if (descriptor.Handler != handler) continue; + descriptors.Remove(descriptor); + } + + Interlocked.Exchange(ref _descriptors, descriptors.ToImmutable()); + }); return descriptor; } diff --git a/src/JsonRpc/HandlerCollection.cs b/src/JsonRpc/HandlerCollection.cs index 48bafe16e..5966ab4e7 100644 --- a/src/JsonRpc/HandlerCollection.cs +++ b/src/JsonRpc/HandlerCollection.cs @@ -1,10 +1,12 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Collections.Immutable; using System.Diagnostics; using System.Linq; using System.Reactive.Disposables; using System.Reflection; +using System.Threading; using MediatR; namespace OmniSharp.Extensions.JsonRpc @@ -13,7 +15,7 @@ namespace OmniSharp.Extensions.JsonRpc public class HandlerCollection : IEnumerable, IHandlersManager { - internal readonly List _handlers = new List(); + private ImmutableArray _descriptors = ImmutableArray.Empty; public HandlerCollection() { } @@ -109,7 +111,7 @@ public void Dispose() public IEnumerator GetEnumerator() { - return _handlers.GetEnumerator(); + return _descriptors.AsEnumerable().GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() @@ -119,11 +121,13 @@ IEnumerator IEnumerable.GetEnumerator() private void Remove(IJsonRpcHandler handler) { - var handlers = _handlers.FindAll(instance => instance.Handler == handler); - foreach (var item in handlers) + var descriptors = _descriptors.ToBuilder(); + foreach (var item in _descriptors.Where(instance => instance.Handler == handler)) { - _handlers.Remove(item); + descriptors.Remove(item); } + + ImmutableInterlocked.InterlockedExchange(ref _descriptors, descriptors.ToImmutableArray()); } public IDisposable Add(params IJsonRpcHandler[] handlers) @@ -131,7 +135,7 @@ public IDisposable Add(params IJsonRpcHandler[] handlers) var cd = new CompositeDisposable(); foreach (var handler in handlers) { - if (_handlers.Any(z => z.Handler == handler)) continue; + if (_descriptors.Any(z => z.Handler == handler)) continue; cd.Add(Add(GetMethodName(handler.GetType()), handler, null)); } return cd; @@ -167,27 +171,27 @@ public IDisposable Add(string method, IJsonRpcHandler handler, JsonRpcHandlerOpt .OfType() .FirstOrDefault()?.Type; - var h = new HandlerInstance(method, handler, @interface, @params, response, requestProcessType, () => Remove(handler)); - _handlers.Add(h); - return h; + var descriptor = new HandlerInstance(method, handler, @interface, @params, response, requestProcessType, () => Remove(handler)); + ImmutableInterlocked.InterlockedExchange(ref _descriptors, _descriptors.Add(descriptor)); + return descriptor; } public IDisposable AddLink(string sourceMethod, string destinationMethod) { - var source = _handlers.Find(z => z.Method == sourceMethod); - var h = new LinkedHandler(destinationMethod, source, () => _handlers.RemoveAll(z => z.Method == destinationMethod)); - _handlers.Add(h); - return h; + var source = _descriptors.FirstOrDefault(z => z.Method == sourceMethod); + var descriptor = new LinkedHandler(destinationMethod, source, () => _descriptors.RemoveAll(z => z.Method == destinationMethod)); + ImmutableInterlocked.InterlockedExchange(ref _descriptors, _descriptors.Add(descriptor)); + return descriptor; } public bool ContainsHandler(Type type) { - return _handlers.Any(z => type.IsAssignableFrom(z.HandlerType)); + return _descriptors.Any(z => type.IsAssignableFrom(z.HandlerType)); } public bool ContainsHandler(TypeInfo type) { - return _handlers.Any(z => type.IsAssignableFrom(z.HandlerType)); + return _descriptors.Any(z => type.IsAssignableFrom(z.HandlerType)); } private static readonly Type[] HandlerTypes = { diff --git a/src/Protocol/Constants.cs b/src/Protocol/Constants.cs index aa3dc4537..62b5cc3b9 100644 --- a/src/Protocol/Constants.cs +++ b/src/Protocol/Constants.cs @@ -1,8 +1,10 @@ -namespace OmniSharp.Extensions.LanguageServer.Protocol +namespace OmniSharp.Extensions.LanguageServer.Protocol { - internal static class Constants + public static class Constants { - public const string Proposal = + internal const string Proposal = "Proposed for the next version of the language server. May not work with all clients. May be removed or changed in the future."; + + public const string PrivateHandlerId = "$$__handler_id__$$"; } } diff --git a/src/Protocol/Document/ICodeLensHandler.cs b/src/Protocol/Document/ICodeLensHandler.cs index 002c1395d..f66786d78 100644 --- a/src/Protocol/Document/ICodeLensHandler.cs +++ b/src/Protocol/Document/ICodeLensHandler.cs @@ -58,7 +58,7 @@ protected PartialCodeLensHandlerBase(CodeLensRegistrationOptions registrationOpt public virtual Guid Id { get; } = Guid.NewGuid(); } - public abstract class CodeLensHandlerBase : CodeLensHandler where T : class + public abstract class CodeLensHandlerBase : CodeLensHandler where T : HandlerIdentity, new() { private readonly ISerializer _serializer; @@ -71,20 +71,20 @@ public CodeLensHandlerBase(CodeLensRegistrationOptions registrationOptions, ISer public sealed override async Task Handle(CodeLensParams request, CancellationToken cancellationToken) { var response = await HandleParams(request, cancellationToken); - return response.Convert(_serializer); + return response; } public sealed override async Task Handle(CodeLens request, CancellationToken cancellationToken) { - var response = await HandleResolve(request.From(_serializer), cancellationToken); - return response.To(_serializer); + var response = await HandleResolve(request, cancellationToken); + return response; } protected abstract Task> HandleParams(CodeLensParams request, CancellationToken cancellationToken); protected abstract Task> HandleResolve(CodeLens request, CancellationToken cancellationToken); } - public abstract class PartialCodeLensHandlerBase : PartialCodeLensHandlerBase where T : class + public abstract class PartialCodeLensHandlerBase : PartialCodeLensHandlerBase where T : HandlerIdentity, new() { private readonly ISerializer _serializer; @@ -97,15 +97,15 @@ protected PartialCodeLensHandlerBase(CodeLensRegistrationOptions registrationOpt protected sealed override void Handle(CodeLensParams request, IObserver> results, CancellationToken cancellationToken) => Handle( request, Observer.Create>>( - x => results.OnNext(x.Select(z => z.To(_serializer))), + x => results.OnNext(x.Select(z => (CodeLens)z)), results.OnError, results.OnCompleted ), cancellationToken); public sealed override async Task Handle(CodeLens request, CancellationToken cancellationToken) { - var response = await HandleResolve(request.From(_serializer), cancellationToken); - return response.To(_serializer); + var response = await HandleResolve(request, cancellationToken); + return response; } protected abstract void Handle(CodeLensParams request, IObserver>> results, CancellationToken cancellationToken); @@ -148,7 +148,7 @@ public static ILanguageServerRegistry OnCodeLens(this ILanguageServerRegistry re public static ILanguageServerRegistry OnCodeLens(this ILanguageServerRegistry registry, Func>> handler, Func, CodeLensCapability, CancellationToken, Task>> resolveHandler, - CodeLensRegistrationOptions registrationOptions) where T : class + CodeLensRegistrationOptions registrationOptions) where T : HandlerIdentity, new() { registrationOptions ??= new CodeLensRegistrationOptions(); registrationOptions.ResolveProvider = true; @@ -196,7 +196,7 @@ public static ILanguageServerRegistry OnCodeLens(this ILanguageServerRegistry re public static ILanguageServerRegistry OnCodeLens(this ILanguageServerRegistry registry, Func>> handler, Func, CancellationToken, Task>> resolveHandler, - CodeLensRegistrationOptions registrationOptions) where T : class + CodeLensRegistrationOptions registrationOptions) where T : HandlerIdentity, new() { registrationOptions ??= new CodeLensRegistrationOptions(); registrationOptions.ResolveProvider = true; @@ -244,7 +244,7 @@ public static ILanguageServerRegistry OnCodeLens(this ILanguageServerRegistry re public static ILanguageServerRegistry OnCodeLens(this ILanguageServerRegistry registry, Func>> handler, Func, Task>> resolveHandler, - CodeLensRegistrationOptions registrationOptions) where T : class + CodeLensRegistrationOptions registrationOptions) where T : HandlerIdentity, new() { registrationOptions ??= new CodeLensRegistrationOptions(); registrationOptions.ResolveProvider = true; @@ -295,7 +295,7 @@ public static ILanguageServerRegistry OnCodeLens(this ILanguageServerRegistry re public static ILanguageServerRegistry OnCodeLens(this ILanguageServerRegistry registry, Action>>, CodeLensCapability, CancellationToken> handler, Func, CodeLensCapability, CancellationToken, Task>> resolveHandler, - CodeLensRegistrationOptions registrationOptions) where T : class + CodeLensRegistrationOptions registrationOptions) where T : HandlerIdentity, new() { registrationOptions ??= new CodeLensRegistrationOptions(); registrationOptions.ResolveProvider = true; @@ -346,7 +346,7 @@ public static ILanguageServerRegistry OnCodeLens(this ILanguageServerRegistry re public static ILanguageServerRegistry OnCodeLens(this ILanguageServerRegistry registry, Action>>, CancellationToken> handler, Func, CancellationToken, Task>> resolveHandler, - CodeLensRegistrationOptions registrationOptions) where T : class + CodeLensRegistrationOptions registrationOptions) where T : HandlerIdentity, new() { registrationOptions ??= new CodeLensRegistrationOptions(); registrationOptions.ResolveProvider = true; @@ -397,7 +397,7 @@ public static ILanguageServerRegistry OnCodeLens(this ILanguageServerRegistry re public static ILanguageServerRegistry OnCodeLens(this ILanguageServerRegistry registry, Action>>> handler, Func, Task>> resolveHandler, - CodeLensRegistrationOptions registrationOptions) where T : class + CodeLensRegistrationOptions registrationOptions) where T : HandlerIdentity, new() { registrationOptions ??= new CodeLensRegistrationOptions(); registrationOptions.ResolveProvider = true; @@ -412,7 +412,7 @@ public static ILanguageServerRegistry OnCodeLens(this ILanguageServerRegistry ); } - class DelegatingCodeLensHandler : CodeLensHandlerBase where T : class + class DelegatingCodeLensHandler : CodeLensHandlerBase where T : HandlerIdentity, new() { private readonly Func>> _handleParams; private readonly Func, CodeLensCapability, CancellationToken, Task>> _handleResolve; @@ -434,7 +434,7 @@ protected override Task> HandleParams(CodeLensParams reques protected override Task> HandleResolve(CodeLens request, CancellationToken cancellationToken) => _handleResolve(request, Capability, cancellationToken); } - class DelegatingPartialCodeLensHandler : PartialCodeLensHandlerBase where T : class + class DelegatingPartialCodeLensHandler : PartialCodeLensHandlerBase where T : HandlerIdentity, new() { private readonly Action>>, CodeLensCapability, CancellationToken> _handleParams; private readonly Func, CodeLensCapability, CancellationToken, Task>> _handleResolve; diff --git a/src/Protocol/Document/ICompletionHandler.cs b/src/Protocol/Document/ICompletionHandler.cs index acf41a00b..da4a6cd40 100644 --- a/src/Protocol/Document/ICompletionHandler.cs +++ b/src/Protocol/Document/ICompletionHandler.cs @@ -58,7 +58,7 @@ protected PartialCompletionHandlerBase(CompletionRegistrationOptions registratio public virtual Guid Id { get; } = Guid.NewGuid(); } - public abstract class CompletionHandlerBase : CompletionHandler where T : class + public abstract class CompletionHandlerBase : CompletionHandler where T : HandlerIdentity, new() { private readonly ISerializer _serializer; @@ -71,20 +71,20 @@ public CompletionHandlerBase(CompletionRegistrationOptions registrationOptions, public sealed override async Task Handle(CompletionParams request, CancellationToken cancellationToken) { var response = await HandleParams(request, cancellationToken); - return response.Convert(_serializer); + return response; } public sealed override async Task Handle(CompletionItem request, CancellationToken cancellationToken) { - var response = await HandleResolve(request.From(_serializer), cancellationToken); - return response.To(_serializer); + var response = await HandleResolve(request, cancellationToken); + return response; } protected abstract Task> HandleParams(CompletionParams request, CancellationToken cancellationToken); protected abstract Task> HandleResolve(CompletionItem request, CancellationToken cancellationToken); } - public abstract class PartialCompletionHandlerBase : PartialCompletionHandlerBase where T : class + public abstract class PartialCompletionHandlerBase : PartialCompletionHandlerBase where T : HandlerIdentity, new() { private readonly ISerializer _serializer; @@ -97,15 +97,15 @@ protected PartialCompletionHandlerBase(CompletionRegistrationOptions registratio protected sealed override void Handle(CompletionParams request, IObserver> results, CancellationToken cancellationToken) => Handle( request, Observer.Create>>( - x => results.OnNext(x.Select(z => z.To(_serializer))), + x => results.OnNext(x.Select(z => (CompletionItem)z)), results.OnError, results.OnCompleted ), cancellationToken); public sealed override async Task Handle(CompletionItem request, CancellationToken cancellationToken) { - var response = await HandleResolve(request.From(_serializer), cancellationToken); - return response.To(_serializer); + var response = await HandleResolve(request, cancellationToken); + return response; } protected abstract void Handle(CompletionParams request, IObserver>> results, CancellationToken cancellationToken); @@ -148,7 +148,7 @@ public static ILanguageServerRegistry OnCompletion(this ILanguageServerRegistry public static ILanguageServerRegistry OnCompletion(this ILanguageServerRegistry registry, Func>> handler, Func, CompletionCapability, CancellationToken, Task>> resolveHandler, - CompletionRegistrationOptions registrationOptions) where T : class + CompletionRegistrationOptions registrationOptions) where T : HandlerIdentity, new() { registrationOptions ??= new CompletionRegistrationOptions(); registrationOptions.ResolveProvider = true; @@ -196,7 +196,7 @@ public static ILanguageServerRegistry OnCompletion(this ILanguageServerRegistry public static ILanguageServerRegistry OnCompletion(this ILanguageServerRegistry registry, Func>> handler, Func, CancellationToken, Task>> resolveHandler, - CompletionRegistrationOptions registrationOptions) where T : class + CompletionRegistrationOptions registrationOptions) where T : HandlerIdentity, new() { registrationOptions ??= new CompletionRegistrationOptions(); registrationOptions.ResolveProvider = true; @@ -244,7 +244,7 @@ public static ILanguageServerRegistry OnCompletion(this ILanguageServerRegistry public static ILanguageServerRegistry OnCompletion(this ILanguageServerRegistry registry, Func>> handler, Func, Task>> resolveHandler, - CompletionRegistrationOptions registrationOptions) where T : class + CompletionRegistrationOptions registrationOptions) where T : HandlerIdentity, new() { registrationOptions ??= new CompletionRegistrationOptions(); registrationOptions.ResolveProvider = true; @@ -295,7 +295,7 @@ public static ILanguageServerRegistry OnCompletion(this ILanguageServerRegistry public static ILanguageServerRegistry OnCompletion(this ILanguageServerRegistry registry, Action>>, CompletionCapability, CancellationToken> handler, Func, CompletionCapability, CancellationToken, Task>> resolveHandler, - CompletionRegistrationOptions registrationOptions) where T : class + CompletionRegistrationOptions registrationOptions) where T : HandlerIdentity, new() { registrationOptions ??= new CompletionRegistrationOptions(); registrationOptions.ResolveProvider = true; @@ -346,7 +346,7 @@ public static ILanguageServerRegistry OnCompletion(this ILanguageServerRegistry public static ILanguageServerRegistry OnCompletion(this ILanguageServerRegistry registry, Action>>, CancellationToken> handler, Func, CancellationToken, Task>> resolveHandler, - CompletionRegistrationOptions registrationOptions) where T : class + CompletionRegistrationOptions registrationOptions) where T : HandlerIdentity, new() { registrationOptions ??= new CompletionRegistrationOptions(); registrationOptions.ResolveProvider = true; @@ -397,7 +397,7 @@ public static ILanguageServerRegistry OnCompletion(this ILanguageServerRegistry public static ILanguageServerRegistry OnCompletion(this ILanguageServerRegistry registry, Action>>> handler, Func, Task>> resolveHandler, - CompletionRegistrationOptions registrationOptions) where T : class + CompletionRegistrationOptions registrationOptions) where T : HandlerIdentity, new() { registrationOptions ??= new CompletionRegistrationOptions(); registrationOptions.ResolveProvider = true; @@ -412,7 +412,7 @@ public static ILanguageServerRegistry OnCompletion(this ILanguageServerRegist ); } - class DelegatingCompletionHandler : CompletionHandlerBase where T : class + class DelegatingCompletionHandler : CompletionHandlerBase where T : HandlerIdentity, new() { private readonly Func>> _handleParams; private readonly Func, CompletionCapability, CancellationToken, Task>> _handleResolve; @@ -434,7 +434,7 @@ protected override Task> HandleParams(CompletionParams request protected override Task> HandleResolve(CompletionItem request, CancellationToken cancellationToken) => _handleResolve(request, Capability, cancellationToken); } - class DelegatingPartialCompletionHandler : PartialCompletionHandlerBase where T : class + class DelegatingPartialCompletionHandler : PartialCompletionHandlerBase where T : HandlerIdentity, new() { private readonly Action>>, CompletionCapability, CancellationToken> _handleParams; private readonly Func, CompletionCapability, CancellationToken, Task>> _handleResolve; diff --git a/src/Protocol/Document/IDocumentLinkHandler.cs b/src/Protocol/Document/IDocumentLinkHandler.cs index be57a7026..76ee34532 100644 --- a/src/Protocol/Document/IDocumentLinkHandler.cs +++ b/src/Protocol/Document/IDocumentLinkHandler.cs @@ -59,7 +59,7 @@ protected PartialDocumentLinkHandlerBase(DocumentLinkRegistrationOptions registr public virtual Guid Id { get; } = Guid.NewGuid(); } - public abstract class DocumentLinkHandlerBase : DocumentLinkHandler where T : class + public abstract class DocumentLinkHandlerBase : DocumentLinkHandler where T : HandlerIdentity, new() { private readonly ISerializer _serializer; @@ -72,20 +72,20 @@ public DocumentLinkHandlerBase(DocumentLinkRegistrationOptions registrationOptio public sealed override async Task Handle(DocumentLinkParams request, CancellationToken cancellationToken) { var response = await HandleParams(request, cancellationToken); - return response.Convert(_serializer); + return response; } public sealed override async Task Handle(DocumentLink request, CancellationToken cancellationToken) { - var response = await HandleResolve(request.From(_serializer), cancellationToken); - return response.To(_serializer); + var response = await HandleResolve(request, cancellationToken); + return response; } protected abstract Task> HandleParams(DocumentLinkParams request, CancellationToken cancellationToken); protected abstract Task> HandleResolve(DocumentLink request, CancellationToken cancellationToken); } - public abstract class PartialDocumentLinkHandlerBase : PartialDocumentLinkHandlerBase where T : class + public abstract class PartialDocumentLinkHandlerBase : PartialDocumentLinkHandlerBase where T : HandlerIdentity, new() { private readonly ISerializer _serializer; @@ -98,15 +98,15 @@ protected PartialDocumentLinkHandlerBase(DocumentLinkRegistrationOptions registr protected sealed override void Handle(DocumentLinkParams request, IObserver> results, CancellationToken cancellationToken) => Handle( request, Observer.Create>>( - x => results.OnNext(x.Select(z => z.To(_serializer))), + x => results.OnNext(x.Select(z => (DocumentLink) z)), results.OnError, results.OnCompleted ), cancellationToken); public sealed override async Task Handle(DocumentLink request, CancellationToken cancellationToken) { - var response = await HandleResolve(request.From(_serializer), cancellationToken); - return response.To(_serializer); + var response = await HandleResolve(request, cancellationToken); + return response; } protected abstract void Handle(DocumentLinkParams request, IObserver>> results, CancellationToken cancellationToken); @@ -149,7 +149,7 @@ public static ILanguageServerRegistry OnDocumentLink(this ILanguageServerRegistr public static ILanguageServerRegistry OnDocumentLink(this ILanguageServerRegistry registry, Func>> handler, Func, DocumentLinkCapability, CancellationToken, Task>> resolveHandler, - DocumentLinkRegistrationOptions registrationOptions) where T : class + DocumentLinkRegistrationOptions registrationOptions) where T : HandlerIdentity, new() { registrationOptions ??= new DocumentLinkRegistrationOptions(); registrationOptions.ResolveProvider = true; @@ -197,7 +197,7 @@ public static ILanguageServerRegistry OnDocumentLink(this ILanguageServerRegistr public static ILanguageServerRegistry OnDocumentLink(this ILanguageServerRegistry registry, Func>> handler, Func, CancellationToken, Task>> resolveHandler, - DocumentLinkRegistrationOptions registrationOptions) where T : class + DocumentLinkRegistrationOptions registrationOptions) where T : HandlerIdentity, new() { registrationOptions ??= new DocumentLinkRegistrationOptions(); registrationOptions.ResolveProvider = true; @@ -245,7 +245,7 @@ public static ILanguageServerRegistry OnDocumentLink(this ILanguageServerRegistr public static ILanguageServerRegistry OnDocumentLink(this ILanguageServerRegistry registry, Func>> handler, Func, Task>> resolveHandler, - DocumentLinkRegistrationOptions registrationOptions) where T : class + DocumentLinkRegistrationOptions registrationOptions) where T : HandlerIdentity, new() { registrationOptions ??= new DocumentLinkRegistrationOptions(); registrationOptions.ResolveProvider = true; @@ -296,7 +296,7 @@ public static ILanguageServerRegistry OnDocumentLink(this ILanguageServerRegistr public static ILanguageServerRegistry OnDocumentLink(this ILanguageServerRegistry registry, Action>>, DocumentLinkCapability, CancellationToken> handler, Func, DocumentLinkCapability, CancellationToken, Task>> resolveHandler, - DocumentLinkRegistrationOptions registrationOptions) where T : class + DocumentLinkRegistrationOptions registrationOptions) where T : HandlerIdentity, new() { registrationOptions ??= new DocumentLinkRegistrationOptions(); registrationOptions.ResolveProvider = true; @@ -347,7 +347,7 @@ public static ILanguageServerRegistry OnDocumentLink(this ILanguageServerRegistr public static ILanguageServerRegistry OnDocumentLink(this ILanguageServerRegistry registry, Action>>, CancellationToken> handler, Func, CancellationToken, Task>> resolveHandler, - DocumentLinkRegistrationOptions registrationOptions) where T : class + DocumentLinkRegistrationOptions registrationOptions) where T : HandlerIdentity, new() { registrationOptions ??= new DocumentLinkRegistrationOptions(); registrationOptions.ResolveProvider = true; @@ -398,7 +398,7 @@ public static ILanguageServerRegistry OnDocumentLink(this ILanguageServerRegistr public static ILanguageServerRegistry OnDocumentLink(this ILanguageServerRegistry registry, Action>>> handler, Func, Task>> resolveHandler, - DocumentLinkRegistrationOptions registrationOptions) where T : class + DocumentLinkRegistrationOptions registrationOptions) where T : HandlerIdentity, new() { registrationOptions ??= new DocumentLinkRegistrationOptions(); registrationOptions.ResolveProvider = true; @@ -413,7 +413,7 @@ public static ILanguageServerRegistry OnDocumentLink(this ILanguageServerRegi ); } - class DelegatingDocumentLinkHandler : DocumentLinkHandlerBase where T : class + class DelegatingDocumentLinkHandler : DocumentLinkHandlerBase where T : HandlerIdentity, new() { private readonly Func>> _handleParams; private readonly Func, DocumentLinkCapability, CancellationToken, Task>> _handleResolve; @@ -435,7 +435,7 @@ protected override Task> HandleParams(DocumentLinkParam protected override Task> HandleResolve(DocumentLink request, CancellationToken cancellationToken) => _handleResolve(request, Capability, cancellationToken); } - class DelegatingPartialDocumentLinkHandler : PartialDocumentLinkHandlerBase where T : class + class DelegatingPartialDocumentLinkHandler : PartialDocumentLinkHandlerBase where T : HandlerIdentity, new() { private readonly Action>>, DocumentLinkCapability, CancellationToken> _handleParams; private readonly Func, DocumentLinkCapability, CancellationToken, Task>> _handleResolve; diff --git a/src/Protocol/Models/CodeLens.cs b/src/Protocol/Models/CodeLens.cs index daf22d377..7d45127d8 100644 --- a/src/Protocol/Models/CodeLens.cs +++ b/src/Protocol/Models/CodeLens.cs @@ -1,4 +1,3 @@ -using System.ComponentModel; using System.Diagnostics; using MediatR; using Newtonsoft.Json.Linq; @@ -17,7 +16,7 @@ namespace OmniSharp.Extensions.LanguageServer.Protocol.Models /// [DebuggerDisplay("{" + nameof(DebuggerDisplay) + ",nq}")] [Method(TextDocumentNames.CodeLensResolve, Direction.ClientToServer)] - public class CodeLens : ICanBeResolved, IRequest + public class CodeLens : IRequest, ICanBeResolved { /// /// The range in which this code lens is valid. Should only span a single line. @@ -37,24 +36,9 @@ public class CodeLens : ICanBeResolved, IRequest [Optional] public JToken Data { get; set; } - private string DebuggerDisplay => $"{Range}{(Command != null ? $" Command" : "")}"; + private string DebuggerDisplay => $"{Range}{(Command != null ? $" {Command}" : "")}"; /// public override string ToString() => DebuggerDisplay; - - /// - /// Convert from a - /// - /// - /// - [EditorBrowsable(EditorBrowsableState.Never)] - internal CodeLens From(ISerializer serializer) where T : class - { - return new CodeLens() { - Command = Command, - Data = Data?.ToObject(serializer.JsonSerializer), - Range = Range - }; - } } /// @@ -68,28 +52,42 @@ internal CodeLens From(ISerializer serializer) where T : class /// Typed code lens used for the typed handlers /// /// - public class CodeLens : CodeLens where T : class + public class CodeLens : ICanBeResolved + where T : HandlerIdentity, new() { + /// + public Range Range { get; set; } + + /// + [Optional] + public Command Command { get; set; } + /// /// A data entry field that is preserved on a code lens item between /// a code lens and a code lens resolve request. /// - public new T Data { get; set; } - - /// - /// Convert to a - /// - /// - /// - [EditorBrowsable(EditorBrowsableState.Never)] - internal CodeLens To(ISerializer serializer) + public T Data { - if (Data != null) - { - base.Data = JObject.FromObject(Data, serializer.JsonSerializer); - } + get => ((ICanBeResolved)this).Data?.ToObject(); + set => ((ICanBeResolved)this).Data = JToken.FromObject(value ?? new object()); + } + + JToken ICanBeResolved.Data { get; set; } + + public static implicit operator CodeLens(CodeLens value) => new CodeLens { + Data = ((ICanBeResolved)value).Data, + Command = value.Command, + Range = value.Range, + }; - return this; + public static implicit operator CodeLens(CodeLens value) + { + var item = new CodeLens { + Command = value.Command, + Range = value.Range, + }; + ((ICanBeResolved)item).Data = value.Data; + return item; } } } diff --git a/src/Protocol/Models/CodeLensContainer.cs b/src/Protocol/Models/CodeLensContainer.cs index c0c8d435a..5cc6b2c09 100644 --- a/src/Protocol/Models/CodeLensContainer.cs +++ b/src/Protocol/Models/CodeLensContainer.cs @@ -34,23 +34,12 @@ public static implicit operator CodeLensContainer(List items) { return new CodeLensContainer(items); } - - /// - /// Convert from a - /// - /// - /// - [EditorBrowsable(EditorBrowsableState.Never)] - internal CodeLensContainer Convert(ISerializer serializer) where T : class - { - return new CodeLensContainer(this.Select(z => z.From(serializer))); - } } /// /// Typed code lens used for the typed handlers /// - public class CodeLensContainer : Container> where T : class + public class CodeLensContainer : Container> where T : HandlerIdentity, new() { public CodeLensContainer() : this(Enumerable.Empty>()) { @@ -79,15 +68,6 @@ public static implicit operator CodeLensContainer(List> items) return new CodeLensContainer(items); } - /// - /// Convert to a - /// - /// - /// - [EditorBrowsable(EditorBrowsableState.Never)] - internal CodeLensContainer Convert(ISerializer serializer) - { - return new CodeLensContainer(this.Select(z => z.To(serializer))); - } + public static implicit operator CodeLensContainer(CodeLensContainer container) => new CodeLensContainer(container.Select(z => (CodeLens)z)); } } diff --git a/src/Protocol/Models/CompletionItem.cs b/src/Protocol/Models/CompletionItem.cs index d77f71313..7a96842ee 100644 --- a/src/Protocol/Models/CompletionItem.cs +++ b/src/Protocol/Models/CompletionItem.cs @@ -138,62 +138,182 @@ public class CompletionItem : ICanBeResolved, IRequest private string DebuggerDisplay => $"[{Kind}] {Label}{(Tags?.Any() == true ? $" tags: {string.Join(", ", Tags.Select(z => z.ToString()))}" : "")}"; /// public override string ToString() => DebuggerDisplay; - - /// - /// Convert from a - /// - /// - /// - [EditorBrowsable(EditorBrowsableState.Never)] - internal CompletionItem From(ISerializer serializer) where T : class - { - return new CompletionItem() { - Command = Command, - Deprecated = Deprecated, - Detail = Detail, - Documentation = Documentation, - Kind = Kind, - Label = Label, - Preselect = Preselect, - Tags = Tags, - CommitCharacters = CommitCharacters, - FilterText = FilterText, - InsertText = InsertText, - SortText = SortText, - TextEdit = TextEdit, - AdditionalTextEdits = AdditionalTextEdits, - InsertTextFormat = InsertTextFormat, - Data = Data?.ToObject(serializer.JsonSerializer) - }; - } } /// /// Typed code lens used for the typed handlers /// - public class CompletionItem : CompletionItem where T : class + public class CompletionItem : ICanBeResolved + where T : HandlerIdentity, new() { /// - /// An data entry field that is preserved on a completion item between - /// a completion and a completion resolve request. + /// The label of this completion item. By default + /// also the text that is inserted when selecting + /// this completion. + /// + public string Label { get; set; } + + /// + /// The kind of this completion item. Based of the kind + /// an icon is chosen by the editor. + /// + [Optional] + public CompletionItemKind Kind { get; set; } + + /// + /// Tags for this completion item. + /// + /// @since 3.15.0 /// [Optional] - public new T Data { get; set; } + public Container Tags { get; set; } /// - /// Convert to a + /// A human-readable string with additional information + /// about this item, like type or symbol information. /// - /// - /// - [EditorBrowsable(EditorBrowsableState.Never)] - internal CompletionItem To(ISerializer serializer) + [Optional] + public string Detail { get; set; } + + /// + /// A human-readable string that represents a doc-comment. + /// + [Optional] + public StringOrMarkupContent Documentation { get; set; } + + /// + /// Indicates if this item is deprecated. + /// + [Optional] + public bool Deprecated { get; set; } + + /// + /// Select this item when showing. + /// + /// *Note* that only one completion item can be selected and that the + /// tool / client decides which item that is. The rule is that the *first* + /// item of those that match best is selected. + /// + [Optional] + public bool Preselect { get; set; } + + /// + /// A string that shoud be used when comparing this item + /// with other items. When `falsy` the label is used. + /// + [Optional] + public string SortText { get; set; } + + /// + /// A string that should be used when filtering a set of + /// completion items. When `falsy` the label is used. + /// + + [Optional] + public string FilterText { get; set; } + + /// + /// A string that should be inserted a document when selecting + /// this completion. When `falsy` the label is used. + /// + + [Optional] + public string InsertText { get; set; } + + /// + /// The format of the insert text. The format applies to both the `insertText` property + /// and the `newText` property of a provided `textEdit`. + /// + [Optional] + public InsertTextFormat InsertTextFormat { get; set; } + + /// + /// An edit which is applied to a document when selecting this completion. When an edit is provided the value of + /// `insertText` is ignored. + /// + /// *Note:* The range of the edit must be a single line range and it must contain the position at which completion + /// has been requested. + /// + [Optional] + public TextEdit TextEdit { get; set; } + + /// + /// An optional array of additional text edits that are applied when + /// selecting this completion. Edits must not overlap with the main edit + /// nor with themselves. + /// + [Optional] + public TextEditContainer AdditionalTextEdits { get; set; } + + /// + /// An optional set of characters that when pressed while this completion is active will accept it first and + /// then type that character. *Note* that all commit characters should have `length=1` and that superfluous + /// characters will be ignored. + /// + [Optional] + public Container CommitCharacters { get; set; } + + /// + /// An optional command that is executed/// after* inserting this completion./// Note* that + /// additional modifications to the current document should be described with the + /// additionalTextEdits-property. + /// + [Optional] + public Command Command { get; set; } + + /// + /// A data entry field that is preserved on a code lens item between + /// a code lens and a code lens resolve request. + /// + [Optional] + public T Data { - if (Data != null) - { - base.Data = JObject.FromObject(Data, serializer.JsonSerializer); - } + get => ((ICanBeResolved)this).Data?.ToObject(); + set => ((ICanBeResolved)this).Data = JToken.FromObject(value ?? new object()); + } + + JToken ICanBeResolved.Data { get; set; } - return this; + public static implicit operator CompletionItem(CompletionItem value) => new CompletionItem { + Data = ((ICanBeResolved)value).Data, + Command = value.Command, + Deprecated = value.Deprecated, + Detail = value.Detail, + Documentation = value.Documentation, + Kind = value.Kind, + Label = value.Label, + Preselect = value.Preselect, + Tags = value.Tags, + CommitCharacters = value.CommitCharacters, + FilterText = value.FilterText, + InsertText = value.InsertText, + SortText = value.SortText, + TextEdit = value.TextEdit, + AdditionalTextEdits = value.AdditionalTextEdits, + InsertTextFormat = value.InsertTextFormat, + }; + + public static implicit operator CompletionItem(CompletionItem value) + { + var item = new CompletionItem { + Command = value.Command, + Deprecated = value.Deprecated, + Detail = value.Detail, + Documentation = value.Documentation, + Kind = value.Kind, + Label = value.Label, + Preselect = value.Preselect, + Tags = value.Tags, + CommitCharacters = value.CommitCharacters, + FilterText = value.FilterText, + InsertText = value.InsertText, + SortText = value.SortText, + TextEdit = value.TextEdit, + AdditionalTextEdits = value.AdditionalTextEdits, + InsertTextFormat = value.InsertTextFormat, + }; + ((ICanBeResolved)item).Data = value.Data; + return item; } } } diff --git a/src/Protocol/Models/CompletionList.cs b/src/Protocol/Models/CompletionList.cs index 024cd3375..569a35e7b 100644 --- a/src/Protocol/Models/CompletionList.cs +++ b/src/Protocol/Models/CompletionList.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Linq; @@ -63,23 +63,13 @@ public static implicit operator CompletionItem[] (CompletionList list) { return list.ToArray(); } - - /// - /// Convert from a - /// - /// - /// - [EditorBrowsable(EditorBrowsableState.Never)] - internal CompletionList Convert(ISerializer serializer) where T : class - { - return new CompletionList(this.Select(z => z.From(serializer))); - } } + /// /// Represents a collection of [completion items](#CompletionItem) to be presented /// in the editor. /// - public class CompletionList : Container> where T : class + public class CompletionList : Container> where T : HandlerIdentity, new() { public CompletionList() : base(Enumerable.Empty>()) { } public CompletionList(bool isIncomplete) : base(Enumerable.Empty>()) @@ -108,7 +98,7 @@ public CompletionList(bool isIncomplete, params CompletionItem[] items) : bas /// /// The completion items. /// - public IEnumerable Items => this; + public IEnumerable> Items => this; public static implicit operator CompletionList(CompletionItem[] items) { @@ -130,15 +120,6 @@ public static implicit operator CompletionItem[] (CompletionList list) return list.ToArray(); } - /// - /// Convert to a - /// - /// - /// - [EditorBrowsable(EditorBrowsableState.Never)] - internal CompletionList Convert(ISerializer serializer) - { - return new CompletionList(this.Select(z => z.To(serializer))); - } + public static implicit operator CompletionList(CompletionList container) => new CompletionList(container.Select(z => (CompletionItem)z)); } } diff --git a/src/Protocol/Models/DocumentLink.cs b/src/Protocol/Models/DocumentLink.cs index bff5805d0..915e118aa 100644 --- a/src/Protocol/Models/DocumentLink.cs +++ b/src/Protocol/Models/DocumentLink.cs @@ -12,8 +12,8 @@ namespace OmniSharp.Extensions.LanguageServer.Protocol.Models /// A document link is a range in a text document that links to an internal or external resource, like another /// text document or a web site. /// - [Method(TextDocumentNames.DocumentLinkResolve, Direction.ClientToServer)] [DebuggerDisplay("{" + nameof(DebuggerDisplay) + ",nq}")] + [Method(TextDocumentNames.DocumentLinkResolve, Direction.ClientToServer)] public class DocumentLink : ICanBeResolved, IRequest { /// @@ -49,50 +49,67 @@ public class DocumentLink : ICanBeResolved, IRequest private string DebuggerDisplay => $"{Range}{(Target != null ? $" {Target}" : "")}{(string.IsNullOrWhiteSpace(Tooltip) ? $" {Tooltip}" : "")}"; /// public override string ToString() => DebuggerDisplay; - - /// - /// Convert from a - /// - /// - /// - [EditorBrowsable(EditorBrowsableState.Never)] - internal DocumentLink From(ISerializer serializer) where T : class - { - return new DocumentLink() { - Range = Range, - Target = Target, - Tooltip = Tooltip, - Data = Data?.ToObject(serializer.JsonSerializer) - }; - } } + /// /// A document link is a range in a text document that links to an internal or external resource, like another /// text document or a web site. /// - public class DocumentLink : DocumentLink where T : class + public class DocumentLink : ICanBeResolved + where T : HandlerIdentity, new() { /// - /// A data entry field that is preserved on a document link between a - /// DocumentLinkRequest and a DocumentLinkResolveRequest. + /// The range this link applies to. + /// + public Range Range { get; set; } + + /// + /// The uri this link points to. If missing a resolve request is sent later. + /// + [Optional] + public DocumentUri Target { get; set; } + + /// + /// The tooltip text when you hover over this link. + /// + /// If a tooltip is provided, is will be displayed in a string that includes instructions on how to + /// trigger the link, such as `{0} (ctrl + click)`. The specific instructions vary depending on OS, + /// user settings, and localization. + /// + /// @since 3.15.0 /// [Optional] - public new T Data { get; set; } + public string Tooltip { get; set; } /// - /// Convert to a + /// A data entry field that is preserved on a code lens item between + /// a code lens and a code lens resolve request. /// - /// - /// - [EditorBrowsable(EditorBrowsableState.Never)] - internal DocumentLink To(ISerializer serializer) + [Optional] + public T Data { - if (Data != null) - { - base.Data = JObject.FromObject(Data, serializer.JsonSerializer); - } + get => ((ICanBeResolved)this).Data?.ToObject(); + set => ((ICanBeResolved)this).Data = JToken.FromObject(value ?? new object()); + } - return this; + JToken ICanBeResolved.Data { get; set; } + + public static implicit operator DocumentLink(DocumentLink value) => new DocumentLink { + Data = ((ICanBeResolved)value).Data, + Range = value.Range, + Target = value.Target, + Tooltip = value.Tooltip, + }; + + public static implicit operator DocumentLink(DocumentLink value) + { + var item = new DocumentLink { + Range = value.Range, + Target = value.Target, + Tooltip = value.Tooltip, + }; + ((ICanBeResolved)item).Data = value.Data; + return item; } } } diff --git a/src/Protocol/Models/DocumentLinkContainer.cs b/src/Protocol/Models/DocumentLinkContainer.cs index 5ecfc1516..1075516b0 100644 --- a/src/Protocol/Models/DocumentLinkContainer.cs +++ b/src/Protocol/Models/DocumentLinkContainer.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Linq; @@ -34,18 +34,9 @@ public static implicit operator DocumentLinkContainer(List items) { return new DocumentLinkContainer(items); } - - /// - /// Convert from a - /// - /// - /// - public DocumentLinkContainer Convert(ISerializer serializer) where T : class - { - return new DocumentLinkContainer(this.Select(z => z.From(serializer))); - } } - public class DocumentLinkContainer : Container> where T : class + + public class DocumentLinkContainer : Container> where T : HandlerIdentity, new() { public DocumentLinkContainer() : this(Enumerable.Empty>()) { @@ -74,15 +65,6 @@ public static implicit operator DocumentLinkContainer(List> i return new DocumentLinkContainer(items); } - /// - /// Convert to a - /// - /// - /// - [EditorBrowsable(EditorBrowsableState.Never)] - internal DocumentLinkContainer Convert(ISerializer serializer) - { - return new DocumentLinkContainer(this.Select(z => z.To(serializer))); - } + public static implicit operator DocumentLinkContainer(DocumentLinkContainer container) => new DocumentLinkContainer(container.Select(z => (DocumentLink)z)); } } diff --git a/src/Protocol/Models/HandlerIdentity.cs b/src/Protocol/Models/HandlerIdentity.cs new file mode 100644 index 000000000..445b58097 --- /dev/null +++ b/src/Protocol/Models/HandlerIdentity.cs @@ -0,0 +1,11 @@ +using System.ComponentModel; +using Newtonsoft.Json; + +namespace OmniSharp.Extensions.LanguageServer.Protocol.Models +{ + public abstract class HandlerIdentity + { + [JsonProperty(Constants.PrivateHandlerId, DefaultValueHandling = DefaultValueHandling.Ignore), EditorBrowsable(EditorBrowsableState.Never)] + public string __identity { get; set; } + } +} diff --git a/src/Server/Matchers/ResolveCommandMatcher.cs b/src/Server/Matchers/ResolveCommandMatcher.cs index b63168a7e..15a835387 100644 --- a/src/Server/Matchers/ResolveCommandMatcher.cs +++ b/src/Server/Matchers/ResolveCommandMatcher.cs @@ -5,6 +5,7 @@ using MediatR; using Microsoft.Extensions.Logging; using Newtonsoft.Json.Linq; +using OmniSharp.Extensions.LanguageServer.Protocol; using OmniSharp.Extensions.LanguageServer.Protocol.Models; using OmniSharp.Extensions.LanguageServer.Protocol.Shared; using OmniSharp.Extensions.LanguageServer.Shared; @@ -14,7 +15,6 @@ namespace OmniSharp.Extensions.LanguageServer.Server.Matchers public class ResolveCommandMatcher : IHandlerMatcher { private readonly ILogger _logger; - internal static string PrivateHandlerId = "$$__handler_id__$$"; public ResolveCommandMatcher(ILogger logger) { @@ -31,9 +31,9 @@ public IEnumerable FindHandler(object parameters, IEnumer { if (parameters is ICanBeResolved canBeResolved) { - if (canBeResolved.Data != null && canBeResolved.Data is JObject jObject && jObject.TryGetValue(PrivateHandlerId, out var value)) + if (canBeResolved.Data != null && canBeResolved.Data is JObject jObject && jObject.TryGetValue(Constants.PrivateHandlerId, out var value)) { - var id = value.Value(); + if (!Guid.TryParse(value.ToString(), out var id)) yield break; foreach (var descriptor in descriptors) { diff --git a/src/Server/Pipelines/ResolveCommandPipeline.cs b/src/Server/Pipelines/ResolveCommandPipeline.cs index 8b28443fc..3879c9b8f 100644 --- a/src/Server/Pipelines/ResolveCommandPipeline.cs +++ b/src/Server/Pipelines/ResolveCommandPipeline.cs @@ -7,6 +7,7 @@ using Microsoft.Extensions.Logging; using Newtonsoft.Json.Linq; using OmniSharp.Extensions.JsonRpc; +using OmniSharp.Extensions.LanguageServer.Protocol; using OmniSharp.Extensions.LanguageServer.Protocol.Models; using OmniSharp.Extensions.LanguageServer.Protocol.Shared; @@ -15,7 +16,6 @@ namespace OmniSharp.Extensions.LanguageServer.Server.Pipelines public class ResolveCommandPipeline : IPipelineBehavior { private readonly ILogger> _logger; - internal static string PrivateHandlerId = "$$__handler_id__$$"; private readonly ILspHandlerDescriptor _descriptor; public ResolveCommandPipeline(IRequestContext context, ILogger> logger) @@ -30,18 +30,20 @@ public async Task Handle(TRequest request, CancellationToken cancella cancellationToken.ThrowIfCancellationRequested(); // Only pin the handler type, if we know the source handler (codelens) is also the resolver. - if (_descriptor is LspHandlerDescriptor handlerDescriptor && - response is IEnumerable canBeResolveds && - _descriptor?.CanBeResolvedHandlerType?.GetTypeInfo().IsAssignableFrom(_descriptor.ImplementationType) == true) + if (response is IEnumerable canBeResolvedItems) { + var id = _descriptor.Handler is ICanBeIdentifiedHandler resolved ? resolved.Id : Guid.Empty; _logger.LogTrace( "Updating Resolve items with wrapped data for {Method}:{Handler}", _descriptor.Method, _descriptor.ImplementationType.FullName); - foreach (var item in canBeResolveds) + foreach (var item in canBeResolvedItems) { item.Data ??= new JObject(); - item.Data[PrivateHandlerId] = _descriptor.Handler is ICanBeIdentifiedHandler resolved ? resolved.Id : Guid.Empty; + if (item.Data is JObject o) + { + o[Constants.PrivateHandlerId] = id; + } } } diff --git a/src/Shared/SharedHandlerCollection.cs b/src/Shared/SharedHandlerCollection.cs index 02f33164b..1a84bc79a 100644 --- a/src/Shared/SharedHandlerCollection.cs +++ b/src/Shared/SharedHandlerCollection.cs @@ -1,9 +1,11 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using System.Reactive.Disposables; using System.Reflection; +using System.Threading; using Microsoft.Extensions.DependencyInjection; using OmniSharp.Extensions.JsonRpc; using OmniSharp.Extensions.LanguageServer.Protocol; @@ -21,7 +23,7 @@ class SharedHandlerCollection : IHandlerCollection private readonly ISupportedCapabilities _supportedCapabilities; private readonly TextDocumentIdentifiers _textDocumentIdentifiers; - internal readonly HashSet _handlers = new HashSet(); + private ImmutableHashSet _descriptors = ImmutableHashSet.Empty; private IServiceProvider _serviceProvider; public SharedHandlerCollection(ISupportedCapabilities supportedCapabilities, @@ -38,7 +40,7 @@ public void SetServiceProvider(IServiceProvider serviceProvider) public IEnumerator GetEnumerator() { - return _handlers.GetEnumerator(); + return _descriptors.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() @@ -46,71 +48,71 @@ IEnumerator IEnumerable.GetEnumerator() return GetEnumerator(); } - IDisposable IHandlersManager.Add(IJsonRpcHandler handler, JsonRpcHandlerOptions options) => Add(new[] { handler }, options); + IDisposable IHandlersManager.Add(IJsonRpcHandler handler, JsonRpcHandlerOptions options) => Add(new[] {handler}, options); IDisposable IHandlersManager.Add(string method, IJsonRpcHandler handler, JsonRpcHandlerOptions options) => Add(method, handler, options); IDisposable IHandlersManager.AddLink(string sourceMethod, string destinationMethod) { - var source = _handlers.First(z => z.Method == sourceMethod); + var source = _descriptors.First(z => z.Method == sourceMethod); LspHandlerDescriptor descriptor = null; descriptor = GetDescriptor( destinationMethod, source.HandlerType, source.Handler, - source.RequestProcessType.HasValue ? new JsonRpcHandlerOptions() { RequestProcessType = source.RequestProcessType.Value } : null, + source.RequestProcessType.HasValue ? new JsonRpcHandlerOptions() {RequestProcessType = source.RequestProcessType.Value} : null, source.TypeDescriptor, source.HandlerType, source.RegistrationType, source.CapabilityType); - _handlers.Add(descriptor); + Interlocked.Exchange(ref _descriptors, _descriptors.Add(descriptor)); var cd = new CompositeDisposable(); if (descriptor.Handler is ITextDocumentIdentifier textDocumentIdentifier) { cd.Add(_textDocumentIdentifiers.Add(textDocumentIdentifier)); } - return new LspHandlerDescriptorDisposable(new[] { descriptor }, cd); + return new LspHandlerDescriptorDisposable(new[] {descriptor}, cd); } public LspHandlerDescriptorDisposable Add(string method, IJsonRpcHandler handler, JsonRpcHandlerOptions options) { var descriptor = GetDescriptor(method, handler.GetType(), handler, options); - _handlers.Add(descriptor); + Interlocked.Exchange(ref _descriptors, _descriptors.Add(descriptor)); var cd = new CompositeDisposable(); if (descriptor.Handler is ITextDocumentIdentifier textDocumentIdentifier) { cd.Add(_textDocumentIdentifiers.Add(textDocumentIdentifier)); } - return new LspHandlerDescriptorDisposable(new[] { descriptor }, cd); + return new LspHandlerDescriptorDisposable(new[] {descriptor}, cd); } public LspHandlerDescriptorDisposable Add(string method, Func handlerFunc, JsonRpcHandlerOptions options) { var handler = handlerFunc(_serviceProvider); var descriptor = GetDescriptor(method, handler.GetType(), handler, options); - _handlers.Add(descriptor); + Interlocked.Exchange(ref _descriptors, _descriptors.Add(descriptor)); var cd = new CompositeDisposable(); if (descriptor.Handler is ITextDocumentIdentifier textDocumentIdentifier) { cd.Add(_textDocumentIdentifiers.Add(textDocumentIdentifier)); } - return new LspHandlerDescriptorDisposable(new[] { descriptor }, cd); + return new LspHandlerDescriptorDisposable(new[] {descriptor}, cd); } public LspHandlerDescriptorDisposable Add(string method, Type handlerType, JsonRpcHandlerOptions options) { var descriptor = GetDescriptor(method, handlerType, _serviceProvider, options); - _handlers.Add(descriptor); + Interlocked.Exchange(ref _descriptors, _descriptors.Add(descriptor)); var cd = new CompositeDisposable(); if (descriptor.Handler is ITextDocumentIdentifier textDocumentIdentifier) { cd.Add(_textDocumentIdentifiers.Add(textDocumentIdentifier)); } - return new LspHandlerDescriptorDisposable(new[] { descriptor }, cd); + return new LspHandlerDescriptorDisposable(new[] {descriptor}, cd); } public LspHandlerDescriptorDisposable Add(params Type[] handlerTypes) @@ -126,7 +128,7 @@ public LspHandlerDescriptorDisposable Add(params Type[] handlerTypes) { var descriptor = GetDescriptor(method, implementedInterface, _serviceProvider, null); descriptors.Add(descriptor); - _handlers.Add(descriptor); + Interlocked.Exchange(ref _descriptors, _descriptors.Add(descriptor)); if (descriptor.Handler is ITextDocumentIdentifier textDocumentIdentifier) { cd.Add(_textDocumentIdentifiers.Add(textDocumentIdentifier)); @@ -154,7 +156,7 @@ public LspHandlerDescriptorDisposable Add(params IJsonRpcHandler[] handlers) { var descriptor = GetDescriptor(method, implementedInterface, handler, null); descriptors.Add(descriptor); - _handlers.Add(descriptor); + Interlocked.Exchange(ref _descriptors, _descriptors.Add(descriptor)); if (descriptor.Handler is ITextDocumentIdentifier textDocumentIdentifier) { cd.Add(_textDocumentIdentifiers.Add(textDocumentIdentifier)); @@ -167,7 +169,6 @@ public LspHandlerDescriptorDisposable Add(params IJsonRpcHandler[] handlers) class EqualityComparer : IEqualityComparer<(string method, Type implementedInterface)> { - public bool Equals((string method, Type implementedInterface) x, (string method, Type implementedInterface) y) { return x.method?.Equals(y.method) == true; @@ -194,7 +195,7 @@ private LspHandlerDescriptorDisposable Add(IJsonRpcHandler[] handlers, JsonRpcHa { var descriptor = GetDescriptor(method, implementedInterface, handler, options); descriptors.Add(descriptor); - _handlers.Add(descriptor); + Interlocked.Exchange(ref _descriptors, _descriptors.Add(descriptor)); if (descriptor.Handler is ITextDocumentIdentifier textDocumentIdentifier) { cd.Add(_textDocumentIdentifiers.Add(textDocumentIdentifier)); @@ -241,7 +242,7 @@ private LspHandlerDescriptor GetDescriptor(string method, Type handlerType, IJso { registrationOptions = GetRegistrationMethod .MakeGenericMethod(registrationType) - .Invoke(null, new object[] { handler }); + .Invoke(null, new object[] {handler}); } var key = "default"; @@ -290,10 +291,19 @@ private LspHandlerDescriptor GetDescriptor(string method, Type handlerType, IJso @params, registrationType, registrationOptions, - (registrationType == null ? (Func)(() => false) : (() => _supportedCapabilities.AllowsDynamicRegistration(capabilityType))), + (registrationType == null ? (Func) (() => false) : (() => _supportedCapabilities.AllowsDynamicRegistration(capabilityType))), capabilityType, requestProcessType, - () => { _handlers.RemoveWhere(d => d.Handler == handler); }, + () => { + var descriptors = _descriptors.ToBuilder(); + foreach (var descriptor in _descriptors) + { + if (descriptor.Handler != handler) continue; + descriptors.Remove(descriptor); + } + + Interlocked.Exchange(ref _descriptors, descriptors.ToImmutable()); + }, typeDescriptor); return descriptor; diff --git a/test/JsonRpc.Tests/HandlerResolverTests.cs b/test/JsonRpc.Tests/HandlerResolverTests.cs index 67459117a..290f38081 100644 --- a/test/JsonRpc.Tests/HandlerResolverTests.cs +++ b/test/JsonRpc.Tests/HandlerResolverTests.cs @@ -36,7 +36,7 @@ public void Should_Contain_AllDefinedMethods(Type requestHandler, string key) { var handler = new HandlerCollection(Enumerable.Empty()); handler.Add((IJsonRpcHandler)Substitute.For(new Type[] { requestHandler }, new object[0])); - handler._handlers.Should().Contain(x => x.Method == key); + handler.Should().Contain(x => x.Method == key); } [Theory] diff --git a/test/Lsp.Tests/HandlerResolverTests.cs b/test/Lsp.Tests/HandlerResolverTests.cs index 05acef73c..16ff6a3a2 100644 --- a/test/Lsp.Tests/HandlerResolverTests.cs +++ b/test/Lsp.Tests/HandlerResolverTests.cs @@ -10,6 +10,7 @@ using MediatR; using OmniSharp.Extensions.LanguageServer.Protocol.Document; using OmniSharp.Extensions.LanguageServer.Protocol.General; +using OmniSharp.Extensions.LanguageServer.Protocol.Shared; using OmniSharp.Extensions.LanguageServer.Shared; namespace Lsp.Tests @@ -27,8 +28,8 @@ public void Should_Contain_AllDefinedMethods(Type requestHandler, string key, in var sub = (IJsonRpcHandler)Substitute.For(new Type[] { requestHandler }, new object[0]); handler.Add(sub); - handler._handlers.Should().Contain(x => x.Method == key); - handler._handlers.Count.Should().Be(count); + handler.Should().Contain(x => x.Method == key); + handler.Should().HaveCount(count); } [Fact] @@ -43,9 +44,9 @@ public void Should_Contain_AllConcreteDefinedMethods() Substitute.For() ); - handler._handlers.Should().Contain(x => x.Method == "exit"); - handler._handlers.Should().Contain(x => x.Method == "shutdown"); - handler._handlers.Count.Should().Be(4); + handler.Should().Contain(x => x.Method == "exit"); + handler.Should().Contain(x => x.Method == "shutdown"); + handler.Should().HaveCount(4); } [Theory] @@ -59,8 +60,8 @@ public void Should_Contain_AllDefinedTextDocumentSyncMethods(string key, int cou var sub = (IJsonRpcHandler)TextDocumentSyncHandlerExtensions.With(DocumentSelector.ForPattern("**/*.something"), "csharp"); handler.Add(sub); - handler._handlers.Should().Contain(x => x.Method == key); - handler._handlers.Count.Should().Be(count); + handler.Should().Contain(x => x.Method == key); + handler.Should().HaveCount(count); } [Theory] @@ -77,8 +78,8 @@ public void Should_Contain_AllDefinedLanguageServerMethods(string key, int count Substitute.For(), Substitute.For() ); - handler._handlers.Should().Contain(x => x.Method == key); - handler._handlers.Count.Should().Be(count); + handler.Should().Contain(x => x.Method == key); + handler.Should().HaveCount(count); } [Theory] @@ -103,8 +104,8 @@ public void Should_Contain_AllDefinedLanguageServerMethods_GivenDuplicates(strin Substitute.For(), Substitute.For() ); - handler._handlers.Should().Contain(x => x.Method == key); - handler._handlers.Count.Should().Be(count); + handler.Should().Contain(x => x.Method == key); + handler.Should().HaveCount(count); } [Theory] @@ -121,8 +122,8 @@ public void Should_Contain_AllDefinedMethods_ForDifferentKeys(string key, int co handler.Add(sub); handler.Add(sub2); - handler._handlers.Should().Contain(x => x.Method == key); - handler._handlers.Count.Should().Be(count); + handler.Should().Contain(x => x.Method == key); + handler.Should().HaveCount(count); } [Theory] @@ -137,9 +138,9 @@ public void Should_Contain_AllDefinedMethods_OnLanguageServer(Type requestHandle DocumentSelector = new DocumentSelector() }); handler.Add(sub); - handler._handlers.Should().Contain(x => x.Method == key); - handler._handlers.Should().Contain(x => x.Method == key2); - handler._handlers.Count.Should().Be(count); + handler.Should().Contain(x => x.Method == key); + handler.Should().Contain(x => x.Method == key2); + handler.Should().HaveCount(count); } [Theory] @@ -161,9 +162,9 @@ public void Should_Contain_AllDefinedMethods_OnLanguageServer_WithDifferentKeys( }); handler.Add(sub); handler.Add(sub2); - handler._handlers.Should().Contain(x => x.Method == key); - handler._handlers.Should().Contain(x => x.Method == key2); - handler._handlers.Count.Should().Be(count); + handler.Should().Contain(x => x.Method == key); + handler.Should().Contain(x => x.Method == key2); + handler.Should().HaveCount(count); } [Theory] @@ -175,9 +176,9 @@ public void Should_AllowSpecificHandlers_ToBeAdded(string method, Type handlerTy var sub2 = (IJsonRpcHandler)Substitute.For(new Type[] { handlerType }, new object[0]); handler.Add(method, sub, null); handler.Add(method, sub2, null); - handler._handlers.Should().Contain(x => x.Method == method); - handler._handlers.Should().Contain(x => x.Method == method); - handler._handlers.Count.Should().Be(1); + handler.Should().Contain(x => x.Method == method); + handler.Should().Contain(x => x.Method == method); + handler.Should().HaveCount(1); } [Theory] @@ -187,7 +188,7 @@ public void Should_DealWithClassesThatImplementMultipleHandlers_WithoutConflicti var handler = new SharedHandlerCollection(SupportedCapabilitiesFixture.AlwaysTrue, new TextDocumentIdentifiers()); handler.Add(sub); - var descriptor = handler._handlers.First(x => x.Method == method); + var descriptor = handler.OfType().First(x => x.Method == method); descriptor.Key.Should().Be("default"); } @@ -203,7 +204,7 @@ public void Should_DealWithClassesThatImplementMultipleHandlers_BySettingKeyAcco var handler = new SharedHandlerCollection(SupportedCapabilitiesFixture.AlwaysTrue, new TextDocumentIdentifiers()); handler.Add(codeLensHandler as IJsonRpcHandler); - var descriptor = handler._handlers.Select(x => x.Key); + var descriptor = handler.OfType().Select(x => x.Key); descriptor.Should().BeEquivalentTo(new [] { "[foo]", "[foo]" }); } diff --git a/test/Lsp.Tests/Integration/TypedCodeLensTests.cs b/test/Lsp.Tests/Integration/TypedCodeLensTests.cs index 9bddff301..c8d1a2388 100644 --- a/test/Lsp.Tests/Integration/TypedCodeLensTests.cs +++ b/test/Lsp.Tests/Integration/TypedCodeLensTests.cs @@ -123,7 +123,7 @@ public async Task Should_Aggregate_With_All_Related_Handlers() var lens = codeLens.ToArray(); var responses = await Task.WhenAll(lens.Select(z => client.ResolveCodeLens(z))); - responses.Select(z => z.Command.Name).Should().Contain("resolved-a", "resolved-b", "resolved-c"); + responses.Select(z => z.Command.Name).Should().Contain(new [] { "resolved-a", "resolved-b", "resolved-c" }); responses.Select(z => z.Command.Name).Should().NotContain("resolved-d"); lens.Length.Should().Be(3); } @@ -566,7 +566,7 @@ public async Task Should_Resolve_Partial() item.Command.Name.Should().Be("resolved"); } - class Data + class Data : HandlerIdentity { public string Name { get; set; } public Guid Id { get; set; } @@ -574,7 +574,7 @@ class Data } - class Nested + class Nested : HandlerIdentity { public DateTimeOffset Date { get; set; } } diff --git a/test/Lsp.Tests/Integration/TypedCompletionTests.cs b/test/Lsp.Tests/Integration/TypedCompletionTests.cs index 2cbe30617..cfb747183 100644 --- a/test/Lsp.Tests/Integration/TypedCompletionTests.cs +++ b/test/Lsp.Tests/Integration/TypedCompletionTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Reactive.Linq; @@ -121,7 +121,7 @@ public async Task Should_Aggregate_With_All_Related_Handlers() var lens = codeLens.ToArray(); var responses = await Task.WhenAll(lens.Select(z => client.ResolveCompletion(z))); - responses.Select(z => z.Command.Name).Should().Contain("resolved-a", "resolved-b", "resolved-c"); + responses.Select(z => z.Command.Name).Should().Contain(new[] { "resolved-a", "resolved-b", "resolved-c" }); responses.Select(z => z.Command.Name).Should().NotContain("resolved-d"); lens.Length.Should().Be(3); } @@ -564,7 +564,7 @@ public async Task Should_Resolve_Partial() item.Detail.Should().Be("resolved"); } - class Data + class Data : HandlerIdentity { public string Name { get; set; } public Guid Id { get; set; } @@ -572,7 +572,7 @@ class Data } - class Nested + class Nested : HandlerIdentity { public DateTimeOffset Date { get; set; } } diff --git a/test/Lsp.Tests/Integration/TypedDocumentLinkTests.cs b/test/Lsp.Tests/Integration/TypedDocumentLinkTests.cs index 9a3fe894f..f141cf934 100644 --- a/test/Lsp.Tests/Integration/TypedDocumentLinkTests.cs +++ b/test/Lsp.Tests/Integration/TypedDocumentLinkTests.cs @@ -110,7 +110,7 @@ public async Task Should_Aggregate_With_All_Related_Handlers() var lens = codeLens.ToArray(); var responses = await Task.WhenAll(lens.Select(z => client.ResolveDocumentLink(z))); - responses.Select(z => z.Tooltip).Should().Contain("resolved-a", "resolved-b", "resolved-c"); + responses.Select(z => z.Tooltip).Should().Contain(new [] { "resolved-a", "resolved-b", "resolved-c" }); responses.Select(z => z.Tooltip).Should().NotContain("resolved-d"); lens.Length.Should().Be(3); } @@ -517,7 +517,7 @@ public async Task Should_Resolve_Partial() item.Tooltip.Should().Be("resolved"); } - class Data + class Data : HandlerIdentity { public string Name { get; set; } public Guid Id { get; set; } @@ -525,7 +525,7 @@ class Data } - class Nested + class Nested : HandlerIdentity { public DateTimeOffset Date { get; set; } } diff --git a/test/Lsp.Tests/Matchers/ResolveCommandMatcherTests.cs b/test/Lsp.Tests/Matchers/ResolveCommandMatcherTests.cs index 1c780b36c..f0c74dfab 100644 --- a/test/Lsp.Tests/Matchers/ResolveCommandMatcherTests.cs +++ b/test/Lsp.Tests/Matchers/ResolveCommandMatcherTests.cs @@ -94,7 +94,7 @@ public void Should_Return_CodeLensResolve_Descriptor() // When var result = handlerMatcher.FindHandler(new CodeLens() { - Data = JObject.FromObject(new Dictionary() { [ResolveCommandMatcher.PrivateHandlerId] = TrueId, ["a"] = 1}) + Data = JObject.FromObject(new Dictionary() { [Constants.PrivateHandlerId] = TrueId, ["a"] = 1}) }, new List { new LspHandlerDescriptor(TextDocumentNames.CodeLensResolve, @@ -171,7 +171,7 @@ public void Should_Return_CompletionResolve_Descriptor() // When var result = handlerMatcher.FindHandler(new CompletionItem() { - Data = JObject.FromObject(new Dictionary() { [ResolveCommandMatcher.PrivateHandlerId] = TrueId, ["a"] = 1}) + Data = JObject.FromObject(new Dictionary() { [Constants.PrivateHandlerId] = TrueId, ["a"] = 1}) }, new List { new LspHandlerDescriptor(TextDocumentNames.CompletionResolve, @@ -246,7 +246,7 @@ public async Task Should_Update_CompletionItems_With_HandlerType() response.Should().BeEquivalentTo(list); (response as CompletionList).Items.Should().Contain(item); var responseItem = (response as CompletionList).Items.First(); - responseItem.Data[ResolveCommandMatcher.PrivateHandlerId].Value().Should().Be(TrueId); + responseItem.Data[Constants.PrivateHandlerId].Value().Should().Be(TrueId); responseItem.Data["hello"].Value().Should().Be("world"); } @@ -290,7 +290,7 @@ public async Task Should_Update_CodeLensContainer_With_HandlerType() response.Should().BeEquivalentTo(list); (response as CodeLensContainer).Should().Contain(item); var responseItem = (response as CodeLensContainer).First(); - responseItem.Data[ResolveCommandMatcher.PrivateHandlerId].Value().Should().Be(TrueId); + responseItem.Data[Constants.PrivateHandlerId].Value().Should().Be(TrueId); responseItem.Data["hello"].Value().Should().Be("world"); } @@ -323,14 +323,14 @@ public async Task Should_Update_CodeLens_Removing_HandlerType() var item = new CodeLens() { Data = JObject.FromObject(new {hello = "world"}) }; - item.Data[ResolveCommandMatcher.PrivateHandlerId] = Guid.Empty; + item.Data[Constants.PrivateHandlerId] = Guid.Empty; // When var response = await handlerMatcher.Handle(item, CancellationToken.None, () => Task.FromResult(item)); // Then response.Should().BeEquivalentTo(item); - item.Data?[ResolveCommandMatcher.PrivateHandlerId].Value().Should().BeEmpty(); + item.Data?[Constants.PrivateHandlerId].Value().Should().BeEmpty(); item.Data["hello"].Value().Should().Be("world"); } }