Thursday, May 15, 2014

Extending JSDT: adding your own content assist

Extending the JSDT content assist is a  topic asked several times on stackoverflow(20738788, 20779899). As I needed it for the Eclipse EASE project, I decided to come up with a short tutorial.

Source code for this tutorial is available on googlecode as a single zip archive, as a Team Project Set or you can checkout the SVN projects directly. 

Step 1: Preparations

As we will have dependencies to JSDT you either need to check out the source code from gerrit or add JSDT to your target platform.

Step 2: Defining the extension point

Create a new Plug-in Project com.codeandme.jsdt.contentassist and add following dependencies:
  • org.eclipse.wst.jsdt.ui
  • org.eclipse.core.runtime
  • org.eclipse.jface.text
Afterwards create a new Extension for org.eclipse.wst.jsdt.ui.javaCompletionProposalComputer. Provide an ID and Name. The ID will be needed as reference to a category, the name will be displayed in Preferences/JavaScript/Editor/Content Assist/Advanced. Create a proposalCategory node and provide a nice icon for your category.


Create a second extension of the same type for our implementation. Again provide an ID. Create a javaCompletionProposalComputer subnode with a class and activate set to true. The categoryID consists of your plugin name followed by the category ID we provided earlier.


If you use a target definition containing JSDT plugins you might not be able to use the Plug-in Manifest Editor for creating these extensions. In that case you have to edit the xml code directly:

<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.4"?>
<plugin>
   <extension point="org.eclipse.wst.jsdt.ui.javaCompletionProposalComputer"
   id="codeandme_category"
   name="Code and me proposals">
   <proposalCategory/>
 </extension>

 <extension
       id="codeandme_proposal"
       point="org.eclipse.wst.jsdt.ui.javaCompletionProposalComputer">
   <javaCompletionProposalComputer
      class="com.codeandme.jsdt.contentassist.CustomCompletionProposalComputer"
      categoryId="com.codeandme.jsdt.contentassist.codeandme_category"
      activate="true">
   </javaCompletionProposalComputer>
 </extension>

</plugin>
Step 3: Proposal computer implementation

Now for the easy part:create a new class CustomCompletionProposalComputer with following content:

package com.codeandme.jsdt.contentassist;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.text.contentassist.CompletionProposal;
import org.eclipse.wst.jsdt.ui.text.java.ContentAssistInvocationContext;
import org.eclipse.wst.jsdt.ui.text.java.IJavaCompletionProposalComputer;

public class CustomCompletionProposalComputer implements IJavaCompletionProposalComputer {

 @Override
 public void sessionStarted() {
 }

 @Override
 public List computeCompletionProposals(ContentAssistInvocationContext context, IProgressMonitor monitor) {

  ArrayList<CompletionProposal> proposals = new ArrayList<CompletionProposal>();

  proposals.add(new CompletionProposal("codeandme.blogspot.com", context.getInvocationOffset(), 0, "codeandme.blogspot.com".length()));
  proposals.add(new CompletionProposal("<your proposal here>", context.getInvocationOffset(), 0, "<your proposal here>".length()));

  return proposals;
 }

 @Override
 public List computeContextInformation(ContentAssistInvocationContext context, IProgressMonitor monitor) {
  return null;
 }

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

 @Override
 public void sessionEnded() {
 }
}



CompletionProposals could contain more information like attached documentation or ContextInformation for variables and other dynamic content. See this post for some information on the Contextinformation.

Optional: Helper methods

Typically you would need to evaluate the last characters before the cursor position to filter your proposals. You also might be interested in the content of the current line left of the cursor position. Maybe these helper methods are of some interest:
 private static final Pattern LINE_DATA_PATTERN = Pattern.compile(".*?([^\\p{Alnum}]?)(\\p{Alnum}*)$");

 /**
  * Extract context relevant information from current line. The returned matcher locates the last alphanumeric word in the line and an optional non
  * alphanumeric character right before that word. result.group(1) contains the last non-alphanumeric token (eg a dot, brackets, arithmetic operators, ...),
  * result.group(2) contains the alphanumeric text. This text can be used to filter content assist proposals.
  * 
  * @param context
  *            content assist context
  * @return matcher containing content assist information
  * @throws BadLocationException
  */
 protected Matcher matchLastToken(final ContentAssistInvocationContext context) throws BadLocationException {
  String data = getCurrentLine(context);
  return LINE_DATA_PATTERN.matcher(data);
 }

 /**
  * Extract text from current line up to the cursor position
  * 
  * @param context
  *            content assist context
  * @return current line data
  * @throws BadLocationException
  */
 protected String getCurrentLine(final ContentAssistInvocationContext context) throws BadLocationException {
  IDocument document = context.getDocument();
  int lineNumber = document.getLineOfOffset(context.getInvocationOffset());
  IRegion lineInformation = document.getLineInformation(lineNumber);

  return document.get(lineInformation.getOffset(), context.getInvocationOffset() - lineInformation.getOffset());
 }

9 comments:

  1. Surely one of the few good resources regarding content assist available on internet.Can you tell me how to add content assist (or even syntax highlighting) in the CDATA part of an xml file. I have script tags written in the CDATA section of my xml file.

    Thanks

    ReplyDelete
    Replies
    1. Sorry, I never looked at the xml editor. I guess the implementation will be similar, but you have to look for the extension points and Interface names on your own. Best grab the source of the ui bundles for the xml editor (I guess org.eclipse.wst.xml.ui ?) and check the plugin.xml.

      Delete
  2. Do you have any idea on how to "inject" custom content into an existing code outline?

    I got the ContentAssistant working just fine for my framework, but I'm looking for a way to display a class hierarchy in the JavaScript outline view. I've been searching for this for 3 days straight now, but still no success.

    Any help is greatly appriciated!

    ReplyDelete
    Replies
    1. I was looking for such a feature too. But as far as I can tell there seems to be no way to do that without changing JSDT sources, which is why I abandoned the idea for now. The outline content is created by the JSDT editor when asked for an IContentOutlinePage adapter. I could not find any extension points allowing to extend that model.

      Delete
  3. Nice tutorial. I followed the same steps but for the java editor using Eclipse Luna (org.eclipse.jdt.ui.javaCompletionProposalComputer). But I could not find the proposal category on the contentAssist->Advanced page. The plugin is also not loaded. I already set the activate value to true. Can you suggest me any solution?

    ReplyDelete
    Replies
    1. Sorry, did that only for JS. Best ask for support on the forums or the JDT mailing lists

      Delete
  4. How to debug CustomCompletionProposalComputer class ?

    ReplyDelete
    Replies
    1. best set a breakpoint somewhere in computeCompletionProposals and trigger a code completion in your debug IDE

      Delete