Archive for the ‘TMF’ Category

Xtext2 keyword hovers

Tuesday, February 12th, 2013

The current default implementation restricts hovers to significant region of an object. Think of them as the region of the name of the object. However, you may want also want to provide information for keywords (e.g. explanations for complicated modifiers).

The first step towards keyword hovers is overriding the binding for IEObjectHover.

public class MydslEObjectHover extends DispatchingEObjectTextHover {

  @Inject 
  MyDslGrammarAccess grammarAccess;
  
  @Override
  protected Pair<EObject, IRegion> getXtextElementAt(XtextResource resource,
      int offset) {
    Pair<EObject, IRegion> temp = super.getXtextElementAt(resource, offset);
    if(temp==null){
      ILeafNode node = NodeModelUtils.findLeafNodeAtOffset(
          resource.getParseResult().getRootNode(), offset);
      if(node.getGrammarElement() instanceof Keyword){
        IRegion region=new Region(node.getOffset(), node.getLength());
        temp= Tuples.create(node.getGrammarElement(), region);
      }
    }
    return temp;
  }

  @Override
  public Object getHoverInfo(EObject first, ITextViewer textViewer,
      IRegion hoverRegion) {
    if(first instanceof Keyword){
      return getHoverInfoForKeyword((Keyword)first);
    }else{
      return super.getHoverInfo(first, textViewer, hoverRegion);
    }
  }

  private Object getHoverInfoForKeyword(final Keyword keyword){
    //use grammarAccess here to see which Keyword you are dealing with
    //and determine the text to show
//    if(keyword==grammarAccess.getGreetingAccess().getHelloKeyword_0()){
//      //...
//    }
    return keyword.getValue();
  }
}

The second step is providing the information for the keyword hover, the third making it look nice.

Xtext: empty string linking

Saturday, February 2nd, 2013

Xtext’s cross reference mechanism is based on named elements. The out-of-the-box support requires that the (simple) name is not empty – there must be some syntactic element that can be associated with the link (both source and target). Linking empty names may not be a default requirement, but it is not purely academic. XML QNames allow the namespace prefix as well as the local name to be empty. While working on Xturtle – an eclipse editor for the RDF serialization format turtle – I came across this use case.
@prefix :<http://www.example.org/>.
:thing a :thing.

The name of the default prefix is empty. Now, if you want the expected editor features like go to declaration, find references or rename refactoring, you kind of need the actual linking.

So here is a list of the main components that need to be adapted. This project provides you with a stripped down working example.

Grammar

In your grammar, you have to make sure that the name an the reference are both mandatory, however the actual string may be empty.
Target: "target" name=Name ".";
Link: "link" to=[Target|Name]".";
Name: ID?;

QualifiedName calculation

In the instantiated model, the name attribute value will be null. Your IQualifiedNameProvider will have to turn that into an empty name. The default implementation of the IQualifiedNameConverter throws an exception for empty names, so that has to be adapted as well.

Linking

If you don’t provide a name in the model, there will be no element in the node model attached to the name.
target /*this is a linking target with an empty name*/
/*The problem is, which empty string between the
keywords target and full stop represents the name*/
/*Xtext cannot know, so empty names are not supported out of the box*/
/*By the way, the same is true for the link.
At which position does the link start?*/
.

Having made the features mandatory, there will be cross reference node, but the linking will not pick it up and create a proxy, as there is no suitable node to attach it to – the corresponding code has to be adapted.

Hyperlinking

You can now navigate from link to target in the semantic model, however hyperlinking is not working yet. You’ll have to tell the framework, from which position in the link actually to jump to the target (for the empty name case).

ILocationInFileProvider

This service is responsible for calculating significant regions of objects, i.e. which part of the file to reveal and highlight. Again the default implementation is bound to fail if there are no actual nodes for the name element.

Refactoring

I have not adapted the refactoring component yet and a first 15 min investigation indicates that quite a bit of work has to be done. I will update the post when there are news.

Custom folding actions and configurable error levels with Xtext2

