- PVSM.RU - https://www.pvsm.ru -
Класс, используемый в IDEA для определения, как соответствующий текстовый диапазон должен быть подсвечен, называется TextAttributesKey [2]. Экземпляр этого класса создается для каждого различного типа элементов, которые должны быть подсвечены (ключевые слова, числа, строки, комментарии и т.д.), он определяет атрибуты по-умолчанию, которые применяются к элементам соответствующего типа (например, ключевые слова выделяются полужирным, числа – синим, строки – курсивом и зеленым фоном). Отображение TextAttributesKey на специфические атрибуты, используемые в редакторе, определено классом EditorColorsScheme [3] и может быть настроено пользователем, если плагин предоставляет соответствующий конфигурационный интерфейс. В подсветке может использоваться наложение нескольких TextAttributeKey: например, один ключ может определять начертание, а другой – цвет элемента.
Подсветка синтаксиса и ошибок выполняется на разных уровнях. На первом – подсветка синтаксиса, основанная на результатах лексического разбора, осуществляется посредством интерфейса SyntaxHighlighter [4]. Этот интерфейс возвращает экземпляры TextAttributeKey для каждого типа токенов, который требует особую подсветку. Для подсвечивания ошибок лексера применяется стандартный объект класса TextAttributeKey для недопустимых символов (HighligherColors.BAD_CHARACTER).
Схема управления цветом немного изменилась в Intelli IDEA 12.1, чтобы облегчить работу дизайнеров схем и сделать одинаковое отображение для различных языков программирования, даже если схема не была изначально предназначена для них. Ранее языковые плагины использовали фиксированные цветовые схемы не всегда совместимые, например, с темными темами. Новая реализация позволяет определить зависимости от набора стандартных текстовых атрибутов, привязанных к схеме, а не конкретному языку. Атрибуты для специфических языков все еще могут быть установлены дизайнером схемы, но теперь они необязательны. Новые цветовые схемы получили расширение .icls во избежание проблем с совместимостью.
Теперь для определения текстовых атрибутов и зависимости от стандартных ключей используется класс DefaultLanguageHighlighterColors [5].
Пример: реализация SyntaxHighlighlighter [6] для плагина Properties.
На втором уровне подсветки – выделение ошибок, произошедших во время синтаксического разбора. Если определенная цепочка токенов не соответствует грамматике языка, метод PsiBuilder.error() может быть использован для подсветки неверных токенов и отображения сообщения об ошибке.
Третий уровень подсветки выполняется с помощью интерфейса Annotator [7]. Плагин может зарегистрировать одну или несколько аннотаций в точках расширения com.intellij.annotator, после чего они будут вызваны в фоновом процессе во время подсветки элементов PSI-дерева пользовательского языка. Аннотации могут анализировать не только синтаксис, но и семантику и таким образом предоставлять более тонкую логику обработки и подсветки ошибок. Аннотация может содержать функциональность для решения обнаруженных проблем (т.н. quick fix).
Когда файл изменен, аннотация вызывается инкрементально для обработки только изменившихся элементов PSI-дерева.
Для подсветки определенного диапазона текста как ошибки или предупреждения, аннотация вызывает createErrorAnnotation() или createWarningAnnotation() на объекте типа AnnotationHolder [8], и опционально registerFix() на возвращаемом объекте класса Annotator для добавления логики исправления ошибки. Для применения дополнительной подсветки, аннотация может вызвать AnnotationHolder.createInfoAnnotation() с пустым сообщением и затем, вызвав Annotation.setTextAttributes(), установить атрибуты текста.
Пример: Annotator для языка Properties [9].
Наконец, если в пользовательском языке используется внешние средства для валидации файлов, их результаты можно предоставить, реализовав интерфейс ExternalAnnotator [10] и зарегистрировав его в точке расширения com.intellij.externalAnnotator. Подсветка с помощью ExternalAnnotator имеет наименьший приоритет и выполняется только после выполнения всех фоновых процессов. Внешние аннотации используют тот же интерфейс AnnotationHolder для адаптации вывода внешних инструментов и показа подсветки.
Плагин также может предоставить конфигурационный интерфейс, позволяющий пользователю настраивать цвета определенных элементов. Для этого необходимо создать экземпляр класса ColorSettingPage [11] и зарегистрировать его в точке расширения com.intellij.colorSettingsPage.
Пример: ColorSettingsPage для Properties [12].
Функция «Export to HTML» использует тот же механизм подсветки, что и редактор, поэтому становится доступной для пользовательского языка сразу после реализации SyntaxHighlighter.
Одна из наиболее важных и запутанных частей в реализации программной структуры пользовательского языка – это разрешение ссылок, т.е. способность проследовать от места использования элемента (переменная в выражении, вызов метода и т.д.) к месту определения (декларации переменной, метода и т.п.). Это требуется для поддержки многих функций IDEA, таких как «Go to Declaration» (Ctrl-B и Ctrl-Click), а также при поиске использований, переименовании, автодополнении.
Каждый PSI-элемент, который должен работать ссылкой обязан переопределить метод PsiElement.getReference(), так чтобы он возвращал соответствующую реализацию интерфейса PsiReference [13]. Этот интерфейс может быть реализован как самим классом PsiElement, так и отдельным. Если элемент может содержать несколько ссылок (например, строка с перечислением классов), то в таком случае он должен реализовать метод PsiElement.getReferences(), возвращающий массив из ссылок.
Главный метод интерфейса PsiReference – это resolve(), который возвращает либо элемент, на который указывает ссылка, либо null, если невозможно разрешить ссылку (например, если указывает на неопределенный класс). Противоположный ему метод isReferenceTo(), который проверяет, указывает ли ссылка на конкретный элемент. Последний метод может быть реализован с помощью вызова resolve() и сравнения результата с переданным PSI-элементом, но позволяет разработчику применить дополнительные оптимизации.
Пример: Ссылка на ResourceBundle [14] в плагине Properties.
IDEA предоставляет множество интерфейсов, которые могут быть использованы как база для реализации поддержки ссылок, а именно интерфейс PsiScopeProcessor [15] и метод PsiElement.processDeclarations(). Эти интерфейсы обладают множеством сложностей, которые не обязательны для большинства пользовательских языков (например, поддержка подстановки обобщенных типов), но они необходимы, если пользовательский язык может ссылаться на Java код. Если интероперабельность с Java не требуется, либо имеются другие причины, плагин может переопределить стандартную реализацию разрешения ссылок.
Стандартные вспомогательные классы IDEA, используемые для разрешения ссылок, состоят из следующих компонентов:
Существует расширение интерфейса PsiReference, которое позволяет ссылкам использовать несколько целевых элементов – PsiPolyVariantReference. Целевые элементы ссылки возвращаются методом multiResolve(). Действие «Go to Declaration» для такого типа ссылок позволяет выбрать какой именно элемент использовать для навигации. Реализация multiResolve() может быть основана на PsiScopeProcessor, если вместо остановки поиска после первого найденного результата продолжить собирать остальные целевые элементы.
С другой стороны в IntelliJ IDEA существует подход к реализации ссылочной системы посредством Reference Contributor и Reference Provider.
PsiReferenceContributor [16] проверяет каждый PsiElement и по соответствующему описанию, заданному пользователем, возвращает зарегистрированный для данного случая объект провайдера ссылок (пример [17]). В свою очередь, PsiReferenceProvider [18] – это класс, предназначенный для нахождения ссылок внутри одного элемента PSI дерева. Он возвращает массив объектов PsiReference (пример [19]).
Метод PsiReferenceProvider.getReferencesByElement() должен возвратить список ссылок (PsiReference), которые содержатся в переданном ему элементе PsiElement. В данном случае возвращается только одна ссылка, но в общем случае их может быть несколько, при этом каждая ссылка должна будет содержать соответствующий textRange (начальный индекс и конечный индекс нахождения ссылки внутри текста PSI-элемента).
Reference Contributor должен быть зарегистрирован в файле plugin.xml в соответствующей точке расширения — com.intellij.psi.referenceContributor.
После чего возможно использовать результаты его работы для получения списка ссылок при реализации метода PsiElement.getReferences(). Чтобы не дублировать этот код в каждом ссылочном элементе можно определить базовый класс для пользовательских Psi-элементов:
public class MyASTWrapperPsiElement extends ASTWrapperPsiElement {
public MyASTWrapperPsiElement(@NotNull ASTNode astNode) {
super(astNode);
}
@Override public PsiReference getReference() {
PsiReference[] references = getReferences();
return references.length == 0 ? null : references[0];
}
@NotNull @Override public PsiReference[] getReferences() {
// Используем провайдеры, зарегистрированные в Contributor
return ReferenceProvidersRegistry.getReferencesFromProviders(this);
}
}
Существует два основных типа автодополнения, которые могут быть использованы в пользовательском языковом плагине: простое ссылочное дополнение и дополнение на базе провайдера.
Для заполнения списка дополнения, IDEA вызывает метод PsiReference.getVariants() либо у ссылки под курсором, либо у фиктивного элемента, который помещается под курсором. Этот метод должен возвратить массив объектов, содержащий строки, PSI-элементы, либо экземпляры класса LookupElement. Если в возвращенном массиве обнаружен экземпляр PsiElement, то в списке дополнения отобразится соответствующая ему иконка.
Наиболее распространенный способ реализации getVariants() является использование той же функции обхода дерева, что и в методе PsiReference.resolve(), но возвращающей все найденные определения.
Реализация дополнения на базе интерфейса CompletionContributor [20] дает наибольший контроль над операцией автодополнения кода.
Основной сценарий использования Completion Contributor состоит из вызова метода extend() и передачи в параметр «pattern» соответствующего контекста, в котором применим данный вариант дополнения, в параметр «provider» передается провайдер дополнений, который генерирует соответствующие пункты списка автодополнения.
Пример: CompletionContributor [21] для автодополнения ключевых слов в файлах MANIFEST.MF.
Пункты в списке автодополнения представлены экземплярами интерфейса LookupElement [22]. Эти объекты обычно создаются с помощью LookupElementBuilder. Для каждого из них можно определить следующие атрибуты:
Действие «Find Usages» в IDEA – это многошаговый процесс, каждый шаг которого требует участия со стороны плагина: в виде реализации и регистрации FindUsagesProvider [23] в точке расширения com.intellij.lang.findUsagesProvider, а также особенностей реализации программной структуры (интерфейсы PsiNamedElement и PsiReference).
Пример: реализация FindUsagesProvider [24] для Properties.
Для реализации данной функции должны быть выполнены следующие шаги:
Чтобы корректно отобразить название найденного элемента в заголовке панели Find Usages необходимо предоставить реализацию интерфейса ElementDescriptionProvider. Объект ElementDescriptionLocation, который передается в провайдер в этом случае должен иметь актуальный тип UsageViewLongNameLocation.
Пример: ElementDescriptionProvider [26] для плагина Properties.
В следующей части: рефакторинги, форматирование и др.
Автор: Lucyfer
Источник [30]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/plugins/39133
Ссылки в тексте:
[1] здесь: http://habrahabr.ru/post/187224/
[2] TextAttributesKey: https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/openapi/editor/colors/TextAttributesKey.java
[3] EditorColorsScheme: https://github.com/JetBrains/intellij-community/blob/master/platform/platform-api/src/com/intellij/openapi/editor/colors/EditorColorsScheme.java
[4] SyntaxHighlighter: https://github.com/JetBrains/intellij-community/blob/master/platform/platform-api/src/com/intellij/openapi/fileTypes/SyntaxHighlighter.java
[5] DefaultLanguageHighlighterColors: https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/openapi/editor/DefaultLanguageHighlighterColors.java
[6] реализация SyntaxHighlighlighter: https://github.com/JetBrains/intellij-community/blob/master/plugins/properties/src/com/intellij/lang/properties/PropertiesHighlighter.java
[7] Annotator: https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/lang/annotation/Annotator.java
[8] AnnotationHolder: https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/lang/annotation/AnnotationHolder.java
[9] Annotator для языка Properties: https://github.com/JetBrains/intellij-community/blob/master/plugins/properties/src/com/intellij/lang/properties/PropertiesAnnotator.java
[10] ExternalAnnotator: https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/lang/annotation/ExternalAnnotator.java
[11] ColorSettingPage: https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/openapi/options/colors/ColorSettingsPage.java
[12] ColorSettingsPage для Properties: https://github.com/JetBrains/intellij-community/blob/master/plugins/properties/src/com/intellij/openapi/options/colors/pages/PropertiesColorsPage.java
[13] PsiReference: https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/psi/PsiReference.java
[14] Ссылка на ResourceBundle: https://github.com/JetBrains/intellij-community/blob/master/plugins/properties/src/com/intellij/lang/properties/ResourceBundleReference.java
[15] PsiScopeProcessor: https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/psi/scope/PsiScopeProcessor.java
[16] PsiReferenceContributor: https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/psi/PsiReferenceContributor.java
[17] пример: https://github.com/JetBrains/intellij-community/blob/master/plugins/maven/src/main/java/org/jetbrains/idea/maven/dom/references/MavenPropertyPsiReferenceContributor.java
[18] PsiReferenceProvider: https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/psi/PsiReferenceProvider.java
[19] пример: https://github.com/JetBrains/intellij-community/blob/master/plugins/maven/src/main/java/org/jetbrains/idea/maven/dom/references/MavenPropertyPsiReferenceProvider.java
[20] CompletionContributor: https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/codeInsight/completion/CompletionContributor.java
[21] CompletionContributor: http://git.jetbrains.org/?p=idea/contrib.git;a=blob;f=osmorc/src/org/osmorc/manifest/ManifestCompletionContributor.java;hb=HEAD
[22] LookupElement: https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/codeInsight/lookup/LookupElement.java
[23] FindUsagesProvider: https://github.com/JetBrains/intellij-community/blob/master/platform/indexing-api/src/com/intellij/lang/findUsages/FindUsagesProvider.java
[24] реализация FindUsagesProvider: https://github.com/JetBrains/intellij-community/blob/master/plugins/properties/src/com/intellij/lang/properties/findUsages/PropertiesFindUsagesProvider.java
[25] PsiNamedElement: https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/psi/PsiNamedElement.java
[26] ElementDescriptionProvider: https://github.com/JetBrains/intellij-community/blob/master/plugins/properties/src/com/intellij/lang/properties/PropertiesDescriptionProvider.java
[27] 1: http://habrahabr.ru/post/187106/
[28] 2: http://habrahabr.ru/post/187142/
[29] 3: http://habrahabr.ru/post/187208/
[30] 5: http://habrahabr.ru/post/187292/
Нажмите здесь для печати.