support auto heading sequence by config enable_heading_sequence

This commit is contained in:
Le Tan 2017-09-11 19:49:30 +08:00
parent ed4044061f
commit 2eb6476c3d
14 changed files with 400 additions and 75 deletions

View File

@ -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) - [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) - 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 ## Linux
[![Build Status](https://travis-ci.org/tamlok/vnote.svg?branch=master)](https://travis-ci.org/tamlok/vnote) [![Build Status](https://travis-ci.org/tamlok/vnote.svg?branch=master)](https://travis-ci.org/tamlok/vnote)

View File

@ -14,6 +14,8 @@
- [Github releases](https://github.com/tamlok/vnote/releases) - [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) - 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 ## Linux
[![Build Status](https://travis-ci.org/tamlok/vnote.svg?branch=master)](https://travis-ci.org/tamlok/vnote) [![Build Status](https://travis-ci.org/tamlok/vnote.svg?branch=master)](https://travis-ci.org/tamlok/vnote)

View File

@ -10,25 +10,57 @@ extern VConfigManager *g_config;
VSettingsDialog::VSettingsDialog(QWidget *p_parent) VSettingsDialog::VSettingsDialog(QWidget *p_parent)
: QDialog(p_parent) : QDialog(p_parent)
{ {
m_tabs = new QTabWidget; m_tabList = new QListWidget(this);
m_tabs->addTab(new VGeneralTab(), tr("General")); m_tabList->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
m_tabs->addTab(new VReadEditTab(), tr("Read/Edit"));
m_tabs->addTab(new VNoteManagementTab(), tr("Note Management")); m_tabs = new QStackedLayout();
m_btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); m_btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
connect(m_btnBox, &QDialogButtonBox::accepted, this, &VSettingsDialog::saveConfiguration); connect(m_btnBox, &QDialogButtonBox::accepted, this, &VSettingsDialog::saveConfiguration);
connect(m_btnBox, &QDialogButtonBox::rejected, this, &QDialog::reject); connect(m_btnBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
QVBoxLayout *mainLayout = new QVBoxLayout; QHBoxLayout *tabLayout = new QHBoxLayout();
mainLayout->addWidget(m_tabs); 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); mainLayout->addWidget(m_btnBox);
setLayout(mainLayout); setLayout(mainLayout);
setWindowTitle(tr("Settings")); 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(); 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() void VSettingsDialog::loadConfiguration()
{ {
// General Tab. // 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; return;
err: err:
VUtils::showMessage(QMessageBox::Warning, tr("Warning"), 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(); accept();
return; return;
err: err:
@ -234,25 +284,14 @@ VReadEditTab::VReadEditTab(QWidget *p_parent)
zoomFactorLayout->addWidget(m_customWebZoom); zoomFactorLayout->addWidget(m_customWebZoom);
zoomFactorLayout->addWidget(m_webZoomFactorSpin); 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(); QFormLayout *readLayout = new QFormLayout();
readLayout->addRow(zoomFactorLayout); readLayout->addRow(zoomFactorLayout);
readLayout->addRow(openModeLabel, m_openModeCombo);
m_readBox->setLayout(readLayout); m_readBox->setLayout(readLayout);
QVBoxLayout *mainLayout = new QVBoxLayout(); QVBoxLayout *mainLayout = new QVBoxLayout();
mainLayout->addWidget(m_readBox); mainLayout->addWidget(m_readBox);
mainLayout->addWidget(m_editBox); mainLayout->addWidget(m_editBox);
m_editBox->hide();
setLayout(mainLayout); setLayout(mainLayout);
} }
@ -262,10 +301,6 @@ bool VReadEditTab::loadConfiguration()
return false; return false;
} }
if (!loadOpenMode()) {
return false;
}
return true; return true;
} }
@ -275,10 +310,6 @@ bool VReadEditTab::saveConfiguration()
return false; return false;
} }
if (!saveOpenMode()) {
return false;
}
return true; return true;
} }
@ -315,29 +346,6 @@ void VReadEditTab::customWebZoomChanged(int p_state)
m_webZoomFactorSpin->setEnabled(p_state == Qt::Checked); 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) VNoteManagementTab::VNoteManagementTab(QWidget *p_parent)
: QWidget(p_parent) : QWidget(p_parent)
{ {
@ -487,3 +495,91 @@ void VNoteManagementTab::customImageFolderExtChanged(int p_state)
m_imageFolderEditExt->setEnabled(false); 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;
}

View File

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

View File

@ -278,9 +278,56 @@ void HGMarkdownHighlighter::initImageRegionsFromResult()
m_imageRegions.resize(idx); m_imageRegions.resize(idx);
} }
emit imageLinksUpdated(m_imageRegions);
qDebug() << "highlighter: parse" << m_imageRegions.size() << "image regions"; qDebug() << "highlighter: parse" << m_imageRegions.size() << "image regions";
emit imageLinksUpdated(m_imageRegions);
}
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) void HGMarkdownHighlighter::initBlockHighlihgtOne(unsigned long pos, unsigned long end, int styleIndex)
@ -399,6 +446,8 @@ void HGMarkdownHighlighter::parse()
initImageRegionsFromResult(); initImageRegionsFromResult();
initHeaderRegionsFromResult();
if (result) { if (result) {
pmh_free_elements(result); pmh_free_elements(result);
result = NULL; result = NULL;

View File

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

View File

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

View File

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

View File

@ -39,9 +39,9 @@ const QString VUtils::c_fencedCodeBlockEndRegExp = QString("^(\\s*)```$");
const QString VUtils::c_previewImageBlockRegExp = QString("[\\n|^][ |\\t]*\\xfffc[ |\\t]*(?=\\n)"); 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() void VUtils::initAvailableLanguage()
{ {

View File

@ -144,8 +144,21 @@ public:
// Regular expression for preview image block. // Regular expression for preview image block.
static const QString c_previewImageBlockRegExp; 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: private:
VUtils(); VUtils() {}
static void initAvailableLanguage(); static void initAvailableLanguage();

View File

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

View File

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

View File

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

View File

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