diff --git a/lspserver/include/lspserver/LSPBinder.h b/lspserver/include/lspserver/LSPBinder.h index 7320319f5..36f71ac3f 100644 --- a/lspserver/include/lspserver/LSPBinder.h +++ b/lspserver/include/lspserver/LSPBinder.h @@ -7,13 +7,33 @@ #include #include +#include + namespace lspserver { +namespace detail { + template -llvm::Expected parseParam(const llvm::json::Value &Raw, - llvm::StringRef PayloadName, - llvm::StringRef PayloadKind) { +typename std::enable_if_t, T> +valueOrUninitialized(const std::optional &OptionalDefault) { T Result; + if (OptionalDefault) { + Result = OptionalDefault.value(); + } + return Result; +} + +template +typename std::enable_if_t, T> +valueOrUninitialized(const std::optional &OptionalDefault) { + return OptionalDefault.value(); +} + +template +llvm::Expected parseParamWithOptionalDefault( + const llvm::json::Value &Raw, llvm::StringRef PayloadName, + llvm::StringRef PayloadKind, std::optional OptionalDefault = {}) { + T Result = valueOrUninitialized(OptionalDefault); llvm::json::Path::Root Root; if (!fromJSON(Raw, Result, Root)) { elog("Failed to decode {0} {1}: {2}", PayloadName, PayloadKind, @@ -31,6 +51,25 @@ llvm::Expected parseParam(const llvm::json::Value &Raw, } return Result; } + +} // namespace detail + +template +typename std::enable_if_t, llvm::Expected> +parseParam(const llvm::json::Value &Raw, llvm::StringRef PayloadName, + llvm::StringRef PayloadKind) { + return detail::parseParamWithOptionalDefault(Raw, PayloadName, + PayloadKind); +} + +template +llvm::Expected +parseParamWithDefault(const llvm::json::Value &Raw, llvm::StringRef PayloadName, + llvm::StringRef PayloadKind, T Default) { + return detail::parseParamWithOptionalDefault(Raw, PayloadName, PayloadKind, + Default); +} + struct HandlerRegistry { using JSON = llvm::json::Value; template diff --git a/nixd/include/nixd/Server/Controller.h b/nixd/include/nixd/Server/Controller.h index e7355fee0..0af96afdc 100644 --- a/nixd/include/nixd/Server/Controller.h +++ b/nixd/include/nixd/Server/Controller.h @@ -41,6 +41,12 @@ namespace nixd { +struct OptionalValue { + std::optional Value; +}; + +bool fromJSON(const llvm::json::Value &E, OptionalValue &R, llvm::json::Path P); + /// The server instance, nix-related language features goes here class Controller : public lspserver::LSPServer { public: @@ -122,6 +128,10 @@ class Controller : public lspserver::LSPServer { std::mutex ConfigLock; + // When the server starts, DefaultConfig is set to the + // parsed contents of .nixd.json if it exists. This is used + // as the default when parsing workspace/configuration. + configuration::TopLevel DefaultConfig; configuration::TopLevel Config; // GUARDED_BY(ConfigLock) std::shared_ptr getDraft(lspserver::PathRef File) const; @@ -135,7 +145,7 @@ class Controller : public lspserver::LSPServer { PublishDiagnostic; llvm::unique_function)> + lspserver::Callback)> WorkspaceConfiguration; std::mutex DiagStatusLock; @@ -255,9 +265,10 @@ class Controller : public lspserver::LSPServer { static llvm::Expected parseConfig(llvm::StringRef JSON); - /// Try to update the server config from json encoded file \p File - /// Won't touch config field if exceptions encountered - void readJSONConfig(lspserver::PathRef File = ".nixd.json") noexcept; + /// Try to update the default config from json encoded file \p File + /// Won't touch default config field if exceptions encountered + /// Returns true if the default config is set and false otherwise. + bool readJSONConfigToDefault(lspserver::PathRef File = ".nixd.json") noexcept; void onWorkspaceDidChangeConfiguration( const lspserver::DidChangeConfigurationParams &) { diff --git a/nixd/lib/Server/Controller.cpp b/nixd/lib/Server/Controller.cpp index 675f2009d..47d017b52 100644 --- a/nixd/lib/Server/Controller.cpp +++ b/nixd/lib/Server/Controller.cpp @@ -15,6 +15,7 @@ #include "lspserver/Connection.h" #include "lspserver/DraftStore.h" +#include "lspserver/LSPBinder.h" #include "lspserver/Logger.h" #include "lspserver/Path.h" #include "lspserver/Protocol.h" @@ -204,9 +205,15 @@ void Controller::fetchConfig() { lspserver::ConfigurationParams{ std::vector{ lspserver::ConfigurationItem{.section = "nixd"}}}, - [this](llvm::Expected Response) { + [this](llvm::Expected Response) { if (Response) { - updateConfig(std::move(Response.get())); + llvm::Expected ResponseConfig = + lspserver::parseParamWithDefault( + Response.get().Value.value(), "workspace/configuration", + "reply", DefaultConfig); + if (ResponseConfig) { + updateConfig(std::move(ResponseConfig.get())); + } } }); } @@ -226,20 +233,23 @@ Controller::parseConfig(llvm::StringRef JSON) { return lspserver::error("value cannot be converted to internal config type"); } -void Controller::readJSONConfig(lspserver::PathRef File) noexcept { +bool Controller::readJSONConfigToDefault(lspserver::PathRef File) noexcept { try { std::string ConfigStr; std::ostringstream SS; std::ifstream In(File.str(), std::ios::in); SS << In.rdbuf(); - if (auto NewConfig = parseConfig(SS.str())) - updateConfig(std::move(NewConfig.get())); - else { - throw nix::Error("configuration cannot be parsed"); + if (auto NewConfig = parseConfig(SS.str())) { + DefaultConfig = std::move(NewConfig.get()); + return true; } + + throw nix::Error(".nixd.json configuration cannot be parsed"); } catch (std::exception &E) { + return false; } catch (...) { + return false; } } @@ -257,6 +267,16 @@ Controller::Controller(std::unique_ptr In, : LSPServer(std::move(In), std::move(Out)), WaitWorker(WaitWorker), ASTMgr(Pool) { + // JSON Config, run before initialize + if (readJSONConfigToDefault()) { + configuration::TopLevel JSONConfigCopy = DefaultConfig; + updateConfig(std::move(JSONConfigCopy)); + }; + + WorkspaceConfiguration = + mkOutMethod( + "workspace/configuration", nullptr); + // Life Cycle Registry.addMethod("initialize", this, &Controller::onInitialize); Registry.addNotification("initialized", this, &Controller::onInitialized); @@ -293,17 +313,12 @@ Controller::Controller(std::unique_ptr In, // Workspace Registry.addNotification("workspace/didChangeConfiguration", this, &Controller::onWorkspaceDidChangeConfiguration); - WorkspaceConfiguration = - mkOutMethod( - "workspace/configuration"); /// IPC Registry.addNotification("nixd/ipc/diagnostic", this, &Controller::onEvalDiagnostic); Registry.addNotification("nixd/ipc/finished", this, &Controller::onFinished); - - readJSONConfig(); } //-----------------------------------------------------------------------------/ @@ -790,4 +805,11 @@ void Controller::onFormat( }; boost::asio::post(Pool, std::move(Task)); } + +bool fromJSON(const llvm::json::Value &E, OptionalValue &R, + llvm::json::Path P) { + R.Value = E; + return true; +} + } // namespace nixd