mirror of
https://github.com/JetBrains/intellij-sdk-code-samples.git
synced 2025-07-30 18:27:49 +08:00
Merge branch 'master' into subdirs-support
# Conflicts: # _SUMMARY.md # basic_topics/getting_started/plugin_compatibility.md
This commit is contained in:
commit
b692c067b8
73
additional_minor_features.md
Normal file
73
additional_minor_features.md
Normal file
@ -0,0 +1,73 @@
|
||||
---
|
||||
layout: editable
|
||||
title: Additional Minor Features
|
||||
---
|
||||
|
||||
|
||||
In order to implement *brace matching*, once the syntax highlighting lexer has been implemented, all that is required is to implement the
|
||||
[PairedBraceMatcher](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/lang/PairedBraceMatcher.java)
|
||||
interface and to return an array of brace pairs (
|
||||
[BracePair](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/lang/BracePair.java)
|
||||
) for the language.
|
||||
Each brace pair specifies the characters for the opening and closing braces and the lexer token types for these characters.
|
||||
(In principle, it is possible to return multi-character tokens, like "begin" and "end", as the start and end tokens of a brace pair.
|
||||
The IDE will match such braces, but the highlighting for such braces will not be fully correct.)
|
||||
|
||||
Certain types of braces can be marked as structural.
|
||||
Structural braces have higher priority than regular braces: they are matched with each other even if there are unmatched braces of other types between them, and an opening non-structural braces is not matched with a closing one if one of them is inside a pair of matched structural braces and another is outside.
|
||||
|
||||
The *code folding* is controlled by the plugin through the
|
||||
[FoldingBuilder](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/lang/folding/FoldingBuilder.java)
|
||||
interface.
|
||||
The interface returns the list of text ranges which are foldable (as an array of
|
||||
[FoldingDescriptor](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/lang/folding/FoldingDescriptor.java)
|
||||
objects), the replacement text which is shown for each range when it is folded, and the default state of each folding region (folded or unfolded).
|
||||
|
||||
The *Comment Code* feature is controlled through the
|
||||
[Commenter](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/lang/Commenter.java)
|
||||
interface.
|
||||
The interface can return the prefix for the line comment, and the prefix and suffix for the block comment, if such features are supported by the language.
|
||||
|
||||
**Example:**
|
||||
[Commenter](https://github.com/JetBrains/intellij-community/blob/master/plugins/properties/properties-psi-impl/src/com/intellij/lang/properties/PropertiesCommenter.java)
|
||||
for [Properties language plugin](https://github.com/JetBrains/intellij-community/blob/master/plugins/properties/)
|
||||
|
||||
|
||||
To support smart/semantic *Join Lines* see
|
||||
[JoinLinesHandlerDelegate](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/codeInsight/editorActions/JoinLinesHandlerDelegate.java).
|
||||
|
||||
*Smart Enter* (e.g. autocomplete missing semicolon/parentheses) can be provided via
|
||||
[SmartEnterProcessor](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/codeInsight/editorActions/smartEnter/SmartEnterProcessor.java).
|
||||
|
||||
*Naming suggestions* for Rename Refactoring can be provided via
|
||||
[NameSuggestionProvider](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/refactoring/rename/NameSuggestionProvider.java).
|
||||
|
||||
*Semantic highlight usages* (e.g. exit points) can be achieved using
|
||||
[HighlightUsagesHandlerFactory](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-impl/src/com/intellij/codeInsight/highlighting/HighlightUsagesHandlerFactory.java).
|
||||
|
||||
*View\|Parameter Info* is provided via
|
||||
[ParameterInfoHandler](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/lang/parameterInfo/ParameterInfoHandler.java)
|
||||
(extension point `codeInsight.parameterInfo`).
|
||||
|
||||
The *To Do view* is supported automatically if the plugin provides a correct implementation of the
|
||||
[ParserDefinition.getCommentTokens()](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/lang/ParserDefinition.java#L79)
|
||||
method.
|
||||
|
||||
The *View \| Context Info* feature is supported for custom languages since IntelliJ IDEA 10.5.
|
||||
In order for it to work, you need to have a structure view implementation based on a
|
||||
[TreeBasedStructureViewBuilder](https://github.com/JetBrains/intellij-community/blob/master/platform/structure-view-api/src/com/intellij/ide/structureView/TreeBasedStructureViewBuilder.java),
|
||||
and additionally to provide an implementation of
|
||||
[DeclarationRangeHandler](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/codeInsight/hint/DeclarationRangeHandler.java)
|
||||
for your language and to register it in the `declarationRangeHandler` extension point.
|
||||
|
||||
*Spellchecking* can be provided via EP `spellchecker.support` (
|
||||
[SpellcheckingStrategy](https://github.com/JetBrains/intellij-community/spellchecker/src/com/intellij/spellchecker/tokenizer/SpellcheckingStrategy.java)
|
||||
) where you can return
|
||||
[Tokenizer](https://github.com/JetBrains/intellij-community/spellchecker/src/com/intellij/spellchecker/tokenizer/Tokenizer.java)
|
||||
to use, possibly depending on the passed in
|
||||
[PsiElement](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/psi/PsiElement.java)
|
||||
(or `EMPTY_TOKENIZER` for no spellchecking).
|
||||
|
||||
New in 13: user-configurable *reference injections* can be provided via `referenceInjector` extension point (
|
||||
[ReferenceInjector](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/psi/injection/ReferenceInjector.java)
|
||||
) (IntelliLang plugin required).
|
@ -23,7 +23,7 @@ You can check out the code either by using IntelliJ IDEA or from the command lin
|
||||
|
||||
* In the **Git Repository URL** field, enter ```git://git.jetbrains.org/idea/community.git```
|
||||
|
||||

|
||||

|
||||
|
||||
**Checking out from the command line**
|
||||
|
||||
@ -89,30 +89,30 @@ or
|
||||
[Groovy plugin](https://plugins.jetbrains.com/plugin/1524)
|
||||
enabled by going to **Settings \| Plugins** and enabling **Groovy** plugin checkbox.
|
||||
|
||||

|
||||

|
||||
|
||||
Parts of IntelliJ IDEA are written in Groovy, and you will get compilation errors if you don't have the plugin enabled
|
||||
|
||||
* Make sure you have the UI Designer plugin enabled.
|
||||
Most of IntelliJ IDEA's UI is built using the UI Designer, and the version you build will not run correctly if you don't have the plugin enabled
|
||||
|
||||

|
||||

|
||||
|
||||
* Open the directory with the source code as a directory-based project
|
||||
|
||||
* Configure a Java SDK named *IDEA jdk* (case sensitive), pointing to an installation of JDK 1.8
|
||||
|
||||

|
||||

|
||||
|
||||
* Add **lib\\tools.jar** from the JDK installation directory to the classpath of IDEA JDK
|
||||
|
||||

|
||||

|
||||
|
||||
* Use **Build \| Make Project** to build the code
|
||||
|
||||
* To run the code, use the provided shared run configuration *IDEA*
|
||||
|
||||

|
||||

|
||||
|
||||
## Building and Running from the Command Line
|
||||
To build the distribution archive of *IntelliJ IDEA Community Edition*, execute
|
||||
@ -120,7 +120,7 @@ To build the distribution archive of *IntelliJ IDEA Community Edition*, execute
|
||||
|
||||
Ant build script in the root directory of the source code.
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
The results of the build execution can be found at *out/artifacts*.
|
@ -14,7 +14,7 @@ For more information, refer to
|
||||
* On the main menu, choose **File \| New \| Project**. The *New Project*
|
||||
wizard starts.
|
||||
|
||||

|
||||

|
||||
|
||||
* Set *IntelliJ Platform Plugin* project type
|
||||
|
||||
|
@ -11,13 +11,13 @@ In order to set up the plugin development environment, you should follow these s
|
||||
|
||||
* Create a new *IntelliJ IDEA SDK*
|
||||
|
||||

|
||||

|
||||
|
||||
* Set *IDEA jdk* created in
|
||||
[Check Out And Build Community Edition](checkout_and_build_community.html)
|
||||
as a default Java SDK
|
||||
|
||||

|
||||

|
||||
|
||||
* Specify your installation of *IntelliJ IDEA Community Edition* as the home path
|
||||
|
||||
@ -26,17 +26,17 @@ In order to set up the plugin development environment, you should follow these s
|
||||
|
||||
* In the Sourcepath tab of the SDK settings, press ```Add``` button
|
||||
|
||||

|
||||

|
||||
|
||||
* Specify the directory into which you have checked out the sources of the *Community Edition*
|
||||
|
||||

|
||||

|
||||
|
||||
* Go to **File \| New \| Module**
|
||||
|
||||
* Choose *IntelliJ Platform Plugin* module type
|
||||
|
||||

|
||||

|
||||
|
||||
* Set desired plugin name
|
||||
|
||||
@ -44,4 +44,4 @@ In order to set up the plugin development environment, you should follow these s
|
||||
|
||||
* Select the newly created *IntelliJ Platform SDK* as a default SDK for the plugin module
|
||||
|
||||

|
||||

|
||||
|
@ -3,115 +3,18 @@ layout: editable
|
||||
title: Indexing and PSI Stubs
|
||||
---
|
||||
|
||||
## Introduction
|
||||
|
||||
The indexing framework provides a quick way to locate certain elements (for example, files containing a certain word or methods with a particular name) in large code bases. Plugin developers can use the existing indexes built by the IDE itself, as well as build and use their own indexes.
|
||||
The indexing framework provides a quick way to locate certain elements, e.g. files containing a certain word or methods with a particular name, in large code bases.
|
||||
Plugin developers can use the existing indexes built by the IDE itself, as well as build and use their own indexes.
|
||||
|
||||
It supports two main types of indexes: *file-based indexes* and *stub indexes*. File-based indexes are built directly over the content of files, and stub indexes are built over serialized *stub trees*. A stub tree for a source file is a subset of its PSI tree which contains only externally visible declarations and is serialized in a compact binary format. Querying a file-based index gets you the set of files matching a certain condition, and querying a stub index gets you the set of matching PSI elements. Therefore, custom language plugin developers should typically use stub indexes in their plugin implementations.
|
||||
It supports two main types of indexes:
|
||||
|
||||
## File-based Indexes
|
||||
|
||||
File-based indexes are based on a map/reduce architecture. Each index has a certain type of key and a certain type of value. The key is what's later used to retrieve data from the index; for example, in the word index the key is the word itself. The value is arbitrary data which is associated with the key in the index; for example, in the word index the value is a mask indicating in which context the word occurs (code, string literal or comment). In the simplest case (when we only need to know in what files some data occurs), the value has type Void and is not stored in the index.
|
||||
|
||||
When the index implementation indexes a file, it receives the content of a file and returns a map from the keys found in the file to the associated values. When the index is accessed, you specify the key that you're interested in and get back the list of files in which the key occurs and the value associated with each file.
|
||||
|
||||
### Implementing a File-based Index
|
||||
|
||||
A fairly simple file-based index implementation is the [UI Designer bound forms index](https://github.com/JetBrains/intellij-community/blob/master/plugins/ui-designer/src/com/intellij/uiDesigner/binding/FormClassIndex.java). You can refer to it as an example to understand this discussion better.
|
||||
|
||||
Each specific index implementation is a class extending [FileBasedIndexExtension](https://github.com/JetBrains/intellij-community/blob/master/platform/indexing-api/src/com/intellij/util/indexing/FileBasedIndexExtension.java),
|
||||
registered in the <fileBasedIndex> extension point.
|
||||
The implementation contains of the following main parts:
|
||||
|
||||
* `getIndexer()` returns the indexer class, which is responsible for actually building a set of key/value pairs based on the file content.
|
||||
|
||||
* `getKeyDescriptor()` returns the key descriptor, which is responsible for comparing the keys and storing them in a serialized binary format. Probably the most commonly used `KeyDescriptor` implementation is `EnumeratorStringDescriptor` implementation, designed for storing identifiers in an efficient way.
|
||||
|
||||
* `getValueExternalizer()` returns the value serializer, which takes care of storing values in a serialized binary format.
|
||||
|
||||
* `getInputFilter()` allows to restrict the indexing only to a certain set of files.
|
||||
|
||||
* `getVersion()` returns the version of the index implementation. The index is automatically rebuilt if the current version differs from the version of the index implementation used to build the index.
|
||||
|
||||
If you don't need to associate any value with the files (i.e. your value type is Void), you can simplify the implementation by using [ScalarIndexExtension](https://github.com/JetBrains/intellij-community/blob/master/platform/indexing-impl/src/com/intellij/util/indexing/ScalarIndexExtension.java) as the base class.
|
||||
|
||||
Note that the data returned by ```DataIndexer.map()``` must depend only on input data passed to the method, and must not depend on any external files. Otherwise your index will not be correctly updated when the external data changes, and you will have stale data in your index.
|
||||
|
||||
### Accessing a File-based Index
|
||||
|
||||
Access to file-based indexes is performed through the *[FileBasedIndex](https://github.com/JetBrains/intellij-community/blob/master/platform/indexing-api/src/com/intellij/util/indexing/FileBasedIndex.java)* class. The following primary operations are supported:
|
||||
|
||||
* `getAllKeys()` and `processAllKeys()` allow to obtain the list of all keys found in files which are part of the specified project. Note that the returned data is guaranteed to contain all keys found in up-to-date project content, but may also contain additional keys not currently found in the project.
|
||||
|
||||
* `getValues()` allows to obtain all values associated with a specific key (but not the files in which they were found);
|
||||
|
||||
* `getContainingFiles()` allows to obtain all files in which a specific key was encountered;
|
||||
|
||||
* `processValues()` allows to iterate though all files in which a specific key was encountered and to access the associated values at the same time.
|
||||
|
||||
### Standard Indexes
|
||||
|
||||
A number of the standard file-based indexes contained in the IntelliJ Platform are often useful for plugin developers. The first of them is the above-mentioned *word index*. This should generally accessed not directly, but using the helper methods in the *[PsiSearchHelper](https://github.com/JetBrains/intellij-community/blob/master/platform/indexing-api/src/com/intellij/psi/search/PsiSearchHelper.java)* class.
|
||||
|
||||
The second is [FilenameIndex](https://github.com/JetBrains/intellij-community/blob/master/platform/indexing-impl/src/com/intellij/psi/search/FilenameIndex.java).
|
||||
It provides a quick way to find all files matching a certain file name.
|
||||
[FileTypeIndex](https://github.com/JetBrains/intellij-community/blob/master/platform/indexing-impl/src/com/intellij/psi/search/FileTypeIndex.java)
|
||||
serves a similar goal: it allows to quickly find all files of a certain file type.
|
||||
|
||||
## Stub Trees
|
||||
|
||||
As mentioned above, a stub tree is a subset of the PSI tree for a file which is stored in a compact serialized binary format. Actually the PSI tree for a file can be backed either by the AST (built by parsing the text of the file) or by the stub tree (deserialized from disk); switching between the two is transparent. The stub tree contains only a subset of the nodes (typically only the nodes that are needed to resolve the declarations contained in this file from external files). Trying to access any node which is not part of the stub tree, or to perform any operation which cannot be satisfied by the stub tree (such as accessing the text of a PSI element), causes the file to be parsed and the PSI to switch to AST backing.
|
||||
|
||||
Each stub in the stub tree is simply a bean class with no behavior, which stores a subset of the state of the corresponding PSI element (for example, its name, modifier flags like public or final, etc.) The stub also holds a pointer to its parent in the tree and a list of its children stubs.
|
||||
|
||||
To support stubs for your custom language, you first need to decide which of the elements of your PSI tree should be stored as stubs. Typically you need to have stubs for things like methods or fields, which are visible from other files, and don't need to have stubs for things like statements or local variables, which are not visible externally.
|
||||
|
||||
For each element type that you want to store in the stub tree, you need to perform the following steps:
|
||||
|
||||
* Define an interface for the stub, derived from the `StubElement` interface ([example](https://github.com/JetBrains/intellij-community/blob/master/plugins/properties/properties-psi-api/src/com/intellij/lang/properties/psi/PropertyStub.java)).
|
||||
|
||||
* Provide an implementation for the interface ([example](https://github.com/JetBrains/intellij-community/blob/master/plugins/properties/properties-psi-impl/src/com/intellij/lang/properties/psi/impl/PropertyStubImpl.java)).
|
||||
|
||||
* Make sure that the interface for the PSI element extends `StubBasedPsiElement` parameterized by the type of the stub interface ([example](https://github.com/JetBrains/intellij-community/blob/master/plugins/properties/properties-psi-api/src/com/intellij/lang/properties/psi/Property.java)).
|
||||
|
||||
* Make sure that the implementation class for the PSI element extends `StubBasedPsiElementBase` parameterized by the type of the stub interface ([example](https://github.com/JetBrains/intellij-community/blob/master/plugins/properties/src/com/intellij/lang/properties/psi/impl/PropertyImpl.java#L45)). Provide both a constructor that accepts an ASTNode and a constructor which accepts a stub.
|
||||
|
||||
* Create a class which implements `IStubElementType` and is parameterized with the stub interface and the actual PSI element interface ([example](https://github.com/JetBrains/intellij-community/blob/master/plugins/properties/properties-psi-impl/src/com/intellij/lang/properties/parsing/PropertyStubElementType.java)). Implement the createPsi() and createStub() methods for creating PSI from a stub and vice versa. Implement the serialize() and deserialize() methods for storing the data in a binary stream.
|
||||
|
||||
* Use the class implementing `IStubElementType` as the element type constant when parsing ([example](https://github.com/JetBrains/intellij-community/blob/master/plugins/properties/properties-psi-impl/src/com/intellij/lang/properties/parsing/PropertiesElementTypes.java))
|
||||
|
||||
* Make sure that all methods in the PSI element interface access the stub data rather than the PSI tree when appropriate ([example: Property.getKey() implementation](https://github.com/JetBrains/intellij-community/blob/master/plugins/properties/properties-psi-impl/src/com/intellij/lang/properties/psi/impl/PropertyImpl.java#L95))
|
||||
|
||||
The following steps need to be performed only once for each language that supports stubs:
|
||||
|
||||
* Change the file element type for your language (the element type that you return from ```ParserDefinition.getFileNodeType()```) to a class that extends IStubFileElementType.
|
||||
|
||||
* In your plugin.xml, define the ```<stubElementTypeHolder>``` extension and specify the interface which contains the `IElementType` constants used by your language's parser ([example](https://github.com/JetBrains/intellij-community/blob/master/plugins/properties/src/META-INF/plugin.xml#L55)).
|
||||
|
||||
For serializing string data in stubs (such as element names), it's recommended to use ```StubOutputStream.writeName()``` and ```StubInputStream.readName()``` methods. These methods ensure that each unique identifier is stored only once in the data stream, and thus reduce the size of the serialized stub tree data.
|
||||
|
||||
If you need to change the stored binary format for the stubs (for example, if you want to store some additional data or some new elements), make sure that you advance the stub version returned from `IStubFileElementType.getStubVersion()` for your language. This will cause the stubs and stub indices to be rebuilt, and will avoid mismatches between the stored data format and the code trying to load it.
|
||||
|
||||
By default, if a PSI element extends `StubBasedPsiElement`, all elements of that type will be stored in the stub tree. If you need more precise control over which elements are stored, override `IStubElementType.shouldCreateStub()` and return `false` for elements which should not be included in the stub tree. Note that the exclusion is not recursive: if some elements of the element for which you returned false are also stub-based PSI elements, they will be included in the stub tree.
|
||||
|
||||
It's essential to make sure that all information stored in the stub tree depends only on the contents of the file for which stubs are being built, and does not depend on any external files. Otherwise the stub tree will not be rebuilt when an external dependency changes, and you will have stale and incorrect data in the stub tree.
|
||||
|
||||
## Stub Indexes
|
||||
|
||||
When building the stub tree, you can at the same time put some data about the stub elements into a number of indexes, which then can be used to find the PSI elements by the corresponding key. Unlike file-based indexes, stub indexes do not support storing custom data as values; the value is always a PSI element. Keys in stub indexes are normally strings (such as class names); other data types are also supported if desired.
|
||||
|
||||
A stub index is a class which extends [AbstractStubIndex](https://github.com/JetBrains/intellij-community/blob/master/platform/indexing-api/src/com/intellij/psi/stubs/AbstractStubIndex.java). In the most common case, when the key type is String, you use a more specific base class, namely [StringStubIndexExtension](https://github.com/JetBrains/intellij-community/blob/master/platform/indexing-api/src/com/intellij/psi/stubs/StringStubIndexExtension.java).
|
||||
Stub index implementation classes are registered in the ```<stubIndex>``` extension point.
|
||||
|
||||
To put data into an index, you implement the method ```IStubElementType.indexStub()``` ([example: JavaClassElementType.indexStub()](https://github.com/JetBrains/intellij-community/blob/master/java/java-psi-impl/src/com/intellij/psi/impl/java/stubs/JavaClassElementType.java#L189)). This method accepts an ```IndexSink``` as a parameter, and puts in the index ID and the key for each index in which the element should be stored.
|
||||
|
||||
To access the data from an index, the following two methods are used:
|
||||
|
||||
* `AbstractStubIndex.getAllKeys()` returns the list all keys in the specified index for the specified project (for example, the list of all class names found in the project);
|
||||
|
||||
* `AbstractStubIndex.get()` returns the collection of PSI elements corresponding to a certain key (for example, classes with the specified short name) in the specified scope.
|
||||
|
||||
### Related Forum Discussions
|
||||
|
||||
* [Lifecycle of stub creation](http://devnet.jetbrains.com/message/5485343)
|
||||
* [File-based indexes](file_based_indexes.html)
|
||||
* [Stub indexes](stub_indexes.html)
|
||||
|
||||
File-based indexes are built directly over the content of files.
|
||||
Stub indexes are built over serialized *stub trees*.
|
||||
A stub tree for a source file is a subset of its PSI tree which contains only externally visible declarations and is serialized in a compact binary format.
|
||||
Querying a file-based index gets you the set of files matching a certain condition.
|
||||
Querying a stub index gets you the set of matching PSI elements.
|
||||
Therefore, custom language plugin developers should typically use stub indexes in their plugin implementations.
|
@ -4,239 +4,20 @@ title: Run Configurations
|
||||
---
|
||||
|
||||
|
||||
# Purpose
|
||||
Run configurations allow users to run a certain type of external processes from within the IDE, i.e a script, an application, a server, etc.
|
||||
You can provide UI for the user to specify execution options, as well as an option to create run configuration based on a specific location in the source code.
|
||||
|
||||
A run configuration provides the user with the possibility to run a certain type of external process (script, application server etc.) from within the IDE. You can provide UI for the user to specify execution options, as well as the possibility to create run configuration based on a specific location in the source code.
|
||||
|
||||
In this document, we'll start with explaining the core concepts used by the run configuration API and then will proceed to look at the sequence of steps required to implement a typical run configuration.
|
||||
# Architectural overview
|
||||
|
||||
# High-level
|
||||
Classes used to manipulate *IntelliJ IDEA's* run configurations can be split into the following groups:
|
||||
|
||||
* [Run configuration management](run_configuration_management.html)
|
||||
This includes creation, persistence, and modification.
|
||||
|
||||
* [Execution](run_configuration_execution.html)
|
||||
|
||||
This diagram shows the main classes related to **IntelliJ IDEA** run configurations
|
||||
|
||||

|
||||
|
||||
We can separate all entities roughly into two parts * run configuration management (creation; persistence; modification etc) and execution.
|
||||
|
||||
# Management
|
||||
|
||||
## ConfigurationType
|
||||
|
||||
The starting point for implementing any run configuration type is the
|
||||
[ConfigurationType](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/execution/configurations/ConfigurationType.java)
|
||||
interface.
|
||||
List of available configuration type is shown when a user opens _'Edit run configurations'_ dialog and executes _'Add'_ action:
|
||||
|
||||

|
||||
|
||||
Every type there is represented as an instance of
|
||||
[ConfigurationType](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/execution/configurations/ConfigurationType.java)
|
||||
and registered like below:
|
||||
|
||||
```xml
|
||||
<configurationType implementation="org.jetbrains.plugins.gradle.service.execution.GradleExternalTaskConfigurationType"/>
|
||||
```
|
||||
|
||||
The easiest way to implement this interface is to use the
|
||||
[ConfigurationTypeBase](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/execution/configurations/ConfigurationTypeBase.java) base class. In order to use it, you need to inherit from it and to provide the configuration type parameters (ID, name, description and icon) as constructor parameters. In addition to that, you need to call the
|
||||
[addFactory()](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/execution/configurations/ConfigurationTypeBase.java#L46)
|
||||
method to add a configuration factory.
|
||||
|
||||
## ConfigurationFactory
|
||||
|
||||
All run configurations are created by
|
||||
[ConfigurationFactory](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/execution/configurations/ConfigurationFactory.java)
|
||||
registered for particular _ConfigurationType_.
|
||||
It's possible that one _ConfigurationType_
|
||||
[has more than one](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/execution/configurations/ConfigurationType.java#L34)
|
||||
_ConfigurationFactory_:
|
||||
|
||||

|
||||
|
||||
The key API of
|
||||
[ConfigurationFactory](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/execution/configurations/ConfigurationFactory.java),
|
||||
and the only method that you're required to implement, is the
|
||||
[createTemplateConfiguration](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/execution/configurations/ConfigurationFactory.java#L45)
|
||||
method.
|
||||
This method is called once per project to create the template run configuration.
|
||||
All real run configurations (loaded from the workspace or created by the user) are called by cloning the template through the
|
||||
[createConfiguration](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/execution/configurations/ConfigurationFactory.java#L39)
|
||||
method.
|
||||
|
||||
You can customize additional aspects of your configuration factory by overriding the
|
||||
[getIcon](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/execution/configurations/ConfigurationFactory.java#L59),
|
||||
[getAddIcon](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/execution/configurations/ConfigurationFactory.java#L55),
|
||||
[getName](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/execution/configurations/ConfigurationFactory.java#L51)
|
||||
and the default settings methods.
|
||||
These additional overrides are optional.
|
||||
|
||||
## RunConfiguration
|
||||
|
||||
Is represented by
|
||||
[RunConfiguration](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/execution/configurations/RunConfiguration.java)
|
||||
interface.
|
||||
_'Run configuration'_ here is some named profile which can be executed, e.g. application started via _'main()'_ class, test, remote debug to particular machine/port etc.
|
||||
Here is an example of a Java run configurations defined for a particular project:
|
||||
|
||||

|
||||
|
||||
When implementing a run configuration, you may want to use one of the common base classes:
|
||||
|
||||
* [RunConfigurationBase](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/execution/configurations/RunConfigurationBase.java)
|
||||
is a general-purpose superclass that contains the most basic implementation of a run configuration.
|
||||
|
||||
* [LocatableConfigurationBase](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/execution/configurations/LocatableConfigurationBase.java)
|
||||
is a common base class that should be used for configurations that can be created from context by a ```RunConfigurationProducer```.
|
||||
It supports automatically generating a name for a configuration from its settings and keeping track of whether the name was changed by the user.
|
||||
|
||||
* [ModuleBasedConfiguration](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/execution/configurations/ModuleBasedConfiguration.java)
|
||||
is a base class for a configuration that is associated with a specific module (for example, Java run configurations use the selected module to determine the run classpath).
|
||||
|
||||
## SettingsEditor
|
||||
|
||||
That common run configuration settings might be modified via:
|
||||
|
||||
* [_RunConfiguration_-specific UI](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/execution/configurations/RunConfiguration.java#L48).
|
||||
That is handled by [SettingsEditor](https://github.com/JetBrains/intellij-community/blob/master/platform/platform-api/src/com/intellij/openapi/options/SettingsEditor.java#L97):
|
||||
* [_getComponent()_](https://github.com/JetBrains/intellij-community/blob/master/platform/platform-api/src/com/intellij/openapi/options/SettingsEditor.java#L97)
|
||||
method is called by the ide and shows run configuration specific UI;
|
||||
* [_resetFrom()_](https://github.com/JetBrains/intellij-community/blob/master/platform/platform-api/src/com/intellij/openapi/options/SettingsEditor.java#L83)
|
||||
is called to discard all non-confirmed user changes made via that UI;
|
||||
* [_applyTo()_](https://github.com/JetBrains/intellij-community/blob/master/platform/platform-api/src/com/intellij/openapi/options/SettingsEditor.java#L93)
|
||||
is called to confirm the changes, i.e. copy current UI state into the target settings object;
|
||||
|
||||
## Persistence
|
||||
|
||||
That run configuration settings are persistent, i.e. they are stored at file system and loaded back on the ide startup.
|
||||
That is performed via
|
||||
[writeExternal()](https://github.com/JetBrains/intellij-community/blob/master/platform/util/src/com/intellij/openapi/util/JDOMExternalizable.java#L27)
|
||||
and
|
||||
[readExternal()](https://github.com/JetBrains/intellij-community/blob/master/platform/util/src/com/intellij/openapi/util/JDOMExternalizable.java#L26)
|
||||
methods of
|
||||
[RunConfiguration](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/execution/configurations/RunConfiguration.java)
|
||||
class correspondingly.
|
||||
|
||||
The actual configurations stored by the IntelliJ Platform are represented by instances of the
|
||||
[RunnerAndConfigurationSettings](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/execution/RunnerAndConfigurationSettings.java)
|
||||
class, which combines a run configuration with runner-specific settings, as well as keeping track of certain run configuration flags such as "temporary" or "singleton".
|
||||
|
||||
Dealing with instances of this class becomes necessary when you need to create run configurations from code. This is accomplished with the following two steps:
|
||||
|
||||
* ```RunManager.createConfiguration()``` creates an instance of ```RunnerAndConfigurationSettings```;
|
||||
|
||||
* ```RunManager.addConfiguration()``` makes it persistent by adding it to either the list of shared configurations stored in a project, or to the list of local configurations stored in the workspace file.
|
||||
|
||||
## Refactoring Support
|
||||
|
||||
Most run configurations contain references to classes, files or directories in their settings, and these settings usually need to be updated when the corresponding element is renamed or moved.
|
||||
In order to support that, your run configuration needs to implement the
|
||||
[RefactoringListenerProvider](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/execution/configurations/RefactoringListenerProvider.java)
|
||||
interface.
|
||||
In your implementation of ```getRefactoringElementListener()```, you need to check whether the element being refactored is the one that your run configuration refers to, and if it is, you return a ```RefactoringElementListener``` that updates your configuration according to the new name and location of the element.
|
||||
|
||||
## Creating Configurations from Context
|
||||
|
||||
Many plugins support automatic creation of run configurations from context, so that the user can click, for example, on an application or test class and automatically run it using the correct run configuration type. In order to support that, you need to provide an implementation of the
|
||||
[RunConfigurationProducer](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/execution/actions/RunConfigurationProducer.java)
|
||||
interface and to register it as \<runConfigurationProducer\> in your plugin.xml.
|
||||
(Note that this API has been redesigned in IntelliJ IDEA 13; ```RuntimeConfigurationProducer``` is an older and much more confusing version of the same API).
|
||||
|
||||
The two main methods that you need to implement are:
|
||||
|
||||
* ```setupConfigurationFromContext``` receives a blank configuration of your type and a ```ConfigurationContext``` containing information about a source code location (accessible by calling ```getLocation()``` or ```getPsiLocation()```).
|
||||
Your implementation needs to check whether the location is applicable for your configuration type (for example, if it's in a file of the language you're supporting).
|
||||
If not, you need to return false, and if it is, you need to put the correct context-specific settings into the run configuration and return true.
|
||||
|
||||
* ```isConfigurationFromContext``` checks if the specified configuration of your type was created from the specified context.
|
||||
Implementing this method allows you to reuse an existing run configuration which is applicable to the current context instead of creating a new one and possibly ignoring the customisations the user has performed in the existing one.
|
||||
|
||||
Note that, in order to support automatic naming of configurations created from context, your configuration should use
|
||||
[LocatableConfigurationBase](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/execution/configurations/LocatableConfigurationBase.java)
|
||||
as the base class.
|
||||
|
||||
|
||||
# Execution
|
||||
|
||||
The standard execution of a run action goes through the following steps:
|
||||
|
||||
* The user selects a *run configuration* (for example, by choosing one from the run configurations combobox) and an *executor* (for example, by pressing a toolbar button created by the executor).
|
||||
|
||||
* The *ProgramRunner* that will actually execute the process is selected, by polling all registered program runners and asking whether they can run the specified RunProfile with the specified executor ID.
|
||||
|
||||
* The *ExecutionEnvironment* object is created. The object aggregates all the settings required to execute the process, as well as the selected ProgramRunner.
|
||||
|
||||
* ```ProgramRunner.execute()``` is called, receiving the executor and the execution environment.
|
||||
|
||||
* Implementations of ```ProgramRunner.execute()``` go through the following steps to execute the process:
|
||||
|
||||
* ```RunProfile.getState()``` method is called to create a ```RunProfileState``` object, describing a process about to be started. At this stage, the command line parameters, environment variables and other information required to start the process is initialized.
|
||||
|
||||
* ```RunProfileState.execute()``` is called. It starts the process, attaches a ```ProcessHandler``` to its input and output streams, creates a console to display the process output, and returns an ```ExecutionResult``` object aggregating the console and the process handler.
|
||||
|
||||
* The ```RunContentBuilder``` object is created and invoked to display the execution console in a tab of the Run or Debug toolwindow.
|
||||
|
||||
## Executor
|
||||
|
||||
The
|
||||
[Executor](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/execution/Executor.java)
|
||||
interface describes a specific way of executing any possible run configuration.
|
||||
The three default executors provided by the IntelliJ Platform by default are _Run_, _Debug_ and (in IntelliJ IDEA Ultimate and certain platform-based IDEs) _Run with Coverage_.
|
||||
Each executor gets its own toolbar button, which starts the selected run configuration using this executor, and its own context menu item for starting a configuration using this executor.
|
||||
|
||||
As a plugin developer, you normally don't need to implement the _Executor_ interface.
|
||||
However, it can be useful, for example, if you're implementing a profiler integration and want to provide the possibility to execute any configuration with profiling.
|
||||
|
||||
## Running a Process
|
||||
|
||||
The _RunProfileState_ interface comes up in every run configuration implementation as the return value ```RunProfile.getState()```.
|
||||
It describes a process which is ready to be started and holds the information like the command line, current working directory and environment variables for the process to be started.
|
||||
(The existence of RunProfileState as a separate step in the execution flow allows run configuration extensions and other components to patch the configuration and to modify the parameters before it gets executed.)
|
||||
|
||||
The standard base class used as implementation of RunProfileState is
|
||||
[CommandLineState](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/execution/configurations/CommandLineState.java).
|
||||
It contains the logic for putting together a running process and a console into an
|
||||
[ExecutionResult](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/execution/ExecutionResult.java),
|
||||
but doesn't know anything how the process is actually started. For starting the process, it's best to use the
|
||||
[GeneralCommandLine](https://github.com/JetBrains/intellij-community/blob/master/platform/platform-api/src/com/intellij/execution/configurations/GeneralCommandLine.java)
|
||||
class, which takes care of setting up the command line parameters and executing the process.
|
||||
|
||||
Alternatively, if the process you need to run is a JVM-based one, you can use the
|
||||
[JavaCommandLineState](https://github.com/JetBrains/intellij-community/blob/master/java/execution/openapi/src/com/intellij/execution/configurations/JavaCommandLineState.java)
|
||||
base class. It knows about the command line parameters of the JVM and can take care of details like calculating the classpath for the JVM.
|
||||
|
||||
To monitor the execution of a process and capture its output, the
|
||||
[OSProcessHandler](https://github.com/JetBrains/intellij-community/blob/master/platform/platform-api/src/com/intellij/execution/process/OSProcessHandler.java)
|
||||
class is normally used.
|
||||
Once you've created an instance of OSProcessHandler from either a command line or a Process object, you need to call the ```startNotify()``` method to start capturing its output.
|
||||
You may also want to attach a [ProcessTerminatedListener](https://github.com/JetBrains/intellij-community/blob/master/platform/platform-api/src/com/intellij/execution/process/ProcessTerminatedListener.java)
|
||||
to the OSProcessHandler, so that the exit status of the process will be displayed in the console.
|
||||
|
||||
## Displaying the Process Output
|
||||
|
||||
If you're using ```CommandLineState```, a console view will be automatically created and attached to the output of the process.
|
||||
Alternatively, you can arrange this yourself:
|
||||
|
||||
* ```TextConsoleBuilderFactory.createBuilder(project).getConsole()``` creates a
|
||||
[ConsoleView](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/execution/ui/ConsoleView.java)
|
||||
instance;
|
||||
|
||||
* ```ConsoleView.attachToProcess()``` attaches it to the output of a process.
|
||||
|
||||
If the process you're running uses ANSI escape codes to color its output, the
|
||||
[ColoredProcessHandler](https://github.com/JetBrains/intellij-community/blob/master/platform/platform-api/src/com/intellij/execution/process/ColoredProcessHandler.java)
|
||||
class will parse it and display the colors in the IntelliJ console.
|
||||
|
||||
Console
|
||||
[filters](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/execution/filters/Filter.java)
|
||||
allow you to convert certain strings found in the process output to clickable hyperlinks. To attach a filter to the console, use ```CommandLineState.addConsoleFilters()``` or, if you're creating a console manually, ```TextConsoleBuilder.addFilter()```.
|
||||
Two common filter implementations you may want to reuse are
|
||||
[RegexpFilter](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/execution/filters/RegexpFilter.java)
|
||||
and
|
||||
[UrlFilter](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/execution/filters/UrlFilter.java).
|
||||
|
||||
## Starting a Run Configuration from Code
|
||||
|
||||
If you have an existing run configuration that you need to execute, the easiest way to do so is to use
|
||||
[ProgramRunnerUtil.executeConfiguration](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-impl/src/com/intellij/execution/ProgramRunnerUtil.java#L110).
|
||||
The method takes a Project, a RunnerAndConfigurationSettings, as well as an Executor.
|
||||
To get the RunnerAndConfigurationSettings for an existing configuration, you can use, for example, ```RunManager.getConfigurationSettings(ConfigurationType)```.
|
||||
As the last parameter, you normally pass either ```DefaultRunExecutor.getRunExecutorInstance()``` or ```DefaultDebugExecutor.getDebugExecutorInstance()```.
|
||||
|
||||
|
@ -4,9 +4,8 @@ title: Testing Plugins
|
||||
---
|
||||
|
||||
|
||||
## General Testing Approach
|
||||
|
||||
Before discussing the specific details of the IntelliJ IDEA plugin test framework, it's worth looking at the general approach that the IntelliJ IDEA team uses for testing the IDE code. Our intention here is not so much to be prescriptive, and more to set the expectations we have good tools to support the approach that we use, and less good (or no) tools for approaches that we don't use.
|
||||
Before discussing the specific details of the IntelliJ IDEA plugin test framework, it's worth looking at the general approach that the IntelliJ IDEA team uses for testing the IDE code.
|
||||
Our intention here is not so much to be prescriptive, and more to set the expectations we have good tools to support the approach that we use, and less good (or no) tools for approaches that we don't use.
|
||||
|
||||
Most of the tests in the IntelliJ IDEA codebase are *model-level functional tests*. What this means is the following:
|
||||
|
||||
@ -18,11 +17,13 @@ Most of the tests in the IntelliJ IDEA codebase are *model-level functional test
|
||||
|
||||
* Most of the tests take a source file or a set of source files as input data, execute a feature, and then compare the output with expected results (which can be specified as another set of source files, as special markup in the input file, or directly in the test code).
|
||||
|
||||
The biggest benefit of this test approach is that tests are very stable and require very little maintenance once they have been written, no matter how much the underlying implementation is refactored or rewritten. In a product with 10+ years of lifetime that has gone through a large number of internal refactorings, we find that this benefit greatly outweighs the downsides of slower test execution and more difficult debugging of failures (compared to more isolated unit tests).
|
||||
The biggest benefit of this test approach is that tests are very stable and require very little maintenance once they have been written, no matter how much the underlying implementation is refactored or rewritten.
|
||||
In a product with 10+ years of lifetime that has gone through a large number of internal refactorings, we find that this benefit greatly outweighs the downsides of slower test execution and more difficult debugging of failures (compared to more isolated unit tests).
|
||||
|
||||
Another consequence of our testing approach is what our test framework does not provide:
|
||||
|
||||
* We do not provide a recommended approach to mocking. We have a few tests in our codebase that use JMock, but in general we find it difficult to mock all of the interactions with IntelliJ IDEA components that your plugin class will need to have, and we recommend working with real components instead.
|
||||
* We do not provide a recommended approach to mocking.
|
||||
We have a few tests in our codebase that use JMock, but in general we find it difficult to mock all of the interactions with IntelliJ IDEA components that your plugin class will need to have, and we recommend working with real components instead.
|
||||
|
||||
* We do not provide a general-purpose framework for Swing UI testing. You can try using tools such as
|
||||
[FEST](http://fest.easytesting.org/) or
|
||||
@ -30,133 +31,15 @@ Another consequence of our testing approach is what our test framework does not
|
||||
for plugin UI testing, but we don't use either of them and cannot provide any guidelines for their use.
|
||||
Internally, we use manual testing for testing our Swing UIs.
|
||||
|
||||
## Tests and Fixtures
|
||||
|
||||
The IntelliJ Platform testing infrastructure is not tied to any specific test framework.
|
||||
In fact, the IntelliJ IDEA team uses both JUnit, TestNG and Cucumber for testing different parts of the project.
|
||||
However, most of the tests are written using JUnit 3.
|
||||
* [Tests and Fixtures](tests_and_fixtures.html)
|
||||
* [Light and Heavy Tests](light_and_heavy_tests.html)
|
||||
* [Test Project and Testdata Directories](test_project_and_testdata_directories.html)
|
||||
* [Writing Tests](writing_tests.html)
|
||||
* [Testing Highlighting](testing_highlighting.html)
|
||||
|
||||
When writing your own tests, you have the choice between using a standard base class to perform the test set up for you and using a fixture class, which lets you perform the setup manually and does not tie you to a specific test framework.
|
||||
With the former approach, you can use classes such as ```LightPlatformCodeInsightFixtureTestCase``` (despite being one of the longest and ugliest class names you'll run into in the IntelliJ IDEA API, it's actually the recommended approach for writing tests).
|
||||
With the latter approach, you use the ```IdeaTestFixtureFactory``` class to create instances of fixtures for the test environment, and you need to call the fixture creation and setup methods from the test setup method used by your test framework.
|
||||
|
||||
## Light and Heavy Tests
|
||||
|
||||
As mentioned above, plugin tests run in a real, rather than mocked, IntelliJ IDEA environment, and use real implementations for most of the application and project components. Loading the project components for a project is a fairly expensive operation, and we want to avoid doing it for each test. Because of that, we distinguish between *heavy* tests, which create a new project for each test, and *light* tests, which reuse a project from the previous test run when possible. Light and heavy tests use different base classes or fixture classes, as described below.
|
||||
|
||||
(i) Because of the performance difference, we recommend plugin developers to write light tests whenever possible.
|
||||
|
||||
The standard way of writing a light test is to extend the ```LightCodeInsightFixtureTestCase``` class (for tests that require the Java PSI or any related functionality) or ```LightPlatformCodeInsightFixtureTestCase``` (for tests that don't have any Java dependencies).
|
||||
|
||||
When writing a light test, you can specify the requirements for the project that you need to have in your test, such as the module type, the configured SDK, facets, libraries etc. You do so by extending the ```LightProjectDescriptor``` class and returning your project descriptor from ```LightCodeInsightFixtureTestCase.getProjectDescriptor()```. Before executing each test, the project will be reused if the test case returns the same project descriptor as the previous one, or recreated if the descriptor is different.
|
||||
|
||||
If you need to set up a multi-module project for your tests, you must write a heavy test. The setup code for a multi-module Java project looks something like that:
|
||||
|
||||
```java
|
||||
final TestFixtureBuilder<IdeaProjectTestFixture> projectBuilder = IdeaTestFixtureFactory.getFixtureFactory().createFixtureBuilder(getName());
|
||||
|
||||
// repeat the following line for each module
|
||||
final JavaModuleFixtureBuilder moduleFixtureBuilder = projectBuilder.addModule(JavaModuleFixtureBuilder.class);
|
||||
|
||||
myFixture = JavaTestFixtureFactory.getFixtureFactory().createCodeInsightFixture(projectBuilder.getFixture());
|
||||
```
|
||||
|
||||
## Test Project and Testdata Directories
|
||||
|
||||
The test fixture creates a *test project* environment. Unless you customize the project creation, the test project will have one module with one source root called "```src```". The files for the test project physically exist either in a temporary directory or in an in-memory file system, depending on which implementation of ```TempDirTestFixture``` is used. ```LightPlatformCodeInsightFixtureTestCase``` uses an in-memory implementation; if you set up the test environment by calling ```IdeaTestFixtureFactory.createCodeInsightFixture```, you can specify the implementation to use.
|
||||
|
||||
Note that if your tests use the in-memory implementation, and you abort the execution of your tests, the persisted filesystem caches may get out of sync with the in-memory structures, and you may get spurious errors in your tests. In that case, you need to try running the test again, and if that doesn't help, delete the "system" subdirectory under the sandbox directory specified in the IntelliJ IDEA SDK settings.
|
||||
|
||||
In your plugin, you normally store the test data for your tests (such as files on which plugin features will be executed and expected output files) in the *testdata* directory. This is just a directory under the content root of your plugin, but not under a source root (files in testdata are normally not valid source code and must not be compiled). To specify the location of testdata, you must override the ```LightPlatformCodeInsightFixtureTestCase.getTestDataPath()``` method (the default implementation assumes running as part of the IntelliJ IDEA source tree and is not appropriate for third-party plugins).
|
||||
|
||||
Note that a very common pattern in IntelliJ IDEA tests is to use the name of the test method being executed as the base for building the testdata file paths. This allows to reuse most of the code between different test methods that test different aspects of the same feature, and this approach is also recommended for third-party plugin tests. The name of the test method can be retrieved using ```UsefulTestCase.getTestName()```.
|
||||
|
||||
To copy files or directories from your testdata directory to the test project directory, you can use the ```copyFileToProject()``` and ```copyDirectoryToProject()``` methods in the ```CodeInsightTestFixture``` class.
|
||||
|
||||
Most operations in plugin tests require a file open in the in-memory editor, in which highlighting, completion and other operations will be performed. The in-memory editor instance is returned by ```CodeInsightTestFixture.getEditor()```. To copy a file from the testdata directory to the test project directory and immediately open it in the editor, you can use the ```CodeInsightTestFixture.configureByFile()``` or ```configureByFiles()``` methods. The latter copies multiple files to the test project directory and opens the *first* of them in the in-memory editor.
|
||||
|
||||
Alternatively, you can use one of the other methods which take parameters annotated with ```@TestDataFile```. These methods copy the specified files from the testdata directory to the test project directory, open the first of the specified files in the in-memory editor, and then perform the requested operation such as highlighting or code completion.
|
||||
|
||||
When a file is opened in the in-memory editor, special markup in the file content can be used to specify the caret position or selection. You can use one of the following markers:
|
||||
|
||||
* ```<caret>``` specifies the position where the caret should be placed;
|
||||
|
||||
* ```<selection>``` and ```</selection>``` specify the start and end of the text range to be selected;
|
||||
|
||||
* ```<block>``` and ```</block>``` specify the start and end points of the column selection.
|
||||
|
||||
## Writing Tests
|
||||
|
||||
In most cases, once you have the necessary files copied to the test project and loaded into the in-memory editor, writing the test itself involves invoking your plugin code and has few dependencies on the test framework. However, for many common cases the framework provides helper methods that can make testing easier:
|
||||
|
||||
* ```type()``` simulates the typing of a character or string into the in-memory editor;
|
||||
|
||||
* ```performEditorAction()``` simulates the execution of an action in the context of the in-memory editor;
|
||||
|
||||
* ```complete()``` simulates the invocation of code completion and returns the list of lookup elements displayed in the completion list (or ```null``` if the completion had no suggestions or one suggestion which was auto-inserted);
|
||||
|
||||
* ```findUsages()``` simulates the invocation of 'Find Usages' and returns the found usages;
|
||||
|
||||
* ```findSingleIntention()``` in combination with ```launchAction()``` simulate the invocation of an intention action or inspection quickfix with the specified name;
|
||||
|
||||
* ```renameElementAtCaret()``` or ```rename()``` simulate the execution of a rename refactoring.
|
||||
|
||||
To compare the results of executing the action with the expected results, you can use the ```checkResultByFile()``` method. The file with the expected results can also contain markup to specify the expected caret position or selected text range. If you're testing an action which modifies multiple files (a project-wide refactoring, for example), you can compare an entire directory under the test project with the expected output using ```PlatformTestUtil.assertDirectoriesEqual()```.
|
||||
|
||||
## Testing Highlighting
|
||||
|
||||
A common task when writing plugin tests is testing various kinds of highlighting (inspections, annotators, parser error highlighting etc.) The IntelliJ Platform provides a dedicated utility and markup format for this task.
|
||||
|
||||
To test the highlighting for the file currently loaded into the in-memory editor, you invoke the ```checkHighlighting()``` method. The parameters to the method specify which severities should be taken into account when comparing the results with the expected results: errors are always taken into account, whereas warnings, weak warnings and infos are optional. Alternatively, you can use the ```testHighlighting()``` method, which loads a testdata file into the in-memory editor and highlights it as a single operation.
|
||||
|
||||
If you need to test inspections (rather than generic highlighting provided by a highlighting lexer or annotator), you need to enable inspections that you're testing. This is done by calling ```CodeInsightTestFixture.enableInspections()``` in the setup method of your test or directly in a test method, before the call to checkHighlighting().
|
||||
|
||||
The expected results of the highlighting are specified directly in the source file. The platform supports an extensive XML-like markup language for this. In its simplest form, the markup looks like this:
|
||||
|
||||
```xml
|
||||
<warning descr="expected error message">code to be highlighted</warning>
|
||||
```
|
||||
|
||||
Or, as a more specific example:
|
||||
|
||||
```xml
|
||||
public int <warning descr="The compareTo() method does not reference 'foo' which is referenced from equals(); inconsistency may result">compareTo</warning>(Simple other) {
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
The tag name specifies the severity of the expected highlighting. The following severities are supported:
|
||||
|
||||
* ```<error>```
|
||||
|
||||
* ```<warning>```
|
||||
|
||||
* ```<weak_warning>```
|
||||
|
||||
* ```<info>```
|
||||
|
||||
* ```<inject>``` (for an injected fragment)
|
||||
|
||||
* ```<symbolName>``` (for a marker that highlights an identifier according to its type)
|
||||
|
||||
* any custom severity can be referenced by its name
|
||||
|
||||
|
||||
The tag can also have the following optional attributes:
|
||||
|
||||
* ```descr``` expected message associated with the highlighter (if not specified, any text will match; if the message contains a quotation mark, it can be escaped by putting two backslash characters before it)
|
||||
|
||||
* ```foregroundColor```, ```backgroundColor```, ```effectColor``` * expected colors for the highlighting
|
||||
|
||||
* ```effectType``` expected effect type for the highlighting (see ```EffectType``` enum for possible values)
|
||||
|
||||
* ```fontType``` expected font style for the highlighting (0 * normal, 1 * bold, 2 * italic, 3 * bold italic)
|
||||
|
||||
|
||||
|
||||
## Tutorial
|
||||
|
||||
Check out
|
||||
[this](writing_tests_for_plugins.html)
|
||||
step-by-step tutorial teaching how to write and run automated tests for your custom language plugin (source code included).
|
||||
[this step-by-step tutorial](writing_tests_for_plugins.html)
|
||||
teaching how to write and run automated tests for your custom language plugin (source code included).
|
||||
|
||||
|
58
code_completion.md
Normal file
58
code_completion.md
Normal file
@ -0,0 +1,58 @@
|
||||
---
|
||||
layout: editable
|
||||
title: Code Completion
|
||||
---
|
||||
|
||||
There are two main types of code completion that can be provided by custom language plugins: reference completion and contributor-based completion.
|
||||
|
||||
Reference completion is easier to implement, but supports only the basic completion action.
|
||||
Contributor-based completion provides more features, supports all three completion types (basic, smart and class name) and can be used, for example, to implement keyword completion.
|
||||
|
||||
### Reference Completion
|
||||
|
||||
To fill the completion list, the IDE calls
|
||||
[PsiReference.getVariants()](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/psi/PsiReference.java)
|
||||
either on the reference at the caret location or on a dummy reference that would be placed at the caret.
|
||||
This method needs to return an array of objects containing either strings,
|
||||
[PsiElement](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/psi/PsiElement.java)
|
||||
instances or instances of the
|
||||
[LookupElement](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/codeInsight/lookup/LookupElement.java)
|
||||
class.
|
||||
If a
|
||||
[PsiElement](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/psi/PsiElement.java)
|
||||
instance is returned in the array, the completion list shows the icon for the element.
|
||||
|
||||
The most common way to implement `getVariants()` is to use the same function for walking up the tree as in
|
||||
[PsiReference.resolve()](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/psi/PsiReference.java),
|
||||
and a different implementation of
|
||||
[PsiScopeProcessor](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/psi/scope/PsiScopeProcessor.java)
|
||||
which collects all declarations passed to its `processDeclarations()` method and returns them as an array for filling the completion list.
|
||||
|
||||
### Contributor-based Completion
|
||||
|
||||
Implementing the
|
||||
[CompletionContributor](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/codeInsight/completion/CompletionContributor.java)
|
||||
interface gives you the greatest control over the operation of code completion for your language.
|
||||
Note that the JavaDoc of that class contains a detailed FAQ for implementing code completion.
|
||||
|
||||
The core scenario of using
|
||||
[CompletionContributor](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/codeInsight/completion/CompletionContributor.java)
|
||||
consists of calling the `extend()` method and passing in the *pattern* specifying the context in which this completion variant is applicable, as well as a *completion provider* which generates the items to show in the completion list.
|
||||
|
||||
**Example**:
|
||||
[CompletionContributor](https://github.com/JetBrains/intellij-plugins/blob/master/osmorc/src/org/osmorc/manifest/completion/OsgiManifestCompletionContributor.java)
|
||||
for completing keywords in MANIFEST.MF files.
|
||||
|
||||
|
||||
Items shown in the completion list are represented by instances of the
|
||||
[LookupElement](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/codeInsight/lookup/LookupElement.java)
|
||||
interface.
|
||||
These instances are normally created through the
|
||||
[LookupElementBuilder](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/codeInsight/lookup/LookupElementBuilder.java)
|
||||
class.
|
||||
For every lookup element, you can specify the following attributes:
|
||||
|
||||
* Text, tail text and type text. Tail text is shown next to the main item text, is not used for prefix matching, and can be used, for example, to show the parameter list of the method. Type text is shown right-aligned in the lookup list and can be used to show the return type or containing class of a method, for example.
|
||||
* Icon
|
||||
* Text attributes (bold, strikeout etc.)
|
||||
* Insert handler. The insert handler is a callback which is called when the item is selected, and can be used to perform additional modifications of the text (for example, to put in the parentheses for a method call)
|
80
code_formatting.md
Normal file
80
code_formatting.md
Normal file
@ -0,0 +1,80 @@
|
||||
---
|
||||
layout: editable
|
||||
title: Code Formatter
|
||||
---
|
||||
|
||||
The IntelliJ Platform includes a powerful framework for implementing custom language formatters.
|
||||
In this framework, the plugin specifies the *constraints* on the spacing between different syntax elements, and the formatting engine, provided by the IDE, calculates the smallest number of whitespace modifications that need to be performed on the file to make it match the constraints.
|
||||
|
||||
The process of formatting a file or a file fragment consists of the following main steps:
|
||||
|
||||
* The _formatting model builder_ (
|
||||
[FormattingModelBuilder](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/formatting/FormattingModelBuilder.java)
|
||||
), implemented by the plugin, provides a formatting model (
|
||||
[FormattingModel](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/formatting/FormattingModel.java)
|
||||
) for the document to be formatted
|
||||
|
||||
* The formatting model is requested to build the structure of the file as applies to formatting, as a tree of _blocks_ (
|
||||
[Block](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/formatting/Block.java)
|
||||
) with associated indent, wrap, alignment and spacing settings.
|
||||
|
||||
* The formatting engine calculates the sequence of whitespace characters (spaces, tabs and/or line breaks) that needs to be placed at every block boundary, based on the formatting model provided by the plugin.
|
||||
|
||||
* The formatting model is requested to insert the calculated whitespace characters at necessary positions in the file.
|
||||
|
||||
The structure of blocks is usually built in such a way that it mirrors the PSI structure of the file - for example, in Java code, the top-level formatting block covers the entire file, its children cover individual classes in the file, blocks on the next level cover methods inside classes, and so on. The formatter modifies only the characters between blocks, and the tree of blocks must be built in such a way that the bottom-level blocks cover all non-whitespace characters in the file: otherwise the characters between blocks may be deleted by the formatter.
|
||||
|
||||
If the formatting operation does not affect the entire file (for example, if the formatter is called to format the pasted block of text), a complete tree of blocks is not built - rather, only blocks for the text range covered by the formatting operation and their parents are built.
|
||||
|
||||
For every block, the plugin specifies the following properties:
|
||||
|
||||
* The _spacing_ (
|
||||
[Spacing](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/formatting/Spacing.java)
|
||||
) specifies what spaces or line breaks are inserted between the specified children of the block.
|
||||
The spacing object specifies the minimum and maximum number of spaces that must be placed between the specified child blocks, the minimum number of line breaks to place there, and whether the existing line breaks and blank lines should be preserved.
|
||||
The formatting model can also specify that the spacing between the specified blocks may not be modified by the formatter.
|
||||
|
||||
* The _indent_ specifies how the block is indented relative to its parent block.
|
||||
There are different modes of indenting defined by factory methods in the Indent class.
|
||||
The most commonly used are the none indent (which means the child block is not indented), the regular indent (the child block is indented by the number of spaces specified in the **Project Code Style \| General \| Indent** setting) and the continuation indent (based on **Project Code Style \| General \| Continuation Indent** setting).
|
||||
If the formatting model does not specify an indent, the "continuation without first" mode is used, which means that the first block in a sequence of blocks with that type is not indented and the following blocks are indented with a continuation indent.
|
||||
|
||||
* The _wrap_ (
|
||||
[Wrap](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/formatting/Wrap.java)
|
||||
) specifies whether the content of the block is wrapped to the next line.
|
||||
Wrapping is performed by inserting a line break before the block content.
|
||||
The plugin can specify that a particular block is never wrapped, always wrapped, or wrapped only if it exceeds the right margin.
|
||||
|
||||
* The _alignment_ (
|
||||
[Alignment](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/formatting/Alignment.java)
|
||||
) specifies which blocks should be aligned with each other.
|
||||
If two blocks with the alignment property set to the same object instance are placed in different lines, and if the second block is the first non-whitespace block in its line, the formatter inserts white spaces before the second block so that it starts from the same column as the first one.
|
||||
|
||||
For each of these properties, a number of special use settings exists, which are described in the JavaDoc comments for the respective classes.
|
||||
See also
|
||||
[SpacingBuilder](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/formatting/SpacingBuilder.java)
|
||||
which aids in building rule-based configuration.
|
||||
|
||||
An important special case in using the formatter is the smart indent performed when the user presses the ```Enter``` key in a source code file.
|
||||
To determine the indent for the new line, the formatter engine calls the method `getChildAttributes()` on either the block immediately before the caret or the parent of that block, depending on the return value of the `isIncomplete()` method for the block before the caret.
|
||||
If the block before the cursor is incomplete (contains elements that the user will probably type but has not yet typed, like a closing parenthesis of the parameter list or the trailing semicolon of a statement), `getChildAttributes()` is called on the block before the caret; otherwise, it's called on the parent block.
|
||||
|
||||
**New in IntelliJ IDEA 13**:
|
||||
Code formatting can be suppressed per region via [special comments](http://youtrack.jetbrains.com/issue/IDEA-56995#comment=27-605969).
|
||||
|
||||
### Code Style Settings
|
||||
|
||||
To specify the default indent size for the language provided by your plugin, and to allow the user to configure the tab size and indent size you need to implement the
|
||||
[FileTypeIndentOptionsProvider](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/psi/codeStyle/FileTypeIndentOptionsProvider.java)
|
||||
interface and to register the implementation in the `fileTypeIndentOptionsProvider` extension point.
|
||||
The return value of `createIndentOptions()` determines the default indent size.
|
||||
|
||||
### Rearranger
|
||||
|
||||
**New in 12:**
|
||||
|
||||
Allows custom languages to provide user-configurable arrangement/grouping rules for element types supported by language plugin.
|
||||
Rules can be refined via modifiers and name, ordering can be applied additionally.
|
||||
Please see
|
||||
[Rearranger](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/psi/codeStyle/arrangement/Rearranger.java)
|
||||
and related for JavaDoc.
|
36
code_inspections_and_intentions.md
Normal file
36
code_inspections_and_intentions.md
Normal file
@ -0,0 +1,36 @@
|
||||
---
|
||||
layout: editable
|
||||
title: Code Inspections and Intentions
|
||||
---
|
||||
|
||||
The code inspections for custom languages use the same API as all other code inspections, based on the
|
||||
[LocalInspectionTool](https://github.com/JetBrains/intellij-community/blob/master/platform/analysis-api/src/com/intellij/codeInspection/LocalInspectionTool.java)
|
||||
class.
|
||||
|
||||
The functionality of
|
||||
[LocalInspectionTool](https://github.com/JetBrains/intellij-community/blob/master/platform/analysis-api/src/com/intellij/codeInspection/LocalInspectionTool.java)
|
||||
partially duplicates that of
|
||||
[Annotator](https://github.com/JetBrains/intellij-community/blob/master/platform/analysis-api/src/com/intellij/lang/annotation/Annotator.java).
|
||||
The main differences are that
|
||||
[LocalInspectionTool](https://github.com/JetBrains/intellij-community/blob/master/platform/analysis-api/src/com/intellij/codeInspection/LocalInspectionTool.java)
|
||||
supports batch analysis of code (through the **Analyze \| Inspect Code...** action), the possibility to turn off the inspection (globally or by suppressing them on various levels) and to configure the inspection options.
|
||||
If none of that is required and the analysis only needs to run in the active editor,
|
||||
[Annotator](https://github.com/JetBrains/intellij-community/blob/master/platform/analysis-api/src/com/intellij/lang/annotation/Annotator.java)
|
||||
provides better performance (because of its support for incremental analysis) and more flexibility for highlighting errors.
|
||||
|
||||
**Example**:
|
||||
A
|
||||
[simple inspection](https://github.com/JetBrains/intellij-community/blob/master/plugins/properties/properties-psi-impl/src/com/intellij/codeInspection/TrailingSpacesInPropertyInspection.java)
|
||||
for
|
||||
[Properties language plugin](https://github.com/JetBrains/intellij-community/blob/master/plugins/properties/)
|
||||
|
||||
|
||||
The code intentions for custom languages also use the regular API for intentions.
|
||||
The intention classes need to implement the
|
||||
[IntentionAction](https://github.com/JetBrains/intellij-community/blob/master/platform/analysis-api/src/com/intellij/codeInsight/intention/IntentionAction.java)
|
||||
interface and to be registered using the `<intentionAction>` bean in your *plugin.xml*.
|
||||
|
||||
**Example:**
|
||||
A
|
||||
[simple intention action](https://github.com/JetBrains/intellij-community/blob/master/plugins/groovy/src/org/jetbrains/plugins/groovy/intentions/control/SplitIfIntention.java)
|
||||
for Groovy
|
File diff suppressed because it is too large
Load Diff
18
documentation.md
Normal file
18
documentation.md
Normal file
@ -0,0 +1,18 @@
|
||||
---
|
||||
layout: editable
|
||||
title: Documentation
|
||||
---
|
||||
|
||||
To provide different kinds of documentation support (tooltips on **Ctrl-hover**, quick documentation popup etc.), the plugin needs to provide an implementation of the
|
||||
[DocumentationProvider](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/lang/documentation/DocumentationProvider.java)
|
||||
interface and register it in the `lang.documentationProvider` extension point.
|
||||
A standard base class for such implementations is available in the class
|
||||
[AbstractDocumentationProvider](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/lang/documentation/AbstractDocumentationProvider.java).
|
||||
|
||||
**Example**:
|
||||
[DocumentationProvider](https://github.com/JetBrains/intellij-community/blob/master/plugins/properties/src/com/intellij/lang/properties/PropertiesDocumentationProvider.java)
|
||||
for
|
||||
[Properties language plugin](https://github.com/JetBrains/intellij-community/blob/master/plugins/properties/)
|
||||
|
||||
|
||||
The `getQuickNavigateInfo()` method returns the text to be displayed when the user holds the mouse over an element with ```Ctrl``` pressed.
|
85
file_based_indexes.md
Normal file
85
file_based_indexes.md
Normal file
@ -0,0 +1,85 @@
|
||||
---
|
||||
layout: editable
|
||||
title: File-based Indexes
|
||||
---
|
||||
|
||||
File-based indexes are based on a MapReduce architecture.
|
||||
Each index has a certain type of key and a certain type of value.
|
||||
|
||||
The key is what's later used to retrieve data from the index.
|
||||
|
||||
*Example:* in the word index the key is the word itself.
|
||||
|
||||
The value is arbitrary data which is associated with the key in the index.
|
||||
|
||||
*Example:* in the word index the value is a mask indicating in which context the word occurs (code, string literal or comment).
|
||||
|
||||
In the simplest case, when we only need to know in what files some data occurs, the value has type Void and is not stored in the index.
|
||||
|
||||
When the index implementation indexes a file, it receives the content of a file and returns a map from the keys found in the file to the associated values.
|
||||
When you access the index, you specify the key that you're interested in and get back the list of files in which the key occurs and the value associated with each file.
|
||||
|
||||
## Implementing a File-based Index
|
||||
|
||||
A fairly simple file-based index implementation is the
|
||||
[UI Designer bound forms index](https://github.com/JetBrains/intellij-community/blob/master/plugins/ui-designer/src/com/intellij/uiDesigner/binding/FormClassIndex.java).
|
||||
Refer to it as an example to understand this topic better.
|
||||
|
||||
Each specific index implementation is a class extending
|
||||
[FileBasedIndexExtension](https://github.com/JetBrains/intellij-community/blob/master/platform/indexing-api/src/com/intellij/util/indexing/FileBasedIndexExtension.java).
|
||||
A file-base index should be registered in the <fileBasedIndex> extension point.
|
||||
|
||||
The implementation of a file-based contains of the following main parts:
|
||||
|
||||
* `getIndexer()` returns the indexer class, which is is responsible for actually building a set of key/value pairs based on the file content.
|
||||
|
||||
* `getKeyDescriptor()` returns the key descriptor, which is responsible for comparing the keys and storing them in a serialized binary format.
|
||||
Probably the most commonly used `KeyDescriptor` implementation is `EnumeratorStringDescriptor` implementation.
|
||||
It is designed for storing identifiers in an efficient way.
|
||||
|
||||
* `getValueExternalizer()` returns the value serializer, which takes care of storing values in a serialized binary format.
|
||||
|
||||
* `getInputFilter()` allows to restrict the indexing only to a certain set of files.
|
||||
|
||||
* `getVersion()` returns the version of the index implementation.
|
||||
The index is automatically rebuilt if the current version differs from the version of the index implementation used to build the index.
|
||||
|
||||
If you don't need to associate any value with the files (i.e. your value type is Void), you can simplify the implementation by using
|
||||
[ScalarIndexExtension](https://github.com/JetBrains/intellij-community/blob/master/platform/indexing-impl/src/com/intellij/util/indexing/ScalarIndexExtension.java)
|
||||
as the base class.
|
||||
|
||||
**Note:** The data returned by ```DataIndexer.map()``` must depend only on input data passed to the method, and must not depend on any external files.
|
||||
Otherwise your index will not be correctly updated when the external data changes, and you will have stale data in your index.
|
||||
|
||||
## Accessing a File-based Index
|
||||
|
||||
Access to file-based indexes is performed through the [FileBasedIndex](https://github.com/JetBrains/intellij-community/blob/master/platform/indexing-api/src/com/intellij/util/indexing/FileBasedIndex.java)
|
||||
class.
|
||||
The following primary operations are supported:
|
||||
|
||||
* `getAllKeys()` and `processAllKeys()` allow to obtain the list of all keys found in files which are part of the specified project.
|
||||
**Note:** The returned data is guaranteed to contain all keys found in up-to-date project content, but may also contain additional keys not currently found in the project.
|
||||
|
||||
* `getValues()` allows to obtain all values associated with a specific key but not the files in which they were found.
|
||||
|
||||
* `getContainingFiles()` allows to obtain all files in which a specific key was encountered.
|
||||
|
||||
* `processValues()` allows to iterate though all files in which a specific key was encountered and to access the associated values at the same time.
|
||||
|
||||
### Standard Indexes
|
||||
|
||||
IntelliJ Platform contains a number of the standard file-based indexes.
|
||||
The most useful indexes for plugin developers are:
|
||||
|
||||
* Word index
|
||||
* File name index
|
||||
|
||||
Generally, the word index should be accessed indirectly, but using the helper methods in the
|
||||
[PsiSearchHelper](https://github.com/JetBrains/intellij-community/blob/master/platform/indexing-api/src/com/intellij/psi/search/PsiSearchHelper.java)
|
||||
class.
|
||||
|
||||
The second index is
|
||||
[FilenameIndex](https://github.com/JetBrains/intellij-community/blob/master/platform/indexing-impl/src/com/intellij/psi/search/FilenameIndex.java).
|
||||
It provides a quick way to find all files matching a certain file name.
|
||||
[FileTypeIndex](https://github.com/JetBrains/intellij-community/blob/master/platform/indexing-impl/src/com/intellij/psi/search/FileTypeIndex.java)
|
||||
serves a similar goal: it allows to quickly find all files of a certain file type.
|
87
find_usages.md
Normal file
87
find_usages.md
Normal file
@ -0,0 +1,87 @@
|
||||
---
|
||||
layout: editable
|
||||
title: Find Usages
|
||||
---
|
||||
|
||||
The ```Find Usages``` action is a multi-step process, and each step of the process requires involvement from the custom language plugin.
|
||||
The language plugin participates in the Find Usages process by registering an implementation of
|
||||
[FindUsagesProvider](https://github.com/JetBrains/intellij-community/blob/master/platform/indexing-api/src/com/intellij/lang/findUsages/FindUsagesProvider.java)
|
||||
in the `com.intellij.lang.findUsagesProvider` extension point, and through the PSI implementation using
|
||||
[PsiNamedElement](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/psi/PsiNamedElement.java)
|
||||
and
|
||||
[PsiReference](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/psi/PsiReference.java)
|
||||
interfaces.
|
||||
|
||||
**Example**:
|
||||
Implementation of
|
||||
[FindUsagesProvider](https://github.com/JetBrains/intellij-community/blob/master/plugins/properties/properties-psi-impl/src/com/intellij/lang/properties/findUsages/PropertiesFindUsagesProvider.java)
|
||||
in
|
||||
[Properties language plugin](https://github.com/JetBrains/intellij-community/blob/master/plugins/properties/)
|
||||
|
||||
|
||||
The steps of the ```Find Usages``` action are the following:
|
||||
|
||||
* Before the ```Find Usages``` action can be invoked, the IDE builds an index of words present in every file in the custom language.
|
||||
Using the
|
||||
[WordsScanner](https://github.com/JetBrains/intellij-community/blob/master/platform/indexing-api/src/com/intellij/lang/cacheBuilder/WordsScanner.java)
|
||||
implementation returned from
|
||||
[FindUsagesProvider.getWordsScanner()](https://github.com/JetBrains/intellij-community/blob/master/platform/indexing-api/src/com/intellij/lang/findUsages/FindUsagesProvider.java),
|
||||
the contents of every file are loaded and passes it to the words scanner, along with the words consumer.
|
||||
The words scanner breaks the text into words, defines the context for each word (code, comments or literals) and passes the word to the consumer.
|
||||
The simplest way to implement the words scanner is to use the
|
||||
[DefaultWordsScanner](https://github.com/JetBrains/intellij-community/blob/master/platform/indexing-api/src/com/intellij/lang/cacheBuilder/DefaultWordsScanner.java)
|
||||
implementation, passing to it the sets of lexer token types which are treated as identifiers, literals and comments.
|
||||
The default words scanner will use the lexer to break the text into tokens, and will handle breaking the text of comment and literal tokens into individual words.
|
||||
|
||||
* When the user invokes the Find Usages action, the IDE locates the PSI element the references to which will be searched.
|
||||
The PSI element at the cursor (the direct tree parent of the token at the cursor position) must be either a
|
||||
[PsiNamedElement](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/psi/PsiNamedElement.java)
|
||||
or a
|
||||
[PsiReference](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/psi/PsiReference.java)
|
||||
which resolves to a
|
||||
[PsiNamedElement](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/psi/PsiNamedElement.java).
|
||||
The word cache will be used to search for the text returned from the
|
||||
[PsiNamedElement.getName()](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/psi/PsiNamedElement.java)
|
||||
method.
|
||||
Also, if the text range of the
|
||||
[PsiNamedElement](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/psi/PsiNamedElement.java)
|
||||
includes some other text besides the identifier returned from `getName()` (for example, if the
|
||||
[PsiNamedElement](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/psi/PsiNamedElement.java)
|
||||
represents a JavaScript function and its text range includes the "`function`" keyword in addition to the name of the function), the method `getTextOffset()` must be overridden for the
|
||||
[PsiNamedElement](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/psi/PsiNamedElement.java),
|
||||
and must return the start offset of the name identifier within the text range of the element.
|
||||
|
||||
* Once the element is located, the IDE calls
|
||||
[FindUsagesProvider.canFindUsagesFor()](https://github.com/JetBrains/intellij-community/blob/master/platform/indexing-api/src/com/intellij/lang/findUsages/FindUsagesProvider.java)
|
||||
to ask the plugin if the Find Usages action is applicable to the specific element.
|
||||
|
||||
* When showing the Find Usages dialog to the user,
|
||||
[FindUsagesProvider.getType()](https://github.com/JetBrains/intellij-community/blob/master/platform/indexing-api/src/com/intellij/lang/findUsages/FindUsagesProvider.java)
|
||||
and
|
||||
[FindUsagesProvider.getDescriptiveName()](https://github.com/JetBrains/intellij-community/blob/master/platform/indexing-api/src/com/intellij/lang/findUsages/FindUsagesProvider.java)
|
||||
are called to determine how the element should be presented to the user.
|
||||
|
||||
* For every file containing the searched words, the IDE builds the PSI tree and recursively descends that tree.
|
||||
The text of each element is broken into words and then scanned.
|
||||
If the element was indexed as an identifier, every word is checked to be a
|
||||
[PsiReference](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/psi/PsiReference.java)
|
||||
resolving to the element the usages of which are searched.
|
||||
If the element was indexed as a comment or literal and the search in comments or literals is enabled, it checks if the word is equal to the name of the searched element.
|
||||
|
||||
* After the usages are collected, results are shown in the usages pane.
|
||||
The text shown for each found element is taken from the
|
||||
[FindUsagesProvider.getNodeText()](https://github.com/JetBrains/intellij-community/blob/master/platform/indexing-api/src/com/intellij/lang/findUsages/FindUsagesProvider.java)
|
||||
method.
|
||||
|
||||
To have the title of the found element be correctly displayed in the title of the Find Usages toolwindow, you need to provide an implementation of the
|
||||
[ElementDescriptionProvider](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/psi/ElementDescriptionProvider.java)
|
||||
interface.
|
||||
The
|
||||
[ElementDescriptionLocation](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/psi/ElementDescriptionLocation.java)
|
||||
passed to the provider in this case will be an instance of
|
||||
[UsageViewLongNameLocation](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-impl/src/com/intellij/usageView/UsageViewLongNameLocation.java).
|
||||
|
||||
**Example:**
|
||||
[ElementDescriptionProvider](https://github.com/JetBrains/intellij-community/blob/master/plugins/properties/src/com/intellij/lang/properties/PropertiesDescriptionProvider.java)
|
||||
for
|
||||
[Properties language plugin](https://github.com/JetBrains/intellij-community/blob/master/plugins/properties/)
|
16
go_to_class_and_go_to_symbol.md
Normal file
16
go_to_class_and_go_to_symbol.md
Normal file
@ -0,0 +1,16 @@
|
||||
---
|
||||
layout: editable
|
||||
title: Go to Class and Go to Symbol
|
||||
---
|
||||
|
||||
A custom language plugin can provide its own items to be included in the lists shown when the user chooses the ```Go to | Class...``` or ```Go to | Symbol...``` action.
|
||||
In order to do so, the plugin must provide implementations for the
|
||||
[ChooseByNameContributor](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/navigation/ChooseByNameContributor.java)
|
||||
interface (separate implementations need to be provided for ```Go to Class``` and ```Go to Symbol```), and register them in the `com.intellij.gotoClassContributor` and `com.intellij.gotoSymbolContributor` extension points.
|
||||
|
||||
Each contributor needs to be able to return a complete list of names to show in the list for a specified project, which will then be filtered by the IDE according to the text typed by the user in the dialog.
|
||||
For each name in that list, the contributor needs to provide a list of
|
||||
[NavigationItem](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/navigation/NavigationItem.java)
|
||||
instances (typically
|
||||
[PsiElement](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/psi/PsiElement.java)
|
||||
), which specify the destinations to jump to when a specific name is selected from the list.
|
101
implementing_lexer.md
Normal file
101
implementing_lexer.md
Normal file
@ -0,0 +1,101 @@
|
||||
---
|
||||
layout: editable
|
||||
title: Implementing Lexer
|
||||
---
|
||||
|
||||
The lexer, or
|
||||
[lexical analyzer](http://en.wikipedia.org/wiki/Lexical_analysis),
|
||||
defines how the contents of a file is broken into tokens.
|
||||
The lexer serves as a foundation for nearly all of the features of custom language plugins, from basic syntax highlighting to advanced code analysis features.
|
||||
The API for the lexer is defined by the
|
||||
[Lexer](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/lexer/Lexer.java) interface.
|
||||
|
||||
The IDE invokes the lexer in three main contexts, and the plugin can provide different lexer implementations for these contexts:
|
||||
|
||||
* Syntax highlighting: The lexer is returned from the implementation of the
|
||||
[SyntaxHighlighterFactory](https://github.com/JetBrains/intellij-community/blob/master/platform/editor-ui-api/src/com/intellij/openapi/fileTypes/SyntaxHighlighterFactory.java)
|
||||
interface which is registered in the ```com.intellij.lang.syntaxHighlighterFactory``` extension point.
|
||||
|
||||
* Building the syntax tree of a file: the lexer is expected to be returned from
|
||||
[ParserDefinition.createLexer()](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/lang/ParserDefinition.java),
|
||||
and the
|
||||
[ParserDefinition](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/lang/ParserDefinition.java)
|
||||
interface is registered in the ```com.intellij.lang.parserDefinition``` extension point.
|
||||
|
||||
* Building the index of the words contained in the file:
|
||||
if the lexer-based words scanner implementation is used, the lexer is passed to the
|
||||
[DefaultWordsScanner](https://github.com/JetBrains/intellij-community/blob/master/platform/indexing-api/src/com/intellij/lang/cacheBuilder/DefaultWordsScanner.java)
|
||||
constructor.
|
||||
|
||||
The lexer used for syntax highlighting can be invoked incrementally to process only the changed part of a file, whereas lexers used in other contexts are always called to process an entire file, or a complete language construction embedded in a file in a different language.
|
||||
|
||||
A lexer that can be used incrementally may need to return its *state*, which means the context corresponding to each position in a file.
|
||||
For example, a
|
||||
[Java lexer](https://github.com/JetBrains/intellij-community/blob/master/java/java-psi-impl/src/com/intellij/lang/java/lexer/JavaLexer.java)
|
||||
could have separate states for top level context, comment context and string literal context.
|
||||
An important requirement for a syntax highlighting lexer is that its state must be represented by a single integer number returned from
|
||||
[Lexer.getState()](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/lexer/Lexer.java).
|
||||
That state will be passed to the
|
||||
[Lexer.start()](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/lexer/Lexer.java)
|
||||
method, along with the start offset of the fragment to process, when lexing is resumed from the middle of a file.
|
||||
Lexers used in other contexts can always return `0` from the `getState()` method.
|
||||
|
||||
The easiest way to create a lexer for a custom language plugin is to use [JFlex](http://jflex.de).
|
||||
Adapter classes,
|
||||
[FlexLexer](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/lexer/FlexLexer.java)
|
||||
and
|
||||
[FlexAdapter](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/lexer/FlexAdapter.java)
|
||||
adapt JFlex lexers to the IntelliJ Platform Lexer API.
|
||||
The source code of
|
||||
[IntelliJ IDEA Community Edition](https://github.com/JetBrains/intellij-community)
|
||||
includes a patched version of JFlex 1.4 located in *tools/lexer/jflex-1.4* and lexer skeleton file *tools/lexer/idea-flex.skeleton* which can be used for creating lexers compatible with
|
||||
[FlexAdapter](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/lexer/FlexAdapter.java).
|
||||
The patched version of JFlex provides a new command line option ```--charat``` which changes the JFlex generated code so that it works with the IntelliJ Platform skeleton.
|
||||
Enabling ```--charat``` option passes the source data for lexing as a
|
||||
[CharSequence](https://docs.oracle.com/javase/8/docs/api/java/lang/CharSequence.html)
|
||||
and not as an array of characters.
|
||||
|
||||
|
||||
For developing lexers using JFlex, the
|
||||
[JFlex Support](https://plugins.jetbrains.com/plugin/?id=263)
|
||||
plugin can be useful.
|
||||
It provides syntax highlighting and other useful features for editing JFlex files.
|
||||
[GrammarKit plugin](https://plugins.jetbrains.com/plugin/?id=6606)
|
||||
also has builtin JFlex support.
|
||||
|
||||
**Note:**
|
||||
Lexers, and in particular JFlex-based lexers, need to be created in such a way that they always match the entire contents of the file, without any gaps between tokens, and generate special tokens for characters which are not valid at their location.
|
||||
Lexers must never abort prematurely because of an invalid character.
|
||||
|
||||
**Example**:
|
||||
[Lexer](https://github.com/JetBrains/intellij-community/blob/master/plugins/properties/src/com/intellij/lang/properties/parsing/Properties.flex)
|
||||
definition for
|
||||
[Properties language plugin](https://github.com/JetBrains/intellij-community/blob/master/plugins/properties/src/com/intellij/lang/properties/)
|
||||
|
||||
|
||||
Types of tokens for lexers are defined by instances of
|
||||
[IElementType](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/psi/tree/IElementType.java).
|
||||
A number of token types common for all languages are defined in the
|
||||
[TokenType](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/psi/TokenType.java)
|
||||
interface.
|
||||
Custom language plugins should reuse these token types wherever applicable.
|
||||
For all other token types, the plugin needs to create new
|
||||
[IElementType](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/psi/tree/IElementType.java)
|
||||
instances and associate with the language in which the token type is used.
|
||||
The same
|
||||
[IElementType](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/psi/tree/IElementType.java)
|
||||
instance should be returned every time a particular token type is encountered by the lexer.
|
||||
|
||||
**Example:**
|
||||
[Token types](https://github.com/JetBrains/intellij-community/blob/master/plugins/properties/properties-psi-api/src/com/intellij/lang/properties/parsing/PropertiesTokenTypes.java)
|
||||
for
|
||||
[Properties language plugin](https://github.com/JetBrains/intellij-community/blob/master/plugins/properties/)
|
||||
|
||||
|
||||
An important feature which can be implemented at lexer level is mixing languages within a file, for example, embedding fragments of Java code in some template language.
|
||||
If a language supports embedding its fragments in another language, it needs to define the chameleon token types for different types of fragments which can be embedded, and these token types need to implement the
|
||||
[ILazyParseableElementType](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/psi/tree/ILazyParseableElementType.java)
|
||||
interface.
|
||||
The lexer of the enclosing language needs to return the entire fragment of the embedded language as a single chameleon token, of the type defined by the embedded language.
|
||||
To parse the contents of the chameleon token, the IDE will call the parser of the embedded language through a call to
|
||||
[ILazyParseableElementType.parseContents()](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/psi/tree/ILazyParseableElementType.java).
|
134
implementing_parser_and_psi.md
Normal file
134
implementing_parser_and_psi.md
Normal file
@ -0,0 +1,134 @@
|
||||
---
|
||||
layout: editable
|
||||
title: Implementing a Parser and PSI
|
||||
---
|
||||
|
||||
Parsing files in IntelliJ Platform is a two-step process.
|
||||
First, an abstract syntax tree (AST) is built, defining the structure of the program.
|
||||
AST nodes are created internally by the IDE and are represented by instances of the
|
||||
[ASTNode](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/lang/ASTNode.java)
|
||||
class.
|
||||
Each AST node has an associated element type
|
||||
[IElementType](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/psi/tree/IElementType.java)
|
||||
instance, and the element types are defined by the language plugin.
|
||||
The top-level node of the AST tree for a file needs to have a special element type, implementing the
|
||||
[IFileElementType](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/psi/tree/IFileElementType.java)
|
||||
interface.
|
||||
|
||||
The AST nodes have a direct mapping to text ranges in the underlying document.
|
||||
The bottom-most nodes of the AST match individual tokens returned by the lexer, and higher level nodes match multiple-token fragments.
|
||||
Operations performed on nodes of the AST tree, such as inserting, removing, reordering nodes and so on, are immediately reflected as changes to the text of the underlying document.
|
||||
|
||||
Second, a PSI, or Program Structure Interface, tree is built on top of the AST, adding semantics and methods for manipulating specific language constructs.
|
||||
Nodes of the PSI tree are represented by classes implementing the
|
||||
[PsiElement](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/psi/PsiElement.java)
|
||||
interface and are created by the language plugin in the
|
||||
[ParserDefinition.createElement()](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/lang/ParserDefinition.java)
|
||||
method.
|
||||
The top-level node of the PSI tree for a file needs to implement the
|
||||
[PsiFile](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/psi/PsiFile.java)
|
||||
interface, and is created in the
|
||||
[ParserDefinition.createFile()](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/lang/ParserDefinition.java)
|
||||
method.
|
||||
|
||||
**Example**:
|
||||
[ParserDefinition](https://github.com/JetBrains/intellij-community/blob/master/plugins/properties/properties-psi-impl/src/com/intellij/lang/properties/parsing/PropertiesParserDefinition.java)
|
||||
for
|
||||
[Properties language plugin](https://github.com/JetBrains/intellij-community/blob/master/plugins/properties/properties-psi-impl/src/com/intellij/lang/properties/)
|
||||
|
||||
|
||||
The lifecycle of the PSI is described in more detail in
|
||||
[Architectural Overview](architectural_overview.html).
|
||||
|
||||
The base classes for the PSI implementation, including
|
||||
[PsiFileBase](https://github.com/JetBrains/intellij-community/blob/master/core-impl/src/com/intellij/extapi/psi/PsiFileBase.java),
|
||||
the base implementation of
|
||||
[PsiFile](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/psi/PsiFile.java),
|
||||
and
|
||||
[ASTWrapperPsiElement](https://github.com/JetBrains/intellij-community/blob/master/platform/core-impl/src/com/intellij/extapi/psi/ASTWrapperPsiElement.java),
|
||||
the base implementation of
|
||||
[PsiElement](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/psi/PsiElement.java),
|
||||
are provided by *IntelliJ Platform*.
|
||||
|
||||
There is currently no ready way to reuse existing language grammars, for example, from ANTLR, for creating custom language parsers.
|
||||
The parsers need to be coded manually.
|
||||
|
||||
Custom language parser and PSI classes can be generated from grammars using
|
||||
[Grammar-Kit](https://plugins.jetbrains.com/plugin/?id=6606) plugin.
|
||||
Besides code generation it provides various features for editing grammar files: syntax highlighting, quick navigation, refactorings and more.
|
||||
The Grammar-Kit plugin is built using its own engine and its source code can be found on
|
||||
[GitHub](https://github.com/JetBrains/Grammar-Kit).
|
||||
|
||||
The language plugin provides the parser implementation as an implementation of the
|
||||
[PsiParser](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/lang/PsiParser.java)
|
||||
interface, returned from
|
||||
[ParserDefinition.createParser()](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/lang/ParserDefinition.java).
|
||||
The parser receives an instance of the
|
||||
[PsiBuilder](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/lang/PsiBuilder.java)
|
||||
class, which is used to get the stream of tokens from the lexer and to hold the intermediate state of the AST being built.
|
||||
The parser must process all tokens returned by the lexer up to the end of stream, in other words until
|
||||
[PsiBuilder.getTokenType()](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/lang/PsiBuilder.java)
|
||||
returns `null`, even if the tokens are not valid according to the language syntax.
|
||||
|
||||
**Example**:
|
||||
[PsiParser](https://github.com/JetBrains/intellij-community/blob/master/plugins/properties/properties-psi-impl/src/com/intellij/lang/properties/parsing/PropertiesParser.java)
|
||||
implementation for
|
||||
[Properties language plugin](https://github.com/JetBrains/intellij-community/blob/master/plugins/properties/properties-psi-impl/src/com/intellij/lang/properties/).
|
||||
|
||||
The parser works by setting pairs of markers (
|
||||
[PsiBuilder.Marker](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/lang/PsiBuilder.java)
|
||||
instances) within the stream of tokens received from the lexer.
|
||||
Each pair of markers defines the range of lexer tokens for a single node in the AST tree.
|
||||
If a pair of markers is nested in another pair (starts after its start and ends before its end), it becomes the child node of the outer pair.
|
||||
|
||||
The element type for the marker pair and for the AST node created from it is specified when the end marker is set, which is done by making call to
|
||||
[PsiBuilder.Marker.done()](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/lang/PsiBuilder.java).
|
||||
Also, it is possible to drop a start marker before its end marker has been set.
|
||||
The ```drop()``` method drops only a single start marker without affecting any markers added after it, and the ```rollbackTo()``` method drops the start marker and all markers added after it and reverts the lexer position to the start marker.
|
||||
These methods can be used to implement lookahead when parsing.
|
||||
|
||||
The method
|
||||
[PsiBuilder.Marker.precede()](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/lang/PsiBuilder.java)
|
||||
is useful for right-to-left parsing when you don't know how many markers you need at a certain position until you read more input.
|
||||
For example, a binary expression ```a+b+c``` needs to be parsed as ```( (a+b) + c )```.
|
||||
Thus, two start markers are needed at the position of the token 'a', but that is not known until the token 'c' is read.
|
||||
When the parser reaches the '+' token following 'b', it can call `precede()` to duplicate the start marker at 'a' position, and then put its matching end marker after 'c'.
|
||||
|
||||
An important feature of
|
||||
[PsiBuilder](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/lang/PsiBuilder.java)
|
||||
is its handling of whitespace and comments.
|
||||
The types of tokens which are treated as whitespace or comments are defined by the methods ```getWhitespaceTokens()``` and ```getCommentTokens()``` in the
|
||||
[ParserDefinition](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/lang/ParserDefinition.java)
|
||||
class.
|
||||
[PsiBuilder](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/lang/PsiBuilder.java)
|
||||
automatically omits whitespace and comment tokens from the stream of tokens it passes to
|
||||
[PsiParser](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/lang/PsiParser.java),
|
||||
and adjusts the token ranges of AST nodes so that leading and trailing whitespace tokens are not included in the node.
|
||||
|
||||
The token set returned from
|
||||
[ParserDefinition.getCommentTokens()](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/lang/ParserDefinition.java)
|
||||
is also used to search for TODO items.
|
||||
|
||||
In order to better understand the process of building a PSI tree for a simple expression, you can refer to the following diagram:
|
||||
|
||||

|
||||
|
||||
In general, there is no single right way to implement a PSI for a custom language, and the plugin author can choose the PSI structure and set of methods which are the most convenient for the code which uses the PSI (error analysis, refactorings and so on).
|
||||
However, there is one base interface which needs to be used by a custom language PSI implementation in order to support features like rename and find usages.
|
||||
Every element which can be renamed or referenced (a class definition, a method definition and so on) needs to implement the
|
||||
[PsiNamedElement](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/psi/PsiNamedElement.java)
|
||||
interface, with methods ```getName()``` and ```setName()```.
|
||||
|
||||
A number of functions which can be used for implementing and using the PSI can be found in the ```com.intellij.psi.util``` package, and in particular in the
|
||||
[PsiUtil](https://github.com/JetBrains/intellij-community/blob/master/java/java-psi-api/src/com/intellij/psi/util/PsiUtil.java)
|
||||
and
|
||||
[PsiTreeUtil](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/psi/util/PsiTreeUtil.java)
|
||||
classes.
|
||||
|
||||
A very helpful tool for debugging the PSI implementation is the
|
||||
[PsiViewer plugin](https://plugins.jetbrains.com/plugin/?id=227).
|
||||
It can show you the structure of the PSI built by your plugin, the properties of every PSI element and highlight its text range.
|
||||
|
||||
Please see
|
||||
[Indexing and PSI Stubs](indexing_and_psi_stubs.html)
|
||||
for advanced topics.
|
27
light_and_heavy_tests.md
Normal file
27
light_and_heavy_tests.md
Normal file
@ -0,0 +1,27 @@
|
||||
---
|
||||
layout: editable
|
||||
title: Light and Heavy Tests
|
||||
---
|
||||
|
||||
As mentioned above, plugin tests run in a real, rather than mocked, IntelliJ IDEA environment, and use real implementations for most of the application and project components. Loading the project components for a project is a fairly expensive operation, and we want to avoid doing it for each test.
|
||||
Because of that, we distinguish between *heavy* tests, which create a new project for each test, and *light* tests, which reuse a project from the previous test run when possible. Light and heavy tests use different base classes or fixture classes, as described below.
|
||||
|
||||
**Note:** Because of the performance difference, we recommend plugin developers to write light tests whenever possible.
|
||||
|
||||
The standard way of writing a light test is to extend the ```LightCodeInsightFixtureTestCase``` class (for tests that require the Java PSI or any related functionality) or ```LightPlatformCodeInsightFixtureTestCase``` (for tests that don't have any Java dependencies).
|
||||
|
||||
When writing a light test, you can specify the requirements for the project that you need to have in your test, such as the module type, the configured SDK, facets, libraries etc.
|
||||
You do so by extending the ```LightProjectDescriptor``` class and returning your project descriptor from ```LightCodeInsightFixtureTestCase.getProjectDescriptor()```.
|
||||
Before executing each test, the project will be reused if the test case returns the same project descriptor as the previous one, or recreated if the descriptor is different.
|
||||
|
||||
If you need to set up a multi-module project for your tests, you must write a heavy test. The setup code for a multi-module Java project looks something like that:
|
||||
|
||||
|
||||
```java
|
||||
final TestFixtureBuilder<IdeaProjectTestFixture> projectBuilder = IdeaTestFixtureFactory.getFixtureFactory().createFixtureBuilder(getName());
|
||||
|
||||
// repeat the following line for each module
|
||||
final JavaModuleFixtureBuilder moduleFixtureBuilder = projectBuilder.addModule(JavaModuleFixtureBuilder.class);
|
||||
|
||||
myFixture = JavaTestFixtureFactory.getFixtureFactory().createCodeInsightFixture(projectBuilder.getFixture());
|
||||
```
|
@ -35,7 +35,7 @@ tab : \u0009
|
||||
The easiest way to get the expected PSI structure for any file is to use PSI Viewer.
|
||||
Run the project and call ```Tools``` → ```View PSI Structure```.
|
||||
|
||||

|
||||

|
||||
|
||||
Use ```Copy PSI``` button to copy the expected PSI structure to the clipboard.
|
||||
|
||||
|
80
references_and_resolve.md
Normal file
80
references_and_resolve.md
Normal file
@ -0,0 +1,80 @@
|
||||
---
|
||||
layout: editable
|
||||
title: References and Resolve
|
||||
---
|
||||
|
||||
One of the most important and tricky parts in the implementation of a custom language PSI is resolving references.
|
||||
Resolving references means the ability to go from the usage of an element (access of a variable, call of a method and so on) to the declaration of the element (the variable definition, the method declaration and so on).
|
||||
This is obviously needed in order to support ```Go to Declaration``` action invoked by **Ctrl-B** and **Ctrl-Click**, and it is also a prerequisite for the ```Find Usages``` action, the ```Rename``` refactoring and the code completion.
|
||||
|
||||
All PSI elements which work as references (for which the ```Go to Declaration``` action applies) need to implement the
|
||||
[PsiElement.getReference()](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/psi/PsiElement.java)
|
||||
method and to return a
|
||||
[PsiReference](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/psi/PsiReference.java)
|
||||
implementation from that method.
|
||||
The
|
||||
[PsiReference](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/psi/PsiReference.java)
|
||||
interface can be implemented by the same class as
|
||||
[PsiElement](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/psi/PsiElement.java),
|
||||
or by a different class. An element can also contain multiple references (for example, a string literal can contain multiple substrings which are valid full-qualified class names), in which case it can implement
|
||||
[PsiElement.getReferences()](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/psi/PsiElement.java)
|
||||
and return the references as an array.
|
||||
|
||||
The main method of the
|
||||
[PsiReference](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/psi/PsiReference.java)
|
||||
interface is ```resolve()```, which returns the element to which the reference points, or ```null``` if it was not possible to resolve the reference to a valid element (for example, it points to an undefined class).
|
||||
A counterpart to this method is ```isReferenceTo()```, which checks if the reference resolves to the specified element.
|
||||
The latter method can be implemented by calling ```resolve()``` and comparing the result with the passed PSI element, but additional optimizations (for example, performing the tree walk only if the text of the element is equal to the text of the reference) are possible.
|
||||
|
||||
**Example**:
|
||||
[Reference](https://github.com/JetBrains/intellij-community/blob/master/plugins/properties/src/com/intellij/lang/properties/ResourceBundleReference.java)
|
||||
to a ResourceBundle in the
|
||||
[Properties language plugin](https://github.com/JetBrains/intellij-community/blob/master/plugins/properties/)
|
||||
|
||||
|
||||
There's a set of interfaces which can be used as a base for implementing resolve support, namely the
|
||||
[PsiScopeProcessor interface](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/psi/scope/PsiScopeProcessor.java) and the
|
||||
[PsiElement.processDeclarations()](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/psi/PsiElement.java)
|
||||
method.
|
||||
These interfaces have a number of extra complexities which are not necessary for most custom languages (like support for substituting Java generics types), but they are required if the custom language can have references to Java code.
|
||||
If Java interoperability is not required, the plugin can forgo the standard interfaces and provide its own, different implementation of resolve.
|
||||
|
||||
The implementation of resolve based on the standard helper classes contains of the following components:
|
||||
|
||||
* A class implementing the
|
||||
[PsiScopeProcessor](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/psi/scope/PsiScopeProcessor.java)
|
||||
interface which gathers the possible declarations for the reference and stops the resolve process when it has successfully completed.
|
||||
The main method which needs to be implemented is `execute()`, which is called to process every declaration encountered during the resolve, and returns `true` if the resolve needs to be continued or `false` if the declaration has been found.
|
||||
The methods `getHint()` and `handleEvent()` are used for internal optimizations and can be left empty in the
|
||||
[PsiScopeProcessor](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/psi/scope/PsiScopeProcessor.java)
|
||||
implementations for custom languages.
|
||||
|
||||
* A function which walks the PSI tree up from the reference location until the resolve has successfully completed or until the end of the resolve scope has been reached.
|
||||
If the target of the reference is located in a different file, the file can be located, for example, using
|
||||
[FilenameIndex.getFilesByName()](https://github.com/JetBrains/intellij-community/blob/master/platform/indexing-impl/src/com/intellij/psi/search/FilenameIndex.java)
|
||||
(if the file name is known) or by iterating through all custom language files in the project (`iterateContent()` in the
|
||||
[FileIndex](https://github.com/JetBrains/intellij-community/blob/master/platform/indexing-impl/src/com/intellij/psi/search/FilenameIndex.java)
|
||||
interface obtained from
|
||||
[ProjectRootManager.getFileIndex()](https://github.com/JetBrains/intellij-community/blob/master/platform/projectModel-api/src/com/intellij/openapi/roots/ProjectRootManager.java)
|
||||
).
|
||||
|
||||
* The individual PSI elements, on which the `processDeclarations()` method is called during the PSI tree walk.
|
||||
If a PSI element is a declaration, it passes itself to the `execute()` method of the
|
||||
[PsiScopeProcessor](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/psi/scope/PsiScopeProcessor.java)
|
||||
passed to it.
|
||||
Also, if necessary according to the language scoping rules, a PSI element can pass the
|
||||
[PsiScopeProcessor](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/psi/scope/PsiScopeProcessor.java)
|
||||
to its child elements.
|
||||
|
||||
An extension of the
|
||||
[PsiReference](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/psi/PsiReference.java)
|
||||
interface, which allows a reference to resolve to multiple targets, is the
|
||||
[PsiPolyVariantReference](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/psi/PsiPolyVariantReference.java)
|
||||
interface.
|
||||
The targets to which the reference resolves are returned from the `multiResolve()` method.
|
||||
The ```Go to Declaration``` action for such references allows the user to choose the target to navigate to.
|
||||
The implementation of `multiResolve()` can be also based on
|
||||
[PsiScopeProcessor](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/psi/scope/PsiScopeProcessor.java),
|
||||
and can collect all valid targets for the reference instead of stopping when the first valid target is found.
|
||||
|
||||
The ```Quick Definition Lookup``` action is based on the same mechanism as ```Go to Declaration```, so it becomes automatically available for all references which can be resolved by the language plugin.
|
26
registering_file_type.md
Normal file
26
registering_file_type.md
Normal file
@ -0,0 +1,26 @@
|
||||
---
|
||||
layout: editable
|
||||
title: Registering a File Type
|
||||
---
|
||||
|
||||
The first step in developing a custom language plugin is registering a file type the language will be associated with.
|
||||
The IDE normally determines the type of a file by looking at its file name.
|
||||
|
||||
A custom language file type is a class derived from
|
||||
[LanguageFileType](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/openapi/fileTypes/LanguageFileType.java),
|
||||
which passes a
|
||||
[Language](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/lang/Language.java)
|
||||
implementation class to its base class constructor.
|
||||
To register a file type, the plugin developer provides an implementation of the
|
||||
[FileTypeFactory](https://github.com/JetBrains/intellij-community/blob/master/platform/platform-api/src/com/intellij/openapi/fileTypes/FileTypeFactory.java)
|
||||
interface, which is registered via the ```com.intellij.fileTypeFactory```
|
||||
[platform extension point](https://github.com/JetBrains/intellij-community/blob/master/platform/platform-resources/src/META-INF/PlatformExtensionPoints.xml).
|
||||
|
||||
**Example**:
|
||||
[LanguageFileType](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/openapi/fileTypes/LanguageFileType.java)
|
||||
implementation in
|
||||
[Properties language plugin](https://github.com/JetBrains/intellij-community/blob/master/plugins/properties/properties-psi-api/src/com/intellij/lang/properties/PropertiesFileType.java)
|
||||
|
||||
To verify that the file type is registered correctly, you can implement the
|
||||
[LanguageFileType.getIcon()](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/openapi/fileTypes/LanguageFileType.java)
|
||||
method and verify that the correct icon is displayed for files which have the extension(s) associated with your file type.
|
69
rename_refactoring.md
Normal file
69
rename_refactoring.md
Normal file
@ -0,0 +1,69 @@
|
||||
---
|
||||
layout: editable
|
||||
title: Rename Refactoring
|
||||
---
|
||||
|
||||
|
||||
The operation of the Rename refactoring is quite similar to that of Find Usages.
|
||||
It uses the same rules for locating the element to be renamed, and the same index of words for locating the files which may have references to the element being renamed.
|
||||
|
||||
When the rename refactoring is performed, the method
|
||||
[PsiNamedElement.setName()](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/psi/PsiNamedElement.java)
|
||||
is called for the renamed element, and
|
||||
[PsiReference.handleElementRename()](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/psi/PsiReference.java)
|
||||
is called for all references to the renamed element.
|
||||
Both of these methods perform basically the same action: replace the underlying AST node of the PSI element with the node containing the new text entered by the user.
|
||||
Creating a fully correct AST node from scratch is quite difficult.
|
||||
Thus, surprisingly, the easiest way to get the replacement node is to create a dummy file in the custom language so that it would contain the necessary node in its parse tree, build the parse tree and extract the necessary node from it.
|
||||
|
||||
**Example:**
|
||||
[setName()](https://github.com/JetBrains/intellij-community/blob/master/plugins/properties/properties-psi-impl/src/com/intellij/lang/properties/psi/impl/PropertyImpl.java#L58)
|
||||
implementation for a
|
||||
[Properties language plugin](https://github.com/JetBrains/intellij-community/blob/master/plugins/properties/)
|
||||
|
||||
|
||||
Another interface related to the Rename refactoring is
|
||||
[NamesValidator](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/lang/refactoring/NamesValidator.java).
|
||||
This interface allows a plugin to check if the name entered by the user in the ```Rename``` dialog is a valid identifier (and not a keyword) according to the custom language rules.
|
||||
If an implementation of this interface is not provided by the plugin, Java rules for validating identifiers are used.
|
||||
Implementations of
|
||||
[NamesValidator](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/lang/refactoring/NamesValidator.java)
|
||||
are registered in the `com.intellij.lang.namesValidator` extension point.
|
||||
|
||||
**Example**:
|
||||
[NamesValidator](https://github.com/JetBrains/intellij-community/blob/master/plugins/properties/src/com/intellij/lang/properties/PropertiesNamesValidator.java)
|
||||
for
|
||||
[Properties language plugin](https://github.com/JetBrains/intellij-community/blob/master/plugins/properties/)
|
||||
|
||||
|
||||
Further customization of the Rename refactoring processing is possible on multiple levels.
|
||||
Providing a custom implementation of the
|
||||
[RenameHandler](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/refactoring/rename/RenameHandler.java)
|
||||
interface allows you to entirely replace the UI and workflow of the rename refactoring, and also to support renaming something which is not a
|
||||
[PsiElement](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/psi/PsiElement.java)
|
||||
at all.
|
||||
|
||||
**Example**:
|
||||
[RenameHandler](https://github.com/JetBrains/intellij-community/blob/master/plugins/properties/src/com/intellij/lang/properties/refactoring/rename/ResourceBundleFromEditorRenameHandler.java)
|
||||
for renaming a resource bundle
|
||||
|
||||
|
||||
If you're fine with the standard UI but need to extend the default logic of renaming, you can provide an implementation of the
|
||||
[RenamePsiElementProcessor](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-impl/src/com/intellij/refactoring/rename/RenamePsiElementProcessor.java)
|
||||
interface.
|
||||
This allows you to:
|
||||
|
||||
* Rename an element different from the one on which the action was invoked (a super method, for example)
|
||||
|
||||
* Rename multiple elements at once (if their names are linked according to the logic of your language)
|
||||
|
||||
* Check for name conflicts (existing names etc.)
|
||||
|
||||
* Customize how search for code references or text references is performed
|
||||
|
||||
* etc.
|
||||
|
||||
**Example**:
|
||||
[RenamePsiElementProcessor](https://github.com/JetBrains/intellij-community/blob/master/plugins/properties/src/com/intellij/lang/properties/refactoring/rename/RenamePropertyProcessor.java)
|
||||
for renaming a property in
|
||||
[Properties plugin language](https://github.com/JetBrains/intellij-community/blob/master/plugins/properties/)
|
89
run_configuration_execution.md
Normal file
89
run_configuration_execution.md
Normal file
@ -0,0 +1,89 @@
|
||||
---
|
||||
layout: editable
|
||||
title: Execution
|
||||
---
|
||||
|
||||
The standard execution of a run action goes through the following steps:
|
||||
|
||||
* The user selects a *run configuration* (for example, by choosing one from the run configurations combobox) and an *executor* (for example, by pressing a toolbar button created by the executor).
|
||||
|
||||
* The *ProgramRunner* that will actually execute the process is selected, by polling all registered program runners and asking whether they can run the specified RunProfile with the specified executor ID.
|
||||
|
||||
* The *ExecutionEnvironment* object is created. The object aggregates all the settings required to execute the process, as well as the selected ProgramRunner.
|
||||
|
||||
* ```ProgramRunner.execute()``` is called, receiving the executor and the execution environment.
|
||||
|
||||
* Implementations of ```ProgramRunner.execute()``` go through the following steps to execute the process:
|
||||
|
||||
* ```RunProfile.getState()``` method is called to create a ```RunProfileState``` object, describing a process about to be started. At this stage, the command line parameters, environment variables and other information required to start the process is initialized.
|
||||
|
||||
* ```RunProfileState.execute()``` is called. It starts the process, attaches a ```ProcessHandler``` to its input and output streams, creates a console to display the process output, and returns an ```ExecutionResult``` object aggregating the console and the process handler.
|
||||
|
||||
* The ```RunContentBuilder``` object is created and invoked to display the execution console in a tab of the Run or Debug toolwindow.
|
||||
|
||||
## Executor
|
||||
|
||||
The
|
||||
[Executor](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/execution/Executor.java)
|
||||
interface describes a specific way of executing any possible run configuration.
|
||||
The three default executors provided by the IntelliJ Platform by default are _Run_, _Debug_ and (in IntelliJ IDEA Ultimate and certain platform-based IDEs) _Run with Coverage_.
|
||||
Each executor gets its own toolbar button, which starts the selected run configuration using this executor, and its own context menu item for starting a configuration using this executor.
|
||||
|
||||
As a plugin developer, you normally don't need to implement the _Executor_ interface.
|
||||
However, it can be useful, for example, if you're implementing a profiler integration and want to provide the possibility to execute any configuration with profiling.
|
||||
|
||||
## Running a Process
|
||||
|
||||
The _RunProfileState_ interface comes up in every run configuration implementation as the return value ```RunProfile.getState()```.
|
||||
It describes a process which is ready to be started and holds the information like the command line, current working directory and environment variables for the process to be started.
|
||||
(The existence of RunProfileState as a separate step in the execution flow allows run configuration extensions and other components to patch the configuration and to modify the parameters before it gets executed.)
|
||||
|
||||
The standard base class used as implementation of RunProfileState is
|
||||
[CommandLineState](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/execution/configurations/CommandLineState.java).
|
||||
It contains the logic for putting together a running process and a console into an
|
||||
[ExecutionResult](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/execution/ExecutionResult.java),
|
||||
but doesn't know anything how the process is actually started. For starting the process, it's best to use the
|
||||
[GeneralCommandLine](https://github.com/JetBrains/intellij-community/blob/master/platform/platform-api/src/com/intellij/execution/configurations/GeneralCommandLine.java)
|
||||
class, which takes care of setting up the command line parameters and executing the process.
|
||||
|
||||
Alternatively, if the process you need to run is a JVM-based one, you can use the
|
||||
[JavaCommandLineState](https://github.com/JetBrains/intellij-community/blob/master/java/execution/openapi/src/com/intellij/execution/configurations/JavaCommandLineState.java)
|
||||
base class. It knows about the command line parameters of the JVM and can take care of details like calculating the classpath for the JVM.
|
||||
|
||||
To monitor the execution of a process and capture its output, the
|
||||
[OSProcessHandler](https://github.com/JetBrains/intellij-community/blob/master/platform/platform-api/src/com/intellij/execution/process/OSProcessHandler.java)
|
||||
class is normally used.
|
||||
Once you've created an instance of OSProcessHandler from either a command line or a Process object, you need to call the ```startNotify()``` method to start capturing its output.
|
||||
You may also want to attach a [ProcessTerminatedListener](https://github.com/JetBrains/intellij-community/blob/master/platform/platform-api/src/com/intellij/execution/process/ProcessTerminatedListener.java)
|
||||
to the OSProcessHandler, so that the exit status of the process will be displayed in the console.
|
||||
|
||||
## Displaying the Process Output
|
||||
|
||||
If you're using ```CommandLineState```, a console view will be automatically created and attached to the output of the process.
|
||||
Alternatively, you can arrange this yourself:
|
||||
|
||||
* ```TextConsoleBuilderFactory.createBuilder(project).getConsole()``` creates a
|
||||
[ConsoleView](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/execution/ui/ConsoleView.java)
|
||||
instance;
|
||||
|
||||
* ```ConsoleView.attachToProcess()``` attaches it to the output of a process.
|
||||
|
||||
If the process you're running uses ANSI escape codes to color its output, the
|
||||
[ColoredProcessHandler](https://github.com/JetBrains/intellij-community/blob/master/platform/platform-api/src/com/intellij/execution/process/ColoredProcessHandler.java)
|
||||
class will parse it and display the colors in the IntelliJ console.
|
||||
|
||||
Console
|
||||
[filters](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/execution/filters/Filter.java)
|
||||
allow you to convert certain strings found in the process output to clickable hyperlinks. To attach a filter to the console, use ```CommandLineState.addConsoleFilters()``` or, if you're creating a console manually, ```TextConsoleBuilder.addFilter()```.
|
||||
Two common filter implementations you may want to reuse are
|
||||
[RegexpFilter](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/execution/filters/RegexpFilter.java)
|
||||
and
|
||||
[UrlFilter](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/execution/filters/UrlFilter.java).
|
||||
|
||||
## Starting a Run Configuration from Code
|
||||
|
||||
If you have an existing run configuration that you need to execute, the easiest way to do so is to use
|
||||
[ProgramRunnerUtil.executeConfiguration](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-impl/src/com/intellij/execution/ProgramRunnerUtil.java#L110).
|
||||
The method takes a Project, a RunnerAndConfigurationSettings, as well as an Executor.
|
||||
To get the RunnerAndConfigurationSettings for an existing configuration, you can use, for example, ```RunManager.getConfigurationSettings(ConfigurationType)```.
|
||||
As the last parameter, you normally pass either ```DefaultRunExecutor.getRunExecutorInstance()``` or ```DefaultDebugExecutor.getDebugExecutorInstance()```.
|
162
run_configuration_management.md
Normal file
162
run_configuration_management.md
Normal file
@ -0,0 +1,162 @@
|
||||
---
|
||||
layout: editable
|
||||
title: Run Configuration Management
|
||||
---
|
||||
|
||||
This document describes main classes to work with run configurations and common use case.
|
||||
|
||||
* [ConfigurationType](#configuration-type)
|
||||
* [ConfigurationFactory](#configuration-factory)
|
||||
* [RunConfiguration](#run-configuration)
|
||||
* [SettingsEditor](#settings-editor)
|
||||
* [Persistence](#persistence)
|
||||
* [Refactoring Support](#refactoring-support)
|
||||
* [Creating Configurations from Context](#creating-from-context)
|
||||
|
||||
<div id="configuration-type"/>
|
||||
|
||||
## ConfigurationType
|
||||
|
||||
The starting point for implementing any run configuration type is the
|
||||
[ConfigurationType](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/execution/configurations/ConfigurationType.java)
|
||||
interface.
|
||||
List of available configuration type is shown when a user opens _'Edit run configurations'_ dialog and executes _'Add'_ action:
|
||||
|
||||

|
||||
|
||||
Every type there is represented as an instance of
|
||||
[ConfigurationType](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/execution/configurations/ConfigurationType.java)
|
||||
and registered like below:
|
||||
|
||||
```xml
|
||||
<configurationType implementation="org.jetbrains.plugins.gradle.service.execution.GradleExternalTaskConfigurationType"/>
|
||||
```
|
||||
|
||||
The easiest way to implement this interface is to use the
|
||||
[ConfigurationTypeBase](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/execution/configurations/ConfigurationTypeBase.java) base class. In order to use it, you need to inherit from it and to provide the configuration type parameters (ID, name, description and icon) as constructor parameters. In addition to that, you need to call the
|
||||
[addFactory()](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/execution/configurations/ConfigurationTypeBase.java#L46)
|
||||
method to add a configuration factory.
|
||||
|
||||
<div id="configuration-factory"/>
|
||||
|
||||
## ConfigurationFactory
|
||||
|
||||
All run configurations are created by
|
||||
[ConfigurationFactory](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/execution/configurations/ConfigurationFactory.java)
|
||||
registered for particular _ConfigurationType_.
|
||||
It's possible that one _ConfigurationType_
|
||||
[has more than one](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/execution/configurations/ConfigurationType.java#L34)
|
||||
_ConfigurationFactory_:
|
||||
|
||||

|
||||
|
||||
The key API of
|
||||
[ConfigurationFactory](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/execution/configurations/ConfigurationFactory.java),
|
||||
and the only method that you're required to implement, is the
|
||||
[createTemplateConfiguration](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/execution/configurations/ConfigurationFactory.java#L45)
|
||||
method.
|
||||
This method is called once per project to create the template run configuration.
|
||||
All real run configurations (loaded from the workspace or created by the user) are called by cloning the template through the
|
||||
[createConfiguration](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/execution/configurations/ConfigurationFactory.java#L39)
|
||||
method.
|
||||
|
||||
You can customize additional aspects of your configuration factory by overriding the
|
||||
[getIcon](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/execution/configurations/ConfigurationFactory.java#L59),
|
||||
[getAddIcon](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/execution/configurations/ConfigurationFactory.java#L55),
|
||||
[getName](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/execution/configurations/ConfigurationFactory.java#L51)
|
||||
and the default settings methods.
|
||||
These additional overrides are optional.
|
||||
|
||||
<div id="run-configuration"/>
|
||||
|
||||
## RunConfiguration
|
||||
|
||||
Is represented by
|
||||
[RunConfiguration](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/execution/configurations/RunConfiguration.java)
|
||||
interface.
|
||||
_'Run configuration'_ here is some named profile which can be executed, e.g. application started via _'main()'_ class, test, remote debug to particular machine/port etc.
|
||||
Here is an example of a Java run configurations defined for a particular project:
|
||||
|
||||

|
||||
|
||||
When implementing a run configuration, you may want to use one of the common base classes:
|
||||
|
||||
* [RunConfigurationBase](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/execution/configurations/RunConfigurationBase.java)
|
||||
is a general-purpose superclass that contains the most basic implementation of a run configuration.
|
||||
|
||||
* [LocatableConfigurationBase](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/execution/configurations/LocatableConfigurationBase.java)
|
||||
is a common base class that should be used for configurations that can be created from context by a ```RunConfigurationProducer```.
|
||||
It supports automatically generating a name for a configuration from its settings and keeping track of whether the name was changed by the user.
|
||||
|
||||
* [ModuleBasedConfiguration](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/execution/configurations/ModuleBasedConfiguration.java)
|
||||
is a base class for a configuration that is associated with a specific module (for example, Java run configurations use the selected module to determine the run classpath).
|
||||
|
||||
<div id="settings-editor"/>
|
||||
|
||||
## SettingsEditor
|
||||
|
||||
That common run configuration settings might be modified via:
|
||||
|
||||
* [_RunConfiguration_-specific UI](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/execution/configurations/RunConfiguration.java#L48).
|
||||
That is handled by [SettingsEditor](https://github.com/JetBrains/intellij-community/blob/master/platform/platform-api/src/com/intellij/openapi/options/SettingsEditor.java#L97):
|
||||
* [_getComponent()_](https://github.com/JetBrains/intellij-community/blob/master/platform/platform-api/src/com/intellij/openapi/options/SettingsEditor.java#L97)
|
||||
method is called by the ide and shows run configuration specific UI;
|
||||
* [_resetFrom()_](https://github.com/JetBrains/intellij-community/blob/master/platform/platform-api/src/com/intellij/openapi/options/SettingsEditor.java#L83)
|
||||
is called to discard all non-confirmed user changes made via that UI;
|
||||
* [_applyTo()_](https://github.com/JetBrains/intellij-community/blob/master/platform/platform-api/src/com/intellij/openapi/options/SettingsEditor.java#L93)
|
||||
is called to confirm the changes, i.e. copy current UI state into the target settings object;
|
||||
|
||||
<div id="persistence"/>
|
||||
|
||||
## Persistence
|
||||
|
||||
That run configuration settings are persistent, i.e. they are stored at file system and loaded back on the ide startup.
|
||||
That is performed via
|
||||
[writeExternal()](https://github.com/JetBrains/intellij-community/blob/master/platform/util/src/com/intellij/openapi/util/JDOMExternalizable.java#L27)
|
||||
and
|
||||
[readExternal()](https://github.com/JetBrains/intellij-community/blob/master/platform/util/src/com/intellij/openapi/util/JDOMExternalizable.java#L26)
|
||||
methods of
|
||||
[RunConfiguration](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/execution/configurations/RunConfiguration.java)
|
||||
class correspondingly.
|
||||
|
||||
The actual configurations stored by the IntelliJ Platform are represented by instances of the
|
||||
[RunnerAndConfigurationSettings](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/execution/RunnerAndConfigurationSettings.java)
|
||||
class, which combines a run configuration with runner-specific settings, as well as keeping track of certain run configuration flags such as "temporary" or "singleton".
|
||||
|
||||
Dealing with instances of this class becomes necessary when you need to create run configurations from code. This is accomplished with the following two steps:
|
||||
|
||||
* ```RunManager.createConfiguration()``` creates an instance of ```RunnerAndConfigurationSettings```;
|
||||
|
||||
* ```RunManager.addConfiguration()``` makes it persistent by adding it to either the list of shared configurations stored in a project, or to the list of local configurations stored in the workspace file.
|
||||
|
||||
<div id="refactoring-support"/>
|
||||
|
||||
## Refactoring Support
|
||||
|
||||
Most run configurations contain references to classes, files or directories in their settings, and these settings usually need to be updated when the corresponding element is renamed or moved.
|
||||
In order to support that, your run configuration needs to implement the
|
||||
[RefactoringListenerProvider](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/execution/configurations/RefactoringListenerProvider.java)
|
||||
interface.
|
||||
In your implementation of ```getRefactoringElementListener()```, you need to check whether the element being refactored is the one that your run configuration refers to, and if it is, you return a ```RefactoringElementListener``` that updates your configuration according to the new name and location of the element.
|
||||
|
||||
<div id="creating-from-context"/>
|
||||
|
||||
## Creating Configurations from Context
|
||||
|
||||
Many plugins support automatic creation of run configurations from context, so that the user can click, for example, on an application or test class and automatically run it using the correct run configuration type. In order to support that, you need to provide an implementation of the
|
||||
[RunConfigurationProducer](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/execution/actions/RunConfigurationProducer.java)
|
||||
interface and to register it as \<runConfigurationProducer\> in your plugin.xml.
|
||||
(Note that this API has been redesigned in IntelliJ IDEA 13; ```RuntimeConfigurationProducer``` is an older and much more confusing version of the same API).
|
||||
|
||||
The two main methods that you need to implement are:
|
||||
|
||||
* ```setupConfigurationFromContext``` receives a blank configuration of your type and a ```ConfigurationContext``` containing information about a source code location (accessible by calling ```getLocation()``` or ```getPsiLocation()```).
|
||||
Your implementation needs to check whether the location is applicable for your configuration type (for example, if it's in a file of the language you're supporting).
|
||||
If not, you need to return false, and if it is, you need to put the correct context-specific settings into the run configuration and return true.
|
||||
|
||||
* ```isConfigurationFromContext``` checks if the specified configuration of your type was created from the specified context.
|
||||
Implementing this method allows you to reuse an existing run configuration which is applicable to the current context instead of creating a new one and possibly ignoring the customisations the user has performed in the existing one.
|
||||
|
||||
Note that, in order to support automatic naming of configurations created from context, your configuration should use
|
||||
[LocatableConfigurationBase](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/execution/configurations/LocatableConfigurationBase.java)
|
||||
as the base class.
|
34
safe_delete_refactoring.md
Normal file
34
safe_delete_refactoring.md
Normal file
@ -0,0 +1,34 @@
|
||||
---
|
||||
layout: editable
|
||||
title: Safe Delete Refactoring
|
||||
---
|
||||
|
||||
|
||||
The ```Safe Delete``` refactoring also builds on the same ```Find Usages``` framework as ```Rename```.
|
||||
In addition to that, in order to support ```Safe Delete```, a plugin needs to implement two things:
|
||||
|
||||
* The
|
||||
[RefactoringSupportProvider](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/lang/refactoring/RefactoringSupportProvider.java)
|
||||
interface, registered in the `com.intellij.lang.refactoringSupport` extension point, and the `isSafeDeleteAvailable()` method, which checks if the ```Safe Delete``` refactoring is available for a specific PSI element
|
||||
|
||||
* The
|
||||
[PsiElement.delete()](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/psi/PsiElement.java#L371)
|
||||
method for the
|
||||
[PsiElement](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/psi/PsiElement.java)
|
||||
subclasses for which ```Safe Delete``` is available.
|
||||
Deleting PSI elements is implemented by deleting the underlying AST nodes from the AST tree (which, in turn, causes the text ranges corresponding to the AST nodes to be deleted from the document).
|
||||
|
||||
|
||||
**Example:**
|
||||
[delete()](https://github.com/JetBrains/intellij-community/blob/master/plugins/properties/properties-psi-impl/src/com/intellij/lang/properties/psi/impl/PropertyImpl.java#L363)
|
||||
implementation for a
|
||||
[Property language plugin](https://github.com/JetBrains/intellij-community/blob/master/plugins/properties/)
|
||||
|
||||
|
||||
If needed, it's possible to further customize how Safe Delete is performed for a particular type of element (how references are searched, etc).
|
||||
This is done by implementing the `SafeDeleteProcessorDelegate` interface.
|
||||
|
||||
|
||||
**Example**:
|
||||
[SafeDeleteProcessorDelegate](https://github.com/JetBrains/intellij-community/blob/master/plugins/properties/src/com/intellij/lang/properties/refactoring/PropertiesFilesSafeDeleteProcessor.java)
|
||||
implementation for a .properties file
|
56
structure_view.md
Normal file
56
structure_view.md
Normal file
@ -0,0 +1,56 @@
|
||||
---
|
||||
layout: editable
|
||||
title: Structure View
|
||||
---
|
||||
|
||||
The Structure View implementation used for a specific file type can be customized on many levels.
|
||||
If a custom language plugin provides an implementation of the
|
||||
[StructureView](https://github.com/JetBrains/intellij-community/blob/master/platform/structure-view-api/src/com/intellij/ide/structureView/StructureView.java)
|
||||
interface, it can completely replace the standard structure view implementation with a custom user interface component.
|
||||
However, for most languages this is not necessary, and the standard
|
||||
[StructureView](https://github.com/JetBrains/intellij-community/blob/master/platform/structure-view-api/src/com/intellij/ide/structureView/StructureView.java)
|
||||
implementation provided by *IntelliJ Platform* can be reused.
|
||||
|
||||
The starting point for the structure view is the
|
||||
[PsiStructureViewFactory](https://github.com/JetBrains/intellij-community/blob/master/platform/structure-view-api/src/com/intellij/lang/PsiStructureViewFactory.java)
|
||||
interface, which is registered in the `com.intellij.lang.psiStructureViewFactory` extension point.
|
||||
|
||||
**Example:**
|
||||
[PsiStructureViewFactory](https://github.com/JetBrains/intellij-community/blob/master/plugins/properties/src/com/intellij/lang/properties/structureView/PropertiesStructureViewBuilderFactory.java)
|
||||
for .properties files
|
||||
|
||||
|
||||
To reuse the *IntelliJ Platform* implementation of the
|
||||
[StructureView](https://github.com/JetBrains/intellij-community/blob/master/platform/structure-view-api/src/com/intellij/ide/structureView/StructureView.java),
|
||||
the plugin returns a
|
||||
[TreeBasedStructureViewBuilder](https://github.com/JetBrains/intellij-community/blob/master/platform/structure-view-api/src/com/intellij/ide/structureView/TreeBasedStructureViewBuilder.java)
|
||||
from its
|
||||
[PsiStructureViewFactory.getStructureViewBuilder()](https://github.com/JetBrains/intellij-community/blob/master/platform/structure-view-api/src/com/intellij/lang/PsiStructureViewFactory.java#L35)
|
||||
method.
|
||||
As the model for the builder, the plugin can specify a subclass of
|
||||
[TextEditorBasedStructureViewModel](https://github.com/JetBrains/intellij-community/blob/master/platform/structure-view-api/src/com/intellij/ide/structureView/TextEditorBasedStructureViewModel.java),
|
||||
and by overriding methods of this subclass it customizes the structure view for a specific language.
|
||||
|
||||
**Example**:
|
||||
[StructureViewModel](https://github.com/JetBrains/intellij-community/blob/master/plugins/properties/properties-psi-impl/src/com/intellij/lang/properties/structureView/PropertiesFileStructureViewModel.java)
|
||||
for .properties files
|
||||
|
||||
|
||||
The main method to override is `getRoot()`, which returns the instance of a class implementing the
|
||||
[StructureViewTreeElement](https://github.com/JetBrains/intellij-community/blob/master/platform/structure-view-api/src/com/intellij/ide/structureView/StructureViewTreeElement.java)
|
||||
interface.
|
||||
There exists no standard implementation of this interface, so a plugin will need to implement it completely.
|
||||
|
||||
The structure view tree is usually built as a partial mirror of the PSI tree.
|
||||
In the implementation of
|
||||
`StructureViewTreeElement.getChildren()`,
|
||||
the plugin can specify which of the child elements of a specific PSI tree node need to be represented as elements in the structure view.
|
||||
Another important method is `getPresentation()`, which can be used to customize the text, attributes and icon used to represent an element in the structure view.
|
||||
|
||||
The implementation of `StructureViewTreeElement.getChildren()` needs to be matched by `TextEditorBasedStructureViewModel.getSuitableClasses()`.
|
||||
The latter method returns an array of `PsiElement`\-derived classes which can be shown as structure view elements, and is used to select the Structure View item matching the cursor position when the structure view is first opened or when the ```Autoscroll from source``` option is used.
|
||||
|
||||
**Example:**
|
||||
[StructureViewElement](https://github.com/JetBrains/intellij-community/blob/master/plugins/properties/properties-psi-impl/src/com/intellij/lang/properties/structureView/PropertiesStructureViewElement.java)
|
||||
for a
|
||||
[Properties language plugin](https://github.com/JetBrains/intellij-community/blob/master/plugins/properties/)
|
82
stub_indexes.md
Normal file
82
stub_indexes.md
Normal file
@ -0,0 +1,82 @@
|
||||
---
|
||||
layout: editable
|
||||
title: Stub Indexes
|
||||
---
|
||||
|
||||
## Stub Trees
|
||||
|
||||
A stub tree is a subset of the PSI tree for a file; it is stored in a compact serialized binary format.
|
||||
The PSI tree for a file can be backed either by the AST (built by parsing the text of the file) or by the stub tree deserialized from disk.
|
||||
Switching between the two is transparent.
|
||||
The stub tree contains only a subset of the nodes.
|
||||
Typically it contains only the nodes that are needed to resolve the declarations contained in this file from external files.
|
||||
Trying to access any node which is not part of the stub tree, or to perform any operation which cannot be satisfied by the stub tree,
|
||||
e.g. accessing the text of a PSI element, causes file parsing and switches the PSI to AST backing.
|
||||
|
||||
Each stub in the stub tree is simply a bean class with no behavior.
|
||||
A stub stores a subset of the state of the corresponding PSI element, like element's name, modifier flags like public or final, etc.
|
||||
The stub also holds a pointer to its parent in the tree and a list of its children stubs.
|
||||
|
||||
To support stubs for your custom language, you first need to decide which of the elements of your PSI tree should be stored as stubs.
|
||||
Typically you need to have stubs for things like methods or fields, which are visible from other files.
|
||||
You usually don't need to have stubs for things like statements or local variables, which are not visible externally.
|
||||
|
||||
For each element type that you want to store in the stub tree, you need to perform the following steps:
|
||||
|
||||
* Define an interface for the stub, derived from the `StubElement` interface ([example](https://github.com/JetBrains/intellij-community/blob/master/plugins/properties/properties-psi-api/src/com/intellij/lang/properties/psi/PropertyStub.java)).
|
||||
|
||||
* Provide an implementation for the interface ([example](https://github.com/JetBrains/intellij-community/blob/master/plugins/properties/properties-psi-impl/src/com/intellij/lang/properties/psi/impl/PropertyStubImpl.java)).
|
||||
|
||||
* Make sure that the interface for the PSI element extends `StubBasedPsiElement` parameterized by the type of the stub interface ([example](https://github.com/JetBrains/intellij-community/blob/master/plugins/properties/properties-psi-api/src/com/intellij/lang/properties/psi/Property.java)).
|
||||
|
||||
* Make sure that the implementation class for the PSI element extends `StubBasedPsiElementBase` parameterized by the type of the stub interface ([example](https://github.com/JetBrains/intellij-community/blob/master/plugins/properties/src/com/intellij/lang/properties/psi/impl/PropertyImpl.java#L45)). Provide both a constructor that accepts an ASTNode and a constructor which accepts a stub.
|
||||
|
||||
* Create a class which implements `IStubElementType` and is parameterized with the stub interface and the actual PSI element interface ([example](https://github.com/JetBrains/intellij-community/blob/master/plugins/properties/properties-psi-impl/src/com/intellij/lang/properties/parsing/PropertyStubElementType.java)). Implement the createPsi() and createStub() methods for creating PSI from a stub and vice versa. Implement the serialize() and deserialize() methods for storing the data in a binary stream.
|
||||
|
||||
* Use the class implementing `IStubElementType` as the element type constant when parsing ([example](https://github.com/JetBrains/intellij-community/blob/master/plugins/properties/properties-psi-impl/src/com/intellij/lang/properties/parsing/PropertiesElementTypes.java))
|
||||
|
||||
* Make sure that all methods in the PSI element interface access the stub data rather than the PSI tree when appropriate ([example: Property.getKey() implementation](https://github.com/JetBrains/intellij-community/blob/master/plugins/properties/properties-psi-impl/src/com/intellij/lang/properties/psi/impl/PropertyImpl.java#L95))
|
||||
|
||||
The following steps need to be performed only once for each language that supports stubs:
|
||||
|
||||
* Change the file element type for your language (the element type that you return from ```ParserDefinition.getFileNodeType()```) to a class that extends IStubFileElementType.
|
||||
|
||||
* In your plugin.xml, define the ```<stubElementTypeHolder>``` extension and specify the interface which contains the `IElementType` constants used by your language's parser ([example](https://github.com/JetBrains/intellij-community/blob/master/plugins/properties/src/META-INF/plugin.xml#L55)).
|
||||
|
||||
For serializing string data, e.g. element names, in stubs, we recommend to use ```StubOutputStream.writeName()``` and ```StubInputStream.readName()``` methods.
|
||||
These methods ensure that each unique identifier is stored only once in the data stream.
|
||||
This reduces the size of the serialized stub tree data.
|
||||
|
||||
If you need to change the stored binary format for the stubs (for example, if you want to store some additional data or some new elements),
|
||||
make sure that you advance the stub version returned from `IStubFileElementType.getStubVersion()` for your language.
|
||||
This will cause the stubs and stub indices rebuilt, and will avoid mismatches between the stored data format and the code trying to load it.
|
||||
|
||||
By default, if a PSI element extends `StubBasedPsiElement`, all elements of that type will be stored in the stub tree.
|
||||
If you need more precise control over which elements are stored, override `IStubElementType.shouldCreateStub()` and return `false` for elements which should not be included in the stub tree.
|
||||
|
||||
**Note:** The exclusion is not recursive: if some elements of the element for which you returned false are also stub-based PSI elements, they will be included in the stub tree.
|
||||
|
||||
It's essential to make sure that all information stored in the stub tree depends only on the contents of the file for which stubs are being built, and does not depend on any external files.
|
||||
Otherwise the stub tree will not be rebuilt when an external dependency changes, and you will have stale and incorrect data in the stub tree.
|
||||
|
||||
## Stub Indexes
|
||||
|
||||
When building the stub tree, you can at the same time put some data about the stub elements into a number of indexes, which then can be used to find the PSI elements by the corresponding key. Unlike file-based indexes, stub indexes do not support storing custom data as values; the value is always a PSI element. Keys in stub indexes are normally strings (such as class names); other data types are also supported if desired.
|
||||
|
||||
A stub index is a class which extends [AbstractStubIndex](https://github.com/JetBrains/intellij-community/blob/master/platform/indexing-api/src/com/intellij/psi/stubs/AbstractStubIndex.java). In the most common case, when the key type is String, you use a more specific base class, namely [StringStubIndexExtension](https://github.com/JetBrains/intellij-community/blob/master/platform/indexing-api/src/com/intellij/psi/stubs/StringStubIndexExtension.java).
|
||||
Stub index implementation classes are registered in the ```<stubIndex>``` extension point.
|
||||
|
||||
To put data into an index, you implement the method ```IStubElementType.indexStub()``` ([example: JavaClassElementType.indexStub()](https://github.com/JetBrains/intellij-community/blob/master/java/java-psi-impl/src/com/intellij/psi/impl/java/stubs/JavaClassElementType.java#L189)). This method accepts an ```IndexSink``` as a parameter, and puts in the index ID and the key for each index in which the element should be stored.
|
||||
|
||||
To access the data from an index, the following two methods are used:
|
||||
|
||||
* `AbstractStubIndex.getAllKeys()` returns the list all keys in the specified index for the specified project (for example, the list of all class names found in the project);
|
||||
|
||||
* `AbstractStubIndex.get()` returns the collection of PSI elements corresponding to a certain key (for example, classes with the specified short name) in the specified scope.
|
||||
|
||||
### Related Forum Discussions
|
||||
|
||||
* [Lifecycle of stub creation](http://devnet.jetbrains.com/message/5485343)
|
||||
|
||||
|
||||
|
20
surround_with.md
Normal file
20
surround_with.md
Normal file
@ -0,0 +1,20 @@
|
||||
---
|
||||
layout: editable
|
||||
title: Surround With
|
||||
---
|
||||
|
||||
In order to support the ```Code | Surround With...``` action, the plugin needs to register one or more implementations of the
|
||||
[SurroundDescriptor](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/lang/surroundWith/SurroundDescriptor.java)
|
||||
interface in the `com.intellij.lang.surroundDescriptor` extension point.
|
||||
Each of the surround descriptors defines a possible type of code fragment which can be surrounded - for example, one surround descriptor can handle surrounding expressions, and another can handle statements.
|
||||
Each surround descriptor, in turn, contains an array of
|
||||
[Surrounder](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/lang/surroundWith/Surrounder.java)
|
||||
objects, defining specific templates which can be used for surrounding the selected code fragment (for example, ```Surround With if```, ```Surround With for``` and so on).
|
||||
|
||||
When the ```Surround With...``` action is invoked, the IDE queries all surround descriptors for the language until it finds one that returns a non-empty array from its `getElementsToSurround()` method.
|
||||
Then it calls the
|
||||
[Surrounder.isApplicable()](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/lang/surroundWith/Surrounder.java#L46)
|
||||
method for each surrounder in that descriptor to check if the specific template is applicable in the current context.
|
||||
Once the user selects a specific surrounder from the popup menu, the
|
||||
[Surrounder.surroundElements()](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/lang/surroundWith/Surrounder.java#L57)
|
||||
method is used to execute the surround action.
|
106
syntax_highlighting_and_error_highlighting.md
Normal file
106
syntax_highlighting_and_error_highlighting.md
Normal file
@ -0,0 +1,106 @@
|
||||
---
|
||||
layout: editable
|
||||
title: Syntax Highlighting and Error Highlighting
|
||||
---
|
||||
|
||||
The class used to specify how a particular range of text should be highlighted is called
|
||||
[TextAttributesKey](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/openapi/editor/colors/TextAttributesKey.java).
|
||||
An instance of this class is created for every distinct type of item which should be highlighted (keyword, number, string and so on).
|
||||
The
|
||||
[TextAttributesKey](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/openapi/editor/colors/TextAttributesKey.java)
|
||||
defines the default attributes which are applied to items of the corresponding type (for example, keywords are bold, numbers are blue, strings are bold and green).
|
||||
The mapping of the
|
||||
[TextAttributesKey](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/openapi/editor/colors/TextAttributesKey.java)
|
||||
to specific attributes used in an editor is defined by the
|
||||
[EditorColorsScheme](https://github.com/JetBrains/intellij-community/blob/master/platform/editor-ui-api/src/com/intellij/openapi/editor/colors/EditorColorsScheme.java)
|
||||
class, and can be configured by the user if the plugin provides an appropriate configuration interface.
|
||||
Highlighting from multiple
|
||||
[TextAttributesKey](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/openapi/editor/colors/TextAttributesKey.java)
|
||||
items can be layered - for example, one key may define an item's boldness and another its color.
|
||||
|
||||
**Note:**
|
||||
New functionality about Language Defaults and support for additional color schemes as detailed in
|
||||
[Color Scheme Management](color_scheme_management.html).
|
||||
|
||||
|
||||
### Lexer
|
||||
|
||||
The syntax and error highlighting is performed on multiple levels.
|
||||
The first level of syntax highlighting is based on the lexer output, and is provided through the
|
||||
[SyntaxHighlighter](https://github.com/JetBrains/intellij-community/blob/master/platform/editor-ui-api/src/com/intellij/openapi/fileTypes/SyntaxHighlighter.java)
|
||||
interface.
|
||||
The syntax highlighter returns the
|
||||
[TextAttributesKey](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/openapi/editor/colors/TextAttributesKey.java)
|
||||
instances for each token type which needs special highlighting.
|
||||
For highlighting lexer errors, the standard
|
||||
[TextAttributesKey](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/openapi/editor/colors/TextAttributesKey.java)
|
||||
for bad characters
|
||||
[HighlighterColors.BAD_CHARACTER](https://github.com/JetBrains/intellij-community/blob/master/platform/editor-ui-api/src/com/intellij/openapi/editor/HighlighterColors.java)
|
||||
can be used.
|
||||
|
||||
**Example:**
|
||||
[SyntaxHighlighter](https://github.com/JetBrains/intellij-community/blob/master/plugins/properties/properties-psi-api/src/com/intellij/lang/properties/PropertiesHighlighter.java)
|
||||
implementation for
|
||||
[Properties language plugin](https://github.com/JetBrains/intellij-community/blob/master/plugins/properties/)
|
||||
|
||||
|
||||
### Parser
|
||||
|
||||
The second level of error highlighting happens during parsing.
|
||||
If a particular sequence of tokens is invalid according to the grammar of the language, the
|
||||
[PsiBuilder.error()](https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/lang/PsiBuilder.java)
|
||||
method can be used to highlight the invalid tokens and display an error message showing why they are not valid.
|
||||
|
||||
### Annotator
|
||||
|
||||
The third level of highlighting is performed through the
|
||||
[Annotator](https://github.com/JetBrains/intellij-community/blob/master/platform/analysis-api/src/com/intellij/lang/annotation/Annotator.java)
|
||||
interface.
|
||||
A plugin can register one or more annotators in the ```com.intellij.annotator``` extension point, and these annotators are called during the background highlighting pass to process the elements in the PSI tree of the custom language.
|
||||
Annotators can analyze not only the syntax, but also the semantics using PSI, and thus can provide much more complex syntax and error highlighting logic.
|
||||
The annotator can also provide quick fixes to problems it detects.
|
||||
|
||||
When the file is changed, the annotator is called incrementally to process only changed elements in the PSI tree.
|
||||
|
||||
To highlight a region of text as a warning or error, the annotator calls ```createErrorAnnotation()``` or ```createWarningAnnotation()``` on the
|
||||
[AnnotationHolder](https://github.com/JetBrains/intellij-community/blob/master/platform/analysis-api/src/com/intellij/lang/annotation/AnnotationHolder.java)
|
||||
object passed to it, and optionally calls ```registerFix()``` on the returned
|
||||
[Annotation](https://github.com/JetBrains/intellij-community/blob/master/platform/analysis-api/src/com/intellij/lang/annotation/Annotation.java)
|
||||
object to add a quick fix for the error or warning.
|
||||
To apply additional syntax highlighting, the annotator can call
|
||||
[AnnotationHolder.createInfoAnnotation()](https://github.com/JetBrains/intellij-community/blob/master/platform/analysis-api/src/com/intellij/lang/annotation/AnnotationHolder.java)
|
||||
with an empty message and then call
|
||||
[Annotation.setTextAttributes()](https://github.com/JetBrains/intellij-community/blob/master/platform/analysis-api/src/com/intellij/lang/annotation/Annotation.java)
|
||||
to specify the text attributes key for the highlighting.
|
||||
|
||||
**Example:**
|
||||
[Annotator](https://github.com/JetBrains/intellij-community/blob/master/plugins/properties/properties-psi-impl/src/com/intellij/lang/properties/PropertiesAnnotator.java)
|
||||
for
|
||||
[Properties language plugin](https://github.com/JetBrains/intellij-community/blob/master/plugins/properties/)
|
||||
|
||||
|
||||
### External tool
|
||||
|
||||
Finally, if the custom language employs external tools for validating files in the language (for example, uses the Xerces library for XML schema validation), it can provide an implementation of the
|
||||
[ExternalAnnotator](https://github.com/JetBrains/intellij-community/blob/master/platform/analysis-api/src/com/intellij/lang/annotation/ExternalAnnotator.java)
|
||||
interface and register it in `com.intellij.externalAnnotator` extension point.
|
||||
The
|
||||
[ExternalAnnotator](https://github.com/JetBrains/intellij-community/blob/master/platform/analysis-api/src/com/intellij/lang/annotation/ExternalAnnotator.java)
|
||||
highlighting has the lowest priority and is invoked only after all other background processing has completed.
|
||||
It uses the same
|
||||
[AnnotationHolder](https://github.com/JetBrains/intellij-community/blob/master/platform/analysis-api/src/com/intellij/lang/annotation/AnnotationHolder.java)
|
||||
interface for converting the output of the external tool into editor highlighting.
|
||||
|
||||
### Color settings
|
||||
|
||||
The plugin can also provide a configuration interface to allow the user to configure the colors used for highlighting specific items.
|
||||
In order to do that, it should provide an implementation of
|
||||
[ColorSettingPage](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/openapi/options/colors/ColorSettingsPage.java)
|
||||
and register it in the ```com.intellij.colorSettingsPage``` extension point.
|
||||
|
||||
**Example**:
|
||||
[ColorSettingsPage](https://github.com/JetBrains/intellij-community/blob/master/plugins/properties/src/com/intellij/openapi/options/colors/pages/PropertiesColorsPage.java)
|
||||
for
|
||||
[Properties language plugin](https://github.com/JetBrains/intellij-community/blob/master/plugins/properties/)
|
||||
|
||||
The ```Export to HTML``` feature uses the same syntax highlighting mechanism as the editor, so it will work automatically for custom languages which provide a syntax highlighter.
|
39
test_project_and_testdata_directories.md
Normal file
39
test_project_and_testdata_directories.md
Normal file
@ -0,0 +1,39 @@
|
||||
---
|
||||
layout: editable
|
||||
title: Test Project and Testdata Directories
|
||||
---
|
||||
|
||||
The test fixture creates a *test project* environment.
|
||||
Unless you customize the project creation, the test project will have one module with one source root called "```src```".
|
||||
The files for the test project physically exist either in a temporary directory or in an in-memory file system, depending on which implementation of ```TempDirTestFixture``` is used.
|
||||
```LightPlatformCodeInsightFixtureTestCase``` uses an in-memory implementation; if you set up the test environment by calling ```IdeaTestFixtureFactory.createCodeInsightFixture```, you can specify the implementation to use.
|
||||
|
||||
Note that if your tests use the in-memory implementation, and you abort the execution of your tests, the persisted filesystem caches may get out of sync with the in-memory structures, and you may get spurious errors in your tests.
|
||||
In that case, you need to try running the test again, and if that doesn't help, delete the "system" subdirectory under the sandbox directory specified in the IntelliJ IDEA SDK settings.
|
||||
|
||||
In your plugin, you normally store the test data for your tests (such as files on which plugin features will be executed and expected output files) in the *testdata* directory.
|
||||
This is just a directory under the content root of your plugin, but not under a source root (files in testdata are normally not valid source code and must not be compiled).
|
||||
To specify the location of testdata, you must override the ```LightPlatformCodeInsightFixtureTestCase.getTestDataPath()``` method (the default implementation assumes running as part of the IntelliJ IDEA source tree and is not appropriate for third-party plugins).
|
||||
|
||||
Note that a very common pattern in IntelliJ IDEA tests is to use the name of the test method being executed as the base for building the testdata file paths.
|
||||
This allows to reuse most of the code between different test methods that test different aspects of the same feature, and this approach is also recommended for third-party plugin tests.
|
||||
The name of the test method can be retrieved using ```UsefulTestCase.getTestName()```.
|
||||
|
||||
To copy files or directories from your testdata directory to the test project directory, you can use the ```copyFileToProject()``` and ```copyDirectoryToProject()``` methods in the ```CodeInsightTestFixture``` class.
|
||||
|
||||
Most operations in plugin tests require a file open in the in-memory editor, in which highlighting, completion and other operations will be performed.
|
||||
The in-memory editor instance is returned by ```CodeInsightTestFixture.getEditor()```.
|
||||
To copy a file from the testdata directory to the test project directory and immediately open it in the editor, you can use the ```CodeInsightTestFixture.configureByFile()``` or ```configureByFiles()``` methods.
|
||||
The latter copies multiple files to the test project directory and opens the *first* of them in the in-memory editor.
|
||||
|
||||
Alternatively, you can use one of the other methods which take parameters annotated with ```@TestDataFile```.
|
||||
These methods copy the specified files from the testdata directory to the test project directory, open the first of the specified files in the in-memory editor, and then perform the requested operation such as highlighting or code completion.
|
||||
|
||||
When a file is opened in the in-memory editor, special markup in the file content can be used to specify the caret position or selection.
|
||||
You can use one of the following markers:
|
||||
|
||||
* ```<caret>``` specifies the position where the caret should be placed;
|
||||
|
||||
* ```<selection>``` and ```</selection>``` specify the start and end of the text range to be selected;
|
||||
|
||||
* ```<block>``` and ```</block>``` specify the start and end points of the column selection.
|
57
testing_highlighting.md
Normal file
57
testing_highlighting.md
Normal file
@ -0,0 +1,57 @@
|
||||
---
|
||||
layout: editable
|
||||
title: Testing Highlighting
|
||||
---
|
||||
|
||||
A common task when writing plugin tests is testing various kinds of highlighting (inspections, annotators, parser error highlighting etc.)
|
||||
The IntelliJ Platform provides a dedicated utility and markup format for this task.
|
||||
|
||||
To test the highlighting for the file currently loaded into the in-memory editor, you invoke the ```checkHighlighting()``` method.
|
||||
The parameters to the method specify which severities should be taken into account when comparing the results with the expected results: errors are always taken into account, whereas warnings, weak warnings and infos are optional.
|
||||
Alternatively, you can use the ```testHighlighting()``` method, which loads a testdata file into the in-memory editor and highlights it as a single operation.
|
||||
|
||||
If you need to test inspections (rather than generic highlighting provided by a highlighting lexer or annotator), you need to enable inspections that you're testing.
|
||||
This is done by calling ```CodeInsightTestFixture.enableInspections()``` in the setup method of your test or directly in a test method, before the call to checkHighlighting().
|
||||
|
||||
The expected results of the highlighting are specified directly in the source file.
|
||||
The platform supports an extensive XML-like markup language for this. In its simplest form, the markup looks like this:
|
||||
|
||||
```xml
|
||||
<warning descr="expected error message">code to be highlighted</warning>
|
||||
```
|
||||
|
||||
Or, as a more specific example:
|
||||
|
||||
```xml
|
||||
public int <warning descr="The compareTo() method does not reference 'foo' which is referenced from equals(); inconsistency may result">compareTo</warning>(Simple other) {
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
The tag name specifies the severity of the expected highlighting.
|
||||
The following severities are supported:
|
||||
|
||||
* ```<error>```
|
||||
|
||||
* ```<warning>```
|
||||
|
||||
* ```<weak_warning>```
|
||||
|
||||
* ```<info>```
|
||||
|
||||
* ```<inject>``` (for an injected fragment)
|
||||
|
||||
* ```<symbolName>``` (for a marker that highlights an identifier according to its type)
|
||||
|
||||
* any custom severity can be referenced by its name
|
||||
|
||||
|
||||
The tag can also have the following optional attributes:
|
||||
|
||||
* ```descr``` expected message associated with the highlighter (if not specified, any text will match; if the message contains a quotation mark, it can be escaped by putting two backslash characters before it)
|
||||
|
||||
* ```foregroundColor```, ```backgroundColor```, ```effectColor``` * expected colors for the highlighting
|
||||
|
||||
* ```effectType``` expected effect type for the highlighting (see ```EffectType``` enum for possible values)
|
||||
|
||||
* ```fontType``` expected font style for the highlighting (0 * normal, 1 * bold, 2 * italic, 3 * bold italic)
|
13
tests_and_fixtures.md
Normal file
13
tests_and_fixtures.md
Normal file
@ -0,0 +1,13 @@
|
||||
---
|
||||
layout: editable
|
||||
title: Tests and Fixtures
|
||||
---
|
||||
|
||||
|
||||
The IntelliJ Platform testing infrastructure is not tied to any specific test framework.
|
||||
In fact, the IntelliJ IDEA team uses both JUnit, TestNG and Cucumber for testing different parts of the project.
|
||||
However, most of the tests are written using JUnit 3.
|
||||
|
||||
When writing your own tests, you have the choice between using a standard base class to perform the test set up for you and using a fixture class, which lets you perform the setup manually and does not tie you to a specific test framework.
|
||||
With the former approach, you can use classes such as ```LightPlatformCodeInsightFixtureTestCase``` (despite being one of the longest and ugliest class names you'll run into in the IntelliJ IDEA API, it's actually the recommended approach for writing tests).
|
||||
With the latter approach, you use the ```IdeaTestFixtureFactory``` class to create instances of fixtures for the test environment, and you need to call the fixture creation and setup methods from the test setup method used by your test framework.
|
23
writing_tests.md
Normal file
23
writing_tests.md
Normal file
@ -0,0 +1,23 @@
|
||||
---
|
||||
layout: editable
|
||||
title: Writing Tests
|
||||
---
|
||||
|
||||
In most cases, once you have the necessary files copied to the test project and loaded into the in-memory editor, writing the test itself involves invoking your plugin code and has few dependencies on the test framework.
|
||||
However, for many common cases the framework provides helper methods that can make testing easier:
|
||||
|
||||
* ```type()``` simulates the typing of a character or string into the in-memory editor;
|
||||
|
||||
* ```performEditorAction()``` simulates the execution of an action in the context of the in-memory editor;
|
||||
|
||||
* ```complete()``` simulates the invocation of code completion and returns the list of lookup elements displayed in the completion list (or ```null``` if the completion had no suggestions or one suggestion which was auto-inserted);
|
||||
|
||||
* ```findUsages()``` simulates the invocation of 'Find Usages' and returns the found usages;
|
||||
|
||||
* ```findSingleIntention()``` in combination with ```launchAction()``` simulate the invocation of an intention action or inspection quickfix with the specified name;
|
||||
|
||||
* ```renameElementAtCaret()``` or ```rename()``` simulate the execution of a rename refactoring.
|
||||
|
||||
To compare the results of executing the action with the expected results, you can use the ```checkResultByFile()``` method.
|
||||
The file with the expected results can also contain markup to specify the expected caret position or selected text range.
|
||||
If you're testing an action which modifies multiple files (a project-wide refactoring, for example), you can compare an entire directory under the test project with the expected output using ```PlatformTestUtil.assertDirectoriesEqual()```.
|
Loading…
x
Reference in New Issue
Block a user