From 8679ffa05139cd59b46dbf5d62f8795a3138ea74 Mon Sep 17 00:00:00 2001 From: Le Tan Date: Fri, 27 Jul 2018 21:05:32 +0800 Subject: [PATCH] PegMarkdownHighlighter: improve performance --- src/peghighlighterresult.cpp | 2 + src/peghighlighterresult.h | 3 + src/pegmarkdownhighlighter.cpp | 318 ++++++++++++++++++++++----------- src/pegmarkdownhighlighter.h | 111 ++++++++++-- src/utils/vutils.cpp | 17 ++ src/utils/vutils.h | 6 + src/vconstants.h | 2 +- src/veditor.h | 2 + src/vmdeditor.cpp | 10 +- src/vmdeditor.h | 5 + src/vpreviewmanager.cpp | 17 +- src/vtextblockdata.cpp | 5 +- src/vtextblockdata.h | 36 ++++ 13 files changed, 403 insertions(+), 131 deletions(-) diff --git a/src/peghighlighterresult.cpp b/src/peghighlighterresult.cpp index 51594f3a..6d1bd89d 100644 --- a/src/peghighlighterresult.cpp +++ b/src/peghighlighterresult.cpp @@ -22,6 +22,7 @@ PegHighlighterFastResult::PegHighlighterFastResult(const PegMarkdownHighlighter PegHighlighterResult::PegHighlighterResult() : m_timeStamp(0), m_numOfBlocks(0), + m_codeBlockTimeStamp(0), m_numOfCodeBlockHighlightsToRecv(0) { m_codeBlockStartExp = QRegExp(VUtils::c_fencedCodeBlockStartRegExp); @@ -32,6 +33,7 @@ PegHighlighterResult::PegHighlighterResult(const PegMarkdownHighlighter *p_peg, const QSharedPointer &p_result) : m_timeStamp(p_result->m_timeStamp), m_numOfBlocks(p_result->m_numOfBlocks), + m_codeBlockTimeStamp(0), m_numOfCodeBlockHighlightsToRecv(0) { m_codeBlockStartExp = QRegExp(VUtils::c_fencedCodeBlockStartRegExp); diff --git a/src/peghighlighterresult.h b/src/peghighlighterresult.h index 1680ef8c..f3ce2891 100644 --- a/src/peghighlighterresult.h +++ b/src/peghighlighterresult.h @@ -54,6 +54,9 @@ public: // Support fenced code block only. QVector > m_codeBlocksHighlights; + // Timestamp for m_codeBlocksHighlights. + TimeStamp m_codeBlockTimeStamp; + // All image link regions. QVector m_imageRegions; diff --git a/src/pegmarkdownhighlighter.cpp b/src/pegmarkdownhighlighter.cpp index b47582c6..8c829c6e 100644 --- a/src/pegmarkdownhighlighter.cpp +++ b/src/pegmarkdownhighlighter.cpp @@ -2,17 +2,22 @@ #include #include +#include #include "pegparser.h" #include "vconfigmanager.h" #include "utils/vutils.h" #include "utils/veditutils.h" +#include "vmdeditor.h" extern VConfigManager *g_config; -PegMarkdownHighlighter::PegMarkdownHighlighter(QTextDocument *p_doc) +#define LARGE_BLOCK_NUMBER 2000 + +PegMarkdownHighlighter::PegMarkdownHighlighter(QTextDocument *p_doc, VMdEditor *p_editor) : QSyntaxHighlighter(p_doc), m_doc(p_doc), + m_editor(p_editor), m_timeStamp(0), m_parser(NULL), m_parserExts(pmh_EXT_NOTES | pmh_EXT_STRIKE | pmh_EXT_FRONTMATTER | pmh_EXT_MARK) @@ -75,15 +80,33 @@ void PegMarkdownHighlighter::init(const QVector &p_styles, const QVector> &hls = result->m_blocksHighlights; for (int i = 0; i < hls.size(); ++i) { if (!hls[i].isEmpty()) { - rehighlightBlock(m_doc->findBlockByNumber(i)); + QTextBlock block = m_doc->findBlockByNumber(i); + if (PegMarkdownHighlighter::blockTimeStamp(block) != m_timeStamp) { + rehighlightBlock(block); + } } } }); + m_rehighlightTimer = new QTimer(this); + m_rehighlightTimer->setSingleShot(true); + m_rehighlightTimer->setInterval(5); + connect(m_rehighlightTimer, &QTimer::timeout, + this, [this]() { + if (m_result->m_numOfBlocks > LARGE_BLOCK_NUMBER) { + rehighlightSensitiveBlocks(); + } + }); + connect(m_doc, &QTextDocument::contentsChange, this, &PegMarkdownHighlighter::handleContentsChange); + + connect(m_editor->verticalScrollBar(), &QScrollBar::valueChanged, + m_rehighlightTimer, static_cast(&QTimer::start)); } +// Just use parse results to highlight block. +// Do not maintain block data and state here. void PegMarkdownHighlighter::highlightBlock(const QString &p_text) { QSharedPointer result(m_result); @@ -93,27 +116,24 @@ void PegMarkdownHighlighter::highlightBlock(const QString &p_text) if (result->matched(m_timeStamp)) { preHighlightSingleFormatBlock(result->m_blocksHighlights, blockNum, p_text); + + highlightBlockOne(result->m_blocksHighlights, blockNum); } else { preHighlightSingleFormatBlock(m_fastResult->m_blocksHighlights, blockNum, p_text); + + // If fast result cover this block, we do not need to use the outdated one. + if (!highlightBlockOne(m_fastResult->m_blocksHighlights, blockNum)) { + highlightBlockOne(result->m_blocksHighlights, blockNum); + } } - highlightBlockOne(result->m_blocksHighlights, blockNum); - - // The complete result is not ready yet. We use fast result for compensation. - if (!result->matched(m_timeStamp)) { - highlightBlockOne(m_fastResult->m_blocksHighlights, blockNum); - } - - // Set current block's user data. - updateBlockUserData(blockNum, p_text); - - updateBlockUserState(result, blockNum, p_text); - if (currentBlockState() == HighlightBlockState::CodeBlock) { highlightCodeBlock(result, blockNum, p_text); - highlightCodeBlockColorColumn(p_text); + PegMarkdownHighlighter::updateBlockCodeBlockTimeStamp(block, result->m_codeBlockTimeStamp); } + + PegMarkdownHighlighter::updateBlockTimeStamp(block, result->m_timeStamp); } void PegMarkdownHighlighter::preHighlightSingleFormatBlock(const QVector> &p_highlights, @@ -140,13 +160,15 @@ void PegMarkdownHighlighter::preHighlightSingleFormatBlock(const QVector> &p_highlights, +bool PegMarkdownHighlighter::highlightBlockOne(const QVector> &p_highlights, int p_blockNum) { + bool highlighted = false; if (p_highlights.size() > p_blockNum) { // units are sorted by start position and length. const QVector &units = p_highlights[p_blockNum]; if (!units.isEmpty()) { + highlighted = true; for (int i = 0; i < units.size(); ++i) { const HLUnit &unit = units[i]; if (i == 0) { @@ -174,6 +196,8 @@ void PegMarkdownHighlighter::highlightBlockOne(const QVector> &p } } } + + return highlighted; } // highlightBlock() will be called before this function. @@ -187,7 +211,9 @@ void PegMarkdownHighlighter::handleContentsChange(int p_position, int p_charsRem ++m_timeStamp; - startFastParse(p_position, p_charsRemoved, p_charsAdded); + if (m_timeStamp > 2) { + startFastParse(p_position, p_charsRemoved, p_charsAdded); + } // We still need a timer to start a complete parse. m_timer->start(); @@ -266,7 +292,8 @@ void PegMarkdownHighlighter::setCodeBlockHighlights(TimeStamp p_timeStamp, const QVector &p_units) { QSharedPointer result(m_result); - if (!result->matched(p_timeStamp)) { + if (!result->matched(p_timeStamp) + || result->m_numOfCodeBlockHighlightsToRecv <= 0) { return; } @@ -330,25 +357,22 @@ void PegMarkdownHighlighter::setCodeBlockHighlights(TimeStamp p_timeStamp, exit: if (--result->m_numOfCodeBlockHighlightsToRecv <= 0) { - // Rehighlight specific blocks. - const QVector> &hls = result->m_codeBlocksHighlights; - for (int i = 0; i < hls.size(); ++i) { - if (!hls[i].isEmpty()) { - rehighlightBlock(m_doc->findBlockByNumber(i)); - } - } + ++result->m_codeBlockTimeStamp; + rehighlightBlocks(); } } -void PegMarkdownHighlighter::updateHighlightFast() -{ - updateHighlight(); -} - void PegMarkdownHighlighter::updateHighlight() { m_timer->stop(); - startParse(); + if (m_result->matched(m_timeStamp)) { + // No need to parse again. Already the latest. + updateCodeBlocks(m_result); + rehighlightBlocks(); + completeHighlight(m_result); + } else { + startParse(); + } } void PegMarkdownHighlighter::handleParseResult(const QSharedPointer &p_result) @@ -360,14 +384,22 @@ void PegMarkdownHighlighter::handleParseResult(const QSharedPointerm_blocksHighlights); - updateCodeBlocks(m_result); + bool matched = m_result->matched(m_timeStamp); + if (matched) { + clearAllBlocksUserDataAndState(m_result); - rehighlight(); + updateAllBlocksUserState(m_result); - completeHighlight(m_result); + updateCodeBlocks(m_result); + } + + rehighlightBlocks(); + + if (matched) { + completeHighlight(m_result); + } } void PegMarkdownHighlighter::updateSingleFormatBlocks(const QVector> &p_highlights) @@ -386,13 +418,11 @@ void PegMarkdownHighlighter::updateSingleFormatBlocks(const QVector p_result) +void PegMarkdownHighlighter::updateCodeBlocks(const QSharedPointer &p_result) { - if (!p_result->matched(m_timeStamp)) { - return; - } - - if (g_config->getEnableCodeBlockHighlight()) { + // Only need to receive code block highlights when it is empty. + if (g_config->getEnableCodeBlockHighlight() + && PegMarkdownHighlighter::isEmptyCodeBlockHighlights(p_result->m_codeBlocksHighlights)) { p_result->m_codeBlocksHighlights.resize(p_result->m_numOfBlocks); p_result->m_numOfCodeBlockHighlightsToRecv = p_result->m_codeBlocks.size(); } @@ -400,84 +430,102 @@ void PegMarkdownHighlighter::updateCodeBlocks(QSharedPointerm_timeStamp, p_result->m_codeBlocks); } -void PegMarkdownHighlighter::updateBlockUserData(int p_blockNum, const QString &p_text) +void PegMarkdownHighlighter::clearAllBlocksUserDataAndState(const QSharedPointer &p_result) { - Q_UNUSED(p_text); - VTextBlockData *blockData = currentBlockData(); - if (!blockData) { - blockData = new VTextBlockData(); - setCurrentBlockUserData(blockData); - } else { - blockData->setCodeBlockIndentation(-1); - } + QTextBlock block = m_doc->firstBlock(); + while (block.isValid()) { + clearBlockUserData(p_result, block); - if (blockData->getPreviews().isEmpty()) { - m_possiblePreviewBlocks.remove(p_blockNum); - } else { - m_possiblePreviewBlocks.insert(p_blockNum); + block.setUserState(HighlightBlockState::Normal); + + block = block.next(); } } -void PegMarkdownHighlighter::updateBlockUserState(const QSharedPointer &p_result, - int p_blockNum, - const QString &p_text) +void PegMarkdownHighlighter::clearBlockUserData(const QSharedPointer &p_result, + QTextBlock &p_block) { - // Newly-added block. - if (currentBlockState() == -1) { - setCurrentBlockState(HighlightBlockState::Normal); + Q_UNUSED(p_result); + + int blockNum = p_block.blockNumber(); + + VTextBlockData *blockData = static_cast(p_block.userData()); + if (!blockData) { + blockData = new VTextBlockData(); + p_block.setUserData(blockData); + + m_possiblePreviewBlocks.remove(blockNum); + } else { + blockData->setCodeBlockIndentation(-1); + + if (blockData->getPreviews().isEmpty()) { + m_possiblePreviewBlocks.remove(blockNum); + } else { + m_possiblePreviewBlocks.insert(blockNum); + } } +} - if (!p_result->matched(m_timeStamp)) { - return; - } +void PegMarkdownHighlighter::updateAllBlocksUserState(const QSharedPointer &p_result) +{ + // Code blocks. + bool hlColumn = g_config->getColorColumn() > 0; + const QHash &cbStates = p_result->m_codeBlocksState; + for (auto it = cbStates.begin(); it != cbStates.end(); ++it) { + QTextBlock block = m_doc->findBlockByNumber(it.key()); + if (!block.isValid()) { + continue; + } - HighlightBlockState state = HighlightBlockState::Normal; - - auto it = p_result->m_codeBlocksState.find(p_blockNum); - if (it != p_result->m_codeBlocksState.end()) { - VTextBlockData *blockData = currentBlockData(); - Q_ASSERT(blockData); - - 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(); + if (hlColumn) { + VTextBlockData *blockData = static_cast(block.userData()); + Q_ASSERT(blockData); + + switch (it.value()) { + case HighlightBlockState::CodeBlockStart: + { + int startLeadingSpaces = 0; + QRegExp reg(VUtils::c_fencedCodeBlockStartRegExp); + int idx = reg.indexIn(block.text()); + if (idx >= 0) { + startLeadingSpaces = reg.capturedTexts()[1].size(); + } + + blockData->setCodeBlockIndentation(startLeadingSpaces); + break; } - blockData->setCodeBlockIndentation(startLeadingSpaces); - break; - } + case HighlightBlockState::CodeBlock: + V_FALLTHROUGH; + case HighlightBlockState::CodeBlockEnd: + { + int startLeadingSpaces = 0; + VTextBlockData *preBlockData = previousBlockData(block); + if (preBlockData) { + startLeadingSpaces = preBlockData->getCodeBlockIndentation(); + } - case HighlightBlockState::CodeBlock: - V_FALLTHROUGH; - case HighlightBlockState::CodeBlockEnd: - { - int startLeadingSpaces = 0; - VTextBlockData *preBlockData = previousBlockData(); - if (preBlockData) { - startLeadingSpaces = preBlockData->getCodeBlockIndentation(); + blockData->setCodeBlockIndentation(startLeadingSpaces); + break; } - blockData->setCodeBlockIndentation(startLeadingSpaces); - break; + default: + Q_ASSERT(false); + break; + } } - default: - Q_ASSERT(false); - break; - } - } else if (p_result->m_hruleBlocks.contains(p_blockNum)) { - state = HighlightBlockState::HRule; + block.setUserState(it.value()); } - // Set code block state. - setCurrentBlockState(state); + // HRule blocks. + foreach (int blk, p_result->m_hruleBlocks) { + QTextBlock block = m_doc->findBlockByNumber(blk); + if (block.isValid()) { + block.setUserState(HighlightBlockState::HRule); + } + } } void PegMarkdownHighlighter::highlightCodeBlock(const QSharedPointer &p_result, @@ -552,10 +600,6 @@ void PegMarkdownHighlighter::highlightCodeBlockColorColumn(const QString &p_text void PegMarkdownHighlighter::completeHighlight(QSharedPointer p_result) { - if (!p_result->matched(m_timeStamp)) { - return; - } - if (isMathJaxEnabled()) { emit mathjaxBlocksUpdated(p_result->m_mathjaxBlocks); } @@ -572,7 +616,7 @@ void PegMarkdownHighlighter::getFastParseBlockRange(int p_position, int &p_firstBlock, int &p_lastBlock) const { - const int maxNumOfBlocks = 500; + const int maxNumOfBlocks = 100; int charsChanged = p_charsRemoved + p_charsAdded; QTextBlock firstBlock = m_doc->findBlock(p_position); @@ -673,3 +717,67 @@ void PegMarkdownHighlighter::getFastParseBlockRange(int p_position, p_firstBlock = p_lastBlock = -1; } } + +void PegMarkdownHighlighter::rehighlightSensitiveBlocks() +{ + QTextBlock cb = m_editor->textCursorW().block(); + + int first, last; + m_editor->visibleBlockRange(first, last); + + bool cursorVisible = cb.blockNumber() >= first && cb.blockNumber() <= last; + + // Include extra blocks. + const int nrUpExtra = 5; + const int nrDownExtra = 20; + first = qMax(0, first - nrUpExtra); + last = qMin(m_doc->blockCount() - 1, last + nrDownExtra); + + if (rehighlightBlockRange(first, last)) { + if (cursorVisible) { + m_editor->ensureCursorVisibleW(); + } + } +} + +void PegMarkdownHighlighter::rehighlightBlocks() +{ + if (m_result->m_numOfBlocks <= LARGE_BLOCK_NUMBER) { + rehighlightBlockRange(0, m_result->m_numOfBlocks - 1); + } else { + rehighlightSensitiveBlocks(); + } +} + +bool PegMarkdownHighlighter::rehighlightBlockRange(int p_first, int p_last) +{ + bool highlighted = false; + const QHash &cbStates = m_result->m_codeBlocksState; + + QTextBlock block = m_doc->findBlockByNumber(p_first); + while (block.isValid()) { + int blockNum = block.blockNumber(); + if (blockNum > p_last) { + break; + } + + bool needHL = PegMarkdownHighlighter::blockTimeStamp(block) != m_result->m_timeStamp; + if (!needHL) { + auto it = cbStates.find(blockNum); + if (it != cbStates.end() + && it.value() == HighlightBlockState::CodeBlock + && PegMarkdownHighlighter::blockCodeBlockTimeStamp(block) != m_result->m_codeBlockTimeStamp) { + needHL = true; + } + } + + if (needHL) { + highlighted = true; + rehighlightBlock(block); + } + + block = block.next(); + } + + return highlighted; +} diff --git a/src/pegmarkdownhighlighter.h b/src/pegmarkdownhighlighter.h index d2461a99..b9b19d30 100644 --- a/src/pegmarkdownhighlighter.h +++ b/src/pegmarkdownhighlighter.h @@ -10,12 +10,13 @@ class PegParser; class QTimer; +class VMdEditor; class PegMarkdownHighlighter : public QSyntaxHighlighter { Q_OBJECT public: - explicit PegMarkdownHighlighter(QTextDocument *p_doc = nullptr); + PegMarkdownHighlighter(QTextDocument *p_doc, VMdEditor *p_editor); void init(const QVector &p_styles, const QHash &p_codeBlockStyles, @@ -33,9 +34,6 @@ public: void addPossiblePreviewBlock(int p_blockNumber); - // Parse and only update the highlight results for rehighlight(). - void updateHighlightFast(); - QHash &getCodeBlockStyles(); QVector &getStyles(); @@ -48,6 +46,10 @@ public slots: // Parse and rehighlight immediately. void updateHighlight(); + // Rehighlight sensitive blocks using current parse result, mainly + // visible blocks. + void rehighlightSensitiveBlocks(); + signals: void highlightCompleted(); @@ -76,14 +78,14 @@ private: void startFastParse(int p_position, int p_charsRemoved, int p_charsAdded); - void updateCodeBlocks(QSharedPointer p_result); + void clearAllBlocksUserDataAndState(const QSharedPointer &p_result); - // Set the user data of currentBlock(). - void updateBlockUserData(int p_blockNum, const QString &p_text); + void updateAllBlocksUserState(const QSharedPointer &p_result); - void updateBlockUserState(const QSharedPointer &p_result, - int p_blockNum, - const QString &p_text); + void updateCodeBlocks(const QSharedPointer &p_result); + + void clearBlockUserData(const QSharedPointer &p_result, + QTextBlock &p_block); // Highlight fenced code block according to VCodeBlockHighlightHelper result. void highlightCodeBlock(const QSharedPointer &p_result, @@ -97,6 +99,8 @@ private: VTextBlockData *previousBlockData() const; + VTextBlockData *previousBlockData(const QTextBlock &p_block) const; + void completeHighlight(QSharedPointer p_result); bool isMathJaxEnabled() const; @@ -109,7 +113,7 @@ private: void processFastParseResult(const QSharedPointer &p_result); - void highlightBlockOne(const QVector> &p_highlights, + bool highlightBlockOne(const QVector> &p_highlights, int p_blockNum); // To avoid line height jitter. @@ -119,8 +123,24 @@ private: void updateSingleFormatBlocks(const QVector> &p_highlights); + void rehighlightBlocks(); + + bool rehighlightBlockRange(int p_first, int p_last); + + static bool isEmptyCodeBlockHighlights(const QVector> &p_highlights); + + static TimeStamp blockTimeStamp(const QTextBlock &p_block); + + static void updateBlockTimeStamp(const QTextBlock &p_block, TimeStamp p_ts); + + static TimeStamp blockCodeBlockTimeStamp(const QTextBlock &p_block); + + static void updateBlockCodeBlockTimeStamp(const QTextBlock &p_block, TimeStamp p_ts); + QTextDocument *m_doc; + VMdEditor *m_editor; + TimeStamp m_timeStamp; QVector m_styles; @@ -146,6 +166,8 @@ private: QTimer *m_fastParseTimer; + QTimer *m_rehighlightTimer; + // Blocks have only one format set which occupies the whole block. QSet m_singleFormatBlocks; }; @@ -207,8 +229,75 @@ inline VTextBlockData *PegMarkdownHighlighter::previousBlockData() const return static_cast(block.userData()); } +inline VTextBlockData *PegMarkdownHighlighter::previousBlockData(const QTextBlock &p_block) const +{ + if (!p_block.isValid()) { + return NULL; + } + + QTextBlock block = p_block.previous(); + if (!block.isValid()) { + return NULL; + } + + return static_cast(block.userData()); +} + inline bool PegMarkdownHighlighter::isMathJaxEnabled() const { return m_parserExts & pmh_EXT_MATH; } + +inline TimeStamp PegMarkdownHighlighter::blockTimeStamp(const QTextBlock &p_block) +{ + VTextBlockData *data = static_cast(p_block.userData()); + if (data) { + return data->getTimeStamp(); + } else { + return 0; + } +} + +inline void PegMarkdownHighlighter::updateBlockTimeStamp(const QTextBlock &p_block, TimeStamp p_ts) +{ + VTextBlockData *data = static_cast(p_block.userData()); + if (data) { + data->setTimeStamp(p_ts); + } +} + +inline TimeStamp PegMarkdownHighlighter::blockCodeBlockTimeStamp(const QTextBlock &p_block) +{ + VTextBlockData *data = static_cast(p_block.userData()); + if (data) { + return data->getCodeBlockTimeStamp(); + } else { + return 0; + } +} + +inline void PegMarkdownHighlighter::updateBlockCodeBlockTimeStamp(const QTextBlock &p_block, TimeStamp p_ts) +{ + VTextBlockData *data = static_cast(p_block.userData()); + if (data) { + data->setCodeBlockTimeStamp(p_ts); + } +} + +inline bool PegMarkdownHighlighter::isEmptyCodeBlockHighlights(const QVector> &p_highlights) +{ + if (p_highlights.isEmpty()) { + return true; + } + + bool empty = true; + for (int i = 0; i < p_highlights.size(); ++i) { + if (!p_highlights[i].isEmpty()) { + empty = false; + break; + } + } + + return empty; +} #endif // PEGMARKDOWNHIGHLIGHTER_H diff --git a/src/utils/vutils.cpp b/src/utils/vutils.cpp index 519f3116..987b24fb 100644 --- a/src/utils/vutils.cpp +++ b/src/utils/vutils.cpp @@ -1620,3 +1620,20 @@ bool VUtils::onlyHasImgInHtml(const QString &p_html) QRegExp reg("<(?:p|span|div) "); return !p_html.contains(reg); } + +int VUtils::elapsedTime(bool p_reset) +{ + static QTime tm; + + if (p_reset) { + tm = QTime(); + return 0; + } + + if (tm.isNull()) { + tm.start(); + return 0; + } + + return tm.restart(); +} diff --git a/src/utils/vutils.h b/src/utils/vutils.h index 84f6bb9c..906722ec 100644 --- a/src/utils/vutils.h +++ b/src/utils/vutils.h @@ -54,6 +54,10 @@ class QFormLayout; # define V_FALLTHROUGH while(false){} #endif +#define DETIME() qDebug() << "ELAPSED_TIME" << __func__ << __LINE__ << VUtils::elapsedTime() + +#define RESET_TIME() VUtils::elapsedTime(true) + enum class MessageBoxType { Normal = 0, @@ -350,6 +354,8 @@ public: // Whether @p_html has only content. static bool onlyHasImgInHtml(const QString &p_html); + static int elapsedTime(bool p_reset = false); + // Regular expression for image link. // ![image title]( http://github.com/tamlok/vnote.jpg "alt text" =200x100) // Captured texts (need to be trimmed): diff --git a/src/vconstants.h b/src/vconstants.h index ed564cf5..884a7417 100644 --- a/src/vconstants.h +++ b/src/vconstants.h @@ -114,7 +114,7 @@ enum class PreviewImageSource { Image, CodeBlock, Invalid }; enum HighlightBlockState { - Normal = 0, + Normal = -1, // A fenced code block. CodeBlockStart, diff --git a/src/veditor.h b/src/veditor.h index 37032c68..3888ee4e 100644 --- a/src/veditor.h +++ b/src/veditor.h @@ -199,6 +199,8 @@ public: virtual void zoomOutW(int p_range = 1) = 0; + virtual void ensureCursorVisibleW() = 0; + protected: void init(); diff --git a/src/vmdeditor.cpp b/src/vmdeditor.cpp index eb969ad3..73812de7 100644 --- a/src/vmdeditor.cpp +++ b/src/vmdeditor.cpp @@ -60,7 +60,7 @@ VMdEditor::VMdEditor(VFile *p_file, setReadOnly(true); - m_pegHighlighter = new PegMarkdownHighlighter(document()); + m_pegHighlighter = new PegMarkdownHighlighter(document(), this); m_pegHighlighter->init(g_config->getMdHighlightingStyles(), g_config->getCodeBlockStyles(), g_config->getEnableMathjax(), @@ -182,13 +182,13 @@ void VMdEditor::reloadFile() const QString &content = m_file->getContent(); setPlainText(content); setModified(false); - m_pegHighlighter->updateHighlightFast(); - - m_freshEdit = true; setReadOnly(readonly); - refreshPreview(); + if (!m_freshEdit) { + m_freshEdit = true; + refreshPreview(); + } } bool VMdEditor::scrollToBlock(int p_blockNumber) diff --git a/src/vmdeditor.h b/src/vmdeditor.h index 1ddded16..74e06e15 100644 --- a/src/vmdeditor.h +++ b/src/vmdeditor.h @@ -184,6 +184,11 @@ public: zoomPage(false, p_range); } + void ensureCursorVisibleW() Q_DECL_OVERRIDE + { + ensureCursorVisible(); + } + signals: // Signal when headers change. void headersChanged(const QVector &p_headers); diff --git a/src/vpreviewmanager.cpp b/src/vpreviewmanager.cpp index 5efe5d58..1a658aad 100644 --- a/src/vpreviewmanager.cpp +++ b/src/vpreviewmanager.cpp @@ -381,8 +381,10 @@ void VPreviewManager::updateBlockPreviewInfo(TS p_timeStamp, continue; } - VTextBlockData *blockData = dynamic_cast(block.userData()); - Q_ASSERT(blockData); + VTextBlockData *blockData = static_cast(block.userData()); + if (!blockData) { + continue; + } VPreviewInfo *info = new VPreviewInfo(PreviewSource::ImageLink, p_timeStamp, @@ -425,8 +427,11 @@ void VPreviewManager::updateBlockPreviewInfo(TS p_timeStamp, continue; } - VTextBlockData *blockData = dynamic_cast(block.userData()); - Q_ASSERT(blockData); + VTextBlockData *blockData = static_cast(block.userData()); + if (!blockData) { + continue; + } + VPreviewInfo *info = new VPreviewInfo(p_source, p_timeStamp, img->m_startPos - img->m_blockPos, @@ -476,7 +481,7 @@ void VPreviewManager::clearBlockObsoletePreviewInfo(long long p_timeStamp, continue; } - VTextBlockData *blockData = dynamic_cast(block.userData()); + VTextBlockData *blockData = static_cast(block.userData()); if (!blockData) { continue; } @@ -550,7 +555,7 @@ void VPreviewManager::checkBlocksForObsoletePreview(const QList &p_blocks) continue; } - VTextBlockData *blockData = dynamic_cast(block.userData()); + VTextBlockData *blockData = static_cast(block.userData()); if (!blockData) { continue; } diff --git a/src/vtextblockdata.cpp b/src/vtextblockdata.cpp index 13eb1510..1ba436f0 100644 --- a/src/vtextblockdata.cpp +++ b/src/vtextblockdata.cpp @@ -1,9 +1,9 @@ #include "vtextblockdata.h" -#include - VTextBlockData::VTextBlockData() : QTextBlockUserData(), + m_timeStamp(0), + m_codeBlockTimeStamp(0), m_codeBlockIndentation(-1) { } @@ -90,7 +90,6 @@ bool VTextBlockData::clearObsoletePreview(long long p_timeStamp, PreviewSource p if (ele->m_source == p_source && ele->m_timeStamp != p_timeStamp) { // Remove it. - qDebug() << "clear obsolete preview" << ele->m_imageInfo.toString(); delete ele; it = m_previews.erase(it); deleted = true; diff --git a/src/vtextblockdata.h b/src/vtextblockdata.h index ecbf5e9d..ed8aa820 100644 --- a/src/vtextblockdata.h +++ b/src/vtextblockdata.h @@ -4,6 +4,8 @@ #include #include +#include "vconstants.h" + // Sources of the preview. enum class PreviewSource { @@ -163,10 +165,24 @@ public: void setCodeBlockIndentation(int p_indent); + TimeStamp getTimeStamp() const; + + void setTimeStamp(TimeStamp p_ts); + + TimeStamp getCodeBlockTimeStamp() const; + + void setCodeBlockTimeStamp(TimeStamp p_ts); + private: // Check the order of elements. bool checkOrder() const; + // TimeStamp of the highlight result which has been applied to this block. + TimeStamp m_timeStamp; + + // TimeStamp of the code block highlight result which has been applied to this block. + TimeStamp m_codeBlockTimeStamp; + // Sorted by m_imageInfo.m_startPos, with no two element's position intersected. QVector m_previews; @@ -188,4 +204,24 @@ inline void VTextBlockData::setCodeBlockIndentation(int p_indent) { m_codeBlockIndentation = p_indent; } + +inline TimeStamp VTextBlockData::getTimeStamp() const +{ + return m_timeStamp; +} + +inline void VTextBlockData::setTimeStamp(TimeStamp p_ts) +{ + m_timeStamp = p_ts; +} + +inline TimeStamp VTextBlockData::getCodeBlockTimeStamp() const +{ + return m_codeBlockTimeStamp; +} + +inline void VTextBlockData::setCodeBlockTimeStamp(TimeStamp p_ts) +{ + m_codeBlockTimeStamp = p_ts; +} #endif // VTEXTBLOCKDATA_H