Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Support Basic Auto Completion #971

Merged
merged 5 commits into from
Sep 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 34 additions & 2 deletions extension/src/languageServer/languageServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,26 @@
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(
context: vscode.ExtensionContext
): Promise<void> {
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<void>(async (resolve, _reject) => {
Expand All @@ -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') {
Expand Down Expand Up @@ -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() }
);
}
})
);
});
}
);
Expand All @@ -101,3 +124,12 @@ async function awaitServerConnection(port: string): Promise<StreamInfo> {
return server;
});
}

function getGradleSettings(): unknown {
return {
gradleHome: getConfigJavaImportGradleHome(),
gradleVersion: getConfigJavaImportGradleVersion(),
gradleWrapperEnabled: getConfigJavaImportGradleWrapperEnabled(),
gradleUserHome: getConfigJavaImportGradleUserHome(),
};
}
1 change: 1 addition & 0 deletions gradle-language-server/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if it's shipped together, you should update thirdpartynotice

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we update the thirdpartynotice for other dependencies?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we'd better do that.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

track in #976

}

ext.mainClass = "com.microsoft.gradle.GradleLanguageServer"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,16 @@

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;

Expand All @@ -32,6 +37,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;
Expand Down Expand Up @@ -71,6 +77,16 @@ public GradleLanguageServer() {

@Override
public CompletableFuture<InitializeResult> initialize(InitializeParams params) {
Map<?, ?> initOptions = new Gson().fromJson((JsonElement) params.getInitializationOptions(), Map.class);
// TODO: support multiple workspace folders
List<WorkspaceFolder> 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");
this.gradleServices.applySetting(settings);
ServerCapabilities serverCapabilities = new ServerCapabilities();
SemanticTokensWithRegistrationOptions semanticOptions = new SemanticTokensWithRegistrationOptions();
semanticOptions.setFull(new SemanticTokensServerFull(false));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -116,7 +130,18 @@ public void didChangeWatchedFiles(DidChangeWatchedFilesParams params) {

@Override
public void didChangeConfiguration(DidChangeConfigurationParams params) {
// TODO
Map<?, ?> settings = new Gson().fromJson((JsonElement) params.getSettings(), Map.class);
this.applySetting(settings);
}

public void applySetting(Object settings) {
if (settings instanceof Map) {
this.getLibraryResolver().setGradleHome((String)((Map<?, ?>)settings).get("gradleHome"));
this.getLibraryResolver().setGradleVersion((String)((Map<?, ?>)settings).get("gradleVersion"));
this.getLibraryResolver().setGradleWrapperEnabled((Boolean)((Map<?, ?>)settings).get("gradleWrapperEnabled"));
this.getLibraryResolver().setGradleUserHome((String)((Map<?, ?>)settings).get("gradleUserHome"));
this.getLibraryResolver().resolve();
}
}

private void compile(URI uri, GradleCompilationUnit unit) {
Expand Down Expand Up @@ -201,13 +226,52 @@ public CompletableFuture<Either<List<CompletionItem>, CompletionList>> completio
}
this.completionVisitor.visitCompilationUnit(uri, unit);
List<DependencyItem> 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();
return CompletableFuture
.completedFuture(Either.forLeft(handler.getDependencyCompletionItems(dependency, params.getPosition())));
}
}
return CompletableFuture.completedFuture(Either.forLeft(Collections.emptyList()));
// should return empty if in constants
List<Expression> 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<MethodCallExpression> methodCalls = this.completionVisitor.getMethodCalls(uri);
MethodCallExpression containingCall = 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;
}
}
CompletionHandler handler = new CompletionHandler();
// check again
if (containingCall == null && isGradleRoot(uri, params.getPosition())) {
return CompletableFuture.completedFuture(Either.forLeft(handler.getCompletionItems(null, this.libraryResolver)));
}
return CompletableFuture
.completedFuture(Either.forLeft(handler.getCompletionItems(containingCall, this.libraryResolver)));
}

