The upcoming
Eclipse Indigo version of Xtext ships with experimental support for generic rename refactoring. It allows you to rename model elements and automatically fix all links to these elements. This blog entry describes the main components behind this new feature. The following screencast shows refactoring for a domainmodel language:
Enabling Refactoring
To enable refactoring support for your language, you have to add the RefactorElementNameFragment in the fragment section of the MWE workflow of your language, e.g.
// rename refactoring
fragment = refactoring.RefactorElementNameFragment {}
The fragment has an additional flag
useJdtRefactoring
which can be used to delegate to JDT's refactorings for languages using the JVM types compiling to Java (i.e. the domain model example or Xtend). Usually users are fine with the defaults.
After running the workflow, you will have working refactoring support - at least if you have stuck to the defaults with regard to naming, cross-referencing. and indexing. Give it a try. If it doesn't work yet you have to adapt the infrastructure a bit more.
Customizing
The most likely component you want to customize is the
IRenameStrategy
. This component defines how the declaration of the target element is performed. It has two major responsibilities:
- Apply and revert the declaration change on the semantic model (methods
applyDeclarationChange
and revertDeclarationChange
))
The default is to look for an EAttribute
'name' on the target object and set its value using EMFs reflective API.
- Create the LTK
Change
objects of the declaration change. These changes will be aggregated, checked for overlaps, presented to you in the preview and finally executed if you apply the refactoring.
The default is to use the ILocationInFileProvider
to locate the text range representing the name and create a ReplaceEdit
for it.
As the
IRenameStrategy
is a stateful object, you have to bind a custom
IRenameStrategy.Factory
instead of the strategy itself to integrate your changes.
The other component you might want to customize is the
IDependentElementsCalculator
. Dependent elements are those elements whose qualified name changes when the target element is renamed. E.g. when you rename a Java class the qualified names of its inner classes change, too, thus references to these have to be updated. This calculation is performed by the
IDependentElementsCalculator
. By default, all elements contained in the target element are added. This matches Xtext's default strategy of qualified name computation.
The Internals
The following describe the steps taken in the refactoring and the responsible components
Refactoring UI
In terms of UI, the refactoring of Xtext looks pretty much the same as in JDT. When the user has triggered the refactoring - by a keystroke or using a context menu entry - the editor goes into a linked editing mode allowing to edit all occurrences of the element in the current document at once. Pressing the keys again or using the popup menu, the user switched to a dialog wizard or a preview wizard. Thanks a lot to
Holger who did the major work on the UI.
The UI parts pass an element of type
IRenameElementContext
to the non-UI components, holding information on the target element, its
EClass
, the current editor etc.
Non-UI
The non-UI part and thereby the actual refactoring is mainly managed by the
RenameElementProcessor
.
As a first step, it loads the model declaring the element to be renamed into a new
ResourceSet
that is configured with respect to the declaring language. This allows to easily abandon all changes as well as avoiding concurrency issues. Some basic checks are performed like 'is the element available' or 'is the resource writeable' and the
IRenameStrategy
is initialized.
Then we calculate the
dependent elements.
After that the URIs of the elements that are affected by the refactoring are calculated. Those URIs may change, e.g. if you use a name-based fragment strategy. So we calculate the original URIs, apply the change and recalculate the new URIs. The result is a
Map<URI, URI>
. This task is the responsibility of the
IRenamedElementTracker
.
In the end, the changes are reverted to leave the resource in the same state as before.
The final step is to handle the cross-references to the renamed elements. We have to consider resource internal cross-references as well as the ones from other resources. The latter can be retrieved from Xtext's index. Note that if your external cross-references are not indexed, they won't be found and consequently won't be updated by the refactoring.
As references can come from any other language, we use a
ReferenceUpdaterDispatcher
that searches for local and indexed references and dispatches to the
IReferenceUpdater
of the referring language. We have two implementations of this interface: The
DefaultReferenceUpdater
works with Xtext resources and uses the language's serializer to update the reference text. OTOH, the
EmfResourceReferenceUpdater
works for all EMF based languages and re-serializes the whole resource after applying the semantic change.
Rename Participants
Finally, one refactoring can trigger another. E.g. when renaming a rule in an Xtext grammar, the returned
EClass
should be renamed, too. For these cases, you can register a
RenameParticipant
by the common means of LTK. If the target of the participant is Xtext based, you can use a
AbstractProcessorBasedRenameParticipant
in order to reuse lots of the already described classes.