diff --git a/eclipse-language-servers/org.springframework.tooling.boot.java.ls/src/org/springframework/tooling/boot/java/ls/SpringBootJavaLanguageServer.java b/eclipse-language-servers/org.springframework.tooling.boot.java.ls/src/org/springframework/tooling/boot/java/ls/SpringBootJavaLanguageServer.java index f7de5a1da..dbcfb041b 100644 --- a/eclipse-language-servers/org.springframework.tooling.boot.java.ls/src/org/springframework/tooling/boot/java/ls/SpringBootJavaLanguageServer.java +++ b/eclipse-language-servers/org.springframework.tooling.boot.java.ls/src/org/springframework/tooling/boot/java/ls/SpringBootJavaLanguageServer.java @@ -32,6 +32,7 @@ import org.springframework.tooling.ls.eclipse.commons.STS4LanguageServerProcessS public class SpringBootJavaLanguageServer extends STS4LanguageServerProcessStreamConnector { public SpringBootJavaLanguageServer() { + super("Boot Java Language Server"); List commands = new ArrayList<>(); JRE jre = getJRE(); commands.add(jre.getJavaExecutable()); @@ -74,12 +75,13 @@ public class SpringBootJavaLanguageServer extends STS4LanguageServerProcessStrea String languageServerLocalCopy = bundleVersion + "-" + languageServer; File dataFile = bundle.getDataFile(languageServerLocalCopy); + Exception error = null; if (!dataFile.exists() || bundleVersion.endsWith("qualifier")) { // qualifier check to get the language server always copied in dev mode try { copyLanguageServerJAR(languageServer, languageServerLocalCopy); } catch (Exception e) { - e.printStackTrace(); + error = e; } } if (!dataFile.exists()) { @@ -91,6 +93,9 @@ public class SpringBootJavaLanguageServer extends STS4LanguageServerProcessStrea if (locallyBuiltJar.exists()) { return locallyBuiltJar.getAbsolutePath(); } + if (error!=null) { + error.printStackTrace(); + } } return dataFile.getAbsolutePath(); } diff --git a/eclipse-language-servers/org.springframework.tooling.bosh.ls/src/org/springframework/tooling/bosh/ls/BoshLanguageServer.java b/eclipse-language-servers/org.springframework.tooling.bosh.ls/src/org/springframework/tooling/bosh/ls/BoshLanguageServer.java index 40f4dffa9..4548862e2 100644 --- a/eclipse-language-servers/org.springframework.tooling.bosh.ls/src/org/springframework/tooling/bosh/ls/BoshLanguageServer.java +++ b/eclipse-language-servers/org.springframework.tooling.bosh.ls/src/org/springframework/tooling/bosh/ls/BoshLanguageServer.java @@ -30,6 +30,7 @@ import org.springframework.tooling.ls.eclipse.commons.STS4LanguageServerProcessS public class BoshLanguageServer extends STS4LanguageServerProcessStreamConnector { public BoshLanguageServer() { + super("Bosh Language Server"); List commands = new ArrayList<>(); commands.add(JRE.currentJRE().getJavaExecutable()); diff --git a/eclipse-language-servers/org.springframework.tooling.cloudfoundry.manifest.ls/src/org/springframework/tooling/cloudfoundry/manifest/ls/CloudFoundryManifestLanguageServer.java b/eclipse-language-servers/org.springframework.tooling.cloudfoundry.manifest.ls/src/org/springframework/tooling/cloudfoundry/manifest/ls/CloudFoundryManifestLanguageServer.java index 504904da9..8a5c9b625 100644 --- a/eclipse-language-servers/org.springframework.tooling.cloudfoundry.manifest.ls/src/org/springframework/tooling/cloudfoundry/manifest/ls/CloudFoundryManifestLanguageServer.java +++ b/eclipse-language-servers/org.springframework.tooling.cloudfoundry.manifest.ls/src/org/springframework/tooling/cloudfoundry/manifest/ls/CloudFoundryManifestLanguageServer.java @@ -43,6 +43,7 @@ public class CloudFoundryManifestLanguageServer extends STS4LanguageServerProces private static List servers = new CopyOnWriteArrayList<>(); public CloudFoundryManifestLanguageServer() { + super("Cloudfoundry Language Server"); List commands = new ArrayList<>(); commands.add(JRE.currentJRE().getJavaExecutable()); diff --git a/eclipse-language-servers/org.springframework.tooling.concourse.ls/src/org/springframework/tooling/concourse/ls/ConcourseLanguageServer.java b/eclipse-language-servers/org.springframework.tooling.concourse.ls/src/org/springframework/tooling/concourse/ls/ConcourseLanguageServer.java index 3900b52df..86e87127a 100644 --- a/eclipse-language-servers/org.springframework.tooling.concourse.ls/src/org/springframework/tooling/concourse/ls/ConcourseLanguageServer.java +++ b/eclipse-language-servers/org.springframework.tooling.concourse.ls/src/org/springframework/tooling/concourse/ls/ConcourseLanguageServer.java @@ -30,6 +30,7 @@ import org.springframework.tooling.ls.eclipse.commons.STS4LanguageServerProcessS public class ConcourseLanguageServer extends STS4LanguageServerProcessStreamConnector { public ConcourseLanguageServer() { + super("Concourse Language Server"); List commands = new ArrayList<>(); commands.add(JRE.currentJRE().getJavaExecutable()); diff --git a/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/META-INF/MANIFEST.MF b/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/META-INF/MANIFEST.MF index 02d86937c..a190b73ae 100644 --- a/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/META-INF/MANIFEST.MF +++ b/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/META-INF/MANIFEST.MF @@ -12,7 +12,9 @@ Require-Bundle: org.eclipse.jdt.launching;bundle-version="3.8.0", org.eclipse.lsp4j;bundle-version="0.4.0", org.eclipse.ui.workbench.texteditor, org.eclipse.jface.text, - com.google.guava + com.google.guava, + org.eclipse.debug.ui, + org.eclipse.ui.console Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Bundle-ActivationPolicy: lazy Export-Package: org.springframework.tooling.ls.eclipse.commons diff --git a/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/STS4LanguageServerProcessStreamConnector.java b/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/STS4LanguageServerProcessStreamConnector.java index 6e0c25a08..150fe9e06 100644 --- a/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/STS4LanguageServerProcessStreamConnector.java +++ b/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/STS4LanguageServerProcessStreamConnector.java @@ -12,18 +12,99 @@ package org.springframework.tooling.ls.eclipse.commons; import java.io.File; import java.io.IOException; -import java.util.List; +import java.io.InputStream; +import java.io.OutputStream; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.Job; import org.eclipse.lsp4e.server.ProcessStreamConnectionProvider; +import org.springframework.tooling.ls.eclipse.commons.console.ConsoleUtil.Console; +import org.springframework.tooling.ls.eclipse.commons.console.LanguageServerConsoles; + +import com.google.common.base.Charsets; +import com.google.common.base.Supplier; public class STS4LanguageServerProcessStreamConnector extends ProcessStreamConnectionProvider { private static LanguageServerProcessReaper processReaper = new LanguageServerProcessReaper(); + private Supplier consoles = null; + + public STS4LanguageServerProcessStreamConnector(String consoleLabel) { + if (consoleLabel!=null) { + this.consoles = LanguageServerConsoles.getConsoleFactory(consoleLabel); + } + } + @Override public void start() throws IOException { super.start(); - processReaper.addProcess(LanguageServerProcessReaper.getProcess(this)); + Process process = LanguageServerProcessReaper.getProcess(this); + processReaper.addProcess(process); + if (consoles!=null) { + forwardTo(getLanguageServerLog(), consoles.get().out); + } + } + + @Override + protected ProcessBuilder createProcessBuilder() { + if (consoles==null) { + return super.createProcessBuilder(); + } + ProcessBuilder builder = new ProcessBuilder(getCommands()); + builder.directory(new File(getWorkingDirectory())); + //builder.redirectError(ProcessBuilder.Redirect.INHERIT); + return builder; + } + + private void forwardTo(InputStream is, OutputStream os) { + Job consoleJob = new Job("Forward Language Server log output to console") { + @Override + protected IStatus run(IProgressMonitor arg0) { + try { + pipe(is, os); + os.write("==== Process Terminated====\n".getBytes(Charsets.UTF_8)); + } catch (IOException e) { + e.printStackTrace(); + } + finally { + try { + os.close(); + } catch (IOException e) { + } + } + return Status.OK_STATUS; + } + + void pipe(InputStream input, OutputStream output) throws IOException { + try { + byte[] buf = new byte[1024*4]; + int n = input.read(buf); + while (n >= 0) { + output.write(buf, 0, n); + n = input.read(buf); + } + output.flush(); + } finally { + input.close(); + } + } + + }; + consoleJob.setSystem(true); + consoleJob.schedule(); + } + + private InputStream getLanguageServerLog() { + return super.getErrorStream(); + } + + @Override + public InputStream getErrorStream() { + // lsp4e doesn't do anything with it. So don't pass it on. (Once they do start reading it it might actually break us otherwise) + return null; } @Override diff --git a/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/console/ConsoleUtil.java b/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/console/ConsoleUtil.java new file mode 100644 index 000000000..80ab01ffa --- /dev/null +++ b/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/console/ConsoleUtil.java @@ -0,0 +1,115 @@ +/******************************************************************************* + * Copyright (c) 2012, 2018 Pivotal Software, 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 Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.tooling.ls.eclipse.commons.console; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.LinkedList; + +import org.eclipse.debug.internal.ui.DebugUIPlugin; +import org.eclipse.debug.internal.ui.preferences.IDebugPreferenceConstants; +import org.eclipse.swt.graphics.Color; +import org.eclipse.ui.console.ConsolePlugin; +import org.eclipse.ui.console.IConsole; +import org.eclipse.ui.console.IOConsole; +import org.eclipse.ui.console.IOConsoleInputStream; +import org.eclipse.ui.console.IOConsoleOutputStream; + +/** + * @author Kris De Volder + */ +@SuppressWarnings("restriction") +public class ConsoleUtil { + + public static class Console { + public final InputStream in; + public final OutputStream out; + public final OutputStream err; + + public Console(IOConsoleInputStream in, IOConsoleOutputStream out, + IOConsoleOutputStream err) { + this.in = in; + this.out = out; + this.err = err; + } + + public void close() { + try { + out.close(); + } catch (IOException e) { + } + try { + err.close(); + } catch (IOException e) { + } + try { + in.close(); + } catch (IOException e) { + } + } + } + + /** + * Maximum number of old consoles to keep open. If this number is exceeded the oldest console will + * be removed from the UI. + */ + private final int MAX_SIZE = 3; + + public static Color getOutputColor() { + return DebugUIPlugin.getPreferenceColor(IDebugPreferenceConstants.CONSOLE_SYS_OUT_COLOR); + } + + public static Color getErrorColor() { + return DebugUIPlugin.getPreferenceColor(IDebugPreferenceConstants.CONSOLE_SYS_ERR_COLOR); + } + + public static Color getInputColor() { + return DebugUIPlugin.getPreferenceColor(IDebugPreferenceConstants.CONSOLE_SYS_IN_COLOR); + } + + private LinkedList history = new LinkedList(); + + private void add(IOConsole console) { + IOConsole toClose = null; + synchronized (history) { + history.addLast(console); + if (history.size()>MAX_SIZE) { + toClose = history.removeFirst(); + } + } + if (toClose!=null) { + close(toClose); + } + console.activate(); + ConsolePlugin.getDefault().getConsoleManager().addConsoles(new IConsole[]{ console }); + } + + private static void close(IOConsole console) { + ConsolePlugin.getDefault().getConsoleManager().removeConsoles(new IConsole[] {console}); + } + + public Console getConsole(String title) { + + final IOConsole console = new IOConsole(title, null); + final IOConsoleInputStream in = console.getInputStream(); + final IOConsoleOutputStream out = console.newOutputStream(); + final IOConsoleOutputStream err = console.newOutputStream(); + + in.setColor(getInputColor()); + out.setColor(getOutputColor()); + err.setColor(getErrorColor()); + + add(console); + return new Console(in, out, err); + } + +} diff --git a/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/console/LanguageServerConsoles.java b/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/console/LanguageServerConsoles.java new file mode 100644 index 000000000..929512ea9 --- /dev/null +++ b/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/console/LanguageServerConsoles.java @@ -0,0 +1,44 @@ +/******************************************************************************* + * Copyright (c) 2018 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.springframework.tooling.ls.eclipse.commons.console; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.tooling.ls.eclipse.commons.console.ConsoleUtil.Console; + +import com.google.common.base.Supplier; + +public class LanguageServerConsoles { + + private static Map> managers; + + public static synchronized Supplier getConsoleFactory(String languageServerName) { + if (managers==null) { + managers = new HashMap<>(); + } + return managers.computeIfAbsent(languageServerName, label -> new Supplier() { + ConsoleUtil consoleMgr = new ConsoleUtil(); //one console manager per language server type. This way each has their + // own history (which limits number of open consoles per type) + int consoleCounter = 0; + + @Override + public Console get() { + return consoleMgr.getConsole(languageServerName+" "+consoleCounter()); + } + + private synchronized int consoleCounter() { + return ++consoleCounter; + } + }); + } + +} diff --git a/eclipse-language-servers/org.springframework.tooling.properties.ls/src/org/springframework/tooling/properties/ls/SpringBootPropertiesLanguageServer.java b/eclipse-language-servers/org.springframework.tooling.properties.ls/src/org/springframework/tooling/properties/ls/SpringBootPropertiesLanguageServer.java index 8d498d419..598d5a7e0 100644 --- a/eclipse-language-servers/org.springframework.tooling.properties.ls/src/org/springframework/tooling/properties/ls/SpringBootPropertiesLanguageServer.java +++ b/eclipse-language-servers/org.springframework.tooling.properties.ls/src/org/springframework/tooling/properties/ls/SpringBootPropertiesLanguageServer.java @@ -30,6 +30,7 @@ import org.springframework.tooling.ls.eclipse.commons.STS4LanguageServerProcessS public class SpringBootPropertiesLanguageServer extends STS4LanguageServerProcessStreamConnector { public SpringBootPropertiesLanguageServer() { + super("Boot Properties Language Server"); List commands = new ArrayList<>(); commands.add(JRE.currentJRE().getJavaExecutable()); @@ -53,14 +54,26 @@ public class SpringBootPropertiesLanguageServer extends STS4LanguageServerProces Bundle bundle = Platform.getBundle(Constants.PLUGIN_ID); File dataFile = bundle.getDataFile(languageServer); -// if (!dataFile.exists()) { - try { - copyLanguageServerJAR(languageServer); + Exception error = null; + try { + copyLanguageServerJAR(languageServer); + } + catch (Exception e) { + error = e; + } + if (!dataFile.exists()) { + File userHome = new File(System.getProperty("user.home")); + File locallyBuiltJar = new File( + userHome, + "git/sts4/headless-services/boot-properties-language-server/target/boot-properties-language-server-"+Constants.LANGUAGE_SERVER_VERSION + ".jar" + ); + if (locallyBuiltJar.exists()) { + return locallyBuiltJar.getAbsolutePath(); } - catch (Exception e) { - e.printStackTrace(); + if (error!=null) { + error.printStackTrace(); } -// } + } return dataFile.getAbsolutePath(); }