From 2dacf1b2ef2d54b4ec44542ad6ca8bc81b67c80d Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Fri, 29 Mar 2019 17:51:18 +0400 Subject: [PATCH] Accept any characters for suggestions. --- .../chat_helpers/emoji_keywords.cpp | 29 +++++- .../SourceFiles/chat_helpers/emoji_keywords.h | 1 + .../chat_helpers/emoji_suggestions_widget.cpp | 91 +++++++------------ 3 files changed, 63 insertions(+), 58 deletions(-) diff --git a/Telegram/SourceFiles/chat_helpers/emoji_keywords.cpp b/Telegram/SourceFiles/chat_helpers/emoji_keywords.cpp index a569c526d..c2447548c 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_keywords.cpp +++ b/Telegram/SourceFiles/chat_helpers/emoji_keywords.cpp @@ -62,10 +62,10 @@ struct LangPackData { yield(Lang::DefaultLanguageId()); yield(Lang::CurrentCloudManager().suggestedLanguage()); yield(Platform::SystemLanguage()); + yieldLocale(QLocale::system()); if (const auto method = QGuiApplication::inputMethod()) { yieldLocale(method->locale()); } - yieldLocale(QLocale::system()); return result; } @@ -118,6 +118,18 @@ void AppendFoundEmoji( void AppendLegacySuggestions( std::vector<Result> &result, const QString &query) { + const auto badSuggestionChar = [](QChar ch) { + return (ch < 'a' || ch > 'z') + && (ch < 'A' || ch > 'Z') + && (ch < '0' || ch > '9') + && (ch != '_') + && (ch != '-') + && (ch != '+'); + }; + if (ranges::find_if(query, badSuggestionChar) != query.end()) { + return; + } + const auto suggestions = GetSuggestions(QStringToUTF16(query)); auto &&add = ranges::view::all( suggestions @@ -217,6 +229,7 @@ public: [[nodiscard]] std::vector<Result> query( const QString &normalized, bool exact) const; + [[nodiscard]] int maxQueryLength() const; private: enum class State { @@ -389,6 +402,10 @@ std::vector<Result> EmojiKeywords::LangPack::query( return result; } +int EmojiKeywords::LangPack::maxQueryLength() const { + return _data.maxKeyLength; +} + EmojiKeywords::EmojiKeywords() { crl::on_main(&_guard, [=] { handleAuthSessionChanges(); @@ -490,6 +507,16 @@ std::vector<Result> EmojiKeywords::query( return result; } +int EmojiKeywords::maxQueryLength() const { + if (_data.empty()) { + return 0; + } + auto &&lengths = _data | ranges::view::transform([](const auto &pair) { + return pair.second->maxQueryLength(); + }); + return *ranges::max_element(lengths); +} + void EmojiKeywords::refreshRemoteList() { if (!_api) { _localList.clear(); diff --git a/Telegram/SourceFiles/chat_helpers/emoji_keywords.h b/Telegram/SourceFiles/chat_helpers/emoji_keywords.h index 7d4e6f13a..d69ec24d2 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_keywords.h +++ b/Telegram/SourceFiles/chat_helpers/emoji_keywords.h @@ -43,6 +43,7 @@ public: [[nodiscard]] std::vector<Result> query( const QString &query, bool exact = false) const; + [[nodiscard]] int maxQueryLength() const; private: class LangPack; diff --git a/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.cpp b/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.cpp index 5041cab1f..1dd873f66 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.cpp @@ -112,7 +112,18 @@ std::vector<SuggestionsWidget::Row> SuggestionsWidget::getRowsByQuery() const { return result; } auto suggestions = std::vector<Row>(); - const auto results = Core::App().emojiKeywords().query(_query); + const auto middle = (_query[0] == ':'); + const auto real = middle ? _query.mid(1) : _query; + const auto simple = [&] { + if (!middle || _query.size() > 2) { + return false; + } + // Suggest :D and :-P only as exact matches. + return ranges::find_if(_query, [](QChar ch) { return ch.isLower(); }) + == _query.end(); + }(); + const auto exact = !middle || simple; + const auto results = Core::App().emojiKeywords().query(real, exact); for (const auto &result : results) { suggestions.emplace_back( result.emoji, @@ -480,6 +491,8 @@ QString SuggestionsController::getEmojiQuery() { return QString(); } + const auto modernLimit = Core::App().emojiKeywords().maxQueryLength(); + const auto legacyLimit = GetSuggestionMaxLength(); const auto position = cursor.position(); const auto findTextPart = [&] { auto document = _field->document(); @@ -506,68 +519,32 @@ QString SuggestionsController::getEmojiQuery() { if (text.isEmpty()) { return QString(); } - for (auto i = position - _queryStartPosition; i != 0;) { + const auto length = position - _queryStartPosition; + for (auto i = length; i != 0;) { if (text[--i] == ':') { - return text.mid(i + 1); - } - } - const auto isUpperCaseLetter = [](QChar ch) { - return (ch >= 'A' && ch <= 'Z'); - }; - const auto isLetter = [](QChar ch) { - return (ch >= 'a' && ch <= 'z') - || (ch >= 'A' && ch <= 'Z') - || (ch >= '0' && ch <= '9'); - }; - const auto isSuggestionChar = [](QChar ch) { - return (ch >= 'a' && ch <= 'z') - || (ch >= 'A' && ch <= 'Z') - || (ch >= '0' && ch <= '9') - || (ch == '_') - || (ch == '-') - || (ch == '+'); - }; - const auto isGoodCharBeforeSuggestion = [&](QChar ch) { - return !isSuggestionChar(ch) || (ch == 0); - }; - Assert(position > 0 && position <= text.size()); - for (auto i = position; i != 0;) { - auto ch = text[--i]; - if (ch == ':') { - auto beforeColon = (i < 1) ? QChar(0) : text[i - 1]; - if (isGoodCharBeforeSuggestion(beforeColon)) { - // At least one letter after colon. - if (position > i + 1) { - // Skip colon and the first letter. - _queryStartPosition += i + 2; - const auto length = position - i; - auto result = text.mid(i, length); - const auto upperCaseLetters = std::count_if( - result.begin(), - result.end(), - isUpperCaseLetter); - const auto letters = std::count_if( - result.begin(), - result.end(), - isLetter); - if (letters == upperCaseLetters && letters == 1) { - // No upper case single letter suggestions. - // We don't want to suggest emoji on :D and :-P - return QString(); - } - return result.toLower(); - } + if (i + 1 == length) { + return QString(); } - return QString(); + _queryStartPosition += i + 2; + return text.mid(i, length - i); } - if (position - i > kSuggestionMaxLength) { - return QString(); - } - if (!isSuggestionChar(ch)) { + if (length - i > legacyLimit && length - i > modernLimit) { return QString(); } } - return QString(); + + // Exact query should be full input field value. + const auto end = [&] { + auto cursor = _field->textCursor(); + cursor.movePosition(QTextCursor::End); + return cursor.position(); + }(); + if ((length > modernLimit) + || (_queryStartPosition != 0) + || (position != end)) { + return QString(); + } + return text.mid(0, length); } void SuggestionsController::replaceCurrent(const QString &replacement) {