Fixed WEB-21215 Inconsistent indentation when object is first parameter of function...
[idea/community.git] / platform / lang-impl / src / com / intellij / psi / impl / source / codeStyle / lineIndent / JavaLikeLangLineIndentProvider.java
1 /*
2  * Copyright 2000-2016 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 com.intellij.psi.impl.source.codeStyle.lineIndent;
17
18 import com.intellij.lang.Language;
19 import com.intellij.openapi.editor.Editor;
20 import com.intellij.openapi.editor.ex.EditorEx;
21 import com.intellij.openapi.project.Project;
22 import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
23 import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
24 import com.intellij.psi.codeStyle.lineIndent.LineIndentProvider;
25 import com.intellij.psi.impl.source.codeStyle.SemanticEditorPosition;
26 import com.intellij.psi.impl.source.codeStyle.SemanticEditorPosition.SyntaxElement;
27 import com.intellij.psi.impl.source.codeStyle.lineIndent.IndentCalculator.BaseLineOffsetCalculator;
28 import com.intellij.psi.tree.IElementType;
29 import org.jetbrains.annotations.NotNull;
30 import org.jetbrains.annotations.Nullable;
31
32 import static com.intellij.formatting.Indent.Type;
33 import static com.intellij.formatting.Indent.Type.*;
34 import static com.intellij.psi.impl.source.codeStyle.lineIndent.JavaLikeLangLineIndentProvider.JavaLikeElement.*;
35
36 /**
37  * A base class Java-like language line indent provider. If JavaLikeLangLineIndentProvider is unable to calculate
38  * the indentation, it forwards the request to FormatterBasedLineIndentProvider.
39  */
40 public abstract class JavaLikeLangLineIndentProvider implements LineIndentProvider{
41   
42   public enum JavaLikeElement implements SyntaxElement {
43     Whitespace,
44     Semicolon,
45     BlockOpeningBrace,
46     BlockClosingBrace,
47     ArrayOpeningBracket,
48     ArrayClosingBracket,
49     RightParenthesis,
50     LeftParenthesis,
51     Colon,
52     SwitchCase,
53     SwitchDefault,
54     ElseKeyword,
55     IfKeyword,
56     ForKeyword,
57     DoKeyword,
58     BlockComment,
59     DocBlockStart,
60     DocBlockEnd,
61     LineComment,
62     Comma,
63     LanguageStartDelimiter
64   }
65   
66   
67   @Nullable
68   @Override
69   public String getLineIndent(@NotNull Project project, @NotNull Editor editor, @Nullable Language language, int offset) {
70     if (offset > 0) {
71       IndentCalculator indentCalculator = getIndent(project, editor, language, offset - 1);
72       if (indentCalculator != null) {
73         return indentCalculator.getIndentString(language, getPosition(editor, offset - 1));
74       }
75     }
76     else {
77       return "";
78     }
79     return null;
80   }
81   
82   @Nullable
83   protected IndentCalculator getIndent(@NotNull Project project, @NotNull Editor editor, @Nullable Language language, int offset) {
84     IndentCalculatorFactory myFactory = new IndentCalculatorFactory(project, editor);
85     if (getPosition(editor, offset).matchesRule(
86       position -> position.isAt(Whitespace) &&
87                   position.isAtMultiline())) {
88       if (getPosition(editor, offset).before().isAt(Comma)) {
89         SemanticEditorPosition position = getPosition(editor,offset);
90         if (position.hasEmptyLineAfter(offset) &&
91             !position.after().isAtAnyOf(ArrayClosingBracket, BlockOpeningBrace, BlockClosingBrace, RightParenthesis) &&
92             !position.isAtEnd()) {
93             return myFactory.createIndentCalculator(NONE, IndentCalculator.LINE_AFTER);
94         }
95       }
96       else if (getPosition(editor, offset + 1).matchesRule(
97         position -> position.isAt(BlockClosingBrace) && !position.after().afterOptional(Whitespace).isAt(Comma))) {
98         return myFactory.createIndentCalculator(
99           NONE,
100           position -> {
101             position.findLeftParenthesisBackwardsSkippingNested(BlockOpeningBrace, BlockClosingBrace);
102             if (!position.isAtEnd()) {
103               return getBlockStatementStartOffset(position);
104             }
105             return -1;
106           });
107       }
108       else if (getPosition(editor, offset).matchesRule(
109         position -> position
110           .before()
111           .beforeOptional(Whitespace)
112           .isAt(BlockClosingBrace))) {
113         return myFactory.createIndentCalculator(getBlockIndentType(project, language), IndentCalculator.LINE_BEFORE);
114       }
115       else if (getPosition(editor, offset).matchesRule(position -> position.before().isAt(Semicolon))) {
116         SemanticEditorPosition beforeSemicolon = getPosition(editor, offset).before().beforeOptional(Semicolon);
117         if (beforeSemicolon.isAt(BlockClosingBrace)) {
118           beforeSemicolon.beforeParentheses(BlockOpeningBrace, BlockClosingBrace);
119         }
120         int statementStart = getStatementStartOffset(beforeSemicolon);
121         SemanticEditorPosition atStatementStart = getPosition(editor, statementStart);
122         if (!atStatementStart.isAfterOnSameLine(ForKeyword)) {
123           return myFactory.createIndentCalculator(NONE, position -> statementStart);
124         }
125       }
126       else if (getPosition(editor, offset).matchesRule(
127         position -> position.before().isAt(ArrayOpeningBracket)
128       )) {
129         return myFactory.createIndentCalculator(getIndentTypeInBrackets(), IndentCalculator.LINE_BEFORE);
130       }
131       else if (getPosition(editor, offset).matchesRule(
132         position -> position.before().isAt(LeftParenthesis)
133       )) {
134         return myFactory.createIndentCalculator(CONTINUATION, IndentCalculator.LINE_BEFORE);
135       }
136       else if (getPosition(editor, offset).matchesRule(
137         position -> position.before().isAt(BlockOpeningBrace) && !position.before().beforeOptional(Whitespace).isAt(LeftParenthesis)
138       )) {
139         SemanticEditorPosition position = getPosition(editor, offset).before();
140         return myFactory.createIndentCalculator(getIndentTypeInBlock(project, language, position), this::getBlockStatementStartOffset);
141       }
142       else if (getPosition(editor, offset).matchesRule(
143         position -> position.before().isAt(Colon) && position.isAfterOnSameLine(SwitchCase, SwitchDefault)
144       ) || getPosition(editor, offset).matchesRule(
145         position -> position.before().isAtAnyOf(ElseKeyword, DoKeyword) 
146       )) {
147         return myFactory.createIndentCalculator(NORMAL, IndentCalculator.LINE_BEFORE);
148       }
149       else if (getPosition(editor, offset).matchesRule(
150         position -> position.before().isAt(BlockComment) && position.before().isAt(Whitespace) && position.isAtMultiline()
151       )) {
152         return myFactory.createIndentCalculator(NONE, position -> position.findStartOf(BlockComment));
153       }
154       else if (getPosition(editor, offset).matchesRule(
155         position -> position.before().isAt(DocBlockEnd)
156       )) {
157         return myFactory.createIndentCalculator(NONE, position -> position.findStartOf(DocBlockStart));
158       }
159       else {
160         SemanticEditorPosition position = getPosition(editor, offset);
161         position = position.before().beforeOptionalMix(LineComment, BlockComment, Whitespace);
162         if (position.isAt(RightParenthesis)) {
163           int offsetAfterParen = position.getStartOffset() + 1;
164           position.beforeParentheses(LeftParenthesis, RightParenthesis);
165           if (!position.isAtEnd()) {
166             position.beforeOptional(Whitespace);
167             if (position.isAt(IfKeyword) || position.isAt(ForKeyword)) {
168               SyntaxElement element = position.getCurrElement();
169               assert element != null;
170               final int controlKeywordOffset = position.getStartOffset();
171               Type indentType = getPosition(editor, offsetAfterParen).afterOptional(Whitespace).isAt(BlockOpeningBrace) ? NONE : NORMAL;
172               return myFactory.createIndentCalculator(indentType, baseLineOffset -> controlKeywordOffset);
173             }
174           }
175         }
176       }
177     }
178     //return myFactory.createIndentCalculator(NONE, IndentCalculator.LINE_BEFORE); /* TO CHECK UNCOVERED CASES */
179     return null;
180   }
181
182
183   private int getBlockStatementStartOffset(@NotNull SemanticEditorPosition position) {
184     position.before().beforeOptional(BlockOpeningBrace);
185     if (position.isAt(Whitespace)) {
186       if (position.isAtMultiline()) return position.after().getStartOffset();
187       position.before();
188     }
189     return getStatementStartOffset(position);
190   }
191   
192   private int getStatementStartOffset(@NotNull SemanticEditorPosition position) {
193     Language currLanguage = position.getLanguage();
194     while (!position.isAtEnd()) {
195       if (currLanguage == Language.ANY || currLanguage == null) currLanguage = position.getLanguage();
196       if (position.isAt(Colon)) {
197         SemanticEditorPosition afterColon = getPosition(position.getEditor(), position.getStartOffset()).after().afterOptional(Whitespace);
198         if (position.isAfterOnSameLine(SwitchCase, SwitchDefault)) {
199           return afterColon.getStartOffset();
200         }
201       }
202       else if (position.isAt(RightParenthesis)) {
203         position.beforeParentheses(LeftParenthesis, RightParenthesis);
204       }
205       else if (position.isAt(BlockClosingBrace)) {
206         position.beforeParentheses(BlockOpeningBrace, BlockClosingBrace);
207       }
208       else if (position.isAt(ArrayClosingBracket)) {
209         position.beforeParentheses(ArrayOpeningBracket, ArrayClosingBracket);
210       }
211       else if (position.isAtAnyOf(Semicolon,
212                                   BlockOpeningBrace, 
213                                   BlockComment, 
214                                   DocBlockEnd, 
215                                   LeftParenthesis,
216                                   LanguageStartDelimiter) ||
217                (position.getLanguage() != Language.ANY) && !position.isAtLanguage(currLanguage)) {
218         SemanticEditorPosition statementStart = getPosition(position.getEditor(), position.getStartOffset());
219         statementStart.after().afterOptionalMix(Whitespace, LineComment);
220         if (!statementStart.isAtEnd()) {
221           return statementStart.getStartOffset();
222         }
223       }
224       position.before();
225     }
226     return 0;
227   }
228   
229
230   protected SemanticEditorPosition getPosition(@NotNull Editor editor, int offset) {
231     return new SemanticEditorPosition((EditorEx)editor, offset) {
232       @Override
233       public SyntaxElement map(@NotNull IElementType elementType) {
234         return mapType(elementType);
235       }
236     };
237   }
238   
239   @Nullable
240   protected abstract SyntaxElement mapType(@NotNull IElementType tokenType);
241   
242   
243   @Nullable
244   protected Type getIndentTypeInBlock(@NotNull Project project,
245                                            @Nullable Language language,
246                                            @NotNull SemanticEditorPosition blockStartPosition) {
247     if (language != null) {
248       CommonCodeStyleSettings settings = CodeStyleSettingsManager.getSettings(project).getCommonSettings(language);
249       if (settings.BRACE_STYLE == CommonCodeStyleSettings.NEXT_LINE_SHIFTED) {
250         return settings.METHOD_BRACE_STYLE == CommonCodeStyleSettings.NEXT_LINE_SHIFTED ? NONE : null;
251       }
252     }
253     return NORMAL;
254   }
255   
256   @Nullable
257   private static Type getBlockIndentType(@NotNull Project project, @Nullable Language language) {
258     if (language != null) {
259       CommonCodeStyleSettings settings = CodeStyleSettingsManager.getSettings(project).getCommonSettings(language);
260       if (settings.BRACE_STYLE == CommonCodeStyleSettings.NEXT_LINE || settings.BRACE_STYLE == CommonCodeStyleSettings.END_OF_LINE) {
261         return NONE;
262       }
263     }
264     return null;
265   }
266   
267   
268   
269   public static class IndentCalculatorFactory {
270     private Project myProject;
271     private Editor myEditor;
272
273     public IndentCalculatorFactory(Project project, Editor editor) {
274       myProject = project;
275       myEditor = editor;
276     }
277     
278     @Nullable
279     public IndentCalculator createIndentCalculator(@Nullable Type indentType, @Nullable BaseLineOffsetCalculator baseLineOffsetCalculator) {
280       return indentType != null ?
281              new IndentCalculator(myProject, myEditor,
282                                   baseLineOffsetCalculator != null ? baseLineOffsetCalculator : IndentCalculator.LINE_BEFORE, indentType) 
283                                 : null;
284     }
285   }
286
287   @Override
288   public final boolean isSuitableFor(@Nullable Language language) {
289     return language != null && isSuitableForLanguage(language);
290   }
291   
292   public abstract boolean isSuitableForLanguage(@NotNull Language language);
293
294   protected Type getIndentTypeInBrackets() {
295     return CONTINUATION;
296   }
297 }