diff --git a/src/resources/markdown_template.js b/src/resources/markdown_template.js index 61125877..1ac22293 100644 --- a/src/resources/markdown_template.js +++ b/src/resources/markdown_template.js @@ -190,7 +190,9 @@ new QWebChannel(qt.webChannelTransport, content.requestPreviewEnabled.connect(setPreviewEnabled); content.requestPreviewCodeBlock.connect(previewCodeBlock); + content.requestSetPreviewContent.connect(setPreviewContent); + content.requestPerformSmartLivePreview.connect(performSmartLivePreview); if (typeof updateHtml == "function") { updateHtml(content.html); @@ -1577,3 +1579,77 @@ var htmlToText = function(identifier, id, timeStamp, html) { var markdown = ts.turndown(html); content.htmlToTextCB(identifier, id, timeStamp, markdown); }; + +var performSmartLivePreview = function(lang, text) { + if (previewDiv.style.display == 'none') { + return; + } + + if (lang != 'puml') { + return; + } + + // PlantUML. + var targetNode = findNodeWithText(previewDiv, new RegExp(text)); + if (!targetNode) { + return; + } + + // (left, top) is relative to the viewport. + // Should add window.scrollX and window.scrollY to get the real content offset. + var trect = targetNode.getBoundingClientRect(); + + var vrect = { + left: document.documentElement.scrollLeft || document.body.scrollLeft || window.pageXOffset, + top: document.documentElement.scrollTop || document.body.scrollTop || window.pageYOffset, + width: document.documentElement.clientWidth || document.body.clientWidth, + height: document.documentElement.clientHeight || document.body.clientHeight + } + + var dx = 0, dy = 0; + + // If target is already in, do not scroll. + if (trect.left < 0 + || trect.left + trect.width > vrect.width) { + if (trect.width >= vrect.width) { + dx = trect.left; + } else { + dx = trect.left - (vrect.width - trect.width) / 2; + } + } + + if (trect.top < 0 + || trect.top + trect.height > vrect.height) { + if (trect.height >= vrect.height) { + dy = trect.top; + } else { + dy = trect.top - (vrect.height - trect.width) / 2; + } + } + + window.scrollBy(dx, dy); +} + +var findNodeWithText = function(node, reg) { + var children = node.children; + if (children.length == 0) { + if (reg.test(node.textContent)) { + return node; + } else { + return null; + } + } + + for (var i = 0; i < children.length; ++i) { + var ret = findNodeWithText(children[i], reg); + if (ret) { + return ret; + } + } + + if (reg.test(node.textContent)) { + return node; + } + + return null; +} diff --git a/src/resources/vnote.ini b/src/resources/vnote.ini index 00555dd1..f18f05e2 100644 --- a/src/resources/vnote.ini +++ b/src/resources/vnote.ini @@ -245,6 +245,9 @@ max_tag_label_length=10 ; Max number of tag labels to display max_num_of_tag_labels=3 +; Smart live preview +smart_live_preview=true + [editor] ; Auto indent as previous line auto_indent=true diff --git a/src/vconfigmanager.cpp b/src/vconfigmanager.cpp index a5e40a53..0ab027b1 100644 --- a/src/vconfigmanager.cpp +++ b/src/vconfigmanager.cpp @@ -311,6 +311,9 @@ void VConfigManager::initialize() m_maxNumOfTagLabels = getConfigFromSettings("global", "max_num_of_tag_labels").toInt(); + m_smartLivePreview = getConfigFromSettings("global", + "smart_live_preview").toBool(); + initEditorConfigs(); } diff --git a/src/vconfigmanager.h b/src/vconfigmanager.h index 9689f717..8ee6cae2 100644 --- a/src/vconfigmanager.h +++ b/src/vconfigmanager.h @@ -547,6 +547,8 @@ public: QChar getVimLeaderKey() const; + bool getSmartLivePreview() const; + private: // Look up a config from user and default settings. QVariant getConfigFromSettings(const QString §ion, const QString &key) const; @@ -984,6 +986,9 @@ private: // Vim leader key. QChar m_vimLeaderKey; + // Smart live preview. + bool m_smartLivePreview; + // The name of the config file in each directory. static const QString c_dirConfigFile; @@ -2539,4 +2544,9 @@ inline QChar VConfigManager::getVimLeaderKey() const { return m_vimLeaderKey; } + +inline bool VConfigManager::getSmartLivePreview() const +{ + return m_smartLivePreview; +} #endif // VCONFIGMANAGER_H diff --git a/src/vdocument.cpp b/src/vdocument.cpp index 57a43f22..ba03a449 100644 --- a/src/vdocument.cpp +++ b/src/vdocument.cpp @@ -202,3 +202,10 @@ 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) +{ + if (!p_text.isEmpty()) { + emit requestPerformSmartLivePreview(p_lang, p_text); + } +} diff --git a/src/vdocument.h b/src/vdocument.h index f5267f92..08fb272e 100644 --- a/src/vdocument.h +++ b/src/vdocument.h @@ -75,6 +75,8 @@ public: void muteWebView(bool p_muted); + void performSmartLivePreview(const QString &p_lang, const QString &p_text); + public slots: // Will be called in the HTML side @@ -192,6 +194,8 @@ signals: void requestMuted(bool p_muted); + void requestPerformSmartLivePreview(const QString &p_lang, const QString &p_text); + private: QString m_toc; QString m_header; diff --git a/src/vlivepreviewhelper.cpp b/src/vlivepreviewhelper.cpp index a72bb996..d7a06344 100644 --- a/src/vlivepreviewhelper.cpp +++ b/src/vlivepreviewhelper.cpp @@ -95,7 +95,7 @@ VLivePreviewHelper::VLivePreviewHelper(VEditor *p_editor, { m_livePreviewTimer = new QTimer(this); m_livePreviewTimer->setSingleShot(true); - m_livePreviewTimer->setInterval(100); + m_livePreviewTimer->setInterval(500); connect(m_livePreviewTimer, &QTimer::timeout, this, &VLivePreviewHelper::handleCursorPositionChanged); @@ -214,6 +214,7 @@ void VLivePreviewHelper::updateCodeBlocks(TimeStamp p_timeStamp, const QVectortextCursorW().block().blockNumber(); - if (m_lastCursorBlock == cursorBlock) { - return; - } + if (m_lastCursorBlock != cursorBlock) { + m_lastCursorBlock = cursorBlock; - m_lastCursorBlock = cursorBlock; + int left = 0, right = m_codeBlocks.size() - 1; + int mid = left; + while (left <= right) { + mid = (left + right) / 2; + const CodeBlockPreviewInfo &cb = m_codeBlocks[mid]; + const VCodeBlock &vcb = cb.codeBlock(); + if (vcb.m_startBlock <= cursorBlock && vcb.m_endBlock >= cursorBlock) { + break; + } else if (vcb.m_startBlock > cursorBlock) { + right = mid - 1; + } else { + left = mid + 1; + } + } - int left = 0, right = m_codeBlocks.size() - 1; - int mid = left; - while (left <= right) { - mid = (left + right) / 2; - const CodeBlockPreviewInfo &cb = m_codeBlocks[mid]; - const VCodeBlock &vcb = cb.codeBlock(); - if (vcb.m_startBlock <= cursorBlock && vcb.m_endBlock >= cursorBlock) { - break; - } else if (vcb.m_startBlock > cursorBlock) { - right = mid - 1; - } else { - left = mid + 1; + if (left <= right) { + if (m_cbIndex != mid) { + m_cbIndex = mid; + updateLivePreview(); + } } } - if (left <= right) { - if (m_cbIndex != mid) { - m_cbIndex = mid; - updateLivePreview(); - } - } + performSmartLivePreview(); } void VLivePreviewHelper::updateLivePreview() @@ -396,6 +397,7 @@ void VLivePreviewHelper::localAsyncResultReady(int p_id, } m_document->setPreviewContent(lang, p_result); + performSmartLivePreview(); } else { // Inplace preview. updateInplacePreview(); @@ -523,3 +525,27 @@ void VLivePreviewHelper::clearObsoleteCache() } } } + +void VLivePreviewHelper::performSmartLivePreview() +{ + if (m_cbIndex < 0 + || m_cbIndex >= m_codeBlocks.size() + || !g_config->getSmartLivePreview()) { + return; + } + + const CodeBlockPreviewInfo &cb = m_codeBlocks[m_cbIndex]; + const VCodeBlock &vcb = cb.codeBlock(); + const QTextBlock block = m_editor->textCursorW().block(); + if (block.blockNumber() <= vcb.m_startBlock + || block.blockNumber() >= vcb.m_endBlock) { + return; + } + + QString keyword; + if (vcb.m_lang == "puml" && m_plantUMLMode == PlantUMLMode::LocalPlantUML) { + keyword = VPlantUMLHelper::keywordForSmartLivePreview(block.text()); + } + + m_document->performSmartLivePreview(vcb.m_lang, keyword); +} diff --git a/src/vlivepreviewhelper.h b/src/vlivepreviewhelper.h index 5f7ab2ce..6c0cd76d 100644 --- a/src/vlivepreviewhelper.h +++ b/src/vlivepreviewhelper.h @@ -233,6 +233,8 @@ private: void clearObsoleteCache(); + void performSmartLivePreview(); + // Sorted by m_startBlock in ascending order. QVector m_codeBlocks; diff --git a/src/vplantumlhelper.cpp b/src/vplantumlhelper.cpp index f0362230..108fb6f7 100644 --- a/src/vplantumlhelper.cpp +++ b/src/vplantumlhelper.cpp @@ -188,3 +188,40 @@ QByteArray VPlantUMLHelper::process(const QString &p_format, const QString &p_te return out; } + +static bool tryClassDiagram(QString &p_keyword) +{ + { + // class ABC #Pink { + QRegExp classDef1("class\\s*(\\w+)\\s*.*"); + if (classDef1.indexIn(p_keyword) >= 0) { + p_keyword = classDef1.cap(1); + return true; + } + } + + { + // 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 false; +} + +QString VPlantUMLHelper::keywordForSmartLivePreview(const QString &p_text) +{ + QString kw = p_text.trimmed(); + if (kw.isEmpty()) { + return kw; + } + + if (tryClassDiagram(kw)) { + return kw; + } + + return kw; +} diff --git a/src/vplantumlhelper.h b/src/vplantumlhelper.h index 31b909ab..12ab7572 100644 --- a/src/vplantumlhelper.h +++ b/src/vplantumlhelper.h @@ -23,6 +23,8 @@ public: static QByteArray process(const QString &p_format, const QString &p_text); + static QString keywordForSmartLivePreview(const QString &p_text); + signals: void resultReady(int p_id, TimeStamp p_timeStamp,