fix: support completing emotes starting with : (#5603)

This commit is contained in:
nerix
2024-10-06 11:29:52 +02:00
committed by GitHub
parent 0085fb64ac
commit 9ba7ef324d
7 changed files with 69 additions and 43 deletions
+1
View File
@@ -55,6 +55,7 @@
- Bugfix: Fixed grammar in the user highlight page. (#5602) - Bugfix: Fixed grammar in the user highlight page. (#5602)
- Bugfix: Fixed incorrect message being disabled in some cases upon approving or denying an automod caught message. (#5611) - Bugfix: Fixed incorrect message being disabled in some cases upon approving or denying an automod caught message. (#5611)
- Bugfix: Fixed double-click selection not working when clicking outside a message. (#5617) - Bugfix: Fixed double-click selection not working when clicking outside a message. (#5617)
- Bugfix: Fixed emotes starting with ":" not tab-completing. (#5603)
- Dev: Update Windows build from Qt 6.5.0 to Qt 6.7.1. (#5420) - Dev: Update Windows build from Qt 6.5.0 to Qt 6.7.1. (#5420)
- Dev: Update vcpkg build Qt from 6.5.0 to 6.7.0, boost from 1.83.0 to 1.85.0, openssl from 3.1.3 to 3.3.0. (#5422) - Dev: Update vcpkg build Qt from 6.5.0 to 6.7.0, boost from 1.83.0 to 1.85.0, openssl from 3.1.3 to 3.3.0. (#5422)
- Dev: Unsingletonize `ISoundController`. (#5462) - Dev: Unsingletonize `ISoundController`. (#5462)
@@ -52,25 +52,36 @@ void ClassicTabEmoteStrategy::apply(const std::vector<EmoteItem> &items,
std::vector<EmoteItem> &output, std::vector<EmoteItem> &output,
const QString &query) const const QString &query) const
{ {
bool emojiOnly = false; bool colonStart = query.startsWith(':');
QString normalizedQuery = query; QStringView normalizedQuery = query;
if (normalizedQuery.startsWith(':')) if (colonStart)
{ {
// TODO(Qt6): use sliced
normalizedQuery = normalizedQuery.mid(1); normalizedQuery = normalizedQuery.mid(1);
// tab completion with : prefix should do emojis only
emojiOnly = true;
} }
std::set<EmoteItem, CompletionEmoteOrder> emotes; std::set<EmoteItem, CompletionEmoteOrder> emotes;
for (const auto &item : items) for (const auto &item : items)
{ {
if (emojiOnly ^ item.isEmoji) QStringView itemQuery;
if (item.isEmoji)
{ {
continue; if (colonStart)
{
itemQuery = normalizedQuery;
}
else
{
continue; // ignore emojis when not completing with ':'
}
}
else
{
itemQuery = query;
} }
if (startsWithOrContains(item.searchName, normalizedQuery, if (startsWithOrContains(item.searchName, itemQuery,
Qt::CaseInsensitive, Qt::CaseInsensitive,
getSettings()->prefixOnlyEmoteCompletion)) getSettings()->prefixOnlyEmoteCompletion))
{ {
@@ -25,8 +25,7 @@ namespace {
* @return How different the emote is from query. Values in the range [-10, * @return How different the emote is from query. Values in the range [-10,
* \infty]. * \infty].
*/ */
int costOfEmote(const QString &query, const QString &emote, int costOfEmote(QStringView query, QStringView emote, bool prioritizeUpper)
bool prioritizeUpper)
{ {
int score = 0; int score = 0;
@@ -68,8 +67,8 @@ namespace {
// matchingFunction is used for testing if the emote should be included in the search. // matchingFunction is used for testing if the emote should be included in the search.
void completeEmotes( void completeEmotes(
const std::vector<EmoteItem> &items, std::vector<EmoteItem> &output, const std::vector<EmoteItem> &items, std::vector<EmoteItem> &output,
const QString &query, bool ignoreColonForCost, QStringView query, bool ignoreColonForCost,
const std::function<bool(EmoteItem, QString, Qt::CaseSensitivity)> const std::function<bool(EmoteItem, Qt::CaseSensitivity)>
&matchingFunction) &matchingFunction)
{ {
// Given these emotes: pajaW, PAJAW // Given these emotes: pajaW, PAJAW
@@ -92,8 +91,7 @@ namespace {
for (const auto &item : items) for (const auto &item : items)
{ {
if (matchingFunction( if (matchingFunction(
item, query, item, haveUpper ? Qt::CaseSensitive : Qt::CaseInsensitive))
haveUpper ? Qt::CaseSensitive : Qt::CaseInsensitive))
{ {
output.push_back(item); output.push_back(item);
} }
@@ -118,7 +116,7 @@ namespace {
// Run the search again but this time without case sensitivity // Run the search again but this time without case sensitivity
for (const auto &item : items) for (const auto &item : items)
{ {
if (matchingFunction(item, query, Qt::CaseInsensitive)) if (matchingFunction(item, Qt::CaseInsensitive))
{ {
output.push_back(item); output.push_back(item);
} }
@@ -170,9 +168,10 @@ void SmartEmoteStrategy::apply(const std::vector<EmoteItem> &items,
ignoreColonForCost = true; ignoreColonForCost = true;
} }
completeEmotes(items, output, normalizedQuery, ignoreColonForCost, completeEmotes(items, output, normalizedQuery, ignoreColonForCost,
[](const EmoteItem &left, const QString &right, [normalizedQuery](const EmoteItem &left,
Qt::CaseSensitivity caseHandling) { Qt::CaseSensitivity caseHandling) {
return left.searchName.contains(right, caseHandling); return left.searchName.contains(normalizedQuery,
caseHandling);
}); });
} }
@@ -180,25 +179,38 @@ void SmartTabEmoteStrategy::apply(const std::vector<EmoteItem> &items,
std::vector<EmoteItem> &output, std::vector<EmoteItem> &output,
const QString &query) const const QString &query) const
{ {
bool emojiOnly = false; bool colonStart = query.startsWith(':');
QString normalizedQuery = query; QStringView normalizedQuery = query;
if (normalizedQuery.startsWith(':')) if (colonStart)
{ {
// TODO(Qt6): use sliced
normalizedQuery = normalizedQuery.mid(1); normalizedQuery = normalizedQuery.mid(1);
// tab completion with : prefix should do emojis only
emojiOnly = true;
} }
completeEmotes(items, output, normalizedQuery, false,
[emojiOnly](const EmoteItem &left, const QString &right, completeEmotes(
Qt::CaseSensitivity caseHandling) -> bool { items, output, normalizedQuery, false,
if (emojiOnly ^ left.isEmoji) [&](const EmoteItem &item, Qt::CaseSensitivity caseHandling) -> bool {
{ QStringView itemQuery;
return false; if (item.isEmoji)
} {
return startsWithOrContains( if (colonStart)
left.searchName, right, caseHandling, {
getSettings()->prefixOnlyEmoteCompletion); itemQuery = normalizedQuery;
}); }
else
{
return false; // ignore emojis when not completing with ':'
}
}
else
{
itemQuery = query;
}
return startsWithOrContains(
item.searchName, itemQuery, caseHandling,
getSettings()->prefixOnlyEmoteCompletion);
});
} }
} // namespace chatterino::completion } // namespace chatterino::completion
+1 -1
View File
@@ -371,7 +371,7 @@ void TwitchAccount::reloadEmotes(void *caller)
auto meta = getTwitchEmoteSetMeta(emote); auto meta = getTwitchEmoteSetMeta(emote);
auto emotePtr = twitchEmotes->getOrCreateEmote(id, name); auto emotePtr = twitchEmotes->getOrCreateEmote(id, name);
if (!emoteMap->try_emplace(name, emotePtr).second) if (!emoteMap->try_emplace(emotePtr->name, emotePtr).second)
{ {
// if the emote already exists, we don't want to add it to a set as // if the emote already exists, we don't want to add it to a set as
// those are assumed to be disjoint // those are assumed to be disjoint
+1 -1
View File
@@ -125,7 +125,7 @@ namespace helpers::detail {
} // namespace helpers::detail } // namespace helpers::detail
using namespace helpers::detail; using namespace helpers::detail;
bool startsWithOrContains(const QString &str1, const QString &str2, bool startsWithOrContains(QStringView str1, QStringView str2,
Qt::CaseSensitivity caseSensitivity, bool startsWith) Qt::CaseSensitivity caseSensitivity, bool startsWith)
{ {
if (startsWith) if (startsWith)
+1 -1
View File
@@ -59,7 +59,7 @@ namespace helpers::detail {
* @brief startsWithOrContains is a wrapper for checking * @brief startsWithOrContains is a wrapper for checking
* whether str1 starts with or contains str2 within itself * whether str1 starts with or contains str2 within itself
**/ **/
bool startsWithOrContains(const QString &str1, const QString &str2, bool startsWithOrContains(QStringView str1, QStringView str2,
Qt::CaseSensitivity caseSensitivity, bool startsWith); Qt::CaseSensitivity caseSensitivity, bool startsWith);
/** /**
+8 -6
View File
@@ -387,10 +387,12 @@ TEST_F(InputCompletionTest, ClassicTabCompletionEmote)
TEST_F(InputCompletionTest, ClassicTabCompletionEmoji) TEST_F(InputCompletionTest, ClassicTabCompletionEmoji)
{ {
auto completion = queryClassicTabCompletion(":tf", false); auto completion = queryClassicTabCompletion(":tf", false);
ASSERT_EQ(completion.size(), 0); ASSERT_EQ(completion.size(), 1);
ASSERT_EQ(completion[0], ":tf: ");
completion = queryClassicTabCompletion(":)", false); completion = queryClassicTabCompletion(":)", false);
ASSERT_EQ(completion.size(), 0); ASSERT_EQ(completion.size(), 1);
ASSERT_EQ(completion[0], ":) ");
completion = queryClassicTabCompletion(":cla", false); completion = queryClassicTabCompletion(":cla", false);
ASSERT_EQ(completion.size(), 8); ASSERT_EQ(completion.size(), 8);
@@ -536,12 +538,12 @@ TEST_F(InputCompletionTest, SmartTabCompletionEmote)
TEST_F(InputCompletionTest, SmartTabCompletionEmoji) TEST_F(InputCompletionTest, SmartTabCompletionEmoji)
{ {
auto completion = querySmartTabCompletion(":tf", false); auto completion = querySmartTabCompletion(":tf", false);
ASSERT_EQ(completion.size(), 0); ASSERT_EQ(completion.size(), 1);
// ASSERT_EQ(completion[0], ":tf: "); ASSERT_EQ(completion[0], ":tf: ");
completion = querySmartTabCompletion(":)", false); completion = querySmartTabCompletion(":)", false);
ASSERT_EQ(completion.size(), 0); ASSERT_EQ(completion.size(), 1);
// ASSERT_EQ(completion[0], ":) "); ASSERT_EQ(completion[0], ":) ");
completion = querySmartTabCompletion(":cla", false); completion = querySmartTabCompletion(":cla", false);
ASSERT_EQ(completion.size(), 8); ASSERT_EQ(completion.size(), 8);