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 {
T___VOLATILE = T_VOLATILE,
T___VOLATILE__ = T_VOLATILE,
T___ATTRIBUTE = T___ATTRIBUTE__
T___ATTRIBUTE = T___ATTRIBUTE__,
T_LAST_TOKEN
};
class CPLUSPLUS_EXPORT Token
......
......@@ -2168,6 +2168,94 @@ void CppToolsPlugin::test_completion_data()
<< QLatin1String("Foo")
<< 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") << _(
"#define SIGNAL(a) #a\n"
"#define SLOT(a) #a\n"
......
......@@ -596,6 +596,34 @@ bool isQPrivateSignal(const Symbol *symbol)
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
// ------------------------------------
......@@ -745,7 +773,8 @@ int InternalCppCompletionAssistProcessor::startOfOperator(int pos,
const QChar ch2 = pos > 0 ? m_interface->characterAt(pos - 2) : 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) {
QTextCursor tc(m_interface->textDocument());
tc.setPosition(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 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;
start = pos;
}
......@@ -836,7 +871,8 @@ int InternalCppCompletionAssistProcessor::startOfOperator(int pos,
const QChar ch4 = start > -1 ? m_interface->characterAt(start - 1) : QChar();
const QChar ch5 = start > 0 ? m_interface->characterAt(start - 2) : 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
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()
{
if (m_languageFeatures.objCEnabled) {
......@@ -929,7 +977,12 @@ int InternalCppCompletionAssistProcessor::startCompletionHelper()
expression = expressionUnderCursor(tc);
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"))) {
m_model->m_completionOperator = T_SIGNAL;
} else if (expression.endsWith(QLatin1String("SLOT"))) {
......@@ -1276,12 +1329,22 @@ int InternalCppCompletionAssistProcessor::startCompletionInternal(const QString
break;
case T_SIGNAL:
if (completeSignal(results))
if (completeQtMethod(results, CompleteQt4Signals))
return m_startPosition;
break;
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;
break;
......@@ -1581,9 +1644,8 @@ void InternalCppCompletionAssistProcessor::addClassMembersToCompletion(Scope *sc
addClassMembersToCompletion(*cit, staticLookup);
}
bool InternalCppCompletionAssistProcessor::completeQtMethod(
const QList<LookupItem> &results,
bool wantSignals)
bool InternalCppCompletionAssistProcessor::completeQtMethod(const QList<LookupItem> &results,
CompleteQtMethodMode type)
{
if (results.isEmpty())
return false;
......@@ -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) {
if (!scope->isClass())
Class *klass = scope->asClass();
if (!klass)
continue;
for (unsigned i = 0; i < scope->memberCount(); ++i) {
Symbol *member = scope->memberAt(i);
Function *fun = member->type()->asFunctionType();
if (!fun)
if (!fun || fun->isGenerated())
continue;
if (wantSignals && !fun->isSignal())
continue;
else if (!wantSignals && !fun->isSlot())
else if (!wantSignals && type == CompleteQt4Slots && !fun->isSlot())
continue;
unsigned count = fun->argumentCount();
while (true) {
QString signature;
signature += Overview().prettyName(fun->name());
signature += QLatin1Char('(');
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());
const QString completionText = wantQt5SignalOrSlot
? createQt5SignalOrSlot(fun, klass)
: createQt4SignalOrSlot(fun, o);
if (!signatures.contains(signature)) {
if (!signatures.contains(completionText)) {
AssistProposalItem *ci = toCompletionItem(fun);
if (!ci)
break;
signatures.insert(signature);
ci->setText(signature); // fix the completion item.
signatures.insert(completionText);
ci->setText(completionText); // fix the completion item.
m_completions.append(ci);
}
......
......@@ -132,11 +132,13 @@ private:
void completeNamespace(CPlusPlus::ClassOrNamespace *binding);
void completeClass(CPlusPlus::ClassOrNamespace *b, bool staticLookup = true);
void addClassMembersToCompletion(CPlusPlus::Scope *scope, bool staticLookup);
bool completeQtMethod(const QList<CPlusPlus::LookupItem> &results, bool wantSignals);
bool completeSignal(const QList<CPlusPlus::LookupItem> &results)
{ return completeQtMethod(results, true); }
bool completeSlot(const QList<CPlusPlus::LookupItem> &results)
{ return completeQtMethod(results, false); }
enum CompleteQtMethodMode {
CompleteQt4Signals,
CompleteQt4Slots,
CompleteQt5Signals,
CompleteQt5Slots,
};
bool completeQtMethod(const QList<CPlusPlus::LookupItem> &results, CompleteQtMethodMode type);
void globalCompletion(CPlusPlus::Scope *scope);
void addCompletionItem(const QString &text,
......@@ -152,6 +154,10 @@ private:
QSet<QString> *processed,
QSet<QString> *definedMacros);
enum {
CompleteQt5SignalTrigger = CPlusPlus::T_LAST_TOKEN + 1,
CompleteQtSlotTrigger
};
CPlusPlus::LanguageFeatures m_languageFeatures;
QScopedPointer<const CppCompletionAssistInterface> m_interface;
QScopedPointer<CppAssistProposalModel> m_model;
......
......@@ -57,7 +57,7 @@ bool CppCompletionAssistProvider::isActivationCharSequence(const QString &sequen
const QChar &ch = sequence.at(2);
const QChar &ch2 = sequence.at(1);
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 false;
}
......@@ -71,7 +71,8 @@ int CppCompletionAssistProvider::activationSequenceChar(const QChar &ch,
const QChar &ch2,
const QChar &ch3,
unsigned *kind,
bool wantFunctionCall)
bool wantFunctionCall,
bool wantQt5SignalSlots)
{
int referencePosition = 0;
int completionKind = T_EOF_SYMBOL;
......@@ -136,6 +137,12 @@ int CppCompletionAssistProvider::activationSequenceChar(const QChar &ch,
completionKind = T_POUND;
referencePosition = 1;
break;
case '&':
if (wantQt5SignalSlots) {
completionKind = T_AMPER;
referencePosition = 1;
}
break;
}
if (kind)
......
......@@ -65,7 +65,7 @@ public:
static int activationSequenceChar(const QChar &ch, const QChar &ch2,
const QChar &ch3, unsigned *kind,
bool wantFunctionCall);
bool wantFunctionCall, bool wantQt5SignalSlots);
};
} // 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