Add new search predicate to enable searching for messages matching a regex (#3282)

Co-authored-by: pajlada <rasmus.karlsson@pajlada.com>
This commit is contained in:
LosFarmosCTL 2021-10-17 14:36:44 +02:00 committed by GitHub
parent c1a3814b7c
commit 06245f3713
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 109 additions and 39 deletions

View file

@ -2,6 +2,7 @@
## Unversioned
- Minor: Added new search predicate to filter for messages matching a regex (#3282)
- Minor: Add `{channel.name}`, `{channel.id}`, `{stream.game}`, `{stream.title}`, `{my.id}`, `{my.name}` placeholders for commands (#3155)
- Minor: Remove TwitchEmotes.com attribution and the open/copy options when right-clicking a Twitch Emote. (#2214, #3136)
- Minor: Strip leading @ and trailing , from username in /user and /usercard commands. (#3143)

View file

@ -185,6 +185,7 @@ SOURCES += \
src/messages/search/ChannelPredicate.cpp \
src/messages/search/LinkPredicate.cpp \
src/messages/search/MessageFlagsPredicate.cpp \
src/messages/search/RegexPredicate.cpp \
src/messages/search/SubstringPredicate.cpp \
src/messages/SharedMessageBuilder.cpp \
src/providers/bttv/BttvEmotes.cpp \

View file

@ -149,6 +149,8 @@ set(SOURCE_FILES
messages/search/LinkPredicate.hpp
messages/search/MessageFlagsPredicate.cpp
messages/search/MessageFlagsPredicate.hpp
messages/search/RegexPredicate.cpp
messages/search/RegexPredicate.hpp
messages/search/SubstringPredicate.cpp
messages/search/SubstringPredicate.hpp

View file

@ -0,0 +1,22 @@
#include "RegexPredicate.hpp"
namespace chatterino {
RegexPredicate::RegexPredicate(const QString &regex)
: regex_(regex, QRegularExpression::CaseInsensitiveOption)
{
}
bool RegexPredicate::appliesTo(const Message &message)
{
if (!regex_.isValid())
{
return false;
}
QRegularExpressionMatch match = regex_.match(message.messageText);
return match.hasMatch();
}
} // namespace chatterino

View file

@ -0,0 +1,42 @@
#pragma once
#include "QRegularExpression"
#include "messages/search/MessagePredicate.hpp"
namespace chatterino {
/**
* @brief MessagePredicate checking whether the message matches a given regex.
*
* This predicate will only allow messages whose `messageText` match the given
* regex.
*/
class RegexPredicate : public MessagePredicate
{
public:
/**
* @brief Create a RegexPredicate with a regex to match the message against.
*
* The message is being matched case-insensitively.
*
* @param regex the regex to match the message against
*/
RegexPredicate(const QString &regex);
/**
* @brief Checks whether the message matches the regex passed in the
* constructor
*
* The check is done case-insensitively.
*
* @param message the message to check
* @return true if the message matches the regex, false otherwise
*/
bool appliesTo(const Message &message);
private:
/// Holds the regular expression to match the message against
QRegularExpression regex_;
};
} // namespace chatterino

View file

@ -11,6 +11,7 @@
#include "messages/search/ChannelPredicate.hpp"
#include "messages/search/LinkPredicate.hpp"
#include "messages/search/MessageFlagsPredicate.hpp"
#include "messages/search/RegexPredicate.hpp"
#include "messages/search/SubstringPredicate.hpp"
#include "util/Shortcut.hpp"
#include "widgets/helper/ChannelView.hpp"
@ -164,51 +165,56 @@ void SearchPopup::initLayout()
std::vector<std::unique_ptr<MessagePredicate>> SearchPopup::parsePredicates(
const QString &input)
{
static QRegularExpression predicateRegex(R"(^(\w+):([\w,]+)$)");
// This regex captures all name:value predicate pairs into named capturing
// groups and matches all other inputs seperated by spaces as normal
// strings.
// It also ignores whitespaces in values when being surrounded by quotation
// marks, to enable inputs like this => regex:"kappa 123"
static QRegularExpression predicateRegex(
R"lit((?:(?<name>\w+):(?<value>".+?"|[^\s]+))|[^\s]+?(?=$|\s))lit");
static QRegularExpression trimQuotationMarksRegex(R"(^"|"$)");
QRegularExpressionMatchIterator it = predicateRegex.globalMatch(input);
std::vector<std::unique_ptr<MessagePredicate>> predicates;
auto words = input.split(' ', QString::SkipEmptyParts);
QStringList authors;
QStringList channels;
for (auto it = words.begin(); it != words.end();)
while (it.hasNext())
{
if (auto match = predicateRegex.match(*it); match.hasMatch())
QRegularExpressionMatch match = it.next();
QString name = match.captured("name");
QString value = match.captured("value");
value.remove(trimQuotationMarksRegex);
// match predicates
if (name == "from")
{
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 if (name == "in")
{
channels.append(value);
}
else if (name == "is")
{
predicates.push_back(
std::make_unique<MessageFlagsPredicate>(value));
}
else
{
remove = false;
}
// remove or advance
it = remove ? words.erase(it) : ++it;
authors.append(value);
}
else if (name == "has" && value == "link")
{
predicates.push_back(std::make_unique<LinkPredicate>());
}
else if (name == "in")
{
channels.append(value);
}
else if (name == "is")
{
predicates.push_back(
std::make_unique<MessageFlagsPredicate>(value));
}
else if (name == "regex")
{
predicates.push_back(std::make_unique<RegexPredicate>(value));
}
else
{
++it;
predicates.push_back(
std::make_unique<SubstringPredicate>(match.captured()));
}
}
@ -218,10 +224,6 @@ std::vector<std::unique_ptr<MessagePredicate>> SearchPopup::parsePredicates(
if (!channels.empty())
predicates.push_back(std::make_unique<ChannelPredicate>(channels));
if (!words.empty())
predicates.push_back(
std::make_unique<SubstringPredicate>(words.join(" ")));
return predicates;
}