720e5aa25f
* Ran clang-format * Implement user-specific search in message history This functionality was originally requested in #1236. This commit changes the SearchPopup::performSearch method so that only messages from specific users can be shown. In order to filter for a specific user, enter their username with a leading '@' in the search popup. You can also add an additional search phrase which will also be considered in the search. * Naive implementation for "from:" tags Rebase later? * Cleverer (?) version using Predicates Commit adds two POC predicates: one for the author of messages, and one for substring search in messages. Problems/TODOs: * Best way to register new predicates? * Clean up tags (e.g. "from:") or not? * Test combinations of different predicates * Add a predicate to check for links in messages * Remove a dumb TODO * Rewrite SearchPopup::performSearch to be cleaner * Ran clang-format on all files * Remove TODO I missed earlier * Forgot to run clang-format peepoSadDank * Re-use {}-initialization Was accidentally removed when fixing earlier merge conflict. * Does this fix line endings? No diffs are shown locally, hopefully Git doesn't lie to me. * Rename "predicates" directory to "search" Resolving one conversation in the review of #1237. * Use LinkParser in LinkPredicate Resolving a conversation in the review of #1237. * Predicates: Use unique_ptr instead of shared_ptr Resolves a conversation in the review of #1237. * Refactor of SearchPopup and AuthorPredicate Resolving some points from the review in #1237. * Moved parsing of comma-seperated values into AuthorPredicate constructor. * Rewrite SearchPopup::parsePredicates as suggested. * Deleted now redundant methods in SearchPopup. * MessagePredicate::appliesTo now takes a Message& ... instead of a MessagePtr. This resolves a conversation in the review of #1237. * Run clang-format on two files I missed * AuthorPredicate: Check for displayName & loginName Resolving conversation on #1237.
184 lines
4.7 KiB
C++
184 lines
4.7 KiB
C++
#include "SearchPopup.hpp"
|
|
|
|
#include <QHBoxLayout>
|
|
#include <QLineEdit>
|
|
#include <QPushButton>
|
|
#include <QVBoxLayout>
|
|
|
|
#include "common/Channel.hpp"
|
|
#include "messages/Message.hpp"
|
|
#include "messages/search/AuthorPredicate.hpp"
|
|
#include "messages/search/LinkPredicate.hpp"
|
|
#include "messages/search/SubstringPredicate.hpp"
|
|
#include "widgets/helper/ChannelView.hpp"
|
|
|
|
namespace chatterino {
|
|
|
|
ChannelPtr SearchPopup::filter(const QString &text, const QString &channelName,
|
|
const LimitedQueueSnapshot<MessagePtr> &snapshot)
|
|
{
|
|
ChannelPtr channel(new Channel(channelName, Channel::Type::None));
|
|
|
|
// Parse predicates from tags in "text"
|
|
auto predicates = parsePredicates(text);
|
|
|
|
// Check for every message whether it fulfills all predicates that have
|
|
// been registered
|
|
for (size_t i = 0; i < snapshot.size(); ++i)
|
|
{
|
|
MessagePtr message = snapshot[i];
|
|
|
|
bool accept = true;
|
|
for (const auto &pred : predicates)
|
|
{
|
|
// Discard the message as soon as one predicate fails
|
|
if (!pred->appliesTo(*message))
|
|
{
|
|
accept = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If all predicates match, add the message to the channel
|
|
if (accept)
|
|
channel->addMessage(message);
|
|
}
|
|
|
|
return channel;
|
|
}
|
|
|
|
SearchPopup::SearchPopup()
|
|
{
|
|
this->initLayout();
|
|
this->resize(400, 600);
|
|
}
|
|
|
|
void SearchPopup::setChannel(const ChannelPtr &channel)
|
|
{
|
|
this->channelName_ = channel->getName();
|
|
this->snapshot_ = channel->getMessageSnapshot();
|
|
this->search();
|
|
|
|
this->updateWindowTitle();
|
|
}
|
|
|
|
void SearchPopup::updateWindowTitle()
|
|
{
|
|
this->setWindowTitle("Searching in " + this->channelName_ + "s history");
|
|
}
|
|
|
|
void SearchPopup::search()
|
|
{
|
|
this->channelView_->setChannel(filter(this->searchInput_->text(),
|
|
this->channelName_, this->snapshot_));
|
|
}
|
|
|
|
void SearchPopup::keyPressEvent(QKeyEvent *e)
|
|
{
|
|
if (e->key() == Qt::Key_Escape)
|
|
{
|
|
this->close();
|
|
return;
|
|
}
|
|
|
|
BaseWidget::keyPressEvent(e);
|
|
}
|
|
|
|
void SearchPopup::initLayout()
|
|
{
|
|
// VBOX
|
|
{
|
|
QVBoxLayout *layout1 = new QVBoxLayout(this);
|
|
layout1->setMargin(0);
|
|
layout1->setSpacing(0);
|
|
|
|
// HBOX
|
|
{
|
|
QHBoxLayout *layout2 = new QHBoxLayout(this);
|
|
layout2->setMargin(8);
|
|
layout2->setSpacing(8);
|
|
|
|
// SEARCH INPUT
|
|
{
|
|
this->searchInput_ = new QLineEdit(this);
|
|
layout2->addWidget(this->searchInput_);
|
|
QObject::connect(this->searchInput_, &QLineEdit::returnPressed,
|
|
[this] { this->search(); });
|
|
}
|
|
|
|
// SEARCH BUTTON
|
|
{
|
|
QPushButton *searchButton = new QPushButton(this);
|
|
searchButton->setText("Search");
|
|
layout2->addWidget(searchButton);
|
|
QObject::connect(searchButton, &QPushButton::clicked,
|
|
[this] { this->search(); });
|
|
}
|
|
|
|
layout1->addLayout(layout2);
|
|
}
|
|
|
|
// CHANNELVIEW
|
|
{
|
|
this->channelView_ = new ChannelView(this);
|
|
|
|
layout1->addWidget(this->channelView_);
|
|
}
|
|
|
|
this->setLayout(layout1);
|
|
}
|
|
}
|
|
|
|
std::vector<std::unique_ptr<MessagePredicate>> SearchPopup::parsePredicates(
|
|
const QString &input)
|
|
{
|
|
static QRegularExpression predicateRegex(R"(^(\w+):([\w,]+)$)");
|
|
|
|
auto predicates = std::vector<std::unique_ptr<MessagePredicate>>();
|
|
auto words = input.split(' ', QString::SkipEmptyParts);
|
|
auto authors = QStringList();
|
|
|
|
for (auto it = words.begin(); it != words.end();)
|
|
{
|
|
if (auto match = predicateRegex.match(*it); match.hasMatch())
|
|
{
|
|
QString name = match.captured(1);
|
|
QString value = match.captured(2);
|
|
|
|
bool remove = true;
|
|
|
|
// match predicates
|
|
if (name == "from")
|
|
{
|
|
authors.append(value);
|
|
}
|
|
else if (name == "has" && value == "link")
|
|
{
|
|
predicates.push_back(std::make_unique<LinkPredicate>());
|
|
}
|
|
else
|
|
{
|
|
remove = false;
|
|
}
|
|
|
|
// remove or advance
|
|
it = remove ? words.erase(it) : ++it;
|
|
}
|
|
else
|
|
{
|
|
++it;
|
|
}
|
|
}
|
|
|
|
if (!authors.empty())
|
|
predicates.push_back(std::make_unique<AuthorPredicate>(authors));
|
|
|
|
if (!words.empty())
|
|
predicates.push_back(
|
|
std::make_unique<SubstringPredicate>(words.join(" ")));
|
|
|
|
return predicates;
|
|
}
|
|
|
|
} // namespace chatterino
|