К первому апреля мы выпустили забавную плагин‑игру «Bug Bounty» для OpenIDE и попробуем разобраться, как эти плагины устроены внутри и как они пишутся.

Важное замечание! Игра разрабатывалась в свободное от работы время. Ни одна важная задача по поддержке различных языков и стеков не пострадала!

Прежде чем перейти к написанию плагинов, призываю всех попробовать нашу игру, скачать, установить и наслаждаться небольшим количеством дуракаваляния в «день дурака» — первого апреля. Плагин с игрой доступен в Маркетплейсе.

Bug Bounty в OpenIDE маркетплейсе
Bug Bounty в OpenIDE маркетплейсе

Во вкладке Tools после этого можно будет запустить игру.

Запуск Bug Bounty через tools
Запуск Bug Bounty через tools

По экрану начнут бегать баги. Их можно будет давить. И набирать очки.

Bug Bounty в OpenIDE
Bug Bounty в OpenIDE
Bug Bounty Score в OpenIDE
Bug Bounty Score в OpenIDE

В нижнем правом углу будет отображаться ваш прогресс. А теперь к делу.

Для читателя, который не осилит поданный мной материал целиком, я подготовил краткое оглавление.

Оглавление

Вступление

Нас с ребятами в OpenIDE уже какое-то время спрашивают, как написать свой плагин. И вот пришла пора рассказать, как это делается. Сразу оговорюсь: данная статья носит ознакомительный характер, предназначена для первого знакомства с темой разработки плагинов и не рассказывает о том, как сделать игру. Объяснить подобное человеку «вне мира плагиностроения» было бы сложно — это как объяснять ученику начальной школы устройство чёрной дыры с точки зрения квантовой механики. Поэтому предлагаю начать с первого знакомства и поэтапного погружения.

Начнём, пожалуй, с документации. Поскольку OpenIDE основана на платформе JetBrains Platform, которая предполагает возможность разработки плагинов, было бы глупо с ней не познакомиться. В документации есть соответствующий раздел, который объясняет, что и как нужно делать.

Ссылка

Но было бы странно просто направить читателя к документации. В жизни мы учимся не так, поэтому начнём с практики и по мере необходимости будем дополнять её теорией.

Сразу оговорюсь, что возможности платформы огромны, и охватить их все сегодня мы не сможем. Предлагаю сосредоточиться на небольшом примере из списка официальных sample-проектов.

Ссылка

Полный список sample-проектов для разработки плагинов представлен ниже.

Ссылка

Первые шаги

Итак, чтобы создать плагин, нам потребуется создать проект и установить Plugin DevKit для разработки плагинов.

Возможность установить Plugin DevKit
Возможность установить Plugin DevKit
Установка Plugin DevKit
Установка Plugin DevKit

После чего досоздать проект, выбрав IDE Plugin.

Создание проекта плагина
Создание проекта плагина

Далее необходимо выбрать технологии, которые ты собираешься поддерживать и расширять.

Выбор технологий при создании проекта
Выбор технологий при создании проекта

Мне ничего из этого не подходит и не нужно, поэтому я просто нигде не буду ставить галочку.

Мало кто знает, но реальные «плагиноделы» создают свои проекты не при помощи IDE.

Для этого существует отдельный шаблон на GitHub, на основе которого создаются новые репозитории.

Ссылка

Далее я буду показывать пример проекта не с нуля, а на основе уже существующего проекта. Однако, создавая свой плагин, вы пройдёте этот путь самостоятельно, и это не вызовет сложностей.

Теоретическая часть

Прежде чем перейти к более практическим действиям, будет необходим некоторый теоретический минимум. Его и предлагаю обрести моему читателю.

В созданном проекте нас ждёт файл plugin.xml. Plugin.xml — это главный конфигурационный файл любого плагина для IntelliJ Platform (включая IntelliJ IDEA). По сути, это манифест плагина, без которого он не загрузится.

Главным тегом здесь является <idea-plugin>, внутри которого располагаются теги <id>, <name>, <description> и <vendor>. Думаю, из их названий понятно, за что они отвечают, поэтому не буду на этом останавливаться.

Описательные теги проекта плагина.
Описательные теги проекта плагина.

