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
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 69 additions and 43 deletions

View file

@ -55,6 +55,7 @@
- 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 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 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)

View file

@ -52,25 +52,36 @@ void ClassicTabEmoteStrategy::apply(const std::vector<EmoteItem> &items,
std::vector<EmoteItem> &output,
const QString &query) const
{
bool emojiOnly = false;
QString normalizedQuery = query;
if (normalizedQuery.startsWith(':'))
bool colonStart = query.startsWith(':');
QStringView normalizedQuery = query;
if (colonStart)
{
// TODO(Qt6): use sliced
normalizedQuery = normalizedQuery.mid(1);
// tab completion with : prefix should do emojis only
emojiOnly = true;
}
std::set<EmoteItem, CompletionEmoteOrder> emotes;
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,
getSettings()->prefixOnlyEmoteCompletion))
{

View file

@ -25,8 +25,7 @@ namespace {
* @return How different the emote is from query. Values in the range [-10,
* \infty].
*/
int costOfEmote(const QString &query, const QString &emote,
bool prioritizeUpper)
int costOfEmote(QStringView query, QStringView emote, bool prioritizeUpper)
{
int score = 0;
@ -68,8 +67,8 @@ namespace {
// matchingFunction is used for testing if the emote should be included in the search.
void completeEmotes(
const std::vector<EmoteItem> &items, std::vector<EmoteItem> &output,
const QString &query, bool ignoreColonForCost,
const std::function<bool(EmoteItem, QString, Qt::CaseSensitivity)>
QStringView query, bool ignoreColonForCost,
const std::function<bool(EmoteItem, Qt::CaseSensitivity)>
&matchingFunction)
{
// Given these emotes: pajaW, PAJAW
@ -92,8 +91,7 @@ namespace {
for (const auto &item : items)
{
if (matchingFunction(
item, query,
haveUpper ? Qt::CaseSensitive : Qt::CaseInsensitive))
item, haveUpper ? Qt::CaseSensitive : Qt::CaseInsensitive))
{
output.push_back(item);
}
@ -118,7 +116,7 @@ namespace {
// Run the search again but this time without case sensitivity
for (const auto &item : items)
{
if (matchingFunction(item, query, Qt::CaseInsensitive))
if (matchingFunction(item, Qt::CaseInsensitive))
{
output.push_back(item);
}
@ -170,9 +168,10 @@ void SmartEmoteStrategy::apply(const std::vector<EmoteItem> &items,
ignoreColonForCost = true;
}
completeEmotes(items, output, normalizedQuery, ignoreColonForCost,
[](const EmoteItem &left, const QString &right,
Qt::CaseSensitivity caseHandling) {
return left.searchName.contains(right, caseHandling);
[normalizedQuery](const EmoteItem &left,
Qt::CaseSensitivity caseHandling) {
return left.searchName.contains(normalizedQuery,
caseHandling);
});
}
@ -180,25 +179,38 @@ void SmartTabEmoteStrategy::apply(const std::vector<EmoteItem> &items,
std::vector<EmoteItem> &output,
const QString &query) const
{
bool emojiOnly = false;
QString normalizedQuery = query;
if (normalizedQuery.startsWith(':'))
bool colonStart = query.startsWith(':');
QStringView normalizedQuery = query;
if (colonStart)
{
// TODO(Qt6): use sliced
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,
Qt::CaseSensitivity caseHandling) -> bool {
if (emojiOnly ^ left.isEmoji)
{
return false;
}
return startsWithOrContains(
left.searchName, right, caseHandling,
getSettings()->prefixOnlyEmoteCompletion);
});
completeEmotes(
items, output, normalizedQuery, false,
[&](const EmoteItem &item, Qt::CaseSensitivity caseHandling) -> bool {
QStringView itemQuery;
if (item.isEmoji)
{
if (colonStart)
{
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

View file

@ -371,7 +371,7 @@ void TwitchAccount::reloadEmotes(void *caller)
auto meta = getTwitchEmoteSetMeta(emote);
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
// those are assumed to be disjoint

View file

@ -125,7 +125,7 @@ namespace helpers::detail {
} // 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)
{
if (startsWith)

View file

@ -59,7 +59,7 @@ namespace helpers::detail {
* @brief startsWithOrContains is a wrapper for checking
* 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);
/**

View file

@ -387,10 +387,12 @@ TEST_F(InputCompletionTest, ClassicTabCompletionEmote)
TEST_F(InputCompletionTest, ClassicTabCompletionEmoji)
{
auto completion = queryClassicTabCompletion(":tf", false);
ASSERT_EQ(completion.size(), 0);
ASSERT_EQ(completion.size(), 1);
ASSERT_EQ(completion[0], ":tf: ");
completion = queryClassicTabCompletion(":)", false);
ASSERT_EQ(completion.size(), 0);
ASSERT_EQ(completion.size(), 1);
ASSERT_EQ(completion[0], ":) ");
completion = queryClassicTabCompletion(":cla", false);
ASSERT_EQ(completion.size(), 8);
@ -536,12 +538,12 @@ TEST_F(InputCompletionTest, SmartTabCompletionEmote)
TEST_F(InputCompletionTest, SmartTabCompletionEmoji)
{
auto completion = querySmartTabCompletion(":tf", false);
ASSERT_EQ(completion.size(), 0);
// ASSERT_EQ(completion[0], ":tf: ");
ASSERT_EQ(completion.size(), 1);
ASSERT_EQ(completion[0], ":tf: ");
completion = querySmartTabCompletion(":)", false);
ASSERT_EQ(completion.size(), 0);
// ASSERT_EQ(completion[0], ":) ");
ASSERT_EQ(completion.size(), 1);
ASSERT_EQ(completion[0], ":) ");
completion = querySmartTabCompletion(":cla", false);
ASSERT_EQ(completion.size(), 8);