Меня зовут Александр Мамонов, и в KODE я занимаюсь разработкой на Flutter. Я столкнулся с бойлерплейтом композиции фич в наших проектах, поэтому решил написать универсальный плагин для создания файловой структуры фич в проекте.
В статье расскажу и покажу, как сделать базовый плагин для создания файловых структур и собрать его для локального использования или публикации.

Подготовка
В начале нужно установить плагин «Plugin Dev Kit» из магазина плагинов https://plugins.jetbrains.com/plugin/22851-plugin-devkit.

Шаг 1. Создание проекта
Выбираем «Создать новый проект». Затем тип проекта — IDE Plugin, и указываем его название.

Должен получиться такой шаблон:

Шаг 2. Конфигурация проекта
Открываем gradle.properties и вставляем параметры. Ниже в комментариях в коде указаны ссылки на ресурсы, где можно подробнее ознакомиться с параметрами.
# IntelliJ Platform Artifacts Repositories -> https://plugins.jetbrains.com/docs/intellij/intellij-artifacts.html pluginGroup = com.your_feature_name pluginName = your_feature_name pluginRepositoryUrl = https://github.com/Name/your_feature_name # SemVer format -> https://semver.org pluginVersion = 1.0.0 # Supported build number ranges and IntelliJ Platform versions -> https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html pluginSinceBuild = 232 #pluginUntilBuild = 251.* # IntelliJ Platform Properties -> https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#configuration-intellij-extension platformType = IC platformVersion = 2023.2.6 # Plugin Dependencies -> https://plugins.jetbrains.com/docs/intellij/plugin-dependencies.html # Example: platformPlugins = com.intellij.java, com.jetbrains.php:203.4449.22 platformPlugins = # Gradle Releases -> https://github.com/gradle/gradle/releases gradleVersion = 8.7 # Opt-out flag for bundling Kotlin standard library -> https://jb.gg/intellij-platform-kotlin-stdlib kotlin.stdlib.default.dependency = false # Enable Gradle Configuration Cache -> https://docs.gradle.org/current/userguide/configuration_cache.html org.gradle.configuration-cache = true # Enable Gradle Build Cache -> https://docs.gradle.org/current/userguide/build_cache.html org.gradle.caching = true
Затем открываем plugin.xml

и заменяем содержимое на:
<!-- Plugin Configuration File. Read more: https://plugins.jetbrains.com/docs/intellij/plugin-configuration-file.html --> <idea-plugin> <!-- Unique identifier of the plugin. It should be FQN. It cannot be changed between the plugin versions. --> <id>com.name.your_feature_name</id> <!-- Public plugin name should be written in Title Case. Guidelines: https://plugins.jetbrains.com/docs/marketplace/plugin-overview-page.html#plugin-name --> <name>Your_feature_name</name> <!-- A displayed Vendor name or Organization ID displayed on the Plugins Page. --> <vendor email="support@yourcompany.com" url="https://www.yourcompany.com">YourCompany</vendor> <!-- Description of the plugin displayed on the Plugin Page and IDE Plugin Manager. Simple HTML elements (text formatting, paragraphs, and lists) can be added inside of <![CDATA[ ]]> tag. Guidelines: https://plugins.jetbrains.com/docs/marketplace/plugin-overview-page.html#plugin-description --> <description><![CDATA[ Enter short description for your plugin here.<br> <em>most HTML tags may be used</em> ]]></description> <!-- Product and plugin compatibility requirements. Read more: https://plugins.jetbrains.com/docs/intellij/plugin-compatibility.html --> <depends>com.intellij.modules.platform</depends> <depends>org.jetbrains.kotlin</depends> <!-- Extension points defined by the plugin. Read more: https://plugins.jetbrains.com/docs/intellij/plugin-extension-points.html --> <extensions defaultExtensionNs="com.intellij"> </extensions> <!-- тут описываются команды или слушатели ide на которые должен реалигровать плагин В нашем примере мы создаим одно действие для генерации шаблона --> <actions> <action id="GenerateCleanCode.NewAction" icon="/META-INF/pluginIcon.svg" class="com.your_feature_name.GenerateFolderStructureAction" text="Create New Feature"> <add-to-group group-id="NewGroup" anchor="last"/> </action> </actions> </idea-plugin>
group-id=»NewGroup» добавляет наше действие в группу “new” при взаимодействии с директориями, а anchor=»last» добавляет наше действие последним в списке.
В параметр icon можно добавить свою svg-иконку, которая будет использоваться как в магазине приложений, так и в самой IDE при выборе действия.
В данный момент IDE будет ругаться на параметр class — мы его создадим в следующем шаге.
Шаг 3. Код плагина
Создадим класс InputDialog

