From b1f7760ea24a8e45dafbe1f9e7107fd812df23d7 Mon Sep 17 00:00:00 2001 From: Le Tan Date: Tue, 16 Jan 2018 19:46:03 +0800 Subject: [PATCH] HGMarkdownHighlighter: use cached header info to highlight headers fast --- src/hgmarkdownhighlighter.cpp | 108 ++++++++++++++++++++++++++-------- src/hgmarkdownhighlighter.h | 12 ++++ 2 files changed, 94 insertions(+), 26 deletions(-) diff --git a/src/hgmarkdownhighlighter.cpp b/src/hgmarkdownhighlighter.cpp index 1ee561ab..2c590b1c 100644 --- a/src/hgmarkdownhighlighter.cpp +++ b/src/hgmarkdownhighlighter.cpp @@ -58,6 +58,13 @@ HGMarkdownHighlighter::HGMarkdownHighlighter(const QVector &s m_colorColumnFormat.setForeground(QColor(g_config->getEditorColorColumnFg())); 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); document = parent; @@ -145,35 +152,39 @@ void HGMarkdownHighlighter::highlightBlock(const QString &text) // fix this. // highlightLinkWithSpacesInURL(text); + highlightHeaderFast(blockNum, text); + // Highlight CodeBlock using VCodeBlockHighlightHelper. if (m_codeBlockHighlights.size() > blockNum) { const QVector &units = m_codeBlockHighlights[blockNum]; - // Manually simply merge the format of all the units within the same block. - // Using QTextCursor to get the char format after setFormat() seems - // not to work. - QVector formats; - formats.reserve(units.size()); - // formatIndex[i] is the index in @formats which is the format of the - // ith character. - QVector formatIndex(currentBlock().length(), -1); - for (int i = 0; i < units.size(); ++i) { - const HLUnitStyle &unit = units[i]; - auto it = m_codeBlockStyles.find(unit.style); - if (it != m_codeBlockStyles.end()) { - QTextCharFormat newFormat; - if (unit.start < (unsigned int)formatIndex.size() && formatIndex[unit.start] != -1) { - newFormat = formats[formatIndex[unit.start]]; - newFormat.merge(*it); - } else { - newFormat = *it; - } - setFormat(unit.start, unit.length, newFormat); + if (!units.isEmpty()) { + // Manually simply merge the format of all the units within the same block. + // Using QTextCursor to get the char format after setFormat() seems + // not to work. + QVector formats; + formats.reserve(units.size()); + // formatIndex[i] is the index in @formats which is the format of the + // ith character. + QVector formatIndex(currentBlock().length(), -1); + for (int i = 0; i < units.size(); ++i) { + const HLUnitStyle &unit = units[i]; + auto it = m_codeBlockStyles.find(unit.style); + if (it != m_codeBlockStyles.end()) { + QTextCharFormat newFormat; + if (unit.start < (unsigned int)formatIndex.size() && formatIndex[unit.start] != -1) { + newFormat = formats[formatIndex[unit.start]]; + newFormat.merge(*it); + } else { + newFormat = *it; + } + setFormat(unit.start, unit.length, newFormat); - formats.append(newFormat); - int idx = formats.size() - 1; - unsigned int endIdx = unit.length + unit.start; - for (unsigned int i = unit.start; i < endIdx && i < (unsigned int)formatIndex.size(); ++i) { - formatIndex[i] = idx; + formats.append(newFormat); + int idx = formats.size() - 1; + unsigned int endIdx = unit.length + unit.start; + for (unsigned int i = unit.start; i < endIdx && i < (unsigned int)formatIndex.size(); ++i) { + formatIndex[i] = idx; + } } } } @@ -294,6 +305,8 @@ void HGMarkdownHighlighter::initImageRegionsFromResult() void HGMarkdownHighlighter::initHeaderRegionsFromResult() { + m_headerBlocks.clear(); + if (!result) { // From Qt5.7, the capacity is preserved. m_headerRegions.clear(); @@ -324,6 +337,11 @@ void HGMarkdownHighlighter::initHeaderRegionsFromResult() 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; 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) { QChar ch = document->characterAt(i); if (ch.isSpace()) { - return true; + return nrNumberSign > 0; } else if (ch == QChar('#')) { if (++nrNumberSign > 6) { return false; @@ -759,3 +777,41 @@ bool HGMarkdownHighlighter::isValidHeader(unsigned long p_pos, unsigned long p_e 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()); + } + } +} diff --git a/src/hgmarkdownhighlighter.h b/src/hgmarkdownhighlighter.h index e0f17741..92611764 100644 --- a/src/hgmarkdownhighlighter.h +++ b/src/hgmarkdownhighlighter.h @@ -178,6 +178,9 @@ private: QVector > blockHighlights; + // Used for cache, [0, 6]. + QVector m_headerStyles; + // Use another member to store the codeblocks highlights, because the highlight // sequence is blockHighlights, regular-expression-based highlihgts, and then // codeBlockHighlights. @@ -197,6 +200,9 @@ private: // Sorted by start position. QVector m_headerRegions; + // [block number] -> header level based on 0 + QHash m_headerBlocks; + // Timer to signal highlightCompleted(). QTimer *m_completeTimer; @@ -265,9 +271,15 @@ private: // Check if [p_pos, p_end) is a valid header. bool isValidHeader(unsigned long p_pos, unsigned long p_end); + bool isValidHeader(const QString &p_text); + VTextBlockData *currentBlockData() 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 &HGMarkdownHighlighter::getHeaderRegions() const