A recent discussion on the Pharo mailing list revealed that people would like to have the possibility of looking for senders of a symbol and references to a class in one step in Spotter. A complementary request was that when we know that we want to search only for senders, we do not want to search for anything else. Stefan Reichhart worked with me to provide the implementation for these use cases.
For example, searching for
#ref Morph will reveal all references to the class
#sen do: will reveal all senders of
There are some things that are not as nice as they can be.
First, the search only shows 5 entries even if there is plenty of space for showing more. This is because right now this limit is hardcoded in the definition of the search processor. We need to make this limit more fluid to also take into account the context of the search. That is, if there is only one category shown, we should be able to see all results.
Second, the categories are not easily discoverable. Ideally, we would like an autocompletion mechanism. This issue is addressed in more details below.
Third, an interesting observation came from Ben Coman and Stéphane Ducasse on the mailing list. Ben noted that it would be interesting to have a
Cmd+n, the pervasive keybinding that searches for senders, be used to autocomplete
#senders in the Spotter search. At the same time, Stef came up with the idea of providing unique one-letter shortcuts for common searches. For example, in the case of senders, we could use
$n. These are things we will play with soon.
This example is a good opportunity to learn how Spotter works. Let’s take a step back and look at how this is implemented.
One question that pops up often is what a word containing a hash actually means. Let’s consider the senders example.
#sen is simply a filter that matches the name of the search category (in our case
#Senders). Given that there is only one category that begins with
#sen, only the Senders search processor will be executed and only the corresponding result category will appear.
But, what would happen if we would use only
#s? Let’s see.
In this case, we get two categories that appear:
Now, the question is how do we know what are all the categories. This is not yet as discoverable as we would want from the user point of view. One way to find this out is by simply searching for a string and then browsing through the categories that appear for that search. Still, this will only provide a way to discover a list of the categories that matched the specific search, and not all possible categories.
At the moment, the most reliable way of finding what processors are available is by looking at the implementation. Wait, that is not as hard as it might appear, especially in a live environment that allows you to narrow down on important pieces of information. Let’s see. Every Spotter processor is defined as a method in a class corresponding to objects for which we want this processor to be active. For example, as our new senders processor is available in the first step, and as the context of that first step is provided by an instance of the
GTSpotter class, it follows that we should find a method in that class. Here it is:
GTSpotter>>spotterForSendersFor: aStep <spotterOrder: 31> aStep listProcessor title: 'Senders'; filter: GTNullFilter item: [ :filter :context | SystemNavigation default allSendersToString: context textTrimmed do: filter ]; wantsToDisplayOnEmptyQuery: false
This relies on a small utility method:
SystemNavigation>>allSendersToString: aString do: aBlock aString isEmptyOrNil ifTrue: [ ^ self ]. aString asSymbol ifNotNil: [ :symbol | self allBehaviorsDo: [ :class | class withMethodSender: symbol do: aBlock ] ]
Please note that the implementation of this use case took 13 lines of code.
To find all processors defined in the image, we simply need to find all usages of
<spotterOrder:>. This can be achieved by using Spotter itself. First we search for
spotterOrder: to find the pragma type:
And then we dive in to find the concrete pragmas:
The same information can also be found by programmatically through the Playground and Inspector, like shown previously. In fact, given that this is such a relevant query for a programmer that wants to learn about Spotter, we already have a method that provides this (currently, there are 118 processors defined in the base Pharo image). For example, the following query will return the extensions that are defined in the top
GTSpotter class (there are 24 of them in the base Pharo image):
GTSpotter spotterExtendingMethods select: [ :each | each methodClass = GTSpotter ]
But, that is not all. Given that we want to provide live documentation, inspecting the GTSpotter class, reveals a dedicated tag with these extensions as well:
In this post we showed that Spotter now supports two new common use cases in one step: searching for senders and for references. While at first look, this appears as a new feature, in fact this is not a special behavior, but a generic mechanism in Spotter that allows us to obtain what we need in specific cases with very little programatic customization.
We took the opportunity to take a closer look at the implementation and we learnt that the extension for senders is 13 lines long (the one for references is similar). It is hard to imagine it going smaller than this.
Getting used to this way of thinking about tools does take some effort. To ease those first steps, we also went through different ways of discovering the existing implementation.
There are certainly more that we can do in this direction, but this example shows once again that going deep down the path of moldable tools in a live environment offers a significant departure from classic IDE concepts. It’s a fun way, and we think it’s worth learning it. At least, give it 5 minutes. Play with it. In fact, let’s play together.