/* * 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; } }