Consolidate input completion code in preparation for advanced completion strategies (#4639)
Co-authored-by: Rasmus Karlsson <rasmus.karlsson@pajlada.com>
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
#include "controllers/completion/sources/CommandSource.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "controllers/commands/Command.hpp"
|
||||
#include "controllers/commands/CommandController.hpp"
|
||||
#include "controllers/completion/sources/Helpers.hpp"
|
||||
#include "providers/twitch/TwitchCommon.hpp"
|
||||
|
||||
namespace chatterino::completion {
|
||||
|
||||
namespace {
|
||||
|
||||
void addCommand(const QString &command, std::vector<CommandItem> &out)
|
||||
{
|
||||
if (command.startsWith('/') || command.startsWith('.'))
|
||||
{
|
||||
out.push_back({command.mid(1), command.at(0)});
|
||||
}
|
||||
else
|
||||
{
|
||||
out.push_back({command, '/'});
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
CommandSource::CommandSource(std::unique_ptr<CommandStrategy> strategy,
|
||||
ActionCallback callback)
|
||||
: strategy_(std::move(strategy))
|
||||
, callback_(std::move(callback))
|
||||
{
|
||||
this->initializeItems();
|
||||
}
|
||||
|
||||
void CommandSource::update(const QString &query)
|
||||
{
|
||||
this->output_.clear();
|
||||
if (this->strategy_)
|
||||
{
|
||||
this->strategy_->apply(this->items_, this->output_, query);
|
||||
}
|
||||
}
|
||||
|
||||
void CommandSource::addToListModel(GenericListModel &model,
|
||||
size_t maxCount) const
|
||||
{
|
||||
addVecToListModel(this->output_, model, maxCount,
|
||||
[this](const CommandItem &command) {
|
||||
return std::make_unique<InputCompletionItem>(
|
||||
nullptr, command.name, this->callback_);
|
||||
});
|
||||
}
|
||||
|
||||
void CommandSource::addToStringList(QStringList &list, size_t maxCount,
|
||||
bool /* isFirstWord */) const
|
||||
{
|
||||
addVecToStringList(this->output_, list, maxCount,
|
||||
[](const CommandItem &command) {
|
||||
return command.prefix + command.name + " ";
|
||||
});
|
||||
}
|
||||
|
||||
void CommandSource::initializeItems()
|
||||
{
|
||||
std::vector<CommandItem> commands;
|
||||
|
||||
#ifdef CHATTERINO_HAVE_PLUGINS
|
||||
for (const auto &command : getApp()->commands->pluginCommands())
|
||||
{
|
||||
addCommand(command, commands);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Custom Chatterino commands
|
||||
for (const auto &command : getIApp()->getCommands()->items)
|
||||
{
|
||||
addCommand(command.name, commands);
|
||||
}
|
||||
|
||||
// Default Chatterino commands
|
||||
auto x = getIApp()->getCommands()->getDefaultChatterinoCommandList();
|
||||
for (const auto &command : x)
|
||||
{
|
||||
addCommand(command, commands);
|
||||
}
|
||||
|
||||
// Default Twitch commands
|
||||
for (const auto &command : TWITCH_DEFAULT_COMMANDS)
|
||||
{
|
||||
addCommand(command, commands);
|
||||
}
|
||||
|
||||
this->items_ = std::move(commands);
|
||||
}
|
||||
|
||||
const std::vector<CommandItem> &CommandSource::output() const
|
||||
{
|
||||
return this->output_;
|
||||
}
|
||||
|
||||
} // namespace chatterino::completion
|
||||
@@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
|
||||
#include "controllers/completion/sources/Source.hpp"
|
||||
#include "controllers/completion/strategies/Strategy.hpp"
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace chatterino::completion {
|
||||
|
||||
struct CommandItem {
|
||||
QString name{};
|
||||
QChar prefix{};
|
||||
};
|
||||
|
||||
class CommandSource : public Source
|
||||
{
|
||||
public:
|
||||
using ActionCallback = std::function<void(const QString &)>;
|
||||
using CommandStrategy = Strategy<CommandItem>;
|
||||
|
||||
/// @brief Initializes a source for CommandItems.
|
||||
/// @param strategy Strategy to apply
|
||||
/// @param callback ActionCallback to invoke upon InputCompletionItem selection.
|
||||
/// See InputCompletionItem::action(). Can be nullptr.
|
||||
CommandSource(std::unique_ptr<CommandStrategy> strategy,
|
||||
ActionCallback callback = nullptr);
|
||||
|
||||
void update(const QString &query) override;
|
||||
void addToListModel(GenericListModel &model,
|
||||
size_t maxCount = 0) const override;
|
||||
void addToStringList(QStringList &list, size_t maxCount = 0,
|
||||
bool isFirstWord = false) const override;
|
||||
|
||||
const std::vector<CommandItem> &output() const;
|
||||
|
||||
private:
|
||||
void initializeItems();
|
||||
|
||||
std::unique_ptr<CommandStrategy> strategy_;
|
||||
ActionCallback callback_;
|
||||
|
||||
std::vector<CommandItem> items_{};
|
||||
std::vector<CommandItem> output_{};
|
||||
};
|
||||
|
||||
} // namespace chatterino::completion
|
||||
@@ -0,0 +1,152 @@
|
||||
#include "controllers/completion/sources/EmoteSource.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "controllers/accounts/AccountController.hpp"
|
||||
#include "controllers/completion/sources/Helpers.hpp"
|
||||
#include "providers/emoji/Emojis.hpp"
|
||||
#include "providers/twitch/TwitchAccount.hpp"
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||
#include "singletons/Emotes.hpp"
|
||||
|
||||
namespace chatterino::completion {
|
||||
|
||||
namespace {
|
||||
|
||||
void addEmotes(std::vector<EmoteItem> &out, const EmoteMap &map,
|
||||
const QString &providerName)
|
||||
{
|
||||
for (auto &&emote : map)
|
||||
{
|
||||
out.push_back({.emote = emote.second,
|
||||
.searchName = emote.first.string,
|
||||
.tabCompletionName = emote.first.string,
|
||||
.displayName = emote.second->name.string,
|
||||
.providerName = providerName,
|
||||
.isEmoji = false});
|
||||
}
|
||||
}
|
||||
|
||||
void addEmojis(std::vector<EmoteItem> &out, const EmojiMap &map)
|
||||
{
|
||||
map.each([&](const QString &, const std::shared_ptr<EmojiData> &emoji) {
|
||||
for (auto &&shortCode : emoji->shortCodes)
|
||||
{
|
||||
out.push_back(
|
||||
{.emote = emoji->emote,
|
||||
.searchName = shortCode,
|
||||
.tabCompletionName = QStringLiteral(":%1:").arg(shortCode),
|
||||
.displayName = shortCode,
|
||||
.providerName = "Emoji",
|
||||
.isEmoji = true});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
EmoteSource::EmoteSource(const Channel *channel,
|
||||
std::unique_ptr<EmoteStrategy> strategy,
|
||||
ActionCallback callback)
|
||||
: strategy_(std::move(strategy))
|
||||
, callback_(std::move(callback))
|
||||
{
|
||||
this->initializeFromChannel(channel);
|
||||
}
|
||||
|
||||
void EmoteSource::update(const QString &query)
|
||||
{
|
||||
this->output_.clear();
|
||||
if (this->strategy_)
|
||||
{
|
||||
this->strategy_->apply(this->items_, this->output_, query);
|
||||
}
|
||||
}
|
||||
|
||||
void EmoteSource::addToListModel(GenericListModel &model, size_t maxCount) const
|
||||
{
|
||||
addVecToListModel(this->output_, model, maxCount,
|
||||
[this](const EmoteItem &e) {
|
||||
return std::make_unique<InputCompletionItem>(
|
||||
e.emote, e.displayName + " - " + e.providerName,
|
||||
this->callback_);
|
||||
});
|
||||
}
|
||||
|
||||
void EmoteSource::addToStringList(QStringList &list, size_t maxCount,
|
||||
bool /* isFirstWord */) const
|
||||
{
|
||||
addVecToStringList(this->output_, list, maxCount, [](const EmoteItem &e) {
|
||||
return e.tabCompletionName + " ";
|
||||
});
|
||||
}
|
||||
|
||||
void EmoteSource::initializeFromChannel(const Channel *channel)
|
||||
{
|
||||
auto *app = getIApp();
|
||||
|
||||
std::vector<EmoteItem> emotes;
|
||||
const auto *tc = dynamic_cast<const TwitchChannel *>(channel);
|
||||
// returns true also for special Twitch channels (/live, /mentions, /whispers, etc.)
|
||||
if (channel->isTwitchChannel())
|
||||
{
|
||||
if (auto user = app->getAccounts()->twitch.getCurrent())
|
||||
{
|
||||
// Twitch Emotes available globally
|
||||
auto emoteData = user->accessEmotes();
|
||||
addEmotes(emotes, emoteData->emotes, "Twitch Emote");
|
||||
|
||||
// Twitch Emotes available locally
|
||||
auto localEmoteData = user->accessLocalEmotes();
|
||||
if ((tc != nullptr) &&
|
||||
localEmoteData->find(tc->roomId()) != localEmoteData->end())
|
||||
{
|
||||
if (const auto *localEmotes = &localEmoteData->at(tc->roomId()))
|
||||
{
|
||||
addEmotes(emotes, *localEmotes, "Local Twitch Emotes");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tc)
|
||||
{
|
||||
// TODO extract "Channel {BetterTTV,7TV,FrankerFaceZ}" text into a #define.
|
||||
if (auto bttv = tc->bttvEmotes())
|
||||
{
|
||||
addEmotes(emotes, *bttv, "Channel BetterTTV");
|
||||
}
|
||||
if (auto ffz = tc->ffzEmotes())
|
||||
{
|
||||
addEmotes(emotes, *ffz, "Channel FrankerFaceZ");
|
||||
}
|
||||
if (auto seventv = tc->seventvEmotes())
|
||||
{
|
||||
addEmotes(emotes, *seventv, "Channel 7TV");
|
||||
}
|
||||
}
|
||||
|
||||
if (auto bttvG = app->getTwitch()->getBttvEmotes().emotes())
|
||||
{
|
||||
addEmotes(emotes, *bttvG, "Global BetterTTV");
|
||||
}
|
||||
if (auto ffzG = app->getTwitch()->getFfzEmotes().emotes())
|
||||
{
|
||||
addEmotes(emotes, *ffzG, "Global FrankerFaceZ");
|
||||
}
|
||||
if (auto seventvG = app->getTwitch()->getSeventvEmotes().globalEmotes())
|
||||
{
|
||||
addEmotes(emotes, *seventvG, "Global 7TV");
|
||||
}
|
||||
}
|
||||
|
||||
addEmojis(emotes, app->getEmotes()->getEmojis()->getEmojis());
|
||||
|
||||
this->items_ = std::move(emotes);
|
||||
}
|
||||
|
||||
const std::vector<EmoteItem> &EmoteSource::output() const
|
||||
{
|
||||
return this->output_;
|
||||
}
|
||||
|
||||
} // namespace chatterino::completion
|
||||
@@ -0,0 +1,63 @@
|
||||
#pragma once
|
||||
|
||||
#include "common/Channel.hpp"
|
||||
#include "controllers/completion/sources/Source.hpp"
|
||||
#include "controllers/completion/strategies/Strategy.hpp"
|
||||
#include "messages/Emote.hpp"
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace chatterino::completion {
|
||||
|
||||
struct EmoteItem {
|
||||
/// Emote image to show in input popup
|
||||
EmotePtr emote{};
|
||||
/// Name to check completion queries against
|
||||
QString searchName{};
|
||||
/// Name to insert into split input upon tab completing
|
||||
QString tabCompletionName{};
|
||||
/// Display name within input popup
|
||||
QString displayName{};
|
||||
/// Emote provider name for input popup
|
||||
QString providerName{};
|
||||
/// Whether emote is emoji
|
||||
bool isEmoji{};
|
||||
};
|
||||
|
||||
class EmoteSource : public Source
|
||||
{
|
||||
public:
|
||||
using ActionCallback = std::function<void(const QString &)>;
|
||||
using EmoteStrategy = Strategy<EmoteItem>;
|
||||
|
||||
/// @brief Initializes a source for EmoteItems from the given channel
|
||||
/// @param channel Channel to initialize emotes from
|
||||
/// @param strategy Strategy to apply
|
||||
/// @param callback ActionCallback to invoke upon InputCompletionItem selection.
|
||||
/// See InputCompletionItem::action(). Can be nullptr.
|
||||
EmoteSource(const Channel *channel, std::unique_ptr<EmoteStrategy> strategy,
|
||||
ActionCallback callback = nullptr);
|
||||
|
||||
void update(const QString &query) override;
|
||||
void addToListModel(GenericListModel &model,
|
||||
size_t maxCount = 0) const override;
|
||||
void addToStringList(QStringList &list, size_t maxCount = 0,
|
||||
bool isFirstWord = false) const override;
|
||||
|
||||
const std::vector<EmoteItem> &output() const;
|
||||
|
||||
private:
|
||||
void initializeFromChannel(const Channel *channel);
|
||||
|
||||
std::unique_ptr<EmoteStrategy> strategy_;
|
||||
ActionCallback callback_;
|
||||
|
||||
std::vector<EmoteItem> items_{};
|
||||
std::vector<EmoteItem> output_{};
|
||||
};
|
||||
|
||||
} // namespace chatterino::completion
|
||||
@@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
|
||||
#include "widgets/listview/GenericListModel.hpp"
|
||||
|
||||
#include <QStringList>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace chatterino::completion {
|
||||
|
||||
namespace {
|
||||
|
||||
size_t sizeWithinLimit(size_t size, size_t limit)
|
||||
{
|
||||
if (limit == 0)
|
||||
{
|
||||
return size;
|
||||
}
|
||||
return std::min(size, limit);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
template <typename T, typename Mapper>
|
||||
void addVecToListModel(const std::vector<T> &input, GenericListModel &model,
|
||||
size_t maxCount, Mapper mapper)
|
||||
{
|
||||
const size_t count = sizeWithinLimit(input.size(), maxCount);
|
||||
model.reserve(model.rowCount() + count);
|
||||
|
||||
for (size_t i = 0; i < count; ++i)
|
||||
{
|
||||
model.addItem(mapper(input[i]));
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, typename Mapper>
|
||||
void addVecToStringList(const std::vector<T> &input, QStringList &list,
|
||||
size_t maxCount, Mapper mapper)
|
||||
{
|
||||
const size_t count = sizeWithinLimit(input.size(), maxCount);
|
||||
list.reserve(list.count() + count);
|
||||
|
||||
for (size_t i = 0; i < count; ++i)
|
||||
{
|
||||
list.push_back(mapper(input[i]));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace chatterino::completion
|
||||
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include "widgets/listview/GenericListModel.hpp"
|
||||
#include "widgets/splits/InputCompletionItem.hpp"
|
||||
|
||||
#include <QStringList>
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace chatterino::completion {
|
||||
|
||||
/// @brief A Source represents a source for generating completion suggestions.
|
||||
///
|
||||
/// The source can be queried to update its suggestions and then write the completion
|
||||
/// suggestions to a GenericListModel or QStringList depending on the consumer's
|
||||
/// requirements.
|
||||
///
|
||||
/// For example, consider providing emotes for completion. The Source instance
|
||||
/// initialized with every available emote in the channel (including global
|
||||
/// emotes). As the user updates their query by typing, the suggestions are
|
||||
/// refined and the output model is updated.
|
||||
class Source
|
||||
{
|
||||
public:
|
||||
virtual ~Source() = default;
|
||||
|
||||
/// @brief Updates the internal completion suggestions for the given query
|
||||
/// @param query Query to complete against
|
||||
virtual void update(const QString &query) = 0;
|
||||
|
||||
/// @brief Appends the internal completion suggestions to a GenericListModel
|
||||
/// @param model GenericListModel to add suggestions to
|
||||
/// @param maxCount Maximum number of suggestions. Zero indicates unlimited.
|
||||
virtual void addToListModel(GenericListModel &model,
|
||||
size_t maxCount = 0) const = 0;
|
||||
|
||||
/// @brief Appends the internal completion suggestions to a QStringList
|
||||
/// @param list QStringList to add suggestions to
|
||||
/// @param maxCount Maximum number of suggestions. Zero indicates unlimited.
|
||||
/// @param isFirstWord Whether the completion is the first word in the input
|
||||
virtual void addToStringList(QStringList &list, size_t maxCount = 0,
|
||||
bool isFirstWord = false) const = 0;
|
||||
};
|
||||
|
||||
}; // namespace chatterino::completion
|
||||
@@ -0,0 +1,80 @@
|
||||
#include "controllers/completion/sources/UnifiedSource.hpp"
|
||||
|
||||
namespace chatterino::completion {
|
||||
|
||||
UnifiedSource::UnifiedSource(
|
||||
const Channel *channel,
|
||||
std::unique_ptr<EmoteSource::EmoteStrategy> emoteStrategy,
|
||||
std::unique_ptr<UserSource::UserStrategy> userStrategy,
|
||||
ActionCallback callback)
|
||||
: emoteSource_(channel, std::move(emoteStrategy), callback)
|
||||
, usersSource_(channel, std::move(userStrategy), callback,
|
||||
false) // disable adding @ to front
|
||||
{
|
||||
}
|
||||
|
||||
void UnifiedSource::update(const QString &query)
|
||||
{
|
||||
this->emoteSource_.update(query);
|
||||
this->usersSource_.update(query);
|
||||
}
|
||||
|
||||
void UnifiedSource::addToListModel(GenericListModel &model,
|
||||
size_t maxCount) const
|
||||
{
|
||||
if (maxCount == 0)
|
||||
{
|
||||
this->emoteSource_.addToListModel(model, 0);
|
||||
this->usersSource_.addToListModel(model, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, make sure to only add maxCount elements in total. We prioritize
|
||||
// accepting results from the emote source before the users source (arbitrarily).
|
||||
|
||||
int startingSize = model.rowCount();
|
||||
|
||||
// Add up to maxCount elements
|
||||
this->emoteSource_.addToListModel(model, maxCount);
|
||||
|
||||
int used = model.rowCount() - startingSize;
|
||||
if (used >= maxCount)
|
||||
{
|
||||
// Used up our limit on emotes
|
||||
return;
|
||||
}
|
||||
|
||||
// Only add maxCount - used to ensure the total added doesn't exceed maxCount
|
||||
this->usersSource_.addToListModel(model, maxCount - used);
|
||||
}
|
||||
|
||||
void UnifiedSource::addToStringList(QStringList &list, size_t maxCount,
|
||||
bool isFirstWord) const
|
||||
{
|
||||
if (maxCount == 0)
|
||||
{
|
||||
this->emoteSource_.addToStringList(list, 0, isFirstWord);
|
||||
this->usersSource_.addToStringList(list, 0, isFirstWord);
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, make sure to only add maxCount elements in total. We prioritize
|
||||
// accepting results from the emote source before the users source (arbitrarily).
|
||||
|
||||
int startingSize = list.size();
|
||||
|
||||
// Add up to maxCount elements
|
||||
this->emoteSource_.addToStringList(list, maxCount, isFirstWord);
|
||||
|
||||
int used = list.size() - startingSize;
|
||||
if (used >= maxCount)
|
||||
{
|
||||
// Used up our limit on emotes
|
||||
return;
|
||||
}
|
||||
|
||||
// Only add maxCount - used to ensure the total added doesn't exceed maxCount
|
||||
this->usersSource_.addToStringList(list, maxCount - used, isFirstWord);
|
||||
}
|
||||
|
||||
} // namespace chatterino::completion
|
||||
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include "common/Channel.hpp"
|
||||
#include "controllers/completion/sources/EmoteSource.hpp"
|
||||
#include "controllers/completion/sources/Source.hpp"
|
||||
#include "controllers/completion/sources/UserSource.hpp"
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
namespace chatterino::completion {
|
||||
|
||||
class UnifiedSource : public Source
|
||||
{
|
||||
public:
|
||||
using ActionCallback = std::function<void(const QString &)>;
|
||||
|
||||
/// @brief Initializes a unified completion source for the given channel.
|
||||
/// Resolves both emotes and usernames for autocompletion.
|
||||
/// @param channel Channel to initialize emotes and users from. Must be a
|
||||
/// TwitchChannel or completion is a no-op.
|
||||
/// @param emoteStrategy Strategy for selecting emotes
|
||||
/// @param userStrategy Strategy for selecting users
|
||||
/// @param callback ActionCallback to invoke upon InputCompletionItem selection.
|
||||
/// See InputCompletionItem::action(). Can be nullptr.
|
||||
UnifiedSource(const Channel *channel,
|
||||
std::unique_ptr<EmoteSource::EmoteStrategy> emoteStrategy,
|
||||
std::unique_ptr<UserSource::UserStrategy> userStrategy,
|
||||
ActionCallback callback = nullptr);
|
||||
|
||||
void update(const QString &query) override;
|
||||
void addToListModel(GenericListModel &model,
|
||||
size_t maxCount = 0) const override;
|
||||
void addToStringList(QStringList &list, size_t maxCount = 0,
|
||||
bool isFirstWord = false) const override;
|
||||
|
||||
private:
|
||||
EmoteSource emoteSource_;
|
||||
UserSource usersSource_;
|
||||
};
|
||||
|
||||
} // namespace chatterino::completion
|
||||
@@ -0,0 +1,69 @@
|
||||
#include "controllers/completion/sources/UserSource.hpp"
|
||||
|
||||
#include "controllers/completion/sources/Helpers.hpp"
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
#include "util/Helpers.hpp"
|
||||
|
||||
namespace chatterino::completion {
|
||||
|
||||
UserSource::UserSource(const Channel *channel,
|
||||
std::unique_ptr<UserStrategy> strategy,
|
||||
ActionCallback callback, bool prependAt)
|
||||
: strategy_(std::move(strategy))
|
||||
, callback_(std::move(callback))
|
||||
, prependAt_(prependAt)
|
||||
{
|
||||
this->initializeFromChannel(channel);
|
||||
}
|
||||
|
||||
void UserSource::update(const QString &query)
|
||||
{
|
||||
this->output_.clear();
|
||||
if (this->strategy_)
|
||||
{
|
||||
this->strategy_->apply(this->items_, this->output_, query);
|
||||
}
|
||||
}
|
||||
|
||||
void UserSource::addToListModel(GenericListModel &model, size_t maxCount) const
|
||||
{
|
||||
addVecToListModel(this->output_, model, maxCount,
|
||||
[this](const UserItem &user) {
|
||||
return std::make_unique<InputCompletionItem>(
|
||||
nullptr, user.second, this->callback_);
|
||||
});
|
||||
}
|
||||
|
||||
void UserSource::addToStringList(QStringList &list, size_t maxCount,
|
||||
bool isFirstWord) const
|
||||
{
|
||||
bool mentionComma = getSettings()->mentionUsersWithComma;
|
||||
addVecToStringList(this->output_, list, maxCount,
|
||||
[this, isFirstWord, mentionComma](const UserItem &user) {
|
||||
const auto userMention = formatUserMention(
|
||||
user.second, isFirstWord, mentionComma);
|
||||
QString strTemplate = this->prependAt_
|
||||
? QStringLiteral("@%1 ")
|
||||
: QStringLiteral("%1 ");
|
||||
return strTemplate.arg(userMention);
|
||||
});
|
||||
}
|
||||
|
||||
void UserSource::initializeFromChannel(const Channel *channel)
|
||||
{
|
||||
const auto *tc = dynamic_cast<const TwitchChannel *>(channel);
|
||||
if (!tc)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this->items_ = tc->accessChatters()->all();
|
||||
}
|
||||
|
||||
const std::vector<UserItem> &UserSource::output() const
|
||||
{
|
||||
return this->output_;
|
||||
}
|
||||
|
||||
} // namespace chatterino::completion
|
||||
@@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
|
||||
#include "common/Channel.hpp"
|
||||
#include "controllers/completion/sources/Source.hpp"
|
||||
#include "controllers/completion/strategies/Strategy.hpp"
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace chatterino::completion {
|
||||
|
||||
using UserItem = std::pair<QString, QString>;
|
||||
|
||||
class UserSource : public Source
|
||||
{
|
||||
public:
|
||||
using ActionCallback = std::function<void(const QString &)>;
|
||||
using UserStrategy = Strategy<UserItem>;
|
||||
|
||||
/// @brief Initializes a source for UserItems from the given channel.
|
||||
/// @param channel Channel to initialize users from. Must be a TwitchChannel
|
||||
/// or completion is a no-op.
|
||||
/// @param strategy Strategy to apply
|
||||
/// @param callback ActionCallback to invoke upon InputCompletionItem selection.
|
||||
/// See InputCompletionItem::action(). Can be nullptr.
|
||||
/// @param prependAt Whether to prepend @ to string completion suggestions.
|
||||
UserSource(const Channel *channel, std::unique_ptr<UserStrategy> strategy,
|
||||
ActionCallback callback = nullptr, bool prependAt = true);
|
||||
|
||||
void update(const QString &query) override;
|
||||
void addToListModel(GenericListModel &model,
|
||||
size_t maxCount = 0) const override;
|
||||
void addToStringList(QStringList &list, size_t maxCount = 0,
|
||||
bool isFirstWord = false) const override;
|
||||
|
||||
const std::vector<UserItem> &output() const;
|
||||
|
||||
private:
|
||||
void initializeFromChannel(const Channel *channel);
|
||||
|
||||
std::unique_ptr<UserStrategy> strategy_;
|
||||
ActionCallback callback_;
|
||||
bool prependAt_;
|
||||
|
||||
std::vector<UserItem> items_{};
|
||||
std::vector<UserItem> output_{};
|
||||
};
|
||||
|
||||
} // namespace chatterino::completion
|
||||
Reference in New Issue
Block a user