-
Notifications
You must be signed in to change notification settings - Fork 0
Step Four
In Step Three we have built a Navigator view for our Handly-based model. It works fine, except that it cannot display the outline of the editor's current contents for a Foo file -- it only "sees" the saved contents of the file. In this step we will employ the so-called working copy facility to make that view even more alive. We will also use the resulting infrastructure to build an Outline page for the Foo editor -- another view of our model. The complete source code for this step of the running example is available in the Step Four repository.
In Handly, a source file (as a model element) can be switched into a working copy mode. Switching to working copy means that the source file's structure and properties shall no longer correspond to the file's saved contents. Instead, those structure and properties will reflect the current contents of the source file's editor. To enable this, the editor needs to be integrated with Handly.
Fortunately, our Foo language is Xtext-based, and Handly provides an out-of-the-box integration of the working copy facility with the Xtext editor. To make use of it, we need to introduce a few bindings in the Xtext UI module for the language:
// FooUiModule.java
@Override
public Class<? extends IReconciler> bindIReconciler()
{
return HandlyXtextReconciler.class;
}
public Class<? extends XtextDocument> bindXtextDocument()
{
return HandlyXtextDocument.class;
}
public Class<? extends DirtyStateEditorSupport>
bindDirtyStateEditorSupport()
{
return HandlyDirtyStateEditorSupport.class;
}
public void configureXtextEditorCallback(Binder binder)
{
binder.bind(IXtextEditorCallback.class).annotatedWith(
Names.named(HandlyXtextEditorCallback.class.getName())).to(
HandlyXtextEditorCallback.class);
}
public Class<? extends IInputElementProvider> bindIInputElementProvider()
{
return FooInputElementProvider.class;
}The only missing piece is the FooInputElementProvider. This class
should implement the interface IInputElementProvider and provide IElement
corresponding to the given IEditorInput:
// package org.eclipse.handly.internal.examples.basic.ui
/**
* Implementation of {@link IInputElementProvider} to be bound
* in the Xtext UI module for the language.
*/
@Singleton
public class FooInputElementProvider
implements IInputElementProvider
{
@Override
public IElement getElement(IEditorInput input)
{
if (input == null)
return null;
IFile file = (IFile)input.getAdapter(IFile.class);
return FooModelCore.create(file);
}
}That's it. Nothing more is required to enable Handly/Xtext integration.
Let's test it. Launch a runtime workbench with the workspace where
you have imported the Test002 project in the previous step.
Ensure that you have opened the Foo Navigator and expand the Test002
and test.foo elements in the view. Then, open the test.foo file
with the Xtext editor and try declaring a new variable: var z;.
Hmm, our view didn't get updated. What's wrong?
Well, we need to notify the view about changes in the working copy.
We will use our existing change notification mechanism
for that. In that way, every IElementChangeListener registered with the
Foo model will get notified about working copy changes. Even better, the
notifications will describe exactly how the working copy changed
(i.e. what variables/functions were added/removed etc.)
To enable notifications about working copy changes, we just need to register the notification manager in the model context:
// FooModelManager.java
private Context modelContext;
public void startup() throws Exception
{
fooModel = new FooModel();
elementManager = new ElementManager(new FooModelCache());
notificationManager = new NotificationManager();
// new code -->
modelContext = new Context();
modelContext.bind(INotificationManager.class).to(
notificationManager);
// <-- new code
fooModel.getWorkspace().addResourceChangeListener(this,
IResourceChangeEvent.POST_CHANGE);
}
public void shutdown() throws Exception
{
fooModel.getWorkspace().removeResourceChangeListener(this);
// new code -->
modelContext = null;
// <-- new code
notificationManager = null;
elementManager = null;
fooModel = null;
}
public IContext getModelContext()
{
if (modelContext == null)
throw new IllegalStateException();
return modelContext;
}and expose the model context in FooModel:
// FooModel.java
@Override
public IContext getModelContext_()
{
return FooModelManager.INSTANCE.getModelContext();
}That will make the notification manager accessible to the generic working copy
change notification facility (for details, see the
ISourceFileImplSupport.workingCopyModeChanged_ method and the nested class
NotifyingReconcileOperation).
And while we are here, let's also handle the special case of a working copy
in the FooDeltaProcessor:
// FooDeltaProcessor.java
private void contentChanged(IFooFile fooFile)
{
// new code -->
if (fooFile.isWorkingCopy())
{
builder.changed(fooFile, IElementDeltaConstants.F_CONTENT
| IElementDeltaConstants.F_UNDERLYING_RESOURCE);
return;
}
// <-- new code
close(fooFile);
builder.changed(fooFile, IElementDeltaConstants.F_CONTENT);
}Everything should work fine now.
Besides providing out-of-the-box integration with the Xtext editor, Handly can also support other editors. In particular, it allows for integration with TextFileBuffer-based editors. The following notes are intended as forward pointers only; a full-blown editor implementation is beyond the scope of this tutorial.
To integrate a TextFileBuffer-based editor with the working copy facility,
you'll need to subclass the editor's document provider from the class
SourceFileDocumentProvider, which extends the TextFileDocumentProvider.
For example:
public class FooFileDocumentProvider
extends SourceFileDocumentProvider
{
@Override
protected ISourceFile getSourceFile(Object element)
{
if (!(element instanceof IEditorInput))
return null;
IInputElementProvider provider =
Activator.getDefault().getFooInputElementProvider();
element = provider.getElement((IEditorInput)element);
if (!(element instanceof ISourceFile))
return null;
return (ISourceFile)element;
}
}You'll also need to implement a Handly-based reconciler for the editor:
public class FooReconciler
extends EditorWorkingCopyReconciler
{
public FooReconciler(IEditorPart editor, IWorkingCopyManager manager)
{
super(editor, manager);
}
@Override
protected void addElementChangeListener(IElementChangeListener listener)
{
FooModelCore.getFooModel().addElementChangeListener(listener);
}
@Override
protected void removeElementChangeListener(IElementChangeListener listener)
{
FooModelCore.getFooModel().removeElementChangeListener(listener);
}
}The reconciler is connected to the editor via a source viewer configuration:
public class FooSourceViewerConfiguration
extends TextSourceViewerConfiguration
{
private final ITextEditor editor;
private final IWorkingCopyManager manager;
public FooSourceViewerConfiguration(IPreferenceStore preferenceStore,
ITextEditor editor, IWorkingCopyManager manager)
{
super(preferenceStore);
this.editor = editor;
this.manager = manager;
}
@Override
public IReconciler getReconciler(ISourceViewer sourceViewer)
{
if (editor != null && editor.isEditable())
{
return new FooReconciler(editor, manager);
}
return null;
}
}Then, you can wire it all together in the editor:
public class FooEditor
extends AbstractDecoratedTextEditor
{
@Override
protected void initializeEditor()
{
super.initializeEditor();
FooFileDocumentProvider provider =
Activator.getDefault().getFooFileDocumentProvider();
setDocumentProvider(provider);
setSourceViewerConfiguration(new FooSourceViewerConfiguration(
getPreferenceStore(), this, provider));
}
}Now that our model has truly come to life with all the infrastructure in place, it should be very easy to create a Foo Outline page using the Common Outline framework provided by Handly. The outline will display the same kind of elements as the Foo Navigator and can reuse existing content and label providers. Basically, we've got a model and would like to have a number of views on it.
public class FooOutlinePage
extends HandlyXtextOutlinePage
{
@Inject
private FooContentProvider contentProvider;
@Inject
private FooLabelProvider labelProvider;
@Override
protected ITreeContentProvider getContentProvider()
{
return contentProvider;
}
@Override
protected IBaseLabelProvider getLabelProvider()
{
return labelProvider;
}
@Override
protected void addElementChangeListener(IElementChangeListener listener)
{
FooModelCore.getFooModel().addElementChangeListener(listener);
}
@Override
protected void removeElementChangeListener(IElementChangeListener listener)
{
FooModelCore.getFooModel().removeElementChangeListener(listener);
}
}The code should be self-explanatory. The superclass HandlyXtextOutlinePage
provides rich outline functionality out-of-the-box, including 'Link with Editor'
and 'Sort'. The outline will refresh itself when its contents is affected
by a change in the Foo model. It will also reset its input when the
corresponding editor input changes.
The Outline page needs to be bound in the Xtext UI module for the language:
// FooUiModule.java
@Override
public Class<? extends IContentOutlinePage> bindIContentOutlinePage()
{
return FooOutlinePage.class;
}Launch the runtime workbench and test the newly added functionality.
We would like to close this guide with a quote from the classic book Contributing to Eclipse by Erich Gamma and Kent Beck. It deeply resonates with our intent:
Overcoming this feeling of dislocation is the primary goal... When you are finished, you won't have a complete map, but you'll know at least one place to get each of your basic needs met. It's as if we draw you a map ... marked with six streets, a restaurant, and a hotel. You won't know everything, but you'll know enough to survive, and enough to learn more.
We hope this tutorial will help you get started in a similar way.
You should know enough now to explore Handly and venture out on your own.
To dive deeper, let us point you to the project's web site
and, of course, source code.
In particular, you might be interested in a more advanced exemplary
implementation that Handly provides: example model for Java
(org.eclipse.handly.examples.jmodel). There is also a concise
architectural overview
of the Handly core framework that can be used as a quick refresher of the concepts
learnt from this guide. Last but not least we have created an
experimental fork of Eclipse Java development tools,
which demonstrates how Handly can be used within the context of a
non-trivial existing model implementation.
If you have found an error in the text or in a code example, or would like to suggest an enhancement, please raise an issue against the guide's repository. We also accept pull requests. It is open source, after all! ;-)
If you could not find an answer to your question or would like to provide feedback about this tutorial or about Handly in general, consider using the project's forum or mailing list.
Put Handly to work for you!
© 2020 1C-Soft LLC. Made available under the Eclipse Public License 2.0