Xtext has become a framework not only for domain specific languages spoken, authored and read by programmers, but also by real (business-) domain experts themselves. Sometimes these people have reservations such as “a (textual) editor can never provide as much guidance as a form can”. The weirdos among them even want intuitive and fail-safe ready-to-go solutions instead of one week training bootcamps. In this post I will introduce Xtext features that meet such requirements.
The first one is about hovers on keywords.
Out of the box, Xtext supports hovers only for identifying features of model artifacts, i.e. the name of an object or crosslinks to other objects. The hover in the example above pops up when the cursor is over “Hover” (the name of a Greeting), but not when it is over “Hello” (which is a keyword belonging to a greeting).
I’m going to show how to adjust Xtext’s Domain-Model Example to show hovers on keywords, such as
We adjust the HoverProvider and the EObjectHover
package org.eclipse.xtext.example.domainmodel.ui.hover; import org.eclipse.emf.ecore.EObject; import org.eclipse.jface.internal.text.html.HTMLPrinter; import org.eclipse.jface.text.IRegion; import org.eclipse.xtext.Keyword; import org.eclipse.xtext.ui.editor.hover.html.XtextBrowserInformationControlInput; import org.eclipse.xtext.xbase.ui.hover.XbaseHoverProvider; import com.google.inject.Inject; public class MyXbaseHoverProvider extends XbaseHoverProvider { /** Utility mapping keywords and hovertext. */ @Inject MyKeywordHovers keywordHovers; @Override protected XtextBrowserInformationControlInput getHoverInfo(EObject obj, IRegion region, XtextBrowserInformationControlInput prev) { if (obj instanceof Keyword) { String html = getHoverInfoAsHtml(obj); if (html != null) { StringBuffer buffer = new StringBuffer(html); HTMLPrinter.insertPageProlog(buffer, 0, getStyleSheet()); HTMLPrinter.addPageEpilog(buffer); return new XtextBrowserInformationControlInput(prev, obj, buffer.toString(), labelProvider); } } return super.getHoverInfo(obj, region, prev); } @Override protected String getHoverInfoAsHtml(EObject o){ if (o instanceof Keyword) return keywordHovers.hoverText((Keyword) o); return super.getHoverInfoAsHtml(o); } }
package org.eclipse.xtext.example.domainmodel.ui.hover; import org.eclipse.emf.ecore.EObject; import org.eclipse.jface.text.IInformationControlCreator; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.ITextViewer; import org.eclipse.xtext.Keyword; import org.eclipse.xtext.resource.XtextResource; import org.eclipse.xtext.ui.editor.hover.IEObjectHoverProvider; import org.eclipse.xtext.ui.editor.hover.IEObjectHoverProvider.IInformationControlCreatorProvider; import org.eclipse.xtext.util.Pair; import org.eclipse.xtext.xbase.ui.hover.XbaseDispatchingEObjectTextHover; import com.google.inject.Inject; public class MyXbaseDispatchingEObjectTextHover extends XbaseDispatchingEObjectTextHover { @Inject MyKeywordAtOffsetHelper keywordAtOffsetHelper; @Inject IEObjectHoverProvider hoverProvider; IInformationControlCreatorProvider lastCreatorProvider = null; @Override public Object getHoverInfo(EObject first, ITextViewer textViewer, IRegion hoverRegion) { if (first instanceof Keyword) { lastCreatorProvider = hoverProvider.getHoverInfo(first, textViewer, hoverRegion); return lastCreatorProvider == null ? null : lastCreatorProvider.getInfo(); } lastCreatorProvider = null; return super.getHoverInfo(first, textViewer, hoverRegion); } @Override public IInformationControlCreator getHoverControlCreator() { return this.lastCreatorProvider == null ? super.getHoverControlCreator() : lastCreatorProvider.getHoverControlCreator(); } @Override protected Pair<EObject, IRegion> getXtextElementAt(XtextResource resource, final int offset) { Pair<EObject, IRegion> result = super.getXtextElementAt(resource, offset); if (result == null) { result = keywordAtOffsetHelper.resolveKeywordAt(resource, offset); } return result; } }
package org.eclipse.xtext.example.domainmodel.ui.hover; import org.eclipse.emf.ecore.EObject; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.Region; import org.eclipse.xtext.Keyword; import org.eclipse.xtext.nodemodel.ILeafNode; import org.eclipse.xtext.nodemodel.util.NodeModelUtils; import org.eclipse.xtext.parser.IParseResult; import org.eclipse.xtext.resource.XtextResource; import org.eclipse.xtext.util.Pair; import org.eclipse.xtext.util.Tuples; /** Inspired by {@link org.eclipse.xtext.resource.EObjectAtOffsetHelper} */ public class MyKeywordAtOffsetHelper { public Pair resolveKeywordAt(XtextResource resource, int offset) { IParseResult parseResult = resource.getParseResult(); if (parseResult != null) { ILeafNode leaf = NodeModelUtils.findLeafNodeAtOffset(parseResult.getRootNode(), offset); if (leaf != null && leaf.isHidden() && leaf.getOffset() == offset) { leaf = NodeModelUtils.findLeafNodeAtOffset(parseResult.getRootNode(), offset - 1); } if (leaf != null && leaf.getGrammarElement() instanceof Keyword) { Keyword keyword = (Keyword) leaf.getGrammarElement(); return Tuples.create((EObject) keyword, (IRegion)new Region(leaf.getOffset(), leaf.getLength())); } } return null; } }
package org.eclipse.xtext.example.domainmodel.ui.hover import org.eclipse.xtext.example.domainmodel.services.DomainmodelGrammarAccess import com.google.inject.Inject import org.eclipse.xtext.Keyword // This is Xtend , not Java class MyKeywordHovers { @Inject DomainmodelGrammarAccess ga; def hoverText(Keyword k) { val result = switch (k) { case ga.entityAccess.entityKeyword_0: ''' An entity represents real business objects. It <ul> <li>can <code>extend</code> another entity,i.e. inherit the features of another entity.</li> <li>has attributes, specification syntax <code><name> : <type></code></li> <li>has operations, specification syntax <code>op <name> (<List of Parameters>)) : <Returntype></code></li> </ul> ''' } result.toString; } }
public class DomainmodelUiModule extends AbstractDomainmodelUiModule { // ... @Override public Class<? extends org.eclipse.xtext.ui.editor.hover.IEObjectHover> bindIEObjectHover() { return MyXbaseDispatchingEObjectTextHover.class; } @Override public Class<? extends org.eclipse.xtext.ui.editor.hover.IEObjectHoverProvider> bindIEObjectHoverProvider() { return MyXbaseHoverProvider.class; } }
Comments