Java tutorial
/* * Copyright 2000-2009 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.intellij.codeInsight.editorActions.smartEnter; import com.intellij.codeInsight.CodeInsightUtil; import com.intellij.codeInsight.lookup.LookupManager; import com.intellij.featureStatistics.FeatureUsageTracker; import com.intellij.openapi.actionSystem.IdeActions; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.RangeMarker; import com.intellij.openapi.editor.actionSystem.EditorActionHandler; import com.intellij.openapi.editor.actionSystem.EditorActionManager; import com.intellij.openapi.editor.ex.EditorEx; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Key; import com.intellij.psi.*; import com.intellij.psi.codeStyle.CodeStyleSettings; import com.intellij.psi.codeStyle.CodeStyleSettingsManager; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.IncorrectOperationException; import com.intellij.util.text.CharArrayUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.List; /** * @author spleaner */ public class JavaSmartEnterProcessor extends SmartEnterProcessor { private static final Logger LOG = Logger .getInstance("#com.intellij.codeInsight.editorActions.smartEnter.JavaSmartEnterProcessor"); private static final Fixer[] ourFixers; private static final EnterProcessor[] ourEnterProcessors; static { final List<Fixer> fixers = new ArrayList<Fixer>(); fixers.add(new LiteralFixer()); fixers.add(new MethodCallFixer()); fixers.add(new IfConditionFixer()); fixers.add(new ForStatementFixer()); fixers.add(new WhileConditionFixer()); fixers.add(new CatchDeclarationFixer()); fixers.add(new SwitchExpressionFixer()); fixers.add(new DoWhileConditionFixer()); fixers.add(new BlockBraceFixer()); fixers.add(new MissingIfBranchesFixer()); fixers.add(new MissingWhileBodyFixer()); fixers.add(new MissingSwitchBodyFixer()); fixers.add(new MissingCatchBodyFixer()); fixers.add(new MissingSynchronizedBodyFixer()); fixers.add(new MissingForBodyFixer()); fixers.add(new MissingForeachBodyFixer()); fixers.add(new ParameterListFixer()); fixers.add(new MissingMethodBodyFixer()); fixers.add(new MissingReturnExpressionFixer()); fixers.add(new MissingThrowExpressionFixer()); fixers.add(new ParenthesizedFixer()); fixers.add(new SemicolonFixer()); fixers.add(new MissingArrayInitializerBraceFixer()); fixers.add(new EnumFieldFixer()); //ourFixers.add(new CompletionFixer()); ourFixers = fixers.toArray(new Fixer[fixers.size()]); List<EnterProcessor> processors = new ArrayList<EnterProcessor>(); processors.add(new CommentBreakerEnterProcessor()); processors.add(new AfterSemicolonEnterProcessor()); processors.add(new LeaveCodeBlockEnterProcessor()); processors.add(new PlainEnterProcessor()); ourEnterProcessors = processors.toArray(new EnterProcessor[processors.size()]); } private int myFirstErrorOffset = Integer.MAX_VALUE; private boolean mySkipEnter; private static final int MAX_ATTEMPTS = 20; private static final Key<Long> SMART_ENTER_TIMESTAMP = Key.create("smartEnterOriginalTimestamp"); public static class TooManyAttemptsException extends Exception { } private final JavadocFixer myJavadocFixer = new JavadocFixer(); @Override public boolean process(@NotNull final Project project, @NotNull final Editor editor, @NotNull final PsiFile psiFile) { FeatureUsageTracker.getInstance().triggerFeatureUsed("codeassists.complete.statement"); final Document document = editor.getDocument(); final String textForRollback = document.getText(); try { editor.putUserData(SMART_ENTER_TIMESTAMP, editor.getDocument().getModificationStamp()); myFirstErrorOffset = Integer.MAX_VALUE; mySkipEnter = false; process(project, editor, psiFile, 0); } catch (TooManyAttemptsException e) { document.replaceString(0, document.getTextLength(), textForRollback); } finally { editor.putUserData(SMART_ENTER_TIMESTAMP, null); } return true; } private void process(@NotNull final Project project, @NotNull final Editor editor, @NotNull final PsiFile file, final int attempt) throws TooManyAttemptsException { if (attempt > MAX_ATTEMPTS) throw new TooManyAttemptsException(); try { commit(editor); if (myFirstErrorOffset != Integer.MAX_VALUE) { editor.getCaretModel().moveToOffset(myFirstErrorOffset); } myFirstErrorOffset = Integer.MAX_VALUE; PsiElement atCaret = getStatementAtCaret(editor, file); if (atCaret == null) { if (myJavadocFixer.process(editor, file)) { return; } if (!new CommentBreakerEnterProcessor().doEnter(editor, file, false)) { plainEnter(editor); } return; } List<PsiElement> queue = new ArrayList<PsiElement>(); collectAllElements(atCaret, queue, true); queue.add(atCaret); for (PsiElement psiElement : queue) { for (Fixer fixer : ourFixers) { fixer.apply(editor, this, psiElement); if (LookupManager.getInstance(project).getActiveLookup() != null) { return; } if (isUncommited(project) || !psiElement.isValid()) { moveCaretInsideBracesIfAny(editor, file); process(project, editor, file, attempt + 1); return; } } } doEnter(atCaret, editor); } catch (IncorrectOperationException e) { LOG.error(e); } } @Override protected void reformat(PsiElement atCaret) throws IncorrectOperationException { if (atCaret == null) { return; } PsiElement parent = atCaret.getParent(); if (parent instanceof PsiCodeBlock) { final PsiCodeBlock block = (PsiCodeBlock) parent; if (block.getStatements().length > 0 && block.getStatements()[0] == atCaret) { atCaret = block; } } else if (parent instanceof PsiForStatement) { atCaret = parent; } super.reformat(atCaret); } private void doEnter(PsiElement atCaret, Editor editor) throws IncorrectOperationException { final PsiFile psiFile = atCaret.getContainingFile(); final RangeMarker rangeMarker = createRangeMarker(atCaret); if (myFirstErrorOffset != Integer.MAX_VALUE) { editor.getCaretModel().moveToOffset(myFirstErrorOffset); reformat(atCaret); return; } reformat(atCaret); commit(editor); if (mySkipEnter) { return; } atCaret = CodeInsightUtil.findElementInRange(psiFile, rangeMarker.getStartOffset(), rangeMarker.getEndOffset(), atCaret.getClass()); for (EnterProcessor processor : ourEnterProcessors) { if (atCaret == null) { // Can't restore element at caret after enter processor execution! break; } if (processor.doEnter(editor, atCaret, isModified(editor))) return; } if (!isModified(editor)) { plainEnter(editor); } else { if (myFirstErrorOffset == Integer.MAX_VALUE) { editor.getCaretModel().moveToOffset(rangeMarker.getEndOffset()); } else { editor.getCaretModel().moveToOffset(myFirstErrorOffset); } } } private static void collectAllElements(PsiElement atCaret, List<PsiElement> res, boolean recurse) { res.add(0, atCaret); if (doNotStepInto(atCaret)) { if (!recurse) return; recurse = false; } final PsiElement[] children = atCaret.getChildren(); for (PsiElement child : children) { if (atCaret instanceof PsiStatement && child instanceof PsiStatement) continue; collectAllElements(child, res, recurse); } } private static boolean doNotStepInto(PsiElement element) { return element instanceof PsiClass || element instanceof PsiCodeBlock || element instanceof PsiStatement || element instanceof PsiMethod; } @Override @Nullable protected PsiElement getStatementAtCaret(Editor editor, PsiFile psiFile) { final PsiElement atCaret = super.getStatementAtCaret(editor, psiFile); if (atCaret instanceof PsiWhiteSpace) return null; if (atCaret instanceof PsiJavaToken && "}".equals(atCaret.getText()) && !(atCaret.getParent() instanceof PsiArrayInitializerExpression)) return null; PsiElement statementAtCaret = PsiTreeUtil.getParentOfType(atCaret, PsiStatement.class, PsiCodeBlock.class, PsiMember.class, PsiComment.class, PsiImportStatementBase.class); if (statementAtCaret instanceof PsiBlockStatement) return null; if (statementAtCaret != null && statementAtCaret.getParent() instanceof PsiForStatement) { if (!PsiTreeUtil.hasErrorElements(statementAtCaret)) { statementAtCaret = statementAtCaret.getParent(); } } return statementAtCaret instanceof PsiStatement || statementAtCaret instanceof PsiMember || statementAtCaret instanceof PsiImportStatementBase ? statementAtCaret : null; } protected void moveCaretInsideBracesIfAny(@NotNull final Editor editor, @NotNull final PsiFile file) throws IncorrectOperationException { int caretOffset = editor.getCaretModel().getOffset(); final CharSequence chars = editor.getDocument().getCharsSequence(); if (CharArrayUtil.regionMatches(chars, caretOffset, "{}")) { caretOffset += 2; } else if (CharArrayUtil.regionMatches(chars, caretOffset, "{\n}")) { caretOffset += 3; } caretOffset = CharArrayUtil.shiftBackward(chars, caretOffset - 1, " \t") + 1; if (CharArrayUtil.regionMatches(chars, caretOffset - "{}".length(), "{}") || CharArrayUtil.regionMatches(chars, caretOffset - "{\n}".length(), "{\n}")) { commit(editor); final CodeStyleSettings settings = CodeStyleSettingsManager.getSettings(file.getProject()); final boolean old = settings.KEEP_SIMPLE_BLOCKS_IN_ONE_LINE; settings.KEEP_SIMPLE_BLOCKS_IN_ONE_LINE = false; PsiElement elt = PsiTreeUtil.getParentOfType(file.findElementAt(caretOffset - 1), PsiCodeBlock.class); reformat(elt); settings.KEEP_SIMPLE_BLOCKS_IN_ONE_LINE = old; editor.getCaretModel().moveToOffset(caretOffset - 1); } } public void registerUnresolvedError(int offset) { if (myFirstErrorOffset > offset) { myFirstErrorOffset = offset; } } public void setSkipEnter(boolean skipEnter) { mySkipEnter = skipEnter; } protected static void plainEnter(@NotNull final Editor editor) { getEnterHandler().execute(editor, ((EditorEx) editor).getDataContext()); } protected static EditorActionHandler getEnterHandler() { return EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_START_NEW_LINE); } protected static boolean isModified(@NotNull final Editor editor) { final Long timestamp = editor.getUserData(SMART_ENTER_TIMESTAMP); return editor.getDocument().getModificationStamp() != timestamp.longValue(); } }