PY-75537 Implement PyAstStringLiteralExpression.createLiteralTextEscaper() (PyFrontendElementTypesFacadeImpl.kt)

(cherry picked from commit c42b7626a6e57c118e7028a4806bd8fa7938076c)

GitOrigin-RevId: e68adbb728051d49654d76df856b522191330170
This commit is contained in:
Petr
2024-10-15 19:04:07 +02:00
committed by intellij-monorepo-bot
parent 78c67850e3
commit 411f36db5a
2 changed files with 80 additions and 92 deletions

View File

@@ -19,6 +19,7 @@ import com.intellij.lang.ASTNode;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.ElementManipulators;
import com.intellij.psi.LiteralTextEscaper;
import com.intellij.psi.PsiLanguageInjectionHost;
import com.intellij.psi.tree.TokenSet;
import com.intellij.util.containers.ContainerUtil;
@@ -179,4 +180,83 @@ public interface PyAstStringLiteralExpression extends PyAstLiteralExpression, St
}
return new TextRange(0, getTextLength());
}
@Override
@NotNull
default LiteralTextEscaper<? extends PsiLanguageInjectionHost> createLiteralTextEscaper() {
return new LiteralTextEscaper<>(this) {
@Override
public boolean decode(@NotNull final TextRange rangeInsideHost, @NotNull final StringBuilder outChars) {
for (Pair<TextRange, String> fragment : myHost.getDecodedFragments()) {
final TextRange encodedTextRange = fragment.getFirst();
final TextRange intersection = encodedTextRange.intersection(rangeInsideHost);
if (intersection != null && !intersection.isEmpty()) {
final String value = fragment.getSecond();
final String intersectedValue;
if (value.codePointCount(0, value.length()) == 1 || value.length() == intersection.getLength()) {
intersectedValue = value;
}
else {
final int start = Math.max(0, rangeInsideHost.getStartOffset() - encodedTextRange.getStartOffset());
final int end = Math.min(value.length(), start + intersection.getLength());
intersectedValue = value.substring(start, end);
}
outChars.append(intersectedValue);
}
}
return true;
}
@Override
public int getOffsetInHost(final int offsetInDecoded, @NotNull final TextRange rangeInsideHost) {
int offset = 0; // running offset in the decoded fragment
int endOffset = -1;
for (Pair<TextRange, String> fragment : myHost.getDecodedFragments()) {
final TextRange encodedTextRange = fragment.getFirst();
final TextRange intersection = encodedTextRange.intersection(rangeInsideHost);
if (intersection != null && !intersection.isEmpty()) {
final String value = fragment.getSecond();
final int valueLength = value.length();
final int intersectionLength = intersection.getLength();
if (valueLength == 0) {
return -1;
}
// A long unicode escape of form \U01234567 can be decoded into a surrogate pair
else if (value.codePointCount(0, valueLength) == 1) {
if (offset == offsetInDecoded) {
return intersection.getStartOffset();
}
offset += valueLength;
}
else {
// Literal fragment without escapes: it's safe to use intersection length instead of value length
if (offset + intersectionLength >= offsetInDecoded) {
final int delta = offsetInDecoded - offset;
return intersection.getStartOffset() + delta;
}
offset += intersectionLength;
}
endOffset = intersection.getEndOffset();
}
}
// XXX: According to the real use of getOffsetInHost() it should return the correct host offset for the offset in decoded at the
// end of the range inside host, not -1
if (offset == offsetInDecoded) {
return endOffset;
}
return -1;
}
@Override
public boolean isOneLine() {
return true;
}
@NotNull
@Override
public TextRange getRelevantTextRange() {
return myHost.getStringValueTextRange();
}
};
}
}