From abee59781253163cd7dffd3825d3789f6062fd41 Mon Sep 17 00:00:00 2001 From: Le Tan Date: Sat, 14 Jul 2018 12:45:15 +0800 Subject: [PATCH] PegHighlighter: support fast parse --- src/peghighlighterresult.cpp | 40 ++++-- src/peghighlighterresult.h | 38 ++++-- src/pegmarkdownhighlighter.cpp | 195 +++++++++++++++++++++++++++--- src/pegmarkdownhighlighter.h | 17 +++ src/pegparser.cpp | 54 ++++++--- src/pegparser.h | 17 ++- src/vcodeblockhighlighthelper.cpp | 4 +- src/vpreviewmanager.cpp | 8 -- 8 files changed, 312 insertions(+), 61 deletions(-) diff --git a/src/peghighlighterresult.cpp b/src/peghighlighterresult.cpp index d79db4c9..74d450e1 100644 --- a/src/peghighlighterresult.cpp +++ b/src/peghighlighterresult.cpp @@ -7,6 +7,19 @@ #include "pegmarkdownhighlighter.h" #include "utils/vutils.h" +PegHighlighterFastResult::PegHighlighterFastResult() + : m_timeStamp(0) +{ +} + +PegHighlighterFastResult::PegHighlighterFastResult(const PegMarkdownHighlighter *p_peg, + const QSharedPointer &p_result) + : m_timeStamp(p_result->m_timeStamp) +{ + PegHighlighterResult::parseBlocksHighlights(m_blocksHighlights, p_peg, p_result); +} + + PegHighlighterResult::PegHighlighterResult() : m_timeStamp(0), m_numOfBlocks(0), @@ -25,7 +38,7 @@ PegHighlighterResult::PegHighlighterResult(const PegMarkdownHighlighter *p_peg, m_codeBlockStartExp = QRegExp(VUtils::c_fencedCodeBlockStartRegExp); m_codeBlockEndExp = QRegExp(VUtils::c_fencedCodeBlockEndRegExp); - parseBlocksHighlights(p_peg, p_result); + parseBlocksHighlights(m_blocksHighlights, p_peg, p_result); // Implicit sharing. m_imageRegions = p_result->m_imageRegions; @@ -47,14 +60,16 @@ static bool compHLUnit(const HLUnit &p_a, const HLUnit &p_b) } } -void PegHighlighterResult::parseBlocksHighlights(const PegMarkdownHighlighter *p_peg, +void PegHighlighterResult::parseBlocksHighlights(QVector> &p_blocksHighlights, + const PegMarkdownHighlighter *p_peg, const QSharedPointer &p_result) { - m_blocksHighlights.resize(m_numOfBlocks); + p_blocksHighlights.resize(p_result->m_numOfBlocks); if (p_result->isEmpty()) { return; } + int offset = p_result->m_offset; const QTextDocument *doc = p_peg->getDocument(); const QVector &styles = p_peg->getStyles(); auto pmhResult = p_result->m_pmhElements; @@ -71,20 +86,25 @@ void PegHighlighterResult::parseBlocksHighlights(const PegMarkdownHighlighter *p continue; } - parseBlocksHighlightOne(doc, elem_cursor->pos, elem_cursor->end, i); + parseBlocksHighlightOne(p_blocksHighlights, + doc, + offset + elem_cursor->pos, + offset + 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); + // Sort p_blocksHighlights. + for (int i = 0; i < p_blocksHighlights.size(); ++i) { + if (p_blocksHighlights[i].size() > 1) { + std::sort(p_blocksHighlights[i].begin(), p_blocksHighlights[i].end(), compHLUnit); } } } -void PegHighlighterResult::parseBlocksHighlightOne(const QTextDocument *p_doc, +void PegHighlighterResult::parseBlocksHighlightOne(QVector> &p_blocksHighlights, + const QTextDocument *p_doc, unsigned long p_pos, unsigned long p_end, int p_styleIndex) @@ -121,7 +141,7 @@ void PegHighlighterResult::parseBlocksHighlightOne(const QTextDocument *p_doc, } unit.styleIndex = p_styleIndex; - m_blocksHighlights[blockNum].append(unit); + p_blocksHighlights[blockNum].append(unit); block = block.next(); } diff --git a/src/peghighlighterresult.h b/src/peghighlighterresult.h index 63a8490f..fef125cc 100644 --- a/src/peghighlighterresult.h +++ b/src/peghighlighterresult.h @@ -7,16 +7,41 @@ class PegMarkdownHighlighter; class QTextDocument; +class PegHighlighterFastResult +{ +public: + PegHighlighterFastResult(); + + PegHighlighterFastResult(const PegMarkdownHighlighter *p_peg, + const QSharedPointer &p_result); + + bool matched(TimeStamp p_timeStamp) const + { + return m_timeStamp == p_timeStamp; + } + + TimeStamp m_timeStamp; + + QVector> m_blocksHighlights; +}; + + class PegHighlighterResult { public: PegHighlighterResult(); + // TODO: handle p_result->m_offset. PegHighlighterResult(const PegMarkdownHighlighter *p_peg, const QSharedPointer &p_result); bool matched(TimeStamp p_timeStamp) const; + // Parse highlight elements for all the blocks from parse results. + static void parseBlocksHighlights(QVector> &p_blocksHighlights, + const PegMarkdownHighlighter *p_peg, + const QSharedPointer &p_result); + TimeStamp m_timeStamp; int m_numOfBlocks; @@ -48,15 +73,12 @@ public: QVector m_mathjaxBlocks; 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); + static void parseBlocksHighlightOne(QVector> &p_blocksHighlights, + 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, diff --git a/src/pegmarkdownhighlighter.cpp b/src/pegmarkdownhighlighter.cpp index bd8fc310..6c7ad5d2 100644 --- a/src/pegmarkdownhighlighter.cpp +++ b/src/pegmarkdownhighlighter.cpp @@ -49,6 +49,7 @@ void PegMarkdownHighlighter::init(const QVector &p_styles, m_colorColumnFormat.setBackground(QColor(g_config->getEditorColorColumnBg())); m_result.reset(new PegHighlighterResult()); + m_fastResult.reset(new PegHighlighterFastResult()); m_parser = new PegParser(this); connect(m_parser, &PegParser::parseResultReady, @@ -62,6 +63,24 @@ void PegMarkdownHighlighter::init(const QVector &p_styles, startParse(); }); + m_fastParseTimer = new QTimer(this); + m_fastParseTimer->setSingleShot(true); + m_fastParseTimer->setInterval(50); + connect(m_fastParseTimer, &QTimer::timeout, + this, [this]() { + QSharedPointer result(m_fastResult); + if (!result->matched(m_timeStamp)) { + return; + } + + const QVector> &hls = result->m_blocksHighlights; + for (int i = 0; i < hls.size(); ++i) { + if (!hls[i].isEmpty()) { + rehighlightBlock(m_doc->findBlockByNumber(i)); + } + } + }); + connect(m_doc, &QTextDocument::contentsChange, this, &PegMarkdownHighlighter::handleContentsChange); } @@ -71,9 +90,31 @@ void PegMarkdownHighlighter::highlightBlock(const QString &p_text) QSharedPointer result(m_result); int blockNum = currentBlock().blockNumber(); - if (result->m_blocksHighlights.size() > blockNum) { + + highlightBlockOne(result->m_blocksHighlights, blockNum); + + highlightBlockOne(m_fastResult->m_blocksHighlights, blockNum); + + // 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, p_text); + + highlightCodeBlockColorColumn(p_text); + } +} + +void PegMarkdownHighlighter::highlightBlockOne(const QVector> &p_highlights, + int p_blockNum) +{ + if (p_highlights.size() > p_blockNum) { // units are sorted by start position and length. - const QVector &units = result->m_blocksHighlights[blockNum]; + const QVector &units = p_highlights[p_blockNum]; if (!units.isEmpty()) { for (int i = 0; i < units.size(); ++i) { const HLUnit &unit = units[i]; @@ -102,19 +143,6 @@ void PegMarkdownHighlighter::highlightBlock(const QString &p_text) } } } - - // 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, p_text); - - highlightCodeBlockColorColumn(p_text); - } } void PegMarkdownHighlighter::handleContentsChange(int p_position, int p_charsRemoved, int p_charsAdded) @@ -127,6 +155,8 @@ void PegMarkdownHighlighter::handleContentsChange(int p_position, int p_charsRem ++m_timeStamp; + startFastParse(p_position, p_charsRemoved, p_charsAdded); + // We still need a timer to start a complete parse. m_timer->start(); } @@ -142,6 +172,47 @@ void PegMarkdownHighlighter::startParse() m_parser->parseAsync(config); } +void PegMarkdownHighlighter::startFastParse(int p_position, int p_charsRemoved, int p_charsAdded) +{ + // Get affected block range. + int firstBlockNum, lastBlockNum; + getFastParseBlockRange(p_position, p_charsRemoved, p_charsAdded, firstBlockNum, lastBlockNum); + + QString text; + QTextBlock block = m_doc->findBlockByNumber(firstBlockNum); + int offset = block.position(); + while (block.isValid()) { + int blockNum = block.blockNumber(); + if (blockNum > lastBlockNum) { + break; + } else if (blockNum == firstBlockNum) { + text = block.text(); + } else { + text = text + "\n" + block.text(); + } + + block = block.next(); + } + + QSharedPointer config(new PegParseConfig()); + config->m_timeStamp = m_timeStamp; + config->m_data = text.toUtf8(); + config->m_numOfBlocks = m_doc->blockCount(); + config->m_offset = offset; + config->m_extensions = m_parserExts; + config->m_fast = true; + + QSharedPointer parseRes = m_parser->parse(config); + processFastParseResult(parseRes); +} + +void PegMarkdownHighlighter::processFastParseResult(const QSharedPointer &p_result) +{ + m_fastParseTimer->stop(); + m_fastResult.reset(new PegHighlighterFastResult(this, p_result)); + m_fastParseTimer->start(); +} + static bool compHLUnitStyle(const HLUnitStyle &a, const HLUnitStyle &b) { if (a.start < b.start) { @@ -424,3 +495,97 @@ void PegMarkdownHighlighter::completeHighlight(QSharedPointerfindBlock(p_position); + + // May be an invalid block. + QTextBlock lastBlock = m_doc->findBlock(qMax(0, p_position + charsChanged)); + if (!lastBlock.isValid()) { + lastBlock = m_doc->lastBlock(); + } + + int num = lastBlock.blockNumber() - firstBlock.blockNumber() + 1; + + if (num >= maxNumOfBlocks) { + p_firstBlock = firstBlock.blockNumber(); + p_lastBlock = p_firstBlock + maxNumOfBlocks - 1; + return; + } + + // Look up. + // Find empty block. + if (!VEditUtils::isEmptyBlock(firstBlock)) { + while (firstBlock.isValid() && num < maxNumOfBlocks) { + QTextBlock block = firstBlock.previous(); + if (block.isValid() && !VEditUtils::isEmptyBlock(block)) { + firstBlock = block; + ++num; + } else { + break; + } + } + } + + // Cross code block. + while (firstBlock.isValid() && num < maxNumOfBlocks) { + int state = firstBlock.userState(); + if (state == HighlightBlockState::CodeBlock + || state == HighlightBlockState::CodeBlockEnd) { + QTextBlock block = firstBlock.previous(); + if (block.isValid()) { + firstBlock = block; + ++num; + } else { + break; + } + } else { + break; + } + } + + // Look down. + // Find empty block. + if (!VEditUtils::isEmptyBlock(lastBlock)) { + while (lastBlock.isValid() && num < maxNumOfBlocks) { + QTextBlock block = lastBlock.next(); + if (block.isValid() && !VEditUtils::isEmptyBlock(block)) { + lastBlock = block; + ++num; + } else { + break; + } + } + } + + // Cross code block. + while (lastBlock.isValid() && num < maxNumOfBlocks) { + int state = lastBlock.userState(); + if (state == HighlightBlockState::CodeBlock + || state == HighlightBlockState::CodeBlockStart) { + QTextBlock block = lastBlock.next(); + if (block.isValid()) { + lastBlock = block; + ++num; + } else { + break; + } + } else { + break; + } + } + + p_firstBlock = firstBlock.blockNumber(); + p_lastBlock = lastBlock.blockNumber(); + if (p_lastBlock < p_firstBlock) { + p_lastBlock = p_firstBlock; + } +} diff --git a/src/pegmarkdownhighlighter.h b/src/pegmarkdownhighlighter.h index 78747a14..205ef016 100644 --- a/src/pegmarkdownhighlighter.h +++ b/src/pegmarkdownhighlighter.h @@ -74,6 +74,8 @@ private slots: private: void startParse(); + void startFastParse(int p_position, int p_charsRemoved, int p_charsAdded); + void updateCodeBlocks(QSharedPointer p_result); // Set the user data of currentBlock(). @@ -99,6 +101,17 @@ private: bool isMathJaxEnabled() const; + void getFastParseBlockRange(int p_position, + int p_charsRemoved, + int p_charsAdded, + int &p_firstBlock, + int &p_lastBlock) const; + + void processFastParseResult(const QSharedPointer &p_result); + + void highlightBlockOne(const QVector> &p_highlights, + int p_blockNum); + QTextDocument *m_doc; TimeStamp m_timeStamp; @@ -113,6 +126,8 @@ private: QSharedPointer m_result; + QSharedPointer m_fastResult; + // Block number of those blocks which possible contains previewed image. QSet m_possiblePreviewBlocks; @@ -121,6 +136,8 @@ private: // Timer to trigger parse. QTimer *m_timer; + + QTimer *m_fastParseTimer; }; inline const QVector &PegMarkdownHighlighter::getHeaderRegions() const diff --git a/src/pegparser.cpp b/src/pegparser.cpp index 00498321..4c62862a 100644 --- a/src/pegparser.cpp +++ b/src/pegparser.cpp @@ -8,8 +8,12 @@ enum WorkerState Finished }; -void PegParseResult::parse(QAtomicInt &p_stop) +void PegParseResult::parse(QAtomicInt &p_stop, bool p_fast) { + if (p_fast) { + return; + } + parseImageRegions(p_stop); parseHeaderRegions(p_stop); @@ -40,7 +44,7 @@ void PegParseResult::parseImageRegions(QAtomicInt &p_stop) return; } - m_imageRegions.push_back(VElementRegion(elem->pos, elem->end)); + m_imageRegions.push_back(VElementRegion(m_offset + elem->pos, m_offset + elem->end)); elem = elem->next; } } @@ -66,7 +70,7 @@ void PegParseResult::parseHeaderRegions(QAtomicInt &p_stop) return; } - m_headerRegions.push_back(VElementRegion(elem->pos, elem->end)); + m_headerRegions.push_back(VElementRegion(m_offset + elem->pos, m_offset + elem->end)); elem = elem->next; } } @@ -96,8 +100,9 @@ void PegParseResult::parseFencedCodeBlockRegions(QAtomicInt &p_stop) return; } - if (!m_codeBlockRegions.contains(elem->pos)) { - m_codeBlockRegions.insert(elem->pos, VElementRegion(elem->pos, elem->end)); + if (!m_codeBlockRegions.contains(m_offset + elem->pos)) { + m_codeBlockRegions.insert(m_offset + elem->pos, + VElementRegion(m_offset + elem->pos, m_offset + elem->end)); } elem = elem->next; @@ -122,7 +127,7 @@ void PegParseResult::parseInlineEquationRegions(QAtomicInt &p_stop) return; } - m_inlineEquationRegions.push_back(VElementRegion(elem->pos, elem->end)); + m_inlineEquationRegions.push_back(VElementRegion(m_offset + elem->pos, m_offset + elem->end)); elem = elem->next; } } @@ -145,7 +150,7 @@ void PegParseResult::parseDisplayFormulaRegions(QAtomicInt &p_stop) return; } - m_displayFormulaRegions.push_back(VElementRegion(elem->pos, elem->end)); + m_displayFormulaRegions.push_back(VElementRegion(m_offset + elem->pos, m_offset + elem->end)); elem = elem->next; } @@ -214,7 +219,7 @@ QSharedPointer PegParserWorker::parseMarkdown(const QSharedPoint return result; } - result->parse(p_stop); + result->parse(p_stop, p_config->m_fast); return result; } @@ -228,13 +233,6 @@ PegParser::PegParser(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) { @@ -267,6 +265,29 @@ PegParser::~PegParser() clear(); } +void PegParser::parseAsync(const QSharedPointer &p_config) +{ + m_pendingWork = p_config; + + pickWorker(); +} + +QSharedPointer PegParser::parse(const QSharedPointer &p_config) +{ + QSharedPointer result(new PegParseResult(p_config)); + + if (p_config->m_data.isEmpty()) { + return result; + } + + result->m_pmhElements = PegParser::parseMarkdownToElements(p_config); + + QAtomicInt stop(0); + result->parse(stop, p_config->m_fast); + + return result; +} + void PegParser::handleWorkerFinished(PegParserWorker *p_worker) { QSharedPointer result; @@ -333,6 +354,7 @@ QVector PegParser::parseImageRegions(const QSharedPointerm_offset; pmh_element *elem = res[pmh_IMAGE]; while (elem != NULL) { if (elem->end <= elem->pos) { @@ -340,7 +362,7 @@ QVector PegParser::parseImageRegions(const QSharedPointerpos, elem->end)); + regs.push_back(VElementRegion(offset + elem->pos, offset + elem->end)); elem = elem->next; } diff --git a/src/pegparser.h b/src/pegparser.h index b36257bd..e126ad60 100644 --- a/src/pegparser.h +++ b/src/pegparser.h @@ -16,7 +16,9 @@ struct PegParseConfig PegParseConfig() : m_timeStamp(0), m_numOfBlocks(0), - m_extensions(pmh_EXT_NONE) + m_offset(0), + m_extensions(pmh_EXT_NONE), + m_fast(false) { } @@ -26,8 +28,14 @@ struct PegParseConfig int m_numOfBlocks; + // Offset of m_data in the document. + int m_offset; + int m_extensions; + // Fast parse. + bool m_fast; + QString toString() const { return QString("PegParseConfig ts %1 data %2 blocks %3").arg(m_timeStamp) @@ -41,6 +49,7 @@ struct PegParseResult PegParseResult(const QSharedPointer &p_config) : m_timeStamp(p_config->m_timeStamp), m_numOfBlocks(p_config->m_numOfBlocks), + m_offset(p_config->m_offset), m_pmhElements(NULL) { } @@ -74,12 +83,14 @@ struct PegParseResult } // Parse m_pmhElements. - void parse(QAtomicInt &p_stop); + void parse(QAtomicInt &p_stop, bool p_fast); TimeStamp m_timeStamp; int m_numOfBlocks; + int m_offset; + pmh_element **m_pmhElements; // All image link regions. @@ -178,6 +189,8 @@ public: ~PegParser(); + QSharedPointer parse(const QSharedPointer &p_config); + void parseAsync(const QSharedPointer &p_config); static QVector parseImageRegions(const QSharedPointer &p_config); diff --git a/src/vcodeblockhighlighthelper.cpp b/src/vcodeblockhighlighthelper.cpp index c88b69b7..969905a9 100644 --- a/src/vcodeblockhighlighthelper.cpp +++ b/src/vcodeblockhighlighthelper.cpp @@ -290,10 +290,10 @@ void VCodeBlockHighlightHelper::addToHighlightCache(const QString &p_text, const QVector &p_units) { const int c_maxEntries = 100; - const int c_maxTimeStampSpan = 3; + const TimeStamp c_maxTimeStampSpan = 3; if (m_cache.size() >= c_maxEntries) { // Remove the oldest one. - int ts = p_timeStamp - c_maxTimeStampSpan; + TimeStamp ts = p_timeStamp - c_maxTimeStampSpan; for (auto it = m_cache.begin(); it != m_cache.end();) { if (it.value().m_timeStamp < ts) { it = m_cache.erase(it); diff --git a/src/vpreviewmanager.cpp b/src/vpreviewmanager.cpp index c025614b..5efe5d58 100644 --- a/src/vpreviewmanager.cpp +++ b/src/vpreviewmanager.cpp @@ -1,7 +1,6 @@ #include "vpreviewmanager.h" #include -#include #include #include #include @@ -63,7 +62,6 @@ void VPreviewManager::imageDownloaded(const QByteArray &p_data, const QString &p if (!image.isNull()) { m_editor->addImage(name, image); - qDebug() << "downloaded image inserted in resource manager" << p_url << name; emit requestUpdateImageLinks(); } } @@ -172,8 +170,6 @@ void VPreviewManager::fetchImageLinksFromRegions(QVector p_image } p_imageLinks.append(info); - - qDebug() << "image region" << i << info.toString(); } } @@ -404,10 +400,6 @@ void VPreviewManager::updateBlockPreviewInfo(TS p_timeStamp, affectedBlocks.insert(link.m_blockNumber, QMapDummyValue()); m_highlighter->addPossiblePreviewBlock(link.m_blockNumber); } - - qDebug() << "block" << link.m_blockNumber - << imageCache(PreviewSource::ImageLink).size() - << blockData->toString(); } relayoutEditor(affectedBlocks);