Improvements to Message Search (#1237)
* 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.
This commit is contained in:
@@ -7,29 +7,45 @@
|
||||
|
||||
#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 {
|
||||
namespace {
|
||||
ChannelPtr filter(const QString &text, const QString &channelName,
|
||||
const LimitedQueueSnapshot<MessagePtr> &snapshot)
|
||||
|
||||
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)
|
||||
{
|
||||
ChannelPtr channel(new Channel(channelName, Channel::Type::None));
|
||||
MessagePtr message = snapshot[i];
|
||||
|
||||
for (size_t i = 0; i < snapshot.size(); i++)
|
||||
bool accept = true;
|
||||
for (const auto &pred : predicates)
|
||||
{
|
||||
MessagePtr message = snapshot[i];
|
||||
|
||||
if (text.isEmpty() ||
|
||||
message->searchText.indexOf(text, 0, Qt::CaseInsensitive) != -1)
|
||||
// Discard the message as soon as one predicate fails
|
||||
if (!pred->appliesTo(*message))
|
||||
{
|
||||
channel->addMessage(message);
|
||||
accept = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return channel;
|
||||
// If all predicates match, add the message to the channel
|
||||
if (accept)
|
||||
channel->addMessage(message);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
return channel;
|
||||
}
|
||||
|
||||
SearchPopup::SearchPopup()
|
||||
{
|
||||
@@ -113,4 +129,55 @@ void SearchPopup::initLayout()
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user