Browse Source

Language protocol: Cleanup, and added support for relatedInformation. Also cleanup in the way tooltip buffers are set. Finally, improved rename handling.

merge-requests/389/head
eidheim 7 years ago
parent
commit
e5d469e3f1
  1. 12
      src/autocomplete.cc
  2. 9
      src/ctags.cc
  3. 56
      src/filesystem.cc
  4. 4
      src/filesystem.h
  5. 97
      src/project.cc
  6. 23
      src/source.cc
  7. 4
      src/source.h
  8. 6
      src/source_base.cc
  9. 34
      src/source_clang.cc
  10. 1
      src/source_clang.h
  11. 4
      src/source_diff.cc
  12. 522
      src/source_language_protocol.cc
  13. 64
      src/source_language_protocol.h
  14. 14
      src/terminal.cc
  15. 74
      src/tooltips.cc
  16. 8
      src/tooltips.h
  17. 2
      src/window.cc
  18. 5
      tests/filesystem_test.cc
  19. 7
      tests/stubs/tooltips.cc

12
src/autocomplete.cc

@ -155,16 +155,10 @@ void Autocomplete::setup_dialog() {
tooltips.hide(); tooltips.hide();
else { else {
tooltips.clear(); tooltips.clear();
auto create_tooltip_buffer = [this, tooltip = std::move(tooltip)]() {
auto tooltip_buffer = Gtk::TextBuffer::create(view->get_buffer()->get_tag_table());
tooltip_buffer->insert(tooltip_buffer->get_insert()->get_iter(), tooltip);
return tooltip_buffer;
};
auto iter = CompletionDialog::get()->start_mark->get_iter(); auto iter = CompletionDialog::get()->start_mark->get_iter();
tooltips.emplace_back(create_tooltip_buffer, view, view->get_buffer()->create_mark(iter), view->get_buffer()->create_mark(iter)); tooltips.emplace_back(view, view->get_buffer()->create_mark(iter), view->get_buffer()->create_mark(iter), [tooltip = std::move(tooltip)](const Glib::RefPtr<Gtk::TextBuffer> &buffer) {
buffer->insert(buffer->get_insert()->get_iter(), tooltip);
});
tooltips.show(true); tooltips.show(true);
} }

9
src/ctags.cc

@ -13,13 +13,8 @@ std::pair<boost::filesystem::path, std::unique_ptr<std::stringstream>> Ctags::ge
auto run_path = build->project_path; auto run_path = build->project_path;
std::string exclude; std::string exclude;
if(!run_path.empty()) { if(!run_path.empty()) {
auto relative_default_path = filesystem::get_relative_path(build->get_default_path(), run_path); exclude += " --exclude=" + filesystem::get_relative_path(build->get_default_path(), run_path).string();
if(!relative_default_path.empty()) exclude += " --exclude=" + filesystem::get_relative_path(build->get_debug_path(), run_path).string();
exclude += " --exclude=" + relative_default_path.string();
auto relative_debug_path = filesystem::get_relative_path(build->get_debug_path(), run_path);
if(!relative_debug_path.empty())
exclude += " --exclude=" + relative_debug_path.string();
} }
else { else {
boost::system::error_code ec; boost::system::error_code ec;

56
src/filesystem.cc

@ -91,13 +91,21 @@ std::string filesystem::unescape_argument(const std::string &argument) {
} }
boost::filesystem::path filesystem::get_home_path() noexcept { boost::filesystem::path filesystem::get_home_path() noexcept {
static boost::filesystem::path home_path;
if(!home_path.empty())
return home_path;
std::vector<std::string> environment_variables = {"HOME", "AppData"}; std::vector<std::string> environment_variables = {"HOME", "AppData"};
char *ptr = nullptr; char *ptr = nullptr;
for(auto &variable : environment_variables) { for(auto &variable : environment_variables) {
ptr = std::getenv(variable.c_str()); ptr = std::getenv(variable.c_str());
boost::system::error_code ec; if(ptr != nullptr) {
if(ptr != nullptr && boost::filesystem::exists(ptr, ec)) boost::system::error_code ec;
return ptr; boost::filesystem::path path(ptr);
if(boost::filesystem::exists(path, ec)) {
home_path = std::move(path);
return home_path;
}
}
} }
return boost::filesystem::path(); return boost::filesystem::path();
} }
@ -106,11 +114,26 @@ boost::filesystem::path filesystem::get_short_path(const boost::filesystem::path
#ifdef _WIN32 #ifdef _WIN32
return path; return path;
#else #else
static auto home_path = get_home_path(); auto home_path = get_home_path();
if(!home_path.empty()) { if(!home_path.empty() && file_in_path(path, home_path))
auto relative_path = filesystem::get_relative_path(path, home_path); return "~" / get_relative_path(path, home_path);
if(!relative_path.empty()) return path;
return "~" / relative_path; #endif
}
boost::filesystem::path filesystem::get_long_path(const boost::filesystem::path &path) noexcept {
#ifdef _WIN32
return path;
#else
if(!path.empty() && *path.begin() == "~") {
auto long_path = get_home_path();
if(!long_path.empty()) {
auto it = path.begin();
++it;
for(; it != path.end(); ++it)
long_path /= *it;
return long_path;
}
} }
return path; return path;
#endif #endif
@ -158,21 +181,20 @@ boost::filesystem::path filesystem::get_normal_path(const boost::filesystem::pat
boost::filesystem::path filesystem::get_relative_path(const boost::filesystem::path &path, const boost::filesystem::path &base) noexcept { boost::filesystem::path filesystem::get_relative_path(const boost::filesystem::path &path, const boost::filesystem::path &base) noexcept {
boost::filesystem::path relative_path; boost::filesystem::path relative_path;
if(std::distance(path.begin(), path.end()) < std::distance(base.begin(), base.end()))
return boost::filesystem::path();
auto base_it = base.begin(); auto base_it = base.begin();
auto path_it = path.begin(); auto path_it = path.begin();
while(path_it != path.end() && base_it != base.end()) { while(path_it != path.end() && base_it != base.end() && *path_it == *base_it) {
if(*path_it != *base_it)
return boost::filesystem::path();
++path_it; ++path_it;
++base_it; ++base_it;
} }
for(; path_it != path.end(); ++path_it) while(base_it != base.end()) {
relative_path /= "..";
++base_it;
}
while(path_it != path.end()) {
relative_path /= *path_it; relative_path /= *path_it;
++path_it;
}
return relative_path; return relative_path;
} }

4
src/filesystem.h

@ -20,7 +20,10 @@ public:
static std::string unescape_argument(const std::string &argument); static std::string unescape_argument(const std::string &argument);
static boost::filesystem::path get_home_path() noexcept; static boost::filesystem::path get_home_path() noexcept;
/// Replaces home path with ~
static boost::filesystem::path get_short_path(const boost::filesystem::path &path) noexcept; static boost::filesystem::path get_short_path(const boost::filesystem::path &path) noexcept;
/// Replaces ~ with home path (boost::filesystem does not recognize ~)
static boost::filesystem::path get_long_path(const boost::filesystem::path &path) noexcept;
static bool file_in_path(const boost::filesystem::path &file_path, const boost::filesystem::path &path); static bool file_in_path(const boost::filesystem::path &file_path, const boost::filesystem::path &path);
static boost::filesystem::path find_file_in_path_parents(const std::string &file_name, const boost::filesystem::path &path); static boost::filesystem::path find_file_in_path_parents(const std::string &file_name, const boost::filesystem::path &path);
@ -28,7 +31,6 @@ public:
/// Return path with dot, dot-dot and directory separator elements removed /// Return path with dot, dot-dot and directory separator elements removed
static boost::filesystem::path get_normal_path(const boost::filesystem::path &path) noexcept; static boost::filesystem::path get_normal_path(const boost::filesystem::path &path) noexcept;
/// Returns empty path on failure
static boost::filesystem::path get_relative_path(const boost::filesystem::path &path, const boost::filesystem::path &base) noexcept; static boost::filesystem::path get_relative_path(const boost::filesystem::path &path, const boost::filesystem::path &base) noexcept;
/// Return executable with latest version in filename on systems that is lacking executable_name symbolic link /// Return executable with latest version in filename on systems that is lacking executable_name symbolic link

97
src/project.cc

@ -602,9 +602,9 @@ void Project::LLDB::debug_show_variables() {
return; return;
} }
self->debug_variable_tooltips.clear(); self->debug_variable_tooltips.clear();
auto create_tooltip_buffer = [rows, view, index]() {
auto set_tooltip_buffer = [rows, index](const Glib::RefPtr<Gtk::TextBuffer> &buffer) {
auto variable = (*rows)[index]; auto variable = (*rows)[index];
auto tooltip_buffer = view ? Gtk::TextBuffer::create(view->get_buffer()->get_tag_table()) : Gtk::TextBuffer::create();
Glib::ustring value = variable.value; Glib::ustring value = variable.value;
if(!value.empty()) { if(!value.empty()) {
@ -614,19 +614,13 @@ void Project::LLDB::debug_show_variables() {
next_char_iter++; next_char_iter++;
value.replace(iter, next_char_iter, "?"); value.replace(iter, next_char_iter, "?");
} }
if(view) buffer->insert(buffer->get_insert()->get_iter(), value.substr(0, value.size() - 1));
tooltip_buffer->insert(tooltip_buffer->get_insert()->get_iter(), value.substr(0, value.size() - 1));
else
tooltip_buffer->insert(tooltip_buffer->get_insert()->get_iter(), value.substr(0, value.size() - 1));
} }
return tooltip_buffer;
}; };
if(view) if(view)
self->debug_variable_tooltips.emplace_back(create_tooltip_buffer, view, view->get_buffer()->create_mark(iter), view->get_buffer()->create_mark(iter)); self->debug_variable_tooltips.emplace_back(view, view->get_buffer()->create_mark(iter), view->get_buffer()->create_mark(iter), std::move(set_tooltip_buffer));
else else
self->debug_variable_tooltips.emplace_back(create_tooltip_buffer); self->debug_variable_tooltips.emplace_back(std::move(set_tooltip_buffer));
self->debug_variable_tooltips.show(true); self->debug_variable_tooltips.show(true);
}; };
@ -707,97 +701,68 @@ void Project::LanguageProtocol::show_symbols() {
SelectionDialog::get()->on_search_entry_changed = nullptr; // To delete client object SelectionDialog::get()->on_search_entry_changed = nullptr; // To delete client object
}; };
auto offsets = std::make_shared<std::vector<Source::Offset>>(); auto locations = std::make_shared<std::vector<::LanguageProtocol::Location>>();
if(capabilities.workspace_symbol) { if(capabilities.workspace_symbol) {
SelectionDialog::get()->on_search_entry_changed = [client, project_path, offsets](const std::string &text) { SelectionDialog::get()->on_search_entry_changed = [client, project_path, locations](const std::string &text) {
if(text.size() > 1) if(text.size() > 1)
return; return;
else { else {
offsets->clear(); locations->clear();
SelectionDialog::get()->erase_rows(); SelectionDialog::get()->erase_rows();
if(text.empty()) if(text.empty())
return; return;
} }
std::vector<std::string> names; std::vector<std::string> names;
std::promise<void> result_processed; std::promise<void> result_processed;
client->write_request(nullptr, "workspace/symbol", R"("query":")" + text + '"', [&result_processed, &names, offsets, project_path](const boost::property_tree::ptree &result, bool error) { client->write_request(nullptr, "workspace/symbol", R"("query":")" + text + '"', [&result_processed, &names, locations, project_path](const boost::property_tree::ptree &result, bool error) {
if(!error) { if(!error) {
for(auto it = result.begin(); it != result.end(); ++it) { for(auto it = result.begin(); it != result.end(); ++it) {
auto name = it->second.get<std::string>("name", ""); try {
if(!name.empty()) { ::LanguageProtocol::Location location(it->second.get_child("location"));
auto location_it = it->second.find("location"); if(filesystem::file_in_path(location.uri, *project_path)) {
if(location_it != it->second.not_found()) { locations->emplace_back(std::move(location));
auto file = location_it->second.get<std::string>("uri", ""); names.emplace_back(it->second.get<std::string>("name"));
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(...) {
}
}
}
}
} }
} }
catch(...) {
}
} }
} }
result_processed.set_value(); result_processed.set_value();
}); });
result_processed.get_future().get(); result_processed.get_future().get();
for(size_t c = 0; c < offsets->size() && c < names.size(); ++c) for(size_t c = 0; c < locations->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()->add_row(filesystem::get_relative_path((*locations)[c].uri, *project_path).string() + ':' + std::to_string((*locations)[c].range.start.line + 1) + ':' + std::to_string((*locations)[c].range.start.character + 1) + ": " + names[c]);
}; };
} }
else { else {
std::vector<std::string> names; std::vector<std::string> names;
std::promise<void> result_processed; std::promise<void> result_processed;
client->write_request(nullptr, "textDocument/documentSymbol", R"("textDocument":{"uri":")" + language_protocol_view->uri + "\"}", [&result_processed, &names, offsets, project_path](const boost::property_tree::ptree &result, bool error) { client->write_request(nullptr, "textDocument/documentSymbol", R"("textDocument":{"uri":"file://)" + language_protocol_view->file_path.string() + "\"}", [&result_processed, &names, locations, project_path](const boost::property_tree::ptree &result, bool error) {
if(!error) { if(!error) {
for(auto it = result.begin(); it != result.end(); ++it) { for(auto it = result.begin(); it != result.end(); ++it) {
auto name = it->second.get<std::string>("name", ""); try {
if(!name.empty()) { locations->emplace_back(it->second.get_child("location"));
auto location_it = it->second.find("location"); names.emplace_back(it->second.get<std::string>("name"));
if(location_it != it->second.not_found()) { }
auto file = location_it->second.get<std::string>("uri", ""); catch(...) {
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.set_value();
}); });
result_processed.get_future().get(); result_processed.get_future().get();
for(size_t c = 0; c < offsets->size() && c < names.size(); ++c) for(size_t c = 0; c < locations->size() && c < names.size(); ++c)
SelectionDialog::get()->add_row(std::to_string((*offsets)[c].line + 1) + ':' + std::to_string((*offsets)[c].index + 1) + ": " + names[c]); SelectionDialog::get()->add_row(std::to_string((*locations)[c].range.start.line + 1) + ':' + std::to_string((*locations)[c].range.start.character + 1) + ": " + names[c]);
} }
SelectionDialog::get()->on_select = [offsets](unsigned int index, const std::string &text, bool hide_window) { SelectionDialog::get()->on_select = [locations](unsigned int index, const std::string &text, bool hide_window) {
auto &offset = (*offsets)[index]; auto &offset = (*locations)[index];
if(!boost::filesystem::is_regular_file(offset.file_path)) if(!boost::filesystem::is_regular_file(offset.uri))
return; return;
Notebook::get().open(offset.file_path); Notebook::get().open(offset.uri);
auto view = Notebook::get().get_current_view(); auto view = Notebook::get().get_current_view();
view->place_cursor_at_line_offset(offset.line, offset.index); view->place_cursor_at_line_offset(offset.range.start.line, offset.range.start.character);
view->scroll_to_cursor_delayed(view, true, false); view->scroll_to_cursor_delayed(view, true, false);
}; };

23
src/source.cc

@ -161,6 +161,8 @@ Source::View::View(const boost::filesystem::path &file_path, const Glib::RefPtr<
mark_attr_debug_breakpoint_and_stop->set_background(rgba); mark_attr_debug_breakpoint_and_stop->set_background(rgba);
set_mark_attributes("debug_breakpoint_and_stop", mark_attr_debug_breakpoint_and_stop, 102); set_mark_attributes("debug_breakpoint_and_stop", mark_attr_debug_breakpoint_and_stop, 102);
link_tag = get_buffer()->create_tag("link");
get_buffer()->signal_changed().connect([this]() { get_buffer()->signal_changed().connect([this]() {
if(update_status_location) if(update_status_location)
update_status_location(this); update_status_location(this);
@ -507,6 +509,9 @@ void Source::View::configure() {
} }
//TODO: clear tag_class and param_spec? //TODO: clear tag_class and param_spec?
link_tag->property_foreground_rgba() = get_style_context()->get_color(Gtk::StateFlags::STATE_FLAG_LINK);
link_tag->property_underline() = Pango::Underline::UNDERLINE_SINGLE;
if(Config::get().menu.keys["source_show_completion"].empty()) { if(Config::get().menu.keys["source_show_completion"].empty()) {
get_completion()->unblock_interactive(); get_completion()->unblock_interactive();
interactive_completion = true; interactive_completion = true;
@ -684,7 +689,9 @@ void Source::View::setup_format_style(bool is_generic_view) {
if(start == end) if(start == end)
start.forward_char(); start.forward_char();
add_diagnostic_tooltip(start, end, sm[1].str(), true); add_diagnostic_tooltip(start, end, true, [sm = std::move(sm)](const Glib::RefPtr<Gtk::TextBuffer> &buffer) {
buffer->insert_at_cursor(sm[1].str());
});
} }
catch(...) { catch(...) {
} }
@ -1198,18 +1205,16 @@ void Source::View::hide_tooltips() {
diagnostic_tooltips.hide(); diagnostic_tooltips.hide();
} }
void Source::View::add_diagnostic_tooltip(const Gtk::TextIter &start, const Gtk::TextIter &end, std::string spelling, bool error) { void Source::View::add_diagnostic_tooltip(const Gtk::TextIter &start, const Gtk::TextIter &end, bool error, std::function<void(const Glib::RefPtr<Gtk::TextBuffer> &)> &&set_buffer) {
diagnostic_offsets.emplace(start.get_offset()); diagnostic_offsets.emplace(start.get_offset());
std::string severity_tag_name = error ? "def:error" : "def:warning"; std::string severity_tag_name = error ? "def:error" : "def:warning";
auto create_tooltip_buffer = [this, spelling = std::move(spelling), error, severity_tag_name]() { diagnostic_tooltips.emplace_back(this, get_buffer()->create_mark(start), get_buffer()->create_mark(end), [error, severity_tag_name, set_buffer = std::move(set_buffer)](const Glib::RefPtr<Gtk::TextBuffer> &buffer) {
auto tooltip_buffer = Gtk::TextBuffer::create(get_buffer()->get_tag_table()); buffer->insert_with_tag(buffer->get_insert()->get_iter(), error ? "Error" : "Warning", severity_tag_name);
tooltip_buffer->insert_with_tag(tooltip_buffer->get_insert()->get_iter(), error ? "Error" : "Warning", severity_tag_name); buffer->insert(buffer->get_insert()->get_iter(), ":\n");
tooltip_buffer->insert(tooltip_buffer->get_insert()->get_iter(), ":\n" + spelling); set_buffer(buffer);
return tooltip_buffer; });
};
diagnostic_tooltips.emplace_back(create_tooltip_buffer, this, get_buffer()->create_mark(start), get_buffer()->create_mark(end));
get_buffer()->apply_tag_by_name(severity_tag_name + "_underline", start, end); get_buffer()->apply_tag_by_name(severity_tag_name + "_underline", start, end);

4
src/source.h

@ -108,7 +108,7 @@ namespace Source {
Glib::RefPtr<Gtk::TextTag> similar_symbol_tag; Glib::RefPtr<Gtk::TextTag> similar_symbol_tag;
sigc::connection delayed_tag_similar_symbols_connection; sigc::connection delayed_tag_similar_symbols_connection;
virtual void show_diagnostic_tooltips(const Gdk::Rectangle &rectangle) { diagnostic_tooltips.show(rectangle); } virtual void show_diagnostic_tooltips(const Gdk::Rectangle &rectangle) { diagnostic_tooltips.show(rectangle); }
void add_diagnostic_tooltip(const Gtk::TextIter &start, const Gtk::TextIter &end, std::string spelling, bool error); void add_diagnostic_tooltip(const Gtk::TextIter &start, const Gtk::TextIter &end, bool error, std::function<void(const Glib::RefPtr<Gtk::TextBuffer> &)> &&set_buffer);
void clear_diagnostic_tooltips(); void clear_diagnostic_tooltips();
virtual void show_type_tooltips(const Gdk::Rectangle &rectangle) {} virtual void show_type_tooltips(const Gdk::Rectangle &rectangle) {}
gdouble on_motion_last_x = 0.0; gdouble on_motion_last_x = 0.0;
@ -169,6 +169,8 @@ namespace Source {
int multiple_cursors_erase_backward_length; int multiple_cursors_erase_backward_length;
int multiple_cursors_erase_forward_length; int multiple_cursors_erase_forward_length;
bool on_key_press_event_multiple_cursors(GdkEventKey *key); bool on_key_press_event_multiple_cursors(GdkEventKey *key);
Glib::RefPtr<Gtk::TextTag> link_tag; /// Used in tooltips
}; };
class GenericView : public View { class GenericView : public View {

6
src/source_base.cc

@ -181,6 +181,12 @@ void Source::BaseView::replace_text(const std::string &new_text) {
void Source::BaseView::rename(const boost::filesystem::path &path) { void Source::BaseView::rename(const boost::filesystem::path &path) {
file_path = path; file_path = path;
boost::system::error_code ec;
last_write_time = boost::filesystem::last_write_time(file_path, ec);
if(ec)
last_write_time = static_cast<std::time_t>(-1);
monitor_file();
if(update_status_file_path) if(update_status_file_path)
update_status_file_path(this); update_status_file_path(this);
if(update_tab_label) if(update_tab_label)

34
src/source_clang.cc

@ -11,6 +11,7 @@
#include "documentation_cppreference.h" #include "documentation_cppreference.h"
#include "filesystem.h" #include "filesystem.h"
#include "info.h" #include "info.h"
#include "notebook.h"
#include "selection_dialog.h" #include "selection_dialog.h"
#include "usages_clang.h" #include "usages_clang.h"
@ -43,6 +44,14 @@ Source::ClangViewParse::ClangViewParse(const boost::filesystem::path &file_path,
}); });
} }
void Source::ClangViewParse::rename(const boost::filesystem::path &path) {
Source::DiffView::rename(path);
if(Notebook::get().get_current_view() == this)
full_reparse();
else
full_reparse_needed = true;
}
bool Source::ClangViewParse::save() { bool Source::ClangViewParse::save() {
if(!Source::View::save()) if(!Source::View::save())
return false; return false;
@ -338,7 +347,9 @@ void Source::ClangViewParse::update_diagnostics() {
if(!fix_its_string.empty()) if(!fix_its_string.empty())
diagnostic.spelling += "\n\n" + fix_its_string; diagnostic.spelling += "\n\n" + fix_its_string;
add_diagnostic_tooltip(start, end, diagnostic.spelling, error); add_diagnostic_tooltip(start, end, error, [spelling = std::move(diagnostic.spelling)](const Glib::RefPtr<Gtk::TextBuffer> &buffer) {
buffer->insert_at_cursor(spelling);
});
} }
} }
status_diagnostics = std::make_tuple(num_warnings, num_errors, num_fix_its); status_diagnostics = std::make_tuple(num_warnings, num_errors, num_fix_its);
@ -371,12 +382,12 @@ void Source::ClangViewParse::show_type_tooltips(const Gdk::Rectangle &rectangle)
if(referenced) { if(referenced) {
auto start = get_buffer()->get_iter_at_line_index(token_offsets.first.line - 1, token_offsets.first.index - 1); auto start = get_buffer()->get_iter_at_line_index(token_offsets.first.line - 1, token_offsets.first.index - 1);
auto end = get_buffer()->get_iter_at_line_index(token_offsets.second.line - 1, token_offsets.second.index - 1); auto end = get_buffer()->get_iter_at_line_index(token_offsets.second.line - 1, token_offsets.second.index - 1);
auto create_tooltip_buffer = [this, &token, &start, &end]() {
auto tooltip_buffer = Gtk::TextBuffer::create(get_buffer()->get_tag_table()); type_tooltips.emplace_back(this, get_buffer()->create_mark(start), get_buffer()->create_mark(end), [this, token, start, end](const Glib::RefPtr<Gtk::TextBuffer> &buffer) {
tooltip_buffer->insert(tooltip_buffer->get_insert()->get_iter(), "Type: " + token.get_cursor().get_type_description()); buffer->insert(buffer->get_insert()->get_iter(), "Type: " + token.get_cursor().get_type_description());
auto brief_comment = token.get_cursor().get_brief_comments(); auto brief_comment = token.get_cursor().get_brief_comments();
if(brief_comment != "") if(brief_comment != "")
tooltip_buffer->insert(tooltip_buffer->get_insert()->get_iter(), "\n\n" + brief_comment); buffer->insert(buffer->get_insert()->get_iter(), "\n\n" + brief_comment);
#ifdef JUCI_ENABLE_DEBUG #ifdef JUCI_ENABLE_DEBUG
if(Debug::LLDB::get().is_stopped()) { if(Debug::LLDB::get().is_stopped()) {
@ -385,8 +396,9 @@ void Source::ClangViewParse::show_type_tooltips(const Gdk::Rectangle &rectangle)
Glib::ustring value_type = "Value"; Glib::ustring value_type = "Value";
auto iter = start; auto iter = start;
auto corrected_start = start;
while((*iter >= 'a' && *iter <= 'z') || (*iter >= 'A' && *iter <= 'Z') || (*iter >= '0' && *iter <= '9') || *iter == '_' || *iter == '.') { while((*iter >= 'a' && *iter <= 'z') || (*iter >= 'A' && *iter <= 'Z') || (*iter >= '0' && *iter <= '9') || *iter == '_' || *iter == '.') {
start = iter; corrected_start = iter;
if(!iter.backward_char()) if(!iter.backward_char())
break; break;
if(*iter == '>') { if(*iter == '>') {
@ -398,7 +410,7 @@ void Source::ClangViewParse::show_type_tooltips(const Gdk::Rectangle &rectangle)
break; break;
} }
} }
auto spelling = get_buffer()->get_text(start, end).raw(); auto spelling = get_buffer()->get_text(corrected_start, end).raw();
Glib::ustring debug_value; Glib::ustring debug_value;
auto cursor_kind = referenced.get_kind(); auto cursor_kind = referenced.get_kind();
@ -421,16 +433,12 @@ void Source::ClangViewParse::show_type_tooltips(const Gdk::Rectangle &rectangle)
next_char_iter++; next_char_iter++;
debug_value.replace(iter, next_char_iter, "?"); debug_value.replace(iter, next_char_iter, "?");
} }
tooltip_buffer->insert(tooltip_buffer->get_insert()->get_iter(), "\n\n" + value_type + ": " + debug_value.substr(pos + 3, debug_value.size() - (pos + 3) - 1)); buffer->insert(buffer->get_insert()->get_iter(), "\n\n" + value_type + ": " + debug_value.substr(pos + 3, debug_value.size() - (pos + 3) - 1));
} }
} }
} }
#endif #endif
});
return tooltip_buffer;
};
type_tooltips.emplace_back(create_tooltip_buffer, this, get_buffer()->create_mark(start), get_buffer()->create_mark(end));
type_tooltips.show(); type_tooltips.show();
return; return;
} }

1
src/source_clang.h

@ -19,6 +19,7 @@ namespace Source {
public: public:
ClangViewParse(const boost::filesystem::path &file_path, const Glib::RefPtr<Gsv::Language> &language); ClangViewParse(const boost::filesystem::path &file_path, const Glib::RefPtr<Gsv::Language> &language);
void rename(const boost::filesystem::path &path) override;
bool save() override; bool save() override;
void configure() override; void configure() override;

4
src/source_diff.cc

@ -313,9 +313,9 @@ std::unique_ptr<Git::Repository::Diff> Source::DiffView::get_diff() {
boost::filesystem::path relative_path; boost::filesystem::path relative_path;
{ {
std::unique_lock<std::mutex> lock(canonical_file_path_mutex); std::unique_lock<std::mutex> lock(canonical_file_path_mutex);
relative_path = filesystem::get_relative_path(canonical_file_path, work_path); if(!filesystem::file_in_path(canonical_file_path, work_path))
if(relative_path.empty())
throw std::runtime_error("not a relative path"); throw std::runtime_error("not a relative path");
relative_path = filesystem::get_relative_path(canonical_file_path, work_path);
} }
return std::make_unique<Git::Repository::Diff>(repository->get_diff(relative_path)); return std::make_unique<Git::Repository::Diff>(repository->get_diff(relative_path));
} }

522
src/source_language_protocol.cc

@ -12,6 +12,9 @@
#include <future> #include <future>
#include <limits> #include <limits>
#include <regex> #include <regex>
#include <unordered_map>
const std::string flow_coverage_message = "Not covered by Flow";
LanguageProtocol::Client::Client(std::string root_uri_, std::string language_id_) : root_uri(std::move(root_uri_)), language_id(std::move(language_id_)) { LanguageProtocol::Client::Client(std::string root_uri_, std::string language_id_) : root_uri(std::move(root_uri_)), language_id(std::move(language_id_)) {
process = std::make_unique<TinyProcessLib::Process>(language_id + "-language-server", root_uri, [this](const char *bytes, size_t n) { process = std::make_unique<TinyProcessLib::Process>(language_id + "-language-server", root_uri, [this](const char *bytes, size_t n) {
@ -297,21 +300,15 @@ void LanguageProtocol::Client::handle_server_request(const std::string &method,
if(!uri.empty()) { if(!uri.empty()) {
auto diagnostics_pt = params.get_child("diagnostics", boost::property_tree::ptree()); auto diagnostics_pt = params.get_child("diagnostics", boost::property_tree::ptree());
for(auto it = diagnostics_pt.begin(); it != diagnostics_pt.end(); ++it) { for(auto it = diagnostics_pt.begin(); it != diagnostics_pt.end(); ++it) {
auto range_it = it->second.find("range"); try {
if(range_it != it->second.not_found()) { diagnostics.emplace_back(it->second);
auto start_it = range_it->second.find("start"); }
auto end_it = range_it->second.find("end"); catch(...) {
if(start_it != range_it->second.not_found() && start_it != range_it->second.not_found()) {
diagnostics.emplace_back(Diagnostic{it->second.get<std::string>("message", ""),
std::make_pair<Source::Offset, Source::Offset>(Source::Offset(start_it->second.get<unsigned>("line", 0), start_it->second.get<unsigned>("character", 0)),
Source::Offset(end_it->second.get<unsigned>("line", 0), end_it->second.get<unsigned>("character", 0))),
it->second.get<unsigned>("severity", 0), uri});
}
} }
} }
std::unique_lock<std::mutex> lock(views_mutex); std::unique_lock<std::mutex> lock(views_mutex);
for(auto view : views) { for(auto view : views) {
if(view->uri == uri) { if(uri.size() >= 7 && uri.compare(7, std::string::npos, view->file_path.string()) == 0) {
view->update_diagnostics(std::move(diagnostics)); view->update_diagnostics(std::move(diagnostics));
break; break;
} }
@ -321,15 +318,11 @@ void LanguageProtocol::Client::handle_server_request(const std::string &method,
} }
Source::LanguageProtocolView::LanguageProtocolView(const boost::filesystem::path &file_path, const Glib::RefPtr<Gsv::Language> &language, std::string language_id_) Source::LanguageProtocolView::LanguageProtocolView(const boost::filesystem::path &file_path, const Glib::RefPtr<Gsv::Language> &language, std::string language_id_)
: Source::BaseView(file_path, language), Source::View(file_path, language), uri("file://" + file_path.string()), language_id(std::move(language_id_)), client(LanguageProtocol::Client::get(file_path, language_id)), autocomplete(this, interactive_completion, last_keyval, false) { : Source::BaseView(file_path, language), Source::View(file_path, language), language_id(std::move(language_id_)), client(LanguageProtocol::Client::get(file_path, language_id)), autocomplete(this, interactive_completion, last_keyval, false) {
configure(); configure();
get_source_buffer()->set_language(language); get_source_buffer()->set_language(language);
get_source_buffer()->set_highlight_syntax(true); get_source_buffer()->set_highlight_syntax(true);
status_state = "initializing...";
if(update_status_state)
update_status_state(this);
if(language_id == "javascript") { if(language_id == "javascript") {
boost::filesystem::path project_path; boost::filesystem::path project_path;
auto build = Project::Build::create(file_path); auto build = Project::Build::create(file_path);
@ -345,30 +338,7 @@ Source::LanguageProtocolView::LanguageProtocolView(const boost::filesystem::path
} }
} }
initialize_thread = std::thread([this] { initialize(true);
auto capabilities = client->initialize(this);
if(!flow_coverage_executable.empty())
add_flow_coverage_tooltips(true);
dispatcher.post([this, capabilities] {
this->capabilities = capabilities;
std::string text = get_buffer()->get_text();
escape_text(text);
client->write_notification("textDocument/didOpen", R"("textDocument":{"uri":")" + uri + R"(","languageId":")" + language_id + R"(","version":)" + std::to_string(document_version++) + R"(,"text":")" + text + "\"}");
setup_autocomplete();
setup_navigation_and_refactoring();
Menu::get().toggle_menu_items();
if(status_state == "initializing...") {
status_state = "";
if(update_status_state)
update_status_state(this);
}
});
});
get_buffer()->signal_changed().connect([this] { get_buffer()->signal_changed().connect([this] {
get_buffer()->remove_tag(similar_symbol_tag, get_buffer()->begin(), get_buffer()->end()); get_buffer()->remove_tag(similar_symbol_tag, get_buffer()->begin(), get_buffer()->end());
@ -398,7 +368,7 @@ Source::LanguageProtocolView::LanguageProtocolView(const boost::filesystem::path
escape_text(text); escape_text(text);
content_changes = R"({"text":")" + text + "\"}"; content_changes = R"({"text":")" + text + "\"}";
} }
client->write_notification("textDocument/didChange", R"("textDocument":{"uri":")" + uri + R"(","version":)" + std::to_string(document_version++) + "},\"contentChanges\":[" + content_changes + "]"); client->write_notification("textDocument/didChange", R"("textDocument":{"uri":"file://)" + this->file_path.string() + R"(","version":)" + std::to_string(document_version++) + "},\"contentChanges\":[" + content_changes + "]");
}, false); }, false);
get_buffer()->signal_erase().connect([this](const Gtk::TextBuffer::iterator &start, const Gtk::TextBuffer::iterator &end) { get_buffer()->signal_erase().connect([this](const Gtk::TextBuffer::iterator &start, const Gtk::TextBuffer::iterator &end) {
@ -412,11 +382,48 @@ Source::LanguageProtocolView::LanguageProtocolView(const boost::filesystem::path
escape_text(text); escape_text(text);
content_changes = R"({"text":")" + text + "\"}"; content_changes = R"({"text":")" + text + "\"}";
} }
client->write_notification("textDocument/didChange", R"("textDocument":{"uri":")" + uri + R"(","version":)" + std::to_string(document_version++) + "},\"contentChanges\":[" + content_changes + "]"); client->write_notification("textDocument/didChange", R"("textDocument":{"uri":"file://)" + this->file_path.string() + R"(","version":)" + std::to_string(document_version++) + "},\"contentChanges\":[" + content_changes + "]");
}, false); }, false);
} }
Source::LanguageProtocolView::~LanguageProtocolView() { void Source::LanguageProtocolView::initialize(bool setup) {
status_diagnostics = std::make_tuple(0, 0, 0);
if(update_status_diagnostics)
update_status_diagnostics(this);
status_state = "initializing...";
if(update_status_state)
update_status_state(this);
initialize_thread = std::thread([this, setup] {
if(!flow_coverage_executable.empty())
add_flow_coverage_tooltips(true);
auto capabilities = client->initialize(this);
dispatcher.post([this, capabilities, setup] {
this->capabilities = capabilities;
std::string text = get_buffer()->get_text();
escape_text(text);
client->write_notification("textDocument/didOpen", R"("textDocument":{"uri":"file://)" + file_path.string() + R"(","languageId":")" + language_id + R"(","version":)" + std::to_string(document_version++) + R"(,"text":")" + text + "\"}");
if(setup) {
setup_autocomplete();
setup_navigation_and_refactoring();
Menu::get().toggle_menu_items();
}
if(status_state == "initializing...") {
status_state = "";
if(update_status_state)
update_status_state(this);
}
});
});
}
void Source::LanguageProtocolView::close() {
if(initialize_thread.joinable()) if(initialize_thread.joinable())
initialize_thread.join(); initialize_thread.join();
@ -424,12 +431,22 @@ Source::LanguageProtocolView::~LanguageProtocolView() {
if(autocomplete.thread.joinable()) if(autocomplete.thread.joinable())
autocomplete.thread.join(); autocomplete.thread.join();
client->write_notification("textDocument/didClose", R"("textDocument":{"uri":")" + uri + "\"}"); client->write_notification("textDocument/didClose", R"("textDocument":{"uri":"file://)" + file_path.string() + "\"}");
client->close(this); client->close(this);
client = nullptr; client = nullptr;
} }
Source::LanguageProtocolView::~LanguageProtocolView() {
close();
}
void Source::LanguageProtocolView::rename(const boost::filesystem::path &path) {
close();
Source::DiffView::rename(path);
client = LanguageProtocol::Client::get(file_path, language_id);
initialize(false);
}
bool Source::LanguageProtocolView::save() { bool Source::LanguageProtocolView::save() {
if(!Source::View::save()) if(!Source::View::save())
return false; return false;
@ -445,7 +462,7 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() {
format_style = [this](bool continue_without_style_file) { format_style = [this](bool continue_without_style_file) {
if(!continue_without_style_file) { if(!continue_without_style_file) {
bool has_style_file = false; bool has_style_file = false;
auto style_file_search_path = this->file_path.parent_path(); auto style_file_search_path = file_path.parent_path();
auto style_file = '.' + language_id + "-format"; auto style_file = '.' + language_id + "-format";
while(true) { while(true) {
@ -462,12 +479,7 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() {
return; return;
} }
class Replace { std::vector<LanguageProtocol::TextEdit> text_edits;
public:
Offset start, end;
std::string text;
};
std::vector<Replace> replaces;
std::promise<void> result_processed; std::promise<void> result_processed;
std::string method; std::string method;
@ -477,30 +489,20 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() {
method = "textDocument/rangeFormatting"; method = "textDocument/rangeFormatting";
Gtk::TextIter start, end; Gtk::TextIter start, end;
get_buffer()->get_selection_bounds(start, end); get_buffer()->get_selection_bounds(start, end);
params = R"("textDocument":{"uri":")" + uri + R"("},"range":{"start":{"line":)" + std::to_string(start.get_line()) + ",\"character\":" + std::to_string(start.get_line_offset()) + R"(},"end":{"line":)" + std::to_string(end.get_line()) + ",\"character\":" + std::to_string(end.get_line_offset()) + "}},\"options\":{" + options + "}"; params = R"("textDocument":{"uri":"file://)" + file_path.string() + R"("},"range":{"start":{"line":)" + std::to_string(start.get_line()) + ",\"character\":" + std::to_string(start.get_line_offset()) + R"(},"end":{"line":)" + std::to_string(end.get_line()) + ",\"character\":" + std::to_string(end.get_line_offset()) + "}},\"options\":{" + options + "}";
} }
else { else {
method = "textDocument/formatting"; method = "textDocument/formatting";
params = R"("textDocument":{"uri":")" + uri + R"("},"options":{)" + options + "}"; params = R"("textDocument":{"uri":"file://)" + file_path.string() + R"("},"options":{)" + options + "}";
} }
client->write_request(this, method, params, [&replaces, &result_processed](const boost::property_tree::ptree &result, bool error) { client->write_request(this, method, params, [&text_edits, &result_processed](const boost::property_tree::ptree &result, bool error) {
if(!error) { if(!error) {
for(auto it = result.begin(); it != result.end(); ++it) { for(auto it = result.begin(); it != result.end(); ++it) {
auto range_it = it->second.find("range"); try {
auto text_it = it->second.find("newText"); text_edits.emplace_back(it->second);
if(range_it != it->second.not_found() && text_it != it->second.not_found()) { }
auto start_it = range_it->second.find("start"); catch(...) {
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<unsigned>("line"), start_it->second.get<unsigned>("character")),
Offset(end_it->second.get<unsigned>("line"), end_it->second.get<unsigned>("character")),
text_it->second.get_value<std::string>()});
}
catch(...) {
}
}
} }
} }
} }
@ -510,20 +512,20 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() {
auto end_iter = get_buffer()->end(); auto end_iter = get_buffer()->end();
// If entire buffer is replaced: // If entire buffer is replaced:
if(replaces.size() == 1 && if(text_edits.size() == 1 &&
replaces[0].start.line == 0 && replaces[0].start.index == 0 && text_edits[0].range.start.line == 0 && text_edits[0].range.start.character == 0 &&
(replaces[0].end.line > static_cast<unsigned>(end_iter.get_line()) || (text_edits[0].range.end.line > static_cast<unsigned>(end_iter.get_line()) ||
(replaces[0].end.line == static_cast<unsigned>(end_iter.get_line()) && replaces[0].end.index >= static_cast<unsigned>(end_iter.get_line_offset())))) { (text_edits[0].range.end.line == static_cast<unsigned>(end_iter.get_line()) && text_edits[0].range.end.character >= static_cast<unsigned>(end_iter.get_line_offset())))) {
replace_text(replaces[0].text); replace_text(text_edits[0].new_text);
} }
else { else {
get_buffer()->begin_user_action(); get_buffer()->begin_user_action();
for(auto it = replaces.rbegin(); it != replaces.rend(); ++it) { for(auto it = text_edits.rbegin(); it != text_edits.rend(); ++it) {
auto start = get_iter_at_line_pos(it->start.line, it->start.index); auto start = get_iter_at_line_pos(it->range.start.line, it->range.start.character);
auto end = get_iter_at_line_pos(it->end.line, it->end.index); auto end = get_iter_at_line_pos(it->range.end.line, it->range.end.character);
get_buffer()->erase(start, end); get_buffer()->erase(start, end);
start = get_iter_at_line_pos(it->start.line, it->start.index); start = get_iter_at_line_pos(it->range.start.line, it->range.start.character);
get_buffer()->insert(start, it->text); get_buffer()->insert(start, it->new_text);
} }
get_buffer()->end_user_action(); get_buffer()->end_user_action();
} }
@ -549,44 +551,23 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() {
if(capabilities.references || capabilities.document_highlight) { if(capabilities.references || capabilities.document_highlight) {
get_usages = [this] { get_usages = [this] {
auto iter = get_buffer()->get_insert()->get_iter(); auto iter = get_buffer()->get_insert()->get_iter();
std::vector<std::pair<Offset, std::string>> usages; std::vector<LanguageProtocol::Location> locations;
std::vector<Offset> end_offsets;
std::promise<void> result_processed; std::promise<void> result_processed;
std::string method; std::string method;
if(capabilities.document_highlight) if(capabilities.references)
method = "textDocument/documentHighlight";
else
method = "textDocument/references"; method = "textDocument/references";
else
method = "textDocument/documentHighlight";
client->write_request(this, method, R"("textDocument":{"uri":")" + uri + R"("}, "position": {"line": )" + std::to_string(iter.get_line()) + ", \"character\": " + std::to_string(iter.get_line_offset()) + R"(}, "context": {"includeDeclaration": true})", [this, &usages, &end_offsets, &result_processed](const boost::property_tree::ptree &result, bool error) { client->write_request(this, method, R"("textDocument":{"uri":"file://)" + file_path.string() + R"("}, "position": {"line": )" + std::to_string(iter.get_line()) + ", \"character\": " + std::to_string(iter.get_line_offset()) + R"(}, "context": {"includeDeclaration": true})", [this, &locations, &result_processed](const boost::property_tree::ptree &result, bool error) {
if(!error) { if(!error) {
try { try {
for(auto it = result.begin(); it != result.end(); ++it) { for(auto it = result.begin(); it != result.end(); ++it)
std::string path; locations.emplace_back(it->second, !capabilities.references ? file_path.string() : std::string());
if(capabilities.document_highlight)
path = file_path.string();
else {
path = it->second.get<std::string>("uri", "");
if(path.size() >= 7)
path.erase(0, 7);
else
continue;
}
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<unsigned>("line"), start_it->second.get<unsigned>("character"), path), ""));
end_offsets.emplace_back(end_it->second.get<unsigned>("line"), end_it->second.get<unsigned>("character"));
}
}
}
} }
catch(...) { catch(...) {
usages.clear(); locations.clear();
end_offsets.clear();
} }
} }
result_processed.set_value(); result_processed.set_value();
@ -624,30 +605,33 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() {
line_ = line.raw(); line_ = line.raw();
}; };
std::map<boost::filesystem::path, std::vector<std::string>> file_lines; std::unordered_map<std::string, std::vector<std::string>> file_lines;
std::vector<std::pair<Offset, std::string>> usages;
auto c = static_cast<size_t>(-1); auto c = static_cast<size_t>(-1);
for(auto &usage : usages) { for(auto &location : locations) {
++c; ++c;
usages.emplace_back(Offset(location.range.start.line, location.range.start.character, location.uri), std::string());
auto &usage = usages.back();
auto view_it = views.end(); auto view_it = views.end();
for(auto it = views.begin(); it != views.end(); ++it) { for(auto it = views.begin(); it != views.end(); ++it) {
if(usage.first.file_path == (*it)->file_path) { if(location.uri == (*it)->file_path) {
view_it = it; view_it = it;
break; break;
} }
} }
if(view_it != views.end()) { if(view_it != views.end()) {
if(usage.first.line < static_cast<unsigned>((*view_it)->get_buffer()->get_line_count())) { if(location.range.start.line < static_cast<unsigned>((*view_it)->get_buffer()->get_line_count())) {
auto start = (*view_it)->get_buffer()->get_iter_at_line(usage.first.line); auto start = (*view_it)->get_buffer()->get_iter_at_line(location.range.start.line);
auto end = start; auto end = start;
end.forward_to_line_end(); end.forward_to_line_end();
usage.second = Glib::Markup::escape_text((*view_it)->get_buffer()->get_text(start, end)); usage.second = Glib::Markup::escape_text((*view_it)->get_buffer()->get_text(start, end));
embolden_token(usage.second, usage.first.index, end_offsets[c].index); embolden_token(usage.second, location.range.start.character, location.range.end.character);
} }
} }
else { else {
auto it = file_lines.find(usage.first.file_path); auto it = file_lines.find(location.uri);
if(it == file_lines.end()) { if(it == file_lines.end()) {
std::ifstream ifs(usage.first.file_path.string()); std::ifstream ifs(location.uri);
if(ifs) { if(ifs) {
std::vector<std::string> lines; std::vector<std::string> lines;
std::string line; std::string line;
@ -656,23 +640,23 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() {
line.pop_back(); line.pop_back();
lines.emplace_back(line); lines.emplace_back(line);
} }
auto pair = file_lines.emplace(usage.first.file_path, lines); auto pair = file_lines.emplace(location.uri, lines);
it = pair.first; it = pair.first;
} }
else { else {
auto pair = file_lines.emplace(usage.first.file_path, std::vector<std::string>()); auto pair = file_lines.emplace(location.uri, std::vector<std::string>());
it = pair.first; it = pair.first;
} }
} }
if(usage.first.line < it->second.size()) { if(location.range.start.line < it->second.size()) {
usage.second = it->second[usage.first.line]; usage.second = Glib::Markup::escape_text(it->second[location.range.start.line]);
embolden_token(usage.second, usage.first.index, end_offsets[c].index); embolden_token(usage.second, location.range.start.character, location.range.end.character);
} }
} }
} }
if(usages.empty()) if(locations.empty())
Info::get().print("No symbol found at current cursor location"); Info::get().print("No symbol found at current cursor location");
return usages; return usages;
@ -698,11 +682,10 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() {
if(capabilities.rename || capabilities.document_highlight) { if(capabilities.rename || capabilities.document_highlight) {
rename_similar_tokens = [this](const std::string &text) { rename_similar_tokens = [this](const std::string &text) {
class Usages { class Changes {
public: public:
boost::filesystem::path path; std::string uri;
std::unique_ptr<std::string> new_text; std::vector<LanguageProtocol::TextEdit> text_edits;
std::vector<std::pair<Offset, Offset>> offsets;
}; };
auto previous_text = get_token_spelling(); auto previous_text = get_token_spelling();
@ -710,10 +693,10 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() {
return; return;
auto iter = get_buffer()->get_insert()->get_iter(); auto iter = get_buffer()->get_insert()->get_iter();
std::vector<Usages> usages; std::vector<Changes> changes;
std::promise<void> result_processed; std::promise<void> result_processed;
if(capabilities.rename) { if(capabilities.rename) {
client->write_request(this, "textDocument/rename", R"("textDocument":{"uri":")" + uri + R"("}, "position": {"line": )" + std::to_string(iter.get_line()) + ", \"character\": " + std::to_string(iter.get_line_offset()) + R"(}, "newName": ")" + text + "\"", [this, &usages, &result_processed](const boost::property_tree::ptree &result, bool error) { client->write_request(this, "textDocument/rename", R"("textDocument":{"uri":"file://)" + file_path.string() + R"("}, "position": {"line": )" + std::to_string(iter.get_line()) + ", \"character\": " + std::to_string(iter.get_line_offset()) + R"(}, "newName": ")" + text + "\"", [this, &changes, &result_processed](const boost::property_tree::ptree &result, bool error) {
if(!error) { if(!error) {
boost::filesystem::path project_path; boost::filesystem::path project_path;
auto build = Project::Build::create(file_path); auto build = Project::Build::create(file_path);
@ -725,22 +708,13 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() {
auto changes_it = result.find("changes"); auto changes_it = result.find("changes");
if(changes_it != result.not_found()) { if(changes_it != result.not_found()) {
for(auto file_it = changes_it->second.begin(); file_it != changes_it->second.end(); ++file_it) { for(auto file_it = changes_it->second.begin(); file_it != changes_it->second.end(); ++file_it) {
auto path = file_it->first; auto uri = file_it->first;
if(path.size() >= 7) { uri.erase(0, 7);
path.erase(0, 7); if(filesystem::file_in_path(uri, project_path)) {
if(filesystem::file_in_path(path, project_path)) { std::vector<LanguageProtocol::TextEdit> edits;
usages.emplace_back(Usages{path, nullptr, std::vector<std::pair<Offset, Offset>>()}); for(auto edit_it = file_it->second.begin(); edit_it != file_it->second.end(); ++edit_it)
for(auto edit_it = file_it->second.begin(); edit_it != file_it->second.end(); ++edit_it) { edits.emplace_back(edit_it->second);
auto range_it = edit_it->second.find("range"); changes.emplace_back(Changes{std::move(uri), std::move(edits)});
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<unsigned>("line"), start_it->second.get<unsigned>("character")),
Offset(end_it->second.get<unsigned>("line"), end_it->second.get<unsigned>("character"))));
}
}
}
} }
} }
} }
@ -749,62 +723,37 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() {
for(auto change_it = changes_pt.begin(); change_it != changes_pt.end(); ++change_it) { for(auto change_it = changes_pt.begin(); change_it != changes_pt.end(); ++change_it) {
auto document_it = change_it->second.find("textDocument"); auto document_it = change_it->second.find("textDocument");
if(document_it != change_it->second.not_found()) { if(document_it != change_it->second.not_found()) {
auto path = document_it->second.get<std::string>("uri", ""); auto uri = document_it->second.get<std::string>("uri", "");
if(path.size() >= 7) { uri.erase(0, 7);
path.erase(0, 7); if(filesystem::file_in_path(uri, project_path)) {
if(filesystem::file_in_path(path, project_path)) { std::vector<LanguageProtocol::TextEdit> edits;
usages.emplace_back(Usages{path, std::make_unique<std::string>(), std::vector<std::pair<Offset, Offset>>()}); auto edits_pt = change_it->second.get_child("edits", boost::property_tree::ptree());
auto edits_pt = change_it->second.get_child("edits", boost::property_tree::ptree()); for(auto edit_it = edits_pt.begin(); edit_it != edits_pt.end(); ++edit_it)
for(auto edit_it = edits_pt.begin(); edit_it != edits_pt.end(); ++edit_it) { edits.emplace_back(edit_it->second);
auto new_text_it = edit_it->second.find("newText"); changes.emplace_back(Changes{std::move(uri), std::move(edits)});
if(new_text_it != edit_it->second.not_found()) {
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()) {
auto end_line = end_it->second.get<size_t>("line");
if(end_line > std::numeric_limits<int>::max())
end_line = std::numeric_limits<int>::max();
*usages.back().new_text = new_text_it->second.get_value<std::string>();
usages.back().offsets.emplace_back(std::make_pair(Offset(start_it->second.get<unsigned>("line"), start_it->second.get<unsigned>("character")),
Offset(static_cast<unsigned>(end_line), end_it->second.get<unsigned>("character"))));
}
}
}
}
}
} }
} }
} }
} }
} }
catch(...) { catch(...) {
usages.clear(); changes.clear();
} }
} }
result_processed.set_value(); result_processed.set_value();
}); });
} }
else { else {
client->write_request(this, "textDocument/documentHighlight", R"("textDocument":{"uri":")" + uri + R"("}, "position": {"line": )" + std::to_string(iter.get_line()) + ", \"character\": " + std::to_string(iter.get_line_offset()) + R"(}, "context": {"includeDeclaration": true})", [this, &usages, &result_processed](const boost::property_tree::ptree &result, bool error) { client->write_request(this, "textDocument/documentHighlight", R"("textDocument":{"uri":"file://)" + file_path.string() + R"("}, "position": {"line": )" + std::to_string(iter.get_line()) + ", \"character\": " + std::to_string(iter.get_line_offset()) + R"(}, "context": {"includeDeclaration": true})", [this, &changes, &text, &result_processed](const boost::property_tree::ptree &result, bool error) {
if(!error) { if(!error) {
try { try {
usages.emplace_back(Usages{file_path, nullptr, std::vector<std::pair<Offset, Offset>>()}); std::vector<LanguageProtocol::TextEdit> edits;
for(auto it = result.begin(); it != result.end(); ++it) { for(auto it = result.begin(); it != result.end(); ++it)
auto range_it = it->second.find("range"); edits.emplace_back(it->second, text);
if(range_it != it->second.not_found()) { changes.emplace_back(Changes{file_path.string(), std::move(edits)});
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<unsigned>("line"), start_it->second.get<unsigned>("character")),
Offset(end_it->second.get<unsigned>("line"), end_it->second.get<unsigned>("character"))));
}
}
}
} }
catch(...) { catch(...) {
usages.clear(); changes.clear();
} }
} }
result_processed.set_value(); result_processed.set_value();
@ -812,11 +761,11 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() {
} }
result_processed.get_future().get(); result_processed.get_future().get();
std::vector<Usages *> usages_renamed; std::vector<Changes *> changes_renamed;
for(auto &usage : usages) { for(auto &change : changes) {
auto view_it = views.end(); auto view_it = views.end();
for(auto it = views.begin(); it != views.end(); ++it) { for(auto it = views.begin(); it != views.end(); ++it) {
if((*it)->file_path == usage.path) { if((*it)->file_path == change.uri) {
view_it = it; view_it = it;
break; break;
} }
@ -827,69 +776,63 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() {
auto end_iter = buffer->end(); auto end_iter = buffer->end();
// If entire buffer is replaced // If entire buffer is replaced
if(usage.new_text && usage.offsets.size() == 1 && if(change.text_edits.size() == 1 &&
usage.offsets[0].first.line == 0 && usage.offsets[0].first.index == 0 && change.text_edits[0].range.start.line == 0 && change.text_edits[0].range.start.character == 0 &&
(usage.offsets[0].second.line > static_cast<unsigned>(end_iter.get_line()) || (change.text_edits[0].range.end.line > static_cast<unsigned>(end_iter.get_line()) ||
(usage.offsets[0].second.line == static_cast<unsigned>(end_iter.get_line()) && usage.offsets[0].second.index >= static_cast<unsigned>(end_iter.get_line_offset())))) (change.text_edits[0].range.end.line == static_cast<unsigned>(end_iter.get_line()) && change.text_edits[0].range.end.character >= static_cast<unsigned>(end_iter.get_line_offset()))))
replace_text(*usage.new_text); replace_text(change.text_edits[0].new_text);
else { else {
for(auto offset_it = usage.offsets.rbegin(); offset_it != usage.offsets.rend(); ++offset_it) { for(auto edit_it = change.text_edits.rbegin(); edit_it != change.text_edits.rend(); ++edit_it) {
auto start_iter = (*view_it)->get_iter_at_line_pos(offset_it->first.line, offset_it->first.index); auto start_iter = (*view_it)->get_iter_at_line_pos(edit_it->range.start.line, edit_it->range.start.character);
auto end_iter = (*view_it)->get_iter_at_line_pos(offset_it->second.line, offset_it->second.index); auto end_iter = (*view_it)->get_iter_at_line_pos(edit_it->range.end.line, edit_it->range.end.character);
buffer->erase(start_iter, end_iter); buffer->erase(start_iter, end_iter);
start_iter = (*view_it)->get_iter_at_line_pos(offset_it->first.line, offset_it->first.index); start_iter = (*view_it)->get_iter_at_line_pos(edit_it->range.start.line, edit_it->range.start.character);
if(usage.new_text) buffer->insert(start_iter, edit_it->new_text);
buffer->insert(start_iter, *usage.new_text);
else
buffer->insert(start_iter, text);
} }
} }
buffer->end_user_action(); buffer->end_user_action();
(*view_it)->save(); (*view_it)->save();
usages_renamed.emplace_back(&usage); changes_renamed.emplace_back(&change);
} }
else { else {
Glib::ustring buffer; Glib::ustring buffer;
{ {
std::ifstream stream(usage.path.string(), std::ifstream::binary); std::ifstream stream(change.uri, std::ifstream::binary);
if(stream) if(stream)
buffer.assign(std::istreambuf_iterator<char>(stream), std::istreambuf_iterator<char>()); buffer.assign(std::istreambuf_iterator<char>(stream), std::istreambuf_iterator<char>());
} }
std::ofstream stream(usage.path.string(), std::ifstream::binary); std::ofstream stream(change.uri, std::ifstream::binary);
if(!buffer.empty() && stream) { if(!buffer.empty() && stream) {
std::vector<size_t> lines_start_pos = {0}; std::vector<size_t> lines_start_pos = {0};
for(size_t c = 0; c < buffer.size(); ++c) { for(size_t c = 0; c < buffer.size(); ++c) {
if(buffer[c] == '\n') if(buffer[c] == '\n')
lines_start_pos.emplace_back(c + 1); lines_start_pos.emplace_back(c + 1);
} }
for(auto offset_it = usage.offsets.rbegin(); offset_it != usage.offsets.rend(); ++offset_it) { for(auto edit_it = change.text_edits.rbegin(); edit_it != change.text_edits.rend(); ++edit_it) {
auto start_line = offset_it->first.line; auto start_line = edit_it->range.start.line;
auto end_line = offset_it->second.line; auto end_line = edit_it->range.end.line;
if(start_line < lines_start_pos.size()) { if(start_line < lines_start_pos.size()) {
auto start = lines_start_pos[start_line] + offset_it->first.index; auto start = lines_start_pos[start_line] + edit_it->range.start.character;
unsigned end; unsigned end;
if(end_line >= lines_start_pos.size()) if(end_line >= lines_start_pos.size())
end = buffer.size(); end = buffer.size();
else else
end = lines_start_pos[end_line] + offset_it->second.index; end = lines_start_pos[end_line] + edit_it->range.end.character;
if(start < buffer.size() && end <= buffer.size()) { if(start < buffer.size() && end <= buffer.size()) {
if(usage.new_text) buffer.replace(start, end - start, edit_it->new_text);
buffer.replace(start, end - start, *usage.new_text);
else
buffer.replace(start, end - start, text);
} }
} }
} }
stream.write(buffer.data(), buffer.bytes()); stream.write(buffer.data(), buffer.bytes());
usages_renamed.emplace_back(&usage); changes_renamed.emplace_back(&change);
} }
else else
Terminal::get().print("Error: could not write to file " + usage.path.string() + '\n', true); Terminal::get().print("Error: could not write to file " + change.uri + '\n', true);
} }
} }
if(!usages_renamed.empty()) { if(!changes_renamed.empty()) {
Terminal::get().print("Renamed "); Terminal::get().print("Renamed ");
Terminal::get().print(previous_text, true); Terminal::get().print(previous_text, true);
Terminal::get().print(" to "); Terminal::get().print(" to ");
@ -926,41 +869,58 @@ void Source::LanguageProtocolView::escape_text(std::string &text) {
} }
void Source::LanguageProtocolView::update_diagnostics(std::vector<LanguageProtocol::Diagnostic> &&diagnostics) { void Source::LanguageProtocolView::update_diagnostics(std::vector<LanguageProtocol::Diagnostic> &&diagnostics) {
dispatcher.post([this, diagnostics = std::move(diagnostics)] { dispatcher.post([this, diagnostics = std::move(diagnostics)]() mutable {
clear_diagnostic_tooltips(); clear_diagnostic_tooltips();
num_warnings = 0; num_warnings = 0;
num_errors = 0; num_errors = 0;
num_fix_its = 0; num_fix_its = 0;
for(auto &diagnostic : diagnostics) { for(auto &diagnostic : diagnostics) {
if(diagnostic.uri == uri) { auto start = get_iter_at_line_pos(diagnostic.range.start.line, diagnostic.range.start.character);
auto start = get_iter_at_line_pos(diagnostic.offsets.first.line, diagnostic.offsets.first.index); auto end = get_iter_at_line_pos(diagnostic.range.end.line, diagnostic.range.end.character);
auto end = get_iter_at_line_pos(diagnostic.offsets.second.line, diagnostic.offsets.second.index);
if(start == end) { if(start == end) {
if(!end.is_end()) if(!end.is_end())
end.forward_char(); end.forward_char();
else else
start.forward_char(); start.forward_char();
} }
bool error = false;
std::string severity_tag_name;
if(diagnostic.severity >= 2) {
severity_tag_name = "def:warning";
num_warnings++;
}
else {
severity_tag_name = "def:error";
num_errors++;
error = true;
}
add_diagnostic_tooltip(start, end, diagnostic.spelling, error); bool error = false;
std::string severity_tag_name;
if(diagnostic.severity >= 2) {
severity_tag_name = "def:warning";
num_warnings++;
}
else {
severity_tag_name = "def:error";
num_errors++;
error = true;
} }
add_diagnostic_tooltip(start, end, error, [this, diagnostic = std::move(diagnostic)](const Glib::RefPtr<Gtk::TextBuffer> &buffer) {
buffer->insert_at_cursor(diagnostic.message);
for(size_t i = 0; i < diagnostic.related_informations.size(); ++i) {
auto link = filesystem::get_relative_path(diagnostic.related_informations[i].location.uri, file_path.parent_path()).string();
link += ':' + std::to_string(diagnostic.related_informations[i].location.range.start.line + 1);
link += ':' + std::to_string(diagnostic.related_informations[i].location.range.start.character + 1);
if(i == 0)
buffer->insert_at_cursor("\n\n");
buffer->insert_at_cursor(diagnostic.related_informations[i].message);
buffer->insert_at_cursor(": ");
auto pos = buffer->get_insert()->get_iter();
buffer->insert_with_tag(pos, link, "link");
if(i != diagnostic.related_informations.size() - 1)
buffer->insert_at_cursor("\n");
}
});
} }
for(auto &mark : flow_coverage_marks) for(auto &mark : flow_coverage_marks)
add_diagnostic_tooltip(mark.first->get_iter(), mark.second->get_iter(), flow_coverage_message, false); add_diagnostic_tooltip(mark.first->get_iter(), mark.second->get_iter(), false, [](const Glib::RefPtr<Gtk::TextBuffer> &buffer) {
buffer->insert_at_cursor(flow_coverage_message);
});
status_diagnostics = std::make_tuple(num_warnings + num_flow_coverage_warnings, num_errors, num_fix_its); status_diagnostics = std::make_tuple(num_warnings + num_flow_coverage_warnings, num_errors, num_fix_its);
if(update_status_diagnostics) if(update_status_diagnostics)
@ -1024,9 +984,16 @@ void Source::LanguageProtocolView::show_type_tooltips(const Gdk::Rectangle &rect
if(offset >= get_buffer()->get_char_count()) if(offset >= get_buffer()->get_char_count())
return; return;
type_tooltips.clear(); type_tooltips.clear();
auto create_tooltip_buffer = [this, offset, content]() {
auto tooltip_buffer = Gtk::TextBuffer::create(get_buffer()->get_tag_table()); auto start = get_buffer()->get_iter_at_offset(offset);
tooltip_buffer->insert(tooltip_buffer->get_insert()->get_iter(), *content); 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(this, get_buffer()->create_mark(start), get_buffer()->create_mark(end), [this, offset, content](const Glib::RefPtr<Gtk::TextBuffer> &buffer) {
buffer->insert(buffer->get_insert()->get_iter(), *content);
#ifdef JUCI_ENABLE_DEBUG #ifdef JUCI_ENABLE_DEBUG
if(language_id == "rust" && capabilities.definition) { if(language_id == "rust" && capabilities.definition) {
@ -1056,24 +1023,13 @@ void Source::LanguageProtocolView::show_type_tooltips(const Gdk::Rectangle &rect
next_char_iter++; next_char_iter++;
debug_value.replace(iter, next_char_iter, "?"); debug_value.replace(iter, next_char_iter, "?");
} }
tooltip_buffer->insert(tooltip_buffer->get_insert()->get_iter(), "\n\n" + value_type + ": " + debug_value.substr(pos + 3, debug_value.size() - (pos + 3) - 1)); buffer->insert(buffer->get_insert()->get_iter(), "\n\n" + value_type + ": " + debug_value.substr(pos + 3, debug_value.size() - (pos + 3) - 1));
} }
} }
} }
} }
#endif #endif
});
return tooltip_buffer;
};
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(); type_tooltips.show();
}); });
} }
@ -1095,33 +1051,24 @@ void Source::LanguageProtocolView::tag_similar_symbols() {
static int request_count = 0; static int request_count = 0;
request_count++; request_count++;
auto current_request = request_count; auto current_request = request_count;
client->write_request(this, method, R"("textDocument":{"uri":")" + uri + R"("}, "position": {"line": )" + std::to_string(iter.get_line()) + ", \"character\": " + std::to_string(iter.get_line_offset()) + R"(}, "context": {"includeDeclaration": true})", [this, current_request](const boost::property_tree::ptree &result, bool error) { client->write_request(this, method, R"("textDocument":{"uri":"file://)" + file_path.string() + R"("}, "position": {"line": )" + std::to_string(iter.get_line()) + ", \"character\": " + std::to_string(iter.get_line_offset()) + R"(}, "context": {"includeDeclaration": true})", [this, current_request](const boost::property_tree::ptree &result, bool error) {
if(!error) { if(!error) {
std::vector<std::pair<Offset, Offset>> offsets; std::vector<LanguageProtocol::Range> ranges;
for(auto it = result.begin(); it != result.end(); ++it) { for(auto it = result.begin(); it != result.end(); ++it) {
if(capabilities.document_highlight || it->second.get<std::string>("uri", "") == uri) { try {
auto range_it = it->second.find("range"); if(capabilities.document_highlight || it->second.get<std::string>("uri") == file_path)
if(range_it != it->second.not_found()) { ranges.emplace_back(it->second.get_child("range"));
auto start_it = range_it->second.find("start"); }
auto end_it = range_it->second.find("end"); catch(...) {
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<unsigned>("line"), start_it->second.get<unsigned>("character")),
Offset(end_it->second.get<unsigned>("line"), end_it->second.get<unsigned>("character"))));
}
catch(...) {
}
}
}
} }
} }
dispatcher.post([this, offsets = std::move(offsets), current_request] { dispatcher.post([this, ranges = std::move(ranges), current_request] {
if(current_request != request_count) if(current_request != request_count)
return; return;
get_buffer()->remove_tag(similar_symbol_tag, get_buffer()->begin(), get_buffer()->end()); get_buffer()->remove_tag(similar_symbol_tag, get_buffer()->begin(), get_buffer()->end());
for(auto &pair : offsets) { for(auto &range : ranges) {
auto start = get_iter_at_line_pos(pair.first.line, pair.first.index); auto start = get_iter_at_line_pos(range.start.line, range.start.character);
auto end = get_iter_at_line_pos(pair.second.line, pair.second.index); auto end = get_iter_at_line_pos(range.end.line, range.end.character);
get_buffer()->apply_tag(similar_symbol_tag, start, end); get_buffer()->apply_tag(similar_symbol_tag, start, end);
} }
}); });
@ -1132,19 +1079,18 @@ void Source::LanguageProtocolView::tag_similar_symbols() {
Source::Offset Source::LanguageProtocolView::get_declaration(const Gtk::TextIter &iter) { Source::Offset Source::LanguageProtocolView::get_declaration(const Gtk::TextIter &iter) {
auto offset = std::make_shared<Offset>(); auto offset = std::make_shared<Offset>();
std::promise<void> result_processed; std::promise<void> result_processed;
client->write_request(this, "textDocument/definition", R"("textDocument":{"uri":")" + uri + R"("}, "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) { client->write_request(this, "textDocument/definition", R"("textDocument":{"uri":"file://)" + file_path.string() + R"("}, "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) { if(!error) {
for(auto it = result.begin(); it != result.end(); ++it) { for(auto it = result.begin(); it != result.end(); ++it) {
auto uri = it->second.get<std::string>("uri", ""); try {
if(uri.compare(0, 7, "file://") == 0) LanguageProtocol::Location location(it->second);
uri.erase(0, 7); offset->file_path = std::move(location.uri);
auto range = it->second.find("range"); offset->line = location.range.start.line;
if(range != it->second.not_found()) { offset->index = location.range.start.character;
auto start = range->second.find("start"); break; // TODO: can a language server return several definitions?
if(start != range->second.not_found()) }
*offset = Offset(start->second.get<unsigned>("line", 0), start->second.get<unsigned>("character", 0), uri); catch(...) {
} }
break; // TODO: can a language server return several definitions?
} }
} }
result_processed.set_value(); result_processed.set_value();
@ -1255,7 +1201,7 @@ void Source::LanguageProtocolView::setup_autocomplete() {
autocomplete_comment.clear(); autocomplete_comment.clear();
autocomplete_insert.clear(); autocomplete_insert.clear();
std::promise<void> result_processed; std::promise<void> result_processed;
client->write_request(this, "textDocument/completion", 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) { client->write_request(this, "textDocument/completion", R"("textDocument":{"uri":"file://)" + file_path.string() + 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) { if(!error) {
auto begin = result.begin(); // rust language server is bugged auto begin = result.begin(); // rust language server is bugged
auto end = result.end(); auto end = result.end();
@ -1445,7 +1391,9 @@ void Source::LanguageProtocolView::add_flow_coverage_tooltips(bool called_in_thr
auto end_pt = it->second.get_child("end"); auto end_pt = it->second.get_child("end");
auto end = get_iter_at_line_offset(end_pt.get<int>("line") - 1, end_pt.get<int>("column")); auto end = get_iter_at_line_offset(end_pt.get<int>("line") - 1, end_pt.get<int>("column"));
add_diagnostic_tooltip(start, end, flow_coverage_message, false); add_diagnostic_tooltip(start, end, false, [](const Glib::RefPtr<Gtk::TextBuffer> &buffer) {
buffer->insert_at_cursor(flow_coverage_message);
});
++num_flow_coverage_warnings; ++num_flow_coverage_warnings;
flow_coverage_marks.emplace_back(get_buffer()->create_mark(start), get_buffer()->create_mark(end)); flow_coverage_marks.emplace_back(get_buffer()->create_mark(start), get_buffer()->create_mark(end));

64
src/source_language_protocol.h

@ -14,12 +14,65 @@ namespace Source {
} }
namespace LanguageProtocol { namespace LanguageProtocol {
class Offset {
public:
Offset(const boost::property_tree::ptree &pt) : line(pt.get<unsigned>("line")),
character(pt.get<unsigned>("character")) {}
unsigned line;
unsigned character;
};
class Range {
public:
Range(const boost::property_tree::ptree &pt) : start(pt.get_child("start")),
end(pt.get_child("end")) {}
Range() = default;
Offset start, end;
};
class Location {
public:
Location(const boost::property_tree::ptree &pt, std::string uri_ = {}) : range(pt.get_child("range")) {
if(uri_.empty()) {
uri = pt.get<std::string>("uri");
uri.erase(0, 7);
}
else
uri = std::move(uri_);
}
std::string uri;
Range range;
};
class Diagnostic { class Diagnostic {
public: public:
std::string spelling; class RelatedInformation {
std::pair<Source::Offset, Source::Offset> offsets; public:
RelatedInformation(const boost::property_tree::ptree &pt) : message(pt.get<std::string>("message")),
location(pt.get_child("location")) {}
std::string message;
Location location;
};
Diagnostic(const boost::property_tree::ptree &pt) : message(pt.get<std::string>("message")),
range(pt.get_child("range")),
severity(pt.get<unsigned>("severity", 0)) {
auto related_information_it = pt.get_child("relatedInformation", boost::property_tree::ptree());
for(auto it = related_information_it.begin(); it != related_information_it.end(); ++it)
related_informations.emplace_back(it->second);
}
std::string message;
Range range;
unsigned severity; unsigned severity;
std::string uri; std::vector<RelatedInformation> related_informations;
};
class TextEdit {
public:
TextEdit(const boost::property_tree::ptree &pt, std::string new_text_ = {}) : range(pt.get_child("range")),
new_text(new_text_.empty() ? pt.get<std::string>("newText") : std::move(new_text_)) {}
Range range;
std::string new_text;
}; };
class Capabilities { class Capabilities {
@ -86,9 +139,11 @@ namespace Source {
class LanguageProtocolView : public View { class LanguageProtocolView : public View {
public: public:
LanguageProtocolView(const boost::filesystem::path &file_path, const Glib::RefPtr<Gsv::Language> &language, std::string language_id_); LanguageProtocolView(const boost::filesystem::path &file_path, const Glib::RefPtr<Gsv::Language> &language, std::string language_id_);
void initialize(bool setup);
void close();
~LanguageProtocolView() override; ~LanguageProtocolView() override;
std::string uri;
void rename(const boost::filesystem::path &path) override;
bool save() override; bool save() override;
void update_diagnostics(std::vector<LanguageProtocol::Diagnostic> &&diagnostics); void update_diagnostics(std::vector<LanguageProtocol::Diagnostic> &&diagnostics);
@ -125,7 +180,6 @@ namespace Source {
boost::filesystem::path flow_coverage_executable; boost::filesystem::path flow_coverage_executable;
std::vector<std::pair<Glib::RefPtr<Gtk::TextMark>, Glib::RefPtr<Gtk::TextMark>>> flow_coverage_marks; std::vector<std::pair<Glib::RefPtr<Gtk::TextMark>, Glib::RefPtr<Gtk::TextMark>>> flow_coverage_marks;
const std::string flow_coverage_message = "Not covered by Flow";
size_t num_warnings = 0, num_errors = 0, num_fix_its = 0, num_flow_coverage_warnings = 0; size_t num_warnings = 0, num_errors = 0, num_fix_its = 0, num_flow_coverage_warnings = 0;
void add_flow_coverage_tooltips(bool called_in_thread); void add_flow_coverage_tooltips(bool called_in_thread);
}; };

14
src/terminal.cc

@ -354,22 +354,10 @@ bool Terminal::on_button_press_event(GdkEventButton *button_event) {
} }
auto link = find_link(get_buffer()->get_text(start_iter, end_iter).raw()); auto link = find_link(get_buffer()->get_text(start_iter, end_iter).raw());
if(std::get<0>(link) != static_cast<size_t>(-1)) { if(std::get<0>(link) != static_cast<size_t>(-1)) {
boost::filesystem::path path = std::get<2>(link); auto path = filesystem::get_long_path(std::get<2>(link));
std::string line = std::get<3>(link); std::string line = std::get<3>(link);
std::string index = std::get<4>(link); std::string index = std::get<4>(link);
if(!path.empty() && *path.begin() == "~") { // boost::filesystem does not recognize ~
boost::filesystem::path corrected_path;
corrected_path = filesystem::get_home_path();
if(!corrected_path.empty()) {
auto it = path.begin();
++it;
for(; it != path.end(); ++it)
corrected_path /= *it;
path = corrected_path;
}
}
if(path.is_relative()) { if(path.is_relative()) {
if(Project::current) { if(Project::current) {
if(boost::filesystem::exists(Project::current->build->get_default_path() / path)) if(boost::filesystem::exists(Project::current->build->get_default_path() / path))

74
src/tooltips.cc

@ -1,12 +1,15 @@
#include "tooltips.h" #include "tooltips.h"
#include "filesystem.h"
#include "notebook.h"
#include "selection_dialog.h" #include "selection_dialog.h"
#include <regex>
std::set<Tooltip *> Tooltips::shown_tooltips; std::set<Tooltip *> Tooltips::shown_tooltips;
Gdk::Rectangle Tooltips::drawn_tooltips_rectangle = Gdk::Rectangle(); Gdk::Rectangle Tooltips::drawn_tooltips_rectangle = Gdk::Rectangle();
Tooltip::Tooltip(std::function<Glib::RefPtr<Gtk::TextBuffer>()> create_tooltip_buffer_, Gtk::TextView *text_view, Tooltip::Tooltip(Gtk::TextView *text_view, Glib::RefPtr<Gtk::TextBuffer::Mark> start_mark_,
Glib::RefPtr<Gtk::TextBuffer::Mark> start_mark_, Glib::RefPtr<Gtk::TextBuffer::Mark> end_mark_) Glib::RefPtr<Gtk::TextBuffer::Mark> end_mark_, std::function<void(const Glib::RefPtr<Gtk::TextBuffer> &)> set_buffer_)
: start_mark(std::move(start_mark_)), end_mark(std::move(end_mark_)), create_tooltip_buffer(std::move(create_tooltip_buffer_)), text_view(text_view) {} : start_mark(std::move(start_mark_)), end_mark(std::move(end_mark_)), text_view(text_view), set_buffer(std::move(set_buffer_)) {}
Tooltip::~Tooltip() { Tooltip::~Tooltip() {
Tooltips::shown_tooltips.erase(this); Tooltips::shown_tooltips.erase(this);
@ -70,13 +73,76 @@ void Tooltip::show(bool disregard_drawn, const std::function<void()> &on_motion)
box->get_style_context()->add_class("juci_tooltip_box"); box->get_style_context()->add_class("juci_tooltip_box");
window->add(*box); window->add(*box);
text_buffer = create_tooltip_buffer(); if(text_view) {
text_buffer = Gtk::TextBuffer::create(text_view->get_buffer()->get_tag_table());
link_tag = text_buffer->get_tag_table()->lookup("link");
}
else {
text_buffer = Gtk::TextBuffer::create();
link_tag = text_buffer->create_tag("link");
link_tag->property_underline() = Pango::Underline::UNDERLINE_SINGLE;
link_tag->property_foreground_rgba() = window->get_style_context()->get_color(Gtk::StateFlags::STATE_FLAG_LINK);
}
set_buffer(text_buffer);
wrap_lines(); wrap_lines();
auto tooltip_text_view = Gtk::manage(new Gtk::TextView(text_buffer)); auto tooltip_text_view = Gtk::manage(new Gtk::TextView(text_buffer));
tooltip_text_view->get_style_context()->add_class("juci_tooltip_text_view"); tooltip_text_view->get_style_context()->add_class("juci_tooltip_text_view");
tooltip_text_view->set_editable(false); tooltip_text_view->set_editable(false);
static auto link_mouse_cursor = Gdk::Cursor::create(Gdk::CursorType::HAND1);
static auto default_mouse_cursor = Gdk::Cursor::create(Gdk::CursorType::XTERM);
tooltip_text_view->signal_motion_notify_event().connect([this, tooltip_text_view](GdkEventMotion *event) {
Gtk::TextIter iter;
int location_x, location_y;
tooltip_text_view->window_to_buffer_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, event->x, event->y, location_x, location_y);
tooltip_text_view->get_iter_at_location(iter, location_x, location_y);
if(iter.has_tag(link_tag))
tooltip_text_view->get_window(Gtk::TextWindowType::TEXT_WINDOW_TEXT)->set_cursor(link_mouse_cursor);
else
tooltip_text_view->get_window(Gtk::TextWindowType::TEXT_WINDOW_TEXT)->set_cursor(default_mouse_cursor);
return false;
});
tooltip_text_view->signal_button_press_event().connect([this, tooltip_text_view](GdkEventButton *event) {
if(event->type == GDK_BUTTON_PRESS && event->button == GDK_BUTTON_PRIMARY) {
Gtk::TextIter iter;
int location_x, location_y;
tooltip_text_view->window_to_buffer_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, event->x, event->y, location_x, location_y);
tooltip_text_view->get_iter_at_location(iter, location_x, location_y);
if(iter.has_tag(link_tag)) {
auto start = iter;
start.backward_to_tag_toggle(link_tag);
auto end = iter;
end.forward_to_tag_toggle(link_tag);
std::string text = tooltip_text_view->get_buffer()->get_text(start, end);
static std::regex regex("^([^:]+):([^:]+):([^:]+)$");
std::smatch sm;
if(std::regex_match(text, sm, regex)) {
auto path = boost::filesystem::path(sm[1].str());
if(auto view = dynamic_cast<Source::View *>(text_view))
path = filesystem::get_normal_path(view->file_path.parent_path() / path);
if(boost::filesystem::is_regular_file(path)) {
Notebook::get().open(path);
if(auto view = Notebook::get().get_current_view()) {
try {
auto line = atoi(sm[2].str().c_str()) - 1;
auto offset = atoi(sm[3].str().c_str()) - 1;
view->place_cursor_at_line_offset(line, offset);
view->scroll_to_cursor_delayed(view, true, false);
}
catch(...) {
}
}
return true;
}
}
}
}
return false;
});
#if GTK_VERSION_GE(3, 20) #if GTK_VERSION_GE(3, 20)
box->add(*tooltip_text_view); box->add(*tooltip_text_view);
#else #else

8
src/tooltips.h

@ -7,8 +7,8 @@
class Tooltip { class Tooltip {
public: public:
Tooltip(std::function<Glib::RefPtr<Gtk::TextBuffer>()> create_tooltip_buffer_, Gtk::TextView *text_view, Glib::RefPtr<Gtk::TextBuffer::Mark> start_mark_, Glib::RefPtr<Gtk::TextBuffer::Mark> end_mark_); Tooltip(Gtk::TextView *text_view, Glib::RefPtr<Gtk::TextBuffer::Mark> start_mark_, Glib::RefPtr<Gtk::TextBuffer::Mark> end_mark_, std::function<void(const Glib::RefPtr<Gtk::TextBuffer> &)> set_buffer_);
Tooltip(std::function<Glib::RefPtr<Gtk::TextBuffer>()> create_tooltip_buffer_) : Tooltip(std::move(create_tooltip_buffer_), nullptr, Glib::RefPtr<Gtk::TextBuffer::Mark>(), Glib::RefPtr<Gtk::TextBuffer::Mark>()) {} Tooltip(std::function<void(const Glib::RefPtr<Gtk::TextBuffer> &)> set_buffer_) : Tooltip(nullptr, Glib::RefPtr<Gtk::TextBuffer::Mark>(), Glib::RefPtr<Gtk::TextBuffer::Mark>(), std::move(set_buffer_)) {}
~Tooltip(); ~Tooltip();
void update(); void update();
@ -25,12 +25,14 @@ private:
std::unique_ptr<Gtk::Window> window; std::unique_ptr<Gtk::Window> window;
void wrap_lines(); void wrap_lines();
std::function<Glib::RefPtr<Gtk::TextBuffer>()> create_tooltip_buffer;
Gtk::TextView *text_view; Gtk::TextView *text_view;
std::function<void(const Glib::RefPtr<Gtk::TextBuffer> &)> set_buffer;
std::pair<int, int> size; std::pair<int, int> size;
Gdk::Rectangle rectangle; Gdk::Rectangle rectangle;
bool shown = false; bool shown = false;
Glib::RefPtr<Gtk::TextTag> link_tag;
}; };
class Tooltips { class Tooltips {

2
src/window.cc

@ -817,8 +817,6 @@ void Window::set_menu_actions() {
} }
for(auto &location : locations) { for(auto &location : locations) {
auto path = filesystem::get_relative_path(filesystem::get_normal_path(location.file_path), project_path); auto path = filesystem::get_relative_path(filesystem::get_normal_path(location.file_path), project_path);
if(path.empty())
path = location.file_path.filename();
auto row = path.string() + ":" + std::to_string(location.line + 1); auto row = path.string() + ":" + std::to_string(location.line + 1);
rows.emplace_back(location); rows.emplace_back(location);
SelectionDialog::get()->add_row(row); SelectionDialog::get()->add_row(row);

5
tests/filesystem_test.cc

@ -78,5 +78,10 @@ int main() {
{ {
boost::filesystem::path relative_path = "filesystem_test.cc"; boost::filesystem::path relative_path = "filesystem_test.cc";
g_assert(filesystem::get_relative_path(tests_path / relative_path, tests_path) == relative_path); g_assert(filesystem::get_relative_path(tests_path / relative_path, tests_path) == relative_path);
g_assert(filesystem::get_relative_path(tests_path / "test" / relative_path, tests_path) == boost::filesystem::path("test") / relative_path);
g_assert(filesystem::get_relative_path("/test/test/test.cc", "/test/base") == boost::filesystem::path("..") / "test" / "test.cc");
g_assert(filesystem::get_relative_path("/test/test/test/test.cc", "/test/base") == boost::filesystem::path("..") / "test" / "test" / "test.cc");
g_assert(filesystem::get_relative_path("/test2/test.cc", "/test/base") == boost::filesystem::path("..") / ".." / "test2" / "test.cc");
} }
} }

7
tests/stubs/tooltips.cc

@ -2,10 +2,9 @@
Gdk::Rectangle Tooltips::drawn_tooltips_rectangle = Gdk::Rectangle(); Gdk::Rectangle Tooltips::drawn_tooltips_rectangle = Gdk::Rectangle();
Tooltip::Tooltip(std::function<Glib::RefPtr<Gtk::TextBuffer>()> create_tooltip_buffer, Tooltip::Tooltip(Gtk::TextView *text_view,
Gtk::TextView *text_view, Glib::RefPtr<Gtk::TextBuffer::Mark> start_mark, Glib::RefPtr<Gtk::TextBuffer::Mark> end_mark,
Glib::RefPtr<Gtk::TextBuffer::Mark> start_mark, std::function<void(const Glib::RefPtr<Gtk::TextBuffer> &)> create_tooltip_buffer) : text_view(text_view) {}
Glib::RefPtr<Gtk::TextBuffer::Mark> end_mark) : text_view(text_view) {}
Tooltip::~Tooltip() {} Tooltip::~Tooltip() {}

Loading…
Cancel
Save