Browse Source

vim-mode: add an indicator for Vim status in status bar

Le Tan 8 years ago
parent
commit
d909091f46

+ 4 - 3
src/resources/icons/current_tab.svg → src/resources/icons/arrow_dropup.svg

@@ -2,7 +2,8 @@
 <!-- Generator: Adobe Illustrator 16.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
 <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
 <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
-	 width="512px" height="512px" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
-<path d="M184.7,413.1l2.1-1.8l156.5-136c5.3-4.6,8.6-11.5,8.6-19.2c0-7.7-3.4-14.6-8.6-19.2L187.1,101l-2.6-2.3
-	C182,97,179,96,175.8,96c-8.7,0-15.8,7.4-15.8,16.6h0v286.8h0c0,9.2,7.1,16.6,15.8,16.6C179.1,416,182.2,414.9,184.7,413.1z"/>
+	 width="512px" height="512px" viewBox="0 0 512 512" enable-background="new 0 0 512 512" xml:space="preserve">
+<g>
+	<polygon points="128,320 256,192 384,320 	"/>
+</g>
 </svg>

+ 16 - 0
src/resources/vnote.qss

@@ -17,6 +17,22 @@ QPushButton[CornerBtn="true"]::focus {
     background-color: @focus-color;
 }
 
+QPushButton[StatusBtn="true"] {
+    font: bold;
+    padding: 0px 2px 0px 2px;
+    margin: 0px;
+    border: none;
+    background-color: transparent;
+}
+
+QPushButton[StatusBtn="true"]::hover {
+    background-color: @hover-color;
+}
+
+QPushButton[StatusBtn="true"]::focus {
+    background-color: @focus-color;
+}
+
 QPushButton[FlatBtn="true"] {
     padding: 4px;
     margin: 0px;

+ 6 - 2
src/src.pro

@@ -66,7 +66,9 @@ SOURCES += main.cpp\
     vmdtab.cpp \
     vhtmltab.cpp \
     utils/vvim.cpp \
-    utils/veditutils.cpp
+    utils/veditutils.cpp \
+    vvimindicator.cpp \
+    vbuttonwithwidget.cpp
 
 HEADERS  += vmainwindow.h \
     vdirectorytree.h \
@@ -119,7 +121,9 @@ HEADERS  += vmainwindow.h \
     vmdtab.h \
     vhtmltab.h \
     utils/vvim.h \
-    utils/veditutils.h
+    utils/veditutils.h \
+    vvimindicator.h \
+    vbuttonwithwidget.h
 
 RESOURCES += \
     vnote.qrc \

+ 35 - 3
src/utils/vvim.cpp

@@ -290,12 +290,16 @@ bool VVim::handleKeyPressEvent(QKeyEvent *p_event)
         goto accept;
     }
 
+    m_pendingKeys.append(keyInfo);
+
     if (expectingRegisterName()) {
         // Expecting a register name.
         QChar reg = keyToRegisterName(keyInfo);
         if (!reg.isNull()) {
-            resetState();
-            m_regName = reg;
+            // We should keep m_pendingKeys.
+            m_keys.clear();
+            m_tokens.clear();
+            setRegister(reg);
             if (m_registers[reg].isNamedRegister()) {
                 m_registers[reg].m_append = (modifiers == Qt::ShiftModifier);
             } else {
@@ -1281,6 +1285,7 @@ accept:
 
 exit:
     m_resetPositionInBlock = resetPositionInBlock;
+    emit vimStatusUpdated(this);
     return ret;
 }
 
@@ -1288,7 +1293,8 @@ void VVim::resetState()
 {
     m_keys.clear();
     m_tokens.clear();
-    m_regName = c_unnamedRegister;
+    m_pendingKeys.clear();
+    setRegister(c_unnamedRegister);
     m_resetPositionInBlock = true;
 }
 
@@ -1305,6 +1311,7 @@ void VVim::setMode(VimMode p_mode)
         resetState();
 
         emit modeChanged(m_mode);
+        emit vimStatusUpdated(this);
     }
 }
 
@@ -3380,3 +3387,28 @@ void VVim::message(const QString &p_msg)
     qDebug() << "vim msg:" << p_msg;
     emit vimMessage(p_msg);
 }
+
+const QMap<QChar, VVim::Register> &VVim::getRegisters() const
+{
+    return m_registers;
+}
+
+QChar VVim::getCurrentRegisterName() const
+{
+    return m_regName;
+}
+
+QString VVim::getPendingKeys() const
+{
+    QString str;
+    for (auto const & key : m_pendingKeys) {
+        str.append(keyToChar(key.m_key, key.m_modifiers));
+    }
+
+    return str;
+}
+
+void VVim::setRegister(QChar p_reg)
+{
+    m_regName = p_reg;
+}

