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 &params, 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 &params) {
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 &&params) {
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 &&params) {
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 &registration : 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> &param) {
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>> &params) {
742   size_t size = params.empty() ? 0 : params.size() - 1;
743   for(auto &param : params)
744     size += param.first.size() + param.second.size() + 3;
745 
746   std::string result;
747   result.reserve(size);
748   for(auto &param : 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 &params, 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>> &params) {
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 &parameter : 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