diff --git a/idea-extensions/idea-boot-java/.gitignore b/idea-extensions/idea-boot-java/.gitignore new file mode 100644 index 000000000..2b98bba13 --- /dev/null +++ b/idea-extensions/idea-boot-java/.gitignore @@ -0,0 +1,3 @@ +/resources/server/language-server.jar +/.idea +*.iml diff --git a/idea-extensions/idea-boot-java/README.md b/idea-extensions/idea-boot-java/README.md new file mode 100644 index 000000000..debece184 --- /dev/null +++ b/idea-extensions/idea-boot-java/README.md @@ -0,0 +1,15 @@ +# Spring Boot Java Support for IntelliJ IDEA (_Experimental_) +The support is based on IntelliJ LSP client plugin https://github.com/gtache/intellij-lsp + +## Dev Instructions +1. Clone and import the project into IntelliJ. ()Project is Scala based hence accept whatever IntelliJ suggests about having Scala) +2. Build `boot-java-language-server` maven project from STS4 (https://github.com/spring-projects/sts4/tree/master/headless-services/boot-java-language-server) +3. Copy the built `jar` file into this project `/resources/server` folder and name it `language-server.jar` +4. Install `LSP Support` plugin into IntelliJ as explained here: https://www.jetbrains.com/help/idea/installing-updating-and-uninstalling-repository-plugins.html +5. For some reason it's not enough for the dev env, hence also download the same plugin from https://www.jetbrains.com/help/idea/installing-updating-and-uninstalling-repository-plugins.html +6. Unzip the downloaded file and copy the folder into IntelliJ `pligins` folder +7. Select `idea-boot-java` project in IntelliJ go to `File -> Project Structure...` and then `Platform Settings -> SDKs` +8. Select `IntelliJ IDEA` in the middle pane. Find `+` in the bottom left corner of the right-most pane and click on it (list of jars within IntelliJ SDK) +9. Find all `Jar`s under the `IntelliJ/plugins/LSP` folder, select all of them and press `Open` in the dialog. This will ensure that your target platform has necessary LSP jars +10. Click on `Run -> Run...`, create new `Plugin` launch config. Select `Use classpath of module` to be `idea-boot-java`, select `JRE` to the `IntelliJ IDEA` SDK from step 8 +11. Run the configuration. This should start IntelliJ runtime workbench where `idea-boot-java` LS plugin is available \ No newline at end of file diff --git a/idea-extensions/idea-boot-java/resources/META-INF/plugin.xml b/idea-extensions/idea-boot-java/resources/META-INF/plugin.xml new file mode 100644 index 000000000..19a8b26b5 --- /dev/null +++ b/idea-extensions/idea-boot-java/resources/META-INF/plugin.xml @@ -0,0 +1,38 @@ + + org.spring.tools.boot-java.ls + Spring Boot Java Support + 1.0 + YourCompany + + + most HTML tags may be used + ]]> + + + most HTML tags may be used + ]]> + + + + + + + com.intellij.modules.lang + com.github.gtache.lsp + + + + + + + + + + + + + + \ No newline at end of file diff --git a/idea-extensions/idea-boot-java/resources/icons/boot-icon.png b/idea-extensions/idea-boot-java/resources/icons/boot-icon.png new file mode 100644 index 000000000..de88ef0d5 Binary files /dev/null and b/idea-extensions/idea-boot-java/resources/icons/boot-icon.png differ diff --git a/idea-extensions/idea-boot-java/src/org/spring/tools/boot/java/ls/BootJavaPreloadingActivity.scala b/idea-extensions/idea-boot-java/src/org/spring/tools/boot/java/ls/BootJavaPreloadingActivity.scala new file mode 100644 index 000000000..b91bfced8 --- /dev/null +++ b/idea-extensions/idea-boot-java/src/org/spring/tools/boot/java/ls/BootJavaPreloadingActivity.scala @@ -0,0 +1,66 @@ +package org.spring.tools.boot.java.ls + +import java.io.{BufferedReader, File, InputStreamReader} +import java.lang.ProcessBuilder +import java.net.URI +import java.nio.file.{Files, Paths} + +import com.github.gtache.lsp.client.languageserver.serverdefinition.{LanguageServerDefinition, RawCommandServerDefinition} +import com.intellij.openapi.application.PreloadingActivity +import com.intellij.openapi.progress.ProgressIndicator + +class BootJavaPreloadingActivity extends PreloadingActivity { + + override def preload(indicator: ProgressIndicator): Unit = { + val javaHome: String = /*sys.env.get("java.home").get*/ System.getProperty("java.home") + val javaVersion: String = /*sys.env.get("java.version").get*/ System.getProperty("java.version") + +// val v = sys.process.Process(Seq("java", "--version")).!! + + if (javaHome == null) { + // TODO throw error? + return + } + + val javaPath = Paths.get(javaHome) + + val javaExec = javaPath.resolve(Paths.get("bin", "java")) + + if (!Files.exists(javaExec)) { + // TODO throw error? + return + } + + var classpath = getClass().getResource("/server/language-server.jar").getPath() + + + if (javaVersion.startsWith("1.8")) { + var toolsJar = javaPath.resolve(Paths.get("lib", "tools.jar")) + if (Files.exists(toolsJar)) { + classpath += ":" + toolsJar + } else { + toolsJar = javaPath.resolve(Paths.get("..", "lib", "tools.jar")) + if (Files.exists(toolsJar)) { + classpath += ":" + toolsJar + } else { + // TODO: tools.jar doesn't exist + } + } + } else if (javaVersion.startsWith("9.")) { + // all good - Java 9 has tools.jar on the classpath + } else { + // Error - not Java 8 or 9 + } + + LanguageServerDefinition.register( + new StsServerDefinition("java", Array( + javaExec.toString(), + "-Xdebug", + "-agentlib:jdwp=transport=dt_socket,server=y,address=7999,suspend=n", + "-cp", + classpath, + "org.springframework.boot.loader.JarLauncher" + ))) + } + +} \ No newline at end of file diff --git a/idea-extensions/idea-boot-java/src/org/spring/tools/boot/java/ls/HighlightParams.java b/idea-extensions/idea-boot-java/src/org/spring/tools/boot/java/ls/HighlightParams.java new file mode 100644 index 000000000..bf2b68a94 --- /dev/null +++ b/idea-extensions/idea-boot-java/src/org/spring/tools/boot/java/ls/HighlightParams.java @@ -0,0 +1,53 @@ +/******************************************************************************* + * Copyright (c) 2017 Pivotal, Inc. + * 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: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.spring.tools.boot.java.ls; + +import java.util.List; + +import org.eclipse.lsp4j.Range; +import org.eclipse.lsp4j.TextDocumentIdentifier; + +public class HighlightParams { + + //TODO: Identical copy of this exists in org.springframework.ide.vscode.commons.languageserver.HighlightParams + // But because that is only built in plain maven jar (not osgi) we can't use it. We should find a solution + // for this somehow (i.e. build a version of some of our 'plain maven' jars as osgi bundles and publish on + // a update site. + + private TextDocumentIdentifier doc; + private List ranges; + + public HighlightParams() { + } + + public HighlightParams(TextDocumentIdentifier doc, List ranges) { + super(); + this.doc = doc; + this.ranges = ranges; + } + public TextDocumentIdentifier getDoc() { + return doc; + } + public void setDoc(TextDocumentIdentifier doc) { + this.doc = doc; + } + public List getRanges() { + return ranges; + } + public void setRanges(List ranges) { + this.ranges = ranges; + } + + @Override + public String toString() { + return "HighlightParams [doc=" + doc + ", ranges=" + ranges + "]"; + } +} diff --git a/idea-extensions/idea-boot-java/src/org/spring/tools/boot/java/ls/ProgressParams.java b/idea-extensions/idea-boot-java/src/org/spring/tools/boot/java/ls/ProgressParams.java new file mode 100644 index 000000000..e14dcac90 --- /dev/null +++ b/idea-extensions/idea-boot-java/src/org/spring/tools/boot/java/ls/ProgressParams.java @@ -0,0 +1,89 @@ +/******************************************************************************* + * Copyright (c) 2016-2017 Pivotal, Inc. + * 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: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ + +package org.spring.tools.boot.java.ls; + +public class ProgressParams { + + /** + * An id representing the enitity for which progress messages are to be shown. + */ + private String id; + + /** + * Updates the current statusMsg associated with a given the id. If null, then the message + * is cleared. + */ + private String statusMsg; + + + public ProgressParams() { + } + + public ProgressParams(String id, String statusMsg) { + super(); + this.id = id; + this.statusMsg = statusMsg; + } + + @Override + public String toString() { + return "ProgressParams [id=" + id + ", statusMsg=" + statusMsg + "]"; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((id == null) ? 0 : id.hashCode()); + result = prime * result + ((statusMsg == null) ? 0 : statusMsg.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + ProgressParams other = (ProgressParams) obj; + if (id == null) { + if (other.id != null) + return false; + } else if (!id.equals(other.id)) + return false; + if (statusMsg == null) { + if (other.statusMsg != null) + return false; + } else if (!statusMsg.equals(other.statusMsg)) + return false; + return true; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getStatusMsg() { + return statusMsg; + } + + public void setStatusMsg(String statusMsg) { + this.statusMsg = statusMsg; + } + +} diff --git a/idea-extensions/idea-boot-java/src/org/spring/tools/boot/java/ls/SpringBootGutterIconRenderer.java b/idea-extensions/idea-boot-java/src/org/spring/tools/boot/java/ls/SpringBootGutterIconRenderer.java new file mode 100644 index 000000000..f7bb40545 --- /dev/null +++ b/idea-extensions/idea-boot-java/src/org/spring/tools/boot/java/ls/SpringBootGutterIconRenderer.java @@ -0,0 +1,35 @@ +package org.spring.tools.boot.java.ls; + +import com.google.common.base.Supplier; +import com.google.common.base.Suppliers; +import com.intellij.openapi.editor.markup.GutterIconRenderer; +import com.intellij.openapi.util.IconLoader; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; + +/** + * @author Alex Boyko + */ +public class SpringBootGutterIconRenderer extends GutterIconRenderer { + + public static SpringBootGutterIconRenderer INSTANCE = new SpringBootGutterIconRenderer(); + + private Supplier icon = Suppliers.memoize(() -> IconLoader.getIcon("/icons/boot-icon.png")); + + @Override + public boolean equals(Object o) { + return this == o; + } + + @Override + public int hashCode() { + return 0; + } + + @NotNull + @Override + public Icon getIcon() { + return icon.get(); + } +} diff --git a/idea-extensions/idea-boot-java/src/org/spring/tools/boot/java/ls/StsLanguageClientImpl.scala b/idea-extensions/idea-boot-java/src/org/spring/tools/boot/java/ls/StsLanguageClientImpl.scala new file mode 100644 index 000000000..423020ce5 --- /dev/null +++ b/idea-extensions/idea-boot-java/src/org/spring/tools/boot/java/ls/StsLanguageClientImpl.scala @@ -0,0 +1,72 @@ +package org.spring.tools.boot.java.ls + +import java.awt.Color + +import com.github.gtache.lsp.client.LanguageClientImpl +import com.github.gtache.lsp.client.languageserver.wrapper.LanguageServerWrapper +import com.github.gtache.lsp.editor.EditorEventManager +import com.github.gtache.lsp.utils.FileUtils +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.editor.markup._ +import org.eclipse.lsp4j.Range +import org.eclipse.lsp4j.jsonrpc.services.JsonNotification +import org.eclipse.lsp4j.services.LanguageServer + +import scala.collection.JavaConversions +import scala.collection.mutable.ListBuffer + +/** + * @author Alex Boyko + */ +class StsLanguageClientImpl extends LanguageClientImpl { + + private var serverWrapper: LanguageServerWrapper = _ + private var rangeHighlights = scala.collection.mutable.Map[String, ListBuffer[RangeHighlighter]]() + + override def connect(server: LanguageServer, wrapper: LanguageServerWrapper): Unit = { + super.connect(server, wrapper) + this.serverWrapper = wrapper + } + + @JsonNotification("sts/highlight") + def publishHints(params: HighlightParams): Unit = { + println("Highlight MSG") + val uri = FileUtils.sanitizeURI(params.getDoc.getUri) + val editorManager = this.serverWrapper.getEditorManagerFor(uri) + if (editorManager == null || editorManager.editor == null || editorManager.editor.getDocument == null) { + return + } + val doc = editorManager.editor.getDocument + // Similar to Eclipse UI thread execution + ApplicationManager.getApplication.invokeLater(new Runnable { + override def run(): Unit = { + if (rangeHighlights.get(uri).isDefined) { + rangeHighlights.get(uri).get.foreach(h => editorManager.editor.getMarkupModel.removeHighlighter(h)) + } + val highlighters: ListBuffer[RangeHighlighter] = ListBuffer() + for (r <- JavaConversions.asScalaBuffer(params.getRanges)) { + val startOffset = doc.getLineStartOffset(r.getStart.getLine) + r.getStart.getCharacter + val endOffset = doc.getLineStartOffset(r.getEnd.getLine) + r.getEnd.getCharacter + val attrs = new TextAttributes() + attrs.setEffectType(EffectType.BOXED) + attrs.setEffectColor(StsLanguageClientImpl.SPRING_BOOT_HINT_COLOR) + attrs.setErrorStripeColor(StsLanguageClientImpl.SPRING_BOOT_HINT_COLOR) + val highlighter = editorManager.editor.getMarkupModel.addRangeHighlighter(startOffset, endOffset, HighlighterLayer.ERROR, attrs, HighlighterTargetArea.EXACT_RANGE) + highlighter.setGutterIconRenderer(SpringBootGutterIconRenderer.INSTANCE) + highlighters += highlighter + } + rangeHighlights += Tuple2(uri, highlighters) + } + }) + } + + @JsonNotification("sts/progress") + def progress(progressEvent: ProgressParams): Unit = { + println("Progress MSG") + } + +} + +object StsLanguageClientImpl { + val SPRING_BOOT_HINT_COLOR = new Color(0x32, 0xBA, 0x56) +} diff --git a/idea-extensions/idea-boot-java/src/org/spring/tools/boot/java/ls/StsServerDefinition.scala b/idea-extensions/idea-boot-java/src/org/spring/tools/boot/java/ls/StsServerDefinition.scala new file mode 100644 index 000000000..aad3e1873 --- /dev/null +++ b/idea-extensions/idea-boot-java/src/org/spring/tools/boot/java/ls/StsServerDefinition.scala @@ -0,0 +1,13 @@ +package org.spring.tools.boot.java.ls + +import com.github.gtache.lsp.client.LanguageClientImpl +import com.github.gtache.lsp.client.languageserver.serverdefinition.{CommandServerDefinition, RawCommandServerDefinition, UserConfigurableServerDefinitionObject} + +/** + * @author Alex Boyko + */ +class StsServerDefinition(override val ext: String, override val command: Array[String]) extends RawCommandServerDefinition(ext, command) { + + override def createLanguageClient: LanguageClientImpl = new StsLanguageClientImpl + +}