Browse Source

PegMarkdownHighlighter: cache result for performance

Le Tan 7 years ago
parent
commit
bfac189cb7

+ 21 - 1
src/markdownhighlighterdata.h

@@ -4,7 +4,6 @@
 #include <QTextCharFormat>
 
 #include "vconstants.h"
-#include "vtextblockdata.h"
 
 extern "C" {
 #include <pmh_parser.h>
@@ -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.

+ 2 - 0
src/peghighlighterresult.h

@@ -1,6 +1,8 @@
 #ifndef PEGHIGHLIGHTERRESULT_H
 #define PEGHIGHLIGHTERRESULT_H
 
+#include <QSet>
+
 #include "vconstants.h"
 #include "pegparser.h"
 

+ 98 - 22
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();
 
+    VTextBlockData *blockData = PegMarkdownHighlighter::getBlockData(block);
+    QVector<HLUnit> *cache = NULL;
+    QVector<HLUnitStyle> *cbCache = NULL;
+    if (blockData) {
+        cache = &blockData->getBlockHighlightCache();
+        cbCache = &blockData->getCodeBlockHighlightCache();
+
+        cache->clear();
+        cbCache->clear();
+    }
+
+    bool cacheValid = true;
+
     if (result->matched(m_timeStamp)) {
-        preHighlightSingleFormatBlock(result->m_blocksHighlights, blockNum, p_text);
+        if (preHighlightSingleFormatBlock(result->m_blocksHighlights, blockNum, p_text)) {
+            cacheValid = false;
+        }
 
-        highlightBlockOne(result->m_blocksHighlights, blockNum);
+        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<QVector<HLUnit>> &p_highlights,
+bool PegMarkdownHighlighter::preHighlightSingleFormatBlock(const QVector<QVector<HLUnit>> &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<QVector
             const HLUnit &unit = units[0];
             if (unit.start == 0 && (int)unit.length < sz) {
                 setFormat(unit.length, sz - unit.length, m_styles[unit.styleIndex].format);
+                return true;
             }
         }
     }
+
+    return false;
 }
 
 bool PegMarkdownHighlighter::highlightBlockOne(const QVector<QVector<HLUnit>> &p_highlights,
-                                               int p_blockNum)
+                                               int p_blockNum,
+                                               QVector<HLUnit> *p_cache)
 {
     bool highlighted = false;
     if (p_highlights.size() > p_blockNum) {
@@ -169,6 +197,10 @@ bool PegMarkdownHighlighter::highlightBlockOne(const QVector<QVector<HLUnit>> &p
         const QVector<HLUnit> &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 QSharedPointer<PegParseResu
 
     m_result.reset(new PegHighlighterResult(this, p_result));
 
+    m_result->m_codeBlockTimeStamp = nextCodeBlockTimeStamp();
+
     m_singleFormatBlocks.clear();
     updateSingleFormatBlocks(m_result->m_blocksHighlights);
 
@@ -421,10 +455,14 @@ void PegMarkdownHighlighter::updateSingleFormatBlocks(const QVector<QVector<HLUn
 void PegMarkdownHighlighter::updateCodeBlocks(const QSharedPointer<PegHighlighterResult> &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<PegHi
 
 void PegMarkdownHighlighter::highlightCodeBlock(const QSharedPointer<PegHighlighterResult> &p_result,
                                                 int p_blockNum,
-                                                const QString &p_text)
+                                                const QString &p_text,
+                                                QVector<HLUnitStyle> *p_cache)
 {
     // Brush the indentation spaces.
     if (currentBlockState() == HighlightBlockState::CodeBlock) {
@@ -544,6 +583,10 @@ void PegMarkdownHighlighter::highlightCodeBlock(const QSharedPointer<PegHighligh
         const QVector<HLUnitStyle> &units = p_result->m_codeBlocksHighlights[p_blockNum];
         if (!units.isEmpty()) {
             QVector<QTextCharFormat *> 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<int, HighlightBlockState> &cbStates = m_result->m_codeBlocksState;
+    const QVector<QVector<HLUnit>> &hls = m_result->m_blocksHighlights;
+    const QVector<QVector<HLUnitStyle>> &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;
+        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
-                && PegMarkdownHighlighter::blockCodeBlockTimeStamp(block) != m_result->m_codeBlockTimeStamp) {
-                needHL = true;
+            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;
 }

+ 21 - 3
src/pegmarkdownhighlighter.h

@@ -90,7 +90,8 @@ private:
     // Highlight fenced code block according to VCodeBlockHighlightHelper result.
     void highlightCodeBlock(const QSharedPointer<PegHighlighterResult> &p_result,
                             int p_blockNum,
-                            const QString &p_text);
+                            const QString &p_text,
+                            QVector<HLUnitStyle> *p_cache);
 
     // Highlight color column in code block.
     void highlightCodeBlockColorColumn(const QString &p_text);
@@ -114,10 +115,11 @@ private:
     void processFastParseResult(const QSharedPointer<PegParseResult> &p_result);
 
     bool highlightBlockOne(const QVector<QVector<HLUnit>> &p_highlights,
-                           int p_blockNum);
+                           int p_blockNum,
+                           QVector<HLUnit> *p_cache);
 
     // To avoid line height jitter.
-    void preHighlightSingleFormatBlock(const QVector<QVector<HLUnit>> &p_highlights,
+    bool preHighlightSingleFormatBlock(const QVector<QVector<HLUnit>> &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<QVector<HLUnitStyle>> &p_highlights);
 
     static TimeStamp blockTimeStamp(const QTextBlock &p_block);
@@ -143,6 +149,8 @@ private:
 
     TimeStamp m_timeStamp;
 
+    TimeStamp m_codeBlockTimeStamp;
+
     QVector<HighlightingStyle> m_styles;
     QHash<QString, QTextCharFormat> m_codeBlockStyles;
 
@@ -300,4 +308,14 @@ inline bool PegMarkdownHighlighter::isEmptyCodeBlockHighlights(const QVector<QVe
 
     return empty;
 }
+
+inline VTextBlockData *PegMarkdownHighlighter::getBlockData(const QTextBlock &p_block)
+{
+    return static_cast<VTextBlockData *>(p_block.userData());
+}
+
+inline TimeStamp PegMarkdownHighlighter::nextCodeBlockTimeStamp()
+{
+    return ++m_codeBlockTimeStamp;
+}
 #endif // PEGMARKDOWNHIGHLIGHTER_H

+ 1 - 0
src/utils/vutils.cpp

@@ -28,6 +28,7 @@
 #include <QTreeWidgetItem>
 #include <QFormLayout>
 #include <QInputDialog>
+#include <QFontDatabase>
 
 #include "vorphanfile.h"
 #include "vnote.h"

+ 1 - 1
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();
                 }
             });

+ 1 - 0
src/vopenedlistmenu.cpp

@@ -7,6 +7,7 @@
 #include <QString>
 #include <QStyleFactory>
 #include <QWidgetAction>
+#include <QKeyEvent>
 
 #include "veditwindow.h"
 #include "vnotefile.h"

+ 1 - 0
src/vtextblockdata.cpp

@@ -4,6 +4,7 @@ VTextBlockData::VTextBlockData()
     : QTextBlockUserData(),
       m_timeStamp(0),
       m_codeBlockTimeStamp(0),
+      m_cacheValid(false),
       m_codeBlockIndentation(-1)
 {
 }

+ 93 - 0
src/vtextblockdata.h

@@ -3,8 +3,10 @@
 
 #include <QTextBlockUserData>
 #include <QVector>
+#include <QDebug>
 
 #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<HLUnit> &p_highlight) const;
+
+    QVector<HLUnit> &getBlockHighlightCache();
+
+    void setBlockHighlightCache(const QVector<HLUnit> &p_highlight);
+
+    bool isCodeBlockHighlightCacheMatched(const QVector<HLUnitStyle> &p_highlight) const;
+
+    QVector<HLUnitStyle> &getCodeBlockHighlightCache();
+
+    void setCodeBlockHighlightCache(const QVector<HLUnitStyle> &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<HLUnit> m_blockHighlightCache;
+
+    // Code block highlight cache.
+    QVector<HLUnitStyle> 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<VPreviewInfo *> m_previews;
 
@@ -224,4 +251,70 @@ inline void VTextBlockData::setCodeBlockTimeStamp(TimeStamp p_ts)
 {
     m_codeBlockTimeStamp = p_ts;
 }
+
+inline bool VTextBlockData::isBlockHighlightCacheMatched(const QVector<HLUnit> &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<HLUnit> &VTextBlockData::getBlockHighlightCache()
+{
+    return m_blockHighlightCache;
+}
+
+inline void VTextBlockData::setBlockHighlightCache(const QVector<HLUnit> &p_highlight)
+{
+    m_blockHighlightCache = p_highlight;
+}
+
+inline bool VTextBlockData::isCodeBlockHighlightCacheMatched(const QVector<HLUnitStyle> &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<HLUnitStyle> &VTextBlockData::getCodeBlockHighlightCache()
+{
+    return m_codeBlockHighlightCache;
+}
+
+inline void VTextBlockData::setCodeBlockHighlightCache(const QVector<HLUnitStyle> &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

+ 1 - 0
src/vvimcmdlineedit.cpp

@@ -2,6 +2,7 @@
 
 #include <QInputMethod>
 #include <QGuiApplication>
+#include <QKeyEvent>
 
 #include "vpalette.h"