blob: 627abdef5836f1311bf1a1b1ab421756c31c2550 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2017 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.debugmodel;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IMarkerDelta;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.debug.core.IBreakpointListener;
import org.eclipse.debug.core.IBreakpointManager;
import org.eclipse.debug.core.IBreakpointManagerListener;
import org.eclipse.debug.core.model.IBreakpoint;
import org.eclipse.debug.core.model.ILineBreakpoint;
import org.eclipse.lsp4e.debug.DSPPlugin;
import org.eclipse.lsp4j.debug.BreakpointEventArguments;
import org.eclipse.lsp4j.debug.Capabilities;
import org.eclipse.lsp4j.debug.SetBreakpointsArguments;
import org.eclipse.lsp4j.debug.SetBreakpointsResponse;
import org.eclipse.lsp4j.debug.Source;
import org.eclipse.lsp4j.debug.SourceBreakpoint;
import org.eclipse.lsp4j.debug.services.IDebugProtocolServer;
/**
* TODO The DSP breakpoint manager is a minimal effort so far. It does not
* really work as there isn't a two way mapping of debug adapter setting. Big
* TODOs:
* <ul>
* <li>Function breakpoints
* <li>Event points
* <li>Update platform breakpoints based on adapter events and responses
* <li>Support for line breakpoints not on IResource. For example CDT has
* additional fields in the marker to specify full path
*/
public class DSPBreakpointManager implements IBreakpointManagerListener, IBreakpointListener {
private Map<Source, List<SourceBreakpoint>> targetBreakpoints = new HashMap<>();
private IDebugProtocolServer debugProtocolServer;
private IBreakpointManager platformBreakpointManager;
private Capabilities capabilities;
public DSPBreakpointManager(IBreakpointManager platformBreakpointManager, IDebugProtocolServer debugProtocolServer,
Capabilities capabilities) {
this.debugProtocolServer = debugProtocolServer;
this.platformBreakpointManager = platformBreakpointManager;
this.capabilities = capabilities;
}
/**
* Initialize the manager and send all platform breakpoints to the debug
* adapter.
*
* @return the completeable future to signify when the breakpoints are all sent.
*/
public CompletableFuture<Void> initialize() {
platformBreakpointManager.addBreakpointListener(this);
platformBreakpointManager.addBreakpointManagerListener(this);
return resendAllTargetBreakpoints(platformBreakpointManager.isEnabled());
}
/**
* Called when the debug manager is no longer needed/debug session is shut down.
*/
public void shutdown() {
platformBreakpointManager.removeBreakpointListener(this);
platformBreakpointManager.removeBreakpointManagerListener(this);
}
/**
* Returns whether this target can install the given breakpoint.
*
* @param breakpoint breakpoint to consider
* @return whether this target can install the given breakpoint
*/
public boolean supportsBreakpoint(IBreakpoint breakpoint) {
return breakpoint instanceof ILineBreakpoint;
}
/**
* When the breakpoint manager disables, remove all registered breakpoints
* requests from the VM. When it enables, reinstall them.
*/
@Override
public void breakpointManagerEnablementChanged(boolean enabled) {
resendAllTargetBreakpoints(enabled);
}
private CompletableFuture<Void> resendAllTargetBreakpoints(boolean enabled) {
IBreakpoint[] breakpoints = platformBreakpointManager.getBreakpoints();
for (IBreakpoint breakpoint : breakpoints) {
if (supportsBreakpoint(breakpoint)) {
if (enabled) {
addBreakpointToMap(breakpoint);
} else {
deleteBreakpointFromMap(breakpoint);
}
}
}
return sendBreakpoints();
}
@Override
public void breakpointAdded(IBreakpoint breakpoint) {
if (supportsBreakpoint(breakpoint)) {
try {
if ((breakpoint.isEnabled() && platformBreakpointManager.isEnabled()) || !breakpoint.isRegistered()) {
addBreakpointToMap(breakpoint);
sendBreakpoints();
}
} catch (CoreException e) {
DSPPlugin.logError(e);
}
}
}
@Override
public void breakpointRemoved(IBreakpoint breakpoint, IMarkerDelta delta) {
if (supportsBreakpoint(breakpoint)) {
deleteBreakpointFromMap(breakpoint);
sendBreakpoints();
}
}
@Override
public void breakpointChanged(IBreakpoint breakpoint, IMarkerDelta delta) {
if (supportsBreakpoint(breakpoint)) {
try {
if (breakpoint.isEnabled() && platformBreakpointManager.isEnabled()) {
breakpointAdded(breakpoint);
} else {
breakpointRemoved(breakpoint, null);
}
} catch (CoreException e) {
}
}
}
private void addBreakpointToMap(IBreakpoint breakpoint) {
Assert.isTrue(supportsBreakpoint(breakpoint) && breakpoint instanceof ILineBreakpoint);
if (breakpoint instanceof ILineBreakpoint) {
ILineBreakpoint lineBreakpoint = (ILineBreakpoint) breakpoint;
IMarker marker = lineBreakpoint.getMarker();
IResource resource = marker.getResource();
IPath location = resource.getLocation();
String path = location.toOSString();
String name = location.lastSegment();
int lineNumber;
try {
lineNumber = lineBreakpoint.getLineNumber();
} catch (CoreException e) {
lineNumber = -1;
}
Source source = new Source();
source.setName(name);
source.setPath(path);
List<SourceBreakpoint> sourceBreakpoints = targetBreakpoints.computeIfAbsent(source,
s -> new ArrayList<>());
SourceBreakpoint sourceBreakpoint = new SourceBreakpoint();
sourceBreakpoint.setLine(lineNumber);
sourceBreakpoints.add(sourceBreakpoint);
}
}
private void deleteBreakpointFromMap(IBreakpoint breakpoint) {
Assert.isTrue(supportsBreakpoint(breakpoint) && breakpoint instanceof ILineBreakpoint);
if (breakpoint instanceof ILineBreakpoint) {
ILineBreakpoint lineBreakpoint = (ILineBreakpoint) breakpoint;
IResource resource = lineBreakpoint.getMarker().getResource();
IPath location = resource.getLocation();
String path = location.toOSString();
String name = location.lastSegment();
int lineNumber;
try {
lineNumber = lineBreakpoint.getLineNumber();
} catch (CoreException e) {
lineNumber = -1;
}
for (Entry<Source, List<SourceBreakpoint>> entry : targetBreakpoints.entrySet()) {
Source source = entry.getKey();
if (Objects.equals(name, source.getName()) && Objects.equals(path, source.getPath())) {
List<SourceBreakpoint> bps = entry.getValue();
for (Iterator<SourceBreakpoint> iterator = bps.iterator(); iterator.hasNext();) {
SourceBreakpoint sourceBreakpoint = iterator.next();
if (Objects.equals(lineNumber, sourceBreakpoint.getLine())) {
iterator.remove();
}
}
}
}
}
}
private CompletableFuture<Void> sendBreakpoints() {
List<CompletableFuture<Void>> all = new ArrayList<>();
for (Iterator<Entry<Source, List<SourceBreakpoint>>> iterator = targetBreakpoints.entrySet()
.iterator(); iterator.hasNext();) {
Entry<Source, List<SourceBreakpoint>> entry = iterator.next();
Source source = entry.getKey();
List<SourceBreakpoint> bps = entry.getValue();
int[] lines = bps.stream().mapToInt(SourceBreakpoint::getLine).toArray();
SourceBreakpoint[] sourceBps = bps.toArray(new SourceBreakpoint[bps.size()]);
SetBreakpointsArguments arguments = new SetBreakpointsArguments();
arguments.setSource(source);
arguments.setLines(lines);
arguments.setBreakpoints(sourceBps);
arguments.setSourceModified(false);
CompletableFuture<SetBreakpointsResponse> future = debugProtocolServer.setBreakpoints(arguments);
CompletableFuture<Void> future2 = future.thenAccept((SetBreakpointsResponse bpResponse) -> {
// TODO update platform breakpoint with new info
});
all.add(future2);
// Once we told adapter there are no breakpoints for a source file, we can stop
// tracking that file
if (bps.isEmpty()) {
iterator.remove();
}
}
return CompletableFuture.allOf(all.toArray(new CompletableFuture[all.size()]));
}
public void breakpointEvent(BreakpointEventArguments args) {
// TODO Implement updates to breakpoints that come from the server (e.g.
// breakpoints inserted/modified/removed from the CLI)
}
}