mirror of
https://gitee.com/vnotex/vnote.git
synced 2025-07-05 05:49:53 +08:00
Search: highlight matched items in opened files
This commit is contained in:
parent
8326d3c702
commit
ed8cd503b5
@ -1 +1 @@
|
||||
Subproject commit 392c0e218f5981b4fc6566512103b6149f714931
|
||||
Subproject commit 59e53f2cdc2f5a4b9dc44cbf28c483cca07559ba
|
@ -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";
|
||||
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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() {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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() {
|
||||
|
@ -513,3 +513,8 @@ void Searcher::createSearchEngine()
|
||||
|
||||
m_engine.reset(new FileSearchEngine());
|
||||
}
|
||||
|
||||
const SearchToken &Searcher::getToken() const
|
||||
{
|
||||
return m_token;
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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 ® : m_regularExpressions) {
|
||||
ret.first << reg.pattern();
|
||||
}
|
||||
} else {
|
||||
ret.first = m_keywords;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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,
|
||||
|
@ -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),
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user