Разработка плагина IntelliJ IDEA. Часть 6

от автора

В этой части: рефакторинги, форматирование, настройки и другие полезные функции. Предыдущая часть.

Рефакторинг «Переименование»

Операция переименования в IntelliJ IDEA похожа на «Find Usages», IDEA использует те же правила для поиска элементов для переименования и тот же индекс слов для нахождения файлов, в которых могут быть ссылки на элемент, который будет переименован.

Когда выполняется этот рефакторинг, на целевом элементе вызывается метод PsiNamedElement.setName(), а для всех ссылок на него – метод PsiReference.handleElementRename(). Оба метода выполняют одно основное действие – замену нижележащего AST-узла, новым, содержащим введенный пользователем текст. Создание полностью корректного AST бывает довольно сложным, но можно воспользоваться следующим методом: создать фиктивный файл пользовательского языка, содержащий необходимый узел, и затем скопировать его.

Пример: реализация метода setName() в плагине Properties.

Другой, связанный с рефакторингом по переименованию интерфейс – NamesValidator. Этот интерфейс позволяет плагину проверять, корректно ли введено пользователем имя идентификатора, т.е. соответствует правилам именования в пользовательском языке и не совпадает с ключевым словом. Если реализация интерфейса не предоставляется плагином, то используются правила именования Java. Реализация должна быть зарегистрирована в точке расширения com.intellij.lang.namesValidator.

Пример: NamesValidator для Properties.

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

Пример: RenameHandler для переименования resource bundle.

Если не требуется переопределять стандартный UI, но необходимо расширить логику переименования, можно предоставить собственную реализацию интерфейса RenamePsiElementProcessor. Что позволяет:

  • Переименовать элемент, отличный от того, над которым было выполнено действие (например, метод базового класса);
  • Переименовать несколько элементов одновременно (если их имена логически связаны, но нужно самостоятельно следить за коллизиями);
  • Переопределить логику поиска ссылок в тексте;
  • И тому подобное.

Пример: RenamePsiElementProcesssor для переименования Property.

Рефакторинг «Безопасное удаление»

Рефакторинг по безопасному удалению тоже построен поверх фреймворка «Find Usages». Для того чтобы использовать безопасное удаление должны быть реализованы две вещи:

  • интерфейс RefactoringSupportProvider, зарегистрированный в точке расширения com.intellij.lang.refactoringSupport и метод isSafeDeleteAvailable(), проверяющий доступность рефакторинга для данного PSI-элемента;
  • методы PsiElement.delete() для элементов, разрешенных в предыдущем пункте. Удаление PSI-элемента реализуется как удаление нижележащих узлов абстрактного синтаксического дерева (пример).

Если требуется настроить поведение рефакторинга для определенных типов элементов, то в этом может помочь интерфейс SafeDeleteProcessorDelegate.

Пример: реализация SafeDeleteProcessorDelegate для файлов *.properties.

Автоматическое форматирование кода

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

Процесс форматирования файла или фрагмента состоит из следующих основных шагов:

  • реализация FormattingModelBuilder, предоставляющая модель форматирования для обрабатываемого документа;
  • модель форматирования выстраивает структуру файла как дерево блоков (объектов Block), с ассоциированными отступами, переносами, выравниванием;
  • движок форматирования вычисляет последовательность пробельных символов (пробелы, табуляции, переносы строк), которая должна быть помещена на границе каждого блока;
  • модель форматирования запрашивает вставку пробельных символов в необходимые позиции в файле.

Структура блоков обычно повторяет PSI-структуру файла. Форматер модифицирует только символы, находящиеся между блоками, поэтому дерево блоков должно покрывать все непробельные символы файла, в противном случае все, что находится между блоками может быть удалено.

Если операция форматирования применяется не для всего файла (например, когда форматер вызывается для вставленного блока текста), полное дерево блоков не строится – вместо этого используются блоки покрывающее текстовый диапазон, переданный для форматирования и его прямой предок.

Для каждого блока плагин должен определить следующие свойства:

  • пробельный интервал, определяющий, сколько пробелов (минимальное и максимальное количество) и переводов строк (также минимальное и максимальное число) должны находиться между дочерними блоками, а также сохранять ли уже существующие пустые строки;
  • отступ – определяет позицию относительно родительского блока. Существует несколько типов отступов: «без отступа», «обычный отступ», «продолжительный отступ». Если модель не определяет конкретный вид отступа, по-умолчанию используется «продолжительный, кроме первого элемента», т.е. первый блок в последовательности не имеет отступа, а остальные получают продолжительный;
  • переносы строк – определяет когда длинную строку переносить на следующую. Реализуется вставкой переноса строки перед содержимым блока;
  • выравнивание – определяет, как блоки будут выровнены относительно друг друга. Если для выравнивания двух блоков используется один и тот же объект Alignment и в строке перед вторым блоком только пробелы, то форматер сдвигает его, так чтобы он начинался с того же столбца, что и первый.

