Java: fix selection behaviour for String Templates (IDEA-333906)

GitOrigin-RevId: 0727e9c5b4a56d752bc38d7fc6fa22ca676dd9ba
This commit is contained in:
Bas Leijdekkers
2023-10-31 17:17:28 +01:00
committed by intellij-monorepo-bot
parent 8925dcb722
commit 2ebaab02d1
28 changed files with 281 additions and 43 deletions

View File

@@ -7,60 +7,80 @@ import com.intellij.lexer.StringLiteralLexer;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.BasicLiteralUtil;
import com.intellij.psi.JavaTokenType;
import com.intellij.psi.PsiElement;
import com.intellij.psi.impl.source.BasicJavaAstTreeUtil;
import com.intellij.psi.impl.source.BasicElementTypes;
import com.intellij.psi.tree.IElementType;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import static com.intellij.psi.impl.source.BasicElementTypes.BASIC_STRING_LITERALS;
public class LiteralSelectioner extends AbstractBasicBackBasicSelectioner {
@Override
public boolean canSelect(@NotNull PsiElement e) {
return BasicJavaAstTreeUtil.is(BasicJavaAstTreeUtil.toNode(e), BASIC_STRING_LITERALS);
IElementType type = e.getNode().getElementType();
return BasicElementTypes.BASIC_STRING_LITERALS.contains(type) ||
BasicElementTypes.BASIC_STRING_TEMPLATE_FRAGMENTS.contains(type);
}
@Override
public List<TextRange> select(@NotNull PsiElement e, @NotNull CharSequence editorText, int cursorOffset, @NotNull Editor editor) {
List<TextRange> result = super.select(e, editorText, cursorOffset, editor);
if (result == null) {
return null;
}
ASTNode node = BasicJavaAstTreeUtil.toNode(e);
if (node == null) {
return null;
}
boolean textBlock = BasicJavaAstTreeUtil.isTextBlock(node);
StringLiteralLexer lexer = textBlock
? new StringLiteralLexer(StringLiteralLexer.NO_QUOTE_CHAR, JavaTokenType.TEXT_BLOCK_LITERAL, true, "s{")
: new StringLiteralLexer('"', JavaTokenType.STRING_LITERAL);
TextRange range = node.getTextRange();
SelectWordUtil.addWordHonoringEscapeSequences(editorText, range, cursorOffset, lexer, result);
if (textBlock) {
int contentStart = StringUtil.indexOf(editorText, '\n', range.getStartOffset());
if (contentStart == -1) return result;
int start = contentStart + 1;
int end = range.getEndOffset();
end -= StringUtil.endsWith(editorText, start, end, "\"\"\"") ? 4 : 1;
for (; end >= start; end--) {
char c = editorText.charAt(end);
if (c == '\n' || !Character.isWhitespace(c)) {
end += 1;
break;
}
}
if (start < end) result.add(new TextRange(start, end));
}
else {
int endOffset = editorText.charAt(range.getEndOffset() - 1) == '"'
? range.getEndOffset() - 1
: range.getEndOffset();
result.add(new TextRange(range.getStartOffset() + 1, endOffset));
}
if (result == null) return null;
ASTNode node = e.getNode();
StringLiteralLexer lexer = new StringLiteralLexer(StringLiteralLexer.NO_QUOTE_CHAR, node.getElementType(), true, "s{");
SelectWordUtil.addWordHonoringEscapeSequences(editorText, node.getTextRange(), cursorOffset, lexer, result);
result.add(getContentRange(node, editorText));
return result;
}
}
private static TextRange getContentRange(ASTNode node, @NotNull CharSequence text) {
final IElementType tokenType = node.getElementType();
final TextRange range = node.getTextRange();
if (tokenType == JavaTokenType.STRING_TEMPLATE_BEGIN || tokenType == JavaTokenType.STRING_TEMPLATE_MID ||
tokenType == JavaTokenType.TEXT_BLOCK_TEMPLATE_MID) {
return new TextRange(range.getStartOffset() + 1, range.getEndOffset() - 2);
}
else if (tokenType == JavaTokenType.STRING_TEMPLATE_END || tokenType == JavaTokenType.STRING_LITERAL) {
int end = text.charAt(range.getEndOffset() - 1) == '"'
? range.getEndOffset() - 1
: range.getEndOffset();
return new TextRange(range.getStartOffset() + 1, end);
}
else if (tokenType == JavaTokenType.TEXT_BLOCK_TEMPLATE_BEGIN || tokenType == JavaTokenType.TEXT_BLOCK_TEMPLATE_END ||
tokenType == JavaTokenType.TEXT_BLOCK_LITERAL) {
int start;
if (tokenType == JavaTokenType.TEXT_BLOCK_TEMPLATE_END) {
start = range.getStartOffset() + 1;
}
else {
start = range.getStartOffset() + 3;
while (BasicLiteralUtil.isTextBlockWhiteSpace(text.charAt(start))) start++;
if (text.charAt(start) == '\n') start++;
}
int end;
if (tokenType == JavaTokenType.TEXT_BLOCK_TEMPLATE_BEGIN) {
end = range.getEndOffset() - 2;
}
else {
end = range.getEndOffset();
end -= StringUtil.endsWith(text, start, end, "\"\"\"") ? 4 : 1;
for (; end >= start; end--) {
char c = text.charAt(end);
if (c == '\n' || !Character.isWhitespace(c)) {
end += 1;
break;
}
}
}
return new TextRange(start, end);
}
else {
throw new IllegalArgumentException();
}
}
}

