Browse Source

Further support for python docstrings

pipelines/235045657
eidheim 5 years ago
parent
commit
3ba7e26529
  1. 177
      src/tooltips.cpp
  2. 63
      tests/tooltips_test.cpp

177
src/tooltips.cpp

@ -367,14 +367,13 @@ void Tooltip::create_tags() {
if(!h1_tag) { if(!h1_tag) {
h1_tag = buffer->create_tag(); h1_tag = buffer->create_tag();
h1_tag->property_weight() = Pango::WEIGHT_ULTRAHEAVY; h1_tag->property_weight() = Pango::WEIGHT_ULTRAHEAVY;
h1_tag->property_underline() = Pango::UNDERLINE_DOUBLE; h1_tag->property_underline() = Pango::UNDERLINE_SINGLE;
h2_tag = buffer->create_tag(); h2_tag = buffer->create_tag();
h2_tag->property_weight() = Pango::WEIGHT_ULTRAHEAVY; h2_tag->property_weight() = Pango::WEIGHT_ULTRAHEAVY;
h2_tag->property_underline() = Pango::UNDERLINE_SINGLE;
h3_tag = buffer->create_tag(); h3_tag = buffer->create_tag();
h3_tag->property_weight() = Pango::WEIGHT_ULTRAHEAVY; h3_tag->property_style() = Pango::Style::STYLE_ITALIC;
auto source_font_family = Pango::FontDescription(Config::get().source.font).get_family(); auto source_font_family = Pango::FontDescription(Config::get().source.font).get_family();
@ -1402,7 +1401,132 @@ void Tooltip::insert_docstring(const std::string &input_) {
partial.reserve(input.size()); partial.reserve(input.size());
size_t i = 0; size_t i = 0;
auto parse_backtick = [&] { auto unescape = [&](size_t &i) {
if(input[i] == '\\') {
if(i + 1 < input.size())
i++;
return true;
}
return false;
};
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 is_whitespace_character = [&](size_t i) {
return input[i] == ' ' || input[i] == '\t' || input[i] == '\n' || input[i] == '\r' || input[i] == '\f';
};
auto insert_header = [&] {
if(is_whitespace_character(i))
return false;
auto i_saved = i;
forward_to({'\n'});
auto end_header = i;
i++;
int header = 0;
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;
}
}
if(header == 0) {
i = i_saved;
return false;
}
insert_with_links_tagged(partial);
partial.clear();
auto start_offset = buffer->get_insert()->get_iter().get_offset();
insert_with_links_tagged(input.substr(i_saved, end_header - i_saved));
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());
buffer->insert_at_cursor("\n");
return true;
};
auto is_punctuation_character = [&](size_t i) {
static std::set<char> punctuation_characters = {'!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', ':', ';', '<', '=', '>', '?', '@', '[', '\\', ']', '^', '_', '`', '{', '|', '}', '~'};
return punctuation_characters.find(input[i]) != punctuation_characters.end();
};
auto left_flanking_delimiter_run = [&](size_t i, size_t n) {
return !(i + n >= input.size() || is_whitespace_character(i + n)) &&
(!is_punctuation_character(i + n) ||
(i == 0 || is_whitespace_character(i - 1) || is_punctuation_character(i - 1)));
};
auto right_flanking_delimiter_run = [&](size_t i, size_t n) {
return !(i == 0 || is_whitespace_character(i - 1)) &&
(!is_punctuation_character(i - 1) ||
(i + n >= input.size() || is_whitespace_character(i + n) || is_punctuation_character(i + n)));
};
/// Using rules in https://spec.commonmark.org/0.29/#emphasis-and-strong-emphasis
auto insert_emphasis = [&] {
auto i_saved = i;
std::string prefix;
if(input[i] == '*') {
prefix += input[i];
++i;
for(; prefix.size() < 2 && input[i] == prefix.back(); ++i)
prefix += input[i];
}
if(prefix.empty())
return false;
// Rule 1 and 5
if(!left_flanking_delimiter_run(i - prefix.size(), prefix.size())) {
i = i_saved;
return false;
}
auto start = i;
for(; i < input.size() - (prefix.size() - 1); i++) {
if(!unescape(i)) {
if(starts_with(input, i, prefix)) {
// Rule 3 and 7
if(right_flanking_delimiter_run(i, prefix.size()))
break;
else if(left_flanking_delimiter_run(i, prefix.size())) {
i = i_saved;
return false;
}
}
}
}
if(i >= input.size() - (prefix.size() - 1)) {
i = i_saved;
return false;
}
insert_with_links_tagged(partial);
partial.clear();
auto start_offset = buffer->get_insert()->get_iter().get_offset();
insert_with_links_tagged(input.substr(start, i - start));
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());
i += prefix.size() - 1;
return true;
};
auto insert_code_or_link = [&] {
if(input[i] == '`') { if(input[i] == '`') {
auto i_saved = i; auto i_saved = i;
i++; i++;
@ -1428,7 +1552,24 @@ void Tooltip::insert_docstring(const std::string &input_) {
} }
insert_with_links_tagged(partial); insert_with_links_tagged(partial);
partial.clear(); partial.clear();
if(!two_backticks && i + 1 < input.size() && input[i + 1] == '_') { // Is a link if(!two_backticks && i + 1 < input.size() && input[i + 1] == '_') { // Insert link
const static std::regex regex("^([^<]*?) ?<([^>]+)>$");
std::smatch sm;
if(std::regex_match(input.begin() + start, input.begin() + i, sm, regex)) {
if(sm[1].length() == 0)
insert_with_links_tagged(sm[2].str());
else {
auto start_offset = buffer->get_insert()->get_iter().get_offset();
buffer->insert_at_cursor(sm[1].str());
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, sm[2].str());
}
}
else
insert_with_links_tagged(input.substr(start, i - start)); insert_with_links_tagged(input.substr(start, i - start));
++i; ++i;
} }
@ -1445,12 +1586,7 @@ void Tooltip::insert_docstring(const std::string &input_) {
return false; return false;
}; };
bool after_newline = true; auto insert_code_block = [&] {
for(; i < input.size(); ++i) {
if(parse_backtick())
continue;
if(after_newline) {
after_newline = false;
auto i_saved = i; auto i_saved = i;
static std::string utf8_space = " "; static std::string utf8_space = " ";
while(starts_with(input, i, utf8_space)) while(starts_with(input, i, utf8_space))
@ -1462,16 +1598,21 @@ void Tooltip::insert_docstring(const std::string &input_) {
partial.clear(); partial.clear();
auto pos = input.find("\n\n", i + 3); auto pos = input.find("\n\n", i + 3);
insert_code(input.substr(i_saved, pos != std::string::npos ? pos - i_saved : pos), {}, true); insert_code(input.substr(i_saved, pos != std::string::npos ? pos - i_saved : pos), {}, true);
buffer->insert_at_cursor("\n\n"); buffer->insert_at_cursor("\n");
if(pos == std::string::npos) i = pos != std::string::npos ? pos : input.size() - 1;
break; return true;
i = pos + 1;
continue;
} }
i = i_saved; i = i_saved;
return false;
};
for(; i < input.size(); ++i) {
if(!unescape(i) && (insert_code_or_link() || insert_emphasis()))
continue;
if(i == 0 || input[i - 1] == '\n') {
if(insert_header() || insert_code_block())
continue;
} }
if(input[i] == '\n')
after_newline = true;
partial += input[i]; partial += input[i];
} }
if(!partial.empty()) if(!partial.empty())