Вообще, чтобы подробно разобраться со всеми этими тегами, стоит ознакомиться с разделом документации, посвящённым описанию конфигурационного файла.

Ссылка

Далее по тексту я лишь кратко рассмотрю те теги, которые есть в моём проекте и те, с которыми точно нужно познакомиться.

Поскольку я не выбирал никаких зависимостей, в теге <depends> моего XML будет указана только одна — стандартная платформенная:

<depends>com.intellij.modules.platform</depends>

Наличие только этой зависимости, по сути, означает: «мой плагин работает поверх базовой платформы без привязки к конкретному языку или технологии».

Также, нам встрпетится тег <change-notes> . Вот так он выглядит в проекте провергающемся изучению.

<change-notes>
    <![CDATA[
      <ul>
        <li><b>2.0.0</b> Renamed from register_actions and converted to Gradle project.</li>
        <li><b>1.1</b> Refactor to give users feedback when selecting menu items.</li>
        <li><b>1.0</b> Release 2018.3 and earlier.</li>
      </ul>
    ]]>
  </change-notes>

<change-notes> — это тег, представляющий собой своего рода release notes. Он описывает изменения в плагине — фактически это changelog, который пользователь видит при обновлении плагина.

Ещё мы встретим <resource-bundle> — это тег, внутри которого можно указать .properties-файл с локализованными строками. По сути, это место, где ты говоришь среде разработки: «Тексты бери не из XML напрямую, а из properties-файла». В дальнейшем к значениям из этого файла можно обращаться по мере необходимости.

<resource-bundle>messages.BasicActionsBundle</resource-bundle>

Прошу читателя немного потерпеть — сейчас будет код (честно!), но перед этим нужно разобраться ещё с двумя блоками, жизненно необходимыми при разработке: <extensions> и <actions>. Не все из них представлены в разбираемом проекте: блок <extensions> в нём отсутствует. Однако знать о нём важно.

Блок <extensions> — это очень важное место: именно здесь мы «встраиваемся» в IntelliJ Platform, то есть используем точки её расширения.

Для наглядности я приведу код этого блока из нашего проекта Bug Bounty, чтобы у читателя сформировалось более реальное понимание.

    <extensions defaultExtensionNs="com.intellij">
        <!-- Persistent settings service -->
        <applicationService serviceImplementation="ru.openide.bugbounty.settings.BugBountySettings"/>

        <!-- Project-scoped game manager -->
        <projectService
            serviceImplementation="ru.openide.bugbounty.game.GameManager"/>

        <!-- Idle detection: auto-starts game after inactivity -->
        <projectService
            serviceImplementation="ru.openide.bugbounty.idle.IdleGameStarter"/>

        <postStartupActivity implementation="ru.openide.bugbounty.startup.IdleStarterStartupActivity"/>

        <!-- Settings page: Settings → Tools → Bug Bounty -->
        <applicationConfigurable
            parentId="tools"
            instance="ru.openide.bugbounty.settings.BugBountyConfigurable"
            id="ru.openide.bugbounty.settings.BugBountyConfigurable"
            displayName="Bug Bounty"/>

        <!-- Status bar widget factory -->
        <statusBarWidgetFactory
            id="BugBountyStatusBar"
            implementation="ru.openide.bugbounty.ui.BugBountyStatusBarWidgetFactory"/>
    </extensions>

Часто в примерах внутри этого тега указывается defaultExtensionNs="com.intellij" — это означает, что используются стандартные extension points платформы.

Внутри <extensions> можно встретить такие элементы, как <applicationService>, <projectService>, <postStartupActivity>, <applicationConfigurable>, <statusBarWidgetFactory>.

Блок <extensions> — это очень важное место: именно здесь мы «встраиваемся» в IntelliJ Platform, то есть используем точки её расширения.

Часто в примерах внутри этого тега указывается defaultExtensionNs="com.intellij" — это означает, что используются стандартные extension points платформы.

Внутри <extensions> можно встретить такие элементы, как <applicationService>, <projectService>, <postStartupActivity>, <applicationConfigurable>, <statusBarWidgetFactory>.

applicationService — это способ зарегистрировать глобальный объект уровня приложения в плагине для IntelliJ Platform. То есть, сколько бы ни было открыто проектов в OpenIDE, такой объект будет доступен глобально — для любого проекта и вообще в рамках всей среды разработки.

