Розробка плагіна IntelliJ IDEA

Останнім часом у мене накопичилося достатньо матеріалів для розробки плагінів для 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. Зазвичай ідентифікатор продукту опускають, використовуючи лише номер гілки та білда:

Сумісність плагінів із продуктами на платформі IntelliJ

Всі продукти, що базуються на IntelliJ Platform (IntelliJ IDEA, RubyMine, WebStorm, PhpStorm, PyCharm та AppCode) поділяють загальний нижчележачий API платформи. Таким чином, плагіни, що не використовують будь-яку специфічну Java функціональність, можуть бути позначені як сумісні з іншими продуктами, а не тільки з самою IDEA. Зробити це можна визначивши залежність від модулів у файлі plugin.xml. Залежно поміщаються всередині тега Якщо плагін не включає теги із залежностями в 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 функціональність, можна використовувати наступний тег:

Перед визначенням плагіна сумісним з іншими продуктами, варто переконатися, що не використовуються ніякі специфічні функції для API IntelliJ IDEA. Для того, щоб зробити це, необхідно створити SDK, що вказує на встановлений RubyMine або PyCharm, скомпілювати плагін з цим SDK і перевірити його працездатність.

Структура плагіна IntelliJ IDEA

Плагіни – це єдиний підтримуваний шлях розширення функціональності IDEA. Плагін використовує програмний інтерфейс середовища або інших плагінів для реалізації власної функціональних можливостей. Звернімо увагу на структуру та життєвий цикл плагіна.

Вміст плагінів

Існують три способи організації вмісту плагіна. Перший – плагін містить один jar-файл, розміщений у папці plugins. В архіві має бути конфігураційний файл (META-INF/plugin.xml) і класи, які реалізують функціональність плагіна. Конфігураційний файл визначає ім'я плагіна, опис, дані про розробника, версія IDE, компоненти, дії, групи дій, що підтримується.

Другий спосіб - файли плагіна розміщені в папці:

Classes і lib автоматично додаються до classpath. Третій спосіб - файли плагіна поміщаються в jar-файл, що знаходиться в папці lib:

Завантажувачі класів

Для завантаження класів кожного плагіна IDEA використовує окремі завантажувачі класів. Це дозволяє використовувати різні версії бібліотеки, навіть якщо вона використовується сама IDEA або іншим плагіном. За замовчуванням, основний завантажувач класів завантажує лише ті класи, які не були знайдені завантажувачем плагіна. Тим не менш, в plugin.xml, у секції depends можна визначити залежність від інших плагінів. У такому випадку завантажувачі класів цих плагінів будуть використані для дозволу не знайдених класів у поточному плагіні. Це дозволяє посилатися на класи з інших плагінів.

Компоненти плагінів

Компоненти є фундаментальним концептом інтеграції плагінів. Існують три види компонентів:

  1. рівня програми;
  2. рівня проекту;
  3. рівня модуля
Компоненти рівня програми створюються та ініціалізуються під час старту IntelliJ IDEA. Вони можуть бути отримані від екземпляра класу Application за допомогою методу getComponent(Class).

Компоненти рівня проекту створюються для кожного екземпляра класу Project (вони можуть бути створені навіть для відкритого проекту). Їх можна отримати від екземпляра Project викликом методу getComponent(Class).

Компоненти рівня модуля створюються для кожного модуля в кожному проекті, завантаженому IDEA. Вони можуть бути одержані аналогічним методом від екземпляра Module.

Кожен компонент повинен реалізувати інтерфейс, визначений у файлі конфігурації. Інтерфейс класу буде використано для отримання компонента з інших компонентів, реалізація класу буде використана у процесі ініціалізації компонента. Важливо, що два компоненти одному рівні (модуля, проекту, докладання) що неспроможні мати однаковий інтерфейс.

Коженкомпонент повинен мати унікальне ім'я, яке буде використано під час експорту та інших внутрішніх потреб. Ім'я компонента повертає метод getComponentName(). Рекомендується використовувати таке ім'я компонента: .

Додатково, клас, що реалізує інтерфейс компонента рівня програми, також може реалізовувати інтерфейс ApplicationComponent. Компонент програми, що не має жодної залежності, повинен надавати конструктор без параметрів, який буде викликаний при створенні. Якщо компонент залежить від інших, він може приймати їх як параметри конструктора IDEA гарантує, що ці компоненти будуть коректно ініціалізовані в правильному порядку.

Класи, що мають функціональність компонента рівня проекту, реалізують інтерфейс ProjectComponent. У конструкторі класу компонента має бути параметр типу Project, якщо він використовує екземпляр проекту. Також він може приймати інші компоненти рівня програми або проекту як параметри за наявності залежності від них.

Компоненти рівня проекту мають бути зареєстровані у секції

