浏览代码

PegMarkdownHighlighter: multi-threads highlighter support

Le Tan 7 年之前
父节点
当前提交
bb308a06d1

+ 3 - 2
src/hgmarkdownhighlighter.cpp

@@ -29,6 +29,7 @@ HGMarkdownHighlighter::HGMarkdownHighlighter(const QVector<HighlightingStyle> &s
                                              int waitInterval,
                                              QTextDocument *parent)
     : QSyntaxHighlighter(parent),
+      m_timeStamp(0),
       highlightingStyles(styles),
       m_codeBlockStyles(codeBlockStyles),
       m_numOfCodeBlockHighlightsToRecv(0),
@@ -864,9 +865,9 @@ void HGMarkdownHighlighter::handleContentChange(int /* position */, int charsRem
         return;
     }
 
-    m_signalOut = false;
+    ++m_timeStamp;
 
-    timer->stop();
+    m_signalOut = false;
     timer->start();
 }
 

+ 3 - 171
src/hgmarkdownhighlighter.h

@@ -1,7 +1,6 @@
 #ifndef HGMARKDOWNHIGHLIGHTER_H
 #define HGMARKDOWNHIGHLIGHTER_H
 
-#include <QTextCharFormat>
 #include <QSyntaxHighlighter>
 #include <QAtomicInt>
 #include <QMap>
@@ -9,177 +8,10 @@
 #include <QString>
 
 #include "vtextblockdata.h"
-#include "vconstants.h"
+#include "markdownhighlighterdata.h"
 
-extern "C" {
-#include <pmh_parser.h>
-}
-
-QT_BEGIN_NAMESPACE
 class QTextDocument;
-QT_END_NAMESPACE
-
-struct HighlightingStyle
-{
-    pmh_element_type type;
-    QTextCharFormat format;
-};
-
-// One continuous region for a certain markdown highlight style
-// within a QTextBlock.
-// Pay attention to the change of HighlightingStyles[]
-struct HLUnit
-{
-    // Highlight offset @start and @length with style HighlightingStyles[styleIndex]
-    // within a QTextBlock
-    unsigned long start;
-    unsigned long length;
-    unsigned int styleIndex;
-};
-
-struct HLUnitStyle
-{
-    unsigned long start;
-    unsigned long length;
-    QString style;
-};
-
-// Fenced code block only.
-struct VCodeBlock
-{
-    // Global position of the start.
-    int m_startPos;
-
-    int m_startBlock;
-    int m_endBlock;
-
-    QString m_lang;
-
-    QString m_text;
-
-    bool equalContent(const VCodeBlock &p_block) const
-    {
-        return p_block.m_lang == m_lang && p_block.m_text == m_text;
-    }
-
-    void updateNonContent(const VCodeBlock &p_block)
-    {
-        m_startPos = p_block.m_startPos;
-        m_startBlock = p_block.m_startBlock;
-        m_endBlock = p_block.m_endBlock;
-    }
-};
-
-
-struct VMathjaxBlock
-{
-    VMathjaxBlock()
-        : m_blockNumber(-1),
-          m_previewedAsBlock(false),
-          m_index(-1),
-          m_length(-1)
-    {
-    }
-
-    VMathjaxBlock(int p_blockNumber, const MathjaxInfo &p_info)
-        : m_blockNumber(p_blockNumber),
-          m_previewedAsBlock(p_info.m_previewedAsBlock),
-          m_index(p_info.m_index),
-          m_length(p_info.m_length),
-          m_text(p_info.m_text)
-    {
-    }
-
-    bool equalContent(const VMathjaxBlock &p_block) const
-    {
-        return m_text == p_block.m_text;
-    }
-
-    void updateNonContent(const VMathjaxBlock &p_block)
-    {
-        m_blockNumber = p_block.m_blockNumber;
-        m_previewedAsBlock = p_block.m_previewedAsBlock;
-        m_index = p_block.m_index;
-        m_length = p_block.m_length;
-    }
-
-    int m_blockNumber;
 
-    bool m_previewedAsBlock;
-
-    // Start index within the block.
-    int m_index;
-
-    int m_length;
-
-    QString m_text;
-};
-
-
-// Highlight unit with global position and string style name.
-struct HLUnitPos
-{
-    HLUnitPos() : m_position(-1), m_length(-1)
-    {
-    }
-
-    HLUnitPos(int p_position, int p_length, const QString &p_style)
-        : m_position(p_position), m_length(p_length), m_style(p_style)
-    {
-    }
-
-    int m_position;
-    int m_length;
-    QString m_style;
-};
-
-// Denote the region of a certain Markdown element.
-struct VElementRegion
-{
-    VElementRegion() : m_startPos(0), m_endPos(0) {}
-
-    VElementRegion(int p_start, int p_end) : m_startPos(p_start), m_endPos(p_end) {}
-
-    // The start position of the region in document.
-    int m_startPos;
-
-    // The end position of the region in document.
-    int m_endPos;
-
-    // Whether this region contains @p_pos.
-    bool contains(int p_pos) const
-    {
-        return m_startPos <= p_pos && m_endPos > p_pos;
-    }
-
-    bool intersect(int p_start, int p_end) const
-    {
-        return !(p_end <= m_startPos || p_start >= m_endPos);
-    }
-
-    bool operator==(const VElementRegion &p_other) const
-    {
-        return (m_startPos == p_other.m_startPos
-                && m_endPos == p_other.m_endPos);
-    }
-
-    bool operator<(const VElementRegion &p_other) const
-    {
-        if (m_startPos < p_other.m_startPos) {
-            return true;
-        } else if (m_startPos == p_other.m_startPos) {
-            // If a < b is true, then b < a must be false.
-            return m_endPos < p_other.m_endPos;
-        } else {
-            return false;
-        }
-    }
-
-    QString toString() const
-    {
-        return QString("[%1,%2)").arg(m_startPos).arg(m_endPos);
-    }
-};
 
 class HGMarkdownHighlighter : public QSyntaxHighlighter
 {
@@ -195,8 +27,6 @@ public:
     // Request to update highlihgt (re-parse and re-highlight)
     void setCodeBlockHighlights(const QVector<HLUnitPos> &p_units);
 
-    const QMap<int, bool> &getPotentialPreviewBlocks() const;
-
     const QVector<VElementRegion> &getHeaderRegions() const;
 
     const QSet<int> &getPossiblePreviewBlocks() const;
@@ -259,6 +89,8 @@ private:
         int m_length;
     };
 
+    TimeStamp m_timeStamp;
+
     QRegExp codeBlockStartExp;
     QRegExp codeBlockEndExp;
 

+ 178 - 0
src/markdownhighlighterdata.h

@@ -1,4 +1,182 @@
 #ifndef MARKDOWNHIGHLIGHTERDATA_H
 #define MARKDOWNHIGHLIGHTERDATA_H
 
+#include <QTextCharFormat>
+
+#include "vconstants.h"
+#include "vtextblockdata.h"
+
+extern "C" {
+#include <pmh_parser.h>
+}
+
+struct HighlightingStyle
+{
+    pmh_element_type type;
+    QTextCharFormat format;
+};
+
+// One continuous region for a certain markdown highlight style
+// within a QTextBlock.
+// Pay attention to the change of HighlightingStyles[]
+struct HLUnit
+{
+    // Highlight offset @start and @length with style HighlightingStyles[styleIndex]
+    // within a QTextBlock
+    unsigned long start;
+    unsigned long length;
+    unsigned int styleIndex;
+};
+
+struct HLUnitStyle
+{
+    unsigned long start;
+    unsigned long length;
+    QString style;
+};
+
+// Fenced code block only.
+struct VCodeBlock
+{
+    // Global position of the start.
+    int m_startPos;
+
+    int m_startBlock;
+    int m_endBlock;
+
+    QString m_lang;
+
+    QString m_text;
+
+    bool equalContent(const VCodeBlock &p_block) const
+    {
+        return p_block.m_lang == m_lang && p_block.m_text == m_text;
+    }
+
+    void updateNonContent(const VCodeBlock &p_block)
+    {
+        m_startPos = p_block.m_startPos;
+        m_startBlock = p_block.m_startBlock;
+        m_endBlock = p_block.m_endBlock;
+    }
+};
+
+
+struct VMathjaxBlock
+{
+    VMathjaxBlock()
+        : m_blockNumber(-1),
+          m_previewedAsBlock(false),
+          m_index(-1),
+          m_length(-1)
+    {
+    }
+
+    VMathjaxBlock(int p_blockNumber, const MathjaxInfo &p_info)
+        : m_blockNumber(p_blockNumber),
+          m_previewedAsBlock(p_info.m_previewedAsBlock),
+          m_index(p_info.m_index),
+          m_length(p_info.m_length),
+          m_text(p_info.m_text)
+    {
+    }
+
+    bool equalContent(const VMathjaxBlock &p_block) const
+    {
+        return m_text == p_block.m_text;
+    }
+
+    void updateNonContent(const VMathjaxBlock &p_block)
+    {
+        m_blockNumber = p_block.m_blockNumber;
+        m_previewedAsBlock = p_block.m_previewedAsBlock;
+        m_index = p_block.m_index;
+        m_length = p_block.m_length;
+    }
+
+    int m_blockNumber;
+
+    bool m_previewedAsBlock;
+
+    // Start index within the block.
+    int m_index;
+
+    int m_length;
+
+    QString m_text;
+};
+
+
+// Highlight unit with global position and string style name.
+struct HLUnitPos
+{
+    HLUnitPos() : m_position(-1), m_length(-1)
+    {
+    }
+
+    HLUnitPos(int p_position, int p_length, const QString &p_style)
+        : m_position(p_position), m_length(p_length), m_style(p_style)
+    {
+    }
+
+    int m_position;
+    int m_length;
+    QString m_style;
+};
+
+// Denote the region of a certain Markdown element.
+struct VElementRegion
+{
+    VElementRegion() : m_startPos(0), m_endPos(0) {}
+
+    VElementRegion(int p_start, int p_end) : m_startPos(p_start), m_endPos(p_end) {}
+
+    // The start position of the region in document.
+    int m_startPos;
+
+    // The end position of the region in document.
+    int m_endPos;
+
+    // Whether this region contains @p_pos.
+    bool contains(int p_pos) const
+    {
+        return m_startPos <= p_pos && m_endPos > p_pos;
+    }
+
+    bool intersect(int p_start, int p_end) const
+    {
+        return !(p_end <= m_startPos || p_start >= m_endPos);
+    }
+
+    bool operator==(const VElementRegion &p_other) const
+    {
+        return (m_startPos == p_other.m_startPos
+                && m_endPos == p_other.m_endPos);
+    }
+
+    bool operator<(const VElementRegion &p_other) const
+    {
+        if (m_startPos < p_other.m_startPos) {
+            return true;
+        } else if (m_startPos == p_other.m_startPos) {
+            // If a < b is true, then b < a must be false.
+            return m_endPos < p_other.m_endPos;
+        } else {
+            return false;
+        }
+    }
+
+    QString toString() const
+    {
+        return QString("[%1,%2)").arg(m_startPos).arg(m_endPos);
+    }
+};
+
+struct PegHighlightResult
+{
+    TimeStamp m_timeStamp;
+
+    QVector<QVector<HLUnit> > m_blockHighlights;
+};
+
 #endif // MARKDOWNHIGHLIGHTERDATA_H

