It is often desirable to not only have content assistance for the XML structure, but also for text content, when editing XML documents. To get the first one you have just to provide a XML Schema, for the latter you need to create an Eclipse Plugin. Which is fortunately not too complicated.

Let’s say we have a XML document which can contain a list of fruits. Wouldn’t it be nice to get a list of possible fruits when pressing CTRL-Shift? Just like on this screenshot:

completion_proposal_3

Here a step by step guide to implement such a completion proposal plugin for the Eclipse XML editor.

Step 1: Create a plugin project

Open a new Eclipse workspace, goto File -> New -> Project… and select Plug-in Development -> Plug-in Project. Enter an ID and a Name and make sure to check “This plug-in will make contributions to the UI”.

xml_contentproposal_1

Step 2: Add the Required Plug-ins

Double click the generated META-INF/MANIFEST.MF and switch to the Dependencies tab in the wizard. Or, alternatively, to the MANIFEST.MF tab if you want to enter the dependencies manually.

The required Plug-ins are:

  • org.eclipse.core.runtime
  • org.eclipse.wst.sse.ui
  • org.eclipse.wst.sse.core
  • org.eclipse.wst.xml.core
  • org.eclipse.jface.text

Step 3: Implement ICompletionProposalComputer

Create an class which implements ICompletionProposalComputer:

public class FruitsCompletionProposalComputer implements ICompletionProposalComputer {

  @Override
  public List<ICompletionProposal> computeCompletionProposals(CompletionProposalInvocationContext context, IProgressMonitor monitor) {
    //TODO
    return null;
  }

  @Override
  public List<ICompletionProposal> computeContextInformation(CompletionProposalInvocationContext context, IProgressMonitor monitor) {
    return Collections.emptyList();
  }

  @Override
  public String getErrorMessage() {
    return null;
  }

  @Override
  public void sessionEnded() {
  }

  @Override
  public void sessionStarted() {
  }
}

We are going to implement computeCompletionProposals() in Step 5.

Step 4: Define the extension

We need to define now our new proposal computer as extension to the extension point org.eclipse.wst.sse.ui.completionProposal. Return to the plugin wizard from Step 2, switch to tab Extensions and add a new one:
xml_contentproposal_2

Save it and open the newly created plugin.xml to enter the extension details, which is far easier than using the wizard. The extension should look like this (you might of course change the IDs and the name):

<extension
     id="at.nonblocking.xml.completionproposal.demo.fruits"
     point="org.eclipse.wst.sse.ui.completionProposal">         
     
	<proposalCategory 
           id="at.nonblocking.xml.completionproposal.demo.fruits.category"
           name="Fruits completion proposals">
	</proposalCategory>

	<proposalComputer
       activate="true"
       categoryId="at.nonblocking.xml.completionproposal.demo.fruits.category"
       class="at.nonblocking.xml.completionproposal.demo.FruitsCompletionProposalComputer"
       id="at.nonblocking.xml.completionproposal.demo.fruits.proposalcomputer">			
		<contentType id="org.eclipse.core.runtime.xml"/>			
	</proposalComputer>         
 </extension>

Very important is the correct contentType in line 15. And of course the extension point id.

Step 5: Implement the completion proposal computer

In the actual implementation of the proposal computer we need to to the following:

  1. Determine the currently edited XML tag. Abort if the tag is not <fruit>.
  2. Determine the current text node and the currently edited text.
  3. Calculate the absolute document offset of the current text node and the cursor offset within the text.
  4. Determine all fruit names that start with the string between text begin and cursor position.
  5. Create content proposals from the found fruit names.

An example implementation below:

private String[] FRUIT_NAMES = { 
  "Apple", "Apricot", 
  "Banana", "Breadfruit", "Blackberry", "Blackcurrant", "Blueberry", 
  "Currant", "Cherry", "Cloudberry", "Coconut", 
  "Date", "Dragonfruit", "Durian", 
  "Fig", 
  "Gooseberry", "Grape", "Grapefruit", "Guava" 
};

