2 * Copyright 2000-2009 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com.intellij.codeInsight.editorActions.smartEnter;
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;
42 import java.util.ArrayList;
43 import java.util.List;
48 public class JavaSmartEnterProcessor extends SmartEnterProcessor {
49 private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.editorActions.smartEnter.JavaSmartEnterProcessor");
51 private static final Fixer[] ourFixers;
52 private static final EnterProcessor[] ourEnterProcessors = {
53 new CommentBreakerEnterProcessor(),
54 new AfterSemicolonEnterProcessor(),
55 new LeaveCodeBlockEnterProcessor(),
56 new PlainEnterProcessor()
58 private static final EnterProcessor[] ourAfterCompletionEnterProcessors = {
59 new AfterSemicolonEnterProcessor(),
60 new EnterProcessor() {
62 public boolean doEnter(Editor editor, PsiElement psiElement, boolean isModified) {
63 return PlainEnterProcessor.expandCodeBlock(editor, psiElement);
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()]);
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");
106 public static class TooManyAttemptsException extends Exception {}
108 private final JavadocFixer myJavadocFixer = new JavadocFixer();
111 public boolean process(@NotNull final Project project, @NotNull final Editor editor, @NotNull final PsiFile psiFile) {
112 FeatureUsageTracker.getInstance().triggerFeatureUsed("codeassists.complete.statement");
114 return invokeProcessor(editor, psiFile, false);
118 public boolean processAfterCompletion(@NotNull Editor editor, @NotNull PsiFile psiFile) {
119 return invokeProcessor(editor, psiFile, true);
122 private boolean invokeProcessor(Editor editor, PsiFile psiFile, boolean afterCompletion) {
123 final Document document = editor.getDocument();
124 final CharSequence textForRollback = document.getImmutableCharSequence();
126 editor.putUserData(SMART_ENTER_TIMESTAMP, editor.getDocument().getModificationStamp());
127 myFirstErrorOffset = Integer.MAX_VALUE;
129 process(editor, psiFile, 0, afterCompletion);
131 catch (TooManyAttemptsException e) {
132 document.replaceString(0, document.getTextLength(), textForRollback);
134 editor.putUserData(SMART_ENTER_TIMESTAMP, null);
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();
144 if (myFirstErrorOffset != Integer.MAX_VALUE) {
145 editor.getCaretModel().moveToOffset(myFirstErrorOffset);
148 myFirstErrorOffset = Integer.MAX_VALUE;
150 PsiElement atCaret = getStatementAtCaret(editor, file);
151 if (atCaret == null) {
152 if (myJavadocFixer.process(editor, file)) {
155 if (!new CommentBreakerEnterProcessor().doEnter(editor, file, false)) {
161 List<PsiElement> queue = new ArrayList<PsiElement>();
162 collectAllElements(atCaret, queue, true);
165 for (PsiElement psiElement : queue) {
166 for (Fixer fixer : ourFixers) {
167 fixer.apply(editor, this, psiElement);
168 if (LookupManager.getInstance(file.getProject()).getActiveLookup() != null) {
171 if (isUncommited(file.getProject()) || !psiElement.isValid()) {
172 moveCaretInsideBracesIfAny(editor, file);
173 process(editor, file, attempt + 1, afterCompletion);
179 doEnter(atCaret, editor, afterCompletion);
181 catch (IncorrectOperationException e) {
188 protected void reformat(PsiElement atCaret) throws IncorrectOperationException {
189 if (atCaret == null) {
192 PsiElement parent = atCaret.getParent();
193 if (parent instanceof PsiForStatement) {
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());
208 super.reformat(atCaret);
212 private void doEnter(PsiElement atCaret, Editor editor, boolean afterCompletion) throws IncorrectOperationException {
213 final PsiFile psiFile = atCaret.getContainingFile();
215 if (myFirstErrorOffset != Integer.MAX_VALUE) {
216 editor.getCaretModel().moveToOffset(myFirstErrorOffset);
221 final RangeMarker rangeMarker = createRangeMarker(atCaret);
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!
233 if (processor.doEnter(editor, atCaret, isModified(editor))) return;
236 if (!isModified(editor) && !afterCompletion) {
239 if (myFirstErrorOffset == Integer.MAX_VALUE) {
240 editor.getCaretModel().moveToOffset(rangeMarker.getEndOffset());
242 editor.getCaretModel().moveToOffset(myFirstErrorOffset);
246 rangeMarker.dispose();
249 private static void collectAllElements(PsiElement atCaret, List<PsiElement> res, boolean recurse) {
251 if (doNotStepInto(atCaret)) {
252 if (!recurse) return;
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);
264 private static boolean doNotStepInto(PsiElement element) {
265 return element instanceof PsiClass || element instanceof PsiCodeBlock || element instanceof PsiStatement || element instanceof PsiMethod;
270 protected PsiElement getStatementAtCaret(Editor editor, PsiFile psiFile) {
271 PsiElement atCaret = super.getStatementAtCaret(editor, psiFile);
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)) {
281 PsiElement statementAtCaret = PsiTreeUtil.getParentOfType(atCaret,
286 PsiImportStatementBase.class,
287 PsiPackageStatement.class
290 if (statementAtCaret instanceof PsiBlockStatement) return null;
292 if (statementAtCaret != null && statementAtCaret.getParent() instanceof PsiForStatement) {
293 if (!PsiTreeUtil.hasErrorElements(statementAtCaret)) {
294 statementAtCaret = statementAtCaret.getParent();
298 return statementAtCaret instanceof PsiStatement ||
299 statementAtCaret instanceof PsiMember ||
300 statementAtCaret instanceof PsiImportStatementBase ||
301 statementAtCaret instanceof PsiPackageStatement
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();
310 if (CharArrayUtil.regionMatches(chars, caretOffset, "{}")) {
313 else if (CharArrayUtil.regionMatches(chars, caretOffset, "{\n}")) {
317 caretOffset = CharArrayUtil.shiftBackward(chars, caretOffset - 1, " \t") + 1;
319 if (CharArrayUtil.regionMatches(chars, caretOffset - "{}".length(), "{}") ||
320 CharArrayUtil.regionMatches(chars, caretOffset - "{\n}".length(), "{\n}")) {
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();
331 settings.KEEP_SIMPLE_BLOCKS_IN_ONE_LINE = old;
332 editor.getCaretModel().moveToOffset(caretOffset - 1);
336 public void registerUnresolvedError(int offset) {
337 if (myFirstErrorOffset > offset) {
338 myFirstErrorOffset = offset;
342 public void setSkipEnter(boolean skipEnter) {
343 mySkipEnter = skipEnter;
346 protected static void plainEnter(@NotNull final Editor editor) {
347 getEnterHandler().execute(editor, ((EditorEx) editor).getDataContext());
350 protected static EditorActionHandler getEnterHandler() {
351 return EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_START_NEW_LINE);
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();