fix: Commands now completable when not starting with / (#4846)
* Add commands to completable inputs when no prefix
This commit is contained in:
+1
-1
@@ -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_)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user