Extending the Pharo debugger with custom actions

One of the main goals of the current debugger from Pharo is to be easily extensible with new debugging actions and new user interfaces. Towards that goal it models all debugging actions as objects that can be dynamically added to various widgets from the user interface. A high-level view of the debugger is given below.

Debugger Model

We can see that a debugger is split into three distinct components:

  1. the session
  2. the debugging actions
  3. the user interface

The session is the base component of the debugger. Its role is to implement the low-level operations needed for working with processes and execution contexts. For simple actions one does not need to extend it. Each debugging action is encapsulated in a subclass of DebugAction. Debugging actions are then loaded by the user interface. Currently there are two different user interfaces for the debugger one written in Spec and the other in Glamour.

Next we will go through the process of creating, step by step, one debugging action. As a running example we will implement an action for reacting to doesNotUnderstand exceptions. This exception is raised when an object is asked to execute a method that is not implemented by its class or any superclass. Given that Pharo uses dynamic typing, this error is *caught* at run time and a debugger is opened. The debugger allows us to create the missing method. We can implement this feature using a debugging action.

First we need to create a subclass of DebugAction. We will name the class DoesNotUnderstandDebugAction and put it in the category ‘DebuggingActions’, as it should go into the main debugger:

DebugAction subclass: #DoesNotUnderstandDebugAction
	instanceVariableNames: ''
	classVariableNames: ''
	category: 'DebuggerActions'

To customize the action we need to override several methods. The first one is #id and it should return a symbol uniquely identifying the debugging action.

DoesNotUnderstandDebugAction>>#id
	^  #doesNotUnderstand

To change the label of the action we need to override #defaultLabel and to control the position of this action among the other debugging actions #defaultOrder.

DoesNotUnderstandDebugAction>>#defaultLabel
	^   'Create'

DoesNotUnderstandDebugAction>>#defaultOrder
	^   45

An important aspect of this action is that it should only be active when the debugger is opened as a result of a doesNotUnderstand exception. To achieve this we need to override #appliesToDebugger:. This method allows us to control when an action is active.

DoesNotUnderstandDebugAction>>#appliesToDebugger: aDebugger
	^ aDebugger session isInterruptedContextDoesNotUnderstand 

Last but not least we need to implement the actual logic for creating the missing method. This is achieved by overriding executeAction. In this case the logic is a bit complicated but you do not need to understand it in details (I also skipped the code for the helper methods).

DoesNotUnderstandDebugAction>>#executeAction	
	| msg msgCategory chosenClass |

	msg := self interruptedContext tempAt: 1.
	chosenClass := self 
		askForSuperclassOf: self interruptedContext receiver class
		toImplement: msg selector
		ifCancel: [^self].
	msgCategory := (self askForCategoryIn: chosenClass default: 'as yet unclassified').
	self  session
		implement: msg 
		classified: msgCategory 
		inClass: chosenClass 
		forContext: self interruptedContext.
	self debugger selectTopContext

Now that the action is ready we can load it in the debugger. Each widget of the user interface loads debugging actions that have on the class side a method with a particular annotation. For example, the SpecDebugger loads in the toolbar action annotated with debuggingAction; the GTDebugger uses gtDebuggingAction. In our case the following code is required:

DoesNotUnderstandDebugAction class>>actionType
	<debuggingAction>

That’s it. Now if we execute code that is calling a missing method (Morph new backgroundColor) we get the possibility to create that method.

MessageNotUnderstood: Morph>>backgroundColor

More information about what makes this possible can be found at scg.unibe.ch/research/moldabledebugger.

Also the debugger is not the only extensible tool from Pharo. There are many more, as part of the Glamorous Toolkit.