Особый случай использования форматера — когда пользователь плагина нажимает Enter во время редактирования исходного кода. Чтобы определить значение отступа, движок форматирования вызывает метод getChildAttributes() либо на блоке непосредственно до курсора, либо на родительском, в зависимости от возвращенного значения метода isIncomplete() на блоке до курсора. Если блок завершен, то на нем и вызывается getChildAttributes(); в противном случае вызов происходит на родительском блоке.

Настройки стиля кода

Для определения величины отступа по-умолчанию и, например, чтобы позволить пользователю настроить размер табуляции и отступов, можно реализовать интерфейс FileTypeIndentOptionsProvider, который необходимо зарегистрировать в точке расширения fileTypeIndentOptionsProvider. Метод интерфейса createIndentOptions() определяет значение по-умолчанию для отступов.

Для более гибкого управления настройками возможно зарегистрировать в точке расширения com.intellij.applicationConfigurable реализацию интерфейса Configurable, который представляет собой Java Swing форму, отображаемую в диалоге Settings.

Инспекции

Инспекции исходного кода для пользовательских языков используют тот же API, что и остальные инспекции и основываются на классе LocalInspectionTool.

Функциональность LocalInspectionTool частично дублирует Annotator, основные отличия заключаются в поддержке пакетной обработки кода (запускается действием «Analyze | Inspect Code…»), возможности отключить инспекции и настройке их опций. Если ничего из перечисленного не требуется, к примеру, анализ нужно запускать только в активном редакторе, то использование Annotator более выгодно, т.к. он обладает лучшей производительностью (благодаря поддержке инкрементального анализа) и большей гибкостью в плане подсветки ошибок.

Пример: простая инспекция для файлов .properties.

Пользовательские Intentions (quickfix) также используют стандартный API, они должны предоставлять реализацию интерфейса IntentionAction, зарегистрированную в точке расширения <intentionAction> в plugin.xml.

Пример: простой Intention для Groovy.

Отображение структуры кода

Отображение структуры кода может быть настроено на нескольких уровнях. Если языковой плагин предоставляет реализацию интерфейса StructureView, он может полностью заменить стандартный компонент. Хотя для большинства языков это не требуется и стандартный Structure View может быть переиспользован.
В первую очередь необходимо зарегистрировать реализацию интерфейса PsiStructureViewFactory в точке расширения com.intellij.lang.psiStructureViewFactory.

Пример: PsiStructureViewFactory для файлов .properties.

Для повторного использования встроенной реализации, плагин должен возвратить TreeBasedStructureViewBuilder из метода PsiStructureViewFactory.getStructureViewBuilder(). В качестве модели плагин может определить подкласс TextEditorBasedStructureViewModel, и переопределив его методы, настроить отображение структуры для пользовательского языка.

Пример: StructureViewModel для файлов .properties.

Базовый метод getRoot() возвращает экземпляр интерфейса StructureViewTreeElement. IDEA не предоставляет стандартной реализации этого интерфейса, поэтому плагин должен реализовать его полностью.

Обычно Structure View строится на основе PSI-дерева. В реализации метода StructureViewTreeElement.getChildren() должны быть представлены дочерние элементы текущего узла, которые будут отображены в структуре кода. Другой важный метод – это getPresentation(), который используется для настройки текста, атрибутов, иконки и т.д., показываемых в Structure View.

Реализация метода StructureViewTreeElement.getChildren() должна совпадать с TextEditorBasedStructureViewModel.getSuitableClasses(). Последний метод возвращает массив PsiElement, которые могут быть показаны как элементы структуры кода и использованы в качестве целевых элементов при наведении курсора, если активна опция «Autoscroll from source».

Пример: StructureViewElement для Property.

Функция «Surround With»

Для поддержки действия «Code | Surround With…», плагин должен зарегистрировать как минимум одну реализацию интерфейса SurroundDescriptor в точке расширения com.intellij.lang.surroundDescriptor. Каждый дескриптор определяет тип фрагмента кода, который может быть им обернут — например, один отвечает за выражения, второй — за обработку операторов. Каждый дескриптор содержит массив объектов Surrounder, определяющих специфичные шаблоны, используемые для оборачивания выделенного текста (например, «Surround With if», «Surround With for»).

