Browse Source

HGMarkdownHighlighter: merge formats when highlighting a block

Le Tan 7 years ago
parent
commit
217eafd91b
2 changed files with 106 additions and 42 deletions
  1. 89 39
      src/hgmarkdownhighlighter.cpp
  2. 17 3
      src/hgmarkdownhighlighter.h

+ 89 - 39
src/hgmarkdownhighlighter.cpp

@@ -122,12 +122,35 @@ void HGMarkdownHighlighter::updateBlockUserData(int p_blockNum, const QString &p
 void HGMarkdownHighlighter::highlightBlock(const QString &text)
 {
     int blockNum = currentBlock().blockNumber();
-    if (m_blockHLResultReady && blockHighlights.size() > blockNum) {
-        const QVector<HLUnit> &units = blockHighlights[blockNum];
-        for (int i = 0; i < units.size(); ++i) {
-            // TODO: merge two format within the same range
-            const HLUnit &unit = units[i];
-            setFormat(unit.start, unit.length, highlightingStyles[unit.styleIndex].format);
+    if (m_blockHLResultReady && m_blockHighlights.size() > blockNum) {
+        // units are sorted by start position and length.
+        const QVector<HLUnit> &units = m_blockHighlights[blockNum];
+        if (!units.isEmpty()) {
+            for (int i = 0; i < units.size(); ++i) {
+                const HLUnit &unit = units[i];
+                if (i == 0) {
+                    // No need to merge format.
+                    setFormat(unit.start,
+                              unit.length,
+                              highlightingStyles[unit.styleIndex].format);
+                } else {
+                    QTextCharFormat newFormat = highlightingStyles[unit.styleIndex].format;
+                    for (int j = i - 1; j >= 0; --j) {
+                        if (units[j].start + units[j].length <= unit.start) {
+                            // It won't affect current unit.
+                            continue;
+                        } else {
+                            // Merge the format.
+                            QTextCharFormat tmpFormat(newFormat);
+                            newFormat = highlightingStyles[units[j].styleIndex].format;
+                            // tmpFormat takes precedence.
+                            newFormat.merge(tmpFormat);
+                        }
+                    }
+
+                    setFormat(unit.start, unit.length, newFormat);
+                }
+            }
         }
     }
 
@@ -158,33 +181,37 @@ void HGMarkdownHighlighter::highlightBlock(const QString &text)
     if (m_codeBlockHighlights.size() > blockNum) {
         const QVector<HLUnitStyle> &units = m_codeBlockHighlights[blockNum];
         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);
+            QVector<QTextCharFormat *> formats(units.size(), NULL);
             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);
+                if (it == m_codeBlockStyles.end()) {
+                    continue;
+                }
 
-                    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;
+                formats[i] = &(*it);
+
+                if (i == 0) {
+                    // No need to merge format.
+                    setFormat(unit.start, unit.length, *it);
+                } else {
+                    QTextCharFormat newFormat = *it;
+                    for (int j = i - 1; j >= 0; --j) {
+                        if (units[j].start + units[j].length <= unit.start) {
+                            // It won't affect current unit.
+                            continue;
+                        } else {
+                            // Merge the format.
+                            if (formats[j]) {
+                                QTextCharFormat tmpFormat(newFormat);
+                                newFormat = *(formats[j]);
+                                // tmpFormat takes precedence.
+                                newFormat.merge(tmpFormat);
+                            }
+                        }
                     }
+
+                    setFormat(unit.start, unit.length, newFormat);
                 }
             }
         }
@@ -196,11 +223,22 @@ exit:
     highlightChanged();
 }
 
+static bool compHLUnit(const HLUnit &p_a, const HLUnit &p_b)
+{
+    if (p_a.start < p_b.start) {
+        return true;
+    } else if (p_a.start == p_b.start) {
+        return p_a.length >= p_b.length;
+    } else {
+        return false;
+    }
+}
+
 void HGMarkdownHighlighter::initBlockHighlightFromResult(int nrBlocks)
 {
-    blockHighlights.resize(nrBlocks);
-    for (int i = 0; i < blockHighlights.size(); ++i) {
-        blockHighlights[i].clear();
+    m_blockHighlights.resize(nrBlocks);
+    for (int i = 0; i < m_blockHighlights.size(); ++i) {
+        m_blockHighlights[i].clear();
     }
 
     if (!result) {
@@ -235,6 +273,13 @@ void HGMarkdownHighlighter::initBlockHighlightFromResult(int nrBlocks)
             elem_cursor = elem_cursor->next;
         }
     }
+
+    // Sort m_blockHighlights.
+    for (int i = 0; i < m_blockHighlights.size(); ++i) {
+        if (m_blockHighlights[i].size() > 1) {
+            std::sort(m_blockHighlights[i].begin(), m_blockHighlights[i].end(), compHLUnit);
+        }
+    }
 }
 
 void HGMarkdownHighlighter::initHtmlCommentRegionsFromResult()
