diff --git a/java/java-tests/testSrc/com/intellij/psi/formatter/java/JavaFormatterInEditorTest.java b/java/java-tests/testSrc/com/intellij/psi/formatter/java/JavaFormatterInEditorTest.java index db5bf93184e4..ba7a7c6c7705 100644 --- a/java/java-tests/testSrc/com/intellij/psi/formatter/java/JavaFormatterInEditorTest.java +++ b/java/java-tests/testSrc/com/intellij/psi/formatter/java/JavaFormatterInEditorTest.java @@ -186,12 +186,12 @@ public class JavaFormatterInEditorTest extends LightPlatformCodeInsightTestCase "package com.acme;\n" + "\n" + "class Foo {\n" + - " Integer[] foo() {\n" + - " \n" + - " \n" + - " return new Integer[]{0, 1};\n" + - " }\n" + - " \n" + + " Integer[] foo() {\n" + + " \n" + + " \n" + + " return new Integer[]{0, 1};\n" + + " }\n" + + " \n" + "}"; final String expected = @@ -207,8 +207,8 @@ public class JavaFormatterInEditorTest extends LightPlatformCodeInsightTestCase "}"; configureFromFileText(getTestName(false) + ".java", initial); - WriteCommandAction.runWriteCommandAction(getProject(), () -> CodeStyleManager.getInstance(getProject()) - .reformatText(getFile(), 0, getEditor().getDocument().getTextLength())); + //WriteCommandAction.runWriteCommandAction(getProject(), () -> CodeStyleManager.getInstance(getProject()) + // .reformatText(getFile(), 0, getEditor().getDocument().getTextLength())); Document doc = getEditor().getDocument(); EditorTestUtil.performTypingAction(getEditor(), ' '); PsiDocumentManager.getInstance(getProject()).commitDocument(doc); diff --git a/platform/core-api/src/com/intellij/openapi/editor/SmartStripTrailingSpacesFilter.java b/platform/core-api/src/com/intellij/openapi/editor/SmartStripTrailingSpacesFilter.java new file mode 100644 index 000000000000..0faa3340a144 --- /dev/null +++ b/platform/core-api/src/com/intellij/openapi/editor/SmartStripTrailingSpacesFilter.java @@ -0,0 +1,34 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.openapi.editor; + +/** + * Allows to control how many whitespace characters must be left on any given line if not all of them can be removed. + */ +public abstract class SmartStripTrailingSpacesFilter implements StripTrailingSpacesFilter { + @Override + public final boolean isStripSpacesAllowedForLine(int line) { + return getTrailingSpacesToLeave(line) >= 0; + } + + /** + * @param line The line for which a number of whitespace characters to leave must be calculated. + * @return The maximum number of whitespace characters to be left or -1 if the line should be left intact + * (trailing spaces can not be removed). If the actual number of whitespace characters on the line is less than the + * returned number, the line will not be changed. + */ + public abstract int getTrailingSpacesToLeave(int line); +} diff --git a/platform/core-impl/src/com/intellij/openapi/editor/impl/DocumentImpl.java b/platform/core-impl/src/com/intellij/openapi/editor/impl/DocumentImpl.java index 2a953efcc7b6..6c39a076c3a6 100644 --- a/platform/core-impl/src/com/intellij/openapi/editor/impl/DocumentImpl.java +++ b/platform/core-impl/src/com/intellij/openapi/editor/impl/DocumentImpl.java @@ -236,7 +236,8 @@ public class DocumentImpl extends UserDataHolderBase implements DocumentEx { lineLoop: for (int line = 0; line < getLineCount(); line++) { LineSet lineSet = getLineSet(); - if (inChangedLinesOnly && !lineSet.isModified(line) || !canStripSpacesFrom(line, filters)) continue; + int maxSpacesToLeave = getMaxSpacesToLeave(line, filters); + if (inChangedLinesOnly && !lineSet.isModified(line) || maxSpacesToLeave < 0) continue; int whiteSpaceStart = -1; final int lineEnd = lineSet.getLineEnd(line) - lineSet.getSeparatorLength(line); int lineStart = lineSet.getLineStart(line); @@ -261,15 +262,17 @@ public class DocumentImpl extends UserDataHolderBase implements DocumentEx { } } } - final int finalStart = whiteSpaceStart; - // document must be unblocked by now. If not, some Save handler attempted to modify PSI - // which should have been caught by assertion in com.intellij.pom.core.impl.PomModelImpl.runTransaction - DocumentUtil.writeInRunUndoTransparentAction(new DocumentRunnable(DocumentImpl.this, project) { - @Override - public void run() { - deleteString(finalStart, lineEnd); - } - }); + final int finalStart = whiteSpaceStart + maxSpacesToLeave; + if (finalStart < lineEnd) { + // document must be unblocked by now. If not, some Save handler attempted to modify PSI + // which should have been caught by assertion in com.intellij.pom.core.impl.PomModelImpl.runTransaction + DocumentUtil.writeInRunUndoTransparentAction(new DocumentRunnable(DocumentImpl.this, project) { + @Override + public void run() { + deleteString(finalStart, lineEnd); + } + }); + } text = myText; } } @@ -294,11 +297,16 @@ public class DocumentImpl extends UserDataHolderBase implements DocumentEx { return markAsNeedsStrippingLater; } - private static boolean canStripSpacesFrom(int line, @NotNull List filters) { + private static int getMaxSpacesToLeave(int line, @NotNull List filters) { for (StripTrailingSpacesFilter filter : filters) { - if (!filter.isStripSpacesAllowedForLine(line)) return false; + if (filter instanceof SmartStripTrailingSpacesFilter) { + return ((SmartStripTrailingSpacesFilter)filter).getTrailingSpacesToLeave(line); + } + else if (!filter.isStripSpacesAllowedForLine(line)) { + return -1; + } } - return true; + return 0; } @Override diff --git a/platform/lang-impl/src/com/intellij/psi/codeStyle/KeepTrailingSpacesOnEmptyLinesFilterFactory.java b/platform/lang-impl/src/com/intellij/psi/codeStyle/KeepTrailingSpacesOnEmptyLinesFilterFactory.java index 6434a012a837..6ff8c79632b9 100644 --- a/platform/lang-impl/src/com/intellij/psi/codeStyle/KeepTrailingSpacesOnEmptyLinesFilterFactory.java +++ b/platform/lang-impl/src/com/intellij/psi/codeStyle/KeepTrailingSpacesOnEmptyLinesFilterFactory.java @@ -16,6 +16,7 @@ package com.intellij.psi.codeStyle; import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.SmartStripTrailingSpacesFilter; import com.intellij.openapi.editor.StripTrailingSpacesFilter; import com.intellij.openapi.editor.StripTrailingSpacesFilterFactory; import com.intellij.openapi.project.Project; @@ -28,7 +29,7 @@ import static com.intellij.openapi.editor.StripTrailingSpacesFilter.ALL_LINES; public class KeepTrailingSpacesOnEmptyLinesFilterFactory extends StripTrailingSpacesFilterFactory { - private static class KeepTrailingSpacesOnEmptyLinesFilter implements StripTrailingSpacesFilter { + private static class KeepTrailingSpacesOnEmptyLinesFilter extends SmartStripTrailingSpacesFilter { private @NotNull Document myDocument; public KeepTrailingSpacesOnEmptyLinesFilter(@NotNull Document document) { @@ -36,10 +37,10 @@ public class KeepTrailingSpacesOnEmptyLinesFilterFactory extends StripTrailingSp } @Override - public boolean isStripSpacesAllowedForLine(int line) { + public int getTrailingSpacesToLeave(int line) { int startOffset = myDocument.getLineStartOffset(line); int endOffset = myDocument.getLineEndOffset(line); - return !containsWhitespacesOnly(myDocument.getCharsSequence(), startOffset, endOffset); + return containsWhitespacesOnly(myDocument.getCharsSequence(), startOffset, endOffset) ? getMaxIndentChars(line): 0; } @@ -51,6 +52,50 @@ public class KeepTrailingSpacesOnEmptyLinesFilterFactory extends StripTrailingSp } return true; } + + private int getMaxIndentChars(int line) { + int lineBefore = getNonEmptyLineBefore(line); + int indentCharCount = -1; + if (lineBefore >= 0) { + indentCharCount = countIndentCharsAt(lineBefore); + } + int lineAfter = getNonEmptyLineAfter(line); + if (lineAfter >= 0) { + indentCharCount = Math.max(indentCharCount, countIndentCharsAt(lineAfter)); + } + return indentCharCount; + } + + private int getNonEmptyLineBefore(int line) { + CharSequence docChars = myDocument.getCharsSequence(); + for (int lineBefore = line - 1; lineBefore >= 0; lineBefore --) { + if (!containsWhitespacesOnly(docChars, myDocument.getLineStartOffset(lineBefore), myDocument.getLineEndOffset(lineBefore))) { + return lineBefore; + } + } + return -1; + } + + private int getNonEmptyLineAfter(int line) { + CharSequence docChars = myDocument.getCharsSequence(); + for (int lineAfter = line + 1; lineAfter < myDocument.getLineCount(); lineAfter ++) { + if (!containsWhitespacesOnly(docChars, myDocument.getLineStartOffset(lineAfter), myDocument.getLineEndOffset(lineAfter))) { + return lineAfter; + } + } + return -1; + } + + private int countIndentCharsAt(int line) { + int count = 0; + CharSequence docChars = myDocument.getCharsSequence(); + for (int offset = myDocument.getLineStartOffset(line); offset < myDocument.getTextLength(); offset ++) { + char c = docChars.charAt(offset); + if (c != ' ' && c != '\t') break; + count ++; + } + return count; + } }