From 719982b23bd265699213650a6ec5e2d995db7f08 Mon Sep 17 00:00:00 2001 From: Andrew Eidsness Date: Wed, 27 Feb 2013 08:16:19 -0500 Subject: Recognize Q_SIGNAL and Q_SLOT on single functions Qt allows signals and slots to be marked directly on the function, e.g., class T { Q_SIGNAL void some_signal(); Q_SLOT void some_slot(); }; This change modifies the Qt signal/slot tagger to look for these tags in addition to the previously implemented search for the visibility label. Change-Id: Ibf43df8d80d4ca9f8b62776e7a35a4fc067a289e Reviewed-on: https://git.eclipse.org/r/10701 Reviewed-by: Doug Schaefer IP-Clean: Doug Schaefer Tested-by: Doug Schaefer --- .../src/org/eclipse/cdt/qt/core/QtKeywords.java | 21 +- .../cdt/qt/internal/core/QtSignalSlotTagger.java | 275 ++++++--- .../internal/ui/QtCompletionProposalComputer.java | 674 ++++++++++----------- 3 files changed, 547 insertions(+), 423 deletions(-) diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/QtKeywords.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/QtKeywords.java index 381655ef025..2e514ebfbeb 100644 --- a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/QtKeywords.java +++ b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/QtKeywords.java @@ -11,16 +11,13 @@ package org.eclipse.cdt.qt.core; /** * Declares constants related to tokens that are special in Qt applications. */ -public class QtKeywords -{ - public static final String Q_SIGNALS = "Q_SIGNALS"; - public static final String Q_SLOTS = "Q_SLOTS"; - public static final String SIGNALS = "signals"; - public static final String SLOTS = "slots"; - - public static final String SIGNAL = "SIGNAL"; - public static final String SLOT = "SLOT"; - - public static final String QOBJECT = "QObject"; - public static final String CONNECT = "connect"; +public class QtKeywords { + public static final String CONNECT = "connect"; + public static final String Q_SIGNAL = "Q_SIGNAL"; + public static final String Q_SIGNALS = "Q_SIGNALS"; + public static final String Q_SLOT = "Q_SLOT"; + public static final String Q_SLOTS = "Q_SLOTS"; + public static final String QOBJECT = "QObject"; + public static final String SIGNALS = "signals"; + public static final String SLOTS = "slots"; } diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/QtSignalSlotTagger.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/QtSignalSlotTagger.java index a82b3c15547..06c2f6f08d7 100644 --- a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/QtSignalSlotTagger.java +++ b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/internal/core/QtSignalSlotTagger.java @@ -9,11 +9,14 @@ package org.eclipse.cdt.qt.internal.core; import org.eclipse.cdt.core.dom.ast.IASTDeclaration; +import org.eclipse.cdt.core.dom.ast.IASTFileLocation; import org.eclipse.cdt.core.dom.ast.IASTMacroExpansionLocation; import org.eclipse.cdt.core.dom.ast.IASTName; import org.eclipse.cdt.core.dom.ast.IASTNode; import org.eclipse.cdt.core.dom.ast.IASTNodeLocation; import org.eclipse.cdt.core.dom.ast.IASTPreprocessorMacroExpansion; +import org.eclipse.cdt.core.dom.ast.IASTProblemHolder; +import org.eclipse.cdt.core.dom.ast.IASTSimpleDeclaration; import org.eclipse.cdt.core.dom.ast.IBinding; import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTCompositeTypeSpecifier; import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTVisibilityLabel; @@ -25,77 +28,203 @@ import org.eclipse.cdt.core.dom.ast.tag.IWritableTag; import org.eclipse.cdt.qt.core.QtKeywords; import org.eclipse.cdt.qt.core.QtPlugin; -public class QtSignalSlotTagger implements IBindingTagger -{ - private static ICPPASTVisibilityLabel findVisibilityLabel( ICPPMethod method, IASTNode ast ) - { - // the visibility cannot be found without an ast - if( ast == null ) - return null; - - IASTNode methodDecl = ast; - ICPPASTCompositeTypeSpecifier classType = null; - while( methodDecl != null - && classType == null ) - { - IASTNode parent = methodDecl.getParent(); - if( parent instanceof ICPPASTCompositeTypeSpecifier ) - classType = (ICPPASTCompositeTypeSpecifier)parent; - else - methodDecl = parent; - } - - if( methodDecl == null - || classType == null ) - return null; - - ICPPASTVisibilityLabel lastLabel = null; - for( IASTDeclaration decl : classType.getMembers() ) - { - if( decl instanceof ICPPASTVisibilityLabel ) - lastLabel = (ICPPASTVisibilityLabel)decl; - else if( decl == methodDecl ) - return lastLabel; - } - - return null; - } - - @Override - public ITag process( ITagWriter tagWriter, IBinding binding, IASTName ast ) - { - // only methods a be signals or slots - if( ! ( binding instanceof ICPPMethod ) ) - return null; - - // a visibility label is required in order to decide whether the method is a signal/slot - ICPPMethod method = (ICPPMethod)binding; - ICPPASTVisibilityLabel v = findVisibilityLabel( method, ast ); - if( v == null ) - return null; - - byte bitset = 0; - for( IASTNodeLocation loc : v.getNodeLocations() ) - if( loc instanceof IASTMacroExpansionLocation ) - { - IASTMacroExpansionLocation macroExpansion = (IASTMacroExpansionLocation)loc; - IASTPreprocessorMacroExpansion exp = macroExpansion.getExpansion(); - String macro = exp.getMacroReference().toString(); - - if( QtKeywords.SIGNALS.equals( macro ) || QtKeywords.Q_SIGNALS.equals( macro ) ) - bitset |= QtPlugin.SignalSlot_Mask_signal; - else if( QtKeywords.SLOTS.equals( macro ) || QtKeywords.Q_SLOTS.equals( macro ) ) - bitset |= QtPlugin.SignalSlot_Mask_slot; - } - - if( bitset != 0 ) - { - IWritableTag tag = tagWriter.createTag( QtPlugin.SIGNAL_SLOT_TAGGER_ID, 1 ); - if( tag != null - && tag.putByte( 0, bitset ) ) - return tag; - } - - return null; - } +/** + * Finds all functions that are marked as Qt signals or slots and tags them in + * the index. There are two ways that Qt understands for marking a function as a + * signal or slot: 1) With a macro in the function's visibility label 2) With a + * macro before the function itself E.g., both of these cases are valid: + * + *
+ * class T
+ * {
+ * private:
+ *     Q_SLOT void some_slot();
+ * 
+ * signals:
+ *     void some_signal();
+ * };
+ * 
+ * + * The 6 applicable macros are signals, Q_SIGNALS, Q_SIGNAL, slots, Q_SLOTS, and + * Q_SLOT. + */ +public class QtSignalSlotTagger implements IBindingTagger { + private static ICPPASTVisibilityLabel findVisibilityLabel( + ICPPMethod method, IASTNode ast) { + // the visibility cannot be found without an ast + if (ast == null) + return null; + + IASTNode methodDecl = ast; + ICPPASTCompositeTypeSpecifier classType = null; + while (methodDecl != null && classType == null) { + IASTNode parent = methodDecl.getParent(); + if (parent instanceof ICPPASTCompositeTypeSpecifier) + classType = (ICPPASTCompositeTypeSpecifier) parent; + else + methodDecl = parent; + } + + if (methodDecl == null || classType == null) + return null; + + ICPPASTVisibilityLabel lastLabel = null; + for (IASTDeclaration decl : classType.getMembers()) { + if (decl instanceof ICPPASTVisibilityLabel) + lastLabel = (ICPPASTVisibilityLabel) decl; + else if (decl == methodDecl) + return lastLabel; + } + + return null; + } + + private static byte getBitset(IASTNodeLocation... locations) { + for (IASTNodeLocation location : locations) + if (location instanceof IASTMacroExpansionLocation) { + IASTMacroExpansionLocation macroExpansion = (IASTMacroExpansionLocation) location; + IASTPreprocessorMacroExpansion exp = macroExpansion + .getExpansion(); + String macro = exp.getMacroReference().toString(); + + if (QtKeywords.Q_SIGNAL.equals(macro) + || QtKeywords.Q_SIGNALS.equals(macro) + || QtKeywords.SIGNALS.equals(macro)) + return QtPlugin.SignalSlot_Mask_signal; + if (QtKeywords.Q_SLOT.equals(macro) + || QtKeywords.Q_SLOTS.equals(macro) + || QtKeywords.SLOTS.equals(macro)) + return QtPlugin.SignalSlot_Mask_slot; + } + + return 0; + } + + private static byte getBitset(IASTNode... nodes) { + byte bitset = 0; + for (IASTNode node : nodes) + if (node != null) + for (IASTNodeLocation loc : node.getNodeLocations()) + bitset |= getBitset(loc); + + return bitset; + } + + private static IASTNode getSimpleDecl(IASTNode node) { + while (node != null && !(node instanceof IASTSimpleDeclaration)) + node = node.getParent(); + return node; + } + + private byte getQtMarkers(ICPPMethod method, IASTName ast) { + byte bitset = 0; + if (ast == null) + return bitset; + + // Look for macros on the previous visibility label. + bitset |= getBitset(findVisibilityLabel(method, ast)); + + // Look for macros on this function. See Bug 401696 for a better + // description of why it needs + // to work this why. Briefly, the parser does not associate empty macros + // with the function when + // they are the first thing in the declaration. E.g., + // #define X + // void func1() {} + // X void func2() {} + // Could also look like: + // void func1() {} X + // void func2() {} + // + // The following code instead looks at the parents and children to find + // all node locations between + // the declarators. + // + // We first look at parents to find the closest SimpleDeclaration. We + // then look at that node's parent + // to find the node that is right before the target. Then we look at all + // node locations between the + // end of that previous node and the end of the target node. + + // find the closest containing SimpleDecl + IASTNode simpleDecl = getSimpleDecl(ast); + IASTNode parent = simpleDecl == null ? null : simpleDecl.getParent(); + if (parent == null) + return bitset; + + // find the declaration before the target + IASTNode previous = null; + IASTNode[] children = parent.getChildren(); + if (children.length > 1) + for (int i = 1; i < children.length; ++i) { + if (children[i] == simpleDecl) { + // if we haven't found a SimpleDecl, then find the nearest + // previous non-problem node + for (int j = i - 1; previous == null && j >= 0; --j) + if (!(children[j] instanceof IASTProblemHolder)) + previous = children[j]; + break; + } + if (children[i] instanceof IASTSimpleDeclaration) + previous = children[i]; + } + + // Signals/slots can only be declared inside of classes, so all cases we + // care about have a + // previous child, even if it is only the Base-class specifier. + if (previous == null) + return bitset; + + IASTFileLocation prevLocation = previous.getFileLocation(); + int prev_off = prevLocation.getNodeOffset(); + int prev_end = prevLocation.getNodeOffset() + + prevLocation.getNodeLength(); + + // Figure out where the target node ends. + int end = ast.getFileLocation().getNodeOffset() + + ast.getFileLocation().getNodeLength(); + + // Examine all locations that appear after the previous node and before + // the target node. + boolean found_previous = false; + for (IASTNodeLocation loc : parent.getNodeLocations()) { + int o = loc.getNodeOffset(); + int e = loc.getNodeOffset() + loc.getNodeLength(); + + // if the previous node has already been found, process this one + if (found_previous) + bitset |= getBitset(loc); + + // otherwise see if this is the previous node + else if (o <= prev_off && e >= prev_end) + found_previous = true; + + // stop processing when we're processed to the end of the target + if (e >= end) + break; + } + + return bitset; + } + + @Override + public ITag process(ITagWriter tagWriter, IBinding binding, IASTName ast) { + // only methods a be signals or slots + if (!(binding instanceof ICPPMethod)) + return null; + + // Find all qt marker macros associated with this node. + ICPPMethod method = (ICPPMethod) binding; + byte bitset = getQtMarkers(method, ast); + + // create and store the bitset if needed + if (bitset != 0) { + IWritableTag tag = tagWriter.createTag( + QtPlugin.SIGNAL_SLOT_TAGGER_ID, 1); + if (tag != null && tag.putByte(0, bitset)) + return tag; + } + + return null; + } } diff --git a/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/qt/internal/ui/QtCompletionProposalComputer.java b/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/qt/internal/ui/QtCompletionProposalComputer.java index 63d22a437fc..2e6e6d53823 100644 --- a/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/qt/internal/ui/QtCompletionProposalComputer.java +++ b/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/qt/internal/ui/QtCompletionProposalComputer.java @@ -46,347 +46,345 @@ 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.ui.CUIPlugin; +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.jface.text.contentassist.ICompletionProposal; -@SuppressWarnings( "restriction" ) -public class QtCompletionProposalComputer extends ParsingBasedProposalComputer -{ - private boolean isApplicable( CContentAssistInvocationContext context ) - { - ITranslationUnit tu = context.getTranslationUnit(); - if( tu == null ) - return false; - - ICProject cProject = tu.getCProject(); - if( cProject == null ) - return false; - - IProject project = cProject.getProject(); - 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( CContentAssistInvocationContext 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( CContentAssistInvocationContext 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 filterMethods( final ICPPClassType cls, final MethodFilter filter ) - { - return new Iterable() - { - @Override - public Iterator iterator() - { - return new Iterator() - { - 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 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 positions= new ArrayList(); - 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 proposals, CContentAssistInvocationContext context, IASTCompletionNode completionNode, String prefix ) - { - IASTName[] names = completionNode.getNames(); - List completions = new LinkedList(); - - 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 ) { CCorePlugin.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: - //if( QtKeywords.SIGNAL.equals( prefix ) ) - addCompletionsFor( completions, args[0], MethodFilter.Qt.Signal ); - break; - case 4: - if( QtKeywords.SLOT.equals( prefix ) ) - addCompletionsFor( completions, args[2], MethodFilter.Qt.Slot ); - break; - } - } - } - - for( Completion completion : completions ) - { - ICompletionProposal proposal = completion.createProposal( context ); - if( proposal != null ) - proposals.add( proposal ); - } - } - - @Override - protected List computeCompletionProposals( CContentAssistInvocationContext context, IASTCompletionNode completionNode, String prefix ) throws CoreException - { - if( !isApplicable( context ) ) - return Collections.emptyList(); - - List proposals = new ArrayList(); - addConnectParameterCompletions( proposals, context, completionNode, prefix ); - return proposals; - } +@SuppressWarnings("restriction") +public class QtCompletionProposalComputer extends ParsingBasedProposalComputer { + private boolean isApplicable(ICEditorContentAssistInvocationContext context) { + ITranslationUnit tu = context.getTranslationUnit(); + if (tu == null) + return false; + + ICProject cProject = tu.getCProject(); + if (cProject == null) + return false; + + IProject project = cProject.getProject(); + 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 filterMethods(final ICPPClassType cls, + final MethodFilter filter) { + return new Iterable() { + @Override + public Iterator iterator() { + return new Iterator() { + 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 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 positions = new ArrayList(); + 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 proposals, + ICEditorContentAssistInvocationContext context, + IASTCompletionNode completionNode, String prefix) { + IASTName[] names = completionNode.getNames(); + List completions = new LinkedList(); + + 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) { + CCorePlugin.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); + } + } + + @Override + protected List computeCompletionProposals( + CContentAssistInvocationContext context, + IASTCompletionNode completionNode, String prefix) + throws CoreException { + if (!isApplicable(context)) + return Collections.emptyList(); + + List proposals = new ArrayList(); + addConnectParameterCompletions(proposals, context, completionNode, + prefix); + return proposals; + } } -- cgit v1.2.3