Inspector

The inspector offers several operators that can be combined in many ways to adapt the inspection to the context:

Show multiple views for each object.

Show connected objects.

Treat code evaluation like navigation.

Extend the object while inspecting.

Show multiple views for each object

Every object is different and should be allowed to look different, too. Here is an inspector example opened on an instance of CompiledMethod CompiledCode variableByteSubclass: #CompiledMethod instanceVariableNames: '' classVariableNames: '' package: 'Kernel-Methods' . Take a moment to play with the different tabs.

Classic inspectors are typically defined as tools that show the interior of an object. In contrast, we define the inspector as a tool to understand an object. Let's go back to our example. A CompiledMethod CompiledCode variableByteSubclass: #CompiledMethod instanceVariableNames: '' classVariableNames: '' package: 'Kernel-Methods' represents a method, and as we recognize a method by its source code the first view shows exactly that. However, there are other perspectives that are relevant from which to look at such an object, too. For example, when wanting to understand how the internal bytecodes look like we can simply switch to the corresponding view.

This behavior is achieved by asking the object to specify what views make sense for it. This is similar to how objects can print themselves, only we can now define multiple representations that offer interactions. For example, the extension that shows the bytecodes can be seen in CompiledCode>>#gtBytecodeFor: gtBytecodeFor: aView <gtView> ^ aView list title: 'Bytecode' translated; priority: 25; items: [ self symbolicBytecodes ]; itemText: [ :each | each printString ] .

The extension is annotated with the <gtView> pragma and is defined as a Phlow view. More details about how to define these views can be found in Phlow.

Show connected objects

Investigating software systems often requires us to drill through objects to find in the search for an answer. Once we do find the answer, we also want to be able to go back throughout the tunnel we went through to link the cause with the effect. To this end, we want to keep track of the navigation. This is achieved through the pager interface that is a variation of Miller columns interface.

Take a look at the inspector showing the working directory.

As the instance of FileReference AbstractFileReference subclass: #FileReference instanceVariableNames: 'filesystem' classVariableNames: '' package: 'FileSystem-Core-Public' represents a directory, the inspector shows the items of the directory. Clicking on an item selects the corresponding object and prepares it to be shown in a page to the right of the current one. If there does not exist a page to the right before clicking, there will only appear a triangle. Clicking on that triangle spawns the page. Once a page exists to the right, clicking on any other object shows the object. Alternatively, if a page does not yet exist, it can be spawned by double clicking on the list item.

Take a moment to explore your file system. Notice how the exploration does not feel much different from a file navigator. Yet, it happens right in the inspector, the most basic tool we have at our disposal.

This design keeps track of the last navigation path and allows one to go back through the navigation steps to trace the reasoning.

Treat code and navigation the same

Clicking on an item in the list spawns a new object to the right. Another way to obtain an object is via code. In the inspector, the two interactions are treated the same. This is achieved through the Lepiter. This simple combination enables an endless set of workflows, such as:

bridging navigation through code,

code querying, or

quick prototyping.

Extend the object while inspecting

Each class can define object extensions that are meaningful for inspecting instances of that class. One of the extensions that exists for all objects is the Meta one. This one shows a Coder for editing the class of the current object.

For example, take as look at a Color Object subclass: #Color instanceVariableNames: 'rgb cachedDepth cachedBitPattern alpha' classVariableNames: 'BlueShift CachedColormaps ColorRegistry ComponentMask ComponentMax GrayToIndexMap GreenShift HalfComponentMask IndexedColors MaskingMap RedShift' package: 'Colors-Base' .

Now, choose the Meta view, select the Color>>#gtPreviewFor: gtPreviewFor: aView <gtView> ^ aView explicit title: 'Preview'; stencil: [ | list | list := BrColumnedList new. list margin: (BlInsets all: 10). list items: { 'name:' -> self name . 'RGB red:' -> self red . 'RGB green:' -> self green . 'RGB blue:' -> self blue . 'alpha:' -> (self alpha * 100) asInteger . 'HSL hue:' -> self hue . 'HSL saturation:' -> self hslSaturation . 'HSL lightness: ' -> self lightness . 'HSV hue:' -> self hue . 'HSV saturation:' -> self hsvSaturation . 'HSV value:' -> self brightness . 'hex:' -> ('#', self asHexString) }. list column title: 'Property'; stencil: [ :assoc | BrColumnCellLabelStencil new text: assoc key asRopedText glamorousRegularFont ]. list column title: 'Value'; stencil: [ :assoc | BrColumnCellLabelStencil new text: assoc value asRopedText glamorousRegularFont]. BlElement new layout: BlFrameLayout new; constraintsDo: [ :c | c horizontal matchParent. c vertical matchParent ]; addChild: ( BlElement new aptitude: (BrShadowAptitude new); background: Color white; constraintsDo: [ :c | c frame horizontal alignCenter. c frame vertical alignCenter. c horizontal exact: 300. c vertical fitContent ]; layout: BlLinearLayout horizontal; addChild: (BlElement new background: self; constraintsDo: [:c | c vertical matchParent. c horizontal exact: 70 ]); addChild: list) ] , and change the title to something like 'Color'. Note how accepting the changed code triggers the inspector to update. Similarly, you can add new extensions from whithin the inspector and this will add new views to the current inspector.

This ability stays at the core of moldable development: Software is highly contextual, and for tools to be effective, they have to take that context into account. Thus, if a tool does not yet exist, it is imperative that developers build one. With the ability of extending the inspector during the inspection process, we can support moldable development without imposing a context switch.