2020-09-02 13:32:57 +03:00
|
|
|
#pragma once
|
|
|
|
|
|
|
|
// Class for showing a limited amount of completions at a time
|
|
|
|
|
2020-11-23 07:08:17 +03:00
|
|
|
#include <QAbstractProxyModel>
|
2020-09-02 13:32:57 +03:00
|
|
|
|
2020-11-20 04:38:08 +03:00
|
|
|
#include "CompletionModelRoles.h"
|
2020-11-23 07:08:17 +03:00
|
|
|
#include "Logging.h"
|
2020-11-20 06:05:31 +03:00
|
|
|
#include "Utils.h"
|
2020-11-20 04:38:08 +03:00
|
|
|
|
2020-11-23 07:08:17 +03:00
|
|
|
template<typename Key, typename Value>
|
|
|
|
struct trie
|
|
|
|
{
|
|
|
|
std::vector<Value> values;
|
|
|
|
std::map<Key, trie> next;
|
|
|
|
|
|
|
|
void insert(const QVector<Key> &keys, const Value &v)
|
|
|
|
{
|
|
|
|
auto t = this;
|
|
|
|
for (const auto k : keys) {
|
|
|
|
t = &t->next[k];
|
|
|
|
}
|
|
|
|
|
|
|
|
t->values.push_back(v);
|
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<Value> valuesAndSubvalues(size_t limit = -1) const
|
|
|
|
{
|
|
|
|
std::vector<Value> ret;
|
|
|
|
if (limit < 200)
|
|
|
|
ret.reserve(limit);
|
|
|
|
|
|
|
|
for (const auto &v : values) {
|
|
|
|
if (ret.size() >= limit)
|
|
|
|
return ret;
|
|
|
|
else
|
|
|
|
ret.push_back(v);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const auto &[k, t] : next) {
|
|
|
|
(void)k;
|
|
|
|
if (ret.size() >= limit)
|
|
|
|
return ret;
|
|
|
|
else {
|
|
|
|
auto temp = t.valuesAndSubvalues(limit - ret.size());
|
2020-11-24 04:35:38 +03:00
|
|
|
for (auto &&v : temp) {
|
|
|
|
if (ret.size() >= limit)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
if (std::find(ret.begin(), ret.end(), v) == ret.end()) {
|
|
|
|
ret.push_back(std::move(v));
|
|
|
|
}
|
|
|
|
}
|
2020-11-23 07:08:17 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2020-11-24 04:56:14 +03:00
|
|
|
std::vector<Value> search(const QVector<Key> &keys, //< TODO(Nico): replace this with a span
|
2020-11-24 04:35:38 +03:00
|
|
|
size_t limit,
|
|
|
|
size_t max_distance = 2) const
|
2020-11-23 07:08:17 +03:00
|
|
|
{
|
|
|
|
std::vector<Value> ret;
|
2020-11-24 04:35:38 +03:00
|
|
|
if (!limit)
|
|
|
|
return ret;
|
|
|
|
|
2020-11-24 04:56:14 +03:00
|
|
|
if (keys.isEmpty())
|
|
|
|
return valuesAndSubvalues(limit);
|
|
|
|
|
2020-11-24 04:35:38 +03:00
|
|
|
auto append = [&ret, limit](std::vector<Value> &&in) {
|
|
|
|
for (auto &&v : in) {
|
|
|
|
if (ret.size() >= limit)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (std::find(ret.begin(), ret.end(), v) == ret.end()) {
|
|
|
|
ret.push_back(std::move(v));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-11-24 04:56:14 +03:00
|
|
|
if (auto e = this->next.find(keys[0]); e != this->next.end()) {
|
|
|
|
append(e->second.search(keys.mid(1), limit, max_distance));
|
2020-11-23 07:08:17 +03:00
|
|
|
}
|
|
|
|
|
2020-11-24 04:56:14 +03:00
|
|
|
if (max_distance && ret.size() < limit) {
|
2020-11-24 04:35:38 +03:00
|
|
|
max_distance -= 1;
|
|
|
|
|
|
|
|
// swap chars case
|
|
|
|
if (keys.size() >= 2) {
|
|
|
|
auto t = this;
|
|
|
|
for (int i = 1; i >= 0; i--) {
|
|
|
|
if (auto e = t->next.find(keys[i]); e != t->next.end()) {
|
|
|
|
t = &e->second;
|
|
|
|
} else {
|
|
|
|
t = nullptr;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (t) {
|
|
|
|
append(t->search(
|
|
|
|
keys.mid(2), (limit - ret.size()) * 2, max_distance));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// delete character case
|
|
|
|
append(this->search(keys.mid(1), (limit - ret.size()) * 2, max_distance));
|
|
|
|
|
|
|
|
// substitute and insert cases
|
|
|
|
for (const auto &[k, t] : this->next) {
|
|
|
|
if (k == keys[0] || ret.size() >= limit)
|
|
|
|
break;
|
|
|
|
|
|
|
|
// substitute
|
2020-11-24 04:56:14 +03:00
|
|
|
append(t.search(keys.mid(1), limit - ret.size(), max_distance));
|
2020-11-24 04:35:38 +03:00
|
|
|
|
|
|
|
if (ret.size() >= limit)
|
|
|
|
break;
|
|
|
|
|
|
|
|
// insert
|
2020-11-24 04:56:14 +03:00
|
|
|
append(t.search(keys, limit - ret.size(), max_distance));
|
2020-11-24 04:35:38 +03:00
|
|
|
}
|
2020-11-23 07:08:17 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
class CompletionProxyModel : public QAbstractProxyModel
|
2020-09-02 13:32:57 +03:00
|
|
|
{
|
2020-11-20 04:38:08 +03:00
|
|
|
Q_OBJECT
|
|
|
|
|
2020-09-02 13:32:57 +03:00
|
|
|
public:
|
|
|
|
CompletionProxyModel(QAbstractItemModel *model, QObject *parent = nullptr)
|
2020-11-23 07:08:17 +03:00
|
|
|
: QAbstractProxyModel(parent)
|
2020-09-02 13:32:57 +03:00
|
|
|
{
|
|
|
|
setSourceModel(model);
|
2020-11-23 07:08:17 +03:00
|
|
|
|
|
|
|
for (int i = 0; i < sourceModel()->rowCount(); i++) {
|
|
|
|
if (i < 7)
|
|
|
|
mapping.push_back(i);
|
|
|
|
|
|
|
|
auto string1 =
|
|
|
|
sourceModel()
|
|
|
|
->data(sourceModel()->index(i, 0), CompletionModel::SearchRole)
|
|
|
|
.toString()
|
|
|
|
.toLower();
|
|
|
|
trie_.insert(string1.toUcs4(), i);
|
|
|
|
|
|
|
|
auto string2 =
|
|
|
|
sourceModel()
|
|
|
|
->data(sourceModel()->index(i, 0), CompletionModel::SearchRole2)
|
|
|
|
.toString()
|
|
|
|
.toLower();
|
|
|
|
|
|
|
|
if (!string2.isEmpty())
|
|
|
|
trie_.insert(string2.toUcs4(), i);
|
|
|
|
}
|
2020-11-20 06:05:31 +03:00
|
|
|
|
|
|
|
connect(
|
|
|
|
this,
|
|
|
|
&CompletionProxyModel::newSearchString,
|
|
|
|
this,
|
|
|
|
[this](QString s) {
|
|
|
|
s.remove(":");
|
|
|
|
s.remove("@");
|
|
|
|
searchString = s.toLower();
|
|
|
|
invalidate();
|
|
|
|
},
|
|
|
|
Qt::QueuedConnection);
|
2020-09-02 13:32:57 +03:00
|
|
|
}
|
2020-11-20 04:38:08 +03:00
|
|
|
|
2020-11-23 07:08:17 +03:00
|
|
|
void invalidate()
|
|
|
|
{
|
|
|
|
auto key = searchString.toUcs4();
|
|
|
|
beginResetModel();
|
|
|
|
mapping = trie_.search(key, 7);
|
|
|
|
endResetModel();
|
|
|
|
|
|
|
|
std::string temp;
|
|
|
|
for (auto v : mapping) {
|
|
|
|
temp += std::to_string(v) + ", ";
|
|
|
|
}
|
|
|
|
nhlog::ui()->debug("mapping: {}", temp);
|
|
|
|
};
|
|
|
|
|
2020-11-20 04:38:08 +03:00
|
|
|
QHash<int, QByteArray> roleNames() const override
|
|
|
|
{
|
|
|
|
return this->sourceModel()->roleNames();
|
|
|
|
}
|
|
|
|
|
|
|
|
int rowCount(const QModelIndex &parent = QModelIndex()) const override
|
2020-09-02 13:32:57 +03:00
|
|
|
{
|
2020-11-23 07:08:17 +03:00
|
|
|
(void)parent;
|
|
|
|
return (int)mapping.size();
|
2020-09-02 13:32:57 +03:00
|
|
|
}
|
2020-11-20 04:38:08 +03:00
|
|
|
|
2020-11-23 07:08:17 +03:00
|
|
|
QModelIndex mapFromSource(const QModelIndex &sourceIndex) const override
|
2020-11-20 06:05:31 +03:00
|
|
|
{
|
2020-11-23 07:08:17 +03:00
|
|
|
for (int i = 0; i < (int)mapping.size(); i++) {
|
|
|
|
if (mapping[i] == sourceIndex.row()) {
|
|
|
|
return index(i, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return QModelIndex();
|
2020-11-20 06:05:31 +03:00
|
|
|
}
|
|
|
|
|
2020-11-23 07:08:17 +03:00
|
|
|
QModelIndex mapToSource(const QModelIndex &proxyIndex) const override
|
2020-11-20 06:05:31 +03:00
|
|
|
{
|
2020-11-23 07:08:17 +03:00
|
|
|
auto row = proxyIndex.row();
|
|
|
|
if (row < 0 || row >= (int)mapping.size())
|
|
|
|
return QModelIndex();
|
2020-11-20 06:05:31 +03:00
|
|
|
|
2020-11-23 07:08:17 +03:00
|
|
|
return sourceModel()->index(mapping[row], 0);
|
|
|
|
}
|
2020-11-20 06:05:31 +03:00
|
|
|
|
2020-11-23 07:08:17 +03:00
|
|
|
QModelIndex index(int row,
|
|
|
|
int column,
|
|
|
|
const QModelIndex &parent = QModelIndex()) const override
|
|
|
|
{
|
|
|
|
(void)parent;
|
|
|
|
return createIndex(row, column);
|
2020-11-20 06:05:31 +03:00
|
|
|
}
|
|
|
|
|
2020-11-23 07:08:17 +03:00
|
|
|
QModelIndex parent(const QModelIndex &) const override { return QModelIndex{}; }
|
|
|
|
int columnCount(const QModelIndex &) const override { return sourceModel()->columnCount(); }
|
|
|
|
|
2020-11-20 04:38:08 +03:00
|
|
|
public slots:
|
|
|
|
QVariant completionAt(int i) const
|
|
|
|
{
|
|
|
|
if (i >= 0 && i < rowCount())
|
|
|
|
return data(index(i, 0), CompletionModel::CompletionRole);
|
|
|
|
else
|
|
|
|
return {};
|
|
|
|
}
|
2020-11-20 06:05:31 +03:00
|
|
|
|
|
|
|
void setSearchString(QString s) { emit newSearchString(s); }
|
|
|
|
|
|
|
|
signals:
|
|
|
|
void newSearchString(QString);
|
|
|
|
|
|
|
|
private:
|
|
|
|
QString searchString;
|
2020-11-23 07:08:17 +03:00
|
|
|
trie<uint, int> trie_;
|
|
|
|
std::vector<int> mapping;
|
2020-09-02 13:32:57 +03:00
|
|
|
};
|