PegMarkdownHighlighter: cache result for performance

This commit is contained in:
Le Tan 2018-07-30 20:40:31 +08:00
parent 24a20e60e9
commit bfac189cb7
10 changed files with 243 additions and 30 deletions

View File

@ -4,7 +4,6 @@
#include <QTextCharFormat>
#include "vconstants.h"
#include "vtextblockdata.h"
extern "C" {
#include <pmh_parser.h>
@ -26,6 +25,18 @@ struct HLUnit
unsigned long start;
unsigned long length;
unsigned int styleIndex;
bool operator==(const HLUnit &p_a) const
{
return start == p_a.start
&& length == p_a.length
&& styleIndex == p_a.styleIndex;
}
QString toString() const
{
return QString("HLUnit %1 %2 %3").arg(start).arg(length).arg(styleIndex);
}
};
struct HLUnitStyle
@ -33,6 +44,15 @@ struct HLUnitStyle
unsigned long start;
unsigned long length;
QString style;
bool operator==(const HLUnitStyle &p_a) const
{
if (start != p_a.start || length != p_a.length) {
return false;
}
return style == p_a.style;
}
};
// Fenced code block only.

View File

@ -1,6 +1,8 @@
#ifndef PEGHIGHLIGHTERRESULT_H
#define PEGHIGHLIGHTERRESULT_H
#include <QSet>
#include "vconstants.h"
#include "pegparser.h"

View File

