From 89efcacb4f427745ed701df7b1a830236fb97742 Mon Sep 17 00:00:00 2001 From: eidheim Date: Wed, 15 Mar 2023 13:00:01 +0100 Subject: [PATCH] Language client: improved support for autocomplete text edits --- src/source_language_protocol.cpp | 105 ++++++++++++++++--------------- src/source_language_protocol.hpp | 1 + 2 files changed, 57 insertions(+), 49 deletions(-) diff --git a/src/source_language_protocol.cpp b/src/source_language_protocol.cpp index f35183c..cbc5767 100644 --- a/src/source_language_protocol.cpp +++ b/src/source_language_protocol.cpp @@ -1774,7 +1774,7 @@ void Source::LanguageProtocolView::setup_autocomplete() { auto insert = label; if(!using_named_parameters || used_named_parameters.find(get_token(insert)) == used_named_parameters.end()) { autocomplete->rows.emplace_back(std::move(label)); - autocomplete_rows.emplace_back(AutocompleteRow{std::move(insert), {}, LanguageProtocol::Documentation(parameter.child_optional("documentation")), {}, {}}); + autocomplete_rows.emplace_back(AutocompleteRow{std::move(insert), {}, {}, LanguageProtocol::Documentation(parameter.child_optional("documentation")), {}, {}}); } } parameter_position++; @@ -1825,9 +1825,16 @@ void Source::LanguageProtocolView::setup_autocomplete() { } auto insert = item.string_or("insertText", ""); + boost::optional text_edit; if(insert.empty()) { - if(auto text_edit = item.object_optional("textEdit")) - insert = text_edit->string_or("newText", ""); + if(auto text_edit_json = item.object_optional("textEdit")) { + insert = text_edit_json->string_or("newText", ""); + try { + text_edit = LanguageProtocol::TextEdit(*text_edit_json); + } + catch(...) { + } + } } if(insert.empty()) insert = label; @@ -1841,7 +1848,7 @@ void Source::LanguageProtocolView::setup_autocomplete() { item_object = std::make_shared(JSON::make_owner(std::move(item))); autocomplete->rows.emplace_back(std::move(label)); - autocomplete_rows.emplace_back(AutocompleteRow{std::move(insert), std::move(detail), std::move(documentation), std::move(item_object), std::move(additional_text_edits)}); + autocomplete_rows.emplace_back(AutocompleteRow{std::move(insert), std::move(text_edit), std::move(detail), std::move(documentation), std::move(item_object), std::move(additional_text_edits)}); } } } @@ -1852,7 +1859,7 @@ void Source::LanguageProtocolView::setup_autocomplete() { for(auto &snippet : *snippets) { if(starts_with(snippet.prefix, prefix)) { autocomplete->rows.emplace_back(snippet.prefix); - autocomplete_rows.emplace_back(AutocompleteRow{snippet.body, {}, LanguageProtocol::Documentation(snippet.description), {}, {}}); + autocomplete_rows.emplace_back(AutocompleteRow{snippet.body, {}, {}, LanguageProtocol::Documentation(snippet.description), {}, {}}); } } } @@ -1877,10 +1884,17 @@ void Source::LanguageProtocolView::setup_autocomplete() { }; autocomplete->on_select = [this](unsigned int index, const std::string &text, bool hide_window) { - auto insert = hide_window ? autocomplete_rows[index].insert : text; - get_buffer()->erase(CompletionDialog::get()->start_mark->get_iter(), get_buffer()->get_insert()->get_iter()); + if(!hide_window) { + get_buffer()->insert(CompletionDialog::get()->start_mark->get_iter(), text); + return; + } + + auto insert = std::move(autocomplete_rows[index].insert); // autocomplete_rows will be cleared after insert_snippet (see autocomplete->on_hide) + auto text_edit = std::move(autocomplete_rows[index].text_edit); + auto additional_text_edits = std::move(autocomplete_rows[index].additional_text_edits); + // Do not insert function/template parameters if they already exist { auto iter = get_buffer()->get_insert()->get_iter(); @@ -1891,54 +1905,47 @@ void Source::LanguageProtocolView::setup_autocomplete() { } } - // Do not instert ?. after ., instead replace . with ?. - if(1 < insert.size() && insert[0] == '?' && insert[1] == '.') { - auto iter = get_buffer()->get_insert()->get_iter(); - auto prev = iter; - if(prev.backward_char() && *prev == '.') { - get_buffer()->erase(prev, iter); - } - } - - if(hide_window) { - if(autocomplete_show_arguments) { - if(auto symbol = get_named_parameter_symbol()) { // Do not select named parameters in for instance Python - auto named_parameter = get_token(insert); - if(!named_parameter.empty()) { - get_buffer()->insert(CompletionDialog::get()->start_mark->get_iter(), named_parameter + *symbol); - return; - } + if(autocomplete_show_arguments) { + if(auto symbol = get_named_parameter_symbol()) { // Do not select named parameters in for instance Python + auto named_parameter = get_token(insert); + if(!named_parameter.empty()) { + get_buffer()->insert(CompletionDialog::get()->start_mark->get_iter(), named_parameter + *symbol); + return; } - 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; } + 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; + } - auto additional_text_edits = std::move(autocomplete_rows[index].additional_text_edits); // autocomplete_rows will be cleared after insert_snippet (see autocomplete->on_hide) - - get_buffer()->begin_user_action(); + get_buffer()->begin_user_action(); + if(text_edit) { + auto start = get_iter_at_line_pos(text_edit->range.start.line, text_edit->range.start.character); + auto end = get_iter_at_line_pos(text_edit->range.end.line, text_edit->range.end.character); + get_buffer()->erase(start, std::min(end, get_buffer()->get_insert()->get_iter())); + start = get_iter_at_line_pos(text_edit->range.start.line, text_edit->range.start.character); + insert_snippet(start, insert); + } + else insert_snippet(CompletionDialog::get()->start_mark->get_iter(), insert); - for(auto it = additional_text_edits.rbegin(); it != additional_text_edits.rend(); ++it) { - auto start = get_iter_at_line_pos(it->range.start.line, it->range.start.character); - auto end = get_iter_at_line_pos(it->range.end.line, it->range.end.character); - get_buffer()->erase(start, end); - start = get_iter_at_line_pos(it->range.start.line, it->range.start.character); - get_buffer()->insert(start, it->new_text); - } - get_buffer()->end_user_action(); + for(auto it = additional_text_edits.rbegin(); it != additional_text_edits.rend(); ++it) { + auto start = get_iter_at_line_pos(it->range.start.line, it->range.start.character); + auto end = get_iter_at_line_pos(it->range.end.line, it->range.end.character); + get_buffer()->erase(start, end); + start = get_iter_at_line_pos(it->range.start.line, it->range.start.character); + get_buffer()->insert(start, it->new_text); + } + get_buffer()->end_user_action(); - auto iter = get_buffer()->get_insert()->get_iter(); - if(*iter == ')' && iter.backward_char() && *iter == '(') { // If no arguments, try signatureHelp - last_keyval = '('; - if(is_js) // Workaround for typescript-language-server - autocomplete_possibly_no_arguments = true; - autocomplete->run(); - } + auto iter = get_buffer()->get_insert()->get_iter(); + if(*iter == ')' && iter.backward_char() && *iter == '(') { // If no arguments, try signatureHelp + last_keyval = '('; + if(is_js) // Workaround for typescript-language-server + autocomplete_possibly_no_arguments = true; + autocomplete->run(); } - else - get_buffer()->insert(CompletionDialog::get()->start_mark->get_iter(), insert); }; autocomplete->set_tooltip_buffer = [this](unsigned int index) -> std::function { diff --git a/src/source_language_protocol.hpp b/src/source_language_protocol.hpp index 7f25d8f..ba2a1f9 100644 --- a/src/source_language_protocol.hpp +++ b/src/source_language_protocol.hpp @@ -275,6 +275,7 @@ namespace Source { struct AutocompleteRow { std::string insert; + boost::optional text_edit; std::string detail; LanguageProtocol::Documentation documentation; /// CompletionItem for completionItem/resolve