How to Create a Caret Aware Code Completion Box

Casper Bang asked a great question at the end of my previous blog entry about the new code completion tutorial:

We have a desire in our team to get code completion for JPA queries. However, JPA is an embedded DSL inside string annotations. Does the approach described in the tutorial also apply if one wanted to "sub-tokenize" and provide completion inside a String token of a x-java MIME type?

The answer is, of course, "Yes!" To set up a scenario, I recreated my code completion plugin so that it now ONLY works for Java parameters. I.e., as you can see in the screenshot below (more or less), whenever I invoke code completion and ONLY IF the cursor is on a parameter (or in the place where a parameter SHOULD BE defined), will my custom items be displayed in the code completion box:

The three high level clues to understanding how to do this are provided by the following:

Especially the last of the above is extremely relevant (it is from Sandip's great blog), it taught me everything I now know in this case, but the other two should be read first (especially the first, don't worry too much about the second, just be aware that it is there and can be useful when actually implementing a scenario).

In short, starting from the sample that the code completion tutorial provides, you need to do the following:

  1. Change the layer.xml file so that the code completion plugin will apply to Java files instead of HTML files. Do this by simply replacing "text/html" with "text/x-java" in the folder hierarchy. Now, anywhere in a Java file, you will see your code completion entries. That's not what you want, you only want to see it in very specific cases.

  2. So, as discussed in Sandip's blog entry, you need to create a class that extends CaretAwareJavaSourceTaskFactory:

    import org.netbeans.api.java.source.CancellableTask;
    import org.netbeans.api.java.source.CompilationInfo;
    import org.netbeans.api.java.source.JavaSource.Phase;
    import org.netbeans.api.java.source.JavaSource.Priority;
    import org.netbeans.api.java.source.support.CaretAwareJavaSourceTaskFactory;
    import org.openide.filesystems.FileObject;
    
    public class CountriesCompletionJavaSourceTaskFactory extends CaretAwareJavaSourceTaskFactory {
        
        public CountriesCompletionJavaSourceTaskFactory() {
            super(Phase.ELEMENTS_RESOLVED, Priority.LOW);
        }
    
        public CancellableTask<CompilationInfo> createTask(FileObject fileObject) {
            return new CountriesCompletionTask(this, fileObject);
        }
        
    }

  3. Next, register the above class in META-INF/Services, for the "org.netbeans.api.java.source.JavaSourceTaskFactory" interface.

  4. Then create the task that you see referred to above, i.e., "CountriesCompletionTask", which exists for no other reason than to toggle a boolean whenever the required element is found under the cursor:

    import com.sun.source.util.TreePath;
    import javax.lang.model.element.Element;
    import javax.lang.model.element.ElementKind;
    import org.netbeans.api.java.source.CancellableTask;
    import org.netbeans.api.java.source.CompilationInfo;
    import org.openide.awt.StatusDisplayer;
    import org.openide.filesystems.FileObject;
    
    public class CountriesCompletionTask implements CancellableTask<CompilationInfo> {
    
        private CountriesCompletionJavaSourceTaskFactory 
                countriesCompletionJavaSourceTaskFactory;
        private FileObject fileObject;
        private boolean canceled;
    
        //Toggle whether code completion should be shown:
        private static boolean showCC = false;
        
        public static boolean isShowCC() {
            return showCC;
        }
        
        public static void setShowCC(boolean showCC) {
            CountriesCompletionTask.showCC = showCC;
        }
        
        //Constructor:
        CountriesCompletionTask(CountriesCompletionJavaSourceTaskFactory
                countriesCompletionJavaSourceTaskFactory, FileObject fileObject) {
            this.countriesCompletionJavaSourceTaskFactory = 
                    countriesCompletionJavaSourceTaskFactory;
            this.fileObject = fileObject;
            
        }
    
        public void run(CompilationInfo compilationInfo) {
    
            //Find the TreePath for the caret position:
            @SuppressWarnings("static-access")
            TreePath tp =
                    compilationInfo.getTreeUtilities().pathFor(
                    countriesCompletionJavaSourceTaskFactory.getLastPosition(fileObject));
    
            //If cancelled, return:
            if (isCancelled()) {
                return;
            }
    
            //Get Element:
            Element element = compilationInfo.getTrees().getElement(tp);
    
            //If cancelled, return:
            if (isCancelled()) {
                return;
            }
    
            if (element != null) {
                //If the element is a parameter...
                if (element.getKind() == ElementKind.PARAMETER) {
                    //...toggle the boolean to show the code completion:
                    showCC = true;
                    StatusDisplayer.getDefault().setStatusText("" +
                            "You can invoke code completion for \\'" 
                            + element.getSimpleName()+"\\'");
                }
            }
        }
    
        public final synchronized void cancel() {
            canceled = true;
        }
    
        protected final synchronized boolean isCancelled() {
            return canceled;
        }
    
    }

    So, Casper, you'd need to identify the element in the code that you want to deal with. I.e., instead of a parameter, you'd look for a certain kind of string for your JPA queries. Here's something I've been playing with, although it doesn't get into the strings:

    Refer to the documents referenced earlier for details.

  5. Finally, in the CompletionProvider class, which is discussed at length in the code completion tutorial, check whether the boolean has been switched or not and, if so, show the code completion box. Once shown, set the boolean back to false so that it is back in its default state. All that's done in the lines in bold below:

    import java.util.Locale;
    import javax.swing.text.Document;
    import javax.swing.text.JTextComponent;
    import org.netbeans.spi.editor.completion.CompletionProvider;
    import org.netbeans.spi.editor.completion.CompletionResultSet;
    import org.netbeans.spi.editor.completion.CompletionTask;
    import org.netbeans.spi.editor.completion.support.AsyncCompletionQuery;
    import org.netbeans.spi.editor.completion.support.AsyncCompletionTask;
    
    public class CountriesCompletionProvider implements CompletionProvider {
    
        public CompletionTask createTask(int i, final JTextComponent jTextComponent) {
    
            
            if (i != CompletionProvider.COMPLETION_QUERY_TYPE) {
                return null;
            }
    
            return new AsyncCompletionTask(new AsyncCompletionQuery() {
    
                @Override
                protected void query(CompletionResultSet completionResultSet, final Document document, int caretOffset) {
    
                    //Iterate through the available locales
                    //and assign each country display name
                    //to a CompletionResultSet:
                    Locale[] locales = Locale.getAvailableLocales();
                    for (int i = 0; i < locales.length; i++) {
                        final Locale locale = locales[i];
                        final String country = locale.getDisplayCountry();
                        if (!country.equals("") && CountriesCompletionTask.isShowCC()) {
                            completionResultSet.addItem(new CountriesCompletionItem(country, caretOffset));
                        }
                    }
                    completionResultSet.finish();
                    CountriesCompletionTask.setShowCC(false);
                }
            }, jTextComponent);
    
        }
    
        public int getAutoQueryTypes(JTextComponent arg0, String arg1) {
            return 0;
        }
    
    }

