From 7419fa1b3db543d44458ba33a7ffa0a80de3085e Mon Sep 17 00:00:00 2001 From: David Schulz Date: Mon, 12 Sep 2022 09:28:57 +0200 Subject: [PATCH] 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 --- src/plugins/coreplugin/actionsfilter.cpp | 129 ++++++++++++------ .../coreplugin/locator/ilocatorfilter.h | 4 +- 2 files changed, 93 insertions(+), 40 deletions(-) diff --git a/src/plugins/coreplugin/actionsfilter.cpp b/src/plugins/coreplugin/actionsfilter.cpp index 62fa2b2b8a1..d7d3b06bae5 100644 --- a/src/plugins/coreplugin/actionsfilter.cpp +++ b/src/plugins/coreplugin/actionsfilter.cpp @@ -10,6 +10,8 @@ #include "locator/locatormanager.h" #include +#include +#include #include #include @@ -61,50 +63,99 @@ static const QList menuBarActions() QList ActionsFilter::matchesFor(QFutureInterface &future, const QString &entry) { - Q_UNUSED(future) - static const QString separators = ". >/"; - static const QRegularExpression seperatorRegExp(QString("[%1]").arg(separators)); - QString normalized = entry; - normalized.replace(seperatorRegExp, separators.at(0)); - const QStringList entryPath = normalized.split(separators.at(0), Qt::SkipEmptyParts); + using Highlight = LocatorFilterEntry::HighlightInfo; + if (entry.simplified().isEmpty()) + return m_entries; - QList filtered; + const QRegularExpression regExp = createRegExp(entry, Qt::CaseInsensitive, true); - for (LocatorFilterEntry filterEntry : qAsConst(m_entries)) { - int entryIndex = 0; - int entryLength = 0; - int pathIndex = 0; - const ActionFilterEntryData data = filterEntry.internalData.value(); - const QString pathText = data.path.join(" > "); - QStringList actionPath(data.path); - if (!entryPath.isEmpty()) { - actionPath << filterEntry.displayName; - for (const QString &entry : entryPath) { - const QRegularExpression re(".*" + entry + ".*", - QRegularExpression::CaseInsensitiveOption); - pathIndex = actionPath.indexOf(re, pathIndex); - if (pathIndex < 0) - continue; - } - const QString &lastEntry(entryPath.last()); - entryLength = lastEntry.length(); - 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}; + using FilterResult = std::pair; + const auto filter = [&](const LocatorFilterEntry &filterEntry) -> std::optional { + if (future.isCanceled()) + return {}; + Highlight highlight; + + const auto withHighlight = [&](LocatorFilterEntry result) { + result.highlightInfo = highlight; + return result; + }; + + Highlight::DataType first = Highlight::DisplayName; + QString allText = filterEntry.displayName + ' ' + filterEntry.extraInfo; + QRegularExpressionMatch allTextMatch = regExp.match(allText); + if (!allTextMatch.hasMatch()) { + first = Highlight::ExtraInfo; + allText = filterEntry.extraInfo + ' ' + filterEntry.displayName; + allTextMatch = regExp.match(allText); } - 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> filtered; + const QList> filterResults = Utils::map(qAsConst(m_entries), filter) + .results(); + for (const std::optional &filterResult : filterResults) { + if (filterResult) + filtered[filterResult->first] << filterResult->second; } - return filtered; + QList result; + for (const QList &sublist : qAsConst(filtered)) + result << sublist; + return result; } void ActionsFilter::accept(const LocatorFilterEntry &selection, QString *newText, diff --git a/src/plugins/coreplugin/locator/ilocatorfilter.h b/src/plugins/coreplugin/locator/ilocatorfilter.h index 58979d014bd..cc7a2124ed3 100644 --- a/src/plugins/coreplugin/locator/ilocatorfilter.h +++ b/src/plugins/coreplugin/locator/ilocatorfilter.h @@ -28,6 +28,8 @@ struct LocatorFilterEntry ExtraInfo }; + HighlightInfo() = default; + HighlightInfo(QVector startIndex, QVector length, 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 */ Utils::FilePath filePath; /* highlighting support */ - HighlightInfo highlightInfo{0, 0}; + HighlightInfo highlightInfo; static bool compareLexigraphically(const Core::LocatorFilterEntry &lhs, const Core::LocatorFilterEntry &rhs)