Merge remote branch 'origin/master'
[idea/community.git] / plugins / groovy / src / org / jetbrains / plugins / groovy / formatter / GroovyBlockGenerator.java
1 /*
2  * Copyright 2000-2011 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package org.jetbrains.plugins.groovy.formatter;
18
19 import com.intellij.formatting.Alignment;
20 import com.intellij.formatting.Block;
21 import com.intellij.formatting.Indent;
22 import com.intellij.formatting.Wrap;
23 import com.intellij.lang.ASTNode;
24 import com.intellij.openapi.diagnostic.Logger;
25 import com.intellij.openapi.util.TextRange;
26 import com.intellij.openapi.util.text.StringUtil;
27 import com.intellij.patterns.PlatformPatterns;
28 import com.intellij.psi.PsiComment;
29 import com.intellij.psi.PsiElement;
30 import com.intellij.psi.PsiFile;
31 import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
32 import com.intellij.psi.impl.source.tree.LeafPsiElement;
33 import com.intellij.psi.templateLanguages.OuterLanguageElement;
34 import com.intellij.psi.tree.IElementType;
35 import com.intellij.psi.tree.TokenSet;
36 import org.jetbrains.annotations.NotNull;
37 import org.jetbrains.annotations.Nullable;
38 import org.jetbrains.plugins.groovy.GroovyFileType;
39 import org.jetbrains.plugins.groovy.formatter.processors.GroovyIndentProcessor;
40 import org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes;
41 import org.jetbrains.plugins.groovy.lang.lexer.TokenSets;
42 import org.jetbrains.plugins.groovy.lang.parser.GroovyElementTypes;
43 import org.jetbrains.plugins.groovy.lang.psi.GrQualifiedReference;
44 import org.jetbrains.plugins.groovy.lang.psi.GroovyFile;
45 import org.jetbrains.plugins.groovy.lang.psi.GroovyFileBase;
46 import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.GrThrowsClause;
47 import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrLabeledStatement;
48 import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrStatement;
49 import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariable;
50 import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariableDeclaration;
51 import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrArgumentList;
52 import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrCodeBlock;
53 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrAssignmentExpression;
54 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrBinaryExpression;
55 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrConditionalExpression;
56 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression;
57 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.path.GrMethodCallExpression;
58 import org.jetbrains.plugins.groovy.lang.psi.api.statements.params.GrParameterList;
59 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrExtendsClause;
60 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinitionBody;
61
62 import java.util.ArrayList;
63 import java.util.LinkedList;
64 import java.util.List;
65
66 /**
67  * Utility class to generate myBlock hierarchy
68  *
69  * @author ilyas
70  */
71 public class GroovyBlockGenerator implements GroovyElementTypes {
72
73   private static final TokenSet NESTED = TokenSet.create(REFERENCE_EXPRESSION,
74       PATH_INDEX_PROPERTY,
75       PATH_METHOD_CALL,
76       PATH_PROPERTY_REFERENCE);
77
78   private static final Logger LOG = Logger.getInstance(GroovyBlockGenerator.class);
79
80   private final GroovyBlock myBlock;
81   private final ASTNode myNode;
82   private final Alignment myAlignment;
83   private final Wrap myWrap;
84   private final CommonCodeStyleSettings mySettings;
85   private final AlignmentProvider myAlignmentProvider;
86   private final GroovyCodeStyleSettings myGroovySettings;
87
88   public GroovyBlockGenerator(GroovyBlock block) {
89     myBlock = block;
90     myNode = myBlock.getNode();
91     myAlignment = myBlock.getAlignment();
92     myWrap = myBlock.getWrap();
93     mySettings = myBlock.getSettings();
94     myAlignmentProvider = myBlock.getAlignmentProvider();
95     myGroovySettings = myBlock.getGroovySettings();
96   }
97
98
99   public List<Block> generateSubBlocks() {
100
101     //For binary expressions
102     PsiElement blockPsi = myNode.getPsi();
103
104     if (blockPsi instanceof GrBinaryExpression && !(blockPsi.getParent() instanceof GrBinaryExpression)) {
105       return generateForBinaryExpr();
106     }
107
108     //For multiline strings
109     if ((myNode.getElementType() == mSTRING_LITERAL || myNode.getElementType() == mGSTRING_LITERAL) &&
110         myBlock.getTextRange().equals(myNode.getTextRange())) {
111       String text = myNode.getText();
112       if (text.length() > 6) {
113         if (text.substring(0, 3).equals("'''") && text.substring(text.length() - 3).equals("'''") ||
114             text.substring(0, 3).equals("\"\"\"") & text.substring(text.length() - 3).equals("\"\"\"")) {
115           return generateForMultiLineString();
116         }
117       }
118     }
119
120     if (myNode.getElementType() == mGSTRING_BEGIN &&
121         myBlock.getTextRange().equals(myNode.getTextRange())) {
122       String text = myNode.getText();
123       if (text.length() > 3) {
124         if (text.substring(0, 3).equals("\"\"\"")) {
125           return generateForMultiLineGStringBegin();
126         }
127       }
128
129     }
130
131     //for gstrings
132     if (myNode.getElementType() == GSTRING) {
133       final ArrayList<Block> subBlocks = new ArrayList<Block>();
134       ASTNode[] children = getGroovyChildren(myNode);
135       for (ASTNode childNode : children) {
136         if (childNode.getTextRange().getLength() > 0) {
137           final Indent indent = GroovyIndentProcessor.getChildIndent(myBlock, childNode);
138           if (myAlignment != null) {
139             myAlignmentProvider.addPair(myNode, childNode);
140           }
141           subBlocks.add(new GroovyBlock(childNode, indent, myWrap, mySettings, myGroovySettings, myAlignmentProvider));
142         }
143       }
144       return subBlocks;
145     }
146
147     // chained properties, calls, indexing, etc
148     if (NESTED.contains(myNode.getElementType()) && blockPsi.getParent() != null && !NESTED.contains(blockPsi.getParent().getNode().getElementType())) {
149       final List<Block> subBlocks = new ArrayList<Block>();
150       AlignmentProvider.Aligner dotsAligner = mySettings.ALIGN_MULTILINE_CHAINED_METHODS ? myAlignmentProvider.createAligner() : null;
151       addNestedChildren(myNode.getPsi(), subBlocks, dotsAligner, true);
152       return subBlocks;
153     }
154
155     // For Parameter lists
156     if (isListLikeClause(blockPsi)) {
157       final ArrayList<Block> subBlocks = new ArrayList<Block>();
158       List<ASTNode> astNodes = visibleChildren(myNode);
159
160       if (mustAlign(blockPsi, astNodes)) {
161         final AlignmentProvider.Aligner aligner = myAlignmentProvider.createAligner();
162         for (ASTNode node : astNodes) {
163           if (!isKeyword(node)) aligner.append(node.getPsi());
164         }
165       }
166       for (ASTNode childNode : astNodes) {
167         final Indent indent = GroovyIndentProcessor.getChildIndent(myBlock, childNode);
168         subBlocks.add(new GroovyBlock(childNode, indent, myWrap, mySettings, myGroovySettings, myAlignmentProvider));
169       }
170       return subBlocks;
171     }
172
173     boolean classLevel = blockPsi instanceof GrTypeDefinitionBody;
174     if (blockPsi instanceof GrCodeBlock || blockPsi instanceof GroovyFile || classLevel) {
175       List<ASTNode> children = visibleChildren(myNode);
176       calculateAlignments(children, classLevel);
177       final ArrayList<Block> subBlocks = new ArrayList<Block>();
178
179       if (classLevel && myAlignment != null) {
180         final AlignmentProvider.Aligner aligner = myAlignmentProvider.createAligner();
181         for (ASTNode child : children) {
182           aligner.append(child.getPsi());
183         }
184       }
185       for (ASTNode childNode : children) {
186         final Indent indent = GroovyIndentProcessor.getChildIndent(myBlock, childNode);
187         subBlocks.add(new GroovyBlock(childNode, indent, myWrap, mySettings, myGroovySettings, myAlignmentProvider));
188       }
189       return subBlocks;
190     }
191
192     // For other cases
193     final ArrayList<Block> subBlocks = new ArrayList<Block>();
194     for (ASTNode childNode : visibleChildren(myNode)) {
195       final Indent indent = GroovyIndentProcessor.getChildIndent(myBlock, childNode);
196       subBlocks.add(new GroovyBlock(childNode, indent, myWrap, mySettings, myGroovySettings, myAlignmentProvider));
197     }
198     return subBlocks;
199   }
200   
201
202   private void calculateAlignments(List<ASTNode> children, boolean classLevel) {
203     List<AlignmentProvider.Aligner> currentGroup = null;
204     for (ASTNode child : children) {
205       PsiElement psi = child.getPsi();
206       if (psi instanceof GrLabeledStatement) {
207         List<LeafPsiElement> table = getSpockTable(((GrLabeledStatement)psi).getStatement());
208         if (table.isEmpty()) {
209           currentGroup = null;
210         }
211         else {
212           currentGroup = new ArrayList<AlignmentProvider.Aligner>();
213           for (LeafPsiElement expression : table) {
214             currentGroup.add(myAlignmentProvider.createAligner(expression));
215           }
216         }
217       }
218       else if (currentGroup != null && isTablePart(psi)) {
219         List<LeafPsiElement> table = getSpockTable((GrStatement)psi);
220         for (int i = 0; i < Math.min(table.size(), currentGroup.size()); i++) {
221           currentGroup.get(i).append(table.get(i));
222         }
223       }
224       else if (psi instanceof GrVariableDeclaration) {
225         final GrVariableDeclaration varDeclaration = (GrVariableDeclaration)psi;
226         GrVariable[] variables = varDeclaration.getVariables();
227         if (variables.length > 0) {
228           if (!classLevel || currentGroup == null || fieldGroupEnded(psi)) {
229             currentGroup = new ArrayList<AlignmentProvider.Aligner>();
230             currentGroup.add(myAlignmentProvider.createAligner());
231             currentGroup.add(myAlignmentProvider.createAligner());
232             currentGroup.add(myAlignmentProvider.createAligner());
233           }
234
235           AlignmentProvider.Aligner varName = currentGroup.get(1);
236           for (GrVariable variable : variables) {
237             varName.append(variable.getNameIdentifierGroovy());
238           }
239
240           if (classLevel && mySettings.ALIGN_GROUP_FIELD_DECLARATIONS) {
241             final AlignmentProvider.Aligner typeElement = currentGroup.get(0);
242             typeElement.append(varDeclaration.getTypeElementGroovy());
243
244             ASTNode current_eq = variables[variables.length - 1].getNode().findChildByType(GroovyTokenTypes.mASSIGN);
245             final AlignmentProvider.Aligner eq = currentGroup.get(2);
246             if (current_eq != null) {
247               eq.append(current_eq.getPsi());
248             }
249           }
250         }
251       }
252       else {
253         if (psi instanceof PsiComment) {
254           PsiElement prev = psi.getPrevSibling();
255           if (prev != null && prev.getNode().getElementType() != mNLS || classLevel && !fieldGroupEnded(psi)) {
256             continue;
257           }
258         }
259         currentGroup = null;
260       }
261     }
262   }
263
264   private boolean fieldGroupEnded(PsiElement psi) {
265     if (!mySettings.ALIGN_GROUP_FIELD_DECLARATIONS) return true;
266     PsiElement prevSibling = psi.getPrevSibling();
267     return prevSibling != null && StringUtil.countChars(prevSibling.getText(), '\n') >= mySettings.KEEP_BLANK_LINES_IN_DECLARATIONS;
268   }
269
270   private static List<LeafPsiElement> getSpockTable(GrStatement statement) {
271     LinkedList<LeafPsiElement> result = new LinkedList<LeafPsiElement>();
272     while (isTablePart(statement)) {
273       result.addFirst((LeafPsiElement)((GrBinaryExpression)statement).getOperationToken());
274       statement = ((GrBinaryExpression)statement).getLeftOperand();
275     }
276     return result;
277   }
278
279   private static boolean isTablePart(PsiElement psi) {
280     return psi instanceof GrBinaryExpression && mBOR == ((GrBinaryExpression)psi).getOperationTokenType();
281   }
282
283   private static List<ASTNode> visibleChildren(ASTNode node) {
284     ArrayList<ASTNode> list = new ArrayList<ASTNode>();
285     for (ASTNode astNode : getGroovyChildren(node)) {
286       if (canBeCorrectBlock(astNode)) {
287         list.add(astNode);
288       }
289     }
290     return list;
291   }
292
293   private boolean mustAlign(PsiElement blockPsi, List<ASTNode> children) {
294     // 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
295     //
296     // foo({
297     //   println 'xxx'
298     // })
299     //
300     // than
301     //
302     // foo({
303     //       println 'xxx'
304     //     })
305     if (blockPsi instanceof GrArgumentList && mySettings.ALIGN_MULTILINE_PARAMETERS_IN_CALLS) {
306       return children.size() != 3 || children.get(0).getElementType() != mLPAREN
307              || children.get(1).getElementType() != CLOSABLE_BLOCK || children.get(2).getElementType() != mRPAREN;
308     }
309
310     if (blockPsi instanceof GrAssignmentExpression && ((GrAssignmentExpression)blockPsi).getRValue() instanceof GrAssignmentExpression) {
311       return mySettings.ALIGN_MULTILINE_ASSIGNMENT;
312     }
313
314     return blockPsi instanceof GrParameterList && mySettings.ALIGN_MULTILINE_PARAMETERS ||
315         blockPsi instanceof GrExtendsClause && mySettings.ALIGN_MULTILINE_EXTENDS_LIST ||
316         blockPsi instanceof GrThrowsClause && mySettings.ALIGN_MULTILINE_THROWS_LIST ||
317         blockPsi instanceof GrConditionalExpression && mySettings.ALIGN_MULTILINE_TERNARY_OPERATION;
318   }
319
320   private static boolean isListLikeClause(PsiElement blockPsi) {
321     return blockPsi instanceof GrParameterList ||
322         blockPsi instanceof GrArgumentList ||
323         blockPsi instanceof GrAssignmentExpression ||
324         blockPsi instanceof GrConditionalExpression ||
325         blockPsi instanceof GrExtendsClause ||
326         blockPsi instanceof GrThrowsClause;
327   }
328
329   private static boolean isKeyword(ASTNode node) {
330     if (node == null) return false;
331     
332     return TokenSets.KEYWORDS.contains(node.getElementType()) ||
333         TokenSets.BRACES.contains(node.getElementType()) && !PlatformPatterns.psiElement().withText(")").withParent(GrArgumentList.class).afterLeaf(",").accepts(node.getPsi());
334   }
335
336
337   private List<Block> generateForMultiLineString() {
338     final ArrayList<Block> subBlocks = new ArrayList<Block>();
339     final int start = myNode.getTextRange().getStartOffset();
340     final int end = myNode.getTextRange().getEndOffset();
341
342     subBlocks.add(new GroovyBlock(myNode, Indent.getNoneIndent(), myWrap, mySettings, myGroovySettings, myAlignmentProvider) {
343       @NotNull
344       public TextRange getTextRange() {
345         return new TextRange(start, start + 3);
346       }
347     });
348     subBlocks.add(new GroovyBlock(myNode, Indent.getAbsoluteNoneIndent(), myWrap, mySettings, myGroovySettings, myAlignmentProvider) {
349       @NotNull
350       public TextRange getTextRange() {
351         return new TextRange(start + 3, end - 3);
352       }
353     });
354     subBlocks.add(new GroovyBlock(myNode, Indent.getAbsoluteNoneIndent(), myWrap, mySettings, myGroovySettings, myAlignmentProvider) {
355       @NotNull
356       public TextRange getTextRange() {
357         return new TextRange(end - 3, end);
358       }
359     });
360     return subBlocks;
361   }
362
363   private List<Block> generateForMultiLineGStringBegin() {
364     final ArrayList<Block> subBlocks = new ArrayList<Block>();
365     final int start = myNode.getTextRange().getStartOffset();
366     final int end = myNode.getTextRange().getEndOffset();
367
368     subBlocks.add(new GroovyBlock(myNode, Indent.getNoneIndent(), myWrap, mySettings, myGroovySettings, myAlignmentProvider) {
369       @NotNull
370       public TextRange getTextRange() {
371         return new TextRange(start, start + 3);
372       }
373     });
374     subBlocks.add(new GroovyBlock(myNode, Indent.getAbsoluteNoneIndent(), myWrap, mySettings, myGroovySettings, myAlignmentProvider) {
375       @NotNull
376       public TextRange getTextRange() {
377         return new TextRange(start + 3, end);
378       }
379     });
380     return subBlocks;
381   }
382
383   /**
384    * @param node Tree node
385    * @return true, if the current node can be myBlock node, else otherwise
386    */
387   private static boolean canBeCorrectBlock(final ASTNode node) {
388     return (node.getText().trim().length() > 0);
389   }
390
391
392   private static ASTNode[] getGroovyChildren(final ASTNode node) {
393     PsiElement psi = node.getPsi();
394     if (psi instanceof OuterLanguageElement) {
395       TextRange range = node.getTextRange();
396       ArrayList<ASTNode> childList = new ArrayList<ASTNode>();
397       PsiFile groovyFile = psi.getContainingFile().getViewProvider().getPsi(GroovyFileType.GROOVY_LANGUAGE);
398       if (groovyFile instanceof GroovyFileBase) {
399         addChildNodes(groovyFile, childList, range);
400       }
401       return childList.toArray(new ASTNode[childList.size()]);
402     }
403     return node.getChildren(null);
404   }
405
406   private static void addChildNodes(PsiElement elem, ArrayList<ASTNode> childNodes, TextRange range) {
407     ASTNode node = elem.getNode();
408     if (range.contains(elem.getTextRange()) && node != null) {
409       childNodes.add(node);
410     } else {
411       for (PsiElement child : elem.getChildren()) {
412         addChildNodes(child, childNodes, range);
413       }
414     }
415
416   }
417
418   /**
419    * Generates blocks for binary expressions
420    *
421    * @return
422    */
423   private List<Block> generateForBinaryExpr() {
424     final ArrayList<Block> subBlocks = new ArrayList<Block>();
425     AlignmentProvider.Aligner
426       alignment = mySettings.ALIGN_MULTILINE_BINARY_OPERATION ? myAlignmentProvider.createAligner() : null;
427
428     GrBinaryExpression binary = (GrBinaryExpression)myNode.getPsi();
429     LOG.assertTrue(binary != null);
430     addBinaryChildrenRecursively(binary, subBlocks, Indent.getContinuationWithoutFirstIndent(), alignment);
431     return subBlocks;
432   }
433
434   /**
435    * Adds all children of specified element to given list
436    *
437    * @param elem
438    * @param list
439    * @param indent
440    * @param aligner
441    */
442   private void addBinaryChildrenRecursively(PsiElement elem, List<Block> list, Indent indent, @Nullable AlignmentProvider.Aligner aligner) {
443     if (elem == null) return;
444     // For binary expressions
445     if ((elem instanceof GrBinaryExpression)) {
446       GrBinaryExpression myExpr = ((GrBinaryExpression) elem);
447       if (myExpr.getLeftOperand() instanceof GrBinaryExpression) {
448         addBinaryChildrenRecursively(myExpr.getLeftOperand(), list, Indent.getContinuationWithoutFirstIndent(), aligner);
449       }
450       PsiElement op = ((GrBinaryExpression)elem).getOperationToken();
451       for (ASTNode childNode : visibleChildren(elem.getNode())) {
452         PsiElement psi = childNode.getPsi();
453         if (!(psi instanceof GrBinaryExpression)) {
454           if (op != psi && aligner != null) {
455             aligner.append(psi);
456           }
457           list.add(new GroovyBlock(childNode, indent, myWrap, mySettings, myGroovySettings, myAlignmentProvider));
458         }
459       }
460       if (myExpr.getRightOperand() instanceof GrBinaryExpression) {
461         addBinaryChildrenRecursively(myExpr.getRightOperand(), list, Indent.getContinuationWithoutFirstIndent(), aligner
462         );
463       }
464     }
465   }
466
467
468   private void addNestedChildren(final PsiElement elem, List<Block> list, @Nullable AlignmentProvider.Aligner aligner, final boolean topLevel) {
469     final List<ASTNode> children = visibleChildren(elem.getNode());
470     if (elem instanceof GrMethodCallExpression) {
471       GrExpression invokedExpression = ((GrMethodCallExpression)elem).getInvokedExpression();
472       if (invokedExpression instanceof GrQualifiedReference) {
473         final PsiElement nameElement = ((GrQualifiedReference)invokedExpression).getReferenceNameElement();
474         if (nameElement != null) {
475           List<ASTNode> grandChildren = visibleChildren(invokedExpression.getNode());
476           int i = 0;
477           while (i < grandChildren.size() && nameElement != grandChildren.get(i).getPsi()) { i++; }
478           if (i > 0) {
479             processNestedChildrenPrefix(list, aligner, false, grandChildren, i);
480           }
481           if (i < grandChildren.size()) {
482             LOG.assertTrue(nameElement == grandChildren.get(i).getPsi());
483             list.add(new MethodCallWithoutQualifierBlock(nameElement, myWrap, mySettings, myGroovySettings, topLevel, children, elem, myAlignmentProvider));
484           }
485           return;
486         }
487       }
488
489     }
490
491
492     processNestedChildrenPrefix(list, aligner, topLevel, children, children.size());
493   }
494
495   private void processNestedChildrenPrefix(List<Block> list, @Nullable AlignmentProvider.Aligner aligner, boolean topLevel, List<ASTNode> children, int limit) {
496     ASTNode fst = children.get(0);
497     LOG.assertTrue(limit > 0);
498     if (NESTED.contains(fst.getElementType())) {
499       addNestedChildren(fst.getPsi(), list, aligner, false);
500     }
501     else {
502       Indent indent = Indent.getContinuationWithoutFirstIndent();
503       list.add(new GroovyBlock(fst, indent, myWrap, mySettings, myGroovySettings, myAlignmentProvider));
504     }
505     addNestedChildrenSuffix(list, aligner, topLevel, children, limit);
506   }
507
508   void addNestedChildrenSuffix(List<Block> list, @Nullable AlignmentProvider.Aligner aligner, boolean topLevel, List<ASTNode> children, int limit) {
509     for (int i = 1; i < limit; i++) {
510       ASTNode childNode = children.get(i);
511       if (canBeCorrectBlock(childNode)) {
512         IElementType type = childNode.getElementType();
513         Indent indent = topLevel || NESTED.contains(type) || type == mIDENT || TokenSets.DOTS.contains(type) ?
514                         Indent.getContinuationWithoutFirstIndent() :
515                         Indent.getNoneIndent();
516
517
518         if (aligner != null && TokenSets.DOTS.contains(type)) {
519           aligner.append(childNode.getPsi());
520         }
521
522         list.add(new GroovyBlock(childNode, indent, myWrap, mySettings, myGroovySettings, myAlignmentProvider));
523       }
524     }
525   }
526 }