Когда выполняется действие «Surround With…», IDEA опрашивает по очереди все дескрипторы, пока один не возвратит непустой массив из метода getElementsToSurround(). Тогда вызывается метод Surrounder.isApplicable() для каждого элемента массива, чтобы проверить могут ли применяться их шаблоны в данном контексте. Если пользователь выделяет определенный Surrounder в сплывающем меню, то выполняется его метод surroundElements().

Навигация до классов и литералов

Пользовательские плагины могут предоставлять их собственные элементы, которые будут включены в список, показываемый IDEA, когда пользователь выбирает действие «Go to | Class…» или «Go to | Symbol…».
Для того чтобы сделать это, плагин обязан предоставить реализацию интерфейса ChooseByNameContributor и зарегистрировать в точке расширения com.intellij.gotoSymbolContributor или com.intellij.gotoClassContributor.
Каждая реализация должна уметь возвращать полный список имен из используемого проекта, который будет отфильтрован IDEA в соответствии с введенным текстом в диалоге поиска. Для каждого имени в списке, должен быть предоставлен список из объектов NavigationItem, которые определяют местоположение имени, выбранного из списка.

Документация

Для использования различных типов встроенной в среду интегрированной разработки документации (всплывающая по Ctrl-hover, быстрая по Ctrl-Q), плагин должен предоставить реализацию интерфейса DocumentationProvider и зарегистрировать ее в точке расширения lang.documentationProvider. Начиная с IDEA версии 10.5, существует стандартный базовый класс AbstractDocumentationProvider.
Метод getQuickNavigateInfo() используется для показа документации при наведении курсора мыши с зажатой клавишей Ctrl.

Пример: DocumentationProvider для языка Properties.

Различные дополнительные функции

Для поддержки функции подсветки парных скобок, все что требуется — это реализовать интерфейс PairedBraceMatcher и возвратить массив пар скобок (BracePair). Каждая пара скобок определяет символы открывающих и закрывающих скобок и соответствующие им типы токенов (в принципе, возможно использовать многосимвольные токены, такие как begin/end, но их подсветка не будет полностью корректна).

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

Сворачивание блоков кода контролируется плагином через интерфейс FoldingBuilder. Метод buildFoldRegions() возвращает список текстовых диапазонов, которые могут быть свернуты (массив объектов FoldingDescriptor), замещающий текст на месте свернутого блока определяется методом getPlaceholderText(), а состояние по-умолчанию – методом isCollapsedByDefault(). Блоки можно объединять в группы: так сворачивание одно элемента из группы сворачивает остальные и наоборот. Для создания групп имеется фабричный метод FoldingGroup.newGroup(). Созданный класс должен быть зарегистрирован в точке расширения lang.foldingBuilder.

Комментатор – это функция IDEA, позволяющая закомментировать текущую строку или выделенный диапазон текста. Функция контролируется интерфейсом Commenter, который определяет префикс для однострочных комментариев, префикс и суффикс для блочных комментариев, если они имеются в пользовательском языке. Интерфейс должен быть зарегистрирован в точке расширения lang.commenter.

Пример: комментатор для языка Properties.

Список заметок «To Do» — функция автоматически становится доступна, когда плагин предоставляет реализацию метода ParserDefinition.getCommentTokens(). После чего заметки можно оставлять в комментариях вида «// Todo текст заметки», и просматривать на панели «To Do View» (Alt+6).

Функция «View | Context Info» поддерживается пользовательскими языками начиная с IDEA 10.5. Для ее работы требуется реализация Structure view, основанная на TreeBasedStructureViewBuilder и реализация интерфейса DeclarationRangeHandler, зарегистрированная в точке расширения declarationRangeHandler.

Строковые маркеры помогают помечать любой код иконками на полях редактора. Иконки могут предоставлять навигацию к связанному Psi-элементу. Для реализации этой функции IntelliJ IDEA предоставляет класс RelatedItemLineMarkerProvider.
Провайдер маркеров должен быть зарегистрирован в точке расширения codeInsight.lineMarkerProvider.

Файловые шаблоны определяют, какое содержимое будет сгенерировано при создании нового файла определенного типа. Они позволяют создавать файлы, которые уже содержат некоторый начальный исходный код (шаблоны поддерживают метасимволы: дата/время, пользователь, пакет/класс и т.д.). Их можно просматривать, редактировать и создавать на странице «Settings | File Templates». Дополнительно, IntelliJ IDEA предоставляет удобный API для управления шаблонами из кода плагинов. Для этого необходимо определить экземпляр интерфейса FileTemplateGroupDescriptorFactory и зарегистрировать его в точке расширения com.intellij.fileTemplateGroup.

Продолжение следует.

Все статьи цикла: 1, 2, 3, 4, 5, 6.

ссылка на оригинал статьи http://habrahabr.ru/post/187474/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *