From 232da153f16844590ab5572f93346669b4ae8c8c Mon Sep 17 00:00:00 2001 From: David Driscoll Date: Sun, 4 Oct 2020 01:42:09 -0400 Subject: [PATCH] Added the latest changes from 1.16 spec. Moniker, CodeAction Resolve, DefaultBehavior and more --- LSP.sln.DotSettings | 2 + language-server-protocol.sha.txt | 2 +- .../LanguageClientRegistrationManager.cs | 109 +-- .../LanguageClientWorkspaceFoldersManager.cs | 20 +- src/Dap.Client/ProgressObservable.cs | 24 +- src/JsonRpc/AggregateResponse.cs | 13 +- src/JsonRpc/OutputHandler.cs | 2 +- src/JsonRpc/RequestRouterBase.cs | 3 +- src/JsonRpc/ResponseRouter.cs | 32 +- src/Protocol/AbstractHandlers.cs | 16 +- .../Capabilities/CodeActionCapability.cs | 26 + ...deActionCapabilityResolveSupportOptions.cs | 18 + .../CompletionItemCapabilityOptions.cs | 10 + ...tionItemCapabilityResolveSupportOptions.cs | 19 + .../Client/Capabilities/MonikerCapability.cs | 8 + .../Client/Capabilities/RenameCapability.cs | 8 + .../TextDocumentClientCapabilities.cs | 7 + src/Protocol/Document/ICodeActionHandler.cs | 567 ++++++++++++++- src/Protocol/Document/ICodeLensHandler.cs | 24 +- src/Protocol/Document/ICompletionHandler.cs | 20 +- src/Protocol/Document/IDocumentLinkHandler.cs | 20 +- .../Document/Proposals/IMonikerHandler.cs | 56 ++ src/Protocol/Models/CodeAction.cs | 147 +++- src/Protocol/Models/CodeActionDisabled.cs | 29 + src/Protocol/Models/CodeActionKind.cs | 4 - .../Models/CodeActionRegistrationOptions.cs | 26 +- src/Protocol/Models/CodeLens.cs | 8 +- src/Protocol/Models/CodeLensContainer.cs | 2 +- src/Protocol/Models/CommandOrCodeAction.cs | 12 +- .../Models/CommandOrCodeActionContainer.cs | 26 + .../CommandOrCodeActionContainerExtensions.cs | 18 + src/Protocol/Models/CompletionItem.cs | 8 +- src/Protocol/Models/CompletionList.cs | 2 +- src/Protocol/Models/DocumentLink.cs | 8 +- src/Protocol/Models/DocumentLinkContainer.cs | 2 +- .../Models/DocumentLinkRegistrationOptions.cs | 1 + src/Protocol/Models/IEnumLikeString.cs | 6 + src/Protocol/Models/Proposals/Moniker.cs | 34 + src/Protocol/Models/Proposals/MonikerKind.cs | 52 ++ .../Models/Proposals/MonikerParams.cs | 26 + .../Proposals/MonikerRegistrationOptions.cs | 32 + .../Models/Proposals/UniquenessLevel.cs | 61 ++ .../Models/RangeOrPlaceholderRange.cs | 23 +- src/Protocol/Models/Registration.cs | 2 +- src/Protocol/Models/RenameDefaultBehavior.cs | 7 + .../PartialItemsRequestProgressObservable.cs | 26 +- src/Protocol/Progress/ProgressObservable.cs | 24 +- .../Progress/RequestProgressObservable.cs | 26 +- .../RangeOrPlaceholderRangeConverter.cs | 12 +- src/Protocol/Serialization/Serializer.cs | 9 +- .../Server/Capabilities/ServerCapabilities.cs | 6 + src/Protocol/TextDocumentNames.cs | 2 + .../LanguageServerWorkspaceFolderManager.cs | 15 +- src/Server/Matchers/TextDocumentMatcher.cs | 7 +- .../ClientCapabilitiesTests_$SimpleTest.json | 3 + ...ntClientCapabilitiesTests_$SimpleTest.json | 3 + .../Server/CodeActionOptionsTests.cs | 27 + .../CodeActionOptionsTests_$SimpleTest.json | 8 + test/Lsp.Tests/FoundationTests.cs | 3 +- .../Integration/DynamicRegistrationTests.cs | 113 ++- test/Lsp.Tests/Integration/MonikerTests.cs | 65 ++ .../Lsp.Tests/Integration/PartialItemTests.cs | 15 +- .../Integration/PartialItemsTests.cs | 20 +- test/Lsp.Tests/Integration/RenameTests.cs | 29 + .../Integration/TypedCodeActionTests.cs | 671 ++++++++++++++++++ ...nRegistrationOptionsTests_$SimpleTest.json | 1 + .../InitializeParamsTests_$SimpleTest.json | 3 + .../InitializeResultTests_$BooleanOrTest.json | 1 + 68 files changed, 2401 insertions(+), 230 deletions(-) create mode 100644 src/Protocol/Client/Capabilities/CodeActionCapabilityResolveSupportOptions.cs create mode 100644 src/Protocol/Client/Capabilities/CompletionItemCapabilityResolveSupportOptions.cs create mode 100644 src/Protocol/Client/Capabilities/MonikerCapability.cs create mode 100644 src/Protocol/Document/Proposals/IMonikerHandler.cs create mode 100644 src/Protocol/Models/CodeActionDisabled.cs create mode 100644 src/Protocol/Models/CommandOrCodeActionContainerExtensions.cs create mode 100644 src/Protocol/Models/IEnumLikeString.cs create mode 100644 src/Protocol/Models/Proposals/Moniker.cs create mode 100644 src/Protocol/Models/Proposals/MonikerKind.cs create mode 100644 src/Protocol/Models/Proposals/MonikerParams.cs create mode 100644 src/Protocol/Models/Proposals/MonikerRegistrationOptions.cs create mode 100644 src/Protocol/Models/Proposals/UniquenessLevel.cs create mode 100644 src/Protocol/Models/RenameDefaultBehavior.cs create mode 100644 test/Lsp.Tests/Capabilities/Server/CodeActionOptionsTests.cs create mode 100644 test/Lsp.Tests/Capabilities/Server/CodeActionOptionsTests_$SimpleTest.json create mode 100644 test/Lsp.Tests/Integration/MonikerTests.cs create mode 100644 test/Lsp.Tests/Integration/TypedCodeActionTests.cs diff --git a/LSP.sln.DotSettings b/LSP.sln.DotSettings index 5f6d29ea7..885217588 100644 --- a/LSP.sln.DotSettings +++ b/LSP.sln.DotSettings @@ -14,6 +14,7 @@ WARNING WARNING SUGGESTION + ShowAndRun True <?xml version="1.0" encoding="utf-16"?><Profile name="Full Cleanup"><CSCodeStyleAttributes ArrangeTypeAccessModifier="True" ArrangeTypeMemberAccessModifier="True" SortModifiers="True" RemoveRedundantParentheses="True" AddMissingParentheses="True" ArrangeBraces="False" ArrangeAttributes="True" ArrangeArgumentsStyle="True" ArrangeCodeBodyStyle="True" ArrangeVarStyle="True" ArrangeTrailingCommas="False" /><RemoveCodeRedundancies>True</RemoveCodeRedundancies><CSUseAutoProperty>True</CSUseAutoProperty><CSMakeFieldReadonly>True</CSMakeFieldReadonly><CSMakeAutoPropertyGetOnly>True</CSMakeAutoPropertyGetOnly><CSArrangeQualifiers>True</CSArrangeQualifiers><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><CSReformatCode>True</CSReformatCode><CSharpFormatDocComments>True</CSharpFormatDocComments><XAMLCollapseEmptyTags>False</XAMLCollapseEmptyTags><IDEA_SETTINGS>&lt;profile version="1.0"&gt; &lt;option name="myName" value="Full Cleanup" /&gt; @@ -238,6 +239,7 @@ </Entry> </TypePattern> </Patterns> + True True True True diff --git a/language-server-protocol.sha.txt b/language-server-protocol.sha.txt index bb2a55802..c9be7de30 100644 --- a/language-server-protocol.sha.txt +++ b/language-server-protocol.sha.txt @@ -1,4 +1,4 @@ -- This is the last commit we caught up with https://github.com/Microsoft/language-server-protocol/commits/gh-pages -lastSha: 56cf06cf95a2eea4e528558fb228d1698d952571 +lastSha: 1cf527d8410a493789de4992f78ee9874a4fd215 https://github.com/Microsoft/language-server-protocol/compare/..gh-pages diff --git a/src/Client/LanguageClientRegistrationManager.cs b/src/Client/LanguageClientRegistrationManager.cs index f42eb9d60..1ef32e538 100644 --- a/src/Client/LanguageClientRegistrationManager.cs +++ b/src/Client/LanguageClientRegistrationManager.cs @@ -25,18 +25,18 @@ internal class LanguageClientRegistrationManager : IRegisterCapabilityHandler, I private readonly ILspHandlerTypeDescriptorProvider _handlerTypeDescriptorProvider; private readonly ILogger _logger; private readonly ConcurrentDictionary _registrations; - private readonly ReplaySubject> _registrationSubject; + private ReplaySubject> _registrationSubject = new ReplaySubject>(1); public LanguageClientRegistrationManager( ISerializer serializer, ILspHandlerTypeDescriptorProvider handlerTypeDescriptorProvider, - ILogger logger) + ILogger logger + ) { _serializer = serializer; _handlerTypeDescriptorProvider = handlerTypeDescriptorProvider; _logger = logger; _registrations = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); - _registrationSubject = new ReplaySubject>(1); } Task IRequestHandler.Handle(RegistrationParams request, CancellationToken cancellationToken) @@ -46,7 +46,11 @@ Task IRequestHandler.Handle(RegistrationParams r Register(request.Registrations.ToArray()); } - _registrationSubject.OnNext(_registrations.Values); + if (!_registrationSubject.IsDisposed) + { + _registrationSubject.OnNext(_registrations.Values); + } + return Unit.Task; } @@ -60,15 +64,17 @@ Task IRequestHandler.Handle(UnregistrationPara } } - _registrationSubject.OnNext(_registrations.Values); + if (!_registrationSubject.IsDisposed) + { + _registrationSubject.OnNext(_registrations.Values); + } + return Unit.Task; } public void RegisterCapabilities(ServerCapabilities serverCapabilities) { - foreach (var registrationOptions in LspHandlerDescriptorHelpers.GetStaticRegistrationOptions( - serverCapabilities - )) + foreach (var registrationOptions in LspHandlerDescriptorHelpers.GetStaticRegistrationOptions(serverCapabilities)) { var method = _handlerTypeDescriptorProvider.GetMethodForRegistrationOptions(registrationOptions); if (method == null) @@ -88,34 +94,30 @@ public void RegisterCapabilities(ServerCapabilities serverCapabilities) } } - if (serverCapabilities.Workspace == null) + if (serverCapabilities.Workspace != null) { - _registrationSubject.OnNext(_registrations.Values); - return; - } - - foreach (var registrationOptions in LspHandlerDescriptorHelpers.GetStaticRegistrationOptions( - serverCapabilities - .Workspace - )) - { - var method = _handlerTypeDescriptorProvider.GetMethodForRegistrationOptions(registrationOptions); - if (method == null) + foreach (var registrationOptions in LspHandlerDescriptorHelpers.GetStaticRegistrationOptions(serverCapabilities.Workspace)) { - // TODO: Log this - continue; - } - - if (registrationOptions.Id != null) - { - var reg = new Registration { - Id = registrationOptions.Id, - Method = method, - RegisterOptions = registrationOptions - }; - _registrations.AddOrUpdate(registrationOptions.Id, x => reg, (a, b) => reg); + var method = _handlerTypeDescriptorProvider.GetMethodForRegistrationOptions(registrationOptions); + if (method == null) + { + // TODO: Log this + continue; + } + + if (registrationOptions.Id != null) + { + var reg = new Registration { + Id = registrationOptions.Id, + Method = method, + RegisterOptions = registrationOptions + }; + _registrations.AddOrUpdate(registrationOptions.Id, x => reg, (a, b) => reg); + } } } + + _registrationSubject.OnNext(_registrations.Values); } private void Register(params Registration[] registrations) @@ -145,7 +147,18 @@ private void Register(Registration registration) _registrations.AddOrUpdate(deserializedRegistration.Id, x => deserializedRegistration, (a, b) => deserializedRegistration); } - public IObservable> Registrations => _registrationSubject.AsObservable(); + public IObservable> Registrations + { + get { + if (_registrationSubject.IsDisposed) + { + return Observable.Empty>(); + } + + return _registrationSubject.AsObservable(); + } + } + public IEnumerable CurrentRegistrations => _registrations.Values; public IEnumerable GetRegistrationsForMethod(string method) => _registrations.Select(z => z.Value).Where(x => x.Method == method); @@ -154,19 +167,25 @@ public IEnumerable GetRegistrationsMatchingSelector(DocumentSelect _registrations .Select(z => z.Value) .Where( - x => x.RegisterOptions is ITextDocumentRegistrationOptions ro && ro.DocumentSelector - .Join( - documentSelector, - z => z.HasLanguage ? z.Language : - z.HasScheme ? z.Scheme : - z.HasPattern ? z.Pattern : string.Empty, - z => z.HasLanguage ? z.Language : - z.HasScheme ? z.Scheme : - z.HasPattern ? z.Pattern : string.Empty, (a, b) => a - ) - .Any(y => y.HasLanguage || y.HasPattern || y.HasScheme) + x => x.RegisterOptions is ITextDocumentRegistrationOptions ro && + ro.DocumentSelector != null && + ro.DocumentSelector + .Join( + documentSelector, + z => z.HasLanguage ? z.Language : + z.HasScheme ? z.Scheme : + z.HasPattern ? z.Pattern : string.Empty, + z => z.HasLanguage ? z.Language : + z.HasScheme ? z.Scheme : + z.HasPattern ? z.Pattern : string.Empty, (a, b) => a + ) + .Any(y => y.HasLanguage || y.HasPattern || y.HasScheme) ); - public void Dispose() => _registrationSubject.Dispose(); + public void Dispose() + { + if (_registrationSubject.IsDisposed) return; + _registrationSubject.Dispose(); + } } } diff --git a/src/Client/LanguageClientWorkspaceFoldersManager.cs b/src/Client/LanguageClientWorkspaceFoldersManager.cs index 88730e8da..050d6514d 100644 --- a/src/Client/LanguageClientWorkspaceFoldersManager.cs +++ b/src/Client/LanguageClientWorkspaceFoldersManager.cs @@ -34,7 +34,7 @@ public LanguageClientWorkspaceFoldersManager(IWorkspaceLanguageClient client, IE Task?> IRequestHandler?>. Handle(WorkspaceFolderParams request, CancellationToken cancellationToken) => - Task.FromResult< Container?>(new Container(_workspaceFolders.Values)); + Task.FromResult?>(new Container(_workspaceFolders.Values)); public void Add(DocumentUri uri, string name) => Add(new WorkspaceFolder { Name = name, Uri = uri }); @@ -60,7 +60,10 @@ public void Add(IEnumerable workspaceFolders) } } ); - _workspaceFoldersSubject.OnNext(_workspaceFolders.Values); + if (!_workspaceFoldersSubject.IsDisposed) + { + _workspaceFoldersSubject.OnNext(_workspaceFolders.Values); + } } public void Remove(DocumentUri name) => Remove(_workspaceFolders.Values.Where(z => z.Uri == name)); @@ -89,13 +92,20 @@ public void Remove(IEnumerable items) } } ); - _workspaceFoldersSubject.OnNext(_workspaceFolders.Values); + if (!_workspaceFoldersSubject.IsDisposed) + { + _workspaceFoldersSubject.OnNext(_workspaceFolders.Values); + } } - public IObservable> WorkspaceFolders => _workspaceFoldersSubject.AsObservable(); + public IObservable> WorkspaceFolders => _workspaceFoldersSubject.IsDisposed ? Observable.Empty>() : _workspaceFoldersSubject.AsObservable(); public IEnumerable CurrentWorkspaceFolders => _workspaceFolders.Values; - public void Dispose() => _workspaceFoldersSubject.Dispose(); + public void Dispose() + { + if (_workspaceFoldersSubject.IsDisposed) return; + _workspaceFoldersSubject.Dispose(); + } } } diff --git a/src/Dap.Client/ProgressObservable.cs b/src/Dap.Client/ProgressObservable.cs index 46c59c3c3..c961689b3 100644 --- a/src/Dap.Client/ProgressObservable.cs +++ b/src/Dap.Client/ProgressObservable.cs @@ -26,13 +26,29 @@ public ProgressObservable(ProgressToken token) public ProgressToken ProgressToken { get; } - void IObserver.OnCompleted() => _dataSubject.OnCompleted(); + void IObserver.OnCompleted() + { + if (_dataSubject.IsDisposed) return; + _dataSubject.OnCompleted(); + } - void IObserver.OnError(Exception error) => _dataSubject.OnError(error); + void IObserver.OnError(Exception error) + { + if (_dataSubject.IsDisposed) return; + _dataSubject.OnError(error); + } - public void OnNext(ProgressEvent value) => _dataSubject.OnNext(value); + public void OnNext(ProgressEvent value) + { + if (_dataSubject.IsDisposed) return; + _dataSubject.OnNext(value); + } - public void Dispose() => _disposable.Dispose(); + public void Dispose() + { + if (_disposable.IsDisposed) return; + _disposable.Dispose(); + } public IDisposable Subscribe(IObserver observer) => _disposable.IsDisposed ? Disposable.Empty : _dataSubject.Subscribe(observer); } diff --git a/src/JsonRpc/AggregateResponse.cs b/src/JsonRpc/AggregateResponse.cs index e68f69abf..a96c5af74 100644 --- a/src/JsonRpc/AggregateResponse.cs +++ b/src/JsonRpc/AggregateResponse.cs @@ -4,12 +4,23 @@ namespace OmniSharp.Extensions.JsonRpc { - public class AggregateResponse where T : IEnumerable + public class AggregateResponse: IEnumerable where T : IEnumerable { public IEnumerable Items { get; } public AggregateResponse(IEnumerable items) => Items = items.ToArray(); public AggregateResponse(IEnumerable items) => Items = items.OfType().ToArray(); + + public IEnumerator GetEnumerator() + { + foreach (var item in Items) + { + foreach (var v in item) + { + yield return v; + } + } + } } } diff --git a/src/JsonRpc/OutputHandler.cs b/src/JsonRpc/OutputHandler.cs index 0723bf90d..74441d757 100644 --- a/src/JsonRpc/OutputHandler.cs +++ b/src/JsonRpc/OutputHandler.cs @@ -64,7 +64,7 @@ ILogger logger public void Send(object? value) { - if (_queue.IsDisposed || value == null) return; + if (_queue.IsDisposed || _disposable.IsDisposed || value == null) return; _queue.OnNext(value); } diff --git a/src/JsonRpc/RequestRouterBase.cs b/src/JsonRpc/RequestRouterBase.cs index ae7a0b207..1bc307a5b 100644 --- a/src/JsonRpc/RequestRouterBase.cs +++ b/src/JsonRpc/RequestRouterBase.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -109,7 +110,7 @@ public virtual async Task RouteRequest(IRequestDescriptor).IsAssignableFrom(descriptors.Default!.Response) && !typeof(JToken).IsAssignableFrom(descriptors.Default!.Response)) + if (typeof(IEnumerable).IsAssignableFrom(descriptors.Default!.Response) && typeof(string) != descriptors.Default!.Response && !typeof(JToken).IsAssignableFrom(descriptors.Default!.Response)) { var responses = await Task.WhenAll(descriptors.Select(descriptor => InnerRoute(_serviceScopeFactory, request, descriptor, @params, token, _logger))).ConfigureAwait(false); var errorResponse = responses.FirstOrDefault(x => x.IsError); diff --git a/src/JsonRpc/ResponseRouter.cs b/src/JsonRpc/ResponseRouter.cs index 18ca374b2..ee7f9326d 100644 --- a/src/JsonRpc/ResponseRouter.cs +++ b/src/JsonRpc/ResponseRouter.cs @@ -81,22 +81,22 @@ public async Task Returning(CancellationToken cancellation cancellationToken.ThrowIfCancellationRequested(); - _router.OutputHandler.Value.Send( - new OutgoingRequest { - Method = _method, - Params = _params, - Id = nextId - } - ); - cancellationToken.Register( - () => { - if (tcs.Task.IsCompleted) return; - _router.CancelRequest(new CancelParams { Id = nextId }); - } - ); - try { + _router.OutputHandler.Value.Send( + new OutgoingRequest { + Method = _method, + Params = _params, + Id = nextId + } + ); + cancellationToken.Register( + () => { + if (tcs.Task.IsCompleted) return; + _router.CancelRequest(new CancelParams { Id = nextId }); + } + ); + var result = await tcs.Task.ConfigureAwait(false); if (typeof(TResponse) == typeof(Unit)) { @@ -105,6 +105,10 @@ public async Task Returning(CancellationToken cancellation return result.ToObject(_router.Serializer.JsonSerializer); } + catch (ObjectDisposedException) + { + throw; + } finally { _router.Requests.TryRemove(nextId, out _); diff --git a/src/Protocol/AbstractHandlers.cs b/src/Protocol/AbstractHandlers.cs index 967e854dc..db525bd43 100644 --- a/src/Protocol/AbstractHandlers.cs +++ b/src/Protocol/AbstractHandlers.cs @@ -99,21 +99,14 @@ public abstract class PartialResults, TResponse> _factory; protected TCapability Capability { get; private set; } = default!; - protected PartialResults( - TRegistrationOptions registrationOptions, - IProgressManager progressManager, - Func, TResponse> factory - ) + protected PartialResults(TRegistrationOptions registrationOptions, IProgressManager progressManager, Func, TResponse> factory) { _registrationOptions = registrationOptions; _progressManager = progressManager; _factory = factory; } - async Task IRequestHandler.Handle( - TParams request, - CancellationToken cancellationToken - ) + async Task IRequestHandler.Handle(TParams request, CancellationToken cancellationToken) { var observer = _progressManager.For(request, cancellationToken); if (observer != ProgressObserver.Noop) @@ -135,10 +128,7 @@ CancellationToken cancellationToken return _factory(await task.ConfigureAwait(false)); } - protected abstract void Handle( - TParams request, IObserver> results, - CancellationToken cancellationToken - ); + protected abstract void Handle(TParams request, IObserver> results, CancellationToken cancellationToken); TRegistrationOptions IRegistration.GetRegistrationOptions() => _registrationOptions; void ICapability.SetCapability(TCapability capability) => Capability = capability; diff --git a/src/Protocol/Client/Capabilities/CodeActionCapability.cs b/src/Protocol/Client/Capabilities/CodeActionCapability.cs index 047dee326..d51aa6da7 100644 --- a/src/Protocol/Client/Capabilities/CodeActionCapability.cs +++ b/src/Protocol/Client/Capabilities/CodeActionCapability.cs @@ -21,5 +21,31 @@ public class CodeActionCapability : DynamicCapability, ConnectedCapability [Optional] public bool IsPreferredSupport { get; set; } + + /// + /// Whether code action supports the `disabled` property. + /// + /// @since 3.16.0 - proposed state + /// + [Optional] + public bool DisabledSupport { get; set; } + + /// + /// Whether code action supports the `data` property which is + /// preserved between a `textDocument/codeAction` and a `codeAction/resolve` request. + /// + /// @since 3.16.0 - proposed state + /// + [Optional] + public bool DataSupport { get; set; } + + /// + /// Whether the client supports resolving additional code action + /// properties via a separate `codeAction/resolve` request. + /// + /// @since 3.16.0 - proposed state + /// + [Optional] + public CodeActionCapabilityResolveSupportOptions? ResolveSupport { get; set; } } } diff --git a/src/Protocol/Client/Capabilities/CodeActionCapabilityResolveSupportOptions.cs b/src/Protocol/Client/Capabilities/CodeActionCapabilityResolveSupportOptions.cs new file mode 100644 index 000000000..4f097c5ff --- /dev/null +++ b/src/Protocol/Client/Capabilities/CodeActionCapabilityResolveSupportOptions.cs @@ -0,0 +1,18 @@ +using OmniSharp.Extensions.LanguageServer.Protocol.Models; + +namespace OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities +{ + /// + /// Whether the client supports resolving additional code action + /// properties via a separate `codeAction/resolve` request. + /// + /// @since 3.16.0 - proposed state + /// + public class CodeActionCapabilityResolveSupportOptions + { + /// + /// The properties that a client can resolve lazily. + /// + public Container Properties { get; set; } = new Container(); + } +} diff --git a/src/Protocol/Client/Capabilities/CompletionItemCapabilityOptions.cs b/src/Protocol/Client/Capabilities/CompletionItemCapabilityOptions.cs index 59e400f2e..660e18e6d 100644 --- a/src/Protocol/Client/Capabilities/CompletionItemCapabilityOptions.cs +++ b/src/Protocol/Client/Capabilities/CompletionItemCapabilityOptions.cs @@ -69,5 +69,15 @@ public class CompletionItemCapabilityOptions /// [Optional] public bool ResolveAdditionalTextEditsSupport { get; set; } + + /// + /// Indicates which properties a client can resolve lazily on a completion + /// item. Before version 3.16.0 only the predefined properties `documentation` + /// and `details` could be resolved lazily. + /// + /// @since 3.16.0 - proposed state + /// + [Optional] + public CompletionItemCapabilityResolveSupportOptions? ResolveSupport { get; set; } } } diff --git a/src/Protocol/Client/Capabilities/CompletionItemCapabilityResolveSupportOptions.cs b/src/Protocol/Client/Capabilities/CompletionItemCapabilityResolveSupportOptions.cs new file mode 100644 index 000000000..1b38b78df --- /dev/null +++ b/src/Protocol/Client/Capabilities/CompletionItemCapabilityResolveSupportOptions.cs @@ -0,0 +1,19 @@ +using OmniSharp.Extensions.LanguageServer.Protocol.Models; + +namespace OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities +{ + /// + /// Indicates which properties a client can resolve lazily on a completion + /// item. Before version 3.16.0 only the predefined properties `documentation` + /// and `details` could be resolved lazily. + /// + /// @since 3.16.0 - proposed state + /// + public class CompletionItemCapabilityResolveSupportOptions + { + /// + /// The properties that a client can resolve lazily. + /// + public Container Properties { get; set; } = new Container(); + } +} diff --git a/src/Protocol/Client/Capabilities/MonikerCapability.cs b/src/Protocol/Client/Capabilities/MonikerCapability.cs new file mode 100644 index 000000000..bff062bbb --- /dev/null +++ b/src/Protocol/Client/Capabilities/MonikerCapability.cs @@ -0,0 +1,8 @@ +using System; + +namespace OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities +{ + [Obsolete(Constants.Proposal)] + [CapabilityKey(nameof(ClientCapabilities.TextDocument), nameof(TextDocumentClientCapabilities.Moniker))] + public class MonikerCapability : DynamicCapability { } +} diff --git a/src/Protocol/Client/Capabilities/RenameCapability.cs b/src/Protocol/Client/Capabilities/RenameCapability.cs index e66c4eb0d..994fba011 100644 --- a/src/Protocol/Client/Capabilities/RenameCapability.cs +++ b/src/Protocol/Client/Capabilities/RenameCapability.cs @@ -12,5 +12,13 @@ public class RenameCapability : DynamicCapability, ConnectedCapability [Optional] public bool PrepareSupport { get; set; } + + /// + /// Client supports the default behavior result (`{ defaultBehavior: boolean }`). + /// + /// @since version 3.16.0 + /// + [Optional] + public bool PrepareSupportDefaultBehavior { get; set; } } } diff --git a/src/Protocol/Client/Capabilities/TextDocumentClientCapabilities.cs b/src/Protocol/Client/Capabilities/TextDocumentClientCapabilities.cs index ca4a528d8..a4ad2a83b 100644 --- a/src/Protocol/Client/Capabilities/TextDocumentClientCapabilities.cs +++ b/src/Protocol/Client/Capabilities/TextDocumentClientCapabilities.cs @@ -136,5 +136,12 @@ public class TextDocumentClientCapabilities : CapabilitiesBase /// [Obsolete(Constants.Proposal)] public Supports SemanticTokens { get; set; } + + /// + /// Capabilities specific to the `textDocument/moniker` + /// + /// @since 3.16.0 + /// + public Supports Moniker { get; set; } } } diff --git a/src/Protocol/Document/ICodeActionHandler.cs b/src/Protocol/Document/ICodeActionHandler.cs index f0e923a37..9226c414b 100644 --- a/src/Protocol/Document/ICodeActionHandler.cs +++ b/src/Protocol/Document/ICodeActionHandler.cs @@ -1,22 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reactive; +using System.Reactive.Linq; +using System.Reactive.Subjects; +using System.Reactive.Threading.Tasks; using System.Threading; using System.Threading.Tasks; +using MediatR; +using Microsoft.Extensions.DependencyInjection; using 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; +using OmniSharp.Extensions.LanguageServer.Protocol.Progress; +using OmniSharp.Extensions.LanguageServer.Protocol.Server; namespace OmniSharp.Extensions.LanguageServer.Protocol.Document { [Parallel] [Method(TextDocumentNames.CodeAction, Direction.ClientToServer)] - [GenerateHandlerMethods] [GenerateRequestMethods(typeof(ITextDocumentLanguageClient), typeof(ILanguageClient))] public interface ICodeActionHandler : IJsonRpcRequestHandler, IRegistration, ICapability { } + [Parallel] + [Method(TextDocumentNames.CodeActionResolve, Direction.ClientToServer)] + [GenerateRequestMethods(typeof(ITextDocumentLanguageClient), typeof(ILanguageClient))] + public interface ICodeActionResolveHandler : ICanBeResolvedHandler, ICanBeIdentifiedHandler + { + } + + [Obsolete("This handler is obsolete and is related by CodeActionHandlerBase")] public abstract class CodeActionHandler : ICodeActionHandler { private readonly CodeActionRegistrationOptions _options; @@ -26,4 +44,551 @@ public abstract class CodeActionHandler : ICodeActionHandler public virtual void SetCapability(CodeActionCapability capability) => Capability = capability; protected CodeActionCapability Capability { get; private set; } = null!; } + + public abstract class CodeActionHandlerBase : ICodeActionHandler, ICodeActionResolveHandler + { + private readonly CodeActionRegistrationOptions _options; + + public CodeActionHandlerBase(CodeActionRegistrationOptions registrationOptions) + { + _options = registrationOptions; + _options.ResolveProvider = true; + } + + public CodeActionRegistrationOptions GetRegistrationOptions() => _options; + public abstract Task Handle(CodeActionParams request, CancellationToken cancellationToken); + public abstract Task Handle(CodeAction request, CancellationToken cancellationToken); + Guid ICanBeIdentifiedHandler.Id { get; } = Guid.NewGuid(); + public virtual void SetCapability(CodeActionCapability capability) => Capability = capability; + protected CodeActionCapability Capability { get; private set; } = null!; + } + + public abstract class PartialCodeActionHandlerBase : + AbstractHandlers.PartialResults, ICodeActionHandler, ICodeActionResolveHandler + { + protected PartialCodeActionHandlerBase(CodeActionRegistrationOptions registrationOptions, IProgressManager progressManager) : base( + registrationOptions, progressManager, + lenses => new CommandOrCodeActionContainer(lenses) + ) + { + } + + public abstract Task Handle(CodeAction request, CancellationToken cancellationToken); + public virtual Guid Id { get; } = Guid.NewGuid(); + } + + public abstract class CodeActionHandlerBase : CodeActionHandlerBase where T : HandlerIdentity?, new() + { + public CodeActionHandlerBase(CodeActionRegistrationOptions registrationOptions) : base(registrationOptions) + { + } + + + public sealed override async Task Handle(CodeActionParams request, CancellationToken cancellationToken) + { + var response = await HandleParams(request, cancellationToken).ConfigureAwait(false); + return response; + } + + public sealed override async Task Handle(CodeAction request, CancellationToken cancellationToken) + { + var response = await HandleResolve(request, cancellationToken).ConfigureAwait(false); + return response; + } + + protected abstract Task HandleParams(CodeActionParams request, CancellationToken cancellationToken); + protected abstract Task> HandleResolve(CodeAction request, CancellationToken cancellationToken); + } + + public abstract class PartialCodeActionHandlerBase : PartialCodeActionHandlerBase where T : HandlerIdentity?, new() + { + protected PartialCodeActionHandlerBase(CodeActionRegistrationOptions registrationOptions, IProgressManager progressManager) : base( + registrationOptions, + progressManager + ) + { + } + + protected sealed override void Handle(CodeActionParams request, IObserver> results, CancellationToken cancellationToken) => Handle( + request, + Observer.Create>>( + x => results.OnNext(x.Select(z => new CommandOrCodeAction(z))), + results.OnError, + results.OnCompleted + ), cancellationToken + ); + + public sealed override async Task Handle(CodeAction request, CancellationToken cancellationToken) + { + var response = await HandleResolve(request, cancellationToken).ConfigureAwait(false); + return response; + } + + protected abstract void Handle(CodeActionParams request, IObserver>> results, CancellationToken cancellationToken); + protected abstract Task> HandleResolve(CodeAction request, CancellationToken cancellationToken); + } + + public static partial class CodeActionExtensions + { + public static ILanguageServerRegistry OnCodeAction( + this ILanguageServerRegistry registry, + Func> handler, + CodeActionRegistrationOptions? registrationOptions + ) => + OnCodeAction(registry, handler, null, registrationOptions); + + public static ILanguageServerRegistry OnCodeAction( + this ILanguageServerRegistry registry, + Func> handler, + Func>? resolveHandler, + CodeActionRegistrationOptions? registrationOptions + ) + { + registrationOptions ??= new CodeActionRegistrationOptions(); + registrationOptions.ResolveProvider = true; + resolveHandler ??= (link, cap, token) => Task.FromResult(link); + var id = Guid.NewGuid(); + + return registry.AddHandler( + TextDocumentNames.CodeAction, + new LanguageProtocolDelegatingHandlers.Request( + id, + handler, + registrationOptions + ) + ) + .AddHandler( + TextDocumentNames.CodeActionResolve, + new LanguageProtocolDelegatingHandlers.CanBeResolved( + id, + resolveHandler, + registrationOptions + ) + ) + ; + } + + public static ILanguageServerRegistry OnCodeAction( + this ILanguageServerRegistry registry, + Func>> handler, + Func, CodeActionCapability, CancellationToken, Task>>? resolveHandler, + CodeActionRegistrationOptions? registrationOptions + ) where T : HandlerIdentity?, new() + { + registrationOptions ??= new CodeActionRegistrationOptions(); + registrationOptions.ResolveProvider = true; + resolveHandler ??= (link, c, token) => Task.FromResult(link); + + return registry.AddHandler( + _ => new DelegatingCodeActionHandler( + registrationOptions, + async (@params, capability, token) => await handler(@params, capability, token), + resolveHandler + ) + ); + } + + public static ILanguageServerRegistry OnCodeAction( + this ILanguageServerRegistry registry, + Func> handler, + CodeActionRegistrationOptions? registrationOptions + ) => + OnCodeAction(registry, handler, null, registrationOptions); + + public static ILanguageServerRegistry OnCodeAction( + this ILanguageServerRegistry registry, + Func> handler, + Func>? resolveHandler, + CodeActionRegistrationOptions? registrationOptions + ) + { + registrationOptions ??= new CodeActionRegistrationOptions(); + registrationOptions.ResolveProvider = true; + resolveHandler ??= (link, token) => Task.FromResult(link); + var id = Guid.NewGuid(); + + return registry.AddHandler( + TextDocumentNames.CodeAction, + new LanguageProtocolDelegatingHandlers.RequestRegistration( + id, + handler, + registrationOptions + ) + ) + .AddHandler( + TextDocumentNames.CodeActionResolve, + new LanguageProtocolDelegatingHandlers.CanBeResolved( + id, + resolveHandler, + registrationOptions + ) + ) + ; + } + + public static ILanguageServerRegistry OnCodeAction( + this ILanguageServerRegistry registry, + Func>> handler, + Func, CancellationToken, Task>>? resolveHandler, + CodeActionRegistrationOptions? registrationOptions + ) where T : HandlerIdentity?, new() + { + registrationOptions ??= new CodeActionRegistrationOptions(); + registrationOptions.ResolveProvider = true; + resolveHandler ??= (link, token) => Task.FromResult(link); + + return registry.AddHandler( + _ => new DelegatingCodeActionHandler( + registrationOptions, + async (@params, capability, token) => await handler(@params, token), + (lens, capability, token) => resolveHandler(lens, token) + ) + ); + } + + public static ILanguageServerRegistry OnCodeAction( + this ILanguageServerRegistry registry, + Func> handler, + CodeActionRegistrationOptions? registrationOptions + ) => + OnCodeAction(registry, handler, null, registrationOptions); + + public static ILanguageServerRegistry OnCodeAction( + this ILanguageServerRegistry registry, + Func> handler, + Func>? resolveHandler, + CodeActionRegistrationOptions? registrationOptions + ) + { + registrationOptions ??= new CodeActionRegistrationOptions(); + registrationOptions.ResolveProvider = true; + resolveHandler ??= Task.FromResult; + var id = Guid.NewGuid(); + + return registry.AddHandler( + TextDocumentNames.CodeAction, + new LanguageProtocolDelegatingHandlers.RequestRegistration( + id, + handler, + registrationOptions + ) + ) + .AddHandler( + TextDocumentNames.CodeActionResolve, + new LanguageProtocolDelegatingHandlers.CanBeResolved( + id, + resolveHandler, + registrationOptions + ) + ) + ; + } + + public static ILanguageServerRegistry OnCodeAction( + this ILanguageServerRegistry registry, + Func>> handler, + Func, Task>>? resolveHandler, + CodeActionRegistrationOptions? registrationOptions + ) where T : HandlerIdentity?, new() + { + registrationOptions ??= new CodeActionRegistrationOptions(); + registrationOptions.ResolveProvider = true; + resolveHandler ??= Task.FromResult; + + return registry.AddHandler( + _ => new DelegatingCodeActionHandler( + registrationOptions, + async (@params, capability, token) => await handler(@params), + (lens, capability, token) => resolveHandler(lens) + ) + ); + } + + public static ILanguageServerRegistry OnCodeAction( + this ILanguageServerRegistry registry, + Action>, CodeActionCapability, CancellationToken> handler, + CodeActionRegistrationOptions? registrationOptions + ) => + OnCodeAction(registry, handler, null, registrationOptions); + + public static ILanguageServerRegistry OnCodeAction( + this ILanguageServerRegistry registry, + Action>, CodeActionCapability, CancellationToken> handler, + Func>? resolveHandler, + CodeActionRegistrationOptions? registrationOptions + ) + { + registrationOptions ??= new CodeActionRegistrationOptions(); + registrationOptions.ResolveProvider = true; + resolveHandler ??= (lens, capability, token) => Task.FromResult(lens); + var id = Guid.NewGuid(); + + return + registry.AddHandler( + TextDocumentNames.CodeAction, + _ => new CodeActionPartialResults( + id, + handler, + registrationOptions, + _.GetRequiredService() + ) + ) + .AddHandler( + TextDocumentNames.CodeActionResolve, + new LanguageProtocolDelegatingHandlers.CanBeResolved( + id, + resolveHandler, + registrationOptions + ) + ) + ; + } + + public static ILanguageServerRegistry OnCodeAction( + this ILanguageServerRegistry registry, + Action>>, CodeActionCapability, CancellationToken> handler, + Func, CodeActionCapability, CancellationToken, Task>>? resolveHandler, + CodeActionRegistrationOptions? registrationOptions + ) where T : HandlerIdentity?, new() + { + registrationOptions ??= new CodeActionRegistrationOptions(); + registrationOptions.ResolveProvider = true; + resolveHandler ??= (lens, capability, token) => Task.FromResult(lens); + + return registry.AddHandler( + _ => new DelegatingPartialCodeActionHandler( + registrationOptions, + _.GetRequiredService(), + handler, + resolveHandler + ) + ); + } + + public static ILanguageServerRegistry OnCodeAction( + this ILanguageServerRegistry registry, + Action>, CancellationToken> handler, + CodeActionRegistrationOptions? registrationOptions + ) => + OnCodeAction(registry, handler, null, registrationOptions); + + public static ILanguageServerRegistry OnCodeAction( + this ILanguageServerRegistry registry, + Action>, CancellationToken> handler, + Func>? resolveHandler, + CodeActionRegistrationOptions? registrationOptions + ) + { + registrationOptions ??= new CodeActionRegistrationOptions(); + registrationOptions.ResolveProvider = true; + resolveHandler ??= (lens, token) => Task.FromResult(lens); + var id = Guid.NewGuid(); + + return registry.AddHandler( + TextDocumentNames.CodeAction, + _ => new CodeActionPartialResults( + id, + (@params, observer, capability, arg4) => handler(@params, observer, arg4), + registrationOptions, + _.GetRequiredService() + ) + ) + .AddHandler( + TextDocumentNames.CodeActionResolve, + new LanguageProtocolDelegatingHandlers.CanBeResolved( + id, + resolveHandler, + registrationOptions + ) + ) + ; + } + + public static ILanguageServerRegistry OnCodeAction( + this ILanguageServerRegistry registry, + Action>>, CancellationToken> handler, + Func, CancellationToken, Task>>? resolveHandler, + CodeActionRegistrationOptions? registrationOptions + ) where T : HandlerIdentity?, new() + { + registrationOptions ??= new CodeActionRegistrationOptions(); + registrationOptions.ResolveProvider = true; + resolveHandler ??= (lens, token) => Task.FromResult(lens); + + return registry.AddHandler( + _ => new DelegatingPartialCodeActionHandler( + registrationOptions, + _.GetRequiredService(), + (@params, observer, capability, token) => handler(@params, observer, token), + (lens, capability, token) => resolveHandler(lens, token) + ) + ); + } + + public static ILanguageServerRegistry OnCodeAction( + this ILanguageServerRegistry registry, + Action>> handler, + CodeActionRegistrationOptions? registrationOptions + ) => + OnCodeAction(registry, handler, null, registrationOptions); + + public static ILanguageServerRegistry OnCodeAction( + this ILanguageServerRegistry registry, + Action>> handler, + Func>? resolveHandler, + CodeActionRegistrationOptions? registrationOptions + ) + { + registrationOptions ??= new CodeActionRegistrationOptions(); + registrationOptions.ResolveProvider = true; + resolveHandler ??= Task.FromResult; + var id = Guid.NewGuid(); + + return registry.AddHandler( + TextDocumentNames.CodeAction, + _ => new CodeActionPartialResults( + id, + (@params, observer, capability, arg3) => handler(@params, observer), + registrationOptions, + _.GetRequiredService() + ) + ) + .AddHandler( + TextDocumentNames.CodeActionResolve, + new LanguageProtocolDelegatingHandlers.CanBeResolved( + id, + resolveHandler, + registrationOptions + ) + ) + ; + } + + sealed class CodeActionPartialResults : + IJsonRpcRequestHandler, + IRegistration, + ICanBeIdentifiedHandler, + ICapability + { + private readonly Action>, CodeActionCapability, CancellationToken> _handler; + private readonly CodeActionRegistrationOptions _registrationOptions; + private readonly IProgressManager _progressManager; + private readonly Guid _id; + private CodeActionCapability? _capability; + Guid ICanBeIdentifiedHandler.Id => _id; + + public CodeActionPartialResults( + Guid id, Action>, CodeActionCapability, CancellationToken> handler, CodeActionRegistrationOptions registrationOptions, IProgressManager progressManager + ) + { + _id = id; + _handler = handler; + _registrationOptions = registrationOptions; + _progressManager = progressManager; + } + + async Task IRequestHandler.Handle(CodeActionParams request, CancellationToken cancellationToken) + { + var observer = _progressManager.For(request, cancellationToken); + if (observer != ProgressObserver>.Noop) + { + _handler( + request, + Observer.Create>( + v => observer.OnNext(v.Select(z => new CommandOrCodeAction(z))), + observer.OnError, + observer.OnCompleted + ), + _capability!, + cancellationToken + ); + await observer; + return new CommandOrCodeActionContainer(); + } + + var subject = new Subject>(); + var task = subject.Aggregate( + new List(), (acc, items) => { + acc.AddRange(items); + return acc; + } + ) + .ToTask(cancellationToken); + _handler(request, subject, _capability!, cancellationToken); + var actions = await task.ConfigureAwait(false); + var result = new CommandOrCodeActionContainer(actions.Select(z => new CommandOrCodeAction(z))); + return result; + } + + CodeActionRegistrationOptions IRegistration.GetRegistrationOptions() => _registrationOptions; + public void SetCapability(CodeActionCapability capability) => _capability = capability; + } + + public static ILanguageServerRegistry OnCodeAction( + this ILanguageServerRegistry registry, + Action>>> handler, + Func, Task>>? resolveHandler, + CodeActionRegistrationOptions? registrationOptions + ) where T : HandlerIdentity?, new() + { + registrationOptions ??= new CodeActionRegistrationOptions(); + registrationOptions.ResolveProvider = true; + resolveHandler ??= Task.FromResult; + + return registry.AddHandler( + _ => new DelegatingPartialCodeActionHandler( + registrationOptions, + _.GetRequiredService(), + (@params, observer, capability, token) => handler(@params, observer), + (lens, capability, token) => resolveHandler(lens) + ) + ); + } + + private class DelegatingCodeActionHandler : CodeActionHandlerBase where T : HandlerIdentity?, new() + { + private readonly Func> _handleParams; + private readonly Func, CodeActionCapability, CancellationToken, Task>> _handleResolve; + + public DelegatingCodeActionHandler( + CodeActionRegistrationOptions registrationOptions, + Func> handleParams, + Func, CodeActionCapability, CancellationToken, Task>> handleResolve + ) : base(registrationOptions) + { + _handleParams = handleParams; + _handleResolve = handleResolve; + } + + protected override Task HandleParams(CodeActionParams request, CancellationToken cancellationToken) => + _handleParams(request, Capability, cancellationToken); + + protected override Task> HandleResolve(CodeAction request, CancellationToken cancellationToken) => _handleResolve(request, Capability, cancellationToken); + } + + private class DelegatingPartialCodeActionHandler : PartialCodeActionHandlerBase where T : HandlerIdentity?, new() + { + private readonly Action>>, CodeActionCapability, CancellationToken> _handleParams; + private readonly Func, CodeActionCapability, CancellationToken, Task>> _handleResolve; + + public DelegatingPartialCodeActionHandler( + CodeActionRegistrationOptions registrationOptions, + IProgressManager progressManager, + Action>>, CodeActionCapability, CancellationToken> handleParams, + Func, CodeActionCapability, CancellationToken, Task>> handleResolve + ) : base(registrationOptions, progressManager) + { + _handleParams = handleParams; + _handleResolve = handleResolve; + } + + protected override void Handle(CodeActionParams request, IObserver>> results, CancellationToken cancellationToken) => + _handleParams(request, results, Capability, cancellationToken); + + protected override Task> HandleResolve(CodeAction request, CancellationToken cancellationToken) => _handleResolve(request, Capability, cancellationToken); + } + } } diff --git a/src/Protocol/Document/ICodeLensHandler.cs b/src/Protocol/Document/ICodeLensHandler.cs index 89b6b7dcb..28a47acde 100644 --- a/src/Protocol/Document/ICodeLensHandler.cs +++ b/src/Protocol/Document/ICodeLensHandler.cs @@ -25,11 +25,11 @@ public interface ICodeLensHandler : IJsonRpcRequestHandler + public interface ICodeLensResolveHandler : ICanBeResolvedHandler, ICanBeIdentifiedHandler { } - public abstract class CodeLensHandler : ICodeLensHandler, ICodeLensResolveHandler, ICanBeIdentifiedHandler + public abstract class CodeLensHandler : ICodeLensHandler, ICodeLensResolveHandler { private readonly CodeLensRegistrationOptions _options; @@ -61,7 +61,7 @@ protected PartialCodeLensHandlerBase(CodeLensRegistrationOptions registrationOpt public virtual Guid Id { get; } = Guid.NewGuid(); } - public abstract class CodeLensHandlerBase : CodeLensHandler where T : HandlerIdentity, new() + public abstract class CodeLensHandlerBase : CodeLensHandler where T : HandlerIdentity?, new() { public CodeLensHandlerBase(CodeLensRegistrationOptions registrationOptions) : base(registrationOptions) { @@ -84,7 +84,7 @@ public sealed override async Task Handle(CodeLens request, Cancellatio protected abstract Task> HandleResolve(CodeLens request, CancellationToken cancellationToken); } - public abstract class PartialCodeLensHandlerBase : PartialCodeLensHandlerBase where T : HandlerIdentity, new() + public abstract class PartialCodeLensHandlerBase : PartialCodeLensHandlerBase where T : HandlerIdentity?, new() { protected PartialCodeLensHandlerBase(CodeLensRegistrationOptions registrationOptions, IProgressManager progressManager) : base( registrationOptions, @@ -158,7 +158,7 @@ public static ILanguageServerRegistry OnCodeLens( Func>> handler, Func, CodeLensCapability, CancellationToken, Task>>? resolveHandler, CodeLensRegistrationOptions? registrationOptions - ) where T : HandlerIdentity, new() + ) where T : HandlerIdentity?, new() { registrationOptions ??= new CodeLensRegistrationOptions(); registrationOptions.ResolveProvider = true; @@ -217,7 +217,7 @@ public static ILanguageServerRegistry OnCodeLens( Func>> handler, Func, CancellationToken, Task>>? resolveHandler, CodeLensRegistrationOptions? registrationOptions - ) where T : HandlerIdentity, new() + ) where T : HandlerIdentity?, new() { registrationOptions ??= new CodeLensRegistrationOptions(); registrationOptions.ResolveProvider = true; @@ -276,7 +276,7 @@ public static ILanguageServerRegistry OnCodeLens( Func>> handler, Func, Task>>? resolveHandler, CodeLensRegistrationOptions? registrationOptions - ) where T : HandlerIdentity, new() + ) where T : HandlerIdentity?, new() { registrationOptions ??= new CodeLensRegistrationOptions(); registrationOptions.ResolveProvider = true; @@ -338,7 +338,7 @@ public static ILanguageServerRegistry OnCodeLens( Action>>, CodeLensCapability, CancellationToken> handler, Func, CodeLensCapability, CancellationToken, Task>>? resolveHandler, CodeLensRegistrationOptions? registrationOptions - ) where T : HandlerIdentity, new() + ) where T : HandlerIdentity?, new() { registrationOptions ??= new CodeLensRegistrationOptions(); registrationOptions.ResolveProvider = true; @@ -400,7 +400,7 @@ public static ILanguageServerRegistry OnCodeLens( Action>>, CancellationToken> handler, Func, CancellationToken, Task>>? resolveHandler, CodeLensRegistrationOptions? registrationOptions - ) where T : HandlerIdentity, new() + ) where T : HandlerIdentity?, new() { registrationOptions ??= new CodeLensRegistrationOptions(); registrationOptions.ResolveProvider = true; @@ -462,7 +462,7 @@ public static ILanguageServerRegistry OnCodeLens( Action>>> handler, Func, Task>>? resolveHandler, CodeLensRegistrationOptions? registrationOptions - ) where T : HandlerIdentity, new() + ) where T : HandlerIdentity?, new() { registrationOptions ??= new CodeLensRegistrationOptions(); registrationOptions.ResolveProvider = true; @@ -478,7 +478,7 @@ public static ILanguageServerRegistry OnCodeLens( ); } - private class DelegatingCodeLensHandler : CodeLensHandlerBase where T : HandlerIdentity, new() + private class DelegatingCodeLensHandler : CodeLensHandlerBase where T : HandlerIdentity?, new() { private readonly Func>> _handleParams; private readonly Func, CodeLensCapability, CancellationToken, Task>> _handleResolve; @@ -499,7 +499,7 @@ protected override Task> HandleParams(CodeLensParams reques protected override Task> HandleResolve(CodeLens request, CancellationToken cancellationToken) => _handleResolve(request, Capability, cancellationToken); } - private class DelegatingPartialCodeLensHandler : PartialCodeLensHandlerBase where T : HandlerIdentity, new() + private 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 2583a2df0..4297467a8 100644 --- a/src/Protocol/Document/ICompletionHandler.cs +++ b/src/Protocol/Document/ICompletionHandler.cs @@ -62,7 +62,7 @@ protected PartialCompletionHandlerBase(CompletionRegistrationOptions registratio public virtual Guid Id { get; } = Guid.NewGuid(); } - public abstract class CompletionHandlerBase : CompletionHandler where T : HandlerIdentity, new() + public abstract class CompletionHandlerBase : CompletionHandler where T : HandlerIdentity?, new() { public CompletionHandlerBase(CompletionRegistrationOptions registrationOptions) : base(registrationOptions) { @@ -84,7 +84,7 @@ public sealed override async Task Handle(CompletionItem request, protected abstract Task> HandleResolve(CompletionItem request, CancellationToken cancellationToken); } - public abstract class PartialCompletionHandlerBase : PartialCompletionHandlerBase where T : HandlerIdentity, new() + public abstract class PartialCompletionHandlerBase : PartialCompletionHandlerBase where T : HandlerIdentity?, new() { protected PartialCompletionHandlerBase(CompletionRegistrationOptions registrationOptions, IProgressManager progressManager) : base( registrationOptions, @@ -158,7 +158,7 @@ public static ILanguageServerRegistry OnCompletion( Func>> handler, Func, CompletionCapability, CancellationToken, Task>>? resolveHandler, CompletionRegistrationOptions? registrationOptions - ) where T : HandlerIdentity, new() + ) where T : HandlerIdentity?, new() { registrationOptions ??= new CompletionRegistrationOptions(); registrationOptions.ResolveProvider = true; @@ -217,7 +217,7 @@ public static ILanguageServerRegistry OnCompletion( Func>> handler, Func, CancellationToken, Task>>? resolveHandler, CompletionRegistrationOptions? registrationOptions - ) where T : HandlerIdentity, new() + ) where T : HandlerIdentity?, new() { registrationOptions ??= new CompletionRegistrationOptions(); registrationOptions.ResolveProvider = true; @@ -276,7 +276,7 @@ public static ILanguageServerRegistry OnCompletion( Func>> handler, Func, Task>>? resolveHandler, CompletionRegistrationOptions? registrationOptions - ) where T : HandlerIdentity, new() + ) where T : HandlerIdentity?, new() { registrationOptions ??= new CompletionRegistrationOptions(); registrationOptions.ResolveProvider = true; @@ -338,7 +338,7 @@ public static ILanguageServerRegistry OnCompletion( Action>>, CompletionCapability, CancellationToken> handler, Func, CompletionCapability, CancellationToken, Task>>? resolveHandler, CompletionRegistrationOptions? registrationOptions - ) where T : HandlerIdentity, new() + ) where T : HandlerIdentity?, new() { registrationOptions ??= new CompletionRegistrationOptions(); registrationOptions.ResolveProvider = true; @@ -400,7 +400,7 @@ public static ILanguageServerRegistry OnCompletion( Action>>, CancellationToken> handler, Func, CancellationToken, Task>>? resolveHandler, CompletionRegistrationOptions? registrationOptions - ) where T : HandlerIdentity, new() + ) where T : HandlerIdentity?, new() { registrationOptions ??= new CompletionRegistrationOptions(); registrationOptions.ResolveProvider = true; @@ -462,7 +462,7 @@ public static ILanguageServerRegistry OnCompletion( Action>>> handler, Func, Task>>? resolveHandler, CompletionRegistrationOptions? registrationOptions - ) where T : HandlerIdentity, new() + ) where T : HandlerIdentity?, new() { registrationOptions ??= new CompletionRegistrationOptions(); registrationOptions.ResolveProvider = true; @@ -478,7 +478,7 @@ public static ILanguageServerRegistry OnCompletion( ); } - private class DelegatingCompletionHandler : CompletionHandlerBase where T : HandlerIdentity, new() + private class DelegatingCompletionHandler : CompletionHandlerBase where T : HandlerIdentity?, new() { private readonly Func>> _handleParams; private readonly Func, CompletionCapability, CancellationToken, Task>> _handleResolve; @@ -500,7 +500,7 @@ protected override Task> HandleResolve(CompletionItem reque _handleResolve(request, Capability, cancellationToken); } - private class DelegatingPartialCompletionHandler : PartialCompletionHandlerBase where T : HandlerIdentity, new() + private 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 20ca09a7c..75cb2efa7 100644 --- a/src/Protocol/Document/IDocumentLinkHandler.cs +++ b/src/Protocol/Document/IDocumentLinkHandler.cs @@ -63,7 +63,7 @@ protected PartialDocumentLinkHandlerBase(DocumentLinkRegistrationOptions registr public virtual Guid Id { get; } = Guid.NewGuid(); } - public abstract class DocumentLinkHandlerBase : DocumentLinkHandler where T : HandlerIdentity, new() + public abstract class DocumentLinkHandlerBase : DocumentLinkHandler where T : HandlerIdentity?, new() { public DocumentLinkHandlerBase(DocumentLinkRegistrationOptions registrationOptions) : base(registrationOptions) { @@ -86,7 +86,7 @@ public sealed override async Task Handle(DocumentLink request, Can protected abstract Task> HandleResolve(DocumentLink request, CancellationToken cancellationToken); } - public abstract class PartialDocumentLinkHandlerBase : PartialDocumentLinkHandlerBase where T : HandlerIdentity, new() + public abstract class PartialDocumentLinkHandlerBase : PartialDocumentLinkHandlerBase where T : HandlerIdentity?, new() { protected PartialDocumentLinkHandlerBase(DocumentLinkRegistrationOptions registrationOptions, IProgressManager progressManager) : base( registrationOptions, @@ -160,7 +160,7 @@ public static ILanguageServerRegistry OnDocumentLink( Func>> handler, Func, DocumentLinkCapability, CancellationToken, Task>>? resolveHandler, DocumentLinkRegistrationOptions? registrationOptions - ) where T : HandlerIdentity, new() + ) where T : HandlerIdentity?, new() { registrationOptions ??= new DocumentLinkRegistrationOptions(); registrationOptions.ResolveProvider = true; @@ -219,7 +219,7 @@ public static ILanguageServerRegistry OnDocumentLink( Func>> handler, Func, CancellationToken, Task>>? resolveHandler, DocumentLinkRegistrationOptions? registrationOptions - ) where T : HandlerIdentity, new() + ) where T : HandlerIdentity?, new() { registrationOptions ??= new DocumentLinkRegistrationOptions(); registrationOptions.ResolveProvider = true; @@ -278,7 +278,7 @@ public static ILanguageServerRegistry OnDocumentLink( Func>> handler, Func, Task>>? resolveHandler, DocumentLinkRegistrationOptions? registrationOptions - ) where T : HandlerIdentity, new() + ) where T : HandlerIdentity?, new() { registrationOptions ??= new DocumentLinkRegistrationOptions(); registrationOptions.ResolveProvider = true; @@ -340,7 +340,7 @@ public static ILanguageServerRegistry OnDocumentLink( Action>>, DocumentLinkCapability, CancellationToken> handler, Func, DocumentLinkCapability, CancellationToken, Task>>? resolveHandler, DocumentLinkRegistrationOptions? registrationOptions - ) where T : HandlerIdentity, new() + ) where T : HandlerIdentity?, new() { registrationOptions ??= new DocumentLinkRegistrationOptions(); registrationOptions.ResolveProvider = true; @@ -402,7 +402,7 @@ public static ILanguageServerRegistry OnDocumentLink( Action>>, CancellationToken> handler, Func, CancellationToken, Task>>? resolveHandler, DocumentLinkRegistrationOptions? registrationOptions - ) where T : HandlerIdentity, new() + ) where T : HandlerIdentity?, new() { registrationOptions ??= new DocumentLinkRegistrationOptions(); registrationOptions.ResolveProvider = true; @@ -464,7 +464,7 @@ public static ILanguageServerRegistry OnDocumentLink( Action>>> handler, Func, Task>>? resolveHandler, DocumentLinkRegistrationOptions? registrationOptions - ) where T : HandlerIdentity, new() + ) where T : HandlerIdentity?, new() { registrationOptions ??= new DocumentLinkRegistrationOptions(); registrationOptions.ResolveProvider = true; @@ -480,7 +480,7 @@ public static ILanguageServerRegistry OnDocumentLink( ); } - private class DelegatingDocumentLinkHandler : DocumentLinkHandlerBase where T : HandlerIdentity, new() + private class DelegatingDocumentLinkHandler : DocumentLinkHandlerBase, ICanBeIdentifiedHandler where T : HandlerIdentity?, new() { private readonly Func>> _handleParams; private readonly Func, DocumentLinkCapability, CancellationToken, Task>> _handleResolve; @@ -502,7 +502,7 @@ protected override Task> HandleResolve(DocumentLink request, _handleResolve(request, Capability, cancellationToken); } - private class DelegatingPartialDocumentLinkHandler : PartialDocumentLinkHandlerBase where T : HandlerIdentity, new() + private class DelegatingPartialDocumentLinkHandler : PartialDocumentLinkHandlerBase, ICanBeIdentifiedHandler where T : HandlerIdentity?, new() { private readonly Action>>, DocumentLinkCapability, CancellationToken> _handleParams; private readonly Func, DocumentLinkCapability, CancellationToken, Task>> _handleResolve; diff --git a/src/Protocol/Document/Proposals/IMonikerHandler.cs b/src/Protocol/Document/Proposals/IMonikerHandler.cs new file mode 100644 index 000000000..e07ea37ff --- /dev/null +++ b/src/Protocol/Document/Proposals/IMonikerHandler.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +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; +using OmniSharp.Extensions.LanguageServer.Protocol.Models.Proposals; +using OmniSharp.Extensions.LanguageServer.Protocol.Progress; + +namespace OmniSharp.Extensions.LanguageServer.Protocol.Document.Proposals +{ + [Obsolete(Constants.Proposal)] + [Parallel] + [Method(TextDocumentNames.Moniker, Direction.ClientToServer)] + [GenerateHandlerMethods] + [GenerateRequestMethods(typeof(ITextDocumentLanguageClient), typeof(ILanguageClient))] + public interface IMonikerHandler : + IJsonRpcRequestHandler?>, + IRegistration, + ICapability + { + } + + [Obsolete(Constants.Proposal)] + public abstract class MonikerHandlerBase : IMonikerHandler + { + private readonly MonikerRegistrationOptions _options; + + protected MonikerHandlerBase(MonikerRegistrationOptions registrationOptions) + { + _options = registrationOptions; + } + + public MonikerRegistrationOptions GetRegistrationOptions() => _options; + public abstract Task?> Handle(MonikerParams request, CancellationToken cancellationToken); + public virtual void SetCapability(MonikerCapability capability) => Capability = capability; + protected MonikerCapability Capability { get; private set; } = null!; + } + + [Obsolete(Constants.Proposal)] + public abstract class PartialMonikerHandlerBase : + AbstractHandlers.PartialResults, Moniker, MonikerCapability, MonikerRegistrationOptions>, IMonikerHandler + { + protected PartialMonikerHandlerBase(MonikerRegistrationOptions registrationOptions, IProgressManager progressManager) : base( + registrationOptions, progressManager, + items => new Container(items) + ) + { + } + + public virtual Guid Id { get; } = Guid.NewGuid(); + } +} diff --git a/src/Protocol/Models/CodeAction.cs b/src/Protocol/Models/CodeAction.cs index ef4d292d6..aa2a17275 100644 --- a/src/Protocol/Models/CodeAction.cs +++ b/src/Protocol/Models/CodeAction.cs @@ -1,10 +1,14 @@ using System.Diagnostics; +using MediatR; +using Newtonsoft.Json.Linq; +using OmniSharp.Extensions.JsonRpc; using OmniSharp.Extensions.LanguageServer.Protocol.Serialization; namespace OmniSharp.Extensions.LanguageServer.Protocol.Models { [DebuggerDisplay("{" + nameof(DebuggerDisplay) + ",nq}")] - public class CodeAction + [Method(TextDocumentNames.CodeActionResolve, Direction.ClientToServer)] + public class CodeAction : ICanBeResolved, IRequest { /// /// A short, human-readable, title for this code action. @@ -51,6 +55,147 @@ public class CodeAction [Optional] public Command? Command { get; set; } + /// + /// Marks that the code action cannot currently be applied. + /// + /// Clients should follow the following guidelines regarding disabled code actions: + /// + /// - Disabled code actions are not shown in automatic [lightbulb](https://code.visualstudio.com/docs/editor/editingevolved#_code-action) + /// code action menu. + /// + /// - Disabled actions are shown as faded out in the code action menu when the user request a more specific type + /// of code action, such as refactorings. + /// + /// - If the user has a [keybinding](https://code.visualstudio.com/docs/editor/refactoring#_keybindings-for-code-actions) + /// that auto applies a code action and only a disabled code actions are returned, the client should show the user an + /// error message with `reason` in the editor. + /// + /// @since 3.16.0 + /// + [Optional] + public CodeActionDisabled? Disabled { get; set; } + + /// + /// A data entry field that is preserved on a document link between a + /// DocumentLinkRequest and a DocumentLinkResolveRequest. + /// + [Optional] + public JToken? Data { get; set; } + + private string DebuggerDisplay => $"[{Kind}] {Title}"; + + /// + public override string ToString() => DebuggerDisplay; + } + + [DebuggerDisplay("{" + nameof(DebuggerDisplay) + ",nq}")] + public class CodeAction : ICanBeResolved + where T : HandlerIdentity?, new() + { + /// + /// A short, human-readable, title for this code action. + /// + public string Title { get; set; } = null!; + + /// + /// The kind of the code action. + /// + /// Used to filter code actions. + /// + [Optional] + public CodeActionKind Kind { get; set; } + + /// + /// Marks this as a preferred action. Preferred actions are used by the `auto fix` command and can be targeted + /// by keybindings. + /// + /// A quick fix should be marked preferred if it properly addresses the underlying error. + /// A refactoring should be marked preferred if it is the most reasonable choice of actions to take. + /// + /// @since 3.15.0 + /// + [Optional] + public bool IsPreferred { get; set; } + + /// + /// The diagnostics that this code action resolves. + /// + [Optional] + public Container? Diagnostics { get; set; } + + /// + /// The workspace edit this code action performs. + /// + [Optional] + public WorkspaceEdit? Edit { get; set; } + + /// + /// A command this code action executes. If a code action + /// provides an edit and a command, first the edit is + /// executed and then the command. + /// + [Optional] + public Command? Command { get; set; } + + /// + /// Marks that the code action cannot currently be applied. + /// + /// Clients should follow the following guidelines regarding disabled code actions: + /// + /// - Disabled code actions are not shown in automatic [lightbulb](https://code.visualstudio.com/docs/editor/editingevolved#_code-action) + /// code action menu. + /// + /// - Disabled actions are shown as faded out in the code action menu when the user request a more specific type + /// of code action, such as refactorings. + /// + /// - If the user has a [keybinding](https://code.visualstudio.com/docs/editor/refactoring#_keybindings-for-code-actions) + /// that auto applies a code action and only a disabled code actions are returned, the client should show the user an + /// error message with `reason` in the editor. + /// + /// @since 3.16.0 + /// + [Optional] + public CodeActionDisabled? Disabled { 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 + { + get => ( (ICanBeResolved) this ).Data?.ToObject()!; + set => ( (ICanBeResolved) this ).Data = JToken.FromObject(value); + } + + JToken? ICanBeResolved.Data { get; set; } + + public static implicit operator CodeAction(CodeAction value) => new CodeAction { + Data = ( (ICanBeResolved) value ).Data, + Command = value.Command, + Diagnostics = value.Diagnostics, + Disabled = value.Disabled, + Edit = value.Edit, + Kind = value.Kind, + Title = value.Title, + IsPreferred = value.IsPreferred, + }; + + public static implicit operator CodeAction(CodeAction value) + { + var item = new CodeAction { + Command = value.Command, + Diagnostics = value.Diagnostics, + Disabled = value.Disabled, + Edit = value.Edit, + Kind = value.Kind, + Title = value.Title, + IsPreferred = value.IsPreferred, + }; + ( (ICanBeResolved) item ).Data = value.Data; + return item; + } + private string DebuggerDisplay => $"[{Kind}] {Title}"; /// diff --git a/src/Protocol/Models/CodeActionDisabled.cs b/src/Protocol/Models/CodeActionDisabled.cs new file mode 100644 index 000000000..417b95552 --- /dev/null +++ b/src/Protocol/Models/CodeActionDisabled.cs @@ -0,0 +1,29 @@ +namespace OmniSharp.Extensions.LanguageServer.Protocol.Models +{ + /// + /// Marks that the code action cannot currently be applied. + /// + /// Clients should follow the following guidelines regarding disabled code actions: + /// + /// - Disabled code actions are not shown in automatic [lightbulb](https://code.visualstudio.com/docs/editor/editingevolved#_code-action) + /// code action menu. + /// + /// - Disabled actions are shown as faded out in the code action menu when the user request a more specific type + /// of code action, such as refactorings. + /// + /// - If the user has a [keybinding](https://code.visualstudio.com/docs/editor/refactoring#_keybindings-for-code-actions) + /// that auto applies a code action and only a disabled code actions are returned, the client should show the user an + /// error message with `reason` in the editor. + /// + /// @since 3.16.0 + /// + public class CodeActionDisabled + { + /// + /// Human readable description of why the code action is currently disabled. + /// + /// This is displayed in the code actions UI. + /// + public string Reason { get; set; } = null!; + } +} diff --git a/src/Protocol/Models/CodeActionKind.cs b/src/Protocol/Models/CodeActionKind.cs index bfd876436..843f55be9 100644 --- a/src/Protocol/Models/CodeActionKind.cs +++ b/src/Protocol/Models/CodeActionKind.cs @@ -99,8 +99,4 @@ namespace OmniSharp.Extensions.LanguageServer.Protocol.Models public static bool operator !=(CodeActionKind left, CodeActionKind right) => !left.Equals(right); } - - public interface IEnumLikeString - { - } } diff --git a/src/Protocol/Models/CodeActionRegistrationOptions.cs b/src/Protocol/Models/CodeActionRegistrationOptions.cs index 826b63e6e..56f2f0d49 100644 --- a/src/Protocol/Models/CodeActionRegistrationOptions.cs +++ b/src/Protocol/Models/CodeActionRegistrationOptions.cs @@ -1,3 +1,6 @@ +using System.Linq; +using OmniSharp.Extensions.JsonRpc; +using OmniSharp.Extensions.LanguageServer.Protocol.Document; using OmniSharp.Extensions.LanguageServer.Protocol.Serialization; using OmniSharp.Extensions.LanguageServer.Protocol.Server.Capabilities; @@ -14,6 +17,14 @@ public class CodeActionRegistrationOptions : WorkDoneTextDocumentRegistrationOpt [Optional] public Container? CodeActionKinds { get; set; } = new Container(); + /// + /// The server provides support to resolve additional + /// information for a code action. + /// + /// @since 3.16.0 + /// + public bool ResolveProvider { get; set; } + public class StaticOptions : WorkDoneProgressOptions { /// @@ -24,18 +35,31 @@ public class StaticOptions : WorkDoneProgressOptions /// [Optional] public Container? CodeActionKinds { get; set; } = new Container(); + + /// + /// The server provides support to resolve additional + /// information for a code action. + /// + /// @since 3.16.0 + /// + [Optional] + public bool ResolveProvider { get; set; } } class CodeActionRegistrationOptionsConverter : RegistrationOptionsConverterBase { - public CodeActionRegistrationOptionsConverter() : base(nameof(ServerCapabilities.CodeActionProvider)) + private readonly IHandlersManager _handlersManager; + + public CodeActionRegistrationOptionsConverter(IHandlersManager handlersManager) : base(nameof(ServerCapabilities.CodeActionProvider)) { + _handlersManager = handlersManager; } public override StaticOptions Convert(CodeActionRegistrationOptions source) { return new StaticOptions { CodeActionKinds = source.CodeActionKinds, + ResolveProvider = source.ResolveProvider || _handlersManager.Descriptors.Any(z => z.HandlerType == typeof(ICodeActionResolveHandler)), WorkDoneProgress = source.WorkDoneProgress, }; } diff --git a/src/Protocol/Models/CodeLens.cs b/src/Protocol/Models/CodeLens.cs index 69891532c..d61d0f557 100644 --- a/src/Protocol/Models/CodeLens.cs +++ b/src/Protocol/Models/CodeLens.cs @@ -52,8 +52,9 @@ public class CodeLens : IRequest, ICanBeResolved /// Typed code lens used for the typed handlers /// /// + [DebuggerDisplay("{" + nameof(DebuggerDisplay) + ",nq}")] public class CodeLens : ICanBeResolved - where T : HandlerIdentity, new() + where T : HandlerIdentity?, new() { /// /// The range in which this code lens is valid. Should only span a single line. @@ -93,5 +94,10 @@ public static implicit operator CodeLens(CodeLens value) ( (ICanBeResolved) item ).Data = value.Data; return item; } + + private string DebuggerDisplay => $"{Range}{( Command != null ? $" {Command}" : "" )}"; + + /// + public override string ToString() => DebuggerDisplay; } } diff --git a/src/Protocol/Models/CodeLensContainer.cs b/src/Protocol/Models/CodeLensContainer.cs index 6b9729648..45f970598 100644 --- a/src/Protocol/Models/CodeLensContainer.cs +++ b/src/Protocol/Models/CodeLensContainer.cs @@ -28,7 +28,7 @@ public CodeLensContainer(params CodeLens[] items) : base(items) /// /// Typed code lens used for the typed handlers /// - public class CodeLensContainer : Container> where T : HandlerIdentity, new() + public class CodeLensContainer : Container> where T : HandlerIdentity?, new() { public CodeLensContainer() : this(Enumerable.Empty>()) { diff --git a/src/Protocol/Models/CommandOrCodeAction.cs b/src/Protocol/Models/CommandOrCodeAction.cs index 5cf615569..300ca0d0e 100644 --- a/src/Protocol/Models/CommandOrCodeAction.cs +++ b/src/Protocol/Models/CommandOrCodeAction.cs @@ -1,12 +1,13 @@ using System.Diagnostics; using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using OmniSharp.Extensions.LanguageServer.Protocol.Serialization.Converters; namespace OmniSharp.Extensions.LanguageServer.Protocol.Models { [JsonConverter(typeof(CommandOrCodeActionConverter))] [DebuggerDisplay("{" + nameof(DebuggerDisplay) + ",nq}")] - public struct CommandOrCodeAction + public class CommandOrCodeAction : ICanBeResolved // This to ensure that code actions get updated as expected { private CodeAction? _codeAction; private Command? _command; @@ -62,5 +63,14 @@ public object? RawValue /// public override string ToString() => DebuggerDisplay; + + JToken? ICanBeResolved.Data + { + get => _codeAction?.Data; + set { + if (_codeAction == null) return; + _codeAction.Data = value; + } + } } } diff --git a/src/Protocol/Models/CommandOrCodeActionContainer.cs b/src/Protocol/Models/CommandOrCodeActionContainer.cs index 861fba7a2..b90b28596 100644 --- a/src/Protocol/Models/CommandOrCodeActionContainer.cs +++ b/src/Protocol/Models/CommandOrCodeActionContainer.cs @@ -24,4 +24,30 @@ public CommandOrCodeActionContainer(params CommandOrCodeAction[] items) : base(i public static implicit operator CommandOrCodeActionContainer(List items) => new CommandOrCodeActionContainer(items); } + + /// + /// Typed code lens used for the typed handlers + /// + public class CodeActionContainer : Container> where T : HandlerIdentity?, new() + { + public CodeActionContainer() : this(Enumerable.Empty>()) + { + } + + public CodeActionContainer(IEnumerable> items) : base(items) + { + } + + public CodeActionContainer(params CodeAction[] items) : base(items) + { + } + + public static implicit operator CodeActionContainer(CodeAction[] items) => new CodeActionContainer(items); + + public static implicit operator CodeActionContainer(Collection> items) => new CodeActionContainer(items); + + public static implicit operator CodeActionContainer(List> items) => new CodeActionContainer(items); + + public static implicit operator CommandOrCodeActionContainer(CodeActionContainer container) => new CommandOrCodeActionContainer(container.Select(z => new CommandOrCodeAction(z))); + } } diff --git a/src/Protocol/Models/CommandOrCodeActionContainerExtensions.cs b/src/Protocol/Models/CommandOrCodeActionContainerExtensions.cs new file mode 100644 index 000000000..4ff3eed56 --- /dev/null +++ b/src/Protocol/Models/CommandOrCodeActionContainerExtensions.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using System.Linq; + +namespace OmniSharp.Extensions.LanguageServer.Protocol.Models +{ + public static class CommandOrCodeActionContainerExtensions + { + public static IEnumerable GetCodeActions(this IEnumerable value ) => + value + .Where(z => z.IsCodeAction) + .Select(z => z.CodeAction!); + + public static IEnumerable GetCommands(this IEnumerable value ) => + value + .Where(z => z.IsCommand) + .Select(z => z.Command!); + } +} diff --git a/src/Protocol/Models/CompletionItem.cs b/src/Protocol/Models/CompletionItem.cs index 129aeb0bf..e4168ac47 100644 --- a/src/Protocol/Models/CompletionItem.cs +++ b/src/Protocol/Models/CompletionItem.cs @@ -143,8 +143,9 @@ public class CompletionItem : ICanBeResolved, IRequest /// /// Typed code lens used for the typed handlers /// + [DebuggerDisplay("{" + nameof(DebuggerDisplay) + ",nq}")] public class CompletionItem : ICanBeResolved - where T : HandlerIdentity, new() + where T : HandlerIdentity?, new() { /// /// The label of this completion item. By default @@ -316,5 +317,10 @@ public static implicit operator CompletionItem(CompletionItem value) ( (ICanBeResolved) item ).Data = value.Data; return item; } + + private string DebuggerDisplay => $"[{Kind}] {Label}{( Tags?.Any() == true ? $" tags: {string.Join(", ", Tags.Select(z => z.ToString()))}" : "" )}"; + + /// + public override string ToString() => DebuggerDisplay; } } diff --git a/src/Protocol/Models/CompletionList.cs b/src/Protocol/Models/CompletionList.cs index 2ec619e19..01df44d59 100644 --- a/src/Protocol/Models/CompletionList.cs +++ b/src/Protocol/Models/CompletionList.cs @@ -55,7 +55,7 @@ public CompletionList(params CompletionItem[] items) : base(items) /// Represents a collection of [completion items](#CompletionItem) to be presented /// in the editor. /// - public class CompletionList : Container> where T : HandlerIdentity, new() + public class CompletionList : Container> where T : HandlerIdentity?, new() { public CompletionList() : base(Enumerable.Empty>()) { diff --git a/src/Protocol/Models/DocumentLink.cs b/src/Protocol/Models/DocumentLink.cs index f93e1c122..00dd163bc 100644 --- a/src/Protocol/Models/DocumentLink.cs +++ b/src/Protocol/Models/DocumentLink.cs @@ -54,8 +54,9 @@ public class DocumentLink : ICanBeResolved, IRequest /// 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. /// + [DebuggerDisplay("{" + nameof(DebuggerDisplay) + ",nq}")] public class DocumentLink : ICanBeResolved - where T : HandlerIdentity, new() + where T : HandlerIdentity?, new() { /// /// The range this link applies to. @@ -110,5 +111,10 @@ public static implicit operator DocumentLink(DocumentLink value) ( (ICanBeResolved) item ).Data = value.Data; return item; } + + private string DebuggerDisplay => $"{Range}{( Target is not null ? $" {Target}" : "" )}{( string.IsNullOrWhiteSpace(Tooltip) ? $" {Tooltip}" : "" )}"; + + /// + public override string ToString() => DebuggerDisplay; } } diff --git a/src/Protocol/Models/DocumentLinkContainer.cs b/src/Protocol/Models/DocumentLinkContainer.cs index 5df8dc011..381093eb0 100644 --- a/src/Protocol/Models/DocumentLinkContainer.cs +++ b/src/Protocol/Models/DocumentLinkContainer.cs @@ -25,7 +25,7 @@ public DocumentLinkContainer(params DocumentLink[] items) : base(items) public static implicit operator DocumentLinkContainer(List items) => new DocumentLinkContainer(items); } - public class DocumentLinkContainer : Container> where T : HandlerIdentity, new() + public class DocumentLinkContainer : Container> where T : HandlerIdentity?, new() { public DocumentLinkContainer() : this(Enumerable.Empty>()) { diff --git a/src/Protocol/Models/DocumentLinkRegistrationOptions.cs b/src/Protocol/Models/DocumentLinkRegistrationOptions.cs index a8203fe3d..695fa8440 100644 --- a/src/Protocol/Models/DocumentLinkRegistrationOptions.cs +++ b/src/Protocol/Models/DocumentLinkRegistrationOptions.cs @@ -33,6 +33,7 @@ public DocumentLinkRegistrationOptionsConverter(IHandlersManager handlersManager { _handlersManager = handlersManager; } + public override StaticOptions Convert(DocumentLinkRegistrationOptions source) => new StaticOptions { ResolveProvider = source.ResolveProvider || _handlersManager.Descriptors.Any(z => z.HandlerType == typeof(IDocumentLinkResolveHandler)), WorkDoneProgress = source.WorkDoneProgress, diff --git a/src/Protocol/Models/IEnumLikeString.cs b/src/Protocol/Models/IEnumLikeString.cs new file mode 100644 index 000000000..cbaa32aa7 --- /dev/null +++ b/src/Protocol/Models/IEnumLikeString.cs @@ -0,0 +1,6 @@ +namespace OmniSharp.Extensions.LanguageServer.Protocol.Models +{ + public interface IEnumLikeString + { + } +} diff --git a/src/Protocol/Models/Proposals/Moniker.cs b/src/Protocol/Models/Proposals/Moniker.cs new file mode 100644 index 000000000..07f8a75d4 --- /dev/null +++ b/src/Protocol/Models/Proposals/Moniker.cs @@ -0,0 +1,34 @@ +using System; +using OmniSharp.Extensions.LanguageServer.Protocol.Serialization; + +namespace OmniSharp.Extensions.LanguageServer.Protocol.Models.Proposals +{ + /// + /// Moniker definition to match LSIF 0.5 moniker definition. + /// + [Obsolete(Constants.Proposal)] + public class Moniker + { + /// + /// The scheme of the moniker. For example tsc or .Net + /// + public string Scheme { get; set; } = null!; + + /// + /// The identifier of the moniker. The value is opaque in LSIF however + /// schema owners are allowed to define the structure if they want. + /// + public string Identifier { get; set; } = null!; + + /// + /// The scope in which the moniker is unique + /// + public UniquenessLevel Unique { get; set; } + + /// + /// The moniker kind if known. + /// + [Optional] + public MonikerKind Kind { get; set; } + } +} diff --git a/src/Protocol/Models/Proposals/MonikerKind.cs b/src/Protocol/Models/Proposals/MonikerKind.cs new file mode 100644 index 000000000..487929845 --- /dev/null +++ b/src/Protocol/Models/Proposals/MonikerKind.cs @@ -0,0 +1,52 @@ +using System; +using System.Diagnostics; +using Newtonsoft.Json; +using OmniSharp.Extensions.LanguageServer.Protocol.Serialization.Converters; + +namespace OmniSharp.Extensions.LanguageServer.Protocol.Models.Proposals +{ + /// + /// Moniker uniqueness level to define scope of the moniker. + /// + [DebuggerDisplay("{" + nameof(_value) + "}")] + [JsonConverter(typeof(EnumLikeStringConverter))] + public readonly struct MonikerKind : IEquatable, IEnumLikeString + { + /// + /// The moniker represent a symbol that is imported into a project + /// + public static readonly MonikerKind Import = new MonikerKind("import"); + + /// + /// The moniker represents a symbol that is exported from a project + /// + public static readonly MonikerKind Export = new MonikerKind("export"); + + /// + /// The moniker represents a symbol that is local to a project (e.g. a local + /// variable of a function, a class not visible outside the project, ...) + /// + public static readonly MonikerKind Local = new MonikerKind("local"); + + private readonly string? _value; + + public MonikerKind(string kind) => _value = kind; + + public static implicit operator MonikerKind(string kind) => new MonikerKind(kind); + + public static implicit operator string(MonikerKind kind) => kind._value ?? string.Empty; + + /// + public override string ToString() => _value ?? string.Empty; + + public bool Equals(MonikerKind other) => _value == other._value; + + public override bool Equals(object obj) => obj is MonikerKind other && Equals(other); + + public override int GetHashCode() => _value != null ? _value.GetHashCode() : 0; + + public static bool operator ==(MonikerKind left, MonikerKind right) => left.Equals(right); + + public static bool operator !=(MonikerKind left, MonikerKind right) => !left.Equals(right); + } +} \ No newline at end of file diff --git a/src/Protocol/Models/Proposals/MonikerParams.cs b/src/Protocol/Models/Proposals/MonikerParams.cs new file mode 100644 index 000000000..2f17c512c --- /dev/null +++ b/src/Protocol/Models/Proposals/MonikerParams.cs @@ -0,0 +1,26 @@ +using System; +using MediatR; +using OmniSharp.Extensions.JsonRpc; +using OmniSharp.Extensions.LanguageServer.Protocol.Serialization; + +namespace OmniSharp.Extensions.LanguageServer.Protocol.Models.Proposals +{ + /// + /// Language Server Index Format (LSIF) introduced the concept of symbol monikers to help associate symbols across different indexes. + /// This request adds capability for LSP server implementations to provide the same symbol moniker information given a text document + /// position. Clients can utilize this method to get the moniker at the current location in a file user is editing and do further + /// code navigation queries in other services that rely on LSIF indexes and link symbols together. + /// + /// The `textDocument/moniker` request is sent from the client to the server to get the symbol monikers for a given text document + /// position. An array of Moniker types is returned as response to indicate possible monikers at the given location. If no monikers + /// can be calculated, an empty array or `null` should be returned. + /// + [Obsolete(Constants.Proposal)] + [Method(TextDocumentNames.Moniker, Direction.ClientToServer)] + public class MonikerParams : WorkDoneTextDocumentPositionParams, IPartialItemsRequest, Moniker> + { + /// + [Optional] + public ProgressToken? PartialResultToken { get; set; } + } +} diff --git a/src/Protocol/Models/Proposals/MonikerRegistrationOptions.cs b/src/Protocol/Models/Proposals/MonikerRegistrationOptions.cs new file mode 100644 index 000000000..795876d3d --- /dev/null +++ b/src/Protocol/Models/Proposals/MonikerRegistrationOptions.cs @@ -0,0 +1,32 @@ +using System; +using OmniSharp.Extensions.JsonRpc; +using OmniSharp.Extensions.LanguageServer.Protocol.Server.Capabilities; + +namespace OmniSharp.Extensions.LanguageServer.Protocol.Models.Proposals +{ + [Obsolete(Constants.Proposal)] + public class MonikerRegistrationOptions : WorkDoneTextDocumentRegistrationOptions + { + /// + /// Code Lens options. + /// + public class StaticOptions : WorkDoneProgressOptions + { + } + + class MonikerRegistrationOptionsOptionsConverter : RegistrationOptionsConverterBase + { + private readonly IHandlersManager _handlersManager; + + public MonikerRegistrationOptionsOptionsConverter(IHandlersManager handlersManager) : base(nameof(ServerCapabilities.MonikerProvider)) + { + _handlersManager = handlersManager; + } + public override StaticOptions Convert(MonikerRegistrationOptions source) + { + return new StaticOptions { + }; + } + } + } +} diff --git a/src/Protocol/Models/Proposals/UniquenessLevel.cs b/src/Protocol/Models/Proposals/UniquenessLevel.cs new file mode 100644 index 000000000..ff2b50b7a --- /dev/null +++ b/src/Protocol/Models/Proposals/UniquenessLevel.cs @@ -0,0 +1,61 @@ +using System; +using System.Diagnostics; +using Newtonsoft.Json; +using OmniSharp.Extensions.LanguageServer.Protocol.Serialization.Converters; + +namespace OmniSharp.Extensions.LanguageServer.Protocol.Models.Proposals +{ + /// + /// A set of predefined code action kinds + /// + [DebuggerDisplay("{" + nameof(_value) + "}")] + [JsonConverter(typeof(EnumLikeStringConverter))] + public readonly struct UniquenessLevel : IEquatable, IEnumLikeString + { + /// + /// The moniker is only unique inside a document + /// + public static readonly UniquenessLevel Document = new UniquenessLevel("document"); + + /// + /// The moniker is unique inside a project for which a dump got created + /// + public static readonly UniquenessLevel Project = new UniquenessLevel("project"); + + /// + /// The moniker is unique inside the group to which a project belongs + /// + public static readonly UniquenessLevel Group = new UniquenessLevel("group"); + + /// + /// The moniker is unique inside the moniker scheme. + /// + public static readonly UniquenessLevel Scheme = new UniquenessLevel("scheme"); + + /// + /// The moniker is globally unique + /// + public static readonly UniquenessLevel Global = new UniquenessLevel("global"); + + private readonly string? _value; + + public UniquenessLevel(string kind) => _value = kind; + + public static implicit operator UniquenessLevel(string kind) => new UniquenessLevel(kind); + + public static implicit operator string(UniquenessLevel kind) => kind._value ?? string.Empty; + + /// + public override string ToString() => _value ?? string.Empty; + + public bool Equals(UniquenessLevel other) => _value == other._value; + + public override bool Equals(object obj) => obj is UniquenessLevel other && Equals(other); + + public override int GetHashCode() => _value != null ? _value.GetHashCode() : 0; + + public static bool operator ==(UniquenessLevel left, UniquenessLevel right) => left.Equals(right); + + public static bool operator !=(UniquenessLevel left, UniquenessLevel right) => !left.Equals(right); + } +} \ No newline at end of file diff --git a/src/Protocol/Models/RangeOrPlaceholderRange.cs b/src/Protocol/Models/RangeOrPlaceholderRange.cs index 2576a5486..9cebc79f3 100644 --- a/src/Protocol/Models/RangeOrPlaceholderRange.cs +++ b/src/Protocol/Models/RangeOrPlaceholderRange.cs @@ -6,21 +6,25 @@ namespace OmniSharp.Extensions.LanguageServer.Protocol.Models [JsonConverter(typeof(RangeOrPlaceholderRangeConverter))] public class RangeOrPlaceholderRange { + private RenameDefaultBehavior? _renameDefaultBehavior; private Range? _range; private PlaceholderRange? _placeholderRange; public RangeOrPlaceholderRange(Range value) { _range = value; - _placeholderRange = default; } public RangeOrPlaceholderRange(PlaceholderRange value) { - _range = default; _placeholderRange = value; } + public RangeOrPlaceholderRange(RenameDefaultBehavior renameDefaultBehavior) + { + _renameDefaultBehavior = renameDefaultBehavior; + } + public bool IsPlaceholderRange => _placeholderRange != null; public PlaceholderRange? PlaceholderRange @@ -28,6 +32,7 @@ public PlaceholderRange? PlaceholderRange get => _placeholderRange; set { _placeholderRange = value; + _renameDefaultBehavior = default; _range = null; } } @@ -39,15 +44,29 @@ public Range? Range get => _range; set { _placeholderRange = default; + _renameDefaultBehavior = default; _range = value; } } + public bool IsDefaultBehavior => _renameDefaultBehavior is not null; + + public RenameDefaultBehavior? DefaultBehavior + { + get => _renameDefaultBehavior; + set { + _placeholderRange = default; + _renameDefaultBehavior = value; + _range = default; + } + } + public object? RawValue { get { if (IsPlaceholderRange) return PlaceholderRange; if (IsRange) return Range; + if (IsDefaultBehavior) return DefaultBehavior; return default; } } diff --git a/src/Protocol/Models/Registration.cs b/src/Protocol/Models/Registration.cs index 4a25dc945..ab9da9785 100644 --- a/src/Protocol/Models/Registration.cs +++ b/src/Protocol/Models/Registration.cs @@ -26,7 +26,7 @@ public class Registration [Optional] public object? RegisterOptions { get; set; } - private string DebuggerDisplay => $"[{Id}] {Method}"; + private string DebuggerDisplay => $"[{Id}] {( RegisterOptions is ITextDocumentRegistrationOptions td ? $"{td.DocumentSelector}" : string.Empty )} {Method}"; /// public override string ToString() => DebuggerDisplay; diff --git a/src/Protocol/Models/RenameDefaultBehavior.cs b/src/Protocol/Models/RenameDefaultBehavior.cs new file mode 100644 index 000000000..20e7b5956 --- /dev/null +++ b/src/Protocol/Models/RenameDefaultBehavior.cs @@ -0,0 +1,7 @@ +namespace OmniSharp.Extensions.LanguageServer.Protocol.Models +{ + public class RenameDefaultBehavior + { + public bool DefaultBehavior { get; set; } + } +} diff --git a/src/Protocol/Progress/PartialItemsRequestProgressObservable.cs b/src/Protocol/Progress/PartialItemsRequestProgressObservable.cs index 0e532ff2d..e3cc16cd1 100644 --- a/src/Protocol/Progress/PartialItemsRequestProgressObservable.cs +++ b/src/Protocol/Progress/PartialItemsRequestProgressObservable.cs @@ -17,7 +17,7 @@ internal class PartialItemsRequestProgressObservable : IRequestP where TResult : IEnumerable { private readonly ISerializer _serializer; - private readonly ISubject> _dataSubject; + private readonly ReplaySubject> _dataSubject; private readonly CompositeDisposable _disposable; private readonly Task _task; @@ -55,13 +55,29 @@ Action disposal public ProgressToken ProgressToken { get; } public Type ParamsType { get; } = typeof(TItem); - public void OnCompleted() => _dataSubject.OnCompleted(); + public void OnCompleted() + { + if (_dataSubject.IsDisposed) return; + _dataSubject.OnCompleted(); + } - public void OnError(Exception error) => _dataSubject.OnError(error); + public void OnError(Exception error) + { + if (_dataSubject.IsDisposed) return; + _dataSubject.OnError(error); + } - public void OnNext(JToken value) => _dataSubject.OnNext(value.ToObject(_serializer.JsonSerializer)); + public void OnNext(JToken value) + { + if (_dataSubject.IsDisposed) return; + _dataSubject.OnNext(value.ToObject(_serializer.JsonSerializer)); + } - public void Dispose() => _disposable.Dispose(); + public void Dispose() + { + if (_disposable.IsDisposed) return; + _disposable.Dispose(); + } public IDisposable Subscribe(IObserver> observer) => _disposable.IsDisposed ? Disposable.Empty : _dataSubject.Subscribe(observer); diff --git a/src/Protocol/Progress/ProgressObservable.cs b/src/Protocol/Progress/ProgressObservable.cs index d36866947..db3847f6b 100644 --- a/src/Protocol/Progress/ProgressObservable.cs +++ b/src/Protocol/Progress/ProgressObservable.cs @@ -30,13 +30,29 @@ public ProgressObservable(ProgressToken token, Func factory, Action d public Type ParamsType { get; } = typeof(T); public void Next(JToken value) => OnNext(value); - void IObserver.OnCompleted() => _dataSubject.OnCompleted(); + void IObserver.OnCompleted() + { + if (_dataSubject.IsDisposed) return; + _dataSubject.OnCompleted(); + } - void IObserver.OnError(Exception error) => _dataSubject.OnError(error); + void IObserver.OnError(Exception error) + { + if (_dataSubject.IsDisposed) return; + _dataSubject.OnError(error); + } - public void OnNext(JToken value) => _dataSubject.OnNext(value); + public void OnNext(JToken value) + { + if (_dataSubject.IsDisposed) return; + _dataSubject.OnNext(value); + } - public void Dispose() => _disposable.Dispose(); + public void Dispose() + { + if (_disposable.IsDisposed) return; + _disposable.Dispose(); + } public IDisposable Subscribe(IObserver observer) => _disposable.IsDisposed ? Disposable.Empty : _dataSubject.Select(_factory).Subscribe(observer); } diff --git a/src/Protocol/Progress/RequestProgressObservable.cs b/src/Protocol/Progress/RequestProgressObservable.cs index d9872ae61..c87335af3 100644 --- a/src/Protocol/Progress/RequestProgressObservable.cs +++ b/src/Protocol/Progress/RequestProgressObservable.cs @@ -15,7 +15,7 @@ namespace OmniSharp.Extensions.LanguageServer.Protocol.Progress internal class RequestProgressObservable : IRequestProgressObservable, IObserver { private readonly ISerializer _serializer; - private readonly ISubject _dataSubject; + private readonly ReplaySubject _dataSubject; private readonly CompositeDisposable _disposable; private readonly Task _task; @@ -46,13 +46,29 @@ Action disposal public ProgressToken ProgressToken { get; } public Type ParamsType { get; } = typeof(TItem); - public void OnCompleted() => _dataSubject.OnCompleted(); + public void OnCompleted() + { + if (_dataSubject.IsDisposed) return; + _dataSubject.OnCompleted(); + } - public void OnError(Exception error) => _dataSubject.OnError(error); + public void OnError(Exception error) + { + if (_dataSubject.IsDisposed) return; + _dataSubject.OnError(error); + } - public void OnNext(JToken value) => _dataSubject.OnNext(value.ToObject(_serializer.JsonSerializer)); + public void OnNext(JToken value) + { + if (_dataSubject.IsDisposed) return; + _dataSubject.OnNext(value.ToObject(_serializer.JsonSerializer)); + } - public void Dispose() => _disposable.Dispose(); + public void Dispose() + { + if (_disposable.IsDisposed) return; + _disposable.Dispose(); + } public IDisposable Subscribe(IObserver observer) => _disposable.IsDisposed ? Disposable.Empty : _dataSubject.Subscribe(observer); diff --git a/src/Protocol/Serialization/Converters/RangeOrPlaceholderRangeConverter.cs b/src/Protocol/Serialization/Converters/RangeOrPlaceholderRangeConverter.cs index f0d1e2504..8bf2c8a48 100644 --- a/src/Protocol/Serialization/Converters/RangeOrPlaceholderRangeConverter.cs +++ b/src/Protocol/Serialization/Converters/RangeOrPlaceholderRangeConverter.cs @@ -18,6 +18,10 @@ public override void WriteJson(JsonWriter writer, RangeOrPlaceholderRange value, { serializer.Serialize(writer, value.PlaceholderRange); } + else if (value.IsDefaultBehavior) + { + serializer.Serialize(writer, value.DefaultBehavior); + } else { writer.WriteNull(); @@ -33,7 +37,13 @@ public override RangeOrPlaceholderRange ReadJson( var obj = JToken.ReadFrom(reader) as JObject; return obj.ContainsKey("placeholder") ? new RangeOrPlaceholderRange(obj.ToObject()) - : new RangeOrPlaceholderRange(obj.ToObject()); + : obj.ContainsKey("defaultBehavior") + ? new RangeOrPlaceholderRange( + obj.ToObject() + ) + : new RangeOrPlaceholderRange( + obj.ToObject() + ); } return null; diff --git a/src/Protocol/Serialization/Serializer.cs b/src/Protocol/Serialization/Serializer.cs index 8f35ac49f..911bb73f3 100644 --- a/src/Protocol/Serialization/Serializer.cs +++ b/src/Protocol/Serialization/Serializer.cs @@ -111,10 +111,11 @@ protected override void AddOrReplaceConverters(ICollection conver ReplaceConverter(converters, new RangeOrPlaceholderRangeConverter()); ReplaceConverter(converters, new EnumLikeStringConverter()); ReplaceConverter(converters, new DocumentUriConverter()); - ReplaceConverter(converters, new AggregateConverter()); - ReplaceConverter(converters, new AggregateConverter()); - ReplaceConverter(converters, new AggregateConverter()); - ReplaceConverter(converters, new AggregateConverter()); +// ReplaceConverter(converters, new AggregateConverter()); +// ReplaceConverter(converters, new AggregateConverter()); +// ReplaceConverter(converters, new AggregateConverter()); +// ReplaceConverter(converters, new AggregateConverter()); +// ReplaceConverter(converters, new AggregateConverter()); ReplaceConverter(converters, new AggregateCompletionListConverter()); base.AddOrReplaceConverters(converters); } diff --git a/src/Protocol/Server/Capabilities/ServerCapabilities.cs b/src/Protocol/Server/Capabilities/ServerCapabilities.cs index ab89f572c..428eb9851 100644 --- a/src/Protocol/Server/Capabilities/ServerCapabilities.cs +++ b/src/Protocol/Server/Capabilities/ServerCapabilities.cs @@ -155,6 +155,12 @@ public class ServerCapabilities : CapabilitiesBase [Optional] [Obsolete(Constants.Proposal)] public SemanticTokensRegistrationOptions.StaticOptions? SemanticTokensProvider { get; set; } + /// + /// The server provides Call Hierarchy support. + /// + [Optional] + [Obsolete(Constants.Proposal)] + public MonikerRegistrationOptions.StaticOptions? MonikerProvider { get; set; } /// /// The server provides folding provider support. diff --git a/src/Protocol/TextDocumentNames.cs b/src/Protocol/TextDocumentNames.cs index 1505ca792..a2e7c7168 100644 --- a/src/Protocol/TextDocumentNames.cs +++ b/src/Protocol/TextDocumentNames.cs @@ -5,6 +5,7 @@ namespace OmniSharp.Extensions.LanguageServer.Protocol public static class TextDocumentNames { public const string CodeAction = "textDocument/codeAction"; + public const string CodeActionResolve = "codeAction/resolve"; public const string CodeLens = "textDocument/codeLens"; public const string CodeLensResolve = "codeLens/resolve"; public const string ColorPresentation = "textDocument/colorPresentation"; @@ -42,5 +43,6 @@ public static class TextDocumentNames [Obsolete(Constants.Proposal)] public const string SemanticTokensFull = "textDocument/semanticTokens/full"; [Obsolete(Constants.Proposal)] public const string SemanticTokensFullDelta = "textDocument/semanticTokens/full/delta"; [Obsolete(Constants.Proposal)] public const string SemanticTokensRange = "textDocument/semanticTokens/range"; + [Obsolete(Constants.Proposal)] public const string Moniker = "textDocument/moniker"; } } diff --git a/src/Server/LanguageServerWorkspaceFolderManager.cs b/src/Server/LanguageServerWorkspaceFolderManager.cs index f68bcc55b..40591d68e 100644 --- a/src/Server/LanguageServerWorkspaceFolderManager.cs +++ b/src/Server/LanguageServerWorkspaceFolderManager.cs @@ -37,16 +37,22 @@ Task IRequestHandler.Handle(DidChan foreach (var folder in request.Event.Added) { _workspaceFolders.AddOrUpdate(folder.Uri, folder, (a, b) => folder); + if (_workspaceFoldersChangedSubject.IsDisposed) continue; _workspaceFoldersChangedSubject.OnNext(new WorkspaceFolderChange(WorkspaceFolderEvent.Add, folder)); } foreach (var folder in request.Event.Removed) { _workspaceFolders.TryRemove(folder.Uri, out _); + if (_workspaceFoldersChangedSubject.IsDisposed) continue; _workspaceFoldersChangedSubject.OnNext(new WorkspaceFolderChange(WorkspaceFolderEvent.Remove, folder)); } - _workspaceFoldersSubject.OnNext(_workspaceFolders.Values); + if (_workspaceFoldersSubject.IsDisposed) + { + _workspaceFoldersSubject.OnNext(_workspaceFolders.Values); + } + return Unit.Task; } @@ -87,6 +93,7 @@ public IObservable Refresh() => Observable.Create Refresh() => Observable.Create Changed => _workspaceFoldersChangedSubject.AsObservable(); - public IObservable> WorkspaceFolders => _workspaceFoldersSubject.AsObservable(); + public IObservable> WorkspaceFolders => _workspaceFoldersSubject.IsDisposed ? Observable.Empty>() : _workspaceFoldersSubject.AsObservable(); public IEnumerable CurrentWorkspaceFolders => _workspaceFolders.Values; public bool IsSupported { get; private set; } @@ -105,8 +112,8 @@ public IObservable Refresh() => Observable.Create GetHandler(IEnumerable(expected); + deresult.Should().BeEquivalentTo(model); + } + } +} diff --git a/test/Lsp.Tests/Capabilities/Server/CodeActionOptionsTests_$SimpleTest.json b/test/Lsp.Tests/Capabilities/Server/CodeActionOptionsTests_$SimpleTest.json new file mode 100644 index 000000000..2f8f4e005 --- /dev/null +++ b/test/Lsp.Tests/Capabilities/Server/CodeActionOptionsTests_$SimpleTest.json @@ -0,0 +1,8 @@ +{ + "codeActionKinds": [ + "quickfix", + "refactor" + ], + "resolveProvider": true, + "workDoneProgress": false +} diff --git a/test/Lsp.Tests/FoundationTests.cs b/test/Lsp.Tests/FoundationTests.cs index 5f503e37c..962269adb 100644 --- a/test/Lsp.Tests/FoundationTests.cs +++ b/test/Lsp.Tests/FoundationTests.cs @@ -684,6 +684,7 @@ public class TypeHandlerExtensionData : TheoryData - x.Method == TextDocumentNames.Completion && SelectorMatches(x, z => z.HasLanguage && z.Language == "csharp") + await TestHelper.DelayUntil( + () => client.RegistrationManager.CurrentRegistrations, + registrations => registrations + .Any(x => x.Method == TextDocumentNames.Completion && SelectorMatches(x, z => z.HasLanguage && z.Language == "csharp")), + CancellationToken ); + + client.RegistrationManager.CurrentRegistrations.Should() + .Contain( + x => + x.Method == TextDocumentNames.Completion && + SelectorMatches(x, z => z.HasLanguage && z.Language == "csharp") + ); } [Fact] public async Task Should_Register_Dynamically_While_Server_Is_Running() { var (client, server) = await Initialize(new ConfigureClient().Configure, new ConfigureServer().Configure); + await client.RegistrationManager.Registrations.Take(1); client.ServerSettings.Capabilities.CompletionProvider.Should().BeNull(); using var _ = server.Register( @@ -60,31 +70,15 @@ public async Task Should_Register_Dynamically_While_Server_Is_Running() ) ); - await WaitForRegistrationUpdate(client); - client.RegistrationManager.CurrentRegistrations.Should().Contain( - x => - x.Method == TextDocumentNames.Completion && SelectorMatches(x, z => z.HasLanguage && z.Language == "vb") - ); - } - - [Fact] - public async Task Should_Register_Links_Dynamically_While_Server_Is_Running() - { - var (client, server) = await Initialize(new ConfigureClient().Configure, new ConfigureServer().Configure); - client.ServerSettings.Capabilities.CompletionProvider.Should().BeNull(); - - using var _ = server.Register( - x => x - .OnCompletion( - (@params, token) => Task.FromResult(new CompletionList()), - new CompletionRegistrationOptions { - DocumentSelector = DocumentSelector.ForLanguage("vb") - } - ) + await TestHelper.DelayUntil( + () => client.RegistrationManager.CurrentRegistrations, + registrations => registrations + .Any( + registration => SelectorMatches(registration, z => z.HasLanguage && z.Language == "vb") + ), + CancellationToken ); - await WaitForRegistrationUpdate(client); - await WaitForRegistrationUpdate(client); client.RegistrationManager.CurrentRegistrations.Should().Contain( x => x.Method == TextDocumentNames.Completion && SelectorMatches(x, z => z.HasLanguage && z.Language == "vb") @@ -95,10 +89,14 @@ public async Task Should_Register_Links_Dynamically_While_Server_Is_Running() public async Task Should_Gather_Linked_Registrations() { var (client, server) = await Initialize(new ConfigureClient().Configure, new ConfigureServer().Configure); + await client.RegistrationManager.Registrations.Take(1); using var _ = server.Register(r => r.AddHandlerLink(TextDocumentNames.SemanticTokensFull, "@/" + TextDocumentNames.SemanticTokensFull)); - await WaitForRegistrationUpdate(client); - await WaitForRegistrationUpdate(client); + await TestHelper.DelayUntil( + () => client.RegistrationManager.CurrentRegistrations, + registrations => registrations.Any(registration => registration.Method.StartsWith("@/")), + CancellationToken + ); client.RegistrationManager.CurrentRegistrations.Should().Contain(x => x.Method == TextDocumentNames.SemanticTokensFull); client.RegistrationManager.CurrentRegistrations.Should().NotContain(x => x.Method == TextDocumentNames.SemanticTokensFullDelta); @@ -110,25 +108,31 @@ public async Task Should_Gather_Linked_Registrations() public async Task Should_Unregister_Dynamically_While_Server_Is_Running() { var (client, server) = await Initialize(new ConfigureClient().Configure, new ConfigureServer().Configure); + await client.RegistrationManager.Registrations.Take(1); client.ServerSettings.Capabilities.CompletionProvider.Should().BeNull(); - using (var disposable = server.Register( + var disposable = server.Register( x => x.OnCompletion( (@params, token) => Task.FromResult(new CompletionList()), new CompletionRegistrationOptions { DocumentSelector = DocumentSelector.ForLanguage("vb") } ) - )) - { - await WaitForRegistrationUpdate(client); - disposable.Dispose(); - await WaitForRegistrationUpdate(client); - await TestHelper.DelayUntil( - () => client.RegistrationManager.CurrentRegistrations, z => !SelectorMatches(z, x => x.HasLanguage && x.Language == "vb"), CancellationToken - ); - } + ); + await TestHelper.DelayUntil( + () => client.RegistrationManager.CurrentRegistrations, + registrations => registrations.Any(registration => SelectorMatches(registration, x => x.HasLanguage && x.Language == "vb")), + CancellationToken + ); + disposable.Dispose(); + + + await TestHelper.DelayUntil( + () => client.RegistrationManager.CurrentRegistrations, + registrations => !registrations.Any(registration => SelectorMatches(registration, x => x.HasLanguage && x.Language == "vb")), + CancellationToken + ); client.RegistrationManager.CurrentRegistrations.Should().NotContain( x => @@ -140,25 +144,15 @@ await TestHelper.DelayUntil( private bool SelectorMatches(object options, Func documentFilter) { + if (options is Registration registration) + return SelectorMatches(registration.RegisterOptions, documentFilter); if (options is ITextDocumentRegistrationOptions tdro) - return tdro.DocumentSelector.Any(documentFilter); + return tdro.DocumentSelector?.Any(documentFilter) == true; if (options is DocumentSelector selector) return selector.Any(documentFilter); return false; } - private async Task WaitForRegistrationUpdate(ILanguageClient client) - { - await client.RegistrationManager.Registrations - .Throttle(TestOptions.WaitTime) - .Take(1) - .ToTask(CancellationToken); - await client.RegistrationManager.Registrations - .Throttle(TestOptions.WaitTime) - .Take(1) - .ToTask(CancellationToken); - } - public DynamicRegistrationTests(ITestOutputHelper testOutputHelper) : base( new JsonRpcTestOptions().ConfigureForXUnit(testOutputHelper) ) @@ -198,7 +192,12 @@ public async Task Should_Gather_Static_Registrations() } ); - await WaitForRegistrationUpdate(client); + await TestHelper.DelayUntil( + () => client.RegistrationManager.CurrentRegistrations, + registrations => registrations.Any(r => r.Method == TextDocumentNames.SemanticTokensFull), + CancellationToken + ); + client.RegistrationManager.CurrentRegistrations.Should().Contain(x => x.Method == TextDocumentNames.SemanticTokensFull); } @@ -268,14 +267,6 @@ public async Task Should_Register_Static_When_Dynamic_Is_Disabled() client.RegistrationManager.CurrentRegistrations.Should().NotContain(x => x.Method == TextDocumentNames.SemanticTokensFull); } - - private Task WaitForRegistrationUpdate(ILanguageClient client) - { - return client.RegistrationManager.Registrations - .Throttle(TestOptions.WaitTime) - .Take(1) - .ToTask(CancellationToken); - } } diff --git a/test/Lsp.Tests/Integration/MonikerTests.cs b/test/Lsp.Tests/Integration/MonikerTests.cs new file mode 100644 index 000000000..6add1ab28 --- /dev/null +++ b/test/Lsp.Tests/Integration/MonikerTests.cs @@ -0,0 +1,65 @@ +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using FluentAssertions; +using NSubstitute; +using OmniSharp.Extensions.JsonRpc.Testing; +using OmniSharp.Extensions.LanguageProtocol.Testing; +using OmniSharp.Extensions.LanguageServer.Client; +using OmniSharp.Extensions.LanguageServer.Protocol.Document.Proposals; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; +using OmniSharp.Extensions.LanguageServer.Protocol.Models.Proposals; +using OmniSharp.Extensions.LanguageServer.Server; +using Serilog.Events; +using Xunit; +using Xunit.Abstractions; + +namespace Lsp.Tests.Integration +{ + public class MonikerTests : LanguageProtocolTestBase + { + private readonly Func>> _request; + public MonikerTests(ITestOutputHelper outputHelper) : base(new JsonRpcTestOptions().ConfigureForXUnit(outputHelper, LogEventLevel.Verbose)) + { + _request = Substitute.For>>>(); + } + + [Fact] + public async Task Should_Get_Monikers() + { + _request.Invoke(Arg.Any(), Arg.Any()) + .Returns(new Container( + new[] { + new Moniker() { + Identifier = "abcd", + Kind = MonikerKind.Export, + Scheme = "http", + Unique = UniquenessLevel.Document + }, + } + )); + + + var (client, server) = await Initialize(ClientOptionsAction, ServerOptionsAction); + + var result = await client.RequestMoniker(new MonikerParams(), CancellationToken); + + result.Should().HaveCount(1); + result.Should().Match(z => z.Any(x => x.Kind == MonikerKind.Export)); + } + + private void ServerOptionsAction(LanguageServerOptions obj) + { + obj.OnMoniker( + _request, new MonikerRegistrationOptions() { + DocumentSelector = DocumentSelector.ForLanguage("csharp"), + } + ); + } + + private void ClientOptionsAction(LanguageClientOptions obj) + { + } + } +} diff --git a/test/Lsp.Tests/Integration/PartialItemTests.cs b/test/Lsp.Tests/Integration/PartialItemTests.cs index 2d914d8e5..11808c881 100644 --- a/test/Lsp.Tests/Integration/PartialItemTests.cs +++ b/test/Lsp.Tests/Integration/PartialItemTests.cs @@ -2,6 +2,7 @@ using System.Collections.Immutable; using System.Linq; using System.Reactive.Linq; +using System.Reactive.Threading.Tasks; using System.Threading.Tasks; using FluentAssertions; using Lsp.Tests.Integration.Fixtures; @@ -42,9 +43,17 @@ public async Task Should_Behave_Like_A_Task() [FactWithSkipOn(SkipOnPlatform.All)] public async Task Should_Behave_Like_An_Observable() { - var items = new List(); - await Client.TextDocument.RequestSemanticTokens(new SemanticTokensParams() { TextDocument = new TextDocumentIdentifier(@"c:\test.cs") }, CancellationToken) - .ForEachAsync(x => items.Add(x)); + var items = await Client.TextDocument + .RequestSemanticTokens( + new SemanticTokensParams() { TextDocument = new TextDocumentIdentifier(@"c:\test.cs") }, CancellationToken + ) + .Aggregate( + new List(), (acc, v) => { + acc.Add(v); + return acc; + } + ) + .ToTask(CancellationToken); items.Should().HaveCount(3); items.Select(z => z.Data.Length).Should().ContainInOrder(1, 2, 3); diff --git a/test/Lsp.Tests/Integration/PartialItemsTests.cs b/test/Lsp.Tests/Integration/PartialItemsTests.cs index 4d049dc28..a29d0b9ad 100644 --- a/test/Lsp.Tests/Integration/PartialItemsTests.cs +++ b/test/Lsp.Tests/Integration/PartialItemsTests.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Reactive; using System.Reactive.Linq; +using System.Reactive.Threading.Tasks; using System.Threading; using System.Threading.Tasks; using FluentAssertions; @@ -44,12 +45,19 @@ public async Task Should_Behave_Like_A_Task() [FactWithSkipOn(SkipOnPlatform.All)] 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 = await Client.TextDocument + .RequestCodeLens( + new CodeLensParams { + TextDocument = new TextDocumentIdentifier(@"c:\test.cs") + }, CancellationToken + ) + .Aggregate( + new List(), (acc, v) => { + acc.AddRange(v); + return acc; + } + ) + .ToTask(CancellationToken); items.Should().HaveCount(3); items.Select(z => z.Command.Name).Should().ContainInOrder("CodeLens 1", "CodeLens 2", "CodeLens 3"); diff --git a/test/Lsp.Tests/Integration/RenameTests.cs b/test/Lsp.Tests/Integration/RenameTests.cs index d7e4061dd..0fe55902d 100644 --- a/test/Lsp.Tests/Integration/RenameTests.cs +++ b/test/Lsp.Tests/Integration/RenameTests.cs @@ -9,6 +9,7 @@ using OmniSharp.Extensions.LanguageServer.Client; using OmniSharp.Extensions.LanguageServer.Protocol; using OmniSharp.Extensions.LanguageServer.Protocol.Document; +using OmniSharp.Extensions.LanguageServer.Protocol.Document.Proposals; using OmniSharp.Extensions.LanguageServer.Protocol.Models; using OmniSharp.Extensions.LanguageServer.Server; using Serilog.Events; @@ -158,6 +159,34 @@ public async Task Should_Handle_Prepare_Rename_With_PlaceholderRange() result.IsPlaceholderRange.Should().BeTrue(); } + [Fact] + public async Task Should_Handle_Prepare_Rename_With_DefaultBehavior() + { + _prepareRename.Invoke(Arg.Any(), Arg.Any()) + .Returns( + call => { + var pos = call.Arg().Position; + return new RangeOrPlaceholderRange( + new RenameDefaultBehavior() { + DefaultBehavior = true + } + ); + } + ); + + var (client, server) = await Initialize(ClientOptionsAction, ServerOptionsAction); + + var result = await client.PrepareRename( + new PrepareRenameParams() { + Position = ( 1, 1 ), + TextDocument = DocumentUri.FromFileSystemPath("/abcd/file.cs") + }, + CancellationToken + ); + + result.IsDefaultBehavior.Should().BeTrue(); + } + private void ServerOptionsAction(LanguageServerOptions obj) { obj.OnPrepareRename( diff --git a/test/Lsp.Tests/Integration/TypedCodeActionTests.cs b/test/Lsp.Tests/Integration/TypedCodeActionTests.cs new file mode 100644 index 000000000..6d90fa9d1 --- /dev/null +++ b/test/Lsp.Tests/Integration/TypedCodeActionTests.cs @@ -0,0 +1,671 @@ +using System; +using System.Linq; +using System.Reactive.Linq; +using System.Reactive.Threading.Tasks; +using System.Threading.Tasks; +using FluentAssertions; +using Newtonsoft.Json.Linq; +using NSubstitute; +using OmniSharp.Extensions.JsonRpc.Testing; +using OmniSharp.Extensions.LanguageProtocol.Testing; +using OmniSharp.Extensions.LanguageServer.Protocol; +using OmniSharp.Extensions.LanguageServer.Protocol.Document; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; +using Serilog.Events; +using TestingUtils; +using Xunit; +using Xunit.Abstractions; + +namespace Lsp.Tests.Integration +{ + public class TypedCodeActionTests : LanguageProtocolTestBase + { + public TypedCodeActionTests(ITestOutputHelper outputHelper) : base(new JsonRpcTestOptions().ConfigureForXUnit(outputHelper, LogEventLevel.Verbose)) + { + } + + [Fact] + public async Task Should_Aggregate_With_All_Related_Handlers() + { + var (client, server) = await Initialize( + options => { }, options => { + var identifier = Substitute.For(); + identifier.GetTextDocumentAttributes(Arg.Any()).Returns( + call => new TextDocumentAttributes(call.ArgAt(0), "file", "csharp") + ); + options.AddTextDocumentIdentifier(identifier); + + options.OnCodeAction( + codeActionParams => { + return Task.FromResult( + new CodeActionContainer( + new CodeAction { + Title = "data-a", + Kind = CodeActionKind.QuickFix, + Command = new Command { + Name = "data-a", + Arguments = JArray.FromObject(new object[] { 1, "2", false }) + }, + Data = new Data { + Child = new Nested { + Date = DateTimeOffset.MinValue + }, + Id = Guid.NewGuid(), + Name = "name" + } + } + ) + ); + }, + codeAction => { + codeAction.Command.Name = "resolved-a"; + return Task.FromResult(codeAction); + }, + new CodeActionRegistrationOptions { + DocumentSelector = DocumentSelector.ForPattern("**/*.cs") + } + ); + + options.OnCodeAction( + codeActionParams => { + return Task.FromResult( + new CodeActionContainer( + new CodeAction { + Title = "nested-b", + Kind = CodeActionKind.QuickFix, + Command = new Command { + Name = "nested-b", + Arguments = JArray.FromObject(new object[] { 1, "2", false }) + }, + Data = new Nested { + Date = DateTimeOffset.Now + } + } + ) + ); + }, + codeAction => { + codeAction.Command.Name = "resolved-b"; + return Task.FromResult(codeAction); + }, + new CodeActionRegistrationOptions { + DocumentSelector = DocumentSelector.ForPattern("**/*.cs") + } + ); + + options.OnCodeAction( + codeActionParams => { + return Task.FromResult( + new CommandOrCodeActionContainer( + new CodeAction { + Title = "no-data-c", + Kind = CodeActionKind.QuickFix, + Command = new Command { + Name = "no-data-c", + Arguments = JArray.FromObject(new object[] { 1, "2", false }) + } + } + ) + ); + }, + codeAction => { + codeAction.Command.Name = "resolved-c"; + return Task.FromResult(codeAction); + }, + new CodeActionRegistrationOptions { + DocumentSelector = DocumentSelector.ForPattern("**/*.cs") + } + ); + + options.OnCodeAction( + codeActionParams => { + return Task.FromResult( + new CommandOrCodeActionContainer( + new CodeAction { + Title = "not-included", + Kind = CodeActionKind.QuickFix, + Command = new Command { + Name = "not-included", + Arguments = JArray.FromObject(new object[] { 1, "2", false }) + } + } + ) + ); + }, + codeAction => { + codeAction.Command.Name = "resolved-d"; + return Task.FromResult(codeAction); + }, + new CodeActionRegistrationOptions { + DocumentSelector = DocumentSelector.ForLanguage("vb") + } + ); + } + ); + + var codeAction = await client.RequestCodeAction( + new CodeActionParams { + TextDocument = new TextDocumentIdentifier("/some/path/file.cs"), + } + ); + + var actions = codeAction.GetCodeActions().ToArray(); + + var responses = await Task.WhenAll(actions.Select(z => client.ResolveCodeAction(z))); + 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"); + actions.Length.Should().Be(3); + } + + [Fact] + public async Task Should_Resolve_With_Data_Capability() + { + var (client, server) = await Initialize( + options => { }, options => { + options.OnCodeAction( + (codeActionParams, capability, token) => { + return Task.FromResult( + new CodeActionContainer( + new CodeAction { + Title = "name", + Kind = CodeActionKind.QuickFix, + Command = new Command { + Name = "execute-a", + Arguments = JArray.FromObject(new object[] { 1, "2", false }) + }, + Data = new Data { + Child = new Nested { + Date = DateTimeOffset.MinValue + }, + Id = Guid.NewGuid(), + Name = "name" + } + } + ) + ); + }, + (codeAction, capability, token) => { + codeAction.Data.Id.Should().NotBeEmpty(); + codeAction.Data.Child.Should().NotBeNull(); + codeAction.Data.Name.Should().Be("name"); + codeAction.Command.Name = "resolved"; + return Task.FromResult(codeAction); + }, + new CodeActionRegistrationOptions() + ); + } + ); + + var items = await client.RequestCodeAction(new CodeActionParams()); + + var item = items.Single(); + + item = await client.ResolveCodeAction(item.CodeAction!); + item.CodeAction!.Command.Name.Should().Be("resolved"); + } + + [FactWithSkipOn(SkipOnPlatform.Mac)] + public async Task Should_Resolve_With_Partial_Data_Capability() + { + var (client, server) = await Initialize( + options => { }, options => { + options.OnCodeAction( + (codeActionParams, observer, capability, token) => { + var a = new CodeActionContainer( + new CodeAction { + Title = "name", + Kind = CodeActionKind.QuickFix, + Command = new Command { + Name = "execute-a", + Arguments = JArray.FromObject(new object[] { 1, "2", false }) + }, + Data = new Data { + Child = new Nested { + Date = DateTimeOffset.MinValue + }, + Id = Guid.NewGuid(), + Name = "name" + } + } + ); + + observer.OnNext(a); + observer.OnCompleted(); + }, + (codeAction, capability, token) => { + codeAction.Data.Id.Should().NotBeEmpty(); + codeAction.Data.Child.Should().NotBeNull(); + codeAction.Data.Name.Should().Be("name"); + codeAction.Command.Name = "resolved"; + return Task.FromResult(codeAction); + }, + new CodeActionRegistrationOptions() + ); + } + ); + + var item = await client.RequestCodeAction(new CodeActionParams()).SelectMany(z => z).Take(1).ToTask(CancellationToken); + + item = await client.ResolveCodeAction(item.CodeAction!); + item.CodeAction!.Command.Name.Should().Be("resolved"); + } + + [Fact] + public async Task Should_Resolve_With_Data_CancellationToken() + { + var (client, server) = await Initialize( + options => { }, options => { + options.OnCodeAction( + (codeActionParams, token) => { + return Task.FromResult( + new CodeActionContainer( + new CodeAction { + Title = "execute-a", + Kind = CodeActionKind.QuickFix, + Command = new Command { + Name = "execute-a", + Arguments = JArray.FromObject(new object[] { 1, "2", false }) + }, + Data = new Data { + Child = new Nested { + Date = DateTimeOffset.MinValue + }, + Id = Guid.NewGuid(), + Name = "name" + } + } + ) + ); + }, + (codeAction, token) => { + codeAction.Data.Id.Should().NotBeEmpty(); + codeAction.Data.Child.Should().NotBeNull(); + codeAction.Data.Name.Should().Be("name"); + codeAction.Command.Name = "resolved"; + return Task.FromResult(codeAction); + }, + new CodeActionRegistrationOptions() + ); + } + ); + + var items = await client.RequestCodeAction(new CodeActionParams()); + + var item = items.Single(); + + item = await client.ResolveCodeAction(item.CodeAction!); + item.CodeAction!.Command.Name.Should().Be("resolved"); + } + + [Fact] + public async Task Should_Resolve_With_Partial_Data_CancellationToken() + { + var (client, server) = await Initialize( + options => { }, options => { + options.OnCodeAction( + (codeActionParams, observer, token) => { + var a = new CodeActionContainer( + new CodeAction { + Title = "execute-a", + Kind = CodeActionKind.QuickFix, + Command = new Command { + Name = "execute-a", + Arguments = JArray.FromObject(new object[] { 1, "2", false }) + }, + Data = new Data { + Child = new Nested { + Date = DateTimeOffset.MinValue + }, + Id = Guid.NewGuid(), + Name = "name" + } + } + ); + + observer.OnNext(a); + observer.OnCompleted(); + }, + (codeAction, token) => { + codeAction.Data.Id.Should().NotBeEmpty(); + codeAction.Data.Child.Should().NotBeNull(); + codeAction.Data.Name.Should().Be("name"); + codeAction.Command.Name = "resolved"; + return Task.FromResult(codeAction); + }, + new CodeActionRegistrationOptions() + ); + } + ); + + var item = await client.RequestCodeAction(new CodeActionParams()).SelectMany(z => z).Take(1).ToTask(CancellationToken); + + item = await client.ResolveCodeAction(item.CodeAction!); + item.CodeAction!.Command.Name.Should().Be("resolved"); + } + + [Fact] + public async Task Should_Resolve_With_Data() + { + var (client, server) = await Initialize( + options => { }, options => { + options.OnCodeAction( + codeActionParams => { + return Task.FromResult( + new CodeActionContainer( + new CodeAction { + Title = "execute-a", + Kind = CodeActionKind.QuickFix, + Command = new Command { + Name = "execute-a", + Arguments = JArray.FromObject(new object[] { 1, "2", false }) + }, + Data = new Data { + Child = new Nested { + Date = DateTimeOffset.MinValue + }, + Id = Guid.NewGuid(), + Name = "name" + } + } + ) + ); + }, + codeAction => { + codeAction.Data.Id.Should().NotBeEmpty(); + codeAction.Data.Child.Should().NotBeNull(); + codeAction.Data.Name.Should().Be("name"); + codeAction.Command.Name = "resolved"; + return Task.FromResult(codeAction); + }, + new CodeActionRegistrationOptions() + ); + } + ); + + var items = await client.RequestCodeAction(new CodeActionParams()); + + var item = items.Single(); + + item = await client.ResolveCodeAction(item.CodeAction!); + item.CodeAction!.Command.Name.Should().Be("resolved"); + } + + [Fact] + public async Task Should_Resolve_With_Partial_Data() + { + var (client, server) = await Initialize( + options => { }, options => { + options.OnCodeAction( + (codeActionParams, observer) => { + var a = new CodeActionContainer( + new CodeAction { + Title = "execute-a", + Kind = CodeActionKind.QuickFix, + Command = new Command { + Name = "execute-a", + Arguments = JArray.FromObject(new object[] { 1, "2", false }) + }, + Data = new Data { + Child = new Nested { + Date = DateTimeOffset.MinValue + }, + Id = Guid.NewGuid(), + Name = "name" + } + } + ); + + observer.OnNext(a); + observer.OnCompleted(); + }, + codeAction => { + codeAction.Data.Id.Should().NotBeEmpty(); + codeAction.Data.Child.Should().NotBeNull(); + codeAction.Data.Name.Should().Be("name"); + codeAction.Command.Name = "resolved"; + return Task.FromResult(codeAction); + }, + new CodeActionRegistrationOptions() + ); + } + ); + + var item = await client.RequestCodeAction(new CodeActionParams()).SelectMany(z => z).Take(1).ToTask(CancellationToken); + + item = await client.ResolveCodeAction(item.CodeAction!); + item.CodeAction!.Command.Name.Should().Be("resolved"); + } + + + [Fact] + public async Task Should_Resolve_Capability() + { + var (client, server) = await Initialize( + options => { }, options => { + options.OnCodeAction( + (codeActionParams, capability, token) => { + return Task.FromResult( + new CommandOrCodeActionContainer( + new CodeAction { + Title = "execute-a", + Kind = CodeActionKind.QuickFix, + Command = new Command { + Name = "execute-a", + Arguments = JArray.FromObject(new object[] { 1, "2", false }) + } + } + ) + ); + }, + (codeAction, capability, token) => { + codeAction.Command.Name = "resolved"; + return Task.FromResult(codeAction); + }, + new CodeActionRegistrationOptions() + ); + } + ); + + var items = await client.RequestCodeAction(new CodeActionParams()); + + var item = items.Single(); + + item = await client.ResolveCodeAction(item.CodeAction!); + item.CodeAction!.Command.Name.Should().Be("resolved"); + } + + [Fact] + public async Task Should_Resolve_Partial_Capability() + { + var (client, server) = await Initialize( + options => { }, options => { + options.OnCodeAction( + (codeActionParams, observer, capability, token) => { + var a = new CommandOrCodeActionContainer( + new CodeAction { + Title = "execute-a", + Kind = CodeActionKind.QuickFix, + Command = new Command { + Name = "execute-a", + Arguments = JArray.FromObject(new object[] { 1, "2", false }) + }, + } + ); + + observer.OnNext(a.GetCodeActions()); + observer.OnCompleted(); + }, + (codeAction, capability, token) => { + codeAction.Command.Name = "resolved"; + return Task.FromResult(codeAction); + }, + new CodeActionRegistrationOptions() + ); + } + ); + + var item = await client.RequestCodeAction(new CodeActionParams()).SelectMany(z => z).Take(1).ToTask(CancellationToken); + + item = await client.ResolveCodeAction(item.CodeAction!); + item.CodeAction!.Command.Name.Should().Be("resolved"); + } + + [Fact] + public async Task Should_Resolve_CancellationToken() + { + var (client, server) = await Initialize( + options => { }, options => { + options.OnCodeAction( + (codeActionParams, token) => { + return Task.FromResult( + new CommandOrCodeActionContainer( + new CodeAction { + Title = "execute-a", + Kind = CodeActionKind.QuickFix, + Command = new Command { + Name = "execute-a", + Arguments = JArray.FromObject(new object[] { 1, "2", false }) + }, + } + ) + ); + }, + (codeAction, token) => { + codeAction.Command.Name = "resolved"; + return Task.FromResult(codeAction); + }, + new CodeActionRegistrationOptions() + ); + } + ); + + var items = await client.RequestCodeAction(new CodeActionParams()); + + var item = items.Single(); + + item = await client.ResolveCodeAction(item.CodeAction!); + item.CodeAction!.Command.Name.Should().Be("resolved"); + } + + [Fact] + public async Task Should_Resolve_Partial_CancellationToken() + { + var (client, server) = await Initialize( + options => { }, options => { + options.OnCodeAction( + (codeActionParams, observer, token) => { + var a = new CommandOrCodeActionContainer( + new CodeAction { + Title = "execute-a", + Kind = CodeActionKind.QuickFix, + Command = new Command { + Name = "execute-a", + Arguments = JArray.FromObject(new object[] { 1, "2", false }) + }, + } + ); + + observer.OnNext(a.GetCodeActions()); + observer.OnCompleted(); + }, + (codeAction, token) => { + codeAction.Command.Name = "resolved"; + return Task.FromResult(codeAction); + }, + new CodeActionRegistrationOptions() + ); + } + ); + + var item = await client.RequestCodeAction(new CodeActionParams()).SelectMany(z => z).Take(1).ToTask(CancellationToken); + + item = await client.ResolveCodeAction(item.CodeAction!); + item.CodeAction!.Command.Name.Should().Be("resolved"); + } + + [Fact] + public async Task Should_Resolve() + { + var (client, server) = await Initialize( + options => { }, options => { + options.OnCodeAction( + codeActionParams => { + return Task.FromResult( + new CommandOrCodeActionContainer( + new CodeAction { + Title = "execute-a", + Kind = CodeActionKind.QuickFix, + Command = new Command { + Name = "execute-a", + Arguments = JArray.FromObject(new object[] { 1, "2", false }) + }, + } + ) + ); + }, + codeAction => { + codeAction.Command.Name = "resolved"; + return Task.FromResult(codeAction); + }, + new CodeActionRegistrationOptions() + ); + } + ); + + var items = await client.RequestCodeAction(new CodeActionParams()); + + var item = items.Single(); + + item = await client.ResolveCodeAction(item.CodeAction!); + item.CodeAction!.Command.Name.Should().Be("resolved"); + } + + [Fact] + public async Task Should_Resolve_Partial() + { + var (client, server) = await Initialize( + options => { }, options => { + options.OnCodeAction( + (codeActionParams, observer) => { + var a = new CommandOrCodeActionContainer( + new CodeAction { + Title = "execute-a", + Kind = CodeActionKind.QuickFix, + Command = new Command { + Name = "execute-a", + Arguments = JArray.FromObject(new object[] { 1, "2", false }) + }, + } + ); + + observer.OnNext(a.GetCodeActions()); + observer.OnCompleted(); + }, + codeAction => { + codeAction.Command.Name = "resolved"; + return Task.FromResult(codeAction); + }, + new CodeActionRegistrationOptions() + ); + } + ); + + var item = await client.RequestCodeAction(new CodeActionParams()).SelectMany(z => z).Take(1).ToTask(CancellationToken); + + item = await client.ResolveCodeAction(item.CodeAction!); + item.CodeAction!.Command.Name.Should().Be("resolved"); + } + + private class Data : HandlerIdentity + { + public string Name { get; set; } + public Guid Id { get; set; } + public Nested Child { get; set; } + } + + private class Nested : HandlerIdentity + { + public DateTimeOffset Date { get; set; } + } + } +} diff --git a/test/Lsp.Tests/Models/CodeActionRegistrationOptionsTests_$SimpleTest.json b/test/Lsp.Tests/Models/CodeActionRegistrationOptionsTests_$SimpleTest.json index 6c45cfe3d..332e1a12c 100644 --- a/test/Lsp.Tests/Models/CodeActionRegistrationOptionsTests_$SimpleTest.json +++ b/test/Lsp.Tests/Models/CodeActionRegistrationOptionsTests_$SimpleTest.json @@ -8,6 +8,7 @@ "source", "source.organizeImports" ], + "resolveProvider": false, "workDoneProgress": false, "documentSelector": [ { diff --git a/test/Lsp.Tests/Models/InitializeParamsTests_$SimpleTest.json b/test/Lsp.Tests/Models/InitializeParamsTests_$SimpleTest.json index ac0d4e00d..ef3bfc564 100644 --- a/test/Lsp.Tests/Models/InitializeParamsTests_$SimpleTest.json +++ b/test/Lsp.Tests/Models/InitializeParamsTests_$SimpleTest.json @@ -70,6 +70,8 @@ }, "codeAction": { "isPreferredSupport": false, + "disabledSupport": false, + "dataSupport": false, "dynamicRegistration": true }, "codeLens": { @@ -81,6 +83,7 @@ }, "rename": { "prepareSupport": false, + "prepareSupportDefaultBehavior": false, "dynamicRegistration": true }, "foldingRange": { diff --git a/test/Lsp.Tests/Models/InitializeResultTests_$BooleanOrTest.json b/test/Lsp.Tests/Models/InitializeResultTests_$BooleanOrTest.json index 02f92f6c6..0a93cb342 100644 --- a/test/Lsp.Tests/Models/InitializeResultTests_$BooleanOrTest.json +++ b/test/Lsp.Tests/Models/InitializeResultTests_$BooleanOrTest.json @@ -4,6 +4,7 @@ "codeActionKinds": [ "quickfix" ], + "resolveProvider": false, "workDoneProgress": false }, "renameProvider": {