Browse Source

Added markdown parsing for tooltips

pipelines/143601543
eidheim 6 years ago
parent
commit
74c28602f5
  1. 2
      src/CMakeLists.txt
  2. 43
      src/source_language_protocol.cc
  3. 540
      src/tooltips.cc
  4. 16
      src/tooltips.h
  5. 8
      src/window.cc
  6. 5
      tests/CMakeLists.txt
  7. 2
      tests/stubs/notebook.cc
  8. 17
      tests/stubs/tooltips.cc
  9. 398
      tests/tooltips_test.cc

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

43
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<Content> contents;
auto contents_pt = result.get_child_optional("contents");
if(!contents_pt)
return;
tooltip = contents_pt->get_value<std::string>("");
if(tooltip.empty()) {
auto value = contents_pt->get_value<std::string>("");
if(!value.empty())
contents.emplace_back(Content{value, true});
else {
auto value_pt = contents_pt->get_optional<std::string>("value");
if(value_pt)
tooltip = *value_pt;
contents.emplace_back(Content{*value_pt, contents_pt->get<std::string>("kind", "") == "markdown"});
else {
for(auto it = contents_pt->begin(); it != contents_pt->end(); ++it) {
auto value = it->second.get<std::string>("value", "");
if(value.empty())
value = it->second.get_value<std::string>("");
if(!value.empty())
tooltip.insert(0, value + (tooltip.empty() ? "" : "\n\n"));
contents.emplace_back(Content{value, contents_pt->get<std::string>("kind", "") == "markdown"});
else {
value = it->second.get_value<std::string>("");
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) {

540
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 <algorithm>
#include <regex>
std::set<Tooltip *> Tooltips::shown_tooltips;
@ -48,7 +51,8 @@ void Tooltip::show(bool disregard_drawn, const std::function<void()> &on_motion)
auto g_application = g_application_get_default();
auto gio_application = Glib::wrap(g_application, true);
auto application = Glib::RefPtr<Gtk::Application>::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<void()> &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<void()> &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<Source::View *>(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<void()> &on_motion)
return true;
}
}
auto path = boost::filesystem::path(link);
if(auto view = dynamic_cast<Source::View *>(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<int, int> &last_mouse_pos, const std::pair<in
}
void Tooltip::wrap_lines() {
if(!buffer)
return;
auto iter = buffer->begin();
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<char> 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<char> 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<void(size_t, size_t)> 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();

16
src/tooltips.h

@ -4,6 +4,7 @@
#include <list>
#include <set>
#include <string>
#include <unordered_map>
class Tooltip {
public:
@ -22,6 +23,9 @@ public:
Glib::RefPtr<Gtk::TextBuffer> 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<Gtk::Window> window;
@ -35,6 +39,18 @@ private:
bool shown = false;
Glib::RefPtr<Gtk::TextTag> link_tag;
Glib::RefPtr<Gtk::TextTag> h1_tag;
Glib::RefPtr<Gtk::TextTag> h2_tag;
Glib::RefPtr<Gtk::TextTag> h3_tag;
Glib::RefPtr<Gtk::TextTag> code_tag;
Glib::RefPtr<Gtk::TextTag> code_block_tag;
Glib::RefPtr<Gtk::TextTag> bold_tag;
Glib::RefPtr<Gtk::TextTag> italic_tag;
Glib::RefPtr<Gtk::TextTag> strikethrough_tag;
std::map<Glib::RefPtr<Gtk::TextTag>, std::string> links;
std::map<Glib::RefPtr<Gtk::TextTag>, std::string> reference_links;
std::unordered_map<std::string, std::string> references;
};
class Tooltips {

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

5
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 $<TARGET_OBJECTS:test_stubs>)
@ -81,3 +80,7 @@ add_executable(ctags_grep_test ctags_grep_test.cc $<TARGET_OBJECTS:test_stubs>)
target_link_libraries(ctags_grep_test juci_shared)
add_test(ctags_grep_test ctags_grep_test)
add_executable(tooltips_test tooltips_test.cc $<TARGET_OBJECTS:test_stubs>)
target_link_libraries(tooltips_test juci_shared)
add_test(tooltips_test tooltips_test)

2
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) {}

17
tests/stubs/tooltips.cc

@ -1,17 +0,0 @@
#include "tooltips.h"
Gdk::Rectangle Tooltips::drawn_tooltips_rectangle = Gdk::Rectangle();
Tooltip::Tooltip(Gtk::TextView *text_view,
Glib::RefPtr<Gtk::TextBuffer::Mark> start_mark, Glib::RefPtr<Gtk::TextBuffer::Mark> end_mark,
std::function<void(Tooltip &)> 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<int, int> &, const std::pair<int, int> &) {}

398
tests/tooltips_test.cc

@ -0,0 +1,398 @@
#include "tooltips.h"
#include <glib.h>
#include <gtkmm.h>
int main() {
auto app = Gtk::Application::create();
auto get_markdown_tooltip = [](const std::string &input) {
auto tooltip = std::make_unique<Tooltip>([&](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");
}
}
Loading…
Cancel
Save