Enhancing the support for multi-step refactorings in Glamorous Toolkit

We are enhancing the support in Glamorous Toolkit for refactorings that require multiple actions and steps. Our goal is to support everything with no modal dialogs.

The first target is "Extract Method" for Pharo code. The second is "Extract Method Object". They are similar, but require slightly different user interface interactions that we wanted to accommodate seamlessly, directly in the editor. Let's look at them in detail.

Extract Method

Let's start with "Extract Method".

When we extract a method, we typically want to extract it in the same class. But that is not always the case. So, now we can choose the variable to extract to from a dropdown directly in the editor.

When we choose a variable that is not self, we get the option to specify the class in which the method will go to, if the class is not already inferred from the code.

Of course, sometimes the order of the variables is not quite the desired one. That's why we can reorder arguments through buttons right in the editor.

We can cancel the refactoring either by pressing Escape, editing the method outside of the embedded editors or clicking on the cancel button embedded in the code. And when we are ready, we get a preview right in place.

Extract Method Object

"Extract Method Object" is quite similar to "Extract Method". The difference is that here we want to instantiate an object, typically of a new class.

Let's look at our example in which we select statements depending on variables (temps and an arg) from the current method.

When we extract a method object, we want to instantiate a class, populate some state, and send a final message. By default, if we find an argument in the used variables, we will have it passed in the final message. All other variables are set in a cascade before it.

The class can be edited as well, including optionally specifying the superclass and the package from a dropdown inside the editor. The defaults are Object and the current package.

Now, here the ordering of arguments is slightly more demanding. On the one hand, we want to reorder the arguments in the final message. On the other hand, we want to move arguments between the final message and state setting. All achieved through buttons in the editor.

No modal dialogs

Seeing this interface materialized is quite exciting. Indeed, this is just a small detail in the whole environment, but it was one of the hardest to crack.

About five years ago we started Glamorous Toolkit from scratch, and one of the guiding principles was to have zero modal dialogs. Zero.

Back then, we did not know the details of how to do it, but we knew it will require new kinds of interactions, especially around code editing. The extract method and method object refactorings are some of the most demanding code interactions I know of. In all other environments we've seen that offer such refactorings, they are supported with modal dialogs. It is indeed an easy solution. Too easy.

We started to play with refactorings almost from the beginning because we knew it will take time to learn and let the pieces come together. For example, here is how it looked like 4 years ago:

The key ingredient was to make the editor flexible enough to accept dynamically arbitrary elements in it. To do that, we had to build a whole new graphical stack. So we did.

Choosing to build a new graphical stack was perhaps not be most sane of decisions. We knew that before we started, but we did it anyway because we saw no other option to build a modal-less experience (and a bit more). We wanted the ability to create an editor that can combine both

It took a while, and now the pieces came together. We'd still like to improve the support for refactorings. For example, we'd like to animate how arguments move from one part to another. But the current solution covers functionally all needed actions. Not only does it rely on no modal dialogs, we find that it feels much better for it, too.

Reproducing the refactoring scenario

To reproduce the situation displayed in the examples above, inspect the following snippet in a recent version of Glamorous Toolkit:

Object subclass: #Something
	instanceVariableNames: ''
	classVariableNames: ''
	package: 'Something'.
#Something asClass compile: 'writeOn: aStream
	| something to extract |
	something := ''Something''.
	to := ''to''.
	extract := ''extract''.
	aStream nextPutAll: something.
	aStream space.
	aStream nextPutAll: to.
	aStream space.
	aStream nextPutAll: extract.'.
#Something asClass>>#writeOn: