diff --git a/libs/vtextedit b/libs/vtextedit index 392c0e21..59e53f2c 160000 --- a/libs/vtextedit +++ b/libs/vtextedit @@ -1 +1 @@ -Subproject commit 392c0e218f5981b4fc6566512103b6149f714931 +Subproject commit 59e53f2cdc2f5a4b9dc44cbf28c483cca07559ba diff --git a/src/core/configmgr.cpp b/src/core/configmgr.cpp index 5cca0ec8..30e0d7c9 100644 --- a/src/core/configmgr.cpp +++ b/src/core/configmgr.cpp @@ -24,7 +24,7 @@ using namespace vnotex; #ifndef QT_NO_DEBUG - // #define VX_DEBUG_WEB + #define VX_DEBUG_WEB #endif const QString ConfigMgr::c_orgName = "VNote"; diff --git a/src/core/fileopenparameters.h b/src/core/fileopenparameters.h index 939d78b4..3a5c61fa 100644 --- a/src/core/fileopenparameters.h +++ b/src/core/fileopenparameters.h @@ -1,11 +1,14 @@ #ifndef FILEOPENPARAMETERS_H #define FILEOPENPARAMETERS_H +#include + #include "global.h" namespace vnotex { class Node; + class SearchToken; struct FileOpenParameters { @@ -32,6 +35,9 @@ namespace vnotex // Whether always open a new window for file. bool m_alwaysNewWindow = false; + + // If not empty, use this token to do a search text highlight. + QSharedPointer m_searchToken; }; } diff --git a/src/data/extra/web/js/markdownviewer.js b/src/data/extra/web/js/markdownviewer.js index 10b534fb..c08ef5b5 100644 --- a/src/data/extra/web/js/markdownviewer.js +++ b/src/data/extra/web/js/markdownviewer.js @@ -39,8 +39,8 @@ new QWebChannel(qt.webChannelTransport, window.vnotex.crossCopy(p_id, p_timeStamp, p_target, p_baseUrl, p_html); }); - adapter.findTextRequested.connect(function(p_text, p_options) { - window.vnotex.findText(p_text, p_options); + adapter.findTextRequested.connect(function(p_texts, p_options, p_currentMatchLine) { + window.vnotex.findText(p_texts, p_options, p_currentMatchLine); }); adapter.contentRequested.connect(function() { diff --git a/src/data/extra/web/js/markjs.js b/src/data/extra/web/js/markjs.js index 32a3d914..34a2f938 100644 --- a/src/data/extra/web/js/markjs.js +++ b/src/data/extra/web/js/markjs.js @@ -7,6 +7,7 @@ class MarkJs { this.markjs = null; this.cache = null; this.matchedNodes = null; + this.currentMatchedNodes = null; this.adapter.on('basicMarkdownRendered', () => { this.clearCache(); @@ -19,59 +20,102 @@ class MarkJs { // wholeWordOnly, // regularExpression // } - findText(p_text, p_options) { + findText(p_texts, p_options, p_currentMatchLine) { if (!this.markjs) { this.markjs = new Mark(this.container); } - if (!p_text) { + if (!p_texts || p_texts.length == 0) { // Clear the cache and highlight. this.clearCache(); return; } - if (this.findInCache(p_text, p_options)) { + if (this.findInCache(p_texts, p_options, p_currentMatchLine)) { return; } // A new find. this.clearCache(); - let callbackFunc = function(markjs, text, options) { + let callbackFunc = function(markjs, texts, options, currentMatchLine) { let _markjs = markjs; - let _text = text; + let _texts = texts; let _options = options; - return function(totalMatches) { - if (!_markjs.matchedNodes) { + let _currentMatchLine = currentMatchLine; + return function() { + if (_markjs.matchedNodes === null) { _markjs.matchedNodes = _markjs.container.getElementsByClassName(_markjs.className); + _markjs.currentMatchedNodes = _markjs.container.getElementsByClassName(_markjs.currentMatchClassName); } // Update cache. _markjs.cache = { - text: _text, + texts: _texts, options: _options, currentIdx: -1 } - _markjs.updateCurrentMatch(_text, !_options.findBackward); + _markjs.updateCurrentMatch(_texts, !_options.findBackward, _currentMatchLine); }; } + + if (p_options.regularExpression) { + this.findByOneRegExp({ + 'texts': p_texts, + 'options': p_options, + 'textIdx': 0, + 'lastCallback': callbackFunc(this, p_texts, p_options, p_currentMatchLine) + }); + } else { + let opt = this.createMarkjsOptions(p_options); + opt.done = callbackFunc(this, p_texts, p_options, p_currentMatchLine); + this.markjs.mark(p_texts, opt); + } + } + + createMarkjsOptions(p_options) { let opt = { 'element': 'span', 'className': this.className, 'caseSensitive': p_options.caseSensitive, 'accuracy': p_options.wholeWordOnly ? 'exactly' : 'partially', - 'done': callbackFunc(this, p_text, p_options), // Ignore SVG, or SVG will be corrupted. 'exclude': ['svg *'] } + return opt; + } - if (p_options.regularExpression) { - // TODO: may need transformation from QRegularExpression to RegExp. - this.markjs.markRegExp(new RegExp(p_text), opt); - } else { - this.markjs.mark(p_text, opt); + // @p_paras: { + // texts, + // options, + // textIdx, + // lastCallback + // } + findByOneRegExp(p_paras) { + console.log('findByOneRegExp', p_paras.texts.length, p_paras.textIdx); + + if (p_paras.textIdx >= p_paras.texts.length) { + return; } + + let opt = this.createMarkjsOptions(p_paras.options); + if (p_paras.textIdx == p_paras.texts.length - 1) { + opt.done = p_paras.lastCallback; + } else { + let callbackFunc = function(markjs, paras) { + let _markjs = markjs; + let _paras = paras; + return function() { + _paras.textIdx += 1; + _markjs.findByOneRegExp(_paras); + }; + }; + opt.done = callbackFunc(this, p_paras); + } + + // TODO: may need transformation from QRegularExpression to RegExp. + this.markjs.markRegExp(new RegExp(p_paras.texts[p_paras.textIdx]), opt); } clearCache() { @@ -83,33 +127,62 @@ class MarkJs { this.markjs.unmark(); } - findInCache(p_text, p_options) { + findInCache(p_texts, p_options, p_currentMatchLine) { if (!this.cache) { return false; } - if (this.cache.text === p_text - && this.cache.options.caseSensitive == p_options.caseSensitive + if (p_texts.length != this.cache.texts.length) { + return false; + } + + for (let i = 0; i < p_texts.length; ++i) { + if (!(p_texts[i] === this.cache.texts[i])) { + return false; + } + } + + if (this.cache.options.caseSensitive == p_options.caseSensitive && this.cache.options.wholeWordOnly == p_options.wholeWordOnly && this.cache.options.regularExpression == p_options.regularExpression) { // Matched. Move current match forward or backward. - this.updateCurrentMatch(p_text, !p_options.findBackward); + this.updateCurrentMatch(p_texts, !p_options.findBackward, p_currentMatchLine); return true; } return false; } - updateCurrentMatch(p_text, p_forward) { + updateCurrentMatch(p_texts, p_forward, p_currentMatchLine) { let matches = this.matchedNodes.length; if (matches == 0) { - this.adapter.showFindResult(p_text, 0, 0); + this.adapter.showFindResult(p_texts, 0, 0); return; } - if (this.cache.currentIdx >= 0) { + + if (this.currentMatchedNodes.length > 0) { + console.assert(this.currentMatchedNodes.length == 1); + if (this.cache.currentIdx >= matches + || this.cache.currentIdx < 0 + || this.matchedNodes[this.cache.currentIdx] != this.currentMatchedNodes[0]) { + // Need to update current index. + // The mismatch may comes from the rendering of graphs which may change the matches. + for (let i = 0; i < matches; ++i) { + if (this.matchedNodes[i] === this.currentMatchedNodes[0]) { + this.cache.currentIdx = i; + break; + } + } + } + this.matchedNodes[this.cache.currentIdx].classList.remove(this.currentMatchClassName); + } else { + this.cache.currentIdx = -1; } - if (p_forward) { + + if (p_currentMatchLine > -1) { + this.cache.currentIdx = this.binarySearchCurrentIndexForLineNumber(p_currentMatchLine); + } else if (p_forward) { this.cache.currentIdx += 1; if (this.cache.currentIdx >= matches) { this.cache.currentIdx = 0; @@ -120,11 +193,39 @@ class MarkJs { this.cache.currentIdx = matches - 1; } } + let node = this.matchedNodes[this.cache.currentIdx]; node.classList.add(this.currentMatchClassName); if (!Utils.isVisible(node)) { node.scrollIntoView(); } - this.adapter.showFindResult(p_text, matches, this.cache.currentIdx); + this.adapter.showFindResult(p_texts, matches, this.cache.currentIdx); + } + + binarySearchCurrentIndexForLineNumber(p_lineNumber) { + let viewY = this.adapter.nodeLineMapper.getViewYOfLine(p_lineNumber); + if (viewY === null) { + return 0; + } + + let left = 0; + let right = this.matchedNodes.length - 1; + let lastIdx = -1; + while (left <= right) { + let mid = Math.floor((left + right) / 2); + let y = this.matchedNodes[mid].getBoundingClientRect().top; + if (y >= viewY) { + lastIdx = mid; + right = mid - 1; + } else { + left = mid + 1; + } + } + + if (lastIdx != -1) { + return lastIdx; + } else { + return 0; + } } } diff --git a/src/data/extra/web/js/nodelinemapper.js b/src/data/extra/web/js/nodelinemapper.js index fe9702a0..026a6bae 100644 --- a/src/data/extra/web/js/nodelinemapper.js +++ b/src/data/extra/web/js/nodelinemapper.js @@ -65,6 +65,22 @@ class NodeLineMapper { this.adapter.setHeadings(headings); } + getViewYOfLine(p_lineNumber) { + if (p_lineNumber == 0) { + return null; + } + + this.fetchAllNodesWithLineNumber(); + + // Binary search the last node with line number not larger than @p_lineNumber. + let targetNode = this.binarySearchNodeForLineNumber(this.nodesWithSourceLine, p_lineNumber); + if (targetNode) { + return targetNode.getBoundingClientRect().top; + } else { + return null; + } + } + scrollToLine(p_lineNumber) { if (p_lineNumber == 0) { this.scrollToY(0, false, true); diff --git a/src/data/extra/web/js/vnotex.js b/src/data/extra/web/js/vnotex.js index be6f391a..48db9e1d 100644 --- a/src/data/extra/web/js/vnotex.js +++ b/src/data/extra/web/js/vnotex.js @@ -280,12 +280,12 @@ class VNoteX extends EventEmitter { window.vxMarkdownAdapter.setCrossCopyResult(p_id, p_timeStamp, p_html); } - findText(p_text, p_options) { - this.searcher.findText(p_text, p_options); + findText(p_texts, p_options, p_currentMatchLine) { + this.searcher.findText(p_texts, p_options, p_currentMatchLine); } - showFindResult(p_text, p_totalMatches, p_currentMatchIndex) { - window.vxMarkdownAdapter.setFindText(p_text, p_totalMatches, p_currentMatchIndex); + showFindResult(p_texts, p_totalMatches, p_currentMatchIndex) { + window.vxMarkdownAdapter.setFindText(p_texts, p_totalMatches, p_currentMatchIndex); } saveContent() { diff --git a/src/search/searcher.cpp b/src/search/searcher.cpp index e5f22b3f..1b6becb7 100644 --- a/src/search/searcher.cpp +++ b/src/search/searcher.cpp @@ -513,3 +513,8 @@ void Searcher::createSearchEngine() m_engine.reset(new FileSearchEngine()); } + +const SearchToken &Searcher::getToken() const +{ + return m_token; +} diff --git a/src/search/searcher.h b/src/search/searcher.h index 4cc9e6ab..9f8d6b98 100644 --- a/src/search/searcher.h +++ b/src/search/searcher.h @@ -34,6 +34,8 @@ namespace vnotex SearchState search(const QSharedPointer &p_option, const QVector &p_notebooks); + const SearchToken &getToken() const; + signals: void progressUpdated(int p_val, int p_maximum); diff --git a/src/search/searchtoken.cpp b/src/search/searchtoken.cpp index 8d5133d8..eaed8b67 100644 --- a/src/search/searchtoken.cpp +++ b/src/search/searchtoken.cpp @@ -271,3 +271,21 @@ QString SearchToken::getHelpText() // Skip the first line containing the application name. return text.mid(text.indexOf('\n') + 1); } + +QPair SearchToken::toPatterns() const +{ + QPair ret; + + ret.second = m_caseSensitivity == Qt::CaseSensitive ? FindOption::CaseSensitive : FindOption::FindNone; + if (m_type == Type::RegularExpression) { + ret.second |= FindOption::RegularExpression; + + for (const auto ® : m_regularExpressions) { + ret.first << reg.pattern(); + } + } else { + ret.first = m_keywords; + } + + return ret; +} diff --git a/src/search/searchtoken.h b/src/search/searchtoken.h index a999df50..8d21e30c 100644 --- a/src/search/searchtoken.h +++ b/src/search/searchtoken.h @@ -54,6 +54,8 @@ namespace vnotex void endBatchMode(); + QPair toPatterns() const; + // Compile tokens from keyword. // Support some magic switchs in the keyword which will suppress the given options. static bool compile(const QString &p_keyword, FindOptions p_options, SearchToken &p_token); diff --git a/src/utils/widgetutils.cpp b/src/utils/widgetutils.cpp index 4597b726..4ef5ab56 100644 --- a/src/utils/widgetutils.cpp +++ b/src/utils/widgetutils.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include @@ -198,8 +199,7 @@ void WidgetUtils::addActionShortcut(QAction *p_action, p_action->setText(QString("%1\t%2").arg(p_action->text(), kseq.toString(QKeySequence::NativeText))); } -void WidgetUtils::addActionShortcutText(QAction *p_action, - const QString &p_shortcut) +void WidgetUtils::addActionShortcutText(QAction *p_action, const QString &p_shortcut) { if (p_shortcut.isEmpty()) { return; @@ -213,6 +213,20 @@ void WidgetUtils::addActionShortcutText(QAction *p_action, p_action->setText(QString("%1\t%2").arg(p_action->text(), kseq.toString(QKeySequence::NativeText))); } +void WidgetUtils::addButtonShortcutText(QPushButton *p_button, const QString &p_shortcut) +{ + if (p_shortcut.isEmpty()) { + return; + } + + QKeySequence kseq(p_shortcut); + if (kseq.isEmpty()) { + return; + } + + p_button->setText(QString("%1 (%2)").arg(p_button->text(), kseq.toString(QKeySequence::NativeText))); +} + void WidgetUtils::updateSize(QWidget *p_widget) { p_widget->adjustSize(); diff --git a/src/utils/widgetutils.h b/src/utils/widgetutils.h index 9163e833..9329e08a 100644 --- a/src/utils/widgetutils.h +++ b/src/utils/widgetutils.h @@ -19,6 +19,7 @@ class QMenu; class QShortcut; class QLineEdit; class QLayout; +class QPushButton; namespace vnotex { @@ -60,8 +61,9 @@ namespace vnotex Qt::ShortcutContext p_context = Qt::WindowShortcut); // Just add a shortcut text hint to the action. - static void addActionShortcutText(QAction *p_action, - const QString &p_shortcut); + static void addActionShortcutText(QAction *p_action, const QString &p_shortcut); + + static void addButtonShortcutText(QPushButton *p_button, const QString &p_shortcut); static QShortcut *createShortcut(const QString &p_shortcut, QWidget *p_widget, diff --git a/src/widgets/dialogs/exportdialog.cpp b/src/widgets/dialogs/exportdialog.cpp index d53e53c4..45d374d1 100644 --- a/src/widgets/dialogs/exportdialog.cpp +++ b/src/widgets/dialogs/exportdialog.cpp @@ -781,12 +781,13 @@ QWidget *ExportDialog::getCustomAdvancedSettings() } { - auto usage = tr("%1: List of input files.\n" - "%2: List of paths to search for images and other resources.\n" - "%3: Path of rendering CSS style sheet.\n" - "%4: Path of syntax highlighting CSS style sheet.\n" - "%5: Path of output file.\n"); - layout->addRow(tr("Command usage:"), new QLabel(usage, widget)); + auto usage = tr("Command:\n" + "\t%1: List of input files.\n" + "\t%2: List of paths to search for images and other resources.\n" + "\t%3: Path of rendering CSS style sheet.\n" + "\t%4: Path of syntax highlighting CSS style sheet.\n" + "\t%5: Path of output file.\n"); + layout->addRow(new QLabel(usage, widget)); } { @@ -798,7 +799,7 @@ QWidget *ExportDialog::getCustomAdvancedSettings() #endif m_commandTextEdit->setMaximumHeight(m_commandTextEdit->minimumSizeHint().height()); m_commandTextEdit->setEnabled(false); - layout->addRow(tr("Command:"), m_commandTextEdit); + layout->addRow(m_commandTextEdit); } connect(m_customExportComboBox, QOverload::of(&QComboBox::currentIndexChanged), diff --git a/src/widgets/editors/markdownvieweradapter.cpp b/src/widgets/editors/markdownvieweradapter.cpp index 0fc5cf2c..f17c8fbc 100644 --- a/src/widgets/editors/markdownvieweradapter.cpp +++ b/src/widgets/editors/markdownvieweradapter.cpp @@ -6,17 +6,10 @@ #include "../outlineprovider.h" #include "plantumlhelper.h" #include "graphvizhelper.h" +#include using namespace vnotex; -MarkdownViewerAdapter::MarkdownData::MarkdownData(const QString &p_text, - int p_lineNumber, - const QString &p_anchor) - : m_text(p_text), - m_position(p_lineNumber, p_anchor) -{ -} - MarkdownViewerAdapter::Position::Position(int p_lineNumber, const QString &p_anchor) : m_lineNumber(p_lineNumber), m_anchor(p_anchor) @@ -92,7 +85,10 @@ void MarkdownViewerAdapter::setText(int p_revision, emit textUpdated(p_text); scrollToPosition(Position(p_lineNumber, "")); } else { - m_pendingData.reset(new MarkdownData(p_text, p_lineNumber, "")); + m_pendingActions.append([this, p_text, p_lineNumber]() { + emit textUpdated(p_text); + scrollToPosition(Position(p_lineNumber, "")); + }); } } @@ -102,7 +98,9 @@ void MarkdownViewerAdapter::setText(const QString &p_text) if (m_viewerReady) { emit textUpdated(p_text); } else { - m_pendingData.reset(new MarkdownData(p_text, -1, "")); + m_pendingActions.append([this, p_text]() { + emit textUpdated(p_text); + }); } } @@ -114,15 +112,12 @@ void MarkdownViewerAdapter::setReady(bool p_ready) m_viewerReady = p_ready; if (m_viewerReady) { - if (m_pendingData) { - emit textUpdated(m_pendingData->m_text); - scrollToPosition(m_pendingData->m_position); - m_pendingData.reset(); + for (auto &act : m_pendingActions) { + act(); } - + m_pendingActions.clear(); emit viewerReady(); } - } void MarkdownViewerAdapter::scrollToLine(int p_lineNumber) @@ -132,11 +127,9 @@ void MarkdownViewerAdapter::scrollToLine(int p_lineNumber) } if (!m_viewerReady) { - if (m_pendingData) { - m_pendingData->m_position = Position(p_lineNumber, QString()); - } else { - qWarning() << "Markdown viewer is not ready"; - } + m_pendingActions.append([this, p_lineNumber]() { + scrollToPosition(Position(p_lineNumber, "")); + }); return; } @@ -321,7 +314,7 @@ void MarkdownViewerAdapter::setCrossCopyResult(quint64 p_id, quint64 p_timeStamp emit crossCopyReady(p_id, p_timeStamp, p_html); } -void MarkdownViewerAdapter::findText(const QString &p_text, FindOptions p_options) +void MarkdownViewerAdapter::findText(const QStringList &p_texts, FindOptions p_options, int p_currentMatchLine) { FindOption opts; if (p_options & vnotex::FindOption::FindBackward) { @@ -337,12 +330,20 @@ void MarkdownViewerAdapter::findText(const QString &p_text, FindOptions p_option opts.m_regularExpression = true; } - emit findTextRequested(p_text, opts.toJson()); + if (m_viewerReady) { + emit findTextRequested(p_texts, opts.toJson(), p_currentMatchLine); + } else { + m_pendingActions.append([this, p_texts, opts, p_currentMatchLine]() { + // FIXME: highlights will be clear once the page is ready. Add a delay here. + Utils::sleepWait(1000); + emit findTextRequested(p_texts, opts.toJson(), p_currentMatchLine); + }); + } } -void MarkdownViewerAdapter::setFindText(const QString &p_text, int p_totalMatches, int p_currentMatchIndex) +void MarkdownViewerAdapter::setFindText(const QStringList &p_texts, int p_totalMatches, int p_currentMatchIndex) { - emit findTextReady(p_text, p_totalMatches, p_currentMatchIndex); + emit findTextReady(p_texts, p_totalMatches, p_currentMatchIndex); } void MarkdownViewerAdapter::setWorkFinished() @@ -367,7 +368,7 @@ void MarkdownViewerAdapter::reset() { m_revision = 0; m_viewerReady = false; - m_pendingData.reset(); + m_pendingActions.clear(); m_topLineNumber = -1; m_headings.clear(); m_currentHeadingIndex = -1; diff --git a/src/widgets/editors/markdownvieweradapter.h b/src/widgets/editors/markdownvieweradapter.h index 7007f66e..23461513 100644 --- a/src/widgets/editors/markdownvieweradapter.h +++ b/src/widgets/editors/markdownvieweradapter.h @@ -29,19 +29,6 @@ namespace vnotex QString m_anchor; }; - struct MarkdownData - { - MarkdownData() = default; - - MarkdownData(const QString &p_text, - int p_lineNumber, - const QString &p_anchor); - - QString m_text; - - Position m_position; - }; - struct PreviewData { PreviewData() = default; @@ -119,7 +106,7 @@ namespace vnotex QString getCrossCopyTargetDisplayName(const QString &p_target) const; - void findText(const QString &p_text, FindOptions p_options); + void findText(const QStringList &p_texts, FindOptions p_options, int p_currentMatchLine = -1); void saveContent(); @@ -168,7 +155,7 @@ namespace vnotex void setCrossCopyResult(quint64 p_id, quint64 p_timeStamp, const QString &p_html); - void setFindText(const QString &p_text, int p_totalMatches, int p_currentMatchIndex); + void setFindText(const QStringList &p_texts, int p_totalMatches, int p_currentMatchIndex); void setSavedContent(const QString &p_headContent, const QString &p_styleContent, const QString &p_content, const QString &p_bodyClassList); @@ -210,7 +197,7 @@ namespace vnotex const QString &p_baseUrl, const QString &p_html); - void findTextRequested(const QString &p_text, const QJsonObject &p_options); + void findTextRequested(const QStringList &p_texts, const QJsonObject &p_options, int p_currentMatchLine); // Request to get the whole HTML content. void contentRequested(); @@ -243,7 +230,7 @@ namespace vnotex void crossCopyReady(quint64 p_id, quint64 p_timeStamp, const QString &p_html); - void findTextReady(const QString &p_text, int p_totalMatches, int p_currentMatchIndex); + void findTextReady(const QStringList &p_texts, int p_totalMatches, int p_currentMatchIndex); void contentReady(const QString &p_headContent, const QString &p_styleContent, @@ -260,8 +247,8 @@ namespace vnotex // Whether web side viewer is ready to handle text update. bool m_viewerReady = false; - // Pending Markdown data for the viewer once it is ready. - QScopedPointer m_pendingData; + // Pending actions for the viewer once it is ready. + QVector> m_pendingActions; // Source line number of the top element node at web side. int m_topLineNumber = -1; diff --git a/src/widgets/findandreplacewidget.cpp b/src/widgets/findandreplacewidget.cpp index 616d43d5..f17ee93a 100644 --- a/src/widgets/findandreplacewidget.cpp +++ b/src/widgets/findandreplacewidget.cpp @@ -14,11 +14,13 @@ #include "lineedit.h" #include "widgetsfactory.h" #include +#include #include #include #include "propertydefs.h" -#include "configmgr.h" -#include "widgetconfig.h" +#include +#include +#include using namespace vnotex; @@ -80,12 +82,16 @@ void FindAndReplaceWidget::setupUI() setFocusProxy(m_findLineEdit); + const auto &editorConfig = ConfigMgr::getInst().getEditorConfig(); + auto findNextBtn = new QPushButton(tr("Find &Next"), this); + WidgetUtils::addButtonShortcutText(findNextBtn, editorConfig.getShortcut(EditorConfig::FindNext)); findNextBtn->setDefault(true); connect(findNextBtn, &QPushButton::clicked, this, &FindAndReplaceWidget::findNext); auto findPrevBtn = new QPushButton(tr("Find &Previous"), this); + WidgetUtils::addButtonShortcutText(findPrevBtn, editorConfig.getShortcut(EditorConfig::FindPrevious)); connect(findPrevBtn, &QPushButton::clicked, this, &FindAndReplaceWidget::findPrevious); diff --git a/src/widgets/historypanel.cpp b/src/widgets/historypanel.cpp index 27e12b8c..cb48abd8 100644 --- a/src/widgets/historypanel.cpp +++ b/src/widgets/historypanel.cpp @@ -159,7 +159,7 @@ void HistoryPanel::updateHistoryList() if (itemIdx >= 0) { // Older. - auto sepItem = ListWidget::createSeparatorItem(tr(">>> Older")); + auto sepItem = ListWidget::createSeparatorItem(tr("Older")); m_historyList->addItem(sepItem); for (; itemIdx >= 0; --itemIdx) { @@ -191,11 +191,11 @@ void HistoryPanel::updateSeparators() auto curDateTime = QDateTime::currentDateTime(); curDateTime.setTime(QTime()); - m_separators[0].m_text = tr(">>> Today"); + m_separators[0].m_text = tr("Today"); m_separators[0].m_dateUtc = curDateTime.toUTC(); - m_separators[1].m_text = tr(">>> Yesterday"); + m_separators[1].m_text = tr("Yesterday"); m_separators[1].m_dateUtc = curDateTime.addDays(-1).toUTC(); - m_separators[2].m_text = tr(">>> Last 7 Days"); + m_separators[2].m_text = tr("Last 7 Days"); m_separators[2].m_dateUtc = curDateTime.addDays(-7).toUTC(); } diff --git a/src/widgets/markdownviewwindow.cpp b/src/widgets/markdownviewwindow.cpp index 0c4d85d1..aeb7fc46 100644 --- a/src/widgets/markdownviewwindow.cpp +++ b/src/widgets/markdownviewwindow.cpp @@ -457,8 +457,8 @@ void MarkdownViewWindow::setupViewer() } }); connect(adapter, &MarkdownViewerAdapter::findTextReady, - this, [this](const QString &p_text, int p_totalMatches, int p_currentMatchIndex) { - this->showFindResult(p_text, p_totalMatches, p_currentMatchIndex); + this, [this](const QStringList &p_texts, int p_totalMatches, int p_currentMatchIndex) { + this->showFindResult(p_texts, p_totalMatches, p_currentMatchIndex); }); } @@ -944,19 +944,19 @@ void MarkdownViewWindow::handleFindTextChanged(const QString &p_text, FindOption { if (isReadMode()) { if (p_options & FindOption::IncrementalSearch) { - adapter()->findText(p_text, p_options); + adapter()->findText(QStringList(p_text), p_options); } } else { TextViewWindowHelper::handleFindTextChanged(this, p_text, p_options); } } -void MarkdownViewWindow::handleFindNext(const QString &p_text, FindOptions p_options) +void MarkdownViewWindow::handleFindNext(const QStringList &p_texts, FindOptions p_options) { if (isReadMode()) { - adapter()->findText(p_text, p_options); + adapter()->findText(p_texts, p_options); } else { - TextViewWindowHelper::handleFindNext(this, p_text, p_options); + TextViewWindowHelper::handleFindNext(this, p_texts, p_options); } } @@ -983,7 +983,7 @@ void MarkdownViewWindow::handleFindAndReplaceWidgetClosed() if (m_editor) { TextViewWindowHelper::handleFindAndReplaceWidgetClosed(this); } else { - adapter()->findText("", FindOption::FindNone); + adapter()->findText(QStringList(), FindOption::FindNone); } } @@ -1013,6 +1013,10 @@ void MarkdownViewWindow::handleFileOpenParameters(const QSharedPointerm_lineNumber); + + if (p_paras->m_searchToken) { + findTextBySearchToken(p_paras->m_searchToken, p_paras->m_lineNumber); + } } } @@ -1031,6 +1035,19 @@ void MarkdownViewWindow::scrollToLine(int p_lineNumber) } } +void MarkdownViewWindow::findTextBySearchToken(const QSharedPointer &p_token, int p_currentMatchLine) +{ + if (isReadMode()) { + Q_ASSERT(m_viewer); + const auto patterns = p_token->toPatterns(); + updateLastFindInfo(patterns.first, patterns.second); + adapter()->findText(patterns.first, patterns.second, p_currentMatchLine); + } else { + Q_ASSERT(m_editor); + TextViewWindowHelper::findTextBySearchToken(this, p_token, p_currentMatchLine); + } +} + bool MarkdownViewWindow::isReadMode() const { return m_mode == ViewWindowMode::Read; diff --git a/src/widgets/markdownviewwindow.h b/src/widgets/markdownviewwindow.h index 37b6486a..96dc99ff 100644 --- a/src/widgets/markdownviewwindow.h +++ b/src/widgets/markdownviewwindow.h @@ -24,6 +24,7 @@ namespace vnotex class MarkdownEditorConfig; class EditorConfig; class ImageHost; + class SearchToken; class MarkdownViewWindow : public ViewWindow { @@ -65,7 +66,7 @@ namespace vnotex void handleFindTextChanged(const QString &p_text, FindOptions p_options) Q_DECL_OVERRIDE; - void handleFindNext(const QString &p_text, FindOptions p_options) Q_DECL_OVERRIDE; + void handleFindNext(const QStringList &p_texts, FindOptions p_options) Q_DECL_OVERRIDE; void handleReplace(const QString &p_text, FindOptions p_options, const QString &p_replaceText) Q_DECL_OVERRIDE; @@ -146,6 +147,8 @@ namespace vnotex void scrollToLine(int p_lineNumber); + void findTextBySearchToken(const QSharedPointer &p_token, int p_currentMatchLine); + bool isReadMode() const; void updatePreviewHelperFromConfig(const MarkdownEditorConfig &p_config); diff --git a/src/widgets/searchpanel.cpp b/src/widgets/searchpanel.cpp index dadc613d..66850556 100644 --- a/src/widgets/searchpanel.cpp +++ b/src/widgets/searchpanel.cpp @@ -554,6 +554,9 @@ void SearchPanel::prepareLocationList() } m_locationList->clear(); + + m_searchTokenOfSession.clear(); + m_locationList->startSession([this](const Location &p_location) { handleLocationActivated(p_location); }); @@ -561,9 +564,15 @@ void SearchPanel::prepareLocationList() void SearchPanel::handleLocationActivated(const Location &p_location) { - qDebug() << "location activated" << p_location; + Q_ASSERT(m_searcher); + + if (!m_searchTokenOfSession) { + m_searchTokenOfSession = QSharedPointer::create(m_searcher->getToken()); + } + // TODO: decode the path of location and handle different types of destination. auto paras = QSharedPointer::create(); paras->m_lineNumber = p_location.m_lineNumber; + paras->m_searchToken = m_searchTokenOfSession; emit VNoteX::getInst().openFileRequested(p_location.m_path, paras); } diff --git a/src/widgets/searchpanel.h b/src/widgets/searchpanel.h index 5be06f73..dec580ef 100644 --- a/src/widgets/searchpanel.h +++ b/src/widgets/searchpanel.h @@ -26,6 +26,7 @@ namespace vnotex class Notebook; class LocationList; struct Location; + class SearchToken; class ISearchInfoProvider { @@ -145,6 +146,8 @@ namespace vnotex Searcher *m_searcher = nullptr; LocationList *m_locationList = nullptr; + + QSharedPointer m_searchTokenOfSession; }; } diff --git a/src/widgets/textviewwindow.cpp b/src/widgets/textviewwindow.cpp index 13543d24..84bcefff 100644 --- a/src/widgets/textviewwindow.cpp +++ b/src/widgets/textviewwindow.cpp @@ -218,9 +218,9 @@ void TextViewWindow::handleFindTextChanged(const QString &p_text, FindOptions p_ TextViewWindowHelper::handleFindTextChanged(this, p_text, p_options); } -void TextViewWindow::handleFindNext(const QString &p_text, FindOptions p_options) +void TextViewWindow::handleFindNext(const QStringList &p_texts, FindOptions p_options) { - TextViewWindowHelper::handleFindNext(this, p_text, p_options); + TextViewWindowHelper::handleFindNext(this, p_texts, p_options); } void TextViewWindow::handleReplace(const QString &p_text, FindOptions p_options, const QString &p_replaceText) @@ -261,6 +261,10 @@ void TextViewWindow::handleFileOpenParameters(const QSharedPointerm_lineNumber > -1) { m_editor->scrollToLine(p_paras->m_lineNumber, true); } + + if (p_paras->m_searchToken) { + TextViewWindowHelper::findTextBySearchToken(this, p_paras->m_searchToken, p_paras->m_lineNumber); + } } ViewWindowSession TextViewWindow::saveSession() const diff --git a/src/widgets/textviewwindow.h b/src/widgets/textviewwindow.h index 4c597704..dc2efb43 100644 --- a/src/widgets/textviewwindow.h +++ b/src/widgets/textviewwindow.h @@ -45,7 +45,7 @@ namespace vnotex void handleFindTextChanged(const QString &p_text, FindOptions p_options) Q_DECL_OVERRIDE; - void handleFindNext(const QString &p_text, FindOptions p_options) Q_DECL_OVERRIDE; + void handleFindNext(const QStringList &p_texts, FindOptions p_options) Q_DECL_OVERRIDE; void handleReplace(const QString &p_text, FindOptions p_options, const QString &p_replaceText) Q_DECL_OVERRIDE; diff --git a/src/widgets/textviewwindowhelper.h b/src/widgets/textviewwindowhelper.h index cc3d9c58..7e841e28 100644 --- a/src/widgets/textviewwindowhelper.h +++ b/src/widgets/textviewwindowhelper.h @@ -5,12 +5,14 @@ #include #include #include +#include #include #include #include #include #include +#include #include "quickselector.h" @@ -169,10 +171,10 @@ namespace vnotex } template - static void handleFindNext(_ViewWindow *p_win, const QString &p_text, FindOptions p_options) + static void handleFindNext(_ViewWindow *p_win, const QStringList &p_texts, FindOptions p_options) { - const auto result = p_win->m_editor->findText(p_text, toEditorFindFlags(p_options)); - p_win->showFindResult(p_text, result.m_totalMatches, result.m_currentMatchIndex); + const auto result = p_win->m_editor->findText(p_texts, toEditorFindFlags(p_options)); + p_win->showFindResult(p_texts, result.m_totalMatches, result.m_currentMatchIndex); } template @@ -293,6 +295,15 @@ namespace vnotex } return textEdit->mapToGlobal(localPos); } + + template + static void findTextBySearchToken(_ViewWindow *p_win, const QSharedPointer &p_token, int p_currentMatchLine) + { + const auto patterns = p_token->toPatterns(); + p_win->updateLastFindInfo(patterns.first, patterns.second); + const auto result = p_win->m_editor->findText(patterns.first, toEditorFindFlags(patterns.second), 0, -1, p_currentMatchLine); + p_win->showFindResult(patterns.first, result.m_totalMatches, result.m_currentMatchIndex); + } }; } diff --git a/src/widgets/viewwindow.cpp b/src/widgets/viewwindow.cpp index 897e326a..ac94d94d 100644 --- a/src/widgets/viewwindow.cpp +++ b/src/widgets/viewwindow.cpp @@ -1007,9 +1007,9 @@ void ViewWindow::handleFindTextChanged(const QString &p_text, FindOptions p_opti Q_UNUSED(p_options); } -void ViewWindow::handleFindNext(const QString &p_text, FindOptions p_options) +void ViewWindow::handleFindNext(const QStringList &p_texts, FindOptions p_options) { - Q_UNUSED(p_text); + Q_UNUSED(p_texts); Q_UNUSED(p_options); } @@ -1039,46 +1039,49 @@ void ViewWindow::findNextOnLastFind(bool p_forward) { // Check if need to update the find info. if (m_findAndReplace && m_findAndReplace->isVisible()) { - m_findInfo.m_text = m_findAndReplace->getFindText(); + m_findInfo.m_texts = QStringList(m_findAndReplace->getFindText()); m_findInfo.m_options = m_findAndReplace->getOptions(); } - if (m_findInfo.m_text.isEmpty()) { + if (m_findInfo.m_texts.isEmpty()) { return; } if (p_forward) { - handleFindNext(m_findInfo.m_text, m_findInfo.m_options & ~FindOption::FindBackward); + handleFindNext(m_findInfo.m_texts, m_findInfo.m_options & ~FindOption::FindBackward); } else { - handleFindNext(m_findInfo.m_text, m_findInfo.m_options | FindOption::FindBackward); + handleFindNext(m_findInfo.m_texts, m_findInfo.m_options | FindOption::FindBackward); } } void ViewWindow::findNext(const QString &p_text, FindOptions p_options) { - m_findInfo.m_text = p_text; + const QStringList texts(p_text); + + m_findInfo.m_texts = texts; m_findInfo.m_options = p_options; - handleFindNext(p_text, p_options); + handleFindNext(texts, p_options); } void ViewWindow::replace(const QString &p_text, FindOptions p_options, const QString &p_replaceText) { - m_findInfo.m_text = p_text; + m_findInfo.m_texts = QStringList(p_text); m_findInfo.m_options = p_options; handleReplace(p_text, p_options, p_replaceText); } void ViewWindow::replaceAll(const QString &p_text, FindOptions p_options, const QString &p_replaceText) { - m_findInfo.m_text = p_text; + m_findInfo.m_texts = QStringList(p_text); m_findInfo.m_options = p_options; handleReplaceAll(p_text, p_options, p_replaceText); } -void ViewWindow::showFindResult(const QString &p_text, int p_totalMatches, int p_currentMatchIndex) +void ViewWindow::showFindResult(const QStringList &p_texts, int p_totalMatches, int p_currentMatchIndex) { if (p_totalMatches == 0) { - showMessage(tr("Pattern not found: %1").arg(p_text)); + showMessage(tr("Pattern not found: %1%2").arg(p_texts.isEmpty() ? QString() : p_texts[0], + p_texts.size() > 1 ? tr(" [+]"): QString())); } else { showMessage(tr("Match found: %1/%2").arg(p_currentMatchIndex + 1).arg(p_totalMatches)); } @@ -1215,3 +1218,9 @@ void ViewWindow::updateImageHostMenu() handleImageHostChanged(curHost ? curHost->getName() : nullptr); } + +void ViewWindow::updateLastFindInfo(const QStringList &p_texts, FindOptions p_options) +{ + m_findInfo.m_texts = p_texts; + m_findInfo.m_options = p_options; +} diff --git a/src/widgets/viewwindow.h b/src/widgets/viewwindow.h index 269698de..7620cca6 100644 --- a/src/widgets/viewwindow.h +++ b/src/widgets/viewwindow.h @@ -164,7 +164,7 @@ namespace vnotex virtual void handleFindTextChanged(const QString &p_text, FindOptions p_options); - virtual void handleFindNext(const QString &p_text, FindOptions p_options); + virtual void handleFindNext(const QStringList &p_texts, FindOptions p_options); virtual void handleReplace(const QString &p_text, FindOptions p_options, const QString &p_replaceText); @@ -229,7 +229,7 @@ namespace vnotex bool findAndReplaceWidgetVisible() const; // @p_currentMatchIndex: 0-based. - void showFindResult(const QString &p_text, int p_totalMatches, int p_currentMatchIndex); + void showFindResult(const QStringList &p_texts, int p_totalMatches, int p_currentMatchIndex); void showReplaceResult(const QString &p_text, int p_totalReplaces); @@ -240,6 +240,8 @@ namespace vnotex // Show message in status widget if exists. Otherwise, show it in the mainwindow's status widget. void showMessage(const QString p_msg); + void updateLastFindInfo(const QStringList &p_texts, FindOptions p_options); + virtual QPoint getFloatingWidgetPosition(); static QToolBar *createToolBar(QWidget *p_parent = nullptr); @@ -259,7 +261,7 @@ namespace vnotex private: struct FindInfo { - QString m_text; + QStringList m_texts; FindOptions m_options; };