LocationList: highlight text segments

This commit is contained in:
Le Tan 2021-07-21 20:54:58 +08:00
parent edfdd68c36
commit ef69ee435f
15 changed files with 184 additions and 41 deletions

@ -1 +1 @@
Subproject commit 76a963c0e2ac488001872ac6f324ba1c8b177bfd Subproject commit a15d859141506142a11cbf843a2d93124ea65bfa

View File

@ -109,6 +109,30 @@ namespace vnotex
Right Right
}; };
struct Segment
{
Segment() = default;
Segment(int p_offset, int p_length)
: m_offset(p_offset),
m_length(p_length)
{
}
bool operator<(const Segment &p_other) const
{
if (m_offset < p_other.m_offset) {
return true;;
} else {
return m_length < p_other.m_length;
}
}
int m_offset = 0;
int m_length = -1;
};
} // ns vnotex } // ns vnotex
Q_DECLARE_OPERATORS_FOR_FLAGS(vnotex::FindOptions); Q_DECLARE_OPERATORS_FOR_FLAGS(vnotex::FindOptions);

View File

@ -3,6 +3,8 @@
#include <QDebug> #include <QDebug>
#include "global.h"
namespace vnotex namespace vnotex
{ {
struct Location struct Location
@ -37,9 +39,10 @@ namespace vnotex
{ {
Line() = default; Line() = default;
Line(int p_lineNumber, const QString &p_text) Line(int p_lineNumber, const QString &p_text, const QVector<Segment> &p_segments)
: m_lineNumber(p_lineNumber), : m_lineNumber(p_lineNumber),
m_text(p_text) m_text(p_text),
m_segments(p_segments)
{ {
} }
@ -47,11 +50,13 @@ namespace vnotex
int m_lineNumber = -1; int m_lineNumber = -1;
QString m_text; QString m_text;
QVector<Segment> m_segments;
}; };
void addLine(int p_lineNumber, const QString &p_text) void addLine(int p_lineNumber, const QString &p_text, const QVector<Segment> &p_segments)
{ {
m_lines.push_back(Line(p_lineNumber, p_text)); m_lines.push_back(Line(p_lineNumber, p_text, p_segments));
} }
friend QDebug operator<<(QDebug p_dbg, const ComplexLocation &p_loc) friend QDebug operator<<(QDebug p_dbg, const ComplexLocation &p_loc)

View File

@ -236,6 +236,10 @@
"locationlist" : { "locationlist" : {
"node_icon" : { "node_icon" : {
"fg" : "@base#icon#fg" "fg" : "@base#icon#fg"
},
"text_highlight" : {
"fg" : "@base#master#fg",
"bg" : "@base#master#bg"
} }
}, },
"viewsplit" : { "viewsplit" : {

View File

@ -44,6 +44,10 @@
"danger" : { "danger" : {
"fg": "@base#danger#fg" "fg": "@base#danger#fg"
} }
},
"master" : {
"fg" : "#fbffff",
"bg" : "#535c65"
} }
}, },
"widgets" : { "widgets" : {
@ -53,8 +57,8 @@
"button": { "button": {
"fg" : "@base#normal#fg", "fg" : "@base#normal#fg",
"active" : { "active" : {
"fg" : "#fbffff", "fg" : "@base#master#fg",
"bg" : "#535c65" "bg" : "@base#master#bg"
} }
} }
} }
@ -95,6 +99,10 @@
"locationlist" : { "locationlist" : {
"node_icon" : { "node_icon" : {
"fg" : "@base#icon#fg" "fg" : "@base#icon#fg"
},
"text_highlight" : {
"fg" : "@base#master#fg",
"bg" : "@base#master#bg"
} }
}, },
"viewsplit" : { "viewsplit" : {
@ -123,8 +131,8 @@
}, },
"quickselector" : { "quickselector" : {
"item_icon" : { "item_icon" : {
"fg" : "#535c65", "fg" : "@base#master#bg",
"border" : "#535c65" "border" : "@base#master#bg"
} }
} }
} }

View File

