blob: 38040726ea909322d3e313b262484e3060d9c7cb [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.blocks;
import com.intellij.formatting.*;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.patterns.PlatformPatterns;
import com.intellij.psi.PsiComment;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.impl.source.tree.LeafPsiElement;
import com.intellij.psi.templateLanguages.OuterLanguageElement;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.tree.TokenSet;
import com.intellij.util.Function;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.GroovyLanguage;
import org.jetbrains.plugins.groovy.formatter.AlignmentProvider;
import org.jetbrains.plugins.groovy.formatter.FormattingContext;
import org.jetbrains.plugins.groovy.formatter.processors.GroovyIndentProcessor;
import org.jetbrains.plugins.groovy.formatter.processors.GroovyWrappingProcessor;
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.parser.GroovyParserDefinition;
import org.jetbrains.plugins.groovy.lang.psi.GrQualifiedReference;
import org.jetbrains.plugins.groovy.lang.psi.GroovyFile;
import org.jetbrains.plugins.groovy.lang.psi.GroovyFileBase;
import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.GrListOrMap;
import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.GrThrowsClause;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrLabeledStatement;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrStatement;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariable;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariableDeclaration;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrArgumentLabel;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrArgumentList;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrNamedArgument;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrCodeBlock;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.clauses.GrTraditionalForClause;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.*;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals.GrString;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.path.GrMethodCallExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.params.GrParameterList;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrExtendsClause;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinitionBody;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod;
import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
/**
* Utility class to generate myBlock hierarchy
*
* @author ilyas
*/
public class GroovyBlockGenerator {
private static final TokenSet NESTED = TokenSet.create(
GroovyElementTypes.REFERENCE_EXPRESSION,
GroovyElementTypes.PATH_INDEX_PROPERTY,
GroovyElementTypes.PATH_METHOD_CALL,
GroovyElementTypes.PATH_PROPERTY_REFERENCE
);
private static final Logger LOG = Logger.getInstance(GroovyBlockGenerator.class);
private final GroovyBlock myBlock;
private final ASTNode myNode;
private final AlignmentProvider myAlignmentProvider;
private final GroovyWrappingProcessor myWrappingProcessor;
private final FormattingContext myContext;
public GroovyBlockGenerator(GroovyBlock block) {
myBlock = block;
myNode = myBlock.getNode();
myContext = block.getContext();
myAlignmentProvider = myContext.getAlignmentProvider();
myWrappingProcessor = new GroovyWrappingProcessor(myBlock);
}
static List<ASTNode> getClosureBodyVisibleChildren(final ASTNode node) {
List<ASTNode> children = visibleChildren(node);
if (!children.isEmpty()) {
ASTNode first = children.get(0);
if (first.getElementType() == GroovyTokenTypes.mLCURLY) children.remove(0);
}
if (!children.isEmpty()) {
ASTNode last = children.get(children.size() - 1);
if (last.getElementType() == GroovyTokenTypes.mRCURLY) children.remove(children.size() - 1);
}
return children;
}
public List<Block> generateSubBlocks() {
//For binary expressions
PsiElement blockPsi = myNode.getPsi();
IElementType elementType = myNode.getElementType();
if (blockPsi instanceof GrBinaryExpression && !(blockPsi.getParent() instanceof GrBinaryExpression)) {
return generateForBinaryExpr();
}
//For multiline strings
if ((elementType == GroovyTokenTypes.mSTRING_LITERAL || elementType == GroovyTokenTypes.mGSTRING_LITERAL) && myBlock.getTextRange().equals(myNode.getTextRange())) {
String text = myNode.getText();
if (text.length() > 6) {
if (text.substring(0, 3).equals("'''") && text.substring(text.length() - 3).equals("'''") ||
text.substring(0, 3).equals("\"\"\"") & text.substring(text.length() - 3).equals("\"\"\"")) {
return generateForMultiLineString();
}
}
}
//for gstrings
if (elementType == GroovyElementTypes.GSTRING || elementType == GroovyElementTypes.REGEX || elementType ==
GroovyTokenTypes.mREGEX_LITERAL || elementType ==
GroovyTokenTypes.mDOLLAR_SLASH_REGEX_LITERAL) {
final FormattingContext context =
myNode.getPsi() instanceof GrString && ((GrString)myNode.getPsi()).isPlainString() ? myContext.createContext(true) : myContext;
final ArrayList<Block> subBlocks = new ArrayList<Block>();
ASTNode[] children = getGroovyChildren(myNode);
for (ASTNode childNode : children) {
if (childNode.getTextRange().getLength() > 0) {
subBlocks.add(new GroovyBlock(childNode, getIndent(childNode), Wrap.createWrap(WrapType.NONE, false), context));
}
}
return subBlocks;
}
// chained properties, calls, indexing, etc
if (NESTED.contains(elementType) && blockPsi.getParent() != null && !NESTED.contains(blockPsi.getParent().getNode().getElementType())) {
final List<Block> subBlocks = new ArrayList<Block>();
AlignmentProvider.Aligner dotsAligner = myContext.getSettings().ALIGN_MULTILINE_CHAINED_METHODS ? myAlignmentProvider.createAligner(false) : null;
final Wrap wrap = myWrappingProcessor.getChainedMethodCallWrap();
addNestedChildren(myNode.getPsi(), subBlocks, dotsAligner, true, wrap);
return subBlocks;
}
if (blockPsi instanceof GrListOrMap && ((GrListOrMap)blockPsi).isMap() && myContext.getGroovySettings().ALIGN_NAMED_ARGS_IN_MAP) {
AlignmentProvider.Aligner labels = myAlignmentProvider.createAligner(false);
AlignmentProvider.Aligner exprs = myAlignmentProvider.createAligner(true);
GrNamedArgument[] namedArgs = ((GrListOrMap)blockPsi).getNamedArguments();
for (GrNamedArgument arg : namedArgs) {
GrArgumentLabel label = arg.getLabel();
if (label != null) labels.append(label);
PsiElement colon = arg.getColon();
if (colon == null) colon = arg.getExpression();
if (colon != null) exprs.append(colon);
}
}
// For Parameter lists
if (isListLikeClause(blockPsi)) {
final ArrayList<Block> subBlocks = new ArrayList<Block>();
List<ASTNode> astNodes = visibleChildren(myNode);
if (mustAlign(blockPsi, astNodes)) {
final AlignmentProvider.Aligner aligner = myAlignmentProvider.createAligner(false);
for (ASTNode node : astNodes) {
if (!isKeyword(node)) aligner.append(node.getPsi());
}
}
for (ASTNode childNode : astNodes) {
subBlocks.add(new GroovyBlock(childNode, getIndent(childNode), getChildWrap(childNode), myContext));
}
return subBlocks;
}
boolean classLevel = blockPsi instanceof GrTypeDefinitionBody;
if (blockPsi instanceof GrClosableBlock &&
((GrClosableBlock)blockPsi).getArrow() != null &&
((GrClosableBlock)blockPsi).getParameters().length > 0 &&
!getClosureBodyVisibleChildren(myNode).isEmpty()) {
GrClosableBlock closableBlock = (GrClosableBlock)blockPsi;
ArrayList<Block> blocks = new ArrayList<Block>();
PsiElement lbrace = closableBlock.getLBrace();
if (lbrace != null) {
ASTNode node = lbrace.getNode();
blocks.add(new GroovyBlock(node, getIndent(node), Wrap.createWrap(WrapType.NONE, false), myContext));
}
/* {
Indent indent = GroovyIndentProcessor.getChildIndent(myBlock, parameterListNode);
GroovyBlock block = new GroovyBlock(parameterListNode, indent, myWrap, mySettings, myGroovySettings, myAlignmentProvider);
blocks.add(block);
}
{
PsiElement arrow = closableBlock.getArrow();
ASTNode node = arrow.getNode();
Indent indent = GroovyIndentProcessor.getChildIndent(myBlock, node);
GroovyBlock block = new GroovyBlock(node, indent, myWrap, mySettings, myGroovySettings, myAlignmentProvider);
blocks.add(block);
}*/
{
Indent indent = Indent.getNormalIndent();
ASTNode parameterListNode = closableBlock.getParameterList().getNode();
ClosureBodyBlock bodyBlock = new ClosureBodyBlock(parameterListNode, indent, Wrap.createWrap(WrapType.NONE, false), myContext);
blocks.add(bodyBlock);
}
PsiElement rbrace = closableBlock.getRBrace();
if (rbrace != null) {
ASTNode node = rbrace.getNode();
blocks.add(new GroovyBlock(node, getIndent(node), Wrap.createWrap(WrapType.NONE, false), myContext));
}
return blocks;
}
if (blockPsi instanceof GrCodeBlock || blockPsi instanceof GroovyFile || classLevel) {
return generateSubBlockForCodeBlocks(classLevel, visibleChildren(myNode), myContext.getGroovySettings().INDENT_LABEL_BLOCKS);
}
if (blockPsi instanceof GrMethod) {
final ArrayList<Block> subBlocks = new ArrayList<Block>();
for (ASTNode childNode : getGroovyChildren(myNode)) {
if (childNode.getElementType() == GroovyTokenTypes.mLPAREN) continue;
if (childNode.getElementType() == GroovyTokenTypes.mRPAREN) continue;
if (childNode.getElementType() == GroovyElementTypes.PARAMETERS_LIST) {
subBlocks.add(new ParameterListBlock(((GrMethod)blockPsi), Indent.getNoneIndent(), Wrap.createWrap(WrapType.NONE, false), myContext));
}
else if (canBeCorrectBlock(childNode)) {
subBlocks.add(new GroovyBlock(childNode, getIndent(childNode), getChildWrap(childNode), myContext));
}
}
return subBlocks;
}
else if (blockPsi instanceof GrTraditionalForClause) {
if (myContext.getSettings().ALIGN_MULTILINE_FOR) {
final GrTraditionalForClause clause = (GrTraditionalForClause)blockPsi;
final AlignmentProvider.Aligner parenthesesAligner = myAlignmentProvider.createAligner(false);
parenthesesAligner.append(clause.getInitialization());
parenthesesAligner.append(clause.getCondition());
parenthesesAligner.append(clause.getUpdate());
}
}
else if (blockPsi instanceof GrBinaryExpression) {
if (myContext.getSettings().ALIGN_MULTILINE_BINARY_OPERATION) {
final GrBinaryExpression binary = (GrBinaryExpression)blockPsi;
final GrExpression left = binary.getLeftOperand();
final GrExpression right = binary.getRightOperand();
if (left != null && right != null) {
myAlignmentProvider.addPair(left, right, false);
}
}
}
else if (blockPsi instanceof GrAssignmentExpression) {
if (myContext.getSettings().ALIGN_MULTILINE_ASSIGNMENT) {
final GrAssignmentExpression assignment = (GrAssignmentExpression)blockPsi;
final GrExpression lValue = assignment.getLValue();
final GrExpression rValue = assignment.getRValue();
if (lValue != null && rValue != null) {
myAlignmentProvider.addPair(lValue, rValue, false);
}
}
}
else if (blockPsi instanceof GrConditionalExpression) {
if (myContext.getSettings().ALIGN_MULTILINE_TERNARY_OPERATION) {
final GrConditionalExpression conditional = (GrConditionalExpression)blockPsi;
final AlignmentProvider.Aligner exprAligner = myAlignmentProvider.createAligner(false);
exprAligner.append(conditional.getCondition());
if (!(conditional instanceof GrElvisExpression)) {
exprAligner.append(conditional.getThenBranch());
}
exprAligner.append(conditional.getElseBranch());
ASTNode question = conditional.getNode().findChildByType(GroovyTokenTypes.mQUESTION);
ASTNode colon = conditional.getNode().findChildByType(GroovyTokenTypes.mCOLON);
if (question != null && colon != null) {
AlignmentProvider.Aligner questionColonAligner = myAlignmentProvider.createAligner(false);
questionColonAligner.append(question.getPsi());
questionColonAligner.append(colon.getPsi());
}
}
}
// For other cases
final ArrayList<Block> subBlocks = new ArrayList<Block>();
for (ASTNode childNode : visibleChildren(myNode)) {
subBlocks.add(new GroovyBlock(childNode, getIndent(childNode), getChildWrap(childNode), myContext));
}
return subBlocks;
}
private Wrap getChildWrap(ASTNode childNode) {
return myWrappingProcessor.getChildWrap(childNode);
}
@NotNull
public List<Block> generateSubBlockForCodeBlocks(boolean classLevel, final List<ASTNode> children, boolean indentLabelBlocks) {
final ArrayList<Block> subBlocks = new ArrayList<Block>();
if (indentLabelBlocks && isCodeBlock()) {
List<ASTNode> flattenChildren = flattenChildren(children);
calculateAlignments(flattenChildren, classLevel);
for (int i = 0; i < flattenChildren.size(); i++) {
ASTNode childNode = flattenChildren.get(i);
if (childNode.getElementType() == GroovyElementTypes.LABELED_STATEMENT) {
int start = i;
do {
i++;
}
while (i < flattenChildren.size() &&
flattenChildren.get(i).getElementType() != GroovyElementTypes.LABELED_STATEMENT &&
flattenChildren.get(i).getElementType() != GroovyTokenTypes.mRCURLY);
subBlocks.add(new GrLabelBlock(childNode, flattenChildren.subList(start + 1, i), classLevel, getIndent(childNode), getChildWrap(childNode), myContext));
i--;
}
else {
subBlocks.add(new GroovyBlock(childNode, getIndent(childNode), getChildWrap(childNode), myContext));
}
}
}
else {
calculateAlignments(children, classLevel);
for (ASTNode childNode : children) {
subBlocks.add(new GroovyBlock(childNode, getIndent(childNode), getChildWrap(childNode), myContext));
}
}
return subBlocks;
}
private boolean isCodeBlock() {
IElementType type = myNode.getElementType();
return type == GroovyElementTypes.OPEN_BLOCK ||
type == GroovyElementTypes.CLOSABLE_BLOCK ||
type == GroovyElementTypes.CONSTRUCTOR_BODY ||
type == GroovyParserDefinition.GROOVY_FILE;
}
private static List<ASTNode> flattenChildren(List<ASTNode> children) {
ArrayList<ASTNode> result = ContainerUtil.newArrayList();
for (ASTNode child : children) {
processNodeFlattening(result, child);
}
return result;
}
private static void processNodeFlattening(ArrayList<ASTNode> result, ASTNode child) {
result.add(child);
if (child.getElementType() == GroovyElementTypes.LABELED_STATEMENT) {
for (ASTNode node : visibleChildren(child)) {
processNodeFlattening(result, node);
}
}
}
private Indent getIndent(ASTNode childNode) {
return new GroovyIndentProcessor().getChildIndent(myBlock, childNode);
}
private void calculateAlignments(List<ASTNode> children, boolean classLevel) {
List<GrStatement> currentGroup = null;
boolean spock = true;
for (ASTNode child : children) {
PsiElement psi = child.getPsi();
if (psi instanceof GrLabeledStatement) {
alignGroup(currentGroup, spock, classLevel);
currentGroup = ContainerUtil.newArrayList();
spock = true;
}
else if (currentGroup != null && spock && isTablePart(psi)) {
currentGroup.add((GrStatement)psi);
}
else if (psi instanceof GrVariableDeclaration) {
GrVariable[] variables = ((GrVariableDeclaration)psi).getVariables();
if (variables.length > 0) {
if (!classLevel || currentGroup == null || fieldGroupEnded(psi) || spock) {
alignGroup(currentGroup, spock, classLevel);
currentGroup = ContainerUtil.newArrayList();
spock = false;
}
currentGroup.add((GrStatement)psi);
}
}
else {
if (shouldSkip(classLevel, psi)) continue;
alignGroup(currentGroup, spock, classLevel);
currentGroup = null;
}
}
if (currentGroup != null) {
alignGroup(currentGroup, spock, classLevel);
}
}
private boolean shouldSkip(boolean classLevel, PsiElement psi) {
if (psi instanceof PsiComment) {
PsiElement prev = psi.getPrevSibling();
if (prev != null && prev.getNode().getElementType() != GroovyTokenTypes.mNLS || classLevel && !fieldGroupEnded(psi)) {
return true;
}
}
if (psi.getParent() instanceof GrLabeledStatement && !(psi instanceof GrStatement)) {
return true;
}
return false;
}
private void alignGroup(@Nullable List<GrStatement> group, boolean spock, boolean classLevel) {
if (group == null) {
return;
}
if (spock) {
alignSpockTable(group);
} else {
alignVariableDeclarations(group, classLevel);
}
}
private void alignVariableDeclarations(List<GrStatement> group, boolean classLevel) {
AlignmentProvider.Aligner typeElement = myAlignmentProvider.createAligner(true);
AlignmentProvider.Aligner varName = myAlignmentProvider.createAligner(true);
AlignmentProvider.Aligner eq = myAlignmentProvider.createAligner(true);
for (GrStatement statement : group) {
GrVariableDeclaration varDeclaration = (GrVariableDeclaration) statement;
GrVariable[] variables = varDeclaration.getVariables();
for (GrVariable variable : variables) {
varName.append(variable.getNameIdentifierGroovy());
}
if (classLevel && myContext.getSettings().ALIGN_GROUP_FIELD_DECLARATIONS) {
typeElement.append(varDeclaration.getTypeElementGroovy());
ASTNode current_eq = variables[variables.length - 1].getNode().findChildByType(GroovyTokenTypes.mASSIGN);
if (current_eq != null) {
eq.append(current_eq.getPsi());
}
}
}
}
private void alignSpockTable(List<GrStatement> group) {
if (group.size() < 2) {
return;
}
GrStatement inner = group.get(0);
boolean embedded = inner != null && isTablePart(inner);
GrStatement first = embedded ? inner : group.get(1);
List<AlignmentProvider.Aligner> alignments = ContainerUtil
.map2List(getSpockTable(first), new Function<LeafPsiElement, AlignmentProvider.Aligner>() {
@Override
public AlignmentProvider.Aligner fun(LeafPsiElement leaf) {
return myAlignmentProvider.createAligner(leaf, true, Alignment.Anchor.RIGHT);
}
});
int second = embedded ? 1 : 2;
for (int i = second; i < group.size(); i++) {
List<LeafPsiElement> table = getSpockTable(group.get(i));
for (int j = 0; j < Math.min(table.size(), alignments.size()); j++) {
alignments.get(j).append(table.get(j));
}
}
}
private boolean fieldGroupEnded(PsiElement psi) {
if (!myContext.getSettings().ALIGN_GROUP_FIELD_DECLARATIONS) return true;
PsiElement prevSibling = psi.getPrevSibling();
return prevSibling != null && StringUtil.countChars(prevSibling.getText(), '\n') >= myContext.getSettings().KEEP_BLANK_LINES_IN_DECLARATIONS;
}
private static List<LeafPsiElement> getSpockTable(GrStatement statement) {
LinkedList<LeafPsiElement> result = new LinkedList<LeafPsiElement>();
while (isTablePart(statement)) {
result.addFirst((LeafPsiElement)((GrBinaryExpression)statement).getOperationToken());
statement = ((GrBinaryExpression)statement).getLeftOperand();
}
return result;
}
private static boolean isTablePart(PsiElement psi) {
return psi instanceof GrBinaryExpression && (GroovyTokenTypes.mBOR == ((GrBinaryExpression)psi).getOperationTokenType() || GroovyTokenTypes.mLOR == ((GrBinaryExpression)psi).getOperationTokenType());
}
public static List<ASTNode> visibleChildren(ASTNode node) {
ArrayList<ASTNode> list = new ArrayList<ASTNode>();
for (ASTNode astNode : getGroovyChildren(node)) {
if (canBeCorrectBlock(astNode)) {
list.add(astNode);
}
}
return list;
}
private boolean mustAlign(PsiElement blockPsi, List<ASTNode> children) {
// We don't want to align single call argument if it's a closure. The reason is that it looks better to have call like
//
// foo({
// println 'xxx'
// })
//
// than
//
// foo({
// println 'xxx'
// })
if (blockPsi instanceof GrArgumentList && myContext.getSettings().ALIGN_MULTILINE_PARAMETERS_IN_CALLS) {
return !(children.size() == 3 &&
children.get(0).getElementType() == GroovyTokenTypes.mLPAREN &&
(children.get(1).getElementType() == GroovyElementTypes.CLOSABLE_BLOCK || children.get(1).getElementType() ==
GroovyElementTypes.LIST_OR_MAP) &&
children.get(2).getElementType() == GroovyTokenTypes.mRPAREN);
}
if (blockPsi instanceof GrAssignmentExpression && ((GrAssignmentExpression)blockPsi).getRValue() instanceof GrAssignmentExpression) {
return myContext.getSettings().ALIGN_MULTILINE_ASSIGNMENT;
}
return blockPsi instanceof GrParameterList && myContext.getSettings().ALIGN_MULTILINE_PARAMETERS ||
blockPsi instanceof GrExtendsClause && myContext.getSettings().ALIGN_MULTILINE_EXTENDS_LIST ||
blockPsi instanceof GrThrowsClause && myContext.getSettings().ALIGN_MULTILINE_THROWS_LIST ||
blockPsi instanceof GrListOrMap && myContext.getGroovySettings().ALIGN_MULTILINE_LIST_OR_MAP;
}
private static boolean isListLikeClause(PsiElement blockPsi) {
return blockPsi instanceof GrParameterList ||
blockPsi instanceof GrArgumentList ||
blockPsi instanceof GrAssignmentExpression ||
blockPsi instanceof GrExtendsClause ||
blockPsi instanceof GrThrowsClause ||
blockPsi instanceof GrListOrMap;
}
private static boolean isKeyword(ASTNode node) {
if (node == null) return false;
return TokenSets.KEYWORDS.contains(node.getElementType()) ||
TokenSets.BRACES.contains(node.getElementType()) && !PlatformPatterns.psiElement().withText(")").withParent(GrArgumentList.class).afterLeaf(",").accepts(node.getPsi());
}
private List<Block> generateForMultiLineString() {
final ArrayList<Block> subBlocks = new ArrayList<Block>();
final int start = myNode.getTextRange().getStartOffset();
final int end = myNode.getTextRange().getEndOffset();
subBlocks.add(new GroovyBlockWithRange(myNode, Indent.getNoneIndent(), new TextRange(start, start + 3), Wrap.createWrap(WrapType.NONE, false), myContext));
subBlocks.add(new GroovyBlockWithRange(myNode, Indent.getAbsoluteNoneIndent(), new TextRange(start + 3, end - 3), Wrap.createWrap(WrapType.NONE, false), myContext));
subBlocks.add(new GroovyBlockWithRange(myNode, Indent.getAbsoluteNoneIndent(), new TextRange(end - 3, end), Wrap.createWrap(WrapType.NONE, false), myContext));
return subBlocks;
}
/**
* @param node Tree node
* @return true, if the current node can be myBlock node, else otherwise
*/
private static boolean canBeCorrectBlock(final ASTNode node) {
return !node.getText().trim().isEmpty();
}
private static ASTNode[] getGroovyChildren(final ASTNode node) {
PsiElement psi = node.getPsi();
if (psi instanceof OuterLanguageElement) {
TextRange range = node.getTextRange();
ArrayList<ASTNode> childList = new ArrayList<ASTNode>();
PsiFile groovyFile = psi.getContainingFile().getViewProvider().getPsi(GroovyLanguage.INSTANCE);
if (groovyFile instanceof GroovyFileBase) {
addChildNodes(groovyFile, childList, range);
}
return childList.toArray(new ASTNode[childList.size()]);
}
return node.getChildren(null);
}
private static void addChildNodes(PsiElement elem, ArrayList<ASTNode> childNodes, TextRange range) {
ASTNode node = elem.getNode();
if (range.contains(elem.getTextRange()) && node != null) {
childNodes.add(node);
} else {
for (PsiElement child : elem.getChildren()) {
addChildNodes(child, childNodes, range);
}
}
}
/**
* Generates blocks for binary expressions
*
* @return
*/
private List<Block> generateForBinaryExpr() {
final ArrayList<Block> subBlocks = new ArrayList<Block>();
AlignmentProvider.Aligner
alignment = myContext.getSettings().ALIGN_MULTILINE_BINARY_OPERATION ? myAlignmentProvider.createAligner(false) : null;
GrBinaryExpression binary = (GrBinaryExpression)myNode.getPsi();
LOG.assertTrue(binary != null);
addBinaryChildrenRecursively(binary, subBlocks, Indent.getContinuationWithoutFirstIndent(), alignment);
return subBlocks;
}
/**
* Adds all children of specified element to given list
*
* @param elem
* @param list
* @param indent
* @param aligner
*/
private void addBinaryChildrenRecursively(PsiElement elem, List<Block> list, Indent indent, @Nullable AlignmentProvider.Aligner aligner) {
if (elem == null) return;
// For binary expressions
if ((elem instanceof GrBinaryExpression)) {
GrBinaryExpression myExpr = ((GrBinaryExpression) elem);
if (myExpr.getLeftOperand() instanceof GrBinaryExpression) {
addBinaryChildrenRecursively(myExpr.getLeftOperand(), list, Indent.getContinuationWithoutFirstIndent(), aligner);
}
PsiElement op = ((GrBinaryExpression)elem).getOperationToken();
for (ASTNode childNode : visibleChildren(elem.getNode())) {
PsiElement psi = childNode.getPsi();
if (!(psi instanceof GrBinaryExpression)) {
if (op != psi && aligner != null) {
aligner.append(psi);
}
list.add(new GroovyBlock(childNode, indent, getChildWrap(childNode), myContext));
}
}
if (myExpr.getRightOperand() instanceof GrBinaryExpression) {
addBinaryChildrenRecursively(myExpr.getRightOperand(), list, Indent.getContinuationWithoutFirstIndent(), aligner
);
}
}
}
private void addNestedChildren(final PsiElement elem,
List<Block> list,
@Nullable AlignmentProvider.Aligner aligner,
final boolean topLevel,
Wrap wrap) {
final List<ASTNode> children = visibleChildren(elem.getNode());
if (elem instanceof GrMethodCallExpression) {
GrExpression invokedExpression = ((GrMethodCallExpression)elem).getInvokedExpression();
if (invokedExpression instanceof GrQualifiedReference) {
final PsiElement nameElement = ((GrQualifiedReference)invokedExpression).getReferenceNameElement();
if (nameElement != null) {
List<ASTNode> grandChildren = visibleChildren(invokedExpression.getNode());
int i = 0;
while (i < grandChildren.size() && nameElement != grandChildren.get(i).getPsi()) i++;
if (i > 0) {
processNestedChildrenPrefix(list, aligner, false, grandChildren, i, wrap);
}
if (i < grandChildren.size()) {
LOG.assertTrue(nameElement == grandChildren.get(i).getPsi());
list.add(new MethodCallWithoutQualifierBlock(nameElement, wrap, topLevel, children, elem, myContext));
}
return;
}
}
}
processNestedChildrenPrefix(list, aligner, topLevel, children, children.size(), wrap);
}
private static boolean isAfterMultiLineClosure(ASTNode dot) {
PsiElement dotPsi = dot.getPsi();
PsiElement prev = PsiUtil.skipWhitespaces(dotPsi.getPrevSibling(), false);
if (prev != null) {
if (prev instanceof GrMethodCall) {
final PsiElement last = prev.getLastChild();
if (last instanceof GrClosableBlock) {
return last.getText().contains("\n");
}
}
}
return false;
}
private void processNestedChildrenPrefix(List<Block> list,
@Nullable AlignmentProvider.Aligner aligner,
boolean topLevel,
List<ASTNode> children,
int limit,
Wrap wrap) {
ASTNode fst = children.get(0);
LOG.assertTrue(limit > 0);
if (NESTED.contains(fst.getElementType())) {
addNestedChildren(fst.getPsi(), list, aligner, false, wrap);
}
else {
Indent indent = Indent.getContinuationWithoutFirstIndent();
list.add(new GroovyBlock(fst, indent, getChildWrap(fst), myContext));
}
addNestedChildrenSuffix(list, aligner, topLevel, children, limit);
}
void addNestedChildrenSuffix(List<Block> list,
@Nullable AlignmentProvider.Aligner aligner,
boolean topLevel,
List<ASTNode> children,
int limit) {
for (int i = 1; i < limit; i++) {
ASTNode childNode = children.get(i);
if (canBeCorrectBlock(childNode)) {
IElementType type = childNode.getElementType();
Indent indent = topLevel || NESTED.contains(type) || type == GroovyTokenTypes.mIDENT || TokenSets.DOTS.contains(type) && !isAfterMultiLineClosure(
childNode) ?
Indent.getContinuationWithoutFirstIndent() :
Indent.getNoneIndent();
if (aligner != null && TokenSets.DOTS.contains(type)) {
aligner.append(childNode.getPsi());
}
list.add(new GroovyBlock(childNode, indent, getChildWrap(childNode), myContext));
}
}
}
}