mirror of
https://bitbucket.org/smil3y/kde-extraapps.git
synced 2025-02-26 11:52:54 +00:00
153 lines
5.5 KiB
C++
153 lines
5.5 KiB
C++
/* This file is part of KDevelop
|
|
Copyright 2014 Sven Brauch <svenbrauch@gmail.com>
|
|
|
|
This library is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU Library General Public
|
|
License as published by the Free Software Foundation; either
|
|
version 2 of the License, or (at your option) any later version.
|
|
|
|
This library is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
Library General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Library General Public License
|
|
along with this library; see the file COPYING.LIB. If not, write to
|
|
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include "abbreviations.h"
|
|
|
|
bool matchesAbbreviationHelper(const QStringRef &word, const QString &typed, const QVarLengthArray< int, 32 > &offsets, int &depth, int atWord, int i)
|
|
{
|
|
int atLetter = 1;
|
|
for ( ; i < typed.size(); i++ ) {
|
|
const QChar c = typed.at(i).toLower();
|
|
bool haveNextWord = offsets.size() > atWord + 1;
|
|
bool canCompare = atWord != -1 && word.size() > offsets.at(atWord) + atLetter;
|
|
if ( canCompare && c == word.at(offsets.at(atWord) + atLetter).toLower() ) {
|
|
// the typed letter matches a letter after the current word beginning
|
|
if ( ! haveNextWord || c != word.at(offsets.at(atWord + 1)).toLower() ) {
|
|
// good, simple case, no conflict
|
|
atLetter += 1;
|
|
continue;
|
|
}
|
|
// For maliciously crafted data, the code used here theoretically can have very high
|
|
// complexity. Thus ensure we don't run into this case, by limiting the amount of branches
|
|
// we walk through to 128.
|
|
depth++;
|
|
if ( depth > 128 ) {
|
|
return false;
|
|
}
|
|
// the letter matches both the next word beginning and the next character in the word
|
|
if ( haveNextWord && matchesAbbreviationHelper(word, typed, offsets, depth, atWord + 1, i + 1) ) {
|
|
// resolving the conflict by taking the next word's first character worked, fine
|
|
return true;
|
|
}
|
|
// otherwise, continue by taking the next letter in the current word.
|
|
atLetter += 1;
|
|
continue;
|
|
}
|
|
else if ( haveNextWord && c == word.at(offsets.at(atWord + 1)).toLower() ) {
|
|
// the typed letter matches the next word beginning
|
|
atWord++;
|
|
atLetter = 1;
|
|
continue;
|
|
}
|
|
// no match
|
|
return false;
|
|
}
|
|
// all characters of the typed word were matched
|
|
return true;
|
|
}
|
|
|
|
bool matchesAbbreviation(const QStringRef &word, const QString &typed)
|
|
{
|
|
// A mismatch is very likely for random even for the first letter,
|
|
// thus this optimization makes sense.
|
|
if ( word.at(0).toLower() != typed.at(0).toLower() ) {
|
|
return false;
|
|
}
|
|
|
|
// First, check if all letters are contained in the word in the right order.
|
|
int atLetter = 0;
|
|
foreach ( const QChar c, typed ) {
|
|
while ( c.toLower() != word.at(atLetter).toLower() ) {
|
|
atLetter += 1;
|
|
if ( atLetter >= word.size() ) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool haveUnderscore = true;
|
|
QVarLengthArray<int, 32> offsets;
|
|
// We want to make "KComplM" match "KateCompletionModel"; this means we need
|
|
// to allow parts of the typed text to be not part of the actual abbreviation,
|
|
// which consists only of the uppercased / underscored letters (so "KCM" in this case).
|
|
// However it might be ambigous whether a letter is part of such a word or part of
|
|
// the following abbreviation, so we need to find all possible word offsets first,
|
|
// then compare.
|
|
for ( int i = 0; i < word.size(); i++ ) {
|
|
const QChar c = word.at(i);
|
|
if ( c == QLatin1Char('_') || c == QLatin1Char('-') ) {
|
|
haveUnderscore = true;
|
|
} else if ( haveUnderscore || c.isUpper() ) {
|
|
offsets.append(i);
|
|
haveUnderscore = false;
|
|
}
|
|
}
|
|
int depth = 0;
|
|
return matchesAbbreviationHelper(word, typed, offsets, depth);
|
|
}
|
|
|
|
bool matchesPath(const QString &path, const QString &typed)
|
|
{
|
|
int consumed = 0;
|
|
int pos = 0;
|
|
// try to find all the characters in typed in the right order in the path;
|
|
// jumps are allowed everywhere
|
|
while ( consumed < typed.size() && pos < path.size() ) {
|
|
if ( typed.at(consumed).toLower() == path.at(pos).toLower() ) {
|
|
consumed++;
|
|
}
|
|
pos++;
|
|
}
|
|
return consumed == typed.size();
|
|
}
|
|
|
|
bool matchesAbbreviationMulti(const QString &word, const QStringList &typedFragments)
|
|
{
|
|
if ( word.size() == 0 ) {
|
|
return true;
|
|
}
|
|
int lastSpace = 0;
|
|
int matchedFragments = 0;
|
|
for ( int i = 0; i < word.size(); i++ ) {
|
|
const QChar& c = word.at(i);
|
|
bool isDoubleColon = false;
|
|
// if it's not a separation char, walk over it.
|
|
if ( c != ' ' && c != '/' && i != word.size() - 1 ) {
|
|
if ( c != ':' && i < word.size()-1 && word.at(i+1) != ':' ) {
|
|
continue;
|
|
}
|
|
isDoubleColon = true;
|
|
i++;
|
|
}
|
|
// if it's '/', ' ' or '::', split the word here and check the next sub-word.
|
|
const QStringRef wordFragment = word.midRef(lastSpace, i-lastSpace);
|
|
const QString& typedFragment = typedFragments.at(matchedFragments);
|
|
Q_ASSERT(!typedFragment.isEmpty());
|
|
if ( !wordFragment.isEmpty() && matchesAbbreviation(wordFragment, typedFragment) ) {
|
|
matchedFragments += 1;
|
|
if ( matchedFragments == typedFragments.size() ) {
|
|
break;
|
|
}
|
|
}
|
|
lastSpace = isDoubleColon ? i : i+1;
|
|
}
|
|
return matchedFragments == typedFragments.size();
|
|
}
|
|
|
|
// kate: space-indent on; indent-width 2
|