Browse Source

Language server protocol: added support for workspace/symbol

merge-requests/365/head
eidheim 8 years ago
parent
commit
d534f070b6
  1. 2
      CMakeLists.txt
  2. 2
      src/files.h
  3. 4
      src/menu.cc
  4. 184
      src/project.cc
  5. 30
      src/project.h
  6. 14
      src/selection_dialog.cc
  7. 3
      src/selection_dialog.h
  8. 7
      src/source_language_protocol.cc
  9. 1
      src/source_language_protocol.h
  10. 67
      src/window.cc

2
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 <eidheim@gmail.com>")

2
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": "<primary>p",
"source_find_symbol_ctags": "<primary><shift>f",
"source_find_symbol": "<primary><shift>f",
"source_comments_toggle": "<primary>slash",
"source_comments_add_documentation": "<primary><alt>slash",
"source_find_documentation": "<primary><shift>d",

4
src/menu.cc

@ -271,8 +271,8 @@ const Glib::ustring menu_xml= R"RAW(<interface>
<attribute name='action'>app.source_find_file</attribute>
</item>
<item>
<attribute name='label' translatable='yes'>_Find _Symbol (Ctags)</attribute>
<attribute name='action'>app.source_find_symbol_ctags</attribute>
<attribute name='label' translatable='yes'>_Find _Symbol</attribute>
<attribute name='action'>app.source_find_symbol</attribute>
</item>
</section>
<section>

184
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 <future>
boost::filesystem::path Project::debug_last_stop_file_path;
std::unordered_map<std::string, std::string> Project::run_arguments;
@ -155,6 +158,8 @@ std::shared_ptr<Project::Base> Project::create() {
return std::shared_ptr<Project::Base>(new Project::Clang(std::move(build)));
else if(dynamic_cast<CargoBuild*>(build.get()))
return std::shared_ptr<Project::Base>(new Project::Rust(std::move(build)));
else if(dynamic_cast<NpmBuild*>(build.get()))
return std::shared_ptr<Project::Base>(new Project::JavaScript(std::move(build)));
else
return std::shared_ptr<Project::Base>(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<Source::Offset> 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<std::string, std::string> 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<boost::filesystem::path>(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<std::vector<Source::Offset>>();
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<std::string> names;
std::promise<void> 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<std::string>("name", "");
if(!name.empty()) {
auto location_it=it->second.find("location");
if(location_it!=it->second.not_found()) {
auto file=location_it->second.get<std::string>("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<unsigned>("line"), start_it->second.get<unsigned>("character"), file));
names.emplace_back(name);
}
catch(...) {}
}
}
}
}
}
}
}
result_processed.set_value();
});
result_processed.get_future().get();
for(size_t c=0;c<offsets->size() && c<names.size();++c)
SelectionDialog::get()->add_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<std::string, std::string> 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');
});
}

30
src/project.h

@ -35,6 +35,7 @@ namespace Project {
};
#endif
public:
Base() {}
Base(std::unique_ptr<Build> &&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<std::string, std::string> 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> &&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> &&build) : LLDB(std::move(build)) {}
Clang(std::unique_ptr<Build> &&build) : Base(std::move(build)) {}
std::pair<std::string, std::string> 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> &&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> &&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> &&build) : LLDB(std::move(build)) {}
Rust(std::unique_ptr<Build> &&build) : Base(std::move(build)) {}
void compile() override;
void compile_and_run() override;
std::string get_language_id() override { return "rust"; }
};
std::shared_ptr<Base> create();

14
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)

3
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<Gtk::ListStore> list_store;
@ -35,6 +36,7 @@ public:
SelectionDialogBase(Gtk::TextView *text_view, Glib::RefPtr<Gtk::TextBuffer::Mark> 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<void()> on_hide;
std::function<void(unsigned int index, const std::string &text, bool hide_window)> on_select;
std::function<void(unsigned int index, const std::string &text)> on_changed;
std::function<void(const std::string &text)> on_search_entry_changed;
Glib::RefPtr<Gtk::TextBuffer::Mark> start_mark;
protected:

7
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<std::mutex> 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<LanguageProtocol::Capabilities::TextDocumentSync>(capabilities_pt->second.get<unsigned>("textDocumentSync", 0));
capabilities.document_highlight=capabilities_pt->second.get<bool>("documentHighlightProvider", false);
capabilities.workspace_symbol=capabilities_pt->second.get<bool>("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+"\"}");

1
src/source_language_protocol.h

@ -29,6 +29,7 @@ namespace LanguageProtocol {
INCREMENTAL };
TextDocumentSync text_document_sync;
bool document_highlight;
bool workspace_symbol;
};
class Client {

67
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<Source::Offset> 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]() {

Loading…
Cancel
Save