Browse Source

support auto heading sequence by config enable_heading_sequence

Le Tan 8 years ago
parent
commit
2eb6476c3d

+ 2 - 0
README.md

@@ -14,6 +14,8 @@ Users from China can download the latest release of VNote from [Baidu Netdisk](h
 - [Github releases](https://github.com/tamlok/vnote/releases)
 - Latest builds on master: [ ![Download](https://api.bintray.com/packages/tamlok/vnote/vnote/images/download.svg) ](https://bintray.com/tamlok/vnote/vnote/_latestVersion)
 
+**NOT** supported in XP since QtWebEngineProcess used by VNote could not work in XP.
+
 ## Linux
 [![Build Status](https://travis-ci.org/tamlok/vnote.svg?branch=master)](https://travis-ci.org/tamlok/vnote)
 

+ 2 - 0
README_zh.md

@@ -14,6 +14,8 @@
 - [Github releases](https://github.com/tamlok/vnote/releases)
 - master分支的最新构建:[ ![Download](https://api.bintray.com/packages/tamlok/vnote/vnote/images/download.svg) ](https://bintray.com/tamlok/vnote/vnote/_latestVersion)
 
+VNote不支持**XP**,因为QtWebEngineProcess无法在XP上运行。
+
 ## Linux
 [![Build Status](https://travis-ci.org/tamlok/vnote.svg?branch=master)](https://travis-ci.org/tamlok/vnote)
 

+ 144 - 48
src/dialog/vsettingsdialog.cpp

@@ -10,25 +10,57 @@ extern VConfigManager *g_config;
 VSettingsDialog::VSettingsDialog(QWidget *p_parent)
     : QDialog(p_parent)
 {
-    m_tabs = new QTabWidget;
-    m_tabs->addTab(new VGeneralTab(), tr("General"));
-    m_tabs->addTab(new VReadEditTab(), tr("Read/Edit"));
-    m_tabs->addTab(new VNoteManagementTab(), tr("Note Management"));
+    m_tabList = new QListWidget(this);
+    m_tabList->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
+
+    m_tabs = new QStackedLayout();
 
     m_btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
     connect(m_btnBox, &QDialogButtonBox::accepted, this, &VSettingsDialog::saveConfiguration);
     connect(m_btnBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
 
-    QVBoxLayout *mainLayout = new QVBoxLayout;
-    mainLayout->addWidget(m_tabs);
+    QHBoxLayout *tabLayout = new QHBoxLayout();
+    tabLayout->addWidget(m_tabList);
+    tabLayout->addLayout(m_tabs);
+    tabLayout->setContentsMargins(0, 0, 0, 0);
+    tabLayout->setSpacing(0);
+    tabLayout->setStretch(0, 0);
+    tabLayout->setStretch(1, 5);
+
+    QVBoxLayout *mainLayout = new QVBoxLayout();
+    mainLayout->addLayout(tabLayout);
     mainLayout->addWidget(m_btnBox);
     setLayout(mainLayout);
 
     setWindowTitle(tr("Settings"));
 
+    // Add tabs.
+    addTab(new VGeneralTab(), tr("General"));
+    addTab(new VReadEditTab(), tr("Read/Edit"));
+    addTab(new VNoteManagementTab(), tr("Note Management"));
+    addTab(new VMarkdownTab(), tr("Markdown"));
+
+    m_tabList->setMaximumWidth(m_tabList->sizeHintForColumn(0) + 5);
+
+    connect(m_tabList, &QListWidget::currentItemChanged,
+            this, [this](QListWidgetItem *p_cur, QListWidgetItem *p_pre) {
+                Q_UNUSED(p_pre);
+                Q_ASSERT(p_cur);
+                int idx = p_cur->data(Qt::UserRole).toInt();
+                Q_ASSERT(idx >= 0);
+                m_tabs->setCurrentWidget(m_tabs->widget(idx));
+            });
+
     loadConfiguration();
 }
 
+void VSettingsDialog::addTab(QWidget *p_widget, const QString &p_label)
+{
+    int idx = m_tabs->addWidget(p_widget);
+    QListWidgetItem *item = new QListWidgetItem(p_label, m_tabList);
+    item->setData(Qt::UserRole, idx);
+}
+
 void VSettingsDialog::loadConfiguration()
 {
     // General Tab.
@@ -58,6 +90,15 @@ void VSettingsDialog::loadConfiguration()
         }
     }
 
+    // Markdown Tab.
+    {
+        VMarkdownTab *markdownTab = dynamic_cast<VMarkdownTab *>(m_tabs->widget(3));
+        Q_ASSERT(markdownTab);
+        if (!markdownTab->loadConfiguration()) {
+            goto err;
+        }
+    }
+
     return;
 err:
     VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
@@ -95,6 +136,15 @@ void VSettingsDialog::saveConfiguration()
         }
     }
 
+    // Markdown Tab.
+    {
+        VMarkdownTab *markdownTab = dynamic_cast<VMarkdownTab *>(m_tabs->widget(3));
+        Q_ASSERT(markdownTab);
+        if (!markdownTab->saveConfiguration()) {
+            goto err;
+        }
+    }
+
     accept();
     return;
 err:
@@ -234,25 +284,14 @@ VReadEditTab::VReadEditTab(QWidget *p_parent)
     zoomFactorLayout->addWidget(m_customWebZoom);
     zoomFactorLayout->addWidget(m_webZoomFactorSpin);
 
-    // Default note open mode.
-    m_openModeCombo = new QComboBox();
-    m_openModeCombo->setToolTip(tr("Default mode to open a note"));
-    m_openModeCombo->addItem(tr("Read Mode"), (int)OpenFileMode::Read);
-    m_openModeCombo->addItem(tr("Edit Mode"), (int)OpenFileMode::Edit);
-
-    QLabel *openModeLabel = new QLabel(tr("Note open mode:"));
-    openModeLabel->setToolTip(m_openModeCombo->toolTip());
-
     QFormLayout *readLayout = new QFormLayout();
     readLayout->addRow(zoomFactorLayout);
-    readLayout->addRow(openModeLabel, m_openModeCombo);
 
     m_readBox->setLayout(readLayout);
 
     QVBoxLayout *mainLayout = new QVBoxLayout();
     mainLayout->addWidget(m_readBox);
     mainLayout->addWidget(m_editBox);
-    m_editBox->hide();
     setLayout(mainLayout);
 }
 
@@ -262,10 +301,6 @@ bool VReadEditTab::loadConfiguration()
         return false;
     }
 
-    if (!loadOpenMode()) {
-        return false;
-    }
-
     return true;
 }
 
@@ -275,10 +310,6 @@ bool VReadEditTab::saveConfiguration()
         return false;
     }
 
-    if (!saveOpenMode()) {
-        return false;
-    }
-
     return true;
 }
 
@@ -315,29 +346,6 @@ void VReadEditTab::customWebZoomChanged(int p_state)
     m_webZoomFactorSpin->setEnabled(p_state == Qt::Checked);
 }
 
-bool VReadEditTab::loadOpenMode()
-{
-    int mode = (int)g_config->getNoteOpenMode();
-    bool found = false;
-    for (int i = 0; i < m_openModeCombo->count(); ++i) {
-        if (m_openModeCombo->itemData(i).toInt() == mode) {
-            m_openModeCombo->setCurrentIndex(i);
-            found = true;
-            break;
-        }
-    }
-
-    Q_ASSERT(found);
-    return true;
-}
-
-bool VReadEditTab::saveOpenMode()
-{
-    int mode = m_openModeCombo->currentData().toInt();
-    g_config->setNoteOpenMode((OpenFileMode)mode);
-    return true;
-}
-
 VNoteManagementTab::VNoteManagementTab(QWidget *p_parent)
     : QWidget(p_parent)
 {
@@ -487,3 +495,91 @@ void VNoteManagementTab::customImageFolderExtChanged(int p_state)
         m_imageFolderEditExt->setEnabled(false);
     }
 }
+
+VMarkdownTab::VMarkdownTab(QWidget *p_parent)
+    : QWidget(p_parent)
+{
+    // Default note open mode.
+    m_openModeCombo = new QComboBox();
+    m_openModeCombo->setToolTip(tr("Default mode to open a note"));
+    m_openModeCombo->addItem(tr("Read Mode"), (int)OpenFileMode::Read);
+    m_openModeCombo->addItem(tr("Edit Mode"), (int)OpenFileMode::Edit);
+
+    QLabel *openModeLabel = new QLabel(tr("Note open mode:"));
+    openModeLabel->setToolTip(m_openModeCombo->toolTip());
+
+    // Heading sequence.
+    m_headingSequence = new QCheckBox();
+    m_headingSequence->setToolTip(tr("Enable auto sequence for all headings (in the form like 1.2.3.4.)"));
+
+    QLabel *headingSequenceLabel = new QLabel(tr("Heading sequence:"));
+    headingSequenceLabel->setToolTip(m_headingSequence->toolTip());
+
+    QFormLayout *mainLayout = new QFormLayout();
+    mainLayout->addRow(openModeLabel, m_openModeCombo);
+    mainLayout->addRow(headingSequenceLabel, m_headingSequence);
+
+    setLayout(mainLayout);
+}
+
+bool VMarkdownTab::loadConfiguration()
+{
+    if (!loadOpenMode()) {
+        return false;
+    }
+
+    if (!loadHeadingSequence()) {
+        return false;
+    }
+
+    return true;
+}
+
+bool VMarkdownTab::saveConfiguration()
+{
+    if (!saveOpenMode()) {
+        return false;
+    }
+
+    if (!saveHeadingSequence()) {
+        return false;
+    }
+
+    return true;
+}
+
+bool VMarkdownTab::loadOpenMode()
+{
+    int mode = (int)g_config->getNoteOpenMode();
+    bool found = false;
+    for (int i = 0; i < m_openModeCombo->count(); ++i) {
+        if (m_openModeCombo->itemData(i).toInt() == mode) {
+            m_openModeCombo->setCurrentIndex(i);
+            found = true;
+            break;
+        }
+    }
+
+    Q_ASSERT(found);
+    return true;
+}
+
+bool VMarkdownTab::saveOpenMode()
+{
+    int mode = m_openModeCombo->currentData().toInt();
+    g_config->setNoteOpenMode((OpenFileMode)mode);
+    return true;
+}
+
+bool VMarkdownTab::loadHeadingSequence()
+{
+    bool enabled = g_config->getEnableHeadingSequence();
+    m_headingSequence->setChecked(enabled);
+    return true;
+}
+
+bool VMarkdownTab::saveHeadingSequence()
+{
+    g_config->setEnableHeadingSequence(m_headingSequence->isChecked());
+    return true;
+}

+ 28 - 8
src/dialog/vsettingsdialog.h

@@ -6,12 +6,13 @@
 #include <QString>
 
 class QDialogButtonBox;
-class QTabWidget;
 class QComboBox;
 class QGroupBox;
 class QDoubleSpinBox;
 class QCheckBox;
 class QLineEdit;
+class QStackedLayout;
+class QListWidget;
 
 class VGeneralTab : public QWidget
 {
@@ -52,18 +53,12 @@ public:
     QCheckBox *m_customWebZoom;
     QDoubleSpinBox *m_webZoomFactorSpin;
 
-    // Default note open mode for markdown.
-    QComboBox *m_openModeCombo;
-
 private slots:
     void customWebZoomChanged(int p_state);
 
 private:
     bool loadWebZoomFactor();
     bool saveWebZoomFactor();
-
-    bool loadOpenMode();
-    bool saveOpenMode();
 };
 
 class VNoteManagementTab : public QWidget
@@ -97,6 +92,28 @@ private:
     bool saveImageFolderExt();
 };
 
+class VMarkdownTab : public QWidget
+{
+    Q_OBJECT
+public:
+    explicit VMarkdownTab(QWidget *p_parent = 0);
+    bool loadConfiguration();
+    bool saveConfiguration();
+
+    // Default note open mode for markdown.
+    QComboBox *m_openModeCombo;
+
+    // Whether enable heading sequence.
+    QCheckBox *m_headingSequence;
+
+private:
+    bool loadOpenMode();
+    bool saveOpenMode();
+
+    bool loadHeadingSequence();
+    bool saveHeadingSequence();
+};
+
 class VSettingsDialog : public QDialog
 {
     Q_OBJECT
@@ -109,7 +126,10 @@ private slots:
 private:
     void loadConfiguration();
 
-    QTabWidget *m_tabs;
+    void addTab(QWidget *p_widget, const QString &p_label);
+
+    QStackedLayout *m_tabs;
+    QListWidget *m_tabList;
     QDialogButtonBox *m_btnBox;
 };
 

+ 50 - 1
src/hgmarkdownhighlighter.cpp

@@ -278,9 +278,56 @@ void HGMarkdownHighlighter::initImageRegionsFromResult()
         m_imageRegions.resize(idx);
     }
 
