HGMarkdownHighlighter: use cached header info to highlight headers fast

This commit is contained in:
Le Tan 2018-01-16 19:46:03 +08:00
parent 0b885e1639
commit b1f7760ea2
2 changed files with 94 additions and 26 deletions

View File

@ -58,6 +58,13 @@ HGMarkdownHighlighter::HGMarkdownHighlighter(const QVector<HighlightingStyle> &s
m_colorColumnFormat.setForeground(QColor(g_config->getEditorColorColumnFg())); m_colorColumnFormat.setForeground(QColor(g_config->getEditorColorColumnFg()));
m_colorColumnFormat.setBackground(QColor(g_config->getEditorColorColumnBg())); m_colorColumnFormat.setBackground(QColor(g_config->getEditorColorColumnBg()));
m_headerStyles.resize(6);
for (auto const & it : highlightingStyles) {
if (it.type >= pmh_H1 && it.type <= pmh_H6) {
m_headerStyles[it.type - pmh_H1] = it.format;
}
}
resizeBuffer(initCapacity); resizeBuffer(initCapacity);
document = parent; document = parent;
@ -145,35 +152,39 @@ void HGMarkdownHighlighter::highlightBlock(const QString &text)
// fix this. // fix this.
// highlightLinkWithSpacesInURL(text); // highlightLinkWithSpacesInURL(text);
highlightHeaderFast(blockNum, text);
// Highlight CodeBlock using VCodeBlockHighlightHelper. // Highlight CodeBlock using VCodeBlockHighlightHelper.
if (m_codeBlockHighlights.size() > blockNum) { if (m_codeBlockHighlights.size() > blockNum) {
const QVector<HLUnitStyle> &units = m_codeBlockHighlights[blockNum]; const QVector<HLUnitStyle> &units = m_codeBlockHighlights[blockNum];
// Manually simply merge the format of all the units within the same block. if (!units.isEmpty()) {
// Using QTextCursor to get the char format after setFormat() seems // Manually simply merge the format of all the units within the same block.
// not to work. // Using QTextCursor to get the char format after setFormat() seems
QVector<QTextCharFormat> formats; // not to work.
formats.reserve(units.size()); QVector<QTextCharFormat> formats;
// formatIndex[i] is the index in @formats which is the format of the formats.reserve(units.size());
// ith character. // formatIndex[i] is the index in @formats which is the format of the
QVector<int> formatIndex(currentBlock().length(), -1); // ith character.
for (int i = 0; i < units.size(); ++i) { QVector<int> formatIndex(currentBlock().length(), -1);
const HLUnitStyle &unit = units[i]; for (int i = 0; i < units.size(); ++i) {
auto it = m_codeBlockStyles.find(unit.style); const HLUnitStyle &unit = units[i];
if (it != m_codeBlockStyles.end()) { auto it = m_codeBlockStyles.find(unit.style);
QTextCharFormat newFormat; if (it != m_codeBlockStyles.end()) {
if (unit.start < (unsigned int)formatIndex.size() && formatIndex[unit.start] != -1) { QTextCharFormat newFormat;
newFormat = formats[formatIndex[unit.start]]; if (unit.start < (unsigned int)formatIndex.size() && formatIndex[unit.start] != -1) {
newFormat.merge(*it); newFormat = formats[formatIndex[unit.start]];
} else { newFormat.merge(*it);
newFormat = *it; } else {
} newFormat = *it;
setFormat(unit.start, unit.length, newFormat); }
setFormat(unit.start, unit.length, newFormat);
formats.append(newFormat); formats.append(newFormat);
int idx = formats.size() - 1; int idx = formats.size() - 1;
unsigned int endIdx = unit.length + unit.start; unsigned int endIdx = unit.length + unit.start;
for (unsigned int i = unit.start; i < endIdx && i < (unsigned int)formatIndex.size(); ++i) { for (unsigned int i = unit.start; i < endIdx && i < (unsigned int)formatIndex.size(); ++i) {
formatIndex[i] = idx; formatIndex[i] = idx;
}
} }
} }
} }
@ -294,6 +305,8 @@ void HGMarkdownHighlighter::initImageRegionsFromResult()
void HGMarkdownHighlighter::initHeaderRegionsFromResult() void HGMarkdownHighlighter::initHeaderRegionsFromResult()
{ {
m_headerBlocks.clear();
if (!result) { if (!result) {
// From Qt5.7, the capacity is preserved. // From Qt5.7, the capacity is preserved.
m_headerRegions.clear(); m_headerRegions.clear();
@ -324,6 +337,11 @@ void HGMarkdownHighlighter::initHeaderRegionsFromResult()
m_headerRegions.push_back(VElementRegion(elem->pos, elem->end)); m_headerRegions.push_back(VElementRegion(elem->pos, elem->end));
} }
QTextBlock block = document->findBlock(elem->pos);
if (block.isValid()) {
m_headerBlocks.insert(block.blockNumber(), i);
}
++idx; ++idx;
elem = elem->next; elem = elem->next;
} }
@ -747,7 +765,7 @@ bool HGMarkdownHighlighter::isValidHeader(unsigned long p_pos, unsigned long p_e
for (unsigned long i = p_pos; i < p_end; ++i) { for (unsigned long i = p_pos; i < p_end; ++i) {
QChar ch = document->characterAt(i); QChar ch = document->characterAt(i);
if (ch.isSpace()) { if (ch.isSpace()) {
return true; return nrNumberSign > 0;
} else if (ch == QChar('#')) { } else if (ch == QChar('#')) {
if (++nrNumberSign > 6) { if (++nrNumberSign > 6) {
return false; return false;
@ -759,3 +777,41 @@ bool HGMarkdownHighlighter::isValidHeader(unsigned long p_pos, unsigned long p_e
return false; return false;
} }
bool HGMarkdownHighlighter::isValidHeader(const QString &p_text)
{
// There must exist spaces after #s.
// No more than 6 #s.
int nrNumberSign = 0;
for (int i = 0; i < p_text.size(); ++i) {
QChar ch = p_text[i];
if (ch.isSpace()) {
return nrNumberSign > 0;
} else if (ch == QChar('#')) {
if (++nrNumberSign > 6) {
return false;
}
} else {
return false;
}
}
return false;
}
void HGMarkdownHighlighter::highlightHeaderFast(int p_blockNumber, const QString &p_text)
{
if (currentBlockState() != HighlightBlockState::Normal) {
return;
}
auto it = m_headerBlocks.find(p_blockNumber);
if (it != m_headerBlocks.end()) {
if (isValidHeader(p_text)) {
setFormat(0, p_text.size(), m_headerStyles[it.value()]);
} else {
// Set an empty format to clear formats. It seems to work.
setFormat(0, p_text.size(), QTextCharFormat());
}
}
}

View File

@ -178,6 +178,9 @@ private:
QVector<QVector<HLUnit> > blockHighlights; QVector<QVector<HLUnit> > blockHighlights;
// Used for cache, [0, 6].
QVector<QTextCharFormat> m_headerStyles;
// Use another member to store the codeblocks highlights, because the highlight // Use another member to store the codeblocks highlights, because the highlight
// sequence is blockHighlights, regular-expression-based highlihgts, and then // sequence is blockHighlights, regular-expression-based highlihgts, and then
// codeBlockHighlights. // codeBlockHighlights.
@ -197,6 +200,9 @@ private:
// Sorted by start position. // Sorted by start position.
QVector<VElementRegion> m_headerRegions; QVector<VElementRegion> m_headerRegions;
// [block number] -> header level based on 0
QHash<int, int> m_headerBlocks;
// Timer to signal highlightCompleted(). // Timer to signal highlightCompleted().
QTimer *m_completeTimer; QTimer *m_completeTimer;
@ -265,9 +271,15 @@ private:
// Check if [p_pos, p_end) is a valid header. // Check if [p_pos, p_end) is a valid header.
bool isValidHeader(unsigned long p_pos, unsigned long p_end); bool isValidHeader(unsigned long p_pos, unsigned long p_end);
bool isValidHeader(const QString &p_text);
VTextBlockData *currentBlockData() const; VTextBlockData *currentBlockData() const;
VTextBlockData *previousBlockData() const; VTextBlockData *previousBlockData() const;
// Highlight headers using regular expression first instead of waiting for
// another parse.
void highlightHeaderFast(int p_blockNumber, const QString &p_text);
}; };
inline const QVector<VElementRegion> &HGMarkdownHighlighter::getHeaderRegions() const inline const QVector<VElementRegion> &HGMarkdownHighlighter::getHeaderRegions() const