За последнее время у меня накопилось достаточно материалов по разработке плагинов для IntelliJ IDEA, чем и собираюсь поделиться с хабрасообществом.
Среда разработки и инфраструктура
Прежде чем начать программировать плагин стоит рассмотреть устройство среды разработки, поддерживаемые функции и их реализацию, и, разумеется, настройку IDE необходимую для разработки плагинов.
Для разработки плагинов подойдет любая современная версия Intellij IDEA – она уже включает в себя полный набор необходимого инструментария.
Для настройки среды разработки следует выполнить несколько шагов:
- скачать исходные коды соответствующей версии Intellij IDEA Community Edition;
- создать SDK типа «Intellij Platform Plugin SDK» (на рисунке ниже) и указать путь до установленной Community Edition (можно использовать Ultimate Edition, но отладка функций внутреннего API работает только в CE);
- в настройках SDK, на странице Sourcepath необходимо указать путь до загруженных на п.1 исходных кодов;
- создать новый модуль с типом «Platform Plugin» и присвоить ему ранее созданный SDK.
После чего можно приступать к разработке плагина как обычного Java-проекта, но с дополнительной возможностью видеть исходный код внутреннего API и трассировать его исполнение в отладчике.
Номера сборок
Это ограничения на диапазон поддерживаемых версий, используемые интегрированной средой для определения возможности корректной работы конкретного плагина. Начальный и конечный номера билдов указываются в файле plugin.xml, как и прочая метаинформация.
Начиная с IntelliJ IDEA 9 используется составная нумерация билда: например, IU-90.94. Этот номер состоит из следующих частей:
- идентификатор продукта (IC для IDEA Community, IU для IDEA Ultimate, RM для RubyMine и PY для PyCharm);
- номер ветви;
- номер билда в этой ветке.
Каждый раз, когда создается очередная релизная ветвь одного из продуктов, основанных на платформе IntelliJ, номер ветки увеличивается на 1, а номер в транке на 2, таким образом, нестабильные билды имеют четный номер, а стабильные – нечетный.
Составные номера билдов могут применяться в тегах since-build и until-build в файле plugin.xml. Обычно идентификатор продукта опускают, используя лишь номер ветки и билда:
<idea-version since-build="94.539"/>
Совместимость плагинов с продуктами на платформе IntelliJ
Все продукты, базирующиеся на IntelliJ Platform (IntelliJ IDEA, RubyMine, WebStorm, PhpStorm, PyCharm и AppCode) разделяют общий нижележащий API платформы. Таким образом, плагины, не использующие какую-либо специфичную Java функциональность могут быть помечены как совместимые с другими продуктами, а не только с самой IDEA. Сделать это можно определив зависимости от модулей в файле plugin.xml.
Зависимости помещаются внутри тега
, содержимое тега начинается с com.intellij.modules. Например:
<idea-plugin version="2">
...
<depends>com.intellij.modules.lang</depends>
...
</idea-plugin>
Если плагин не включает теги с зависимостями в plugin.xml, то он считается устаревшим и способен запуститься только в IDEA. Если plugin.xml содержит по крайней мере один такой тег, то он загрузится если в продукте содержатся все модули на которые он ссылается.
На данный момент следующие модули доступны во всех продуктах семейства IntelliJ:
- com.intellij.modules.platform;
- com.intellij.modules.lang;
- com.intellij.modules.vcs;
- com.intellij.modules.xml;
- com.intellij.modules.xdebugger.
А эти модули доступны только в соответствующих продуктах:
- com.intellij.modules.java – IntelliJ IDEA;
- com.intellij.modules.ruby – RubyMine;
- com.intellij.modules.python – PyCharm;
- com.intellij.modules.objc – AppCode.
PhpStorm не обладает специфичным модулем, но включает в себя PHP плагин, который тоже возможно использовать как зависимость: com.jetbrains.php.
Также возможно использовать необязательные зависимости. Если плагин работает со всеми продуктами, но предоставляет некоторую специфичную Java функциональность, то можно использовать следующий тег:
<depends optional="true"
config-file="my-java-features.xml">com.intellij.modules.java</depends>
Перед определением плагина совместимым с остальными продуктами, стоит убедиться, что не используются никакие функции специфичные для API IntelliJ IDEA. Для того чтобы сделать это, необходимо создать SDK указывающей на установленный RubyMine или PyCharm, скомпилировать плагин с этим SDK и проверить его работоспособность.
Структура плагина IntelliJ IDEA
Плагины – это единственный поддерживаемый путь расширения функциональности IDEA. Плагин использует предоставляемый программный интерфейс среды или других плагинов для реализации собственной функциональных возможностей. Обратим внимание на структуру и жизненный цикл плагина.
Содержимое плагинов
Существуют три способа организации содержимого плагина.
Первый – плагин содержит один jar-файл, размещенный в папке plugins. В архиве должен находиться конфигурационный файл (META-INF/plugin.xml) и классы, которые реализуют функциональность плагина. Конфигурационный файл определяет имя плагина, описание, данные о разработчике, поддерживаемая версия IDE, компоненты, действия, группы действий.
.IntelliJIDEAx0
plugins
sample.jar/
com/foo/.....
...
...
META-INF
plugin.xml
Второй способ – файлы плагина размещены в папке:
.IntelliJIDEAx0
plugins
Sample
lib
libfoo.jar
libbar.jar
classes
com/foo/.....
...
...
META-INF
plugin.xml
Classes и lib автоматически добавляются в classpath.
Третий способ – файлы плагина помещаются в jar-файл, находящийся в папке lib:
.IntelliJIDEAx0
plugins
Sample
lib
libfoo.jar
libbar.jar
Sample.jar/
com/foo/.....
...
...
META-INF
plugin.xml
Загрузчики классов
Чтобы загрузить классы каждого плагина, IDEA использует раздельные загрузчики классов. Это позволяет использовать различные версии библиотеки, даже если она используются самой IDEA или другим плагином.
По-умолчанию, основной загрузчик классов загружает только те классы, которые не были найдены загрузчиком плагина. Тем не менее, в plugin.xml, в секции depends можно определить зависимости от других плагинов. В таком случае, загрузчики классов этих плагинов будут использованы для разрешения не найденных классов в текущем плагине. Это позволяет ссылаться на классы из других плагинов.
Компоненты плагинов
Компоненты – это фундаментальный концепт интеграции плагинов. Существуют три вида компонентов:
- уровня приложения;
- уровня проекта;
- уровня модуля.
Компоненты уровня приложения создаются и инициализируются во время старта IntelliJ IDEA.
Они могут быть получены от экземпляра класса Application с помощью метода getComponent(Class).
Компоненты уровня проекта создаются для каждого экземпляра класса Project (они могут быть созданы даже для неоткрытого проекта). Их можно получить от экземпляра Project вызовом метода getComponent(Class).
Компоненты уровня модуля создаются для каждого модуля в каждом проекте, загруженном в IDEA. Они могут быть получены аналогичным методом от экземпляра Module.
Каждый компонент должен реализовать интерфейс, определенный в конфигурационном файле. Интерфейс класса будет использован для получения компонента из других компонентов, реализация класса будет использована в процессе инициализации компонента. Важно отметить, что два компонента на одном уровне (модуля, проекта, приложения) не могут иметь одинаковый интерфейс.
Каждый компонент обязан иметь уникальное имя, которое будет использовано при экспортировании и прочих внутренних нуждах. Имя компонента возвращает метод getComponentName(). Рекомендуется применять следующее имя компонента: <имя плагина><точка><имя компонента>.
Дополнительно, класс, реализующий интерфейс компонента уровня приложения также может реализовывать интерфейс ApplicationComponent. Компонент приложения, не имеющий ни одной зависимости должен предоставлять конструктор без параметров, который будет вызван при создании. Если компонент зависит от других, он может принимать их как параметры конструктора, IDEA гарантирует, что эти компоненты будут корректно инициализированы в правильном порядке.
Компоненты уровня приложения должны быть объявлены в секции <application-components>
файла plugin.xml.
IntelliJ IDEA предлагает упрощенный путь создания компонентов приложения, со всей необходимой инфраструктурой. В интерфейсе IDEA предусмотрены инструменты для определения реализации классов компонентов и автоматического изменения секции <application-component>
файла plugin.xml. Для того чтобы создать и зарегистрировать компонент приложения следует выполнить следующие шаги:
- выбрать New в контекстном меню Java пакета или нажать сочетание Alt+Insert;
- в подменю New выбрать Application Component;
- откроется диалог New Application Component, в котором необходимо ввести название компонента и нажать OK.
IDEA сгенерирует новый класс в выбранном пакете, реализующий интерфейс ApplicationComponent, зарегистрирует его в plugin.xml, добавит новый узел в Module Tree View и откроет класс в редакторе.
Классы, имеющие функциональность компонента уровня проекта реализуют интерфейс ProjectComponent. В конструкторе класса компонента должен быть параметр типа Project, если он использует экземпляр проекта. Также он может принимать другие компоненты уровня приложения или проекта как параметры, при наличии зависимости от них.
Компоненты уровня проекта должны быть зарегистрированы в секции <project-components>
конфигурационного файла. Как и в случае компонентов приложения можно воспользоваться помощью IDE, выбрав подменю "New | Project Component".
Компоненты уровня модуля реализуют интерфейс ModuleComponent. Зависимости компонента могут быть переданы как параметры конструктора. Компоненты должны быть зарегистрированы в <module-components>
вручную или посредством выполнения пункта контекстного меню "New | Module Component".
Сохранение состояния компонентов
Состояние каждого компонента будет автоматически сохраняться и загружаться, если класс реализует один из интерфейсов: JDOMExternalizable (устарел, не рекомендуется к применению) или PersistentStateComponent.
Если класс компонента реализует интерфейс PersistentStateComponent, то состояние сохраняется в XML файле, который указан в аннотации @State
или @Storage
(по-умолчанию, помещается в файл <имя компонента>.xml
).
Если класс реализует JDOMExternalizable, то состояние сохраняется в следующих файлах:
- компоненты уровня проекта сохраняют состояние в файле проекта (.ipr), либо при включенной в plugin.xml опции «workspace» – в файле .iws;
- компоненты уровня модуля сохраняют данные в .iml файл.
Жизненный цикл компонентов
Компоненты загружаются в следующем порядке:
- создание – выполнение конструктора;
- инициализация – вызов метода initComponent (если реализован интерфейс ApplicationComponent);
- конфигурация – вызов readExternal (если реализован JDOMExternalizable) или loadState (если реализован PersistentStateComponent и компонент в состоянии не по-умолчанию);
- для компонентов уровня модуля – вызов moduleAdded (если реализован ModuleComponent);
- для компонентов уровня проекта – projectOpened (если реализован интерфейс ProjectComponent).
Компоненты выгружаются в следующем порядке:
- сохранение конфигурации – вызов writeExternal (если реализован интерфейс JDOMExternalizable) или getState (если реализован PersistentStateComponent);
- освобождение ресурсов – вызов метода disposeComponent.
В конструкторе компонента запрещено использовать метод getComponent() для получения каких-либо зависимостей. Если данному компоненту нужно получить другие компоненты при инициализации, они должны быть переданы в параметрах конструктора, либо перенести инициализацию в метод initComponent().
Расширения плагинов и точки расширения
Intellij IDEA предоставляет концепт расширений и точек расширения, которые позволяют взаимодействовать с другими плагинами и ядром IDEA.
Если требуется, чтобы плагин позволял расширять его функциональность, то необходимо определить одну или несколько точек расширения. Каждая такая точка определяет класс или интерфейс, который определяет протокол доступа к ней. Это же касается и расширений плагина.
Определить расширения и точки расширения можно в конфигурационном файле в секциях и соответственно.
Чтобы зарегистрировать точку расширения нужно добавить дочерний элемент внутри секции , который содержит название точки, класс или интерфейс, расширяющий функциональность плагина. Для примера рассмотрим фрагмент файла plugin.xml:
<extensionPoints>
<extensionPoint name="MyExtensionPoint1" beanClass="MyPlugin.MyBeanClass1">
<extensionPoint name="MyExtensionPoint2 interface="MyPlugin.MyInterface">
</extensionPoints>
Атрибут "interface" устанавливает интерфейс, который должен быть реализован для расширения функциональности. Атрибут "beanClass" определяет класс, содержащий одно или несколько свойств помеченных аннотацией @Attribute
. Плагин, предоставляющий точку расширения прочитает эти свойства из файла plugin.xml.
Рассмотрим пример с MyBeanClass1:
public class MyBeanClass1 extends AbstractExtensionPointBean {
@Attribute("key") public String key;
@Attribute("implementationClass") public String implementationClass;
public String getKey() {
return key;
}
public String getClass() {
return implementationClass;
}
}
Чтобы объявить расширение с доступом к точке расширения MyExtPoint, конфигурационный файл должен содержать тег с атрибутами "key" и "implementationClass" с соответствующими значениями.
Для регистрации расширения требуется выполнить следующие шаги:
- в элементе установить значение атрибута «xmlns» (устарел) или «defaultExtensionNs» одним из следующих:
- "com.intellij", если плагин расширяет функциональность ядра IDEA;
- <идентификатор плагина>, если плагин расширяет функциональность другого плагина.
добавить новый дочерний элемент в секцию , название тега должно совпадать с идентификатором точки расширения;
определить тип точки расширения, выбрав из следующих:
- если точка расширения объявлена с атрибутом "interface", то в дочернем элементе необходимо указать атрибут "implementation", значение которого – класс, реализующий этот интерфейс;
- если точка расширения объявлена с атрибутом "beanClass", то дочерний элемент должен содержать все атрибуты, которые были помечены в этом классе аннотацией
@Attribute
.
Действия (Actions)
Также Intellij IDEA предоставляет концепт действий (actions).
Действие – это класс, наследуемый от AnAction, чей метод actionPerformed() вызывается, когда выбран элемент меню или кнопка тулбара.
Действия объединяются в группы, которые также могут содержать вложенные группы. Группы действий могут быть отображены как меню или тулбары. Подгруппы отображаются как подменю.
Позже действия будут рассмотрены более подробно.
Сервисы
Сервис – это компонент, загружаемый по требованию, когда плагин вызывает метод getService() класса ServiceManager. Intellij IDEA гарантирует, что будет создан только один экземпляр сервиса, независимо от того сколько раз был вызван метод.
Сервисы должны иметь интерфейс, определенный в plugin.xml. Класс с реализацией будет использован при создании сервиса.
Сервисы подразделяются по уровням подобно компонентам, т.е. на сервисы уровня приложения, проекта и модуля, которым соответствуют точки расширения applicationService, projectService и moduleService соответственно.
Чтобы объявить сервис необходимо:
- добавить соответствующий дочерний элемент (
, , ) в секцию ;
для добавленного элемента установить следующие атрибуты:
- "serviceInterface" – интерфейс сервиса;
- "serviceImplementaion" – реализация сервиса.
Классы интерфейса и реализации могут совпадать.
Пример из файла plugin.xml:
<extensions defaultExtensionNs="com.intellij">
<!-- сервис уровня приложения -->
<applicationService serviceInterface="Mypackage.MyServiceInterfaceClass" serviceImplementation="Mypackage.MyServiceImplClass">
</applicationService>
<!-- сервис уровня проекта -->
<projectService serviceInterface="Mypackage.MyProjectServiceImplClass" serviceImplementation="Mypackage.MyProjectServiceImplClass">
</projectService>
</extensions>
В следующей части: конфигурационный файл, действия, проекты и др.
Все статьи цикла: 1, 2, 3, 4, 5, 6, 7.