summaryrefslogtreecommitdiffstats
path: root/clang-tools-extra/clangd
diff options
context:
space:
mode:
Diffstat (limited to 'clang-tools-extra/clangd')
-rw-r--r--clang-tools-extra/clangd/ClangdLSPServer.cpp4
-rw-r--r--clang-tools-extra/clangd/ClangdLSPServer.h3
-rw-r--r--clang-tools-extra/clangd/JSONRPCDispatcher.cpp203
-rw-r--r--clang-tools-extra/clangd/JSONRPCDispatcher.h9
-rw-r--r--clang-tools-extra/clangd/tool/ClangdMain.cpp22
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;
}
OpenPOWER on IntegriCloud