Thursday, December 14, 2017

Debugger 11: Watch expressions

Now that we have variables working, we might also want to include watch expressions to dynamically inspect code fragments.

Debug Framework Tutorials

For a list of all debug related tutorials see Debug Framework Tutorials Overview.

Source code for this tutorial is available on github as a single zip archive, as a Team Project Set or you can browse the files online.

Step 1: Provide the Watch Expression Delegate

Watch points are implemented via an extension point. So switch to your plugin.xml and add a new extension point for org.eclipse.debug.core.watchExpressionDelegates.
The new delegate simply points to our debugModel identifier: com.codeandme.debugModelPresentation.textinterpreter and provides a class implementation:
public class TextWatchExpressionDelegate implements IWatchExpressionDelegate {

 @Override
 public void evaluateExpression(String expression, IDebugElement context, IWatchExpressionListener listener) {
  if (context instanceof TextStackFrame)
   ((TextStackFrame) context).getDebugTarget().fireModelEvent(new EvaluateExpressionRequest(expression, listener));
 }
}
Delegates can decide on which context they may operate. For our interpreter we could evaluate expressions on StackFrames, Threads or the Process, but typically evaluations do take place on a dedicated StackFrame.

Step 2: Evaluation

Now we apply the usual pattern: send an event, let the debugger process it and send some event back to the debug target. Once the evaluation is done we then will inform the provided listener of the outcome of the evaluation.
public class TextDebugTarget extends TextDebugElement implements IDebugTarget, IEventProcessor {

 @Override
 public void handleEvent(final IDebugEvent event) {

   [...]

   } else if (event instanceof EvaluateExpressionResult) {
    IWatchExpressionListener listener = ((EvaluateExpressionResult) event).getOriginalRequest().getListener();
    TextWatchExpressionResult result = new TextWatchExpressionResult((EvaluateExpressionResult)event, this);
    listener.watchEvaluationFinished(result);    
   }
 }
The TextWatchExpressionResult uses a TextValue to represent the evaluation result. As before with variables we may support nested child variables within the value. In case the evaluation failed for some reason we may provide error messages which do get displayed in the Expressions view.

Debugger 10: Editing variables

In the previous tutorial we introduced variables support for our debugger. Now lets see how we can modify variables dynamically during a debug session.

Debug Framework Tutorials

For a list of all debug related tutorials see Debug Framework Tutorials Overview.

Source code for this tutorial is available on github as a single zip archive, as a Team Project Set or you can browse the files online.

Step 1: Allowing for editing and trigger update

First variables need to support editing. Then the variables view will automatically provide a text input box on the value field once clicked by the user. This is also a limitation: editing variables requires the framework to interpret an input string and process it accordingly to the target language.

The relevant changes for the TextVariable class are shown below:
public class TextVariable extends TextDebugElement implements IVariable {

 @Override
 public void setValue(String expression) {
  getDebugTarget().fireModelEvent(new ChangeVariableRequest(getName(), expression));
 }

 @Override
 public boolean supportsValueModification() {
  return true;
 }

 @Override
 public boolean verifyValue(String expression) throws DebugException {
  return true;
 }
}
verifyValue(String) and setValue(String) are used by the debug framework when a user tries to edit a variable in the UI. We do not need to update the value yet, but simply trigger an event to update the variable in the debugger.

Step 2: Variable update & refresh

As our primitive interpreter accepts any kind of text variables there is nothing which can go wrong here. Instead of sending an update event for the changed variable we simply use the already existing VariablesEvent to force a refresh of all variables of the current TextStackFrame:
public class TextDebugger implements IDebugger, IEventProcessor {

 @Override
 public void handleEvent(final IDebugEvent event) {

  [...]

  } else if (event instanceof ChangeVariableRequest) {
   fInterpreter.getVariables().put(((ChangeVariableRequest) event).getName(), ((ChangeVariableRequest) event).getContent());
   fireEvent(new VariablesEvent(fInterpreter.getVariables()));
  }
 }
}