Introduction
Documentation is important. Okay, let’s be slightly more precise – good documentation is important. Take as an example the IntelliJ plugin documentation – at the time of writing, it’s hopefully out of date.
Trying to find any useful sources elsewhere on the ‘net isn’t any easier – you’re urged to “check out the community source code.” Totally as easy as it sounds.
But, you know, it’s not always easy writing documentation. Especially when you have corporate commenting standards that are somewhat ruthless – ever spent a day converting “getTheThing” into
1 2 3 4 |
/** * Get the thing * @return the thing **/ |
Well, I for one, was over that. And I wanted to learn how IntelliJ plugins work. And I couldn’t read up on it, which leads me back to the beginningĀ of this article.
Which leads us neatly onto the point of this article – let’s see how to create a basic IntelliJ plugin that will look at the currently opened source file, find methods that don’t have JavaDocs yet and document them. Of course documentation like this is next to useless, but sometimes documentation just isn’t appropriate. Accordingly, let’s called the plugin noDoc.
If, somehow or other, you’re just here after the source code, or the compiled JAR, it can be found here.
Setup
Setting up a project like this is fairly easy – create a new plugin project. The only thing you’ll need to do is setup an IntelliJ SDK – the official guidesĀ do have this bit documented.
The example project has a space action already configured – in my case, I modified itĀ to give us a “noDoc” menu item and a “Add no documentation” option. For anyone unfamiliar with the _Ā syntax in the name, that indicates button keyboard shortcut can be used when navigating menus using the Alt key.
1 2 3 4 5 6 |
<actions> <group id="NoDoc.MainMenu" text="_noDoc" description="No doc"> <add-to-group group-id="MainMenu" anchor="last"/> <action id="NoDoc.AddNoDoc" class="za.co.knonchalant.plugins.nodoc.TextBoxes" text="Add _no documentation" description="Add no documentation"/> </group> </actions> |
Getting to it
Working with IntelliJ plugins is fairly easy – far more straightforward than Eclipse plugins, or at least as far as my experience with it has gone. Most everything you’ll want to modify will exist as an object with PsiĀ prefixed to it – PsiFileĀ , PsiClassĀ , PsiMethodĀ .
This makes searching for more information easier as well – much easier than Google-ing “how do i make the method pls”, which hopefully would bring up articles pitched at a different level of developer (but if not, good on you. Don’t forget to click the green tick to accept answers!).
Actions in menus should extend the OnActionĀ class – this provides one method to implement, onActionĀ , which supplies an event object. From this event object we can extract information about the current setup. To start with, let’s check that when the event ran the user was editing a Java file. All JavaDoc is provided courtesy of the final product, before any hate rains down on me.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
/** * Action performed * * @param event the event */ @Override public void actionPerformed(AnActionEvent event) { Project project = event.getRequiredData(CommonDataKeys.PROJECT); PsiFile file = event.getData(LangDataKeys.PSI_FILE); if (file instanceof PsiJavaFile) { processFile(project, (PsiJavaFile) file); } } |
Here we ask the event for the project and the file. We check that the file is an instance of PsiJavaFileĀ , and if so hand it and our project down to the processFileĀ method.
One thing to remember when modifying the file is that IntelliJ requires that you be in a “write” mode – this is so that it can support undo-ing the actions. In our case, we can’t just modify the methods as we find them because this would force the user to undo every method one-by-one – we need to write them as one large write.
Reading
First we loop through the classes in the current file (which we handed down), then we loop through each method in the class. We check to see that the method does not already have JavaDoc, and we add it to our list if so. Finally, we perform all of our writes.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
/** * Process file * @param project the project * @param file the file **/ private void processFile(Project project, PsiJavaFile file) { List<List<CommentItem>> items = new ArrayList<List<CommentItem>>(); for (PsiClass clazz : file.getClasses()) { List<CommentItem> classItems = new ArrayList<CommentItem>(); items.add(classItems); for (PsiMethod method : clazz.getMethods()) { PsiDocComment comment = method.getDocComment(); if (comment == null) { classItems.add(new CommentItem(clazz, method, ProduceComment.produceCommentFor(method))); } } } insertAllCommentsIntoProject(project, items); } |
The details of how the comments StringĀ s themselves are produced are fairly length and uninteresting, this being a guide on creating the actual plugin, but the source is available on the linked GitHub.
For each CommentItem, we store its class, the method and the produced comment. We need to keep track of the class and method as we’ll be using them to insert in a moment.
Writing
Once we’ve gotten a list of all the methods, their locations and their new, shiny JavaDocs, we must insert them into the code. This is done by creating an anonymous RunnableĀ , and having it executed under a WriteCommandActionĀ . An ElementFactoryĀ handles the creation of the appropriate PsiCommentĀ from our String.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
/** * Insert all comments into project * * @param project the project * @param comments the comments */ private void insertAllCommentsIntoProject(final Project project, final List<List<CommentItem>> comments) { final PsiElementFactory factory = JavaPsiFacade.getInstance(project).getElementFactory(); Runnable runnable = new Runnable() { public void run() { PsiClass clazz = null; for (List<CommentItem> list : comments) { for (CommentItem commentItem : list) { PsiComment commentFromText = factory.createCommentFromText(commentItem.getComment(), commentItem.getMethod()); clazz = commentItem.getClazz(); commentItem.getClazz().addBefore(commentFromText, commentItem.getMethod()); } } } if (clazz != null) { CodeStyleManager.getInstance(project).reformat(clazz); } }; WriteCommandAction.runWriteCommandAction(project, runnable); } |
For each comment, we create a PsiComment, then we grab our reference to the class that it’s contained in and tell it to insert the new comment directly before the method we’re commenting. Finally, we reformat the class, just to keep it nice and neat.
Easy enough!
Running
You can have IntelliJ launch a test run, which also lets you Debug, or if you’re supremely confident yo can just click Build and have it generate the plugin JAR that can be installed in your IntelliJ via the Plugins menu.
Let me know if you have any questions, or (as is fairly likely) know more about IntelliJ plugins than I do and can point out where I’ve made any errors (no matter how major.minor).
The full source (and compiled JAR) is available on GitHub.
Leave a Reply