GTInspector is the Pharo inspector that is based on lessons learnt from Moose. It is part of the Glamorous Toolkit project, it is built using it is available by default in the Moose image and can be loaded in a fresh Pharo 3.0 image by executing the following snippet:
Gofer new url: 'http://www.smalltalkhub.com/mc/Moose/GToolkit/main'; configuration; load. (Smalltalk globals at: #ConfigurationOfGToolkit) loadDevelopment (Smalltalk globals at: #GTInspector) registerToolsOn: Smalltalk tools. (Smalltalk globals at: #GTDebugger) registerToolsOn: Smalltalk tools.
(You have to install the GTDebugger as well due to a bug in the existing SpecDebugger that relies on a specific implementation of the inspector.)
The goal of the Glamorous Toolkit project is to offer a new breed of development tools based on the Glamour browsing engine. The inspector was the first important tool and it was officially announced two years ago. Since then it grew to support multiple use cases. It offers a different workflow than the default inspector and as such, it requires a little getting used to. Essentially, the inspector embodies the idea of moldable inspection through which the tool can be adapted easily to the analysis at hand.
This long post describes how the inspector works and what makes the inspector different, and it provides a hint for several usage scenarios.
Objects are not created equal. And every object is interesting from multiple points of view, too. As a consequence, a one-size-fits-all interface does not do them justice.
The GTInspector solves this problem by providing multiple interchangeable presentations for every object. By default, the GTInspector does offer a generic instance variables plus a small workspace presentation. For example, below you can see the inspector opened on a
Object>>#deep:collect:as: compiled method.
But, when reaching a compiled method, one might also want to see its source code. Or its bytecode. These are all presentations that can be switched to dynamically during inspection. For example, you can see below the source code of the
There are many objects that already have various such presentations available. For example, when inspecting a collection, you most often want to see its items. Below you see the items from
Smalltalk globals. Given that the result is a dictionary, the items are not shown in simple a list, like for regular collections, but in a table. The list view can handle easily many elements by limiting the visible items using a pagination mechanism.
Objects can have complicated states, and these should be shown easily.
Just consider the basic inspector that traditionally comes with a Smalltalk system. For some reason, it does not show the values of the instance variables. You need to select a variable in order to see its value. Why? In the GTInspector, variables are shown in a table. It’s that simple.
Let’s consider some more examples. As seen above, the source of a compiled method appears with syntax highlighting in a dedicated presentation, and collections are displayed as a list. But, depending on the situation, you want more than simple presentations like a table or text.
Consider the case of the abstract syntax tree of a method. If you want to learn how to query abstract syntax trees in general, you need to understand how they map to the code. The inspector helps you with a dedicated browser. For example, below you can see the inspector on a method node resulted by executing
(Object>>#deep:collect:as:) parseTree. In this case, selecting a node in the tree above highlights the corresponding source code at the bottom.
The GTInspector is written in Glamour. A while ago I reported a Glamour problem that required significant efforts to get fixed, and that in the end, the only way it could get solved was by means of a visualization. The visualization proved to be useful for all sorts of debugging scenarios of Glamour browsers. Thus, when reasoning about a Glamour browser we want to have that visualization at hand. The GTInspector provides it out of the box. For example, below you can see the diagram corresponding to an inspector (the actual inspected object is the morph window rendering an inspector, but the same diagram is available on an inspector object as well).
PetitParser offers another example. Specifically, when building parsers using PetitParser, you want to play with them and investigate their behavior. To this end, PetitParser provides a dedicated browser that enables you to build full parsers in a class. But, when working in a workspace, you cannot benefit from this tool. The inspector offers the same sampler workspace possibility as in the PetitParser browser, only this time it lets you try samples on any parser instances. For example, below you see the result of inspecting the following parser:
natural := (#digit asParser plus flatten trim) name: 'natural'. e := (($e asParser / $E asParser) , ($- asParser / $+ asParser) optional , natural) name: 'e'. number := (($- asParser optional , natural , ($. asParser , natural , e optional) optional) flatten) name: 'number'
In regular inspectors, the next objects are shown in either a separate window, or by replacing the existing pane. The GTInspector is based on the Miller columns technique (also seen in Mac OS X Finder) and it shows the next selected objects to the right.
One consequence of this design is that one inspector window holds one drill down session, and the programmer can at any time navigate through the steps to figure out how the current object was reached. It’s true: the chosen design consumes more space. To some extent, showing the history of a session could be provided through a back/forward buttons like in the default inspector from Pharo 3. However, the back/forward design provides for a less fluid interaction and offers limited overview over the session history. This is particularly important when drilling consists of more than five or so steps.
But, the surprising feature of showing the connected objects in a finder-like interface is the support for creating simple workflows.
For example, when searching for a morph in a tree of submorphs, you might want to see the enlarged visual representation of each morph. Below you can see how navigating through the submorphs tree of the system preferences window. This is particularly useful for debugging purposes.
Another use case is when you have a collection of items that you want to preview. If you want to get a quick way of choosing from a collection of icons, you can construct a collection with the desired items, and simply browse through them. For example, below you see the inspector showing the collection of icons that comes in the
GLMUIThemeExtraIcons icons. When an item is selected, you can preview the visual representation of the form object to the right. Because our collection is a dictionary, once you like an icon, you can simply use the name of the corresponding icon.
Not only visual objects can benefit from this type of workflow. Let’s consider the case in which you want to browse the result of querying methods that match a certain pattern. For example, in a previous post, I talked about looking for hardcoded traversal methods, and I used this query:
traversals := OrderedCollection new. Object withDeep: #subclasses do: [ :each | traversals addAll: (each methods select: [ :m | ('all*do:*' match: m selector) and: [ (m parseTree deepCollect: #children) anySatisfy: [ :node | node isMessage and: [ node selector = m selector ] ] ] ]) ]. traversals.
Opening the result of this detection provides a quick browser through which you can go through the detected source code, as seen below. It’s true that Pharo does offer by default the Finder tool, but its interface does not support arbitrary queries. With a simple workflow, the inspector fills a gap that makes querying source code more digestible.
Like in any Smalltalk inspector, you can write and execute code specific to the inspected object. For example, if you have a CompiledMethod you can execute
self parseTree to obtain the abstract syntax tree. The classic approach is to spawn another window when you desire to inspect the result of the executed code. In the GTInspector, you have another option: spawn it to the right (select and Cmd+o). After all, clicking on an instance variable is no different in intent then navigating to another object obtained by sending a message. The user interface should offer uniform manipulation.
To get a more concrete idea, here is how it looks when evaluating and opening the snippet to the left on a compiled method: the node is opened to the right with all interactive features.
With this approach, you get no new disconnected windows, and you can integrate interaction with custom code snippets arbitrarily into one single cohesive session. For a fun example, imagine you want to choose a color for a user interface. One way to do it is let serendipity play a role in it. You can inspect
Color and then send
self random. By opening to the right repeatedly (just press Cmd+o continuously), you can construct a little inspiration tool.
But, you can go a little step further. By default, loading the inspector also implies loads Roassal, the visualization engine with which you can construct views quickly. The inspector also happens to have a dedicated presentation that shows the rendering of a given view. Thus, if you need a visualization for your object, you can simply write it in place and open it in the next pane.
For example, the code below can be executed in the context of a class (the Collection class in the picture), and when opened to the right, you get see the visualization.
| view | view := ROMondrianViewBuilder new. self withDeep: #subclasses do: [ :each | view node: each ] relationDo: [ :from :to | view edge: from->to from: from to: to ]. view radialTreeLayout. view
Essentially, this functionality is similar to the one from the Roassal Easel from Moose. Using it in the inspector is not quite so convenient, given that you have to instantiate the view, and there is a bit less space for the visualization. However, a significant advantage is that the visualization on the right is interactive, and you can click on the boxes to continue exploring. This means that visualization can become part of a working session just like a query. This can be transformative to the way you approach your objects.
A similar approach can be taken with Glamour itself. If you need a quick browser for presenting your objects in a dedicated way, you can just build it in place and preview it to the right.
For example, the code below constructs a simple browser with a tree widget for displaying the classes from a class hierarchy. In this case, too, the live widget is interactive and it lets you continue exploring by selecting items.
GLMCompositePresentation new with: [:c | c tree display: #asOrderedCollection; children: #subclasses; rootsExpanded ]; startOn: self
Code is traditionally constructed in the class browser. However, in a Smalltalk-inspired environment, developers tend to think in terms of live objects. For this reason it would be natural to have code available in the midst of objects, rather having it only segregated in a separate tool. To remedy this problem, the inspector offers the code next to any object in a dedicated presentation.
For example, below you can see the browser opened on an Announcer object. The top left pane shows the inheritance hierarchy in reverse order, the top right pane shows the methods in the class, while the selected method is shown at the bottom.
There at least two scenarios in which this feature is useful. First, when playing with objects, you sometimes need to quickly lookup methods from that object. When this happens, you can just switch to the methods pane, and lookup the code.
Second, you can develop code right in place. The current browser is rather limited and it has to see significant improvements, but it already exhibits interesting abilities. It is not where you want to spend all coding time, but some things are better done fast and in place. In particular, because the code editor binds
self to the object, you can use it as a workspace, too, and can execute code and preview the result. Writing code within the context of an object enables fast prototyping.
For example, the inspector below shows a code editor in the context of an
RGMethodDefinition class. Selecting
self compiledMethod senders spawns the collection to the right.
The inspector already comes with some 41 extensions. However, its strength comes from its extensibility. Your objects are special and they deserve special treatment. Thus, you must be empowered to mold the inspector to suit your needs.
The way you do it, is by simply adding a method on the instance side of the class of interest. For example, to make
RGMethodDefinition be able to show senders, you would need a method like:
RGMethodDefinition>> gtInspectorSendersIn: composite <gtInspectorPresentationOrder: 30> composite list title: 'Senders'; display: [ self compiledMethod implementors ]
Once this is available, you will see a new senders tab in the inspector. But, wait a second, if you can write code directly in the inspector, does it not mean that you can extend the inspector from within itself? Of course it does. Look closely at the picture above and you will see that it shows the same method.
Once the code accepted, a new tab appears, and you can interact with it.
Molding the inspector to your needs has never been so accessible. What you do with it, depends only on you.
The inspector will continue to get developed. For example, the inspector relies on the mouse while it should be accessible from the keyboard alone. Or, the code editor has to be enhanced significantly to rely less on clunky panes.
Nevertheless, the inspector is already versatile enough to handle multiple use cases. Even if there are many ways to use it, it still is based on simple interactions. Essentially, you need to remember four conceptual actions: select object, select presentation, preview to the right, and Cmd+o. The combination of these, provides a uniform visual language.
Add to it the ability of extending the inspector (most of the extensions were actually done from within the inspector), and you get a new moldable perspective on live programming.