Доброго дня, читатель! Меня зовут Симонова Анастасия, я Android‑разработчик в команде мобильного приложения Домклик.
В современном мире разработка программного обеспечения становится всё более сложной и многообразной. Одним из ключевых инструментов, используемых для создания мобильных приложений, является Android Studio. И помимо стандартных возможностей, Android Studio позволяет разработчикам расширять функциональность своей среды с помощью плагинов. Их использование имеет несколько направлений:
автоматизация рутинных задач;
интеграция дополнительных инструментов и библиотек;
настройка интерфейса под специфические нужды команды.
Оглавление
Инструменты для разработки плагинов
В контексте разработки под Android и JetBrains Marketplace, «плагины» — это модульные расширения, которые добавляют функциональность в среду разработки (IDE), такую как Android Studio. Для начала напомню, где их найти:
Для разработки плагинов нужно всего три вещи:
IntelliJ IDEA, минимум Community Edition. Можно работать и в Ultimate‑версии, но особых преимуществ при разработке плагинов это не даст.
Подключенный к ней Plugin DevKit — специальный плагин, который добавляет возможность писать другие плагины.
И любой JVM‑ный язык, на котором вы хотите писать плагин. Это может быть Kotlin, Java, Groovy — что угодно.
Базовые компоненты
Начинаем с создания проекта плагина. Выбираем New project, пункт Gradle, отмечаем галочкой IntelliJ Platform Plugin и создаём проект.
Видим проект плагина по умолчанию, который состоит из:
Папок, в которых вы будете писать код будущего проекта (main/java, main/kotlin, etc)
Файла build.gradle, в котором вы будете объявлять зависимости вашего плагина от каких‑то библиотек
Файла plugin.xml. В нём содержится необходимая метаинформация о вашем плагине: идентификатор, название, описание, сведения о вендоре, change log, описание зависимостей от других плагинов. Также здесь описывают actions и extensions. Actions позволяет создавать удобные для пользователя действия в интерфейсе, в то время как extensions позволяет интегрировать новую функциональность и взаимодействовать с различными точками расширения Android Studio.
Разработка плагина для проверки орфографии в тексте
Я задумалась: а для решения какой реальной проблемы можно написать свой плагин, но так, чтобы не изобретать велосипед? Ведь на миллион проблем сообщества Android‑разработчиков уже придуманы разные решения.
Прошло несколько недель, и мне присылают скриншот с орфографической ошибкой в тексте. И тут пазл складывается, у меня возникает идея…
Я увидела такую проблему: Разработчики и ревьюверы кода часто не замечают орфографические ошибки в текстах, видимых пользователю. Ошибки в тексте могут негативно сказываться на пользовательском опыте и приводить к низким отзывам о приложении.
И решение: написать плагин для автоматизации проверки орфографии в тексте.
Поставила я себе следующее техническое задание:
Функциональность плагина должна работать таким образом: перед каждым
git commit
должен запускаться механизм, который автоматически проверяет тексты, включённые в изменения, представленные вgit diff
. В случае обнаружения ошибок разработчик получает уведомление, которое позволяет быстро обратить внимание на проблемы и устранить их до завершения коммита.
Естественно, я решила поискать готовые решения.
Russian spell checker
Первым решением, которое я обнаружила, стал плагин Russian spell checker. Он проверяет выбранный текст и показывает подсказку для быстрого исправления.
У плагина есть два недостатка:
Он использует Yandex Speller (технологию поиска и исправления ошибок) для проверки орфографии русскоязычного текста. Казалось бы, машинное обучение, последние инновационные технологии, будущее…
Чтобы запустить проверку, нужно нажимать на определённые команды, то есть проверка не обязательна и не автоматическая, скажем честно — на усмотрение разработчика, а значит её можно игнорировать…
Использование машинного обучения из сторонних библиотеĸ или решений может нести значительные рисĸи для вашей компании. Корпоративные данные часто содержат чувствительную информацию, и передача их для обучения может привести к утечкам или несанкционированному доступу. Поэтому вряд ли кибербезопасность вашей компании одобрит применение таких решений.
Если взглянуть на Yandex Speller внимательнее, то мы увидим библиотеку машинного обучения CatBoost. Благодаря её применению Спеллер может расшифровывать искажённые до неузнаваемости слова («адникасниеи» → «одноклассники») и учитывать контекст при поиске опечаток («скучать музыку» → «скачать музыку»).
Grazie Lite
Также я обнаружила плагин Grazie Lite. Как уверяют авторы : "вся проверка текстов выполняется локально, внутри вашей IDE." В качестве механизма проверки ошибок используется библиотека LanguageTool.
В своей основной реализации LanguageTool для проверки грамматики и стиля текста использует набор правил и паттернов. Да, тоже есть версии с применением нейронок, но нужно настраивать конфигурацию определённым образом, чтобы они работали над проверкой текста.
Ошибочные слова будут подчеркнуты, и при наведении курсора на них появится подсказка с исправлением.
Таким образом у данного плагина есть только 1 недостаток: проверка не обязательна и зависит от желания разработчика, поэтому данный плагин оставляет возможность не заметить ошибку.
Собственное решение
Окей, тогда давайте напишем свой плагин! GitHub проекта тут.
Возможно, моё решение будет не такое универсальное и хайповое, но оно будет достаточно практичным для интеграции в компанию.
Машинное обучение в решении Яндекса применяется для определения контекста и формирования рекомендаций по исправлению сильно искажённого текста. Однако, возможно, в нашем случае это не так необходимо, поскольку тексты мы копируем из Figma, и дизайнеры, как правило, для текстов привлекают копирайтеров, которые не допускают ошибок в контексте и не искажают слова. Могут возникать лишь небольшие опечатки. Кроме того, хотелось бы иметь решение, которое будет активироваться в «добровольно‑принудительном порядке». Для этого необходимо интегрировать его в какой‑либо обязательный процесс, например, в выполнение команд Git.
В качестве механизма для проверки орфографии возьмем LanguageTool как и у плагина Grazie Lite, который будет работать локально, а следовательно безопасно для конфиденциальной информации компании.
Создаем проект, открываем plugin.xml и настраиваем:
<idea-plugin>
<id>com.spellchecker</id>
<name>PreCommitSpellChecker</name>
<vendor url="https://github.com/simonovaa97" email="simonovaaanastasia97@gmail.com">Анастасия Симонова</vendor>
<description><![CDATA[
Plugin for checking spelling in text before git committing changes. The Languagetool library was used.<br>
]]></description>
<depends>com.intellij.modules.platform</depends>
<extensions defaultExtensionNs="com.intellij">
<checkinHandlerFactory implementation="com.spellchecker.PreCommitSpellCheckFactory"/>
</extensions>
</idea-plugin>
Определяем идентификатор, название, описание, информацию о вендоре, зависимости. Зависимость com.intellij.modules.platform
указывает, что плагин требует базовые модули платформы IntelliJ IDEA для работы.
Тег <checkinHandlerFactory …/>
используется для указания обработчиков проверки кода. Они управляют процессом интеграции изменений в систему контроля версий. Получается, что мы можем встроиться в этот процесс для выполнения своих различных проверок и валидаций. Когда пользователь пытается внести изменения в систему контроля версий, например, коммит, будет вызван указанный обработчик. Он будет запускаться по кнопке из Студии, но не будет исполняться по команде коммита из терминала! Плагины Android Studio работают в контексте самой IDE и могут обрабатывать события, происходящие в ней. Когда вы делаете коммит через терминал, это событие не обрабатывается плагином, так как оно не связано с интерфейсом Android Studio.
Окей, а как тогда реализовать проверку коммита из терминала? Для этого нужно использовать хуки системы контроля версий. Они позволяют выполнять скрипты до или после определённых действий в Git.
Создаём новый класс: возвращаемся к CheckinHandlerFactory
:
Видим, что существует множество методов, которые позволяют нам внедрить собственные условия в процесс проверки изменений для системы контроля версий. Нас интересует метод beforeCheckin
, в который мы добавим проверку орфографии, и в случае ошибок отклоним коммит. А также давайте ознакомимся со структурой нашего обработчика.
class PreCommitSpellCheckFactory : CheckinHandlerFactory() {
override fun createHandler(panel: CheckinProjectPanel, commitContext: CommitContext): CheckinHandler {
return PreCommitSpellCheckFactoryHandler(panel)
}
}
class PreCommitSpellCheckFactoryHandler(private val panel: CheckinProjectPanel) : CheckinHandler() {
private val languageTool = JLanguageTool(Russian()).apply {
disableRule("UPPERCASE_SENTENCE_START")
}
override fun beforeCheckin(): ReturnResult {
val warnings = getSpellingWarnings(
ChangeListManager.getInstance(panel.project).changeLists
)
if (warnings.isNotEmpty()) {
showWarningDialog(warnings)
return ReturnResult.CANCEL
}
return ReturnResult.COMMIT
}
private fun getSpellingWarnings(changes: List<LocalChangeList>): String {...}
private fun checkCorrectText(text: String): Boolean {...}
private fun showWarningDialog(warningText: String) {...}
private fun forceCommit() {...}
}
Посмотрим подробнее на саму проверку. Тут всё просто: проверяем, были ли найдены ошибки для нашего текста. Для этого у languageTool вызовем метод check(string)
, который вернёт список сопоставлений ошибочного текста с правилами проверки. И если тот окажется пуст, то текст корректный.
private val languageTool = JLanguageTool(Russian()).apply {
disableRule("UPPERCASE_SENTENCE_START") // Для languageTool можно настраивать правила
}
. . .
private fun checkCorrectText(text: String): Boolean {
val htmlTagRegex = Regex("<.*?>")
val whitespaceRegex = Regex("[\\n\\t\\r]+")
val filteredText = text
.replace(htmlTagRegex, "")
.replace(whitespaceRegex, " ")
.trim() // очистим текст от ненужных символов
val matches: List<RuleMatch> = languageTool.check(filteredText)
return matches.isEmpty()
}
Для того, чтобы проверить текст, нам нужно его откуда‑то получить. В следующем методе мы извлекаем git diff
. Для проверок будем ориентироваться на измененный или новый текст (DeltaType.CHANGE
, DeltaType.INSERT
), а также будем фильтровать сами файлы и выбирать только текстовые ресурсы.
private fun getSpellingWarnings(changes: List<LocalChangeList>): String {
val textForChecking = StringBuilder()
for (changeList in changes) {
val changesBeforeCommit = changeList.changes
for (change in changesBeforeCommit) {
val beforeRevision: ContentRevision? = change.beforeRevision
val afterRevision: ContentRevision? = change.afterRevision
val beforeContent = beforeRevision?.content
val afterContent = afterRevision?.content
val filePath = beforeRevision?.file?.path ?: afterRevision?.file?.path
if (filePath != null && filePath.endsWith(".xml") && filePath.contains("/values/")) {
val diff = DiffUtils.diff(beforeContent?.split("\n"), afterContent?.split("\n"))
diff.deltas.forEach { deltas ->
when (deltas?.type) {
DeltaType.CHANGE, DeltaType.INSERT -> {
deltas.target.lines?.forEach { text ->
val isCorrectText = checkCorrectText(text)
if (isCorrectText.not()) textForChecking.appendLine(text.trim())
}
}
else -> {}
}
}
}
}
}
return textForChecking.toString()
}
Далее создаём диалоговое окно для пользователя с использованием объекта DialogWrapper
. Реализуем простое окно с двумя кнопками «Проверить» и «Пропустить».
private fun showWarningDialog(warningText: String) {
val dialog = object : DialogWrapper(true) {
init { init() }
override fun createCenterPanel(): JComponent {
val panelUi = JPanel()
panelUi.layout = BorderLayout()
val textArea = JTextArea(warningText)
textArea.setSize(600, 600)
textArea.isEditable = false
textArea.wrapStyleWord = true
textArea.lineWrap = true
panelUi.add(textArea, BorderLayout.CENTER)
val buttonPanel = JPanel()
buttonPanel.layout = FlowLayout()
val yesButton = JButton("Проверить")
yesButton.addActionListener { close(0) }
val noButton = JButton("Пропустить")
noButton.addActionListener {
forceCommit()
close(1)
}
buttonPanel.add(yesButton)
buttonPanel.add(noButton)
panelUi.add(buttonPanel, BorderLayout.SOUTH)
return panelUi
}
override fun createActions(): Array<Action> { return emptyArray() }
}
dialog.show()
}
Интерфейс должен быть user friendly, для этого нужно добавить возможность принудительного коммита forceCommit()
. Например, корпоративная лексика или различные сокращения не будут проходить проверку нашим обработчиком, и в таком случае мы не должны мешать разработчику сохранять свои действия.
Билдим, получаем артефакт zip/jar (находится в build), открываем Android Studio. Подключаем наш плагин:
Теперь видим его в списке плагинов.
Давайте проверим его работоспособность! Специально делаем ошибки в двух последних строках.
Нажимает на кнопку «Commit».
Видим уведомление о том, что есть проблемы с текстом. Ура! Всё работает!
А что с производительностью?
Проверка строится не на всём проекте, а только на дифе
change
иinsert
.Проверка применятся для
filePath.endsWith(".xml") && filePath.contains("/values/")
.Замедление коммита максимум на 10 %.
Публикация в маркетплейсе
После достижения одной цели и получения от неё эмоций, появляется следующая: публикация плагина в маркетплейсе JetBrains.
Для начала зафиксируем название «PreCommitSpellChecker» и логотип для плагина.
После регистрации открывается возможность загрузки собственного плагина.
После чего необходимо заполнить данные о вендоре и плагине.
Если всё будет сделано по инструкции, то плагин будет загружен и отправлен на проверку, которая обычно занимает два рабочих дня.
Уже через день мне пришло письмо о том, что публикация прошла успешно.
И теперь мой плагин можно найти в маркетплейсе.
Заключение
В будущем планирую добавить в плагин PreCommitSpellChecker:
настройку определённых правил проверок под нужды команды;
проверку корректности имён классов, методов и переменных;
улучшить интерфейс диалогового окна с ошибками;
возможно, добавлю ИИ‑модель на серверах компании для использования всех возможностей библиотеки LanguageTool;
добавить плагин в репозиторий проекта, чтобы он автоматически подключался разработчикам в Android Studio.
Разработка плагина PreCommitSpellChecker открыла новые возможности для команды. Теперь можно интегрировать собственные проверки прямо в среду разработки. Это позволяет разработчикам проводить локальные проверки перед коммитом, что повышает стабильность приложения и уменьшает риск ошибок в основной ветке. Такой подход улучшает качество кода и упрощает работу. Команда может больше внимания уделять созданию новых функций, а не исправлению старых проблем. В итоге команда становится более эффективной и быстрее реагирует на изменения, что приводит к лучшему качеству продукта и удовлетворённости пользователей.
Разработка собственных плагинов для Android Studio не только улучшает ваши навыки программирования, но и помогает внести свой вклад в сообщество разработчиков, а это приятно :)
Beanut
И потом андроид программист должен поправить вручную этот текст и ... что потом? Написать письмо iOS разработчикам чтобы они тоже поправили? Или кому? Почему орфография вдруг стала проблемой разработчиков только одной платформы и только на этапе комитта? Мне кажется тут фундаментальная ошибка в процессах, а плагин тут только для того чтобы научиться писать плагины.