Browse Source

HGMarkdownHighlighter: use cached header info to highlight headers fast

Le Tan 8 years ago
parent
commit
b1f7760ea2
2 changed files with 95 additions and 27 deletions
  1. 83 27
      src/hgmarkdownhighlighter.cpp
  2. 12 0
      src/hgmarkdownhighlighter.h

+ 83 - 27
src/hgmarkdownhighlighter.cpp

@@ -58,6 +58,13 @@ HGMarkdownHighlighter::HGMarkdownHighlighter(const QVector<HighlightingStyle> &s
     m_colorColumnFormat.setForeground(QColor(g_config->getEditorColorColumnFg()));
     m_colorColumnFormat.setBackground(QColor(g_config->getEditorColorColumnBg()));
 
+    m_headerStyles.resize(6);
+    for (auto const & it : highlightingStyles) {
+        if (it.type >= pmh_H1 && it.type <= pmh_H6) {
+            m_headerStyles[it.type - pmh_H1] = it.format;
+        }
+    }
+
     resizeBuffer(initCapacity);
     document = parent;
 
@@ -145,35 +152,39 @@ void HGMarkdownHighlighter::highlightBlock(const QString &text)
     // fix this.
     // highlightLinkWithSpacesInURL(text);
 
+    highlightHeaderFast(blockNum, text);
+
     // Highlight CodeBlock using VCodeBlockHighlightHelper.
     if (m_codeBlockHighlights.size() > blockNum) {
         const QVector<HLUnitStyle> &units = m_codeBlockHighlights[blockNum];
-        // Manually simply merge the format of all the units within the same block.
-        // Using QTextCursor to get the char format after setFormat() seems
-        // not to work.
-        QVector<QTextCharFormat> formats;
-        formats.reserve(units.size());
-        // formatIndex[i] is the index in @formats which is the format of the
-        // ith character.
-        QVector<int> formatIndex(currentBlock().length(), -1);
-        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()) {
-                QTextCharFormat newFormat;
-                if (unit.start < (unsigned int)formatIndex.size() && formatIndex[unit.start] != -1) {
-                    newFormat = formats[formatIndex[unit.start]];
-                    newFormat.merge(*it);
-                } else {
-                    newFormat = *it;
-                }
-                setFormat(unit.start, unit.length, newFormat);
-
-                formats.append(newFormat);
-                int idx = formats.size() - 1;
-                unsigned int endIdx = unit.length + unit.start;
-                for (unsigned int i = unit.start; i < endIdx && i < (unsigned int)formatIndex.size(); ++i) {
-                    formatIndex[i] = idx;
+        if (!units.isEmpty()) {
+            // Manually simply merge the format of all the units within the same block.
+            // Using QTextCursor to get the char format after setFormat() seems
+            // not to work.
+            QVector<QTextCharFormat> formats;
+            formats.reserve(units.size());
+            // formatIndex[i] is the index in @formats which is the format of the
+            // ith character.
+            QVector<int> formatIndex(currentBlock().length(), -1);
+            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()) {
+                    QTextCharFormat newFormat;
+                    if (unit.start < (unsigned int)formatIndex.size() && formatIndex[unit.start] != -1) {
+                        newFormat = formats[formatIndex[unit.start]];
+                        newFormat.merge(*it);
+                    } else {
+                        newFormat = *it;
+                    }
+                    setFormat(unit.start, unit.length, newFormat);
+
+                    formats.append(newFormat);
+                    int idx = formats.size() - 1;
+                    unsigned int endIdx = unit.length + unit.start;
+                    for (unsigned int i = unit.start; i < endIdx && i < (unsigned int)formatIndex.size(); ++i) {
+                        formatIndex[i] = idx;
+                    }
                 }
             }
         }
@@ -294,6 +305,8 @@ void HGMarkdownHighlighter::initImageRegionsFromResult()
 
 void HGMarkdownHighlighter::initHeaderRegionsFromResult()
 {
+    m_headerBlocks.clear();
+
     if (!result) {
         // From Qt5.7, the capacity is preserved.
         m_headerRegions.clear();
@@ -324,6 +337,11 @@ void HGMarkdownHighlighter::initHeaderRegionsFromResult()
                 m_headerRegions.push_back(VElementRegion(elem->pos, elem->end));
             }
 
+            QTextBlock block = document->findBlock(elem->pos);
+            if (block.isValid()) {
+                m_headerBlocks.insert(block.blockNumber(), i);
+            }
+
             ++idx;
             elem = elem->next;
         }
@@ -747,7 +765,28 @@ bool HGMarkdownHighlighter::isValidHeader(unsigned long p_pos, unsigned long p_e
     for (unsigned long i = p_pos; i < p_end; ++i) {
         QChar ch = document->characterAt(i);
         if (ch.isSpace()) {
-            return true;
+            return nrNumberSign > 0;
+        } else if (ch == QChar('#')) {
+            if (++nrNumberSign > 6) {
+                return false;
+            }
+        } else {
+            return false;
+        }
+    }
+
+    return false;
+}
+
+bool HGMarkdownHighlighter::isValidHeader(const QString &p_text)
+{
+    // There must exist spaces after #s.
+    // No more than 6 #s.
+    int nrNumberSign = 0;
+    for (int i = 0; i < p_text.size(); ++i) {
+        QChar ch = p_text[i];
+        if (ch.isSpace()) {
+            return nrNumberSign > 0;
         } else if (ch == QChar('#')) {
             if (++nrNumberSign > 6) {
                 return false;
@@ -759,3 +798,20 @@ bool HGMarkdownHighlighter::isValidHeader(unsigned long p_pos, unsigned long p_e
 
     return false;
 }
+
+void HGMarkdownHighlighter::highlightHeaderFast(int p_blockNumber, const QString &p_text)
+{
+    if (currentBlockState() != HighlightBlockState::Normal) {
+        return;
+    }
+
+    auto it = m_headerBlocks.find(p_blockNumber);
+    if (it != m_headerBlocks.end()) {
+        if (isValidHeader(p_text)) {
+            setFormat(0, p_text.size(), m_headerStyles[it.value()]);
+        } else {
+            // Set an empty format to clear formats. It seems to work.
+            setFormat(0, p_text.size(), QTextCharFormat());
+        }
+    }
+}

+ 12 - 0
src/hgmarkdownhighlighter.h

@@ -178,6 +178,9 @@ private:
 
     QVector<QVector<HLUnit> > blockHighlights;
 
+    // Used for cache, [0, 6].
+    QVector<QTextCharFormat> m_headerStyles;
+
     // Use another member to store the codeblocks highlights, because the highlight
     // sequence is blockHighlights, regular-expression-based highlihgts, and then
     // codeBlockHighlights.
@@ -197,6 +200,9 @@ private:
     // Sorted by start position.
     QVector<VElementRegion> m_headerRegions;
 
+    // [block number] -> header level based on 0
+    QHash<int, int> m_headerBlocks;
+
     // Timer to signal highlightCompleted().
     QTimer *m_completeTimer;
 
@@ -265,9 +271,15 @@ private:
     // Check if [p_pos, p_end) is a valid header.
     bool isValidHeader(unsigned long p_pos, unsigned long p_end);
 
+    bool isValidHeader(const QString &p_text);
+
     VTextBlockData *currentBlockData() const;
 
     VTextBlockData *previousBlockData() const;
+
+    // Highlight headers using regular expression first instead of waiting for
+    // another parse.
+    void highlightHeaderFast(int p_blockNumber, const QString &p_text);
 };
 
 inline const QVector<VElementRegion> &HGMarkdownHighlighter::getHeaderRegions() const