Большинство из нас, разработчиков, используют платформы IntelliJ: IDEA, PHPStorm, WebStorm, Android Studio, PyCharm, и этот список можно продолжать и продолжать. Однако иногда, когда мы его используем, мы обнаруживаем, что какая-то функция отсутствует, но мы не знаем, как на самом деле добавить эту функцию и в конечном итоге просто жить без нее.

В этой статье я расскажу, как создать простой плагин для всех IDE IntelliJ, чтобы при добавлении файла project.dic он автоматически добавлялся как один из ваших словарей. Он также будет искать файл в пакетах, поэтому пакеты могут добавлять пользовательские слова в словарь. Файл .dic - это простой словарь, где каждая строка - это слово в словаре.

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

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

При создании плагинов для IntelliJ мы должны сделать это на Java или Kotlin. Я сделаю это на Java, поскольку большинство пользователей с этим знакомо. Поскольку это проект Java, мы будем использовать IntelliJ IDEA в качестве нашей IDE.

Согласно руководству по разработке, рекомендуемый способ создания проекта - использование Gradle. Мы начинаем с открытия preferences и проверяем, установлены ли плагины Gradle и Plugin DevKit.

После установки плагинов и перезапуска IDE мы переходим к потоку новых проектов и в Gradle. Здесь теперь есть опция под названием IntelliJ Platform Plugin, которая нам нужна.

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

Настройка plugin.xml

Теперь, когда у нас есть проект, нам нужно настроить наш plugin.xml файл и build.gradle. Файл plugin.xml - это файл, используемый IntelliJ, который определяет всю информацию о плагине. Это включает в себя имя, зависимости, какие действия он должен добавить или должен ли он что-то расширять в IntelliJ. По сути, этот файл определяет все, что должен делать ваш плагин, и является корнем вашего проекта. В нашем build.gradle файле мы можем определить некоторые значения из plugin.xml и информацию, например, на какой версии IntelliJ мы хотим протестировать наш плагин при сборке с помощью gradle.

Начнем с определения нашего plugin.xml файла. Вы можете найти файл в src/main/resources/META-INF/plugin.xml. Мы хотим, чтобы наш плагин был доступен во всех IntelliJ IDE, поэтому мы установили для нашего dependencies значение com.intellij.modules.lang. Сейчас наш файл выглядит так:

<idea-plugin>
    <id>dk.lost_world.Dictionary</id>
    <name>Dictionary</name>
    <vendor email="[email protected]" url="https://github.com/olivernybroe/intellij-Dictionary">GitHub</vendor>

    <depends>com.intellij.modules.lang</depends>
</idea-plugin>

Однако сейчас в этом нет никакой логики, и мы ничего не регистрируем на платформе IntelliJ.

Поскольку этот проект найдет project.dic файлов внутри проекта и зарегистрирует их как словари в этом проекте, нам нужно будет зарегистрировать компонент уровня проекта. Этот компонент будет вызываться при открытии и закрытии проекта. Давайте создадим класс и реализуем ProjectComponent интерфейс. Когда мы наводим курсор на имя класса, это говорит нам, что компонент не зарегистрирован.

Затем мы можем вызвать действие с именем Register Project Component, и оно зарегистрирует его для нас в файле plugin.xml.

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

<project-components>
    <component>
        <implementation-class>dk.lost_world.dictionary.DictionaryProjectComponent</implementation-class>
    </component>
</project-components>

Файловая система IntelliJ

При работе с файлами в IntelliJ мы используем V виртуальную F файловую систему (VFS). VFS дает нам универсальный API для работы с файлами, при этом нам не нужно думать о том, откуда они: с FTP, HTTP-сервера или просто на локальном диске.

Поскольку наш подключаемый модуль ищет файлы с именем project.dic, ему, конечно же, потребуется взаимодействовать с системой виртуального F файла V. Все файлы в VFS - это виртуальные файлы. Это может показаться немного пугающим, но на самом деле это всего лишь API для файловой системы и файла. Можно подумать о том, что виртуальная F файловая система - это интерфейс вашей файловой системы, а виртуальные файлы - это ваши файлы.

Настройки проверки орфографии

Поскольку IntelliJ уже поддерживает .dic файлы и проверку орфографии в целом, единственное, что нам нужно сделать, это зарегистрировать наши project.dic файлы в настройках проверки орфографии.

