diff --git a/src/source.hpp b/src/source.hpp index 207007c..c58a0b9 100644 --- a/src/source.hpp +++ b/src/source.hpp @@ -125,14 +125,21 @@ namespace Source { std::vector fix_its; + /// Returns true if code iter (not part of comment or string, and not whitespace) is found. + /// Iter will not be moved if iter is already a code iter. bool backward_to_code(Gtk::TextIter &iter); + /// Returns true if code iter (not part of comment or string, and not whitespace) is found. + /// Iter will not be moved if iter is already a code iter. bool forward_to_code(Gtk::TextIter &iter); + /// Iter will not be moved if iter is already a code iter (not part of comment or string, and not whitespace) or at line start void backward_to_code_or_line_start(Gtk::TextIter &iter); /// If closing bracket is found, continues until the open bracket. /// Returns if open bracket is found that has no corresponding closing bracket. /// Else, return at start of line. Gtk::TextIter get_start_of_expression(Gtk::TextIter iter); + /// Iter will not be moved if iter is already at close symbol. bool find_close_symbol_forward(Gtk::TextIter iter, Gtk::TextIter &found_iter, unsigned int positive_char, unsigned int negative_char); + /// Iter will not be moved if iter is already at open symbol. bool find_open_symbol_backward(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 = 0); bool is_templated_function(Gtk::TextIter iter, Gtk::TextIter &parenthesis_end_iter); diff --git a/src/source_language_protocol.cpp b/src/source_language_protocol.cpp index 0cb058e..d80c3cf 100644 --- a/src/source_language_protocol.cpp +++ b/src/source_language_protocol.cpp @@ -1402,7 +1402,7 @@ void Source::LanguageProtocolView::setup_autocomplete() { }, false); // Remove argument completions - if(!has_named_parameters()) { // Do not remove named parameters in for instance Python + if(!get_named_parameter_symbol()) { // Do not remove named parameters in for instance Python signal_key_press_event().connect([this](GdkEventKey *event) { if(autocomplete_show_arguments && CompletionDialog::get() && CompletionDialog::get()->is_visible() && event->keyval != GDK_KEY_Down && event->keyval != GDK_KEY_Up && @@ -1524,29 +1524,70 @@ void Source::LanguageProtocolView::setup_autocomplete() { if(autocomplete_show_arguments) { if(!capabilities.signature_help) return; - client->write_request(this, "textDocument/signatureHelp", R"("textDocument":{"uri":")" + uri + 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", ""); - std::string kind; - if(documentation.empty()) { - auto documentation_pt = parameter_it->second.get_child_optional("documentation"); - if(documentation_pt) { - documentation = documentation_pt->get("value", ""); - kind = documentation_pt->get("kind", ""); + dispatcher.post([this, line_number, column, &result_processed] { + // Find current parameter number and previously used named parameters + unsigned current_parameter_position = 0; + auto named_parameter_symbol = get_named_parameter_symbol(); + std::set used_named_parameters; + auto iter = get_buffer()->get_insert()->get_iter(); + int para_count = 0; + while(iter.backward_char() && backward_to_code(iter)) { + if(para_count == 0) { + if(named_parameter_symbol && (*iter == ',' || *iter == '(')) { + auto next = iter; + if(next.forward_char() && forward_to_code(next)) { + auto pair = get_token_iters(next); + if(pair.first != pair.second) { + auto symbol = pair.second; + if(forward_to_code(symbol) && *symbol == static_cast(*named_parameter_symbol)) + used_named_parameters.emplace(get_buffer()->get_text(pair.first, pair.second)); } } - autocomplete->rows.emplace_back(std::move(label)); - autocomplete_rows.emplace_back(AutocompleteRow{std::move(insert), {}, std::move(documentation), std::move(kind)}); } + if(*iter == ',') + ++current_parameter_position; + else if(*iter == '(') + break; } + if(*iter == '(') + ++para_count; + else if(*iter == ')') + --para_count; } - result_processed.set_value(); + bool using_named_parameters = named_parameter_symbol && !(current_parameter_position > 0 && used_named_parameters.empty()); + + client->write_request(this, "textDocument/signatureHelp", R"("textDocument":{"uri":")" + uri + R"("}, "position": {"line": )" + std::to_string(line_number - 1) + ", \"character\": " + std::to_string(column - 1) + "}", [this, &result_processed, current_parameter_position, using_named_parameters, used_named_parameters = std::move(used_named_parameters)](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()); + unsigned parameter_position = 0; + for(auto parameter_it = parameters.begin(); parameter_it != parameters.end(); ++parameter_it) { + if(parameter_position == current_parameter_position || using_named_parameters) { + auto label = parameter_it->second.get("label", ""); + auto insert = label; + auto documentation = parameter_it->second.get("documentation", ""); + std::string kind; + if(documentation.empty()) { + auto documentation_pt = parameter_it->second.get_child_optional("documentation"); + if(documentation_pt) { + documentation = documentation_pt->get("value", ""); + kind = documentation_pt->get("kind", ""); + } + } + if(documentation == "null") // Python erroneously returns "null" when a parameter is not documented + documentation.clear(); + if(!using_named_parameters || used_named_parameters.find(insert) == used_named_parameters.end()) { + autocomplete->rows.emplace_back(std::move(label)); + autocomplete_rows.emplace_back(AutocompleteRow{std::move(insert), {}, std::move(documentation), std::move(kind)}); + } + } + parameter_position++; + } + } + } + result_processed.set_value(); + }); }); } else { @@ -1562,67 +1603,40 @@ void Source::LanguageProtocolView::setup_autocomplete() { begin = result.begin(); end = result.end(); } + std::string prefix; + { + LockGuard lock(autocomplete->prefix_mutex); + prefix = autocomplete->prefix; + } 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", ""); - std::string kind; - if(documentation.empty()) { - auto documentation_pt = it->second.get_child_optional("documentation"); - if(documentation_pt) { - documentation = documentation_pt->get("value", ""); - kind = documentation_pt->get("kind", ""); - } - } - auto insert = it->second.get("insertText", ""); - if(insert.empty()) - insert = it->second.get("textEdit.newText", ""); - 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)) + "})"; + if(starts_with(label, prefix)) { + auto detail = it->second.get("detail", ""); + auto documentation = it->second.get("documentation", ""); + std::string documentation_kind; + if(documentation.empty()) { + auto documentation_pt = it->second.get_child_optional("documentation"); + if(documentation_pt) { + documentation = documentation_pt->get("value", ""); + documentation_kind = documentation_pt->get("kind", ""); } } - } - 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) + auto insert = it->second.get("insertText", ""); + if(insert.empty()) + insert = it->second.get("textEdit.newText", ""); + if(insert.empty()) + insert = label; + if(!insert.empty()) { + auto kind = it->second.get("kind", 0); + if(kind >= 2 && kind <= 3 && insert.find('(') == std::string::npos) // If kind is method or function, but parentheses are missing insert += "(${1:})"; - } - } - if(!label.empty()) { - std::string prefix; - { - LockGuard lock(autocomplete->prefix_mutex); - prefix = autocomplete->prefix; - } - if(starts_with(label, prefix)) { autocomplete->rows.emplace_back(std::move(label)); - autocomplete_rows.emplace_back(AutocompleteRow{std::move(insert), std::move(detail), std::move(documentation), std::move(kind)}); + autocomplete_rows.emplace_back(AutocompleteRow{std::move(insert), std::move(detail), std::move(documentation), std::move(documentation_kind)}); } } } if(autocomplete_enable_snippets) { - std::string prefix; - { - LockGuard lock(autocomplete->prefix_mutex); - prefix = autocomplete->prefix; - } LockGuard lock(snippets_mutex); if(snippets) { for(auto &snippet : *snippets) { @@ -1675,8 +1689,8 @@ void Source::LanguageProtocolView::setup_autocomplete() { if(hide_window) { if(autocomplete_show_arguments) { - if(has_named_parameters()) // Do not select named parameters in for instance Python - get_buffer()->insert(CompletionDialog::get()->start_mark->get_iter(), insert); + if(auto symbol = get_named_parameter_symbol()) // Do not select named parameters in for instance Python + get_buffer()->insert(CompletionDialog::get()->start_mark->get_iter(), insert + *symbol); else { get_buffer()->insert(CompletionDialog::get()->start_mark->get_iter(), insert); int start_offset = CompletionDialog::get()->start_mark->get_iter().get_offset(); @@ -1720,10 +1734,10 @@ void Source::LanguageProtocolView::setup_autocomplete() { }; } -bool Source::LanguageProtocolView::has_named_parameters() { +boost::optional Source::LanguageProtocolView::get_named_parameter_symbol() { if(language_id == "python") // TODO: add more languages that supports named parameters - return true; - return false; + return '='; + return {}; } void Source::LanguageProtocolView::update_type_coverage() { diff --git a/src/source_language_protocol.hpp b/src/source_language_protocol.hpp index bd76f3c..272c1b9 100644 --- a/src/source_language_protocol.hpp +++ b/src/source_language_protocol.hpp @@ -210,7 +210,9 @@ namespace Source { bool autocomplete_show_arguments = false; sigc::connection autocomplete_delayed_show_arguments_connection; - bool has_named_parameters(); + /// If language supports named parameters, returns the symbol separating the named parameter and value, + /// for instance '=' for Python + boost::optional get_named_parameter_symbol(); std::vector last_diagnostics;