LivePreview: refine smart live preview

- Support class diagram.
- Support activity diagram.
This commit is contained in:
Le Tan 2018-08-28 20:43:51 +08:00
parent 28d5954bc3
commit 126600dbb1
6 changed files with 303 additions and 24 deletions

View File

@ -1622,7 +1622,7 @@ var htmlToText = function(identifier, id, timeStamp, html) {
content.htmlToTextCB(identifier, id, timeStamp, markdown); content.htmlToTextCB(identifier, id, timeStamp, markdown);
}; };
var performSmartLivePreview = function(lang, text) { var performSmartLivePreview = function(lang, text, hints, isRegex) {
if (previewDiv.style.display == 'none') { if (previewDiv.style.display == 'none') {
return; return;
} }
@ -1632,7 +1632,35 @@ var performSmartLivePreview = function(lang, text) {
} }
// PlantUML. // PlantUML.
var targetNode = findNodeWithText(previewDiv, new RegExp(text)); var targetNode = null;
if (hints.indexOf('id') >= 0) {
// isRegex is ignored.
targetNode = findNodeWithText(previewDiv,
text,
function (node, text) {
if (!node.id) {
return false;
}
return node.id == text;
});
} else {
if (isRegex) {
var nodeReg = new RegExp(text);
targetNode = findNodeWithText(previewDiv,
text,
function(node, text) {
return nodeReg.test(node.textContent);
});
} else {
targetNode = findNodeWithText(previewDiv,
text,
function(node, text) {
return node.textContent.indexOf(text) >= 0;
});
}
}
if (!targetNode) { if (!targetNode) {
return; return;
} }
@ -1665,17 +1693,17 @@ var performSmartLivePreview = function(lang, text) {
if (trect.height >= vrect.height) { if (trect.height >= vrect.height) {
dy = trect.top; dy = trect.top;
} else { } else {
dy = trect.top - (vrect.height - trect.width) / 2; dy = trect.top - (vrect.height - trect.height) / 2;
} }
} }
window.scrollBy(dx, dy); window.scrollBy(dx, dy);
} }
var findNodeWithText = function(node, reg) { var findNodeWithText = function(node, text, isMatched) {
var children = node.children; var children = node.children;
if (children.length == 0) { if (children.length == 0) {
if (reg.test(node.textContent)) { if (isMatched(node, text)) {
return node; return node;
} else { } else {
return null; return null;
@ -1683,13 +1711,13 @@ var findNodeWithText = function(node, reg) {
} }
for (var i = 0; i < children.length; ++i) { for (var i = 0; i < children.length; ++i) {
var ret = findNodeWithText(children[i], reg); var ret = findNodeWithText(children[i], text, isMatched);
if (ret) { if (ret) {
return ret; return ret;
} }
} }
if (reg.test(node.textContent)) { if (isMatched(node, text)) {
return node; return node;
} }

View File

@ -203,9 +203,13 @@ void VDocument::previewCodeBlockCB(int p_id, const QString &p_lang, const QStrin
emit codeBlockPreviewReady(p_id, p_lang, p_html); emit codeBlockPreviewReady(p_id, p_lang, p_html);
} }
void VDocument::performSmartLivePreview(const QString &p_lang, const QString &p_text) void VDocument::performSmartLivePreview(const QString &p_lang,
const QString &p_text,
const QString &p_hints,
bool p_isRegex)
{ {
if (!p_text.isEmpty()) { if (!p_text.isEmpty()) {
emit requestPerformSmartLivePreview(p_lang, p_text); qDebug() << "performSmartLivePreview" << p_lang << p_text << p_hints << p_isRegex;
emit requestPerformSmartLivePreview(p_lang, p_text, p_hints, p_isRegex);
} }
} }

View File

@ -75,7 +75,10 @@ public:
void muteWebView(bool p_muted); void muteWebView(bool p_muted);
void performSmartLivePreview(const QString &p_lang, const QString &p_text); void performSmartLivePreview(const QString &p_lang,
const QString &p_text,
const QString &p_hints,
bool p_isRegex);
public slots: public slots:
// Will be called in the HTML side // Will be called in the HTML side
@ -194,7 +197,10 @@ signals:
void requestMuted(bool p_muted); void requestMuted(bool p_muted);
void requestPerformSmartLivePreview(const QString &p_lang, const QString &p_text); void requestPerformSmartLivePreview(const QString &p_lang,
const QString &p_text,
const QString &p_hints,
bool p_isRegex);
private: private:
QString m_toc; QString m_toc;

View File

@ -535,6 +535,10 @@ void VLivePreviewHelper::performSmartLivePreview()
} }
const CodeBlockPreviewInfo &cb = m_codeBlocks[m_cbIndex]; const CodeBlockPreviewInfo &cb = m_codeBlocks[m_cbIndex];
if (!cb.hasImageData()) {
return;
}
const VCodeBlock &vcb = cb.codeBlock(); const VCodeBlock &vcb = cb.codeBlock();
const QTextBlock block = m_editor->textCursorW().block(); const QTextBlock block = m_editor->textCursorW().block();
if (block.blockNumber() <= vcb.m_startBlock if (block.blockNumber() <= vcb.m_startBlock
@ -542,10 +546,13 @@ void VLivePreviewHelper::performSmartLivePreview()
return; return;
} }
QString keyword; QString keyword, hints;
bool isRegex = false;
if (vcb.m_lang == "puml" && m_plantUMLMode == PlantUMLMode::LocalPlantUML) { if (vcb.m_lang == "puml" && m_plantUMLMode == PlantUMLMode::LocalPlantUML) {
keyword = VPlantUMLHelper::keywordForSmartLivePreview(block.text()); keyword = VPlantUMLHelper::keywordForSmartLivePreview(block.text(),
hints,
isRegex);
} }
m_document->performSmartLivePreview(vcb.m_lang, keyword); m_document->performSmartLivePreview(vcb.m_lang, keyword, hints, isRegex);
} }

