Browse Source

support word count

Le Tan 7 years ago
parent
commit
9710659a00

+ 51 - 0
src/resources/markdown_template.js

@@ -701,6 +701,8 @@ var insertImageCaption = function() {
 // markdown-specifi handle logics, such as Mermaid, MathJax.
 var finishLogics = function() {
     content.finishLogics();
+
+    calculateWordCount();
 };
 
 // Escape @text to Html.
@@ -1107,3 +1109,52 @@ var postProcessMathJax = function() {
 
     finishLogics();
 };
+
+function getNodeText(el) {
+    ret = "";
+    var length = el.childNodes.length;
+    for(var i = 0; i < length; i++) {
+        var node = el.childNodes[i];
+        if(node.nodeType != 8) {
+            ret += node.nodeType != 1 ? node.nodeValue : getNodeText(node);
+        }
+    }
+
+    return ret;
+}
+
+var calculateWordCount = function() {
+    var words = getNodeText(placeholder);
+
+    // Char without spaces.
+    var cns = 0;
+    var wc = 0;
+    var cc = words.length;
+    // 0 - not in word;
+    // 1 - in English word;
+    // 2 - in non-English word;
+    var state = 0;
+
+    for (var i = 0; i < cc; ++i) {
+        var ch = words[i];
+        if (/\s/.test(ch)) {
+            if (state != 0) {
+                state = 0;
+            }
+
+            continue;
+        } else if (ch.charCodeAt() < 128) {
+            if (state != 1) {
+                state = 1;
+                ++wc;
+            }
+        } else {
+            state = 2;
+            ++wc;
+        }
+
+        ++cns;
+    }
+
+    content.updateWordCountInfo(wc, cns, cc);
+};

+ 2 - 1
src/src.pro

@@ -213,7 +213,8 @@ HEADERS  += vmainwindow.h \
     vstyleditemdelegate.h \
     vtreewidget.h \
     dialog/vexportdialog.h \
-    vexporter.h
+    vexporter.h \
+    vwordcountinfo.h
 
 RESOURCES += \
     vnote.qrc \

+ 12 - 0
src/vdocument.cpp

@@ -120,3 +120,15 @@ void VDocument::htmlContentCB(const QString &p_head,
 {
     emit htmlContentFinished(p_head, p_style, p_body);
 }
+
+void VDocument::updateWordCountInfo(int p_wordCount,
+                                    int p_charWithoutSpacesCount,
+                                    int p_charWithSpacesCount)
+{
+    m_wordCountInfo.m_mode = VWordCountInfo::Read;
+    m_wordCountInfo.m_wordCount = p_wordCount;
+    m_wordCountInfo.m_charWithoutSpacesCount = p_charWithoutSpacesCount;
+    m_wordCountInfo.m_charWithSpacesCount = p_charWithSpacesCount;
+
+    emit wordCountInfoUpdated();
+}

+ 18 - 0
src/vdocument.h

@@ -4,6 +4,8 @@
 #include <QObject>
 #include <QString>
 
+#include "vwordcountinfo.h"
+
 class VFile;
 
 class VDocument : public QObject
@@ -41,6 +43,8 @@ public:
     // Request to get the HTML content.
     void getHtmlContentAsync();
 
+    const VWordCountInfo &getWordCountInfo() const;
+
 public slots:
     // Will be called in the HTML side
 
@@ -74,6 +78,10 @@ public slots:
                        const QString &p_style,
                        const QString &p_body);
 
+    void updateWordCountInfo(int p_wordCount,
+                             int p_charWithoutSpacesCount,
+                             int p_charWithSpacesCount);
+
 signals:
     void textChanged(const QString &text);
 
@@ -108,6 +116,8 @@ signals:
                              const QString &p_styleContent,
                              const QString &p_bodyContent);
 
+    void wordCountInfoUpdated();
+
 private:
     QString m_toc;
     QString m_header;
@@ -125,6 +135,8 @@ private:
 
     // Whether the web side is ready to convert text to html.
     bool m_readyToTextToHtml;
