В последнее время на Хабре стали появляться статьи про создание расширений для 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-плагину для поддержки одного очень интересного языка программирования;)