+    qDebug() << "highlighter: parse" << m_imageRegions.size() << "image regions";
+
     emit imageLinksUpdated(m_imageRegions);
+}
 
-    qDebug() << "highlighter: parse" << m_imageRegions.size() << "image regions";
+void HGMarkdownHighlighter::initHeaderRegionsFromResult()
+{
+    if (!result) {
+        // From Qt5.7, the capacity is preserved.
+        m_headerRegions.clear();
+        emit headersUpdated(m_headerRegions);
+        return;
+    }
+
+    int idx = 0;
+    int oriSize = m_headerRegions.size();
+    pmh_element_type hx[6] = {pmh_H1, pmh_H2, pmh_H3, pmh_H4, pmh_H5, pmh_H6};
+    for (int i = 0; i < 6; ++i) {
+        pmh_element *elem = result[hx[i]];
+        while (elem != NULL) {
+            if (elem->end <= elem->pos) {
+                elem = elem->next;
+                continue;
+            }
+
+            if (idx < oriSize) {
+                // Try to reuse the original element.
+                VElementRegion &reg = m_headerRegions[idx];
+                if ((int)elem->pos != reg.m_startPos || (int)elem->end != reg.m_endPos) {
+                    reg.m_startPos = (int)elem->pos;
+                    reg.m_endPos = (int)elem->end;
+                }
+            } else {
+                m_headerRegions.push_back(VElementRegion(elem->pos, elem->end));
+            }
+
+            ++idx;
+            elem = elem->next;
+        }
+    }
+
+    if (idx < oriSize) {
+        m_headerRegions.resize(idx);
+    }
+
+    std::sort(m_headerRegions.begin(), m_headerRegions.end());
+
+    qDebug() << "highlighter: parse" << m_headerRegions.size() << "header regions";
+
+    emit headersUpdated(m_headerRegions);
 }
 
 void HGMarkdownHighlighter::initBlockHighlihgtOne(unsigned long pos, unsigned long end, int styleIndex)
