Locator: reduce strictness of actions filtering

Use the updated fuzzy matcher to get more relevant results.

Also highlight all ocurences of the search string in the display name
and the extra info.

Change-Id: I500b3323fd35953eac4f28db9fa03fc3870286dd
Reviewed-by: Eike Ziller <eike.ziller@qt.io>
This commit is contained in:
David Schulz
2022-09-12 09:28:57 +02:00
parent ded6500922
commit 7419fa1b3d
2 changed files with 93 additions and 40 deletions

View File

@@ -10,6 +10,8 @@
#include "locator/locatormanager.h" #include "locator/locatormanager.h"
#include <utils/algorithm.h> #include <utils/algorithm.h>
#include <utils/fuzzymatcher.h>
#include <utils/mapreduce.h>
#include <utils/qtcassert.h> #include <utils/qtcassert.h>
#include <utils/stringutils.h> #include <utils/stringutils.h>
@@ -61,50 +63,99 @@ static const QList<QAction *> menuBarActions()
QList<LocatorFilterEntry> ActionsFilter::matchesFor(QFutureInterface<LocatorFilterEntry> &future, QList<LocatorFilterEntry> ActionsFilter::matchesFor(QFutureInterface<LocatorFilterEntry> &future,
const QString &entry) const QString &entry)
{ {
Q_UNUSED(future) using Highlight = LocatorFilterEntry::HighlightInfo;
static const QString separators = ". >/"; if (entry.simplified().isEmpty())
static const QRegularExpression seperatorRegExp(QString("[%1]").arg(separators)); return m_entries;
QString normalized = entry;
normalized.replace(seperatorRegExp, separators.at(0));
const QStringList entryPath = normalized.split(separators.at(0), Qt::SkipEmptyParts);
QList<LocatorFilterEntry> filtered; const QRegularExpression regExp = createRegExp(entry, Qt::CaseInsensitive, true);
for (LocatorFilterEntry filterEntry : qAsConst(m_entries)) { using FilterResult = std::pair<MatchLevel, LocatorFilterEntry>;
int entryIndex = 0; const auto filter = [&](const LocatorFilterEntry &filterEntry) -> std::optional<FilterResult> {
int entryLength = 0; if (future.isCanceled())
int pathIndex = 0; return {};
const ActionFilterEntryData data = filterEntry.internalData.value<ActionFilterEntryData>(); Highlight highlight;
const QString pathText = data.path.join(" > ");
QStringList actionPath(data.path); const auto withHighlight = [&](LocatorFilterEntry result) {
if (!entryPath.isEmpty()) { result.highlightInfo = highlight;
actionPath << filterEntry.displayName; return result;
for (const QString &entry : entryPath) { };
const QRegularExpression re(".*" + entry + ".*",
QRegularExpression::CaseInsensitiveOption); Highlight::DataType first = Highlight::DisplayName;
pathIndex = actionPath.indexOf(re, pathIndex); QString allText = filterEntry.displayName + ' ' + filterEntry.extraInfo;
if (pathIndex < 0) QRegularExpressionMatch allTextMatch = regExp.match(allText);
continue; if (!allTextMatch.hasMatch()) {
} first = Highlight::ExtraInfo;
const QString &lastEntry(entryPath.last()); allText = filterEntry.extraInfo + ' ' + filterEntry.displayName;
entryLength = lastEntry.length(); allTextMatch = regExp.match(allText);
entryIndex = filterEntry.displayName.indexOf(lastEntry, 0, Qt::CaseInsensitive);
LocatorFilterEntry::HighlightInfo::DataType highlightType =
LocatorFilterEntry::HighlightInfo::DisplayName;
if (entryIndex >= 0) {
highlightType = LocatorFilterEntry::HighlightInfo::DisplayName;
} else {
entryIndex = pathText.indexOf(lastEntry, 0, Qt::CaseInsensitive);
if (entryIndex < 0)
continue;
highlightType = LocatorFilterEntry::HighlightInfo::ExtraInfo;
}
filterEntry.highlightInfo = {entryIndex, entryLength, highlightType};
} }
filtered << filterEntry; if (allTextMatch.hasMatch()) {
if (first == Highlight::DisplayName) {
const QRegularExpressionMatch displayMatch = regExp.match(filterEntry.displayName);
if (displayMatch.hasMatch())
highlight = highlightInfo(displayMatch);
const QRegularExpressionMatch extraMatch = regExp.match(filterEntry.extraInfo);
if (extraMatch.hasMatch()) {
Highlight extraHighlight = highlightInfo(extraMatch, Highlight::ExtraInfo);
highlight.startsExtraInfo = extraHighlight.startsExtraInfo;
highlight.lengthsExtraInfo = extraHighlight.lengthsExtraInfo;
}
if (filterEntry.displayName.startsWith(entry, Qt::CaseInsensitive))
return FilterResult{MatchLevel::Best, withHighlight(filterEntry)};
if (filterEntry.displayName.contains(entry, Qt::CaseInsensitive))
return FilterResult{MatchLevel::Better, withHighlight(filterEntry)};
if (displayMatch.hasMatch())
return FilterResult{MatchLevel::Good, withHighlight(filterEntry)};
if (extraMatch.hasMatch())
return FilterResult{MatchLevel::Normal, withHighlight(filterEntry)};
}
FuzzyMatcher::HighlightingPositions positions = FuzzyMatcher::highlightingPositions(
allTextMatch);
const int positionsCount = positions.starts.count();
QTC_ASSERT(positionsCount == positions.lengths.count(), return {});
const int border = first == Highlight::DisplayName ? filterEntry.displayName.length()
: filterEntry.extraInfo.length();
for (int i = 0; i < positionsCount; ++i) {
int start = positions.starts.at(i);
const int length = positions.lengths.at(i);
Highlight::DataType type = first;
if (start > border) {
// this highlight is behind the border so switch type and reset start index
start -= border + 1;
type = first == Highlight::DisplayName ? Highlight::ExtraInfo
: Highlight::DisplayName;
} else if (start + length > border) {
// skip this highlight since it starts before and ends after the border
// between the concatenated strings
continue;
}
if (type == Highlight::DisplayName) {
highlight.startsDisplay.append(start);
highlight.lengthsDisplay.append(length);
} else {
highlight.startsExtraInfo.append(start);
highlight.lengthsExtraInfo.append(length);
}
}
return FilterResult{MatchLevel::Normal, withHighlight(filterEntry)};
}
return {};
};
QMap<MatchLevel, QList<LocatorFilterEntry>> filtered;
const QList<std::optional<FilterResult>> filterResults = Utils::map(qAsConst(m_entries), filter)
.results();
for (const std::optional<FilterResult> &filterResult : filterResults) {
if (filterResult)
filtered[filterResult->first] << filterResult->second;
} }
return filtered; QList<LocatorFilterEntry> result;
for (const QList<LocatorFilterEntry> &sublist : qAsConst(filtered))
result << sublist;
return result;
} }
void ActionsFilter::accept(const LocatorFilterEntry &selection, QString *newText, void ActionsFilter::accept(const LocatorFilterEntry &selection, QString *newText,

View File

@@ -28,6 +28,8 @@ struct LocatorFilterEntry
ExtraInfo ExtraInfo
}; };
HighlightInfo() = default;
HighlightInfo(QVector<int> startIndex, HighlightInfo(QVector<int> startIndex,
QVector<int> length, QVector<int> length,
DataType type = DataType::DisplayName) DataType type = DataType::DisplayName)
@@ -88,7 +90,7 @@ struct LocatorFilterEntry
/* file path, if the entry is related to a file, is used e.g. for resolving a file icon */ /* file path, if the entry is related to a file, is used e.g. for resolving a file icon */
Utils::FilePath filePath; Utils::FilePath filePath;
/* highlighting support */ /* highlighting support */
HighlightInfo highlightInfo{0, 0}; HighlightInfo highlightInfo;
static bool compareLexigraphically(const Core::LocatorFilterEntry &lhs, static bool compareLexigraphically(const Core::LocatorFilterEntry &lhs,
const Core::LocatorFilterEntry &rhs) const Core::LocatorFilterEntry &rhs)