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: Refactor `Image` & Image's `Frames`. (#4773)
|
||||||
- Dev: Add `WindowManager::getLastSelectedWindow()` to replace `getMainWindow()`. (#4816)
|
- Dev: Add `WindowManager::getLastSelectedWindow()` to replace `getMainWindow()`. (#4816)
|
||||||
- Dev: Clarify signal connection lifetimes where applicable. (#4818)
|
- 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
|
## 2.4.5
|
||||||
|
|
||||||
|
|||||||
@@ -83,11 +83,12 @@ std::optional<TabCompletionModel::SourceKind>
|
|||||||
|
|
||||||
if (getSettings()->userCompletionOnlyWithAt)
|
if (getSettings()->userCompletionOnlyWithAt)
|
||||||
{
|
{
|
||||||
return SourceKind::Emote;
|
// All kinds but user are possible
|
||||||
|
return SourceKind::EmoteCommand;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Either is possible, use unified source
|
// Any kind is possible
|
||||||
return SourceKind::EmoteAndUser;
|
return SourceKind::EmoteUserCommand;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<completion::Source> TabCompletionModel::buildSource(
|
std::unique_ptr<completion::Source> TabCompletionModel::buildSource(
|
||||||
@@ -95,25 +96,55 @@ std::unique_ptr<completion::Source> TabCompletionModel::buildSource(
|
|||||||
{
|
{
|
||||||
switch (kind)
|
switch (kind)
|
||||||
{
|
{
|
||||||
case SourceKind::Emote:
|
case SourceKind::Emote: {
|
||||||
return std::make_unique<completion::EmoteSource>(
|
return this->buildEmoteSource();
|
||||||
&this->channel_,
|
}
|
||||||
std::make_unique<completion::ClassicTabEmoteStrategy>());
|
case SourceKind::User: {
|
||||||
case SourceKind::User:
|
return this->buildUserSource();
|
||||||
return std::make_unique<completion::UserSource>(
|
}
|
||||||
&this->channel_,
|
case SourceKind::Command: {
|
||||||
std::make_unique<completion::ClassicUserStrategy>());
|
return this->buildCommandSource();
|
||||||
case SourceKind::Command:
|
}
|
||||||
return std::make_unique<completion::CommandSource>(
|
case SourceKind::EmoteCommand: {
|
||||||
std::make_unique<completion::CommandStrategy>(true));
|
std::vector<std::unique_ptr<completion::Source>> sources;
|
||||||
case SourceKind::EmoteAndUser:
|
sources.push_back(this->buildEmoteSource());
|
||||||
|
sources.push_back(this->buildCommandSource());
|
||||||
|
|
||||||
return std::make_unique<completion::UnifiedSource>(
|
return std::make_unique<completion::UnifiedSource>(
|
||||||
&this->channel_,
|
std::move(sources));
|
||||||
std::make_unique<completion::ClassicTabEmoteStrategy>(),
|
}
|
||||||
std::make_unique<completion::ClassicUserStrategy>());
|
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:
|
default:
|
||||||
return nullptr;
|
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
|
} // namespace chatterino
|
||||||
|
|||||||
@@ -30,7 +30,13 @@ public:
|
|||||||
void updateResults(const QString &query, bool isFirstWord = false);
|
void updateResults(const QString &query, bool isFirstWord = false);
|
||||||
|
|
||||||
private:
|
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.
|
/// @brief Updates the internal completion source based on the current query.
|
||||||
/// The completion source will only change if the deduced completion kind
|
/// 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> 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_;
|
Channel &channel_;
|
||||||
std::unique_ptr<completion::Source> source_{};
|
std::unique_ptr<completion::Source> source_{};
|
||||||
std::optional<SourceKind> sourceKind_{};
|
std::optional<SourceKind> sourceKind_{};
|
||||||
|
|||||||
@@ -14,11 +14,17 @@ namespace {
|
|||||||
{
|
{
|
||||||
if (command.startsWith('/') || command.startsWith('.'))
|
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
|
else
|
||||||
{
|
{
|
||||||
out.push_back({command, '/'});
|
out.push_back({
|
||||||
|
.name = command,
|
||||||
|
.prefix = "",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ namespace chatterino::completion {
|
|||||||
|
|
||||||
struct CommandItem {
|
struct CommandItem {
|
||||||
QString name{};
|
QString name{};
|
||||||
QChar prefix{};
|
QString prefix{};
|
||||||
};
|
};
|
||||||
|
|
||||||
class CommandSource : public Source
|
class CommandSource : public Source
|
||||||
|
|||||||
@@ -2,21 +2,18 @@
|
|||||||
|
|
||||||
namespace chatterino::completion {
|
namespace chatterino::completion {
|
||||||
|
|
||||||
UnifiedSource::UnifiedSource(
|
UnifiedSource::UnifiedSource(std::vector<std::unique_ptr<Source>> sources)
|
||||||
const Channel *channel,
|
: sources_(std::move(sources))
|
||||||
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)
|
void UnifiedSource::update(const QString &query)
|
||||||
{
|
{
|
||||||
this->emoteSource_.update(query);
|
// Update all sources
|
||||||
this->usersSource_.update(query);
|
for (const auto &source : this->sources_)
|
||||||
|
{
|
||||||
|
source->update(query);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void UnifiedSource::addToListModel(GenericListModel &model,
|
void UnifiedSource::addToListModel(GenericListModel &model,
|
||||||
@@ -24,28 +21,28 @@ void UnifiedSource::addToListModel(GenericListModel &model,
|
|||||||
{
|
{
|
||||||
if (maxCount == 0)
|
if (maxCount == 0)
|
||||||
{
|
{
|
||||||
this->emoteSource_.addToListModel(model, 0);
|
for (const auto &source : this->sources_)
|
||||||
this->usersSource_.addToListModel(model, 0);
|
{
|
||||||
|
source->addToListModel(model, 0);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, make sure to only add maxCount elements in total. We prioritize
|
// Make sure to only add maxCount elements in total.
|
||||||
// accepting results from the emote source before the users source (arbitrarily).
|
|
||||||
|
|
||||||
int startingSize = model.rowCount();
|
int startingSize = model.rowCount();
|
||||||
|
int used = 0;
|
||||||
|
|
||||||
// Add up to maxCount elements
|
for (const auto &source : this->sources_)
|
||||||
this->emoteSource_.addToListModel(model, maxCount);
|
|
||||||
|
|
||||||
int used = model.rowCount() - startingSize;
|
|
||||||
if (used >= maxCount)
|
|
||||||
{
|
{
|
||||||
// Used up our limit on emotes
|
source->addToListModel(model, maxCount - used);
|
||||||
return;
|
// 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,
|
void UnifiedSource::addToStringList(QStringList &list, size_t maxCount,
|
||||||
@@ -53,28 +50,28 @@ void UnifiedSource::addToStringList(QStringList &list, size_t maxCount,
|
|||||||
{
|
{
|
||||||
if (maxCount == 0)
|
if (maxCount == 0)
|
||||||
{
|
{
|
||||||
this->emoteSource_.addToStringList(list, 0, isFirstWord);
|
for (const auto &source : this->sources_)
|
||||||
this->usersSource_.addToStringList(list, 0, isFirstWord);
|
{
|
||||||
|
source->addToStringList(list, 0, isFirstWord);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, make sure to only add maxCount elements in total. We prioritize
|
// Make sure to only add maxCount elements in total.
|
||||||
// accepting results from the emote source before the users source (arbitrarily).
|
|
||||||
|
|
||||||
int startingSize = list.size();
|
int startingSize = list.size();
|
||||||
|
int used = 0;
|
||||||
|
|
||||||
// Add up to maxCount elements
|
for (const auto &source : this->sources_)
|
||||||
this->emoteSource_.addToStringList(list, maxCount, isFirstWord);
|
|
||||||
|
|
||||||
int used = list.size() - startingSize;
|
|
||||||
if (used >= maxCount)
|
|
||||||
{
|
{
|
||||||
// Used up our limit on emotes
|
source->addToStringList(list, maxCount - used, isFirstWord);
|
||||||
return;
|
// 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
|
} // namespace chatterino::completion
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "common/Channel.hpp"
|
#include "common/Channel.hpp"
|
||||||
|
#include "controllers/completion/sources/CommandSource.hpp"
|
||||||
#include "controllers/completion/sources/EmoteSource.hpp"
|
#include "controllers/completion/sources/EmoteSource.hpp"
|
||||||
#include "controllers/completion/sources/Source.hpp"
|
#include "controllers/completion/sources/Source.hpp"
|
||||||
#include "controllers/completion/sources/UserSource.hpp"
|
#include "controllers/completion/sources/UserSource.hpp"
|
||||||
@@ -13,20 +14,9 @@ namespace chatterino::completion {
|
|||||||
class UnifiedSource : public Source
|
class UnifiedSource : public Source
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
using ActionCallback = std::function<void(const QString &)>;
|
/// @brief Initializes a unified completion source.
|
||||||
|
/// @param sources Vector of sources to unify
|
||||||
/// @brief Initializes a unified completion source for the given channel.
|
UnifiedSource(std::vector<std::unique_ptr<Source>> sources);
|
||||||
/// 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 update(const QString &query) override;
|
||||||
void addToListModel(GenericListModel &model,
|
void addToListModel(GenericListModel &model,
|
||||||
@@ -35,8 +25,7 @@ public:
|
|||||||
bool isFirstWord = false) const override;
|
bool isFirstWord = false) const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
EmoteSource emoteSource_;
|
std::vector<std::unique_ptr<Source>> sources_;
|
||||||
UserSource usersSource_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace chatterino::completion
|
} // namespace chatterino::completion
|
||||||
|
|||||||
@@ -2,6 +2,16 @@
|
|||||||
|
|
||||||
namespace chatterino::completion {
|
namespace chatterino::completion {
|
||||||
|
|
||||||
|
QString normalizeQuery(const QString &query)
|
||||||
|
{
|
||||||
|
if (query.startsWith('/') || query.startsWith('.'))
|
||||||
|
{
|
||||||
|
return query.mid(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
CommandStrategy::CommandStrategy(bool startsWithOnly)
|
CommandStrategy::CommandStrategy(bool startsWithOnly)
|
||||||
: startsWithOnly_(startsWithOnly)
|
: startsWithOnly_(startsWithOnly)
|
||||||
{
|
{
|
||||||
@@ -11,11 +21,7 @@ void CommandStrategy::apply(const std::vector<CommandItem> &items,
|
|||||||
std::vector<CommandItem> &output,
|
std::vector<CommandItem> &output,
|
||||||
const QString &query) const
|
const QString &query) const
|
||||||
{
|
{
|
||||||
QString normalizedQuery = query;
|
QString normalizedQuery = normalizeQuery(query);
|
||||||
if (normalizedQuery.startsWith('/') || normalizedQuery.startsWith('.'))
|
|
||||||
{
|
|
||||||
normalizedQuery = normalizedQuery.mid(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (startsWithOnly_)
|
if (startsWithOnly_)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user