From 48db50fd5eedd2008507b236f1ffe4f981296204 Mon Sep 17 00:00:00 2001 From: Le Tan Date: Tue, 17 Jul 2018 20:43:28 +0800 Subject: [PATCH] PegHighlighter: refine fast parse - Fast parse block range: look upward till the indentation is 0; - Rehighlight all the fast-parsed blocks explicitly; - Do not reset block user state by default; - Pre highlight single format blocks to avoid jitter of line height; --- src/pegmarkdownhighlighter.cpp | 144 +++++++++++++++++++++++++-------- src/pegmarkdownhighlighter.h | 12 ++- src/resources/vnote.ini | 2 +- src/utils/veditutils.cpp | 5 ++ src/utils/veditutils.h | 2 + 5 files changed, 130 insertions(+), 35 deletions(-) diff --git a/src/pegmarkdownhighlighter.cpp b/src/pegmarkdownhighlighter.cpp index 6c7ad5d2..0b60c2b8 100644 --- a/src/pegmarkdownhighlighter.cpp +++ b/src/pegmarkdownhighlighter.cpp @@ -2,7 +2,6 @@ #include #include -#include #include "pegparser.h" #include "vconfigmanager.h" @@ -69,7 +68,7 @@ void PegMarkdownHighlighter::init(const QVector &p_styles, connect(m_fastParseTimer, &QTimer::timeout, this, [this]() { QSharedPointer result(m_fastResult); - if (!result->matched(m_timeStamp)) { + if (!result->matched(m_timeStamp) || m_result->matched(m_timeStamp)) { return; } @@ -89,18 +88,26 @@ void PegMarkdownHighlighter::highlightBlock(const QString &p_text) { QSharedPointer result(m_result); - int blockNum = currentBlock().blockNumber(); + QTextBlock block = currentBlock(); + int blockNum = block.blockNumber(); + + if (result->matched(m_timeStamp)) { + preHighlightMonospaceBlock(result->m_blocksHighlights, blockNum, p_text); + } else { + preHighlightMonospaceBlock(m_fastResult->m_blocksHighlights, blockNum, p_text); + } highlightBlockOne(result->m_blocksHighlights, blockNum); - highlightBlockOne(m_fastResult->m_blocksHighlights, blockNum); + // The complete result is not ready yet. We use fast result for compensation. + if (!result->matched(m_timeStamp)) { + highlightBlockOne(m_fastResult->m_blocksHighlights, blockNum); + } // Set current block's user data. updateBlockUserData(blockNum, p_text); - setCurrentBlockState(HighlightBlockState::Normal); - - updateCodeBlockState(result, blockNum, p_text); + updateBlockUserState(result, blockNum, p_text); if (currentBlockState() == HighlightBlockState::CodeBlock) { highlightCodeBlock(result, blockNum, p_text); @@ -109,6 +116,30 @@ void PegMarkdownHighlighter::highlightBlock(const QString &p_text) } } +void PegMarkdownHighlighter::preHighlightMonospaceBlock(const QVector> &p_highlights, + int p_blockNum, + const QString &p_text) +{ + int sz = p_text.size(); + if (sz == 0) { + return; + } + + if (!m_singleFormatBlocks.contains(p_blockNum)) { + return; + } + + if (p_highlights.size() > p_blockNum) { + const QVector &units = p_highlights[p_blockNum]; + if (units.size() == 1) { + const HLUnit &unit = units[0]; + if (unit.start == 0 && (int)unit.length < sz) { + setFormat(unit.length, sz - unit.length, m_styles[unit.styleIndex].format); + } + } + } +} + void PegMarkdownHighlighter::highlightBlockOne(const QVector> &p_highlights, int p_blockNum) { @@ -145,6 +176,7 @@ void PegMarkdownHighlighter::highlightBlockOne(const QVector> &p } } +// highlightBlock() will be called before this function. void PegMarkdownHighlighter::handleContentsChange(int p_position, int p_charsRemoved, int p_charsAdded) { Q_UNUSED(p_position); @@ -210,6 +242,8 @@ void PegMarkdownHighlighter::processFastParseResult(const QSharedPointerstop(); m_fastResult.reset(new PegHighlighterFastResult(this, p_result)); + // Add additional single format blocks. + updateSingleFormatBlocks(m_fastResult->m_blocksHighlights); m_fastParseTimer->start(); } @@ -228,7 +262,6 @@ void PegMarkdownHighlighter::setCodeBlockHighlights(TimeStamp p_timeStamp, const QVector &p_units) { QSharedPointer result(m_result); - if (!result->matched(p_timeStamp)) { return; } @@ -293,7 +326,13 @@ void PegMarkdownHighlighter::setCodeBlockHighlights(TimeStamp p_timeStamp, exit: if (--result->m_numOfCodeBlockHighlightsToRecv <= 0) { - rehighlight(); + // Rehighlight specific blocks. + const QVector> &hls = result->m_codeBlocksHighlights; + for (int i = 0; i < hls.size(); ++i) { + if (!hls[i].isEmpty()) { + rehighlightBlock(m_doc->findBlockByNumber(i)); + } + } } } @@ -314,17 +353,35 @@ void PegMarkdownHighlighter::handleParseResult(const QSharedPointerm_blocksHighlights); updateCodeBlocks(m_result); - // Now we got a new result, rehighlight. rehighlight(); 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 == unit.length) { + m_singleFormatBlocks.insert(i); + } + } + } + } +} + void PegMarkdownHighlighter::updateCodeBlocks(QSharedPointer p_result) { if (!p_result->matched(m_timeStamp)) { @@ -357,20 +414,27 @@ void PegMarkdownHighlighter::updateBlockUserData(int p_blockNum, const QString & } } -void PegMarkdownHighlighter::updateCodeBlockState(const QSharedPointer &p_result, +void PegMarkdownHighlighter::updateBlockUserState(const QSharedPointer &p_result, int p_blockNum, const QString &p_text) { + // Newly-added block. + if (currentBlockState() == -1) { + setCurrentBlockState(HighlightBlockState::Normal); + } + if (!p_result->matched(m_timeStamp)) { return; } + HighlightBlockState state = HighlightBlockState::Normal; + auto it = p_result->m_codeBlocksState.find(p_blockNum); if (it != p_result->m_codeBlocksState.end()) { VTextBlockData *blockData = currentBlockData(); Q_ASSERT(blockData); - HighlightBlockState state = it.value(); + state = it.value(); // Set code block indentation. switch (state) { case HighlightBlockState::CodeBlockStart: @@ -404,10 +468,10 @@ void PegMarkdownHighlighter::updateCodeBlockState(const QSharedPointer &p_result, @@ -523,15 +587,14 @@ void PegMarkdownHighlighter::getFastParseBlockRange(int p_position, // Look up. // Find empty block. - if (!VEditUtils::isEmptyBlock(firstBlock)) { - while (firstBlock.isValid() && num < maxNumOfBlocks) { - QTextBlock block = firstBlock.previous(); - if (block.isValid() && !VEditUtils::isEmptyBlock(block)) { - firstBlock = block; - ++num; - } else { - break; - } + // When firstBlock is an empty block at first, we should always skip it. + while (firstBlock.isValid() && num < maxNumOfBlocks) { + QTextBlock block = firstBlock.previous(); + if (block.isValid() && !VEditUtils::isEmptyBlock(block)) { + firstBlock = block; + ++num; + } else { + break; } } @@ -552,13 +615,15 @@ void PegMarkdownHighlighter::getFastParseBlockRange(int p_position, } } - // Look down. - // Find empty block. - if (!VEditUtils::isEmptyBlock(lastBlock)) { - while (lastBlock.isValid() && num < maxNumOfBlocks) { - QTextBlock block = lastBlock.next(); - if (block.isValid() && !VEditUtils::isEmptyBlock(block)) { - lastBlock = block; + // Till the block with 0 indentation to handle contents in list. + while (firstBlock.isValid() && num < maxNumOfBlocks) { + if (VEditUtils::fetchIndentation(firstBlock) == 0 + && !VEditUtils::isEmptyBlock(firstBlock)) { + break; + } else { + QTextBlock block = firstBlock.previous(); + if (block.isValid()) { + firstBlock = block; ++num; } else { break; @@ -566,6 +631,19 @@ void PegMarkdownHighlighter::getFastParseBlockRange(int p_position, } } + // Look down. + // Find empty block. + // If lastBlock is an empty block at first, we should always skip it. + while (lastBlock.isValid() && num < maxNumOfBlocks) { + QTextBlock block = lastBlock.next(); + if (block.isValid() && !VEditUtils::isEmptyBlock(block)) { + lastBlock = block; + ++num; + } else { + break; + } + } + // Cross code block. while (lastBlock.isValid() && num < maxNumOfBlocks) { int state = lastBlock.userState(); diff --git a/src/pegmarkdownhighlighter.h b/src/pegmarkdownhighlighter.h index 205ef016..3ec1e6cf 100644 --- a/src/pegmarkdownhighlighter.h +++ b/src/pegmarkdownhighlighter.h @@ -81,7 +81,7 @@ private: // Set the user data of currentBlock(). void updateBlockUserData(int p_blockNum, const QString &p_text); - void updateCodeBlockState(const QSharedPointer &p_result, + void updateBlockUserState(const QSharedPointer &p_result, int p_blockNum, const QString &p_text); @@ -112,6 +112,13 @@ private: void highlightBlockOne(const QVector> &p_highlights, int p_blockNum); + // To avoid line height jitter. + void preHighlightMonospaceBlock(const QVector> &p_highlights, + int p_blockNum, + const QString &p_text); + + void updateSingleFormatBlocks(const QVector> &p_highlights); + QTextDocument *m_doc; TimeStamp m_timeStamp; @@ -138,6 +145,9 @@ private: QTimer *m_timer; QTimer *m_fastParseTimer; + + // Blocks have only one format set which occupies the whole block. + QSet m_singleFormatBlocks; }; inline const QVector &PegMarkdownHighlighter::getHeaderRegions() const diff --git a/src/resources/vnote.ini b/src/resources/vnote.ini index baf7dafd..a1329aef 100644 --- a/src/resources/vnote.ini +++ b/src/resources/vnote.ini @@ -106,7 +106,7 @@ minimize_to_system_tray=-1 markdown_suffix=md,markdown,mkd ; Markdown highlight timer interval (milliseconds) -markdown_highlight_interval=200 +markdown_highlight_interval=400 ; Adds specified height between lines (in pixels) line_distance_height=3 diff --git a/src/utils/veditutils.cpp b/src/utils/veditutils.cpp index 5f2a1b08..c986be18 100644 --- a/src/utils/veditutils.cpp +++ b/src/utils/veditutils.cpp @@ -956,6 +956,11 @@ int VEditUtils::fetchIndentation(const QString &p_text) return idx; } +int VEditUtils::fetchIndentation(const QTextBlock &p_block) +{ + return fetchIndentation(p_block.text()); +} + void VEditUtils::insertBlock(QTextCursor &p_cursor, bool p_above) { diff --git a/src/utils/veditutils.h b/src/utils/veditutils.h index 9c4e283c..fa99db10 100644 --- a/src/utils/veditutils.h +++ b/src/utils/veditutils.h @@ -186,6 +186,8 @@ public: static int fetchIndentation(const QString &p_text); + static int fetchIndentation(const QTextBlock &p_block); + // Insert a block above/below current block. Move the cursor to the start of // the new block after insertion. static void insertBlock(QTextCursor &p_cursor,