diff --git a/gradle-language-server/src/main/java/com/microsoft/gradle/GradleServices.java b/gradle-language-server/src/main/java/com/microsoft/gradle/GradleServices.java index 0ca17e3a6..df22fb099 100644 --- a/gradle-language-server/src/main/java/com/microsoft/gradle/GradleServices.java +++ b/gradle-language-server/src/main/java/com/microsoft/gradle/GradleServices.java @@ -298,15 +298,22 @@ public CompletableFuture, CompletionList>> completio } } Set methodCalls = this.completionVisitor.getMethodCalls(uri); - MethodCallExpression containingCall = null; + List containingCallPath = new ArrayList<>(); + MethodCallExpression lastCall = null; for (MethodCallExpression call : methodCalls) { Expression expression = call.getArguments(); Range range = LSPUtils.toRange(expression); - if (Ranges.containsPosition(range, params.getPosition()) - && (containingCall == null || Ranges.containsRange(LSPUtils.toRange(containingCall.getArguments()), - LSPUtils.toRange(call.getArguments())))) { - // find inner containing call - containingCall = call; + if (Ranges.containsPosition(range, params.getPosition())) { + lastCall = call; + if (Ranges.containsRange(LSPUtils.toRange(lastCall.getArguments()), + LSPUtils.toRange(call.getArguments()))) { + // if current call contains inside last call, nested closure. + containingCallPath.add(call); + } else if (Ranges.containsRange(LSPUtils.toRange(call.getArguments()), + LSPUtils.toRange(lastCall.getArguments()))) { + // if current call is the parent of last call, nested closure. + containingCallPath.add(containingCallPath.size() - 1, call); + } } } this.libraryResolver.loadGradleClasses(); @@ -315,11 +322,11 @@ public CompletableFuture, CompletionList>> completio CompletionHandler handler = new CompletionHandler(); // check again String projectPath = Utils.getFolderPath(uri); - if (containingCall == null && isGradleRoot(uri, params.getPosition())) { - return CompletableFuture.completedFuture(Either.forLeft(handler.getCompletionItems(null, + if (containingCallPath.isEmpty() && isGradleRoot(uri, params.getPosition())) { + return CompletableFuture.completedFuture(Either.forLeft(handler.getCompletionItems(Collections.emptyList(), Paths.get(uri).getFileName().toString(), this.libraryResolver, javaPluginsIncluded, projectPath))); } - return CompletableFuture.completedFuture(Either.forLeft(handler.getCompletionItems(containingCall, + return CompletableFuture.completedFuture(Either.forLeft(handler.getCompletionItems(containingCallPath, Paths.get(uri).getFileName().toString(), this.libraryResolver, javaPluginsIncluded, projectPath))); } diff --git a/gradle-language-server/src/main/java/com/microsoft/gradle/handlers/CompletionHandler.java b/gradle-language-server/src/main/java/com/microsoft/gradle/handlers/CompletionHandler.java index 438f29318..f5424016c 100644 --- a/gradle-language-server/src/main/java/com/microsoft/gradle/handlers/CompletionHandler.java +++ b/gradle-language-server/src/main/java/com/microsoft/gradle/handlers/CompletionHandler.java @@ -17,6 +17,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.stream.Collectors; import org.apache.bcel.classfile.Attribute; import org.apache.bcel.classfile.FieldOrMethod; import org.apache.bcel.classfile.JavaClass; @@ -35,12 +36,12 @@ public class CompletionHandler { private static String SETTING_GRADLE = "settings.gradle"; private static String DEPENDENCYHANDLER_CLASS = "org.gradle.api.artifacts.dsl.DependencyHandler"; - public List getCompletionItems(MethodCallExpression containingCall, String fileName, + public List getCompletionItems(List containingCallPath, String fileName, GradleLibraryResolver resolver, boolean javaPluginsIncluded, String projectPath) { List results = new ArrayList<>(); Set resultSet = new HashSet<>(); List delegateClassNames = new ArrayList<>(); - if (containingCall == null) { + if (containingCallPath.isEmpty()) { if (fileName.equals(BUILD_GRADLE)) { delegateClassNames.add(GradleDelegate.getDefault()); } else if (fileName.equals(SETTING_GRADLE)) { @@ -48,7 +49,8 @@ public List getCompletionItems(MethodCallExpression containingCa } results.addAll(getCompletionItemsFromExtClosures(resolver, projectPath, resultSet)); } else { - String methodName = containingCall.getMethodAsString(); + String methodName = containingCallPath.stream().map(MethodCallExpression::getMethodAsString) + .collect(Collectors.joining(".")); List re = getCompletionItemsFromExtClosures(resolver, projectPath, methodName, resultSet); results.addAll(re); List delegates = GradleDelegate.getDelegateMap().get(methodName); diff --git a/gradle-plugin/src/main/java/com/microsoft/gradle/GradleProjectModelBuilder.java b/gradle-plugin/src/main/java/com/microsoft/gradle/GradleProjectModelBuilder.java index 960f42e84..9cfade6c2 100644 --- a/gradle-plugin/src/main/java/com/microsoft/gradle/GradleProjectModelBuilder.java +++ b/gradle-plugin/src/main/java/com/microsoft/gradle/GradleProjectModelBuilder.java @@ -15,6 +15,8 @@ import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; @@ -192,31 +194,60 @@ private List getPluginClosures(Project project) { for (ExtensionSchema schema : extensionsSchema.getElements()) { TypeOf publicType = schema.getPublicType(); Class concreteClass = publicType.getConcreteClass(); - List methods = new ArrayList<>(); - List fields = new ArrayList<>(); - for (Method method : concreteClass.getMethods()) { - String name = method.getName(); - List parameterTypes = new ArrayList<>(); - for (Class parameterType : method.getParameterTypes()) { - parameterTypes.add(parameterType.getName()); - } - methods.add(new DefaultGradleMethod(name, parameterTypes, isDeprecated(method))); - int modifiers = method.getModifiers(); - // See: - // https://docs.gradle.org/current/userguide/custom_gradle_types.html#managed_properties - // we offer managed properties for an abstract getter method - if (name.startsWith("get") && name.length() > 3 && Modifier.isPublic(modifiers) - && Modifier.isAbstract(modifiers)) { - fields.add(new DefaultGradleField(name.substring(3, 4).toLowerCase() + name.substring(4), - isDeprecated(method))); + closures.addAll(buildClosure(schema.getName(), concreteClass)); + } + return closures; + } + + /** + * @param closureName + * @param concreteClass + * @return + */ + private List buildClosure(String closureName, Class concreteClass) { + List closures = new ArrayList<>(); + List methods = new ArrayList<>(); + List fields = new ArrayList<>(); + for (Method method : concreteClass.getMethods()) { + String name = method.getName(); + List parameterTypes = new ArrayList<>(); + for (Class parameterType : method.getParameterTypes()) { + parameterTypes.add(parameterType.getName()); + } + + // check for nested closure methods and include them in the final closure list + // for completions. + if (method.getGenericParameterTypes().length == 1) { + Type parameterType = method.getGenericParameterTypes()[0]; + if (parameterType.getTypeName().startsWith("org.gradle.api.Action<") + && (parameterType instanceof ParameterizedType)) { + Type[] actualTypeArguments = ((ParameterizedType) parameterType).getActualTypeArguments(); + if (actualTypeArguments.length == 1) { + try { + closures.addAll(buildClosure(closureName.concat(".").concat(name), + concreteClass.getClassLoader().loadClass(actualTypeArguments[0].getTypeName()))); + } catch (ClassNotFoundException e) { + // continue if we cannot find the extension class. + } + } } } - for (Field field : concreteClass.getFields()) { - fields.add(new DefaultGradleField(field.getName(), isDeprecated(field))); + + methods.add(new DefaultGradleMethod(name, parameterTypes, isDeprecated(method))); + int modifiers = method.getModifiers(); + // See: + // https://docs.gradle.org/current/userguide/custom_gradle_types.html#managed_properties + // we offer managed properties for an abstract getter method + if (name.startsWith("get") && name.length() > 3 && Modifier.isPublic(modifiers) + && Modifier.isAbstract(modifiers)) { + fields.add(new DefaultGradleField(name.substring(3, 4).toLowerCase() + name.substring(4), + isDeprecated(method))); } - DefaultGradleClosure closure = new DefaultGradleClosure(schema.getName(), methods, fields); - closures.add(closure); } + for (Field field : concreteClass.getFields()) { + fields.add(new DefaultGradleField(field.getName(), isDeprecated(field))); + } + closures.add(new DefaultGradleClosure(closureName, methods, fields)); return closures; }