+ 83 - 65
src/utils/vvim.h

@@ -4,7 +4,7 @@
 #include <QObject>
 #include <QString>
 #include <QTextCursor>
-#include <QHash>
+#include <QMap>
 #include <QDebug>
 #include "vutils.h"
 
@@ -28,6 +28,69 @@ class VVim : public QObject
 public:
     explicit VVim(VEdit *p_editor);
 
+    struct Register
+    {
+        Register(QChar p_name, const QString &p_value)
+            : m_name(p_name), m_value(p_value), m_append(false)
+        {
+        }
+
+        Register(QChar p_name)
+            : m_name(p_name), m_append(false)
+        {
+        }
+
+        Register()
+            : m_append(false)
+        {
+        }
+
+        // Register a-z.
+        bool isNamedRegister() const
+        {
+            char ch = m_name.toLatin1();
+            return ch >= 'a' && ch <= 'z';
+        }
+
+        bool isUnnamedRegister() const
+        {
+            return m_name == c_unnamedRegister;
+        }
+
+        bool isBlackHoleRegister() const
+        {
+            return m_name == c_blackHoleRegister;
+        }
+
+        bool isSelectionRegister() const
+        {
+            return m_name == c_selectionRegister;
+        }
+
+        bool isBlock() const
+        {
+            return m_value.endsWith('\n');
+        }
+
+        // @p_value is the content to update.
+        // If @p_value ends with \n, then it is a block.
+        // When @p_value is a block, we need to add \n at the end if necessary.
+        // If @m_append is true and @p_value is a block, we need to add \n between
+        // them if necessary.
+        void update(const QString &p_value);
+
+        // Read the value of this register.
+        const QString &read();
+
+        QChar m_name;
+        QString m_value;
+
+        // This is not info of Register itself, but a hint to the handling logics
+        // whether we need to append the content to this register.
+        // Only valid for a-z registers.
+        bool m_append;
+    };
+
     // Handle key press event.
     // Returns true if the event is consumed and need no more handling.
     bool handleKeyPressEvent(QKeyEvent *p_event);
@@ -38,6 +101,18 @@ public:
     // Set current mode.
     void setMode(VimMode p_mode);
 
+    // Set current register.
+    void setRegister(QChar p_reg);
+
+    // Get m_registers.
+    const QMap<QChar, Register> &getRegisters() const;
+
+    QChar getCurrentRegisterName() const;
+
+    // Get pending keys.
+    // Turn m_pendingKeys to a string.
+    QString getPendingKeys() const;
+
 signals:
     // Emit when current mode has been changed.
     void modeChanged(VimMode p_mode);
@@ -45,6 +120,9 @@ signals:
     // Emit when VVim want to display some messages.
     void vimMessage(const QString &p_msg);
 
+    // Emit when current status updated.
+    void vimStatusUpdated(const VVim *p_vim);
+
 private slots:
     // When user use mouse to select texts in Normal mode, we should change to
     // Visual mode.
@@ -265,69 +343,6 @@ private:
         Key m_key;
     };
 
-    struct Register
-    {
-        Register(QChar p_name, const QString &p_value)
-            : m_name(p_name), m_value(p_value), m_append(false)
-        {
-        }
-
-        Register(QChar p_name)
-            : m_name(p_name), m_append(false)
-        {
-        }
-
-        Register()
-            : m_append(false)
-        {
-        }
-
-        // Register a-z.
-        bool isNamedRegister() const
-        {
-            char ch = m_name.toLatin1();
-            return ch >= 'a' && ch <= 'z';
-        }
-
-        bool isUnnamedRegister() const
-        {
-            return m_name == c_unnamedRegister;
-        }
-
-        bool isBlackHoleRegister() const
-        {
-            return m_name == c_blackHoleRegister;
-        }
-
-        bool isSelectionRegister() const
-        {
-            return m_name == c_selectionRegister;
-        }
-
-        bool isBlock() const
-        {
-            return m_value.endsWith('\n');
-        }
-
-        // @p_value is the content to update.
-        // If @p_value ends with \n, then it is a block.
-        // When @p_value is a block, we need to add \n at the end if necessary.
-        // If @m_append is true and @p_value is a block, we need to add \n between
-        // them if necessary.
-        void update(const QString &p_value);
-
-        // Read the value of this register.
-        const QString &read();
-
-        QChar m_name;
-        QString m_value;
-
-        // This is not info of Register itself, but a hint to the handling logics
-        // whether we need to append the content to this register.
-        // Only valid for a-z registers.
-        bool m_append;
-    };
-
     // Reset all key state info.
     void resetState();
 