и наполним его:
import com.intellij.openapi.ui.DialogWrapper import com.intellij.openapi.ui.Messages import java.awt.* import javax.swing.JComponent import javax.swing.JLabel import javax.swing.JPanel import javax.swing.JTextField class InputDialog : DialogWrapper(null) { private val textField = JTextField(20) var featureName = "" init { init() title = "Enter Feature Name" } override fun createCenterPanel(): JComponent { val panel = JPanel(BorderLayout()) panel.add(JLabel("Feature Name:"), BorderLayout.WEST) panel.add(textField, BorderLayout.CENTER) return panel } override fun getPreferredSize(): Dimension { return Dimension(300, 100) } override fun doOKAction() { featureName = textField.text.trim() if (featureName.isEmpty()) { Messages.showErrorDialog("Feature name cannot be empty.", "Error") return } super.doOKAction() } }
Этот диалог будет вызываться при нажатии нашего действия и будет выглядеть вот так:

Следом создаем GenerateFolderStructureAction

и наполняем его:
package com.your_feature_name import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.PlatformDataKeys import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.ui.Messages import com.intellij.openapi.vfs.VirtualFile import java.util.* class GenerateFolderStructureAction : AnAction() { override fun actionPerformed(event: AnActionEvent) { // тут мы используем созданный нами ранее диалог val dialog = InputDialog() // показываем его dialog.show() ApplicationManager.getApplication().runWriteAction { if (dialog.isOK) { // достаем введенное пользователем название val featureName = dialog.featureName // получаем системный путь где начать создание шаблона val libDir = event.getData(PlatformDataKeys.VIRTUAL_FILE) generateFolderStructure(libDir, featureName) } } } private fun generateFolderStructure(libDir: VirtualFile?, featureName: String) { if (libDir != null) { // создаем подпапку с введеным пользователем названием val featureDir = libDir.createChildDirectory(null, featureName) // data val dataDir = featureDir.createChildDirectory(null, "data") // в созданной директории создаем файл "${featureName}_data_source.dart" - расширение файла и его содержимое могут быть любыми // .createChildData создает файл с указаным именем и расширениием // .setBinaryContent записывает нужный нам текст внутрь созданного файла dataDir.createChildDirectory(null, "data_source").createChildData(null, "${featureName}_data_source.dart") .setBinaryContent(getDataSourceContent(featureName).toByteArray()) // ... тут можно дальше создавать новые директории и файлы // показываем диалог с успешным завершением генерации шаблона showToastMessage("Generated Successfully!") } } private fun String.toCamelCase(): String { return this.split("_") .joinToString("") { it.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() } } } private fun showToastMessage(message: String) { ApplicationManager.getApplication().invokeLater { Messages.showMessageDialog(message, "Success", Messages.getInformationIcon()) } } private fun getDataSourceContent(featureName: String): String { // при записи в файл | - указывает начало строки, чтобы у содержимого сохранилось форматирование return """ |final class ${featureName.toCamelCase()}DataSource { | const ${featureName.toCamelCase()}DataSource({required NetworkService service}) : _service = service; | | final NetworkService _service; | | } | """.trimMargin() } }
Шаг 4. Запуск и сборка
Для того, чтобы проверить плагин, в верхнем правом углу будет команда run plugin.

Она запустит новое окно IDE с включенным в него вашим плагином. Если плагин отработал корректно, пришло время сборки и публикации.
Создаем новое действие Gradle, а в команде выбираем runPluginVerifier:

Запускаем новый скрипт:

И исправляем ошибки — в том случае, если они будут.
Если скрипт прошел успешно, то в папке build/distributions будет лежать .zip архив с вашим плагином.

Если вы хотите поделиться плагином локально, данный архив можно установить без публикации, через команду install Plugin from Disk в самой студии.

Шаг 5. Публикация
Создаем аккаунт в https://plugins.jetbrains.com/ и выбираем upload plugin:

Заполняем поля, делаем скриншоты работы нашего плагина и следуем дальнейшим подсказкам от jetbrains.

Ревью и публикация плагина занимает два-три рабочих дня, так что наберитесь терпения.
В данном туториале мы создали базовый плагин для создания файловых структур и собрали его для локального использования или публикации.
Ознакомиться с реализацией можно на GitHub: https://github.com/Skleprozzz/intellij_flutter_clean_feature
Магазин плагинов JetBrains: https://plugins.jetbrains.com/plugin/24862-flutter-clean-feature
Надеюсь, дальнейшее развитие вашего плагина пойдет проще!
ссылка на оригинал статьи https://habr.com/ru/articles/880028/
Добавить комментарий