[Java. Code Formatting] Add support to align last line in text block if there is no symbols

IDEA-351758

GitOrigin-RevId: fca446697ac8eb5f1c24fbb3a1bf72924a48a65a
This commit is contained in:
Mikhail Pyltsin
2024-05-13 13:59:09 +00:00
committed by intellij-monorepo-bot
parent 69c61c874b
commit a87cef51de
17 changed files with 168 additions and 46 deletions
+1 -1
View File
@@ -1322,7 +1322,7 @@
<liveTemplateMacro implementation="com.intellij.codeInsight.template.macro.TypeOfVariableMacro"/>
<codeInspection.InspectionExtension implementation="com.intellij.codeInspection.ex.JavaInspectionExtensionsFactory"/>
<postFormatProcessor implementation="com.intellij.psi.impl.source.codeStyle.ImportPostFormatProcessor"/>
<postFormatProcessor implementation="com.intellij.psi.formatter.java.AdjustWhitespaceLineTextBlockReformatPostProcessor"/>
<!--<preFormatProcessor implementation="com.intellij.psi.formatter.java.AdjustWhitespaceLineTextBlockReformatPostProcessor"/>-->
<postFormatProcessor implementation="com.intellij.psi.impl.source.codeStyle.BracePostFormatProcessor"/>
<preFormatProcessor implementation="com.intellij.psi.impl.source.codeStyle.FormatCommentsProcessor"/>
<elementSignatureProvider implementation="com.intellij.codeInsight.folding.impl.JavaElementSignatureProvider"/>
@@ -19,7 +19,6 @@ import com.intellij.psi.impl.source.tree.ElementType;
import com.intellij.psi.impl.source.tree.JavaElementType;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.tree.TokenSet;
import com.intellij.psi.util.PsiLiteralUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
@@ -57,7 +56,7 @@ public final class JavaFormatterUtil {
* @param node1 node to check
* @param node2 node to check
* @return {@code true} if given nodes are binary expressions and have the same priority;
* {@code false} otherwise
* {@code false} otherwise
*/
public static boolean areSamePriorityBinaryExpressions(ASTNode node1, ASTNode node2) {
if (node1 == null || node2 == null) {
@@ -83,7 +82,7 @@ public final class JavaFormatterUtil {
* if there's a line break after it and "keep line breaks" is on.
*
* @param settings The current settings
* @param node The node to check.
* @param node The node to check.
* @return True for call chunk start.
*/
static boolean isStartOfCallChunk(@NotNull CommonCodeStyleSettings settings, @NotNull ASTNode node) {
@@ -127,9 +126,9 @@ public final class JavaFormatterUtil {
* @return wrap to use for the sub-blocks of the given block
*/
static @Nullable Wrap createDefaultWrap(ASTBlock block,
CommonCodeStyleSettings settings,
JavaCodeStyleSettings javaSettings,
ReservedWrapsProvider reservedWrapsProvider) {
CommonCodeStyleSettings settings,
JavaCodeStyleSettings javaSettings,
ReservedWrapsProvider reservedWrapsProvider) {
ASTNode node = block.getNode();
Wrap wrap = block.getWrap();
if (node == null) return null;
@@ -211,11 +210,11 @@ public final class JavaFormatterUtil {
* {@code null} otherwise
*/
static @Nullable Wrap arrangeChildWrap(ASTNode child,
ASTNode parent,
CommonCodeStyleSettings settings,
JavaCodeStyleSettings javaSettings,
Wrap suggestedWrap,
AbstractJavaBlock reservedWrapsProvider) {
ASTNode parent,
CommonCodeStyleSettings settings,
JavaCodeStyleSettings javaSettings,
Wrap suggestedWrap,
AbstractJavaBlock reservedWrapsProvider) {
ASTNode directParent = child.getTreeParent();
int role = ((CompositeElement)directParent).getChildRole(child);
@@ -291,7 +290,8 @@ public final class JavaFormatterUtil {
ASTNode last = prev.getLastChildNode();
if (last != null && last.getElementType() == JavaElementType.ANNOTATION) {
if (javaSettings.DO_NOT_WRAP_AFTER_SINGLE_ANNOTATION && isModifierListWithSingleAnnotation(prev, JavaElementType.FIELD) ||
javaSettings.DO_NOT_WRAP_AFTER_SINGLE_ANNOTATION_IN_PARAMETER && isModifierListWithSingleAnnotation(prev, JavaElementType.PARAMETER) ||
javaSettings.DO_NOT_WRAP_AFTER_SINGLE_ANNOTATION_IN_PARAMETER &&
isModifierListWithSingleAnnotation(prev, JavaElementType.PARAMETER) ||
isAnnotationAfterKeyword(last)
) {
return Wrap.createWrap(WrapType.NONE, false);
@@ -456,7 +456,10 @@ public final class JavaFormatterUtil {
return false;
}
private static int getAnnotationWrapType(ASTNode parent, ASTNode child, CommonCodeStyleSettings settings, JavaCodeStyleSettings javaSettings) {
private static int getAnnotationWrapType(ASTNode parent,
ASTNode child,
CommonCodeStyleSettings settings,
JavaCodeStyleSettings javaSettings) {
IElementType nodeType = parent.getElementType();
if (nodeType == JavaElementType.METHOD) {
@@ -528,9 +531,9 @@ public final class JavaFormatterUtil {
* Traverses the children of the node and collects nodes with type method calls or reference expressions to the list.
* If the quantity of the call expressions is greater than {@link JavaFormatterUtil#CALL_EXPRESSION_DEPTH}, call expressions will not be
* collected, and you should not format them.
* @param nodes List in which the method add nodes
* @param node Node to traverse
*
* @param nodes List in which the method add nodes
* @param node Node to traverse
*/
public static void collectCallExpressionNodes(@NotNull List<? super ASTNode> nodes, @NotNull ASTNode node) {
ArrayDeque<ASTNode> stack = new ArrayDeque<>(CALL_EXPRESSION_DEPTH);
@@ -541,51 +544,81 @@ public final class JavaFormatterUtil {
return;
}
ASTNode currentNode = stack.removeLast();
if (!FormatterUtil.containsWhiteSpacesOnly(currentNode)) {
IElementType type = currentNode.getElementType();
if (type == JavaElementType.METHOD_CALL_EXPRESSION ||
type == JavaElementType.REFERENCE_EXPRESSION) {
ASTNode firstChild = currentNode.getFirstChildNode();
currentNode = FormatterUtil.getNextNonWhitespaceSibling(currentNode);
ContainerUtil.addIfNotNull(stack, currentNode);
ContainerUtil.addIfNotNull(stack, firstChild);
}
else {
nodes.add(currentNode);
currentNode = FormatterUtil.getNextNonWhitespaceSibling(currentNode);
ContainerUtil.addIfNotNull(stack, currentNode);
}
} else {
if (!FormatterUtil.containsWhiteSpacesOnly(currentNode)) {
IElementType type = currentNode.getElementType();
if (type == JavaElementType.METHOD_CALL_EXPRESSION ||
type == JavaElementType.REFERENCE_EXPRESSION) {
ASTNode firstChild = currentNode.getFirstChildNode();
currentNode = FormatterUtil.getNextNonWhitespaceSibling(currentNode);
ContainerUtil.addIfNotNull(stack, currentNode);
ContainerUtil.addIfNotNull(stack, firstChild);
}
else {
nodes.add(currentNode);
currentNode = FormatterUtil.getNextNonWhitespaceSibling(currentNode);
ContainerUtil.addIfNotNull(stack, currentNode);
}
}
else {
currentNode = FormatterUtil.getNextNonWhitespaceSibling(currentNode);
ContainerUtil.addIfNotNull(stack, currentNode);
}
}
}
/**
* Extracts text ranges corresponding to the lines in a given literal multiline text.
*
* @param text the literal text to extract text ranges from
* @param indent the number of spaces used for indentation
* @param shouldTreatWholeLine specifies whether the entire line should be treated as a text range or
* @param text the literal text to extract text ranges from
* @param indent the number of spaces used for indentation
* @return a list of {@code TextRange} objects representing the extracted text ranges
*/
public static @NotNull List<TextRange> extractTextRangesFromLiteralText(@NotNull String text, int indent, boolean shouldTreatWholeLine) {
public static @NotNull List<TextRange> extractTextRangesFromLiteralText(@NotNull String text, int indent) {
List<TextRange> linesRanges = new ArrayList<>();
boolean isLastLine = false;
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 && !shouldTreatWholeLine) start += indent;
if (start != end || shouldTreatWholeLine) linesRanges.add(new TextRange(start, end));
if (end == -1) {
isLastLine = true;
end = text.length();
}
if (start + indent <= end) {
int quoteStartIndex = end - 3;
if (!isLastLine && allEmpty(start + indent, end, text)) {
// todo here we can delete and the last \s\s\s"""
start = end;
}
else if (isLastLine && allEmpty(start + indent, quoteStartIndex, text) && isEndsWithTripleQuote(quoteStartIndex, end, text)) {
start = quoteStartIndex;
}
else {
start += indent;
}
}
else {
start = end;
}
linesRanges.add(new TextRange(start, end));
start = end + 1;
}
return linesRanges;
}
private static boolean isEndsWithTripleQuote(int start, int end, @NotNull String text) {
if (end - start != 3 || start < 0) return false;
String tripleQuote = text.substring(start, end);
return tripleQuote.equals("\"\"\"");
}
private static boolean allEmpty(int i, int end, @NotNull String text) {
for (int j = i; j < end; j++) {
if(!Character.isWhitespace(text.charAt(j))) return false;
}
return true;
}
}
@@ -5,7 +5,6 @@ import com.intellij.formatting.*;
import com.intellij.formatting.alignment.AlignmentStrategy;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiLiteralExpression;
import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
import com.intellij.psi.codeStyle.JavaCodeStyleSettings;
@@ -57,7 +56,7 @@ public class TextBlockBlock extends AbstractJavaBlock {
if (indent == -1) return Collections.emptyList();
String text = literal.getText();
return extractTextRangesFromLiteralText(text, indent, false);
return extractTextRangesFromLiteralText(text, indent);
}
@Override
@@ -1,7 +1,7 @@
public class Formatter {
void foo() {
String s2 = """
<caret>
<caret>
""";
}
}
@@ -1,7 +1,7 @@
public class Formatter {
void foo() {
String s2 = """
<caret>
<caret>
""";
}
}
@@ -1,7 +1,7 @@
public class Formatter {
void foo() {
String s2 = """
<caret>
<caret>
""";
}
}
@@ -0,0 +1,7 @@
public class Formatter {
void foo() {
String s2 = """
a
<caret>""";
}
}
@@ -0,0 +1,7 @@
public class Formatter {
void foo() {
String s2 = """
a
<caret>""";
}
}
@@ -0,0 +1,7 @@
public class Formatter {
void foo() {
String s2 = """
a
d d ds \t"""<caret>;
}
}
@@ -0,0 +1,7 @@
public class Formatter {
void foo() {
String s2 = """
a
d d ds \t"""<caret>;
}
}
@@ -0,0 +1,7 @@
public class Formatter {
void foo() {
String s2 = """
a
d d ds \t<caret>""";
}
}
@@ -0,0 +1,7 @@
public class Formatter {
void foo() {
String s2 = """
a
d d ds \t<caret>""";
}
}
@@ -0,0 +1,7 @@
public class Formatter {
void foo() {
String s2 = """
a
<caret> """;
}
}
@@ -0,0 +1,7 @@
public class Formatter {
void foo() {
String s2 = """
a
<caret>""";
}
}
@@ -0,0 +1,7 @@
public class Formatter {
void foo() {
String s2 = """
a
<caret>""";
}
}
@@ -0,0 +1,7 @@
public class Formatter {
void foo() {
String s2 = """
a
<caret>""";
}
}
@@ -9,7 +9,7 @@ import com.intellij.psi.codeStyle.CommonCodeStyleSettings
import com.intellij.psi.codeStyle.JavaCodeStyleSettings
import com.intellij.testFramework.LightPlatformCodeInsightTestCase
class AdjustWhitespaceLineTextBlockReformatPostProcessorTest : LightPlatformCodeInsightTestCase() {
class TextBlockBlankLinesFormatterTest : LightPlatformCodeInsightTestCase() {
override fun getTestDataPath(): String = "${JavaTestUtil.getJavaTestDataPath()}/psi/formatter/java/textBlock/"
fun testWhitespacesLessThanAlignment() = doTest()
@@ -71,6 +71,26 @@ class AdjustWhitespaceLineTextBlockReformatPostProcessorTest : LightPlatformCode
doTest()
}
fun testLastLineWithoutSymbolsAfterAlignment() {
doTest()
}
fun testLastLineWithSymbolsAfterAlignment() {
doTest()
}
fun testLastLineWithoutSymbolsBeforeAlignment() {
doTest()
}
fun testLastLineWithSymbolsBeforeAlignment() {
doTest()
}
fun testEmptyLastLine() {
doTest()
}
private fun getCommonSettings(): CommonCodeStyleSettings = currentCodeStyleSettings.getCommonSettings(JavaLanguage.INSTANCE)