@@ -468,10 +483,13 @@ private:
     QList<Key> m_keys;
     QList<Token> m_tokens;
 
+    // Keys for status indication.
+    QList<Key> m_pendingKeys;
+
     // Whether reset the position in block when moving cursor.
     bool m_resetPositionInBlock;
 
-    QHash<QChar, Register> m_registers;
+    QMap<QChar, Register> m_registers;
 
     // Currently used register.
     QChar m_regName;

+ 98 - 0
src/vbuttonwithwidget.cpp

@@ -0,0 +1,98 @@
+#include "vbuttonwithwidget.h"
+
+#include <QEvent>
+#include <QMouseEvent>
+#include <QRect>
+
+VButtonWithWidget::VButtonWithWidget(QWidget *p_parent)
+    : QPushButton(p_parent), m_popupWidget(NULL)
+{
+    init();
+}
+
+VButtonWithWidget::VButtonWithWidget(const QString &p_text, QWidget *p_parent)
+    : QPushButton(p_text, p_parent), m_popupWidget(NULL)
+{
+    init();
+}
+
+VButtonWithWidget::VButtonWithWidget(const QIcon &p_icon,
+                                     const QString &p_text,
+                                     QWidget *p_parent)
+    : QPushButton(p_icon, p_text, p_parent), m_popupWidget(NULL)
+{
+    init();
+}
+
+VButtonWithWidget::~VButtonWithWidget()
+{
+    if (m_popupWidget) {
+        delete m_popupWidget;
+    }
+}
+
+void VButtonWithWidget::init()
+{
+    connect(this, &QPushButton::clicked,
+            this, &VButtonWithWidget::showPopupWidget);
+}
+
+void VButtonWithWidget::setPopupWidget(QWidget *p_widget)
+{
+    if (m_popupWidget) {
+        delete m_popupWidget;
+    }
+
+    m_popupWidget = p_widget;
+    if (m_popupWidget) {
+        m_popupWidget->hide();
+        m_popupWidget->setParent(NULL);
+
+        Qt::WindowFlags flags = Qt::Popup;
+        m_popupWidget->setWindowFlags(flags);
+        m_popupWidget->setWindowModality(Qt::NonModal);
+
+        // Let popup widget to hide itself if focus lost.
+        m_popupWidget->installEventFilter(this);
+    }
+}
+
+QWidget *VButtonWithWidget::getPopupWidget() const
+{
+    return m_popupWidget;
+}
+
+void VButtonWithWidget::showPopupWidget()
+{
+    if (m_popupWidget->isVisible()) {
+        m_popupWidget->hide();
+    } else {
+        emit popupWidgetAboutToShow(m_popupWidget);
+
+        // Calculate the position of the popup widget.
+        QPoint btnPos = mapToGlobal(QPoint(0, 0));
+        int btnWidth = width();
+
+        int popupWidth = btnWidth * 10;
+        int popupHeight = height() * 10;
+        int popupX = btnPos.x() + btnWidth - popupWidth;
+        int popupY = btnPos.y() - popupHeight - 10;
+
+        m_popupWidget->setGeometry(popupX, popupY, popupWidth, popupHeight);
+        m_popupWidget->show();
+    }
+}
+
+bool VButtonWithWidget::eventFilter(QObject *p_obj, QEvent *p_event)
+{
+    if (p_event->type() == QEvent::MouseButtonRelease) {
+        QMouseEvent *eve = dynamic_cast<QMouseEvent *>(p_event);
+        QPoint clickPos = eve->pos();
+        const QRect &rect = m_popupWidget->rect();
+        if (!rect.contains(clickPos)) {
+            m_popupWidget->hide();
+        }
+    }
+
+    return QPushButton::eventFilter(p_obj, p_event);
+}

+ 42 - 0
src/vbuttonwithwidget.h