Все настройки для проверки орфографии сохраняются в классе с именем com.intellij.spellchecker.settings.SpellCheckerSettings. Чтобы получить его экземпляр, просто вызовите метод getInstance (большинство классов IntelliJ получили метод getInstance, который использует IntelliJ ServiceManager внизу).
Класс настроек получил метод getCustomDictionariesPaths, который возвращает все пути к словарям, которые устанавливаются пользователем.

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

Поскольку метод возвращает список, мы можем просто вызвать add метода, чтобы добавить новый путь к словарю.

Запуск нашего плагина (build.gradle)

Теперь, когда мы знаем, как добавить словарь в средство проверки орфографии, давайте добавим небольшой пример кода в наш класс DictionaryProjectComponent для этого.

public class DictionaryProjectComponent implements ProjectComponent {
    private Project project;

    public DictionaryProjectComponent(Project project) {
        this.project = project;
    }

    @Override
    public void projectOpened() {
        SpellCheckerSettings
            .getInstance(project)
            .getCustomDictionariesPaths()
            .add("./project.dic");
    }
}

Этот код будет регистрировать project.dic файл из корня нашего проекта при каждом открытии проекта.

Чтобы протестировать наш небольшой пример, нам нужно обновить наш build.gradle файл. В разделе intellij файла gradle мы добавляем, какую версию IntelliJ мы хотим использовать. Этот номер версии указан в аннотации AvailableSince к классу SpellCheckerSettings.

plugins {
    id 'java'
    id 'org.jetbrains.intellij' version '0.4.4'
}

group 'dk.lost_world'
version '1.0-SNAPSHOT'

sourceCompatibility = 1.8

repositories {
    mavenCentral()
}

dependencies {
    testCompile group: 'junit', name: 'junit', version: '4.12'
}

// See https://github.com/JetBrains/gradle-intellij-plugin/
intellij {
    pluginName 'Dictionary'
    version '181.2784.17'
    type 'IC'
    downloadSources true
}

Выполнение команды runIde из gradle запустит экземпляр IntelliJ конкретной версии. После запуска тестовой IDE наш плагин должен был запуститься. Если мы откроем preferences > Editor > Spelling > Dictionaries, мы увидим под пользовательскими словарями, что путь, который мы указали в нашем примере, теперь добавлен.

Теперь мы можем протестировать наш плагин, так что пришло время правильно его собрать, чтобы он нашел project.dic файлы и зарегистрировал их для нас.

В методе DictionaryProjectComponent::projectOpened нам нужно сначала найти все файлы с именем project.dic и зарегистрировать их, а также добавить прослушиватель файлов, чтобы при добавлении новых project.dic файлов они регистрировались автоматически.

Класс словаря

У нас будет класс с именем Dictionary, этот класс будет содержать логику для регистрации и удаления файлов из словаря. У класса будут следующие общедоступные методы:
void registerAndNotify(Collection<VirtualFile> files)
void registerAndNotify(VirtualFile file)
void removeAndNotify(VirtualFile file)
void moveAndNotify(VirtualFile oldFile, VirtualFile newFile)

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

Поиск всех файлов словарей

Для поиска всех файлов словарей в проекте project.dic мы используем класс FilenameIndex. Файл находится в пространстве имен com.intellij.psi.search.FilenameIndex, у него есть метод getVirtualFilesByName, который мы можем использовать для поиска наших project.dic файлов.

FilenameIndex.getVirtualFilesByName(
    project,
    "project.dic",
    false,
    GlobalSearchScope.allScope(project)
)

Этот вызов вернет все виртуальные файлы, соответствующие критериям поиска. Затем мы помещаем возвращаемый результат в метод класса Dictionary registerAndNotify.

@Override
public void projectOpened() {
    Dictionary dictionary = new Dictionary(project);

    dictionary.registerAndNotify(
        FilenameIndex.getVirtualFilesByName(
            project,
            "project.dic",
            false,
            GlobalSearchScope.allScope(project)
        )
    );
}

Теперь наш код может находить project.dic файлы при запуске и регистрировать их, если они еще не зарегистрированы. Он также будет уведомлять о вновь зарегистрированных файлах.

Добавление прослушивателя виртуальных файлов

Следующая часть предназначена для отслеживания изменений в виртуальных файлах. Для этого нам нужен слушатель. Для этого нам понадобится com.intellij.openapi.vfs.VirtualFileListener.

В док-блоке для класса слушателя мы видим, что для его регистрации мы можем использовать VirtualFilemanager#addVirtualFileListener.
Давайте создадим класс с именем DictionaryFileListener и реализуем методы, которые нам нужны для нашего проекта.

