1 #include "source_language_protocol.hpp"
2 #include "filesystem.hpp"
3 #include "info.hpp"
4 #include "notebook.hpp"
5 #include "project.hpp"
6 #include "selection_dialog.hpp"
7 #include "terminal.hpp"
8 #ifdef JUCI_ENABLE_DEBUG
9 #include "debug_lldb.hpp"
10 #endif
11 #include "config.hpp"
12 #include "json.hpp"
13 #include "menu.hpp"
14 #include "utility.hpp"
15 #include <future>
16 #include <limits>
17 #include <regex>
18 #include <unordered_map>
19
20 const std::string type_coverage_message = "Un-type checked code. Consider adding type annotations.";
21
Position(const JSON & position)22 LanguageProtocol::Position::Position(const JSON &position) {
23 try {
24 line = position.integer("line");
25 character = position.integer("character");
26 }
27 catch(...) {
28 // Workaround for buggy rls
29 line = std::min(position.integer("line"), std::numeric_limits<long long>::max());
30 character = std::min(position.integer("character"), std::numeric_limits<long long>::max());
31 }
32 }
Range(const JSON & range)33 LanguageProtocol::Range::Range(const JSON &range) : start(range.object("start")), end(range.object("end")) {}
34
Location(const JSON & location,std::string file_)35 LanguageProtocol::Location::Location(const JSON &location, std::string file_) : file(file_.empty() ? filesystem::get_path_from_uri(location.string("uri")).string() : std::move(file_)), range(location.object("range")) {
36 }
37
Documentation(const boost::optional<JSON> & documentation)38 LanguageProtocol::Documentation::Documentation(const boost::optional<JSON> &documentation) {
39 if(!documentation)
40 return;
41 if(auto child = documentation->object_optional()) {
42 value = child->string_or("value", "");
43 kind = child->string_or("kind", "");
44 }
45 else
46 value = documentation->string_or("");
47 }
48
RelatedInformation(const JSON & related_information)49 LanguageProtocol::Diagnostic::RelatedInformation::RelatedInformation(const JSON &related_information) : message(related_information.string("message")), location(related_information.object("location")) {}
50
Diagnostic(JSON && diagnostic)51 LanguageProtocol::Diagnostic::Diagnostic(JSON &&diagnostic) : message(diagnostic.string("message")), range(diagnostic.object("range")), severity(diagnostic.integer_or("severity", 0)), code(diagnostic.string_or("code", "")) {
52 for(auto &related_information : diagnostic.array_or_empty("relatedInformation"))
53 related_informations.emplace_back(related_information);
54 object = std::make_shared<JSON>(JSON::make_owner(std::move(diagnostic)));
55 }
56
TextEdit(const JSON & text_edit,std::string new_text_)57 LanguageProtocol::TextEdit::TextEdit(const JSON &text_edit, std::string new_text_) : range(text_edit.object("range")), new_text(new_text_.empty() ? text_edit.string("newText") : std::move(new_text_)) {}
58
TextDocumentEdit(const JSON & text_document_edit)59 LanguageProtocol::TextDocumentEdit::TextDocumentEdit(const JSON &text_document_edit) : file(filesystem::get_path_from_uri(text_document_edit.object("textDocument").string("uri")).string()) {
60 for(auto &text_edit : text_document_edit.array_or_empty("edits"))
61 text_edits.emplace_back(text_edit);
62 }
63
TextDocumentEdit(std::string file,std::vector<TextEdit> text_edits)64 LanguageProtocol::TextDocumentEdit::TextDocumentEdit(std::string file, std::vector<TextEdit> text_edits) : file(std::move(file)), text_edits(std::move(text_edits)) {}
65
WorkspaceEdit(const JSON & workspace_edit,boost::filesystem::path file_path)66 LanguageProtocol::WorkspaceEdit::WorkspaceEdit(const JSON &workspace_edit, boost::filesystem::path file_path) {
67 boost::filesystem::path project_path;
68 auto build = Project::Build::create(file_path);
69 if(!build->project_path.empty())
70 project_path = build->project_path;
71 else
72 project_path = file_path.parent_path();
73 try {
74 if(auto children = workspace_edit.children_optional("changes")) {
75 for(auto &child : *children) {
76 auto file = filesystem::get_path_from_uri(child.first);
77 if(filesystem::file_in_path(file, project_path)) {
78 std::vector<LanguageProtocol::TextEdit> text_edits;
79 for(auto &text_edit : child.second.array())
80 text_edits.emplace_back(text_edit);
81 document_edits.emplace_back(file.string(), std::move(text_edits));
82 }
83 }
84 }
85 else if(auto children = workspace_edit.array_optional("documentChanges")) {
86 for(auto &child : *children) {
87 LanguageProtocol::TextDocumentEdit document_edit(child);
88 if(filesystem::file_in_path(document_edit.file, project_path))
89 document_edits.emplace_back(std::move(document_edit.file), std::move(document_edit.text_edits));
90 }
91 }
92 else if(auto child = workspace_edit.object_optional("documentChanges")) {
93 LanguageProtocol::TextDocumentEdit document_edit(*child);
94 if(filesystem::file_in_path(document_edit.file, project_path))
95 document_edits.emplace_back(std::move(document_edit.file), std::move(document_edit.text_edits));
96 }
97 }
98 catch(...) {
99 document_edits.clear();
100 }
101 }
102
Client(boost::filesystem::path root_path_,std::string language_id_,const std::string & language_server)103 LanguageProtocol::Client::Client(boost::filesystem::path root_path_, std::string language_id_, const std::string &language_server) : root_path(std::move(root_path_)), language_id(std::move(language_id_)) {
104 process = std::make_unique<TinyProcessLib::Process>(
105 language_server, root_path.string(),
106 [this](const char *bytes, size_t n) {
107 server_message_stream.write(bytes, n);
108 parse_server_message();
109 },
110 [](const char *bytes, size_t n) {
111 std::cerr.write(bytes, n);
112 },
113 true, TinyProcessLib::Config{1048576});
114 }
115
get(const boost::filesystem::path & file_path,const std::string & language_id,const std::string & language_server)116 std::shared_ptr<LanguageProtocol::Client> LanguageProtocol::Client::get(const boost::filesystem::path &file_path, const std::string &language_id, const std::string &language_server) {
117 boost::filesystem::path root_path;
118 auto build = Project::Build::create(file_path);
119 if(!build->project_path.empty())
120 root_path = build->project_path;
121 else
122 root_path = file_path.parent_path();
123
124 auto cache_id = root_path.string() + '|' + language_id;
125
126 static Mutex mutex;
127 static std::unordered_map<std::string, std::weak_ptr<Client>> cache GUARDED_BY(mutex);
128
129 LockGuard lock(mutex);
130 auto it = cache.find(cache_id);
131 if(it == cache.end())
132 it = cache.emplace(cache_id, std::weak_ptr<Client>()).first;
133 auto instance = it->second.lock();
134 if(!instance)
135 it->second = instance = std::shared_ptr<Client>(new Client(root_path, language_id, language_server), [](Client *client_ptr) {
136 client_ptr->dispatcher = nullptr; // Dispatcher must be destroyed in main thread
137
138 std::thread delete_thread([client_ptr] { // Delete client in the background
139 delete client_ptr;
140 });
141 delete_thread.detach();
142 });
143 return instance;
144 }
145
~Client()146 LanguageProtocol::Client::~Client() {
147 std::promise<void> result_processed;
148 write_request(nullptr, "shutdown", "", [this, &result_processed](JSON &&result, bool error) {
149 if(!error)
150 this->write_notification("exit");
151 result_processed.set_value();
152 });
153 result_processed.get_future().get();
154
155 LockGuard lock(timeout_threads_mutex);
156 for(auto &thread : timeout_threads)
157 thread.join();
158
159 int exit_status = -1;
160 for(size_t c = 0; c < 10; ++c) {
161 std::this_thread::sleep_for(std::chrono::milliseconds(500));
162 if(process->try_get_exit_status(exit_status))
163 break;
164 }
165 if(exit_status == -1) {
166 process->kill();
167 exit_status = process->get_exit_status();
168 }
169
170 if(on_exit_status)
171 on_exit_status(exit_status);
172 if(Config::get().log.language_server)
173 std::cout << "Language server exit status: " << exit_status << std::endl;
174 }
175
get_capabilities(Source::LanguageProtocolView * view)176 boost::optional<LanguageProtocol::Capabilities> LanguageProtocol::Client::get_capabilities(Source::LanguageProtocolView *view) {
177 if(view) {
178 LockGuard lock(views_mutex);
179 views.emplace(view);
180 }
181
182 LockGuard lock(initialize_mutex);
183 if(initialized)
184 return capabilities;
185 return {};
186 }
187
initialize(Source::LanguageProtocolView * view)188 LanguageProtocol::Capabilities LanguageProtocol::Client::initialize(Source::LanguageProtocolView *view) {
189 if(view) {
190 LockGuard lock(views_mutex);
191 views.emplace(view);
192 }
193
194 LockGuard lock(initialize_mutex);
195
196 if(initialized)
197 return capabilities;
198
199 std::promise<void> result_processed;
200 TinyProcessLib::Process::id_type process_id;
201 {
202 LockGuard lock(read_write_mutex);
203 process_id = process->get_id();
204 }
205 write_request(
206 nullptr, "initialize", "\"processId\":" + std::to_string(process_id) + ",\"rootPath\":\"" + JSON::escape_string(root_path.string()) + "\",\"rootUri\":\"" + JSON::escape_string(filesystem::get_uri_from_path(root_path)) + R"(","capabilities": {
207 "workspace": {
208 "symbol": { "dynamicRegistration": false },
209 "workspaceFolders": true
210 },
211 "textDocument": {
212 "synchronization": { "dynamicRegistration": false, "didSave": true },
213 "completion": {
214 "dynamicRegistration": false,
215 "completionItem": {
216 "snippetSupport": true,
217 "documentationFormat": ["markdown", "plaintext"],
218 "resolveSupport": {
219 "properties": ["documentation", "detail"]
220 }
221 }
222 },
223 "hover": {
224 "dynamicRegistration": false,
225 "contentFormat": ["markdown", "plaintext"]
226 },
227 "signatureHelp": {
228 "dynamicRegistration": false,
229 "signatureInformation": {
230 "documentationFormat": ["markdown", "plaintext"]
231 }
232 },
233 "definition": { "dynamicRegistration": false },
234 "typeDefinition": { "dynamicRegistration": false },
235 "implementation": { "dynamicRegistration": false },
236 "references": { "dynamicRegistration": false },
237 "documentHighlight": { "dynamicRegistration": false },
238 "documentSymbol": { "dynamicRegistration": false },
239 "formatting": { "dynamicRegistration": false },
240 "rangeFormatting": { "dynamicRegistration": false },
241 "rename": { "dynamicRegistration": false },
242 "publishDiagnostics": { "relatedInformation":true },
243 "codeAction": {
244 "dynamicRegistration": false,
245 "resolveSupport": {
246 "properties": ["edit"]
247 },
248 "codeActionLiteralSupport": {
249 "codeActionKind": {
250 "valueSet": [
251 "",
252 "quickfix",
253 "refactor",
254 "refactor.extract",
255 "refactor.inline",
256 "refactor.rewrite",
257 "source",
258 "source.organizeImports"
259 ]
260 }
261 }
262 },
263 "executeCommand": { "dynamicRegistration": false }
264 }
265 },
266 "initializationOptions": {
267 "checkOnSave": { "enable": true }
268 },
269 "trace": "off")",
270 [this, &result_processed](JSON &&result, bool error) {
271 if(!error) {
272 if(auto object = result.object_optional("capabilities")) {
273 if(auto child = object->child_optional("textDocumentSync")) {
274 if(auto integer = child->integer_optional())
275 capabilities.text_document_sync = static_cast<LanguageProtocol::Capabilities::TextDocumentSync>(*integer);
276 else
277 capabilities.text_document_sync = static_cast<LanguageProtocol::Capabilities::TextDocumentSync>(child->integer_or("change", 0));
278 }
279 capabilities.hover = static_cast<bool>(object->child_optional("hoverProvider"));
280 if(auto child = object->child_optional("completionProvider")) {
281 capabilities.completion = true;
282 capabilities.completion_resolve = child->boolean_or("resolveProvider", false);
283 }
284 capabilities.signature_help = static_cast<bool>(object->child_optional("signatureHelpProvider"));
285 capabilities.definition = static_cast<bool>(object->child_optional("definitionProvider"));
286 capabilities.type_definition = static_cast<bool>(object->child_optional("typeDefinitionProvider"));
287 capabilities.implementation = static_cast<bool>(object->child_optional("implementationProvider"));
288 capabilities.references = static_cast<bool>(object->child_optional("referencesProvider"));
289 capabilities.document_highlight = static_cast<bool>(object->child_optional("documentHighlightProvider"));
290 capabilities.workspace_symbol = static_cast<bool>(object->child_optional("workspaceSymbolProvider"));
291 capabilities.document_symbol = static_cast<bool>(object->child_optional("documentSymbolProvider"));
292 capabilities.document_formatting = static_cast<bool>(object->child_optional("documentFormattingProvider"));
293 capabilities.document_range_formatting = static_cast<bool>(object->child_optional("documentRangeFormattingProvider"));
294 capabilities.rename = static_cast<bool>(object->child_optional("renameProvider"));
295 if(auto child = object->child_optional("codeActionProvider")) {
296 capabilities.code_action = true;
297 capabilities.code_action_resolve = child->boolean_or("resolveProvider", false);
298 }
299 capabilities.execute_command = static_cast<bool>(object->child_optional("executeCommandProvider"));
300 capabilities.type_coverage = static_cast<bool>(object->child_optional("typeCoverageProvider"));
301 if(auto workspace = object->object_optional("workspace")) {
302 if(auto workspace_folders = workspace->object_optional("workspaceFolders"))
303 capabilities.workspace_folders = static_cast<bool>(workspace_folders->child_optional("changeNotifications"));
304 }
305 }
306
307 // See https://clangd.llvm.org/extensions.html#utf-8-offsets for documentation on offsetEncoding
308 // Disabled for now since rust-analyzer does not seem to support utf-8 byte offsets from clients
309 // capabilities.use_line_index = result.get<std::string>("offsetEncoding", "") == "utf-8";
310
311 write_notification("initialized");
312 if(capabilities.workspace_folders)
313 write_notification("workspace/didChangeWorkspaceFolders", "\"event\":{\"added\":[{\"uri\": \"" + JSON::escape_string(filesystem::get_uri_from_path(root_path)) + "\",\"name\":\"" + JSON::escape_string(root_path.filename().string()) + "\"}],\"removed\":[]}");
314 }
315 result_processed.set_value();
316 });
317 result_processed.get_future().get();
318
319 initialized = true;
320 return capabilities;
321 }
322
close(Source::LanguageProtocolView * view)323 void LanguageProtocol::Client::close(Source::LanguageProtocolView *view) {
324 {
325 LockGuard lock(views_mutex);
326 auto it = views.find(view);
327 if(it != views.end())
328 views.erase(it);
329 }
330 LockGuard lock(read_write_mutex);
331 for(auto it = handlers.begin(); it != handlers.end();) {
332 if(it->second.first == view) {
333 auto function = std::move(it->second.second);
334 it = handlers.erase(it);
335 lock.unlock();
336 function({}, true);
337 lock.lock();
338 }
339 else
340 it++;
341 }
342 }
343
parse_server_message()344 void LanguageProtocol::Client::parse_server_message() {
345 if(!header_read) {
346 std::string line;
347 while(!header_read && std::getline(server_message_stream, line)) {
348 if(!line.empty() && line != "\r") {
349 if(starts_with(line, "Content-Length: ")) {
350 try {
351 server_message_size = static_cast<size_t>(std::stoul(line.substr(16)));
352 }
353 catch(...) {
354 }
355 }
356 }
357 else if(server_message_size) {
358 server_message_content_pos = server_message_stream.tellg();
359 *server_message_size += server_message_content_pos;
360 header_read = true;
361 }
362 }
363 }
364
365 if(header_read) {
366 server_message_stream.seekg(0, std::ios::end);
367 size_t read_size = server_message_stream.tellg();
368 if(read_size >= *server_message_size) {
369 std::stringstream tmp;
370 if(read_size > *server_message_size) {
371 server_message_stream.seekg(*server_message_size, std::ios::beg);
372 server_message_stream.seekp(*server_message_size, std::ios::beg);
373 for(size_t c = *server_message_size; c < read_size; ++c) {
374 tmp.put(server_message_stream.get());
375 server_message_stream.put(' ');
376 }
377 }
378
379 try {
380 server_message_stream.seekg(server_message_content_pos, std::ios::beg);
381 JSON object(server_message_stream);
382
383 if(Config::get().log.language_server) {
384 std::cout << "language server: ";
385 std::cout << std::setw(2) << object << '\n';
386 }
387
388 auto message_id = object.integer_optional("id");
389 {
390 LockGuard lock(read_write_mutex);
391 if(auto result = object.child_optional("result")) {
392 if(message_id) {
393 auto id_it = handlers.find(*message_id);
394 if(id_it != handlers.end()) {
395 auto function = std::move(id_it->second.second);
396 handlers.erase(id_it);
397 lock.unlock();
398 function(JSON::make_owner(std::move(*result)), false);
399 lock.lock();
400 }
401 }
402 }
403 else if(auto error = object.child_optional("error")) {
404 if(!Config::get().log.language_server)
405 std::cerr << std::setw(2) << object << '\n';
406 if(message_id) {
407 auto id_it = handlers.find(*message_id);
408 if(id_it != handlers.end()) {
409 auto function = std::move(id_it->second.second);
410 handlers.erase(id_it);
411 lock.unlock();
412 function(JSON::make_owner(std::move(*error)), true);
413 lock.lock();
414 }
415 }
416 }
417 else if(auto method = object.string_optional("method")) {
418 if(auto params = object.object_optional("params")) {
419 lock.unlock();
420 if(message_id)
421 handle_server_request(*message_id, *method, JSON::make_owner(std::move(*params)));
422 else
423 handle_server_notification(*method, JSON::make_owner(std::move(*params)));
424 lock.lock();
425 }
426 }
427 }
428 }
429 catch(...) {
430 Terminal::get().async_print("\e[31mError\e[m: failed to parse message from language server\n", true);
431 }
432
433 server_message_stream = std::stringstream();
434 server_message_size.reset();
435 header_read = false;
436
437 tmp.seekg(0, std::ios::end);
438 if(tmp.tellg() > 0) {
439 tmp.seekg(0, std::ios::beg);
440 server_message_stream = std::move(tmp);
441 parse_server_message();
442 }
443 }
444 }
445 }
446
write_request(Source::LanguageProtocolView * view,const std::string & method,const std::string & params,std::function<void (JSON && result,bool error)> && function)447 void LanguageProtocol::Client::write_request(Source::LanguageProtocolView *view, const std::string &method, const std::string ¶ms, std::function<void(JSON &&result, bool error)> &&function) {
448 LockGuard lock(read_write_mutex);
449 if(function) {
450 handlers.emplace(message_id, std::make_pair(view, std::move(function)));
451
452 auto message_id = this->message_id;
453 LockGuard lock(timeout_threads_mutex);
454 timeout_threads.emplace_back([this, message_id] {
455 for(size_t c = 0; c < 40; ++c) {
456 std::this_thread::sleep_for(std::chrono::milliseconds(500) * (language_id == "julia" ? 100 : 1));
457 LockGuard lock(read_write_mutex);
458 auto id_it = handlers.find(message_id);
459 if(id_it == handlers.end())
460 return;
461 }
462 LockGuard lock(read_write_mutex);
463 auto id_it = handlers.find(message_id);
464 if(id_it != handlers.end()) {
465 Terminal::get().async_print("\e[33mWarning\e[m: request to language server timed out. If you suspect the server has crashed, please close and reopen all project source files.\n", true);
466 auto function = std::move(id_it->second.second);
467 handlers.erase(id_it);
468 lock.unlock();
469 function({}, true);
470 lock.lock();
471 }
472 });
473 }
474 std::string content("{\"jsonrpc\":\"2.0\",\"id\":" + std::to_string(message_id++) + ",\"method\":\"" + method + "\"" + (params.empty() ? "" : ",\"params\":{" + params + '}') + '}');
475 if(Config::get().log.language_server)
476 std::cout << "Language client: " << std::setw(2) << JSON(content) << std::endl;
477 if(!process->write("Content-Length: " + std::to_string(content.size()) + "\r\n\r\n" + content)) {
478 Terminal::get().async_print("\e[31mError\e[m: could not write to language server. Please close and reopen all project files.\n", true);
479 auto id_it = handlers.find(message_id - 1);
480 if(id_it != handlers.end()) {
481 auto function = std::move(id_it->second.second);
482 handlers.erase(id_it);
483 lock.unlock();
484 function({}, true);
485 lock.lock();
486 }
487 }
488 }
489
write_response(size_t id,const std::string & result)490 void LanguageProtocol::Client::write_response(size_t id, const std::string &result) {
491 LockGuard lock(read_write_mutex);
492 std::string content("{\"jsonrpc\":\"2.0\",\"id\":" + std::to_string(id) + ",\"result\":" + result + "}");
493 if(Config::get().log.language_server)
494 std::cout << "Language client: " << std::setw(2) << JSON(content) << std::endl;
495 process->write("Content-Length: " + std::to_string(content.size()) + "\r\n\r\n" + content);
496 }
497
write_notification(const std::string & method,const std::string & params)498 void LanguageProtocol::Client::write_notification(const std::string &method, const std::string ¶ms) {
499 LockGuard lock(read_write_mutex);
500 std::string content("{\"jsonrpc\":\"2.0\",\"method\":\"" + method + "\",\"params\":{" + params + "}}");
501 if(Config::get().log.language_server)
502 std::cout << "Language client: " << std::setw(2) << JSON(content) << std::endl;
503 process->write("Content-Length: " + std::to_string(content.size()) + "\r\n\r\n" + content);
504 }
505
handle_server_notification(const std::string & method,JSON && params)506 void LanguageProtocol::Client::handle_server_notification(const std::string &method, JSON &¶ms) {
507 if(method == "textDocument/publishDiagnostics") {
508 std::vector<Diagnostic> diagnostics;
509 auto file = filesystem::get_path_from_uri(params.string_or("uri", ""));
510 if(!file.empty()) {
511 for(auto &child : params.array_or_empty("diagnostics")) {
512 try {
513 diagnostics.emplace_back(std::move(child));
514 }
515 catch(...) {
516 }
517 }
518 LockGuard lock(views_mutex);
519 for(auto view : views) {
520 if(file == view->file_path) {
521 view->update_diagnostics_async(std::move(diagnostics));
522 break;
523 }
524 }
525 }
526 }
527 }
528
handle_server_request(size_t id,const std::string & method,JSON && params)529 void LanguageProtocol::Client::handle_server_request(size_t id, const std::string &method, JSON &¶ms) {
530 if(method == "workspace/applyEdit") {
531 std::promise<void> result_processed;
532 bool applied = true;
533 dispatcher->post([&result_processed, &applied, params = std::make_shared<JSON>(std::move(params))] {
534 ScopeGuard guard({[&result_processed] {
535 result_processed.set_value();
536 }});
537 if(auto current_view = dynamic_cast<Source::LanguageProtocolView *>(Notebook::get().get_current_view())) {
538 LanguageProtocol::WorkspaceEdit workspace_edit;
539 try {
540 workspace_edit = LanguageProtocol::WorkspaceEdit(params->object("edit"), current_view->file_path);
541 }
542 catch(...) {
543 applied = false;
544 return;
545 }
546
547 struct DocumentEditAndView {
548 LanguageProtocol::TextDocumentEdit *document_edit;
549 Source::View *view;
550 };
551 std::vector<DocumentEditAndView> document_edits_and_views;
552
553 for(auto &document_edit : workspace_edit.document_edits) {
554 Source::View *view = nullptr;
555 for(auto it = Source::View::views.begin(); it != Source::View::views.end(); ++it) {
556 if((*it)->file_path == document_edit.file) {
557 view = *it;
558 break;
559 }
560 }
561 if(!view) {
562 if(!Notebook::get().open(document_edit.file)) {
563 applied = false;
564 return;
565 }
566 view = Notebook::get().get_current_view();
567 document_edits_and_views.emplace_back(DocumentEditAndView{&document_edit, view});
568 }
569 else
570 document_edits_and_views.emplace_back(DocumentEditAndView{&document_edit, view});
571 }
572
573 if(current_view)
574 Notebook::get().open(current_view);
575
576 for(auto &document_edit_and_view : document_edits_and_views) {
577 auto document_edit = document_edit_and_view.document_edit;
578 auto view = document_edit_and_view.view;
579 auto buffer = view->get_buffer();
580 buffer->begin_user_action();
581
582 auto end_iter = buffer->end();
583 // If entire buffer is replaced
584 if(document_edit->text_edits.size() == 1 &&
585 document_edit->text_edits[0].range.start.line == 0 && document_edit->text_edits[0].range.start.character == 0 &&
586 (document_edit->text_edits[0].range.end.line > end_iter.get_line() ||
587 (document_edit->text_edits[0].range.end.line == end_iter.get_line() && document_edit->text_edits[0].range.end.character >= current_view->get_line_pos(end_iter)))) {
588 view->replace_text(document_edit->text_edits[0].new_text);
589 }
590 else {
591 for(auto text_edit_it = document_edit->text_edits.rbegin(); text_edit_it != document_edit->text_edits.rend(); ++text_edit_it) {
592 auto start_iter = view->get_iter_at_line_pos(text_edit_it->range.start.line, text_edit_it->range.start.character);
593 auto end_iter = view->get_iter_at_line_pos(text_edit_it->range.end.line, text_edit_it->range.end.character);
594 if(view != current_view)
595 view->get_buffer()->place_cursor(start_iter);
596 buffer->erase(start_iter, end_iter);
597 start_iter = view->get_iter_at_line_pos(text_edit_it->range.start.line, text_edit_it->range.start.character);
598 buffer->insert(start_iter, text_edit_it->new_text);
599 }
600 }
601
602 buffer->end_user_action();
603 if(!view->save()) {
604 applied = false;
605 return;
606 }
607 }
608 }
609 });
610 result_processed.get_future().get();
611 write_response(id, std::string("{\"applied\":") + (applied ? "true" : "false") + '}');
612 }
613 else if(method == "workspace/workspaceFolders")
614 write_response(id, "[{\"uri\": \"" + JSON::escape_string(filesystem::get_uri_from_path(root_path)) + "\",\"name\":\"" + JSON::escape_string(root_path.filename().string()) + "\"}]");
615 else if(method == "client/registerCapability") {
616 try {
617 for(auto ®istration : params.array("registrations")) {
618 if(registration.string("method") == "workspace/didChangeWorkspaceFolders")
619 write_notification("workspace/didChangeWorkspaceFolders", "\"event\":{\"added\":[{\"uri\": \"" + JSON::escape_string(filesystem::get_uri_from_path(root_path)) + "\",\"name\":\"" + JSON::escape_string(root_path.filename().string()) + "\"}],\"removed\":[]}");
620 }
621 }
622 catch(...) {
623 }
624 write_response(id, "{}");
625 }
626 else
627 write_response(id, "{}"); // TODO: write error instead on unsupported methods
628 }
629
LanguageProtocolView(const boost::filesystem::path & file_path,const Glib::RefPtr<Gsv::Language> & language,std::string language_id_,std::string language_server_)630 Source::LanguageProtocolView::LanguageProtocolView(const boost::filesystem::path &file_path, const Glib::RefPtr<Gsv::Language> &language, std::string language_id_, std::string language_server_)
631 : Source::BaseView(file_path, language), Source::View(file_path, language), uri(filesystem::get_uri_from_path(file_path)), uri_escaped(JSON::escape_string(uri)), language_id(std::move(language_id_)), language_server(std::move(language_server_)), client(LanguageProtocol::Client::get(file_path, language_id, language_server)) {
632 initialize();
633 }
634
initialize()635 void Source::LanguageProtocolView::initialize() {
636 status_diagnostics = std::make_tuple(0, 0, 0);
637 if(update_status_diagnostics)
638 update_status_diagnostics(this);
639
640 status_state = "initializing...";
641 if(update_status_state)
642 update_status_state(this);
643
644 set_editable(false);
645
646 auto init = [this](const LanguageProtocol::Capabilities &capabilities) {
647 this->capabilities = capabilities;
648 set_editable(true);
649
650 write_did_open_notification();
651
652 if(!initialized) {
653 setup_signals();
654 setup_autocomplete();
655 setup_navigation_and_refactoring();
656 Menu::get().toggle_menu_items();
657 }
658
659 if(status_state == "initializing...") {
660 status_state = "";
661 if(update_status_state)
662 update_status_state(this);
663 }
664
665 update_type_coverage();
666
667 initialized = true;
668 };
669
670 if(auto capabilities = client->get_capabilities(this))
671 init(*capabilities);
672 else {
673 initialize_thread = std::thread([this, init] {
674 auto capabilities = client->initialize(this);
675 dispatcher.post([init, capabilities] {
676 init(capabilities);
677 });
678 });
679 }
680 }
681
close()682 void Source::LanguageProtocolView::close() {
683 autocomplete_delayed_show_arguments_connection.disconnect();
684 update_type_coverage_connection.disconnect();
685
686 if(initialize_thread.joinable())
687 initialize_thread.join();
688
689 if(autocomplete) {
690 autocomplete->state = Autocomplete::State::idle;
691 if(autocomplete->thread.joinable())
692 autocomplete->thread.join();
693 }
694
695 write_notification("textDocument/didClose");
696 client->close(this);
697 client = nullptr;
698 }
699
~LanguageProtocolView()700 Source::LanguageProtocolView::~LanguageProtocolView() {
701 close();
702 thread_pool.shutdown(true);
703 }
704
get_line_pos(const Gtk::TextIter & iter)705 int Source::LanguageProtocolView::get_line_pos(const Gtk::TextIter &iter) {
706 if(capabilities.use_line_index)
707 return iter.get_line_index();
708 return utf16_code_unit_count(get_line(iter), 0, iter.get_line_index());
709 }
710
get_line_pos(int line,int line_index)711 int Source::LanguageProtocolView::get_line_pos(int line, int line_index) {
712 if(capabilities.use_line_index)
713 return line_index;
714 return utf16_code_unit_count(get_line(line), 0, line_index);
715 }
716
get_iter_at_line_pos(int line,int pos)717 Gtk::TextIter Source::LanguageProtocolView::get_iter_at_line_pos(int line, int pos) {
718 if(capabilities.use_line_index)
719 return get_iter_at_line_index(line, pos);
720 return get_iter_at_line_index(line, utf16_code_units_byte_count(get_line(line), pos));
721 }
722
make_position(int line,int character)723 std::pair<std::string, std::string> Source::LanguageProtocolView::make_position(int line, int character) {
724 return {"position", "{\"line\":" + std::to_string(line) + ",\"character\":" + std::to_string(character) + "}"};
725 }
726
make_range(const std::pair<int,int> & start,const std::pair<int,int> & end)727 std::pair<std::string, std::string> Source::LanguageProtocolView::make_range(const std::pair<int, int> &start, const std::pair<int, int> &end) {
728 return {"range", "{\"start\":{\"line\":" + std::to_string(start.first) + ",\"character\":" + std::to_string(start.second) + "},\"end\":{\"line\":" + std::to_string(end.first) + ",\"character\":" + std::to_string(end.second) + "}}"};
729 }
730
to_string(const std::pair<std::string,std::string> & param)731 std::string Source::LanguageProtocolView::to_string(const std::pair<std::string, std::string> ¶m) {
732 std::string result;
733 result.reserve(param.first.size() + param.second.size() + 3);
734 result += '"';
735 result += param.first;
736 result += "\":";
737 result += param.second;
738 return result;
739 }
740
to_string(const std::vector<std::pair<std::string,std::string>> & params)741 std::string Source::LanguageProtocolView::to_string(const std::vector<std::pair<std::string, std::string>> ¶ms) {
742 size_t size = params.empty() ? 0 : params.size() - 1;
743 for(auto ¶m : params)
744 size += param.first.size() + param.second.size() + 3;
745
746 std::string result;
747 result.reserve(size);
748 for(auto ¶m : params) {
749 if(!result.empty())
750 result += ',';
751 result += '"';
752 result += param.first;
753 result += "\":";
754 result += param.second;
755 }
756 return result;
757 }
758
write_request(const std::string & method,const std::string & params,std::function<void (JSON && result,bool)> && function)759 void Source::LanguageProtocolView::write_request(const std::string &method, const std::string ¶ms, std::function<void(JSON &&result, bool)> &&function) {
760 client->write_request(this, method, "\"textDocument\":{\"uri\":\"" + uri_escaped + "\"}" + (params.empty() ? "" : "," + params), std::move(function));
761 }
762
write_notification(const std::string & method)763 void Source::LanguageProtocolView::write_notification(const std::string &method) {
764 client->write_notification(method, "\"textDocument\":{\"uri\":\"" + uri_escaped + "\"}");
765 }
766
write_did_open_notification()767 void Source::LanguageProtocolView::write_did_open_notification() {
768 client->write_notification("textDocument/didOpen", "\"textDocument\":{\"uri\":\"" + uri_escaped + "\",\"version\":" + std::to_string(document_version++) + ",\"languageId\":\"" + language_id + "\",\"text\":\"" + JSON::escape_string(get_buffer()->get_text().raw()) + "\"}");
769 }
770
write_did_change_notification(const std::vector<std::pair<std::string,std::string>> & params)771 void Source::LanguageProtocolView::write_did_change_notification(const std::vector<std::pair<std::string, std::string>> ¶ms) {
772 client->write_notification("textDocument/didChange", "\"textDocument\":{\"uri\":\"" + uri_escaped + "\",\"version\":" + std::to_string(document_version++) + (params.empty() ? "}" : "}," + to_string(params)));
773 }
774
rename(const boost::filesystem::path & path)775 void Source::LanguageProtocolView::rename(const boost::filesystem::path &path) {
776 // Reset view
777 close();
778 dispatcher.reset();
779 Source::DiffView::rename(path);
780 uri = filesystem::get_uri_from_path(path);
781 uri_escaped = JSON::escape_string(uri);
782 client = LanguageProtocol::Client::get(file_path, language_id, language_server);
783 initialize();
784 }
785
save()786 bool Source::LanguageProtocolView::save() {
787 if(!Source::View::save())
788 return false;
789
790 write_notification("textDocument/didSave");
791
792 update_type_coverage();
793
794 return true;
795 }
796
setup_navigation_and_refactoring()797 void Source::LanguageProtocolView::setup_navigation_and_refactoring() {
798 if(capabilities.document_formatting && !(format_style && is_js /* Use Prettier instead */)) {
799 format_style = [this](bool continue_without_style_file) {
800 if(!continue_without_style_file) {
801 bool has_style_file = false;
802 auto style_file_search_path = file_path.parent_path();
803 auto style_file = '.' + language_id + "-format";
804
805 boost::system::error_code ec;
806 while(true) {
807 if(boost::filesystem::exists(style_file_search_path / style_file, ec)) {
808 has_style_file = true;
809 break;
810 }
811 if(style_file_search_path == style_file_search_path.root_directory())
812 break;
813 style_file_search_path = style_file_search_path.parent_path();
814 }
815
816 if(!has_style_file && language_id == "rust") {
817 auto style_file_search_path = file_path.parent_path();
818 while(true) {
819 if(boost::filesystem::exists(style_file_search_path / "rustfmt.toml", ec) ||
820 boost::filesystem::exists(style_file_search_path / ".rustfmt.toml", ec)) {
821 has_style_file = true;
822 break;
823 }
824 if(style_file_search_path == style_file_search_path.root_directory())
825 break;
826 style_file_search_path = style_file_search_path.parent_path();
827 }
828 }
829
830 if(!has_style_file && !continue_without_style_file)
831 return;
832 }
833
834 std::vector<LanguageProtocol::TextEdit> text_edits;
835 std::promise<void> result_processed;
836
837 std::string method;
838 std::vector<std::pair<std::string, std::string>> params = {{"options", '{' + to_string({{"tabSize", std::to_string(tab_size)}, {"insertSpaces", tab_char == ' ' ? "true" : "false"}}) + '}'}};
839 if(get_buffer()->get_has_selection() && capabilities.document_range_formatting) {
840 method = "textDocument/rangeFormatting";
841 Gtk::TextIter start, end;
842 get_buffer()->get_selection_bounds(start, end);
843 params.emplace_back(make_range({start.get_line(), get_line_pos(start)}, {end.get_line(), get_line_pos(end)}));
844 }
845 else
846 method = "textDocument/formatting";
847
848 write_request(method, to_string(params), [&text_edits, &result_processed](JSON &&result, bool error) {
849 if(!error) {
850 for(auto &edit : result.array_or_empty()) {
851 try {
852 text_edits.emplace_back(edit);
853 }
854 catch(...) {
855 }
856 }
857 }
858 result_processed.set_value();
859 });
860 result_processed.get_future().get();
861
862 auto end_iter = get_buffer()->end();
863 // If entire buffer is replaced:
864 if(text_edits.size() == 1 &&
865 text_edits[0].range.start.line == 0 && text_edits[0].range.start.character == 0 &&
866 (text_edits[0].range.end.line > end_iter.get_line() ||
867 (text_edits[0].range.end.line == end_iter.get_line() && text_edits[0].range.end.character >= get_line_pos(end_iter)))) {
868 replace_text(text_edits[0].new_text);
869 }
870 else {
871 get_buffer()->begin_user_action();
872 for(auto it = text_edits.rbegin(); it != text_edits.rend(); ++it) {
873 auto start = get_iter_at_line_pos(it->range.start.line, it->range.start.character);
874 auto end = get_iter_at_line_pos(it->range.end.line, it->range.end.character);
875 get_buffer()->erase(start, end);
876 start = get_iter_at_line_pos(it->range.start.line, it->range.start.character);
877 get_buffer()->insert(start, it->new_text);
878 }
879 get_buffer()->end_user_action();
880 }
881 };
882 }
883
884 if(capabilities.definition) {
885 get_declaration_location = [this]() {
886 auto offset = get_declaration(get_buffer()->get_insert()->get_iter());
887 if(!offset)
888 Info::get().print("No declaration found");
889 return offset;
890 };
891
892 // Assumes that capabilities.definition is available when capabilities.implementation is supported
893 if(capabilities.implementation) {
894 get_declaration_or_implementation_locations = [this]() {
895 // Try implementation locations first
896 auto iter = get_buffer()->get_insert()->get_iter();
897 auto offsets = get_implementations(iter);
898 auto token_iters = get_token_iters(iter);
899 bool is_implementation = false;
900 for(auto &offset : offsets) {
901 if(offset.file_path == file_path && get_iter_at_line_pos(offset.line, offset.index) == token_iters.first) {
902 is_implementation = true;
903 break;
904 }
905 }
906 if(offsets.empty() || is_implementation) {
907 if(auto offset = get_declaration_location())
908 return std::vector<Offset>({std::move(offset)});
909 }
910 return offsets;
911 };
912 }
913 else {
914 get_declaration_or_implementation_locations = [this]() {
915 std::vector<Offset> offsets;
916 if(auto offset = get_declaration_location())
917 offsets.emplace_back(std::move(offset));
918 return offsets;
919 };
920 }
921 }
922 if(capabilities.type_definition) {
923 get_type_declaration_location = [this]() {
924 auto offset = get_type_declaration(get_buffer()->get_insert()->get_iter());
925 if(!offset)
926 Info::get().print("No type declaration found");
927 return offset;
928 };
929 }
930 if(capabilities.implementation) {
931 get_implementation_locations = [this]() {
932 auto offsets = get_implementations(get_buffer()->get_insert()->get_iter());
933 if(offsets.empty())
934 Info::get().print("No implementation found");
935 return offsets;
936 };
937 }
938
939 if(capabilities.references || capabilities.document_highlight) {
940 get_usages = [this] {
941 auto iter = get_buffer()->get_insert()->get_iter();
942 std::set<LanguageProtocol::Location> locations;
943 std::promise<void> result_processed;
944
945 std::string method;
946 if(capabilities.references)
947 method = "textDocument/references";
948 else
949 method = "textDocument/documentHighlight";
950
951 write_request(method, to_string({make_position(iter.get_line(), get_line_pos(iter)), {"context", "{\"includeDeclaration\":true}"}}), [this, &locations, &result_processed](JSON &&result, bool error) {
952 if(!error) {
953 try {
954 for(auto &location : result.array_or_empty())
955 locations.emplace(location, !capabilities.references ? file_path.string() : std::string());
956 }
957 catch(...) {
958 locations.clear();
959 }
960 }
961 result_processed.set_value();
962 });
963 result_processed.get_future().get();
964
965 auto embolden_token = [this](std::string &line, int token_start_pos, int token_end_pos) {
966 // Markup token as bold
967 size_t pos = 0;
968 while((pos = line.find('&', pos)) != std::string::npos) {
969 size_t pos2 = line.find(';', pos + 2);
970 if(static_cast<size_t>(token_start_pos) > pos) {
971 token_start_pos += pos2 - pos;
972 token_end_pos += pos2 - pos;
973 }
974 else if(static_cast<size_t>(token_end_pos) > pos)
975 token_end_pos += pos2 - pos;
976 else
977 break;
978 pos = pos2 + 1;
979 }
980
981 if(!capabilities.use_line_index) {
982 auto code_units_diff = token_end_pos - token_start_pos;
983 token_start_pos = utf16_code_units_byte_count(line, token_start_pos);
984 token_end_pos = token_start_pos + utf16_code_units_byte_count(line, code_units_diff, token_start_pos);
985 }
986
987 if(static_cast<size_t>(token_start_pos) > line.size())
988 token_start_pos = line.size();
989 if(static_cast<size_t>(token_end_pos) > line.size())
990 token_end_pos = line.size();
991 if(token_start_pos < token_end_pos) {
992 line.insert(token_end_pos, "</b>");
993 line.insert(token_start_pos, "<b>");
994 }
995
996 size_t start_pos = 0;
997 while(start_pos < line.size() && (line[start_pos] == ' ' || line[start_pos] == '\t'))
998 ++start_pos;
999 if(start_pos > 0)
1000 line.erase(0, start_pos);
1001 };
1002
1003 std::unordered_map<std::string, std::vector<std::string>> file_lines;
1004 std::vector<std::pair<Offset, std::string>> usages;
1005 auto c = static_cast<size_t>(-1);
1006 for(auto &location : locations) {
1007 ++c;
1008 usages.emplace_back(Offset(location.range.start.line, location.range.start.character, location.file), std::string());
1009 auto &usage = usages.back();
1010 auto view_it = views.end();
1011 for(auto it = views.begin(); it != views.end(); ++it) {
1012 if(location.file == (*it)->file_path) {
1013 view_it = it;
1014 break;
1015 }
1016 }
1017 if(view_it != views.end()) {
1018 if(location.range.start.line < (*view_it)->get_buffer()->get_line_count()) {
1019 auto start = (*view_it)->get_buffer()->get_iter_at_line(location.range.start.line);
1020 auto end = start;
1021 end.forward_to_line_end();
1022 usage.second = Glib::Markup::escape_text((*view_it)->get_buffer()->get_text(start, end));
1023 embolden_token(usage.second, location.range.start.character, location.range.end.character);
1024 }
1025 }
1026 else {
1027 auto it = file_lines.find(location.file);
1028 if(it == file_lines.end()) {
1029 std::ifstream ifs(location.file);
1030 if(ifs) {
1031 std::vector<std::string> lines;
1032 std::string line;
1033 while(std::getline(ifs, line)) {
1034 if(!line.empty() && line.back() == '\r')
1035 line.pop_back();
1036 lines.emplace_back(line);
1037 }
1038 auto pair = file_lines.emplace(location.file, lines);
1039 it = pair.first;
1040 }
1041 else {
1042 auto pair = file_lines.emplace(location.file, std::vector<std::string>());
1043 it = pair.first;
1044 }
1045 }
1046
1047 if(static_cast<size_t>(location.range.start.line) < it->second.size()) {
1048 usage.second = Glib::Markup::escape_text(it->second[location.range.start.line]);
1049 embolden_token(usage.second, location.range.start.character, location.range.end.character);
1050 }
1051 }
1052 }
1053
1054 if(locations.empty())
1055 Info::get().print("No symbol found at current cursor location");
1056
1057 return usages;
1058 };
1059 }
1060
1061 get_token_spelling = [this] {
1062 auto spelling = get_token(get_buffer()->get_insert()->get_iter());
1063 if(spelling.empty())
1064 Info::get().print("No valid symbol found at current cursor location");
1065 return spelling;
1066 };
1067
1068 if(capabilities.rename || capabilities.document_highlight) {
1069 rename_similar_tokens = [this](const std::string &text) {
1070 auto previous_text = get_token(get_buffer()->get_insert()->get_iter());
1071 if(previous_text.empty())
1072 return;
1073
1074 auto iter = get_buffer()->get_insert()->get_iter();
1075 LanguageProtocol::WorkspaceEdit workspace_edit;
1076 std::promise<void> result_processed;
1077 if(capabilities.rename) {
1078 write_request("textDocument/rename", to_string({make_position(iter.get_line(), get_line_pos(iter)), {"newName", '"' + text + '"'}}), [this, &workspace_edit, &result_processed](JSON &&result, bool error) {
1079 if(!error) {
1080 workspace_edit = LanguageProtocol::WorkspaceEdit(result, file_path);
1081 }
1082 result_processed.set_value();
1083 });
1084 }
1085 else {
1086 write_request("textDocument/documentHighlight", to_string({make_position(iter.get_line(), get_line_pos(iter)), {"context", "{\"includeDeclaration\":true}"}}), [this, &workspace_edit, &text, &result_processed](JSON &&result, bool error) {
1087 if(!error) {
1088 try {
1089 std::vector<LanguageProtocol::TextEdit> edits;
1090 for(auto &edit : result.array_or_empty())
1091 edits.emplace_back(edit, text);
1092 workspace_edit.document_edits.emplace_back(file_path.string(), std::move(edits));
1093 }
1094 catch(...) {
1095 workspace_edit.document_edits.clear();
1096 }
1097 }
1098 result_processed.set_value();
1099 });
1100 }
1101 result_processed.get_future().get();
1102
1103 auto current_view = Notebook::get().get_current_view();
1104
1105 struct DocumentEditAndView {
1106 LanguageProtocol::TextDocumentEdit *document_edit;
1107 Source::View *view;
1108 bool close;
1109 };
1110 std::vector<DocumentEditAndView> document_edits_and_views;
1111
1112 for(auto &document_edit : workspace_edit.document_edits) {
1113 Source::View *view = nullptr;
1114 for(auto it = views.begin(); it != views.end(); ++it) {
1115 if((*it)->file_path == document_edit.file) {
1116 view = *it;
1117 break;
1118 }
1119 }
1120 if(!view) {
1121 if(!Notebook::get().open(document_edit.file))
1122 return;
1123 view = Notebook::get().get_current_view();
1124 document_edits_and_views.emplace_back(DocumentEditAndView{&document_edit, view, true});
1125 }
1126 else
1127 document_edits_and_views.emplace_back(DocumentEditAndView{&document_edit, view, false});
1128 }
1129
1130 if(current_view)
1131 Notebook::get().open(current_view);
1132
1133 if(!document_edits_and_views.empty()) {
1134 Terminal::get().print("Renamed ");
1135 Terminal::get().print(previous_text, true);
1136 Terminal::get().print(" to ");
1137 Terminal::get().print(text, true);
1138 Terminal::get().print(" at:\n");
1139 }
1140
1141 for(auto &document_edit_and_view : document_edits_and_views) {
1142 auto document_edit = document_edit_and_view.document_edit;
1143 auto view = document_edit_and_view.view;
1144 auto buffer = view->get_buffer();
1145 buffer->begin_user_action();
1146
1147 auto end_iter = buffer->end();
1148 // If entire buffer is replaced
1149 if(document_edit->text_edits.size() == 1 &&
1150 document_edit->text_edits[0].range.start.line == 0 && document_edit->text_edits[0].range.start.character == 0 &&
1151 (document_edit->text_edits[0].range.end.line > end_iter.get_line() ||
1152 (document_edit->text_edits[0].range.end.line == end_iter.get_line() && document_edit->text_edits[0].range.end.character >= get_line_pos(end_iter)))) {
1153 view->replace_text(document_edit->text_edits[0].new_text);
1154
1155 Terminal::get().print(filesystem::get_short_path(view->file_path).string() + ":1:1\n");
1156 }
1157 else {
1158 struct TerminalOutput {
1159 std::string prefix;
1160 std::string new_text;
1161 std::string postfix;
1162 };
1163 std::list<TerminalOutput> terminal_output_list;
1164
1165 for(auto text_edit_it = document_edit->text_edits.rbegin(); text_edit_it != document_edit->text_edits.rend(); ++text_edit_it) {
1166 auto start_iter = view->get_iter_at_line_pos(text_edit_it->range.start.line, text_edit_it->range.start.character);
1167 auto end_iter = view->get_iter_at_line_pos(text_edit_it->range.end.line, text_edit_it->range.end.character);
1168 if(view != current_view)
1169 view->get_buffer()->place_cursor(start_iter);
1170 buffer->erase(start_iter, end_iter);
1171 start_iter = view->get_iter_at_line_pos(text_edit_it->range.start.line, text_edit_it->range.start.character);
1172
1173 auto start_line_iter = buffer->get_iter_at_line(start_iter.get_line());
1174 while((*start_line_iter == ' ' || *start_line_iter == '\t') && start_line_iter < start_iter && start_line_iter.forward_char()) {
1175 }
1176 auto end_line_iter = start_iter;
1177 while(!end_line_iter.ends_line() && end_line_iter.forward_char()) {
1178 }
1179 terminal_output_list.emplace_front(TerminalOutput{filesystem::get_short_path(view->file_path).string() + ':' + std::to_string(text_edit_it->range.start.line + 1) + ':' + std::to_string(text_edit_it->range.start.character + 1) + ": " + buffer->get_text(start_line_iter, start_iter),
1180 text_edit_it->new_text,
1181 buffer->get_text(start_iter, end_line_iter) + '\n'});
1182
1183 buffer->insert(start_iter, text_edit_it->new_text);
1184 }
1185
1186 for(auto &output : terminal_output_list) {
1187 Terminal::get().print(output.prefix);
1188 Terminal::get().print(output.new_text, true);
1189 Terminal::get().print(output.postfix);
1190 }
1191 }
1192
1193 buffer->end_user_action();
1194 if(!view->save())
1195 return;
1196 }
1197
1198 for(auto &document_edit_and_view : document_edits_and_views) {
1199 if(document_edit_and_view.close)
1200 Notebook::get().close(document_edit_and_view.view);
1201 document_edit_and_view.close = false;
1202 }
1203 };
1204 }
1205
1206 if(capabilities.document_symbol) {
1207 get_methods = [this]() {
1208 std::vector<std::pair<Offset, std::string>> methods;
1209
1210 std::promise<void> result_processed;
1211 write_request("textDocument/documentSymbol", {}, [&result_processed, &methods](JSON &&result, bool error) {
1212 if(!error) {
1213 std::function<void(const JSON &symbols, const std::string &container)> parse_result = [&methods, &parse_result](const JSON &symbols, const std::string &container) {
1214 for(auto &symbol : symbols.array_or_empty()) {
1215 try {
1216 auto name = symbol.string("name");
1217 auto kind = symbol.integer("kind");
1218 if(kind == 6 || kind == 9 || kind == 12) {
1219 std::unique_ptr<LanguageProtocol::Range> range;
1220 std::string prefix;
1221 if(auto location_object = symbol.object_optional("location")) {
1222 LanguageProtocol::Location location(*location_object);
1223 range = std::make_unique<LanguageProtocol::Range>(location.range);
1224 std::string container = symbol.string_or("containerName", "");
1225 if(!container.empty())
1226 prefix = container;
1227 }
1228 else {
1229 range = std::make_unique<LanguageProtocol::Range>(symbol.object("range"));
1230 if(!container.empty())
1231 prefix = container;
1232 }
1233 methods.emplace_back(Offset(range->start.line, range->start.character), (!prefix.empty() ? Glib::Markup::escape_text(prefix) + ':' : "") + std::to_string(range->start.line + 1) + ": " + "<b>" + Glib::Markup::escape_text(name) + "</b>");
1234 }
1235 if(auto children = symbol.child_optional("children"))
1236 parse_result(*children, (!container.empty() ? container + "::" : "") + name);
1237 }
1238 catch(...) {
1239 }
1240 }
1241 };
1242 parse_result(result, "");
1243 }
1244 result_processed.set_value();
1245 });
1246 result_processed.get_future().get();
1247
1248 std::sort(methods.begin(), methods.end(), [](const std::pair<Offset, std::string> &a, const std::pair<Offset, std::string> &b) {
1249 return a.first < b.first;
1250 });
1251 return methods;
1252 };
1253 }
1254
1255 goto_next_diagnostic = [this]() {
1256 place_cursor_at_next_diagnostic();
1257 };
1258
1259 get_fix_its = [this]() {
1260 if(!fix_its.empty())
1261 return fix_its;
1262
1263 if(!capabilities.code_action) {
1264 Info::get().print("No fix-its found in current buffer");
1265 return std::vector<FixIt>{};
1266 }
1267
1268 // Fetch code actions if no fix-its are available
1269 std::promise<void> result_processed;
1270 Gtk::TextIter start, end;
1271 get_buffer()->get_selection_bounds(start, end);
1272 std::vector<std::pair<std::string, std::shared_ptr<JSON>>> results;
1273 write_request("textDocument/codeAction", to_string({make_range({start.get_line(), get_line_pos(start)}, {end.get_line(), get_line_pos(end)}), {"context", "{\"diagnostics\":[]}"}}), [&result_processed, &results](JSON &&result, bool error) {
1274 if(!error) {
1275 for(auto &code_action : result.array_or_empty()) {
1276 auto title = code_action.string_or("title", "");
1277 if(!title.empty())
1278 results.emplace_back(title, std::make_shared<JSON>(JSON::make_owner(std::move(code_action))));
1279 }
1280 }
1281 result_processed.set_value();
1282 });
1283 result_processed.get_future().get();
1284
1285 if(results.empty()) {
1286 Info::get().print("No code actions found at cursor");
1287 return std::vector<FixIt>{};
1288 }
1289
1290 SelectionDialog::create(this, true, true);
1291 for(auto &result : results)
1292 SelectionDialog::get()->add_row(result.first);
1293 SelectionDialog::get()->on_select = [this, results = std::move(results)](unsigned int index, const std::string &text, bool hide_window) {
1294 if(index >= results.size())
1295 return;
1296
1297 auto edit = results[index].second->object_optional("edit");
1298 if(capabilities.code_action_resolve && !edit) {
1299 std::promise<void> result_processed;
1300 std::stringstream ss;
1301 bool first = true;
1302 for(auto &child : results[index].second->children_or_empty()) {
1303 ss << (!first ? ",\"" : "\"") << JSON::escape_string(child.first) << "\":" << child.second;
1304 first = false;
1305 }
1306 write_request("codeAction/resolve", ss.str(), [&result_processed, &edit](JSON &&result, bool error) {
1307 if(!error) {
1308 auto object = result.object_optional("edit");
1309 if(object)
1310 edit = JSON::make_owner(std::move(*object));
1311 }
1312 result_processed.set_value();
1313 });
1314 result_processed.get_future().get();
1315 }
1316
1317 if(edit) {
1318 LanguageProtocol::WorkspaceEdit workspace_edit;
1319 try {
1320 workspace_edit = LanguageProtocol::WorkspaceEdit(*edit, file_path);
1321 }
1322 catch(...) {
1323 return;
1324 }
1325
1326 auto current_view = Notebook::get().get_current_view();
1327
1328 struct DocumentEditAndView {
1329 LanguageProtocol::TextDocumentEdit *document_edit;
1330 Source::View *view;
1331 };
1332 std::vector<DocumentEditAndView> document_edits_and_views;
1333
1334 for(auto &document_edit : workspace_edit.document_edits) {
1335 Source::View *view = nullptr;
1336 for(auto it = views.begin(); it != views.end(); ++it) {
1337 if((*it)->file_path == document_edit.file) {
1338 view = *it;
1339 break;
1340 }
1341 }
1342 if(!view) {
1343 if(!Notebook::get().open(document_edit.file))
1344 return;
1345 view = Notebook::get().get_current_view();
1346 document_edits_and_views.emplace_back(DocumentEditAndView{&document_edit, view});
1347 }
1348 else
1349 document_edits_and_views.emplace_back(DocumentEditAndView{&document_edit, view});
1350 }
1351
1352 if(current_view)
1353 Notebook::get().open(current_view);
1354
1355 for(auto &document_edit_and_view : document_edits_and_views) {
1356 auto document_edit = document_edit_and_view.document_edit;
1357 auto view = document_edit_and_view.view;
1358 auto buffer = view->get_buffer();
1359 buffer->begin_user_action();
1360
1361 auto end_iter = buffer->end();
1362 // If entire buffer is replaced
1363 if(document_edit->text_edits.size() == 1 &&
1364 document_edit->text_edits[0].range.start.line == 0 && document_edit->text_edits[0].range.start.character == 0 &&
1365 (document_edit->text_edits[0].range.end.line > end_iter.get_line() ||
1366 (document_edit->text_edits[0].range.end.line == end_iter.get_line() && document_edit->text_edits[0].range.end.character >= get_line_pos(end_iter)))) {
1367 view->replace_text(document_edit->text_edits[0].new_text);
1368 }
1369 else {
1370 for(auto text_edit_it = document_edit->text_edits.rbegin(); text_edit_it != document_edit->text_edits.rend(); ++text_edit_it) {
1371 auto start_iter = view->get_iter_at_line_pos(text_edit_it->range.start.line, text_edit_it->range.start.character);
1372 auto end_iter = view->get_iter_at_line_pos(text_edit_it->range.end.line, text_edit_it->range.end.character);
1373 if(view != current_view)
1374 view->get_buffer()->place_cursor(start_iter);
1375 buffer->erase(start_iter, end_iter);
1376 start_iter = view->get_iter_at_line_pos(text_edit_it->range.start.line, text_edit_it->range.start.character);
1377 buffer->insert(start_iter, text_edit_it->new_text);
1378 }
1379 }
1380
1381 buffer->end_user_action();
1382 if(!view->save())
1383 return;
1384 }
1385 }
1386
1387 if(capabilities.execute_command) {
1388 auto command = results[index].second->object_optional("command");
1389 if(command) {
1390 std::stringstream ss;
1391 bool first = true;
1392 for(auto &child : command->children_or_empty()) {
1393 ss << (!first ? ",\"" : "\"") << JSON::escape_string(child.first) << "\":" << child.second;
1394 first = false;
1395 }
1396 write_request("workspace/executeCommand", ss.str(), [](JSON &&result, bool error) {
1397 });
1398 }
1399 }
1400 };
1401 hide_tooltips();
1402 SelectionDialog::get()->show();
1403
1404 return std::vector<FixIt>{};
1405 };
1406 }
1407
setup_signals()1408 void Source::LanguageProtocolView::setup_signals() {
1409 if(capabilities.text_document_sync == LanguageProtocol::Capabilities::TextDocumentSync::incremental) {
1410 get_buffer()->signal_insert().connect(
1411 [this](const Gtk::TextIter &start, const Glib::ustring &text, int bytes) {
1412 std::pair<int, int> location = {start.get_line(), get_line_pos(start)};
1413 write_did_change_notification({{"contentChanges", "[{" + to_string({make_range(location, location), {"text", '"' + JSON::escape_string(text.raw()) + '"'}}) + "}]"}});
1414 },
1415 false);
1416
1417 get_buffer()->signal_erase().connect(
1418 [this](const Gtk::TextIter &start, const Gtk::TextIter &end) {
1419 write_did_change_notification({{"contentChanges", "[{" + to_string({make_range({start.get_line(), get_line_pos(start)}, {end.get_line(), get_line_pos(end)}), {"text", "\"\""}}) + "}]"}});
1420 },
1421 false);
1422 }
1423 else if(capabilities.text_document_sync == LanguageProtocol::Capabilities::TextDocumentSync::full) {
1424 get_buffer()->signal_changed().connect([this]() {
1425 write_did_change_notification({{"contentChanges", "[{" + to_string({"text", '"' + JSON::escape_string(get_buffer()->get_text().raw()) + '"'}) + "}]"}});
1426 });
1427 }
1428 }
1429
setup_autocomplete()1430 void Source::LanguageProtocolView::setup_autocomplete() {
1431 autocomplete = std::make_unique<Autocomplete>(this, interactive_completion, last_keyval, false, true);
1432
1433 if(!capabilities.completion)
1434 return;
1435
1436 non_interactive_completion = [this] {
1437 if(CompletionDialog::get() && CompletionDialog::get()->is_visible())
1438 return;
1439 autocomplete->run();
1440 };
1441
1442 autocomplete->reparse = [this] {
1443 autocomplete_rows.clear();
1444 };
1445
1446 if(capabilities.signature_help) {
1447 // Activate argument completions
1448 get_buffer()->signal_changed().connect(
1449 [this] {
1450 autocompete_possibly_no_arguments = false;
1451 if(!interactive_completion)
1452 return;
1453 if(CompletionDialog::get() && CompletionDialog::get()->is_visible())
1454 return;
1455 if(!has_focus())
1456 return;
1457 if(autocomplete_show_arguments)
1458 autocomplete->stop();
1459 autocomplete_show_arguments = false;
1460 autocomplete_delayed_show_arguments_connection.disconnect();
1461 autocomplete_delayed_show_arguments_connection = Glib::signal_timeout().connect(
1462 [this]() {
1463 if(get_buffer()->get_has_selection())
1464 return false;
1465 if(CompletionDialog::get() && CompletionDialog::get()->is_visible())
1466 return false;
1467 if(!has_focus())
1468 return false;
1469 if(is_possible_argument()) {
1470 autocomplete->stop();
1471 autocomplete->run();
1472 }
1473 return false;
1474 },
1475 500);
1476 },
1477 false);
1478
1479 // Remove argument completions
1480 signal_key_press_event().connect(
1481 [this](GdkEventKey *event) {
1482 if(autocomplete_show_arguments && CompletionDialog::get() && CompletionDialog::get()->is_visible() &&
1483 event->keyval != GDK_KEY_Down && event->keyval != GDK_KEY_Up &&
1484 event->keyval != GDK_KEY_Return && event->keyval != GDK_KEY_KP_Enter &&
1485 event->keyval != GDK_KEY_ISO_Left_Tab && event->keyval != GDK_KEY_Tab &&
1486 (event->keyval < GDK_KEY_Shift_L || event->keyval > GDK_KEY_Hyper_R)) {
1487 get_buffer()->erase(CompletionDialog::get()->start_mark->get_iter(), get_buffer()->get_insert()->get_iter());
1488 CompletionDialog::get()->hide();
1489 }
1490 return false;
1491 },
1492 false);
1493 }
1494
1495 autocomplete->is_restart_key = [this](guint keyval) {
1496 auto iter = get_buffer()->get_insert()->get_iter();
1497 iter.backward_chars(2);
1498 if(keyval == '.' || (keyval == ':' && *iter == ':'))
1499 return true;
1500 return false;
1501 };
1502
1503 std::function<bool(Gtk::TextIter)> is_possible_xml_attribute = [](Gtk::TextIter) { return false; };
1504 if(is_js) {
1505 autocomplete->is_restart_key = [](guint keyval) {
1506 if(keyval == '.' || keyval == ' ')
1507 return true;
1508 return false;
1509 };
1510
1511 is_possible_xml_attribute = [this](Gtk::TextIter iter) {
1512 return (*iter == ' ' || iter.ends_line() || *iter == '/' || (*iter == '>' && iter.backward_char())) && find_open_symbol_backward(iter, iter, '<', '>');
1513 };
1514 }
1515
1516 autocomplete->run_check = [this, is_possible_xml_attribute]() {
1517 auto prefix_start = get_buffer()->get_insert()->get_iter();
1518 auto prefix_end = prefix_start;
1519
1520 auto prev = prefix_start;
1521 prev.backward_char();
1522 if(!is_code_iter(prev))
1523 return false;
1524
1525 size_t count = 0;
1526 while(prefix_start.backward_char() && is_token_char(*prefix_start))
1527 ++count;
1528
1529 autocomplete_enable_snippets = false;
1530 autocomplete_show_arguments = false;
1531
1532 if(prefix_start != prefix_end && !is_token_char(*prefix_start))
1533 prefix_start.forward_char();
1534
1535 prev = prefix_start;
1536 prev.backward_char();
1537 auto prevprev = prev;
1538 if(*prev == '.') {
1539 auto iter = prev;
1540 bool starts_with_num = false;
1541 size_t count = 0;
1542 while(iter.backward_char() && is_token_char(*iter)) {
1543 ++count;
1544 starts_with_num = Glib::Unicode::isdigit(*iter);
1545 }
1546 if((count >= 1 || *iter == ')' || *iter == ']' || *iter == '"' || *iter == '\'' || *iter == '?') && !starts_with_num) {
1547 {
1548 LockGuard lock(autocomplete->prefix_mutex);
1549 autocomplete->prefix = get_buffer()->get_text(prefix_start, prefix_end);
1550 }
1551 return true;
1552 }
1553 }
1554 else if((prevprev.backward_char() && *prevprev == ':' && *prev == ':')) {
1555 {
1556 LockGuard lock(autocomplete->prefix_mutex);
1557 autocomplete->prefix = get_buffer()->get_text(prefix_start, prefix_end);
1558 }
1559 return true;
1560 }
1561 else if(count >= 3) { // part of symbol
1562 {
1563 LockGuard lock(autocomplete->prefix_mutex);
1564 autocomplete->prefix = get_buffer()->get_text(prefix_start, prefix_end);
1565 }
1566 autocomplete_enable_snippets = true;
1567 return true;
1568 }
1569 if(is_possible_argument()) {
1570 autocomplete_show_arguments = true;
1571 LockGuard lock(autocomplete->prefix_mutex);
1572 autocomplete->prefix = "";
1573 return true;
1574 }
1575 if(is_possible_xml_attribute(prefix_start)) {
1576 LockGuard lock(autocomplete->prefix_mutex);
1577 autocomplete->prefix = "";
1578 return true;
1579 }
1580 if(!interactive_completion) {
1581 {
1582 LockGuard lock(autocomplete->prefix_mutex);
1583 autocomplete->prefix = get_buffer()->get_text(prefix_start, prefix_end);
1584 }
1585 auto prevprev = prev;
1586 autocomplete_enable_snippets = !(*prev == '.' || (prevprev.backward_char() && *prevprev == ':' && *prev == ':'));
1587 return true;
1588 }
1589
1590 return false;
1591 };
1592
1593 autocomplete->before_add_rows = [this] {
1594 status_state = "autocomplete...";
1595 if(update_status_state)
1596 update_status_state(this);
1597 };
1598
1599 autocomplete->after_add_rows = [this] {
1600 status_state = "";
1601 if(update_status_state)
1602 update_status_state(this);
1603 };
1604
1605 autocomplete->add_rows = [this](std::string &buffer, int line, int line_index) {
1606 if(autocomplete->state == Autocomplete::State::starting) {
1607 autocomplete_rows.clear();
1608 std::promise<void> result_processed;
1609 if(autocomplete_show_arguments) {
1610 if(!capabilities.signature_help)
1611 return true;
1612 dispatcher.post([this, line, line_index, &result_processed] {
1613 // Find current parameter number and previously used named parameters
1614 unsigned current_parameter_position = 0;
1615 auto named_parameter_symbol = get_named_parameter_symbol();
1616 std::set<std::string> used_named_parameters;
1617 auto iter = get_buffer()->get_insert()->get_iter();
1618 int para_count = 0;
1619 int square_count = 0;
1620 int angle_count = 0;
1621 int curly_count = 0;
1622 while(iter.backward_char() && backward_to_code(iter)) {
1623 if(para_count == 0 && square_count == 0 && angle_count == 0 && curly_count == 0) {
1624 if(named_parameter_symbol && (*iter == ',' || *iter == '(')) {
1625 auto next = iter;
1626 if(next.forward_char() && forward_to_code(next)) {
1627 auto pair = get_token_iters(next);
1628 if(pair.first != pair.second) {
1629 auto symbol = pair.second;
1630 if(forward_to_code(symbol) && *symbol == static_cast<unsigned char>(*named_parameter_symbol))
1631 used_named_parameters.emplace(get_buffer()->get_text(pair.first, pair.second));
1632 }
1633 }
1634 }
1635 if(*iter == ',')
1636 ++current_parameter_position;
1637 else if(*iter == '(')
1638 break;
1639 }
1640 if(*iter == '(')
1641 ++para_count;
1642 else if(*iter == ')')
1643 --para_count;
1644 else if(*iter == '[')
1645 ++square_count;
1646 else if(*iter == ']')
1647 --square_count;
1648 else if(*iter == '<')
1649 ++angle_count;
1650 else if(*iter == '>')
1651 --angle_count;
1652 else if(*iter == '{')
1653 ++curly_count;
1654 else if(*iter == '}')
1655 --curly_count;
1656 }
1657 bool using_named_parameters = named_parameter_symbol && !(current_parameter_position > 0 && used_named_parameters.empty());
1658
1659 write_request("textDocument/signatureHelp", to_string({make_position(line, get_line_pos(line, line_index))}), [this, &result_processed, current_parameter_position, using_named_parameters, used_named_parameters = std::move(used_named_parameters)](JSON &&result, bool error) {
1660 if(!error) {
1661 for(auto &signature : result.array_or_empty("signatures")) {
1662 unsigned parameter_position = 0;
1663 for(auto ¶meter : signature.array_or_empty("parameters")) {
1664 if(parameter_position == current_parameter_position || using_named_parameters) {
1665 auto label = parameter.string_or("label", "");
1666 auto insert = label;
1667 if(!using_named_parameters || used_named_parameters.find(insert) == used_named_parameters.end()) {
1668 autocomplete->rows.emplace_back(std::move(label));
1669 autocomplete_rows.emplace_back(AutocompleteRow{std::move(insert), {}, LanguageProtocol::Documentation(parameter.child_optional("documentation")), {}, {}});
1670 }
1671 }
1672 parameter_position++;
1673 }
1674 }
1675 if(autocomplete_rows.empty()) {
1676 dispatcher.post([this] {
1677 // Move cursor forward if no arguments in completed function and if cursor is still inside ()
1678 if(autocompete_possibly_no_arguments) {
1679 auto iter = get_buffer()->get_insert()->get_iter();
1680 auto prev = iter;
1681 if(prev.backward_char() && *prev == '(' && *iter == ')' && iter.forward_char())
1682 get_buffer()->place_cursor(iter);
1683 }
1684 });
1685 }
1686 }
1687 result_processed.set_value();
1688 });
1689 });
1690 }
1691 else {
1692 dispatcher.post([this, line, line_index, &result_processed] {
1693 write_request("textDocument/completion", to_string({make_position(line, get_line_pos(line, line_index))}), [this, &result_processed](JSON &&result, bool error) {
1694 if(!error) {
1695 bool is_incomplete = result.boolean_or("isIncomplete", false);
1696 auto items = result.array_or_empty();
1697 if(items.empty())
1698 items = result.array_or_empty("items");
1699 std::string prefix;
1700 {
1701 LockGuard lock(autocomplete->prefix_mutex);
1702 prefix = autocomplete->prefix;
1703 }
1704 for(auto &item : items) {
1705 auto label = item.string_or("label", "");
1706 if(starts_with(label, prefix)) {
1707 auto detail = item.string_or("detail", "");
1708 LanguageProtocol::Documentation documentation(item.child_optional("documentation"));
1709
1710 std::vector<LanguageProtocol::TextEdit> additional_text_edits;
1711 try {
1712 for(auto &text_edit : item.array_or_empty("additionalTextEdits"))
1713 additional_text_edits.emplace_back(text_edit);
1714 }
1715 catch(...) {
1716 additional_text_edits.clear();
1717 }
1718
1719 auto insert = item.string_or("insertText", "");
1720 if(insert.empty()) {
1721 if(auto text_edit = item.object_optional("textEdit"))
1722 insert = text_edit->string_or("newText", "");
1723 }
1724 if(insert.empty())
1725 insert = label;
1726 if(!insert.empty()) {
1727 auto kind = item.integer_or("kind", 0);
1728 if(kind >= 2 && kind <= 4 && insert.find('(') == std::string::npos) // If kind is method, function or constructor, but parentheses are missing
1729 insert += "(${1:})";
1730
1731 std::shared_ptr<JSON> item_object;
1732 if(detail.empty() && documentation.value.empty() && (is_incomplete || is_js || language_id == "python")) // Workaround for typescript-language-server (is_js) and python-lsp-server
1733 item_object = std::make_shared<JSON>(JSON::make_owner(std::move(item)));
1734
1735 autocomplete->rows.emplace_back(std::move(label));
1736 autocomplete_rows.emplace_back(AutocompleteRow{std::move(insert), std::move(detail), std::move(documentation), std::move(item_object), std::move(additional_text_edits)});
1737 }
1738 }
1739 }
1740
1741 if(autocomplete_enable_snippets) {
1742 LockGuard lock(snippets_mutex);
1743 if(snippets) {
1744 for(auto &snippet : *snippets) {
1745 if(starts_with(snippet.prefix, prefix)) {
1746 autocomplete->rows.emplace_back(snippet.prefix);
1747 autocomplete_rows.emplace_back(AutocompleteRow{snippet.body, {}, LanguageProtocol::Documentation(snippet.description), {}, {}});
1748 }
1749 }
1750 }
1751 }
1752 }
1753 result_processed.set_value();
1754 });
1755 });
1756 }
1757 result_processed.get_future().get();
1758 }
1759 return true;
1760 };
1761
1762 autocomplete->on_show = [this] {
1763 hide_tooltips();
1764 };
1765
1766 autocomplete->on_hide = [this] {
1767 autocomplete_rows.clear();
1768 ++set_tooltip_count;
1769 };
1770
1771 autocomplete->on_select = [this](unsigned int index, const std::string &text, bool hide_window) {
1772 auto insert = hide_window ? autocomplete_rows[index].insert : text;
1773
1774 get_buffer()->erase(CompletionDialog::get()->start_mark->get_iter(), get_buffer()->get_insert()->get_iter());
1775
1776 // Do not insert function/template parameters if they already exist
1777 {
1778 auto iter = get_buffer()->get_insert()->get_iter();
1779 if(*iter == '(' || *iter == '<') {
1780 auto bracket_pos = insert.find(*iter);
1781 if(bracket_pos != std::string::npos)
1782 insert.erase(bracket_pos);
1783 }
1784 }
1785
1786 // Do not instert ?. after ., instead replace . with ?.
1787 if(1 < insert.size() && insert[0] == '?' && insert[1] == '.') {
1788 auto iter = get_buffer()->get_insert()->get_iter();
1789 auto prev = iter;
1790 if(prev.backward_char() && *prev == '.') {
1791 get_buffer()->erase(prev, iter);
1792 }
1793 }
1794
1795 if(hide_window) {
1796 if(autocomplete_show_arguments) {
1797 if(auto symbol = get_named_parameter_symbol()) // Do not select named parameters in for instance Python
1798 get_buffer()->insert(CompletionDialog::get()->start_mark->get_iter(), insert + *symbol);
1799 else {
1800 get_buffer()->insert(CompletionDialog::get()->start_mark->get_iter(), insert);
1801 int start_offset = CompletionDialog::get()->start_mark->get_iter().get_offset();
1802 int end_offset = CompletionDialog::get()->start_mark->get_iter().get_offset() + insert.size();
1803 get_buffer()->select_range(get_buffer()->get_iter_at_offset(start_offset), get_buffer()->get_iter_at_offset(end_offset));
1804 }
1805 return;
1806 }
1807
1808 auto additional_text_edits = std::move(autocomplete_rows[index].additional_text_edits); // autocomplete_rows will be cleared after insert_snippet (see autocomplete->on_hide)
1809
1810 get_buffer()->begin_user_action();
1811 insert_snippet(CompletionDialog::get()->start_mark->get_iter(), insert);
1812 for(auto it = additional_text_edits.rbegin(); it != additional_text_edits.rend(); ++it) {
1813 auto start = get_iter_at_line_pos(it->range.start.line, it->range.start.character);
1814 auto end = get_iter_at_line_pos(it->range.end.line, it->range.end.character);
1815 get_buffer()->erase(start, end);
1816 start = get_iter_at_line_pos(it->range.start.line, it->range.start.character);
1817 get_buffer()->insert(start, it->new_text);
1818 }
1819 get_buffer()->end_user_action();
1820
1821 auto iter = get_buffer()->get_insert()->get_iter();
1822 if(*iter == ')' && iter.backward_char() && *iter == '(') { // If no arguments, try signatureHelp
1823 last_keyval = '(';
1824 if(is_js) // Workaround for typescript-language-server
1825 autocompete_possibly_no_arguments = true;
1826 autocomplete->run();
1827 }
1828 }
1829 else
1830 get_buffer()->insert(CompletionDialog::get()->start_mark->get_iter(), insert);
1831 };
1832
1833 autocomplete->set_tooltip_buffer = [this](unsigned int index) -> std::function<void(Tooltip & tooltip)> {
1834 size_t last_count = ++set_tooltip_count;
1835 auto &autocomplete_row = autocomplete_rows[index];
1836
1837 const static auto insert_documentation = [](Source::LanguageProtocolView *view, Tooltip &tooltip, const std::string &detail, const LanguageProtocol::Documentation &documentation) {
1838 if(view->language_id == "python") // Python might support markdown in the future
1839 tooltip.insert_docstring(documentation.value);
1840 else {
1841 if(!detail.empty()) {
1842 tooltip.insert_code(detail, view->language);
1843 tooltip.remove_trailing_newlines();
1844 }
1845 if(!documentation.value.empty()) {
1846 if(tooltip.buffer->size() > 0)
1847 tooltip.buffer->insert_at_cursor("\n\n");
1848 if(documentation.kind == "plaintext" || documentation.kind.empty())
1849 tooltip.insert_with_links_tagged(documentation.value);
1850 else if(documentation.kind == "markdown")
1851 tooltip.insert_markdown(documentation.value);
1852 else
1853 tooltip.insert_code(documentation.value, documentation.kind);
1854 }
1855 }
1856 };
1857
1858 if(capabilities.completion_resolve && autocomplete_row.detail.empty() && autocomplete_row.documentation.value.empty() && autocomplete_row.item_object) {
1859 std::stringstream ss;
1860 bool first = true;
1861 for(auto &child : autocomplete_row.item_object->children_or_empty()) {
1862 ss << (!first ? ",\"" : "\"") << JSON::escape_string(child.first) << "\":" << child.second;
1863 first = false;
1864 }
1865 write_request("completionItem/resolve", ss.str(), [this, last_count](JSON &&result, bool error) {
1866 if(!error) {
1867 if(last_count != set_tooltip_count)
1868 return;
1869 auto detail = result.string_or("detail", "");
1870 auto documentation = LanguageProtocol::Documentation(result.child_optional("documentation"));
1871 if(detail.empty() && documentation.value.empty())
1872 return;
1873 dispatcher.post([this, last_count, detail = std::move(detail), documentation = std::move(documentation)] {
1874 if(last_count != set_tooltip_count)
1875 return;
1876 autocomplete->tooltips.clear();
1877 auto iter = CompletionDialog::get()->start_mark->get_iter();
1878 autocomplete->tooltips.emplace_back(this, iter, iter, [this, detail = std::move(detail), documentation = std::move(documentation)](Tooltip &tooltip) {
1879 insert_documentation(this, tooltip, detail, documentation);
1880 });
1881 autocomplete->tooltips.show(true);
1882 });
1883 }
1884 });
1885 return nullptr;
1886 }
1887 if(autocomplete_row.detail.empty() && autocomplete_row.documentation.value.empty())
1888 return nullptr;
1889
1890 return [this, &autocomplete_row](Tooltip &tooltip) mutable {
1891 insert_documentation(this, tooltip, autocomplete_row.detail, autocomplete_row.documentation);
1892 };
1893 };
1894 }
1895
update_diagnostics_async(std::vector<LanguageProtocol::Diagnostic> && diagnostics)1896 void Source::LanguageProtocolView::update_diagnostics_async(std::vector<LanguageProtocol::Diagnostic> &&diagnostics) {
1897 size_t last_count = ++update_diagnostics_async_count;
1898 if(capabilities.code_action && !diagnostics.empty()) {
1899 dispatcher.post([this, diagnostics = std::move(diagnostics), last_count]() mutable {
1900 if(last_count != update_diagnostics_async_count)
1901 return;
1902 std::stringstream ss;
1903 bool first = true;
1904 for(auto &diagnostic : diagnostics) {
1905 if(!first)
1906 ss << ',';
1907 ss << *diagnostic.object;
1908 first = false;
1909 }
1910 std::pair<std::string, std::string> range;
1911 if(diagnostics.size() == 1) // Use diagnostic range if only one diagnostic, otherwise use whole buffer
1912 range = make_range({diagnostics[0].range.start.line, diagnostics[0].range.start.character}, {diagnostics[0].range.end.line, diagnostics[0].range.end.character});
1913 else {
1914 auto start = get_buffer()->begin();
1915 auto end = get_buffer()->end();
1916 range = make_range({start.get_line(), get_line_pos(start)}, {end.get_line(), get_line_pos(end)});
1917 }
1918 std::vector<std::pair<std::string, std::string>> params = {range, {"context", '{' + to_string({{"diagnostics", '[' + ss.str() + ']'}, {"only", "[\"quickfix\"]"}}) + '}'}};
1919 thread_pool.push([this, diagnostics = std::move(diagnostics), params = std::move(params), last_count]() mutable {
1920 if(last_count != update_diagnostics_async_count)
1921 return;
1922 std::promise<void> result_processed;
1923 write_request("textDocument/codeAction", to_string(params), [this, &result_processed, &diagnostics, last_count](JSON &&result, bool error) {
1924 if(!error && last_count == update_diagnostics_async_count) {
1925 try {
1926 for(auto &code_action : result.array_or_empty()) {
1927 auto kind = code_action.string_or("kind", "");
1928 if(kind == "quickfix" || kind.empty()) { // Workaround for typescript-language-server (kind.empty())
1929 auto title = code_action.string("title");
1930 std::vector<LanguageProtocol::Diagnostic> quickfix_diagnostics;
1931 for(auto &diagnostic : code_action.array_or_empty("diagnostics"))
1932 quickfix_diagnostics.emplace_back(std::move(diagnostic));
1933 auto edit = code_action.object_optional("edit");
1934 if(!edit) {
1935 if(auto arguments = code_action.array_optional("arguments")) {
1936 if(!arguments->empty())
1937 edit = std::move((*arguments)[0]);
1938 }
1939 }
1940 if(edit) {
1941 LanguageProtocol::WorkspaceEdit workspace_edit(*edit, file_path);
1942 for(auto &document_edit : workspace_edit.document_edits) {
1943 for(auto &text_edit : document_edit.text_edits) {
1944 if(!quickfix_diagnostics.empty()) {
1945 for(auto &diagnostic : diagnostics) {
1946 for(auto &quickfix_diagnostic : quickfix_diagnostics) {
1947 if(diagnostic.message == quickfix_diagnostic.message && diagnostic.range == quickfix_diagnostic.range) {
1948 auto pair = diagnostic.quickfixes.emplace(title, std::set<Source::FixIt>{});
1949 pair.first->second.emplace(
1950 text_edit.new_text,
1951 document_edit.file,
1952 std::make_pair<Offset, Offset>(Offset(text_edit.range.start.line, text_edit.range.start.character),
1953 Offset(text_edit.range.end.line, text_edit.range.end.character)));
1954 break;
1955 }
1956 }
1957 }
1958 }
1959 else { // Workaround for language server that does not report quickfix diagnostics
1960 for(auto &diagnostic : diagnostics) {
1961 if(text_edit.range.start.line == diagnostic.range.start.line) {
1962 auto pair = diagnostic.quickfixes.emplace(title, std::set<Source::FixIt>{});
1963 pair.first->second.emplace(
1964 text_edit.new_text,
1965 document_edit.file,
1966 std::make_pair<Offset, Offset>(Offset(text_edit.range.start.line, text_edit.range.start.character),
1967 Offset(text_edit.range.end.line, text_edit.range.end.character)));
1968 break;
1969 }
1970 }
1971 }
1972 }
1973 }
1974 }
1975 }
1976 }
1977 }
1978 catch(...) {
1979 }
1980 }
1981 result_processed.set_value();
1982 });
1983 result_processed.get_future().get();
1984 dispatcher.post([this, diagnostics = std::move(diagnostics), last_count]() mutable {
1985 if(last_count == update_diagnostics_async_count) {
1986 if(capabilities.type_coverage)
1987 last_diagnostics = diagnostics;
1988 update_diagnostics(std::move(diagnostics));
1989 }
1990 });
1991 });
1992 });
1993 }
1994 else {
1995 dispatcher.post([this, diagnostics = std::move(diagnostics), last_count]() mutable {
1996 if(last_count == update_diagnostics_async_count) {
1997 if(capabilities.type_coverage)
1998 last_diagnostics = diagnostics;
1999 update_diagnostics(std::move(diagnostics));
2000 }
2001 });
2002 }
2003 }
2004
update_diagnostics(std::vector<LanguageProtocol::Diagnostic> diagnostics)2005 void Source::LanguageProtocolView::update_diagnostics(std::vector<LanguageProtocol::Diagnostic> diagnostics) {
2006 diagnostic_offsets.clear();
2007 diagnostic_tooltips.clear();
2008 fix_its.clear();
2009 get_buffer()->remove_tag_by_name("def:warning_underline", get_buffer()->begin(), get_buffer()->end());
2010 get_buffer()->remove_tag_by_name("def:error_underline", get_buffer()->begin(), get_buffer()->end());
2011 num_warnings = 0;
2012 num_errors = 0;
2013 num_fix_its = 0;
2014
2015 for(auto &diagnostic : diagnostics) {
2016 auto start = get_iter_at_line_pos(diagnostic.range.start.line, diagnostic.range.start.character);
2017 auto end = get_iter_at_line_pos(diagnostic.range.end.line, diagnostic.range.end.character);
2018
2019 if(start == end) {
2020 if(!end.ends_line())
2021 end.forward_char();
2022 else
2023 while(start.ends_line() && start.backward_char()) { // Move start so that diagnostic underline is visible
2024 }
2025 }
2026
2027 bool error = false;
2028 if(diagnostic.severity >= 2)
2029 num_warnings++;
2030 else {
2031 num_errors++;
2032 error = true;
2033 }
2034 num_fix_its += diagnostic.quickfixes.size();
2035
2036 for(auto &quickfix : diagnostic.quickfixes)
2037 fix_its.insert(fix_its.end(), quickfix.second.begin(), quickfix.second.end());
2038
2039 add_diagnostic_tooltip(start, end, error, [this, diagnostic = std::move(diagnostic)](Tooltip &tooltip) {
2040 if(language_id == "python") { // Python might support markdown in the future
2041 tooltip.insert_with_links_tagged(diagnostic.message);
2042 return;
2043 }
2044 tooltip.insert_markdown(diagnostic.message);
2045
2046 if(!diagnostic.related_informations.empty()) {
2047 auto link_tag = tooltip.buffer->get_tag_table()->lookup("link");
2048 for(size_t i = 0; i < diagnostic.related_informations.size(); ++i) {
2049 auto link = filesystem::get_relative_path(diagnostic.related_informations[i].location.file, file_path.parent_path()).string();
2050 link += ':' + std::to_string(diagnostic.related_informations[i].location.range.start.line + 1);
2051 link += ':' + std::to_string(diagnostic.related_informations[i].location.range.start.character + 1);
2052
2053 if(i == 0)
2054 tooltip.buffer->insert_at_cursor("\n\n");
2055 else
2056 tooltip.buffer->insert_at_cursor("\n");
2057 tooltip.insert_markdown(diagnostic.related_informations[i].message);
2058 tooltip.buffer->insert_at_cursor(": ");
2059 tooltip.buffer->insert_with_tag(tooltip.buffer->get_insert()->get_iter(), link, link_tag);
2060 }
2061 }
2062
2063 if(!diagnostic.quickfixes.empty()) {
2064 if(diagnostic.quickfixes.size() == 1)
2065 tooltip.buffer->insert_at_cursor("\n\nFix-it:");
2066 else
2067 tooltip.buffer->insert_at_cursor("\n\nFix-its:");
2068 for(auto &quickfix : diagnostic.quickfixes) {
2069 tooltip.buffer->insert_at_cursor("\n");
2070 tooltip.insert_markdown(quickfix.first);
2071 }
2072 }
2073 });
2074 }
2075
2076 for(auto &mark : type_coverage_marks) {
2077 add_diagnostic_tooltip(mark.first->get_iter(), mark.second->get_iter(), false, [](Tooltip &tooltip) {
2078 tooltip.buffer->insert_at_cursor(type_coverage_message);
2079 });
2080 num_warnings++;
2081 }
2082
2083 status_diagnostics = std::make_tuple(num_warnings, num_errors, num_fix_its);
2084 if(update_status_diagnostics)
2085 update_status_diagnostics(this);
2086 }
2087
show_type_tooltips(const Gdk::Rectangle & rectangle)2088 void Source::LanguageProtocolView::show_type_tooltips(const Gdk::Rectangle &rectangle) {
2089 if(!capabilities.hover)
2090 return;
2091
2092 Gtk::TextIter iter;
2093 int location_x, location_y;
2094 window_to_buffer_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, rectangle.get_x(), rectangle.get_y(), location_x, location_y);
2095 location_x += (rectangle.get_width() - 1) / 2;
2096 get_iter_at_location(iter, location_x, location_y);
2097 Gdk::Rectangle iter_rectangle;
2098 get_iter_location(iter, iter_rectangle);
2099 if(iter.ends_line() && location_x > iter_rectangle.get_x())
2100 return;
2101
2102 auto offset = iter.get_offset();
2103
2104 static int request_count = 0;
2105 request_count++;
2106 auto current_request = request_count;
2107 write_request("textDocument/hover", to_string({make_position(iter.get_line(), get_line_pos(iter))}), [this, offset, current_request](JSON &&result, bool error) {
2108 if(!error) {
2109 // hover result structure vary significantly from the different language servers
2110 struct Content {
2111 std::string value;
2112 std::string kind;
2113 };
2114 std::list<Content> contents;
2115 auto contents_pt = result.child_optional("contents");
2116 if(!contents_pt)
2117 return;
2118 if(auto string = contents_pt->string_optional())
2119 contents.emplace_back(Content{std::move(*string), "markdown"});
2120 else if(auto object = contents_pt->object_optional()) {
2121 auto value = object->string_or("value", "");
2122 if(!value.empty()) {
2123 auto kind = object->string_or("kind", "");
2124 if(kind.empty())
2125 kind = object->string_or("language", "");
2126 contents.emplace_front(Content{std::move(value), std::move(kind)});
2127 }
2128 }
2129 else if(auto array = contents_pt->array_optional()) {
2130 bool first_value_in_object = true;
2131 for(auto &object_or_string : *array) {
2132 if(auto object = object_or_string.object_optional()) {
2133 auto value = object_or_string.string_or("value", "");
2134 if(!value.empty()) {
2135 auto kind = object_or_string.string_or("kind", "");
2136 if(kind.empty())
2137 kind = object_or_string.string_or("language", "");
2138 if(first_value_in_object) // Place first value, which most likely is type information, to front (workaround for flow-bin's language server)
2139 contents.emplace_front(Content{std::move(value), std::move(kind)});
2140 else
2141 contents.emplace_back(Content{std::move(value), std::move(kind)});
2142 first_value_in_object = false;
2143 }
2144 }
2145 else if(auto string = object_or_string.string_optional()) {
2146 if(!string->empty())
2147 contents.emplace_back(Content{std::move(*string), "markdown"});
2148 }
2149 }
2150 }
2151 if(!contents.empty()) {
2152 dispatcher.post([this, offset, contents = std::move(contents), current_request]() mutable {
2153 if(current_request != request_count)
2154 return;
2155 if(Notebook::get().get_current_view() != this)
2156 return;
2157 if(offset >= get_buffer()->get_char_count())
2158 return;
2159 type_tooltips.clear();
2160
2161 auto token_iters = get_token_iters(get_buffer()->get_iter_at_offset(offset));
2162 type_tooltips.emplace_back(this, token_iters.first, token_iters.second, [this, offset, contents = std::move(contents)](Tooltip &tooltip) mutable {
2163 bool first = true;
2164 if(language_id == "python") { // Python might support markdown in the future
2165 for(auto &content : contents) {
2166 if(!first)
2167 tooltip.buffer->insert_at_cursor("\n\n");
2168 first = false;
2169 if(content.kind == "python")
2170 tooltip.insert_code(content.value, content.kind);
2171 else
2172 tooltip.insert_docstring(content.value);
2173 }
2174 }
2175 else {
2176 for(auto &content : contents) {
2177 if(!first)
2178 tooltip.buffer->insert_at_cursor("\n\n");
2179 first = false;
2180 if(content.kind == "plaintext" || content.kind.empty())
2181 tooltip.insert_with_links_tagged(content.value);
2182 else if(content.kind == "markdown")
2183 tooltip.insert_markdown(content.value);
2184 else
2185 tooltip.insert_code(content.value, content.kind);
2186 tooltip.remove_trailing_newlines();
2187 }
2188 }
2189
2190 #ifdef JUCI_ENABLE_DEBUG
2191 if(language_id == "rust" && capabilities.definition) {
2192 if(Debug::LLDB::get().is_stopped()) {
2193 Glib::ustring value_type = "Value";
2194
2195 auto token_iters = get_token_iters(get_buffer()->get_iter_at_offset(offset));
2196 auto offset = get_declaration(token_iters.first);
2197
2198 auto variable = get_buffer()->get_text(token_iters.first, token_iters.second);
2199 Glib::ustring debug_value = Debug::LLDB::get().get_value(variable, offset.file_path, offset.line + 1, offset.index + 1);
2200 if(debug_value.empty()) {
2201 debug_value = Debug::LLDB::get().get_return_value(file_path, token_iters.first.get_line() + 1, token_iters.first.get_line_index() + 1);
2202 if(!debug_value.empty())
2203 value_type = "Return value";
2204 }
2205 if(debug_value.empty()) {
2206 auto end = token_iters.second;
2207 while((end.ends_line() || *end == ' ' || *end == '\t') && end.forward_char()) {
2208 }
2209 if(*end != '(') {
2210 auto iter = token_iters.first;
2211 auto start = iter;
2212 while(iter.backward_char()) {
2213 if(*iter == '.') {
2214 while(iter.backward_char() && (*iter == ' ' || *iter == '\t' || iter.ends_line())) {
2215 }
2216 }
2217 if(!is_token_char(*iter))
2218 break;
2219 start = iter;
2220 }
2221 if(is_token_char(*start))
2222 debug_value = Debug::LLDB::get().get_value(get_buffer()->get_text(start, token_iters.second));
2223 }
2224 }
2225 if(!debug_value.empty()) {
2226 size_t pos = debug_value.find(" = ");
2227 if(pos != Glib::ustring::npos) {
2228 Glib::ustring::iterator iter;
2229 while(!debug_value.validate(iter)) {
2230 auto next_char_iter = iter;
2231 next_char_iter++;
2232 debug_value.replace(iter, next_char_iter, "?");
2233 }
2234 tooltip.buffer->insert_at_cursor("\n\n" + value_type + ":\n");
2235 tooltip.insert_code(debug_value.substr(pos + 3, debug_value.size() - (pos + 3) - 1));
2236 }
2237 }
2238 }
2239 }
2240 #endif
2241 });
2242 type_tooltips.show();
2243 });
2244 }
2245 }
2246 });
2247 }
2248
apply_similar_symbol_tag()2249 void Source::LanguageProtocolView::apply_similar_symbol_tag() {
2250 if(!capabilities.document_highlight)
2251 return;
2252
2253 auto iter = get_buffer()->get_insert()->get_iter();
2254 static int request_count = 0;
2255 request_count++;
2256 auto current_request = request_count;
2257 write_request("textDocument/documentHighlight", to_string({make_position(iter.get_line(), get_line_pos(iter)), {"context", "{\"includeDeclaration\":true}"}}), [this, current_request](JSON &&result, bool error) {
2258 if(!error) {
2259 std::vector<LanguageProtocol::Range> ranges;
2260 for(auto &location : result.array_or_empty()) {
2261 try {
2262 ranges.emplace_back(location.object("range"));
2263 }
2264 catch(...) {
2265 }
2266 }
2267 dispatcher.post([this, ranges = std::move(ranges), current_request] {
2268 if(current_request != request_count || !similar_symbol_tag_applied)
2269 return;
2270 get_buffer()->remove_tag(similar_symbol_tag, get_buffer()->begin(), get_buffer()->end());
2271 for(auto &range : ranges) {
2272 auto start = get_iter_at_line_pos(range.start.line, range.start.character);
2273 auto end = get_iter_at_line_pos(range.end.line, range.end.character);
2274 get_buffer()->apply_tag(similar_symbol_tag, start, end);
2275 }
2276 });
2277 }
2278 });
2279 }
2280
apply_clickable_tag(const Gtk::TextIter & iter)2281 void Source::LanguageProtocolView::apply_clickable_tag(const Gtk::TextIter &iter) {
2282 static int request_count = 0;
2283 request_count++;
2284 auto current_request = request_count;
2285 write_request("textDocument/definition", to_string({make_position(iter.get_line(), get_line_pos(iter))}), [this, current_request, line = iter.get_line(), line_offset = iter.get_line_offset()](JSON &&result, bool error) {
2286 if(!error) {
2287 if(result.array_optional() || result.object_optional()) {
2288 dispatcher.post([this, current_request, line, line_offset] {
2289 if(current_request != request_count || !clickable_tag_applied)
2290 return;
2291 get_buffer()->remove_tag(clickable_tag, get_buffer()->begin(), get_buffer()->end());
2292 auto range = get_token_iters(get_iter_at_line_offset(line, line_offset));
2293 get_buffer()->apply_tag(clickable_tag, range.first, range.second);
2294 });
2295 }
2296 }
2297 });
2298 }
2299
get_declaration(const Gtk::TextIter & iter)2300 Source::Offset Source::LanguageProtocolView::get_declaration(const Gtk::TextIter &iter) {
2301 auto offset = std::make_shared<Offset>();
2302 std::promise<void> result_processed;
2303 write_request("textDocument/definition", to_string({make_position(iter.get_line(), get_line_pos(iter))}), [offset, &result_processed](JSON &&result, bool error) {
2304 if(!error) {
2305 auto locations = result.array_or_empty();
2306 if(locations.empty()) {
2307 if(auto object = result.object_optional())
2308 locations.emplace_back(std::move(*object));
2309 }
2310 for(auto &location_object : locations) {
2311 try {
2312 LanguageProtocol::Location location(location_object);
2313 offset->file_path = std::move(location.file);
2314 offset->line = location.range.start.line;
2315 offset->index = location.range.start.character;
2316 break; // TODO: can a language server return several definitions?
2317 }
2318 catch(...) {
2319 }
2320 }
2321 }
2322 result_processed.set_value();
2323 });
2324 result_processed.get_future().get();
2325 return *offset;
2326 }
2327
get_type_declaration(const Gtk::TextIter & iter)2328 Source::Offset Source::LanguageProtocolView::get_type_declaration(const Gtk::TextIter &iter) {
2329 auto offset = std::make_shared<Offset>();
2330 std::promise<void> result_processed;
2331 write_request("textDocument/typeDefinition", to_string({make_position(iter.get_line(), get_line_pos(iter))}), [offset, &result_processed](JSON &&result, bool error) {
2332 if(!error) {
2333 auto locations = result.array_or_empty();
2334 if(locations.empty()) {
2335 if(auto object = result.object_optional())
2336 locations.emplace_back(std::move(*object));
2337 }
2338 for(auto &location_object : locations) {
2339 try {
2340 LanguageProtocol::Location location(location_object);
2341 offset->file_path = std::move(location.file);
2342 offset->line = location.range.start.line;
2343 offset->index = location.range.start.character;
2344 break; // TODO: can a language server return several type definitions?
2345 }
2346 catch(...) {
2347 }
2348 }
2349 }
2350 result_processed.set_value();
2351 });
2352 result_processed.get_future().get();
2353 return *offset;
2354 }
2355
get_implementations(const Gtk::TextIter & iter)2356 std::vector<Source::Offset> Source::LanguageProtocolView::get_implementations(const Gtk::TextIter &iter) {
2357 auto offsets = std::make_shared<std::vector<Offset>>();
2358 std::promise<void> result_processed;
2359 write_request("textDocument/implementation", to_string({make_position(iter.get_line(), get_line_pos(iter))}), [offsets, &result_processed](JSON &&result, bool error) {
2360 if(!error) {
2361 auto locations = result.array_or_empty();
2362 if(locations.empty()) {
2363 if(auto object = result.object_optional())
2364 locations.emplace_back(std::move(*object));
2365 }
2366 for(auto &location_object : locations) {
2367 try {
2368 LanguageProtocol::Location location(location_object);
2369 offsets->emplace_back(location.range.start.line, location.range.start.character, location.file);
2370 }
2371 catch(...) {
2372 }
2373 }
2374 }
2375 result_processed.set_value();
2376 });
2377 result_processed.get_future().get();
2378 return *offsets;
2379 }
2380
get_named_parameter_symbol()2381 boost::optional<char> Source::LanguageProtocolView::get_named_parameter_symbol() {
2382 if(language_id == "python") // TODO: add more languages that supports named parameters
2383 return '=';
2384 return {};
2385 }
2386
update_type_coverage()2387 void Source::LanguageProtocolView::update_type_coverage() {
2388 if(capabilities.type_coverage) {
2389 write_request("textDocument/typeCoverage", {}, [this](JSON &&result, bool error) {
2390 if(error) {
2391 if(update_type_coverage_retries > 0) { // Retry typeCoverage request, since these requests can fail while waiting for language server to start
2392 dispatcher.post([this] {
2393 update_type_coverage_connection.disconnect();
2394 update_type_coverage_connection = Glib::signal_timeout().connect(
2395 [this]() {
2396 --update_type_coverage_retries;
2397 update_type_coverage();
2398 return false;
2399 },
2400 1000);
2401 });
2402 }
2403 return;
2404 }
2405 update_type_coverage_retries = 0;
2406
2407 std::vector<LanguageProtocol::Range> ranges;
2408 for(auto &uncovered_range : result.array_or_empty("uncoveredRanges")) {
2409 try {
2410 ranges.emplace_back(uncovered_range.object("range"));
2411 }
2412 catch(...) {
2413 }
2414 }
2415
2416 dispatcher.post([this, ranges = std::move(ranges)] {
2417 type_coverage_marks.clear();
2418 for(auto &range : ranges) {
2419 auto start = get_iter_at_line_pos(range.start.line, range.start.character);
2420 auto end = get_iter_at_line_pos(range.end.line, range.end.character);
2421 type_coverage_marks.emplace_back(start, end);
2422 }
2423
2424 update_diagnostics(last_diagnostics);
2425 });
2426 });
2427 }
2428 }
2429