@@ -0,0 +1,42 @@
+#ifndef VBUTTONWITHWIDGET_H
+#define VBUTTONWITHWIDGET_H
+
+#include <QPushButton>
+#include <QString>
+#include <QIcon>
+
+// A QPushButton with popup widget.
+class VButtonWithWidget : public QPushButton
+{
+    Q_OBJECT
+public:
+    VButtonWithWidget(QWidget *p_parent = Q_NULLPTR);
+    VButtonWithWidget(const QString &p_text, QWidget *p_parent = Q_NULLPTR);
+    VButtonWithWidget(const QIcon &p_icon,
+                      const QString &p_text,
+                      QWidget *p_parent = Q_NULLPTR);
+    ~VButtonWithWidget();
+
+    // Set the widget which will transfer the ownership to VButtonWithWidget.
+    void setPopupWidget(QWidget *p_widget);
+
+    QWidget *getPopupWidget() const;
+
+signals:
+    // Emit when popup widget is about to show.
+    void popupWidgetAboutToShow(QWidget *p_widget);
+
+protected:
+    bool eventFilter(QObject *p_obj, QEvent *p_event) Q_DECL_OVERRIDE;
+
+private slots:
+    // Show the popup widget.
+    void showPopupWidget();
+
+private:
+    void init();
+
+    QWidget *m_popupWidget;
+};
+
+#endif // VBUTTONWITHWIDGET_H

+ 9 - 0
src/vedit.cpp

@@ -781,3 +781,12 @@ void VEdit::mouseMoveEvent(QMouseEvent *p_event)
 
     QTextEdit::mouseMoveEvent(p_event);
 }
+
+void VEdit::requestUpdateVimStatus()
+{
+    if (m_editOps) {
+        m_editOps->requestUpdateVimStatus();
+    } else {
+        emit vimStatusUpdated(NULL);
+    }
+}

+ 7 - 0
src/vedit.h

@@ -15,6 +15,7 @@
 class VEditOperations;
 class QLabel;
 class QTimer;
+class VVim;
 
 enum class SelectionId {
     CurrentLine = 0,
@@ -81,6 +82,9 @@ public:
 
     VEditConfig &getConfig();
 
+    // Request to update Vim status.
+    void requestUpdateVimStatus();
+
 signals:
     void saveAndRead();
     void discardAndRead();
@@ -92,6 +96,9 @@ signals:
     // Emit when want to show message in status bar.
     void statusMessage(const QString &p_msg);
 
+    // Emit when Vim status updated.
+    void vimStatusUpdated(const VVim *p_vim);
+
 public slots:
     virtual void highlightCurrentLine();
 

+ 9 - 0
src/veditarea.cpp

@@ -81,6 +81,8 @@ void VEditArea::insertSplitWindow(int idx)
             this, &VEditArea::handleCurHeaderChanged);
     connect(win, &VEditWindow::statusMessage,
             this, &VEditArea::handleWindowStatusMessage);
+    connect(win, &VEditWindow::vimStatusUpdated,
+            this, &VEditArea::handleWindowVimStatusUpdated);
 }
 
 void VEditArea::handleEditWindowStatusChanged(const VFile *p_file,
@@ -99,6 +101,13 @@ void VEditArea::handleWindowStatusMessage(const QString &p_msg)
     }
 }
 
+void VEditArea::handleWindowVimStatusUpdated(const VVim *p_vim)
+{
+    if (splitter->widget(curWindowIndex) == sender()) {
+        emit vimStatusUpdated(p_vim);
+    }
+}
+
 void VEditArea::removeSplitWindow(VEditWindow *win)
 {
     if (!win) {

+ 7 - 0
src/veditarea.h

@@ -20,6 +20,7 @@ class VFile;
 class VDirectory;
 class VFindReplaceDialog;
 class QLabel;
+class VVim;
 
 class VEditArea : public QWidget, public VNavigationMode
 {
@@ -66,6 +67,9 @@ signals:
     // Emit when want to show message in status bar.
     void statusMessage(const QString &p_msg);
 
+    // Emit when Vim status updated.
+    void vimStatusUpdated(const VVim *p_vim);
+
 protected:
     void mousePressEvent(QMouseEvent *event) Q_DECL_OVERRIDE;
 
@@ -101,6 +105,9 @@ private slots:
     // Handle the statusMessage signal of VEditWindow.
     void handleWindowStatusMessage(const QString &p_msg);
 
+    // Handle the vimStatusUpdated signal of VEditWindow.
+    void handleWindowVimStatusUpdated(const VVim *p_vim);
+
 private:
     void setupUI();
     QVector<QPair<int, int> > findTabsByFile(const VFile *p_file);

+ 11 - 0
src/veditoperations.cpp

@@ -20,6 +20,8 @@ VEditOperations::VEditOperations(VEdit *p_editor, VFile *p_file)
             this, &VEditOperations::handleVimModeChanged);
     connect(m_vim, &VVim::vimMessage,
             this, &VEditOperations::statusMessage);
+    connect(m_vim, &VVim::vimStatusUpdated,
+            this, &VEditOperations::vimStatusUpdated);
 }
 
 void VEditOperations::insertTextAtCurPos(const QString &p_text)
@@ -80,3 +82,12 @@ void VEditOperations::handleVimModeChanged(VimMode p_mode)
 
     updateCursorLineBg();
 }
