Thursday, December 9, 2010

MigLayout Verification plug-in for IntelliJ - what it was like to write it

I am happy to report I have written my first plug-in for IntelliJ. It gets around the issues I reported in a previous post about MigLayout using strings for constraints and those strings are validated at run time. This puts in you the fun situation of writing code, running it, getting an error, fixing it and repeating until you have the constraint syntax correct.

That is no longer an issue if you use the MigLayout Verification plug-in for IntelliJ. I have submitted it to Jet Brains but have not heard back from them as of yet. You can grab it and manually install it from the MigLayout form if you want to give it a shot. Download from MigLayout forum. This will be moved to a permanent spot on his main page soon and hopefully available from JetBrains too.

If you look at the forum you can see the author of MigLayout is an IntelliJ user and is using the plug-in himself. I needed to contact him as I am using MigLayout code in my plug-in and I wanted to make sure that was OK with him per his license agreement. He was very kind and allowed me to use the code meaning the parse logic and errors you get are the exact same as the ones you would get at run time.

What does the plug-in do for you? It allows you to highlight the lines of text in your Java code that use MigLayout, press Alt+Shift+Y and it will find every constraint error in the code showing them to you in a console view. You can click on each error to have it pop to that line of code highlighting the string in error so you can fix it. Notice it is every error so you can quickly clean them all up. It handles layout, row, column and component constraints.

On a side note I am happy I did not try to name this blog based on a language. A lot of blogs are C# Corner or Java Jabbering etc. Since I end up all over the place - Android, iPhone, Java, C#, plug-ins, etc. that would have been pretty misleading.

What was it like to develop an IntelliJ plug-in?

Not easy which is a shame. The big issue was the lack of documentation. I figured IntelliJ had been out for a long time, they are in the process of finalizing the version 10 release and there appear to be a lot of plug-ins so there must be web sites about it and various bits of on-line help. That just was not the case. The only help I found was directly on the IntelliJ forums and a lot of that was many years out of date. See very bottom of this post for code snippets to solve the issues I mention.

I wrote the guts of the program using a MigLayout based test program. A basic text area with a button to parse and a listbox with the errors I found. I needed to get the code processing in place first as I have to skip comments, combine lines of code that span multiple lines, split out the string pieces, do RegEx processing and guess if it really is constraint etc. No need to do all that work with the plug-in overhead. Once I had that running I contacted the MigLayout author to see how he felt about things.

The initial plug-in documentation seemed pretty easy, set up an action, query the editor and parse. I have written Eclipse plug-ins in the past, in fact it was a whole RCP, so I remember how involved that can be. Very powerful but very involved with XML things here and linking three files perfectly via string names to tie it together. This was going to be so much easier.

First thing you can't just developer a plug-in using the out of the box community edition of IntelliJ. You have to install GIT and download the full source project. This is 1g worth of data. It failed on me the first two times. I had to make sure my machine did not come anywhere near going to sleep during the process. After that initial 1g hit was another 1/2g of deltas that got automatically downloaded. GIT is an all or nothing download so it does not restart in the middle if things to awry. It takes a really long time to download. Finally you have to build the program with a simple ant build script. No problem if you have ant installed and configured which I did not but that was easy to do.

It then creates a ZIP file for Windows and the proper images for the other support OS platforms. Honestly if would be nice if they just posted the 80 meg zip file I needed for Windows. This is a fully functional version of IntelliJ along with plug-in SDK support that you install.

You point your running instance to this new instance as the plug-in SDK and you can start code development. So far so good but there is very little sample code to get you started. I did a lot of source code scrounging and using the little bit of on-line help I could fine. I was able to figure out how to get the project, editor and document so I could pull out the selected lines. I found a way to determine what line number I was on and I could run my already functional parser and show the errors in an alert dialog. I wanted a console window.

Others have asked on the forums about the console window and I pieced together various posts to get it working but all I could output was straight text lines. I wanted you to be able to click on them to jump to the spot in the code of the issue. Others had asked the question with no answers. I did my first release to the developers at work without that support. In the end I did figure out how to use the printHyperlink call to do what I wanted.

Hot keys threw me off for a bit too. I misread the dialog and thought you put in two hot keys, a primary and a secondary but it really meant if you put in two the user would have to do them in order. I wanted different hot keys for the default key mapping vs. the Eclipse key mapping I use. I did not figure out how to do that so I finally found Alt+Shift+Y that was free in both mappings. Since access to the feature also appears in the Code menu you can still use it even if that key sequence is not available in your mapping.