Но здесь есть важное слово — «Service».

Сервис — это компонент плагина, загружаемый по запросу, когда плагин вызывает метод getService() у соответствующего экземпляра ComponentManager. Платформа IntelliJ гарантирует, что будет создан только один экземпляр сервиса, даже если к нему обращаются несколько раз. Сервисы используются для инкапсуляции логики, работающей с набором связанных классов, или для предоставления переиспользуемой функциональности, доступной во всём плагине. По сути, они не отличаются от сервисов в других языках или фреймворках.

Платформа IntelliJ предлагает три типа сервисов: уровня приложения (глобальный синглтон), уровня проекта и уровня модуля. Для двух последних типов создаётся отдельный экземпляр сервиса для каждой области видимости.

В контексте IntelliJ Platform project и module — это разные уровни структуры.

Проект — это верхний контейнер: то, что пользователь открывает в IDE. Внутри него находятся общие настройки, список модулей, конфигурации запуска и весь рабочий контекст. Проще говоря, проект — это вся рабочая область целиком.

Модуль — это часть проекта. Обычно это отдельный логический блок: например, backend, frontend, api, common или plugin-core. У модуля свои исходники, зависимости, настройки SDK, ресурсы и собственный classpath. Один проект может содержать как один модуль, так и несколько.

Чтобы упростить: то, что вы открываете в IDE как проект (например, Java-проект), — это область видимости «проект». Если же у вас проект состоит из нескольких Maven-модулей, то у каждого из них есть своя область видимости «модуль». Платформа гарантирует, что для каждой области будет создан свой экземпляр сервиса: один на всё приложение, по одному на каждый проект и по одному на каждый модуль.

Все возможные варианты я описывать не буду — моя задача здесь лишь познакомить читателя с тем, как всё устроено. Для первого знакомства этой информации, на мой взгляд, достаточно, а углубляться в детали сейчас не требуется. Поэтому идём дальше. Этот блок приведён для общего понимания, так как с ним неизбежно придётся столкнуться при разработке.

Теперь про <actions>.

Платформа предоставляет концепцию действий. Действие — это класс, унаследованный от AnAction, метод actionPerformed() которого вызывается при выборе соответствующего пункта меню или нажатии кнопки на панели инструментов.

Действия — это наиболее распространённый способ, с помощью которого пользователь вызывает функциональность вашего плагина. Действие можно запустить из меню или панели инструментов, с помощью сочетания клавиш или через поиск «Справка | Найти действие…».

  <actions>
    <action id="org.intellij.sdk.action.PopupDialogAction" class="org.intellij.sdk.action.PopupDialogAction"
            text="Action Basics Plugin: Pop Dialog Action" description="SDK action example"
            icon="SdkIcons.Sdk_default_icon">
      <add-to-group group-id="ToolsMenu" anchor="first"/>
      <override-text place="MainMenu" text="Pop Dialog Action"/>
      <keyboard-shortcut first-keystroke="control alt A" second-keystroke="C" keymap="$default"/>
      <mouse-shortcut keystroke="control button3 doubleClick" keymap="$default"/>
    </action>

    <group id="org.intellij.sdk.action.GroupedActions"
           text="Static Grouped Actions" description="SDK statically grouped action example"
           popup="true" icon="SdkIcons.Sdk_default_icon">
      <add-to-group group-id="ToolsMenu" anchor="after" relative-to-action="org.intellij.sdk.action.PopupDialogAction"/>
      <action id="org.intellij.sdk.action.GroupPopDialogAction" class="org.intellij.sdk.action.PopupDialogAction"
              text="A Group Action" description="SDK static grouped action example"
              icon="SdkIcons.Sdk_default_icon">
      </action>
    </group>
    
    <group id="org.intellij.sdk.action.CustomDefaultActionGroup"
           class="org.intellij.sdk.action.CustomDefaultActionGroup"
           popup="true">
      <add-to-group group-id="EditorPopupMenu" anchor="first"/>
      <action id="org.intellij.sdk.action.CustomGroupedAction" class="org.intellij.sdk.action.PopupDialogAction"
              icon="SdkIcons.Sdk_default_icon"/>
    </group>

    <group id="org.intellij.sdk.action.DynamicActionGroup" class="org.intellij.sdk.action.DynamicActionGroup"
           popup="true" text="Dynamically Grouped Actions" description="SDK dynamically grouped action example"
           icon="SdkIcons.Sdk_default_icon">
      <add-to-group group-id="ToolsMenu" anchor="after" relative-to-action="org.intellij.sdk.action.GroupedActions"/>
    </group>
  </actions>

