HGMarkdownHighlighter: merge formats when highlighting a block

This commit is contained in:
Le Tan 2018-01-17 20:13:28 +08:00
parent b1f7760ea2
commit 217eafd91b
2 changed files with 106 additions and 42 deletions

View File

@ -122,12 +122,35 @@ void HGMarkdownHighlighter::updateBlockUserData(int p_blockNum, const QString &p
void HGMarkdownHighlighter::highlightBlock(const QString &text) void HGMarkdownHighlighter::highlightBlock(const QString &text)
{ {
int blockNum = currentBlock().blockNumber(); int blockNum = currentBlock().blockNumber();
if (m_blockHLResultReady && blockHighlights.size() > blockNum) { if (m_blockHLResultReady && m_blockHighlights.size() > blockNum) {
const QVector<HLUnit> &units = blockHighlights[blockNum]; // units are sorted by start position and length.
const QVector<HLUnit> &units = m_blockHighlights[blockNum];
if (!units.isEmpty()) {
for (int i = 0; i < units.size(); ++i) { for (int i = 0; i < units.size(); ++i) {
// TODO: merge two format within the same range
const HLUnit &unit = units[i]; const HLUnit &unit = units[i];
setFormat(unit.start, unit.length, highlightingStyles[unit.styleIndex].format); if (i == 0) {
// No need to merge format.
setFormat(unit.start,
unit.length,
highlightingStyles[unit.styleIndex].format);
} else {
QTextCharFormat newFormat = highlightingStyles[unit.styleIndex].format;
for (int j = i - 1; j >= 0; --j) {
if (units[j].start + units[j].length <= unit.start) {
// It won't affect current unit.
continue;
} else {
// Merge the format.
QTextCharFormat tmpFormat(newFormat);
newFormat = highlightingStyles[units[j].styleIndex].format;
// tmpFormat takes precedence.
newFormat.merge(tmpFormat);
}
}
setFormat(unit.start, unit.length, newFormat);
}
}
} }
} }
@ -158,35 +181,39 @@ void HGMarkdownHighlighter::highlightBlock(const QString &text)
if (m_codeBlockHighlights.size() > blockNum) { if (m_codeBlockHighlights.size() > blockNum) {
const QVector<HLUnitStyle> &units = m_codeBlockHighlights[blockNum]; const QVector<HLUnitStyle> &units = m_codeBlockHighlights[blockNum];
if (!units.isEmpty()) { if (!units.isEmpty()) {
// Manually simply merge the format of all the units within the same block. QVector<QTextCharFormat *> formats(units.size(), NULL);
// Using QTextCursor to get the char format after setFormat() seems
// not to work.
QVector<QTextCharFormat> formats;
formats.reserve(units.size());
// formatIndex[i] is the index in @formats which is the format of the
// ith character.
QVector<int> formatIndex(currentBlock().length(), -1);
for (int i = 0; i < units.size(); ++i) { for (int i = 0; i < units.size(); ++i) {
const HLUnitStyle &unit = units[i]; const HLUnitStyle &unit = units[i];
auto it = m_codeBlockStyles.find(unit.style); auto it = m_codeBlockStyles.find(unit.style);
if (it != m_codeBlockStyles.end()) { if (it == m_codeBlockStyles.end()) {
QTextCharFormat newFormat; continue;
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); formats[i] = &(*it);
int idx = formats.size() - 1;
unsigned int endIdx = unit.length + unit.start; if (i == 0) {
for (unsigned int i = unit.start; i < endIdx && i < (unsigned int)formatIndex.size(); ++i) { // No need to merge format.
formatIndex[i] = idx; setFormat(unit.start, unit.length, *it);
} else {
QTextCharFormat newFormat = *it;
for (int j = i - 1; j >= 0; --j) {
if (units[j].start + units[j].length <= unit.start) {
// It won't affect current unit.
continue;
} else {
// Merge the format.
if (formats[j]) {
QTextCharFormat tmpFormat(newFormat);
newFormat = *(formats[j]);
// tmpFormat takes precedence.
newFormat.merge(tmpFormat);
} }
} }
} }
setFormat(unit.start, unit.length, newFormat);
}
}
} }
} }
@ -196,11 +223,22 @@ exit:
highlightChanged(); highlightChanged();
} }
static bool compHLUnit(const HLUnit &p_a, const HLUnit &p_b)
{
if (p_a.start < p_b.start) {
return true;
} else if (p_a.start == p_b.start) {
return p_a.length >= p_b.length;
} else {
return false;
}
}
void HGMarkdownHighlighter::initBlockHighlightFromResult(int nrBlocks) void HGMarkdownHighlighter::initBlockHighlightFromResult(int nrBlocks)
{ {
blockHighlights.resize(nrBlocks); m_blockHighlights.resize(nrBlocks);
for (int i = 0; i < blockHighlights.size(); ++i) { for (int i = 0; i < m_blockHighlights.size(); ++i) {
blockHighlights[i].clear(); m_blockHighlights[i].clear();
} }
if (!result) { if (!result) {
@ -235,6 +273,13 @@ void HGMarkdownHighlighter::initBlockHighlightFromResult(int nrBlocks)
elem_cursor = elem_cursor->next; elem_cursor = elem_cursor->next;
} }
} }
// Sort m_blockHighlights.
for (int i = 0; i < m_blockHighlights.size(); ++i) {
if (m_blockHighlights[i].size() > 1) {
std::sort(m_blockHighlights[i].begin(), m_blockHighlights[i].end(), compHLUnit);
}
}
} }
void HGMarkdownHighlighter::initHtmlCommentRegionsFromResult() void HGMarkdownHighlighter::initHtmlCommentRegionsFromResult()
@ -339,7 +384,8 @@ void HGMarkdownHighlighter::initHeaderRegionsFromResult()
QTextBlock block = document->findBlock(elem->pos); QTextBlock block = document->findBlock(elem->pos);
if (block.isValid()) { if (block.isValid()) {
m_headerBlocks.insert(block.blockNumber(), i); // Header element will contain the new line character.
m_headerBlocks.insert(block.blockNumber(), HeaderBlockInfo(i, elem->end - elem->pos - 1));
} }
++idx; ++idx;
@ -390,7 +436,7 @@ void HGMarkdownHighlighter::initBlockHighlihgtOne(unsigned long pos,
} }
unit.styleIndex = styleIndex; unit.styleIndex = styleIndex;
blockHighlights[i].append(unit); m_blockHighlights[i].append(unit);
} }
} }
@ -663,12 +709,12 @@ bool HGMarkdownHighlighter::updateCodeBlocks()
} }
} }
static bool HLUnitStyleComp(const HLUnitStyle &a, const HLUnitStyle &b) static bool compHLUnitStyle(const HLUnitStyle &a, const HLUnitStyle &b)
{ {
if (a.start < b.start) { if (a.start < b.start) {
return true; return true;
} else if (a.start == b.start) { } else if (a.start == b.start) {
return a.length > b.length; return a.length >= b.length;
} else { } else {
return false; return false;
} }
@ -720,7 +766,10 @@ void HGMarkdownHighlighter::setCodeBlockHighlights(const QVector<HLUnitPos> &p_u
for (int i = 0; i < highlights.size(); ++i) { for (int i = 0; i < highlights.size(); ++i) {
QVector<HLUnitStyle> &units = highlights[i]; QVector<HLUnitStyle> &units = highlights[i];
if (!units.isEmpty()) { if (!units.isEmpty()) {
std::sort(units.begin(), units.end(), HLUnitStyleComp); if (units.size() > 1) {
std::sort(units.begin(), units.end(), compHLUnitStyle);
}
m_codeBlockHighlights[i].append(units); m_codeBlockHighlights[i].append(units);
} }
} }
@ -807,11 +856,12 @@ void HGMarkdownHighlighter::highlightHeaderFast(int p_blockNumber, const QString
auto it = m_headerBlocks.find(p_blockNumber); auto it = m_headerBlocks.find(p_blockNumber);
if (it != m_headerBlocks.end()) { if (it != m_headerBlocks.end()) {
if (isValidHeader(p_text)) { const HeaderBlockInfo &info = it.value();
setFormat(0, p_text.size(), m_headerStyles[it.value()]); if (!isValidHeader(p_text)) {
} else {
// Set an empty format to clear formats. It seems to work. // Set an empty format to clear formats. It seems to work.
setFormat(0, p_text.size(), QTextCharFormat()); setFormat(0, p_text.size(), QTextCharFormat());
} else if (info.m_length < p_text.size()) {
setFormat(info.m_length, p_text.size() - info.m_length, m_headerStyles[info.m_level]);
} }
} }
} }

View File

@ -163,6 +163,20 @@ private slots:
void startParseAndHighlight(bool p_fast = false); void startParseAndHighlight(bool p_fast = false);
private: private:
struct HeaderBlockInfo
{
HeaderBlockInfo(int p_level = -1, int p_length = 0)
: m_level(p_level), m_length(p_length)
{
}
// Header level based on 0.
int m_level;
// Block length;
int m_length;
};
QRegExp codeBlockStartExp; QRegExp codeBlockStartExp;
QRegExp codeBlockEndExp; QRegExp codeBlockEndExp;
QTextCharFormat codeBlockFormat; QTextCharFormat codeBlockFormat;
@ -176,7 +190,7 @@ private:
QHash<QString, QTextCharFormat> m_codeBlockStyles; QHash<QString, QTextCharFormat> m_codeBlockStyles;
QVector<QVector<HLUnit> > blockHighlights; QVector<QVector<HLUnit> > m_blockHighlights;
// Used for cache, [0, 6]. // Used for cache, [0, 6].
QVector<QTextCharFormat> m_headerStyles; QVector<QTextCharFormat> m_headerStyles;
@ -200,8 +214,8 @@ private:
// Sorted by start position. // Sorted by start position.
QVector<VElementRegion> m_headerRegions; QVector<VElementRegion> m_headerRegions;
// [block number] -> header level based on 0 // Indexed by block number.
QHash<int, int> m_headerBlocks; QHash<int, HeaderBlockInfo> m_headerBlocks;
// Timer to signal highlightCompleted(). // Timer to signal highlightCompleted().
QTimer *m_completeTimer; QTimer *m_completeTimer;