конфігураційного файлу. Як і у випадку компонентів програми, можна скористатися за допомогою IDE, вибравши підменю "New Project Component".

Компоненти модуля реалізують інтерфейс ModuleComponent. Залежність компонента можуть бути передані як параметри конструктора. Компоненти повинні бути зареєстровані уручну або за допомогою пункту контекстного меню "New Module Component".

Збереження стану компонентів
Життєвий цикл компонентів

Компоненти завантажуються в наступному порядку:

  • створення – виконання конструктора;
  • ініціалізація – виклик методу initComponent (якщо реалізовано інтерфейс ApplicationComponent);
  • конфігурація –виклик readExternal (якщо реалізований JDOMExternalizable) або loadState (якщо реалізований PersistentStateComponent і компонент може не за замовчуванням);
  • для компонентів рівня модуля – виклик moduleAdded (якщо реалізовано ModuleComponent);
  • для компонентів рівня проекту – projectOpened (якщо реалізовано інтерфейс ProjectComponent).
Компоненти вивантажуються у такому порядку:
  • збереження конфігурації - виклик writeExternal (якщо реалізований інтерфейс JDOMExternalizable) або getState (якщо реалізований PersistentStateComponent);
  • Звільнення ресурсів – виклик методу, що складається зкомпоненту.
У конструкторі компонента заборонено використовувати метод getComponent() для отримання будь-яких залежностей. Якщо даному компоненту потрібно отримати інші компоненти при ініціалізації, вони повинні бути передані в параметрах конструктора або перенести ініціалізацію метод initComponent().

Розширення плагінів та точки розширення

Intellij IDEA надає концептрозширеньтаточок розширення, які дозволяють взаємодіяти з іншими плагінами та ядром IDEA. Якщо потрібно, щоб плагін дозволяв розширювати його функціональність, необхідно визначити одну або кілька точок розширення. Кожна така точка визначає клас чи інтерфейс, який визначає протокол доступу до неї. Те саме стосується й розширень плагіна.

Визначити розширення та точки розширення можна у конфігураційному файлі в секціях Атрибут "interface" встановлює інтерфейс, який має бути реалізований для розширення функціональності. Атрибут "beanClass" визначає клас, що містить одну або декілька властивостей, помічених анотацією @Attribute . Плагін, що надає точку розширення, прочитає ці властивості з файлу.plugin.xml. Розглянемо приклад з MyBeanClass1:

Щоб оголосити розширення з доступом до точки розширення MyExtPoint, файл конфігурації повинен містити тег з атрибутами "key" і "implementationClass" з відповідними значеннями.

Для реєстрації розширення потрібно виконати такі кроки:

  1. в елементі встановити значення атрибуту "xmlns" (застарілий) або "defaultExtensionNs" одним з наступних:
  2. "com.intellij", якщо плагін розширює функціональність ядра IDEA;
  3. якщо плагін розширює функціональність іншого плагіна.
  4. додати новий дочірній елемент у секцію , назва тега має збігатися з ідентифікатором точки розширення; визначити тип точки розширення, вибравши з таких:
    • якщо точка розширення оголошена з атрибутом "interface", то в дочірньому елементі необхідно вказати атрибут "implementation", значення якого клас, що реалізує цей інтерфейс;
    • якщо точку розширення оголошено з атрибутом "beanClass", то дочірній елемент повинен містити всі атрибути, помічені в цьому класі анотацією @Attribute .

Дії (Actions)

Також Intellij IDEA пропонує концепт дій (actions). Дія – це клас, успадкований від AnAction, чий метод actionPerformed() викликається, коли вибрано елемент меню чи кнопка тулбара.

Дії об'єднуються у групи, які можуть містити вкладені групи. Групи дій можуть відображатися як меню або тулбари. Підгрупи відображаються як підменю. Пізніше дії будуть розглянуті докладніше.

Сервіс – це компонент, що завантажується на вимогу, коли плагін викликає метод getService() класу ServiceManager. Intellij IDEA гарантує, що буде створено лише один екземплярсервісу, незалежно від того, скільки разів був викликаний метод.

Сервіси повинні мати інтерфейс, визначений plugin.xml. Клас з реалізацією буде використано під час створення сервісу.

Сервіси поділяються за рівнями подібно до компонентів, тобто. на сервіси рівня програми, проекту та модуля, яким відповідають точки розширення applicationService, projectService та moduleService відповідно.

Щоб оголосити сервіс, необхідно:

  • додати відповідний дочірній елемент ( , , ) у секцію; для доданого елемента встановити такі атрибути:
  • "serviceInterface" - інтерфейс сервісу;
  • "ServiceImplementaion" - реалізація сервісу.

Класи інтерфейсу та реалізації можуть збігатися. Приклад файлу plugin.xml:

У наступній частині: конфігураційний файл, дії, проекти та ін.