/* Copyright 2017-2018 ccls Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ #include "lsp.h" #include "log.hh" #include "serializers/json.h" #include #include MethodType kMethodType_Exit = "exit"; void Reflect(Reader &visitor, lsRequestId &value) { if (visitor.IsInt64()) { value.type = lsRequestId::kInt; value.value = int(visitor.GetInt64()); } else if (visitor.IsInt()) { value.type = lsRequestId::kInt; value.value = visitor.GetInt(); } else if (visitor.IsString()) { value.type = lsRequestId::kString; value.value = atoll(visitor.GetString()); } else { value.type = lsRequestId::kNone; value.value = -1; } } void Reflect(Writer &visitor, lsRequestId &value) { switch (value.type) { case lsRequestId::kNone: visitor.Null(); break; case lsRequestId::kInt: visitor.Int(value.value); break; case lsRequestId::kString: auto s = std::to_string(value.value); visitor.String(s.c_str(), s.length()); break; } } InMessage::~InMessage() {} MessageRegistry *MessageRegistry::instance_ = nullptr; lsTextDocumentIdentifier lsVersionedTextDocumentIdentifier::AsTextDocumentIdentifier() const { lsTextDocumentIdentifier result; result.uri = uri; return result; } // Reads a JsonRpc message. |read| returns the next input character. std::optional ReadJsonRpcContentFrom(std::function()> read) { // Read the content length. It is terminated by the "\r\n" sequence. int exit_seq = 0; std::string stringified_content_length; while (true) { std::optional opt_c = read(); if (!opt_c) { LOG_S(INFO) << "No more input when reading content length header"; return std::nullopt; } char c = *opt_c; if (exit_seq == 0 && c == '\r') ++exit_seq; if (exit_seq == 1 && c == '\n') break; stringified_content_length += c; } const char *kContentLengthStart = "Content-Length: "; assert(StartsWith(stringified_content_length, kContentLengthStart)); int content_length = atoi(stringified_content_length.c_str() + strlen(kContentLengthStart)); // There is always a "\r\n" sequence before the actual content. auto expect_char = [&](char expected) { std::optional opt_c = read(); return opt_c && *opt_c == expected; }; if (!expect_char('\r') || !expect_char('\n')) { LOG_S(INFO) << "Unexpected token (expected \\r\\n sequence)"; return std::nullopt; } // Read content. std::string content; content.reserve(content_length); for (int i = 0; i < content_length; ++i) { std::optional c = read(); if (!c) { LOG_S(INFO) << "No more input when reading content body"; return std::nullopt; } content += *c; } return content; } std::optional ReadCharFromStdinBlocking() { // We do not use std::cin because it does not read bytes once stuck in // cin.bad(). We can call cin.clear() but C++ iostream has other annoyance // like std::{cin,cout} is tied by default, which causes undesired cout flush // for cin operations. int c = getchar(); if (c >= 0) return c; return std::nullopt; } std::optional MessageRegistry::ReadMessageFromStdin(std::unique_ptr *message) { std::optional content = ReadJsonRpcContentFrom(&ReadCharFromStdinBlocking); if (!content) { LOG_S(ERROR) << "Failed to read JsonRpc input; exiting"; exit(1); } rapidjson::Document document; document.Parse(content->c_str(), content->length()); assert(!document.HasParseError()); JsonReader json_reader{&document}; return Parse(json_reader, message); } std::optional MessageRegistry::Parse(Reader &visitor, std::unique_ptr *message) { if (!visitor.HasMember("jsonrpc") || std::string(visitor["jsonrpc"]->GetString()) != "2.0") { LOG_S(FATAL) << "Bad or missing jsonrpc version"; exit(1); } std::string method; ReflectMember(visitor, "method", method); if (allocators.find(method) == allocators.end()) return std::string("Unable to find registered handler for method '") + method + "'"; Allocator &allocator = allocators[method]; try { allocator(visitor, message); return std::nullopt; } catch (std::invalid_argument &e) { // *message is partially deserialized but some field (e.g. |id|) are likely // available. return std::string("Fail to parse '") + method + "' " + static_cast(visitor).GetPath() + ", expected " + e.what(); } } MessageRegistry *MessageRegistry::instance() { if (!instance_) instance_ = new MessageRegistry(); return instance_; } lsDocumentUri lsDocumentUri::FromPath(const std::string &path) { lsDocumentUri result; result.SetPath(path); return result; } bool lsDocumentUri::operator==(const lsDocumentUri &other) const { return raw_uri == other.raw_uri; } void lsDocumentUri::SetPath(const std::string &path) { // file:///c%3A/Users/jacob/Desktop/superindex/indexer/full_tests raw_uri = path; size_t index = raw_uri.find(":"); if (index == 1) { // widows drive letters must always be 1 char raw_uri.replace(raw_uri.begin() + index, raw_uri.begin() + index + 1, "%3A"); } // subset of reserved characters from the URI standard // http://www.ecma-international.org/ecma-262/6.0/#sec-uri-syntax-and-semantics std::string t; t.reserve(8 + raw_uri.size()); // TODO: proper fix #if defined(_WIN32) t += "file:///"; #else t += "file://"; #endif // clang-format off for (char c : raw_uri) switch (c) { case ' ': t += "%20"; break; case '#': t += "%23"; break; case '$': t += "%24"; break; case '&': t += "%26"; break; case '(': t += "%28"; break; case ')': t += "%29"; break; case '+': t += "%2B"; break; case ',': t += "%2C"; break; case ';': t += "%3B"; break; case '?': t += "%3F"; break; case '@': t += "%40"; break; default: t += c; break; } // clang-format on raw_uri = std::move(t); } std::string lsDocumentUri::GetPath() const { if (raw_uri.compare(0, 7, "file://")) { LOG_S(WARNING) << "Received potentially bad URI (not starting with file://): " << raw_uri; return raw_uri; } std::string ret; #ifdef _WIN32 // Skipping the initial "/" on Windows size_t i = 8; #else size_t i = 7; #endif auto from_hex = [](unsigned char c) { return c - '0' < 10 ? c - '0' : (c | 32) - 'a' + 10; }; for (; i < raw_uri.size(); i++) { if (i + 3 <= raw_uri.size() && raw_uri[i] == '%') { ret.push_back(from_hex(raw_uri[i + 1]) * 16 + from_hex(raw_uri[i + 2])); i += 2; } else ret.push_back(raw_uri[i]); } #ifdef _WIN32 std::replace(ret.begin(), ret.end(), '\\', '/'); if (ret.size() > 1 && ret[0] >= 'a' && ret[0] <= 'z' && ret[1] == ':') { ret[0] = toupper(ret[0]); } #endif return ret; } std::string lsPosition::ToString() const { return std::to_string(line) + ":" + std::to_string(character); } bool lsTextEdit::operator==(const lsTextEdit &that) { return range == that.range && newText == that.newText; } void Reflect(Writer &visitor, lsMarkedString &value) { // If there is a language, emit a `{language:string, value:string}` object. If // not, emit a string. if (value.language) { REFLECT_MEMBER_START(); REFLECT_MEMBER(language); REFLECT_MEMBER(value); REFLECT_MEMBER_END(); } else { Reflect(visitor, value.value); } }