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;
}
}