From 348057994366ac3a8fb8181061f507f5fb9ff00e Mon Sep 17 00:00:00 2001 From: Andre Hartmann Date: Wed, 26 Jul 2017 14:08:26 +0200 Subject: [PATCH] Extract CamelHumpMatcher for use in the C++/QML locator filters Started-by: David Kaspar Change-Id: I928ccdc4084f8d07826746c771e7fca6994f95ab Reviewed-by: Orgad Shaneh --- src/libs/utils/camelhumpmatcher.cpp | 106 ++++++++++++++++++ src/libs/utils/camelhumpmatcher.h | 48 ++++++++ src/libs/utils/utils-lib.pri | 6 +- src/libs/utils/utils.qbs | 2 + .../codeassist/genericproposalmodel.cpp | 68 +++-------- .../codeassist/genericproposalmodel.h | 5 +- .../camelhumpmatcher/camelhumpmatcher.pro | 6 + .../camelhumpmatcher/camelhumpmatcher.qbs | 8 ++ .../camelhumpmatcher/tst_camelhumpmatcher.cpp | 73 ++++++++++++ tests/auto/utils/utils.pro | 1 + 10 files changed, 268 insertions(+), 55 deletions(-) create mode 100644 src/libs/utils/camelhumpmatcher.cpp create mode 100644 src/libs/utils/camelhumpmatcher.h create mode 100644 tests/auto/utils/camelhumpmatcher/camelhumpmatcher.pro create mode 100644 tests/auto/utils/camelhumpmatcher/camelhumpmatcher.qbs create mode 100644 tests/auto/utils/camelhumpmatcher/tst_camelhumpmatcher.cpp diff --git a/src/libs/utils/camelhumpmatcher.cpp b/src/libs/utils/camelhumpmatcher.cpp new file mode 100644 index 00000000000..fca366765c1 --- /dev/null +++ b/src/libs/utils/camelhumpmatcher.cpp @@ -0,0 +1,106 @@ +/************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2017 BlackBerry Limited +** Copyright (C) 2017 Andre Hartmann +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "camelhumpmatcher.h" + +#include +#include + +/** + * \brief Creates a regexp that represents a specified camel hump pattern, + * underscores are not included. + * + * \param pattern the camel hump pattern + * \param caseSensitivity case sensitivity used for camel hump matching; + * does not affect wildcard style matching + * \return the regexp + */ +QRegExp CamelHumpMatcher::createCamelHumpRegExp(const QString &pattern, + CamelHumpMatcher::CaseSensitivity caseSensitivity) +{ + if (pattern.isEmpty()) + return QRegExp(); + + /* + * This code builds a regular expression in order to more intelligently match + * camel-case and underscore names. + * + * For any but the first letter, the following replacements are made: + * A => [a-z0-9_]*A + * a => (?:[a-zA-Z0-9]*_)?a + * + * That means any sequence of lower-case or underscore characters can preceed an + * upper-case character. And any sequence of lower-case or upper case characters - + * followed by an underscore can preceed a lower-case character. + * + * Examples: (case sensitive mode) + * gAC matches getActionController + * gac matches get_action_controller + * + * It also implements the fully and first-letter-only case sensitivity. + */ + + QString keyRegExp; + bool first = true; + const QChar asterisk = QLatin1Char('*'); + const QChar question = QLatin1Char('?'); + const QLatin1String uppercaseWordContinuation("[a-z0-9_]*"); + const QLatin1String lowercaseWordContinuation("(?:[a-zA-Z0-9]*_)?"); + foreach (const QChar &c, pattern) { + if (!c.isLetter()) { + if (c == question) + keyRegExp += QLatin1Char('.'); + else if (c == asterisk) + keyRegExp += QLatin1String(".*"); + else + keyRegExp += QRegExp::escape(c); + } else if (caseSensitivity == CaseSensitivity::CaseInsensitive || + (caseSensitivity == CaseSensitivity::FirstLetterCaseSensitive && !first)) { + + keyRegExp += QLatin1String("(?:"); + if (!first) + keyRegExp += uppercaseWordContinuation; + keyRegExp += QRegExp::escape(c.toUpper()); + keyRegExp += QLatin1Char('|'); + if (!first) + keyRegExp += lowercaseWordContinuation; + keyRegExp += QRegExp::escape(c.toLower()); + keyRegExp += QLatin1Char(')'); + } else { + if (!first) { + if (c.isUpper()) + keyRegExp += uppercaseWordContinuation; + else + keyRegExp += lowercaseWordContinuation; + } + keyRegExp += QRegExp::escape(c); + } + + first = false; + } + return QRegExp(keyRegExp); +} diff --git a/src/libs/utils/camelhumpmatcher.h b/src/libs/utils/camelhumpmatcher.h new file mode 100644 index 00000000000..0bfd1f217f2 --- /dev/null +++ b/src/libs/utils/camelhumpmatcher.h @@ -0,0 +1,48 @@ +/************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2017 BlackBerry Limited +** Copyright (C) 2017 Andre Hartmann +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "utils_global.h" + +QT_BEGIN_NAMESPACE +class QRegExp; +class QString; +QT_END_NAMESPACE + +class QTCREATOR_UTILS_EXPORT CamelHumpMatcher +{ +public: + enum class CaseSensitivity { + CaseInsensitive, + CaseSensitive, + FirstLetterCaseSensitive + }; + + static QRegExp createCamelHumpRegExp(const QString &pattern, + CaseSensitivity caseSensitivity = CaseSensitivity::CaseInsensitive); +}; diff --git a/src/libs/utils/utils-lib.pri b/src/libs/utils/utils-lib.pri index 578e0a8d5e4..6143ce7d400 100644 --- a/src/libs/utils/utils-lib.pri +++ b/src/libs/utils/utils-lib.pri @@ -114,7 +114,8 @@ SOURCES += $$PWD/environment.cpp \ $$PWD/runextensions.cpp \ $$PWD/utilsicons.cpp \ $$PWD/guard.cpp \ - $$PWD/highlightingitemdelegate.cpp + $$PWD/highlightingitemdelegate.cpp \ + $$PWD/camelhumpmatcher.cpp win32:SOURCES += $$PWD/consoleprocess_win.cpp else:SOURCES += $$PWD/consoleprocess_unix.cpp @@ -242,7 +243,8 @@ HEADERS += \ $$PWD/optional.h \ $$PWD/../3rdparty/optional/optional.hpp \ $$PWD/qtcfallthrough.h \ - $$PWD/highlightingitemdelegate.cpp + $$PWD/highlightingitemdelegate.cpp \ + $$PWD/camelhumpmatcher.h FORMS += $$PWD/filewizardpage.ui \ $$PWD/projectintropage.ui \ diff --git a/src/libs/utils/utils.qbs b/src/libs/utils/utils.qbs index 0c0562acaf7..c6e7f7c0395 100644 --- a/src/libs/utils/utils.qbs +++ b/src/libs/utils/utils.qbs @@ -52,6 +52,8 @@ Project { "bracematcher.h", "buildablehelperlibrary.cpp", "buildablehelperlibrary.h", + "camelhumpmatcher.cpp", + "camelhumpmatcher.h", "categorysortfiltermodel.cpp", "categorysortfiltermodel.h", "changeset.cpp", diff --git a/src/plugins/texteditor/codeassist/genericproposalmodel.cpp b/src/plugins/texteditor/codeassist/genericproposalmodel.cpp index 34a51a4ba13..b04db2163b8 100644 --- a/src/plugins/texteditor/codeassist/genericproposalmodel.cpp +++ b/src/plugins/texteditor/codeassist/genericproposalmodel.cpp @@ -255,58 +255,9 @@ void GenericProposalModel::filter(const QString &prefix) if (prefix.isEmpty()) return; - /* - * This code builds a regular expression in order to more intelligently match - * camel-case and underscore names. - * - * For any but the first letter, the following replacements are made: - * A => [a-z0-9_]*A - * a => (?:[a-zA-Z0-9]*_)?a - * - * That means any sequence of lower-case or underscore characters can preceed an - * upper-case character. And any sequence of lower-case or upper case characters - - * followed by an underscore can preceed a lower-case character. - * - * Examples: (case sensitive mode) - * gAC matches getActionController - * gac matches get_action_controller - * - * It also implements the fully and first-letter-only case sensitivity. - */ - const CaseSensitivity caseSensitivity = - TextEditorSettings::completionSettings().m_caseSensitivity; - - QString keyRegExp; - keyRegExp += QLatin1Char('^'); - bool first = true; - const QLatin1String uppercaseWordContinuation("[a-z0-9_]*"); - const QLatin1String lowercaseWordContinuation("(?:[a-zA-Z0-9]*_)?"); - foreach (const QChar &c, prefix) { - if (caseSensitivity == CaseInsensitive || - (caseSensitivity == FirstLetterCaseSensitive && !first)) { - - keyRegExp += QLatin1String("(?:"); - if (!first) - keyRegExp += uppercaseWordContinuation; - keyRegExp += QRegExp::escape(c.toUpper()); - keyRegExp += QLatin1Char('|'); - if (!first) - keyRegExp += lowercaseWordContinuation; - keyRegExp += QRegExp::escape(c.toLower()); - keyRegExp += QLatin1Char(')'); - } else { - if (!first) { - if (c.isUpper()) - keyRegExp += uppercaseWordContinuation; - else - keyRegExp += lowercaseWordContinuation; - } - keyRegExp += QRegExp::escape(c); - } - - first = false; - } - QRegExp regExp(keyRegExp); + const CamelHumpMatcher::CaseSensitivity caseSensitivity = + convertCaseSensitivity(TextEditorSettings::completionSettings().m_caseSensitivity); + const QRegExp regExp = CamelHumpMatcher::createCamelHumpRegExp(prefix, caseSensitivity); m_currentItems.clear(); const QString lowerPrefix = prefix.toLower(); @@ -328,6 +279,19 @@ void GenericProposalModel::filter(const QString &prefix) } } +CamelHumpMatcher::CaseSensitivity + GenericProposalModel::convertCaseSensitivity(TextEditor::CaseSensitivity textEditorCaseSensitivity) +{ + switch (textEditorCaseSensitivity) { + case TextEditor::CaseSensitive: + return CamelHumpMatcher::CaseSensitivity::CaseSensitive; + case TextEditor::FirstLetterCaseSensitive: + return CamelHumpMatcher::CaseSensitivity::FirstLetterCaseSensitive; + default: + return CamelHumpMatcher::CaseSensitivity::CaseInsensitive; + } +} + bool GenericProposalModel::isSortable(const QString &prefix) const { Q_UNUSED(prefix); diff --git a/src/plugins/texteditor/codeassist/genericproposalmodel.h b/src/plugins/texteditor/codeassist/genericproposalmodel.h index 32cfd008a80..1a9fd7df572 100644 --- a/src/plugins/texteditor/codeassist/genericproposalmodel.h +++ b/src/plugins/texteditor/codeassist/genericproposalmodel.h @@ -28,8 +28,9 @@ #include "iassistproposalmodel.h" #include "assistenums.h" +#include #include - +#include #include #include @@ -71,6 +72,8 @@ public: bool isPrefiltered(const QString &prefix) const; void setPrefilterPrefix(const QString &prefix); + CamelHumpMatcher::CaseSensitivity convertCaseSensitivity(TextEditor::CaseSensitivity textEditorCaseSensitivity); + protected: QList m_currentItems; diff --git a/tests/auto/utils/camelhumpmatcher/camelhumpmatcher.pro b/tests/auto/utils/camelhumpmatcher/camelhumpmatcher.pro new file mode 100644 index 00000000000..74469073f6c --- /dev/null +++ b/tests/auto/utils/camelhumpmatcher/camelhumpmatcher.pro @@ -0,0 +1,6 @@ +QTC_LIB_DEPENDS += utils +include(../../qttest.pri) + +DEFINES += QTCREATOR_UTILS_LIB + +SOURCES += tst_camelhumpmatcher.cpp diff --git a/tests/auto/utils/camelhumpmatcher/camelhumpmatcher.qbs b/tests/auto/utils/camelhumpmatcher/camelhumpmatcher.qbs new file mode 100644 index 00000000000..2ecb08dd988 --- /dev/null +++ b/tests/auto/utils/camelhumpmatcher/camelhumpmatcher.qbs @@ -0,0 +1,8 @@ +import qbs +import "../../autotest.qbs" as Autotest + +Autotest { + name: "CamelHumpMatcher autotest" + Depends { name: "Utils" } + files: "tst_camelhumpmatcher.cpp" +} diff --git a/tests/auto/utils/camelhumpmatcher/tst_camelhumpmatcher.cpp b/tests/auto/utils/camelhumpmatcher/tst_camelhumpmatcher.cpp new file mode 100644 index 00000000000..677d4fad3cf --- /dev/null +++ b/tests/auto/utils/camelhumpmatcher/tst_camelhumpmatcher.cpp @@ -0,0 +1,73 @@ +/************************************************************************** +** +** Copyright (C) 2017 BlackBerry Limited +** Copyright (C) 2017 Andre Hartmann +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include + +#include + +class tst_CamelHumpMatcher : public QObject +{ + Q_OBJECT + +private slots: + void camelHumpMatcher(); + void camelHumpMatcher_data(); +}; + +void tst_CamelHumpMatcher::camelHumpMatcher() +{ + QFETCH(QString, pattern); + QFETCH(QString, candidate); + QFETCH(int, expectedIndex); + + QCOMPARE(CamelHumpMatcher::createCamelHumpRegExp(pattern).indexIn(candidate), expectedIndex); +} + +void tst_CamelHumpMatcher::camelHumpMatcher_data() +{ + QTest::addColumn("pattern"); + QTest::addColumn("candidate"); + QTest::addColumn("expectedIndex"); + + QTest::newRow("underscore") << "vl" << "very_long_camel_hump" << 0; + QTest::newRow("exact") << "VeryLongCamelHump" << "VeryLongCamelHump" << 0; + QTest::newRow("prefix-segments") << "velo" << "very_long_Camel_hump" << 0; + QTest::newRow("humps") << "vlc" << "VeryLongCamelHump" << 0; + QTest::newRow("case-insensitive-humps") << "VlCh" << "VeryLongCamelHump" << 0; + QTest::newRow("incorrect-hump") << "vxc" << "VeryLongCamelHump" << -1; + QTest::newRow("skipped-hump") << "vc" << "VeryLongCamelHump" << -1; + QTest::newRow("middle-humps") << "lc" << "VeryLongCamelHump" << 4; + QTest::newRow("incorrect-hump") << "lyn" << "VeryLongCamelHump" << -1; + QTest::newRow("humps") << "VL" << "VeryLongCamelHump" << 0; + QTest::newRow("skipped-humps-upper") << "VH" << "VeryLongCamelHump" << -1; + QTest::newRow("question-wildcard") << "Lon?Ca" << "VeryLongCamelHump" << 4; + QTest::newRow("unmatched-question-wildcard") << "Long?Ca" << "VeryLongCamelHump" << -1; + QTest::newRow("asterix-wildcard") << "Long*Ca" << "VeryLongCamelHump" << 4; + QTest::newRow("empty-asterix-wildcard") << "Lo*Ca" << "VeryLongCamelHump" << 4; +} + +QTEST_APPLESS_MAIN(tst_CamelHumpMatcher) +#include "tst_camelhumpmatcher.moc" diff --git a/tests/auto/utils/utils.pro b/tests/auto/utils/utils.pro index f5d9e94b008..b5b44098a53 100644 --- a/tests/auto/utils/utils.pro +++ b/tests/auto/utils/utils.pro @@ -3,6 +3,7 @@ TEMPLATE = subdirs SUBDIRS = \ fileutils \ ansiescapecodehandler \ + camelhumpmatcher \ objectpool \ stringutils \ templateengine \