Intention fails to change code in injected language snippets
Hi,
I've implemented an intention action for a custom language. It works well in actual documents but it does work when my language is injected in another document. When being used there, the intention is offered upon alt-enter as usual, but the actual document modification does not happen. The code just is just the same as before. Here are the relevant code bits:
public void processIntention(@NotNull PsiElement element, @NotNull Project project, Editor editor)
throws IncorrectOperationException {
...
if (Objects.equals(element.getText(), "T")) {
PsiElement replacement = element.replace(RElementFactory.createLeafFromText(element.getProject(), "TRUE"));
editor.getCaretModel().moveToOffset(replacement.getTextOffset() + 4);
}
...
I could imagine that element.getProject is wrong in such a context, but I have not idea about how to do better/different.
Any help is appreciated.
Best,
Holger
请先登录再写评论。
Have you tried doing a text replacement instead of PSI replacement?
Thanks Vladimir, indeed that may be an option. However, it lacks beauty and in fact contradicts the idea of having PsiElement::replace as intended API for such a usecase. It's also used by other "official" intentions like com.jetbrains.python.inspections.quickfix.PyAddPropertyForFieldQuickFix or org.jetbrains.plugins.groovy.intentions.conversions.strings.ConvertToDollarSlashRegexIntention.
So I did a little testing and it turned out that the success of replacement depends on the main language of the document. Intentions using PsiElement::replace function correctly in injected chunks in (e.g.) java, bash, R, groovy or html. But fail just in markdown. So I've filed: https://youtrack.jetbrains.com/issue/RUBY-19204
Holger,
I guess I am less orthodox about always using PsiElements, since in my plugin for Markdown it is not possible to replace PsiElements because Markdown elements' textual representation depends on context making text based replacement the only option without having to create the full text context of the replacement element.
There is no possibility in these cases just to create an element, but actually having to create an arbitrarily complex nested context.
I found that you can mix both text and Psi based replacements as long as you commit changes between the two types of changes. If you don't you will get exceptions with helpful messages to guide you to the right API usage. :D
Vladimir,
About your last point: Why are `PsiElement::replace` calls (in contrast to `document.replaceString` ) possible without committing and wrapping into WriteCommandAction?
Example PsiElement::replace: org.jetbrains.plugins.groovy.intentions.conversions.strings.ConvertToDollarSlashRegexIntention
Example Document::replaceString: com.intellij.xml.util.CollapseTagIntention
Does
require aWriteCommandAction or I can just call it after PsiElement::replace?
Thanks again for you help,
Holger
All modifications have to be wrapped in a WriteAction. The only reason this is not done in you examples is because the caller does the wrapping.
IntentionAction.java:78-87After modifying the document commit changes by invoking:
The reason the second call has an explicit WriteCommand wrapper is because it can be called from a context other than intention invoke() method which means that it may or may not be wrapped in a WriteAction by the caller.
Since
PsiElement::replacehas to be called inside aWriteActionI would assume that you can callreformat()right afterreplace()