+
+    VWordCountInfo m_wordCountInfo;
 };
 
 inline bool VDocument::isReadyToHighlight() const
@@ -136,4 +148,10 @@ inline bool VDocument::isReadyToTextToHtml() const
 {
     return m_readyToTextToHtml;
 }
+
+inline const VWordCountInfo &VDocument::getWordCountInfo() const
+{
+    return m_wordCountInfo;
+}
+
 #endif // VDOCUMENT_H

+ 3 - 0
src/veditor.h

@@ -10,6 +10,7 @@
 #include "veditconfig.h"
 #include "vconstants.h"
 #include "vfile.h"
+#include "vwordcountinfo.h"
 
 class QWidget;
 class VEditorObject;
@@ -149,6 +150,8 @@ public:
     // @p_modified: if true, delete the whole content and insert the new content.
     virtual void setContent(const QString &p_content, bool p_modified = false) = 0;
 
+    virtual VWordCountInfo fetchWordCountInfo() const = 0;
+
 // Wrapper functions for QPlainTextEdit/QTextEdit.
 // Ends with W to distinguish it from the original interfaces.
 public:

+ 7 - 0
src/vedittab.cpp

@@ -236,3 +236,10 @@ QString VEditTab::handleVimCmdRequestRegister(int p_key, int p_modifiers)
 
     return QString();
 }
+
+VWordCountInfo VEditTab::fetchWordCountInfo(bool p_editMode) const
+{
+    Q_UNUSED(p_editMode);
+
+    return VWordCountInfo();
+}

+ 4 - 0
src/vedittab.h

@@ -8,6 +8,7 @@
 #include "vfile.h"
 #include "utils/vvim.h"
 #include "vedittabinfo.h"
+#include "vwordcountinfo.h"
 
 class VEditArea;
 class VSnippet;
@@ -114,6 +115,9 @@ public:
     // Handle the change of file or directory, such as the file has been moved.
     virtual void handleFileOrDirectoryChange(bool p_isFile, UpdateAction p_act);
 
+    // Fetch tab stat info.
+    virtual VWordCountInfo fetchWordCountInfo(bool p_editMode) const;
+
 public slots:
     // Enter edit mode
     virtual void editFile() = 0;

+ 5 - 0
src/vedittabinfo.h

@@ -1,6 +1,8 @@
 #ifndef VEDITTABINFO_H
 #define VEDITTABINFO_H
 
+#include "vwordcountinfo.h"
+
 class VEditTab;
 
 struct VEditTabInfo
@@ -31,6 +33,7 @@ struct VEditTabInfo
         m_cursorBlockNumber = -1;
         m_cursorPositionInBlock = -1;
         m_blockCount = -1;
+        m_wordCountInfo.clear();
         m_headerIndex = -1;
     }
 
@@ -43,6 +46,8 @@ struct VEditTabInfo
     int m_cursorPositionInBlock;
     int m_blockCount;
 
+    VWordCountInfo m_wordCountInfo;
+
     // Header index in outline.
     int m_headerIndex;
 };

+ 42 - 0
src/vmdeditor.cpp

@@ -1233,3 +1233,45 @@ void VMdEditor::insertImageLink(const QString &p_text, const QString &p_url)
     }
 }
 
+VWordCountInfo VMdEditor::fetchWordCountInfo() const
+{
+    VWordCountInfo info;
+    QTextDocument *doc = document();
+
+    // Char without spaces.
+    int cns = 0;
+    int wc = 0;
+    // Remove th ending new line.
+    int cc = doc->characterCount() - 1;
+    // 0 - not in word;
+    // 1 - in English word;
+    // 2 - in non-English word;
+    int state = 0;
+
+    for (int i = 0; i < cc; ++i) {
+        QChar ch = doc->characterAt(i);
+        if (ch.isSpace()) {
+            if (state) {
+                state = 0;
+            }
+
+            continue;
+        } else if (ch.unicode() < 128) {
+            if (state != 1) {
+                state = 1;
+                ++wc;
+            }
+        } else {
+            state = 2;
+            ++wc;
+        }
+
+        ++cns;
+    }
+
+    info.m_mode = VWordCountInfo::Edit;
+    info.m_wordCount = wc;
+    info.m_charWithoutSpacesCount = cns;
+    info.m_charWithSpacesCount = cc;
+    return info;
+}