@@ -399,6 +446,8 @@ void HGMarkdownHighlighter::parse()
 
     initImageRegionsFromResult();
 
+    initHeaderRegionsFromResult();
+
     if (result) {
         pmh_free_elements(result);
         result = NULL;

+ 22 - 0
src/hgmarkdownhighlighter.h

@@ -106,6 +106,17 @@ struct VElementRegion
         return (m_startPos == p_other.m_startPos
                 && m_endPos == p_other.m_endPos);
     }
+
+    bool operator<(const VElementRegion &p_other) const
+    {
+        if (m_startPos < p_other.m_startPos) {
+            return true;
+        } else if (m_startPos == p_other.m_startPos) {
+            return m_endPos <= p_other.m_endPos;
+        } else {
+            return false;
+        }
+    }
 };
 
 class HGMarkdownHighlighter : public QSyntaxHighlighter
@@ -133,6 +144,9 @@ signals:
     // Emitted when image regions have been fetched from a new parsing result.
     void imageLinksUpdated(const QVector<VElementRegion> &p_imageRegions);
 
+    // Emitted when header regions have been fetched from a new parsing result.
+    void headersUpdated(const QVector<VElementRegion> &p_headerRegions);
+
 protected:
     void highlightBlock(const QString &text) Q_DECL_OVERRIDE;
 
