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;
import com.intellij.psi.tree.TokenSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
/**
* @author yole
*/
public class SpacingBuilder {
private static class SpacingRule {
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(@NotNull RuleCondition condition, int minSpaces, int maxSpaces, int minLF, boolean keepLineBreaks, int keepBlankLines) {
myRuleCondition = condition;
myMinSpaces = minSpaces;
myMaxSpaces = maxSpaces;
myMinLF = minLF;
myKeepLineBreaks = keepLineBreaks;
myKeepBlankLines = keepBlankLines;
}
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(@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;
private final TokenSet myChild2Type;
private RuleCondition(TokenSet parentType, TokenSet child1Type, TokenSet child2Type) {
myParentType = parentType;
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 {
RuleCondition[] myConditions;
private RuleBuilder(RuleCondition... conditions) {
myConditions = conditions;
}
public SpacingBuilder none() {
return spaces(0);
}
public SpacingBuilder spaceIf(boolean option) {
return spaceIf(option, false);
}
/**
* 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);
}
}
public SpacingBuilder blankLines(int count) {
int blankLines = count + 1;
for (RuleCondition condition : myConditions) {
myRules.add(new SpacingRule(condition, 0, 0, blankLines,
myCodeStyleSettings.KEEP_LINE_BREAKS, myCodeStyleSettings.KEEP_BLANK_LINES_IN_DECLARATIONS));
}
return SpacingBuilder.this;
}
public SpacingBuilder lineBreakInCodeIf(boolean option) {
return option ? lineBreakInCode() : SpacingBuilder.this;
}
public SpacingBuilder lineBreakInCode() {
for (RuleCondition condition : myConditions) {
myRules.add(new SpacingRule(condition, 1, 0, 1,
myCodeStyleSettings.KEEP_LINE_BREAKS, myCodeStyleSettings.KEEP_BLANK_LINES_IN_CODE));
}
return SpacingBuilder.this;
}
public SpacingBuilder lineBreakOrForceSpace(boolean lbOption, boolean spaceOption) {
if (lbOption) {
return lineBreakInCode();
}
int count = spaceOption ? 1 : 0;
return spacing(count, count, 0, false, 0);
}
public SpacingBuilder spacing(int minSpaces, int maxSpaces, int minLF, boolean keepLineBreaks, int keepBlankLines) {
for (RuleCondition condition : myConditions) {
myRules.add(new SpacingRule(condition, minSpaces, maxSpaces, minLF, keepLineBreaks, keepBlankLines));
}
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;
private final List<SpacingRule> myRules = new ArrayList<SpacingRule>();
/**
* @param codeStyleSettings
* @deprecated Use other constructors!
*/
@Deprecated
public SpacingBuilder(CodeStyleSettings codeStyleSettings) {
// TODO: remove deprecated method (v.14)
myCodeStyleSettings = codeStyleSettings;
}
/**
* Creates SpacingBuilder with given code style settings and language whose settings must be used.
* @param codeStyleSettings The root code style settings.
* @param language The language to obtain settings for.
*/
public SpacingBuilder(@NotNull CodeStyleSettings codeStyleSettings, @NotNull Language language) {
myCodeStyleSettings = codeStyleSettings.getCommonSettings(language);
}
/**
* Creates SpacingBuilder with given language code style settings.
* @param languageCodeStyleSettings The language code style settings. Note that <code>getLanguage()</code> method must not
* return null!
*/
public SpacingBuilder(@NotNull CommonCodeStyleSettings languageCodeStyleSettings) {
assert languageCodeStyleSettings.getLanguage() != null : "Only language code style settings are accepted (getLanguage() != null)";
myCodeStyleSettings = languageCodeStyleSettings;
}
public RuleBuilder after(IElementType elementType) {
return new RuleBuilder(new RuleCondition(null, TokenSet.create(elementType), null));
}
public RuleBuilder after(TokenSet tokenSet) {
return new RuleBuilder(new RuleCondition(null, tokenSet, null));
}
public RuleBuilder afterInside(IElementType elementType, IElementType parentType) {
return new RuleBuilder(new RuleCondition(TokenSet.create(parentType), TokenSet.create(elementType), null));
}
public RuleBuilder afterInside(IElementType elementType, TokenSet parentType) {
return new RuleBuilder(new RuleCondition(parentType, TokenSet.create(elementType), null));
}
public RuleBuilder afterInside(TokenSet tokenSet, IElementType parentType) {
return new RuleBuilder(new RuleCondition(TokenSet.create(parentType), tokenSet, null));
}
public RuleBuilder before(IElementType elementType) {
return new RuleBuilder(new RuleCondition(null, null, TokenSet.create(elementType)));
}
public RuleBuilder beforeInside(TokenSet tokenSet, IElementType parentType) {
return new RuleBuilder(new RuleCondition(TokenSet.create(parentType), null, tokenSet));
}
public RuleBuilder beforeInside(IElementType elementType, IElementType parentType) {
return new RuleBuilder(new RuleCondition(TokenSet.create(parentType), null, TokenSet.create(elementType)));
}
public RuleBuilder beforeInside(IElementType elementType, TokenSet parentTypes) {
return new RuleBuilder(new RuleCondition(parentTypes, null, TokenSet.create(elementType)));
}
public RuleBuilder between(IElementType left, IElementType right) {
return new RuleBuilder(new RuleCondition(null, TokenSet.create(left), TokenSet.create(right)));
}
public RuleBuilder between(IElementType left, TokenSet rightSet) {
return new RuleBuilder(new RuleCondition(null, TokenSet.create(left), rightSet));
}
public RuleBuilder between(TokenSet leftSet, IElementType right) {
return new RuleBuilder(new RuleCondition(null, leftSet, TokenSet.create(right)));
}
public RuleBuilder between(TokenSet leftType, TokenSet rightType) {
return new RuleBuilder(new RuleCondition(null, leftType, rightType));
}
public RuleBuilder betweenInside(IElementType leftType, IElementType rightType, IElementType parentType) {
return new RuleBuilder(new RuleCondition(TokenSet.create(parentType), TokenSet.create(leftType), TokenSet.create(rightType)));
}
public RuleBuilder betweenInside(TokenSet leftSet, TokenSet rightSet, IElementType parentType) {
return new RuleBuilder(new RuleCondition(TokenSet.create(parentType), leftSet, rightSet));
}
public RuleBuilder withinPair(IElementType pairFirst, IElementType pairSecond) {
RuleCondition before = new RuleCondition(null, TokenSet.create(pairFirst), null);
RuleCondition after = new RuleCondition(null, null, TokenSet.create(pairSecond));
return new RuleBuilder(before, after);
}
public RuleBuilder withinPairInside(IElementType pairFirst, IElementType pairSecond, IElementType parent) {
TokenSet parentSet = TokenSet.create(parent);
RuleCondition before = new RuleCondition(parentSet, TokenSet.create(pairFirst), null);
RuleCondition after = new RuleCondition(parentSet, null, TokenSet.create(pairSecond));
return new RuleBuilder(before, after);
}
public RuleBuilder around(IElementType elementType) {
return around(TokenSet.create(elementType));
}
public RuleBuilder around(TokenSet tokenSet) {
RuleCondition before = new RuleCondition(null, null, tokenSet);
RuleCondition after = new RuleCondition(null, tokenSet, null);
return new RuleBuilder(before, after);
}
public RuleBuilder aroundInside(TokenSet tokenSet, TokenSet parent) {
RuleCondition before = new RuleCondition(parent, null, tokenSet);
RuleCondition after = new RuleCondition(parent, tokenSet, null);
return new RuleBuilder(before, after);
}
public RuleBuilder aroundInside(TokenSet tokenSet, IElementType parent) {
RuleCondition before = new RuleCondition(TokenSet.create(parent), null, tokenSet);
RuleCondition after = new RuleCondition(TokenSet.create(parent), tokenSet, null);
return new RuleBuilder(before, after);
}
public RuleBuilder aroundInside(IElementType token, IElementType parent) {
final TokenSet tokenSet = TokenSet.create(token);
RuleCondition before = new RuleCondition(TokenSet.create(parent), null, tokenSet);
RuleCondition after = new RuleCondition(TokenSet.create(parent), tokenSet, null);
return new RuleBuilder(before, after);
}
public RuleBuilder aroundInside(IElementType token, TokenSet parent) {
final TokenSet tokenSet = TokenSet.create(token);
RuleCondition before = new RuleCondition(parent, null, tokenSet);
RuleCondition after = new RuleCondition(parent, tokenSet, null);
return new RuleBuilder(before, after);
}
public SpacingBuilder append(SpacingBuilder builder) {
myRules.addAll(builder.myRules);
return this;
}
@Nullable
public Spacing getSpacing(Block parent, Block child1, Block child2) {
if (!(parent instanceof ASTBlock) || !(child1 instanceof ASTBlock) || !(child2 instanceof ASTBlock)) {
return null;
}
for (SpacingRule rule : myRules) {
if (rule.matches((ASTBlock)parent, (ASTBlock)child1, (ASTBlock) child2)) {
return rule.createSpacing((ASTBlock)parent, (ASTBlock)child1, (ASTBlock) child2);
}
}
return null;
}
}