Friday, November 15, 2013

Debugger 5: UI breakpoint integration

In the previous tutorial we implemented stepping through our source code. While single stepping is quite nice, we need to add breakpoint support for a better debugging experience. By the end of this tutorial we will be able to set them using the UI:

In eclipse there exist different kinds of breakpoints: line breakpoints (stop execution at a certain line), function breakpoints (stop whenever function foo() is called), watchpoints (stop whenever a variable is accessed/changed) and some more. Not all of them make sense for a dedicated language. In this tutorial we will have a closer look at line breakpoints only.
  1. A fictional interpreter
  2. The launch framework
  3. A tale of debuggers, processes and threads
  4. Stepping, suspending and other actions
  5. UI breakpoint integration
  6. Debugger breakpoint integration
  7. Display current source code - source lookup
  8. Run to line support
  9. Displaying variables
  10. Displaying memory areas
Source code for this tutorial is available on googlecode as a single zip archive, as a Team Project Set or you can checkout the SVN projects directly. 

Step 1: Breakpoint definition

Breakpoints are defined by the breakpoints extension point. To make such breakpoints persistent, eclipse uses markers. So each breakpoint is backed by a marker which is bound to a workspace resource.

Create a new org.eclipse.core.resource.markers extension in com.codeandme.textinterpreter.debugger/plugin.xml. The marker ID should be a single name without any plugin name qualifier before it, so set it to "textLineBreakpointMarker". The full marker name is automatically prepended with the Plug-in ID. Our marker should be a subtype of org.eclipse.debug.core.lineBreakpointMarker and persistable.

Afterwards we may create an org.eclipse.debug.core.breakpoints extension which refers to the marker type just created before.


The implementation is straight forward. We only add a constructor to attach a breakpoint to a resource:
package com.codeandme.textinterpreter.debugger.breakpoints;

public class TextLineBreakpoint extends LineBreakpoint {

 public TextLineBreakpoint() {
 }

 public TextLineBreakpoint(final IResource resource, final int lineNumber) throws CoreException {
  this(resource, lineNumber, true);
 }

 protected TextLineBreakpoint(final IResource resource, final int lineNumber, final boolean persistent) throws CoreException {
  IWorkspaceRunnable runnable = new IWorkspaceRunnable() {
   @Override
   public void run(IProgressMonitor monitor) throws CoreException {
    IMarker marker = resource.createMarker("com.codeandme.textinterpreter.debugger.textLineBreakpointMarker");
    setMarker(marker);
    marker.setAttribute(IBreakpoint.ENABLED, true);
    marker.setAttribute(IBreakpoint.PERSISTED, persistent);
    marker.setAttribute(IMarker.LINE_NUMBER, lineNumber);
    marker.setAttribute(IBreakpoint.ID, getModelIdentifier());
    marker.setAttribute(IMarker.MESSAGE, "Line Breakpoint: " + resource.getName() + " [line: " + lineNumber + "]");
   }
  };
  run(getMarkerRule(resource), runnable);
 }

 @Override
 public String getModelIdentifier() {
  return TextDebugModelPresentation.ID;
 }
}
Make sure to keep the default constructor, as it is needed by the debug framework to restore breakpoints.

As you can see, we used a new class here: TextDebugModelPresentation. It is needed to embrace all parts of the debug model for a certain debugger type. For now it is an empty implementation of IDebugModelPresentation, only providing its ID as a static constant. Do not forget to register it as an extension in com.codeandme.textinterpreter.debugger/plugin.xml.


Step 2: Add breakpoint support to editors

Users want to set their breakpoints in the code editor. For that our editor of choice needs to implement the IToggleBreakpointsTarget interface. Unfortunately the default text editor we use does not support this interface. Therefore we need to create an adapter for the editor. The adapter pattern is out of scope of this tutorial. If you need more information on how to create adapters you may read this article.

The adapter factory creates instances of TextLineBreakpointTarget:
package com.codeandme.textinterpreter.debugger.breakpoints;

public class TextLineBreakpointTarget implements IToggleBreakpointsTarget {

 // FIXME for full source code see http://codeandme.googlecode.com/svn/trunk/blog/debugger/05/com.codeandme.textinterpreter.debugger/src/com/codeandme/textinterpreter/debugger/breakpoints/TextLineBreakpointTarget.java

 @Override
 public boolean canToggleLineBreakpoints(IWorkbenchPart part, ISelection selection) {
  return true;
 }

 @Override
 public void toggleLineBreakpoints(IWorkbenchPart part, ISelection selection) throws CoreException {
  ITextEditor textEditor = getEditor(part);
  if (textEditor != null) {
   IResource resource = (IResource) textEditor.getEditorInput().getAdapter(IResource.class);
   ITextSelection textSelection = (ITextSelection) selection;
   int lineNumber = textSelection.getStartLine();
   IBreakpoint[] breakpoints = DebugPlugin.getDefault().getBreakpointManager().getBreakpoints(TextDebugModelPresentation.ID);
   for (int i = 0; i < breakpoints.length; i++) {
    IBreakpoint breakpoint = breakpoints[i];
    if (resource.equals(breakpoint.getMarker().getResource())) {
     if (((ILineBreakpoint) breakpoint).getLineNumber() == (lineNumber + 1)) {
      // existing breakpoint; delete
      breakpoint.delete();
      return;
     }
    }
   }

   // new breakpoint; create
   TextLineBreakpoint lineBreakpoint = new TextLineBreakpoint(resource, lineNumber + 1);
   DebugPlugin.getDefault().getBreakpointManager().addBreakpoint(lineBreakpoint);
  }
 }
}

Similar to the step and resume commands in the previous tutorials we have to implement some canToggleBreakpoint() methods. In our case we allow for LineBreakpoints only. The toggling itself is simple:
  1. get the active editor (line 14)
  2. get the current line number from the selection (lines 17, 18)
  3. look for an existing breakpoint at that line (lines 19-29)
  4. optionally delete an existing breakpoint or create a new one (lines 25/33)
When launching your application you should be able to set breakpoints using the Run/Toggle Breakpoint menu entry.

Step 3: Toggle breakpoints from the ruler context menu

To add an item to the context menu of the ruler we need to use actions. Open the plugin.xml of the debugger project and add a new org.eclipse.ui.popupMenus extension. Add a new viewerContribution to it. Set id to "textEditor.rulerActions" and targetID to "#TextRulerContext". Now add a new action with id set to "pda.editor.ruler.toggleBreakpointAction". The implementation is rather boring as it only resolves the line number of the mouse click and then uses our IToggleBreakpointsTarget from before to toggle a line breakpoint.
 
Conclusion

We are now able to set breakpoints in the UI. But our debugger does not use them right now. In the next tutorial we will implement debugger support for breakpoints.

1 comment:

  1. Very nice tutorials! Exactly what i need for my project at the moment.

    ReplyDelete