fix: Commands now completable when not starting with / (#4846)

* Add commands to completable inputs when no prefix
This commit is contained in:
Daniel Sage
2023-09-30 07:06:16 -04:00
committed by GitHub
parent bdd7d95092
commit 4d8b62364d
8 changed files with 123 additions and 84 deletions
+1 -1
View File
@@ -25,7 +25,7 @@
- Dev: Refactor `Image` & Image's `Frames`. (#4773)
- Dev: Add `WindowManager::getLastSelectedWindow()` to replace `getMainWindow()`. (#4816)
- Dev: Clarify signal connection lifetimes where applicable. (#4818)
- Dev: Laid the groundwork for advanced input completion strategies. (#4639)
- Dev: Laid the groundwork for advanced input completion strategies. (#4639, #4846)
## 2.4.5
@@ -83,11 +83,12 @@ std::optional<TabCompletionModel::SourceKind>
if (getSettings()->userCompletionOnlyWithAt)
{
return SourceKind::Emote;
// All kinds but user are possible
return SourceKind::EmoteCommand;
}
// Either is possible, use unified source
return SourceKind::EmoteAndUser;
// Any kind is possible
return SourceKind::EmoteUserCommand;
}
std::unique_ptr<completion::Source> TabCompletionModel::buildSource(
@@ -95,25 +96,55 @@ std::unique_ptr<completion::Source> TabCompletionModel::buildSource(
{
switch (kind)
{
case SourceKind::Emote:
return std::make_unique<completion::EmoteSource>(
&this->channel_,
std::make_unique<completion::ClassicTabEmoteStrategy>());
case SourceKind::User:
return std::make_unique<completion::UserSource>(
&this->channel_,
std::make_unique<completion::ClassicUserStrategy>());
case SourceKind::Command:
return std::make_unique<completion::CommandSource>(
std::make_unique<completion::CommandStrategy>(true));
case SourceKind::EmoteAndUser:
case SourceKind::Emote: {
return this->buildEmoteSource();
}
case SourceKind::User: {
return this->buildUserSource();
}
case SourceKind::Command: {
return this->buildCommandSource();
}
case SourceKind::EmoteCommand: {
std::vector<std::unique_ptr<completion::Source>> sources;
sources.push_back(this->buildEmoteSource());
sources.push_back(this->buildCommandSource());
return std::make_unique<completion::UnifiedSource>(
&this->channel_,
std::make_unique<completion::ClassicTabEmoteStrategy>(),
std::make_unique<completion::ClassicUserStrategy>());
std::move(sources));
}
case SourceKind::EmoteUserCommand: {
std::vector<std::unique_ptr<completion::Source>> sources;
sources.push_back(this->buildEmoteSource());
sources.push_back(this->buildUserSource());
sources.push_back(this->buildCommandSource());
return std::make_unique<completion::UnifiedSource>(
std::move(sources));
}
default:
return nullptr;
}
}
std::unique_ptr<completion::Source> TabCompletionModel::buildEmoteSource() const
{
return std::make_unique<completion::EmoteSource>(
&this->channel_,
std::make_unique<completion::ClassicTabEmoteStrategy>());
}
std::unique_ptr<completion::Source> TabCompletionModel::buildUserSource() const
{
return std::make_unique<completion::UserSource>(
&this->channel_, std::make_unique<completion::ClassicUserStrategy>());
}
std::unique_ptr<completion::Source> TabCompletionModel::buildCommandSource()
const
{
return std::make_unique<completion::CommandSource>(
std::make_unique<completion::CommandStrategy>(true));
}
} // namespace chatterino
@@ -30,7 +30,13 @@ public:
void updateResults(const QString &query, bool isFirstWord = false);
private:
enum class SourceKind { Emote, User, Command, EmoteAndUser };
enum class SourceKind {
Emote,
User,
Command,
EmoteCommand,
EmoteUserCommand
};
/// @brief Updates the internal completion source based on the current query.
/// The completion source will only change if the deduced completion kind
@@ -47,6 +53,10 @@ private:
std::unique_ptr<completion::Source> buildSource(SourceKind kind) const;
std::unique_ptr<completion::Source> buildEmoteSource() const;
std::unique_ptr<completion::Source> buildUserSource() const;
std::unique_ptr<completion::Source> buildCommandSource() const;
Channel &channel_;
std::unique_ptr<completion::Source> source_{};
std::optional<SourceKind> sourceKind_{};
@@ -14,11 +14,17 @@ namespace {
{
if (command.startsWith('/') || command.startsWith('.'))
{
out.push_back({command.mid(1), command.at(0)});
out.push_back({
.name = command.mid(1),
.prefix = command.at(0),
});
}
else
{
out.push_back({command, '/'});
out.push_back({
.name = command,
.prefix = "",
});
}
}
@@ -13,7 +13,7 @@ namespace chatterino::completion {
struct CommandItem {
QString name{};
QChar prefix{};
QString prefix{};
};
class CommandSource : public Source
@@ -2,21 +2,18 @@
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
UnifiedSource::UnifiedSource(std::vector<std::unique_ptr<Source>> sources)
: sources_(std::move(sources))
{
}
void UnifiedSource::update(const QString &query)
{
this->emoteSource_.update(query);
this->usersSource_.update(query);
// Update all sources
for (const auto &source : this->sources_)
{
source->update(query);
}
}
void UnifiedSource::addToListModel(GenericListModel &model,
@@ -24,28 +21,28 @@ void UnifiedSource::addToListModel(GenericListModel &model,
{
if (maxCount == 0)
{
this->emoteSource_.addToListModel(model, 0);
this->usersSource_.addToListModel(model, 0);
for (const auto &source : this->sources_)
{
source->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).
// Make sure to only add maxCount elements in total.
int startingSize = model.rowCount();
int used = 0;
// Add up to maxCount elements
this->emoteSource_.addToListModel(model, maxCount);
int used = model.rowCount() - startingSize;
if (used >= maxCount)
for (const auto &source : this->sources_)
{
// Used up our limit on emotes
return;
source->addToListModel(model, maxCount - used);
// Calculate how many items have been added so far
used = model.rowCount() - startingSize;
if (used >= maxCount)
{
// Used up all of limit
break;
}
}
// 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,
@@ -53,28 +50,28 @@ void UnifiedSource::addToStringList(QStringList &list, size_t maxCount,
{
if (maxCount == 0)
{
this->emoteSource_.addToStringList(list, 0, isFirstWord);
this->usersSource_.addToStringList(list, 0, isFirstWord);
for (const auto &source : this->sources_)
{
source->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).
// Make sure to only add maxCount elements in total.
int startingSize = list.size();
int used = 0;
// Add up to maxCount elements
this->emoteSource_.addToStringList(list, maxCount, isFirstWord);
int used = list.size() - startingSize;
if (used >= maxCount)
for (const auto &source : this->sources_)
{
// Used up our limit on emotes
return;
source->addToStringList(list, maxCount - used, isFirstWord);
// Calculate how many items have been added so far
used = list.size() - startingSize;
if (used >= maxCount)
{
// Used up all of limit
break;
}
}
// Only add maxCount - used to ensure the total added doesn't exceed maxCount
this->usersSource_.addToStringList(list, maxCount - used, isFirstWord);
}
} // namespace chatterino::completion
@@ -1,6 +1,7 @@
#pragma once
#include "common/Channel.hpp"
#include "controllers/completion/sources/CommandSource.hpp"
#include "controllers/completion/sources/EmoteSource.hpp"
#include "controllers/completion/sources/Source.hpp"
#include "controllers/completion/sources/UserSource.hpp"
@@ -13,20 +14,9 @@ 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);
/// @brief Initializes a unified completion source.
/// @param sources Vector of sources to unify
UnifiedSource(std::vector<std::unique_ptr<Source>> sources);
void update(const QString &query) override;
void addToListModel(GenericListModel &model,
@@ -35,8 +25,7 @@ public:
bool isFirstWord = false) const override;
private:
EmoteSource emoteSource_;
UserSource usersSource_;
std::vector<std::unique_ptr<Source>> sources_;
};
} // namespace chatterino::completion
@@ -2,6 +2,16 @@
namespace chatterino::completion {
QString normalizeQuery(const QString &query)
{
if (query.startsWith('/') || query.startsWith('.'))
{
return query.mid(1);
}
return query;
}
CommandStrategy::CommandStrategy(bool startsWithOnly)
: startsWithOnly_(startsWithOnly)
{
@@ -11,11 +21,7 @@ void CommandStrategy::apply(const std::vector<CommandItem> &items,
std::vector<CommandItem> &output,
const QString &query) const
{
QString normalizedQuery = query;
if (normalizedQuery.startsWith('/') || normalizedQuery.startsWith('.'))
{
normalizedQuery = normalizedQuery.mid(1);
}
QString normalizedQuery = normalizeQuery(query);
if (startsWithOnly_)
{