Pull to refresh

Как создать плагин для IntelliJ IDEA на примере генератора директорий для проекта

Level of difficultyEasy
Reading time6 min
Views2.1K

Меня зовут Александр Мамонов, и в 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

Магазин плагинов JetBrains

Надеюсь, дальнейшее развитие вашего плагина пойдет проще!

Tags:
Hubs:
Total votes 6: ↑5 and ↓1+6
Comments0

Articles