diff --git a/autotests/src/completion_test.cpp b/autotests/src/completion_test.cpp --- a/autotests/src/completion_test.cpp +++ b/autotests/src/completion_test.cpp @@ -370,45 +370,50 @@ void CompletionTest::testAbbreviationEngine() { - QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("FooBar"), QStringLiteral("fb"))); - QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("FooBar"), QStringLiteral("foob"))); - QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("FooBar"), QStringLiteral("fbar"))); - QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("FooBar"), QStringLiteral("fba"))); - QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("FooBar"), QStringLiteral("foba"))); - QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("FooBarBazBang"), QStringLiteral("fbbb"))); - QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("foo_bar_cat"), QStringLiteral("fbc"))); - QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("foo_bar_cat"), QStringLiteral("fb"))); - QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("FooBarArr"), QStringLiteral("fba"))); - QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("FooBarArr"), QStringLiteral("fbara"))); - QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("FooBarArr"), QStringLiteral("fobaar"))); - QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("FooBarArr"), QStringLiteral("fb"))); - - QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("QualifiedIdentifier"), QStringLiteral("qid"))); - QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("QualifiedIdentifier"), QStringLiteral("qualid"))); - QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("QualifiedIdentifier"), QStringLiteral("qualidentifier"))); - QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("QualifiedIdentifier"), QStringLiteral("qi"))); - QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("KateCompletionModel"), QStringLiteral("kcmodel"))); - QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("KateCompletionModel"), QStringLiteral("kc"))); - QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("KateCompletionModel"), QStringLiteral("kcomplmodel"))); - QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("KateCompletionModel"), QStringLiteral("kacomplmodel"))); - QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("KateCompletionModel"), QStringLiteral("kacom"))); - - QVERIFY(! KateCompletionModel::matchesAbbreviation(QStringLiteral("QualifiedIdentifier"), QStringLiteral("identifier"))); - QVERIFY(! KateCompletionModel::matchesAbbreviation(QStringLiteral("FooBarArr"), QStringLiteral("fobaara"))); - QVERIFY(! KateCompletionModel::matchesAbbreviation(QStringLiteral("FooBarArr"), QStringLiteral("fbac"))); - QVERIFY(! KateCompletionModel::matchesAbbreviation(QStringLiteral("KateCompletionModel"), QStringLiteral("kamodel"))); - - QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("AbcdefBcdefCdefDefEfFzZ"), QStringLiteral("AbcdefBcdefCdefDefEfFzZ"))); - QVERIFY(! KateCompletionModel::matchesAbbreviation(QStringLiteral("AbcdefBcdefCdefDefEfFzZ"), QStringLiteral("ABCDEFX"))); - QVERIFY(! KateCompletionModel::matchesAbbreviation(QStringLiteral("AaaaaaBbbbbCcccDddEeFzZ"), QStringLiteral("XZYBFA"))); + QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("FooBar"), QStringLiteral("fb"), Qt::CaseInsensitive)); + QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("FooBar"), QStringLiteral("foob"), Qt::CaseInsensitive)); + QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("FooBar"), QStringLiteral("fbar"), Qt::CaseInsensitive)); + QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("FooBar"), QStringLiteral("fba"), Qt::CaseInsensitive)); + QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("FooBar"), QStringLiteral("foba"), Qt::CaseInsensitive)); + QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("FooBarBazBang"), QStringLiteral("fbbb"), Qt::CaseInsensitive)); + QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("foo_bar_cat"), QStringLiteral("fbc"), Qt::CaseInsensitive)); + QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("foo_bar_cat"), QStringLiteral("fb"), Qt::CaseInsensitive)); + QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("FooBarArr"), QStringLiteral("fba"), Qt::CaseInsensitive)); + QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("FooBarArr"), QStringLiteral("fbara"), Qt::CaseInsensitive)); + QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("FooBarArr"), QStringLiteral("fobaar"), Qt::CaseInsensitive)); + QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("FooBarArr"), QStringLiteral("fb"), Qt::CaseInsensitive)); + + QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("QualifiedIdentifier"), QStringLiteral("qid"), Qt::CaseInsensitive)); + QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("QualifiedIdentifier"), QStringLiteral("qualid"), Qt::CaseInsensitive)); + QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("QualifiedIdentifier"), QStringLiteral("qualidentifier"), Qt::CaseInsensitive)); + QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("QualifiedIdentifier"), QStringLiteral("qi"), Qt::CaseInsensitive)); + QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("KateCompletionModel"), QStringLiteral("kcmodel"), Qt::CaseInsensitive)); + QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("KateCompletionModel"), QStringLiteral("kc"), Qt::CaseInsensitive)); + QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("KateCompletionModel"), QStringLiteral("kcomplmodel"), Qt::CaseInsensitive)); + QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("KateCompletionModel"), QStringLiteral("kacomplmodel"), Qt::CaseInsensitive)); + QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("KateCompletionModel"), QStringLiteral("kacom"), Qt::CaseInsensitive)); + + QVERIFY(! KateCompletionModel::matchesAbbreviation(QStringLiteral("QualifiedIdentifier"), QStringLiteral("identifier"), Qt::CaseInsensitive)); + QVERIFY(! KateCompletionModel::matchesAbbreviation(QStringLiteral("FooBarArr"), QStringLiteral("fobaara"), Qt::CaseInsensitive)); + QVERIFY(! KateCompletionModel::matchesAbbreviation(QStringLiteral("FooBarArr"), QStringLiteral("fbac"), Qt::CaseInsensitive)); + QVERIFY(! KateCompletionModel::matchesAbbreviation(QStringLiteral("KateCompletionModel"), QStringLiteral("kamodel"), Qt::CaseInsensitive)); + + QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("AbcdefBcdefCdefDefEfFzZ"), QStringLiteral("AbcdefBcdefCdefDefEfFzZ"), Qt::CaseInsensitive)); + QVERIFY(! KateCompletionModel::matchesAbbreviation(QStringLiteral("AbcdefBcdefCdefDefEfFzZ"), QStringLiteral("ABCDEFX"), Qt::CaseInsensitive)); + QVERIFY(! KateCompletionModel::matchesAbbreviation(QStringLiteral("AaaaaaBbbbbCcccDddEeFzZ"), QStringLiteral("XZYBFA"), Qt::CaseInsensitive)); + + QVERIFY(! KateCompletionModel::matchesAbbreviation(QStringLiteral("FooBar"), QStringLiteral("fb"), Qt::CaseSensitive)); + QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("FooBar"), QStringLiteral("FB"), Qt::CaseSensitive)); + QVERIFY(! KateCompletionModel::matchesAbbreviation(QStringLiteral("KateCompletionModel"), QStringLiteral("kcmodel"), Qt::CaseSensitive)); + QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("KateCompletionModel"), QStringLiteral("KCModel"), Qt::CaseSensitive)); } void CompletionTest::benchAbbreviationEngineGoodCase() { QBENCHMARK { for (int i = 0; i < 10000; i++) { - QVERIFY(! KateCompletionModel::matchesAbbreviation(QStringLiteral("AaaaaaBbbbbCcccDddEeFzZ"), QStringLiteral("XZYBFA"))); + QVERIFY(! KateCompletionModel::matchesAbbreviation(QStringLiteral("AaaaaaBbbbbCcccDddEeFzZ"), QStringLiteral("XZYBFA"), Qt::CaseInsensitive)); } } } @@ -418,7 +423,7 @@ QBENCHMARK { for (int i = 0; i < 10000; i++) { - QVERIFY(! KateCompletionModel::matchesAbbreviation(QStringLiteral("AaaaaaBbbbbCcccDddEeFzZ"), QStringLiteral("ABCDEFX"))); + QVERIFY(! KateCompletionModel::matchesAbbreviation(QStringLiteral("AaaaaaBbbbbCcccDddEeFzZ"), QStringLiteral("ABCDEFX"), Qt::CaseInsensitive)); } } } @@ -430,7 +435,7 @@ { // This case is quite horrible, because it requires a branch at every letter. // The current code will at some point drop out and just return false. - KateCompletionModel::matchesAbbreviation(QStringLiteral("XxBbbbbbBbbbbbBbbbbBbbbBbbbbbbBbbbbbBbbbbbBbbbFox"), QStringLiteral("XbbbbbbbbbbbbbbbbbbbbFx")); + KateCompletionModel::matchesAbbreviation(QStringLiteral("XxBbbbbbBbbbbbBbbbbBbbbBbbbbbbBbbbbbBbbbbbBbbbFox"), QStringLiteral("XbbbbbbbbbbbbbbbbbbbbFx"), Qt::CaseInsensitive); } } } diff --git a/src/completion/katecompletionmodel.h b/src/completion/katecompletionmodel.h --- a/src/completion/katecompletionmodel.h +++ b/src/completion/katecompletionmodel.h @@ -67,7 +67,9 @@ void setCurrentCompletion(KTextEditor::CodeCompletionModel *model, const QString &completion); Qt::CaseSensitivity matchCaseSensitivity() const; - void setMatchCaseSensitivity(Qt::CaseSensitivity cs); + Qt::CaseSensitivity exactMatchCaseSensitivity() const; + void setMatchCaseSensitivity(Qt::CaseSensitivity match_cs); + void setMatchCaseSensitivity(Qt::CaseSensitivity match_cs, Qt::CaseSensitivity exact_match_cs); static QString columnName(int column); int translateColumn(int sourceColumn) const; @@ -359,15 +361,14 @@ void resort(); void refilter(); - static bool matchesAbbreviation(const QString &word, const QString &typed); + static bool matchesAbbreviation(const QString &word, const QString &typed, Qt::CaseSensitivity caseSensitive); bool m_hasGroups = false; // ### Runtime state // General QList m_completionModels; QMap m_currentMatch; - Qt::CaseSensitivity m_matchCaseSensitivity = Qt::CaseInsensitive; // Column merging QList< QList > m_columnMerges; @@ -387,6 +388,10 @@ QHash m_customGroupHash; // ### Configurable state + // Matching + Qt::CaseSensitivity m_matchCaseSensitivity = Qt::CaseInsensitive; + Qt::CaseSensitivity m_exactMatchCaseSensitivity = Qt::CaseInsensitive; // Must be equal to or stricter than m_matchCaseSensitivity. + // Sorting bool m_sortingEnabled = false; bool m_sortingAlphabetical = false; diff --git a/src/completion/katecompletionmodel.cpp b/src/completion/katecompletionmodel.cpp --- a/src/completion/katecompletionmodel.cpp +++ b/src/completion/katecompletionmodel.cpp @@ -370,9 +370,17 @@ return widget()->view(); } -void KateCompletionModel::setMatchCaseSensitivity(Qt::CaseSensitivity cs) +void KateCompletionModel::setMatchCaseSensitivity(Qt::CaseSensitivity match_cs) { - m_matchCaseSensitivity = cs; + m_matchCaseSensitivity = match_cs; + Q_ASSERT(m_exactMatchCaseSensitivity == m_matchCaseSensitivity || m_matchCaseSensitivity == Qt::CaseInsensitive); +} + +void KateCompletionModel::setMatchCaseSensitivity(Qt::CaseSensitivity match_cs, Qt::CaseSensitivity exact_match_cs) +{ + m_matchCaseSensitivity = match_cs; + m_exactMatchCaseSensitivity = exact_match_cs; + Q_ASSERT(m_exactMatchCaseSensitivity == m_matchCaseSensitivity || m_matchCaseSensitivity == Qt::CaseInsensitive); } int KateCompletionModel::columnCount(const QModelIndex &) const @@ -1562,6 +1570,19 @@ return false; } + if (ret == 0) { + const QString& filter = rhs.model->currentCompletion(rhs.m_sourceRow.first); + bool thisStartWithFilter = m_nameColumn.startsWith(filter, Qt::CaseSensitive); + bool rhsStartsWithFilter = rhs.m_nameColumn.startsWith(filter, Qt::CaseSensitive); + + if( thisStartWithFilter && !rhsStartsWithFilter ) { + return true; + } + if( rhsStartsWithFilter && !thisStartWithFilter ) { + return false; + } + } + if (model->isSortingByInheritanceDepth()) { ret = inheritanceDepth - rhs.inheritanceDepth; } @@ -1572,14 +1593,6 @@ } if (ret == 0) { - const QString& filter = rhs.model->currentCompletion(rhs.m_sourceRow.first); - if( m_nameColumn.startsWith(filter, Qt::CaseSensitive) ) { - return true; - } - if( rhs.m_nameColumn.startsWith(filter, Qt::CaseSensitive) ) { - return false; - } - // FIXME need to define a better default ordering for multiple model display ret = m_sourceRow.second.row() - rhs.m_sourceRow.second.row(); } @@ -1873,17 +1886,22 @@ return doHide; } +static inline QChar toLowerIfInsensitive(QChar c, Qt::CaseSensitivity caseSensitive) +{ + return (caseSensitive == Qt::CaseInsensitive) ? c.toLower() : c; +} + static inline bool matchesAbbreviationHelper(const QString &word, const QString &typed, const QVarLengthArray &offsets, - int &depth, int atWord = -1, int i = 0) + Qt::CaseSensitivity caseSensitive, int &depth, int atWord = -1, int i = 0) { int atLetter = 1; for (; i < typed.size(); i++) { - const QChar c = typed.at(i).toLower(); + const QChar c = toLowerIfInsensitive(typed.at(i), caseSensitive); 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()) { + if (canCompare && c == toLowerIfInsensitive(word.at(offsets.at(atWord) + atLetter), caseSensitive)) { // the typed letter matches a letter after the current word beginning - if (! haveNextWord || c != word.at(offsets.at(atWord + 1)).toLower()) { + if (! haveNextWord || c != toLowerIfInsensitive(word.at(offsets.at(atWord + 1)), caseSensitive)) { // good, simple case, no conflict atLetter += 1; continue; @@ -1896,14 +1914,14 @@ 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)) { + if (haveNextWord && matchesAbbreviationHelper(word, typed, offsets, caseSensitive, 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()) { + } else if (haveNextWord && c == toLowerIfInsensitive(word.at(offsets.at(atWord + 1)), caseSensitive)) { // the typed letter matches the next word beginning atWord++; atLetter = 1; @@ -1916,18 +1934,18 @@ return true; } -bool KateCompletionModel::matchesAbbreviation(const QString &word, const QString &typed) +bool KateCompletionModel::matchesAbbreviation(const QString &word, const QString &typed, Qt::CaseSensitivity caseSensitive) { // 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()) { + if (toLowerIfInsensitive(word.at(0), caseSensitive) != toLowerIfInsensitive(typed.at(0), caseSensitive)) { 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()) { + while (toLowerIfInsensitive(c, caseSensitive) != toLowerIfInsensitive(word.at(atLetter), caseSensitive)) { atLetter += 1; if (atLetter >= word.size()) { return false; @@ -1953,7 +1971,7 @@ } } int depth = 0; - return matchesAbbreviationHelper(word, typed, offsets, depth); + return matchesAbbreviationHelper(word, typed, offsets, caseSensitive, depth); } static inline bool containsAtWordBeginning(const QString &word, const QString &typed, Qt::CaseSensitivity caseSensitive) @@ -2001,12 +2019,17 @@ if (matchCompletion == NoMatch && !m_nameColumn.isEmpty() && !match.isEmpty()) { // if still no match, try abbreviation matching - if (matchesAbbreviation(m_nameColumn, match)) { + if (matchesAbbreviation(m_nameColumn, match, model->matchCaseSensitivity())) { matchCompletion = AbbreviationMatch; } } if (matchCompletion && match.length() == m_nameColumn.length()) { + if (model->matchCaseSensitivity() == Qt::CaseInsensitive && + model->exactMatchCaseSensitivity() == Qt::CaseSensitive && + !m_nameColumn.startsWith(match, Qt::CaseSensitive)) { + return matchCompletion; + } matchCompletion = PerfectMatch; m_haveExactMatch = true; } @@ -2118,6 +2141,11 @@ return m_matchCaseSensitivity; } +Qt::CaseSensitivity KateCompletionModel::exactMatchCaseSensitivity() const +{ + return m_exactMatchCaseSensitivity; +} + void KateCompletionModel::addCompletionModel(KTextEditor::CodeCompletionModel *model) { if (m_completionModels.contains(model)) {