@Override
public List<ICompletionProposal> computeCompletionProposals(
  CompletionProposalInvocationContext context, IProgressMonitor monitor) {

  Node selectedNode = (Node) ContentAssistUtils
    .getNodeAt(context.getViewer(), context.getInvocationOffset());

  Element tag = null;
  if (selectedNode instanceof Element) {
    tag = (Element) selectedNode;
  } else if (selectedNode instanceof Text 
    && selectedNode.getParentNode() instanceof Element) {
    tag = (Element) selectedNode.getParentNode();
  }

  // Process only <fruit> tags
  if (tag == null || !"fruit".equals(tag.getLocalName())) {
    return Collections.EMPTY_LIST;
  }

  // Determine text node
  IDOMNode textNode = null;
  if (selectedNode instanceof Text) {
    textNode = (IDOMNode) selectedNode;
  } else {
    if (selectedNode.getChildNodes().getLength() == 1 
      && selectedNode.getChildNodes().item(0) instanceof Text) {
      // Cursor at the end of a text node
      textNode = (IDOMNode) selectedNode.getChildNodes().item(0);
    } else {
      // Cursor between two tags, no text node yet
    }
  }

  // Determine selected text and offsets
  String selectedText = null;
  int textNodeOffset = -1;
  int cursorOffsetWithinTextNode = -1;

  if (textNode != null) {
    selectedText = textNode.getStartStructuredDocumentRegion().getText();
    textNodeOffset = textNode.getStartOffset();
    cursorOffsetWithinTextNode = context.getInvocationOffset() - textNodeOffset;
  } else {
    selectedText = "";
    textNodeOffset = context.getInvocationOffset();
    cursorOffsetWithinTextNode = 0;
  }

  // Gather proposals
  List<ICompletionProposal> proposals = new ArrayList<ICompletionProposal>();
  String searchPrefix = selectedText.substring(0, cursorOffsetWithinTextNode);

  for (String searchResult : findFruits(searchPrefix)) {
    proposals.add(new CompletionProposal(
     searchResult, // replacement text
     textNodeOffset, 
     selectedText.length(), // replace the full text
     searchResult.length()));
  }

  return proposals;
}

private List<String> findFruits(String searchPrefix) {
  List<String> result = new ArrayList<String>();

  for (String fruitName : FRUIT_NAMES) {
    if (fruitName.startsWith(searchPrefix)) {
      result.add(fruitName);
    }
  }

  return result;
}

Step 6: Launch a new Eclipse instance

Right click on your project and select Run as -> Eclipse Application. Create a new sample project an a XML file. After creating a <fruit> tag you should now have content assistance for possible fruit names.

You should also be able to see your plugin in the XML Content Assist preferences (Window -> Preferences -> XML -> XML Files -> Editor -> Content Assist):
xml_contentproposal_3

Move your category up in the list in order to make your entries the first ones in the completion list.

You can find this example plugin on GitHub.

14 thoughts on “Extending the content assist capabilities of the Eclipse XML editor

    1. Does the plugin appear in the Eclipse preferences? If not, something is wrong with your extension point configuration. Otherwise it should work.

  1. yes, the plugin appears in
    Window > Preferences > XML > XML Files > Editor > Content Assist.
    There’s an option for “Fruits completion proposals” already active (checked) but my breakpoint / your list of fruits not appearing…

    could you let a link for whole project available in post?

    There’s a warning: “Discouraged access: The type ContentAssistUtils is not accessible due to restriction on required library org.eclipse.wst.sse.ui/org.eclipse.wst.sse.ui_1.3.2.v201201041522.jar”

    I’m using Eclipse 3.7 (Indigo) with Java 7.

    Thanks.

  2. How to have content assistance for xml structure? You have mentioned “To get the first one you have just to provide a XML Schema,” and how to do this. I developed an xml editor as a n eclipse plugin and now I want to provide content assistance feature for it via a xsd. How to do it?

  3. Really useful tutorial. Is there a way to modify the sample to add the content assist inside the tag to add attributes to it? for example:

    The content assist should provide the “name” attribute when I’m inside the tag, I’m trying to edit your sample with no success at the moment.

  4. Thank you for this tutorial/blog. Github project is working. I didnt need to change anything, it just worked.

    Do you have a tutorial also for extending content assist for java editor?

  5. Please let me know the tutorial for java editor. I tried by referring xml editor but got stuck on how to get the text where ctrl+space is invoked.i got offset but pls tell me how to get the text if i know the offset. Thanks in advance

  6. Pingback: techstanza
  7. I cannot find these dependencies –

    org.eclipse.wst.sse.ui
    org.eclipse.wst.sse.core
    org.eclipse.wst.xml.core

    Please guide me in finding them, I’m using Eclipse Neon for Eclipse committers and Java 1.8

Leave a Reply

Your email address will not be published. Required fields are marked *


*