mirror of
https://gitee.com/vnotex/vnote.git
synced 2025-07-05 13:59:52 +08:00
234 lines
6.5 KiB
C++
234 lines
6.5 KiB
C++
/* PEG Markdown Highlight
|
|
* Copyright 2011-2016 Ali Rantakari -- http://hasseg.org
|
|
* Licensed under the GPL2+ and MIT licenses (see LICENSE for more info).
|
|
*
|
|
* highlighter.cpp
|
|
*
|
|
* Qt 4.7 example for highlighting a rich text widget.
|
|
*/
|
|
|
|
#include <QtGui>
|
|
#include <QtDebug>
|
|
#include "hgmarkdownhighlighter.h"
|
|
|
|
const int HGMarkdownHighlighter::initCapacity = 1024;
|
|
|
|
void HGMarkdownHighlighter::resizeBuffer(int newCap)
|
|
{
|
|
if (newCap == capacity) {
|
|
return;
|
|
}
|
|
if (capacity > 0) {
|
|
Q_ASSERT(content);
|
|
delete [] content;
|
|
}
|
|
capacity = newCap;
|
|
content = new char [capacity];
|
|
}
|
|
|
|
// Will be freeed by parent automatically
|
|
HGMarkdownHighlighter::HGMarkdownHighlighter(const QVector<HighlightingStyle> &styles, int waitInterval,
|
|
QTextDocument *parent)
|
|
: QSyntaxHighlighter(parent), parsing(0),
|
|
waitInterval(waitInterval), content(NULL), capacity(0), result(NULL)
|
|
{
|
|
codeBlockStartExp = QRegExp("^```");
|
|
codeBlockEndExp = QRegExp("^```$");
|
|
codeBlockFormat.setForeground(QBrush(Qt::darkYellow));
|
|
for (int index = 0; index < styles.size(); ++index) {
|
|
if (styles[index].type == pmh_VERBATIM) {
|
|
codeBlockFormat = styles[index].format;
|
|
break;
|
|
}
|
|
}
|
|
|
|
resizeBuffer(initCapacity);
|
|
setStyles(styles);
|
|
document = parent;
|
|
timer = new QTimer(this);
|
|
timer->setSingleShot(true);
|
|
timer->setInterval(this->waitInterval);
|
|
connect(timer, &QTimer::timeout, this, &HGMarkdownHighlighter::timerTimeout);
|
|
connect(document, &QTextDocument::contentsChange,
|
|
this, &HGMarkdownHighlighter::handleContentChange);
|
|
}
|
|
|
|
HGMarkdownHighlighter::~HGMarkdownHighlighter()
|
|
{
|
|
if (result) {
|
|
pmh_free_elements(result);
|
|
result = NULL;
|
|
}
|
|
if (content) {
|
|
delete [] content;
|
|
capacity = 0;
|
|
content = NULL;
|
|
}
|
|
}
|
|
|
|
void HGMarkdownHighlighter::highlightBlock(const QString &text)
|
|
{
|
|
int blockNum = currentBlock().blockNumber();
|
|
if (!parsing && blockHighlights.size() > blockNum) {
|
|
QVector<HLUnit> &units = blockHighlights[blockNum];
|
|
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);
|
|
}
|
|
}
|
|
setCurrentBlockState(0);
|
|
highlightCodeBlock(text);
|
|
}
|
|
|
|
void HGMarkdownHighlighter::setStyles(const QVector<HighlightingStyle> &styles)
|
|
{
|
|
this->highlightingStyles = styles;
|
|
}
|
|
|
|
void HGMarkdownHighlighter::initBlockHighlightFromResult(int nrBlocks)
|
|
{
|
|
blockHighlights.resize(nrBlocks);
|
|
for (int i = 0; i < blockHighlights.size(); ++i) {
|
|
blockHighlights[i].clear();
|
|
}
|
|
|
|
if (!result) {
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < highlightingStyles.size(); i++)
|
|
{
|
|
const HighlightingStyle &style = highlightingStyles[i];
|
|
pmh_element *elem_cursor = result[style.type];
|
|
while (elem_cursor != NULL)
|
|
{
|
|
if (elem_cursor->end <= elem_cursor->pos) {
|
|
elem_cursor = elem_cursor->next;
|
|
continue;
|
|
}
|
|
initBlockHighlihgtOne(elem_cursor->pos, elem_cursor->end, i);
|
|
elem_cursor = elem_cursor->next;
|
|
}
|
|
}
|
|
|
|
pmh_free_elements(result);
|
|
result = NULL;
|
|
}
|
|
|
|
void HGMarkdownHighlighter::initBlockHighlihgtOne(unsigned long pos, unsigned long end, int styleIndex)
|
|
{
|
|
int startBlockNum = document->findBlock(pos).blockNumber();
|
|
int endBlockNum = document->findBlock(end).blockNumber();
|
|
for (int i = startBlockNum; i <= endBlockNum; ++i)
|
|
{
|
|
QTextBlock block = document->findBlockByNumber(i);
|
|
int blockStartPos = block.position();
|
|
HLUnit unit;
|
|
if (i == startBlockNum) {
|
|
unit.start = pos - blockStartPos;
|
|
unit.length = (startBlockNum == endBlockNum) ?
|
|
(end - pos) : (block.length() - unit.start);
|
|
} else if (i == endBlockNum) {
|
|
unit.start = 0;
|
|
unit.length = end - blockStartPos;
|
|
} else {
|
|
unit.start = 0;
|
|
unit.length = block.length();
|
|
}
|
|
unit.styleIndex = styleIndex;
|
|
|
|
blockHighlights[i].append(unit);
|
|
}
|
|
}
|
|
|
|
void HGMarkdownHighlighter::highlightCodeBlock(const QString &text)
|
|
{
|
|
int nextIndex = 0;
|
|
int startIndex = 0;
|
|
if (previousBlockState() != 1) {
|
|
startIndex = codeBlockStartExp.indexIn(text);
|
|
if (startIndex >= 0) {
|
|
nextIndex = startIndex + codeBlockStartExp.matchedLength();
|
|
} else {
|
|
nextIndex = -1;
|
|
}
|
|
}
|
|
|
|
while (nextIndex >= 0) {
|
|
int endIndex = codeBlockEndExp.indexIn(text, nextIndex);
|
|
int codeBlockLength;
|
|
if (endIndex == -1) {
|
|
setCurrentBlockState(1);
|
|
codeBlockLength = text.length() - startIndex;
|
|
} else {
|
|
codeBlockLength = endIndex - startIndex + codeBlockEndExp.matchedLength();
|
|
}
|
|
setFormat(startIndex, codeBlockLength, codeBlockFormat);
|
|
startIndex = codeBlockStartExp.indexIn(text, startIndex + codeBlockLength);
|
|
if (startIndex >= 0) {
|
|
nextIndex = startIndex + codeBlockStartExp.matchedLength();
|
|
} else {
|
|
nextIndex = -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
void HGMarkdownHighlighter::parse()
|
|
{
|
|
if (!parsing.testAndSetRelaxed(0, 1)) {
|
|
return;
|
|
}
|
|
|
|
int nrBlocks = document->blockCount();
|
|
parseInternal();
|
|
|
|
if (highlightingStyles.isEmpty()) {
|
|
qWarning() << "error: HighlightingStyles is not set";
|
|
return;
|
|
}
|
|
initBlockHighlightFromResult(nrBlocks);
|
|
parsing.store(0);
|
|
}
|
|
|
|
void HGMarkdownHighlighter::parseInternal()
|
|
{
|
|
QString text = document->toPlainText();
|
|
QByteArray ba = text.toUtf8();
|
|
const char *data = (const char *)ba.data();
|
|
int len = ba.size();
|
|
|
|
if (result) {
|
|
pmh_free_elements(result);
|
|
result = NULL;
|
|
}
|
|
|
|
if (len == 0) {
|
|
return;
|
|
} else if (len >= capacity) {
|
|
resizeBuffer(qMax(2 * capacity, len * 2));
|
|
} else if (len < (capacity >> 2)) {
|
|
resizeBuffer(qMax(capacity >> 1, len * 2));
|
|
}
|
|
|
|
memcpy(content, data, len);
|
|
content[len] = '\0';
|
|
|
|
pmh_markdown_to_elements(content, pmh_EXT_NONE, &result);
|
|
}
|
|
|
|
void HGMarkdownHighlighter::handleContentChange(int position, int charsRemoved, int charsAdded)
|
|
{
|
|
if (charsRemoved == 0 && charsAdded == 0) {
|
|
return;
|
|
}
|
|
timer->stop();
|
|
timer->start();
|
|
}
|
|
|
|
void HGMarkdownHighlighter::timerTimeout()
|
|
{
|
|
parse();
|
|
rehighlight();
|
|
}
|