В блоке <actions> может быть один или несколько тегов <action>.

  • id (необязательный; по умолчанию используется краткое имя класса действия, если не указано) — уникальный идентификатор действия. Рекомендуется явно задавать этот атрибут. Идентификатор должен быть уникальным среди всех плагинов, поэтому обычно к нему добавляют значение <id> плагина в качестве префикса.

  • class (обязательный) — полное имя класса, реализующего действие.

  • text (обязателен, если действие не локализовано) — текст по умолчанию, отображаемый для действия (подсказка для кнопки на панели инструментов или текст пункта меню).

  • description (необязательный) — текст, отображаемый в строке состояния при наведении на действие.

  • icon (необязательный) — иконка, отображаемая на кнопке панели инструментов или рядом с пунктом меню.

  • use-shortcut-of (необязательный) — идентификатор действия, сочетание клавиш которого будет использоваться данным действием.

Кроме того, действия можно группировать. Например, можно привязаться к стандартным группам платформы.

    <action id="org.intellij.sdk.action.PopupDialogAction" class="org.intellij.sdk.action.PopupDialogAction"
            text="Action Basics Plugin: Pop Dialog Action" description="SDK action example"
            icon="SdkIcons.Sdk_default_icon">
      <add-to-group group-id="ToolsMenu" anchor="first"/>
      <override-text place="MainMenu" text="Pop Dialog Action"/>
      <keyboard-shortcut first-keystroke="control alt A" second-keystroke="C" keymap="$default"/>
      <mouse-shortcut keystroke="control button3 doubleClick" keymap="$default"/>
    </action>

За это отвечает тег <add-to-group>. На скриншоте выше мы привязываем действие к вкладке «Tools» в среде разработки, указывая id группы (это платформенная группа; их описание доступно в документации, а самим группам посвящён абзац ниже).

Положение действия в списке задаётся с помощью атрибута anchor. В данном случае action будет доступен первым. Всего возможны четыре варианта: first, last, after, before. Если используются after или before, необходимо указать связанный action, относительно которого будет определяться позиция.

Кроме того, можно задать сочетание клавиш с помощью тега <keyboard-shortcut>, указать keymap, к которому будет привязан этот shortcut, а также задать shortcut для мыши через <mouse-shortcut>.

Теперь про группы и тег <group>.

Действия, описываемые тегом <action>, можно группировать. Можно, например, объявить <action> внутри группы и тем самым сразу привязать его к ней. Также можно отдельно описать группу и затем ссылаться на неё из блока <action>.

При привязке действия к группе вовсе не обязательно использовать вкладку «Tools». Можно выбрать, например, вкладку «Editor» или любую другую подходящую группу.

    <group id="org.intellij.sdk.action.GroupedActions"
           text="Static Grouped Actions" description="SDK statically grouped action example"
           popup="true" icon="SdkIcons.Sdk_default_icon">
      <add-to-group group-id="ToolsMenu" anchor="after" relative-to-action="org.intellij.sdk.action.PopupDialogAction"/>
      <action id="org.intellij.sdk.action.GroupPopDialogAction" class="org.intellij.sdk.action.PopupDialogAction"
              text="A Group Action" description="SDK static grouped action example"
              icon="SdkIcons.Sdk_default_icon">
      </action>
    </group>
   
    <group id="org.intellij.sdk.action.CustomDefaultActionGroup"
           class="org.intellij.sdk.action.CustomDefaultActionGroup"
           popup="true">
      <add-to-group group-id="EditorPopupMenu" anchor="first"/>
      <action id="org.intellij.sdk.action.CustomGroupedAction" class="org.intellij.sdk.action.PopupDialogAction"
              icon="SdkIcons.Sdk_default_icon"/>
    </group>
    
    <group id="org.intellij.sdk.action.DynamicActionGroup" class="org.intellij.sdk.action.DynamicActionGroup"
           popup="true" text="Dynamically Grouped Actions" description="SDK dynamically grouped action example"
           icon="SdkIcons.Sdk_default_icon">
      <add-to-group group-id="ToolsMenu" anchor="after" relative-to-action="org.intellij.sdk.action.GroupedActions"/>
    </group>