+ 223 - 0
src/peghighlighterresult.cpp

@@ -0,0 +1,223 @@
+#include "peghighlighterresult.h"
+
+#include <QTextDocument>
+#include <QTextBlock>
+
+#include "pegmarkdownhighlighter.h"
+#include "utils/vutils.h"
+
+PegHighlighterResult::PegHighlighterResult()
+    : m_timeStamp(0),
+      m_numOfBlocks(0),
+      m_numOfCodeBlockHighlightsToRecv(0)
+{
+    m_codeBlockStartExp = QRegExp(VUtils::c_fencedCodeBlockStartRegExp);
+    m_codeBlockEndExp = QRegExp(VUtils::c_fencedCodeBlockEndRegExp);
+}
+
+PegHighlighterResult::PegHighlighterResult(const PegMarkdownHighlighter *p_peg,
+                                           const QSharedPointer<PegParseResult> &p_result)
+    : m_timeStamp(p_result->m_timeStamp),
+      m_numOfBlocks(p_result->m_numOfBlocks),
+      m_numOfCodeBlockHighlightsToRecv(0)
+{
+    m_codeBlockStartExp = QRegExp(VUtils::c_fencedCodeBlockStartRegExp);
+    m_codeBlockEndExp = QRegExp(VUtils::c_fencedCodeBlockEndRegExp);
+
+    parseBlocksHighlights(p_peg, p_result);
+
+    // Implicit sharing.
+    m_imageRegions = p_result->m_imageRegions;
+    m_headerRegions = p_result->m_headerRegions;
+
+    parseFencedCodeBlocks(p_peg, p_result);
+}
+
+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 PegHighlighterResult::parseBlocksHighlights(const PegMarkdownHighlighter *p_peg,
+                                                 const QSharedPointer<PegParseResult> &p_result)
+{
+    m_blocksHighlights.resize(m_numOfBlocks);
+    if (p_result->isEmpty()) {
+        return;
+    }
+
+    const QTextDocument *doc = p_peg->getDocument();
+    const QVector<HighlightingStyle> &styles = p_peg->getStyles();
+    auto pmhResult = p_result->m_pmhElements;
+    for (int i = 0; i < styles.size(); i++)
+    {
+        const HighlightingStyle &style = styles[i];
+        pmh_element *elem_cursor = pmhResult[style.type];
+        while (elem_cursor != NULL)
+        {
+            // elem_cursor->pos and elem_cursor->end is the start
+            // and end position of the element in document.
+            if (elem_cursor->end <= elem_cursor->pos) {
+                elem_cursor = elem_cursor->next;
+                continue;
+            }
+
+            parseBlocksHighlightOne(doc, elem_cursor->pos, elem_cursor->end, i);
+            elem_cursor = elem_cursor->next;
+        }
+    }
+
+    // Sort m_blocksHighlights.
+    for (int i = 0; i < m_blocksHighlights.size(); ++i) {
+        if (m_blocksHighlights[i].size() > 1) {
+            std::sort(m_blocksHighlights[i].begin(), m_blocksHighlights[i].end(), compHLUnit);
+        }
+    }
+}
+
+void PegHighlighterResult::parseBlocksHighlightOne(const QTextDocument *p_doc,
+                                                   unsigned long p_pos,
+                                                   unsigned long p_end,
+                                                   int p_styleIndex)
+{
+    // When the the highlight element is at the end of document, @p_end will equals
+    // to the characterCount.
+    unsigned int nrChar = (unsigned int)p_doc->characterCount();
+    if (p_end >= nrChar && nrChar > 0) {
+        p_end = nrChar - 1;
+    }
+
+    QTextBlock block = p_doc->findBlock(p_pos);
+    int startBlockNum = block.blockNumber();
+    int endBlockNum = p_doc->findBlock(p_end).blockNumber();
+    while (block.isValid())
+    {
+        int blockNum = block.blockNumber();
+        if (blockNum > endBlockNum) {
+            break;
+        }
+
+        int blockStartPos = block.position();
+        HLUnit unit;
+        if (blockNum == startBlockNum) {
+            unit.start = p_pos - blockStartPos;
+            unit.length = (startBlockNum == endBlockNum) ?
+                          (p_end - p_pos) : (block.length() - unit.start);
+        } else if (blockNum == endBlockNum) {
+            unit.start = 0;
+            unit.length = p_end - blockStartPos;
+        } else {
+            unit.start = 0;
+            unit.length = block.length();
+        }
+        unit.styleIndex = p_styleIndex;
+
+        m_blocksHighlights[blockNum].append(unit);
+
+        block = block.next();
+    }
+}
+
+void PegHighlighterResult::parseBlocksElementRegionOne(QHash<int, QVector<VElementRegion>> &p_regs,
+                                                       const QTextDocument *p_doc,
+                                                       unsigned long p_pos,
+                                                       unsigned long p_end)
+{
+    // When the the highlight element is at the end of document, @p_end will equals
+    // to the characterCount.
+    unsigned int nrChar = (unsigned int)p_doc->characterCount();
+    if (p_end >= nrChar && nrChar > 0) {
+        p_end = nrChar - 1;
+    }
+
+    QTextBlock block = p_doc->findBlock(p_pos);
+    int startBlockNum = block.blockNumber();
+    int endBlockNum = p_doc->findBlock(p_end).blockNumber();
+    while (block.isValid())
+    {
+        int blockNum = block.blockNumber();
+        if (blockNum > endBlockNum) {
+            break;
+        }
+
+        int blockStartPos = block.position();
+        QVector<VElementRegion> &regs = p_regs[blockNum];
+        int start, end;
+        if (blockNum == startBlockNum) {
+            start = p_pos - blockStartPos;
+            end = (startBlockNum == endBlockNum) ? (p_end - blockStartPos)
+                                                 : block.length();
+        } else if (blockNum == endBlockNum) {
+            start = 0;
+            end = p_end - blockStartPos;
+        } else {
+            start = 0;
+            end = block.length();
+        }
+
+        regs.append(VElementRegion(start, end));
+    }
+}
+
+void PegHighlighterResult::parseFencedCodeBlocks(const PegMarkdownHighlighter *p_peg,
+                                                 const QSharedPointer<PegParseResult> &p_result)
+{
+    const QMap<int, VElementRegion> &regs = p_result->m_codeBlockRegions;
+
+    const QTextDocument *doc = p_peg->getDocument();
+    VCodeBlock item;
+    bool inBlock = false;
+    for (auto it = regs.begin(); it != regs.end(); ++it) {
+        QTextBlock block = doc->findBlock(it.value().m_startPos);
+        int lastBlock = doc->findBlock(it.value().m_endPos - 1).blockNumber();
+
+        while (block.isValid()) {
+            int blockNumber = block.blockNumber();
+            if (blockNumber > lastBlock) {
+                break;
+            }
+
+            HighlightBlockState state = HighlightBlockState::Normal;
+            QString text = block.text();
+            if (inBlock) {
+                item.m_text = item.m_text + "\n" + text;
+                int idx = m_codeBlockEndExp.indexIn(text);
+                if (idx >= 0) {
+                    // End block.
+                    inBlock = false;
+                    state = HighlightBlockState::CodeBlockEnd;
+                    item.m_endBlock = blockNumber;
+                    m_codeBlocks.append(item);
+                } else {
+                    // Within code block.
+                    state = HighlightBlockState::CodeBlock;
+                }
+            } else {
+                int idx = m_codeBlockStartExp.indexIn(text);
+                if (idx >= 0) {
+                    // Start block.
+                    inBlock = true;
+                    state = HighlightBlockState::CodeBlockStart;
+                    item.m_startBlock = blockNumber;
+                    item.m_startPos = block.position();
+                    item.m_text = text;
+                    if (m_codeBlockStartExp.captureCount() == 2) {
+                        item.m_lang = m_codeBlockStartExp.capturedTexts()[2];
+                    }
+                }
+            }
+
+            if (state != HighlightBlockState::Normal) {
+                m_codeBlocksState.insert(blockNumber, state);
+            }
+
+            block = block.next();
+        }
+    }
+}

+ 75 - 0
src/peghighlighterresult.h