+ 2 - 0
src/vmdeditor.h

@@ -67,6 +67,8 @@ public:
     // Update m_initImages and m_insertedImages to handle the change of the note path.
     void updateInitAndInsertedImages(bool p_fileChanged, UpdateAction p_act);
 
+    VWordCountInfo fetchWordCountInfo() const Q_DECL_OVERRIDE;
+
 public slots:
     bool jumpTitle(bool p_forward, int p_relativeLevel, int p_repeat) Q_DECL_OVERRIDE;
 

+ 47 - 5
src/vmdtab.cpp

@@ -85,17 +85,22 @@ void VMdTab::showFileReadMode()
     // Will recover the header when web side is ready.
     m_headerFromEditMode = m_currentHeader;
 
+    updateWebView();
+
+    m_stacks->setCurrentWidget(m_webViewer);
+    clearSearchedWordHighlight();
+
+    updateStatus();
+}
+
+void VMdTab::updateWebView()
+{
     if (m_mdConType == MarkdownConverterType::Hoedown) {
         viewWebByConverter();
     } else {
         m_document->updateText();
         updateOutlineFromHtml(m_document->getToc());
     }
-
-    m_stacks->setCurrentWidget(m_webViewer);
-    clearSearchedWordHighlight();
-
-    updateStatus();
 }
 
 bool VMdTab::scrollWebViewToHeader(const VHeaderPointer &p_header)
@@ -424,6 +429,15 @@ void VMdTab::setupMarkdownViewer()
                 Q_ASSERT(m_editor);
                 m_editor->textToHtmlFinished(p_text, m_webViewer->url(), p_html);
             });
+    connect(m_document, &VDocument::wordCountInfoUpdated,
+            this, [this]() {
+                VEditTabInfo info = fetchTabInfo(VEditTabInfo::InfoType::All);
+                if (m_isEditMode) {
+                    info.m_wordCountInfo = m_document->getWordCountInfo();
+                }
+
+                emit statusUpdated(info);
+            });
 
     page->setWebChannel(channel);
 
@@ -790,6 +804,16 @@ VEditTabInfo VMdTab::fetchTabInfo(VEditTabInfo::InfoType p_type) const
         info.m_blockCount = m_editor->document()->blockCount();
     }
 
+    if (m_isEditMode) {
+        if (m_editor) {
+            // We do not get the full word count info in edit mode.
+            info.m_wordCountInfo.m_mode = VWordCountInfo::Edit;
+            info.m_wordCountInfo.m_charWithSpacesCount = m_editor->document()->characterCount() - 1;
+        }
+    } else {
+        info.m_wordCountInfo = m_document->getWordCountInfo();
+    }
+
     info.m_headerIndex = m_currentHeader.m_index;
 
     return info;
@@ -1257,3 +1281,21 @@ void VMdTab::handleSavePageRequested()
 
     m_webViewer->page()->save(fileName, format);
 }
+
+VWordCountInfo VMdTab::fetchWordCountInfo(bool p_editMode) const
+{
+    if (p_editMode) {
+        if (m_editor) {
+            return m_editor->fetchWordCountInfo();
+        }
+    } else {
+        // Request to update with current text.
+        if (m_isEditMode) {
+            const_cast<VMdTab *>(this)->updateWebView();
+        }
+
+        return m_document->getWordCountInfo();
+    }
+
+    return VWordCountInfo();
+}

+ 6 - 0
src/vmdtab.h

@@ -88,6 +88,9 @@ public:
 
     void handleFileOrDirectoryChange(bool p_isFile, UpdateAction p_act) Q_DECL_OVERRIDE;
 
+    // Fetch tab stat info.
+    VWordCountInfo fetchWordCountInfo(bool p_editMode) const Q_DECL_OVERRIDE;
+
 public slots:
     // Enter edit mode.
     void editFile() Q_DECL_OVERRIDE;
