В последнее время на Хабре стали появляться статьи про создание расширений для Intellij IDE — одна, а вот и другая.
Я продолжу эту славную тенденцию и постараюсь описать те места Intellij OpenAPI, которых еще не коснулись; а примером будет plug-in с веселыми комиксами.

Расширение, на самом деле, занимается всего одной простой вещью — отображает в панельке свежие картинки с моего любимого Geek&Poke, периодически выкачивая их с сайта и кэшируя на диск. Исходники, кстати, на GitHub'e.
Для особо бдительных — комиксы находятся под доброй лицензией CC BY-SA 3.0, так что все законно:)
Поскольку само создание проекта, написание
IntelliJ IDEA (да и другие IDE) умеют подключаться к сети через прокси, настройка подробно описана в документации.
А вот чтобы заставить свое расширение использовать глобальные настройки IDE, стоит посмотреть в сторону класса
Проще всего воспользоваться методом
Например, в коде для некоторого
На форуме JetBrains кто-то ругается, что этот метод не помогает — но, по-моему, зря они так.
Мне было нужно запустить отдельный поток, который бы периодически проверял главную страницу на наличие обновлений.
В этом поможет
Добавим в
А в нем самом определим метод
Для локализации удобно использовать вот такой сниппет:
Здесь стоит обратить внимание на несколько моментов.
Первое — храним
К слову, советую посмотреть на класс
Второе — аннотация
Третье — аннотации
При некоторых событиях хочется показать красивое уведомление. Такое, например:

Здесь стоит смотреть в сторону класса
Группа уведомления отображается в настройках IDE, подробнее — в документации.


Про то, как сделать страницу настроек для plug-in'a, подробно написано в документации.
Но если вкратце, то, во-первых, регистрируемся в
Во-вторых, реализуем методы интерфейса
Моему plug-in'у понадобилось хранить картинки на диске — в принципе, тот же вопрос встанет и при записи всяких кэшей, которым не место ни в директории проекта, ни в
Расширения по-умолчанию устанавливаются в
Полученный путь, к слову, легко может оказаться JAR-файлом — если расширение не распаковано в отдельную директорию.
Надеюсь, этот короткий FAQ будет полезен начинающим копаться в платформе IntelliJ. А я, тем временем, приступаю к тому, ради чего и начал во всем этом разбираться — IDEA-плагину для поддержки одного очень интересного языка программирования;)
Я продолжу эту славную тенденцию и постараюсь описать те места Intellij OpenAPI, которых еще не коснулись; а примером будет plug-in с веселыми комиксами.

