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