diff --git a/.gitignore b/.gitignore
index fc0d887..700005d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,36 @@
-.settings
-/bin
-/.project
-target
\ No newline at end of file
+build/
+.out/
+bin/
+.bin/
+target/
+
+/gradle.properties
+/archive/
+/META-INF
+
+.classpath
+.project
+.settings
+
+.idea/
+*.iml
+*.iws
+*.ipr
+.idea_modules/
+**/out/
+
+*.tmp
+*.bak
+*.swp
+*~
+
+.gradle
+
+.DS_Store*
+.AppleDouble
+.LSOverride
+
+.directory
+.Trash*
+
+**/adhoctest/
\ No newline at end of file
diff --git a/README.md b/README.md
index a660c7e..17f6676 100644
--- a/README.md
+++ b/README.md
@@ -41,3 +41,17 @@ Once https://github.com/mickaelistria/eclipse-bluesky/issues/63 will work in Pho
HTML syntax coloration (managed with TextMate) and HTML completion, mark occurrences, etc is not a part of this plugin. I suggest you that you install https://github.com/mickaelistria/eclipse-bluesky
which provides those features.
+
+Development in Eclipse
+======================
+
+1. Use "Eclipse for Committers" (Photon M6 as of this writing).
+
+2. In Eclipse, "File" / "Import..." / "Existing Maven Projects". Point at the `lsp4e-freemarker` project root directory, add all the Maven projects it finds.
+
+3. Now go to "Window" / "Preferences" / "Plug-in Development" / "Target Platform", and Select "lsp4e-freemarker" (this only appears if you have imported the "target-platform" Maven project earlier).
+ After this, there shouldn't be more errors in the project (no dependency classes that aren't found).
+
+4. To try the plugin, right click on the `org.eclipse.lsp4j.freemarker` project, then "Run as" / "Eclipse Application".
+ (TODO: Currently that will fail with `Application "org.eclipse.ui.ide.workbench" could not be found in the registry`. I have worked that around by adding
+ `` to the target platform, but of course there must be a better way.)
diff --git a/org.eclipse.lsp4e.freemarker/META-INF/MANIFEST.MF b/org.eclipse.lsp4e.freemarker/META-INF/MANIFEST.MF
index 5ac8e68..948e723 100644
--- a/org.eclipse.lsp4e.freemarker/META-INF/MANIFEST.MF
+++ b/org.eclipse.lsp4e.freemarker/META-INF/MANIFEST.MF
@@ -14,8 +14,7 @@ Require-Bundle: org.eclipse.lsp4e,
org.eclipse.jface,
org.eclipse.ui.workbench.texteditor,
org.eclipse.ui.editors,
- org.eclipse.ui.genericeditor,
- org.eclipse.jdt.launching
+ org.eclipse.ui.genericeditor
Bundle-Activator: org.eclipse.lsp4e.freemarker.FreemarkerPlugin
Bundle-ActivationPolicy: lazy
Eclipse-BundleShape: dir
diff --git a/org.eclipse.lsp4e.freemarker/plugin.xml b/org.eclipse.lsp4e.freemarker/plugin.xml
index e68868b..4fa382f 100644
--- a/org.eclipse.lsp4e.freemarker/plugin.xml
+++ b/org.eclipse.lsp4e.freemarker/plugin.xml
@@ -34,7 +34,7 @@
diff --git a/org.eclipse.lsp4e.freemarker/server/freemarker-languageserver-all.jar b/org.eclipse.lsp4e.freemarker/server/freemarker-languageserver-all.jar
index 0a7226b..b4be85f 100644
Binary files a/org.eclipse.lsp4e.freemarker/server/freemarker-languageserver-all.jar and b/org.eclipse.lsp4e.freemarker/server/freemarker-languageserver-all.jar differ
diff --git a/org.eclipse.lsp4e.freemarker/src/org/eclipse/lsp4e/freemarker/FreemarkerLanguageServer.java b/org.eclipse.lsp4e.freemarker/src/org/eclipse/lsp4e/freemarker/FreemarkerLanguageServer.java
deleted file mode 100644
index ae77b33..0000000
--- a/org.eclipse.lsp4e.freemarker/src/org/eclipse/lsp4e/freemarker/FreemarkerLanguageServer.java
+++ /dev/null
@@ -1,179 +0,0 @@
-/**
- * Copyright (c) 2018 Angelo ZERR.
- * 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:
- * Angelo Zerr - initial API and implementation
- */
-package org.eclipse.lsp4e.freemarker;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.lang.reflect.Field;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.List;
-
-import org.eclipse.core.runtime.FileLocator;
-import org.eclipse.core.runtime.IStatus;
-import org.eclipse.core.runtime.Platform;
-import org.eclipse.core.runtime.Status;
-import org.eclipse.jdt.internal.launching.StandardVMType;
-import org.eclipse.jdt.launching.IVMInstall;
-import org.eclipse.jdt.launching.JavaRuntime;
-import org.eclipse.lsp4e.server.ProcessStreamConnectionProvider;
-import org.osgi.framework.Bundle;
-
-/**
- * LSP4e Freemarker Language server.
- *
- */
-public class FreemarkerLanguageServer extends ProcessStreamConnectionProvider {
-
- public FreemarkerLanguageServer() {
- super(computeCommands(), computeWorkingDir());
- }
-
- private static String computeWorkingDir() {
- return System.getProperty("user.dir");
- }
-
- private static List computeCommands() {
- List commands = new ArrayList<>();
- // Try to use the configured Install JRE.
- IVMInstall install = JavaRuntime.getDefaultVMInstall();
- if (install != null) {
- File vmInstallLocation = install.getInstallLocation();
- File javaExecutableLocation = StandardVMType.findJavaExecutable(vmInstallLocation);
- // ex: C:\Program Files\Java\jre1.8.0_77\bin\javaw.exe
- commands.add(javaExecutableLocation.getAbsolutePath());
- } else {
- commands.add("java");
- }
- commands.add("-jar");
- commands.add(computeFreemarkerLanguageServerJarPath());
- return commands;
- }
-
- private static String computeFreemarkerLanguageServerJarPath() {
- Bundle bundle = Platform.getBundle(FreemarkerPlugin.PLUGIN_ID);
- URL fileURL = bundle.getEntry("server/freemarker-languageserver-all.jar");
- try {
- URL resolvedFileURL = FileLocator.toFileURL(fileURL);
-
- // We need to use the 3-arg constructor of URI in order to properly escape file
- // system chars
- URI resolvedURI = new URI(resolvedFileURL.getProtocol(), resolvedFileURL.getPath(), null);
- File file = new File(resolvedURI);
- if (Platform.OS_WIN32.equals(Platform.getOS())) {
- return "\"" + file.getAbsolutePath() + "\"";
- } else {
- return file.getAbsolutePath();
- }
- } catch (URISyntaxException | IOException exception) {
- FreemarkerPlugin.log(new Status(IStatus.ERROR, FreemarkerPlugin.PLUGIN_ID,
- "Cannot get the FreeMarker LSP Server jar.", exception)); //$NON-NLS-1$
- }
- return "";
- }
-
- class GetErrorThread extends Thread {
-
- private final Process process;
- private String message = null;
-
- public GetErrorThread(Process process) {
- this.process = process;
- }
-
- @Override
- public void run() {
- try (final BufferedReader b = new BufferedReader(new InputStreamReader(getErrorStream()))) {
- String line;
- if ((line = b.readLine()) != null) {
- message = line;
- synchronized (FreemarkerLanguageServer.this) {
- FreemarkerLanguageServer.this.notifyAll();
- }
- }
- } catch (IOException e) {
- message = e.getMessage();
- }
- }
-
- public void check() throws IOException {
- if (message != null) {
- throw new IOException(message);
- }
- if (!process.isAlive()) {
- throw new IOException("Process is not alive"); //$NON-NLS-1$
- }
- }
-
- }
-
- @Override
- public void start() throws IOException {
- // Start the process
- super.start();
- // Get the process by Java reflection
- Process p = getProcess();
- if (p != null) {
- // Sart a thread which read error stream to check that the java command is
- // working.
- GetErrorThread t = new GetErrorThread(p);
- try {
- t.start();
- // wait a little to execute java command line...
- synchronized (FreemarkerLanguageServer.this) {
- try {
- this.wait(500);
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- }
- }
- // check if there is an error or if process is not alived.
- try {
- t.check();
- } catch (IOException e) {
- throw new IOException("Unable to start language server: " + this.toString(), e); //$NON-NLS-1$
- }
- } finally {
- t.interrupt();
- }
- }
- }
-
- private Process getProcess() {
- try {
- Field f = ProcessStreamConnectionProvider.class.getDeclaredField("process");
- f.setAccessible(true);
- Process p = (Process) f.get(this);
- return p;
- } catch (Exception e) {
- e.printStackTrace();
- return null;
- }
- }
-
- @Override
- protected ProcessBuilder createProcessBuilder() {
- ProcessBuilder builder = super.createProcessBuilder();
- // override redirect to PIPE to read error stream with GetErrorThread
- builder.redirectError(ProcessBuilder.Redirect.PIPE);
- return builder;
- }
-
- @Override
- public String toString() {
- return "FreeMarker (" + super.toString() + ")";
- }
-
-}
diff --git a/org.eclipse.lsp4e.freemarker/src/org/eclipse/lsp4e/freemarker/FreemarkerStreamConnectionProvider.java b/org.eclipse.lsp4e.freemarker/src/org/eclipse/lsp4e/freemarker/FreemarkerStreamConnectionProvider.java
new file mode 100644
index 0000000..c966379
--- /dev/null
+++ b/org.eclipse.lsp4e.freemarker/src/org/eclipse/lsp4e/freemarker/FreemarkerStreamConnectionProvider.java
@@ -0,0 +1,95 @@
+/**
+ * Copyright (c) 2018 Angelo ZERR, Daniel Dekany.
+ * 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:
+ * Angelo Zerr - initial API and implementation
+ */
+package org.eclipse.lsp4e.freemarker;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Arrays;
+import java.util.concurrent.Future;
+
+import org.eclipse.core.runtime.Platform;
+import org.osgi.framework.Bundle;
+
+/**
+ * Starts the FreeMarker LSP server inside the current JVM, and connects to it.
+ */
+public class FreemarkerStreamConnectionProvider extends LocalStreamConnectionProvider {
+
+ public FreemarkerStreamConnectionProvider() {
+ super(FreemarkerPlugin.getDefault().getLog(), FreemarkerPlugin.getPluginId());
+ }
+
+ private static final String LANGUAGE_SERVER_JAR_ENTRY_NAME = "server/freemarker-languageserver-all.jar"; //$NON-NLS-1$
+ private static final String LANGUAGE_SERVER_LAUNCHER_CLASS_NAME = "freemarker.ext.languageserver.FreemarkerServerLauncher"; //$NON-NLS-1$
+
+ @Override
+ protected LocalServer launchServer(InputStream clientToServerStream, OutputStream serverToClientStream)
+ throws IOException {
+ URL[] classPath = getFreemarkerLanguageServerClassPath();
+ logInfo("Using class path: " + Arrays.toString(classPath));
+
+ URLClassLoader dynamicJarClassLoader = new URLClassLoader(classPath);
+
+ Method launcherMethod;
+ try {
+ Class> launcherClass = dynamicJarClassLoader.loadClass(LANGUAGE_SERVER_LAUNCHER_CLASS_NAME);
+ launcherMethod = launcherClass.getMethod("launch", //$NON-NLS-1$
+ new Class>[] { InputStream.class, OutputStream.class });
+ } catch (Exception e) {
+ throw new RuntimeException(
+ "Couldn't get launcher class and method via Java reflection (using class path: "
+ + Arrays.toString(classPath) + "); see cause exception", e); //$NON-NLS-2$
+ }
+ Future> launchedFuture;
+ try {
+ launchedFuture = (Future>) launcherMethod.invoke(null, clientToServerStream, serverToClientStream);
+ } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
+ throw new RuntimeException("Error when calling launcher method; see cause exception", e); //$NON-NLS-1$
+ }
+
+ return new LocalServer(launchedFuture) {
+ @Override
+ public void stop() {
+ super.stop();
+ try {
+ dynamicJarClassLoader.close();
+ } catch (IOException e) {
+ logError("Error when closing the dynamic jar class-loader", e); //$NON-NLS-1$
+ }
+ }
+ };
+ }
+
+ private URL[] getFreemarkerLanguageServerClassPath() {
+ Bundle bundle = Platform.getBundle(FreemarkerPlugin.PLUGIN_ID);
+ if (bundle == null) {
+ throw new RuntimeException("Bundle " + FreemarkerPlugin.PLUGIN_ID + " not found"); //$NON-NLS-1$
+ }
+
+ URL languageServerJarURL = bundle.getEntry(LANGUAGE_SERVER_JAR_ENTRY_NAME);
+ if (languageServerJarURL == null) {
+ throw new RuntimeException(
+ "Entity " + LANGUAGE_SERVER_JAR_ENTRY_NAME + " not found in bundle " + FreemarkerPlugin.PLUGIN_ID); //$NON-NLS-1$
+ }
+
+ // TODO: Add freemarker.jar from the user project here, if it's found and has
+ // high enough version, otherwise add freemarker.jar from this plugin.
+ // (Currently, freemarker.jar is bundled into the language server jar.)
+
+ return new URL[] { languageServerJarURL };
+ }
+
+}
diff --git a/org.eclipse.lsp4e.freemarker/src/org/eclipse/lsp4e/freemarker/LocalStreamConnectionProvider.java b/org.eclipse.lsp4e.freemarker/src/org/eclipse/lsp4e/freemarker/LocalStreamConnectionProvider.java
new file mode 100644
index 0000000..2628bd9
--- /dev/null
+++ b/org.eclipse.lsp4e.freemarker/src/org/eclipse/lsp4e/freemarker/LocalStreamConnectionProvider.java
@@ -0,0 +1,184 @@
+/**
+ * Copyright (c) 2018 Angelo ZERR, Daniel Dekany.
+ * 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:
+ * Angelo Zerr - initial API and implementation
+ */
+package org.eclipse.lsp4e.freemarker;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.util.Objects;
+import java.util.concurrent.Future;
+
+import org.eclipse.core.runtime.ILog;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.lsp4e.server.StreamConnectionProvider;
+
+/**
+ * A {@link StreamConnectionProvider} that connects to a LSP server that runs in
+ * the client's JVM, not as a separate process.
+ */
+// This meant to be independent of the Freemareker plugin project, so it shouldn't use local dependencies, like
+// FreemarkerPlugin.log(...).
+public abstract class LocalStreamConnectionProvider implements StreamConnectionProvider {
+
+ private static final int PIPE_BUFFER_SIZE = 8192;
+
+ // Certainly nobody wants to internationalize error messages like this...
+ private static final String STOP_ERROR_MESSAGE = "Error while stopping the local LSP service."; //$NON-NLS-1$
+
+ private PipedOutputStream clientToServerStream;
+ private PipedInputStream serverToClientStream;
+ private PipedInputStream clientToServerStreamReverse;
+ private PipedOutputStream serverToClientStreamReverse;
+ private LocalServer localServer;
+
+ private final ILog log;
+ private final String pluginId;
+
+ /**
+ * @param log The log used by the embedding plug-in. (It's assumed that the
+ * server is local because it's embedded into an Eclipse
+ * plug-in.)
+ * @param pluginId The ID of the embedding Eclipse plug-in. Used for
+ * {@link IStatus#getPlugin()} for example.
+ */
+ protected LocalStreamConnectionProvider(ILog log, String pluginId) {
+ this.log = log;
+ this.pluginId = pluginId;
+ }
+
+ @Override
+ public synchronized void start() throws IOException {
+ clientToServerStream = new PipedOutputStream();
+ serverToClientStream = new PipedInputStream(PIPE_BUFFER_SIZE);
+ clientToServerStreamReverse = new PipedInputStream(clientToServerStream, PIPE_BUFFER_SIZE);
+ serverToClientStreamReverse = new PipedOutputStream(serverToClientStream);
+ localServer = launchServer(clientToServerStreamReverse, serverToClientStreamReverse);
+ }
+
+ protected abstract LocalServer launchServer(InputStream clientToServerStream, OutputStream serverToClientStream)
+ throws IOException;
+
+ @Override
+ public InputStream getInputStream() {
+ return serverToClientStream;
+ }
+
+ @Override
+ public OutputStream getOutputStream() {
+ return clientToServerStream;
+ }
+
+ @Override
+ public InputStream getErrorStream() {
+ return null;
+ }
+
+ @Override
+ public synchronized void stop() {
+ if (localServer == null) {
+ return;
+ }
+
+ try {
+ localServer.stop();
+ } catch (Exception e) {
+ logError(STOP_ERROR_MESSAGE, e);
+ }
+ localServer = null;
+
+ try {
+ clientToServerStream.close();
+ } catch (IOException e) {
+ logError(STOP_ERROR_MESSAGE, e);
+ }
+ clientToServerStream = null;
+
+ try {
+ clientToServerStreamReverse.close();
+ } catch (IOException e) {
+ logError(STOP_ERROR_MESSAGE, e);
+ }
+ clientToServerStreamReverse = null;
+
+ try {
+ serverToClientStreamReverse.close();
+ } catch (IOException e) {
+ logError(STOP_ERROR_MESSAGE, e);
+ }
+ serverToClientStreamReverse = null;
+
+ try {
+ serverToClientStream.close();
+ } catch (IOException e) {
+ logError(STOP_ERROR_MESSAGE, e);
+ }
+ serverToClientStream = null;
+ }
+
+ /**
+ * See similar {@link LocalStreamConnectionProvider} constructor parameter.
+ */
+ protected ILog getLog() {
+ return log;
+ }
+
+ /**
+ * See similar {@link LocalStreamConnectionProvider} constructor parameter.
+ */
+ protected String getPluginId() {
+ return pluginId;
+ }
+
+ /**
+ * Convenience method to log an error.
+ */
+ protected void logError(String message, Throwable e) {
+ getLog().log(new Status(IStatus.ERROR, getPluginId(), message, e));
+ }
+
+ /**
+ * Convenience method to log an info message.
+ */
+ protected void logInfo(String message) {
+ getLog().log(new Status(IStatus.INFO, getPluginId(), message));
+ }
+
+ /**
+ * Represents a locally launched server, that can be stopped.
+ */
+ public static abstract class LocalServer {
+
+ private final Future> launcherFuture;
+
+ /**
+ * @param launcherFuture The future returned by
+ * {@link org.eclipse.lsp4j.jsonrpc.Launcher#startListening()}
+ */
+ public LocalServer(Future> launcherFuture) {
+ Objects.requireNonNull(launcherFuture, "launcherFuture");
+ this.launcherFuture = launcherFuture;
+ }
+
+ /**
+ * Override this if you have resource to release.
+ */
+ public void stop() {
+ // TODO I'm not sure if I can stop the language server like this... will have to
+ // look into the org.eclipse.lsp4j.jsonrpc.Launcher#startListening()
+ // implementation, as it has no JavaDoc.
+ launcherFuture.cancel(true);
+ }
+ }
+
+}
diff --git a/pom.xml b/pom.xml
index 14d9a4e..7852a09 100644
--- a/pom.xml
+++ b/pom.xml
@@ -11,6 +11,10 @@
https://github.com/angelozerr/lsp4e-freemarker
2018
+
+
+ 1.1.0
+
@@ -53,13 +57,13 @@
org.eclipse.tycho
tycho-maven-plugin
- 1.0.0
+ ${tycho.version}
true
org.eclipse.tycho
target-platform-configuration
- 1.0.0
+ ${tycho.version}
true
p2
@@ -77,7 +81,7 @@
org.eclipse.tycho
tycho-source-plugin
- 1.0.0
+ ${tycho.version}
plugin-source
@@ -88,13 +92,13 @@
org.eclipse.tycho
tycho-p2-plugin
- 1.0.0
+ ${tycho.version}
attach-p2-metadata
diff --git a/target-platform/target-platform.target b/target-platform/target-platform.target
index 412209a..102ba3e 100644
--- a/target-platform/target-platform.target
+++ b/target-platform/target-platform.target
@@ -1,5 +1,5 @@
-
+