PY-17564 Keep blank lines between imports inserted by user
authorMikhail Golubev <mikhail.golubev@jetbrains.com>
Thu, 12 Nov 2015 14:47:00 +0000 (17:47 +0300)
committerMikhail Golubev <mikhail.golubev@jetbrains.com>
Fri, 13 Nov 2015 14:23:05 +0000 (17:23 +0300)
It doesn't solve the problem that reformatting doesn't add blank lines
before import groups according to PEP-8 until "Optimize Imports" is
called on a file, but at least it doesn't remove blank lines inserted
manually by user.

python/src/com/jetbrains/python/formatter/PyBlock.java
python/src/com/jetbrains/python/formatter/PythonFormattingModelBuilder.java
python/src/com/jetbrains/python/inspections/quickfix/MoveFromFutureImportQuickFix.java
python/testData/formatter/blanksBetweenImportsPreservedWithoutOptimizeImports.py [new file with mode: 0644]
python/testData/formatter/blanksBetweenImportsPreservedWithoutOptimizeImports_after.py [new file with mode: 0644]
python/testSrc/com/jetbrains/python/PyFormatterTest.java

index 9269c742eff3bc4fdfa95a27fe08c6ebc23adae4..461df768fb175d114f4a98f7db856bbc8bec913f 100644 (file)
@@ -719,9 +719,16 @@ public class PyBlock implements ASTBlock {
       }
 
       if (psi1 instanceof PyImportStatementBase) {
-        if (psi2 instanceof PyImportStatementBase &&
-            psi2.getCopyableUserData(IMPORT_GROUP_BEGIN) != null) {
-          return Spacing.createSpacing(0, 0, 2, true, 1);
+        if (psi2 instanceof PyImportStatementBase) {
+          if (psi2.getCopyableUserData(IMPORT_GROUP_BEGIN) != null) {
+            return Spacing.createSpacing(0, 0, 2, true, 1);
+          }
+          else if (psi1.getCopyableUserData(IMPORT_GROUP_BEGIN) != null) {
+            // It's a trick to keep spacing consistent when new import statement is inserted
+            // at the beginning of an import group, i.e. if there is a blank line before the next
+            // import we want to save it, but remove line *after* inserted import.
+            return Spacing.createSpacing(0, 0, 1, false, 0);
+          }
         }
         if (psi2 instanceof PyStatement && !(psi2 instanceof PyImportStatementBase)) {
           if (PyUtil.isTopLevel(psi1)) {
index 525d6a96da9b317e95386cc80a590cf71fe78dea..69469754d7709c1b7eed51b6f1a3be849ce3f720 100644 (file)
@@ -17,14 +17,12 @@ package com.jetbrains.python.formatter;
 
 import com.intellij.formatting.*;
 import com.intellij.lang.ASTNode;
-import com.intellij.lang.LanguageParserDefinitions;
 import com.intellij.openapi.util.TextRange;
 import com.intellij.psi.PsiElement;
 import com.intellij.psi.PsiFile;
 import com.intellij.psi.codeStyle.CodeStyleSettings;
 import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
 import com.intellij.psi.tree.IElementType;
-import com.intellij.psi.tree.IFileElementType;
 import com.intellij.psi.tree.TokenSet;
 import com.jetbrains.python.PythonDialectsTokenSetProvider;
 import com.jetbrains.python.PythonLanguage;
@@ -74,7 +72,6 @@ public class PythonFormattingModelBuilder implements FormattingModelBuilderEx, C
   }
 
   protected SpacingBuilder createSpacingBuilder(CodeStyleSettings settings) {
-    final IFileElementType file = LanguageParserDefinitions.INSTANCE.forLanguage(PythonLanguage.getInstance()).getFileNodeType();
     final PyCodeStyleSettings pySettings = settings.getCustomSettings(PyCodeStyleSettings.class);
 
     final CommonCodeStyleSettings commonSettings = settings.getCommonSettings(PythonLanguage.getInstance());
@@ -85,9 +82,10 @@ public class PythonFormattingModelBuilder implements FormattingModelBuilderEx, C
       .between(STATEMENT_OR_DECLARATION, FUNCTION_DECLARATION).blankLines(commonSettings.BLANK_LINES_AROUND_METHOD)
       .after(FUNCTION_DECLARATION).blankLines(commonSettings.BLANK_LINES_AROUND_METHOD)
       .after(CLASS_DECLARATION).blankLines(commonSettings.BLANK_LINES_AROUND_CLASS)
-      // Remove excess blank lines between imports, because ImportOptimizer gets rid of them anyway.
+      // Remove excess blank lines between imports (at most one is allowed). 
+      // Note that ImportOptimizer gets rid of them anyway.
       // Empty lines between import groups are handles in PyBlock#getSpacing
-      .between(IMPORT_STATEMENTS, IMPORT_STATEMENTS).spacing(0, Integer.MAX_VALUE, 1, false, 0)
+      .between(IMPORT_STATEMENTS, IMPORT_STATEMENTS).spacing(0, Integer.MAX_VALUE, 1, false, 1)
       .between(STATEMENT_OR_DECLARATION, STATEMENT_OR_DECLARATION).spacing(0, Integer.MAX_VALUE, 1, false, 1)
 
       .between(COLON, STATEMENT_LIST).spacing(1, Integer.MAX_VALUE, 0, true, 0)
index 585a6d1a3b6de3bf58c9abc4008346f238695c0d..1fe8b3cb88d9714feefce39e4c78f2c638ee442e 100644 (file)
@@ -21,7 +21,9 @@ import com.intellij.openapi.project.Project;
 import com.intellij.psi.PsiElement;
 import com.intellij.psi.PsiFile;
 import com.jetbrains.python.PyBundle;
+import com.jetbrains.python.codeInsight.imports.AddImportHelper;
 import com.jetbrains.python.documentation.docstrings.DocStringUtil;
+import com.jetbrains.python.formatter.PyBlock;
 import com.jetbrains.python.psi.PyFile;
 import com.jetbrains.python.psi.PyStringLiteralExpression;
 import org.jetbrains.annotations.NotNull;
@@ -44,6 +46,7 @@ public class MoveFromFutureImportQuickFix implements LocalQuickFix {
     PsiElement problemElement = descriptor.getPsiElement();
     PsiFile psiFile = problemElement.getContainingFile();
     if (psiFile instanceof PyFile) {
+      problemElement.putCopyableUserData(PyBlock.IMPORT_GROUP_BEGIN, true);
       PyFile file = (PyFile)psiFile;
       PyStringLiteralExpression docString = DocStringUtil.findDocStringExpression(file);
       if (docString != null) {
diff --git a/python/testData/formatter/blanksBetweenImportsPreservedWithoutOptimizeImports.py b/python/testData/formatter/blanksBetweenImportsPreservedWithoutOptimizeImports.py
new file mode 100644 (file)
index 0000000..0e3e952
--- /dev/null
@@ -0,0 +1,9 @@
+import os
+import uuid
+
+from django.db import models
+from django.utils.translation import ugettext_lazy as _
+
+from project import something
+
+print(something, _, models, os, uuid)
diff --git a/python/testData/formatter/blanksBetweenImportsPreservedWithoutOptimizeImports_after.py b/python/testData/formatter/blanksBetweenImportsPreservedWithoutOptimizeImports_after.py
new file mode 100644 (file)
index 0000000..0e3e952
--- /dev/null
@@ -0,0 +1,9 @@
+import os
+import uuid
+
+from django.db import models
+from django.utils.translation import ugettext_lazy as _
+
+from project import something
+
+print(something, _, models, os, uuid)
index 62f7f76454924e3c2a5b9dca79b05b51c05b1bc5..9520cf90f0a3a19f2188b5d99ff597cf5a7c2bf1 100644 (file)
@@ -648,4 +648,9 @@ public class PyFormatterTest extends PyTestCase {
   public void testAlignmentOfEmptyCollectionLiterals() {
     doTest();
   }
+
+  // PY-17593
+  public void testBlanksBetweenImportsPreservedWithoutOptimizeImports() {
+    doTest();
+  }
 }