forked from qt-creator/qt-creator
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:
@@ -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,
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user