X

Geertjan's Blog

  • November 9, 2006

Code Completion for Beginners

Geertjan Wielenga
Product Manager
Code completion is pretty cool. No, I'm not talking about using code completion, but about providing it. Here's a quick introduction to the Editor Code Completion API, at the end of which you'll have this super useful code completion box:

The only reason you'd be providing code completion is if you're creating an editor for some specific syntax that the IDE doesn't already support. However, though this scenario is limited, within the confines of this scenario, code completion is vital. Basically, without code completion, your editor isn't going to be very nice. (Wait and see what the next NetBeans editor will look like, in 6.0, with immense improvements in the editor area, including a sped up code completion infrastructure.)

Anyway, this is the simplest approach to providing code completion. We start by creating a module project, then we use a wizard to add files that will recognize our new file type (which has ".hello" as its extension), and then we add three Java files that implement the Code Completion API. One of the files is the factory for the other two. We register the factory in the XML layer. Here we provide an absolutely minimal implementation. We will only be able to make the code completion box appear. We will not be able to do anything with it at this stage.

  1. Create a new module called "HelloWorldFileType", using org.netbeans.modules.helloworldfiletype as the new package name. Click Finish.

  2. Use the File Type wizard to let the IDE recognize new files with the extension ".hello". Make the class name prefix "Hello" and select a 16x16 icon. Click Finish.

  3. Right-click the project and choose Properties. In the Libraries panel, click Add and scroll to "Editor Code Completion". Click OK.

  4. Right-click the main package and choose New | Java Class. Name the class HelloCompletionProvider and click Finish. Add implements CompletionProvider to the signature. A lightbulb appears when you put the cursor on the line containing the signature. Let the IDE add an import statement for org.netbeans.spi.editor.completion.CompletionProvider. Put the cursor on the line again. When the next lightbulb appears, let the IDE create skeleton methods for you.

  5. Two methods are created. Lets look at the second one first. It is called getAutoQueryTypes(JTextComponent jTextComponent, String string) and returns an int. This method determines whether the code completion box will open automatically and, if so, which type of completion query will be automatically performed by the code completion infrastructure. For example, while typing, should the user see the code completion box automatically? Or only when pressing the related keystrokes? For example, if Ctrl-Space is pressed, the standard code completion box appears, but if you return 1, then the standard code completion box will appear automatically. There are also other query types, such as a tooltip query type and a documentation query types, as well as two other ones (from 6.0 onwards) as shown in this NetBeans Javadoc. In this case, let's just return 0, so that nothing appears automatically.

    The first method, createTask(int i, JTextComponent jTextComponent), creates a CompletionTask. In the next step, we will register our CompletionProvider class in the XML layer, within a folder applicable to our data object's MIME type. Whenever the user types anything, the completion infrastructure asks all completion providers registered for the given MIME type to create tasks. The createTask(int i, JTextComponent jTextComponent) method is the method that creates the task. The task is invoked by the CompletionTask's query() method. So, let's provide for the creation of a simple task, using the AsyncCompletionTask, because we want to perform our code completion asynchronously:

    public CompletionTask createTask(int queryType, final JTextComponent component) {
    return new AsyncCompletionTask(new AsyncCompletionQuery() {
    protected void query(CompletionResultSet resultSet, Document doc, int caretOffset) {
    int startOffset = caretOffset-1;
    final Iterator it1 = morningKeywords.iterator();
    while(it1.hasNext()){
    final String entry = (String)it1.next();
    resultSet.addItem(new HelloCompletionItem(entry, startOffset, caretOffset));
    }
    final Iterator it2 = eveningKeywords.iterator();
    while(it2.hasNext()){
    final String entry = (String)it2.next();
    resultSet.addItem(new GoodbyeCompletionItem(entry, startOffset, caretOffset));
    }
    resultSet.finish();
    }
    });
    }

    And, at the bottom of the file, here are our two sets of keywords:

    private final static List morningKeywords = new ArrayList();
    static{
    morningKeywords.add("Good morning");
    morningKeywords.add("Want some breakfast?");
    morningKeywords.add("How are you?");
    morningKeywords.add("Did you sleep well?");
    morningKeywords.add("You need a shower!");
    }
    private final static List eveningKeywords = new ArrayList();
    static{
    eveningKeywords.add("Sleep well.");
    eveningKeywords.add("Good night!");
    eveningKeywords.add("Don't let the bed bugs bite.");
    eveningKeywords.add("Sweet dreams.");
    eveningKeywords.add("Don't snore so loud.");
    }

  6. Now create a file called "HelloCompletionItem". In the signature, specify that the class implements CompletionItem. Put the cursor on the line and when the lightbulb appears, let the IDE add the import statement for org.netbeans.spi.editor.completion.CompletionItem. Put the cursor on the line again and let the IDE create a whole bunch of skeleton methods. For details on all these skeleton methods, see CompletionItem in the Javadoc. Here, we won't go into any detail at all. We'll just provide the absolute bare minimum; just enough to let the module compile and successfully produce our code completion box.

    Change the signature and add variables as follows:

    private static Color fieldColor = Color.decode("0x0000B2");
    private static ImageIcon fieldIcon = null;
    private ImageIcon _icon;
    private int _type;
    private int _carretOffset;
    private int _dotOffset;
    private String _text;
    public HelloCompletionItem(String text, int dotOffset, int carretOffset) {
    _text = text;
    _dotOffset = dotOffset;
    _carretOffset = carretOffset;
    if(fieldIcon == null){
    fieldIcon = new ImageIcon(Utilities.loadImage("org/netbeans/modules/helloworldfiletype/icon1.png"));
    }
    _icon = fieldIcon;
    }

    Next, for createDocumentationTask(), createToolTipTask(), and getInsertPrefix(), return null. Next, for getSortPriority, return 0. Finally, for instantSubstitution(), return false.

    Only three methods need to be filled out. The first is render():

    public void render(Graphics g, Font defaultFont, Color defaultColor, Color backgroundColor, int width, int height, boolean selected) {
    CompletionUtilities.renderHtml(_icon, _text, null, g, defaultFont,
    (selected ? Color.white : fieldColor), width, height, selected);
    }

    Next, fill out getPreferredWidth() as follows:

    public int getPreferredWidth(Graphics graphics, Font font) {
    return CompletionUtilities.getPreferredWidth(_text, null, graphics, font);
    }

    Finally, change getSortText() as follows:

    public CharSequence getSortText() {
    return _text;
    }

  7. Repeat the above, creating a file called "GoodbyeCompletionItem". Make sure to change the icon. Importantly, return 1 from getSortPriority(). This will ensure that the keywords for our second completion item will appear after the first, because in the first case our priority is set to a lower number. For the rest, it is exactly the same as the above.

  8. Final step, register the CompletionProvider in the XML layer file:

    <folder name="Editors">
    <folder name="text">
    <folder name="x-hello">
    <folder name="CompletionProviders">
    <file name="org-netbeans-modules-helloworldfiletype-HelloCompletionProvider.instance"/>
    </folder>
    </folder>
    </folder>
    </folder>

  9. Install the module. Create a project. Add a file of the file type you created. Press Ctrl-Space and you will see your code completion box. You can't do anything with it yet. Here, all you've done is: you created two items, added keywords, set their priority, and rendered them.

It's maybe not all that much yet, but it is definitely a start on your journey into the land of code completion.

Join the discussion

Comments ( 4 )
  • Rich Unger Friday, November 10, 2006
    One thing I'd like to know about is providing code completion in text components other than editors. For example, does Matisse provide code completion in the text boxes you can get to in the properties window where you enter code snippets?
  • Geertjan Friday, November 10, 2006
    Rich, code completion providers are registered by MIME type in the XML layer (as a tutorial I am working on right now will demonstrate). Therefore, in a JEditorPane with a MIME type set to the MIME type in the XML layer, the code completion should be available. Haven't tried this, but am now incorporating it into the tutorial. However, without a MIME type, you have no code completion. Hope this answer helps.
  • Gustavo Sunday, November 12, 2006
    > does Matisse provide code completion in the text boxes

    Yes. The code related ones (pre-init, etc.) provide code completion. The debugger also provides code completion when you add a watch.
  • Bruno R Bertechini Wednesday, August 29, 2007

    Hi..Great Article!!!

    I have a question: I am using beanshell inside an app and I would like to have code completion in my custom editor. Can I implement this code completion in a JEditorPane and use it OUTSIDE netbeans? (in my custom app)? Can you please guide me on that?

    Many thanks

    Bruno


Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.