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)
{
int blockNum = currentBlock().blockNumber();
if (m_blockHLResultReady && blockHighlights.size() > blockNum) {
const QVector<HLUnit> &units = blockHighlights[blockNum];
if (m_blockHLResultReady && m_blockHighlights.size() > 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) {
// TODO: merge two format within the same range
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) {
const QVector<HLUnitStyle> &units = m_codeBlockHighlights[blockNum];
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<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);
QVector<QTextCharFormat *> formats(units.size(), NULL);
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;
if (it == m_codeBlockStyles.end()) {
continue;
}
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[i] = &(*it);
if (i == 0) {
// No need to merge format.
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();
}
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)
{
blockHighlights.resize(nrBlocks);
for (int i = 0; i < blockHighlights.size(); ++i) {
blockHighlights[i].clear();
m_blockHighlights.resize(nrBlocks);
for (int i = 0; i < m_blockHighlights.size(); ++i) {
m_blockHighlights[i].clear();
}
if (!result) {
@ -235,6 +273,13 @@ void HGMarkdownHighlighter::initBlockHighlightFromResult(int nrBlocks)
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()
@ -339,7 +384,8 @@ void HGMarkdownHighlighter::initHeaderRegionsFromResult()
QTextBlock block = document->findBlock(elem->pos);
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;
@ -390,7 +436,7 @@ void HGMarkdownHighlighter::initBlockHighlihgtOne(unsigned long pos,
}
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) {
return true;
} else if (a.start == b.start) {
return a.length > b.length;
return a.length >= b.length;
} else {
return false;
}
@ -720,7 +766,10 @@ void HGMarkdownHighlighter::setCodeBlockHighlights(const QVector<HLUnitPos> &p_u
for (int i = 0; i < highlights.size(); ++i) {
QVector<HLUnitStyle> &units = highlights[i];
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);
}
}
@ -807,11 +856,12 @@ void HGMarkdownHighlighter::highlightHeaderFast(int p_blockNumber, const QString
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 {
const HeaderBlockInfo &info = it.value();
if (!isValidHeader(p_text)) {
// Set an empty format to clear formats. It seems to work.
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);
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 codeBlockEndExp;
QTextCharFormat codeBlockFormat;
@ -176,7 +190,7 @@ private:
QHash<QString, QTextCharFormat> m_codeBlockStyles;
QVector<QVector<HLUnit> > blockHighlights;
QVector<QVector<HLUnit> > m_blockHighlights;
// Used for cache, [0, 6].
QVector<QTextCharFormat> m_headerStyles;
@ -200,8 +214,8 @@ private:
// Sorted by start position.
QVector<VElementRegion> m_headerRegions;
// [block number] -> header level based on 0
QHash<int, int> m_headerBlocks;
// Indexed by block number.
QHash<int, HeaderBlockInfo> m_headerBlocks;
// Timer to signal highlightCompleted().
QTimer *m_completeTimer;