Skip to content

Commit

Permalink
Fix for issue #410: improve method completion with '{' trigger
Browse files Browse the repository at this point in the history
[].sort|
  sort(Closure) proposal -> [].sort { it } -- "it" selected
  sort(boolean) proposal -> [].sort(mutate) { -- "mutate" selected
  sort(boolean, Closure) proposal -> [].sort(mutate) { it } -- ditto
  sort(Comparator) proposal -> [].sort { o1, o2 -> } -- 1st param select

NOTE: Named parameters supported but parameter guessing not supported
  • Loading branch information
eric-milles committed Jan 31, 2018
1 parent 97bbd23 commit 612b3f3
Show file tree
Hide file tree
Showing 8 changed files with 440 additions and 115 deletions.
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -46,6 +46,7 @@ import org.junit.runners.Suite
org.codehaus.groovy.eclipse.codeassist.tests.ProposalProviderAndFilterTests,
org.codehaus.groovy.eclipse.codeassist.tests.RelevanceTests,
org.codehaus.groovy.eclipse.codeassist.tests.StaticImportsCompletionTests,
org.codehaus.groovy.eclipse.codeassist.tests.TriggerCharacterCompletionTests,
org.codehaus.groovy.eclipse.codeassist.tests.TypeCompletionTests,
org.codehaus.groovy.eclipse.codeassist.tests.TypeCompletionTests2,

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
/*
* 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.codehaus.groovy.eclipse.codeassist.tests

import static org.eclipse.jdt.core.formatter.DefaultCodeFormatterConstants.*

import org.codehaus.groovy.eclipse.codeassist.GroovyContentAssist
import org.eclipse.jdt.core.JavaCore
import org.eclipse.jface.preference.IPreferenceStore
import org.eclipse.jface.text.Document
import org.eclipse.jface.text.IDocument
import org.eclipse.jface.text.contentassist.ICompletionProposal
import org.junit.Assert
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import org.junit.runners.Parameterized.Parameter
import org.junit.runners.Parameterized.Parameters

@RunWith(Parameterized)
final class TriggerCharacterCompletionTests extends CompletionTestSuite {

@Parameter(0)
public List options

@Parameters(name='{0}')
static Iterable parameters() {
def prefs = [
GroovyContentAssist.NAMED_ARGUMENTS,
GroovyContentAssist.CLOSURE_BRACKETS,
GroovyContentAssist.CLOSURE_NOPARENS,
//GroovyContentAssist.PARAMETER_GUESSING
]

// produce every combination of all prefs, each paired with a boolean
prefs.collect { [[it], [true, false]].combinations() }.combinations()
}

private static final IPreferenceStore groovyPrefs = GroovyContentAssist.default.preferenceStore

private static boolean isEnabled(String pref) {
if (pref.startsWith(GroovyContentAssist.PLUGIN_ID)) {
return groovyPrefs.getBoolean(pref)
} else {
return (JavaCore.getOption(pref) ==~ (JavaCore.ENABLED + '|' + JavaCore.INSERT))
}
}

@Before
void setUp() {
options.each { key, val -> groovyPrefs.setValue(key, val) }
}

/**
* Tries each completion proposal for <code>sort</code> using the trigger character <code>'{'</code>.
*/
@Test
void testTriggerForMethodProposals() {
String contents = '[].sort'; int offset = contents.length()
ICompletionProposal[] proposals = orderByRelevance(createProposalsAtOffset(contents, offset))
/* expecting:
sort(Comparator c) : void - List
sort() : List - DefaultGroovyMethods
sort(Closure closure) : List - DefaultGroovyMethods
sort(boolean mutate) : List - DefaultGroovyMethods
sort(boolean mutate, Closure closure) : List - DefaultGroovyMethods
sort(boolean mutate, Comparator comparator) : List - DefaultGroovyMethods
*/

for (proposal in proposals) {
if (proposal.toString().contains('()')) {
continue // '{' is not a valid trigger
}

StringBuilder closure = new StringBuilder()
closure << '{'
if (true || isEnabled(FORMATTER_INSERT_SPACE_AFTER_OPENING_BRACE_IN_ARRAY_INITIALIZER)) {
closure << ' '
}
if (proposal.toString().contains('Closure')) {
closure << 'it'
} else {
closure << 'o1, o2 ->'
}
if (true || isEnabled(FORMATTER_INSERT_SPACE_BEFORE_CLOSING_BRACE_IN_ARRAY_INITIALIZER)) {
closure << ' '
}
closure << '}'

//
StringBuilder expected = new StringBuilder(contents)
if (!proposal.toString().contains('mutate')) {
if (isEnabled(GroovyContentAssist.CLOSURE_NOPARENS)) {
if (isEnabled(FORMATTER_INSERT_SPACE_BEFORE_OPENING_BRACE_IN_BLOCK)) {
expected << ' '
}
expected << closure
} else {
if (isEnabled(FORMATTER_INSERT_SPACE_BEFORE_OPENING_PAREN_IN_METHOD_INVOCATION)) {
expected << ' '
}
expected << '('

if (isEnabled(FORMATTER_INSERT_SPACE_AFTER_OPENING_PAREN_IN_METHOD_INVOCATION)) {
expected << ' '
}
if (isEnabled(GroovyContentAssist.NAMED_ARGUMENTS)) {
expected << (proposal.toString() =~ /(\w+)\)/)[0][1] << ': '
}
expected << closure

if (isEnabled(FORMATTER_INSERT_SPACE_BEFORE_CLOSING_PAREN_IN_METHOD_INVOCATION)) {
expected << ' '
}
expected << ')'
}
} else /*proposal.toString().contains('mutate')*/ {
if (isEnabled(FORMATTER_INSERT_SPACE_BEFORE_OPENING_PAREN_IN_METHOD_INVOCATION)) {
expected << ' '
}
expected << '('

if (isEnabled(FORMATTER_INSERT_SPACE_AFTER_OPENING_PAREN_IN_METHOD_INVOCATION)) {
expected << ' '
}
if (isEnabled(GroovyContentAssist.NAMED_ARGUMENTS)) {
expected << 'mutate: '
}
expected << 'mutate'

if (!(proposal.toString() =~ /Closure|Comparator/)) {
if (isEnabled(FORMATTER_INSERT_SPACE_BEFORE_CLOSING_PAREN_IN_METHOD_INVOCATION)) {
expected << ' '
}
expected << ')'

if (isEnabled(FORMATTER_INSERT_SPACE_BEFORE_OPENING_BRACE_IN_BLOCK)) {
expected << ' '
}
expected << '{'
} else {
if (!isEnabled(GroovyContentAssist.CLOSURE_NOPARENS)) {
if (isEnabled(FORMATTER_INSERT_SPACE_BEFORE_COMMA_IN_METHOD_INVOCATION_ARGUMENTS)) {
expected << ' '
}
expected << ','

if (isEnabled(FORMATTER_INSERT_SPACE_AFTER_COMMA_IN_METHOD_INVOCATION_ARGUMENTS)) {
expected << ' '
}
if (isEnabled(GroovyContentAssist.NAMED_ARGUMENTS)) {
expected << (proposal.toString() =~ /(\w+)\)/)[0][1] << ': '
}
} else {
if (isEnabled(FORMATTER_INSERT_SPACE_BEFORE_CLOSING_PAREN_IN_METHOD_INVOCATION)) {
expected << ' '
}
expected << ')'

if (isEnabled(FORMATTER_INSERT_SPACE_BEFORE_OPENING_BRACE_IN_BLOCK)) {
expected << ' '
}
}
expected << closure

if (!isEnabled(GroovyContentAssist.CLOSURE_NOPARENS)) {
if (isEnabled(FORMATTER_INSERT_SPACE_BEFORE_CLOSING_PAREN_IN_METHOD_INVOCATION)) {
expected << ' '
}
expected << ')'
}
}
}

IDocument document = new Document(contents)
proposal.apply(document, '{' as char, offset)

Assert.assertEquals(proposal.toString(), expected.normalize(), document.get().normalize())
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
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.jdt.groovy.internal.SimplifiedExtendedCompletionContext;
import org.codehaus.jdt.groovy.model.GroovyProjectFacade;
import org.eclipse.jdt.core.IField;
import org.eclipse.jdt.core.IJavaElement;
Expand All @@ -33,14 +32,16 @@
import org.eclipse.jdt.core.ITypeHierarchy;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.Signature;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.groovy.core.util.GroovyUtils;
import org.eclipse.jdt.groovy.search.VariableScope;
import org.eclipse.jdt.groovy.search.VariableScope.VariableInfo;
import org.eclipse.jdt.internal.codeassist.InternalExtendedCompletionContext;
import org.eclipse.jdt.internal.core.JavaElement;
import org.eclipse.jdt.internal.core.LocalVariable;
import org.eclipse.jdt.internal.core.SourceField;

public class GroovyExtendedCompletionContext extends SimplifiedExtendedCompletionContext {
public class GroovyExtendedCompletionContext extends InternalExtendedCompletionContext {

private static class PropertyVariant extends SourceField implements IField {
private final IMethod baseMethod;
Expand Down Expand Up @@ -81,6 +82,8 @@ private static String toFieldName(IMethod method) {
private final Map<String, IJavaElement[]> visibleElements;

public GroovyExtendedCompletionContext(ContentAssistContext context, VariableScope currentScope) {
super(null, null, null, null, null, null, null, null, null);

this.context = context;
this.currentScope = currentScope;
this.visibleElements = new HashMap<>();
Expand Down Expand Up @@ -196,33 +199,50 @@ public void addFields(ClassNode targetType, Map<String, IJavaElement> visibleEle
}
}

private ClassNode toClassNode(String typeSignature) {
protected ClassNode toClassNode(char[] typeSignature) {
int dims = Signature.getArrayCount(typeSignature);
String noArray = Signature.getElementType(typeSignature);
String qualifiedName = getQualifiedName(noArray);
ClassNode resolved;
if (typeSignature.length() == 1 + dims) { // is primitive type
resolved = /*ClassHelper.getWrapper(*/ClassHelper.make(qualifiedName)/*)*/;
char[] base = Signature.getElementType(typeSignature);
char[] name = CharOperation.concat(Signature.getSignatureQualifier(base), Signature.getSignatureSimpleName(base), '.');

ClassNode node;
if (typeSignature.length == (1 + dims)) { // primitive type
node = ClassHelper.make(String.valueOf(name));
} else {
try {
resolved = context.unit.getModuleInfo(false).resolver.resolve(qualifiedName);
} catch (NullPointerException e) {
// ignore; likely DSL support not available
resolved = VariableScope.OBJECT_CLASS_NODE;
}
node = resolve(String.valueOf(name));
}
while (dims-- > 0) {
node = node.makeArray();
}
return node;
}

protected ClassNode toClassNode(String typeSignature) {
int dims = Signature.getArrayCount(typeSignature);
String base = Signature.getElementType(typeSignature);
String name = Signature.getSignatureSimpleName(base);
String qual = Signature.getSignatureQualifier(base);
if (qual.length() > 0) {
name = qual + '.' + name;
}

ClassNode node;
if (typeSignature.length() == (1 + dims)) { // primitive type
node = ClassHelper.make(name);
} else {
node = resolve(name);
}
for (int i = 0; i < dims; i += 1) {
resolved = resolved.makeArray();
while (dims-- > 0) {
node = node.makeArray();
}
return resolved;
return node;
}

private String getQualifiedName(String typeSignature) {
String qualifier = Signature.getSignatureQualifier(typeSignature);
String qualifiedName = Signature.getSignatureSimpleName(typeSignature);
if (qualifier.length() > 0) {
qualifiedName = qualifier + "." + qualifiedName;
protected ClassNode resolve(String fullyQualifiedTypeName) {
try {
return context.unit.getModuleInfo(false).resolver.resolve(fullyQualifiedTypeName);
} catch (NullPointerException e) {
// ignore; likely DSL support not available
return VariableScope.OBJECT_CLASS_NODE;
}
return qualifiedName;
}
}
Loading

0 comments on commit 612b3f3

Please sign in to comment.