Platform SpacingBuilder supports simple dependentLFSpacing dependent on parent block

This type of dependent line feed spacing is often used to implement
code style settings like "New line after '['" and "Put ']' on new line"
for collection literals and similar elements in various languages.
This commit is contained in:
Mikhail Golubev
2015-05-27 22:45:01 +03:00
parent 6b5ea8f750
commit 890f1c8ea1
5 changed files with 82 additions and 64 deletions
@@ -21,7 +21,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.List;
import static com.intellij.json.JsonElementTypes.*;
import static com.intellij.json.JsonParserDefinition.*;
import static com.intellij.json.JsonParserDefinition.JSON_CONTAINERS;
import static com.intellij.json.formatter.JsonCodeStyleSettings.ALIGN_PROPERTY_ON_COLON;
import static com.intellij.json.formatter.JsonCodeStyleSettings.ALIGN_PROPERTY_ON_VALUE;
import static com.intellij.json.psi.JsonPsiUtil.hasElementType;
@@ -165,24 +165,6 @@ public class JsonBlock implements ASTBlock {
@Nullable
@Override
public Spacing getSpacing(@Nullable Block child1, @NotNull Block child2) {
final CommonCodeStyleSettings commonSettings = getCommonSettings();
final ASTNode leftChild = child1 instanceof JsonBlock ? ((JsonBlock)child1).myNode : null;
final ASTNode rightChild = child2 instanceof JsonBlock ? ((JsonBlock)child2).myNode : null;
// This causes braces/brackets to be on their own lines if whole object/array spans several lines.
if (leftChild != null && rightChild != null) {
if (hasElementType(leftChild, JSON_BRACES) ^ hasElementType(rightChild, JSON_BRACES)) {
final int numSpaces = commonSettings.SPACE_WITHIN_BRACES ? 1 : 0;
return Spacing.createDependentLFSpacing(numSpaces, numSpaces, myNode.getTextRange(),
commonSettings.KEEP_LINE_BREAKS,
commonSettings.KEEP_BLANK_LINES_IN_CODE);
}
else if (hasElementType(leftChild, JSON_BRACKETS) ^ hasElementType(rightChild, JSON_BRACKETS)) {
final int numSpaces = commonSettings.SPACE_WITHIN_BRACKETS ? 1 : 0;
return Spacing.createDependentLFSpacing(numSpaces, numSpaces, myNode.getTextRange(),
commonSettings.KEEP_LINE_BREAKS,
commonSettings.KEEP_BLANK_LINES_IN_CODE);
}
}
return mySpacingBuilder.getSpacing(this, child1, child2);
}
@@ -41,8 +41,8 @@ public class JsonFormattingBuilderModel implements FormattingModelBuilder {
return new SpacingBuilder(settings, JsonLanguage.INSTANCE)
.before(COLON).spacing(spacesBeforeColon, spacesBeforeColon, 0, false, 0)
.after(COLON).spacing(spacesAfterColon, spacesAfterColon, 0, false, 0)
.withinPair(L_BRACKET, R_BRACKET).spaceIf(commonSettings.SPACE_WITHIN_BRACKETS)
.withinPair(L_CURLY, R_CURLY).spaceIf(commonSettings.SPACE_WITHIN_BRACES)
.withinPair(L_BRACKET, R_BRACKET).spaceIf(commonSettings.SPACE_WITHIN_BRACKETS, true)
.withinPair(L_CURLY, R_CURLY).spaceIf(commonSettings.SPACE_WITHIN_BRACES, true)
.before(COMMA).spacing(spacesBeforeComma, spacesBeforeComma, 0, false, 0)
.after(COMMA).spaceIf(commonSettings.SPACE_AFTER_COMMA);
}
@@ -1,6 +1,7 @@
package com.intellij.formatting;
import com.intellij.lang.Language;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.codeStyle.CodeStyleSettings;
import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
import com.intellij.psi.tree.IElementType;
@@ -16,19 +17,15 @@ import java.util.List;
*/
public class SpacingBuilder {
private static class SpacingRule {
private final TokenSet myParentType;
private final TokenSet myChild1Type;
private final TokenSet myChild2Type;
private final int myMinSpaces;
private final int myMaxSpaces;
private final int myMinLF;
private final boolean myKeepLineBreaks;
private final int myKeepBlankLines;
protected final RuleCondition myRuleCondition;
protected final int myMinSpaces;
protected final int myMaxSpaces;
protected final int myMinLF;
protected final boolean myKeepLineBreaks;
protected final int myKeepBlankLines;
private SpacingRule(RuleCondition condition, int minSpaces, int maxSpaces, int minLF, boolean keepLineBreaks, int keepBlankLines) {
myParentType = condition.myParentType;
myChild1Type = condition.myChild1Type;
myChild2Type = condition.myChild2Type;
private SpacingRule(@NotNull RuleCondition condition, int minSpaces, int maxSpaces, int minLF, boolean keepLineBreaks, int keepBlankLines) {
myRuleCondition = condition;
myMinSpaces = minSpaces;
myMaxSpaces = maxSpaces;
myMinLF = minLF;
@@ -36,17 +33,33 @@ public class SpacingBuilder {
myKeepBlankLines = keepBlankLines;
}
public boolean matches(IElementType parentType, IElementType child1Type, IElementType child2Type) {
return ((myParentType == null || myParentType.contains(parentType)) &&
(myChild1Type == null || myChild1Type.contains(child1Type)) &&
(myChild2Type == null || myChild2Type.contains(child2Type)));
public boolean matches(@NotNull ASTBlock parentBlock, @NotNull ASTBlock childBlock1, @NotNull ASTBlock childBlock2) {
return myRuleCondition.matches(parentBlock.getNode().getElementType(),
childBlock1.getNode().getElementType(),
childBlock2.getNode().getElementType());
}
public Spacing createSpacing() {
public Spacing createSpacing(@NotNull ASTBlock parentBlock, @NotNull ASTBlock childBlock1, @NotNull ASTBlock childBlock2) {
return Spacing.createSpacing(myMinSpaces, myMaxSpaces, myMinLF, myKeepLineBreaks, myKeepBlankLines);
}
}
private static class DependentLFSpacingRule extends SpacingRule {
public DependentLFSpacingRule(@NotNull RuleCondition condition,
int minSpaces,
int maxSpaces,
boolean keepLineBreaks,
int keepBlankLines) {
super(condition, minSpaces, maxSpaces, 1, keepLineBreaks, keepBlankLines);
}
@Override
public Spacing createSpacing(@NotNull ASTBlock parentBlock, @NotNull ASTBlock childBlock1, @NotNull ASTBlock childBlock2) {
final TextRange range = parentBlock.getNode().getTextRange();
return Spacing.createDependentLFSpacing(myMinSpaces, myMaxSpaces, range, myKeepLineBreaks, myKeepBlankLines);
}
}
private static class RuleCondition {
private final TokenSet myParentType;
private final TokenSet myChild1Type;
@@ -57,6 +70,12 @@ public class SpacingBuilder {
myChild1Type = child1Type;
myChild2Type = child2Type;
}
private boolean matches(@NotNull IElementType parentType, @NotNull IElementType firstChildType, @NotNull IElementType secondChildType) {
return ((myParentType == null || myParentType.contains(parentType)) &&
(myChild1Type == null || myChild1Type.contains(firstChildType)) &&
(myChild2Type == null || myChild2Type.contains(secondChildType)));
}
}
public class RuleBuilder {
@@ -71,15 +90,36 @@ public class SpacingBuilder {
}
public SpacingBuilder spaceIf(boolean option) {
return spaces(option ? 1 : 0);
return spaceIf(option, false);
}
public SpacingBuilder spaces(final int count) {
for (RuleCondition condition : myConditions) {
myRules.add(new SpacingRule(condition, count, count, 0,
myCodeStyleSettings.KEEP_LINE_BREAKS, myCodeStyleSettings.KEEP_BLANK_LINES_IN_CODE));
/**
* If {@code useParentDependentLFSpacing} is true and parent block spans multiple lines, insert single line break.
* Otherwise insert whitespace block with exactly one or no spaces depending on value of {@code option} parameter.
*
* @see Spacing#createDependentLFSpacing
*/
public SpacingBuilder spaceIf(boolean option, boolean useParentDependentLFSpacing) {
return spaces(option ? 1 : 0, useParentDependentLFSpacing);
}
public SpacingBuilder spaces(int count) {
return spaces(count, false);
}
/**
* If {@code useParentDependentLFSpacing} is true and parent block spans multiple lines, insert single line break.
* Otherwise insert whitespace block that contains as many spaces as specified via {@code count} parameter.
*
* @see Spacing#createDependentLFSpacing
*/
public SpacingBuilder spaces(int count, boolean useParentDependentLFSpacing) {
if (useParentDependentLFSpacing) {
return parentDependentLFSpacing(count, count, myCodeStyleSettings.KEEP_LINE_BREAKS, myCodeStyleSettings.KEEP_BLANK_LINES_IN_CODE);
}
else {
return spacing(count, count, 0, myCodeStyleSettings.KEEP_LINE_BREAKS, myCodeStyleSettings.KEEP_BLANK_LINES_IN_CODE);
}
return SpacingBuilder.this;
}
public SpacingBuilder blankLines(int count) {
@@ -118,6 +158,18 @@ public class SpacingBuilder {
}
return SpacingBuilder.this;
}
/**
* Similar to {@link #spacing} but replaced by single line break, if parent block spans multiple lines.
*
* @see Spacing#createDependentLFSpacing
*/
public SpacingBuilder parentDependentLFSpacing(int minSpaces, int maxSpaces, boolean keepLineBreaks, int keepBlankLines) {
for (RuleCondition condition : myConditions) {
myRules.add(new DependentLFSpacingRule(condition, minSpaces, maxSpaces, keepLineBreaks, keepBlankLines));
}
return SpacingBuilder.this;
}
}
private final CommonCodeStyleSettings myCodeStyleSettings;
@@ -271,12 +323,9 @@ public class SpacingBuilder {
if (!(parent instanceof ASTBlock) || !(child1 instanceof ASTBlock) || !(child2 instanceof ASTBlock)) {
return null;
}
IElementType parentType = ((ASTBlock) parent).getNode().getElementType();
IElementType child1Type = ((ASTBlock) child1).getNode().getElementType();
IElementType child2Type = ((ASTBlock) child2).getNode().getElementType();
for (SpacingRule rule : myRules) {
if (rule.matches(parentType, child1Type, child2Type)) {
return rule.createSpacing();
if (rule.matches((ASTBlock)parent, (ASTBlock)child1, (ASTBlock) child2)) {
return rule.createSpacing((ASTBlock)parent, (ASTBlock)child1, (ASTBlock) child2);
}
}
return null;
@@ -684,12 +684,10 @@ public class PyBlock implements ASTBlock {
public Spacing getSpacing(Block child1, @NotNull Block child2) {
if (child1 instanceof ASTBlock && child2 instanceof ASTBlock) {
final ASTNode node1 = ((ASTBlock)child1).getNode();
final ASTNode node2 = ((ASTBlock)child2).getNode();
final PsiElement psi1 = node1.getPsi();
final PsiElement psi2 = ((ASTBlock)child2).getNode().getPsi();
final IElementType childType1 = node1.getElementType();
final IElementType childType2 = node2.getElementType();
if (psi1 instanceof PyImportStatementBase && psi2 instanceof PyImportStatementBase &&
psi2.getCopyableUserData(IMPORT_GROUP_BEGIN) != null) {
return Spacing.createSpacing(0, 0, 2, true, 1);
@@ -718,14 +716,6 @@ public class PyBlock implements ASTBlock {
}
}
if (myNode.getElementType() == PyElementTypes.DICT_LITERAL_EXPRESSION) {
if (shouldInsertNewLineForBrace(childType1) || shouldInsertNewLineForBrace(childType2)) {
final int spaces = settings.SPACE_WITHIN_BRACES ? 1 : 0;
return Spacing.createDependentLFSpacing(spaces, spaces, myNode.getTextRange(),
settings.KEEP_LINE_BREAKS, settings.KEEP_BLANK_LINES_IN_CODE);
}
}
if (psi2 instanceof PsiComment && !hasLineBreaksBefore(psi2.getNode(), 1) && myContext.getPySettings().SPACE_BEFORE_NUMBER_SIGN) {
return Spacing.createSpacing(2, 0, 0, false, 0);
}
@@ -733,11 +723,6 @@ public class PyBlock implements ASTBlock {
return myContext.getSpacingBuilder().getSpacing(this, child1, child2);
}
private boolean shouldInsertNewLineForBrace(IElementType type) {
return (type == PyTokenTypes.LBRACE && myContext.getPySettings().DICT_NEW_LINE_AFTER_LEFT_BRACE) ||
(type == PyTokenTypes.RBRACE && myContext.getPySettings().DICT_NEW_LINE_BEFORE_RIGHT_BRACE);
}
private Spacing getBlankLinesForOption(final int option) {
final int blankLines = option + 1;
return Spacing
@@ -119,6 +119,8 @@ public class PythonFormattingModelBuilder implements FormattingModelBuilderEx, C
.withinPairInside(LPAR, RPAR, PARENTHESIZED_EXPRESSION).spaces(0)
.before(LBRACKET).spaceIf(pySettings.SPACE_BEFORE_LBRACKET)
.afterInside(LBRACE, DICT_LITERAL_EXPRESSION).spaceIf(pySettings.SPACE_WITHIN_BRACES, pySettings.DICT_NEW_LINE_AFTER_LEFT_BRACE)
.beforeInside(RBRACE, DICT_LITERAL_EXPRESSION).spaceIf(pySettings.SPACE_WITHIN_BRACES, pySettings.DICT_NEW_LINE_BEFORE_RIGHT_BRACE)
.withinPair(LBRACE, RBRACE).spaceIf(pySettings.SPACE_WITHIN_BRACES)
.withinPair(LBRACKET, RBRACKET).spaceIf(commonSettings.SPACE_WITHIN_BRACKETS)