blob: f16a5005eebf6b6bf728caa11b02be1943eab129 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2017, 2018 Kichwa Coders Ltd. 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
*******************************************************************************/
package org.eclipse.lsp4e.debug.launcher;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Type;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubMonitor;
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.ILaunchManager;
import org.eclipse.debug.core.IStreamListener;
import org.eclipse.debug.core.model.ILaunchConfigurationDelegate;
import org.eclipse.debug.core.model.IProcess;
import org.eclipse.debug.core.model.IStreamMonitor;
import org.eclipse.lsp4e.debug.DSPPlugin;
import org.eclipse.lsp4e.debug.debugmodel.DSPDebugTarget;
import org.eclipse.osgi.util.NLS;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
public class DSPLaunchDelegate implements ILaunchConfigurationDelegate {
@Override
public void launch(ILaunchConfiguration configuration, String mode, ILaunch launch, IProgressMonitor monitor)
throws CoreException {
SubMonitor subMonitor = SubMonitor.convert(monitor, 100);
boolean launchNotConnect = DSPPlugin.DSP_MODE_LAUNCH
.equals(configuration.getAttribute(DSPPlugin.ATTR_DSP_MODE, DSPPlugin.DSP_MODE_LAUNCH));
String dspParametersJson = configuration.getAttribute(DSPPlugin.ATTR_DSP_PARAM, (String) null);
Gson gson = new Gson();
Type type = new TypeToken<Map<String, Object>>() {
}.getType();
Map<String, Object> dspParameters = gson.fromJson(dspParametersJson, type);
// DSP supports run/debug as a simple flag to the debug server.
// See LaunchRequestArguments.noDebug
if (ILaunchManager.DEBUG_MODE.equals(mode)) {
subMonitor.setTaskName("Starting debug session");
dspParameters.put("noDebug", false);
} else if (ILaunchManager.RUN_MODE.equals(mode)) {
subMonitor.setTaskName("Starting run session");
dspParameters.put("noDebug", true);
} else {
abort(NLS.bind("Unsupported launch mode '{0}'.", mode), null);
}
Runnable cleanup;
InputStream inputStream;
OutputStream outputStream;
try {
if (launchNotConnect) {
List<String> command = new ArrayList<>();
String debugCmd = configuration.getAttribute(DSPPlugin.ATTR_DSP_CMD, (String) null);
if (debugCmd == null) {
abort("Debug command unspecified.", null); //$NON-NLS-1$
}
command.add(debugCmd);
List<String> debugArgs = configuration.getAttribute(DSPPlugin.ATTR_DSP_ARGS, (List<String>) null);
if (debugArgs != null && !debugArgs.isEmpty()) {
command.addAll(debugArgs);
}
ProcessBuilder processBuilder = new ProcessBuilder(command);
subMonitor
.subTask(NLS.bind("Launching debug adapter: {0}", "\"" + String.join("\" \"", command) + "\""));
Process debugAdapterProcess = processBuilder.start();
if (launch.getLaunchConfiguration().getAttribute(DSPPlugin.ATTR_DSP_MONITOR_DEBUG_ADAPTER, false)) {
// Uglish workaround: should instead have a dedicated ProcessFactory reading process attribute rather than launch one
String initialCaptureOutput = launch.getAttribute(DebugPlugin.ATTR_CAPTURE_OUTPUT);
launch.setAttribute(DebugPlugin.ATTR_CAPTURE_OUTPUT, Boolean.toString(true));
IProcess debugAdapterIProcess = DebugPlugin.newProcess(launch, debugAdapterProcess, "Debug Adapter");
launch.setAttribute(DebugPlugin.ATTR_CAPTURE_OUTPUT, initialCaptureOutput);
List<Byte> bytes = Collections.synchronizedList(new LinkedList<>());
inputStream = new InputStream() {
@Override public int read() throws IOException {
while (debugAdapterProcess.isAlive()) {
if (!bytes.isEmpty()) {
return bytes.remove(0);
} else {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
DSPPlugin.logError(e);
}
}
}
return -1;
}
};
debugAdapterIProcess.getStreamsProxy().getOutputStreamMonitor().addListener(new IStreamListener() {
@Override public void streamAppended(String text, IStreamMonitor monitor) {
try {
for (byte b : text.getBytes(launch.getAttribute(DebugPlugin.ATTR_CONSOLE_ENCODING))) {
bytes.add(Byte.valueOf(b));
}
} catch (IOException e) {
DSPPlugin.logError(e);
}
}
});
outputStream = new OutputStream() {
@Override public void write(int b) throws IOException {
debugAdapterIProcess.getStreamsProxy().write(new String(new byte[] { (byte)b }));
}
};
cleanup = () -> {
try {
debugAdapterIProcess.terminate();
debugAdapterProcess.destroy();
} catch (DebugException e) {
DSPPlugin.logError(e);
}
};
} else {
inputStream = debugAdapterProcess.getInputStream();
outputStream = debugAdapterProcess.getOutputStream();
cleanup = () -> debugAdapterProcess.destroy();
}
} else {
String server = configuration.getAttribute(DSPPlugin.ATTR_DSP_SERVER_HOST, (String) null);
if (server == null) {
abort("Debug server host unspecified.", null); //$NON-NLS-1$
}
int port = configuration.getAttribute(DSPPlugin.ATTR_DSP_SERVER_PORT, 0);
if (port < 1 || port > 65535) {
abort("Debug server port unspecified or out of range 1-65535.", null); //$NON-NLS-1$
}
subMonitor.subTask(NLS.bind("Connecting to debug adapter: {0}:{1}", server, port));
Socket socket = new Socket(server, port);
inputStream = socket.getInputStream();
outputStream = socket.getOutputStream();
cleanup = () -> {
try {
socket.close();
} catch (IOException e) {
// ignore inner resource exception
}
};
}
subMonitor.setWorkRemaining(80);
DSPDebugTarget target = new DSPDebugTarget(launch, cleanup, inputStream, outputStream, dspParameters);
target.initialize(subMonitor.split(80));
launch.addDebugTarget(target);
} catch (IOException | OperationCanceledException e1) {
abort("Failed to start debugging", e1);
} finally {
subMonitor.done();
}
}
/**
* Throws an exception with a new status containing the given message and
* optional exception.
*
* @param message
* error message
* @param e
* underlying exception
* @throws CoreException
*/
private void abort(String message, Throwable e) throws CoreException {
throw new CoreException(new Status(IStatus.ERROR, DSPPlugin.PLUGIN_ID, 0, message, e));
}
}