diff --git a/src/autocomplete.cc b/src/autocomplete.cc index 8e75f66..892b6e7 100644 --- a/src/autocomplete.cc +++ b/src/autocomplete.cc @@ -1,8 +1,8 @@ #include "autocomplete.h" #include "selection_dialog.h" -Autocomplete::Autocomplete(Gtk::TextView *view, bool &interactive_completion, guint &last_keyval, bool strip_word) - : view(view), interactive_completion(interactive_completion), strip_word(strip_word), state(State::IDLE) { +Autocomplete::Autocomplete(Gtk::TextView *view, bool &interactive_completion, guint &last_keyval, bool pass_buffer_and_strip_word) + : view(view), interactive_completion(interactive_completion), pass_buffer_and_strip_word(pass_buffer_and_strip_word) { view->get_buffer()->signal_changed().connect([this, &last_keyval] { if(CompletionDialog::get() && CompletionDialog::get()->is_visible()) { cancel_reparse(); @@ -56,12 +56,13 @@ void Autocomplete::run() { if(thread.joinable()) thread.join(); - auto buffer = view->get_buffer()->get_text(); auto iter = view->get_buffer()->get_insert()->get_iter(); auto line_nr = iter.get_line() + 1; auto column_nr = iter.get_line_index() + 1; - if(strip_word) { + Glib::ustring buffer; + if(pass_buffer_and_strip_word) { auto pos = iter.get_offset() - 1; + buffer = view->get_buffer()->get_text(); while(pos >= 0 && ((buffer[pos] >= 'a' && buffer[pos] <= 'z') || (buffer[pos] >= 'A' && buffer[pos] <= 'Z') || (buffer[pos] >= '0' && buffer[pos] <= '9') || buffer[pos] == '_')) { buffer.replace(pos, 1, " "); diff --git a/src/autocomplete.h b/src/autocomplete.h index 6e2fc45..df224db 100644 --- a/src/autocomplete.h +++ b/src/autocomplete.h @@ -7,8 +7,9 @@ class Autocomplete { Gtk::TextView *view; bool &interactive_completion; - /// Some libraries/utilities, like libclang, require that autocomplete is started at the beginning of a word - bool strip_word; + /// If text_view buffer should be passed to add_rows. Empty buffer is passed if not. + /// Also, some utilities, like libclang, require that autocomplete is started at the beginning of a word. + bool pass_buffer_and_strip_word; Dispatcher dispatcher; @@ -20,7 +21,7 @@ public: std::vector rows; Tooltips tooltips; - std::atomic state; + std::atomic state = {State::IDLE}; std::thread thread; @@ -48,7 +49,7 @@ public: std::function get_tooltip = [](unsigned int index) { return std::string(); }; - Autocomplete(Gtk::TextView *view, bool &interactive_completion, guint &last_keyval, bool strip_word); + Autocomplete(Gtk::TextView *view, bool &interactive_completion, guint &last_keyval, bool pass_buffer_and_strip_word); void run(); void stop(); diff --git a/src/source.cc b/src/source.cc index f8ac713..af4ad8b 100644 --- a/src/source.cc +++ b/src/source.cc @@ -113,7 +113,7 @@ std::string Source::FixIt::string(const Glib::RefPtr &buffer) { std::set Source::View::non_deleted_views; std::set Source::View::views; -Source::View::View(const boost::filesystem::path &file_path, const Glib::RefPtr &language, bool is_generic_view) : BaseView(file_path, language), SpellCheckView(file_path, language), DiffView(file_path, language), parsed(true) { +Source::View::View(const boost::filesystem::path &file_path, const Glib::RefPtr &language, bool is_generic_view) : BaseView(file_path, language), SpellCheckView(file_path, language), DiffView(file_path, language) { non_deleted_views.emplace(this); views.emplace(this); @@ -1318,6 +1318,18 @@ bool Source::View::is_templated_function(Gtk::TextIter iter, Gtk::TextIter &pare return false; } +bool Source::View::is_possible_argument() { + auto iter = get_buffer()->get_insert()->get_iter(); + if(iter.backward_char() && (!interactive_completion || last_keyval == '(' || last_keyval == ',' || last_keyval == ' ' || + last_keyval == GDK_KEY_Return || last_keyval == GDK_KEY_KP_Enter)) { + while((*iter == ' ' || *iter == '\t' || *iter == '\n' || *iter == '\r') && iter.backward_char()) { + } + if(*iter == '(' || *iter == ',') + return true; + } + return false; +} + bool Source::View::on_key_press_event(GdkEventKey *key) { class Guard { public: diff --git a/src/source.h b/src/source.h index f717a1c..508de04 100644 --- a/src/source.h +++ b/src/source.h @@ -87,7 +87,7 @@ namespace Source { virtual void full_reparse() { full_reparse_needed = false; } protected: - std::atomic parsed; + std::atomic parsed = {true}; Tooltips diagnostic_tooltips; Tooltips type_tooltips; sigc::connection delayed_tooltips_connection; @@ -122,6 +122,8 @@ namespace Source { bool find_close_symbol_forward(Gtk::TextIter iter, Gtk::TextIter &found_iter, unsigned int positive_char, unsigned int negative_char); long symbol_count(Gtk::TextIter iter, unsigned int positive_char, unsigned int negative_char); bool is_templated_function(Gtk::TextIter iter, Gtk::TextIter &parenthesis_end_iter); + /// If insert is at an possible argument. Also based on last key press. + bool is_possible_argument(); bool on_key_press_event(GdkEventKey *key) override; bool on_key_press_event_basic(GdkEventKey *key); diff --git a/src/source_clang.cc b/src/source_clang.cc index e6aa4ab..95581ed 100644 --- a/src/source_clang.cc +++ b/src/source_clang.cc @@ -485,9 +485,9 @@ Source::ClangViewAutocomplete::ClangViewAutocomplete(const boost::filesystem::pa return; if(!has_focus()) return; - if(show_arguments) + if(show_parameters) autocomplete.stop(); - show_arguments = false; + show_parameters = false; delayed_show_arguments_connection.disconnect(); delayed_show_arguments_connection = Glib::signal_timeout().connect([this]() { if(get_buffer()->get_has_selection()) @@ -496,7 +496,7 @@ Source::ClangViewAutocomplete::ClangViewAutocomplete(const boost::filesystem::pa return false; if(!has_focus()) return false; - if(is_possible_parameter()) { + if(is_possible_argument()) { autocomplete.stop(); autocomplete.run(); } @@ -506,7 +506,7 @@ Source::ClangViewAutocomplete::ClangViewAutocomplete(const boost::filesystem::pa // Remove argument completions signal_key_press_event().connect([this](GdkEventKey *key) { - if(show_arguments && CompletionDialog::get() && CompletionDialog::get()->is_visible() && + if(show_parameters && CompletionDialog::get() && CompletionDialog::get()->is_visible() && key->keyval != GDK_KEY_Down && key->keyval != GDK_KEY_Up && key->keyval != GDK_KEY_Return && key->keyval != GDK_KEY_KP_Enter && key->keyval != GDK_KEY_ISO_Left_Tab && key->keyval != GDK_KEY_Tab && @@ -538,7 +538,7 @@ Source::ClangViewAutocomplete::ClangViewAutocomplete(const boost::filesystem::pa if(!is_code_iter(iter)) return false; - show_arguments = false; + show_parameters = false; std::string line = " " + get_line_before(); const static std::regex dot_or_arrow(R"(^.*[a-zA-Z0-9_\)\]\>](\.|->)([a-zA-Z0-9_]*)$)"); @@ -569,8 +569,8 @@ Source::ClangViewAutocomplete::ClangViewAutocomplete(const boost::filesystem::pa if(autocomplete.prefix.size() == 0 || autocomplete.prefix[0] < '0' || autocomplete.prefix[0] > '9') return true; } - else if(is_possible_parameter()) { - show_arguments = true; + else if(is_possible_argument()) { + show_parameters = true; std::unique_lock lock(autocomplete.prefix_mutex); autocomplete.prefix = ""; return true; @@ -631,7 +631,7 @@ Source::ClangViewAutocomplete::ClangViewAutocomplete(const boost::filesystem::pa auto result = code_complete_results->get(i); if(result.available()) { std::string text; - if(show_arguments) { + if(show_parameters) { class Recursive { public: static void f(const clangmm::CompletionString &completion_string, std::string &text) { @@ -741,7 +741,7 @@ Source::ClangViewAutocomplete::ClangViewAutocomplete(const boost::filesystem::pa if(hide_window) { size_t start_pos = std::string::npos; size_t end_pos = std::string::npos; - if(show_arguments) { + if(show_parameters) { start_pos = 0; end_pos = row.size(); } @@ -798,18 +798,6 @@ Source::ClangViewAutocomplete::ClangViewAutocomplete(const boost::filesystem::pa }; } -bool Source::ClangViewAutocomplete::is_possible_parameter() { - auto iter = get_buffer()->get_insert()->get_iter(); - if(iter.backward_char() && (!interactive_completion || last_keyval == '(' || last_keyval == ',' || last_keyval == ' ' || - last_keyval == GDK_KEY_Return || last_keyval == GDK_KEY_KP_Enter)) { - while((*iter == ' ' || *iter == '\t' || *iter == '\n' || *iter == '\r') && iter.backward_char()) { - } - if(*iter == '(' || *iter == ',') - return true; - } - return false; -} - const std::unordered_map &Source::ClangViewAutocomplete::autocomplete_manipulators_map() { //TODO: feel free to add more static std::unordered_map map = { diff --git a/src/source_clang.h b/src/source_clang.h index ea8d16f..d5d7f88 100644 --- a/src/source_clang.h +++ b/src/source_clang.h @@ -68,8 +68,8 @@ namespace Source { sigc::connection delayed_show_arguments_connection; private: - bool is_possible_parameter(); - bool show_arguments; + std::atomic show_parameters = {false}; + const std::unordered_map &autocomplete_manipulators_map(); }; diff --git a/src/source_language_protocol.cc b/src/source_language_protocol.cc index 625bbe8..06a706c 100644 --- a/src/source_language_protocol.cc +++ b/src/source_language_protocol.cc @@ -119,6 +119,7 @@ LanguageProtocol::Capabilities LanguageProtocol::Client::initialize(Source::Lang capabilities.text_document_sync = static_cast(capabilities_pt->second.get("textDocumentSync", 0)); capabilities.hover = capabilities_pt->second.get("hoverProvider", false); capabilities.completion = capabilities_pt->second.find("completionProvider") != capabilities_pt->second.not_found() ? true : false; + capabilities.signature_help = capabilities_pt->second.find("signatureHelpProvider") != capabilities_pt->second.not_found() ? true : false; capabilities.definition = capabilities_pt->second.get("definitionProvider", false); capabilities.references = capabilities_pt->second.get("referencesProvider", false); capabilities.document_highlight = capabilities_pt->second.get("documentHighlightProvider", false); @@ -433,6 +434,8 @@ void Source::LanguageProtocolView::initialize(bool setup) { } void Source::LanguageProtocolView::close() { + autocomplete_delayed_show_arguments_connection.disconnect(); + if(initialize_thread.joinable()) initialize_thread.join(); @@ -1145,6 +1148,50 @@ void Source::LanguageProtocolView::setup_autocomplete() { autocomplete_insert.clear(); }; + if(capabilities.signature_help) { + // Activate argument completions + get_buffer()->signal_changed().connect([this] { + if(!interactive_completion) + return; + if(CompletionDialog::get() && CompletionDialog::get()->is_visible()) + return; + if(!has_focus()) + return; + if(autocomplete_show_parameters) + autocomplete.stop(); + autocomplete_show_parameters = false; + autocomplete_delayed_show_arguments_connection.disconnect(); + autocomplete_delayed_show_arguments_connection = Glib::signal_timeout().connect([this]() { + if(get_buffer()->get_has_selection()) + return false; + if(CompletionDialog::get() && CompletionDialog::get()->is_visible()) + return false; + if(!has_focus()) + return false; + if(is_possible_argument()) { + autocomplete.stop(); + autocomplete.run(); + } + return false; + }, 500); + }, false); + + // Remove argument completions + if(!has_named_parameters()) { // Do not remove named parameters in for instance Python + signal_key_press_event().connect([this](GdkEventKey *key) { + if(autocomplete_show_parameters && CompletionDialog::get() && CompletionDialog::get()->is_visible() && + key->keyval != GDK_KEY_Down && key->keyval != GDK_KEY_Up && + key->keyval != GDK_KEY_Return && key->keyval != GDK_KEY_KP_Enter && + key->keyval != GDK_KEY_ISO_Left_Tab && key->keyval != GDK_KEY_Tab && + (key->keyval < GDK_KEY_Shift_L || key->keyval > GDK_KEY_Hyper_R)) { + get_buffer()->erase(CompletionDialog::get()->start_mark->get_iter(), get_buffer()->get_insert()->get_iter()); + CompletionDialog::get()->hide(); + } + return false; + }, false); + } + } + autocomplete.is_continue_key = [](guint keyval) { if((keyval >= '0' && keyval <= '9') || (keyval >= 'a' && keyval <= 'z') || (keyval >= 'A' && keyval <= 'Z') || keyval == '_') return true; @@ -1166,6 +1213,8 @@ void Source::LanguageProtocolView::setup_autocomplete() { if(!is_code_iter(iter)) return false; + autocomplete_show_parameters = false; + std::string line = " " + get_line_before(); const static std::regex dot_or_arrow(R"(^.*[a-zA-Z0-9_\)\]\>"'](\.)([a-zA-Z0-9_]*)$)"); const static std::regex colon_colon(R"(^.*::([a-zA-Z0-9_]*)$)"); @@ -1195,6 +1244,12 @@ void Source::LanguageProtocolView::setup_autocomplete() { if(autocomplete.prefix.size() == 0 || autocomplete.prefix[0] < '0' || autocomplete.prefix[0] > '9') return true; } + else if(is_possible_argument()) { + autocomplete_show_parameters = true; + std::unique_lock lock(autocomplete.prefix_mutex); + autocomplete.prefix = ""; + return true; + } else if(!interactive_completion) { auto end_iter = get_buffer()->get_insert()->get_iter(); auto iter = end_iter; @@ -1232,61 +1287,84 @@ void Source::LanguageProtocolView::setup_autocomplete() { autocomplete_comment.clear(); autocomplete_insert.clear(); std::promise result_processed; - client->write_request(this, "textDocument/completion", R"("textDocument":{"uri":"file://)" + file_path.string() + R"("}, "position": {"line": )" + std::to_string(line_number - 1) + ", \"character\": " + std::to_string(column - 1) + "}", [this, &result_processed](const boost::property_tree::ptree &result, bool error) { - if(!error) { - auto begin = result.begin(); // rust language server is bugged - auto end = result.end(); - auto items_it = result.find("items"); // correct - if(items_it != result.not_found()) { - begin = items_it->second.begin(); - end = items_it->second.end(); + if(autocomplete_show_parameters) { + if(!capabilities.signature_help) + return; + client->write_request(this, "textDocument/signatureHelp", R"("textDocument":{"uri":"file://)" + file_path.string() + R"("}, "position": {"line": )" + std::to_string(line_number - 1) + ", \"character\": " + std::to_string(column - 1) + "}", [this, &result_processed](const boost::property_tree::ptree &result, bool error) { + if(!error) { + auto signatures = result.get_child("signatures", boost::property_tree::ptree()); + for(auto signature_it = signatures.begin(); signature_it != signatures.end(); ++signature_it) { + auto parameters = signature_it->second.get_child("parameters", boost::property_tree::ptree()); + for(auto parameter_it = parameters.begin(); parameter_it != parameters.end(); ++parameter_it) { + auto label = parameter_it->second.get("label", ""); + auto insert = label; + auto documentation = parameter_it->second.get("documentation", ""); + autocomplete.rows.emplace_back(std::move(label)); + autocomplete_insert.emplace_back(std::move(insert)); + autocomplete_comment.emplace_back(std::move(documentation)); + } + } } - for(auto it = begin; it != end; ++it) { - auto label = it->second.get("label", ""); - auto detail = it->second.get("detail", ""); - auto documentation = it->second.get("documentation", ""); - auto insert = it->second.get("insertText", ""); - if(!insert.empty()) { - // In case ( is missing in insert but is present in label - if(label.size() > insert.size() && label.back() == ')' && insert.find('(') == std::string::npos) { - auto pos = label.find('('); - if(pos != std::string::npos && pos == insert.size() && pos + 1 < label.size()) { - if(pos + 2 == label.size()) // If no parameters - insert += "()"; - else - insert += "(${1:" + label.substr(pos + 1, label.size() - 1 - (pos + 1)) + "})"; + result_processed.set_value(); + }); + } + else { + client->write_request(this, "textDocument/completion", R"("textDocument":{"uri":"file://)" + file_path.string() + R"("}, "position": {"line": )" + std::to_string(line_number - 1) + ", \"character\": " + std::to_string(column - 1) + "}", [this, &result_processed](const boost::property_tree::ptree &result, bool error) { + if(!error) { + auto begin = result.begin(); // rust language server is bugged + auto end = result.end(); + auto items_it = result.find("items"); // correct + if(items_it != result.not_found()) { + begin = items_it->second.begin(); + end = items_it->second.end(); + } + for(auto it = begin; it != end; ++it) { + auto label = it->second.get("label", ""); + auto detail = it->second.get("detail", ""); + auto documentation = it->second.get("documentation", ""); + auto insert = it->second.get("insertText", ""); + if(!insert.empty()) { + // In case ( is missing in insert but is present in label + if(label.size() > insert.size() && label.back() == ')' && insert.find('(') == std::string::npos) { + auto pos = label.find('('); + if(pos != std::string::npos && pos == insert.size() && pos + 1 < label.size()) { + if(pos + 2 == label.size()) // If no parameters + insert += "()"; + else + insert += "(${1:" + label.substr(pos + 1, label.size() - 1 - (pos + 1)) + "})"; + } } } - } - else { - insert = label; - auto kind = it->second.get("kind", 0); - if(kind >= 2 && kind <= 3) { - bool found_bracket = false; - for(auto &chr : insert) { - if(chr == '(' || chr == '{') { - found_bracket = true; - break; + else { + insert = label; + auto kind = it->second.get("kind", 0); + if(kind >= 2 && kind <= 3) { + bool found_bracket = false; + for(auto &chr : insert) { + if(chr == '(' || chr == '{') { + found_bracket = true; + break; + } } + if(!found_bracket) + insert += "(${1:})"; } - if(!found_bracket) - insert += "(${1:})"; } - } - if(!label.empty()) { - autocomplete.rows.emplace_back(std::move(label)); - autocomplete_comment.emplace_back(std::move(detail)); - if(!documentation.empty()) { - if(!autocomplete_comment.back().empty()) - autocomplete_comment.back() += "\n\n"; - autocomplete_comment.back() += documentation; + if(!label.empty()) { + autocomplete.rows.emplace_back(std::move(label)); + autocomplete_comment.emplace_back(std::move(detail)); + if(!documentation.empty()) { + if(!autocomplete_comment.back().empty()) + autocomplete_comment.back() += "\n\n"; + autocomplete_comment.back() += documentation; + } + autocomplete_insert.emplace_back(std::move(insert)); } - autocomplete_insert.emplace_back(std::move(insert)); } } - } - result_processed.set_value(); - }); + result_processed.set_value(); + }); + } result_processed.get_future().get(); } }; @@ -1342,6 +1420,20 @@ void Source::LanguageProtocolView::setup_autocomplete() { if(hide_window) { Glib::ustring insert = autocomplete_insert[index]; + if(autocomplete_show_parameters) { + if(has_named_parameters()) { // Do not select named parameters in for instance Python, instead add = after insert + get_buffer()->insert(CompletionDialog::get()->start_mark->get_iter(), insert + '='); + return; + } + else { + get_buffer()->insert(CompletionDialog::get()->start_mark->get_iter(), insert); + int start_offset = CompletionDialog::get()->start_mark->get_iter().get_offset(); + int end_offset = CompletionDialog::get()->start_mark->get_iter().get_offset() + insert.size(); + get_buffer()->select_range(get_buffer()->get_iter_at_offset(start_offset), get_buffer()->get_iter_at_offset(end_offset)); + return; + } + } + // Do not insert function/template parameters if they already exist auto iter = get_buffer()->get_insert()->get_iter(); if(*iter == '(' || *iter == '<') { @@ -1398,6 +1490,12 @@ void Source::LanguageProtocolView::setup_autocomplete() { }; } +bool Source::LanguageProtocolView::has_named_parameters() { + if(language_id == "python") // TODO: add more languages that supports named parameters + return true; + return false; +} + void Source::LanguageProtocolView::add_flow_coverage_tooltips(bool called_in_thread) { std::stringstream stdin_stream, stderr_stream; auto stdout_stream = std::make_shared(); diff --git a/src/source_language_protocol.h b/src/source_language_protocol.h index 8bb8f12..67a4411 100644 --- a/src/source_language_protocol.h +++ b/src/source_language_protocol.h @@ -66,6 +66,7 @@ namespace LanguageProtocol { TextDocumentSync text_document_sync; bool hover; bool completion; + bool signature_help; bool definition; bool references; bool document_highlight; @@ -162,6 +163,10 @@ namespace Source { std::vector autocomplete_comment; std::vector autocomplete_insert; std::list, Glib::RefPtr>> argument_marks; + bool autocomplete_show_parameters = false; + sigc::connection autocomplete_delayed_show_arguments_connection; + + bool has_named_parameters(); boost::filesystem::path flow_coverage_executable; std::vector, Glib::RefPtr>> flow_coverage_marks;