Spacing (@Nullable Block child1, @NotNull Block child2) { final CommonCodeStyleSettings settings = myContext.getSettings(); final PyCodeStyleSettings pySettings = myContext.getPySettings(); if (child1 instanceof ASTBlock && child2 instanceof ASTBlock) { final ASTNode node1 = ((ASTBlock)child1).getNode(); ASTNode node2 = ((ASTBlock)child2).getNode(); final IElementType childType1 = node1.getElementType(); final PsiElement psi1 = node1.getPsi(); PsiElement psi2 = node2.getPsi(); if (psi2 instanceof PyStatementList) { // Quite odd getSpacing() doesn't get called with child1=null for the first statement // in the statement list of a class, yet it does get called for the preceding colon and // the statement list itself. Hence we have to handle blank lines around methods here in // addition to SpacingBuilder. if (myNode.getElementType() == PyElementTypes.CLASS_DECLARATION) { final PyStatement[] statements = ((PyStatementList)psi2).getStatements(); if (statements.length > 0 && statements[0] instanceof PyFunction) { return getBlankLinesForOption(pySettings.BLANK_LINES_BEFORE_FIRST_METHOD); } } if (childType1 == PyTokenTypes.COLON && (needLineBreakInStatement())) { return Spacing.createSpacing(0, 0, 1, true, settings.KEEP_BLANK_LINES_IN_CODE); } } // pycodestyle.py enforces at most 2 blank lines only between comments directly // at the top-level of a file, not inside if, try/except, etc. if (psi1 instanceof PsiComment && myNode.getPsi() instanceof PsiFile) { return Spacing.createSpacing(0, 0, 1, true, 2); } // skip not inline comments to handles blank lines between various declarations if (psi2 instanceof PsiComment && hasLineBreaksBeforeInSameParent(node2, 1)) { final PsiElement nonCommentAfter = PyPsiUtils.getNextNonCommentSibling(psi2, true); if (nonCommentAfter != null) { psi2 = nonCommentAfter; } } node2 = psi2.getNode(); final IElementType childType2 = psi2.getNode().getElementType(); child2 = getSubBlockByNode(node2); if (isInsideFStringFragmentWithEqualsSign(myNode)) { return Spacing.getReadOnlySpacing(); } if (childType1 == PyTokenTypes.FSTRING_FRAGMENT_START) { final LeafElement firstLeaf = TreeUtil.findFirstLeaf(node2); if (firstLeaf != null && firstLeaf.getElementType() == PyTokenTypes.LBRACE) { return Spacing.createSpacing(1, 1, 0, false, 0); } } if (childType2 == PyTokenTypes.FSTRING_FRAGMENT_END) { final ASTNode lastLeaf = TreeUtil.findLastLeaf(node1); if (lastLeaf != null && lastLeaf.getElementType() == PyTokenTypes.RBRACE) { return Spacing.createSpacing(1, 1, 0, false, 0); } } if ((childType1 == PyTokenTypes.EQ || childType2 == PyTokenTypes.EQ)) { final PyNamedParameter namedParameter = as(myNode.getPsi(), PyNamedParameter.class); if (namedParameter != null && namedParameter.getAnnotation() != null) { return Spacing.createSpacing(1, 1, 0, settings.KEEP_LINE_BREAKS, settings.KEEP_BLANK_LINES_IN_CODE); } } if (psi1 instanceof PyImportStatementBase) { if (psi2 instanceof PyImportStatementBase) { final Boolean leftImportIsGroupStart = psi1.getCopyableUserData(PythonCodeStyleService.IMPORT_GROUP_BEGIN); final Boolean rightImportIsGroupStart = psi2.getCopyableUserData(PythonCodeStyleService.IMPORT_GROUP_BEGIN); // Cleanup user data, it's no longer needed psi1.putCopyableUserData(PythonCodeStyleService.IMPORT_GROUP_BEGIN, null); // Don't remove IMPORT_GROUP_BEGIN from the element psi2 yet, because spacing is constructed pairwise: // it might be needed on the next iteration. //psi2.putCopyableUserData(IMPORT_GROUP_BEGIN, null); if (rightImportIsGroupStart != null) { return Spacing.createSpacing(0, 0, 2, true, 1); } else if (leftImportIsGroupStart != 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)) { // If there is any function or class after a top-level import, it should be top-level as well if (PyElementTypes.CLASS_OR_FUNCTION.contains(childType2)) { return getBlankLinesForOption(Math.max(settings.BLANK_LINES_AFTER_IMPORTS, pySettings.BLANK_LINES_AROUND_TOP_LEVEL_CLASSES_FUNCTIONS)); } return getBlankLinesForOption(settings.BLANK_LINES_AFTER_IMPORTS); } else { return getBlankLinesForOption(pySettings.BLANK_LINES_AFTER_LOCAL_IMPORTS); } } } if (psi2 instanceof PyImportStatementBase && AddImportHelper.isAssignmentToModuleLevelDunderName(psi1)) { // blank line between module-level dunder name and import statement return Spacing.createSpacing(0, 0, 2, settings.KEEP_LINE_BREAKS, settings.KEEP_BLANK_LINES_IN_DECLARATIONS); } if ((PyElementTypes.CLASS_OR_FUNCTION.contains(childType1) && STATEMENT_OR_DECLARATION.contains(childType2)) || STATEMENT_OR_DECLARATION.contains(childType1) && PyElementTypes.CLASS_OR_FUNCTION.contains(childType2)) { if (PyUtil.isTopLevel(psi1)) { return getBlankLinesForOption(pySettings.BLANK_LINES_AROUND_TOP_LEVEL_CLASSES_FUNCTIONS); } } } return myContext.getSpacingBuilder().getSpacing(this, child1, child2); }