Change the outline, part 2

More children!

Now that we have retrieved our missing child, let’s change the structure of the outline a bit more, adding the TypeDeclaration to the Room node and the Room cross-references of Share, if it has one.
Again, Xtext provide us a convenience class for customization, the Transformer (MyDslTransformer) class. Just write down one method that returns the children of a Room element and one that returns the children of Share:

MyDslTransformer.java

...
public List<EObject> getChildren(Room room)
   {
   //Create a List with only one element: Room node will have only one child.
   List<EObject> res = new Vector<EObject>(1, 1);
   if(room.getType()!=null)
      res.add(room.getType());
   else
      return NO_CHILDREN;
   return res;
   }

public List<EObject> getChildren(Share share)
   {
   //Add the rooms where the furniture come from, eventually.
   if(share.getList()!=null)
      {
      List<EObject> res = new Vector<EObject>(1, 1);
      //Put the two rooms.
      if(share.getList().getSecond()!=null)
         res.add(share.getList().getSecond());
      if(share.getList().getFirst()!=null)
         res.add(share.getList().getFirst());
      if(res.size()>0)
         return res;
      else
         return NO_CHILDREN;
      }
   return NO_CHILDREN;
   }
...

Compile, run and you should see the new nodes appearing in the outline:


What if we want to see less nodes? How we can tell the framework to hide some of them? Imagine, for instance, that we don’t want to see "Kitchen" node in a Share element but we still want to see it for a Room element.
First of all, we have to see how Xtext actually skip a node:

  • The topmost parent class of MyDslTransformer is AbstractSemanticModelTransformer.
  • In there we can see that the method transformSemanticNode create a new node only if consumeSemanticNode returns true; otherwise it simply return the parentNode.

So we have two ways to hide a node in the outline:

  1. Override consumeSemanticNode, returning false accordingly.
  2. Write the right createNode method, in the Transform class, returning the parent node.

We’ll do the latter because it is a bit tricky to tell if the TypeDeclaration element comes from a cross reference in Share or from the declaration of Room, in the mehod consumeSemanticNode:

MyDslTransformer.java

public ContentOutlineNode createNode(TypeDeclaration semanticNode,
                                                             ContentOutlineNode parentNode)
   {
   //Used to see where we are.
   ContentOutlineNode grandParent = parentNode.getParent();
   if(grandParent!=null)
      {
      //If the EClass of the grand parent is named "Share", then we must hide the node.
      if(grandParent.getClazz()!=null &&
         grandParent.getClazz().getName().compareTo("Share")==0)
         return parentNode;
      }
   //Otherwise, standard behaviour.
   ContentOutlineNode outNode = super.createNode(semanticNode, parentNode);
   return outNode;
   }

Now you should see something like this:

Targeting problems

If you try to click one of the new nodes, you should see that they point to wrong sections of the code:

I think that the problem here is that in the MyDslTransformer‘s context, all the cross references are already resolved and we manipulate the pointed elements. To correct this we can either try to generate some custom TextLocation through a special implementation of ILocationInFileProvider (going nearer to the creation of virtual nodes), or we can change a little bit the model so that it has some nodes that specifically hold the cross references: at that point the default behaviour of Xtext should give us everything, as it should use that new model’s nodes to calculate the regions.
I will take the latter way because I think that when the outline is required to show some nodes that are not present in the model, then probably the model has to be changed to hold the new nodes. Personally, I think that the creation of outline’s virtual nodes must be done rarely. In other words, in my opinion the outline customization must be used only to hide or to redraw, not to inject.
(Still want to create virtual nodes? Jump to the appendix!)

So update MyDsl.xtext as follows:

//Use the default terminals.
grammar org.xtext.example.mydsl.MyDsl with org.eclipse.xtext.common.Terminals

//Generate the model.
generate myDsl "http://www.xtext.org/example/mydsl/MyDsl"

//A house will be considered as a particular kind of structure.
Structure:
   //First, we must declare what type of structure it is.
   type=TypeDeclaration
   //A structure can be composed of various rooms.
   (rooms+=Room)*
   //A structure has various furnitures.
   (furns+=FurnitureDeclaration)*
   //A structure can have furnitures of other rooms.
   (shares+=Share)*
   ;

TypeDeclaration:
   'TYPE' name=STRING
   ;

//A room has a name and is a structure of a particular type.
Room:
   'ROOM' name=STRING 'OF' 'TYPE' type=TypeReference
   ;

FurnitureDeclaration:
   'FURNITURE' name=STRING
   ;

//We let the furnitures to be shared among rooms/structures.
Share:
   'SHARE' name=[FurnitureDeclaration|STRING] (list=List)?
   ;

//This is used to define where the shared furniture come from.
//Share level is limited to two.
//{} <- [II] <- [I]
List:
   ('<-' second=RoomReference)? '<-' first=RoomReference
   ;
   
TypeReference:
   ref=[TypeDeclaration|STRING]
   ;
   
RoomReference:
   ref=[Room|STRING]
   ;

TypeReference and RoomReference are the novelty here. As a further clarification, here are the two .genmodel files, the old one at the right:

Finally delete getChildren(Room room) in MyDslTransformer and add two methods in MyDslLabelProvider:

MyDslLabelProvider.java

...
String text(TypeReference typeRef)
   {
   return typeRef.getRef().getName();
   }
   
String text(RoomReference roomRef)
   {
   return roomRef.getRef().getName();
   }
...

Now the outline should be correct, with the right nodes that point at the right sections of the code!

Advertisements

About Luong
http://www.linkedin.com/in/manhluong

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s