feat: add global channel search support (#3694)

Co-authored-by: Rasmus Karlsson <rasmus.karlsson@pajlada.com>
This commit is contained in:
James Upjohn 2022-05-23 12:47:16 +12:00 committed by GitHub
parent e11677c62b
commit 57783c7478
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 135 additions and 36 deletions

View file

@ -2,6 +2,7 @@
## Unversioned ## Unversioned
- Major: Added multi-channel searching to search dialog via keyboard shortcut. [Ctrl+Shift+F by default] (#3694)
- Minor: Added `is:first-msg` search option. (#3700) - Minor: Added `is:first-msg` search option. (#3700)
- Minor: Added quotation marks in the permitted/blocked Automod messages for clarity. (#3654) - Minor: Added quotation marks in the permitted/blocked Automod messages for clarity. (#3654)
- Minor: Adjust large stream thumbnail to 16:9 (#3655) - Minor: Adjust large stream thumbnail to 16:9 (#3655)

View file

@ -103,7 +103,8 @@ inline const std::map<HotkeyCategory, ActionDefinitionMap> actionNames{
0, 0,
1, 1,
}}, }},
{"showSearch", ActionDefinition{"Search"}}, {"showSearch", ActionDefinition{"Search current channel"}},
{"showGlobalSearch", ActionDefinition{"Search all channels"}},
{"startWatching", ActionDefinition{"Start watching"}}, {"startWatching", ActionDefinition{"Start watching"}},
{"debug", ActionDefinition{"Show debug popup"}}, {"debug", ActionDefinition{"Show debug popup"}},
}}, }},

View file

@ -339,6 +339,9 @@ void HotkeyController::addDefaults(std::set<QString> &addedHotkeys)
this->tryAddDefault(addedHotkeys, HotkeyCategory::Split, this->tryAddDefault(addedHotkeys, HotkeyCategory::Split,
QKeySequence("Ctrl+F"), "showSearch", QKeySequence("Ctrl+F"), "showSearch",
std::vector<QString>(), "show search"); std::vector<QString>(), "show search");
this->tryAddDefault(addedHotkeys, HotkeyCategory::Split,
QKeySequence("Ctrl+Shift+F"), "showGlobalSearch",
std::vector<QString>(), "show global search");
this->tryAddDefault(addedHotkeys, HotkeyCategory::Split, this->tryAddDefault(addedHotkeys, HotkeyCategory::Split,
QKeySequence("Ctrl+F5"), "reconnect", QKeySequence("Ctrl+F5"), "reconnect",
std::vector<QString>(), "reconnect"); std::vector<QString>(), "reconnect");

View file