63
tests/tooltips_test.cpp

@ -23,6 +23,14 @@ int main() {
return tooltip; return tooltip;
}; };
auto get_docstring_tooltip = [](const std::string &input) {
auto tooltip = std::make_unique<Tooltip>([&](Tooltip &tooltip) {
tooltip.insert_docstring(input);
});
tooltip->show();
return tooltip;
};
// Testing insert_markdown(): // Testing insert_markdown():
{ {
auto tooltip = get_markdown_tooltip(""); auto tooltip = get_markdown_tooltip("");
@ -761,4 +769,59 @@ Detailed description starts here.)");
auto tooltip = get_doxygen_tooltip("/** # testing*/", true); auto tooltip = get_doxygen_tooltip("/** # testing*/", true);
g_assert(tooltip->buffer->get_text() == "testing"); g_assert(tooltip->buffer->get_text() == "testing");
} }
// Testing insert_docstring
{
auto tooltip = get_docstring_tooltip(R"(**Some** *Python* \` ``testing`` `routine`.
Section
====
>>> test
test
Subsection
----
A `link <https://test.test>`_)");
g_assert(tooltip->buffer->get_text() == R"(Some Python ` testing routine.
Section
>>> test
test
Subsection
A link)");
auto it = tooltip->buffer->begin();
g_assert(it.starts_tag(tooltip->bold_tag));
it.forward_chars(4);
g_assert(it.ends_tag(tooltip->bold_tag));
it.forward_chars(1);
g_assert(it.starts_tag(tooltip->italic_tag));
it.forward_chars(6);
g_assert(it.ends_tag(tooltip->italic_tag));
it.forward_chars(3);
g_assert(it.starts_tag(tooltip->code_tag));
it.forward_chars(7);
g_assert(it.ends_tag(tooltip->code_tag));
it.forward_chars(1);
g_assert(it.starts_tag(tooltip->code_tag));
it.forward_chars(7);
g_assert(it.ends_tag(tooltip->code_tag));
it.forward_chars(2);
g_assert(it.starts_tag(tooltip->h1_tag));
it.forward_chars(7);
g_assert(it.ends_tag(tooltip->h1_tag));
it.forward_chars(1);
g_assert(it.starts_tag(tooltip->code_block_tag));
it.forward_chars(13);
g_assert(it.ends_tag(tooltip->code_block_tag));
it.forward_chars(2);
g_assert(it.starts_tag(tooltip->h2_tag));
it.forward_chars(10);
g_assert(it.ends_tag(tooltip->h2_tag));
it.forward_chars(4);
g_assert(it.starts_tag(tooltip->link_tag));
it.forward_chars(4);
g_assert(it.ends_tag(tooltip->link_tag));
g_assert(tooltip->links.begin()->second == "https://test.test");
}
} }

Loading…
Cancel
Save