aboutsummaryrefslogtreecommitdiff
path: root/clangd
diff options
context:
space:
mode:
authorIlya Biryukov <ibiryukov@google.com>2017-05-16 14:40:30 +0000
committerIlya Biryukov <ibiryukov@google.com>2017-05-16 14:40:30 +0000
commit53c6ae9211549cdf523407b4826a3c6429eec83d (patch)
tree9a3ebcda7bd9ff09fb15e8340be6894bec7ff011 /clangd
parent18140c41ccaf948359c9ee26f1ef6e8ec7167946 (diff)
[clangd] Refactor ProtocolHandlers to decouple them from ClangdLSPServer
Summary: A refactoring to decouple ProtocolHandlers and Language Server input parsing loop from the ClangdLSPServer. The input parsing was extracted from `main` to a function(runLanguageServerLoop). ProtocolHandlers now provide an interface to handle various LSP methods, this interface is used by ClangdLSPServer. Methods for code formatting were moved from ProtocolHandlers to ClangdServer. ClangdLSPServer now provides a cleaner interface that only runs Language Server input loop. Reviewers: bkramer, krasimir Reviewed By: krasimir Subscribers: cfe-commits, klimek Tags: #clang-tools-extra Differential Revision: https://reviews.llvm.org/D33201 git-svn-id: https://llvm.org/svn/llvm-project/clang-tools-extra/trunk@303173 91177308-0d34-0410-b5e6-96231b3b80d8
Diffstat (limited to 'clangd')
-rw-r--r--clangd/ClangdLSPServer.cpp194
-rw-r--r--clangd/ClangdLSPServer.h42
-rw-r--r--clangd/ClangdMain.cpp90
-rw-r--r--clangd/ClangdServer.cpp67
-rw-r--r--clangd/ClangdServer.h19
-rw-r--r--clangd/JSONRPCDispatcher.cpp58
-rw-r--r--clangd/JSONRPCDispatcher.h9
-rw-r--r--clangd/ProtocolHandlers.cpp339
-rw-r--r--clangd/ProtocolHandlers.h138
9 files changed, 549 insertions, 407 deletions
diff --git a/clangd/ClangdLSPServer.cpp b/clangd/ClangdLSPServer.cpp
index 2a2e0bf9..dcd23714 100644
--- a/clangd/ClangdLSPServer.cpp
+++ b/clangd/ClangdLSPServer.cpp
@@ -9,10 +9,35 @@
#include "ClangdLSPServer.h"
#include "JSONRPCDispatcher.h"
+#include "ProtocolHandlers.h"
using namespace clang::clangd;
using namespace clang;
+namespace {
+
+std::string
+replacementsToEdits(StringRef Code,
+ const std::vector<tooling::Replacement> &Replacements) {
+ // Turn the replacements into the format specified by the Language Server
+ // Protocol. Fuse them into one big JSON array.
+ std::string Edits;
+ for (auto &R : Replacements) {
+ Range ReplacementRange = {
+ offsetToPosition(Code, R.getOffset()),
+ offsetToPosition(Code, R.getOffset() + R.getLength())};
+ TextEdit TE = {ReplacementRange, R.getReplacementText()};
+ Edits += TextEdit::unparse(TE);
+ Edits += ',';
+ }
+ if (!Edits.empty())
+ Edits.pop_back();
+
+ return Edits;
+}
+
+} // namespace
+
class ClangdLSPServer::LSPDiagnosticsConsumer : public DiagnosticsConsumer {
public:
LSPDiagnosticsConsumer(ClangdLSPServer &Server) : Server(Server) {}
@@ -26,23 +51,170 @@ private:
ClangdLSPServer &Server;
};
+class ClangdLSPServer::LSPProtocolCallbacks : public ProtocolCallbacks {
+public:
+ LSPProtocolCallbacks(ClangdLSPServer &LangServer) : LangServer(LangServer) {}
+
+ void onInitialize(StringRef ID, JSONOutput &Out) override;
+ void onShutdown(JSONOutput &Out) override;
+ void onDocumentDidOpen(DidOpenTextDocumentParams Params,
+ JSONOutput &Out) override;
+ void onDocumentDidChange(DidChangeTextDocumentParams Params,
+ JSONOutput &Out) override;
+ void onDocumentDidClose(DidCloseTextDocumentParams Params,
+ JSONOutput &Out) override;
+ void onDocumentOnTypeFormatting(DocumentOnTypeFormattingParams Params,
+ StringRef ID, JSONOutput &Out) override;
+ void onDocumentRangeFormatting(DocumentRangeFormattingParams Params,
+ StringRef ID, JSONOutput &Out) override;
+ void onDocumentFormatting(DocumentFormattingParams Params, StringRef ID,
+ JSONOutput &Out) override;
+ void onCodeAction(CodeActionParams Params, StringRef ID,
+ JSONOutput &Out) override;
+ void onCompletion(TextDocumentPositionParams Params, StringRef ID,
+ JSONOutput &Out) override;
+
+private:
+ ClangdLSPServer &LangServer;
+};
+
+void ClangdLSPServer::LSPProtocolCallbacks::onInitialize(StringRef ID,
+ JSONOutput &Out) {
+ Out.writeMessage(
+ R"({"jsonrpc":"2.0","id":)" + ID +
+ R"(,"result":{"capabilities":{
+ "textDocumentSync": 1,
+ "documentFormattingProvider": true,
+ "documentRangeFormattingProvider": true,
+ "documentOnTypeFormattingProvider": {"firstTriggerCharacter":"}","moreTriggerCharacter":[]},
+ "codeActionProvider": true,
+ "completionProvider": {"resolveProvider": false, "triggerCharacters": [".",">"]}
+ }}})");
+}
+
+void ClangdLSPServer::LSPProtocolCallbacks::onShutdown(JSONOutput &Out) {
+ LangServer.IsDone = true;
+}
+
+void ClangdLSPServer::LSPProtocolCallbacks::onDocumentDidOpen(
+ DidOpenTextDocumentParams Params, JSONOutput &Out) {
+ LangServer.Server.addDocument(Params.textDocument.uri.file,
+ Params.textDocument.text);
+}
+
+void ClangdLSPServer::LSPProtocolCallbacks::onDocumentDidChange(
+ DidChangeTextDocumentParams Params, JSONOutput &Out) {
+ // We only support full syncing right now.
+ LangServer.Server.addDocument(Params.textDocument.uri.file,
+ Params.contentChanges[0].text);
+}
+
+void ClangdLSPServer::LSPProtocolCallbacks::onDocumentDidClose(
+ DidCloseTextDocumentParams Params, JSONOutput &Out) {
+ LangServer.Server.removeDocument(Params.textDocument.uri.file);
+}
+
+void ClangdLSPServer::LSPProtocolCallbacks::onDocumentOnTypeFormatting(
+ DocumentOnTypeFormattingParams Params, StringRef ID, JSONOutput &Out) {
+ auto File = Params.textDocument.uri.file;
+ std::string Code = LangServer.Server.getDocument(File);
+ std::string Edits = replacementsToEdits(
+ Code, LangServer.Server.formatOnType(File, Params.position));
+
+ Out.writeMessage(R"({"jsonrpc":"2.0","id":)" + ID.str() +
+ R"(,"result":[)" + Edits + R"(]})");
+}
+
+void ClangdLSPServer::LSPProtocolCallbacks::onDocumentRangeFormatting(
+ DocumentRangeFormattingParams Params, StringRef ID, JSONOutput &Out) {
+ auto File = Params.textDocument.uri.file;
+ std::string Code = LangServer.Server.getDocument(File);
+ std::string Edits = replacementsToEdits(
+ Code, LangServer.Server.formatRange(File, Params.range));
+
+ Out.writeMessage(R"({"jsonrpc":"2.0","id":)" + ID.str() +
+ R"(,"result":[)" + Edits + R"(]})");
+}
+
+void ClangdLSPServer::LSPProtocolCallbacks::onDocumentFormatting(
+ DocumentFormattingParams Params, StringRef ID, JSONOutput &Out) {
+ auto File = Params.textDocument.uri.file;
+ std::string Code = LangServer.Server.getDocument(File);
+ std::string Edits =
+ replacementsToEdits(Code, LangServer.Server.formatFile(File));
+
+ Out.writeMessage(R"({"jsonrpc":"2.0","id":)" + ID.str() +
+ R"(,"result":[)" + Edits + R"(]})");
+}
+
+void ClangdLSPServer::LSPProtocolCallbacks::onCodeAction(
+ CodeActionParams Params, StringRef ID, JSONOutput &Out) {
+ // We provide a code action for each diagnostic at the requested location
+ // which has FixIts available.
+ std::string Code =
+ LangServer.Server.getDocument(Params.textDocument.uri.file);
+ std::string Commands;
+ for (Diagnostic &D : Params.context.diagnostics) {
+ std::vector<clang::tooling::Replacement> Fixes =
+ LangServer.getFixIts(Params.textDocument.uri.file, D);
+ std::string Edits = replacementsToEdits(Code, Fixes);
+
+ if (!Edits.empty())
+ Commands +=
+ R"({"title":"Apply FixIt ')" + llvm::yaml::escape(D.message) +
+ R"('", "command": "clangd.applyFix", "arguments": [")" +
+ llvm::yaml::escape(Params.textDocument.uri.uri) +
+ R"(", [)" + Edits +
+ R"(]]},)";
+ }
+ if (!Commands.empty())
+ Commands.pop_back();
+
+ Out.writeMessage(
+ R"({"jsonrpc":"2.0","id":)" + ID.str() +
+ R"(, "result": [)" + Commands +
+ R"(]})");
+}
+
+void ClangdLSPServer::LSPProtocolCallbacks::onCompletion(
+ TextDocumentPositionParams Params, StringRef ID, JSONOutput &Out) {
+
+ auto Items = LangServer.Server.codeComplete(
+ Params.textDocument.uri.file,
+ Position{Params.position.line, Params.position.character});
+
+ std::string Completions;
+ for (const auto &Item : Items) {
+ Completions += CompletionItem::unparse(Item);
+ Completions += ",";
+ }
+ if (!Completions.empty())
+ Completions.pop_back();
+ Out.writeMessage(
+ R"({"jsonrpc":"2.0","id":)" + ID.str() +
+ R"(,"result":[)" + Completions + R"(]})");
+}
+
ClangdLSPServer::ClangdLSPServer(JSONOutput &Out, bool RunSynchronously)
: Out(Out),
Server(llvm::make_unique<DirectoryBasedGlobalCompilationDatabase>(),
llvm::make_unique<LSPDiagnosticsConsumer>(*this),
RunSynchronously) {}
-void ClangdLSPServer::openDocument(StringRef File, StringRef Contents) {
- Server.addDocument(File, Contents);
-}
+void ClangdLSPServer::run(std::istream &In) {
+ assert(!IsDone && "Run was called before");
-void ClangdLSPServer::closeDocument(StringRef File) {
- Server.removeDocument(File);
-}
+ // Set up JSONRPCDispatcher.
+ LSPProtocolCallbacks Callbacks(*this);
+ JSONRPCDispatcher Dispatcher(llvm::make_unique<Handler>(Out));
+ regiterCallbackHandlers(Dispatcher, Out, Callbacks);
-std::vector<CompletionItem> ClangdLSPServer::codeComplete(PathRef File,
- Position Pos) {
- return Server.codeComplete(File, Pos);
+ // Run the Language Server loop.
+ runLanguageServerLoop(In, Out, Dispatcher, IsDone);
+
+ // Make sure IsDone is set to true after this method exits to ensure assertion
+ // at the start of the method fires if it's ever executed again.
+ IsDone = true;
}
std::vector<clang::tooling::Replacement>
@@ -60,10 +232,6 @@ ClangdLSPServer::getFixIts(StringRef File, const clangd::Diagnostic &D) {
return FixItsIter->second;
}
-std::string ClangdLSPServer::getDocument(PathRef File) {
- return Server.getDocument(File);
-}
-
void ClangdLSPServer::consumeDiagnostics(
PathRef File, std::vector<DiagWithFixIts> Diagnostics) {
std::string DiagnosticsJSON;
diff --git a/clangd/ClangdLSPServer.h b/clangd/ClangdLSPServer.h
index 108d8d70..ee3ada31 100644
--- a/clangd/ClangdLSPServer.h
+++ b/clangd/ClangdLSPServer.h
@@ -20,43 +20,25 @@ namespace clangd {
class JSONOutput;
-/// This class serves as an intermediate layer of LSP server implementation,
-/// glueing the JSON LSP protocol layer and ClangdServer together. It doesn't
-/// directly handle input from LSP client.
-/// Most methods are synchronous and return their result directly, but
-/// diagnostics are provided asynchronously when ready via
-/// JSONOutput::writeMessage.
+/// This class provides implementation of an LSP server, glueing the JSON
+/// dispatch and ClangdServer together.
class ClangdLSPServer {
public:
ClangdLSPServer(JSONOutput &Out, bool RunSynchronously);
- /// Update the document text for \p File with \p Contents, schedule update of
- /// diagnostics. Out.writeMessage will called to push diagnostics to LSP
- /// client asynchronously when they are ready.
- void openDocument(PathRef File, StringRef Contents);
- /// Stop tracking the document for \p File.
- void closeDocument(PathRef File);
+ /// Run LSP server loop, receiving input for it from \p In. \p In must be
+ /// opened in binary mode. Output will be written using Out variable passed to
+ /// class constructor. This method must not be executed more than once for
+ /// each instance of ClangdLSPServer.
+ void run(std::istream &In);
- /// Run code completion synchronously.
- std::vector<CompletionItem> codeComplete(PathRef File, Position Pos);
+private:
+ class LSPProtocolCallbacks;
+ class LSPDiagnosticsConsumer;
- /// Get the fixes associated with a certain diagnostic in a specified file as
- /// replacements.
- ///
- /// This function is thread-safe. It returns a copy to avoid handing out
- /// references to unguarded data.
std::vector<clang::tooling::Replacement>
getFixIts(StringRef File, const clangd::Diagnostic &D);
- /// Get the current document contents stored for \p File.
- /// FIXME(ibiryukov): This function is here to allow implementation of
- /// formatCode from ProtocolHandlers.cpp. We should move formatCode to
- /// ClangdServer class and remove this function from public interface.
- std::string getDocument(PathRef File);
-
-private:
- class LSPDiagnosticsConsumer;
-
/// Function that will be called on a separate thread when diagnostics are
/// ready. Sends the Dianostics to LSP client via Out.writeMessage and caches
/// corresponding fixits in the FixItsMap.
@@ -64,6 +46,10 @@ private:
std::vector<DiagWithFixIts> Diagnostics);
JSONOutput &Out;
+ /// Used to indicate that the 'shutdown' request was received from the
+ /// Language Server client.
+ /// It's used to break out of the LSP parsing loop.
+ bool IsDone = false;
std::mutex FixItsMutex;
typedef std::map<clangd::Diagnostic, std::vector<clang::tooling::Replacement>>
diff --git a/clangd/ClangdMain.cpp b/clangd/ClangdMain.cpp
index 44f86656..d67f75ba 100644
--- a/clangd/ClangdMain.cpp
+++ b/clangd/ClangdMain.cpp
@@ -7,10 +7,8 @@
//
//===----------------------------------------------------------------------===//
-#include "JSONRPCDispatcher.h"
#include "ClangdLSPServer.h"
-#include "Protocol.h"
-#include "ProtocolHandlers.h"
+#include "JSONRPCDispatcher.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Program.h"
@@ -29,6 +27,7 @@ static llvm::cl::opt<bool>
int main(int argc, char *argv[]) {
llvm::cl::ParseCommandLineOptions(argc, argv, "clangd");
+
llvm::raw_ostream &Outs = llvm::outs();
llvm::raw_ostream &Logs = llvm::errs();
JSONOutput Out(Outs, Logs);
@@ -36,89 +35,6 @@ int main(int argc, char *argv[]) {
// Change stdin to binary to not lose \r\n on windows.
llvm::sys::ChangeStdinToBinary();
- // Set up a document store and intialize all the method handlers for JSONRPC
- // dispatching.
ClangdLSPServer LSPServer(Out, RunSynchronously);
- JSONRPCDispatcher Dispatcher(llvm::make_unique<Handler>(Out));
- Dispatcher.registerHandler("initialize",
- llvm::make_unique<InitializeHandler>(Out));
- auto ShutdownPtr = llvm::make_unique<ShutdownHandler>(Out);
- auto *ShutdownHandler = ShutdownPtr.get();
- Dispatcher.registerHandler("shutdown", std::move(ShutdownPtr));
- Dispatcher.registerHandler(
- "textDocument/didOpen",
- llvm::make_unique<TextDocumentDidOpenHandler>(Out, LSPServer));
- Dispatcher.registerHandler(
- "textDocument/didClose",
- llvm::make_unique<TextDocumentDidCloseHandler>(Out, LSPServer));
- Dispatcher.registerHandler(
- "textDocument/didChange",
- llvm::make_unique<TextDocumentDidChangeHandler>(Out, LSPServer));
- Dispatcher.registerHandler(
- "textDocument/rangeFormatting",
- llvm::make_unique<TextDocumentRangeFormattingHandler>(Out, LSPServer));
- Dispatcher.registerHandler(
- "textDocument/onTypeFormatting",
- llvm::make_unique<TextDocumentOnTypeFormattingHandler>(Out, LSPServer));
- Dispatcher.registerHandler(
- "textDocument/formatting",
- llvm::make_unique<TextDocumentFormattingHandler>(Out, LSPServer));
- Dispatcher.registerHandler("textDocument/codeAction",
- llvm::make_unique<CodeActionHandler>(Out, LSPServer));
- Dispatcher.registerHandler("textDocument/completion",
- llvm::make_unique<CompletionHandler>(Out, LSPServer));
-
- while (std::cin.good()) {
- // A Language Server Protocol message starts with a HTTP header, delimited
- // by \r\n.
- std::string Line;
- std::getline(std::cin, Line);
- if (!std::cin.good() && errno == EINTR) {
- std::cin.clear();
- continue;
- }
-
- // Skip empty lines.
- llvm::StringRef LineRef(Line);
- if (LineRef.trim().empty())
- continue;
-
- // We allow YAML-style comments. Technically this isn't part of the
- // LSP specification, but makes writing tests easier.
- if (LineRef.startswith("#"))
- continue;
-
- unsigned long long Len = 0;
- // FIXME: Content-Type is a specified header, but does nothing.
- // Content-Length is a mandatory header. It specifies the length of the
- // following JSON.
- if (LineRef.consume_front("Content-Length: "))
- llvm::getAsUnsignedInteger(LineRef.trim(), 0, Len);
-
- // Check if the next line only contains \r\n. If not this is another header,
- // which we ignore.
- char NewlineBuf[2];
- std::cin.read(NewlineBuf, 2);
- if (std::memcmp(NewlineBuf, "\r\n", 2) != 0)
- continue;
-
- // Now read the JSON. Insert a trailing null byte as required by the YAML
- // parser.
- std::vector<char> JSON(Len + 1, '\0');
- std::cin.read(JSON.data(), Len);
-
- if (Len > 0) {
- llvm::StringRef JSONRef(JSON.data(), Len);
- // Log the message.
- Out.log("<-- " + JSONRef + "\n");
-
- // Finally, execute the action for this JSON message.
- if (!Dispatcher.call(JSONRef))
- Out.log("JSON dispatch failed!\n");
-
- // If we're done, exit the loop.
- if (ShutdownHandler->isDone())
- break;
- }
- }
+ LSPServer.run(std::cin);
}
diff --git a/clangd/ClangdServer.cpp b/clangd/ClangdServer.cpp
index e0f0af7e..a228cfad 100644
--- a/clangd/ClangdServer.cpp
+++ b/clangd/ClangdServer.cpp
@@ -8,15 +8,54 @@
//===-------------------------------------------------------------------===//
#include "ClangdServer.h"
+#include "clang/Format/Format.h"
#include "clang/Frontend/ASTUnit.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/CompilerInvocation.h"
#include "clang/Tooling/CompilationDatabase.h"
+#include "llvm/ADT/ArrayRef.h"
#include "llvm/Support/FileSystem.h"
using namespace clang;
using namespace clang::clangd;
+namespace {
+
+std::vector<tooling::Replacement> formatCode(StringRef Code, StringRef Filename,
+ ArrayRef<tooling::Range> Ranges) {
+ // Call clang-format.
+ // FIXME: Don't ignore style.
+ format::FormatStyle Style = format::getLLVMStyle();
+ auto Result = format::reformat(Style, Code, Ranges, Filename);
+
+ return std::vector<tooling::Replacement>(Result.begin(), Result.end());
+}
+
+} // namespace
+
+size_t clangd::positionToOffset(StringRef Code, Position P) {
+ size_t Offset = 0;
+ for (int I = 0; I != P.line; ++I) {
+ // FIXME: \r\n
+ // FIXME: UTF-8
+ size_t F = Code.find('\n', Offset);
+ if (F == StringRef::npos)
+ return 0; // FIXME: Is this reasonable?
+ Offset = F + 1;
+ }
+ return (Offset == 0 ? 0 : (Offset - 1)) + P.character;
+}
+
+/// Turn an offset in Code into a [line, column] pair.
+Position clangd::offsetToPosition(StringRef Code, size_t Offset) {
+ StringRef JustBefore = Code.substr(0, Offset);
+ // FIXME: \r\n
+ // FIXME: UTF-8
+ int Lines = JustBefore.count('\n');
+ int Cols = JustBefore.size() - JustBefore.rfind('\n') - 1;
+ return {Lines, Cols};
+}
+
WorkerRequest::WorkerRequest(WorkerRequestKind Kind, Path File,
DocVersion Version)
: Kind(Kind), File(File), Version(Version) {}
@@ -117,6 +156,34 @@ std::vector<CompletionItem> ClangdServer::codeComplete(PathRef File,
});
return Result;
}
+std::vector<tooling::Replacement> ClangdServer::formatRange(PathRef File,
+ Range Rng) {
+ std::string Code = getDocument(File);
+
+ size_t Begin = positionToOffset(Code, Rng.start);
+ size_t Len = positionToOffset(Code, Rng.end) - Begin;
+ return formatCode(Code, File, {tooling::Range(Begin, Len)});
+}
+
+std::vector<tooling::Replacement> ClangdServer::formatFile(PathRef File) {
+ // Format everything.
+ std::string Code = getDocument(File);
+ return formatCode(Code, File, {tooling::Range(0, Code.size())});
+}
+
+std::vector<tooling::Replacement> ClangdServer::formatOnType(PathRef File,
+ Position Pos) {
+ // Look for the previous opening brace from the character position and
+ // format starting from there.
+ std::string Code = getDocument(File);
+ size_t CursorPos = positionToOffset(Code, Pos);
+ size_t PreviousLBracePos = StringRef(Code).find_last_of('{', CursorPos);
+ if (PreviousLBracePos == StringRef::npos)
+ PreviousLBracePos = CursorPos;
+ size_t Len = 1 + CursorPos - PreviousLBracePos;
+
+ return formatCode(Code, File, {tooling::Range(PreviousLBracePos, Len)});
+}
std::string ClangdServer::getDocument(PathRef File) {
auto draft = DraftMgr.getDraft(File);
diff --git a/clangd/ClangdServer.h b/clangd/ClangdServer.h
index 72b6bd57..f4ce2845 100644
--- a/clangd/ClangdServer.h
+++ b/clangd/ClangdServer.h
@@ -34,6 +34,12 @@ class PCHContainerOperations;
namespace clangd {
+/// Turn a [line, column] pair into an offset in Code.
+size_t positionToOffset(StringRef Code, Position P);
+
+/// Turn an offset in Code into a [line, column] pair.
+Position offsetToPosition(StringRef Code, size_t Offset);
+
class DiagnosticsConsumer {
public:
virtual ~DiagnosticsConsumer() = default;
@@ -100,7 +106,6 @@ public:
/// separate thread. When the parsing is complete, DiagConsumer passed in
/// constructor will receive onDiagnosticsReady callback.
void addDocument(PathRef File, StringRef Contents);
-
/// Remove \p File from list of tracked files, schedule a request to free
/// resources associated with it.
void removeDocument(PathRef File);
@@ -108,11 +113,17 @@ public:
/// Run code completion for \p File at \p Pos.
std::vector<CompletionItem> codeComplete(PathRef File, Position Pos);
+ /// Run formatting for \p Rng inside \p File.
+ std::vector<tooling::Replacement> formatRange(PathRef File, Range Rng);
+ /// Run formatting for the whole \p File.
+ std::vector<tooling::Replacement> formatFile(PathRef File);
+ /// Run formatting after a character was typed at \p Pos in \p File.
+ std::vector<tooling::Replacement> formatOnType(PathRef File, Position Pos);
+
/// Gets current document contents for \p File. \p File must point to a
/// currently tracked file.
- /// FIXME(ibiryukov): This function is here to allow implementation of
- /// formatCode from ProtocolHandlers.cpp. We should move formatCode to this
- /// class and remove this function from public interface.
+ /// FIXME(ibiryukov): This function is here to allow offset-to-Position
+ /// conversions in outside code, maybe there's a way to get rid of it.
std::string getDocument(PathRef File);
private:
diff --git a/clangd/JSONRPCDispatcher.cpp b/clangd/JSONRPCDispatcher.cpp
index f3045fbc..be6be770 100644
--- a/clangd/JSONRPCDispatcher.cpp
+++ b/clangd/JSONRPCDispatcher.cpp
@@ -129,3 +129,61 @@ bool JSONRPCDispatcher::call(StringRef Content) const {
return true;
}
+
+void clangd::runLanguageServerLoop(std::istream &In, JSONOutput &Out,
+ JSONRPCDispatcher &Dispatcher,
+ bool &IsDone) {
+ while (In.good()) {
+ // A Language Server Protocol message starts with a HTTP header, delimited
+ // by \r\n.
+ std::string Line;
+ std::getline(In, Line);
+ if (!In.good() && errno == EINTR) {
+ In.clear();
+ continue;
+ }
+
+ // Skip empty lines.
+ llvm::StringRef LineRef(Line);
+ if (LineRef.trim().empty())
+ continue;
+
+ // We allow YAML-style comments. Technically this isn't part of the
+ // LSP specification, but makes writing tests easier.
+ if (LineRef.startswith("#"))
+ continue;
+
+ unsigned long long Len = 0;
+ // FIXME: Content-Type is a specified header, but does nothing.
+ // Content-Length is a mandatory header. It specifies the length of the
+ // following JSON.
+ if (LineRef.consume_front("Content-Length: "))
+ llvm::getAsUnsignedInteger(LineRef.trim(), 0, Len);
+
+ // Check if the next line only contains \r\n. If not this is another header,
+ // which we ignore.
+ char NewlineBuf[2];
+ In.read(NewlineBuf, 2);
+ if (std::memcmp(NewlineBuf, "\r\n", 2) != 0)
+ continue;
+
+ // Now read the JSON. Insert a trailing null byte as required by the YAML
+ // parser.
+ std::vector<char> JSON(Len + 1, '\0');
+ In.read(JSON.data(), Len);
+
+ if (Len > 0) {
+ llvm::StringRef JSONRef(JSON.data(), Len);
+ // Log the message.
+ Out.log("<-- " + JSONRef + "\n");
+
+ // Finally, execute the action for this JSON message.
+ if (!Dispatcher.call(JSONRef))
+ Out.log("JSON dispatch failed!\n");
+
+ // If we're done, exit the loop.
+ if (IsDone)
+ break;
+ }
+ }
+}
diff --git a/clangd/JSONRPCDispatcher.h b/clangd/JSONRPCDispatcher.h
index 7040d2d0..13ad9c6f 100644
--- a/clangd/JSONRPCDispatcher.h
+++ b/clangd/JSONRPCDispatcher.h
@@ -79,6 +79,15 @@ private:
std::unique_ptr<Handler> UnknownHandler;
};
+/// Parses input queries from LSP client (coming from \p In) and runs call
+/// method of \p Dispatcher for each query.
+/// After handling each query checks if \p IsDone is set true and exits the loop
+/// if it is.
+/// Input stream(\p In) must be opened in binary mode to avoid preliminary
+/// replacements of \r\n with \n.
+void runLanguageServerLoop(std::istream &In, JSONOutput &Out,
+ JSONRPCDispatcher &Dispatcher, bool &IsDone);
+
} // namespace clangd
} // namespace clang
diff --git a/clangd/ProtocolHandlers.cpp b/clangd/ProtocolHandlers.cpp
index 66edee3d..264ef33e 100644
--- a/clangd/ProtocolHandlers.cpp
+++ b/clangd/ProtocolHandlers.cpp
@@ -8,204 +8,215 @@
//===----------------------------------------------------------------------===//
#include "ProtocolHandlers.h"
+#include "ClangdLSPServer.h"
#include "ClangdServer.h"
#include "DraftStore.h"
-#include "clang/Format/Format.h"
-#include "ClangdLSPServer.h"
using namespace clang;
using namespace clangd;
-void TextDocumentDidOpenHandler::handleNotification(
- llvm::yaml::MappingNode *Params) {
- auto DOTDP = DidOpenTextDocumentParams::parse(Params);
- if (!DOTDP) {
- Output.log("Failed to decode DidOpenTextDocumentParams!\n");
- return;
- }
- AST.openDocument(DOTDP->textDocument.uri.file, DOTDP->textDocument.text);
-}
+namespace {
-void TextDocumentDidCloseHandler::handleNotification(
- llvm::yaml::MappingNode *Params) {
- auto DCTDP = DidCloseTextDocumentParams::parse(Params);
- if (!DCTDP) {
- Output.log("Failed to decode DidCloseTextDocumentParams!\n");
- return;
+struct InitializeHandler : Handler {
+ InitializeHandler(JSONOutput &Output, ProtocolCallbacks &Callbacks)
+ : Handler(Output), Callbacks(Callbacks) {}
+
+ void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override {
+ Callbacks.onInitialize(ID, Output);
}
- AST.closeDocument(DCTDP->textDocument.uri.file);
-}
+private:
+ ProtocolCallbacks &Callbacks;
+};
-void TextDocumentDidChangeHandler::handleNotification(
- llvm::yaml::MappingNode *Params) {
- auto DCTDP = DidChangeTextDocumentParams::parse(Params);
- if (!DCTDP || DCTDP->contentChanges.size() != 1) {
- Output.log("Failed to decode DidChangeTextDocumentParams!\n");
- return;
+struct ShutdownHandler : Handler {
+ ShutdownHandler(JSONOutput &Output, ProtocolCallbacks &Callbacks)
+ : Handler(Output), Callbacks(Callbacks) {}
+
+ void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override {
+ Callbacks.onShutdown(Output);
}
- // We only support full syncing right now.
- AST.openDocument(DCTDP->textDocument.uri.file, DCTDP->contentChanges[0].text);
-}
-/// Turn a [line, column] pair into an offset in Code.
-static size_t positionToOffset(StringRef Code, Position P) {
- size_t Offset = 0;
- for (int I = 0; I != P.line; ++I) {
- // FIXME: \r\n
- // FIXME: UTF-8
- size_t F = Code.find('\n', Offset);
- if (F == StringRef::npos)
- return 0; // FIXME: Is this reasonable?
- Offset = F + 1;
+private:
+ ProtocolCallbacks &Callbacks;
+};
+
+struct TextDocumentDidOpenHandler : Handler {
+ TextDocumentDidOpenHandler(JSONOutput &Output, ProtocolCallbacks &Callbacks)
+ : Handler(Output), Callbacks(Callbacks) {}
+
+ void handleNotification(llvm::yaml::MappingNode *Params) override {
+ auto DOTDP = DidOpenTextDocumentParams::parse(Params);
+ if (!DOTDP) {
+ Output.log("Failed to decode DidOpenTextDocumentParams!\n");
+ return;
+ }
+ Callbacks.onDocumentDidOpen(*DOTDP, Output);
}
- return (Offset == 0 ? 0 : (Offset - 1)) + P.character;
-}
-/// Turn an offset in Code into a [line, column] pair.
-static Position offsetToPosition(StringRef Code, size_t Offset) {
- StringRef JustBefore = Code.substr(0, Offset);
- // FIXME: \r\n
- // FIXME: UTF-8
- int Lines = JustBefore.count('\n');
- int Cols = JustBefore.size() - JustBefore.rfind('\n') - 1;
- return {Lines, Cols};
-}
+private:
+ ProtocolCallbacks &Callbacks;
+};
+
+struct TextDocumentDidChangeHandler : Handler {
+ TextDocumentDidChangeHandler(JSONOutput &Output, ProtocolCallbacks &Callbacks)
+ : Handler(Output), Callbacks(Callbacks) {}
+
+ void handleNotification(llvm::yaml::MappingNode *Params) override {
+ auto DCTDP = DidChangeTextDocumentParams::parse(Params);
+ if (!DCTDP || DCTDP->contentChanges.size() != 1) {
+ Output.log("Failed to decode DidChangeTextDocumentParams!\n");
+ return;
+ }
-template <typename T>
-static std::string replacementsToEdits(StringRef Code, const T &Replacements) {
- // Turn the replacements into the format specified by the Language Server
- // Protocol. Fuse them into one big JSON array.
- std::string Edits;
- for (auto &R : Replacements) {
- Range ReplacementRange = {
- offsetToPosition(Code, R.getOffset()),
- offsetToPosition(Code, R.getOffset() + R.getLength())};
- TextEdit TE = {ReplacementRange, R.getReplacementText()};
- Edits += TextEdit::unparse(TE);
- Edits += ',';
+ Callbacks.onDocumentDidChange(*DCTDP, Output);
}
- if (!Edits.empty())
- Edits.pop_back();
- return Edits;
-}
+private:
+ ProtocolCallbacks &Callbacks;
+};
-static std::string formatCode(StringRef Code, StringRef Filename,
- ArrayRef<tooling::Range> Ranges, StringRef ID) {
- // Call clang-format.
- // FIXME: Don't ignore style.
- format::FormatStyle Style = format::getLLVMStyle();
- tooling::Replacements Replacements =
- format::reformat(Style, Code, Ranges, Filename);
-
- std::string Edits = replacementsToEdits(Code, Replacements);
- return R"({"jsonrpc":"2.0","id":)" + ID.str() +
- R"(,"result":[)" + Edits + R"(]})";
-}
+struct TextDocumentDidCloseHandler : Handler {
+ TextDocumentDidCloseHandler(JSONOutput &Output, ProtocolCallbacks &Callbacks)
+ : Handler(Output), Callbacks(Callbacks) {}
+
+ void handleNotification(llvm::yaml::MappingNode *Params) override {
+ auto DCTDP = DidCloseTextDocumentParams::parse(Params);
+ if (!DCTDP) {
+ Output.log("Failed to decode DidCloseTextDocumentParams!\n");
+ return;
+ }
-void TextDocumentRangeFormattingHandler::handleMethod(
- llvm::yaml::MappingNode *Params, StringRef ID) {
- auto DRFP = DocumentRangeFormattingParams::parse(Params);
- if (!DRFP) {
- Output.log("Failed to decode DocumentRangeFormattingParams!\n");
- return;
+ Callbacks.onDocumentDidClose(*DCTDP, Output);
}
- std::string Code = AST.getDocument(DRFP->textDocument.uri.file);
+private:
+ ProtocolCallbacks &Callbacks;
+};
- size_t Begin = positionToOffset(Code, DRFP->range.start);
- size_t Len = positionToOffset(Code, DRFP->range.end) - Begin;
+struct TextDocumentOnTypeFormattingHandler : Handler {
+ TextDocumentOnTypeFormattingHandler(JSONOutput &Output,
+ ProtocolCallbacks &Callbacks)
+ : Handler(Output), Callbacks(Callbacks) {}
- writeMessage(formatCode(Code, DRFP->textDocument.uri.file,
- {clang::tooling::Range(Begin, Len)}, ID));
-}
+ void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override {
+ auto DOTFP = DocumentOnTypeFormattingParams::parse(Params);
+ if (!DOTFP) {
+ Output.log("Failed to decode DocumentOnTypeFormattingParams!\n");
+ return;
+ }
-void TextDocumentOnTypeFormattingHandler::handleMethod(
- llvm::yaml::MappingNode *Params, StringRef ID) {
- auto DOTFP = DocumentOnTypeFormattingParams::parse(Params);
- if (!DOTFP) {
- Output.log("Failed to decode DocumentOnTypeFormattingParams!\n");
- return;
+ Callbacks.onDocumentOnTypeFormatting(*DOTFP, ID, Output);
}
- // Look for the previous opening brace from the character position and format
- // starting from there.
- std::string Code = AST.getDocument(DOTFP->textDocument.uri.file);
- size_t CursorPos = positionToOffset(Code, DOTFP->position);
- size_t PreviousLBracePos = StringRef(Code).find_last_of('{', CursorPos);
- if (PreviousLBracePos == StringRef::npos)
- PreviousLBracePos = CursorPos;
- size_t Len = 1 + CursorPos - PreviousLBracePos;
-
- writeMessage(formatCode(Code, DOTFP->textDocument.uri.file,
- {clang::tooling::Range(PreviousLBracePos, Len)}, ID));
-}
+private:
+ ProtocolCallbacks &Callbacks;
+};
-void TextDocumentFormattingHandler::handleMethod(
- llvm::yaml::MappingNode *Params, StringRef ID) {
- auto DFP = DocumentFormattingParams::parse(Params);
- if (!DFP) {
- Output.log("Failed to decode DocumentFormattingParams!\n");
- return;
- }
+struct TextDocumentRangeFormattingHandler : Handler {
+ TextDocumentRangeFormattingHandler(JSONOutput &Output,
+ ProtocolCallbacks &Callbacks)
+ : Handler(Output), Callbacks(Callbacks) {}
- // Format everything.
- std::string Code = AST.getDocument(DFP->textDocument.uri.file);
- writeMessage(formatCode(Code, DFP->textDocument.uri.file,
- {clang::tooling::Range(0, Code.size())}, ID));
-}
+ void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override {
+ auto DRFP = DocumentRangeFormattingParams::parse(Params);
+ if (!DRFP) {
+ Output.log("Failed to decode DocumentRangeFormattingParams!\n");
+ return;
+ }
-void CodeActionHandler::handleMethod(llvm::yaml::MappingNode *Params,
- StringRef ID) {
- auto CAP = CodeActionParams::parse(Params);
- if (!CAP) {
- Output.log("Failed to decode CodeActionParams!\n");
- return;
+ Callbacks.onDocumentRangeFormatting(*DRFP, ID, Output);
}
- // We provide a code action for each diagnostic at the requested location
- // which has FixIts available.
- std::string Code = AST.getDocument(CAP->textDocument.uri.file);
- std::string Commands;
- for (Diagnostic &D : CAP->context.diagnostics) {
- std::vector<clang::tooling::Replacement> Fixes = AST.getFixIts(CAP->textDocument.uri.file, D);
- std::string Edits = replacementsToEdits(Code, Fixes);
-
- if (!Edits.empty())
- Commands +=
- R"({"title":"Apply FixIt ')" + llvm::yaml::escape(D.message) +
- R"('", "command": "clangd.applyFix", "arguments": [")" +
- llvm::yaml::escape(CAP->textDocument.uri.uri) +
- R"(", [)" + Edits +
- R"(]]},)";
+private:
+ ProtocolCallbacks &Callbacks;
+};
+
+struct TextDocumentFormattingHandler : Handler {
+ TextDocumentFormattingHandler(JSONOutput &Output,
+ ProtocolCallbacks &Callbacks)
+ : Handler(Output), Callbacks(Callbacks) {}
+
+ void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override {
+ auto DFP = DocumentFormattingParams::parse(Params);
+ if (!DFP) {
+ Output.log("Failed to decode DocumentFormattingParams!\n");
+ return;
+ }
+
+ Callbacks.onDocumentFormatting(*DFP, ID, Output);
}
- if (!Commands.empty())
- Commands.pop_back();
- writeMessage(
- R"({"jsonrpc":"2.0","id":)" + ID.str() +
- R"(, "result": [)" + Commands +
- R"(]})");
-}
+private:
+ ProtocolCallbacks &Callbacks;
+};
+
+struct CodeActionHandler : Handler {
+ CodeActionHandler(JSONOutput &Output, ProtocolCallbacks &Callbacks)
+ : Handler(Output), Callbacks(Callbacks) {}
-void CompletionHandler::handleMethod(llvm::yaml::MappingNode *Params,
- StringRef ID) {
- auto TDPP = TextDocumentPositionParams::parse(Params);
- if (!TDPP) {
- Output.log("Failed to decode TextDocumentPositionParams!\n");
- return;
+ void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override {
+ auto CAP = CodeActionParams::parse(Params);
+ if (!CAP) {
+ Output.log("Failed to decode CodeActionParams!\n");
+ return;
+ }
+
+ Callbacks.onCodeAction(*CAP, ID, Output);
}
- auto Items = AST.codeComplete(TDPP->textDocument.uri.file, Position{TDPP->position.line,
- TDPP->position.character});
- std::string Completions;
- for (const auto &Item : Items) {
- Completions += CompletionItem::unparse(Item);
- Completions += ",";
+private:
+ ProtocolCallbacks &Callbacks;
+};
+
+struct CompletionHandler : Handler {
+ CompletionHandler(JSONOutput &Output, ProtocolCallbacks &Callbacks)
+ : Handler(Output), Callbacks(Callbacks) {}
+
+ void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override {
+ auto TDPP = TextDocumentPositionParams::parse(Params);
+ if (!TDPP) {
+ Output.log("Failed to decode TextDocumentPositionParams!\n");
+ return;
+ }
+
+ Callbacks.onCompletion(*TDPP, ID, Output);
}
- if (!Completions.empty())
- Completions.pop_back();
- writeMessage(
- R"({"jsonrpc":"2.0","id":)" + ID.str() +
- R"(,"result":[)" + Completions + R"(]})");
+
+private:
+ ProtocolCallbacks &Callbacks;
+};
+
+} // namespace
+
+void clangd::regiterCallbackHandlers(JSONRPCDispatcher &Dispatcher,
+ JSONOutput &Out,
+ ProtocolCallbacks &Callbacks) {
+ Dispatcher.registerHandler(
+ "initialize", llvm::make_unique<InitializeHandler>(Out, Callbacks));
+ Dispatcher.registerHandler(
+ "shutdown", llvm::make_unique<ShutdownHandler>(Out, Callbacks));
+ Dispatcher.registerHandler(
+ "textDocument/didOpen",
+ llvm::make_unique<TextDocumentDidOpenHandler>(Out, Callbacks));
+ Dispatcher.registerHandler(
+ "textDocument/didClose",
+ llvm::make_unique<TextDocumentDidCloseHandler>(Out, Callbacks));
+ Dispatcher.registerHandler(
+ "textDocument/didChange",
+ llvm::make_unique<TextDocumentDidChangeHandler>(Out, Callbacks));
+ Dispatcher.registerHandler(
+ "textDocument/rangeFormatting",
+ llvm::make_unique<TextDocumentRangeFormattingHandler>(Out, Callbacks));
+ Dispatcher.registerHandler(
+ "textDocument/onTypeFormatting",
+ llvm::make_unique<TextDocumentOnTypeFormattingHandler>(Out, Callbacks));
+ Dispatcher.registerHandler(
+ "textDocument/formatting",
+ llvm::make_unique<TextDocumentFormattingHandler>(Out, Callbacks));
+ Dispatcher.registerHandler(
+ "textDocument/codeAction",
+ llvm::make_unique<CodeActionHandler>(Out, Callbacks));
+ Dispatcher.registerHandler(
+ "textDocument/completion",
+ llvm::make_unique<CompletionHandler>(Out, Callbacks));
}
diff --git a/clangd/ProtocolHandlers.h b/clangd/ProtocolHandlers.h
index e957d355..2fb3db81 100644
--- a/clangd/ProtocolHandlers.h
+++ b/clangd/ProtocolHandlers.h
@@ -22,118 +22,34 @@
namespace clang {
namespace clangd {
-class ClangdLSPServer;
-class ClangdLSPServer;
-struct InitializeHandler : Handler {
- InitializeHandler(JSONOutput &Output) : Handler(Output) {}
-
- void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override {
- writeMessage(
- R"({"jsonrpc":"2.0","id":)" + ID +
- R"(,"result":{"capabilities":{
- "textDocumentSync": 1,
- "documentFormattingProvider": true,
- "documentRangeFormattingProvider": true,
- "documentOnTypeFormattingProvider": {"firstTriggerCharacter":"}","moreTriggerCharacter":[]},
- "codeActionProvider": true,
- "completionProvider": {"resolveProvider": false, "triggerCharacters": [".",">"]}
- }}})");
- }
-};
-
-struct ShutdownHandler : Handler {
- ShutdownHandler(JSONOutput &Output) : Handler(Output) {}
-
- void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override {
- IsDone = true;
- }
-
- bool isDone() const { return IsDone; }
-
-private:
- bool IsDone = false;
-};
-
-struct TextDocumentDidOpenHandler : Handler {
- TextDocumentDidOpenHandler(JSONOutput &Output, ClangdLSPServer &AST)
- : Handler(Output), AST(AST) {}
-
- void handleNotification(llvm::yaml::MappingNode *Params) override;
-
-private:
- ClangdLSPServer &AST;
-};
-
-struct TextDocumentDidChangeHandler : Handler {
- TextDocumentDidChangeHandler(JSONOutput &Output, ClangdLSPServer &AST)
- : Handler(Output), AST(AST) {}
-
- void handleNotification(llvm::yaml::MappingNode *Params) override;
-
-private:
- ClangdLSPServer &AST;
-};
-
-struct TextDocumentDidCloseHandler : Handler {
- TextDocumentDidCloseHandler(JSONOutput &Output, ClangdLSPServer &AST)
- : Handler(Output), AST(AST) {}
-
- void handleNotification(llvm::yaml::MappingNode *Params) override;
-
-private:
- ClangdLSPServer &AST;
-};
-
-struct TextDocumentOnTypeFormattingHandler : Handler {
- TextDocumentOnTypeFormattingHandler(JSONOutput &Output, ClangdLSPServer &AST)
- : Handler(Output), AST(AST) {}
-
- void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override;
-
-private:
- ClangdLSPServer &AST;
-};
-
-struct TextDocumentRangeFormattingHandler : Handler {
- TextDocumentRangeFormattingHandler(JSONOutput &Output, ClangdLSPServer &AST)
- : Handler(Output), AST(AST) {}
-
- void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override;
-
-private:
- ClangdLSPServer &AST;
-};
-
-struct TextDocumentFormattingHandler : Handler {
- TextDocumentFormattingHandler(JSONOutput &Output, ClangdLSPServer &AST)
- : Handler(Output), AST(AST) {}
-
- void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override;
-
-private:
- ClangdLSPServer &AST;
-};
-
-struct CodeActionHandler : Handler {
- CodeActionHandler(JSONOutput &Output, ClangdLSPServer &AST)
- : Handler(Output), AST(AST) {}
-
- void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override;
-
-private:
- ClangdLSPServer &AST;
-};
-
-struct CompletionHandler : Handler {
- CompletionHandler(JSONOutput &Output, ClangdLSPServer &AST)
- : Handler(Output), AST(AST) {}
-
- void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override;
-
- private:
- ClangdLSPServer &AST;
-};
+class ProtocolCallbacks {
+public:
+ virtual ~ProtocolCallbacks() = default;
+
+ virtual void onInitialize(StringRef ID, JSONOutput &Out) = 0;
+ virtual void onShutdown(JSONOutput &Out) = 0;
+ virtual void onDocumentDidOpen(DidOpenTextDocumentParams Params,
+ JSONOutput &Out) = 0;
+ virtual void onDocumentDidChange(DidChangeTextDocumentParams Params,
+ JSONOutput &Out) = 0;
+
+ virtual void onDocumentDidClose(DidCloseTextDocumentParams Params,
+ JSONOutput &Out) = 0;
+ virtual void onDocumentFormatting(DocumentFormattingParams Params,
+ StringRef ID, JSONOutput &Out) = 0;
+ virtual void onDocumentOnTypeFormatting(DocumentOnTypeFormattingParams Params,
+ StringRef ID, JSONOutput &Out) = 0;
+ virtual void onDocumentRangeFormatting(DocumentRangeFormattingParams Params,
+ StringRef ID, JSONOutput &Out) = 0;
+ virtual void onCodeAction(CodeActionParams Params, StringRef ID,
+ JSONOutput &Out) = 0;
+ virtual void onCompletion(TextDocumentPositionParams Params, StringRef ID,
+ JSONOutput &Out) = 0;
+};
+
+void regiterCallbackHandlers(JSONRPCDispatcher &Dispatcher, JSONOutput &Out,
+ ProtocolCallbacks &Callbacks);
} // namespace clangd
} // namespace clang