/* * Copyright 2012-2014 Sergey Ignatov * * 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 org.intellij.erlang.editor; import com.intellij.codeInsight.CodeInsightSettings; import com.intellij.codeInsight.editorActions.enter.EnterHandlerDelegateAdapter; import com.intellij.lang.ASTNode; import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.editor.CaretModel; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.actionSystem.EditorActionHandler; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.TextRange; import com.intellij.psi.*; import com.intellij.psi.codeStyle.CodeStyleManager; import com.intellij.psi.formatter.FormatterUtil; import com.intellij.psi.impl.source.tree.LeafPsiElement; import com.intellij.psi.tree.IElementType; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.ObjectUtils; import org.intellij.erlang.ErlangTypes; import org.intellij.erlang.psi.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.List; public class ErlangEnterHandler extends EnterHandlerDelegateAdapter { @Override public Result preprocessEnter(@NotNull PsiFile file, @NotNull Editor editor, @NotNull Ref<Integer> caretOffset, @NotNull Ref<Integer> caretAdvance, @NotNull DataContext dataContext, EditorActionHandler originalHandler) { if (!(file instanceof ErlangFile)) return Result.Continue; if (!CodeInsightSettings.getInstance().INSERT_BRACE_ON_ENTER) return Result.Continue; if (completeBeginEnd(file, editor) || completeCaseOf(file, editor) || completeReceive(file, editor) || completeIf(file, editor) || completeTry(file, editor)) { return Result.Stop; } return Result.Continue; } private static boolean completeBeginEnd(@NotNull PsiFile file, @NotNull Editor editor) { return completeExpression(file, editor, ErlangTypes.ERL_BEGIN, ErlangBeginEndExpression.class); } private static boolean completeCaseOf(@NotNull PsiFile file, @NotNull Editor editor) { return completeExpression(file, editor, ErlangTypes.ERL_OF, ErlangCaseExpression.class); } private static boolean completeReceive(@NotNull PsiFile file, @NotNull Editor editor) { return completeExpression(file, editor, ErlangTypes.ERL_RECEIVE, ErlangReceiveExpression.class); } private static boolean completeIf(@NotNull PsiFile file, @NotNull Editor editor) { return completeExpression(file, editor, ErlangTypes.ERL_IF, ErlangIfExpression.class); } private static boolean completeTry(@NotNull PsiFile file, @NotNull Editor editor) { return completeExpression(file, editor, ErlangTypes.ERL_CATCH, ErlangTryExpression.class) || completeExpression(file, editor, ErlangTypes.ERL_AFTER, ErlangTryExpression.class) || completeExpression(file, editor, ErlangTypes.ERL_OF, ErlangTryExpression.class); } private static boolean completeExpression(@NotNull PsiFile file, @NotNull Editor editor, @NotNull IElementType lastElementType, @NotNull Class expectedParentClass) { PsiElement lastElement = getPrecedingLeafOnSameLineOfType(file, editor, lastElementType); PsiElement parent = lastElement != null ? lastElement.getParent() : null; parent = parent != null && expectedParentClass.isInstance(parent) && !hasEnd(parent) ? parent : null; if (parent != null) { appendEndAndMoveCaret(file, editor, lastElement.getTextRange().getEndOffset(), needCommaAfter(parent)); return true; } return false; } @Nullable private static LeafPsiElement getPrecedingLeafOnSameLineOfType(@NotNull PsiFile file, @NotNull Editor editor, @NotNull IElementType type) { CaretModel caretModel = editor.getCaretModel(); PsiElement element = file.findElementAt(caretModel.getOffset() - 1); if (element instanceof PsiWhiteSpace) { ASTNode node = element.getNode(); ASTNode previousLeaf = FormatterUtil.getPreviousLeaf(node, TokenType.WHITE_SPACE, TokenType.ERROR_ELEMENT); element = previousLeaf != null ? previousLeaf.getPsi() : null; } LeafPsiElement leaf = ObjectUtils.tryCast(element, LeafPsiElement.class); return leaf != null && leaf.getElementType() == type && editor.offsetToLogicalPosition(leaf.getTextOffset()).line == caretModel.getLogicalPosition().line ? leaf : null; } private static void appendEndAndMoveCaret(@NotNull final PsiFile file, @NotNull final Editor editor, final int offset, final boolean addComma) { final Project project = editor.getProject(); if (project == null) return; ApplicationManager.getApplication().runWriteAction(() -> { CaretModel caretModel = editor.getCaretModel(); caretModel.moveToOffset(offset); String endText = "\n\nend" + (addComma ? "," : ""); editor.getDocument().insertString(offset, endText); PsiDocumentManager.getInstance(project).commitDocument(editor.getDocument()); CodeStyleManager.getInstance(project).adjustLineIndent(file, TextRange.from(offset, endText.length())); caretModel.moveCaretRelatively(0, 1, false, false, true); CodeStyleManager.getInstance(project).adjustLineIndent(file, caretModel.getOffset()); }); } private static boolean hasEnd(PsiElement element) { while (element != null && !(element instanceof ErlangFunctionClause)) { if (shouldEndWithEnd(element)) { if (getEnd(element) == null) return false; } element = element.getParent(); } return true; } private static boolean needCommaAfter(@NotNull PsiElement parent) { if (parent instanceof ErlangBeginEndExpression) return needCommaAfter((ErlangBeginEndExpression) parent); if (parent instanceof ErlangClauseOwner) return needCommaAfter((ErlangClauseOwner) parent); if (parent instanceof ErlangIfExpression) return needCommaAfter((ErlangIfExpression) parent); return false; } private static boolean needCommaAfter(@NotNull ErlangBeginEndExpression beginEndExpr) { ErlangBeginEndBody beginEndBody = beginEndExpr.getBeginEndBody(); if (beginEndBody == null) return false; ErlangExpression expression = PsiTreeUtil.getChildOfType(beginEndBody, ErlangExpression.class); if (expression == null) return false; if (expression instanceof ErlangCatchExpression) { ErlangTryExpression tryExpression = PsiTreeUtil.getParentOfType(beginEndExpr, ErlangTryExpression.class); return tryExpression == null || tryExpression.getCatch() != null || tryExpression.getAfter() != null; } return true; } private static boolean needCommaAfter(@NotNull ErlangClauseOwner owner) { List<ErlangCrClause> crClauses = owner.getCrClauseList(); if (crClauses.isEmpty()) return false; return crClauses.get(0).getClauseBody() == null; } private static boolean needCommaAfter(@NotNull ErlangIfExpression ifExpression) { List<ErlangIfClause> ifClauseList = ifExpression.getIfClauseList(); if (ifClauseList.isEmpty()) return false; ErlangIfClause firstIfClause = ifClauseList.get(0); return firstIfClause.getClauseBody() == null || PsiTreeUtil.findChildOfType(firstIfClause, ErlangIfExpression.class) != null; } private static boolean shouldEndWithEnd(@NotNull PsiElement element) { return element instanceof ErlangBeginEndExpression || element instanceof ErlangCaseExpression || element instanceof ErlangIfExpression || element instanceof ErlangReceiveExpression || element instanceof ErlangFunExpression && PsiTreeUtil.getChildOfType(element, ErlangFunClauses.class) != null || element instanceof ErlangTryExpression; } @Nullable private static PsiElement getEnd(@Nullable PsiElement element) { if (element == null) return null; PsiElement lastChild = element.getLastChild(); if (lastChild instanceof LeafPsiElement && ((LeafPsiElement)lastChild).getElementType().equals(ErlangTypes.ERL_END)) return lastChild; return null; } }