From 9566e6f5d2516387c7df1aebfaf0bd1a4e63bf25 Mon Sep 17 00:00:00 2001 From: Le Tan Date: Thu, 12 Apr 2018 19:32:27 +0800 Subject: [PATCH] highlight: highlight MathJax formula in editor --- src/hgmarkdownhighlighter.cpp | 133 ++++++++++++++++-- src/hgmarkdownhighlighter.h | 19 ++- .../themes/v_moonlight/v_moonlight.mdhl | 4 +- src/resources/themes/v_native/v_native.mdhl | 4 +- src/resources/themes/v_pure/v_pure.mdhl | 4 +- src/utils/vutils.cpp | 4 + src/utils/vutils.h | 8 ++ src/vconfigmanager.cpp | 6 + src/vconfigmanager.h | 10 ++ src/vconstants.h | 6 +- src/vmdeditor.cpp | 1 + src/vtextblockdata.h | 82 +++++++++++ 12 files changed, 264 insertions(+), 17 deletions(-) diff --git a/src/hgmarkdownhighlighter.cpp b/src/hgmarkdownhighlighter.cpp index 96bce5e9..6b413ae9 100644 --- a/src/hgmarkdownhighlighter.cpp +++ b/src/hgmarkdownhighlighter.cpp @@ -35,6 +35,7 @@ HGMarkdownHighlighter::HGMarkdownHighlighter(const QVector &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 &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 &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 &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> &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> 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(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(); diff --git a/src/hgmarkdownhighlighter.h b/src/hgmarkdownhighlighter.h index 3badf08e..abe36502 100644 --- a/src/hgmarkdownhighlighter.h +++ b/src/hgmarkdownhighlighter.h @@ -155,6 +155,8 @@ public: QVector &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 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 diff --git a/src/resources/themes/v_moonlight/v_moonlight.mdhl b/src/resources/themes/v_moonlight/v_moonlight.mdhl index 6875b11f..7ecba233 100644 --- a/src/resources/themes/v_moonlight/v_moonlight.mdhl +++ b/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 diff --git a/src/resources/themes/v_native/v_native.mdhl b/src/resources/themes/v_native/v_native.mdhl index 299fc2b7..17afb968 100644 --- a/src/resources/themes/v_native/v_native.mdhl +++ b/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 diff --git a/src/resources/themes/v_pure/v_pure.mdhl b/src/resources/themes/v_pure/v_pure.mdhl index d8311139..9b77b2bc 100644 --- a/src/resources/themes/v_pure/v_pure.mdhl +++ b/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 diff --git a/src/utils/vutils.cpp b/src/utils/vutils.cpp index 144b0c44..efa70cc6 100644 --- a/src/utils/vutils.cpp +++ b/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.*)?)$"); diff --git a/src/utils/vutils.h b/src/utils/vutils.h index 7ae5ec34..4e776efd 100644 --- a/src/utils/vutils.h +++ b/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; diff --git a/src/vconfigmanager.cpp b/src/vconfigmanager.cpp index 3ce07083..511fff4c 100644 --- a/src/vconfigmanager.cpp +++ b/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; + } } } diff --git a/src/vconfigmanager.h b/src/vconfigmanager.h index 4c7dad67..ecc47b23 100644 --- a/src/vconfigmanager.h +++ b/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; diff --git a/src/vconstants.h b/src/vconstants.h index ef36f4e4..b171efcc 100644 --- a/src/vconstants.h +++ b/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. diff --git a/src/vmdeditor.cpp b/src/vmdeditor.cpp index fabc2cb0..ff76ebc2 100644 --- a/src/vmdeditor.cpp +++ b/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); diff --git a/src/vtextblockdata.h b/src/vtextblockdata.h index 7b447212..067cd04a 100644 --- a/src/vtextblockdata.h +++ b/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 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 m_mathjax; }; inline const QVector &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 VTextBlockData::getMathjax() const +{ + return m_mathjax; +} + +inline void VTextBlockData::addMathjax(const MathjaxInfo &p_info) +{ + m_mathjax.append(p_info); +} + #endif // VTEXTBLOCKDATA_H