/************************************************************************** ** ** 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 "fuzzymatcher.h" #include #include /** * \brief Creates a regexp that represents a specified wildcard and camel hump pattern, * underscores are not included. * * \param pattern the camel hump pattern * \param caseSensitivity case sensitivity used for camel hump matching * \return the regexp */ QRegularExpression FuzzyMatcher::createRegExp( const QString &pattern, FuzzyMatcher::CaseSensitivity caseSensitivity) { if (pattern.isEmpty()) return QRegularExpression(); /* * 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; QString plainRegExp; bool first = true; const QChar asterisk = '*'; const QChar question = '?'; const QLatin1String uppercaseWordFirst("(?<=\\b|[a-z0-9_])"); const QLatin1String lowercaseWordFirst("(?<=\\b|[A-Z0-9_])"); const QLatin1String uppercaseWordContinuation("[a-z0-9_]*"); const QLatin1String lowercaseWordContinuation("(?:[a-zA-Z0-9]*_)?"); const QLatin1String upperSnakeWordContinuation("[A-Z0-9]*_?"); keyRegExp += "(?:"; for (const QChar &c : pattern) { if (!c.isLetterOrNumber()) { if (c == question) { keyRegExp += '.'; plainRegExp += ").("; } else if (c == asterisk) { keyRegExp += ".*"; plainRegExp += ").*("; } else { const QString escaped = QRegularExpression::escape(c); keyRegExp += '(' + escaped + ')'; plainRegExp += escaped; } } else if (caseSensitivity == CaseSensitivity::CaseInsensitive || (caseSensitivity == CaseSensitivity::FirstLetterCaseSensitive && !first)) { const QString upper = QRegularExpression::escape(c.toUpper()); const QString lower = QRegularExpression::escape(c.toLower()); keyRegExp += "(?:"; keyRegExp += first ? uppercaseWordFirst : uppercaseWordContinuation; keyRegExp += '(' + upper + ')'; if (first) { keyRegExp += '|' + lowercaseWordFirst + '(' + lower + ')'; } else { keyRegExp += '|' + lowercaseWordContinuation + '(' + lower + ')'; keyRegExp += '|' + upperSnakeWordContinuation + '(' + upper + ')'; } keyRegExp += ')'; plainRegExp += '[' + upper + lower + ']'; } else { if (!first) { if (c.isUpper()) keyRegExp += uppercaseWordContinuation; else keyRegExp += lowercaseWordContinuation; } const QString escaped = QRegularExpression::escape(c); keyRegExp += escaped; plainRegExp += escaped; } first = false; } keyRegExp += ')'; return QRegularExpression('(' + plainRegExp + ")|" + 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. */ FuzzyMatcher::HighlightingPositions FuzzyMatcher::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; }