mirror of
https://github.com/MaskRay/ccls.git
synced 2025-06-08 17:24:56 +00:00
Delete method.{cc,h} Rename $ccls/setSkippedRanges to $ccls/publishSkippedRanges Rename $ccls/publishSemanticHighlighting to $ccls/publishSemanticHighlight; stableId -> id
287 lines
7.9 KiB
C++
287 lines
7.9 KiB
C++
/* 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 <rapidjson/document.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
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<std::string>
|
|
ReadJsonRpcContentFrom(std::function<std::optional<char>()> 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<char> 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<char> 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<char> c = read();
|
|
if (!c) {
|
|
LOG_S(INFO) << "No more input when reading content body";
|
|
return std::nullopt;
|
|
}
|
|
content += *c;
|
|
}
|
|
|
|
return content;
|
|
}
|
|
|
|
std::optional<char> 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<std::string>
|
|
MessageRegistry::ReadMessageFromStdin(std::unique_ptr<InMessage> *message) {
|
|
std::optional<std::string> 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<std::string>
|
|
MessageRegistry::Parse(Reader &visitor, std::unique_ptr<InMessage> *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<JsonReader &>(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);
|
|
}
|
|
}
|