From 97dadc5a0bf4ce153efe2c169cbf635a48ac1c25 Mon Sep 17 00:00:00 2001 From: eidheim Date: Sun, 26 Jul 2020 11:50:26 +0200 Subject: [PATCH] Now shows full documentation, not only brief documentation, on C/C++ tooltips, and added a doxygen parser. --- src/source_clang.cpp | 13 +- src/tooltips.cpp | 558 +++++++++++++++++++++++++++++++++++++--- src/tooltips.hpp | 3 +- tests/tooltips_test.cpp | 376 ++++++++++++++++++++++++++- 4 files changed, 906 insertions(+), 44 deletions(-) diff --git a/src/source_clang.cpp b/src/source_clang.cpp index 01f8099..26cc0ae 100644 --- a/src/source_clang.cpp +++ b/src/source_clang.cpp @@ -580,9 +580,14 @@ void Source::ClangViewParse::show_type_tooltips(const Gdk::Rectangle &rectangle) } } tooltip.insert_code(type_description, language); - auto brief_comment = cursor.get_brief_comments(); - if(brief_comment != "") - tooltip.insert_with_links_tagged("\n\n" + brief_comment); + + { + auto doxygen = clangmm::to_string(clang_Cursor_getRawCommentText(cursor.get_referenced().cx_cursor)); + if(!doxygen.empty()) { + tooltip.buffer->insert_at_cursor("\n\n"); + tooltip.insert_doxygen(doxygen, true); + } + } #ifdef JUCI_ENABLE_DEBUG if(Debug::LLDB::get().is_stopped()) { @@ -1108,7 +1113,7 @@ Source::ClangViewAutocomplete::ClangViewAutocomplete(const boost::filesystem::pa if(tooltip_str.empty()) return nullptr; return [tooltip_str = std::move(tooltip_str)](Tooltip &tooltip) { - tooltip.insert_with_links_tagged(tooltip_str); + tooltip.insert_doxygen(tooltip_str, false); }; }; } diff --git a/src/tooltips.cpp b/src/tooltips.cpp index 34cd666..1a36dcb 100644 --- a/src/tooltips.cpp +++ b/src/tooltips.cpp @@ -6,6 +6,7 @@ #include "selection_dialog.hpp" #include "utility.hpp" #include +#include #include std::set Tooltips::shown_tooltips; @@ -404,6 +405,8 @@ void Tooltip::create_tags() { } void Tooltip::insert_with_links_tagged(const std::string &text) { + if(text.empty()) + return; const static std::regex http_regex("(https?://[a-zA-Z0-9\\-._~:/?#\\[\\]@!$&'()*+,;=]+[a-zA-Z0-9\\-_~/@$*+;=])", std::regex::optimize); std::smatch sm; std::sregex_iterator it(text.begin(), text.end(), http_regex); @@ -448,6 +451,9 @@ void Tooltip::insert_markdown(const std::string &input) { }; std::function insert_text = [&](size_t from, size_t to) { + if(from == to) + return; + auto i = from; std::string partial; @@ -456,40 +462,75 @@ void Tooltip::insert_markdown(const std::string &input) { return input[i] == ' ' || input[i] == '\t' || input[i] == '\n' || input[i] == '\r' || input[i] == '\f'; }; + auto is_punctuation_character = [&](size_t i) { + static std::set 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 >= to || is_whitespace_character(i + n)) && + (!is_punctuation_character(i + n) || + (i == from || is_whitespace_character(i - 1) || is_punctuation_character(i - 1))); + }; + auto right_flanking_delimiter_run = [&](size_t i, size_t n) { + return !(i == from || is_whitespace_character(i - 1)) && + (!is_punctuation_character(i - 1) || + (i + n >= to || 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 = [&] { - 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++) + if(input[i] == '*' || input[i] == '_') { prefix += input[i]; + ++i; + for(; prefix.size() < 3 && input[i] == prefix.back(); ++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; + if(prefix.back() == '*') { // Rule 1 and 5 + if(!left_flanking_delimiter_run(i - prefix.size(), prefix.size())) { + i = i_saved; + return false; + } + } + else { // Rule 2 and 6 + if(!(left_flanking_delimiter_run(i - prefix.size(), prefix.size()) && + (!right_flanking_delimiter_run(i - prefix.size(), prefix.size()) || i - prefix.size() == from || is_punctuation_character(i - prefix.size() - 1)))) { + i = i_saved; + return false; + } } insert_with_links_tagged(partial); partial.clear(); auto start = i; - for(; i < to; i++) { + for(; i < to - (prefix.size() - 1); i++) { if(!unescape(i)) { if(starts_with(input, i, prefix)) { - if(i - 1 > from && is_whitespace_character(i - 1)) { // Do not emphasis _test in: _test _test_ - i = i_saved; - return false; + if(prefix.back() == '*') { // 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; + } + } + else { // Rule 4 and 8 + if(right_flanking_delimiter_run(i, prefix.size()) && + (!left_flanking_delimiter_run(i, prefix.size()) || i + prefix.size() == to || is_punctuation_character(i + prefix.size()))) + break; + else if(left_flanking_delimiter_run(i, prefix.size()) && + (!right_flanking_delimiter_run(i, prefix.size()) || i == from || is_punctuation_character(i - 1))) { + 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) { + if(i >= to - (prefix.size() - 1)) { i = i_saved; return false; } @@ -631,7 +672,7 @@ void Tooltip::insert_markdown(const std::string &input) { reference_links.emplace(tag, input.substr(link_start, i - link_start)); return true; } - else { + else if(text_start != text_end) { 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); @@ -652,7 +693,12 @@ void Tooltip::insert_markdown(const std::string &input) { for(; i < to; i++) { if(!unescape(i) && (insert_code() || insert_emphasis() || insert_strikethrough() || insert_link())) continue; - partial += input[i]; + if(input[i] == '\n' && i + 1 < to) + partial += ' '; + else if(input[i] == ' ' && ((i > from && (input[i - 1] == '\n' || input[i - 1] == ' ')))) + continue; + else + partial += input[i]; } insert_with_links_tagged(partial); }; @@ -700,6 +746,13 @@ void Tooltip::insert_markdown(const std::string &input) { return true; }; + auto is_empty_line = [&] { + auto i_saved = i; + if(forward_passed({' ', '\t'}) && input[i] == '\n') + return true; + i = i_saved; + return false; + }; auto insert_code_block = [&] { if(starts_with(input, i, "```")) { @@ -715,12 +768,10 @@ void Tooltip::insert_markdown(const std::string &input) { i = i_saved; return false; } - auto end = buffer->end(); - if(end.backward_char() && !end.starts_line()) - buffer->insert_at_cursor("\n"); insert_code(input.substr(start, i - start), language, true); - buffer->insert_at_cursor("\n"); - i += 3; + i += 4; + if(is_empty_line()) + buffer->insert_at_cursor("\n"); return true; } } @@ -765,18 +816,102 @@ void Tooltip::insert_markdown(const std::string &input) { return false; }; - while(forward_passed({'\n'})) { - if(insert_header() || insert_code_block() || insert_reference()) + auto is_number = [&] { + return input[i] >= '0' && input[i] <= '9'; + }; + + auto forward_passed_number = [&] { + while(i < input.size() && is_number()) + ++i; + return i < input.size(); + }; + + std::function insert_list = [&] { + auto i_saved = i; + if(starts_with(input, i, "- ") || starts_with(input, i, "+ ") || starts_with(input, i, "* ") || (is_number() && forward_passed_number() && starts_with(input, i, ". "))) { + auto start = i_saved; + while(true) { + forward_to({'\n'}); + ++i; + if(i >= input.size()) { + insert_text(start, i - 1); + break; + } + if(is_empty_line()) { + insert_text(start, i - 1); + buffer->insert_at_cursor("\n\n"); + break; + } + auto i_saved = i; + // TODO: Fix check: starts_with(input, i, "1. ") + if(forward_passed({' ', '\t'})) { + auto i_saved2 = i; + if(starts_with(input, i, "- ") || starts_with(input, i, "+ ") || starts_with(input, i, "* ") || (is_number() && forward_passed_number() && starts_with(input, i, ". "))) { + i = i_saved2; + insert_text(start, i_saved - 1); + buffer->insert_at_cursor('\n' + input.substr(i_saved, i - i_saved)); + insert_list(); + break; + } + } + i = i_saved; + } + return true; + } + i = i_saved; + return false; + }; + + auto forward_passed_empty_line = [&] { + while(is_empty_line()) + ++i; + return i < input.size(); + }; + + while(forward_passed_empty_line()) { + if(insert_header() || insert_code_block() || insert_reference() || insert_list()) continue; // Insert paragraph: auto start = i; + bool ends_with_empty_line = false; for(; forward_to({'\n'}); i++) { - if(i + 1 < input.size() && (input[i + 1] == '\n' || input[i + 1] == '#' || starts_with(input, i + 1, "```"))) + ++i; + ScopeGuard guard{[&i] { --i; }}; + if(is_empty_line()) { + ends_with_empty_line = true; break; + } + if(i < input.size() && (input[i] == '#' || + starts_with(input, i, "```") || starts_with(input, i, "- ") || + starts_with(input, i, "+ ") || starts_with(input, i, "* ") || + starts_with(input, i, "1. "))) { + break; + } } + while(start < i && (input[start] == ' ' || input[start] == '\t' || input[start] == '\n')) + ++start; insert_text(start, i); - if(i < input.size()) - buffer->insert_at_cursor("\n\n"); + buffer->insert_at_cursor(ends_with_empty_line ? "\n\n" : "\n"); + } + + // Remove invalid reference links + for(auto link_it = reference_links.begin(); link_it != reference_links.end();) { + auto reference_it = references.find(link_it->second); + if(reference_it == references.end()) { + auto it = buffer->begin(); + if(it.begins_tag(link_it->first) || it.forward_to_tag_toggle(link_it->first)) { + auto start = it; + it.forward_to_tag_toggle(link_it->first); + buffer->remove_tag(link_tag, start, it); + buffer->remove_tag(link_it->first, buffer->begin(), buffer->end()); + auto start_mark = Source::Mark(start); + buffer->insert(it, "]"); + buffer->insert(start_mark->get_iter(), "["); + link_it = reference_links.erase(link_it); + continue; + } + } + ++link_it; } remove_trailing_newlines(); @@ -808,6 +943,8 @@ void Tooltip::insert_code(const std::string &code, boost::variant(&language_variant)) { if(!language_identifier->empty()) { language = Source::LanguageManager::get_default()->get_language(*language_identifier); + if(!language) + language = Source::guess_language('.' + *language_identifier); if(!language) { if(auto source_view = dynamic_cast(view)) language = source_view->language; @@ -887,6 +1024,367 @@ void Tooltip::insert_code(const std::string &code, boost::variant chars) { + for(; i < input.size(); i++) { + if(std::none_of(chars.begin(), chars.end(), [&](char chr) { return input[i] == chr; })) + return true; + } + return false; + }; + + size_t min_indent = std::numeric_limits::max(); + auto forward_to_text = [&](bool first_line) -> bool { + auto line_start = i; + auto after_delimiter = i; + forward_passed({' ', '\t'}); + if(i < input.size() && (input[i] == '/' || input[i] == '*')) { + forward_passed({'/'}); + if(i < input.size() && input[i] == '*' && forward_passed({'*'})) { + if(input[i] == '/') + return false; + } + forward_passed({'!'}); + after_delimiter = i; + } + forward_passed({' ', '\t'}); + if(!first_line && i < input.size() && input[i] != '\n') { + min_indent = std::min(min_indent, i - line_start); + if(i - line_start > min_indent && line_start + min_indent >= after_delimiter) + i = line_start + min_indent; + } + return i < input.size(); + }; + + std::string text; + text.reserve(input.size()); + bool first_line = true; + for(; i < input.size(); ++i) { + if(!forward_to_text(first_line)) + break; + if(!(first_line && input[i] == '\n')) { + for(; i < input.size(); ++i) { + text += input[i]; + if(input[i] == '\n') + break; + } + } + first_line = false; + } + if(text.size() >= 2 && text[text.size() - 2] == '*' && text[text.size() - 1] == '/') { + text.erase(text.size() - 2, 2); + while(!text.empty() && text.back() == ' ') + text.pop_back(); + } + return text; + }; + + const auto &input = remove_delimiters ? get_text() : input_; + size_t i = 0; + auto forward_passed = [&](const std::vector chars) { + for(; i < input.size(); i++) { + if(std::none_of(chars.begin(), chars.end(), [&](char chr) { return input[i] == chr; })) + return true; + } + return false; + }; + + auto get_token = [&] { + auto start = i; + while(i < input.size() && ((input[i] >= 'a' && input[i] <= 'z') || (input[i] >= 'A' && input[i] <= 'Z') || (input[i] >= '0' && input[i] <= '9') || input[i] == '_')) + ++i; + return input.substr(start, i - start); + }; + + auto is_escape_character = [&] { + static std::set escape_characters{'\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '#', '+', '-', '.', '!', '|'}; + return escape_characters.find(input[i]) != escape_characters.end(); + }; + + auto get_word = [&](bool escape) { + std::string word; + for(; i < input.size() && input[i] != ' ' && input[i] != '\t' && input[i] != '\n'; ++i) { + if((input[i] == '.' || input[i] == ',' || input[i] == ':' || input[i] == ';' || input[i] == '!' || input[i] == '?') && + (i + 1 >= input.size() || input[i + 1] == ' ' || input[i + 1] == '\t' || input[i + 1] == '\n')) { + break; + } + if(escape && is_escape_character()) + word += "\\"; + word += input[i]; + } + return word; + }; + + std::string markdown; + markdown.reserve(input.size()); + bool first_parameter = true; + + while(i < input.size()) { + if(input[i] == '\\' || input[i] == '@') { + auto i_saved = i; + ++i; + auto token = get_token(); + if(token.empty()) { + if(i < input.size()) { + if(input[i] == '\\') + markdown += "\\\\"; + else + markdown += input[i]; + } + ++i; + } + else if(token == "a" || token == "e" || token == "em") { + forward_passed({' ', '\t', '\n'}); + markdown += '*' + get_word(true) + '*'; + } + else if(token == "b") { + forward_passed({' ', '\t', '\n'}); + markdown += "**" + get_word(true) + "**"; + } + else if(token == "c" || token == "p") { + forward_passed({' ', '\t', '\n'}); + markdown += '`' + get_word(false) + '`'; + } + else if(token == "brief") + forward_passed({' ', '\t', '\n'}); + else if(token == "param") { + std::string direction; + if(input[i] == '[') { + ++i; + if(i >= input.size()) + break; + auto pos = input.find(']', i); + if(pos != std::string::npos) { + direction = input.substr(i, pos - i); + i = pos + 1; + } + } + forward_passed({' ', '\t', '\n'}); + if(first_parameter) { + markdown += "\n\n**Parameters:**\n"; + first_parameter = false; + } + markdown += "- `" + get_word(false) + '`'; + if(!direction.empty()) + markdown += " (" + direction + ')'; + } + else if(token == "return" || token == "returns" || token == "result") { + forward_passed({' ', '\t', '\n'}); + markdown += "\n\n**Returns** "; + } + else if(token == "sa" || token == "see") { + forward_passed({' ', '\t', '\n'}); + markdown += "\n\nSee also "; + } + else if(token == "throw" || token == "throws" || token == "exception") { + forward_passed({' ', '\t', '\n'}); + markdown += "\n\n**Throws** "; + } + else if(token == "deprecated") { + forward_passed({' ', '\t', '\n'}); + markdown += "\n\n**Deprecated** "; + } + else if(token == "note") { + forward_passed({' ', '\t', '\n'}); + markdown += "\n\n**Note** "; + } + else if(token == "code") { + std::string language_id; + if(input[i] == '{') { + ++i; + if(i >= input.size()) + break; + if(input[i] == '.') { + auto pos = input.find('}', i + 1); + if(pos != std::string::npos) { + language_id = input.substr(i + 1, pos - (i + 1)); + i = pos + 1; + } + } + } + auto start = i + 1; + for(; i < input.size(); ++i) { + if(input[i] == '\n') { + ++i; + if(i >= input.size()) + break; + if(input[i] == '\\' || input[i] == '@') { + auto end = i - 1; + ++i; + auto token = get_token(); + if(token == "endcode") { + if(language_id.empty() && view) { + if(auto source_view = dynamic_cast(view)) { + if(source_view->language) + language_id = source_view->language->get_id(); + } + } + markdown += "```" + language_id + '\n' + input.substr(start, end - start) + "\n```\n"; + ++i; + break; + } + } + } + } + } + else if(token == "dot") { + for(; i < input.size(); ++i) { + if(input[i] == '\n') { + ++i; + if(i >= input.size()) + break; + if(input[i] == '\\') { + ++i; + auto token = get_token(); + if(token == "enddot") { + markdown += "\n\n"; + ++i; + break; + } + } + } + } + } + else if(token == "verbatim") { + auto start = i + 1; + for(; i < input.size(); ++i) { + if(input[i] == '\n') { + ++i; + if(i >= input.size()) + break; + if(input[i] == '\\') { + auto end = i - 1; + ++i; + auto token = get_token(); + if(token == "endverbatim") { + markdown += "```\n" + input.substr(start, end - start) + "\n```\n"; + ++i; + break; + } + } + } + } + } + else { + auto i2 = i_saved; + --i2; + while(i2 > 0 && (input[i2] == ' ' || input[i2] == '\t')) + --i2; + if(input[i2] == '\n' || i2 == 0) + markdown += "\n\n"; + if(input[i_saved] == '\\') + markdown += '\\'; + markdown += input[i_saved]; + markdown += token; + } + } + else if(input[i] == '%') + ++i; + else if(starts_with(input, i, "")) { + i += 4; + auto pos = input.find("", i); + if(pos != std::string::npos) { + markdown += '`' + input.substr(i, pos - i) + '`'; + i = pos + 5; + } + } + else if(starts_with(input, i, "")) { + i += 6; + auto pos = input.find("", i); + if(pos != std::string::npos) { + markdown += '`' + input.substr(i, pos - i) + '`'; + i = pos + 7; + } + } + else if(starts_with(input, i, "")) { + i += 4; + markdown += '*'; + for(; i < input.size(); ++i) { + if(input[i] == '\\' && i + 1 < input.size()) { + markdown += input[i]; + markdown += input[i + 1]; + ++i; + } + else { + if(starts_with(input, i, "")) { + i += 5; + break; + } + if(is_escape_character()) + markdown += '\\'; + markdown += input[i]; + } + } + markdown += '*'; + } + else if(starts_with(input, i, "")) { + i += 3; + markdown += "**"; + for(; i < input.size(); ++i) { + if(input[i] == '\\' && i + 1 < input.size()) { + markdown += input[i]; + markdown += input[i + 1]; + ++i; + } + else { + if(starts_with(input, i, "")) { + i += 4; + break; + } + if(is_escape_character()) + markdown += '\\'; + markdown += input[i]; + } + } + markdown += "**"; + } + else if(input[i] == '`') { + auto i_saved = i; + std::string graves; + do { + graves += input[i]; + ++i; + } while(i < input.size() && input[i] == '`'); + auto pos = input.find(graves, i); + if(pos != std::string::npos) { + markdown += input.substr(i_saved, pos + graves.size() - i_saved); + i = pos + graves.size(); + } + } + else if(input[i] == '"') { + markdown += "\""; + ++i; + for(; i < input.size(); ++i) { + if(input[i] == '\\' && i + 1 < input.size()) { + markdown += input[i]; + markdown += input[i + 1]; + ++i; + } + else { + if(is_escape_character()) + markdown += '\\'; + markdown += input[i]; + if(input[i] == '"') + break; + } + } + ++i; + } + else { + markdown += input[i]; + ++i; + } + } + + if(!markdown.empty()) + insert_markdown(markdown); +} + void Tooltip::remove_trailing_newlines() { auto end = buffer->end(); while(end.starts_line() && end.backward_char()) { diff --git a/src/tooltips.hpp b/src/tooltips.hpp index f1df39c..29ea699 100644 --- a/src/tooltips.hpp +++ b/src/tooltips.hpp @@ -26,8 +26,9 @@ public: void insert_with_links_tagged(const std::string &text); void insert_markdown(const std::string &text); - // TODO, c++17: use std::monostate instead of Void void insert_code(const std::string &code, boost::variant> language = Glib::RefPtr{}, bool block = false); + void insert_doxygen(const std::string &input, bool remove_delimiters); + /// Remove empty lines at end of buffer void remove_trailing_newlines(); diff --git a/tests/tooltips_test.cpp b/tests/tooltips_test.cpp index d89f73d..e079d12 100644 --- a/tests/tooltips_test.cpp +++ b/tests/tooltips_test.cpp @@ -1,6 +1,7 @@ #include "tooltips.hpp" #include #include +#include int main() { auto app = Gtk::Application::create(); @@ -14,6 +15,14 @@ int main() { return tooltip; }; + auto get_doxygen_tooltip = [](const std::string &input, bool remove_delimiters) { + auto tooltip = std::make_unique([&](Tooltip &tooltip) { + tooltip.insert_doxygen(input, remove_delimiters); + }); + tooltip->show(); + return tooltip; + }; + // Testing insert_markdown(): { auto tooltip = get_markdown_tooltip(""); @@ -25,7 +34,7 @@ int main() { } { auto tooltip = get_markdown_tooltip("test\ntest"); - g_assert(tooltip->buffer->get_text() == "test\ntest"); + g_assert(tooltip->buffer->get_text() == "test test"); } { auto tooltip = get_markdown_tooltip("test\n\ntest"); @@ -121,6 +130,20 @@ int main() { 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*test"); + auto buffer = tooltip->buffer; + g_assert(buffer->get_text() == "testtesttest"); + g_assert(buffer->get_iter_at_offset(4).starts_tag(tooltip->italic_tag)); + g_assert(buffer->get_iter_at_offset(8).ends_tag(tooltip->italic_tag)); + } + { + auto tooltip = get_markdown_tooltip("test**test**test"); + auto buffer = tooltip->buffer; + g_assert(buffer->get_text() == "testtesttest"); + g_assert(buffer->get_iter_at_offset(4).starts_tag(tooltip->bold_tag)); + g_assert(buffer->get_iter_at_offset(8).ends_tag(tooltip->bold_tag)); + } { auto tooltip = get_markdown_tooltip("**test**"); auto buffer = tooltip->buffer; @@ -140,13 +163,13 @@ int main() { g_assert(!buffer->get_iter_at_offset(4).ends_tag(tooltip->italic_tag)); } { - auto tooltip = get_markdown_tooltip("*_test_*"); + 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)); + 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***"); @@ -252,6 +275,11 @@ int main() { auto buffer = tooltip->buffer; g_assert(buffer->get_text() == "_test test"); } + { + auto tooltip = get_markdown_tooltip("_test _test __test __test"); + auto buffer = tooltip->buffer; + g_assert(buffer->get_text() == "_test _test __test __test"); + } { auto tooltip = get_markdown_tooltip("`test`"); auto buffer = tooltip->buffer; @@ -273,6 +301,11 @@ int main() { 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"); + auto buffer = tooltip->buffer; + g_assert(buffer->get_text() == "Test"); + } { auto tooltip = get_markdown_tooltip("# Test\ntest"); auto buffer = tooltip->buffer; @@ -283,20 +316,33 @@ int main() { auto buffer = tooltip->buffer; g_assert(buffer->get_text() == "test\n\nTest\n\ntest"); } + { + auto tooltip = get_markdown_tooltip("```\ntest\n```"); + auto buffer = tooltip->buffer; + g_assert(buffer->get_text() == "test"); + g_assert(buffer->begin().starts_tag(tooltip->code_block_tag)); + } { auto tooltip = get_markdown_tooltip("```\ntest\n```\ntest"); auto buffer = tooltip->buffer; - g_assert(buffer->get_text() == "test\n\ntest"); + g_assert(buffer->get_text() == "test\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 tooltip = get_markdown_tooltip("test\n\n```c++\ntest\n```\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("test\n```c++\ntest\n```\ntest"); + auto buffer = tooltip->buffer; + g_assert(buffer->get_text() == "test\ntest\ntest"); + g_assert(buffer->get_iter_at_offset(5).starts_tag(tooltip->code_block_tag)); + g_assert(buffer->get_iter_at_offset(10).ends_tag(tooltip->code_block_tag)); + } { auto tooltip = get_markdown_tooltip("http://test.com"); g_assert(tooltip->buffer->get_text() == "http://test.com"); @@ -345,6 +391,17 @@ int main() { g_assert(tooltip->references.size() == 1); g_assert(tooltip->references.begin()->second == "http://test.com"); } + { + auto tooltip = get_markdown_tooltip("[]"); + g_assert(tooltip->buffer->get_text() == "[]"); + g_assert(!tooltip->buffer->get_iter_at_offset(1).has_tag(tooltip->link_tag)); + } + { + auto tooltip = get_markdown_tooltip("[`test`]"); + g_assert(tooltip->buffer->get_text() == "[test]"); + g_assert(!tooltip->buffer->get_iter_at_offset(3).has_tag(tooltip->link_tag)); + g_assert(tooltip->buffer->get_iter_at_offset(3).has_tag(tooltip->code_tag)); + } { auto tooltip = get_markdown_tooltip("[`text`][test]\n\n[test]: http://test.com"); g_assert(tooltip->buffer->get_text() == "text"); @@ -359,6 +416,34 @@ int main() { g_assert(tooltip->references.size() == 1); g_assert(tooltip->references.begin()->second == "http://test.com"); } + { + auto tooltip = get_markdown_tooltip("- test"); + g_assert(tooltip->buffer->get_text() == "- test"); + } + { + auto tooltip = get_markdown_tooltip("test\n- test"); + g_assert(tooltip->buffer->get_text() == "test\n- test"); + } + { + auto tooltip = get_markdown_tooltip("test\n\n- test"); + g_assert(tooltip->buffer->get_text() == "test\n\n- test"); + } + { + auto tooltip = get_markdown_tooltip("- test\ntest"); + g_assert(tooltip->buffer->get_text() == "- test test"); + } + { + auto tooltip = get_markdown_tooltip("- test\n\ntest"); + g_assert(tooltip->buffer->get_text() == "- test\n\ntest"); + } + { + auto tooltip = get_markdown_tooltip("- test\n - test"); + g_assert(tooltip->buffer->get_text() == "- test\n - test"); + } + { + auto tooltip = get_markdown_tooltip("1. test\n2. test\n 30. test\n42. test"); + g_assert(tooltip->buffer->get_text() == "1. test\n2. test\n 30. test\n42. test"); + } // Testing wrap_lines(): { @@ -369,7 +454,7 @@ int main() { { auto tooltip = get_markdown_tooltip("test\ntest"); tooltip->wrap_lines(); - g_assert(tooltip->buffer->get_text() == "test\ntest"); + g_assert(tooltip->buffer->get_text() == "test test"); } { 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"); @@ -396,4 +481,277 @@ int main() { tooltip->wrap_lines(); g_assert(tooltip->buffer->get_text() == "testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttest\ntest test"); } + + // Testing insert_doxygen + { + auto tooltip = get_doxygen_tooltip("", false); + g_assert(tooltip->buffer->get_text() == ""); + } + { + auto tooltip = get_doxygen_tooltip("", true); + g_assert(tooltip->buffer->get_text() == ""); + } + { + auto tooltip = get_doxygen_tooltip("test", false); + g_assert(tooltip->buffer->get_text() == "test"); + } + { + auto tooltip = get_doxygen_tooltip("`test`", false); + g_assert(tooltip->buffer->get_text() == "test"); + } + { + auto tooltip = get_doxygen_tooltip("*test*", false); + g_assert(tooltip->buffer->get_text() == "test"); + } + { + auto tooltip = get_doxygen_tooltip("**test**", false); + g_assert(tooltip->buffer->get_text() == "test"); + } + { + auto tooltip = get_doxygen_tooltip("\\ test", false); + g_assert(tooltip->buffer->get_text() == "test"); + } + { + auto tooltip = get_doxygen_tooltip("\\%test", false); + g_assert(tooltip->buffer->get_text() == "%test"); + } + { + auto tooltip = get_doxygen_tooltip("\\\\test", false); + g_assert(tooltip->buffer->get_text() == "\\test"); + } + { + auto tooltip = get_doxygen_tooltip("\\@test", false); + g_assert(tooltip->buffer->get_text() == "@test"); + } + { + auto tooltip = get_doxygen_tooltip("%test", false); + g_assert(tooltip->buffer->get_text() == "test"); + } + { + auto tooltip = get_doxygen_tooltip("\\a test", false); + g_assert(tooltip->buffer->get_text() == "test"); + } + { + auto tooltip = get_doxygen_tooltip("\\a __test", false); + g_assert(tooltip->buffer->get_text() == "__test"); + } + { + auto tooltip = get_doxygen_tooltip("\\a &str[0]", false); + g_assert(tooltip->buffer->get_text() == "&str[0]"); + } + { + auto tooltip = get_doxygen_tooltip("\\a str.data()", false); + g_assert(tooltip->buffer->get_text() == "str.data()"); + } + { + auto tooltip = get_doxygen_tooltip("@a str@data()", false); + g_assert(tooltip->buffer->get_text() == "str@data()"); + } + { + auto tooltip = get_doxygen_tooltip("\"@a str@data()\"", false); + g_assert(tooltip->buffer->get_text() == "\"@a str@data()\""); + } + { + auto tooltip = get_doxygen_tooltip("\\a str.data().", false); + g_assert(tooltip->buffer->begin().starts_tag(tooltip->italic_tag)); + auto it = tooltip->buffer->end(); + it.backward_char(); + g_assert(it.ends_tag(tooltip->italic_tag)); + g_assert(tooltip->buffer->get_text() == "str.data()."); + } + { + auto tooltip = get_doxygen_tooltip("_test_", false); + g_assert(tooltip->buffer->get_text() == "_test_"); + } + { + auto tooltip = get_doxygen_tooltip("_test_", false); + g_assert(tooltip->buffer->get_text() == "_test_"); + } + { + auto tooltip = get_doxygen_tooltip("_test_", false); + g_assert(tooltip->buffer->get_text() == "_test_"); + } + { + auto tooltip = get_doxygen_tooltip(R"(/** +* Constructor that sets the time to a given value. +* +* @param timemillis is a number of milliseconds +* passed since Jan 1, 1970. +*/)", true); + g_assert(tooltip->buffer->get_text() == R"(Constructor that sets the time to a given value. + +Parameters: +- timemillis is a number of milliseconds passed since Jan 1, 1970.)"); + } + { + auto tooltip = get_doxygen_tooltip(R"(/** Returns true if @a test points to the start. +* Test pointing to the \\n of a \\r\\n pair will not. +* +* +* @return Whether @a test is at the end of a line. +*/)", true); + g_assert(tooltip->buffer->get_text() == R"(Returns true if test points to the start. Test pointing to the \n of a \r\n pair +will not. + +Returns Whether test is at the end of a line.)"); + } + { + auto tooltip = get_doxygen_tooltip(R"(/** +* test +* \a test +* test +*/)", true); + g_assert(tooltip->buffer->get_text() == "test test test"); + } + { + auto tooltip = get_doxygen_tooltip(R"(/** +* Testing +* - t +* - t2 +* - asd +* +* More testing +* end +*/)", true); + g_assert(tooltip->buffer->get_text() == R"(Testing +- t + - t2 +- asd + +More testing end)"); + } + { + auto tooltip = get_doxygen_tooltip(R"(/** + * Testing + * + * - t + * - t2 + * - asd + * + * More testing + * end + */)", true); + g_assert(tooltip->buffer->get_text() == R"(Testing + +- t + - t2 +- asd + +More testing end)"); + } + { + auto tooltip = get_doxygen_tooltip(R"( //! A normal member taking two arguments and returning an integer value. + /*! + \param a an integer argument. + \param s a constant character pointer. + \return The test results + \sa Test(), ~Test(), testMeToo() and publicVar() + \test testing + @test testing + */)", true); + g_assert(tooltip->buffer->get_text() == R"(A normal member taking two arguments and returning an integer value. + +Parameters: +- a an integer argument. +- s a constant character pointer. + +Returns The test results + +See also Test(), ~Test(), testMeToo() and publicVar() + +\test testing + +@test testing)"); + } + { + auto tooltip = get_doxygen_tooltip(R"(/*! \brief Brief description. + * Brief description continued. + * Brief description continued. + * Brief description continued. + * + * Detailed description starts here. + */)", true); + g_assert(tooltip->buffer->get_text() == R"(Brief description. Brief description continued. Brief description continued. +Brief description continued. + +Detailed description starts here.)"); + } + { + auto tooltip = get_doxygen_tooltip(R"(/** +* \code +* int a = 2; +* \endcode +*/)", true); + g_assert(tooltip->buffer->get_text() == "int a = 2;"); + } + { + auto tooltip = get_doxygen_tooltip(R"(/** +* @code +* int a = 2; +* @endcode +*/)", true); + g_assert(tooltip->buffer->get_text() == "int a = 2;"); + } + { + auto tooltip = get_doxygen_tooltip(R"(/** +* test +* \code +* int a = 2; +* \endcode +* test +*/)", true); + g_assert(tooltip->buffer->get_text() == "test\nint a = 2;\ntest"); + } + { + auto tooltip = get_doxygen_tooltip(R"(/** +* test +* +* \code +* int a = 2; +* \endcode +* +* test +*/)", true); + g_assert(tooltip->buffer->get_text() == "test\n\nint a = 2;\n\ntest"); + } + { + auto tooltip = get_doxygen_tooltip("*this == \"\"", false); + g_assert(tooltip->buffer->get_text() == "*this == \"\""); + } + { + auto tooltip = get_doxygen_tooltip("In some cases (`@{<-n>}` or `@{upstream}`), the expression", false); + g_assert(tooltip->buffer->get_text() == "In some cases (@{<-n>} or @{upstream}), the expression"); + } + { + auto tooltip = get_doxygen_tooltip("/** The intent of test*/", true); + g_assert(tooltip->buffer->get_text() == "The intent of test"); + } + { + auto tooltip = get_doxygen_tooltip("/** The intent of test */", true); + g_assert(tooltip->buffer->get_text() == "The intent of test"); + } + { + auto tooltip = get_doxygen_tooltip("/** Don't insert \"[PATCH]\" in testing*/", true); + g_assert(tooltip->buffer->get_text() == "Don't insert \"[PATCH]\" in testing"); + } + { + auto tooltip = get_doxygen_tooltip("/** Don't insert \"[PAT\\\\CH]\" in testing*/", true); + g_assert(tooltip->buffer->get_text() == "Don't insert \"[PAT\\CH]\" in testing"); + } + { + auto tooltip = get_doxygen_tooltip("/** Don't insert \"[PAT\\\"CH]\" in testing*/", true); + g_assert(tooltip->buffer->get_text() == "Don't insert \"[PAT\"CH]\" in testing"); + } + { + auto tooltip = get_doxygen_tooltip("/** Don't insert \" *test* \" in testing*/", true); + g_assert(tooltip->buffer->get_text() == "Don't insert \" *test* \" in testing"); + } + { + auto tooltip = get_doxygen_tooltip("/** testing*/", true); + g_assert(tooltip->buffer->get_text() == "testing"); + } + { + auto tooltip = get_doxygen_tooltip("/** # testing*/", true); + g_assert(tooltip->buffer->get_text() == "testing"); + } }