blob: 8235a99fcee31815a4c73f236b2f7c33ca75092e [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.plugins.groovy.formatter.processors;
import com.intellij.formatting.Spacing;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiErrorElement;
import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
import com.intellij.psi.tree.IElementType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.groovy.codeStyle.GroovyCodeStyleSettings;
import org.jetbrains.plugins.groovy.formatter.FormattingContext;
import org.jetbrains.plugins.groovy.formatter.blocks.ClosureBodyBlock;
import org.jetbrains.plugins.groovy.formatter.blocks.GrLabelBlock;
import org.jetbrains.plugins.groovy.formatter.blocks.GroovyBlock;
import org.jetbrains.plugins.groovy.formatter.blocks.MethodCallWithoutQualifierBlock;
import org.jetbrains.plugins.groovy.formatter.models.spacing.SpacingTokens;
import org.jetbrains.plugins.groovy.lang.groovydoc.lexer.GroovyDocTokenTypes;
import org.jetbrains.plugins.groovy.lang.groovydoc.parser.GroovyDocElementTypes;
import org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes;
import org.jetbrains.plugins.groovy.lang.lexer.TokenSets;
import org.jetbrains.plugins.groovy.lang.parser.GroovyElementTypes;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrStatement;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrConditionalExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrNewExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrUnaryExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals.GrStringInjection;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrAnonymousClassDefinition;
import org.jetbrains.plugins.groovy.lang.psi.api.types.GrCodeReferenceElement;
import org.jetbrains.plugins.groovy.lang.psi.api.types.GrTypeArgumentList;
import org.jetbrains.plugins.groovy.lang.psi.api.util.GrStatementOwner;
/**
* @author ilyas
*/
public abstract class GroovySpacingProcessorBasic {
private static final Spacing NO_SPACING_WITH_NEWLINE = Spacing.createSpacing(0, 0, 0, true, 1);
private static final Spacing NO_SPACING = Spacing.createSpacing(0, 0, 0, false, 0);
private static final Spacing COMMON_SPACING = Spacing.createSpacing(1, 1, 0, true, 100);
private static final Spacing COMMON_SPACING_WITH_NL = Spacing.createSpacing(1, 1, 1, true, 100);
private static final Spacing LAZY_SPACING = Spacing.createSpacing(0, 239, 0, true, 100);
public static Spacing getSpacing(GroovyBlock child1,
GroovyBlock child2,
FormattingContext context) {
ASTNode leftNode = child1.getNode();
ASTNode rightNode = child2.getNode();
final PsiElement left = leftNode.getPsi();
final PsiElement right = rightNode.getPsi();
IElementType leftType = leftNode.getElementType();
IElementType rightType = rightNode.getElementType();
final CommonCodeStyleSettings settings = context.getSettings();
final GroovyCodeStyleSettings groovySettings = context.getGroovySettings();
if (!(mirrorsAst(child1) && mirrorsAst(child2))) {
return NO_SPACING;
}
if (child2 instanceof ClosureBodyBlock) {
return settings.SPACE_WITHIN_BRACES ? COMMON_SPACING : NO_SPACING_WITH_NEWLINE;
}
if (child1 instanceof ClosureBodyBlock) {
return createDependentSpacingForClosure(settings, groovySettings, (GrClosableBlock)left.getParent(), false);
}
if (leftType == GroovyDocElementTypes.GROOVY_DOC_COMMENT) {
return COMMON_SPACING_WITH_NL;
}
if (right instanceof GrTypeArgumentList) {
return NO_SPACING_WITH_NEWLINE;
}
/********** punctuation marks ************/
if (GroovyTokenTypes.mCOMMA == leftType) {
return settings.SPACE_AFTER_COMMA ? COMMON_SPACING : NO_SPACING_WITH_NEWLINE;
}
if (GroovyTokenTypes.mCOMMA == rightType) {
return settings.SPACE_BEFORE_COMMA ? COMMON_SPACING : NO_SPACING_WITH_NEWLINE;
}
if (GroovyTokenTypes.mSEMI == leftType) {
return settings.SPACE_AFTER_SEMICOLON ? COMMON_SPACING : NO_SPACING_WITH_NEWLINE;
}
if (GroovyTokenTypes.mSEMI == rightType) {
return settings.SPACE_BEFORE_SEMICOLON ? COMMON_SPACING : NO_SPACING_WITH_NEWLINE;
}
// For dots, commas etc.
if ((TokenSets.DOTS.contains(rightType)) ||
(GroovyTokenTypes.mCOLON.equals(rightType) && !(right.getParent() instanceof GrConditionalExpression))) {
return NO_SPACING_WITH_NEWLINE;
}
if (TokenSets.DOTS.contains(leftType)) {
return NO_SPACING_WITH_NEWLINE;
}
//todo:check it for multiple assignments
if ((GroovyElementTypes.VARIABLE_DEFINITION.equals(leftType) || GroovyElementTypes.VARIABLE_DEFINITION.equals(rightType)) &&
!(leftNode.getTreeNext() instanceof PsiErrorElement)) {
return Spacing.createSpacing(0, 0, 1, false, 100);
}
// For regexes
if (leftNode.getTreeParent().getElementType() == GroovyTokenTypes.mREGEX_LITERAL ||
leftNode.getTreeParent().getElementType() == GroovyTokenTypes.mDOLLAR_SLASH_REGEX_LITERAL) {
return NO_SPACING;
}
/********** exclusions ************/
// For << and >> ...
if ((GroovyTokenTypes.mLT.equals(leftType) && GroovyTokenTypes.mLT.equals(rightType)) ||
(GroovyTokenTypes.mGT.equals(leftType) && GroovyTokenTypes.mGT.equals(rightType))) {
return NO_SPACING_WITH_NEWLINE;
}
// Unary and postfix expressions
if (SpacingTokens.PREFIXES.contains(leftType) ||
SpacingTokens.POSTFIXES.contains(rightType) ||
(SpacingTokens.PREFIXES_OPTIONAL.contains(leftType) && left.getParent() instanceof GrUnaryExpression)) {
return NO_SPACING_WITH_NEWLINE;
}
if (SpacingTokens.RANGES.contains(leftType) || SpacingTokens.RANGES.contains(rightType)) {
return NO_SPACING_WITH_NEWLINE;
}
if (GroovyDocTokenTypes.mGDOC_ASTERISKS == leftType && GroovyDocTokenTypes.mGDOC_COMMENT_DATA == rightType) {
String text = rightNode.getText();
if (!text.isEmpty() && !StringUtil.startsWithChar(text, ' ')) {
return COMMON_SPACING;
}
return NO_SPACING;
}
if (leftType == GroovyDocTokenTypes.mGDOC_TAG_VALUE_TOKEN && rightType == GroovyDocTokenTypes.mGDOC_COMMENT_DATA) {
return LAZY_SPACING;
}
if (left instanceof GrStatement &&
right instanceof GrStatement &&
left.getParent() instanceof GrStatementOwner &&
right.getParent() instanceof GrStatementOwner) {
return COMMON_SPACING_WITH_NL;
}
if (rightType == GroovyDocTokenTypes.mGDOC_INLINE_TAG_END ||
leftType == GroovyDocTokenTypes.mGDOC_INLINE_TAG_START ||
rightType == GroovyDocTokenTypes.mGDOC_INLINE_TAG_START ||
leftType == GroovyDocTokenTypes.mGDOC_INLINE_TAG_END) {
return NO_SPACING;
}
if ((leftType == GroovyDocElementTypes.GDOC_INLINED_TAG && rightType == GroovyDocTokenTypes.mGDOC_COMMENT_DATA)
|| (leftType == GroovyDocTokenTypes.mGDOC_COMMENT_DATA && rightType == GroovyDocElementTypes.GDOC_INLINED_TAG))
{
// Keep formatting between groovy doc text and groovy doc reference tag as is.
return NO_SPACING;
}
if (leftType == GroovyElementTypes.CLASS_TYPE_ELEMENT && rightType == GroovyTokenTypes.mTRIPLE_DOT) {
return NO_SPACING;
}
// diamonds
if (rightType == GroovyTokenTypes.mLT || rightType == GroovyTokenTypes.mGT) {
if (right.getParent() instanceof GrCodeReferenceElement) {
PsiElement p = right.getParent().getParent();
if (p instanceof GrNewExpression || p instanceof GrAnonymousClassDefinition) {
return NO_SPACING;
}
}
}
return COMMON_SPACING;
}
@NotNull
static Spacing createDependentSpacingForClosure(@NotNull CommonCodeStyleSettings settings,
@NotNull GroovyCodeStyleSettings groovySettings,
@NotNull GrClosableBlock closure,
final boolean forArrow) {
boolean spaceWithinBraces = closure.getParent() instanceof GrStringInjection
? groovySettings.SPACE_WITHIN_GSTRING_INJECTION_BRACES
: settings.SPACE_WITHIN_BRACES;
GrStatement[] statements = closure.getStatements();
if (statements.length > 0) {
final PsiElement startElem = forArrow ? statements[0] : closure;
int start = startElem.getTextRange().getStartOffset();
int end = statements[statements.length - 1].getTextRange().getEndOffset();
TextRange range = new TextRange(start, end);
int minSpaces = spaceWithinBraces || forArrow ? 1 : 0;
int maxSpaces = spaceWithinBraces || forArrow ? 1 : 0;
return Spacing.createDependentLFSpacing(minSpaces, maxSpaces, range, settings.KEEP_LINE_BREAKS, settings.KEEP_BLANK_LINES_IN_CODE);
}
return spaceWithinBraces || forArrow ? COMMON_SPACING : NO_SPACING_WITH_NEWLINE;
}
private static boolean mirrorsAst(GroovyBlock block) {
return block.getNode().getTextRange().equals(block.getTextRange()) ||
block instanceof MethodCallWithoutQualifierBlock ||
block instanceof ClosureBodyBlock ||
block instanceof GrLabelBlock;
}
}