mirror of
https://gitee.com/vnotex/vnote.git
synced 2025-07-05 13:59:52 +08:00
PegMarkdownHighlighter: cache result for performance
This commit is contained in:
parent
24a20e60e9
commit
bfac189cb7
@ -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.
|
||||
|
@ -1,6 +1,8 @@
|
||||
#ifndef PEGHIGHLIGHTERRESULT_H
|
||||
#define PEGHIGHLIGHTERRESULT_H
|
||||
|
||||
#include <QSet>
|
||||
|
||||
#include "vconstants.h"
|
||||
#include "pegparser.h"
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -28,6 +28,7 @@
|
||||
#include <QTreeWidgetItem>
|
||||
#include <QFormLayout>
|
||||
#include <QInputDialog>
|
||||
#include <QFontDatabase>
|
||||
|
||||
#include "vorphanfile.h"
|
||||
#include "vnote.h"
|
||||
|
@ -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();
|
||||
}
|
||||
});
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include <QString>
|
||||
#include <QStyleFactory>
|
||||
#include <QWidgetAction>
|
||||
#include <QKeyEvent>
|
||||
|
||||
#include "veditwindow.h"
|
||||
#include "vnotefile.h"
|
||||
|
@ -4,6 +4,7 @@ VTextBlockData::VTextBlockData()
|
||||
: QTextBlockUserData(),
|
||||
m_timeStamp(0),
|
||||
m_codeBlockTimeStamp(0),
|
||||
m_cacheValid(false),
|
||||
m_codeBlockIndentation(-1)
|
||||
{
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
#include <QInputMethod>
|
||||
#include <QGuiApplication>
|
||||
#include <QKeyEvent>
|
||||
|
||||
#include "vpalette.h"
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user