determine content range of text block correctly on pasting in literal (IDEA-CR-50963)

GitOrigin-RevId: aca891bae186771688369389803a7b47da165f91
This commit is contained in:
Bas Leijdekkers
2019-08-12 16:06:12 +03:00
committed by intellij-monorepo-bot
parent ed264d14d2
commit 1c31987860
9 changed files with 67 additions and 42 deletions
@@ -164,28 +164,18 @@ public class StringLiteralCopyPasteProcessor implements CopyPastePreProcessor {
@Nullable
protected PsiElement findLiteralTokenType(PsiFile file, int selectionStart, int selectionEnd) {
final PsiElement elementAtSelectionStart = file.findElementAt(selectionStart);
if (elementAtSelectionStart == null) {
final PsiElement elementAtSelectionEnd = file.findElementAt(selectionEnd);
if (elementAtSelectionStart == null || elementAtSelectionEnd == null ||
elementAtSelectionEnd.getNode().getElementType() != elementAtSelectionStart.getNode().getElementType()) {
return null;
}
final boolean isTextBlock = isTextBlock(elementAtSelectionStart);
if (!isStringLiteral(elementAtSelectionStart) && !isCharLiteral(elementAtSelectionStart) && !isTextBlock) {
if (!isStringLiteral(elementAtSelectionStart) && !isCharLiteral(elementAtSelectionStart) && !isTextBlock(elementAtSelectionStart)) {
return null;
}
final TextRange range = elementAtSelectionStart.getTextRange();
final TextRange textRange = isTextBlock ? new TextRange(range.getStartOffset() + 3, range.getEndOffset() - 2) : range;
if (textRange.getEndOffset() < selectionEnd) {
final PsiElement elementAtSelectionEnd = file.findElementAt(selectionEnd);
if (elementAtSelectionEnd == null) {
return null;
}
if (elementAtSelectionEnd.getNode().getElementType() == elementAtSelectionStart.getNode().getElementType() &&
elementAtSelectionEnd.getTextRange().getStartOffset() < selectionEnd) {
return elementAtSelectionStart;
}
}
if (selectionStart <= textRange.getStartOffset() || selectionEnd >= textRange.getEndOffset()) {
final TextRange startTextRange = getEscapedRange(elementAtSelectionStart);
final TextRange endTextRange = getEscapedRange(elementAtSelectionEnd);
if (startTextRange == null || endTextRange == null ||
!startTextRange.containsOffset(selectionStart) || !endTextRange.containsOffset(selectionEnd)) {
return null;
}
return elementAtSelectionStart;
@@ -208,18 +198,10 @@ public class StringLiteralCopyPasteProcessor implements CopyPastePreProcessor {
@Nullable
protected TextRange getEscapedRange(@NotNull PsiElement token) {
if (isCharLiteral(token) || isStringLiteral(token)) {
TextRange tokenRange = token.getTextRange();
return new TextRange(tokenRange.getStartOffset() + 1, tokenRange.getEndOffset() - 1); // Excluding String/char literal quotes
}
else if (isTextBlock(token)) {
PsiElement parent = token.getParent();
if (parent instanceof PsiLiteralExpression && ((PsiLiteralExpression)parent).getValue() != null) {
TextRange rangeInParent = StringLiteralManipulator.getValueRange((PsiLiteralExpression)parent);
return rangeInParent.shiftRight(parent.getTextRange().getStartOffset());
}
}
return null;
PsiElement parent = token.getParent();
if (!(parent instanceof PsiLiteralExpression)) return null;
final TextRange valueTextRange = StringLiteralManipulator.getValueRange((PsiLiteralExpression)token.getParent());
return valueTextRange.shiftRight(token.getTextRange().getStartOffset());
}
@NotNull
@@ -236,9 +218,12 @@ public class StringLiteralCopyPasteProcessor implements CopyPastePreProcessor {
for (int i = 0; i < lines.length; i++) {
buffer.append(PsiLiteralUtil.escapeTextBlockCharacters(lines[i], i == 0 && escapeStartQuote, i == lines.length - 1 && escapeEndQuote));
if (i < lines.length - 1) {
buffer.append("\n");
buffer.append('\n');
}
}
if (StringUtil.endsWithChar(text, '\n')) {
buffer.append('\n');
}
return buffer.toString();
}
}
@@ -45,8 +45,11 @@ public class StringLiteralManipulator extends AbstractElementManipulator<PsiLite
// avoid calling getValue(): it allocates new string, it returns null for invalid escapes
IElementType type = ((PsiLiteralExpressionImpl)element).getLiteralElementType();
if (type == JavaTokenType.TEXT_BLOCK_LITERAL) {
int startOffset = element.getValue() == null ? -1 : element.getText().indexOf('\n');
return startOffset < 0 ? TextRange.from(0, length) : new TextRange(startOffset + 1, Math.max(startOffset + 1, length - 3));
final String text = element.getText();
int startOffset = findBlockStart(text);
return startOffset < 0
? new TextRange(0, length)
: new TextRange(startOffset, length - (text.endsWith("\"\"\"") ? 3 : 0));
}
isQuoted = type == JavaTokenType.STRING_LITERAL || type == JavaTokenType.CHARACTER_LITERAL;
}
@@ -56,4 +59,15 @@ public class StringLiteralManipulator extends AbstractElementManipulator<PsiLite
}
return isQuoted ? new TextRange(1, Math.max(1, length - 1)) : TextRange.from(0, length);
}
private static int findBlockStart(String text) {
if (!text.startsWith("\"\"\"")) return -1;
final int length = text.length();
for (int i = 3; i < length; i++) {
final char c = text.charAt(i);
if (c == '\n') return i + 1;
if (!Character.isWhitespace(c)) return -1;
}
return -1;
}
}
@@ -0,0 +1,4 @@
class C {
String empty = """
\a\\""";
}
@@ -0,0 +1,4 @@
class C {
String empty = """
\a<caret>""";
}
@@ -0,0 +1,4 @@
class C {
String empty = """ \
b\ad esc\ape """;
}
@@ -0,0 +1,4 @@
class C {
String empty = """ <caret>
b\ad esc\ape """;
}
@@ -0,0 +1,6 @@
class C {
String empty = """
""";
}
@@ -0,0 +1,4 @@
class C {
String empty = """
<caret>""";
}
@@ -22,17 +22,17 @@ class JavaTextBlocksHighlightingTest : LightJavaCodeInsightFixtureTestCase() {
myFixture.checkHighlighting()
}
fun testEscapeQuotes() {
doTestPaste("\"\"\"\ntarget\"\"\"")
}
fun testEscapeQuotes() = doTestPaste("\"\"\"\ntarget\"\"\"")
fun testEscapeQuotes2() {
doTestPaste("\"\ntarget\"")
}
fun testEscapeQuotes2() = doTestPaste("\"\ntarget\"")
fun testNoEscapeWhenNotInTextBlockContent() {
doTestPaste("\\");
}
fun testNoEscapeWhenNotInTextBlockContent() = doTestPaste("\\")
fun testNoEscapeWhenInTextBlockPrefix() = doTestPaste("\\")
fun testPasteNewline() = doTestPaste("\n\n")
fun testBadEscape() = doTestPaste("\\");
private fun doTestPaste(textToPaste: String) {
myFixture.configureByText("plain.txt", "<selection>$textToPaste</selection>")