mirror of
https://gitee.com/vnotex/vnote.git
synced 2025-07-05 13:59:52 +08:00
support auto heading sequence by config enable_heading_sequence
This commit is contained in:
parent
ed4044061f
commit
2eb6476c3d
@ -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: [  ](https://bintray.com/tamlok/vnote/vnote/_latestVersion)
|
||||
|
||||
**NOT** supported in XP since QtWebEngineProcess used by VNote could not work in XP.
|
||||
|
||||
## Linux
|
||||
[](https://travis-ci.org/tamlok/vnote)
|
||||
|
||||
|
@ -14,6 +14,8 @@
|
||||
- [Github releases](https://github.com/tamlok/vnote/releases)
|
||||
- master分支的最新构建:[  ](https://bintray.com/tamlok/vnote/vnote/_latestVersion)
|
||||
|
||||
VNote不支持**XP**,因为QtWebEngineProcess无法在XP上运行。
|
||||
|
||||
## Linux
|
||||
[](https://travis-ci.org/tamlok/vnote)
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -278,9 +278,56 @@ void HGMarkdownHighlighter::initImageRegionsFromResult()
|
||||
m_imageRegions.resize(idx);
|
||||
}
|
||||
|
||||
emit imageLinksUpdated(m_imageRegions);
|
||||
|
||||
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 ® = 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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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!
|
||||
|
@ -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
|
||||
|
||||
|
@ -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()
|
||||
{
|
||||
|
@ -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();
|
||||
|
||||
|
@ -174,6 +174,9 @@ void VConfigManager::initialize()
|
||||
} else {
|
||||
m_noteOpenMode = OpenFileMode::Read;
|
||||
}
|
||||
|
||||
m_enableHeadingSequence = getConfigFromSettings("global",
|
||||
"enable_heading_sequence").toBool();
|
||||
}
|
||||
|
||||
void VConfigManager::readPredefinedColorsFromSettings()
|
||||
|
@ -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
|
||||
|
115
src/vmdedit.cpp
115
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();
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
Loading…
x
Reference in New Issue
Block a user