From b88a480ca2c3096a0b84d8c9847806c0a44288d5 Mon Sep 17 00:00:00 2001 From: eidheim Date: Tue, 19 May 2020 14:05:35 +0200 Subject: [PATCH] Language client: added support for fixits --- src/source.h | 2 + src/source_clang.h | 2 - src/source_language_protocol.cc | 130 ++++++++++++++++++++++++++++---- src/source_language_protocol.h | 9 ++- 4 files changed, 125 insertions(+), 18 deletions(-) diff --git a/src/source.h b/src/source.h index 8f8eaf7..12fbf2c 100644 --- a/src/source.h +++ b/src/source.h @@ -122,6 +122,8 @@ namespace Source { gdouble on_motion_last_x = 0.0; gdouble on_motion_last_y = 0.0; + std::vector fix_its; + bool backward_to_code(Gtk::TextIter &iter); bool forward_to_code(Gtk::TextIter &iter); void backward_to_code_or_line_start(Gtk::TextIter &iter); diff --git a/src/source_clang.h b/src/source_clang.h index 1b35f88..e3b86b9 100644 --- a/src/source_clang.h +++ b/src/source_clang.h @@ -36,8 +36,6 @@ namespace Source { void show_type_tooltips(const Gdk::Rectangle &rectangle) override; - std::vector fix_its; - Mutex parse_mutex; std::thread parse_thread; std::atomic parse_state; diff --git a/src/source_language_protocol.cc b/src/source_language_protocol.cc index 1dfcefd..7ed9503 100644 --- a/src/source_language_protocol.cc +++ b/src/source_language_protocol.cc @@ -129,7 +129,7 @@ LanguageProtocol::Capabilities LanguageProtocol::Client::initialize(Source::Lang } // When using rust-analyzer instead of rls, remove: "initializationOptions":{"omitInitBuild":true} // Also remove: "workspace":{"didChangeConfiguration":{"dynamicRegistration":true},"didChangeWatchedFiles":{"dynamicRegistration":true} ? - write_request(nullptr, "initialize", "\"processId\":" + std::to_string(process_id) + R"(,"rootUri":")" + filesystem::get_uri_from_path(root_path) + R"(","capabilities":{"workspace":{"didChangeConfiguration":{"dynamicRegistration":true},"didChangeWatchedFiles":{"dynamicRegistration":true},"symbol":{"dynamicRegistration":true}},"textDocument":{"synchronization":{"dynamicRegistration":true,"didSave":true},"completion":{"dynamicRegistration":true,"completionItem":{"snippetSupport":true,"documentationFormat":["markdown", "plaintext"]}},"hover":{"dynamicRegistration":true,"contentFormat": ["markdown", "plaintext"]},"signatureHelp":{"dynamicRegistration":true,"signatureInformation":{"documentationFormat":["markdown", "plaintext"]}},"definition":{"dynamicRegistration":true},"references":{"dynamicRegistration":true},"documentHighlight":{"dynamicRegistration":true},"documentSymbol":{"dynamicRegistration":true},"formatting":{"dynamicRegistration":true},"rangeFormatting":{"dynamicRegistration":true},"rename":{"dynamicRegistration":true}}},"initializationOptions":{"omitInitBuild":true},"trace":"off")", [this, &result_processed](const boost::property_tree::ptree &result, bool error) { + write_request(nullptr, "initialize", "\"processId\":" + std::to_string(process_id) + R"(,"rootUri":")" + filesystem::get_uri_from_path(root_path) + R"(","capabilities":{"workspace":{"didChangeConfiguration":{"dynamicRegistration":true},"didChangeWatchedFiles":{"dynamicRegistration":true},"symbol":{"dynamicRegistration":true}},"textDocument":{"synchronization":{"dynamicRegistration":true,"didSave":true},"completion":{"dynamicRegistration":true,"completionItem":{"snippetSupport":true,"documentationFormat":["markdown", "plaintext"]}},"hover":{"dynamicRegistration":true,"contentFormat": ["markdown", "plaintext"]},"signatureHelp":{"dynamicRegistration":true,"signatureInformation":{"documentationFormat":["markdown", "plaintext"]}},"definition":{"dynamicRegistration":true},"references":{"dynamicRegistration":true},"documentHighlight":{"dynamicRegistration":true},"documentSymbol":{"dynamicRegistration":true},"formatting":{"dynamicRegistration":true},"rangeFormatting":{"dynamicRegistration":true},"rename":{"dynamicRegistration":true},"codeAction":{"dynamicRegistration":true,"codeActionLiteralSupport":{"codeActionKind":{"valueSet":["quickfix"]}}}}},"initializationOptions":{"omitInitBuild":true},"trace":"off")", [this, &result_processed](const boost::property_tree::ptree &result, bool error) { if(!error) { auto capabilities_pt = result.find("capabilities"); if(capabilities_pt != result.not_found()) { @@ -152,6 +152,9 @@ LanguageProtocol::Capabilities LanguageProtocol::Client::initialize(Source::Lang capabilities.rename = capabilities_pt->second.get("renameProvider", false); if(!capabilities.rename) capabilities.rename = capabilities_pt->second.get("renameProvider.prepareProvider", false); + capabilities.code_action = capabilities_pt->second.get("codeActionProvider", false); + if(!capabilities.code_action) + capabilities.code_action = static_cast(capabilities_pt->second.get_child_optional("codeActionProvider.codeActionKinds")); capabilities.type_coverage = capabilities_pt->second.get("typeCoverageProvider", false); } @@ -357,7 +360,7 @@ void LanguageProtocol::Client::handle_server_request(const std::string &method, LockGuard lock(views_mutex); for(auto view : views) { if(file == view->file_path) { - view->update_diagnostics(std::move(diagnostics)); + view->update_diagnostics_async(std::move(diagnostics)); break; } } @@ -422,6 +425,8 @@ void Source::LanguageProtocolView::close() { if(autocomplete->thread.joinable()) autocomplete->thread.join(); + thread_pool.shutdown(true); + client->write_notification("textDocument/didClose", R"("textDocument":{"uri":")" + uri + "\"}"); client->close(this); client = nullptr; @@ -894,6 +899,12 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { goto_next_diagnostic = [this]() { place_cursor_at_next_diagnostic(); }; + + get_fix_its = [this]() { + if(fix_its.empty()) + Info::get().print("No fix-its found in current buffer"); + return fix_its; + }; } void Source::LanguageProtocolView::escape_text(std::string &text) { @@ -921,16 +932,93 @@ void Source::LanguageProtocolView::escape_text(std::string &text) { } } -void Source::LanguageProtocolView::update_diagnostics(std::vector &&diagnostics) { - dispatcher.post([this, diagnostics = std::move(diagnostics)]() { - update_diagnostics(diagnostics); - last_diagnostics = std::move(diagnostics); - }); +void Source::LanguageProtocolView::update_diagnostics_async(std::vector &&diagnostics) { + update_diagnostics_async_count++; + size_t last_count = update_diagnostics_async_count; + if(capabilities.code_action && !diagnostics.empty()) { + dispatcher.post([this, diagnostics = std::move(diagnostics), last_count]() mutable { + if(last_count != update_diagnostics_async_count) + return; + std::string diagnostics_string; + for(auto &diagnostic : diagnostics) { + auto start = get_iter_at_line_pos(diagnostic.range.start.line, diagnostic.range.start.character); + auto end = get_iter_at_line_pos(diagnostic.range.end.line, diagnostic.range.end.character); + auto range = "{\"start\":{\"line\": " + std::to_string(start.get_line()) + ",\"character\":" + std::to_string(start.get_line_offset()) + R"(},"end":{"line":)" + std::to_string(end.get_line()) + ",\"character\":" + std::to_string(end.get_line_offset()) + "}}"; + auto message = diagnostic.message; + escape_text(message); + if(!diagnostics_string.empty()) + diagnostics_string += ','; + diagnostics_string += "{\"range\":" + range + ",\"message\":\"" + message + "\"}"; + } + auto start = get_buffer()->begin(); + auto end = get_buffer()->end(); + auto range = "{\"start\":{\"line\": " + std::to_string(start.get_line()) + ",\"character\":" + std::to_string(start.get_line_offset()) + R"(},"end":{"line":)" + std::to_string(end.get_line()) + ",\"character\":" + std::to_string(end.get_line_offset()) + "}}"; + + auto request = (R"("textDocument":{"uri":")" + uri + "\"},\"range\":" + range + ",\"context\":{\"diagnostics\":[" + diagnostics_string + "],\"only\":[\"quickfix\"]}"); + thread_pool.push([this, diagnostics = std::move(diagnostics), request = std::move(request), last_count]() mutable { + if(last_count != update_diagnostics_async_count) + return; + std::promise result_processed; + client->write_request(this, "textDocument/codeAction", request, [this, &result_processed, &diagnostics, last_count](const boost::property_tree::ptree &result, bool error) { + if(!error && last_count == update_diagnostics_async_count) { + try { + for(auto it = result.begin(); it != result.end(); ++it) { + if(it->second.get("kind") == "quickfix") { + auto title = it->second.get("title"); + std::vector quickfix_diagnostics; + auto diagnostics_pt = it->second.get_child("diagnostics"); + for(auto it = diagnostics_pt.begin(); it != diagnostics_pt.end(); ++it) + quickfix_diagnostics.emplace_back(it->second); + auto changes = it->second.get_child("edit.changes"); + for(auto file_it = changes.begin(); file_it != changes.end(); ++file_it) { + if(file_it->first == uri) { + for(auto edit_it = file_it->second.begin(); edit_it != file_it->second.end(); ++edit_it) { + LanguageProtocol::TextEdit edit(edit_it->second); + for(auto &diagnostic : diagnostics) { + for(auto &quickfix_diagnostic : quickfix_diagnostics) { + if(diagnostic.message == quickfix_diagnostic.message && diagnostic.range == quickfix_diagnostic.range) { + auto pair = diagnostic.quickfixes.emplace(title, std::vector{}); + pair.first->second.emplace_back(edit.new_text, std::make_pair(Offset(edit.range.start.line, edit.range.start.character), + Offset(edit.range.end.line, edit.range.end.character))); + break; + } + } + } + } + } + } + } + } + } + catch(...) { + } + } + result_processed.set_value(); + }); + result_processed.get_future().get(); + dispatcher.post([this, diagnostics = std::move(diagnostics), last_count]() mutable { + if(last_count == update_diagnostics_async_count) { + last_diagnostics = diagnostics; + update_diagnostics(std::move(diagnostics)); + } + }); + }); + }); + } + else { + dispatcher.post([this, diagnostics = std::move(diagnostics), last_count]() mutable { + if(last_count == update_diagnostics_async_count) { + last_diagnostics = diagnostics; + update_diagnostics(std::move(diagnostics)); + } + }); + } } -void Source::LanguageProtocolView::update_diagnostics(const std::vector &diagnostics) { +void Source::LanguageProtocolView::update_diagnostics(std::vector diagnostics) { diagnostic_offsets.clear(); diagnostic_tooltips.clear(); + fix_its.clear(); get_buffer()->remove_tag_by_name("def:warning_underline", get_buffer()->begin(), get_buffer()->end()); get_buffer()->remove_tag_by_name("def:error_underline", get_buffer()->begin(), get_buffer()->end()); num_warnings = 0; @@ -956,6 +1044,10 @@ void Source::LanguageProtocolView::update_diagnostics(const std::vectorinsert_at_cursor("\n\n"); + else + tooltip.buffer->insert_at_cursor("\n"); tooltip.insert_markdown(diagnostic.related_informations[i].message); tooltip.buffer->insert_at_cursor(": "); - auto pos = tooltip.buffer->get_insert()->get_iter(); - tooltip.buffer->insert_with_tag(pos, link, link_tag); - if(i != diagnostic.related_informations.size() - 1) - tooltip.buffer->insert_at_cursor("\n"); + tooltip.buffer->insert_with_tag(tooltip.buffer->get_insert()->get_iter(), link, link_tag); + } + } + + if(!diagnostic.quickfixes.empty()) { + if(diagnostic.quickfixes.size() == 1) + tooltip.buffer->insert_at_cursor("\n\nFix-it:"); + else + tooltip.buffer->insert_at_cursor("\n\nFix-its:"); + for(auto &quickfix : diagnostic.quickfixes) { + tooltip.buffer->insert_at_cursor("\n"); + tooltip.insert_markdown(quickfix.first); } } }); @@ -1117,7 +1219,7 @@ void Source::LanguageProtocolView::show_type_tooltips(const Gdk::Rectangle &rect next_char_iter++; debug_value.replace(iter, next_char_iter, "?"); } - tooltip.buffer->insert(tooltip.buffer->get_insert()->get_iter(), "\n\n" + value_type + ": " + debug_value.substr(pos + 3, debug_value.size() - (pos + 3) - 1)); + tooltip.buffer->insert_at_cursor("\n\n" + value_type + ": " + debug_value.substr(pos + 3, debug_value.size() - (pos + 3) - 1)); } } } @@ -1564,7 +1666,7 @@ void Source::LanguageProtocolView::setup_autocomplete() { tooltip.insert_with_links_tagged(plaintext); if(!markdown.empty()) { if(!plaintext.empty()) - tooltip.buffer->insert(tooltip.buffer->get_insert()->get_iter(), "\n\n"); + tooltip.buffer->insert_at_cursor("\n\n"); tooltip.insert_markdown(markdown); } }; diff --git a/src/source_language_protocol.h b/src/source_language_protocol.h index 05f3625..ad9c0bf 100644 --- a/src/source_language_protocol.h +++ b/src/source_language_protocol.h @@ -70,6 +70,7 @@ namespace LanguageProtocol { Range range; int severity; std::vector related_informations; + std::map> quickfixes; }; class TextEdit { @@ -96,6 +97,7 @@ namespace LanguageProtocol { bool document_formatting = false; bool document_range_formatting = false; bool rename = false; + bool code_action = false; bool type_coverage = false; }; @@ -153,10 +155,12 @@ namespace Source { void rename(const boost::filesystem::path &path) override; bool save() override; - void update_diagnostics(std::vector &&diagnostics); + void update_diagnostics_async(std::vector &&diagnostics); private: - void update_diagnostics(const std::vector &diagnostics); + std::atomic update_diagnostics_async_count = {0}; + + void update_diagnostics(std::vector diagnostics); public: Gtk::TextIter get_iter_at_line_pos(int line, int pos) override; @@ -180,6 +184,7 @@ namespace Source { std::thread initialize_thread; Dispatcher dispatcher; + Glib::ThreadPool thread_pool; void setup_navigation_and_refactoring();