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)