From edc924f076d9b7ec4b548986d9b51eaab756a834 Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Fri, 24 Apr 2015 15:42:56 +0300 Subject: [PATCH] PY-12360 Post format processor inserts line feed at the end of file if it was enabled in code style settings --- .../python/formatter/PyCodeStyleSettings.java | 2 + .../PyLanguageCodeStyleSettingsProvider.java | 4 +- ...TrailingBlankLinesPostFormatProcessor.java | 94 ++++++++++++++----- 3 files changed, 74 insertions(+), 26 deletions(-) diff --git a/python/src/com/jetbrains/python/formatter/PyCodeStyleSettings.java b/python/src/com/jetbrains/python/formatter/PyCodeStyleSettings.java index 3343fd8ef25e..3bf9855ff9e3 100644 --- a/python/src/com/jetbrains/python/formatter/PyCodeStyleSettings.java +++ b/python/src/com/jetbrains/python/formatter/PyCodeStyleSettings.java @@ -31,6 +31,8 @@ public class PyCodeStyleSettings extends CustomCodeStyleSettings { public boolean SPACE_BEFORE_BACKSLASH = true; public int BLANK_LINES_AROUND_TOP_LEVEL_CLASSES_FUNCTIONS = 2; + // TODO make boolean + public int NEW_LINE_AT_FILE_END = 1; public boolean ALIGN_COLLECTIONS_AND_COMPREHENSIONS = true; public boolean ALIGN_MULTILINE_IMPORTS = true; diff --git a/python/src/com/jetbrains/python/formatter/PyLanguageCodeStyleSettingsProvider.java b/python/src/com/jetbrains/python/formatter/PyLanguageCodeStyleSettingsProvider.java index 05fd182fded0..3780a5c1bcb4 100644 --- a/python/src/com/jetbrains/python/formatter/PyLanguageCodeStyleSettingsProvider.java +++ b/python/src/com/jetbrains/python/formatter/PyLanguageCodeStyleSettingsProvider.java @@ -85,8 +85,10 @@ public class PyLanguageCodeStyleSettingsProvider extends LanguageCodeStyleSettin "BLANK_LINES_AFTER_IMPORTS", "KEEP_BLANK_LINES_IN_DECLARATIONS", "KEEP_BLANK_LINES_IN_CODE"); - consumer.showCustomOption(PyCodeStyleSettings.class, "BLANK_LINES_AROUND_TOP_LEVEL_CLASSES_FUNCTIONS", "Around top-level classes and functions:", + consumer.showCustomOption(PyCodeStyleSettings.class, "BLANK_LINES_AROUND_TOP_LEVEL_CLASSES_FUNCTIONS", + "Around top-level classes and functions:", BLANK_LINES); + consumer.showCustomOption(PyCodeStyleSettings.class, "NEW_LINE_AT_FILE_END", "New line at the end of file", BLANK_LINES); } else if (settingsType == SettingsType.WRAPPING_AND_BRACES_SETTINGS) { consumer.showStandardOptions("RIGHT_MARGIN", diff --git a/python/src/com/jetbrains/python/formatter/PyTrailingBlankLinesPostFormatProcessor.java b/python/src/com/jetbrains/python/formatter/PyTrailingBlankLinesPostFormatProcessor.java index 57bb3761b83b..a39119bba35e 100644 --- a/python/src/com/jetbrains/python/formatter/PyTrailingBlankLinesPostFormatProcessor.java +++ b/python/src/com/jetbrains/python/formatter/PyTrailingBlankLinesPostFormatProcessor.java @@ -17,27 +17,34 @@ package com.jetbrains.python.formatter; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.ex.EditorSettingsExternalizable; +import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiWhiteSpace; import com.intellij.psi.codeStyle.CodeStyleManager; import com.intellij.psi.codeStyle.CodeStyleSettings; +import com.intellij.psi.codeStyle.CodeStyleSettingsManager; import com.intellij.psi.impl.source.codeStyle.PostFormatProcessor; +import com.intellij.psi.util.PsiTreeUtil; import com.jetbrains.python.psi.LanguageLevel; import com.jetbrains.python.psi.PyElementGenerator; import com.jetbrains.python.psi.PyFile; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import static com.jetbrains.python.psi.PyUtil.as; /** - * Strip trailing blank lines at the end of the file if corresponding whitespace element belongs to formatted range/element. - * Final whitespace is replaced by single line feed regardless of whether the option {@link EditorSettingsExternalizable#isEnsureNewLineAtEOF()} - * was enabled, because it's required by PEP 8. Note however that this option is still necessary if file doesn't contain any whitespaces - * at its end initially. + * Strip trailing extra blank lines at the end of the file and insert necessary line feed if corresponding whitespace element belongs to + * formatted range/element. If "Add new line at the file end" option was selected in Python code style settings final whitespace is replaced + * by single line feed, and it removed completely otherwise. + *

+ * Note however that if option {@link EditorSettingsExternalizable#isEnsureNewLineAtEOF()} was also enabled line feed will be added at the + * end of file on next "Save" action regardless of the code style settings for Python. * * @author Mikhail Golubev */ @@ -45,10 +52,10 @@ public class PyTrailingBlankLinesPostFormatProcessor implements PostFormatProces @Override public PsiElement processElement(@NotNull PsiElement source, @NotNull CodeStyleSettings settings) { if (source instanceof PyFile) { - final PsiFile pyFile = (PsiFile)source; - final PsiWhiteSpace lastWhitespace = as(pyFile.getLastChild(), PsiWhiteSpace.class); - if (lastWhitespace != null) { - replaceTrailingWhitespaceBySingleLineFeed(lastWhitespace); + final PyFile pyFile = (PyFile)source; + final PsiWhiteSpace lastWhitespace = findLastWhitespace(pyFile); + if (lastWhitespace != null && PsiTreeUtil.isAncestor(source, lastWhitespace, false)) { + replaceOrDeleteTrailingWhitespace(pyFile, lastWhitespace); } } return source; @@ -59,10 +66,17 @@ public class PyTrailingBlankLinesPostFormatProcessor implements PostFormatProces if (!(source instanceof PyFile)) { return rangeToReformat; } - final PsiWhiteSpace lastWhitespace = as(source.getLastChild(), PsiWhiteSpace.class); - if (lastWhitespace != null && rangeToReformat.intersects(lastWhitespace.getTextRange())) { - final TextRange oldWhitespaceRange = lastWhitespace.getTextRange(); - final TextRange newWhitespaceRange = replaceTrailingWhitespaceBySingleLineFeed(lastWhitespace).getTextRange(); + final PsiWhiteSpace lastWhitespace = findLastWhitespace(source); + final TextRange oldWhitespaceRange = lastWhitespace != null ? lastWhitespace.getTextRange() : TextRange.from(source.getTextLength(), 0); + if (lastWhitespace != null && rangeToReformat.intersects(oldWhitespaceRange)) { + final PsiWhiteSpace newWhitespace = replaceOrDeleteTrailingWhitespace((PyFile)source, lastWhitespace); + final TextRange newWhitespaceRange; + if (newWhitespace != null) { + newWhitespaceRange = newWhitespace.getTextRange(); + } + else { + newWhitespaceRange = TextRange.from(oldWhitespaceRange.getStartOffset(), 0); + } final int delta = newWhitespaceRange.getLength() - oldWhitespaceRange.getLength(); if (newWhitespaceRange.contains(oldWhitespaceRange)) { @@ -84,20 +98,50 @@ public class PyTrailingBlankLinesPostFormatProcessor implements PostFormatProces return rangeToReformat; } - @NotNull - private static PsiWhiteSpace replaceTrailingWhitespaceBySingleLineFeed(@NotNull final PsiWhiteSpace whitespace) { - final PsiDocumentManager manager = PsiDocumentManager.getInstance(whitespace.getProject()); - final Document document = manager.getDocument(whitespace.getContainingFile()); + @Nullable + private static PsiWhiteSpace findLastWhitespace(@NotNull PsiFile file) { + // TODO support ranges of whitespaces with backslashes between them + return as(PsiTreeUtil.lastChild(file), PsiWhiteSpace.class); + } + + @Nullable + private static PsiWhiteSpace replaceOrDeleteTrailingWhitespace(@NotNull final PyFile pyFile, @Nullable final PsiWhiteSpace whitespace) { + final Project project = pyFile.getProject(); + final PsiDocumentManager documentManager = PsiDocumentManager.getInstance(project); + final Document document = documentManager.getDocument(pyFile); if (document != null) { - final PyElementGenerator generator = PyElementGenerator.getInstance(whitespace.getProject()); - final PsiWhiteSpace newWhitespace = generator.createPhysicalFromText(LanguageLevel.forElement(whitespace), PsiWhiteSpace.class, "\n"); - manager.doPostponedOperationsAndUnblockDocument(document); - CodeStyleManager.getInstance(newWhitespace.getProject()).performActionWithFormatterDisabled(new Computable() { - @Override - public PsiWhiteSpace compute() { - return (PsiWhiteSpace)whitespace.replace(newWhitespace); - } - }); + documentManager.doPostponedOperationsAndUnblockDocument(document); + final CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(project); + int numLineFeedsAtEnd = CodeStyleSettingsManager.getSettings(project).getCustomSettings(PyCodeStyleSettings.class).NEW_LINE_AT_FILE_END; + if (numLineFeedsAtEnd <= 0 && EditorSettingsExternalizable.getInstance().isEnsureNewLineAtEOF()) { + numLineFeedsAtEnd = 1; + } + if (numLineFeedsAtEnd > 0) { + final PyElementGenerator generator = PyElementGenerator.getInstance(project); + final String text = StringUtil.repeat("\n", numLineFeedsAtEnd); + final LanguageLevel language = LanguageLevel.forElement(pyFile); + final PsiWhiteSpace lineFeeds = generator.createFromText(language, PsiWhiteSpace.class, "(" + text + ")", new int[]{0, 0, 1}); + codeStyleManager.performActionWithFormatterDisabled(new Computable() { + @Override + public PsiWhiteSpace compute() { + if (whitespace != null) { + return (PsiWhiteSpace)whitespace.replace(lineFeeds); + } + else { + return (PsiWhiteSpace)pyFile.add(lineFeeds); + } + } + }); + } + else if (whitespace != null) { + codeStyleManager.performActionWithFormatterDisabled(new Runnable() { + @Override + public void run() { + whitespace.delete(); + } + }); + return null; + } } return whitespace; }