diff --git a/gradle-language-server/build.gradle b/gradle-language-server/build.gradle index 29a8bd680..1ae8b25cd 100644 --- a/gradle-language-server/build.gradle +++ b/gradle-language-server/build.gradle @@ -14,6 +14,7 @@ repositories { dependencies { implementation "org.eclipse.lsp4j:org.eclipse.lsp4j:0.12.0" implementation "org.eclipse.lsp4j:org.eclipse.lsp4j.jsonrpc:0.12.0" + implementation "org.codehaus.groovy:groovy-eclipse-batch:3.0.8-01" } ext.mainClass = "com.microsoft.gradle.GradleLanguageServer" 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 dd84ac216..bc4308fb6 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 @@ -11,14 +11,34 @@ package com.microsoft.gradle; +import java.net.URI; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.microsoft.gradle.compile.GradleCompilationUnit; import com.microsoft.gradle.manager.GradleFilesManager; +import com.microsoft.gradle.utils.LSPUtils; +import org.codehaus.groovy.control.CompilationFailedException; +import org.codehaus.groovy.control.ErrorCollector; +import org.codehaus.groovy.control.Phases; +import org.codehaus.groovy.control.messages.Message; +import org.codehaus.groovy.control.messages.SyntaxErrorMessage; +import org.codehaus.groovy.syntax.SyntaxException; +import org.eclipse.lsp4j.Diagnostic; +import org.eclipse.lsp4j.DiagnosticSeverity; import org.eclipse.lsp4j.DidChangeConfigurationParams; import org.eclipse.lsp4j.DidChangeTextDocumentParams; import org.eclipse.lsp4j.DidChangeWatchedFilesParams; import org.eclipse.lsp4j.DidCloseTextDocumentParams; import org.eclipse.lsp4j.DidOpenTextDocumentParams; import org.eclipse.lsp4j.DidSaveTextDocumentParams; +import org.eclipse.lsp4j.PublishDiagnosticsParams; +import org.eclipse.lsp4j.Range; import org.eclipse.lsp4j.services.LanguageClient; import org.eclipse.lsp4j.services.LanguageClientAware; import org.eclipse.lsp4j.services.TextDocumentService; @@ -26,6 +46,7 @@ public class GradleServices implements TextDocumentService, WorkspaceService, LanguageClientAware { + private LanguageClient client; private GradleFilesManager gradleFilesManager; public GradleServices() { @@ -34,22 +55,29 @@ public GradleServices() { @Override public void connect(LanguageClient client) { - // TODO + this.client = client; } @Override public void didOpen(DidOpenTextDocumentParams params) { - gradleFilesManager.didOpen(params); + URI uri = URI.create(params.getTextDocument().getUri()); + gradleFilesManager.didOpen(uri, params.getTextDocument().getText()); + GradleCompilationUnit unit = this.gradleFilesManager.getCompilationUnit(uri, params.getTextDocument().getVersion()); + compile(uri, unit); } @Override public void didChange(DidChangeTextDocumentParams params) { - gradleFilesManager.didChange(params); + URI uri = URI.create(params.getTextDocument().getUri()); + gradleFilesManager.didChange(uri, params.getContentChanges().get(0)); + GradleCompilationUnit unit = this.gradleFilesManager.getCompilationUnit(uri, params.getTextDocument().getVersion()); + compile(uri, unit); } @Override public void didClose(DidCloseTextDocumentParams params) { - gradleFilesManager.didClose(params); + URI uri = URI.create(params.getTextDocument().getUri()); + gradleFilesManager.didClose(uri); } @Override @@ -66,4 +94,49 @@ public void didChangeWatchedFiles(DidChangeWatchedFilesParams params) { public void didChangeConfiguration(DidChangeConfigurationParams params) { // TODO } + + private void compile(URI uri, GradleCompilationUnit unit) { + if (unit == null) { + return; + } + Set diagnostics = new HashSet<>(); + try { + unit.compile(Phases.CANONICALIZATION); + // Send empty diagnostic if there is no error + diagnostics.add(new PublishDiagnosticsParams(uri.toString(), new ArrayList<>())); + } catch (CompilationFailedException e) { + diagnostics = generateDiagnostics(unit.getErrorCollector()); + } + for (PublishDiagnosticsParams diagnostic : diagnostics) { + client.publishDiagnostics(diagnostic); + } + } + + private Set generateDiagnostics(ErrorCollector collector) { + // URI, List + Map> diagnosticsStorage = new HashMap<>(); + for (Message error : collector.getErrors()) { + if (error instanceof SyntaxErrorMessage) { + SyntaxException exp = ((SyntaxErrorMessage)error).getCause(); + Range range = LSPUtils.toRange(exp); + Diagnostic diagnostic = new Diagnostic(); + diagnostic.setRange(range); + diagnostic.setSeverity(DiagnosticSeverity.Error); + diagnostic.setMessage(exp.getMessage()); + diagnostic.setSource("Gradle"); + if (diagnosticsStorage.containsKey(exp.getSourceLocator())) { + diagnosticsStorage.get(exp.getSourceLocator()).add(diagnostic); + } else { + List diagnostics = new ArrayList<>(); + diagnostics.add(diagnostic); + diagnosticsStorage.put(exp.getSourceLocator(), diagnostics); + } + } + } + Set diagnosticsParams = new HashSet<>(); + for (Map.Entry> entry : diagnosticsStorage.entrySet()) { + diagnosticsParams.add(new PublishDiagnosticsParams(entry.getKey(), entry.getValue())); + } + return diagnosticsParams; + } } diff --git a/gradle-language-server/src/main/java/com/microsoft/gradle/compile/GradleCompilationUnit.java b/gradle-language-server/src/main/java/com/microsoft/gradle/compile/GradleCompilationUnit.java new file mode 100644 index 000000000..e5ab55e99 --- /dev/null +++ b/gradle-language-server/src/main/java/com/microsoft/gradle/compile/GradleCompilationUnit.java @@ -0,0 +1,32 @@ +/******************************************************************************* + * 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.compile; + +import java.security.CodeSource; + +import org.codehaus.groovy.control.CompilationUnit; +import org.codehaus.groovy.control.CompilerConfiguration; + +import groovy.lang.GroovyClassLoader; + +public class GradleCompilationUnit extends CompilationUnit { + private Integer version; + + public GradleCompilationUnit(CompilerConfiguration configuration, CodeSource codeSource, GroovyClassLoader loader, Integer version) { + super(configuration, codeSource, loader); + this.version = version; + } + + public Integer getVersion() { + return this.version; + } +} diff --git a/gradle-language-server/src/main/java/com/microsoft/gradle/manager/GradleFilesManager.java b/gradle-language-server/src/main/java/com/microsoft/gradle/manager/GradleFilesManager.java index 9f29b2020..3833c2e48 100644 --- a/gradle-language-server/src/main/java/com/microsoft/gradle/manager/GradleFilesManager.java +++ b/gradle-language-server/src/main/java/com/microsoft/gradle/manager/GradleFilesManager.java @@ -18,25 +18,27 @@ import java.util.HashMap; import java.util.Map; -import org.eclipse.lsp4j.DidChangeTextDocumentParams; -import org.eclipse.lsp4j.DidCloseTextDocumentParams; -import org.eclipse.lsp4j.DidOpenTextDocumentParams; +import com.microsoft.gradle.compile.GradleCompilationUnit; + +import org.codehaus.groovy.control.CompilerConfiguration; +import org.codehaus.groovy.control.SourceUnit; +import org.codehaus.groovy.control.io.StringReaderSource; import org.eclipse.lsp4j.Position; import org.eclipse.lsp4j.Range; import org.eclipse.lsp4j.TextDocumentContentChangeEvent; +import groovy.lang.GroovyClassLoader; + public class GradleFilesManager { private Map openFiles = new HashMap<>(); + private Map unitStorage = new HashMap<>(); - public void didOpen(DidOpenTextDocumentParams params) { - URI uri = URI.create(params.getTextDocument().getUri()); - openFiles.put(uri, params.getTextDocument().getText()); + public void didOpen(URI uri, String content) { + openFiles.put(uri, content); } - public void didChange(DidChangeTextDocumentParams params) { - URI uri = URI.create(params.getTextDocument().getUri()); + public void didChange(URI uri, TextDocumentContentChangeEvent change) { String oldText = openFiles.get(uri); - TextDocumentContentChangeEvent change = params.getContentChanges().get(0); Range range = change.getRange(); if (range == null) { openFiles.put(uri, change.getText()); @@ -51,8 +53,7 @@ public void didChange(DidChangeTextDocumentParams params) { } } - public void didClose(DidCloseTextDocumentParams params) { - URI uri = URI.create(params.getTextDocument().getUri()); + public void didClose(URI uri) { openFiles.remove(uri); } @@ -91,4 +92,20 @@ public int getOffset(String string, Position position) { } return currentIndex + character; } + + public GradleCompilationUnit getCompilationUnit(URI uri, Integer version) { + if (this.unitStorage.containsKey(uri) && this.unitStorage.get(uri).getVersion().equals(version)) { + return this.unitStorage.get(uri); + } + CompilerConfiguration config = new CompilerConfiguration(); + GroovyClassLoader classLoader = new GroovyClassLoader(ClassLoader.getSystemClassLoader().getParent(), config, true); + GradleCompilationUnit unit = new GradleCompilationUnit(config, null, classLoader, version); + SourceUnit sourceUnit = new SourceUnit(uri.toString(), + new StringReaderSource(getContents(uri), unit.getConfiguration()), + unit.getConfiguration(), unit.getClassLoader(), unit.getErrorCollector()); + unit.addSource(sourceUnit); + this.unitStorage.put(uri, unit); + return unit; + } + } 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 new file mode 100644 index 000000000..75f2e2c02 --- /dev/null +++ b/gradle-language-server/src/main/java/com/microsoft/gradle/utils/LSPUtils.java @@ -0,0 +1,24 @@ +/******************************************************************************* + * 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.utils; + +import org.codehaus.groovy.syntax.SyntaxException; +import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.Range; + +public class LSPUtils { + public static Range toRange(SyntaxException exp) { + // LSP Range start from 0, while groovy classes start from 1 + return new Range(new Position(exp.getStartLine() - 1, exp.getStartColumn() - 1), + new Position(exp.getEndLine() - 1, exp.getEndColumn() - 1)); + } +}