vnote/src/vsearchconfig.h

615 lines
14 KiB
C++

#ifndef VSEARCHCONFIG_H
#define VSEARCHCONFIG_H
#include <QString>
#include <QStringList>
#include <QSharedPointer>
#include <QVector>
#include <QRegExp>
#include "utils/vutils.h"
struct VSearchToken
{
enum Type
{
RawString = 0,
RegularExpression
};
enum Operator
{
And = 0,
Or
};
VSearchToken()
: m_type(Type::RawString),
m_op(Operator::And),
m_caseSensitivity(Qt::CaseSensitive)
{
}
void clear()
{
m_keywords.clear();
m_regs.clear();
}
void append(const QString &p_rawStr)
{
m_keywords.append(p_rawStr);
}
void append(const QRegExp &p_reg)
{
m_regs.append(p_reg);
}
QString toString() const
{
return QString("token %1 %2 %3 %4 %5").arg(m_type)
.arg(m_op)
.arg(m_caseSensitivity)
.arg(m_keywords.size())
.arg(m_regs.size());
}
// Whether @p_text match all the constraint.
bool matched(const QString &p_text) const
{
int size = m_keywords.size();
if (m_type == Type::RegularExpression) {
size = m_regs.size();
}
if (size == 0) {
return false;
}
bool ret = m_op == Operator::And ? true : false;
for (int i = 0; i < size; ++i) {
bool tmp = false;
if (m_type == Type::RawString) {
tmp = p_text.contains(m_keywords[i], m_caseSensitivity);
} else {
tmp = p_text.contains(m_regs[i]);
}
if (tmp) {
if (m_op == Operator::Or) {
ret = true;
break;
}
} else {
if (m_op == Operator::And) {
ret = false;
break;
}
}
}
return ret;
}
void startBatchMode()
{
int size = m_type == Type::RawString ? m_keywords.size() : m_regs.size();
m_matchesInBatch.resize(size);
m_matchesInBatch.fill(false);
m_numOfMatches = 0;
}
// Match one string in batch mode.
// Returns true if @p_text matches one.
bool matchBatchMode(const QString &p_text)
{
bool ret = false;
int size = m_matchesInBatch.size();
for (int i = 0; i < size; ++i) {
if (m_matchesInBatch[i]) {
continue;
}
bool tmp = false;
if (m_type == Type::RawString) {
tmp = p_text.contains(m_keywords[i], m_caseSensitivity);
} else {
tmp = p_text.contains(m_regs[i]);
}
if (tmp) {
m_matchesInBatch[i] = true;
++m_numOfMatches;
ret = true;
}
}
return ret;
}
// Whether it is OK to finished batch mode.
// @p_matched: the overall match result.
bool readyToEndBatchMode(bool &p_matched) const
{
if (m_op == VSearchToken::And) {
// We need all the tokens matched.
if (m_numOfMatches == m_matchesInBatch.size()) {
p_matched = true;
return true;
} else {
p_matched = false;
return false;
}
} else {
// We only need one match.
if (m_numOfMatches > 0) {
p_matched = true;
return true;
} else {
p_matched = false;
return false;
}
}
}
void endBatchMode()
{
m_matchesInBatch.clear();
m_numOfMatches = 0;
}
int tokenSize() const
{
return m_type == Type::RawString ? m_keywords.size() : m_regs.size();
}
bool isEmpty() const
{
return tokenSize() == 0;
}
bool operator==(const VSearchToken &p_other) const
{
if (m_type != p_other.m_type
|| m_op != p_other.m_op
|| m_caseSensitivity != p_other.m_caseSensitivity
|| m_keywords.size() != p_other.m_keywords.size()
|| m_numOfMatches != p_other.m_numOfMatches
|| m_regs.size() != p_other.m_regs.size()) {
return false;
}
return m_keywords == p_other.m_keywords
&& m_regs == p_other.m_regs;
}
VSearchToken::Type m_type;
VSearchToken::Operator m_op;
Qt::CaseSensitivity m_caseSensitivity;
// Valid at RawString.
QVector<QString> m_keywords;
// Valid at RegularExpression.
QVector<QRegExp> m_regs;
// Bitmap for batch mode.
// True if m_regs[i] or m_keywords[i] has been matched.
QVector<bool> m_matchesInBatch;
int m_numOfMatches;
};
struct VSearchConfig
{
enum Scope
{
NoneScope = 0,
CurrentNote,
OpenedNotes,
CurrentFolder,
CurrentNotebook,
AllNotebooks,
ExplorerDirectory
};
enum Object
{
NoneObject = 0,
Name = 0x1UL,
Content = 0x2UL,
Outline = 0x4UL,
Tag = 0x8UL,
Path = 0x10UL
};
enum Target
{
NoneTarget = 0,
Note = 0x1UL,
Folder = 0x2UL,
Notebook = 0x4UL
};
enum Engine
{
Internal = 0
};
enum Option
{
NoneOption = 0,
CaseSensitive = 0x1UL,
WholeWordOnly = 0x2UL,
Fuzzy = 0x4UL,
RegularExpression = 0x8UL
};
VSearchConfig()
: VSearchConfig(Scope::NoneScope,
Object::NoneObject,
Target::NoneTarget,
Engine::Internal,
Option::NoneOption,
"",
"")
{
}
VSearchConfig(int p_scope,
int p_object,
int p_target,
int p_engine,
int p_option,
const QString &p_keyword,
const QString &p_pattern)
: m_scope(p_scope),
m_object(p_object),
m_target(p_target),
m_engine(p_engine),
m_option(p_option),
m_pattern(p_pattern)
{
compileToken(p_keyword);
}
// We support some magic switch in the keyword which will suppress the specified
// options:
// \c: Case insensitive;
// \C: Case sensitive;
// \r: Turn off regular expression;
// \R: Turn on regular expression;
// \f: Turn off fuzzy search;
// \F: Turn on fuzzy search (invalid when searching content);
// \w: Turn off whole word only;
// \W: Turn on whole word only;
void compileToken(const QString &p_keyword)
{
m_token.clear();
m_contentToken.clear();
if (p_keyword.isEmpty()) {
return;
}
// """ to input a ";
// && for AND, || for OR;
QStringList args = VUtils::parseCombinedArgString(p_keyword);
Qt::CaseSensitivity cs = m_option & VSearchConfig::CaseSensitive
? Qt::CaseSensitive : Qt::CaseInsensitive;
bool useReg = m_option & VSearchConfig::RegularExpression;
bool wwo = m_option & VSearchConfig::WholeWordOnly;
bool fuzzy = m_option & VSearchConfig::Fuzzy;
// Read magic switch from keyword.
for (int i = 0; i < args.size();) {
const QString &arg = args[i];
if (arg.size() != 2 || arg[0] != '\\') {
++i;
continue;
}
if (arg == "\\c") {
cs = Qt::CaseInsensitive;
} else if (arg == "\\C") {
cs = Qt::CaseSensitive;
} else if (arg == "\\r") {
useReg = false;
} else if (arg == "\\R") {
useReg = true;
} else if (arg == "\\f") {
fuzzy = false;
} else if (arg == "\\F") {
fuzzy = true;
} else if (arg == "\\w") {
wwo = false;
} else if (arg == "\\W") {
wwo = true;
} else {
++i;
continue;
}
args.removeAt(i);
}
if (args.isEmpty()) {
return;
}
m_token.m_caseSensitivity = cs;
m_contentToken.m_caseSensitivity = cs;
if (useReg) {
m_token.m_type = VSearchToken::RegularExpression;
m_contentToken.m_type = VSearchToken::RegularExpression;
} else {
if (fuzzy) {
m_token.m_type = VSearchToken::RegularExpression;
m_contentToken.m_type = VSearchToken::RawString;
} else if (wwo) {
m_token.m_type = VSearchToken::RegularExpression;
m_contentToken.m_type = VSearchToken::RegularExpression;
} else {
m_token.m_type = VSearchToken::RawString;
m_contentToken.m_type = VSearchToken::RawString;
}
}
VSearchToken::Operator op = VSearchToken::And;
for (auto const & arg : args) {
if (arg == QStringLiteral("&&")) {
op = VSearchToken::And;
continue;
} else if (arg == QStringLiteral("||")) {
op = VSearchToken::Or;
continue;
}
if (useReg) {
QRegExp reg(arg, cs);
m_token.append(reg);
m_contentToken.append(reg);
} else {
if (fuzzy) {
QString wildcardText(arg.size() * 2 + 1, '*');
for (int i = 0, j = 1; i < arg.size(); ++i, j += 2) {
wildcardText[j] = arg[i];
}
QRegExp reg(wildcardText, cs, QRegExp::Wildcard);
m_token.append(reg);
m_contentToken.append(arg);
} else if (wwo) {
QString pattern = QRegExp::escape(arg);
pattern = "\\b" + pattern + "\\b";
QRegExp reg(pattern, cs);
m_token.append(reg);
m_contentToken.append(reg);
} else {
m_token.append(arg);
m_contentToken.append(arg);
}
}
}
m_token.m_op = op;
m_contentToken.m_op = op;
}
bool isEmpty() const
{
return m_token.tokenSize() == 0;
}
QStringList toConfig() const
{
QStringList str;
str << QString::number(m_scope);
str << QString::number(m_object);
str << QString::number(m_target);
str << QString::number(m_engine);
str << QString::number(m_option);
str << m_pattern;
return str;
}
static VSearchConfig fromConfig(const QStringList &p_str)
{
VSearchConfig config;
if (p_str.size() != 6) {
return config;
}
config.m_scope = p_str[0].toInt();
config.m_object = p_str[1].toInt();
config.m_target = p_str[2].toInt();
config.m_engine = p_str[3].toInt();
config.m_option = p_str[4].toInt();
config.m_pattern = p_str[5];
return config;
}
int m_scope;
int m_object;
int m_target;
int m_engine;
int m_option;
// Wildcard pattern to filter file.
QString m_pattern;
// Token for name, outline.
VSearchToken m_token;
// Token for content and tag.
VSearchToken m_contentToken;
};
struct VSearchResultSubItem
{
VSearchResultSubItem()
: m_lineNumber(-1)
{
}
VSearchResultSubItem(int p_lineNumber,
const QString &p_text)
: m_lineNumber(p_lineNumber),
m_text(p_text)
{
}
int m_lineNumber;
QString m_text;
};
struct VSearchResultItem
{
enum ItemType
{
None = 0,
Note,
Folder,
Notebook
};
enum MatchType
{
LineNumber = 0,
OutlineIndex
};
VSearchResultItem()
: m_type(ItemType::None),
m_matchType(MatchType::LineNumber)
{
}
VSearchResultItem(VSearchResultItem::ItemType p_type,
VSearchResultItem::MatchType p_matchType,
const QString &p_text,
const QString &p_path,
const QSharedPointer<VSearchConfig> &p_config = nullptr)
: m_type(p_type),
m_matchType(p_matchType),
m_text(p_text),
m_path(p_path),
m_config(p_config)
{
}
bool isEmpty() const
{
return m_type == ItemType::None;
}
QString toString() const
{
return QString("item text: [%1] path: [%2] subitems: %3")
.arg(m_text)
.arg(m_path)
.arg(m_matches.size());
}
ItemType m_type;
MatchType m_matchType;
// Text to displayed. If empty, use @m_path instead.
QString m_text;
// Path of the target.
QString m_path;
// Matched places within this item.
QList<VSearchResultSubItem> m_matches;
// Search config to search for this item.
QSharedPointer<VSearchConfig> m_config;
};
class VSearch;
enum class VSearchState
{
Idle = 0,
Busy,
Success,
Fail,
Cancelled
};
struct VSearchResult
{
friend class VSearch;
explicit VSearchResult(VSearch *p_search)
: m_state(VSearchState::Idle),
m_search(p_search)
{
}
bool hasError() const
{
return !m_errMsg.isEmpty();
}
void logError(const QString &p_err)
{
if (m_errMsg.isEmpty()) {
m_errMsg = p_err;
} else {
m_errMsg = "\n" + p_err;
}
}
void addSecondPhaseItem(const QString &p_item)
{
m_secondPhaseItems.append(p_item);
}
QString toString() const
{
QString str = QString("search result: state %1 err %2")
.arg((int)m_state)
.arg(!m_errMsg.isEmpty());
return str;
}
bool hasSecondPhaseItems() const
{
return !m_secondPhaseItems.isEmpty();
}
VSearchState m_state;
QString m_errMsg;
QStringList m_secondPhaseItems;
private:
VSearch *m_search;
};
#endif // VSEARCHCONFIG_H