diff --git a/chatterino.pro b/chatterino.pro index 7ddf1fd34..522dea930 100644 --- a/chatterino.pro +++ b/chatterino.pro @@ -250,7 +250,8 @@ SOURCES += \ src/widgets/helper/EffectLabel.cpp \ src/widgets/helper/Button.cpp \ src/messages/MessageContainer.cpp \ - src/debug/Benchmark.cpp + src/debug/Benchmark.cpp \ + src/common/UsernameSet.cpp HEADERS += \ src/Application.hpp \ @@ -445,7 +446,8 @@ HEADERS += \ src/widgets/helper/EffectLabel.hpp \ src/util/LayoutHelper.hpp \ src/widgets/helper/Button.hpp \ - src/messages/MessageContainer.hpp + src/messages/MessageContainer.hpp \ + src/common/UsernameSet.hpp RESOURCES += \ resources/resources.qrc \ diff --git a/src/common/Channel.hpp b/src/common/Channel.hpp index c1dd06651..f265f4b70 100644 --- a/src/common/Channel.hpp +++ b/src/common/Channel.hpp @@ -51,7 +51,6 @@ public: void addOrReplaceTimeout(MessagePtr message); void disableAllMessages(); void replaceMessage(MessagePtr message, MessagePtr replacement); - virtual void addRecentChatter(const MessagePtr &message); QStringList modList; @@ -67,6 +66,7 @@ public: protected: virtual void onConnected(); + virtual void addRecentChatter(const MessagePtr &message); private: const QString name_; diff --git a/src/common/CompletionModel.cpp b/src/common/CompletionModel.cpp index 04425a624..69e7ffe70 100644 --- a/src/common/CompletionModel.cpp +++ b/src/common/CompletionModel.cpp @@ -2,8 +2,10 @@ #include "Application.hpp" #include "common/Common.hpp" +#include "common/UsernameSet.hpp" #include "controllers/accounts/AccountController.hpp" #include "controllers/commands/CommandController.hpp" +#include "debug/Benchmark.hpp" #include "debug/Log.hpp" #include "providers/twitch/TwitchChannel.hpp" #include "providers/twitch/TwitchServer.hpp" @@ -103,88 +105,81 @@ int CompletionModel::rowCount(const QModelIndex &) const return this->emotes_.size(); } -void CompletionModel::refresh() +void CompletionModel::refresh(const QString &prefix) { - log("[CompletionModel:{}] Refreshing...]", this->channelName_); + { + std::lock_guard guard(this->emotesMutex_); + this->emotes_.clear(); + } - auto app = getApp(); + if (prefix.length() < 2) return; - // User-specific: Twitch Emotes - if (auto account = app->accounts->twitch.getCurrent()) { - for (const auto &emote : account->accessEmotes()->allEmoteNames) { - // XXX: No way to discern between a twitch global emote and sub - // emote right now - this->addString(emote.string, - TaggedString::Type::TwitchGlobalEmote); + BenchmarkGuard guard("CompletionModel::refresh"); + + auto addString = [&](const QString &str, TaggedString::Type type) { + if (str.startsWith(prefix)) this->emotes_.emplace(str + " ", type); + }; + + auto _channel = getApp()->twitch2->getChannelOrEmpty(this->channelName_); + + if (auto channel = dynamic_cast(_channel.get())) { + // account emotes + if (auto account = getApp()->accounts->twitch.getCurrent()) { + for (const auto &emote : account->accessEmotes()->allEmoteNames) { + // XXX: No way to discern between a twitch global emote and sub + // emote right now + addString(emote.string, TaggedString::Type::TwitchGlobalEmote); + } + } + + // Usernames + if (prefix.length() >= UsernameSet::PrefixLength) { + auto usernames = channel->accessChatters(); + + for (const auto &name : usernames->subrange(Prefix(prefix))) { + addString(name, TaggedString::Type::Username); + addString("@" + name, TaggedString::Type::Username); + } + } + + // Bttv Global + for (auto &emote : *channel->globalBttv().emotes()) { + addString(emote.first.string, TaggedString::Type::BTTVChannelEmote); + } + + // Ffz Global + for (auto &emote : *channel->globalFfz().emotes()) { + addString(emote.first.string, TaggedString::Type::FFZChannelEmote); + } + + // Bttv Channel + for (auto &emote : *channel->bttvEmotes()) { + addString(emote.first.string, TaggedString::Type::BTTVGlobalEmote); + } + + // Ffz Channel + for (auto &emote : *channel->ffzEmotes()) { + addString(emote.first.string, TaggedString::Type::BTTVGlobalEmote); + } + + // Emojis + if (prefix.startsWith(":")) { + const auto &emojiShortCodes = getApp()->emotes->emojis.shortCodes; + for (auto &m : emojiShortCodes) { + addString(":" + m + ":", TaggedString::Type::Emoji); + } + } + + // Commands + for (auto &command : getApp()->commands->items.getVector()) { + addString(command.name, TaggedString::Command); + } + + for (auto &command : + getApp()->commands->getDefaultTwitchCommandList()) { + addString(command, TaggedString::Command); } } - - // // Global: BTTV Global Emotes - // std::vector &bttvGlobalEmoteCodes = - // app->emotes->bttv.globalEmoteNames_; for (const auto &m : - // bttvGlobalEmoteCodes) { - // this->addString(m, TaggedString::Type::BTTVGlobalEmote); - // } - - // // Global: FFZ Global Emotes - // std::vector &ffzGlobalEmoteCodes = - // app->emotes->ffz.globalEmoteCodes; for (const auto &m : - // ffzGlobalEmoteCodes) { - // this->addString(m, TaggedString::Type::FFZGlobalEmote); - // } - - // Channel emotes - if (auto channel = dynamic_cast( - getApp() - ->twitch2->getChannelOrEmptyByID(this->channelName_) - .get())) { - auto bttv = channel->bttvEmotes(); - // auto it = bttv->begin(); - // for (const auto &emote : *bttv) { - // } - // std::vector &bttvChannelEmoteCodes = - // app->emotes->bttv.channelEmoteName_[this->channelName_]; - // for (const auto &m : bttvChannelEmoteCodes) { - // this->addString(m, TaggedString::Type::BTTVChannelEmote); - // } - - // Channel-specific: FFZ Channel Emotes - for (const auto &emote : *channel->ffzEmotes()) { - this->addString(emote.second->name.string, - TaggedString::Type::FFZChannelEmote); - } - } - - // Emojis - const auto &emojiShortCodes = app->emotes->emojis.shortCodes; - for (const auto &m : emojiShortCodes) { - this->addString(":" + m + ":", TaggedString::Type::Emoji); - } - - // Commands - for (auto &command : app->commands->items.getVector()) { - this->addString(command.name, TaggedString::Command); - } - - for (auto &command : app->commands->getDefaultTwitchCommandList()) { - this->addString(command, TaggedString::Command); - } - - // Channel-specific: Usernames - // fourtf: only works with twitch chat - // auto c = - // ChannelManager::getInstance().getTwitchChannel(this->channelName); - // auto usernames = c->getUsernamesForCompletions(); - // for (const auto &name : usernames) { - // assert(!name.displayName.isEmpty()); - // this->addString(name.displayName); - // this->addString('@' + name.displayName); - - // if (!name.localizedName.isEmpty()) { - // this->addString(name.localizedName); - // this->addString('@' + name.localizedName); - // } - // } } void CompletionModel::addString(const QString &str, TaggedString::Type type) @@ -200,8 +195,7 @@ void CompletionModel::addUser(const QString &username) auto add = [this](const QString &str) { auto ts = this->createUser(str + " "); // Always add a space at the end of completions - std::pair::iterator, bool> p = - this->emotes_.insert(ts); + auto p = this->emotes_.insert(ts); if (!p.second) { // No inseration was made, figure out if we need to replace the // username. diff --git a/src/common/CompletionModel.hpp b/src/common/CompletionModel.hpp index 977768474..d0a5e3a06 100644 --- a/src/common/CompletionModel.hpp +++ b/src/common/CompletionModel.hpp @@ -51,7 +51,7 @@ public: virtual QVariant data(const QModelIndex &index, int) const override; virtual int rowCount(const QModelIndex &) const override; - void refresh(); + void refresh(const QString &prefix); void addString(const QString &str, TaggedString::Type type); void addUser(const QString &str); diff --git a/src/common/UsernameSet.cpp b/src/common/UsernameSet.cpp new file mode 100644 index 000000000..4356772a9 --- /dev/null +++ b/src/common/UsernameSet.cpp @@ -0,0 +1,110 @@ +#include "UsernameSet.hpp" + +#include + +namespace chatterino { + +// +// UsernameSet +// + +UsernameSet::ConstIterator UsernameSet::begin() const +{ + return this->items.begin(); +} + +UsernameSet::ConstIterator UsernameSet::end() const +{ + return this->items.end(); +} + +UsernameSet::Range UsernameSet::subrange(const Prefix &prefix) const +{ + auto it = this->firstKeyForPrefix.find(prefix); + if (it != this->firstKeyForPrefix.end()) { + auto start = this->items.find(it->second); + auto end = start; + + while (end != this->items.end() && prefix.isStartOf(*end)) { + end++; + } + return {start, end}; + } + + return {this->items.end(), this->items.end()}; +} + +std::set::size_type UsernameSet::size() const +{ + return this->items.size(); +} + +std::pair UsernameSet::insert(const QString &value) +{ + this->insertPrefix(value); + + return this->items.insert(value); +} + +std::pair UsernameSet::insert(QString &&value) +{ + this->insertPrefix(value); + + return this->items.insert(std::move(value)); +} + +void UsernameSet::insertPrefix(const QString &value) +{ + auto &string = this->firstKeyForPrefix[Prefix(value)]; + + if (string.isNull() || value < string) string = value; +} + +// +// Range +// + +UsernameSet::Range::Range(ConstIterator start, ConstIterator end) + : start_(start) + , end_(end) +{ +} + +UsernameSet::ConstIterator UsernameSet::Range::begin() +{ + return this->start_; +} + +UsernameSet::ConstIterator UsernameSet::Range::end() +{ + return this->end_; +} + +// +// Prefix +// + +Prefix::Prefix(const QString &string) + : first(string.size() >= 1 ? string[0] : '\0') + , second(string.size() >= 2 ? string[1] : '\0') +{ +} + +bool Prefix::operator==(const Prefix &other) const +{ + return std::tie(this->first, this->second) == + std::tie(other.first, other.second); +} + +bool Prefix::isStartOf(const QString &string) const +{ + if (string.size() == 0) { + return this->first == QChar('\0') && this->second == QChar('\0'); + } else if (string.size() == 1) { + return this->first == string[0] && this->second == QChar('\0'); + } else { + return this->first == string[0] && this->second == string[1]; + } +} + +} // namespace chatterino diff --git a/src/common/UsernameSet.hpp b/src/common/UsernameSet.hpp new file mode 100644 index 000000000..d71bcdaf6 --- /dev/null +++ b/src/common/UsernameSet.hpp @@ -0,0 +1,73 @@ +#pragma once + +#include +#include +#include +#include + +namespace chatterino { +class Prefix +{ +public: + Prefix(const QString &string); + bool operator==(const Prefix &other) const; + bool isStartOf(const QString &string) const; + +private: + QChar first; + QChar second; + + friend struct std::hash; +}; +} // namespace chatterino + +namespace std { +template <> +struct hash { + size_t operator()(const chatterino::Prefix &prefix) const + { + return (size_t(prefix.first.unicode()) << 16) | + size_t(prefix.second.unicode()); + } +}; +} // namespace std + +namespace chatterino { +class UsernameSet +{ +public: + static constexpr int PrefixLength = 2; + + using Iterator = std::set::iterator; + using ConstIterator = std::set::const_iterator; + + class Range + { + public: + Range(ConstIterator start, ConstIterator end); + + ConstIterator begin(); + ConstIterator end(); + + private: + ConstIterator start_; + ConstIterator end_; + }; + + ConstIterator begin() const; + ConstIterator end() const; + Range subrange(const Prefix &prefix) const; + + std::set::size_type size() const; + + std::pair insert(const QString &value); + std::pair insert(QString &&value); + +private: + void insertPrefix(const QString &string); + + std::set items; + std::unordered_map firstKeyForPrefix; +}; + +} // namespace chatterino diff --git a/src/providers/twitch/TwitchChannel.cpp b/src/providers/twitch/TwitchChannel.cpp index 6413629f6..ad3cdc94a 100644 --- a/src/providers/twitch/TwitchChannel.cpp +++ b/src/providers/twitch/TwitchChannel.cpp @@ -49,6 +49,24 @@ auto parseRecentMessages(const QJsonObject &jsonRoot, TwitchChannel &channel) return messages; } +std::pair parseChatters(const QJsonObject &jsonRoot) +{ + static QStringList categories = {"moderators", "staff", "admins", + "global_mods", "viewers"}; + + auto usernames = UsernameSet(); + + // parse json + QJsonObject jsonCategories = jsonRoot.value("chatters").toObject(); + + for (const auto &category : categories) { + for (auto jsonCategory : jsonCategories.value(category).toArray()) { + usernames.insert(jsonCategory.toString()); + } + } + + return {Success, std::move(usernames)}; +} } // namespace TwitchChannel::TwitchChannel(const QString &name, BttvEmotes &bttv, @@ -72,22 +90,22 @@ TwitchChannel::TwitchChannel(const QString &name, BttvEmotes &bttv, [=] { this->setMod(false); }); // pubsub - this->userStateChanged.connect([=] { this->refreshPubsub(); }); this->managedConnect(getApp()->accounts->twitch.currentUserChanged, [=] { this->refreshPubsub(); }); this->refreshPubsub(); + this->userStateChanged.connect([this] { this->refreshPubsub(); }); // room id loaded -> refresh live status this->roomIdChanged.connect([this]() { this->refreshPubsub(); this->refreshLiveStatus(); - this->loadBadges(); - this->loadCheerEmotes(); + this->refreshBadges(); + this->refreshCheerEmotes(); }); // timers QObject::connect(&this->chattersListTimer_, &QTimer::timeout, - [=] { this->refreshViewerList(); }); + [=] { this->refreshChatters(); }); this->chattersListTimer_.start(5 * 60 * 1000); QObject::connect(&this->liveStatusTimer_, &QTimer::timeout, @@ -101,11 +119,17 @@ TwitchChannel::TwitchChannel(const QString &name, BttvEmotes &bttv, // debugging #if 0 for (int i = 0; i < 1000; i++) { - this->addMessage(makeSystemMessage("asdf")); + this->addMessage(makeSystemMessage("asef")); } #endif } +void TwitchChannel::initialize() +{ + this->refreshChatters(); + this->refreshChannelEmotes(); +} + bool TwitchChannel::isEmpty() const { return this->getName().isEmpty(); @@ -293,6 +317,11 @@ TwitchChannel::accessStreamStatus() const return this->streamStatus_.accessConst(); } +AccessGuard TwitchChannel::accessChatters() const +{ + return this->chatters_.accessConst(); +} + const BttvEmotes &TwitchChannel::globalBttv() const { return this->globalBttv_; @@ -314,7 +343,7 @@ boost::optional TwitchChannel::bttvEmote(const EmoteName &name) const boost::optional TwitchChannel::ffzEmote(const EmoteName &name) const { - auto emotes = this->bttvEmotes_.get(); + auto emotes = this->ffzEmotes_.get(); auto it = emotes->find(name); if (it == emotes->end()) return boost::none; @@ -508,7 +537,7 @@ void TwitchChannel::refreshPubsub() account); } -void TwitchChannel::refreshViewerList() +void TwitchChannel::refreshChatters() { // setting? const auto streamStatus = this->accessStreamStatus(); @@ -531,31 +560,18 @@ void TwitchChannel::refreshViewerList() auto shared = weak.lock(); if (!shared) return Failure; - return this->parseViewerList(result.parseJson()); + auto pair = parseChatters(result.parseJson()); + if (pair.first) { + *this->chatters_.access() = std::move(pair.second); + } + + return pair.first; }); request.execute(); } -Outcome TwitchChannel::parseViewerList(const QJsonObject &jsonRoot) -{ - static QStringList categories = {"moderators", "staff", "admins", - "global_mods", "viewers"}; - - // parse json - QJsonObject jsonCategories = jsonRoot.value("chatters").toObject(); - - for (const auto &category : categories) { - for (const auto jsonCategory : - jsonCategories.value(category).toArray()) { - this->completionModel.addUser(jsonCategory.toString()); - } - } - - return Success; -} - -void TwitchChannel::loadBadges() +void TwitchChannel::refreshBadges() { auto url = Url{"https://badges.twitch.tv/v1/badges/channels/" + this->roomId() + "/display?language=en"}; @@ -600,7 +616,7 @@ void TwitchChannel::loadBadges() req.execute(); } -void TwitchChannel::loadCheerEmotes() +void TwitchChannel::refreshCheerEmotes() { /*auto url = Url{"https://api.twitch.tv/kraken/bits/actions?channel_id=" + this->getRoomId()}; @@ -664,7 +680,7 @@ void TwitchChannel::loadCheerEmotes() */ } -boost::optional TwitchChannel::getTwitchBadge( +boost::optional TwitchChannel::twitchBadge( const QString &set, const QString &version) const { auto badgeSets = this->badgeSets_.access(); diff --git a/src/providers/twitch/TwitchChannel.hpp b/src/providers/twitch/TwitchChannel.hpp index a444d26e2..ea95c0df3 100644 --- a/src/providers/twitch/TwitchChannel.hpp +++ b/src/providers/twitch/TwitchChannel.hpp @@ -1,13 +1,17 @@ #pragma once -#include - #include "common/Aliases.hpp" #include "common/Atomic.hpp" #include "common/Channel.hpp" #include "common/Outcome.hpp" #include "common/UniqueAccess.hpp" +#include "common/UsernameSet.hpp" +#include "providers/twitch/TwitchEmotes.hpp" +#include +#include +#include +#include #include #include #include @@ -37,11 +41,6 @@ public: QString streamType; }; - struct UserState { - bool mod; - bool broadcaster; - }; - struct RoomModes { bool submode = false; bool r9k = false; @@ -51,32 +50,24 @@ public: QString broadcasterLang; }; - void refreshChannelEmotes(); + void initialize(); // Channel methods virtual bool isEmpty() const override; virtual bool canSendMessage() const override; virtual void sendMessage(const QString &message) override; - - // Auto completion - void addRecentChatter(const MessagePtr &message) final; - void addJoinedUser(const QString &user); - void addPartedUser(const QString &user); - - bool isLive() const; virtual bool isMod() const override; - void setMod(bool value); virtual bool isBroadcaster() const override; // Data const QString &subscriptionUrl(); const QString &channelUrl(); const QString &popoutPlayerUrl(); + bool isLive() const; QString roomId() const; - void setRoomId(const QString &id); AccessGuard accessRoomModes() const; - void setRoomModes(const RoomModes &roomModes_); AccessGuard accessStreamStatus() const; + AccessGuard accessChatters() const; // Emotes const BttvEmotes &globalBttv() const; @@ -86,58 +77,53 @@ public: std::shared_ptr bttvEmotes() const; std::shared_ptr ffzEmotes() const; - boost::optional getTwitchBadge(const QString &set, - const QString &version) const; + void refreshChannelEmotes(); + + // Badges + boost::optional twitchBadge(const QString &set, + const QString &version) const; // Signals pajlada::Signals::NoArgSignal roomIdChanged; - pajlada::Signals::NoArgSignal liveStatusChanged; pajlada::Signals::NoArgSignal userStateChanged; + pajlada::Signals::NoArgSignal liveStatusChanged; pajlada::Signals::NoArgSignal roomModesChanged; +protected: + void addRecentChatter(const MessagePtr &message) override; + private: struct NameOptions { QString displayName; QString localizedName; }; - struct CheerEmote { - // a Cheermote indicates one tier - QColor color; - int minBits; - - EmotePtr animatedEmote; - EmotePtr staticEmote; - }; - - struct CheerEmoteSet { - QRegularExpression regex; - std::vector cheerEmotes; - }; - - explicit TwitchChannel(const QString &channelName, BttvEmotes &bttv, - FfzEmotes &ffz); + explicit TwitchChannel(const QString &channelName, BttvEmotes &globalBttv, + FfzEmotes &globalFfz); // Methods void refreshLiveStatus(); Outcome parseLiveStatus(const rapidjson::Document &document); void refreshPubsub(); - void refreshViewerList(); - Outcome parseViewerList(const QJsonObject &jsonRoot); + void refreshChatters(); + void refreshBadges(); + void refreshCheerEmotes(); void loadRecentMessages(); + void addJoinedUser(const QString &user); + void addPartedUser(const QString &user); void setLive(bool newLiveStatus); - - void loadBadges(); - void loadCheerEmotes(); + void setMod(bool value); + void setRoomId(const QString &id); + void setRoomModes(const RoomModes &roomModes_); // Data const QString subscriptionUrl_; const QString channelUrl_; const QString popoutPlayerUrl_; UniqueAccess streamStatus_; - UniqueAccess userState_; UniqueAccess roomModes_; + UniqueAccess chatters_; // maps 2 char prefix to set of names // Emotes BttvEmotes &globalBttv_; @@ -145,6 +131,11 @@ private: Atomic> bttvEmotes_; Atomic> ffzEmotes_; + // Badges + UniqueAccess>> + badgeSets_; // "subscribers": { "0": ... "3": ... "6": ... + UniqueAccess> cheerEmoteSets_; + bool mod_ = false; UniqueAccess roomID_; @@ -153,10 +144,6 @@ private: UniqueAccess partedUsers_; bool partedUsersMergeQueued_ = false; - // "subscribers": { "0": ... "3": ... "6": ... - UniqueAccess>> badgeSets_; - UniqueAccess> cheerEmoteSets_; - // -- QByteArray messageSuffix_; QString lastSentMessage_; @@ -165,6 +152,8 @@ private: QTimer chattersListTimer_; friend class TwitchServer; + friend class TwitchMessageBuilder; + friend class IrcMessageHandler; }; } // namespace chatterino diff --git a/src/providers/twitch/TwitchEmotes.hpp b/src/providers/twitch/TwitchEmotes.hpp index 90cafe440..1c1b0b216 100644 --- a/src/providers/twitch/TwitchEmotes.hpp +++ b/src/providers/twitch/TwitchEmotes.hpp @@ -1,5 +1,7 @@ #pragma once +#include +#include #include #include @@ -11,10 +13,22 @@ "https://static-cdn.jtvnw.net/emoticons/v1/{id}/{scale}" namespace chatterino { - struct Emote; using EmotePtr = std::shared_ptr; +struct CheerEmote { + QColor color; + int minBits; + + EmotePtr animatedEmote; + EmotePtr staticEmote; +}; + +struct CheerEmoteSet { + QRegularExpression regex; + std::vector cheerEmotes; +}; + class TwitchEmotes { public: diff --git a/src/providers/twitch/TwitchMessageBuilder.cpp b/src/providers/twitch/TwitchMessageBuilder.cpp index 7f8c2aa3d..97b41561a 100644 --- a/src/providers/twitch/TwitchMessageBuilder.cpp +++ b/src/providers/twitch/TwitchMessageBuilder.cpp @@ -646,7 +646,7 @@ Outcome TwitchMessageBuilder::tryAppendEmote(const EmoteName &name) flags = MessageElementFlag::BttvEmote; } else if ((emote = this->twitchChannel->globalFfz().emote(name))) { flags = MessageElementFlag::FfzEmote; - } else if (twitchChannel && (emote = this->twitchChannel->ffzEmote(name))) { + } else if ((emote = this->twitchChannel->ffzEmote(name))) { flags = MessageElementFlag::FfzEmote; } @@ -691,7 +691,7 @@ void TwitchMessageBuilder::appendTwitchBadges() // Try to fetch channel-specific bit badge try { if (twitchChannel) - if (const auto &badge = this->twitchChannel->getTwitchBadge( + if (const auto &badge = this->twitchChannel->twitchBadge( "bits", cheerAmount)) { this->emplace( badge.get(), MessageElementFlag::BadgeVanity) diff --git a/src/providers/twitch/TwitchServer.cpp b/src/providers/twitch/TwitchServer.cpp index bb7b16fd9..465fe3315 100644 --- a/src/providers/twitch/TwitchServer.cpp +++ b/src/providers/twitch/TwitchServer.cpp @@ -85,7 +85,7 @@ std::shared_ptr TwitchServer::createChannel(const QString &channelName) { auto channel = std::shared_ptr( new TwitchChannel(channelName, this->bttv, this->ffz)); - channel->refreshChannelEmotes(); + channel->initialize(); channel->sendMessageSignal.connect( [this, channel = channel.get()](auto &chan, auto &msg, bool &sent) { diff --git a/src/widgets/helper/ResizingTextEdit.cpp b/src/widgets/helper/ResizingTextEdit.cpp index 731bed53b..1a06b1868 100644 --- a/src/widgets/helper/ResizingTextEdit.cpp +++ b/src/widgets/helper/ResizingTextEdit.cpp @@ -102,7 +102,7 @@ void ResizingTextEdit::keyPressEvent(QKeyEvent *event) // First type pressing tab after modifying a message, we refresh our // completion model this->completer_->setModel(completionModel); - completionModel->refresh(); + completionModel->refresh(currentCompletionPrefix); this->completionInProgress_ = true; this->completer_->setCompletionPrefix(currentCompletionPrefix); this->completer_->complete();