mirror of
https://github.com/JetBrains/intellij-sdk-code-samples.git
synced 2025-07-30 02:07:50 +08:00
"Modifying the PSI" document
This commit is contained in:
parent
9a207cc869
commit
85bb45a1c4
@ -41,9 +41,11 @@
|
|||||||
## Part II - Base Platform
|
## Part II - Base Platform
|
||||||
* [Fundamentals](platform/fundamentals.md)
|
* [Fundamentals](platform/fundamentals.md)
|
||||||
* Component Model
|
* Component Model
|
||||||
|
* Disposers
|
||||||
* [Threading](basics/architectural_overview/general_threading_rules.md)
|
* [Threading](basics/architectural_overview/general_threading_rules.md)
|
||||||
* Background Tasks
|
* Background Tasks
|
||||||
* [Messaging Infrastructure](reference_guide/messaging_infrastructure.md)
|
* [Messaging Infrastructure](reference_guide/messaging_infrastructure.md)
|
||||||
|
* Queries and Query Executors
|
||||||
* [User Interface Components](user_interface_components/user_interface_components.md)
|
* [User Interface Components](user_interface_components/user_interface_components.md)
|
||||||
* [Tool Windows](user_interface_components/tool_windows.md)
|
* [Tool Windows](user_interface_components/tool_windows.md)
|
||||||
* [Dialogs](user_interface_components/dialog_wrapper.md)
|
* [Dialogs](user_interface_components/dialog_wrapper.md)
|
||||||
@ -61,6 +63,7 @@
|
|||||||
* [2. Grouping Actions](tutorials/action_system/grouping_action.md)
|
* [2. Grouping Actions](tutorials/action_system/grouping_action.md)
|
||||||
* Settings
|
* Settings
|
||||||
* [Persisting State of Components](basics/persisting_state_of_components.md)
|
* [Persisting State of Components](basics/persisting_state_of_components.md)
|
||||||
|
* Editing Settings
|
||||||
* [Files](basics/architectural_overview/files.md)
|
* [Files](basics/architectural_overview/files.md)
|
||||||
* [Virtual File System](basics/virtual_file_system.md)
|
* [Virtual File System](basics/virtual_file_system.md)
|
||||||
* [Virtual Files](basics/architectural_overview/virtual_file.md)
|
* [Virtual Files](basics/architectural_overview/virtual_file.md)
|
||||||
@ -103,11 +106,12 @@
|
|||||||
* [PSI Elements](basics/architectural_overview/psi_elements.md)
|
* [PSI Elements](basics/architectural_overview/psi_elements.md)
|
||||||
* [Navigating the PSI](basics/architectural_overview/navigating_psi.md)
|
* [Navigating the PSI](basics/architectural_overview/navigating_psi.md)
|
||||||
* [References](basics/architectural_overview/psi_references.md)
|
* [References](basics/architectural_overview/psi_references.md)
|
||||||
* Modifying the PSI
|
* [Modifying the PSI](basics/architectural_overview/modifying_psi.md)
|
||||||
* [PSI Cookbook](basics/psi_cookbook.md)
|
* [PSI Cookbook](basics/psi_cookbook.md)
|
||||||
* [Indexing and PSI Stubs](basics/indexing_and_psi_stubs.md)
|
* [Indexing and PSI Stubs](basics/indexing_and_psi_stubs.md)
|
||||||
* [File-based indexes](basics/indexing_and_psi_stubs/file_based_indexes.md)
|
* [File-based indexes](basics/indexing_and_psi_stubs/file_based_indexes.md)
|
||||||
* [Stub indexes](basics/indexing_and_psi_stubs/stub_indexes.md)
|
* [Stub indexes](basics/indexing_and_psi_stubs/stub_indexes.md)
|
||||||
|
* Element Patterns
|
||||||
* Unified AST
|
* Unified AST
|
||||||
* [XML DOM API](reference_guide/frameworks_and_external_apis/xml_dom_api.md)
|
* [XML DOM API](reference_guide/frameworks_and_external_apis/xml_dom_api.md)
|
||||||
|
|
||||||
|
106
basics/architectural_overview/modifying_psi.md
Normal file
106
basics/architectural_overview/modifying_psi.md
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
---
|
||||||
|
title: Modifying the PSI
|
||||||
|
---
|
||||||
|
|
||||||
|
The PSI is a read-write interface for working with the source code. You can modify the PSI by *adding*, *replacing*,
|
||||||
|
and *deleting* PSI elements.
|
||||||
|
|
||||||
|
To perform these operations, you use methods such as `PsiElement.add()`, `PsiElement.delete()`, and `PsiElement.replace()`,
|
||||||
|
as well as other methods defined in the `PsiElement` interface that let you process multiple elements in a single
|
||||||
|
operation, or to specify the exact location in the tree where an element needs to be added.
|
||||||
|
|
||||||
|
Just as document operations, PSI modifications need to be wrapped in a write action and in a command (and therefore
|
||||||
|
can only be performed in the event dispatch thread). See [the Documents article](/basics/architectural_overview/documents.html#what-are-the-rules-of-working-with-documents)
|
||||||
|
for more information on commands and write actions.
|
||||||
|
|
||||||
|
|
||||||
|
## Creating the New PSI
|
||||||
|
|
||||||
|
The PSI elements to add to the tree, or to replace the existing PSI elements, are normally *created from text*.
|
||||||
|
In the most general case, you use the `createFileFromText` method of [`PsiFileFactory`](upsource:///platform/core-api/src/com/intellij/psi/PsiFileFactory.java)
|
||||||
|
to create a new file that contains the code construct which you need to add to the tree or to use as a replacement
|
||||||
|
for an existing element, traverse the resulting tree to locate the specific element that you need, and then pass that
|
||||||
|
element to `add()` or `replace()`.
|
||||||
|
|
||||||
|
Most languages provide factory methods which let you create specific code constructs more easily. For example,
|
||||||
|
the [`PsiJavaParserFacade`](upsource:///java/java-psi-api/src/com/intellij/psi/PsiJavaParserFacade.java) class
|
||||||
|
contains methods such as `createMethodFromText`, which creates a Java method from the given text.
|
||||||
|
|
||||||
|
When you're implementing refactorings, intentions or inspection quickfixes that work with existing code, the text that
|
||||||
|
you pass to the various `createFromText` methods will combine hard-coded fragments and fragments of code taken from
|
||||||
|
the existing file. For small code fragments (individual identifiers), you can simply append the text from the existing
|
||||||
|
code to the text of the code fragment you're building. In that case, you need to make sure that the resulting text is
|
||||||
|
syntactically correct, otherwise the `createFromText` method will throw an exception.
|
||||||
|
|
||||||
|
For larger code fragments, it's best to perform the modification in several steps:
|
||||||
|
|
||||||
|
* create a replacement tree fragment from text, leaving placeholders for the user code fragments;
|
||||||
|
* replace the placeholders with the user code fragments;
|
||||||
|
* replace the element in the original source file with the replacement tree.
|
||||||
|
|
||||||
|
This ensure that the formatting of the user code is preserved and that the modification doesn't introduce any unwanted
|
||||||
|
whitespace changes.
|
||||||
|
|
||||||
|
As an example of this approach, see the quickfix in the `ComparingReferencesInspection` example:
|
||||||
|
|
||||||
|
```java
|
||||||
|
// binaryExpression holds a PSI expression of the form "x == y", which needs to be replaced with "x.equals(y)"
|
||||||
|
PsiBinaryExpression binaryExpression = (PsiBinaryExpression) descriptor.getPsiElement();
|
||||||
|
IElementType opSign = binaryExpression.getOperationTokenType();
|
||||||
|
PsiExpression lExpr = binaryExpression.getLOperand();
|
||||||
|
PsiExpression rExpr = binaryExpression.getROperand();
|
||||||
|
|
||||||
|
// Step 1: Create a replacement fragment from text, with "a" and "b" as placeholders
|
||||||
|
PsiElementFactory factory = JavaPsiFacade.getInstance(project).getElementFactory();
|
||||||
|
PsiMethodCallExpression equalsCall =
|
||||||
|
(PsiMethodCallExpression) factory.createExpressionFromText("a.equals(b)", null);
|
||||||
|
|
||||||
|
// Step 2: replace "a" and "b" with elements from the original file
|
||||||
|
equalsCall.getMethodExpression().getQualifierExpression().replace(lExpr);
|
||||||
|
equalsCall.getArgumentList().getExpressions()[0].replace(rExpr);
|
||||||
|
|
||||||
|
// Step 3: replace a larger element in the original file with te replacement tree
|
||||||
|
PsiExpression result = (PsiExpression) binaryExpression.replace(equalsCall);
|
||||||
|
```
|
||||||
|
|
||||||
|
Just as everywhere else in the IntelliJ Platform API, the text passed to `createFileFromText` and other `createFromText`
|
||||||
|
methods must use only `\n` as line separators.
|
||||||
|
|
||||||
|
|
||||||
|
## Maintaining Tree Structure Consistency
|
||||||
|
|
||||||
|
The PSI modification methods do not restrict you in the way you can build the resulting tree structure. For example,
|
||||||
|
when working with a Java class, you can add a `for` statement as a direct child of a `PsiMethod` element, even though
|
||||||
|
the Java parser will never produce such a structure (the `for` statement will always be a child of the `PsiCodeBlock`)
|
||||||
|
representing the method body). Modifications that produce incorrect tree structure may appear to work, but they will
|
||||||
|
lead to problems and exceptions later. Therefore, you always need to ensure that the structure you built with PSI
|
||||||
|
modification operations is the same as what the parser would produce when parsing the code that you've built.
|
||||||
|
|
||||||
|
To make sure you're not introducing inconsistencies, you can call `PsiTestUtil.checkFileStructure()` in the tests for
|
||||||
|
your action that modifies the PSI. This method ensures that the structure you've built is the same as what the parser produces.
|
||||||
|
|
||||||
|
|
||||||
|
## Whitespaces and Imports
|
||||||
|
|
||||||
|
When working with PSI modification functions, you should never create individual whitespace nodes (spaces or line breaks)
|
||||||
|
from text. Instead, all whitespace modifications are performed by the formatter, which follows the code style settings
|
||||||
|
selected by the user. Formatting is automatically performed at the end of every command, and if you need, you can
|
||||||
|
also perform it manually using the `reformat(PsiElement)` method in the
|
||||||
|
[`CodeStyleManager`](upsource:///platform/core-api/src/com/intellij/psi/codeStyle/CodeStyleManager.java) class.
|
||||||
|
|
||||||
|
Also, when working with Java code (or with code in other languages with a similar import mechanism such as Groovy or Python),
|
||||||
|
you should never create imports manually. Instead, you should insert fully-qualified names into the code you're
|
||||||
|
generating, and then call the `shortenClassReferences()` method in the
|
||||||
|
[`JavaCodeStyleManager`](upsource:///java/java-psi-api/src/com/intellij/psi/codeStyle/JavaCodeStyleManager.java)
|
||||||
|
(or the equivalent API for the language you're working with). This ensures that the imports are created according to
|
||||||
|
the user's code style settings and inserted into the correct place of the file.
|
||||||
|
|
||||||
|
|
||||||
|
## Combining PSI and Document Modifications
|
||||||
|
|
||||||
|
In some cases, you need to perform a PSI modification and then to perform an operation on the document you've just
|
||||||
|
modified through the PSI (for example, start a live template). In this case, you need to call a special method that
|
||||||
|
completes the PSI-based postprocessing (such as formatting) and commits the changes to the document. The method
|
||||||
|
you need to call is called `doPostponedOperationsAndUnblockDocument`, and it's defined in the
|
||||||
|
[`PsiDocumentManager`](upsource:///platform/core-api/src/com/intellij/psi/PsiDocumentManager.java) class.
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user