diff --git a/src/hgmarkdownhighlighter.cpp b/src/hgmarkdownhighlighter.cpp index e9ba5dca..febabe9b 100644 --- a/src/hgmarkdownhighlighter.cpp +++ b/src/hgmarkdownhighlighter.cpp @@ -185,8 +185,13 @@ void HGMarkdownHighlighter::highlightBlock(const QString &text) if (isVerbatimBlock(curBlock)) { setCurrentBlockState(HighlightBlockState::Verbatim); goto exit; - } else if (m_enableMathjax) { - highlightMathJax(text); + } + + highlightHeaderFast(blockNum, text); + + if (m_enableMathjax + && currentBlockState() == HighlightBlockState::Normal) { + highlightMathJax(curBlock, text); } } @@ -195,8 +200,6 @@ void HGMarkdownHighlighter::highlightBlock(const QString &text) // fix this. // highlightLinkWithSpacesInURL(text); - highlightHeaderFast(blockNum, text); - if (currentBlockState() != HighlightBlockState::CodeBlock) { goto exit; } @@ -304,7 +307,6 @@ void HGMarkdownHighlighter::initBlockHighlightFromResult(int nrBlocks) void HGMarkdownHighlighter::initHtmlCommentRegionsFromResult() { - // From Qt5.7, the capacity is preserved. m_commentRegions.clear(); if (!result) { @@ -318,12 +320,9 @@ void HGMarkdownHighlighter::initHtmlCommentRegionsFromResult() continue; } - m_commentRegions.push_back(VElementRegion(elem->pos, elem->end)); - + initBlockElementRegionOne(m_commentRegions, elem->pos, elem->end); elem = elem->next; } - - qDebug() << "highlighter: parse" << m_commentRegions.size() << "HTML comment regions"; } void HGMarkdownHighlighter::initImageRegionsFromResult() @@ -523,7 +522,7 @@ static bool intersect(const QList> &p_indices, int &p_start, int return false; } -void HGMarkdownHighlighter::highlightMathJax(const QString &p_text) +void HGMarkdownHighlighter::highlightMathJax(const QTextBlock &p_block, const QString &p_text) { const int blockMarkLength = 2; const int inlineMarkLength = 1; @@ -531,6 +530,8 @@ void HGMarkdownHighlighter::highlightMathJax(const QString &p_text) VTextBlockData *blockData = currentBlockData(); Q_ASSERT(blockData); + int blockNumber = p_block.blockNumber(); + int startIdx = 0; // Next position to search. int pos = 0; @@ -542,71 +543,84 @@ void HGMarkdownHighlighter::highlightMathJax(const QString &p_text) // Mathjax block formula. if (state != HighlightBlockState::MathjaxBlock) { fromPreBlock = false; - startIdx = m_mathjaxBlockExp.indexIn(p_text); - pos = startIdx + m_mathjaxBlockExp.matchedLength(); - startIdx = pos - blockMarkLength; + startIdx = findMathjaxMarker(blockNumber, p_text, pos, m_mathjaxBlockExp, blockMarkLength); + pos = startIdx + blockMarkLength; } while (startIdx >= 0) { - int endIdx = m_mathjaxBlockExp.indexIn(p_text, pos); + int endIdx = findMathjaxMarker(blockNumber, p_text, pos, m_mathjaxBlockExp, blockMarkLength); int mathLength = 0; MathjaxInfo info; + bool valid = false; if (endIdx == -1) { - setCurrentBlockState(HighlightBlockState::MathjaxBlock); mathLength = p_text.length() - startIdx; - pos = startIdx + mathLength; + pos = p_text.length(); - info.m_previewedAsBlock = false; - info.m_index = startIdx, - info.m_length = mathLength; - if (fromPreBlock) { - VTextBlockData *preBlockData = previousBlockData(); - Q_ASSERT(preBlockData); - const MathjaxInfo &preInfo = preBlockData->getPendingMathjax(); - info.m_text = preInfo.text() + "\n" + p_text.mid(startIdx, mathLength); - } else { - info.m_text = p_text.mid(startIdx, mathLength); + if (isValidMathjaxRegion(blockNumber, startIdx, pos)) { + valid = true; + setCurrentBlockState(HighlightBlockState::MathjaxBlock); + info.m_previewedAsBlock = false; + info.m_index = startIdx, + info.m_length = mathLength; + if (fromPreBlock) { + VTextBlockData *preBlockData = previousBlockData(); + Q_ASSERT(preBlockData); + const MathjaxInfo &preInfo = preBlockData->getPendingMathjax(); + info.m_text = preInfo.text() + "\n" + p_text.mid(startIdx, mathLength); + } else { + info.m_text = p_text.mid(startIdx, mathLength); + } + + blockData->setPendingMathjax(info); } - - blockData->setPendingMathjax(info); } else { // Found end marker of a formula. - mathLength = endIdx - startIdx + m_mathjaxBlockExp.matchedLength(); - pos = startIdx + mathLength; + pos = endIdx + blockMarkLength; + mathLength = pos - startIdx; - info.m_previewedAsBlock = false; - info.m_index = startIdx; - info.m_length = mathLength; - if (fromPreBlock) { - // A cross-block formula. - if (pos >= p_text.length()) { - info.m_previewedAsBlock = true; + if (isValidMathjaxRegion(blockNumber, startIdx, pos)) { + valid = true; + info.m_previewedAsBlock = false; + info.m_index = startIdx; + info.m_length = mathLength; + if (fromPreBlock) { + // A cross-block formula. + if (pos >= p_text.length()) { + info.m_previewedAsBlock = true; + } + + VTextBlockData *preBlockData = previousBlockData(); + Q_ASSERT(preBlockData); + const MathjaxInfo &preInfo = preBlockData->getPendingMathjax(); + info.m_text = preInfo.text() + "\n" + p_text.mid(startIdx, mathLength); + } else { + // A formula within one block. + if (pos >= p_text.length() && startIdx == 0) { + info.m_previewedAsBlock = true; + } + + info.m_text = p_text.mid(startIdx, mathLength); } - VTextBlockData *preBlockData = previousBlockData(); - Q_ASSERT(preBlockData); - const MathjaxInfo &preInfo = preBlockData->getPendingMathjax(); - info.m_text = preInfo.text() + "\n" + p_text.mid(startIdx, mathLength); - } else { - // A formula within one block. - if (pos >= p_text.length() && startIdx == 0) { - info.m_previewedAsBlock = true; - } - - info.m_text = p_text.mid(startIdx, mathLength); + blockData->addMathjax(info); } - - blockData->addMathjax(info); } fromPreBlock = false; - blockIdices.append(QPair(startIdx, pos)); + if (valid) { + blockIdices.append(QPair(startIdx, pos)); + setFormat(startIdx, mathLength, m_mathjaxFormat); + startIdx = findMathjaxMarker(blockNumber, p_text, pos, m_mathjaxBlockExp, blockMarkLength); + pos = startIdx + blockMarkLength; + } else { + // Make the second mark as the first one and try again. + if (endIdx == -1) { + break; + } - setFormat(startIdx, mathLength, m_mathjaxFormat); - startIdx = m_mathjaxBlockExp.indexIn(p_text, pos); - pos = startIdx + m_mathjaxBlockExp.matchedLength(); - startIdx = pos - blockMarkLength; + startIdx = pos - blockMarkLength; + } } // Mathjax inline formula. @@ -615,24 +629,25 @@ void HGMarkdownHighlighter::highlightMathJax(const QString &p_text) fromPreBlock = true; if (state != HighlightBlockState::MathjaxInline) { fromPreBlock = false; - startIdx = m_mathjaxInlineExp.indexIn(p_text); - pos = startIdx + m_mathjaxInlineExp.matchedLength(); - startIdx = pos - inlineMarkLength; + startIdx = findMathjaxMarker(blockNumber, p_text, pos, m_mathjaxInlineExp, inlineMarkLength); + pos = startIdx + inlineMarkLength; } while (startIdx >= 0) { - int endIdx = m_mathjaxInlineExp.indexIn(p_text, pos); + int endIdx = findMathjaxMarker(blockNumber, p_text, pos, m_mathjaxInlineExp, inlineMarkLength); int mathLength = 0; + bool valid = false; if (endIdx == -1) { - setCurrentBlockState(HighlightBlockState::MathjaxBlock); mathLength = p_text.length() - startIdx; + pos = p_text.length(); } else { - mathLength = endIdx - startIdx + m_mathjaxInlineExp.matchedLength(); + pos = endIdx + inlineMarkLength; + mathLength = pos - startIdx; } - pos = startIdx + mathLength; + valid = isValidMathjaxRegion(blockNumber, startIdx, pos); // Check if it intersect with blocks. - if (!intersect(blockIdices, startIdx, pos)) { + if (valid && !intersect(blockIdices, startIdx, pos)) { // A valid inline mathjax. MathjaxInfo info; if (endIdx == -1) { @@ -769,6 +784,12 @@ void HGMarkdownHighlighter::parse(bool p_fast) initHeaderRegionsFromResult(); initVerbatimBlocksFromResult(); + + initInlineCodeRegionsFromResult(); + + initBoldItalicRegionsFromResult(); + + initLinkRegionsFromResult(); } if (result) { @@ -989,11 +1010,12 @@ bool HGMarkdownHighlighter::isBlockInsideCommentRegion(const QTextBlock &p_block return false; } - int start = p_block.position(); - int end = start + p_block.length(); - - for (auto const & reg : m_commentRegions) { - if (reg.contains(start) && reg.contains(end)) { + auto it = m_commentRegions.find(p_block.blockNumber()); + if (it != m_commentRegions.end()) { + const QVector ®s = it.value(); + if (regs.size() == 1 + && regs[0].m_startPos == 0 + && regs[0].m_endPos == p_block.length()) { return true; } } @@ -1051,18 +1073,20 @@ bool HGMarkdownHighlighter::isValidHeader(const QString &p_text) void HGMarkdownHighlighter::highlightHeaderFast(int p_blockNumber, const QString &p_text) { - if (currentBlockState() != HighlightBlockState::Normal) { - return; - } - auto it = m_headerBlocks.find(p_blockNumber); if (it != m_headerBlocks.end()) { const HeaderBlockInfo &info = it.value(); if (!isValidHeader(p_text)) { // Set an empty format to clear formats. It seems to work. setFormat(0, p_text.size(), QTextCharFormat()); - } else if (info.m_length < p_text.size()) { - setFormat(info.m_length, p_text.size() - info.m_length, m_headerStyles[info.m_level]); + } else { + if (info.m_length < p_text.size()) { + setFormat(info.m_length, + p_text.size() - info.m_length, + m_headerStyles[info.m_level]); + } + + setCurrentBlockState(HighlightBlockState::Header); } } } @@ -1092,3 +1116,228 @@ void HGMarkdownHighlighter::updateMathjaxBlocks() emit mathjaxBlocksUpdated(blocks); } + +void HGMarkdownHighlighter::initInlineCodeRegionsFromResult() +{ + m_inlineCodeRegions.clear(); + + if (!result) { + return; + } + + pmh_element *elem = result[pmh_CODE]; + while (elem != NULL) { + if (elem->end <= elem->pos) { + elem = elem->next; + continue; + } + + initBlockElementRegionOne(m_inlineCodeRegions, elem->pos, elem->end); + elem = elem->next; + } +} + +void HGMarkdownHighlighter::initBoldItalicRegionsFromResult() +{ + m_boldItalicRegions.clear(); + + if (!result) { + return; + } + + pmh_element_type types[2] = {pmh_EMPH, pmh_STRONG}; + + for (int i = 0; i < 2; ++i) { + pmh_element *elem = result[types[i]]; + while (elem != NULL) { + if (elem->end <= elem->pos) { + elem = elem->next; + continue; + } + + initBlockElementRegionOne(m_boldItalicRegions, elem->pos, elem->end); + elem = elem->next; + } + } +} + +void HGMarkdownHighlighter::initLinkRegionsFromResult() +{ + m_linkRegions.clear(); + + if (!result) { + return; + } + + pmh_element_type types[2] = {pmh_LINK, pmh_IMAGE}; + + for (int i = 0; i < 2; ++i) { + pmh_element *elem = result[types[i]]; + while (elem != NULL) { + if (elem->end <= elem->pos) { + elem = elem->next; + continue; + } + + initBlockElementRegionOne(m_linkRegions, elem->pos, elem->end); + elem = elem->next; + } + } +} + +void HGMarkdownHighlighter::initBlockElementRegionOne(QHash> &p_regs, + 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)document->characterCount(); + if (p_end >= nrChar && nrChar > 0) { + p_end = nrChar - 1; + } + + int startBlockNum = document->findBlock(p_pos).blockNumber(); + int endBlockNum = document->findBlock(p_end).blockNumber(); + + for (int i = startBlockNum; i <= endBlockNum; ++i) + { + QTextBlock block = document->findBlockByNumber(i); + int blockStartPos = block.position(); + + QVector ®s = p_regs[i]; + int start, end; + if (i == startBlockNum) { + start = p_pos - blockStartPos; + end = (startBlockNum == endBlockNum) ? (p_end - blockStartPos) + : block.length(); + } else if (i == endBlockNum) { + start = 0; + end = p_end - blockStartPos; + } else { + start = 0; + end = block.length(); + } + + regs.append(VElementRegion(start, end)); + } +} + +static bool indexInsideRegion(const QVector &p_regions, int p_idx) +{ + for (auto const & reg : p_regions) { + if (reg.contains(p_idx)) { + return true; + } + } + + return false; +} + +int HGMarkdownHighlighter::findMathjaxMarker(int p_blockNumber, + const QString &p_text, + int p_pos, + QRegExp &p_reg, + int p_markerLength) +{ + if (p_pos >= p_text.size()) { + return -1; + } + + int idx = -1; + auto inlineCodeIt = m_inlineCodeRegions.find(p_blockNumber); + auto commentIt = m_commentRegions.find(p_blockNumber); + auto boldItalicIt = m_boldItalicRegions.find(p_blockNumber); + auto linkIt = m_linkRegions.find(p_blockNumber); + + while (p_pos < p_text.size()) { + idx = p_reg.indexIn(p_text, p_pos); + if (idx == -1) { + return idx; + } + + p_pos = idx + p_reg.matchedLength(); + idx = p_pos - p_markerLength; + + // Check if this idx is legal. + // Check inline code. + if (inlineCodeIt != m_inlineCodeRegions.end()) { + if (indexInsideRegion(inlineCodeIt.value(), idx)) { + idx = -1; + continue; + } + } + + if (commentIt != m_commentRegions.end()) { + if (indexInsideRegion(commentIt.value(), idx)) { + idx = -1; + continue; + } + } + + if (boldItalicIt != m_boldItalicRegions.end()) { + if (indexInsideRegion(boldItalicIt.value(), idx)) { + idx = -1; + continue; + } + } + + if (linkIt != m_linkRegions.end()) { + if (indexInsideRegion(linkIt.value(), idx)) { + idx = -1; + continue; + } + } + + break; + } + + return idx; +} + +static bool intersectRegion(const QVector &p_regions, + int p_start, + int p_end) +{ + for (auto const & reg : p_regions) { + if (reg.intersect(p_start, p_end)) { + return true; + } + } + + return false; +} + +bool HGMarkdownHighlighter::isValidMathjaxRegion(int p_blockNumber, + int p_start, + int p_end) +{ + auto inlineCodeIt = m_inlineCodeRegions.find(p_blockNumber); + if (inlineCodeIt != m_inlineCodeRegions.end()) { + if (intersectRegion(inlineCodeIt.value(), p_start, p_end)) { + return false; + } + } + + auto commentIt = m_commentRegions.find(p_blockNumber); + if (commentIt != m_commentRegions.end()) { + if (intersectRegion(commentIt.value(), p_start, p_end)) { + return false; + } + } + + auto boldItalicIt = m_boldItalicRegions.find(p_blockNumber); + if (boldItalicIt != m_boldItalicRegions.end()) { + if (intersectRegion(boldItalicIt.value(), p_start, p_end)) { + return false; + } + } + + auto linkIt = m_linkRegions.find(p_blockNumber); + if (linkIt != m_linkRegions.end()) { + if (intersectRegion(linkIt.value(), p_start, p_end)) { + return false; + } + } + + return true; +} diff --git a/src/hgmarkdownhighlighter.h b/src/hgmarkdownhighlighter.h index 1283f73e..d48bc723 100644 --- a/src/hgmarkdownhighlighter.h +++ b/src/hgmarkdownhighlighter.h @@ -148,7 +148,12 @@ struct VElementRegion // Whether this region contains @p_pos. bool contains(int p_pos) const { - return m_startPos <= p_pos && m_endPos >= p_pos; + 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 @@ -168,6 +173,11 @@ struct VElementRegion return false; } } + + QString toString() const + { + return QString("[%1,%2)").arg(m_startPos).arg(m_endPos); + } }; class HGMarkdownHighlighter : public QSyntaxHighlighter @@ -277,9 +287,6 @@ private: int m_numOfCodeBlockHighlightsToRecv; - // All HTML comment regions. - QVector m_commentRegions; - // All image link regions. QVector m_imageRegions; @@ -311,6 +318,18 @@ private: bool m_enableMathjax; + // Inline code regions for each block. + // VElementRegion's position is relative position within a block. + QHash> m_inlineCodeRegions; + + QHash> m_boldItalicRegions; + + // Including links and images. + QHash> m_linkRegions; + + // Comment regions for each block. + QHash> m_commentRegions; + char *content; int capacity; pmh_element **result; @@ -321,7 +340,7 @@ private: void highlightCodeBlock(const QTextBlock &p_block, const QString &p_text); - void highlightMathJax(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 @@ -358,6 +377,19 @@ private: // Fetch all the verbatim blocks from parsing result. void initVerbatimBlocksFromResult(); + // Fetch all the inlnie code regions from parsing result. + void initInlineCodeRegionsFromResult(); + + // Fetch all bold/italic regions from parsing result. + void initBoldItalicRegionsFromResult(); + + // Fetch all bold/italic regions from parsing result. + void initLinkRegionsFromResult(); + + void initBlockElementRegionOne(QHash> &p_regs, + unsigned long p_pos, + unsigned long p_end); + // Whether @p_block is totally inside a HTML comment. bool isBlockInsideCommentRegion(const QTextBlock &p_block) const; @@ -385,6 +417,14 @@ private: // Highlight headers using regular expression first instead of waiting for // another parse. void highlightHeaderFast(int p_blockNumber, const QString &p_text); + + int findMathjaxMarker(int p_blockNumber, + const QString &p_text, + int p_pos, + QRegExp &p_reg, + int p_markerLength); + + bool isValidMathjaxRegion(int p_blockNumber, int p_start, int p_end); }; inline const QVector &HGMarkdownHighlighter::getHeaderRegions() const diff --git a/src/resources/themes/v_moonlight/v_moonlight.mdhl b/src/resources/themes/v_moonlight/v_moonlight.mdhl index 7ecba233..eb69c559 100644 --- a/src/resources/themes/v_moonlight/v_moonlight.mdhl +++ b/src/resources/themes/v_moonlight/v_moonlight.mdhl @@ -38,7 +38,7 @@ color-column-foreground: eeeeee # [VNote] Style for preview image line preview-image-line-foreground: 6f5799 # [VNote] Style for MathJax -mathjax-foreground: ce93d8 +mathjax-foreground: 4db6ac editor-selection foreground: dadada diff --git a/src/resources/themes/v_native/v_native.mdhl b/src/resources/themes/v_native/v_native.mdhl index 17afb968..05b229ff 100644 --- a/src/resources/themes/v_native/v_native.mdhl +++ b/src/resources/themes/v_native/v_native.mdhl @@ -37,7 +37,7 @@ color-column-foreground: ffff00 # [VNote] Style for preview image line preview-image-line-foreground: 9575cd # [VNote] Style for MathJax -mathjax-foreground: 8e24aa +mathjax-foreground: 00897b editor-selection foreground: eeeeee diff --git a/src/resources/themes/v_pure/v_pure.mdhl b/src/resources/themes/v_pure/v_pure.mdhl index 9b77b2bc..a2d8a19f 100644 --- a/src/resources/themes/v_pure/v_pure.mdhl +++ b/src/resources/themes/v_pure/v_pure.mdhl @@ -38,7 +38,7 @@ color-column-foreground: ffff00 # [VNote] Style for preview image line preview-image-line-foreground: 9575cd # [VNote] Style for MathJax -mathjax-foreground: 8e24aa +mathjax-foreground: 00897b editor-selection foreground: eeeeee diff --git a/src/vconstants.h b/src/vconstants.h index b171efcc..05ed2ebf 100644 --- a/src/vconstants.h +++ b/src/vconstants.h @@ -125,7 +125,10 @@ enum HighlightBlockState // Mathjax. It means the pending state of the block. MathjaxBlock, - MathjaxInline + MathjaxInline, + + // Header. + Header }; // Pages to open on start up. diff --git a/src/vmathjaxpreviewhelper.cpp b/src/vmathjaxpreviewhelper.cpp index c0f84b16..d107985d 100644 --- a/src/vmathjaxpreviewhelper.cpp +++ b/src/vmathjaxpreviewhelper.cpp @@ -43,12 +43,6 @@ void VMathJaxPreviewHelper::doInit() m_webView->hide(); m_webView->setFocusPolicy(Qt::NoFocus); - if (focusWid) { - focusWid->setFocus(); - } else { - m_parentWidget->setFocus(); - } - m_webDoc = new VMathJaxWebDocument(m_webView); connect(m_webDoc, &VMathJaxWebDocument::mathjaxPreviewResultReady, this, [this](int p_identifier, @@ -75,6 +69,12 @@ void VMathJaxPreviewHelper::doInit() m_webView->page()->setWebChannel(channel); m_webView->setHtml(VUtils::generateMathJaxPreviewTemplate(), QUrl("qrc:/resources")); + + if (focusWid) { + focusWid->setFocus(); + } else { + m_parentWidget->setFocus(); + } } void VMathJaxPreviewHelper::previewMathJax(int p_identifier,