Commit 9fb5b0be authored by Nikolai Kosjar's avatar Nikolai Kosjar
Browse files

CppTools: Add basic completion support for qt5 style signals/slots



Trigger completion for Qt5 signals/slots as soon as the user types '&'
in

    connect(object, &
    connect(object, &Foo:signal, object2, &

Change-Id: I338a26415196959e3dc413bdfd023314812f3aaa
Reviewed-by: default avatarErik Verbruggen <erik.verbruggen@theqtcompany.com>
parent 6546a292
...@@ -283,7 +283,8 @@ enum Kind { ...@@ -283,7 +283,8 @@ enum Kind {
T___VOLATILE = T_VOLATILE, T___VOLATILE = T_VOLATILE,
T___VOLATILE__ = T_VOLATILE, T___VOLATILE__ = T_VOLATILE,
T___ATTRIBUTE = T___ATTRIBUTE__ T___ATTRIBUTE = T___ATTRIBUTE__,
T_LAST_TOKEN
}; };
class CPLUSPLUS_EXPORT Token class CPLUSPLUS_EXPORT Token
......
...@@ -2168,6 +2168,94 @@ void CppToolsPlugin::test_completion_data() ...@@ -2168,6 +2168,94 @@ void CppToolsPlugin::test_completion_data()
<< QLatin1String("Foo") << QLatin1String("Foo")
<< QLatin1String("bar")); << QLatin1String("bar"));
const QByteArray commonSignalSlotCompletionTestCode =
"#define SIGNAL(a) #a\n"
"#define SLOT(a) #a\n"
"#define signals public\n"
"#define slots\n"
"#define Q_OBJECT virtual const QMetaObject *metaObject() const;"
"\n"
"class Base : public QObject\n"
"{\n"
" Q_OBJECT\n"
"public:\n"
" void hiddenFunction();\n"
" void baseFunction();\n"
"signals:\n"
" void hiddenSignal();\n"
" void baseSignal1();\n"
" void baseSignal2(int newValue);\n"
"public slots:\n"
" void baseSlot1();\n"
" void baseSlot2(int newValue);\n"
"};\n"
"\n"
"class Derived : public Base\n"
"{\n"
" Q_OBJECT\n"
"public:\n"
" void hiddenFunction();\n"
" void derivedFunction();\n"
"signals:\n"
" void hiddenSignal();\n"
" void derivedSignal1();\n"
" void derivedSignal2(int newValue);\n"
"public slots:\n"
" void derivedSlot1();\n"
" void derivedSlot2(int newValue);\n"
"};\n"
"\n"
"void client()\n"
"{\n"
" Derived *myObject = new Derived;\n"
" @\n"
"}\n";
QTest::newRow("SIGNAL(")
<< commonSignalSlotCompletionTestCode
<< _("connect(myObject, SIGNAL(") << (QStringList()
<< QLatin1String("baseSignal1()")
<< QLatin1String("baseSignal2(int)")
<< QLatin1String("hiddenSignal()")
<< QLatin1String("derivedSignal1()")
<< QLatin1String("derivedSignal2(int)"));
QTest::newRow("SLOT(")
<< commonSignalSlotCompletionTestCode
<< _("connect(myObject, SIGNAL(baseSignal1()), myObject, SLOT(") << (QStringList()
<< QLatin1String("baseSlot1()")
<< QLatin1String("baseSlot2(int)")
<< QLatin1String("derivedSlot1()")
<< QLatin1String("derivedSlot2(int)"));
QTest::newRow("Qt5 signal")
<< commonSignalSlotCompletionTestCode
<< _("connect(myObject, &") << (QStringList()
<< QLatin1String("Base::baseSignal1")
<< QLatin1String("Base::baseSignal2")
<< QLatin1String("Base::hiddenSignal")
<< QLatin1String("Derived::derivedSignal1")
<< QLatin1String("Derived::derivedSignal2")
<< QLatin1String("Derived::hiddenSignal")); // OK, hidden signal
QTest::newRow("Qt5 slot")
<< commonSignalSlotCompletionTestCode
<< _("connect(myObject, &MyObject::timeout, myObject, &") << (QStringList()
<< QLatin1String("Base::baseSignal1")
<< QLatin1String("Base::baseSignal2")
<< QLatin1String("Base::baseSlot1")
<< QLatin1String("Base::baseSlot2")
<< QLatin1String("Base::baseFunction")
<< QLatin1String("Base::hiddenFunction")
<< QLatin1String("Base::hiddenSignal")
<< QLatin1String("Derived::derivedFunction")
<< QLatin1String("Derived::derivedSignal1")
<< QLatin1String("Derived::derivedSignal2")
<< QLatin1String("Derived::derivedSlot1")
<< QLatin1String("Derived::derivedSlot2")
<< QLatin1String("Derived::hiddenFunction")
<< QLatin1String("Derived::hiddenSignal"));
QTest::newRow("signals_hide_QPrivateSignal") << _( QTest::newRow("signals_hide_QPrivateSignal") << _(
"#define SIGNAL(a) #a\n" "#define SIGNAL(a) #a\n"
"#define SLOT(a) #a\n" "#define SLOT(a) #a\n"
......
...@@ -596,6 +596,34 @@ bool isQPrivateSignal(const Symbol *symbol) ...@@ -596,6 +596,34 @@ bool isQPrivateSignal(const Symbol *symbol)
return false; return false;
} }
QString createQt4SignalOrSlot(CPlusPlus::Function *function, const Overview &overview)
{
QString signature;
signature += Overview().prettyName(function->name());
signature += QLatin1Char('(');
for (unsigned i = 0, to = function->argumentCount(); i < to; ++i) {
Symbol *arg = function->argumentAt(i);
if (isQPrivateSignal(arg))
continue;
if (i != 0)
signature += QLatin1Char(',');
signature += overview.prettyType(arg->type());
}
signature += QLatin1Char(')');
const QByteArray normalized = QMetaObject::normalizedSignature(signature.toUtf8());
return QString::fromUtf8(normalized, normalized.size());
}
QString createQt5SignalOrSlot(CPlusPlus::Function *function, Class *klass)
{
QString text;
text += Overview().prettyName(klass->name());
text += QLatin1String("::");
text += Overview().prettyName(function->name());
return text;
}
} // Anonymous } // Anonymous
// ------------------------------------ // ------------------------------------
...@@ -745,7 +773,8 @@ int InternalCppCompletionAssistProcessor::startOfOperator(int pos, ...@@ -745,7 +773,8 @@ int InternalCppCompletionAssistProcessor::startOfOperator(int pos,
const QChar ch2 = pos > 0 ? m_interface->characterAt(pos - 2) : QChar(); const QChar ch2 = pos > 0 ? m_interface->characterAt(pos - 2) : QChar();
const QChar ch3 = pos > 1 ? m_interface->characterAt(pos - 3) : QChar(); const QChar ch3 = pos > 1 ? m_interface->characterAt(pos - 3) : QChar();
int start = pos - CppCompletionAssistProvider::activationSequenceChar(ch, ch2, ch3, kind, wantFunctionCall); int start = pos - CppCompletionAssistProvider::activationSequenceChar(ch, ch2, ch3, kind,
wantFunctionCall, /*wantQt5SignalSlots*/ true);
if (start != pos) { if (start != pos) {
QTextCursor tc(m_interface->textDocument()); QTextCursor tc(m_interface->textDocument());
tc.setPosition(pos); tc.setPosition(pos);
...@@ -776,7 +805,13 @@ int InternalCppCompletionAssistProcessor::startOfOperator(int pos, ...@@ -776,7 +805,13 @@ int InternalCppCompletionAssistProcessor::startOfOperator(int pos,
const int tokenIdx = SimpleLexer::tokenBefore(tokens, qMax(0, tc.positionInBlock() - 1)); // get the token at the left of the cursor const int tokenIdx = SimpleLexer::tokenBefore(tokens, qMax(0, tc.positionInBlock() - 1)); // get the token at the left of the cursor
const Token tk = (tokenIdx == -1) ? Token() : tokens.at(tokenIdx); const Token tk = (tokenIdx == -1) ? Token() : tokens.at(tokenIdx);
if (*kind == T_DOXY_COMMENT && !(tk.is(T_DOXY_COMMENT) || tk.is(T_CPP_DOXY_COMMENT))) { if (*kind == T_AMPER && tokenIdx > 0) {
const Token &previousToken = tokens.at(tokenIdx - 1);
if (previousToken.kind() == T_COMMA) {
start = pos - (tk.utf16charOffset - previousToken.utf16charOffset) - 1;
QTC_CHECK(m_interface->characterAt(start) == QLatin1Char(','));
}
} else if (*kind == T_DOXY_COMMENT && !(tk.is(T_DOXY_COMMENT) || tk.is(T_CPP_DOXY_COMMENT))) {
*kind = T_EOF_SYMBOL; *kind = T_EOF_SYMBOL;
start = pos; start = pos;
} }
...@@ -836,7 +871,8 @@ int InternalCppCompletionAssistProcessor::startOfOperator(int pos, ...@@ -836,7 +871,8 @@ int InternalCppCompletionAssistProcessor::startOfOperator(int pos,
const QChar ch4 = start > -1 ? m_interface->characterAt(start - 1) : QChar(); const QChar ch4 = start > -1 ? m_interface->characterAt(start - 1) : QChar();
const QChar ch5 = start > 0 ? m_interface->characterAt(start - 2) : QChar(); const QChar ch5 = start > 0 ? m_interface->characterAt(start - 2) : QChar();
const QChar ch6 = start > 1 ? m_interface->characterAt(start - 3) : QChar(); const QChar ch6 = start > 1 ? m_interface->characterAt(start - 3) : QChar();
start = start - CppCompletionAssistProvider::activationSequenceChar(ch4, ch5, ch6, kind, wantFunctionCall); start = start - CppCompletionAssistProvider::activationSequenceChar(
ch4, ch5, ch6, kind, wantFunctionCall, false);
} }
} }
} }
...@@ -859,6 +895,18 @@ int InternalCppCompletionAssistProcessor::findStartOfName(int pos) const ...@@ -859,6 +895,18 @@ int InternalCppCompletionAssistProcessor::findStartOfName(int pos) const
return pos + 1; return pos + 1;
} }
static bool isPrecededByConnectAndOpenParenthesis(
const CppCompletionAssistInterface *assistInterface,
int startOfExpression)
{
QTC_ASSERT(startOfExpression >= 0, return false);
int beforeExpression = startOfExpression;
while (beforeExpression > 0 && assistInterface->characterAt(--beforeExpression).isSpace()) ;
const int pos = beforeExpression - 7;
return pos >= 0 && assistInterface->textAt(pos, 7) == QLatin1String("connect");
}
int InternalCppCompletionAssistProcessor::startCompletionHelper() int InternalCppCompletionAssistProcessor::startCompletionHelper()
{ {
if (m_languageFeatures.objCEnabled) { if (m_languageFeatures.objCEnabled) {
...@@ -929,7 +977,12 @@ int InternalCppCompletionAssistProcessor::startCompletionHelper() ...@@ -929,7 +977,12 @@ int InternalCppCompletionAssistProcessor::startCompletionHelper()
expression = expressionUnderCursor(tc); expression = expressionUnderCursor(tc);
startOfExpression = endOfExpression - expression.length(); startOfExpression = endOfExpression - expression.length();
if (m_model->m_completionOperator == T_LPAREN) { if (m_model->m_completionOperator == T_AMPER) {
m_model->m_completionOperator
= isPrecededByConnectAndOpenParenthesis(m_interface.data(), startOfExpression)
? CompleteQt5SignalTrigger
: CompleteQtSlotTrigger;
} else if (m_model->m_completionOperator == T_LPAREN) {
if (expression.endsWith(QLatin1String("SIGNAL"))) { if (expression.endsWith(QLatin1String("SIGNAL"))) {
m_model->m_completionOperator = T_SIGNAL; m_model->m_completionOperator = T_SIGNAL;
} else if (expression.endsWith(QLatin1String("SLOT"))) { } else if (expression.endsWith(QLatin1String("SLOT"))) {
...@@ -1276,12 +1329,22 @@ int InternalCppCompletionAssistProcessor::startCompletionInternal(const QString ...@@ -1276,12 +1329,22 @@ int InternalCppCompletionAssistProcessor::startCompletionInternal(const QString
break; break;
case T_SIGNAL: case T_SIGNAL:
if (completeSignal(results)) if (completeQtMethod(results, CompleteQt4Signals))
return m_startPosition; return m_startPosition;
break; break;
case T_SLOT: case T_SLOT:
if (completeSlot(results)) if (completeQtMethod(results, CompleteQt4Slots))
return m_startPosition;
break;
case CompleteQt5SignalTrigger:
if (completeQtMethod(results, CompleteQt5Signals))
return m_startPosition;
break;
case CompleteQtSlotTrigger:
if (completeQtMethod(results, CompleteQt5Slots))
return m_startPosition; return m_startPosition;
break; break;
...@@ -1581,9 +1644,8 @@ void InternalCppCompletionAssistProcessor::addClassMembersToCompletion(Scope *sc ...@@ -1581,9 +1644,8 @@ void InternalCppCompletionAssistProcessor::addClassMembersToCompletion(Scope *sc
addClassMembersToCompletion(*cit, staticLookup); addClassMembersToCompletion(*cit, staticLookup);
} }
bool InternalCppCompletionAssistProcessor::completeQtMethod( bool InternalCppCompletionAssistProcessor::completeQtMethod(const QList<LookupItem> &results,
const QList<LookupItem> &results, CompleteQtMethodMode type)
bool wantSignals)
{ {
if (results.isEmpty()) if (results.isEmpty())
return false; return false;
...@@ -1630,46 +1692,35 @@ bool InternalCppCompletionAssistProcessor::completeQtMethod( ...@@ -1630,46 +1692,35 @@ bool InternalCppCompletionAssistProcessor::completeQtMethod(
} }
} }
const bool wantSignals = type == CompleteQt4Signals || type == CompleteQt5Signals;
const bool wantQt5SignalOrSlot = type == CompleteQt5Signals || type == CompleteQt5Slots;
foreach (Scope *scope, scopes) { foreach (Scope *scope, scopes) {
if (!scope->isClass()) Class *klass = scope->asClass();
if (!klass)
continue; continue;
for (unsigned i = 0; i < scope->memberCount(); ++i) { for (unsigned i = 0; i < scope->memberCount(); ++i) {
Symbol *member = scope->memberAt(i); Symbol *member = scope->memberAt(i);
Function *fun = member->type()->asFunctionType(); Function *fun = member->type()->asFunctionType();
if (!fun) if (!fun || fun->isGenerated())
continue; continue;
if (wantSignals && !fun->isSignal()) if (wantSignals && !fun->isSignal())
continue; continue;
else if (!wantSignals && !fun->isSlot()) else if (!wantSignals && type == CompleteQt4Slots && !fun->isSlot())
continue; continue;
unsigned count = fun->argumentCount(); unsigned count = fun->argumentCount();
while (true) { while (true) {
QString signature; const QString completionText = wantQt5SignalOrSlot
signature += Overview().prettyName(fun->name()); ? createQt5SignalOrSlot(fun, klass)
signature += QLatin1Char('('); : createQt4SignalOrSlot(fun, o);
for (unsigned i = 0; i < count; ++i) {
Symbol *arg = fun->argumentAt(i);
if (isQPrivateSignal(arg))
continue;
if (i != 0)
signature += QLatin1Char(',');
signature += o.prettyType(arg->type());
}
signature += QLatin1Char(')');
const QByteArray normalized =
QMetaObject::normalizedSignature(signature.toUtf8());
signature = QString::fromUtf8(normalized, normalized.size());
if (!signatures.contains(signature)) { if (!signatures.contains(completionText)) {
AssistProposalItem *ci = toCompletionItem(fun); AssistProposalItem *ci = toCompletionItem(fun);
if (!ci) if (!ci)
break; break;
signatures.insert(signature); signatures.insert(completionText);
ci->setText(signature); // fix the completion item. ci->setText(completionText); // fix the completion item.
m_completions.append(ci); m_completions.append(ci);
} }
......
...@@ -132,11 +132,13 @@ private: ...@@ -132,11 +132,13 @@ private:
void completeNamespace(CPlusPlus::ClassOrNamespace *binding); void completeNamespace(CPlusPlus::ClassOrNamespace *binding);
void completeClass(CPlusPlus::ClassOrNamespace *b, bool staticLookup = true); void completeClass(CPlusPlus::ClassOrNamespace *b, bool staticLookup = true);
void addClassMembersToCompletion(CPlusPlus::Scope *scope, bool staticLookup); void addClassMembersToCompletion(CPlusPlus::Scope *scope, bool staticLookup);
bool completeQtMethod(const QList<CPlusPlus::LookupItem> &results, bool wantSignals); enum CompleteQtMethodMode {
bool completeSignal(const QList<CPlusPlus::LookupItem> &results) CompleteQt4Signals,
{ return completeQtMethod(results, true); } CompleteQt4Slots,
bool completeSlot(const QList<CPlusPlus::LookupItem> &results) CompleteQt5Signals,
{ return completeQtMethod(results, false); } CompleteQt5Slots,
};
bool completeQtMethod(const QList<CPlusPlus::LookupItem> &results, CompleteQtMethodMode type);
void globalCompletion(CPlusPlus::Scope *scope); void globalCompletion(CPlusPlus::Scope *scope);
void addCompletionItem(const QString &text, void addCompletionItem(const QString &text,
...@@ -152,6 +154,10 @@ private: ...@@ -152,6 +154,10 @@ private:
QSet<QString> *processed, QSet<QString> *processed,
QSet<QString> *definedMacros); QSet<QString> *definedMacros);
enum {
CompleteQt5SignalTrigger = CPlusPlus::T_LAST_TOKEN + 1,
CompleteQtSlotTrigger
};
CPlusPlus::LanguageFeatures m_languageFeatures; CPlusPlus::LanguageFeatures m_languageFeatures;
QScopedPointer<const CppCompletionAssistInterface> m_interface; QScopedPointer<const CppCompletionAssistInterface> m_interface;
QScopedPointer<CppAssistProposalModel> m_model; QScopedPointer<CppAssistProposalModel> m_model;
......
...@@ -57,7 +57,7 @@ bool CppCompletionAssistProvider::isActivationCharSequence(const QString &sequen ...@@ -57,7 +57,7 @@ bool CppCompletionAssistProvider::isActivationCharSequence(const QString &sequen
const QChar &ch = sequence.at(2); const QChar &ch = sequence.at(2);
const QChar &ch2 = sequence.at(1); const QChar &ch2 = sequence.at(1);
const QChar &ch3 = sequence.at(0); const QChar &ch3 = sequence.at(0);
if (activationSequenceChar(ch, ch2, ch3, 0, true) != 0) if (activationSequenceChar(ch, ch2, ch3, 0, true, false) != 0)
return true; return true;
return false; return false;
} }
...@@ -71,7 +71,8 @@ int CppCompletionAssistProvider::activationSequenceChar(const QChar &ch, ...@@ -71,7 +71,8 @@ int CppCompletionAssistProvider::activationSequenceChar(const QChar &ch,
const QChar &ch2, const QChar &ch2,
const QChar &ch3, const QChar &ch3,
unsigned *kind, unsigned *kind,
bool wantFunctionCall) bool wantFunctionCall,
bool wantQt5SignalSlots)
{ {
int referencePosition = 0; int referencePosition = 0;
int completionKind = T_EOF_SYMBOL; int completionKind = T_EOF_SYMBOL;
...@@ -136,6 +137,12 @@ int CppCompletionAssistProvider::activationSequenceChar(const QChar &ch, ...@@ -136,6 +137,12 @@ int CppCompletionAssistProvider::activationSequenceChar(const QChar &ch,
completionKind = T_POUND; completionKind = T_POUND;
referencePosition = 1; referencePosition = 1;
break; break;
case '&':
if (wantQt5SignalSlots) {
completionKind = T_AMPER;
referencePosition = 1;
}
break;
} }
if (kind) if (kind)
......
...@@ -65,7 +65,7 @@ public: ...@@ -65,7 +65,7 @@ public:
static int activationSequenceChar(const QChar &ch, const QChar &ch2, static int activationSequenceChar(const QChar &ch, const QChar &ch2,
const QChar &ch3, unsigned *kind, const QChar &ch3, unsigned *kind,
bool wantFunctionCall); bool wantFunctionCall, bool wantQt5SignalSlots);
}; };
} // namespace CppTools } // namespace CppTools
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment