diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp --- a/clang-tools-extra/clangd/ClangdLSPServer.cpp +++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp @@ -524,98 +524,106 @@ BackgroundIndexProgressState = BackgroundIndexProgress::Empty; BackgroundIndexSkipCreate = Params.capabilities.ImplicitProgressCreation; + llvm::json::Object ServerCaps{ + {"textDocumentSync", + llvm::json::Object{ + {"openClose", true}, + {"change", (int)TextDocumentSyncKind::Incremental}, + {"save", true}, + }}, + {"documentFormattingProvider", true}, + {"documentRangeFormattingProvider", true}, + {"documentOnTypeFormattingProvider", + llvm::json::Object{ + {"firstTriggerCharacter", "\n"}, + {"moreTriggerCharacter", {}}, + }}, + {"completionProvider", + llvm::json::Object{ + {"allCommitCharacters", + {" ", "\t", "(", ")", "[", "]", "{", "}", "<", + ">", ":", ";", ",", "+", "-", "/", "*", "%", + "^", "&", "#", "?", ".", "=", "\"", "'", "|"}}, + {"resolveProvider", false}, + // We do extra checks, e.g. that > is part of ->. + {"triggerCharacters", {".", "<", ">", ":", "\"", "/"}}, + }}, + {"semanticTokensProvider", + llvm::json::Object{ + {"full", llvm::json::Object{{"delta", true}}}, + {"range", false}, + {"legend", + llvm::json::Object{{"tokenTypes", semanticTokenTypes()}, + {"tokenModifiers", semanticTokenModifiers()}}}, + }}, + {"signatureHelpProvider", + llvm::json::Object{ + {"triggerCharacters", {"(", ","}}, + }}, + {"declarationProvider", true}, + {"definitionProvider", true}, + {"implementationProvider", true}, + {"documentHighlightProvider", true}, + {"documentLinkProvider", + llvm::json::Object{ + {"resolveProvider", false}, + }}, + {"hoverProvider", true}, + {"selectionRangeProvider", true}, + {"documentSymbolProvider", true}, + {"workspaceSymbolProvider", true}, + {"referencesProvider", true}, + {"astProvider", true}, // clangd extension + {"typeHierarchyProvider", true}, + {"memoryUsageProvider", true}, // clangd extension + {"compilationDatabase", // clangd extension + llvm::json::Object{{"automaticReload", true}}}, + {"callHierarchyProvider", true}, + }; + + { + LSPBinder Binder(Handlers); + if (Opts.Modules) + for (auto &Mod : *Opts.Modules) + Mod.initializeLSP(Binder, Params.capabilities, ServerCaps); + } + // Per LSP, renameProvider can be either boolean or RenameOptions. // RenameOptions will be specified if the client states it supports prepare. - llvm::json::Value RenameProvider = - llvm::json::Object{{"prepareProvider", true}}; - if (!Params.capabilities.RenamePrepareSupport) // Only boolean allowed per LSP - RenameProvider = true; + ServerCaps["renameProvider"] = + Params.capabilities.RenamePrepareSupport + ? llvm::json::Object{{"prepareProvider", true}} + : llvm::json::Value(true); - // Per LSP, codeActionProvide can be either boolean or CodeActionOptions. + // Per LSP, codeActionProvider can be either boolean or CodeActionOptions. // CodeActionOptions is only valid if the client supports action literal // via textDocument.codeAction.codeActionLiteralSupport. llvm::json::Value CodeActionProvider = true; - if (Params.capabilities.CodeActionStructure) - CodeActionProvider = llvm::json::Object{ - {"codeActionKinds", - {CodeAction::QUICKFIX_KIND, CodeAction::REFACTOR_KIND, - CodeAction::INFO_KIND}}}; + ServerCaps["codeActionProvider"] = + Params.capabilities.CodeActionStructure + ? llvm::json::Object{{"codeActionKinds", + {CodeAction::QUICKFIX_KIND, + CodeAction::REFACTOR_KIND, + CodeAction::INFO_KIND}}} + : llvm::json::Value(true); + + if (Opts.FoldingRanges) + ServerCaps["foldingRangeProvider"] = true; std::vector Commands; for (llvm::StringRef Command : Handlers.CommandHandlers.keys()) Commands.push_back(Command); llvm::sort(Commands); + ServerCaps["executeCommandProvider"] = + llvm::json::Object{{"commands", Commands}}; llvm::json::Object Result{ {{"serverInfo", llvm::json::Object{{"name", "clangd"}, {"version", getClangToolFullVersion("clangd")}}}, - {"capabilities", - llvm::json::Object{ - {"textDocumentSync", - llvm::json::Object{ - {"openClose", true}, - {"change", (int)TextDocumentSyncKind::Incremental}, - {"save", true}, - }}, - {"documentFormattingProvider", true}, - {"documentRangeFormattingProvider", true}, - {"documentOnTypeFormattingProvider", - llvm::json::Object{ - {"firstTriggerCharacter", "\n"}, - {"moreTriggerCharacter", {}}, - }}, - {"codeActionProvider", std::move(CodeActionProvider)}, - {"completionProvider", - llvm::json::Object{ - {"allCommitCharacters", - {" ", "\t", "(", ")", "[", "]", "{", "}", "<", - ">", ":", ";", ",", "+", "-", "/", "*", "%", - "^", "&", "#", "?", ".", "=", "\"", "'", "|"}}, - {"resolveProvider", false}, - // We do extra checks, e.g. that > is part of ->. - {"triggerCharacters", {".", "<", ">", ":", "\"", "/"}}, - }}, - {"semanticTokensProvider", - llvm::json::Object{ - {"full", llvm::json::Object{{"delta", true}}}, - {"range", false}, - {"legend", - llvm::json::Object{ - {"tokenTypes", semanticTokenTypes()}, - {"tokenModifiers", semanticTokenModifiers()}}}, - }}, - {"signatureHelpProvider", - llvm::json::Object{ - {"triggerCharacters", {"(", ","}}, - }}, - {"declarationProvider", true}, - {"definitionProvider", true}, - {"implementationProvider", true}, - {"documentHighlightProvider", true}, - {"documentLinkProvider", - llvm::json::Object{ - {"resolveProvider", false}, - }}, - {"hoverProvider", true}, - {"renameProvider", std::move(RenameProvider)}, - {"selectionRangeProvider", true}, - {"documentSymbolProvider", true}, - {"workspaceSymbolProvider", true}, - {"referencesProvider", true}, - {"astProvider", true}, // clangd extension - {"executeCommandProvider", - llvm::json::Object{{"commands", Commands}}}, - {"typeHierarchyProvider", true}, - {"memoryUsageProvider", true}, // clangd extension - {"compilationDatabase", // clangd extension - llvm::json::Object{{"automaticReload", true}}}, - {"callHierarchyProvider", true}, - }}}}; + {"capabilities", std::move(ServerCaps)}}}; if (Opts.Encoding) Result["offsetEncoding"] = *Opts.Encoding; - if (Opts.FoldingRanges) - Result.getObject("capabilities")->insert({"foldingRangeProvider", true}); Reply(std::move(Result)); } diff --git a/clang-tools-extra/clangd/Module.h b/clang-tools-extra/clangd/Module.h --- a/clang-tools-extra/clangd/Module.h +++ b/clang-tools-extra/clangd/Module.h @@ -1,7 +1,10 @@ #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_MODULE_H #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_MODULE_H +#include "LSPBinder.h" +#include "Protocol.h" #include "llvm/ADT/StringRef.h" +#include "llvm/Support/JSON.h" #include #include @@ -10,14 +13,11 @@ /// A Module contributes a vertical feature to clangd. /// -/// FIXME: Extend this with LSP bindings to support reading/updating -/// capabilities and implementing LSP endpoints. +/// FIXME: Extend this to support outgoing LSP calls. /// /// The lifetime of a module is roughly: /// - modules are created before the LSP server, in ClangdMain.cpp /// - these modules are then passed to ClangdLSPServer and ClangdServer -/// FIXME: LSP bindings should be registered at ClangdLSPServer -/// initialization. /// - module hooks can be called at this point. /// FIXME: We should make some server facilities like TUScheduler and index /// available to those modules after ClangdServer is initalized. @@ -30,6 +30,17 @@ class Module { public: virtual ~Module() = default; + + /// Called by the server to connect this module to LSP. + /// The module should register the methods/notifications/commands it handles, + /// and update the server capabilities to advertise them. + /// + /// This is only called if the module is running in ClangdLSPServer! + /// Modules with a public interface should satisfy it without LSP bindings. + // FIXME: ClientCaps should be a raw json::Object here. + virtual void initializeLSP(LSPBinder &Bind, + const ClientCapabilities &ClientCaps, + llvm::json::Object &ServerCaps) {} }; class ModuleSet { diff --git a/clang-tools-extra/clangd/unittests/ClangdLSPServerTests.cpp b/clang-tools-extra/clangd/unittests/ClangdLSPServerTests.cpp --- a/clang-tools-extra/clangd/unittests/ClangdLSPServerTests.cpp +++ b/clang-tools-extra/clangd/unittests/ClangdLSPServerTests.cpp @@ -221,6 +221,29 @@ DiagMessage("Use of undeclared identifier 'BAR'")))); } +TEST_F(LSPTest, ModulesTest) { + class MathModule : public Module { + void initializeLSP(LSPBinder &Bind, const ClientCapabilities &ClientCaps, + llvm::json::Object &ServerCaps) override { + Bind.notification("add", this, &MathModule::add); + Bind.method("get", this, &MathModule::get); + } + + void add(const int &X) { Value += X; } + void get(const std::nullptr_t &, Callback Reply) { Reply(Value); } + int Value = 0; + }; + std::vector> Mods; + Mods.push_back(std::make_unique()); + ModuleSet ModSet(std::move(Mods)); + Opts.Modules = &ModSet; + + auto &Client = start(); + Client.notify("add", 2); + Client.notify("add", 8); + EXPECT_EQ(10, Client.call("get", nullptr).takeValue()); +} + } // namespace } // namespace clangd } // namespace clang