@@ -0,0 +1,75 @@
+#ifndef PEGHIGHLIGHTERRESULT_H
+#define PEGHIGHLIGHTERRESULT_H
+
+#include "vconstants.h"
+#include "pegparser.h"
+
+class PegMarkdownHighlighter;
+class QTextDocument;
+
+class PegHighlighterResult
+{
+public:
+    PegHighlighterResult();
+
+    PegHighlighterResult(const PegMarkdownHighlighter *p_peg,
+                         const QSharedPointer<PegParseResult> &p_result);
+
+    bool matched(TimeStamp p_timeStamp) const;
+
+    TimeStamp m_timeStamp;
+
+    int m_numOfBlocks;
+
+    QVector<QVector<HLUnit>> m_blocksHighlights;
+
+    // Use another member to store the codeblocks highlights, because the highlight
+    // sequence is blockHighlights, regular-expression-based highlihgts, and then
+    // codeBlockHighlights.
+    // Support fenced code block only.
+    QVector<QVector<HLUnitStyle> > m_codeBlocksHighlights;
+
+    // All image link regions.
+    QVector<VElementRegion> m_imageRegions;
+
+    // All header regions.
+    // Sorted by start position.
+    QVector<VElementRegion> m_headerRegions;
+
+    // All fenced code blocks.
+    QVector<VCodeBlock> m_codeBlocks;
+
+    // Indexed by block number.
+    QHash<int, HighlightBlockState> m_codeBlocksState;
+
+    int m_numOfCodeBlockHighlightsToRecv;
+
+private:
+    // Parse highlight elements for all the blocks from parse results.
+    void parseBlocksHighlights(const PegMarkdownHighlighter *p_peg,
+                               const QSharedPointer<PegParseResult> &p_result);
+
+    // Parse highlight elements for blocks from one parse result.
+    void parseBlocksHighlightOne(const QTextDocument *p_doc,
+                                 unsigned long p_pos,
+                                 unsigned long p_end,
+                                 int p_styleIndex);
+
+    // Parse fenced code blocks from parse results.
+    void parseFencedCodeBlocks(const PegMarkdownHighlighter *p_peg,
+                               const QSharedPointer<PegParseResult> &p_result);
+
+    void parseBlocksElementRegionOne(QHash<int, QVector<VElementRegion>> &p_regs,
+                                     const QTextDocument *p_doc,
+                                     unsigned long p_pos,
+                                     unsigned long p_end);
+
+    QRegExp m_codeBlockStartExp;
+    QRegExp m_codeBlockEndExp;
+};
+
+inline bool PegHighlighterResult::matched(TimeStamp p_timeStamp) const
+{
+    return m_timeStamp == p_timeStamp;
+}
+#endif // PEGHIGHLIGHTERRESULT_H

+ 409 - 0
src/pegmarkdownhighlighter.cpp

@@ -0,0 +1,409 @@
+#include "pegmarkdownhighlighter.h"
+
+#include <QTextDocument>
+#include <QTimer>
+#include <QDebug>
+
+#include "pegparser.h"
+#include "vconfigmanager.h"
+#include "utils/vutils.h"
+
+extern VConfigManager *g_config;
+
+PegMarkdownHighlighter::PegMarkdownHighlighter(QTextDocument *p_doc)
+    : QSyntaxHighlighter(p_doc),
+      m_doc(p_doc),
+      m_timeStamp(0),
+      m_parser(NULL),
+      m_parserExts(pmh_EXT_STRIKE | pmh_EXT_FRONTMATTER)
+{
+}
+
+void PegMarkdownHighlighter::init(const QVector<HighlightingStyle> &p_styles,
+                                  const QHash<QString, QTextCharFormat> &p_codeBlockStyles,
+                                  bool p_mathjaxEnabled,
+                                  int p_timerInterval)
+{
+    m_styles = p_styles;
+    m_codeBlockStyles = p_codeBlockStyles;
+
+    m_codeBlockFormat.setForeground(QBrush(Qt::darkYellow));
+    for (int index = 0; index < m_styles.size(); ++index) {
+        switch (m_styles[index].type) {
+        case pmh_FENCEDCODEBLOCK:
+            m_codeBlockFormat = m_styles[index].format;
+            break;
+
+        default:
+            break;
+        }
+    }
+
+    m_colorColumnFormat = m_codeBlockFormat;
+    m_colorColumnFormat.setForeground(QColor(g_config->getEditorColorColumnFg()));
+    m_colorColumnFormat.setBackground(QColor(g_config->getEditorColorColumnBg()));
+
+    m_result.reset(new PegHighlighterResult());
+
+    m_parser = new PegParser(this);
+    connect(m_parser, &PegParser::parseResultReady,
+            this, &PegMarkdownHighlighter::handleParseResult);
+
+    m_timer = new QTimer(this);
+    m_timer->setSingleShot(true);
+    m_timer->setInterval(p_timerInterval);
+    connect(m_timer, &QTimer::timeout,
+            this, [this]() {
+                startParse();
+            });
+
+    connect(m_doc, &QTextDocument::contentsChange,
+            this, &PegMarkdownHighlighter::handleContentsChange);
+}
+
+void PegMarkdownHighlighter::highlightBlock(const QString &p_text)
+{
+    QSharedPointer<PegHighlighterResult> result(m_result);
+
+    int blockNum = currentBlock().blockNumber();
+    if (result->m_blocksHighlights.size() > blockNum) {
+        // units are sorted by start position and length.
+        const QVector<HLUnit> &units = result->m_blocksHighlights[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,
+                              m_styles[unit.styleIndex].format);
+                } else {
+                    QTextCharFormat newFormat = m_styles[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 = m_styles[units[j].styleIndex].format;
+                            // tmpFormat takes precedence.
+                            newFormat.merge(tmpFormat);
+                        }
+                    }
+
+                    setFormat(unit.start, unit.length, newFormat);
+                }
+            }
+        }
+    }
+
+    // Set current block's user data.
+    updateBlockUserData(blockNum, p_text);
+
+    setCurrentBlockState(HighlightBlockState::Normal);
+
+    updateCodeBlockState(result, blockNum, p_text);
+
+    if (currentBlockState() == HighlightBlockState::CodeBlock) {
+        highlightCodeBlock(result, blockNum);
+
+        highlightCodeBlockColorColumn(p_text);
+    }
+}
+
+void PegMarkdownHighlighter::handleContentsChange(int p_position, int p_charsRemoved, int p_charsAdded)
+{
+    Q_UNUSED(p_position);
+
+    if (p_charsRemoved == 0 && p_charsAdded == 0) {
+        return;
+    }
+
+    ++m_timeStamp;
+
+    // We still need a timer to start a complete parse.
+    m_timer->start();
+}
+
+void PegMarkdownHighlighter::startParse()
+{
+    QSharedPointer<PegParseConfig> config(new PegParseConfig());
+    config->m_timeStamp = m_timeStamp;
+    config->m_data = m_doc->toPlainText().toUtf8();
+    config->m_numOfBlocks = m_doc->blockCount();
+    config->m_extensions = m_parserExts;
+
+    m_parser->parseAsync(config);
+}
+
+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;
+    } else {
+        return false;
+    }
+}
+
+void PegMarkdownHighlighter::setCodeBlockHighlights(TimeStamp p_timeStamp,
+                                                    const QVector<HLUnitPos> &p_units)
+{
+    QSharedPointer<PegHighlighterResult> result(m_result);
+
+    if (!result->matched(p_timeStamp)) {
+        return;
+    }
+
+    if (p_units.isEmpty()) {
+        goto exit;
+    }
+
+    {
+    QVector<QVector<HLUnitStyle>> highlights(result->m_codeBlocksHighlights.size());
+    for (auto const &unit : p_units) {
+        int pos = unit.m_position;
+        int end = unit.m_position + unit.m_length;
+        QTextBlock block = m_doc->findBlock(pos);
+        int startBlockNum = block.blockNumber();
+        int endBlockNum = m_doc->findBlock(end).blockNumber();
+
+        // Text has been changed. Abandon the obsolete parsed result.
+        if (startBlockNum == -1 || endBlockNum >= highlights.size()) {
+            goto exit;
+        }
+
+        while (block.isValid()) {
+            int blockNumber = block.blockNumber();
+            if (blockNumber > endBlockNum) {
+                break;
+            }
+
+            int blockStartPos = block.position();
+            HLUnitStyle hl;
+            hl.style = unit.m_style;
+            if (blockNumber == startBlockNum) {
+                hl.start = pos - blockStartPos;
+                hl.length = (startBlockNum == endBlockNum) ?
+                                (end - pos) : (block.length() - hl.start);
+            } else if (blockNumber == endBlockNum) {
+                hl.start = 0;
+                hl.length = end - blockStartPos;
+            } else {
+                hl.start = 0;
+                hl.length = block.length();
+            }
+
+            highlights[blockNumber].append(hl);
+
+            block = block.next();
+        }
+    }
+
+    // Need to highlight in order.
+    for (int i = 0; i < highlights.size(); ++i) {
+        QVector<HLUnitStyle> &units = highlights[i];
+        if (!units.isEmpty()) {
+            if (units.size() > 1) {
+                std::sort(units.begin(), units.end(), compHLUnitStyle);
+            }
+
+            result->m_codeBlocksHighlights[i].append(units);
+        }
+    }
+    }
+
+exit:
+    if (--result->m_numOfCodeBlockHighlightsToRecv <= 0) {
+        rehighlight();
+    }
+}
+
+void PegMarkdownHighlighter::updateHighlightFast()
+{
+    updateHighlight();
+}
+
+void PegMarkdownHighlighter::updateHighlight()
+{
+    m_timer->stop();
+    startParse();
+}
+
+void PegMarkdownHighlighter::handleParseResult(const QSharedPointer<PegParseResult> &p_result)
+{
+    if (!m_result.isNull() && m_result->m_timeStamp > p_result->m_timeStamp) {
+        return;
+    }
+
+    PegHighlighterResult *pegRes = new PegHighlighterResult(this, p_result);
+    m_result.reset(pegRes);
+
+    updateCodeBlocks(m_result);
+
+    // Now we got a new result, rehighlight.
+    rehighlight();
+
+    completeHighlight(m_result);
+}
+
+void PegMarkdownHighlighter::updateCodeBlocks(QSharedPointer<PegHighlighterResult> p_result)
+{
+    if (!p_result->matched(m_timeStamp)) {
+        return;
+    }
+
+    if (g_config->getEnableCodeBlockHighlight()) {
+        p_result->m_codeBlocksHighlights.resize(p_result->m_numOfBlocks);
+        p_result->m_numOfCodeBlockHighlightsToRecv = p_result->m_codeBlocks.size();
+    }
+
+    emit codeBlocksUpdated(p_result->m_timeStamp, p_result->m_codeBlocks);
+}
+
+void PegMarkdownHighlighter::updateBlockUserData(int p_blockNum, const QString &p_text)
+{
+    Q_UNUSED(p_text);
+    VTextBlockData *blockData = currentBlockData();
+    if (!blockData) {
+        blockData = new VTextBlockData();
+        setCurrentBlockUserData(blockData);
+    } else {
+        blockData->setCodeBlockIndentation(-1);
+        blockData->clearMathjax();
+    }
+
+    if (blockData->getPreviews().isEmpty()) {
+        m_possiblePreviewBlocks.remove(p_blockNum);
+    } else {
+        m_possiblePreviewBlocks.insert(p_blockNum);
+    }
+}
+
+void PegMarkdownHighlighter::updateCodeBlockState(const QSharedPointer<PegHighlighterResult> &p_result,
+                                                  int p_blockNum,
+                                                  const QString &p_text)
+{
+    if (!p_result->matched(m_timeStamp)) {
+        return;
+    }
+
+    auto it = p_result->m_codeBlocksState.find(p_blockNum);
+    if (it != p_result->m_codeBlocksState.end()) {
+        VTextBlockData *blockData = currentBlockData();
+        Q_ASSERT(blockData);
+
+        HighlightBlockState 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();
+            }
+
+            blockData->setCodeBlockIndentation(startLeadingSpaces);
+            break;
+        }
+
+        case HighlightBlockState::CodeBlock:
+            V_FALLTHROUGH;
+        case HighlightBlockState::CodeBlockEnd:
+        {
+            int startLeadingSpaces = 0;
+            VTextBlockData *preBlockData = previousBlockData();
+            if (preBlockData) {
+                startLeadingSpaces = preBlockData->getCodeBlockIndentation();
+            }
+
+            blockData->setCodeBlockIndentation(startLeadingSpaces);
+            break;
+        }
+
+        default:
+            Q_ASSERT(false);
+            break;
+        }
+
+        // Set code block state.
+        setCurrentBlockState(state);
+    }
+}
+
+void PegMarkdownHighlighter::highlightCodeBlock(const QSharedPointer<PegHighlighterResult> &p_result,
+                                                int p_blockNum)
+{
+    if (p_result->m_codeBlocksHighlights.size() > p_blockNum) {
+        const QVector<HLUnitStyle> &units = p_result->m_codeBlocksHighlights[p_blockNum];
+        if (!units.isEmpty()) {
+            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()) {
+                    continue;
+                }
+
+                formats[i] = &(*it);
+
+                QTextCharFormat newFormat = m_codeBlockFormat;
+                newFormat.merge(*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);
+            }
+        }
+    }
+}
+
+void PegMarkdownHighlighter::highlightCodeBlockColorColumn(const QString &p_text)
+{
+    int cc = g_config->getColorColumn();
+    if (cc <= 0) {
+        return;
+    }
+
+    VTextBlockData *blockData = currentBlockData();
+    Q_ASSERT(blockData);
+    int indent = blockData->getCodeBlockIndentation();
+    if (indent == -1) {
+        return;
+    }
+
+    cc += indent;
+    if (p_text.size() < cc) {
+        return;
+    }
+
+    setFormat(cc - 1, 1, m_colorColumnFormat);
+}
+
+void PegMarkdownHighlighter::completeHighlight(QSharedPointer<PegHighlighterResult> p_result)
+{
+    if (!p_result->matched(m_timeStamp)) {
+        return;
+    }
+
+    emit imageLinksUpdated(p_result->m_imageRegions);
+    emit headersUpdated(p_result->m_headerRegions);
+
+    emit highlightCompleted();
+}

