GTSpotter is a novel search interface for spotting objects that we introduced a short while ago. It is part of the Glamorous Toolkit and it is now also available by default in the latest Pharo 4.0.
You trigger it with Shift+Enter and you search. For example, GTSpotter allows you to search through the file system. It looks like this:
Pressing Enter inspects the file. However, before pressing Enter it would be mighty useful to take a peak into that file, would it not? To this end, we introduced the Preview capability. Pressing Cmd+P toggles the preview. As we are looking at an st file, we even get code highlighting.
The preview of a picture file shows the picture.
And for a folder it shows its contents.
Obviously, these previews are not hardcoded in a central place. Instead, like in the case of the GTInspector, each object can decide how to be previewed. By default, the preview consists of the basic object structure.
By adding dedicated extension methods in desired classes allows you to customize the preview. For example, the below code shows how the source code preview of a
CompiledMethod is being defined:
title: [ self gtDisplayString ];
display: [:compiledMethod |
' ', compiledMethod getSource];
Combining the preview ability with the diving option described in the previous post, opens new possibilities for GTSpotter. For example, besides fast file browsing, GTSpotter now also can replace the myriads of windows produced by Implementors/Senders/References. For example, in the picture below, searching for
do: provides the list of implementors, and diving into a method shows more implementors and senders.
Let’s take another example: an often occurring need consists in finding previous loading scripts. Combining the search through the history of GTPlayground pages with the preview of a page provides a quick and convenient solution.
Adding preview increases dramatically the usefulness of the interface. It is now up to you to fine tune its behavior to fit your workflows.
Print it is one of the actions that exists since ancient times in Smalltalk environments. A while ago, we enhanced the GTPlayground with a Print It action that does not affect the existing code. Unlike in the classic behavior in which the printout gets inserted in the code editor, the improved version pops it up in a separate floating morph.
While this worked fine for most coding cases, every once in a while people reported the need to paste the printout in the text editor - hence the exact opposite the new solution tried to mitigate. To accommodate this use case Andrei and I made it so that you can now simply press Enter while the popping morph shows the printout and the text gets inserted in the editor.
But, there are two tiny details that come with the new version.
When no text is selected and you print, like in any Smalltalk environment, the whole like will be evaluated and printed. However, unlike other environments, the line will only be highlighted. This is a new mechanism that we introduced and that applies to any other commands as well (like evaluating or inspecting), and it is useful to provide a visual feedback to the user of what was actually executed. As opposed to selection the highlight has no influence on editing - in fact, it disappears as soon as you type anything.
When a selection does exist, the highlight is added on top. With this mechanism, when existing the popper widget via Esc or Backspace, only the highlight gets erased, and the exact editor state is restored to how it was before printing.
The other tiny detail is that when you press Enter, the text gets pasted as a comment. In this way, the syntax highlight does not get affected.
It is interesting to notice how many variations there can be even in a small feature like the one mentioned above. Just because a certain solution resisted for decades, like in the case of print it, it does not mean it is the only one or even the best one. For this reason, it is our duty to explore variations and guide the search by having the developer experience in mind.
When releasing the latest version of the Glamorous Toolkit we noticed a little problem: while in Pharo 4.0 the code worked just fine, in Pharo 3.0 we got a problem due to a reference to a class that exists only in Pharo 4.0. The problem came from the GlamourCore project, but I could not exactly see where the problem lies given that the dependencies are deeply nested.
Thus, to fix the problem I first needed a map. I inspected the object corresponding to the configuration version:
ConfigurationOfGToolkit new project version: #stable
and I started to work on a small visualization showing the dependency graph. After a couple of minutes, I ended up with a reasonable view:
The problem was that GlamourCore appeared twice: once with the 3.0.6 version and once with the 3.0.7 version. After a brief inspection, it became clear that 3.0.7 is the correct version. All that was left to do was to update the corresponding configuration dependencies.
Once the visualization code was ready, I installed it in the class of the configuration version as an inspector extension method (through the
title: 'Project map';
initializeView: [ RTMondrian new ];
painting: [ :view |
view shape label text: [:each | each asString copyReplaceAll: 'ConfigurationOf' with: ''].
view nodes: self allProjects.
connectToAll: [:each | each projects collect: #version];
view layout horizontalDominanceTree ]
To install the method, I did not have to leave my object but wrote the code right in the inspector.
After installing the new method, the inspector automatically updated itself and the visualization was ready to be used.
Equipped with the map, we could fix the configurations and use the visualization again to test that everything is fine:
The whole scenario was measured in minutes. In fact it took more time to write this post than to do the job.
Search is pervasive in software development. Yet, most IDEs treat it somewhat as a side issue. As a consequence, you get multiple different search interfaces within the same environment, each of them being limited to a specific search, and not being composable with other search intefaces.
Alex Syrel, Andrei Chis and I decided it is time to change this state of facts, and we are happy to announce the first public version of GTSpotter, a novel interface for spotting objects that is part of the Glamorous Toolkit.
We had two broad goals when developing the GTSpotter interface:
- Provide a uniform yet moldable interface that can work on any object,
- Handle searching through arbitrary levels of object nesting.
Let’s take a closer look.
Spotting packages, classes, pragmas, implementors
Searching for classes or packages is a common code search requirement. Although very similar in nature, these two searches are too often supported through separated search interfaces. GTSpotter integrates them easily in one. In the example below we see the basic interface that is triggered via Cmd+Enter. On top, the user can enter a textual query, and below the search is executed through multiple search categories. In our case, entering
GTSpo, leads to matching 39 classes (of which only 5 are shown) and 1 package. For each element, the matching substring is underlined.
The interface is fully controllable through the keyboard. The user can move with ArrowUp/ArrowDown between items or Cmd+Shift+ArrowUp/ArrowDown through categories. At the same time, the search field has the focus, so the user can switch seamlessly between selecting items and refining the search. Pressing Enter reveals the code browser.
While packages and classes are typical structural entities, annotations can be equally important. GTSpotter searches for Pharo pragmas in the same interface. In this case, pressing Enter opens an inspector on the
Searching for implementors is another common code search use case. Until now, the way to search for implementors in Pharo is to open a Playground, enter a the symbol, press Cmd+m and then close the Playground window. Tedious. GTSpotter makes it easier.
Spotting menu items
But, it's not just code that is interesting for search. Finding a tool is a search activity as well. That is why, the main GTSpotter offers, by default, the list with the items from the World menu.
GTSpotter matches substrings. For example, searching for
m b gets to the System Browser menu item, and pressing Enter spawns the browser. In essence, this produces a simple shortcut scheme.
Spotting past Playground pages
GTPlayground remembers all snippets ever used within the image. This is nice, but it can quickly become hard to find a previous snippet. That is why, GTSpotter makes this list searchable. For example, a common use of Playground is to enable the triggering of a Gofer loading script. Searching for
Gofer new reveals all snippets used in the past. Pressing Enter reveals a Playground populated with the desired code.
Spotting public Playground pages
Since a while, the GTPlayground has the ability of publishing the contents to the sharing service available at: http://ws.stfx.eu. However, until now, the playground offered no easy way to load the contents of a published page. GTSpotter fixes the situation: simply pasting the url offers an object with the remote page. Pressing Enter opens a playground with the page contents.
Spotting the last spotted objects
GTSpotter can find various types of objects rather fast. Yet, often we just want to get to the objects we just visited recently. It is for this reason that GTSpotter offers the history of previously spotted objects by default. For example, the picture below shows a history of 5 most recent objects.
Diving inside an object
Typical IDE search tools behave like general search tools in that they offer only one level of search. However, software systems have structure, and we often need to be able to search inside a found object. To this end, GTSpotter allows the user to dive in an object and continue searching through the same interface. This is accomplished by pressing Cmd+RightArrow.
For example, the below picture shows the case of going inside the
GTSpotter class. The context of the current search is shown in the breadcrumb on top of the window. For this method, the user can search through class related facts such as methods, variables or references.
Diving in a method, again offers the same interface through which the user can search for senders or implementors.
Diving in a sender reveals the senders and implementors. Thus, this simple interface provides the basic block for replacing other interfaces that are dedicated to showing the list of senders, implementors or references in a separate window.
But, GTSpotter is about objects, not about code. To exemplify the implication of this, let’s consider the typical use case of looking for a file somewhere in the directory structure inside the current directory. In the below picture, the main GTSpotter finds a directory.
Diving in the directory, allows the user to continue searching for deeper items.
Committing to Monticello from GTSpotter
Many development actions can be seen as search activities. Let’s consider the typical case of committing the changes of a package. First, we have to find the dirty package. Then, we have to find and choose the target repository, and then we commit.
These actions can be done directly from GTSpotter. When a dirty package exists in the image, it appears by default in the main GTSpotter in a separate searchable category.
Diving in the package, reveals the repositories.
Selecting a repository and pressing Enter triggers the commit dialog. This is but an example of how a search interface can change established workflows. This is certainly an area that has great potential.
Diving inside a category
Let’s search for
do:. The search raises 81 results. However, only 5 of them are shown.
How can we get a hold of
To do this, GTSpotter offers an extra action: diving in a category. Pressing Cmd+Shift+ArrowRight dives in the collection object containing only the items from that category. Thus, we can continue refining the search inside the category.
Refining a search can be applied to any category.
Spotting your objects
The existing implementations already comes with more than 30 various categories that support a rather extensive set of common search actions. However, the true power of GTSpotter comes from it being moldable.
Like with the GTInspector, each category is defined through corresponding extension method in the object representing the current search context.
By default, the main GTSpotter opens on itself. Thus, all the top level categories are defined as extensions of the
GTSpotter class. For example, searching for all classes in the image, is achieved as follows:
allCandidates: [ Smalltalk allClasses ];
Similarly, searching for the instance side methods inside a class, is achieved through an extension method of
title: 'Instance methods';
allCandidates: [ self methods ];
itemName: [ :method | method selector ];
That means that you can get your objects to be equally searchable with just a handful of lines of code.
GTSpotter offers a novel interface of getting hold of an object.
If the GTPlayground and GTInspector enable a deep conversation with an object, the GTSpotter makes it possible to start that conversation quickly.