View File

@ -189,37 +189,269 @@ QByteArray VPlantUMLHelper::process(const QString &p_format, const QString &p_te
return out; return out;
} }
static bool tryClassDiagram(QString &p_keyword) static bool tryClassDiagram(QString &p_keyword, QString &p_hints, bool &p_isRegex)
{ {
{ Q_UNUSED(p_isRegex);
// class ABC #Pink { // class ABC #Pink {
QRegExp classDef1("class\\s*(\\w+)\\s*.*"); static QRegExp classDef1("^\\s*(?:class|(?:abstract(?:\\s+class)?)|interface|annotation|enum)\\s*"
"(?!class)(\\w+)\\s*.*");
if (classDef1.indexIn(p_keyword) >= 0) { if (classDef1.indexIn(p_keyword) >= 0) {
p_keyword = classDef1.cap(1); p_keyword = classDef1.cap(1);
p_hints = "id";
return true; return true;
} }
// class "ABC DEF" as AD #Pink {
static QRegExp classDef2("^\\s*(?:class|(?:abstract(?:\\s+class)?)|interface|annotation|enum)\\s*"
"\"([^\"]+)\"\\s*(?:\\bas (\\w+))?.*");
if (classDef2.indexIn(p_keyword) >= 0) {
if (classDef2.cap(2).isEmpty()) {
p_keyword = classDef2.cap(1);
} else {
p_keyword = classDef2.cap(2);
}
p_hints = "id";
return true;
}
// class01 "1" *-- "many" class02 : contains 4 >
static QRegExp relation("^\\s*(?:(\\w+)|\"([^\"]+)\")\\s+"
"(?:\"[^\"]+\"\\s+)?"
"(?:<\\||[*o<#x}+^])?" "(?:-+|\\.+)" "(?:\\|>|[*o>#x{+^])?\\s+"
"(?:\"[^\"]+\"\\s+)?"
"(?:(\\w+)|\"([^\"]+)\")\\s*"
"(?::(.+))?");
if (relation.indexIn(p_keyword) >= 0) {
QString note(relation.cap(5));
if (note.isEmpty()) {
QString class2 = relation.cap(3);
if (class2.isEmpty()) {
class2 = relation.cap(4);
}
p_keyword = class2;
p_hints = "id";
} else {
p_keyword = note.trimmed();
}
return true;
}
// {static} field : String
bool containsModifier = false;
static QRegExp modifier("\\{(?:static|abstract|classifier)\\}");
if (modifier.indexIn(p_keyword) >= 0) {
containsModifier = true;
p_keyword.remove(modifier);
}
// + field
static QRegExp member("^\\s*[-#~+]\\s*(.*)");
if (member.indexIn(p_keyword) >= 0) {
p_keyword = member.cap(1).trimmed();
return true;
} else if (containsModifier) {
p_keyword = p_keyword.trimmed();
return true;
}
// note left on link: message
// note left on link
// node on link: message
// MUST before next rule "note".
static QRegExp note4("^\\s*note\\s+(?:(?:left|top|right|bottom)\\s+)?"
"on\\s+link"
"[^:]*"
"(?::(.*))?");
if (note4.indexIn(p_keyword) >= 0) {
p_keyword = note4.cap(1).trimmed();
return true;
}
// note top of Object: message
// note top of Object
// note top: message
static QRegExp note("^\\s*note\\s+(?:left|top|right|bottom)"
"(?:\\s+of\\s+(\\w+))?\\s*"
"(?::(.*))?");
if (note.indexIn(p_keyword) >= 0) {
p_keyword = note.cap(2).trimmed();
if (p_keyword.isEmpty()) {
p_keyword = note.cap(1);
if (!p_keyword.isEmpty()) {
p_hints = "id";
}
} }
{
// class "ABC DEF" as AD #Pink {
QRegExp classDef2("class\\s*\"([^\"]+)\"\\s*.*");
if (classDef2.indexIn(p_keyword) >= 0) {
p_keyword = classDef2.cap(1);
return true; return true;
} }
// note "a floating note" as N1
// note as N1
static QRegExp note2("^\\s*note\\s+(?:\"([^\"]*)\"\\s+)?as\\s+\\w+\\s*");
if (note2.indexIn(p_keyword) >= 0) {
p_keyword = note2.cap(1);
return true;
}
// end note
static QRegExp note3("^\\s*end note\\s*$");
if (note3.indexIn(p_keyword) >= 0) {
p_keyword.clear();
return true;
} }
return false; return false;
} }
QString VPlantUMLHelper::keywordForSmartLivePreview(const QString &p_text) static bool tryCommonElements(QString &p_keyword, QString &p_hints, bool &p_isRegex)
{
Q_UNUSED(p_isRegex);
Q_UNUSED(p_hints);
// List.
// ** list
// # list
static QRegExp listMark("^\\s*(?:\\*+|#+)\\s+(.+)$");
if (listMark.indexIn(p_keyword) >= 0) {
p_keyword = listMark.cap(1).trimmed();
return true;
}
// Words in quotes.
// cmf("abc")
static QRegExp quote("^[^\"]*\"([^\"]+)\"[^\"]*$");
if (quote.indexIn(p_keyword) >= 0) {
p_keyword = quote.cap(1).trimmed();
return true;
}
return false;
}
static bool tryActivityDiagram(QString &p_keyword, QString &p_hints, bool &p_isRegex)
{
Q_UNUSED(p_isRegex);
Q_UNUSED(p_hints);
// Activity. (Do not support color.)
// :Hello world;
// :Across multiple lines
static QRegExp activity1("^\\s*:(.+)\\s*$");
if (activity1.indexIn(p_keyword) >= 0) {
p_keyword = activity1.cap(1).trimmed();
if (!p_keyword.isEmpty()) {
QChar ch = p_keyword[p_keyword.size() - 1];
if (ch == ';' || ch == '|' || ch == '<' || ch == '>'
|| ch == '/' || ch == ']' || ch == '}') {
p_keyword = p_keyword.left(p_keyword.size() - 1);
}
}
return true;
}
// Activity.
// multiple lines;
static QRegExp activity2("^\\s*(.+)[;|<>/\\]}]\\s*$");
if (activity2.indexIn(p_keyword) >= 0) {
p_keyword = activity2.cap(1).trimmed();
return true;
}
// start, stop, end, endif, repeat, fork, fork again, end fork, },
// detach
static QRegExp start("^\\s*(?:start|stop|end|endif|repeat|"
"fork(?:\\s+again)?|end\\s+fork|\\}|detach)\\s*$");
if (start.indexIn(p_keyword) >= 0) {
p_keyword.clear();
return true;
}
// Conditionals.
// if (Graphviz) then (yes)
// else if (Graphviz) then (yes)
static QRegExp conIf("^\\s*(?:else)?if\\s+\\(([^\\)]+)\\)\\s+then(?:\\s+\\([^\\)]+\\))?\\s*$");
if (conIf.indexIn(p_keyword) >= 0) {
p_keyword = conIf.cap(1).trimmed();
return true;
}
// else (no)
static QRegExp conElse("^\\s*else(?:\\s+\\(([^\\)]+)\\))?\\s*$");
if (conElse.indexIn(p_keyword) >= 0) {
p_keyword = conElse.cap(1).trimmed();
return true;
}
// Repeat loop.
// repeat while (more data?)
static QRegExp repeat("^\\s*repeat\\s+while\\s+\\(([^\\)]+)\\)\\s*$");
if (repeat.indexIn(p_keyword) >= 0) {
p_keyword = repeat.cap(1).trimmed();
return true;
}
// while (check?) is (not empty)
static QRegExp whileLoop("^\\s*while\\s+\\(([^\\)]+)\\)(?:\\s+is\\s+\\([^\\)]+\\))?\\s*$");
if (whileLoop.indexIn(p_keyword) >= 0) {
p_keyword = whileLoop.cap(1).trimmed();
return true;
}
// endwhile (empty)
static QRegExp endWhile("^\\s*endwhile(?:\\s+\\(([^\\)]+)\\))?\\s*$");
if (endWhile.indexIn(p_keyword) >= 0) {
p_keyword = endWhile.cap(1).trimmed();
return true;
}
// partition Running {
static QRegExp partition("^\\s*partition\\s+(\\w+)\\s+\\{\\s*$");
if (partition.indexIn(p_keyword) >= 0) {
p_keyword = partition.cap(1).trimmed();
return true;
}
// |Swimlane1|
// |#Pink|Swimlane1|
static QRegExp swimline("^\\s*(?:\\|[^\\|]+)?\\|([^|]+)\\|\\s*$");
if (swimline.indexIn(p_keyword) >= 0) {
p_keyword = swimline.cap(1).trimmed();
return true;
}
return false;
}
QString VPlantUMLHelper::keywordForSmartLivePreview(const QString &p_text,
QString &p_hints,
bool &p_isRegex)
{ {
QString kw = p_text.trimmed(); QString kw = p_text.trimmed();
if (kw.isEmpty()) { if (kw.isEmpty()) {
return kw; return kw;
} }
if (tryClassDiagram(kw)) { p_isRegex = false;
qDebug() << "tryClassDiagram" << kw;
if (tryClassDiagram(kw, p_hints, p_isRegex)) {
return kw;
}
qDebug() << "tryActivityDiagram" << kw;
if (tryActivityDiagram(kw, p_hints, p_isRegex)) {
return kw;
}
qDebug() << "tryCommonElements" << kw;
if (tryCommonElements(kw, p_hints, p_isRegex)) {
return kw; return kw;
} }

View File

@ -23,7 +23,9 @@ public:
static QByteArray process(const QString &p_format, const QString &p_text); static QByteArray process(const QString &p_format, const QString &p_text);
static QString keywordForSmartLivePreview(const QString &p_text); static QString keywordForSmartLivePreview(const QString &p_text,
QString &p_hints,
bool &p_isRegex);
signals: signals:
void resultReady(int p_id, void resultReady(int p_id,