From afb555473ee95bbcf9d0524de4c9904b463d1d8e Mon Sep 17 00:00:00 2001 From: Ilya Biryukov Date: Tue, 16 May 2017 14:40:30 +0000 Subject: [PATCH] [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 llvm-svn: 303173 --- clang-tools-extra/clangd/ClangdLSPServer.cpp | 194 +++++++++++++- clang-tools-extra/clangd/ClangdLSPServer.h | 42 +-- clang-tools-extra/clangd/ClangdMain.cpp | 90 +------ clang-tools-extra/clangd/ClangdServer.cpp | 67 +++++ clang-tools-extra/clangd/ClangdServer.h | 19 +- clang-tools-extra/clangd/JSONRPCDispatcher.cpp | 58 +++++ clang-tools-extra/clangd/JSONRPCDispatcher.h | 9 + clang-tools-extra/clangd/ProtocolHandlers.cpp | 339 +++++++++++++------------ clang-tools-extra/clangd/ProtocolHandlers.h | 138 ++-------- 9 files changed, 549 insertions(+), 407 deletions(-) diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp index 2a2e0bf..dcd2371 100644 --- a/clang-tools-extra/clangd/ClangdLSPServer.cpp +++ b/clang-tools-extra/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 &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 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(), llvm::make_unique(*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(Out)); + regiterCallbackHandlers(Dispatcher, Out, Callbacks); -std::vector 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 @@ -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 Diagnostics) { std::string DiagnosticsJSON; diff --git a/clang-tools-extra/clangd/ClangdLSPServer.h b/clang-tools-extra/clangd/ClangdLSPServer.h index 108d8d7..ee3ada3 100644 --- a/clang-tools-extra/clangd/ClangdLSPServer.h +++ b/clang-tools-extra/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 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 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 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> diff --git a/clang-tools-extra/clangd/ClangdMain.cpp b/clang-tools-extra/clangd/ClangdMain.cpp index 44f8665..d67f75b 100644 --- a/clang-tools-extra/clangd/ClangdMain.cpp +++ b/clang-tools-extra/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 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(Out)); - Dispatcher.registerHandler("initialize", - llvm::make_unique(Out)); - auto ShutdownPtr = llvm::make_unique(Out); - auto *ShutdownHandler = ShutdownPtr.get(); - Dispatcher.registerHandler("shutdown", std::move(ShutdownPtr)); - Dispatcher.registerHandler( - "textDocument/didOpen", - llvm::make_unique(Out, LSPServer)); - Dispatcher.registerHandler( - "textDocument/didClose", - llvm::make_unique(Out, LSPServer)); - Dispatcher.registerHandler( - "textDocument/didChange", - llvm::make_unique(Out, LSPServer)); - Dispatcher.registerHandler( - "textDocument/rangeFormatting", - llvm::make_unique(Out, LSPServer)); - Dispatcher.registerHandler( - "textDocument/onTypeFormatting", - llvm::make_unique(Out, LSPServer)); - Dispatcher.registerHandler( - "textDocument/formatting", - llvm::make_unique(Out, LSPServer)); - Dispatcher.registerHandler("textDocument/codeAction", - llvm::make_unique(Out, LSPServer)); - Dispatcher.registerHandler("textDocument/completion", - llvm::make_unique(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 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/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp index e0f0af7..a228cfa 100644 --- a/clang-tools-extra/clangd/ClangdServer.cpp +++ b/clang-tools-extra/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 formatCode(StringRef Code, StringRef Filename, + ArrayRef 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(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 ClangdServer::codeComplete(PathRef File, }); return Result; } +std::vector 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 ClangdServer::formatFile(PathRef File) { + // Format everything. + std::string Code = getDocument(File); + return formatCode(Code, File, {tooling::Range(0, Code.size())}); +} + +std::vector 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/clang-tools-extra/clangd/ClangdServer.h b/clang-tools-extra/clangd/ClangdServer.h index 72b6bd5..f4ce284 100644 --- a/clang-tools-extra/clangd/ClangdServer.h +++ b/clang-tools-extra/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 codeComplete(PathRef File, Position Pos); + /// Run formatting for \p Rng inside \p File. + std::vector formatRange(PathRef File, Range Rng); + /// Run formatting for the whole \p File. + std::vector formatFile(PathRef File); + /// Run formatting after a character was typed at \p Pos in \p File. + std::vector 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/clang-tools-extra/clangd/JSONRPCDispatcher.cpp b/clang-tools-extra/clangd/JSONRPCDispatcher.cpp index f3045fb..be6be77 100644 --- a/clang-tools-extra/clangd/JSONRPCDispatcher.cpp +++ b/clang-tools-extra/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 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/clang-tools-extra/clangd/JSONRPCDispatcher.h b/clang-tools-extra/clangd/JSONRPCDispatcher.h index 7040d2d..13ad9c6 100644 --- a/clang-tools-extra/clangd/JSONRPCDispatcher.h +++ b/clang-tools-extra/clangd/JSONRPCDispatcher.h @@ -79,6 +79,15 @@ private: std::unique_ptr 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/clang-tools-extra/clangd/ProtocolHandlers.cpp b/clang-tools-extra/clangd/ProtocolHandlers.cpp index 66edee3..264ef33 100644 --- a/clang-tools-extra/clangd/ProtocolHandlers.cpp +++ b/clang-tools-extra/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 -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 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 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(Out, Callbacks)); + Dispatcher.registerHandler( + "shutdown", llvm::make_unique(Out, Callbacks)); + Dispatcher.registerHandler( + "textDocument/didOpen", + llvm::make_unique(Out, Callbacks)); + Dispatcher.registerHandler( + "textDocument/didClose", + llvm::make_unique(Out, Callbacks)); + Dispatcher.registerHandler( + "textDocument/didChange", + llvm::make_unique(Out, Callbacks)); + Dispatcher.registerHandler( + "textDocument/rangeFormatting", + llvm::make_unique(Out, Callbacks)); + Dispatcher.registerHandler( + "textDocument/onTypeFormatting", + llvm::make_unique(Out, Callbacks)); + Dispatcher.registerHandler( + "textDocument/formatting", + llvm::make_unique(Out, Callbacks)); + Dispatcher.registerHandler( + "textDocument/codeAction", + llvm::make_unique(Out, Callbacks)); + Dispatcher.registerHandler( + "textDocument/completion", + llvm::make_unique(Out, Callbacks)); } diff --git a/clang-tools-extra/clangd/ProtocolHandlers.h b/clang-tools-extra/clangd/ProtocolHandlers.h index e957d35..2fb3db8 100644 --- a/clang-tools-extra/clangd/ProtocolHandlers.h +++ b/clang-tools-extra/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 -- 2.7.4