Из важного и необходимого для первого знакомства с плагинами стоит обратить внимание на теги внутри группы:

  • id (обязательный) — уникальный идентификатор группы. Он должен быть уникальным среди всех плагинов, поэтому рекомендуется добавлять к нему значение <id> плагина в качестве префикса.

  • class (необязательный) — полное имя класса реализации группы. Если не указано, используется DefaultActionGroup. Класс реализации должен наследоваться от платформенного класса ActionGroup или DefaultActionGroup.

 Имплементация группы унаследованная от ActionGroup
Имплементация группы унаследованная от ActionGroup
 Имплементация группы унаследованная от DefaultActionGroup
Имплементация группы унаследованная от DefaultActionGroup

В случае с DynamicActionGroup не предполагается фиксированного набора действий. Есть лишь метод getChildren(), который возвращает действия, формирующие содержимое всплывающего окна с нужным текстом. Такая группа будет доступна, например, во вкладке «Tools».

В случае с CustomDefaultActionGroup вызвать действие можно из окна редактирования файла. Метод update() будет вызываться в момент клика правой кнопкой мыши и определять, доступно ли действие для выполнения.

Дорогой читатель, не переживай, если сейчас многое выглядит ненаглядно и непонятно — всё станет ясно в ходе практической части.

Также у группы есть следующие атрибуты:

  • text (обязательный, если popup="true" и группа не локализована) — текст по умолчанию (полное название), отображаемый для группы, например в пункте меню, открывающем подменю.

  • description (необязательный) — текст, отображаемый в строке состояния, когда группа находится в фокусе.

  • icon (необязательный) — значок, отображаемый рядом с пунктом меню группы. Подробнее — в разделе «Работа со значками». В нашем случае используется отдельный класс с одним статическим свойством, содержащим путь к изображению в папке icons проекта.

Класс описывающий иконку
Класс описывающий иконку
  • popup (необязательный) — логический флаг, определяющий, отображаются ли элементы группы во всплывающем подменю:

    • true — действия группы размещаются в подменю;

    • false (по умолчанию) — действия отображаются как часть того же меню, разделённые сепараторами.

  • compact (необязательный) — логический флаг, определяющий, скрыты ли отключённые действия в группе:

    • true — отключённые действия скрыты;

    • false (по умолчанию) — отключённые действия видны.

  • use-shortcut-of (необязательный) — id действия, сочетание клавиш которого будет использоваться данной группой.

  • searchable (необязательный; доступен с версии 2020.3) — логический флаг, определяющий, отображается ли группа в окнах «Справка | Найти действие…» или «Навигация | Поиск везде». Значение по умолчанию — true.

Практическая часть

Пожалуй, для первого знакомства теории даже многовато. Пора перейти к практике.

Поскольку проект у нас уже есть, его стоит запустить. Для этого воспользуемся Gradle-плагином. В разделе Tasks выбираем IntelliJ Platform — перед нами появится довольно обширный список задач.

Чтобы собрать дистрибутив плагина, можно использовать задачу buildPlugin. Но сейчас наша цель — «потыкать» всё руками и проверить работоспособность. Поэтому для запуска воспользуемся задачей runIde и выберем режим debug.

возможности Gradle при работе с плагином
возможности Gradle при работе с плагином

Будет запущена новая среда разработки и откроется окно выбора проекта. Выбираем любой проект из доступных на компьютере, открываем его и ждем, когда закончится индексация.

Далее нам будут доступны действия, которые мы описали в плагине.

Например, в Tools будут доступны действия и группы, которые мы написали.

Группы и действия в запущенном плагине
Группы и действия в запущенном плагине

Первым в списке идёт Pop Dialog Action.

Чтобы всё встало на свои места, давайте ещё раз посмотрим на нужный блок xml и связанный с ним код.