@@ -207,6 +210,9 @@ private:
 
     bool executeVimCommandInWebView(const QString &p_cmd);
 
+    // Update web view by current content.
+    void updateWebView();
+
     VMdEditor *m_editor;
     VWebView *m_webViewer;
     VDocument *m_document;

+ 180 - 8
src/vtabindicator.cpp

@@ -1,13 +1,114 @@
 #include "vtabindicator.h"
 
-#include <QLabel>
-#include <QHBoxLayout>
+#include <QtWidgets>
 
 #include "vedittab.h"
 #include "vorphanfile.h"
+#include "vbuttonwithwidget.h"
+#include "vwordcountinfo.h"
+
+class VWordCountPanel : public QWidget
+{
+public:
+    VWordCountPanel(QWidget *p_parent)
+        : QWidget(p_parent)
+    {
+        m_wordLabel = new QLabel();
+        m_wordLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
+        m_charWithoutSpacesLabel = new QLabel();
+        m_charWithoutSpacesLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
+        m_charWithSpacesLabel = new QLabel();
+        m_charWithSpacesLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
+
+        QFormLayout *readLayout = new QFormLayout();
+        readLayout->addRow(tr("Words"), m_wordLabel);
+        readLayout->addRow(tr("Characters (no spaces)"), m_charWithoutSpacesLabel);
+        readLayout->addRow(tr("Characters (with spaces)"), m_charWithSpacesLabel);
+        m_readBox = new QGroupBox(tr("Read"));
+        m_readBox->setLayout(readLayout);
+
+        m_wordEditLabel = new QLabel();
+        m_wordEditLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
+        m_charWithoutSpacesEditLabel = new QLabel();
+        m_charWithoutSpacesEditLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
+        m_charWithSpacesEditLabel = new QLabel();
+        m_charWithSpacesEditLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
+
+        QLabel *cwsLabel = new QLabel(tr("Characters (with spaces)"));
+        cwsLabel->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
+
+        QFormLayout *editLayout = new QFormLayout();
+        editLayout->addRow(tr("Words"), m_wordEditLabel);
+        editLayout->addRow(tr("Characters (no spaces)"), m_charWithoutSpacesEditLabel);
+        editLayout->addRow(cwsLabel, m_charWithSpacesEditLabel);
+        m_editBox = new QGroupBox(tr("Edit"));
+        m_editBox->setLayout(editLayout);
+
+        QLabel *titleLabel = new QLabel(tr("Word Count"));
+        titleLabel->setProperty("TitleLabel", true);
+        QVBoxLayout *mainLayout = new QVBoxLayout();
+        mainLayout->addWidget(titleLabel);
+        mainLayout->addWidget(m_readBox);
+        mainLayout->addWidget(m_editBox);
+
+        setLayout(mainLayout);
+
+        setMinimumWidth(300);
+    }
+
+    void updateReadInfo(const VWordCountInfo &p_readInfo)
+    {
+        if (p_readInfo.isNull()) {
+            m_wordLabel->clear();
+            m_charWithoutSpacesLabel->clear();
+            m_charWithSpacesLabel->clear();
+        } else {
+            m_wordLabel->setText(QString::number(p_readInfo.m_wordCount));
+            m_charWithoutSpacesLabel->setText(QString::number(p_readInfo.m_charWithoutSpacesCount));
+            m_charWithSpacesLabel->setText(QString::number(p_readInfo.m_charWithSpacesCount));
+        }
+    }
+
+    void updateEditInfo(const VWordCountInfo &p_editInfo)
+    {
+        if (p_editInfo.isNull()) {
+            m_wordEditLabel->clear();
+            m_charWithoutSpacesEditLabel->clear();
+            m_charWithSpacesEditLabel->clear();
+        } else {
+            m_wordEditLabel->setText(QString::number(p_editInfo.m_wordCount));
+            m_charWithoutSpacesEditLabel->setText(QString::number(p_editInfo.m_charWithoutSpacesCount));
+            m_charWithSpacesEditLabel->setText(QString::number(p_editInfo.m_charWithSpacesCount));
+        }
+    }
+
+    void clear()
+    {
+        m_wordLabel->clear();
+        m_charWithoutSpacesLabel->clear();
+        m_charWithSpacesLabel->clear();
+
+        m_wordEditLabel->clear();
+        m_charWithoutSpacesEditLabel->clear();
+        m_charWithSpacesEditLabel->clear();
+    }
+
+private:
+    QLabel *m_wordLabel;
+    QLabel *m_charWithoutSpacesLabel;
+    QLabel *m_charWithSpacesLabel;
+
+    QLabel *m_wordEditLabel;
+    QLabel *m_charWithoutSpacesEditLabel;
+    QLabel *m_charWithSpacesEditLabel;
+
+    QGroupBox *m_readBox;
+    QGroupBox *m_editBox;
+};
 
 VTabIndicator::VTabIndicator(QWidget *p_parent)
