From 1c9090d350d4d60d4946503ef93d21231b250de8 Mon Sep 17 00:00:00 2001 From: droidicus Date: Sun, 10 Aug 2025 05:21:52 -0400 Subject: [PATCH] Add emote completion widget filter for only zero-width emotes using :~ (#6362) Co-authored-by: Rasmus Karlsson --- CHANGELOG.md | 1 + src/common/QLogging.cpp | 1 + src/common/QLogging.hpp | 1 + .../strategies/ClassicEmoteStrategy.cpp | 25 ++++++++++++- .../strategies/SmartEmoteStrategy.cpp | 37 +++++++++++++++++-- 5 files changed, 60 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bdf53fa3..f60e4119 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ - Minor: Message character length label is now monospaced. (#6366) - Minor: Add a setting under Moderation -> Logs to customize the timestamp used for chat logs. (#6338) - Minor: Add a setting under Moderation -> Logs to use server timestamp from the message instead of the local clock time for logging. (#6346) +- Minor: Add feature to search for only zero-width emotes when prepended by `:~`. (#6362) - Bugfix: Commands are no longer tab-completable in the middle of messages. (#6273) - Bugfix: Automatic streamer mode detection now works from Flatpak. (#6250) - Bugfix: Don't create native messaging manifest file if browser directory doesn't exist. (#6116) diff --git a/src/common/QLogging.cpp b/src/common/QLogging.cpp index 2feb4118..7f7d66f9 100644 --- a/src/common/QLogging.cpp +++ b/src/common/QLogging.cpp @@ -13,6 +13,7 @@ Q_LOGGING_CATEGORY(chatterinoBttv, "chatterino.bttv", logThreshold); Q_LOGGING_CATEGORY(chatterinoCache, "chatterino.cache", logThreshold); Q_LOGGING_CATEGORY(chatterinoCommands, "chatterino.commands", logThreshold); Q_LOGGING_CATEGORY(chatterinoCommon, "chatterino.common", logThreshold); +Q_LOGGING_CATEGORY(chatterinoCompletion, "chatterino.completion", logThreshold); Q_LOGGING_CATEGORY(chatterinoCrashhandler, "chatterino.crashhandler", logThreshold); Q_LOGGING_CATEGORY(chatterinoEmoji, "chatterino.emoji", logThreshold); diff --git a/src/common/QLogging.hpp b/src/common/QLogging.hpp index 0da52a72..e512a29d 100644 --- a/src/common/QLogging.hpp +++ b/src/common/QLogging.hpp @@ -9,6 +9,7 @@ Q_DECLARE_LOGGING_CATEGORY(chatterinoBttv); Q_DECLARE_LOGGING_CATEGORY(chatterinoCache); Q_DECLARE_LOGGING_CATEGORY(chatterinoCommands); Q_DECLARE_LOGGING_CATEGORY(chatterinoCommon); +Q_DECLARE_LOGGING_CATEGORY(chatterinoCompletion); Q_DECLARE_LOGGING_CATEGORY(chatterinoCrashhandler); Q_DECLARE_LOGGING_CATEGORY(chatterinoEmoji); Q_DECLARE_LOGGING_CATEGORY(chatterinoEnv); diff --git a/src/controllers/completion/strategies/ClassicEmoteStrategy.cpp b/src/controllers/completion/strategies/ClassicEmoteStrategy.cpp index 134e6e1e..e0d60ef1 100644 --- a/src/controllers/completion/strategies/ClassicEmoteStrategy.cpp +++ b/src/controllers/completion/strategies/ClassicEmoteStrategy.cpp @@ -1,23 +1,45 @@ #include "controllers/completion/strategies/ClassicEmoteStrategy.hpp" +#include "common/QLogging.hpp" #include "singletons/Settings.hpp" #include "util/Helpers.hpp" +#include + namespace chatterino::completion { +namespace { + +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) +const auto &LOG = chatterinoCompletion; + +} // namespace + void ClassicEmoteStrategy::apply(const std::vector &items, std::vector &output, const QString &query) const { + qCDebug(LOG) << "ClassicEmoteStrategy apply" << query; QString normalizedQuery = query; + bool zeroWidthOnly = false; if (normalizedQuery.startsWith(':')) { normalizedQuery = normalizedQuery.mid(1); } + if (normalizedQuery.startsWith('~')) + { + normalizedQuery = normalizedQuery.mid(1); + zeroWidthOnly = true; + } - // First pass: filter by contains match + // First pass: filter by zero-width only and contains match for (const auto &item : items) { + if (zeroWidthOnly && !item.emote->zeroWidth) + { + continue; + } + if (item.searchName.contains(normalizedQuery, Qt::CaseInsensitive)) { output.push_back(item); @@ -52,6 +74,7 @@ void ClassicTabEmoteStrategy::apply(const std::vector &items, std::vector &output, const QString &query) const { + qCDebug(LOG) << "ClassicTabEmoteStrategy apply" << query; bool colonStart = query.startsWith(':'); QStringView normalizedQuery = query; if (colonStart) diff --git a/src/controllers/completion/strategies/SmartEmoteStrategy.cpp b/src/controllers/completion/strategies/SmartEmoteStrategy.cpp index 6573df40..e57b44f5 100644 --- a/src/controllers/completion/strategies/SmartEmoteStrategy.cpp +++ b/src/controllers/completion/strategies/SmartEmoteStrategy.cpp @@ -11,6 +11,10 @@ namespace chatterino::completion { namespace { + +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) +const auto &LOG = chatterinoCompletion; + /** * @brief This function calculates the "cost" of the changes that need to * be done to the query to make it the value. @@ -67,7 +71,7 @@ int costOfEmote(QStringView query, QStringView emote, bool prioritizeUpper) // matchingFunction is used for testing if the emote should be included in the search. void completeEmotes( const std::vector &items, std::vector &output, - QStringView query, bool ignoreColonForCost, + QStringView query, bool ignoreColonForCost, bool ignoreTildeForCost, const std::function &matchingFunction) { // Given these emotes: pajaW, PAJAW @@ -128,7 +132,7 @@ void completeEmotes( } std::sort(output.begin(), output.end(), - [query, prioritizeUpper, ignoreColonForCost]( + [query, prioritizeUpper, ignoreColonForCost, ignoreTildeForCost]( const EmoteItem &a, const EmoteItem &b) -> bool { auto tempA = a.searchName; auto tempB = b.searchName; @@ -136,10 +140,18 @@ void completeEmotes( { tempA = tempA.mid(1); } + if (ignoreTildeForCost && tempA.startsWith("~")) + { + tempA = tempA.mid(1); + } if (ignoreColonForCost && tempB.startsWith(":")) { tempB = tempB.mid(1); } + if (ignoreTildeForCost && tempB.startsWith("~")) + { + tempB = tempB.mid(1); + } auto costA = costOfEmote(query, tempA, prioritizeUpper); auto costB = costOfEmote(query, tempB, prioritizeUpper); @@ -159,14 +171,30 @@ void SmartEmoteStrategy::apply(const std::vector &items, std::vector &output, const QString &query) const { + qCDebug(LOG) << "SmartEmoteStrategy apply" << query; + std::vector filteredItems = items; QString normalizedQuery = query; bool ignoreColonForCost = false; + bool zeroWidthOnly = false; if (normalizedQuery.startsWith(':')) { normalizedQuery = normalizedQuery.mid(1); ignoreColonForCost = true; } - completeEmotes(items, output, normalizedQuery, ignoreColonForCost, + if (normalizedQuery.startsWith('~')) + { + normalizedQuery = normalizedQuery.mid(1); + zeroWidthOnly = true; + + auto [first, last] = std::ranges::remove_if( + filteredItems, [](const EmoteItem &emoteItem) { + return !emoteItem.emote->zeroWidth; + }); + filteredItems.erase(first, last); + } + + completeEmotes(filteredItems, output, normalizedQuery, ignoreColonForCost, + zeroWidthOnly, [normalizedQuery](const EmoteItem &left, Qt::CaseSensitivity caseHandling) { return left.searchName.contains(normalizedQuery, @@ -178,6 +206,7 @@ void SmartTabEmoteStrategy::apply(const std::vector &items, std::vector &output, const QString &query) const { + qCDebug(LOG) << "SmartTabEmoteStrategy apply" << query; bool colonStart = query.startsWith(':'); QStringView normalizedQuery = query; if (colonStart) @@ -187,7 +216,7 @@ void SmartTabEmoteStrategy::apply(const std::vector &items, } completeEmotes( - items, output, normalizedQuery, false, + items, output, normalizedQuery, false, false, [&](const EmoteItem &item, Qt::CaseSensitivity caseHandling) -> bool { QStringView itemQuery; if (item.isEmoji)