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