+ 179 - 0
src/pegmarkdownhighlighter.h

@@ -0,0 +1,179 @@
+#ifndef PEGMARKDOWNHIGHLIGHTER_H
+#define PEGMARKDOWNHIGHLIGHTER_H
+
+#include <QSyntaxHighlighter>
+#include <QTextCharFormat>
+
+#include "vtextblockdata.h"
+#include "markdownhighlighterdata.h"
+#include "peghighlighterresult.h"
+
+class PegParser;
+class QTimer;
+
+class PegMarkdownHighlighter : public QSyntaxHighlighter
+{
+    Q_OBJECT
+public:
+    explicit PegMarkdownHighlighter(QTextDocument *p_doc = nullptr);
+
+    void init(const QVector<HighlightingStyle> &p_styles,
+              const QHash<QString, QTextCharFormat> &p_codeBlockStyles,
+              bool p_mathjaxEnabled,
+              int p_timerInterval);
+
+    // Set code block highlight result by VCodeBlockHighlightHelper.
+    void setCodeBlockHighlights(TimeStamp p_timeStamp, const QVector<HLUnitPos> &p_units);
+
+    const QVector<VElementRegion> &getHeaderRegions() const;
+
+    const QSet<int> &getPossiblePreviewBlocks() const;
+
+    void clearPossiblePreviewBlocks(const QVector<int> &p_blocksToClear);
+
+    void addPossiblePreviewBlock(int p_blockNumber);
+
+    // Parse and only update the highlight results for rehighlight().
+    void updateHighlightFast();
+
+    QHash<QString, QTextCharFormat> &getCodeBlockStyles();
+
+    QVector<HighlightingStyle> &getStyles();
+
+    const QVector<HighlightingStyle> &getStyles() const;
+
+    const QTextDocument *getDocument() const;
+
+public slots:
+    // Parse and rehighlight immediately.
+    void updateHighlight();
+
+signals:
+    void highlightCompleted();
+
+    // QVector is implicitly shared.
+    void codeBlocksUpdated(TimeStamp p_timeStamp, const QVector<VCodeBlock> &p_codeBlocks);
+
+    // Emitted when image regions have been fetched from a new parsing result.
+    void imageLinksUpdated(const QVector<VElementRegion> &p_imageRegions);
+
+    // Emitted when header regions have been fetched from a new parsing result.
+    void headersUpdated(const QVector<VElementRegion> &p_headerRegions);
+
+    // Emitted when Mathjax blocks updated.
+    void mathjaxBlocksUpdated(const QVector<VMathjaxBlock> &p_mathjaxBlocks);
+
+protected:
+    void highlightBlock(const QString &p_text) Q_DECL_OVERRIDE;
+
+private slots:
+    void handleContentsChange(int p_position, int p_charsRemoved, int p_charsAdded);
+
+    void handleParseResult(const QSharedPointer<PegParseResult> &p_result);
+
+private:
+    void startParse();
+
+    void updateCodeBlocks(QSharedPointer<PegHighlighterResult> p_result);
+
+    // Set the user data of currentBlock().
+    void updateBlockUserData(int p_blockNum, const QString &p_text);
+
+    void updateCodeBlockState(const QSharedPointer<PegHighlighterResult> &p_result,
+                              int p_blockNum,
+                              const QString &p_text);
+
+    // Highlight fenced code block according to VCodeBlockHighlightHelper result.
+    void highlightCodeBlock(const QSharedPointer<PegHighlighterResult> &p_result,
+                            int p_blockNum);
+
+    // Highlight color column in code block.
+    void highlightCodeBlockColorColumn(const QString &p_text);
+
+    VTextBlockData *currentBlockData() const;
+
+    VTextBlockData *previousBlockData() const;
+
+    void completeHighlight(QSharedPointer<PegHighlighterResult> p_result);
+
+    QTextDocument *m_doc;
+
+    TimeStamp m_timeStamp;
+
+    QVector<HighlightingStyle> m_styles;
+    QHash<QString, QTextCharFormat> m_codeBlockStyles;
+
+    QTextCharFormat m_codeBlockFormat;
+    QTextCharFormat m_colorColumnFormat;
+
+    PegParser *m_parser;
+
+    QSharedPointer<PegHighlighterResult> m_result;
+
+    // Block number of those blocks which possible contains previewed image.
+    QSet<int> m_possiblePreviewBlocks;
+
+    // Extensions for parser.
+    int m_parserExts;
+
+    // Timer to trigger parse.
+    QTimer *m_timer;
+};
+
+inline const QVector<VElementRegion> &PegMarkdownHighlighter::getHeaderRegions() const
+{
+    return m_result->m_headerRegions;
+}
+
+inline const QSet<int> &PegMarkdownHighlighter::getPossiblePreviewBlocks() const
+{
+    return m_possiblePreviewBlocks;
+}
+
+inline void PegMarkdownHighlighter::clearPossiblePreviewBlocks(const QVector<int> &p_blocksToClear)
+{
+    for (auto i : p_blocksToClear) {
+        m_possiblePreviewBlocks.remove(i);
+    }
+}
+
+inline void PegMarkdownHighlighter::addPossiblePreviewBlock(int p_blockNumber)
+{
+    m_possiblePreviewBlocks.insert(p_blockNumber);
+}
+
+inline QHash<QString, QTextCharFormat> &PegMarkdownHighlighter::getCodeBlockStyles()
+{
+    return m_codeBlockStyles;
+}
+
+inline QVector<HighlightingStyle> &PegMarkdownHighlighter::getStyles()
+{
+    return m_styles;
+}
+
+inline const QVector<HighlightingStyle> &PegMarkdownHighlighter::getStyles() const
+{
+    return m_styles;
+}
+
+inline const QTextDocument *PegMarkdownHighlighter::getDocument() const
+{
+    return m_doc;
+}
+
+inline VTextBlockData *PegMarkdownHighlighter::currentBlockData() const
+{
+    return static_cast<VTextBlockData *>(currentBlockUserData());
+}
+
+inline VTextBlockData *PegMarkdownHighlighter::previousBlockData() const
+{
+    QTextBlock block = currentBlock().previous();
+    if (!block.isValid()) {
+        return NULL;
+    }
+
+    return static_cast<VTextBlockData *>(block.userData());
+}
+#endif // PEGMARKDOWNHIGHLIGHTER_H

+ 276 - 0
src/pegparser.cpp

