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; using namespace vnotex;
#ifndef QT_NO_DEBUG #ifndef QT_NO_DEBUG
// #define VX_DEBUG_WEB #define VX_DEBUG_WEB
#endif #endif
const QString ConfigMgr::c_orgName = "VNote"; const QString ConfigMgr::c_orgName = "VNote";

View File

@ -1,11 +1,14 @@
#ifndef FILEOPENPARAMETERS_H #ifndef FILEOPENPARAMETERS_H
#define FILEOPENPARAMETERS_H #define FILEOPENPARAMETERS_H
#include <QSharedPointer>
#include "global.h" #include "global.h"
namespace vnotex namespace vnotex
{ {
class Node; class Node;
class SearchToken;
struct FileOpenParameters struct FileOpenParameters
{ {
@ -32,6 +35,9 @@ namespace vnotex
// Whether always open a new window for file. // Whether always open a new window for file.
bool m_alwaysNewWindow = false; 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); window.vnotex.crossCopy(p_id, p_timeStamp, p_target, p_baseUrl, p_html);
}); });
adapter.findTextRequested.connect(function(p_text, p_options) { adapter.findTextRequested.connect(function(p_texts, p_options, p_currentMatchLine) {
window.vnotex.findText(p_text, p_options); window.vnotex.findText(p_texts, p_options, p_currentMatchLine);
}); });
adapter.contentRequested.connect(function() { adapter.contentRequested.connect(function() {

View File

@ -7,6 +7,7 @@ class MarkJs {
this.markjs = null; this.markjs = null;
this.cache = null; this.cache = null;
this.matchedNodes = null; this.matchedNodes = null;
this.currentMatchedNodes = null;
this.adapter.on('basicMarkdownRendered', () => { this.adapter.on('basicMarkdownRendered', () => {
this.clearCache(); this.clearCache();
@ -19,59 +20,102 @@ class MarkJs {
// wholeWordOnly, // wholeWordOnly,
// regularExpression // regularExpression
// } // }
findText(p_text, p_options) { findText(p_texts, p_options, p_currentMatchLine) {
if (!this.markjs) { if (!this.markjs) {
this.markjs = new Mark(this.container); this.markjs = new Mark(this.container);
} }
if (!p_text) { if (!p_texts || p_texts.length == 0) {
// Clear the cache and highlight. // Clear the cache and highlight.
this.clearCache(); this.clearCache();
return; return;
} }
if (this.findInCache(p_text, p_options)) { if (this.findInCache(p_texts, p_options, p_currentMatchLine)) {
return; return;
} }
// A new find. // A new find.
this.clearCache(); this.clearCache();
let callbackFunc = function(markjs, text, options) { let callbackFunc = function(markjs, texts, options, currentMatchLine) {
let _markjs = markjs; let _markjs = markjs;
let _text = text; let _texts = texts;
let _options = options; let _options = options;
return function(totalMatches) { let _currentMatchLine = currentMatchLine;
if (!_markjs.matchedNodes) { return function() {
if (_markjs.matchedNodes === null) {
_markjs.matchedNodes = _markjs.container.getElementsByClassName(_markjs.className); _markjs.matchedNodes = _markjs.container.getElementsByClassName(_markjs.className);
_markjs.currentMatchedNodes = _markjs.container.getElementsByClassName(_markjs.currentMatchClassName);
} }
// Update cache. // Update cache.
_markjs.cache = { _markjs.cache = {
text: _text, texts: _texts,
options: _options, options: _options,
currentIdx: -1 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 = { let opt = {
'element': 'span', 'element': 'span',
'className': this.className, 'className': this.className,
'caseSensitive': p_options.caseSensitive, 'caseSensitive': p_options.caseSensitive,
'accuracy': p_options.wholeWordOnly ? 'exactly' : 'partially', 'accuracy': p_options.wholeWordOnly ? 'exactly' : 'partially',
'done': callbackFunc(this, p_text, p_options),
// Ignore SVG, or SVG will be corrupted. // Ignore SVG, or SVG will be corrupted.
'exclude': ['svg *'] '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() { clearCache() {
@ -83,33 +127,62 @@ class MarkJs {
this.markjs.unmark(); this.markjs.unmark();
} }
findInCache(p_text, p_options) { findInCache(p_texts, p_options, p_currentMatchLine) {
if (!this.cache) { if (!this.cache) {
return false; return false;
} }
if (this.cache.text === p_text if (p_texts.length != this.cache.texts.length) {
&& this.cache.options.caseSensitive == p_options.caseSensitive 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.wholeWordOnly == p_options.wholeWordOnly
&& this.cache.options.regularExpression == p_options.regularExpression) { && this.cache.options.regularExpression == p_options.regularExpression) {
// Matched. Move current match forward or backward. // 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 true;
} }
return false; return false;
} }
updateCurrentMatch(p_text, p_forward) { updateCurrentMatch(p_texts, p_forward, p_currentMatchLine) {
let matches = this.matchedNodes.length; let matches = this.matchedNodes.length;
if (matches == 0) { if (matches == 0) {
this.adapter.showFindResult(p_text, 0, 0); this.adapter.showFindResult(p_texts, 0, 0);
return; return;
} }
if (this.cache.currentIdx >= 0) {
this.matchedNodes[this.cache.currentIdx].classList.remove(this.currentMatchClassName); 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;
} }
if (p_forward) { }
}
this.matchedNodes[this.cache.currentIdx].classList.remove(this.currentMatchClassName);
} else {
this.cache.currentIdx = -1;
}
if (p_currentMatchLine > -1) {
this.cache.currentIdx = this.binarySearchCurrentIndexForLineNumber(p_currentMatchLine);
} else if (p_forward) {
this.cache.currentIdx += 1; this.cache.currentIdx += 1;
if (this.cache.currentIdx >= matches) { if (this.cache.currentIdx >= matches) {
this.cache.currentIdx = 0; this.cache.currentIdx = 0;
@ -120,11 +193,39 @@ class MarkJs {
this.cache.currentIdx = matches - 1; this.cache.currentIdx = matches - 1;
} }
} }
let node = this.matchedNodes[this.cache.currentIdx]; let node = this.matchedNodes[this.cache.currentIdx];
node.classList.add(this.currentMatchClassName); node.classList.add(this.currentMatchClassName);
if (!Utils.isVisible(node)) { if (!Utils.isVisible(node)) {
node.scrollIntoView(); 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); 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) { scrollToLine(p_lineNumber) {
if (p_lineNumber == 0) { if (p_lineNumber == 0) {
this.scrollToY(0, false, true); this.scrollToY(0, false, true);

View File

@ -280,12 +280,12 @@ class VNoteX extends EventEmitter {
window.vxMarkdownAdapter.setCrossCopyResult(p_id, p_timeStamp, p_html); window.vxMarkdownAdapter.setCrossCopyResult(p_id, p_timeStamp, p_html);
} }
findText(p_text, p_options) { findText(p_texts, p_options, p_currentMatchLine) {
this.searcher.findText(p_text, p_options); this.searcher.findText(p_texts, p_options, p_currentMatchLine);
} }
showFindResult(p_text, p_totalMatches, p_currentMatchIndex) { showFindResult(p_texts, p_totalMatches, p_currentMatchIndex) {
window.vxMarkdownAdapter.setFindText(p_text, p_totalMatches, p_currentMatchIndex); window.vxMarkdownAdapter.setFindText(p_texts, p_totalMatches, p_currentMatchIndex);
} }
saveContent() { saveContent() {

View File

@ -513,3 +513,8 @@ void Searcher::createSearchEngine()
m_engine.reset(new FileSearchEngine()); 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); SearchState search(const QSharedPointer<SearchOption> &p_option, const QVector<Notebook *> &p_notebooks);
const SearchToken &getToken() const;
signals: signals:
void progressUpdated(int p_val, int p_maximum); 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. // Skip the first line containing the application name.
return text.mid(text.indexOf('\n') + 1); 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(); void endBatchMode();
QPair<QStringList, FindOptions> toPatterns() const;
// Compile tokens from keyword. // Compile tokens from keyword.
// Support some magic switchs in the keyword which will suppress the given options. // 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); static bool compile(const QString &p_keyword, FindOptions p_options, SearchToken &p_token);

View File

@ -23,6 +23,7 @@
#include <QDebug> #include <QDebug>
#include <QLineEdit> #include <QLineEdit>
#include <QLayout> #include <QLayout>
#include <QPushButton>
#include <core/global.h> #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))); p_action->setText(QString("%1\t%2").arg(p_action->text(), kseq.toString(QKeySequence::NativeText)));
} }
void WidgetUtils::addActionShortcutText(QAction *p_action, void WidgetUtils::addActionShortcutText(QAction *p_action, const QString &p_shortcut)
const QString &p_shortcut)
{ {
if (p_shortcut.isEmpty()) { if (p_shortcut.isEmpty()) {
return; 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))); 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) void WidgetUtils::updateSize(QWidget *p_widget)
{ {
p_widget->adjustSize(); p_widget->adjustSize();

View File

@ -19,6 +19,7 @@ class QMenu;
class QShortcut; class QShortcut;
class QLineEdit; class QLineEdit;
class QLayout; class QLayout;
class QPushButton;
namespace vnotex namespace vnotex
{ {
@ -60,8 +61,9 @@ namespace vnotex
Qt::ShortcutContext p_context = Qt::WindowShortcut); Qt::ShortcutContext p_context = Qt::WindowShortcut);
// Just add a shortcut text hint to the action. // Just add a shortcut text hint to the action.
static void addActionShortcutText(QAction *p_action, static void addActionShortcutText(QAction *p_action, const QString &p_shortcut);
const QString &p_shortcut);
static void addButtonShortcutText(QPushButton *p_button, const QString &p_shortcut);
static QShortcut *createShortcut(const QString &p_shortcut, static QShortcut *createShortcut(const QString &p_shortcut,
QWidget *p_widget, QWidget *p_widget,

View File

@ -781,12 +781,13 @@ QWidget *ExportDialog::getCustomAdvancedSettings()
} }
{ {
auto usage = tr("%1: List of input files.\n" auto usage = tr("Command:\n"
"%2: List of paths to search for images and other resources.\n" "\t%1: List of input files.\n"
"%3: Path of rendering CSS style sheet.\n" "\t%2: List of paths to search for images and other resources.\n"
"%4: Path of syntax highlighting CSS style sheet.\n" "\t%3: Path of rendering CSS style sheet.\n"
"%5: Path of output file.\n"); "\t%4: Path of syntax highlighting CSS style sheet.\n"
layout->addRow(tr("Command usage:"), new QLabel(usage, widget)); "\t%5: Path of output file.\n");
layout->addRow(new QLabel(usage, widget));
} }
{ {
@ -798,7 +799,7 @@ QWidget *ExportDialog::getCustomAdvancedSettings()
#endif #endif
m_commandTextEdit->setMaximumHeight(m_commandTextEdit->minimumSizeHint().height()); m_commandTextEdit->setMaximumHeight(m_commandTextEdit->minimumSizeHint().height());
m_commandTextEdit->setEnabled(false); m_commandTextEdit->setEnabled(false);
layout->addRow(tr("Command:"), m_commandTextEdit); layout->addRow(m_commandTextEdit);
} }
connect(m_customExportComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), connect(m_customExportComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged),

View File

@ -6,17 +6,10 @@
#include "../outlineprovider.h" #include "../outlineprovider.h"
#include "plantumlhelper.h" #include "plantumlhelper.h"
#include "graphvizhelper.h" #include "graphvizhelper.h"
#include <utils/utils.h>
using namespace vnotex; 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) MarkdownViewerAdapter::Position::Position(int p_lineNumber, const QString &p_anchor)
: m_lineNumber(p_lineNumber), : m_lineNumber(p_lineNumber),
m_anchor(p_anchor) m_anchor(p_anchor)
@ -92,7 +85,10 @@ void MarkdownViewerAdapter::setText(int p_revision,
emit textUpdated(p_text); emit textUpdated(p_text);
scrollToPosition(Position(p_lineNumber, "")); scrollToPosition(Position(p_lineNumber, ""));
} else { } 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) { if (m_viewerReady) {
emit textUpdated(p_text); emit textUpdated(p_text);
} else { } 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; m_viewerReady = p_ready;
if (m_viewerReady) { if (m_viewerReady) {
if (m_pendingData) { for (auto &act : m_pendingActions) {
emit textUpdated(m_pendingData->m_text); act();
scrollToPosition(m_pendingData->m_position);
m_pendingData.reset();
} }
m_pendingActions.clear();
emit viewerReady(); emit viewerReady();
} }
} }
void MarkdownViewerAdapter::scrollToLine(int p_lineNumber) void MarkdownViewerAdapter::scrollToLine(int p_lineNumber)
@ -132,11 +127,9 @@ void MarkdownViewerAdapter::scrollToLine(int p_lineNumber)
} }
if (!m_viewerReady) { if (!m_viewerReady) {
if (m_pendingData) { m_pendingActions.append([this, p_lineNumber]() {
m_pendingData->m_position = Position(p_lineNumber, QString()); scrollToPosition(Position(p_lineNumber, ""));
} else { });
qWarning() << "Markdown viewer is not ready";
}
return; return;
} }
@ -321,7 +314,7 @@ void MarkdownViewerAdapter::setCrossCopyResult(quint64 p_id, quint64 p_timeStamp
emit crossCopyReady(p_id, p_timeStamp, p_html); 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; FindOption opts;
if (p_options & vnotex::FindOption::FindBackward) { if (p_options & vnotex::FindOption::FindBackward) {
@ -337,12 +330,20 @@ void MarkdownViewerAdapter::findText(const QString &p_text, FindOptions p_option
opts.m_regularExpression = true; 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() void MarkdownViewerAdapter::setWorkFinished()
@ -367,7 +368,7 @@ void MarkdownViewerAdapter::reset()
{ {
m_revision = 0; m_revision = 0;
m_viewerReady = false; m_viewerReady = false;
m_pendingData.reset(); m_pendingActions.clear();
m_topLineNumber = -1; m_topLineNumber = -1;
m_headings.clear(); m_headings.clear();
m_currentHeadingIndex = -1; m_currentHeadingIndex = -1;

View File

@ -29,19 +29,6 @@ namespace vnotex
QString m_anchor; 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 struct PreviewData
{ {
PreviewData() = default; PreviewData() = default;
@ -119,7 +106,7 @@ namespace vnotex
QString getCrossCopyTargetDisplayName(const QString &p_target) const; 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(); void saveContent();
@ -168,7 +155,7 @@ namespace vnotex
void setCrossCopyResult(quint64 p_id, quint64 p_timeStamp, const QString &p_html); 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); 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_baseUrl,
const QString &p_html); 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. // Request to get the whole HTML content.
void contentRequested(); void contentRequested();
@ -243,7 +230,7 @@ namespace vnotex
void crossCopyReady(quint64 p_id, quint64 p_timeStamp, const QString &p_html); 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, void contentReady(const QString &p_headContent,
const QString &p_styleContent, const QString &p_styleContent,
@ -260,8 +247,8 @@ namespace vnotex
// Whether web side viewer is ready to handle text update. // Whether web side viewer is ready to handle text update.
bool m_viewerReady = false; bool m_viewerReady = false;
// Pending Markdown data for the viewer once it is ready. // Pending actions for the viewer once it is ready.
QScopedPointer<MarkdownData> m_pendingData; QVector<std::function<void()>> m_pendingActions;
// Source line number of the top element node at web side. // Source line number of the top element node at web side.
int m_topLineNumber = -1; int m_topLineNumber = -1;

View File

@ -14,11 +14,13 @@
#include "lineedit.h" #include "lineedit.h"
#include "widgetsfactory.h" #include "widgetsfactory.h"
#include <utils/iconutils.h> #include <utils/iconutils.h>
#include <utils/widgetutils.h>
#include <core/thememgr.h> #include <core/thememgr.h>
#include <core/vnotex.h> #include <core/vnotex.h>
#include "propertydefs.h" #include "propertydefs.h"
#include "configmgr.h" #include <core/configmgr.h>
#include "widgetconfig.h" #include <core/editorconfig.h>
#include <widgetconfig.h>
using namespace vnotex; using namespace vnotex;
@ -80,12 +82,16 @@ void FindAndReplaceWidget::setupUI()
setFocusProxy(m_findLineEdit); setFocusProxy(m_findLineEdit);
const auto &editorConfig = ConfigMgr::getInst().getEditorConfig();
auto findNextBtn = new QPushButton(tr("Find &Next"), this); auto findNextBtn = new QPushButton(tr("Find &Next"), this);
WidgetUtils::addButtonShortcutText(findNextBtn, editorConfig.getShortcut(EditorConfig::FindNext));
findNextBtn->setDefault(true); findNextBtn->setDefault(true);
connect(findNextBtn, &QPushButton::clicked, connect(findNextBtn, &QPushButton::clicked,
this, &FindAndReplaceWidget::findNext); this, &FindAndReplaceWidget::findNext);
auto findPrevBtn = new QPushButton(tr("Find &Previous"), this); auto findPrevBtn = new QPushButton(tr("Find &Previous"), this);
WidgetUtils::addButtonShortcutText(findPrevBtn, editorConfig.getShortcut(EditorConfig::FindPrevious));
connect(findPrevBtn, &QPushButton::clicked, connect(findPrevBtn, &QPushButton::clicked,
this, &FindAndReplaceWidget::findPrevious); this, &FindAndReplaceWidget::findPrevious);

View File

@ -159,7 +159,7 @@ void HistoryPanel::updateHistoryList()
if (itemIdx >= 0) { if (itemIdx >= 0) {
// Older. // Older.
auto sepItem = ListWidget::createSeparatorItem(tr(">>> Older")); auto sepItem = ListWidget::createSeparatorItem(tr("Older"));
m_historyList->addItem(sepItem); m_historyList->addItem(sepItem);
for (; itemIdx >= 0; --itemIdx) { for (; itemIdx >= 0; --itemIdx) {
@ -191,11 +191,11 @@ void HistoryPanel::updateSeparators()
auto curDateTime = QDateTime::currentDateTime(); auto curDateTime = QDateTime::currentDateTime();
curDateTime.setTime(QTime()); 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[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[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(); m_separators[2].m_dateUtc = curDateTime.addDays(-7).toUTC();
} }

View File

@ -457,8 +457,8 @@ void MarkdownViewWindow::setupViewer()
} }
}); });
connect(adapter, &MarkdownViewerAdapter::findTextReady, connect(adapter, &MarkdownViewerAdapter::findTextReady,
this, [this](const QString &p_text, int p_totalMatches, int p_currentMatchIndex) { this, [this](const QStringList &p_texts, int p_totalMatches, int p_currentMatchIndex) {
this->showFindResult(p_text, p_totalMatches, 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 (isReadMode()) {
if (p_options & FindOption::IncrementalSearch) { if (p_options & FindOption::IncrementalSearch) {
adapter()->findText(p_text, p_options); adapter()->findText(QStringList(p_text), p_options);
} }
} else { } else {
TextViewWindowHelper::handleFindTextChanged(this, p_text, p_options); 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()) { if (isReadMode()) {
adapter()->findText(p_text, p_options); adapter()->findText(p_texts, p_options);
} else { } 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) { if (m_editor) {
TextViewWindowHelper::handleFindAndReplaceWidgetClosed(this); TextViewWindowHelper::handleFindAndReplaceWidgetClosed(this);
} else { } 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); 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 bool MarkdownViewWindow::isReadMode() const
{ {
return m_mode == ViewWindowMode::Read; return m_mode == ViewWindowMode::Read;

View File

@ -24,6 +24,7 @@ namespace vnotex
class MarkdownEditorConfig; class MarkdownEditorConfig;
class EditorConfig; class EditorConfig;
class ImageHost; class ImageHost;
class SearchToken;
class MarkdownViewWindow : public ViewWindow class MarkdownViewWindow : public ViewWindow
{ {
@ -65,7 +66,7 @@ namespace vnotex
void handleFindTextChanged(const QString &p_text, FindOptions p_options) Q_DECL_OVERRIDE; 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; 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 scrollToLine(int p_lineNumber);
void findTextBySearchToken(const QSharedPointer<SearchToken> &p_token, int p_currentMatchLine);
bool isReadMode() const; bool isReadMode() const;
void updatePreviewHelperFromConfig(const MarkdownEditorConfig &p_config); void updatePreviewHelperFromConfig(const MarkdownEditorConfig &p_config);

View File

@ -554,6 +554,9 @@ void SearchPanel::prepareLocationList()
} }
m_locationList->clear(); m_locationList->clear();
m_searchTokenOfSession.clear();
m_locationList->startSession([this](const Location &p_location) { m_locationList->startSession([this](const Location &p_location) {
handleLocationActivated(p_location); handleLocationActivated(p_location);
}); });
@ -561,9 +564,15 @@ void SearchPanel::prepareLocationList()
void SearchPanel::handleLocationActivated(const Location &p_location) 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. // TODO: decode the path of location and handle different types of destination.
auto paras = QSharedPointer<FileOpenParameters>::create(); auto paras = QSharedPointer<FileOpenParameters>::create();
paras->m_lineNumber = p_location.m_lineNumber; paras->m_lineNumber = p_location.m_lineNumber;
paras->m_searchToken = m_searchTokenOfSession;
emit VNoteX::getInst().openFileRequested(p_location.m_path, paras); emit VNoteX::getInst().openFileRequested(p_location.m_path, paras);
} }

View File

@ -26,6 +26,7 @@ namespace vnotex
class Notebook; class Notebook;
class LocationList; class LocationList;
struct Location; struct Location;
class SearchToken;
class ISearchInfoProvider class ISearchInfoProvider
{ {
@ -145,6 +146,8 @@ namespace vnotex
Searcher *m_searcher = nullptr; Searcher *m_searcher = nullptr;
LocationList *m_locationList = 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); 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) 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) { if (p_paras->m_lineNumber > -1) {
m_editor->scrollToLine(p_paras->m_lineNumber, true); 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 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 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; 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 <QTextCursor>
#include <QRegularExpression> #include <QRegularExpression>
#include <QTextBlock> #include <QTextBlock>
#include <QSharedPointer>
#include <vtextedit/texteditorconfig.h> #include <vtextedit/texteditorconfig.h>
#include <core/texteditorconfig.h> #include <core/texteditorconfig.h>
#include <core/configmgr.h> #include <core/configmgr.h>
#include <utils/widgetutils.h> #include <utils/widgetutils.h>
#include <snippet/snippetmgr.h> #include <snippet/snippetmgr.h>
#include <search/searchtoken.h>
#include "quickselector.h" #include "quickselector.h"
@ -169,10 +171,10 @@ namespace vnotex
} }
template <typename _ViewWindow> 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)); const auto result = p_win->m_editor->findText(p_texts, toEditorFindFlags(p_options));
p_win->showFindResult(p_text, result.m_totalMatches, result.m_currentMatchIndex); p_win->showFindResult(p_texts, result.m_totalMatches, result.m_currentMatchIndex);
} }
template <typename _ViewWindow> template <typename _ViewWindow>
@ -293,6 +295,15 @@ namespace vnotex
} }
return textEdit->mapToGlobal(localPos); 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); 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); Q_UNUSED(p_options);
} }
@ -1039,46 +1039,49 @@ void ViewWindow::findNextOnLastFind(bool p_forward)
{ {
// Check if need to update the find info. // Check if need to update the find info.
if (m_findAndReplace && m_findAndReplace->isVisible()) { 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(); m_findInfo.m_options = m_findAndReplace->getOptions();
} }
if (m_findInfo.m_text.isEmpty()) { if (m_findInfo.m_texts.isEmpty()) {
return; return;
} }
if (p_forward) { 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 { } 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) 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; 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) 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; m_findInfo.m_options = p_options;
handleReplace(p_text, p_options, p_replaceText); handleReplace(p_text, p_options, p_replaceText);
} }
void ViewWindow::replaceAll(const QString &p_text, FindOptions p_options, const QString &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; m_findInfo.m_options = p_options;
handleReplaceAll(p_text, p_options, p_replaceText); 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) { 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 { } else {
showMessage(tr("Match found: %1/%2").arg(p_currentMatchIndex + 1).arg(p_totalMatches)); 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); 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 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); virtual void handleReplace(const QString &p_text, FindOptions p_options, const QString &p_replaceText);
@ -229,7 +229,7 @@ namespace vnotex
bool findAndReplaceWidgetVisible() const; bool findAndReplaceWidgetVisible() const;
// @p_currentMatchIndex: 0-based. // @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); 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. // Show message in status widget if exists. Otherwise, show it in the mainwindow's status widget.
void showMessage(const QString p_msg); void showMessage(const QString p_msg);
void updateLastFindInfo(const QStringList &p_texts, FindOptions p_options);
virtual QPoint getFloatingWidgetPosition(); virtual QPoint getFloatingWidgetPosition();
static QToolBar *createToolBar(QWidget *p_parent = nullptr); static QToolBar *createToolBar(QWidget *p_parent = nullptr);
@ -259,7 +261,7 @@ namespace vnotex
private: private:
struct FindInfo struct FindInfo
{ {
QString m_text; QStringList m_texts;
FindOptions m_options; FindOptions m_options;
}; };