浏览代码

Editor: support auto scrolling cursor line into center

Le Tan 7 年之前
父节点
当前提交
aeb2263be3

+ 11 - 0
src/resources/vnote.ini

@@ -299,6 +299,17 @@ enable_tab_highlight=false
 ; Download images in parsed HTML text
 parse_paste_local_image=true
 
+; Enable extra buffer at the bottom of the editor to avoid placing
+; cursor at the bottom
+enable_extra_buffer=true
+
+; Auto scroll cursor line when it is at the bottom part of the content
+; Need enable_extra_buffer to be enabled
+; 0 - disabled
+; 1 - end of doc
+; 2 - always
+auto_scroll_cursor_line=1
+
 [export]
 ; Path of the wkhtmltopdf tool
 wkhtmltopdf=wkhtmltopdf

+ 67 - 32
src/utils/veditutils.cpp

@@ -406,7 +406,8 @@ int VEditUtils::selectedBlockCount(const QTextCursor &p_cursor)
 
 void VEditUtils::scrollBlockInPage(QTextEdit *p_edit,
                                    int p_blockNum,
-                                   int p_dest)
+                                   int p_dest,
+                                   int p_margin)
 {
     QTextDocument *doc = p_edit->document();
     QTextCursor cursor = p_edit->textCursor();
@@ -416,9 +417,9 @@ void VEditUtils::scrollBlockInPage(QTextEdit *p_edit,
 
     QTextBlock block = doc->findBlockByNumber(p_blockNum);
 
-    int pib = cursor.positionInBlock();
     if (cursor.block().blockNumber() != p_blockNum) {
         // Move the cursor to the block.
+        int pib = cursor.positionInBlock();
         if (pib >= block.length()) {
             pib = block.length() - 1;
         }
@@ -427,15 +428,20 @@ void VEditUtils::scrollBlockInPage(QTextEdit *p_edit,
         p_edit->setTextCursor(cursor);
     }
 
-    // Scroll to let current cursor locate in proper position.
-    p_edit->ensureCursorVisible();
     QScrollBar *vsbar = p_edit->verticalScrollBar();
-
     if (!vsbar || !vsbar->isVisible()) {
         // No vertical scrollbar. No need to scroll.
         return;
     }
 
+    int sstep = vsbar->singleStep();
+    if (p_margin < sstep) {
+        p_margin = sstep;
+    }
+
+    // Scroll to let current cursor locate in proper position.
+    p_edit->ensureCursorVisible();
+
     QRect rect = p_edit->cursorRect();
     int height = p_edit->rect().height();
     QScrollBar *sbar = p_edit->horizontalScrollBar();
@@ -443,13 +449,15 @@ void VEditUtils::scrollBlockInPage(QTextEdit *p_edit,
         height -= sbar->height();
     }
 
+    bool moved = false;
     switch (p_dest) {
     case 0:
     {
         // Top.
-        while (rect.y() > 0 && vsbar->value() < vsbar->maximum()) {
-            vsbar->setValue(vsbar->value() + vsbar->singleStep());
+        while (rect.y() > p_margin && vsbar->value() < vsbar->maximum()) {
+            vsbar->setValue(vsbar->value() + sstep);
             rect = p_edit->cursorRect();
+            moved = true;
         }
 
         break;
@@ -459,15 +467,19 @@ void VEditUtils::scrollBlockInPage(QTextEdit *p_edit,
     {
         // Center.
         height = qMax(height / 2, 1);
-        if (rect.y() > height) {
-            while (rect.y() > height && vsbar->value() < vsbar->maximum()) {
-                vsbar->setValue(vsbar->value() + vsbar->singleStep());
+        int upBound = height + p_margin;
+        int lowBound = height - p_margin;
+        if (rect.y() > upBound) {
+            while (rect.y() > upBound && vsbar->value() < vsbar->maximum()) {
+                vsbar->setValue(vsbar->value() + sstep);
                 rect = p_edit->cursorRect();
+                moved = true;
             }
-        } else if (rect.y() < height) {
-            while (rect.y() < height && vsbar->value() > vsbar->minimum()) {
-                vsbar->setValue(vsbar->value() - vsbar->singleStep());
+        } else if (rect.y() < lowBound) {
+            while (rect.y() < lowBound && vsbar->value() > vsbar->minimum()) {
+                vsbar->setValue(vsbar->value() - sstep);
                 rect = p_edit->cursorRect();
+                moved = true;
             }
         }
 
@@ -475,24 +487,31 @@ void VEditUtils::scrollBlockInPage(QTextEdit *p_edit,
     }
 
     case 2:
+    {
         // Bottom.
-        while (rect.y() < height && vsbar->value() > vsbar->minimum()) {
-            vsbar->setValue(vsbar->value() - vsbar->singleStep());
+        int lowBound = height - p_margin;
+        while (rect.y() < lowBound && vsbar->value() > vsbar->minimum()) {
+            vsbar->setValue(vsbar->value() - sstep);
             rect = p_edit->cursorRect();
+            moved = true;
         }
 
         break;
+    }
 
     default:
         break;
     }
 
-    p_edit->ensureCursorVisible();
+    if (moved) {
+        p_edit->ensureCursorVisible();
+    }
 }
 
 void VEditUtils::scrollBlockInPage(QPlainTextEdit *p_edit,
                                    int p_blockNum,
-                                   int p_dest)
+                                   int p_dest,
+                                   int p_margin)
 {
     QTextDocument *doc = p_edit->document();
     QTextCursor cursor = p_edit->textCursor();
@@ -502,9 +521,9 @@ void VEditUtils::scrollBlockInPage(QPlainTextEdit *p_edit,
 
     QTextBlock block = doc->findBlockByNumber(p_blockNum);
 
-    int pib = cursor.positionInBlock();
     if (cursor.block().blockNumber() != p_blockNum) {
         // Move the cursor to the block.
+        int pib = cursor.positionInBlock();
         if (pib >= block.length()) {
             pib = block.length() - 1;
         }
@@ -513,15 +532,20 @@ void VEditUtils::scrollBlockInPage(QPlainTextEdit *p_edit,
         p_edit->setTextCursor(cursor);
     }
 
-    // Scroll to let current cursor locate in proper position.
-    p_edit->ensureCursorVisible();
     QScrollBar *vsbar = p_edit->verticalScrollBar();
-
     if (!vsbar || !vsbar->isVisible()) {
         // No vertical scrollbar. No need to scroll.
         return;
     }
 
+    int sstep = vsbar->singleStep();
+    if (p_margin < sstep) {
+        p_margin = sstep;
+    }
+
+    // Scroll to let current cursor locate in proper position.
+    p_edit->ensureCursorVisible();
+
     QRect rect = p_edit->cursorRect();
     int height = p_edit->rect().height();
     QScrollBar *sbar = p_edit->horizontalScrollBar();
@@ -529,12 +553,13 @@ void VEditUtils::scrollBlockInPage(QPlainTextEdit *p_edit,
         height -= sbar->height();
     }
 
+    bool moved = false;
     switch (p_dest) {
     case 0:
     {
         // Top.
-        while (rect.y() > 0 && vsbar->value() < vsbar->maximum()) {
-            vsbar->setValue(vsbar->value() + vsbar->singleStep());
+        while (rect.y() > p_margin && vsbar->value() < vsbar->maximum()) {
+            vsbar->setValue(vsbar->value() + sstep);
             rect = p_edit->cursorRect();
         }
 
@@ -545,15 +570,19 @@ void VEditUtils::scrollBlockInPage(QPlainTextEdit *p_edit,
     {
         // Center.
         height = qMax(height / 2, 1);
-        if (rect.y() > height) {
-            while (rect.y() > height && vsbar->value() < vsbar->maximum()) {
-                vsbar->setValue(vsbar->value() + vsbar->singleStep());
+        int upBound = height + p_margin;
+        int lowBound = height - p_margin;
+        if (rect.y() > upBound) {
+            while (rect.y() > upBound && vsbar->value() < vsbar->maximum()) {
+                vsbar->setValue(vsbar->value() + sstep);
                 rect = p_edit->cursorRect();
+                moved = true;
             }
-        } else if (rect.y() < height) {
-            while (rect.y() < height && vsbar->value() > vsbar->minimum()) {
-                vsbar->setValue(vsbar->value() - vsbar->singleStep());
+        } else if (rect.y() < lowBound) {
+            while (rect.y() < lowBound && vsbar->value() > vsbar->minimum()) {
+                vsbar->setValue(vsbar->value() - sstep);
                 rect = p_edit->cursorRect();
+                moved = true;
             }
         }
 
@@ -561,19 +590,25 @@ void VEditUtils::scrollBlockInPage(QPlainTextEdit *p_edit,
     }
 
     case 2:
+    {
         // Bottom.
-        while (rect.y() < height && vsbar->value() > vsbar->minimum()) {
-            vsbar->setValue(vsbar->value() - vsbar->singleStep());
+        int lowBound = height - p_margin;
+        while (rect.y() < lowBound && vsbar->value() > vsbar->minimum()) {
+            vsbar->setValue(vsbar->value() - sstep);
             rect = p_edit->cursorRect();
+            moved = true;
         }
 
         break;
+    }
 
     default:
         break;
     }
 
-    p_edit->ensureCursorVisible();
+    if (moved) {
+        p_edit->ensureCursorVisible();
+    }
 }
 
 bool VEditUtils::isBlockQuoteBlock(const QTextBlock &p_block)

+ 4 - 2
src/utils/veditutils.h

@@ -129,7 +129,8 @@ public:
     // Will set the cursor to the block.
     static void scrollBlockInPage(QTextEdit *p_edit,
                                   int p_blockNum,
-                                  int p_dest);
+                                  int p_dest,
+                                  int p_margin = 0);
 
     // Scroll block @p_blockNum into the visual window.
     // @p_dest is the position of the window: 0 for top, 1 for center, 2 for bottom.
@@ -137,7 +138,8 @@ public:
     // Will set the cursor to the block.
     static void scrollBlockInPage(QPlainTextEdit *p_edit,
                                   int p_blockNum,
-                                  int p_dest);
+                                  int p_dest,
+                                  int p_margin = 0);
 
     // Check if @p_block is a auto list block.
     // @p_seq will be the seq number of the ordered list, or -1.

+ 4 - 0
src/vconfigmanager.cpp

@@ -361,6 +361,10 @@ void VConfigManager::initEditorConfigs()
                                                  "enable_tab_highlight").toBool();
 
     m_parsePasteLocalImage = getConfigFromSettings("editor", "parse_paste_local_image").toBool();
+
+    m_enableExtraBuffer = getConfigFromSettings("editor", "enable_extra_buffer").toBool();
+
+    m_autoScrollCursorLine = getConfigFromSettings("editor", "auto_scroll_cursor_line").toInt();
 }
 
 void VConfigManager::initSettings()

+ 51 - 0
src/vconfigmanager.h

@@ -67,6 +67,13 @@ enum SmartLivePreview
     WebToEditor = 0x2
 };
 
+enum
+{
+    AutoScrollDisabled = 0,
+    AutoScrollEndOfDoc = 1,
+    AutoScrollAlways = 2
+};
+
 class VConfigManager : public QObject
 {
 public:
@@ -587,6 +594,11 @@ public:
     const QColor &getBaseBackground() const;
     void setBaseBackground(const QColor &p_bg);
 
+    bool getEnableExtraBuffer() const;
+
+    int getAutoScrollCursorLine() const;
+    void setAutoScrollCursorLine(int p_mode);
+
 private:
     // Look up a config from user and default settings.
     QVariant getConfigFromSettings(const QString &section, const QString &key) const;
@@ -1050,6 +1062,16 @@ private:
     // Base background of MainWindow.
     QColor m_baseBackground;
 
+    // Whether enable extra buffer at the bottom of editor.
+    bool m_enableExtraBuffer;
+
+    // Whether auto scroll cursor line to the middle of the window when
+    // cursor is at the bottom part of the content.
+    // 0 - disalbed
+    // 1 - end of doc
+    // 2 - always
+    int m_autoScrollCursorLine;
+
     // The name of the config file in each directory.
     static const QString c_dirConfigFile;
 
@@ -2730,4 +2752,33 @@ inline void VConfigManager::setBaseBackground(const QColor &p_bg)
 {
     m_baseBackground = p_bg;
 }
+
+inline bool VConfigManager::getEnableExtraBuffer() const
+{
+    return m_enableExtraBuffer;
+}
+
+inline int VConfigManager::getAutoScrollCursorLine() const
+{
+    if (m_enableExtraBuffer) {
+        return m_autoScrollCursorLine;
+    } else {
+        return AutoScrollDisabled;
+    }
+}
+
+inline void VConfigManager::setAutoScrollCursorLine(int p_mode)
+{
+    m_autoScrollCursorLine = p_mode;
+    if (p_mode == AutoScrollDisabled) {
+        setConfigToSettings("editor", "auto_scroll_cursor_line", m_autoScrollCursorLine);
+    } else {
+        if (!m_enableExtraBuffer) {
+            m_enableExtraBuffer = true;
+            setConfigToSettings("editor", "enable_extra_buffer", m_enableExtraBuffer);
+        }
+
+        setConfigToSettings("editor", "auto_scroll_cursor_line", m_autoScrollCursorLine);
+    }
+}
 #endif // VCONFIGMANAGER_H

+ 36 - 0
src/veditor.cpp

@@ -1705,3 +1705,39 @@ void VEditor::pastePlainText()
 
     m_editOps->insertText(text);
 }
+
+void VEditor::scrollCursorLineIfNecessary()
+{
+    bool moved = false;
+    QTextCursor cursor = textCursorW();
+    int mode = g_config->getAutoScrollCursorLine();
+    switch (mode) {
+    case AutoScrollEndOfDoc:
+        V_FALLTHROUGH;
+    case AutoScrollAlways:
+    {
+        const int bc = m_document->blockCount();
+        int bn = cursor.blockNumber();
+        int margin = -1;
+        if (mode == AutoScrollAlways) {
+            margin = m_editor->rect().height() / 8;
+        } else if (bn == bc - 1) {
+            margin = m_editor->rect().height() / 4;
+        }
+
+        if (margin > -1) {
+            moved = true;
+            scrollBlockInPage(bn, 1, margin);
+        }
+
+        break;
+    }
+
+    default:
+        break;
+    }
+
+    if (!moved) {
+        makeBlockVisible(cursor.block());
+    }
+}

+ 4 - 1
src/veditor.h

@@ -154,7 +154,7 @@ public:
     // @p_dest is the position of the window: 0 for top, 1 for center, 2 for bottom.
     // @p_blockNum is based on 0.
     // Will set the cursor to the block.
-    virtual void scrollBlockInPage(int p_blockNum, int p_dest) = 0;
+    virtual void scrollBlockInPage(int p_blockNum, int p_dest, int p_margin = 0) = 0;
 
     // Update config according to global configurations.
     virtual void updateConfig();
@@ -273,6 +273,9 @@ protected:
     // Paste plain text.
     void pastePlainText();
 
+    // Scroll cursor line if in need.
+    void scrollCursorLineIfNecessary();
+
     QWidget *m_editor;
 
     VEditorObject *m_object;

+ 65 - 36
src/vmainwindow.cpp

@@ -815,7 +815,7 @@ void VMainWindow::initHelpMenu()
     QAction *docAct = new QAction(tr("&Documentation"), this);
     docAct->setToolTip(tr("View VNote's documentation"));
     connect(docAct, &QAction::triggered,
-            this, [this]() {
+            this, []() {
                 QString url("http://vnote.readthedocs.io");
                 QDesktopServices::openUrl(url);
             });
@@ -823,7 +823,7 @@ void VMainWindow::initHelpMenu()
     QAction *donateAct = new QAction(tr("Do&nate"), this);
     donateAct->setToolTip(tr("Donate to VNote or view the donate list"));
     connect(donateAct, &QAction::triggered,
-            this, [this]() {
+            this, []() {
                 QString url("https://github.com/tamlok/vnote#donate");
                 QDesktopServices::openUrl(url);
             });
@@ -839,7 +839,7 @@ void VMainWindow::initHelpMenu()
     QAction *starAct = new QAction(tr("Star VNote on &Github"), this);
     starAct->setToolTip(tr("Give a star to VNote on Github project"));
     connect(starAct, &QAction::triggered,
-            this, [this]() {
+            this, []() {
                 QString url("https://github.com/tamlok/vnote");
                 QDesktopServices::openUrl(url);
             });
@@ -847,7 +847,7 @@ void VMainWindow::initHelpMenu()
     QAction *feedbackAct = new QAction(tr("&Feedback"), this);
     feedbackAct->setToolTip(tr("Open an issue on Github"));
     connect(feedbackAct, &QAction::triggered,
-            this, [this]() {
+            this, []() {
                 QString url("https://github.com/tamlok/vnote/issues");
                 QDesktopServices::openUrl(url);
             });
@@ -958,7 +958,7 @@ void VMainWindow::initMarkdownMenu()
     lineNumberAct->setToolTip(tr("Enable line number in code blocks in read mode"));
     lineNumberAct->setCheckable(true);
     connect(lineNumberAct, &QAction::triggered,
-            this, [this](bool p_checked){
+            this, [](bool p_checked){
                 g_config->setEnableCodeBlockLineNumber(p_checked);
             });
     markdownMenu->addAction(lineNumberAct);
@@ -1081,7 +1081,7 @@ void VMainWindow::initFileMenu()
     QAction *openConfigAct = new QAction(tr("Open Configuration Folder"), this);
     openConfigAct->setToolTip(tr("Open configuration folder of VNote"));
     connect(openConfigAct, &QAction::triggered,
-            this, [this](){
+            this, [](){
                 QUrl url = QUrl::fromLocalFile(g_config->getConfigFolder());
                 QDesktopServices::openUrl(url);
             });
@@ -1247,7 +1247,7 @@ void VMainWindow::initEditMenu()
     tabAct->setToolTip(tr("Highlight all the tabs"));
     tabAct->setCheckable(true);
     connect(tabAct, &QAction::triggered,
-            this, [this](bool p_checked) {
+            this, [](bool p_checked) {
                 g_config->setEnableTabHighlight(p_checked);
             });
 
@@ -1329,6 +1329,8 @@ void VMainWindow::initEditMenu()
 
     editMenu->addAction(tabAct);
     tabAct->setChecked(g_config->getEnableTabHighlight());
+
+    initAutoScrollCursorLineMenu(editMenu);
 }
 
 void VMainWindow::initDockWindows()
@@ -1582,7 +1584,7 @@ void VMainWindow::initMarkdownitOptionMenu(QMenu *p_menu)
     htmlAct->setCheckable(true);
     htmlAct->setChecked(opt.m_html);
     connect(htmlAct, &QAction::triggered,
-            this, [this](bool p_checked) {
+            this, [](bool p_checked) {
                 MarkdownitOption opt = g_config->getMarkdownitOption();
                 opt.m_html = p_checked;
                 g_config->setMarkdownitOption(opt);
@@ -1593,7 +1595,7 @@ void VMainWindow::initMarkdownitOptionMenu(QMenu *p_menu)
     breaksAct->setCheckable(true);
     breaksAct->setChecked(opt.m_breaks);
     connect(breaksAct, &QAction::triggered,
-            this, [this](bool p_checked) {
+            this, [](bool p_checked) {
                 MarkdownitOption opt = g_config->getMarkdownitOption();
                 opt.m_breaks = p_checked;
                 g_config->setMarkdownitOption(opt);
@@ -1604,7 +1606,7 @@ void VMainWindow::initMarkdownitOptionMenu(QMenu *p_menu)
     linkifyAct->setCheckable(true);
     linkifyAct->setChecked(opt.m_linkify);
     connect(linkifyAct, &QAction::triggered,
-            this, [this](bool p_checked) {
+            this, [](bool p_checked) {
                 MarkdownitOption opt = g_config->getMarkdownitOption();
                 opt.m_linkify = p_checked;
                 g_config->setMarkdownitOption(opt);
@@ -1615,7 +1617,7 @@ void VMainWindow::initMarkdownitOptionMenu(QMenu *p_menu)
     supAct->setCheckable(true);
     supAct->setChecked(opt.m_sup);
     connect(supAct, &QAction::triggered,
-            this, [this](bool p_checked) {
+            this, [](bool p_checked) {
                 MarkdownitOption opt = g_config->getMarkdownitOption();
                 opt.m_sup = p_checked;
                 g_config->setMarkdownitOption(opt);
@@ -1626,7 +1628,7 @@ void VMainWindow::initMarkdownitOptionMenu(QMenu *p_menu)
     subAct->setCheckable(true);
     subAct->setChecked(opt.m_sub);
     connect(subAct, &QAction::triggered,
-            this, [this](bool p_checked) {
+            this, [](bool p_checked) {
                 MarkdownitOption opt = g_config->getMarkdownitOption();
                 opt.m_sub = p_checked;
                 g_config->setMarkdownitOption(opt);
@@ -1637,7 +1639,7 @@ void VMainWindow::initMarkdownitOptionMenu(QMenu *p_menu)
     metadataAct->setCheckable(true);
     metadataAct->setChecked(opt.m_metadata);
     connect(metadataAct, &QAction::triggered,
-            this, [this](bool p_checked) {
+            this, [](bool p_checked) {
                 MarkdownitOption opt = g_config->getMarkdownitOption();
                 opt.m_metadata = p_checked;
                 g_config->setMarkdownitOption(opt);
@@ -1648,7 +1650,7 @@ void VMainWindow::initMarkdownitOptionMenu(QMenu *p_menu)
     emojiAct->setCheckable(true);
     emojiAct->setChecked(opt.m_emoji);
     connect(emojiAct, &QAction::triggered,
-            this, [this](bool p_checked) {
+            this, [](bool p_checked) {
                 MarkdownitOption opt = g_config->getMarkdownitOption();
                 opt.m_emoji = p_checked;
                 g_config->setMarkdownitOption(opt);
@@ -1740,14 +1742,10 @@ void VMainWindow::initRenderStyleMenu(QMenu *p_menu)
 
     QActionGroup *ag = new QActionGroup(this);
     connect(ag, &QActionGroup::triggered,
-            this, [this](QAction *p_action) {
-                if (!p_action) {
-                    return;
-                }
-
+            this, [](QAction *p_action) {
                 QString data = p_action->data().toString();
                 g_config->setCssStyle(data);
-                vnote->updateTemplate();
+                g_vnote->updateTemplate();
             });
 
     QList<QString> styles = g_config->getCssStyles();
@@ -1793,14 +1791,10 @@ void VMainWindow::initCodeBlockStyleMenu(QMenu *p_menu)
 
     QActionGroup *ag = new QActionGroup(this);
     connect(ag, &QActionGroup::triggered,
-            this, [this](QAction *p_action) {
-                if (!p_action) {
-                    return;
-                }
-
+            this, [](QAction *p_action) {
                 QString data = p_action->data().toString();
                 g_config->setCodeBlockCssStyle(data);
-                vnote->updateTemplate();
+                g_vnote->updateTemplate();
             });
 
     QList<QString> styles = g_config->getCodeBlockCssStyles();
@@ -1942,11 +1936,7 @@ void VMainWindow::initEditorStyleMenu(QMenu *p_menu)
 
     QActionGroup *ag = new QActionGroup(this);
     connect(ag, &QActionGroup::triggered,
-            this, [this](QAction *p_action) {
-                if (!p_action) {
-                    return;
-                }
-
+            this, [](QAction *p_action) {
                 QString data = p_action->data().toString();
                 g_config->setEditorStyle(data);
             });
@@ -1968,6 +1958,49 @@ void VMainWindow::initEditorStyleMenu(QMenu *p_menu)
     }
 }
 
+void VMainWindow::initAutoScrollCursorLineMenu(QMenu *p_menu)
+{
+    QMenu *subMenu = p_menu->addMenu(tr("Auto Scroll Cursor Line"));
+    subMenu->setToolTipsVisible(true);
+
+    QActionGroup *ag = new QActionGroup(this);
+    connect(ag, &QActionGroup::triggered,
+            this, [](QAction *p_action) {
+                g_config->setAutoScrollCursorLine(p_action->data().toInt());
+            });
+
+    int mode = g_config->getAutoScrollCursorLine();
+
+    int data = AutoScrollDisabled;
+    QAction *act = new QAction(tr("Disabled"), ag);
+    act->setCheckable(true);
+    act->setData(data);
+    subMenu->addAction(act);
+    if (mode == data) {
+        act->setChecked(true);
+    }
+
+    data = AutoScrollEndOfDoc;
+    act = new QAction(tr("End Of Document"), ag);
+    act->setToolTip(tr("Scroll cursor line into the center when it locates at the end of document"));
+    act->setCheckable(true);
+    act->setData(data);
+    subMenu->addAction(act);
+    if (mode == data) {
+        act->setChecked(true);
+    }
+
+    data = AutoScrollAlways;
+    act = new QAction(tr("Always"), ag);
+    act->setToolTip(tr("Always scroll cursor line into the center"));
+    act->setCheckable(true);
+    act->setData(data);
+    subMenu->addAction(act);
+    if (mode == data) {
+        act->setChecked(true);
+    }
+}
+
 void VMainWindow::setRenderBackgroundColor(QAction *action)
 {
     if (!action) {
@@ -3018,11 +3051,7 @@ void VMainWindow::initThemeMenu(QMenu *p_menu)
 
     QActionGroup *ag = new QActionGroup(this);
     connect(ag, &QActionGroup::triggered,
-            this, [this](QAction *p_action) {
-                if (!p_action) {
-                    return;
-                }
-
+            this, [](QAction *p_action) {
                 QString data = p_action->data().toString();
                 g_config->setTheme(data);
             });

+ 3 - 1
src/vmainwindow.h

@@ -260,7 +260,9 @@ private:
     // Init the Line Number submenu in Edit menu.
     void initEditorLineNumberMenu(QMenu *p_menu);
 
-    void initEditorStyleMenu(QMenu *p_emnu);
+    void initEditorStyleMenu(QMenu *p_menu);
+
+    void initAutoScrollCursorLineMenu(QMenu *p_menu);
 
     void updateWindowTitle(const QString &str);
 

+ 6 - 4
src/vmdeditor.cpp

@@ -91,7 +91,7 @@ VMdEditor::VMdEditor(VFile *p_file,
     // in this case.
     connect(m_pegHighlighter, &PegMarkdownHighlighter::highlightCompleted,
             this, [this]() {
-            makeBlockVisible(textCursor().block());
+            scrollCursorLineIfNecessary();
 
             if (m_freshEdit) {
                 m_freshEdit = false;
@@ -215,7 +215,7 @@ bool VMdEditor::scrollToBlock(int p_blockNumber)
 {
     QTextBlock block = document()->findBlockByNumber(p_blockNumber);
     if (block.isValid()) {
-        VEditUtils::scrollBlockInPage(this, block.blockNumber(), 0);
+        scrollBlockInPage(block.blockNumber(), 0);
         moveCursor(QTextCursor::EndOfBlock);
         return true;
     }
@@ -1014,9 +1014,9 @@ bool VMdEditor::jumpTitle(bool p_forward, int p_relativeLevel, int p_repeat)
     return false;
 }
 
-void VMdEditor::scrollBlockInPage(int p_blockNum, int p_dest)
+void VMdEditor::scrollBlockInPage(int p_blockNum, int p_dest, int p_margin)
 {
-    VEditUtils::scrollBlockInPage(this, p_blockNum, p_dest);
+    VEditUtils::scrollBlockInPage(this, p_blockNum, p_dest, p_margin);
 }
 
 void VMdEditor::updateTextEditConfig()
@@ -1029,6 +1029,8 @@ void VMdEditor::updateTextEditConfig()
 
     setImageLineColor(g_config->getEditorPreviewImageLineFg());
 
+    setEnableExtraBuffer(g_config->getEnableExtraBuffer());
+
     int lineNumber = g_config->getEditorLineNumber();
     if (lineNumber < (int)LineNumberType::None || lineNumber >= (int)LineNumberType::Invalid) {
         lineNumber = (int)LineNumberType::None;

+ 1 - 1
src/vmdeditor.h

@@ -58,7 +58,7 @@ public:
     // Return true if @p_blockNumber is valid to scroll to.
     bool scrollToHeader(int p_blockNumber);
 
-    void scrollBlockInPage(int p_blockNum, int p_dest) Q_DECL_OVERRIDE;
+    void scrollBlockInPage(int p_blockNum, int p_dest, int p_margin = 0) Q_DECL_OVERRIDE;
 
     void updateConfig() Q_DECL_OVERRIDE;
 

+ 3 - 2
src/vtextdocumentlayout.cpp

@@ -41,7 +41,8 @@ VTextDocumentLayout::VTextDocumentLayout(QTextDocument *p_doc,
       m_lastCursorBlockWidth(-1),
       m_highlightCursorLineBlock(false),
       m_cursorLineBlockBg("#C0C0C0"),
-      m_cursorLineBlockNumber(-1)
+      m_cursorLineBlockNumber(-1),
+      m_extraBufferHeight(0)
 {
 }
 
@@ -396,7 +397,7 @@ int VTextDocumentLayout::pageCount() const
 
 QSizeF VTextDocumentLayout::documentSize() const
 {
-    return QSizeF(m_width, m_height);
+    return QSizeF(m_width, m_height + m_extraBufferHeight);
 }
 
 QRectF VTextDocumentLayout::frameBoundingRect(QTextFrame *p_frame) const

+ 16 - 1
src/vtextdocumentlayout.h

@@ -77,6 +77,8 @@ public:
     // Request update block by block number.
     void updateBlockByNumber(int p_blockNumber);
 
+    void setExtraBufferHeight(int p_height);
+
 signals:
     // Emit to update current cursor block width if m_cursorBlockMode is enabled.
     void cursorBlockWidthUpdated(int p_width);
@@ -196,7 +198,7 @@ private:
     // The block number of the block which contains the m_width.
     int m_maximumWidthBlockNumber;
 
-    // Height of all the document (all the blocks).
+    // Height of all the document (all the blocks, excluding m_extraBufferHeight).
     qreal m_height;
 
     // Set the leading space of a line.
@@ -237,6 +239,9 @@ private:
 
     // The block containing the cursor.
     int m_cursorLineBlockNumber;
+
+    // Extra buffer height in document size.
+    int m_extraBufferHeight;
 };
 
 inline qreal VTextDocumentLayout::getLineLeading() const
@@ -325,4 +330,14 @@ inline void VTextDocumentLayout::updateOffset(const QTextBlock &p_block)
     updateOffsetBefore(p_block);
     updateOffsetAfter(p_block);
 }
+
+inline void VTextDocumentLayout::setExtraBufferHeight(int p_height)
+{
+    if (m_extraBufferHeight == p_height) {
+        return;
+    }
+
+    m_extraBufferHeight = p_height;
+    emit documentSizeChanged(documentSize());
+}
 #endif // VTEXTDOCUMENTLAYOUT_H

+ 7 - 1
src/vtextedit.cpp

@@ -52,6 +52,8 @@ void VTextEdit::init()
 
     m_highlightCursorLineBlock = false;
 
+    m_enableExtraBuffer = false;
+
     m_imageMgr = new VImageResourceManager2();
 
     QTextDocument *doc = document();
@@ -106,13 +108,17 @@ void VTextEdit::resizeEvent(QResizeEvent *p_event)
 {
     QTextEdit::resizeEvent(p_event);
 
+    QRect rect = contentsRect();
     if (m_lineNumberType != LineNumberType::None) {
-        QRect rect = contentsRect();
         m_lineNumberArea->setGeometry(QRect(rect.left(),
                                             rect.top(),
                                             m_lineNumberArea->calculateWidth(),
                                             rect.height()));
     }
+
+    if (m_enableExtraBuffer) {
+        getLayout()->setExtraBufferHeight(rect.height() / 2);
+    }
 }
 
 void VTextEdit::paintLineNumberArea(QPaintEvent *p_event)

+ 15 - 0
src/vtextedit.h

@@ -78,6 +78,8 @@ public:
 
     void setDisplayScaleFactor(qreal p_factor);
 
+    void setEnableExtraBuffer(bool p_enable);
+
 protected:
     void resizeEvent(QResizeEvent *p_event) Q_DECL_OVERRIDE;
 
@@ -112,6 +114,8 @@ private:
     bool m_highlightCursorLineBlock;
 
     int m_defaultCursorWidth;
+
+    bool m_enableExtraBuffer;
 };
 
 inline void VTextEdit::setLineNumberType(LineNumberType p_type)
@@ -131,4 +135,15 @@ inline void VTextEdit::setLineNumberColor(const QColor &p_foreground,
     m_lineNumberArea->setForegroundColor(p_foreground);
     m_lineNumberArea->setBackgroundColor(p_background);
 }
+
+inline void VTextEdit::setEnableExtraBuffer(bool p_enable)
+{
+    if (m_enableExtraBuffer == p_enable) {
+        return;
+    }
+
+    m_enableExtraBuffer = p_enable;
+    getLayout()->setExtraBufferHeight(m_enableExtraBuffer ? contentsRect().height() / 2
+                                                          : 0);
+}
 #endif // VTEXTEDIT_H