@@ -0,0 +1,276 @@
+#include "pegparser.h"
+
+enum WorkerState
+{
+    Idle,
+    Busy,
+    Cancelled,
+    Finished
+};
+
+void PegParseResult::parse(QAtomicInt &p_stop)
+{
+    parseImageRegions(p_stop);
+
+    parseHeaderRegions(p_stop);
+
+    parseFencedCodeBlockRegions(p_stop);
+}
+
+void PegParseResult::parseImageRegions(QAtomicInt &p_stop)
+{
+    // From Qt5.7, the capacity is preserved.
+    m_imageRegions.clear();
+    if (isEmpty()) {
+        return;
+    }
+
+    pmh_element *elem = m_pmhElements[pmh_IMAGE];
+    while (elem != NULL) {
+        if (elem->end <= elem->pos) {
+            elem = elem->next;
+            continue;
+        }
+
+        if (p_stop.load() == 1) {
+            return;
+        }
+
+        m_imageRegions.push_back(VElementRegion(elem->pos, elem->end));
+        elem = elem->next;
+    }
+}
+
+void PegParseResult::parseHeaderRegions(QAtomicInt &p_stop)
+{
+    // From Qt5.7, the capacity is preserved.
+    m_headerRegions.clear();
+    if (isEmpty()) {
+        return;
+    }
+
+    pmh_element_type hx[6] = {pmh_H1, pmh_H2, pmh_H3, pmh_H4, pmh_H5, pmh_H6};
+    for (int i = 0; i < 6; ++i) {
+        pmh_element *elem = m_pmhElements[hx[i]];
+        while (elem != NULL) {
+            if (elem->end <= elem->pos) {
+                elem = elem->next;
+                continue;
+            }
+
+            if (p_stop.load() == 1) {
+                return;
+            }
+
+            m_headerRegions.push_back(VElementRegion(elem->pos, elem->end));
+            elem = elem->next;
+        }
+    }
+
+    if (p_stop.load() == 1) {
+        return;
+    }
+
+    std::sort(m_headerRegions.begin(), m_headerRegions.end());
+}
+
+void PegParseResult::parseFencedCodeBlockRegions(QAtomicInt &p_stop)
+{
+    m_codeBlockRegions.clear();
+    if (isEmpty()) {
+        return;
+    }
+
+    pmh_element *elem = m_pmhElements[pmh_FENCEDCODEBLOCK];
+    while (elem != NULL) {
+        if (elem->end <= elem->pos) {
+            elem = elem->next;
+            continue;
+        }
+
+        if (p_stop.load() == 1) {
+            return;
+        }
+
+        if (!m_codeBlockRegions.contains(elem->pos)) {
+            m_codeBlockRegions.insert(elem->pos, VElementRegion(elem->pos, elem->end));
+        }
+
+        elem = elem->next;
+    }
+}
+
+
+PegParserWorker::PegParserWorker(QObject *p_parent)
+    : QThread(p_parent),
+      m_stop(0),
+      m_state(WorkerState::Idle)
+{
+}
+
+void PegParserWorker::prepareParse(const QSharedPointer<PegParseConfig> &p_config)
+{
+    Q_ASSERT(m_parseConfig.isNull());
+
+    m_state = WorkerState::Busy;
+    m_parseConfig = p_config;
+}
+
+void PegParserWorker::reset()
+{
+    m_parseConfig.reset();
+    m_parseResult.reset();
+    m_stop.store(0);
+    m_state = WorkerState::Idle;
+}
+
+void PegParserWorker::stop()
+{
+    m_stop.store(1);
+}
+
+void PegParserWorker::run()
+{
+    Q_ASSERT(m_state == WorkerState::Busy);
+
+    m_parseResult = parseMarkdown(m_parseConfig, m_stop);
+
+    if (isAskedToStop()) {
+        m_state = WorkerState::Cancelled;
+        return;
+    }
+
+    m_state = WorkerState::Finished;
+}
+
+QSharedPointer<PegParseResult> PegParserWorker::parseMarkdown(const QSharedPointer<PegParseConfig> &p_config,
+                                                              QAtomicInt &p_stop)
+{
+    QSharedPointer<PegParseResult> result(new PegParseResult(p_config));
+
+    if (p_config->m_data.isEmpty()) {
+        return result;
+    }
+
+    pmh_element **pmhResult = NULL;
+
+    char *data = p_config->m_data.data();
+
+    pmh_markdown_to_elements(data, p_config->m_extensions, &pmhResult);
+
+    result->m_pmhElements = pmhResult;
+
+    if (p_stop.load() == 1) {
+        return result;
+    }
+
+    result->parse(p_stop);
+
+    return result;
+}
+
+
+#define NUM_OF_THREADS 2
+
+PegParser::PegParser(QObject *p_parent)
+    : QObject(p_parent)
+{
+    init();
+}
+
+void PegParser::parseAsync(const QSharedPointer<PegParseConfig> &p_config)
+{
+    m_pendingWork = p_config;
+
+    pickWorker();
+}
+
+void PegParser::init()
+{
+    for (int i = 0; i < NUM_OF_THREADS; ++i) {
+        PegParserWorker *th = new PegParserWorker(this);
+        connect(th, &PegParserWorker::finished,
+                this, [this, th]() {
+                    handleWorkerFinished(th);
+                });
+
+        m_workers.append(th);
+    }
+}
+
+void PegParser::clear()
+{
+    m_pendingWork.reset();
+
+    for (auto const & th : m_workers) {
+        th->quit();
+        th->wait();
+
+        delete th;
+    }
+
+    m_workers.clear();
+}
+
+PegParser::~PegParser()
+{
+    clear();
+}
+
+void PegParser::handleWorkerFinished(PegParserWorker *p_worker)
+{
+    QSharedPointer<PegParseResult> result;
+    if (p_worker->state() == WorkerState::Finished) {
+        result = p_worker->parseResult();
+    }
+
+    p_worker->reset();
+
+    pickWorker();
+
+    if (!result.isNull()) {
+        emit parseResultReady(result);
+    }
+}
+
+void PegParser::pickWorker()
+{
+    if (m_pendingWork.isNull()) {
+        return;
+    }
+
+    bool allBusy = true;
+    for (auto th : m_workers) {
+        if (th->state() == WorkerState::Idle) {
+            scheduleWork(th, m_pendingWork);
+            m_pendingWork.reset();
+            return;
+        } else if (th->state() != WorkerState::Busy) {
+            allBusy = false;
+        }
+    }
+
+    if (allBusy) {
+        // Need to stop the worker with non-minimal timestamp.
+        int idx = 0;
+        TimeStamp minTS = m_workers[idx]->workTimeStamp();
+
+        if (m_workers.size() > 1) {
+            if (m_workers[1]->workTimeStamp() > minTS) {
+                idx = 1;
+            }
+        }
+
+        m_workers[idx]->stop();
+    }
+}
+
+void PegParser::scheduleWork(PegParserWorker *p_worker,
+                             const QSharedPointer<PegParseConfig> &p_config)
+{
+    Q_ASSERT(p_worker->state() == WorkerState::Idle);
+
+    p_worker->reset();
+    p_worker->prepareParse(p_config);
+    p_worker->start();
+}

+ 186 - 0
src/pegparser.h

@@ -0,0 +1,186 @@
+#ifndef PEGPARSER_H
+#define PEGPARSER_H
+
+#include <QObject>
+
+#include <QSharedPointer>
+#include <QThread>
+#include <QAtomicInt>
+#include <QVector>
+
+#include "vconstants.h"
+#include "markdownhighlighterdata.h"
+
+struct PegParseConfig
+{
+    TimeStamp m_timeStamp;
+
+    QByteArray m_data;
+
+    int m_numOfBlocks;
+
+    int m_extensions;
+
+    QString toString() const
+    {
+        return QString("PegParseConfig ts %1 data %2 blocks %3").arg(m_timeStamp)
+                                                                .arg(m_data.size())
+                                                                .arg(m_numOfBlocks);
+    }
+};
+
+struct PegParseResult
+{
+    PegParseResult(const QSharedPointer<PegParseConfig> &p_config)
+        : m_timeStamp(p_config->m_timeStamp),
+          m_numOfBlocks(p_config->m_numOfBlocks),
+          m_pmhElements(NULL)
+    {
+    }
+
+    ~PegParseResult()
+    {
+        clearPmhElements();
+    }
+
+    void clearPmhElements()
+    {
+        if (m_pmhElements) {
+            pmh_free_elements(m_pmhElements);
+            m_pmhElements = NULL;
+        }
+    }
+
+    bool operator<(const PegParseResult &p_other) const
+    {
+        return m_timeStamp < p_other.m_timeStamp;
+    }
+
+    QString toString() const
+    {
+        return QString("PegParseResult ts %1").arg(m_timeStamp);
+    }
+
+    bool isEmpty() const
+    {
+        return !m_pmhElements;
+    }
+
+    // Parse m_pmhElements.
+    void parse(QAtomicInt &p_stop);
+
+    TimeStamp m_timeStamp;
+
+    int m_numOfBlocks;
+
+    pmh_element **m_pmhElements;
+
+    // All image link regions.
+    QVector<VElementRegion> m_imageRegions;
+
+    // All header regions.
+    // Sorted by start position.
+    QVector<VElementRegion> m_headerRegions;
+
+    // Fenced code block regions.
+    // Ordered by start position in ascending order.
+    QMap<int, VElementRegion>  m_codeBlockRegions;
+
+private:
+    void parseImageRegions(QAtomicInt &p_stop);
+
+    void parseHeaderRegions(QAtomicInt &p_stop);
+
+    void parseFencedCodeBlockRegions(QAtomicInt &p_stop);
+};
+
+class PegParserWorker : public QThread
+{
+    Q_OBJECT
+public:
+    explicit PegParserWorker(QObject *p_parent = nullptr);
+
+    void prepareParse(const QSharedPointer<PegParseConfig> &p_config);
+
+    void reset();
+
+    int state() const
+    {
+        return m_state;
+    }
+
+    TimeStamp workTimeStamp() const
+    {
+        if (m_parseConfig.isNull()) {
+            return 0;
+        }
+
+        return m_parseConfig->m_timeStamp;
+    }
+
+    const QSharedPointer<PegParseConfig> &parseConfig() const
+    {
+        return m_parseConfig;
+    }
+
+    const QSharedPointer<PegParseResult> &parseResult() const
+    {
+        return m_parseResult;
+    }
+
+public slots:
+    void stop();
+
+protected:
+    void run() Q_DECL_OVERRIDE;
+
+private:
+    QSharedPointer<PegParseResult> parseMarkdown(const QSharedPointer<PegParseConfig> &p_config,
+                                                 QAtomicInt &p_stop);
+
+    bool isAskedToStop() const
+    {
+        return m_stop.load() == 1;
+    }
+
+    QAtomicInt m_stop;
+
+    int m_state;
+
+    QSharedPointer<PegParseConfig> m_parseConfig;
+
+    QSharedPointer<PegParseResult> m_parseResult;
+};
+
+class PegParser : public QObject
+{
+    Q_OBJECT
+public:
+    explicit PegParser(QObject *p_parent = nullptr);
+
+    ~PegParser();
+
+    void parseAsync(const QSharedPointer<PegParseConfig> &p_config);
+
+signals:
+    void parseResultReady(const QSharedPointer<PegParseResult> &p_result);
+
+private slots:
+    void handleWorkerFinished(PegParserWorker *p_worker);
+
+private:
+    void init();
+
+    void clear();
+
+    void pickWorker();
+
+    void scheduleWork(PegParserWorker *p_worker, const QSharedPointer<PegParseConfig> &p_config);
+
+    // Maintain a fixed number of workers to pick work.
+    QVector<PegParserWorker *> m_workers;
+
+    QSharedPointer<PegParseConfig> m_pendingWork;
+};
+
+#endif // PEGPARSER_H

