mirror of
https://github.com/Chatterino/chatterino2.git
synced 2024-11-13 19:49:51 +01:00
optimize chatter list (#2814)
* optimize chatter list * changelog * Fix tests Co-authored-by: Rasmus Karlsson <rasmus.karlsson@pajlada.com>
This commit is contained in:
parent
7659dc27ae
commit
3fddafb867
|
@ -5,6 +5,7 @@
|
||||||
- Minor: Added moderation buttons to search popup when searching in a split with moderation mode enabled. (#2148, #2803)
|
- Minor: Added moderation buttons to search popup when searching in a split with moderation mode enabled. (#2148, #2803)
|
||||||
- Minor: Made "#channel" in `/mentions` tab show in usercards and in the search popup. (#2802)
|
- Minor: Made "#channel" in `/mentions` tab show in usercards and in the search popup. (#2802)
|
||||||
- Minor: Added settings to disable custom FrankerFaceZ VIP/mod badges. (#2693, #2759)
|
- Minor: Added settings to disable custom FrankerFaceZ VIP/mod badges. (#2693, #2759)
|
||||||
|
- Minor: Limit the number of recent chatters to improve memory usage and reduce freezes. (#2796, #2814)
|
||||||
- Minor: Added `/popout` command. Usage: `/popout [channel]`. It opens browser chat for the provided channel. Can also be used without arguments to open current channels browser chat. (#2556, #2812)
|
- Minor: Added `/popout` command. Usage: `/popout [channel]`. It opens browser chat for the provided channel. Can also be used without arguments to open current channels browser chat. (#2556, #2812)
|
||||||
- Bugfix: Fixed FFZ emote links for global emotes (#2807, #2808)
|
- Bugfix: Fixed FFZ emote links for global emotes (#2807, #2808)
|
||||||
|
|
||||||
|
|
|
@ -127,6 +127,7 @@ SOURCES += \
|
||||||
src/common/Args.cpp \
|
src/common/Args.cpp \
|
||||||
src/common/Channel.cpp \
|
src/common/Channel.cpp \
|
||||||
src/common/ChannelChatters.cpp \
|
src/common/ChannelChatters.cpp \
|
||||||
|
src/common/ChatterSet.cpp \
|
||||||
src/common/ChatterinoSetting.cpp \
|
src/common/ChatterinoSetting.cpp \
|
||||||
src/common/CompletionModel.cpp \
|
src/common/CompletionModel.cpp \
|
||||||
src/common/Credentials.cpp \
|
src/common/Credentials.cpp \
|
||||||
|
@ -139,7 +140,6 @@ SOURCES += \
|
||||||
src/common/NetworkPrivate.cpp \
|
src/common/NetworkPrivate.cpp \
|
||||||
src/common/NetworkRequest.cpp \
|
src/common/NetworkRequest.cpp \
|
||||||
src/common/NetworkResult.cpp \
|
src/common/NetworkResult.cpp \
|
||||||
src/common/UsernameSet.cpp \
|
|
||||||
src/common/Version.cpp \
|
src/common/Version.cpp \
|
||||||
src/common/WindowDescriptors.cpp \
|
src/common/WindowDescriptors.cpp \
|
||||||
src/common/QLogging.cpp \
|
src/common/QLogging.cpp \
|
||||||
|
@ -340,6 +340,7 @@ HEADERS += \
|
||||||
src/common/Atomic.hpp \
|
src/common/Atomic.hpp \
|
||||||
src/common/Channel.hpp \
|
src/common/Channel.hpp \
|
||||||
src/common/ChannelChatters.hpp \
|
src/common/ChannelChatters.hpp \
|
||||||
|
src/common/ChatterSet.hpp \
|
||||||
src/common/ChatterinoSetting.hpp \
|
src/common/ChatterinoSetting.hpp \
|
||||||
src/common/Common.hpp \
|
src/common/Common.hpp \
|
||||||
src/common/CompletionModel.hpp \
|
src/common/CompletionModel.hpp \
|
||||||
|
@ -363,7 +364,6 @@ HEADERS += \
|
||||||
src/common/SignalVectorModel.hpp \
|
src/common/SignalVectorModel.hpp \
|
||||||
src/common/Singleton.hpp \
|
src/common/Singleton.hpp \
|
||||||
src/common/UniqueAccess.hpp \
|
src/common/UniqueAccess.hpp \
|
||||||
src/common/UsernameSet.hpp \
|
|
||||||
src/common/Version.hpp \
|
src/common/Version.hpp \
|
||||||
src/common/QLogging.hpp \
|
src/common/QLogging.hpp \
|
||||||
src/controllers/accounts/Account.hpp \
|
src/controllers/accounts/Account.hpp \
|
||||||
|
|
35
lib/lrucache/.clang-format
Normal file
35
lib/lrucache/.clang-format
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
Language: Cpp
|
||||||
|
|
||||||
|
AccessModifierOffset: -4
|
||||||
|
AlignEscapedNewlinesLeft: true
|
||||||
|
AllowShortFunctionsOnASingleLine: false
|
||||||
|
AllowShortIfStatementsOnASingleLine: false
|
||||||
|
AllowShortLambdasOnASingleLine: Empty
|
||||||
|
AllowShortLoopsOnASingleLine: false
|
||||||
|
AlwaysBreakAfterDefinitionReturnType: false
|
||||||
|
AlwaysBreakBeforeMultilineStrings: false
|
||||||
|
BasedOnStyle: Google
|
||||||
|
BraceWrapping: {
|
||||||
|
AfterClass: 'true'
|
||||||
|
AfterControlStatement: 'true'
|
||||||
|
AfterFunction: 'true'
|
||||||
|
AfterNamespace: 'false'
|
||||||
|
BeforeCatch: 'true'
|
||||||
|
BeforeElse: 'true'
|
||||||
|
}
|
||||||
|
BreakBeforeBraces: Custom
|
||||||
|
BreakConstructorInitializersBeforeComma: true
|
||||||
|
ColumnLimit: 80
|
||||||
|
ConstructorInitializerAllOnOneLineOrOnePerLine: false
|
||||||
|
DerivePointerBinding: false
|
||||||
|
FixNamespaceComments: true
|
||||||
|
IndentCaseLabels: true
|
||||||
|
IndentWidth: 4
|
||||||
|
IndentWrappedFunctionNames: true
|
||||||
|
IndentPPDirectives: AfterHash
|
||||||
|
IncludeBlocks: Preserve
|
||||||
|
NamespaceIndentation: Inner
|
||||||
|
PointerBindsToType: false
|
||||||
|
SpacesBeforeTrailingComments: 2
|
||||||
|
Standard: Auto
|
||||||
|
ReflowComments: false
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* File: lrucache.hpp
|
* File: lrucache.hpp
|
||||||
* Author: Alexander Ponomarev
|
* Original Author: Alexander Ponomarev
|
||||||
*
|
*
|
||||||
* Created on June 20, 2013, 5:09 PM
|
* Created on June 20, 2013, 5:09 PM
|
||||||
*/
|
*/
|
||||||
|
@ -8,33 +8,60 @@
|
||||||
#ifndef _LRUCACHE_HPP_INCLUDED_
|
#ifndef _LRUCACHE_HPP_INCLUDED_
|
||||||
#define _LRUCACHE_HPP_INCLUDED_
|
#define _LRUCACHE_HPP_INCLUDED_
|
||||||
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <list>
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
#include <list>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
namespace cache {
|
namespace cache {
|
||||||
|
|
||||||
template <typename key_t, typename value_t>
|
template <typename key_t, typename value_t>
|
||||||
class lru_cache {
|
class lru_cache
|
||||||
|
{
|
||||||
public:
|
public:
|
||||||
typedef typename std::pair<key_t, value_t> key_value_pair_t;
|
typedef typename std::pair<key_t, value_t> key_value_pair_t;
|
||||||
typedef typename std::list<key_value_pair_t>::iterator list_iterator_t;
|
typedef typename std::list<key_value_pair_t>::iterator list_iterator_t;
|
||||||
|
|
||||||
lru_cache(size_t max_size) :
|
lru_cache(size_t max_size)
|
||||||
_max_size(max_size) {
|
: _max_size(max_size)
|
||||||
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void put(const key_t& key, const value_t& value) {
|
// copy doesn't make sense since we reference the linked list elements directly
|
||||||
|
lru_cache(lru_cache<key_t, value_t> &) = delete;
|
||||||
|
lru_cache<key_t, value_t> &operator=(lru_cache<key_t, value_t> &) = delete;
|
||||||
|
|
||||||
|
// move
|
||||||
|
lru_cache(lru_cache<key_t, value_t> &&other)
|
||||||
|
: _cache_items_list(std::move(other._cache_items_list))
|
||||||
|
, _cache_items_map(std::move(other._cache_items_map))
|
||||||
|
{
|
||||||
|
other._cache_items_list.clear();
|
||||||
|
other._cache_items_map.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
lru_cache<key_t, value_t> &operator=(lru_cache<key_t, value_t> &&other)
|
||||||
|
{
|
||||||
|
_cache_items_list = std::move(other._cache_items_list);
|
||||||
|
_cache_items_map = std::move(other._cache_items_map);
|
||||||
|
other._cache_items_list.clear();
|
||||||
|
other._cache_items_map.clear();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void put(const key_t &key, const value_t &value)
|
||||||
|
{
|
||||||
auto it = _cache_items_map.find(key);
|
auto it = _cache_items_map.find(key);
|
||||||
_cache_items_list.push_front(key_value_pair_t(key, value));
|
_cache_items_list.push_front(key_value_pair_t(key, value));
|
||||||
if (it != _cache_items_map.end()) {
|
if (it != _cache_items_map.end())
|
||||||
|
{
|
||||||
_cache_items_list.erase(it->second);
|
_cache_items_list.erase(it->second);
|
||||||
_cache_items_map.erase(it);
|
_cache_items_map.erase(it);
|
||||||
}
|
}
|
||||||
_cache_items_map[key] = _cache_items_list.begin();
|
_cache_items_map[key] = _cache_items_list.begin();
|
||||||
|
|
||||||
if (_cache_items_map.size() > _max_size) {
|
if (_cache_items_map.size() > _max_size)
|
||||||
|
{
|
||||||
auto last = _cache_items_list.end();
|
auto last = _cache_items_list.end();
|
||||||
last--;
|
last--;
|
||||||
_cache_items_map.erase(last->first);
|
_cache_items_map.erase(last->first);
|
||||||
|
@ -42,24 +69,41 @@ public:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const value_t& get(const key_t& key) {
|
const value_t &get(const key_t &key)
|
||||||
|
{
|
||||||
auto it = _cache_items_map.find(key);
|
auto it = _cache_items_map.find(key);
|
||||||
if (it == _cache_items_map.end()) {
|
if (it == _cache_items_map.end())
|
||||||
|
{
|
||||||
throw std::range_error("There is no such key in cache");
|
throw std::range_error("There is no such key in cache");
|
||||||
} else {
|
}
|
||||||
_cache_items_list.splice(_cache_items_list.begin(), _cache_items_list, it->second);
|
else
|
||||||
|
{
|
||||||
|
_cache_items_list.splice(_cache_items_list.begin(),
|
||||||
|
_cache_items_list, it->second);
|
||||||
return it->second->second;
|
return it->second->second;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool exists(const key_t& key) const {
|
bool exists(const key_t &key) const
|
||||||
|
{
|
||||||
return _cache_items_map.find(key) != _cache_items_map.end();
|
return _cache_items_map.find(key) != _cache_items_map.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t size() const {
|
size_t size() const
|
||||||
|
{
|
||||||
return _cache_items_map.size();
|
return _cache_items_map.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto begin() const
|
||||||
|
{
|
||||||
|
return _cache_items_list.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto end() const
|
||||||
|
{
|
||||||
|
return _cache_items_list.end();
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::list<key_value_pair_t> _cache_items_list;
|
std::list<key_value_pair_t> _cache_items_list;
|
||||||
std::unordered_map<key_t, list_iterator_t> _cache_items_map;
|
std::unordered_map<key_t, list_iterator_t> _cache_items_map;
|
||||||
|
@ -69,4 +113,3 @@ private:
|
||||||
} // namespace cache
|
} // namespace cache
|
||||||
|
|
||||||
#endif /* _LRUCACHE_HPP_INCLUDED_ */
|
#endif /* _LRUCACHE_HPP_INCLUDED_ */
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,8 @@ set(SOURCE_FILES main.cpp
|
||||||
common/ChannelChatters.hpp
|
common/ChannelChatters.hpp
|
||||||
common/ChatterinoSetting.cpp
|
common/ChatterinoSetting.cpp
|
||||||
common/ChatterinoSetting.hpp
|
common/ChatterinoSetting.hpp
|
||||||
|
common/ChatterSet.cpp
|
||||||
|
common/ChatterSet.hpp
|
||||||
common/CompletionModel.cpp
|
common/CompletionModel.cpp
|
||||||
common/CompletionModel.hpp
|
common/CompletionModel.hpp
|
||||||
common/Credentials.cpp
|
common/Credentials.cpp
|
||||||
|
@ -42,8 +44,6 @@ set(SOURCE_FILES main.cpp
|
||||||
common/NetworkResult.hpp
|
common/NetworkResult.hpp
|
||||||
common/QLogging.cpp
|
common/QLogging.cpp
|
||||||
common/QLogging.hpp
|
common/QLogging.hpp
|
||||||
common/UsernameSet.cpp
|
|
||||||
common/UsernameSet.hpp
|
|
||||||
common/Version.cpp
|
common/Version.cpp
|
||||||
common/Version.hpp
|
common/Version.hpp
|
||||||
common/WindowDescriptors.cpp
|
common/WindowDescriptors.cpp
|
||||||
|
|
|
@ -11,14 +11,15 @@ ChannelChatters::ChannelChatters(Channel &channel)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
SharedAccessGuard<const UsernameSet> ChannelChatters::accessChatters() const
|
SharedAccessGuard<const ChatterSet> ChannelChatters::accessChatters() const
|
||||||
{
|
{
|
||||||
return this->chatters_.accessConst();
|
return this->chatters_.accessConst();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChannelChatters::addRecentChatter(const QString &user)
|
void ChannelChatters::addRecentChatter(const QString &user)
|
||||||
{
|
{
|
||||||
this->chatters_.access()->insert(user);
|
auto chatters = this->chatters_.access();
|
||||||
|
chatters->addRecentChatter(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChannelChatters::addJoinedUser(const QString &user)
|
void ChannelChatters::addJoinedUser(const QString &user)
|
||||||
|
@ -66,9 +67,11 @@ void ChannelChatters::addPartedUser(const QString &user)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChannelChatters::setChatters(UsernameSet &&set)
|
void ChannelChatters::updateOnlineChatters(
|
||||||
|
const std::unordered_set<QString> &chatters)
|
||||||
{
|
{
|
||||||
this->chatters_.access()->merge(std::move(set));
|
auto chatters_ = this->chatters_.access();
|
||||||
|
chatters_->updateOnlineChatters(chatters);
|
||||||
}
|
}
|
||||||
|
|
||||||
const QColor ChannelChatters::getUserColor(const QString &user)
|
const QColor ChannelChatters::getUserColor(const QString &user)
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "common/Channel.hpp"
|
#include "common/Channel.hpp"
|
||||||
|
#include "common/ChatterSet.hpp"
|
||||||
#include "common/UniqueAccess.hpp"
|
#include "common/UniqueAccess.hpp"
|
||||||
#include "common/UsernameSet.hpp"
|
|
||||||
#include "util/QStringHash.hpp"
|
|
||||||
|
|
||||||
#include "lrucache/lrucache.hpp"
|
#include "lrucache/lrucache.hpp"
|
||||||
|
#include "util/QStringHash.hpp"
|
||||||
|
|
||||||
#include <QRgb>
|
#include <QRgb>
|
||||||
|
|
||||||
|
@ -17,14 +16,14 @@ public:
|
||||||
ChannelChatters(Channel &channel);
|
ChannelChatters(Channel &channel);
|
||||||
virtual ~ChannelChatters() = default; // add vtable
|
virtual ~ChannelChatters() = default; // add vtable
|
||||||
|
|
||||||
SharedAccessGuard<const UsernameSet> accessChatters() const;
|
SharedAccessGuard<const ChatterSet> accessChatters() const;
|
||||||
|
|
||||||
void addRecentChatter(const QString &user);
|
void addRecentChatter(const QString &user);
|
||||||
void addJoinedUser(const QString &user);
|
void addJoinedUser(const QString &user);
|
||||||
void addPartedUser(const QString &user);
|
void addPartedUser(const QString &user);
|
||||||
void setChatters(UsernameSet &&set);
|
|
||||||
const QColor getUserColor(const QString &user);
|
const QColor getUserColor(const QString &user);
|
||||||
void setUserColor(const QString &user, const QColor &color);
|
void setUserColor(const QString &user, const QColor &color);
|
||||||
|
void updateOnlineChatters(const std::unordered_set<QString> &chatters);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static constexpr int maxChatterColorCount = 5000;
|
static constexpr int maxChatterColorCount = 5000;
|
||||||
|
@ -32,7 +31,7 @@ private:
|
||||||
Channel &channel_;
|
Channel &channel_;
|
||||||
|
|
||||||
// maps 2 char prefix to set of names
|
// maps 2 char prefix to set of names
|
||||||
UniqueAccess<UsernameSet> chatters_;
|
UniqueAccess<ChatterSet> chatters_;
|
||||||
UniqueAccess<cache::lru_cache<QString, QRgb>> chatterColors_;
|
UniqueAccess<cache::lru_cache<QString, QRgb>> chatterColors_;
|
||||||
|
|
||||||
// combines multiple joins/parts into one message
|
// combines multiple joins/parts into one message
|
||||||
|
|
58
src/common/ChatterSet.cpp
Normal file
58
src/common/ChatterSet.cpp
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
#include "common/ChatterSet.hpp"
|
||||||
|
|
||||||
|
#include <tuple>
|
||||||
|
#include "debug/Benchmark.hpp"
|
||||||
|
|
||||||
|
namespace chatterino {
|
||||||
|
|
||||||
|
ChatterSet::ChatterSet()
|
||||||
|
: items(chatterLimit)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatterSet::addRecentChatter(const QString &userName)
|
||||||
|
{
|
||||||
|
this->items.put(userName.toLower(), userName);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatterSet::updateOnlineChatters(
|
||||||
|
const std::unordered_set<QString> &lowerCaseUsernames)
|
||||||
|
{
|
||||||
|
BenchmarkGuard bench("update online chatters");
|
||||||
|
|
||||||
|
// Create a new lru cache without the users that are not present anymore.
|
||||||
|
cache::lru_cache<QString, QString> tmp(chatterLimit);
|
||||||
|
|
||||||
|
for (auto &&chatter : lowerCaseUsernames)
|
||||||
|
{
|
||||||
|
if (this->items.exists(chatter))
|
||||||
|
tmp.put(chatter, this->items.get(chatter));
|
||||||
|
|
||||||
|
// Less chatters than the limit => try to preserve as many as possible.
|
||||||
|
else if (lowerCaseUsernames.size() < chatterLimit)
|
||||||
|
tmp.put(chatter, chatter);
|
||||||
|
}
|
||||||
|
|
||||||
|
this->items = std::move(tmp);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ChatterSet::contains(const QString &userName) const
|
||||||
|
{
|
||||||
|
return this->items.exists(userName.toLower());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<QString> ChatterSet::filterByPrefix(const QString &prefix) const
|
||||||
|
{
|
||||||
|
QString lowerPrefix = prefix.toLower();
|
||||||
|
std::vector<QString> result;
|
||||||
|
|
||||||
|
for (auto &&item : this->items)
|
||||||
|
{
|
||||||
|
if (item.first.startsWith(lowerPrefix))
|
||||||
|
result.push_back(item.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace chatterino
|
46
src/common/ChatterSet.hpp
Normal file
46
src/common/ChatterSet.hpp
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
#include <functional>
|
||||||
|
#include <set>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <unordered_set>
|
||||||
|
#include "lrucache/lrucache.hpp"
|
||||||
|
#include "util/QStringHash.hpp"
|
||||||
|
|
||||||
|
namespace chatterino {
|
||||||
|
|
||||||
|
/// ChatterSet is a limited container that contains a list of recent chatters
|
||||||
|
/// that can be referenced by name.
|
||||||
|
class ChatterSet
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/// The limit of how many chatters can be saved for a channel.
|
||||||
|
static constexpr size_t chatterLimit = 2000;
|
||||||
|
|
||||||
|
ChatterSet();
|
||||||
|
|
||||||
|
/// Inserts a user name if it isn't contained. Doesn't replace the original
|
||||||
|
/// if the casing hasn't changed.
|
||||||
|
void addRecentChatter(const QString &userName);
|
||||||
|
|
||||||
|
/// Removes chatters that aren't online anymore. Adds chatters that aren't
|
||||||
|
/// in the list yet.
|
||||||
|
void updateOnlineChatters(
|
||||||
|
const std::unordered_set<QString> &lowerCaseUsernames);
|
||||||
|
|
||||||
|
/// Checks if a username is in the list.
|
||||||
|
bool contains(const QString &userName) const;
|
||||||
|
|
||||||
|
/// Get filtered usernames by a prefix for autocompletion. Contained items
|
||||||
|
/// are in mixed case if available.
|
||||||
|
std::vector<QString> filterByPrefix(const QString &prefix) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// user name in lower case -> user name in normal case
|
||||||
|
cache::lru_cache<QString, QString> items;
|
||||||
|
};
|
||||||
|
|
||||||
|
using ChatterSet = ChatterSet;
|
||||||
|
|
||||||
|
} // namespace chatterino
|
|
@ -1,8 +1,8 @@
|
||||||
#include "common/CompletionModel.hpp"
|
#include "common/CompletionModel.hpp"
|
||||||
|
|
||||||
#include "Application.hpp"
|
#include "Application.hpp"
|
||||||
|
#include "common/ChatterSet.hpp"
|
||||||
#include "common/Common.hpp"
|
#include "common/Common.hpp"
|
||||||
#include "common/UsernameSet.hpp"
|
|
||||||
#include "controllers/accounts/AccountController.hpp"
|
#include "controllers/accounts/AccountController.hpp"
|
||||||
#include "controllers/commands/CommandController.hpp"
|
#include "controllers/commands/CommandController.hpp"
|
||||||
#include "debug/Benchmark.hpp"
|
#include "debug/Benchmark.hpp"
|
||||||
|
@ -108,20 +108,19 @@ void CompletionModel::refresh(const QString &prefix, bool isFirstWord)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Usernames
|
// Usernames
|
||||||
if (prefix.length() >= UsernameSet::PrefixLength)
|
|
||||||
{
|
|
||||||
auto usernames = channel->accessChatters();
|
|
||||||
|
|
||||||
QString usernamePrefix = prefix;
|
|
||||||
QString usernamePostfix =
|
QString usernamePostfix =
|
||||||
isFirstWord && getSettings()->mentionUsersWithComma ? ","
|
isFirstWord && getSettings()->mentionUsersWithComma ? ","
|
||||||
: QString();
|
: QString();
|
||||||
|
|
||||||
if (usernamePrefix.startsWith("@"))
|
if (prefix.startsWith("@"))
|
||||||
{
|
{
|
||||||
|
QString usernamePrefix = prefix;
|
||||||
usernamePrefix.remove(0, 1);
|
usernamePrefix.remove(0, 1);
|
||||||
for (const auto &name :
|
|
||||||
usernames->subrange(Prefix(usernamePrefix)))
|
auto chatters =
|
||||||
|
channel->accessChatters()->filterByPrefix(usernamePrefix);
|
||||||
|
|
||||||
|
for (const auto &name : chatters)
|
||||||
{
|
{
|
||||||
addString("@" + name + usernamePostfix,
|
addString("@" + name + usernamePostfix,
|
||||||
TaggedString::Type::Username);
|
TaggedString::Type::Username);
|
||||||
|
@ -129,12 +128,11 @@ void CompletionModel::refresh(const QString &prefix, bool isFirstWord)
|
||||||
}
|
}
|
||||||
else if (!getSettings()->userCompletionOnlyWithAt)
|
else if (!getSettings()->userCompletionOnlyWithAt)
|
||||||
{
|
{
|
||||||
for (const auto &name :
|
auto chatters = channel->accessChatters()->filterByPrefix(prefix);
|
||||||
usernames->subrange(Prefix(usernamePrefix)))
|
|
||||||
|
for (const auto &name : chatters)
|
||||||
{
|
{
|
||||||
addString(name + usernamePostfix,
|
addString(name + usernamePostfix, TaggedString::Type::Username);
|
||||||
TaggedString::Type::Username);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -64,7 +64,7 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
UniqueAccess(T &&element)
|
UniqueAccess(T &&element)
|
||||||
: element_(element)
|
: element_(std::move(element))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,7 +76,7 @@ public:
|
||||||
|
|
||||||
UniqueAccess<T> &operator=(T &&element)
|
UniqueAccess<T> &operator=(T &&element)
|
||||||
{
|
{
|
||||||
this->element_ = element;
|
this->element_ = std::move(element);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,182 +0,0 @@
|
||||||
#include "UsernameSet.hpp"
|
|
||||||
|
|
||||||
#include <tuple>
|
|
||||||
|
|
||||||
namespace chatterino {
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
std::pair<UsernameSet::Iterator, bool> findOrErase(
|
|
||||||
std::set<QString, CaseInsensitiveLess> &set, const QString &value)
|
|
||||||
{
|
|
||||||
if (!value.isLower())
|
|
||||||
{
|
|
||||||
auto iter = set.find(value);
|
|
||||||
if (iter != set.end())
|
|
||||||
{
|
|
||||||
if (QString::compare(*iter, value, Qt::CaseSensitive) != 0)
|
|
||||||
{
|
|
||||||
set.erase(iter);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return {iter, false};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {set.end(), true};
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
//
|
|
||||||
// UsernameSet
|
|
||||||
//
|
|
||||||
|
|
||||||
UsernameSet::ConstIterator UsernameSet::begin() const
|
|
||||||
{
|
|
||||||
return this->items.begin();
|
|
||||||
}
|
|
||||||
|
|
||||||
UsernameSet::ConstIterator UsernameSet::end() const
|
|
||||||
{
|
|
||||||
return this->items.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
UsernameSet::Range UsernameSet::subrange(const Prefix &prefix) const
|
|
||||||
{
|
|
||||||
auto it = this->firstKeyForPrefix.find(prefix);
|
|
||||||
if (it != this->firstKeyForPrefix.end())
|
|
||||||
{
|
|
||||||
auto start = this->items.find(it->second);
|
|
||||||
auto end = start;
|
|
||||||
|
|
||||||
while (end != this->items.end() && prefix.isStartOf(*end))
|
|
||||||
{
|
|
||||||
end++;
|
|
||||||
}
|
|
||||||
return {start, end};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {this->items.end(), this->items.end()};
|
|
||||||
}
|
|
||||||
|
|
||||||
std::set<QString>::size_type UsernameSet::size() const
|
|
||||||
{
|
|
||||||
return this->items.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::pair<UsernameSet::Iterator, bool> UsernameSet::insert(const QString &value)
|
|
||||||
{
|
|
||||||
auto pair = findOrErase(this->items, value);
|
|
||||||
if (!pair.second)
|
|
||||||
{
|
|
||||||
return pair;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->insertPrefix(value);
|
|
||||||
return this->items.insert(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::pair<UsernameSet::Iterator, bool> UsernameSet::insert(QString &&value)
|
|
||||||
{
|
|
||||||
auto pair = findOrErase(this->items, value);
|
|
||||||
if (!pair.second)
|
|
||||||
{
|
|
||||||
return pair;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->insertPrefix(value);
|
|
||||||
return this->items.insert(std::move(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
void UsernameSet::insertPrefix(const QString &value)
|
|
||||||
{
|
|
||||||
auto &string = this->firstKeyForPrefix[Prefix(value)];
|
|
||||||
|
|
||||||
if (string.isNull() || value.compare(string, Qt::CaseInsensitive) < 0)
|
|
||||||
string = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool UsernameSet::contains(const QString &value) const
|
|
||||||
{
|
|
||||||
return this->items.count(value) == 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
void UsernameSet::merge(UsernameSet &&set)
|
|
||||||
{
|
|
||||||
for (auto it = this->items.begin(); it != this->items.end();)
|
|
||||||
{
|
|
||||||
auto iter = set.items.find(*it);
|
|
||||||
if (iter == set.items.end())
|
|
||||||
{
|
|
||||||
it = this->items.erase(it);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
++it;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this->items.merge(set.items);
|
|
||||||
this->firstKeyForPrefix = set.firstKeyForPrefix;
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Range
|
|
||||||
//
|
|
||||||
|
|
||||||
UsernameSet::Range::Range(ConstIterator start, ConstIterator end)
|
|
||||||
: start_(start)
|
|
||||||
, end_(end)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
UsernameSet::ConstIterator UsernameSet::Range::begin()
|
|
||||||
{
|
|
||||||
return this->start_;
|
|
||||||
}
|
|
||||||
|
|
||||||
UsernameSet::ConstIterator UsernameSet::Range::end()
|
|
||||||
{
|
|
||||||
return this->end_;
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Prefix
|
|
||||||
//
|
|
||||||
|
|
||||||
Prefix::Prefix(const QString &string)
|
|
||||||
: first(string.size() >= 1 ? string[0].toLower() : '\0')
|
|
||||||
, second(string.size() >= 2 ? string[1].toLower() : '\0')
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Prefix::operator==(const Prefix &other) const
|
|
||||||
{
|
|
||||||
return std::tie(this->first, this->second) ==
|
|
||||||
std::tie(other.first, other.second);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Prefix::operator!=(const Prefix &other) const
|
|
||||||
{
|
|
||||||
return !(*this == other);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Prefix::isStartOf(const QString &string) const
|
|
||||||
{
|
|
||||||
if (string.size() == 0)
|
|
||||||
{
|
|
||||||
return this->first == QChar('\0') && this->second == QChar('\0');
|
|
||||||
}
|
|
||||||
else if (string.size() == 1)
|
|
||||||
{
|
|
||||||
return this->first == string[0].toLower() &&
|
|
||||||
this->second == QChar('\0');
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return this->first == string[0].toLower() &&
|
|
||||||
this->second == string[1].toLower();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace chatterino
|
|
|
@ -1,89 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <QString>
|
|
||||||
#include <functional>
|
|
||||||
#include <set>
|
|
||||||
#include <unordered_map>
|
|
||||||
|
|
||||||
namespace chatterino {
|
|
||||||
|
|
||||||
class Prefix
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
Prefix(const QString &string);
|
|
||||||
bool operator==(const Prefix &other) const;
|
|
||||||
bool operator!=(const Prefix &other) const;
|
|
||||||
bool isStartOf(const QString &string) const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
QChar first;
|
|
||||||
QChar second;
|
|
||||||
|
|
||||||
friend struct std::hash<Prefix>;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace chatterino
|
|
||||||
|
|
||||||
namespace std {
|
|
||||||
|
|
||||||
template <>
|
|
||||||
struct hash<chatterino::Prefix> {
|
|
||||||
size_t operator()(const chatterino::Prefix &prefix) const
|
|
||||||
{
|
|
||||||
return (size_t(prefix.first.unicode()) << 16) |
|
|
||||||
size_t(prefix.second.unicode());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace std
|
|
||||||
|
|
||||||
namespace chatterino {
|
|
||||||
|
|
||||||
struct CaseInsensitiveLess {
|
|
||||||
bool operator()(const QString &lhs, const QString &rhs) const
|
|
||||||
{
|
|
||||||
return lhs.compare(rhs, Qt::CaseInsensitive) < 0;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class UsernameSet
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
static constexpr int PrefixLength = 2;
|
|
||||||
|
|
||||||
using Iterator = std::set<QString>::iterator;
|
|
||||||
using ConstIterator = std::set<QString>::const_iterator;
|
|
||||||
|
|
||||||
class Range
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
Range(ConstIterator start, ConstIterator end);
|
|
||||||
|
|
||||||
ConstIterator begin();
|
|
||||||
ConstIterator end();
|
|
||||||
|
|
||||||
private:
|
|
||||||
ConstIterator start_;
|
|
||||||
ConstIterator end_;
|
|
||||||
};
|
|
||||||
|
|
||||||
ConstIterator begin() const;
|
|
||||||
ConstIterator end() const;
|
|
||||||
Range subrange(const Prefix &prefix) const;
|
|
||||||
|
|
||||||
std::set<QString>::size_type size() const;
|
|
||||||
|
|
||||||
std::pair<Iterator, bool> insert(const QString &value);
|
|
||||||
std::pair<Iterator, bool> insert(QString &&value);
|
|
||||||
|
|
||||||
bool contains(const QString &value) const;
|
|
||||||
void merge(UsernameSet &&set);
|
|
||||||
|
|
||||||
private:
|
|
||||||
void insertPrefix(const QString &string);
|
|
||||||
|
|
||||||
std::set<QString, CaseInsensitiveLess> items;
|
|
||||||
std::unordered_map<Prefix, QString> firstKeyForPrefix;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace chatterino
|
|
|
@ -119,13 +119,14 @@ namespace {
|
||||||
|
|
||||||
return messages;
|
return messages;
|
||||||
}
|
}
|
||||||
std::pair<Outcome, UsernameSet> parseChatters(const QJsonObject &jsonRoot)
|
std::pair<Outcome, std::unordered_set<QString>> parseChatters(
|
||||||
|
const QJsonObject &jsonRoot)
|
||||||
{
|
{
|
||||||
static QStringList categories = {"broadcaster", "vips", "moderators",
|
static QStringList categories = {"broadcaster", "vips", "moderators",
|
||||||
"staff", "admins", "global_mods",
|
"staff", "admins", "global_mods",
|
||||||
"viewers"};
|
"viewers"};
|
||||||
|
|
||||||
auto usernames = UsernameSet();
|
auto usernames = std::unordered_set<QString>();
|
||||||
|
|
||||||
// parse json
|
// parse json
|
||||||
QJsonObject jsonCategories = jsonRoot.value("chatters").toObject();
|
QJsonObject jsonCategories = jsonRoot.value("chatters").toObject();
|
||||||
|
@ -823,7 +824,7 @@ void TwitchChannel::refreshChatters()
|
||||||
auto pair = parseChatters(std::move(data));
|
auto pair = parseChatters(std::move(data));
|
||||||
if (pair.first)
|
if (pair.first)
|
||||||
{
|
{
|
||||||
this->setChatters(std::move(pair.second));
|
this->updateOnlineChatters(pair.second);
|
||||||
}
|
}
|
||||||
|
|
||||||
return pair.first;
|
return pair.first;
|
||||||
|
|
|
@ -4,9 +4,9 @@
|
||||||
#include "common/Atomic.hpp"
|
#include "common/Atomic.hpp"
|
||||||
#include "common/Channel.hpp"
|
#include "common/Channel.hpp"
|
||||||
#include "common/ChannelChatters.hpp"
|
#include "common/ChannelChatters.hpp"
|
||||||
|
#include "common/ChatterSet.hpp"
|
||||||
#include "common/Outcome.hpp"
|
#include "common/Outcome.hpp"
|
||||||
#include "common/UniqueAccess.hpp"
|
#include "common/UniqueAccess.hpp"
|
||||||
#include "common/UsernameSet.hpp"
|
|
||||||
#include "providers/twitch/ChannelPointReward.hpp"
|
#include "providers/twitch/ChannelPointReward.hpp"
|
||||||
#include "providers/twitch/TwitchEmotes.hpp"
|
#include "providers/twitch/TwitchEmotes.hpp"
|
||||||
#include "providers/twitch/api/Helix.hpp"
|
#include "providers/twitch/api/Helix.hpp"
|
||||||
|
|
|
@ -519,11 +519,11 @@ void TwitchMessageBuilder::addTextOrEmoji(const QString &string_)
|
||||||
|
|
||||||
if (this->twitchChannel != nullptr && getSettings()->findAllUsernames)
|
if (this->twitchChannel != nullptr && getSettings()->findAllUsernames)
|
||||||
{
|
{
|
||||||
auto chatters = this->twitchChannel->accessChatters();
|
|
||||||
auto match = allUsernamesMentionRegex.match(string);
|
auto match = allUsernamesMentionRegex.match(string);
|
||||||
QString username = match.captured(1);
|
QString username = match.captured(1);
|
||||||
|
|
||||||
if (match.hasMatch() && chatters->contains(username))
|
if (match.hasMatch() &&
|
||||||
|
this->twitchChannel->accessChatters()->contains(username))
|
||||||
{
|
{
|
||||||
auto originalTextColor = textColor;
|
auto originalTextColor = textColor;
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,9 @@ set(chatterino_SOURCES
|
||||||
${CMAKE_SOURCE_DIR}/src/common/NetworkRequest.cpp
|
${CMAKE_SOURCE_DIR}/src/common/NetworkRequest.cpp
|
||||||
${CMAKE_SOURCE_DIR}/src/common/NetworkResult.cpp
|
${CMAKE_SOURCE_DIR}/src/common/NetworkResult.cpp
|
||||||
${CMAKE_SOURCE_DIR}/src/common/QLogging.cpp
|
${CMAKE_SOURCE_DIR}/src/common/QLogging.cpp
|
||||||
${CMAKE_SOURCE_DIR}/src/common/UsernameSet.cpp
|
${CMAKE_SOURCE_DIR}/src/common/ChatterSet.cpp
|
||||||
|
|
||||||
|
${CMAKE_SOURCE_DIR}/src/debug/Benchmark.cpp
|
||||||
|
|
||||||
${CMAKE_SOURCE_DIR}/src/controllers/highlights/HighlightPhrase.cpp
|
${CMAKE_SOURCE_DIR}/src/controllers/highlights/HighlightPhrase.cpp
|
||||||
|
|
||||||
|
@ -39,7 +41,7 @@ set(test_SOURCES
|
||||||
${CMAKE_CURRENT_LIST_DIR}/src/AccessGuard.cpp
|
${CMAKE_CURRENT_LIST_DIR}/src/AccessGuard.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/src/NetworkCommon.cpp
|
${CMAKE_CURRENT_LIST_DIR}/src/NetworkCommon.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/src/NetworkRequest.cpp
|
${CMAKE_CURRENT_LIST_DIR}/src/NetworkRequest.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/src/UsernameSet.cpp
|
${CMAKE_CURRENT_LIST_DIR}/src/ChatterSet.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/src/HighlightPhrase.cpp
|
${CMAKE_CURRENT_LIST_DIR}/src/HighlightPhrase.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/src/Emojis.cpp
|
${CMAKE_CURRENT_LIST_DIR}/src/Emojis.cpp
|
||||||
)
|
)
|
||||||
|
@ -68,6 +70,7 @@ target_link_libraries(${PROJECT_NAME} PRIVATE
|
||||||
Pajlada::Settings
|
Pajlada::Settings
|
||||||
Pajlada::Signals
|
Pajlada::Signals
|
||||||
Threads::Threads
|
Threads::Threads
|
||||||
|
LRUCache
|
||||||
)
|
)
|
||||||
|
|
||||||
target_compile_definitions(${PROJECT_NAME} PRIVATE
|
target_compile_definitions(${PROJECT_NAME} PRIVATE
|
||||||
|
|
84
tests/src/ChatterSet.cpp
Normal file
84
tests/src/ChatterSet.cpp
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
#include "common/ChatterSet.hpp"
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <QStringList>
|
||||||
|
|
||||||
|
TEST(ChatterSet, insert)
|
||||||
|
{
|
||||||
|
chatterino::ChatterSet set;
|
||||||
|
|
||||||
|
EXPECT_FALSE(set.contains("pajlada"));
|
||||||
|
EXPECT_FALSE(set.contains("Pajlada"));
|
||||||
|
|
||||||
|
set.addRecentChatter("pajlada");
|
||||||
|
EXPECT_TRUE(set.contains("pajlada"));
|
||||||
|
EXPECT_TRUE(set.contains("Pajlada"));
|
||||||
|
|
||||||
|
set.addRecentChatter("pajlada");
|
||||||
|
EXPECT_TRUE(set.contains("pajlada"));
|
||||||
|
EXPECT_TRUE(set.contains("Pajlada"));
|
||||||
|
|
||||||
|
set.addRecentChatter("PAJLADA");
|
||||||
|
EXPECT_TRUE(set.contains("pajlada"));
|
||||||
|
EXPECT_TRUE(set.contains("Pajlada"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ChatterSet, MaxSize)
|
||||||
|
{
|
||||||
|
chatterino::ChatterSet set;
|
||||||
|
|
||||||
|
EXPECT_FALSE(set.contains("pajlada"));
|
||||||
|
EXPECT_FALSE(set.contains("Pajlada"));
|
||||||
|
|
||||||
|
set.addRecentChatter("pajlada");
|
||||||
|
EXPECT_TRUE(set.contains("pajlada"));
|
||||||
|
EXPECT_TRUE(set.contains("Pajlada"));
|
||||||
|
|
||||||
|
// After adding CHATTER_LIMIT-1 additional chatters, pajlada should still be in the set
|
||||||
|
for (auto i = 0; i < chatterino::ChatterSet::chatterLimit - 1; ++i)
|
||||||
|
{
|
||||||
|
set.addRecentChatter(QString("%1").arg(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_TRUE(set.contains("pajlada"));
|
||||||
|
EXPECT_TRUE(set.contains("Pajlada"));
|
||||||
|
|
||||||
|
// But adding one more chatter should bump pajlada out of the set
|
||||||
|
set.addRecentChatter("notpajlada");
|
||||||
|
|
||||||
|
EXPECT_FALSE(set.contains("pajlada"));
|
||||||
|
EXPECT_FALSE(set.contains("Pajlada"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ChatterSet, MaxSizeLastUsed)
|
||||||
|
{
|
||||||
|
chatterino::ChatterSet set;
|
||||||
|
|
||||||
|
EXPECT_FALSE(set.contains("pajlada"));
|
||||||
|
EXPECT_FALSE(set.contains("Pajlada"));
|
||||||
|
|
||||||
|
set.addRecentChatter("pajlada");
|
||||||
|
EXPECT_TRUE(set.contains("pajlada"));
|
||||||
|
EXPECT_TRUE(set.contains("Pajlada"));
|
||||||
|
|
||||||
|
// After adding CHATTER_LIMIT-1 additional chatters, pajlada should still be in the set
|
||||||
|
for (auto i = 0; i < chatterino::ChatterSet::chatterLimit - 1; ++i)
|
||||||
|
{
|
||||||
|
set.addRecentChatter(QString("%1").arg(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_TRUE(set.contains("pajlada"));
|
||||||
|
EXPECT_TRUE(set.contains("Pajlada"));
|
||||||
|
|
||||||
|
// Bump pajlada as a recent chatter
|
||||||
|
set.addRecentChatter("pajlada");
|
||||||
|
|
||||||
|
// After another CHATTER_LIMIT-1 additional chatters, pajlada should still be there
|
||||||
|
for (auto i = 0; i < chatterino::ChatterSet::chatterLimit - 1; ++i)
|
||||||
|
{
|
||||||
|
set.addRecentChatter(QString("new-%1").arg(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_TRUE(set.contains("pajlada"));
|
||||||
|
EXPECT_TRUE(set.contains("Pajlada"));
|
||||||
|
}
|
|
@ -1,161 +0,0 @@
|
||||||
#include "common/UsernameSet.hpp"
|
|
||||||
|
|
||||||
#include <gtest/gtest.h>
|
|
||||||
#include <QStringList>
|
|
||||||
|
|
||||||
chatterino::Prefix prefix_pajlada(QString("pajlada"));
|
|
||||||
chatterino::Prefix prefix_Pajlada(QString("Pajlada"));
|
|
||||||
chatterino::Prefix prefix_randers(QString("randers"));
|
|
||||||
chatterino::Prefix prefix_Chancu(QString("ch"));
|
|
||||||
|
|
||||||
TEST(Prefix, isStartOf)
|
|
||||||
{
|
|
||||||
EXPECT_TRUE(prefix_pajlada.isStartOf("pajlada"));
|
|
||||||
EXPECT_TRUE(prefix_pajlada.isStartOf("Pajlada"));
|
|
||||||
EXPECT_FALSE(prefix_pajlada.isStartOf("randers"));
|
|
||||||
|
|
||||||
EXPECT_TRUE(prefix_Pajlada.isStartOf("pajlada"));
|
|
||||||
EXPECT_TRUE(prefix_Pajlada.isStartOf("Pajlada"));
|
|
||||||
EXPECT_TRUE(prefix_Pajlada.isStartOf("pajbot"));
|
|
||||||
EXPECT_TRUE(prefix_Pajlada.isStartOf("Pajbot"));
|
|
||||||
EXPECT_FALSE(prefix_Pajlada.isStartOf("randers"));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(Prefix, EqualsOperator)
|
|
||||||
{
|
|
||||||
EXPECT_EQ(prefix_pajlada, prefix_Pajlada);
|
|
||||||
EXPECT_NE(prefix_pajlada, prefix_randers);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(UsernameSet, insert)
|
|
||||||
{
|
|
||||||
std::pair<chatterino::UsernameSet::Iterator, bool> p;
|
|
||||||
chatterino::UsernameSet set;
|
|
||||||
|
|
||||||
EXPECT_EQ(set.size(), 0);
|
|
||||||
|
|
||||||
p = set.insert("pajlada");
|
|
||||||
EXPECT_TRUE(p.second);
|
|
||||||
|
|
||||||
EXPECT_EQ(set.size(), 1);
|
|
||||||
|
|
||||||
p = set.insert("pajlada");
|
|
||||||
EXPECT_FALSE(p.second);
|
|
||||||
|
|
||||||
EXPECT_EQ(set.size(), 1);
|
|
||||||
|
|
||||||
// Non-lowercase variant should override full lowercase variant
|
|
||||||
p = set.insert("PAJLADA");
|
|
||||||
EXPECT_TRUE(p.second);
|
|
||||||
|
|
||||||
EXPECT_EQ(set.size(), 1);
|
|
||||||
|
|
||||||
// Lowercase variant should not override non-lowercase variant
|
|
||||||
p = set.insert("pajlada");
|
|
||||||
EXPECT_FALSE(p.second);
|
|
||||||
|
|
||||||
EXPECT_EQ(set.size(), 1);
|
|
||||||
|
|
||||||
p = set.insert("pajbot");
|
|
||||||
EXPECT_TRUE(p.second);
|
|
||||||
|
|
||||||
EXPECT_EQ(set.size(), 2);
|
|
||||||
|
|
||||||
p = set.insert("pajbot");
|
|
||||||
EXPECT_FALSE(p.second);
|
|
||||||
|
|
||||||
EXPECT_EQ(set.size(), 2);
|
|
||||||
|
|
||||||
p = set.insert("Pajbot");
|
|
||||||
EXPECT_TRUE(p.second);
|
|
||||||
|
|
||||||
EXPECT_EQ(set.size(), 2);
|
|
||||||
|
|
||||||
p = set.insert("PAJBOT");
|
|
||||||
EXPECT_TRUE(p.second);
|
|
||||||
|
|
||||||
EXPECT_EQ(set.size(), 2);
|
|
||||||
|
|
||||||
// Same uppercase should not result in a change
|
|
||||||
p = set.insert("PAJBOT");
|
|
||||||
EXPECT_FALSE(p.second);
|
|
||||||
|
|
||||||
EXPECT_EQ(set.size(), 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(UsernameSet, CollisionTest)
|
|
||||||
{
|
|
||||||
QString s;
|
|
||||||
chatterino::UsernameSet set;
|
|
||||||
chatterino::Prefix prefix("not_");
|
|
||||||
|
|
||||||
set.insert("pajlada");
|
|
||||||
set.insert("Chancu");
|
|
||||||
set.insert("chief_tony");
|
|
||||||
set.insert("ChodzacyKac");
|
|
||||||
set.insert("ChatAbuser");
|
|
||||||
set.insert("Normies_GTFO");
|
|
||||||
set.insert("not_remzy");
|
|
||||||
set.insert("Mullo2500");
|
|
||||||
set.insert("muggedbyapie");
|
|
||||||
|
|
||||||
EXPECT_EQ(set.size(), 9);
|
|
||||||
|
|
||||||
{
|
|
||||||
QStringList result;
|
|
||||||
QStringList expectation{"Normies_GTFO", "not_remzy"};
|
|
||||||
auto subrange = set.subrange(QString("not_"));
|
|
||||||
std::copy(subrange.begin(), subrange.end(), std::back_inserter(result));
|
|
||||||
EXPECT_EQ(expectation, result);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
QStringList result;
|
|
||||||
QStringList expectation{};
|
|
||||||
auto subrange = set.subrange(QString("te"));
|
|
||||||
std::copy(subrange.begin(), subrange.end(), std::back_inserter(result));
|
|
||||||
EXPECT_EQ(expectation, result);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
QStringList result;
|
|
||||||
QStringList expectation{"pajlada"};
|
|
||||||
auto subrange = set.subrange(QString("PA"));
|
|
||||||
std::copy(subrange.begin(), subrange.end(), std::back_inserter(result));
|
|
||||||
EXPECT_EQ(expectation, result);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
QStringList result;
|
|
||||||
QStringList expectation{"pajlada"};
|
|
||||||
auto subrange = set.subrange(QString("pajlada"));
|
|
||||||
std::copy(subrange.begin(), subrange.end(), std::back_inserter(result));
|
|
||||||
EXPECT_EQ(expectation, result);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
QStringList result;
|
|
||||||
QStringList expectation{"pajlada"};
|
|
||||||
auto subrange = set.subrange(QString("Pajl"));
|
|
||||||
std::copy(subrange.begin(), subrange.end(), std::back_inserter(result));
|
|
||||||
EXPECT_EQ(expectation, result);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
QStringList result;
|
|
||||||
QStringList expectation{"Chancu", "ChatAbuser", "chief_tony",
|
|
||||||
"ChodzacyKac"};
|
|
||||||
auto subrange = set.subrange(QString("chan"));
|
|
||||||
std::copy(subrange.begin(), subrange.end(), std::back_inserter(result));
|
|
||||||
EXPECT_EQ(expectation, result);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
QStringList result;
|
|
||||||
QStringList expectation{"muggedbyapie", "Mullo2500"};
|
|
||||||
auto subrange = set.subrange(QString("mu"));
|
|
||||||
std::copy(subrange.begin(), subrange.end(), std::back_inserter(result));
|
|
||||||
EXPECT_EQ(expectation, result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue