blob: 1818355a6f3ff715ec0a730a68efb51151e79438 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2016 Red Hat Inc. and others.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Mickael Istria (Red Hat Inc.) - initial implementation
*******************************************************************************/
package org.eclipse.lsp4e;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Platform;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchConfigurationType;
import org.eclipse.debug.core.ILaunchManager;
import org.eclipse.debug.core.IStreamListener;
import org.eclipse.debug.core.model.IProcess;
import org.eclipse.debug.core.model.IStreamMonitor;
import org.eclipse.debug.core.model.RuntimeProcess;
import org.eclipse.debug.internal.core.IInternalDebugCoreConstants;
import org.eclipse.debug.internal.core.Preferences;
import org.eclipse.lsp4e.server.StreamConnectionProvider;
/**
* Access and control IO streams from a Launch Configuration to connect
* them to language server protocol client.
*/
public class LaunchConfigurationStreamProvider implements StreamConnectionProvider {
private StreamProxyInputStream inputStream;
private StreamProxyInputStream errorStream;
private OutputStream outputStream;
private ILaunch launch;
private IProcess process;
private ILaunchConfiguration launchConfiguration;
private Set<String> launchModes;
protected static class StreamProxyInputStream extends InputStream implements IStreamListener {
private ConcurrentLinkedQueue<Byte> queue = new ConcurrentLinkedQueue<>();
private IProcess process;
public StreamProxyInputStream(IProcess process) {
this.process = process;
}
@Override
public void streamAppended(String text, IStreamMonitor monitor) {
byte[] bytes = text.getBytes(Charset.defaultCharset());
List<Byte> bytesAsList = new ArrayList<>(bytes.length);
for (byte b : bytes) {
bytesAsList.add(b);
}
queue.addAll(bytesAsList);
}
@Override
public int read() throws IOException {
while (queue.isEmpty()) {
if (this.process.isTerminated()) {
return -1;
}
try {
Thread.sleep(5, 0);
} catch (InterruptedException e) {
LanguageServerPlugin.logError(e);
Thread.currentThread().interrupt();
}
}
return queue.poll();
}
@Override
public int available() throws IOException {
return queue.size();
}
}
public LaunchConfigurationStreamProvider(ILaunchConfiguration launchConfig, Set<String> launchModes) {
super();
Assert.isNotNull(launchConfig);
this.launchConfiguration = launchConfig;
if (launchModes != null) {
this.launchModes = launchModes;
} else {
this.launchModes = Collections.singleton(ILaunchManager.RUN_MODE);
}
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (!(obj instanceof LaunchConfigurationStreamProvider)) {
return false;
}
LaunchConfigurationStreamProvider other = (LaunchConfigurationStreamProvider)obj;
return this.launchConfiguration.equals(other.launchConfiguration) && this.launchModes.equals(other.launchModes);
}
@Override
public int hashCode() {
return this.launchConfiguration.hashCode() ^ this.launchModes.hashCode();
}
public static ILaunchConfiguration findLaunchConfiguration(String typeId, String name) {
ILaunchManager manager = DebugPlugin.getDefault().getLaunchManager();
ILaunchConfigurationType type = manager.getLaunchConfigurationType(typeId);
ILaunchConfiguration res = null;
try {
for (ILaunchConfiguration launch : manager.getLaunchConfigurations(type)) {
if (launch.getName().equals(name)) {
res = launch;
}
}
} catch (CoreException e) {
LanguageServerPlugin.logError(e);
}
return res;
}
@Override
public void start() throws IOException {
// Disable status handler to avoid starting launch in an UI Thread which freezes
// the IDE when Outline is displayed.
boolean statusHandlerToUpdate = disableStatusHandler();
try {
this.launch = this.launchConfiguration.launch(this.launchModes.iterator().next(), new NullProgressMonitor(),
false);
long initialTimestamp = System.currentTimeMillis();
while (this.launch.getProcesses().length == 0 && System.currentTimeMillis() - initialTimestamp < 5000) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
LanguageServerPlugin.logError(e);
Thread.currentThread().interrupt();
}
}
if (this.launch.getProcesses().length > 0) {
this.process = this.launch.getProcesses()[0];
this.inputStream = new StreamProxyInputStream(process);
process.getStreamsProxy().getOutputStreamMonitor().addListener(this.inputStream);
// TODO: Ugly hack, find something better to retrieve stream!
try {
Method systemProcessGetter = RuntimeProcess.class.getDeclaredMethod("getSystemProcess"); //$NON-NLS-1$
systemProcessGetter.setAccessible(true);
Process systemProcess = (Process) systemProcessGetter.invoke(process);
this.outputStream = systemProcess.getOutputStream();
} catch (ReflectiveOperationException ex) {
LanguageServerPlugin.logError(ex);
}
this.errorStream = new StreamProxyInputStream(process);
process.getStreamsProxy().getErrorStreamMonitor().addListener(this.errorStream);
}
} catch (Exception e) {
LanguageServerPlugin.logError(e);
}
finally {
if (statusHandlerToUpdate) {
setStatusHandler(true);
}
}
}
/**
* Disable status handler while launch is done.
*
* @return true if the status handler preferences was <code>enabled</code> and
* <code>false</code> otherwise.
*/
private boolean disableStatusHandler() {
boolean enabled = Platform.getPreferencesService().getBoolean(DebugPlugin.getUniqueIdentifier(),
IInternalDebugCoreConstants.PREF_ENABLE_STATUS_HANDLERS, false, null);
if (enabled) {
setStatusHandler(false);
}
return enabled;
}
/**
* Update the the status handler preferences
*
* @param enabled
* the status handler preferences
*/
private void setStatusHandler(boolean enabled) {
Preferences.setBoolean(DebugPlugin.getUniqueIdentifier(),
IInternalDebugCoreConstants.PREF_ENABLE_STATUS_HANDLERS, enabled, null);
}
@Override
public InputStream getInputStream() {
return this.inputStream;
}
@Override
public OutputStream getOutputStream() {
return this.outputStream;
}
@Override
public InputStream getErrorStream() {
return this.errorStream;
}
@Override
public void stop() {
if (this.launch == null) {
return;
}
try {
this.launch.terminate();
for (IProcess p : this.launch.getProcesses()) {
p.terminate();
}
} catch (DebugException e1) {
LanguageServerPlugin.logError(e1);
}
this.launch = null;
this.process = null;
try {
if (this.inputStream != null) {
this.inputStream.close();
}
} catch (IOException e) {
LanguageServerPlugin.logError(e);
}
try {
if (this.errorStream != null) {
this.errorStream.close();
}
} catch (IOException e) {
LanguageServerPlugin.logError(e);
}
this.inputStream = null;
this.outputStream = null;
this.errorStream = null;
}
}