highlight: highlight MathJax formula in editor

This commit is contained in:
Le Tan 2018-04-12 19:32:27 +08:00
parent 05e4159530
commit 9566e6f5d2
12 changed files with 264 additions and 17 deletions

View File

@ -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)) {
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();

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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.*)?)$");

View File

@ -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;

View File

@ -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;
}
}
}

View File

@ -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;

View File

@ -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.

View File

@ -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);

View File

@ -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