And that's all. Now you can have a code completion box for very specific elements within your Java code.

Comments:

Extremely cool, thanks for not only answering my question but making it hard for me not to make word of my proposal.

Following the steps you outline works well, a note to other thought that since the NetBeans API's have been generified in 6.0, you will want to implement CancellableTask<CompilationInfo> rather than CancellableTask.

Posted by Casper Bang on August 10, 2008 at 08:36 AM PDT #

Thanks, fixed that. Simply wasn't rendered properly in the blog entry, but was there in the code. Looking forward to hearing what results from this with your JPA queries!

Posted by Geertjan on August 10, 2008 at 05:28 PM PDT #

I have been trying to understand the difference between this approach and the seemingly similar one laid out in the RCP book (Manifest example) where it, among other things, is not necessary to register a TaskFactory SPI.

Correct to assume that the CodeCompletion example in the RCP book is strictly lexer/token based and very limited, while this one is AST/element based with maximum potential?

Posted by Casper Bang on August 11, 2008 at 04:38 AM PDT #

Reason is simple: Manifest files are not Java files.

Posted by Geertjan on August 11, 2008 at 05:10 AM PDT #

"Correct to assume that the CodeCompletion example in the RCP book is strictly lexer/token based and very limited, while this one is AST/element based with maximum potential?"

That sums it up very well.

Posted by Geertjan on August 11, 2008 at 05:50 AM PDT #

In my blog I've a info about semantic code modification with code completion:

http://www.sepix.de/blogs/blogrittner/blog/archive/2008/november/27/beandev_code_completion_global_im_java_quelltext/index.html

The code completion works only with swing components, but for global (syntax aware) modifications we need the Java Infrastructure. My blog entry shows how to glue "Code Completion" with the "Java Infrastructure".

best regards,
josh.

Posted by Aljoscha Rittner on November 26, 2008 at 09:20 PM PST #

Dear sir,
i am creating my own java ide and i done 50% of work like syntax recognition and code grouping and noy for java but several other laguages also now i want to create netbeans like intelligence window in java swings please guide me how can i do it?

Thanks in advance

Posted by Neeraj Sharma on August 15, 2010 at 03:28 AM PDT #

Post a Comment:
  • HTML Syntax: NOT allowed
About

Geertjan Wielenga (@geertjanw) is a Principal Product Manager in the Oracle Developer Tools group living & working in Amsterdam. He is a Java technology enthusiast, evangelist, trainer, speaker, and writer. He blogs here daily.

The focus of this blog is mostly on NetBeans (a development tool primarily for Java programmers), with an occasional reference to NetBeans, and sometimes diverging to topics relating to NetBeans. And then there are days when NetBeans is mentioned, just for a change.

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
12
13
14
25
26
27
28
29
30
   
       
Today