Расширение, на самом деле, занимается всего одной простой вещью — отображает в панельке свежие картинки с моего любимого Geek&Poke, периодически выкачивая их с сайта и кэшируя на диск. Исходники, кстати, на GitHub'e.
Для особо бдительных — комиксы находятся под доброй лицензией CC BY-SA 3.0, так что все законно:)
Поскольку само создание проекта, написание
plugin.xml и другие основные вещи — как и ссылки на соответствующую документацию — уже описаны в вышеупомянутых статьях, повторяться не будем; и я просто опишу несколько возникших у меня при разработке вопросов с их решениями.Поддержка proxy
IntelliJ IDEA (да и другие IDE) умеют подключаться к сети через прокси, настройка подробно описана в документации.
А вот чтобы заставить свое расширение использовать глобальные настройки IDE, стоит посмотреть в сторону класса
com.intellij.util.net.HttpConfigurable. В его публичных полях содержится вся необходимая информация: флаг USE_HTTP_PROXY, к примеру, говорит, используем ли мы вообще прокси или нет; а также есть информация о хосте, порте и пользователе.Проще всего воспользоваться методом
prepareURL, вызывая его для каждого соединения:  /**
   * 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 {
Например, в коде для некоторого
url это может выглядеть так:    // Ensure that proxy (if any) is set up for this request.
    final HttpConfigurable httpConfigurable = HttpConfigurable.getInstance();
    httpConfigurable.prepareURL(url.toExternalForm());
На форуме JetBrains кто-то ругается, что этот метод не помогает — но, по-моему, зря они так.
Запуск процесса при инициализации plug-in'a
Мне было нужно запустить отдельный поток, который бы периодически проверял главную страницу на наличие обновлений.
В этом поможет
ApplicationComponent. Типы компонентов и их создание замечательно описаны в документации, в статье Plugin Structure.Добавим в
plugin.xml наш компонент:    <application-components>
        <component>
            <implementation-class>com.abelsky.idea.geekandpoke.ComicsPlugin</implementation-class>
            <interface-class>com.abelsky.idea.geekandpoke.ComicsPlugin</interface-class>
        </component>
    </application-components>
А в нем самом определим метод
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);
    }
Локализация
Для локализации удобно использовать вот такой сниппет:
package com.abelsky.idea.geekandpoke.messages;
// ...
public class MessageBundle {
    private static Reference<ResourceBundle> 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<ResourceBundle>(bundle);
        }
        return bundle;
    }
}
Здесь стоит обратить внимание на несколько моментов.
Первое — храним
ResourceBundle в SoftReference. Это достаточно распространенная практика в исходниках IDEA — держать как можно больше объектов в не-hard ссылках. К слову, советую посмотреть на класс
com.intellij.reference.SoftReference — именно его сами разработчики используют вместо реализации из java.lang.ref. Отличие в том, что при подозрениях в утечке памяти com.intellij.reference.SoftReference можно быстро переделать в hard-ссылку, а это поможет при профилировании.Второе — аннотация
org.jetbrains.annotations.PropertyKey. Она указывает на то, что аннотированный аргумент метода может являться только строкой из указанного в параметре resourceBundle бандла. Ее использование добавляет уверенности в том, что ключи в .properties-файле и в коде не рассинхронизировались (да еще и рефакторинг в IDEA многое учится делать, так как появляется связь между ключем и бандлом).Третье — аннотации
org.jetbrains.annotations.NonNls/org.jetbrains.annotations.Nls, помечающие строки, которые не должны (или, наоборот, должны) быть переведены. Документация от JetBrains по использованию — здесь.Нотификации
При некоторых событиях хочется показать красивое уведомление. Такое, например:

Здесь стоит смотреть в сторону класса
com.intellij.notification.Notifications. Например, так:    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);
    }
Группа уведомления отображается в настройках IDE, подробнее — в документации.

Настройки

Про то, как сделать страницу настроек для plug-in'a, подробно написано в документации.
Но если вкратце, то, во-первых, регистрируемся в
plugin.xml:    <extensions defaultExtensionNs="com.intellij">
    	<!-- ... -->
        <applicationConfigurable instance="com.abelsky.idea.geekandpoke.ui.SettingsPanel"/>
    </extensions>
Во-вторых, реализуем методы интерфейса
com.intellij.openapi.options.Configurable. Из них самый важный — createComponent — должен вернуть компонент, на котором отображаются наши настройки.Offline cache
Моему plug-in'у понадобилось хранить картинки на диске — в принципе, тот же вопрос встанет и при записи всяких кэшей, которым не место ни в директории проекта, ни в
%TMP%. Можно, к примеру, записывать их куда-нибудь в %USERPROFILE%, а можно сделать интереснее — использовать для этого директорию, в которую сам plug-in установлен.Расширения по-умолчанию устанавливаются в
%USERPROFILE%/.IdeaIC11/config/plugins/PLUGIN_NAME; этот путь, впрочем, можно поменять установкой переменной idea.plugins.path в idea.properties.    // PLUGIN_ID - значение элемента id в plugin.xml.
    final PluginId id = PluginId.getId(PLUGIN_ID);
    final IdeaPluginDescriptor plugin = PluginManager.getPlugin(id);
    // Путь к установленному расширению.
    File path = plugin.getPath();
Полученный путь, к слову, легко может оказаться JAR-файлом — если расширение не распаковано в отдельную директорию.
P.S.
Надеюсь, этот короткий FAQ будет полезен начинающим копаться в платформе IntelliJ. А я, тем временем, приступаю к тому, ради чего и начал во всем этом разбираться — IDEA-плагину для поддержки одного очень интересного языка программирования;)

