mirror of
https://github.com/eclipse-cdt/cdt
synced 2025-04-29 19:45:01 +02:00
Content assistant for Qt elements
This adds content assistants for QObject::connect function calls and Q_PROPERTY expansions. QObject::connect function calls look like: QObject::connect( sender, SIGNAL(someSignalFunction(int), receiver, SLOT(someSlotFunction(int)); The assistant provides proposals in the SIGNAL and SLOT expansions. The QObject for the corresponding type is used to create a list of signal or slot function signatures. Q_PROPERTY expansions look like: Q_PROPERTY( type name READ someFunction ... ) [The ... is a list of optional attributes.] The assistant proposes attribute names that have not yet been added. It also proposals appropriate values for the attribute. This patch also adds test cases for this feature. Change-Id: I0eb25235bb423c1cfcd743075331f90f269afea7 Signed-off-by: Andrew Eidsness <eclipse@jfront.com> Reviewed-on: https://git.eclipse.org/r/19721 Tested-by: Hudson CI Reviewed-by: Doug Schaefer <dschaefer@qnx.com> IP-Clean: Doug Schaefer <dschaefer@qnx.com>
This commit is contained in:
parent
c6c1ef94fc
commit
ff690ab953
22 changed files with 2651 additions and 347 deletions
|
@ -12,6 +12,8 @@ Require-Bundle: org.eclipse.core.runtime,
|
|||
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Bundle-Localization: plugin
|
||||
Export-Package: org.eclipse.cdt.qt.core,
|
||||
org.eclipse.cdt.qt.core.index,
|
||||
org.eclipse.cdt.internal.qt.core.index;x-friends:="org.eclipse.cdt.qt.tests"
|
||||
Export-Package: org.eclipse.cdt.internal.qt.core;x-friends:="org.eclipse.cdt.qt.ui",
|
||||
org.eclipse.cdt.internal.qt.core.index;x-friends:="org.eclipse.cdt.qt.tests",
|
||||
org.eclipse.cdt.internal.qt.core.parser;x-friends:="org.eclipse.cdt.qt.ui",
|
||||
org.eclipse.cdt.qt.core,
|
||||
org.eclipse.cdt.qt.core.index
|
||||
|
|
|
@ -0,0 +1,217 @@
|
|||
/*
|
||||
* Copyright (c) 2013 QNX Software Systems and others.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.cdt.internal.qt.core;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.cdt.core.dom.ast.IASTDeclaration;
|
||||
import org.eclipse.cdt.core.dom.ast.IASTExpression;
|
||||
import org.eclipse.cdt.core.dom.ast.IASTFunctionCallExpression;
|
||||
import org.eclipse.cdt.core.dom.ast.IASTIdExpression;
|
||||
import org.eclipse.cdt.core.dom.ast.IASTInitializerClause;
|
||||
import org.eclipse.cdt.core.dom.ast.IASTName;
|
||||
import org.eclipse.cdt.core.dom.ast.IASTNode;
|
||||
import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit;
|
||||
import org.eclipse.cdt.core.dom.ast.IBinding;
|
||||
import org.eclipse.cdt.core.dom.ast.IType;
|
||||
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTCompositeTypeSpecifier;
|
||||
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTExpression;
|
||||
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTFieldReference;
|
||||
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTInitializerClause;
|
||||
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTVisibilityLabel;
|
||||
import org.eclipse.cdt.core.dom.ast.cpp.ICPPClassType;
|
||||
import org.eclipse.cdt.core.dom.ast.cpp.ICPPMethod;
|
||||
import org.eclipse.cdt.core.model.ICProject;
|
||||
import org.eclipse.cdt.core.model.ITranslationUnit;
|
||||
import org.eclipse.cdt.internal.core.dom.parser.ITypeContainer;
|
||||
import org.eclipse.cdt.internal.core.dom.parser.cpp.ICPPEvaluation;
|
||||
import org.eclipse.cdt.internal.core.dom.parser.cpp.ICPPInternalBinding;
|
||||
import org.eclipse.cdt.qt.core.index.IQMethod;
|
||||
import org.eclipse.cdt.qt.core.index.IQObject;
|
||||
import org.eclipse.core.resources.IProject;
|
||||
|
||||
@SuppressWarnings("restriction")
|
||||
public class ASTUtil {
|
||||
|
||||
/**
|
||||
* A convenience method to find the project that contains the given node. Returns null if
|
||||
* the project cannot be found.
|
||||
*/
|
||||
public static IProject getProject(IASTNode node) {
|
||||
IASTTranslationUnit astTU = node.getTranslationUnit();
|
||||
if (astTU == null)
|
||||
return null;
|
||||
|
||||
ITranslationUnit tu = astTU.getOriginatingTranslationUnit();
|
||||
if (tu == null)
|
||||
return null;
|
||||
|
||||
ICProject cProject = tu.getCProject();
|
||||
if (cProject == null)
|
||||
return null;
|
||||
|
||||
return cProject.getProject();
|
||||
}
|
||||
|
||||
// NOTE: This expression allows embedded line terminators (?s) for cases where the code looks like:
|
||||
// QObject::connect( &a, SIGNAL(
|
||||
// sig1(
|
||||
// int
|
||||
// ), ...
|
||||
// The two patterns are nearly identical. The difference is because the first is for matching SIGNAL/
|
||||
// SLOT expansions. The second is for matching the argument to that expansion.
|
||||
public static final Pattern Regex_SignalSlotExpansion = Pattern.compile("(?s)(SIGNAL|SLOT)\\s*\\(\\s*(.*?)\\s*\\)\\s*");
|
||||
public static final Pattern Regex_FunctionCall = Pattern.compile("(?s)\\s*(.*)\\s*\\(\\s*(.*?)\\s*\\)\\s*");
|
||||
|
||||
public static IType getBaseType(IType type) {
|
||||
while (type instanceof ITypeContainer)
|
||||
type = ((ITypeContainer) type).getType();
|
||||
return type;
|
||||
}
|
||||
|
||||
public static IType getBaseType(IASTNode node) {
|
||||
if (node instanceof IASTIdExpression)
|
||||
return getBaseType((IASTIdExpression) node);
|
||||
if (node instanceof IASTFunctionCallExpression)
|
||||
return getReceiverType((IASTFunctionCallExpression) node);
|
||||
if (node instanceof IASTExpression)
|
||||
return getBaseType(((IASTExpression) node).getExpressionType());
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static IType getBaseType(IASTInitializerClause init) {
|
||||
if (!(init instanceof ICPPASTInitializerClause))
|
||||
return null;
|
||||
|
||||
ICPPASTInitializerClause cppInit = (ICPPASTInitializerClause) init;
|
||||
ICPPEvaluation eval = cppInit.getEvaluation();
|
||||
return eval == null ? null : getBaseType(eval.getTypeOrFunctionSet(cppInit));
|
||||
}
|
||||
|
||||
public static ICPPClassType getReceiverType(IASTFunctionCallExpression fncall) {
|
||||
|
||||
// NOTE: This cannot rely on the Evaluation because we're in a contest assist context.
|
||||
// At this point is likely that the full function call is not complete, so at least
|
||||
// some of the eval leads to a Problem. We don't need the Eval anyhow, just lookup
|
||||
// the type of the receiver.
|
||||
|
||||
IASTExpression fnName = fncall.getFunctionNameExpression();
|
||||
if (!(fnName instanceof ICPPASTFieldReference))
|
||||
return null;
|
||||
|
||||
ICPPASTFieldReference fieldRef = (ICPPASTFieldReference) fnName;
|
||||
ICPPASTExpression receiver = fieldRef.getFieldOwner();
|
||||
|
||||
IType recvType = getBaseType(receiver);
|
||||
return recvType instanceof ICPPClassType ? (ICPPClassType) recvType : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does not return null.
|
||||
*/
|
||||
public static Collection<IQMethod> findMethods(IQObject qobj, QtSignalSlotReference ref) {
|
||||
Set<IQMethod> bindings = new LinkedHashSet<IQMethod>();
|
||||
|
||||
Iterable<IQMethod> methods = null;
|
||||
switch(ref.type)
|
||||
{
|
||||
case Signal:
|
||||
methods = qobj.getSignals().withoutOverrides();
|
||||
break;
|
||||
case Slot:
|
||||
methods = qobj.getSlots().withoutOverrides();
|
||||
break;
|
||||
}
|
||||
|
||||
if (methods != null) {
|
||||
String qtNormalizedSig = QtMethodUtil.getQtNormalizedMethodSignature(ref.signature);
|
||||
if (qtNormalizedSig == null)
|
||||
return bindings;
|
||||
|
||||
for (IQMethod method : methods)
|
||||
for(String signature : method.getSignatures())
|
||||
if (signature.equals(qtNormalizedSig))
|
||||
bindings.add(method);
|
||||
}
|
||||
return bindings;
|
||||
}
|
||||
|
||||
public static IBinding resolveFunctionBinding(IASTFunctionCallExpression fnCall) {
|
||||
IASTName fnName = null;
|
||||
IASTExpression fnNameExpr = fnCall.getFunctionNameExpression();
|
||||
if (fnNameExpr instanceof IASTIdExpression)
|
||||
fnName = ((IASTIdExpression) fnNameExpr).getName();
|
||||
else if (fnNameExpr instanceof ICPPASTFieldReference)
|
||||
fnName = ((ICPPASTFieldReference) fnNameExpr).getFieldName();
|
||||
|
||||
return fnName == null ? null : fnName.resolveBinding();
|
||||
}
|
||||
|
||||
public static ICPPASTVisibilityLabel findVisibilityLabel(ICPPMethod method, IASTNode ast) {
|
||||
// the visibility cannot be found without an ast
|
||||
if (ast == null)
|
||||
return null;
|
||||
|
||||
// We need to get the CompTypeSpec in order to see the token that created the method's
|
||||
// visibility specifier. The ast parameter will be either the method definition or a
|
||||
// declaration. If it happens to be a declaration, then the CompTypeSpec is a parent of
|
||||
// the AST and it can be accessed through public API. However, if the ast parameter happens
|
||||
// to be a definition, then there isn't any public API (that I've found) to get to the
|
||||
// CompTypeSpec. Instead, we cheat and use the InternalBinding.
|
||||
|
||||
MethodSpec methodSpec = new MethodSpec(ast);
|
||||
if (methodSpec.clsSpec == null
|
||||
&& method instanceof ICPPInternalBinding)
|
||||
{
|
||||
ICPPInternalBinding internalBinding = (ICPPInternalBinding) method;
|
||||
IASTNode[] decls = internalBinding.getDeclarations();
|
||||
for (int i = 0; methodSpec.clsSpec == null && i < decls.length; ++i)
|
||||
methodSpec = new MethodSpec(decls[i]);
|
||||
}
|
||||
|
||||
if(methodSpec.clsSpec == null)
|
||||
return null;
|
||||
|
||||
ICPPASTVisibilityLabel lastLabel = null;
|
||||
for (IASTDeclaration decl : methodSpec.clsSpec.getMembers()) {
|
||||
if (decl instanceof ICPPASTVisibilityLabel)
|
||||
lastLabel = (ICPPASTVisibilityLabel) decl;
|
||||
else if (decl == methodSpec.methodDecl)
|
||||
return lastLabel;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static class MethodSpec
|
||||
{
|
||||
public final ICPPASTCompositeTypeSpecifier clsSpec;
|
||||
public final IASTNode methodDecl;
|
||||
|
||||
public MethodSpec( IASTNode node )
|
||||
{
|
||||
ICPPASTCompositeTypeSpecifier cls = null;
|
||||
IASTNode mth = node;
|
||||
while( mth != null && cls == null )
|
||||
{
|
||||
IASTNode parent = mth.getParent();
|
||||
if (parent instanceof ICPPASTCompositeTypeSpecifier)
|
||||
cls = (ICPPASTCompositeTypeSpecifier) parent;
|
||||
else
|
||||
mth = parent;
|
||||
}
|
||||
|
||||
clsSpec = cls;
|
||||
methodDecl = mth;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,132 @@
|
|||
/*
|
||||
* Copyright (c) 2013 QNX Software Systems and others.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.cdt.internal.qt.core;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.cdt.core.dom.ast.IASTCompletionContext;
|
||||
import org.eclipse.cdt.core.dom.ast.IASTFunctionCallExpression;
|
||||
import org.eclipse.cdt.core.dom.ast.IASTInitializerClause;
|
||||
import org.eclipse.cdt.core.dom.ast.IASTName;
|
||||
import org.eclipse.cdt.core.dom.ast.IASTNode;
|
||||
import org.eclipse.cdt.core.dom.ast.IBinding;
|
||||
import org.eclipse.cdt.qt.core.QtKeywords;
|
||||
|
||||
/**
|
||||
* Utility for managing interaction with QObject::connect and QObject::disconnect function
|
||||
* calls. These function calls can contain two expansions. The first is always SIGNAL,
|
||||
* the second is either SIGNAL (which will cause the second signal to be emitted when the
|
||||
* first is received) or SLOT (which will cause the slot to be evaluate when the signal
|
||||
* is received). This class follows the Qt convention of calling the first SIGNAL expansion
|
||||
* the sender and the second (which could be SIGNAL or SLOT) the receiver.
|
||||
*
|
||||
* In the following examples, the type of the signal is the type of the q_sender variable.
|
||||
* The type of the method is the type of the q_receiver variable. The variable q_unrelated is
|
||||
* some instance that is not needed for either case.
|
||||
* <pre>
|
||||
* QObject::connect( q_sender, SIGNAL(destroyed()), q_receiver, SIGNAL() );
|
||||
* QObject::connect( q_sender, SIGNAL(destroyed()), q_receiver, SLOT(deleteLater()) );
|
||||
* QObject::connect( q_sender, SIGNAL(destroyed()), q_receiver, SIGNAL(), Qt::AutoConnection );
|
||||
* QObject::connect( q_sender, SIGNAL(destroyed()), q_receiver, SLOT(deleteLater()), Qt::AutoConnection );
|
||||
* q_unrelated->connect( q_sender, SIGNAL(destroyed()), q_receiver, SIGNAL() );
|
||||
* q_unrelated->connect( q_sender, SIGNAL(destroyed()), q_receiver, SLOT(deleteLater()) );
|
||||
* q_unrelated->connect( q_sender, SIGNAL(destroyed()), q_receiver, SIGNAL(), Qt::AutoConnection );
|
||||
* q_unrelated->connect( q_sender, SIGNAL(destroyed()), q_receiver, SLOT(deleteLater()), Qt::AutoConnection );
|
||||
*
|
||||
* q_receiver->connect( q_sender, SIGNAL(destroyed()), SIGNAL() );
|
||||
* q_receiver->connect( q_sender, SIGNAL(destroyed()), SLOT() );
|
||||
* q_receiver->connect( q_sender, SIGNAL(destroyed()), SIGNAL(), Qt::AutoConnection );
|
||||
* q_receiver->connect( q_sender, SIGNAL(destroyed()), SLOT(), Qt::AutoConnection );
|
||||
*
|
||||
* QObject::disconnect( q_sender, SIGNAL(), q_receiver, SIGNAL() );
|
||||
* QObject::disconnect( q_sender, SIGNAL(), q_receiver, SLOT() );
|
||||
* q_unrelated->disconnect( q_sender, SIGNAL(), q_receiver, SIGNAL() );
|
||||
* q_unrelated->disconnect( q_sender, SIGNAL(), q_receiver, SLOT() );
|
||||
*
|
||||
* q_sender->disconnect( SIGNAL(), q_receiver, SIGNAL() );
|
||||
* q_sender->disconnect( SIGNAL(), q_receiver, SLOT() );
|
||||
* q_sender->disconnect( SIGNAL(), q_receiver );
|
||||
* q_sender->disconnect( SIGNAL() );
|
||||
* q_sender->disconnect();
|
||||
* </pre>
|
||||
*/
|
||||
public class QtFunctionCallUtil {
|
||||
|
||||
private static final Pattern SignalRegex = Pattern.compile("^\\s*" + QtKeywords.SIGNAL + ".*");
|
||||
private static final Pattern MethodRegex = Pattern.compile("^\\s*(?:" + QtKeywords.SIGNAL + '|' + QtKeywords.SLOT + ").*");
|
||||
|
||||
/**
|
||||
* Return true if the specified name is a QObject::connect or QObject::disconnect function
|
||||
* and false otherwise.
|
||||
*/
|
||||
public static boolean isQObjectFunctionCall(IASTCompletionContext astContext, boolean isPrefix, IASTName name) {
|
||||
if (name == null)
|
||||
return false;
|
||||
|
||||
// Bug332201: Qt content assist should always be applied to the most specific part of
|
||||
// the target name.
|
||||
IBinding[] funcBindings = astContext.findBindings(name.getLastName(), isPrefix);
|
||||
for (IBinding funcBinding : funcBindings)
|
||||
if (QtKeywords.is_QObject_connect(funcBinding)
|
||||
|| QtKeywords.is_QObject_disconnect(funcBinding))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given function call argument is a SIGNAL or SLOT expansion
|
||||
* and false otherwise.
|
||||
public static boolean isQtMethodExpansion(IASTInitializerClause arg) {
|
||||
return MethodRegex.matcher(arg.getRawSignature()).matches();
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* If the given argument is a SIGNAL or SLOT expansion then find and return the node in the AST
|
||||
* that will be used for this method. Returns null if the argument is not a Qt method call or
|
||||
* if the associated node cannot be found.
|
||||
*/
|
||||
public static IASTNode getTypeNode(IASTFunctionCallExpression call, IASTInitializerClause[] args, int argIndex) {
|
||||
int sigExpIndex = getExpansionArgIndex(args, 0, SignalRegex);
|
||||
if (argIndex == sigExpIndex)
|
||||
return getSignalTargetNode(sigExpIndex, call, args);
|
||||
|
||||
int methodExpIndex = getExpansionArgIndex(args, sigExpIndex + 1, MethodRegex);
|
||||
if (argIndex == methodExpIndex)
|
||||
return getMethodTargetNode(methodExpIndex, sigExpIndex, call, args);
|
||||
|
||||
// Otherwise the given argument is not a SIGNAL or SLOT expansion.
|
||||
return null;
|
||||
}
|
||||
|
||||
private static IASTNode getSignalTargetNode(int sigExpIndex, IASTFunctionCallExpression call, IASTInitializerClause[] args) {
|
||||
// When the SIGNAL expansion is first, the type is based on the receiver of
|
||||
// the function call. Otherwise the type is the previous argument.
|
||||
return sigExpIndex == 0 ? call : args[sigExpIndex - 1];
|
||||
}
|
||||
|
||||
private static IASTNode getMethodTargetNode(int methodExpIndex, int sigExpIndex, IASTFunctionCallExpression call, IASTInitializerClause[] args) {
|
||||
// If the method is right after the signal, then the type is based on the receiver
|
||||
// of the function call. Otherwise the method type is based on the parameter right
|
||||
// before the expansion.
|
||||
return (methodExpIndex == (sigExpIndex + 1)) ? call : args[methodExpIndex - 1];
|
||||
}
|
||||
|
||||
private static int getExpansionArgIndex(IASTInitializerClause[] args, int begin, Pattern macroNameRegex) {
|
||||
for(int i = begin; i < args.length; ++i) {
|
||||
IASTInitializerClause arg = args[i];
|
||||
String raw = arg.getRawSignature();
|
||||
Matcher m = macroNameRegex.matcher(raw);
|
||||
if (m.matches())
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
* Copyright (c) 2013 QNX Software Systems and others.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.cdt.internal.qt.core;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
import org.eclipse.cdt.core.dom.ast.IASTName;
|
||||
import org.eclipse.cdt.core.dom.ast.IASTNode;
|
||||
import org.eclipse.cdt.core.dom.ast.IBinding;
|
||||
import org.eclipse.cdt.qt.core.QtKeywords;
|
||||
|
||||
public class QtSignalSlotReference
|
||||
{
|
||||
public static enum Type
|
||||
{
|
||||
Signal( "sender", "SIGNAL", "signal" ),
|
||||
Slot ( "receiver", "SLOT", "slot" );
|
||||
|
||||
public final String roleName;
|
||||
public final String macroName;
|
||||
public final String paramName;
|
||||
|
||||
public boolean matches( Type other )
|
||||
{
|
||||
if( other == null )
|
||||
return false;
|
||||
|
||||
// The signal parameter must be a SIGNAL, but the slot could be a
|
||||
// SLOT or a SIGNAL.
|
||||
return this == Signal ? other == Signal : true;
|
||||
}
|
||||
|
||||
private Type( String roleName, String macroName, String paramName )
|
||||
{
|
||||
this.roleName = roleName;
|
||||
this.macroName = macroName;
|
||||
this.paramName = paramName;
|
||||
}
|
||||
}
|
||||
|
||||
public final IASTNode parent;
|
||||
public final IASTNode node;
|
||||
public final Type type;
|
||||
public final String signature;
|
||||
public final int offset;
|
||||
public final int length;
|
||||
|
||||
private QtSignalSlotReference( IASTNode parent, IASTNode node, Type type, String signature, int offset, int length )
|
||||
{
|
||||
this.parent = parent;
|
||||
this.node = node;
|
||||
this.type = type;
|
||||
this.signature = signature;
|
||||
this.offset = offset;
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
public IASTName createName( IBinding binding )
|
||||
{
|
||||
return new QtSignalSlotReferenceName( parent, node, signature, offset, length, binding );
|
||||
}
|
||||
|
||||
public static QtSignalSlotReference parse( IASTNode parent, IASTNode arg )
|
||||
{
|
||||
// This check will miss cases like:
|
||||
// #define MY_SIG1 SIGNAL
|
||||
// #define MY_SIG2(s) SIGNAL(s)
|
||||
// #define MY_SIG3(s) SIGNAL(signal())
|
||||
// connect( &a, MY_SIG1(signal()), ...
|
||||
// connect( &a, MY_SIG2(signal()), ...
|
||||
// connect( &a, MY_SIG2, ...
|
||||
// This could be improved by adding tests when arg represents a macro expansion. However, I'm
|
||||
// not sure if we would be able to follow the more complicated case of macros that call functions
|
||||
// that use the SIGNAL macro. For now I've implemented the simpler check of forcing the call to
|
||||
// use the SIGNAL/SLOT macro directly.
|
||||
String raw = arg.getRawSignature();
|
||||
Matcher m = ASTUtil.Regex_SignalSlotExpansion.matcher( raw );
|
||||
if( ! m.matches() )
|
||||
return null;
|
||||
|
||||
Type type;
|
||||
String macroName = m.group( 1 );
|
||||
if( QtKeywords.SIGNAL.equals( macroName ) )
|
||||
type = Type.Signal;
|
||||
else if( QtKeywords.SLOT.equals( macroName ) )
|
||||
type = Type.Slot;
|
||||
else
|
||||
return null;
|
||||
|
||||
// Get the argument to the SIGNAL/SLOT macro and the offset/length of that argument within the
|
||||
// complete function argument. E.g., with this argument to QObject::connect
|
||||
// SIGNAL( signal(int) )
|
||||
// the values are
|
||||
// expansionArgs: "signal(int)"
|
||||
// expansionOffset: 8
|
||||
// expansionLength: 11
|
||||
String expansionArgs = m.group( 2 );
|
||||
int expansionOffset = m.start( 2 );
|
||||
int expansionLength = m.end( 2 ) - expansionOffset;
|
||||
|
||||
return new QtSignalSlotReference( parent, arg, type, expansionArgs, expansionOffset, expansionLength );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* Copyright (c) 2013 QNX Software Systems and others.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.cdt.internal.qt.core;
|
||||
|
||||
import org.eclipse.cdt.core.dom.ast.IASTFileLocation;
|
||||
import org.eclipse.cdt.core.dom.ast.IASTImageLocation;
|
||||
import org.eclipse.cdt.core.dom.ast.IASTPreprocessorIncludeStatement;
|
||||
|
||||
/**
|
||||
* The location of the signal/slot reference is stored as the location of the parent
|
||||
* macro expansion + an offset, which is the number of characters between the start
|
||||
* of the expansion and the start of the argument (including whitespace). E.g. in,
|
||||
*
|
||||
* <pre>
|
||||
* SIGNAL( signal1( int ) )
|
||||
* ^ ^ ^ c: end of reference name
|
||||
* | +------------- b: start of reference name
|
||||
* +--------------------- a: start of macro expansion
|
||||
* </pre>
|
||||
*
|
||||
* The offset is b - a and length is c - a. This means that the result of 'Find
|
||||
* References' will highlight just "signal( int )".
|
||||
*
|
||||
* @see QtSignalSlotReferenceName
|
||||
*/
|
||||
public class QtSignalSlotReferenceLocation implements IASTImageLocation {
|
||||
|
||||
private final IASTFileLocation referenceLocation;
|
||||
private final int offset;
|
||||
private final int length;
|
||||
|
||||
public QtSignalSlotReferenceLocation(IASTFileLocation referenceLocation, int offset, int length) {
|
||||
this.referenceLocation = referenceLocation;
|
||||
this.offset = offset;
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLocationKind() {
|
||||
return IASTImageLocation.ARGUMENT_TO_MACRO_EXPANSION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNodeOffset() {
|
||||
return referenceLocation.getNodeOffset() + offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNodeLength() {
|
||||
return length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFileName() {
|
||||
return referenceLocation.getFileName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IASTFileLocation asFileLocation() {
|
||||
return referenceLocation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getEndingLineNumber() {
|
||||
return referenceLocation.getEndingLineNumber();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStartingLineNumber() {
|
||||
return referenceLocation.getStartingLineNumber();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IASTPreprocessorIncludeStatement getContextInclusionStatement() {
|
||||
return referenceLocation.getContextInclusionStatement();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,287 @@
|
|||
/*
|
||||
* Copyright (c) 2013 QNX Software Systems and others.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.cdt.internal.qt.core;
|
||||
|
||||
import org.eclipse.cdt.core.dom.ILinkage;
|
||||
import org.eclipse.cdt.core.dom.ast.ASTNodeProperty;
|
||||
import org.eclipse.cdt.core.dom.ast.ASTVisitor;
|
||||
import org.eclipse.cdt.core.dom.ast.ExpansionOverlapsBoundaryException;
|
||||
import org.eclipse.cdt.core.dom.ast.IASTCompletionContext;
|
||||
import org.eclipse.cdt.core.dom.ast.IASTFileLocation;
|
||||
import org.eclipse.cdt.core.dom.ast.IASTImageLocation;
|
||||
import org.eclipse.cdt.core.dom.ast.IASTName;
|
||||
import org.eclipse.cdt.core.dom.ast.IASTNameOwner;
|
||||
import org.eclipse.cdt.core.dom.ast.IASTNode;
|
||||
import org.eclipse.cdt.core.dom.ast.IASTNodeLocation;
|
||||
import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit;
|
||||
import org.eclipse.cdt.core.dom.ast.IBinding;
|
||||
import org.eclipse.cdt.core.parser.IToken;
|
||||
|
||||
/**
|
||||
* Signals are connected to slots by referencing them within an expansion of SIGNAL
|
||||
* or SLOT. E.g.,
|
||||
*
|
||||
* <pre>
|
||||
* class A : public QObject
|
||||
* {
|
||||
* Q_SIGNAL void signal1( int );
|
||||
* Q_SLOT void slot1();
|
||||
* };
|
||||
* A a;
|
||||
* QObject::connect( &a, SIGNAL( signal1( int ) ), &a, SLOT( slot1() ) );
|
||||
* </pre>
|
||||
*
|
||||
* The goal is for 'Find References' on the function declarations to find the references
|
||||
* in the macro expansions. The PDOM stores references as a linked list from the binding
|
||||
* for the function.
|
||||
*
|
||||
* This class represents the name within the expansion, i.e., "signal1( int )" within
|
||||
* "SIGNAL( signal1( int ) )" and "slot1()" within "SLOT( slot1() )".
|
||||
*/
|
||||
public class QtSignalSlotReferenceName implements IASTName {
|
||||
|
||||
private final IASTNode referenceNode;
|
||||
private final String argument;
|
||||
private final IBinding binding;
|
||||
private final IASTImageLocation location;
|
||||
|
||||
private IASTNode parent;
|
||||
private ASTNodeProperty propertyInParent;
|
||||
|
||||
public QtSignalSlotReferenceName(IASTNode parent, IASTNode referenceNode, String argument, int offset, int length, IBinding binding) {
|
||||
this.parent = parent;
|
||||
this.referenceNode = referenceNode;
|
||||
this.argument = argument;
|
||||
this.binding = binding;
|
||||
|
||||
IASTFileLocation referenceLocation = referenceNode.getFileLocation();
|
||||
this.location
|
||||
= referenceLocation == null
|
||||
? null
|
||||
: new QtSignalSlotReferenceLocation(referenceLocation, offset, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public char[] toCharArray() {
|
||||
return argument.toCharArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public char[] getSimpleID() {
|
||||
return toCharArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public char[] getLookupKey() {
|
||||
return toCharArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IASTTranslationUnit getTranslationUnit() {
|
||||
return referenceNode.getTranslationUnit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IASTFileLocation getFileLocation() {
|
||||
return getImageLocation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IASTNodeLocation[] getNodeLocations() {
|
||||
// The javadoc says that locations that are completely enclosed within a
|
||||
// macro expansion return only the location of that expansion.
|
||||
return referenceNode.getNodeLocations();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContainingFilename() {
|
||||
return referenceNode.getContainingFilename();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPartOfTranslationUnitFile() {
|
||||
return referenceNode.isPartOfTranslationUnitFile();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IASTNode[] getChildren() {
|
||||
return new IASTNode[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public IASTNode getParent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setParent(IASTNode node) {
|
||||
this.parent = node;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ASTNodeProperty getPropertyInParent() {
|
||||
return propertyInParent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPropertyInParent(ASTNodeProperty property) {
|
||||
propertyInParent = property;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean accept(ASTVisitor visitor) {
|
||||
// The signal/slot reference has nothing to visit. It will have been
|
||||
// reached by the reference node, so we can't visit that, and there is
|
||||
// nothing else.
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRawSignature() {
|
||||
// The raw signature of the reference is the text of the argument.
|
||||
return argument;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(IASTNode node) {
|
||||
// There aren't any nodes contained within the signal/slot reference.
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IToken getLeadingSyntax() throws ExpansionOverlapsBoundaryException, UnsupportedOperationException {
|
||||
// The parent is the macro reference name, and this is the entire
|
||||
// content of the arguments. Since there is nothing between these, there
|
||||
// will not be any leading syntax.
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IToken getTrailingSyntax() throws ExpansionOverlapsBoundaryException, UnsupportedOperationException {
|
||||
// The parent is the macro reference name, and this is the entire
|
||||
// content of the arguments. Since there is nothing between these, there
|
||||
// will not be any leading syntax.
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IToken getSyntax() throws ExpansionOverlapsBoundaryException {
|
||||
// This reference to the signal/slot function is fully contained within
|
||||
// a preprocessor node, which does not support syntax.
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFrozen() {
|
||||
return referenceNode.isFrozen();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
return referenceNode.isActive();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRoleOfName(boolean allowResolution) {
|
||||
return IASTNameOwner.r_reference;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDeclaration() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReference() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDefinition() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinding getBinding() {
|
||||
return binding;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinding resolveBinding() {
|
||||
return getBinding();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IASTCompletionContext getCompletionContext() {
|
||||
// Signal/slot references are fully contained within a macro expansion,
|
||||
// so there is no completion context.
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ILinkage getLinkage() {
|
||||
return referenceNode instanceof IASTName ? ((IASTName) referenceNode).getLinkage() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IASTImageLocation getImageLocation() {
|
||||
return location;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IASTName getLastName() {
|
||||
// Signal/slot references are not qualified, so return itself.
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isQualified() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IASTName copy() {
|
||||
// Signal/slot references are preprocessor nodes, so they don't support
|
||||
// copying.
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IASTName copy(CopyStyle style) {
|
||||
// Signal/slot references are preprocessor nodes, so they don't support
|
||||
// copying.
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IASTNode getOriginalNode() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBinding(IBinding binding) {
|
||||
// Signal/slot references find their binding on instantiation, they
|
||||
// never allow it to be replaced.
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinding getPreBinding() {
|
||||
return getBinding();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinding resolvePreBinding() {
|
||||
return getBinding();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "QtSignalSlotReference(" + new String(toCharArray()) + ')';
|
||||
}
|
||||
}
|
|
@ -48,6 +48,9 @@ public class QObject implements IQObject {
|
|||
for(QtPDOMQObject base : pdomQObject.findBases()) {
|
||||
QObject baseQObj = new QObject(qtIndex, cdtIndex, base);
|
||||
this.bases.add(baseQObj);
|
||||
baseSlots.addAll(baseQObj.getSlots().all());
|
||||
baseSignals.addAll(baseQObj.getSignals().all());
|
||||
baseInvokables.addAll(baseQObj.getInvokables().all());
|
||||
baseProps.addAll(baseQObj.getProperties().all());
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,10 @@
|
|||
|
||||
package org.eclipse.cdt.qt.core;
|
||||
|
||||
import org.eclipse.cdt.core.dom.ast.DOMException;
|
||||
import org.eclipse.cdt.core.dom.ast.IBinding;
|
||||
import org.eclipse.cdt.core.dom.ast.cpp.ICPPFunction;
|
||||
|
||||
/**
|
||||
* Declares constants related to tokens that are special in Qt applications.
|
||||
*/
|
||||
|
@ -36,4 +40,50 @@ public class QtKeywords {
|
|||
public static final String SIGNALS = "signals";
|
||||
public static final String SLOT = "SLOT";
|
||||
public static final String SLOTS = "slots";
|
||||
|
||||
/**
|
||||
* Returns true if the argument binding is for the QObject::connect function
|
||||
* and false otherwise.
|
||||
*/
|
||||
public static boolean is_QObject_connect(IBinding binding) {
|
||||
if (binding == null)
|
||||
return false;
|
||||
|
||||
// IBinding#getAdapter returns null when binding is an instance of
|
||||
// PDOMCPPMethod.
|
||||
if (!(binding instanceof ICPPFunction))
|
||||
return false;
|
||||
|
||||
try {
|
||||
String[] qualName = ((ICPPFunction) binding).getQualifiedName();
|
||||
return qualName.length == 2
|
||||
&& QOBJECT.equals(qualName[0])
|
||||
&& CONNECT.equals(qualName[1]);
|
||||
} catch (DOMException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the argument binding is for the QObject::disconnect function
|
||||
* and false otherwise.
|
||||
*/
|
||||
public static boolean is_QObject_disconnect(IBinding binding) {
|
||||
if (binding == null)
|
||||
return false;
|
||||
|
||||
// IBinding#getAdapter returns null when binding is an instance of
|
||||
// PDOMCPPMethod.
|
||||
if (!(binding instanceof ICPPFunction))
|
||||
return false;
|
||||
|
||||
try {
|
||||
String[] qualName = ((ICPPFunction) binding).getQualifiedName();
|
||||
return qualName.length == 2
|
||||
&& QOBJECT.equals(qualName[0])
|
||||
&& DISCONNECT.equals(qualName[1]);
|
||||
} catch (DOMException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,8 +31,7 @@
|
|||
<artifactId>tycho-surefire-plugin</artifactId>
|
||||
<version>${tycho-version}</version>
|
||||
<configuration>
|
||||
<useUIHarness>false</useUIHarness>
|
||||
<!-- Core tests actually use eclipse.ui classes, see CProjectHelper -->
|
||||
<useUIHarness>true</useUIHarness>
|
||||
<argLine>${base.ui.test.vmargs} -ea -Xms256m -Xmx512m -XX:MaxPermSize=256M</argLine>
|
||||
<includes>
|
||||
<include>**/AllQtTests.*</include>
|
||||
|
|
|
@ -15,8 +15,9 @@ public class AllQtTests extends TestSuite {
|
|||
public static Test suite() throws Exception {
|
||||
return
|
||||
new TestSuite(
|
||||
SimpleTests.class,
|
||||
QMakeTests.class,
|
||||
QObjectTests.class,
|
||||
QtContentAssistantTests.class,
|
||||
QtIndexTests.class,
|
||||
QtRegressionTests.class);
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ import org.eclipse.cdt.internal.qt.core.index.QMakeInfo;
|
|||
import org.eclipse.cdt.internal.qt.core.index.QMakeParser;
|
||||
import org.eclipse.cdt.internal.qt.core.index.QMakeVersion;
|
||||
|
||||
public class SimpleTests extends TestCase {
|
||||
public class QMakeTests extends TestCase {
|
||||
|
||||
public void testQMakeVersion() throws Exception {
|
||||
// Make sure null is returned for invalid version strings.
|
|
@ -0,0 +1,188 @@
|
|||
/*
|
||||
* Copyright (c) 2013 QNX Software Systems and others.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.cdt.qt.tests;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.eclipse.cdt.core.dom.ast.IASTCompletionNode;
|
||||
import org.eclipse.cdt.core.model.ICProject;
|
||||
import org.eclipse.cdt.core.model.ITranslationUnit;
|
||||
import org.eclipse.cdt.internal.qt.ui.assist.QPropertyExpansion;
|
||||
import org.eclipse.cdt.ui.text.contentassist.ICEditorContentAssistInvocationContext;
|
||||
import org.eclipse.jface.text.BadLocationException;
|
||||
import org.eclipse.jface.text.Document;
|
||||
import org.eclipse.jface.text.IDocument;
|
||||
import org.eclipse.jface.text.ITextViewer;
|
||||
import org.eclipse.ui.IEditorPart;
|
||||
|
||||
public class QtContentAssistantTests extends TestCase {
|
||||
|
||||
public void testQPropertyProposals() throws Exception {
|
||||
|
||||
String decl = "Q_PROPERTY( type name READ accessor WRITE modifier )";
|
||||
int atEnd = decl.length();
|
||||
int atRParen = decl.indexOf(')');
|
||||
int afterModifier = decl.indexOf("modifier") + "modifier".length();
|
||||
int afterWRITE = decl.indexOf("WRITE") + "WRITE".length();
|
||||
int inModifier = decl.indexOf("modifier") + 3;
|
||||
int inWRITE = decl.indexOf("WRITE") + 3;
|
||||
|
||||
IDocument doc = new Document(decl);
|
||||
|
||||
// The expansion is not applicable when invoked after the closing paren
|
||||
QPropertyExpansion exp = QPropertyExpansion.create(new Context(doc, atEnd));
|
||||
assertNull(exp);
|
||||
|
||||
exp = QPropertyExpansion.create(new Context(doc, atRParen));
|
||||
assertNotNull(exp);
|
||||
assertNull(exp.getCurrIdentifier());
|
||||
assertEquals("modifier", exp.getPrevIdentifier());
|
||||
|
||||
exp = QPropertyExpansion.create(new Context(doc, afterModifier));
|
||||
assertNotNull(exp);
|
||||
assertEquals("modifier", exp.getCurrIdentifier());
|
||||
assertEquals("WRITE", exp.getPrevIdentifier());
|
||||
|
||||
exp = QPropertyExpansion.create(new Context(doc, afterWRITE));
|
||||
assertNotNull(exp);
|
||||
assertEquals("WRITE", exp.getCurrIdentifier());
|
||||
assertEquals("accessor", exp.getPrevIdentifier());
|
||||
|
||||
exp = QPropertyExpansion.create(new Context(doc, inModifier));
|
||||
assertNotNull(exp);
|
||||
assertEquals("modifier", exp.getCurrIdentifier());
|
||||
assertEquals("WRITE", exp.getPrevIdentifier());
|
||||
|
||||
exp = QPropertyExpansion.create(new Context(doc, inWRITE));
|
||||
assertNotNull(exp);
|
||||
assertEquals("WRITE", exp.getCurrIdentifier());
|
||||
assertEquals("accessor", exp.getPrevIdentifier());
|
||||
}
|
||||
|
||||
public void testQPropertyWithoutLeadingWhitespace() throws Exception {
|
||||
|
||||
String decl = "Q_PROPERTY(type name READ accessor )";
|
||||
int atRParen = decl.indexOf(')');
|
||||
IDocument doc = new Document(decl);
|
||||
|
||||
// The expansion should be created even when there is no leading whitesapce in the
|
||||
// expansion parameter.
|
||||
QPropertyExpansion exp = QPropertyExpansion.create(new Context(doc, atRParen));
|
||||
assertNotNull(exp);
|
||||
}
|
||||
|
||||
public void testQPropertyPrefixes() throws Exception {
|
||||
String decl = "Q_PROPERTY( type name READ accessor WRITE )";
|
||||
int len = decl.length();
|
||||
IDocument doc = new Document(decl);
|
||||
|
||||
// The expansion is not applicable when invoked after the closing paren
|
||||
QPropertyExpansion atEnd = QPropertyExpansion.create(new Context(doc, len));
|
||||
assertNull(atEnd);
|
||||
|
||||
QPropertyExpansion inWS = QPropertyExpansion.create(new Context(doc, len - 2));
|
||||
assertNotNull(inWS);
|
||||
assertNull(inWS.getPrefix());
|
||||
assertNull(inWS.getCurrIdentifier());
|
||||
assertEquals("WRITE", inWS.getPrevIdentifier());
|
||||
|
||||
QPropertyExpansion afterWRITE = QPropertyExpansion.create(new Context(doc, len - 3));
|
||||
assertNotNull(afterWRITE);
|
||||
assertEquals("WRITE", afterWRITE.getPrefix());
|
||||
assertEquals("WRITE", afterWRITE.getCurrIdentifier());
|
||||
assertEquals("accessor", afterWRITE.getPrevIdentifier());
|
||||
|
||||
QPropertyExpansion inWRITE_e = QPropertyExpansion.create(new Context(doc, len - 4));
|
||||
assertNotNull(inWRITE_e);
|
||||
assertEquals("WRIT", inWRITE_e.getPrefix());
|
||||
assertEquals("WRITE", inWRITE_e.getCurrIdentifier());
|
||||
assertEquals("accessor", inWRITE_e.getPrevIdentifier());
|
||||
|
||||
QPropertyExpansion inWRITE_b = QPropertyExpansion.create(new Context(doc, len - 6));
|
||||
assertNotNull(inWRITE_b);
|
||||
assertEquals("WR", inWRITE_b.getPrefix());
|
||||
assertEquals("WRITE", inWRITE_b.getCurrIdentifier());
|
||||
assertEquals("accessor", inWRITE_b.getPrevIdentifier());
|
||||
|
||||
QPropertyExpansion startWRITE = QPropertyExpansion.create(new Context(doc, len - 8));
|
||||
assertNotNull(startWRITE);
|
||||
assertNull(startWRITE.getPrefix());
|
||||
assertNull(startWRITE.getCurrIdentifier());
|
||||
assertEquals("accessor", startWRITE.getPrevIdentifier());
|
||||
}
|
||||
|
||||
// This implements only the parts that are known to be used in the QPropertyExpansion
|
||||
// implementation.
|
||||
private static class Context implements ICEditorContentAssistInvocationContext {
|
||||
|
||||
private final IDocument doc;
|
||||
private final int contextOffset;
|
||||
private final int invokedOffset;
|
||||
|
||||
public Context(IDocument doc, int invoked) {
|
||||
this.doc = doc;
|
||||
this.contextOffset = doc.get().indexOf('(') + 1;
|
||||
this.invokedOffset = invoked;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInvocationOffset() {
|
||||
return invokedOffset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getContextInformationOffset() {
|
||||
return contextOffset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IDocument getDocument() {
|
||||
return doc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isContextInformationStyle() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ITextViewer getViewer() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ITranslationUnit getTranslationUnit() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ICProject getProject() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getParseOffset() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IEditorPart getEditor() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IASTCompletionNode getCompletionNode() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence computeIdentifierPrefix() throws BadLocationException {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
|
@ -12,6 +12,10 @@ Require-Bundle: org.eclipse.ui,
|
|||
org.eclipse.cdt.core,
|
||||
org.eclipse.cdt.qt.core;bundle-version="[1.1.0,2.0.0)",
|
||||
org.eclipse.jface.text,
|
||||
org.eclipse.core.resources
|
||||
org.eclipse.core.resources,
|
||||
org.eclipse.ui.workbench.texteditor,
|
||||
org.eclipse.ui.editors
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Export-Package: org.eclipse.cdt.internal.qt.ui.assist;x-friends:="org.eclipse.cdt.qt.tests",
|
||||
org.eclipse.cdt.qt.ui
|
||||
|
|
|
@ -0,0 +1,326 @@
|
|||
/*
|
||||
* Copyright (c) 2013 QNX Software Systems and others.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.cdt.internal.qt.ui;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.cdt.core.CCorePlugin;
|
||||
import org.eclipse.cdt.core.dom.ast.DOMException;
|
||||
import org.eclipse.cdt.core.dom.ast.IASTCompletionContext;
|
||||
import org.eclipse.cdt.core.dom.ast.IASTExpression;
|
||||
import org.eclipse.cdt.core.dom.ast.IASTFunctionCallExpression;
|
||||
import org.eclipse.cdt.core.dom.ast.IASTIdExpression;
|
||||
import org.eclipse.cdt.core.dom.ast.IASTInitializerClause;
|
||||
import org.eclipse.cdt.core.dom.ast.IASTName;
|
||||
import org.eclipse.cdt.core.dom.ast.IASTNode;
|
||||
import org.eclipse.cdt.core.dom.ast.IBinding;
|
||||
import org.eclipse.cdt.core.dom.ast.IType;
|
||||
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTFieldReference;
|
||||
import org.eclipse.cdt.core.dom.ast.cpp.ICPPClassType;
|
||||
import org.eclipse.cdt.internal.qt.core.ASTUtil;
|
||||
import org.eclipse.cdt.internal.qt.core.QtFunctionCallUtil;
|
||||
import org.eclipse.cdt.internal.ui.text.contentassist.CCompletionProposal;
|
||||
import org.eclipse.cdt.internal.ui.text.contentassist.RelevanceConstants;
|
||||
import org.eclipse.cdt.qt.core.QtKeywords;
|
||||
import org.eclipse.cdt.qt.core.index.IQMethod;
|
||||
import org.eclipse.cdt.qt.core.index.IQObject;
|
||||
import org.eclipse.cdt.qt.core.index.QtIndex;
|
||||
import org.eclipse.cdt.qt.ui.QtUIPlugin;
|
||||
import org.eclipse.cdt.ui.text.contentassist.ICEditorContentAssistInvocationContext;
|
||||
import org.eclipse.jface.text.BadLocationException;
|
||||
import org.eclipse.jface.text.contentassist.ICompletionProposal;
|
||||
|
||||
@SuppressWarnings("restriction")
|
||||
public class QObjectConnectCompletion {
|
||||
// These suggestions are populated from the index, so the case is always an exact match.
|
||||
// Secondly, these suggestions should appear above generic variable and method matches, since
|
||||
// have based the calculation on the exact function that is being called.
|
||||
|
||||
private static final int MACRO_RELEVANCE
|
||||
= RelevanceConstants.CASE_MATCH_RELEVANCE + RelevanceConstants.LOCAL_VARIABLE_TYPE_RELEVANCE + 2;
|
||||
private static final int MACRO_PARAM_RELEVANCE
|
||||
= RelevanceConstants.CASE_MATCH_RELEVANCE + RelevanceConstants.METHOD_TYPE_RELEVANCE + 1;
|
||||
|
||||
/**
|
||||
* Different suggestions should be proposed for each parameter of the QObject::connect
|
||||
* function call. The 'sender' parameter should suggest SIGNAL, but 'member' can be
|
||||
* either SLOT or SIGNAL.
|
||||
*/
|
||||
public enum Param {
|
||||
Signal,
|
||||
Member,
|
||||
Generic
|
||||
}
|
||||
|
||||
private final Param param;
|
||||
private final Data data;
|
||||
|
||||
public QObjectConnectCompletion(Param param) {
|
||||
this.param = param;
|
||||
this.data = null;
|
||||
}
|
||||
|
||||
public QObjectConnectCompletion(String replacement) {
|
||||
this.param = Param.Generic;
|
||||
this.data = new Data(replacement);
|
||||
}
|
||||
|
||||
/**
|
||||
* The data used to produce the completions varies depending on the role of the
|
||||
* parameter that is being completed.
|
||||
*/
|
||||
private static class Data
|
||||
{
|
||||
public final String replacement;
|
||||
public final String display;
|
||||
public final int cursorOffset;
|
||||
|
||||
public static final Data SIGNAL = new Data("SIGNAL()", "SIGNAL(a)", -1);
|
||||
public static final Data SLOT = new Data("SLOT()", "SLOT(a)", -1);
|
||||
|
||||
public Data(String replacement) {
|
||||
this(replacement, replacement, 0);
|
||||
}
|
||||
|
||||
public Data(String replacement, String display, int cursorOffset) {
|
||||
this.replacement = replacement;
|
||||
this.display = display;
|
||||
this.cursorOffset = cursorOffset;
|
||||
}
|
||||
|
||||
public ICompletionProposal createProposal(ICEditorContentAssistInvocationContext context, int relevance) {
|
||||
int repLength = replacement.length();
|
||||
int repOffset = context.getInvocationOffset();
|
||||
CCompletionProposal p
|
||||
= new CCompletionProposal(replacement, repOffset, repLength, QtUIPlugin.getQtLogo(), display, relevance, context.getViewer());
|
||||
p.setCursorPosition(repLength + cursorOffset);
|
||||
return p;
|
||||
}
|
||||
}
|
||||
|
||||
private static void addProposal(Collection<ICompletionProposal> proposals, ICEditorContentAssistInvocationContext context, Data data, int relevance) {
|
||||
if (data == null)
|
||||
return;
|
||||
|
||||
ICompletionProposal proposal = data.createProposal(context, relevance);
|
||||
if (proposal != null)
|
||||
proposals.add(proposal);
|
||||
}
|
||||
|
||||
private void addProposals(Collection<ICompletionProposal> proposals, ICEditorContentAssistInvocationContext context) {
|
||||
|
||||
if (data != null)
|
||||
addProposal(proposals, context, data, MACRO_PARAM_RELEVANCE);
|
||||
else
|
||||
switch(param) {
|
||||
case Signal:
|
||||
addProposal(proposals, context, Data.SIGNAL, MACRO_RELEVANCE);
|
||||
break;
|
||||
case Member:
|
||||
addProposal(proposals, context, Data.SLOT, MACRO_RELEVANCE);
|
||||
addProposal(proposals, context, Data.SIGNAL, MACRO_RELEVANCE - 1);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean is_QObject_connect(ICEditorContentAssistInvocationContext context, IASTCompletionContext astContext, IASTName name) {
|
||||
|
||||
// Bug332201: Qt content assist should always be applied to the most specific part of
|
||||
// the target name.
|
||||
IBinding[] funcBindings = astContext.findBindings(name.getLastName(), !context.isContextInformationStyle());
|
||||
for (IBinding funcBinding : funcBindings)
|
||||
if (QtKeywords.is_QObject_connect(funcBinding))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Copied from org.eclipse.cdt.internal.ui.text.CParameterListValidator
|
||||
private static int indexOfClosingPeer(String code, char left, char right, int pos) {
|
||||
int level = 0;
|
||||
final int length = code.length();
|
||||
while (pos < length) {
|
||||
char ch = code.charAt(pos);
|
||||
if (ch == left) {
|
||||
++level;
|
||||
} else if (ch == right) {
|
||||
if (--level == 0) {
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
++pos;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Copied from org.eclipse.cdt.internal.ui.text.CParameterListValidator
|
||||
private static int[] computeCommaPositions(String code) {
|
||||
final int length = code.length();
|
||||
int pos = 0;
|
||||
List<Integer> positions = new ArrayList<Integer>();
|
||||
positions.add(new Integer(-1));
|
||||
while (pos < length && pos != -1) {
|
||||
char ch = code.charAt(pos);
|
||||
switch (ch) {
|
||||
case ',':
|
||||
positions.add(new Integer(pos));
|
||||
break;
|
||||
case '(':
|
||||
pos = indexOfClosingPeer(code, '(', ')', pos);
|
||||
break;
|
||||
case '<':
|
||||
pos = indexOfClosingPeer(code, '<', '>', pos);
|
||||
break;
|
||||
case '[':
|
||||
pos = indexOfClosingPeer(code, '[', ']', pos);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (pos != -1)
|
||||
pos++;
|
||||
}
|
||||
positions.add(new Integer(length));
|
||||
|
||||
int[] fields = new int[positions.size()];
|
||||
for (int i = 0; i < fields.length; i++)
|
||||
fields[i] = positions.get(i).intValue();
|
||||
return fields;
|
||||
}
|
||||
|
||||
private static Collection<QObjectConnectCompletion> getCompletionsFor(IASTNode targetNode, IASTInitializerClause arg) {
|
||||
|
||||
IType targetType = ASTUtil.getBaseType(targetNode);
|
||||
if (!(targetType instanceof ICPPClassType))
|
||||
return null;
|
||||
ICPPClassType cls = (ICPPClassType) targetType;
|
||||
|
||||
QtIndex qtIndex = QtIndex.getIndex(ASTUtil.getProject(targetNode));
|
||||
if (qtIndex == null)
|
||||
return null;
|
||||
|
||||
IQObject qobj = null;
|
||||
try {
|
||||
qobj = qtIndex.findQObject(cls.getQualifiedName());
|
||||
} catch(DOMException e) {
|
||||
CCorePlugin.log(e);
|
||||
}
|
||||
|
||||
// QtIndex.findQObject will return null in some cases, e.g., when the parameter is null
|
||||
if (qobj == null)
|
||||
return null;
|
||||
|
||||
Collection<QObjectConnectCompletion> completions = new ArrayList<QObjectConnectCompletion>();
|
||||
String raw = arg.getRawSignature();
|
||||
if (raw.startsWith(QtKeywords.SIGNAL))
|
||||
for(IQMethod method : qobj.getSignals().withoutOverrides())
|
||||
for(String signature : method.getSignatures())
|
||||
completions.add(new QObjectConnectCompletion(signature));
|
||||
if (raw.startsWith(QtKeywords.SLOT))
|
||||
for(IQMethod method : qobj.getSlots().withoutOverrides())
|
||||
for(String signature : method.getSignatures())
|
||||
completions.add(new QObjectConnectCompletion(signature));
|
||||
return completions;
|
||||
}
|
||||
|
||||
public static Collection<QObjectConnectCompletion> getConnectProposals(
|
||||
ICEditorContentAssistInvocationContext context, IASTName name, IASTCompletionContext astContext, IASTNode astNode) {
|
||||
|
||||
if (QtFunctionCallUtil.isQObjectFunctionCall(astContext, !context.isContextInformationStyle(), name)) {
|
||||
int parseOffset = context.getParseOffset();
|
||||
int invocationOffset = context.getInvocationOffset();
|
||||
|
||||
String unparsed = "";
|
||||
try {
|
||||
unparsed = context.getDocument().get(parseOffset, invocationOffset - parseOffset);
|
||||
} catch (BadLocationException e) {
|
||||
CCorePlugin.log(e);
|
||||
}
|
||||
|
||||
if (unparsed.length() > 0 && unparsed.charAt(0) == '(')
|
||||
unparsed = unparsed.substring(1);
|
||||
|
||||
int[] commas = computeCommaPositions(unparsed);
|
||||
switch (commas.length) {
|
||||
case 2:
|
||||
case 3:
|
||||
// Across all possible connect/disconnect overloads, the first and second arguments
|
||||
// can be SIGNAL expansion.
|
||||
return Collections.singletonList(new QObjectConnectCompletion(QObjectConnectCompletion.Param.Signal));
|
||||
case 4:
|
||||
case 5:
|
||||
// Across all possible connect/disconnect overloads, the first and second arguments
|
||||
// can be SIGNAL or SLOT expansions.
|
||||
return Collections.singletonList(new QObjectConnectCompletion(QObjectConnectCompletion.Param.Member));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if (astNode.getPropertyInParent() == IASTFunctionCallExpression.ARGUMENT) {
|
||||
IASTNode parent = astNode.getParent();
|
||||
if (!(parent instanceof IASTFunctionCallExpression))
|
||||
return null;
|
||||
|
||||
// NOTE: QtConnectFunctionCall cannot be used here because that class expects a
|
||||
// valid expression. During content assist the function is still being
|
||||
// created.
|
||||
|
||||
IASTFunctionCallExpression call = (IASTFunctionCallExpression) parent;
|
||||
IASTExpression nameExpr = call.getFunctionNameExpression();
|
||||
IASTName funcName = null;
|
||||
if (nameExpr instanceof IASTIdExpression)
|
||||
funcName = ((IASTIdExpression) nameExpr).getName();
|
||||
else if (nameExpr instanceof ICPPASTFieldReference)
|
||||
funcName = ((ICPPASTFieldReference) nameExpr).getFieldName();
|
||||
|
||||
// If this isn't a QObject::connect or QObject::disconnect function call then
|
||||
// look no further.
|
||||
if (!QtFunctionCallUtil.isQObjectFunctionCall(astContext, !context.isContextInformationStyle(), funcName))
|
||||
return null;
|
||||
|
||||
// In a content assist context the argument that is currently being entered is
|
||||
// last in the function call.
|
||||
IASTInitializerClause[] args = call.getArguments();
|
||||
if (args == null
|
||||
|| args.length < 0)
|
||||
return null;
|
||||
int argIndex = args.length - 1;
|
||||
|
||||
// Find the type node that is used for this expansion.
|
||||
IASTNode typeNode = QtFunctionCallUtil.getTypeNode(call, args, argIndex);
|
||||
if (typeNode == null)
|
||||
return null;
|
||||
|
||||
// Returns completions for the given expansion using the given type as the
|
||||
// source for Qt methods.
|
||||
return getCompletionsFor(typeNode, args[argIndex]);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Collection<ICompletionProposal> getProposals(
|
||||
ICEditorContentAssistInvocationContext context, IASTName name, IASTCompletionContext astContext, IASTNode astNode) {
|
||||
|
||||
Collection<QObjectConnectCompletion> qtProposals = getConnectProposals(context, name, astContext, astNode);
|
||||
if (qtProposals == null
|
||||
|| qtProposals.isEmpty())
|
||||
return null;
|
||||
|
||||
Collection<ICompletionProposal> proposals = new ArrayList<ICompletionProposal>();
|
||||
for (QObjectConnectCompletion qtProposal : qtProposals)
|
||||
qtProposal.addProposals(proposals, context);
|
||||
return proposals;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* Copyright (c) 2013 QNX Software Systems and others.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.cdt.internal.qt.ui;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.eclipse.cdt.core.dom.ast.IASTName;
|
||||
import org.eclipse.cdt.internal.corext.template.c.CContextType;
|
||||
import org.eclipse.cdt.internal.corext.template.c.TranslationUnitContext;
|
||||
import org.eclipse.cdt.internal.corext.template.c.TranslationUnitContextType;
|
||||
import org.eclipse.cdt.internal.ui.text.template.TemplateEngine.CTemplateProposal;
|
||||
import org.eclipse.cdt.qt.ui.QtUIPlugin;
|
||||
import org.eclipse.cdt.ui.text.contentassist.ICEditorContentAssistInvocationContext;
|
||||
import org.eclipse.jface.text.IRegion;
|
||||
import org.eclipse.jface.text.ITextSelection;
|
||||
import org.eclipse.jface.text.Position;
|
||||
import org.eclipse.jface.text.Region;
|
||||
import org.eclipse.jface.text.contentassist.ICompletionProposal;
|
||||
import org.eclipse.jface.text.templates.Template;
|
||||
import org.eclipse.jface.viewers.ISelection;
|
||||
import org.eclipse.jface.viewers.ISelectionProvider;
|
||||
import org.eclipse.ui.texteditor.ITextEditor;
|
||||
|
||||
@SuppressWarnings("restriction")
|
||||
public class QObjectDeclarationCompletion {
|
||||
|
||||
private static final String TEMPLATE = "class ${name} : public ${QObject}\n{\nQ_OBJECT\n\n${cursor}\n};";
|
||||
|
||||
private final static TranslationUnitContextType context;
|
||||
static {
|
||||
context = new CContextType();
|
||||
context.setId(CContextType.ID);
|
||||
}
|
||||
|
||||
public static Collection<ICompletionProposal> getProposals(ICEditorContentAssistInvocationContext ctx, IASTName name) {
|
||||
|
||||
String token = name.getLastName().toString();
|
||||
if (token.isEmpty()
|
||||
|| !"class".startsWith(token))
|
||||
return null;
|
||||
|
||||
Position position = getPosition(ctx);
|
||||
if (position == null)
|
||||
return null;
|
||||
|
||||
TranslationUnitContext tuCtx = context.createContext(ctx.getDocument(), position, ctx.getTranslationUnit());
|
||||
IRegion region = new Region(position.getOffset(), position.getLength());
|
||||
|
||||
Template template = new Template( "class", "QObject declaration", CContextType.ID, TEMPLATE, true);
|
||||
return Collections.<ICompletionProposal>singletonList(new CTemplateProposal(template, tuCtx, region, QtUIPlugin.getQtLogo()));
|
||||
}
|
||||
|
||||
private static Position getPosition(ICEditorContentAssistInvocationContext context) {
|
||||
ITextEditor textEditor = (ITextEditor) context.getEditor().getAdapter(ITextEditor.class);
|
||||
if (textEditor == null)
|
||||
return null;
|
||||
|
||||
ISelectionProvider selectionProvider = textEditor.getSelectionProvider();
|
||||
if (selectionProvider == null)
|
||||
return null;
|
||||
|
||||
ISelection selection = selectionProvider.getSelection();
|
||||
if (!(selection instanceof ITextSelection))
|
||||
return null;
|
||||
|
||||
ITextSelection text = (ITextSelection) selection;
|
||||
return new Position(text.getOffset(), text.getLength());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright (c) 2013 QNX Software Systems and others.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.cdt.internal.qt.ui;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.eclipse.cdt.core.dom.ast.IASTCompletionContext;
|
||||
import org.eclipse.cdt.core.dom.ast.IASTName;
|
||||
import org.eclipse.cdt.core.dom.ast.IASTNode;
|
||||
import org.eclipse.cdt.internal.corext.template.c.CContextType;
|
||||
import org.eclipse.cdt.internal.qt.ui.assist.QPropertyExpansion;
|
||||
import org.eclipse.cdt.internal.qt.ui.assist.QtProposalContext;
|
||||
import org.eclipse.cdt.internal.qt.ui.assist.QtTemplateProposal;
|
||||
import org.eclipse.cdt.qt.core.QtKeywords;
|
||||
import org.eclipse.cdt.qt.ui.QtUIPlugin;
|
||||
import org.eclipse.cdt.ui.text.contentassist.ICEditorContentAssistInvocationContext;
|
||||
import org.eclipse.jface.text.Region;
|
||||
import org.eclipse.jface.text.contentassist.ICompletionProposal;
|
||||
import org.eclipse.jface.text.templates.Template;
|
||||
import org.eclipse.jface.text.templates.TemplateContextType;
|
||||
|
||||
@SuppressWarnings("restriction")
|
||||
public class QPropertyCompletion {
|
||||
|
||||
private static final String CONTEXT_ID = QtUIPlugin.PLUGIN_ID + ".proposal.Q_PROPERTY";
|
||||
|
||||
private static final Template QPropertyTemplate
|
||||
= new Template("Q_PROPERTY", "Q_PROPERTY declaration", CONTEXT_ID, "Q_PROPERTY( ${type} ${name} READ ${accessor} ${cursor} )", true);
|
||||
|
||||
public static Collection<ICompletionProposal> getAttributeProposals(ICEditorContentAssistInvocationContext context) {
|
||||
QPropertyExpansion expansion = QPropertyExpansion.create(context);
|
||||
return expansion == null
|
||||
? Collections.<ICompletionProposal>emptyList()
|
||||
: expansion.getProposals(CONTEXT_ID, context);
|
||||
}
|
||||
|
||||
public static Collection<ICompletionProposal> getProposals(
|
||||
ICEditorContentAssistInvocationContext context, IASTName name, IASTCompletionContext astContext, IASTNode astNode) {
|
||||
|
||||
String token = name.getLastName().toString();
|
||||
if (token.isEmpty()
|
||||
|| !QtKeywords.Q_PROPERTY.startsWith(token))
|
||||
return Collections.emptyList();
|
||||
|
||||
TemplateContextType ctxType = new CContextType();
|
||||
ctxType.setId(CONTEXT_ID);
|
||||
|
||||
QtProposalContext templateCtx = new QtProposalContext(context, ctxType);
|
||||
Region region = new Region(templateCtx.getCompletionOffset(), templateCtx.getCompletionLength());
|
||||
|
||||
return Collections.<ICompletionProposal>singletonList(new QtTemplateProposal(QPropertyTemplate, templateCtx, region));
|
||||
}
|
||||
}
|
|
@ -5,56 +5,59 @@
|
|||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
|
||||
package org.eclipse.cdt.internal.qt.ui;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.cdt.core.CCorePlugin;
|
||||
import org.eclipse.cdt.core.dom.ast.ASTTypeUtil;
|
||||
import org.eclipse.cdt.core.dom.ast.IASTCompletionContext;
|
||||
import org.eclipse.cdt.core.dom.ast.IASTCompletionNode;
|
||||
import org.eclipse.cdt.core.dom.ast.IASTExpression;
|
||||
import org.eclipse.cdt.core.dom.ast.IASTFunctionCallExpression;
|
||||
import org.eclipse.cdt.core.dom.ast.IASTIdExpression;
|
||||
import org.eclipse.cdt.core.dom.ast.IASTInitializerClause;
|
||||
import org.eclipse.cdt.core.dom.ast.IASTName;
|
||||
import org.eclipse.cdt.core.dom.ast.IASTNode;
|
||||
import org.eclipse.cdt.core.dom.ast.IBinding;
|
||||
import org.eclipse.cdt.core.dom.ast.IPointerType;
|
||||
import org.eclipse.cdt.core.dom.ast.IType;
|
||||
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTInitializerClause;
|
||||
import org.eclipse.cdt.core.dom.ast.cpp.ICPPClassType;
|
||||
import org.eclipse.cdt.core.dom.ast.cpp.ICPPFunction;
|
||||
import org.eclipse.cdt.core.dom.ast.cpp.ICPPMethod;
|
||||
import org.eclipse.cdt.core.dom.ast.cpp.ICPPParameter;
|
||||
import org.eclipse.cdt.core.dom.ast.tag.ITag;
|
||||
import org.eclipse.cdt.core.dom.ast.tag.ITagReader;
|
||||
import org.eclipse.cdt.core.model.ICProject;
|
||||
import org.eclipse.cdt.core.model.ITranslationUnit;
|
||||
import org.eclipse.cdt.internal.core.dom.parser.cpp.ICPPEvaluation;
|
||||
import org.eclipse.cdt.internal.ui.text.contentassist.CCompletionProposal;
|
||||
import org.eclipse.cdt.internal.ui.text.contentassist.CContentAssistInvocationContext;
|
||||
import org.eclipse.cdt.internal.ui.text.contentassist.ParsingBasedProposalComputer;
|
||||
import org.eclipse.cdt.internal.ui.text.contentassist.RelevanceConstants;
|
||||
import org.eclipse.cdt.qt.core.QtKeywords;
|
||||
import org.eclipse.cdt.qt.core.QtNature;
|
||||
import org.eclipse.cdt.qt.core.QtPlugin;
|
||||
import org.eclipse.cdt.qt.ui.QtUIPlugin;
|
||||
import org.eclipse.cdt.ui.CUIPlugin;
|
||||
import org.eclipse.cdt.ui.text.contentassist.ContentAssistInvocationContext;
|
||||
import org.eclipse.cdt.ui.text.contentassist.ICEditorContentAssistInvocationContext;
|
||||
import org.eclipse.core.resources.IProject;
|
||||
import org.eclipse.core.runtime.CoreException;
|
||||
import org.eclipse.jface.text.BadLocationException;
|
||||
import org.eclipse.core.runtime.IProgressMonitor;
|
||||
import org.eclipse.jface.text.contentassist.ICompletionProposal;
|
||||
|
||||
@SuppressWarnings("restriction")
|
||||
public class QtCompletionProposalComputer extends ParsingBasedProposalComputer {
|
||||
|
||||
@Override
|
||||
public List<ICompletionProposal> computeCompletionProposals(
|
||||
ContentAssistInvocationContext context, IProgressMonitor monitor) {
|
||||
// this is overridden in order to find proposals when the completion node is null
|
||||
try {
|
||||
if (context instanceof CContentAssistInvocationContext) {
|
||||
CContentAssistInvocationContext cContext = (CContentAssistInvocationContext) context;
|
||||
|
||||
String prefix = null;
|
||||
IASTCompletionNode completionNode = cContext.getCompletionNode();
|
||||
// the parent implementation gives up when this condition is false
|
||||
if (completionNode != null)
|
||||
prefix = completionNode.getPrefix();
|
||||
|
||||
if (prefix == null)
|
||||
prefix = cContext.computeIdentifierPrefix().toString();
|
||||
|
||||
return computeCompletionProposals(cContext, completionNode, prefix);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
QtUIPlugin.log(e);
|
||||
}
|
||||
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
private boolean isApplicable(ICEditorContentAssistInvocationContext context) {
|
||||
ITranslationUnit tu = context.getTranslationUnit();
|
||||
if (tu == null)
|
||||
|
@ -68,324 +71,52 @@ public class QtCompletionProposalComputer extends ParsingBasedProposalComputer {
|
|||
if (project == null)
|
||||
return false;
|
||||
|
||||
try {
|
||||
return project.hasNature(QtNature.ID);
|
||||
} catch (CoreException e) {
|
||||
CUIPlugin.log(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean is_QObject_connect(
|
||||
ICEditorContentAssistInvocationContext context,
|
||||
IASTCompletionContext astContext, IASTName name) {
|
||||
IASTName connectName = name.getLastName();
|
||||
if (!QtKeywords.CONNECT.equals(new String(connectName.getSimpleID())))
|
||||
return false;
|
||||
|
||||
IBinding[] funcBindings = astContext.findBindings(connectName,
|
||||
!context.isContextInformationStyle());
|
||||
for (IBinding funcBinding : funcBindings)
|
||||
if (funcBinding instanceof ICPPFunction) {
|
||||
IBinding ownerBinding = ((ICPPFunction) funcBinding).getOwner();
|
||||
if (ownerBinding != null
|
||||
&& QtKeywords.QOBJECT.equals(ownerBinding.getName()))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static class Completion {
|
||||
private final String replacement;
|
||||
private final String display;
|
||||
private final int cursorOffset;
|
||||
|
||||
public static final Completion SIGNAL = new Completion("SIGNAL()",
|
||||
"SIGNAL(a)", -1);
|
||||
public static final Completion SLOT = new Completion("SLOT()",
|
||||
"SLOT(a)", -1);
|
||||
|
||||
public Completion(String replacement) {
|
||||
this(replacement, replacement, 0);
|
||||
}
|
||||
|
||||
public Completion(String replacement, String display, int cursorOffset) {
|
||||
this.replacement = replacement;
|
||||
this.display = display;
|
||||
this.cursorOffset = cursorOffset;
|
||||
}
|
||||
|
||||
public ICompletionProposal createProposal(
|
||||
ICEditorContentAssistInvocationContext context) {
|
||||
int repLength = replacement.length();
|
||||
int repOffset = context.getInvocationOffset();
|
||||
CCompletionProposal p = new CCompletionProposal(replacement,
|
||||
repOffset, repLength, null, display,
|
||||
RelevanceConstants.DEFAULT_TYPE_RELEVANCE,
|
||||
context.getViewer());
|
||||
p.setCursorPosition(repLength + cursorOffset);
|
||||
return p;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (replacement == null)
|
||||
return super.toString();
|
||||
return replacement + '@' + cursorOffset;
|
||||
}
|
||||
}
|
||||
|
||||
private static interface MethodFilter {
|
||||
public boolean keep(ICPPMethod method);
|
||||
|
||||
public static class Qt {
|
||||
public static final MethodFilter Signal = new MethodFilter() {
|
||||
@Override
|
||||
public boolean keep(ICPPMethod method) {
|
||||
ITagReader tagReader = CCorePlugin.getTagService()
|
||||
.findTagReader(method);
|
||||
if (tagReader == null)
|
||||
return false;
|
||||
|
||||
ITag tag = tagReader.getTag(QtPlugin.SIGNAL_SLOT_TAGGER_ID);
|
||||
if (tag == null)
|
||||
return false;
|
||||
|
||||
int result = tag.getByte(0);
|
||||
return result != ITag.FAIL
|
||||
&& ((result & QtPlugin.SignalSlot_Mask_signal) == QtPlugin.SignalSlot_Mask_signal);
|
||||
}
|
||||
};
|
||||
|
||||
public static final MethodFilter Slot = new MethodFilter() {
|
||||
@Override
|
||||
public boolean keep(ICPPMethod method) {
|
||||
ITagReader tagReader = CCorePlugin.getTagService()
|
||||
.findTagReader(method);
|
||||
if (tagReader == null)
|
||||
return false;
|
||||
|
||||
ITag tag = tagReader.getTag(QtPlugin.SIGNAL_SLOT_TAGGER_ID);
|
||||
if (tag == null)
|
||||
return false;
|
||||
|
||||
int result = tag.getByte(0);
|
||||
return result != ITag.FAIL
|
||||
&& ((result & QtPlugin.SignalSlot_Mask_slot) == QtPlugin.SignalSlot_Mask_slot);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private static Iterable<ICPPMethod> filterMethods(final ICPPClassType cls,
|
||||
final MethodFilter filter) {
|
||||
return new Iterable<ICPPMethod>() {
|
||||
@Override
|
||||
public Iterator<ICPPMethod> iterator() {
|
||||
return new Iterator<ICPPMethod>() {
|
||||
private int index = 0;
|
||||
private final ICPPMethod[] methods = cls.getMethods();
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
for (; index < methods.length; ++index)
|
||||
if (filter.keep(methods[index]))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ICPPMethod next() {
|
||||
return methods[index++];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static String getSignature(ICPPMethod method) {
|
||||
StringBuilder signature = new StringBuilder();
|
||||
|
||||
signature.append(method.getName());
|
||||
signature.append('(');
|
||||
boolean first = true;
|
||||
for (ICPPParameter param : method.getParameters()) {
|
||||
if (first)
|
||||
first = false;
|
||||
else
|
||||
signature.append(", ");
|
||||
signature.append(ASTTypeUtil.getType(param.getType()));
|
||||
}
|
||||
|
||||
signature.append(')');
|
||||
return signature.toString();
|
||||
}
|
||||
|
||||
private static void addCompletionsFor(Collection<Completion> completions,
|
||||
IASTInitializerClause init, MethodFilter filter) {
|
||||
if (!(init instanceof ICPPASTInitializerClause))
|
||||
return;
|
||||
|
||||
ICPPEvaluation eval = ((ICPPASTInitializerClause) init).getEvaluation();
|
||||
if (eval == null)
|
||||
return;
|
||||
|
||||
IType type = eval.getTypeOrFunctionSet(init);
|
||||
while (type instanceof IPointerType)
|
||||
type = ((IPointerType) type).getType();
|
||||
|
||||
if (type instanceof ICPPClassType)
|
||||
for (ICPPMethod signal : filterMethods((ICPPClassType) type, filter))
|
||||
completions.add(new Completion(getSignature(signal)));
|
||||
}
|
||||
|
||||
// Copied from org.eclipse.cdt.internal.ui.text.CParameterListValidator
|
||||
private static int indexOfClosingPeer(String code, char left, char right,
|
||||
int pos) {
|
||||
int level = 0;
|
||||
final int length = code.length();
|
||||
while (pos < length) {
|
||||
char ch = code.charAt(pos);
|
||||
if (ch == left) {
|
||||
++level;
|
||||
} else if (ch == right) {
|
||||
if (--level == 0) {
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
++pos;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Copied from org.eclipse.cdt.internal.ui.text.CParameterListValidator
|
||||
private static int[] computeCommaPositions(String code) {
|
||||
final int length = code.length();
|
||||
int pos = 0;
|
||||
List<Integer> positions = new ArrayList<Integer>();
|
||||
positions.add(new Integer(-1));
|
||||
while (pos < length && pos != -1) {
|
||||
char ch = code.charAt(pos);
|
||||
switch (ch) {
|
||||
case ',':
|
||||
positions.add(new Integer(pos));
|
||||
break;
|
||||
case '(':
|
||||
pos = indexOfClosingPeer(code, '(', ')', pos);
|
||||
break;
|
||||
case '<':
|
||||
pos = indexOfClosingPeer(code, '<', '>', pos);
|
||||
break;
|
||||
case '[':
|
||||
pos = indexOfClosingPeer(code, '[', ']', pos);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (pos != -1)
|
||||
pos++;
|
||||
}
|
||||
positions.add(new Integer(length));
|
||||
|
||||
int[] fields = new int[positions.size()];
|
||||
for (int i = 0; i < fields.length; i++)
|
||||
fields[i] = positions.get(i).intValue();
|
||||
return fields;
|
||||
}
|
||||
|
||||
private void addConnectParameterCompletions(
|
||||
List<ICompletionProposal> proposals,
|
||||
ICEditorContentAssistInvocationContext context,
|
||||
IASTCompletionNode completionNode, String prefix) {
|
||||
IASTName[] names = completionNode.getNames();
|
||||
List<Completion> completions = new LinkedList<Completion>();
|
||||
|
||||
for (IASTName name : names) {
|
||||
// The node isn't properly hooked up, must have backtracked out of
|
||||
// this node
|
||||
if (name.getTranslationUnit() == null)
|
||||
continue;
|
||||
|
||||
IASTCompletionContext astContext = name.getCompletionContext();
|
||||
if (astContext == null || !(astContext instanceof IASTNode))
|
||||
continue;
|
||||
IASTNode astNode = (IASTNode) astContext;
|
||||
|
||||
if (is_QObject_connect(context, astContext, name)) {
|
||||
int parseOffset = context.getParseOffset();
|
||||
int invocationOffset = context.getInvocationOffset();
|
||||
|
||||
String unparsed = "";
|
||||
try {
|
||||
unparsed = context.getDocument().get(parseOffset,
|
||||
invocationOffset - parseOffset);
|
||||
} catch (BadLocationException e) {
|
||||
QtUIPlugin.log(e);
|
||||
}
|
||||
|
||||
if (unparsed.length() > 0 && unparsed.charAt(0) == '(')
|
||||
unparsed = unparsed.substring(1);
|
||||
|
||||
int[] commas = computeCommaPositions(unparsed);
|
||||
switch (commas.length) {
|
||||
case 3:
|
||||
completions.add(Completion.SIGNAL);
|
||||
break;
|
||||
case 5:
|
||||
completions.add(Completion.SLOT);
|
||||
break;
|
||||
}
|
||||
} else if (astNode.getPropertyInParent() == IASTFunctionCallExpression.ARGUMENT) {
|
||||
IASTNode parent = astNode.getParent();
|
||||
if (!(parent instanceof IASTFunctionCallExpression))
|
||||
continue;
|
||||
IASTFunctionCallExpression call = (IASTFunctionCallExpression) parent;
|
||||
IASTExpression nameExpr = call.getFunctionNameExpression();
|
||||
if (!(nameExpr instanceof IASTIdExpression))
|
||||
continue;
|
||||
IASTIdExpression funcNameIdExpr = (IASTIdExpression) nameExpr;
|
||||
IASTName funcName = funcNameIdExpr.getName();
|
||||
|
||||
if (!is_QObject_connect(context, astContext, funcName))
|
||||
continue;
|
||||
|
||||
IASTInitializerClause[] args = call.getArguments();
|
||||
switch (args.length) {
|
||||
case 2:
|
||||
addCompletionsFor(completions, args[0],
|
||||
MethodFilter.Qt.Signal);
|
||||
break;
|
||||
case 4:
|
||||
addCompletionsFor(completions, args[2],
|
||||
MethodFilter.Qt.Slot);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (Completion completion : completions) {
|
||||
ICompletionProposal proposal = completion.createProposal(context);
|
||||
if (proposal != null)
|
||||
proposals.add(proposal);
|
||||
}
|
||||
}
|
||||
return QtNature.hasNature(project);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<ICompletionProposal> computeCompletionProposals(
|
||||
CContentAssistInvocationContext context,
|
||||
IASTCompletionNode completionNode, String prefix)
|
||||
throws CoreException {
|
||||
CContentAssistInvocationContext context, IASTCompletionNode completionNode, String prefix) throws CoreException {
|
||||
|
||||
// make sure this is a Qt project
|
||||
if (!isApplicable(context))
|
||||
return Collections.emptyList();
|
||||
|
||||
List<ICompletionProposal> proposals = new ArrayList<ICompletionProposal>();
|
||||
addConnectParameterCompletions(proposals, context, completionNode,
|
||||
prefix);
|
||||
return proposals;
|
||||
List<ICompletionProposal> proposals = null;
|
||||
|
||||
if (completionNode != null) {
|
||||
IASTName[] names = completionNode.getNames();
|
||||
for (IASTName name : names) {
|
||||
// the node isn't properly hooked up, must have backtracked out of this node
|
||||
if (name.getTranslationUnit() == null)
|
||||
continue;
|
||||
|
||||
IASTCompletionContext astContext = name.getCompletionContext();
|
||||
if (astContext == null || !(astContext instanceof IASTNode))
|
||||
continue;
|
||||
IASTNode astNode = (IASTNode) astContext;
|
||||
|
||||
proposals = addAll(proposals, QObjectConnectCompletion.getProposals(context, name, astContext, astNode));
|
||||
proposals = addAll(proposals, QObjectDeclarationCompletion.getProposals(context, name));
|
||||
proposals = addAll(proposals, QPropertyCompletion.getProposals(context, name, astContext, astNode));
|
||||
}
|
||||
}
|
||||
|
||||
// Attributes within Q_PROPERTY declarations
|
||||
proposals = addAll(proposals, QPropertyCompletion.getAttributeProposals(context));
|
||||
|
||||
return proposals == null ? Collections.<ICompletionProposal>emptyList() : proposals;
|
||||
}
|
||||
|
||||
private static <T> List<T> addAll(List<T> list, Collection<T> toAdd) {
|
||||
if (toAdd == null
|
||||
|| toAdd.isEmpty())
|
||||
return list;
|
||||
|
||||
if (list == null)
|
||||
return new ArrayList<T>(toAdd);
|
||||
|
||||
list.addAll(toAdd);
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,506 @@
|
|||
/*
|
||||
* Copyright (c) 2013 QNX Software Systems and others.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.cdt.internal.qt.ui.assist;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.cdt.core.CCorePlugin;
|
||||
import org.eclipse.cdt.core.dom.ast.DOMException;
|
||||
import org.eclipse.cdt.core.dom.ast.IASTCompositeTypeSpecifier;
|
||||
import org.eclipse.cdt.core.dom.ast.IASTEqualsInitializer;
|
||||
import org.eclipse.cdt.core.dom.ast.IASTFileLocation;
|
||||
import org.eclipse.cdt.core.dom.ast.IASTInitializer;
|
||||
import org.eclipse.cdt.core.dom.ast.IASTInitializerClause;
|
||||
import org.eclipse.cdt.core.dom.ast.IASTName;
|
||||
import org.eclipse.cdt.core.dom.ast.IASTNode;
|
||||
import org.eclipse.cdt.core.dom.ast.IASTNodeSelector;
|
||||
import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit;
|
||||
import org.eclipse.cdt.core.dom.ast.IBasicType;
|
||||
import org.eclipse.cdt.core.dom.ast.IBinding;
|
||||
import org.eclipse.cdt.core.dom.ast.IType;
|
||||
import org.eclipse.cdt.core.dom.ast.cpp.ICPPClassType;
|
||||
import org.eclipse.cdt.core.dom.ast.cpp.ICPPFunctionType;
|
||||
import org.eclipse.cdt.core.dom.ast.cpp.ICPPMethod;
|
||||
import org.eclipse.cdt.core.dom.ast.cpp.ICPPParameter;
|
||||
import org.eclipse.cdt.core.index.IIndex;
|
||||
import org.eclipse.cdt.core.model.ICProject;
|
||||
import org.eclipse.cdt.core.model.ITranslationUnit;
|
||||
import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPParameter;
|
||||
import org.eclipse.cdt.internal.ui.text.contentassist.CCompletionProposal;
|
||||
import org.eclipse.cdt.qt.core.index.IQMethod;
|
||||
import org.eclipse.cdt.qt.core.index.IQObject;
|
||||
import org.eclipse.cdt.qt.core.index.IQProperty;
|
||||
import org.eclipse.cdt.qt.core.index.QtIndex;
|
||||
import org.eclipse.cdt.qt.ui.QtUIPlugin;
|
||||
import org.eclipse.cdt.ui.text.contentassist.ICEditorContentAssistInvocationContext;
|
||||
import org.eclipse.core.runtime.CoreException;
|
||||
import org.eclipse.jface.text.contentassist.ICompletionProposal;
|
||||
|
||||
/**
|
||||
* An attribute-based proposal depends on the both the attribute (the previous identifier) and the
|
||||
* containing class definition. The class definition is not needed for all attribute types, but
|
||||
* is used to build the list of proposals for attributes like READ, WRITE, etc.
|
||||
*/
|
||||
@SuppressWarnings("restriction")
|
||||
public class QPropertyAttributeProposal {
|
||||
private final int relevance;
|
||||
private final String identifier;
|
||||
private final String display;
|
||||
|
||||
public QPropertyAttributeProposal(String identifier, int relevance) {
|
||||
this(identifier, identifier, relevance);
|
||||
}
|
||||
|
||||
public ICompletionProposal createProposal(String prefix, int offset) {
|
||||
int prefixLen = prefix == null ? 0 : prefix.length();
|
||||
|
||||
String disp = identifier.equals(display) ? display : ( identifier + " - " + display );
|
||||
return new CCompletionProposal(identifier.substring(prefixLen), offset, prefixLen, QtUIPlugin.getQtLogo(), disp, relevance);
|
||||
}
|
||||
|
||||
private QPropertyAttributeProposal(String identifier, String display, int relevance) {
|
||||
this.identifier = identifier;
|
||||
this.display = display;
|
||||
this.relevance = relevance;
|
||||
}
|
||||
|
||||
public String getIdentifier() {
|
||||
return identifier;
|
||||
}
|
||||
|
||||
public static Collection<QPropertyAttributeProposal> buildProposals(IQProperty.Attribute attr, ICEditorContentAssistInvocationContext context, IType type, String name) {
|
||||
switch(attr) {
|
||||
// propose true/false for bool Attributes
|
||||
case DESIGNABLE:
|
||||
case SCRIPTABLE:
|
||||
case STORED:
|
||||
case USER:
|
||||
return Arrays.asList(
|
||||
new QPropertyAttributeProposal("true", IMethodAttribute.BaseRelevance + 11),
|
||||
new QPropertyAttributeProposal("false", IMethodAttribute.BaseRelevance + 10));
|
||||
|
||||
// propose appropriate methods for method-based attributes
|
||||
case READ:
|
||||
case WRITE:
|
||||
case RESET:
|
||||
return getMethodProposals(context, get(attr, type, name));
|
||||
|
||||
// propose appropriate signals for NOTIFY
|
||||
case NOTIFY:
|
||||
return getSignalProposals(context, get(attr, type, name));
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
private static Collection<QPropertyAttributeProposal> getMethodProposals(ICEditorContentAssistInvocationContext context, IMethodAttribute methodAttribute) {
|
||||
|
||||
ICPPClassType cls = getEnclosingClassDefinition(context);
|
||||
if (cls == null)
|
||||
return Collections.emptyList();
|
||||
|
||||
// Return all the methods, including inherited and non-visible ones.
|
||||
ICPPMethod[] methods = cls.getMethods();
|
||||
List<ICPPMethod> filtered = new ArrayList<ICPPMethod>(methods.length);
|
||||
for(ICPPMethod method : methods)
|
||||
if (methodAttribute.keep(method))
|
||||
filtered.add(method);
|
||||
|
||||
// TODO Choose the overload that is the best match -- closest parameter type and fewest
|
||||
// parameters with default values.
|
||||
|
||||
List<QPropertyAttributeProposal> proposals = new ArrayList<QPropertyAttributeProposal>();
|
||||
for(ICPPMethod method : getMethods(context, methodAttribute))
|
||||
proposals.add(new QPropertyAttributeProposal(method.getName(), getDisplay(cls, method), methodAttribute.getRelevance(method)));
|
||||
|
||||
return proposals;
|
||||
}
|
||||
|
||||
private static Collection<QPropertyAttributeProposal> getSignalProposals(ICEditorContentAssistInvocationContext context, IMethodAttribute methodAttribute) {
|
||||
ICPPClassType cls = getEnclosingClassDefinition(context);
|
||||
if (cls == null)
|
||||
return Collections.emptyList();
|
||||
|
||||
ICProject cProject = context.getProject();
|
||||
if (cProject == null)
|
||||
return Collections.emptyList();
|
||||
|
||||
QtIndex qtIndex = QtIndex.getIndex(cProject.getProject());
|
||||
if (qtIndex == null)
|
||||
return Collections.emptyList();
|
||||
|
||||
IQObject qobj = null;
|
||||
try {
|
||||
qobj = qtIndex.findQObject(cls.getQualifiedName());
|
||||
} catch(DOMException e) {
|
||||
QtUIPlugin.log(e);
|
||||
}
|
||||
|
||||
if (qobj == null)
|
||||
return Collections.emptyList();
|
||||
|
||||
List<QPropertyAttributeProposal> proposals = new ArrayList<QPropertyAttributeProposal>();
|
||||
for(IQMethod qMethod : qobj.getSignals().all())
|
||||
proposals.add(new QPropertyAttributeProposal(qMethod.getName(), IMethodAttribute.BaseRelevance));
|
||||
|
||||
return proposals;
|
||||
}
|
||||
|
||||
private static boolean isSameClass(ICPPClassType cls1, ICPPClassType cls2) {
|
||||
|
||||
// IType.isSameType doesn't work in this case. Given an instance of ICPPClassType, cls,
|
||||
// the following returns false:
|
||||
// cls.isSameType( cls.getMethods()[0].getOwner() )
|
||||
//
|
||||
// Instead we check the fully qualified names.
|
||||
|
||||
try {
|
||||
String[] qn1 = cls1.getQualifiedName();
|
||||
String[] qn2 = cls2.getQualifiedName();
|
||||
|
||||
if (qn1.length != qn2.length)
|
||||
return false;
|
||||
|
||||
for(int i = 0; i < qn1.length; ++i)
|
||||
if (!qn1[i].equals(qn2[i]))
|
||||
return false;
|
||||
return true;
|
||||
} catch(DOMException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static String getDisplay(ICPPClassType referenceContext, ICPPMethod method) {
|
||||
|
||||
boolean includeClassname = !isSameClass(referenceContext, method.getClassOwner());
|
||||
|
||||
StringBuilder sig = new StringBuilder();
|
||||
ICPPFunctionType type = method.getType();
|
||||
|
||||
sig.append(type.getReturnType().toString());
|
||||
sig.append(' ');
|
||||
if (includeClassname) {
|
||||
sig.append(method.getOwner().getName());
|
||||
sig.append("::");
|
||||
}
|
||||
sig.append(method.getName());
|
||||
sig.append('(');
|
||||
boolean first = true;
|
||||
for(ICPPParameter param : method.getParameters()) {
|
||||
if (first)
|
||||
first = false;
|
||||
else
|
||||
sig.append(", ");
|
||||
|
||||
String defValue = null;
|
||||
if (param instanceof CPPParameter) {
|
||||
CPPParameter cppParam = (CPPParameter) param;
|
||||
IASTInitializer defaultValue = cppParam.getDefaultValue();
|
||||
if (defaultValue instanceof IASTEqualsInitializer) {
|
||||
IASTInitializerClause clause = ((IASTEqualsInitializer) defaultValue).getInitializerClause();
|
||||
defValue = clause.toString();
|
||||
}
|
||||
}
|
||||
|
||||
sig.append(defValue == null ? param.getType().toString() : defValue);
|
||||
}
|
||||
sig.append(')');
|
||||
return sig.toString();
|
||||
}
|
||||
|
||||
private static interface IMethodAttribute {
|
||||
public boolean keep(ICPPMethod method);
|
||||
|
||||
public static final int BaseRelevance = 2000;
|
||||
public int getRelevance(ICPPMethod method);
|
||||
|
||||
public static final IMethodAttribute Null = new IMethodAttribute() {
|
||||
@Override
|
||||
public boolean keep(ICPPMethod method) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRelevance(ICPPMethod method) {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static IMethodAttribute get(IQProperty.Attribute attr, IType type, String propertyName) {
|
||||
switch(attr) {
|
||||
case READ:
|
||||
return new Read(type, propertyName);
|
||||
case WRITE:
|
||||
return new Write(type, propertyName);
|
||||
case RESET:
|
||||
return new Reset(type, propertyName);
|
||||
default:
|
||||
return IMethodAttribute.Null;
|
||||
}
|
||||
}
|
||||
|
||||
private static class Read implements IMethodAttribute {
|
||||
private final IType type;
|
||||
private final String propertyName;
|
||||
|
||||
public Read(IType type, String propertyName) {
|
||||
this.type = type;
|
||||
this.propertyName = propertyName;
|
||||
}
|
||||
|
||||
// From the Qt docs, http://qt-project.org/doc/qt-4.8/properties.html:
|
||||
// "A READ accessor function is required. It is for reading the property value. Ideally, a
|
||||
// const function is used for this purpose, and it must return either the property's type
|
||||
// or a pointer or reference to that type. e.g., QWidget::focus is a read-only property with
|
||||
// READ function, QWidget::hasFocus().
|
||||
@Override
|
||||
public boolean keep(ICPPMethod method) {
|
||||
// READ must have no params without default values
|
||||
if (method.getParameters().length > 0
|
||||
&& !method.getParameters()[0].hasDefaultValue())
|
||||
return false;
|
||||
|
||||
// Make sure the return type of the method can be assigned to the property's type.
|
||||
IType retType = method.getType().getReturnType();
|
||||
if (!isAssignable(retType, type))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRelevance(ICPPMethod method) {
|
||||
String methodName = method.getName();
|
||||
if (methodName == null)
|
||||
return 0;
|
||||
|
||||
// exact match is the most relevant
|
||||
if (methodName.equals(propertyName))
|
||||
return BaseRelevance + 20;
|
||||
|
||||
// accessor with "get" prefix is the 2nd highest rank
|
||||
if (methodName.equalsIgnoreCase("get" + propertyName))
|
||||
return BaseRelevance + 19;
|
||||
|
||||
// method names that include the property name anywhere are the next
|
||||
// most relevant
|
||||
if (methodName.matches(".*(?i)" + propertyName + ".*"))
|
||||
return BaseRelevance + 18;
|
||||
|
||||
// otherwise return default relevance
|
||||
return 10;
|
||||
}
|
||||
}
|
||||
|
||||
private static class Write implements IMethodAttribute {
|
||||
private final IType type;
|
||||
private final String propertyName;
|
||||
|
||||
public Write(IType type, String propertyName) {
|
||||
this.type = type;
|
||||
this.propertyName = propertyName;
|
||||
}
|
||||
|
||||
// From the Qt docs, http://qt-project.org/doc/qt-4.8/properties.html:
|
||||
// A WRITE accessor function is optional. It is for setting the property value. It must
|
||||
// return void and must take exactly one argument, either of the property's type or a
|
||||
// pointer or reference to that type. e.g., QWidget::enabled has the WRITE function
|
||||
// QWidget::setEnabled(). Read-only properties do not need WRITE functions. e.g., QWidget::focus
|
||||
// has no WRITE function.
|
||||
@Override
|
||||
public boolean keep(ICPPMethod method) {
|
||||
|
||||
// The Qt moc doesn't seem to check that the return type is void, and I'm not sure why it
|
||||
// would need to. This filter doesn't reject non-void methods.
|
||||
|
||||
// WRITE must have at least one parameter and no more than one param without default values
|
||||
if (method.getParameters().length < 1
|
||||
|| (method.getParameters().length > 1
|
||||
&& !method.getParameters()[1].hasDefaultValue()))
|
||||
return false;
|
||||
|
||||
// Make sure the property's type can be assigned to the type of the first parameter
|
||||
IType paramType = method.getParameters()[0].getType();
|
||||
if (!isAssignable(type, paramType))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRelevance(ICPPMethod method) {
|
||||
String methodName = method.getName();
|
||||
if (methodName == null)
|
||||
return 0;
|
||||
|
||||
// exact match is the most relevant
|
||||
if (methodName.equals(propertyName))
|
||||
return BaseRelevance + 20;
|
||||
|
||||
// accessor with "get" prefix is the 2nd highest rank
|
||||
if (methodName.equalsIgnoreCase("set" + propertyName))
|
||||
return BaseRelevance + 19;
|
||||
|
||||
// method names that include the property name anywhere are the next
|
||||
// most relevant
|
||||
if (methodName.matches(".*(?i)" + propertyName + ".*"))
|
||||
return BaseRelevance + 18;
|
||||
|
||||
// otherwise return default relevance
|
||||
return 10;
|
||||
}
|
||||
}
|
||||
|
||||
private static class Reset implements IMethodAttribute {
|
||||
private final IType type;
|
||||
private final String propertyName;
|
||||
|
||||
public Reset(IType type, String propertyName) {
|
||||
this.type = type;
|
||||
this.propertyName = propertyName;
|
||||
}
|
||||
|
||||
// From the Qt docs, http://qt-project.org/doc/qt-4.8/properties.html:
|
||||
// A RESET function is optional. It is for setting the property back to its context
|
||||
// specific default value. e.g., QWidget::cursor has the typical READ and WRITE
|
||||
// functions, QWidget::cursor() and QWidget::setCursor(), and it also has a RESET
|
||||
// function, QWidget::unsetCursor(), since no call to QWidget::setCursor() can mean
|
||||
// reset to the context specific cursor. The RESET function must return void and take
|
||||
// no parameters.
|
||||
@Override
|
||||
public boolean keep(ICPPMethod method) {
|
||||
|
||||
// RESET must have void return type
|
||||
IType retType = method.getType().getReturnType();
|
||||
if (!(retType instanceof IBasicType)
|
||||
|| ((IBasicType) retType).getKind() != IBasicType.Kind.eVoid)
|
||||
return false;
|
||||
|
||||
// RESET must have no parameters
|
||||
if (method.getParameters().length > 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRelevance(ICPPMethod method) {
|
||||
String methodName = method.getName();
|
||||
if (methodName == null)
|
||||
return 0;
|
||||
|
||||
// accessor with "reet" prefix is the most relevant
|
||||
if (methodName.equalsIgnoreCase("reset" + propertyName))
|
||||
return BaseRelevance + 20;
|
||||
|
||||
// method names that include the property name anywhere are the next
|
||||
// most relevant
|
||||
if (methodName.matches(".*(?i)" + propertyName + ".*"))
|
||||
return BaseRelevance + 18;
|
||||
|
||||
// otherwise return default relevance
|
||||
return 10;
|
||||
}
|
||||
}
|
||||
|
||||
private static ICPPClassType getEnclosingClassDefinition(ICEditorContentAssistInvocationContext context) {
|
||||
try {
|
||||
IIndex index = CCorePlugin.getIndexManager().getIndex(context.getProject());
|
||||
ITranslationUnit tu = context.getTranslationUnit();
|
||||
if (tu == null)
|
||||
return null;
|
||||
|
||||
// Disable all unneeded parts of the parser.
|
||||
IASTTranslationUnit astTU
|
||||
= tu.getAST(
|
||||
index,
|
||||
ITranslationUnit.AST_SKIP_FUNCTION_BODIES
|
||||
| ITranslationUnit.AST_SKIP_ALL_HEADERS
|
||||
| ITranslationUnit.AST_CONFIGURE_USING_SOURCE_CONTEXT
|
||||
| ITranslationUnit.AST_SKIP_TRIVIAL_EXPRESSIONS_IN_AGGREGATE_INITIALIZERS
|
||||
| ITranslationUnit.AST_PARSE_INACTIVE_CODE);
|
||||
if (astTU == null)
|
||||
return null;
|
||||
|
||||
IASTNodeSelector selector = astTU.getNodeSelector(null);
|
||||
|
||||
// Macro expansions don't provide valid enclosing nodes. Backup until we are no longer in a
|
||||
// macro expansions. A loop is needed because consecutive expansions have no valid node
|
||||
// between them.
|
||||
int offset = context.getInvocationOffset();
|
||||
IASTNode enclosing;
|
||||
do {
|
||||
enclosing = selector.findEnclosingNode(offset, 0);
|
||||
if (enclosing == null)
|
||||
return null;
|
||||
|
||||
IASTFileLocation location = enclosing.getFileLocation();
|
||||
if (location == null)
|
||||
return null;
|
||||
|
||||
offset = location.getNodeOffset() - 1;
|
||||
} while(offset > 0
|
||||
&& !(enclosing instanceof IASTCompositeTypeSpecifier));
|
||||
|
||||
if (!(enclosing instanceof IASTCompositeTypeSpecifier))
|
||||
return null;
|
||||
|
||||
IASTName name = ((IASTCompositeTypeSpecifier) enclosing).getName();
|
||||
if (name == null)
|
||||
return null;
|
||||
|
||||
IBinding binding = name.getBinding();
|
||||
if (binding == null)
|
||||
return null;
|
||||
|
||||
return (ICPPClassType) binding.getAdapter(ICPPClassType.class);
|
||||
} catch(CoreException e) {
|
||||
QtUIPlugin.log(e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find and return all methods that are accessible in the class definition that encloses the argument
|
||||
* invocation context. Does not return null.
|
||||
*/
|
||||
private static Collection<ICPPMethod> getMethods(ICEditorContentAssistInvocationContext context, IMethodAttribute methodAttribute) {
|
||||
|
||||
ICPPClassType cls = getEnclosingClassDefinition(context);
|
||||
if (cls == null)
|
||||
return Collections.emptyList();
|
||||
|
||||
// Return all the methods, including inherited and non-visible ones.
|
||||
ICPPMethod[] methods = cls.getMethods();
|
||||
List<ICPPMethod> filtered = new ArrayList<ICPPMethod>(methods.length);
|
||||
for(ICPPMethod method : methods)
|
||||
if (methodAttribute.keep(method))
|
||||
filtered.add(method);
|
||||
|
||||
// TODO Choose the overload that is the best match -- closest parameter type and fewest
|
||||
// parameters with default values.
|
||||
|
||||
return filtered;
|
||||
}
|
||||
|
||||
private static boolean isAssignable(IType lhs, IType rhs) {
|
||||
// TODO This needs a real assignment check. If the types are different by implicitly convertible
|
||||
// then this should return true.
|
||||
return lhs != null
|
||||
&& rhs.isSameType(lhs);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,397 @@
|
|||
/*
|
||||
* Copyright (c) 2013 QNX Software Systems and others.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.cdt.internal.qt.ui.assist;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.cdt.core.dom.ast.IASTDeclarator;
|
||||
import org.eclipse.cdt.core.dom.ast.IType;
|
||||
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTTypeId;
|
||||
import org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.CPPVisitor;
|
||||
import org.eclipse.cdt.internal.corext.template.c.CContextType;
|
||||
import org.eclipse.cdt.internal.qt.core.parser.QtParser;
|
||||
import org.eclipse.cdt.internal.ui.text.CHeuristicScanner;
|
||||
import org.eclipse.cdt.internal.ui.text.Symbols;
|
||||
import org.eclipse.cdt.internal.ui.text.contentassist.CCompletionProposal;
|
||||
import org.eclipse.cdt.qt.core.QtKeywords;
|
||||
import org.eclipse.cdt.qt.core.index.IQProperty;
|
||||
import org.eclipse.cdt.qt.ui.QtUIPlugin;
|
||||
import org.eclipse.cdt.ui.text.contentassist.ICEditorContentAssistInvocationContext;
|
||||
import org.eclipse.jface.text.BadLocationException;
|
||||
import org.eclipse.jface.text.IDocument;
|
||||
import org.eclipse.jface.text.Region;
|
||||
import org.eclipse.jface.text.contentassist.ICompletionProposal;
|
||||
import org.eclipse.jface.text.templates.Template;
|
||||
import org.eclipse.jface.text.templates.TemplateContextType;
|
||||
|
||||
/**
|
||||
* A utility class for accessing parts of the Q_PROPERTY expansion that have already
|
||||
* been entered as well as the offset of various parts of the declaration. This is
|
||||
* used for things like proposing only parameters that are not already used, offering
|
||||
* appropriate suggestions for a specific parameter, etc.
|
||||
*/
|
||||
@SuppressWarnings("restriction")
|
||||
public class QPropertyExpansion {
|
||||
|
||||
/** The full text of the expansion */
|
||||
private final String expansion;
|
||||
|
||||
/** The offset of the first character in the attributes section. This is usually the
|
||||
* start of READ. */
|
||||
private final int startOfAttrs;
|
||||
|
||||
/** The offset of the cursor in the expansion. */
|
||||
private final int cursor;
|
||||
|
||||
/** The parsed type of the property. */
|
||||
private final IType type;
|
||||
|
||||
/** The parsed name of the property. This is the last identifier before the first attribute. */
|
||||
private final String name;
|
||||
|
||||
/** The identifier at which the cursor is currently pointing. */
|
||||
private final Identifier currIdentifier;
|
||||
|
||||
/** The identifier before the one where the cursor is pointing. This is needed to figure out what
|
||||
* values are valid for an attribute like READ, WRITE, etc. */
|
||||
private final Identifier prevIdentifier;
|
||||
|
||||
// The type/name section ends right before the first attribute.
|
||||
private static final Pattern TYPENAME_REGEX;
|
||||
static {
|
||||
StringBuilder regexBuilder = new StringBuilder();
|
||||
regexBuilder.append("^(?:Q_PROPERTY\\s*\\()?\\s*(.*?)(\\s+)(?:");
|
||||
for(IQProperty.Attribute attr : IQProperty.Attribute.values()) {
|
||||
if (attr.ordinal() > 0)
|
||||
regexBuilder.append('|');
|
||||
regexBuilder.append("(?:");
|
||||
regexBuilder.append(attr.identifier);
|
||||
regexBuilder.append(")");
|
||||
}
|
||||
regexBuilder.append(").*$");
|
||||
TYPENAME_REGEX = Pattern.compile(regexBuilder.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* A small utility to store the important parts of an identifier. This is just the starting
|
||||
* offset and the text of the identifier.
|
||||
*/
|
||||
private static class Identifier {
|
||||
public final int start;
|
||||
public final String ident;
|
||||
public Identifier(int start, String ident) {
|
||||
this.start = start;
|
||||
this.ident = ident;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Integer.toString(start) + ':' + ident;
|
||||
}
|
||||
}
|
||||
|
||||
public static QPropertyExpansion create(ICEditorContentAssistInvocationContext context) {
|
||||
|
||||
// Extract the substring that likely contributes to this Q_PROPERTY declaration. The declaration
|
||||
// could be in any state of being entered, so use the HeuristicScanner to guess about the
|
||||
// possible structure. The fixed assumptions are that the content assistant was invoked within
|
||||
// the expansion parameter of Q_PROPERTY. We try to guess at the end of the String, which is
|
||||
// either the closing paren (within 512 characters from the opening paren) or the current cursor
|
||||
// location.
|
||||
|
||||
// The offset is always right after the opening paren, use it to get to a fixed point in the
|
||||
// declaration.
|
||||
int offset = context.getContextInformationOffset();
|
||||
if (offset < 0)
|
||||
return null;
|
||||
|
||||
IDocument doc = context.getDocument();
|
||||
CHeuristicScanner scanner = new CHeuristicScanner(doc);
|
||||
|
||||
// We should only need to backup the length of Q_PROPERTY, but allow extra to deal
|
||||
// with whitespace.
|
||||
int lowerBound = Math.max(0, offset - 64);
|
||||
|
||||
// Allow up to 512 characters from the opening paren.
|
||||
int upperBound = Math.min(doc.getLength(), offset + 512);
|
||||
|
||||
int openingParen = scanner.findOpeningPeer(offset, lowerBound, '(', ')');
|
||||
if (openingParen == CHeuristicScanner.NOT_FOUND)
|
||||
return null;
|
||||
|
||||
int token = scanner.previousToken(scanner.getPosition() - 1, lowerBound);
|
||||
if (token != Symbols.TokenIDENT)
|
||||
return null;
|
||||
|
||||
// Find the start of the previous identifier. This scans backward, so it stops one
|
||||
// position before the identifier (unless the identifer is at the start of the content).
|
||||
int begin = scanner.getPosition();
|
||||
if (begin != 0)
|
||||
++begin;
|
||||
|
||||
String identifier = null;
|
||||
try {
|
||||
identifier = doc.get(begin, openingParen - begin);
|
||||
} catch (BadLocationException e) {
|
||||
QtUIPlugin.log(e);
|
||||
}
|
||||
|
||||
if (!QtKeywords.Q_PROPERTY.equals(identifier))
|
||||
return null;
|
||||
|
||||
// advance past the opening paren
|
||||
++openingParen;
|
||||
|
||||
String expansion = null;
|
||||
int closingParen = scanner.findClosingPeer(openingParen, upperBound, '(', ')');
|
||||
|
||||
// This expansion is not applicable if the assistant was invoked after the closing paren.
|
||||
if (closingParen != CHeuristicScanner.NOT_FOUND
|
||||
&& context.getInvocationOffset() > scanner.getPosition())
|
||||
return null;
|
||||
|
||||
try {
|
||||
if (closingParen != CHeuristicScanner.NOT_FOUND)
|
||||
expansion = doc.get(openingParen, closingParen - openingParen);
|
||||
else
|
||||
expansion = doc.get(openingParen, context.getInvocationOffset() - openingParen );
|
||||
} catch (BadLocationException e) {
|
||||
QtUIPlugin.log(e);
|
||||
}
|
||||
|
||||
if (expansion == null)
|
||||
return null;
|
||||
|
||||
int cursor = context.getInvocationOffset();
|
||||
Identifier currIdentifier = identifier(doc, scanner, cursor, lowerBound, upperBound);
|
||||
if (currIdentifier == null)
|
||||
return null;
|
||||
Identifier prevIdentifier = identifier(doc, scanner, currIdentifier.start - 1, lowerBound, upperBound);
|
||||
|
||||
// There are two significant regions in a Q_PROPERTY declaration. The first is everything
|
||||
// between the opening paren and the first parameter. This region specifies the type and the
|
||||
// name. The other is the region that declares all the parameters. There is an arbitrary
|
||||
// amount of whitespace between these regions.
|
||||
//
|
||||
// This function finds and returns the offset of the end of the region containing the type and
|
||||
// name. Returns 0 if the type/name region cannot be found.
|
||||
IType type = null;
|
||||
String name = null;
|
||||
int endOfTypeName = 0;
|
||||
Matcher m = TYPENAME_REGEX.matcher(expansion);
|
||||
if (m.matches()) {
|
||||
endOfTypeName = openingParen + m.end(2);
|
||||
|
||||
// parse the type/name part and then extract the type and name from the result
|
||||
ICPPASTTypeId typeId = QtParser.parseTypeId(m.group(1));
|
||||
type = CPPVisitor.createType(typeId);
|
||||
|
||||
IASTDeclarator declarator = typeId.getAbstractDeclarator();
|
||||
if (declarator != null
|
||||
&& declarator.getName() != null)
|
||||
name = declarator.getName().toString();
|
||||
}
|
||||
|
||||
return new QPropertyExpansion(expansion, endOfTypeName, cursor, type, name, prevIdentifier, currIdentifier);
|
||||
}
|
||||
|
||||
private QPropertyExpansion(String expansion, int startOfAttrs, int cursor, IType type, String name, Identifier prev, Identifier curr) {
|
||||
this.expansion = expansion;
|
||||
this.startOfAttrs = startOfAttrs;
|
||||
this.cursor = cursor;
|
||||
|
||||
this.type = type;
|
||||
this.name = name;
|
||||
this.prevIdentifier = prev;
|
||||
this.currIdentifier = curr;
|
||||
}
|
||||
|
||||
public String getCurrIdentifier() {
|
||||
return currIdentifier.ident;
|
||||
}
|
||||
|
||||
public String getPrevIdentifier() {
|
||||
return prevIdentifier.ident;
|
||||
}
|
||||
|
||||
public String getPrefix() {
|
||||
if (currIdentifier.ident == null)
|
||||
return null;
|
||||
|
||||
if (cursor > currIdentifier.start + currIdentifier.ident.length())
|
||||
return null;
|
||||
|
||||
return currIdentifier.ident.substring(0, cursor - currIdentifier.start);
|
||||
}
|
||||
|
||||
private static class Attribute {
|
||||
public final IQProperty.Attribute attribute;
|
||||
public final int relevance;
|
||||
|
||||
public Attribute(IQProperty.Attribute attribute) {
|
||||
this.attribute = attribute;
|
||||
|
||||
// Give attribute proposals the same order as the Qt documentation.
|
||||
switch(attribute) {
|
||||
case READ: this.relevance = 11; break;
|
||||
case WRITE: this.relevance = 10; break;
|
||||
case RESET: this.relevance = 9; break;
|
||||
case NOTIFY: this.relevance = 8; break;
|
||||
case REVISION: this.relevance = 7; break;
|
||||
case DESIGNABLE: this.relevance = 6; break;
|
||||
case SCRIPTABLE: this.relevance = 5; break;
|
||||
case STORED: this.relevance = 4; break;
|
||||
case USER: this.relevance = 3; break;
|
||||
case CONSTANT: this.relevance = 2; break;
|
||||
case FINAL: this.relevance = 1; break;
|
||||
default: this.relevance = 0; break;
|
||||
}
|
||||
}
|
||||
|
||||
public ICompletionProposal getProposal(String contextId, ICEditorContentAssistInvocationContext context) {
|
||||
|
||||
// Attributes without values propose only their own identifier.
|
||||
if (!attribute.hasValue)
|
||||
return new CCompletionProposal(attribute.identifier, context.getInvocationOffset(), 0, QtUIPlugin.getQtLogo(), attribute.identifier + " - Q_PROPERTY declaration parameter", relevance);
|
||||
|
||||
// Otherwise create a template where the content depends on the type of the attribute's parameter.
|
||||
String display = attribute.identifier + ' ' + attribute.paramName;
|
||||
String replacement = attribute.identifier;
|
||||
if ("bool".equals(attribute.paramName))
|
||||
replacement += " ${true}";
|
||||
else if ("int".equals(attribute.paramName))
|
||||
replacement += " ${0}";
|
||||
else if (attribute.paramName != null)
|
||||
replacement += " ${" + attribute.paramName + '}';
|
||||
|
||||
return templateProposal(contextId, context, display, replacement, relevance);
|
||||
}
|
||||
}
|
||||
|
||||
private static ICompletionProposal templateProposal(String contextId, ICEditorContentAssistInvocationContext context, String display, String replacement, int relevance) {
|
||||
Template template = new Template(display, "Q_PROPERTY declaration parameter", contextId, replacement, true);
|
||||
|
||||
TemplateContextType ctxType = new CContextType();
|
||||
ctxType.setId(contextId);
|
||||
|
||||
QtProposalContext templateCtx = new QtProposalContext(context, ctxType);
|
||||
Region region = new Region(templateCtx.getCompletionOffset(), templateCtx.getCompletionLength());
|
||||
return new QtTemplateProposal(template, templateCtx, region, relevance);
|
||||
}
|
||||
|
||||
public List<ICompletionProposal> getProposals(String contextId, ICEditorContentAssistInvocationContext context) {
|
||||
|
||||
// Make no suggestions when the start of the current identifier is before the end of
|
||||
// the "type name" portion of the declaration.
|
||||
if (currIdentifier.start < startOfAttrs)
|
||||
return Collections.emptyList();
|
||||
|
||||
// Propose nothing but READ as the first attribute. If the previous identifier is before
|
||||
// the end of the typeName region, then we're currently at the first attribute.
|
||||
if (prevIdentifier.start < startOfAttrs)
|
||||
return Collections.singletonList(new Attribute(IQProperty.Attribute.READ).getProposal(contextId, context));
|
||||
|
||||
// If the previous token is an Attribute name that has a parameter then suggest appropriate
|
||||
// values for that parameter. Otherwise suggest the other Attribute names.
|
||||
|
||||
String prefix = getPrefix();
|
||||
|
||||
// There are two types of proposals. If the previous identifier matches a known attribute name,
|
||||
// then we propose possible values for that attribute. Otherwise we want to propose the identifiers
|
||||
// that don't already appear in the expansion.
|
||||
//
|
||||
// This is implemented by iterating over the list of known attributes. If any of the attributes
|
||||
// matches the previous identifier, then we build and return a list of valid proposals for that
|
||||
// attribute.
|
||||
//
|
||||
// Otherwise, for each attribute we build a regular expression that checks to see if that token
|
||||
// appears within the expansion. If it already appears, then the attribute is ignored. Otherwise
|
||||
// it is added as an unspecified attribute. If the loop completes, then we create a list of proposals
|
||||
// for from that unspecified list.
|
||||
|
||||
List<Attribute> unspecifiedAttributes = new ArrayList<Attribute>();
|
||||
for(IQProperty.Attribute attr : IQProperty.Attribute.values()) {
|
||||
if (attr.hasValue
|
||||
&& (prevIdentifier != null && attr.identifier.equals(prevIdentifier.ident))) {
|
||||
|
||||
Collection<QPropertyAttributeProposal> attrProposals = QPropertyAttributeProposal.buildProposals(attr, context, type, name);
|
||||
if (attrProposals != null) {
|
||||
List<ICompletionProposal> proposals = new ArrayList<ICompletionProposal>();
|
||||
for(QPropertyAttributeProposal value : attrProposals)
|
||||
if (prefix == null
|
||||
|| value.getIdentifier().startsWith(prefix))
|
||||
proposals.add(value.createProposal(prefix, context.getInvocationOffset()));
|
||||
return proposals;
|
||||
}
|
||||
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
if (prefix != null) {
|
||||
if (attr.identifier.startsWith(prefix)
|
||||
&&(!expansion.matches(".*\\s+" + attr.identifier + "\\s+.*")
|
||||
||attr.identifier.equals(currIdentifier.ident)))
|
||||
unspecifiedAttributes.add(new Attribute(attr));
|
||||
} else if (!expansion.matches(".*\\s+" + attr.identifier + "\\s+.*"))
|
||||
unspecifiedAttributes.add(new Attribute(attr));
|
||||
}
|
||||
|
||||
List<ICompletionProposal> proposals = new ArrayList<ICompletionProposal>();
|
||||
for(Attribute attr : unspecifiedAttributes) {
|
||||
ICompletionProposal proposal = attr.getProposal(contextId, context);
|
||||
if (proposal != null)
|
||||
proposals.add(proposal);
|
||||
}
|
||||
|
||||
return proposals;
|
||||
}
|
||||
|
||||
private static Identifier identifier(IDocument doc, CHeuristicScanner scanner, int cursor, int lower, int upper) {
|
||||
try {
|
||||
// If the cursor is in whitespace, then the current identifier is null. Scan backward to find
|
||||
// the start of this whitespace.
|
||||
if (Character.isWhitespace(doc.getChar(cursor - 1))) {
|
||||
int prev = scanner.findNonWhitespaceBackward(cursor, lower);
|
||||
return new Identifier(Math.min(cursor, prev + 1), null);
|
||||
}
|
||||
|
||||
int tok = scanner.previousToken(cursor, lower);
|
||||
if (tok != CHeuristicScanner.TokenIDENT)
|
||||
return null;
|
||||
int begin = scanner.getPosition() + 1;
|
||||
|
||||
tok = scanner.nextToken(begin, upper);
|
||||
if (tok != CHeuristicScanner.TokenIDENT)
|
||||
return null;
|
||||
int end = scanner.getPosition();
|
||||
|
||||
return new Identifier(begin, doc.get(begin, end - begin));
|
||||
} catch(BadLocationException e) {
|
||||
QtUIPlugin.log(e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (expansion == null)
|
||||
return super.toString();
|
||||
|
||||
if (cursor >= expansion.length())
|
||||
return expansion + '|';
|
||||
if (cursor < 0)
|
||||
return "|" + expansion;
|
||||
|
||||
return expansion.substring(0, cursor) + '|' + expansion.substring(cursor);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* Copyright (c) 2013 QNX Software Systems and others.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.cdt.internal.qt.ui.assist;
|
||||
|
||||
import org.eclipse.cdt.core.dom.ast.IASTCompletionNode;
|
||||
import org.eclipse.cdt.internal.corext.template.c.CContext;
|
||||
import org.eclipse.cdt.ui.text.contentassist.ICEditorContentAssistInvocationContext;
|
||||
import org.eclipse.jface.text.BadLocationException;
|
||||
import org.eclipse.jface.text.IDocument;
|
||||
import org.eclipse.jface.text.ITextViewer;
|
||||
import org.eclipse.jface.text.Position;
|
||||
import org.eclipse.jface.text.templates.Template;
|
||||
import org.eclipse.jface.text.templates.TemplateContextType;
|
||||
import org.eclipse.swt.graphics.Point;
|
||||
|
||||
@SuppressWarnings("restriction")
|
||||
public class QtProposalContext extends CContext {
|
||||
|
||||
private final String contextId;
|
||||
|
||||
public QtProposalContext(ICEditorContentAssistInvocationContext context, TemplateContextType ctxType) {
|
||||
super(ctxType, context.getDocument(), getCompletionPosition(context), context.getTranslationUnit());
|
||||
this.contextId = ctxType.getId();
|
||||
}
|
||||
|
||||
private static Position getCompletionPosition(ICEditorContentAssistInvocationContext context) {
|
||||
// The normal CDT behaviour is to not offer template proposals when text is selected. I
|
||||
// don't know why they avoid it, so I've opted to replace the selection instead.
|
||||
|
||||
int adjustment = 0;
|
||||
IASTCompletionNode node = context.getCompletionNode();
|
||||
if (node != null) {
|
||||
String prefix = node.getPrefix();
|
||||
if (prefix != null)
|
||||
adjustment -= prefix.length();
|
||||
}
|
||||
|
||||
int length = -adjustment;
|
||||
ITextViewer viewer = context.getViewer();
|
||||
if (viewer != null) {
|
||||
Point selection = viewer.getSelectedRange();
|
||||
if (selection != null
|
||||
&& selection.y > 0)
|
||||
length += selection.y;
|
||||
}
|
||||
|
||||
int offset = context.getInvocationOffset() + adjustment;
|
||||
return new Position(offset, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canEvaluate(Template template) {
|
||||
// The base implementation uses a length of 0 to create an empty string for the key
|
||||
// and then refuses to apply the template. This override offers all templates that
|
||||
// have the right ID. This is ok, because only the templates that apply were proposed.
|
||||
return contextId.equals(template.getContextTypeId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStart() {
|
||||
// The base implementation creates a different offset when the replacement length
|
||||
// is not 0. We need to use the same start of the replacement region regardless of
|
||||
// whether or not characters are selected.
|
||||
|
||||
try {
|
||||
IDocument document= getDocument();
|
||||
|
||||
int start= getCompletionOffset();
|
||||
int end= getCompletionOffset() + getCompletionLength();
|
||||
|
||||
while (start != 0 && isUnicodeIdentifierPartOrPoundSign(document.getChar(start - 1)))
|
||||
start--;
|
||||
|
||||
while (start != end && Character.isWhitespace(document.getChar(start)))
|
||||
start++;
|
||||
|
||||
if (start == end)
|
||||
start= getCompletionOffset();
|
||||
|
||||
return start;
|
||||
} catch (BadLocationException e) {
|
||||
return super.getStart();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isUnicodeIdentifierPartOrPoundSign(char c) {
|
||||
return Character.isUnicodeIdentifierPart(c) || c == '#';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright (c) 2013 QNX Software Systems and others.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.cdt.internal.qt.ui.assist;
|
||||
|
||||
import org.eclipse.cdt.qt.ui.QtUIPlugin;
|
||||
import org.eclipse.cdt.ui.text.ICCompletionProposal;
|
||||
import org.eclipse.jface.text.IRegion;
|
||||
import org.eclipse.jface.text.templates.Template;
|
||||
import org.eclipse.jface.text.templates.TemplateContext;
|
||||
import org.eclipse.jface.text.templates.TemplateProposal;
|
||||
|
||||
public class QtTemplateProposal extends TemplateProposal implements ICCompletionProposal {
|
||||
|
||||
// The Qt proposals are made more relevant than the default built- proposals.
|
||||
private static int BASE_RELEVANCE = 1100;
|
||||
|
||||
public QtTemplateProposal(Template template, TemplateContext context, IRegion region) {
|
||||
this(template, context, region, 0);
|
||||
}
|
||||
|
||||
public QtTemplateProposal(Template template, TemplateContext context, IRegion region, int relevance) {
|
||||
super(template, context, region, QtUIPlugin.getQtLogo(), BASE_RELEVANCE + relevance);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIdString() {
|
||||
return getDisplayString();
|
||||
}
|
||||
}
|
|
@ -11,6 +11,7 @@ import org.eclipse.cdt.core.model.CModelException;
|
|||
import org.eclipse.core.runtime.CoreException;
|
||||
import org.eclipse.core.runtime.IStatus;
|
||||
import org.eclipse.core.runtime.Status;
|
||||
import org.eclipse.swt.graphics.Image;
|
||||
import org.eclipse.ui.plugin.AbstractUIPlugin;
|
||||
import org.osgi.framework.BundleContext;
|
||||
|
||||
|
@ -31,6 +32,14 @@ public class QtUIPlugin extends AbstractUIPlugin {
|
|||
public QtUIPlugin() {
|
||||
}
|
||||
|
||||
public static Image getQtLogo() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Image getQtLogoLarge() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext)
|
||||
|
|
Loading…
Add table
Reference in a new issue