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;
This commit is contained in:
Le Tan 2018-07-17 20:43:28 +08:00
parent 59efed94f3
commit 48db50fd5e
5 changed files with 130 additions and 35 deletions

View File

@ -2,7 +2,6 @@
#include <QTextDocument> #include <QTextDocument>
#include <QTimer> #include <QTimer>
#include <QDebug>
#include "pegparser.h" #include "pegparser.h"
#include "vconfigmanager.h" #include "vconfigmanager.h"
@ -69,7 +68,7 @@ void PegMarkdownHighlighter::init(const QVector<HighlightingStyle> &p_styles,
connect(m_fastParseTimer, &QTimer::timeout, connect(m_fastParseTimer, &QTimer::timeout,
this, [this]() { this, [this]() {
QSharedPointer<PegHighlighterFastResult> result(m_fastResult); QSharedPointer<PegHighlighterFastResult> result(m_fastResult);
if (!result->matched(m_timeStamp)) { if (!result->matched(m_timeStamp) || m_result->matched(m_timeStamp)) {
return; return;
} }
@ -89,18 +88,26 @@ void PegMarkdownHighlighter::highlightBlock(const QString &p_text)
{ {
QSharedPointer<PegHighlighterResult> result(m_result); QSharedPointer<PegHighlighterResult> 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(result->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); highlightBlockOne(m_fastResult->m_blocksHighlights, blockNum);
}
// Set current block's user data. // Set current block's user data.
updateBlockUserData(blockNum, p_text); updateBlockUserData(blockNum, p_text);
setCurrentBlockState(HighlightBlockState::Normal); updateBlockUserState(result, blockNum, p_text);
updateCodeBlockState(result, blockNum, p_text);
if (currentBlockState() == HighlightBlockState::CodeBlock) { if (currentBlockState() == HighlightBlockState::CodeBlock) {
highlightCodeBlock(result, blockNum, p_text); highlightCodeBlock(result, blockNum, p_text);
@ -109,6 +116,30 @@ void PegMarkdownHighlighter::highlightBlock(const QString &p_text)
} }
} }
void PegMarkdownHighlighter::preHighlightMonospaceBlock(const QVector<QVector<HLUnit>> &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<HLUnit> &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<QVector<HLUnit>> &p_highlights, void PegMarkdownHighlighter::highlightBlockOne(const QVector<QVector<HLUnit>> &p_highlights,
int p_blockNum) int p_blockNum)
{ {
@ -145,6 +176,7 @@ void PegMarkdownHighlighter::highlightBlockOne(const QVector<QVector<HLUnit>> &p
} }
} }
// highlightBlock() will be called before this function.
void PegMarkdownHighlighter::handleContentsChange(int p_position, int p_charsRemoved, int p_charsAdded) void PegMarkdownHighlighter::handleContentsChange(int p_position, int p_charsRemoved, int p_charsAdded)
{ {
Q_UNUSED(p_position); Q_UNUSED(p_position);
@ -210,6 +242,8 @@ void PegMarkdownHighlighter::processFastParseResult(const QSharedPointer<PegPars
{ {
m_fastParseTimer->stop(); m_fastParseTimer->stop();
m_fastResult.reset(new PegHighlighterFastResult(this, p_result)); m_fastResult.reset(new PegHighlighterFastResult(this, p_result));
// Add additional single format blocks.
updateSingleFormatBlocks(m_fastResult->m_blocksHighlights);
m_fastParseTimer->start(); m_fastParseTimer->start();
} }
@ -228,7 +262,6 @@ void PegMarkdownHighlighter::setCodeBlockHighlights(TimeStamp p_timeStamp,
const QVector<HLUnitPos> &p_units) const QVector<HLUnitPos> &p_units)
{ {
QSharedPointer<PegHighlighterResult> result(m_result); QSharedPointer<PegHighlighterResult> result(m_result);
if (!result->matched(p_timeStamp)) { if (!result->matched(p_timeStamp)) {
return; return;
} }
@ -293,7 +326,13 @@ void PegMarkdownHighlighter::setCodeBlockHighlights(TimeStamp p_timeStamp,
exit: exit:
if (--result->m_numOfCodeBlockHighlightsToRecv <= 0) { if (--result->m_numOfCodeBlockHighlightsToRecv <= 0) {
rehighlight(); // Rehighlight specific blocks.
const QVector<QVector<HLUnitStyle>> &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 QSharedPointer<PegParseResu
return; return;
} }
PegHighlighterResult *pegRes = new PegHighlighterResult(this, p_result); m_result.reset(new PegHighlighterResult(this, p_result));
m_result.reset(pegRes);
m_singleFormatBlocks.clear();
updateSingleFormatBlocks(m_result->m_blocksHighlights);
updateCodeBlocks(m_result); updateCodeBlocks(m_result);
// Now we got a new result, rehighlight.
rehighlight(); rehighlight();
completeHighlight(m_result); 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 == unit.length) {
m_singleFormatBlocks.insert(i);
}
}
}
}
}
void PegMarkdownHighlighter::updateCodeBlocks(QSharedPointer<PegHighlighterResult> p_result) void PegMarkdownHighlighter::updateCodeBlocks(QSharedPointer<PegHighlighterResult> p_result)
{ {
if (!p_result->matched(m_timeStamp)) { if (!p_result->matched(m_timeStamp)) {
@ -357,20 +414,27 @@ void PegMarkdownHighlighter::updateBlockUserData(int p_blockNum, const QString &
} }
} }
void PegMarkdownHighlighter::updateCodeBlockState(const QSharedPointer<PegHighlighterResult> &p_result, void PegMarkdownHighlighter::updateBlockUserState(const QSharedPointer<PegHighlighterResult> &p_result,
int p_blockNum, int p_blockNum,
const QString &p_text) const QString &p_text)
{ {
// Newly-added block.
if (currentBlockState() == -1) {
setCurrentBlockState(HighlightBlockState::Normal);
}
if (!p_result->matched(m_timeStamp)) { if (!p_result->matched(m_timeStamp)) {
return; return;
} }
HighlightBlockState state = HighlightBlockState::Normal;
auto it = p_result->m_codeBlocksState.find(p_blockNum); auto it = p_result->m_codeBlocksState.find(p_blockNum);
if (it != p_result->m_codeBlocksState.end()) { if (it != p_result->m_codeBlocksState.end()) {
VTextBlockData *blockData = currentBlockData(); VTextBlockData *blockData = currentBlockData();
Q_ASSERT(blockData); Q_ASSERT(blockData);
HighlightBlockState state = it.value(); state = it.value();
// Set code block indentation. // Set code block indentation.
switch (state) { switch (state) {
case HighlightBlockState::CodeBlockStart: case HighlightBlockState::CodeBlockStart:
@ -404,10 +468,10 @@ void PegMarkdownHighlighter::updateCodeBlockState(const QSharedPointer<PegHighli
Q_ASSERT(false); Q_ASSERT(false);
break; break;
} }
}
// Set code block state. // Set code block state.
setCurrentBlockState(state); setCurrentBlockState(state);
}
} }
void PegMarkdownHighlighter::highlightCodeBlock(const QSharedPointer<PegHighlighterResult> &p_result, void PegMarkdownHighlighter::highlightCodeBlock(const QSharedPointer<PegHighlighterResult> &p_result,
@ -523,7 +587,7 @@ void PegMarkdownHighlighter::getFastParseBlockRange(int p_position,
// Look up. // Look up.
// Find empty block. // Find empty block.
if (!VEditUtils::isEmptyBlock(firstBlock)) { // When firstBlock is an empty block at first, we should always skip it.
while (firstBlock.isValid() && num < maxNumOfBlocks) { while (firstBlock.isValid() && num < maxNumOfBlocks) {
QTextBlock block = firstBlock.previous(); QTextBlock block = firstBlock.previous();
if (block.isValid() && !VEditUtils::isEmptyBlock(block)) { if (block.isValid() && !VEditUtils::isEmptyBlock(block)) {
@ -533,7 +597,6 @@ void PegMarkdownHighlighter::getFastParseBlockRange(int p_position,
break; break;
} }
} }
}
// Cross code block. // Cross code block.
while (firstBlock.isValid() && num < maxNumOfBlocks) { while (firstBlock.isValid() && num < maxNumOfBlocks) {
@ -552,9 +615,25 @@ void PegMarkdownHighlighter::getFastParseBlockRange(int p_position,
} }
} }
// 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;
}
}
}
// Look down. // Look down.
// Find empty block. // Find empty block.
if (!VEditUtils::isEmptyBlock(lastBlock)) { // If lastBlock is an empty block at first, we should always skip it.
while (lastBlock.isValid() && num < maxNumOfBlocks) { while (lastBlock.isValid() && num < maxNumOfBlocks) {
QTextBlock block = lastBlock.next(); QTextBlock block = lastBlock.next();
if (block.isValid() && !VEditUtils::isEmptyBlock(block)) { if (block.isValid() && !VEditUtils::isEmptyBlock(block)) {
@ -564,7 +643,6 @@ void PegMarkdownHighlighter::getFastParseBlockRange(int p_position,
break; break;
} }
} }
}
// Cross code block. // Cross code block.
while (lastBlock.isValid() && num < maxNumOfBlocks) { while (lastBlock.isValid() && num < maxNumOfBlocks) {

View File

@ -81,7 +81,7 @@ private:
// Set the user data of currentBlock(). // Set the user data of currentBlock().
void updateBlockUserData(int p_blockNum, const QString &p_text); void updateBlockUserData(int p_blockNum, const QString &p_text);
void updateCodeBlockState(const QSharedPointer<PegHighlighterResult> &p_result, void updateBlockUserState(const QSharedPointer<PegHighlighterResult> &p_result,
int p_blockNum, int p_blockNum,
const QString &p_text); const QString &p_text);
@ -112,6 +112,13 @@ private:
void highlightBlockOne(const QVector<QVector<HLUnit>> &p_highlights, void highlightBlockOne(const QVector<QVector<HLUnit>> &p_highlights,
int p_blockNum); int p_blockNum);
// To avoid line height jitter.
void preHighlightMonospaceBlock(const QVector<QVector<HLUnit>> &p_highlights,
int p_blockNum,
const QString &p_text);
void updateSingleFormatBlocks(const QVector<QVector<HLUnit>> &p_highlights);
QTextDocument *m_doc; QTextDocument *m_doc;
TimeStamp m_timeStamp; TimeStamp m_timeStamp;
@ -138,6 +145,9 @@ private:
QTimer *m_timer; QTimer *m_timer;
QTimer *m_fastParseTimer; QTimer *m_fastParseTimer;
// Blocks have only one format set which occupies the whole block.
QSet<int> m_singleFormatBlocks;
}; };
inline const QVector<VElementRegion> &PegMarkdownHighlighter::getHeaderRegions() const inline const QVector<VElementRegion> &PegMarkdownHighlighter::getHeaderRegions() const

View File

@ -106,7 +106,7 @@ minimize_to_system_tray=-1
markdown_suffix=md,markdown,mkd markdown_suffix=md,markdown,mkd
; Markdown highlight timer interval (milliseconds) ; Markdown highlight timer interval (milliseconds)
markdown_highlight_interval=200 markdown_highlight_interval=400
; Adds specified height between lines (in pixels) ; Adds specified height between lines (in pixels)
line_distance_height=3 line_distance_height=3

View File

@ -956,6 +956,11 @@ int VEditUtils::fetchIndentation(const QString &p_text)
return idx; return idx;
} }
int VEditUtils::fetchIndentation(const QTextBlock &p_block)
{
return fetchIndentation(p_block.text());
}
void VEditUtils::insertBlock(QTextCursor &p_cursor, void VEditUtils::insertBlock(QTextCursor &p_cursor,
bool p_above) bool p_above)
{ {

View File

@ -186,6 +186,8 @@ public:
static int fetchIndentation(const QString &p_text); 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 // Insert a block above/below current block. Move the cursor to the start of
// the new block after insertion. // the new block after insertion.
static void insertBlock(QTextCursor &p_cursor, static void insertBlock(QTextCursor &p_cursor,