From d534f070b6401a37a0e78a9f06aa224dec7e7faa Mon Sep 17 00:00:00 2001 From: eidheim Date: Sun, 21 Jan 2018 18:00:33 +0100 Subject: [PATCH] Language server protocol: added support for workspace/symbol --- CMakeLists.txt | 2 +- src/files.h | 2 +- src/menu.cc | 4 +- src/project.cc | 184 +++++++++++++++++++++++++++++++- src/project.h | 30 ++++-- src/selection_dialog.cc | 14 +++ src/selection_dialog.h | 3 + src/source_language_protocol.cc | 7 +- src/source_language_protocol.h | 1 + src/window.cc | 67 +----------- 10 files changed, 234 insertions(+), 80 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d0b6572..5185456 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required (VERSION 2.8.8) project(juci) -set(JUCI_VERSION "1.4.0") +set(JUCI_VERSION "1.4.0.1") set(CPACK_PACKAGE_NAME "jucipp") set(CPACK_PACKAGE_CONTACT "Ole Christian Eidheim ") diff --git a/src/files.h b/src/files.h index ab7d9e8..a78253c 100644 --- a/src/files.h +++ b/src/files.h @@ -118,7 +118,7 @@ R"RAW( "source_show_completion_comment" : "Add completion keybinding to disable interactive autocompletion", "source_show_completion" : "", "source_find_file": "p", - "source_find_symbol_ctags": "f", + "source_find_symbol": "f", "source_comments_toggle": "slash", "source_comments_add_documentation": "slash", "source_find_documentation": "d", diff --git a/src/menu.cc b/src/menu.cc index e328676..f9bb0f3 100644 --- a/src/menu.cc +++ b/src/menu.cc @@ -271,8 +271,8 @@ const Glib::ustring menu_xml= R"RAW( app.source_find_file - _Find _Symbol (Ctags) - app.source_find_symbol_ctags + _Find _Symbol + app.source_find_symbol
diff --git a/src/project.cc b/src/project.cc index ca00b09..d04d84e 100644 --- a/src/project.cc +++ b/src/project.cc @@ -12,7 +12,10 @@ #endif #include "info.h" #include "source_clang.h" +#include "source_language_protocol.h" #include "usages_clang.h" +#include "ctags.h" +#include boost::filesystem::path Project::debug_last_stop_file_path; std::unordered_map Project::run_arguments; @@ -155,6 +158,8 @@ std::shared_ptr Project::create() { return std::shared_ptr(new Project::Clang(std::move(build))); else if(dynamic_cast(build.get())) return std::shared_ptr(new Project::Rust(std::move(build))); + else if(dynamic_cast(build.get())) + return std::shared_ptr(new Project::JavaScript(std::move(build))); else return std::shared_ptr(new Project::Base(std::move(build))); } @@ -176,6 +181,71 @@ void Project::Base::recreate_build() { Info::get().print("Could not find a supported project"); } +void Project::Base::show_symbols() { + auto view=Notebook::get().get_current_view(); + + boost::filesystem::path search_path; + if(view) + search_path=view->file_path.parent_path(); + else if(!Directories::get().path.empty()) + search_path=Directories::get().path; + else { + boost::system::error_code ec; + search_path=boost::filesystem::current_path(ec); + if(ec) { + Terminal::get().print("Error: could not find current path\n", true); + return; + } + } + auto pair=Ctags::get_result(search_path); + + auto path=std::move(pair.first); + auto stream=std::move(pair.second); + stream->seekg(0, std::ios::end); + if(stream->tellg()==0) { + Info::get().print("No symbols found in current project"); + return; + } + stream->seekg(0, std::ios::beg); + + if(view) { + auto dialog_iter=view->get_iter_for_dialog(); + SelectionDialog::create(view, view->get_buffer()->create_mark(dialog_iter), true, true); + } + else + SelectionDialog::create(true, true); + + std::vector rows; + + std::string line; + while(std::getline(*stream, line)) { + auto location=Ctags::get_location(line, true); + + std::string row=location.file_path.string()+":"+std::to_string(location.line+1)+": "+location.source; + rows.emplace_back(Source::Offset(location.line, location.index, location.file_path)); + SelectionDialog::get()->add_row(row); + } + + if(rows.size()==0) + return; + SelectionDialog::get()->on_select=[rows=std::move(rows), path=std::move(path)](unsigned int index, const std::string &text, bool hide_window) { + if(index>=rows.size()) + return; + auto offset=rows[index]; + auto full_path=path/offset.file_path; + if(!boost::filesystem::is_regular_file(full_path)) + return; + Notebook::get().open(full_path); + auto view=Notebook::get().get_current_view(); + view->place_cursor_at_line_index(offset.line, offset.index); + view->scroll_to_cursor_delayed(view, true, false); + view->hide_tooltips(); + }; + if(view) + view->hide_tooltips(); + SelectionDialog::get()->show(); +} + std::pair Project::Base::debug_get_run_arguments() { Info::get().print("Could not find a supported project"); return {"", ""}; @@ -587,6 +657,102 @@ void Project::LLDB::debug_cancel() { } #endif +void Project::LanguageProtocol::show_symbols() { + if(build->project_path.empty()) { + Info::get().print("Could not find project folder"); + return; + } + + auto language_id=get_language_id(); + auto executable_name=language_id+"-language-server"; + if(filesystem::find_executable(executable_name).empty()) { + Info::get().print("Executable "+executable_name+" not found"); + return; + } + + auto project_path=std::make_shared(build->project_path); + + auto client=::LanguageProtocol::Client::get(*project_path, language_id); + auto capabilities=client->initialize(nullptr); + + if(!capabilities.workspace_symbol) { + Info::get().print("Language server does not support workspace/symbol"); + return; + } + + auto view=Notebook::get().get_current_view(); + if(view) { + auto dialog_iter=view->get_iter_for_dialog(); + SelectionDialog::create(view, view->get_buffer()->create_mark(dialog_iter), true, true); + } + else + SelectionDialog::create(true, true); + + SelectionDialog::get()->on_hide=[] { + SelectionDialog::get()->on_search_entry_changed=nullptr; // To delete client object + }; + + auto offsets=std::make_shared>(); + SelectionDialog::get()->on_search_entry_changed=[client, project_path, offsets](const std::string &text) { + if(text.size()>1) + return; + else { + offsets->clear(); + SelectionDialog::get()->erase_rows(); + if(text.empty()) + return; + } + std::vector names; + std::promise result_processed; + client->write_request("workspace/symbol", "\"query\":\""+text+"\"", [&result_processed, &names, offsets, project_path](const boost::property_tree::ptree &result, bool error) { + if(!error) { + for(auto it=result.begin();it!=result.end();++it) { + auto name=it->second.get("name", ""); + if(!name.empty()) { + auto location_it=it->second.find("location"); + if(location_it!=it->second.not_found()) { + auto file=location_it->second.get("uri", ""); + if(file.size()>7) { + file.erase(0, 7); + auto range_it=location_it->second.find("range"); + if(range_it!=location_it->second.not_found()) { + auto start_it=range_it->second.find("start"); + if(start_it!=range_it->second.not_found()) { + try { + offsets->emplace_back(Source::Offset(start_it->second.get("line"), start_it->second.get("character"), file)); + names.emplace_back(name); + } + catch(...) {} + } + } + } + } + } + } + } + result_processed.set_value(); + }); + result_processed.get_future().get(); + for(size_t c=0;csize() && cadd_row(filesystem::get_relative_path((*offsets)[c].file_path, *project_path).string()+':'+std::to_string((*offsets)[c].line+1)+':'+std::to_string((*offsets)[c].index+1)+": "+names[c]); + }; + + SelectionDialog::get()->on_select=[offsets](unsigned int index, const std::string &text, bool hide_window) { + auto &offset=(*offsets)[index]; + if(!boost::filesystem::is_regular_file(offset.file_path)) + return; + Notebook::get().open(offset.file_path); + auto view=Notebook::get().get_current_view(); + view->place_cursor_at_line_offset(offset.line, offset.index); + view->scroll_to_cursor_delayed(view, true, false); + view->hide_tooltips(); + }; + + if(view) + view->hide_tooltips(); + SelectionDialog::get()->show(); +} + std::pair Project::Clang::get_run_arguments() { auto build_path=build->get_default_path(); if(build_path.empty()) @@ -755,9 +921,23 @@ void Project::Python::compile_and_run() { } void Project::JavaScript::compile_and_run() { - auto command="node --harmony "+filesystem::escape_argument(filesystem::get_short_path(Notebook::get().get_current_view()->file_path).string()); + std::string command; + boost::filesystem::path path; + if(!build->project_path.empty()) { + command="npm start"; + path=build->project_path; + } + else { + auto view=Notebook::get().get_current_view(); + if(!view) { + Info::get().print("No executable found"); + return; + } + command="node --harmony "+filesystem::escape_argument(filesystem::get_short_path(view->file_path).string()); + path=view->file_path.parent_path(); + } Terminal::get().print("Running "+command+"\n"); - Terminal::get().async_process(command, Notebook::get().get_current_view()->file_path.parent_path(), [command](int exit_status) { + Terminal::get().async_process(command, path, [command](int exit_status) { Terminal::get().async_print(command+" returned: "+std::to_string(exit_status)+'\n'); }); } diff --git a/src/project.h b/src/project.h index aeffc4a..0bfca6b 100644 --- a/src/project.h +++ b/src/project.h @@ -35,6 +35,7 @@ namespace Project { }; #endif public: + Base() {} Base(std::unique_ptr &&build): build(std::move(build)) {} virtual ~Base() {} @@ -47,6 +48,8 @@ namespace Project { virtual void compile_and_run(); virtual void recreate_build(); + virtual void show_symbols(); + virtual std::pair debug_get_run_arguments(); virtual Gtk::Popover *debug_get_options() { return nullptr; } Tooltips debug_variable_tooltips; @@ -67,7 +70,7 @@ namespace Project { virtual void debug_cancel() {} }; - class LLDB : public Base { + class LLDB : public virtual Base { #ifdef JUCI_ENABLE_DEBUG class DebugOptions : public Base::DebugOptions { public: @@ -79,7 +82,7 @@ namespace Project { #endif public: - LLDB(std::unique_ptr &&build) : Base(std::move(build)) {} + LLDB() {} ~LLDB() { dispatcher.disconnect(); } #ifdef JUCI_ENABLE_DEBUG @@ -103,9 +106,16 @@ namespace Project { #endif }; + class LanguageProtocol : public virtual Base { + public: + LanguageProtocol() {} + virtual std::string get_language_id()=0; + void show_symbols() override; + }; + class Clang : public LLDB { public: - Clang(std::unique_ptr &&build) : LLDB(std::move(build)) {} + Clang(std::unique_ptr &&build) : Base(std::move(build)) {} std::pair get_run_arguments() override; void compile() override; @@ -122,18 +132,22 @@ namespace Project { void compile_and_run() override; }; - class Python : public Base { + class Python : public LanguageProtocol { public: Python(std::unique_ptr &&build) : Base(std::move(build)) {} void compile_and_run() override; + + std::string get_language_id() override { return "python"; } }; - class JavaScript : public Base { + class JavaScript : public LanguageProtocol { public: JavaScript(std::unique_ptr &&build) : Base(std::move(build)) {} void compile_and_run() override; + + std::string get_language_id() override { return "javascript"; } }; class HTML : public Base { @@ -143,12 +157,14 @@ namespace Project { void compile_and_run() override; }; - class Rust : public LLDB { + class Rust : public LLDB, public LanguageProtocol { public: - Rust(std::unique_ptr &&build) : LLDB(std::move(build)) {} + Rust(std::unique_ptr &&build) : Base(std::move(build)) {} void compile() override; void compile_and_run() override; + + std::string get_language_id() override { return "rust"; } }; std::shared_ptr create(); diff --git a/src/selection_dialog.cc b/src/selection_dialog.cc index 811f6d0..2f2abbb 100644 --- a/src/selection_dialog.cc +++ b/src/selection_dialog.cc @@ -25,6 +25,11 @@ void SelectionDialogBase::ListViewText::append(const std::string& value) { new_row->set_value(column_record.index, size++); } +void SelectionDialogBase::ListViewText::erase_rows() { + list_store->clear(); + size=0; +} + void SelectionDialogBase::ListViewText::clear() { unset_model(); list_store.reset(); @@ -40,6 +45,11 @@ SelectionDialogBase::SelectionDialogBase(Gtk::TextView *text_view, Glib::RefPtr< window.set_type_hint(Gdk::WindowTypeHint::WINDOW_TYPE_HINT_COMBO); + search_entry.signal_changed().connect([this] { + if(on_search_entry_changed) + on_search_entry_changed(search_entry.get_text()); + }, false); + list_view_text.set_search_entry(search_entry); window.set_default_size(0, 0); @@ -135,6 +145,10 @@ void SelectionDialogBase::add_row(const std::string& row) { list_view_text.append(row); } +void SelectionDialogBase::erase_rows() { + list_view_text.erase_rows(); +} + void SelectionDialogBase::show() { window.show_all(); if(text_view) diff --git a/src/selection_dialog.h b/src/selection_dialog.h index 457fd37..49d7113 100644 --- a/src/selection_dialog.h +++ b/src/selection_dialog.h @@ -18,6 +18,7 @@ class SelectionDialogBase { ColumnRecord column_record; ListViewText(bool use_markup); void append(const std::string& value); + void erase_rows(); void clear(); private: Glib::RefPtr list_store; @@ -35,6 +36,7 @@ public: SelectionDialogBase(Gtk::TextView *text_view, Glib::RefPtr start_mark, bool show_search_entry, bool use_markup); virtual ~SelectionDialogBase(); void add_row(const std::string& row); + void erase_rows(); void set_cursor_at_last_row(); void show(); void hide(); @@ -46,6 +48,7 @@ public: std::function on_hide; std::function on_select; std::function on_changed; + std::function on_search_entry_changed; Glib::RefPtr start_mark; protected: diff --git a/src/source_language_protocol.cc b/src/source_language_protocol.cc index 2c0ba88..e938eb9 100644 --- a/src/source_language_protocol.cc +++ b/src/source_language_protocol.cc @@ -70,7 +70,7 @@ LanguageProtocol::Client::~Client() { } LanguageProtocol::Capabilities LanguageProtocol::Client::initialize(Source::LanguageProtocolView *view) { - { + if(view) { std::unique_lock lock(views_mutex); views.emplace(view); } @@ -85,9 +85,12 @@ LanguageProtocol::Capabilities LanguageProtocol::Client::initialize(Source::Lang if(capabilities_pt!=result.not_found()) { capabilities.text_document_sync=static_cast(capabilities_pt->second.get("textDocumentSync", 0)); capabilities.document_highlight=capabilities_pt->second.get("documentHighlightProvider", false); + capabilities.workspace_symbol=capabilities_pt->second.get("workspaceSymbolProvider", false); } write_notification("initialized", ""); + if(language_id=="rust") + 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}}"); } result_processed.set_value(); }); @@ -299,8 +302,6 @@ Source::LanguageProtocolView::LanguageProtocolView(const boost::filesystem::path 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}}"); std::string text=get_buffer()->get_text(); escape_text(text); client->write_notification("textDocument/didOpen", "\"textDocument\":{\"uri\":\""+uri+"\",\"languageId\":\""+language_id+"\",\"version\":"+std::to_string(document_version++)+",\"text\":\""+text+"\"}"); diff --git a/src/source_language_protocol.h b/src/source_language_protocol.h index 52d9b92..5358215 100644 --- a/src/source_language_protocol.h +++ b/src/source_language_protocol.h @@ -29,6 +29,7 @@ namespace LanguageProtocol { INCREMENTAL }; TextDocumentSync text_document_sync; bool document_highlight; + bool workspace_symbol; }; class Client { diff --git a/src/window.cc b/src/window.cc index fb6fbbc..bca1863 100644 --- a/src/window.cc +++ b/src/window.cc @@ -8,7 +8,6 @@ #include "project.h" #include "entrybox.h" #include "info.h" -#include "ctags.h" #include "selection_dialog.h" #include "terminal.h" @@ -600,69 +599,9 @@ void Window::set_menu_actions() { } }); - menu.add_action("source_find_symbol_ctags", [this]() { - auto view=Notebook::get().get_current_view(); - - boost::filesystem::path search_path; - if(view) - search_path=view->file_path.parent_path(); - else if(!Directories::get().path.empty()) - search_path=Directories::get().path; - else { - boost::system::error_code ec; - search_path=boost::filesystem::current_path(ec); - if(ec) { - Terminal::get().print("Error: could not find current path\n", true); - return; - } - } - auto pair=Ctags::get_result(search_path); - - auto path=std::move(pair.first); - auto stream=std::move(pair.second); - stream->seekg(0, std::ios::end); - if(stream->tellg()==0) { - Info::get().print("No symbols found in current project"); - return; - } - stream->seekg(0, std::ios::beg); - - if(view) { - auto dialog_iter=view->get_iter_for_dialog(); - SelectionDialog::create(view, view->get_buffer()->create_mark(dialog_iter), true, true); - } - else - SelectionDialog::create(true, true); - - std::vector rows; - - std::string line; - while(std::getline(*stream, line)) { - auto location=Ctags::get_location(line, true); - - std::string row=location.file_path.string()+":"+std::to_string(location.line+1)+": "+location.source; - rows.emplace_back(Source::Offset(location.line, location.index, location.file_path)); - SelectionDialog::get()->add_row(row); - } - - if(rows.size()==0) - return; - SelectionDialog::get()->on_select=[this, rows=std::move(rows), path=std::move(path)](unsigned int index, const std::string &text, bool hide_window) { - if(index>=rows.size()) - return; - auto offset=rows[index]; - auto full_path=path/offset.file_path; - if(!boost::filesystem::is_regular_file(full_path)) - return; - Notebook::get().open(full_path); - auto view=Notebook::get().get_current_view(); - view->place_cursor_at_line_index(offset.line, offset.index); - view->scroll_to_cursor_delayed(view, true, false); - view->hide_tooltips(); - }; - if(view) - view->hide_tooltips(); - SelectionDialog::get()->show(); + menu.add_action("source_find_symbol", [this]() { + auto project=Project::create(); + project->show_symbols(); }); menu.add_action("source_find_file", [this]() {