+ 1 - 1
src/resources/vnote.ini

@@ -106,7 +106,7 @@ minimize_to_system_tray=-1
 markdown_suffix=md,markdown,mkd
 
 ; Markdown highlight timer interval (milliseconds)
-markdown_highlight_interval=400
+markdown_highlight_interval=200
 
 ; Adds specified height between lines (in pixels)
 line_distance_height=3

+ 9 - 2
src/src.pro

@@ -140,7 +140,10 @@ SOURCES += main.cpp\
     vtagpanel.cpp \
     valltagspanel.cpp \
     vtaglabel.cpp \
-    vtagexplorer.cpp
+    vtagexplorer.cpp \
+    pegmarkdownhighlighter.cpp \
+    pegparser.cpp \
+    peghighlighterresult.cpp
 
 HEADERS  += vmainwindow.h \
     vdirectorytree.h \
@@ -274,7 +277,11 @@ HEADERS  += vmainwindow.h \
     vtagpanel.h \
     valltagspanel.h \
     vtaglabel.h \
-    vtagexplorer.h
+    vtagexplorer.h \
+    markdownhighlighterdata.h \
+    pegmarkdownhighlighter.h \
+    pegparser.h \
+    peghighlighterresult.h
 
 RESOURCES += \
     vnote.qrc \

+ 23 - 20
src/vcodeblockhighlighthelper.cpp

@@ -2,10 +2,12 @@
 
 #include <QDebug>
 #include <QStringList>
+
 #include "vdocument.h"
 #include "utils/vutils.h"
+#include "pegmarkdownhighlighter.h"
 
