[{"data":1,"prerenderedAt":163},["ShallowReactive",2],{"$f7AVZHmPutZ23_8XKkLbDtEszKCuiY3yPjIDp9eRXGag":3,"$fBBQ5ulyG5jVWbIBJe3r8F-kTKQKf1scMJiu0B6qzX5k":5,"$fkkPSWsqJ-G6ZMzdeqY8C588uYSsbUk3M8dBNYyhWnJ0":7,"$f_JQxgptF7RA7XmjTDS150QI_1dWmeOozEp1pFu4Vswo":140},{"data":4},[],{"data":6},[],{"data":8,"meta":136},[9],{"id":10,"documentId":11,"title":12,"seo_title":13,"summary":14,"seo_description":15,"slug":16,"published_date":17,"body":18,"content":19,"createdAt":38,"updatedAt":39,"publishedAt":40,"blogauthor":41,"cover":92,"categories":125},366,"w9md0091xq4vl7bwk4iiby0b","OpenIDE Plugin: от нуля до Bug Bounty — учимся создавать  плагины и давим баги","Как написать плагин для OpenIDE: игра Bug Bounty","Разбираем, как устроены и пишутся плагины для OpenIDE, на примере игры Bug Bounty, в которой нужно давить баги прямо в окне IDE.","Как создать плагин для OpenIDE на примере первоапрельской игры Bug Bounty: первые шаги, устройство плагинов на IntelliJ Platform и практическая часть.","openide-plugin","2026-04-01","![]({{strapiUrl}}\u002Fuploads\u002F40ea1c4c9fcd9c3fcd2cb863721d5cc7_7583d3d347.webp)\n\nК первому апреля мы выпустили забавную плагин‑игру «Bug Bounty» для OpenIDE и попробуем разобраться, как эти плагины устроены внутри и как они пишутся.\n\n**Важное замечание! Игра разрабатывалась в свободное от работы время. Ни одна важная задача по поддержке различных языков и стеков не пострадала!**\n\nПрежде чем перейти к написанию плагинов, призываю всех попробовать нашу игру, скачать, установить и наслаждаться небольшим количеством дуракаваляния в «день дурака» — первого апреля. Плагин с игрой доступен в Маркетплейсе.\n\n![Bug Bounty в OpenIDE маркетплейсе]({{strapiUrl}}\u002Fuploads\u002Ff2153fcda593f9c58827d2257c486e7b_dd08b2300c.webp)\n\nВо вкладке **Tools** после этого можно будет запустить игру.\n\n![Запуск Bug Bounty через tools]({{strapiUrl}}\u002Fuploads\u002F6117c1067ed708674f693ecf22b4515f_06f6781251.webp)\n\nПо экрану начнут бегать баги. Их можно будет давить. И набирать очки.\n\n![Bug Bounty в OpenIDE]({{strapiUrl}}\u002Fuploads\u002F866d3a6d5cd08f0016a4eaff1738b684_83d17580e1.webp)\n\n![Bug Bounty Score в OpenIDE]({{strapiUrl}}\u002Fuploads\u002F3c94a2e34ec6135cc05d3526658459d1_1ebf1eddca.webp)\n\nВ нижнем правом углу будет отображаться ваш прогресс. А теперь к делу.\n\nДля читателя, который не осилит поданный мной материал целиком, я подготовил краткое оглавление.\n\n\u003Ch2 id=\"oglavlenie\">Оглавление\u003C\u002Fh2>\n\n- [Вступление](#%D0%92%D1%81%D1%82%D1%83%D0%BF%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5)\n- [Первые шаги](#%D0%9F%D0%B5%D1%80%D0%B2%D1%8B%D0%B5%20%D1%88%D0%B0%D0%B3%D0%B8)\n- [Теоретическая часть](#%D0%A2%D0%B5%D0%BE%D1%80%D0%B5%D1%82%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B0%D1%8F%20%D1%87%D0%B0%D1%81%D1%82%D1%8C)\n- [Практическая часть](#%D0%9F%D1%80%D0%B0%D0%BA%D1%82%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B0%D1%8F%20%D1%87%D0%B0%D1%81%D1%82%D1%8C)\n\n\u003Ch2 id=\"vstuplenie\">Вступление\u003C\u002Fh2>\n\nНас с ребятами в OpenIDE уже какое-то время спрашивают, как написать свой плагин. И вот пришла пора рассказать, как это делается. Сразу оговорюсь: данная статья носит ознакомительный характер, предназначена для первого знакомства с темой разработки плагинов и не рассказывает о том, как сделать игру. Объяснить подобное человеку «вне мира плагиностроения» было бы сложно — это как объяснять ученику начальной школы устройство чёрной дыры с точки зрения квантовой механики. Поэтому предлагаю начать с первого знакомства и поэтапного погружения.\n\nНачнём, пожалуй, с документации. Поскольку OpenIDE основана на платформе JetBrains Platform, которая предполагает возможность разработки плагинов, было бы глупо с ней не познакомиться. В документации есть соответствующий раздел, который объясняет, что и как нужно делать.\n\n> [Ссылка](https:\u002F\u002Fplugins.jetbrains.com\u002Fdocs\u002Fintellij\u002Fdeveloping-plugins.html)\n\nНо было бы странно просто направить читателя к документации. В жизни мы учимся не так, поэтому начнём с практики и по мере необходимости будем дополнять её теорией.\n\nСразу оговорюсь, что возможности платформы огромны, и охватить их все сегодня мы не сможем. Предлагаю сосредоточиться на небольшом примере из списка официальных sample-проектов.\n\n> [Ссылка](https:\u002F\u002Fgithub.com\u002FJetBrains\u002Fintellij-sdk-code-samples\u002Ftree\u002Fmain\u002Faction_basics)\n\nПолный список sample-проектов для разработки плагинов представлен ниже.\n\n> [Ссылка](https:\u002F\u002Fgithub.com\u002FJetBrains\u002Fintellij-sdk-code-samples)\n\n\u003Ch2 id=\"pervye-shagi\">Первые шаги\u003C\u002Fh2>\n\nИтак, чтобы создать плагин, нам потребуется создать проект и установить Plugin DevKit для разработки плагинов.\n\n![Возможность установить Plugin DevKit]({{strapiUrl}}\u002Fuploads\u002F9d6a980326a6e3eaaa4188cd98567851_909c6a487c.webp)\n\n![Установка Plugin DevKit]({{strapiUrl}}\u002Fuploads\u002F2389bc48b7fe5ec06577529dbdcec2a7_c8b6c0c17d.webp)\n\nПосле чего досоздать проект, выбрав IDE Plugin.\n\n![Создание проекта плагина]({{strapiUrl}}\u002Fuploads\u002F3f2f2874aca709cef8b4a7db490c94d8_c8a214b3e0.webp)\n\nДалее необходимо выбрать технологии, которые ты собираешься поддерживать и расширять.\n\n![Выбор технологий при создании проекта]({{strapiUrl}}\u002Fuploads\u002Fa42a9b60007e95cd7bccdbc0f1c78202_12b6333fb6.webp)\n\nМне ничего из этого не подходит и не нужно, поэтому я просто нигде не буду ставить галочку.\n\nМало кто знает, но реальные «плагиноделы» создают свои проекты не при помощи IDE.\n\nДля этого существует отдельный шаблон на GitHub, на основе которого создаются новые репозитории.\n\n> [Ссылка](https:\u002F\u002Fgithub.com\u002FJetBrains\u002Fintellij-platform-plugin-template)\n\nДалее я буду показывать пример проекта не с нуля, а на основе уже существующего проекта. Однако, создавая свой плагин, вы пройдёте этот путь самостоятельно, и это не вызовет сложностей.\n\n\u003Ch2 id=\"teoreticheskaya-chast\">Теоретическая часть\u003C\u002Fh2>\n\nПрежде чем перейти к более практическим действиям, будет необходим некоторый теоретический минимум. Его и предлагаю обрести моему читателю.\n\nВ созданном проекте нас ждёт файл plugin.xml. Plugin.xml — это главный конфигурационный файл любого плагина для IntelliJ Platform (включая IntelliJ IDEA). По сути, это манифест плагина, без которого он не загрузится.\n\nГлавным тегом здесь является `\u003Cidea-plugin>`, внутри которого располагаются теги `\u003Cid>`, `\u003Cname>`, `\u003Cdescription>` и `\u003Cvendor>`. Думаю, из их названий понятно, за что они отвечают, поэтому не буду на этом останавливаться.\n\n![Описательные теги проекта плагина.]({{strapiUrl}}\u002Fuploads\u002F81af3acb7ef5f17bf6052832d4191223_04294ad209.webp)\n\nВообще, чтобы подробно разобраться со всеми этими тегами, стоит ознакомиться с разделом документации, посвящённым описанию конфигурационного файла.\n\n> [Ссылка](https:\u002F\u002FChttps:\u002F\u002Fplugins.jetbrains.com\u002Fdocs\u002Fintellij\u002Fplugin-configuration-file.html)\n\nДалее по тексту я лишь кратко рассмотрю те теги, которые есть в моём проекте и те, с которыми точно нужно познакомиться.\n\nПоскольку я не выбирал никаких зависимостей, в теге `\u003Cdepends>` моего XML будет указана только одна — стандартная платформенная:\n\n`\u003Cdepends>com.intellij.modules.platform\u003C\u002Fdepends>`\n\nНаличие только этой зависимости, по сути, означает: «мой плагин работает поверх базовой платформы без привязки к конкретному языку или технологии».\n\nТакже, нам встрпетится тег `\u003Cchange-notes>` . Вот так он выглядит в проекте провергающемся изучению.\n\n```xml\n\u003Cchange-notes>\n    \u003C![CDATA[\n      \u003Cul>\n        \u003Cli>\u003Cb>2.0.0\u003C\u002Fb> Renamed from register_actions and converted to Gradle project.\u003C\u002Fli>\n        \u003Cli>\u003Cb>1.1\u003C\u002Fb> Refactor to give users feedback when selecting menu items.\u003C\u002Fli>\n        \u003Cli>\u003Cb>1.0\u003C\u002Fb> Release 2018.3 and earlier.\u003C\u002Fli>\n      \u003C\u002Ful>\n    ]]>\n  \u003C\u002Fchange-notes>\n```\n\n`\u003Cchange-notes>` — это тег, представляющий собой своего рода release notes. Он описывает изменения в плагине — фактически это changelog, который пользователь видит при обновлении плагина.\n\nЕщё мы встретим `\u003Cresource-bundle>` — это тег, внутри которого можно указать `.properties`-файл с локализованными строками. По сути, это место, где ты говоришь среде разработки: «Тексты бери не из XML напрямую, а из properties-файла». В дальнейшем к значениям из этого файла можно обращаться по мере необходимости.\n\n```xml\n\u003Cresource-bundle>messages.BasicActionsBundle\u003C\u002Fresource-bundle>\n```\n\nПрошу читателя немного потерпеть — сейчас будет код (честно!), но перед этим нужно разобраться ещё с двумя блоками, жизненно необходимыми при разработке: `\u003Cextensions>` и `\u003Cactions>`. Не все из них представлены в разбираемом проекте: блок `\u003Cextensions>` в нём отсутствует. Однако знать о нём важно.\n\nБлок `\u003Cextensions>` — это очень важное место: именно здесь мы «встраиваемся» в IntelliJ Platform, то есть используем точки её расширения.\n\nДля наглядности я приведу код этого блока из нашего проекта Bug Bounty, чтобы у читателя сформировалось более реальное понимание.\n\n```xml\n    \u003Cextensions defaultExtensionNs=\"com.intellij\">\n        \u003C!-- Persistent settings service -->\n        \u003CapplicationService serviceImplementation=\"ru.openide.bugbounty.settings.BugBountySettings\"\u002F>\n\n        \u003C!-- Project-scoped game manager -->\n        \u003CprojectService\n            serviceImplementation=\"ru.openide.bugbounty.game.GameManager\"\u002F>\n\n        \u003C!-- Idle detection: auto-starts game after inactivity -->\n        \u003CprojectService\n            serviceImplementation=\"ru.openide.bugbounty.idle.IdleGameStarter\"\u002F>\n\n        \u003CpostStartupActivity implementation=\"ru.openide.bugbounty.startup.IdleStarterStartupActivity\"\u002F>\n\n        \u003C!-- Settings page: Settings → Tools → Bug Bounty -->\n        \u003CapplicationConfigurable\n            parentId=\"tools\"\n            instance=\"ru.openide.bugbounty.settings.BugBountyConfigurable\"\n            id=\"ru.openide.bugbounty.settings.BugBountyConfigurable\"\n            displayName=\"Bug Bounty\"\u002F>\n\n        \u003C!-- Status bar widget factory -->\n        \u003CstatusBarWidgetFactory\n            id=\"BugBountyStatusBar\"\n            implementation=\"ru.openide.bugbounty.ui.BugBountyStatusBarWidgetFactory\"\u002F>\n    \u003C\u002Fextensions>\n```\n\nЧасто в примерах в описании этого тега указывается `defaultExtensionNs=\"com.intellij\"` — это означает, что используются стандартные extension points платформы.\n\nВнутри `\u003Cextensions>` можно встретить такие элементы, как `\u003CapplicationService>`, `\u003CprojectService>`, `\u003CpostStartupActivity>`, `\u003CapplicationConfigurable>`, `\u003CstatusBarWidgetFactory>`.\n\n`applicationService` — это способ зарегистрировать глобальный объект уровня приложения в плагине для IntelliJ Platform. То есть, сколько бы ни было открыто проектов в OpenIDE, такой объект будет доступен глобально — для любого проекта и вообще в рамках всей среды разработки.\n\nНо здесь есть важное слово — «Service».\n\n**Сервис** — это компонент плагина, загружаемый по запросу, когда плагин вызывает метод `getService()` у соответствующего экземпляра `ComponentManager`. Платформа IntelliJ гарантирует, что будет создан только один экземпляр сервиса, даже если к нему обращаются несколько раз. Сервисы используются для инкапсуляции логики, работающей с набором связанных классов, или для предоставления переиспользуемой функциональности, доступной во всём плагине. По сути, они не отличаются от сервисов в других языках или фреймворках.\n\nПлатформа IntelliJ предлагает три типа сервисов: уровня приложения (глобальный синглтон), уровня проекта и уровня модуля. Для двух последних типов создаётся отдельный экземпляр сервиса для каждой области видимости.\n\nВ контексте IntelliJ Platform _project_ и _module_ — это разные уровни структуры.\n\n**Проект** — это верхний контейнер: то, что пользователь открывает в IDE. Внутри него находятся общие настройки, список модулей, конфигурации запуска и весь рабочий контекст. Проще говоря, проект — это вся рабочая область целиком.\n\n**Модуль** — это часть проекта. Обычно это отдельный логический блок: например, backend, frontend, api, common или plugin-core. У модуля свои исходники, зависимости, настройки SDK, ресурсы и собственный classpath. Один проект может содержать как один модуль, так и несколько.\n\n**Чтобы упростить:** _то, что вы открываете в IDE как проект (например, Java-проект), — это область видимости «проект». Если же у вас проект состоит из нескольких Maven-модулей, то у каждого из них есть своя область видимости «модуль». Платформа гарантирует, что для каждой области будет создан свой экземпляр сервиса: один на всё приложение, по одному на каждый проект и по одному на каждый модуль._\n\nВсе возможные варианты я описывать не буду — моя задача здесь лишь познакомить читателя с тем, как всё устроено. Для первого знакомства этой информации, на мой взгляд, достаточно, а углубляться в детали сейчас не требуется. Поэтому идём дальше. Этот блок приведён для общего понимания, так как с ним неизбежно придётся столкнуться при разработке.\n\nТеперь про `\u003Cactions>`.\n\nПлатформа предоставляет концепцию действий. Действие — это класс, унаследованный от `AnAction`, метод `actionPerformed()` которого вызывается при выборе соответствующего пункта меню или нажатии кнопки на панели инструментов.\n\n**Действия** — это наиболее распространённый способ, с помощью которого пользователь вызывает функциональность вашего плагина. Действие можно запустить из меню или панели инструментов, с помощью сочетания клавиш или через поиск «Справка | Найти действие…».\n\n```xml\n  \u003Cactions>\n    \u003Caction id=\"org.intellij.sdk.action.PopupDialogAction\" class=\"org.intellij.sdk.action.PopupDialogAction\"\n            text=\"Action Basics Plugin: Pop Dialog Action\" description=\"SDK action example\"\n            icon=\"SdkIcons.Sdk_default_icon\">\n      \u003Cadd-to-group group-id=\"ToolsMenu\" anchor=\"first\"\u002F>\n      \u003Coverride-text place=\"MainMenu\" text=\"Pop Dialog Action\"\u002F>\n      \u003Ckeyboard-shortcut first-keystroke=\"control alt A\" second-keystroke=\"C\" keymap=\"$default\"\u002F>\n      \u003Cmouse-shortcut keystroke=\"control button3 doubleClick\" keymap=\"$default\"\u002F>\n    \u003C\u002Faction>\n\n    \u003Cgroup id=\"org.intellij.sdk.action.GroupedActions\"\n           text=\"Static Grouped Actions\" description=\"SDK statically grouped action example\"\n           popup=\"true\" icon=\"SdkIcons.Sdk_default_icon\">\n      \u003Cadd-to-group group-id=\"ToolsMenu\" anchor=\"after\" relative-to-action=\"org.intellij.sdk.action.PopupDialogAction\"\u002F>\n      \u003Caction id=\"org.intellij.sdk.action.GroupPopDialogAction\" class=\"org.intellij.sdk.action.PopupDialogAction\"\n              text=\"A Group Action\" description=\"SDK static grouped action example\"\n              icon=\"SdkIcons.Sdk_default_icon\">\n      \u003C\u002Faction>\n    \u003C\u002Fgroup>\n    \n    \u003Cgroup id=\"org.intellij.sdk.action.CustomDefaultActionGroup\"\n           class=\"org.intellij.sdk.action.CustomDefaultActionGroup\"\n           popup=\"true\">\n      \u003Cadd-to-group group-id=\"EditorPopupMenu\" anchor=\"first\"\u002F>\n      \u003Caction id=\"org.intellij.sdk.action.CustomGroupedAction\" class=\"org.intellij.sdk.action.PopupDialogAction\"\n              icon=\"SdkIcons.Sdk_default_icon\"\u002F>\n    \u003C\u002Fgroup>\n\n    \u003Cgroup id=\"org.intellij.sdk.action.DynamicActionGroup\" class=\"org.intellij.sdk.action.DynamicActionGroup\"\n           popup=\"true\" text=\"Dynamically Grouped Actions\" description=\"SDK dynamically grouped action example\"\n           icon=\"SdkIcons.Sdk_default_icon\">\n      \u003Cadd-to-group group-id=\"ToolsMenu\" anchor=\"after\" relative-to-action=\"org.intellij.sdk.action.GroupedActions\"\u002F>\n    \u003C\u002Fgroup>\n  \u003C\u002Factions>\n```\n\nВ блоке `\u003Cactions>` может быть один или несколько тегов `\u003Caction>`.\n\n- `id` (необязательный; по умолчанию используется краткое имя класса действия, если не указано) — уникальный идентификатор действия. Рекомендуется явно задавать этот атрибут. Идентификатор должен быть уникальным среди всех плагинов, поэтому обычно к нему добавляют значение `\u003Cid>` плагина в качестве префикса.\n- `class` (обязательный) — полное имя класса, реализующего действие.\n- `text` (обязателен, если действие не локализовано) — текст по умолчанию, отображаемый для действия (подсказка для кнопки на панели инструментов или текст пункта меню).\n- `description` (необязательный) — текст, отображаемый в строке состояния при наведении на действие.\n- `icon` (необязательный) — иконка, отображаемая на кнопке панели инструментов или рядом с пунктом меню.\n- `use-shortcut-of` (необязательный) — идентификатор действия, сочетание клавиш которого будет использоваться данным действием.\n\nКроме того, действия можно группировать. Например, можно привязаться к стандартным группам платформы.\n\n```xml\n    \u003Caction id=\"org.intellij.sdk.action.PopupDialogAction\" class=\"org.intellij.sdk.action.PopupDialogAction\"\n            text=\"Action Basics Plugin: Pop Dialog Action\" description=\"SDK action example\"\n            icon=\"SdkIcons.Sdk_default_icon\">\n      \u003Cadd-to-group group-id=\"ToolsMenu\" anchor=\"first\"\u002F>\n      \u003Coverride-text place=\"MainMenu\" text=\"Pop Dialog Action\"\u002F>\n      \u003Ckeyboard-shortcut first-keystroke=\"control alt A\" second-keystroke=\"C\" keymap=\"$default\"\u002F>\n      \u003Cmouse-shortcut keystroke=\"control button3 doubleClick\" keymap=\"$default\"\u002F>\n    \u003C\u002Faction>\n```\n\nЗа это отвечает тег `\u003Cadd-to-group>`. На скриншоте выше мы привязываем действие к вкладке «Tools» в среде разработки, указывая `id` группы (это платформенная группа; их описание доступно в документации, а самим группам посвящён абзац ниже).\n\nПоложение действия в списке задаётся с помощью атрибута `anchor`. В данном случае action будет доступен первым. Всего возможны четыре варианта: `first`, `last`, `after`, `before`. Если используются `after` или `before`, необходимо указать связанный action, относительно которого будет определяться позиция.\n\nКроме того, можно задать сочетание клавиш с помощью тега `\u003Ckeyboard-shortcut>`, указать `keymap`, к которому будет привязан этот shortcut, а также задать shortcut для мыши через `\u003Cmouse-shortcut>`.\n\nТеперь про группы и тег `\u003Cgroup>`.\n\nДействия, описываемые тегом `\u003Caction>`, можно группировать. Можно, например, объявить `\u003Caction>` внутри группы и тем самым сразу привязать его к ней. Также можно отдельно описать группу и затем ссылаться на неё из блока `\u003Caction>`.\n\nПри привязке действия к группе вовсе не обязательно использовать вкладку «Tools». Можно выбрать, например, вкладку «Editor» или любую другую подходящую группу.\n\n```xml\n    \u003Cgroup id=\"org.intellij.sdk.action.GroupedActions\"\n           text=\"Static Grouped Actions\" description=\"SDK statically grouped action example\"\n           popup=\"true\" icon=\"SdkIcons.Sdk_default_icon\">\n      \u003Cadd-to-group group-id=\"ToolsMenu\" anchor=\"after\" relative-to-action=\"org.intellij.sdk.action.PopupDialogAction\"\u002F>\n      \u003Caction id=\"org.intellij.sdk.action.GroupPopDialogAction\" class=\"org.intellij.sdk.action.PopupDialogAction\"\n              text=\"A Group Action\" description=\"SDK static grouped action example\"\n              icon=\"SdkIcons.Sdk_default_icon\">\n      \u003C\u002Faction>\n    \u003C\u002Fgroup>\n   \n    \u003Cgroup id=\"org.intellij.sdk.action.CustomDefaultActionGroup\"\n           class=\"org.intellij.sdk.action.CustomDefaultActionGroup\"\n           popup=\"true\">\n      \u003Cadd-to-group group-id=\"EditorPopupMenu\" anchor=\"first\"\u002F>\n      \u003Caction id=\"org.intellij.sdk.action.CustomGroupedAction\" class=\"org.intellij.sdk.action.PopupDialogAction\"\n              icon=\"SdkIcons.Sdk_default_icon\"\u002F>\n    \u003C\u002Fgroup>\n    \n    \u003Cgroup id=\"org.intellij.sdk.action.DynamicActionGroup\" class=\"org.intellij.sdk.action.DynamicActionGroup\"\n           popup=\"true\" text=\"Dynamically Grouped Actions\" description=\"SDK dynamically grouped action example\"\n           icon=\"SdkIcons.Sdk_default_icon\">\n      \u003Cadd-to-group group-id=\"ToolsMenu\" anchor=\"after\" relative-to-action=\"org.intellij.sdk.action.GroupedActions\"\u002F>\n    \u003C\u002Fgroup>\n```\n\nИз важного и необходимого для первого знакомства с плагинами стоит обратить внимание на теги внутри группы:\n\n- `id` (обязательный) — уникальный идентификатор группы. Он должен быть уникальным среди всех плагинов, поэтому рекомендуется добавлять к нему значение `\u003Cid>` плагина в качестве префикса.\n- `class` (необязательный) — полное имя класса реализации группы. Если не указано, используется `DefaultActionGroup`. Класс реализации должен наследоваться от платформенного класса `ActionGroup` или `DefaultActionGroup`.\n\n![ Имплементация группы унаследованная от ActionGroup]({{strapiUrl}}\u002Fuploads\u002Fb0e3aa916e4d26e0f367e400a7652df5_42ad877ec6.webp)\n\n![ Имплементация группы унаследованная от DefaultActionGroup]({{strapiUrl}}\u002Fuploads\u002Fbc04d47840b5c27a5fee030bbb665178_afc389698a.webp)\n\nВ случае с `DynamicActionGroup` не предполагается фиксированного набора действий. Есть лишь метод `getChildren()`, который возвращает действия, формирующие содержимое всплывающего окна с нужным текстом. Такая группа будет доступна, например, во вкладке «Tools».\n\nВ случае с `CustomDefaultActionGroup` вызвать действие можно из окна редактирования файла. Метод `update()` будет вызываться в момент клика правой кнопкой мыши и определять, доступно ли действие для выполнения.\n\n**Дорогой читатель, не переживай, если сейчас многое выглядит ненаглядно и непонятно — всё станет ясно в ходе практической части.**\n\nТакже у группы есть следующие атрибуты:\n\n- `text` (обязательный, если `popup=\"true\"` и группа не локализована) — текст по умолчанию (полное название), отображаемый для группы, например в пункте меню, открывающем подменю.\n- `description` (необязательный) — текст, отображаемый в строке состояния, когда группа находится в фокусе.\n- `icon` (необязательный) — значок, отображаемый рядом с пунктом меню группы. Подробнее — в разделе «Работа со значками». В нашем случае используется отдельный класс с одним статическим свойством, содержащим путь к изображению в папке `icons` проекта.\n\n![Класс описывающий иконку]({{strapiUrl}}\u002Fuploads\u002F5681ed657e5e399877a323574c19d122_0da933a10b.webp)\n\n- `popup` (необязательный) — логический флаг, определяющий, отображаются ли элементы группы во всплывающем подменю:\n  - `true` — действия группы размещаются в подменю;\n  - `false` (по умолчанию) — действия отображаются как часть того же меню, разделённые сепараторами.\n- `compact` (необязательный) — логический флаг, определяющий, скрыты ли отключённые действия в группе:\n  - `true` — отключённые действия скрыты;\n  - `false` (по умолчанию) — отключённые действия видны.\n- `use-shortcut-of` (необязательный) — `id` действия, сочетание клавиш которого будет использоваться данной группой.\n- `searchable` (необязательный; доступен с версии 2020.3) — логический флаг, определяющий, отображается ли группа в окнах «Справка | Найти действие…» или «Навигация | Поиск везде». Значение по умолчанию — `true`.\n\n\u003Ch2 id=\"prakticheskaya-chast\">Практическая часть\u003C\u002Fh2>\n\nПожалуй, для первого знакомства теории даже многовато. Пора перейти к практике.\n\nПоскольку проект у нас уже есть, его стоит запустить. Для этого воспользуемся Gradle-плагином. В разделе _Tasks_ выбираем _IntelliJ Platform_ — перед нами появится довольно обширный список задач.\n\nЧтобы собрать дистрибутив плагина, можно использовать задачу `buildPlugin`. Но сейчас наша цель — «потыкать» всё руками и проверить работоспособность. Поэтому для запуска воспользуемся задачей `runIde` и выберем режим _debug_.\n\n![возможности Gradle при работе с плагином]({{strapiUrl}}\u002Fuploads\u002F6c97aa4194236e224f4d61c8be518554_5a75ec96d9.webp)\n\nБудет запущена новая среда разработки и откроется окно выбора проекта. Выбираем любой проект из доступных на компьютере, открываем его и ждем, когда закончится индексация.\n\nДалее нам будут доступны действия, которые мы описали в плагине.\n\nНапример, в Tools будут доступны действия и группы, которые мы написали.\n\n![Группы и действия в запущенном плагине]({{strapiUrl}}\u002Fuploads\u002F0acae30600b691b90cc8581db5500256_ee035f3ad3.webp)\n\nПервым в списке идёт `Pop Dialog Action`.\n\nЧтобы всё встало на свои места, давайте ещё раз посмотрим на нужный блок `xml` и связанный с ним код.\n\nВ блоке `\u003Cactions>` у нас есть `\u003Caction>` для всплывающего диалога.\n\n```xml\n    \u003Caction id=\"org.intellij.sdk.action.PopupDialogAction\" class=\"org.intellij.sdk.action.PopupDialogAction\"\n            text=\"Action Basics Plugin: Pop Dialog Action\" description=\"SDK action example\"\n            icon=\"SdkIcons.Sdk_default_icon\">\n      \u003Cadd-to-group group-id=\"ToolsMenu\" anchor=\"first\"\u002F>\n      \u003Coverride-text place=\"MainMenu\" text=\"Pop Dialog Action\"\u002F>\n      \u003Ckeyboard-shortcut first-keystroke=\"control alt A\" second-keystroke=\"C\" keymap=\"$default\"\u002F>\n      \u003Cmouse-shortcut keystroke=\"control button3 doubleClick\" keymap=\"$default\"\u002F>\n    \u003C\u002Faction>\n```\n\nДанный `action` содержит текст и привязан к группе «Tools». Связанная с ним логика описана в классе `PopupDialogAction`, который, как и любой `action`-класс, должен наследовать платформенный класс действия, например `AnAction`, и переопределять его методы.\n\n![PopupDialogAction класс имплементации]({{strapiUrl}}\u002Fuploads\u002F123d24b3252fabe44e679242315873d4_79328afd90.webp)\n\nМетод `update` в данном случае отвечает за отображение текста и самого `action` в списке доступных во вкладке «Tools» и вызывается при взаимодействии с ним.\n\n![метод actionPerformed]({{strapiUrl}}\u002Fuploads\u002Fde1f0ae3c359e36c584de581ef419875_a239bbde9b.webp)\n\nМетод `actionPerformed` содержит логику выполнения. Что интересно, она зависит от того, выбран ли в проекте какой-либо элемент, относительно которого можно выполнить навигацию.\n\nЕсли такой элемент выбран (например, файл), то при выполнении `action` пользователю будет показана информация о «навигабельном» элементе.\n\nСмотрим:\n\n![Вызов описанного выше action]({{strapiUrl}}\u002Fuploads\u002F5b1f9b8335b95631a62728839625aedb_80bd22e528.webp)\n\nВкладка проекта свернута, значит, никакой «навигабельный» элемент не выбран. Нажимаем на выбранное действие и видим всплывающее окно.\n\n![Появившееся всплывающее окно диалога]({{strapiUrl}}\u002Fuploads\u002F88e8afcb9f26c4ff61b74afac8c9da49_736f4c146d.webp)\n\nТекст заголовка в данном случае прописан в блоке `description` у нашего `action` в `plugin.xml`. В описании `\u003Caction>` также есть тег:\n\n`\u003Coverride-text place=\"MainMenu\" text=\"Pop Dialog Action\"\u002F>`\n\nОн позволяет переопределить текст `action` в зависимости от контекста вызова. То есть один и тот же `action` может отображаться по-разному в разных местах IDE. В нашем случае поведение задано только для одного сценария, да и сам `action` доступен лишь из одного места.\n\nЛогика метода добавляет к тексту строковый литерал `\" Selected!\"`.\n\nТеперь выберем «навигабельный» элемент в проекте, например `docker-compose.yaml`, и выполним те же действия.\n\n![Выполнение Pop Dialog Action с выбранным Navigatable-элементом]({{strapiUrl}}\u002Fuploads\u002Fa1ea8202ad8fb1181ad67a43bce71667_ed656eb0d4.webp)\n\nВ результате выполнения нам будет показана информация о выбранном файле.\n\n![Всплывающее окно диалога]({{strapiUrl}}\u002Fuploads\u002F021e97cb83376b3e89053c3f11008e62_08d5aa5bb5.webp)\n\nВернемся к списку доступных действий.\n\n![Группы и действия в запущенном плагине]({{strapiUrl}}\u002Fuploads\u002F5dc9e870a3e0a83d1a39935c531213da_789a1bbbe0.webp)\n\nК выбору доступны _Static Grouped Actions_ и _Dynamically Grouped Actions_. Разберёмся с ними по порядку.\n\nНачнём со _Static Grouped Actions_. Вернёмся к `xml` и увидим уже знакомое описание группы. Внутри неё объявлен `action`, который ссылается на тот же класс `PopupDialogAction`, но с изменённым текстом — `\"A Group Action\"`.\n\n```xml\n    \u003Cgroup id=\"org.intellij.sdk.action.GroupedActions\"\n           text=\"Static Grouped Actions\" description=\"SDK statically grouped action example\"\n           popup=\"true\" icon=\"SdkIcons.Sdk_default_icon\">\n      \u003Cadd-to-group group-id=\"ToolsMenu\" anchor=\"after\" relative-to-action=\"org.intellij.sdk.action.PopupDialogAction\"\u002F>\n      \u003Caction id=\"org.intellij.sdk.action.GroupPopDialogAction\" class=\"org.intellij.sdk.action.PopupDialogAction\"\n              text=\"A Group Action\" description=\"SDK static grouped action example\"\n              icon=\"SdkIcons.Sdk_default_icon\">\n      \u003C\u002Faction>\n    \u003C\u002Fgroup>\n```\n\n![Static Group Action группа и вложенный action]({{strapiUrl}}\u002Fuploads\u002F44f919fe380b85de60bd40d05137dc92_0734176b44.webp)\n\nВыполним действие и увидим уже знакомое всплывающее окно с информацией о `docker-compose`.\n\n![Всплывающее окно диалога]({{strapiUrl}}\u002Fuploads\u002F4b06f45ff0e908b6d63a6b25a41f88a3_30c6f8c46c.webp)\n\nЗаголовок и текстовка взяты из блоков _description_ и _text_ группы в plugin.xml.\n\nДальше по списку идёт группа «Dynamically Grouped Actions».\n\n```xml\n    \u003Cgroup id=\"org.intellij.sdk.action.DynamicActionGroup\" class=\"org.intellij.sdk.action.DynamicActionGroup\"\n           popup=\"true\" text=\"Dynamically Grouped Actions\" description=\"SDK dynamically grouped action example\"\n           icon=\"SdkIcons.Sdk_default_icon\">\n      \u003Cadd-to-group group-id=\"ToolsMenu\" anchor=\"after\" relative-to-action=\"org.intellij.sdk.action.GroupedActions\"\u002F>\n    \u003C\u002Fgroup>\n```\n\nОписание нам уже понятно. Однако здесь указан класс реализации, поэтому давайте заглянем в код.\n\n![DynamicActionGroup класс имплементации]({{strapiUrl}}\u002Fuploads\u002F1ea8627e817d9bfeb3b5af1ce828c22b_a467ae1af3.webp)\n\nВ данном случае класс группы наследуется от платформенного класса `ActionGroup` и переопределяет метод `getChildren()`, который возвращает список `AnAction`.\n\nЗдесь объект `Action` создаётся прямо в коде — это уже знакомый нам `PopupDialogAction` с соответствующими аргументами. Пробуем.\n\n![Динамически добавленное действие к группе]({{strapiUrl}}\u002Fuploads\u002F89574a6abae6036782cc996516cc8da7_3f1a59bbbe.webp)\n\n![Уже знакомый нам всплывающий диалог с верными текстовками]({{strapiUrl}}\u002Fuploads\u002F967d83feebc56d36a667fb836a83e7c6_27be289e7c.webp)\n\nОткуда берутся тексты — понятно: они заданы в виде строковых литералов при создании объекта `PopupDialogAction`.\n\nЧто ж, осталась последняя группа, и она привязана не к «Tools», а к меню редактирования. Возвращаемся в `plugin.xml` и смотрим.\n\n```xml\n    \u003Cgroup id=\"org.intellij.sdk.action.CustomDefaultActionGroup\"\n           class=\"org.intellij.sdk.action.CustomDefaultActionGroup\"\n           popup=\"true\">\n      \u003Cadd-to-group group-id=\"EditorPopupMenu\" anchor=\"first\"\u002F>\n      \u003Caction id=\"org.intellij.sdk.action.CustomGroupedAction\" class=\"org.intellij.sdk.action.PopupDialogAction\"\n              icon=\"SdkIcons.Sdk_default_icon\"\u002F>\n    \u003C\u002Fgroup>\n```\n\nЗдесь есть отдельный класс реализации и отдельный `action`. Заглянем в код.\n\n![Код имплементации CustomDefaultActionGroup]({{strapiUrl}}\u002Fuploads\u002F95273be1d472c86d9a61c09fc8e79443_c486fc9c37.webp)\n\nКласс группы здесь небольшой и содержит только метод `update`, который вызывается в момент рендеринга. Это происходит, когда открыта область редактирования и мы нажимаем правую кнопку мыши, чтобы вызвать `EditorPopupMenu` — контекстное меню редактора.\n\nВ качестве реализации `action` используется уже знакомый `PopupDialogAction`.\n\nСтоит отметить, что `description` и `text` здесь не заданы ни для группы, ни для `action`. Они вынесены в отдельный файл `BasicActionsBundle.properties`, о котором я говорил ранее, и имеют англоязычную локализацию. При обращении к группе платформа подставляет все необходимые тексты именно из этого файла.\n\n![BasicActionsBundle.properties]({{strapiUrl}}\u002Fuploads\u002Ffce43ab4e609519f5fe99a12502c12f3_03a41c44c4.webp)\n\nПробуем: открываем уже знакомый `docker-compose`, нажимаем правую кнопку мыши и видим группу и действие с соответствующими текстами. Здесь также можно увидеть отображение иконки, использование и описание которой я приводил ранее.\n\n![Выполнение действия из меню редактирования]({{strapiUrl}}\u002Fuploads\u002F7a8896f5a196f6903c485637015dc0e8_270d6e6b80.webp)\n\nВыполняем действие и видим соответствующие текстовки.\n\n![Уже знакомое окно при выполнении действия]({{strapiUrl}}\u002Fuploads\u002F6590e7bbde82ec08da65e1261cc2acf6_c7fd0731f0.webp)\n\nПодобные `.properties`-файлы привязаны к пакетам локализации среды разработки. Чтобы удобно разделять их по языкам и избежать путаницы, следует соблюдать правила именования. Например, для русской локализации можно создать файл `BasicActionsBundle_ru_RU.properties`, после чего переключить язык среды разработки на русский.\n\nНа этом первое знакомство завершено. Охватить всё в рамках одной статьи невозможно. Для более глубокого погружения рекомендую ознакомиться с циклом статей. [Ссылка](https:\u002F\u002Fhabr.com\u002Fru\u002Farticles\u002F187106\u002F).\n\nА ещё, в честь первого апреля, предлагаю немного развлечься и поиграть в игру «Bug Bounty» в OpenIDE — половить «баги», имитируя бурную рабочую деятельность. В вашем проекте их от этого меньше не станет, зато настроение поднимется, появится заряд дофамина, кровь прильёт к мозгу, и справляться даже с самыми сложными задачами станет гораздо проще.\n\nНа этом у меня всё. Спасибо за внимание! Создавайте свои плагины и публикуйте их в нашем маркетплейсе. Активное комьюнити — это драйвер развития среды разработки. Мы слышим нашу аудиторию и всегда на связи. Хорошего дня!\n\n![]({{strapiUrl}}\u002Fuploads\u002Fedd09a77c88b3201a636af7e740a976f_77e93f0ef8.webp)\n\nУже сейчас [OpenIDE](https:\u002F\u002Fopenide.ru\u002F) позволяет разрабатывать проекты на Java, Spring, Python, Go, JavaScript и TypeScript! А поддержка Docker и 300+ плагинов доступны абсолютно бесплатно в [маркетплейсе](https:\u002F\u002Fmarketplace.openide.ru\u002F). Пробуйте российскую IDE в деле и подписывайтесь на нас в [Telegram](https:\u002F\u002Ft.me\u002Fopenide_official), чтобы не пропустить свежие обновления и полезные материалы.\n\n---\n\n_Впервые опубликовано на [Хабре](https:\u002F\u002Fhabr.com\u002Fru\u002Fcompanies\u002Fhaulmont\u002Farticles\u002F1017968\u002F)._\n",[20],{"title":21,"links":22},"Содержание",[23,26,29,32,35],{"label":24,"href":25},"Оглавление","#oglavlenie",{"label":27,"href":28},"Вступление","#vstuplenie",{"label":30,"href":31},"Первые шаги","#pervye-shagi",{"label":33,"href":34},"Теоретическая часть","#teoreticheskaya-chast",{"label":36,"href":37},"Практическая часть","#prakticheskaya-chast","2026-11-12T11:27:34.621Z","2026-11-13T11:49:21.660Z","2026-11-13T11:49:21.725Z",{"id":42,"documentId":43,"firstname":44,"lastname":45,"createdAt":46,"updatedAt":47,"publishedAt":48,"slug":49,"photo":50},38,"m8neann2s46tgq858cpr124q","Павел","Кислов","2026-02-15T12:49:47.587Z","2026-11-13T11:49:24.957Z","2026-11-13T11:49:25.060Z","pavel-kislov",{"id":51,"documentId":52,"name":53,"alternativeText":54,"caption":54,"width":55,"height":55,"formats":56,"hash":87,"ext":60,"mime":61,"size":88,"url":89,"previewUrl":54,"provider":90,"provider_metadata":54,"createdAt":91,"updatedAt":91,"publishedAt":91,"focalPoint":54},1358,"hpsfwiispy3prtyl96f3d4jx","Frame_59_1_f8af65a416.webp",null,1920,{"thumbnail":57,"large":66,"medium":73,"small":80},{"name":58,"hash":59,"ext":60,"mime":61,"path":54,"width":62,"height":62,"size":63,"sizeInBytes":64,"url":65},"thumbnail_Frame_59_1_f8af65a416.webp","thumbnail_Frame_59_1_f8af65a416_47e5d2e1a1",".webp","image\u002Fwebp",156,1.68,1678,"\u002Fuploads\u002Fthumbnail_Frame_59_1_f8af65a416_47e5d2e1a1.webp",{"name":67,"hash":68,"ext":60,"mime":61,"path":54,"width":69,"height":69,"size":70,"sizeInBytes":71,"url":72},"large_Frame_59_1_f8af65a416.webp","large_Frame_59_1_f8af65a416_47e5d2e1a1",1000,22.16,22162,"\u002Fuploads\u002Flarge_Frame_59_1_f8af65a416_47e5d2e1a1.webp",{"name":74,"hash":75,"ext":60,"mime":61,"path":54,"width":76,"height":76,"size":77,"sizeInBytes":78,"url":79},"medium_Frame_59_1_f8af65a416.webp","medium_Frame_59_1_f8af65a416_47e5d2e1a1",750,14.26,14258,"\u002Fuploads\u002Fmedium_Frame_59_1_f8af65a416_47e5d2e1a1.webp",{"name":81,"hash":82,"ext":60,"mime":61,"path":54,"width":83,"height":83,"size":84,"sizeInBytes":85,"url":86},"small_Frame_59_1_f8af65a416.webp","small_Frame_59_1_f8af65a416_47e5d2e1a1",500,7.77,7774,"\u002Fuploads\u002Fsmall_Frame_59_1_f8af65a416_47e5d2e1a1.webp","Frame_59_1_f8af65a416_47e5d2e1a1",82.09,"\u002Fuploads\u002FFrame_59_1_f8af65a416_47e5d2e1a1.webp","local","2026-11-13T11:47:14.953Z",{"id":93,"documentId":94,"name":95,"alternativeText":54,"caption":54,"width":96,"height":97,"formats":98,"hash":121,"ext":60,"mime":61,"size":122,"url":123,"previewUrl":54,"provider":90,"provider_metadata":54,"createdAt":124,"updatedAt":124,"publishedAt":124,"focalPoint":54},1237,"b40y6b8ua93d4zape3wkmrxm","cover-openide-plugin.webp",780,440,{"thumbnail":99,"medium":107,"small":114},{"name":100,"hash":101,"ext":60,"mime":61,"path":54,"width":102,"height":103,"size":104,"sizeInBytes":105,"url":106},"thumbnail_cover-openide-plugin.webp","thumbnail_cover_openide_plugin_3047f75fe9",245,138,12.56,12558,"\u002Fuploads\u002Fthumbnail_cover_openide_plugin_3047f75fe9.webp",{"name":108,"hash":109,"ext":60,"mime":61,"path":54,"width":76,"height":110,"size":111,"sizeInBytes":112,"url":113},"medium_cover-openide-plugin.webp","medium_cover_openide_plugin_3047f75fe9",423,63.75,63748,"\u002Fuploads\u002Fmedium_cover_openide_plugin_3047f75fe9.webp",{"name":115,"hash":116,"ext":60,"mime":61,"path":54,"width":83,"height":117,"size":118,"sizeInBytes":119,"url":120},"small_cover-openide-plugin.webp","small_cover_openide_plugin_3047f75fe9",282,38.2,38204,"\u002Fuploads\u002Fsmall_cover_openide_plugin_3047f75fe9.webp","cover_openide_plugin_3047f75fe9",78.69,"\u002Fuploads\u002Fcover_openide_plugin_3047f75fe9.webp","2026-11-13T11:44:30.667Z",[126],{"id":127,"documentId":128,"title":129,"published":130,"weight":131,"slug":132,"createdAt":133,"updatedAt":134,"publishedAt":135,"color":54},7,"h4gk8pqqd5nuh7vsv4dzm6lk","Сообщество",true,6,"community","2026-11-12T11:20:31.552Z","2026-11-12T11:22:44.404Z","2026-11-12T11:22:44.391Z",{"pagination":137},{"page":138,"pageSize":139,"pageCount":138,"total":138},1,25,{"data":141,"meta":160},[142,148,154],{"published_date":143,"id":144,"documentId":145,"title":146,"slug":147},"2026-09-16",304,"gvti99z6b19onn318znv9us7","В OpenIDE появилась поддержка C\u002FC++ благодаря независимому разработчику","openide-podderzhka-c-cpp",{"published_date":149,"id":150,"documentId":151,"title":152,"slug":153},"2026-02-16",295,"o9brsxu0wpro5t7iqhi52a6n","Плагины с человеческим лицом: истории комьюнити OpenIDE","plaginy-s-chelovecheskim-liczom-istorii-komyuniti-openide",{"published_date":155,"id":156,"documentId":157,"title":158,"slug":159},"2026-01-21",391,"sf9c0kma39g2pwl3l5kw74tb","Роман Елизаров про AI в разработке ПО, OpenSource и культуру IT [Видео]","roman-elizarov-pro-ai-v-razrabotke-po-opensource-i-kulturu-it",{"pagination":161},{"page":138,"pageSize":162,"pageCount":138,"total":162},3,1794584602235]