+
+void VEditOperations::requestUpdateVimStatus()
+{
+    if (m_editConfig->m_enableVimMode) {
+        emit vimStatusUpdated(m_vim);
+    } else {
+        emit vimStatusUpdated(NULL);
+    }
+}

+ 6 - 0
src/veditoperations.h

@@ -27,10 +27,16 @@ public:
     // processed.
     virtual bool handleKeyPressEvent(QKeyEvent *p_event) = 0;
 
+    // Request to propogate Vim status.
+    void requestUpdateVimStatus();
+
 signals:
     // Want to display a template message in status bar.
     void statusMessage(const QString &p_msg);
 
+    // Propogate Vim status.
+    void vimStatusUpdated(const VVim *p_vim);
+
 protected slots:
     // Handle the update of VEditConfig of the editor.
     virtual void handleEditConfigUpdated();

+ 6 - 0
src/vedittab.h

@@ -6,6 +6,7 @@
 #include <QPointer>
 #include "vtoc.h"
 #include "vfile.h"
+#include "utils/vvim.h"
 
 class VEditArea;
 
@@ -63,6 +64,9 @@ public:
 
     virtual void clearSearchedWordHighlight() = 0;
 
+    // Request current tab to propogate its status about Vim.
+    virtual void requestUpdateVimStatus() = 0;
+
 public slots:
     // Enter edit mode
     virtual void editFile() = 0;
@@ -96,6 +100,8 @@ signals:
     // Emit when want to show message in status bar.
     void statusMessage(const QString &p_msg);
 
+    void vimStatusUpdated(const VVim *p_vim);
+
 private slots:
     // Called when app focus changed.
     void handleFocusChanged(QWidget *p_old, QWidget *p_now);

+ 10 - 0
src/veditwindow.cpp

@@ -270,6 +270,8 @@ int VEditWindow::openFileInTab(VFile *p_file, OpenFileMode p_mode)
             this, &VEditWindow::handleTabStatusChanged);
     connect(editor, &VEditTab::statusMessage,
             this, &VEditWindow::handleTabStatusMessage);
+    connect(editor, &VEditTab::vimStatusUpdated,
+            this, &VEditWindow::handleTabVimStatusUpdated);
 
     int idx = appendEditTab(p_file, editor);
     return idx;
@@ -586,6 +588,14 @@ void VEditWindow::handleTabStatusMessage(const QString &p_msg)
     }
 }
 
+void VEditWindow::handleTabVimStatusUpdated(const VVim *p_vim)
+{
+    int idx = indexOf(dynamic_cast<QWidget *>(sender()));
+    if (idx == currentIndex()) {
+        emit vimStatusUpdated(p_vim);
+    }
+}
+
 void VEditWindow::updateFileInfo(const VFile *p_file)
 {
     if (!p_file) {

+ 6 - 0
src/veditwindow.h

@@ -71,6 +71,9 @@ signals:
     // Emit when want to show message in status bar.
     void statusMessage(const QString &p_msg);
 
+    // Emit when Vim mode status changed.
+    void vimStatusUpdated(const VVim *p_vim);
+
 private slots:
     bool handleTabCloseRequest(int index);
     void splitWindow();
@@ -91,6 +94,9 @@ private slots:
     // Handle the statusMessage signal of VEditTab.
     void handleTabStatusMessage(const QString &p_msg);
 
+    // Handle the vimStatusUpdated() signal of VEditTab.
+    void handleTabVimStatusUpdated(const VVim *p_vim);
+
 private:
     void initTabActions();
     void setupCornerWidget();

+ 8 - 0
src/vhtmltab.cpp

@@ -41,6 +41,9 @@ void VHtmlTab::setupUI()
             this, &VHtmlTab::editFile);
     connect(m_editor, &VEdit::statusMessage,
             this, &VEditTab::statusMessage);
+    connect(m_editor, &VEdit::vimStatusUpdated,
+            this, &VEditTab::vimStatusUpdated);
+
     m_editor->reloadFile();
 
     QVBoxLayout *mainLayout = new QVBoxLayout();
@@ -250,3 +253,8 @@ void VHtmlTab::focusChild()
 {
     m_editor->setFocus();
 }
+
+void VHtmlTab::requestUpdateVimStatus()
+{
+    m_editor->requestUpdateVimStatus();
+}

+ 2 - 0
src/vhtmltab.h

@@ -46,6 +46,8 @@ public:
 
     void clearSearchedWordHighlight() Q_DECL_OVERRIDE;
 
+    void requestUpdateVimStatus() Q_DECL_OVERRIDE;
+
 public slots:
     // Enter edit mode.
     void editFile() Q_DECL_OVERRIDE;

+ 29 - 1
src/vmainwindow.cpp

@@ -20,6 +20,7 @@
 #include "vwebview.h"
 #include "vexporter.h"
 #include "vmdtab.h"
+#include "vvimindicator.h"
 
 extern VConfigManager vconfig;
 
@@ -111,12 +112,17 @@ void VMainWindow::setupUI()
             this, &VMainWindow::handleCurTabStatusChanged);
     connect(editArea, &VEditArea::statusMessage,
             this, &VMainWindow::showStatusMessage);
