7ab0acddbcafe41d9df06b543bcac69f4b7eb1b6
[idea/community.git] / java / java-impl / src / com / intellij / codeInsight / editorActions / smartEnter / JavaSmartEnterProcessor.java
1 /*
2  * Copyright 2000-2009 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.codeInsight.editorActions.smartEnter;
17
18 import com.intellij.codeInsight.CodeInsightUtil;
19 import com.intellij.codeInsight.lookup.LookupManager;
20 import com.intellij.featureStatistics.FeatureUsageTracker;
21 import com.intellij.openapi.actionSystem.IdeActions;
22 import com.intellij.openapi.diagnostic.Logger;
23 import com.intellij.openapi.editor.Document;
24 import com.intellij.openapi.editor.Editor;
25 import com.intellij.openapi.editor.RangeMarker;
26 import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
27 import com.intellij.openapi.editor.actionSystem.EditorActionManager;
28 import com.intellij.openapi.editor.ex.EditorEx;
29 import com.intellij.openapi.project.Project;
30 import com.intellij.openapi.util.Key;
31 import com.intellij.openapi.util.TextRange;
32 import com.intellij.psi.*;
33 import com.intellij.psi.codeStyle.CodeStyleManager;
34 import com.intellij.psi.codeStyle.CodeStyleSettings;
35 import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
36 import com.intellij.psi.util.PsiTreeUtil;
37 import com.intellij.util.IncorrectOperationException;
38 import com.intellij.util.text.CharArrayUtil;
39 import org.jetbrains.annotations.NotNull;
40 import org.jetbrains.annotations.Nullable;
41
42 import java.util.ArrayList;
43 import java.util.List;
44
45 /**
46  * @author spleaner
47  */
48 public class JavaSmartEnterProcessor extends SmartEnterProcessor {
49   private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.editorActions.smartEnter.JavaSmartEnterProcessor");
50
51   private static final Fixer[] ourFixers;
52   private static final EnterProcessor[] ourEnterProcessors = {
53     new CommentBreakerEnterProcessor(),
54     new AfterSemicolonEnterProcessor(),
55     new LeaveCodeBlockEnterProcessor(),
56     new PlainEnterProcessor()
57   };
58   private static final EnterProcessor[] ourAfterCompletionEnterProcessors = {
59     new AfterSemicolonEnterProcessor(),
60     new EnterProcessor() {
61       @Override
62       public boolean doEnter(Editor editor, PsiElement psiElement, boolean isModified) {
63         return PlainEnterProcessor.expandCodeBlock(editor, psiElement);
64       }
65     }
66   };
67
68   static {
69     final List<Fixer> fixers = new ArrayList<Fixer>();
70     fixers.add(new LiteralFixer());
71     fixers.add(new MethodCallFixer());
72     fixers.add(new IfConditionFixer());
73     fixers.add(new ForStatementFixer());
74     fixers.add(new WhileConditionFixer());
75     fixers.add(new CatchDeclarationFixer());
76     fixers.add(new SwitchExpressionFixer());
77     fixers.add(new CaseColonFixer());
78     fixers.add(new DoWhileConditionFixer());
79     fixers.add(new BlockBraceFixer());
80     fixers.add(new MissingIfBranchesFixer());
81     fixers.add(new MissingWhileBodyFixer());
82     fixers.add(new MissingTryBodyFixer());
83     fixers.add(new MissingSwitchBodyFixer());
84     fixers.add(new MissingCatchBodyFixer());
85     fixers.add(new MissingSynchronizedBodyFixer());
86     fixers.add(new MissingForBodyFixer());
87     fixers.add(new MissingForeachBodyFixer());
88     fixers.add(new ParameterListFixer());
89     fixers.add(new MissingMethodBodyFixer());
90     fixers.add(new MissingClassBodyFixer());
91     fixers.add(new MissingReturnExpressionFixer());
92     fixers.add(new MissingThrowExpressionFixer());
93     fixers.add(new ParenthesizedFixer());
94     fixers.add(new SemicolonFixer());
95     fixers.add(new MissingArrayInitializerBraceFixer());
96     fixers.add(new MissingArrayConstructorBracketFixer());
97     fixers.add(new EnumFieldFixer());
98     ourFixers = fixers.toArray(new Fixer[fixers.size()]);
99   }
100
101   private int myFirstErrorOffset = Integer.MAX_VALUE;
102   private boolean mySkipEnter;
103   private static final int MAX_ATTEMPTS = 20;
104   private static final Key<Long> SMART_ENTER_TIMESTAMP = Key.create("smartEnterOriginalTimestamp");
105
106   public static class TooManyAttemptsException extends Exception {}
107   
108   private final JavadocFixer myJavadocFixer = new JavadocFixer();
109
110   @Override
111   public boolean process(@NotNull final Project project, @NotNull final Editor editor, @NotNull final PsiFile psiFile) {
112     FeatureUsageTracker.getInstance().triggerFeatureUsed("codeassists.complete.statement");
113
114     return invokeProcessor(editor, psiFile, false);
115   }
116
117   @Override
118   public boolean processAfterCompletion(@NotNull Editor editor, @NotNull PsiFile psiFile) {
119     return invokeProcessor(editor, psiFile, true);
120   }
121
122   private boolean invokeProcessor(Editor editor, PsiFile psiFile, boolean afterCompletion) {
123     final Document document = editor.getDocument();
124     final CharSequence textForRollback = document.getImmutableCharSequence();
125     try {
126       editor.putUserData(SMART_ENTER_TIMESTAMP, editor.getDocument().getModificationStamp());
127       myFirstErrorOffset = Integer.MAX_VALUE;
128       mySkipEnter = false;
129       process(editor, psiFile, 0, afterCompletion);
130     }
131     catch (TooManyAttemptsException e) {
132       document.replaceString(0, document.getTextLength(), textForRollback);
133     } finally {
134       editor.putUserData(SMART_ENTER_TIMESTAMP, null);
135     }
136     return true;
137   }
138
139   private void process(@NotNull final Editor editor, @NotNull final PsiFile file, final int attempt, boolean afterCompletion) throws TooManyAttemptsException {
140     if (attempt > MAX_ATTEMPTS) throw new TooManyAttemptsException();
141
142     try {
143       commit(editor);
144       if (myFirstErrorOffset != Integer.MAX_VALUE) {
145         editor.getCaretModel().moveToOffset(myFirstErrorOffset);
146       }
147
148       myFirstErrorOffset = Integer.MAX_VALUE;
149
150       PsiElement atCaret = getStatementAtCaret(editor, file);
151       if (atCaret == null) {
152         if (myJavadocFixer.process(editor, file)) {
153           return;
154         }
155         if (!new CommentBreakerEnterProcessor().doEnter(editor, file, false)) {
156           plainEnter(editor);
157         }
158         return;
159       }
160
161       List<PsiElement> queue = new ArrayList<PsiElement>();
162       collectAllElements(atCaret, queue, true);
163       queue.add(atCaret);
164
165       for (PsiElement psiElement : queue) {
166         for (Fixer fixer : ourFixers) {
167           fixer.apply(editor, this, psiElement);
168           if (LookupManager.getInstance(file.getProject()).getActiveLookup() != null) {
169             return;
170           }
171           if (isUncommited(file.getProject()) || !psiElement.isValid()) {
172             moveCaretInsideBracesIfAny(editor, file);
173             process(editor, file, attempt + 1, afterCompletion);
174             return;
175           }
176         }
177       }
178
179       doEnter(atCaret, editor, afterCompletion);
180     }
181     catch (IncorrectOperationException e) {
182       LOG.error(e);
183     }
184   }
185
186
187   @Override
188   protected void reformat(PsiElement atCaret) throws IncorrectOperationException {
189     if (atCaret == null) {
190       return;
191     }
192     PsiElement parent = atCaret.getParent();
193     if (parent instanceof PsiForStatement) {
194       atCaret = parent;
195     }
196
197     if (parent instanceof PsiIfStatement && atCaret == ((PsiIfStatement)parent).getElseBranch()) {
198       PsiFile file = atCaret.getContainingFile();
199       Document document = file.getViewProvider().getDocument();
200       if (document != null) {
201         TextRange elseIfRange = atCaret.getTextRange();
202         int lineStart = document.getLineStartOffset(document.getLineNumber(elseIfRange.getStartOffset()));
203         CodeStyleManager.getInstance(atCaret.getProject()).reformatText(file, lineStart, elseIfRange.getEndOffset());
204         return;
205       }
206     }
207
208     super.reformat(atCaret);
209   }
210
211
212   private void doEnter(PsiElement atCaret, Editor editor, boolean afterCompletion) throws IncorrectOperationException {
213     final PsiFile psiFile = atCaret.getContainingFile();
214
215     if (myFirstErrorOffset != Integer.MAX_VALUE) {
216       editor.getCaretModel().moveToOffset(myFirstErrorOffset);
217       reformat(atCaret);
218       return;
219     }
220
221     final RangeMarker rangeMarker = createRangeMarker(atCaret);
222     reformat(atCaret);
223     commit(editor);
224
225     if (!mySkipEnter) {
226       atCaret = CodeInsightUtil.findElementInRange(psiFile, rangeMarker.getStartOffset(), rangeMarker.getEndOffset(), atCaret.getClass());
227       for (EnterProcessor processor : afterCompletion ? ourAfterCompletionEnterProcessors : ourEnterProcessors) {
228         if (atCaret == null) {
229           // Can't restore element at caret after enter processor execution!
230           break;
231         }
232
233         if (processor.doEnter(editor, atCaret, isModified(editor))) return;
234       }
235
236       if (!isModified(editor) && !afterCompletion) {
237         plainEnter(editor);
238       } else {
239         if (myFirstErrorOffset == Integer.MAX_VALUE) {
240           editor.getCaretModel().moveToOffset(rangeMarker.getEndOffset());
241         } else {
242           editor.getCaretModel().moveToOffset(myFirstErrorOffset);
243         }
244       }
245     }
246     rangeMarker.dispose();
247   }
248
249   private static void collectAllElements(PsiElement atCaret, List<PsiElement> res, boolean recurse) {
250     res.add(0, atCaret);
251     if (doNotStepInto(atCaret)) {
252       if (!recurse) return;
253       recurse = false;
254     }
255
256     final PsiElement[] children = atCaret.getChildren();
257     for (PsiElement child : children) {
258       if (atCaret instanceof PsiStatement && child instanceof PsiStatement &&
259           !(atCaret instanceof PsiForStatement && child == ((PsiForStatement)atCaret).getInitialization())) continue;
260       collectAllElements(child, res, recurse);
261     }
262   }
263
264   private static boolean doNotStepInto(PsiElement element) {
265     return element instanceof PsiClass || element instanceof PsiCodeBlock || element instanceof PsiStatement || element instanceof PsiMethod;
266   }
267
268   @Override
269   @Nullable
270   protected PsiElement getStatementAtCaret(Editor editor, PsiFile psiFile) {
271     PsiElement atCaret = super.getStatementAtCaret(editor, psiFile);
272
273     if (atCaret instanceof PsiWhiteSpace) return null;
274     if (atCaret instanceof PsiJavaToken && "}".equals(atCaret.getText())) {
275       atCaret = atCaret.getParent();
276       if (!(atCaret instanceof PsiAnonymousClass || atCaret instanceof PsiArrayInitializerExpression)) {
277         return null;
278       }
279     }
280
281     PsiElement statementAtCaret = PsiTreeUtil.getParentOfType(atCaret,
282                                                               PsiStatement.class,
283                                                               PsiCodeBlock.class,
284                                                               PsiMember.class,
285                                                               PsiComment.class,
286                                                               PsiImportStatementBase.class,
287                                                               PsiPackageStatement.class
288     );
289
290     if (statementAtCaret instanceof PsiBlockStatement) return null;
291
292     if (statementAtCaret != null && statementAtCaret.getParent() instanceof PsiForStatement) {
293       if (!PsiTreeUtil.hasErrorElements(statementAtCaret)) {
294         statementAtCaret = statementAtCaret.getParent();
295       }
296     }
297
298     return statementAtCaret instanceof PsiStatement ||
299            statementAtCaret instanceof PsiMember ||
300            statementAtCaret instanceof PsiImportStatementBase ||
301            statementAtCaret instanceof PsiPackageStatement
302            ? statementAtCaret
303            : null;
304   }
305
306   protected void moveCaretInsideBracesIfAny(@NotNull final Editor editor, @NotNull final PsiFile file) throws IncorrectOperationException {
307     int caretOffset = editor.getCaretModel().getOffset();
308     final CharSequence chars = editor.getDocument().getCharsSequence();
309
310     if (CharArrayUtil.regionMatches(chars, caretOffset, "{}")) {
311       caretOffset+=2;
312     }
313     else if (CharArrayUtil.regionMatches(chars, caretOffset, "{\n}")) {
314       caretOffset+=3;
315     }
316
317     caretOffset = CharArrayUtil.shiftBackward(chars, caretOffset - 1, " \t") + 1;
318
319     if (CharArrayUtil.regionMatches(chars, caretOffset - "{}".length(), "{}") ||
320         CharArrayUtil.regionMatches(chars, caretOffset - "{\n}".length(), "{\n}")) {
321       commit(editor);
322       final CodeStyleSettings settings = CodeStyleSettingsManager.getSettings(file.getProject());
323       final boolean old = settings.KEEP_SIMPLE_BLOCKS_IN_ONE_LINE;
324       settings.KEEP_SIMPLE_BLOCKS_IN_ONE_LINE = false;
325       PsiElement leaf = file.findElementAt(caretOffset - 1);
326       PsiElement elt = PsiTreeUtil.getParentOfType(leaf, PsiCodeBlock.class);
327       if (elt == null && leaf != null && leaf.getParent() instanceof PsiClass) {
328         elt = leaf.getParent();
329       }
330       reformat(elt);
331       settings.KEEP_SIMPLE_BLOCKS_IN_ONE_LINE = old;
332       editor.getCaretModel().moveToOffset(caretOffset - 1);
333     }
334   }
335
336   public void registerUnresolvedError(int offset) {
337     if (myFirstErrorOffset > offset) {
338       myFirstErrorOffset = offset;
339     }
340   }
341
342   public void setSkipEnter(boolean skipEnter) {
343     mySkipEnter = skipEnter;
344   }
345
346   protected static void plainEnter(@NotNull final Editor editor) {
347     getEnterHandler().execute(editor, ((EditorEx) editor).getDataContext());
348   }
349
350   protected static EditorActionHandler getEnterHandler() {
351     return EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_START_NEW_LINE);
352   }
353
354   protected static boolean isModified(@NotNull final Editor editor) {
355     final Long timestamp = editor.getUserData(SMART_ENTER_TIMESTAMP);
356     return editor.getDocument().getModificationStamp() != timestamp.longValue();
357   }
358
359 }