mirror of
https://gitee.com/vnotex/vnote.git
synced 2025-07-05 13:59:52 +08:00
support code block syntax highlihgt in edit mode
In edit mode, highlight code blocks via parsing the result of HighlightJS. We only highlight fenced code blocks by token-matching. Support custom style in MDHL file.
This commit is contained in:
parent
f1c101b1d8
commit
44257913f7
@ -1,6 +1,11 @@
|
|||||||
#include <QtGui>
|
#include <QtGui>
|
||||||
#include <QtDebug>
|
#include <QtDebug>
|
||||||
|
#include <QTextCursor>
|
||||||
|
#include <algorithm>
|
||||||
#include "hgmarkdownhighlighter.h"
|
#include "hgmarkdownhighlighter.h"
|
||||||
|
#include "vconfigmanager.h"
|
||||||
|
|
||||||
|
extern VConfigManager vconfig;
|
||||||
|
|
||||||
const int HGMarkdownHighlighter::initCapacity = 1024;
|
const int HGMarkdownHighlighter::initCapacity = 1024;
|
||||||
|
|
||||||
@ -18,13 +23,16 @@ void HGMarkdownHighlighter::resizeBuffer(int newCap)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Will be freeed by parent automatically
|
// Will be freeed by parent automatically
|
||||||
HGMarkdownHighlighter::HGMarkdownHighlighter(const QVector<HighlightingStyle> &styles, int waitInterval,
|
HGMarkdownHighlighter::HGMarkdownHighlighter(const QVector<HighlightingStyle> &styles,
|
||||||
|
const QMap<QString, QTextCharFormat> &codeBlockStyles,
|
||||||
|
int waitInterval,
|
||||||
QTextDocument *parent)
|
QTextDocument *parent)
|
||||||
: QSyntaxHighlighter(parent), parsing(0),
|
: QSyntaxHighlighter(parent), highlightingStyles(styles),
|
||||||
waitInterval(waitInterval), content(NULL), capacity(0), result(NULL)
|
m_codeBlockStyles(codeBlockStyles), m_numOfCodeBlockHighlightsToRecv(0),
|
||||||
|
parsing(0), waitInterval(waitInterval), content(NULL), capacity(0), result(NULL)
|
||||||
{
|
{
|
||||||
codeBlockStartExp = QRegExp("^(\\s)*```");
|
codeBlockStartExp = QRegExp("^\\s*```(\\S*)");
|
||||||
codeBlockEndExp = QRegExp("^(\\s)*```$");
|
codeBlockEndExp = QRegExp("^\\s*```$");
|
||||||
codeBlockFormat.setForeground(QBrush(Qt::darkYellow));
|
codeBlockFormat.setForeground(QBrush(Qt::darkYellow));
|
||||||
for (int index = 0; index < styles.size(); ++index) {
|
for (int index = 0; index < styles.size(); ++index) {
|
||||||
const pmh_element_type &eleType = styles[index].type;
|
const pmh_element_type &eleType = styles[index].type;
|
||||||
@ -38,7 +46,6 @@ HGMarkdownHighlighter::HGMarkdownHighlighter(const QVector<HighlightingStyle> &s
|
|||||||
}
|
}
|
||||||
|
|
||||||
resizeBuffer(initCapacity);
|
resizeBuffer(initCapacity);
|
||||||
setStyles(styles);
|
|
||||||
document = parent;
|
document = parent;
|
||||||
timer = new QTimer(this);
|
timer = new QTimer(this);
|
||||||
timer->setSingleShot(true);
|
timer->setSingleShot(true);
|
||||||
@ -65,10 +72,10 @@ void HGMarkdownHighlighter::highlightBlock(const QString &text)
|
|||||||
{
|
{
|
||||||
int blockNum = currentBlock().blockNumber();
|
int blockNum = currentBlock().blockNumber();
|
||||||
if (!parsing && blockHighlights.size() > blockNum) {
|
if (!parsing && blockHighlights.size() > blockNum) {
|
||||||
QVector<HLUnit> &units = blockHighlights[blockNum];
|
const QVector<HLUnit> &units = blockHighlights[blockNum];
|
||||||
for (int i = 0; i < units.size(); ++i) {
|
for (int i = 0; i < units.size(); ++i) {
|
||||||
// TODO: merge two format within the same range
|
// 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);
|
setFormat(unit.start, unit.length, highlightingStyles[unit.styleIndex].format);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -82,11 +89,40 @@ void HGMarkdownHighlighter::highlightBlock(const QString &text)
|
|||||||
|
|
||||||
// PEG Markdown Highlight does not handle links with spaces in the URL.
|
// PEG Markdown Highlight does not handle links with spaces in the URL.
|
||||||
highlightLinkWithSpacesInURL(text);
|
highlightLinkWithSpacesInURL(text);
|
||||||
}
|
|
||||||
|
|
||||||
void HGMarkdownHighlighter::setStyles(const QVector<HighlightingStyle> &styles)
|
// Highlight CodeBlock using VCodeBlockHighlightHelper.
|
||||||
{
|
if (m_codeBlockHighlights.size() > blockNum) {
|
||||||
this->highlightingStyles = styles;
|
const QVector<HLUnitStyle> &units = m_codeBlockHighlights[blockNum];
|
||||||
|
// 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);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void HGMarkdownHighlighter::initBlockHighlightFromResult(int nrBlocks)
|
void HGMarkdownHighlighter::initBlockHighlightFromResult(int nrBlocks)
|
||||||
@ -286,7 +322,9 @@ void HGMarkdownHighlighter::handleContentChange(int /* position */, int charsRem
|
|||||||
void HGMarkdownHighlighter::timerTimeout()
|
void HGMarkdownHighlighter::timerTimeout()
|
||||||
{
|
{
|
||||||
parse();
|
parse();
|
||||||
|
if (!updateCodeBlocks()) {
|
||||||
rehighlight();
|
rehighlight();
|
||||||
|
}
|
||||||
emit highlightCompleted();
|
emit highlightCompleted();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -295,3 +333,122 @@ void HGMarkdownHighlighter::updateHighlight()
|
|||||||
timer->stop();
|
timer->stop();
|
||||||
timerTimeout();
|
timerTimeout();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool HGMarkdownHighlighter::updateCodeBlocks()
|
||||||
|
{
|
||||||
|
if (!vconfig.getEnableCodeBlockHighlight()) {
|
||||||
|
m_codeBlockHighlights.clear();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_codeBlockHighlights.resize(document->blockCount());
|
||||||
|
for (int i = 0; i < m_codeBlockHighlights.size(); ++i) {
|
||||||
|
m_codeBlockHighlights[i].clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<VCodeBlock> codeBlocks;
|
||||||
|
|
||||||
|
VCodeBlock item;
|
||||||
|
bool inBlock = false;
|
||||||
|
|
||||||
|
// Only handle complete codeblocks.
|
||||||
|
QTextBlock block = document->firstBlock();
|
||||||
|
while (block.isValid()) {
|
||||||
|
QString text = block.text();
|
||||||
|
if (inBlock) {
|
||||||
|
item.m_text = item.m_text + "\n" + text;
|
||||||
|
int idx = codeBlockEndExp.indexIn(text);
|
||||||
|
if (idx >= 0) {
|
||||||
|
// End block.
|
||||||
|
inBlock = false;
|
||||||
|
item.m_endBlock = block.blockNumber();
|
||||||
|
codeBlocks.append(item);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
int idx = codeBlockStartExp.indexIn(text);
|
||||||
|
if (idx >= 0) {
|
||||||
|
// Start block.
|
||||||
|
inBlock = true;
|
||||||
|
item.m_startBlock = block.blockNumber();
|
||||||
|
item.m_startPos = block.position();
|
||||||
|
item.m_text = text;
|
||||||
|
if (codeBlockStartExp.captureCount() == 1) {
|
||||||
|
item.m_lang = codeBlockStartExp.capturedTexts()[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
block = block.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
m_numOfCodeBlockHighlightsToRecv = codeBlocks.size();
|
||||||
|
if (m_numOfCodeBlockHighlightsToRecv > 0) {
|
||||||
|
emit codeBlocksUpdated(codeBlocks);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool HLUnitStyleComp(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 HGMarkdownHighlighter::setCodeBlockHighlights(const QList<HLUnitPos> &p_units)
|
||||||
|
{
|
||||||
|
if (p_units.isEmpty()) {
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
QVector<QVector<HLUnitStyle>> highlights(m_codeBlockHighlights.size());
|
||||||
|
|
||||||
|
for (auto const &unit : p_units) {
|
||||||
|
int pos = unit.m_position;
|
||||||
|
int end = unit.m_position + unit.m_length;
|
||||||
|
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();
|
||||||
|
HLUnitStyle hl;
|
||||||
|
hl.style = unit.m_style;
|
||||||
|
if (i == startBlockNum) {
|
||||||
|
hl.start = pos - blockStartPos;
|
||||||
|
hl.length = (startBlockNum == endBlockNum) ?
|
||||||
|
(end - pos) : (block.length() - hl.start);
|
||||||
|
} else if (i == endBlockNum) {
|
||||||
|
hl.start = 0;
|
||||||
|
hl.length = end - blockStartPos;
|
||||||
|
} else {
|
||||||
|
hl.start = 0;
|
||||||
|
hl.length = block.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
highlights[i].append(hl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Need to highlight in order.
|
||||||
|
for (int i = 0; i < highlights.size(); ++i) {
|
||||||
|
QVector<HLUnitStyle> &units = highlights[i];
|
||||||
|
if (!units.isEmpty()) {
|
||||||
|
std::sort(units.begin(), units.end(), HLUnitStyleComp);
|
||||||
|
m_codeBlockHighlights[i].append(units);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exit:
|
||||||
|
--m_numOfCodeBlockHighlightsToRecv;
|
||||||
|
if (m_numOfCodeBlockHighlightsToRecv <= 0) {
|
||||||
|
rehighlight();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -5,6 +5,9 @@
|
|||||||
#include <QSyntaxHighlighter>
|
#include <QSyntaxHighlighter>
|
||||||
#include <QAtomicInt>
|
#include <QAtomicInt>
|
||||||
#include <QSet>
|
#include <QSet>
|
||||||
|
#include <QList>
|
||||||
|
#include <QString>
|
||||||
|
#include <QMap>
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include <pmh_parser.h>
|
#include <pmh_parser.h>
|
||||||
@ -38,25 +41,65 @@ struct HLUnit
|
|||||||
unsigned int styleIndex;
|
unsigned int styleIndex;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct HLUnitStyle
|
||||||
|
{
|
||||||
|
unsigned long start;
|
||||||
|
unsigned long length;
|
||||||
|
QString style;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Fenced code block only.
|
||||||
|
struct VCodeBlock
|
||||||
|
{
|
||||||
|
int m_startPos;
|
||||||
|
int m_startBlock;
|
||||||
|
int m_endBlock;
|
||||||
|
QString m_lang;
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
class HGMarkdownHighlighter : public QSyntaxHighlighter
|
class HGMarkdownHighlighter : public QSyntaxHighlighter
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
HGMarkdownHighlighter(const QVector<HighlightingStyle> &styles, int waitInterval,
|
HGMarkdownHighlighter(const QVector<HighlightingStyle> &styles,
|
||||||
|
const QMap<QString, QTextCharFormat> &codeBlockStyles,
|
||||||
|
int waitInterval,
|
||||||
QTextDocument *parent = 0);
|
QTextDocument *parent = 0);
|
||||||
~HGMarkdownHighlighter();
|
~HGMarkdownHighlighter();
|
||||||
void setStyles(const QVector<HighlightingStyle> &styles);
|
|
||||||
// Request to update highlihgt (re-parse and re-highlight)
|
// Request to update highlihgt (re-parse and re-highlight)
|
||||||
void updateHighlight();
|
void setCodeBlockHighlights(const QList<HLUnitPos> &p_units);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void highlightCompleted();
|
void highlightCompleted();
|
||||||
void imageBlocksUpdated(QSet<int> p_blocks);
|
void imageBlocksUpdated(QSet<int> p_blocks);
|
||||||
|
void codeBlocksUpdated(const QList<VCodeBlock> &p_codeBlocks);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void highlightBlock(const QString &text) Q_DECL_OVERRIDE;
|
void highlightBlock(const QString &text) Q_DECL_OVERRIDE;
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void updateHighlight();
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void handleContentChange(int position, int charsRemoved, int charsAdded);
|
void handleContentChange(int position, int charsRemoved, int charsAdded);
|
||||||
void timerTimeout();
|
void timerTimeout();
|
||||||
@ -70,7 +113,17 @@ private:
|
|||||||
|
|
||||||
QTextDocument *document;
|
QTextDocument *document;
|
||||||
QVector<HighlightingStyle> highlightingStyles;
|
QVector<HighlightingStyle> highlightingStyles;
|
||||||
|
QMap<QString, QTextCharFormat> m_codeBlockStyles;
|
||||||
QVector<QVector<HLUnit> > blockHighlights;
|
QVector<QVector<HLUnit> > blockHighlights;
|
||||||
|
|
||||||
|
// 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_codeBlockHighlights;
|
||||||
|
|
||||||
|
int m_numOfCodeBlockHighlightsToRecv;
|
||||||
|
|
||||||
// Block numbers containing image link(s).
|
// Block numbers containing image link(s).
|
||||||
QSet<int> imageBlocks;
|
QSet<int> imageBlocks;
|
||||||
QAtomicInt parsing;
|
QAtomicInt parsing;
|
||||||
@ -92,6 +145,9 @@ private:
|
|||||||
void initBlockHighlihgtOne(unsigned long pos, unsigned long end,
|
void initBlockHighlihgtOne(unsigned long pos, unsigned long end,
|
||||||
int styleIndex);
|
int styleIndex);
|
||||||
void updateImageBlocks();
|
void updateImageBlocks();
|
||||||
|
// Return true if there are fenced code blocks and it will call rehighlight() later.
|
||||||
|
// Return false if there is none.
|
||||||
|
bool updateCodeBlocks();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -1,5 +1,12 @@
|
|||||||
var placeholder = document.getElementById('placeholder');
|
var placeholder = document.getElementById('placeholder');
|
||||||
|
|
||||||
|
// Use Marked to highlight code blocks.
|
||||||
|
marked.setOptions({
|
||||||
|
highlight: function(code) {
|
||||||
|
return hljs.highlightAuto(code).value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
var updateHtml = function(html) {
|
var updateHtml = function(html) {
|
||||||
placeholder.innerHTML = html;
|
placeholder.innerHTML = html;
|
||||||
var codes = document.getElementsByTagName('code');
|
var codes = document.getElementsByTagName('code');
|
||||||
@ -45,3 +52,8 @@ var updateHtml = function(html) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var highlightText = function(text, id, timeStamp) {
|
||||||
|
var html = marked(text);
|
||||||
|
content.highlightTextCB(html, id, timeStamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -178,3 +178,8 @@ var updateText = function(text) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var highlightText = function(text, id, timeStamp) {
|
||||||
|
var html = mdit.render(text);
|
||||||
|
content.highlightTextCB(html, id, timeStamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -1,20 +1,6 @@
|
|||||||
var content;
|
var content;
|
||||||
var keyState = 0;
|
var keyState = 0;
|
||||||
|
|
||||||
new QWebChannel(qt.webChannelTransport,
|
|
||||||
function(channel) {
|
|
||||||
content = channel.objects.content;
|
|
||||||
if (typeof updateHtml == "function") {
|
|
||||||
updateHtml(content.html);
|
|
||||||
content.htmlChanged.connect(updateHtml);
|
|
||||||
}
|
|
||||||
if (typeof updateText == "function") {
|
|
||||||
content.textChanged.connect(updateText);
|
|
||||||
content.updateText();
|
|
||||||
}
|
|
||||||
content.requestScrollToAnchor.connect(scrollToAnchor);
|
|
||||||
});
|
|
||||||
|
|
||||||
var VMermaidDivClass = 'mermaid-diagram';
|
var VMermaidDivClass = 'mermaid-diagram';
|
||||||
if (typeof VEnableMermaid == 'undefined') {
|
if (typeof VEnableMermaid == 'undefined') {
|
||||||
VEnableMermaid = false;
|
VEnableMermaid = false;
|
||||||
@ -28,6 +14,25 @@ if (typeof VEnableMathjax == 'undefined') {
|
|||||||
VEnableMathjax = false;
|
VEnableMathjax = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
new QWebChannel(qt.webChannelTransport,
|
||||||
|
function(channel) {
|
||||||
|
content = channel.objects.content;
|
||||||
|
if (typeof updateHtml == "function") {
|
||||||
|
updateHtml(content.html);
|
||||||
|
content.htmlChanged.connect(updateHtml);
|
||||||
|
}
|
||||||
|
if (typeof updateText == "function") {
|
||||||
|
content.textChanged.connect(updateText);
|
||||||
|
content.updateText();
|
||||||
|
}
|
||||||
|
content.requestScrollToAnchor.connect(scrollToAnchor);
|
||||||
|
|
||||||
|
if (typeof highlightText == "function") {
|
||||||
|
content.requestHighlightText.connect(highlightText);
|
||||||
|
content.noticeReadyToHighlightText();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
var scrollToAnchor = function(anchor) {
|
var scrollToAnchor = function(anchor) {
|
||||||
var anc = document.getElementById(anchor);
|
var anc = document.getElementById(anchor);
|
||||||
if (anc != null) {
|
if (anc != null) {
|
||||||
|
@ -131,3 +131,8 @@ var updateText = function(text) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var highlightText = function(text, id, timeStamp) {
|
||||||
|
var html = marked(text);
|
||||||
|
content.highlightTextCB(html, id, timeStamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -90,8 +90,43 @@ COMMENT
|
|||||||
foreground: 93a1a1
|
foreground: 93a1a1
|
||||||
|
|
||||||
VERBATIM
|
VERBATIM
|
||||||
foreground: 551A8B
|
foreground: 551a8b
|
||||||
font-family: Consolas, Monaco, Andale Mono, Monospace, Courier New
|
font-family: Consolas, Monaco, Andale Mono, Monospace, Courier New
|
||||||
|
# Codeblock sylte from HighlightJS (bold, italic, underlined, color)
|
||||||
|
# The last occurence of the same attribute takes effect
|
||||||
|
hljs-comment: 888888
|
||||||
|
hljs-keyword: bold
|
||||||
|
hljs-attribute: bold
|
||||||
|
hljs-selector-tag: bold
|
||||||
|
hljs-meta-keyword: bold
|
||||||
|
hljs-doctag: bold
|
||||||
|
hljs-name: bold
|
||||||
|
hljs-type: 880000
|
||||||
|
hljs-string: 880000
|
||||||
|
hljs-number: 880000
|
||||||
|
hljs-selector-id: 880000
|
||||||
|
hljs-selector-class: 880000
|
||||||
|
hljs-quote: 880000
|
||||||
|
hljs-template-tag: 880000
|
||||||
|
hljs-deletion: 880000
|
||||||
|
hljs-title: bold, 880000
|
||||||
|
hljs-section: bold, 880000
|
||||||
|
hljs-regexp: bc6060
|
||||||
|
hljs-symbol: bc6060
|
||||||
|
hljs-variable: bc6060
|
||||||
|
hljs-template-variable: bc6060
|
||||||
|
hljs-link: bc6060
|
||||||
|
hljs-selector-attr: bc6060
|
||||||
|
hljs-selector-pseudo: bc6060
|
||||||
|
hljs-literal: 78a960
|
||||||
|
hljs-built_in: 397300
|
||||||
|
hljs-bullet: 397300
|
||||||
|
hljs-code: 397300
|
||||||
|
hljs-addition: 397300
|
||||||
|
hljs-meta: 1f7199
|
||||||
|
hljs-meta-string: 4d99bf
|
||||||
|
hljs-emphasis: italic
|
||||||
|
hljs-strong: bold
|
||||||
|
|
||||||
BLOCKQUOTE
|
BLOCKQUOTE
|
||||||
foreground: 00af00
|
foreground: 00af00
|
||||||
|
@ -19,6 +19,8 @@ enable_mermaid=false
|
|||||||
enable_mathjax=false
|
enable_mathjax=false
|
||||||
; -1 - calculate the factor
|
; -1 - calculate the factor
|
||||||
web_zoom_factor=-1
|
web_zoom_factor=-1
|
||||||
|
; Syntax highlight within code blocks in edit mode
|
||||||
|
enable_code_block_highlight=false
|
||||||
|
|
||||||
[session]
|
[session]
|
||||||
tools_dock_checked=true
|
tools_dock_checked=true
|
||||||
|
@ -58,7 +58,8 @@ SOURCES += main.cpp\
|
|||||||
dialog/vselectdialog.cpp \
|
dialog/vselectdialog.cpp \
|
||||||
vcaptain.cpp \
|
vcaptain.cpp \
|
||||||
vopenedlistmenu.cpp \
|
vopenedlistmenu.cpp \
|
||||||
vorphanfile.cpp
|
vorphanfile.cpp \
|
||||||
|
vcodeblockhighlighthelper.cpp
|
||||||
|
|
||||||
HEADERS += vmainwindow.h \
|
HEADERS += vmainwindow.h \
|
||||||
vdirectorytree.h \
|
vdirectorytree.h \
|
||||||
@ -103,7 +104,8 @@ HEADERS += vmainwindow.h \
|
|||||||
vcaptain.h \
|
vcaptain.h \
|
||||||
vopenedlistmenu.h \
|
vopenedlistmenu.h \
|
||||||
vnavigationmode.h \
|
vnavigationmode.h \
|
||||||
vorphanfile.h
|
vorphanfile.h \
|
||||||
|
vcodeblockhighlighthelper.h
|
||||||
|
|
||||||
RESOURCES += \
|
RESOURCES += \
|
||||||
vnote.qrc \
|
vnote.qrc \
|
||||||
|
235
src/vcodeblockhighlighthelper.cpp
Normal file
235
src/vcodeblockhighlighthelper.cpp
Normal file
@ -0,0 +1,235 @@
|
|||||||
|
#include "vcodeblockhighlighthelper.h"
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QStringList>
|
||||||
|
#include "vdocument.h"
|
||||||
|
#include "utils/vutils.h"
|
||||||
|
|
||||||
|
VCodeBlockHighlightHelper::VCodeBlockHighlightHelper(HGMarkdownHighlighter *p_highlighter,
|
||||||
|
VDocument *p_vdoc,
|
||||||
|
MarkdownConverterType p_type)
|
||||||
|
: QObject(p_highlighter), m_highlighter(p_highlighter), m_vdocument(p_vdoc),
|
||||||
|
m_type(p_type), m_timeStamp(0)
|
||||||
|
{
|
||||||
|
connect(m_highlighter, &HGMarkdownHighlighter::codeBlocksUpdated,
|
||||||
|
this, &VCodeBlockHighlightHelper::handleCodeBlocksUpdated);
|
||||||
|
connect(m_vdocument, &VDocument::textHighlighted,
|
||||||
|
this, &VCodeBlockHighlightHelper::handleTextHighlightResult);
|
||||||
|
connect(m_vdocument, &VDocument::readyToHighlightText,
|
||||||
|
m_highlighter, &HGMarkdownHighlighter::updateHighlight);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString VCodeBlockHighlightHelper::unindentCodeBlock(const QString &p_text)
|
||||||
|
{
|
||||||
|
if (p_text.isEmpty()) {
|
||||||
|
return p_text;
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList lines = p_text.split('\n');
|
||||||
|
V_ASSERT(lines[0].trimmed().startsWith("```"));
|
||||||
|
V_ASSERT(lines.size() > 1);
|
||||||
|
|
||||||
|
QRegExp regExp("(^\\s*)");
|
||||||
|
regExp.indexIn(lines[0]);
|
||||||
|
V_ASSERT(regExp.captureCount() == 1);
|
||||||
|
int nrSpaces = regExp.capturedTexts()[1].size();
|
||||||
|
|
||||||
|
if (nrSpaces == 0) {
|
||||||
|
return p_text;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString res = lines[0].right(lines[0].size() - nrSpaces);
|
||||||
|
for (int i = 1; i < lines.size(); ++i) {
|
||||||
|
const QString &line = lines[i];
|
||||||
|
|
||||||
|
int idx = 0;
|
||||||
|
while (idx < nrSpaces && idx < line.size() && line[idx].isSpace()) {
|
||||||
|
++idx;
|
||||||
|
}
|
||||||
|
res = res + "\n" + line.right(line.size() - idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VCodeBlockHighlightHelper::handleCodeBlocksUpdated(const QList<VCodeBlock> &p_codeBlocks)
|
||||||
|
{
|
||||||
|
int curStamp = m_timeStamp.fetchAndAddRelaxed(1) + 1;
|
||||||
|
m_codeBlocks = p_codeBlocks;
|
||||||
|
for (int i = 0; i < m_codeBlocks.size(); ++i) {
|
||||||
|
QString unindentedText = unindentCodeBlock(m_codeBlocks[i].m_text);
|
||||||
|
m_vdocument->highlightTextAsync(unindentedText, i, curStamp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VCodeBlockHighlightHelper::handleTextHighlightResult(const QString &p_html,
|
||||||
|
int p_id,
|
||||||
|
int p_timeStamp)
|
||||||
|
{
|
||||||
|
int curStamp = m_timeStamp.load();
|
||||||
|
// Abandon obsolete result.
|
||||||
|
if (curStamp != p_timeStamp) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
parseHighlightResult(p_timeStamp, p_id, p_html);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void revertEscapedHtml(QString &p_html)
|
||||||
|
{
|
||||||
|
p_html.replace(">", ">").replace("<", "<").replace("&", "&");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search @p_tokenStr in @p_text from p_index. Spaces after `\n` will not make
|
||||||
|
// a difference in the match. The matched range will be returned as
|
||||||
|
// [@p_start, @p_end]. Update @p_index to @p_end + 1.
|
||||||
|
// Set @p_start and @p_end to -1 to indicate mismatch.
|
||||||
|
static void matchTokenRelaxed(const QString &p_text, const QString &p_tokenStr,
|
||||||
|
int &p_index, int &p_start, int &p_end)
|
||||||
|
{
|
||||||
|
QString regStr = QRegExp::escape(p_tokenStr);
|
||||||
|
// Do not replace the ending '\n'.
|
||||||
|
regStr.replace(QRegExp("\n(?!$)"), "\\s+");
|
||||||
|
QRegExp regExp(regStr);
|
||||||
|
p_start = p_text.indexOf(regExp, p_index);
|
||||||
|
if (p_start == -1) {
|
||||||
|
p_end = -1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
p_end = p_start + regExp.matchedLength() - 1;
|
||||||
|
p_index = p_end + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For now, we could only handle code blocks outside the list.
|
||||||
|
void VCodeBlockHighlightHelper::parseHighlightResult(int p_timeStamp,
|
||||||
|
int p_idx,
|
||||||
|
const QString &p_html)
|
||||||
|
{
|
||||||
|
const VCodeBlock &block = m_codeBlocks.at(p_idx);
|
||||||
|
int startPos = block.m_startPos;
|
||||||
|
QString text = block.m_text;
|
||||||
|
|
||||||
|
QList<HLUnitPos> hlUnits;
|
||||||
|
|
||||||
|
bool failed = true;
|
||||||
|
|
||||||
|
QXmlStreamReader xml(p_html);
|
||||||
|
|
||||||
|
// Must have a fenced line at the front.
|
||||||
|
// textIndex is the start index in the code block text to search for.
|
||||||
|
int textIndex = text.indexOf('\n');
|
||||||
|
if (textIndex == -1) {
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
++textIndex;
|
||||||
|
|
||||||
|
if (xml.readNextStartElement()) {
|
||||||
|
if (xml.name() != "pre") {
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!xml.readNextStartElement()) {
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xml.name() != "code") {
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (xml.readNext()) {
|
||||||
|
if (xml.isCharacters()) {
|
||||||
|
// Revert the HTML escape to match.
|
||||||
|
QString tokenStr = xml.text().toString();
|
||||||
|
revertEscapedHtml(tokenStr);
|
||||||
|
|
||||||
|
int start, end;
|
||||||
|
matchTokenRelaxed(text, tokenStr, textIndex, start, end);
|
||||||
|
if (start == -1) {
|
||||||
|
failed = true;
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
} else if (xml.isStartElement()) {
|
||||||
|
if (xml.name() != "span") {
|
||||||
|
failed = true;
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
if (!parseSpanElement(xml, startPos, text, textIndex, hlUnits)) {
|
||||||
|
failed = true;
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
} else if (xml.isEndElement()) {
|
||||||
|
if (xml.name() != "code" && xml.name() != "pre") {
|
||||||
|
failed = true;
|
||||||
|
} else {
|
||||||
|
failed = false;
|
||||||
|
}
|
||||||
|
goto exit;
|
||||||
|
} else {
|
||||||
|
failed = true;
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exit:
|
||||||
|
// Pass result back to highlighter.
|
||||||
|
int curStamp = m_timeStamp.load();
|
||||||
|
// Abandon obsolete result.
|
||||||
|
if (curStamp != p_timeStamp) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xml.hasError() || failed) {
|
||||||
|
qWarning() << "fail to parse highlighted result"
|
||||||
|
<< "stamp:" << p_timeStamp << "index:" << p_idx << p_html;
|
||||||
|
hlUnits.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to call this function anyway to trigger the rehighlight.
|
||||||
|
m_highlighter->setCodeBlockHighlights(hlUnits);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VCodeBlockHighlightHelper::parseSpanElement(QXmlStreamReader &p_xml,
|
||||||
|
int p_startPos,
|
||||||
|
const QString &p_text,
|
||||||
|
int &p_index,
|
||||||
|
QList<HLUnitPos> &p_units)
|
||||||
|
{
|
||||||
|
int unitStart = p_index;
|
||||||
|
QString style = p_xml.attributes().value("class").toString();
|
||||||
|
|
||||||
|
while (p_xml.readNext()) {
|
||||||
|
if (p_xml.isCharacters()) {
|
||||||
|
// Revert the HTML escape to match.
|
||||||
|
QString tokenStr = p_xml.text().toString();
|
||||||
|
revertEscapedHtml(tokenStr);
|
||||||
|
|
||||||
|
int start, end;
|
||||||
|
matchTokenRelaxed(p_text, tokenStr, p_index, start, end);
|
||||||
|
if (start == -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (p_xml.isStartElement()) {
|
||||||
|
if (p_xml.name() != "span") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sub-span.
|
||||||
|
if (!parseSpanElement(p_xml, p_startPos, p_text, p_index, p_units)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (p_xml.isEndElement()) {
|
||||||
|
if (p_xml.name() != "span") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Got a complete span.
|
||||||
|
HLUnitPos unit(unitStart + p_startPos, p_index - unitStart, style);
|
||||||
|
p_units.append(unit);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
49
src/vcodeblockhighlighthelper.h
Normal file
49
src/vcodeblockhighlighthelper.h
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
#ifndef VCODEBLOCKHIGHLIGHTHELPER_H
|
||||||
|
#define VCODEBLOCKHIGHLIGHTHELPER_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QList>
|
||||||
|
#include <QAtomicInteger>
|
||||||
|
#include <QXmlStreamReader>
|
||||||
|
#include "vconfigmanager.h"
|
||||||
|
|
||||||
|
class VDocument;
|
||||||
|
|
||||||
|
class VCodeBlockHighlightHelper : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
VCodeBlockHighlightHelper(HGMarkdownHighlighter *p_highlighter,
|
||||||
|
VDocument *p_vdoc, MarkdownConverterType p_type);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void handleCodeBlocksUpdated(const QList<VCodeBlock> &p_codeBlocks);
|
||||||
|
void handleTextHighlightResult(const QString &p_html, int p_id, int p_timeStamp);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void parseHighlightResult(int p_timeStamp, int p_idx, const QString &p_html);
|
||||||
|
|
||||||
|
// @p_startPos: the global position of the start of the code block;
|
||||||
|
// @p_text: the raw text of the code block;
|
||||||
|
// @p_index: the start index of the span element within @p_text;
|
||||||
|
// @p_units: all the highlight units of this code block;
|
||||||
|
bool parseSpanElement(QXmlStreamReader &p_xml, int p_startPos,
|
||||||
|
const QString &p_text, int &p_index,
|
||||||
|
QList<HLUnitPos> &p_units);
|
||||||
|
// @p_text: text of fenced code block.
|
||||||
|
// Get the indent level of the first line (fence) and unindent the whole block
|
||||||
|
// to make the fence at the highest indent level.
|
||||||
|
// This operation is to make sure JS could handle the code block correctly
|
||||||
|
// without any context.
|
||||||
|
QString unindentCodeBlock(const QString &p_text);
|
||||||
|
|
||||||
|
HGMarkdownHighlighter *m_highlighter;
|
||||||
|
VDocument *m_vdocument;
|
||||||
|
MarkdownConverterType m_type;
|
||||||
|
QAtomicInteger<int> m_timeStamp;
|
||||||
|
QList<VCodeBlock> m_codeBlocks;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // VCODEBLOCKHIGHLIGHTHELPER_H
|
@ -96,6 +96,9 @@ void VConfigManager::initialize()
|
|||||||
m_webZoomFactor = VUtils::calculateScaleFactor();
|
m_webZoomFactor = VUtils::calculateScaleFactor();
|
||||||
qDebug() << "set WebZoomFactor to" << m_webZoomFactor;
|
qDebug() << "set WebZoomFactor to" << m_webZoomFactor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m_enableCodeBlockHighlight = getConfigFromSettings("global",
|
||||||
|
"enable_code_block_highlight").toBool();
|
||||||
}
|
}
|
||||||
|
|
||||||
void VConfigManager::readPredefinedColorsFromSettings()
|
void VConfigManager::readPredefinedColorsFromSettings()
|
||||||
@ -243,6 +246,7 @@ void VConfigManager::updateMarkdownEditStyle()
|
|||||||
VStyleParser parser;
|
VStyleParser parser;
|
||||||
parser.parseMarkdownStyle(styleStr);
|
parser.parseMarkdownStyle(styleStr);
|
||||||
mdHighlightingStyles = parser.fetchMarkdownStyles(baseEditFont);
|
mdHighlightingStyles = parser.fetchMarkdownStyles(baseEditFont);
|
||||||
|
m_codeBlockStyles = parser.fetchCodeBlockStyles(baseEditFont);
|
||||||
mdEditPalette = baseEditPalette;
|
mdEditPalette = baseEditPalette;
|
||||||
mdEditFont = baseEditFont;
|
mdEditFont = baseEditFont;
|
||||||
QMap<QString, QMap<QString, QString>> styles;
|
QMap<QString, QMap<QString, QString>> styles;
|
||||||
|
@ -50,6 +50,8 @@ public:
|
|||||||
|
|
||||||
inline QVector<HighlightingStyle> getMdHighlightingStyles() const;
|
inline QVector<HighlightingStyle> getMdHighlightingStyles() const;
|
||||||
|
|
||||||
|
inline QMap<QString, QTextCharFormat> getCodeBlockStyles() const;
|
||||||
|
|
||||||
inline QString getWelcomePagePath() const;
|
inline QString getWelcomePagePath() const;
|
||||||
|
|
||||||
inline QString getTemplateCssUrl() const;
|
inline QString getTemplateCssUrl() const;
|
||||||
@ -135,6 +137,9 @@ public:
|
|||||||
inline QString getEditorCurrentLineBackground() const;
|
inline QString getEditorCurrentLineBackground() const;
|
||||||
inline QString getEditorCurrentLineVimBackground() const;
|
inline QString getEditorCurrentLineVimBackground() const;
|
||||||
|
|
||||||
|
inline bool getEnableCodeBlockHighlight() const;
|
||||||
|
inline void setEnableCodeBlockHighlight(bool p_enabled);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void updateMarkdownEditStyle();
|
void updateMarkdownEditStyle();
|
||||||
QVariant getConfigFromSettings(const QString §ion, const QString &key);
|
QVariant getConfigFromSettings(const QString §ion, const QString &key);
|
||||||
@ -151,6 +156,7 @@ private:
|
|||||||
QFont mdEditFont;
|
QFont mdEditFont;
|
||||||
QPalette mdEditPalette;
|
QPalette mdEditPalette;
|
||||||
QVector<HighlightingStyle> mdHighlightingStyles;
|
QVector<HighlightingStyle> mdHighlightingStyles;
|
||||||
|
QMap<QString, QTextCharFormat> m_codeBlockStyles;
|
||||||
QString welcomePagePath;
|
QString welcomePagePath;
|
||||||
QString templateCssUrl;
|
QString templateCssUrl;
|
||||||
int curNotebookIndex;
|
int curNotebookIndex;
|
||||||
@ -213,6 +219,9 @@ private:
|
|||||||
// Current line background color in editor in Vim mode.
|
// Current line background color in editor in Vim mode.
|
||||||
QString m_editorCurrentLineVimBackground;
|
QString m_editorCurrentLineVimBackground;
|
||||||
|
|
||||||
|
// Enable colde block syntax highlight.
|
||||||
|
bool m_enableCodeBlockHighlight;
|
||||||
|
|
||||||
// The name of the config file in each directory
|
// The name of the config file in each directory
|
||||||
static const QString dirConfigFileName;
|
static const QString dirConfigFileName;
|
||||||
// The name of the default configuration file
|
// The name of the default configuration file
|
||||||
@ -239,6 +248,11 @@ inline QVector<HighlightingStyle> VConfigManager::getMdHighlightingStyles() cons
|
|||||||
return mdHighlightingStyles;
|
return mdHighlightingStyles;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline QMap<QString, QTextCharFormat> VConfigManager::getCodeBlockStyles() const
|
||||||
|
{
|
||||||
|
return m_codeBlockStyles;
|
||||||
|
}
|
||||||
|
|
||||||
inline QString VConfigManager::getWelcomePagePath() const
|
inline QString VConfigManager::getWelcomePagePath() const
|
||||||
{
|
{
|
||||||
return welcomePagePath;
|
return welcomePagePath;
|
||||||
@ -609,4 +623,20 @@ inline QString VConfigManager::getEditorCurrentLineVimBackground() const
|
|||||||
{
|
{
|
||||||
return m_editorCurrentLineVimBackground;
|
return m_editorCurrentLineVimBackground;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline bool VConfigManager::getEnableCodeBlockHighlight() const
|
||||||
|
{
|
||||||
|
return m_enableCodeBlockHighlight;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void VConfigManager::setEnableCodeBlockHighlight(bool p_enabled)
|
||||||
|
{
|
||||||
|
if (m_enableCodeBlockHighlight == p_enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_enableCodeBlockHighlight = p_enabled;
|
||||||
|
setConfigToSettings("global", "enable_code_block_highlight",
|
||||||
|
m_enableCodeBlockHighlight);
|
||||||
|
}
|
||||||
|
|
||||||
#endif // VCONFIGMANAGER_H
|
#endif // VCONFIGMANAGER_H
|
||||||
|
@ -59,3 +59,18 @@ void VDocument::keyPressEvent(int p_key, bool p_ctrl, bool p_shift)
|
|||||||
{
|
{
|
||||||
emit keyPressed(p_key, p_ctrl, p_shift);
|
emit keyPressed(p_key, p_ctrl, p_shift);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void VDocument::highlightTextAsync(const QString &p_text, int p_id, int p_timeStamp)
|
||||||
|
{
|
||||||
|
emit requestHighlightText(p_text, p_id, p_timeStamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VDocument::highlightTextCB(const QString &p_html, int p_id, int p_timeStamp)
|
||||||
|
{
|
||||||
|
emit textHighlighted(p_html, p_id, p_timeStamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VDocument::noticeReadyToHighlightText()
|
||||||
|
{
|
||||||
|
emit readyToHighlightText();
|
||||||
|
}
|
||||||
|
@ -18,6 +18,9 @@ public:
|
|||||||
QString getToc();
|
QString getToc();
|
||||||
void scrollToAnchor(const QString &anchor);
|
void scrollToAnchor(const QString &anchor);
|
||||||
void setHtml(const QString &html);
|
void setHtml(const QString &html);
|
||||||
|
// 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);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
// Will be called in the HTML side
|
// Will be called in the HTML side
|
||||||
@ -26,6 +29,8 @@ public slots:
|
|||||||
void setLog(const QString &p_log);
|
void setLog(const QString &p_log);
|
||||||
void keyPressEvent(int p_key, bool p_ctrl, bool p_shift);
|
void keyPressEvent(int p_key, bool p_ctrl, bool p_shift);
|
||||||
void updateText();
|
void updateText();
|
||||||
|
void highlightTextCB(const QString &p_html, int p_id, int p_timeStamp);
|
||||||
|
void noticeReadyToHighlightText();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void textChanged(const QString &text);
|
void textChanged(const QString &text);
|
||||||
@ -35,6 +40,9 @@ signals:
|
|||||||
void htmlChanged(const QString &html);
|
void htmlChanged(const QString &html);
|
||||||
void logChanged(const QString &p_log);
|
void logChanged(const QString &p_log);
|
||||||
void keyPressed(int p_key, bool p_ctrl, bool p_shift);
|
void keyPressed(int p_key, bool p_ctrl, bool p_shift);
|
||||||
|
void requestHighlightText(const QString &p_text, int p_id, int p_timeStamp);
|
||||||
|
void textHighlighted(const QString &p_html, int p_id, int p_timeStamp);
|
||||||
|
void readyToHighlightText();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString m_toc;
|
QString m_toc;
|
||||||
|
@ -57,7 +57,7 @@ void VEditTab::setupUI()
|
|||||||
switch (m_file->getDocType()) {
|
switch (m_file->getDocType()) {
|
||||||
case DocType::Markdown:
|
case DocType::Markdown:
|
||||||
if (m_file->isModifiable()) {
|
if (m_file->isModifiable()) {
|
||||||
m_textEditor = new VMdEdit(m_file, this);
|
m_textEditor = new VMdEdit(m_file, &document, mdConverterType, this);
|
||||||
connect(dynamic_cast<VMdEdit *>(m_textEditor), &VMdEdit::headersChanged,
|
connect(dynamic_cast<VMdEdit *>(m_textEditor), &VMdEdit::headersChanged,
|
||||||
this, &VEditTab::updateTocFromHeaders);
|
this, &VEditTab::updateTocFromHeaders);
|
||||||
connect(dynamic_cast<VMdEdit *>(m_textEditor), &VMdEdit::statusChanged,
|
connect(dynamic_cast<VMdEdit *>(m_textEditor), &VMdEdit::statusChanged,
|
||||||
@ -105,7 +105,6 @@ void VEditTab::noticeStatusChanged()
|
|||||||
|
|
||||||
void VEditTab::showFileReadMode()
|
void VEditTab::showFileReadMode()
|
||||||
{
|
{
|
||||||
qDebug() << "read" << m_file->getName();
|
|
||||||
isEditMode = false;
|
isEditMode = false;
|
||||||
int outlineIndex = curHeader.m_outlineIndex;
|
int outlineIndex = curHeader.m_outlineIndex;
|
||||||
switch (m_file->getDocType()) {
|
switch (m_file->getDocType()) {
|
||||||
@ -298,6 +297,8 @@ void VEditTab::setupMarkdownPreview()
|
|||||||
|
|
||||||
case MarkdownConverterType::Hoedown:
|
case MarkdownConverterType::Hoedown:
|
||||||
jsFile = "qrc" + VNote::c_hoedownJsFile;
|
jsFile = "qrc" + VNote::c_hoedownJsFile;
|
||||||
|
// Use Marked to highlight code blocks.
|
||||||
|
extraFile = "<script src=\"qrc" + VNote::c_markedExtraFile + "\"></script>\n";
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MarkdownConverterType::MarkdownIt:
|
case MarkdownConverterType::MarkdownIt:
|
||||||
|
@ -367,6 +367,16 @@ void VMainWindow::initMarkdownMenu()
|
|||||||
markdownMenu->addAction(mathjaxAct);
|
markdownMenu->addAction(mathjaxAct);
|
||||||
|
|
||||||
mathjaxAct->setChecked(vconfig.getEnableMathjax());
|
mathjaxAct->setChecked(vconfig.getEnableMathjax());
|
||||||
|
|
||||||
|
markdownMenu->addSeparator();
|
||||||
|
|
||||||
|
QAction *codeBlockAct = new QAction(tr("Highlight Code Blocks In Edit Mode"), this);
|
||||||
|
codeBlockAct->setToolTip(tr("Enable syntax highlight within code blocks in edit mode"));
|
||||||
|
codeBlockAct->setCheckable(true);
|
||||||
|
connect(codeBlockAct, &QAction::triggered,
|
||||||
|
this, &VMainWindow::enableCodeBlockHighlight);
|
||||||
|
markdownMenu->addAction(codeBlockAct);
|
||||||
|
codeBlockAct->setChecked(vconfig.getEnableCodeBlockHighlight());
|
||||||
}
|
}
|
||||||
|
|
||||||
void VMainWindow::initViewMenu()
|
void VMainWindow::initViewMenu()
|
||||||
@ -1120,6 +1130,11 @@ void VMainWindow::changeAutoList(bool p_checked)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void VMainWindow::enableCodeBlockHighlight(bool p_checked)
|
||||||
|
{
|
||||||
|
vconfig.setEnableCodeBlockHighlight(p_checked);
|
||||||
|
}
|
||||||
|
|
||||||
void VMainWindow::shortcutHelp()
|
void VMainWindow::shortcutHelp()
|
||||||
{
|
{
|
||||||
QString locale = VUtils::getLocale();
|
QString locale = VUtils::getLocale();
|
||||||
|
@ -71,6 +71,7 @@ private slots:
|
|||||||
void handleCaptainModeChanged(bool p_enabled);
|
void handleCaptainModeChanged(bool p_enabled);
|
||||||
void changeAutoIndent(bool p_checked);
|
void changeAutoIndent(bool p_checked);
|
||||||
void changeAutoList(bool p_checked);
|
void changeAutoList(bool p_checked);
|
||||||
|
void enableCodeBlockHighlight(bool p_checked);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void closeEvent(QCloseEvent *event) Q_DECL_OVERRIDE;
|
void closeEvent(QCloseEvent *event) Q_DECL_OVERRIDE;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
#include <QtWidgets>
|
#include <QtWidgets>
|
||||||
#include "vmdedit.h"
|
#include "vmdedit.h"
|
||||||
#include "hgmarkdownhighlighter.h"
|
#include "hgmarkdownhighlighter.h"
|
||||||
|
#include "vcodeblockhighlighthelper.h"
|
||||||
#include "vmdeditoperations.h"
|
#include "vmdeditoperations.h"
|
||||||
#include "vnote.h"
|
#include "vnote.h"
|
||||||
#include "vconfigmanager.h"
|
#include "vconfigmanager.h"
|
||||||
@ -13,18 +14,24 @@ extern VNote *g_vnote;
|
|||||||
|
|
||||||
enum ImageProperty { ImagePath = 1 };
|
enum ImageProperty { ImagePath = 1 };
|
||||||
|
|
||||||
VMdEdit::VMdEdit(VFile *p_file, QWidget *p_parent)
|
VMdEdit::VMdEdit(VFile *p_file, VDocument *p_vdoc, MarkdownConverterType p_type,
|
||||||
|
QWidget *p_parent)
|
||||||
: VEdit(p_file, p_parent), m_mdHighlighter(NULL), m_previewImage(true)
|
: VEdit(p_file, p_parent), m_mdHighlighter(NULL), m_previewImage(true)
|
||||||
{
|
{
|
||||||
Q_ASSERT(p_file->getDocType() == DocType::Markdown);
|
Q_ASSERT(p_file->getDocType() == DocType::Markdown);
|
||||||
|
|
||||||
setAcceptRichText(false);
|
setAcceptRichText(false);
|
||||||
m_mdHighlighter = new HGMarkdownHighlighter(vconfig.getMdHighlightingStyles(),
|
m_mdHighlighter = new HGMarkdownHighlighter(vconfig.getMdHighlightingStyles(),
|
||||||
|
vconfig.getCodeBlockStyles(),
|
||||||
500, document());
|
500, document());
|
||||||
connect(m_mdHighlighter, &HGMarkdownHighlighter::highlightCompleted,
|
connect(m_mdHighlighter, &HGMarkdownHighlighter::highlightCompleted,
|
||||||
this, &VMdEdit::generateEditOutline);
|
this, &VMdEdit::generateEditOutline);
|
||||||
connect(m_mdHighlighter, &HGMarkdownHighlighter::imageBlocksUpdated,
|
connect(m_mdHighlighter, &HGMarkdownHighlighter::imageBlocksUpdated,
|
||||||
this, &VMdEdit::updateImageBlocks);
|
this, &VMdEdit::updateImageBlocks);
|
||||||
|
|
||||||
|
m_cbHighlighter = new VCodeBlockHighlightHelper(m_mdHighlighter, p_vdoc,
|
||||||
|
p_type);
|
||||||
|
|
||||||
m_editOps = new VMdEditOperations(this, m_file);
|
m_editOps = new VMdEditOperations(this, m_file);
|
||||||
connect(m_editOps, &VEditOperations::keyStateChanged,
|
connect(m_editOps, &VEditOperations::keyStateChanged,
|
||||||
this, &VMdEdit::handleEditStateChanged);
|
this, &VMdEdit::handleEditStateChanged);
|
||||||
|
@ -8,14 +8,18 @@
|
|||||||
#include <QClipboard>
|
#include <QClipboard>
|
||||||
#include "vtoc.h"
|
#include "vtoc.h"
|
||||||
#include "veditoperations.h"
|
#include "veditoperations.h"
|
||||||
|
#include "vconfigmanager.h"
|
||||||
|
|
||||||
class HGMarkdownHighlighter;
|
class HGMarkdownHighlighter;
|
||||||
|
class VCodeBlockHighlightHelper;
|
||||||
|
class VDocument;
|
||||||
|
|
||||||
class VMdEdit : public VEdit
|
class VMdEdit : public VEdit
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
VMdEdit(VFile *p_file, QWidget *p_parent = 0);
|
VMdEdit(VFile *p_file, VDocument *p_vdoc, MarkdownConverterType p_type,
|
||||||
|
QWidget *p_parent = 0);
|
||||||
void beginEdit() Q_DECL_OVERRIDE;
|
void beginEdit() Q_DECL_OVERRIDE;
|
||||||
void endEdit() Q_DECL_OVERRIDE;
|
void endEdit() Q_DECL_OVERRIDE;
|
||||||
void saveFile() Q_DECL_OVERRIDE;
|
void saveFile() Q_DECL_OVERRIDE;
|
||||||
@ -76,6 +80,7 @@ private:
|
|||||||
QString selectedImage();
|
QString selectedImage();
|
||||||
|
|
||||||
HGMarkdownHighlighter *m_mdHighlighter;
|
HGMarkdownHighlighter *m_mdHighlighter;
|
||||||
|
VCodeBlockHighlightHelper *m_cbHighlighter;
|
||||||
QVector<QString> m_insertedImages;
|
QVector<QString> m_insertedImages;
|
||||||
QVector<QString> m_initImages;
|
QVector<QString> m_initImages;
|
||||||
QVector<VHeader> m_headers;
|
QVector<VHeader> m_headers;
|
||||||
|
@ -130,6 +130,58 @@ QVector<HighlightingStyle> VStyleParser::fetchMarkdownStyles(const QFont &baseFo
|
|||||||
return styles;
|
return styles;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QMap<QString, QTextCharFormat> VStyleParser::fetchCodeBlockStyles(const QFont & p_baseFont) const
|
||||||
|
{
|
||||||
|
QMap<QString, QTextCharFormat> styles;
|
||||||
|
|
||||||
|
pmh_style_attribute *attrs = markdownStyles->element_styles[pmh_VERBATIM];
|
||||||
|
|
||||||
|
// First set up the base format.
|
||||||
|
QTextCharFormat baseFormat = QTextCharFormatFromAttrs(attrs, p_baseFont);
|
||||||
|
|
||||||
|
while (attrs) {
|
||||||
|
switch (attrs->type) {
|
||||||
|
case pmh_attr_type_other:
|
||||||
|
{
|
||||||
|
QString attrName(attrs->name);
|
||||||
|
QString attrValue(attrs->value->string);
|
||||||
|
QTextCharFormat format;
|
||||||
|
format.setFontFamily(baseFormat.fontFamily());
|
||||||
|
|
||||||
|
QStringList items = attrValue.split(',', QString::SkipEmptyParts);
|
||||||
|
for (auto const &item : items) {
|
||||||
|
QString val = item.trimmed().toLower();
|
||||||
|
if (val == "bold") {
|
||||||
|
format.setFontWeight(QFont::Bold);
|
||||||
|
} else if (val == "italic") {
|
||||||
|
format.setFontItalic(true);
|
||||||
|
} else if (val == "underlined") {
|
||||||
|
format.setFontUnderline(true);
|
||||||
|
} else {
|
||||||
|
// Treat it as the color RGB value string without '#'.
|
||||||
|
QColor color("#" + val);
|
||||||
|
if (color.isValid()) {
|
||||||
|
format.setForeground(QBrush(color));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (format.isValid()) {
|
||||||
|
styles[attrName] = format;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
// We just only handle custom attribute here.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
attrs = attrs->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
return styles;
|
||||||
|
}
|
||||||
|
|
||||||
void VStyleParser::fetchMarkdownEditorStyles(QPalette &palette, QFont &font,
|
void VStyleParser::fetchMarkdownEditorStyles(QPalette &palette, QFont &font,
|
||||||
QMap<QString, QMap<QString, QString>> &styles) const
|
QMap<QString, QMap<QString, QString>> &styles) const
|
||||||
{
|
{
|
||||||
|
@ -26,6 +26,7 @@ public:
|
|||||||
// @styles: [rule] -> ([attr] -> value).
|
// @styles: [rule] -> ([attr] -> value).
|
||||||
void fetchMarkdownEditorStyles(QPalette &palette, QFont &font,
|
void fetchMarkdownEditorStyles(QPalette &palette, QFont &font,
|
||||||
QMap<QString, QMap<QString, QString>> &styles) const;
|
QMap<QString, QMap<QString, QString>> &styles) const;
|
||||||
|
QMap<QString, QTextCharFormat> fetchCodeBlockStyles(const QFont &p_baseFont) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QColor QColorFromPmhAttr(pmh_attr_argb_color *attr) const;
|
QColor QColorFromPmhAttr(pmh_attr_argb_color *attr) const;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user