diff --git a/src/Protocol/Client/Capabilities/SynchronizationCapability.cs b/src/Protocol/Client/Capabilities/SynchronizationCapability.cs index 9f1cb988e..d84f0f3bb 100644 --- a/src/Protocol/Client/Capabilities/SynchronizationCapability.cs +++ b/src/Protocol/Client/Capabilities/SynchronizationCapability.cs @@ -5,7 +5,13 @@ namespace OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities { - public class SynchronizationCapability : DynamicCapability, ConnectedCapability, ConnectedCapability, ConnectedCapability, ConnectedCapability, ConnectedCapability, ConnectedCapability + public class SynchronizationCapability : DynamicCapability, + ConnectedCapability, + ConnectedCapability, + ConnectedCapability, + ConnectedCapability, + ConnectedCapability, + ConnectedCapability { /// /// The client supports sending will save notifications. diff --git a/src/Protocol/Server/Capabilities/TextDocumentSyncOptions.cs b/src/Protocol/Server/Capabilities/TextDocumentSyncOptions.cs index 3bd76486d..8860c3619 100644 --- a/src/Protocol/Server/Capabilities/TextDocumentSyncOptions.cs +++ b/src/Protocol/Server/Capabilities/TextDocumentSyncOptions.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using Newtonsoft.Json; @@ -38,17 +39,24 @@ public class TextDocumentSyncOptions : ITextDocumentSyncOptions public static TextDocumentSyncOptions Of(IEnumerable options) { - return new TextDocumentSyncOptions() { - OpenClose = options.Any(z => z.OpenClose), - Change = options + var change = TextDocumentSyncKind.None; + if (options.Any(x => x.Change != TextDocumentSyncKind.None)) + { + change = options .Where(x => x.Change != TextDocumentSyncKind.None) - .Min(z => z.Change), + .Min(z => z.Change); + } + return new TextDocumentSyncOptions() + { + OpenClose = options.Any(z => z.OpenClose), + Change = change, WillSave = options.Any(z => z.WillSave), WillSaveWaitUntil = options.Any(z => z.WillSaveWaitUntil), - Save = new SaveOptions() { + Save = new SaveOptions() + { IncludeText = options.Any(z => z.Save?.IncludeText == true) } - }; + }; } } } diff --git a/src/Server/ClientCapabilityProvider.cs b/src/Server/ClientCapabilityProvider.cs index 59fd1b233..b4a8aebcc 100644 --- a/src/Server/ClientCapabilityProvider.cs +++ b/src/Server/ClientCapabilityProvider.cs @@ -30,7 +30,7 @@ public bool HasStaticHandler(Supports capability) .Where(x => x.GetTypeInfo().IsGenericType && x.GetTypeInfo().GetGenericTypeDefinition() == typeof(ConnectedCapability<>)) .Select(x => x.GetTypeInfo().GetGenericArguments()[0].GetTypeInfo()); - return handlerTypes.All(_collection.ContainsHandler); + return handlerTypes.Any(_collection.ContainsHandler); } public IOptionsGetter GetStaticOptions(Supports capability) diff --git a/src/Server/LanguageServer.cs b/src/Server/LanguageServer.cs index 389f746ac..21b904c50 100644 --- a/src/Server/LanguageServer.cs +++ b/src/Server/LanguageServer.cs @@ -313,7 +313,8 @@ private IDisposable RegisterHandlers(LspHandlerDescriptorDisposable handlerDispo { var registrations = handlerDisposable.Descriptors .Where(d => d.AllowsDynamicRegistration) - .Select(d => new Registration() { + .Select(d => new Registration() + { Id = d.Id.ToString(), Method = d.Method, RegisterOptions = d.RegistrationOptions @@ -433,13 +434,20 @@ async Task IRequestHandler if (ccp.HasStaticHandler(textDocumentCapabilities.Synchronization)) { - var textDocumentSyncKind = _collection.ContainsHandler(typeof(IDidChangeTextDocumentHandler)) - ? _collection + var textDocumentSyncKind = TextDocumentSyncKind.None; + if (_collection.ContainsHandler(typeof(IDidChangeTextDocumentHandler))) + { + var kinds = _collection .Select(x => x.Handler) .OfType() - .Where(x => x.GetRegistrationOptions()?.SyncKind != TextDocumentSyncKind.None) - .Min(z => z.GetRegistrationOptions()?.SyncKind) - : TextDocumentSyncKind.None; + .Select(x => x.GetRegistrationOptions()?.SyncKind ?? TextDocumentSyncKind.None) + .Where(x => x != TextDocumentSyncKind.None) + .ToArray(); + if (kinds.Any()) + { + textDocumentSyncKind = kinds.Min(z => z); + } + } if (_clientVersion == ClientVersion.Lsp2) { @@ -449,7 +457,7 @@ async Task IRequestHandler { serverCapabilities.TextDocumentSync = new TextDocumentSyncOptions() { - Change = textDocumentSyncKind ?? TextDocumentSyncKind.None, + Change = TextDocumentSyncKind.None, OpenClose = _collection.ContainsHandler(typeof(IDidOpenTextDocumentHandler)) || _collection.ContainsHandler(typeof(IDidCloseTextDocumentHandler)), Save = _collection.ContainsHandler(typeof(IDidSaveTextDocumentHandler)) ? new SaveOptions() { IncludeText = true /* TODO: Make configurable */ } : diff --git a/test/Lsp.Tests/ClientCapabilityProviderTests.cs b/test/Lsp.Tests/ClientCapabilityProviderTests.cs index bf02d87f3..dedb54a67 100644 --- a/test/Lsp.Tests/ClientCapabilityProviderTests.cs +++ b/test/Lsp.Tests/ClientCapabilityProviderTests.cs @@ -36,7 +36,8 @@ public void Should_AllowSupportedCapabilities(IJsonRpcHandler handler, object in public static IEnumerable AllowSupportedCapabilities() { - return GetItems(Capabilities, type => { + return GetItems(Capabilities, type => + { var handlerTypes = GetHandlerTypes(type); var handler = Substitute.For(handlerTypes.ToArray(), new object[0]); return new[] { handler, Activator.CreateInstance(typeof(Supports<>).MakeGenericType(type), true, Activator.CreateInstance(type)) }; @@ -56,7 +57,8 @@ public void Should_AllowUnsupportedCapabilities(IJsonRpcHandler handler, object public static IEnumerable AllowUnsupportedCapabilities() { - return GetItems(Capabilities, type => { + return GetItems(Capabilities, type => + { var handlerTypes = GetHandlerTypes(type); var handler = Substitute.For(handlerTypes.ToArray(), new object[0]); return new[] { handler, Activator.CreateInstance(typeof(Supports<>).MakeGenericType(type), false) }; @@ -104,7 +106,8 @@ public void Should_AllowNullSupportedCapabilities(IJsonRpcHandler handler, objec public static IEnumerable AllowNullSupportsCapabilities() { - return GetItems(Capabilities, type => { + return GetItems(Capabilities, type => + { var handlerTypes = GetHandlerTypes(type); var handler = Substitute.For(handlerTypes.ToArray(), new object[0]); return new[] { handler, Activator.CreateInstance(typeof(Supports<>).MakeGenericType(type), true) }; @@ -125,7 +128,8 @@ public void Should_DisallowDynamicSupportedCapabilities(IJsonRpcHandler handler, public static IEnumerable DisallowDynamicSupportsCapabilities() { - return GetItems(Capabilities, type => { + return GetItems(Capabilities, type => + { var handlerTypes = GetHandlerTypes(type); var handler = Substitute.For(handlerTypes.ToArray(), new object[0]); var capability = Activator.CreateInstance(type); @@ -145,12 +149,16 @@ public void Should_Handle_Mixed_Capabilities() var collection = new HandlerCollection(SupportedCapabilitiesFixture.AlwaysTrue, new TextDocumentIdentifiers()) { textDocumentSyncHandler, codeActionHandler, definitionHandler, typeDefinitionHandler }; var provider = new ClientCapabilityProvider(collection); - var capabilities = new ClientCapabilities() { - TextDocument = new TextDocumentClientCapabilities() { - CodeAction = new Supports(true, new CodeActionCapability() { + var capabilities = new ClientCapabilities() + { + TextDocument = new TextDocumentClientCapabilities() + { + CodeAction = new Supports(true, new CodeActionCapability() + { DynamicRegistration = false, }), - TypeDefinition = new Supports(true, new TypeDefinitionCapability() { + TypeDefinition = new Supports(true, new TypeDefinitionCapability() + { DynamicRegistration = true, }) } @@ -161,6 +169,57 @@ public void Should_Handle_Mixed_Capabilities() provider.HasStaticHandler(capabilities.TextDocument.TypeDefinition).Should().BeFalse(); } + [Fact] + public void GH162_TextDocumentSync_Should_Work_Without_WillSave_Or_WillSaveWaitUntil() + { + var textDocumentSyncHandler = TextDocumentSyncHandlerExtensions.With(DocumentSelector.ForPattern("**/*.cs"), "csharp"); + + var collection = new HandlerCollection(SupportedCapabilitiesFixture.AlwaysTrue, new TextDocumentIdentifiers()) { textDocumentSyncHandler }; + var provider = new ClientCapabilityProvider(collection); + var capabilities = new ClientCapabilities() + { + TextDocument = new TextDocumentClientCapabilities() + { + Synchronization = new SynchronizationCapability() + { + DidSave = true, + DynamicRegistration = false, + WillSave = true, + WillSaveWaitUntil = true + }, + } + }; + + provider.HasStaticHandler(capabilities.TextDocument.Synchronization).Should().BeTrue(); + } + + [Fact] + public void GH162_TextDocumentSync_Should_Work_With_WillSave_Or_WillSaveWaitUntil() + { + var textDocumentSyncHandler = TextDocumentSyncHandlerExtensions.With(DocumentSelector.ForPattern("**/*.cs"), "csharp"); + var willSaveTextDocumentHandler = Substitute.For(); + var willSaveWaitUntilTextDocumentHandler = Substitute.For(); + var didSaveTextDocumentHandler = Substitute.For(); + + var collection = new HandlerCollection(SupportedCapabilitiesFixture.AlwaysTrue, new TextDocumentIdentifiers()) { textDocumentSyncHandler, willSaveTextDocumentHandler, willSaveWaitUntilTextDocumentHandler, didSaveTextDocumentHandler }; + var provider = new ClientCapabilityProvider(collection); + var capabilities = new ClientCapabilities() + { + TextDocument = new TextDocumentClientCapabilities() + { + Synchronization = new SynchronizationCapability() + { + DidSave = true, + DynamicRegistration = false, + WillSave = true, + WillSaveWaitUntil = true + }, + } + }; + + provider.HasStaticHandler(capabilities.TextDocument.Synchronization).Should().BeTrue(); + } + private static bool HasHandler(ClientCapabilityProvider provider, object instance) { return (bool)typeof(ClientCapabilityProviderTests).GetTypeInfo() diff --git a/test/Lsp.Tests/Models/InitializeResultTests.cs b/test/Lsp.Tests/Models/InitializeResultTests.cs index c2731eac1..b2dc94f54 100644 --- a/test/Lsp.Tests/Models/InitializeResultTests.cs +++ b/test/Lsp.Tests/Models/InitializeResultTests.cs @@ -17,29 +17,36 @@ public class InitializeResultTests [Theory, JsonFixture] public void SimpleTest(string expected) { - var model = new InitializeResult() { - Capabilities = new ServerCapabilities() { + var model = new InitializeResult() + { + Capabilities = new ServerCapabilities() + { CodeActionProvider = true, - CodeLensProvider = new CodeLensOptions() { + CodeLensProvider = new CodeLensOptions() + { ResolveProvider = true, }, - CompletionProvider = new CompletionOptions() { + CompletionProvider = new CompletionOptions() + { ResolveProvider = true, TriggerCharacters = new[] { "a", "b", "c" } }, DefinitionProvider = true, DocumentFormattingProvider = true, DocumentHighlightProvider = true, - DocumentLinkProvider = new DocumentLinkOptions() { + DocumentLinkProvider = new DocumentLinkOptions() + { ResolveProvider = true }, - DocumentOnTypeFormattingProvider = new DocumentOnTypeFormattingOptions() { + DocumentOnTypeFormattingProvider = new DocumentOnTypeFormattingOptions() + { FirstTriggerCharacter = ".", MoreTriggerCharacter = new[] { ";", " " } }, DocumentRangeFormattingProvider = true, DocumentSymbolProvider = true, - ExecuteCommandProvider = new ExecuteCommandOptions() { + ExecuteCommandProvider = new ExecuteCommandOptions() + { Commands = new string[] { "command1", "command2" } }, Experimental = new Dictionary() { @@ -48,13 +55,16 @@ public void SimpleTest(string expected) HoverProvider = true, ReferencesProvider = true, RenameProvider = true, - SignatureHelpProvider = new SignatureHelpOptions() { + SignatureHelpProvider = new SignatureHelpOptions() + { TriggerCharacters = new[] { ";", " " } }, - TextDocumentSync = new TextDocumentSync(new TextDocumentSyncOptions() { + TextDocumentSync = new TextDocumentSync(new TextDocumentSyncOptions() + { Change = TextDocumentSyncKind.Full, OpenClose = true, - Save = new SaveOptions() { + Save = new SaveOptions() + { IncludeText = true }, WillSave = true, @@ -74,33 +84,42 @@ public void SimpleTest(string expected) [Theory, JsonFixture] public void BooleanOrTest(string expected) { - var model = new InitializeResult() { - Capabilities = new ServerCapabilities { - CodeActionProvider = new CodeActionOptions { - CodeActionKinds = new [] { + var model = new InitializeResult() + { + Capabilities = new ServerCapabilities + { + CodeActionProvider = new CodeActionOptions + { + CodeActionKinds = new[] { CodeActionKind.QuickFix } }, - ColorProvider = new ColorOptions { + ColorProvider = new ColorOptions + { DocumentSelector = DocumentSelector.ForPattern("**/*.foo"), Id = "foo" }, - DeclarationProvider = new DeclarationOptions { + DeclarationProvider = new DeclarationOptions + { DocumentSelector = DocumentSelector.ForPattern("**/*.foo"), Id = "foo" }, - FoldingRangeProvider = new FoldingRangeOptions { + FoldingRangeProvider = new FoldingRangeOptions + { DocumentSelector = DocumentSelector.ForPattern("**/*.foo"), Id = "foo" }, - ImplementationProvider = new ImplementationOptions { + ImplementationProvider = new ImplementationOptions + { DocumentSelector = DocumentSelector.ForPattern("**/*.foo"), Id = "foo" }, - RenameProvider = new RenameOptions { + RenameProvider = new RenameOptions + { PrepareProvider = true }, - TypeDefinitionProvider = new TypeDefinitionOptions { + TypeDefinitionProvider = new TypeDefinitionOptions + { DocumentSelector = DocumentSelector.ForPattern("**/*.foo"), Id = "foo" }