My proposals, my order

Xtext provides us with a very useful content assist out of the box:





However, what if we want to change the order of the proposed elements?
In Xtext the ordering mechanism of these elements is broken in three parts: the assignment of a number that represents the priority of one element (implementation of IContentProposalPriorities), the availment of those numbers/priorities to achieve a sort logic (implementation of ICompletionProposalComparator) and the storing of the priority number in a ConfigurableCompletionProposal, one for each proposal; this class also deals with the default comparison logic, which compares the two priorities first, then the two display string if no outcome is achieved from the two numbers.

Also, can we change the order of the proposals, according to the state of the model? In other words, what we have to do to assign the priority based upon the model?
In fact, it seems that the sort order is not aware of “where” (related to the model) we are:



To do this we have to change some things, because I couldn’t find any direct reference to the model (or to the IDocument) in the aforementioned three classes.
Digging in the Xtext code, I found two ways:

  1. Create a ConfigurableCompletionProposal with a ContentAssistContext, setted in the AbstractContentProposalProvider.doCreateProposal. Then our IContentProposalPriorities will extract the context each time from the ConfigurableCompletionProposal.
  2. Create a ContentProposalPriorities that take a context holder as a parameter, each time it adjust the priorities.

I think the second way is cleaner but it’s also longer: we have to override each method that calls the IContentProposalPriorities (in other words when AbstractContentProposalProvider.getPriorityHelper() is called) and then call the new version of the adjust method.

First of all, create our new implementation of ConfigurableCompletionProposal, with a pointer to a ContentAssistContext:

MyDslConfigurableCompletionProposal.java

public class MyDslConfigurableCompletionProposal extends ConfigurableCompletionProposal
   {
   /**
    * It is our access to the IDocument, to the model.
    */
   protected ContentAssistContext context;

   public MyDslConfigurableCompletionProposal(String replacementString,
                                              int replacementOffset,
                                              int replacementLength,
                                              int cursorPosition)
      {
      super(replacementString, replacementOffset, replacementLength, cursorPosition);
	  }
   
   public MyDslConfigurableCompletionProposal(String replacementString,
		                                      int replacementOffset,
		                                      int replacementLength,
		                                      int cursorPosition,
		                                      Image image,
		                                      StyledString displayString,
		                                      IContextInformation contextInformation,
		                                      String additionalProposalInfo)
      {
	  super(replacementString, replacementOffset, replacementLength, cursorPosition, image, displayString, contextInformation, additionalProposalInfo);
      }
   
   public void setContext(ContentAssistContext context)
      {
	  this.context = context;
      }
   
   public ContentAssistContext getContext()
      {
	  return this.context;
      }
   }

Now in our ProposalProvider (MyDslProposalProvider) we have to build the new version of the ConfigurableCompletionProposal, setting the context accordingly. Searching a bit, we would see that a ConfigurableCompletionProposal is created in only one place: AbstractMyDslProposalProvider.doCreateProposal, so we have to override that method:

MyDslProposalProvider.java

...
@Override
   protected ConfigurableCompletionProposal doCreateProposal(String proposal,
		                                                     StyledString displayString,
		                                                     Image image,
			                                                 int priority,
			                                                 ContentAssistContext context)
      {
      ConfigurableCompletionProposal result = super.doCreateProposal(proposal, displayString, image, priority, context);
      //We've got our implementation here...
      if(result instanceof MyDslConfigurableCompletionProposal)
         //Set the context. We will use it during the priority adjustment.
         ((MyDslConfigurableCompletionProposal)result).setContext(context);
      return result;
      }

    @Override
	protected MyDslConfigurableCompletionProposal doCreateProposal(
                                                          String proposal,
			                                  StyledString displayString,
			                                  Image image,
			                                  int replacementOffset,
			                                  int replacementLength)
      {
      //Build the new version.
      return new MyDslConfigurableCompletionProposal(proposal,
    		                                         replacementOffset,
    		                                         replacementLength, 
				                                     proposal.length(),
				                                     image,
				                                     displayString,
				                                     null,
				                                     null);
      }
...

Finally we can use the new “injected” context, in our own extension of ContentProposalPriorities. The context holds an IDocument that we can use to extract the model. Always remember to use an IUnitOfWork to handle the model:

MyDslContentProposalPriorities.java

public class MyDslContentProposalPriorities extends ContentProposalPriorities
   {
   protected void adjustPriority(ICompletionProposal proposal, String prefix, int priority)
      {
	  //First call the default implementation.
      super.adjustPriority(proposal, prefix, priority);
      //We have our implementation.
      if (proposal instanceof MyDslConfigurableCompletionProposal)
         {
         final MyDslConfigurableCompletionProposal myConfigurable = (MyDslConfigurableCompletionProposal) proposal;
         //Get the context we previously setted. 
         final ContentAssistContext myContext = myConfigurable.getContext();
         //Retrieve the IDocument.
         IXtextDocument doc = (IXtextDocument)myContext.getDocument();
         int newPriority = doc.readOnly(
            new IUnitOfWork<Integer, XtextResource>()
               {
               @Override
               public Integer exec(XtextResource state) throws Exception
                  {
            	  //The context itself holds various info of the model.
                  AbstractNode lastNode = myContext.getLastCompleteNode();
                  EObject lastObject = NodeUtil.findASTElement(lastNode);
                  //As an example, increase the position of '<-' according to what we have already typed.
                  if(lastObject.eContainingFeature().getName().compareTo("first")==0 &&
                     myConfigurable.getDisplayString().compareTo("<-")==0)
                     {
                	 return new Integer(myConfigurable.getPriority()*proposalWithPrefixMultiplier);
                     }
                  return new Integer(myConfigurable.getPriority());
                  }
               });
         //Set the new priority.
         myConfigurable.setPriority(newPriority);
         }
      }
   }

Oh and don’t forget to bind the new ContentProposalPriorities!

MyDslUiModule.java

...
 public Class<? extends org.eclipse.xtext.ui.editor.contentassist.IContentProposalPriorities> bindIContentProposalPriorities()
      {
      return org.xtext.example.mydsl.ui.contentassist.MyDslContentProposalPriorities.class;
      }
...

That’s it! Now, the order changes according to what we have typed:

Advertisements

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

One Response to My proposals, my order

  1. Pingback: Confluence: Xtext

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