From f5668e4fbe5832824d30cdf6e291c884db4db7f3 Mon Sep 17 00:00:00 2001 From: David Driscoll Date: Mon, 31 Aug 2020 23:44:32 -0400 Subject: [PATCH 1/3] Added additional textdocumentsync converter tests --- .../Converters/TextDocumentSyncConverter.cs | 17 +++++++++++++---- .../Server/ServerCapabilitiesTests.cs | 12 ++++++++++++ 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/Protocol/Serialization/Converters/TextDocumentSyncConverter.cs b/src/Protocol/Serialization/Converters/TextDocumentSyncConverter.cs index 48f9ba612..2aa25e1e2 100644 --- a/src/Protocol/Serialization/Converters/TextDocumentSyncConverter.cs +++ b/src/Protocol/Serialization/Converters/TextDocumentSyncConverter.cs @@ -9,6 +9,11 @@ internal class TextDocumentSyncConverter : JsonConverter { public override void WriteJson(JsonWriter writer, TextDocumentSync value, JsonSerializer serializer) { + if (value == null) + { + writer.WriteNull(); + return; + } if (value.HasOptions) { serializer.Serialize(writer, value.Options); @@ -21,12 +26,16 @@ public override void WriteJson(JsonWriter writer, TextDocumentSync value, JsonSe public override TextDocumentSync ReadJson(JsonReader reader, Type objectType, TextDocumentSync existingValue, bool hasExistingValue, JsonSerializer serializer) { - if (reader.TokenType == JsonToken.Integer) + switch (reader.TokenType) { - return new TextDocumentSync((TextDocumentSyncKind) Convert.ToInt32(reader.Value)); + case JsonToken.Integer: + return new TextDocumentSync((TextDocumentSyncKind) Convert.ToInt32(reader.Value)); + case JsonToken.Null: + case JsonToken.Undefined: + return new TextDocumentSync(TextDocumentSyncKind.None); + default: + return new TextDocumentSync(JObject.Load(reader).ToObject(serializer)); } - - return new TextDocumentSync(JObject.Load(reader).ToObject(serializer)); } public override bool CanRead => true; diff --git a/test/Lsp.Tests/Capabilities/Server/ServerCapabilitiesTests.cs b/test/Lsp.Tests/Capabilities/Server/ServerCapabilitiesTests.cs index 1b422d374..33f45144e 100644 --- a/test/Lsp.Tests/Capabilities/Server/ServerCapabilitiesTests.cs +++ b/test/Lsp.Tests/Capabilities/Server/ServerCapabilitiesTests.cs @@ -88,5 +88,17 @@ public void Optional(string expected) var deresult = new Serializer(ClientVersion.Lsp3).DeserializeObject(expected); deresult.Should().BeEquivalentTo(model); } + + [Theory] + [JsonFixture] + public void Null_Text_Document_Sync(string expected) + { + var model = new ServerCapabilities { + TextDocumentSync = new TextDocumentSync(new TextDocumentSyncOptions()) + }; + + var deresult = new Serializer(ClientVersion.Lsp3).DeserializeObject(expected); + deresult.Should().BeEquivalentTo(model); + } } } From 686cf592f55cb061180b4c5619aad0e58e0fef36 Mon Sep 17 00:00:00 2001 From: David Driscoll Date: Mon, 31 Aug 2020 23:45:54 -0400 Subject: [PATCH 2/3] added new semantic token refresh handler --- .../LanguageClientRegistrationManager.cs | 5 + .../GenerateHandlerMethodsGenerator.cs | 12 +- src/JsonRpc.Generators/Helpers.cs | 61 ++++- .../Proposals/ISemanticTokensDeltaHandler.cs | 8 +- .../LanguageProtocolDelegatingHandlers.cs | 82 +++--- .../Models/Proposals/ISemanticTokenResult.cs | 18 ++ .../Models/Proposals/SemanticTokens.cs | 12 +- .../Models/Proposals/SemanticTokensDelta.cs | 12 +- .../Proposals/SemanticTokensFullOrDelta.cs | 18 ++ .../SemanticTokensFullOrDeltaPartialResult.cs | 3 + .../Proposals/SemanticTokensRefreshParams.cs | 11 + .../Converters/ProgressTokenConverter.cs | 6 +- .../ISemanticTokensRefreshHandler.cs | 28 ++ src/Protocol/WorkspaceNames.cs | 4 + ...nguageServerServiceCollectionExtensions.cs | 1 + .../Pipelines/SemanticTokensDeltaPipeline.cs | 56 ++++ ...ilitiesTests_$Null_Text_Document_Sync.json | 3 + .../Lsp.Tests/Integration/PartialItemTests.cs | 214 ++------------- .../Integration/PartialItemsTests.cs | 243 ++++++++++++++++++ 19 files changed, 548 insertions(+), 249 deletions(-) create mode 100644 src/Protocol/Models/Proposals/ISemanticTokenResult.cs create mode 100644 src/Protocol/Models/Proposals/SemanticTokensRefreshParams.cs create mode 100644 src/Protocol/Workspace/Proposals/ISemanticTokensRefreshHandler.cs create mode 100644 src/Server/Pipelines/SemanticTokensDeltaPipeline.cs create mode 100644 test/Lsp.Tests/Capabilities/Server/ServerCapabilitiesTests_$Null_Text_Document_Sync.json create mode 100644 test/Lsp.Tests/Integration/PartialItemsTests.cs diff --git a/src/Client/LanguageClientRegistrationManager.cs b/src/Client/LanguageClientRegistrationManager.cs index 8bed4f93c..4493a4bf1 100644 --- a/src/Client/LanguageClientRegistrationManager.cs +++ b/src/Client/LanguageClientRegistrationManager.cs @@ -77,6 +77,11 @@ public void RegisterCapabilities(ServerCapabilities serverCapabilities) continue; } + if (string.IsNullOrWhiteSpace(registrationOptions.Id)) + { + registrationOptions.Id = Guid.NewGuid().ToString(); + } + var reg = new Registration { Id = registrationOptions.Id, Method = method, diff --git a/src/JsonRpc.Generators/GenerateHandlerMethodsGenerator.cs b/src/JsonRpc.Generators/GenerateHandlerMethodsGenerator.cs index e9da8efd7..78822e6f0 100644 --- a/src/JsonRpc.Generators/GenerateHandlerMethodsGenerator.cs +++ b/src/JsonRpc.Generators/GenerateHandlerMethodsGenerator.cs @@ -330,13 +330,13 @@ MemberDeclarationSyntax MakeDerivedAction(TypeSyntax syntax) { var partialTypeSyntax = ResolveTypeName(partialItem); - method = method.WithExpressionBody(GetPartialResultHandlerExpression(GetMethodName(handlerInterface), requestType, responseType)); + method = method.WithExpressionBody(GetPartialResultHandlerExpression(GetMethodName(handlerInterface), requestType, partialItem, responseType)); yield return MakeAction(CreatePartialAction(requestType, partialTypeSyntax, true)); yield return MakeAction(CreatePartialAction(requestType, partialTypeSyntax, false)); if (capability != null) { - method = method.WithExpressionBody(GetPartialResultCapabilityHandlerExpression(GetMethodName(handlerInterface), requestType, responseType, capability)); + method = method.WithExpressionBody(GetPartialResultCapabilityHandlerExpression(GetMethodName(handlerInterface), requestType, partialItem, responseType, capability)); yield return MakeAction(CreatePartialAction(requestType, partialTypeSyntax, capability)); } } @@ -346,6 +346,10 @@ MemberDeclarationSyntax MakeDerivedAction(TypeSyntax syntax) method = method.WithExpressionBody( GetRequestCapabilityHandlerExpression(GetMethodName(handlerInterface), requestType, responseType, capability) ); + if (responseType.Name == "Unit") + { + method = method.WithExpressionBody(GetVoidRequestCapabilityHandlerExpression(GetMethodName(handlerInterface), requestType, capability)); + } yield return MakeAction(CreateAsyncFunc(responseType, requestType, capability)); } } @@ -443,7 +447,7 @@ MemberDeclarationSyntax MakeDerivedAction(TypeSyntax syntax) { var partialTypeSyntax = ResolveTypeName(partialItem); - method = method.WithBody(GetPartialResultRegistrationHandlerExpression(GetMethodName(handlerInterface), requestType, responseType, registrationOptions)); + method = method.WithBody(GetPartialResultRegistrationHandlerExpression(GetMethodName(handlerInterface), requestType, partialItem, responseType, registrationOptions)); yield return MakeAction(CreatePartialAction(requestType, partialTypeSyntax, true)); yield return MakeAction(CreatePartialAction(requestType, partialTypeSyntax, false)); @@ -451,7 +455,7 @@ MemberDeclarationSyntax MakeDerivedAction(TypeSyntax syntax) { method = method.WithBody( GetPartialResultRegistrationHandlerExpression( - GetMethodName(handlerInterface), requestType, responseType, registrationOptions, + GetMethodName(handlerInterface), requestType, partialItem, responseType, registrationOptions, capability ) ); diff --git a/src/JsonRpc.Generators/Helpers.cs b/src/JsonRpc.Generators/Helpers.cs index 0574e369a..18c3fc48c 100644 --- a/src/JsonRpc.Generators/Helpers.cs +++ b/src/JsonRpc.Generators/Helpers.cs @@ -444,6 +444,29 @@ ITypeSymbol capability ); } + public static ArrowExpressionClauseSyntax GetVoidRequestCapabilityHandlerExpression( + ExpressionSyntax nameExpression, ITypeSymbol requestType, + ITypeSymbol capability + ) + { + var requestName = ResolveTypeName(requestType); + var capabilityName = ResolveTypeName(capability); + return ArrowExpressionClause( + AddHandler( + Argument(nameExpression), + Argument( + CreateHandlerArgument( + IdentifierName("LanguageProtocolDelegatingHandlers"), + "RequestCapability", + requestName, + capabilityName + ) + .WithArgumentList(GetHandlerArgumentList()) + ) + ) + ); + } + public static BlockSyntax GetRequestRegistrationHandlerExpression( ExpressionSyntax nameExpression, ITypeSymbol requestType, ITypeSymbol responseType, ITypeSymbol registrationOptions @@ -593,36 +616,44 @@ public static ArrowExpressionClauseSyntax GetRequestHandlerExpression(Expression } public static ArrowExpressionClauseSyntax GetPartialResultCapabilityHandlerExpression( - ExpressionSyntax nameExpression, ITypeSymbol requestType, ITypeSymbol responseType, + ExpressionSyntax nameExpression, ITypeSymbol requestType, ITypeSymbol itemType, ITypeSymbol responseType, ITypeSymbol capability ) { var requestName = ResolveTypeName(requestType); + var itemName = ResolveTypeName(itemType); var responseName = ResolveTypeName(responseType); var capabilityName = ResolveTypeName(capability); return ArrowExpressionClause( AddHandler( Argument(nameExpression), Argument( + SimpleLambdaExpression( + Parameter( + Identifier("_") + ), CreateHandlerArgument( IdentifierName("LanguageProtocolDelegatingHandlers"), "PartialResultCapability", requestName, responseName, + itemName, capabilityName ) .WithArgumentList(GetPartialResultArgumentList(responseName)) + ) ) ) ); } public static BlockSyntax GetPartialResultRegistrationHandlerExpression( - ExpressionSyntax nameExpression, ITypeSymbol requestType, ITypeSymbol responseType, + ExpressionSyntax nameExpression, ITypeSymbol requestType, ITypeSymbol itemType, ITypeSymbol responseType, ITypeSymbol registrationOptions ) { var requestName = ResolveTypeName(requestType); + var itemName = ResolveTypeName(itemType); var responseName = ResolveTypeName(responseType); var registrationOptionsName = ResolveTypeName(registrationOptions); return Block( @@ -631,14 +662,20 @@ ITypeSymbol registrationOptions AddHandler( Argument(nameExpression), Argument( + SimpleLambdaExpression( + Parameter( + Identifier("_") + ), CreateHandlerArgument( IdentifierName("LanguageProtocolDelegatingHandlers"), "PartialResult", requestName, responseName, + itemName, registrationOptionsName ) .WithArgumentList(GetPartialResultRegistrationArgumentList(IdentifierName("registrationOptions"), responseName)) + ) ) ) ) @@ -646,12 +683,13 @@ ITypeSymbol registrationOptions } public static BlockSyntax GetPartialResultRegistrationHandlerExpression( - ExpressionSyntax nameExpression, ITypeSymbol requestType, ITypeSymbol responseType, + ExpressionSyntax nameExpression, ITypeSymbol requestType, ITypeSymbol itemType, ITypeSymbol responseType, ITypeSymbol registrationOptions, ITypeSymbol capability ) { var requestName = ResolveTypeName(requestType); + var itemName = ResolveTypeName(itemType); var responseName = ResolveTypeName(responseType); var registrationOptionsName = ResolveTypeName(registrationOptions); var capabilityName = ResolveTypeName(capability); @@ -661,36 +699,49 @@ ITypeSymbol capability AddHandler( Argument(nameExpression), Argument( + SimpleLambdaExpression( + Parameter( + Identifier("_") + ), CreateHandlerArgument( IdentifierName("LanguageProtocolDelegatingHandlers"), "PartialResult", requestName, responseName, + itemName, capabilityName, registrationOptionsName ) .WithArgumentList(GetPartialResultRegistrationArgumentList(IdentifierName("registrationOptions"), responseName)) + ) ) ) ) ); } - public static ArrowExpressionClauseSyntax GetPartialResultHandlerExpression(ExpressionSyntax nameExpression, ITypeSymbol requestType, ITypeSymbol responseType) + public static ArrowExpressionClauseSyntax GetPartialResultHandlerExpression(ExpressionSyntax nameExpression, ITypeSymbol requestType, ITypeSymbol partialItem, ITypeSymbol responseType) { var requestName = ResolveTypeName(requestType); + var itemName = ResolveTypeName(partialItem); var responseName = ResolveTypeName(responseType); return ArrowExpressionClause( AddHandler( Argument(nameExpression), Argument( + SimpleLambdaExpression( + Parameter( + Identifier("_") + ), CreateHandlerArgument( IdentifierName("LanguageProtocolDelegatingHandlers"), "PartialResult", requestName, - responseName + responseName, + itemName ) .WithArgumentList(GetPartialResultArgumentList(responseName)) + ) ) ) ); diff --git a/src/Protocol/Document/Proposals/ISemanticTokensDeltaHandler.cs b/src/Protocol/Document/Proposals/ISemanticTokensDeltaHandler.cs index 427366485..c41186aa3 100644 --- a/src/Protocol/Document/Proposals/ISemanticTokensDeltaHandler.cs +++ b/src/Protocol/Document/Proposals/ISemanticTokensDeltaHandler.cs @@ -1,7 +1,10 @@ using System; +using System.Reactive.Subjects; using System.Threading; using System.Threading.Tasks; +using MediatR; using OmniSharp.Extensions.JsonRpc; +using OmniSharp.Extensions.JsonRpc.Generation; using OmniSharp.Extensions.LanguageServer.Protocol.Client; using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities; using OmniSharp.Extensions.LanguageServer.Protocol.Models; @@ -14,6 +17,7 @@ namespace OmniSharp.Extensions.LanguageServer.Protocol.Document.Proposals [Obsolete(Constants.Proposal)] [Parallel] [Method(TextDocumentNames.SemanticTokensFull, Direction.ClientToServer)] + [GenerateHandlerMethods] public interface ISemanticTokensHandler : IJsonRpcRequestHandler, IRegistration, ICapability { @@ -22,6 +26,7 @@ public interface ISemanticTokensHandler : IJsonRpcRequestHandler, IRegistration, ICapability, IDoesNotParticipateInRegistration @@ -31,6 +36,7 @@ public interface ISemanticTokensDeltaHandler : [Obsolete(Constants.Proposal)] [Parallel] [Method(TextDocumentNames.SemanticTokensRange, Direction.ClientToServer)] + [GenerateHandlerMethods] public interface ISemanticTokensRangeHandler : IJsonRpcRequestHandler, IRegistration, ICapability, IDoesNotParticipateInRegistration { @@ -79,7 +85,7 @@ public virtual async Task Handle(SemanticTokensRangeParams reque } [Obsolete(Constants.Proposal)] - public static class SemanticTokensExtensions + public static partial class SemanticTokensExtensions { public static ILanguageServerRegistry OnSemanticTokens( this ILanguageServerRegistry registry, diff --git a/src/Protocol/LanguageProtocolDelegatingHandlers.cs b/src/Protocol/LanguageProtocolDelegatingHandlers.cs index 30c3c70c5..138818a4b 100644 --- a/src/Protocol/LanguageProtocolDelegatingHandlers.cs +++ b/src/Protocol/LanguageProtocolDelegatingHandlers.cs @@ -334,17 +334,17 @@ async Task IRequestHandler. void ICapability.SetCapability(TCapability capability) => _capability = capability; } - public sealed class PartialResult : - IJsonRpcRequestHandler, + public sealed class PartialResult : + IJsonRpcRequestHandler, IRegistration, ICapability, ICanBeIdentifiedHandler - where TItem : IPartialItemRequest - where TResponse : class, new() + where TParams : IPartialItemRequest + where TResponse : new() where TRegistrationOptions : class, new() where TCapability : ICapability { private readonly Func _factory; - private readonly Action, TCapability, CancellationToken> _handler; + private readonly Action, TCapability, CancellationToken> _handler; private readonly TRegistrationOptions _registrationOptions; private readonly IProgressManager _progressManager; private TCapability _capability; @@ -352,14 +352,14 @@ public sealed class PartialResult _id; public PartialResult( - Action, TCapability> handler, TRegistrationOptions registrationOptions, IProgressManager progressManager, Func factory + Action, TCapability> handler, TRegistrationOptions registrationOptions, IProgressManager progressManager, Func factory ) : this(Guid.Empty, (p, o, c, ct) => handler(p, o, c), registrationOptions, progressManager, factory) { } public PartialResult( - Action, TCapability, CancellationToken> handler, TRegistrationOptions registrationOptions, IProgressManager progressManager, + Action, TCapability, CancellationToken> handler, TRegistrationOptions registrationOptions, IProgressManager progressManager, Func factory ) : this(Guid.Empty, handler, registrationOptions, progressManager, factory) @@ -367,7 +367,7 @@ Func factory } public PartialResult( - Guid id, Action, TCapability> handler, TRegistrationOptions registrationOptions, IProgressManager progressManager, + Guid id, Action, TCapability> handler, TRegistrationOptions registrationOptions, IProgressManager progressManager, Func factory ) : this(id, (p, o, c, ct) => handler(p, o, c), registrationOptions, progressManager, factory) @@ -375,7 +375,7 @@ Func factory } public PartialResult( - Guid id, Action, TCapability, CancellationToken> handler, TRegistrationOptions registrationOptions, IProgressManager progressManager, + Guid id, Action, TCapability, CancellationToken> handler, TRegistrationOptions registrationOptions, IProgressManager progressManager, Func factory ) { @@ -386,8 +386,8 @@ Func factory _factory = factory; } - async Task IRequestHandler.Handle( - TItem request, + async Task IRequestHandler.Handle( + TParams request, CancellationToken cancellationToken ) { @@ -408,15 +408,15 @@ CancellationToken cancellationToken void ICapability.SetCapability(TCapability capability) => _capability = capability; } - public sealed class PartialResult : - IJsonRpcRequestHandler, + public sealed class PartialResult : + IJsonRpcRequestHandler, IRegistration, ICanBeIdentifiedHandler - where TItem : IPartialItemRequest - where TResponse : class, new() + where TParams : IPartialItemRequest + where TResponse : new() where TRegistrationOptions : class, new() { - private readonly Action, CancellationToken> _handler; + private readonly Action, CancellationToken> _handler; private readonly TRegistrationOptions _registrationOptions; private readonly IProgressManager _progressManager; private readonly Func _factory; @@ -424,14 +424,14 @@ public sealed class PartialResult : Guid ICanBeIdentifiedHandler.Id => _id; public PartialResult( - Action> handler, TRegistrationOptions registrationOptions, IProgressManager progressManager, Func factory + Action> handler, TRegistrationOptions registrationOptions, IProgressManager progressManager, Func factory ) : this(Guid.Empty, (p, o, ct) => handler(p, o), registrationOptions, progressManager, factory) { } public PartialResult( - Action, CancellationToken> handler, TRegistrationOptions registrationOptions, IProgressManager progressManager, + Action, CancellationToken> handler, TRegistrationOptions registrationOptions, IProgressManager progressManager, Func factory ) : this(Guid.Empty, handler, registrationOptions, progressManager, factory) @@ -439,14 +439,14 @@ Func factory } public PartialResult( - Guid id, Action> handler, TRegistrationOptions registrationOptions, IProgressManager progressManager, Func factory + Guid id, Action> handler, TRegistrationOptions registrationOptions, IProgressManager progressManager, Func factory ) : this(id, (p, o, ct) => handler(p, o), registrationOptions, progressManager, factory) { } public PartialResult( - Guid id, Action, CancellationToken> handler, TRegistrationOptions registrationOptions, IProgressManager progressManager, + Guid id, Action, CancellationToken> handler, TRegistrationOptions registrationOptions, IProgressManager progressManager, Func factory ) { @@ -457,7 +457,7 @@ Func factory _factory = factory; } - async Task IRequestHandler.Handle(TItem request, CancellationToken cancellationToken) + async Task IRequestHandler.Handle(TParams request, CancellationToken cancellationToken) { var observer = _progressManager.For(request, cancellationToken); if (observer != ProgressObserver.Noop) @@ -475,40 +475,40 @@ async Task IRequestHandler.Handle(TItem request, Ca TRegistrationOptions IRegistration.GetRegistrationOptions() => _registrationOptions; } - public sealed class PartialResultCapability : - IJsonRpcRequestHandler, + public sealed class PartialResultCapability : + IJsonRpcRequestHandler, ICapability, ICanBeIdentifiedHandler - where TItem : IPartialItemRequest - where TResponse : class, new() + where TParams : IPartialItemRequest + where TResponse : new() where TCapability : ICapability { - private readonly Action, CancellationToken> _handler; + private readonly Action, CancellationToken> _handler; private readonly IProgressManager _progressManager; private readonly Func _factory; private TCapability _capability; private readonly Guid _id; Guid ICanBeIdentifiedHandler.Id => _id; - public PartialResultCapability(Action> handler, IProgressManager progressManager, Func factory) : + public PartialResultCapability(Action> handler, IProgressManager progressManager, Func factory) : this((p, c, o, ct) => handler(p, c, o), progressManager, factory) { } public PartialResultCapability( - Action, CancellationToken> handler, IProgressManager progressManager, Func factory + Action, CancellationToken> handler, IProgressManager progressManager, Func factory ) : this(Guid.Empty, handler, progressManager, factory) { } - public PartialResultCapability(Guid id, Action> handler, IProgressManager progressManager, Func factory) : + public PartialResultCapability(Guid id, Action> handler, IProgressManager progressManager, Func factory) : this(id, (p, c, o, ct) => handler(p, c, o), progressManager, factory) { } public PartialResultCapability( - Guid id, Action, CancellationToken> handler, IProgressManager progressManager, Func factory + Guid id, Action, CancellationToken> handler, IProgressManager progressManager, Func factory ) { _id = id; @@ -517,7 +517,7 @@ public PartialResultCapability( _factory = factory; } - async Task IRequestHandler.Handle(TItem request, CancellationToken cancellationToken) + async Task IRequestHandler.Handle(TParams request, CancellationToken cancellationToken) { var observer = _progressManager.For(request, cancellationToken); if (observer != ProgressObserver.Noop) @@ -535,24 +535,24 @@ async Task IRequestHandler.Handle(TItem request, Ca void ICapability.SetCapability(TCapability capability) => _capability = capability; } - public sealed class PartialResult : - IJsonRpcRequestHandler, + public sealed class PartialResult : + IJsonRpcRequestHandler, ICanBeIdentifiedHandler - where TItem : IPartialItemRequest - where TResponse : class, new() + where TParams : IPartialItemRequest + where TResponse : new() { - private readonly Action, CancellationToken> _handler; + private readonly Action, CancellationToken> _handler; private readonly IProgressManager _progressManager; private readonly Func _factory; private readonly Guid _id; Guid ICanBeIdentifiedHandler.Id => _id; - public PartialResult(Action> handler, IProgressManager progressManager, Func factory) : + public PartialResult(Action> handler, IProgressManager progressManager, Func factory) : this(Guid.Empty, (p, o, ct) => handler(p, o), progressManager, factory) { } - public PartialResult(Action, CancellationToken> handler, IProgressManager progressManager, Func factory) : + public PartialResult(Action, CancellationToken> handler, IProgressManager progressManager, Func factory) : this(Guid.Empty, handler, progressManager, factory) { _handler = handler; @@ -560,12 +560,12 @@ public PartialResult(Action, CancellationToken> handler, _factory = factory; } - public PartialResult(Guid id, Action> handler, IProgressManager progressManager, Func factory) : + public PartialResult(Guid id, Action> handler, IProgressManager progressManager, Func factory) : this(id, (p, o, ct) => handler(p, o), progressManager, factory) { } - public PartialResult(Guid id, Action, CancellationToken> handler, IProgressManager progressManager, Func factory) + public PartialResult(Guid id, Action, CancellationToken> handler, IProgressManager progressManager, Func factory) { _id = id; _handler = handler; @@ -573,7 +573,7 @@ public PartialResult(Guid id, Action, CancellationToken> _factory = factory; } - async Task IRequestHandler.Handle(TItem request, CancellationToken cancellationToken) + async Task IRequestHandler.Handle(TParams request, CancellationToken cancellationToken) { var observer = _progressManager.For(request, cancellationToken); if (observer != ProgressObserver.Noop) diff --git a/src/Protocol/Models/Proposals/ISemanticTokenResult.cs b/src/Protocol/Models/Proposals/ISemanticTokenResult.cs new file mode 100644 index 000000000..b4d57330e --- /dev/null +++ b/src/Protocol/Models/Proposals/ISemanticTokenResult.cs @@ -0,0 +1,18 @@ +using System; +using OmniSharp.Extensions.LanguageServer.Protocol.Serialization; + +namespace OmniSharp.Extensions.LanguageServer.Protocol.Models.Proposals +{ + [Obsolete(Constants.Proposal)] + public interface ISemanticTokenResult + { + /// + /// An optional result id. If provided and clients support delta updating + /// the client will include the result id in the next semantic token request. + /// A server can then instead of computing all semantic tokens again simply + /// send a delta. + /// + [Optional] + public string ResultId { get; set; } + } +} diff --git a/src/Protocol/Models/Proposals/SemanticTokens.cs b/src/Protocol/Models/Proposals/SemanticTokens.cs index 61d1413b7..06820af9d 100644 --- a/src/Protocol/Models/Proposals/SemanticTokens.cs +++ b/src/Protocol/Models/Proposals/SemanticTokens.cs @@ -8,8 +8,18 @@ namespace OmniSharp.Extensions.LanguageServer.Protocol.Models.Proposals /// @since 3.16.0 /// [Obsolete(Constants.Proposal)] - public class SemanticTokens + public class SemanticTokens : ISemanticTokenResult { + public SemanticTokens() + { + + } + + public SemanticTokens(SemanticTokensPartialResult partialResult) + { + Data = partialResult.Data; + } + /// /// An optional result id. If provided and clients support delta updating /// the client will include the result id in the next semantic token request. diff --git a/src/Protocol/Models/Proposals/SemanticTokensDelta.cs b/src/Protocol/Models/Proposals/SemanticTokensDelta.cs index b6bda34ac..da9e44c58 100644 --- a/src/Protocol/Models/Proposals/SemanticTokensDelta.cs +++ b/src/Protocol/Models/Proposals/SemanticTokensDelta.cs @@ -7,8 +7,18 @@ namespace OmniSharp.Extensions.LanguageServer.Protocol.Models.Proposals /// @since 3.16.0 /// [Obsolete(Constants.Proposal)] - public class SemanticTokensDelta + public class SemanticTokensDelta : ISemanticTokenResult { + public SemanticTokensDelta() + { + + } + + public SemanticTokensDelta(SemanticTokensDeltaPartialResult partialResult) + { + Edits = partialResult.Edits; + } + /// /// An optional result id. If provided and clients support delta updating /// the client will include the result id in the next semantic token request. diff --git a/src/Protocol/Models/Proposals/SemanticTokensFullOrDelta.cs b/src/Protocol/Models/Proposals/SemanticTokensFullOrDelta.cs index a4b26c090..e2e0277fd 100644 --- a/src/Protocol/Models/Proposals/SemanticTokensFullOrDelta.cs +++ b/src/Protocol/Models/Proposals/SemanticTokensFullOrDelta.cs @@ -20,6 +20,24 @@ public SemanticTokensFullOrDelta(SemanticTokens full) Full = full; } + public SemanticTokensFullOrDelta(SemanticTokensFullOrDeltaPartialResult partialResult) + { + Full = null; + Delta = null; + + if (partialResult.IsDelta) + { + Delta = new SemanticTokensDelta(partialResult.Delta) { + Edits = partialResult.Delta.Edits + }; + } + + if (partialResult.IsFull) + { + Full = new SemanticTokens(partialResult.Full); + } + } + public bool IsFull => Full != null; public SemanticTokens Full { get; } diff --git a/src/Protocol/Models/Proposals/SemanticTokensFullOrDeltaPartialResult.cs b/src/Protocol/Models/Proposals/SemanticTokensFullOrDeltaPartialResult.cs index 2f830e8c0..2dacd8e74 100644 --- a/src/Protocol/Models/Proposals/SemanticTokensFullOrDeltaPartialResult.cs +++ b/src/Protocol/Models/Proposals/SemanticTokensFullOrDeltaPartialResult.cs @@ -35,5 +35,8 @@ public static implicit operator SemanticTokensFullOrDeltaPartialResult(SemanticT public static implicit operator SemanticTokensFullOrDeltaPartialResult(SemanticTokensDeltaPartialResult semanticTokensDeltaPartialResult) => new SemanticTokensFullOrDeltaPartialResult(semanticTokensDeltaPartialResult); + + public static implicit operator SemanticTokensFullOrDelta(SemanticTokensFullOrDeltaPartialResult semanticTokensDeltaPartialResult) => + new SemanticTokensFullOrDelta(semanticTokensDeltaPartialResult); } } diff --git a/src/Protocol/Models/Proposals/SemanticTokensRefreshParams.cs b/src/Protocol/Models/Proposals/SemanticTokensRefreshParams.cs new file mode 100644 index 000000000..f3f025c6b --- /dev/null +++ b/src/Protocol/Models/Proposals/SemanticTokensRefreshParams.cs @@ -0,0 +1,11 @@ +using System; +using MediatR; +using OmniSharp.Extensions.JsonRpc; + +namespace OmniSharp.Extensions.LanguageServer.Protocol.Models.Proposals +{ + [Obsolete(Constants.Proposal)] + [Parallel] + [Method(WorkspaceNames.SemanticTokensRefresh, Direction.ServerToClient)] + public class SemanticTokensRefreshParams : IRequest { } +} diff --git a/src/Protocol/Serialization/Converters/ProgressTokenConverter.cs b/src/Protocol/Serialization/Converters/ProgressTokenConverter.cs index 6be01b373..35de548f6 100644 --- a/src/Protocol/Serialization/Converters/ProgressTokenConverter.cs +++ b/src/Protocol/Serialization/Converters/ProgressTokenConverter.cs @@ -20,12 +20,12 @@ public override ProgressToken ReadJson(JsonReader reader, Type objectType, Progr return new ProgressToken((long) reader.Value); } - if (reader.TokenType == JsonToken.String) + if (reader.TokenType == JsonToken.String && reader.Value is string str && !string.IsNullOrWhiteSpace(str)) { - return new ProgressToken((string) reader.Value); + return new ProgressToken(str); } - return new ProgressToken(string.Empty); + return null; } public override bool CanRead => true; diff --git a/src/Protocol/Workspace/Proposals/ISemanticTokensRefreshHandler.cs b/src/Protocol/Workspace/Proposals/ISemanticTokensRefreshHandler.cs new file mode 100644 index 000000000..003b39a55 --- /dev/null +++ b/src/Protocol/Workspace/Proposals/ISemanticTokensRefreshHandler.cs @@ -0,0 +1,28 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using OmniSharp.Extensions.JsonRpc; +using OmniSharp.Extensions.JsonRpc.Generation; +using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities; +using OmniSharp.Extensions.LanguageServer.Protocol.Models.Proposals; +using OmniSharp.Extensions.LanguageServer.Protocol.Server; + +namespace OmniSharp.Extensions.LanguageServer.Protocol.Workspace.Proposals +{ + [Obsolete(Constants.Proposal)] + [Parallel] + [Method(WorkspaceNames.SemanticTokensRefresh, Direction.ServerToClient)] + [GenerateHandlerMethods] + [GenerateRequestMethods(typeof(IWorkspaceLanguageServer), typeof(ILanguageServer))] + public interface ISemanticTokensRefreshHandler : IJsonRpcRequestHandler, ICapability { } + + [Obsolete(Constants.Proposal)] + public abstract class SemanticTokensRefreshHandlerBase : ISemanticTokensRefreshHandler + { + private SemanticTokensCapability _capability; + + public abstract Task Handle(SemanticTokensRefreshParams request, CancellationToken cancellationToken); + public void SetCapability(SemanticTokensCapability capability) => _capability = capability; + } +} diff --git a/src/Protocol/WorkspaceNames.cs b/src/Protocol/WorkspaceNames.cs index 1618a1f21..730dc17fd 100644 --- a/src/Protocol/WorkspaceNames.cs +++ b/src/Protocol/WorkspaceNames.cs @@ -1,3 +1,5 @@ +using System; + namespace OmniSharp.Extensions.LanguageServer.Protocol { public static class WorkspaceNames @@ -10,5 +12,7 @@ public static class WorkspaceNames public const string ExecuteCommand = "workspace/executeCommand"; public const string WorkspaceSymbol = "workspace/symbol"; public const string WorkspaceFolders = "workspace/workspaceFolders"; + [Obsolete(Constants.Proposal)] + public const string SemanticTokensRefresh = "workspace/semanticTokens/refresh"; } } diff --git a/src/Server/LanguageServerServiceCollectionExtensions.cs b/src/Server/LanguageServerServiceCollectionExtensions.cs index ba14c6ed7..0a434162c 100644 --- a/src/Server/LanguageServerServiceCollectionExtensions.cs +++ b/src/Server/LanguageServerServiceCollectionExtensions.cs @@ -105,6 +105,7 @@ internal static IContainer AddLanguageServerInternals(this IContainer container, container.RegisterMany(Reuse.Singleton); container.RegisterMany(Reuse.Singleton); container.RegisterMany(new[] { typeof(ResolveCommandPipeline<,>) }); + container.RegisterMany(new[] { typeof(SemanticTokensDeltaPipeline<,>) }); container.RegisterMany(Reuse.Singleton); container.RegisterMany(Reuse.Singleton); diff --git a/src/Server/Pipelines/SemanticTokensDeltaPipeline.cs b/src/Server/Pipelines/SemanticTokensDeltaPipeline.cs new file mode 100644 index 000000000..14a61891e --- /dev/null +++ b/src/Server/Pipelines/SemanticTokensDeltaPipeline.cs @@ -0,0 +1,56 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using OmniSharp.Extensions.LanguageServer.Protocol.Models.Proposals; + +namespace OmniSharp.Extensions.LanguageServer.Server.Pipelines +{ + class SemanticTokensDeltaPipeline : IPipelineBehavior + { + public async Task Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate next) + { + if (request is SemanticTokensParams semanticTokensParams) + { + var response = await next(); + if (GetResponse(semanticTokensParams, response, out var result) && string.IsNullOrEmpty(result.ResultId)) + { + result.ResultId = Guid.NewGuid().ToString(); + } + return response; + } + + if (request is SemanticTokensDeltaParams semanticTokensDeltaParams) + { + var response = await next(); + if (GetResponse(semanticTokensDeltaParams, response, out var result)) + { + if (result?.IsFull == true && string.IsNullOrEmpty(result.Value.Full.ResultId)) + { + result.Value.Full.ResultId = semanticTokensDeltaParams.PreviousResultId; + } + + if (result?.IsDelta == true && string.IsNullOrEmpty(result.Value.Delta.ResultId)) + { + result.Value.Delta.ResultId = semanticTokensDeltaParams.PreviousResultId; + } + } + return response; + } + + return await next(); + } + + private bool GetResponse(IRequest request, object response, out TR result) + { + if (response is TR r) + { + result = r; + return true; + } + + result = default; + return false; + } + } +} diff --git a/test/Lsp.Tests/Capabilities/Server/ServerCapabilitiesTests_$Null_Text_Document_Sync.json b/test/Lsp.Tests/Capabilities/Server/ServerCapabilitiesTests_$Null_Text_Document_Sync.json new file mode 100644 index 000000000..bec029cee --- /dev/null +++ b/test/Lsp.Tests/Capabilities/Server/ServerCapabilitiesTests_$Null_Text_Document_Sync.json @@ -0,0 +1,3 @@ +{ + "textDocumentSync": {} +} diff --git a/test/Lsp.Tests/Integration/PartialItemTests.cs b/test/Lsp.Tests/Integration/PartialItemTests.cs index 01ecda4b6..d20a2fddc 100644 --- a/test/Lsp.Tests/Integration/PartialItemTests.cs +++ b/test/Lsp.Tests/Integration/PartialItemTests.cs @@ -1,9 +1,7 @@ -using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; -using System.Reactive; using System.Reactive.Linq; -using System.Threading; using System.Threading.Tasks; using FluentAssertions; using Lsp.Tests.Integration.Fixtures; @@ -12,12 +10,10 @@ using OmniSharp.Extensions.LanguageProtocol.Testing; using OmniSharp.Extensions.LanguageServer.Client; using OmniSharp.Extensions.LanguageServer.Protocol.Client; -using OmniSharp.Extensions.LanguageServer.Protocol.Client.WorkDone; -using OmniSharp.Extensions.LanguageServer.Protocol.Document; +using OmniSharp.Extensions.LanguageServer.Protocol.Document.Proposals; using OmniSharp.Extensions.LanguageServer.Protocol.Models; -using OmniSharp.Extensions.LanguageServer.Protocol.Progress; +using OmniSharp.Extensions.LanguageServer.Protocol.Models.Proposals; using OmniSharp.Extensions.LanguageServer.Protocol.Server; -using OmniSharp.Extensions.LanguageServer.Protocol.Server.WorkDone; using OmniSharp.Extensions.LanguageServer.Server; using Xunit; using Xunit.Abstractions; @@ -35,213 +31,45 @@ public Delegates(ITestOutputHelper testOutputHelper, LanguageProtocolFixture z.Command.Name).Should().ContainInOrder("CodeLens 1", "CodeLens 2", "CodeLens 3"); + result.Should().NotBeNull(); + result.Data.Should().HaveCount(3); } [Fact] public async Task Should_Behave_Like_An_Observable() { - var items = new List(); - await Client.TextDocument.RequestCodeLens( - new CodeLensParams { - TextDocument = new TextDocumentIdentifier(@"c:\test.cs") - }, CancellationToken - ).ForEachAsync(x => items.AddRange(x)); + var items = new List(); + await Client.TextDocument.RequestSemanticTokens(new SemanticTokensParams() { TextDocument = new TextDocumentIdentifier(@"c:\test.cs") }, CancellationToken) + .ForEachAsync(x => items.Add(x)); items.Should().HaveCount(3); - items.Select(z => z.Command.Name).Should().ContainInOrder("CodeLens 1", "CodeLens 2", "CodeLens 3"); + items.Select(z => z.Data.Length).Should().ContainInOrder(1, 2, 3); } [Fact] public async Task Should_Behave_Like_An_Observable_Without_Progress_Support() { - var response = await Client.SendRequest( - new CodeLensParams { - TextDocument = new TextDocumentIdentifier(@"c:\test.cs") - }, CancellationToken - ); + var response = await Client.SendRequest(new SemanticTokensParams { TextDocument = new TextDocumentIdentifier(@"c:\test.cs") }, CancellationToken); - response.Should().HaveCount(3); - response.Select(z => z.Command.Name).Should().ContainInOrder("CodeLens 1", "CodeLens 2", "CodeLens 3"); + response.Should().NotBeNull(); + response.Data.Should().HaveCount(3); } public class DelegateServer : IConfigureLanguageServerOptions { - public void Configure(LanguageServerOptions options) => - options.OnCodeLens( - (@params, observer, capability, cancellationToken) => { - observer.OnNext( - new[] { - new CodeLens { - Command = new Command { - Name = "CodeLens 1" - } - }, - } - ); - observer.OnNext( - new[] { - new CodeLens { - Command = new Command { - Name = "CodeLens 2" - } - }, - } - ); - observer.OnNext( - new[] { - new CodeLens { - Command = new Command { - Name = "CodeLens 3" - } - }, - } - ); - observer.OnCompleted(); - }, new CodeLensRegistrationOptions() - ); - } - } - public class Handlers : LanguageProtocolFixtureTest - { - public Handlers(ITestOutputHelper testOutputHelper, LanguageProtocolFixture fixture) : base(testOutputHelper, fixture) - { - } - - [Fact] - public async Task Should_Behave_Like_An_Observable_With_WorkDone() - { - var items = new List(); - var work = new List(); - Client.TextDocument - .ObserveWorkDone( - new CodeLensParams { TextDocument = new TextDocumentIdentifier(@"c:\test.cs") }, - (client, request) => client.RequestCodeLens(request, CancellationToken), - Observer.Create(z => work.Add(z)) - ).Subscribe(x => items.AddRange(x)); - - await Task.Delay(1000); - await SettleNext(); - - var workResults = work.Select(z => z.Message); - - items.Should().HaveCount(4); - items.Select(z => z.Command.Name).Should().ContainInOrder("CodeLens 1", "CodeLens 2", "CodeLens 3", "CodeLens 4"); - - workResults.Should().ContainInOrder("Begin", "Report 1", "Report 2", "Report 3", "Report 4", "End"); - } - - - public class HandlersServer : IConfigureLanguageServerOptions - { - public void Configure(LanguageServerOptions options) => options.AddHandler(); - } - } - - private class InnerCodeLensHandler : CodeLensHandler - { - private readonly IServerWorkDoneManager _workDoneManager; - private readonly IProgressManager _progressManager; - - public InnerCodeLensHandler(IServerWorkDoneManager workDoneManager, IProgressManager progressManager) : base(new CodeLensRegistrationOptions()) - { - _workDoneManager = workDoneManager; - _progressManager = progressManager; - } - - public override async Task Handle(CodeLensParams request, CancellationToken cancellationToken) - { - var partial = _progressManager.For(request, cancellationToken); - var workDone = _workDoneManager.For( - request, new WorkDoneProgressBegin { - Cancellable = true, - Message = "Begin", - Percentage = 0, - Title = "Work is pending" - }, onComplete: () => new WorkDoneProgressEnd { - Message = "End" - } - ); - - partial.OnNext( - new[] { - new CodeLens { - Command = new Command { - Name = "CodeLens 1" - } - }, + public void Configure(LanguageServerOptions options) => options.OnSemanticTokens( + (@params, observer, arg3) => { + observer.OnNext(new SemanticTokensPartialResult() { Data = new[] { 0 }.ToImmutableArray() }); + observer.OnNext(new SemanticTokensPartialResult() { Data = new[] { 0, 1 }.ToImmutableArray() }); + observer.OnNext(new SemanticTokensPartialResult() { Data = new[] { 0, 1, 2 }.ToImmutableArray() }); + observer.OnCompleted(); + }, new SemanticTokensRegistrationOptions() { } ); - workDone.OnNext( - new WorkDoneProgressReport { - Percentage = 10, - Message = "Report 1" - } - ); - - partial.OnNext( - new[] { - new CodeLens { - Command = new Command { - Name = "CodeLens 2" - } - }, - } - ); - workDone.OnNext( - new WorkDoneProgressReport { - Percentage = 20, - Message = "Report 2" - } - ); - - partial.OnNext( - new[] { - new CodeLens { - Command = new Command { - Name = "CodeLens 3" - } - }, - } - ); - workDone.OnNext( - new WorkDoneProgressReport { - Percentage = 30, - Message = "Report 3" - } - ); - - partial.OnNext( - new[] { - new CodeLens { - Command = new Command { - Name = "CodeLens 4" - } - }, - } - ); - workDone.OnNext( - new WorkDoneProgressReport { - Percentage = 40, - Message = "Report 4" - } - ); - - workDone.OnCompleted(); - - await Task.Yield(); - - return new CodeLensContainer(); } - - public override Task Handle(CodeLens request, CancellationToken cancellationToken) => Task.FromResult(request); } } } diff --git a/test/Lsp.Tests/Integration/PartialItemsTests.cs b/test/Lsp.Tests/Integration/PartialItemsTests.cs new file mode 100644 index 000000000..cda910370 --- /dev/null +++ b/test/Lsp.Tests/Integration/PartialItemsTests.cs @@ -0,0 +1,243 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reactive; +using System.Reactive.Linq; +using System.Threading; +using System.Threading.Tasks; +using FluentAssertions; +using Lsp.Tests.Integration.Fixtures; +using OmniSharp.Extensions.LanguageServer.Protocol.Client; +using OmniSharp.Extensions.LanguageServer.Protocol.Client.WorkDone; +using OmniSharp.Extensions.LanguageServer.Protocol.Document; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; +using OmniSharp.Extensions.LanguageServer.Protocol.Progress; +using OmniSharp.Extensions.LanguageServer.Protocol.Server.WorkDone; +using OmniSharp.Extensions.LanguageServer.Server; +using Xunit; +using Xunit.Abstractions; + +namespace Lsp.Tests.Integration +{ + public static class PartialItemsTests + { + public class Delegates : LanguageProtocolFixtureTest + { + public Delegates(ITestOutputHelper testOutputHelper, LanguageProtocolFixture fixture) : base(testOutputHelper, fixture) + { + } + + [Fact] + public async Task Should_Behave_Like_A_Task() + { + var result = await Client.TextDocument.RequestCodeLens( + new CodeLensParams { + TextDocument = new TextDocumentIdentifier(@"c:\test.cs") + }, CancellationToken + ); + + result.Should().HaveCount(3); + result.Select(z => z.Command.Name).Should().ContainInOrder("CodeLens 1", "CodeLens 2", "CodeLens 3"); + } + + [Fact] + public async Task Should_Behave_Like_An_Observable() + { + var items = new List(); + await Client.TextDocument.RequestCodeLens( + new CodeLensParams { + TextDocument = new TextDocumentIdentifier(@"c:\test.cs") + }, CancellationToken + ).ForEachAsync(x => items.AddRange(x)); + + items.Should().HaveCount(3); + items.Select(z => z.Command.Name).Should().ContainInOrder("CodeLens 1", "CodeLens 2", "CodeLens 3"); + } + + [Fact] + public async Task Should_Behave_Like_An_Observable_Without_Progress_Support() + { + var response = await Client.SendRequest( + new CodeLensParams { + TextDocument = new TextDocumentIdentifier(@"c:\test.cs") + }, CancellationToken + ); + + response.Should().HaveCount(3); + response.Select(z => z.Command.Name).Should().ContainInOrder("CodeLens 1", "CodeLens 2", "CodeLens 3"); + } + + public class DelegateServer : IConfigureLanguageServerOptions + { + public void Configure(LanguageServerOptions options) => + options.OnCodeLens( + (@params, observer, capability, cancellationToken) => { + observer.OnNext( + new[] { + new CodeLens { + Command = new Command { + Name = "CodeLens 1" + } + }, + } + ); + observer.OnNext( + new[] { + new CodeLens { + Command = new Command { + Name = "CodeLens 2" + } + }, + } + ); + observer.OnNext( + new[] { + new CodeLens { + Command = new Command { + Name = "CodeLens 3" + } + }, + } + ); + observer.OnCompleted(); + }, new CodeLensRegistrationOptions() + ); + } + } + + public class Handlers : LanguageProtocolFixtureTest + { + public Handlers(ITestOutputHelper testOutputHelper, LanguageProtocolFixture fixture) : base(testOutputHelper, fixture) + { + } + + [Fact] + public async Task Should_Behave_Like_An_Observable_With_WorkDone() + { + var items = new List(); + var work = new List(); + Client.TextDocument + .ObserveWorkDone( + new CodeLensParams { TextDocument = new TextDocumentIdentifier(@"c:\test.cs") }, + (client, request) => CodeLensExtensions.RequestCodeLens((ITextDocumentLanguageClient) client, request, CancellationToken), + Observer.Create(z => work.Add(z)) + ).Subscribe(x => items.AddRange(x)); + + await Task.Delay(1000); + await SettleNext(); + + var workResults = work.Select(z => z.Message); + + items.Should().HaveCount(4); + items.Select(z => z.Command.Name).Should().ContainInOrder("CodeLens 1", "CodeLens 2", "CodeLens 3", "CodeLens 4"); + + workResults.Should().ContainInOrder("Begin", "Report 1", "Report 2", "Report 3", "Report 4", "End"); + } + + + public class HandlersServer : IConfigureLanguageServerOptions + { + public void Configure(LanguageServerOptions options) => options.AddHandler(); + } + } + + private class InnerCodeLensHandler : CodeLensHandler + { + private readonly IServerWorkDoneManager _workDoneManager; + private readonly IProgressManager _progressManager; + + public InnerCodeLensHandler(IServerWorkDoneManager workDoneManager, IProgressManager progressManager) : base(new CodeLensRegistrationOptions()) + { + _workDoneManager = workDoneManager; + _progressManager = progressManager; + } + + public override async Task Handle(CodeLensParams request, CancellationToken cancellationToken) + { + var partial = _progressManager.For(request, cancellationToken); + var workDone = _workDoneManager.For( + request, new WorkDoneProgressBegin { + Cancellable = true, + Message = "Begin", + Percentage = 0, + Title = "Work is pending" + }, onComplete: () => new WorkDoneProgressEnd { + Message = "End" + } + ); + + partial.OnNext( + new[] { + new CodeLens { + Command = new Command { + Name = "CodeLens 1" + } + }, + } + ); + workDone.OnNext( + new WorkDoneProgressReport { + Percentage = 10, + Message = "Report 1" + } + ); + + partial.OnNext( + new[] { + new CodeLens { + Command = new Command { + Name = "CodeLens 2" + } + }, + } + ); + workDone.OnNext( + new WorkDoneProgressReport { + Percentage = 20, + Message = "Report 2" + } + ); + + partial.OnNext( + new[] { + new CodeLens { + Command = new Command { + Name = "CodeLens 3" + } + }, + } + ); + workDone.OnNext( + new WorkDoneProgressReport { + Percentage = 30, + Message = "Report 3" + } + ); + + partial.OnNext( + new[] { + new CodeLens { + Command = new Command { + Name = "CodeLens 4" + } + }, + } + ); + workDone.OnNext( + new WorkDoneProgressReport { + Percentage = 40, + Message = "Report 4" + } + ); + + workDone.OnCompleted(); + + await Task.Yield(); + + return new CodeLensContainer(); + } + + public override Task Handle(CodeLens request, CancellationToken cancellationToken) => Task.FromResult(request); + } + } +} From b6a43277a4b93d16c97a732957c156143da6c75a Mon Sep 17 00:00:00 2001 From: David Driscoll Date: Tue, 1 Sep 2020 00:10:43 -0400 Subject: [PATCH 3/3] fixed partial abstract handler --- src/Protocol/AbstractHandlers.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Protocol/AbstractHandlers.cs b/src/Protocol/AbstractHandlers.cs index b1582e589..b6e1223c3 100644 --- a/src/Protocol/AbstractHandlers.cs +++ b/src/Protocol/AbstractHandlers.cs @@ -33,10 +33,10 @@ public abstract class Request.SetCapability(TCapability capability) => Capability = capability; } - public abstract class PartialResult : - IJsonRpcRequestHandler, + public abstract class PartialResult : + IJsonRpcRequestHandler, IRegistration, ICapability - where TItem : IPartialItemRequest + where TParams : IPartialItemRequest where TResponse : class, new() where TRegistrationOptions : class, new() where TCapability : ICapability @@ -57,8 +57,8 @@ Func factory _factory = factory; } - async Task IRequestHandler.Handle( - TItem request, + async Task IRequestHandler.Handle( + TParams request, CancellationToken cancellationToken ) { @@ -78,7 +78,7 @@ CancellationToken cancellationToken } protected abstract void Handle( - TItem request, IObserver results, + TParams request, IObserver results, CancellationToken cancellationToken );