@@ -174,6 +188,11 @@ private:
     // All image link regions.
     QVector<VElementRegion> m_imageRegions;
 
+    // All header regions.
+    // May contains illegal elements.
+    // Sorted by start position.
+    QVector<VElementRegion> m_headerRegions;
+
     // Timer to signal highlightCompleted().
     QTimer *m_completeTimer;
 
@@ -211,6 +230,9 @@ private:
     // Fetch all the image link regions from parsing result.
     void initImageRegionsFromResult();
 
+    // Fetch all the header regions from parsing result.
+    void initHeaderRegionsFromResult();
+
     // Whether @p_block is totally inside a HTML comment.
     bool isBlockInsideCommentRegion(const QTextBlock &p_block) const;
 

+ 1 - 1
src/resources/docs/shortcuts_en.md

@@ -189,4 +189,4 @@ VNote supports following features of Vim:
 
 For now, VNote does **NOT** support the macro and repeat(`.`) features of Vim.
 
-Enjoy Vim on VNote!
+Enjoy Vim in VNote!

+ 3 - 0
src/resources/vnote.ini

@@ -83,6 +83,9 @@ insert_title_from_note_name=true
 ; 0 - Read, 1 - Edit
 note_open_mode=0
 
+; Whether auto generate heading sequence
+enable_heading_sequence=false
+
 [session]
 tools_dock_checked=true
 

