diff --git a/src/language-server/Contextive.LanguageServer.Tests/CompletionTests.fs b/src/language-server/Contextive.LanguageServer.Tests/CompletionTests.fs index 7fd5c41c..686ea0f9 100644 --- a/src/language-server/Contextive.LanguageServer.Tests/CompletionTests.fs +++ b/src/language-server/Contextive.LanguageServer.Tests/CompletionTests.fs @@ -30,7 +30,7 @@ let completionTests = $"Given {fileName} contextive, in document {text} at position {position} respond with expected completion list " { let config = [ Workspace.optionsBuilder <| Path.Combine("fixtures", "completion_tests") - ConfigurationSection.contextivePathOptionsBuilder $"{fileName}.yml" ] + ConfigurationSection.contextivePathBuilder $"{fileName}.yml" ] use! client = TestClient(config) |> init diff --git a/src/language-server/Contextive.LanguageServer.Tests/ConfigurationTests.fs b/src/language-server/Contextive.LanguageServer.Tests/ConfigurationTests.fs index c7f484a8..b08e996f 100644 --- a/src/language-server/Contextive.LanguageServer.Tests/ConfigurationTests.fs +++ b/src/language-server/Contextive.LanguageServer.Tests/ConfigurationTests.fs @@ -13,7 +13,7 @@ let definitionsTests = [ testAsync "Can receive configuration value" { let config = [ Workspace.optionsBuilder <| Path.Combine("fixtures", "completion_tests") - ConfigurationSection.contextivePathOptionsBuilder "one.yml" ] + ConfigurationSection.contextivePathBuilder "one.yml" ] use! client = TestClient(config) |> init @@ -29,12 +29,12 @@ let definitionsTests = let config = [ Workspace.optionsBuilder <| Path.Combine("fixtures", "completion_tests") - ConfigurationSection.contextivePathLoaderOptionsBuilder pathLoader ] + ConfigurationSection.contextivePathLoaderBuilder pathLoader ] use! client = TestClient(config) |> init path <- "two.yml" - ConfigurationSection.didChange client path + ConfigurationSection.didChangePath client path let! labels = Completion.getCompletionLabels client @@ -42,4 +42,19 @@ let definitionsTests = } + testAsync "Can handle client that doesn't support didChangeConfiguration" { + let mutable path = "one.yml" + + let config = + [ Workspace.optionsBuilder <| Path.Combine("fixtures", "completion_tests") + ConfigurationSection.contextivePathBuilder path ] + + use! client = TestClient(config) |> init + + let! labels = Completion.getCompletionLabels client + + test <@ (labels, Fixtures.One.expectedCompletionLabels) ||> Seq.compareWith compare = 0 @> + + } + ] diff --git a/src/language-server/Contextive.LanguageServer.Tests/DefinitionsTests.fs b/src/language-server/Contextive.LanguageServer.Tests/DefinitionsTests.fs index b1a3f262..62454a90 100644 --- a/src/language-server/Contextive.LanguageServer.Tests/DefinitionsTests.fs +++ b/src/language-server/Contextive.LanguageServer.Tests/DefinitionsTests.fs @@ -163,7 +163,7 @@ let definitionsTests = let config = [ Workspace.optionsBuilder <| Path.Combine("fixtures", "completion_tests") - ConfigurationSection.contextivePathLoaderOptionsBuilder pathLoader ] + ConfigurationSection.contextivePathLoaderBuilder pathLoader ] use! client = TestClient(config) |> init @@ -171,13 +171,13 @@ let definitionsTests = test <@ (termsWhenValidAtStart, Fixtures.One.expectedCompletionLabels) ||> compareList = 0 @> path <- $"{fileName}.yml" - ConfigurationSection.didChange client path + ConfigurationSection.didChangePath client path let! termsWhenInvalid = Completion.getCompletionLabels client test <@ Seq.length termsWhenInvalid = 0 @> path <- validPath - ConfigurationSection.didChange client path + ConfigurationSection.didChangePath client path let! termsWhenValidAtEnd = Completion.getCompletionLabels client test <@ (termsWhenValidAtEnd, Fixtures.One.expectedCompletionLabels) ||> compareList = 0 @> diff --git a/src/language-server/Contextive.LanguageServer.Tests/Helpers/ConfigurationSection.fs b/src/language-server/Contextive.LanguageServer.Tests/Helpers/ConfigurationSection.fs index 6382a5f1..5901bff5 100644 --- a/src/language-server/Contextive.LanguageServer.Tests/Helpers/ConfigurationSection.fs +++ b/src/language-server/Contextive.LanguageServer.Tests/Helpers/ConfigurationSection.fs @@ -9,7 +9,7 @@ open OmniSharp.Extensions.LanguageServer.Protocol.Client open OmniSharp.Extensions.LanguageServer.Protocol.Workspace open System.Threading.Tasks -let jTokenFromMap values = +let private jTokenFromMap values = let configValue = JObject() values @@ -38,27 +38,50 @@ let private createHandler section configValuesLoader = else Task.FromResult(configSectionResultFromMap <| Map []) -let optionsBuilder section configValuesLoader (b: LanguageClientOptions) = +let configurationHandlerBuilder section configValuesLoader (b: LanguageClientOptions) = let handler = createHandler section configValuesLoader - + b.OnConfiguration(handler) |> ignore b - .WithCapability(Capabilities.DidChangeConfigurationCapability(DynamicRegistration = true)) - .OnConfiguration(handler) + +let private didChangeConfigurationBuilder (b: LanguageClientOptions) = + b.WithCapability(Capabilities.DidChangeConfigurationCapability(DynamicRegistration = true)) |> ignore b -type PathLoader = unit -> obj - -let mapLoader (pathLoader: PathLoader) () = Map[("path", pathLoader ())] - -let contextivePathLoaderOptionsBuilder (pathLoader: PathLoader) = - optionsBuilder "contextive" <| mapLoader pathLoader - -let contextivePathOptionsBuilder path = - contextivePathLoaderOptionsBuilder (fun () -> path) - -let didChange (client: ILanguageClient) path = +type ValueLoader = unit -> obj + +let private mapLoader (key: string) (valueLoader: ValueLoader) () = Map[(key, valueLoader ())] + +let private mapPathLoader = mapLoader "path" + +let private contextivePathConfigurationHandlerBuilder (pathLoader: ValueLoader) = + configurationHandlerBuilder "contextive" <| mapPathLoader pathLoader + +/// +/// Use when you have a setting value that needs to change during the test. +/// The test client will support `workspace/configuration` AND `workspace/didChangeConfiguration`. +/// When a `workspace/configuration` request is received, the `pathLoader` function will be invoked to get the current value. +/// To trigger a `workspace/didChangeConfiguration` notification, use didChangePath. +/// +let contextivePathLoaderBuilder (pathLoader: ValueLoader) = + contextivePathConfigurationHandlerBuilder pathLoader + >> didChangeConfigurationBuilder + +/// +/// Use when you have a fixed setting value. +/// The test client will ONLY support `workspace/configuration` and will always supply this fixed value +/// Using didChangePath will have no impact as the server will not support the `workspace/didChangeConfiguration` notification +/// +let contextivePathBuilder path = + contextivePathConfigurationHandlerBuilder (fun () -> path) + +/// +/// Trigger a `workspace/didChangeConfiguration` notification with the new value. +/// This will only work if the TestClient was created with the contextivePathLoaderBuilder. +/// Ensure this method is invoked with the same path value as will be returned from the `pathLoader` registered with the TestClient. +/// +let didChangePath (client: ILanguageClient) path = let setting = jTokenFromMap <| Map[("path", path)] let configSection = jTokenFromMap <| Map[("contextive", setting)] diff --git a/src/language-server/Contextive.LanguageServer.Tests/HoverTests.fs b/src/language-server/Contextive.LanguageServer.Tests/HoverTests.fs index dfe5dcf8..47f8eb83 100644 --- a/src/language-server/Contextive.LanguageServer.Tests/HoverTests.fs +++ b/src/language-server/Contextive.LanguageServer.Tests/HoverTests.fs @@ -37,7 +37,7 @@ let hoverTests = $"Given definitions file '{fileName}' and file contents '{multiLineToSingleLine text}', server responds to hover request at Position {position} with '{expectedTerm}'" { let config = [ Workspace.optionsBuilder <| Path.Combine("fixtures", "completion_tests") - ConfigurationSection.contextivePathOptionsBuilder $"{fileName}.yml" ] + ConfigurationSection.contextivePathBuilder $"{fileName}.yml" ] use! client = TestClient(config) |> init @@ -89,7 +89,7 @@ let hoverTests = let config = [ Workspace.optionsBuilder <| Path.Combine("fixtures", "completion_tests") - ConfigurationSection.contextivePathOptionsBuilder $"{fileName}.yml" ] + ConfigurationSection.contextivePathBuilder $"{fileName}.yml" ] use! client = TestClient(config) |> init @@ -129,7 +129,7 @@ let hoverTests = let config = [ Workspace.optionsBuilder <| Path.Combine("fixtures", "completion_tests") - ConfigurationSection.contextivePathOptionsBuilder $"{fileName}.yml" ] + ConfigurationSection.contextivePathBuilder $"{fileName}.yml" ] use! client = TestClient(config) |> init diff --git a/src/language-server/Contextive.LanguageServer.Tests/InitializationTests.fs b/src/language-server/Contextive.LanguageServer.Tests/InitializationTests.fs index 825f623f..2474a636 100644 --- a/src/language-server/Contextive.LanguageServer.Tests/InitializationTests.fs +++ b/src/language-server/Contextive.LanguageServer.Tests/InitializationTests.fs @@ -28,12 +28,11 @@ let initializationTests = let config = [ Workspace.optionsBuilder "" - ConfigurationSection.contextivePathOptionsBuilder pathValue ] + ConfigurationSection.contextivePathBuilder pathValue ] let! (client, reply) = TestClient(config) |> initAndWaitForReply test <@ client.ClientSettings.Capabilities.Workspace.Configuration.IsSupported @> - test <@ client.ClientSettings.Capabilities.Workspace.DidChangeConfiguration.IsSupported @> test <@ (defaultArg reply "").Contains(pathValue) @> } @@ -41,25 +40,22 @@ let initializationTests = testAsync "Server loads contextive file from absolute location without workspace" { let pathValue = Guid.NewGuid().ToString() - let config = - [ ConfigurationSection.contextivePathOptionsBuilder $"/tmp/{pathValue}" ] + let config = [ ConfigurationSection.contextivePathBuilder $"/tmp/{pathValue}" ] let! (client, reply) = TestClient(config) |> initAndWaitForReply test <@ client.ClientSettings.Capabilities.Workspace.Configuration.IsSupported @> - test <@ client.ClientSettings.Capabilities.Workspace.DidChangeConfiguration.IsSupported @> test <@ (defaultArg reply "").Contains(pathValue) @> } testAsync "Server does NOT load contextive file from relative location without workspace" { let pathValue = Guid.NewGuid().ToString() - let config = [ ConfigurationSection.contextivePathOptionsBuilder pathValue ] + let config = [ ConfigurationSection.contextivePathBuilder pathValue ] let! (client, reply) = TestClientWithCustomInitWait(config, Some pathValue) |> initAndWaitForReply test <@ client.ClientSettings.Capabilities.Workspace.Configuration.IsSupported @> - test <@ client.ClientSettings.Capabilities.Workspace.DidChangeConfiguration.IsSupported @> test <@ @@ -71,7 +67,7 @@ let initializationTests = testAsync "Server loads contextive file from default location when no configuration supplied" { let config = [ Workspace.optionsBuilder "" - ConfigurationSection.optionsBuilder "dummySection" (fun () -> Map []) ] + ConfigurationSection.configurationHandlerBuilder "dummySection" (fun () -> Map []) ] let defaultPath = ".contextive/definitions.yml" diff --git a/src/language-server/Contextive.LanguageServer.Tests/SurveyTests.fs b/src/language-server/Contextive.LanguageServer.Tests/SurveyTests.fs index 75671e90..aba553d1 100644 --- a/src/language-server/Contextive.LanguageServer.Tests/SurveyTests.fs +++ b/src/language-server/Contextive.LanguageServer.Tests/SurveyTests.fs @@ -48,7 +48,7 @@ let initializationTests = let config = [ showMessageRequestHandlerBuilder <| handler messageAwaiter null Workspace.optionsBuilder "" - ConfigurationSection.contextivePathOptionsBuilder pathValue ] + ConfigurationSection.contextivePathBuilder pathValue ] let! (client, reply) = TestClient(config) |> initAndWaitForReply @@ -75,7 +75,7 @@ let initializationTests = let config = [ showMessageRequestHandlerBuilder <| handler messageAwaiter null Workspace.optionsBuilder "" - ConfigurationSection.contextivePathOptionsBuilder pathValue ] + ConfigurationSection.contextivePathBuilder pathValue ] let! (client, reply) = TestClient(config) |> initAndWaitForReply @@ -102,7 +102,7 @@ let initializationTests = [ showMessageRequestHandlerBuilder <| handler messageAwaiter response showDocumentRequestHandlerBuilder <| handler showDocAwaiter showDocResponse Workspace.optionsBuilder "" - ConfigurationSection.contextivePathOptionsBuilder pathValue ] + ConfigurationSection.contextivePathBuilder pathValue ] let! (client, reply) = TestClient(config) |> initAndWaitForReply diff --git a/src/language-server/Contextive.LanguageServer.Tests/TextDocumentTests.fs b/src/language-server/Contextive.LanguageServer.Tests/TextDocumentTests.fs index 50e1318e..d5f5371a 100644 --- a/src/language-server/Contextive.LanguageServer.Tests/TextDocumentTests.fs +++ b/src/language-server/Contextive.LanguageServer.Tests/TextDocumentTests.fs @@ -52,7 +52,7 @@ let textDocumentTests = testAsync "Server supports full sync" { let config = [ Workspace.optionsBuilder <| Path.Combine("fixtures", "completion_tests") - ConfigurationSection.contextivePathOptionsBuilder $"one.yml" ] + ConfigurationSection.contextivePathBuilder $"one.yml" ] use! client = TestClient(config) |> init test <@ client.ServerSettings.Capabilities.TextDocumentSync.Options.Change = TextDocumentSyncKind.Full @> diff --git a/src/language-server/Contextive.LanguageServer.Tests/WatchedFilesTests.fs b/src/language-server/Contextive.LanguageServer.Tests/WatchedFilesTests.fs index 345c3e8a..739e2030 100644 --- a/src/language-server/Contextive.LanguageServer.Tests/WatchedFilesTests.fs +++ b/src/language-server/Contextive.LanguageServer.Tests/WatchedFilesTests.fs @@ -36,7 +36,7 @@ let watchedFileTests = let config = [ Workspace.optionsBuilder <| Path.Combine("fixtures", "completion_tests") - ConfigurationSection.contextivePathOptionsBuilder $"one.yml" + ConfigurationSection.contextivePathBuilder $"one.yml" WatchedFiles.optionsBuilder registrationAwaiter ] let! client = TestClient(config) |> init @@ -62,7 +62,7 @@ let watchedFileTests = let config = [ Workspace.optionsBuilder <| Path.Combine("fixtures", "completion_tests") - ConfigurationSection.contextivePathLoaderOptionsBuilder pathLoader + ConfigurationSection.contextivePathLoaderBuilder pathLoader WatchedFiles.optionsBuilder registrationAwaiter ] let! client = TestClient(config) |> init @@ -72,7 +72,7 @@ let watchedFileTests = ConditionAwaiter.clear registrationAwaiter 500 definitionsFile <- newDefinitionsFile - ConfigurationSection.didChange client definitionsFile + ConfigurationSection.didChangePath client definitionsFile let! secondRegistrationMsg = ConditionAwaiter.waitFor @@ -113,7 +113,7 @@ let watchedFileTests = let config = [ Workspace.optionsBuilder relativePath - ConfigurationSection.contextivePathOptionsBuilder definitionsFile ] + ConfigurationSection.contextivePathBuilder definitionsFile ] let! client = TestClient(config) |> init @@ -153,7 +153,7 @@ let watchedFileTests = let config = [ Workspace.optionsBuilder relativePath - ConfigurationSection.contextivePathOptionsBuilder definitionsFile ] + ConfigurationSection.contextivePathBuilder definitionsFile ] let! client = TestClient(config) |> init diff --git a/src/language-server/Contextive.LanguageServer/Server.fs b/src/language-server/Contextive.LanguageServer/Server.fs index d8a1a3ce..0dccd9a7 100644 --- a/src/language-server/Contextive.LanguageServer/Server.fs +++ b/src/language-server/Contextive.LanguageServer/Server.fs @@ -30,7 +30,10 @@ let private getConfig (s: ILanguageServer) section key = Log.Logger.Information $"Getting {section} {key} config..." let! config = - s.Configuration.GetConfiguration(ConfigurationItem(Section = configSection)) + // We need to use `GetScopedConfiguration` because of https://github.com/OmniSharp/csharp-language-server-protocol/issues/1101 + // We can revert to `GetConfiguration` when that bug is fixed. + s.Configuration.GetScopedConfiguration("file:///", System.Threading.CancellationToken.None) + //s.Configuration.GetConfiguration(ConfigurationItem(Section = configSection)) |> Async.AwaitTask let configValue = config.GetSection(section).Item(key)