From 1ba889a1bf354c34f806d8b33cda0262d0cd6145 Mon Sep 17 00:00:00 2001 From: Roberto Raggi <roberto.raggi@nokia.com> Date: Tue, 26 Jan 2010 11:44:45 +0100 Subject: [PATCH] Generalized the filtering of completion items. --- src/plugins/cppeditor/cppquickfix.cpp | 9 +- src/plugins/cppeditor/cppquickfix.h | 3 + src/plugins/cpptools/cppcodecompletion.cpp | 79 +++--------------- src/plugins/cpptools/cppcodecompletion.h | 8 +- src/plugins/qmljseditor/qmlcodecompletion.cpp | 64 ++------------ src/plugins/qmljseditor/qmlcodecompletion.h | 2 + .../texteditor/icompletioncollector.cpp | 83 +++++++++++++++++++ src/plugins/texteditor/icompletioncollector.h | 19 ++++- 8 files changed, 134 insertions(+), 133 deletions(-) diff --git a/src/plugins/cppeditor/cppquickfix.cpp b/src/plugins/cppeditor/cppquickfix.cpp index 4be8afe773c..c2989afd113 100644 --- a/src/plugins/cppeditor/cppquickfix.cpp +++ b/src/plugins/cppeditor/cppquickfix.cpp @@ -1332,12 +1332,18 @@ const QList<LookupItem> QuickFixOperation::typeOf(CPlusPlus::ExpressionAST *ast) } CPPQuickFixCollector::CPPQuickFixCollector() - : _modelManager(CppTools::CppModelManagerInterface::instance()), _editor(0) + : _modelManager(CppTools::CppModelManagerInterface::instance()), _editable(0), _editor(0) { } CPPQuickFixCollector::~CPPQuickFixCollector() { } +TextEditor::ITextEditable *CPPQuickFixCollector::editor() const +{ return _editable; } + +int CPPQuickFixCollector::startPosition() const +{ return _editable->position(); } + bool CPPQuickFixCollector::supportsEditor(TextEditor::ITextEditable *editor) { return qobject_cast<CPPEditorEditable *>(editor) != 0; } @@ -1348,6 +1354,7 @@ int CPPQuickFixCollector::startCompletion(TextEditor::ITextEditable *editable) { Q_ASSERT(editable != 0); + _editable = editable; _editor = qobject_cast<CPPEditor *>(editable->widget()); Q_ASSERT(_editor != 0); diff --git a/src/plugins/cppeditor/cppquickfix.h b/src/plugins/cppeditor/cppquickfix.h index 77fcb20cfbf..47de6bcb88d 100644 --- a/src/plugins/cppeditor/cppquickfix.h +++ b/src/plugins/cppeditor/cppquickfix.h @@ -151,6 +151,8 @@ public: QList<QuickFixOperationPtr> quickFixes() const { return _quickFixes; } + virtual TextEditor::ITextEditable *editor() const; + virtual int startPosition() const; virtual bool supportsEditor(TextEditor::ITextEditable *editor); virtual bool triggersCompletion(TextEditor::ITextEditable *editor); virtual int startCompletion(TextEditor::ITextEditable *editor); @@ -163,6 +165,7 @@ public Q_SLOTS: private: CppTools::CppModelManagerInterface *_modelManager; + TextEditor::ITextEditable *_editable; CPPEditor *_editor; QList<QuickFixOperationPtr> _quickFixes; }; diff --git a/src/plugins/cpptools/cppcodecompletion.cpp b/src/plugins/cpptools/cppcodecompletion.cpp index 64843200ecf..87bc14ff494 100644 --- a/src/plugins/cpptools/cppcodecompletion.cpp +++ b/src/plugins/cpptools/cppcodecompletion.cpp @@ -690,6 +690,12 @@ static int startOfOperator(TextEditor::ITextEditable *editor, bool CppCodeCompletion::supportsEditor(TextEditor::ITextEditable *editor) { return m_manager->isCppEditor(editor); } +TextEditor::ITextEditable *CppCodeCompletion::editor() const +{ return m_editor; } + +int CppCodeCompletion::startPosition() const +{ return m_startPosition; } + bool CppCodeCompletion::triggersCompletion(TextEditor::ITextEditable *editor) { const int pos = editor->position(); @@ -1466,12 +1472,11 @@ bool CppCodeCompletion::completeQtMethod(const QList<LookupItem> &results, void CppCodeCompletion::completions(QList<TextEditor::CompletionItem> *completions) { const int length = m_editor->position() - m_startPosition; + const QString key = m_editor->textAt(m_startPosition, length); if (length == 0) *completions = m_completions; else if (length > 0) { - const QString key = m_editor->textAt(m_startPosition, length); - /* Close on the trailing slash for include completion, to enable the slash to * trigger a new completion list. */ if ((m_completionOperator == T_STRING_LITERAL || @@ -1479,62 +1484,14 @@ void CppCodeCompletion::completions(QList<TextEditor::CompletionItem> *completio return; if (m_completionOperator != T_LPAREN) { - /* - * This code builds a regular expression in order to more intelligently match - * camel-case style. This means upper-case characters will be rewritten as follows: - * - * A => [a-z0-9_]*A (for any but the first capital letter) - * - * Meaning it allows any sequence of lower-case characters to preceed an - * upper-case character. So for example gAC matches getActionController. - * - * It also implements the first-letter-only case sensitivity. - */ - QString keyRegExp; - keyRegExp += QLatin1Char('^'); - bool first = true; - const QLatin1String wordContinuation("[a-z0-9_]*"); - foreach (const QChar &c, key) { - if (m_caseSensitivity == CaseInsensitive || - (m_caseSensitivity == FirstLetterCaseSensitive && !first)) { - - keyRegExp += QLatin1String("(?:"); - if (c.isUpper() && !first) - keyRegExp += wordContinuation; - keyRegExp += QRegExp::escape(c.toUpper()); - keyRegExp += "|"; - keyRegExp += QRegExp::escape(c.toLower()); - keyRegExp += QLatin1Char(')'); - } else { - if (c.isUpper() && !first) - keyRegExp += wordContinuation; - keyRegExp += QRegExp::escape(c); - } + filter(m_completions, completions, key, m_caseSensitivity); - first = false; - } - const QRegExp regExp(keyRegExp); - - const bool hasKey = !key.isEmpty(); - foreach (TextEditor::CompletionItem item, m_completions) { - if (regExp.indexIn(item.text) == 0) { - if (hasKey) { - if (item.text.startsWith(key, Qt::CaseSensitive)) { - item.relevance = 2; - } else if (m_caseSensitivity != CaseSensitive - && item.text.startsWith(key, Qt::CaseInsensitive)) { - item.relevance = 1; - } - } - (*completions) << item; - } - } } else if (m_completionOperator == T_LPAREN || m_completionOperator == T_SIGNAL || m_completionOperator == T_SLOT) { foreach (TextEditor::CompletionItem item, m_completions) { if (item.text.startsWith(key, Qt::CaseInsensitive)) { - (*completions) << item; + completions->append(item); } } } @@ -1681,23 +1638,7 @@ bool CppCodeCompletion::partiallyComplete(const QList<TextEditor::CompletionItem complete(completionItems.first()); return true; } else if (m_partialCompletionEnabled && m_completionOperator != T_LPAREN) { - // Compute common prefix - QString firstKey = completionItems.first().text; - QString lastKey = completionItems.last().text; - const int length = qMin(firstKey.length(), lastKey.length()); - firstKey.truncate(length); - lastKey.truncate(length); - - while (firstKey != lastKey) { - firstKey.chop(1); - lastKey.chop(1); - } - - int typedLength = m_editor->position() - m_startPosition; - if (!firstKey.isEmpty() && firstKey.length() > typedLength) { - m_editor->setCurPos(m_startPosition); - m_editor->replace(typedLength, firstKey); - } + return TextEditor::ICompletionCollector::partiallyComplete(completionItems); } return false; diff --git a/src/plugins/cpptools/cppcodecompletion.h b/src/plugins/cpptools/cppcodecompletion.h index f15ddf8f103..d4160ee3c1d 100644 --- a/src/plugins/cpptools/cppcodecompletion.h +++ b/src/plugins/cpptools/cppcodecompletion.h @@ -65,6 +65,8 @@ public: void setObjcEnabled(bool objcEnabled) { m_objcEnabled = objcEnabled; } + TextEditor::ITextEditable *editor() const; + int startPosition() const; QList<TextEditor::CompletionItem> getCompletions(); bool supportsEditor(TextEditor::ITextEditable *editor); bool triggersCompletion(TextEditor::ITextEditable *editor); @@ -77,12 +79,6 @@ public: QIcon iconForSymbol(CPlusPlus::Symbol *symbol) const; - enum CaseSensitivity { - CaseInsensitive, - CaseSensitive, - FirstLetterCaseSensitive - }; - CaseSensitivity caseSensitivity() const; void setCaseSensitivity(CaseSensitivity caseSensitivity); diff --git a/src/plugins/qmljseditor/qmlcodecompletion.cpp b/src/plugins/qmljseditor/qmlcodecompletion.cpp index 851b86eb55a..a33c92e5c5b 100644 --- a/src/plugins/qmljseditor/qmlcodecompletion.cpp +++ b/src/plugins/qmljseditor/qmlcodecompletion.cpp @@ -715,6 +715,12 @@ Qt::CaseSensitivity QmlCodeCompletion::caseSensitivity() const void QmlCodeCompletion::setCaseSensitivity(Qt::CaseSensitivity caseSensitivity) { m_caseSensitivity = caseSensitivity; } +TextEditor::ITextEditable *QmlCodeCompletion::editor() const +{ return m_editor; } + +int QmlCodeCompletion::startPosition() const +{ return m_startPosition; } + bool QmlCodeCompletion::supportsEditor(TextEditor::ITextEditable *editor) { if (qobject_cast<QmlJSTextEditor *>(editor->widget())) @@ -1007,8 +1013,6 @@ int QmlCodeCompletion::startCompletion(TextEditor::ITextEditable *editor) void QmlCodeCompletion::completions(QList<TextEditor::CompletionItem> *completions) { - // ### FIXME: this code needs to be generalized. - const int length = m_editor->position() - m_startPosition; if (length == 0) @@ -1016,41 +1020,7 @@ void QmlCodeCompletion::completions(QList<TextEditor::CompletionItem> *completio else if (length > 0) { const QString key = m_editor->textAt(m_startPosition, length); - /* - * This code builds a regular expression in order to more intelligently match - * camel-case style. This means upper-case characters will be rewritten as follows: - * - * A => [a-z0-9_]*A (for any but the first capital letter) - * - * Meaning it allows any sequence of lower-case characters to preceed an - * upper-case character. So for example gAC matches getActionController. - */ - QString keyRegExp; - keyRegExp += QLatin1Char('^'); - bool first = true; - foreach (const QChar &c, key) { - if (c.isUpper() && !first) { - keyRegExp += QLatin1String("[a-z0-9_]*"); - keyRegExp += c; - } else if (m_caseSensitivity == Qt::CaseInsensitive && c.isLower()) { - keyRegExp += QLatin1Char('['); - keyRegExp += c; - keyRegExp += c.toUpper(); - keyRegExp += QLatin1Char(']'); - } else { - keyRegExp += QRegExp::escape(c); - } - first = false; - } - const QRegExp regExp(keyRegExp, Qt::CaseSensitive); - - foreach (TextEditor::CompletionItem item, m_completions) { - if (regExp.indexIn(item.text) == 0) { - item.relevance = (key.length() > 0 && - item.text.startsWith(key, Qt::CaseInsensitive)) ? 1 : 0; - (*completions) << item; - } - } + filter(m_completions, completions, key, FirstLetterCaseSensitive); } } @@ -1089,25 +1059,7 @@ bool QmlCodeCompletion::partiallyComplete(const QList<TextEditor::CompletionItem } } - // Compute common prefix - QString firstKey = completionItems.first().text; - QString lastKey = completionItems.last().text; - const int length = qMin(firstKey.length(), lastKey.length()); - firstKey.truncate(length); - lastKey.truncate(length); - - while (firstKey != lastKey) { - firstKey.chop(1); - lastKey.chop(1); - } - - int typedLength = m_editor->position() - m_startPosition; - if (!firstKey.isEmpty() && firstKey.length() > typedLength) { - m_editor->setCurPos(m_startPosition); - m_editor->replace(typedLength, firstKey); - } - - return false; + return TextEditor::ICompletionCollector::partiallyComplete(completionItems); } void QmlCodeCompletion::cleanup() diff --git a/src/plugins/qmljseditor/qmlcodecompletion.h b/src/plugins/qmljseditor/qmlcodecompletion.h index efa00f89477..b56e246b030 100644 --- a/src/plugins/qmljseditor/qmlcodecompletion.h +++ b/src/plugins/qmljseditor/qmlcodecompletion.h @@ -59,6 +59,8 @@ public: Qt::CaseSensitivity caseSensitivity() const; void setCaseSensitivity(Qt::CaseSensitivity caseSensitivity); + virtual TextEditor::ITextEditable *editor() const; + virtual int startPosition() const; virtual bool supportsEditor(TextEditor::ITextEditable *editor); virtual bool triggersCompletion(TextEditor::ITextEditable *editor); virtual int startCompletion(TextEditor::ITextEditable *editor); diff --git a/src/plugins/texteditor/icompletioncollector.cpp b/src/plugins/texteditor/icompletioncollector.cpp index aaf68699069..cc369031036 100644 --- a/src/plugins/texteditor/icompletioncollector.cpp +++ b/src/plugins/texteditor/icompletioncollector.cpp @@ -28,6 +28,8 @@ **************************************************************************/ #include "icompletioncollector.h" +#include "itexteditable.h" +#include <QtCore/QRegExp> #include <algorithm> using namespace TextEditor; @@ -84,3 +86,84 @@ QList<CompletionItem> ICompletionCollector::getCompletions() return uniquelist; } +bool ICompletionCollector::partiallyComplete(const QList<TextEditor::CompletionItem> &completionItems) +{ + // Compute common prefix + QString firstKey = completionItems.first().text; + QString lastKey = completionItems.last().text; + const int length = qMin(firstKey.length(), lastKey.length()); + firstKey.truncate(length); + lastKey.truncate(length); + + while (firstKey != lastKey) { + firstKey.chop(1); + lastKey.chop(1); + } + + if (ITextEditable *ed = editor()) { + const int typedLength = ed->position() - startPosition(); + if (!firstKey.isEmpty() && firstKey.length() > typedLength) { + ed->setCurPos(startPosition()); + ed->replace(typedLength, firstKey); + } + } + + return false; +} + +void ICompletionCollector::filter(const QList<TextEditor::CompletionItem> &items, + QList<TextEditor::CompletionItem> *filteredItems, + const QString &key, + ICompletionCollector::CaseSensitivity caseSensitivity) +{ + /* + * This code builds a regular expression in order to more intelligently match + * camel-case style. This means upper-case characters will be rewritten as follows: + * + * A => [a-z0-9_]*A (for any but the first capital letter) + * + * Meaning it allows any sequence of lower-case characters to preceed an + * upper-case character. So for example gAC matches getActionController. + * + * It also implements the first-letter-only case sensitivity. + */ + QString keyRegExp; + keyRegExp += QLatin1Char('^'); + bool first = true; + const QLatin1String wordContinuation("[a-z0-9_]*"); + foreach (const QChar &c, key) { + if (caseSensitivity == CaseInsensitive || + (caseSensitivity == FirstLetterCaseSensitive && !first)) { + + keyRegExp += QLatin1String("(?:"); + if (c.isUpper() && !first) + keyRegExp += wordContinuation; + keyRegExp += QRegExp::escape(c.toUpper()); + keyRegExp += "|"; + keyRegExp += QRegExp::escape(c.toLower()); + keyRegExp += QLatin1Char(')'); + } else { + if (c.isUpper() && !first) + keyRegExp += wordContinuation; + keyRegExp += QRegExp::escape(c); + } + + first = false; + } + const QRegExp regExp(keyRegExp); + + const bool hasKey = !key.isEmpty(); + foreach (TextEditor::CompletionItem item, items) { + if (regExp.indexIn(item.text) == 0) { + if (hasKey) { + if (item.text.startsWith(key, Qt::CaseSensitive)) { + item.relevance = 2; + } else if (caseSensitivity != CaseSensitive + && item.text.startsWith(key, Qt::CaseInsensitive)) { + item.relevance = 1; + } + } + filteredItems->append(item); + } + } +} diff --git a/src/plugins/texteditor/icompletioncollector.h b/src/plugins/texteditor/icompletioncollector.h index 57941952225..ad2ea7fa2e6 100644 --- a/src/plugins/texteditor/icompletioncollector.h +++ b/src/plugins/texteditor/icompletioncollector.h @@ -78,6 +78,10 @@ public: virtual QList<CompletionItem> getCompletions(); + /* Returns the current active ITextEditable */ + virtual ITextEditable *editor() const = 0; + virtual int startPosition() const = 0; + /* * Returns true if this completion collector can be used with the given editor. */ @@ -107,12 +111,25 @@ public: * * Returns whether the completion popup should be closed. */ - virtual bool partiallyComplete(const QList<TextEditor::CompletionItem> &completionItems) = 0; + virtual bool partiallyComplete(const QList<TextEditor::CompletionItem> &completionItems); /* Called when it's safe to clean up the completion items. */ virtual void cleanup() = 0; + // helpers + + enum CaseSensitivity { + CaseInsensitive, + CaseSensitive, + FirstLetterCaseSensitive + }; + + void filter(const QList<TextEditor::CompletionItem> &items, + QList<TextEditor::CompletionItem> *filteredItems, + const QString &key, + CaseSensitivity caseSensitivity); + protected: static bool compareChar(const QChar &item, const QChar &other); static bool lessThan(const QString &item, const QString &other); -- GitLab