package com.jetbrains.lang.dart.ide.editor; import com.intellij.codeInsight.CodeInsightSettings; import com.intellij.codeInsight.editorActions.TypedHandler; import com.intellij.codeInsight.editorActions.TypedHandlerDelegate; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.EditorModificationUtil; import com.intellij.openapi.editor.ex.EditorEx; import com.intellij.openapi.editor.highlighter.HighlighterIterator; import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.project.Project; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiWhiteSpace; import com.intellij.psi.codeStyle.CodeStyleManager; import com.intellij.psi.tree.IElementType; import com.intellij.psi.tree.TokenSet; import com.intellij.psi.util.PsiTreeUtil; import com.jetbrains.lang.dart.DartFileType; import com.jetbrains.lang.dart.DartTokenTypes; import com.jetbrains.lang.dart.psi.*; import com.jetbrains.lang.dart.util.UsefulPsiTreeUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; public class DartTypeHandler extends TypedHandlerDelegate { private boolean myAfterTypeOrComponentName = false; private boolean myAfterDollarInStringInterpolation = false; private static final TokenSet INVALID_INSIDE_REFERENCE = TokenSet.create(DartTokenTypes.SEMICOLON, DartTokenTypes.LBRACE, DartTokenTypes.RBRACE); @Override public Result beforeCharTyped(final char c, final Project project, final Editor editor, final PsiFile file, final FileType fileType) { myAfterTypeOrComponentName = false; myAfterDollarInStringInterpolation = false; if (fileType != DartFileType.INSTANCE) return Result.CONTINUE; if (c == '<' && CodeInsightSettings.getInstance().AUTOINSERT_PAIR_BRACKET) { TypedHandler.commitDocumentIfCurrentCaretIsNotTheFirstOne(editor, project); myAfterTypeOrComponentName = isAfterTypeOrComponentName(file, editor.getCaretModel().getOffset()); return Result.CONTINUE; } if (c == '>' && CodeInsightSettings.getInstance().AUTOINSERT_PAIR_BRACKET) { if (handleDartGT(editor, DartTokenTypes.LT, DartTokenTypes.GT, INVALID_INSIDE_REFERENCE)) { return Result.STOP; } return Result.CONTINUE; } if (c == '{') { TypedHandler.commitDocumentIfCurrentCaretIsNotTheFirstOne(editor, project); final PsiElement element = file.findElementAt(editor.getCaretModel().getOffset() - 1); if (CodeInsightSettings.getInstance().AUTOINSERT_PAIR_BRACKET && isAfterDollarInStringInterpolation(element)) { myAfterDollarInStringInterpolation = true; return Result.CONTINUE; } PsiElement nextLeaf = element == null ? null : PsiTreeUtil.nextLeaf(element); if (nextLeaf instanceof PsiWhiteSpace) { nextLeaf = PsiTreeUtil.nextLeaf(nextLeaf); } if (PsiTreeUtil.getParentOfType(element, DartLazyParseableBlock.class, false) != null || nextLeaf != null && nextLeaf.getText().equals("=>")) { // Use case 1: manually wrapping code with {} in 'if', 'while', etc (if (a) <caret>return;). Closing '}' will be auto-inserted on Enter. // Use case 2: manual transformation of arrow block to standard (foo()<caret> => 499;) EditorModificationUtil.insertStringAtCaret(editor, "{"); return Result.STOP; } } return Result.CONTINUE; } @Override public Result charTyped(char c, Project project, @NotNull Editor editor, @NotNull PsiFile file) { if (c == '<' && myAfterTypeOrComponentName) { myAfterTypeOrComponentName = false; EditorModificationUtil.insertStringAtCaret(editor, ">", false, 0); return Result.STOP; } if (c == '{' && myAfterDollarInStringInterpolation) { myAfterDollarInStringInterpolation = false; // for global vars matching '}' is already added at this point by com.intellij.codeInsight.editorActions.TypedHandler.handleAfterLParen() // but if this fragment is inside another {} block standard TypedHandler doesn't work because it finds matching '}' if (editor.getDocument().getCharsSequence().charAt(editor.getCaretModel().getOffset()) != '}') { EditorModificationUtil.insertStringAtCaret(editor, "}", false, 0); return Result.STOP; } } if (c == ':' && autoIndentCase(editor, project, file)) { return Result.STOP; } return Result.CONTINUE; } private static boolean isAfterTypeOrComponentName(@NotNull final PsiFile file, final int offset) { final PsiElement element = file.findElementAt(offset - 1); final PsiElement previousElement = UsefulPsiTreeUtil.getPrevSiblingSkipWhiteSpacesAndComments(element, false); return PsiTreeUtil.getParentOfType(previousElement, DartType.class, DartComponentName.class) != null; } private static boolean isAfterDollarInStringInterpolation(@Nullable final PsiElement elementAtOffsetMinusOne) { return elementAtOffsetMinusOne != null && elementAtOffsetMinusOne.getNode().getElementType() == DartTokenTypes.SHORT_TEMPLATE_ENTRY_START; } // similar to com.intellij.codeInsight.editorActions.JavaTypedHandler.autoIndentCase() private static boolean autoIndentCase(Editor editor, Project project, PsiFile file) { int offset = editor.getCaretModel().getOffset(); PsiDocumentManager.getInstance(project).commitDocument(editor.getDocument()); PsiElement currElement = file.findElementAt(offset - 1); if (currElement != null) { PsiElement parent = currElement.getParent(); if (parent != null && parent instanceof DartSwitchCase || parent instanceof DartDefaultCase) { CodeStyleManager.getInstance(project).adjustLineIndent(file, parent.getTextRange().getStartOffset()); return true; } } return false; } // todo extract helper method, duplicated code in com.intellij.codeInsight.editorActions.JavaTypedHandler.handleJavaGT() private static boolean handleDartGT(final Editor editor, final IElementType lt, final IElementType gt, final TokenSet invalidInsideReference) { if (!CodeInsightSettings.getInstance().AUTOINSERT_PAIR_BRACKET) return false; int offset = editor.getCaretModel().getOffset(); if (offset == editor.getDocument().getTextLength()) return false; HighlighterIterator iterator = ((EditorEx)editor).getHighlighter().createIterator(offset); if (iterator.getTokenType() != gt) return false; while (!iterator.atEnd() && !invalidInsideReference.contains(iterator.getTokenType())) { iterator.advance(); } if (!iterator.atEnd() && invalidInsideReference.contains(iterator.getTokenType())) iterator.retreat(); int balance = 0; while (!iterator.atEnd() && balance >= 0) { final IElementType tokenType = iterator.getTokenType(); if (tokenType == lt) { balance--; } else if (tokenType == gt) { balance++; } else if (invalidInsideReference.contains(tokenType)) { break; } iterator.retreat(); } if (balance == 0) { EditorModificationUtil.moveCaretRelatively(editor, 1); return true; } return false; } }