Friday, December 14th, 2012

Recently, I have been working on an eclipse editor prototype for the semantic web language turtle based on Xtext2. Two features not supported out of the box are custom folding actions and configurable error levels. You can find my solution attempt on github.

Additional preference pages

When adding your own preference pages, don’t forget to adapt the XtextEditor, so that these pages are not filtered away when opening the preferences from the editor’s context menu.

Folding

Adapting the creation of folding regions is quite well supported, already. The problem is, that they are not typed, i.e. the region has no information about the type of the foldable content. Worse still, it is everything but trivial to extract that information from the region finally created. This is because the region starts at the beginning of the first line and ends with the last character on the last line. So you cannot easily use the node model (at least I could not) to ask for the semantic element, as you don’t know the exact offset to use – the regions of foldable types may overlap after all.

What I did is using a custom region holding the type information. I added a custom folding action “Collapse strings” to the folding menu and a preference page for defining which type of region should be folded on editor startup. You find the main code in the folding package of the UI project, plugin.xml and the custom XtextEditor-implementation. I’d be interested in suggestions for a more elegant solution.

Custom error levels

Allowing configurable error levels for validation rules is a bit more straightforward. Have an interface for the providing the levels, feed it with default values in the language project and override it in the ui project there using values from the preference page. In the validator you simply invoke the error-, warning-, info-message generation based on the configured level.

Integrating your Xtext editor with eclipse help

Friday, April 27th, 2012

You have implemented a Xtext editor for a cool language and have even documented both via eclipse help, but unfortunately pressing F1 does not yield the desired effect? Here are some (hopefully) helpful code snippets.

First of all, it is important that you provided context ids for your help.

For a simple scenario, the necessary modifications in the UI project are quite small.
1) add org.eclipse.help to the plugin dependencies
2) bind your own implementation of XtextEditor (code snippets from below go there)

If you want a “static” help for your editor, i.e. the result of pressing F1 does not depend on the cursor position within the editor, overriding the createPartControl method is enough:

  @Override
  public void createPartControl(Composite parent) {
    //important to set context id before super call
    setHelpContextId(ITextEditorHelpContextIds.TEMPLATES_VIEW);
    super.createPartControl(parent);
  }

If you want more dynamic behaviour, e.g. the help should depend on the cursor position, explaining a language concept used there, one way to go is the IContextProvider… The following is a somewhat minimal example. Just as above, it uses existing help ids, so that you can play around without actually needing your own help. Normally, you would inspect the model element present at the given offset (available via text.getSelection().x):

  @Override
  public Object getAdapter(Class adapter) {
    if(adapter.equals(IContextProvider.class)){
      //this is just an example!
      //you should not instantiate a new provider every time
      return new IContextProvider() {
        @Override
        public String getSearchExpression(Object target) {
          //adapt the search expression according to the current position
          return "grammar mixin";
        }
        @Override
        public int getContextChangeMask() {
          //provide a new context whenever the selection changes
          return IContextProvider.SELECTION;
        }
        @Override
        public IContext getContext(Object target) {
          //retrieve one (or more for combining them) context
          //from the help system and return the result
          StyledText text = (StyledText)target;
          if(text.getSelection().x>20){
            return HelpSystem.getContext(
              ITextEditorHelpContextIds.TEXT_EDITOR_PREFERENCE_PAGE);
          }else{
            return HelpSystem.getContext(
              ITextEditorHelpContextIds.TEMPLATES_VIEW);
          }
        }
      };
    }
    return super.getAdapter(adapter);
  }

Some additional remarks:

PlatformUI.getWorkbench().getHelpSystem().setHelp(control, contextId) is nice for setting the help for a particular control. We used it for redirecting to our help in the DSL’s preference pages (bind extension of e.g. SyntaxColoringPreferencePage, there override setControl with super call + setHelp for the given control).

Assume that for a certain position in the editor several of your help contexts are relevant. In that case you could create a new “artificial” IContext with the accumulated related topics.

There are extensions of IContext that allow changing the title, categories etc.