From bb308a06d10ec9266732dd51545712fa026bd665 Mon Sep 17 00:00:00 2001 From: Le Tan Date: Thu, 12 Jul 2018 20:19:23 +0800 Subject: [PATCH] PegMarkdownHighlighter: multi-threads highlighter support --- src/hgmarkdownhighlighter.cpp | 5 +- src/hgmarkdownhighlighter.h | 174 +------------ src/markdownhighlighterdata.h | 178 +++++++++++++ src/peghighlighterresult.cpp | 223 ++++++++++++++++ src/peghighlighterresult.h | 75 ++++++ src/pegmarkdownhighlighter.cpp | 409 ++++++++++++++++++++++++++++++ src/pegmarkdownhighlighter.h | 179 +++++++++++++ src/pegparser.cpp | 276 ++++++++++++++++++++ src/pegparser.h | 186 ++++++++++++++ src/resources/vnote.ini | 2 +- src/src.pro | 11 +- src/vcodeblockhighlighthelper.cpp | 43 ++-- src/vcodeblockhighlighthelper.h | 31 ++- src/vconfigmanager.h | 2 +- src/vconstants.h | 3 + src/vdocument.cpp | 4 +- src/vdocument.h | 8 +- src/vlivepreviewhelper.cpp | 3 +- src/vlivepreviewhelper.h | 2 +- src/vmdedit.cpp | 7 +- src/vmdedit.h | 2 - src/vmdeditor.cpp | 44 ++-- src/vmdeditor.h | 10 +- src/vmdtab.cpp | 6 +- src/vpreviewmanager.cpp | 4 +- src/vpreviewmanager.h | 6 +- 26 files changed, 1634 insertions(+), 259 deletions(-) create mode 100644 src/peghighlighterresult.cpp create mode 100644 src/peghighlighterresult.h create mode 100644 src/pegmarkdownhighlighter.cpp create mode 100644 src/pegmarkdownhighlighter.h create mode 100644 src/pegparser.cpp create mode 100644 src/pegparser.h diff --git a/src/hgmarkdownhighlighter.cpp b/src/hgmarkdownhighlighter.cpp index 9a239289..bbd8c65c 100644 --- a/src/hgmarkdownhighlighter.cpp +++ b/src/hgmarkdownhighlighter.cpp @@ -29,6 +29,7 @@ HGMarkdownHighlighter::HGMarkdownHighlighter(const QVector &s int waitInterval, QTextDocument *parent) : QSyntaxHighlighter(parent), + m_timeStamp(0), highlightingStyles(styles), m_codeBlockStyles(codeBlockStyles), m_numOfCodeBlockHighlightsToRecv(0), @@ -864,9 +865,9 @@ void HGMarkdownHighlighter::handleContentChange(int /* position */, int charsRem return; } - m_signalOut = false; + ++m_timeStamp; - timer->stop(); + m_signalOut = false; timer->start(); } diff --git a/src/hgmarkdownhighlighter.h b/src/hgmarkdownhighlighter.h index 15545d64..215b58d6 100644 --- a/src/hgmarkdownhighlighter.h +++ b/src/hgmarkdownhighlighter.h @@ -1,7 +1,6 @@ #ifndef HGMARKDOWNHIGHLIGHTER_H #define HGMARKDOWNHIGHLIGHTER_H -#include #include #include #include @@ -9,177 +8,10 @@ #include #include "vtextblockdata.h" -#include "vconstants.h" +#include "markdownhighlighterdata.h" -extern "C" { -#include -} - -QT_BEGIN_NAMESPACE class QTextDocument; -QT_END_NAMESPACE -struct HighlightingStyle -{ - pmh_element_type type; - QTextCharFormat format; -}; - -// One continuous region for a certain markdown highlight style -// within a QTextBlock. -// Pay attention to the change of HighlightingStyles[] -struct HLUnit -{ - // Highlight offset @start and @length with style HighlightingStyles[styleIndex] - // within a QTextBlock - unsigned long start; - unsigned long length; - unsigned int styleIndex; -}; - -struct HLUnitStyle -{ - unsigned long start; - unsigned long length; - QString style; -}; - -// Fenced code block only. -struct VCodeBlock -{ - // Global position of the start. - int m_startPos; - - int m_startBlock; - int m_endBlock; - - QString m_lang; - - QString m_text; - - bool equalContent(const VCodeBlock &p_block) const - { - return p_block.m_lang == m_lang && p_block.m_text == m_text; - } - - void updateNonContent(const VCodeBlock &p_block) - { - m_startPos = p_block.m_startPos; - m_startBlock = p_block.m_startBlock; - m_endBlock = p_block.m_endBlock; - } -}; - - -struct VMathjaxBlock -{ - VMathjaxBlock() - : m_blockNumber(-1), - m_previewedAsBlock(false), - m_index(-1), - m_length(-1) - { - } - - VMathjaxBlock(int p_blockNumber, const MathjaxInfo &p_info) - : m_blockNumber(p_blockNumber), - m_previewedAsBlock(p_info.m_previewedAsBlock), - m_index(p_info.m_index), - m_length(p_info.m_length), - m_text(p_info.m_text) - { - } - - bool equalContent(const VMathjaxBlock &p_block) const - { - return m_text == p_block.m_text; - } - - void updateNonContent(const VMathjaxBlock &p_block) - { - m_blockNumber = p_block.m_blockNumber; - m_previewedAsBlock = p_block.m_previewedAsBlock; - m_index = p_block.m_index; - m_length = p_block.m_length; - } - - int m_blockNumber; - - bool m_previewedAsBlock; - - // Start index within the block. - int m_index; - - int m_length; - - QString m_text; -}; - - -// Highlight unit with global position and string style name. -struct HLUnitPos -{ - HLUnitPos() : m_position(-1), m_length(-1) - { - } - - HLUnitPos(int p_position, int p_length, const QString &p_style) - : m_position(p_position), m_length(p_length), m_style(p_style) - { - } - - int m_position; - int m_length; - QString m_style; -}; - -// Denote the region of a certain Markdown element. -struct VElementRegion -{ - VElementRegion() : m_startPos(0), m_endPos(0) {} - - VElementRegion(int p_start, int p_end) : m_startPos(p_start), m_endPos(p_end) {} - - // The start position of the region in document. - int m_startPos; - - // The end position of the region in document. - int m_endPos; - - // Whether this region contains @p_pos. - bool contains(int p_pos) const - { - return m_startPos <= p_pos && m_endPos > p_pos; - } - - bool intersect(int p_start, int p_end) const - { - return !(p_end <= m_startPos || p_start >= m_endPos); - } - - bool operator==(const VElementRegion &p_other) const - { - return (m_startPos == p_other.m_startPos - && m_endPos == p_other.m_endPos); - } - - bool operator<(const VElementRegion &p_other) const - { - if (m_startPos < p_other.m_startPos) { - return true; - } else if (m_startPos == p_other.m_startPos) { - // If a < b is true, then b < a must be false. - return m_endPos < p_other.m_endPos; - } else { - return false; - } - } - - QString toString() const - { - return QString("[%1,%2)").arg(m_startPos).arg(m_endPos); - } -}; class HGMarkdownHighlighter : public QSyntaxHighlighter { @@ -195,8 +27,6 @@ public: // Request to update highlihgt (re-parse and re-highlight) void setCodeBlockHighlights(const QVector &p_units); - const QMap &getPotentialPreviewBlocks() const; - const QVector &getHeaderRegions() const; const QSet &getPossiblePreviewBlocks() const; @@ -259,6 +89,8 @@ private: int m_length; }; + TimeStamp m_timeStamp; + QRegExp codeBlockStartExp; QRegExp codeBlockEndExp; diff --git a/src/markdownhighlighterdata.h b/src/markdownhighlighterdata.h index bc40a054..cdf64bf8 100644 --- a/src/markdownhighlighterdata.h +++ b/src/markdownhighlighterdata.h @@ -1,4 +1,182 @@ #ifndef MARKDOWNHIGHLIGHTERDATA_H #define MARKDOWNHIGHLIGHTERDATA_H +#include + +#include "vconstants.h" +#include "vtextblockdata.h" + +extern "C" { +#include +} + +struct HighlightingStyle +{ + pmh_element_type type; + QTextCharFormat format; +}; + +// One continuous region for a certain markdown highlight style +// within a QTextBlock. +// Pay attention to the change of HighlightingStyles[] +struct HLUnit +{ + // Highlight offset @start and @length with style HighlightingStyles[styleIndex] + // within a QTextBlock + unsigned long start; + unsigned long length; + unsigned int styleIndex; +}; + +struct HLUnitStyle +{ + unsigned long start; + unsigned long length; + QString style; +}; + +// Fenced code block only. +struct VCodeBlock +{ + // Global position of the start. + int m_startPos; + + int m_startBlock; + int m_endBlock; + + QString m_lang; + + QString m_text; + + bool equalContent(const VCodeBlock &p_block) const + { + return p_block.m_lang == m_lang && p_block.m_text == m_text; + } + + void updateNonContent(const VCodeBlock &p_block) + { + m_startPos = p_block.m_startPos; + m_startBlock = p_block.m_startBlock; + m_endBlock = p_block.m_endBlock; + } +}; + + +struct VMathjaxBlock +{ + VMathjaxBlock() + : m_blockNumber(-1), + m_previewedAsBlock(false), + m_index(-1), + m_length(-1) + { + } + + VMathjaxBlock(int p_blockNumber, const MathjaxInfo &p_info) + : m_blockNumber(p_blockNumber), + m_previewedAsBlock(p_info.m_previewedAsBlock), + m_index(p_info.m_index), + m_length(p_info.m_length), + m_text(p_info.m_text) + { + } + + bool equalContent(const VMathjaxBlock &p_block) const + { + return m_text == p_block.m_text; + } + + void updateNonContent(const VMathjaxBlock &p_block) + { + m_blockNumber = p_block.m_blockNumber; + m_previewedAsBlock = p_block.m_previewedAsBlock; + m_index = p_block.m_index; + m_length = p_block.m_length; + } + + int m_blockNumber; + + bool m_previewedAsBlock; + + // Start index within the block. + int m_index; + + int m_length; + + QString m_text; +}; + + +// Highlight unit with global position and string style name. +struct HLUnitPos +{ + HLUnitPos() : m_position(-1), m_length(-1) + { + } + + HLUnitPos(int p_position, int p_length, const QString &p_style) + : m_position(p_position), m_length(p_length), m_style(p_style) + { + } + + int m_position; + int m_length; + QString m_style; +}; + +// Denote the region of a certain Markdown element. +struct VElementRegion +{ + VElementRegion() : m_startPos(0), m_endPos(0) {} + + VElementRegion(int p_start, int p_end) : m_startPos(p_start), m_endPos(p_end) {} + + // The start position of the region in document. + int m_startPos; + + // The end position of the region in document. + int m_endPos; + + // Whether this region contains @p_pos. + bool contains(int p_pos) const + { + return m_startPos <= p_pos && m_endPos > p_pos; + } + + bool intersect(int p_start, int p_end) const + { + return !(p_end <= m_startPos || p_start >= m_endPos); + } + + bool operator==(const VElementRegion &p_other) const + { + return (m_startPos == p_other.m_startPos + && m_endPos == p_other.m_endPos); + } + + bool operator<(const VElementRegion &p_other) const + { + if (m_startPos < p_other.m_startPos) { + return true; + } else if (m_startPos == p_other.m_startPos) { + // If a < b is true, then b < a must be false. + return m_endPos < p_other.m_endPos; + } else { + return false; + } + } + + QString toString() const + { + return QString("[%1,%2)").arg(m_startPos).arg(m_endPos); + } +}; + +struct PegHighlightResult +{ + TimeStamp m_timeStamp; + + QVector > m_blockHighlights; +}; + #endif // MARKDOWNHIGHLIGHTERDATA_H diff --git a/src/peghighlighterresult.cpp b/src/peghighlighterresult.cpp new file mode 100644 index 00000000..5936e166 --- /dev/null +++ b/src/peghighlighterresult.cpp @@ -0,0 +1,223 @@ +#include "peghighlighterresult.h" + +#include +#include + +#include "pegmarkdownhighlighter.h" +#include "utils/vutils.h" + +PegHighlighterResult::PegHighlighterResult() + : m_timeStamp(0), + m_numOfBlocks(0), + m_numOfCodeBlockHighlightsToRecv(0) +{ + m_codeBlockStartExp = QRegExp(VUtils::c_fencedCodeBlockStartRegExp); + m_codeBlockEndExp = QRegExp(VUtils::c_fencedCodeBlockEndRegExp); +} + +PegHighlighterResult::PegHighlighterResult(const PegMarkdownHighlighter *p_peg, + const QSharedPointer &p_result) + : m_timeStamp(p_result->m_timeStamp), + m_numOfBlocks(p_result->m_numOfBlocks), + m_numOfCodeBlockHighlightsToRecv(0) +{ + m_codeBlockStartExp = QRegExp(VUtils::c_fencedCodeBlockStartRegExp); + m_codeBlockEndExp = QRegExp(VUtils::c_fencedCodeBlockEndRegExp); + + parseBlocksHighlights(p_peg, p_result); + + // Implicit sharing. + m_imageRegions = p_result->m_imageRegions; + m_headerRegions = p_result->m_headerRegions; + + parseFencedCodeBlocks(p_peg, p_result); +} + +static bool compHLUnit(const HLUnit &p_a, const HLUnit &p_b) +{ + if (p_a.start < p_b.start) { + return true; + } else if (p_a.start == p_b.start) { + return p_a.length > p_b.length; + } else { + return false; + } +} + +void PegHighlighterResult::parseBlocksHighlights(const PegMarkdownHighlighter *p_peg, + const QSharedPointer &p_result) +{ + m_blocksHighlights.resize(m_numOfBlocks); + if (p_result->isEmpty()) { + return; + } + + const QTextDocument *doc = p_peg->getDocument(); + const QVector &styles = p_peg->getStyles(); + auto pmhResult = p_result->m_pmhElements; + for (int i = 0; i < styles.size(); i++) + { + const HighlightingStyle &style = styles[i]; + pmh_element *elem_cursor = pmhResult[style.type]; + while (elem_cursor != NULL) + { + // elem_cursor->pos and elem_cursor->end is the start + // and end position of the element in document. + if (elem_cursor->end <= elem_cursor->pos) { + elem_cursor = elem_cursor->next; + continue; + } + + parseBlocksHighlightOne(doc, elem_cursor->pos, elem_cursor->end, i); + elem_cursor = elem_cursor->next; + } + } + + // Sort m_blocksHighlights. + for (int i = 0; i < m_blocksHighlights.size(); ++i) { + if (m_blocksHighlights[i].size() > 1) { + std::sort(m_blocksHighlights[i].begin(), m_blocksHighlights[i].end(), compHLUnit); + } + } +} + +void PegHighlighterResult::parseBlocksHighlightOne(const QTextDocument *p_doc, + unsigned long p_pos, + unsigned long p_end, + int p_styleIndex) +{ + // When the the highlight element is at the end of document, @p_end will equals + // to the characterCount. + unsigned int nrChar = (unsigned int)p_doc->characterCount(); + if (p_end >= nrChar && nrChar > 0) { + p_end = nrChar - 1; + } + + QTextBlock block = p_doc->findBlock(p_pos); + int startBlockNum = block.blockNumber(); + int endBlockNum = p_doc->findBlock(p_end).blockNumber(); + while (block.isValid()) + { + int blockNum = block.blockNumber(); + if (blockNum > endBlockNum) { + break; + } + + int blockStartPos = block.position(); + HLUnit unit; + if (blockNum == startBlockNum) { + unit.start = p_pos - blockStartPos; + unit.length = (startBlockNum == endBlockNum) ? + (p_end - p_pos) : (block.length() - unit.start); + } else if (blockNum == endBlockNum) { + unit.start = 0; + unit.length = p_end - blockStartPos; + } else { + unit.start = 0; + unit.length = block.length(); + } + unit.styleIndex = p_styleIndex; + + m_blocksHighlights[blockNum].append(unit); + + block = block.next(); + } +} + +void PegHighlighterResult::parseBlocksElementRegionOne(QHash> &p_regs, + const QTextDocument *p_doc, + unsigned long p_pos, + unsigned long p_end) +{ + // When the the highlight element is at the end of document, @p_end will equals + // to the characterCount. + unsigned int nrChar = (unsigned int)p_doc->characterCount(); + if (p_end >= nrChar && nrChar > 0) { + p_end = nrChar - 1; + } + + QTextBlock block = p_doc->findBlock(p_pos); + int startBlockNum = block.blockNumber(); + int endBlockNum = p_doc->findBlock(p_end).blockNumber(); + while (block.isValid()) + { + int blockNum = block.blockNumber(); + if (blockNum > endBlockNum) { + break; + } + + int blockStartPos = block.position(); + QVector ®s = p_regs[blockNum]; + int start, end; + if (blockNum == startBlockNum) { + start = p_pos - blockStartPos; + end = (startBlockNum == endBlockNum) ? (p_end - blockStartPos) + : block.length(); + } else if (blockNum == endBlockNum) { + start = 0; + end = p_end - blockStartPos; + } else { + start = 0; + end = block.length(); + } + + regs.append(VElementRegion(start, end)); + } +} + +void PegHighlighterResult::parseFencedCodeBlocks(const PegMarkdownHighlighter *p_peg, + const QSharedPointer &p_result) +{ + const QMap ®s = p_result->m_codeBlockRegions; + + const QTextDocument *doc = p_peg->getDocument(); + VCodeBlock item; + bool inBlock = false; + for (auto it = regs.begin(); it != regs.end(); ++it) { + QTextBlock block = doc->findBlock(it.value().m_startPos); + int lastBlock = doc->findBlock(it.value().m_endPos - 1).blockNumber(); + + while (block.isValid()) { + int blockNumber = block.blockNumber(); + if (blockNumber > lastBlock) { + break; + } + + HighlightBlockState state = HighlightBlockState::Normal; + QString text = block.text(); + if (inBlock) { + item.m_text = item.m_text + "\n" + text; + int idx = m_codeBlockEndExp.indexIn(text); + if (idx >= 0) { + // End block. + inBlock = false; + state = HighlightBlockState::CodeBlockEnd; + item.m_endBlock = blockNumber; + m_codeBlocks.append(item); + } else { + // Within code block. + state = HighlightBlockState::CodeBlock; + } + } else { + int idx = m_codeBlockStartExp.indexIn(text); + if (idx >= 0) { + // Start block. + inBlock = true; + state = HighlightBlockState::CodeBlockStart; + item.m_startBlock = blockNumber; + item.m_startPos = block.position(); + item.m_text = text; + if (m_codeBlockStartExp.captureCount() == 2) { + item.m_lang = m_codeBlockStartExp.capturedTexts()[2]; + } + } + } + + if (state != HighlightBlockState::Normal) { + m_codeBlocksState.insert(blockNumber, state); + } + + block = block.next(); + } + } +} diff --git a/src/peghighlighterresult.h b/src/peghighlighterresult.h new file mode 100644 index 00000000..8c471b47 --- /dev/null +++ b/src/peghighlighterresult.h @@ -0,0 +1,75 @@ +#ifndef PEGHIGHLIGHTERRESULT_H +#define PEGHIGHLIGHTERRESULT_H + +#include "vconstants.h" +#include "pegparser.h" + +class PegMarkdownHighlighter; +class QTextDocument; + +class PegHighlighterResult +{ +public: + PegHighlighterResult(); + + PegHighlighterResult(const PegMarkdownHighlighter *p_peg, + const QSharedPointer &p_result); + + bool matched(TimeStamp p_timeStamp) const; + + TimeStamp m_timeStamp; + + int m_numOfBlocks; + + QVector> m_blocksHighlights; + + // Use another member to store the codeblocks highlights, because the highlight + // sequence is blockHighlights, regular-expression-based highlihgts, and then + // codeBlockHighlights. + // Support fenced code block only. + QVector > m_codeBlocksHighlights; + + // All image link regions. + QVector m_imageRegions; + + // All header regions. + // Sorted by start position. + QVector m_headerRegions; + + // All fenced code blocks. + QVector m_codeBlocks; + + // Indexed by block number. + QHash m_codeBlocksState; + + int m_numOfCodeBlockHighlightsToRecv; + +private: + // Parse highlight elements for all the blocks from parse results. + void parseBlocksHighlights(const PegMarkdownHighlighter *p_peg, + const QSharedPointer &p_result); + + // Parse highlight elements for blocks from one parse result. + void parseBlocksHighlightOne(const QTextDocument *p_doc, + unsigned long p_pos, + unsigned long p_end, + int p_styleIndex); + + // Parse fenced code blocks from parse results. + void parseFencedCodeBlocks(const PegMarkdownHighlighter *p_peg, + const QSharedPointer &p_result); + + void parseBlocksElementRegionOne(QHash> &p_regs, + const QTextDocument *p_doc, + unsigned long p_pos, + unsigned long p_end); + + QRegExp m_codeBlockStartExp; + QRegExp m_codeBlockEndExp; +}; + +inline bool PegHighlighterResult::matched(TimeStamp p_timeStamp) const +{ + return m_timeStamp == p_timeStamp; +} +#endif // PEGHIGHLIGHTERRESULT_H diff --git a/src/pegmarkdownhighlighter.cpp b/src/pegmarkdownhighlighter.cpp new file mode 100644 index 00000000..d5927abd --- /dev/null +++ b/src/pegmarkdownhighlighter.cpp @@ -0,0 +1,409 @@ +#include "pegmarkdownhighlighter.h" + +#include +#include +#include + +#include "pegparser.h" +#include "vconfigmanager.h" +#include "utils/vutils.h" + +extern VConfigManager *g_config; + +PegMarkdownHighlighter::PegMarkdownHighlighter(QTextDocument *p_doc) + : QSyntaxHighlighter(p_doc), + m_doc(p_doc), + m_timeStamp(0), + m_parser(NULL), + m_parserExts(pmh_EXT_STRIKE | pmh_EXT_FRONTMATTER) +{ +} + +void PegMarkdownHighlighter::init(const QVector &p_styles, + const QHash &p_codeBlockStyles, + bool p_mathjaxEnabled, + int p_timerInterval) +{ + m_styles = p_styles; + m_codeBlockStyles = p_codeBlockStyles; + + m_codeBlockFormat.setForeground(QBrush(Qt::darkYellow)); + for (int index = 0; index < m_styles.size(); ++index) { + switch (m_styles[index].type) { + case pmh_FENCEDCODEBLOCK: + m_codeBlockFormat = m_styles[index].format; + break; + + default: + break; + } + } + + m_colorColumnFormat = m_codeBlockFormat; + m_colorColumnFormat.setForeground(QColor(g_config->getEditorColorColumnFg())); + m_colorColumnFormat.setBackground(QColor(g_config->getEditorColorColumnBg())); + + m_result.reset(new PegHighlighterResult()); + + m_parser = new PegParser(this); + connect(m_parser, &PegParser::parseResultReady, + this, &PegMarkdownHighlighter::handleParseResult); + + m_timer = new QTimer(this); + m_timer->setSingleShot(true); + m_timer->setInterval(p_timerInterval); + connect(m_timer, &QTimer::timeout, + this, [this]() { + startParse(); + }); + + connect(m_doc, &QTextDocument::contentsChange, + this, &PegMarkdownHighlighter::handleContentsChange); +} + +void PegMarkdownHighlighter::highlightBlock(const QString &p_text) +{ + QSharedPointer result(m_result); + + int blockNum = currentBlock().blockNumber(); + if (result->m_blocksHighlights.size() > blockNum) { + // units are sorted by start position and length. + const QVector &units = result->m_blocksHighlights[blockNum]; + if (!units.isEmpty()) { + for (int i = 0; i < units.size(); ++i) { + const HLUnit &unit = units[i]; + if (i == 0) { + // No need to merge format. + setFormat(unit.start, + unit.length, + m_styles[unit.styleIndex].format); + } else { + QTextCharFormat newFormat = m_styles[unit.styleIndex].format; + for (int j = i - 1; j >= 0; --j) { + if (units[j].start + units[j].length <= unit.start) { + // It won't affect current unit. + continue; + } else { + // Merge the format. + QTextCharFormat tmpFormat(newFormat); + newFormat = m_styles[units[j].styleIndex].format; + // tmpFormat takes precedence. + newFormat.merge(tmpFormat); + } + } + + setFormat(unit.start, unit.length, newFormat); + } + } + } + } + + // Set current block's user data. + updateBlockUserData(blockNum, p_text); + + setCurrentBlockState(HighlightBlockState::Normal); + + updateCodeBlockState(result, blockNum, p_text); + + if (currentBlockState() == HighlightBlockState::CodeBlock) { + highlightCodeBlock(result, blockNum); + + highlightCodeBlockColorColumn(p_text); + } +} + +void PegMarkdownHighlighter::handleContentsChange(int p_position, int p_charsRemoved, int p_charsAdded) +{ + Q_UNUSED(p_position); + + if (p_charsRemoved == 0 && p_charsAdded == 0) { + return; + } + + ++m_timeStamp; + + // We still need a timer to start a complete parse. + m_timer->start(); +} + +void PegMarkdownHighlighter::startParse() +{ + QSharedPointer config(new PegParseConfig()); + config->m_timeStamp = m_timeStamp; + config->m_data = m_doc->toPlainText().toUtf8(); + config->m_numOfBlocks = m_doc->blockCount(); + config->m_extensions = m_parserExts; + + m_parser->parseAsync(config); +} + +static bool compHLUnitStyle(const HLUnitStyle &a, const HLUnitStyle &b) +{ + if (a.start < b.start) { + return true; + } else if (a.start == b.start) { + return a.length > b.length; + } else { + return false; + } +} + +void PegMarkdownHighlighter::setCodeBlockHighlights(TimeStamp p_timeStamp, + const QVector &p_units) +{ + QSharedPointer result(m_result); + + if (!result->matched(p_timeStamp)) { + return; + } + + if (p_units.isEmpty()) { + goto exit; + } + + { + QVector> highlights(result->m_codeBlocksHighlights.size()); + for (auto const &unit : p_units) { + int pos = unit.m_position; + int end = unit.m_position + unit.m_length; + QTextBlock block = m_doc->findBlock(pos); + int startBlockNum = block.blockNumber(); + int endBlockNum = m_doc->findBlock(end).blockNumber(); + + // Text has been changed. Abandon the obsolete parsed result. + if (startBlockNum == -1 || endBlockNum >= highlights.size()) { + goto exit; + } + + while (block.isValid()) { + int blockNumber = block.blockNumber(); + if (blockNumber > endBlockNum) { + break; + } + + int blockStartPos = block.position(); + HLUnitStyle hl; + hl.style = unit.m_style; + if (blockNumber == startBlockNum) { + hl.start = pos - blockStartPos; + hl.length = (startBlockNum == endBlockNum) ? + (end - pos) : (block.length() - hl.start); + } else if (blockNumber == endBlockNum) { + hl.start = 0; + hl.length = end - blockStartPos; + } else { + hl.start = 0; + hl.length = block.length(); + } + + highlights[blockNumber].append(hl); + + block = block.next(); + } + } + + // Need to highlight in order. + for (int i = 0; i < highlights.size(); ++i) { + QVector &units = highlights[i]; + if (!units.isEmpty()) { + if (units.size() > 1) { + std::sort(units.begin(), units.end(), compHLUnitStyle); + } + + result->m_codeBlocksHighlights[i].append(units); + } + } + } + +exit: + if (--result->m_numOfCodeBlockHighlightsToRecv <= 0) { + rehighlight(); + } +} + +void PegMarkdownHighlighter::updateHighlightFast() +{ + updateHighlight(); +} + +void PegMarkdownHighlighter::updateHighlight() +{ + m_timer->stop(); + startParse(); +} + +void PegMarkdownHighlighter::handleParseResult(const QSharedPointer &p_result) +{ + if (!m_result.isNull() && m_result->m_timeStamp > p_result->m_timeStamp) { + return; + } + + PegHighlighterResult *pegRes = new PegHighlighterResult(this, p_result); + m_result.reset(pegRes); + + updateCodeBlocks(m_result); + + // Now we got a new result, rehighlight. + rehighlight(); + + completeHighlight(m_result); +} + +void PegMarkdownHighlighter::updateCodeBlocks(QSharedPointer p_result) +{ + if (!p_result->matched(m_timeStamp)) { + return; + } + + if (g_config->getEnableCodeBlockHighlight()) { + p_result->m_codeBlocksHighlights.resize(p_result->m_numOfBlocks); + p_result->m_numOfCodeBlockHighlightsToRecv = p_result->m_codeBlocks.size(); + } + + emit codeBlocksUpdated(p_result->m_timeStamp, p_result->m_codeBlocks); +} + +void PegMarkdownHighlighter::updateBlockUserData(int p_blockNum, const QString &p_text) +{ + Q_UNUSED(p_text); + VTextBlockData *blockData = currentBlockData(); + if (!blockData) { + blockData = new VTextBlockData(); + setCurrentBlockUserData(blockData); + } else { + blockData->setCodeBlockIndentation(-1); + blockData->clearMathjax(); + } + + if (blockData->getPreviews().isEmpty()) { + m_possiblePreviewBlocks.remove(p_blockNum); + } else { + m_possiblePreviewBlocks.insert(p_blockNum); + } +} + +void PegMarkdownHighlighter::updateCodeBlockState(const QSharedPointer &p_result, + int p_blockNum, + const QString &p_text) +{ + if (!p_result->matched(m_timeStamp)) { + return; + } + + auto it = p_result->m_codeBlocksState.find(p_blockNum); + if (it != p_result->m_codeBlocksState.end()) { + VTextBlockData *blockData = currentBlockData(); + Q_ASSERT(blockData); + + HighlightBlockState state = it.value(); + // Set code block indentation. + switch (state) { + case HighlightBlockState::CodeBlockStart: + { + int startLeadingSpaces = 0; + QRegExp reg(VUtils::c_fencedCodeBlockStartRegExp); + int idx = reg.indexIn(p_text); + if (idx >= 0) { + startLeadingSpaces = reg.capturedTexts()[1].size(); + } + + blockData->setCodeBlockIndentation(startLeadingSpaces); + break; + } + + case HighlightBlockState::CodeBlock: + V_FALLTHROUGH; + case HighlightBlockState::CodeBlockEnd: + { + int startLeadingSpaces = 0; + VTextBlockData *preBlockData = previousBlockData(); + if (preBlockData) { + startLeadingSpaces = preBlockData->getCodeBlockIndentation(); + } + + blockData->setCodeBlockIndentation(startLeadingSpaces); + break; + } + + default: + Q_ASSERT(false); + break; + } + + // Set code block state. + setCurrentBlockState(state); + } +} + +void PegMarkdownHighlighter::highlightCodeBlock(const QSharedPointer &p_result, + int p_blockNum) +{ + if (p_result->m_codeBlocksHighlights.size() > p_blockNum) { + const QVector &units = p_result->m_codeBlocksHighlights[p_blockNum]; + if (!units.isEmpty()) { + QVector formats(units.size(), NULL); + for (int i = 0; i < units.size(); ++i) { + const HLUnitStyle &unit = units[i]; + auto it = m_codeBlockStyles.find(unit.style); + if (it == m_codeBlockStyles.end()) { + continue; + } + + formats[i] = &(*it); + + QTextCharFormat newFormat = m_codeBlockFormat; + newFormat.merge(*it); + for (int j = i - 1; j >= 0; --j) { + if (units[j].start + units[j].length <= unit.start) { + // It won't affect current unit. + continue; + } else { + // Merge the format. + if (formats[j]) { + QTextCharFormat tmpFormat(newFormat); + newFormat = *(formats[j]); + // tmpFormat takes precedence. + newFormat.merge(tmpFormat); + } + } + } + + setFormat(unit.start, unit.length, newFormat); + } + } + } +} + +void PegMarkdownHighlighter::highlightCodeBlockColorColumn(const QString &p_text) +{ + int cc = g_config->getColorColumn(); + if (cc <= 0) { + return; + } + + VTextBlockData *blockData = currentBlockData(); + Q_ASSERT(blockData); + int indent = blockData->getCodeBlockIndentation(); + if (indent == -1) { + return; + } + + cc += indent; + if (p_text.size() < cc) { + return; + } + + setFormat(cc - 1, 1, m_colorColumnFormat); +} + +void PegMarkdownHighlighter::completeHighlight(QSharedPointer p_result) +{ + if (!p_result->matched(m_timeStamp)) { + return; + } + + emit imageLinksUpdated(p_result->m_imageRegions); + emit headersUpdated(p_result->m_headerRegions); + + emit highlightCompleted(); +} diff --git a/src/pegmarkdownhighlighter.h b/src/pegmarkdownhighlighter.h new file mode 100644 index 00000000..31f1a747 --- /dev/null +++ b/src/pegmarkdownhighlighter.h @@ -0,0 +1,179 @@ +#ifndef PEGMARKDOWNHIGHLIGHTER_H +#define PEGMARKDOWNHIGHLIGHTER_H + +#include +#include + +#include "vtextblockdata.h" +#include "markdownhighlighterdata.h" +#include "peghighlighterresult.h" + +class PegParser; +class QTimer; + +class PegMarkdownHighlighter : public QSyntaxHighlighter +{ + Q_OBJECT +public: + explicit PegMarkdownHighlighter(QTextDocument *p_doc = nullptr); + + void init(const QVector &p_styles, + const QHash &p_codeBlockStyles, + bool p_mathjaxEnabled, + int p_timerInterval); + + // Set code block highlight result by VCodeBlockHighlightHelper. + void setCodeBlockHighlights(TimeStamp p_timeStamp, const QVector &p_units); + + const QVector &getHeaderRegions() const; + + const QSet &getPossiblePreviewBlocks() const; + + void clearPossiblePreviewBlocks(const QVector &p_blocksToClear); + + void addPossiblePreviewBlock(int p_blockNumber); + + // Parse and only update the highlight results for rehighlight(). + void updateHighlightFast(); + + QHash &getCodeBlockStyles(); + + QVector &getStyles(); + + const QVector &getStyles() const; + + const QTextDocument *getDocument() const; + +public slots: + // Parse and rehighlight immediately. + void updateHighlight(); + +signals: + void highlightCompleted(); + + // QVector is implicitly shared. + void codeBlocksUpdated(TimeStamp p_timeStamp, const QVector &p_codeBlocks); + + // Emitted when image regions have been fetched from a new parsing result. + void imageLinksUpdated(const QVector &p_imageRegions); + + // Emitted when header regions have been fetched from a new parsing result. + void headersUpdated(const QVector &p_headerRegions); + + // Emitted when Mathjax blocks updated. + void mathjaxBlocksUpdated(const QVector &p_mathjaxBlocks); + +protected: + void highlightBlock(const QString &p_text) Q_DECL_OVERRIDE; + +private slots: + void handleContentsChange(int p_position, int p_charsRemoved, int p_charsAdded); + + void handleParseResult(const QSharedPointer &p_result); + +private: + void startParse(); + + void updateCodeBlocks(QSharedPointer p_result); + + // Set the user data of currentBlock(). + void updateBlockUserData(int p_blockNum, const QString &p_text); + + void updateCodeBlockState(const QSharedPointer &p_result, + int p_blockNum, + const QString &p_text); + + // Highlight fenced code block according to VCodeBlockHighlightHelper result. + void highlightCodeBlock(const QSharedPointer &p_result, + int p_blockNum); + + // Highlight color column in code block. + void highlightCodeBlockColorColumn(const QString &p_text); + + VTextBlockData *currentBlockData() const; + + VTextBlockData *previousBlockData() const; + + void completeHighlight(QSharedPointer p_result); + + QTextDocument *m_doc; + + TimeStamp m_timeStamp; + + QVector m_styles; + QHash m_codeBlockStyles; + + QTextCharFormat m_codeBlockFormat; + QTextCharFormat m_colorColumnFormat; + + PegParser *m_parser; + + QSharedPointer m_result; + + // Block number of those blocks which possible contains previewed image. + QSet m_possiblePreviewBlocks; + + // Extensions for parser. + int m_parserExts; + + // Timer to trigger parse. + QTimer *m_timer; +}; + +inline const QVector &PegMarkdownHighlighter::getHeaderRegions() const +{ + return m_result->m_headerRegions; +} + +inline const QSet &PegMarkdownHighlighter::getPossiblePreviewBlocks() const +{ + return m_possiblePreviewBlocks; +} + +inline void PegMarkdownHighlighter::clearPossiblePreviewBlocks(const QVector &p_blocksToClear) +{ + for (auto i : p_blocksToClear) { + m_possiblePreviewBlocks.remove(i); + } +} + +inline void PegMarkdownHighlighter::addPossiblePreviewBlock(int p_blockNumber) +{ + m_possiblePreviewBlocks.insert(p_blockNumber); +} + +inline QHash &PegMarkdownHighlighter::getCodeBlockStyles() +{ + return m_codeBlockStyles; +} + +inline QVector &PegMarkdownHighlighter::getStyles() +{ + return m_styles; +} + +inline const QVector &PegMarkdownHighlighter::getStyles() const +{ + return m_styles; +} + +inline const QTextDocument *PegMarkdownHighlighter::getDocument() const +{ + return m_doc; +} + +inline VTextBlockData *PegMarkdownHighlighter::currentBlockData() const +{ + return static_cast(currentBlockUserData()); +} + +inline VTextBlockData *PegMarkdownHighlighter::previousBlockData() const +{ + QTextBlock block = currentBlock().previous(); + if (!block.isValid()) { + return NULL; + } + + return static_cast(block.userData()); +} +#endif // PEGMARKDOWNHIGHLIGHTER_H diff --git a/src/pegparser.cpp b/src/pegparser.cpp new file mode 100644 index 00000000..7784aef4 --- /dev/null +++ b/src/pegparser.cpp @@ -0,0 +1,276 @@ +#include "pegparser.h" + +enum WorkerState +{ + Idle, + Busy, + Cancelled, + Finished +}; + +void PegParseResult::parse(QAtomicInt &p_stop) +{ + parseImageRegions(p_stop); + + parseHeaderRegions(p_stop); + + parseFencedCodeBlockRegions(p_stop); +} + +void PegParseResult::parseImageRegions(QAtomicInt &p_stop) +{ + // From Qt5.7, the capacity is preserved. + m_imageRegions.clear(); + if (isEmpty()) { + return; + } + + pmh_element *elem = m_pmhElements[pmh_IMAGE]; + while (elem != NULL) { + if (elem->end <= elem->pos) { + elem = elem->next; + continue; + } + + if (p_stop.load() == 1) { + return; + } + + m_imageRegions.push_back(VElementRegion(elem->pos, elem->end)); + elem = elem->next; + } +} + +void PegParseResult::parseHeaderRegions(QAtomicInt &p_stop) +{ + // From Qt5.7, the capacity is preserved. + m_headerRegions.clear(); + if (isEmpty()) { + return; + } + + pmh_element_type hx[6] = {pmh_H1, pmh_H2, pmh_H3, pmh_H4, pmh_H5, pmh_H6}; + for (int i = 0; i < 6; ++i) { + pmh_element *elem = m_pmhElements[hx[i]]; + while (elem != NULL) { + if (elem->end <= elem->pos) { + elem = elem->next; + continue; + } + + if (p_stop.load() == 1) { + return; + } + + m_headerRegions.push_back(VElementRegion(elem->pos, elem->end)); + elem = elem->next; + } + } + + if (p_stop.load() == 1) { + return; + } + + std::sort(m_headerRegions.begin(), m_headerRegions.end()); +} + +void PegParseResult::parseFencedCodeBlockRegions(QAtomicInt &p_stop) +{ + m_codeBlockRegions.clear(); + if (isEmpty()) { + return; + } + + pmh_element *elem = m_pmhElements[pmh_FENCEDCODEBLOCK]; + while (elem != NULL) { + if (elem->end <= elem->pos) { + elem = elem->next; + continue; + } + + if (p_stop.load() == 1) { + return; + } + + if (!m_codeBlockRegions.contains(elem->pos)) { + m_codeBlockRegions.insert(elem->pos, VElementRegion(elem->pos, elem->end)); + } + + elem = elem->next; + } +} + + +PegParserWorker::PegParserWorker(QObject *p_parent) + : QThread(p_parent), + m_stop(0), + m_state(WorkerState::Idle) +{ +} + +void PegParserWorker::prepareParse(const QSharedPointer &p_config) +{ + Q_ASSERT(m_parseConfig.isNull()); + + m_state = WorkerState::Busy; + m_parseConfig = p_config; +} + +void PegParserWorker::reset() +{ + m_parseConfig.reset(); + m_parseResult.reset(); + m_stop.store(0); + m_state = WorkerState::Idle; +} + +void PegParserWorker::stop() +{ + m_stop.store(1); +} + +void PegParserWorker::run() +{ + Q_ASSERT(m_state == WorkerState::Busy); + + m_parseResult = parseMarkdown(m_parseConfig, m_stop); + + if (isAskedToStop()) { + m_state = WorkerState::Cancelled; + return; + } + + m_state = WorkerState::Finished; +} + +QSharedPointer PegParserWorker::parseMarkdown(const QSharedPointer &p_config, + QAtomicInt &p_stop) +{ + QSharedPointer result(new PegParseResult(p_config)); + + if (p_config->m_data.isEmpty()) { + return result; + } + + pmh_element **pmhResult = NULL; + + char *data = p_config->m_data.data(); + + pmh_markdown_to_elements(data, p_config->m_extensions, &pmhResult); + + result->m_pmhElements = pmhResult; + + if (p_stop.load() == 1) { + return result; + } + + result->parse(p_stop); + + return result; +} + + +#define NUM_OF_THREADS 2 + +PegParser::PegParser(QObject *p_parent) + : QObject(p_parent) +{ + init(); +} + +void PegParser::parseAsync(const QSharedPointer &p_config) +{ + m_pendingWork = p_config; + + pickWorker(); +} + +void PegParser::init() +{ + for (int i = 0; i < NUM_OF_THREADS; ++i) { + PegParserWorker *th = new PegParserWorker(this); + connect(th, &PegParserWorker::finished, + this, [this, th]() { + handleWorkerFinished(th); + }); + + m_workers.append(th); + } +} + +void PegParser::clear() +{ + m_pendingWork.reset(); + + for (auto const & th : m_workers) { + th->quit(); + th->wait(); + + delete th; + } + + m_workers.clear(); +} + +PegParser::~PegParser() +{ + clear(); +} + +void PegParser::handleWorkerFinished(PegParserWorker *p_worker) +{ + QSharedPointer result; + if (p_worker->state() == WorkerState::Finished) { + result = p_worker->parseResult(); + } + + p_worker->reset(); + + pickWorker(); + + if (!result.isNull()) { + emit parseResultReady(result); + } +} + +void PegParser::pickWorker() +{ + if (m_pendingWork.isNull()) { + return; + } + + bool allBusy = true; + for (auto th : m_workers) { + if (th->state() == WorkerState::Idle) { + scheduleWork(th, m_pendingWork); + m_pendingWork.reset(); + return; + } else if (th->state() != WorkerState::Busy) { + allBusy = false; + } + } + + if (allBusy) { + // Need to stop the worker with non-minimal timestamp. + int idx = 0; + TimeStamp minTS = m_workers[idx]->workTimeStamp(); + + if (m_workers.size() > 1) { + if (m_workers[1]->workTimeStamp() > minTS) { + idx = 1; + } + } + + m_workers[idx]->stop(); + } +} + +void PegParser::scheduleWork(PegParserWorker *p_worker, + const QSharedPointer &p_config) +{ + Q_ASSERT(p_worker->state() == WorkerState::Idle); + + p_worker->reset(); + p_worker->prepareParse(p_config); + p_worker->start(); +} diff --git a/src/pegparser.h b/src/pegparser.h new file mode 100644 index 00000000..40f308d5 --- /dev/null +++ b/src/pegparser.h @@ -0,0 +1,186 @@ +#ifndef PEGPARSER_H +#define PEGPARSER_H + +#include + +#include +#include +#include +#include + +#include "vconstants.h" +#include "markdownhighlighterdata.h" + +struct PegParseConfig +{ + TimeStamp m_timeStamp; + + QByteArray m_data; + + int m_numOfBlocks; + + int m_extensions; + + QString toString() const + { + return QString("PegParseConfig ts %1 data %2 blocks %3").arg(m_timeStamp) + .arg(m_data.size()) + .arg(m_numOfBlocks); + } +}; + +struct PegParseResult +{ + PegParseResult(const QSharedPointer &p_config) + : m_timeStamp(p_config->m_timeStamp), + m_numOfBlocks(p_config->m_numOfBlocks), + m_pmhElements(NULL) + { + } + + ~PegParseResult() + { + clearPmhElements(); + } + + void clearPmhElements() + { + if (m_pmhElements) { + pmh_free_elements(m_pmhElements); + m_pmhElements = NULL; + } + } + + bool operator<(const PegParseResult &p_other) const + { + return m_timeStamp < p_other.m_timeStamp; + } + + QString toString() const + { + return QString("PegParseResult ts %1").arg(m_timeStamp); + } + + bool isEmpty() const + { + return !m_pmhElements; + } + + // Parse m_pmhElements. + void parse(QAtomicInt &p_stop); + + TimeStamp m_timeStamp; + + int m_numOfBlocks; + + pmh_element **m_pmhElements; + + // All image link regions. + QVector m_imageRegions; + + // All header regions. + // Sorted by start position. + QVector m_headerRegions; + + // Fenced code block regions. + // Ordered by start position in ascending order. + QMap m_codeBlockRegions; + +private: + void parseImageRegions(QAtomicInt &p_stop); + + void parseHeaderRegions(QAtomicInt &p_stop); + + void parseFencedCodeBlockRegions(QAtomicInt &p_stop); +}; + +class PegParserWorker : public QThread +{ + Q_OBJECT +public: + explicit PegParserWorker(QObject *p_parent = nullptr); + + void prepareParse(const QSharedPointer &p_config); + + void reset(); + + int state() const + { + return m_state; + } + + TimeStamp workTimeStamp() const + { + if (m_parseConfig.isNull()) { + return 0; + } + + return m_parseConfig->m_timeStamp; + } + + const QSharedPointer &parseConfig() const + { + return m_parseConfig; + } + + const QSharedPointer &parseResult() const + { + return m_parseResult; + } + +public slots: + void stop(); + +protected: + void run() Q_DECL_OVERRIDE; + +private: + QSharedPointer parseMarkdown(const QSharedPointer &p_config, + QAtomicInt &p_stop); + + bool isAskedToStop() const + { + return m_stop.load() == 1; + } + + QAtomicInt m_stop; + + int m_state; + + QSharedPointer m_parseConfig; + + QSharedPointer m_parseResult; +}; + +class PegParser : public QObject +{ + Q_OBJECT +public: + explicit PegParser(QObject *p_parent = nullptr); + + ~PegParser(); + + void parseAsync(const QSharedPointer &p_config); + +signals: + void parseResultReady(const QSharedPointer &p_result); + +private slots: + void handleWorkerFinished(PegParserWorker *p_worker); + +private: + void init(); + + void clear(); + + void pickWorker(); + + void scheduleWork(PegParserWorker *p_worker, const QSharedPointer &p_config); + + // Maintain a fixed number of workers to pick work. + QVector m_workers; + + QSharedPointer m_pendingWork; +}; + +#endif // PEGPARSER_H diff --git a/src/resources/vnote.ini b/src/resources/vnote.ini index 54c6576a..9b124100 100644 --- a/src/resources/vnote.ini +++ b/src/resources/vnote.ini @@ -106,7 +106,7 @@ minimize_to_system_tray=-1 markdown_suffix=md,markdown,mkd ; Markdown highlight timer interval (milliseconds) -markdown_highlight_interval=400 +markdown_highlight_interval=200 ; Adds specified height between lines (in pixels) line_distance_height=3 diff --git a/src/src.pro b/src/src.pro index c2e8159e..8b11b4f6 100644 --- a/src/src.pro +++ b/src/src.pro @@ -140,7 +140,10 @@ SOURCES += main.cpp\ vtagpanel.cpp \ valltagspanel.cpp \ vtaglabel.cpp \ - vtagexplorer.cpp + vtagexplorer.cpp \ + pegmarkdownhighlighter.cpp \ + pegparser.cpp \ + peghighlighterresult.cpp HEADERS += vmainwindow.h \ vdirectorytree.h \ @@ -274,7 +277,11 @@ HEADERS += vmainwindow.h \ vtagpanel.h \ valltagspanel.h \ vtaglabel.h \ - vtagexplorer.h + vtagexplorer.h \ + markdownhighlighterdata.h \ + pegmarkdownhighlighter.h \ + pegparser.h \ + peghighlighterresult.h RESOURCES += \ vnote.qrc \ diff --git a/src/vcodeblockhighlighthelper.cpp b/src/vcodeblockhighlighthelper.cpp index c6551579..c88b69b7 100644 --- a/src/vcodeblockhighlighthelper.cpp +++ b/src/vcodeblockhighlighthelper.cpp @@ -2,10 +2,12 @@ #include #include + #include "vdocument.h" #include "utils/vutils.h" +#include "pegmarkdownhighlighter.h" -VCodeBlockHighlightHelper::VCodeBlockHighlightHelper(HGMarkdownHighlighter *p_highlighter, +VCodeBlockHighlightHelper::VCodeBlockHighlightHelper(PegMarkdownHighlighter *p_highlighter, VDocument *p_vdoc, MarkdownConverterType p_type) : QObject(p_highlighter), @@ -14,14 +16,14 @@ VCodeBlockHighlightHelper::VCodeBlockHighlightHelper(HGMarkdownHighlighter *p_hi m_type(p_type), m_timeStamp(0) { - connect(m_highlighter, &HGMarkdownHighlighter::codeBlocksUpdated, + connect(m_highlighter, &PegMarkdownHighlighter::codeBlocksUpdated, this, &VCodeBlockHighlightHelper::handleCodeBlocksUpdated); connect(m_vdocument, &VDocument::textHighlighted, this, &VCodeBlockHighlightHelper::handleTextHighlightResult); // Web side is ready for code block highlight. connect(m_vdocument, &VDocument::readyToHighlightText, - m_highlighter, &HGMarkdownHighlighter::updateHighlight); + m_highlighter, &PegMarkdownHighlighter::updateHighlight); } QString VCodeBlockHighlightHelper::unindentCodeBlock(const QString &p_text) @@ -57,44 +59,45 @@ QString VCodeBlockHighlightHelper::unindentCodeBlock(const QString &p_text) return res; } -void VCodeBlockHighlightHelper::handleCodeBlocksUpdated(const QVector &p_codeBlocks) +void VCodeBlockHighlightHelper::handleCodeBlocksUpdated(TimeStamp p_timeStamp, + const QVector &p_codeBlocks) { if (!m_vdocument->isReadyToHighlight()) { // Immediately return empty results. QVector emptyRes; for (int i = 0; i < p_codeBlocks.size(); ++i) { - updateHighlightResults(0, emptyRes); + updateHighlightResults(p_timeStamp, 0, emptyRes); } return; } - int curStamp = m_timeStamp.fetchAndAddRelaxed(1) + 1; + m_timeStamp = p_timeStamp; m_codeBlocks = p_codeBlocks; for (int i = 0; i < m_codeBlocks.size(); ++i) { const VCodeBlock &block = m_codeBlocks[i]; auto it = m_cache.find(block.m_text); if (it != m_cache.end()) { // Hit cache. - qDebug() << "code block highlight hit cache" << curStamp << i; - it.value().m_timeStamp = curStamp; - updateHighlightResults(block.m_startPos, it.value().m_units); + qDebug() << "code block highlight hit cache" << p_timeStamp << i; + it.value().m_timeStamp = p_timeStamp; + updateHighlightResults(p_timeStamp, block.m_startPos, it.value().m_units); } else { QString unindentedText = unindentCodeBlock(block.m_text); - m_vdocument->highlightTextAsync(unindentedText, i, curStamp); + m_vdocument->highlightTextAsync(unindentedText, i, p_timeStamp); } } } void VCodeBlockHighlightHelper::handleTextHighlightResult(const QString &p_html, int p_id, - int p_timeStamp) + unsigned long long p_timeStamp) { - int curStamp = m_timeStamp.load(); // Abandon obsolete result. - if (curStamp != p_timeStamp) { + if (m_timeStamp != p_timeStamp) { return; } + parseHighlightResult(p_timeStamp, p_id, p_html); } @@ -137,7 +140,7 @@ static void matchTokenRelaxed(const QString &p_text, const QString &p_tokenStr, } // For now, we could only handle code blocks outside the list. -void VCodeBlockHighlightHelper::parseHighlightResult(int p_timeStamp, +void VCodeBlockHighlightHelper::parseHighlightResult(TimeStamp p_timeStamp, int p_idx, const QString &p_html) { @@ -209,9 +212,8 @@ void VCodeBlockHighlightHelper::parseHighlightResult(int p_timeStamp, exit: // Pass result back to highlighter. - int curStamp = m_timeStamp.load(); // Abandon obsolete result. - if (curStamp != p_timeStamp) { + if (m_timeStamp != p_timeStamp) { return; } @@ -224,10 +226,11 @@ exit: // Add it to cache. addToHighlightCache(text, p_timeStamp, hlUnits); - updateHighlightResults(startPos, hlUnits); + updateHighlightResults(p_timeStamp, startPos, hlUnits); } -void VCodeBlockHighlightHelper::updateHighlightResults(int p_startPos, +void VCodeBlockHighlightHelper::updateHighlightResults(TimeStamp p_timeStamp, + int p_startPos, QVector p_units) { for (int i = 0; i < p_units.size(); ++i) { @@ -235,7 +238,7 @@ void VCodeBlockHighlightHelper::updateHighlightResults(int p_startPos, } // We need to call this function anyway to trigger the rehighlight. - m_highlighter->setCodeBlockHighlights(p_units); + m_highlighter->setCodeBlockHighlights(p_timeStamp, p_units); } bool VCodeBlockHighlightHelper::parseSpanElement(QXmlStreamReader &p_xml, @@ -283,7 +286,7 @@ bool VCodeBlockHighlightHelper::parseSpanElement(QXmlStreamReader &p_xml, } void VCodeBlockHighlightHelper::addToHighlightCache(const QString &p_text, - int p_timeStamp, + TimeStamp p_timeStamp, const QVector &p_units) { const int c_maxEntries = 100; diff --git a/src/vcodeblockhighlighthelper.h b/src/vcodeblockhighlighthelper.h index a5147a08..cba7c72c 100644 --- a/src/vcodeblockhighlighthelper.h +++ b/src/vcodeblockhighlighthelper.h @@ -6,15 +6,17 @@ #include #include #include + #include "vconfigmanager.h" class VDocument; +class PegMarkdownHighlighter; class VCodeBlockHighlightHelper : public QObject { Q_OBJECT public: - VCodeBlockHighlightHelper(HGMarkdownHighlighter *p_highlighter, + VCodeBlockHighlightHelper(PegMarkdownHighlighter *p_highlighter, VDocument *p_vdoc, MarkdownConverterType p_type); // @p_text: text of fenced code block. @@ -25,27 +27,30 @@ public: static QString unindentCodeBlock(const QString &p_text); private slots: - void handleCodeBlocksUpdated(const QVector &p_codeBlocks); + void handleCodeBlocksUpdated(TimeStamp p_timeStamp, const QVector &p_codeBlocks); - void handleTextHighlightResult(const QString &p_html, int p_id, int p_timeStamp); + void handleTextHighlightResult(const QString &p_html, int p_id, unsigned long long p_timeStamp); private: struct HLResult { - HLResult() : m_timeStamp(-1) + HLResult() + : m_timeStamp(0) { } - HLResult(int p_timeStamp, const QVector &p_units) - : m_timeStamp(p_timeStamp), m_units(p_units) + HLResult(TimeStamp p_timeStamp, const QVector &p_units) + : m_timeStamp(p_timeStamp), + m_units(p_units) { } - int m_timeStamp; + TimeStamp m_timeStamp; + QVector m_units; }; - void parseHighlightResult(int p_timeStamp, int p_idx, const QString &p_html); + void parseHighlightResult(TimeStamp p_timeStamp, int p_idx, const QString &p_html); // @p_text: the raw text of the code block; // @p_index: the start index of the span element within @p_text; @@ -54,16 +59,18 @@ private: const QString &p_text, int &p_index, QVector &p_units); - void updateHighlightResults(int p_startPos, QVector p_units); + void updateHighlightResults(TimeStamp p_timeStamp, int p_startPos, QVector p_units); void addToHighlightCache(const QString &p_text, - int p_timeStamp, + TimeStamp p_timeStamp, const QVector &p_units); - HGMarkdownHighlighter *m_highlighter; + PegMarkdownHighlighter *m_highlighter; VDocument *m_vdocument; MarkdownConverterType m_type; - QAtomicInteger m_timeStamp; + + TimeStamp m_timeStamp; + QVector m_codeBlocks; // Cache for highlight result, using the code block text as key. diff --git a/src/vconfigmanager.h b/src/vconfigmanager.h index 5be2a1ba..3bba53dc 100644 --- a/src/vconfigmanager.h +++ b/src/vconfigmanager.h @@ -11,7 +11,7 @@ #include #include "vnotebook.h" -#include "hgmarkdownhighlighter.h" +#include "markdownhighlighterdata.h" #include "vmarkdownconverter.h" #include "vconstants.h" #include "vfilesessioninfo.h" diff --git a/src/vconstants.h b/src/vconstants.h index 0fc0a04e..5079b180 100644 --- a/src/vconstants.h +++ b/src/vconstants.h @@ -122,9 +122,11 @@ enum HighlightBlockState CodeBlockEnd, // This block is inside a HTML comment region. + // Obsolete. Comment, // Verbatim code block. + // Obsolete. Verbatim, // Mathjax. It means the pending state of the block. @@ -132,6 +134,7 @@ enum HighlightBlockState MathjaxInline, // Header. + // Obsolete. Header }; diff --git a/src/vdocument.cpp b/src/vdocument.cpp index 99fe8611..d1a6fa1e 100644 --- a/src/vdocument.cpp +++ b/src/vdocument.cpp @@ -74,12 +74,12 @@ void VDocument::keyPressEvent(int p_key, bool p_ctrl, bool p_shift, bool p_meta) emit keyPressed(p_key, p_ctrl, p_shift, p_meta); } -void VDocument::highlightTextAsync(const QString &p_text, int p_id, int p_timeStamp) +void VDocument::highlightTextAsync(const QString &p_text, int p_id, unsigned long long p_timeStamp) { emit requestHighlightText(p_text, p_id, p_timeStamp); } -void VDocument::highlightTextCB(const QString &p_html, int p_id, int p_timeStamp) +void VDocument::highlightTextCB(const QString &p_html, int p_id, unsigned long long p_timeStamp) { emit textHighlighted(p_html, p_id, p_timeStamp); } diff --git a/src/vdocument.h b/src/vdocument.h index cbe5555f..66c94460 100644 --- a/src/vdocument.h +++ b/src/vdocument.h @@ -31,7 +31,7 @@ public: // Request to highlight a segment text. // Use p_id to identify the result. - void highlightTextAsync(const QString &p_text, int p_id, int p_timeStamp); + void highlightTextAsync(const QString &p_text, int p_id, unsigned long long p_timeStamp); // Request to convert @p_text to HTML. void textToHtmlAsync(int p_identitifer, @@ -84,7 +84,7 @@ public slots: void keyPressEvent(int p_key, bool p_ctrl, bool p_shift, bool p_meta); void updateText(); - void highlightTextCB(const QString &p_html, int p_id, int p_timeStamp); + void highlightTextCB(const QString &p_html, int p_id, unsigned long long p_timeStamp); void noticeReadyToHighlightText(); @@ -128,9 +128,9 @@ signals: void keyPressed(int p_key, bool p_ctrl, bool p_shift, bool p_meta); - void requestHighlightText(const QString &p_text, int p_id, int p_timeStamp); + void requestHighlightText(const QString &p_text, int p_id, unsigned long long p_timeStamp); - void textHighlighted(const QString &p_html, int p_id, int p_timeStamp); + void textHighlighted(const QString &p_html, int p_id, unsigned long long p_timeStamp); void readyToHighlightText(); diff --git a/src/vlivepreviewhelper.cpp b/src/vlivepreviewhelper.cpp index 2ffa9a0f..9989fe2a 100644 --- a/src/vlivepreviewhelper.cpp +++ b/src/vlivepreviewhelper.cpp @@ -125,8 +125,9 @@ void VLivePreviewHelper::checkLang(const QString &p_lang, } } -void VLivePreviewHelper::updateCodeBlocks(const QVector &p_codeBlocks) +void VLivePreviewHelper::updateCodeBlocks(TimeStamp p_timeStamp, const QVector &p_codeBlocks) { + Q_UNUSED(p_timeStamp); if (!m_livePreviewEnabled && !m_inplacePreviewEnabled) { return; } diff --git a/src/vlivepreviewhelper.h b/src/vlivepreviewhelper.h index 5de40d53..d2edb2c4 100644 --- a/src/vlivepreviewhelper.h +++ b/src/vlivepreviewhelper.h @@ -109,7 +109,7 @@ public: bool isPreviewEnabled() const; public slots: - void updateCodeBlocks(const QVector &p_codeBlocks); + void updateCodeBlocks(TimeStamp p_timeStamp, const QVector &p_codeBlocks); signals: void inplacePreviewCodeBlockUpdated(const QVector > &p_images); diff --git a/src/vmdedit.cpp b/src/vmdedit.cpp index 74a9e0f6..cd064c15 100644 --- a/src/vmdedit.cpp +++ b/src/vmdedit.cpp @@ -1,7 +1,6 @@ #include #include "vmdedit.h" #include "hgmarkdownhighlighter.h" -#include "vcodeblockhighlighthelper.h" #include "vmdeditoperations.h" #include "vnote.h" #include "vconfigmanager.h" @@ -24,6 +23,9 @@ VMdEdit::VMdEdit(VFile *p_file, VDocument *p_vdoc, MarkdownConverterType p_type, : VEdit(p_file, p_parent), m_mdHighlighter(NULL), m_freshEdit(true), m_finishedAsyncJobs(c_numberOfAysncJobs) { + Q_UNUSED(p_type); + Q_UNUSED(p_vdoc); + V_ASSERT(p_file->getDocType() == DocType::Markdown); setAcceptRichText(false); @@ -43,9 +45,6 @@ VMdEdit::VMdEdit(VFile *p_file, VDocument *p_vdoc, MarkdownConverterType p_type, makeBlockVisible(textCursor().block()); }); - m_cbHighlighter = new VCodeBlockHighlightHelper(m_mdHighlighter, p_vdoc, - p_type); - /* m_imagePreviewer = new VImagePreviewer(this, m_mdHighlighter); connect(m_mdHighlighter, &HGMarkdownHighlighter::imageLinksUpdated, diff --git a/src/vmdedit.h b/src/vmdedit.h index 36ab0974..c7b73767 100644 --- a/src/vmdedit.h +++ b/src/vmdedit.h @@ -13,7 +13,6 @@ #include "utils/vutils.h" class HGMarkdownHighlighter; -class VCodeBlockHighlightHelper; class VDocument; class VMdEdit : public VEdit @@ -110,7 +109,6 @@ private: int indexOfCurrentHeader() const; HGMarkdownHighlighter *m_mdHighlighter; - VCodeBlockHighlightHelper *m_cbHighlighter; // VImagePreviewer *m_imagePreviewer; // Image links inserted while editing. diff --git a/src/vmdeditor.cpp b/src/vmdeditor.cpp index 853d3eb8..9b0eefb4 100644 --- a/src/vmdeditor.cpp +++ b/src/vmdeditor.cpp @@ -9,7 +9,7 @@ #include "vdocument.h" #include "utils/veditutils.h" #include "vedittab.h" -#include "hgmarkdownhighlighter.h" +#include "pegmarkdownhighlighter.h" #include "vcodeblockhighlighthelper.h" #include "vmdeditoperations.h" #include "vtableofcontent.h" @@ -35,7 +35,7 @@ VMdEditor::VMdEditor(VFile *p_file, QWidget *p_parent) : VTextEdit(p_parent), VEditor(p_file, this), - m_mdHighlighter(NULL), + m_pegHighlighter(NULL), m_freshEdit(true), m_textToHtmlDialog(NULL), m_zoomDelta(0), @@ -60,18 +60,17 @@ VMdEditor::VMdEditor(VFile *p_file, setReadOnly(true); - m_mdHighlighter = new HGMarkdownHighlighter(g_config->getMdHighlightingStyles(), - g_config->getCodeBlockStyles(), - g_config->getMarkdownHighlightInterval(), - document()); - m_mdHighlighter->setMathjaxEnabled(g_config->getEnableMathjax()); - - connect(m_mdHighlighter, &HGMarkdownHighlighter::headersUpdated, + m_pegHighlighter = new PegMarkdownHighlighter(document()); + m_pegHighlighter->init(g_config->getMdHighlightingStyles(), + g_config->getCodeBlockStyles(), + g_config->getEnableMathjax(), + g_config->getMarkdownHighlightInterval()); + connect(m_pegHighlighter, &PegMarkdownHighlighter::headersUpdated, this, &VMdEditor::updateHeaders); // After highlight, the cursor may trun into non-visible. We should make it visible // in this case. - connect(m_mdHighlighter, &HGMarkdownHighlighter::highlightCompleted, + connect(m_pegHighlighter, &PegMarkdownHighlighter::highlightCompleted, this, [this]() { makeBlockVisible(textCursor().block()); @@ -81,15 +80,15 @@ VMdEditor::VMdEditor(VFile *p_file, } }); - m_cbHighlighter = new VCodeBlockHighlightHelper(m_mdHighlighter, + m_cbHighlighter = new VCodeBlockHighlightHelper(m_pegHighlighter, p_doc, p_type); - m_previewMgr = new VPreviewManager(this, m_mdHighlighter); - connect(m_mdHighlighter, &HGMarkdownHighlighter::imageLinksUpdated, + m_previewMgr = new VPreviewManager(this, m_pegHighlighter); + connect(m_pegHighlighter, &PegMarkdownHighlighter::imageLinksUpdated, m_previewMgr, &VPreviewManager::updateImageLinks); connect(m_previewMgr, &VPreviewManager::requestUpdateImageLinks, - m_mdHighlighter, &HGMarkdownHighlighter::updateHighlight); + m_pegHighlighter, &PegMarkdownHighlighter::updateHighlight); m_editOps = new VMdEditOperations(this, m_file); connect(m_editOps, &VEditOperations::statusMessage, @@ -132,10 +131,10 @@ void VMdEditor::beginEdit() emit statusChanged(); if (m_freshEdit) { - m_mdHighlighter->updateHighlight(); + m_pegHighlighter->updateHighlight(); relayout(); } else { - updateHeaders(m_mdHighlighter->getHeaderRegions()); + updateHeaders(m_pegHighlighter->getHeaderRegions()); } } @@ -170,7 +169,7 @@ void VMdEditor::reloadFile() const QString &content = m_file->getContent(); setPlainText(content); setModified(false); - m_mdHighlighter->updateHighlightFast(); + m_pegHighlighter->updateHighlightFast(); m_freshEdit = true; @@ -461,7 +460,7 @@ static void insertSequenceToHeader(QTextCursor& p_cursor, void VMdEditor::updateHeaderSequenceByConfigChange() { - updateHeadersHelper(m_mdHighlighter->getHeaderRegions(), true); + updateHeadersHelper(m_pegHighlighter->getHeaderRegions(), true); } void VMdEditor::updateHeadersHelper(const QVector &p_headerRegions, bool p_configChanged) @@ -493,8 +492,7 @@ void VMdEditor::updateHeadersHelper(const QVector &p_headerRegio << block.text(); } - if ((block.userState() == HighlightBlockState::Header) - && headerReg.exactMatch(block.text())) { + if (headerReg.exactMatch(block.text())) { int level = headerReg.cap(1).length(); VTableOfContentItem header(headerReg.cap(2).trimmed(), level, @@ -1199,7 +1197,7 @@ void VMdEditor::zoomPage(bool p_zoomIn, int p_range) m_zoomDelta += delta; - QVector &styles = m_mdHighlighter->getHighlightingStyles(); + QVector &styles = m_pegHighlighter->getStyles(); for (auto & it : styles) { int size = it.format.fontPointSize(); if (size == 0) { @@ -1215,7 +1213,7 @@ void VMdEditor::zoomPage(bool p_zoomIn, int p_range) it.format.setFontPointSize(size); } - QHash &cbStyles = m_mdHighlighter->getCodeBlockStyles(); + QHash &cbStyles = m_pegHighlighter->getCodeBlockStyles(); for (auto it = cbStyles.begin(); it != cbStyles.end(); ++it) { int size = it.value().fontPointSize(); if (size == 0) { @@ -1231,7 +1229,7 @@ void VMdEditor::zoomPage(bool p_zoomIn, int p_range) it.value().setFontPointSize(size); } - m_mdHighlighter->rehighlight(); + m_pegHighlighter->rehighlight(); } void VMdEditor::initCopyAsMenu(QAction *p_before, QMenu *p_menu) diff --git a/src/vmdeditor.h b/src/vmdeditor.h index 5d7003bf..c73cfbfb 100644 --- a/src/vmdeditor.h +++ b/src/vmdeditor.h @@ -14,7 +14,7 @@ #include "vconfigmanager.h" #include "utils/vutils.h" -class HGMarkdownHighlighter; +class PegMarkdownHighlighter; class VCodeBlockHighlightHelper; class VDocument; class VPreviewManager; @@ -72,7 +72,7 @@ public: void setEditTab(VEditTab *p_editTab); - HGMarkdownHighlighter *getMarkdownHighlighter() const; + PegMarkdownHighlighter *getMarkdownHighlighter() const; VPreviewManager *getPreviewManager() const; @@ -256,7 +256,7 @@ private: void insertImageLink(const QString &p_text, const QString &p_url); - HGMarkdownHighlighter *m_mdHighlighter; + PegMarkdownHighlighter *m_pegHighlighter; VCodeBlockHighlightHelper *m_cbHighlighter; @@ -282,9 +282,9 @@ private: int m_copyTimeStamp; }; -inline HGMarkdownHighlighter *VMdEditor::getMarkdownHighlighter() const +inline PegMarkdownHighlighter *VMdEditor::getMarkdownHighlighter() const { - return m_mdHighlighter; + return m_pegHighlighter; } inline VPreviewManager *VMdEditor::getPreviewManager() const diff --git a/src/vmdtab.cpp b/src/vmdtab.cpp index fdc5d34f..83366ee7 100644 --- a/src/vmdtab.cpp +++ b/src/vmdtab.cpp @@ -8,7 +8,7 @@ #include "vnote.h" #include "utils/vutils.h" #include "vpreviewpage.h" -#include "hgmarkdownhighlighter.h" +#include "pegmarkdownhighlighter.h" #include "vconfigmanager.h" #include "vmarkdownconverter.h" #include "vnotebook.h" @@ -535,7 +535,7 @@ void VMdTab::setupMarkdownEditor() m_splitter->insertWidget(0, m_editor); m_livePreviewHelper = new VLivePreviewHelper(m_editor, m_document, this); - connect(m_editor->getMarkdownHighlighter(), &HGMarkdownHighlighter::codeBlocksUpdated, + connect(m_editor->getMarkdownHighlighter(), &PegMarkdownHighlighter::codeBlocksUpdated, m_livePreviewHelper, &VLivePreviewHelper::updateCodeBlocks); connect(m_editor->getPreviewManager(), &VPreviewManager::previewEnabledChanged, m_livePreviewHelper, &VLivePreviewHelper::setInplacePreviewEnabled); @@ -546,7 +546,7 @@ void VMdTab::setupMarkdownEditor() m_livePreviewHelper->setInplacePreviewEnabled(m_editor->getPreviewManager()->isPreviewEnabled()); m_mathjaxPreviewHelper = new VMathJaxInplacePreviewHelper(m_editor, m_document, this); - connect(m_editor->getMarkdownHighlighter(), &HGMarkdownHighlighter::mathjaxBlocksUpdated, + connect(m_editor->getMarkdownHighlighter(), &PegMarkdownHighlighter::mathjaxBlocksUpdated, m_mathjaxPreviewHelper, &VMathJaxInplacePreviewHelper::updateMathjaxBlocks); connect(m_editor->getPreviewManager(), &VPreviewManager::previewEnabledChanged, m_mathjaxPreviewHelper, &VMathJaxInplacePreviewHelper::setEnabled); diff --git a/src/vpreviewmanager.cpp b/src/vpreviewmanager.cpp index 97817c25..c025614b 100644 --- a/src/vpreviewmanager.cpp +++ b/src/vpreviewmanager.cpp @@ -10,11 +10,11 @@ #include "vconfigmanager.h" #include "utils/vutils.h" #include "vdownloader.h" -#include "hgmarkdownhighlighter.h" +#include "pegmarkdownhighlighter.h" extern VConfigManager *g_config; -VPreviewManager::VPreviewManager(VMdEditor *p_editor, HGMarkdownHighlighter *p_highlighter) +VPreviewManager::VPreviewManager(VMdEditor *p_editor, PegMarkdownHighlighter *p_highlighter) : QObject(p_editor), m_editor(p_editor), m_document(p_editor->document()), diff --git a/src/vpreviewmanager.h b/src/vpreviewmanager.h index 3d9d4371..5560d109 100644 --- a/src/vpreviewmanager.h +++ b/src/vpreviewmanager.h @@ -8,7 +8,7 @@ #include #include -#include "hgmarkdownhighlighter.h" +#include "markdownhighlighterdata.h" #include "vmdeditor.h" #include "vtextblockdata.h" @@ -59,7 +59,7 @@ class VPreviewManager : public QObject { Q_OBJECT public: - VPreviewManager(VMdEditor *p_editor, HGMarkdownHighlighter *p_highlighter); + VPreviewManager(VMdEditor *p_editor, PegMarkdownHighlighter *p_highlighter); void setPreviewEnabled(bool p_enabled); @@ -215,7 +215,7 @@ private: QTextDocument *m_document; - HGMarkdownHighlighter *m_highlighter; + PegMarkdownHighlighter *m_highlighter; VDownloader *m_downloader;