View File

@@ -335,10 +335,6 @@ public final class BasicJavaAstTreeUtil {
return findParent(e, elementType);
}
public static boolean isTextBlock(@NotNull ASTNode node) {
return node.getElementType() == JavaTokenType.TEXT_BLOCK_LITERAL;
}
@Nullable
public static ASTNode getMethodExpression(@Nullable ASTNode element) {
if (!is(element, BASIC_METHOD_CALL_EXPRESSION)) {

View File

@@ -204,6 +204,13 @@ public abstract class AbstractBasicJavaSelectWordTest extends SelectWordTestBase
public void testUnclosedLiteral() { doTest("java"); }
public void testStringTemplate1() { doTest("java"); }
public void testStringTemplate2() { doTest("java"); }
public void testStringTemplate3() { doTest("java"); }
public void testStringTemplate4() { doTest("java"); }
public void testStringTemplate5() { doTest("java"); }
public void testStringTemplate6() { doTest("java"); }
public void testLineComments() { doTest("java"); }
public void testLineCommentsAtStart() { doTest("java"); }

View File

@@ -0,0 +1,6 @@
class StringTemplate1 {
String x(int i) {
return STR."one <selection><caret>two</selection> three \{i} four five six \{i} seven eight nine}";
}
}

View File

@@ -0,0 +1,6 @@
class StringTemplate1 {
String x(int i) {
return STR."<selection>one <caret>two three </selection>\{i} four five six \{i} seven eight nine}";
}
}

View File

@@ -0,0 +1,6 @@
class StringTemplate1 {
String x(int i) {
return STR.<selection>"one <caret>two three \{</selection>i} four five six \{i} seven eight nine}";
}
}

View File

@@ -0,0 +1,6 @@
class StringTemplate1 {
String x(int i) {
return STR."one <caret>two three \{i} four five six \{i} seven eight nine}";
}
}

View File

@@ -0,0 +1,6 @@
class StringTemplate1 {
String x(int i) {
return STR."one two three \{i} four <selection><caret>five</selection> six \{i} seven eight nine}";
}
}

View File

@@ -0,0 +1,6 @@
class StringTemplate1 {
String x(int i) {
return STR."one two three \{i}<selection> four <caret>five six </selection>\{i} seven eight nine}";
}
}

View File

@@ -0,0 +1,6 @@
class StringTemplate1 {
String x(int i) {
return STR."one two three \{i<selection>} four <caret>five six \{</selection>i} seven eight nine}";
}
}

View File

@@ -0,0 +1,6 @@
class StringTemplate1 {
String x(int i) {
return STR."one two three \{i} four <caret>five six \{i} seven eight nine}";
}
}

View File

@@ -0,0 +1,6 @@
class StringTemplate1 {
String x(int i) {
return STR."one two three \{i} four five six \{i} seven <selection><caret>eight</selection> nine";
}
}

View File

@@ -0,0 +1,6 @@
class StringTemplate1 {
String x(int i) {
return STR."one two three \{i} four five six \{i}<selection> seven <caret>eight nine</selection>";
}
}

View File

@@ -0,0 +1,6 @@
class StringTemplate1 {
String x(int i) {
return STR."one two three \{i} four five six \{i<selection>} seven <caret>eight nine"</selection>;
}
}

View File

@@ -0,0 +1,6 @@
class StringTemplate1 {
String x(int i) {
return STR."one two three \{i} four five six \{i} seven <caret>eight nine";
}
}

View File

@@ -0,0 +1,11 @@
class StringTemplate1 {
String x(int i) {
return STR."""
one <selection><caret>two</selection>
three \{i}
four five six \{i}
seven eight nine
""";
}
}

View File

@@ -0,0 +1,11 @@
class StringTemplate1 {
String x(int i) {
return STR."""
<selection> one <caret>two
three </selection>\{i}
four five six \{i}
seven eight nine
""";
}
}

View File

@@ -0,0 +1,11 @@
class StringTemplate1 {
String x(int i) {
return STR.<selection>"""
one <caret>two
three \{</selection>i}
four five six \{i}
seven eight nine
""";
}
}

View File

@@ -0,0 +1,11 @@
class StringTemplate1 {
String x(int i) {
return STR.<selection>"""
one <caret>two
three \{i}
four five six \{i}
seven eight nine
"""</selection>;
}
}

View File

@@ -0,0 +1,11 @@
class StringTemplate1 {
String x(int i) {
return STR."""
one <caret>two
three \{i}
four five six \{i}
seven eight nine
""";
}
}

View File

@@ -0,0 +1,11 @@
class StringTemplate1 {
String x(int i) {
return STR."""
one two three \{i}
four <selection><caret>five</selection>
six \{i}
seven eight nine
""";
}
}

View File

@@ -0,0 +1,11 @@
class StringTemplate1 {
String x(int i) {
return STR."""
one two three \{i}<selection>
four <caret>five
six </selection>\{i}
seven eight nine
""";
}
}

View File

@@ -0,0 +1,11 @@
class StringTemplate1 {
String x(int i) {
return STR."""
one two three \{i<selection>}
four <caret>five
six \{</selection>i}
seven eight nine
""";
}
}

View File

@@ -0,0 +1,11 @@
class StringTemplate1 {
String x(int i) {
return STR."""
one two three \{i}
four <caret>five
six \{i}
seven eight nine
""";
}
}

View File

@@ -0,0 +1,11 @@
class StringTemplate1 {
String x(int i) {
return STR."""
one two three \{i}
four five six \{i}
seven <selection><caret>eight</selection>
nine
""";
}
}

View File

@@ -0,0 +1,11 @@
class StringTemplate1 {
String x(int i) {
return STR."""
one two three \{i}
four five six \{i}<selection>
seven <caret>eight
nine
</selection> """;
}
}

View File

@@ -0,0 +1,11 @@
class StringTemplate1 {
String x(int i) {
return STR."""
one two three \{i}
four five six \{i<selection>}
seven <caret>eight
nine
"""</selection>;
}
}

View File

@@ -0,0 +1,11 @@
class StringTemplate1 {
String x(int i) {
return STR."""
one two three \{i}
four five six \{i}
seven <caret>eight
nine
""";
}
}