#include "pegmarkdownhighlighter.h" #include #include #include #include "pegparser.h" #include "vconfigmanager.h" #include "utils/vutils.h" #include "utils/veditutils.h" #include "vmdeditor.h" extern VConfigManager *g_config; #define LARGE_BLOCK_NUMBER 1000 PegMarkdownHighlighter::PegMarkdownHighlighter(QTextDocument *p_doc, VMdEditor *p_editor) : QSyntaxHighlighter(p_doc), m_doc(p_doc), m_editor(p_editor), m_timeStamp(0), m_codeBlockTimeStamp(0), m_parser(NULL), m_parserExts(pmh_EXT_NOTES | pmh_EXT_STRIKE | pmh_EXT_FRONTMATTER | pmh_EXT_MARK | pmh_EXT_TABLE), m_parseInterval(50), m_notifyHighlightComplete(false), m_fastParseInterval(30) { } void PegMarkdownHighlighter::init(const QVector &p_styles, const QHash &p_codeBlockStyles, bool p_mathjaxEnabled, int p_timerInterval) { m_styles = p_styles; m_codeBlockStyles = p_codeBlockStyles; if (p_mathjaxEnabled) { m_parserExts |= (pmh_EXT_MATH | pmh_EXT_MATH_RAW); } m_parseInterval = p_timerInterval; m_codeBlockFormat.setForeground(QBrush(Qt::darkYellow)); for (int index = 0; index < m_styles.size(); ++index) { switch (m_styles[index].type) { case pmh_FENCEDCODEBLOCK: m_codeBlockFormat = m_styles[index].format; break; default: break; } } m_colorColumnFormat = m_codeBlockFormat; m_colorColumnFormat.setForeground(QColor(g_config->getEditorColorColumnFg())); m_colorColumnFormat.setBackground(QColor(g_config->getEditorColorColumnBg())); m_result.reset(new PegHighlighterResult()); m_fastResult.reset(new PegHighlighterFastResult()); m_fastParseBlocks = QPair(-1, -1); m_parser = new PegParser(this); connect(m_parser, &PegParser::parseResultReady, this, &PegMarkdownHighlighter::handleParseResult); m_timer = new QTimer(this); m_timer->setSingleShot(true); m_timer->setInterval(m_parseInterval); connect(m_timer, &QTimer::timeout, this, &PegMarkdownHighlighter::startParse); m_fastParseTimer = new QTimer(this); m_fastParseTimer->setSingleShot(true); m_fastParseTimer->setInterval(m_fastParseInterval); connect(m_fastParseTimer, &QTimer::timeout, this, [this]() { startFastParse(m_fastParseInfo.m_position, m_fastParseInfo.m_charsRemoved, m_fastParseInfo.m_charsAdded); }); m_scrollRehighlightTimer = new QTimer(this); m_scrollRehighlightTimer->setSingleShot(true); m_scrollRehighlightTimer->setInterval(5); connect(m_scrollRehighlightTimer, &QTimer::timeout, this, [this]() { if (m_result->m_numOfBlocks > LARGE_BLOCK_NUMBER) { rehighlightSensitiveBlocks(); } }); m_rehighlightTimer = new QTimer(this); m_rehighlightTimer->setSingleShot(true); m_rehighlightTimer->setInterval(10); connect(m_rehighlightTimer, &QTimer::timeout, this, &PegMarkdownHighlighter::rehighlightBlocks); connect(m_doc, &QTextDocument::contentsChange, this, &PegMarkdownHighlighter::handleContentsChange); connect(m_editor->verticalScrollBar(), &QScrollBar::valueChanged, m_scrollRehighlightTimer, static_cast(&QTimer::start)); m_contentChangeTime.start(); } // Just use parse results to highlight block. // Do not maintain block data and state here. void PegMarkdownHighlighter::highlightBlock(const QString &p_text) { QSharedPointer result(m_result); QTextBlock block = currentBlock(); int blockNum = block.blockNumber(); bool isCodeBlock = currentBlockState() == HighlightBlockState::CodeBlock; bool isNewBlock = block.userData() == NULL; VTextBlockData *blockData = VTextBlockData::blockData(block); QVector *cache = &blockData->getBlockHighlightCache(); // Fast parse can not cross multiple empty lines in code block, which // cause the wrong parse results. if (isNewBlock) { int pstate = previousBlockState(); if (pstate == HighlightBlockState::CodeBlock || pstate == HighlightBlockState::CodeBlockStart) { setCurrentBlockState(HighlightBlockState::CodeBlock); isCodeBlock = true; } } bool cacheValid = true; if (result->matched(m_timeStamp)) { if (preHighlightSingleFormatBlock(result->m_blocksHighlights, blockNum, p_text, isCodeBlock)) { cacheValid = false; } else if (blockData->isCacheValid() && blockData->getTimeStamp() == m_timeStamp) { // Use the cache to highlight. highlightBlockOne(*cache); } else { cache->clear(); highlightBlockOne(result->m_blocksHighlights, blockNum, cache); } } else { // If fast result cover this block, we do not need to use the outdated one. if (isFastParseBlock(blockNum)) { if (!preHighlightSingleFormatBlock(m_fastResult->m_blocksHighlights, blockNum, p_text, isCodeBlock)) { highlightBlockOne(m_fastResult->m_blocksHighlights, blockNum, NULL); } cacheValid = false; } else { if (preHighlightSingleFormatBlock(result->m_blocksHighlights, blockNum, p_text, isCodeBlock)) { cacheValid = false; } else if (blockData->isCacheValid() && result->matched(blockData->getTimeStamp())) { // Use the cache to highlight. highlightBlockOne(*cache); } else { cache->clear(); highlightBlockOne(result->m_blocksHighlights, blockNum, cache); } } } blockData->setCacheValid(cacheValid); PegMarkdownHighlighter::updateBlockTimeStamp(block, result->m_timeStamp); if (isCodeBlock) { QVector *cbCache = &blockData->getCodeBlockHighlightCache(); if (blockData->getCodeBlockTimeStamp() == result->m_codeBlockTimeStamp || !result->m_codeBlockHighlightReceived) { highlightCodeBlock(*cbCache, p_text); } else { cbCache->clear(); highlightCodeBlock(result, blockNum, p_text, cbCache); PegMarkdownHighlighter::updateBlockCodeBlockTimeStamp(block, result->m_codeBlockTimeStamp); } highlightCodeBlockColorColumn(p_text); } } inline static bool containSpecialChar(const QString &p_str) { QChar fi = p_str[0]; QChar la = p_str[p_str.size() - 1]; return fi == '#' || la == '`' || la == '$' || la == '~' || la == '*' || la == '_'; } bool PegMarkdownHighlighter::preHighlightSingleFormatBlock(const QVector> &p_highlights, int p_blockNum, const QString &p_text, bool p_forced) { int sz = p_text.size(); if (sz == 0) { return false; } if (p_highlights.size() <= p_blockNum) { return false; } if (!p_forced && !m_singleFormatBlocks.contains(p_blockNum)) { return false; } const QVector &units = p_highlights[p_blockNum]; if (units.size() == 1) { const HLUnit &unit = units[0]; if (unit.start == 0 && (int)unit.length < sz && (p_forced || containSpecialChar(p_text))) { setFormat(0, sz, m_styles[unit.styleIndex].format); return true; } } return false; } bool PegMarkdownHighlighter::highlightBlockOne(const QVector> &p_highlights, int p_blockNum, QVector *p_cache) { bool highlighted = false; if (p_highlights.size() > p_blockNum) { // units are sorted by start position and length. const QVector &units = p_highlights[p_blockNum]; if (!units.isEmpty()) { highlighted = true; if (p_cache) { p_cache->append(units); } highlightBlockOne(units); } } return highlighted; } void PegMarkdownHighlighter::highlightBlockOne(const QVector &p_units) { for (int i = 0; i < p_units.size(); ++i) { const HLUnit &unit = p_units[i]; if (i == 0) { // No need to merge format. setFormat(unit.start, unit.length, m_styles[unit.styleIndex].format); } else { QTextCharFormat newFormat = m_styles[unit.styleIndex].format; for (int j = i - 1; j >= 0; --j) { if (p_units[j].start + p_units[j].length <= unit.start) { // It won't affect current unit. continue; } else { // Merge the format. QTextCharFormat tmpFormat(newFormat); newFormat = m_styles[p_units[j].styleIndex].format; // tmpFormat takes precedence. newFormat.merge(tmpFormat); } } setFormat(unit.start, unit.length, newFormat); } } } #define KEY_PRESS_INTERVAL 50 // highlightBlock() will be called before this function. void PegMarkdownHighlighter::handleContentsChange(int p_position, int p_charsRemoved, int p_charsAdded) { Q_UNUSED(p_position); int interval = m_contentChangeTime.restart(); if (p_charsRemoved == 0 && p_charsAdded == 0) { return; } ++m_timeStamp; m_timer->stop(); if (m_timeStamp > 2) { m_fastParseInfo.m_position = p_position; m_fastParseInfo.m_charsRemoved = p_charsRemoved; m_fastParseInfo.m_charsAdded = p_charsAdded; m_fastParseTimer->start(interval < KEY_PRESS_INTERVAL ? 100 : m_fastParseInterval); } // We still need a timer to start a complete parse. m_timer->start(m_timeStamp == 2 ? 0 : m_parseInterval); } void PegMarkdownHighlighter::startParse() { QSharedPointer config(new PegParseConfig()); config->m_timeStamp = m_timeStamp; config->m_data = m_doc->toPlainText().toUtf8(); config->m_numOfBlocks = m_doc->blockCount(); config->m_extensions = m_parserExts; m_parser->parseAsync(config); } void PegMarkdownHighlighter::startFastParse(int p_position, int p_charsRemoved, int p_charsAdded) { // Get affected block range. int firstBlockNum, lastBlockNum; getFastParseBlockRange(p_position, p_charsRemoved, p_charsAdded, firstBlockNum, lastBlockNum); if (firstBlockNum == -1) { // We could not let m_fastResult NULL here. clearFastParseResult(); m_fastParseInterval = 100; return; } else { m_fastParseInterval = (lastBlockNum - firstBlockNum) < 5 ? 0 : 30; } QString text; QTextBlock block = m_doc->findBlockByNumber(firstBlockNum); int offset = block.position(); while (block.isValid()) { int blockNum = block.blockNumber(); if (blockNum > lastBlockNum) { break; } else if (blockNum == firstBlockNum) { text = block.text(); } else { text = text + "\n" + block.text(); } block = block.next(); } m_fastParseBlocks.first = firstBlockNum; m_fastParseBlocks.second = lastBlockNum; QSharedPointer config(new PegParseConfig()); config->m_timeStamp = m_timeStamp; config->m_data = text.toUtf8(); config->m_numOfBlocks = m_doc->blockCount(); config->m_offset = offset; config->m_extensions = m_parserExts; config->m_fast = true; QSharedPointer parseRes = m_parser->parse(config); processFastParseResult(parseRes); } void PegMarkdownHighlighter::processFastParseResult(const QSharedPointer &p_result) { m_fastResult.reset(new PegHighlighterFastResult(this, p_result)); // Add additional single format blocks. updateSingleFormatBlocks(m_fastResult->m_blocksHighlights); if (!m_fastResult->matched(m_timeStamp) || m_result->matched(m_timeStamp)) { return; } for (int i = m_fastParseBlocks.first; i <= m_fastParseBlocks.second; ++i) { QTextBlock block = m_doc->findBlockByNumber(i); rehighlightBlock(block); } } static bool compHLUnitStyle(const HLUnitStyle &a, const HLUnitStyle &b) { if (a.start < b.start) { return true; } else if (a.start == b.start) { return a.length > b.length; } else { return false; } } void PegMarkdownHighlighter::setCodeBlockHighlights(TimeStamp p_timeStamp, const QVector &p_units) { QSharedPointer result(m_result); if (!result->matched(p_timeStamp) || result->m_numOfCodeBlockHighlightsToRecv <= 0) { return; } if (p_units.isEmpty()) { goto exit; } { QVector> highlights(result->m_codeBlocksHighlights.size()); for (auto const &unit : p_units) { int pos = unit.m_position; int end = unit.m_position + unit.m_length; QTextBlock block = m_doc->findBlock(pos); int startBlockNum = block.blockNumber(); int endBlockNum = m_doc->findBlock(end).blockNumber(); // Text has been changed. Abandon the obsolete parsed result. if (startBlockNum == -1 || endBlockNum >= highlights.size()) { goto exit; } while (block.isValid()) { int blockNumber = block.blockNumber(); if (blockNumber > endBlockNum) { break; } int blockStartPos = block.position(); HLUnitStyle hl; hl.style = unit.m_style; if (blockNumber == startBlockNum) { hl.start = pos - blockStartPos; hl.length = (startBlockNum == endBlockNum) ? (end - pos) : (block.length() - hl.start); } else if (blockNumber == endBlockNum) { hl.start = 0; hl.length = end - blockStartPos; } else { hl.start = 0; hl.length = block.length(); } highlights[blockNumber].append(hl); block = block.next(); } } // Need to highlight in order. for (int i = 0; i < highlights.size(); ++i) { QVector &units = highlights[i]; if (!units.isEmpty()) { if (units.size() > 1) { std::sort(units.begin(), units.end(), compHLUnitStyle); } result->m_codeBlocksHighlights[i].append(units); } } } exit: if (--result->m_numOfCodeBlockHighlightsToRecv <= 0) { result->m_codeBlockTimeStamp = nextCodeBlockTimeStamp(); result->m_codeBlockHighlightReceived = true; rehighlightBlocksLater(); } } void PegMarkdownHighlighter::updateHighlight() { m_timer->stop(); if (m_result->matched(m_timeStamp)) { // No need to parse again. Already the latest. updateCodeBlocks(m_result); rehighlightBlocksLater(); completeHighlight(m_result); } else { startParse(); } } void PegMarkdownHighlighter::handleParseResult(const QSharedPointer &p_result) { if (!m_result.isNull() && m_result->m_timeStamp > p_result->m_timeStamp) { return; } clearFastParseResult(); m_result.reset(new PegHighlighterResult(this, p_result)); m_result->m_codeBlockTimeStamp = nextCodeBlockTimeStamp(); m_singleFormatBlocks.clear(); updateSingleFormatBlocks(m_result->m_blocksHighlights); bool matched = m_result->matched(m_timeStamp); if (matched) { clearAllBlocksUserDataAndState(m_result); updateAllBlocksUserState(m_result); updateCodeBlocks(m_result); } if (m_result->m_timeStamp == 2) { m_notifyHighlightComplete = true; rehighlightBlocks(); } else { rehighlightBlocksLater(); } if (matched) { completeHighlight(m_result); } } void PegMarkdownHighlighter::updateSingleFormatBlocks(const QVector> &p_highlights) { for (int i = 0; i < p_highlights.size(); ++i) { const QVector &units = p_highlights[i]; if (units.size() == 1) { const HLUnit &unit = units[0]; if (unit.start == 0 && unit.length > 0) { QTextBlock block = m_doc->findBlockByNumber(i); if (block.length() - 1 <= (int)unit.length) { m_singleFormatBlocks.insert(i); } } } } } void PegMarkdownHighlighter::updateCodeBlocks(const QSharedPointer &p_result) { // Only need to receive code block highlights when it is empty. if (g_config->getEnableCodeBlockHighlight()) { int cbSz = p_result->m_codeBlocks.size(); if (cbSz > 0) { if (PegMarkdownHighlighter::isEmptyCodeBlockHighlights(p_result->m_codeBlocksHighlights)) { p_result->m_codeBlocksHighlights.resize(p_result->m_numOfBlocks); p_result->m_numOfCodeBlockHighlightsToRecv = cbSz; } } else { p_result->m_codeBlockHighlightReceived = true; } } else { p_result->m_codeBlockHighlightReceived = true; } emit codeBlocksUpdated(p_result->m_timeStamp, p_result->m_codeBlocks); } void PegMarkdownHighlighter::clearAllBlocksUserDataAndState(const QSharedPointer &p_result) { QTextBlock block = m_doc->firstBlock(); while (block.isValid()) { clearBlockUserData(p_result, block); block.setUserState(HighlightBlockState::Normal); block = block.next(); } } void PegMarkdownHighlighter::clearBlockUserData(const QSharedPointer &p_result, QTextBlock &p_block) { Q_UNUSED(p_result); int blockNum = p_block.blockNumber(); VTextBlockData *data = VTextBlockData::blockData(p_block); if (!data) { return; } data->setCodeBlockIndentation(-1); if (data->getPreviews().isEmpty()) { m_possiblePreviewBlocks.remove(blockNum); } else { m_possiblePreviewBlocks.insert(blockNum); } } void PegMarkdownHighlighter::updateAllBlocksUserState(const QSharedPointer &p_result) { // Code blocks. bool hlColumn = g_config->getColorColumn() > 0; const QHash &cbStates = p_result->m_codeBlocksState; for (auto it = cbStates.begin(); it != cbStates.end(); ++it) { QTextBlock block = m_doc->findBlockByNumber(it.key()); if (!block.isValid()) { continue; } // Set code block indentation. if (hlColumn) { VTextBlockData *blockData = static_cast(block.userData()); Q_ASSERT(blockData); switch (it.value()) { case HighlightBlockState::CodeBlockStart: { int startLeadingSpaces = 0; QRegularExpression reg(VUtils::c_fencedCodeBlockStartRegExp); auto match = reg.match(block.text()); if (match.hasMatch()) { startLeadingSpaces = match.captured(1).size(); } blockData->setCodeBlockIndentation(startLeadingSpaces); break; } case HighlightBlockState::CodeBlock: V_FALLTHROUGH; case HighlightBlockState::CodeBlockEnd: { int startLeadingSpaces = 0; VTextBlockData *preBlockData = previousBlockData(block); if (preBlockData) { startLeadingSpaces = preBlockData->getCodeBlockIndentation(); } blockData->setCodeBlockIndentation(startLeadingSpaces); break; } default: Q_ASSERT(false); break; } } block.setUserState(it.value()); } // HRule blocks. foreach (int blk, p_result->m_hruleBlocks) { QTextBlock block = m_doc->findBlockByNumber(blk); if (block.isValid()) { block.setUserState(HighlightBlockState::HRule); } } } void PegMarkdownHighlighter::highlightCodeBlock(const QSharedPointer &p_result, int p_blockNum, const QString &p_text, QVector *p_cache) { // Brush the indentation spaces. if (currentBlockState() == HighlightBlockState::CodeBlock) { int spaces = VEditUtils::fetchIndentation(p_text); if (spaces > 0) { setFormat(0, spaces, m_codeBlockFormat); } } if (p_result->m_codeBlocksHighlights.size() > p_blockNum) { const QVector &units = p_result->m_codeBlocksHighlights[p_blockNum]; if (!units.isEmpty()) { if (p_cache) { p_cache->append(units); } highlightCodeBlockOne(units); } } } void PegMarkdownHighlighter::highlightCodeBlock(const QVector &p_units, const QString &p_text) { // Brush the indentation spaces. if (currentBlockState() == HighlightBlockState::CodeBlock) { int spaces = VEditUtils::fetchIndentation(p_text); if (spaces > 0) { setFormat(0, spaces, m_codeBlockFormat); } } if (!p_units.isEmpty()) { highlightCodeBlockOne(p_units); } } void PegMarkdownHighlighter::highlightCodeBlockOne(const QVector &p_units) { QVector formats(p_units.size(), NULL); for (int i = 0; i < p_units.size(); ++i) { const HLUnitStyle &unit = p_units[i]; auto it = m_codeBlockStyles.find(unit.style); if (it == m_codeBlockStyles.end()) { continue; } formats[i] = &(*it); QTextCharFormat newFormat = m_codeBlockFormat; newFormat.merge(*it); for (int j = i - 1; j >= 0; --j) { if (p_units[j].start + p_units[j].length <= unit.start) { // It won't affect current unit. continue; } else { // Merge the format. if (formats[j]) { QTextCharFormat tmpFormat(newFormat); newFormat = *(formats[j]); // tmpFormat takes precedence. newFormat.merge(tmpFormat); } } } setFormat(unit.start, unit.length, newFormat); } } void PegMarkdownHighlighter::highlightCodeBlockColorColumn(const QString &p_text) { int cc = g_config->getColorColumn(); if (cc <= 0) { return; } VTextBlockData *blockData = currentBlockData(); Q_ASSERT(blockData); int indent = blockData->getCodeBlockIndentation(); if (indent == -1) { return; } cc += indent; if (p_text.size() < cc) { return; } setFormat(cc - 1, 1, m_colorColumnFormat); } void PegMarkdownHighlighter::completeHighlight(QSharedPointer p_result) { m_notifyHighlightComplete = true; if (isMathJaxEnabled()) { emit mathjaxBlocksUpdated(p_result->m_mathjaxBlocks); } emit tableBlocksUpdated(p_result->m_tableBlocks); emit imageLinksUpdated(p_result->m_imageRegions); emit headersUpdated(p_result->m_headerRegions); } void PegMarkdownHighlighter::getFastParseBlockRange(int p_position, int p_charsRemoved, int p_charsAdded, int &p_firstBlock, int &p_lastBlock) const { const int maxNumOfBlocks = 15; int charsChanged = p_charsRemoved + p_charsAdded; QTextBlock firstBlock = m_doc->findBlock(p_position); // May be an invalid block. QTextBlock lastBlock = m_doc->findBlock(qMax(0, p_position + charsChanged)); if (!lastBlock.isValid()) { lastBlock = m_doc->lastBlock(); } int num = lastBlock.blockNumber() - firstBlock.blockNumber() + 1; if (num > maxNumOfBlocks) { p_firstBlock = p_lastBlock = -1; return; } // Look up. while (firstBlock.isValid() && num <= maxNumOfBlocks) { QTextBlock preBlock = firstBlock.previous(); if (!preBlock.isValid()) { break; } // Check code block. int state = firstBlock.userState(); if (state == HighlightBlockState::CodeBlock || state == HighlightBlockState::CodeBlockEnd) { goto goup; } // Empty block. if (VEditUtils::isEmptyBlock(firstBlock)) { goto goup; } if (VEditUtils::fetchIndentation(firstBlock) < 4) { // If previous block is empty, then we could stop now. if (VEditUtils::isEmptyBlock(preBlock)) { int preState = preBlock.userState(); if (preState != HighlightBlockState::CodeBlockStart && preState != HighlightBlockState::CodeBlock) { break; } } } goup: firstBlock = preBlock; ++num; } // Look down. bool inCodeBlock = false; while (lastBlock.isValid() && num <= maxNumOfBlocks) { QTextBlock nextBlock = lastBlock.next(); if (!nextBlock.isValid()) { break; } // Check code block. switch (lastBlock.userState()) { case HighlightBlockState::CodeBlockStart: V_FALLTHROUGH; case HighlightBlockState::CodeBlock: inCodeBlock = true; goto godown; case HighlightBlockState::CodeBlockEnd: inCodeBlock = false; break; default: break; } // Empty block. if (VEditUtils::isEmptyBlock(nextBlock) && !inCodeBlock) { int nstate = nextBlock.userState(); if (nstate != HighlightBlockState::CodeBlockStart && nstate != HighlightBlockState::CodeBlock && nstate != HighlightBlockState::CodeBlockEnd) { break; } } godown: lastBlock = nextBlock; ++num; } p_firstBlock = firstBlock.blockNumber(); p_lastBlock = lastBlock.blockNumber(); if (p_lastBlock < p_firstBlock) { p_lastBlock = p_firstBlock; } else if (p_lastBlock - p_firstBlock + 1 > maxNumOfBlocks) { p_firstBlock = p_lastBlock = -1; } } void PegMarkdownHighlighter::rehighlightSensitiveBlocks() { QTextBlock cb = m_editor->textCursorW().block(); int first, last; m_editor->visibleBlockRange(first, last); bool cursorVisible = cb.blockNumber() >= first && cb.blockNumber() <= last; // Include extra blocks. const int nrUpExtra = 5; const int nrDownExtra = 20; first = qMax(0, first - nrUpExtra); last = qMin(m_doc->blockCount() - 1, last + nrDownExtra); if (rehighlightBlockRange(first, last)) { if (cursorVisible) { m_editor->ensureCursorVisibleW(); } } } void PegMarkdownHighlighter::rehighlightBlocks() { if (m_result->m_numOfBlocks <= LARGE_BLOCK_NUMBER) { rehighlightBlockRange(0, m_result->m_numOfBlocks - 1); } else { rehighlightSensitiveBlocks(); } if (m_notifyHighlightComplete) { m_notifyHighlightComplete = false; emit highlightCompleted(); } } bool PegMarkdownHighlighter::rehighlightBlockRange(int p_first, int p_last) { bool highlighted = false; const QHash &cbStates = m_result->m_codeBlocksState; const QVector> &hls = m_result->m_blocksHighlights; const QVector> &cbHls = m_result->m_codeBlocksHighlights; int nr = 0; QTextBlock block = m_doc->findBlockByNumber(p_first); while (block.isValid()) { int blockNum = block.blockNumber(); if (blockNum > p_last) { break; } bool needHL = false; bool updateTS = false; VTextBlockData *data = VTextBlockData::blockData(block); if (PegMarkdownHighlighter::blockTimeStamp(block) != m_result->m_timeStamp) { needHL = true; // Try to find cache. if (blockNum < hls.size()) { if (data->isBlockHighlightCacheMatched(hls[blockNum])) { needHL = false; updateTS = true; } } } if (!needHL) { // FIXME: what about a previous code block turn into a non-code block? For now, // they can be distinguished by block highlights. auto it = cbStates.find(blockNum); if (it != cbStates.end() && it.value() == HighlightBlockState::CodeBlock) { if (PegMarkdownHighlighter::blockCodeBlockTimeStamp(block) != m_result->m_codeBlockTimeStamp && m_result->m_codeBlockHighlightReceived) { needHL = true; // Try to find cache. if (blockNum < cbHls.size()) { if (data->isCodeBlockHighlightCacheMatched(cbHls[blockNum])) { needHL = false; updateTS = true; } } } } } if (needHL) { highlighted = true; rehighlightBlock(block); ++nr; } else if (updateTS) { data->setCacheValid(true); data->setTimeStamp(m_result->m_timeStamp); data->setCodeBlockTimeStamp(m_result->m_codeBlockTimeStamp); } block = block.next(); } qDebug() << "rehighlightBlockRange" << p_first << p_last << nr; return highlighted; } void PegMarkdownHighlighter::clearFastParseResult() { m_fastParseBlocks.first = -1; m_fastParseBlocks.second = -1; m_fastResult->clear(); } void PegMarkdownHighlighter::rehighlightBlocksLater() { m_rehighlightTimer->start(); }