diff --git a/src/hgmarkdownhighlighter.cpp b/src/hgmarkdownhighlighter.cpp index 2a8972e4..98a85920 100644 --- a/src/hgmarkdownhighlighter.cpp +++ b/src/hgmarkdownhighlighter.cpp @@ -446,7 +446,7 @@ bool HGMarkdownHighlighter::updateCodeBlocks() m_codeBlockHighlights[i].clear(); } - QList codeBlocks; + QVector codeBlocks; VCodeBlock item; bool inBlock = false; @@ -509,7 +509,7 @@ static bool HLUnitStyleComp(const HLUnitStyle &a, const HLUnitStyle &b) } } -void HGMarkdownHighlighter::setCodeBlockHighlights(const QList &p_units) +void HGMarkdownHighlighter::setCodeBlockHighlights(const QVector &p_units) { if (p_units.isEmpty()) { goto exit; diff --git a/src/hgmarkdownhighlighter.h b/src/hgmarkdownhighlighter.h index 98314c9f..0d91faa7 100644 --- a/src/hgmarkdownhighlighter.h +++ b/src/hgmarkdownhighlighter.h @@ -5,7 +5,6 @@ #include #include #include -#include #include #include @@ -120,13 +119,13 @@ public: QTextDocument *parent = 0); ~HGMarkdownHighlighter(); // Request to update highlihgt (re-parse and re-highlight) - void setCodeBlockHighlights(const QList &p_units); + void setCodeBlockHighlights(const QVector &p_units); signals: void highlightCompleted(); - // QList is implicitly shared. - void codeBlocksUpdated(const QList &p_codeBlocks); + // QVector is implicitly shared. + void codeBlocksUpdated(const QVector &p_codeBlocks); // Emitted when image regions have been fetched from a new parsing result. void imageLinksUpdated(const QVector &p_imageRegions); diff --git a/src/vcodeblockhighlighthelper.cpp b/src/vcodeblockhighlighthelper.cpp index 130448a0..bff1cbae 100644 --- a/src/vcodeblockhighlighthelper.cpp +++ b/src/vcodeblockhighlighthelper.cpp @@ -15,6 +15,8 @@ VCodeBlockHighlightHelper::VCodeBlockHighlightHelper(HGMarkdownHighlighter *p_hi this, &VCodeBlockHighlightHelper::handleCodeBlocksUpdated); connect(m_vdocument, &VDocument::textHighlighted, this, &VCodeBlockHighlightHelper::handleTextHighlightResult); + + // Web side is ready for code block highlight. connect(m_vdocument, &VDocument::readyToHighlightText, m_highlighter, &HGMarkdownHighlighter::updateHighlight); } @@ -52,13 +54,22 @@ QString VCodeBlockHighlightHelper::unindentCodeBlock(const QString &p_text) return res; } -void VCodeBlockHighlightHelper::handleCodeBlocksUpdated(const QList &p_codeBlocks) +void VCodeBlockHighlightHelper::handleCodeBlocksUpdated(const QVector &p_codeBlocks) { int curStamp = m_timeStamp.fetchAndAddRelaxed(1) + 1; m_codeBlocks = p_codeBlocks; for (int i = 0; i < m_codeBlocks.size(); ++i) { - QString unindentedText = unindentCodeBlock(m_codeBlocks[i].m_text); - m_vdocument->highlightTextAsync(unindentedText, i, curStamp); + const VCodeBlock &block = m_codeBlocks[i]; + auto it = m_cache.find(block.m_text); + if (it != m_cache.end()) { + // Hit cache. + qDebug() << "code block highlight hit cache" << curStamp << i; + it.value().m_timeStamp = curStamp; + updateHighlightResults(block.m_startPos, it.value().m_units); + } else { + QString unindentedText = unindentCodeBlock(block.m_text); + m_vdocument->highlightTextAsync(unindentedText, i, curStamp); + } } } @@ -109,7 +120,7 @@ void VCodeBlockHighlightHelper::parseHighlightResult(int p_timeStamp, int startPos = block.m_startPos; QString text = block.m_text; - QList hlUnits; + QVector hlUnits; bool failed = true; @@ -153,7 +164,7 @@ void VCodeBlockHighlightHelper::parseHighlightResult(int p_timeStamp, failed = true; goto exit; } - if (!parseSpanElement(xml, startPos, text, textIndex, hlUnits)) { + if (!parseSpanElement(xml, text, textIndex, hlUnits)) { failed = true; goto exit; } @@ -185,15 +196,27 @@ exit: hlUnits.clear(); } + // Add it to cache. + addToHighlightCache(text, p_timeStamp, hlUnits); + + updateHighlightResults(startPos, hlUnits); +} + +void VCodeBlockHighlightHelper::updateHighlightResults(int p_startPos, + QVector p_units) +{ + for (int i = 0; i < p_units.size(); ++i) { + p_units[i].m_position += p_startPos; + } + // We need to call this function anyway to trigger the rehighlight. - m_highlighter->setCodeBlockHighlights(hlUnits); + m_highlighter->setCodeBlockHighlights(p_units); } bool VCodeBlockHighlightHelper::parseSpanElement(QXmlStreamReader &p_xml, - int p_startPos, const QString &p_text, int &p_index, - QList &p_units) + QVector &p_units) { int unitStart = p_index; QString style = p_xml.attributes().value("class").toString(); @@ -215,7 +238,7 @@ bool VCodeBlockHighlightHelper::parseSpanElement(QXmlStreamReader &p_xml, } // Sub-span. - if (!parseSpanElement(p_xml, p_startPos, p_text, p_index, p_units)) { + if (!parseSpanElement(p_xml, p_text, p_index, p_units)) { return false; } } else if (p_xml.isEndElement()) { @@ -223,8 +246,8 @@ bool VCodeBlockHighlightHelper::parseSpanElement(QXmlStreamReader &p_xml, return false; } - // Got a complete span. - HLUnitPos unit(unitStart + p_startPos, p_index - unitStart, style); + // Got a complete span. Use relative position here. + HLUnitPos unit(unitStart, p_index - unitStart, style); p_units.append(unit); return true; } else { @@ -233,3 +256,24 @@ bool VCodeBlockHighlightHelper::parseSpanElement(QXmlStreamReader &p_xml, } return false; } + +void VCodeBlockHighlightHelper::addToHighlightCache(const QString &p_text, + int p_timeStamp, + const QVector &p_units) +{ + const int c_maxEntries = 100; + const int c_maxTimeStampSpan = 3; + if (m_cache.size() >= c_maxEntries) { + // Remove the oldest one. + int ts = p_timeStamp - c_maxTimeStampSpan; + for (auto it = m_cache.begin(); it != m_cache.end();) { + if (it.value().m_timeStamp < ts) { + it = m_cache.erase(it); + } else { + ++it; + } + } + } + + m_cache.insert(p_text, HLResult(p_timeStamp, p_units)); +} diff --git a/src/vcodeblockhighlighthelper.h b/src/vcodeblockhighlighthelper.h index 1ff28376..0f194c56 100644 --- a/src/vcodeblockhighlighthelper.h +++ b/src/vcodeblockhighlighthelper.h @@ -2,9 +2,10 @@ #define VCODEBLOCKHIGHLIGHTHELPER_H #include -#include +#include #include #include +#include #include "vconfigmanager.h" class VDocument; @@ -16,22 +17,36 @@ public: VCodeBlockHighlightHelper(HGMarkdownHighlighter *p_highlighter, VDocument *p_vdoc, MarkdownConverterType p_type); -signals: - private slots: - void handleCodeBlocksUpdated(const QList &p_codeBlocks); + void handleCodeBlocksUpdated(const QVector &p_codeBlocks); + void handleTextHighlightResult(const QString &p_html, int p_id, int p_timeStamp); private: + struct HLResult + { + HLResult() : m_timeStamp(-1) + { + } + + HLResult(int p_timeStamp, const QVector &p_units) + : m_timeStamp(p_timeStamp), m_units(p_units) + { + } + + int m_timeStamp; + QVector m_units; + }; + void parseHighlightResult(int p_timeStamp, int p_idx, const QString &p_html); - // @p_startPos: the global position of the start of the code block; // @p_text: the raw text of the code block; // @p_index: the start index of the span element within @p_text; // @p_units: all the highlight units of this code block; - bool parseSpanElement(QXmlStreamReader &p_xml, int p_startPos, + bool parseSpanElement(QXmlStreamReader &p_xml, const QString &p_text, int &p_index, - QList &p_units); + QVector &p_units); + // @p_text: text of fenced code block. // Get the indent level of the first line (fence) and unindent the whole block // to make the fence at the highest indent level. @@ -39,11 +54,21 @@ private: // without any context. QString unindentCodeBlock(const QString &p_text); + void updateHighlightResults(int p_startPos, QVector p_units); + + void addToHighlightCache(const QString &p_text, + int p_timeStamp, + const QVector &p_units); + HGMarkdownHighlighter *m_highlighter; VDocument *m_vdocument; MarkdownConverterType m_type; QAtomicInteger m_timeStamp; - QList m_codeBlocks; + QVector m_codeBlocks; + + // Cache for highlight result, using the code block text as key. + // The HLResult has relative position only. + QHash m_cache; }; #endif // VCODEBLOCKHIGHLIGHTHELPER_H