Browse Source

highlight: highlight MathJax formula in editor

Le Tan 7 years ago
parent
commit
9566e6f5d2

+ 121 - 12
src/hgmarkdownhighlighter.cpp

@@ -35,6 +35,7 @@ HGMarkdownHighlighter::HGMarkdownHighlighter(const QVector<HighlightingStyle> &s
       parsing(0),
       m_blockHLResultReady(false),
       waitInterval(waitInterval),
+      m_enableMathjax(false),
       content(NULL),
       capacity(0),
       result(NULL)
@@ -42,6 +43,9 @@ HGMarkdownHighlighter::HGMarkdownHighlighter(const QVector<HighlightingStyle> &s
     codeBlockStartExp = QRegExp(VUtils::c_fencedCodeBlockStartRegExp);
     codeBlockEndExp = QRegExp(VUtils::c_fencedCodeBlockEndRegExp);
 
+    m_mathjaxInlineExp = QRegExp(VUtils::c_mathjaxInlineRegExp);
+    m_mathjaxBlockExp = QRegExp(VUtils::c_mathjaxBlockRegExp);
+
     m_codeBlockFormat.setForeground(QBrush(Qt::darkYellow));
     for (int index = 0; index < styles.size(); ++index) {
         const pmh_element_type &eleType = styles[index].type;
@@ -58,6 +62,8 @@ HGMarkdownHighlighter::HGMarkdownHighlighter(const QVector<HighlightingStyle> &s
     m_colorColumnFormat.setForeground(QColor(g_config->getEditorColorColumnFg()));
     m_colorColumnFormat.setBackground(QColor(g_config->getEditorColorColumnBg()));
 
+    m_mathjaxFormat.setForeground(QColor(g_config->getEditorMathjaxFg()));
+
     m_headerStyles.resize(6);
     for (auto const & it : highlightingStyles) {
         if (it.type >= pmh_H1 && it.type <= pmh_H6) {
@@ -108,6 +114,9 @@ void HGMarkdownHighlighter::updateBlockUserData(int p_blockNum, const QString &p
     if (!blockData) {
         blockData = new VTextBlockData();
         setCurrentBlockUserData(blockData);
+    } else {
+        blockData->setCodeBlockIndentation(-1);
+        blockData->clearMathjax();
     }
 
     if (blockData->getPreviews().isEmpty()) {
@@ -115,8 +124,6 @@ void HGMarkdownHighlighter::updateBlockUserData(int p_blockNum, const QString &p
     } else {
         m_possiblePreviewBlocks.insert(p_blockNum);
     }
-
-    blockData->setCodeBlockIndentation(-1);
 }
 
 void HGMarkdownHighlighter::highlightBlock(const QString &text)
@@ -171,10 +178,13 @@ void HGMarkdownHighlighter::highlightBlock(const QString &text)
     setCurrentBlockState(HighlightBlockState::Normal);
     highlightCodeBlock(curBlock, text);
 
-    if (currentBlockState() == HighlightBlockState::Normal
-        && isVerbatimBlock(curBlock)) {
-        setCurrentBlockState(HighlightBlockState::Verbatim);
-        goto exit;
+    if (currentBlockState() == HighlightBlockState::Normal) {
+        if (isVerbatimBlock(curBlock)) {
+            setCurrentBlockState(HighlightBlockState::Verbatim);
+            goto exit;
+        } else if (m_enableMathjax) {
+            highlightMathJax(curBlock, text);
+        }
     }
 
     // PEG Markdown Highlight does not handle links with spaces in the URL.
@@ -184,6 +194,10 @@ void HGMarkdownHighlighter::highlightBlock(const QString &text)
 
     highlightHeaderFast(blockNum, text);
 
+    if (currentBlockState() != HighlightBlockState::CodeBlock) {
+        goto exit;
+    }
+
     // Highlight CodeBlock using VCodeBlockHighlightHelper.
     if (m_codeBlockHighlights.size() > blockNum) {
         const QVector<HLUnitStyle> &units = m_codeBlockHighlights[blockNum];
@@ -436,7 +450,7 @@ void HGMarkdownHighlighter::initBlockHighlihgtOne(unsigned long pos,
     }
 }
 
-void HGMarkdownHighlighter::highlightCodeBlock(const QTextBlock &p_block, const QString &text)
+void HGMarkdownHighlighter::highlightCodeBlock(const QTextBlock &p_block, const QString &p_text)
 {
     VTextBlockData *blockData = currentBlockData();
     Q_ASSERT(blockData);
@@ -449,10 +463,10 @@ void HGMarkdownHighlighter::highlightCodeBlock(const QTextBlock &p_block, const
     if (preState != HighlightBlockState::CodeBlock
         && preState != HighlightBlockState::CodeBlockStart) {
         // Need to find a new code block start.
-        index = codeBlockStartExp.indexIn(text);
+        index = codeBlockStartExp.indexIn(p_text);
         if (index >= 0 && !isVerbatimBlock(p_block)) {
             // Start a new code block.
-            length = text.length();
+            length = p_text.length();
             state = HighlightBlockState::CodeBlockStart;
 
             // The leading spaces of code block start and end must be identical.
@@ -471,18 +485,18 @@ void HGMarkdownHighlighter::highlightCodeBlock(const QTextBlock &p_block, const
             startLeadingSpaces = preBlockData->getCodeBlockIndentation();
         }
 
-        index = codeBlockEndExp.indexIn(text);
+        index = codeBlockEndExp.indexIn(p_text);
 
         // The closing ``` should have the same indentation as the open ```.
         if (index >= 0
             && startLeadingSpaces == codeBlockEndExp.capturedTexts()[1].size()) {
             // End of code block.
-            length = text.length();
+            length = p_text.length();
             state = HighlightBlockState::CodeBlockEnd;
         } else {
             // Within code block.
             index = 0;
-            length = text.length();
+            length = p_text.length();
             state = HighlightBlockState::CodeBlock;
         }
 
@@ -493,6 +507,101 @@ void HGMarkdownHighlighter::highlightCodeBlock(const QTextBlock &p_block, const
     setFormat(index, length, m_codeBlockFormat);
 }
 
+static bool intersect(const QList<QPair<int, int>> &p_indices, int &p_start, int &p_end)
+{
+    for (auto const & range : p_indices) {
+        if (p_end <= range.first) {
+            return false;
+        } else if (p_start < range.second) {
+            return true;
+        }
+    }
+
+    return false;
+}
+
+void HGMarkdownHighlighter::highlightMathJax(const QTextBlock &p_block, const QString &p_text)
+{
+    const int blockMarkLength = 2;
+    const int inlineMarkLength = 1;
+
+    int startIdx = 0;
+    // Next position to search.
+    int pos = 0;
+    HighlightBlockState state = (HighlightBlockState)previousBlockState();
+
+    QList<QPair<int, int>> blockIdices;
+
+    // Mathjax block formula.
+    if (state != HighlightBlockState::MathjaxBlock) {
+        startIdx = m_mathjaxBlockExp.indexIn(p_text);
+        pos = startIdx + m_mathjaxBlockExp.matchedLength();
+        startIdx = pos - blockMarkLength;
+    }
+
+    while (startIdx >= 0) {
+        int endIdx = m_mathjaxBlockExp.indexIn(p_text, pos);
+        int mathLength = 0;
+        if (endIdx == -1) {
+            setCurrentBlockState(HighlightBlockState::MathjaxBlock);
+            mathLength = p_text.length() - startIdx;
+        } else {
+            mathLength = endIdx - startIdx + m_mathjaxBlockExp.matchedLength();
+        }
+
+        pos = startIdx + mathLength;
+
+        blockIdices.append(QPair<int, int>(startIdx, pos));
+
+        setFormat(startIdx, mathLength, m_mathjaxFormat);
+        startIdx = m_mathjaxBlockExp.indexIn(p_text, pos);
+        pos = startIdx + m_mathjaxBlockExp.matchedLength();
+        startIdx = pos - blockMarkLength;
+    }
+
+    // Mathjax inline formula.
+    startIdx = 0;
+    pos = 0;
+    if (state != HighlightBlockState::MathjaxInline) {
+        startIdx = m_mathjaxInlineExp.indexIn(p_text);
+        pos = startIdx + m_mathjaxInlineExp.matchedLength();
+        startIdx = pos - inlineMarkLength;
+    }
+
+    while (startIdx >= 0) {
+        int endIdx = m_mathjaxInlineExp.indexIn(p_text, pos);
+        int mathLength = 0;
+        if (endIdx == -1) {
+            setCurrentBlockState(HighlightBlockState::MathjaxBlock);
+            mathLength = p_text.length() - startIdx;
+        } else {
+            mathLength = endIdx - startIdx + m_mathjaxInlineExp.matchedLength();
+        }
+
+        pos = startIdx + mathLength;
+        // Check if it intersect with blocks.
+        if (!intersect(blockIdices, startIdx, pos)) {
+            // A valid inline mathjax.
+            if (endIdx == -1) {
+                setCurrentBlockState(HighlightBlockState::MathjaxInline);
+            }
+
+            setFormat(startIdx, mathLength, m_mathjaxFormat);
+
+            startIdx = m_mathjaxInlineExp.indexIn(p_text, pos);
+            pos = startIdx + m_mathjaxInlineExp.matchedLength();
+            startIdx = pos - inlineMarkLength;
+        } else {
+            // Make the second mark as the first one and try again.
+            if (endIdx == -1) {
+                break;
+            }
+
+            startIdx = pos - inlineMarkLength;
+        }
+    }
+}
+
 void HGMarkdownHighlighter::highlightCodeBlockColorColumn(const QString &p_text)
 {
     int cc = g_config->getColorColumn();

+ 18 - 1
src/hgmarkdownhighlighter.h

@@ -155,6 +155,8 @@ public:
 
     QVector<HighlightingStyle> &getHighlightingStyles();
 
+    void setMathjaxEnabled(bool p_enabled);
+
 signals:
     void highlightCompleted();
 
@@ -197,10 +199,15 @@ private:
 
     QRegExp codeBlockStartExp;
     QRegExp codeBlockEndExp;
+
+    QRegExp m_mathjaxInlineExp;
+    QRegExp m_mathjaxBlockExp;
+
     QTextCharFormat m_codeBlockFormat;
     QTextCharFormat m_linkFormat;
     QTextCharFormat m_imageFormat;
     QTextCharFormat m_colorColumnFormat;
+    QTextCharFormat m_mathjaxFormat;
 
     QTextDocument *document;
 
@@ -253,6 +260,8 @@ private:
     // Block number of those blocks which possible contains previewed image.
     QSet<int> m_possiblePreviewBlocks;
 
+    bool m_enableMathjax;
+
     char *content;
     int capacity;
     pmh_element **result;
@@ -260,7 +269,10 @@ private:
     static const int initCapacity;
 
     void resizeBuffer(int newCap);
-    void highlightCodeBlock(const QTextBlock &p_block, const QString &text);
+
+    void highlightCodeBlock(const QTextBlock &p_block, const QString &p_text);
+
+    void highlightMathJax(const QTextBlock &p_block, const QString &p_text);
 
     // Highlight links using regular expression.
     // PEG Markdown Highlight treat URLs with spaces illegal. This function is
@@ -375,4 +387,9 @@ inline bool HGMarkdownHighlighter::isVerbatimBlock(const QTextBlock &p_block) co
 {
     return m_verbatimBlocks.contains(p_block.blockNumber());
 }
+
+inline void HGMarkdownHighlighter::setMathjaxEnabled(bool p_enabled)
+{
+    m_enableMathjax = p_enabled;
+}
 #endif

+ 3 - 1
src/resources/themes/v_moonlight/v_moonlight.mdhl

@@ -35,8 +35,10 @@ incremental-searched-word-background: ce93d8
 # [VNote] Style for color column in fenced code block
 color-column-background: c9302c
 color-column-foreground: eeeeee
-# [VNote} Style for preview image line
+# [VNote] Style for preview image line
 preview-image-line-foreground: 6f5799
+# [VNote] Style for MathJax
+mathjax-foreground: ce93d8
 
 editor-selection
 foreground: dadada

+ 3 - 1
src/resources/themes/v_native/v_native.mdhl

@@ -34,8 +34,10 @@ incremental-searched-word-background: ce93d8
 # [VNote] Style for color column in fenced code block
 color-column-background: dd0000
 color-column-foreground: ffff00
-# [VNote} Style for preview image line
+# [VNote] Style for preview image line
 preview-image-line-foreground: 9575cd
+# [VNote] Style for MathJax
+mathjax-foreground: 8e24aa
 
 editor-selection
 foreground: eeeeee

+ 3 - 1
src/resources/themes/v_pure/v_pure.mdhl

@@ -35,8 +35,10 @@ incremental-searched-word-background: ce93d8
 # [VNote] Style for color column in fenced code block
 color-column-background: dd0000
 color-column-foreground: ffff00
-# [VNote} Style for preview image line
+# [VNote] Style for preview image line
 preview-image-line-foreground: 9575cd
+# [VNote] Style for MathJax
+mathjax-foreground: 8e24aa
 
 editor-selection
 foreground: eeeeee

+ 4 - 0
src/utils/vutils.cpp

@@ -47,6 +47,10 @@ const QString VUtils::c_fencedCodeBlockStartRegExp = QString("^(\\s*)```([^`\\s]
 
 const QString VUtils::c_fencedCodeBlockEndRegExp = QString("^(\\s*)```$");
 
+const QString VUtils::c_mathjaxInlineRegExp = QString("(?:^|[^\\$\\\\]|(?:^|[^\\\\])(?:\\\\\\\\)+)\\$(?!\\$)");
+
+const QString VUtils::c_mathjaxBlockRegExp = QString("(?:^|[^\\$\\\\]|(?:^|[^\\\\])(?:\\\\\\\\)+)\\$\\$(?!\\$)");
+
 const QString VUtils::c_previewImageBlockRegExp = QString("[\\n|^][ |\\t]*\\xfffc[ |\\t]*(?=\\n)");
 
 const QString VUtils::c_headerRegExp = QString("^(#{1,6})\\s+(((\\d+\\.)+(?=\\s))?\\s*(\\S.*)?)$");

+ 8 - 0
src/utils/vutils.h

@@ -340,6 +340,14 @@ public:
     static const QString c_fencedCodeBlockStartRegExp;
     static const QString c_fencedCodeBlockEndRegExp;
 
+    // Regular expression for inline mathjax formula.
+    // $..$
+    static const QString c_mathjaxInlineRegExp;
+
+    // Regular expression for block mathjax formula.
+    // $$..$$
+    static const QString c_mathjaxBlockRegExp;
+
     // Regular expression for preview image block.
     static const QString c_previewImageBlockRegExp;
 

+ 6 - 0
src/vconfigmanager.cpp

@@ -635,6 +635,7 @@ void VConfigManager::updateMarkdownEditStyle()
     m_editorColorColumnBg = defaultColor;
     m_editorColorColumnFg = defaultColor;
     m_editorPreviewImageLineFg = defaultColor;
+    m_editorMathjaxFg = defaultColor;
 
     auto editorIt = styles.find("editor");
     if (editorIt != styles.end()) {
@@ -707,6 +708,11 @@ void VConfigManager::updateMarkdownEditStyle()
         if (it != editorIt->end()) {
             m_editorPreviewImageLineFg = "#" + *it;
         }
+
+        it = editorIt->find("mathjax-foreground");
+        if (it != editorIt->end()) {
+            m_editorMathjaxFg = "#" + *it;
+        }
     }
 }
 

+ 10 - 0
src/vconfigmanager.h

@@ -316,6 +316,8 @@ public:
 
     const QString &getEditorPreviewImageLineFg() const;
 
+    const QString &getEditorMathjaxFg() const;
+
     bool getEnableCodeBlockLineNumber() const;
     void setEnableCodeBlockLineNumber(bool p_enabled);
 
@@ -779,6 +781,9 @@ private:
     // The foreground color of the preview image line.
     QString m_editorPreviewImageLineFg;
 
+    // The foreground color of the MathJax.
+    QString m_editorMathjaxFg;
+
     // Icon size of tool bar in pixels.
     int m_toolBarIconSize;
 
@@ -1826,6 +1831,11 @@ inline const QString &VConfigManager::getEditorPreviewImageLineFg() const
     return m_editorPreviewImageLineFg;
 }
 
+inline const QString &VConfigManager::getEditorMathjaxFg() const
+{
+    return m_editorMathjaxFg;
+}
+
 inline bool VConfigManager::getEnableCodeBlockLineNumber() const
 {
     return m_enableCodeBlockLineNumber;

+ 5 - 1
src/vconstants.h

@@ -121,7 +121,11 @@ enum HighlightBlockState
     Comment,
 
     // Verbatim code block.
-    Verbatim
+    Verbatim,
+
+    // Mathjax. It means the pending state of the block.
+    MathjaxBlock,
+    MathjaxInline
 };
 
 // Pages to open on start up.

+ 1 - 0
src/vmdeditor.cpp

@@ -63,6 +63,7 @@ VMdEditor::VMdEditor(VFile *p_file,
                                                 g_config->getCodeBlockStyles(),
                                                 g_config->getMarkdownHighlightInterval(),
                                                 document());
+    m_mdHighlighter->setMathjaxEnabled(g_config->getEnableMathjax());
 
     connect(m_mdHighlighter, &HGMarkdownHighlighter::headersUpdated,
             this, &VMdEditor::updateHeaders);

+ 82 - 0
src/vtextblockdata.h

@@ -129,6 +129,45 @@ struct VPreviewInfo
 };
 
 
+struct MathjaxInfo
+{
+public:
+    MathjaxInfo()
+        : m_isBlock(false),
+          m_index(-1),
+          m_length(0)
+    {
+    }
+
+
+    bool isValid() const
+    {
+        return m_index >= 0 && m_length > 0;
+    }
+
+    bool isBlock() const
+    {
+        return m_isBlock;
+    }
+
+    void clear()
+    {
+        m_isBlock = false;
+        m_index = -1;
+        m_length = 0;
+    }
+
+    // Inline or block formula.
+    bool m_isBlock;
+
+    // Start index wihtin block, including the start mark.
+    int m_index;
+
+    // Length of this mathjax, including the end mark.
+    int m_length;
+};
+
+
 // User data for each block.
 class VTextBlockData : public QTextBlockUserData
 {
@@ -153,6 +192,16 @@ public:
 
     void setCodeBlockIndentation(int p_indent);
 
+    void clearMathjax();
+
+    const MathjaxInfo &getPendingMathjax() const;
+
+    void setPendingMathjax(const MathjaxInfo &p_info);
+
+    const QVector<MathjaxInfo> getMathjax() const;
+
+    void addMathjax(const MathjaxInfo &p_info);
+
 private:
     // Check the order of elements.
     bool checkOrder() const;
@@ -162,6 +211,12 @@ private:
 
     // Indentation of the this code block if this block is a fenced code block.
     int m_codeBlockIndentation;
+
+    // Pending Mathjax info, such as this block is the start of a Mathjax formula.
+    MathjaxInfo m_pendingMathjax;
+
+    // Mathjax info ends in this block.
+    QVector<MathjaxInfo> m_mathjax;
 };
 
 inline const QVector<VPreviewInfo *> &VTextBlockData::getPreviews() const
@@ -178,4 +233,31 @@ inline void VTextBlockData::setCodeBlockIndentation(int p_indent)
 {
     m_codeBlockIndentation = p_indent;
 }
+
+inline void VTextBlockData::clearMathjax()
+{
+    m_pendingMathjax.clear();
+    m_mathjax.clear();
+}
+
+inline const MathjaxInfo &VTextBlockData::getPendingMathjax() const
+{
+    return m_pendingMathjax;
+}
+
+inline void VTextBlockData::setPendingMathjax(const MathjaxInfo &p_info)
+{
+    m_pendingMathjax = p_info;
+}
+
+inline const QVector<MathjaxInfo> VTextBlockData::getMathjax() const
+{
+    return m_mathjax;
+}
+
+inline void VTextBlockData::addMathjax(const MathjaxInfo &p_info)
+{
+    m_mathjax.append(p_info);
+}
+
 #endif // VTEXTBLOCKDATA_H