Search: highlight matched items in opened files

This commit is contained in:
Le Tan 2021-08-26 20:33:49 +08:00
parent 8326d3c702
commit ed8cd503b5
27 changed files with 343 additions and 124 deletions

@ -1 +1 @@
Subproject commit 392c0e218f5981b4fc6566512103b6149f714931
Subproject commit 59e53f2cdc2f5a4b9dc44cbf28c483cca07559ba

View File

@ -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";

View File

@ -1,11 +1,14 @@
#ifndef FILEOPENPARAMETERS_H
#define FILEOPENPARAMETERS_H
#include <QSharedPointer>
#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<SearchToken> m_searchToken;
};
}

View File

@ -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() {

View File

@ -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;
}
}
}

View File

@ -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);

View File

@ -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() {

View File

@ -513,3 +513,8 @@ void Searcher::createSearchEngine()
m_engine.reset(new FileSearchEngine());
}
const SearchToken &Searcher::getToken() const
{
return m_token;
}

View File

@ -34,6 +34,8 @@ namespace vnotex
SearchState search(const QSharedPointer<SearchOption> &p_option, const QVector<Notebook *> &p_notebooks);
const SearchToken &getToken() const;
signals:
void progressUpdated(int p_val, int p_maximum);

View File

@ -271,3 +271,21 @@ QString SearchToken::getHelpText()
// Skip the first line containing the application name.
return text.mid(text.indexOf('\n') + 1);
}
QPair<QStringList, FindOptions> SearchToken::toPatterns() const
{
QPair<QStringList, FindOptions> ret;
ret.second = m_caseSensitivity == Qt::CaseSensitive ? FindOption::CaseSensitive : FindOption::FindNone;
if (m_type == Type::RegularExpression) {
ret.second |= FindOption::RegularExpression;
for (const auto &reg : m_regularExpressions) {
ret.first << reg.pattern();
}
} else {
ret.first = m_keywords;
}
return ret;
}

View File

@ -54,6 +54,8 @@ namespace vnotex
void endBatchMode();
QPair<QStringList, FindOptions> 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);

View File

@ -23,6 +23,7 @@
#include <QDebug>
#include <QLineEdit>
#include <QLayout>
#include <QPushButton>
#include <core/global.h>
@ -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();

View File

@ -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,

View File

@ -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<int>::of(&QComboBox::currentIndexChanged),

View File

@ -6,17 +6,10 @@
#include "../outlineprovider.h"
#include "plantumlhelper.h"
#include "graphvizhelper.h"
#include <utils/utils.h>
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;

View File

@ -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<MarkdownData> m_pendingData;
// Pending actions for the viewer once it is ready.
QVector<std::function<void()>> m_pendingActions;
// Source line number of the top element node at web side.
int m_topLineNumber = -1;

View File

@ -14,11 +14,13 @@
#include "lineedit.h"
#include "widgetsfactory.h"
#include <utils/iconutils.h>
#include <utils/widgetutils.h>
#include <core/thememgr.h>
#include <core/vnotex.h>
#include "propertydefs.h"
#include "configmgr.h"
#include "widgetconfig.h"
#include <core/configmgr.h>
#include <core/editorconfig.h>
#include <widgetconfig.h>
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);

View File

@ -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();
}

View File

@ -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 QSharedPointer<FileOpenP
}
scrollToLine(p_paras->m_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<SearchToken> &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;

View File

@ -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<SearchToken> &p_token, int p_currentMatchLine);
bool isReadMode() const;
void updatePreviewHelperFromConfig(const MarkdownEditorConfig &p_config);

View File

@ -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<SearchToken>::create(m_searcher->getToken());
}
// TODO: decode the path of location and handle different types of destination.
auto paras = QSharedPointer<FileOpenParameters>::create();
paras->m_lineNumber = p_location.m_lineNumber;
paras->m_searchToken = m_searchTokenOfSession;
emit VNoteX::getInst().openFileRequested(p_location.m_path, paras);
}

View File

@ -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<SearchToken> m_searchTokenOfSession;
};
}

View File

@ -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 QSharedPointer<FileOpenParam
if (p_paras->m_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

View File

@ -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;

View File

@ -5,12 +5,14 @@
#include <QTextCursor>
#include <QRegularExpression>
#include <QTextBlock>
#include <QSharedPointer>
#include <vtextedit/texteditorconfig.h>
#include <core/texteditorconfig.h>
#include <core/configmgr.h>
#include <utils/widgetutils.h>
#include <snippet/snippetmgr.h>
#include <search/searchtoken.h>
#include "quickselector.h"
@ -169,10 +171,10 @@ namespace vnotex
}
template <typename _ViewWindow>
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 <typename _ViewWindow>
@ -293,6 +295,15 @@ namespace vnotex
}
return textEdit->mapToGlobal(localPos);
}
template <typename _ViewWindow>
static void findTextBySearchToken(_ViewWindow *p_win, const QSharedPointer<SearchToken> &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);
}
};
}

View File

@ -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;
}

View File

@ -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;
};