@ -12,13 +12,14 @@
extern VConfigManager *g_config;
#define LARGE_BLOCK_NUMBER 2000
#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)
{
@ -114,39 +115,62 @@ void PegMarkdownHighlighter::highlightBlock(const QString &p_text)
QTextBlock block = currentBlock();
int blockNum = block.blockNumber();
if (result->matched(m_timeStamp)) {
preHighlightSingleFormatBlock(result->m_blocksHighlights, blockNum, p_text);
VTextBlockData *blockData = PegMarkdownHighlighter::getBlockData(block);
QVector<HLUnit> *cache = NULL;
QVector<HLUnitStyle> *cbCache = NULL;
if (blockData) {
cache = &blockData->getBlockHighlightCache();
cbCache = &blockData->getCodeBlockHighlightCache();
highlightBlockOne(result->m_blocksHighlights, blockNum);
cache->clear();
cbCache->clear();
}
bool cacheValid = true;
if (result->matched(m_timeStamp)) {
if (preHighlightSingleFormatBlock(result->m_blocksHighlights, blockNum, p_text)) {
cacheValid = false;
}
highlightBlockOne(result->m_blocksHighlights, blockNum, cacheValid ? cache : NULL);
} else {
preHighlightSingleFormatBlock(m_fastResult->m_blocksHighlights, blockNum, p_text);
if (preHighlightSingleFormatBlock(m_fastResult->m_blocksHighlights, blockNum, p_text)) {
cacheValid = false;
}
// 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);
if (highlightBlockOne(m_fastResult->m_blocksHighlights, blockNum, NULL)) {
cacheValid = false;
} else {
highlightBlockOne(result->m_blocksHighlights, blockNum, cacheValid ? cache : NULL);
}
}
if (currentBlockState() == HighlightBlockState::CodeBlock) {
highlightCodeBlock(result, blockNum, p_text);
highlightCodeBlock(result, blockNum, p_text, cacheValid ? cbCache : NULL);
highlightCodeBlockColorColumn(p_text);
PegMarkdownHighlighter::updateBlockCodeBlockTimeStamp(block, result->m_codeBlockTimeStamp);
}
PegMarkdownHighlighter::updateBlockTimeStamp(block, result->m_timeStamp);
if (blockData) {
blockData->setCacheValid(cacheValid);
}
}
void PegMarkdownHighlighter::preHighlightSingleFormatBlock(const QVector<QVector<HLUnit>> &p_highlights,
bool PegMarkdownHighlighter::preHighlightSingleFormatBlock(const QVector<QVector<HLUnit>> &p_highlights,
int p_blockNum,
const QString &p_text)
{
int sz = p_text.size();
if (sz == 0) {
return;
return false;
}
if (!m_singleFormatBlocks.contains(p_blockNum)) {
return;
return false;
}
if (p_highlights.size() > p_blockNum) {
@ -155,13 +179,17 @@ void PegMarkdownHighlighter::preHighlightSingleFormatBlock(const QVector<QVector
const HLUnit &unit = units[0];
if (unit.start == 0 && (int)unit.length < sz) {
setFormat(unit.length, sz - unit.length, m_styles[unit.styleIndex].format);
return true;
}
}
}
return false;
}
bool PegMarkdownHighlighter::highlightBlockOne(const QVector<QVector<HLUnit>> &p_highlights,
int p_blockNum)
int p_blockNum,
QVector<HLUnit> *p_cache)
{
bool highlighted = false;
if (p_highlights.size() > p_blockNum) {
@ -169,6 +197,10 @@ bool PegMarkdownHighlighter::highlightBlockOne(const QVector<QVector<HLUnit>> &p
const QVector<HLUnit> &units = p_highlights[p_blockNum];
if (!units.isEmpty()) {
highlighted = true;
if (p_cache) {
p_cache->append(units);
}
for (int i = 0; i < units.size(); ++i) {
const HLUnit &unit = units[i];
if (i == 0) {
@ -357,7 +389,7 @@ void PegMarkdownHighlighter::setCodeBlockHighlights(TimeStamp p_timeStamp,
exit:
if (--result->m_numOfCodeBlockHighlightsToRecv <= 0) {
++result->m_codeBlockTimeStamp;
result->m_codeBlockTimeStamp = nextCodeBlockTimeStamp();
rehighlightBlocks();
}
}
@ -383,6 +415,8 @@ void PegMarkdownHighlighter::handleParseResult(const QSharedPointer<PegParseResu
m_result.reset(new PegHighlighterResult(this, p_result));
m_result->m_codeBlockTimeStamp = nextCodeBlockTimeStamp();
m_singleFormatBlocks.clear();
updateSingleFormatBlocks(m_result->m_blocksHighlights);
@ -421,10 +455,14 @@ void PegMarkdownHighlighter::updateSingleFormatBlocks(const QVector<QVector<HLUn
void PegMarkdownHighlighter::updateCodeBlocks(const QSharedPointer<PegHighlighterResult> &p_result)
{
// Only need to receive code block highlights when it is empty.
if (g_config->getEnableCodeBlockHighlight()
&& PegMarkdownHighlighter::isEmptyCodeBlockHighlights(p_result->m_codeBlocksHighlights)) {
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 = p_result->m_codeBlocks.size();
p_result->m_numOfCodeBlockHighlightsToRecv = cbSz;
}
}
}
emit codeBlocksUpdated(p_result->m_timeStamp, p_result->m_codeBlocks);
@ -530,7 +568,8 @@ void PegMarkdownHighlighter::updateAllBlocksUserState(const QSharedPointer<PegHi
void PegMarkdownHighlighter::highlightCodeBlock(const QSharedPointer<PegHighlighterResult> &p_result,
int p_blockNum,
const QString &p_text)
const QString &p_text,
QVector<HLUnitStyle> *p_cache)
{
// Brush the indentation spaces.
if (currentBlockState() == HighlightBlockState::CodeBlock) {
@ -544,6 +583,10 @@ void PegMarkdownHighlighter::highlightCodeBlock(const QSharedPointer<PegHighligh
const QVector<HLUnitStyle> &units = p_result->m_codeBlocksHighlights[p_blockNum];
if (!units.isEmpty()) {
QVector<QTextCharFormat *> formats(units.size(), NULL);
if (p_cache) {
p_cache->append(units);
}
for (int i = 0; i < units.size(); ++i) {
const HLUnitStyle &unit = units[i];
auto it = m_codeBlockStyles.find(unit.style);
@ -753,7 +796,10 @@ 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();
@ -761,23 +807,53 @@ bool PegMarkdownHighlighter::rehighlightBlockRange(int p_first, int 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) {
bool needHL = false;
bool updateTS = false;
VTextBlockData *data = PegMarkdownHighlighter::getBlockData(block);
if (PegMarkdownHighlighter::blockTimeStamp(block) != m_result->m_timeStamp) {
needHL = true;
// Try to find cache.
if (data && 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) {
needHL = true;
// Try to find cache.
if (updateTS && data && blockNum < cbHls.size()) {
if (data->isCodeBlockHighlightCacheMatched(cbHls[blockNum])) {
needHL = false;
}
}
}
}
}
if (!needHL && data && !data->getPreviews().isEmpty()) {
needHL = true;
}
if (needHL) {
highlighted = true;
rehighlightBlock(block);
++nr;
} else if (updateTS) {
data->setTimeStamp(m_result->m_timeStamp);
data->setCodeBlockTimeStamp(m_result->m_codeBlockTimeStamp);
}
block = block.next();
}
qDebug() << "rehighlightBlockRange" << p_first << p_last << nr;
return highlighted;
}

View File

@ -90,7 +90,8 @@ private:
// Highlight fenced code block according to VCodeBlockHighlightHelper result.
void highlightCodeBlock(const QSharedPointer<PegHighlighterResult> &p_result,
int p_blockNum,
const QString &p_text);
const QString &p_text,
QVector<HLUnitStyle> *p_cache);
// Highlight color column in code block.
void highlightCodeBlockColorColumn(const QString &p_text);
@ -114,10 +115,11 @@ private:
void processFastParseResult(const QSharedPointer<PegParseResult> &p_result);
bool highlightBlockOne(const QVector<QVector<HLUnit>> &p_highlights,
int p_blockNum);
int p_blockNum,
QVector<HLUnit> *p_cache);
// To avoid line height jitter.
void preHighlightSingleFormatBlock(const QVector<QVector<HLUnit>> &p_highlights,
bool preHighlightSingleFormatBlock(const QVector<QVector<HLUnit>> &p_highlights,
int p_blockNum,
const QString &p_text);
@ -127,6 +129,10 @@ private:
bool rehighlightBlockRange(int p_first, int p_last);
TimeStamp nextCodeBlockTimeStamp();
static VTextBlockData *getBlockData(const QTextBlock &p_block);
static bool isEmptyCodeBlockHighlights(const QVector<QVector<HLUnitStyle>> &p_highlights);
static TimeStamp blockTimeStamp(const QTextBlock &p_block);
@ -143,6 +149,8 @@ private:
TimeStamp m_timeStamp;
TimeStamp m_codeBlockTimeStamp;
QVector<HighlightingStyle> m_styles;
QHash<QString, QTextCharFormat> m_codeBlockStyles;
@ -300,4 +308,14 @@ inline bool PegMarkdownHighlighter::isEmptyCodeBlockHighlights(const QVector<QVe
return empty;
}
inline VTextBlockData *PegMarkdownHighlighter::getBlockData(const QTextBlock &p_block)
{
return static_cast<VTextBlockData *>(p_block.userData());
}
inline TimeStamp PegMarkdownHighlighter::nextCodeBlockTimeStamp()
{
return ++m_codeBlockTimeStamp;
}
#endif // PEGMARKDOWNHIGHLIGHTER_H

View File

@ -28,6 +28,7 @@
#include <QTreeWidgetItem>
#include <QFormLayout>
#include <QInputDialog>
#include <QFontDatabase>
#include "vorphanfile.h"
#include "vnote.h"

View File

@ -55,7 +55,7 @@ VMdEditor::VMdEditor(VFile *p_file,
connect(document(), &QTextDocument::contentsChange,
this, [this](int p_position, int p_charsRemoved, int p_charsAdded) {
Q_UNUSED(p_position);
if (p_charsAdded > 0 || p_charsAdded > 0) {
if (p_charsRemoved > 0 || p_charsAdded > 0) {
updateTimeStamp();
}
});

View File

@ -7,6 +7,7 @@
#include <QString>
#include <QStyleFactory>
#include <QWidgetAction>
#include <QKeyEvent>
#include "veditwindow.h"
#include "vnotefile.h"

View File

@ -4,6 +4,7 @@ VTextBlockData::VTextBlockData()
: QTextBlockUserData(),
m_timeStamp(0),
m_codeBlockTimeStamp(0),
m_cacheValid(false),
m_codeBlockIndentation(-1)
{
}

View File

@ -3,8 +3,10 @@
#include <QTextBlockUserData>
#include <QVector>
#include <QDebug>
#include "vconstants.h"
#include "markdownhighlighterdata.h"
// Sources of the preview.
enum class PreviewSource
@ -173,6 +175,22 @@ public:
void setCodeBlockTimeStamp(TimeStamp p_ts);
bool isBlockHighlightCacheMatched(const QVector<HLUnit> &p_highlight) const;
QVector<HLUnit> &getBlockHighlightCache();
void setBlockHighlightCache(const QVector<HLUnit> &p_highlight);
bool isCodeBlockHighlightCacheMatched(const QVector<HLUnitStyle> &p_highlight) const;
QVector<HLUnitStyle> &getCodeBlockHighlightCache();
void setCodeBlockHighlightCache(const QVector<HLUnitStyle> &p_highlight);
bool isCacheValid() const;
void setCacheValid(bool p_valid);
private:
// Check the order of elements.
bool checkOrder() const;
@ -183,6 +201,15 @@ private:
// TimeStamp of the code block highlight result which has been applied to this block.
TimeStamp m_codeBlockTimeStamp;
// Block highlight cache.
QVector<HLUnit> m_blockHighlightCache;
// Code block highlight cache.
QVector<HLUnitStyle> m_codeBlockHighlightCache;
// Whether the above two cahces are valid.
bool m_cacheValid;
// Sorted by m_imageInfo.m_startPos, with no two element's position intersected.
QVector<VPreviewInfo *> m_previews;
@ -224,4 +251,70 @@ inline void VTextBlockData::setCodeBlockTimeStamp(TimeStamp p_ts)
{
m_codeBlockTimeStamp = p_ts;
}
inline bool VTextBlockData::isBlockHighlightCacheMatched(const QVector<HLUnit> &p_highlight) const
{
if (!m_cacheValid
|| p_highlight.size() != m_blockHighlightCache.size()) {
return false;
}
int sz = p_highlight.size();
for (int i = 0; i < sz; ++i)
{
if (!(p_highlight[i] == m_blockHighlightCache[i])) {
return false;
}
}
return true;
}
inline QVector<HLUnit> &VTextBlockData::getBlockHighlightCache()
{
return m_blockHighlightCache;
}
inline void VTextBlockData::setBlockHighlightCache(const QVector<HLUnit> &p_highlight)
{
m_blockHighlightCache = p_highlight;
}
inline bool VTextBlockData::isCodeBlockHighlightCacheMatched(const QVector<HLUnitStyle> &p_highlight) const
{
if (!m_cacheValid
|| p_highlight.size() != m_codeBlockHighlightCache.size()) {
return false;
}
int sz = p_highlight.size();
for (int i = 0; i < sz; ++i)
{
if (!(p_highlight[i] == m_codeBlockHighlightCache[i])) {
return false;
}
}
return true;
}
inline QVector<HLUnitStyle> &VTextBlockData::getCodeBlockHighlightCache()
{
return m_codeBlockHighlightCache;
}
inline void VTextBlockData::setCodeBlockHighlightCache(const QVector<HLUnitStyle> &p_highlight)
{
m_codeBlockHighlightCache = p_highlight;
}
inline bool VTextBlockData::isCacheValid() const
{
return m_cacheValid;
}
inline void VTextBlockData::setCacheValid(bool p_valid)
{
m_cacheValid = p_valid;
}
#endif // VTEXTBLOCKDATA_H

View File

@ -2,6 +2,7 @@
#include <QInputMethod>
#include <QGuiApplication>
#include <QKeyEvent>
#include "vpalette.h"