2017-03-25 22:13:19 +00:00
|
|
|
#include "language_server_api.h"
|
|
|
|
|
2017-09-13 03:35:27 +00:00
|
|
|
#include <doctest/doctest.h>
|
|
|
|
#include <loguru.hpp>
|
|
|
|
|
2017-03-25 22:13:19 +00:00
|
|
|
void Reflect(Writer& visitor, lsRequestId& value) {
|
|
|
|
assert(value.id0.has_value() || value.id1.has_value());
|
|
|
|
|
|
|
|
if (value.id0) {
|
|
|
|
Reflect(visitor, value.id0.value());
|
2017-09-22 01:14:57 +00:00
|
|
|
} else {
|
2017-03-25 22:13:19 +00:00
|
|
|
Reflect(visitor, value.id1.value());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Reflect(Reader& visitor, lsRequestId& id) {
|
|
|
|
if (visitor.IsInt())
|
|
|
|
Reflect(visitor, id.id0);
|
|
|
|
else if (visitor.IsString())
|
|
|
|
Reflect(visitor, id.id1);
|
|
|
|
else
|
|
|
|
std::cerr << "Unable to deserialize id" << std::endl;
|
|
|
|
}
|
|
|
|
|
|
|
|
MessageRegistry* MessageRegistry::instance_ = nullptr;
|
|
|
|
|
2017-09-22 02:25:33 +00:00
|
|
|
lsTextDocumentIdentifier
|
|
|
|
lsVersionedTextDocumentIdentifier::AsTextDocumentIdentifier() const {
|
|
|
|
lsTextDocumentIdentifier result;
|
|
|
|
result.uri = uri;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2017-09-13 03:35:27 +00:00
|
|
|
// Reads a JsonRpc message. |read| returns the next input character.
|
2017-09-22 01:14:57 +00:00
|
|
|
optional<std::string> ReadJsonRpcContentFrom(
|
|
|
|
std::function<optional<char>()> read) {
|
2017-09-13 03:35:27 +00:00
|
|
|
// Read the content length. It is terminated by the "\r\n" sequence.
|
|
|
|
int exit_seq = 0;
|
|
|
|
std::string stringified_content_length;
|
2017-03-25 22:13:19 +00:00
|
|
|
while (true) {
|
2017-09-13 03:35:27 +00:00
|
|
|
optional<char> opt_c = read();
|
|
|
|
if (!opt_c) {
|
|
|
|
LOG_S(INFO) << "No more input when reading content length header";
|
|
|
|
return nullopt;
|
2017-03-25 22:13:19 +00:00
|
|
|
}
|
2017-09-13 03:35:27 +00:00
|
|
|
char c = *opt_c;
|
2017-03-25 22:13:19 +00:00
|
|
|
|
2017-09-22 01:14:57 +00:00
|
|
|
if (exit_seq == 0 && c == '\r')
|
|
|
|
++exit_seq;
|
|
|
|
if (exit_seq == 1 && c == '\n')
|
|
|
|
break;
|
2017-09-13 17:53:13 +00:00
|
|
|
|
2017-09-13 03:35:27 +00:00
|
|
|
stringified_content_length += c;
|
|
|
|
}
|
2017-09-13 17:53:13 +00:00
|
|
|
const char* kContentLengthStart = "Content-Length: ";
|
2017-09-13 03:35:27 +00:00
|
|
|
assert(StartsWith(stringified_content_length, kContentLengthStart));
|
2017-09-22 01:14:57 +00:00
|
|
|
int content_length =
|
|
|
|
atoi(stringified_content_length.c_str() + strlen(kContentLengthStart));
|
2017-09-13 03:35:27 +00:00
|
|
|
|
|
|
|
// There is always a "\r\n" sequence before the actual content.
|
|
|
|
auto expect_char = [&](char expected) {
|
|
|
|
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 nullopt;
|
|
|
|
}
|
2017-05-03 06:45:10 +00:00
|
|
|
|
2017-09-13 03:35:27 +00:00
|
|
|
// Read content.
|
|
|
|
std::string content;
|
|
|
|
content.reserve(content_length);
|
|
|
|
for (size_t i = 0; i < content_length; ++i) {
|
|
|
|
optional<char> c = read();
|
|
|
|
if (!c) {
|
|
|
|
LOG_S(INFO) << "No more input when reading content body";
|
|
|
|
return nullopt;
|
2017-05-19 01:13:51 +00:00
|
|
|
}
|
2017-09-13 03:35:27 +00:00
|
|
|
content += *c;
|
|
|
|
}
|
2017-09-13 17:53:13 +00:00
|
|
|
|
2017-09-13 03:35:27 +00:00
|
|
|
return content;
|
|
|
|
}
|
2017-05-03 06:45:10 +00:00
|
|
|
|
2017-09-13 03:35:27 +00:00
|
|
|
TEST_SUITE("FindIncludeLine");
|
|
|
|
|
2017-09-22 01:14:57 +00:00
|
|
|
std::function<optional<char>()> MakeContentReader(std::string* content,
|
|
|
|
bool can_be_empty) {
|
2017-09-13 17:53:13 +00:00
|
|
|
return [content, can_be_empty]() -> optional<char> {
|
2017-09-13 03:35:27 +00:00
|
|
|
if (!can_be_empty)
|
|
|
|
REQUIRE(!content->empty());
|
|
|
|
if (content->empty())
|
|
|
|
return nullopt;
|
|
|
|
char c = (*content)[0];
|
|
|
|
content->erase(content->begin());
|
|
|
|
return c;
|
|
|
|
};
|
|
|
|
}
|
2017-03-25 22:13:19 +00:00
|
|
|
|
2017-09-13 03:35:27 +00:00
|
|
|
TEST_CASE("ReadContentFromSource") {
|
2017-09-13 17:53:13 +00:00
|
|
|
auto parse_correct = [](std::string content) -> std::string {
|
2017-09-13 03:35:27 +00:00
|
|
|
auto reader = MakeContentReader(&content, false /*can_be_empty*/);
|
|
|
|
auto got = ReadJsonRpcContentFrom(reader);
|
|
|
|
REQUIRE(got);
|
|
|
|
return got.value();
|
|
|
|
};
|
|
|
|
|
2017-09-13 17:53:13 +00:00
|
|
|
auto parse_incorrect = [](std::string content) -> optional<std::string> {
|
2017-09-13 03:35:27 +00:00
|
|
|
auto reader = MakeContentReader(&content, true /*can_be_empty*/);
|
|
|
|
return ReadJsonRpcContentFrom(reader);
|
|
|
|
};
|
|
|
|
|
|
|
|
REQUIRE(parse_correct("Content-Length: 0\r\n\r\n") == "");
|
|
|
|
REQUIRE(parse_correct("Content-Length: 1\r\n\r\na") == "a");
|
|
|
|
REQUIRE(parse_correct("Content-Length: 4\r\n\r\nabcd") == "abcd");
|
|
|
|
|
|
|
|
REQUIRE(parse_incorrect("ggg") == optional<std::string>());
|
|
|
|
REQUIRE(parse_incorrect("Content-Length: 0\r\n") == optional<std::string>());
|
2017-09-22 01:14:57 +00:00
|
|
|
REQUIRE(parse_incorrect("Content-Length: 5\r\n\r\nab") ==
|
|
|
|
optional<std::string>());
|
2017-09-13 03:35:27 +00:00
|
|
|
}
|
2017-03-25 22:13:19 +00:00
|
|
|
|
2017-09-13 03:35:27 +00:00
|
|
|
TEST_SUITE_END();
|
2017-03-25 22:13:19 +00:00
|
|
|
|
2017-09-13 03:35:27 +00:00
|
|
|
optional<char> ReadCharFromStdinBlocking() {
|
|
|
|
// Bad stdin means parent process has probably exited. Either way, cquery
|
|
|
|
// can no longer be communicated with so just exit.
|
|
|
|
if (!std::cin.good()) {
|
|
|
|
LOG_S(FATAL) << "std::cin.good() is false; exiting";
|
|
|
|
exit(1);
|
2017-03-25 22:13:19 +00:00
|
|
|
}
|
|
|
|
|
2017-09-13 03:35:27 +00:00
|
|
|
char c = 0;
|
|
|
|
std::cin.read(&c, 1);
|
|
|
|
return c;
|
|
|
|
}
|
2017-03-25 22:13:19 +00:00
|
|
|
|
2017-09-13 03:35:27 +00:00
|
|
|
std::unique_ptr<BaseIpcMessage> MessageRegistry::ReadMessageFromStdin() {
|
2017-09-22 01:14:57 +00:00
|
|
|
optional<std::string> content =
|
|
|
|
ReadJsonRpcContentFrom(&ReadCharFromStdinBlocking);
|
2017-09-13 03:35:27 +00:00
|
|
|
if (!content) {
|
|
|
|
LOG_S(FATAL) << "Failed to read JsonRpc input; exiting";
|
|
|
|
exit(1);
|
|
|
|
}
|
2017-04-16 23:52:42 +00:00
|
|
|
|
2017-03-25 22:13:19 +00:00
|
|
|
rapidjson::Document document;
|
2017-09-13 03:35:27 +00:00
|
|
|
document.Parse(content->c_str(), content->length());
|
2017-03-25 22:13:19 +00:00
|
|
|
assert(!document.HasParseError());
|
|
|
|
|
|
|
|
return Parse(document);
|
|
|
|
}
|
|
|
|
|
|
|
|
std::unique_ptr<BaseIpcMessage> MessageRegistry::Parse(Reader& visitor) {
|
2017-09-22 01:14:57 +00:00
|
|
|
if (!visitor.HasMember("jsonrpc") ||
|
|
|
|
std::string(visitor["jsonrpc"].GetString()) != "2.0") {
|
2017-05-03 06:45:10 +00:00
|
|
|
std::cerr << "Bad or missing jsonrpc version" << std::endl;
|
2017-03-25 22:13:19 +00:00
|
|
|
exit(1);
|
2017-05-03 06:45:10 +00:00
|
|
|
}
|
2017-03-25 22:13:19 +00:00
|
|
|
|
|
|
|
std::string method;
|
|
|
|
ReflectMember(visitor, "method", method);
|
|
|
|
|
|
|
|
if (allocators.find(method) == allocators.end()) {
|
2017-09-22 01:14:57 +00:00
|
|
|
LOG_S(ERROR) << "Unable to find registered handler for method \"" << method
|
|
|
|
<< "\"" << std::endl;
|
2017-03-25 22:13:19 +00:00
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
Allocator& allocator = allocators[method];
|
|
|
|
return allocator(visitor);
|
|
|
|
}
|
|
|
|
|
|
|
|
MessageRegistry* MessageRegistry::instance() {
|
|
|
|
if (!instance_)
|
|
|
|
instance_ = new MessageRegistry();
|
|
|
|
|
|
|
|
return instance_;
|
|
|
|
}
|
|
|
|
|
2017-06-09 06:20:29 +00:00
|
|
|
lsBaseOutMessage::~lsBaseOutMessage() = default;
|
|
|
|
|
2017-03-25 22:13:19 +00:00
|
|
|
void lsResponseError::Write(Writer& visitor) {
|
|
|
|
auto& value = *this;
|
2017-05-21 23:22:00 +00:00
|
|
|
int code2 = static_cast<int>(this->code);
|
2017-03-25 22:13:19 +00:00
|
|
|
|
|
|
|
visitor.StartObject();
|
2017-05-21 23:22:00 +00:00
|
|
|
REFLECT_MEMBER2("code", code2);
|
2017-03-25 22:13:19 +00:00
|
|
|
REFLECT_MEMBER(message);
|
|
|
|
if (data) {
|
|
|
|
visitor.Key("data");
|
|
|
|
data->Write(visitor);
|
|
|
|
}
|
|
|
|
visitor.EndObject();
|
|
|
|
}
|
|
|
|
|
|
|
|
lsDocumentUri lsDocumentUri::FromPath(const std::string& path) {
|
|
|
|
lsDocumentUri result;
|
|
|
|
result.SetPath(path);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
lsDocumentUri::lsDocumentUri() {}
|
|
|
|
|
|
|
|
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(":");
|
2017-09-22 01:14:57 +00:00
|
|
|
if (index == 1) { // widows drive letters must always be 1 char
|
|
|
|
raw_uri.replace(raw_uri.begin() + index, raw_uri.begin() + index + 1,
|
|
|
|
"%3A");
|
2017-03-25 22:13:19 +00:00
|
|
|
}
|
|
|
|
|
2017-04-13 06:01:42 +00:00
|
|
|
raw_uri = ReplaceAll(raw_uri, " ", "%20");
|
|
|
|
raw_uri = ReplaceAll(raw_uri, "(", "%28");
|
|
|
|
raw_uri = ReplaceAll(raw_uri, ")", "%29");
|
|
|
|
|
2017-03-28 05:27:06 +00:00
|
|
|
// TODO: proper fix
|
|
|
|
#if defined(_WIN32)
|
|
|
|
raw_uri = "file:///" + raw_uri;
|
|
|
|
#else
|
2017-03-28 02:28:45 +00:00
|
|
|
raw_uri = "file://" + raw_uri;
|
2017-03-28 05:27:06 +00:00
|
|
|
#endif
|
2017-09-22 01:14:57 +00:00
|
|
|
// std::cerr << "Set uri to " << raw_uri << " from " << path;
|
2017-03-25 22:13:19 +00:00
|
|
|
}
|
|
|
|
|
2017-03-26 21:40:34 +00:00
|
|
|
std::string lsDocumentUri::GetPath() const {
|
2017-04-13 06:01:42 +00:00
|
|
|
// c:/Program%20Files%20%28x86%29/Microsoft%20Visual%20Studio%2014.0/VC/include/vcruntime.
|
|
|
|
// C:/Program Files (x86)
|
|
|
|
|
2017-03-25 22:13:19 +00:00
|
|
|
// TODO: make this not a hack.
|
|
|
|
std::string result = raw_uri;
|
|
|
|
|
2017-04-13 06:01:42 +00:00
|
|
|
result = ReplaceAll(result, "%20", " ");
|
|
|
|
result = ReplaceAll(result, "%28", "(");
|
|
|
|
result = ReplaceAll(result, "%29", ")");
|
|
|
|
|
2017-03-25 22:13:19 +00:00
|
|
|
size_t index = result.find("%3A");
|
|
|
|
if (index != -1) {
|
|
|
|
result.replace(result.begin() + index, result.begin() + index + 3, ":");
|
|
|
|
}
|
|
|
|
|
|
|
|
index = result.find("file://");
|
|
|
|
if (index != -1) {
|
2017-03-28 05:27:06 +00:00
|
|
|
// TODO: proper fix
|
|
|
|
#if defined(_WIN32)
|
|
|
|
result.replace(result.begin() + index, result.begin() + index + 8, "");
|
|
|
|
#else
|
2017-03-28 02:28:45 +00:00
|
|
|
result.replace(result.begin() + index, result.begin() + index + 7, "");
|
2017-03-28 05:27:06 +00:00
|
|
|
#endif
|
2017-03-25 22:13:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
std::replace(result.begin(), result.end(), '\\', '/');
|
2017-04-14 06:43:50 +00:00
|
|
|
|
|
|
|
#if defined(_WIN32)
|
2017-09-22 01:14:57 +00:00
|
|
|
// std::transform(result.begin(), result.end(), result.begin(), ::tolower);
|
2017-04-14 06:43:50 +00:00
|
|
|
#endif
|
|
|
|
|
2017-03-25 22:13:19 +00:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
lsPosition::lsPosition() {}
|
2017-09-22 01:14:57 +00:00
|
|
|
lsPosition::lsPosition(int line, int character)
|
|
|
|
: line(line), character(character) {}
|
2017-03-25 22:13:19 +00:00
|
|
|
|
|
|
|
bool lsPosition::operator==(const lsPosition& other) const {
|
|
|
|
return line == other.line && character == other.character;
|
|
|
|
}
|
|
|
|
|
2017-05-15 07:28:53 +00:00
|
|
|
std::string lsPosition::ToString() const {
|
|
|
|
return std::to_string(line) + ":" + std::to_string(character);
|
|
|
|
}
|
|
|
|
|
2017-03-25 22:13:19 +00:00
|
|
|
lsRange::lsRange() {}
|
2017-04-05 08:06:18 +00:00
|
|
|
lsRange::lsRange(lsPosition start, lsPosition end) : start(start), end(end) {}
|
2017-03-25 22:13:19 +00:00
|
|
|
|
|
|
|
bool lsRange::operator==(const lsRange& other) const {
|
|
|
|
return start == other.start && end == other.end;
|
|
|
|
}
|
|
|
|
|
|
|
|
lsLocation::lsLocation() {}
|
2017-09-22 01:14:57 +00:00
|
|
|
lsLocation::lsLocation(lsDocumentUri uri, lsRange range)
|
|
|
|
: uri(uri), range(range) {}
|
2017-03-25 22:13:19 +00:00
|
|
|
|
|
|
|
bool lsLocation::operator==(const lsLocation& other) const {
|
|
|
|
return uri == other.uri && range == other.range;
|
|
|
|
}
|
|
|
|
|
2017-04-21 06:56:42 +00:00
|
|
|
bool lsTextEdit::operator==(const lsTextEdit& that) {
|
|
|
|
return range == that.range && newText == that.newText;
|
|
|
|
}
|
|
|
|
|
2017-06-29 06:59:38 +00:00
|
|
|
const std::string& lsCompletionItem::InsertedContent() const {
|
|
|
|
if (textEdit)
|
|
|
|
return textEdit->newText;
|
|
|
|
if (!insertText.empty())
|
|
|
|
return insertText;
|
|
|
|
return label;
|
|
|
|
}
|
|
|
|
|
2017-03-25 22:13:19 +00:00
|
|
|
void Reflect(Reader& reader, lsInitializeParams::lsTrace& value) {
|
2017-11-18 17:27:37 +00:00
|
|
|
if (!reader.IsString()) {
|
|
|
|
value = lsInitializeParams::lsTrace::Off;
|
|
|
|
return;
|
|
|
|
}
|
2017-03-25 22:13:19 +00:00
|
|
|
std::string v = reader.GetString();
|
|
|
|
if (v == "off")
|
|
|
|
value = lsInitializeParams::lsTrace::Off;
|
|
|
|
else if (v == "messages")
|
|
|
|
value = lsInitializeParams::lsTrace::Messages;
|
|
|
|
else if (v == "verbose")
|
|
|
|
value = lsInitializeParams::lsTrace::Verbose;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Reflect(Writer& writer, lsInitializeParams::lsTrace& value) {
|
|
|
|
switch (value) {
|
2017-09-22 01:14:57 +00:00
|
|
|
case lsInitializeParams::lsTrace::Off:
|
|
|
|
writer.String("off");
|
|
|
|
break;
|
|
|
|
case lsInitializeParams::lsTrace::Messages:
|
|
|
|
writer.String("messages");
|
|
|
|
break;
|
|
|
|
case lsInitializeParams::lsTrace::Verbose:
|
|
|
|
writer.String("verbose");
|
|
|
|
break;
|
2017-03-25 22:13:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Reflect(Writer& visitor, lsCodeLensCommandArguments& value) {
|
|
|
|
visitor.StartArray();
|
|
|
|
Reflect(visitor, value.uri);
|
|
|
|
Reflect(visitor, value.position);
|
|
|
|
Reflect(visitor, value.locations);
|
|
|
|
visitor.EndArray();
|
|
|
|
}
|
|
|
|
void Reflect(Reader& visitor, lsCodeLensCommandArguments& value) {
|
|
|
|
auto it = visitor.Begin();
|
|
|
|
Reflect(*it, value.uri);
|
|
|
|
++it;
|
|
|
|
Reflect(*it, value.position);
|
|
|
|
++it;
|
|
|
|
Reflect(*it, value.locations);
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string Out_ShowLogMessage::method() {
|
|
|
|
if (display_type == DisplayType::Log)
|
|
|
|
return "window/logMessage";
|
|
|
|
return "window/showMessage";
|
2017-03-28 02:28:45 +00:00
|
|
|
}
|