+ 3 - 3
src/utils/vutils.cpp

@@ -39,9 +39,9 @@ const QString VUtils::c_fencedCodeBlockEndRegExp = QString("^(\\s*)```$");
 
 const QString VUtils::c_previewImageBlockRegExp = QString("[\\n|^][ |\\t]*\\xfffc[ |\\t]*(?=\\n)");
 
-VUtils::VUtils()
-{
-}
+const QString VUtils::c_headerRegExp = QString("^(#{1,6})\\s+(((\\d+\\.)+(?=\\s))?\\s?\\S.*)\\s*$");
+
+const QString VUtils::c_headerPrefixRegExp = QString("^(#{1,6}\\s+((\\d+\\.)+(?=\\s))?\\s?)\\S.*\\s*$");
 
 void VUtils::initAvailableLanguage()
 {

+ 14 - 1
src/utils/vutils.h

@@ -144,8 +144,21 @@ public:
     // Regular expression for preview image block.
     static const QString c_previewImageBlockRegExp;
 
+    // Regular expression for header block.
+    // Captured texts:
+    // 1. Header marker (##);
+    // 2. Header Title (need to be trimmed);
+    // 3. Header Sequence (1.1., 1.2., optional);
+    // 4. Unused;
+    static const QString c_headerRegExp;
+
+    // Regular expression for header block.
+    // Captured texts:
+    // 1. prefix till the real header title content;
+    static const QString c_headerPrefixRegExp;
+
 private:
-    VUtils();
+    VUtils() {}
 
     static void initAvailableLanguage();
 

+ 3 - 0
src/vconfigmanager.cpp

@@ -174,6 +174,9 @@ void VConfigManager::initialize()
     } else {
         m_noteOpenMode = OpenFileMode::Read;
     }
+
+    m_enableHeadingSequence = getConfigFromSettings("global",
+                                                    "enable_heading_sequence").toBool();
 }
 
 void VConfigManager::readPredefinedColorsFromSettings()

+ 22 - 0
src/vconfigmanager.h

@@ -232,6 +232,9 @@ public:
     OpenFileMode getNoteOpenMode() const;
     void setNoteOpenMode(OpenFileMode p_mode);
 
+    bool getEnableHeadingSequence() const;
+    void setEnableHeadingSequence(bool p_enabled);
+
     // Return the configured key sequence of @p_operation.
     // Return empty if there is no corresponding config.
     QString getShortcutKeySequence(const QString &p_operation) const;
@@ -469,6 +472,9 @@ private:
     // Default mode when opening a note.
     OpenFileMode m_noteOpenMode;
 