+    connect(editArea, &VEditArea::vimStatusUpdated,
+            this, &VMainWindow::handleVimStatusUpdated);
     connect(m_findReplaceDialog, &VFindReplaceDialog::findTextChanged,
             this, &VMainWindow::handleFindDialogTextChanged);
 
     setCentralWidget(mainSplitter);
+
     // Create and show the status bar
-    statusBar();
+    m_vimIndicator = new VVimIndicator(this);
+    m_vimIndicator->hide();
+    statusBar()->addPermanentWidget(m_vimIndicator);
 }
 
 QWidget *VMainWindow::setupDirectoryPanel()
@@ -1121,9 +1127,12 @@ void VMainWindow::handleCurTabStatusChanged(const VFile *p_file, const VEditTab
             title.append('#');
         }
     }
+
     updateWindowTitle(title);
     m_curFile = const_cast<VFile *>(p_file);
     m_curTab = const_cast<VEditTab *>(p_editTab);
+
+    updateStatusInfo(p_editMode);
 }
 
 void VMainWindow::onePanelView()
@@ -1496,3 +1505,22 @@ void VMainWindow::showStatusMessage(const QString &p_msg)
     const int timeout = 3000;
     statusBar()->showMessage(p_msg, timeout);
 }
+
+void VMainWindow::updateStatusInfo(bool p_editMode)
+{
+    if (!p_editMode || !m_curTab) {
+        m_vimIndicator->hide();
+    } else {
+        m_curTab->requestUpdateVimStatus();
+    }
+}
+
+void VMainWindow::handleVimStatusUpdated(const VVim *p_vim)
+{
+    if (!p_vim || !m_curTab) {
+        m_vimIndicator->hide();
+    } else {
+        m_vimIndicator->update(p_vim);
+        m_vimIndicator->show();
+    }
+}

+ 8 - 0
src/vmainwindow.h

@@ -29,6 +29,7 @@ class VNotebookSelector;
 class VAvatar;
 class VFindReplaceDialog;
 class VCaptain;
