From 073208bde2ea246861a85b329cd0b65c2cfb2b9a Mon Sep 17 00:00:00 2001 From: Sergey Ignatov Date: Wed, 23 Jan 2013 15:25:59 +0400 Subject: [PATCH] smart enter processor with fixers --- .../lang/SmartEnterProcessorWithFixers.java | 191 ++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 platform/lang-impl/src/com/intellij/lang/SmartEnterProcessorWithFixers.java diff --git a/platform/lang-impl/src/com/intellij/lang/SmartEnterProcessorWithFixers.java b/platform/lang-impl/src/com/intellij/lang/SmartEnterProcessorWithFixers.java new file mode 100644 index 000000000000..6c026a5add58 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/lang/SmartEnterProcessorWithFixers.java @@ -0,0 +1,191 @@ +package com.intellij.lang; + +import com.intellij.codeInsight.editorActions.smartEnter.SmartEnterProcessor; +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.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.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.util.IncorrectOperationException; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.OrderedSet; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author ignatov + */ +public abstract class SmartEnterProcessorWithFixers extends SmartEnterProcessor { + protected final static Logger LOG = Logger.getInstance(SmartEnterProcessorWithFixers.class); + protected static final int MAX_ATTEMPTS = 20; + protected static final Key SMART_ENTER_TIMESTAMP = Key.create("smartEnterOriginalTimestamp"); + private static final List> ourFixers = new ArrayList>(); + private static final List ourEnterProcessors = new ArrayList(); + protected int myFirstErrorOffset = Integer.MAX_VALUE; + + 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); + assert timestamp != null; + return editor.getDocument().getModificationStamp() != timestamp.longValue(); + } + + public boolean doNotStepInto(PsiElement element) { + return false; + } + + @Override + public boolean process(@NotNull final Project project, @NotNull final Editor editor, @NotNull final PsiFile psiFile) { + final Document document = editor.getDocument(); + final String textForRollback = document.getText(); + try { + editor.putUserData(SMART_ENTER_TIMESTAMP, editor.getDocument().getModificationStamp()); + myFirstErrorOffset = Integer.MAX_VALUE; + process(project, editor, psiFile, 0); + } + catch (TooManyAttemptsException e) { + document.replaceString(0, document.getTextLength(), textForRollback); + } + finally { + editor.putUserData(SMART_ENTER_TIMESTAMP, null); + } + return true; + } + + protected void process(@NotNull final Project project, @NotNull final Editor editor, @NotNull final PsiFile file, final int attempt) + throws TooManyAttemptsException { + FeatureUsageTracker.getInstance().triggerFeatureUsed("codeassists.complete.statement"); + 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) { + return; + } + + OrderedSet queue = new OrderedSet(); + 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, file, editor); + } + catch (IncorrectOperationException e) { + LOG.error(e); + } + } + + protected void collectAllElements(@NotNull PsiElement element, @NotNull OrderedSet result, boolean recursive) { + result.add(0, element); + if (doNotStepInto(element)) { + if (!recursive) return; + recursive = false; + } + + collectAdditionalElements(element, result); + + for (PsiElement child : element.getChildren()) { + collectAllElements(child, result, recursive); + } + } + + protected void doEnter(@NotNull PsiElement atCaret, @NotNull PsiFile file, @NotNull Editor editor) throws IncorrectOperationException { + if (myFirstErrorOffset != Integer.MAX_VALUE) { + editor.getCaretModel().moveToOffset(myFirstErrorOffset); + reformat(atCaret); + return; + } + + reformat(atCaret); + commit(editor); + + for (FixEnterProcessor enterProcessor : ourEnterProcessors) { + if (enterProcessor.doEnter(atCaret, file, editor, isModified(editor))) { + return; + } + } + + if (!isModified(editor)) { + plainEnter(editor); + } + else { + if (myFirstErrorOffset == Integer.MAX_VALUE) { + editor.getCaretModel().moveToOffset(atCaret.getTextRange().getEndOffset()); + } + else { + editor.getCaretModel().moveToOffset(myFirstErrorOffset); + } + } + } + + protected void addEnterProcessors(FixEnterProcessor... processors) { + ContainerUtil.addAllNotNull(ourEnterProcessors, processors); + } + + protected void addFixers(Fixer... fixers) { + ContainerUtil.addAllNotNull(ourFixers, fixers); + } + + protected void collectAdditionalElements(@NotNull PsiElement element, @NotNull List result) { + } + + protected void moveCaretInsideBracesIfAny(@NotNull Editor editor, @NotNull PsiFile file) throws IncorrectOperationException { + } + + public static class TooManyAttemptsException extends Exception { + } + + public abstract static class Fixer

{ + abstract public void apply(@NotNull Editor editor, @NotNull P processor, @NotNull PsiElement element) throws IncorrectOperationException; + } + + public abstract static class FixEnterProcessor { + abstract public boolean doEnter(PsiElement atCaret, PsiFile file, @NotNull Editor editor, boolean modified); + + protected void plainEnter(@NotNull Editor editor) { + SmartEnterProcessorWithFixers.plainEnter(editor); + } + } + + @Override + public void commit(@NotNull Editor editor) { // pull up + super.commit(editor); + } +}