#include <algorithm>
#include "src/torque/ls/message-handler.h"
#include "src/torque/ls/globals.h"
#include "src/torque/ls/json-parser.h"
#include "src/torque/ls/message-pipe.h"
#include "src/torque/ls/message.h"
#include "src/torque/server-data.h"
#include "src/torque/source-positions.h"
#include "src/torque/torque-compiler.h"
EXPORT_CONTEXTUAL_VARIABLE(v8::internal::torque::DiagnosticsFiles)
namespace v8 {
namespace internal {
namespace torque {
namespace ls {
static const char kContentLength[] = "Content-Length: ";
static const size_t kContentLengthSize = sizeof(kContentLength) - 1;
#ifdef V8_OS_WIN
constexpr const char* kProtocolLineEnding = "\n\n";
#else
constexpr const char* kProtocolLineEnding = "\r\n\r\n";
#endif
JsonValue ReadMessage() {
std::string line;
std::getline(std::cin, line);
if (line.rfind(kContentLength) != 0) {
Logger::Log("[fatal] Did not find Content-Length ...\n");
v8::base::OS::Abort();
}
const int content_length = std::atoi(line.substr(kContentLengthSize).c_str());
std::getline(std::cin, line);
std::string content(content_length, ' ');
std::cin.read(&content[0], content_length);
Logger::Log("[incoming] ", content, "\n\n");
return ParseJson(content).value;
}
void WriteMessage(JsonValue message) {
std::string content = SerializeToString(message);
Logger::Log("[outgoing] ", content, "\n\n");
std::cout << kContentLength << content.size() << kProtocolLineEnding;
std::cout << content << std::flush;
}
namespace {
void ResetCompilationErrorDiagnostics(MessageWriter writer) {
for (const SourceId& source : DiagnosticsFiles::Get()) {
PublishDiagnosticsNotification notification;
notification.set_method("textDocument/publishDiagnostics");
std::string error_file = SourceFileMap::AbsolutePath(source);
notification.params().set_uri(error_file);
USE(notification.params().diagnostics_size());
writer(std::move(notification.GetJsonValue()));
}
DiagnosticsFiles::Get() = {};
}
class DiagnosticCollector {
public:
void AddTorqueMessage(const TorqueMessage& message) {
if (!ShouldAddMessageOfKind(message.kind)) return;
SourceId id =
message.position ? message.position->source : SourceId::Invalid();
auto& notification = GetOrCreateNotificationForSource(id);
Diagnostic diagnostic = notification.params().add_diagnostics();
diagnostic.set_severity(ServerityFor(message.kind));
diagnostic.set_message(message.message);
diagnostic.set_source("Torque Compiler");
if (message.position) {
PopulateRangeFromSourcePosition(diagnostic.range(), *message.position);
}
}
std::map<SourceId, PublishDiagnosticsNotification>& notifications() {
return notifications_;
}
private:
PublishDiagnosticsNotification& GetOrCreateNotificationForSource(
SourceId id) {
auto iter = notifications_.find(id);
if (iter != notifications_.end()) return iter->second;
PublishDiagnosticsNotification& notification = notifications_[id];
notification.set_method("textDocument/publishDiagnostics");
std::string file =
id.IsValid() ? SourceFileMap::AbsolutePath(id) : "<unknown>";
notification.params().set_uri(file);
return notification;
}
bool ShouldAddMessageOfKind(TorqueMessage::Kind kind) {
switch (kind) {
case TorqueMessage::Kind::kError:
suppress_lint_messages_ = true;
return true;
case TorqueMessage::Kind::kLint:
if (suppress_lint_messages_) return false;
return true;
}
}
void PopulateRangeFromSourcePosition(Range range,
const SourcePosition& position) {
range.start().set_line(position.start.line);
range.start().set_character(position.start.column);
range.end().set_line(position.end.line);
range.end().set_character(position.end.column);
}
Diagnostic::DiagnosticSeverity ServerityFor(TorqueMessage::Kind kind) {
switch (kind) {
case TorqueMessage::Kind::kError:
return Diagnostic::kError;
case TorqueMessage::Kind::kLint:
return Diagnostic::kWarning;
}
}
std::map<SourceId, PublishDiagnosticsNotification> notifications_;
bool suppress_lint_messages_ = false;
};
void SendCompilationDiagnostics(const TorqueCompilerResult& result,
MessageWriter writer) {
DiagnosticCollector collector;
for (const TorqueMessage& message : result.messages) {
collector.AddTorqueMessage(message);
}
for (auto& pair : collector.notifications()) {
PublishDiagnosticsNotification& notification = pair.second;
writer(std::move(notification.GetJsonValue()));
const SourceId& source = pair.first;
if (source.IsValid()) DiagnosticsFiles::Get().push_back(source);
}
}
}
void CompilationFinished(TorqueCompilerResult result, MessageWriter writer) {
LanguageServerData::Get() = std::move(result.language_server_data);
SourceFileMap::Get() = *result.source_file_map;
SendCompilationDiagnostics(result, writer);
}
namespace {
void RecompileTorque(MessageWriter writer) {
Logger::Log("[info] Start compilation run ...\n");
TorqueCompilerOptions options;
options.output_directory = "";
options.collect_language_server_data = true;
options.force_assert_statements = true;
TorqueCompilerResult result = CompileTorque(TorqueFileList::Get(), options);
Logger::Log("[info] Finished compilation run ...\n");
CompilationFinished(std::move(result), writer);
}
void RecompileTorqueWithDiagnostics(MessageWriter writer) {
ResetCompilationErrorDiagnostics(writer);
RecompileTorque(writer);
}
void HandleInitializeRequest(InitializeRequest request, MessageWriter writer) {
InitializeResponse response;
response.set_id(request.id());
response.result().capabilities().textDocumentSync();
response.result().capabilities().set_definitionProvider(true);
response.result().capabilities().set_documentSymbolProvider(true);
writer(std::move(response.GetJsonValue()));
}
void HandleInitializedNotification(MessageWriter writer) {
RegistrationRequest request;
request.set_id(2000);
request.set_method("client/registerCapability");
Registration reg = request.params().add_registrations();
auto options =
reg.registerOptions<DidChangeWatchedFilesRegistrationOptions>();
FileSystemWatcher watcher = options.add_watchers();
watcher.set_globPattern("**/*.tq");
watcher.set_kind(FileSystemWatcher::WatchKind::kAll);
reg.set_id("did-change-id");
reg.set_method("workspace/didChangeWatchedFiles");
writer(std::move(request.GetJsonValue()));
}
void HandleTorqueFileListNotification(TorqueFileListNotification notification,
MessageWriter writer) {
CHECK_EQ(notification.params().object()["files"].tag, JsonValue::ARRAY);
std::vector<std::string>& files = TorqueFileList::Get();
Logger::Log("[info] Initial file list:\n");
for (const auto& file_json :
notification.params().object()["files"].ToArray()) {
CHECK(file_json.IsString());
files.push_back(file_json.ToString());
Logger::Log(" ", file_json.ToString(), "\n");
}
RecompileTorqueWithDiagnostics(writer);
}
void HandleGotoDefinitionRequest(GotoDefinitionRequest request,
MessageWriter writer) {
GotoDefinitionResponse response;
response.set_id(request.id());
SourceId id =
SourceFileMap::GetSourceId(request.params().textDocument().uri());
if (!id.IsValid()) {
response.SetNull("result");
writer(std::move(response.GetJsonValue()));
return;
}
auto pos =
LineAndColumn::WithUnknownOffset(request.params().position().line(),
request.params().position().character());
if (auto maybe_definition = LanguageServerData::FindDefinition(id, pos)) {
SourcePosition definition = *maybe_definition;
response.result().SetTo(definition);
} else {
response.SetNull("result");
}
writer(std::move(response.GetJsonValue()));
}
void HandleChangeWatchedFilesNotification(
DidChangeWatchedFilesNotification notification, MessageWriter writer) {
RecompileTorqueWithDiagnostics(writer);
}
void HandleDocumentSymbolRequest(DocumentSymbolRequest request,
MessageWriter writer) {
DocumentSymbolResponse response;
response.set_id(request.id());
SourceId id =
SourceFileMap::GetSourceId(request.params().textDocument().uri());
for (const auto& symbol : LanguageServerData::SymbolsForSourceId(id)) {
DCHECK(symbol->IsUserDefined());
if (symbol->IsMacro()) {
Macro* macro = Macro::cast(symbol);
SymbolInformation info = response.add_result();
info.set_name(macro->ReadableName());
info.set_kind(SymbolKind::kFunction);
info.location().SetTo(macro->Position());
} else if (symbol->IsBuiltin()) {
Builtin* builtin = Builtin::cast(symbol);
SymbolInformation info = response.add_result();
info.set_name(builtin->ReadableName());
info.set_kind(SymbolKind::kFunction);
info.location().SetTo(builtin->Position());
} else if (symbol->IsGenericCallable()) {
GenericCallable* generic = GenericCallable::cast(symbol);
SymbolInformation info = response.add_result();
info.set_name(generic->name());
info.set_kind(SymbolKind::kFunction);
info.location().SetTo(generic->Position());
} else if (symbol->IsTypeAlias()) {
const Type* type = TypeAlias::cast(symbol)->type();
SymbolKind kind =
type->IsClassType() ? SymbolKind::kClass : SymbolKind::kStruct;
SymbolInformation sym = response.add_result();
sym.set_name(type->ToString());
sym.set_kind(kind);
sym.location().SetTo(symbol->Position());
}
}
USE(response.result_size());
writer(std::move(response.GetJsonValue()));
}
}
void HandleMessage(JsonValue raw_message, MessageWriter writer) {
Request<bool> request(std::move(raw_message));
if (!request.has_method()) {
Logger::Log("[info] Unhandled response with id ", request.id(), "\n\n");
return;
}
const std::string method = request.method();
if (method == "initialize") {
HandleInitializeRequest(
InitializeRequest(std::move(request.GetJsonValue())), writer);
} else if (method == "initialized") {
HandleInitializedNotification(writer);
} else if (method == "torque/fileList") {
HandleTorqueFileListNotification(
TorqueFileListNotification(std::move(request.GetJsonValue())), writer);
} else if (method == "textDocument/definition") {
HandleGotoDefinitionRequest(
GotoDefinitionRequest(std::move(request.GetJsonValue())), writer);
} else if (method == "workspace/didChangeWatchedFiles") {
HandleChangeWatchedFilesNotification(
DidChangeWatchedFilesNotification(std::move(request.GetJsonValue())),
writer);
} else if (method == "textDocument/documentSymbol") {
HandleDocumentSymbolRequest(
DocumentSymbolRequest(std::move(request.GetJsonValue())), writer);
} else {
Logger::Log("[error] Message of type ", method, " is not handled!\n\n");
}
}
}
}
}
}