@ -232,6 +232,10 @@
"locationlist" : { "locationlist" : {
"node_icon" : { "node_icon" : {
"fg" : "@base#icon#fg" "fg" : "@base#icon#fg"
},
"text_highlight" : {
"fg" : "@base#master#fg",
"bg" : "@base#master#bg"
} }
}, },
"viewsplit" : { "viewsplit" : {

View File

@ -97,17 +97,18 @@ void FileSearchEngineWorker::searchFile(const QString &p_filePath, const QString
const auto lineText = ins.readLine(); const auto lineText = ins.readLine();
bool matched = false; bool matched = false;
QVector<Segment> segments;
if (!shouldStartBatchMode) { if (!shouldStartBatchMode) {
matched = m_token.matched(lineText); matched = m_token.matched(lineText, &segments);
} else { } else {
matched = m_token.matchedInBatchMode(lineText); matched = m_token.matchedInBatchMode(lineText, &segments);
} }
if (matched) { if (matched) {
if (resultItem) { if (resultItem) {
resultItem->addLine(lineNum, lineText); resultItem->addLine(lineNum, lineText, segments);
} else { } else {
resultItem = SearchResultItem::createFileItem(p_filePath, p_displayPath, lineNum, lineText); resultItem = SearchResultItem::createFileItem(p_filePath, p_displayPath, lineNum, lineText, segments);
} }
} }

View File

@ -207,13 +207,13 @@ bool Searcher::firstPhaseSearch(const File *p_file)
if (testObject(SearchObject::SearchName)) { if (testObject(SearchObject::SearchName)) {
if (isTokenMatched(name)) { if (isTokenMatched(name)) {
emit resultItemAdded(SearchResultItem::createBufferItem(filePath, relativePath, -1, name)); emit resultItemAdded(SearchResultItem::createBufferItem(filePath, relativePath));
} }
} }
if (testObject(SearchObject::SearchPath)) { if (testObject(SearchObject::SearchPath)) {
if (isTokenMatched(relativePath)) { if (isTokenMatched(relativePath)) {
emit resultItemAdded(SearchResultItem::createBufferItem(filePath, relativePath, -1, name)); emit resultItemAdded(SearchResultItem::createBufferItem(filePath, relativePath));
} }
} }
@ -294,17 +294,18 @@ bool Searcher::searchContent(const File *p_file)
if (idx > pos) { if (idx > pos) {
QString lineText = content.mid(pos, idx - pos); QString lineText = content.mid(pos, idx - pos);
bool matched = false; bool matched = false;
QVector<Segment> segments;
if (!shouldStartBatchMode) { if (!shouldStartBatchMode) {
matched = m_token.matched(lineText); matched = m_token.matched(lineText, &segments);
} else { } else {
matched = m_token.matchedInBatchMode(lineText); matched = m_token.matchedInBatchMode(lineText, &segments);
} }
if (matched) { if (matched) {
if (resultItem) { if (resultItem) {
resultItem->addLine(lineNum, lineText); resultItem->addLine(lineNum, lineText, segments);
} else { } else {
resultItem = SearchResultItem::createBufferItem(filePath, relativePath, lineNum, lineText); resultItem = SearchResultItem::createBufferItem(filePath, relativePath, lineNum, lineText, segments);
} }
} }
} }
@ -407,13 +408,13 @@ bool Searcher::firstPhaseSearch(Node *p_node, QVector<SearchSecondPhaseItem> &p_
if (testObject(SearchObject::SearchName)) { if (testObject(SearchObject::SearchName)) {
if (isTokenMatched(name)) { if (isTokenMatched(name)) {
emit resultItemAdded(SearchResultItem::createFileItem(filePath, relativePath, -1, name)); emit resultItemAdded(SearchResultItem::createFileItem(filePath, relativePath));
} }
} }
if (testObject(SearchObject::SearchPath)) { if (testObject(SearchObject::SearchPath)) {
if (isTokenMatched(relativePath)) { if (isTokenMatched(relativePath)) {
emit resultItemAdded(SearchResultItem::createFileItem(filePath, relativePath, -1, name)); emit resultItemAdded(SearchResultItem::createFileItem(filePath, relativePath));
} }
} }

View File

@ -5,26 +5,42 @@ using namespace vnotex;
QSharedPointer<SearchResultItem> SearchResultItem::createBufferItem(const QString &p_targetPath, QSharedPointer<SearchResultItem> SearchResultItem::createBufferItem(const QString &p_targetPath,
const QString &p_displayPath, const QString &p_displayPath,
int p_lineNumber, int p_lineNumber,
const QString &p_text) const QString &p_text,
const QVector<Segment> &p_segments)
{
auto item = createBufferItem(p_targetPath, p_displayPath);
item->m_location.addLine(p_lineNumber, p_text, p_segments);
return item;
}
QSharedPointer<SearchResultItem> SearchResultItem::createBufferItem(const QString &p_targetPath,
const QString &p_displayPath)
{ {
auto item = QSharedPointer<SearchResultItem>::create(); auto item = QSharedPointer<SearchResultItem>::create();
item->m_location.m_type = LocationType::Buffer; item->m_location.m_type = LocationType::Buffer;
item->m_location.m_path = p_targetPath; item->m_location.m_path = p_targetPath;
item->m_location.m_displayPath = p_displayPath; item->m_location.m_displayPath = p_displayPath;
item->m_location.addLine(p_lineNumber, p_text);
return item; return item;
} }
QSharedPointer<SearchResultItem> SearchResultItem::createFileItem(const QString &p_targetPath, QSharedPointer<SearchResultItem> SearchResultItem::createFileItem(const QString &p_targetPath,
const QString &p_displayPath, const QString &p_displayPath,
int p_lineNumber, int p_lineNumber,
const QString &p_text) const QString &p_text,
const QVector<Segment> &p_segments)
{
auto item = createFileItem(p_targetPath, p_displayPath);
item->m_location.addLine(p_lineNumber, p_text, p_segments);
return item;
}
QSharedPointer<SearchResultItem> SearchResultItem::createFileItem(const QString &p_targetPath,
const QString &p_displayPath)
{ {
auto item = QSharedPointer<SearchResultItem>::create(); auto item = QSharedPointer<SearchResultItem>::create();
item->m_location.m_type = LocationType::File; item->m_location.m_type = LocationType::File;
item->m_location.m_path = p_targetPath; item->m_location.m_path = p_targetPath;
item->m_location.m_displayPath = p_displayPath; item->m_location.m_displayPath = p_displayPath;
item->m_location.addLine(p_lineNumber, p_text);
return item; return item;
} }
@ -48,7 +64,7 @@ QSharedPointer<SearchResultItem> SearchResultItem::createNotebookItem(const QStr
return item; return item;
} }
void SearchResultItem::addLine(int p_lineNumber, const QString &p_text) void SearchResultItem::addLine(int p_lineNumber, const QString &p_text, const QVector<Segment> &p_segments)
{ {
m_location.addLine(p_lineNumber, p_text); m_location.addLine(p_lineNumber, p_text, p_segments);
} }

View File

@ -17,17 +17,25 @@ namespace vnotex
return p_dbg; return p_dbg;
} }
void addLine(int p_lineNumber, const QString &p_text); void addLine(int p_lineNumber, const QString &p_text, const QVector<Segment> &p_segments);
static QSharedPointer<SearchResultItem> createBufferItem(const QString &p_targetPath, static QSharedPointer<SearchResultItem> createBufferItem(const QString &p_targetPath,
const QString &p_displayPath, const QString &p_displayPath,
int p_lineNumber, int p_lineNumber,
const QString &p_text); const QString &p_text,
const QVector<Segment> &p_segments);
static QSharedPointer<SearchResultItem> createBufferItem(const QString &p_targetPath,
const QString &p_displayPath);
static QSharedPointer<SearchResultItem> createFileItem(const QString &p_targetPath, static QSharedPointer<SearchResultItem> createFileItem(const QString &p_targetPath,
const QString &p_displayPath, const QString &p_displayPath,
int p_lineNumber, int p_lineNumber,
const QString &p_text); const QString &p_text,
const QVector<Segment> &p_segments);
static QSharedPointer<SearchResultItem> createFileItem(const QString &p_targetPath,
const QString &p_displayPath);
static QSharedPointer<SearchResultItem> createFolderItem(const QString &p_targetPath, static QSharedPointer<SearchResultItem> createFolderItem(const QString &p_targetPath,
const QString &p_displayPath); const QString &p_displayPath);

View File

@ -31,7 +31,7 @@ void SearchToken::append(const QRegularExpression &p_regExp)
m_regularExpressions.append(p_regExp); m_regularExpressions.append(p_regExp);
} }
bool SearchToken::matched(const QString &p_text) const bool SearchToken::matched(const QString &p_text, QVector<Segment> *p_segments) const
{ {
const int consSize = constraintSize(); const int consSize = constraintSize();
if (consSize == 0) { if (consSize == 0) {
@ -42,9 +42,22 @@ bool SearchToken::matched(const QString &p_text) const
for (int i = 0; i < consSize; ++i) { for (int i = 0; i < consSize; ++i) {
bool consMatched = false; bool consMatched = false;
if (m_type == Type::PlainText) { if (m_type == Type::PlainText) {
consMatched = p_text.contains(m_keywords[i], m_caseSensitivity); int idx = p_text.indexOf(m_keywords[i], 0, m_caseSensitivity);
if (idx > -1) {
consMatched = true;
if (p_segments) {
p_segments->push_back(Segment(idx, m_keywords[i].size()));
}
}
} else { } else {
consMatched = p_text.contains(m_regularExpressions[i]); QRegularExpressionMatch match;
int idx = p_text.indexOf(m_regularExpressions[i], 0, &match);
if (idx > -1) {
consMatched = true;
if (p_segments) {
p_segments->push_back(Segment(idx, match.capturedLength()));
}
}
} }
if (consMatched) { if (consMatched) {
@ -77,7 +90,7 @@ void SearchToken::startBatchMode()
m_matchedConstraintsCountInBatchMode = 0; m_matchedConstraintsCountInBatchMode = 0;
} }
bool SearchToken::matchedInBatchMode(const QString &p_text) bool SearchToken::matchedInBatchMode(const QString &p_text, QVector<Segment> *p_segments)
{ {
bool isMatched = false; bool isMatched = false;
const int consSize = m_matchedConstraintsInBatchMode.size(); const int consSize = m_matchedConstraintsInBatchMode.size();
@ -88,9 +101,22 @@ bool SearchToken::matchedInBatchMode(const QString &p_text)
bool consMatched = false; bool consMatched = false;
if (m_type == Type::PlainText) { if (m_type == Type::PlainText) {
consMatched = p_text.contains(m_keywords[i], m_caseSensitivity); int idx = p_text.indexOf(m_keywords[i], 0, m_caseSensitivity);
if (idx > -1) {
consMatched = true;
if (p_segments) {
p_segments->push_back(Segment(idx, m_keywords[i].size()));
}
}
} else { } else {
consMatched = p_text.contains(m_regularExpressions[i]); QRegularExpressionMatch match;
int idx = p_text.indexOf(m_regularExpressions[i], 0, &match);
if (idx > -1) {
consMatched = true;
if (p_segments) {
p_segments->push_back(Segment(idx, match.capturedLength()));
}
}
} }
if (consMatched) { if (consMatched) {

View File

@ -5,7 +5,6 @@
#include <QVector> #include <QVector>
#include <QRegularExpression> #include <QRegularExpression>
#include <QBitArray> #include <QBitArray>
#include <QPair>
#include <QScopedPointer> #include <QScopedPointer>
#include <core/global.h> #include <core/global.h>
@ -36,7 +35,7 @@ namespace vnotex
void append(const QRegularExpression &p_regExp); void append(const QRegularExpression &p_regExp);
// Whether @p_text is matched. // Whether @p_text is matched.
bool matched(const QString &p_text) const; bool matched(const QString &p_text, QVector<Segment> *p_segments = nullptr) const;
int constraintSize() const; int constraintSize() const;
@ -49,7 +48,7 @@ namespace vnotex
// Match one string in batch mode. // Match one string in batch mode.
// Return true if @p_text is matched. // Return true if @p_text is matched.
bool matchedInBatchMode(const QString &p_text); bool matchedInBatchMode(const QString &p_text, QVector<Segment> *p_segments = nullptr);
bool readyToEndBatchMode() const; bool readyToEndBatchMode() const;

View File

@ -32,7 +32,7 @@ SnippetInfoWidget::SnippetInfoWidget(const Snippet *p_snippet, QWidget *p_parent
void SnippetInfoWidget::setupUI() void SnippetInfoWidget::setupUI()
{ {
auto mainLayout = new QFormLayout(this); auto mainLayout = WidgetsFactory::createFormLayout(this);
m_nameLineEdit = WidgetsFactory::createLineEdit(this); m_nameLineEdit = WidgetsFactory::createLineEdit(this);
auto validator = new QRegularExpressionValidator(QRegularExpression(PathUtils::c_fileNameRegularExpression), auto validator = new QRegularExpressionValidator(QRegularExpression(PathUtils::c_fileNameRegularExpression),

View File

@ -2,12 +2,14 @@
#include <QVBoxLayout> #include <QVBoxLayout>
#include <QToolButton> #include <QToolButton>
#include <QLabel>
#include "treewidget.h" #include "treewidget.h"
#include "widgetsfactory.h" #include "widgetsfactory.h"
#include "titlebar.h" #include "titlebar.h"
#include <core/vnotex.h> #include <core/vnotex.h>
#include <core/thememgr.h>
#include <utils/iconutils.h> #include <utils/iconutils.h>
#include <utils/widgetutils.h> #include <utils/widgetutils.h>
@ -21,10 +23,20 @@ QIcon LocationList::s_folderIcon;
QIcon LocationList::s_notebookIcon; QIcon LocationList::s_notebookIcon;
QString LocationList::s_textHighlightForeground;
QString LocationList::s_textHighlightBackground;
LocationList::LocationList(QWidget *p_parent) LocationList::LocationList(QWidget *p_parent)
: QFrame(p_parent) : QFrame(p_parent)
{ {
setupUI(); setupUI();
if (s_textHighlightForeground.isEmpty()) {
const auto &themeMgr = VNoteX::getInst().getThemeMgr();
s_textHighlightForeground = themeMgr.paletteColor(QStringLiteral("widgets#locationlist#text_highlight#fg"));
s_textHighlightBackground = themeMgr.paletteColor(QStringLiteral("widgets#locationlist#text_highlight#bg"));
}
} }
void LocationList::setupUI() void LocationList::setupUI()
@ -120,7 +132,38 @@ void LocationList::setItemLocationLineAndText(QTreeWidgetItem *p_item, const Com
if (p_line.m_lineNumber != -1) { if (p_line.m_lineNumber != -1) {
p_item->setText(Columns::LineColumn, QString::number(p_line.m_lineNumber + 1)); p_item->setText(Columns::LineColumn, QString::number(p_line.m_lineNumber + 1));
} }
p_item->setText(Columns::TextColumn, p_line.m_text);
if (p_line.m_segments.isEmpty()) {
p_item->setText(Columns::TextColumn, p_line.m_text);
} else {
auto segments = p_line.m_segments;
std::sort(segments.begin(), segments.end());
// Use \n as a marker for < and use \r for >.
QString text(p_line.m_text);
int lastOffset = text.size();
for (int i = segments.size() - 1; i >= 0; --i) {
Q_ASSERT(segments[i].m_length > 0);
if (segments[i].m_offset + segments[i].m_length > lastOffset) {
// Interset.
continue;
}
lastOffset = segments[i].m_offset;
text.insert(segments[i].m_offset + segments[i].m_length, QStringLiteral("\n/span\r"));
text.insert(segments[i].m_offset,
QString("\nspan style='color:%1;background-color:%2'\r").arg(s_textHighlightForeground, s_textHighlightBackground));
}
text = text.toHtmlEscaped();
text.replace(QLatin1Char('\n'), QLatin1Char('<'));
text.replace(QLatin1Char('\r'), QLatin1Char('>'));
auto label = new QLabel(m_tree);
label->setTextFormat(Qt::RichText);
label->setText(text);
m_tree->setItemWidget(p_item, Columns::TextColumn, label);
}
} }
void LocationList::addLocation(const ComplexLocation &p_location) void LocationList::addLocation(const ComplexLocation &p_location)

View File

@ -68,6 +68,10 @@ namespace vnotex
static QIcon s_folderIcon; static QIcon s_folderIcon;
static QIcon s_notebookIcon; static QIcon s_notebookIcon;
static QString s_textHighlightForeground;
static QString s_textHighlightBackground;
}; };
} }