@ -81,6 +81,8 @@ public:
void pause(PauseReason reason, boost::optional<uint> msecs = boost::none); void pause(PauseReason reason, boost::optional<uint> msecs = boost::none);
void unpause(PauseReason reason); void unpause(PauseReason reason);
MessageElementFlags getFlags() const;
ChannelPtr channel(); ChannelPtr channel();
void setChannel(ChannelPtr channel_); void setChannel(ChannelPtr channel_);
@ -158,7 +160,6 @@ private:
void drawMessages(QPainter &painter); void drawMessages(QPainter &painter);
void setSelection(const SelectionItem &start, const SelectionItem &end); void setSelection(const SelectionItem &start, const SelectionItem &end);
MessageElementFlags getFlags() const;
void selectWholeMessage(MessageLayout *layout, int &messageIndex); void selectWholeMessage(MessageLayout *layout, int &messageIndex);
void getWordBounds(MessageLayout *layout, void getWordBounds(MessageLayout *layout,
const MessageLayoutElement *element, const MessageLayoutElement *element,

View file

@ -3,11 +3,9 @@
#include <QHBoxLayout> #include <QHBoxLayout>
#include <QLineEdit> #include <QLineEdit>
#include <QPushButton> #include <QPushButton>
#include <QVBoxLayout>
#include "common/Channel.hpp" #include "common/Channel.hpp"
#include "controllers/hotkeys/HotkeyController.hpp" #include "controllers/hotkeys/HotkeyController.hpp"
#include "messages/Message.hpp"
#include "messages/search/AuthorPredicate.hpp" #include "messages/search/AuthorPredicate.hpp"
#include "messages/search/ChannelPredicate.hpp" #include "messages/search/ChannelPredicate.hpp"
#include "messages/search/LinkPredicate.hpp" #include "messages/search/LinkPredicate.hpp"
@ -19,8 +17,7 @@
namespace chatterino { namespace chatterino {
ChannelPtr SearchPopup::filter(const QString &text, const QString &channelName, ChannelPtr SearchPopup::filter(const QString &text, const QString &channelName,
const LimitedQueueSnapshot<MessagePtr> &snapshot, const LimitedQueueSnapshot<MessagePtr> &snapshot)
FilterSetPtr filterSet)
{ {
ChannelPtr channel(new Channel(channelName, Channel::Type::None)); ChannelPtr channel(new Channel(channelName, Channel::Type::None));
@ -44,9 +41,6 @@ ChannelPtr SearchPopup::filter(const QString &text, const QString &channelName,
} }
} }
if (accept && filterSet)
accept = filterSet->filter(message, channel);
// If all predicates match, add the message to the channel // If all predicates match, add the message to the channel
if (accept) if (accept)
channel->addMessage(message); channel->addMessage(message);
@ -67,13 +61,13 @@ void SearchPopup::addShortcuts()
{ {
HotkeyController::HotkeyMap actions{ HotkeyController::HotkeyMap actions{
{"search", {"search",
[this](std::vector<QString>) -> QString { [this](const std::vector<QString> &) -> QString {
this->searchInput_->setFocus(); this->searchInput_->setFocus();
this->searchInput_->selectAll(); this->searchInput_->selectAll();
return ""; return "";
}}, }},
{"delete", {"delete",
[this](std::vector<QString>) -> QString { [this](const std::vector<QString> &) -> QString {
this->close(); this->close();
return ""; return "";
}}, }},
@ -88,17 +82,25 @@ void SearchPopup::addShortcuts()
HotkeyCategory::PopupWindow, actions, this); HotkeyCategory::PopupWindow, actions, this);
} }
void SearchPopup::setChannelFilters(FilterSetPtr filters) void SearchPopup::addChannel(ChannelView &channel)
{ {
this->channelFilters_ = std::move(filters); if (this->searchChannels_.empty())
} {
this->channelView_->setSourceChannel(channel.channel());
this->channelName_ = channel.channel()->getName();
}
else if (this->searchChannels_.size() == 1)
{
this->channelView_->setSourceChannel(
std::make_shared<Channel>("multichannel", Channel::Type::None));
void SearchPopup::setChannel(const ChannelPtr &channel) auto flags = this->channelView_->getFlags();
{ flags.set(MessageElementFlag::ChannelName);
this->channelView_->setSourceChannel(channel); flags.unset(MessageElementFlag::ModeratorTools);
this->channelName_ = channel->getName(); this->channelView_->setOverrideFlags(flags);
this->snapshot_ = channel->getMessageSnapshot(); }
this->search();
this->searchChannels_.append(std::ref(channel));
this->updateWindowTitle(); this->updateWindowTitle();
} }
@ -115,6 +117,10 @@ void SearchPopup::updateWindowTitle()
{ {
historyName = "mentions"; historyName = "mentions";
} }
else if (this->searchChannels_.size() > 1)
{
historyName = "multiple channels'";
}
else if (this->channelName_.isEmpty()) else if (this->channelName_.isEmpty())
{ {
historyName = "<empty>'s"; historyName = "<empty>'s";
@ -126,24 +132,90 @@ void SearchPopup::updateWindowTitle()
this->setWindowTitle("Searching in " + historyName + " history"); this->setWindowTitle("Searching in " + historyName + " history");
} }
void SearchPopup::showEvent(QShowEvent *)
{
this->search();
}
void SearchPopup::search() void SearchPopup::search()
{ {
if (this->snapshot_.size() == 0)
{
this->snapshot_ = this->buildSnapshot();
}
this->channelView_->setChannel(filter(this->searchInput_->text(), this->channelView_->setChannel(filter(this->searchInput_->text(),
this->channelName_, this->snapshot_, this->channelName_, this->snapshot_));
this->channelFilters_)); }
LimitedQueueSnapshot<MessagePtr> SearchPopup::buildSnapshot()
{
// no point in filtering/sorting if it's a single channel search
if (this->searchChannels_.length() == 1)
{
const auto channelPtr = this->searchChannels_.at(0);
return channelPtr.get().channel()->getMessageSnapshot();
}
auto combinedSnapshot = std::vector<std::shared_ptr<const Message>>{};
for (auto &channel : this->searchChannels_)
{
ChannelView &sharedView = channel.get();
const FilterSetPtr filterSet = sharedView.getFilterSet();
const LimitedQueueSnapshot<MessagePtr> &snapshot =
sharedView.channel()->getMessageSnapshot();
// TODO: implement iterator on LimitedQueueSnapshot?
for (auto i = 0; i < snapshot.size(); ++i)
{
const MessagePtr &message = snapshot[i];
if (filterSet && !filterSet->filter(message, sharedView.channel()))
{
continue;
}
combinedSnapshot.push_back(message);
}
}
// remove any duplicate messages from splits containing the same channel
std::sort(combinedSnapshot.begin(), combinedSnapshot.end(),
[](MessagePtr &a, MessagePtr &b) {
return a->id > b->id;
});
auto uniqueIterator =
std::unique(combinedSnapshot.begin(), combinedSnapshot.end(),
[](MessagePtr &a, MessagePtr &b) {
return a->id == b->id;
});
combinedSnapshot.erase(uniqueIterator, combinedSnapshot.end());
// resort by time for presentation
std::sort(combinedSnapshot.begin(), combinedSnapshot.end(),
[](MessagePtr &a, MessagePtr &b) {
return a->serverReceivedTime < b->serverReceivedTime;
});
auto queue = LimitedQueue<MessagePtr>(combinedSnapshot.size());
queue.pushFront(combinedSnapshot);
return queue.getSnapshot();
} }
void SearchPopup::initLayout() void SearchPopup::initLayout()
{ {
// VBOX // VBOX
{ {
QVBoxLayout *layout1 = new QVBoxLayout(this); auto *layout1 = new QVBoxLayout(this);
layout1->setMargin(0); layout1->setMargin(0);
layout1->setSpacing(0); layout1->setSpacing(0);
// HBOX // HBOX
{ {
QHBoxLayout *layout2 = new QHBoxLayout(this); auto *layout2 = new QHBoxLayout(this);
layout2->setMargin(8); layout2->setMargin(8);
layout2->setSpacing(8); layout2->setSpacing(8);

View file

@ -17,16 +17,17 @@ class SearchPopup : public BasePopup
public: public:
SearchPopup(QWidget *parent); SearchPopup(QWidget *parent);
virtual void setChannel(const ChannelPtr &channel); virtual void addChannel(ChannelView &channel);
virtual void setChannelFilters(FilterSetPtr filters);
protected: protected:
virtual void updateWindowTitle(); virtual void updateWindowTitle();
void showEvent(QShowEvent *event) override;
private: private:
void initLayout(); void initLayout();
void search(); void search();
void addShortcuts() override; void addShortcuts() override;
LimitedQueueSnapshot<MessagePtr> buildSnapshot();
/** /**
* @brief Only retains those message from a list of messages that satisfy a * @brief Only retains those message from a list of messages that satisfy a
@ -41,8 +42,7 @@ private:
* "snapshot" * "snapshot"
*/ */
static ChannelPtr filter(const QString &text, const QString &channelName, static ChannelPtr filter(const QString &text, const QString &channelName,
const LimitedQueueSnapshot<MessagePtr> &snapshot, const LimitedQueueSnapshot<MessagePtr> &snapshot);
FilterSetPtr filterSet);
/** /**
* @brief Checks the input for tags and registers their corresponding * @brief Checks the input for tags and registers their corresponding
@ -58,7 +58,7 @@ private:
QLineEdit *searchInput_{}; QLineEdit *searchInput_{};
ChannelView *channelView_{}; ChannelView *channelView_{};
QString channelName_{}; QString channelName_{};
FilterSetPtr channelFilters_; QList<std::reference_wrapper<ChannelView>> searchChannels_;
}; };
} // namespace chatterino } // namespace chatterino

View file

@ -273,7 +273,12 @@ void Split::addShortcuts()
}}, }},
{"showSearch", {"showSearch",
[this](std::vector<QString>) -> QString { [this](std::vector<QString>) -> QString {
this->showSearch(); this->showSearch(true);
return "";
}},
{"showGlobalSearch",
[this](std::vector<QString>) -> QString {
this->showSearch(false);
return ""; return "";
}}, }},
{"reconnect", {"reconnect",
@ -1155,13 +1160,29 @@ const QList<QUuid> Split::getFilters() const
return this->view_->getFilterIds(); return this->view_->getFilterIds();
} }
void Split::showSearch() void Split::showSearch(bool singleChannel)
{ {
SearchPopup *popup = new SearchPopup(this); auto *popup = new SearchPopup(this);
popup->setChannelFilters(this->view_->getFilterSet());
popup->setAttribute(Qt::WA_DeleteOnClose); popup->setAttribute(Qt::WA_DeleteOnClose);
popup->setChannel(this->getChannel());
if (singleChannel)
{
popup->addChannel(this->getChannelView());
popup->show();
return;
}
// Pass every ChannelView for every Split across the app to the search popup
auto &notebook = getApp()->windows->getMainWindow().getNotebook();
for (int i = 0; i < notebook.getPageCount(); ++i)
{
auto container = dynamic_cast<SplitContainer *>(notebook.getPageAt(i));
for (auto split : container->getSplits())
{
popup->addChannel(split->getChannelView());
}
}
popup->show(); popup->show();
} }

View file

@ -171,7 +171,7 @@ public slots:
void copyToClipboard(); void copyToClipboard();
void startWatching(); void startWatching();
void setFiltersDialog(); void setFiltersDialog();
void showSearch(); void showSearch(bool singleChannel);
void showViewerList(); void showViewerList();
void openSubPage(); void openSubPage();
void reloadChannelAndSubscriberEmotes(); void reloadChannelAndSubscriberEmotes();