From ef69ee435f544cbd64f6f0f7b071be1fb899438a Mon Sep 17 00:00:00 2001 From: Le Tan Date: Wed, 21 Jul 2021 20:54:58 +0800 Subject: [PATCH] LocationList: highlight text segments --- libs/vtextedit | 2 +- src/core/global.h | 24 +++++++++++ src/core/location.h | 13 ++++-- src/data/extra/themes/moonlight/palette.json | 4 ++ src/data/extra/themes/native/palette.json | 16 +++++-- src/data/extra/themes/pure/palette.json | 4 ++ src/search/filesearchengine.cpp | 9 ++-- src/search/searcher.cpp | 17 ++++---- src/search/searchresultitem.cpp | 28 +++++++++--- src/search/searchresultitem.h | 14 ++++-- src/search/searchtoken.cpp | 38 ++++++++++++++--- src/search/searchtoken.h | 5 +-- src/widgets/dialogs/snippetinfowidget.cpp | 2 +- src/widgets/locationlist.cpp | 45 +++++++++++++++++++- src/widgets/locationlist.h | 4 ++ 15 files changed, 184 insertions(+), 41 deletions(-) diff --git a/libs/vtextedit b/libs/vtextedit index 76a963c0..a15d8591 160000 --- a/libs/vtextedit +++ b/libs/vtextedit @@ -1 +1 @@ -Subproject commit 76a963c0e2ac488001872ac6f324ba1c8b177bfd +Subproject commit a15d859141506142a11cbf843a2d93124ea65bfa diff --git a/src/core/global.h b/src/core/global.h index 306e3a91..967fb3a5 100644 --- a/src/core/global.h +++ b/src/core/global.h @@ -109,6 +109,30 @@ namespace vnotex 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 Q_DECLARE_OPERATORS_FOR_FLAGS(vnotex::FindOptions); diff --git a/src/core/location.h b/src/core/location.h index b4976e05..1ccba00c 100644 --- a/src/core/location.h +++ b/src/core/location.h @@ -3,6 +3,8 @@ #include +#include "global.h" + namespace vnotex { struct Location @@ -37,9 +39,10 @@ namespace vnotex { Line() = default; - Line(int p_lineNumber, const QString &p_text) + Line(int p_lineNumber, const QString &p_text, const QVector &p_segments) : 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; QString m_text; + + QVector m_segments; }; - void addLine(int p_lineNumber, const QString &p_text) + void addLine(int p_lineNumber, const QString &p_text, const QVector &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) diff --git a/src/data/extra/themes/moonlight/palette.json b/src/data/extra/themes/moonlight/palette.json index 9cadfe0c..b4889768 100644 --- a/src/data/extra/themes/moonlight/palette.json +++ b/src/data/extra/themes/moonlight/palette.json @@ -236,6 +236,10 @@ "locationlist" : { "node_icon" : { "fg" : "@base#icon#fg" + }, + "text_highlight" : { + "fg" : "@base#master#fg", + "bg" : "@base#master#bg" } }, "viewsplit" : { diff --git a/src/data/extra/themes/native/palette.json b/src/data/extra/themes/native/palette.json index e9f2bc96..796d683f 100644 --- a/src/data/extra/themes/native/palette.json +++ b/src/data/extra/themes/native/palette.json @@ -44,6 +44,10 @@ "danger" : { "fg": "@base#danger#fg" } + }, + "master" : { + "fg" : "#fbffff", + "bg" : "#535c65" } }, "widgets" : { @@ -53,8 +57,8 @@ "button": { "fg" : "@base#normal#fg", "active" : { - "fg" : "#fbffff", - "bg" : "#535c65" + "fg" : "@base#master#fg", + "bg" : "@base#master#bg" } } } @@ -95,6 +99,10 @@ "locationlist" : { "node_icon" : { "fg" : "@base#icon#fg" + }, + "text_highlight" : { + "fg" : "@base#master#fg", + "bg" : "@base#master#bg" } }, "viewsplit" : { @@ -123,8 +131,8 @@ }, "quickselector" : { "item_icon" : { - "fg" : "#535c65", - "border" : "#535c65" + "fg" : "@base#master#bg", + "border" : "@base#master#bg" } } } diff --git a/src/data/extra/themes/pure/palette.json b/src/data/extra/themes/pure/palette.json index 89363361..ef909c64 100644 --- a/src/data/extra/themes/pure/palette.json +++ b/src/data/extra/themes/pure/palette.json @@ -232,6 +232,10 @@ "locationlist" : { "node_icon" : { "fg" : "@base#icon#fg" + }, + "text_highlight" : { + "fg" : "@base#master#fg", + "bg" : "@base#master#bg" } }, "viewsplit" : { diff --git a/src/search/filesearchengine.cpp b/src/search/filesearchengine.cpp index b2ad8f63..9df01888 100644 --- a/src/search/filesearchengine.cpp +++ b/src/search/filesearchengine.cpp @@ -97,17 +97,18 @@ void FileSearchEngineWorker::searchFile(const QString &p_filePath, const QString const auto lineText = ins.readLine(); bool matched = false; + QVector segments; if (!shouldStartBatchMode) { - matched = m_token.matched(lineText); + matched = m_token.matched(lineText, &segments); } else { - matched = m_token.matchedInBatchMode(lineText); + matched = m_token.matchedInBatchMode(lineText, &segments); } if (matched) { if (resultItem) { - resultItem->addLine(lineNum, lineText); + resultItem->addLine(lineNum, lineText, segments); } else { - resultItem = SearchResultItem::createFileItem(p_filePath, p_displayPath, lineNum, lineText); + resultItem = SearchResultItem::createFileItem(p_filePath, p_displayPath, lineNum, lineText, segments); } } diff --git a/src/search/searcher.cpp b/src/search/searcher.cpp index 35f0148a..3539ef4f 100644 --- a/src/search/searcher.cpp +++ b/src/search/searcher.cpp @@ -207,13 +207,13 @@ bool Searcher::firstPhaseSearch(const File *p_file) if (testObject(SearchObject::SearchName)) { if (isTokenMatched(name)) { - emit resultItemAdded(SearchResultItem::createBufferItem(filePath, relativePath, -1, name)); + emit resultItemAdded(SearchResultItem::createBufferItem(filePath, relativePath)); } } if (testObject(SearchObject::SearchPath)) { 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) { QString lineText = content.mid(pos, idx - pos); bool matched = false; + QVector segments; if (!shouldStartBatchMode) { - matched = m_token.matched(lineText); + matched = m_token.matched(lineText, &segments); } else { - matched = m_token.matchedInBatchMode(lineText); + matched = m_token.matchedInBatchMode(lineText, &segments); } if (matched) { if (resultItem) { - resultItem->addLine(lineNum, lineText); + resultItem->addLine(lineNum, lineText, segments); } 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 &p_ if (testObject(SearchObject::SearchName)) { if (isTokenMatched(name)) { - emit resultItemAdded(SearchResultItem::createFileItem(filePath, relativePath, -1, name)); + emit resultItemAdded(SearchResultItem::createFileItem(filePath, relativePath)); } } if (testObject(SearchObject::SearchPath)) { if (isTokenMatched(relativePath)) { - emit resultItemAdded(SearchResultItem::createFileItem(filePath, relativePath, -1, name)); + emit resultItemAdded(SearchResultItem::createFileItem(filePath, relativePath)); } } diff --git a/src/search/searchresultitem.cpp b/src/search/searchresultitem.cpp index 1d374e9c..fbb0dfdc 100644 --- a/src/search/searchresultitem.cpp +++ b/src/search/searchresultitem.cpp @@ -5,26 +5,42 @@ using namespace vnotex; QSharedPointer SearchResultItem::createBufferItem(const QString &p_targetPath, const QString &p_displayPath, int p_lineNumber, - const QString &p_text) + const QString &p_text, + const QVector &p_segments) +{ + auto item = createBufferItem(p_targetPath, p_displayPath); + item->m_location.addLine(p_lineNumber, p_text, p_segments); + return item; +} + +QSharedPointer SearchResultItem::createBufferItem(const QString &p_targetPath, + const QString &p_displayPath) { auto item = QSharedPointer::create(); item->m_location.m_type = LocationType::Buffer; item->m_location.m_path = p_targetPath; item->m_location.m_displayPath = p_displayPath; - item->m_location.addLine(p_lineNumber, p_text); return item; } QSharedPointer SearchResultItem::createFileItem(const QString &p_targetPath, const QString &p_displayPath, int p_lineNumber, - const QString &p_text) + const QString &p_text, + const QVector &p_segments) +{ + auto item = createFileItem(p_targetPath, p_displayPath); + item->m_location.addLine(p_lineNumber, p_text, p_segments); + return item; +} + +QSharedPointer SearchResultItem::createFileItem(const QString &p_targetPath, + const QString &p_displayPath) { auto item = QSharedPointer::create(); item->m_location.m_type = LocationType::File; item->m_location.m_path = p_targetPath; item->m_location.m_displayPath = p_displayPath; - item->m_location.addLine(p_lineNumber, p_text); return item; } @@ -48,7 +64,7 @@ QSharedPointer SearchResultItem::createNotebookItem(const QStr return item; } -void SearchResultItem::addLine(int p_lineNumber, const QString &p_text) +void SearchResultItem::addLine(int p_lineNumber, const QString &p_text, const QVector &p_segments) { - m_location.addLine(p_lineNumber, p_text); + m_location.addLine(p_lineNumber, p_text, p_segments); } diff --git a/src/search/searchresultitem.h b/src/search/searchresultitem.h index ec804eb6..abe60e1f 100644 --- a/src/search/searchresultitem.h +++ b/src/search/searchresultitem.h @@ -17,17 +17,25 @@ namespace vnotex return p_dbg; } - void addLine(int p_lineNumber, const QString &p_text); + void addLine(int p_lineNumber, const QString &p_text, const QVector &p_segments); static QSharedPointer createBufferItem(const QString &p_targetPath, const QString &p_displayPath, int p_lineNumber, - const QString &p_text); + const QString &p_text, + const QVector &p_segments); + + static QSharedPointer createBufferItem(const QString &p_targetPath, + const QString &p_displayPath); static QSharedPointer createFileItem(const QString &p_targetPath, const QString &p_displayPath, int p_lineNumber, - const QString &p_text); + const QString &p_text, + const QVector &p_segments); + + static QSharedPointer createFileItem(const QString &p_targetPath, + const QString &p_displayPath); static QSharedPointer createFolderItem(const QString &p_targetPath, const QString &p_displayPath); diff --git a/src/search/searchtoken.cpp b/src/search/searchtoken.cpp index 4cf576bf..d3900946 100644 --- a/src/search/searchtoken.cpp +++ b/src/search/searchtoken.cpp @@ -31,7 +31,7 @@ void SearchToken::append(const QRegularExpression &p_regExp) m_regularExpressions.append(p_regExp); } -bool SearchToken::matched(const QString &p_text) const +bool SearchToken::matched(const QString &p_text, QVector *p_segments) const { const int consSize = constraintSize(); if (consSize == 0) { @@ -42,9 +42,22 @@ bool SearchToken::matched(const QString &p_text) const for (int i = 0; i < consSize; ++i) { bool consMatched = false; 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 { - 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) { @@ -77,7 +90,7 @@ void SearchToken::startBatchMode() m_matchedConstraintsCountInBatchMode = 0; } -bool SearchToken::matchedInBatchMode(const QString &p_text) +bool SearchToken::matchedInBatchMode(const QString &p_text, QVector *p_segments) { bool isMatched = false; const int consSize = m_matchedConstraintsInBatchMode.size(); @@ -88,9 +101,22 @@ bool SearchToken::matchedInBatchMode(const QString &p_text) bool consMatched = false; 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 { - 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) { diff --git a/src/search/searchtoken.h b/src/search/searchtoken.h index d6dd3e02..c276067e 100644 --- a/src/search/searchtoken.h +++ b/src/search/searchtoken.h @@ -5,7 +5,6 @@ #include #include #include -#include #include #include @@ -36,7 +35,7 @@ namespace vnotex void append(const QRegularExpression &p_regExp); // Whether @p_text is matched. - bool matched(const QString &p_text) const; + bool matched(const QString &p_text, QVector *p_segments = nullptr) const; int constraintSize() const; @@ -49,7 +48,7 @@ namespace vnotex // Match one string in batch mode. // Return true if @p_text is matched. - bool matchedInBatchMode(const QString &p_text); + bool matchedInBatchMode(const QString &p_text, QVector *p_segments = nullptr); bool readyToEndBatchMode() const; diff --git a/src/widgets/dialogs/snippetinfowidget.cpp b/src/widgets/dialogs/snippetinfowidget.cpp index 9192b5af..08df5f48 100644 --- a/src/widgets/dialogs/snippetinfowidget.cpp +++ b/src/widgets/dialogs/snippetinfowidget.cpp @@ -32,7 +32,7 @@ SnippetInfoWidget::SnippetInfoWidget(const Snippet *p_snippet, QWidget *p_parent void SnippetInfoWidget::setupUI() { - auto mainLayout = new QFormLayout(this); + auto mainLayout = WidgetsFactory::createFormLayout(this); m_nameLineEdit = WidgetsFactory::createLineEdit(this); auto validator = new QRegularExpressionValidator(QRegularExpression(PathUtils::c_fileNameRegularExpression), diff --git a/src/widgets/locationlist.cpp b/src/widgets/locationlist.cpp index f4fef7c9..4b255d20 100644 --- a/src/widgets/locationlist.cpp +++ b/src/widgets/locationlist.cpp @@ -2,12 +2,14 @@ #include #include +#include #include "treewidget.h" #include "widgetsfactory.h" #include "titlebar.h" #include +#include #include #include @@ -21,10 +23,20 @@ QIcon LocationList::s_folderIcon; QIcon LocationList::s_notebookIcon; +QString LocationList::s_textHighlightForeground; + +QString LocationList::s_textHighlightBackground; + LocationList::LocationList(QWidget *p_parent) : QFrame(p_parent) { 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() @@ -120,7 +132,38 @@ void LocationList::setItemLocationLineAndText(QTreeWidgetItem *p_item, const Com if (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) diff --git a/src/widgets/locationlist.h b/src/widgets/locationlist.h index b987bf98..bc2a9e17 100644 --- a/src/widgets/locationlist.h +++ b/src/widgets/locationlist.h @@ -68,6 +68,10 @@ namespace vnotex static QIcon s_folderIcon; static QIcon s_notebookIcon; + + static QString s_textHighlightForeground; + + static QString s_textHighlightBackground; }; }