| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947 |
- #include "pegmarkdownhighlighter.h"
- #include <QTextDocument>
- #include <QTimer>
- #include <QScrollBar>
- #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),
- m_parseInterval(50),
- m_notifyHighlightComplete(false)
- {
- }
- void PegMarkdownHighlighter::init(const QVector<HighlightingStyle> &p_styles,
- const QHash<QString, QTextCharFormat> &p_codeBlockStyles,
- bool p_mathjaxEnabled,
- int p_timerInterval)
- {
- m_styles = p_styles;
- m_codeBlockStyles = p_codeBlockStyles;
- if (p_mathjaxEnabled) {
- m_parserExts |= pmh_EXT_MATH;
- }
- 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<int, int>(-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(50);
- 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<void(QTimer::*)()>(&QTimer::start));
- }
- // Just use parse results to highlight block.
- // Do not maintain block data and state here.
- void PegMarkdownHighlighter::highlightBlock(const QString &p_text)
- {
- QSharedPointer<PegHighlighterResult> result(m_result);
- QTextBlock block = currentBlock();
- int blockNum = block.blockNumber();
- bool isCodeBlock = currentBlockState() == HighlightBlockState::CodeBlock;
- VTextBlockData *blockData = VTextBlockData::blockData(block);
- QVector<HLUnit> *cache = &blockData->getBlockHighlightCache();
- 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<HLUnitStyle> *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);
- }
- }
- bool PegMarkdownHighlighter::preHighlightSingleFormatBlock(const QVector<QVector<HLUnit>> &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<HLUnit> &units = p_highlights[p_blockNum];
- if (units.size() == 1) {
- const HLUnit &unit = units[0];
- if (unit.start == 0 && (int)unit.length < sz) {
- setFormat(0, sz, m_styles[unit.styleIndex].format);
- return true;
- }
- }
- return false;
- }
- bool PegMarkdownHighlighter::highlightBlockOne(const QVector<QVector<HLUnit>> &p_highlights,
- int p_blockNum,
- QVector<HLUnit> *p_cache)
- {
- bool highlighted = false;
- if (p_highlights.size() > p_blockNum) {
- // units are sorted by start position and length.
- const QVector<HLUnit> &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<HLUnit> &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);
- }
- }
- }
- // highlightBlock() will be called before this function.
- void PegMarkdownHighlighter::handleContentsChange(int p_position, int p_charsRemoved, int p_charsAdded)
- {
- Q_UNUSED(p_position);
- 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();
- }
- // We still need a timer to start a complete parse.
- m_timer->start(m_timeStamp == 2 ? 0 : m_parseInterval);
- }
- void PegMarkdownHighlighter::startParse()
- {
- QSharedPointer<PegParseConfig> 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();
- return;
- }
- 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<PegParseConfig> 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<PegParseResult> parseRes = m_parser->parse(config);
- processFastParseResult(parseRes);
- }
- void PegMarkdownHighlighter::processFastParseResult(const QSharedPointer<PegParseResult> &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<HLUnitPos> &p_units)
- {
- QSharedPointer<PegHighlighterResult> result(m_result);
- if (!result->matched(p_timeStamp)
- || result->m_numOfCodeBlockHighlightsToRecv <= 0) {
- return;
- }
- if (p_units.isEmpty()) {
- goto exit;
- }
- {
- QVector<QVector<HLUnitStyle>> 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<HLUnitStyle> &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<PegParseResult> &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<QVector<HLUnit>> &p_highlights)
- {
- for (int i = 0; i < p_highlights.size(); ++i) {
- const QVector<HLUnit> &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<PegHighlighterResult> &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<PegHighlighterResult> &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<PegHighlighterResult> &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<PegHighlighterResult> &p_result)
- {
- // Code blocks.
- bool hlColumn = g_config->getColorColumn() > 0;
- const QHash<int, HighlightBlockState> &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<VTextBlockData *>(block.userData());
- Q_ASSERT(blockData);
- switch (it.value()) {
- case HighlightBlockState::CodeBlockStart:
- {
- int startLeadingSpaces = 0;
- QRegExp reg(VUtils::c_fencedCodeBlockStartRegExp);
- int idx = reg.indexIn(block.text());
- if (idx >= 0) {
- startLeadingSpaces = reg.capturedTexts()[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<PegHighlighterResult> &p_result,
- int p_blockNum,
- const QString &p_text,
- QVector<HLUnitStyle> *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<HLUnitStyle> &units = p_result->m_codeBlocksHighlights[p_blockNum];
- if (!units.isEmpty()) {
- if (p_cache) {
- p_cache->append(units);
- }
- highlightCodeBlockOne(units);
- }
- }
- }
- void PegMarkdownHighlighter::highlightCodeBlock(const QVector<HLUnitStyle> &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<HLUnitStyle> &p_units)
- {
- QVector<QTextCharFormat *> 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<PegHighlighterResult> p_result)
- {
- m_notifyHighlightComplete = true;
- if (isMathJaxEnabled()) {
- emit mathjaxBlocksUpdated(p_result->m_mathjaxBlocks);
- }
- 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 = 50;
- 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<int, HighlightBlockState> &cbStates = m_result->m_codeBlocksState;
- const QVector<QVector<HLUnit>> &hls = m_result->m_blocksHighlights;
- const QVector<QVector<HLUnitStyle>> &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 && !data->getPreviews().isEmpty()) {
- needHL = 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();
- }
|