From 53c4c598e8fdebfd85cf8d1333126d58cbe08fc2 Mon Sep 17 00:00:00 2001 From: Tim Gromeyer Date: Tue, 22 Aug 2023 15:03:28 +0200 Subject: [PATCH] More robust link highlighting --- markdownhighlighter.cpp | 344 ++++++++++++++++++++++++---------------- markdownhighlighter.h | 9 ++ 2 files changed, 214 insertions(+), 139 deletions(-) diff --git a/markdownhighlighter.cpp b/markdownhighlighter.cpp index 201e57e..5d9c3c7 100644 --- a/markdownhighlighter.cpp +++ b/markdownhighlighter.cpp @@ -117,15 +117,6 @@ void MarkdownHighlighter::addDirtyBlock(const QTextBlock &block) { * /usr/share/kde4/apps/katepart/syntax/markdown.xml */ void MarkdownHighlighter::initHighlightingRules() { - // highlight the reference of reference links - { - HighlightingRule rule(HighlighterState::MaskedSyntax); - rule.pattern = - QRegularExpression(QStringLiteral(R"(^\[.+?\]: \w+://.+$)")); - rule.shouldContain = QStringLiteral("://"); - _highlightingRules.append(rule); - } - // highlight block quotes { HighlightingRule rule(HighlighterState::BlockQuote); @@ -144,98 +135,6 @@ void MarkdownHighlighter::initHighlightingRules() { // rule.pattern = QRegularExpression("^.+? \\| .+? \\| .+$"); // rule.state = HighlighterState::Table; // _highlightingRulesPre.append(rule); - - // highlight urls - { - HighlightingRule rule(HighlighterState::Link); - - // highlight urls without any other markup like http://www.github.com - rule.pattern = - QRegularExpression(QStringLiteral(R"(\b\w+?:\/\/[^\s>]+)")); - rule.capturingGroup = 0; - rule.shouldContain = QStringLiteral("://"); - _highlightingRules.append(rule); - - // highlight urls without any other markup like www.github.com - rule.pattern = - QRegularExpression(QStringLiteral(R"(\bwww\.[^\s]+\.[^\s]+\b)")); - rule.capturingGroup = 0; - rule.shouldContain = QStringLiteral("www."); - _highlightingRules.append(rule); - - // highlight urls with <> but without any . in it - rule.pattern = - QRegularExpression(QStringLiteral(R"(<(\w+?:\/\/[^\s]+)>)")); - rule.capturingGroup = 1; - rule.shouldContain = QStringLiteral("://"); - _highlightingRules.append(rule); - - // highlight links with <> that have a .in it - // rule.pattern = QRegularExpression("<(.+?:\\/\\/.+?)>"); - rule.pattern = QRegularExpression( - QStringLiteral("<([^\\s`][^`]*?\\.[^`]*?[^\\s`])>")); - rule.capturingGroup = 1; - rule.shouldContain = QStringLiteral("<"); - _highlightingRules.append(rule); - - // highlight urls with title - // rule.pattern = QRegularExpression("\\[(.+?)\\]\\(.+?://.+?\\)"); - // rule.pattern = QRegularExpression("\\[(.+?)\\]\\(.+\\)\\B"); - rule.pattern = QRegularExpression( - QStringLiteral(R"(\[([^\[\]]+)\]\((\S+|.+?)\)\B)")); - rule.shouldContain = QStringLiteral("]("); - _highlightingRules.append(rule); - - // highlight urls with empty title - // rule.pattern = QRegularExpression("\\[\\]\\((.+?://.+?)\\)"); - rule.pattern = QRegularExpression(QStringLiteral(R"(\[\]\((.+?)\))")); - rule.shouldContain = QStringLiteral("[]("); - _highlightingRules.append(rule); - - // highlight email links - rule.pattern = QRegularExpression(QStringLiteral("<(.+?@.+?)>")); - rule.shouldContain = QStringLiteral("@"); - _highlightingRules.append(rule); - - // highlight reference links - rule.pattern = - QRegularExpression(QStringLiteral(R"(\[(.+?)\]\[.+?\])")); - rule.shouldContain = QStringLiteral("["); - _highlightingRules.append(rule); - } - - // Images - { - // highlight images with text - HighlightingRule rule(HighlighterState::Image); - rule.pattern = - QRegularExpression(QStringLiteral(R"(!\[(.+?)\]\(.+?\))")); - rule.shouldContain = QStringLiteral("!["); - rule.capturingGroup = 1; - _highlightingRules.append(rule); - - // highlight images without text - rule.pattern = QRegularExpression(QStringLiteral(R"(!\[\]\((.+?)\))")); - rule.shouldContain = QStringLiteral("![]"); - _highlightingRules.append(rule); - } - - // highlight images links - { - HighlightingRule rule(HighlighterState::Link); - rule.pattern = QRegularExpression( - QStringLiteral(R"(\[!\[(.+?)\]\(.+?\)\]\(.+?\))")); - rule.shouldContain = QStringLiteral("[!["); - rule.capturingGroup = 1; - _highlightingRules.append(rule); - - // highlight images links without text - rule.pattern = - QRegularExpression(QStringLiteral(R"(\[!\[\]\(.+?\)\]\((.+?)\))")); - rule.shouldContain = QStringLiteral("[![]("); - _highlightingRules.append(rule); - } - // highlight trailing spaces { HighlightingRule rule(HighlighterState::TrailingSpace); @@ -669,10 +568,8 @@ void MarkdownHighlighter::highlightIndentedCodeBlock(const QString &text) { const QString prevTrimmed = currentBlock().previous().text().trimmed(); // previous line must be empty according to CommonMark except if it is a // heading https://spec.commonmark.org/0.29/#indented-code-block - if (!prevTrimmed.isEmpty() && - previousBlockState() != CodeBlockIndented && - (previousBlockState() < H1 || previousBlockState() > H6) && - previousBlockState() != HeadlineEnd) + if (!prevTrimmed.isEmpty() && previousBlockState() != CodeBlockIndented && + !isHeading(previousBlockState()) && previousBlockState() != HeadlineEnd) return; const QString trimmed = text.trimmed(); @@ -1913,7 +1810,6 @@ void MarkdownHighlighter::setHeadingStyles(HighlighterState rule, void MarkdownHighlighter::highlightAdditionalRules( const QVector &rules, const QString &text) { const auto &maskedFormat = _formats[HighlighterState::MaskedSyntax]; - _linkRanges.clear(); for (const HighlightingRule &rule : rules) { // continue if another current block state was already set if @@ -1943,26 +1839,16 @@ void MarkdownHighlighter::highlightAdditionalRules( format.fontPointSize()); } - if (currentBlockState() >= H1 && currentBlockState() <= H6) { + if (isHeading(currentBlockState())) { // setHeadingStyles(format, match, maskedGroup); } else { - // store masked part of the link as a range - if (rule.state == Link) { - const int start = match.capturedStart(maskedGroup); - const int end = match.capturedStart(maskedGroup) + - match.capturedLength(maskedGroup); - if (!_linkRanges.contains({start, end})) { - _linkRanges.append({start, end}); - } - } - setFormat(match.capturedStart(maskedGroup), match.capturedLength(maskedGroup), currentMaskedFormat); } } - if (currentBlockState() >= H1 && currentBlockState() <= H6) { + if (isHeading(currentBlockState())) { setHeadingStyles(rule.state, match, capturingGroup); } else { @@ -1995,42 +1881,222 @@ int isInLinkRange(int pos, QVector> &range) { /** * @brief highlight inline rules aka Emphasis, bolds, inline code spans, - * underlines, strikethrough. + * underlines, strikethrough, links, and images. */ void MarkdownHighlighter::highlightInlineRules(const QString &text) { bool isEmStrongDone = false; - // TODO: Add Links and Images parsing for (int i = 0; i < text.length(); ++i) { - // make sure we are not in a link range - if (!_linkRanges.isEmpty()) { - const int res = isInLinkRange(i, _linkRanges); - if (res > -1) { - i += res - 1; - continue; - } + QChar currentChar = text.at(i); + + if (currentChar == QLatin1Char('`') || + currentChar == QLatin1Char('~')) { + i = highlightInlineSpans(text, i, currentChar); + } else if (currentChar == QLatin1Char('<') && + MH_SUBSTR(i, 4) == QLatin1String("