[Java. Code Formatting] Add better whitespace detection for AdjustWhitespaceLineTextBlockReformatPostProcessor

IDEA-271085

GitOrigin-RevId: 9b77c8be1713797b705fe5860b826a9d36dcb2d9
This commit is contained in:
Georgii Ustinov
2024-05-03 12:05:50 +03:00
committed by intellij-monorepo-bot
parent 6920302df3
commit 4daf0e509c
6 changed files with 89 additions and 22 deletions

View File

@@ -8,6 +8,19 @@ public final class BasicLiteralUtil {
private BasicLiteralUtil() {
}
/**
* Returns the indent string for a text block expression. The indent string is calculated based on the indentation
* of the text block lines.
*
* @param expression the text block expression to calculate indent for
* @return The indent string of the text block, or null if it is impossible to determine indent string.
*/
public static @Nullable String getTextBlockIndentString(@NotNull PsiElement expression) {
String[] lines = getTextBlockLines(expression.getText(), true);
if (lines == null) return null;
return getTextBlockIndentString(lines);
}
/**
* @param expression text block expression to calculate indent for
* @return the indent of text block lines; may return -1 if text block is heavily malformed
@@ -18,7 +31,6 @@ public final class BasicLiteralUtil {
return getTextBlockIndent(lines);
}
/**
* Returns the lines of text inside the quotes of a text block. No further processing is performed.
* Any escaped characters will remain escaped. Indent is not stripped.
@@ -65,17 +77,48 @@ public final class BasicLiteralUtil {
}
public static int getTextBlockIndent(String @NotNull [] lines, boolean preserveContent, boolean ignoreLastLine) {
return getTextBlockIndentInner(lines, preserveContent, ignoreLastLine).indentSize;
}
private static @NotNull IndentResult getTextBlockIndentInner(String @NotNull[] lines, boolean preserveContent, boolean ignoreLastLine) {
int prefix = Integer.MAX_VALUE;
int position = -1;
for (int i = 0; i < lines.length && prefix != 0; i++) {
String line = lines[i];
int indent = 0;
while (indent < line.length() && Character.isWhitespace(line.charAt(indent))) indent++;
if (indent == line.length() && (i < lines.length - 1 || ignoreLastLine)) {
if (!preserveContent) lines[i] = "";
if (lines.length == 1) prefix = indent;
if (lines.length == 1) {
prefix = indent;
position = i;
}
}
else if (indent < prefix) {
prefix = indent;
position = i;
}
else if (indent < prefix) prefix = indent;
}
return prefix;
return new IndentResult(prefix, position);
}
private static @NotNull String getTextBlockIndentString(String @NotNull [] lines) {
IndentResult result = getTextBlockIndentInner(lines, true, false);
String restorationLine = lines[result.indentLineNumber];
StringBuilder builder = new StringBuilder();
for (int i = 0; i < result.indentSize; ++i) {
builder.append(restorationLine.charAt(i));
}
return builder.toString();
}
private static class IndentResult {
private final int indentSize;
private final int indentLineNumber;
private IndentResult(int indentSize, int indentLineNumber) {
this.indentSize = indentSize;
this.indentLineNumber = indentLineNumber;
}
}
}

View File

@@ -1,7 +1,6 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.psi.formatter.java
import com.intellij.openapi.fileTypes.FileType
import com.intellij.openapi.util.TextRange
import com.intellij.psi.*
import com.intellij.psi.codeStyle.CodeStyleSettings
@@ -12,15 +11,15 @@ import com.intellij.util.text.CharArrayUtil
class AdjustWhitespaceLineTextBlockReformatPostProcessor : PostFormatProcessor {
override fun processElement(source: PsiElement, settings: CodeStyleSettings): PsiElement {
processFile(source.containingFile, source.textRange, settings)
processFile(source.containingFile, source.textRange)
return source
}
override fun processText(source: PsiFile, rangeToReformat: TextRange, settings: CodeStyleSettings): TextRange {
return processFile(source, rangeToReformat, settings)
return processFile(source, rangeToReformat)
}
private fun processFile(source: PsiFile, rangeToReformat: TextRange, settings: CodeStyleSettings): TextRange {
private fun processFile(source: PsiFile, rangeToReformat: TextRange): TextRange {
val documentManager = PsiDocumentManager.getInstance(source.project)
val document = documentManager.getDocument(source) ?: return rangeToReformat
@@ -30,12 +29,11 @@ class AdjustWhitespaceLineTextBlockReformatPostProcessor : PostFormatProcessor {
var deltaLength = 0
textBlockVisitor.whiteSpaceRangesWithIndentList.sortedByDescending {
it.whiteSpaceRange.startOffset
}.forEach { (range, indent) ->
val newSpaces = buildWhitespaceString(settings, source.fileType, indent)
deltaLength += newSpaces.length
}.forEach { (range, indentString) ->
deltaLength += indentString.length
deltaLength -= range.length
document.deleteString(range.startOffset, range.endOffset)
document.insertString(range.startOffset, newSpaces)
document.insertString(range.startOffset, indentString)
}
documentManager.commitDocument(document)
@@ -44,11 +42,6 @@ class AdjustWhitespaceLineTextBlockReformatPostProcessor : PostFormatProcessor {
override fun isWhitespaceOnly(): Boolean = true
private fun buildWhitespaceString(settings: CodeStyleSettings, fileType: FileType, indent: Int): String {
val whitespaceSymbol = if (settings.useTabCharacter(fileType)) "\t" else " "
return whitespaceSymbol.repeat(indent)
}
private class TextBlockVisitor(private val rangeToReformat: TextRange) : JavaRecursiveElementVisitor() {
val whiteSpaceRangesWithIndentList: List<WhiteSpaceRangeWithIndent>
get() = _whiteSpaceRangesWithIndentList
@@ -60,13 +53,13 @@ class AdjustWhitespaceLineTextBlockReformatPostProcessor : PostFormatProcessor {
return
}
val indent = PsiLiteralUtil.getTextBlockIndent(literal)
if (indent == -1) {
val indentString = PsiLiteralUtil.getTextBlockIndentString(literal)
if (indentString == null) {
return
}
val text = literal.text
JavaFormatterUtil.extractTextRangesFromLiteralText(text, indent, true)
JavaFormatterUtil.extractTextRangesFromLiteralText(text, indentString.length, true)
.filter { textRange ->
rangeToReformat.contains(textRange.shiftRight(literal.startOffset)) &&
CharArrayUtil.isEmptyOrSpaces(literal.text, textRange.startOffset, textRange.endOffset)
@@ -74,11 +67,11 @@ class AdjustWhitespaceLineTextBlockReformatPostProcessor : PostFormatProcessor {
.map { it.shiftRight(literal.startOffset) }
.reversed()
.forEach {
_whiteSpaceRangesWithIndentList.add(WhiteSpaceRangeWithIndent(it, indent))
_whiteSpaceRangesWithIndentList.add(WhiteSpaceRangeWithIndent(it, indentString))
}
}
}
private data class WhiteSpaceRangeWithIndent(val whiteSpaceRange: TextRange, val indent: Int)
private data class WhiteSpaceRangeWithIndent(val whiteSpaceRange: TextRange, val indentString: String)
}

View File

@@ -418,6 +418,16 @@ public final class PsiLiteralUtil {
return BasicLiteralUtil.getTextBlockIndent(expression);
}
/**
* Retrieves the indent string of a text block literal expression.
* @param expression The text block literal expression.
* @return The indent string of the text block, or null if it is impossible to determine indent string.
* @see #getTextBlockIndent(PsiLiteralExpression)
*/
public static @Nullable String getTextBlockIndentString(@NotNull PsiLiteralExpression expression) {
return BasicLiteralUtil.getTextBlockIndentString(expression);
}
/**
* @see #getTextBlockIndent(PsiLiteralExpression)
*/

View File

@@ -0,0 +1,8 @@
public class Formatter {
void foo() {
String sss = """
foo""";
}
}

View File

@@ -0,0 +1,8 @@
public class Formatter {
void foo() {
String sss = """
foo""";
}
}

View File

@@ -30,6 +30,11 @@ class AdjustWhitespaceLineTextBlockReformatPostProcessorTest : LightPlatformCode
doTest()
}
fun testNonIntegerNumberOfTabs() {
getCommonSettings().indentOptions?.USE_TAB_CHARACTER = true
getJavaSettings().ALIGN_MULTILINE_TEXT_BLOCKS = true
doTest()
}
fun testAlignTextBlockWhitespacesLessThanAlignment() {
getJavaSettings().ALIGN_MULTILINE_TEXT_BLOCKS = true