fix: support completing emotes starting with : (#5603)
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user