diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 713f62f..222a7aa 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -21,6 +21,7 @@ set(JUCI_SHARED_FILES source_language_protocol.cc source_spellcheck.cc terminal.cc + tooltips.cc usages_clang.cc utility.cc ) @@ -51,7 +52,6 @@ set(JUCI_FILES notebook.cc project.cc selection_dialog.cc - tooltips.cc window.cc ) if(APPLE) diff --git a/src/source_language_protocol.cc b/src/source_language_protocol.cc index b807ae0..c364228 100644 --- a/src/source_language_protocol.cc +++ b/src/source_language_protocol.cc @@ -1012,30 +1012,36 @@ void Source::LanguageProtocolView::show_type_tooltips(const Gdk::Rectangle &rect client->write_request(this, "textDocument/hover", R"("textDocument": {"uri":")" + uri + R"("}, "position": {"line": )" + std::to_string(iter.get_line()) + ", \"character\": " + std::to_string(iter.get_line_offset()) + "}", [this, offset, current_request](const boost::property_tree::ptree &result, bool error) { if(!error) { // hover result structure vary significantly from the different language servers - std::string tooltip; + struct Content { + std::string value; + bool markdown; + }; + std::vector contents; auto contents_pt = result.get_child_optional("contents"); if(!contents_pt) return; - tooltip = contents_pt->get_value(""); - if(tooltip.empty()) { + auto value = contents_pt->get_value(""); + if(!value.empty()) + contents.emplace_back(Content{value, true}); + else { auto value_pt = contents_pt->get_optional("value"); if(value_pt) - tooltip = *value_pt; + contents.emplace_back(Content{*value_pt, contents_pt->get("kind", "") == "markdown"}); else { for(auto it = contents_pt->begin(); it != contents_pt->end(); ++it) { auto value = it->second.get("value", ""); - if(value.empty()) - value = it->second.get_value(""); if(!value.empty()) - tooltip.insert(0, value + (tooltip.empty() ? "" : "\n\n")); + contents.emplace_back(Content{value, contents_pt->get("kind", "") == "markdown"}); + else { + value = it->second.get_value(""); + if(!value.empty()) + contents.emplace_back(Content{value, true}); + } } } } - if(!tooltip.empty()) { - while(!tooltip.empty() && tooltip.back() == '\n') { - tooltip.pop_back(); // Remove unnecessary newlines - } - dispatcher.post([this, offset, tooltip_text = std::move(tooltip), current_request] { + if(!contents.empty()) { + dispatcher.post([this, offset, contents = std::move(contents), current_request] { if(current_request != request_count) return; if(Notebook::get().get_current_view() != this) @@ -1051,8 +1057,17 @@ void Source::LanguageProtocolView::show_type_tooltips(const Gdk::Rectangle &rect } 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, tooltip_text = std::move(tooltip_text)](Tooltip &tooltip) { - tooltip.insert_with_links_tagged(tooltip_text); + type_tooltips.emplace_back(this, get_buffer()->create_mark(start), get_buffer()->create_mark(end), [this, offset, contents = std::move(contents)](Tooltip &tooltip) { + for(size_t i = 0; i < contents.size(); i++) { + if(i > 0) + tooltip.buffer->insert_at_cursor("\n\n"); + if(contents[i].markdown && language_id != "python") // TODO: python-language-server might support markdown in the future + tooltip.insert_markdown(contents[i].value); + else { + tooltip.insert_with_links_tagged(contents[i].value); + tooltip.remove_trailing_newlines(); + } + } #ifdef JUCI_ENABLE_DEBUG if(language_id == "rust" && capabilities.definition) { diff --git a/src/tooltips.cc b/src/tooltips.cc index 01edc1f..20e9b26 100644 --- a/src/tooltips.cc +++ b/src/tooltips.cc @@ -1,7 +1,10 @@ #include "tooltips.h" +#include "config.h" #include "filesystem.h" +#include "info.h" #include "notebook.h" #include "selection_dialog.h" +#include #include std::set Tooltips::shown_tooltips; @@ -48,7 +51,8 @@ void Tooltip::show(bool disregard_drawn, const std::function &on_motion) auto g_application = g_application_get_default(); auto gio_application = Glib::wrap(g_application, true); auto application = Glib::RefPtr::cast_static(gio_application); - window->set_transient_for(*application->get_active_window()); + if(auto active_window = application->get_active_window()) + window->set_transient_for(*active_window); window->set_type_hint(Gdk::WindowTypeHint::WINDOW_TYPE_HINT_TOOLTIP); @@ -96,6 +100,8 @@ void Tooltip::show(bool disregard_drawn, const std::function &on_motion) set_buffer(*this); + remove_trailing_newlines(); + wrap_lines(); auto tooltip_text_view = Gtk::manage(new Gtk::TextView(buffer)); @@ -123,19 +129,37 @@ void Tooltip::show(bool disregard_drawn, const std::function &on_motion) 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); + if(!start.starts_tag(link_tag)) + 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); + std::string link; + for(auto &tag : start.get_tags()) { + auto it = links.find(tag); + if(it != links.end()) { + link = it->second; + break; + } + it = reference_links.find(tag); + if(it != reference_links.end()) { + auto reference_it = references.find(it->second); + if(reference_it != references.end()) + link = reference_it->second; + break; + } + } + if(link.empty()) + link = text; - if(text.compare(0, 7, "http://") == 0 || text.compare(0, 8, "https://") == 0) { - Notebook::get().open_uri(text); + if(link.compare(0, 7, "http://") == 0 || link.compare(0, 8, "https://") == 0) { + Notebook::get().open_uri(link); return true; } static std::regex regex("^([^:]+):([^:]+):([^:]+)$"); std::smatch sm; - if(std::regex_match(text, sm, regex)) { + if(std::regex_match(link, sm, regex)) { auto path = boost::filesystem::path(sm[1].str()); if(auto view = dynamic_cast(text_view)) path = filesystem::get_normal_path(view->file_path.parent_path() / path); @@ -156,25 +180,47 @@ void Tooltip::show(bool disregard_drawn, const std::function &on_motion) return true; } } + + auto path = boost::filesystem::path(link); + if(auto view = dynamic_cast(text_view)) + path = filesystem::get_normal_path(view->file_path.parent_path() / path); + boost::system::error_code ec; + if(boost::filesystem::is_regular_file(path, ec)) { + Notebook::get().open(path); + return true; + } + Info::get().print("Could not open: " + link); } } return false; }); -#if GTK_VERSION_GE(3, 20) - box->add(*tooltip_text_view); -#else - auto box2 = Gtk::manage(new Gtk::Box()); - box2->pack_start(*tooltip_text_view, true, true, 3); - box->pack_start(*box2, true, true, 3); -#endif - auto layout = Pango::Layout::create(tooltip_text_view->get_pango_context()); layout->set_text(buffer->get_text()); layout->get_pixel_size(size.first, size.second); size.first += 6; // 2xpadding size.second += 8; // 2xpadding + 2 + // Add ScrolledWindow if needed + Gtk::Widget *widget = tooltip_text_view; + auto screen_height = Gdk::Screen::get_default()->get_height(); + if(size.second > screen_height - 6 /* 2xpadding */) { + auto scrolled_window = Gtk::manage(new Gtk::ScrolledWindow()); + scrolled_window->property_hscrollbar_policy() = Gtk::PolicyType::POLICY_NEVER; + scrolled_window->property_vscrollbar_policy() = Gtk::PolicyType::POLICY_AUTOMATIC; + scrolled_window->add(*tooltip_text_view); + scrolled_window->set_size_request(-1, screen_height - 6 /* 2xpadding */); + widget = scrolled_window; + } + +#if GTK_VERSION_GE(3, 20) + box->add(*widget); +#else + auto box2 = Gtk::manage(new Gtk::Box()); + box2->pack_start(*widget, true, true, 3); + box->pack_start(*box2, true, true, 3); +#endif + window->signal_realize().connect([this] { if(!text_view) { auto &dialog = SelectionDialog::get(); @@ -270,47 +316,50 @@ void Tooltip::hide(const std::pair &last_mouse_pos, const std::pairbegin(); + if(!iter) + return; - while(iter) { + while(true) { auto last_space = buffer->end(); - bool end = false; + bool long_line = true; for(unsigned c = 0; c <= 80; c++) { - if(!iter) { - end = true; - break; - } if(*iter == ' ') last_space = iter; if(iter.ends_line()) { - end = true; - iter.forward_char(); + long_line = false; break; } - iter.forward_char(); + if(!iter.forward_char()) + return; } - if(!end) { - while(!last_space && iter) { //If no space (word longer than 80) - iter.forward_char(); - if(iter && *iter == ' ') - last_space = iter; + if(long_line) { + if(!last_space) { // If word is longer than 80 + while(true) { + if(iter.ends_line()) + break; + if(*iter == ' ') { + last_space = iter; + break; + } + if(!iter.forward_char()) + return; + } } - if(iter && last_space) { + if(last_space) { auto mark = buffer->create_mark(last_space); - auto last_space_p = last_space; - last_space.forward_char(); - buffer->erase(last_space_p, last_space); + + auto next = last_space; + next.forward_char(); + buffer->erase(last_space, next); buffer->insert(mark->get_iter(), "\n"); iter = mark->get_iter(); - iter.forward_char(); - buffer->delete_mark(mark); } } + if(!iter.forward_char()) + return; } } @@ -320,17 +369,422 @@ void Tooltip::insert_with_links_tagged(const std::string &text) { std::sregex_iterator it(text.begin(), text.end(), http_regex); std::sregex_iterator end; size_t start_pos = 0; - if(it != end) { - auto link_tag = buffer->get_tag_table()->lookup("link"); - for(; it != end; ++it) { - buffer->insert(buffer->get_insert()->get_iter(), &text[start_pos], &text[it->position()]); - buffer->insert_with_tag(buffer->get_insert()->get_iter(), &text[it->position()], &text[it->position() + it->length()], link_tag); - start_pos = it->position() + it->length(); - } + for(; it != end; ++it) { + buffer->insert(buffer->get_insert()->get_iter(), &text[start_pos], &text[it->position()]); + buffer->insert_with_tag(buffer->get_insert()->get_iter(), &text[it->position()], &text[it->position() + it->length()], link_tag); + start_pos = it->position() + it->length(); } buffer->insert(buffer->get_insert()->get_iter(), &text[start_pos], &text[text.size()]); } +void Tooltip::insert_markdown(const std::string &input) { + if(!h1_tag) { + h1_tag = buffer->create_tag(); + h1_tag->property_weight() = Pango::WEIGHT_ULTRAHEAVY; + h1_tag->property_underline() = Pango::UNDERLINE_DOUBLE; + + h2_tag = buffer->create_tag(); + h2_tag->property_weight() = Pango::WEIGHT_ULTRAHEAVY; + h2_tag->property_underline() = Pango::UNDERLINE_SINGLE; + + h3_tag = buffer->create_tag(); + h3_tag->property_weight() = Pango::WEIGHT_ULTRAHEAVY; + + auto source_font_family = Pango::FontDescription(Config::get().source.font).get_family(); + + code_tag = buffer->create_tag(); + code_tag->property_family() = source_font_family; + auto background_rgba = Gdk::RGBA(); + auto normal_color = window->get_style_context()->get_color(Gtk::StateFlags::STATE_FLAG_NORMAL); + auto light_theme = (normal_color.get_red() + normal_color.get_green() + normal_color.get_blue()) / 3 < 0.5; + if(light_theme) + background_rgba.set_rgba(1.0, 1.0, 1.0, 0.4); + else + background_rgba.set_rgba(0.0, 0.0, 0.0, 0.2); + code_tag->property_background_rgba() = background_rgba; + + code_block_tag = buffer->create_tag(); + code_block_tag->property_family() = source_font_family; + code_block_tag->property_paragraph_background_rgba() = background_rgba; + + bold_tag = buffer->create_tag(); + bold_tag->property_weight() = Pango::WEIGHT_ULTRAHEAVY; + + italic_tag = buffer->create_tag(); + italic_tag->property_style() = Pango::Style::STYLE_ITALIC; + + strikethrough_tag = buffer->create_tag(); + strikethrough_tag->property_strikethrough() = true; + } + + size_t i = 0; + + auto forward_to = [&](const std::vector chars) { + for(; i < input.size(); i++) { + if(std::any_of(chars.begin(), chars.end(), [&input, &i](char chr) { return input[i] == chr; })) + return true; + } + return false; + }; + + auto forward_passed = [&](const std::vector chars) { + for(; i < input.size(); i++) { + if(std::none_of(chars.begin(), chars.end(), [&input, &i](char chr) { return input[i] == chr; })) + return true; + } + return false; + }; + + auto unescape = [&](size_t &i) { + if(input[i] == '\\') { + if(i + 1 < input.size()) + i++; + return true; + } + return false; + }; + + std::function insert_text = [&](size_t from, size_t to) { + auto i = from; + + std::string partial; + + auto is_whitespace_character = [&](size_t i) { + return input[i] == ' ' || input[i] == '\t' || input[i] == '\n' || input[i] == '\r' || input[i] == '\f'; + }; + + auto insert_emphasis = [&] { + if(i > from && !is_whitespace_character(i - 1)) // Do not emphasis: normal_text + return false; + auto i_saved = i; + std::string prefix; + for(; i < to && prefix.size() < 3 && (input[i] == '*' || input[i] == '_'); i++) + prefix += input[i]; + if(prefix.empty()) + return false; + if(prefix.size() == 2) + std::swap(prefix[0], prefix[1]); // To match: *_test_* + else if(prefix.size() == 3) + std::swap(prefix[0], prefix[2]); // To match: **_test_** + if(i < to && is_whitespace_character(i)) { // Do not emphasis for instance: 2 * 2 * 2 + i = i_saved; + return false; + } + insert_with_links_tagged(partial); + partial.clear(); + auto start = i; + for(; i < to; i++) { + if(!unescape(i)) { + if(input.compare(i, prefix.size(), prefix) == 0) { + if(i - 1 > from && is_whitespace_character(i - 1)) { // Do not emphasis _test in: _test _test_ + i = i_saved; + return false; + } + if(i + prefix.size() < to && !is_whitespace_character(i + prefix.size())) // Emphasis italic_text in: _italic_text_ + continue; + break; + } + } + } + if(i == to) { + i = i_saved; + return false; + } + auto start_offset = buffer->get_insert()->get_iter().get_offset(); + insert_text(start, i); + if(prefix.size() == 1) + buffer->apply_tag(italic_tag, buffer->get_iter_at_offset(start_offset), buffer->get_insert()->get_iter()); + else if(prefix.size() == 2) + buffer->apply_tag(bold_tag, buffer->get_iter_at_offset(start_offset), buffer->get_insert()->get_iter()); + else { + buffer->apply_tag(italic_tag, buffer->get_iter_at_offset(start_offset), buffer->get_insert()->get_iter()); + buffer->apply_tag(bold_tag, buffer->get_iter_at_offset(start_offset), buffer->get_insert()->get_iter()); + } + i += prefix.size() - 1; + return true; + }; + + auto insert_strikethrough = [&] { + if(input.compare(i, 2, "~~") == 0) { + insert_with_links_tagged(partial); + partial.clear(); + auto i_saved = i; + i += 2; + if(i < to) { + auto start = i; + for(; i < to; i++) { + if(!unescape(i) && input.compare(i, 2, "~~") == 0) + break; + } + if(i == to) { + i = i_saved; + return false; + } + auto start_offset = buffer->get_insert()->get_iter().get_offset(); + insert_text(start, i); + buffer->apply_tag(strikethrough_tag, buffer->get_iter_at_offset(start_offset), buffer->get_insert()->get_iter()); + i++; + return true; + } + i = i_saved; + } + return false; + }; + + auto insert_code = [&] { + if(input[i] == '`') { + insert_with_links_tagged(partial); + partial.clear(); + auto i_saved = i; + i++; + if(i < to) { + bool escaped = false; + if(input[i] == '`') { + escaped = true; + i++; + } + if(i < to) { + auto start = i; + for(; i < to; i++) { + if(input[i] == '`') { + if(!escaped) + break; + if(i + 1 < to && input[i + 1] == '`') + break; + } + } + if(i == to) { + i = i_saved; + return false; + } + buffer->insert_with_tag(buffer->get_insert()->get_iter(), input.substr(start, i - start), code_tag); + if(escaped) + i++; + return true; + } + } + i = i_saved; + } + return false; + }; + + auto insert_link = [&] { + if(input[i] == '[') { + insert_with_links_tagged(partial); + partial.clear(); + auto i_saved = i; + i++; + if(i < to) { + auto text_start = i; + for(; i < to; i++) { + if(!unescape(i) && input[i] == ']') + break; + } + if(i == to) { + i = i_saved; + return false; + } + auto text_end = i; + i++; + if(i < to && input[i] == '(') { + i++; + auto link_start = i; + for(; i < to; i++) { + if(!unescape(i) && input[i] == ')') + break; + } + if(i == to) { + i = i_saved; + return false; + } + auto start_offset = buffer->get_insert()->get_iter().get_offset(); + insert_text(text_start, text_end); + auto start = buffer->get_iter_at_offset(start_offset); + auto end = buffer->get_insert()->get_iter(); + buffer->apply_tag(link_tag, start, end); + auto tag = buffer->create_tag(); + buffer->apply_tag(tag, start, end); + links.emplace(tag, input.substr(link_start, i - link_start)); + return true; + } + else if(i < to && input[i] == '[') { + i++; + auto link_start = i; + for(; i < to; i++) { + if(!unescape(i) && input[i] == ']') + break; + } + if(i == to) { + i = i_saved; + return false; + } + auto start_offset = buffer->get_insert()->get_iter().get_offset(); + insert_text(text_start, text_end); + auto start = buffer->get_iter_at_offset(start_offset); + auto end = buffer->get_insert()->get_iter(); + buffer->apply_tag(link_tag, start, end); + auto tag = buffer->create_tag(); + buffer->apply_tag(tag, start, end); + reference_links.emplace(tag, input.substr(link_start, i - link_start)); + return true; + } + else { + auto start_offset = buffer->get_insert()->get_iter().get_offset(); + insert_text(text_start, text_end); + auto start = buffer->get_iter_at_offset(start_offset); + auto end = buffer->get_insert()->get_iter(); + buffer->apply_tag(link_tag, start, end); + auto tag = buffer->create_tag(); + buffer->apply_tag(tag, start, end); + reference_links.emplace(tag, buffer->get_text(start, end)); + i = text_end; + return true; + } + } + i = i_saved; + } + return false; + }; + + for(; i < to; i++) { + if(!unescape(i) && (insert_code() || insert_emphasis() || insert_strikethrough() || insert_link())) + continue; + partial += input[i]; + } + insert_with_links_tagged(partial); + }; + + auto insert_header = [&] { + size_t end_next_line = std::string::npos; + int header = 0; + for(; i < input.size() && header < 6 && input[i] == '#'; i++) + header++; + if(header == 0) { + auto i_saved = i; + forward_to({'\n'}); + i++; + if(i < input.size() && (input[i] == '=' || input[i] == '-')) { + forward_passed({input[i]}); + if(i == input.size() || input[i] == '\n') { + if(input[i - 1] == '=') + header = 1; + else + header = 2; + end_next_line = i; + } + } + i = i_saved; + } + if(header == 0) + return false; + forward_passed({' '}); + auto start = i; + forward_to({'\n'}); + auto end = buffer->end(); + if(end.backward_char() && !end.starts_line()) + buffer->insert_at_cursor("\n"); + auto start_offset = buffer->get_insert()->get_iter().get_offset(); + insert_text(start, i); + if(header == 1) + buffer->apply_tag(h1_tag, buffer->get_iter_at_offset(start_offset), buffer->get_insert()->get_iter()); + else if(header == 2) + buffer->apply_tag(h2_tag, buffer->get_iter_at_offset(start_offset), buffer->get_insert()->get_iter()); + else if(header == 3) + buffer->apply_tag(h3_tag, buffer->get_iter_at_offset(start_offset), buffer->get_insert()->get_iter()); + buffer->insert_at_cursor("\n\n"); + if(end_next_line != std::string::npos) + i = end_next_line; + return true; + }; + + + auto insert_code_block = [&] { + if(input.compare(i, 3, "```") == 0) { + auto i_saved = i; + if(forward_to({'\n'})) { + i++; + if(i < input.size()) { + auto start = i; + while(i < input.size() && !(input[i - 1] == '\n' && input.compare(i, 3, "```") == 0)) + i++; + if(i == input.size()) { + i = i_saved; + return false; + } + auto end = buffer->end(); + if(end.backward_char() && !end.starts_line()) + buffer->insert_at_cursor("\n"); + buffer->insert_with_tag(buffer->get_insert()->get_iter(), input.substr(start, i - start), code_block_tag); + buffer->insert_at_cursor("\n"); + i += 3; + return true; + } + } + i = i_saved; + } + return false; + }; + + auto insert_reference = [&] { + if(input[i] == '[') { + auto i_saved = i; + i++; + if(i < input.size()) { + auto reference_start = i; + for(; i < input.size(); i++) { + if(!unescape(i) && input[i] == ']') + break; + } + if(i == input.size()) { + i = i_saved; + return false; + } + auto reference_end = i; + i++; + if(forward_passed({' ', '\n'}) && input[i] == ':') { + i++; + if(forward_passed({' ', '\n'})) { + auto link_start = i; + forward_to({' ', '\n'}); + auto start_offset = buffer->get_insert()->get_iter().get_offset(); + insert_text(reference_start, reference_end); + auto start = buffer->get_iter_at_offset(start_offset); + auto end = buffer->get_insert()->get_iter(); + references.emplace(buffer->get_text(start, end), input.substr(link_start, i - link_start)); + buffer->erase(start, end); + return true; + } + } + } + i = i_saved; + } + return false; + }; + + while(forward_passed({'\n'})) { + if(insert_header() || insert_code_block() || insert_reference()) + continue; + // Insert paragraph: + auto start = i; + for(; forward_to({'\n'}); i++) { + if(i + 1 < input.size() && (input[i + 1] == '\n' || input[i + 1] == '#' || input.compare(i + 1, 3, "```") == 0)) + break; + } + insert_text(start, i); + if(i < input.size()) + buffer->insert_at_cursor("\n\n"); + } + + remove_trailing_newlines(); +} + +void Tooltip::remove_trailing_newlines() { + auto end = buffer->end(); + while(end.starts_line() && end.backward_char()) { + } + buffer->erase(end, buffer->end()); +} + void Tooltips::show(const Gdk::Rectangle &rectangle, bool disregard_drawn) { for(auto &tooltip : tooltip_list) { tooltip.update(); diff --git a/src/tooltips.h b/src/tooltips.h index 7afb40a..5da3d7f 100644 --- a/src/tooltips.h +++ b/src/tooltips.h @@ -4,6 +4,7 @@ #include #include #include +#include class Tooltip { public: @@ -22,6 +23,9 @@ public: Glib::RefPtr buffer; void insert_with_links_tagged(const std::string &text); + void insert_markdown(const std::string &text); + // Remove empty lines at end of buffer + void remove_trailing_newlines(); private: std::unique_ptr window; @@ -35,6 +39,18 @@ private: bool shown = false; Glib::RefPtr link_tag; + Glib::RefPtr h1_tag; + Glib::RefPtr h2_tag; + Glib::RefPtr h3_tag; + Glib::RefPtr code_tag; + Glib::RefPtr code_block_tag; + Glib::RefPtr bold_tag; + Glib::RefPtr italic_tag; + Glib::RefPtr strikethrough_tag; + + std::map, std::string> links; + std::map, std::string> reference_links; + std::unordered_map references; }; class Tooltips { diff --git a/src/window.cc b/src/window.cc index ef9180b..7716453 100644 --- a/src/window.cc +++ b/src/window.cc @@ -804,7 +804,6 @@ void Window::set_menu_actions() { SelectionDialog::create(view, true, true); else SelectionDialog::create(true, true); - std::string line; std::string current_path; unsigned int current_line = 0; @@ -813,14 +812,15 @@ void Window::set_menu_actions() { current_path = filesystem::get_relative_path(view->file_path, grep->project_path).string(); current_line = view->get_buffer()->get_insert()->get_iter().get_line(); } - bool set_cursor_at_path = true; + bool cursor_set = false; + std::string line; while(std::getline(grep->output, line)) { auto location = grep->get_location(std::move(line), true, false, current_path); SelectionDialog::get()->add_row(location.markup); if(view && location) { - if(set_cursor_at_path) { + if(!cursor_set) { SelectionDialog::get()->set_cursor_at_last_row(); - set_cursor_at_path = false; + cursor_set = true; } else { if(current_line >= location.line) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 29063bd..c9e162a 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -19,7 +19,6 @@ add_library(test_stubs OBJECT stubs/notebook.cc stubs/project.cc stubs/selection_dialog.cc - stubs/tooltips.cc ) add_executable(process_test process_test.cc $) @@ -81,3 +80,7 @@ add_executable(ctags_grep_test ctags_grep_test.cc $) target_link_libraries(ctags_grep_test juci_shared) add_test(ctags_grep_test ctags_grep_test) +add_executable(tooltips_test tooltips_test.cc $) +target_link_libraries(tooltips_test juci_shared) +add_test(tooltips_test tooltips_test) + diff --git a/tests/stubs/notebook.cc b/tests/stubs/notebook.cc index f045b8f..d61218f 100644 --- a/tests/stubs/notebook.cc +++ b/tests/stubs/notebook.cc @@ -7,3 +7,5 @@ Source::View *Notebook::get_current_view() { } void Notebook::open(const boost::filesystem::path &file_path, Position position) {} + +void Notebook::open_uri(const std::string &uri) {} diff --git a/tests/stubs/tooltips.cc b/tests/stubs/tooltips.cc deleted file mode 100644 index 719dac2..0000000 --- a/tests/stubs/tooltips.cc +++ /dev/null @@ -1,17 +0,0 @@ -#include "tooltips.h" - -Gdk::Rectangle Tooltips::drawn_tooltips_rectangle = Gdk::Rectangle(); - -Tooltip::Tooltip(Gtk::TextView *text_view, - Glib::RefPtr start_mark, Glib::RefPtr end_mark, - std::function create_tooltip_buffer) : text_view(text_view) {} - -Tooltip::~Tooltip() {} - -void Tooltip::insert_with_links_tagged(const std::string &) {} - -void Tooltips::show(Gdk::Rectangle const &, bool) {} - -void Tooltips::show(bool) {} - -void Tooltips::hide(const std::pair &, const std::pair &) {} diff --git a/tests/tooltips_test.cc b/tests/tooltips_test.cc new file mode 100644 index 0000000..d33d9e6 --- /dev/null +++ b/tests/tooltips_test.cc @@ -0,0 +1,398 @@ +#include "tooltips.h" +#include +#include + +int main() { + auto app = Gtk::Application::create(); + + auto get_markdown_tooltip = [](const std::string &input) { + auto tooltip = std::make_unique([&](Tooltip &tooltip) { + tooltip.insert_markdown(input); + }); + tooltip->show(); + return tooltip; + }; + + // Testing insert_markdown(): + { + auto tooltip = get_markdown_tooltip(""); + g_assert(tooltip->buffer->get_text() == ""); + } + { + auto tooltip = get_markdown_tooltip("test"); + g_assert(tooltip->buffer->get_text() == "test"); + } + { + auto tooltip = get_markdown_tooltip("test\ntest"); + g_assert(tooltip->buffer->get_text() == "test\ntest"); + } + { + auto tooltip = get_markdown_tooltip("test\n\ntest"); + g_assert(tooltip->buffer->get_text() == "test\n\ntest"); + } + { + auto tooltip = get_markdown_tooltip("test\n\ntest\n\n"); + g_assert(tooltip->buffer->get_text() == "test\n\ntest"); + } + { + auto tooltip = get_markdown_tooltip("\\# test"); + auto buffer = tooltip->buffer; + g_assert(buffer->get_text() == "# test"); + } + { + auto tooltip = get_markdown_tooltip("# test"); + auto buffer = tooltip->buffer; + g_assert(buffer->get_text() == "test"); + g_assert(buffer->begin().starts_tag(tooltip->h1_tag)); + g_assert(buffer->get_iter_at_offset(4).ends_tag(tooltip->h1_tag)); + } + { + auto tooltip = get_markdown_tooltip("# test\ntest"); + auto buffer = tooltip->buffer; + g_assert(buffer->get_text() == "test\n\ntest"); + g_assert(buffer->begin().starts_tag(tooltip->h1_tag)); + g_assert(buffer->get_iter_at_offset(4).ends_tag(tooltip->h1_tag)); + } + { + auto tooltip = get_markdown_tooltip("# test\n\ntest"); + auto buffer = tooltip->buffer; + g_assert(buffer->get_text() == "test\n\ntest"); + g_assert(buffer->begin().starts_tag(tooltip->h1_tag)); + g_assert(buffer->get_iter_at_offset(4).ends_tag(tooltip->h1_tag)); + } + { + auto tooltip = get_markdown_tooltip("test\n# test\ntest"); + auto buffer = tooltip->buffer; + g_assert(buffer->get_text() == "test\n\ntest\n\ntest"); + g_assert(buffer->get_iter_at_offset(6).starts_tag(tooltip->h1_tag)); + g_assert(buffer->get_iter_at_offset(10).ends_tag(tooltip->h1_tag)); + } + { + auto tooltip = get_markdown_tooltip("## test"); + auto buffer = tooltip->buffer; + g_assert(buffer->get_text() == "test"); + g_assert(buffer->begin().starts_tag(tooltip->h2_tag)); + g_assert(buffer->get_iter_at_offset(4).ends_tag(tooltip->h2_tag)); + } + { + auto tooltip = get_markdown_tooltip("test\n===="); + auto buffer = tooltip->buffer; + g_assert(buffer->get_text() == "test"); + g_assert(buffer->begin().starts_tag(tooltip->h1_tag)); + g_assert(buffer->get_iter_at_offset(4).ends_tag(tooltip->h1_tag)); + } + { + auto tooltip = get_markdown_tooltip("test\n----"); + auto buffer = tooltip->buffer; + g_assert(buffer->get_text() == "test"); + g_assert(buffer->begin().starts_tag(tooltip->h2_tag)); + g_assert(buffer->get_iter_at_offset(4).ends_tag(tooltip->h2_tag)); + } + { + auto tooltip = get_markdown_tooltip("### test"); + auto buffer = tooltip->buffer; + g_assert(buffer->get_text() == "test"); + g_assert(buffer->begin().starts_tag(tooltip->h3_tag)); + g_assert(buffer->get_iter_at_offset(4).ends_tag(tooltip->h3_tag)); + } + { + auto tooltip = get_markdown_tooltip("_test_"); + auto buffer = tooltip->buffer; + g_assert(buffer->get_text() == "test"); + g_assert(buffer->begin().starts_tag(tooltip->italic_tag)); + g_assert(buffer->get_iter_at_offset(4).ends_tag(tooltip->italic_tag)); + } + { + auto tooltip = get_markdown_tooltip("*test*"); + auto buffer = tooltip->buffer; + g_assert(buffer->get_text() == "test"); + g_assert(buffer->begin().starts_tag(tooltip->italic_tag)); + g_assert(buffer->get_iter_at_offset(4).ends_tag(tooltip->italic_tag)); + g_assert(!buffer->begin().starts_tag(tooltip->bold_tag)); + g_assert(!buffer->get_iter_at_offset(4).ends_tag(tooltip->bold_tag)); + } + { + auto tooltip = get_markdown_tooltip("*test* test"); + auto buffer = tooltip->buffer; + g_assert(buffer->get_text() == "test test"); + g_assert(buffer->begin().starts_tag(tooltip->italic_tag)); + g_assert(buffer->get_iter_at_offset(4).ends_tag(tooltip->italic_tag)); + g_assert(!buffer->begin().starts_tag(tooltip->bold_tag)); + g_assert(!buffer->get_iter_at_offset(4).ends_tag(tooltip->bold_tag)); + } + { + auto tooltip = get_markdown_tooltip("**test**"); + auto buffer = tooltip->buffer; + g_assert(buffer->get_text() == "test"); + g_assert(buffer->begin().starts_tag(tooltip->bold_tag)); + g_assert(buffer->get_iter_at_offset(4).ends_tag(tooltip->bold_tag)); + g_assert(!buffer->begin().starts_tag(tooltip->italic_tag)); + g_assert(!buffer->get_iter_at_offset(4).ends_tag(tooltip->italic_tag)); + } + { + auto tooltip = get_markdown_tooltip("__test__"); + auto buffer = tooltip->buffer; + g_assert(buffer->get_text() == "test"); + g_assert(buffer->begin().starts_tag(tooltip->bold_tag)); + g_assert(buffer->get_iter_at_offset(4).ends_tag(tooltip->bold_tag)); + g_assert(!buffer->begin().starts_tag(tooltip->italic_tag)); + g_assert(!buffer->get_iter_at_offset(4).ends_tag(tooltip->italic_tag)); + } + { + auto tooltip = get_markdown_tooltip("*_test_*"); + auto buffer = tooltip->buffer; + g_assert(buffer->get_text() == "test"); + g_assert(buffer->begin().starts_tag(tooltip->bold_tag)); + g_assert(buffer->get_iter_at_offset(4).ends_tag(tooltip->bold_tag)); + g_assert(!buffer->begin().starts_tag(tooltip->italic_tag)); + g_assert(!buffer->get_iter_at_offset(4).ends_tag(tooltip->italic_tag)); + } + { + auto tooltip = get_markdown_tooltip("***test***"); + auto buffer = tooltip->buffer; + g_assert(buffer->get_text() == "test"); + g_assert(buffer->begin().starts_tag(tooltip->bold_tag)); + g_assert(buffer->begin().starts_tag(tooltip->italic_tag)); + g_assert(buffer->get_iter_at_offset(4).ends_tag(tooltip->bold_tag)); + g_assert(buffer->get_iter_at_offset(4).ends_tag(tooltip->italic_tag)); + } + { + auto tooltip = get_markdown_tooltip("**_test_**"); + auto buffer = tooltip->buffer; + g_assert(buffer->get_text() == "test"); + g_assert(buffer->begin().starts_tag(tooltip->bold_tag)); + g_assert(buffer->begin().starts_tag(tooltip->italic_tag)); + g_assert(buffer->get_iter_at_offset(4).ends_tag(tooltip->bold_tag)); + g_assert(buffer->get_iter_at_offset(4).ends_tag(tooltip->italic_tag)); + } + { + auto tooltip = get_markdown_tooltip("~~test~~"); + auto buffer = tooltip->buffer; + g_assert(buffer->get_text() == "test"); + g_assert(buffer->begin().starts_tag(tooltip->strikethrough_tag)); + g_assert(buffer->get_iter_at_offset(4).ends_tag(tooltip->strikethrough_tag)); + } + { + auto tooltip = get_markdown_tooltip("~~test"); + auto buffer = tooltip->buffer; + g_assert(buffer->get_text() == "~~test"); + } + { + auto tooltip = get_markdown_tooltip("~~test~~ test"); + auto buffer = tooltip->buffer; + g_assert(buffer->get_text() == "test test"); + g_assert(buffer->begin().starts_tag(tooltip->strikethrough_tag)); + g_assert(buffer->get_iter_at_offset(4).ends_tag(tooltip->strikethrough_tag)); + } + { + auto tooltip = get_markdown_tooltip("~~*test*~~"); + auto buffer = tooltip->buffer; + g_assert(buffer->get_text() == "test"); + g_assert(buffer->begin().starts_tag(tooltip->strikethrough_tag)); + g_assert(buffer->begin().starts_tag(tooltip->italic_tag)); + g_assert(buffer->get_iter_at_offset(4).ends_tag(tooltip->strikethrough_tag)); + g_assert(buffer->get_iter_at_offset(4).ends_tag(tooltip->italic_tag)); + } + { + auto tooltip = get_markdown_tooltip("test_test"); + auto buffer = tooltip->buffer; + g_assert(buffer->get_text() == "test_test"); + } + { + auto tooltip = get_markdown_tooltip("_test_test_"); + auto buffer = tooltip->buffer; + g_assert(buffer->get_text() == "test_test"); + g_assert(buffer->begin().starts_tag(tooltip->italic_tag)); + g_assert(buffer->get_iter_at_offset(9).ends_tag(tooltip->italic_tag)); + } + { + auto tooltip = get_markdown_tooltip("2 * 2 * 2"); + auto buffer = tooltip->buffer; + g_assert(buffer->get_text() == "2 * 2 * 2"); + } + { + auto tooltip = get_markdown_tooltip("* *2*"); + auto buffer = tooltip->buffer; + g_assert(buffer->get_text() == "* 2"); + } + { + auto tooltip = get_markdown_tooltip("* 2*"); + auto buffer = tooltip->buffer; + g_assert(buffer->get_text() == "* 2*"); + } + { + auto tooltip = get_markdown_tooltip("*"); + auto buffer = tooltip->buffer; + g_assert(buffer->get_text() == "*"); + } + { + auto tooltip = get_markdown_tooltip("\\*test\\*"); + auto buffer = tooltip->buffer; + g_assert(buffer->get_text() == "*test*"); + } + { + auto tooltip = get_markdown_tooltip("*\\*test\\**"); + auto buffer = tooltip->buffer; + g_assert(buffer->get_text() == "*test*"); + g_assert(buffer->get_iter_at_offset(0).starts_tag(tooltip->italic_tag)); + g_assert(buffer->get_iter_at_offset(6).ends_tag(tooltip->italic_tag)); + } + { + auto tooltip = get_markdown_tooltip("**test _test_**"); + auto buffer = tooltip->buffer; + g_assert(buffer->get_text() == "test test"); + g_assert(buffer->get_iter_at_offset(0).starts_tag(tooltip->bold_tag)); + g_assert(buffer->get_iter_at_offset(9).ends_tag(tooltip->bold_tag)); + g_assert(buffer->get_iter_at_offset(5).starts_tag(tooltip->italic_tag)); + g_assert(buffer->get_iter_at_offset(9).ends_tag(tooltip->italic_tag)); + } + { + auto tooltip = get_markdown_tooltip("_test _test_"); + auto buffer = tooltip->buffer; + g_assert(buffer->get_text() == "_test test"); + } + { + auto tooltip = get_markdown_tooltip("`test`"); + auto buffer = tooltip->buffer; + g_assert(buffer->get_text() == "test"); + g_assert(buffer->begin().starts_tag(tooltip->code_tag)); + g_assert(buffer->get_iter_at_offset(4).ends_tag(tooltip->code_tag)); + } + { + auto tooltip = get_markdown_tooltip("test `test` test"); + auto buffer = tooltip->buffer; + g_assert(buffer->get_text() == "test test test"); + g_assert(buffer->get_iter_at_offset(5).starts_tag(tooltip->code_tag)); + g_assert(buffer->get_iter_at_offset(9).ends_tag(tooltip->code_tag)); + } + { + auto tooltip = get_markdown_tooltip("``te`st``"); + auto buffer = tooltip->buffer; + g_assert(buffer->get_text() == "te`st"); + g_assert(buffer->begin().starts_tag(tooltip->code_tag)); + g_assert(buffer->get_iter_at_offset(5).ends_tag(tooltip->code_tag)); + } + { + auto tooltip = get_markdown_tooltip("# Test\ntest"); + auto buffer = tooltip->buffer; + g_assert(buffer->get_text() == "Test\n\ntest"); + } + { + auto tooltip = get_markdown_tooltip("test\n\n# Test\ntest"); + auto buffer = tooltip->buffer; + g_assert(buffer->get_text() == "test\n\nTest\n\ntest"); + } + { + auto tooltip = get_markdown_tooltip("```\ntest\n```\ntest"); + auto buffer = tooltip->buffer; + g_assert(buffer->get_text() == "test\n\ntest"); + g_assert(buffer->begin().starts_tag(tooltip->code_block_tag)); + g_assert(buffer->get_iter_at_offset(5).ends_tag(tooltip->code_block_tag)); + } + { + auto tooltip = get_markdown_tooltip("test\n```c++\ntest\n```\ntest"); + auto buffer = tooltip->buffer; + g_assert(buffer->get_text() == "test\n\ntest\n\ntest"); + g_assert(buffer->get_iter_at_offset(6).starts_tag(tooltip->code_block_tag)); + g_assert(buffer->get_iter_at_offset(11).ends_tag(tooltip->code_block_tag)); + } + { + auto tooltip = get_markdown_tooltip("http://test.com"); + g_assert(tooltip->buffer->get_text() == "http://test.com"); + auto buffer = tooltip->buffer; + g_assert(buffer->begin().starts_tag(tooltip->link_tag)); + g_assert(buffer->get_iter_at_offset(15).ends_tag(tooltip->link_tag)); + } + { + auto tooltip = get_markdown_tooltip("http://test.com."); + g_assert(tooltip->buffer->get_text() == "http://test.com."); + auto buffer = tooltip->buffer; + g_assert(buffer->begin().starts_tag(tooltip->link_tag)); + g_assert(buffer->get_iter_at_offset(15).ends_tag(tooltip->link_tag)); + } + { + auto tooltip = get_markdown_tooltip("[test](http://test.com)"); + g_assert(tooltip->buffer->get_text() == "test"); + auto buffer = tooltip->buffer; + g_assert(buffer->begin().starts_tag(tooltip->link_tag)); + g_assert(buffer->get_iter_at_offset(4).ends_tag(tooltip->link_tag)); + } + { + auto tooltip = get_markdown_tooltip("[`test`](http://test.com)"); + g_assert(tooltip->buffer->get_text() == "test"); + auto buffer = tooltip->buffer; + g_assert(buffer->begin().starts_tag(tooltip->link_tag)); + g_assert(buffer->get_iter_at_offset(4).ends_tag(tooltip->link_tag)); + g_assert(buffer->begin().starts_tag(tooltip->code_tag)); + g_assert(buffer->get_iter_at_offset(4).ends_tag(tooltip->code_tag)); + } + { + auto tooltip = get_markdown_tooltip("[1]: http://test.com"); + g_assert(tooltip->buffer->get_text() == ""); + } + { + auto tooltip = get_markdown_tooltip("[`test`]\n\n[`test`]: http://test.com"); + g_assert(tooltip->buffer->get_text() == "test"); + auto buffer = tooltip->buffer; + g_assert(buffer->begin().starts_tag(tooltip->link_tag)); + g_assert(buffer->get_iter_at_offset(4).ends_tag(tooltip->link_tag)); + g_assert(buffer->begin().starts_tag(tooltip->code_tag)); + g_assert(buffer->get_iter_at_offset(4).ends_tag(tooltip->code_tag)); + + g_assert(tooltip->reference_links.size() == 1); + g_assert(tooltip->reference_links.begin()->second == "test"); + g_assert(tooltip->references.size() == 1); + g_assert(tooltip->references.begin()->second == "http://test.com"); + } + { + auto tooltip = get_markdown_tooltip("[`text`][test]\n\n[test]: http://test.com"); + g_assert(tooltip->buffer->get_text() == "text"); + auto buffer = tooltip->buffer; + g_assert(buffer->begin().starts_tag(tooltip->link_tag)); + g_assert(buffer->get_iter_at_offset(4).ends_tag(tooltip->link_tag)); + g_assert(buffer->begin().starts_tag(tooltip->code_tag)); + g_assert(buffer->get_iter_at_offset(4).ends_tag(tooltip->code_tag)); + + g_assert(tooltip->reference_links.size() == 1); + g_assert(tooltip->reference_links.begin()->second == "test"); + g_assert(tooltip->references.size() == 1); + g_assert(tooltip->references.begin()->second == "http://test.com"); + } + + // Testing wrap_lines(): + { + auto tooltip = get_markdown_tooltip(""); + tooltip->wrap_lines(); + g_assert(tooltip->buffer->get_text() == ""); + } + { + auto tooltip = get_markdown_tooltip("test\ntest"); + tooltip->wrap_lines(); + g_assert(tooltip->buffer->get_text() == "test\ntest"); + } + { + auto tooltip = get_markdown_tooltip("test test test test test test test test test test test test test test test test test test test test test"); + tooltip->wrap_lines(); + g_assert(tooltip->buffer->get_text() == "test test test test test test test test test test test test test test test test\ntest test test test test"); + } + { + auto tooltip = get_markdown_tooltip("test test test test test test test test test test test test test test test testt test test test test test"); + tooltip->wrap_lines(); + g_assert(tooltip->buffer->get_text() == "test test test test test test test test test test test test test test test testt\ntest test test test test"); + } + { + auto tooltip = get_markdown_tooltip("test test test test test test test test test test test test test test test testtt test test test test test"); + tooltip->wrap_lines(); + g_assert(tooltip->buffer->get_text() == "test test test test test test test test test test test test test test test\ntesttt test test test test test"); + } + { + auto tooltip = get_markdown_tooltip("testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttest\ntest test"); + tooltip->wrap_lines(); + g_assert(tooltip->buffer->get_text() == "testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttest\ntest test"); + } + { + auto tooltip = get_markdown_tooltip("testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttest test test"); + tooltip->wrap_lines(); + g_assert(tooltip->buffer->get_text() == "testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttest\ntest test"); + } +}