mirror of
https://gitee.com/vnotex/vnote.git
synced 2025-07-05 13:59:52 +08:00
PegMarkdownHighlighter: multi-threads highlighter support
This commit is contained in:
parent
fbfc6c1dd6
commit
bb308a06d1
@ -29,6 +29,7 @@ HGMarkdownHighlighter::HGMarkdownHighlighter(const QVector<HighlightingStyle> &s
|
||||
int waitInterval,
|
||||
QTextDocument *parent)
|
||||
: QSyntaxHighlighter(parent),
|
||||
m_timeStamp(0),
|
||||
highlightingStyles(styles),
|
||||
m_codeBlockStyles(codeBlockStyles),
|
||||
m_numOfCodeBlockHighlightsToRecv(0),
|
||||
@ -864,9 +865,9 @@ void HGMarkdownHighlighter::handleContentChange(int /* position */, int charsRem
|
||||
return;
|
||||
}
|
||||
|
||||
m_signalOut = false;
|
||||
++m_timeStamp;
|
||||
|
||||
timer->stop();
|
||||
m_signalOut = false;
|
||||
timer->start();
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
#ifndef HGMARKDOWNHIGHLIGHTER_H
|
||||
#define HGMARKDOWNHIGHLIGHTER_H
|
||||
|
||||
#include <QTextCharFormat>
|
||||
#include <QSyntaxHighlighter>
|
||||
#include <QAtomicInt>
|
||||
#include <QMap>
|
||||
@ -9,177 +8,10 @@
|
||||
#include <QString>
|
||||
|
||||
#include "vtextblockdata.h"
|
||||
#include "vconstants.h"
|
||||
#include "markdownhighlighterdata.h"
|
||||
|
||||
extern "C" {
|
||||
#include <pmh_parser.h>
|
||||
}
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QTextDocument;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
struct HighlightingStyle
|
||||
{
|
||||
pmh_element_type type;
|
||||
QTextCharFormat format;
|
||||
};
|
||||
|
||||
// One continuous region for a certain markdown highlight style
|
||||
// within a QTextBlock.
|
||||
// Pay attention to the change of HighlightingStyles[]
|
||||
struct HLUnit
|
||||
{
|
||||
// Highlight offset @start and @length with style HighlightingStyles[styleIndex]
|
||||
// within a QTextBlock
|
||||
unsigned long start;
|
||||
unsigned long length;
|
||||
unsigned int styleIndex;
|
||||
};
|
||||
|
||||
struct HLUnitStyle
|
||||
{
|
||||
unsigned long start;
|
||||
unsigned long length;
|
||||
QString style;
|
||||
};
|
||||
|
||||
// Fenced code block only.
|
||||
struct VCodeBlock
|
||||
{
|
||||
// Global position of the start.
|
||||
int m_startPos;
|
||||
|
||||
int m_startBlock;
|
||||
int m_endBlock;
|
||||
|
||||
QString m_lang;
|
||||
|
||||
QString m_text;
|
||||
|
||||
bool equalContent(const VCodeBlock &p_block) const
|
||||
{
|
||||
return p_block.m_lang == m_lang && p_block.m_text == m_text;
|
||||
}
|
||||
|
||||
void updateNonContent(const VCodeBlock &p_block)
|
||||
{
|
||||
m_startPos = p_block.m_startPos;
|
||||
m_startBlock = p_block.m_startBlock;
|
||||
m_endBlock = p_block.m_endBlock;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
struct VMathjaxBlock
|
||||
{
|
||||
VMathjaxBlock()
|
||||
: m_blockNumber(-1),
|
||||
m_previewedAsBlock(false),
|
||||
m_index(-1),
|
||||
m_length(-1)
|
||||
{
|
||||
}
|
||||
|
||||
VMathjaxBlock(int p_blockNumber, const MathjaxInfo &p_info)
|
||||
: m_blockNumber(p_blockNumber),
|
||||
m_previewedAsBlock(p_info.m_previewedAsBlock),
|
||||
m_index(p_info.m_index),
|
||||
m_length(p_info.m_length),
|
||||
m_text(p_info.m_text)
|
||||
{
|
||||
}
|
||||
|
||||
bool equalContent(const VMathjaxBlock &p_block) const
|
||||
{
|
||||
return m_text == p_block.m_text;
|
||||
}
|
||||
|
||||
void updateNonContent(const VMathjaxBlock &p_block)
|
||||
{
|
||||
m_blockNumber = p_block.m_blockNumber;
|
||||
m_previewedAsBlock = p_block.m_previewedAsBlock;
|
||||
m_index = p_block.m_index;
|
||||
m_length = p_block.m_length;
|
||||
}
|
||||
|
||||
int m_blockNumber;
|
||||
|
||||
bool m_previewedAsBlock;
|
||||
|
||||
// Start index within the block.
|
||||
int m_index;
|
||||
|
||||
int m_length;
|
||||
|
||||
QString m_text;
|
||||
};
|
||||
|
||||
|
||||
// Highlight unit with global position and string style name.
|
||||
struct HLUnitPos
|
||||
{
|
||||
HLUnitPos() : m_position(-1), m_length(-1)
|
||||
{
|
||||
}
|
||||
|
||||
HLUnitPos(int p_position, int p_length, const QString &p_style)
|
||||
: m_position(p_position), m_length(p_length), m_style(p_style)
|
||||
{
|
||||
}
|
||||
|
||||
int m_position;
|
||||
int m_length;
|
||||
QString m_style;
|
||||
};
|
||||
|
||||
// Denote the region of a certain Markdown element.
|
||||
struct VElementRegion
|
||||
{
|
||||
VElementRegion() : m_startPos(0), m_endPos(0) {}
|
||||
|
||||
VElementRegion(int p_start, int p_end) : m_startPos(p_start), m_endPos(p_end) {}
|
||||
|
||||
// The start position of the region in document.
|
||||
int m_startPos;
|
||||
|
||||
// The end position of the region in document.
|
||||
int m_endPos;
|
||||
|
||||
// Whether this region contains @p_pos.
|
||||
bool contains(int p_pos) const
|
||||
{
|
||||
return m_startPos <= p_pos && m_endPos > p_pos;
|
||||
}
|
||||
|
||||
bool intersect(int p_start, int p_end) const
|
||||
{
|
||||
return !(p_end <= m_startPos || p_start >= m_endPos);
|
||||
}
|
||||
|
||||
bool operator==(const VElementRegion &p_other) const
|
||||
{
|
||||
return (m_startPos == p_other.m_startPos
|
||||
&& m_endPos == p_other.m_endPos);
|
||||
}
|
||||
|
||||
bool operator<(const VElementRegion &p_other) const
|
||||
{
|
||||
if (m_startPos < p_other.m_startPos) {
|
||||
return true;
|
||||
} else if (m_startPos == p_other.m_startPos) {
|
||||
// If a < b is true, then b < a must be false.
|
||||
return m_endPos < p_other.m_endPos;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
QString toString() const
|
||||
{
|
||||
return QString("[%1,%2)").arg(m_startPos).arg(m_endPos);
|
||||
}
|
||||
};
|
||||
|
||||
class HGMarkdownHighlighter : public QSyntaxHighlighter
|
||||
{
|
||||
@ -195,8 +27,6 @@ public:
|
||||
// Request to update highlihgt (re-parse and re-highlight)
|
||||
void setCodeBlockHighlights(const QVector<HLUnitPos> &p_units);
|
||||
|
||||
const QMap<int, bool> &getPotentialPreviewBlocks() const;
|
||||
|
||||
const QVector<VElementRegion> &getHeaderRegions() const;
|
||||
|
||||
const QSet<int> &getPossiblePreviewBlocks() const;
|
||||
@ -259,6 +89,8 @@ private:
|
||||
int m_length;
|
||||
};
|
||||
|
||||
TimeStamp m_timeStamp;
|
||||
|
||||
QRegExp codeBlockStartExp;
|
||||
QRegExp codeBlockEndExp;
|
||||
|
||||
|
@ -1,4 +1,182 @@
|
||||
#ifndef MARKDOWNHIGHLIGHTERDATA_H
|
||||
#define MARKDOWNHIGHLIGHTERDATA_H
|
||||
|
||||
#include <QTextCharFormat>
|
||||
|
||||
#include "vconstants.h"
|
||||
#include "vtextblockdata.h"
|
||||
|
||||
extern "C" {
|
||||
#include <pmh_parser.h>
|
||||
}
|
||||
|
||||
struct HighlightingStyle
|
||||
{
|
||||
pmh_element_type type;
|
||||
QTextCharFormat format;
|
||||
};
|
||||
|
||||
// One continuous region for a certain markdown highlight style
|
||||
// within a QTextBlock.
|
||||
// Pay attention to the change of HighlightingStyles[]
|
||||
struct HLUnit
|
||||
{
|
||||
// Highlight offset @start and @length with style HighlightingStyles[styleIndex]
|
||||
// within a QTextBlock
|
||||
unsigned long start;
|
||||
unsigned long length;
|
||||
unsigned int styleIndex;
|
||||
};
|
||||
|
||||
struct HLUnitStyle
|
||||
{
|
||||
unsigned long start;
|
||||
unsigned long length;
|
||||
QString style;
|
||||
};
|
||||
|
||||
// Fenced code block only.
|
||||
struct VCodeBlock
|
||||
{
|
||||
// Global position of the start.
|
||||
int m_startPos;
|
||||
|
||||
int m_startBlock;
|
||||
int m_endBlock;
|
||||
|
||||
QString m_lang;
|
||||
|
||||
QString m_text;
|
||||
|
||||
bool equalContent(const VCodeBlock &p_block) const
|
||||
{
|
||||
return p_block.m_lang == m_lang && p_block.m_text == m_text;
|
||||
}
|
||||
|
||||
void updateNonContent(const VCodeBlock &p_block)
|
||||
{
|
||||
m_startPos = p_block.m_startPos;
|
||||
m_startBlock = p_block.m_startBlock;
|
||||
m_endBlock = p_block.m_endBlock;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
struct VMathjaxBlock
|
||||
{
|
||||
VMathjaxBlock()
|
||||
: m_blockNumber(-1),
|
||||
m_previewedAsBlock(false),
|
||||
m_index(-1),
|
||||
m_length(-1)
|
||||
{
|
||||
}
|
||||
|
||||
VMathjaxBlock(int p_blockNumber, const MathjaxInfo &p_info)
|
||||
: m_blockNumber(p_blockNumber),
|
||||
m_previewedAsBlock(p_info.m_previewedAsBlock),
|
||||
m_index(p_info.m_index),
|
||||
m_length(p_info.m_length),
|
||||
m_text(p_info.m_text)
|
||||
{
|
||||
}
|
||||
|
||||
bool equalContent(const VMathjaxBlock &p_block) const
|
||||
{
|
||||
return m_text == p_block.m_text;
|
||||
}
|
||||
|
||||
void updateNonContent(const VMathjaxBlock &p_block)
|
||||
{
|
||||
m_blockNumber = p_block.m_blockNumber;
|
||||
m_previewedAsBlock = p_block.m_previewedAsBlock;
|
||||
m_index = p_block.m_index;
|
||||
m_length = p_block.m_length;
|
||||
}
|
||||
|
||||
int m_blockNumber;
|
||||
|
||||
bool m_previewedAsBlock;
|
||||
|
||||
// Start index within the block.
|
||||
int m_index;
|
||||
|
||||
int m_length;
|
||||
|
||||
QString m_text;
|
||||
};
|
||||
|
||||
|
||||
// Highlight unit with global position and string style name.
|
||||
struct HLUnitPos
|
||||
{
|
||||
HLUnitPos() : m_position(-1), m_length(-1)
|
||||
{
|
||||
}
|
||||
|
||||
HLUnitPos(int p_position, int p_length, const QString &p_style)
|
||||
: m_position(p_position), m_length(p_length), m_style(p_style)
|
||||
{
|
||||
}
|
||||
|
||||
int m_position;
|
||||
int m_length;
|
||||
QString m_style;
|
||||
};
|
||||
|
||||
// Denote the region of a certain Markdown element.
|
||||
struct VElementRegion
|
||||
{
|
||||
VElementRegion() : m_startPos(0), m_endPos(0) {}
|
||||
|
||||
VElementRegion(int p_start, int p_end) : m_startPos(p_start), m_endPos(p_end) {}
|
||||
|
||||
// The start position of the region in document.
|
||||
int m_startPos;
|
||||
|
||||
// The end position of the region in document.
|
||||
int m_endPos;
|
||||
|
||||
// Whether this region contains @p_pos.
|
||||
bool contains(int p_pos) const
|
||||
{
|
||||
return m_startPos <= p_pos && m_endPos > p_pos;
|
||||
}
|
||||
|
||||
bool intersect(int p_start, int p_end) const
|
||||
{
|
||||
return !(p_end <= m_startPos || p_start >= m_endPos);
|
||||
}
|
||||
|
||||
bool operator==(const VElementRegion &p_other) const
|
||||
{
|
||||
return (m_startPos == p_other.m_startPos
|
||||
&& m_endPos == p_other.m_endPos);
|
||||
}
|
||||
|
||||
bool operator<(const VElementRegion &p_other) const
|
||||
{
|
||||
if (m_startPos < p_other.m_startPos) {
|
||||
return true;
|
||||
} else if (m_startPos == p_other.m_startPos) {
|
||||
// If a < b is true, then b < a must be false.
|
||||
return m_endPos < p_other.m_endPos;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
QString toString() const
|
||||
{
|
||||
return QString("[%1,%2)").arg(m_startPos).arg(m_endPos);
|
||||
}
|
||||
};
|
||||
|
||||
struct PegHighlightResult
|
||||
{
|
||||
TimeStamp m_timeStamp;
|
||||
|
||||
QVector<QVector<HLUnit> > m_blockHighlights;
|
||||
};
|
||||
|
||||
#endif // MARKDOWNHIGHLIGHTERDATA_H
|
||||
|
223
src/peghighlighterresult.cpp
Normal file
223
src/peghighlighterresult.cpp
Normal file
@ -0,0 +1,223 @@
|
||||
#include "peghighlighterresult.h"
|
||||
|
||||
#include <QTextDocument>
|
||||
#include <QTextBlock>
|
||||
|
||||
#include "pegmarkdownhighlighter.h"
|
||||
#include "utils/vutils.h"
|
||||
|
||||
PegHighlighterResult::PegHighlighterResult()
|
||||
: m_timeStamp(0),
|
||||
m_numOfBlocks(0),
|
||||
m_numOfCodeBlockHighlightsToRecv(0)
|
||||
{
|
||||
m_codeBlockStartExp = QRegExp(VUtils::c_fencedCodeBlockStartRegExp);
|
||||
m_codeBlockEndExp = QRegExp(VUtils::c_fencedCodeBlockEndRegExp);
|
||||
}
|
||||
|
||||
PegHighlighterResult::PegHighlighterResult(const PegMarkdownHighlighter *p_peg,
|
||||
const QSharedPointer<PegParseResult> &p_result)
|
||||
: m_timeStamp(p_result->m_timeStamp),
|
||||
m_numOfBlocks(p_result->m_numOfBlocks),
|
||||
m_numOfCodeBlockHighlightsToRecv(0)
|
||||
{
|
||||
m_codeBlockStartExp = QRegExp(VUtils::c_fencedCodeBlockStartRegExp);
|
||||
m_codeBlockEndExp = QRegExp(VUtils::c_fencedCodeBlockEndRegExp);
|
||||
|
||||
parseBlocksHighlights(p_peg, p_result);
|
||||
|
||||
// Implicit sharing.
|
||||
m_imageRegions = p_result->m_imageRegions;
|
||||
m_headerRegions = p_result->m_headerRegions;
|
||||
|
||||
parseFencedCodeBlocks(p_peg, p_result);
|
||||
}
|
||||
|
||||
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 PegHighlighterResult::parseBlocksHighlights(const PegMarkdownHighlighter *p_peg,
|
||||
const QSharedPointer<PegParseResult> &p_result)
|
||||
{
|
||||
m_blocksHighlights.resize(m_numOfBlocks);
|
||||
if (p_result->isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QTextDocument *doc = p_peg->getDocument();
|
||||
const QVector<HighlightingStyle> &styles = p_peg->getStyles();
|
||||
auto pmhResult = p_result->m_pmhElements;
|
||||
for (int i = 0; i < styles.size(); i++)
|
||||
{
|
||||
const HighlightingStyle &style = styles[i];
|
||||
pmh_element *elem_cursor = pmhResult[style.type];
|
||||
while (elem_cursor != NULL)
|
||||
{
|
||||
// elem_cursor->pos and elem_cursor->end is the start
|
||||
// and end position of the element in document.
|
||||
if (elem_cursor->end <= elem_cursor->pos) {
|
||||
elem_cursor = elem_cursor->next;
|
||||
continue;
|
||||
}
|
||||
|
||||
parseBlocksHighlightOne(doc, elem_cursor->pos, elem_cursor->end, i);
|
||||
elem_cursor = elem_cursor->next;
|
||||
}
|
||||
}
|
||||
|
||||
// Sort m_blocksHighlights.
|
||||
for (int i = 0; i < m_blocksHighlights.size(); ++i) {
|
||||
if (m_blocksHighlights[i].size() > 1) {
|
||||
std::sort(m_blocksHighlights[i].begin(), m_blocksHighlights[i].end(), compHLUnit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PegHighlighterResult::parseBlocksHighlightOne(const QTextDocument *p_doc,
|
||||
unsigned long p_pos,
|
||||
unsigned long p_end,
|
||||
int p_styleIndex)
|
||||
{
|
||||
// When the the highlight element is at the end of document, @p_end will equals
|
||||
// to the characterCount.
|
||||
unsigned int nrChar = (unsigned int)p_doc->characterCount();
|
||||
if (p_end >= nrChar && nrChar > 0) {
|
||||
p_end = nrChar - 1;
|
||||
}
|
||||
|
||||
QTextBlock block = p_doc->findBlock(p_pos);
|
||||
int startBlockNum = block.blockNumber();
|
||||
int endBlockNum = p_doc->findBlock(p_end).blockNumber();
|
||||
while (block.isValid())
|
||||
{
|
||||
int blockNum = block.blockNumber();
|
||||
if (blockNum > endBlockNum) {
|
||||
break;
|
||||
}
|
||||
|
||||
int blockStartPos = block.position();
|
||||
HLUnit unit;
|
||||
if (blockNum == startBlockNum) {
|
||||
unit.start = p_pos - blockStartPos;
|
||||
unit.length = (startBlockNum == endBlockNum) ?
|
||||
(p_end - p_pos) : (block.length() - unit.start);
|
||||
} else if (blockNum == endBlockNum) {
|
||||
unit.start = 0;
|
||||
unit.length = p_end - blockStartPos;
|
||||
} else {
|
||||
unit.start = 0;
|
||||
unit.length = block.length();
|
||||
}
|
||||
unit.styleIndex = p_styleIndex;
|
||||
|
||||
m_blocksHighlights[blockNum].append(unit);
|
||||
|
||||
block = block.next();
|
||||
}
|
||||
}
|
||||
|
||||
void PegHighlighterResult::parseBlocksElementRegionOne(QHash<int, QVector<VElementRegion>> &p_regs,
|
||||
const QTextDocument *p_doc,
|
||||
unsigned long p_pos,
|
||||
unsigned long p_end)
|
||||
{
|
||||
// When the the highlight element is at the end of document, @p_end will equals
|
||||
// to the characterCount.
|
||||
unsigned int nrChar = (unsigned int)p_doc->characterCount();
|
||||
if (p_end >= nrChar && nrChar > 0) {
|
||||
p_end = nrChar - 1;
|
||||
}
|
||||
|
||||
QTextBlock block = p_doc->findBlock(p_pos);
|
||||
int startBlockNum = block.blockNumber();
|
||||
int endBlockNum = p_doc->findBlock(p_end).blockNumber();
|
||||
while (block.isValid())
|
||||
{
|
||||
int blockNum = block.blockNumber();
|
||||
if (blockNum > endBlockNum) {
|
||||
break;
|
||||
}
|
||||
|
||||
int blockStartPos = block.position();
|
||||
QVector<VElementRegion> ®s = p_regs[blockNum];
|
||||
int start, end;
|
||||
if (blockNum == startBlockNum) {
|
||||
start = p_pos - blockStartPos;
|
||||
end = (startBlockNum == endBlockNum) ? (p_end - blockStartPos)
|
||||
: block.length();
|
||||
} else if (blockNum == endBlockNum) {
|
||||
start = 0;
|
||||
end = p_end - blockStartPos;
|
||||
} else {
|
||||
start = 0;
|
||||
end = block.length();
|
||||
}
|
||||
|
||||
regs.append(VElementRegion(start, end));
|
||||
}
|
||||
}
|
||||
|
||||
void PegHighlighterResult::parseFencedCodeBlocks(const PegMarkdownHighlighter *p_peg,
|
||||
const QSharedPointer<PegParseResult> &p_result)
|
||||
{
|
||||
const QMap<int, VElementRegion> ®s = p_result->m_codeBlockRegions;
|
||||
|
||||
const QTextDocument *doc = p_peg->getDocument();
|
||||
VCodeBlock item;
|
||||
bool inBlock = false;
|
||||
for (auto it = regs.begin(); it != regs.end(); ++it) {
|
||||
QTextBlock block = doc->findBlock(it.value().m_startPos);
|
||||
int lastBlock = doc->findBlock(it.value().m_endPos - 1).blockNumber();
|
||||
|
||||
while (block.isValid()) {
|
||||
int blockNumber = block.blockNumber();
|
||||
if (blockNumber > lastBlock) {
|
||||
break;
|
||||
}
|
||||
|
||||
HighlightBlockState state = HighlightBlockState::Normal;
|
||||
QString text = block.text();
|
||||
if (inBlock) {
|
||||
item.m_text = item.m_text + "\n" + text;
|
||||
int idx = m_codeBlockEndExp.indexIn(text);
|
||||
if (idx >= 0) {
|
||||
// End block.
|
||||
inBlock = false;
|
||||
state = HighlightBlockState::CodeBlockEnd;
|
||||
item.m_endBlock = blockNumber;
|
||||
m_codeBlocks.append(item);
|
||||
} else {
|
||||
// Within code block.
|
||||
state = HighlightBlockState::CodeBlock;
|
||||
}
|
||||
} else {
|
||||
int idx = m_codeBlockStartExp.indexIn(text);
|
||||
if (idx >= 0) {
|
||||
// Start block.
|
||||
inBlock = true;
|
||||
state = HighlightBlockState::CodeBlockStart;
|
||||
item.m_startBlock = blockNumber;
|
||||
item.m_startPos = block.position();
|
||||
item.m_text = text;
|
||||
if (m_codeBlockStartExp.captureCount() == 2) {
|
||||
item.m_lang = m_codeBlockStartExp.capturedTexts()[2];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (state != HighlightBlockState::Normal) {
|
||||
m_codeBlocksState.insert(blockNumber, state);
|
||||
}
|
||||
|
||||
block = block.next();
|
||||
}
|
||||
}
|
||||
}
|
75
src/peghighlighterresult.h
Normal file
75
src/peghighlighterresult.h
Normal file
@ -0,0 +1,75 @@
|
||||
#ifndef PEGHIGHLIGHTERRESULT_H
|
||||
#define PEGHIGHLIGHTERRESULT_H
|
||||
|
||||
#include "vconstants.h"
|
||||
#include "pegparser.h"
|
||||
|
||||
class PegMarkdownHighlighter;
|
||||
class QTextDocument;
|
||||
|
||||
class PegHighlighterResult
|
||||
{
|
||||
public:
|
||||
PegHighlighterResult();
|
||||
|
||||
PegHighlighterResult(const PegMarkdownHighlighter *p_peg,
|
||||
const QSharedPointer<PegParseResult> &p_result);
|
||||
|
||||
bool matched(TimeStamp p_timeStamp) const;
|
||||
|
||||
TimeStamp m_timeStamp;
|
||||
|
||||
int m_numOfBlocks;
|
||||
|
||||
QVector<QVector<HLUnit>> m_blocksHighlights;
|
||||
|
||||
// Use another member to store the codeblocks highlights, because the highlight
|
||||
// sequence is blockHighlights, regular-expression-based highlihgts, and then
|
||||
// codeBlockHighlights.
|
||||
// Support fenced code block only.
|
||||
QVector<QVector<HLUnitStyle> > m_codeBlocksHighlights;
|
||||
|
||||
// All image link regions.
|
||||
QVector<VElementRegion> m_imageRegions;
|
||||
|
||||
// All header regions.
|
||||
// Sorted by start position.
|
||||
QVector<VElementRegion> m_headerRegions;
|
||||
|
||||
// All fenced code blocks.
|
||||
QVector<VCodeBlock> m_codeBlocks;
|
||||
|
||||
// Indexed by block number.
|
||||
QHash<int, HighlightBlockState> m_codeBlocksState;
|
||||
|
||||
int m_numOfCodeBlockHighlightsToRecv;
|
||||
|
||||
private:
|
||||
// Parse highlight elements for all the blocks from parse results.
|
||||
void parseBlocksHighlights(const PegMarkdownHighlighter *p_peg,
|
||||
const QSharedPointer<PegParseResult> &p_result);
|
||||
|
||||
// Parse highlight elements for blocks from one parse result.
|
||||
void parseBlocksHighlightOne(const QTextDocument *p_doc,
|
||||
unsigned long p_pos,
|
||||
unsigned long p_end,
|
||||
int p_styleIndex);
|
||||
|
||||
// Parse fenced code blocks from parse results.
|
||||
void parseFencedCodeBlocks(const PegMarkdownHighlighter *p_peg,
|
||||
const QSharedPointer<PegParseResult> &p_result);
|
||||
|
||||
void parseBlocksElementRegionOne(QHash<int, QVector<VElementRegion>> &p_regs,
|
||||
const QTextDocument *p_doc,
|
||||
unsigned long p_pos,
|
||||
unsigned long p_end);
|
||||
|
||||
QRegExp m_codeBlockStartExp;
|
||||
QRegExp m_codeBlockEndExp;
|
||||
};
|
||||
|
||||
inline bool PegHighlighterResult::matched(TimeStamp p_timeStamp) const
|
||||
{
|
||||
return m_timeStamp == p_timeStamp;
|
||||
}
|
||||
#endif // PEGHIGHLIGHTERRESULT_H
|
409
src/pegmarkdownhighlighter.cpp
Normal file
409
src/pegmarkdownhighlighter.cpp
Normal file
@ -0,0 +1,409 @@
|
||||
#include "pegmarkdownhighlighter.h"
|
||||
|
||||
#include <QTextDocument>
|
||||
#include <QTimer>
|
||||
#include <QDebug>
|
||||
|
||||
#include "pegparser.h"
|
||||
#include "vconfigmanager.h"
|
||||
#include "utils/vutils.h"
|
||||
|
||||
extern VConfigManager *g_config;
|
||||
|
||||
PegMarkdownHighlighter::PegMarkdownHighlighter(QTextDocument *p_doc)
|
||||
: QSyntaxHighlighter(p_doc),
|
||||
m_doc(p_doc),
|
||||
m_timeStamp(0),
|
||||
m_parser(NULL),
|
||||
m_parserExts(pmh_EXT_STRIKE | pmh_EXT_FRONTMATTER)
|
||||
{
|
||||
}
|
||||
|
||||
void PegMarkdownHighlighter::init(const QVector<HighlightingStyle> &p_styles,
|
||||
const QHash<QString, QTextCharFormat> &p_codeBlockStyles,
|
||||
bool p_mathjaxEnabled,
|
||||
int p_timerInterval)
|
||||
{
|
||||
m_styles = p_styles;
|
||||
m_codeBlockStyles = p_codeBlockStyles;
|
||||
|
||||
m_codeBlockFormat.setForeground(QBrush(Qt::darkYellow));
|
||||
for (int index = 0; index < m_styles.size(); ++index) {
|
||||
switch (m_styles[index].type) {
|
||||
case pmh_FENCEDCODEBLOCK:
|
||||
m_codeBlockFormat = m_styles[index].format;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
m_colorColumnFormat = m_codeBlockFormat;
|
||||
m_colorColumnFormat.setForeground(QColor(g_config->getEditorColorColumnFg()));
|
||||
m_colorColumnFormat.setBackground(QColor(g_config->getEditorColorColumnBg()));
|
||||
|
||||
m_result.reset(new PegHighlighterResult());
|
||||
|
||||
m_parser = new PegParser(this);
|
||||
connect(m_parser, &PegParser::parseResultReady,
|
||||
this, &PegMarkdownHighlighter::handleParseResult);
|
||||
|
||||
m_timer = new QTimer(this);
|
||||
m_timer->setSingleShot(true);
|
||||
m_timer->setInterval(p_timerInterval);
|
||||
connect(m_timer, &QTimer::timeout,
|
||||
this, [this]() {
|
||||
startParse();
|
||||
});
|
||||
|
||||
connect(m_doc, &QTextDocument::contentsChange,
|
||||
this, &PegMarkdownHighlighter::handleContentsChange);
|
||||
}
|
||||
|
||||
void PegMarkdownHighlighter::highlightBlock(const QString &p_text)
|
||||
{
|
||||
QSharedPointer<PegHighlighterResult> result(m_result);
|
||||
|
||||
int blockNum = currentBlock().blockNumber();
|
||||
if (result->m_blocksHighlights.size() > blockNum) {
|
||||
// units are sorted by start position and length.
|
||||
const QVector<HLUnit> &units = result->m_blocksHighlights[blockNum];
|
||||
if (!units.isEmpty()) {
|
||||
for (int i = 0; i < units.size(); ++i) {
|
||||
const HLUnit &unit = units[i];
|
||||
if (i == 0) {
|
||||
// No need to merge format.
|
||||
setFormat(unit.start,
|
||||
unit.length,
|
||||
m_styles[unit.styleIndex].format);
|
||||
} else {
|
||||
QTextCharFormat newFormat = m_styles[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 = m_styles[units[j].styleIndex].format;
|
||||
// tmpFormat takes precedence.
|
||||
newFormat.merge(tmpFormat);
|
||||
}
|
||||
}
|
||||
|
||||
setFormat(unit.start, unit.length, newFormat);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set current block's user data.
|
||||
updateBlockUserData(blockNum, p_text);
|
||||
|
||||
setCurrentBlockState(HighlightBlockState::Normal);
|
||||
|
||||
updateCodeBlockState(result, blockNum, p_text);
|
||||
|
||||
if (currentBlockState() == HighlightBlockState::CodeBlock) {
|
||||
highlightCodeBlock(result, blockNum);
|
||||
|
||||
highlightCodeBlockColorColumn(p_text);
|
||||
}
|
||||
}
|
||||
|
||||
void PegMarkdownHighlighter::handleContentsChange(int p_position, int p_charsRemoved, int p_charsAdded)
|
||||
{
|
||||
Q_UNUSED(p_position);
|
||||
|
||||
if (p_charsRemoved == 0 && p_charsAdded == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
++m_timeStamp;
|
||||
|
||||
// We still need a timer to start a complete parse.
|
||||
m_timer->start();
|
||||
}
|
||||
|
||||
void PegMarkdownHighlighter::startParse()
|
||||
{
|
||||
QSharedPointer<PegParseConfig> config(new PegParseConfig());
|
||||
config->m_timeStamp = m_timeStamp;
|
||||
config->m_data = m_doc->toPlainText().toUtf8();
|
||||
config->m_numOfBlocks = m_doc->blockCount();
|
||||
config->m_extensions = m_parserExts;
|
||||
|
||||
m_parser->parseAsync(config);
|
||||
}
|
||||
|
||||
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;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void PegMarkdownHighlighter::setCodeBlockHighlights(TimeStamp p_timeStamp,
|
||||
const QVector<HLUnitPos> &p_units)
|
||||
{
|
||||
QSharedPointer<PegHighlighterResult> result(m_result);
|
||||
|
||||
if (!result->matched(p_timeStamp)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (p_units.isEmpty()) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
{
|
||||
QVector<QVector<HLUnitStyle>> highlights(result->m_codeBlocksHighlights.size());
|
||||
for (auto const &unit : p_units) {
|
||||
int pos = unit.m_position;
|
||||
int end = unit.m_position + unit.m_length;
|
||||
QTextBlock block = m_doc->findBlock(pos);
|
||||
int startBlockNum = block.blockNumber();
|
||||
int endBlockNum = m_doc->findBlock(end).blockNumber();
|
||||
|
||||
// Text has been changed. Abandon the obsolete parsed result.
|
||||
if (startBlockNum == -1 || endBlockNum >= highlights.size()) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
while (block.isValid()) {
|
||||
int blockNumber = block.blockNumber();
|
||||
if (blockNumber > endBlockNum) {
|
||||
break;
|
||||
}
|
||||
|
||||
int blockStartPos = block.position();
|
||||
HLUnitStyle hl;
|
||||
hl.style = unit.m_style;
|
||||
if (blockNumber == startBlockNum) {
|
||||
hl.start = pos - blockStartPos;
|
||||
hl.length = (startBlockNum == endBlockNum) ?
|
||||
(end - pos) : (block.length() - hl.start);
|
||||
} else if (blockNumber == endBlockNum) {
|
||||
hl.start = 0;
|
||||
hl.length = end - blockStartPos;
|
||||
} else {
|
||||
hl.start = 0;
|
||||
hl.length = block.length();
|
||||
}
|
||||
|
||||
highlights[blockNumber].append(hl);
|
||||
|
||||
block = block.next();
|
||||
}
|
||||
}
|
||||
|
||||
// Need to highlight in order.
|
||||
for (int i = 0; i < highlights.size(); ++i) {
|
||||
QVector<HLUnitStyle> &units = highlights[i];
|
||||
if (!units.isEmpty()) {
|
||||
if (units.size() > 1) {
|
||||
std::sort(units.begin(), units.end(), compHLUnitStyle);
|
||||
}
|
||||
|
||||
result->m_codeBlocksHighlights[i].append(units);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exit:
|
||||
if (--result->m_numOfCodeBlockHighlightsToRecv <= 0) {
|
||||
rehighlight();
|
||||
}
|
||||
}
|
||||
|
||||
void PegMarkdownHighlighter::updateHighlightFast()
|
||||
{
|
||||
updateHighlight();
|
||||
}
|
||||
|
||||
void PegMarkdownHighlighter::updateHighlight()
|
||||
{
|
||||
m_timer->stop();
|
||||
startParse();
|
||||
}
|
||||
|
||||
void PegMarkdownHighlighter::handleParseResult(const QSharedPointer<PegParseResult> &p_result)
|
||||
{
|
||||
if (!m_result.isNull() && m_result->m_timeStamp > p_result->m_timeStamp) {
|
||||
return;
|
||||
}
|
||||
|
||||
PegHighlighterResult *pegRes = new PegHighlighterResult(this, p_result);
|
||||
m_result.reset(pegRes);
|
||||
|
||||
updateCodeBlocks(m_result);
|
||||
|
||||
// Now we got a new result, rehighlight.
|
||||
rehighlight();
|
||||
|
||||
completeHighlight(m_result);
|
||||
}
|
||||
|
||||
void PegMarkdownHighlighter::updateCodeBlocks(QSharedPointer<PegHighlighterResult> p_result)
|
||||
{
|
||||
if (!p_result->matched(m_timeStamp)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (g_config->getEnableCodeBlockHighlight()) {
|
||||
p_result->m_codeBlocksHighlights.resize(p_result->m_numOfBlocks);
|
||||
p_result->m_numOfCodeBlockHighlightsToRecv = p_result->m_codeBlocks.size();
|
||||
}
|
||||
|
||||
emit codeBlocksUpdated(p_result->m_timeStamp, p_result->m_codeBlocks);
|
||||
}
|
||||
|
||||
void PegMarkdownHighlighter::updateBlockUserData(int p_blockNum, const QString &p_text)
|
||||
{
|
||||
Q_UNUSED(p_text);
|
||||
VTextBlockData *blockData = currentBlockData();
|
||||
if (!blockData) {
|
||||
blockData = new VTextBlockData();
|
||||
setCurrentBlockUserData(blockData);
|
||||
} else {
|
||||
blockData->setCodeBlockIndentation(-1);
|
||||
blockData->clearMathjax();
|
||||
}
|
||||
|
||||
if (blockData->getPreviews().isEmpty()) {
|
||||
m_possiblePreviewBlocks.remove(p_blockNum);
|
||||
} else {
|
||||
m_possiblePreviewBlocks.insert(p_blockNum);
|
||||
}
|
||||
}
|
||||
|
||||
void PegMarkdownHighlighter::updateCodeBlockState(const QSharedPointer<PegHighlighterResult> &p_result,
|
||||
int p_blockNum,
|
||||
const QString &p_text)
|
||||
{
|
||||
if (!p_result->matched(m_timeStamp)) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto it = p_result->m_codeBlocksState.find(p_blockNum);
|
||||
if (it != p_result->m_codeBlocksState.end()) {
|
||||
VTextBlockData *blockData = currentBlockData();
|
||||
Q_ASSERT(blockData);
|
||||
|
||||
HighlightBlockState state = it.value();
|
||||
// Set code block indentation.
|
||||
switch (state) {
|
||||
case HighlightBlockState::CodeBlockStart:
|
||||
{
|
||||
int startLeadingSpaces = 0;
|
||||
QRegExp reg(VUtils::c_fencedCodeBlockStartRegExp);
|
||||
int idx = reg.indexIn(p_text);
|
||||
if (idx >= 0) {
|
||||
startLeadingSpaces = reg.capturedTexts()[1].size();
|
||||
}
|
||||
|
||||
blockData->setCodeBlockIndentation(startLeadingSpaces);
|
||||
break;
|
||||
}
|
||||
|
||||
case HighlightBlockState::CodeBlock:
|
||||
V_FALLTHROUGH;
|
||||
case HighlightBlockState::CodeBlockEnd:
|
||||
{
|
||||
int startLeadingSpaces = 0;
|
||||
VTextBlockData *preBlockData = previousBlockData();
|
||||
if (preBlockData) {
|
||||
startLeadingSpaces = preBlockData->getCodeBlockIndentation();
|
||||
}
|
||||
|
||||
blockData->setCodeBlockIndentation(startLeadingSpaces);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
Q_ASSERT(false);
|
||||
break;
|
||||
}
|
||||
|
||||
// Set code block state.
|
||||
setCurrentBlockState(state);
|
||||
}
|
||||
}
|
||||
|
||||
void PegMarkdownHighlighter::highlightCodeBlock(const QSharedPointer<PegHighlighterResult> &p_result,
|
||||
int p_blockNum)
|
||||
{
|
||||
if (p_result->m_codeBlocksHighlights.size() > p_blockNum) {
|
||||
const QVector<HLUnitStyle> &units = p_result->m_codeBlocksHighlights[p_blockNum];
|
||||
if (!units.isEmpty()) {
|
||||
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()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
formats[i] = &(*it);
|
||||
|
||||
QTextCharFormat newFormat = m_codeBlockFormat;
|
||||
newFormat.merge(*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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PegMarkdownHighlighter::highlightCodeBlockColorColumn(const QString &p_text)
|
||||
{
|
||||
int cc = g_config->getColorColumn();
|
||||
if (cc <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
VTextBlockData *blockData = currentBlockData();
|
||||
Q_ASSERT(blockData);
|
||||
int indent = blockData->getCodeBlockIndentation();
|
||||
if (indent == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
cc += indent;
|
||||
if (p_text.size() < cc) {
|
||||
return;
|
||||
}
|
||||
|
||||
setFormat(cc - 1, 1, m_colorColumnFormat);
|
||||
}
|
||||
|
||||
void PegMarkdownHighlighter::completeHighlight(QSharedPointer<PegHighlighterResult> p_result)
|
||||
{
|
||||
if (!p_result->matched(m_timeStamp)) {
|
||||
return;
|
||||
}
|
||||
|
||||
emit imageLinksUpdated(p_result->m_imageRegions);
|
||||
emit headersUpdated(p_result->m_headerRegions);
|
||||
|
||||
emit highlightCompleted();
|
||||
}
|
179
src/pegmarkdownhighlighter.h
Normal file
179
src/pegmarkdownhighlighter.h
Normal file
@ -0,0 +1,179 @@
|
||||
#ifndef PEGMARKDOWNHIGHLIGHTER_H
|
||||
#define PEGMARKDOWNHIGHLIGHTER_H
|
||||
|
||||
#include <QSyntaxHighlighter>
|
||||
#include <QTextCharFormat>
|
||||
|
||||
#include "vtextblockdata.h"
|
||||
#include "markdownhighlighterdata.h"
|
||||
#include "peghighlighterresult.h"
|
||||
|
||||
class PegParser;
|
||||
class QTimer;
|
||||
|
||||
class PegMarkdownHighlighter : public QSyntaxHighlighter
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit PegMarkdownHighlighter(QTextDocument *p_doc = nullptr);
|
||||
|
||||
void init(const QVector<HighlightingStyle> &p_styles,
|
||||
const QHash<QString, QTextCharFormat> &p_codeBlockStyles,
|
||||
bool p_mathjaxEnabled,
|
||||
int p_timerInterval);
|
||||
|
||||
// Set code block highlight result by VCodeBlockHighlightHelper.
|
||||
void setCodeBlockHighlights(TimeStamp p_timeStamp, const QVector<HLUnitPos> &p_units);
|
||||
|
||||
const QVector<VElementRegion> &getHeaderRegions() const;
|
||||
|
||||
const QSet<int> &getPossiblePreviewBlocks() const;
|
||||
|
||||
void clearPossiblePreviewBlocks(const QVector<int> &p_blocksToClear);
|
||||
|
||||
void addPossiblePreviewBlock(int p_blockNumber);
|
||||
|
||||
// Parse and only update the highlight results for rehighlight().
|
||||
void updateHighlightFast();
|
||||
|
||||
QHash<QString, QTextCharFormat> &getCodeBlockStyles();
|
||||
|
||||
QVector<HighlightingStyle> &getStyles();
|
||||
|
||||
const QVector<HighlightingStyle> &getStyles() const;
|
||||
|
||||
const QTextDocument *getDocument() const;
|
||||
|
||||
public slots:
|
||||
// Parse and rehighlight immediately.
|
||||
void updateHighlight();
|
||||
|
||||
signals:
|
||||
void highlightCompleted();
|
||||
|
||||
// QVector is implicitly shared.
|
||||
void codeBlocksUpdated(TimeStamp p_timeStamp, const QVector<VCodeBlock> &p_codeBlocks);
|
||||
|
||||
// Emitted when image regions have been fetched from a new parsing result.
|
||||
void imageLinksUpdated(const QVector<VElementRegion> &p_imageRegions);
|
||||
|
||||
// Emitted when header regions have been fetched from a new parsing result.
|
||||
void headersUpdated(const QVector<VElementRegion> &p_headerRegions);
|
||||
|
||||
// Emitted when Mathjax blocks updated.
|
||||
void mathjaxBlocksUpdated(const QVector<VMathjaxBlock> &p_mathjaxBlocks);
|
||||
|
||||
protected:
|
||||
void highlightBlock(const QString &p_text) Q_DECL_OVERRIDE;
|
||||
|
||||
private slots:
|
||||
void handleContentsChange(int p_position, int p_charsRemoved, int p_charsAdded);
|
||||
|
||||
void handleParseResult(const QSharedPointer<PegParseResult> &p_result);
|
||||
|
||||
private:
|
||||
void startParse();
|
||||
|
||||
void updateCodeBlocks(QSharedPointer<PegHighlighterResult> p_result);
|
||||
|
||||
// Set the user data of currentBlock().
|
||||
void updateBlockUserData(int p_blockNum, const QString &p_text);
|
||||
|
||||
void updateCodeBlockState(const QSharedPointer<PegHighlighterResult> &p_result,
|
||||
int p_blockNum,
|
||||
const QString &p_text);
|
||||
|
||||
// Highlight fenced code block according to VCodeBlockHighlightHelper result.
|
||||
void highlightCodeBlock(const QSharedPointer<PegHighlighterResult> &p_result,
|
||||
int p_blockNum);
|
||||
|
||||
// Highlight color column in code block.
|
||||
void highlightCodeBlockColorColumn(const QString &p_text);
|
||||
|
||||
VTextBlockData *currentBlockData() const;
|
||||
|
||||
VTextBlockData *previousBlockData() const;
|
||||
|
||||
void completeHighlight(QSharedPointer<PegHighlighterResult> p_result);
|
||||
|
||||
QTextDocument *m_doc;
|
||||
|
||||
TimeStamp m_timeStamp;
|
||||
|
||||
QVector<HighlightingStyle> m_styles;
|
||||
QHash<QString, QTextCharFormat> m_codeBlockStyles;
|
||||
|
||||
QTextCharFormat m_codeBlockFormat;
|
||||
QTextCharFormat m_colorColumnFormat;
|
||||
|
||||
PegParser *m_parser;
|
||||
|
||||
QSharedPointer<PegHighlighterResult> m_result;
|
||||
|
||||
// Block number of those blocks which possible contains previewed image.
|
||||
QSet<int> m_possiblePreviewBlocks;
|
||||
|
||||
// Extensions for parser.
|
||||
int m_parserExts;
|
||||
|
||||
// Timer to trigger parse.
|
||||
QTimer *m_timer;
|
||||
};
|
||||
|
||||
inline const QVector<VElementRegion> &PegMarkdownHighlighter::getHeaderRegions() const
|
||||
{
|
||||
return m_result->m_headerRegions;
|
||||
}
|
||||
|
||||
inline const QSet<int> &PegMarkdownHighlighter::getPossiblePreviewBlocks() const
|
||||
{
|
||||
return m_possiblePreviewBlocks;
|
||||
}
|
||||
|
||||
inline void PegMarkdownHighlighter::clearPossiblePreviewBlocks(const QVector<int> &p_blocksToClear)
|
||||
{
|
||||
for (auto i : p_blocksToClear) {
|
||||
m_possiblePreviewBlocks.remove(i);
|
||||
}
|
||||
}
|
||||
|
||||
inline void PegMarkdownHighlighter::addPossiblePreviewBlock(int p_blockNumber)
|
||||
{
|
||||
m_possiblePreviewBlocks.insert(p_blockNumber);
|
||||
}
|
||||
|
||||
inline QHash<QString, QTextCharFormat> &PegMarkdownHighlighter::getCodeBlockStyles()
|
||||
{
|
||||
return m_codeBlockStyles;
|
||||
}
|
||||
|
||||
inline QVector<HighlightingStyle> &PegMarkdownHighlighter::getStyles()
|
||||
{
|
||||
return m_styles;
|
||||
}
|
||||
|
||||
inline const QVector<HighlightingStyle> &PegMarkdownHighlighter::getStyles() const
|
||||
{
|
||||
return m_styles;
|
||||
}
|
||||
|
||||
inline const QTextDocument *PegMarkdownHighlighter::getDocument() const
|
||||
{
|
||||
return m_doc;
|
||||
}
|
||||
|
||||
inline VTextBlockData *PegMarkdownHighlighter::currentBlockData() const
|
||||
{
|
||||
return static_cast<VTextBlockData *>(currentBlockUserData());
|
||||
}
|
||||
|
||||
inline VTextBlockData *PegMarkdownHighlighter::previousBlockData() const
|
||||
{
|
||||
QTextBlock block = currentBlock().previous();
|
||||
if (!block.isValid()) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return static_cast<VTextBlockData *>(block.userData());
|
||||
}
|
||||
#endif // PEGMARKDOWNHIGHLIGHTER_H
|
276
src/pegparser.cpp
Normal file
276
src/pegparser.cpp
Normal file
@ -0,0 +1,276 @@
|
||||
#include "pegparser.h"
|
||||
|
||||
enum WorkerState
|
||||
{
|
||||
Idle,
|
||||
Busy,
|
||||
Cancelled,
|
||||
Finished
|
||||
};
|
||||
|
||||
void PegParseResult::parse(QAtomicInt &p_stop)
|
||||
{
|
||||
parseImageRegions(p_stop);
|
||||
|
||||
parseHeaderRegions(p_stop);
|
||||
|
||||
parseFencedCodeBlockRegions(p_stop);
|
||||
}
|
||||
|
||||
void PegParseResult::parseImageRegions(QAtomicInt &p_stop)
|
||||
{
|
||||
// From Qt5.7, the capacity is preserved.
|
||||
m_imageRegions.clear();
|
||||
if (isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
pmh_element *elem = m_pmhElements[pmh_IMAGE];
|
||||
while (elem != NULL) {
|
||||
if (elem->end <= elem->pos) {
|
||||
elem = elem->next;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (p_stop.load() == 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_imageRegions.push_back(VElementRegion(elem->pos, elem->end));
|
||||
elem = elem->next;
|
||||
}
|
||||
}
|
||||
|
||||
void PegParseResult::parseHeaderRegions(QAtomicInt &p_stop)
|
||||
{
|
||||
// From Qt5.7, the capacity is preserved.
|
||||
m_headerRegions.clear();
|
||||
if (isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
pmh_element_type hx[6] = {pmh_H1, pmh_H2, pmh_H3, pmh_H4, pmh_H5, pmh_H6};
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
pmh_element *elem = m_pmhElements[hx[i]];
|
||||
while (elem != NULL) {
|
||||
if (elem->end <= elem->pos) {
|
||||
elem = elem->next;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (p_stop.load() == 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_headerRegions.push_back(VElementRegion(elem->pos, elem->end));
|
||||
elem = elem->next;
|
||||
}
|
||||
}
|
||||
|
||||
if (p_stop.load() == 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::sort(m_headerRegions.begin(), m_headerRegions.end());
|
||||
}
|
||||
|
||||
void PegParseResult::parseFencedCodeBlockRegions(QAtomicInt &p_stop)
|
||||
{
|
||||
m_codeBlockRegions.clear();
|
||||
if (isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
pmh_element *elem = m_pmhElements[pmh_FENCEDCODEBLOCK];
|
||||
while (elem != NULL) {
|
||||
if (elem->end <= elem->pos) {
|
||||
elem = elem->next;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (p_stop.load() == 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_codeBlockRegions.contains(elem->pos)) {
|
||||
m_codeBlockRegions.insert(elem->pos, VElementRegion(elem->pos, elem->end));
|
||||
}
|
||||
|
||||
elem = elem->next;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
PegParserWorker::PegParserWorker(QObject *p_parent)
|
||||
: QThread(p_parent),
|
||||
m_stop(0),
|
||||
m_state(WorkerState::Idle)
|
||||
{
|
||||
}
|
||||
|
||||
void PegParserWorker::prepareParse(const QSharedPointer<PegParseConfig> &p_config)
|
||||
{
|
||||
Q_ASSERT(m_parseConfig.isNull());
|
||||
|
||||
m_state = WorkerState::Busy;
|
||||
m_parseConfig = p_config;
|
||||
}
|
||||
|
||||
void PegParserWorker::reset()
|
||||
{
|
||||
m_parseConfig.reset();
|
||||
m_parseResult.reset();
|
||||
m_stop.store(0);
|
||||
m_state = WorkerState::Idle;
|
||||
}
|
||||
|
||||
void PegParserWorker::stop()
|
||||
{
|
||||
m_stop.store(1);
|
||||
}
|
||||
|
||||
void PegParserWorker::run()
|
||||
{
|
||||
Q_ASSERT(m_state == WorkerState::Busy);
|
||||
|
||||
m_parseResult = parseMarkdown(m_parseConfig, m_stop);
|
||||
|
||||
if (isAskedToStop()) {
|
||||
m_state = WorkerState::Cancelled;
|
||||
return;
|
||||
}
|
||||
|
||||
m_state = WorkerState::Finished;
|
||||
}
|
||||
|
||||
QSharedPointer<PegParseResult> PegParserWorker::parseMarkdown(const QSharedPointer<PegParseConfig> &p_config,
|
||||
QAtomicInt &p_stop)
|
||||
{
|
||||
QSharedPointer<PegParseResult> result(new PegParseResult(p_config));
|
||||
|
||||
if (p_config->m_data.isEmpty()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
pmh_element **pmhResult = NULL;
|
||||
|
||||
char *data = p_config->m_data.data();
|
||||
|
||||
pmh_markdown_to_elements(data, p_config->m_extensions, &pmhResult);
|
||||
|
||||
result->m_pmhElements = pmhResult;
|
||||
|
||||
if (p_stop.load() == 1) {
|
||||
return result;
|
||||
}
|
||||
|
||||
result->parse(p_stop);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
#define NUM_OF_THREADS 2
|
||||
|
||||
PegParser::PegParser(QObject *p_parent)
|
||||
: QObject(p_parent)
|
||||
{
|
||||
init();
|
||||
}
|
||||
|
||||
void PegParser::parseAsync(const QSharedPointer<PegParseConfig> &p_config)
|
||||
{
|
||||
m_pendingWork = p_config;
|
||||
|
||||
pickWorker();
|
||||
}
|
||||
|
||||
void PegParser::init()
|
||||
{
|
||||
for (int i = 0; i < NUM_OF_THREADS; ++i) {
|
||||
PegParserWorker *th = new PegParserWorker(this);
|
||||
connect(th, &PegParserWorker::finished,
|
||||
this, [this, th]() {
|
||||
handleWorkerFinished(th);
|
||||
});
|
||||
|
||||
m_workers.append(th);
|
||||
}
|
||||
}
|
||||
|
||||
void PegParser::clear()
|
||||
{
|
||||
m_pendingWork.reset();
|
||||
|
||||
for (auto const & th : m_workers) {
|
||||
th->quit();
|
||||
th->wait();
|
||||
|
||||
delete th;
|
||||
}
|
||||
|
||||
m_workers.clear();
|
||||
}
|
||||
|
||||
PegParser::~PegParser()
|
||||
{
|
||||
clear();
|
||||
}
|
||||
|
||||
void PegParser::handleWorkerFinished(PegParserWorker *p_worker)
|
||||
{
|
||||
QSharedPointer<PegParseResult> result;
|
||||
if (p_worker->state() == WorkerState::Finished) {
|
||||
result = p_worker->parseResult();
|
||||
}
|
||||
|
||||
p_worker->reset();
|
||||
|
||||
pickWorker();
|
||||
|
||||
if (!result.isNull()) {
|
||||
emit parseResultReady(result);
|
||||
}
|
||||
}
|
||||
|
||||
void PegParser::pickWorker()
|
||||
{
|
||||
if (m_pendingWork.isNull()) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool allBusy = true;
|
||||
for (auto th : m_workers) {
|
||||
if (th->state() == WorkerState::Idle) {
|
||||
scheduleWork(th, m_pendingWork);
|
||||
m_pendingWork.reset();
|
||||
return;
|
||||
} else if (th->state() != WorkerState::Busy) {
|
||||
allBusy = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (allBusy) {
|
||||
// Need to stop the worker with non-minimal timestamp.
|
||||
int idx = 0;
|
||||
TimeStamp minTS = m_workers[idx]->workTimeStamp();
|
||||
|
||||
if (m_workers.size() > 1) {
|
||||
if (m_workers[1]->workTimeStamp() > minTS) {
|
||||
idx = 1;
|
||||
}
|
||||
}
|
||||
|
||||
m_workers[idx]->stop();
|
||||
}
|
||||
}
|
||||
|
||||
void PegParser::scheduleWork(PegParserWorker *p_worker,
|
||||
const QSharedPointer<PegParseConfig> &p_config)
|
||||
{
|
||||
Q_ASSERT(p_worker->state() == WorkerState::Idle);
|
||||
|
||||
p_worker->reset();
|
||||
p_worker->prepareParse(p_config);
|
||||
p_worker->start();
|
||||
}
|
186
src/pegparser.h
Normal file
186
src/pegparser.h
Normal file
@ -0,0 +1,186 @@
|
||||
#ifndef PEGPARSER_H
|
||||
#define PEGPARSER_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include <QSharedPointer>
|
||||
#include <QThread>
|
||||
#include <QAtomicInt>
|
||||
#include <QVector>
|
||||
|
||||
#include "vconstants.h"
|
||||
#include "markdownhighlighterdata.h"
|
||||
|
||||
struct PegParseConfig
|
||||
{
|
||||
TimeStamp m_timeStamp;
|
||||
|
||||
QByteArray m_data;
|
||||
|
||||
int m_numOfBlocks;
|
||||
|
||||
int m_extensions;
|
||||
|
||||
QString toString() const
|
||||
{
|
||||
return QString("PegParseConfig ts %1 data %2 blocks %3").arg(m_timeStamp)
|
||||
.arg(m_data.size())
|
||||
.arg(m_numOfBlocks);
|
||||
}
|
||||
};
|
||||
|
||||
struct PegParseResult
|
||||
{
|
||||
PegParseResult(const QSharedPointer<PegParseConfig> &p_config)
|
||||
: m_timeStamp(p_config->m_timeStamp),
|
||||
m_numOfBlocks(p_config->m_numOfBlocks),
|
||||
m_pmhElements(NULL)
|
||||
{
|
||||
}
|
||||
|
||||
~PegParseResult()
|
||||
{
|
||||
clearPmhElements();
|
||||
}
|
||||
|
||||
void clearPmhElements()
|
||||
{
|
||||
if (m_pmhElements) {
|
||||
pmh_free_elements(m_pmhElements);
|
||||
m_pmhElements = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
bool operator<(const PegParseResult &p_other) const
|
||||
{
|
||||
return m_timeStamp < p_other.m_timeStamp;
|
||||
}
|
||||
|
||||
QString toString() const
|
||||
{
|
||||
return QString("PegParseResult ts %1").arg(m_timeStamp);
|
||||
}
|
||||
|
||||
bool isEmpty() const
|
||||
{
|
||||
return !m_pmhElements;
|
||||
}
|
||||
|
||||
// Parse m_pmhElements.
|
||||
void parse(QAtomicInt &p_stop);
|
||||
|
||||
TimeStamp m_timeStamp;
|
||||
|
||||
int m_numOfBlocks;
|
||||
|
||||
pmh_element **m_pmhElements;
|
||||
|
||||
// All image link regions.
|
||||
QVector<VElementRegion> m_imageRegions;
|
||||
|
||||
// All header regions.
|
||||
// Sorted by start position.
|
||||
QVector<VElementRegion> m_headerRegions;
|
||||
|
||||
// Fenced code block regions.
|
||||
// Ordered by start position in ascending order.
|
||||
QMap<int, VElementRegion> m_codeBlockRegions;
|
||||
|
||||
private:
|
||||
void parseImageRegions(QAtomicInt &p_stop);
|
||||
|
||||
void parseHeaderRegions(QAtomicInt &p_stop);
|
||||
|
||||
void parseFencedCodeBlockRegions(QAtomicInt &p_stop);
|
||||
};
|
||||
|
||||
class PegParserWorker : public QThread
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit PegParserWorker(QObject *p_parent = nullptr);
|
||||
|
||||
void prepareParse(const QSharedPointer<PegParseConfig> &p_config);
|
||||
|
||||
void reset();
|
||||
|
||||
int state() const
|
||||
{
|
||||
return m_state;
|
||||
}
|
||||
|
||||
TimeStamp workTimeStamp() const
|
||||
{
|
||||
if (m_parseConfig.isNull()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return m_parseConfig->m_timeStamp;
|
||||
}
|
||||
|
||||
const QSharedPointer<PegParseConfig> &parseConfig() const
|
||||
{
|
||||
return m_parseConfig;
|
||||
}
|
||||
|
||||
const QSharedPointer<PegParseResult> &parseResult() const
|
||||
{
|
||||
return m_parseResult;
|
||||
}
|
||||
|
||||
public slots:
|
||||
void stop();
|
||||
|
||||
protected:
|
||||
void run() Q_DECL_OVERRIDE;
|
||||
|
||||
private:
|
||||
QSharedPointer<PegParseResult> parseMarkdown(const QSharedPointer<PegParseConfig> &p_config,
|
||||
QAtomicInt &p_stop);
|
||||
|
||||
bool isAskedToStop() const
|
||||
{
|
||||
return m_stop.load() == 1;
|
||||
}
|
||||
|
||||
QAtomicInt m_stop;
|
||||
|
||||
int m_state;
|
||||
|
||||
QSharedPointer<PegParseConfig> m_parseConfig;
|
||||
|
||||
QSharedPointer<PegParseResult> m_parseResult;
|
||||
};
|
||||
|
||||
class PegParser : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit PegParser(QObject *p_parent = nullptr);
|
||||
|
||||
~PegParser();
|
||||
|
||||
void parseAsync(const QSharedPointer<PegParseConfig> &p_config);
|
||||
|
||||
signals:
|
||||
void parseResultReady(const QSharedPointer<PegParseResult> &p_result);
|
||||
|
||||
private slots:
|
||||
void handleWorkerFinished(PegParserWorker *p_worker);
|
||||
|
||||
private:
|
||||
void init();
|
||||
|
||||
void clear();
|
||||
|
||||
void pickWorker();
|
||||
|
||||
void scheduleWork(PegParserWorker *p_worker, const QSharedPointer<PegParseConfig> &p_config);
|
||||
|
||||
// Maintain a fixed number of workers to pick work.
|
||||
QVector<PegParserWorker *> m_workers;
|
||||
|
||||
QSharedPointer<PegParseConfig> m_pendingWork;
|
||||
};
|
||||
|
||||
#endif // PEGPARSER_H
|
@ -106,7 +106,7 @@ minimize_to_system_tray=-1
|
||||
markdown_suffix=md,markdown,mkd
|
||||
|
||||
; Markdown highlight timer interval (milliseconds)
|
||||
markdown_highlight_interval=400
|
||||
markdown_highlight_interval=200
|
||||
|
||||
; Adds specified height between lines (in pixels)
|
||||
line_distance_height=3
|
||||
|
11
src/src.pro
11
src/src.pro
@ -140,7 +140,10 @@ SOURCES += main.cpp\
|
||||
vtagpanel.cpp \
|
||||
valltagspanel.cpp \
|
||||
vtaglabel.cpp \
|
||||
vtagexplorer.cpp
|
||||
vtagexplorer.cpp \
|
||||
pegmarkdownhighlighter.cpp \
|
||||
pegparser.cpp \
|
||||
peghighlighterresult.cpp
|
||||
|
||||
HEADERS += vmainwindow.h \
|
||||
vdirectorytree.h \
|
||||
@ -274,7 +277,11 @@ HEADERS += vmainwindow.h \
|
||||
vtagpanel.h \
|
||||
valltagspanel.h \
|
||||
vtaglabel.h \
|
||||
vtagexplorer.h
|
||||
vtagexplorer.h \
|
||||
markdownhighlighterdata.h \
|
||||
pegmarkdownhighlighter.h \
|
||||
pegparser.h \
|
||||
peghighlighterresult.h
|
||||
|
||||
RESOURCES += \
|
||||
vnote.qrc \
|
||||
|
@ -2,10 +2,12 @@
|
||||
|
||||
#include <QDebug>
|
||||
#include <QStringList>
|
||||
|
||||
#include "vdocument.h"
|
||||
#include "utils/vutils.h"
|
||||
#include "pegmarkdownhighlighter.h"
|
||||
|
||||
VCodeBlockHighlightHelper::VCodeBlockHighlightHelper(HGMarkdownHighlighter *p_highlighter,
|
||||
VCodeBlockHighlightHelper::VCodeBlockHighlightHelper(PegMarkdownHighlighter *p_highlighter,
|
||||
VDocument *p_vdoc,
|
||||
MarkdownConverterType p_type)
|
||||
: QObject(p_highlighter),
|
||||
@ -14,14 +16,14 @@ VCodeBlockHighlightHelper::VCodeBlockHighlightHelper(HGMarkdownHighlighter *p_hi
|
||||
m_type(p_type),
|
||||
m_timeStamp(0)
|
||||
{
|
||||
connect(m_highlighter, &HGMarkdownHighlighter::codeBlocksUpdated,
|
||||
connect(m_highlighter, &PegMarkdownHighlighter::codeBlocksUpdated,
|
||||
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);
|
||||
m_highlighter, &PegMarkdownHighlighter::updateHighlight);
|
||||
}
|
||||
|
||||
QString VCodeBlockHighlightHelper::unindentCodeBlock(const QString &p_text)
|
||||
@ -57,44 +59,45 @@ QString VCodeBlockHighlightHelper::unindentCodeBlock(const QString &p_text)
|
||||
return res;
|
||||
}
|
||||
|
||||
void VCodeBlockHighlightHelper::handleCodeBlocksUpdated(const QVector<VCodeBlock> &p_codeBlocks)
|
||||
void VCodeBlockHighlightHelper::handleCodeBlocksUpdated(TimeStamp p_timeStamp,
|
||||
const QVector<VCodeBlock> &p_codeBlocks)
|
||||
{
|
||||
if (!m_vdocument->isReadyToHighlight()) {
|
||||
// Immediately return empty results.
|
||||
QVector<HLUnitPos> emptyRes;
|
||||
for (int i = 0; i < p_codeBlocks.size(); ++i) {
|
||||
updateHighlightResults(0, emptyRes);
|
||||
updateHighlightResults(p_timeStamp, 0, emptyRes);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
int curStamp = m_timeStamp.fetchAndAddRelaxed(1) + 1;
|
||||
m_timeStamp = p_timeStamp;
|
||||
m_codeBlocks = p_codeBlocks;
|
||||
for (int i = 0; i < m_codeBlocks.size(); ++i) {
|
||||
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);
|
||||
qDebug() << "code block highlight hit cache" << p_timeStamp << i;
|
||||
it.value().m_timeStamp = p_timeStamp;
|
||||
updateHighlightResults(p_timeStamp, block.m_startPos, it.value().m_units);
|
||||
} else {
|
||||
QString unindentedText = unindentCodeBlock(block.m_text);
|
||||
m_vdocument->highlightTextAsync(unindentedText, i, curStamp);
|
||||
m_vdocument->highlightTextAsync(unindentedText, i, p_timeStamp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VCodeBlockHighlightHelper::handleTextHighlightResult(const QString &p_html,
|
||||
int p_id,
|
||||
int p_timeStamp)
|
||||
unsigned long long p_timeStamp)
|
||||
{
|
||||
int curStamp = m_timeStamp.load();
|
||||
// Abandon obsolete result.
|
||||
if (curStamp != p_timeStamp) {
|
||||
if (m_timeStamp != p_timeStamp) {
|
||||
return;
|
||||
}
|
||||
|
||||
parseHighlightResult(p_timeStamp, p_id, p_html);
|
||||
}
|
||||
|
||||
@ -137,7 +140,7 @@ static void matchTokenRelaxed(const QString &p_text, const QString &p_tokenStr,
|
||||
}
|
||||
|
||||
// For now, we could only handle code blocks outside the list.
|
||||
void VCodeBlockHighlightHelper::parseHighlightResult(int p_timeStamp,
|
||||
void VCodeBlockHighlightHelper::parseHighlightResult(TimeStamp p_timeStamp,
|
||||
int p_idx,
|
||||
const QString &p_html)
|
||||
{
|
||||
@ -209,9 +212,8 @@ void VCodeBlockHighlightHelper::parseHighlightResult(int p_timeStamp,
|
||||
|
||||
exit:
|
||||
// Pass result back to highlighter.
|
||||
int curStamp = m_timeStamp.load();
|
||||
// Abandon obsolete result.
|
||||
if (curStamp != p_timeStamp) {
|
||||
if (m_timeStamp != p_timeStamp) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -224,10 +226,11 @@ exit:
|
||||
// Add it to cache.
|
||||
addToHighlightCache(text, p_timeStamp, hlUnits);
|
||||
|
||||
updateHighlightResults(startPos, hlUnits);
|
||||
updateHighlightResults(p_timeStamp, startPos, hlUnits);
|
||||
}
|
||||
|
||||
void VCodeBlockHighlightHelper::updateHighlightResults(int p_startPos,
|
||||
void VCodeBlockHighlightHelper::updateHighlightResults(TimeStamp p_timeStamp,
|
||||
int p_startPos,
|
||||
QVector<HLUnitPos> p_units)
|
||||
{
|
||||
for (int i = 0; i < p_units.size(); ++i) {
|
||||
@ -235,7 +238,7 @@ void VCodeBlockHighlightHelper::updateHighlightResults(int p_startPos,
|
||||
}
|
||||
|
||||
// We need to call this function anyway to trigger the rehighlight.
|
||||
m_highlighter->setCodeBlockHighlights(p_units);
|
||||
m_highlighter->setCodeBlockHighlights(p_timeStamp, p_units);
|
||||
}
|
||||
|
||||
bool VCodeBlockHighlightHelper::parseSpanElement(QXmlStreamReader &p_xml,
|
||||
@ -283,7 +286,7 @@ bool VCodeBlockHighlightHelper::parseSpanElement(QXmlStreamReader &p_xml,
|
||||
}
|
||||
|
||||
void VCodeBlockHighlightHelper::addToHighlightCache(const QString &p_text,
|
||||
int p_timeStamp,
|
||||
TimeStamp p_timeStamp,
|
||||
const QVector<HLUnitPos> &p_units)
|
||||
{
|
||||
const int c_maxEntries = 100;
|
||||
|
@ -6,15 +6,17 @@
|
||||
#include <QAtomicInteger>
|
||||
#include <QXmlStreamReader>
|
||||
#include <QHash>
|
||||
|
||||
#include "vconfigmanager.h"
|
||||
|
||||
class VDocument;
|
||||
class PegMarkdownHighlighter;
|
||||
|
||||
class VCodeBlockHighlightHelper : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
VCodeBlockHighlightHelper(HGMarkdownHighlighter *p_highlighter,
|
||||
VCodeBlockHighlightHelper(PegMarkdownHighlighter *p_highlighter,
|
||||
VDocument *p_vdoc, MarkdownConverterType p_type);
|
||||
|
||||
// @p_text: text of fenced code block.
|
||||
@ -25,27 +27,30 @@ public:
|
||||
static QString unindentCodeBlock(const QString &p_text);
|
||||
|
||||
private slots:
|
||||
void handleCodeBlocksUpdated(const QVector<VCodeBlock> &p_codeBlocks);
|
||||
void handleCodeBlocksUpdated(TimeStamp p_timeStamp, const QVector<VCodeBlock> &p_codeBlocks);
|
||||
|
||||
void handleTextHighlightResult(const QString &p_html, int p_id, int p_timeStamp);
|
||||
void handleTextHighlightResult(const QString &p_html, int p_id, unsigned long long p_timeStamp);
|
||||
|
||||
private:
|
||||
struct HLResult
|
||||
{
|
||||
HLResult() : m_timeStamp(-1)
|
||||
HLResult()
|
||||
: m_timeStamp(0)
|
||||
{
|
||||
}
|
||||
|
||||
HLResult(int p_timeStamp, const QVector<HLUnitPos> &p_units)
|
||||
: m_timeStamp(p_timeStamp), m_units(p_units)
|
||||
HLResult(TimeStamp p_timeStamp, const QVector<HLUnitPos> &p_units)
|
||||
: m_timeStamp(p_timeStamp),
|
||||
m_units(p_units)
|
||||
{
|
||||
}
|
||||
|
||||
int m_timeStamp;
|
||||
TimeStamp m_timeStamp;
|
||||
|
||||
QVector<HLUnitPos> m_units;
|
||||
};
|
||||
|
||||
void parseHighlightResult(int p_timeStamp, int p_idx, const QString &p_html);
|
||||
void parseHighlightResult(TimeStamp p_timeStamp, int p_idx, const QString &p_html);
|
||||
|
||||
// @p_text: the raw text of the code block;
|
||||
// @p_index: the start index of the span element within @p_text;
|
||||
@ -54,16 +59,18 @@ private:
|
||||
const QString &p_text, int &p_index,
|
||||
QVector<HLUnitPos> &p_units);
|
||||
|
||||
void updateHighlightResults(int p_startPos, QVector<HLUnitPos> p_units);
|
||||
void updateHighlightResults(TimeStamp p_timeStamp, int p_startPos, QVector<HLUnitPos> p_units);
|
||||
|
||||
void addToHighlightCache(const QString &p_text,
|
||||
int p_timeStamp,
|
||||
TimeStamp p_timeStamp,
|
||||
const QVector<HLUnitPos> &p_units);
|
||||
|
||||
HGMarkdownHighlighter *m_highlighter;
|
||||
PegMarkdownHighlighter *m_highlighter;
|
||||
VDocument *m_vdocument;
|
||||
MarkdownConverterType m_type;
|
||||
QAtomicInteger<int> m_timeStamp;
|
||||
|
||||
TimeStamp m_timeStamp;
|
||||
|
||||
QVector<VCodeBlock> m_codeBlocks;
|
||||
|
||||
// Cache for highlight result, using the code block text as key.
|
||||
|
@ -11,7 +11,7 @@
|
||||
#include <QDir>
|
||||
|
||||
#include "vnotebook.h"
|
||||
#include "hgmarkdownhighlighter.h"
|
||||
#include "markdownhighlighterdata.h"
|
||||
#include "vmarkdownconverter.h"
|
||||
#include "vconstants.h"
|
||||
#include "vfilesessioninfo.h"
|
||||
|
@ -122,9 +122,11 @@ enum HighlightBlockState
|
||||
CodeBlockEnd,
|
||||
|
||||
// This block is inside a HTML comment region.
|
||||
// Obsolete.
|
||||
Comment,
|
||||
|
||||
// Verbatim code block.
|
||||
// Obsolete.
|
||||
Verbatim,
|
||||
|
||||
// Mathjax. It means the pending state of the block.
|
||||
@ -132,6 +134,7 @@ enum HighlightBlockState
|
||||
MathjaxInline,
|
||||
|
||||
// Header.
|
||||
// Obsolete.
|
||||
Header
|
||||
};
|
||||
|
||||
|
@ -74,12 +74,12 @@ void VDocument::keyPressEvent(int p_key, bool p_ctrl, bool p_shift, bool p_meta)
|
||||
emit keyPressed(p_key, p_ctrl, p_shift, p_meta);
|
||||
}
|
||||
|
||||
void VDocument::highlightTextAsync(const QString &p_text, int p_id, int p_timeStamp)
|
||||
void VDocument::highlightTextAsync(const QString &p_text, int p_id, unsigned long long p_timeStamp)
|
||||
{
|
||||
emit requestHighlightText(p_text, p_id, p_timeStamp);
|
||||
}
|
||||
|
||||
void VDocument::highlightTextCB(const QString &p_html, int p_id, int p_timeStamp)
|
||||
void VDocument::highlightTextCB(const QString &p_html, int p_id, unsigned long long p_timeStamp)
|
||||
{
|
||||
emit textHighlighted(p_html, p_id, p_timeStamp);
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ public:
|
||||
|
||||
// Request to highlight a segment text.
|
||||
// Use p_id to identify the result.
|
||||
void highlightTextAsync(const QString &p_text, int p_id, int p_timeStamp);
|
||||
void highlightTextAsync(const QString &p_text, int p_id, unsigned long long p_timeStamp);
|
||||
|
||||
// Request to convert @p_text to HTML.
|
||||
void textToHtmlAsync(int p_identitifer,
|
||||
@ -84,7 +84,7 @@ public slots:
|
||||
void keyPressEvent(int p_key, bool p_ctrl, bool p_shift, bool p_meta);
|
||||
void updateText();
|
||||
|
||||
void highlightTextCB(const QString &p_html, int p_id, int p_timeStamp);
|
||||
void highlightTextCB(const QString &p_html, int p_id, unsigned long long p_timeStamp);
|
||||
|
||||
void noticeReadyToHighlightText();
|
||||
|
||||
@ -128,9 +128,9 @@ signals:
|
||||
|
||||
void keyPressed(int p_key, bool p_ctrl, bool p_shift, bool p_meta);
|
||||
|
||||
void requestHighlightText(const QString &p_text, int p_id, int p_timeStamp);
|
||||
void requestHighlightText(const QString &p_text, int p_id, unsigned long long p_timeStamp);
|
||||
|
||||
void textHighlighted(const QString &p_html, int p_id, int p_timeStamp);
|
||||
void textHighlighted(const QString &p_html, int p_id, unsigned long long p_timeStamp);
|
||||
|
||||
void readyToHighlightText();
|
||||
|
||||
|
@ -125,8 +125,9 @@ void VLivePreviewHelper::checkLang(const QString &p_lang,
|
||||
}
|
||||
}
|
||||
|
||||
void VLivePreviewHelper::updateCodeBlocks(const QVector<VCodeBlock> &p_codeBlocks)
|
||||
void VLivePreviewHelper::updateCodeBlocks(TimeStamp p_timeStamp, const QVector<VCodeBlock> &p_codeBlocks)
|
||||
{
|
||||
Q_UNUSED(p_timeStamp);
|
||||
if (!m_livePreviewEnabled && !m_inplacePreviewEnabled) {
|
||||
return;
|
||||
}
|
||||
|
@ -109,7 +109,7 @@ public:
|
||||
bool isPreviewEnabled() const;
|
||||
|
||||
public slots:
|
||||
void updateCodeBlocks(const QVector<VCodeBlock> &p_codeBlocks);
|
||||
void updateCodeBlocks(TimeStamp p_timeStamp, const QVector<VCodeBlock> &p_codeBlocks);
|
||||
|
||||
signals:
|
||||
void inplacePreviewCodeBlockUpdated(const QVector<QSharedPointer<VImageToPreview> > &p_images);
|
||||
|
@ -1,7 +1,6 @@
|
||||
#include <QtWidgets>
|
||||
#include "vmdedit.h"
|
||||
#include "hgmarkdownhighlighter.h"
|
||||
#include "vcodeblockhighlighthelper.h"
|
||||
#include "vmdeditoperations.h"
|
||||
#include "vnote.h"
|
||||
#include "vconfigmanager.h"
|
||||
@ -24,6 +23,9 @@ VMdEdit::VMdEdit(VFile *p_file, VDocument *p_vdoc, MarkdownConverterType p_type,
|
||||
: VEdit(p_file, p_parent), m_mdHighlighter(NULL), m_freshEdit(true),
|
||||
m_finishedAsyncJobs(c_numberOfAysncJobs)
|
||||
{
|
||||
Q_UNUSED(p_type);
|
||||
Q_UNUSED(p_vdoc);
|
||||
|
||||
V_ASSERT(p_file->getDocType() == DocType::Markdown);
|
||||
|
||||
setAcceptRichText(false);
|
||||
@ -43,9 +45,6 @@ VMdEdit::VMdEdit(VFile *p_file, VDocument *p_vdoc, MarkdownConverterType p_type,
|
||||
makeBlockVisible(textCursor().block());
|
||||
});
|
||||
|
||||
m_cbHighlighter = new VCodeBlockHighlightHelper(m_mdHighlighter, p_vdoc,
|
||||
p_type);
|
||||
|
||||
/*
|
||||
m_imagePreviewer = new VImagePreviewer(this, m_mdHighlighter);
|
||||
connect(m_mdHighlighter, &HGMarkdownHighlighter::imageLinksUpdated,
|
||||
|
@ -13,7 +13,6 @@
|
||||
#include "utils/vutils.h"
|
||||
|
||||
class HGMarkdownHighlighter;
|
||||
class VCodeBlockHighlightHelper;
|
||||
class VDocument;
|
||||
|
||||
class VMdEdit : public VEdit
|
||||
@ -110,7 +109,6 @@ private:
|
||||
int indexOfCurrentHeader() const;
|
||||
|
||||
HGMarkdownHighlighter *m_mdHighlighter;
|
||||
VCodeBlockHighlightHelper *m_cbHighlighter;
|
||||
// VImagePreviewer *m_imagePreviewer;
|
||||
|
||||
// Image links inserted while editing.
|
||||
|
@ -9,7 +9,7 @@
|
||||
#include "vdocument.h"
|
||||
#include "utils/veditutils.h"
|
||||
#include "vedittab.h"
|
||||
#include "hgmarkdownhighlighter.h"
|
||||
#include "pegmarkdownhighlighter.h"
|
||||
#include "vcodeblockhighlighthelper.h"
|
||||
#include "vmdeditoperations.h"
|
||||
#include "vtableofcontent.h"
|
||||
@ -35,7 +35,7 @@ VMdEditor::VMdEditor(VFile *p_file,
|
||||
QWidget *p_parent)
|
||||
: VTextEdit(p_parent),
|
||||
VEditor(p_file, this),
|
||||
m_mdHighlighter(NULL),
|
||||
m_pegHighlighter(NULL),
|
||||
m_freshEdit(true),
|
||||
m_textToHtmlDialog(NULL),
|
||||
m_zoomDelta(0),
|
||||
@ -60,18 +60,17 @@ VMdEditor::VMdEditor(VFile *p_file,
|
||||
|
||||
setReadOnly(true);
|
||||
|
||||
m_mdHighlighter = new HGMarkdownHighlighter(g_config->getMdHighlightingStyles(),
|
||||
m_pegHighlighter = new PegMarkdownHighlighter(document());
|
||||
m_pegHighlighter->init(g_config->getMdHighlightingStyles(),
|
||||
g_config->getCodeBlockStyles(),
|
||||
g_config->getMarkdownHighlightInterval(),
|
||||
document());
|
||||
m_mdHighlighter->setMathjaxEnabled(g_config->getEnableMathjax());
|
||||
|
||||
connect(m_mdHighlighter, &HGMarkdownHighlighter::headersUpdated,
|
||||
g_config->getEnableMathjax(),
|
||||
g_config->getMarkdownHighlightInterval());
|
||||
connect(m_pegHighlighter, &PegMarkdownHighlighter::headersUpdated,
|
||||
this, &VMdEditor::updateHeaders);
|
||||
|
||||
// After highlight, the cursor may trun into non-visible. We should make it visible
|
||||
// in this case.
|
||||
connect(m_mdHighlighter, &HGMarkdownHighlighter::highlightCompleted,
|
||||
connect(m_pegHighlighter, &PegMarkdownHighlighter::highlightCompleted,
|
||||
this, [this]() {
|
||||
makeBlockVisible(textCursor().block());
|
||||
|
||||
@ -81,15 +80,15 @@ VMdEditor::VMdEditor(VFile *p_file,
|
||||
}
|
||||
});
|
||||
|
||||
m_cbHighlighter = new VCodeBlockHighlightHelper(m_mdHighlighter,
|
||||
m_cbHighlighter = new VCodeBlockHighlightHelper(m_pegHighlighter,
|
||||
p_doc,
|
||||
p_type);
|
||||
|
||||
m_previewMgr = new VPreviewManager(this, m_mdHighlighter);
|
||||
connect(m_mdHighlighter, &HGMarkdownHighlighter::imageLinksUpdated,
|
||||
m_previewMgr = new VPreviewManager(this, m_pegHighlighter);
|
||||
connect(m_pegHighlighter, &PegMarkdownHighlighter::imageLinksUpdated,
|
||||
m_previewMgr, &VPreviewManager::updateImageLinks);
|
||||
connect(m_previewMgr, &VPreviewManager::requestUpdateImageLinks,
|
||||
m_mdHighlighter, &HGMarkdownHighlighter::updateHighlight);
|
||||
m_pegHighlighter, &PegMarkdownHighlighter::updateHighlight);
|
||||
|
||||
m_editOps = new VMdEditOperations(this, m_file);
|
||||
connect(m_editOps, &VEditOperations::statusMessage,
|
||||
@ -132,10 +131,10 @@ void VMdEditor::beginEdit()
|
||||
emit statusChanged();
|
||||
|
||||
if (m_freshEdit) {
|
||||
m_mdHighlighter->updateHighlight();
|
||||
m_pegHighlighter->updateHighlight();
|
||||
relayout();
|
||||
} else {
|
||||
updateHeaders(m_mdHighlighter->getHeaderRegions());
|
||||
updateHeaders(m_pegHighlighter->getHeaderRegions());
|
||||
}
|
||||
}
|
||||
|
||||
@ -170,7 +169,7 @@ void VMdEditor::reloadFile()
|
||||
const QString &content = m_file->getContent();
|
||||
setPlainText(content);
|
||||
setModified(false);
|
||||
m_mdHighlighter->updateHighlightFast();
|
||||
m_pegHighlighter->updateHighlightFast();
|
||||
|
||||
m_freshEdit = true;
|
||||
|
||||
@ -461,7 +460,7 @@ static void insertSequenceToHeader(QTextCursor& p_cursor,
|
||||
|
||||
void VMdEditor::updateHeaderSequenceByConfigChange()
|
||||
{
|
||||
updateHeadersHelper(m_mdHighlighter->getHeaderRegions(), true);
|
||||
updateHeadersHelper(m_pegHighlighter->getHeaderRegions(), true);
|
||||
}
|
||||
|
||||
void VMdEditor::updateHeadersHelper(const QVector<VElementRegion> &p_headerRegions, bool p_configChanged)
|
||||
@ -493,8 +492,7 @@ void VMdEditor::updateHeadersHelper(const QVector<VElementRegion> &p_headerRegio
|
||||
<< block.text();
|
||||
}
|
||||
|
||||
if ((block.userState() == HighlightBlockState::Header)
|
||||
&& headerReg.exactMatch(block.text())) {
|
||||
if (headerReg.exactMatch(block.text())) {
|
||||
int level = headerReg.cap(1).length();
|
||||
VTableOfContentItem header(headerReg.cap(2).trimmed(),
|
||||
level,
|
||||
@ -1199,7 +1197,7 @@ void VMdEditor::zoomPage(bool p_zoomIn, int p_range)
|
||||
|
||||
m_zoomDelta += delta;
|
||||
|
||||
QVector<HighlightingStyle> &styles = m_mdHighlighter->getHighlightingStyles();
|
||||
QVector<HighlightingStyle> &styles = m_pegHighlighter->getStyles();
|
||||
for (auto & it : styles) {
|
||||
int size = it.format.fontPointSize();
|
||||
if (size == 0) {
|
||||
@ -1215,7 +1213,7 @@ void VMdEditor::zoomPage(bool p_zoomIn, int p_range)
|
||||
it.format.setFontPointSize(size);
|
||||
}
|
||||
|
||||
QHash<QString, QTextCharFormat> &cbStyles = m_mdHighlighter->getCodeBlockStyles();
|
||||
QHash<QString, QTextCharFormat> &cbStyles = m_pegHighlighter->getCodeBlockStyles();
|
||||
for (auto it = cbStyles.begin(); it != cbStyles.end(); ++it) {
|
||||
int size = it.value().fontPointSize();
|
||||
if (size == 0) {
|
||||
@ -1231,7 +1229,7 @@ void VMdEditor::zoomPage(bool p_zoomIn, int p_range)
|
||||
it.value().setFontPointSize(size);
|
||||
}
|
||||
|
||||
m_mdHighlighter->rehighlight();
|
||||
m_pegHighlighter->rehighlight();
|
||||
}
|
||||
|
||||
void VMdEditor::initCopyAsMenu(QAction *p_before, QMenu *p_menu)
|
||||
|
@ -14,7 +14,7 @@
|
||||
#include "vconfigmanager.h"
|
||||
#include "utils/vutils.h"
|
||||
|
||||
class HGMarkdownHighlighter;
|
||||
class PegMarkdownHighlighter;
|
||||
class VCodeBlockHighlightHelper;
|
||||
class VDocument;
|
||||
class VPreviewManager;
|
||||
@ -72,7 +72,7 @@ public:
|
||||
|
||||
void setEditTab(VEditTab *p_editTab);
|
||||
|
||||
HGMarkdownHighlighter *getMarkdownHighlighter() const;
|
||||
PegMarkdownHighlighter *getMarkdownHighlighter() const;
|
||||
|
||||
VPreviewManager *getPreviewManager() const;
|
||||
|
||||
@ -256,7 +256,7 @@ private:
|
||||
|
||||
void insertImageLink(const QString &p_text, const QString &p_url);
|
||||
|
||||
HGMarkdownHighlighter *m_mdHighlighter;
|
||||
PegMarkdownHighlighter *m_pegHighlighter;
|
||||
|
||||
VCodeBlockHighlightHelper *m_cbHighlighter;
|
||||
|
||||
@ -282,9 +282,9 @@ private:
|
||||
int m_copyTimeStamp;
|
||||
};
|
||||
|
||||
inline HGMarkdownHighlighter *VMdEditor::getMarkdownHighlighter() const
|
||||
inline PegMarkdownHighlighter *VMdEditor::getMarkdownHighlighter() const
|
||||
{
|
||||
return m_mdHighlighter;
|
||||
return m_pegHighlighter;
|
||||
}
|
||||
|
||||
inline VPreviewManager *VMdEditor::getPreviewManager() const
|
||||
|
@ -8,7 +8,7 @@
|
||||
#include "vnote.h"
|
||||
#include "utils/vutils.h"
|
||||
#include "vpreviewpage.h"
|
||||
#include "hgmarkdownhighlighter.h"
|
||||
#include "pegmarkdownhighlighter.h"
|
||||
#include "vconfigmanager.h"
|
||||
#include "vmarkdownconverter.h"
|
||||
#include "vnotebook.h"
|
||||
@ -535,7 +535,7 @@ void VMdTab::setupMarkdownEditor()
|
||||
m_splitter->insertWidget(0, m_editor);
|
||||
|
||||
m_livePreviewHelper = new VLivePreviewHelper(m_editor, m_document, this);
|
||||
connect(m_editor->getMarkdownHighlighter(), &HGMarkdownHighlighter::codeBlocksUpdated,
|
||||
connect(m_editor->getMarkdownHighlighter(), &PegMarkdownHighlighter::codeBlocksUpdated,
|
||||
m_livePreviewHelper, &VLivePreviewHelper::updateCodeBlocks);
|
||||
connect(m_editor->getPreviewManager(), &VPreviewManager::previewEnabledChanged,
|
||||
m_livePreviewHelper, &VLivePreviewHelper::setInplacePreviewEnabled);
|
||||
@ -546,7 +546,7 @@ void VMdTab::setupMarkdownEditor()
|
||||
m_livePreviewHelper->setInplacePreviewEnabled(m_editor->getPreviewManager()->isPreviewEnabled());
|
||||
|
||||
m_mathjaxPreviewHelper = new VMathJaxInplacePreviewHelper(m_editor, m_document, this);
|
||||
connect(m_editor->getMarkdownHighlighter(), &HGMarkdownHighlighter::mathjaxBlocksUpdated,
|
||||
connect(m_editor->getMarkdownHighlighter(), &PegMarkdownHighlighter::mathjaxBlocksUpdated,
|
||||
m_mathjaxPreviewHelper, &VMathJaxInplacePreviewHelper::updateMathjaxBlocks);
|
||||
connect(m_editor->getPreviewManager(), &VPreviewManager::previewEnabledChanged,
|
||||
m_mathjaxPreviewHelper, &VMathJaxInplacePreviewHelper::setEnabled);
|
||||
|
@ -10,11 +10,11 @@
|
||||
#include "vconfigmanager.h"
|
||||
#include "utils/vutils.h"
|
||||
#include "vdownloader.h"
|
||||
#include "hgmarkdownhighlighter.h"
|
||||
#include "pegmarkdownhighlighter.h"
|
||||
|
||||
extern VConfigManager *g_config;
|
||||
|
||||
VPreviewManager::VPreviewManager(VMdEditor *p_editor, HGMarkdownHighlighter *p_highlighter)
|
||||
VPreviewManager::VPreviewManager(VMdEditor *p_editor, PegMarkdownHighlighter *p_highlighter)
|
||||
: QObject(p_editor),
|
||||
m_editor(p_editor),
|
||||
m_document(p_editor->document()),
|
||||
|
@ -8,7 +8,7 @@
|
||||
#include <QVector>
|
||||
#include <QSharedPointer>
|
||||
|
||||
#include "hgmarkdownhighlighter.h"
|
||||
#include "markdownhighlighterdata.h"
|
||||
#include "vmdeditor.h"
|
||||
#include "vtextblockdata.h"
|
||||
|
||||
@ -59,7 +59,7 @@ class VPreviewManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
VPreviewManager(VMdEditor *p_editor, HGMarkdownHighlighter *p_highlighter);
|
||||
VPreviewManager(VMdEditor *p_editor, PegMarkdownHighlighter *p_highlighter);
|
||||
|
||||
void setPreviewEnabled(bool p_enabled);
|
||||
|
||||
@ -215,7 +215,7 @@ private:
|
||||
|
||||
QTextDocument *m_document;
|
||||
|
||||
HGMarkdownHighlighter *m_highlighter;
|
||||
PegMarkdownHighlighter *m_highlighter;
|
||||
|
||||
VDownloader *m_downloader;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user