diff options
Diffstat (limited to 'clang-tools-extra/clangd')
-rw-r--r-- | clang-tools-extra/clangd/ClangdLSPServer.cpp | 4 | ||||
-rw-r--r-- | clang-tools-extra/clangd/ClangdLSPServer.h | 3 | ||||
-rw-r--r-- | clang-tools-extra/clangd/JSONRPCDispatcher.cpp | 203 | ||||
-rw-r--r-- | clang-tools-extra/clangd/JSONRPCDispatcher.h | 9 | ||||
-rw-r--r-- | clang-tools-extra/clangd/tool/ClangdMain.cpp | 22 |
5 files changed, 155 insertions, 86 deletions
diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp index 38dfcb8e7b9..50dbbce89ad 100644 --- a/clang-tools-extra/clangd/ClangdLSPServer.cpp +++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp @@ -332,7 +332,7 @@ ClangdLSPServer::ClangdLSPServer(JSONOutput &Out, unsigned AsyncThreadsCount, StorePreamblesInMemory, BuildDynamicSymbolIndex, StaticIdx, ResourceDir) {} -bool ClangdLSPServer::run(std::istream &In) { +bool ClangdLSPServer::run(std::istream &In, JSONStreamStyle InputStyle) { assert(!IsDone && "Run was called before"); // Set up JSONRPCDispatcher. @@ -342,7 +342,7 @@ bool ClangdLSPServer::run(std::istream &In) { registerCallbackHandlers(Dispatcher, Out, /*Callbacks=*/*this); // Run the Language Server loop. - runLanguageServerLoop(In, Out, Dispatcher, IsDone); + runLanguageServerLoop(In, Out, InputStyle, 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. diff --git a/clang-tools-extra/clangd/ClangdLSPServer.h b/clang-tools-extra/clangd/ClangdLSPServer.h index 6ece4572cbd..fdc9cd6f3a0 100644 --- a/clang-tools-extra/clangd/ClangdLSPServer.h +++ b/clang-tools-extra/clangd/ClangdLSPServer.h @@ -45,7 +45,8 @@ public: /// each instance of ClangdLSPServer. /// /// \return Wether we received a 'shutdown' request before an 'exit' request - bool run(std::istream &In); + bool run(std::istream &In, + JSONStreamStyle InputStyle = JSONStreamStyle::Standard); private: // Implement DiagnosticsConsumer. diff --git a/clang-tools-extra/clangd/JSONRPCDispatcher.cpp b/clang-tools-extra/clangd/JSONRPCDispatcher.cpp index 24cbd1e86a7..4a7250256ca 100644 --- a/clang-tools-extra/clangd/JSONRPCDispatcher.cpp +++ b/clang-tools-extra/clangd/JSONRPCDispatcher.cpp @@ -180,89 +180,132 @@ bool JSONRPCDispatcher::call(const json::Expr &Message, JSONOutput &Out) const { return true; } -void clangd::runLanguageServerLoop(std::istream &In, JSONOutput &Out, - JSONRPCDispatcher &Dispatcher, - bool &IsDone) { +static llvm::Optional<std::string> readStandardMessage(std::istream &In, + JSONOutput &Out) { + // A Language Server Protocol message starts with a set of HTTP headers, + // delimited by \r\n, and terminated by an empty line (\r\n). + unsigned long long ContentLength = 0; while (In.good()) { - // A Language Server Protocol message starts with a set of HTTP headers, - // delimited by \r\n, and terminated by an empty line (\r\n). - unsigned long long ContentLength = 0; - while (In.good()) { - std::string Line; - std::getline(In, Line); - if (!In.good() && errno == EINTR) { - In.clear(); - continue; - } + std::string Line; + std::getline(In, Line); + if (!In.good() && errno == EINTR) { + In.clear(); + continue; + } - Out.mirrorInput(Line); - // Mirror '\n' that gets consumed by std::getline, but is not included in - // the resulting Line. - // Note that '\r' is part of Line, so we don't need to mirror it - // separately. - if (!In.eof()) - Out.mirrorInput("\n"); - - llvm::StringRef LineRef(Line); - - // We allow comments in headers. Technically this isn't part - // of the LSP specification, but makes writing tests easier. - if (LineRef.startswith("#")) - continue; - - // Content-Type is a specified header, but does nothing. - // Content-Length is a mandatory header. It specifies the length of the - // following JSON. - // It is unspecified what sequence headers must be supplied in, so we - // allow any sequence. - // The end of headers is signified by an empty line. - if (LineRef.consume_front("Content-Length: ")) { - if (ContentLength != 0) { - log("Warning: Duplicate Content-Length header received. " - "The previous value for this message (" + - llvm::Twine(ContentLength) + ") was ignored.\n"); - } - - llvm::getAsUnsignedInteger(LineRef.trim(), 0, ContentLength); - continue; - } else if (!LineRef.trim().empty()) { - // It's another header, ignore it. - continue; - } else { - // An empty line indicates the end of headers. - // Go ahead and read the JSON. - break; + Out.mirrorInput(Line); + // Mirror '\n' that gets consumed by std::getline, but is not included in + // the resulting Line. + // Note that '\r' is part of Line, so we don't need to mirror it + // separately. + if (!In.eof()) + Out.mirrorInput("\n"); + + llvm::StringRef LineRef(Line); + + // We allow comments in headers. Technically this isn't part + // of the LSP specification, but makes writing tests easier. + if (LineRef.startswith("#")) + continue; + + // Content-Type is a specified header, but does nothing. + // Content-Length is a mandatory header. It specifies the length of the + // following JSON. + // It is unspecified what sequence headers must be supplied in, so we + // allow any sequence. + // The end of headers is signified by an empty line. + if (LineRef.consume_front("Content-Length: ")) { + if (ContentLength != 0) { + log("Warning: Duplicate Content-Length header received. " + "The previous value for this message (" + + llvm::Twine(ContentLength) + ") was ignored.\n"); } + + llvm::getAsUnsignedInteger(LineRef.trim(), 0, ContentLength); + continue; + } else if (!LineRef.trim().empty()) { + // It's another header, ignore it. + continue; + } else { + // An empty line indicates the end of headers. + // Go ahead and read the JSON. + break; } + } - // Guard against large messages. This is usually a bug in the client code - // and we don't want to crash downstream because of it. - if (ContentLength > 1 << 30) { // 1024M - In.ignore(ContentLength); - log("Skipped overly large message of " + Twine(ContentLength) + - " bytes.\n"); + // Guard against large messages. This is usually a bug in the client code + // and we don't want to crash downstream because of it. + if (ContentLength > 1 << 30) { // 1024M + In.ignore(ContentLength); + log("Skipped overly large message of " + Twine(ContentLength) + + " bytes.\n"); + return llvm::None; + } + + if (ContentLength > 0) { + std::string JSON(ContentLength, '\0'); + llvm::StringRef JSONRef; + In.read(&JSON[0], ContentLength); + Out.mirrorInput(StringRef(JSON.data(), In.gcount())); + + // If the stream is aborted before we read ContentLength bytes, In + // will have eofbit and failbit set. + if (!In) { + log("Input was aborted. Read only " + llvm::Twine(In.gcount()) + + " bytes of expected " + llvm::Twine(ContentLength) + ".\n"); + return llvm::None; + } + return std::move(JSON); + } else { + log("Warning: Missing Content-Length header, or message has zero " + "length.\n"); + return llvm::None; + } +} + +// For lit tests we support a simplified syntax: +// - messages are delimited by '---' on a line by itself +// - lines starting with # are ignored. +// This is a testing path, so favor simplicity over performance here. +static llvm::Optional<std::string> readDelimitedMessage(std::istream &In, + JSONOutput &Out) { + std::string JSON; + std::string Line; + while (std::getline(In, Line)) { + if (!In.eof()) // getline() consumed the newline. + Line.push_back('\n'); + auto LineRef = llvm::StringRef(Line).trim(); + if (LineRef.startswith("#")) // comment continue; + + bool IsDelim = LineRef.find_first_not_of('-') == llvm::StringRef::npos; + if (!IsDelim) // Line is part of a JSON message. + JSON += Line; + if (IsDelim || In.eof()) { + Out.mirrorInput( + llvm::formatv("Content-Length: {0}\r\n\r\n{1}", JSON.size(), JSON)); + return std::move(JSON); } + } - if (ContentLength > 0) { - std::vector<char> JSON(ContentLength); - llvm::StringRef JSONRef; - { - In.read(JSON.data(), ContentLength); - Out.mirrorInput(StringRef(JSON.data(), In.gcount())); - - // If the stream is aborted before we read ContentLength bytes, In - // will have eofbit and failbit set. - if (!In) { - log("Input was aborted. Read only " + llvm::Twine(In.gcount()) + - " bytes of expected " + llvm::Twine(ContentLength) + ".\n"); - break; - } - - JSONRef = StringRef(JSON.data(), ContentLength); - } + if (In.bad()) { + log("Input error while reading message!"); + return llvm::None; + } else { + log("Input message terminated by EOF"); + return std::move(JSON); + } +} - if (auto Doc = json::parse(JSONRef)) { +void clangd::runLanguageServerLoop(std::istream &In, JSONOutput &Out, + JSONStreamStyle InputStyle, + JSONRPCDispatcher &Dispatcher, + bool &IsDone) { + auto &ReadMessage = + (InputStyle == Delimited) ? readDelimitedMessage : readStandardMessage; + while (In.good()) { + if (auto JSON = ReadMessage(In, Out)) { + if (auto Doc = json::parse(*JSON)) { // Log the formatted message. log(llvm::formatv(Out.Pretty ? "<-- {0:2}\n" : "<-- {0}\n", *Doc)); // Finally, execute the action for this JSON message. @@ -270,17 +313,13 @@ void clangd::runLanguageServerLoop(std::istream &In, JSONOutput &Out, log("JSON dispatch failed!\n"); } else { // Parse error. Log the raw message. - log("<-- " + JSONRef + "\n"); + log(llvm::formatv("<-- {0}\n" , *JSON)); log(llvm::Twine("JSON parse error: ") + llvm::toString(Doc.takeError()) + "\n"); } - - // If we're done, exit the loop. - if (IsDone) - break; - } else { - log("Warning: Missing Content-Length header, or message has zero " - "length.\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 d7fea0f5c00..ce7e744eea9 100644 --- a/clang-tools-extra/clangd/JSONRPCDispatcher.h +++ b/clang-tools-extra/clangd/JSONRPCDispatcher.h @@ -88,6 +88,14 @@ private: Handler UnknownHandler; }; +/// Controls the way JSON-RPC messages are encoded (both input and output). +enum JSONStreamStyle { + /// Encoding per the LSP specification, with mandatory Content-Length header. + Standard, + /// Messages are delimited by a '---' line. Comment lines start with #. + Delimited +}; + /// 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 @@ -95,6 +103,7 @@ private: /// 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, + JSONStreamStyle InputStyle, JSONRPCDispatcher &Dispatcher, bool &IsDone); } // namespace clangd diff --git a/clang-tools-extra/clangd/tool/ClangdMain.cpp b/clang-tools-extra/clangd/tool/ClangdMain.cpp index 12a52a31c81..6dfcd75b96e 100644 --- a/clang-tools-extra/clangd/tool/ClangdMain.cpp +++ b/clang-tools-extra/clangd/tool/ClangdMain.cpp @@ -73,10 +73,25 @@ static llvm::cl::opt<bool> IncludeIneligibleResults( llvm::cl::init(clangd::CodeCompleteOptions().IncludeIneligibleResults), llvm::cl::Hidden); +static llvm::cl::opt<JSONStreamStyle> InputStyle( + "input-style", llvm::cl::desc("Input JSON stream encoding"), + llvm::cl::values( + clEnumValN(JSONStreamStyle::Standard, "standard", "usual LSP protocol"), + clEnumValN(JSONStreamStyle::Delimited, "delimited", + "messages delimited by --- lines, with # comment support")), + llvm::cl::init(JSONStreamStyle::Standard)); + static llvm::cl::opt<bool> PrettyPrint("pretty", llvm::cl::desc("Pretty-print JSON output"), llvm::cl::init(false)); +static llvm::cl::opt<bool> Test( + "lit-test", + llvm::cl::desc( + "Abbreviation for -input-style=delimited -pretty -run-synchronously. " + "Intended to simplify lit tests."), + llvm::cl::init(false), llvm::cl::Hidden); + static llvm::cl::opt<PCHStorageFlag> PCHStorage( "pch-storage", llvm::cl::desc("Storing PCHs in memory increases memory usages, but may " @@ -132,6 +147,11 @@ static llvm::cl::opt<Path> YamlSymbolFile( int main(int argc, char *argv[]) { llvm::cl::ParseCommandLineOptions(argc, argv, "clangd"); + if (Test) { + RunSynchronously = true; + InputStyle = JSONStreamStyle::Delimited; + PrettyPrint = true; + } if (!RunSynchronously && WorkerThreadsCount == 0) { llvm::errs() << "A number of worker threads cannot be 0. Did you mean to " @@ -228,5 +248,5 @@ int main(int argc, char *argv[]) { EnableIndexBasedCompletion, StaticIdx.get()); constexpr int NoShutdownRequestErrorCode = 1; llvm::set_thread_name("clangd.main"); - return LSPServer.run(std::cin) ? 0 : NoShutdownRequestErrorCode; + return LSPServer.run(std::cin, InputStyle) ? 0 : NoShutdownRequestErrorCode; } |