-    : QWidget(p_parent)
+    : QWidget(p_parent),
+      m_editTab(NULL)
 {
     setupUI();
 }
@@ -33,8 +134,17 @@ void VTabIndicator::setupUI()
     m_cursorLabel = new QLabel(this);
     m_cursorLabel->setProperty("TabIndicatorLabel", true);
 
+    m_wordCountPanel = new VWordCountPanel(this);
+    m_wordCountBtn = new VButtonWithWidget(tr("[W]"), m_wordCountPanel, this);
+    m_wordCountBtn->setToolTip(tr("Word Count Information"));
+    m_wordCountBtn->setProperty("StatusBtn", true);
+    m_wordCountBtn->setFocusPolicy(Qt::NoFocus);
+    connect(m_wordCountBtn, &VButtonWithWidget::popupWidgetAboutToShow,
+            this, &VTabIndicator::updateWordCountInfo);
+
     QHBoxLayout *mainLayout = new QHBoxLayout(this);
     mainLayout->addWidget(m_cursorLabel);
+    mainLayout->addWidget(m_wordCountBtn);
     mainLayout->addWidget(m_externalLabel);
     mainLayout->addWidget(m_systemLabel);
     mainLayout->addWidget(m_readonlyLabel);
@@ -75,7 +185,6 @@ static QString docTypeToString(DocType p_type)
 
 void VTabIndicator::update(const VEditTabInfo &p_info)
 {
-    const VEditTab *editTab = NULL;
     const VFile *file = NULL;
     DocType docType = DocType::Html;
     bool readonly = false;
@@ -83,16 +192,17 @@ void VTabIndicator::update(const VEditTabInfo &p_info)
     bool system = false;
     QString cursorStr;
 
-    if (p_info.m_editTab)
+    m_editTab = p_info.m_editTab;
+
+    if (m_editTab)
     {
-        editTab = p_info.m_editTab;
-        file = editTab->getFile();
+        file = m_editTab->getFile();
         docType = file->getDocType();
         readonly = !file->isModifiable();
         external = file->getType() == FileType::Orphan;
         system = external && dynamic_cast<const VOrphanFile *>(file)->isSystemFile();
 
-        if (editTab->isEditMode()) {
+        if (m_editTab->isEditMode()) {
             int line = p_info.m_cursorBlockNumber + 1;
             int col = p_info.m_cursorPositionInBlock;
             if (col < 0) {
@@ -113,8 +223,70 @@ void VTabIndicator::update(const VEditTabInfo &p_info)
         }
     }
 
+    updateWordCountBtn(p_info);
+
+    if (p_info.m_wordCountInfo.m_mode == VWordCountInfo::Read) {
+        m_wordCountPanel->updateReadInfo(p_info.m_wordCountInfo);
+    }
+
     m_docTypeLabel->setText(docTypeToString(docType));
     m_readonlyLabel->setVisible(readonly);
     m_externalLabel->setVisible(external);
     m_systemLabel->setVisible(system);
 }
+
+void VTabIndicator::updateWordCountInfo(QWidget *p_widget)
+{
+    VWordCountPanel *wcp = dynamic_cast<VWordCountPanel *>(p_widget);
+    if (!m_editTab) {
+        wcp->clear();
+        return;
+    }
+
+    wcp->updateReadInfo(m_editTab->fetchWordCountInfo(false));
+    wcp->updateEditInfo(m_editTab->fetchWordCountInfo(true));
+}
+
+void VTabIndicator::updateWordCountBtn(const VEditTabInfo &p_info)
+{
+    const VEditTab *editTab = p_info.m_editTab;
+    if (!editTab) {
+        m_wordCountBtn->setText(tr("[W]"));
+        return;
+    }
+
+    const VWordCountInfo &wci = p_info.m_wordCountInfo;
+    bool editMode = editTab->isEditMode();
+    int wc = -1;
+    bool needUpdate = false;
+    switch (wci.m_mode) {
+    case VWordCountInfo::Read:
+        if (!editMode) {
+            wc = wci.m_wordCount;
+            needUpdate = true;
+        }
+
+        break;
+
+    case VWordCountInfo::Edit:
+        if (editMode) {
+            wc = wci.m_charWithSpacesCount;
+            needUpdate = true;
+        }
+
+        break;
+
+    case VWordCountInfo::Invalid:
+        needUpdate = true;
+        break;
+
+    default:
+        break;
+    }
+
+    if (needUpdate) {
+        QString text = tr("[%1]%2").arg(editMode ? tr("C") : tr("W"))
+                                   .arg(wc > -1 ? QString::number(wc) : "");
+        m_wordCountBtn->setText(text);
+    }
+}

+ 15 - 0
src/vtabindicator.h

@@ -5,6 +5,9 @@
 #include "vedittabinfo.h"
 
 class QLabel;
+class VButtonWithWidget;
+class VEditTab;
+class VWordCountPanel;
 
 class VTabIndicator : public QWidget
 {
@@ -16,9 +19,14 @@ public:
     // Update indicator.
     void update(const VEditTabInfo &p_info);
 
+private slots:
+    void updateWordCountInfo(QWidget *p_widget);
+
 private:
     void setupUI();
 
+    void updateWordCountBtn(const VEditTabInfo &p_info);
+
     // Indicate the doc type.
     QLabel *m_docTypeLabel;
 
@@ -33,6 +41,13 @@ private:
 
     // Indicate the position of current cursor.
     QLabel *m_cursorLabel;
+
+    // Indicate the word count.
+    VButtonWithWidget *m_wordCountBtn;
+
+    VEditTab *m_editTab;
+
+    VWordCountPanel *m_wordCountPanel;
 };
 
 #endif // VTABINDICATOR_H

+ 53 - 0
src/vwordcountinfo.h

@@ -0,0 +1,53 @@
+#ifndef VWORDCOUNTINFO_H
+#define VWORDCOUNTINFO_H
+
+#include <QString>
+#include <QDebug>
+
+
+struct VWordCountInfo
+{
+    enum Mode
+    {
+        Read = 0,
+        Edit,
+        Invalid
+    };
+
+    VWordCountInfo()
+        : m_mode(Mode::Invalid),
+          m_wordCount(-1),
+          m_charWithoutSpacesCount(-1),
+          m_charWithSpacesCount(-1)
+    {
+    }
+
+    bool isNull() const
+    {
+        return m_mode == Mode::Invalid;
+    }
+
+    void clear()
+    {
+        m_mode = Mode::Invalid;
+        m_wordCount = -1;
+        m_charWithoutSpacesCount = -1;
+        m_charWithSpacesCount = -1;
+    }
+
+    QString toString() const
+    {
+        return QString("VWordCountInfo mode %1 WC %2 CNSC %3 CSC %4")
+                      .arg(m_mode)
+                      .arg(m_wordCount)
+                      .arg(m_charWithoutSpacesCount)
+                      .arg(m_charWithSpacesCount);
+    }
+
+    Mode m_mode;
+    int m_wordCount;
+    int m_charWithoutSpacesCount;
+    int m_charWithSpacesCount;
+};
+
+#endif // VWORDCOUNTINFO_H