Line data Source code
1 : // Copyright 2019 the V8 project authors. All rights reserved.
2 : // Use of this source code is governed by a BSD-style license that can be
3 : // found in the LICENSE file.
4 :
5 : #include <algorithm>
6 : #include "src/torque/ls/message-handler.h"
7 :
8 : #include "src/torque/ls/globals.h"
9 : #include "src/torque/ls/json-parser.h"
10 : #include "src/torque/ls/message-pipe.h"
11 : #include "src/torque/ls/message.h"
12 : #include "src/torque/server-data.h"
13 : #include "src/torque/source-positions.h"
14 : #include "src/torque/torque-compiler.h"
15 :
16 : namespace v8 {
17 : namespace internal {
18 : namespace torque {
19 :
20 0 : DEFINE_CONTEXTUAL_VARIABLE(Logger)
21 0 : DEFINE_CONTEXTUAL_VARIABLE(TorqueFileList)
22 :
23 : namespace ls {
24 :
25 : static const char kContentLength[] = "Content-Length: ";
26 : static const size_t kContentLengthSize = sizeof(kContentLength) - 1;
27 :
28 : #ifdef V8_OS_WIN
29 : // On Windows, in text mode, \n is translated to \r\n.
30 : constexpr const char* kProtocolLineEnding = "\n\n";
31 : #else
32 : constexpr const char* kProtocolLineEnding = "\r\n\r\n";
33 : #endif
34 :
35 0 : JsonValue ReadMessage() {
36 : std::string line;
37 0 : std::getline(std::cin, line);
38 :
39 0 : if (line.rfind(kContentLength) != 0) {
40 : // Invalid message, we just crash.
41 0 : Logger::Log("[fatal] Did not find Content-Length ...\n");
42 0 : v8::base::OS::Abort();
43 : }
44 :
45 0 : const int content_length = std::atoi(line.substr(kContentLengthSize).c_str());
46 0 : std::getline(std::cin, line);
47 0 : std::string content(content_length, ' ');
48 0 : std::cin.read(&content[0], content_length);
49 :
50 0 : Logger::Log("[incoming] ", content, "\n\n");
51 :
52 0 : return ParseJson(content).value;
53 : }
54 :
55 0 : void WriteMessage(JsonValue& message) {
56 0 : std::string content = SerializeToString(message);
57 :
58 0 : Logger::Log("[outgoing] ", content, "\n\n");
59 :
60 0 : std::cout << kContentLength << content.size() << kProtocolLineEnding;
61 : std::cout << content << std::flush;
62 0 : }
63 :
64 : namespace {
65 :
66 0 : void RecompileTorque() {
67 0 : Logger::Log("[info] Start compilation run ...\n");
68 :
69 : TorqueCompilerOptions options;
70 : options.output_directory = "";
71 0 : options.verbose = false;
72 0 : options.collect_language_server_data = true;
73 0 : options.abort_on_lint_errors = false;
74 :
75 0 : TorqueCompilerResult result = CompileTorque(TorqueFileList::Get(), options);
76 :
77 : LanguageServerData::Get() = result.language_server_data;
78 : SourceFileMap::Get() = result.source_file_map;
79 :
80 0 : Logger::Log("[info] Finished compilation run ...\n");
81 0 : }
82 :
83 1 : void HandleInitializeRequest(InitializeRequest request, MessageWriter writer) {
84 : InitializeResponse response;
85 1 : response.set_id(request.id());
86 1 : response.result().capabilities().textDocumentSync();
87 1 : response.result().capabilities().set_definitionProvider(true);
88 :
89 : // TODO(szuend): Register for document synchronisation here,
90 : // so we work with the content that the client
91 : // provides, not directly read from files.
92 : // TODO(szuend): Check that the client actually supports dynamic
93 : // "workspace/didChangeWatchedFiles" capability.
94 : // TODO(szuend): Check if client supports "LocationLink". This will
95 : // influence the result of "goto definition".
96 1 : writer(response.GetJsonValue());
97 1 : }
98 :
99 1 : void HandleInitializedNotification(MessageWriter writer) {
100 : RegistrationRequest request;
101 : // TODO(szuend): The language server needs a "global" request id counter.
102 1 : request.set_id(2000);
103 2 : request.set_method("client/registerCapability");
104 :
105 1 : Registration reg = request.params().add_registrations();
106 : auto options =
107 1 : reg.registerOptions<DidChangeWatchedFilesRegistrationOptions>();
108 1 : FileSystemWatcher watcher = options.add_watchers();
109 2 : watcher.set_globPattern("**/*.tq");
110 1 : watcher.set_kind(FileSystemWatcher::WatchKind::kAll);
111 :
112 2 : reg.set_id("did-change-id");
113 2 : reg.set_method("workspace/didChangeWatchedFiles");
114 :
115 1 : writer(request.GetJsonValue());
116 1 : }
117 :
118 0 : void HandleTorqueFileListNotification(TorqueFileListNotification notification) {
119 0 : CHECK_EQ(notification.params().object()["files"].tag, JsonValue::ARRAY);
120 :
121 : std::vector<std::string>& files = TorqueFileList::Get();
122 0 : Logger::Log("[info] Initial file list:\n");
123 0 : for (const auto& file_json :
124 0 : notification.params().object()["files"].ToArray()) {
125 0 : CHECK(file_json.IsString());
126 :
127 : // We only consider file URIs (there shouldn't be anything else).
128 : // Internally we store the URI instead of the path, eliminating the need
129 : // to encode it again.
130 0 : files.push_back(file_json.ToString());
131 0 : Logger::Log(" ", file_json.ToString(), "\n");
132 : }
133 :
134 : // The Torque compiler expects to see some files first,
135 : // we need to order them in the correct way.
136 : // TODO(szuend): Remove this, once the compiler doesn't require the input
137 : // files to be in a specific order.
138 : std::vector<std::string> sort_to_front = {"base.tq", "frames.tq",
139 0 : "arguments.tq", "array.tq"};
140 0 : std::sort(files.begin(), files.end(), [&](std::string a, std::string b) {
141 0 : for (const std::string& fixed_file : sort_to_front) {
142 0 : if (a.find(fixed_file) != std::string::npos) return true;
143 0 : if (b.find(fixed_file) != std::string::npos) return false;
144 : }
145 0 : return a < b;
146 : });
147 :
148 0 : RecompileTorque();
149 0 : }
150 :
151 3 : void HandleGotoDefinitionRequest(GotoDefinitionRequest request,
152 : MessageWriter writer) {
153 : GotoDefinitionResponse response;
154 3 : response.set_id(request.id());
155 :
156 : SourceId id =
157 3 : SourceFileMap::GetSourceId(request.params().textDocument().uri());
158 :
159 : // Unknown source files cause an empty response which corresponds with
160 : // the definition not beeing found.
161 3 : if (!id.IsValid()) {
162 2 : response.SetNull("result");
163 1 : writer(response.GetJsonValue());
164 : return;
165 : }
166 :
167 4 : LineAndColumn pos{request.params().position().line(),
168 4 : request.params().position().character()};
169 :
170 2 : if (auto maybe_definition = LanguageServerData::FindDefinition(id, pos)) {
171 1 : SourcePosition definition = *maybe_definition;
172 :
173 1 : std::string definition_file = SourceFileMap::GetSource(definition.source);
174 1 : response.result().set_uri(definition_file);
175 :
176 1 : Range range = response.result().range();
177 1 : range.start().set_line(definition.start.line);
178 1 : range.start().set_character(definition.start.column);
179 1 : range.end().set_line(definition.end.line);
180 1 : range.end().set_character(definition.end.column);
181 : } else {
182 2 : response.SetNull("result");
183 : }
184 :
185 2 : writer(response.GetJsonValue());
186 : }
187 :
188 : void HandleChangeWatchedFilesNotification(
189 : DidChangeWatchedFilesNotification notification) {
190 : // TODO(szuend): Implement updates to the TorqueFile list when create/delete
191 : // notifications are received. Currently we simply re-compile.
192 0 : RecompileTorque();
193 : }
194 :
195 : } // namespace
196 :
197 5 : void HandleMessage(JsonValue& raw_message, MessageWriter writer) {
198 : Request<bool> request(raw_message);
199 :
200 : // We ignore responses for now. They are matched to requests
201 : // by id and don't have a method set.
202 : // TODO(szuend): Implement proper response handling for requests
203 : // that originate from the server.
204 5 : if (!request.has_method()) {
205 0 : Logger::Log("[info] Unhandled response with id ", request.id(), "\n\n");
206 : return;
207 : }
208 :
209 5 : const std::string method = request.method();
210 5 : if (method == "initialize") {
211 2 : HandleInitializeRequest(InitializeRequest(request.GetJsonValue()), writer);
212 4 : } else if (method == "initialized") {
213 1 : HandleInitializedNotification(writer);
214 3 : } else if (method == "torque/fileList") {
215 0 : HandleTorqueFileListNotification(
216 0 : TorqueFileListNotification(request.GetJsonValue()));
217 3 : } else if (method == "textDocument/definition") {
218 3 : HandleGotoDefinitionRequest(GotoDefinitionRequest(request.GetJsonValue()),
219 3 : writer);
220 0 : } else if (method == "workspace/didChangeWatchedFiles") {
221 0 : HandleChangeWatchedFilesNotification(
222 : DidChangeWatchedFilesNotification(request.GetJsonValue()));
223 : } else {
224 0 : Logger::Log("[error] Message of type ", method, " is not handled!\n\n");
225 : }
226 : }
227 :
228 : } // namespace ls
229 : } // namespace torque
230 : } // namespace internal
231 59454 : } // namespace v8
|