AbstractJavaBlock: added formatting for java text blocks (IDEA-216737)

GitOrigin-RevId: 091cd09f3dac6b9d3dc130d5e2e65bee5fd88792
This commit is contained in:
Artemiy Sartakov
2019-10-03 04:32:43 +00:00
committed by intellij-monorepo-bot
parent 8b1b040a7a
commit 37137f76ac
28 changed files with 237 additions and 79 deletions
@@ -1,7 +1,6 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.codeInspection;
import com.google.common.base.Strings;
import com.intellij.codeInsight.daemon.impl.analysis.HighlightUtil;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.project.Project;
@@ -64,22 +63,19 @@ public class TextBlockMigrationInspection extends AbstractBaseJavaLocalInspectio
if (expression == null) return;
Document document = PsiDocumentManager.getInstance(project).getDocument(expression.getContainingFile());
if (document == null) return;
int expressionOffset = expression.getTextOffset();
int offset = expressionOffset - document.getLineStartOffset(document.getLineNumber(expressionOffset));
PsiLiteralExpressionImpl literalExpression = tryCast(expression, PsiLiteralExpressionImpl.class);
if (literalExpression != null && literalExpression.getLiteralElementType() == JavaTokenType.STRING_LITERAL) {
replaceWithTextBlock(new PsiLiteralExpressionImpl[]{literalExpression}, offset, literalExpression);
replaceWithTextBlock(new PsiLiteralExpressionImpl[]{literalExpression}, literalExpression);
return;
}
PsiPolyadicExpression polyadicExpression = tryCast(expression, PsiPolyadicExpression.class);
if (polyadicExpression == null || !isConcatenation(polyadicExpression)) return;
replaceWithTextBlock(polyadicExpression.getOperands(), offset, polyadicExpression);
replaceWithTextBlock(polyadicExpression.getOperands(), polyadicExpression);
}
private static void replaceWithTextBlock(@NotNull PsiExpression[] operands, int offset, @NotNull PsiExpression toReplace) {
private static void replaceWithTextBlock(@NotNull PsiExpression[] operands, @NotNull PsiExpression toReplace) {
StringBuilder textBlock = new StringBuilder();
String indent = Strings.repeat(" ", offset);
textBlock.append("\"\"\"\n").append(indent);
textBlock.append("\"\"\"\n");
boolean escapeStartQuote = false;
for (int i = 0; i < operands.length; i++) {
PsiExpression operand = operands[i];
@@ -88,7 +84,7 @@ public class TextBlockMigrationInspection extends AbstractBaseJavaLocalInspectio
boolean isLastLine = i == operands.length - 1;
text = PsiLiteralUtil.escapeTextBlockCharacters(text, escapeStartQuote, isLastLine, isLastLine);
escapeStartQuote = text.endsWith("\"");
textBlock.append(text.replaceAll("\n", '\n' + indent));
textBlock.append(text);
}
textBlock.append("\"\"\"");
PsiReplacementUtil.replaceExpression(toReplace, textBlock.toString(), new CommentTracker());
@@ -16,7 +16,9 @@
package com.intellij.ide;
import com.intellij.application.options.*;
import com.intellij.application.options.codeStyle.properties.*;
import com.intellij.application.options.codeStyle.properties.CodeStyleFieldAccessor;
import com.intellij.application.options.codeStyle.properties.CodeStylePropertiesUtil;
import com.intellij.application.options.codeStyle.properties.CodeStylePropertyAccessor;
import com.intellij.lang.Language;
import com.intellij.lang.java.JavaLanguage;
import com.intellij.openapi.application.ApplicationBundle;
@@ -173,6 +175,7 @@ public class JavaLanguageCodeStyleSettingsProvider extends LanguageCodeStyleSett
"ALIGN_MULTILINE_PARENTHESIZED_EXPRESSION",
"ALIGN_MULTILINE_ARRAY_INITIALIZER_EXPRESSION",
"ALIGN_GROUP_FIELD_DECLARATIONS",
"ALIGN_MULTILINE_TEXT_BLOCKS",
"BRACE_STYLE",
"CLASS_BRACE_STYLE",
"METHOD_BRACE_STYLE",
@@ -204,6 +207,11 @@ public class JavaLanguageCodeStyleSettingsProvider extends LanguageCodeStyleSett
ApplicationBundle.message("wrapping.align.when.multiline"),
ApplicationBundle.message("wrapping.annotation.parameters"));
consumer.showCustomOption(JavaCodeStyleSettings.class,
"ALIGN_MULTILINE_TEXT_BLOCKS",
ApplicationBundle.message("wrapping.align.when.multiline"),
ApplicationBundle.message("wrapping.text.blocks") );
String groupName = ApplicationBundle.message("wrapping.fields.annotation");
consumer.showCustomOption(JavaCodeStyleSettings.class, "DO_NOT_WRAP_AFTER_SINGLE_ANNOTATION", "Do not wrap after single annotation", groupName);
}
@@ -126,6 +126,8 @@ public class JavaCodeStyleSettings extends CustomCodeStyleSettings implements Im
public int ANNOTATION_PARAMETER_WRAP = CommonCodeStyleSettings.DO_NOT_WRAP;
public boolean ALIGN_MULTILINE_ANNOTATION_PARAMETERS;
public boolean ALIGN_MULTILINE_TEXT_BLOCKS = false;
public int BLANK_LINES_AROUND_INITIALIZER = 1;
public static final int FULLY_QUALIFY_NAMES_IF_NOT_IMPORTED = 1;
@@ -20,8 +20,10 @@ import com.intellij.psi.impl.source.codeStyle.ShiftIndentInsideHelper;
import com.intellij.psi.impl.source.tree.*;
import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil;
import com.intellij.psi.impl.source.tree.java.ClassElement;
import com.intellij.psi.impl.source.tree.java.PsiLiteralExpressionImpl;
import com.intellij.psi.jsp.JspElementType;
import com.intellij.psi.tree.IElementType;
import com.intellij.util.ObjectUtils;
import com.intellij.util.SmartList;
import com.intellij.util.text.CharArrayUtil;
import org.jetbrains.annotations.NotNull;
@@ -226,6 +228,9 @@ public abstract class AbstractJavaBlock extends AbstractBlock implements JavaBlo
if (elementType == JavaDocElementType.DOC_COMMENT) {
return new DocCommentBlock(child, wrap, alignment, actualIndent, settings, javaSettings, formattingMode);
}
if (isTextBlock(childPsi)) {
return new TextBlockBlock(child, wrap, alignmentStrategy, actualIndent, settings, javaSettings, formattingMode);
}
SimpleJavaBlock simpleJavaBlock = new SimpleJavaBlock(child, wrap, alignmentStrategy, actualIndent, settings, javaSettings, myFormattingMode);
simpleJavaBlock.setStartOffset(startOffset);
@@ -436,6 +441,9 @@ public abstract class AbstractJavaBlock extends AbstractBlock implements JavaBlo
}
return createAlignment(mySettings.ALIGN_MULTILINE_BINARY_OPERATION, defaultAlignment);
}
if (isTextBlock(myNode.getPsi())) {
return createAlignment(myJavaSettings.ALIGN_MULTILINE_TEXT_BLOCKS,null);
}
return null;
}
@@ -459,6 +467,11 @@ public abstract class AbstractJavaBlock extends AbstractBlock implements JavaBlo
}
}
private static boolean isTextBlock(@NotNull PsiElement childPsi) {
PsiLiteralExpressionImpl literal = ObjectUtils.tryCast(childPsi, PsiLiteralExpressionImpl.class);
return literal != null && literal.getLiteralElementType() == JavaTokenType.TEXT_BLOCK_LITERAL;
}
private boolean shouldInheritAlignment() {
if (myNode instanceof PsiPolyadicExpression) {
final ASTNode treeParent = myNode.getTreeParent();
@@ -0,0 +1,93 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.psi.formatter.java;
import com.intellij.formatting.*;
import com.intellij.formatting.alignment.AlignmentStrategy;
import com.intellij.formatting.blocks.TextLineBlock;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.JavaTokenType;
import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
import com.intellij.psi.codeStyle.JavaCodeStyleSettings;
import com.intellij.psi.impl.source.tree.java.PsiLiteralExpressionImpl;
import com.intellij.util.ObjectUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class TextBlockBlock extends AbstractJavaBlock {
private final Indent myIndent;
public TextBlockBlock(ASTNode textBlock,
Wrap wrap,
AlignmentStrategy alignment,
Indent indent,
CommonCodeStyleSettings settings,
JavaCodeStyleSettings javaSettings,
@NotNull FormattingMode formattingMode) {
super(textBlock, wrap, alignment, indent, settings, javaSettings, formattingMode);
myIndent = indent;
}
@Override
protected List<Block> buildChildren() {
if (getFormattingMode() != FormattingMode.REFORMAT) return Collections.emptyList();
int offset = myNode.getStartOffset();
Alignment alignment = createChildAlignment();
List<TextRange> textRanges = extractLinesRanges();
List<Block> children = new ArrayList<>(textRanges.size());
for (int i = 0; i < textRanges.size(); i++) {
TextRange range = textRanges.get(i).shiftRight(offset);
Indent indent = i == 0 ? Indent.getNoneIndent() : Indent.getContinuationIndent();
children.add(new TextLineBlock(range, alignment, indent, null));
}
return children;
}
@NotNull
private List<TextRange> extractLinesRanges() {
PsiLiteralExpressionImpl literal = ObjectUtils.tryCast(myNode.getPsi(), PsiLiteralExpressionImpl.class);
if (literal == null || literal.getLiteralElementType() != JavaTokenType.TEXT_BLOCK_LITERAL) return Collections.emptyList();
int indent = literal.getTextBlockIndent();
if (indent == -1) return Collections.emptyList();
String text = myNode.getText();
List<TextRange> linesRanges = new ArrayList<>();
// open quotes
int start = StringUtil.indexOf(text, '\n', 3);
if (start == -1) return Collections.emptyList();
linesRanges.add(new TextRange(0, start));
start += 1;
while (start < text.length()) {
int end = StringUtil.indexOf(text, '\n', start);
if (end == -1) end = text.length();
if (start + indent < end) start += indent;
if (start != end) linesRanges.add(new TextRange(start, end));
start = end + 1;
}
return linesRanges;
}
@Nullable
@Override
public Spacing getSpacing(@Nullable Block child1, @NotNull Block child2) {
return null;
}
@Override
public Indent getIndent() {
return myIndent;
}
@Override
public boolean isLeaf() {
return getFormattingMode() != FormattingMode.REFORMAT;
}
}
@@ -3,9 +3,9 @@ class C {
void x(int a, int b) {
//keep me
String s = java.text.MessageFormat.format("""
the text
block
line2
{0}{1} "to" be""", a, b);
the text
block
line2
{0}{1} "to" be""", a, b);
}
}
@@ -1,5 +1,5 @@
class C {
String empty = """
""\"
target""\"<caret>""";
String empty = """
""\"
target""\"<caret>""";
}
@@ -1,4 +1,4 @@
class C {
String empty = """
<caret>""";
String empty = """
<caret>""";
}
@@ -1,6 +1,6 @@
class C {
String x = """
"\"
target\""
""";
String x = """
"\"
target\""
""";
}
@@ -1,5 +1,5 @@
class C {
String x = """
"<caret>"
""";
String x = """
"<caret>"
""";
}
@@ -1,8 +1,8 @@
class C {
String empty = """
<html>
<body>
</body>
</html>
<html>
<body>
</body>
</html>
""";
}
@@ -19,6 +19,7 @@
"align_multiline_parenthesized_expression": false,
"align_multiline_resources": true,
"align_multiline_ternary_operation": false,
"align_multiline_text_blocks": false,
"align_multiline_throws_list": false,
"align_subsequent_simple_methods": false,
"align_throws_keyword": false,
@@ -4,9 +4,9 @@ class TextBlockMigration {
void concatenation() {
String foobarbaz = """
foo
bar
baz""";
foo
bar
baz""";
}
}
@@ -4,9 +4,9 @@ class TextBlockMigration {
void concatenationWithMultipleNewLines() {
String text = """
This text should be on the same line as \\n this one
foo
bar
""";
This text should be on the same line as \\n this one
foo
bar
""";
}
}
@@ -4,11 +4,11 @@ class TextBlockMigration {
void concatenationWithMultipleNewLines() {
String html = """
<html>
<body>
<p>Hello, world</p>
</body>
</html>
""";
<html>
<body>
<p>Hello, world</p>
</body>
</html>
""";
}
}
@@ -4,11 +4,11 @@ class TextBlockMigration {
void concatenationWithNonStrings() {
String answer = """
The answer to the meaning of life,
the universe,
and everything
is 42
""";
The answer to the meaning of life,
the universe,
and everything
is 42
""";
}
}
@@ -4,10 +4,10 @@ class TextBlockMigration {
void concatenationWithExtraSpaces() {
String code = """
<html>\040\040
<body>
</body>
</html>\040\040""";
<html>\040\040
<body>
</body>
</html>\040\040""";
}
}
@@ -4,10 +4,10 @@ class TextBlockMigration {
void concatenationWithThreeQuotes() {
String quotes = """
this concatenation contains
three quotes
one after another
"\"\"""";
this concatenation contains
three quotes
one after another
"\"\"""";
}
}
@@ -4,10 +4,10 @@ class TextBlockMigration {
void literalWithNewLine() {
String foo = """
foo
bar
baz
""";
foo
bar
baz
""";
}
}
@@ -929,6 +929,28 @@ public class JavaFormatterAlignmentTest extends AbstractJavaFormatterTest {
);
}
public void test_alignMultilineTextBlock() {
getJavaSettings().ALIGN_MULTILINE_TEXT_BLOCKS = true;
doTextTest(
"public class Test {\n" +
" void foo() {\n" +
" String block = \"\"\"\n" +
" text\n" +
" block\n" +
" \"\"\";\n" +
" }\n" +
"}",
"public class Test {\n" +
" void foo() {\n" +
" String block = \"\"\"\n" +
" text\n" +
" block\n" +
" \"\"\";\n" +
" }\n" +
"}"
);
}
@SuppressWarnings("unused")
public void _testIdea199677() {
getSettings().ALIGN_CONSECUTIVE_VARIABLE_DECLARATIONS = true;
@@ -640,4 +640,26 @@ public class JavaFormatterIndentationTest extends AbstractJavaFormatterTest {
doTextTest(before, after);
}
public void testTextBlock() {
String before = "class Formatting {\n" +
" void test() {\n" +
" String block = \"\"\"\n" +
" \n" +
" text\n" +
"block\"\"\";\n" +
" " +
" }\n" +
"}";
String after = "class Formatting {\n" +
" void test() {\n" +
" String block = \"\"\"\n" +
" \n" +
" text\n" +
" block\"\"\";\n" +
" " +
"}\n" +
"}";
doTextTest(before, after);
}
}
@@ -246,6 +246,7 @@ wrapping.long.lines=Ensure right margin is not exceeded
wrapping.comments=Comments
wrapping.comments.wrap.at.right.margin=Wrap at right margin
wrapping.annotation.parameters=Annotation parameters
wrapping.text.blocks=Text blocks
checkbox.align.multiline.chained.methods=Chained methods
checkbox.align.multiline.method.parameters=Method parameters
@@ -3,7 +3,7 @@ class X {
String s = """
\"""";
String t = """
" """;
" """;
String u = """
""\" """;
String v = """
@@ -3,7 +3,7 @@ class X {
\'""";
String t = """
""";
""";
String u = """
\" ""\" "\"" \"""
\""
@@ -2,9 +2,9 @@ class TextBlocks {
static {
//c1
System.out.println("""
first
second
third
\\for<caret>th\"""");
first
second
third
\\for<caret>th\"""");
}
}
@@ -2,10 +2,10 @@ class TextBlocks {
static {
//c1
System.out.println("""
first
second
third
forth
<caret> """);
first
second
third
forth
<caret> """);
}
}
@@ -2,9 +2,9 @@ class TextBlocks {
static {
//c1
System.out.println("""
first
second
third_no sp<caret>ace
""");
first
second
third_no sp<caret>ace
""");
}
}
@@ -1,8 +1,8 @@
class C {
//keep me
String s = String.format("""
the text
block
line2
%d%d t<caret>o be""", 1, 2);
the text
block
line2
%d%d t<caret>o be""", 1, 2);
}