В блоке <actions> у нас есть <action> для всплывающего диалога.

    <action id="org.intellij.sdk.action.PopupDialogAction" class="org.intellij.sdk.action.PopupDialogAction"
            text="Action Basics Plugin: Pop Dialog Action" description="SDK action example"
            icon="SdkIcons.Sdk_default_icon">
      <add-to-group group-id="ToolsMenu" anchor="first"/>
      <override-text place="MainMenu" text="Pop Dialog Action"/>
      <keyboard-shortcut first-keystroke="control alt A" second-keystroke="C" keymap="$default"/>
      <mouse-shortcut keystroke="control button3 doubleClick" keymap="$default"/>
    </action>

Данный action содержит текст и привязан к группе «Tools». Связанная с ним логика описана в классе PopupDialogAction, который, как и любой action-класс, должен наследовать платформенный класс действия, например AnAction, и переопределять его методы.

PopupDialogAction класс имплементации
PopupDialogAction класс имплементации

Метод update в данном случае отвечает за отображение текста и самого action в списке доступных во вкладке «Tools» и вызывается при взаимодействии с ним.

метод actionPerformed
метод actionPerformed

Метод actionPerformed содержит логику выполнения. Что интересно, она зависит от того, выбран ли в проекте какой-либо элемент, относительно которого можно выполнить навигацию.

Если такой элемент выбран (например, файл), то при выполнении action пользователю будет показана информация о «навигабельном» элементе.

Смотрим:

Вызов описанного выше action
Вызов описанного выше action

Вкладка проекта свернута, значит, никакой «навигабельный» элемент не выбран. Нажимаем на выбранное действие и видим всплывающее окно.

Появившееся всплывающее окно диалога
Появившееся всплывающее окно диалога

Текст заголовка в данном случае прописан в блоке description у нашего action в plugin.xml. В описании <action> также есть тег:

<override-text place="MainMenu" text="Pop Dialog Action"/>

Он позволяет переопределить текст action в зависимости от контекста вызова. То есть один и тот же action может отображаться по-разному в разных местах IDE. В нашем случае поведение задано только для одного сценария, да и сам action доступен лишь из одного места.

Логика метода добавляет к тексту строковый литерал " Selected!".

Теперь выберем «навигабельный» элемент в проекте, например docker-compose.yaml, и выполним те же действия.

Выполнение Pop Dialog Action с выбранным Navigatable-элементом
Выполнение Pop Dialog Action с выбранным Navigatable-элементом

В результате выполнения нам будет показана информация о выбранном файле.

Всплывающее окно диалога
Всплывающее окно диалога

Вернемся к списку доступных действий.

Группы и действия в запущенном плагине
Группы и действия в запущенном плагине

К выбору доступны Static Grouped Actions и Dynamically Grouped Actions. Разберёмся с ними по порядку.

Начнём со Static Grouped Actions. Вернёмся к xml и увидим уже знакомое описание группы. Внутри неё объявлен action, который ссылается на тот же класс PopupDialogAction, но с изменённым текстом — "A Group Action".

    <group id="org.intellij.sdk.action.GroupedActions"
           text="Static Grouped Actions" description="SDK statically grouped action example"
           popup="true" icon="SdkIcons.Sdk_default_icon">
      <add-to-group group-id="ToolsMenu" anchor="after" relative-to-action="org.intellij.sdk.action.PopupDialogAction"/>
      <action id="org.intellij.sdk.action.GroupPopDialogAction" class="org.intellij.sdk.action.PopupDialogAction"
              text="A Group Action" description="SDK static grouped action example"
              icon="SdkIcons.Sdk_default_icon">
      </action>
    </group>
Static Group Action группа и вложенный action
Static Group Action группа и вложенный action

Выполним действие и увидим уже знакомое всплывающее окно с информацией о docker-compose.

Всплывающее окно диалога
Всплывающее окно диалога

Заголовок и текстовка взяты из блоков description и text группы в plugin.xml.

Дальше по списку идёт группа «Dynamically Grouped Actions».

    <group id="org.intellij.sdk.action.DynamicActionGroup" class="org.intellij.sdk.action.DynamicActionGroup"
           popup="true" text="Dynamically Grouped Actions" description="SDK dynamically grouped action example"
           icon="SdkIcons.Sdk_default_icon">
      <add-to-group group-id="ToolsMenu" anchor="after" relative-to-action="org.intellij.sdk.action.GroupedActions"/>
    </group>

