A little more about developing plugins for IntelliJ

Recently articles on creation of extensions for Intellij IDE began to appear on Habré - one , and here another .

I will continue this glorious trend and try to describe those places of Intellij OpenAPI that have not yet been touched; and an example would be a plug-in with funny comics.





The extension, in fact, does just one simple thing - it displays in the socket panel fresh pictures from my favorite Geek & Poke , periodically downloading them from the site and caching to disk. Sources, by the way, on GitHub'e .

For those who are especially vigilant - comics are under the good license of CC BY-SA 3.0 , so everything is legal :)

Since the very creation of the project, writingplugin.xmland other basic things - as well as links to the relevant documentation - are already described in the above articles, we will not repeat; and I’ll just describe a few questions that I had while developing with their solutions.

Proxy support


IntelliJ IDEA (and other IDEs) are able to connect to the network through a proxy, the settings are described in detail in the documentation .

But to make your extension use the global IDE settings, it's worth looking in the direction of the class com.intellij.util.net.HttpConfigurable. Its public fields contain all the necessary information: the flag USE_HTTP_PROXY, for example, says whether we use proxies at all or not; as well as information about the host, port, and user.

The easiest way is to use the method prepareURL, calling it for each connection:
  /**
   * Call this function before every HTTP connection.
   * If system configured to use HTTP proxy, this function
   * checks all required parameters and ask password if
   * required.
   * @param url URL for HTTP connection
   * @throws IOException
   */
  public void prepareURL (String url) throws IOException {


For example, in the code for some, urlit might look like this:
    // Ensure that proxy (if any) is set up for this request.
    final HttpConfigurable httpConfigurable = HttpConfigurable.getInstance();
    httpConfigurable.prepareURL(url.toExternalForm());


At the JetBrains forum, someone swears that this method does not help - but, in my opinion, they are in vain.

Starting the process when initializing the plug-in


I needed to start a separate thread, which would periodically check the main page for updates.

This will help ApplicationComponent. The types of components and their creation are remarkably described in the documentation, in the Plugin Structure article .

Add to plugin.xmlour component:

com.abelsky.idea.geekandpoke.ComicsPlugincom.abelsky.idea.geekandpoke.ComicsPlugin


And in it we define the method initComponent:

public class ComicsPlugin implements ApplicationComponent {
    private static final int UPDATE_PERIOD = 15 * 60 * 60 * 1000;
    // Этот метод будет вызываться один раз при инициализации расширения;
    // если бы использовали ProjectComponent - то для каждого проекта.
    @Override
    public void initComponent() {
        startUpdateTimer();
    }
    private void startUpdateTimer() {
        final Timer timer = new Timer("Geek and Poke updater");
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                // А тут что-то делаем каждые 15 минут...
            }
        }, 0, ComicsPlugin.UPDATE_PERIOD);
    }


Localization


For localization it’s convenient to use this snippet:

package com.abelsky.idea.geekandpoke.messages;
// ...
public class MessageBundle {
    private static Reference bundleRef;
    // Сами тексты лежат в com/abelsky/idea/geekandpoke/messages/MessageBundle.properties
    //  - стандартный key-value .properties-файл.
    @NonNls
    private static final String BUNDLE = "com.abelsky.idea.geekandpoke.messages.MessageBundle";
    private MessageBundle() {
    }
    public static String message(@PropertyKey(resourceBundle = BUNDLE)String key, Object... params) {
        return CommonBundle.message(getBundle(), key, params);
    }
    private static ResourceBundle getBundle() {
        ResourceBundle bundle = null;
        if (MessageBundle.bundleRef != null) {
            bundle = MessageBundle.bundleRef.get();
        }
        if (bundle == null) {
            bundle = ResourceBundle.getBundle(BUNDLE);
            MessageBundle.bundleRef = new SoftReference(bundle);
        }
        return bundle;
    }
}


Here it is worth paying attention to several points.

The first is storedResourceBundle in SoftReference . This is a fairly common practice in the IDEA source code - to keep as many objects as possible in non-hard links.

By the way, I advise you to look at the class com.intellij.reference.SoftReference- it is the developers themselves who use it instead of implementing from java.lang.ref. The difference is that if you suspect a memory leak, com.intellij.reference.SoftReferenceyou can quickly convert it into a hard link, and this will help with profiling.

The second is an annotation org.jetbrains.annotations.PropertyKey. It indicates that the annotated argument of the method can only be a string from the one specified in the parameterresourceBundlethe bundle. Its use adds confidence that the keys in the .properties file and in the code are not out of sync (and even refactoring in IDEA learns a lot, since there is a connection between the key and the bundle).

Third, annotations org.jetbrains.annotations.NonNls/ org.jetbrains.annotations.Nls, marking lines that should not (or, conversely, should) be translated. JetBrains documentation for use is here .

Notifications


For some events, I want to show a beautiful notification. Such, for example:



Here it is worth looking towards the class com.intellij.notification.Notifications. For example, like this:

    private void notifyNewEntry() {
        final Notification newEntryNotification = new Notification(
                /* Группа нотификаций */
                MessageBundle.message("notification.new.strip.group"),		
                /* Заголовок */
                MessageBundle.message("notification.new.strip.title"),
                /* Содержание */
                MessageBundle.message("notification.new.strip.content"),
                NotificationType.INFORMATION);
        // Не обязательно вызывать из UI-треда - внутри все равно будет сделан invokeLater.
        Notifications.Bus.notify(newEntryNotification);
    }


The notification group is displayed in the IDE settings, for more details, see the documentation .



Settings




How to make the settings page for the plug-in is described in detail in the documentation .

But in short, then, firstly, register in plugin.xml:



Secondly, we implement interface methods com.intellij.openapi.options.Configurable. Of these, the most important - createComponentshould return a component on which our settings are displayed.

Offline cache


My plug-in needed to store pictures on disk - in principle, the same question will arise when writing all sorts of caches that have no place in the project directory or in %TMP%. You can, for example, write them somewhere in %USERPROFILE%, or you can make it more interesting - use the directory in which the plug-in itself is installed.

Extensions are set to by default %USERPROFILE%/.IdeaIC11/config/plugins/PLUGIN_NAME; this path, however, can be changed by setting the variable idea.plugins.pathto idea.properties.

    // PLUGIN_ID - значение элемента id в plugin.xml.
    final PluginId id = PluginId.getId(PLUGIN_ID);
    final IdeaPluginDescriptor plugin = PluginManager.getPlugin(id);
    // Путь к установленному расширению.
    File path = plugin.getPath();


The resulting path, by the way, can easily turn out to be a JAR file - if the extension is not unpacked into a separate directory.

PS


I hope this short FAQ will be useful for beginners delving into the IntelliJ platform. And in the meantime, I proceed to the point for which I began to understand all this - an IDEA plug-in to support one very interesting programming language;)

Also popular now: