PegMarkdownHighlighter: improve performance

This commit is contained in:
Le Tan 2018-07-27 21:05:32 +08:00
parent 9b49de3ab5
commit 8679ffa051
13 changed files with 403 additions and 131 deletions

View File

@ -22,6 +22,7 @@ PegHighlighterFastResult::PegHighlighterFastResult(const PegMarkdownHighlighter
PegHighlighterResult::PegHighlighterResult() PegHighlighterResult::PegHighlighterResult()
: m_timeStamp(0), : m_timeStamp(0),
m_numOfBlocks(0), m_numOfBlocks(0),
m_codeBlockTimeStamp(0),
m_numOfCodeBlockHighlightsToRecv(0) m_numOfCodeBlockHighlightsToRecv(0)
{ {
m_codeBlockStartExp = QRegExp(VUtils::c_fencedCodeBlockStartRegExp); m_codeBlockStartExp = QRegExp(VUtils::c_fencedCodeBlockStartRegExp);
@ -32,6 +33,7 @@ PegHighlighterResult::PegHighlighterResult(const PegMarkdownHighlighter *p_peg,
const QSharedPointer<PegParseResult> &p_result) const QSharedPointer<PegParseResult> &p_result)
: m_timeStamp(p_result->m_timeStamp), : m_timeStamp(p_result->m_timeStamp),
m_numOfBlocks(p_result->m_numOfBlocks), m_numOfBlocks(p_result->m_numOfBlocks),
m_codeBlockTimeStamp(0),
m_numOfCodeBlockHighlightsToRecv(0) m_numOfCodeBlockHighlightsToRecv(0)
{ {
m_codeBlockStartExp = QRegExp(VUtils::c_fencedCodeBlockStartRegExp); m_codeBlockStartExp = QRegExp(VUtils::c_fencedCodeBlockStartRegExp);

View File

@ -54,6 +54,9 @@ public:
// Support fenced code block only. // Support fenced code block only.
QVector<QVector<HLUnitStyle> > m_codeBlocksHighlights; QVector<QVector<HLUnitStyle> > m_codeBlocksHighlights;
// Timestamp for m_codeBlocksHighlights.
TimeStamp m_codeBlockTimeStamp;
// All image link regions. // All image link regions.
QVector<VElementRegion> m_imageRegions; QVector<VElementRegion> m_imageRegions;

View File

@ -2,17 +2,22 @@
#include <QTextDocument> #include <QTextDocument>
#include <QTimer> #include <QTimer>
#include <QScrollBar>
#include "pegparser.h" #include "pegparser.h"
#include "vconfigmanager.h" #include "vconfigmanager.h"
#include "utils/vutils.h" #include "utils/vutils.h"
#include "utils/veditutils.h" #include "utils/veditutils.h"
#include "vmdeditor.h"
extern VConfigManager *g_config; extern VConfigManager *g_config;
PegMarkdownHighlighter::PegMarkdownHighlighter(QTextDocument *p_doc) #define LARGE_BLOCK_NUMBER 2000
PegMarkdownHighlighter::PegMarkdownHighlighter(QTextDocument *p_doc, VMdEditor *p_editor)
: QSyntaxHighlighter(p_doc), : QSyntaxHighlighter(p_doc),
m_doc(p_doc), m_doc(p_doc),
m_editor(p_editor),
m_timeStamp(0), m_timeStamp(0),
m_parser(NULL), m_parser(NULL),
m_parserExts(pmh_EXT_NOTES | pmh_EXT_STRIKE | pmh_EXT_FRONTMATTER | pmh_EXT_MARK) m_parserExts(pmh_EXT_NOTES | pmh_EXT_STRIKE | pmh_EXT_FRONTMATTER | pmh_EXT_MARK)
@ -75,15 +80,33 @@ void PegMarkdownHighlighter::init(const QVector<HighlightingStyle> &p_styles,
const QVector<QVector<HLUnit>> &hls = result->m_blocksHighlights; const QVector<QVector<HLUnit>> &hls = result->m_blocksHighlights;
for (int i = 0; i < hls.size(); ++i) { for (int i = 0; i < hls.size(); ++i) {
if (!hls[i].isEmpty()) { if (!hls[i].isEmpty()) {
rehighlightBlock(m_doc->findBlockByNumber(i)); QTextBlock block = m_doc->findBlockByNumber(i);
if (PegMarkdownHighlighter::blockTimeStamp(block) != m_timeStamp) {
rehighlightBlock(block);
} }
} }
}
});
m_rehighlightTimer = new QTimer(this);
m_rehighlightTimer->setSingleShot(true);
m_rehighlightTimer->setInterval(5);
connect(m_rehighlightTimer, &QTimer::timeout,
this, [this]() {
if (m_result->m_numOfBlocks > LARGE_BLOCK_NUMBER) {
rehighlightSensitiveBlocks();
}
}); });
connect(m_doc, &QTextDocument::contentsChange, connect(m_doc, &QTextDocument::contentsChange,
this, &PegMarkdownHighlighter::handleContentsChange); this, &PegMarkdownHighlighter::handleContentsChange);
connect(m_editor->verticalScrollBar(), &QScrollBar::valueChanged,
m_rehighlightTimer, 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) void PegMarkdownHighlighter::highlightBlock(const QString &p_text)
{ {
QSharedPointer<PegHighlighterResult> result(m_result); QSharedPointer<PegHighlighterResult> result(m_result);
@ -93,27 +116,24 @@ void PegMarkdownHighlighter::highlightBlock(const QString &p_text)
if (result->matched(m_timeStamp)) { if (result->matched(m_timeStamp)) {
preHighlightSingleFormatBlock(result->m_blocksHighlights, blockNum, p_text); preHighlightSingleFormatBlock(result->m_blocksHighlights, blockNum, p_text);
} else {
preHighlightSingleFormatBlock(m_fastResult->m_blocksHighlights, blockNum, p_text);
}
highlightBlockOne(result->m_blocksHighlights, blockNum); highlightBlockOne(result->m_blocksHighlights, blockNum);
} else {
preHighlightSingleFormatBlock(m_fastResult->m_blocksHighlights, blockNum, p_text);
// The complete result is not ready yet. We use fast result for compensation. // If fast result cover this block, we do not need to use the outdated one.
if (!result->matched(m_timeStamp)) { if (!highlightBlockOne(m_fastResult->m_blocksHighlights, blockNum)) {
highlightBlockOne(m_fastResult->m_blocksHighlights, blockNum); highlightBlockOne(result->m_blocksHighlights, blockNum);
}
} }
// Set current block's user data.
updateBlockUserData(blockNum, p_text);
updateBlockUserState(result, blockNum, p_text);
if (currentBlockState() == HighlightBlockState::CodeBlock) { if (currentBlockState() == HighlightBlockState::CodeBlock) {
highlightCodeBlock(result, blockNum, p_text); highlightCodeBlock(result, blockNum, p_text);
highlightCodeBlockColorColumn(p_text); highlightCodeBlockColorColumn(p_text);
PegMarkdownHighlighter::updateBlockCodeBlockTimeStamp(block, result->m_codeBlockTimeStamp);
} }
PegMarkdownHighlighter::updateBlockTimeStamp(block, result->m_timeStamp);
} }
void PegMarkdownHighlighter::preHighlightSingleFormatBlock(const QVector<QVector<HLUnit>> &p_highlights, void PegMarkdownHighlighter::preHighlightSingleFormatBlock(const QVector<QVector<HLUnit>> &p_highlights,
@ -140,13 +160,15 @@ void PegMarkdownHighlighter::preHighlightSingleFormatBlock(const QVector<QVector
} }
} }
void PegMarkdownHighlighter::highlightBlockOne(const QVector<QVector<HLUnit>> &p_highlights, bool PegMarkdownHighlighter::highlightBlockOne(const QVector<QVector<HLUnit>> &p_highlights,
int p_blockNum) int p_blockNum)
{ {
bool highlighted = false;
if (p_highlights.size() > p_blockNum) { if (p_highlights.size() > p_blockNum) {
// units are sorted by start position and length. // units are sorted by start position and length.
const QVector<HLUnit> &units = p_highlights[p_blockNum]; const QVector<HLUnit> &units = p_highlights[p_blockNum];
if (!units.isEmpty()) { if (!units.isEmpty()) {
highlighted = true;
for (int i = 0; i < units.size(); ++i) { for (int i = 0; i < units.size(); ++i) {
const HLUnit &unit = units[i]; const HLUnit &unit = units[i];
if (i == 0) { if (i == 0) {
@ -174,6 +196,8 @@ void PegMarkdownHighlighter::highlightBlockOne(const QVector<QVector<HLUnit>> &p
} }
} }
} }
return highlighted;
} }
// highlightBlock() will be called before this function. // highlightBlock() will be called before this function.
@ -187,7 +211,9 @@ void PegMarkdownHighlighter::handleContentsChange(int p_position, int p_charsRem
++m_timeStamp; ++m_timeStamp;
if (m_timeStamp > 2) {
startFastParse(p_position, p_charsRemoved, p_charsAdded); startFastParse(p_position, p_charsRemoved, p_charsAdded);
}
// We still need a timer to start a complete parse. // We still need a timer to start a complete parse.
m_timer->start(); m_timer->start();
@ -266,7 +292,8 @@ 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)
|| result->m_numOfCodeBlockHighlightsToRecv <= 0) {
return; return;
} }
@ -330,25 +357,22 @@ void PegMarkdownHighlighter::setCodeBlockHighlights(TimeStamp p_timeStamp,
exit: exit:
if (--result->m_numOfCodeBlockHighlightsToRecv <= 0) { if (--result->m_numOfCodeBlockHighlightsToRecv <= 0) {
// Rehighlight specific blocks. ++result->m_codeBlockTimeStamp;
const QVector<QVector<HLUnitStyle>> &hls = result->m_codeBlocksHighlights; rehighlightBlocks();
for (int i = 0; i < hls.size(); ++i) {
if (!hls[i].isEmpty()) {
rehighlightBlock(m_doc->findBlockByNumber(i));
} }
}
}
}
void PegMarkdownHighlighter::updateHighlightFast()
{
updateHighlight();
} }
void PegMarkdownHighlighter::updateHighlight() void PegMarkdownHighlighter::updateHighlight()
{ {
m_timer->stop(); m_timer->stop();
if (m_result->matched(m_timeStamp)) {
// No need to parse again. Already the latest.
updateCodeBlocks(m_result);
rehighlightBlocks();
completeHighlight(m_result);
} else {
startParse(); startParse();
}
} }
void PegMarkdownHighlighter::handleParseResult(const QSharedPointer<PegParseResult> &p_result) void PegMarkdownHighlighter::handleParseResult(const QSharedPointer<PegParseResult> &p_result)
@ -360,14 +384,22 @@ void PegMarkdownHighlighter::handleParseResult(const QSharedPointer<PegParseResu
m_result.reset(new PegHighlighterResult(this, p_result)); m_result.reset(new PegHighlighterResult(this, p_result));
m_singleFormatBlocks.clear(); m_singleFormatBlocks.clear();
updateSingleFormatBlocks(m_result->m_blocksHighlights); updateSingleFormatBlocks(m_result->m_blocksHighlights);
bool matched = m_result->matched(m_timeStamp);
if (matched) {
clearAllBlocksUserDataAndState(m_result);
updateAllBlocksUserState(m_result);
updateCodeBlocks(m_result); updateCodeBlocks(m_result);
}
rehighlight(); rehighlightBlocks();
if (matched) {
completeHighlight(m_result); completeHighlight(m_result);
}
} }
void PegMarkdownHighlighter::updateSingleFormatBlocks(const QVector<QVector<HLUnit>> &p_highlights) void PegMarkdownHighlighter::updateSingleFormatBlocks(const QVector<QVector<HLUnit>> &p_highlights)
@ -386,13 +418,11 @@ void PegMarkdownHighlighter::updateSingleFormatBlocks(const QVector<QVector<HLUn
} }
} }
void PegMarkdownHighlighter::updateCodeBlocks(QSharedPointer<PegHighlighterResult> p_result) void PegMarkdownHighlighter::updateCodeBlocks(const QSharedPointer<PegHighlighterResult> &p_result)
{ {
if (!p_result->matched(m_timeStamp)) { // Only need to receive code block highlights when it is empty.
return; if (g_config->getEnableCodeBlockHighlight()
} && PegMarkdownHighlighter::isEmptyCodeBlockHighlights(p_result->m_codeBlocksHighlights)) {
if (g_config->getEnableCodeBlockHighlight()) {
p_result->m_codeBlocksHighlights.resize(p_result->m_numOfBlocks); p_result->m_codeBlocksHighlights.resize(p_result->m_numOfBlocks);
p_result->m_numOfCodeBlockHighlightsToRecv = p_result->m_codeBlocks.size(); p_result->m_numOfCodeBlockHighlightsToRecv = p_result->m_codeBlocks.size();
} }
@ -400,52 +430,64 @@ void PegMarkdownHighlighter::updateCodeBlocks(QSharedPointer<PegHighlighterResul
emit codeBlocksUpdated(p_result->m_timeStamp, p_result->m_codeBlocks); emit codeBlocksUpdated(p_result->m_timeStamp, p_result->m_codeBlocks);
} }
void PegMarkdownHighlighter::updateBlockUserData(int p_blockNum, const QString &p_text) void PegMarkdownHighlighter::clearAllBlocksUserDataAndState(const QSharedPointer<PegHighlighterResult> &p_result)
{ {
Q_UNUSED(p_text); QTextBlock block = m_doc->firstBlock();
VTextBlockData *blockData = currentBlockData(); while (block.isValid()) {
if (!blockData) { clearBlockUserData(p_result, block);
blockData = new VTextBlockData();
setCurrentBlockUserData(blockData);
} else {
blockData->setCodeBlockIndentation(-1);
}
if (blockData->getPreviews().isEmpty()) { block.setUserState(HighlightBlockState::Normal);
m_possiblePreviewBlocks.remove(p_blockNum);
} else { block = block.next();
m_possiblePreviewBlocks.insert(p_blockNum);
} }
} }
void PegMarkdownHighlighter::updateBlockUserState(const QSharedPointer<PegHighlighterResult> &p_result, void PegMarkdownHighlighter::clearBlockUserData(const QSharedPointer<PegHighlighterResult> &p_result,
int p_blockNum, QTextBlock &p_block)
const QString &p_text)
{ {
// Newly-added block. Q_UNUSED(p_result);
if (currentBlockState() == -1) {
setCurrentBlockState(HighlightBlockState::Normal); int blockNum = p_block.blockNumber();
VTextBlockData *blockData = static_cast<VTextBlockData *>(p_block.userData());
if (!blockData) {
blockData = new VTextBlockData();
p_block.setUserData(blockData);
m_possiblePreviewBlocks.remove(blockNum);
} else {
blockData->setCodeBlockIndentation(-1);
if (blockData->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;
} }
if (!p_result->matched(m_timeStamp)) { // Set code block indentation.
return; if (hlColumn) {
} VTextBlockData *blockData = static_cast<VTextBlockData *>(block.userData());
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); Q_ASSERT(blockData);
state = it.value(); switch (it.value()) {
// Set code block indentation.
switch (state) {
case HighlightBlockState::CodeBlockStart: case HighlightBlockState::CodeBlockStart:
{ {
int startLeadingSpaces = 0; int startLeadingSpaces = 0;
QRegExp reg(VUtils::c_fencedCodeBlockStartRegExp); QRegExp reg(VUtils::c_fencedCodeBlockStartRegExp);
int idx = reg.indexIn(p_text); int idx = reg.indexIn(block.text());
if (idx >= 0) { if (idx >= 0) {
startLeadingSpaces = reg.capturedTexts()[1].size(); startLeadingSpaces = reg.capturedTexts()[1].size();
} }
@ -459,7 +501,7 @@ void PegMarkdownHighlighter::updateBlockUserState(const QSharedPointer<PegHighli
case HighlightBlockState::CodeBlockEnd: case HighlightBlockState::CodeBlockEnd:
{ {
int startLeadingSpaces = 0; int startLeadingSpaces = 0;
VTextBlockData *preBlockData = previousBlockData(); VTextBlockData *preBlockData = previousBlockData(block);
if (preBlockData) { if (preBlockData) {
startLeadingSpaces = preBlockData->getCodeBlockIndentation(); startLeadingSpaces = preBlockData->getCodeBlockIndentation();
} }
@ -472,12 +514,18 @@ void PegMarkdownHighlighter::updateBlockUserState(const QSharedPointer<PegHighli
Q_ASSERT(false); Q_ASSERT(false);
break; break;
} }
} else if (p_result->m_hruleBlocks.contains(p_blockNum)) {
state = HighlightBlockState::HRule;
} }
// Set code block state. block.setUserState(it.value());
setCurrentBlockState(state); }
// 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, void PegMarkdownHighlighter::highlightCodeBlock(const QSharedPointer<PegHighlighterResult> &p_result,
@ -552,10 +600,6 @@ void PegMarkdownHighlighter::highlightCodeBlockColorColumn(const QString &p_text
void PegMarkdownHighlighter::completeHighlight(QSharedPointer<PegHighlighterResult> p_result) void PegMarkdownHighlighter::completeHighlight(QSharedPointer<PegHighlighterResult> p_result)
{ {
if (!p_result->matched(m_timeStamp)) {
return;
}
if (isMathJaxEnabled()) { if (isMathJaxEnabled()) {
emit mathjaxBlocksUpdated(p_result->m_mathjaxBlocks); emit mathjaxBlocksUpdated(p_result->m_mathjaxBlocks);
} }
@ -572,7 +616,7 @@ void PegMarkdownHighlighter::getFastParseBlockRange(int p_position,
int &p_firstBlock, int &p_firstBlock,
int &p_lastBlock) const int &p_lastBlock) const
{ {
const int maxNumOfBlocks = 500; const int maxNumOfBlocks = 100;
int charsChanged = p_charsRemoved + p_charsAdded; int charsChanged = p_charsRemoved + p_charsAdded;
QTextBlock firstBlock = m_doc->findBlock(p_position); QTextBlock firstBlock = m_doc->findBlock(p_position);
@ -673,3 +717,67 @@ void PegMarkdownHighlighter::getFastParseBlockRange(int p_position,
p_firstBlock = p_lastBlock = -1; 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();
}
}
bool PegMarkdownHighlighter::rehighlightBlockRange(int p_first, int p_last)
{
bool highlighted = false;
const QHash<int, HighlightBlockState> &cbStates = m_result->m_codeBlocksState;
QTextBlock block = m_doc->findBlockByNumber(p_first);
while (block.isValid()) {
int blockNum = block.blockNumber();
if (blockNum > p_last) {
break;
}
bool needHL = PegMarkdownHighlighter::blockTimeStamp(block) != m_result->m_timeStamp;
if (!needHL) {
auto it = cbStates.find(blockNum);
if (it != cbStates.end()
&& it.value() == HighlightBlockState::CodeBlock
&& PegMarkdownHighlighter::blockCodeBlockTimeStamp(block) != m_result->m_codeBlockTimeStamp) {
needHL = true;
}
}
if (needHL) {
highlighted = true;
rehighlightBlock(block);
}
block = block.next();
}
return highlighted;
}

View File

@ -10,12 +10,13 @@
class PegParser; class PegParser;
class QTimer; class QTimer;
class VMdEditor;
class PegMarkdownHighlighter : public QSyntaxHighlighter class PegMarkdownHighlighter : public QSyntaxHighlighter
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit PegMarkdownHighlighter(QTextDocument *p_doc = nullptr); PegMarkdownHighlighter(QTextDocument *p_doc, VMdEditor *p_editor);
void init(const QVector<HighlightingStyle> &p_styles, void init(const QVector<HighlightingStyle> &p_styles,
const QHash<QString, QTextCharFormat> &p_codeBlockStyles, const QHash<QString, QTextCharFormat> &p_codeBlockStyles,
@ -33,9 +34,6 @@ public:
void addPossiblePreviewBlock(int p_blockNumber); void addPossiblePreviewBlock(int p_blockNumber);
// Parse and only update the highlight results for rehighlight().
void updateHighlightFast();
QHash<QString, QTextCharFormat> &getCodeBlockStyles(); QHash<QString, QTextCharFormat> &getCodeBlockStyles();
QVector<HighlightingStyle> &getStyles(); QVector<HighlightingStyle> &getStyles();
@ -48,6 +46,10 @@ public slots:
// Parse and rehighlight immediately. // Parse and rehighlight immediately.
void updateHighlight(); void updateHighlight();
// Rehighlight sensitive blocks using current parse result, mainly
// visible blocks.
void rehighlightSensitiveBlocks();
signals: signals:
void highlightCompleted(); void highlightCompleted();
@ -76,14 +78,14 @@ private:
void startFastParse(int p_position, int p_charsRemoved, int p_charsAdded); void startFastParse(int p_position, int p_charsRemoved, int p_charsAdded);
void updateCodeBlocks(QSharedPointer<PegHighlighterResult> p_result); void clearAllBlocksUserDataAndState(const QSharedPointer<PegHighlighterResult> &p_result);
// Set the user data of currentBlock(). void updateAllBlocksUserState(const QSharedPointer<PegHighlighterResult> &p_result);
void updateBlockUserData(int p_blockNum, const QString &p_text);
void updateBlockUserState(const QSharedPointer<PegHighlighterResult> &p_result, void updateCodeBlocks(const QSharedPointer<PegHighlighterResult> &p_result);
int p_blockNum,
const QString &p_text); void clearBlockUserData(const QSharedPointer<PegHighlighterResult> &p_result,
QTextBlock &p_block);
// Highlight fenced code block according to VCodeBlockHighlightHelper result. // Highlight fenced code block according to VCodeBlockHighlightHelper result.
void highlightCodeBlock(const QSharedPointer<PegHighlighterResult> &p_result, void highlightCodeBlock(const QSharedPointer<PegHighlighterResult> &p_result,
@ -97,6 +99,8 @@ private:
VTextBlockData *previousBlockData() const; VTextBlockData *previousBlockData() const;
VTextBlockData *previousBlockData(const QTextBlock &p_block) const;
void completeHighlight(QSharedPointer<PegHighlighterResult> p_result); void completeHighlight(QSharedPointer<PegHighlighterResult> p_result);
bool isMathJaxEnabled() const; bool isMathJaxEnabled() const;
@ -109,7 +113,7 @@ private:
void processFastParseResult(const QSharedPointer<PegParseResult> &p_result); void processFastParseResult(const QSharedPointer<PegParseResult> &p_result);
void highlightBlockOne(const QVector<QVector<HLUnit>> &p_highlights, bool highlightBlockOne(const QVector<QVector<HLUnit>> &p_highlights,
int p_blockNum); int p_blockNum);
// To avoid line height jitter. // To avoid line height jitter.
@ -119,8 +123,24 @@ private:
void updateSingleFormatBlocks(const QVector<QVector<HLUnit>> &p_highlights); void updateSingleFormatBlocks(const QVector<QVector<HLUnit>> &p_highlights);
void rehighlightBlocks();
bool rehighlightBlockRange(int p_first, int p_last);
static bool isEmptyCodeBlockHighlights(const QVector<QVector<HLUnitStyle>> &p_highlights);
static TimeStamp blockTimeStamp(const QTextBlock &p_block);
static void updateBlockTimeStamp(const QTextBlock &p_block, TimeStamp p_ts);
static TimeStamp blockCodeBlockTimeStamp(const QTextBlock &p_block);
static void updateBlockCodeBlockTimeStamp(const QTextBlock &p_block, TimeStamp p_ts);
QTextDocument *m_doc; QTextDocument *m_doc;
VMdEditor *m_editor;
TimeStamp m_timeStamp; TimeStamp m_timeStamp;
QVector<HighlightingStyle> m_styles; QVector<HighlightingStyle> m_styles;
@ -146,6 +166,8 @@ private:
QTimer *m_fastParseTimer; QTimer *m_fastParseTimer;
QTimer *m_rehighlightTimer;
// Blocks have only one format set which occupies the whole block. // Blocks have only one format set which occupies the whole block.
QSet<int> m_singleFormatBlocks; QSet<int> m_singleFormatBlocks;
}; };
@ -207,8 +229,75 @@ inline VTextBlockData *PegMarkdownHighlighter::previousBlockData() const
return static_cast<VTextBlockData *>(block.userData()); return static_cast<VTextBlockData *>(block.userData());
} }
inline VTextBlockData *PegMarkdownHighlighter::previousBlockData(const QTextBlock &p_block) const
{
if (!p_block.isValid()) {
return NULL;
}
QTextBlock block = p_block.previous();
if (!block.isValid()) {
return NULL;
}
return static_cast<VTextBlockData *>(block.userData());
}
inline bool PegMarkdownHighlighter::isMathJaxEnabled() const inline bool PegMarkdownHighlighter::isMathJaxEnabled() const
{ {
return m_parserExts & pmh_EXT_MATH; return m_parserExts & pmh_EXT_MATH;
} }
inline TimeStamp PegMarkdownHighlighter::blockTimeStamp(const QTextBlock &p_block)
{
VTextBlockData *data = static_cast<VTextBlockData *>(p_block.userData());
if (data) {
return data->getTimeStamp();
} else {
return 0;
}
}
inline void PegMarkdownHighlighter::updateBlockTimeStamp(const QTextBlock &p_block, TimeStamp p_ts)
{
VTextBlockData *data = static_cast<VTextBlockData *>(p_block.userData());
if (data) {
data->setTimeStamp(p_ts);
}
}
inline TimeStamp PegMarkdownHighlighter::blockCodeBlockTimeStamp(const QTextBlock &p_block)
{
VTextBlockData *data = static_cast<VTextBlockData *>(p_block.userData());
if (data) {
return data->getCodeBlockTimeStamp();
} else {
return 0;
}
}
inline void PegMarkdownHighlighter::updateBlockCodeBlockTimeStamp(const QTextBlock &p_block, TimeStamp p_ts)
{
VTextBlockData *data = static_cast<VTextBlockData *>(p_block.userData());
if (data) {
data->setCodeBlockTimeStamp(p_ts);
}
}
inline bool PegMarkdownHighlighter::isEmptyCodeBlockHighlights(const QVector<QVector<HLUnitStyle>> &p_highlights)
{
if (p_highlights.isEmpty()) {
return true;
}
bool empty = true;
for (int i = 0; i < p_highlights.size(); ++i) {
if (!p_highlights[i].isEmpty()) {
empty = false;
break;
}
}
return empty;
}
#endif // PEGMARKDOWNHIGHLIGHTER_H #endif // PEGMARKDOWNHIGHLIGHTER_H

View File

@ -1620,3 +1620,20 @@ bool VUtils::onlyHasImgInHtml(const QString &p_html)
QRegExp reg("<(?:p|span|div) "); QRegExp reg("<(?:p|span|div) ");
return !p_html.contains(reg); return !p_html.contains(reg);
} }
int VUtils::elapsedTime(bool p_reset)
{
static QTime tm;
if (p_reset) {
tm = QTime();
return 0;
}
if (tm.isNull()) {
tm.start();
return 0;
}
return tm.restart();
}

View File

@ -54,6 +54,10 @@ class QFormLayout;
# define V_FALLTHROUGH while(false){} # define V_FALLTHROUGH while(false){}
#endif #endif
#define DETIME() qDebug() << "ELAPSED_TIME" << __func__ << __LINE__ << VUtils::elapsedTime()
#define RESET_TIME() VUtils::elapsedTime(true)
enum class MessageBoxType enum class MessageBoxType
{ {
Normal = 0, Normal = 0,
@ -350,6 +354,8 @@ public:
// Whether @p_html has only <img> content. // Whether @p_html has only <img> content.
static bool onlyHasImgInHtml(const QString &p_html); static bool onlyHasImgInHtml(const QString &p_html);
static int elapsedTime(bool p_reset = false);
// Regular expression for image link. // Regular expression for image link.
// ![image title]( http://github.com/tamlok/vnote.jpg "alt text" =200x100) // ![image title]( http://github.com/tamlok/vnote.jpg "alt text" =200x100)
// Captured texts (need to be trimmed): // Captured texts (need to be trimmed):

View File

@ -114,7 +114,7 @@ enum class PreviewImageSource { Image, CodeBlock, Invalid };
enum HighlightBlockState enum HighlightBlockState
{ {
Normal = 0, Normal = -1,
// A fenced code block. // A fenced code block.
CodeBlockStart, CodeBlockStart,

View File

@ -199,6 +199,8 @@ public:
virtual void zoomOutW(int p_range = 1) = 0; virtual void zoomOutW(int p_range = 1) = 0;
virtual void ensureCursorVisibleW() = 0;
protected: protected:
void init(); void init();

View File

@ -60,7 +60,7 @@ VMdEditor::VMdEditor(VFile *p_file,
setReadOnly(true); setReadOnly(true);
m_pegHighlighter = new PegMarkdownHighlighter(document()); m_pegHighlighter = new PegMarkdownHighlighter(document(), this);
m_pegHighlighter->init(g_config->getMdHighlightingStyles(), m_pegHighlighter->init(g_config->getMdHighlightingStyles(),
g_config->getCodeBlockStyles(), g_config->getCodeBlockStyles(),
g_config->getEnableMathjax(), g_config->getEnableMathjax(),
@ -182,13 +182,13 @@ void VMdEditor::reloadFile()
const QString &content = m_file->getContent(); const QString &content = m_file->getContent();
setPlainText(content); setPlainText(content);
setModified(false); setModified(false);
m_pegHighlighter->updateHighlightFast();
m_freshEdit = true;
setReadOnly(readonly); setReadOnly(readonly);
if (!m_freshEdit) {
m_freshEdit = true;
refreshPreview(); refreshPreview();
}
} }
bool VMdEditor::scrollToBlock(int p_blockNumber) bool VMdEditor::scrollToBlock(int p_blockNumber)

View File

@ -184,6 +184,11 @@ public:
zoomPage(false, p_range); zoomPage(false, p_range);
} }
void ensureCursorVisibleW() Q_DECL_OVERRIDE
{
ensureCursorVisible();
}
signals: signals:
// Signal when headers change. // Signal when headers change.
void headersChanged(const QVector<VTableOfContentItem> &p_headers); void headersChanged(const QVector<VTableOfContentItem> &p_headers);

View File

@ -381,8 +381,10 @@ void VPreviewManager::updateBlockPreviewInfo(TS p_timeStamp,
continue; continue;
} }
VTextBlockData *blockData = dynamic_cast<VTextBlockData *>(block.userData()); VTextBlockData *blockData = static_cast<VTextBlockData *>(block.userData());
Q_ASSERT(blockData); if (!blockData) {
continue;
}
VPreviewInfo *info = new VPreviewInfo(PreviewSource::ImageLink, VPreviewInfo *info = new VPreviewInfo(PreviewSource::ImageLink,
p_timeStamp, p_timeStamp,
@ -425,8 +427,11 @@ void VPreviewManager::updateBlockPreviewInfo(TS p_timeStamp,
continue; continue;
} }
VTextBlockData *blockData = dynamic_cast<VTextBlockData *>(block.userData()); VTextBlockData *blockData = static_cast<VTextBlockData *>(block.userData());
Q_ASSERT(blockData); if (!blockData) {
continue;
}
VPreviewInfo *info = new VPreviewInfo(p_source, VPreviewInfo *info = new VPreviewInfo(p_source,
p_timeStamp, p_timeStamp,
img->m_startPos - img->m_blockPos, img->m_startPos - img->m_blockPos,
@ -476,7 +481,7 @@ void VPreviewManager::clearBlockObsoletePreviewInfo(long long p_timeStamp,
continue; continue;
} }
VTextBlockData *blockData = dynamic_cast<VTextBlockData *>(block.userData()); VTextBlockData *blockData = static_cast<VTextBlockData *>(block.userData());
if (!blockData) { if (!blockData) {
continue; continue;
} }
@ -550,7 +555,7 @@ void VPreviewManager::checkBlocksForObsoletePreview(const QList<int> &p_blocks)
continue; continue;
} }
VTextBlockData *blockData = dynamic_cast<VTextBlockData *>(block.userData()); VTextBlockData *blockData = static_cast<VTextBlockData *>(block.userData());
if (!blockData) { if (!blockData) {
continue; continue;
} }

View File

@ -1,9 +1,9 @@
#include "vtextblockdata.h" #include "vtextblockdata.h"
#include <QDebug>
VTextBlockData::VTextBlockData() VTextBlockData::VTextBlockData()
: QTextBlockUserData(), : QTextBlockUserData(),
m_timeStamp(0),
m_codeBlockTimeStamp(0),
m_codeBlockIndentation(-1) m_codeBlockIndentation(-1)
{ {
} }
@ -90,7 +90,6 @@ bool VTextBlockData::clearObsoletePreview(long long p_timeStamp, PreviewSource p
if (ele->m_source == p_source if (ele->m_source == p_source
&& ele->m_timeStamp != p_timeStamp) { && ele->m_timeStamp != p_timeStamp) {
// Remove it. // Remove it.
qDebug() << "clear obsolete preview" << ele->m_imageInfo.toString();
delete ele; delete ele;
it = m_previews.erase(it); it = m_previews.erase(it);
deleted = true; deleted = true;

View File

@ -4,6 +4,8 @@
#include <QTextBlockUserData> #include <QTextBlockUserData>
#include <QVector> #include <QVector>
#include "vconstants.h"
// Sources of the preview. // Sources of the preview.
enum class PreviewSource enum class PreviewSource
{ {
@ -163,10 +165,24 @@ public:
void setCodeBlockIndentation(int p_indent); void setCodeBlockIndentation(int p_indent);
TimeStamp getTimeStamp() const;
void setTimeStamp(TimeStamp p_ts);
TimeStamp getCodeBlockTimeStamp() const;
void setCodeBlockTimeStamp(TimeStamp p_ts);
private: private:
// Check the order of elements. // Check the order of elements.
bool checkOrder() const; bool checkOrder() const;
// TimeStamp of the highlight result which has been applied to this block.
TimeStamp m_timeStamp;
// TimeStamp of the code block highlight result which has been applied to this block.
TimeStamp m_codeBlockTimeStamp;
// Sorted by m_imageInfo.m_startPos, with no two element's position intersected. // Sorted by m_imageInfo.m_startPos, with no two element's position intersected.
QVector<VPreviewInfo *> m_previews; QVector<VPreviewInfo *> m_previews;
@ -188,4 +204,24 @@ inline void VTextBlockData::setCodeBlockIndentation(int p_indent)
{ {
m_codeBlockIndentation = p_indent; m_codeBlockIndentation = p_indent;
} }
inline TimeStamp VTextBlockData::getTimeStamp() const
{
return m_timeStamp;
}
inline void VTextBlockData::setTimeStamp(TimeStamp p_ts)
{
m_timeStamp = p_ts;
}
inline TimeStamp VTextBlockData::getCodeBlockTimeStamp() const
{
return m_codeBlockTimeStamp;
}
inline void VTextBlockData::setCodeBlockTimeStamp(TimeStamp p_ts)
{
m_codeBlockTimeStamp = p_ts;
}
#endif // VTEXTBLOCKDATA_H #endif // VTEXTBLOCKDATA_H