diff --git a/java/java-frontback-psi-api/src/com/intellij/psi/BasicLiteralUtil.java b/java/java-frontback-psi-api/src/com/intellij/psi/BasicLiteralUtil.java index 6ccfd562773e..1787d84e396d 100644 --- a/java/java-frontback-psi-api/src/com/intellij/psi/BasicLiteralUtil.java +++ b/java/java-frontback-psi-api/src/com/intellij/psi/BasicLiteralUtil.java @@ -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; + } } } diff --git a/java/java-impl/src/com/intellij/psi/formatter/java/AdjustWhitespaceLineTextBlockReformatPostProcessor.kt b/java/java-impl/src/com/intellij/psi/formatter/java/AdjustWhitespaceLineTextBlockReformatPostProcessor.kt index 0304356b9979..383fd0b24506 100644 --- a/java/java-impl/src/com/intellij/psi/formatter/java/AdjustWhitespaceLineTextBlockReformatPostProcessor.kt +++ b/java/java-impl/src/com/intellij/psi/formatter/java/AdjustWhitespaceLineTextBlockReformatPostProcessor.kt @@ -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 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) } \ No newline at end of file diff --git a/java/java-psi-api/src/com/intellij/psi/util/PsiLiteralUtil.java b/java/java-psi-api/src/com/intellij/psi/util/PsiLiteralUtil.java index bf7344a70bb1..9c4cb12bcbd4 100644 --- a/java/java-psi-api/src/com/intellij/psi/util/PsiLiteralUtil.java +++ b/java/java-psi-api/src/com/intellij/psi/util/PsiLiteralUtil.java @@ -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) */ diff --git a/java/java-tests/testData/psi/formatter/java/textBlock/NonIntegerNumberOfTabs.java b/java/java-tests/testData/psi/formatter/java/textBlock/NonIntegerNumberOfTabs.java new file mode 100644 index 000000000000..e3bebdd9fb0b --- /dev/null +++ b/java/java-tests/testData/psi/formatter/java/textBlock/NonIntegerNumberOfTabs.java @@ -0,0 +1,8 @@ +public class Formatter { + void foo() { + String sss = """ + + + foo"""; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/psi/formatter/java/textBlock/NonIntegerNumberOfTabs_after.java b/java/java-tests/testData/psi/formatter/java/textBlock/NonIntegerNumberOfTabs_after.java new file mode 100644 index 000000000000..1d2e72216257 --- /dev/null +++ b/java/java-tests/testData/psi/formatter/java/textBlock/NonIntegerNumberOfTabs_after.java @@ -0,0 +1,8 @@ +public class Formatter { + void foo() { + String sss = """ + + + foo"""; + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/psi/formatter/java/AdjustWhitespaceLineTextBlockReformatPostProcessorTest.kt b/java/java-tests/testSrc/com/intellij/java/psi/formatter/java/AdjustWhitespaceLineTextBlockReformatPostProcessorTest.kt index 202eba478fcd..d7417956eb7d 100644 --- a/java/java-tests/testSrc/com/intellij/java/psi/formatter/java/AdjustWhitespaceLineTextBlockReformatPostProcessorTest.kt +++ b/java/java-tests/testSrc/com/intellij/java/psi/formatter/java/AdjustWhitespaceLineTextBlockReformatPostProcessorTest.kt @@ -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