Generalized the filtering of completion items.

This commit is contained in:
Roberto Raggi
2010-01-26 11:44:45 +01:00
parent 478907e5c3
commit 1ba889a1bf
8 changed files with 134 additions and 133 deletions

View File

@@ -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);

View File

@@ -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;
};

View File

@@ -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)) {
filter(m_completions, completions, key, m_caseSensitivity);
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, 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;

View File

@@ -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);

View File

@@ -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()

View File

@@ -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);

View File

@@ -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);
}
}
}

View File

@@ -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);