@@ -339,7 +384,8 @@ void HGMarkdownHighlighter::initHeaderRegionsFromResult()
 
             QTextBlock block = document->findBlock(elem->pos);
             if (block.isValid()) {
-                m_headerBlocks.insert(block.blockNumber(), i);
+                // Header element will contain the new line character.
+                m_headerBlocks.insert(block.blockNumber(), HeaderBlockInfo(i, elem->end - elem->pos - 1));
             }
 
             ++idx;
@@ -390,7 +436,7 @@ void HGMarkdownHighlighter::initBlockHighlihgtOne(unsigned long pos,
         }
         unit.styleIndex = styleIndex;
 
-        blockHighlights[i].append(unit);
+        m_blockHighlights[i].append(unit);
     }
 }
 
@@ -663,12 +709,12 @@ bool HGMarkdownHighlighter::updateCodeBlocks()
     }
 }
 
-static bool HLUnitStyleComp(const HLUnitStyle &a, const HLUnitStyle &b)
+static bool compHLUnitStyle(const HLUnitStyle &a, const HLUnitStyle &b)
 {
     if (a.start < b.start) {
         return true;
     } else if (a.start == b.start) {
-        return a.length > b.length;
+        return a.length >= b.length;
     } else {
         return false;
     }
@@ -720,7 +766,10 @@ void HGMarkdownHighlighter::setCodeBlockHighlights(const QVector<HLUnitPos> &p_u
     for (int i = 0; i < highlights.size(); ++i) {
         QVector<HLUnitStyle> &units = highlights[i];
         if (!units.isEmpty()) {
-            std::sort(units.begin(), units.end(), HLUnitStyleComp);
+            if (units.size() > 1) {
+                std::sort(units.begin(), units.end(), compHLUnitStyle);
+            }
+
             m_codeBlockHighlights[i].append(units);
         }
     }
@@ -807,11 +856,12 @@ void HGMarkdownHighlighter::highlightHeaderFast(int p_blockNumber, const QString
 
     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 {
+        const HeaderBlockInfo &info = it.value();
+        if (!isValidHeader(p_text)) {
             // Set an empty format to clear formats. It seems to work.
             setFormat(0, p_text.size(), QTextCharFormat());
+        } else if (info.m_length < p_text.size()) {
+            setFormat(info.m_length, p_text.size() - info.m_length, m_headerStyles[info.m_level]);
         }
     }
 }

+ 17 - 3
src/hgmarkdownhighlighter.h

@@ -163,6 +163,20 @@ private slots:
     void startParseAndHighlight(bool p_fast = false);
 
 private:
+    struct HeaderBlockInfo
+    {
+        HeaderBlockInfo(int p_level = -1, int p_length = 0)
+            : m_level(p_level), m_length(p_length)
+        {
+        }
+
+        // Header level based on 0.
+        int m_level;
+
+        // Block length;
+        int m_length;
+    };
+
     QRegExp codeBlockStartExp;
     QRegExp codeBlockEndExp;
     QTextCharFormat codeBlockFormat;
@@ -176,7 +190,7 @@ private:
 
     QHash<QString, QTextCharFormat> m_codeBlockStyles;
 
-    QVector<QVector<HLUnit> > blockHighlights;
+    QVector<QVector<HLUnit> > m_blockHighlights;
 
     // Used for cache, [0, 6].
     QVector<QTextCharFormat> m_headerStyles;
@@ -200,8 +214,8 @@ private:
     // Sorted by start position.
     QVector<VElementRegion> m_headerRegions;
 
-    // [block number] -> header level based on 0
-    QHash<int, int> m_headerBlocks;
+    // Indexed by block number.
+    QHash<int, HeaderBlockInfo> m_headerBlocks;
 
     // Timer to signal highlightCompleted().
     QTimer *m_completeTimer;