diff --git a/extension/src/languageServer/languageServer.ts b/extension/src/languageServer/languageServer.ts index 29fe1fe3a..bc89b3803 100644 --- a/extension/src/languageServer/languageServer.ts +++ b/extension/src/languageServer/languageServer.ts @@ -6,9 +6,18 @@ import * as net from 'net'; import * as path from 'path'; import * as vscode from 'vscode'; -import { LanguageClientOptions } from 'vscode-languageclient'; +import { + DidChangeConfigurationNotification, + LanguageClientOptions, +} from 'vscode-languageclient'; import { LanguageClient, StreamInfo } from 'vscode-languageclient/node'; -import { getConfigGradleJavaHome } from '../util/config'; +import { + getConfigGradleJavaHome, + getConfigJavaImportGradleHome, + getConfigJavaImportGradleUserHome, + getConfigJavaImportGradleVersion, + getConfigJavaImportGradleWrapperEnabled, +} from '../util/config'; const CHANNEL_NAME = 'Gradle Language Server'; export async function startLanguageServer( @@ -16,6 +25,7 @@ export async function startLanguageServer( ): Promise { void vscode.window.withProgress( { location: vscode.ProgressLocation.Window }, + // eslint-disable-next-line sonarjs/cognitive-complexity (progress) => { // eslint-disable-next-line @typescript-eslint/no-unused-vars return new Promise(async (resolve, _reject) => { @@ -26,6 +36,9 @@ export async function startLanguageServer( documentSelector: [{ scheme: 'file', language: 'gradle' }], outputChannel: vscode.window.createOutputChannel(CHANNEL_NAME), outputChannelName: CHANNEL_NAME, + initializationOptions: { + settings: getGradleSettings(), + }, }; let serverOptions; if (process.env.VSCODE_DEBUG_LANGUAGE_SERVER === 'true') { @@ -82,6 +95,16 @@ export async function startLanguageServer( }); const disposable = languageClient.start(); context.subscriptions.push(disposable); + context.subscriptions.push( + vscode.workspace.onDidChangeConfiguration((e) => { + if (e.affectsConfiguration('java.import.gradle')) { + languageClient.sendNotification( + DidChangeConfigurationNotification.type, + { settings: getGradleSettings() } + ); + } + }) + ); }); } ); @@ -101,3 +124,12 @@ async function awaitServerConnection(port: string): Promise { return server; }); } + +function getGradleSettings(): unknown { + return { + gradleHome: getConfigJavaImportGradleHome(), + gradleVersion: getConfigJavaImportGradleVersion(), + gradleWrapperEnabled: getConfigJavaImportGradleWrapperEnabled(), + gradleUserHome: getConfigJavaImportGradleUserHome(), + }; +} diff --git a/gradle-language-server/build.gradle b/gradle-language-server/build.gradle index 2b2fdecba..1f5829cb2 100644 --- a/gradle-language-server/build.gradle +++ b/gradle-language-server/build.gradle @@ -16,6 +16,7 @@ dependencies { implementation "org.eclipse.lsp4j:org.eclipse.lsp4j.jsonrpc:0.12.0" implementation "org.codehaus.groovy:groovy-eclipse-batch:3.0.8-01" implementation "com.google.code.gson:gson:2.8.7" + implementation "org.apache.bcel:bcel:6.5.0" } ext.mainClass = "com.microsoft.gradle.GradleLanguageServer" diff --git a/gradle-language-server/src/main/java/com/microsoft/gradle/GradleLanguageServer.java b/gradle-language-server/src/main/java/com/microsoft/gradle/GradleLanguageServer.java index d202d5487..d1cb46e10 100644 --- a/gradle-language-server/src/main/java/com/microsoft/gradle/GradleLanguageServer.java +++ b/gradle-language-server/src/main/java/com/microsoft/gradle/GradleLanguageServer.java @@ -13,13 +13,19 @@ import java.io.IOException; import java.net.Socket; +import java.net.URI; +import java.nio.file.Paths; import java.util.Arrays; import java.util.List; +import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; +import com.google.gson.Gson; +import com.google.gson.JsonElement; import com.microsoft.gradle.semantictokens.TokenModifier; import com.microsoft.gradle.semantictokens.TokenType; +import com.microsoft.gradle.utils.LSPUtils; import org.eclipse.lsp4j.CompletionOptions; import org.eclipse.lsp4j.DocumentFilter; @@ -32,6 +38,7 @@ import org.eclipse.lsp4j.ServerCapabilities; import org.eclipse.lsp4j.TextDocumentSyncKind; import org.eclipse.lsp4j.TextDocumentSyncOptions; +import org.eclipse.lsp4j.WorkspaceFolder; import org.eclipse.lsp4j.jsonrpc.Launcher; import org.eclipse.lsp4j.services.LanguageClient; import org.eclipse.lsp4j.services.LanguageClientAware; @@ -71,6 +78,16 @@ public GradleLanguageServer() { @Override public CompletableFuture initialize(InitializeParams params) { + Map initOptions = new Gson().fromJson((JsonElement) params.getInitializationOptions(), Map.class); + // TODO: support multiple workspace folders + List workspaceFolders = params.getWorkspaceFolders(); + for (WorkspaceFolder folder : workspaceFolders) { + URI uri = URI.create(folder.getUri()); + this.gradleServices.getLibraryResolver().setWorkspacePath(Paths.get(uri)); + break; + } + Object settings = initOptions.get("settings"); + LSPUtils.applySetting(this.gradleServices, settings); ServerCapabilities serverCapabilities = new ServerCapabilities(); SemanticTokensWithRegistrationOptions semanticOptions = new SemanticTokensWithRegistrationOptions(); semanticOptions.setFull(new SemanticTokensServerFull(false)); 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 55f3dd972..3766e1512 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 @@ -21,16 +21,23 @@ import java.util.Set; import java.util.concurrent.CompletableFuture; -import com.microsoft.gradle.compile.SemanticTokenVisitor; -import com.microsoft.gradle.compile.DocumentSymbolVisitor; +import com.google.gson.Gson; +import com.google.gson.JsonElement; import com.microsoft.gradle.compile.CompletionVisitor; -import com.microsoft.gradle.compile.GradleCompilationUnit; import com.microsoft.gradle.compile.CompletionVisitor.DependencyItem; +import com.microsoft.gradle.compile.DocumentSymbolVisitor; +import com.microsoft.gradle.compile.GradleCompilationUnit; +import com.microsoft.gradle.compile.SemanticTokenVisitor; +import com.microsoft.gradle.handlers.CompletionHandler; import com.microsoft.gradle.handlers.DependencyCompletionHandler; import com.microsoft.gradle.manager.GradleFilesManager; +import com.microsoft.gradle.resolver.GradleLibraryResolver; import com.microsoft.gradle.semantictokens.SemanticToken; import com.microsoft.gradle.utils.LSPUtils; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.expr.MethodCallExpression; +import org.codehaus.groovy.ast.stmt.Statement; import org.codehaus.groovy.control.CompilationFailedException; import org.codehaus.groovy.control.ErrorCollector; import org.codehaus.groovy.control.Phases; @@ -50,6 +57,7 @@ import org.eclipse.lsp4j.DidSaveTextDocumentParams; import org.eclipse.lsp4j.DocumentSymbol; import org.eclipse.lsp4j.DocumentSymbolParams; +import org.eclipse.lsp4j.Position; import org.eclipse.lsp4j.PublishDiagnosticsParams; import org.eclipse.lsp4j.Range; import org.eclipse.lsp4j.SemanticTokens; @@ -69,12 +77,18 @@ public class GradleServices implements TextDocumentService, WorkspaceService, La private SemanticTokenVisitor semanticTokenVisitor; private DocumentSymbolVisitor documentSymbolVisitor; private CompletionVisitor completionVisitor; + private GradleLibraryResolver libraryResolver; public GradleServices() { this.gradleFilesManager = new GradleFilesManager(); this.semanticTokenVisitor = new SemanticTokenVisitor(); this.documentSymbolVisitor = new DocumentSymbolVisitor(); this.completionVisitor = new CompletionVisitor(); + this.libraryResolver = new GradleLibraryResolver(); + } + + public GradleLibraryResolver getLibraryResolver() { + return this.libraryResolver; } @Override @@ -116,7 +130,8 @@ public void didChangeWatchedFiles(DidChangeWatchedFilesParams params) { @Override public void didChangeConfiguration(DidChangeConfigurationParams params) { - // TODO + Map settings = new Gson().fromJson((JsonElement) params.getSettings(), Map.class); + LSPUtils.applySetting(this, settings); } private void compile(URI uri, GradleCompilationUnit unit) { @@ -201,6 +216,9 @@ public CompletableFuture, CompletionList>> completio } this.completionVisitor.visitCompilationUnit(uri, unit); List dependencies = this.completionVisitor.getDependencies(uri); + if (dependencies == null) { + return CompletableFuture.completedFuture(Either.forLeft(Collections.emptyList())); + } for (DependencyItem dependency : dependencies) { if (Ranges.containsPosition(dependency.getRange(), params.getPosition())) { DependencyCompletionHandler handler = new DependencyCompletionHandler(); @@ -208,6 +226,46 @@ public CompletableFuture, CompletionList>> completio .completedFuture(Either.forLeft(handler.getDependencyCompletionItems(dependency, params.getPosition()))); } } - return CompletableFuture.completedFuture(Either.forLeft(Collections.emptyList())); + // should return empty if in constants + List constants = this.completionVisitor.getConstants(uri); + for (Expression constant : constants) { + Range range = LSPUtils.toRange(constant); + if (Ranges.containsPosition(range, params.getPosition())) { + return CompletableFuture.completedFuture(Either.forLeft(Collections.emptyList())); + } + } + Set methodCalls = this.completionVisitor.getMethodCalls(uri); + List containingCalls = new ArrayList<>(); + for (MethodCallExpression call : methodCalls) { + Expression expression = call.getArguments(); + Range range = LSPUtils.toRange(expression); + if (Ranges.containsPosition(range, params.getPosition())) { + containingCalls.add(call); + } + } + containingCalls.sort((MethodCallExpression a, MethodCallExpression b) -> { + if (Ranges.containsRange(LSPUtils.toRange(a), LSPUtils.toRange(b))) { + return -1; + } + return 1; + }); + CompletionHandler handler = new CompletionHandler(); + // check again + if (containingCalls.isEmpty() && isGradleRoot(uri, params.getPosition())) { + return CompletableFuture.completedFuture(Either.forLeft(handler.getRootCompletionItems(this.libraryResolver))); + } + return CompletableFuture + .completedFuture(Either.forLeft(handler.getCompletionItems(containingCalls, this.libraryResolver))); + } + + private boolean isGradleRoot(URI uri, Position position) { + List statements = this.completionVisitor.getStatements(uri); + for (Statement statement : statements) { + Range range = LSPUtils.toRange(statement); + if (Ranges.containsPosition(range, position)) { + return false; + } + } + return true; } } diff --git a/gradle-language-server/src/main/java/com/microsoft/gradle/compile/CompletionVisitor.java b/gradle-language-server/src/main/java/com/microsoft/gradle/compile/CompletionVisitor.java index c158948b5..828463f40 100644 --- a/gradle-language-server/src/main/java/com/microsoft/gradle/compile/CompletionVisitor.java +++ b/gradle-language-server/src/main/java/com/microsoft/gradle/compile/CompletionVisitor.java @@ -12,11 +12,12 @@ import java.net.URI; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import com.microsoft.gradle.utils.LSPUtils; @@ -56,11 +57,26 @@ public Range getRange() { private URI currentUri; private Map> dependencies = new HashMap<>(); + private Map> methodCalls = new HashMap<>(); + private Map> statements = new HashMap<>(); + private Map> constants = new HashMap<>(); public List getDependencies(URI uri) { return this.dependencies.get(uri); } + public Set getMethodCalls(URI uri) { + return this.methodCalls.get(uri); + } + + public List getStatements(URI uri) { + return this.statements.get(uri); + } + + public List getConstants(URI uri) { + return this.constants.get(uri); + } + public void visitCompilationUnit(URI uri, GradleCompilationUnit compilationUnit) { this.currentUri = uri; compilationUnit.iterator().forEachRemaining(unit -> visitSourceUnit(uri, unit)); @@ -70,11 +86,16 @@ public void visitSourceUnit(URI uri, SourceUnit unit) { ModuleNode moduleNode = unit.getAST(); if (moduleNode != null) { this.dependencies.put(uri, new ArrayList<>()); + this.methodCalls.put(uri, new HashSet<>()); + this.statements.put(uri, new ArrayList<>()); + this.constants.put(uri, new ArrayList<>()); visitModule(moduleNode); } } public void visitModule(ModuleNode node) { + BlockStatement blockStatement = node.getStatementBlock(); + this.statements.put(currentUri, blockStatement.getStatements()); node.getClasses().forEach(classNode -> { super.visitClass(classNode); }); @@ -82,6 +103,7 @@ public void visitModule(ModuleNode node) { @Override public void visitMethodCallExpression(MethodCallExpression node) { + this.methodCalls.get(this.currentUri).add(node); if (node.getMethodAsString().equals("dependencies")) { this.dependencies.put(currentUri, getDependencies(node)); } @@ -139,4 +161,16 @@ private List getDependencies(ExpressionStatement expressionState } return Collections.emptyList(); } + + @Override + public void visitConstantExpression(ConstantExpression expression) { + this.constants.get(currentUri).add(expression); + super.visitConstantExpression(expression); + } + + @Override + public void visitGStringExpression(GStringExpression expression) { + this.constants.get(currentUri).add(expression); + super.visitGStringExpression(expression); + } } 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 new file mode 100644 index 000000000..33c9b6c07 --- /dev/null +++ b/gradle-language-server/src/main/java/com/microsoft/gradle/handlers/CompletionHandler.java @@ -0,0 +1,169 @@ +/******************************************************************************* + * Copyright (c) 2021 Microsoft Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Microsoft Corporation - initial API and implementation + *******************************************************************************/ +package com.microsoft.gradle.handlers; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import com.microsoft.gradle.resolver.GradleLibraryResolver; + +import org.apache.bcel.classfile.JavaClass; +import org.apache.bcel.classfile.Method; +import org.apache.bcel.generic.ObjectType; +import org.apache.bcel.generic.Type; +import org.codehaus.groovy.ast.expr.MethodCallExpression; +import org.eclipse.lsp4j.CompletionItem; +import org.eclipse.lsp4j.CompletionItemKind; +import org.eclipse.lsp4j.InsertTextFormat; + +public class CompletionHandler { + + public List getRootCompletionItems(GradleLibraryResolver resolver) { + List result = new ArrayList<>(); + List candidateClasses = new ArrayList<>(); + candidateClasses.add("org.gradle.api.Project"); + candidateClasses.add("org.gradle.api.plugins.PluginAware"); + for (String candidateClass : candidateClasses) { + result.addAll(getCompletionItemsFromClass(resolver.getGradleLibraries().get(candidateClass))); + } + return result; + } + + public List getCompletionItems(List containingCalls, + GradleLibraryResolver resolver) { + List candidateClasses = new ArrayList<>(); + candidateClasses.add("org.gradle.api.Project"); + candidateClasses.add("org.gradle.api.plugins.PluginAware"); + List matchedMethods = new ArrayList<>(); + for (int i = 0; i < containingCalls.size(); i++) { + matchedMethods.clear(); + MethodCallExpression call = containingCalls.get(i); + String methodName = call.getMethodAsString(); + for (String candidateClass : candidateClasses) { + JavaClass javaClass = resolver.getGradleLibraries().get(candidateClass); + if (javaClass == null) { + continue; + } + matchedMethods.addAll(findMatchedMethods(methodName, javaClass)); + } + if (i == containingCalls.size() - 1) { + // current is the last Closure + List results = new ArrayList<>(); + // make sure result type is unique + Set typeSets = new HashSet<>(); + for (Method method : matchedMethods) { + Type returnType = method.getReturnType(); + if (checkValidMethod(method) && typeSets.add(returnType)) { + String className = ((ObjectType) returnType).getClassName(); + results.addAll(getCompletionItemsFromClass(resolver.getGradleLibraries().get(className))); + } + } + return results; + } + candidateClasses.clear(); + for (Method method : matchedMethods) { + candidateClasses.add(((ObjectType)method.getReturnType()).getClassName()); + } + } + return Collections.emptyList(); + } + + private List findMatchedMethods(String methodName, JavaClass javaClass) { + List matchedMethods = new ArrayList<>(); + Method[] methods = javaClass.getMethods(); + for (Method method : methods) { + String name = method.getName(); + if (!checkValidMethod(method)) { + continue; + } + if (name.equals(methodName)) { + // full match + matchedMethods.add(method); + } else if (methodName.length() > 0 + && name.equals("get" + methodName.substring(0, 1).toUpperCase() + methodName.substring(1))) { + // matches dependencies -> getDependencies() + matchedMethods.add(method); + } + } + return matchedMethods; + } + + private static boolean checkValidMethod(Method method) { + return method.getReturnType() instanceof ObjectType; + } + + private List getCompletionItemsFromClass(JavaClass javaClass) { + if (javaClass == null) { + return Collections.emptyList(); + } + List results = new ArrayList<>(); + List methodNames = new ArrayList<>(); + Set resultSet = new HashSet<>(); + Method[] methods = javaClass.getMethods(); + for (Method method : methods) { + StringBuilder labelBuilder = new StringBuilder(); + String methodName = method.getName(); + methodNames.add(methodName); + labelBuilder.append(methodName); + labelBuilder.append("("); + for (Type type : method.getArgumentTypes()) { + if (type instanceof ObjectType) { + String[] classNameSplits = ((ObjectType) type).getClassName().split("\\."); + String className = classNameSplits[classNameSplits.length - 1]; + String variableName = className.substring(0, 1).toLowerCase(); + labelBuilder.append(className); + labelBuilder.append(" "); + labelBuilder.append(variableName); + labelBuilder.append(","); + } + } + if (labelBuilder.charAt(labelBuilder.length() - 1) == ',') { + labelBuilder.deleteCharAt(labelBuilder.length() - 1); + } + labelBuilder.append(")"); + String label = labelBuilder.toString(); + CompletionItem item = new CompletionItem(label); + item.setKind(CompletionItemKind.Function); + if (label.endsWith("(Closure c)")) { + // handle single closure case + StringBuilder closureInsertBuilder = new StringBuilder(); + closureInsertBuilder.append(methodName); + closureInsertBuilder.append(" {$0}"); + item.setInsertText(closureInsertBuilder.toString()); + item.setInsertTextFormat(InsertTextFormat.Snippet); + } else { + item.setInsertText(methodName); + } + if (resultSet.add(label)) { + results.add(item); + } + } + for (String methodName : methodNames) { + if (methodName.startsWith("set") && methodName.length() > 3) { + // for cases like setVersion() and getVersion(), + // we offer version as a property + String getMethod = "get" + methodName.substring(3); + if (methodNames.contains(getMethod)) { + String property = methodName.substring(3, 4).toLowerCase() + methodName.substring(4); + CompletionItem item = new CompletionItem(property); + item.setKind(CompletionItemKind.Property); + if (resultSet.add(property)) { + results.add(item); + } + } + } + } + return results; + } +} diff --git a/gradle-language-server/src/main/java/com/microsoft/gradle/resolver/GradleLibraryResolver.java b/gradle-language-server/src/main/java/com/microsoft/gradle/resolver/GradleLibraryResolver.java new file mode 100644 index 000000000..7219c933d --- /dev/null +++ b/gradle-language-server/src/main/java/com/microsoft/gradle/resolver/GradleLibraryResolver.java @@ -0,0 +1,170 @@ +/******************************************************************************* + * Copyright (c) 2021 Microsoft Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Microsoft Corporation - initial API and implementation + *******************************************************************************/ +package com.microsoft.gradle.resolver; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.file.Path; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.bcel.classfile.ClassFormatException; +import org.apache.bcel.classfile.ClassParser; +import org.apache.bcel.classfile.JavaClass; + +public class GradleLibraryResolver { + private Map gradleLibraries = new HashMap<>(); + private String gradleHome; + private String gradleVersion; + private boolean gradleWrapperEnabled; + private String gradleUserHome; + private Path workspacePath; + + public void setGradleHome(String gradleHome) { + this.gradleHome = gradleHome; + } + + public void setGradleVersion(String gradleVersion) { + this.gradleVersion = gradleVersion; + } + + public void setGradleWrapperEnabled(boolean gradleWrapperEnabled) { + this.gradleWrapperEnabled = gradleWrapperEnabled; + } + + public void setGradleUserHome(String gradleUserHome) { + this.gradleUserHome = gradleUserHome; + } + + public void setWorkspacePath(Path workspacePath) { + this.workspacePath = workspacePath; + } + + public Map getGradleLibraries() { + return this.gradleLibraries; + } + + public void resolve() { + Path gradleUserHomePath = (this.gradleUserHome == null) ? Path.of(System.getProperty("user.home"), ".gradle") + : Path.of(this.gradleUserHome); + File gradleLibFile = null; + if (this.gradleWrapperEnabled) { + gradleLibFile = findGradleLibFileWithWrapper(gradleUserHomePath); + } else if (this.gradleVersion != null) { + gradleLibFile = findGradleLibFileWithGradleDist(gradleUserHomePath, "gradle-" + this.gradleVersion); + } else if (this.gradleHome != null) { + Path libPath = Path.of(this.gradleHome).resolve("lib"); + gradleLibFile = findGradleLibFile(libPath.toFile()); + } else { + return; + } + if (gradleLibFile == null || !gradleLibFile.exists()) { + return; + } + try { + JarFile jarFile = new JarFile(gradleLibFile); + getGradleLibraries(gradleLibFile.toPath(), jarFile); + } catch (Exception e) { + // Do Nothing + } + } + + private File findGradleLibFileWithWrapper(Path gradleUserHomePath) { + if (this.workspacePath == null) { + return null; + } + Path propertiesRelativePath = Path.of("gradle", "wrapper", "gradle-wrapper.properties"); + Path propertiesPath = this.workspacePath.resolve(propertiesRelativePath); + File propertiesFile = propertiesPath.toFile(); + if (!propertiesFile.exists()) { + return null; + } + try (FileInputStream stream = new FileInputStream(propertiesFile)) { + BufferedReader reader = new BufferedReader(new InputStreamReader(stream)); + String content = null; + while ((content = reader.readLine()) != null) { + if (content.startsWith("distributionUrl")) { + Pattern p = Pattern.compile("(gradle-(?s)(.*))-bin"); + Matcher matcher = p.matcher(content); + if (matcher.find()) { + String gradleDist = matcher.group(1); + return findGradleLibFileWithGradleDist(gradleUserHomePath, gradleDist); + } + } + } + } catch (IOException e) { + // Do Nothing + } + return null; + } + + private File findGradleLibFileWithGradleDist(Path gradleUserHomePath, String gradleDist) { + Path distPath = gradleUserHomePath.resolve(Path.of("wrapper", "dists")); + File distFolder = searchInFolder(gradleDist, distPath.toFile()); + if (distFolder != null && distFolder.exists()) { + Path libPath = distFolder.toPath().resolve("lib"); + return findGradleLibFile(libPath.toFile()); + } + return null; + } + + private File searchInFolder(String gradleDist, File folder) { + for (File file : folder.listFiles()) { + if (file.isDirectory()) { + if (file.getName().equals(gradleDist)) { + return file; + } else { + File searchResult = searchInFolder(gradleDist, file); + if (searchResult != null) { + return searchResult; + } + } + } + } + return null; + } + + private File findGradleLibFile(File libPathFile) { + for (File file : libPathFile.listFiles()) { + String name = file.getName(); + if (name.startsWith("gradle-core-api") && name.endsWith(".jar")) { + return file; + } + } + return null; + } + + private void getGradleLibraries(Path jarPath, JarFile jarFile) { + Enumeration entries = jarFile.entries(); + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + if (!entry.getName().endsWith(".class")) { + continue; + } + ClassParser parser = new ClassParser(jarPath.toString(), entry.getName()); + try { + JavaClass javaClass = parser.parse(); + String className = javaClass.getClassName(); + this.gradleLibraries.put(className, javaClass); + } catch (IOException | ClassFormatException e) { + // Do Nothing + } + } + } +} diff --git a/gradle-language-server/src/main/java/com/microsoft/gradle/utils/LSPUtils.java b/gradle-language-server/src/main/java/com/microsoft/gradle/utils/LSPUtils.java index 2dbdac510..dcff3a353 100644 --- a/gradle-language-server/src/main/java/com/microsoft/gradle/utils/LSPUtils.java +++ b/gradle-language-server/src/main/java/com/microsoft/gradle/utils/LSPUtils.java @@ -10,7 +10,12 @@ *******************************************************************************/ package com.microsoft.gradle.utils; +import java.util.Map; + +import com.microsoft.gradle.GradleServices; + import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.stmt.Statement; import org.codehaus.groovy.syntax.SyntaxException; import org.eclipse.lsp4j.Position; import org.eclipse.lsp4j.Range; @@ -28,6 +33,12 @@ public static Range toRange(Expression expression) { new Position(expression.getLastLineNumber() - 1, expression.getLastColumnNumber() - 1)); } + public static Range toRange(Statement statement) { + // LSP Range start from 0, while groovy expressions start from 1 + return new Range(new Position(statement.getLineNumber() - 1, statement.getColumnNumber() - 1), + new Position(statement.getLastLineNumber() - 1, statement.getLastColumnNumber() - 1)); + } + public static Range toDependencyRange(Expression expression) { // For dependency, the string includes open/close quotes should be excluded return new Range(new Position(expression.getLineNumber() - 1, expression.getColumnNumber()), @@ -42,4 +53,14 @@ public static String getStringBeforePosition(String text, Range range, Position } return text.substring(0, position.getCharacter() - start.getCharacter()); } + + public static void applySetting(GradleServices services, Object settings) { + if (settings instanceof Map) { + services.getLibraryResolver().setGradleHome((String)((Map)settings).get("gradleHome")); + services.getLibraryResolver().setGradleVersion((String)((Map)settings).get("gradleVersion")); + services.getLibraryResolver().setGradleWrapperEnabled((Boolean)((Map)settings).get("gradleWrapperEnabled")); + services.getLibraryResolver().setGradleUserHome((String)((Map)settings).get("gradleUserHome")); + services.getLibraryResolver().resolve(); + } + } }