Browse Source

bugfix: fix nested fenced code block issue

The opening ``` and closing ``` should have the same indentation.
Le Tan 8 years ago
parent
commit
4730d67393
4 changed files with 59 additions and 28 deletions
  1. 49 28
      src/hgmarkdownhighlighter.cpp
  2. 2 0
      src/hgmarkdownhighlighter.h
  3. 4 0
      src/utils/vutils.cpp
  4. 4 0
      src/utils/vutils.h

+ 49 - 28
src/hgmarkdownhighlighter.cpp

@@ -4,6 +4,7 @@
 #include <algorithm>
 #include "hgmarkdownhighlighter.h"
 #include "vconfigmanager.h"
+#include "utils/vutils.h"
 
 extern VConfigManager vconfig;
 
@@ -31,8 +32,9 @@ HGMarkdownHighlighter::HGMarkdownHighlighter(const QVector<HighlightingStyle> &s
       m_codeBlockStyles(codeBlockStyles), m_numOfCodeBlockHighlightsToRecv(0),
       parsing(0), waitInterval(waitInterval), content(NULL), capacity(0), result(NULL)
 {
-    codeBlockStartExp = QRegExp("^\\s*```(\\S*)");
-    codeBlockEndExp = QRegExp("^\\s*```$");
+    codeBlockStartExp = QRegExp(VUtils::c_fencedCodeBlockStartRegExp);
+    codeBlockEndExp = QRegExp(VUtils::c_fencedCodeBlockEndRegExp);
+
     codeBlockFormat.setForeground(QBrush(Qt::darkYellow));
     for (int index = 0; index < styles.size(); ++index) {
         const pmh_element_type &eleType = styles[index].type;
@@ -223,34 +225,48 @@ void HGMarkdownHighlighter::initBlockHighlihgtOne(unsigned long pos, unsigned lo
 
 void HGMarkdownHighlighter::highlightCodeBlock(const QString &text)
 {
-    int nextIndex = 0;
-    int startIndex = 0;
-    if (previousBlockState() != HighlightBlockState::CodeBlock) {
-        startIndex = codeBlockStartExp.indexIn(text);
-        if (startIndex >= 0) {
-            nextIndex = startIndex + codeBlockStartExp.matchedLength();
-        } else {
-            nextIndex = -1;
-        }
-    }
-
-    while (nextIndex >= 0) {
-        int endIndex = codeBlockEndExp.indexIn(text, nextIndex);
-        int codeBlockLength;
-        if (endIndex == -1) {
-            setCurrentBlockState(HighlightBlockState::CodeBlock);
-            codeBlockLength = text.length() - startIndex;
+    static int startLeadingSpaces = -1;
+    int length = 0;
+    int index = -1;
+    int preState = previousBlockState();
+    int state = HighlightBlockState::Normal;
+
+    if (preState != HighlightBlockState::CodeBlock
+        && preState != HighlightBlockState::CodeBlockStart) {
+        // Need to find a new code block start.
+        index = codeBlockStartExp.indexIn(text);
+        if (index >= 0) {
+            // Start a new code block.
+            length = text.length();
+            state = HighlightBlockState::CodeBlockStart;
+
+            // The leading spaces of code block start and end must be identical.
+            startLeadingSpaces = codeBlockStartExp.capturedTexts()[1].size();
         } else {
-            codeBlockLength = endIndex - startIndex + codeBlockEndExp.matchedLength();
+            // A normal block.
+            startLeadingSpaces = -1;
+            return;
         }
-        setFormat(startIndex, codeBlockLength, codeBlockFormat);
-        startIndex = codeBlockStartExp.indexIn(text, startIndex + codeBlockLength);
-        if (startIndex >= 0) {
-            nextIndex = startIndex + codeBlockStartExp.matchedLength();
+    } else {
+        // Need to find a code block end.
+        index = codeBlockEndExp.indexIn(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();
+            state = HighlightBlockState::CodeBlockEnd;
         } else {
-            nextIndex = -1;
+            // Within code block.
+            index = 0;
+            length = text.length();
+            state = HighlightBlockState::CodeBlock;
         }
     }
+
+    setCurrentBlockState(state);
+    setFormat(index, length, codeBlockFormat);
 }
 
 void HGMarkdownHighlighter::highlightLinkWithSpacesInURL(const QString &p_text)
@@ -369,6 +385,7 @@ bool HGMarkdownHighlighter::updateCodeBlocks()
 
     VCodeBlock item;
     bool inBlock = false;
+    int startLeadingSpaces = -1;
 
     // Only handle complete codeblocks.
     QTextBlock block = document->firstBlock();
@@ -377,13 +394,14 @@ bool HGMarkdownHighlighter::updateCodeBlocks()
         if (inBlock) {
             item.m_text = item.m_text + "\n" + text;
             int idx = codeBlockEndExp.indexIn(text);
-            if (idx >= 0) {
+            if (idx >= 0 && codeBlockEndExp.capturedTexts()[1].size() == startLeadingSpaces) {
                 // End block.
                 inBlock = false;
                 item.m_endBlock = block.blockNumber();
 
                 // See if it is a code block inside HTML comment.
                 if (!isBlockInsideCommentRegion(block)) {
+                    qDebug() << "add one code block in lang" << item.m_lang;
                     codeBlocks.append(item);
                 }
             }
@@ -395,11 +413,14 @@ bool HGMarkdownHighlighter::updateCodeBlocks()
                 item.m_startBlock = block.blockNumber();
                 item.m_startPos = block.position();
                 item.m_text = text;
-                if (codeBlockStartExp.captureCount() == 1) {
-                    item.m_lang = codeBlockStartExp.capturedTexts()[1];
+                if (codeBlockStartExp.captureCount() == 2) {
+                    item.m_lang = codeBlockStartExp.capturedTexts()[2];
                 }
+
+                startLeadingSpaces = codeBlockStartExp.capturedTexts()[1].size();
             }
         }
+
         block = block.next();
     }
 

+ 2 - 0
src/hgmarkdownhighlighter.h

@@ -28,7 +28,9 @@ enum HighlightBlockState
     Normal = 0,
 
     // A fenced code block.
+    CodeBlockStart,
     CodeBlock,
+    CodeBlockEnd,
 
     // This block is inside a HTML comment region.
     Comment

+ 4 - 0
src/utils/vutils.cpp

@@ -29,6 +29,10 @@ const QString VUtils::c_imageLinkRegExp = QString("\\!\\[([^\\]]*)\\]\\(([^\\)\"
 
 const QString VUtils::c_fileNameRegExp = QString("[^\\\\/:\\*\\?\"<>\\|]*");
 
+const QString VUtils::c_fencedCodeBlockStartRegExp = QString("^(\\s*)```([^`\\s]*)\\s*[^`]*$");
+
+const QString VUtils::c_fencedCodeBlockEndRegExp = QString("^(\\s*)```$");
+
 VUtils::VUtils()
 {
 }

+ 4 - 0
src/utils/vutils.h

@@ -109,6 +109,10 @@ public:
     // Forbidden char: \/:*?"<>|
     static const QString c_fileNameRegExp;
 
+    // Regular expression for fenced code block.
+    static const QString c_fencedCodeBlockStartRegExp;
+    static const QString c_fencedCodeBlockEndRegExp;
+
 private:
     // <value, name>
     static const QVector<QPair<QString, QString>> c_availableLanguages;