diff --git a/src/markdownhighlighterdata.h b/src/markdownhighlighterdata.h index cac2e2d8..33e21d55 100644 --- a/src/markdownhighlighterdata.h +++ b/src/markdownhighlighterdata.h @@ -4,7 +4,6 @@ #include #include "vconstants.h" -#include "vtextblockdata.h" extern "C" { #include @@ -26,6 +25,18 @@ struct HLUnit unsigned long start; unsigned long length; unsigned int styleIndex; + + bool operator==(const HLUnit &p_a) const + { + return start == p_a.start + && length == p_a.length + && styleIndex == p_a.styleIndex; + } + + QString toString() const + { + return QString("HLUnit %1 %2 %3").arg(start).arg(length).arg(styleIndex); + } }; struct HLUnitStyle @@ -33,6 +44,15 @@ struct HLUnitStyle unsigned long start; unsigned long length; QString style; + + bool operator==(const HLUnitStyle &p_a) const + { + if (start != p_a.start || length != p_a.length) { + return false; + } + + return style == p_a.style; + } }; // Fenced code block only. diff --git a/src/peghighlighterresult.h b/src/peghighlighterresult.h index f3ce2891..0caa7e0c 100644 --- a/src/peghighlighterresult.h +++ b/src/peghighlighterresult.h @@ -1,6 +1,8 @@ #ifndef PEGHIGHLIGHTERRESULT_H #define PEGHIGHLIGHTERRESULT_H +#include + #include "vconstants.h" #include "pegparser.h" diff --git a/src/pegmarkdownhighlighter.cpp b/src/pegmarkdownhighlighter.cpp index 8c829c6e..3ea29231 100644 --- a/src/pegmarkdownhighlighter.cpp +++ b/src/pegmarkdownhighlighter.cpp @@ -12,13 +12,14 @@ extern VConfigManager *g_config; -#define LARGE_BLOCK_NUMBER 2000 +#define LARGE_BLOCK_NUMBER 1000 PegMarkdownHighlighter::PegMarkdownHighlighter(QTextDocument *p_doc, VMdEditor *p_editor) : QSyntaxHighlighter(p_doc), m_doc(p_doc), m_editor(p_editor), m_timeStamp(0), + m_codeBlockTimeStamp(0), m_parser(NULL), m_parserExts(pmh_EXT_NOTES | pmh_EXT_STRIKE | pmh_EXT_FRONTMATTER | pmh_EXT_MARK) { @@ -114,39 +115,62 @@ void PegMarkdownHighlighter::highlightBlock(const QString &p_text) QTextBlock block = currentBlock(); int blockNum = block.blockNumber(); - if (result->matched(m_timeStamp)) { - preHighlightSingleFormatBlock(result->m_blocksHighlights, blockNum, p_text); + VTextBlockData *blockData = PegMarkdownHighlighter::getBlockData(block); + QVector *cache = NULL; + QVector *cbCache = NULL; + if (blockData) { + cache = &blockData->getBlockHighlightCache(); + cbCache = &blockData->getCodeBlockHighlightCache(); - highlightBlockOne(result->m_blocksHighlights, blockNum); + cache->clear(); + cbCache->clear(); + } + + bool cacheValid = true; + + if (result->matched(m_timeStamp)) { + if (preHighlightSingleFormatBlock(result->m_blocksHighlights, blockNum, p_text)) { + cacheValid = false; + } + + highlightBlockOne(result->m_blocksHighlights, blockNum, cacheValid ? cache : NULL); } else { - preHighlightSingleFormatBlock(m_fastResult->m_blocksHighlights, blockNum, p_text); + if (preHighlightSingleFormatBlock(m_fastResult->m_blocksHighlights, blockNum, p_text)) { + cacheValid = false; + } // 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); + if (highlightBlockOne(m_fastResult->m_blocksHighlights, blockNum, NULL)) { + cacheValid = false; + } else { + highlightBlockOne(result->m_blocksHighlights, blockNum, cacheValid ? cache : NULL); } } if (currentBlockState() == HighlightBlockState::CodeBlock) { - highlightCodeBlock(result, blockNum, p_text); + highlightCodeBlock(result, blockNum, p_text, cacheValid ? cbCache : NULL); highlightCodeBlockColorColumn(p_text); PegMarkdownHighlighter::updateBlockCodeBlockTimeStamp(block, result->m_codeBlockTimeStamp); } PegMarkdownHighlighter::updateBlockTimeStamp(block, result->m_timeStamp); + + if (blockData) { + blockData->setCacheValid(cacheValid); + } } -void PegMarkdownHighlighter::preHighlightSingleFormatBlock(const QVector> &p_highlights, +bool PegMarkdownHighlighter::preHighlightSingleFormatBlock(const QVector> &p_highlights, int p_blockNum, const QString &p_text) { int sz = p_text.size(); if (sz == 0) { - return; + return false; } if (!m_singleFormatBlocks.contains(p_blockNum)) { - return; + return false; } if (p_highlights.size() > p_blockNum) { @@ -155,13 +179,17 @@ void PegMarkdownHighlighter::preHighlightSingleFormatBlock(const QVector> &p_highlights, - int p_blockNum) + int p_blockNum, + QVector *p_cache) { bool highlighted = false; if (p_highlights.size() > p_blockNum) { @@ -169,6 +197,10 @@ bool PegMarkdownHighlighter::highlightBlockOne(const QVector> &p const QVector &units = p_highlights[p_blockNum]; if (!units.isEmpty()) { highlighted = true; + if (p_cache) { + p_cache->append(units); + } + for (int i = 0; i < units.size(); ++i) { const HLUnit &unit = units[i]; if (i == 0) { @@ -357,7 +389,7 @@ void PegMarkdownHighlighter::setCodeBlockHighlights(TimeStamp p_timeStamp, exit: if (--result->m_numOfCodeBlockHighlightsToRecv <= 0) { - ++result->m_codeBlockTimeStamp; + result->m_codeBlockTimeStamp = nextCodeBlockTimeStamp(); rehighlightBlocks(); } } @@ -383,6 +415,8 @@ void PegMarkdownHighlighter::handleParseResult(const QSharedPointerm_codeBlockTimeStamp = nextCodeBlockTimeStamp(); + m_singleFormatBlocks.clear(); updateSingleFormatBlocks(m_result->m_blocksHighlights); @@ -421,10 +455,14 @@ void PegMarkdownHighlighter::updateSingleFormatBlocks(const QVector &p_result) { // 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(); + if (g_config->getEnableCodeBlockHighlight()) { + int cbSz = p_result->m_codeBlocks.size(); + if (cbSz > 0) { + if (PegMarkdownHighlighter::isEmptyCodeBlockHighlights(p_result->m_codeBlocksHighlights)) { + p_result->m_codeBlocksHighlights.resize(p_result->m_numOfBlocks); + p_result->m_numOfCodeBlockHighlightsToRecv = cbSz; + } + } } emit codeBlocksUpdated(p_result->m_timeStamp, p_result->m_codeBlocks); @@ -530,7 +568,8 @@ void PegMarkdownHighlighter::updateAllBlocksUserState(const QSharedPointer &p_result, int p_blockNum, - const QString &p_text) + const QString &p_text, + QVector *p_cache) { // Brush the indentation spaces. if (currentBlockState() == HighlightBlockState::CodeBlock) { @@ -544,6 +583,10 @@ void PegMarkdownHighlighter::highlightCodeBlock(const QSharedPointer &units = p_result->m_codeBlocksHighlights[p_blockNum]; if (!units.isEmpty()) { QVector formats(units.size(), NULL); + if (p_cache) { + p_cache->append(units); + } + for (int i = 0; i < units.size(); ++i) { const HLUnitStyle &unit = units[i]; auto it = m_codeBlockStyles.find(unit.style); @@ -753,7 +796,10 @@ bool PegMarkdownHighlighter::rehighlightBlockRange(int p_first, int p_last) { bool highlighted = false; const QHash &cbStates = m_result->m_codeBlocksState; + const QVector> &hls = m_result->m_blocksHighlights; + const QVector> &cbHls = m_result->m_codeBlocksHighlights; + int nr = 0; QTextBlock block = m_doc->findBlockByNumber(p_first); while (block.isValid()) { int blockNum = block.blockNumber(); @@ -761,23 +807,53 @@ bool PegMarkdownHighlighter::rehighlightBlockRange(int p_first, int 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; + bool needHL = false; + bool updateTS = false; + VTextBlockData *data = PegMarkdownHighlighter::getBlockData(block); + if (PegMarkdownHighlighter::blockTimeStamp(block) != m_result->m_timeStamp) { + needHL = true; + // Try to find cache. + if (data && blockNum < hls.size()) { + if (data->isBlockHighlightCacheMatched(hls[blockNum])) { + needHL = false; + updateTS = true; + } } } + if (!needHL) { + // FIXME: what about a previous code block turn into a non-code block? For now, + // they can be distinguished by block highlights. + auto it = cbStates.find(blockNum); + if (it != cbStates.end() && it.value() == HighlightBlockState::CodeBlock) { + if (PegMarkdownHighlighter::blockCodeBlockTimeStamp(block) != m_result->m_codeBlockTimeStamp) { + needHL = true; + // Try to find cache. + if (updateTS && data && blockNum < cbHls.size()) { + if (data->isCodeBlockHighlightCacheMatched(cbHls[blockNum])) { + needHL = false; + } + } + } + } + } + + if (!needHL && data && !data->getPreviews().isEmpty()) { + needHL = true; + } + if (needHL) { highlighted = true; rehighlightBlock(block); + ++nr; + } else if (updateTS) { + data->setTimeStamp(m_result->m_timeStamp); + data->setCodeBlockTimeStamp(m_result->m_codeBlockTimeStamp); } block = block.next(); } + qDebug() << "rehighlightBlockRange" << p_first << p_last << nr; return highlighted; } diff --git a/src/pegmarkdownhighlighter.h b/src/pegmarkdownhighlighter.h index b9b19d30..bac05844 100644 --- a/src/pegmarkdownhighlighter.h +++ b/src/pegmarkdownhighlighter.h @@ -90,7 +90,8 @@ private: // Highlight fenced code block according to VCodeBlockHighlightHelper result. void highlightCodeBlock(const QSharedPointer &p_result, int p_blockNum, - const QString &p_text); + const QString &p_text, + QVector *p_cache); // Highlight color column in code block. void highlightCodeBlockColorColumn(const QString &p_text); @@ -114,10 +115,11 @@ private: void processFastParseResult(const QSharedPointer &p_result); bool highlightBlockOne(const QVector> &p_highlights, - int p_blockNum); + int p_blockNum, + QVector *p_cache); // To avoid line height jitter. - void preHighlightSingleFormatBlock(const QVector> &p_highlights, + bool preHighlightSingleFormatBlock(const QVector> &p_highlights, int p_blockNum, const QString &p_text); @@ -127,6 +129,10 @@ private: bool rehighlightBlockRange(int p_first, int p_last); + TimeStamp nextCodeBlockTimeStamp(); + + static VTextBlockData *getBlockData(const QTextBlock &p_block); + static bool isEmptyCodeBlockHighlights(const QVector> &p_highlights); static TimeStamp blockTimeStamp(const QTextBlock &p_block); @@ -143,6 +149,8 @@ private: TimeStamp m_timeStamp; + TimeStamp m_codeBlockTimeStamp; + QVector m_styles; QHash m_codeBlockStyles; @@ -300,4 +308,14 @@ inline bool PegMarkdownHighlighter::isEmptyCodeBlockHighlights(const QVector(p_block.userData()); +} + +inline TimeStamp PegMarkdownHighlighter::nextCodeBlockTimeStamp() +{ + return ++m_codeBlockTimeStamp; +} #endif // PEGMARKDOWNHIGHLIGHTER_H diff --git a/src/utils/vutils.cpp b/src/utils/vutils.cpp index 987b24fb..fe265096 100644 --- a/src/utils/vutils.cpp +++ b/src/utils/vutils.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include "vorphanfile.h" #include "vnote.h" diff --git a/src/vmdeditor.cpp b/src/vmdeditor.cpp index 74a883eb..9fcba38e 100644 --- a/src/vmdeditor.cpp +++ b/src/vmdeditor.cpp @@ -55,7 +55,7 @@ VMdEditor::VMdEditor(VFile *p_file, connect(document(), &QTextDocument::contentsChange, this, [this](int p_position, int p_charsRemoved, int p_charsAdded) { Q_UNUSED(p_position); - if (p_charsAdded > 0 || p_charsAdded > 0) { + if (p_charsRemoved > 0 || p_charsAdded > 0) { updateTimeStamp(); } }); diff --git a/src/vopenedlistmenu.cpp b/src/vopenedlistmenu.cpp index 016bf5d1..1257d2c2 100644 --- a/src/vopenedlistmenu.cpp +++ b/src/vopenedlistmenu.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include "veditwindow.h" #include "vnotefile.h" diff --git a/src/vtextblockdata.cpp b/src/vtextblockdata.cpp index 1ba436f0..cc5caf65 100644 --- a/src/vtextblockdata.cpp +++ b/src/vtextblockdata.cpp @@ -4,6 +4,7 @@ VTextBlockData::VTextBlockData() : QTextBlockUserData(), m_timeStamp(0), m_codeBlockTimeStamp(0), + m_cacheValid(false), m_codeBlockIndentation(-1) { } diff --git a/src/vtextblockdata.h b/src/vtextblockdata.h index ed8aa820..ab00e6e4 100644 --- a/src/vtextblockdata.h +++ b/src/vtextblockdata.h @@ -3,8 +3,10 @@ #include #include +#include #include "vconstants.h" +#include "markdownhighlighterdata.h" // Sources of the preview. enum class PreviewSource @@ -173,6 +175,22 @@ public: void setCodeBlockTimeStamp(TimeStamp p_ts); + bool isBlockHighlightCacheMatched(const QVector &p_highlight) const; + + QVector &getBlockHighlightCache(); + + void setBlockHighlightCache(const QVector &p_highlight); + + bool isCodeBlockHighlightCacheMatched(const QVector &p_highlight) const; + + QVector &getCodeBlockHighlightCache(); + + void setCodeBlockHighlightCache(const QVector &p_highlight); + + bool isCacheValid() const; + + void setCacheValid(bool p_valid); + private: // Check the order of elements. bool checkOrder() const; @@ -183,6 +201,15 @@ private: // TimeStamp of the code block highlight result which has been applied to this block. TimeStamp m_codeBlockTimeStamp; + // Block highlight cache. + QVector m_blockHighlightCache; + + // Code block highlight cache. + QVector m_codeBlockHighlightCache; + + // Whether the above two cahces are valid. + bool m_cacheValid; + // Sorted by m_imageInfo.m_startPos, with no two element's position intersected. QVector m_previews; @@ -224,4 +251,70 @@ inline void VTextBlockData::setCodeBlockTimeStamp(TimeStamp p_ts) { m_codeBlockTimeStamp = p_ts; } + +inline bool VTextBlockData::isBlockHighlightCacheMatched(const QVector &p_highlight) const +{ + if (!m_cacheValid + || p_highlight.size() != m_blockHighlightCache.size()) { + return false; + } + + int sz = p_highlight.size(); + for (int i = 0; i < sz; ++i) + { + if (!(p_highlight[i] == m_blockHighlightCache[i])) { + return false; + } + } + + return true; +} + +inline QVector &VTextBlockData::getBlockHighlightCache() +{ + return m_blockHighlightCache; +} + +inline void VTextBlockData::setBlockHighlightCache(const QVector &p_highlight) +{ + m_blockHighlightCache = p_highlight; +} + +inline bool VTextBlockData::isCodeBlockHighlightCacheMatched(const QVector &p_highlight) const +{ + if (!m_cacheValid + || p_highlight.size() != m_codeBlockHighlightCache.size()) { + return false; + } + + int sz = p_highlight.size(); + for (int i = 0; i < sz; ++i) + { + if (!(p_highlight[i] == m_codeBlockHighlightCache[i])) { + return false; + } + } + + return true; +} + +inline QVector &VTextBlockData::getCodeBlockHighlightCache() +{ + return m_codeBlockHighlightCache; +} + +inline void VTextBlockData::setCodeBlockHighlightCache(const QVector &p_highlight) +{ + m_codeBlockHighlightCache = p_highlight; +} + +inline bool VTextBlockData::isCacheValid() const +{ + return m_cacheValid; +} + +inline void VTextBlockData::setCacheValid(bool p_valid) +{ + m_cacheValid = p_valid; +} #endif // VTEXTBLOCKDATA_H diff --git a/src/vvimcmdlineedit.cpp b/src/vvimcmdlineedit.cpp index 99e67100..8bda06ec 100644 --- a/src/vvimcmdlineedit.cpp +++ b/src/vvimcmdlineedit.cpp @@ -2,6 +2,7 @@ #include #include +#include #include "vpalette.h"