Merge remote branch 'origin/master'
[idea/community.git] / plugins / groovy / src / org / jetbrains / plugins / groovy / formatter / GeeseUtil.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 package org.jetbrains.plugins.groovy.formatter;
17
18 import com.intellij.lang.ASTNode;
19 import com.intellij.openapi.diagnostic.Logger;
20 import com.intellij.psi.PsiElement;
21 import com.intellij.psi.TokenType;
22 import com.intellij.psi.tree.IElementType;
23 import com.intellij.psi.util.PsiTreeUtil;
24 import org.jetbrains.annotations.Nullable;
25 import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock;
26 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression;
27 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrMethodCall;
28 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression;
29 import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil;
30
31 import static org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes.mRCURLY;
32 import static org.jetbrains.plugins.groovy.lang.lexer.TokenSets.WHITE_SPACES_SET;
33 import static org.jetbrains.plugins.groovy.lang.parser.GroovyElementTypes.CLOSABLE_BLOCK;
34
35 /**
36  * @author Max Medvedev
37  */
38 public class GeeseUtil {
39   private static final Logger LOG = Logger.getInstance(GeeseUtil.class);
40
41   private GeeseUtil() {
42   }
43
44   @Nullable
45   public static ASTNode getClosureRBraceAtTheEnd(ASTNode node) {
46     IElementType elementType = node.getElementType();
47     if (elementType == CLOSABLE_BLOCK) {
48       PsiElement rBrace = ((GrClosableBlock)node.getPsi()).getRBrace();
49       return rBrace != null ? rBrace.getNode() : null;
50     }
51
52     ASTNode lastChild = node.getLastChildNode();
53     while (lastChild != null && WHITE_SPACES_SET.contains(lastChild.getElementType())) {
54       lastChild = lastChild.getTreePrev();
55     }
56     if (lastChild == null) return null;
57
58     return getClosureRBraceAtTheEnd(lastChild);
59   }
60
61   public static boolean isClosureRBrace(PsiElement e) {
62     return e != null && e.getNode().getElementType() == mRCURLY &&
63            e.getParent() instanceof GrClosableBlock &&
64            ((GrClosableBlock)e.getParent()).getRBrace() == e;
65   }
66
67   @Nullable
68   public static PsiElement getNextNonWhitespaceToken(PsiElement e) {
69     PsiElement next = PsiTreeUtil.nextLeaf(e);
70     while (next != null && next.getNode().getElementType() == TokenType.WHITE_SPACE) next = PsiTreeUtil.nextLeaf(next);
71     return next;
72   }
73
74   @Nullable
75   public static PsiElement getPreviousNonWhitespaceToken(PsiElement e) {
76     PsiElement next = PsiTreeUtil.prevLeaf(e);
77     while (next != null && next.getNode().getElementType() == TokenType.WHITE_SPACE) next = PsiTreeUtil.prevLeaf(next);
78     return next;
79   }
80
81   static void calculateRBraceAlignment(PsiElement rBrace, AlignmentProvider alignments) {
82     int leadingBraceCount = 0;
83     PsiElement next;
84
85     if (!isClosureContainLF(rBrace)) return;
86
87     for (next = getPreviousNonWhitespaceToken(rBrace);
88          isClosureRBrace(next) && isClosureContainLF(next);
89          next = getPreviousNonWhitespaceToken(next)) {
90       leadingBraceCount++;
91     }
92
93     PsiElement cur = rBrace;
94     for (next = getNextNonWhitespaceToken(cur); isClosureRBrace(next); next = getNextNonWhitespaceToken(cur)) {
95       cur = next;
96     }
97
98     for (; leadingBraceCount > 0; leadingBraceCount--) {
99       cur = getPreviousNonWhitespaceToken(cur);
100     }
101
102     PsiElement parent = cur.getParent();
103     LOG.assertTrue(parent instanceof GrClosableBlock);
104
105     //search for start of the line
106     cur = parent;
107     if (cur.getParent() instanceof GrMethodCall) {
108       GrMethodCall call = (GrMethodCall)cur.getParent();
109       GrExpression invoked = call.getInvokedExpression();
110       if (invoked instanceof GrReferenceExpression && ((GrReferenceExpression)invoked).getReferenceNameElement() != null) {
111         cur = ((GrReferenceExpression)invoked).getReferenceNameElement();
112       }
113       else {
114         cur = call;
115       }
116     }
117     cur = PsiTreeUtil.getDeepestFirst(cur);
118     while (!PsiUtil.isNewLine(next = PsiTreeUtil.prevLeaf(cur, true))) {
119       if (next == null) break;
120       if (next.getNode().getElementType() == TokenType.WHITE_SPACE && PsiTreeUtil.prevLeaf(next) == null) {
121         break; //if cur is first word in the text, whitespace could be before it
122       }
123       cur = next;
124     }
125
126     int startOffset = cur.getTextRange().getStartOffset();
127     int endOffset = rBrace.getTextRange().getStartOffset();
128
129     if (rBrace.getContainingFile().getText().substring(startOffset, endOffset).indexOf('\n') < 0) {
130       return;
131     }
132
133     while (true) {
134       final PsiElement p = cur.getParent();
135       if (p != null && p.getTextOffset() == cur.getTextOffset()) {
136         cur = p;
137       }
138       else {
139         break;
140       }
141     }
142     alignments.addPair(rBrace, cur);
143   }
144
145   public static boolean isClosureContainLF(PsiElement rBrace) {
146     PsiElement parent = rBrace.getParent();
147     return parent.getText().indexOf('\n') >= 0;
148   }
149 }