Описание нам уже понятно. Однако здесь указан класс реализации, поэтому давайте заглянем в код.

DynamicActionGroup класс имплементации
DynamicActionGroup класс имплементации

В данном случае класс группы наследуется от платформенного класса ActionGroup и переопределяет метод getChildren(), который возвращает список AnAction.

Здесь объект Action создаётся прямо в коде — это уже знакомый нам PopupDialogAction с соответствующими аргументами. Пробуем.

Динамически добавленное действие к группе
Динамически добавленное действие к группе
Уже знакомый нам всплывающий диалог с верными текстовками
Уже знакомый нам всплывающий диалог с верными текстовками

Откуда берутся тексты — понятно: они заданы в виде строковых литералов при создании объекта PopupDialogAction.

Что ж, осталась последняя группа, и она привязана не к «Tools», а к меню редактирования. Возвращаемся в plugin.xml и смотрим.

    <group id="org.intellij.sdk.action.CustomDefaultActionGroup"
           class="org.intellij.sdk.action.CustomDefaultActionGroup"
           popup="true">
      <add-to-group group-id="EditorPopupMenu" anchor="first"/>
      <action id="org.intellij.sdk.action.CustomGroupedAction" class="org.intellij.sdk.action.PopupDialogAction"
              icon="SdkIcons.Sdk_default_icon"/>
    </group>

Здесь есть отдельный класс реализации и отдельный action. Заглянем в код.

Код имплементации CustomDefaultActionGroup
Код имплементации CustomDefaultActionGroup

Класс группы здесь небольшой и содержит только метод update, который вызывается в момент рендеринга. Это происходит, когда открыта область редактирования и мы нажимаем правую кнопку мыши, чтобы вызвать EditorPopupMenu — контекстное меню редактора.

В качестве реализации action используется уже знакомый PopupDialogAction.

Стоит отметить, что description и text здесь не заданы ни для группы, ни для action. Они вынесены в отдельный файл BasicActionsBundle.properties, о котором я говорил ранее, и имеют англоязычную локализацию. При обращении к группе платформа подставляет все необходимые тексты именно из этого файла.

BasicActionsBundle.properties
BasicActionsBundle.properties

Пробуем: открываем уже знакомый docker-compose, нажимаем правую кнопку мыши и видим группу и действие с соответствующими текстами. Здесь также можно увидеть отображение иконки, использование и описание которой я приводил ранее.

Выполнение действия из меню редактирования
Выполнение действия из меню редактирования

Выполняем действие и видим соответствующие текстовки.

Уже знакомое окно при выполнении действия
Уже знакомое окно при выполнении действия

Подобные .properties-файлы привязаны к пакетам локализации среды разработки. Чтобы удобно разделять их по языкам и избежать путаницы, следует соблюдать правила именования. Например, для русской локализации можно создать файл BasicActionsBundle_ru_RU.properties, после чего переключить язык среды разработки на русский.

На этом первое знакомство завершено. Охватить всё в рамках одной статьи невозможно. Для более глубокого погружения рекомендую ознакомиться с циклом статей. Ссылка.

А ещё, в честь первого апреля, предлагаю немного развлечься и поиграть в игру «Bug Bounty» в OpenIDE — половить «баги», имитируя бурную рабочую деятельность. В вашем проекте их от этого меньше не станет, зато настроение поднимется, появится заряд дофамина, кровь прильёт к мозгу, и справляться даже с самыми сложными задачами станет гораздо проще.

На этом у меня всё. Спасибо за внимание! Создавайте свои плагины и публикуйте их в нашем маркетплейсе. Активное комьюнити — это драйвер развития среды разработки. Мы слышим нашу аудиторию и всегда на связи. Хорошего дня!

Уже сейчас OpenIDE позволяет разрабатывать проекты на Java, Spring, Python, Go, JavaScript и TypeScript! А поддержка Docker и 300+ плагинов доступны абсолютно бесплатно в маркетплейсе. Пробуйте российскую IDE в деле и подписывайтесь на нас в Telegram, чтобы не пропустить свежие обновления и полезные материалы.