Затем мы обновляем наш класс projectOpened, чтобы также добавить VirtualFileListener.

@Override
public void projectOpened() {
    Dictionary dictionary = new Dictionary(project);

    dictionary.registerAndNotify(
        FilenameIndex.getVirtualFilesByName(
            project,
            "project.dic",
            false,
            GlobalSearchScope.allScope(project)
        )
    );

    VirtualFileManager.getInstance().addVirtualFileListener(
        new DictionaryFileListener(dictionary)
    );
}

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

Добавление информации о плагине

Чтобы добавить информацию о плагине, открываем файл build.gradle и редактируем объект patchPluginXml. Здесь нам нужно указать, какая версия сборки требуется для плагина, версия плагина, описание и примечания к изменениям.

patchPluginXml {
    sinceBuild intellij.version
    untilBuild null
    version project.version
    pluginDescription """
Plugin for having a shared dictionary for all members of your project. <br>
<br>
It will automatically find any <code>project.dic</code> files and add them
to the list of dictionaries. <br>
<br>
It will also search packages for dictionary files and add them to our list of dictionaries.
    """
    changeNotes """
<p>0.2</p>
<ul>
    <li>Added support for listening for when a <code>project.dic</code> file is added, moved, deleted, copied.</li>
</ul>
<p>0.1</p>
<ul>
    <li>First edition of the plugin.</li>
</ul>
    """
}

Мы также обновляем свойство version до '0.2' самого проекта gradle. Плагин теперь может работать во всех версиях, так как был добавлен метод регистрации пользовательских словарей.

Чтобы проверить, генерирует ли он желаемый результат, мы можем запустить задачу gradle patchPluginXml, и в build/patchedPluginXmlFiles наш сгенерированный файл plugin.xml будет там.

Начиная с версии IntelliJ 2019.1, все плагины поддерживают значки. Поскольку это довольно новый вариант, у многих плагинов нет значка, и ваш плагин может сильно выделяться, имея его. Соглашение об именах pluginIcon.svg в качестве значка по умолчанию и pluginIcon_dark.svg для темы даркула.

Значки плагинов должны быть перечислены вместе с файлом plugin.xml в пути resources/META-INF.

Здание для распространения

Теперь плагин готов к сборке и отправке. Для этого мы запускаем задачу gradle buildPlugin. В разделе build/distributions появится zip-файл, который вы можете распространять и устанавливать вручную в своей среде IDE. Добавьте этот zip-файл в качестве релиза в репозиторий github, чтобы у пользователей была возможность загрузить его вручную из вашего репозитория.

Публикация плагина

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

Введите всю информацию в диалоговом окне (вы должны добавить лицензию, но это довольно просто с Github). Сюда мы добавляем zip-файл с дистрибутивом.

Когда вы отправляете форму, теперь вы можете увидеть свой плагин в репозитории плагинов. Однако другие пользователи не имеют к нему доступа до того, как IntelliJ одобрит его. Утверждение плагина обычно занимает 2–3 дня.

Обновление вашего плагина через Gradle

После создания плагина мы можем обновить его программно. Для этого лучше всего создать токен. Откройте хаб jetbrains и перейдите на вкладку аутентификация. Отсюда нажмите New token... и добавьте область Plugin Repository.

При нажатии create вы получаете токен. Создайте файл с именем gradle.properties и добавьте токен под ключом intellijPublishToken (не забудьте git игнорировать этот файл).

В нашем build.gradle файле мы просто добавляем следующее:

publishPlugin {
    token intellijPublishToken
}

И теперь мы можем запустить задачу gradle publishPlugin для публикации нашей новой версии. Все номера версий должны быть уникальными, иначе обновление не удастся. После создания обновления вам придется снова подождать 2–3 дня, пока они не утвердят обновление.

Подождав несколько дней, наш плагин был одобрен, и теперь его можно найти на торговой площадке плагинов, выполнив поиск по словарю!

Заключение

Я надеюсь, что эта статья придала вам смелости начать разработку собственных плагинов. Одна из самых больших проблем, с которыми я столкнулся при его разработке, заключалась в том, чтобы выяснить, какие классы использовать. У IntelliJ есть обширное руководство, которое я бы рекомендовал вам прочитать от начала до конца, однако многие классы в нем не упоминаются. В тех случаях, когда вы застреваете, у них есть чат Gitter, который действительно полезен, и есть люди из IntelliJ, которые также могут помочь.

Исходный код этого проекта можно найти на Github, а созданный нами плагин - на JetBrains marketplace.