Use a trie for filtering completions (not fuzzy yet)

This commit is contained in:
Nicolas Werner 2020-11-23 05:08:17 +01:00
parent b47d2a809c
commit c8fa40a2df
2 changed files with 132 additions and 75 deletions

View file

@ -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
} }

View file

@ -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,82 +137,39 @@ 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 = QModelIndex index(int row,
sourceModel()->data(source_right, CompletionModel::SearchRole).toString(); int column,
auto right2 = const QModelIndex &parent = QModelIndex()) const override
sourceModel()->data(source_right, CompletionModel::SearchRole2).toString(); {
auto right = (size_t)right1.toLower().indexOf(searchString); (void)parent;
// auto right = return createIndex(row, column);
// 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 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;
}; };