+    // Whether auto genearte heading sequence.
+    bool m_enableHeadingSequence;
+
     // The name of the config file in each directory, obsolete.
     // Use c_dirConfigFile instead.
     static const QString c_obsoleteDirConfigFile;
@@ -1204,4 +1210,20 @@ inline void VConfigManager::setNoteOpenMode(OpenFileMode p_mode)
                         m_noteOpenMode == OpenFileMode::Read ? 0 : 1);
 }
 
+inline bool VConfigManager::getEnableHeadingSequence() const
+{
+    return m_enableHeadingSequence;
+}
+
+inline void VConfigManager::setEnableHeadingSequence(bool p_enabled)
+{
+    if (m_enableHeadingSequence == p_enabled) {
+        return;
+    }
+
+    m_enableHeadingSequence = p_enabled;
+    setConfigToSettings("global", "enable_heading_sequence",
+                        m_enableHeadingSequence);
+}
+
 #endif // VCONFIGMANAGER_H

+ 104 - 11
src/vmdedit.cpp

@@ -31,8 +31,9 @@ VMdEdit::VMdEdit(VFile *p_file, VDocument *p_vdoc, MarkdownConverterType p_type,
                                                 g_config->getCodeBlockStyles(),
                                                 g_config->getMarkdownHighlightInterval(),
                                                 document());
-    connect(m_mdHighlighter, &HGMarkdownHighlighter::highlightCompleted,
-            this, &VMdEdit::generateEditOutline);
+
+    connect(m_mdHighlighter, &HGMarkdownHighlighter::headersUpdated,
+            this, &VMdEdit::updateOutline);
 
     // After highlight, the cursor may trun into non-visible. We should make it visible
     // in this case.
@@ -99,9 +100,6 @@ void VMdEdit::beginEdit()
 
     setModified(false);
 
-    // Request update outline.
-    generateEditOutline();
-
     if (m_freshEdit) {
         // Will set to false when all async jobs completed.
         setReadOnly(true);
@@ -110,6 +108,8 @@ void VMdEdit::beginEdit()
     } else {
         setReadOnly(false);
     }
+
+    m_mdHighlighter->updateHighlight();
 }
 
 void VMdEdit::endEdit()
@@ -330,24 +330,98 @@ void VMdEdit::updateCurHeader()
     emit curHeaderChanged(VAnchor(m_file, "", m_headers[idx].lineNumber, m_headers[idx].index));
 }
 
