Refactor Tab completion for Twitch commands (#3144)
Co-authored-by: Rasmus Karlsson <rasmus.karlsson@pajlada.com>
This commit is contained in:
@@ -38,6 +38,7 @@
|
|||||||
- Minor: Show picked outcome in prediction badges. (#3357)
|
- Minor: Show picked outcome in prediction badges. (#3357)
|
||||||
- Minor: Add support for Emoji in IRC (#3354)
|
- Minor: Add support for Emoji in IRC (#3354)
|
||||||
- Minor: Moved `/live` logs to its own subdirectory. (Logs from before this change will still be available in `Channels -> live`). (#3393)
|
- Minor: Moved `/live` logs to its own subdirectory. (Logs from before this change will still be available in `Channels -> live`). (#3393)
|
||||||
|
- Minor: Added autocompletion for default Twitch commands starting with the dot (e.g. `.mods` which does the same as `/mods`). (#3144)
|
||||||
- Minor: Sorted usernames in `Users joined/parted` messages alphabetically. (#3421)
|
- Minor: Sorted usernames in `Users joined/parted` messages alphabetically. (#3421)
|
||||||
- Minor: Mod list, VIP list, and Users joined/parted messages are now searchable. (#3426)
|
- Minor: Mod list, VIP list, and Users joined/parted messages are now searchable. (#3426)
|
||||||
- Bugfix: Fix Split Input hotkeys not being available when input is hidden (#3362)
|
- Bugfix: Fix Split Input hotkeys not being available when input is hidden (#3362)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include "controllers/commands/CommandController.hpp"
|
#include "controllers/commands/CommandController.hpp"
|
||||||
#include "debug/Benchmark.hpp"
|
#include "debug/Benchmark.hpp"
|
||||||
#include "providers/twitch/TwitchChannel.hpp"
|
#include "providers/twitch/TwitchChannel.hpp"
|
||||||
|
#include "providers/twitch/TwitchCommon.hpp"
|
||||||
#include "providers/twitch/TwitchIrcServer.hpp"
|
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||||
#include "singletons/Emotes.hpp"
|
#include "singletons/Emotes.hpp"
|
||||||
#include "singletons/Settings.hpp"
|
#include "singletons/Settings.hpp"
|
||||||
@@ -85,21 +86,40 @@ void CompletionModel::refresh(const QString &prefix, bool isFirstWord)
|
|||||||
// Twitch channel
|
// Twitch channel
|
||||||
auto tc = dynamic_cast<TwitchChannel *>(&this->channel_);
|
auto tc = dynamic_cast<TwitchChannel *>(&this->channel_);
|
||||||
|
|
||||||
std::function<void(const QString &str, TaggedString::Type type)> addString;
|
auto addString = [=](const QString &str, TaggedString::Type type) {
|
||||||
if (getSettings()->prefixOnlyEmoteCompletion)
|
// Special case for handling default Twitch commands
|
||||||
{
|
if (type == TaggedString::TwitchCommand)
|
||||||
addString = [=](const QString &str, TaggedString::Type type) {
|
{
|
||||||
if (str.startsWith(prefix, Qt::CaseInsensitive))
|
if (prefix.size() < 2)
|
||||||
this->items_.emplace(str + " ", type);
|
{
|
||||||
};
|
return;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
auto prefixChar = prefix.at(0);
|
||||||
addString = [=](const QString &str, TaggedString::Type type) {
|
|
||||||
if (str.contains(prefix, Qt::CaseInsensitive))
|
static std::set<QChar> validPrefixChars{'/', '.'};
|
||||||
this->items_.emplace(str + " ", type);
|
|
||||||
};
|
if (validPrefixChars.find(prefixChar) == validPrefixChars.end())
|
||||||
}
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startsWithOrContains((prefixChar + str), prefix,
|
||||||
|
Qt::CaseInsensitive,
|
||||||
|
getSettings()->prefixOnlyEmoteCompletion))
|
||||||
|
{
|
||||||
|
this->items_.emplace((prefixChar + str + " "), type);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startsWithOrContains(str, prefix, Qt::CaseInsensitive,
|
||||||
|
getSettings()->prefixOnlyEmoteCompletion))
|
||||||
|
{
|
||||||
|
this->items_.emplace(str + " ", type);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (auto account = getApp()->accounts->twitch.getCurrent())
|
if (auto account = getApp()->accounts->twitch.getCurrent())
|
||||||
{
|
{
|
||||||
@@ -190,15 +210,22 @@ void CompletionModel::refresh(const QString &prefix, bool isFirstWord)
|
|||||||
addString(emote.first.string, TaggedString::Type::BTTVGlobalEmote);
|
addString(emote.first.string, TaggedString::Type::BTTVGlobalEmote);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Commands
|
// Custom Chatterino commands
|
||||||
for (auto &command : getApp()->commands->items_)
|
for (auto &command : getApp()->commands->items)
|
||||||
{
|
{
|
||||||
addString(command.name, TaggedString::Command);
|
addString(command.name, TaggedString::CustomCommand);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto &command : getApp()->commands->getDefaultTwitchCommandList())
|
// Default Chatterino commands
|
||||||
|
for (auto &command : getApp()->commands->getDefaultChatterinoCommandList())
|
||||||
{
|
{
|
||||||
addString(command, TaggedString::Command);
|
addString(command, TaggedString::ChatterinoCommand);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default Twitch commands
|
||||||
|
for (auto &command : TWITCH_DEFAULT_COMMANDS)
|
||||||
|
{
|
||||||
|
addString(command, TaggedString::TwitchCommand);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,9 @@ class CompletionModel : public QAbstractListModel
|
|||||||
EmoteEnd,
|
EmoteEnd,
|
||||||
// end emotes
|
// end emotes
|
||||||
|
|
||||||
Command,
|
CustomCommand,
|
||||||
|
ChatterinoCommand,
|
||||||
|
TwitchCommand,
|
||||||
};
|
};
|
||||||
|
|
||||||
TaggedString(const QString &string, Type type);
|
TaggedString(const QString &string, Type type);
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include "messages/Message.hpp"
|
#include "messages/Message.hpp"
|
||||||
#include "messages/MessageBuilder.hpp"
|
#include "messages/MessageBuilder.hpp"
|
||||||
#include "messages/MessageElement.hpp"
|
#include "messages/MessageElement.hpp"
|
||||||
|
#include "providers/twitch/TwitchCommon.hpp"
|
||||||
#include "providers/twitch/TwitchIrcServer.hpp"
|
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||||
#include "providers/twitch/api/Helix.hpp"
|
#include "providers/twitch/api/Helix.hpp"
|
||||||
#include "singletons/Emotes.hpp"
|
#include "singletons/Emotes.hpp"
|
||||||
@@ -34,42 +35,6 @@
|
|||||||
namespace {
|
namespace {
|
||||||
using namespace chatterino;
|
using namespace chatterino;
|
||||||
|
|
||||||
static const QStringList twitchDefaultCommands{
|
|
||||||
"/help",
|
|
||||||
"/w",
|
|
||||||
"/me",
|
|
||||||
"/disconnect",
|
|
||||||
"/mods",
|
|
||||||
"/vips",
|
|
||||||
"/color",
|
|
||||||
"/commercial",
|
|
||||||
"/mod",
|
|
||||||
"/unmod",
|
|
||||||
"/vip",
|
|
||||||
"/unvip",
|
|
||||||
"/ban",
|
|
||||||
"/unban",
|
|
||||||
"/timeout",
|
|
||||||
"/untimeout",
|
|
||||||
"/slow",
|
|
||||||
"/slowoff",
|
|
||||||
"/r9kbeta",
|
|
||||||
"/r9kbetaoff",
|
|
||||||
"/emoteonly",
|
|
||||||
"/emoteonlyoff",
|
|
||||||
"/clear",
|
|
||||||
"/subscribers",
|
|
||||||
"/subscribersoff",
|
|
||||||
"/followers",
|
|
||||||
"/followersoff",
|
|
||||||
"/host",
|
|
||||||
"/unhost",
|
|
||||||
"/raid",
|
|
||||||
"/unraid",
|
|
||||||
};
|
|
||||||
|
|
||||||
static const QStringList whisperCommands{"/w", ".w"};
|
|
||||||
|
|
||||||
// stripUserName removes any @ prefix or , suffix to make it more suitable for command use
|
// stripUserName removes any @ prefix or , suffix to make it more suitable for command use
|
||||||
void stripUserName(QString &userName)
|
void stripUserName(QString &userName)
|
||||||
{
|
{
|
||||||
@@ -217,7 +182,7 @@ bool appendWhisperMessageStringLocally(const QString &textNoEmoji)
|
|||||||
|
|
||||||
QString commandName = words[0];
|
QString commandName = words[0];
|
||||||
|
|
||||||
if (whisperCommands.contains(commandName, Qt::CaseInsensitive))
|
if (TWITCH_WHISPER_COMMANDS.contains(commandName, Qt::CaseInsensitive))
|
||||||
{
|
{
|
||||||
if (words.length() > 2)
|
if (words.length() > 2)
|
||||||
{
|
{
|
||||||
@@ -298,13 +263,11 @@ namespace chatterino {
|
|||||||
|
|
||||||
void CommandController::initialize(Settings &, Paths &paths)
|
void CommandController::initialize(Settings &, Paths &paths)
|
||||||
{
|
{
|
||||||
this->commandAutoCompletions_ = twitchDefaultCommands;
|
|
||||||
|
|
||||||
// Update commands map when the vector of commands has been updated
|
// Update commands map when the vector of commands has been updated
|
||||||
auto addFirstMatchToMap = [this](auto args) {
|
auto addFirstMatchToMap = [this](auto args) {
|
||||||
this->userCommands_.remove(args.item.name);
|
this->userCommands_.remove(args.item.name);
|
||||||
|
|
||||||
for (const Command &cmd : this->items_)
|
for (const Command &cmd : this->items)
|
||||||
{
|
{
|
||||||
if (cmd.name == args.item.name)
|
if (cmd.name == args.item.name)
|
||||||
{
|
{
|
||||||
@@ -315,7 +278,7 @@ void CommandController::initialize(Settings &, Paths &paths)
|
|||||||
|
|
||||||
int maxSpaces = 0;
|
int maxSpaces = 0;
|
||||||
|
|
||||||
for (const Command &cmd : this->items_)
|
for (const Command &cmd : this->items)
|
||||||
{
|
{
|
||||||
auto localMaxSpaces = cmd.name.count(' ');
|
auto localMaxSpaces = cmd.name.count(' ');
|
||||||
if (localMaxSpaces > maxSpaces)
|
if (localMaxSpaces > maxSpaces)
|
||||||
@@ -326,8 +289,8 @@ void CommandController::initialize(Settings &, Paths &paths)
|
|||||||
|
|
||||||
this->maxSpaces_ = maxSpaces;
|
this->maxSpaces_ = maxSpaces;
|
||||||
};
|
};
|
||||||
this->items_.itemInserted.connect(addFirstMatchToMap);
|
this->items.itemInserted.connect(addFirstMatchToMap);
|
||||||
this->items_.itemRemoved.connect(addFirstMatchToMap);
|
this->items.itemRemoved.connect(addFirstMatchToMap);
|
||||||
|
|
||||||
// Initialize setting manager for commands.json
|
// Initialize setting manager for commands.json
|
||||||
auto path = combinePath(paths.settingsDirectory, "commands.json");
|
auto path = combinePath(paths.settingsDirectory, "commands.json");
|
||||||
@@ -343,8 +306,8 @@ void CommandController::initialize(Settings &, Paths &paths)
|
|||||||
|
|
||||||
// Update the setting when the vector of commands has been updated (most
|
// Update the setting when the vector of commands has been updated (most
|
||||||
// likely from the settings dialog)
|
// likely from the settings dialog)
|
||||||
this->items_.delayedItemsChanged.connect([this] {
|
this->items.delayedItemsChanged.connect([this] {
|
||||||
this->commandsSetting_->setValue(this->items_.raw());
|
this->commandsSetting_->setValue(this->items.raw());
|
||||||
});
|
});
|
||||||
|
|
||||||
// Load commands from commands.json
|
// Load commands from commands.json
|
||||||
@@ -354,7 +317,7 @@ void CommandController::initialize(Settings &, Paths &paths)
|
|||||||
// of commands)
|
// of commands)
|
||||||
for (const auto &command : this->commandsSetting_->getValue())
|
for (const auto &command : this->commandsSetting_->getValue())
|
||||||
{
|
{
|
||||||
this->items_.append(command);
|
this->items.append(command);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Deprecated commands
|
/// Deprecated commands
|
||||||
@@ -918,7 +881,7 @@ void CommandController::save()
|
|||||||
CommandModel *CommandController::createModel(QObject *parent)
|
CommandModel *CommandController::createModel(QObject *parent)
|
||||||
{
|
{
|
||||||
CommandModel *model = new CommandModel(parent);
|
CommandModel *model = new CommandModel(parent);
|
||||||
model->initialize(&this->items_);
|
model->initialize(&this->items);
|
||||||
|
|
||||||
return model;
|
return model;
|
||||||
}
|
}
|
||||||
@@ -939,7 +902,7 @@ QString CommandController::execCommand(const QString &textNoEmoji,
|
|||||||
// works in a valid Twitch channel and /whispers, etc...
|
// works in a valid Twitch channel and /whispers, etc...
|
||||||
if (!dryRun && channel->isTwitchChannel())
|
if (!dryRun && channel->isTwitchChannel())
|
||||||
{
|
{
|
||||||
if (whisperCommands.contains(commandName, Qt::CaseInsensitive))
|
if (TWITCH_WHISPER_COMMANDS.contains(commandName, Qt::CaseInsensitive))
|
||||||
{
|
{
|
||||||
if (words.length() > 2)
|
if (words.length() > 2)
|
||||||
{
|
{
|
||||||
@@ -1003,7 +966,7 @@ void CommandController::registerCommand(QString commandName,
|
|||||||
|
|
||||||
this->commands_[commandName] = commandFunction;
|
this->commands_[commandName] = commandFunction;
|
||||||
|
|
||||||
this->commandAutoCompletions_.append(commandName);
|
this->defaultChatterinoCommandAutoCompletions_.append(commandName);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString CommandController::execCustomCommand(const QStringList &words,
|
QString CommandController::execCustomCommand(const QStringList &words,
|
||||||
@@ -1114,9 +1077,9 @@ QString CommandController::execCustomCommand(const QStringList &words,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList CommandController::getDefaultTwitchCommandList()
|
QStringList CommandController::getDefaultChatterinoCommandList()
|
||||||
{
|
{
|
||||||
return this->commandAutoCompletions_;
|
return this->defaultChatterinoCommandAutoCompletions_;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|||||||
@@ -23,11 +23,11 @@ class CommandModel;
|
|||||||
class CommandController final : public Singleton
|
class CommandController final : public Singleton
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
SignalVector<Command> items_;
|
SignalVector<Command> items;
|
||||||
|
|
||||||
QString execCommand(const QString &text, std::shared_ptr<Channel> channel,
|
QString execCommand(const QString &text, std::shared_ptr<Channel> channel,
|
||||||
bool dryRun);
|
bool dryRun);
|
||||||
QStringList getDefaultTwitchCommandList();
|
QStringList getDefaultChatterinoCommandList();
|
||||||
|
|
||||||
virtual void initialize(Settings &, Paths &paths) override;
|
virtual void initialize(Settings &, Paths &paths) override;
|
||||||
virtual void save() override;
|
virtual void save() override;
|
||||||
@@ -61,7 +61,7 @@ private:
|
|||||||
std::unique_ptr<pajlada::Settings::Setting<std::vector<Command>>>
|
std::unique_ptr<pajlada::Settings::Setting<std::vector<Command>>>
|
||||||
commandsSetting_;
|
commandsSetting_;
|
||||||
|
|
||||||
QStringList commandAutoCompletions_;
|
QStringList defaultChatterinoCommandAutoCompletions_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|||||||
@@ -38,4 +38,41 @@ static const std::vector<QColor> TWITCH_USERNAME_COLORS = {
|
|||||||
{0, 255, 127}, // SpringGreen
|
{0, 255, 127}, // SpringGreen
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static const QStringList TWITCH_DEFAULT_COMMANDS{
|
||||||
|
"help",
|
||||||
|
"w",
|
||||||
|
"me",
|
||||||
|
"disconnect",
|
||||||
|
"mods",
|
||||||
|
"vips",
|
||||||
|
"color",
|
||||||
|
"commercial",
|
||||||
|
"mod",
|
||||||
|
"unmod",
|
||||||
|
"vip",
|
||||||
|
"unvip",
|
||||||
|
"ban",
|
||||||
|
"unban",
|
||||||
|
"timeout",
|
||||||
|
"untimeout",
|
||||||
|
"slow",
|
||||||
|
"slowoff",
|
||||||
|
"r9kbeta",
|
||||||
|
"r9kbetaoff",
|
||||||
|
"emoteonly",
|
||||||
|
"emoteonlyoff",
|
||||||
|
"clear",
|
||||||
|
"subscribers",
|
||||||
|
"subscribersoff",
|
||||||
|
"followers",
|
||||||
|
"followersoff",
|
||||||
|
"host",
|
||||||
|
"unhost",
|
||||||
|
"raid",
|
||||||
|
"unraid",
|
||||||
|
"delete",
|
||||||
|
};
|
||||||
|
|
||||||
|
static const QStringList TWITCH_WHISPER_COMMANDS{"/w", ".w"};
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|||||||
@@ -7,6 +7,17 @@
|
|||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
|
bool startsWithOrContains(const QString &str1, const QString &str2,
|
||||||
|
Qt::CaseSensitivity caseSensitivity, bool startsWith)
|
||||||
|
{
|
||||||
|
if (startsWith)
|
||||||
|
{
|
||||||
|
return str1.startsWith(str2, caseSensitivity);
|
||||||
|
}
|
||||||
|
|
||||||
|
return str1.contains(str2, caseSensitivity);
|
||||||
|
}
|
||||||
|
|
||||||
QString generateUuid()
|
QString generateUuid()
|
||||||
{
|
{
|
||||||
auto uuid = QUuid::createUuid();
|
auto uuid = QUuid::createUuid();
|
||||||
|
|||||||
@@ -5,6 +5,13 @@
|
|||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief startsWithOrContains is a wrapper for checking
|
||||||
|
* whether str1 starts with or contains str2 within itself
|
||||||
|
**/
|
||||||
|
bool startsWithOrContains(const QString &str1, const QString &str2,
|
||||||
|
Qt::CaseSensitivity caseSensitivity, bool startsWith);
|
||||||
|
|
||||||
QString generateUuid();
|
QString generateUuid();
|
||||||
|
|
||||||
QString formatRichLink(const QString &url, bool file = false);
|
QString formatRichLink(const QString &url, bool file = false);
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ CommandPage::CommandPage()
|
|||||||
view->setTitles({"Trigger", "Command"});
|
view->setTitles({"Trigger", "Command"});
|
||||||
view->getTableView()->horizontalHeader()->setStretchLastSection(true);
|
view->getTableView()->horizontalHeader()->setStretchLastSection(true);
|
||||||
view->addButtonPressed.connect([] {
|
view->addButtonPressed.connect([] {
|
||||||
getApp()->commands->items_.append(
|
getApp()->commands->items.append(
|
||||||
Command{"/command", "I made a new command HeyGuys"});
|
Command{"/command", "I made a new command HeyGuys"});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -65,7 +65,7 @@ CommandPage::CommandPage()
|
|||||||
{
|
{
|
||||||
if (int index = line.indexOf(' '); index != -1)
|
if (int index = line.indexOf(' '); index != -1)
|
||||||
{
|
{
|
||||||
getApp()->commands->items_.insert(
|
getApp()->commands->items.insert(
|
||||||
Command(line.mid(0, index), line.mid(index + 1)));
|
Command(line.mid(0, index), line.mid(index + 1)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user