Even getting to the name of the currently edited file was not something I was able to find on the forums. I pieced together how to do that too. While doing that I realized there were various deprecation warnings. I like my code to be warning free so I went about fixing those. Remember I said a lot of forum post are years old, many from 2004. I was able to clean up all the warnings and the new code looked better and is easier to read. One clean up just created new deprecation warnings around the console view. I was finally able to piece all that together and ended up with warning free.

The hyperlink stuff into the console window was confusing. Each hyperlink you create needs to implement an interface but the only call you get is public void navigate(Project project). You don't even get the text they clicked on! I was hoping to have one generic link click listener but instead I had to create a new object that implements the interface taking the text of the link in the constructor. From there I had to figure out all the editor document access to pull lines and selection model processing to scroll to and highlight the errors.

I am very happy with the end results and will get a lot of use out of the plug-in as I am sure others in the office and hopefully you will too. I don't have a good code formatter for blogspot so below are my code chunks that may help other plug-in developers.

UPDATE: The plug-in has been accepted by IntelliJ and is available from their website.  MigLayout Verifier at JetBrains

Imports I am using for all of the below

import com.intellij.execution.filters.TextConsoleBuilder;
import com.intellij.execution.filters.TextConsoleBuilderFactory;
import com.intellij.execution.ui.ConsoleView;
import com.intellij.execution.ui.ConsoleViewContentType;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.PlatformDataKeys;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.SelectionModel;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.wm.ToolWindow;
import com.intellij.openapi.wm.ToolWindowAnchor;
import com.intellij.openapi.wm.ToolWindowManager;
import com.intellij.ui.content.Content;
import com.intellij.ui.content.ContentFactory;


Creating an associated console view in actionPerformed(AnActionEvent e) method



  if (project != null) {
      ToolWindowManager manager = ToolWindowManager.getInstance(project);
      ToolWindow window = manager.getToolWindow(id);
      if (window == null) {
          TextConsoleBuilderFactory factory = TextConsoleBuilderFactory.getInstance();
          TextConsoleBuilder builder = factory.createBuilder(project);
          view = builder.getConsole();
          window = manager.getToolWindow(id);

          if (window == null) {
              window = manager.registerToolWindow(id, true, ToolWindowAnchor.BOTTOM);
              ContentFactory contentFactory = ServiceManager.getService(ContentFactory.class);
              Content content = contentFactory.createContent(view.getComponent(), "", false);
              window.getContentManager().addContent(content);
              window.show(new Runnable(){
                  public void run() {
                      // Nothing for us to do here
                  }
              });
          }
      }
  }


Jumping to a line of code and highlighting text on that line


public void actionPerformed(AnActionEvent e) {
  Editor editor = e.getData(PlatformDataKeys.EDITOR);
  String findMe = "Text to find on line"
  int line = {line in document you are processing - 1 based}

  int lineStart = editor.getDocument().getLineStartOffset(line - 1);
  int lineEnd = editor.getDocument().getLineEndOffset(line - 1);

  TextRange textRange = new TextRange(lineStart, lineEnd);

  String lineText = editor.getDocument().getText(textRange);
  int pos = lineText.indexOf(findMe);
  if (pos != -1) {
      SelectionModel selModel = editor.getSelectionModel();
      selModel.setSelection(lineStart + pos, lineStart + pos + findMe.length());
      ScrollingModel scrollModel = editor.getScrollingModel();
      scrollModel.scrollTo(new LogicalPosition(line - 1, col), ScrollType.MAKE_VISIBLE);
  }

Getting file name of active editor

public void actionPerformed(AnActionEvent e) {
  VirtualFile virtualFile = e.getData(PlatformDataKeys.VIRTUAL_FILE);
  String fileName = virtualFile.getName()

Getting selected text and line where it starts

public void actionPerformed(AnActionEvent e) {
  Editor editor = e.getData(PlatformDataKeys.EDITOR);
  SelectionModel selModel = editor.getSelectionModel();

  int curLine = editor.getDocument().getLineNumber(selModel.getSelectionStart());
  String selText = selModel.getSelectedText();


No comments:

Post a Comment