Browse Source

LivePreview: smart live preview

Le Tan 7 years ago
parent
commit
cda48a612a

+ 76 - 0
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;
+}

+ 3 - 0
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

+ 3 - 0
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();
 }
 

+ 10 - 0
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 &section, 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

+ 7 - 0
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);
+    }
+}

+ 4 - 0
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;

+ 50 - 24
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 QVector<V
 
     if (needUpdate) {
         updateLivePreview();
+        performSmartLivePreview();
     }
 
     clearObsoleteCache();
@@ -227,33 +228,33 @@ void VLivePreviewHelper::handleCursorPositionChanged()
     }
 
     int cursorBlock = m_editor->textCursorW().block().blockNumber();
-    if (m_lastCursorBlock == cursorBlock) {
-        return;
-    }
-
-    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;
+    if (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;
+            }
         }
-    }
 
-    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);
+}

+ 2 - 0
src/vlivepreviewhelper.h

@@ -233,6 +233,8 @@ private:
 
     void clearObsoleteCache();
 
+    void performSmartLivePreview();
+
     // Sorted by m_startBlock in ascending order.
     QVector<CodeBlockPreviewInfo> m_codeBlocks;
 

+ 37 - 0
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;
+}

+ 2 - 0
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,