fix MathJax highlight with inline code, HTML comment, link, bold, and italic elements

This commit is contained in:
Le Tan 2018-04-19 20:31:50 +08:00
parent 80e65faca2
commit 67e40cbe87
7 changed files with 381 additions and 89 deletions

View File

@ -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<QPair<int, int>> &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,20 +543,22 @@ 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();
if (isValidMathjaxRegion(blockNumber, startIdx, pos)) {
valid = true;
setCurrentBlockState(HighlightBlockState::MathjaxBlock);
info.m_previewedAsBlock = false;
info.m_index = startIdx,
info.m_length = mathLength;
@ -569,11 +572,14 @@ void HGMarkdownHighlighter::highlightMathJax(const QString &p_text)
}
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;
if (isValidMathjaxRegion(blockNumber, startIdx, pos)) {
valid = true;
info.m_previewedAsBlock = false;
info.m_index = startIdx;
info.m_length = mathLength;
@ -598,16 +604,24 @@ void HGMarkdownHighlighter::highlightMathJax(const QString &p_text)
blockData->addMathjax(info);
}
}
fromPreBlock = false;
if (valid) {
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 = 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;
}
startIdx = pos - blockMarkLength;
}
}
// Mathjax inline formula.
startIdx = 0;
@ -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<VElementRegion> &regs = 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<int, QVector<VElementRegion>> &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<VElementRegion> &regs = 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<VElementRegion> &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<VElementRegion> &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;
}

View File

@ -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<VElementRegion> m_commentRegions;
// All image link regions.
QVector<VElementRegion> 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<int, QVector<VElementRegion>> m_inlineCodeRegions;
QHash<int, QVector<VElementRegion>> m_boldItalicRegions;
// Including links and images.
QHash<int, QVector<VElementRegion>> m_linkRegions;
// Comment regions for each block.
QHash<int, QVector<VElementRegion>> 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<int, QVector<VElementRegion>> &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<VElementRegion> &HGMarkdownHighlighter::getHeaderRegions() const

View File

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

View File

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

View File

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

View File

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

View File

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