699 lines
22 KiB
C++
699 lines
22 KiB
C++
/*
|
|
* Copyright (c) 2014-2019 Patrizio Bekerle -- http://www.bekerle.com
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; version 2 of the License.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
* for more details.
|
|
*
|
|
* QPlainTextEdit markdown highlighter
|
|
*/
|
|
|
|
#include <QTimer>
|
|
#include <QDebug>
|
|
#include "markdownhighlighter.h"
|
|
#include <QRegularExpression>
|
|
#include <QRegularExpressionMatch>
|
|
#include <QRegularExpressionMatchIterator>
|
|
|
|
|
|
/**
|
|
* Markdown syntax highlighting
|
|
*
|
|
* markdown syntax:
|
|
* http://daringfireball.net/projects/markdown/syntax
|
|
*
|
|
* @param parent
|
|
* @return
|
|
*/
|
|
MarkdownHighlighter::MarkdownHighlighter(
|
|
QTextDocument *parent, HighlightingOptions highlightingOptions)
|
|
: QSyntaxHighlighter(parent) {
|
|
_highlightingOptions = highlightingOptions;
|
|
_timer = new QTimer(this);
|
|
QObject::connect(_timer, SIGNAL(timeout()),
|
|
this, SLOT(timerTick()));
|
|
_timer->start(1000);
|
|
|
|
// initialize the highlighting rules
|
|
initHighlightingRules();
|
|
|
|
// initialize the text formats
|
|
initTextFormats();
|
|
}
|
|
|
|
/**
|
|
* Does jobs every second
|
|
*/
|
|
void MarkdownHighlighter::timerTick() {
|
|
// qDebug() << "timerTick: " << this << ", " << this->parent()->parent()->parent()->objectName();
|
|
|
|
// re-highlight all dirty blocks
|
|
reHighlightDirtyBlocks();
|
|
|
|
// emit a signal every second if there was some highlighting done
|
|
if (_highlightingFinished) {
|
|
_highlightingFinished = false;
|
|
emit(highlightingFinished());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Re-highlights all dirty blocks
|
|
*/
|
|
void MarkdownHighlighter::reHighlightDirtyBlocks() {
|
|
while (_dirtyTextBlocks.count() > 0) {
|
|
QTextBlock block = _dirtyTextBlocks.at(0);
|
|
rehighlightBlock(block);
|
|
_dirtyTextBlocks.removeFirst();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Clears the dirty blocks vector
|
|
*/
|
|
void MarkdownHighlighter::clearDirtyBlocks() {
|
|
_dirtyTextBlocks.clear();
|
|
}
|
|
|
|
/**
|
|
* Adds a dirty block to the list if it doesn't already exist
|
|
*
|
|
* @param block
|
|
*/
|
|
void MarkdownHighlighter::addDirtyBlock(QTextBlock block) {
|
|
if (!_dirtyTextBlocks.contains(block)) {
|
|
_dirtyTextBlocks.append(block);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initializes the highlighting rules
|
|
*
|
|
* regexp tester:
|
|
* https://regex101.com
|
|
*
|
|
* other examples:
|
|
* /usr/share/kde4/apps/katepart/syntax/markdown.xml
|
|
*/
|
|
void MarkdownHighlighter::initHighlightingRules() {
|
|
// highlight the reference of reference links
|
|
{
|
|
HighlightingRule rule(HighlighterState::MaskedSyntax);
|
|
rule.pattern = QRegularExpression("^\\[.+?\\]: \\w+://.+$");
|
|
_highlightingRulesPre.append(rule);
|
|
}
|
|
|
|
// highlight unordered lists
|
|
{
|
|
HighlightingRule rule(HighlighterState::List);
|
|
rule.pattern = QRegularExpression("^\\s*[-*+]\\s");
|
|
rule.useStateAsCurrentBlockState = true;
|
|
_highlightingRulesPre.append(rule);
|
|
|
|
// highlight ordered lists
|
|
rule.pattern = QRegularExpression("^\\s*\\d+\\.\\s");
|
|
_highlightingRulesPre.append(rule);
|
|
}
|
|
|
|
// highlight block quotes
|
|
{
|
|
HighlightingRule rule(HighlighterState::BlockQuote);
|
|
rule.pattern = QRegularExpression(
|
|
_highlightingOptions.testFlag(
|
|
HighlightingOption::FullyHighlightedBlockQuote) ?
|
|
"^\\s*(>\\s*.+)" : "^\\s*(>\\s*)+");
|
|
_highlightingRulesPre.append(rule);
|
|
}
|
|
|
|
// highlight horizontal rulers
|
|
{
|
|
HighlightingRule rule(HighlighterState::HorizontalRuler);
|
|
rule.pattern = QRegularExpression("^([*\\-_]\\s?){3,}$");
|
|
_highlightingRulesPre.append(rule);
|
|
}
|
|
|
|
// highlight tables without starting |
|
|
// we drop that for now, it's far too messy to deal with
|
|
// rule = HighlightingRule();
|
|
// rule.pattern = QRegularExpression("^.+? \\| .+? \\| .+$");
|
|
// rule.state = HighlighterState::Table;
|
|
// _highlightingRulesPre.append(rule);
|
|
|
|
/*
|
|
* highlight italic
|
|
* this goes before bold so that bold can overwrite italic
|
|
*
|
|
* text to test:
|
|
* **bold** normal **bold**
|
|
* *start of line* normal
|
|
* normal *end of line*
|
|
* * list item *italic*
|
|
*/
|
|
{
|
|
HighlightingRule rule(HighlighterState::Italic);
|
|
// we don't allow a space after the starting * to prevent problems with
|
|
// unordered lists starting with a *
|
|
rule.pattern = QRegularExpression(
|
|
"(?:^|[^\\*\\b])(?:\\*([^\\* ][^\\*]*?)\\*)(?:[^\\*\\b]|$)");
|
|
rule.capturingGroup = 1;
|
|
_highlightingRulesAfter.append(rule);
|
|
|
|
rule.pattern = QRegularExpression("\\b_([^_]+)_\\b");
|
|
_highlightingRulesAfter.append(rule);
|
|
}
|
|
|
|
{
|
|
HighlightingRule rule(HighlighterState::Bold);
|
|
// highlight bold
|
|
rule.pattern = QRegularExpression("\\B\\*{2}(.+?)\\*{2}\\B");
|
|
rule.capturingGroup = 1;
|
|
_highlightingRulesAfter.append(rule);
|
|
rule.pattern = QRegularExpression("\\b__(.+?)__\\b");
|
|
_highlightingRulesAfter.append(rule);
|
|
}
|
|
|
|
// highlight urls
|
|
{
|
|
HighlightingRule rule(HighlighterState::Link);
|
|
|
|
// highlight urls without any other markup
|
|
rule.pattern = QRegularExpression("\\b\\w+?:\\/\\/[^\\s]+");
|
|
rule.capturingGroup = 1;
|
|
_highlightingRulesAfter.append(rule);
|
|
|
|
// rule.pattern = QRegularExpression("<(.+?:\\/\\/.+?)>");
|
|
rule.pattern = QRegularExpression("<([^\\s`][^`]*?[^\\s`])>");
|
|
rule.capturingGroup = 1;
|
|
_highlightingRulesAfter.append(rule);
|
|
|
|
// highlight urls with title
|
|
// rule.pattern = QRegularExpression("\\[(.+?)\\]\\(.+?://.+?\\)");
|
|
// rule.pattern = QRegularExpression("\\[(.+?)\\]\\(.+\\)\\B");
|
|
rule.pattern = QRegularExpression("\\[([^\\[\\]]+)\\]\\((\\S+|.+?)\\)\\B");
|
|
_highlightingRulesAfter.append(rule);
|
|
|
|
// highlight urls with empty title
|
|
// rule.pattern = QRegularExpression("\\[\\]\\((.+?://.+?)\\)");
|
|
rule.pattern = QRegularExpression("\\[\\]\\((.+?)\\)");
|
|
_highlightingRulesAfter.append(rule);
|
|
|
|
// highlight email links
|
|
rule.pattern = QRegularExpression("<(.+?@.+?)>");
|
|
_highlightingRulesAfter.append(rule);
|
|
|
|
// highlight reference links
|
|
rule.pattern = QRegularExpression("\\[(.+?)\\]\\s?\\[.+?\\]");
|
|
_highlightingRulesAfter.append(rule);
|
|
}
|
|
|
|
// Images
|
|
{
|
|
// highlight images with text
|
|
HighlightingRule rule(HighlighterState::Image);
|
|
rule.pattern = QRegularExpression("!\\[(.+?)\\]\\(.+?\\)");
|
|
rule.capturingGroup = 1;
|
|
_highlightingRulesAfter.append(rule);
|
|
|
|
// highlight images without text
|
|
rule.pattern = QRegularExpression("!\\[\\]\\((.+?)\\)");
|
|
_highlightingRulesAfter.append(rule);
|
|
}
|
|
|
|
// highlight images links
|
|
{
|
|
// HighlightingRule rule;
|
|
HighlightingRule rule(HighlighterState::Link);
|
|
rule.pattern = QRegularExpression("\\[!\\[(.+?)\\]\\(.+?\\)\\]\\(.+?\\)");
|
|
rule.capturingGroup = 1;
|
|
_highlightingRulesAfter.append(rule);
|
|
|
|
// highlight images links without text
|
|
rule.pattern = QRegularExpression("\\[!\\[\\]\\(.+?\\)\\]\\((.+?)\\)");
|
|
_highlightingRulesAfter.append(rule);
|
|
}
|
|
|
|
// highlight inline code
|
|
{
|
|
HighlightingRule rule(HighlighterState::InlineCodeBlock);
|
|
// HighlightingRule rule;
|
|
rule.pattern = QRegularExpression("`(.+?)`");
|
|
rule.capturingGroup = 1;
|
|
_highlightingRulesAfter.append(rule);
|
|
}
|
|
|
|
// highlight code blocks with four spaces or tabs in front of them
|
|
// and no list character after that
|
|
{
|
|
HighlightingRule rule(HighlighterState::CodeBlock);
|
|
// HighlightingRule rule;
|
|
rule.pattern = QRegularExpression("^((\\t)|( {4,})).+$");
|
|
rule.disableIfCurrentStateIsSet = true;
|
|
_highlightingRulesAfter.append(rule);
|
|
}
|
|
|
|
// highlight inline comments
|
|
{
|
|
HighlightingRule rule(HighlighterState::Comment);
|
|
rule.pattern = QRegularExpression("<!\\-\\-(.+?)\\-\\->");
|
|
rule.capturingGroup = 1;
|
|
_highlightingRulesAfter.append(rule);
|
|
|
|
// highlight comments for Rmarkdown for academic papers
|
|
rule.pattern = QRegularExpression("^\\[.+?\\]: # \\(.+?\\)$");
|
|
_highlightingRulesAfter.append(rule);
|
|
}
|
|
|
|
// highlight tables with starting |
|
|
{
|
|
HighlightingRule rule(HighlighterState::Table);
|
|
rule.pattern = QRegularExpression("^\\|.+?\\|$");
|
|
_highlightingRulesAfter.append(rule);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initializes the text formats
|
|
*
|
|
* @param defaultFontSize
|
|
*/
|
|
void MarkdownHighlighter::initTextFormats(int defaultFontSize) {
|
|
QTextCharFormat format;
|
|
|
|
// set character formats for headlines
|
|
format = QTextCharFormat();
|
|
format.setForeground(QBrush(QColor(0, 49, 110)));
|
|
format.setFontWeight(QFont::Bold);
|
|
format.setFontPointSize(defaultFontSize * 1.6);
|
|
_formats[H1] = format;
|
|
format.setFontPointSize(defaultFontSize * 1.5);
|
|
_formats[H2] = format;
|
|
format.setFontPointSize(defaultFontSize * 1.4);
|
|
_formats[H3] = format;
|
|
format.setFontPointSize(defaultFontSize * 1.3);
|
|
_formats[H4] = format;
|
|
format.setFontPointSize(defaultFontSize * 1.2);
|
|
_formats[H5] = format;
|
|
format.setFontPointSize(defaultFontSize * 1.1);
|
|
_formats[H6] = format;
|
|
format.setFontPointSize(defaultFontSize);
|
|
|
|
// set character format for horizontal rulers
|
|
format = QTextCharFormat();
|
|
format.setForeground(QBrush(Qt::darkGray));
|
|
format.setBackground(QBrush(Qt::lightGray));
|
|
_formats[HorizontalRuler] = format;
|
|
|
|
// set character format for lists
|
|
format = QTextCharFormat();
|
|
format.setForeground(QBrush(QColor(163, 0, 123)));
|
|
_formats[List] = format;
|
|
|
|
// set character format for links
|
|
format = QTextCharFormat();
|
|
format.setForeground(QBrush(QColor(0, 128, 255)));
|
|
format.setFontUnderline(true);
|
|
_formats[Link] = format;
|
|
|
|
// set character format for images
|
|
format = QTextCharFormat();
|
|
format.setForeground(QBrush(QColor(0, 191, 0)));
|
|
format.setBackground(QBrush(QColor(228, 255, 228)));
|
|
_formats[Image] = format;
|
|
|
|
// set character format for code blocks
|
|
format = QTextCharFormat();
|
|
format.setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
|
|
format.setBackground(QColor(220, 220, 220));
|
|
_formats[CodeBlock] = format;
|
|
_formats[InlineCodeBlock] = format;
|
|
|
|
// set character format for italic
|
|
format = QTextCharFormat();
|
|
format.setFontWeight(QFont::StyleItalic);
|
|
format.setFontItalic(true);
|
|
_formats[Italic] = format;
|
|
|
|
// set character format for bold
|
|
format = QTextCharFormat();
|
|
format.setFontWeight(QFont::Bold);
|
|
_formats[Bold] = format;
|
|
|
|
// set character format for comments
|
|
format = QTextCharFormat();
|
|
format.setForeground(QBrush(Qt::gray));
|
|
_formats[Comment] = format;
|
|
|
|
// set character format for masked syntax
|
|
format = QTextCharFormat();
|
|
format.setForeground(QBrush("#cccccc"));
|
|
_formats[MaskedSyntax] = format;
|
|
|
|
// set character format for tables
|
|
format = QTextCharFormat();
|
|
format.setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
|
|
format.setForeground(QBrush(QColor("#649449")));
|
|
_formats[Table] = format;
|
|
|
|
// set character format for block quotes
|
|
format = QTextCharFormat();
|
|
format.setForeground(QBrush(QColor(Qt::darkRed)));
|
|
_formats[BlockQuote] = format;
|
|
|
|
format = QTextCharFormat();
|
|
_formats[HeadlineEnd] = format;
|
|
|
|
format = QTextCharFormat();
|
|
_formats[NoState] = format;
|
|
}
|
|
|
|
/**
|
|
* Sets the text formats
|
|
*
|
|
* @param formats
|
|
*/
|
|
void MarkdownHighlighter::setTextFormats(
|
|
QHash<HighlighterState, QTextCharFormat> formats) {
|
|
_formats = formats;
|
|
}
|
|
|
|
/**
|
|
* Sets a text format
|
|
*
|
|
* @param formats
|
|
*/
|
|
void MarkdownHighlighter::setTextFormat(HighlighterState state,
|
|
QTextCharFormat format) {
|
|
_formats[state] = format;
|
|
}
|
|
|
|
/**
|
|
* Does the markdown highlighting
|
|
*
|
|
* @param text
|
|
*/
|
|
void MarkdownHighlighter::highlightBlock(const QString &text) {
|
|
setCurrentBlockState(HighlighterState::NoState);
|
|
currentBlock().setUserState(HighlighterState::NoState);
|
|
highlightMarkdown(text);
|
|
_highlightingFinished = true;
|
|
}
|
|
|
|
void MarkdownHighlighter::highlightMarkdown(QString text) {
|
|
if (!text.isEmpty()) {
|
|
highlightAdditionalRules(_highlightingRulesPre, text);
|
|
|
|
// needs to be called after the horizontal ruler highlighting
|
|
highlightHeadline(text);
|
|
|
|
highlightAdditionalRules(_highlightingRulesAfter, text);
|
|
}
|
|
|
|
highlightCommentBlock(text);
|
|
highlightCodeBlock(text);
|
|
}
|
|
|
|
/**
|
|
* Highlight headlines
|
|
*
|
|
* @param text
|
|
*/
|
|
void MarkdownHighlighter::highlightHeadline(QString text) {
|
|
QRegularExpression regex("^(#+)\\s+(.+?)$");
|
|
QRegularExpressionMatch match = regex.match(text);
|
|
QTextCharFormat &maskedFormat = _formats[HighlighterState::MaskedSyntax];
|
|
|
|
// check for headline blocks with # in front of them
|
|
if (match.hasMatch()) {
|
|
int count = match.captured(1).count();
|
|
|
|
// we just have H1 to H6
|
|
count = qMin(count, 6);
|
|
|
|
HighlighterState state = HighlighterState(
|
|
HighlighterState::H1 + count - 1);
|
|
|
|
QTextCharFormat &format = _formats[state];
|
|
QTextCharFormat currentMaskedFormat = maskedFormat;
|
|
|
|
// set the font size from the current rule's font format
|
|
currentMaskedFormat.setFontPointSize(format.fontPointSize());
|
|
|
|
// first highlight everything as MaskedSyntax
|
|
setFormat(match.capturedStart(), match.capturedLength(),
|
|
currentMaskedFormat);
|
|
|
|
// then highlight with the real format
|
|
setFormat(match.capturedStart(2), match.capturedLength(2),
|
|
_formats[state]);
|
|
|
|
// set a margin for the current block
|
|
setCurrentBlockMargin(state);
|
|
|
|
setCurrentBlockState(state);
|
|
currentBlock().setUserState(state);
|
|
return;
|
|
}
|
|
|
|
// take care of ==== and ---- headlines
|
|
QRegularExpression patternH1 = QRegularExpression("^=+$");
|
|
QRegularExpression patternH2 = QRegularExpression("^-+$");
|
|
QTextBlock previousBlock = currentBlock().previous();
|
|
QString previousText = previousBlock.text();
|
|
previousText.trimmed().remove(QRegularExpression("[=-]"));
|
|
|
|
// check for ===== after a headline text and highlight as H1
|
|
if (patternH1.match(text).hasMatch()) {
|
|
if (((previousBlockState() == HighlighterState::H1) ||
|
|
(previousBlockState() == HighlighterState::NoState)) &&
|
|
(previousText.length() > 0)) {
|
|
// set the font size from the current rule's font format
|
|
QTextCharFormat currentMaskedFormat = maskedFormat;
|
|
currentMaskedFormat.setFontPointSize(
|
|
_formats[HighlighterState::H1].fontPointSize());
|
|
|
|
setFormat(0, text.length(), currentMaskedFormat);
|
|
setCurrentBlockState(HighlighterState::HeadlineEnd);
|
|
previousBlock.setUserState(HighlighterState::H1);
|
|
|
|
// set a margin for the current block
|
|
setCurrentBlockMargin(HighlighterState::H1);
|
|
|
|
// we want to re-highlight the previous block
|
|
// this must not done directly, but with a queue, otherwise it
|
|
// will crash
|
|
// setting the character format of the previous text, because this
|
|
// causes text to be formatted the same way when writing after
|
|
// the text
|
|
addDirtyBlock(previousBlock);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// check for ----- after a headline text and highlight as H2
|
|
if (patternH2.match(text).hasMatch()) {
|
|
if (((previousBlockState() == HighlighterState::H2) ||
|
|
(previousBlockState() == HighlighterState::NoState)) &&
|
|
(previousText.length() > 0)) {
|
|
// set the font size from the current rule's font format
|
|
QTextCharFormat currentMaskedFormat = maskedFormat;
|
|
currentMaskedFormat.setFontPointSize(
|
|
_formats[HighlighterState::H2].fontPointSize());
|
|
|
|
setFormat(0, text.length(), currentMaskedFormat);
|
|
setCurrentBlockState(HighlighterState::HeadlineEnd);
|
|
previousBlock.setUserState(HighlighterState::H2);
|
|
|
|
// set a margin for the current block
|
|
setCurrentBlockMargin(HighlighterState::H2);
|
|
|
|
// we want to re-highlight the previous block
|
|
addDirtyBlock(previousBlock);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
QTextBlock nextBlock = currentBlock().next();
|
|
QString nextBlockText = nextBlock.text();
|
|
|
|
// highlight as H1 if next block is =====
|
|
if (patternH1.match(nextBlockText).hasMatch() ||
|
|
patternH2.match(nextBlockText).hasMatch()) {
|
|
setFormat(0, text.length(), _formats[HighlighterState::H1]);
|
|
setCurrentBlockState(HighlighterState::H1);
|
|
currentBlock().setUserState(HighlighterState::H1);
|
|
}
|
|
|
|
// highlight as H2 if next block is -----
|
|
if (patternH2.match(nextBlockText).hasMatch()) {
|
|
setFormat(0, text.length(), _formats[HighlighterState::H2]);
|
|
setCurrentBlockState(HighlighterState::H2);
|
|
currentBlock().setUserState(HighlighterState::H2);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets a margin for the current block
|
|
*
|
|
* @param state
|
|
*/
|
|
void MarkdownHighlighter::setCurrentBlockMargin(
|
|
MarkdownHighlighter::HighlighterState state) {
|
|
// this is currently disabled because it causes multiple problems:
|
|
// - it prevents "undo" in headlines
|
|
// https://github.com/pbek/QOwnNotes/issues/520
|
|
// - invisible lines at the end of a note
|
|
// https://github.com/pbek/QOwnNotes/issues/667
|
|
// - a crash when reaching the invisible lines when the current line is
|
|
// highlighted
|
|
// https://github.com/pbek/QOwnNotes/issues/701
|
|
return;
|
|
|
|
qreal margin;
|
|
|
|
switch (state) {
|
|
case HighlighterState::H1:
|
|
margin = 5;
|
|
break;
|
|
case HighlighterState::H2:
|
|
case HighlighterState::H3:
|
|
case HighlighterState::H4:
|
|
case HighlighterState::H5:
|
|
case HighlighterState::H6:
|
|
margin = 3;
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
QTextBlockFormat blockFormat = currentBlock().blockFormat();
|
|
blockFormat.setTopMargin(2);
|
|
blockFormat.setBottomMargin(margin);
|
|
|
|
// this prevents "undo" in headlines!
|
|
QTextCursor* myCursor = new QTextCursor(currentBlock());
|
|
myCursor->setBlockFormat(blockFormat);
|
|
}
|
|
|
|
/**
|
|
* Highlight multi-line code blocks
|
|
*
|
|
* @param text
|
|
*/
|
|
void MarkdownHighlighter::highlightCodeBlock(QString text) {
|
|
QRegularExpression regex("^```\\w*?$");
|
|
QRegularExpressionMatch match = regex.match(text);
|
|
|
|
if (match.hasMatch()) {
|
|
setCurrentBlockState(
|
|
previousBlockState() == HighlighterState::CodeBlock ?
|
|
HighlighterState::CodeBlockEnd : HighlighterState::CodeBlock);
|
|
// set the font size from the current rule's font format
|
|
QTextCharFormat &maskedFormat =
|
|
_formats[HighlighterState::MaskedSyntax];
|
|
maskedFormat.setFontPointSize(
|
|
_formats[HighlighterState::CodeBlock].fontPointSize());
|
|
|
|
setFormat(0, text.length(), maskedFormat);
|
|
} else if (previousBlockState() == HighlighterState::CodeBlock) {
|
|
setCurrentBlockState(HighlighterState::CodeBlock);
|
|
setFormat(0, text.length(), _formats[HighlighterState::CodeBlock]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Highlight multi-line comments
|
|
*
|
|
* @param text
|
|
*/
|
|
void MarkdownHighlighter::highlightCommentBlock(QString text) {
|
|
bool highlight = false;
|
|
text = text.trimmed();
|
|
QString startText = "<!--";
|
|
QString endText = "-->";
|
|
|
|
// we will skip this case because that is an inline comment and causes
|
|
// troubles here
|
|
if (text.startsWith(startText) && text.contains(endText)) {
|
|
return;
|
|
}
|
|
|
|
if (text.startsWith(startText) ||
|
|
(!text.endsWith(endText) &&
|
|
(previousBlockState() == HighlighterState::Comment))) {
|
|
setCurrentBlockState(HighlighterState::Comment);
|
|
highlight = true;
|
|
} else if (text.endsWith(endText)) {
|
|
highlight = true;
|
|
}
|
|
|
|
if (highlight) {
|
|
setFormat(0, text.length(), _formats[HighlighterState::Comment]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Highlights the rules from the _highlightingRules list
|
|
*
|
|
* @param text
|
|
*/
|
|
void MarkdownHighlighter::highlightAdditionalRules(
|
|
QVector<HighlightingRule> &rules, QString text) {
|
|
QTextCharFormat &maskedFormat = _formats[HighlighterState::MaskedSyntax];
|
|
|
|
for(const HighlightingRule &rule : rules) {
|
|
// continue if an other current block state was already set if
|
|
// disableIfCurrentStateIsSet is set
|
|
if (rule.disableIfCurrentStateIsSet &&
|
|
(currentBlockState() != HighlighterState::NoState)) {
|
|
continue;
|
|
}
|
|
|
|
QRegularExpression expression(rule.pattern);
|
|
QRegularExpressionMatchIterator iterator = expression.globalMatch(text);
|
|
int capturingGroup = rule.capturingGroup;
|
|
int maskedGroup = rule.maskedGroup;
|
|
QTextCharFormat &format = _formats[rule.state];
|
|
|
|
// store the current block state if useStateAsCurrentBlockState
|
|
// is set
|
|
if (iterator.hasNext() && rule.useStateAsCurrentBlockState) {
|
|
setCurrentBlockState(rule.state);
|
|
}
|
|
|
|
// find and format all occurrences
|
|
while (iterator.hasNext()) {
|
|
QRegularExpressionMatch match = iterator.next();
|
|
|
|
// if there is a capturingGroup set then first highlight
|
|
// everything as MaskedSyntax and highlight capturingGroup
|
|
// with the real format
|
|
if (capturingGroup > 0) {
|
|
QTextCharFormat currentMaskedFormat = maskedFormat;
|
|
// set the font size from the current rule's font format
|
|
if (format.fontPointSize() > 0) {
|
|
currentMaskedFormat.setFontPointSize(format.fontPointSize());
|
|
}
|
|
|
|
setFormat(match.capturedStart(maskedGroup),
|
|
match.capturedLength(maskedGroup),
|
|
currentMaskedFormat);
|
|
}
|
|
|
|
setFormat(match.capturedStart(capturingGroup),
|
|
match.capturedLength(capturingGroup),
|
|
format);
|
|
}
|
|
}
|
|
}
|
|
|
|
void MarkdownHighlighter::setHighlightingOptions(HighlightingOptions options) {
|
|
_highlightingOptions = options;
|
|
}
|