diff --git a/README.md b/README.md index 1044d69..d021c4d 100644 --- a/README.md +++ b/README.md @@ -24,8 +24,8 @@ towards libclang with speed, stability, and ease of use in mind. * Git support through libgit2 * Fast C++ autocompletion * Keyword and buffer autocompletion for other file types -* Experimental language server protocol support that is enabled if `[language identifier]-language-server` executable is found. This executable can be a symbolic link to one of your installed language server binaries. -See [language-server-protocol/specification.md](https://github.com/Microsoft/language-server-protocol/blob/gh-pages/specification.md) for a table of the currently defined language identifiers. +* Language server protocol support that is enabled if `[language identifier]-language-server` executable is found. This executable can be a symbolic link to one of your installed language server binaries. +See [language-server-protocol/specification.md](https://github.com/Microsoft/language-server-protocol/blob/gh-pages/specification.md) for the currently defined language identifiers. * Tooltips showing type information and doxygen documentation (C++) * Rename refactoring across files (C++) * Highlighting of similar types (C++) diff --git a/src/source.cc b/src/source.cc index bf399c8..318d54c 100644 --- a/src/source.cc +++ b/src/source.cc @@ -1213,6 +1213,21 @@ Gtk::TextIter Source::View::get_iter_for_dialog() { return iter; } +Gtk::TextIter Source::View::get_iter_at_line_pos(int line, int pos) { + line=std::min(line, get_buffer()->get_line_count()-1); + if(line<0) + line=0; + auto iter=get_iter_at_line_end(line); + pos=std::min(pos, iter.get_line_index()); + if(pos<0) + pos=0; + return get_buffer()->get_iter_at_line_index(line, pos); +} + +void Source::View::place_cursor_at_line_pos(int line, int pos) { + get_buffer()->place_cursor(get_iter_at_line_pos(line, pos)); +} + void Source::View::place_cursor_at_line_offset(int line, int offset) { line=std::min(line, get_buffer()->get_line_count()-1); if(line<0) diff --git a/src/source.h b/src/source.h index 38f450e..1bca74b 100644 --- a/src/source.h +++ b/src/source.h @@ -87,7 +87,13 @@ namespace Source { Gtk::TextIter get_iter_for_dialog(); std::function scroll_to_cursor_delayed=[](View* view, bool center, bool show_tooltips) {}; + /// Safely returns iter at a line at an offset using either byte index or character offset. Defaults to using byte index. + virtual Gtk::TextIter get_iter_at_line_pos(int line, int pos); + /// Safely places cursor at line using get_iter_at_line_pos. + void place_cursor_at_line_pos(int line, int pos); + /// Safely places cursor at line offset void place_cursor_at_line_offset(int line, int offset); + /// Safely places cursor at line index void place_cursor_at_line_index(int line, int index); void hide_tooltips() override; diff --git a/src/source_language_protocol.cc b/src/source_language_protocol.cc index 12c40b3..1b4e13c 100644 --- a/src/source_language_protocol.cc +++ b/src/source_language_protocol.cc @@ -3,8 +3,9 @@ #include "selection_dialog.h" #include "terminal.h" #include "project.h" +#include "filesystem.h" #include -#include +#include const bool output_messages_and_errors=false; @@ -42,22 +43,13 @@ std::shared_ptr LanguageProtocol::Client::get(const bo } LanguageProtocol::Client::~Client() { - std::condition_variable cv; - std::mutex cv_mutex; - bool cv_notified=false; - write_request("shutdown", "", [this, &cv, &cv_mutex, &cv_notified](const boost::property_tree::ptree &result, bool error) { + std::promise result_processed; + write_request("shutdown", "", [this, &result_processed](const boost::property_tree::ptree &result, bool error) { if(!error) this->write_notification("exit", ""); - std::unique_lock lock(cv_mutex); - cv_notified=true; - cv.notify_one(); + result_processed.set_value(); }); - - { - std::unique_lock lock(cv_mutex); - if(!cv_notified) - cv.wait(lock); - } + result_processed.get_future().get(); std::unique_lock lock(timeout_threads_mutex); for(auto &thread: timeout_threads) @@ -84,25 +76,18 @@ LanguageProtocol::Capabilities LanguageProtocol::Client::initialize(Source::Lang if(initialized) return capabilities; - std::condition_variable cv; - std::mutex cv_mutex; - bool cv_notified=false; - write_request("initialize", "\"processId\":"+std::to_string(process->get_id())+",\"rootUri\":\"file://"+root_uri+"\",\"capabilities\":{\"workspace\":{\"didChangeConfiguration\":{\"dynamicRegistration\":true},\"didChangeWatchedFiles\":{\"dynamicRegistration\":true},\"symbol\":{\"dynamicRegistration\":true},\"executeCommand\":{\"dynamicRegistration\":true}},\"textDocument\":{\"synchronization\":{\"dynamicRegistration\":true,\"willSave\":true,\"willSaveWaitUntil\":true,\"didSave\":true},\"completion\":{\"dynamicRegistration\":true,\"completionItem\":{\"snippetSupport\":true}},\"hover\":{\"dynamicRegistration\":true},\"signatureHelp\":{\"dynamicRegistration\":true},\"definition\":{\"dynamicRegistration\":true},\"references\":{\"dynamicRegistration\":true},\"documentHighlight\":{\"dynamicRegistration\":true},\"documentSymbol\":{\"dynamicRegistration\":true},\"codeAction\":{\"dynamicRegistration\":true},\"codeLens\":{\"dynamicRegistration\":true},\"formatting\":{\"dynamicRegistration\":true},\"rangeFormatting\":{\"dynamicRegistration\":true},\"onTypeFormatting\":{\"dynamicRegistration\":true},\"rename\":{\"dynamicRegistration\":true},\"documentLink\":{\"dynamicRegistration\":true}}},\"initializationOptions\":{\"omitInitBuild\":true},\"trace\":\"off\"", [this, &cv, &cv_mutex, &cv_notified](const boost::property_tree::ptree &result, bool error) { + std::promise result_processed; + write_request("initialize", "\"processId\":"+std::to_string(process->get_id())+",\"rootUri\":\"file://"+root_uri+"\",\"capabilities\":{\"workspace\":{\"didChangeConfiguration\":{\"dynamicRegistration\":true},\"didChangeWatchedFiles\":{\"dynamicRegistration\":true},\"symbol\":{\"dynamicRegistration\":true},\"executeCommand\":{\"dynamicRegistration\":true}},\"textDocument\":{\"synchronization\":{\"dynamicRegistration\":true,\"willSave\":true,\"willSaveWaitUntil\":true,\"didSave\":true},\"completion\":{\"dynamicRegistration\":true,\"completionItem\":{\"snippetSupport\":true}},\"hover\":{\"dynamicRegistration\":true},\"signatureHelp\":{\"dynamicRegistration\":true},\"definition\":{\"dynamicRegistration\":true},\"references\":{\"dynamicRegistration\":true},\"documentHighlight\":{\"dynamicRegistration\":true},\"documentSymbol\":{\"dynamicRegistration\":true},\"codeAction\":{\"dynamicRegistration\":true},\"codeLens\":{\"dynamicRegistration\":true},\"formatting\":{\"dynamicRegistration\":true},\"rangeFormatting\":{\"dynamicRegistration\":true},\"onTypeFormatting\":{\"dynamicRegistration\":true},\"rename\":{\"dynamicRegistration\":true},\"documentLink\":{\"dynamicRegistration\":true}}},\"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()) capabilities.text_document_sync=static_cast(capabilities_pt->second.get("textDocumentSync", 0)); write_notification("initialized", ""); } - std::unique_lock lock(cv_mutex); - cv_notified=true; - cv.notify_one(); + result_processed.set_value(); }); - { - std::unique_lock lock(cv_mutex); - if(!cv_notified) - cv.wait(lock); - } + result_processed.get_future().get(); + initialized=true; return capabilities; } @@ -161,6 +146,7 @@ void LanguageProtocol::Client::parse_server_message() { auto message_id=pt.get("id", 0); auto result_it=pt.find("result"); + auto error_it=pt.find("error"); { std::unique_lock lock(read_write_mutex); if(result_it!=pt.not_found()) { @@ -175,6 +161,18 @@ void LanguageProtocol::Client::parse_server_message() { } } } + else if(error_it!=pt.not_found()) { + if(message_id) { + auto id_it=handlers.find(message_id); + if(id_it!=handlers.end()) { + auto function=std::move(id_it->second); + lock.unlock(); + function(result_it->second, true); + lock.lock(); + handlers.erase(id_it->first); + } + } + } else { auto method_it=pt.find("method"); if(method_it!=pt.not_found()) { @@ -207,8 +205,9 @@ void LanguageProtocol::Client::write_request(const std::string &method, const st if(function) handlers.emplace(message_id, std::move(function)); { + auto message_id=this->message_id; std::unique_lock lock(timeout_threads_mutex); - timeout_threads.emplace_back([this] { + timeout_threads.emplace_back([this, message_id] { for(size_t c=0;c<20;++c) { std::this_thread::sleep_for(std::chrono::milliseconds(500)); std::unique_lock lock(read_write_mutex); @@ -280,6 +279,9 @@ Source::LanguageProtocolView::LanguageProtocolView(const boost::filesystem::path get_source_buffer()->set_highlight_syntax(true); parsed=true; + similar_symbol_tag=get_buffer()->create_tag(); + similar_symbol_tag->property_weight()=Pango::WEIGHT_ULTRAHEAVY; + capabilities=client->initialize(this); if(language_id=="rust") client->write_notification("workspace/didChangeConfiguration", "\"settings\":{\"rust\":{\"sysroot\":null,\"target\":null,\"rustflags\":null,\"clear_env_rust_log\":true,\"build_lib\":null,\"build_bin\":null,\"cfg_test\":false,\"unstable_features\":false,\"wait_to_build\":500,\"show_warnings\":true,\"goto_def_racer_fallback\":false,\"use_crate_blacklist\":true,\"build_on_save\":false,\"workspace_mode\":false,\"analyze_package\":null,\"features\":[],\"all_features\":false,\"no_default_features\":false}}"); @@ -287,45 +289,19 @@ Source::LanguageProtocolView::LanguageProtocolView(const boost::filesystem::path escape_text(text); client->write_notification("textDocument/didOpen", "\"textDocument\":{\"uri\":\""+uri+"\",\"languageId\":\""+language_id+"\",\"version\":"+std::to_string(document_version++)+",\"text\":\""+text+"\"}"); - format_style=[this](bool) { - client->write_request("textDocument/formatting", "\"textDocument\":{\"uri\":\""+uri+"\"},\"options\":{\"tabSize\":2,\"insertSpaces\":true}"); - }; + get_buffer()->signal_changed().connect([this] { + get_buffer()->remove_tag(similar_symbol_tag, get_buffer()->begin(), get_buffer()->end()); + }); - // Completion test - get_declaration_location=[this]() { - auto iter=get_buffer()->get_insert()->get_iter(); - std::condition_variable cv; - std::mutex cv_mutex; - bool cv_notified=false; - auto offset=std::make_shared(); - client->write_request("textDocument/definition", "\"textDocument\":{\"uri\":\""+uri+"\"}, \"position\": {\"line\": "+std::to_string(iter.get_line())+", \"character\": "+std::to_string(iter.get_line_offset())+"}", [&cv, &cv_mutex, &cv_notified, offset](const boost::property_tree::ptree &result, bool error) { - if(!error) { - for(auto it=result.begin();it!=result.end();++it) { - auto uri=it->second.get("uri", ""); - if(uri.compare(0, 7, "file://")==0) - uri.erase(0, 7); - auto range=it->second.find("range"); - if(range!=it->second.not_found()) { - auto start=range->second.find("start"); - if(start!=range->second.not_found()) - *offset=Offset(start->second.get("line", 0), start->second.get("character", 0), uri); - } - break; // TODO: can a language server return several definitions? - } - } - std::unique_lock lock(cv_mutex); - cv_notified=true; - cv.notify_one(); - }); - { - std::unique_lock lock(cv_mutex); - if(!cv_notified) - cv.wait(lock); + get_buffer()->signal_mark_set().connect([this](const Gtk::TextBuffer::iterator &iterator, const Glib::RefPtr &mark) { + if(mark->get_name() == "insert") { + delayed_tag_similar_symbols_connection.disconnect(); + delayed_tag_similar_symbols_connection=Glib::signal_timeout().connect([this]() { + tag_similar_symbols(); + return false; + }, 200); } - if(!*offset) - Info::get().print("No declaration found"); - return *offset; - }; + }); get_buffer()->signal_insert().connect([this](const Gtk::TextBuffer::iterator &start, const Glib::ustring &text_, int bytes) { std::string content_changes; @@ -358,6 +334,340 @@ Source::LanguageProtocolView::LanguageProtocolView(const boost::filesystem::path client->write_notification("textDocument/didChange", "\"textDocument\":{\"uri\":\""+uri+"\",\"version\":"+std::to_string(document_version++)+"},\"contentChanges\":["+content_changes+"]"); }, false); + setup_navigation_and_refactoring(); + setup_autocomplete(); +} + +Source::LanguageProtocolView::~LanguageProtocolView() { + delayed_tag_similar_symbols_connection.disconnect(); + + autocomplete.state=Autocomplete::State::IDLE; + if(autocomplete.thread.joinable()) + autocomplete.thread.join(); + + client->write_notification("textDocument/didClose", "\"textDocument\":{\"uri\":\""+uri+"\"}"); + client->close(this); + + client=nullptr; +} + +void Source::LanguageProtocolView::setup_navigation_and_refactoring() { + format_style=[this](bool continue_without_style_file) { + if(!continue_without_style_file) + return; + + class Replace { + public: + Offset start, end; + std::string text; + }; + std::vector replaces; + std::promise result_processed; + client->write_request("textDocument/formatting", "\"textDocument\":{\"uri\":\""+uri+"\"},\"options\":{\"tabSize\":2,\"insertSpaces\":true}", [&replaces, &result_processed](const boost::property_tree::ptree &result, bool error) { + if(!error) { + for(auto it=result.begin();it!=result.end();++it) { + auto range_it=it->second.find("range"); + auto text_it=it->second.find("newText"); + if(range_it!=it->second.not_found() && text_it!=it->second.not_found()) { + auto start_it=range_it->second.find("start"); + auto end_it=range_it->second.find("end"); + if(start_it!=range_it->second.not_found() && end_it!=range_it->second.not_found()) { + try { + replaces.emplace_back(Replace{Offset(start_it->second.get("line"), start_it->second.get("character")), + Offset(end_it->second.get("line"), end_it->second.get("character")), + text_it->second.get_value()}); + } + catch(...) {} + } + } + } + } + result_processed.set_value(); + }); + result_processed.get_future().get(); + + get_buffer()->begin_user_action(); + for(auto it=replaces.rbegin();it!=replaces.rend();++it) { + auto start=get_iter_at_line_pos(it->start.line, it->start.index); + auto end=get_iter_at_line_pos(it->end.line, it->end.index); + get_buffer()->erase(start, end); + start=get_iter_at_line_pos(it->start.line, it->start.index); + unescape_text(it->text); + get_buffer()->insert(start, it->text); + } + get_buffer()->end_user_action(); + }; + + get_declaration_location=[this]() { + auto iter=get_buffer()->get_insert()->get_iter(); + auto offset=std::make_shared(); + std::promise result_processed; + client->write_request("textDocument/definition", "\"textDocument\":{\"uri\":\""+uri+"\"}, \"position\": {\"line\": "+std::to_string(iter.get_line())+", \"character\": "+std::to_string(iter.get_line_offset())+"}", [offset, &result_processed](const boost::property_tree::ptree &result, bool error) { + if(!error) { + for(auto it=result.begin();it!=result.end();++it) { + auto uri=it->second.get("uri", ""); + if(uri.compare(0, 7, "file://")==0) + uri.erase(0, 7); + auto range=it->second.find("range"); + if(range!=it->second.not_found()) { + auto start=range->second.find("start"); + if(start!=range->second.not_found()) + *offset=Offset(start->second.get("line", 0), start->second.get("character", 0), uri); + } + break; // TODO: can a language server return several definitions? + } + } + result_processed.set_value(); + }); + result_processed.get_future().get(); + + if(!*offset) + Info::get().print("No declaration found"); + return *offset; + }; + + get_usages=[this] { + auto iter=get_buffer()->get_insert()->get_iter(); + std::vector> usages; + std::vector end_offsets; + std::promise result_processed; + client->write_request("textDocument/references", "\"textDocument\":{\"uri\":\""+uri+"\"}, \"position\": {\"line\": "+std::to_string(iter.get_line())+", \"character\": "+std::to_string(iter.get_line_offset())+"}, \"context\": {\"includeDeclaration\": true}", [&usages, &end_offsets, &result_processed](const boost::property_tree::ptree &result, bool error) { + if(!error) { + try { + for(auto it=result.begin();it!=result.end();++it) { + auto path=it->second.get("uri", ""); + if(path.size()>=7) { + path.erase(0, 7); + auto range_it=it->second.find("range"); + if(range_it!=it->second.not_found()) { + auto start_it=range_it->second.find("start"); + auto end_it=range_it->second.find("end"); + if(start_it!=range_it->second.not_found() && end_it!=range_it->second.not_found()) { + usages.emplace_back(std::make_pair(Offset(start_it->second.get("line"), start_it->second.get("character"), path), "")); + end_offsets.emplace_back(end_it->second.get("line"), end_it->second.get("character")); + } + } + } + } + } + catch(...) { + usages.clear(); + end_offsets.clear(); + } + } + result_processed.set_value(); + }); + result_processed.get_future().get(); + + auto embolden_token=[](std::string &line_, unsigned token_start_pos, unsigned token_end_pos) { + Glib::ustring line=line_; + if(token_start_pos>=line.size() || token_end_pos>=line.size()) + return; + + //markup token as bold + size_t pos=0; + while((pos=line.find('&', pos))!=Glib::ustring::npos) { + size_t pos2=line.find(';', pos+2); + if(token_start_pos>pos) { + token_start_pos+=pos2-pos; + token_end_pos+=pos2-pos; + } + else if(token_end_pos>pos) + token_end_pos+=pos2-pos; + else + break; + pos=pos2+1; + } + line.insert(token_end_pos, ""); + line.insert(token_start_pos, ""); + + size_t start_pos=0; + while(start_pos0) + line.erase(0, start_pos); + + line_=line.raw(); + }; + + std::map> file_lines; + size_t c=static_cast(-1); + for(auto &usage: usages) { + ++c; + auto view_it=views.end(); + for(auto it=views.begin();it!=views.end();++it) { + if(usage.first.file_path==(*it)->file_path) { + view_it=it; + break; + } + } + if(view_it!=views.end()) { + if(usage.first.line((*view_it)->get_buffer()->get_line_count())) { + auto start=(*view_it)->get_buffer()->get_iter_at_line(usage.first.line); + auto end=start; + end.forward_to_line_end(); + usage.second=(*view_it)->get_buffer()->get_text(start, end); + embolden_token(usage.second, usage.first.index, end_offsets[c].index); + } + } + else { + auto it=file_lines.find(usage.first.file_path); + if(it==file_lines.end()) { + std::ifstream ifs(usage.first.file_path.string()); + if(ifs) { + std::vector lines; + std::string line; + while(std::getline(ifs, line)) { + if(!line.empty() && line.back()=='\r') + line.pop_back(); + lines.emplace_back(line); + } + auto pair=file_lines.emplace(usage.first.file_path, lines); + it=pair.first; + } + else { + auto pair=file_lines.emplace(usage.first.file_path, std::vector()); + it=pair.first; + } + } + + if(usage.first.linesecond.size()) { + usage.second=it->second[usage.first.line]; + embolden_token(usage.second, usage.first.index, end_offsets[c].index); + } + } + } + + if(usages.empty()) + Info::get().print("No symbol found at current cursor location"); + + return usages; + }; + + get_token_spelling=[this]() -> std::string { + auto start=get_buffer()->get_insert()->get_iter(); + auto end=start; + auto previous=start; + while(previous.backward_char() && ((*previous>='A' && *previous<='Z') || (*previous>='a' && *previous<='z') || (*previous>='0' && *previous<='9') || *previous=='_') && start.backward_char()) {} + while(((*end>='A' && *end<='Z') || (*end>='a' && *end<='z') || (*end>='0' && *end<='9') || *end=='_') && end.forward_char()) {} + + if(start==end) { + Info::get().print("No valid symbol found at current cursor location"); + return ""; + } + + return get_buffer()->get_text(start, end); + }; + + rename_similar_tokens=[this](const std::string &text) { + class Usages { + public: + boost::filesystem::path path; + std::vector> offsets; + }; + + auto previous_text=get_token_spelling(); + if(previous_text.empty()) + return; + + auto iter=get_buffer()->get_insert()->get_iter(); + std::vector usages; + std::promise result_processed; + client->write_request("textDocument/rename", "\"textDocument\":{\"uri\":\""+uri+"\"}, \"position\": {\"line\": "+std::to_string(iter.get_line())+", \"character\": "+std::to_string(iter.get_line_offset())+"}, \"newName\": \""+text+"\"", [&usages, &result_processed](const boost::property_tree::ptree &result, bool error) { + if(!error) { + try { + auto changes_it=result.find("changes"); + if(changes_it!=result.not_found()) { + for(auto file_it=changes_it->second.begin();file_it!=changes_it->second.end();++file_it) { + auto path=file_it->first; + if(path.size()>=7) { + path.erase(0, 7); + usages.emplace_back(Usages{path, std::vector>()}); + for(auto edit_it=file_it->second.begin();edit_it!=file_it->second.end();++edit_it) { + auto range_it=edit_it->second.find("range"); + if(range_it!=edit_it->second.not_found()) { + auto start_it=range_it->second.find("start"); + auto end_it=range_it->second.find("end"); + if(start_it!=range_it->second.not_found() && end_it!=range_it->second.not_found()) + usages.back().offsets.emplace_back(std::make_pair(Offset(start_it->second.get("line"), start_it->second.get("character")), + Offset(end_it->second.get("line"), end_it->second.get("character")))); + } + } + } + } + } + } + catch(...) { + usages.clear(); + } + } + result_processed.set_value(); + }); + result_processed.get_future().get(); + + std::vector usages_renamed; + for(auto &usage: usages) { + auto view_it=views.end(); + for(auto it=views.begin();it!=views.end();++it) { + if((*it)->file_path==usage.path) { + view_it=it; + break; + } + } + if(view_it!=views.end()) { + (*view_it)->get_buffer()->begin_user_action(); + for(auto offset_it=usage.offsets.rbegin();offset_it!=usage.offsets.rend();++offset_it) { + auto start_iter=(*view_it)->get_iter_at_line_pos(offset_it->first.line, offset_it->first.index); + auto end_iter=(*view_it)->get_iter_at_line_pos(offset_it->second.line, offset_it->second.index); + (*view_it)->get_buffer()->erase(start_iter, end_iter); + start_iter=(*view_it)->get_iter_at_line_pos(offset_it->first.line, offset_it->first.index); + (*view_it)->get_buffer()->insert(start_iter, text); + } + (*view_it)->get_buffer()->end_user_action(); + (*view_it)->save(); + usages_renamed.emplace_back(&usage); + } + else { + Glib::ustring buffer; + { + std::ifstream stream(usage.path.string(), std::ifstream::binary); + if(stream) + buffer.assign(std::istreambuf_iterator(stream), std::istreambuf_iterator()); + } + std::ofstream stream(usage.path.string(), std::ifstream::binary); + if(!buffer.empty() && stream) { + std::vector lines_start_pos={0}; + for(size_t c=0;cfirst.line; + auto end_line=offset_it->second.line; + if(start_linefirst.index; + auto end=lines_start_pos[end_line]+offset_it->second.index; + if(startget_insert()->get_iter().get_offset(); for(auto offset: diagnostic_offsets) { @@ -375,19 +685,6 @@ Source::LanguageProtocolView::LanguageProtocolView(const boost::filesystem::path scroll_to(get_buffer()->get_insert(), 0.0, 1.0, 0.5); } }; - - setup_autocomplete(); -} - -Source::LanguageProtocolView::~LanguageProtocolView() { - autocomplete.state=Autocomplete::State::IDLE; - if(autocomplete.thread.joinable()) - autocomplete.thread.join(); - - client->write_notification("textDocument/didClose", "\"textDocument\":{\"uri\":\""+uri+"\"}"); - client->close(this); - - client=nullptr; } void Source::LanguageProtocolView::escape_text(std::string &text) { @@ -407,6 +704,19 @@ void Source::LanguageProtocolView::escape_text(std::string &text) { } } +void Source::LanguageProtocolView::unescape_text(std::string &text) { + for(size_t c=0;c &&diagnostics) { dispatcher.post([this, diagnostics=std::move(diagnostics)] { diagnostic_offsets.clear(); @@ -417,27 +727,9 @@ void Source::LanguageProtocolView::update_diagnostics(std::vector=get_buffer()->get_line_count()) - line=get_buffer()->get_line_count()-1; - auto start=get_iter_at_line_end(line); - int index=diagnostic.offsets.first.index; - if(index>=0 && indexget_iter_at_line_offset(line, index); - if(start.ends_line()) { - while(!start.is_start() && start.ends_line()) - start.backward_char(); - } - diagnostic_offsets.emplace(start.get_offset()); - - line=diagnostic.offsets.second.line; - if(line<0 || line>=get_buffer()->get_line_count()) - line=get_buffer()->get_line_count(); - auto end=get_iter_at_line_end(line); - index=diagnostic.offsets.second.index; - if(index>=0 && indexget_iter_at_line_offset(line, index); + if(diagnostic.uri==uri) { + auto start=get_iter_at_line_pos(diagnostic.offsets.first.line, diagnostic.offsets.first.index); + auto end=get_iter_at_line_pos(diagnostic.offsets.second.line, diagnostic.offsets.second.index); if(start==end) { if(!end.is_end()) @@ -446,6 +738,8 @@ void Source::LanguageProtocolView::update_diagnostics(std::vector=2) { @@ -484,6 +778,17 @@ void Source::LanguageProtocolView::update_diagnostics(std::vectorget_line_count()-1); + if(line<0) + line=0; + auto iter=get_iter_at_line_end(line); + pos=std::min(pos, iter.get_line_offset()); + if(pos<0) + pos=0; + return get_buffer()->get_iter_at_line_offset(line, pos); +} + void Source::LanguageProtocolView::show_diagnostic_tooltips(const Gdk::Rectangle &rectangle) { diagnostic_tooltips.show(rectangle); } @@ -518,11 +823,10 @@ void Source::LanguageProtocolView::show_type_tooltips(const Gdk::Rectangle &rect return tooltip_buffer; }; - auto iter=get_buffer()->get_iter_at_offset(offset); - auto start=iter; - auto end=iter; - while(((*start>='A' && *start<='Z') || (*start>='a' && *start<='z') || (*start>='0' && *start<='9') || *start=='_') && start.backward_char()) {} - start.forward_char(); + auto start=get_buffer()->get_iter_at_offset(offset); + auto end=start; + auto previous=start; + while(previous.backward_char() && ((*previous>='A' && *previous<='Z') || (*previous>='a' && *previous<='z') || (*previous>='0' && *previous<='9') || *previous=='_') && start.backward_char()) {} while(((*end>='A' && *end<='Z') || (*end>='a' && *end<='z') || (*end>='0' && *end<='9') || *end=='_') && end.forward_char()) {} type_tooltips.emplace_back(create_tooltip_buffer, this, get_buffer()->create_mark(start), get_buffer()->create_mark(end)); type_tooltips.show(); @@ -533,6 +837,41 @@ void Source::LanguageProtocolView::show_type_tooltips(const Gdk::Rectangle &rect }); } +void Source::LanguageProtocolView::tag_similar_symbols() { + auto iter=get_buffer()->get_insert()->get_iter(); + std::vector> offsets; + std::promise result_processed; + client->write_request("textDocument/references", "\"textDocument\":{\"uri\":\""+uri+"\"}, \"position\": {\"line\": "+std::to_string(iter.get_line())+", \"character\": "+std::to_string(iter.get_line_offset())+"}, \"context\": {\"includeDeclaration\": true}", [this, &result_processed, &offsets](const boost::property_tree::ptree &result, bool error) { + if(!error) { + for(auto it=result.begin();it!=result.end();++it) { + if(it->second.get("uri", "")==uri) { + auto range_it=it->second.find("range"); + if(range_it!=it->second.not_found()) { + auto start_it=range_it->second.find("start"); + auto end_it=range_it->second.find("end"); + if(start_it!=range_it->second.not_found() && end_it!=range_it->second.not_found()) { + try { + offsets.emplace_back(std::make_pair(Offset(start_it->second.get("line"), start_it->second.get("character")), + Offset(end_it->second.get("line"), end_it->second.get("character")))); + } + catch(...) {} + } + } + } + } + } + result_processed.set_value(); + }); + result_processed.get_future().get(); + + get_buffer()->remove_tag(similar_symbol_tag, get_buffer()->begin(), get_buffer()->end()); + for(auto &pair: offsets) { + auto start=get_iter_at_line_pos(pair.first.line, pair.first.index); + auto end=get_iter_at_line_pos(pair.second.line, pair.second.index); + get_buffer()->apply_tag(similar_symbol_tag, start, end); + } +} + void Source::LanguageProtocolView::setup_autocomplete() { non_interactive_completion=[this] { if(CompletionDialog::get() && CompletionDialog::get()->is_visible()) @@ -630,10 +969,8 @@ void Source::LanguageProtocolView::setup_autocomplete() { if(autocomplete.state==Autocomplete::State::STARTING) { autocomplete_comment.clear(); autocomplete_insert.clear(); - std::condition_variable cv; - std::mutex cv_mutex; - bool cv_notified=false; - client->write_request("textDocument/completion", "\"textDocument\":{\"uri\":\""+uri+"\"}, \"position\": {\"line\": "+std::to_string(line_number-1)+", \"character\": "+std::to_string(column-1)+"}", [this, &cv, &cv_mutex, &cv_notified](const boost::property_tree::ptree &result, bool error) { + std::promise result_processed; + client->write_request("textDocument/completion", "\"textDocument\":{\"uri\":\""+uri+"\"}, \"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(); @@ -653,13 +990,9 @@ void Source::LanguageProtocolView::setup_autocomplete() { } } } - std::unique_lock lock(cv_mutex); - cv_notified=true; - cv.notify_one(); + result_processed.set_value(); }); - std::unique_lock lock(cv_mutex); - if(!cv_notified) - cv.wait(lock); + result_processed.get_future().get(); } }; diff --git a/src/source_language_protocol.h b/src/source_language_protocol.h index 5e33097..063bb78 100644 --- a/src/source_language_protocol.h +++ b/src/source_language_protocol.h @@ -78,6 +78,8 @@ namespace Source { std::string uri; void update_diagnostics(std::vector &&diagnostics); + + Gtk::TextIter get_iter_at_line_pos(int line, int pos) override; protected: void show_diagnostic_tooltips(const Gdk::Rectangle &rectangle) override; @@ -93,9 +95,16 @@ namespace Source { Dispatcher dispatcher; + void setup_navigation_and_refactoring(); + void escape_text(std::string &text); + void unescape_text(std::string &text); std::set diagnostic_offsets; + + Glib::RefPtr similar_symbol_tag; + sigc::connection delayed_tag_similar_symbols_connection; + void tag_similar_symbols(); Autocomplete autocomplete; void setup_autocomplete(); diff --git a/src/window.cc b/src/window.cc index a837c54..fb6fbbc 100644 --- a/src/window.cc +++ b/src/window.cc @@ -756,9 +756,9 @@ void Window::set_menu_actions() { return; Notebook::get().open(offset.file_path); auto view=Notebook::get().get_current_view(); - auto iter=view->get_buffer()->get_iter_at_line_index(offset.line, offset.index); + auto iter=view->get_iter_at_line_pos(offset.line, offset.index); view->get_buffer()->insert(iter, std::get<1>(documentation_template)); - iter=view->get_buffer()->get_iter_at_line_index(offset.line, offset.index); + iter=view->get_iter_at_line_pos(offset.line, offset.index); iter.forward_chars(std::get<2>(documentation_template)); view->get_buffer()->place_cursor(iter); view->scroll_to_cursor_delayed(view, true, false); @@ -826,7 +826,7 @@ void Window::set_menu_actions() { auto view=Notebook::get().get_current_view(); auto line=static_cast(location.line); auto index=static_cast(location.index); - view->place_cursor_at_line_index(line, index); + view->place_cursor_at_line_pos(line, index); view->scroll_to_cursor_delayed(view, true, false); } } @@ -843,7 +843,7 @@ void Window::set_menu_actions() { auto view=Notebook::get().get_current_view(); auto line=static_cast(location.line); auto index=static_cast(location.index); - view->place_cursor_at_line_index(line, index); + view->place_cursor_at_line_pos(line, index); view->scroll_to_cursor_delayed(view, true, false); } } @@ -880,7 +880,7 @@ void Window::set_menu_actions() { auto view=Notebook::get().get_current_view(); auto line=static_cast(location.line); auto index=static_cast(location.index); - view->place_cursor_at_line_index(line, index); + view->place_cursor_at_line_pos(line, index); view->scroll_to_cursor_delayed(view, true, false); return; } @@ -892,7 +892,7 @@ void Window::set_menu_actions() { return; Notebook::get().open(location.file_path); auto view=Notebook::get().get_current_view(); - view->place_cursor_at_line_index(location.line, location.index); + view->place_cursor_at_line_pos(location.line, location.index); view->scroll_to_cursor_delayed(view, true, false); view->hide_tooltips(); }; @@ -952,7 +952,7 @@ void Window::set_menu_actions() { return; Notebook::get().open(offset.file_path); auto view=Notebook::get().get_current_view(); - view->place_cursor_at_line_index(offset.line, offset.index); + view->place_cursor_at_line_pos(offset.line, offset.index); view->scroll_to_cursor_delayed(view, true, false); view->hide_tooltips(); }; @@ -981,7 +981,7 @@ void Window::set_menu_actions() { if(index>=rows.size()) return; auto offset=rows[index]; - view->get_buffer()->place_cursor(view->get_buffer()->get_iter_at_line_index(offset.line, offset.index)); + view->get_buffer()->place_cursor(view->get_iter_at_line_pos(offset.line, offset.index)); view->scroll_to(view->get_buffer()->get_insert(), 0.0, 1.0, 0.5); view->hide_tooltips(); }; @@ -1037,8 +1037,8 @@ void Window::set_menu_actions() { auto fix_its=view->get_fix_its(); std::vector, Glib::RefPtr > > fix_it_marks; for(auto &fix_it: fix_its) { - auto start_iter=buffer->get_iter_at_line_index(fix_it.offsets.first.line, fix_it.offsets.first.index); - auto end_iter=buffer->get_iter_at_line_index(fix_it.offsets.second.line, fix_it.offsets.second.index); + auto start_iter=view->get_iter_at_line_pos(fix_it.offsets.first.line, fix_it.offsets.first.index); + auto end_iter=view->get_iter_at_line_pos(fix_it.offsets.second.line, fix_it.offsets.second.index); fix_it_marks.emplace_back(buffer->create_mark(start_iter), buffer->create_mark(end_iter)); } size_t c=0;