private boolean isGradleRoot(URI uri, Position position) {
List<Statement> statements = this.completionVisitor.getStatements(uri);
for (Statement statement : statements) {
Range range = LSPUtils.toRange(statement);
if (Ranges.containsPosition(range, position)) {
return false;
}
}
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -56,11 +57,26 @@ public Range getRange() {

private URI currentUri;
private Map<URI, List<DependencyItem>> dependencies = new HashMap<>();
private Map<URI, Set<MethodCallExpression>> methodCalls = new HashMap<>();
private Map<URI, List<Statement>> statements = new HashMap<>();
private Map<URI, List<Expression>> constants = new HashMap<>();

public List<DependencyItem> getDependencies(URI uri) {
return this.dependencies.get(uri);
}

public Set<MethodCallExpression> getMethodCalls(URI uri) {
return this.methodCalls.get(uri);
}

public List<Statement> getStatements(URI uri) {
return this.statements.get(uri);
}

public List<Expression> 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));
Expand All @@ -70,18 +86,24 @@ 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);
});
}

@Override
public void visitMethodCallExpression(MethodCallExpression node) {
this.methodCalls.get(this.currentUri).add(node);
if (node.getMethodAsString().equals("dependencies")) {
this.dependencies.put(currentUri, getDependencies(node));
}
Expand Down Expand Up @@ -139,4 +161,16 @@ private List<DependencyItem> 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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*******************************************************************************
* 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.delegate;

import java.util.HashMap;
import java.util.Map;

public class GradleDelegate {
private static String PROJECT = "org.gradle.api.Project";
private static Map<String, String> delegateMap;

static {
delegateMap = new HashMap<>();
// plugins
delegateMap.put("application", "org.gradle.api.plugins.ApplicationPluginConvention");
delegateMap.put("base", "org.gradle.api.plugins.BasePluginConvention");
delegateMap.put("java", "org.gradle.api.plugins.JavaPluginConvention");
delegateMap.put("war", "org.gradle.api.plugins.WarPluginConvention");
// basic closures
delegateMap.put("plugins", "org.gradle.plugin.use.PluginDependenciesSpec");
delegateMap.put("configurations", "org.gradle.api.artifacts.Configuration");
delegateMap.put("dependencySubstitution", "org.gradle.api.artifacts.DependencySubstitutions");
delegateMap.put("resolutionStrategy", "org.gradle.api.artifacts.ResolutionStrategy");
delegateMap.put("artifacts", "org.gradle.api.artifacts.dsl.ArtifactHandler");
delegateMap.put("components", "org.gradle.api.artifacts.dsl.ComponentMetadataHandler");
delegateMap.put("modules", "org.gradle.api.artifacts.dsl.ComponentModuleMetadataHandler");
delegateMap.put("dependencies", "org.gradle.api.artifacts.dsl.DependencyHandler");
delegateMap.put("repositories", "org.gradle.api.artifacts.dsl.RepositoryHandler");
delegateMap.put("publishing", "org.gradle.api.publish.PublishingExtension");
delegateMap.put("publications", "org.gradle.api.publish.PublicationContainer");
delegateMap.put("sourceSets", "org.gradle.api.tasks.SourceSet");
delegateMap.put("distributions", "org.gradle.api.distribution.Distribution");
delegateMap.put("fileTree", "org.gradle.api.file.ConfigurableFileTree");
delegateMap.put("copySpec", "org.gradle.api.file.CopySpec");
delegateMap.put("exec", "org.gradle.process.ExecSpec");
delegateMap.put("files", "org.gradle.api.file.ConfigurableFileCollection");
delegateMap.put("task", "org.gradle.api.Task");
}

public static Map<String, String> getDelegateMap() {
return delegateMap;
}

public static String getDefault() {
return PROJECT;
}
}
Loading