diff --git a/src/resources/markdown_template.js b/src/resources/markdown_template.js index bbc021ca..e0658979 100644 --- a/src/resources/markdown_template.js +++ b/src/resources/markdown_template.js @@ -1622,7 +1622,7 @@ var htmlToText = function(identifier, id, timeStamp, html) { content.htmlToTextCB(identifier, id, timeStamp, markdown); }; -var performSmartLivePreview = function(lang, text) { +var performSmartLivePreview = function(lang, text, hints, isRegex) { if (previewDiv.style.display == 'none') { return; } @@ -1632,7 +1632,35 @@ var performSmartLivePreview = function(lang, text) { } // 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) { return; } @@ -1665,17 +1693,17 @@ var performSmartLivePreview = function(lang, text) { if (trect.height >= vrect.height) { dy = trect.top; } else { - dy = trect.top - (vrect.height - trect.width) / 2; + dy = trect.top - (vrect.height - trect.height) / 2; } } window.scrollBy(dx, dy); } -var findNodeWithText = function(node, reg) { +var findNodeWithText = function(node, text, isMatched) { var children = node.children; if (children.length == 0) { - if (reg.test(node.textContent)) { + if (isMatched(node, text)) { return node; } else { return null; @@ -1683,13 +1711,13 @@ var findNodeWithText = function(node, reg) { } for (var i = 0; i < children.length; ++i) { - var ret = findNodeWithText(children[i], reg); + var ret = findNodeWithText(children[i], text, isMatched); if (ret) { return ret; } } - if (reg.test(node.textContent)) { + if (isMatched(node, text)) { return node; } diff --git a/src/vdocument.cpp b/src/vdocument.cpp index ba03a449..cc904d6c 100644 --- a/src/vdocument.cpp +++ b/src/vdocument.cpp @@ -203,9 +203,13 @@ void VDocument::previewCodeBlockCB(int p_id, const QString &p_lang, const QStrin 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()) { - 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); } } diff --git a/src/vdocument.h b/src/vdocument.h index 08fb272e..b9cc90c0 100644 --- a/src/vdocument.h +++ b/src/vdocument.h @@ -75,7 +75,10 @@ public: 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: // Will be called in the HTML side @@ -194,7 +197,10 @@ signals: 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: QString m_toc; diff --git a/src/vlivepreviewhelper.cpp b/src/vlivepreviewhelper.cpp index d7a06344..240fc8a2 100644 --- a/src/vlivepreviewhelper.cpp +++ b/src/vlivepreviewhelper.cpp @@ -535,6 +535,10 @@ void VLivePreviewHelper::performSmartLivePreview() } const CodeBlockPreviewInfo &cb = m_codeBlocks[m_cbIndex]; + if (!cb.hasImageData()) { + return; + } + const VCodeBlock &vcb = cb.codeBlock(); const QTextBlock block = m_editor->textCursorW().block(); if (block.blockNumber() <= vcb.m_startBlock @@ -542,10 +546,13 @@ void VLivePreviewHelper::performSmartLivePreview() return; } - QString keyword; + QString keyword, hints; + bool isRegex = false; 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); } diff --git a/src/vplantumlhelper.cpp b/src/vplantumlhelper.cpp index 108fb6f7..cb59230a 100644 --- a/src/vplantumlhelper.cpp +++ b/src/vplantumlhelper.cpp @@ -189,37 +189,269 @@ QByteArray VPlantUMLHelper::process(const QString &p_format, const QString &p_te 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 { - 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) { p_keyword = classDef1.cap(1); + p_hints = "id"; return true; } - } - { // class "ABC DEF" as AD #Pink { - QRegExp classDef2("class\\s*\"([^\"]+)\"\\s*.*"); + static QRegExp classDef2("^\\s*(?:class|(?:abstract(?:\\s+class)?)|interface|annotation|enum)\\s*" + "\"([^\"]+)\"\\s*(?:\\bas (\\w+))?.*"); if (classDef2.indexIn(p_keyword) >= 0) { - p_keyword = classDef2.cap(1); + 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"; + } + } + + 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; } -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(); if (kw.isEmpty()) { 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; } diff --git a/src/vplantumlhelper.h b/src/vplantumlhelper.h index 12ab7572..77f1c6d3 100644 --- a/src/vplantumlhelper.h +++ b/src/vplantumlhelper.h @@ -23,7 +23,9 @@ public: 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: void resultReady(int p_id,