-void VMdEdit::generateEditOutline()
+static void addHeaderSequence(QVector<int> &p_sequence, int p_level)
+{
+    Q_ASSERT(p_level >= 1 && p_level < p_sequence.size());
+    ++p_sequence[p_level];
+    for (int i = p_level + 1; i < p_sequence.size(); ++i) {
+        p_sequence[i] = 0;
+    }
+}
+
+static QString headerSequenceStr(const QVector<int> &p_sequence)
+{
+    QString res;
+    for (int i = 1; i < p_sequence.size(); ++i) {
+        if (p_sequence[i] != 0) {
+            res = res + QString::number(p_sequence[i]) + '.';
+        } else if (res.isEmpty()) {
+            continue;
+        } else {
+            break;
+        }
+    }
+
+    return res;
+}
+
+static void insertSequenceToHeader(QTextBlock &p_block,
+                                   QRegExp &p_reg,
+                                   QRegExp &p_preReg,
+                                   const QString &p_seq)
+{
+    if (!p_block.isValid()) {
+        return;
+    }
+
+    QString text = p_block.text();
+    bool matched = p_reg.exactMatch(text);
+    Q_ASSERT(matched);
+
+    matched = p_preReg.exactMatch(text);
+    Q_ASSERT(matched);
+
+    int start = p_reg.cap(1).length() + 1;
+    int end = p_preReg.cap(1).length();
+
+    Q_ASSERT(start <= end);
+
+    QTextCursor cursor(p_block);
+    cursor.setPosition(p_block.position() + start);
+    if (start != end) {
+        cursor.setPosition(p_block.position() + end, QTextCursor::KeepAnchor);
+    }
+
+    cursor.insertText(p_seq + ' ');
+}
+
+void VMdEdit::updateOutline(const QVector<VElementRegion> &p_headerRegions)
 {
     QTextDocument *doc = document();
 
     QVector<VHeader> headers;
+    QVector<int> headerBlockNumbers;
+    QVector<QString> headerSequences;
+    if (!p_headerRegions.isEmpty()) {
+        headers.reserve(p_headerRegions.size());
+        headerBlockNumbers.reserve(p_headerRegions.size());
+        headerSequences.reserve(p_headerRegions.size());
+    }
 
     // Assume that each block contains only one line
     // Only support # syntax for now
-    QRegExp headerReg("(#{1,6})\\s+(\\S.*)");  // Need to trim the spaces
+    QRegExp headerReg(VUtils::c_headerRegExp);
     int baseLevel = -1;
-    for (QTextBlock block = doc->begin(); block != doc->end(); block = block.next()) {
-        V_ASSERT(block.lineCount() == 1);
+    for (auto const & reg : p_headerRegions) {
+        QTextBlock block = doc->findBlock(reg.m_startPos);
+        if (!block.isValid()) {
+            continue;
+        }
+
+        Q_ASSERT(block.lineCount() == 1);
+
+        if (!block.contains(reg.m_endPos - 1)) {
+            continue;
+        }
+
         if ((block.userState() == HighlightBlockState::Normal) &&
             headerReg.exactMatch(block.text())) {
             int level = headerReg.cap(1).length();
             VHeader header(level, headerReg.cap(2).trimmed(),
                            "", block.firstLineNumber(), headers.size());
             headers.append(header);
+            headerBlockNumbers.append(block.blockNumber());
+            headerSequences.append(headerReg.cap(3));
 
             if (baseLevel == -1) {
                 baseLevel = level;
@@ -359,18 +433,37 @@ void VMdEdit::generateEditOutline()
 
     m_headers.clear();
 
+    bool autoSequence = g_config->getEnableHeadingSequence() && !isReadOnly();
+    QVector<int> seqs(7, 0);
+    QRegExp preReg(VUtils::c_headerPrefixRegExp);
     int curLevel = baseLevel - 1;
-    for (auto & item : headers) {
+    for (int i = 0; i < headers.size(); ++i) {
+        VHeader &item = headers[i];
         while (item.level > curLevel + 1) {
             curLevel += 1;
 
             // Insert empty level which is an invalid header.
             m_headers.append(VHeader(curLevel, c_emptyHeaderName, "", -1, m_headers.size()));
+            if (autoSequence) {
+                addHeaderSequence(seqs, curLevel);
+            }
         }
 
         item.index = m_headers.size();
         m_headers.append(item);
         curLevel = item.level;
+        if (autoSequence) {
+            addHeaderSequence(seqs, item.level);
+
+            QString seqStr = headerSequenceStr(seqs);
+            if (headerSequences[i] != seqStr) {
+                // Insert correct sequence.
+                insertSequenceToHeader(doc->findBlockByNumber(headerBlockNumbers[i]),
+                                       headerReg,
+                                       preReg,
+                                       seqStr);
+            }
+        }
     }
 
     emit headersChanged(m_headers);
@@ -670,10 +763,10 @@ void VMdEdit::finishOneAsyncJob(int p_idx)
     m_finishedAsyncJobs[p_idx] = true;
     if (-1 == m_finishedAsyncJobs.indexOf(false)) {
         // All jobs finished.
-        m_freshEdit = false;
         setUndoRedoEnabled(true);
         setReadOnly(false);
         setModified(false);
+        m_freshEdit = false;
         emit statusChanged();
     }
 }

+ 1 - 1
src/vmdedit.h

@@ -53,7 +53,7 @@ signals:
     void statusChanged();
 
 private slots:
-    void generateEditOutline();
+    void updateOutline(const QVector<VElementRegion> &p_headerRegions);
 
     // When there is no header in current cursor, will signal an invalid header.
     void updateCurHeader();