diff options
author | Andre Hartmann <aha_1980@gmx.de> | 2017-07-24 20:55:47 +0200 |
---|---|---|
committer | André Hartmann <aha_1980@gmx.de> | 2017-09-19 08:24:38 +0000 |
commit | 632f2a77098e57eeecdaac39efd32741dfc68e76 (patch) | |
tree | 80fa68fca6f958eadd0f095df02a48cb2e438d3f | |
parent | 2fb54abd03d71e1e205a1dc6f94769688d1bc2e0 (diff) |
Locator: Add camel hump locator filter for C++, QML, and files
* Use the CamelHumpMatcher in the C++, QML, and files filters
* Supports matching against UpperCamelCase, lowerCamelCase
and snake_case strings
* Supports highlighting of matched characters
Task-number: QTCREATORBUG-3111
Started-by: David Kaspar <dkaspar@blackberry.com>
Change-Id: If6220191432ef965bde3c8dbe4a10d89e222ba6f
Reviewed-by: Orgad Shaneh <orgads@gmail.com>
Reviewed-by: Eike Ziller <eike.ziller@qt.io>
-rw-r--r-- | src/libs/utils/camelhumpmatcher.cpp | 44 | ||||
-rw-r--r-- | src/libs/utils/camelhumpmatcher.h | 10 | ||||
-rw-r--r-- | src/libs/utils/highlightingitemdelegate.cpp | 43 | ||||
-rw-r--r-- | src/plugins/coreplugin/locator/basefilefilter.cpp | 30 | ||||
-rw-r--r-- | src/plugins/coreplugin/locator/ilocatorfilter.cpp | 20 | ||||
-rw-r--r-- | src/plugins/coreplugin/locator/ilocatorfilter.h | 15 | ||||
-rw-r--r-- | src/plugins/coreplugin/locator/locatorwidget.cpp | 3 | ||||
-rw-r--r-- | src/plugins/coreplugin/locator/opendocumentsfilter.cpp | 18 | ||||
-rw-r--r-- | src/plugins/cpptools/cppcurrentdocumentfilter.cpp | 33 | ||||
-rw-r--r-- | src/plugins/cpptools/cpplocatorfilter.cpp | 52 | ||||
-rw-r--r-- | src/plugins/cpptools/cpplocatorfilter_test.cpp | 13 | ||||
-rw-r--r-- | src/plugins/qmljstools/qmljsfunctionfilter.cpp | 35 | ||||
-rw-r--r-- | tests/auto/utils/camelhumpmatcher/tst_camelhumpmatcher.cpp | 61 | ||||
-rw-r--r-- | tests/cpplocators/testdata_basic/file1.cpp | 4 |
14 files changed, 280 insertions, 101 deletions
diff --git a/src/libs/utils/camelhumpmatcher.cpp b/src/libs/utils/camelhumpmatcher.cpp index 7c8af9b1292..4ede263d0d4 100644 --- a/src/libs/utils/camelhumpmatcher.cpp +++ b/src/libs/utils/camelhumpmatcher.cpp @@ -79,16 +79,19 @@ QRegularExpression CamelHumpMatcher::createCamelHumpRegExp( else if (c == asterisk) keyRegExp += ".*"; else - keyRegExp += QRegularExpression::escape(c); + keyRegExp += '(' + QRegularExpression::escape(c) + ')'; } else if (caseSensitivity == CaseSensitivity::CaseInsensitive || (caseSensitivity == CaseSensitivity::FirstLetterCaseSensitive && !first)) { keyRegExp += "(?:"; keyRegExp += first ? uppercaseWordFirst : uppercaseWordContinuation; - keyRegExp += QRegularExpression::escape(c.toUpper()); - keyRegExp += '|'; - keyRegExp += first ? lowercaseWordFirst : lowercaseWordContinuation; - keyRegExp += QRegularExpression::escape(c.toLower()); + keyRegExp += '(' + QRegularExpression::escape(c.toUpper()); + if (first) { + keyRegExp += '|' + lowercaseWordFirst + QRegularExpression::escape(c.toLower()) + ')'; + } else { + keyRegExp += ")|" + lowercaseWordContinuation; + keyRegExp += '(' + QRegularExpression::escape(c.toLower()) + ')'; + } keyRegExp += ')'; } else { if (!first) { @@ -104,3 +107,34 @@ QRegularExpression CamelHumpMatcher::createCamelHumpRegExp( } return QRegularExpression(keyRegExp); } + +/*! + * \brief Returns a list of matched character positions and their matched lengths for the + * given regular expression \a match. + * + * The list is minimized by combining adjacent highlighting positions to a single position. + */ +CamelHumpMatcher::HighlightingPositions CamelHumpMatcher::highlightingPositions( + const QRegularExpressionMatch &match) +{ + HighlightingPositions result; + + for (int i = 1, size = match.capturedTexts().size(); i < size; ++i) { + // skip unused positions, they can appear because upper- and lowercase + // checks for one character are done using two capture groups + if (match.capturedStart(i) < 0) + continue; + + // check for possible highlighting continuation to keep the list minimal + if (!result.starts.isEmpty() + && (result.starts.last() + result.lengths.last() == match.capturedStart(i))) { + result.lengths.last() += match.capturedLength(i); + } else { + // no continuation, append as different chunk + result.starts.append(match.capturedStart(i)); + result.lengths.append(match.capturedLength(i)); + } + } + + return result; +} diff --git a/src/libs/utils/camelhumpmatcher.h b/src/libs/utils/camelhumpmatcher.h index 8364678ab47..8021c4ddf68 100644 --- a/src/libs/utils/camelhumpmatcher.h +++ b/src/libs/utils/camelhumpmatcher.h @@ -29,8 +29,11 @@ #include "utils_global.h" +#include <QVector> + QT_BEGIN_NAMESPACE class QRegularExpression; +class QRegularExpressionMatch; class QString; QT_END_NAMESPACE @@ -43,6 +46,13 @@ public: FirstLetterCaseSensitive }; + class HighlightingPositions { + public: + QVector<int> starts; + QVector<int> lengths; + }; + static QRegularExpression createCamelHumpRegExp(const QString &pattern, CaseSensitivity caseSensitivity = CaseSensitivity::CaseInsensitive); + static HighlightingPositions highlightingPositions(const QRegularExpressionMatch &match); }; diff --git a/src/libs/utils/highlightingitemdelegate.cpp b/src/libs/utils/highlightingitemdelegate.cpp index fe2525122c8..c9bb43eb948 100644 --- a/src/libs/utils/highlightingitemdelegate.cpp +++ b/src/libs/utils/highlightingitemdelegate.cpp @@ -155,28 +155,37 @@ void HighlightingItemDelegate::drawText(QPainter *painter, if (index.model()->hasChildren(index)) text += " (" + QString::number(index.model()->rowCount(index)) + ')'; - int searchTermStart = index.model()->data(index, int(HighlightingItemRole::StartColumn)).toInt(); - int searchTermLength = index.model()->data(index, int(HighlightingItemRole::Length)).toInt(); - if (searchTermStart < 0 || searchTermStart >= text.length() || searchTermLength < 1) { + QVector<int> searchTermStarts = + index.model()->data(index, int(HighlightingItemRole::StartColumn)).value<QVector<int>>(); + QVector<int> searchTermLengths = + index.model()->data(index, int(HighlightingItemRole::Length)).value<QVector<int>>(); + + if (searchTermStarts.isEmpty()) { drawDisplay(painter, option, rect, text.replace('\t', m_tabString), {}); return; } // replace tabs with searchTerm bookkeeping - int searchTermEnd = searchTermStart + searchTermLength; const int tabDiff = m_tabString.size() - 1; for (int i = 0; i < text.length(); i++) { - if (text.at(i) == '\t') { - text.replace(i, 1, m_tabString); - if (i < searchTermStart) { - searchTermStart += tabDiff; - searchTermEnd += tabDiff; - } else if (i < searchTermEnd) { - searchTermEnd += tabDiff; - searchTermLength += tabDiff; - } - i += tabDiff; + if (text.at(i) != '\t') + continue; + + text.replace(i, 1, m_tabString); + + // adjust highlighting length if tab is highlighted + for (int j = 0; j < searchTermStarts.size(); ++j) { + if (i >= searchTermStarts.at(j) && i < searchTermStarts.at(j) + searchTermLengths.at(j)) + searchTermLengths[j] += tabDiff; } + + // adjust all following highlighting starts + for (int j = 0; j < searchTermStarts.size(); ++j) { + if (searchTermStarts.at(j) > i) + searchTermStarts[j] += tabDiff; + } + + i += tabDiff; } const QColor highlightForeground = @@ -187,7 +196,11 @@ void HighlightingItemDelegate::drawText(QPainter *painter, highlightFormat.setForeground(highlightForeground); highlightFormat.setBackground(highlightBackground); - drawDisplay(painter, option, rect, text, {{searchTermStart, searchTermLength, highlightFormat}}); + QVector<QTextLayout::FormatRange> formats; + for (int i = 0, size = searchTermStarts.size(); i < size; ++i) + formats.append({searchTermStarts.at(i), searchTermLengths.at(i), highlightFormat}); + + drawDisplay(painter, option, rect, text, formats); } // copied from QItemDelegate for drawDisplay diff --git a/src/plugins/coreplugin/locator/basefilefilter.cpp b/src/plugins/coreplugin/locator/basefilefilter.cpp index 4d7cb124f38..86fe5078f85 100644 --- a/src/plugins/coreplugin/locator/basefilefilter.cpp +++ b/src/plugins/coreplugin/locator/basefilefilter.cpp @@ -26,12 +26,12 @@ #include "basefilefilter.h" #include <coreplugin/editormanager/editormanager.h> +#include <utils/camelhumpmatcher.h> #include <utils/fileutils.h> #include <utils/qtcassert.h> #include <QDir> -#include <QRegExp> -#include <QStringMatcher> +#include <QRegularExpression> #include <QTimer> using namespace Core; @@ -100,16 +100,15 @@ QList<LocatorFilterEntry> BaseFileFilter::matchesFor(QFutureInterface<LocatorFil QList<LocatorFilterEntry> goodEntries; const QString entry = QDir::fromNativeSeparators(origEntry); const EditorManager::FilePathInfo fp = EditorManager::splitLineAndColumnNumber(entry); - const Qt::CaseSensitivity cs = caseSensitivity(fp.filePath); - QStringMatcher matcher(fp.filePath, cs); - QRegExp regexp(fp.filePath, cs, QRegExp::Wildcard); + const QRegularExpression regexp = containsWildcard(entry) + ? createWildcardRegExp(entry) : CamelHumpMatcher::createCamelHumpRegExp(entry); + if (!regexp.isValid()) { d->m_current.clear(); // free memory return betterEntries; } const QChar pathSeparator(QLatin1Char('/')); const bool hasPathSeparator = fp.filePath.contains(pathSeparator); - const bool hasWildcard = containsWildcard(fp.filePath); const bool containsPreviousEntry = !d->m_current.previousEntry.isEmpty() && fp.filePath.contains(d->m_current.previousEntry); const bool pathSeparatorAdded = !d->m_current.previousEntry.contains(pathSeparator) @@ -136,27 +135,24 @@ QList<LocatorFilterEntry> BaseFileFilter::matchesFor(QFutureInterface<LocatorFil QString path = d->m_current.iterator->filePath(); QString name = d->m_current.iterator->fileName(); QString matchText = hasPathSeparator ? path : name; - int index = hasWildcard ? regexp.indexIn(matchText) : matcher.indexIn(matchText); + QRegularExpressionMatch match = regexp.match(matchText); - if (index >= 0) { + if (match.hasMatch()) { QFileInfo fi(path); LocatorFilterEntry filterEntry(this, fi.fileName(), QString(path + fp.postfix)); filterEntry.fileName = path; filterEntry.extraInfo = FileUtils::shortNativePath(FileName(fi)); LocatorFilterEntry::HighlightInfo::DataType hDataType = LocatorFilterEntry::HighlightInfo::DisplayName; - int length = hasWildcard ? regexp.matchedLength() : fp.filePath.length(); - const bool betterMatch = index == 0; + const bool betterMatch = match.capturedStart() == 0; if (hasPathSeparator) { - const int indexCandidate = index + filterEntry.extraInfo.length() - path.length(); - const int cutOff = indexCandidate < 0 ? -indexCandidate : 0; - index = qMax(indexCandidate, 0); - length = qMax(length - cutOff, 1); + match = regexp.match(filterEntry.extraInfo); hDataType = LocatorFilterEntry::HighlightInfo::ExtraInfo; } - - if (index >= 0) - filterEntry.highlightInfo = LocatorFilterEntry::HighlightInfo(index, length, hDataType); + const CamelHumpMatcher::HighlightingPositions positions = + CamelHumpMatcher::highlightingPositions(match); + filterEntry.highlightInfo = + LocatorFilterEntry::HighlightInfo(positions.starts, positions.lengths, hDataType); if (betterMatch) betterEntries.append(filterEntry); diff --git a/src/plugins/coreplugin/locator/ilocatorfilter.cpp b/src/plugins/coreplugin/locator/ilocatorfilter.cpp index 3b5736f95ba..3a0d897a63f 100644 --- a/src/plugins/coreplugin/locator/ilocatorfilter.cpp +++ b/src/plugins/coreplugin/locator/ilocatorfilter.cpp @@ -34,6 +34,7 @@ #include <QDialogButtonBox> #include <QLabel> #include <QLineEdit> +#include <QRegularExpression> using namespace Core; @@ -202,6 +203,25 @@ bool ILocatorFilter::containsWildcard(const QString &str) } /*! + * \brief Returns a simple regular expression to search for \a text. + * + * \a text may contain the simple '?' and '*' wildcards known from the shell. + * '?' matches exactly one character, '*' matches a number of characters + * (including none). + * + * The regular expression contains capture groups to allow highlighting + * matched characters after a match. + */ +QRegularExpression ILocatorFilter::createWildcardRegExp(const QString &text) +{ + QString pattern = '(' + text + ')'; + pattern.replace('?', ").("); + pattern.replace('*', ").*("); + pattern.remove("()"); + return QRegularExpression(pattern, QRegularExpression::CaseInsensitiveOption); +} + +/*! Specifies a title for configuration dialogs. */ QString ILocatorFilter::msgConfigureDialogTitle() diff --git a/src/plugins/coreplugin/locator/ilocatorfilter.h b/src/plugins/coreplugin/locator/ilocatorfilter.h index e649a1c959d..e48464f86fc 100644 --- a/src/plugins/coreplugin/locator/ilocatorfilter.h +++ b/src/plugins/coreplugin/locator/ilocatorfilter.h @@ -46,13 +46,19 @@ struct LocatorFilterEntry }; HighlightInfo(int startIndex, int length, DataType type = DataType::DisplayName) - : startIndex(startIndex) - , length(length) + : starts{startIndex} + , lengths{length} , dataType(type) {} - int startIndex; - int length; + HighlightInfo(QVector<int> startIndex, QVector<int> length, DataType type = DataType::DisplayName) + : starts(startIndex) + , lengths(length) + , dataType(type) + {} + + QVector<int> starts; + QVector<int> lengths; DataType dataType; }; @@ -138,6 +144,7 @@ public: static Qt::CaseSensitivity caseSensitivity(const QString &str); static bool containsWildcard(const QString &str); + static QRegularExpression createWildcardRegExp(const QString &text); static QString msgConfigureDialogTitle(); static QString msgPrefixLabel(); diff --git a/src/plugins/coreplugin/locator/locatorwidget.cpp b/src/plugins/coreplugin/locator/locatorwidget.cpp index dcacc1a4f1a..3ba6b7f3d14 100644 --- a/src/plugins/coreplugin/locator/locatorwidget.cpp +++ b/src/plugins/coreplugin/locator/locatorwidget.cpp @@ -217,7 +217,8 @@ QVariant LocatorModel::data(const QModelIndex &index, int role) const : ExtraInfoColumn; if (highlightColumn == index.column()) { const bool startIndexRole = role == int(HighlightingItemRole::StartColumn); - return startIndexRole ? entry.highlightInfo.startIndex : entry.highlightInfo.length; + return startIndexRole ? QVariant::fromValue(entry.highlightInfo.starts) + : QVariant::fromValue(entry.highlightInfo.lengths); } break; } diff --git a/src/plugins/coreplugin/locator/opendocumentsfilter.cpp b/src/plugins/coreplugin/locator/opendocumentsfilter.cpp index 8975aeb6e0b..b9b1779529e 100644 --- a/src/plugins/coreplugin/locator/opendocumentsfilter.cpp +++ b/src/plugins/coreplugin/locator/opendocumentsfilter.cpp @@ -27,12 +27,13 @@ #include <coreplugin/editormanager/editormanager.h> #include <coreplugin/editormanager/ieditor.h> +#include <utils/camelhumpmatcher.h> #include <utils/fileutils.h> #include <QAbstractItemModel> #include <QFileInfo> #include <QMutexLocker> -#include <QRegExp> +#include <QRegularExpression> using namespace Core; using namespace Core::Internal; @@ -60,7 +61,9 @@ QList<LocatorFilterEntry> OpenDocumentsFilter::matchesFor(QFutureInterface<Locat QList<LocatorFilterEntry> goodEntries; QList<LocatorFilterEntry> betterEntries; const EditorManager::FilePathInfo fp = EditorManager::splitLineAndColumnNumber(entry); - QRegExp regexp(fp.filePath, caseSensitivity(fp.filePath), QRegExp::Wildcard); + const QRegularExpression regexp = containsWildcard(entry) + ? createWildcardRegExp(entry) : CamelHumpMatcher::createCamelHumpRegExp(entry); + if (!regexp.isValid()) return goodEntries; @@ -71,13 +74,16 @@ QList<LocatorFilterEntry> OpenDocumentsFilter::matchesFor(QFutureInterface<Locat if (fileName.isEmpty()) continue; QString displayName = editorEntry.displayName; - const int index = regexp.indexIn(displayName); - if (index >= 0) { + const QRegularExpressionMatch match = regexp.match(displayName); + if (match.hasMatch()) { + const CamelHumpMatcher::HighlightingPositions positions = + CamelHumpMatcher::highlightingPositions(match); LocatorFilterEntry filterEntry(this, displayName, QString(fileName + fp.postfix)); filterEntry.extraInfo = FileUtils::shortNativePath(FileName::fromString(fileName)); filterEntry.fileName = fileName; - filterEntry.highlightInfo = {index, regexp.matchedLength()}; - if (index == 0) + filterEntry.highlightInfo.starts = positions.starts; + filterEntry.highlightInfo.lengths = positions.lengths; + if (match.capturedStart() == 0) betterEntries.append(filterEntry); else goodEntries.append(filterEntry); diff --git a/src/plugins/cpptools/cppcurrentdocumentfilter.cpp b/src/plugins/cpptools/cppcurrentdocumentfilter.cpp index bc0584a0a56..6f528a3f4f5 100644 --- a/src/plugins/cpptools/cppcurrentdocumentfilter.cpp +++ b/src/plugins/cpptools/cppcurrentdocumentfilter.cpp @@ -30,9 +30,9 @@ #include <coreplugin/idocument.h> #include <coreplugin/editormanager/editormanager.h> #include <coreplugin/editormanager/ieditor.h> +#include <utils/camelhumpmatcher.h> -#include <QRegExp> -#include <QStringMatcher> +#include <QRegularExpression> using namespace CppTools::Internal; using namespace CPlusPlus; @@ -66,12 +66,12 @@ QList<Core::LocatorFilterEntry> CppCurrentDocumentFilter::matchesFor( { QList<Core::LocatorFilterEntry> goodEntries; QList<Core::LocatorFilterEntry> betterEntries; - const Qt::CaseSensitivity cs = caseSensitivity(entry); - QStringMatcher matcher(entry, cs); - QRegExp regexp(entry, cs, QRegExp::Wildcard); + + const QRegularExpression regexp = containsWildcard(entry) + ? createWildcardRegExp(entry) : CamelHumpMatcher::createCamelHumpRegExp(entry); + if (!regexp.isValid()) return goodEntries; - bool hasWildcard = containsWildcard(entry); foreach (IndexItem::Ptr info, itemsOfCurrentDocument()) { if (future.isCanceled()) @@ -83,29 +83,30 @@ QList<Core::LocatorFilterEntry> CppCurrentDocumentFilter::matchesFor( else if (info->type() == IndexItem::Function) matchString += info->symbolType(); - int index = hasWildcard ? regexp.indexIn(matchString) : matcher.indexIn(matchString); - if (index >= 0) { - const bool betterMatch = index == 0; + QRegularExpressionMatch match = regexp.match(matchString); + if (match.hasMatch()) { + const bool betterMatch = match.capturedStart() == 0; QVariant id = qVariantFromValue(info); QString name = matchString; QString extraInfo = info->symbolScope(); if (info->type() == IndexItem::Function) { if (info->unqualifiedNameAndScope(matchString, &name, &extraInfo)) { name += info->symbolType(); - index = hasWildcard ? regexp.indexIn(name) : matcher.indexIn(name); + match = regexp.match(name); } } Core::LocatorFilterEntry filterEntry(this, name, id, info->icon()); filterEntry.extraInfo = extraInfo; - if (index < 0) { - index = hasWildcard ? regexp.indexIn(extraInfo) : matcher.indexIn(extraInfo); + if (!match.hasMatch()) { + match = regexp.match(extraInfo); filterEntry.highlightInfo.dataType = Core::LocatorFilterEntry::HighlightInfo::ExtraInfo; } - if (index >= 0) { - filterEntry.highlightInfo.startIndex = index; - filterEntry.highlightInfo.length = hasWildcard ? regexp.matchedLength() : entry.length(); - } + const CamelHumpMatcher::HighlightingPositions positions = + CamelHumpMatcher::highlightingPositions(match); + filterEntry.highlightInfo.starts = positions.starts; + filterEntry.highlightInfo.lengths = positions.lengths; + if (betterMatch) betterEntries.append(filterEntry); else diff --git a/src/plugins/cpptools/cpplocatorfilter.cpp b/src/plugins/cpptools/cpplocatorfilter.cpp index f8bf53dd325..86028fbe624 100644 --- a/src/plugins/cpptools/cpplocatorfilter.cpp +++ b/src/plugins/cpptools/cpplocatorfilter.cpp @@ -28,9 +28,9 @@ #include <coreplugin/editormanager/editormanager.h> #include <utils/algorithm.h> +#include <utils/camelhumpmatcher.h> -#include <QRegExp> -#include <QStringMatcher> +#include <QRegularExpression> #include <algorithm> @@ -72,34 +72,37 @@ QList<Core::LocatorFilterEntry> CppLocatorFilter::matchesFor( { QList<Core::LocatorFilterEntry> goodEntries; QList<Core::LocatorFilterEntry> betterEntries; - const Qt::CaseSensitivity cs = caseSensitivity(entry); - QStringMatcher matcher(entry, cs); - QRegExp regexp(entry, cs, QRegExp::Wildcard); - if (!regexp.isValid()) - return goodEntries; - bool hasWildcard = containsWildcard(entry); + QList<Core::LocatorFilterEntry> bestEntries; + const Qt::CaseSensitivity caseSensitivityForPrefix = caseSensitivity(entry); bool hasColonColon = entry.contains(QLatin1String("::")); const IndexItem::ItemType wanted = matchTypes(); + const QRegularExpression regexp = containsWildcard(entry) + ? createWildcardRegExp(entry) : CamelHumpMatcher::createCamelHumpRegExp(entry); + + if (!regexp.isValid()) + return goodEntries; m_data->filterAllFiles([&](const IndexItem::Ptr &info) -> IndexItem::VisitorResult { if (future.isCanceled()) return IndexItem::Break; if (info->type() & wanted) { - const QString matchString = hasColonColon ? info->scopedSymbolName() : info->symbolName(); - int index = hasWildcard ? regexp.indexIn(matchString) : matcher.indexIn(matchString); - if (index >= 0) { - const bool betterMatch = index == 0; + QString matchString = hasColonColon ? info->scopedSymbolName() : info->symbolName(); + QRegularExpressionMatch match = regexp.match(matchString); + if (match.hasMatch()) { Core::LocatorFilterEntry filterEntry = filterEntryFromIndexItem(info); - if (matchString != filterEntry.displayName) { - index = hasWildcard ? regexp.indexIn(filterEntry.displayName) - : matcher.indexIn(filterEntry.displayName); - } - - if (index >= 0) - filterEntry.highlightInfo = {index, (hasWildcard ? regexp.matchedLength() : entry.length())}; - - if (betterMatch) + // Highlight the matched characters, therefore it may be necessary + // to update the match if the displayName is different from matchString + if (matchString != filterEntry.displayName) + match = regexp.match(filterEntry.displayName); + const CamelHumpMatcher::HighlightingPositions positions = + CamelHumpMatcher::highlightingPositions(match); + filterEntry.highlightInfo.starts = positions.starts; + filterEntry.highlightInfo.lengths = positions.lengths; + + if (matchString.startsWith(entry, caseSensitivityForPrefix)) + bestEntries.append(filterEntry); + else if (matchString.contains(entry, caseSensitivityForPrefix)) betterEntries.append(filterEntry); else goodEntries.append(filterEntry); @@ -116,9 +119,12 @@ QList<Core::LocatorFilterEntry> CppLocatorFilter::matchesFor( Utils::sort(goodEntries, Core::LocatorFilterEntry::compareLexigraphically); if (betterEntries.size() < 1000) Utils::sort(betterEntries, Core::LocatorFilterEntry::compareLexigraphically); + if (bestEntries.size() < 1000) + Utils::sort(bestEntries, Core::LocatorFilterEntry::compareLexigraphically); - betterEntries += goodEntries; - return betterEntries; + bestEntries += betterEntries; + bestEntries += goodEntries; + return bestEntries; } void CppLocatorFilter::accept(Core::LocatorFilterEntry selection, diff --git a/src/plugins/cpptools/cpplocatorfilter_test.cpp b/src/plugins/cpptools/cpplocatorfilter_test.cpp index 27258855d80..501a396eb13 100644 --- a/src/plugins/cpptools/cpplocatorfilter_test.cpp +++ b/src/plugins/cpptools/cpplocatorfilter_test.cpp @@ -190,6 +190,16 @@ void CppToolsPlugin::test_cpplocatorfilters_CppLocatorFilter_data() << ResultData(_("myFunction(bool, int)"), _("<anonymous namespace> (file1.cpp)")) ); + QTest::newRow("CppFunctionsFilter-Sorting") + << testFile + << cppFunctionsFilter + << _("pos") + << (QList<ResultData>() + << ResultData(_("positiveNumber()"), testFileShort) + << ResultData(_("getPosition()"), testFileShort) + << ResultData(_("pointOfService()"), testFileShort) + ); + QTest::newRow("CppFunctionsFilter-WithNamespacePrefix") << testFile << cppFunctionsFilter @@ -280,6 +290,9 @@ void CppToolsPlugin::test_cpplocatorfilters_CppCurrentDocumentFilter() QList<ResultData> expectedResults = QList<ResultData>() << ResultData(_("int myVariable"), _("")) << ResultData(_("myFunction(bool, int)"), _("")) + << ResultData(_("pointOfService()"), _("")) + << ResultData(_("getPosition()"), _("")) + << ResultData(_("positiveNumber()"), _("")) << ResultData(_("MyEnum"), _("")) << ResultData(_("int V1"), _("MyEnum")) << ResultData(_("int V2"), _("MyEnum")) diff --git a/src/plugins/qmljstools/qmljsfunctionfilter.cpp b/src/plugins/qmljstools/qmljsfunctionfilter.cpp index 7c00e6c9f02..02081f7e906 100644 --- a/src/plugins/qmljstools/qmljsfunctionfilter.cpp +++ b/src/plugins/qmljstools/qmljsfunctionfilter.cpp @@ -28,9 +28,9 @@ #include <coreplugin/editormanager/editormanager.h> #include <utils/algorithm.h> +#include <utils/camelhumpmatcher.h> -#include <QRegExp> -#include <QStringMatcher> +#include <QRegularExpression> using namespace QmlJSTools::Internal; @@ -59,12 +59,13 @@ QList<Core::LocatorFilterEntry> FunctionFilter::matchesFor( { QList<Core::LocatorFilterEntry> goodEntries; QList<Core::LocatorFilterEntry> betterEntries; - const Qt::CaseSensitivity cs = caseSensitivity(entry); - QStringMatcher matcher(entry, cs); - QRegExp regexp(entry, cs, QRegExp::Wildcard); + QList<Core::LocatorFilterEntry> bestEntries; + const Qt::CaseSensitivity caseSensitivityForPrefix = caseSensitivity(entry); + const QRegularExpression regexp = containsWildcard(entry) + ? createWildcardRegExp(entry) : CamelHumpMatcher::createCamelHumpRegExp(entry); + if (!regexp.isValid()) return goodEntries; - bool hasWildcard = containsWildcard(entry); QHashIterator<QString, QList<LocatorData::Entry> > it(m_data->entries()); while (it.hasNext()) { @@ -78,16 +79,19 @@ QList<Core::LocatorFilterEntry> FunctionFilter::matchesFor( if (info.type != LocatorData::Function) continue; - const int index = hasWildcard ? regexp.indexIn(info.symbolName) - : matcher.indexIn(info.symbolName); - if (index >= 0) { + const QRegularExpressionMatch match = regexp.match(info.symbolName); + if (match.hasMatch()) { QVariant id = qVariantFromValue(info); Core::LocatorFilterEntry filterEntry(this, info.displayName, id/*, info.icon*/); + const CamelHumpMatcher::HighlightingPositions positions = + CamelHumpMatcher::highlightingPositions(match); filterEntry.extraInfo = info.extraInfo; - const int length = hasWildcard ? regexp.matchedLength() : entry.length(); - filterEntry.highlightInfo = {index, length}; + filterEntry.highlightInfo.starts = positions.starts; + filterEntry.highlightInfo.lengths = positions.lengths; - if (index == 0) + if (filterEntry.displayName.startsWith(entry, caseSensitivityForPrefix)) + bestEntries.append(filterEntry); + else if (filterEntry.displayName.contains(entry, caseSensitivityForPrefix)) betterEntries.append(filterEntry); else goodEntries.append(filterEntry); @@ -99,9 +103,12 @@ QList<Core::LocatorFilterEntry> FunctionFilter::matchesFor( Utils::sort(goodEntries, Core::LocatorFilterEntry::compareLexigraphically); if (betterEntries.size() < 1000) Utils::sort(betterEntries, Core::LocatorFilterEntry::compareLexigraphically); + if (bestEntries.size() < 1000) + Utils::sort(bestEntries, Core::LocatorFilterEntry::compareLexigraphically); - betterEntries += goodEntries; - return betterEntries; + bestEntries += betterEntries; + bestEntries += goodEntries; + return bestEntries; } void FunctionFilter::accept(Core::LocatorFilterEntry selection, diff --git a/tests/auto/utils/camelhumpmatcher/tst_camelhumpmatcher.cpp b/tests/auto/utils/camelhumpmatcher/tst_camelhumpmatcher.cpp index b42a46cba71..dea71dbf235 100644 --- a/tests/auto/utils/camelhumpmatcher/tst_camelhumpmatcher.cpp +++ b/tests/auto/utils/camelhumpmatcher/tst_camelhumpmatcher.cpp @@ -35,6 +35,8 @@ class tst_CamelHumpMatcher : public QObject private slots: void camelHumpMatcher(); void camelHumpMatcher_data(); + void highlighting(); + void highlighting_data(); }; void tst_CamelHumpMatcher::camelHumpMatcher() @@ -75,5 +77,64 @@ void tst_CamelHumpMatcher::camelHumpMatcher_data() QTest::newRow("middle-continued") << "cahu" << "LongCamelHump" << 4; } +typedef QVector<int> MatchStart; +typedef QVector<int> MatchLength; + +void tst_CamelHumpMatcher::highlighting() +{ + QFETCH(QString, pattern); + QFETCH(QString, candidate); + QFETCH(MatchStart, matchStart); + QFETCH(MatchLength, matchLength); + + const QRegularExpression regExp = CamelHumpMatcher::createCamelHumpRegExp(pattern); + const QRegularExpressionMatch match = regExp.match(candidate); + const CamelHumpMatcher::HighlightingPositions positions = + CamelHumpMatcher::highlightingPositions(match); + + QCOMPARE(positions.starts.size(), matchStart.size()); + for (int i = 0; i < positions.starts.size(); ++i) { + QCOMPARE(positions.starts.at(i), matchStart.at(i)); + QCOMPARE(positions.lengths.at(i), matchLength.at(i)); + } +} + +void tst_CamelHumpMatcher::highlighting_data() +{ + QTest::addColumn<QString>("pattern"); + QTest::addColumn<QString>("candidate"); + QTest::addColumn<MatchStart>("matchStart"); + QTest::addColumn<MatchLength>("matchLength"); + + QTest::newRow("prefix-snake") << "very" << "very_long_camel_hump" + << MatchStart{0} << MatchLength{4}; + QTest::newRow("middle-snake") << "long" << "very_long_camel_hump" + << MatchStart{5} << MatchLength{4}; + QTest::newRow("suffix-snake") << "hump" << "very_long_camel_hump" + << MatchStart{16} << MatchLength{4}; + QTest::newRow("prefix-camel") << "very" << "VeryLongCamelHump" + << MatchStart{0} << MatchLength{4}; + QTest::newRow("middle-camel") << "Long" << "VeryLongCamelHump" + << MatchStart{4} << MatchLength{4}; + QTest::newRow("suffix-camel") << "Hump" << "VeryLongCamelHump" + << MatchStart{13} << MatchLength{4}; + QTest::newRow("humps-camel") << "vlch" << "VeryLongCamelHump" + << MatchStart{0, 4, 8, 13} << MatchLength{1, 1, 1, 1}; + QTest::newRow("humps-camel-lower") << "vlch" << "veryLongCamelHump" + << MatchStart{0, 4, 8, 13} << MatchLength{1, 1, 1, 1}; + QTest::newRow("humps-snake") << "vlch" << "very_long_camel_hump" + << MatchStart{0, 5, 10, 16} << MatchLength{1, 1, 1, 1}; + QTest::newRow("humps-middle") << "lc" << "VeryLongCamelHump" + << MatchStart{4, 8} << MatchLength{1, 1}; + QTest::newRow("humps-last") << "h" << "VeryLongCamelHump" + << MatchStart{13} << MatchLength{1}; + QTest::newRow("humps-continued") << "LoCa" << "VeryLongCamelHump" + << MatchStart{4, 8} << MatchLength{2, 2}; + QTest::newRow("wildcard-asterisk") << "Lo*Hu" << "VeryLongCamelHump" + << MatchStart{4, 13} << MatchLength{2, 2}; + QTest::newRow("wildcard-question") << "Lo?g" << "VeryLongCamelHump" + << MatchStart{4, 7} << MatchLength{2, 1}; +} + QTEST_APPLESS_MAIN(tst_CamelHumpMatcher) #include "tst_camelhumpmatcher.moc" diff --git a/tests/cpplocators/testdata_basic/file1.cpp b/tests/cpplocators/testdata_basic/file1.cpp index 98a53752377..5c1e3caff32 100644 --- a/tests/cpplocators/testdata_basic/file1.cpp +++ b/tests/cpplocators/testdata_basic/file1.cpp @@ -12,6 +12,10 @@ int myVariable; int myFunction(bool yesno, int number) {} +void pointOfService() {} +int getPosition() { return 0; } +int positiveNumber() { return 2; } + enum MyEnum { V1, V2 }; class MyClass |