+class VVimIndicator;
 
 class VMainWindow : public QMainWindow
 {
@@ -88,6 +89,9 @@ private slots:
     // Show a temporary message in status bar.
     void showStatusMessage(const QString &p_msg);
 
+    // Handle Vim status updated.
+    void handleVimStatusUpdated(const VVim *p_vim);
+
 protected:
     void closeEvent(QCloseEvent *event) Q_DECL_OVERRIDE;
     void resizeEvent(QResizeEvent *event) Q_DECL_OVERRIDE;
@@ -127,6 +131,9 @@ private:
     void toggleOnePanelView();
     void closeCurrentFile();
 
+    // Update status bar information according to m_curTab and m_curFile.
+    void updateStatusInfo(bool p_editMode);
+
     // Wrapper to create a QAction.
     QAction *newAction(const QIcon &p_icon,
                        const QString &p_text,
@@ -150,6 +157,7 @@ private:
     VOutline *outline;
     VAvatar *m_avatar;
     VFindReplaceDialog *m_findReplaceDialog;
+    VVimIndicator *m_vimIndicator;
 
     // Whether it is one panel or two panles.
     bool m_onePanel;

+ 2 - 0
src/vmdedit.cpp

@@ -53,6 +53,8 @@ VMdEdit::VMdEdit(VFile *p_file, VDocument *p_vdoc, MarkdownConverterType p_type,
 
     connect(m_editOps, &VEditOperations::statusMessage,
             this, &VEdit::statusMessage);
+    connect(m_editOps, &VEditOperations::vimStatusUpdated,
+            this, &VEdit::vimStatusUpdated);
 
     connect(this, &VMdEdit::cursorPositionChanged,
             this, &VMdEdit::updateCurHeader);

+ 11 - 0
src/vmdtab.cpp

@@ -60,6 +60,8 @@ void VMdTab::setupUI()
                 this, &VMdTab::discardAndRead);
         connect(m_editor, &VEdit::statusMessage,
                 this, &VEditTab::statusMessage);
+        connect(m_editor, &VEdit::vimStatusUpdated,
+                this, &VEditTab::vimStatusUpdated);
 
         m_editor->reloadFile();
         m_stacks->addWidget(m_editor);
@@ -643,3 +645,12 @@ void VMdTab::focusChild()
 {
     m_stacks->currentWidget()->setFocus();
 }
+
+void VMdTab::requestUpdateVimStatus()
+{
+    if (m_editor) {
+        m_editor->requestUpdateVimStatus();
+    } else {
+        emit vimStatusUpdated(NULL);
+    }
+}

+ 2 - 0
src/vmdtab.h

@@ -55,6 +55,8 @@ public:
 
     MarkdownConverterType getMarkdownConverterType() const;
 
+    void requestUpdateVimStatus() Q_DECL_OVERRIDE;
+
 public slots:
     // Enter edit mode.
     void editFile() Q_DECL_OVERRIDE;

+ 1 - 1
src/vnote.qrc

@@ -66,7 +66,6 @@
         <file>resources/icons/dir_item.svg</file>
         <file>resources/icons/notebook_item.svg</file>
         <file>resources/icons/arrow_dropdown.svg</file>
-        <file>resources/icons/current_tab.svg</file>
         <file>resources/icons/vnote.png</file>
         <file>resources/icons/insert_image.svg</file>
         <file>resources/icons/import_note.svg</file>
@@ -108,5 +107,6 @@
         <file>utils/markdown-it/markdown-it-sub.min.js</file>
         <file>utils/markdown-it/markdown-it-sup.min.js</file>
         <file>utils/markdown-it/markdown-it-footnote.min.js</file>
+        <file>resources/icons/arrow_dropup.svg</file>
     </qresource>
 </RCC>

+ 174 - 0
src/vvimindicator.cpp

@@ -0,0 +1,174 @@
+#include "vvimindicator.h"
+
+#include <QLabel>
+#include <QHBoxLayout>
+#include <QTreeWidget>
+#include <QStringList>
+#include <QFontMetrics>
+#include <QFont>
+#include <QHeaderView>
+
+#include "vconfigmanager.h"
+#include "vbuttonwithwidget.h"
+
+extern VConfigManager vconfig;
+
+VVimIndicator::VVimIndicator(QWidget *parent)
+    : QWidget(parent), m_vim(NULL)
+{
+    setupUI();
+}
+
+void VVimIndicator::setupUI()
+{
+    m_modeLabel = new QLabel(this);
+
+    m_regBtn = new VButtonWithWidget(QIcon(":/resources/icons/arrow_dropup.svg"),
+                                     "\"",
+                                     this);
+    m_regBtn->setProperty("StatusBtn", true);
+    m_regBtn->setFocusPolicy(Qt::NoFocus);
+    QTreeWidget *regTree = new QTreeWidget(this);
+    regTree->setColumnCount(2);
+    regTree->header()->setStretchLastSection(true);
+    regTree->header()->setProperty("RegisterTree", true);
+    QStringList headers;
+    headers << tr("Register") << tr("Value");
+    regTree->setHeaderLabels(headers);
+    m_regBtn->setPopupWidget(regTree);
+    connect(m_regBtn, &VButtonWithWidget::popupWidgetAboutToShow,
+            this, &VVimIndicator::updateRegistersTree);
+
+    m_keyLabel = new QLabel(this);
+    QFontMetrics metric(font());
+    m_keyLabel->setMinimumWidth(metric.width('A') * 5);
+
+    QHBoxLayout *mainLayout = new QHBoxLayout(this);
+    mainLayout->addWidget(m_modeLabel);
+    mainLayout->addWidget(m_regBtn);
+    mainLayout->addWidget(m_keyLabel);
+    mainLayout->setContentsMargins(0, 0, 0, 0);
+
+    setLayout(mainLayout);
+}
+
+QString VVimIndicator::modeToString(VimMode p_mode) const
+{
+    QString str;
+
+    switch (p_mode) {
+    case VimMode::Normal:
+       str = tr("Normal");
+       break;
+
+    case VimMode::Insert:
+       str = tr("Insert");
+       break;
+
+    case VimMode::Visual:
+       str = tr("Visual");
+       break;
+
+    case VimMode::VisualLine:
+       str = tr("VisualLine");
+       break;
+
+    case VimMode::Replace:
+       str = tr("Replace");
+       break;
+
+    default:
+       str = tr("Unknown");
+        break;
+    }
+
+    return str;
+}
+
+static QString modeBackgroundColor(VimMode p_mode)
+{
+    QString color;
+
+    switch (p_mode) {
+    case VimMode::Normal:
+       color = vconfig.getEditorVimNormalBg();
+       break;
+
+    case VimMode::Insert:
+       color = vconfig.getEditorVimInsertBg();
+       break;
+
+    case VimMode::Visual:
+       color = vconfig.getEditorVimVisualBg();
+       break;
+
+    case VimMode::VisualLine:
+       color = vconfig.getEditorVimVisualBg();
+       break;
+
+    case VimMode::Replace:
+       color = vconfig.getEditorVimReplaceBg();
+       break;
+
+    default:
+       color = "red";
+       break;
+    }
+
+    return color;
+}
+
+static void fillTreeItemsWithRegisters(QTreeWidget *p_tree,
+                                       const QMap<QChar, VVim::Register> &p_regs)
+{
+    p_tree->clear();
+    for (auto const &reg : p_regs) {
+        if (reg.m_value.isEmpty()) {
+            continue;
+        }
+
+        QStringList itemStr;
+        itemStr << reg.m_name << reg.m_value;
+        QTreeWidgetItem *item = new QTreeWidgetItem(p_tree, itemStr);
+        item->setFlags(item->flags() | Qt::ItemIsSelectable | Qt::ItemIsEditable);
+    }
+
+    p_tree->resizeColumnToContents(0);
+    p_tree->resizeColumnToContents(1);
+}
+
+void VVimIndicator::update(const VVim *p_vim)
+{
+    if (!p_vim) {
+        m_vim = p_vim;
+        return;
+    }
+
+    m_vim = p_vim;
+
+    VimMode mode = p_vim->getMode();
+    QChar curRegName = p_vim->getCurrentRegisterName();
+
+    QString style = QString("QLabel { padding: 0px 2px 0px 2px; font: bold; background-color: %1; }")
+                           .arg(modeBackgroundColor(mode));
+    m_modeLabel->setStyleSheet(style);
+    m_modeLabel->setText(modeToString(mode));
+
+    m_regBtn->setText(curRegName);
+
+    QString keyText = QString("<span style=\"font-weight:bold;\">%1</span>")
+                             .arg(p_vim->getPendingKeys());
+    m_keyLabel->setText(keyText);
+}
+
+void VVimIndicator::updateRegistersTree(QWidget *p_widget)
+{
+    QTreeWidget *regTree = dynamic_cast<QTreeWidget *>(p_widget);
+    if (!m_vim) {
+        regTree->clear();
+        return;
+    }
+
+    const QMap<QChar, VVim::Register> &regs = m_vim->getRegisters();
+    fillTreeItemsWithRegisters(regTree, regs);
+}

+ 40 - 0
src/vvimindicator.h

@@ -0,0 +1,40 @@
+#ifndef VVIMINDICATOR_H
+#define VVIMINDICATOR_H
+
+#include <QWidget>
+#include "utils/vvim.h"
+
+class QLabel;
+class VButtonWithWidget;
+
+class VVimIndicator : public QWidget
+{
+    Q_OBJECT
+
+public:
+    explicit VVimIndicator(QWidget *parent = 0);
+
+public slots:
+    void update(const VVim *p_vim);
+
+private slots:
+    void updateRegistersTree(QWidget *p_widget);
+
+private:
+    void setupUI();
+
+    QString modeToString(VimMode p_mode) const;
+
+    // Indicate the mode.
+    QLabel *m_modeLabel;
+
+    // Indicate the registers.
+    VButtonWithWidget *m_regBtn;
+
+    // Indicate the pending keys.
+    QLabel *m_keyLabel;
+
+    const VVim *m_vim;
+};
+
+#endif // VVIMINDICATOR_H