mirror of
https://github.com/Nheko-Reborn/nheko.git
synced 2024-11-23 03:18:49 +03:00
Use a trie for filtering completions (not fuzzy yet)
This commit is contained in:
parent
b47d2a809c
commit
c8fa40a2df
2 changed files with 132 additions and 75 deletions
|
@ -55,7 +55,7 @@ Popup {
|
||||||
model: completer
|
model: completer
|
||||||
|
|
||||||
delegate: Rectangle {
|
delegate: Rectangle {
|
||||||
color: model.index == popup.currentIndex ? colors.window : colors.base
|
color: model.index == popup.currentIndex ? colors.window : colors.alternateBase
|
||||||
height: chooser.childrenRect.height + 4
|
height: chooser.childrenRect.height + 4
|
||||||
width: chooser.childrenRect.width + 4
|
width: chooser.childrenRect.width + 4
|
||||||
|
|
||||||
|
@ -141,7 +141,7 @@ Popup {
|
||||||
}
|
}
|
||||||
|
|
||||||
background: Rectangle {
|
background: Rectangle {
|
||||||
color: colors.base
|
color: colors.alternateBase
|
||||||
implicitHeight: popup.contentHeight
|
implicitHeight: popup.contentHeight
|
||||||
implicitWidth: popup.contentWidth
|
implicitWidth: popup.contentWidth
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,22 +2,106 @@
|
||||||
|
|
||||||
// Class for showing a limited amount of completions at a time
|
// Class for showing a limited amount of completions at a time
|
||||||
|
|
||||||
#include <QSortFilterProxyModel>
|
#include <QAbstractProxyModel>
|
||||||
|
|
||||||
#include "CompletionModelRoles.h"
|
#include "CompletionModelRoles.h"
|
||||||
|
#include "Logging.h"
|
||||||
#include "Utils.h"
|
#include "Utils.h"
|
||||||
|
|
||||||
class CompletionProxyModel : public QSortFilterProxyModel
|
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());
|
||||||
|
ret.insert(ret.end(), temp.begin(), temp.end());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Value> search(const QVector<Key> &keys, size_t limit) const
|
||||||
|
{
|
||||||
|
std::vector<Value> ret;
|
||||||
|
auto t = this;
|
||||||
|
int i = 0;
|
||||||
|
for (; i < (int)keys.size(); i++) {
|
||||||
|
if (auto e = t->next.find(keys[i]); e != t->next.end()) {
|
||||||
|
t = &e->second;
|
||||||
|
} else {
|
||||||
|
t = nullptr;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (t) {
|
||||||
|
ret = t->valuesAndSubvalues(limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class CompletionProxyModel : public QAbstractProxyModel
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
CompletionProxyModel(QAbstractItemModel *model, QObject *parent = nullptr)
|
CompletionProxyModel(QAbstractItemModel *model, QObject *parent = nullptr)
|
||||||
: QSortFilterProxyModel(parent)
|
: QAbstractProxyModel(parent)
|
||||||
{
|
{
|
||||||
setSourceModel(model);
|
setSourceModel(model);
|
||||||
sort(0, Qt::AscendingOrder);
|
|
||||||
setFilterRole(CompletionModel::SearchRole);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
connect(
|
connect(
|
||||||
this,
|
this,
|
||||||
|
@ -32,6 +116,20 @@ public:
|
||||||
Qt::QueuedConnection);
|
Qt::QueuedConnection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
};
|
||||||
|
|
||||||
QHash<int, QByteArray> roleNames() const override
|
QHash<int, QByteArray> roleNames() const override
|
||||||
{
|
{
|
||||||
return this->sourceModel()->roleNames();
|
return this->sourceModel()->roleNames();
|
||||||
|
@ -39,83 +137,40 @@ public:
|
||||||
|
|
||||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override
|
int rowCount(const QModelIndex &parent = QModelIndex()) const override
|
||||||
{
|
{
|
||||||
auto row_count = QSortFilterProxyModel::rowCount(parent);
|
(void)parent;
|
||||||
return (row_count < 7) ? row_count : 7;
|
return (int)mapping.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
|
QModelIndex mapFromSource(const QModelIndex &sourceIndex) const override
|
||||||
{
|
{
|
||||||
if (searchString.size() < 1)
|
for (int i = 0; i < (int)mapping.size(); i++) {
|
||||||
return true;
|
if (mapping[i] == sourceIndex.row()) {
|
||||||
|
return index(i, 0);
|
||||||
auto source_index = sourceModel()->index(source_row, 0, source_parent);
|
}
|
||||||
auto role1 = sourceModel()
|
}
|
||||||
->data(source_index, CompletionModel::SearchRole)
|
return QModelIndex();
|
||||||
.toString()
|
|
||||||
.toLower();
|
|
||||||
|
|
||||||
if (role1.contains(searchString))
|
|
||||||
return true;
|
|
||||||
// auto score =
|
|
||||||
// utils::levenshtein_distance(searchString, role1.toLower().toStdString());
|
|
||||||
// if ((size_t)role1.size() >= searchString.size() &&
|
|
||||||
// ((size_t)score) < (size_t)role1.size() - searchString.size() + 2)
|
|
||||||
// return true;
|
|
||||||
|
|
||||||
auto role2 = sourceModel()
|
|
||||||
->data(source_index, CompletionModel::SearchRole2)
|
|
||||||
.toString()
|
|
||||||
.toLower();
|
|
||||||
if (role2.contains(searchString))
|
|
||||||
return true;
|
|
||||||
// if (!role2.isEmpty()) {
|
|
||||||
// score =
|
|
||||||
// utils::levenshtein_distance(searchString,
|
|
||||||
// role2.toLower().toStdString());
|
|
||||||
// if ((size_t)role2.size() >= searchString.size() &&
|
|
||||||
// ((size_t)score) < (size_t)role2.size() - searchString.size() + 2)
|
|
||||||
// return true;
|
|
||||||
//}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool lessThan(const QModelIndex &source_left,
|
QModelIndex mapToSource(const QModelIndex &proxyIndex) const override
|
||||||
const QModelIndex &source_right) const override
|
|
||||||
{
|
{
|
||||||
if (searchString.size() < 1)
|
auto row = proxyIndex.row();
|
||||||
return false;
|
if (row < 0 || row >= (int)mapping.size())
|
||||||
|
return QModelIndex();
|
||||||
|
|
||||||
auto left1 =
|
return sourceModel()->index(mapping[row], 0);
|
||||||
sourceModel()->data(source_left, CompletionModel::SearchRole).toString();
|
|
||||||
auto left2 =
|
|
||||||
sourceModel()->data(source_left, CompletionModel::SearchRole2).toString();
|
|
||||||
auto left = (size_t)left1.toLower().indexOf(searchString);
|
|
||||||
// utils::levenshtein_distance(searchString, left1.toLower().toStdString());
|
|
||||||
if (!left2.isEmpty()) {
|
|
||||||
// left = std::min(
|
|
||||||
// utils::levenshtein_distance(searchString,
|
|
||||||
// left2.toLower().toStdString()), left);
|
|
||||||
left = std::min((size_t)left2.toLower().indexOf(searchString), left);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto right1 =
|
|
||||||
sourceModel()->data(source_right, CompletionModel::SearchRole).toString();
|
|
||||||
auto right2 =
|
|
||||||
sourceModel()->data(source_right, CompletionModel::SearchRole2).toString();
|
|
||||||
auto right = (size_t)right1.toLower().indexOf(searchString);
|
|
||||||
// auto right =
|
|
||||||
// utils::levenshtein_distance(searchString, right1.toLower().toStdString());
|
|
||||||
if (!right2.isEmpty()) {
|
|
||||||
// right = std::min(
|
|
||||||
// utils::levenshtein_distance(searchString,
|
|
||||||
// right2.toLower().toStdString()), right);
|
|
||||||
right = std::min((size_t)right2.toLower().indexOf(searchString), right);
|
|
||||||
}
|
|
||||||
|
|
||||||
return left < right;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QModelIndex index(int row,
|
||||||
|
int column,
|
||||||
|
const QModelIndex &parent = QModelIndex()) const override
|
||||||
|
{
|
||||||
|
(void)parent;
|
||||||
|
return createIndex(row, column);
|
||||||
|
}
|
||||||
|
|
||||||
|
QModelIndex parent(const QModelIndex &) const override { return QModelIndex{}; }
|
||||||
|
int columnCount(const QModelIndex &) const override { return sourceModel()->columnCount(); }
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
QVariant completionAt(int i) const
|
QVariant completionAt(int i) const
|
||||||
{
|
{
|
||||||
|
@ -132,4 +187,6 @@ signals:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString searchString;
|
QString searchString;
|
||||||
|
trie<uint, int> trie_;
|
||||||
|
std::vector<int> mapping;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue