mirror of
https://gitee.com/vnotex/vnote.git
synced 2025-07-05 13:59:52 +08:00
PegMarkdownHighlighter: improve performance
This commit is contained in:
parent
9b49de3ab5
commit
8679ffa051
@ -22,6 +22,7 @@ PegHighlighterFastResult::PegHighlighterFastResult(const PegMarkdownHighlighter
|
||||
PegHighlighterResult::PegHighlighterResult()
|
||||
: m_timeStamp(0),
|
||||
m_numOfBlocks(0),
|
||||
m_codeBlockTimeStamp(0),
|
||||
m_numOfCodeBlockHighlightsToRecv(0)
|
||||
{
|
||||
m_codeBlockStartExp = QRegExp(VUtils::c_fencedCodeBlockStartRegExp);
|
||||
@ -32,6 +33,7 @@ PegHighlighterResult::PegHighlighterResult(const PegMarkdownHighlighter *p_peg,
|
||||
const QSharedPointer<PegParseResult> &p_result)
|
||||
: m_timeStamp(p_result->m_timeStamp),
|
||||
m_numOfBlocks(p_result->m_numOfBlocks),
|
||||
m_codeBlockTimeStamp(0),
|
||||
m_numOfCodeBlockHighlightsToRecv(0)
|
||||
{
|
||||
m_codeBlockStartExp = QRegExp(VUtils::c_fencedCodeBlockStartRegExp);
|
||||
|
@ -54,6 +54,9 @@ public:
|
||||
// Support fenced code block only.
|
||||
QVector<QVector<HLUnitStyle> > m_codeBlocksHighlights;
|
||||
|
||||
// Timestamp for m_codeBlocksHighlights.
|
||||
TimeStamp m_codeBlockTimeStamp;
|
||||
|
||||
// All image link regions.
|
||||
QVector<VElementRegion> m_imageRegions;
|
||||
|
||||
|
@ -2,17 +2,22 @@
|
||||
|
||||
#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;
|
||||
|
||||
PegMarkdownHighlighter::PegMarkdownHighlighter(QTextDocument *p_doc)
|
||||
#define LARGE_BLOCK_NUMBER 2000
|
||||
|
||||
PegMarkdownHighlighter::PegMarkdownHighlighter(QTextDocument *p_doc, VMdEditor *p_editor)
|
||||
: QSyntaxHighlighter(p_doc),
|
||||
m_doc(p_doc),
|
||||
m_editor(p_editor),
|
||||
m_timeStamp(0),
|
||||
m_parser(NULL),
|
||||
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;
|
||||
for (int i = 0; i < hls.size(); ++i) {
|
||||
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,
|
||||
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)
|
||||
{
|
||||
QSharedPointer<PegHighlighterResult> result(m_result);
|
||||
@ -93,27 +116,24 @@ void PegMarkdownHighlighter::highlightBlock(const QString &p_text)
|
||||
|
||||
if (result->matched(m_timeStamp)) {
|
||||
preHighlightSingleFormatBlock(result->m_blocksHighlights, blockNum, p_text);
|
||||
} else {
|
||||
preHighlightSingleFormatBlock(m_fastResult->m_blocksHighlights, blockNum, p_text);
|
||||
}
|
||||
|
||||
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 (!result->matched(m_timeStamp)) {
|
||||
highlightBlockOne(m_fastResult->m_blocksHighlights, blockNum);
|
||||
// If fast result cover this block, we do not need to use the outdated one.
|
||||
if (!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) {
|
||||
highlightCodeBlock(result, blockNum, 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,
|
||||
@ -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)
|
||||
{
|
||||
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;
|
||||
for (int i = 0; i < units.size(); ++i) {
|
||||
const HLUnit &unit = units[i];
|
||||
if (i == 0) {
|
||||
@ -174,6 +196,8 @@ void PegMarkdownHighlighter::highlightBlockOne(const QVector<QVector<HLUnit>> &p
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return highlighted;
|
||||
}
|
||||
|
||||
// highlightBlock() will be called before this function.
|
||||
@ -187,7 +211,9 @@ void PegMarkdownHighlighter::handleContentsChange(int p_position, int p_charsRem
|
||||
|
||||
++m_timeStamp;
|
||||
|
||||
if (m_timeStamp > 2) {
|
||||
startFastParse(p_position, p_charsRemoved, p_charsAdded);
|
||||
}
|
||||
|
||||
// We still need a timer to start a complete parse.
|
||||
m_timer->start();
|
||||
@ -266,7 +292,8 @@ void PegMarkdownHighlighter::setCodeBlockHighlights(TimeStamp p_timeStamp,
|
||||
const QVector<HLUnitPos> &p_units)
|
||||
{
|
||||
QSharedPointer<PegHighlighterResult> result(m_result);
|
||||
if (!result->matched(p_timeStamp)) {
|
||||
if (!result->matched(p_timeStamp)
|
||||
|| result->m_numOfCodeBlockHighlightsToRecv <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -330,25 +357,22 @@ void PegMarkdownHighlighter::setCodeBlockHighlights(TimeStamp p_timeStamp,
|
||||
|
||||
exit:
|
||||
if (--result->m_numOfCodeBlockHighlightsToRecv <= 0) {
|
||||
// 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));
|
||||
++result->m_codeBlockTimeStamp;
|
||||
rehighlightBlocks();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PegMarkdownHighlighter::updateHighlightFast()
|
||||
{
|
||||
updateHighlight();
|
||||
}
|
||||
|
||||
void PegMarkdownHighlighter::updateHighlight()
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
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_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);
|
||||
}
|
||||
|
||||
rehighlight();
|
||||
rehighlightBlocks();
|
||||
|
||||
if (matched) {
|
||||
completeHighlight(m_result);
|
||||
}
|
||||
}
|
||||
|
||||
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)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (g_config->getEnableCodeBlockHighlight()) {
|
||||
// Only need to receive code block highlights when it is empty.
|
||||
if (g_config->getEnableCodeBlockHighlight()
|
||||
&& PegMarkdownHighlighter::isEmptyCodeBlockHighlights(p_result->m_codeBlocksHighlights)) {
|
||||
p_result->m_codeBlocksHighlights.resize(p_result->m_numOfBlocks);
|
||||
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);
|
||||
}
|
||||
|
||||
void PegMarkdownHighlighter::updateBlockUserData(int p_blockNum, const QString &p_text)
|
||||
void PegMarkdownHighlighter::clearAllBlocksUserDataAndState(const QSharedPointer<PegHighlighterResult> &p_result)
|
||||
{
|
||||
Q_UNUSED(p_text);
|
||||
VTextBlockData *blockData = currentBlockData();
|
||||
if (!blockData) {
|
||||
blockData = new VTextBlockData();
|
||||
setCurrentBlockUserData(blockData);
|
||||
} else {
|
||||
blockData->setCodeBlockIndentation(-1);
|
||||
}
|
||||
QTextBlock block = m_doc->firstBlock();
|
||||
while (block.isValid()) {
|
||||
clearBlockUserData(p_result, block);
|
||||
|
||||
if (blockData->getPreviews().isEmpty()) {
|
||||
m_possiblePreviewBlocks.remove(p_blockNum);
|
||||
} else {
|
||||
m_possiblePreviewBlocks.insert(p_blockNum);
|
||||
block.setUserState(HighlightBlockState::Normal);
|
||||
|
||||
block = block.next();
|
||||
}
|
||||
}
|
||||
|
||||
void PegMarkdownHighlighter::updateBlockUserState(const QSharedPointer<PegHighlighterResult> &p_result,
|
||||
int p_blockNum,
|
||||
const QString &p_text)
|
||||
void PegMarkdownHighlighter::clearBlockUserData(const QSharedPointer<PegHighlighterResult> &p_result,
|
||||
QTextBlock &p_block)
|
||||
{
|
||||
// Newly-added block.
|
||||
if (currentBlockState() == -1) {
|
||||
setCurrentBlockState(HighlightBlockState::Normal);
|
||||
Q_UNUSED(p_result);
|
||||
|
||||
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)) {
|
||||
return;
|
||||
}
|
||||
|
||||
HighlightBlockState state = HighlightBlockState::Normal;
|
||||
|
||||
auto it = p_result->m_codeBlocksState.find(p_blockNum);
|
||||
if (it != p_result->m_codeBlocksState.end()) {
|
||||
VTextBlockData *blockData = currentBlockData();
|
||||
// Set code block indentation.
|
||||
if (hlColumn) {
|
||||
VTextBlockData *blockData = static_cast<VTextBlockData *>(block.userData());
|
||||
Q_ASSERT(blockData);
|
||||
|
||||
state = it.value();
|
||||
// Set code block indentation.
|
||||
switch (state) {
|
||||
switch (it.value()) {
|
||||
case HighlightBlockState::CodeBlockStart:
|
||||
{
|
||||
int startLeadingSpaces = 0;
|
||||
QRegExp reg(VUtils::c_fencedCodeBlockStartRegExp);
|
||||
int idx = reg.indexIn(p_text);
|
||||
int idx = reg.indexIn(block.text());
|
||||
if (idx >= 0) {
|
||||
startLeadingSpaces = reg.capturedTexts()[1].size();
|
||||
}
|
||||
@ -459,7 +501,7 @@ void PegMarkdownHighlighter::updateBlockUserState(const QSharedPointer<PegHighli
|
||||
case HighlightBlockState::CodeBlockEnd:
|
||||
{
|
||||
int startLeadingSpaces = 0;
|
||||
VTextBlockData *preBlockData = previousBlockData();
|
||||
VTextBlockData *preBlockData = previousBlockData(block);
|
||||
if (preBlockData) {
|
||||
startLeadingSpaces = preBlockData->getCodeBlockIndentation();
|
||||
}
|
||||
@ -472,12 +514,18 @@ void PegMarkdownHighlighter::updateBlockUserState(const QSharedPointer<PegHighli
|
||||
Q_ASSERT(false);
|
||||
break;
|
||||
}
|
||||
} else if (p_result->m_hruleBlocks.contains(p_blockNum)) {
|
||||
state = HighlightBlockState::HRule;
|
||||
}
|
||||
|
||||
// Set code block state.
|
||||
setCurrentBlockState(state);
|
||||
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,
|
||||
@ -552,10 +600,6 @@ void PegMarkdownHighlighter::highlightCodeBlockColorColumn(const QString &p_text
|
||||
|
||||
void PegMarkdownHighlighter::completeHighlight(QSharedPointer<PegHighlighterResult> p_result)
|
||||
{
|
||||
if (!p_result->matched(m_timeStamp)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isMathJaxEnabled()) {
|
||||
emit mathjaxBlocksUpdated(p_result->m_mathjaxBlocks);
|
||||
}
|
||||
@ -572,7 +616,7 @@ void PegMarkdownHighlighter::getFastParseBlockRange(int p_position,
|
||||
int &p_firstBlock,
|
||||
int &p_lastBlock) const
|
||||
{
|
||||
const int maxNumOfBlocks = 500;
|
||||
const int maxNumOfBlocks = 100;
|
||||
|
||||
int charsChanged = p_charsRemoved + p_charsAdded;
|
||||
QTextBlock firstBlock = m_doc->findBlock(p_position);
|
||||
@ -673,3 +717,67 @@ void PegMarkdownHighlighter::getFastParseBlockRange(int p_position,
|
||||
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;
|
||||
}
|
||||
|
@ -10,12 +10,13 @@
|
||||
|
||||
class PegParser;
|
||||
class QTimer;
|
||||
class VMdEditor;
|
||||
|
||||
class PegMarkdownHighlighter : public QSyntaxHighlighter
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit PegMarkdownHighlighter(QTextDocument *p_doc = nullptr);
|
||||
PegMarkdownHighlighter(QTextDocument *p_doc, VMdEditor *p_editor);
|
||||
|
||||
void init(const QVector<HighlightingStyle> &p_styles,
|
||||
const QHash<QString, QTextCharFormat> &p_codeBlockStyles,
|
||||
@ -33,9 +34,6 @@ public:
|
||||
|
||||
void addPossiblePreviewBlock(int p_blockNumber);
|
||||
|
||||
// Parse and only update the highlight results for rehighlight().
|
||||
void updateHighlightFast();
|
||||
|
||||
QHash<QString, QTextCharFormat> &getCodeBlockStyles();
|
||||
|
||||
QVector<HighlightingStyle> &getStyles();
|
||||
@ -48,6 +46,10 @@ public slots:
|
||||
// Parse and rehighlight immediately.
|
||||
void updateHighlight();
|
||||
|
||||
// Rehighlight sensitive blocks using current parse result, mainly
|
||||
// visible blocks.
|
||||
void rehighlightSensitiveBlocks();
|
||||
|
||||
signals:
|
||||
void highlightCompleted();
|
||||
|
||||
@ -76,14 +78,14 @@ private:
|
||||
|
||||
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 updateBlockUserData(int p_blockNum, const QString &p_text);
|
||||
void updateAllBlocksUserState(const QSharedPointer<PegHighlighterResult> &p_result);
|
||||
|
||||
void updateBlockUserState(const QSharedPointer<PegHighlighterResult> &p_result,
|
||||
int p_blockNum,
|
||||
const QString &p_text);
|
||||
void updateCodeBlocks(const QSharedPointer<PegHighlighterResult> &p_result);
|
||||
|
||||
void clearBlockUserData(const QSharedPointer<PegHighlighterResult> &p_result,
|
||||
QTextBlock &p_block);
|
||||
|
||||
// Highlight fenced code block according to VCodeBlockHighlightHelper result.
|
||||
void highlightCodeBlock(const QSharedPointer<PegHighlighterResult> &p_result,
|
||||
@ -97,6 +99,8 @@ private:
|
||||
|
||||
VTextBlockData *previousBlockData() const;
|
||||
|
||||
VTextBlockData *previousBlockData(const QTextBlock &p_block) const;
|
||||
|
||||
void completeHighlight(QSharedPointer<PegHighlighterResult> p_result);
|
||||
|
||||
bool isMathJaxEnabled() const;
|
||||
@ -109,7 +113,7 @@ private:
|
||||
|
||||
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);
|
||||
|
||||
// To avoid line height jitter.
|
||||
@ -119,8 +123,24 @@ private:
|
||||
|
||||
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;
|
||||
|
||||
VMdEditor *m_editor;
|
||||
|
||||
TimeStamp m_timeStamp;
|
||||
|
||||
QVector<HighlightingStyle> m_styles;
|
||||
@ -146,6 +166,8 @@ private:
|
||||
|
||||
QTimer *m_fastParseTimer;
|
||||
|
||||
QTimer *m_rehighlightTimer;
|
||||
|
||||
// Blocks have only one format set which occupies the whole block.
|
||||
QSet<int> m_singleFormatBlocks;
|
||||
};
|
||||
@ -207,8 +229,75 @@ inline VTextBlockData *PegMarkdownHighlighter::previousBlockData() const
|
||||
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
|
||||
{
|
||||
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
|
||||
|
@ -1620,3 +1620,20 @@ bool VUtils::onlyHasImgInHtml(const QString &p_html)
|
||||
QRegExp reg("<(?:p|span|div) ");
|
||||
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();
|
||||
}
|
||||
|
@ -54,6 +54,10 @@ class QFormLayout;
|
||||
# define V_FALLTHROUGH while(false){}
|
||||
#endif
|
||||
|
||||
#define DETIME() qDebug() << "ELAPSED_TIME" << __func__ << __LINE__ << VUtils::elapsedTime()
|
||||
|
||||
#define RESET_TIME() VUtils::elapsedTime(true)
|
||||
|
||||
enum class MessageBoxType
|
||||
{
|
||||
Normal = 0,
|
||||
@ -350,6 +354,8 @@ public:
|
||||
// Whether @p_html has only <img> content.
|
||||
static bool onlyHasImgInHtml(const QString &p_html);
|
||||
|
||||
static int elapsedTime(bool p_reset = false);
|
||||
|
||||
// Regular expression for image link.
|
||||
// 
|
||||
// Captured texts (need to be trimmed):
|
||||
|
@ -114,7 +114,7 @@ enum class PreviewImageSource { Image, CodeBlock, Invalid };
|
||||
|
||||
enum HighlightBlockState
|
||||
{
|
||||
Normal = 0,
|
||||
Normal = -1,
|
||||
|
||||
// A fenced code block.
|
||||
CodeBlockStart,
|
||||
|
@ -199,6 +199,8 @@ public:
|
||||
|
||||
virtual void zoomOutW(int p_range = 1) = 0;
|
||||
|
||||
virtual void ensureCursorVisibleW() = 0;
|
||||
|
||||
protected:
|
||||
void init();
|
||||
|
||||
|
@ -60,7 +60,7 @@ VMdEditor::VMdEditor(VFile *p_file,
|
||||
|
||||
setReadOnly(true);
|
||||
|
||||
m_pegHighlighter = new PegMarkdownHighlighter(document());
|
||||
m_pegHighlighter = new PegMarkdownHighlighter(document(), this);
|
||||
m_pegHighlighter->init(g_config->getMdHighlightingStyles(),
|
||||
g_config->getCodeBlockStyles(),
|
||||
g_config->getEnableMathjax(),
|
||||
@ -182,13 +182,13 @@ void VMdEditor::reloadFile()
|
||||
const QString &content = m_file->getContent();
|
||||
setPlainText(content);
|
||||
setModified(false);
|
||||
m_pegHighlighter->updateHighlightFast();
|
||||
|
||||
m_freshEdit = true;
|
||||
|
||||
setReadOnly(readonly);
|
||||
|
||||
if (!m_freshEdit) {
|
||||
m_freshEdit = true;
|
||||
refreshPreview();
|
||||
}
|
||||
}
|
||||
|
||||
bool VMdEditor::scrollToBlock(int p_blockNumber)
|
||||
|
@ -184,6 +184,11 @@ public:
|
||||
zoomPage(false, p_range);
|
||||
}
|
||||
|
||||
void ensureCursorVisibleW() Q_DECL_OVERRIDE
|
||||
{
|
||||
ensureCursorVisible();
|
||||
}
|
||||
|
||||
signals:
|
||||
// Signal when headers change.
|
||||
void headersChanged(const QVector<VTableOfContentItem> &p_headers);
|
||||
|
@ -381,8 +381,10 @@ void VPreviewManager::updateBlockPreviewInfo(TS p_timeStamp,
|
||||
continue;
|
||||
}
|
||||
|
||||
VTextBlockData *blockData = dynamic_cast<VTextBlockData *>(block.userData());
|
||||
Q_ASSERT(blockData);
|
||||
VTextBlockData *blockData = static_cast<VTextBlockData *>(block.userData());
|
||||
if (!blockData) {
|
||||
continue;
|
||||
}
|
||||
|
||||
VPreviewInfo *info = new VPreviewInfo(PreviewSource::ImageLink,
|
||||
p_timeStamp,
|
||||
@ -425,8 +427,11 @@ void VPreviewManager::updateBlockPreviewInfo(TS p_timeStamp,
|
||||
continue;
|
||||
}
|
||||
|
||||
VTextBlockData *blockData = dynamic_cast<VTextBlockData *>(block.userData());
|
||||
Q_ASSERT(blockData);
|
||||
VTextBlockData *blockData = static_cast<VTextBlockData *>(block.userData());
|
||||
if (!blockData) {
|
||||
continue;
|
||||
}
|
||||
|
||||
VPreviewInfo *info = new VPreviewInfo(p_source,
|
||||
p_timeStamp,
|
||||
img->m_startPos - img->m_blockPos,
|
||||
@ -476,7 +481,7 @@ void VPreviewManager::clearBlockObsoletePreviewInfo(long long p_timeStamp,
|
||||
continue;
|
||||
}
|
||||
|
||||
VTextBlockData *blockData = dynamic_cast<VTextBlockData *>(block.userData());
|
||||
VTextBlockData *blockData = static_cast<VTextBlockData *>(block.userData());
|
||||
if (!blockData) {
|
||||
continue;
|
||||
}
|
||||
@ -550,7 +555,7 @@ void VPreviewManager::checkBlocksForObsoletePreview(const QList<int> &p_blocks)
|
||||
continue;
|
||||
}
|
||||
|
||||
VTextBlockData *blockData = dynamic_cast<VTextBlockData *>(block.userData());
|
||||
VTextBlockData *blockData = static_cast<VTextBlockData *>(block.userData());
|
||||
if (!blockData) {
|
||||
continue;
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
#include "vtextblockdata.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
VTextBlockData::VTextBlockData()
|
||||
: QTextBlockUserData(),
|
||||
m_timeStamp(0),
|
||||
m_codeBlockTimeStamp(0),
|
||||
m_codeBlockIndentation(-1)
|
||||
{
|
||||
}
|
||||
@ -90,7 +90,6 @@ bool VTextBlockData::clearObsoletePreview(long long p_timeStamp, PreviewSource p
|
||||
if (ele->m_source == p_source
|
||||
&& ele->m_timeStamp != p_timeStamp) {
|
||||
// Remove it.
|
||||
qDebug() << "clear obsolete preview" << ele->m_imageInfo.toString();
|
||||
delete ele;
|
||||
it = m_previews.erase(it);
|
||||
deleted = true;
|
||||
|
@ -4,6 +4,8 @@
|
||||
#include <QTextBlockUserData>
|
||||
#include <QVector>
|
||||
|
||||
#include "vconstants.h"
|
||||
|
||||
// Sources of the preview.
|
||||
enum class PreviewSource
|
||||
{
|
||||
@ -163,10 +165,24 @@ public:
|
||||
|
||||
void setCodeBlockIndentation(int p_indent);
|
||||
|
||||
TimeStamp getTimeStamp() const;
|
||||
|
||||
void setTimeStamp(TimeStamp p_ts);
|
||||
|
||||
TimeStamp getCodeBlockTimeStamp() const;
|
||||
|
||||
void setCodeBlockTimeStamp(TimeStamp p_ts);
|
||||
|
||||
private:
|
||||
// Check the order of elements.
|
||||
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.
|
||||
QVector<VPreviewInfo *> m_previews;
|
||||
|
||||
@ -188,4 +204,24 @@ inline void VTextBlockData::setCodeBlockIndentation(int 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
|
||||
|
Loading…
x
Reference in New Issue
Block a user