-VCodeBlockHighlightHelper::VCodeBlockHighlightHelper(HGMarkdownHighlighter *p_highlighter,
+VCodeBlockHighlightHelper::VCodeBlockHighlightHelper(PegMarkdownHighlighter *p_highlighter,
                                                      VDocument *p_vdoc,
                                                      MarkdownConverterType p_type)
     : QObject(p_highlighter),
@@ -14,14 +16,14 @@ VCodeBlockHighlightHelper::VCodeBlockHighlightHelper(HGMarkdownHighlighter *p_hi
       m_type(p_type),
       m_timeStamp(0)
 {
-    connect(m_highlighter, &HGMarkdownHighlighter::codeBlocksUpdated,
+    connect(m_highlighter, &PegMarkdownHighlighter::codeBlocksUpdated,
             this, &VCodeBlockHighlightHelper::handleCodeBlocksUpdated);
     connect(m_vdocument, &VDocument::textHighlighted,
             this, &VCodeBlockHighlightHelper::handleTextHighlightResult);
 
     // Web side is ready for code block highlight.
     connect(m_vdocument, &VDocument::readyToHighlightText,
-            m_highlighter, &HGMarkdownHighlighter::updateHighlight);
+            m_highlighter, &PegMarkdownHighlighter::updateHighlight);
 }
 
 QString VCodeBlockHighlightHelper::unindentCodeBlock(const QString &p_text)
@@ -57,44 +59,45 @@ QString VCodeBlockHighlightHelper::unindentCodeBlock(const QString &p_text)
     return res;
 }
 
-void VCodeBlockHighlightHelper::handleCodeBlocksUpdated(const QVector<VCodeBlock> &p_codeBlocks)
+void VCodeBlockHighlightHelper::handleCodeBlocksUpdated(TimeStamp p_timeStamp,
+                                                        const QVector<VCodeBlock> &p_codeBlocks)
 {
     if (!m_vdocument->isReadyToHighlight()) {
         // Immediately return empty results.
         QVector<HLUnitPos> emptyRes;
         for (int i = 0; i < p_codeBlocks.size(); ++i) {
-            updateHighlightResults(0, emptyRes);
+            updateHighlightResults(p_timeStamp, 0, emptyRes);
         }
 
         return;
     }
 
-    int curStamp = m_timeStamp.fetchAndAddRelaxed(1) + 1;
+    m_timeStamp = p_timeStamp;
     m_codeBlocks = p_codeBlocks;
     for (int i = 0; i < m_codeBlocks.size(); ++i) {
         const VCodeBlock &block = m_codeBlocks[i];
         auto it = m_cache.find(block.m_text);
         if (it != m_cache.end()) {
             // Hit cache.
-            qDebug() << "code block highlight hit cache" << curStamp << i;
-            it.value().m_timeStamp = curStamp;
-            updateHighlightResults(block.m_startPos, it.value().m_units);
+            qDebug() << "code block highlight hit cache" << p_timeStamp << i;
+            it.value().m_timeStamp = p_timeStamp;
+            updateHighlightResults(p_timeStamp, block.m_startPos, it.value().m_units);
         } else {
             QString unindentedText = unindentCodeBlock(block.m_text);
-            m_vdocument->highlightTextAsync(unindentedText, i, curStamp);
+            m_vdocument->highlightTextAsync(unindentedText, i, p_timeStamp);
         }
     }
 }
 
 void VCodeBlockHighlightHelper::handleTextHighlightResult(const QString &p_html,
                                                           int p_id,
-                                                          int p_timeStamp)
+                                                          unsigned long long p_timeStamp)
 {
-    int curStamp = m_timeStamp.load();
     // Abandon obsolete result.
-    if (curStamp != p_timeStamp) {
+    if (m_timeStamp != p_timeStamp) {
         return;
     }
+
     parseHighlightResult(p_timeStamp, p_id, p_html);
 }
 
@@ -137,7 +140,7 @@ static void matchTokenRelaxed(const QString &p_text, const QString &p_tokenStr,
 }
 
 // For now, we could only handle code blocks outside the list.
-void VCodeBlockHighlightHelper::parseHighlightResult(int p_timeStamp,
+void VCodeBlockHighlightHelper::parseHighlightResult(TimeStamp p_timeStamp,
                                                      int p_idx,
                                                      const QString &p_html)
 {
@@ -209,9 +212,8 @@ void VCodeBlockHighlightHelper::parseHighlightResult(int p_timeStamp,
 
 exit:
     // Pass result back to highlighter.
-    int curStamp = m_timeStamp.load();
     // Abandon obsolete result.
-    if (curStamp != p_timeStamp) {
+    if (m_timeStamp != p_timeStamp) {
         return;
     }
 
@@ -224,10 +226,11 @@ exit:
     // Add it to cache.
     addToHighlightCache(text, p_timeStamp, hlUnits);
 
-    updateHighlightResults(startPos, hlUnits);
+    updateHighlightResults(p_timeStamp, startPos, hlUnits);
 }
 
-void VCodeBlockHighlightHelper::updateHighlightResults(int p_startPos,
+void VCodeBlockHighlightHelper::updateHighlightResults(TimeStamp p_timeStamp,
+                                                       int p_startPos,
                                                        QVector<HLUnitPos> p_units)
 {
     for (int i = 0; i < p_units.size(); ++i) {
@@ -235,7 +238,7 @@ void VCodeBlockHighlightHelper::updateHighlightResults(int p_startPos,
     }
 
     // We need to call this function anyway to trigger the rehighlight.
-    m_highlighter->setCodeBlockHighlights(p_units);
+    m_highlighter->setCodeBlockHighlights(p_timeStamp, p_units);
 }
 
 bool VCodeBlockHighlightHelper::parseSpanElement(QXmlStreamReader &p_xml,
@@ -283,7 +286,7 @@ bool VCodeBlockHighlightHelper::parseSpanElement(QXmlStreamReader &p_xml,
 }
 
 void VCodeBlockHighlightHelper::addToHighlightCache(const QString &p_text,
-                                                    int p_timeStamp,
+                                                    TimeStamp p_timeStamp,
                                                     const QVector<HLUnitPos> &p_units)
 {
     const int c_maxEntries = 100;

+ 19 - 12
src/vcodeblockhighlighthelper.h

@@ -6,15 +6,17 @@
 #include <QAtomicInteger>
 #include <QXmlStreamReader>
 #include <QHash>
+
 #include "vconfigmanager.h"
 
 class VDocument;
+class PegMarkdownHighlighter;
 
 class VCodeBlockHighlightHelper : public QObject
 {
     Q_OBJECT
 public:
-    VCodeBlockHighlightHelper(HGMarkdownHighlighter *p_highlighter,
+    VCodeBlockHighlightHelper(PegMarkdownHighlighter *p_highlighter,
                               VDocument *p_vdoc, MarkdownConverterType p_type);
 
     // @p_text: text of fenced code block.
@@ -25,27 +27,30 @@ public:
     static QString unindentCodeBlock(const QString &p_text);
 
 private slots:
-    void handleCodeBlocksUpdated(const QVector<VCodeBlock> &p_codeBlocks);
+    void handleCodeBlocksUpdated(TimeStamp p_timeStamp, const QVector<VCodeBlock> &p_codeBlocks);
 
-    void handleTextHighlightResult(const QString &p_html, int p_id, int p_timeStamp);
+    void handleTextHighlightResult(const QString &p_html, int p_id, unsigned long long p_timeStamp);
 
 private:
     struct HLResult
     {
-        HLResult() : m_timeStamp(-1)
+        HLResult()
+            : m_timeStamp(0)
         {
         }
 
-        HLResult(int p_timeStamp, const QVector<HLUnitPos> &p_units)
-            : m_timeStamp(p_timeStamp), m_units(p_units)
+        HLResult(TimeStamp p_timeStamp, const QVector<HLUnitPos> &p_units)
+            : m_timeStamp(p_timeStamp),
+              m_units(p_units)
         {
         }
 
-        int m_timeStamp;
+        TimeStamp m_timeStamp;
+
         QVector<HLUnitPos> m_units;
     };
 
-    void parseHighlightResult(int p_timeStamp, int p_idx, const QString &p_html);
+    void parseHighlightResult(TimeStamp p_timeStamp, int p_idx, const QString &p_html);
 
     // @p_text: the raw text of the code block;
     // @p_index: the start index of the span element within @p_text;
@@ -54,16 +59,18 @@ private:
                           const QString &p_text, int &p_index,
                           QVector<HLUnitPos> &p_units);
 
-    void updateHighlightResults(int p_startPos, QVector<HLUnitPos> p_units);
+    void updateHighlightResults(TimeStamp p_timeStamp, int p_startPos, QVector<HLUnitPos> p_units);
 
     void addToHighlightCache(const QString &p_text,
-                             int p_timeStamp,
+                             TimeStamp p_timeStamp,
                              const QVector<HLUnitPos> &p_units);
 
-    HGMarkdownHighlighter *m_highlighter;
+    PegMarkdownHighlighter *m_highlighter;
     VDocument *m_vdocument;
     MarkdownConverterType m_type;
-    QAtomicInteger<int> m_timeStamp;
+
+    TimeStamp m_timeStamp;
+
     QVector<VCodeBlock> m_codeBlocks;
 
     // Cache for highlight result, using the code block text as key.

+ 1 - 1
src/vconfigmanager.h

@@ -11,7 +11,7 @@
 #include <QDir>
 
 #include "vnotebook.h"
-#include "hgmarkdownhighlighter.h"
+#include "markdownhighlighterdata.h"
 #include "vmarkdownconverter.h"
 #include "vconstants.h"
 #include "vfilesessioninfo.h"

+ 3 - 0
src/vconstants.h

@@ -122,9 +122,11 @@ enum HighlightBlockState
     CodeBlockEnd,
 
     // This block is inside a HTML comment region.
+    // Obsolete.
     Comment,
 
     // Verbatim code block.
+    // Obsolete.
     Verbatim,
 
     // Mathjax. It means the pending state of the block.
@@ -132,6 +134,7 @@ enum HighlightBlockState
     MathjaxInline,
 
     // Header.
+    // Obsolete.
     Header
 };
 

+ 2 - 2
src/vdocument.cpp

@@ -74,12 +74,12 @@ void VDocument::keyPressEvent(int p_key, bool p_ctrl, bool p_shift, bool p_meta)
     emit keyPressed(p_key, p_ctrl, p_shift, p_meta);
 }
 
-void VDocument::highlightTextAsync(const QString &p_text, int p_id, int p_timeStamp)
+void VDocument::highlightTextAsync(const QString &p_text, int p_id, unsigned long long p_timeStamp)
 {
     emit requestHighlightText(p_text, p_id, p_timeStamp);
 }
 
-void VDocument::highlightTextCB(const QString &p_html, int p_id, int p_timeStamp)
+void VDocument::highlightTextCB(const QString &p_html, int p_id, unsigned long long p_timeStamp)
 {
     emit textHighlighted(p_html, p_id, p_timeStamp);
 }

+ 4 - 4
src/vdocument.h

@@ -31,7 +31,7 @@ public:
 
     // Request to highlight a segment text.
     // Use p_id to identify the result.
-    void highlightTextAsync(const QString &p_text, int p_id, int p_timeStamp);
+    void highlightTextAsync(const QString &p_text, int p_id, unsigned long long p_timeStamp);
 
     // Request to convert @p_text to HTML.
     void textToHtmlAsync(int p_identitifer,
@@ -84,7 +84,7 @@ public slots:
     void keyPressEvent(int p_key, bool p_ctrl, bool p_shift, bool p_meta);
     void updateText();
 
-    void highlightTextCB(const QString &p_html, int p_id, int p_timeStamp);
+    void highlightTextCB(const QString &p_html, int p_id, unsigned long long p_timeStamp);
 
     void noticeReadyToHighlightText();
 
@@ -128,9 +128,9 @@ signals:
 
     void keyPressed(int p_key, bool p_ctrl, bool p_shift, bool p_meta);
 
-    void requestHighlightText(const QString &p_text, int p_id, int p_timeStamp);
+    void requestHighlightText(const QString &p_text, int p_id, unsigned long long p_timeStamp);
 
-    void textHighlighted(const QString &p_html, int p_id, int p_timeStamp);
+    void textHighlighted(const QString &p_html, int p_id, unsigned long long p_timeStamp);
 
     void readyToHighlightText();
 

+ 2 - 1
src/vlivepreviewhelper.cpp

@@ -125,8 +125,9 @@ void VLivePreviewHelper::checkLang(const QString &p_lang,
     }
 }
 
-void VLivePreviewHelper::updateCodeBlocks(const QVector<VCodeBlock> &p_codeBlocks)
+void VLivePreviewHelper::updateCodeBlocks(TimeStamp p_timeStamp, const QVector<VCodeBlock> &p_codeBlocks)
 {
+    Q_UNUSED(p_timeStamp);
     if (!m_livePreviewEnabled && !m_inplacePreviewEnabled) {
         return;
     }

+ 1 - 1
src/vlivepreviewhelper.h

@@ -109,7 +109,7 @@ public:
     bool isPreviewEnabled() const;
 
 public slots:
-    void updateCodeBlocks(const QVector<VCodeBlock> &p_codeBlocks);
+    void updateCodeBlocks(TimeStamp p_timeStamp, const QVector<VCodeBlock> &p_codeBlocks);
 
 signals:
     void inplacePreviewCodeBlockUpdated(const QVector<QSharedPointer<VImageToPreview> > &p_images);

+ 3 - 4
src/vmdedit.cpp

@@ -1,7 +1,6 @@
 #include <QtWidgets>
 #include "vmdedit.h"
 #include "hgmarkdownhighlighter.h"
-#include "vcodeblockhighlighthelper.h"
 #include "vmdeditoperations.h"
 #include "vnote.h"
 #include "vconfigmanager.h"
@@ -24,6 +23,9 @@ VMdEdit::VMdEdit(VFile *p_file, VDocument *p_vdoc, MarkdownConverterType p_type,
     : VEdit(p_file, p_parent), m_mdHighlighter(NULL), m_freshEdit(true),
       m_finishedAsyncJobs(c_numberOfAysncJobs)
 {
+    Q_UNUSED(p_type);
+    Q_UNUSED(p_vdoc);
+
     V_ASSERT(p_file->getDocType() == DocType::Markdown);
 
     setAcceptRichText(false);
@@ -43,9 +45,6 @@ VMdEdit::VMdEdit(VFile *p_file, VDocument *p_vdoc, MarkdownConverterType p_type,
             makeBlockVisible(textCursor().block());
     });
 
-    m_cbHighlighter = new VCodeBlockHighlightHelper(m_mdHighlighter, p_vdoc,
-                                                    p_type);
-
     /*
     m_imagePreviewer = new VImagePreviewer(this, m_mdHighlighter);
     connect(m_mdHighlighter, &HGMarkdownHighlighter::imageLinksUpdated,

+ 0 - 2
src/vmdedit.h

@@ -13,7 +13,6 @@
 #include "utils/vutils.h"
 
 class HGMarkdownHighlighter;
-class VCodeBlockHighlightHelper;
 class VDocument;
 
 class VMdEdit : public VEdit
@@ -110,7 +109,6 @@ private:
     int indexOfCurrentHeader() const;
 
     HGMarkdownHighlighter *m_mdHighlighter;
-    VCodeBlockHighlightHelper *m_cbHighlighter;
     // VImagePreviewer *m_imagePreviewer;
 
     // Image links inserted while editing.

+ 21 - 23
src/vmdeditor.cpp

@@ -9,7 +9,7 @@
 #include "vdocument.h"
 #include "utils/veditutils.h"
 #include "vedittab.h"
-#include "hgmarkdownhighlighter.h"
+#include "pegmarkdownhighlighter.h"
 #include "vcodeblockhighlighthelper.h"
 #include "vmdeditoperations.h"
 #include "vtableofcontent.h"
@@ -35,7 +35,7 @@ VMdEditor::VMdEditor(VFile *p_file,
                      QWidget *p_parent)
     : VTextEdit(p_parent),
       VEditor(p_file, this),
-      m_mdHighlighter(NULL),
+      m_pegHighlighter(NULL),
       m_freshEdit(true),
       m_textToHtmlDialog(NULL),
       m_zoomDelta(0),
@@ -60,18 +60,17 @@ VMdEditor::VMdEditor(VFile *p_file,
 
     setReadOnly(true);
 
-    m_mdHighlighter = new HGMarkdownHighlighter(g_config->getMdHighlightingStyles(),
-                                                g_config->getCodeBlockStyles(),
-                                                g_config->getMarkdownHighlightInterval(),
-                                                document());
-    m_mdHighlighter->setMathjaxEnabled(g_config->getEnableMathjax());
-
-    connect(m_mdHighlighter, &HGMarkdownHighlighter::headersUpdated,
+    m_pegHighlighter = new PegMarkdownHighlighter(document());
+    m_pegHighlighter->init(g_config->getMdHighlightingStyles(),
+                           g_config->getCodeBlockStyles(),
+                           g_config->getEnableMathjax(),
+                           g_config->getMarkdownHighlightInterval());
+    connect(m_pegHighlighter, &PegMarkdownHighlighter::headersUpdated,
             this, &VMdEditor::updateHeaders);
 
     // After highlight, the cursor may trun into non-visible. We should make it visible
     // in this case.
-    connect(m_mdHighlighter, &HGMarkdownHighlighter::highlightCompleted,
+    connect(m_pegHighlighter, &PegMarkdownHighlighter::highlightCompleted,
             this, [this]() {
             makeBlockVisible(textCursor().block());
 
@@ -81,15 +80,15 @@ VMdEditor::VMdEditor(VFile *p_file,
             }
     });
 
-    m_cbHighlighter = new VCodeBlockHighlightHelper(m_mdHighlighter,
+    m_cbHighlighter = new VCodeBlockHighlightHelper(m_pegHighlighter,
                                                     p_doc,
                                                     p_type);
 
-    m_previewMgr = new VPreviewManager(this, m_mdHighlighter);
-    connect(m_mdHighlighter, &HGMarkdownHighlighter::imageLinksUpdated,
+    m_previewMgr = new VPreviewManager(this, m_pegHighlighter);
+    connect(m_pegHighlighter, &PegMarkdownHighlighter::imageLinksUpdated,
             m_previewMgr, &VPreviewManager::updateImageLinks);
     connect(m_previewMgr, &VPreviewManager::requestUpdateImageLinks,
-            m_mdHighlighter, &HGMarkdownHighlighter::updateHighlight);
+            m_pegHighlighter, &PegMarkdownHighlighter::updateHighlight);
 
     m_editOps = new VMdEditOperations(this, m_file);
     connect(m_editOps, &VEditOperations::statusMessage,
@@ -132,10 +131,10 @@ void VMdEditor::beginEdit()
     emit statusChanged();
 
     if (m_freshEdit) {
-        m_mdHighlighter->updateHighlight();
+        m_pegHighlighter->updateHighlight();
         relayout();
     } else {
-        updateHeaders(m_mdHighlighter->getHeaderRegions());
+        updateHeaders(m_pegHighlighter->getHeaderRegions());
     }
 }
 
@@ -170,7 +169,7 @@ void VMdEditor::reloadFile()
     const QString &content = m_file->getContent();
     setPlainText(content);
     setModified(false);
-    m_mdHighlighter->updateHighlightFast();
+    m_pegHighlighter->updateHighlightFast();
 
     m_freshEdit = true;
 
@@ -461,7 +460,7 @@ static void insertSequenceToHeader(QTextCursor& p_cursor,
 
 void VMdEditor::updateHeaderSequenceByConfigChange()
 {
-    updateHeadersHelper(m_mdHighlighter->getHeaderRegions(), true);
+    updateHeadersHelper(m_pegHighlighter->getHeaderRegions(), true);
 }
 
 void VMdEditor::updateHeadersHelper(const QVector<VElementRegion> &p_headerRegions, bool p_configChanged)
@@ -493,8 +492,7 @@ void VMdEditor::updateHeadersHelper(const QVector<VElementRegion> &p_headerRegio
                        << block.text();
         }
 
-        if ((block.userState() == HighlightBlockState::Header)
-            && headerReg.exactMatch(block.text())) {
+        if (headerReg.exactMatch(block.text())) {
             int level = headerReg.cap(1).length();
             VTableOfContentItem header(headerReg.cap(2).trimmed(),
                                        level,
@@ -1199,7 +1197,7 @@ void VMdEditor::zoomPage(bool p_zoomIn, int p_range)
 
     m_zoomDelta += delta;
 
-    QVector<HighlightingStyle> &styles = m_mdHighlighter->getHighlightingStyles();
+    QVector<HighlightingStyle> &styles = m_pegHighlighter->getStyles();
     for (auto & it : styles) {
         int size = it.format.fontPointSize();
         if (size == 0) {
@@ -1215,7 +1213,7 @@ void VMdEditor::zoomPage(bool p_zoomIn, int p_range)
         it.format.setFontPointSize(size);
     }
 
-    QHash<QString, QTextCharFormat> &cbStyles = m_mdHighlighter->getCodeBlockStyles();
+    QHash<QString, QTextCharFormat> &cbStyles = m_pegHighlighter->getCodeBlockStyles();
     for (auto it = cbStyles.begin(); it != cbStyles.end(); ++it) {
         int size = it.value().fontPointSize();
         if (size == 0) {
@@ -1231,7 +1229,7 @@ void VMdEditor::zoomPage(bool p_zoomIn, int p_range)
         it.value().setFontPointSize(size);
     }
 
-    m_mdHighlighter->rehighlight();
+    m_pegHighlighter->rehighlight();
 }
 
 void VMdEditor::initCopyAsMenu(QAction *p_before, QMenu *p_menu)

+ 5 - 5
src/vmdeditor.h

@@ -14,7 +14,7 @@
 #include "vconfigmanager.h"
 #include "utils/vutils.h"
 
-class HGMarkdownHighlighter;
+class PegMarkdownHighlighter;
 class VCodeBlockHighlightHelper;
 class VDocument;
 class VPreviewManager;
@@ -72,7 +72,7 @@ public:
 
     void setEditTab(VEditTab *p_editTab);
 
-    HGMarkdownHighlighter *getMarkdownHighlighter() const;
+    PegMarkdownHighlighter *getMarkdownHighlighter() const;
 
     VPreviewManager *getPreviewManager() const;
 
@@ -256,7 +256,7 @@ private:
 
     void insertImageLink(const QString &p_text, const QString &p_url);
 
-    HGMarkdownHighlighter *m_mdHighlighter;
+    PegMarkdownHighlighter *m_pegHighlighter;
 
     VCodeBlockHighlightHelper *m_cbHighlighter;
 
@@ -282,9 +282,9 @@ private:
     int m_copyTimeStamp;
 };
 
-inline HGMarkdownHighlighter *VMdEditor::getMarkdownHighlighter() const
+inline PegMarkdownHighlighter *VMdEditor::getMarkdownHighlighter() const
 {
-    return m_mdHighlighter;
+    return m_pegHighlighter;
 }
 
 inline VPreviewManager *VMdEditor::getPreviewManager() const

+ 3 - 3
src/vmdtab.cpp

@@ -8,7 +8,7 @@
 #include "vnote.h"
 #include "utils/vutils.h"
 #include "vpreviewpage.h"
-#include "hgmarkdownhighlighter.h"
+#include "pegmarkdownhighlighter.h"
 #include "vconfigmanager.h"
 #include "vmarkdownconverter.h"
 #include "vnotebook.h"
@@ -535,7 +535,7 @@ void VMdTab::setupMarkdownEditor()
     m_splitter->insertWidget(0, m_editor);
 
     m_livePreviewHelper = new VLivePreviewHelper(m_editor, m_document, this);
-    connect(m_editor->getMarkdownHighlighter(), &HGMarkdownHighlighter::codeBlocksUpdated,
+    connect(m_editor->getMarkdownHighlighter(), &PegMarkdownHighlighter::codeBlocksUpdated,
             m_livePreviewHelper, &VLivePreviewHelper::updateCodeBlocks);
     connect(m_editor->getPreviewManager(), &VPreviewManager::previewEnabledChanged,
             m_livePreviewHelper, &VLivePreviewHelper::setInplacePreviewEnabled);
@@ -546,7 +546,7 @@ void VMdTab::setupMarkdownEditor()
     m_livePreviewHelper->setInplacePreviewEnabled(m_editor->getPreviewManager()->isPreviewEnabled());
 
     m_mathjaxPreviewHelper = new VMathJaxInplacePreviewHelper(m_editor, m_document, this);
-    connect(m_editor->getMarkdownHighlighter(), &HGMarkdownHighlighter::mathjaxBlocksUpdated,
+    connect(m_editor->getMarkdownHighlighter(), &PegMarkdownHighlighter::mathjaxBlocksUpdated,
             m_mathjaxPreviewHelper, &VMathJaxInplacePreviewHelper::updateMathjaxBlocks);
     connect(m_editor->getPreviewManager(), &VPreviewManager::previewEnabledChanged,
             m_mathjaxPreviewHelper, &VMathJaxInplacePreviewHelper::setEnabled);

+ 2 - 2
src/vpreviewmanager.cpp

@@ -10,11 +10,11 @@
 #include "vconfigmanager.h"
 #include "utils/vutils.h"
 #include "vdownloader.h"
-#include "hgmarkdownhighlighter.h"
+#include "pegmarkdownhighlighter.h"
 
 extern VConfigManager *g_config;
 
-VPreviewManager::VPreviewManager(VMdEditor *p_editor, HGMarkdownHighlighter *p_highlighter)
+VPreviewManager::VPreviewManager(VMdEditor *p_editor, PegMarkdownHighlighter *p_highlighter)
     : QObject(p_editor),
       m_editor(p_editor),
       m_document(p_editor->document()),

+ 3 - 3
src/vpreviewmanager.h

@@ -8,7 +8,7 @@
 #include <QVector>
 #include <QSharedPointer>
 
-#include "hgmarkdownhighlighter.h"
+#include "markdownhighlighterdata.h"
 #include "vmdeditor.h"
 #include "vtextblockdata.h"
 
@@ -59,7 +59,7 @@ class VPreviewManager : public QObject
 {
     Q_OBJECT
 public:
-    VPreviewManager(VMdEditor *p_editor, HGMarkdownHighlighter *p_highlighter);
+    VPreviewManager(VMdEditor *p_editor, PegMarkdownHighlighter *p_highlighter);
 
     void setPreviewEnabled(bool p_enabled);
 
@@ -215,7 +215,7 @@ private:
 
     QTextDocument *m_document;
 
-    HGMarkdownHighlighter *m_highlighter;
+    PegMarkdownHighlighter *m_highlighter;
 
     VDownloader *m_downloader;