Replacing a PsiElement but keeping the newlines and spaces
Hello,
I am extending an IntelliJ Plugin to add a Quick Fix via an Annotator and an IntentionAction. The PsiElement I find containing the text I want to fix is just text typed inside an xml file, and it turns out in the IntentAction as an XmlText containing for example the following:
\n\n\n #ddd\n\n
The goal is to take the `#ddd` color and change it to the following:
<color name="alto">#ddd</color>
To do so, I replace the PsiElement with a newly created XmlTag:
val insert = "<color name=\"$name\">${hexColor.inputToString()}</color>"
val newElement = XmlElementFactory.getInstance(project).createTagFromText(insert)
oldElement.replace(newElement)
The missing part now is the newlines. I want the user to keep the newlines he added before and after the color code. I tried using addBefore() and addAfter():
val split = oldElement.text.split(hexColor.input)
oldElement.replace(newElement)
if (split.isNotEmpty()) rootTag.addBefore(XmlElementFactory.getInstance(project).createDisplayText(split[0]), newElement)
if (split.size > 1) rootTag.addAfter(XmlElementFactory.getInstance(project).createDisplayText(split[1].trim()), newElement)
But then I get a NullPointerException, saying that the "parent is null" on the newly created element:
Element: class com.intellij.psi.impl.source.xml.XmlTextImpl because: parent is null
invalidated at: see attachment
com.intellij.psi.PsiInvalidElementAccessException: Element: class com.intellij.psi.impl.source.xml.XmlTextImpl because: parent is null
I tried replace() after addBefore() or addAfter(), anchored on the oldElement or on the newElement, nothing works :/
So what is the right way to keep the newlines/spaces that were inside the XmlText so that the user doesn't lose those?
Thank you in advance for your help!
Please sign in to leave a comment.
There is a chance that the call to `replace` is making a copy of the new element to insert into the tree rather than actually using the `newElement` instance, which would explain the `null` parent value. It is a good idea to always use the return value of the `replace` method call:
This should hopefully give you a valid element to which you can add the preceding and following whitespace.
My mind is literally blown away. It works!
This produces the following:
Almost there! So using createDisplayText() is not the way to create white spaces in a XmlFile, so how should I do this?
I tried to create a new WhitespaceImpl() myself but that just triggered the same "parent is null" error.
Any idea?
Found a solution! I copied what the XmlElementFactory.getInstance(project).createDisplayText() does and removed the creation of the CDATA:
private fun whitespace(project: Project, text: String): XmlText {
val tagFromText = XmlElementFactory.getInstance(project).createTagFromText("<a>$text</a>")
val textElements = tagFromText.value.textElements
return if (textElements.isEmpty()) ASTFactory.composite(XmlElementType.XML_TEXT) as XmlText else textElements[0]
}
Unbelievable how hard it was to find how to insert some newlines :/