diff --git a/src/hgmarkdownhighlighter.cpp b/src/hgmarkdownhighlighter.cpp index c87bc3a3..7dfc34bb 100644 --- a/src/hgmarkdownhighlighter.cpp +++ b/src/hgmarkdownhighlighter.cpp @@ -77,7 +77,7 @@ void HGMarkdownHighlighter::highlightBlock(const QString &text) setFormat(unit.start, unit.length, highlightingStyles[unit.styleIndex].format); } } - setCurrentBlockState(0); + setCurrentBlockState(HighlightBlockState::BlockNormal); highlightCodeBlock(text); } @@ -146,7 +146,7 @@ void HGMarkdownHighlighter::highlightCodeBlock(const QString &text) { int nextIndex = 0; int startIndex = 0; - if (previousBlockState() != 1) { + if (previousBlockState() != HighlightBlockState::BlockCodeBlock) { startIndex = codeBlockStartExp.indexIn(text); if (startIndex >= 0) { nextIndex = startIndex + codeBlockStartExp.matchedLength(); @@ -159,7 +159,7 @@ void HGMarkdownHighlighter::highlightCodeBlock(const QString &text) int endIndex = codeBlockEndExp.indexIn(text, nextIndex); int codeBlockLength; if (endIndex == -1) { - setCurrentBlockState(1); + setCurrentBlockState(HighlightBlockState::BlockCodeBlock); codeBlockLength = text.length() - startIndex; } else { codeBlockLength = endIndex - startIndex + codeBlockEndExp.matchedLength(); @@ -230,4 +230,5 @@ void HGMarkdownHighlighter::timerTimeout() { parse(); rehighlight(); + emit highlightCompleted(); } diff --git a/src/hgmarkdownhighlighter.h b/src/hgmarkdownhighlighter.h index fa269984..89522c37 100644 --- a/src/hgmarkdownhighlighter.h +++ b/src/hgmarkdownhighlighter.h @@ -28,6 +28,12 @@ struct HighlightingStyle QTextCharFormat format; }; +enum HighlightBlockState +{ + BlockNormal = 0, + BlockCodeBlock = 1, +}; + // One continuous region for a certain markdown highlight style // within a QTextBlock. // Pay attention to the change of HighlightingStyles[] @@ -50,6 +56,9 @@ public: ~HGMarkdownHighlighter(); void setStyles(const QVector &styles); +signals: + void highlightCompleted(); + protected: void highlightBlock(const QString &text) Q_DECL_OVERRIDE; diff --git a/src/vedit.cpp b/src/vedit.cpp index fcad2dec..94f90d10 100644 --- a/src/vedit.cpp +++ b/src/vedit.cpp @@ -1,9 +1,11 @@ #include +#include #include "vedit.h" #include "vnote.h" #include "vconfigmanager.h" #include "hgmarkdownhighlighter.h" #include "vmdeditoperations.h" +#include "vtoc.h" extern VConfigManager vconfig; @@ -14,6 +16,8 @@ VEdit::VEdit(VNoteFile *noteFile, QWidget *parent) setAcceptRichText(false); mdHighlighter = new HGMarkdownHighlighter(vconfig.getMdHighlightingStyles(), 500, document()); + connect(mdHighlighter, &HGMarkdownHighlighter::highlightCompleted, + this, &VEdit::generateEditOutline); editOps = new VMdEditOperations(this, noteFile); } else { setAutoFormatting(QTextEdit::AutoBulletList); @@ -22,6 +26,8 @@ VEdit::VEdit(VNoteFile *noteFile, QWidget *parent) updateTabSettings(); updateFontAndPalette(); + connect(this, &VEdit::cursorPositionChanged, + this, &VEdit::updateCurHeader); } VEdit::~VEdit() @@ -164,3 +170,50 @@ void VEdit::insertFromMimeData(const QMimeData *source) } QTextEdit::insertFromMimeData(source); } + +void VEdit::generateEditOutline() +{ + QTextDocument *doc = document(); + headers.clear(); + // Assume that each block contains only one line + // Only support # syntax for now + QRegExp headerReg("(#{1,6})\\s*(\\S.*)"); // Need to trim the spaces + for (QTextBlock block = doc->begin(); block != doc->end(); block = block.next()) { + Q_ASSERT(block.lineCount() == 1); + if (headerReg.exactMatch(block.text())) { + VHeader header(headerReg.cap(1).length(), + headerReg.cap(2).trimmed(), "", block.firstLineNumber()); + headers.append(header); + } + } + + emit headersChanged(headers); + updateCurHeader(); +} + +void VEdit::scrollToLine(int lineNumber) +{ + Q_ASSERT(lineNumber >= 0); + + // Move the cursor to the end first + moveCursor(QTextCursor::End); + QTextCursor cursor(document()->findBlockByLineNumber(lineNumber)); + cursor.movePosition(QTextCursor::EndOfLine); + setTextCursor(cursor); + + setFocus(); +} + +void VEdit::updateCurHeader() +{ + int curHeader = 0; + QTextCursor cursor(this->textCursor()); + int curLine = cursor.block().firstLineNumber(); + for (int i = headers.size() - 1; i >= 0; --i) { + if (headers[i].lineNumber <= curLine) { + curHeader = headers[i].lineNumber; + break; + } + } + emit curHeaderChanged(curHeader); +} diff --git a/src/vedit.h b/src/vedit.h index 80fefb8c..c27d3ffe 100644 --- a/src/vedit.h +++ b/src/vedit.h @@ -5,6 +5,7 @@ #include #include "vconstants.h" #include "vnotefile.h" +#include "vtoc.h" class HGMarkdownHighlighter; class VEditOperations; @@ -24,12 +25,21 @@ public: inline bool isModified() const; void reloadFile(); + void scrollToLine(int lineNumber); + +signals: + void headersChanged(const QVector &headers); + void curHeaderChanged(int lineNumber); protected: void keyPressEvent(QKeyEvent *event) Q_DECL_OVERRIDE; bool canInsertFromMimeData(const QMimeData *source) const Q_DECL_OVERRIDE; void insertFromMimeData(const QMimeData *source) Q_DECL_OVERRIDE; +private slots: + void generateEditOutline(); + void updateCurHeader(); + private: void updateTabSettings(); void updateFontAndPalette(); @@ -39,6 +49,7 @@ private: VNoteFile *noteFile; HGMarkdownHighlighter *mdHighlighter; VEditOperations *editOps; + QVector headers; }; diff --git a/src/vedittab.cpp b/src/vedittab.cpp index 38ba91d5..0ad88bed 100644 --- a/src/vedittab.cpp +++ b/src/vedittab.cpp @@ -49,6 +49,10 @@ VEditTab::~VEditTab() void VEditTab::setupUI() { textEditor = new VEdit(noteFile); + connect(textEditor, &VEdit::headersChanged, + this, &VEditTab::updateTocFromHeaders); + connect(textEditor, SIGNAL(curHeaderChanged(int)), + this, SLOT(updateCurHeader(int))); addWidget(textEditor); switch (noteFile->docType) { @@ -220,8 +224,8 @@ void VEditTab::setupMarkdownPreview() channel->registerObject(QStringLiteral("content"), &document); connect(&document, &VDocument::tocChanged, this, &VEditTab::updateTocFromHtml); - connect(&document, &VDocument::headerChanged, - this, &VEditTab::updateCurHeader); + connect(&document, SIGNAL(headerChanged(const QString&)), + this, SLOT(updateCurHeader(const QString &))); page->setWebChannel(channel); if (mdConverterType == MarkdownConverterType::Marked) { @@ -273,6 +277,16 @@ void VEditTab::updateTocFromHtml(const QString &tocHtml) emit outlineChanged(tableOfContent); } +void VEditTab::updateTocFromHeaders(const QVector &headers) +{ + tableOfContent.type = VHeaderType::LineNumber; + tableOfContent.headers = headers; + tableOfContent.filePath = QDir::cleanPath(QDir(noteFile->basePath).filePath(noteFile->fileName)); + tableOfContent.valid = true; + + emit outlineChanged(tableOfContent); +} + void VEditTab::parseTocUl(QXmlStreamReader &xml, QVector &headers, int level) { Q_ASSERT(xml.isStartElement() && xml.name() == "ul"); @@ -349,7 +363,7 @@ void VEditTab::scrollToAnchor(const VAnchor &anchor) { if (isEditMode) { if (anchor.lineNumber > -1) { - + textEditor->scrollToLine(anchor.lineNumber); } } else { if (!anchor.anchor.isEmpty()) { @@ -369,3 +383,15 @@ void VEditTab::updateCurHeader(const QString &anchor) emit curHeaderChanged(curHeader); } } + +void VEditTab::updateCurHeader(int lineNumber) +{ + if (curHeader.lineNumber == lineNumber) { + return; + } + curHeader = VAnchor(QDir::cleanPath(QDir(noteFile->basePath).filePath(noteFile->fileName)), + "", lineNumber); + if (lineNumber > -1) { + emit curHeaderChanged(curHeader); + } +} diff --git a/src/vedittab.h b/src/vedittab.h index c975f5a2..00696c97 100644 --- a/src/vedittab.h +++ b/src/vedittab.h @@ -46,6 +46,8 @@ private slots: void handleFocusChanged(QWidget *old, QWidget *now); void updateTocFromHtml(const QString &tocHtml); void updateCurHeader(const QString &anchor); + void updateCurHeader(int lineNumber); + void updateTocFromHeaders(const QVector &headers); private: bool isMarkdown(const QString &name); diff --git a/src/voutline.cpp b/src/voutline.cpp index 6b45e91b..828489e2 100644 --- a/src/voutline.cpp +++ b/src/voutline.cpp @@ -21,6 +21,7 @@ void VOutline::updateOutline(const VToc &toc) outline = toc; updateTreeFromOutline(outline); expandTree(); + updateCurHeader(curHeader); } void VOutline::updateTreeFromOutline(const VToc &toc) @@ -90,6 +91,7 @@ void VOutline::updateCurHeader(const VAnchor &anchor) selectAnchor(anchor.anchor); } else { // Select by lineNumber + selectLineNumber(anchor.lineNumber); } } @@ -129,3 +131,40 @@ bool VOutline::selectAnchorOne(QTreeWidgetItem *item, const QString &anchor) } return false; } + +void VOutline::selectLineNumber(int lineNumber) +{ + QList selected = selectedItems(); + foreach (QTreeWidgetItem *item, selected) { + setItemSelected(item, false); + } + + int nrTop = topLevelItemCount(); + for (int i = 0; i < nrTop; ++i) { + if (selectLineNumberOne(topLevelItem(i), lineNumber)) { + return; + } + } +} + +bool VOutline::selectLineNumberOne(QTreeWidgetItem *item, int lineNumber) +{ + if (!item) { + return false; + } + QJsonObject itemJson = item->data(0, Qt::UserRole).toJsonObject(); + int itemLineNum = itemJson["line_number"].toInt(); + if (itemLineNum == lineNumber) { + // Select this item + setItemSelected(item, true); + return true; + } + + int nrChild = item->childCount(); + for (int i = 0; i < nrChild; ++i) { + if (selectLineNumberOne(item->child(i), lineNumber)) { + return true; + } + } + return false; +} diff --git a/src/voutline.h b/src/voutline.h index bd775710..874629c9 100644 --- a/src/voutline.h +++ b/src/voutline.h @@ -27,6 +27,8 @@ private: void expandTree(); void selectAnchor(const QString &anchor); bool selectAnchorOne(QTreeWidgetItem *item, const QString &anchor); + void selectLineNumber(int lineNumber); + bool selectLineNumberOne(QTreeWidgetItem *item, int lineNumber); VToc outline; VAnchor curHeader;