diff --git a/ide-test/org.codehaus.groovy.eclipse.codeassist.completion.test/src/org/codehaus/groovy/eclipse/codeassist/tests/InnerTypeCompletionTests.groovy b/ide-test/org.codehaus.groovy.eclipse.codeassist.completion.test/src/org/codehaus/groovy/eclipse/codeassist/tests/InnerTypeCompletionTests.groovy index 4f59115b93..9bc1e4d4b1 100644 --- a/ide-test/org.codehaus.groovy.eclipse.codeassist.completion.test/src/org/codehaus/groovy/eclipse/codeassist/tests/InnerTypeCompletionTests.groovy +++ b/ide-test/org.codehaus.groovy.eclipse.codeassist.completion.test/src/org/codehaus/groovy/eclipse/codeassist/tests/InnerTypeCompletionTests.groovy @@ -116,11 +116,11 @@ final class InnerTypeCompletionTests extends CompletionTestSuite { } Outer.Inn '''.stripIndent() - assertProposalCreated(contents, 'Inn', 'Inner - Outer') + applyProposalAndCheck(assertProposalCreated(contents, 'Inn', 'Inner - Outer'), contents.replaceFirst(/Inn\b/, 'Inner')) } @Test - void testInnerClass3a() { + void testInnerClass4() { String contents = '''\ Map.Ent '''.stripIndent() @@ -128,13 +128,13 @@ final class InnerTypeCompletionTests extends CompletionTestSuite { } @Test - void testInnerClass3b() { + void testInnerClass5() { setJavaPreference(PreferenceConstants.CODEASSIST_ADDIMPORT, 'false') - testInnerClass3a() // no difference; no qualifier should be inserted + testInnerClass4() // no difference; no qualifier should be inserted } @Test - void testInnerClass4() { + void testInnerClass6() { addGroovySource '''\ class Outer { class Inner { @@ -150,7 +150,7 @@ final class InnerTypeCompletionTests extends CompletionTestSuite { } @Test - void testInnerClass5() { + void testInnerClass7() { addGroovySource '''\ class Outer { class Inner { @@ -165,7 +165,7 @@ final class InnerTypeCompletionTests extends CompletionTestSuite { } @Test - void testInnerClass6() { + void testInnerClass8() { addGroovySource '''\ class Outer { class Inner { @@ -181,7 +181,7 @@ final class InnerTypeCompletionTests extends CompletionTestSuite { } @Test - void testInnerClass6a() { + void testInnerClass9() { addGroovySource '''\ class Outer { class Inner { @@ -198,7 +198,7 @@ final class InnerTypeCompletionTests extends CompletionTestSuite { } @Test - void testInnerClass7() { + void testInnerClass10() { addGroovySource '''\ class Outer { interface Inner { @@ -215,7 +215,7 @@ final class InnerTypeCompletionTests extends CompletionTestSuite { } @Test - void testInnerClass7a() { + void testInnerClass11() { addGroovySource '''\ class Outer { interface Inner { @@ -231,7 +231,44 @@ final class InnerTypeCompletionTests extends CompletionTestSuite { } @Test - void testInnerClass8() { + void testInnerClass12() { + addGroovySource '''\ + class Outer { + class Inner { + } + } + ''', 'Outer', 'v' + + String contents = '''\ + Outer.Inn + '''.stripIndent() + applyProposalAndCheck(assertProposalCreated(contents, 'Inn', 'Inner - v.Outer'), '''\ + |import v.Outer + | + |Outer.Inner + |'''.stripMargin()) + } + + @Test + void testInnerClass13() { + addGroovySource '''\ + class Outer { + class Inner { + } + } + ''', 'Outer', 'w' + + String contents = '''\ + Outer.Inn + '''.stripIndent() + setJavaPreference(PreferenceConstants.CODEASSIST_ADDIMPORT, 'false') + applyProposalAndCheck(assertProposalCreated(contents, 'Inn', 'Inner - w.Outer'), '''\ + |w.Outer.Inner + |'''.stripMargin()) + } + + @Test + void testInnerClass14() { addGroovySource '''\ class Outer { class Inner { @@ -239,12 +276,49 @@ final class InnerTypeCompletionTests extends CompletionTestSuite { } } } - ''', 'Outer', 'q' + ''', 'Outer', 'x' String contents = '''\ - q.Outer.Inner.N + x.Outer.Inner.N '''.stripIndent() - applyProposalAndCheck(assertProposalCreated(contents, 'N', 'Nucleus - q.Outer.Inner'), contents.replace('N', 'Nucleus')) + applyProposalAndCheck(assertProposalCreated(contents, 'N', 'Nucleus - x.Outer.Inner'), contents.replace('N', 'Nucleus')) + } + + @Test + void testInnerClass15() { + addGroovySource '''\ + class Outer { + class Inner { + } + } + ''', 'Outer', 'y' + + String contents = '''\ + Inn + '''.stripIndent() + applyProposalAndCheck(assertProposalCreated(contents, 'Inn', 'Inner - y.Outer'), '''\ + |import y.Outer.Inner + | + |Inner + |'''.stripMargin()) + } + + @Test + void testInnerClass16() { + addGroovySource '''\ + class Outer { + class Inner { + } + } + ''', 'Outer', 'z' + + String contents = '''\ + Inn + '''.stripIndent() + setJavaPreference(PreferenceConstants.CODEASSIST_ADDIMPORT, 'false') + applyProposalAndCheck(assertProposalCreated(contents, 'Inn', 'Inner - z.Outer'), '''\ + |z.Outer.Inner + |'''.stripMargin()) } // diff --git a/ide/org.codehaus.groovy.eclipse.codeassist.completion/src/org/codehaus/groovy/eclipse/codeassist/processors/AbstractGroovyCompletionProcessor.java b/ide/org.codehaus.groovy.eclipse.codeassist.completion/src/org/codehaus/groovy/eclipse/codeassist/processors/AbstractGroovyCompletionProcessor.java index a365f9639c..3b267cf2b8 100644 --- a/ide/org.codehaus.groovy.eclipse.codeassist.completion/src/org/codehaus/groovy/eclipse/codeassist/processors/AbstractGroovyCompletionProcessor.java +++ b/ide/org.codehaus.groovy.eclipse.codeassist.completion/src/org/codehaus/groovy/eclipse/codeassist/processors/AbstractGroovyCompletionProcessor.java @@ -20,6 +20,7 @@ import org.codehaus.groovy.eclipse.codeassist.creators.IProposalCreator; import org.codehaus.groovy.eclipse.codeassist.creators.MethodProposalCreator; import org.codehaus.groovy.eclipse.codeassist.requestor.ContentAssistContext; +import org.eclipse.jdt.core.search.SearchPattern; import org.eclipse.jdt.internal.core.SearchableEnvironment; import org.eclipse.jdt.ui.text.java.JavaContentAssistInvocationContext; @@ -35,15 +36,15 @@ public AbstractGroovyCompletionProcessor(ContentAssistContext context, JavaConte this.javaContext = javaContext; } - public ContentAssistContext getContext() { + public final ContentAssistContext getContext() { return context; } - public SearchableEnvironment getNameEnvironment() { + public final SearchableEnvironment getNameEnvironment() { return nameEnvironment; } - public JavaContentAssistInvocationContext getJavaContext() { + public final JavaContentAssistInvocationContext getJavaContext() { return javaContext; } @@ -60,4 +61,11 @@ protected final GroovyCompletionProposal createProposal(int kind, int completion proposal.setNameLookup(nameEnvironment.nameLookup); return proposal; } + + protected static boolean matches(String prefix, String candidate, boolean camelCaseMatch) { + if (!camelCaseMatch) { + return candidate.startsWith(prefix); + } + return SearchPattern.camelCaseMatch(prefix, candidate); + } } diff --git a/ide/org.codehaus.groovy.eclipse.codeassist.completion/src/org/codehaus/groovy/eclipse/codeassist/processors/ConstructorCompletionProcessor.java b/ide/org.codehaus.groovy.eclipse.codeassist.completion/src/org/codehaus/groovy/eclipse/codeassist/processors/ConstructorCompletionProcessor.java index c2637660cd..05e08186fe 100644 --- a/ide/org.codehaus.groovy.eclipse.codeassist.completion/src/org/codehaus/groovy/eclipse/codeassist/processors/ConstructorCompletionProcessor.java +++ b/ide/org.codehaus.groovy.eclipse.codeassist.completion/src/org/codehaus/groovy/eclipse/codeassist/processors/ConstructorCompletionProcessor.java @@ -40,7 +40,8 @@ public class ConstructorCompletionProcessor extends AbstractGroovyCompletionProcessor implements ITypeResolver { - private JDTResolver resolver; + protected ModuleNode module; + protected JDTResolver resolver; public ConstructorCompletionProcessor(ContentAssistContext context, JavaContentAssistInvocationContext javaContext, SearchableEnvironment nameEnvironment) { super(context, javaContext, nameEnvironment); @@ -48,36 +49,38 @@ public ConstructorCompletionProcessor(ContentAssistContext context, JavaContentA @Override public void setResolverInformation(ModuleNode module, JDTResolver resolver) { + this.module = module; this.resolver = resolver; } @Override public List generateProposals(IProgressMonitor monitor) { ContentAssistContext context = getContext(); - char[] constructorText; int constructorStart; + char[] completionChars; int completionStart; switch (context.location) { case CONSTRUCTOR: context.extend(getJavaContext().getCoreContext(), null); - constructorText = context.fullCompletionExpression.replaceFirst("^new\\s+", "").toCharArray(); - constructorStart = context.completionLocation - CharOperation.lastSegment(constructorText, '.').length; + completionChars = context.fullCompletionExpression.replaceAll("^new|\\s+", "").toCharArray(); + completionStart = context.completionLocation - CharOperation.lastSegment(completionChars, '.').length; break; case METHOD_CONTEXT: - constructorText = ((MethodInfoContentAssistContext) context).methodName.toCharArray(); - constructorStart = ((MethodInfoContentAssistContext) context).methodNameEnd - CharOperation.lastSegment(constructorText, '.').length;; + completionChars = ((MethodInfoContentAssistContext) context).methodName.replace('$', '.').toCharArray(); + completionStart = ((MethodInfoContentAssistContext) context).methodNameEnd - CharOperation.lastSegment(completionChars, '.').length; break; default: throw new IllegalStateException("Invalid constructor completion location: " + context.location.name()); } - GroovyProposalTypeSearchRequestor requestor = new GroovyProposalTypeSearchRequestor(context, getJavaContext(), - constructorStart, context.completionEnd - constructorStart, getNameEnvironment().nameLookup, monitor); - - getNameEnvironment().findConstructorDeclarations(constructorText, true, requestor, monitor); + SearchableEnvironment environment = getNameEnvironment(); + int replacementLength = context.completionEnd - completionStart; + GroovyProposalTypeSearchRequestor requestor = new GroovyProposalTypeSearchRequestor( + context, getJavaContext(), completionStart, replacementLength, environment.nameLookup, monitor); + environment.findConstructorDeclarations(completionChars, requestor.options.camelCaseMatch, requestor, monitor); return requestor.processAcceptedConstructors(findUsedParameters(context), resolver); } - private Set findUsedParameters(ContentAssistContext context) { + protected static Set findUsedParameters(ContentAssistContext context) { if (context.location != ContentAssistLocation.METHOD_CONTEXT) { return Collections.emptySet(); } @@ -95,13 +98,10 @@ private Set findUsedParameters(ContentAssistContext context) { } } } - // now remove the arguments that are already written if (arguments instanceof MapExpression) { - // Do extra filtering to determine what parameters are still - // available - MapExpression enclosingCallArgs = (MapExpression) arguments; - for (MapEntryExpression entry : enclosingCallArgs.getMapEntryExpressions()) { + // do extra filtering to determine what parameters are still available + for (MapEntryExpression entry : ((MapExpression) arguments).getMapEntryExpressions()) { usedParams.add(entry.getKeyExpression().getText()); } } diff --git a/ide/org.codehaus.groovy.eclipse.codeassist.completion/src/org/codehaus/groovy/eclipse/codeassist/processors/GroovyProposalTypeSearchRequestor.java b/ide/org.codehaus.groovy.eclipse.codeassist.completion/src/org/codehaus/groovy/eclipse/codeassist/processors/GroovyProposalTypeSearchRequestor.java index 11865587ff..f292d659eb 100644 --- a/ide/org.codehaus.groovy.eclipse.codeassist.completion/src/org/codehaus/groovy/eclipse/codeassist/processors/GroovyProposalTypeSearchRequestor.java +++ b/ide/org.codehaus.groovy.eclipse.codeassist.completion/src/org/codehaus/groovy/eclipse/codeassist/processors/GroovyProposalTypeSearchRequestor.java @@ -90,6 +90,7 @@ public class GroovyProposalTypeSearchRequestor implements ISearchRequestor { private static final char[] NO_TYPE_NAME = {'.'}; + private static final char[] _AS_ = {' ','a','s',' '}; private static final int CHECK_CANCEL_FREQUENCY = 50; private int foundTypesCount = 0; @@ -130,8 +131,8 @@ public class GroovyProposalTypeSearchRequestor implements ISearchRequestor { // instead of inserting text, show context information only for constructors private boolean contextOnly; - private final ContentAssistContext context; - private final AssistOptions options; + final ContentAssistContext context; + final AssistOptions options; public GroovyProposalTypeSearchRequestor( ContentAssistContext context, @@ -154,7 +155,7 @@ public GroovyProposalTypeSearchRequestor( // if contextOnly then do not insert any text, only show context information this.contextOnly = (context.location == ContentAssistLocation.METHOD_CONTEXT); this.shouldAcceptConstructors = (context.location == ContentAssistLocation.METHOD_CONTEXT || context.location == ContentAssistLocation.CONSTRUCTOR); - this.completionExpression = (contextOnly ? ((MethodInfoContentAssistContext) context).methodName : context.fullCompletionExpression.replaceFirst("^new\\s+", "")); + this.completionExpression = (contextOnly ? ((MethodInfoContentAssistContext) context).methodName.replace('$', '.') : context.getQualifiedCompletionExpression()); this.groovyRewriter = new GroovyImportRewriteFactory(this.unit, this.module); this.options = new AssistOptions(javaContext.getProject().getOptions(true)); @@ -185,18 +186,62 @@ public void acceptPackage(char[] packageName) { } @Override - public void acceptConstructor( - int modifiers, - char[] simpleTypeName, - int parameterCount, - char[] signature, - char[][] parameterTypes, - char[][] parameterNames, - int typeModifiers, - char[] packageName, - int extraFlags, - String path, - AccessRestriction accessRestriction) { + public void acceptType(char[] packageName, char[] simpleTypeName, char[][] enclosingTypeNames, int modifiers, AccessRestriction accessRestriction) { + // do not check cancellation for every type to avoid performance loss + if ((foundTypesCount % CHECK_CANCEL_FREQUENCY) == 0) + checkCancel(); + foundTypesCount += 1; + + // do not propose synthetic types + if (CharOperation.contains('$', simpleTypeName)) { + return; + } + if (TypeFilter.isFiltered(packageName, simpleTypeName)) { + return; + } + if (options.checkDeprecation && (modifiers & Flags.AccDeprecated) != 0) { + return; + } + if (context.location == ContentAssistLocation.EXTENDS && (modifiers & Flags.AccFinal) != 0) { + return; + } + + if (options.checkVisibility) { + if ((modifiers & Flags.AccPublic) == 0) { + if ((modifiers & Flags.AccPrivate) != 0) + return; + + if (!CharOperation.equals(packageName, CharOperation.concatWith(unit.getPackageName(), '.'))) + return; + } + } + + int accessibility = IAccessRule.K_ACCESSIBLE; + if (accessRestriction != null) { + switch (accessRestriction.getProblemId()) { + case IProblem.DiscouragedReference: + if (options.checkDiscouragedReference) { + return; + } + accessibility = IAccessRule.K_DISCOURAGED; + break; + case IProblem.ForbiddenReference: + if (options.checkForbiddenReference) { + return; + } + accessibility = IAccessRule.K_NON_ACCESSIBLE; + break; + } + } + + if (acceptedTypes == null) + acceptedTypes = new ObjectVector(); + acceptedTypes.add(new AcceptedType(packageName, simpleTypeName, enclosingTypeNames, modifiers, accessibility)); + } + + @Override + public void acceptConstructor(int modifiers, char[] simpleTypeName, int parameterCount, char[] signature, char[][] parameterTypes, + char[][] parameterNames, int typeModifiers, char[] packageName, int extraFlags, String path, AccessRestriction accessRestriction) { if (shouldAcceptConstructors) { // do not check cancellation for every ctor to avoid performance loss @@ -243,74 +288,41 @@ public void acceptConstructor( } } - if (signature == null) { - //signature = Signature.createArraySignature(typeSignature, arrayCount) - } - if (acceptedConstructors == null) acceptedConstructors = new ObjectVector(); acceptedConstructors.add(new AcceptedCtor(modifiers, simpleTypeName, parameterCount, signature, parameterTypes, parameterNames, typeModifiers, packageName, extraFlags, accessibility)); } } - @Override - public void acceptType(char[] packageName, char[] simpleTypeName, char[][] enclosingTypeNames, int modifiers, AccessRestriction accessRestriction) { - // do not check cancellation for every type to avoid performance loss - if ((foundTypesCount % CHECK_CANCEL_FREQUENCY) == 0) - checkCancel(); - foundTypesCount += 1; - - // do not propose synthetic types - if (CharOperation.contains('$', simpleTypeName)) { - return; - } - if (TypeFilter.isFiltered(packageName, simpleTypeName)) { - return; - } - if (options.checkDeprecation && (modifiers & Flags.AccDeprecated) != 0) { - return; - } - if (context.location == ContentAssistLocation.EXTENDS && (modifiers & Flags.AccFinal) != 0) { - return; + private void checkCancel() { + if (monitor != null && monitor.isCanceled()) { + throw new OperationCanceledException(); } + } - if (options.checkVisibility) { - if ((modifiers & Flags.AccPublic) == 0) { - if ((modifiers & Flags.AccPrivate) != 0) - return; + //-------------------------------------------------------------------------- - if (!CharOperation.equals(packageName, CharOperation.concatWith(unit.getPackageName(), '.'))) - return; - } - } + List processAcceptedPackages() { + checkCancel(); + List proposals = new LinkedList<>(); + if (acceptedPackages != null && acceptedPackages.size() > 0) { + for (String packageNameStr : acceptedPackages) { + char[] packageName = packageNameStr.toCharArray(); + GroovyCompletionProposal proposal = createProposal(CompletionProposal.PACKAGE_REF, context.completionLocation); + proposal.setDeclarationSignature(packageName); + proposal.setPackageName(packageName); + proposal.setCompletion(packageName); + proposal.setReplaceRange(offset, context.completionLocation); + proposal.setTokenRange(offset, context.completionLocation); + proposal.setRelevance(Relevance.LOWEST.getRelevance()); - int accessibility = IAccessRule.K_ACCESSIBLE; - if (accessRestriction != null) { - switch (accessRestriction.getProblemId()) { - case IProblem.DiscouragedReference: - if (options.checkDiscouragedReference) { - return; - } - accessibility = IAccessRule.K_DISCOURAGED; - break; - case IProblem.ForbiddenReference: - if (options.checkForbiddenReference) { - return; - } - accessibility = IAccessRule.K_NON_ACCESSIBLE; - break; + LazyJavaCompletionProposal javaProposal = new LazyJavaCompletionProposal(proposal, javaContext); + javaProposal.setTriggerCharacters(ProposalUtils.TYPE_TRIGGERS); + javaProposal.setRelevance(proposal.getRelevance()); + proposals.add(javaProposal); } } - - if (acceptedTypes == null) - acceptedTypes = new ObjectVector(); - acceptedTypes.add(new AcceptedType(packageName, simpleTypeName, enclosingTypeNames, modifiers, accessibility)); - } - - private void checkCancel() { - if (monitor != null && monitor.isCanceled()) { - throw new OperationCanceledException(); - } + return proposals; } /** @@ -339,62 +351,61 @@ List processAcceptedTypes(JDTResolver resolver) { checkCancel(); } - AcceptedType acceptedType = (AcceptedType) acceptedTypes.elementAt(i); - char[] packageName = acceptedType.packageName; - char[] simpleTypeName = acceptedType.simpleTypeName; - char[][] enclosingTypeNames = acceptedType.enclosingTypeNames; - int modifiers = acceptedType.modifiers; - int accessibility = acceptedType.accessibility; + AcceptedType type = (AcceptedType) acceptedTypes.elementAt(i); + char[] packageName = type.packageName; + char[] simpleTypeName = type.simpleTypeName; + char[][] enclosingTypeNames = type.enclosingTypeNames; - char[] typeName; - char[] flatEnclosingTypeNames; if (enclosingTypeNames == null || enclosingTypeNames.length == 0) { - flatEnclosingTypeNames = null; - typeName = simpleTypeName; + type.qualifiedTypeName = simpleTypeName; } else { - flatEnclosingTypeNames = CharOperation.concatWith(acceptedType.enclosingTypeNames, '$'); - typeName = CharOperation.concat(flatEnclosingTypeNames, simpleTypeName, '$'); + type.qualifiedTypeName = CharOperation.concatWith(type.enclosingTypeNames, simpleTypeName, '$'); + } + type.fullyQualifiedName = CharOperation.concat(packageName, type.qualifiedTypeName, '.'); + + if (isImport) { + proposals.add(proposeType(type)); + continue next; } - char[] fullyQualifiedName = CharOperation.concat(packageName, typeName, '.'); - // get this imports from the module node + if (imports == null && resolver.getScope() != null) { initializeImportArrays(resolver.getScope()); } if (imports != null && !qualified) { - // check to see if this type is imported explicitly - for (char[][] importName : imports) { - if (CharOperation.equals(typeName, importName[0])) { - // potentially use fully qualified type name if there is already something else with the same simple name imported - proposals.add(proposeType(packageName, simpleTypeName, modifiers, accessibility, typeName, fullyQualifiedName, !CharOperation.equals(fullyQualifiedName, importName[1]))); // TODO: This last test fails to handle aliased imports + char[] fullName = CharOperation.replaceOnCopy(type.fullyQualifiedName, '$', '.'); + for (char[][] importSpec : imports) { + // check to see if this type name is imported explicitly + if (CharOperation.equals(simpleTypeName, importSpec[0])) { + int end = CharOperation.indexOf(_AS_, importSpec[1], true); + // use qualified name if there is already something with the same simple name imported + type.mustBeQualified = !CharOperation.equals(fullName, importSpec[1], 0, end > 0 ? end : importSpec[1].length); + proposals.add(proposeType(type)); continue next; } } } - if (qualified || (enclosingTypeNames == null || enclosingTypeNames.length == 0) && isCurrentPackage(packageName)) { - proposals.add(proposeType(packageName, simpleTypeName, modifiers, accessibility, typeName, fullyQualifiedName, false)); - } else if (((AcceptedType) onDemandFound.get(simpleTypeName)) == null && onDemandImports != null) { - char[] fullyQualifiedEnclosingTypeOrPackageName = null; - for (char[] importFlatName : onDemandImports) { - if (fullyQualifiedEnclosingTypeOrPackageName == null) { - if (enclosingTypeNames != null && enclosingTypeNames.length != 0) { - fullyQualifiedEnclosingTypeOrPackageName = CharOperation.concat(packageName, flatEnclosingTypeNames, '.'); - } else { - fullyQualifiedEnclosingTypeOrPackageName = packageName; - } - } - if (CharOperation.equals(fullyQualifiedEnclosingTypeOrPackageName, importFlatName)) { - acceptedType.qualifiedTypeName = typeName; - acceptedType.fullyQualifiedName = fullyQualifiedName; - onDemandFound.put(simpleTypeName, acceptedType); + // check for star import if expression is not qualified and type name not seen already and + if (!qualified && onDemandImports != null && !onDemandFound.containsKey(simpleTypeName) && + // type is an inner class or is not from the same package as the current compilation unit + ((enclosingTypeNames != null && enclosingTypeNames.length > 0) || !isCurrentPackage(packageName))) { + char[] qualifier = packageName; + if (enclosingTypeNames != null && enclosingTypeNames.length > 0) { + qualifier = CharOperation.concatWith(packageName, type.enclosingTypeNames, '.'); + } + + for (char[] importName : onDemandImports) { + if (CharOperation.equals(qualifier, importName)) { + onDemandFound.put(simpleTypeName, type); continue next; } } - char[] qualifier = (fullyQualifiedEnclosingTypeOrPackageName != null ? fullyQualifiedEnclosingTypeOrPackageName : packageName); - proposals.add(proposeType(qualifier, simpleTypeName, modifiers, accessibility, typeName, fullyQualifiedName, true)); + + type.mustBeQualified = true; } + proposals.add(proposeType(type)); } char[][] keys = onDemandFound.keyTable; @@ -405,7 +416,7 @@ List processAcceptedTypes(JDTResolver resolver) { if (keys[i] != null) { AcceptedType value = (AcceptedType) vals[i]; if (value != null) { - proposals.add(proposeType(value.packageName, value.simpleTypeName, value.modifiers, value.accessibility, value.qualifiedTypeName, value.fullyQualifiedName, value.mustBeQualified)); + proposals.add(proposeType(value)); } } } @@ -416,61 +427,62 @@ List processAcceptedTypes(JDTResolver resolver) { return proposals; } - private ICompletionProposal proposeType(char[] packageName, char[] simpleTypeName, int modifiers, int accessibility, char[] qualifiedTypeName, char[] fullyQualifiedName, boolean isQualified) { - int completionExpressionLength = context.completionExpression.length(), completionOffset = context.completionLocation - completionExpressionLength; + private ICompletionProposal proposeType(AcceptedType type) { + int completionOffset = (isImport || type.mustBeQualified ? offset : + context.completionLocation - context.completionExpression.length()); GroovyCompletionProposal proposal = createProposal(CompletionProposal.TYPE_REF, completionOffset); - proposal.setAccessibility(accessibility); - proposal.setCompletion(isQualified ? fullyQualifiedName : simpleTypeName); - proposal.setDeclarationSignature(packageName); - proposal.setFlags(modifiers); - proposal.setPackageName(packageName); - proposal.setRelevance(computeRelevanceForTypeProposal(fullyQualifiedName, accessibility, modifiers)); + proposal.setAccessibility(type.accessibility); + proposal.setCompletion(!type.mustBeQualified ? type.simpleTypeName : + CharOperation.replaceOnCopy(type.fullyQualifiedName, '$', '.')); + proposal.setDeclarationSignature(type.packageName); + proposal.setFlags(type.modifiers); + proposal.setPackageName(type.packageName); + proposal.setRelevance(computeRelevanceForTypeProposal(type.fullyQualifiedName, type.accessibility, type.modifiers)); proposal.setReplaceRange(completionOffset, context.completionLocation); - proposal.setSignature(Signature.createCharArrayTypeSignature(fullyQualifiedName, true)); + proposal.setSignature(Signature.createCharArrayTypeSignature(type.fullyQualifiedName, true)); proposal.setTokenRange(completionOffset, context.completionLocation); - proposal.setTypeName(simpleTypeName); + proposal.setTypeName(type.qualifiedTypeName); - if (qualifiedTypeName.length != simpleTypeName.length) { - char[] outerTypeName = CharOperation.subarray(qualifiedTypeName, 0, qualifiedTypeName.length - simpleTypeName.length - 1); - proposal.setDeclarationSignature(Signature.createCharArrayTypeSignature(CharOperation.concat(packageName, outerTypeName, '.'), true)); + if (type.qualifiedTypeName.length != type.simpleTypeName.length) { + char[] outerTypeName = CharOperation.subarray(type.qualifiedTypeName, 0, type.qualifiedTypeName.length - type.simpleTypeName.length - 1); + proposal.setDeclarationSignature(Signature.createCharArrayTypeSignature(CharOperation.concat(type.packageName, outerTypeName, '.'), true)); + proposal.setDeclarationPackageName(type.packageName); + proposal.setDeclarationTypeName(outerTypeName); } AbstractJavaCompletionProposal javaProposal; if (isImport) { - javaProposal = new JavaTypeCompletionProposal(String.valueOf(proposal.getCompletion()), null, completionOffset, completionExpressionLength, - ProposalUtils.getImage(proposal), ProposalUtils.createDisplayString(proposal), proposal.getRelevance(), String.valueOf(fullyQualifiedName), javaContext); + String fullyQualifiedName = String.valueOf(type.fullyQualifiedName).replace('$', '.'); // as it would appear in an import statement + javaProposal = new JavaTypeCompletionProposal(fullyQualifiedName, null, completionOffset, context.completionLocation - completionOffset, + ProposalUtils.getImage(proposal), ProposalUtils.createDisplayString(proposal), proposal.getRelevance(), fullyQualifiedName, javaContext); } else { javaProposal = new LazyJavaTypeCompletionProposal(proposal, javaContext); + + // check for required supporting type reference + if (type.qualifiedTypeName.length != type.simpleTypeName.length) { + int lastDotIndex = context.fullCompletionExpression.lastIndexOf('.'); + if (lastDotIndex > 0 && !context.getQualifiedCompletionExpression().startsWith(String.valueOf(firstSegment(type.packageName, '.')) + '.')) { + char[] typeName = CharOperation.subarray(type.qualifiedTypeName, 0, CharOperation.lastIndexOf('$', type.qualifiedTypeName)); + + // expression is partially-qualified; check type name availability + char[][] parts = qualifierAndSimpleTypeName(type.packageName, typeName); + if (!isImported(parts[0], parts[1])) { + GroovyCompletionProposal typeProposal = createProposal(CompletionProposal.TYPE_REF, offset); + typeProposal.setCompletion(CharOperation.concat(type.packageName, typeName, '.')); + typeProposal.setReplaceRange(offset, offset + lastDotIndex); + typeProposal.setSignature(Signature.createCharArrayTypeSignature(typeProposal.getCompletion(), true)); + + proposal.setRequiredProposals(new CompletionProposal[] {typeProposal}); + } + } + } } javaProposal.setTriggerCharacters(ProposalUtils.TYPE_TRIGGERS); javaProposal.setRelevance(proposal.getRelevance()); return javaProposal; } - List processAcceptedPackages() { - checkCancel(); - List proposals = new LinkedList<>(); - if (acceptedPackages != null && acceptedPackages.size() > 0) { - for (String packageNameStr : acceptedPackages) { - char[] packageName = packageNameStr.toCharArray(); - GroovyCompletionProposal proposal = createProposal(CompletionProposal.PACKAGE_REF, context.completionLocation); - proposal.setDeclarationSignature(packageName); - proposal.setPackageName(packageName); - proposal.setCompletion(packageName); - proposal.setReplaceRange(offset, context.completionLocation); - proposal.setTokenRange(offset, context.completionLocation); - proposal.setRelevance(Relevance.LOWEST.getRelevance()); - - LazyJavaCompletionProposal javaProposal = new LazyJavaCompletionProposal(proposal, javaContext); - javaProposal.setTriggerCharacters(ProposalUtils.TYPE_TRIGGERS); - javaProposal.setRelevance(proposal.getRelevance()); - proposals.add(javaProposal); - } - } - return proposals; - } - List processAcceptedConstructors(Set usedParams, JDTResolver resolver) { int n; if (acceptedConstructors == null || (n = acceptedConstructors.size()) == 0) { @@ -500,57 +512,43 @@ List processAcceptedConstructors(Set usedParams, JD checkCancel(); } - AcceptedCtor acceptedConstructor = (AcceptedCtor) acceptedConstructors.elementAt(i); + AcceptedCtor ctor = (AcceptedCtor) acceptedConstructors.elementAt(i); - final int typeModifiers = acceptedConstructor.typeModifiers; + int typeModifiers = ctor.typeModifiers; if (Flags.isInterface(typeModifiers) || Flags.isAnnotation(typeModifiers) || Flags.isEnum(typeModifiers)) { continue; } - final char[] packageName = acceptedConstructor.packageName; - final char[] simpleTypeName = acceptedConstructor.simpleTypeName; - final int modifiers = acceptedConstructor.modifiers; - final int parameterCount = acceptedConstructor.parameterCount; - final char[] signature = acceptedConstructor.signature; - final char[][] parameterTypes = acceptedConstructor.parameterTypes; - final char[][] parameterNames = acceptedConstructor.parameterNames; - final int extraFlags = acceptedConstructor.extraFlags; - final int accessibility = acceptedConstructor.accessibility; - char[] fullyQualifiedName = CharOperation.concat(packageName, simpleTypeName, '.'); - if (imports == null && resolver.getScope() != null) { initializeImportArrays(resolver.getScope()); } - // propose all constructors regardless of package, but ignore enums - if (!Flags.isEnum(typeModifiers)) { - ICompletionProposal constructorProposal = proposeConstructor(simpleTypeName, parameterCount, signature, parameterTypes, parameterNames, modifiers, packageName, typeModifiers, accessibility, fullyQualifiedName, false, extraFlags); - if (constructorProposal != null) { - proposals.add(constructorProposal); - - if (contextOnly) { - // also add all of the constructor arguments for constructors with no args and when it is the only constructor in the class - ClassNode resolved = resolver.resolve(String.valueOf(fullyQualifiedName)); - if (resolved != null) { - List constructors = resolved.getDeclaredConstructors(); - if (constructors != null && constructors.size() == 1) { - ConstructorNode constructor = constructors.get(0); - Parameter[] parameters = constructor.getParameters(); - if (parameters == null || parameters.length == 0) { - // instead of proposing no-arg constructor, propose type's properties as named arguments - proposals.remove(constructorProposal); - for (PropertyNode prop : resolved.getProperties()) { String name = prop.getName(); - if (!"metaClass".equals(name) && !usedParams.contains(name) && isCamelCaseMatch(context.completionExpression, name)) { - GroovyNamedArgumentProposal namedArgument = new GroovyNamedArgumentProposal(name, prop.getType(), null, String.valueOf(simpleTypeName)); - proposals.add(namedArgument.createJavaProposal(context, javaContext)); - } + ICompletionProposal constructorProposal = proposeConstructor(ctor); + if (constructorProposal != null) { + proposals.add(constructorProposal); + + if (contextOnly) { + // also add all of the constructor arguments for constructors with no args and when it is the only constructor in the class + ClassNode resolved = resolver.resolve(String.valueOf(ctor.fullyQualifiedName)); + if (resolved != null) { + List constructors = resolved.getDeclaredConstructors(); + if (constructors != null && constructors.size() == 1) { + ConstructorNode constructor = constructors.get(0); + Parameter[] parameters = constructor.getParameters(); + if (parameters == null || parameters.length == 0) { + // instead of proposing no-arg constructor, propose type's properties as named arguments + proposals.remove(constructorProposal); + for (PropertyNode prop : resolved.getProperties()) { String name = prop.getName(); + if (!"metaClass".equals(name) && !usedParams.contains(name) && isCamelCaseMatch(context.completionExpression, name)) { + GroovyNamedArgumentProposal namedArgument = new GroovyNamedArgumentProposal(name, prop.getType(), null, String.valueOf(ctor.simpleTypeName)); + proposals.add(namedArgument.createJavaProposal(context, javaContext)); } - for (MethodNode meth : resolved.getMethods()) { - if (!meth.isStatic() && AccessorSupport.isSetter(meth)) { String name = Introspector.decapitalize(meth.getName().substring(3)); - if (!"metaClass".equals(name) && !usedParams.contains(name) && resolved.getProperty(name) == null && isCamelCaseMatch(context.completionExpression, name)) { - GroovyNamedArgumentProposal namedArgument = new GroovyNamedArgumentProposal(name, meth.getParameters()[0].getType(), null, String.valueOf(simpleTypeName)); - proposals.add(namedArgument.createJavaProposal(context, javaContext)); - } + } + for (MethodNode meth : resolved.getMethods()) { + if (!meth.isStatic() && AccessorSupport.isSetter(meth)) { String name = Introspector.decapitalize(meth.getName().substring(3)); + if (!"metaClass".equals(name) && !usedParams.contains(name) && resolved.getProperty(name) == null && isCamelCaseMatch(context.completionExpression, name)) { + GroovyNamedArgumentProposal namedArgument = new GroovyNamedArgumentProposal(name, meth.getParameters()[0].getType(), null, String.valueOf(ctor.simpleTypeName)); + proposals.add(namedArgument.createJavaProposal(context, javaContext)); } } } @@ -560,7 +558,6 @@ List processAcceptedConstructors(Set usedParams, JD } } } - } finally { acceptedTypes = null; relevanceRule = null; @@ -568,13 +565,12 @@ List processAcceptedConstructors(Set usedParams, JD return proposals; } - private ICompletionProposal proposeConstructor(char[] simpleTypeName, int parameterCount, char[] signature, char[][] parameterTypes, char[][] parameterNames, int modifiers, char[] packageName, int typeModifiers, int accessibility, char[] fullyQualifiedName, boolean isQualified, int extraFlags) { + private ICompletionProposal proposeConstructor(AcceptedCtor ctor) { char[] completionExpressionChars = completionExpression.toCharArray(); int completionOffset = offset - 1, kind = CompletionProposal.CONSTRUCTOR_INVOCATION; if (contextOnly) { - // only show context information and only for constructors that exactly match the name - if (!CharOperation.equals(completionExpressionChars, simpleTypeName) && - !CharOperation.equals(completionExpressionChars, fullyQualifiedName)) { + // only show context information and only for constructors that exactly match + if (!CharOperation.equals(ctor.simpleTypeName, CharOperation.lastSegment(completionExpressionChars, '.'))) { return null; } kind = CompletionProposal.METHOD_REF; @@ -583,22 +579,22 @@ private ICompletionProposal proposeConstructor(char[] simpleTypeName, int parame GroovyCompletionProposal proposal = createProposal(kind, completionOffset); proposal.setIsContructor(true); - proposal.setName(simpleTypeName); - proposal.setTypeName(simpleTypeName); - proposal.setPackageName(packageName); - proposal.setDeclarationTypeName(simpleTypeName); - proposal.setDeclarationPackageName(packageName); - proposal.setDeclarationSignature(CompletionEngine.createNonGenericTypeSignature(packageName, simpleTypeName)); - proposal.setFlags(Flags.isDeprecated(typeModifiers) ? modifiers | Flags.AccDeprecated : modifiers); - proposal.setAdditionalFlags(extraFlags); - proposal.setAccessibility(accessibility); - - populateParameterInfo(proposal, parameterCount, parameterNames, parameterTypes, signature, isQualified); - populateReplacementInfo(proposal, packageName, simpleTypeName, fullyQualifiedName); // deals with context-only and imports + proposal.setName(ctor.simpleTypeName); + proposal.setTypeName(ctor.qualifiedTypeName); + proposal.setPackageName(ctor.packageName); + proposal.setDeclarationTypeName(ctor.qualifiedTypeName); + proposal.setDeclarationPackageName(ctor.packageName); + proposal.setDeclarationSignature(CompletionEngine.createNonGenericTypeSignature(ctor.packageName, ctor.qualifiedTypeName)); + proposal.setFlags(Flags.isDeprecated(ctor.typeModifiers) ? ctor.modifiers | Flags.AccDeprecated : ctor.modifiers); + proposal.setAdditionalFlags(ctor.extraFlags); + proposal.setAccessibility(ctor.accessibility); + + populateParameterInfo(proposal, ctor.parameterCount, ctor.parameterNames, ctor.parameterTypes, ctor.signature); + populateReplacementInfo(proposal, ctor.packageName, ctor.simpleTypeName, ctor.fullyQualifiedName); // deals with context-only and imports // TODO: Leverage IRelevanceRule for this? - float relevanceMultiplier = (accessibility == IAccessRule.K_ACCESSIBLE) ? 3 : 0; - relevanceMultiplier += computeRelevanceForCaseMatching(completionExpressionChars, simpleTypeName); + float relevanceMultiplier = (ctor.accessibility == IAccessRule.K_ACCESSIBLE) ? 3 : 0; + relevanceMultiplier += computeRelevanceForCaseMatching(completionExpressionChars, ctor.simpleTypeName); proposal.setRelevance(Relevance.MEDIUM_HIGH.getRelevance(relevanceMultiplier)); GroovyJavaMethodCompletionProposal lazyProposal = new GroovyJavaMethodCompletionProposal(proposal, getProposalOptions(), javaContext, null); @@ -606,7 +602,7 @@ private ICompletionProposal proposeConstructor(char[] simpleTypeName, int parame return lazyProposal; } - private void populateParameterInfo(GroovyCompletionProposal proposal, int parameterCount, char[][] parameterNames, char[][] parameterTypes, char[] signature, boolean isQualified) { + private void populateParameterInfo(GroovyCompletionProposal proposal, int parameterCount, char[][] parameterNames, char[][] parameterTypes, char[] signature) { if (parameterCount == -1) { // default constructor parameterNames = CharOperation.NO_CHAR_CHAR; @@ -618,7 +614,7 @@ private void populateParameterInfo(GroovyCompletionProposal proposal, int parame } } if (signature == null) { - proposal.setSignature(createConstructorSignature(parameterTypes, isQualified)); + proposal.setSignature(createConstructorSignature(parameterTypes, true)); } else { proposal.setSignature(CharOperation.replaceOnCopy(signature, '/', '.')); } @@ -674,13 +670,6 @@ private void populateReplacementInfo(GroovyCompletionProposal proposal, char[] p typeProposal.setReplaceRange(offset, offset + replaceLength); typeProposal.setSignature(proposal.getDeclarationSignature()); - //typeProposal.setFlags(typeModifiers); - //typeProposal.setTypeName(simpleTypeName); - //typeProposal.setPackageName(packageName); - //typeProposal.setDeclarationSignature(declarationSignature); - //typeProposal.setTokenRange(offset, offset + replaceLength); - //typeProposal.setRelevance(computeRelevanceForTypeProposal(fullyQualifiedName, accessibility, augmentedModifiers)); - proposal.setRequiredProposals(new CompletionProposal[] {typeProposal}); } } @@ -826,8 +815,8 @@ private boolean isImported(char[] packName, char[] typeName) { } } if (!imported && !conflict && !isCurrentPackage(packName) && onDemandImports != null) { - for (char[] importPack : onDemandImports) { - if (CharOperation.equals(packName, importPack)) { + for (char[] importName : onDemandImports) { + if (CharOperation.equals(packName, importName)) { imported = true; break; } @@ -850,6 +839,31 @@ private static char[] getSimpleName(ImportBinding binding) { return binding.compoundName[binding.compoundName.length - 1]; } + /** + * @see CharOperation#lastSegment + */ + private static char[] firstSegment(char[] arr, char sep) { + int pos = CharOperation.indexOf(sep, arr); + if (pos < 0) { + return arr; + } + return CharOperation.subarray(arr, 0, pos); + } + + /** + * "java.lang", "Object" -> "java.lang", "Object" + * "java.util", "Map$Entry" -> "java.util.Map", "Entry" + */ + private static char[][] qualifierAndSimpleTypeName(char[] packName, char[] typeName) { + int pos = CharOperation.lastIndexOf('$', typeName); + if (pos > 0) { + char[] typeQual = CharOperation.subarray(typeName, 0, pos); + typeName = CharOperation.subarray(typeName, pos + 1, typeName.length); + packName = CharOperation.concat(packName, CharOperation.replaceOnCopy(typeQual, '$', '.'), '.'); + } + return new char[][] {packName, typeName}; + } + //-------------------------------------------------------------------------- private static class AcceptedCtor { @@ -864,6 +878,9 @@ private static class AcceptedCtor { public int extraFlags; public int accessibility; + public final char[] qualifiedTypeName; + public final char[] fullyQualifiedName; + public AcceptedCtor( int modifiers, char[] simpleTypeName, @@ -877,7 +894,7 @@ public AcceptedCtor( int accessibility) { this.modifiers = modifiers; - this.simpleTypeName = simpleTypeName; + this.simpleTypeName = CharOperation.lastSegment(simpleTypeName, '$'); this.parameterCount = parameterCount; this.signature = signature; this.parameterTypes = parameterTypes; @@ -886,16 +903,18 @@ public AcceptedCtor( this.packageName = packageName; this.extraFlags = extraFlags; this.accessibility = accessibility; + + this.qualifiedTypeName = simpleTypeName; + this.fullyQualifiedName = CharOperation.concat(packageName, qualifiedTypeName, '.'); } @Override public String toString() { StringBuilder buffer = new StringBuilder(); buffer.append('{'); - buffer.append(packageName); - buffer.append(','); - buffer.append(simpleTypeName); + buffer.append(fullyQualifiedName); buffer.append('}'); + return buffer.toString(); } } @@ -906,9 +925,9 @@ private static class AcceptedType { public char[][] enclosingTypeNames; public int modifiers; public int accessibility; - public boolean mustBeQualified = false; - public char[] fullyQualifiedName = null; - public char[] qualifiedTypeName = null; + public boolean mustBeQualified; + public char[] fullyQualifiedName; + public char[] qualifiedTypeName; AcceptedType( char[] packageName, @@ -928,12 +947,14 @@ private static class AcceptedType { public String toString() { StringBuilder buffer = new StringBuilder(); buffer.append('{'); - buffer.append(packageName); - buffer.append(','); - buffer.append(simpleTypeName); - buffer.append(','); - buffer.append(CharOperation.concatWith(enclosingTypeNames, '$')); + if (fullyQualifiedName != null) { + buffer.append(fullyQualifiedName); + } else { + buffer.append(packageName).append('.'); + buffer.append(CharOperation.concatWith(enclosingTypeNames, simpleTypeName, '$')); + } buffer.append('}'); + return buffer.toString(); } } diff --git a/ide/org.codehaus.groovy.eclipse.codeassist.completion/src/org/codehaus/groovy/eclipse/codeassist/processors/PackageCompletionProcessor.java b/ide/org.codehaus.groovy.eclipse.codeassist.completion/src/org/codehaus/groovy/eclipse/codeassist/processors/PackageCompletionProcessor.java index ed4596d3cc..76db4dadb1 100644 --- a/ide/org.codehaus.groovy.eclipse.codeassist.completion/src/org/codehaus/groovy/eclipse/codeassist/processors/PackageCompletionProcessor.java +++ b/ide/org.codehaus.groovy.eclipse.codeassist.completion/src/org/codehaus/groovy/eclipse/codeassist/processors/PackageCompletionProcessor.java @@ -16,25 +16,16 @@ package org.codehaus.groovy.eclipse.codeassist.processors; import java.util.Collections; -import java.util.LinkedList; import java.util.List; +import java.util.regex.Pattern; import org.codehaus.groovy.ast.AnnotatedNode; import org.codehaus.groovy.ast.ModuleNode; -import org.codehaus.groovy.eclipse.codeassist.GroovyContentAssist; -import org.codehaus.groovy.eclipse.codeassist.ProposalUtils; import org.codehaus.groovy.eclipse.codeassist.requestor.ContentAssistContext; import org.codehaus.groovy.eclipse.codeassist.requestor.ContentAssistLocation; import org.codehaus.jdt.groovy.internal.compiler.ast.JDTResolver; import org.eclipse.core.runtime.IProgressMonitor; -import org.eclipse.core.runtime.IStatus; -import org.eclipse.jdt.core.IType; -import org.eclipse.jdt.core.JavaConventions; -import org.eclipse.jdt.core.JavaModelException; -import org.eclipse.jdt.core.compiler.CharOperation; -import org.eclipse.jdt.core.search.IJavaSearchConstants; import org.eclipse.jdt.groovy.search.ITypeResolver; -import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; import org.eclipse.jdt.internal.core.SearchableEnvironment; import org.eclipse.jdt.ui.text.java.JavaContentAssistInvocationContext; import org.eclipse.jface.text.contentassist.ICompletionProposal; @@ -58,136 +49,46 @@ public void setResolverInformation(ModuleNode module, JDTResolver resolver) { public List generateProposals(IProgressMonitor monitor) { ContentAssistContext context = getContext(); - char[] completionChars = getPackageCompletion(context.fullCompletionExpression); - if (completionChars == null || completionChars.length == 0 || !mightBePackage(completionChars)) { + String expression = context.getQualifiedCompletionExpression(); + if (!doPackageCompletion(context, expression)) { return Collections.emptyList(); } - if (context.location == ContentAssistLocation.PARAMETER) { - AnnotatedNode completionNode = (AnnotatedNode) context.completionNode; - if (completionNode.getStart() < completionNode.getNameStart() && - context.completionLocation >= completionNode.getNameStart()) { - return Collections.emptyList(); - } + int replacementStart; + switch (context.location) { + case ANNOTATION: + case CONSTRUCTOR: + case METHOD_CONTEXT: + // skip over "new " for constructor invocation + replacementStart = context.completionNode.getStart(); + break; + default: + replacementStart = (context.completionLocation - context.fullCompletionExpression.replaceFirst("^\\s+", "").length()); } - int expressionStart = context.completionLocation - context.fullCompletionExpression.trim().length(), - replacementLength = context.completionEnd - expressionStart; SearchableEnvironment environment = getNameEnvironment(); + int replacementLength = (context.completionEnd - replacementStart); GroovyProposalTypeSearchRequestor requestor = new GroovyProposalTypeSearchRequestor( - context, getJavaContext(), expressionStart, replacementLength, environment.nameLookup, monitor); - environment.findPackages(completionChars, requestor); - - // - List proposals = requestor.processAcceptedPackages(); - - if (lookForTypes(completionChars)) { - // not sure about findMembers; javadoc says method does not find member types - boolean findMembers = true; boolean camelCaseMatch = true; int searchFor = getSearchFor(); - environment.findTypes(completionChars, findMembers, camelCaseMatch, searchFor, requestor, monitor); + context, getJavaContext(), replacementStart, replacementLength, environment.nameLookup, monitor); + environment.findPackages(expression.toCharArray(), requestor); - // check for member types - char[] qualifier = CharOperation.subarray(completionChars, 0, CharOperation.lastIndexOf('.', completionChars)); - if (!environment.nameLookup.isPackage(CharOperation.toStrings(CharOperation.splitOn('.', qualifier)))) { - String fullyQualifiedName = resolver.resolve(String.valueOf(qualifier)).getName(); - IType outer = environment.nameLookup.findType(fullyQualifiedName, false, 0); - String prefix = String.valueOf(CharOperation.lastSegment(completionChars, '.')); - try { - for (IType inner : outer.getTypes()) { - if (ProposalUtils.looselyMatches(prefix, inner.getElementName()) && isAcceptable(inner, searchFor)) { - requestor.acceptType(inner.getPackageFragment().getElementName().toCharArray(), inner.getElementName().toCharArray(), - CharOperation.splitOn('$', outer.getTypeQualifiedName().toCharArray()), inner.getFlags(), ProposalUtils.getTypeAccessibility(inner)); - } - } - } catch (JavaModelException e) { - GroovyContentAssist.logError(e); - } - } - - // - proposals.addAll(requestor.processAcceptedTypes(resolver)); - } - return proposals; + return requestor.processAcceptedPackages(); } - /** - * Removes whitespace and does a fail-fast if a non-java identifier is found. - */ - protected char[] getPackageCompletion(String fullCompletionExpression) { - List chars = new LinkedList<>(); - if (fullCompletionExpression == null) { - return CharOperation.NO_CHAR; + protected boolean doPackageCompletion(ContentAssistContext context, String expression) { + if (expression.isEmpty() || ILLEGAL_CHARS.matcher(expression).find()) { + return false; } - char[] fullArray = fullCompletionExpression.toCharArray(); - for (int i = 0, n = fullArray.length; i < n; i += 1) { - if (Character.isWhitespace(fullArray[i])) { - continue; - } else if (Character.isJavaIdentifierPart(fullArray[i]) || fullArray[i] == '.') { - chars.add(fullArray[i]); - } else { - // fail fast if something odd is found like parens or brackets - return null; - } - } - char[] res = new char[chars.size()]; - int i = 0; - for (Character c : chars) { - res[i] = c.charValue(); - i += 1; - } - return res; - } - - /** - * Do not look for types if there is no '.'. In this case, - * type searching is handled by {@link TypeCompletionProcessor}. - */ - protected boolean lookForTypes(char[] packageCompletion) { - return CharOperation.contains('.', packageCompletion); - } - - /** - * More complete search to see if this is a valid package name. - */ - protected boolean mightBePackage(char[] packageCompletion) { - for (char[] segment : CharOperation.splitOn('.', packageCompletion)) { - if (segment.length > 0) { - // use 1.7 because backwards compatibility ensures that nothing is missed - IStatus status = JavaConventions.validateIdentifier(String.valueOf(segment), "1.7", "1.7"); - if (status.getSeverity() >= IStatus.ERROR) { - return false; - } + // check for parameter name completion + if (context.location == ContentAssistLocation.PARAMETER) { + AnnotatedNode completionNode = (AnnotatedNode) context.completionNode; + if (completionNode.getStart() < completionNode.getNameStart() && + context.completionLocation >= completionNode.getNameStart()) { + return false; } } return true; } - protected int getSearchFor() { - switch(getContext().location) { - case EXTENDS: - return IJavaSearchConstants.CLASS; - case IMPLEMENTS: - return IJavaSearchConstants.INTERFACE; - case EXCEPTIONS: - return IJavaSearchConstants.CLASS; - default: - return IJavaSearchConstants.TYPE; - } - } - - protected boolean isAcceptable(IType type, int searchFor) throws JavaModelException { - if (searchFor == IJavaSearchConstants.TYPE) { - return true; - } - int kind = TypeDeclaration.kind(type.getFlags()); - switch (kind) { - case TypeDeclaration.ENUM_DECL: - case TypeDeclaration.CLASS_DECL: - case TypeDeclaration.ANNOTATION_TYPE_DECL: - return (searchFor == IJavaSearchConstants.CLASS); - case TypeDeclaration.INTERFACE_DECL: - return (searchFor == IJavaSearchConstants.INTERFACE); - } - return false; - } + protected static final Pattern ILLEGAL_CHARS = Pattern.compile("[^\\p{javaJavaIdentifierPart}\\s\\.]"); } diff --git a/ide/org.codehaus.groovy.eclipse.codeassist.completion/src/org/codehaus/groovy/eclipse/codeassist/processors/TypeCompletionProcessor.java b/ide/org.codehaus.groovy.eclipse.codeassist.completion/src/org/codehaus/groovy/eclipse/codeassist/processors/TypeCompletionProcessor.java index 96d5ee4b29..79b6a828ea 100644 --- a/ide/org.codehaus.groovy.eclipse.codeassist.completion/src/org/codehaus/groovy/eclipse/codeassist/processors/TypeCompletionProcessor.java +++ b/ide/org.codehaus.groovy.eclipse.codeassist.completion/src/org/codehaus/groovy/eclipse/codeassist/processors/TypeCompletionProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2009-2017 the original author or authors. + * Copyright 2009-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,28 +20,36 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.function.Consumer; import org.codehaus.groovy.ast.AnnotatedNode; +import org.codehaus.groovy.ast.ClassHelper; +import org.codehaus.groovy.ast.ClassNode; import org.codehaus.groovy.ast.FieldNode; import org.codehaus.groovy.ast.ModuleNode; import org.codehaus.groovy.eclipse.codeassist.CharArraySourceBuffer; +import org.codehaus.groovy.eclipse.codeassist.GroovyContentAssist; +import org.codehaus.groovy.eclipse.codeassist.ProposalUtils; import org.codehaus.groovy.eclipse.codeassist.requestor.ContentAssistContext; import org.codehaus.groovy.eclipse.codeassist.requestor.ContentAssistLocation; import org.codehaus.groovy.eclipse.core.util.ExpressionFinder; import org.codehaus.groovy.eclipse.core.util.ExpressionFinder.NameAndLocation; import org.codehaus.jdt.groovy.internal.compiler.ast.JDTResolver; import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.jdt.core.IType; +import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.core.compiler.CharOperation; import org.eclipse.jdt.core.search.IJavaSearchConstants; import org.eclipse.jdt.groovy.search.ITypeResolver; +import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; import org.eclipse.jdt.internal.core.SearchableEnvironment; +import org.eclipse.jdt.internal.ui.text.java.AbstractJavaCompletionProposal; import org.eclipse.jdt.ui.text.java.JavaContentAssistInvocationContext; import org.eclipse.jface.text.contentassist.ICompletionProposal; public class TypeCompletionProcessor extends AbstractGroovyCompletionProcessor implements ITypeResolver { - private static final Set FIELD_MODIFIERS = Collections.unmodifiableSet( - new HashSet<>(Arrays.asList("private", "protected", "public", "static", "final"))); - + protected ModuleNode module; protected JDTResolver resolver; public TypeCompletionProcessor(ContentAssistContext context, JavaContentAssistInvocationContext javaContext, SearchableEnvironment nameEnvironment) { @@ -50,56 +58,92 @@ public TypeCompletionProcessor(ContentAssistContext context, JavaContentAssistIn @Override public void setResolverInformation(ModuleNode module, JDTResolver resolver) { + this.module = module; this.resolver = resolver; } @Override public List generateProposals(IProgressMonitor monitor) { ContentAssistContext context = getContext(); - String prefix = context.completionExpression.replaceFirst("^new\\s+", ""); - if (!canProposeTypes(context, prefix)) { + + String expression = context.getQualifiedCompletionExpression(); + if (!doTypeCompletion(context, expression)) { return Collections.emptyList(); } - int replacementLength = prefix.length(); - int replacementOffset = context.completionLocation - replacementLength; + int replacementStart; + switch (context.location) { + case ANNOTATION: + case CONSTRUCTOR: + case METHOD_CONTEXT: + // skip over "new " for constructor invocation + replacementStart = context.completionNode.getStart(); + break; + default: + replacementStart = (context.completionLocation - context.fullCompletionExpression.replaceFirst("^\\s+", "").length()); + } + + SearchableEnvironment environment = getNameEnvironment(); + int replacementLength = (context.completionEnd - replacementStart); GroovyProposalTypeSearchRequestor requestor = new GroovyProposalTypeSearchRequestor( - context, getJavaContext(), replacementOffset, replacementLength, getNameEnvironment().nameLookup, monitor); + context, getJavaContext(), replacementStart, replacementLength, environment.nameLookup, monitor); - boolean findMembers = true /*should be false when in constructor*/, camelCaseMatch = true; - getNameEnvironment().findTypes(prefix.toCharArray(), findMembers, camelCaseMatch, getSearchFor(), requestor, monitor); + int lastDotIndex = expression.lastIndexOf('.'); + // check for free variable or fully-qualified (by packages) expression + if (lastDotIndex < 0 || environment.nameLookup.isPackage(expression.substring(0, lastDotIndex).split("\\."))) { + boolean findMembers = true; // not sure about findMembers; javadoc says method does not find member types + environment.findTypes(expression.toCharArray(), findMembers, requestor.options.camelCaseMatch, getSearchFor(), requestor, monitor); + } else { + // qualified expression; requires manual inner types checking - return requestor.processAcceptedTypes(resolver); - } + String qualifier = expression.substring(0, lastDotIndex); + String pattern = expression.substring(lastDotIndex + 1, expression.length()); - protected int getSearchFor() { - switch(getContext().location) { - case EXTENDS: - return IJavaSearchConstants.CLASS; - case IMPLEMENTS: - return IJavaSearchConstants.INTERFACE; - case EXCEPTIONS: - return IJavaSearchConstants.CLASS; - case ANNOTATION: - return IJavaSearchConstants.ANNOTATION_TYPE; - default: - return IJavaSearchConstants.TYPE; + Consumer checker = (IType outerType) -> { + if (outerType != null && outerType.exists() && qualifier.endsWith(outerType.getElementName())) + try { + for (IType innerType : outerType.getTypes()) { + if (isAcceptable(innerType, getSearchFor()) && matches(pattern, innerType.getElementName(), requestor.options.camelCaseMatch)) { + requestor.acceptType(innerType.getPackageFragment().getElementName().toCharArray(), innerType.getElementName().toCharArray(), + CharOperation.splitOn('$', outerType.getTypeQualifiedName().toCharArray()), innerType.getFlags(), ProposalUtils.getTypeAccessibility(innerType)); + } + } + } catch (JavaModelException e) { + GroovyContentAssist.logError(e); + } + }; + + ClassNode outerTypeNode = resolver.resolve(qualifier); + if (!ClassHelper.DYNAMIC_TYPE.equals(outerTypeNode)) { + checker.accept(environment.nameLookup.findType(outerTypeNode.getName(), false, 0)); + } else if (qualifier.indexOf('.') < 0) { + // unknown qualifier; search for types with exact matching + environment.findTypes(qualifier.toCharArray(), true, false, 0, requestor, monitor); + List proposals = requestor.processAcceptedTypes(resolver); + for (ICompletionProposal proposal : proposals) { + if (proposal instanceof AbstractJavaCompletionProposal) { + checker.accept((IType) ((AbstractJavaCompletionProposal) proposal).getJavaElement()); + } + } + } } + + return requestor.processAcceptedTypes(resolver); } /** * Don't show types... *
    *
  • if there is no previous text (except for imports or annotations) - *
  • if completing a method or constructor parameter name - *
  • if there is a '.' + *
  • if completing a constructor, method, for loop or catch parameter name *
  • when in a class body and there is a type declaration immediately before *
*/ - private static boolean canProposeTypes(ContentAssistContext context, String prefix) { - if (prefix.length() == 0) { + protected boolean doTypeCompletion(ContentAssistContext context, String expression) { + if (expression.isEmpty()) { return (context.location == ContentAssistLocation.ANNOTATION || context.location == ContentAssistLocation.IMPORT); } + // check for parameter name completion if (context.location == ContentAssistLocation.PARAMETER) { AnnotatedNode completionNode = (AnnotatedNode) context.completionNode; if (completionNode.getStart() < completionNode.getNameStart() && @@ -107,13 +151,41 @@ private static boolean canProposeTypes(ContentAssistContext context, String pref return false; } } - if (context.fullCompletionExpression.contains(".")) { - return false; - } return !isBeforeTypeName(context); } - private static boolean isBeforeTypeName(ContentAssistContext context) { + protected int getSearchFor() { + switch(getContext().location) { + case EXTENDS: + return IJavaSearchConstants.CLASS; + case IMPLEMENTS: + return IJavaSearchConstants.INTERFACE; + case EXCEPTIONS: + return IJavaSearchConstants.CLASS; + case ANNOTATION: + return IJavaSearchConstants.ANNOTATION_TYPE; + default: + return IJavaSearchConstants.TYPE; + } + } + + protected boolean isAcceptable(IType type, int searchFor) throws JavaModelException { + if (searchFor == IJavaSearchConstants.TYPE) { + return true; + } + int kind = TypeDeclaration.kind(type.getFlags()); + switch (kind) { + case TypeDeclaration.ENUM_DECL: + case TypeDeclaration.CLASS_DECL: + case TypeDeclaration.ANNOTATION_TYPE_DECL: + return (searchFor == IJavaSearchConstants.CLASS); + case TypeDeclaration.INTERFACE_DECL: + return (searchFor == IJavaSearchConstants.INTERFACE); + } + return false; + } + + protected static boolean isBeforeTypeName(ContentAssistContext context) { if (context.location != ContentAssistLocation.CLASS_BODY) { return false; } @@ -127,4 +199,7 @@ private static boolean isBeforeTypeName(ContentAssistContext context) { } return !FIELD_MODIFIERS.contains(nameAndLocation.name.trim()); } + + protected static final Set FIELD_MODIFIERS = Collections.unmodifiableSet( + new HashSet<>(Arrays.asList("private", "protected", "public", "static", "final"))); } diff --git a/ide/org.codehaus.groovy.eclipse.codeassist.completion/src/org/codehaus/groovy/eclipse/codeassist/requestor/CompletionNodeFinder.java b/ide/org.codehaus.groovy.eclipse.codeassist.completion/src/org/codehaus/groovy/eclipse/codeassist/requestor/CompletionNodeFinder.java index 2bb933b9e7..d0fd3dc5b7 100644 --- a/ide/org.codehaus.groovy.eclipse.codeassist.completion/src/org/codehaus/groovy/eclipse/codeassist/requestor/CompletionNodeFinder.java +++ b/ide/org.codehaus.groovy.eclipse.codeassist.completion/src/org/codehaus/groovy/eclipse/codeassist/requestor/CompletionNodeFinder.java @@ -520,15 +520,9 @@ public void visitConstructorCallExpression(ConstructorCallExpression expression) // completing constructor argument (https://github.com/groovy/groovy-eclipse/issues/331) } - // GRECLIPSE-1235: completion invocation offset is outside of type name and argument expressions; it is probably after opening paren or separating comma + // completion invocation offset is outside of type name and argument expressions; it is probably after opening paren or separating comma - int offset = expression.getNameStart(), length = expression.getNameEnd() - offset + 1; - String constructorText = constructorType.getNameWithoutPackage(); - if (constructorText.length() < length) { - constructorText = constructorType.getName(); - } - - createContextForCallContext(expression, constructorType, constructorText); + createContextForCallContext(expression, constructorType, constructorType.getUnresolvedName()); } @Override diff --git a/ide/org.codehaus.groovy.eclipse.codeassist.completion/src/org/codehaus/groovy/eclipse/codeassist/requestor/ContentAssistContext.java b/ide/org.codehaus.groovy.eclipse.codeassist.completion/src/org/codehaus/groovy/eclipse/codeassist/requestor/ContentAssistContext.java index 60bc8d83ed..b0000f99f6 100644 --- a/ide/org.codehaus.groovy.eclipse.codeassist.completion/src/org/codehaus/groovy/eclipse/codeassist/requestor/ContentAssistContext.java +++ b/ide/org.codehaus.groovy.eclipse.codeassist.completion/src/org/codehaus/groovy/eclipse/codeassist/requestor/ContentAssistContext.java @@ -191,6 +191,10 @@ public String getPerceivedCompletionExpression() { return completionExpression; } + public String getQualifiedCompletionExpression() { + return fullCompletionExpression.replaceAll("^(?:@|new\\b)|\\s+", ""); + } + public VariableScope getPerceivedCompletionScope() { if (currentScope == null && completionNode != null) { new TypeInferencingVisitorFactory().createVisitor(unit).visitCompilationUnit( diff --git a/ide/org.codehaus.groovy.eclipse.codeassist.completion/src/org/codehaus/groovy/eclipse/codeassist/requestor/GroovyCompletionProposalComputer.java b/ide/org.codehaus.groovy.eclipse.codeassist.completion/src/org/codehaus/groovy/eclipse/codeassist/requestor/GroovyCompletionProposalComputer.java index 4994bc47a0..e6a9a35cd1 100644 --- a/ide/org.codehaus.groovy.eclipse.codeassist.completion/src/org/codehaus/groovy/eclipse/codeassist/requestor/GroovyCompletionProposalComputer.java +++ b/ide/org.codehaus.groovy.eclipse.codeassist.completion/src/org/codehaus/groovy/eclipse/codeassist/requestor/GroovyCompletionProposalComputer.java @@ -1,5 +1,5 @@ /* - * Copyright 2009-2017 the original author or authors. + * Copyright 2009-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -101,8 +101,9 @@ public class GroovyCompletionProposalComputer implements IJavaCompletionProposal ))); locationFactories.put(ContentAssistLocation.EXPRESSION, Collections.unmodifiableList(Arrays.asList( - new ExpressionCompletionProcessorFactory(), - new PackageCompletionProcessorFactory() + new TypeCompletionProcessorFactory(), + new PackageCompletionProcessorFactory(), + new ExpressionCompletionProcessorFactory() ))); locationFactories.put(ContentAssistLocation.EXTENDS, Collections.unmodifiableList(Arrays.asList( diff --git a/ide/org.codehaus.groovy.eclipse.codeassist.completion/src/org/codehaus/groovy/eclipse/codeassist/requestor/MethodInfoContentAssistContext.java b/ide/org.codehaus.groovy.eclipse.codeassist.completion/src/org/codehaus/groovy/eclipse/codeassist/requestor/MethodInfoContentAssistContext.java index 10ec104168..3d17126e5e 100644 --- a/ide/org.codehaus.groovy.eclipse.codeassist.completion/src/org/codehaus/groovy/eclipse/codeassist/requestor/MethodInfoContentAssistContext.java +++ b/ide/org.codehaus.groovy.eclipse.codeassist.completion/src/org/codehaus/groovy/eclipse/codeassist/requestor/MethodInfoContentAssistContext.java @@ -31,12 +31,6 @@ */ public class MethodInfoContentAssistContext extends ContentAssistContext { - /** - * the end of the method name for this context - * use instead of completionEnd for getting the method context proposal - */ - public final int methodNameEnd; - /** * The expression corresponding to call.getExpression() * use instead of completionExpression for getting the method context @@ -45,6 +39,12 @@ public class MethodInfoContentAssistContext extends ContentAssistContext { */ public final AnnotatedNode methodExpression; + /** + * the end of the method name for this context + * use instead of completionEnd for getting the method context proposal + */ + public final int methodNameEnd; + /** * The name of the method (use instead of completionExpression for getting * method context proposal) @@ -66,8 +66,8 @@ public MethodInfoContentAssistContext( int methodNameEnd) { super(completionLocation, completionExpression, fullCompletionExpression, completionNode, containingCodeBlock, lhsNode, ContentAssistLocation.METHOD_CONTEXT, unit, containingDeclaration, completionEnd); - this.methodNameEnd = methodNameEnd; this.methodExpression = methodExpression; + this.methodNameEnd = methodNameEnd; this.methodName = methodName; } diff --git a/ide/org.codehaus.groovy.eclipse.dsl/src/org/codehaus/groovy/eclipse/dsl/proposals/DSLDProposalProvider.java b/ide/org.codehaus.groovy.eclipse.dsl/src/org/codehaus/groovy/eclipse/dsl/proposals/DSLDProposalProvider.java index abaf589d99..b8bbd9830f 100644 --- a/ide/org.codehaus.groovy.eclipse.dsl/src/org/codehaus/groovy/eclipse/dsl/proposals/DSLDProposalProvider.java +++ b/ide/org.codehaus.groovy.eclipse.dsl/src/org/codehaus/groovy/eclipse/dsl/proposals/DSLDProposalProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2009-2017 the original author or authors. + * Copyright 2009-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -87,7 +87,7 @@ public List getStatementAndExpressionProposals(ContentAssistCon } if (isMethodContext) { // also add any related proposals, like those for method parameters - proposals.addAll(element.extraProposals(completionType, pattern.getResolverCache(), (Expression) ((MethodInfoContentAssistContext) context).completionNode)); + proposals.addAll(element.extraProposals(completionType, pattern.getResolverCache(), (Expression) context.completionNode)); } } }