package com.jetbrains.lang.dart.psi.impl; import com.intellij.lang.ASTNode; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.*; import com.intellij.psi.tree.IElementType; import com.jetbrains.lang.dart.DartLanguage; import com.jetbrains.lang.dart.DartTokenTypes; import com.jetbrains.lang.dart.psi.DartStringLiteralExpression; import com.jetbrains.lang.dart.util.DartPsiImplUtil; import org.jetbrains.annotations.NotNull; public abstract class DartStringLiteralExpressionBase extends DartClassReferenceImpl implements DartStringLiteralExpression { private static final Logger LOG = Logger.getInstance(DartStringLiteralExpressionBase.class.getName()); public DartStringLiteralExpressionBase(ASTNode node) { super(node); } @Override public boolean isValidHost() { return true; } @Override public PsiLanguageInjectionHost updateText(@NotNull final String text) { return ElementManipulators.handleContentChange(this, text); } @NotNull @Override public LiteralTextEscaper<? extends PsiLanguageInjectionHost> createLiteralTextEscaper() { // TODO return LiteralTextEscaper.createSimple(this); } public static class DartStringManipulator extends AbstractElementManipulator<DartStringLiteralExpression> { @Override public DartStringLiteralExpression handleContentChange(@NotNull final DartStringLiteralExpression oldElement, @NotNull final TextRange range, @NotNull final String newContent) { // this check helps to avoid loosing text in case of concatenated strings + typing escape sequences; need proper fix final int expectedNewLength = oldElement.getTextLength() - range.getLength() + newContent.length(); final String newText = StringUtil.replaceSubstring(oldElement.getText(), range, newContent); final PsiFile fileFromText = PsiFileFactory.getInstance(oldElement.getProject()) .createFileFromText(DartLanguage.INSTANCE, "var a = " + newText + ";"); final PsiElement elementAt = fileFromText.findElementAt("var a = ".length()); if (elementAt != null && elementAt.getParent() instanceof DartStringLiteralExpression && expectedNewLength == elementAt.getParent().getTextLength()) { return (DartStringLiteralExpression)oldElement.replace(elementAt.getParent()); } return oldElement; } @NotNull @Override public TextRange getRangeInElement(@NotNull final DartStringLiteralExpression element) { // StringLiteralExpression may consist of several strings (that become concatenated). We want to return range of the first one. If none (e.g. "$a") - return zero-length range after quote PsiElement child = element.getFirstChild(); while (child != null) { final IElementType type = child.getNode().getElementType(); if (type == DartTokenTypes.OPEN_QUOTE) { final PsiElement next = child.getNextSibling(); if (next == null || next.getNode().getElementType() != DartTokenTypes.REGULAR_STRING_PART) { return TextRange.from(child.getStartOffsetInParent() + child.getTextLength(), 0); } } if (type == DartTokenTypes.REGULAR_STRING_PART) { return child.getTextRange().shiftRight(-element.getTextRange().getStartOffset()); } if (type == DartTokenTypes.RAW_SINGLE_QUOTED_STRING || type == DartTokenTypes.RAW_TRIPLE_QUOTED_STRING) { final TextRange textRange = DartPsiImplUtil.getUnquotedDartStringAndItsRange(child.getText()).second; return textRange.shiftRight(child.getStartOffsetInParent()); } child = child.getNextSibling(); } LOG.error(element.getText()); return element.getTextRange(); } } }