blob: 5d2acb275291155ddb7a5a85159de732d46e5c94 [file] [log] [blame]
/*
* Copyright 2000-2014 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;
import com.intellij.codeInsight.AutoPopupController;
import com.intellij.codeInsight.CodeInsightSettings;
import com.intellij.codeInsight.completion.JavaClassReferenceCompletionContributor;
import com.intellij.codeInsight.editorActions.smartEnter.JavaSmartEnterProcessor;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.editor.Document;
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.fileTypes.StdFileTypes;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.javadoc.PsiDocComment;
import com.intellij.psi.jsp.JspFile;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.tree.TokenSet;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* @author yole
*/
public class JavaTypedHandler extends TypedHandlerDelegate {
static final TokenSet INVALID_INSIDE_REFERENCE = TokenSet.create(JavaTokenType.SEMICOLON, JavaTokenType.LBRACE, JavaTokenType.RBRACE);
private boolean myJavaLTTyped;
private static void autoPopupMemberLookup(Project project, final Editor editor) {
AutoPopupController.getInstance(project).autoPopupMemberLookup(editor, new Condition<PsiFile>() {
@Override
public boolean value(final PsiFile file) {
int offset = editor.getCaretModel().getOffset();
PsiElement lastElement = file.findElementAt(offset - 1);
if (lastElement == null) {
return false;
}
//do not show lookup when typing varargs ellipsis
final PsiElement prevSibling = PsiTreeUtil.prevVisibleLeaf(lastElement);
if (prevSibling == null || ".".equals(prevSibling.getText())) return false;
PsiElement parent = prevSibling;
do {
parent = parent.getParent();
} while(parent instanceof PsiJavaCodeReferenceElement || parent instanceof PsiTypeElement);
if (parent instanceof PsiParameterList || parent instanceof PsiParameter) return false;
if (!".".equals(lastElement.getText()) && !"#".equals(lastElement.getText())) {
return JavaClassReferenceCompletionContributor.findJavaClassReference(file, offset - 1) != null;
}
else{
final PsiElement element = file.findElementAt(offset);
return element == null ||
!"#".equals(lastElement.getText()) ||
PsiTreeUtil.getParentOfType(element, PsiDocComment.class) != null;
}
}
});
}
@Override
public Result beforeCharTyped(final char c, final Project project, final Editor editor, final PsiFile file, final FileType fileType) {
if (c == '@' && file instanceof PsiJavaFile) {
autoPopupJavadocLookup(project, editor);
}
else if (c == '#' || c == '.') {
autoPopupMemberLookup(project, editor);
}
final FileType originalFileType = getOriginalFileType(file);
int offsetBefore = editor.getCaretModel().getOffset();
//important to calculate before inserting charTyped
myJavaLTTyped = '<' == c &&
file instanceof PsiJavaFile &&
!(file instanceof JspFile) &&
CodeInsightSettings.getInstance().AUTOINSERT_PAIR_BRACKET &&
PsiUtil.isLanguageLevel5OrHigher(file) &&
isAfterClassLikeIdentifierOrDot(offsetBefore, editor);
if ('>' == c) {
if (file instanceof PsiJavaFile && !(file instanceof JspFile) &&
CodeInsightSettings.getInstance().AUTOINSERT_PAIR_BRACKET &&
PsiUtil.isLanguageLevel5OrHigher(file)) {
if (handleJavaGT(editor, JavaTokenType.LT, JavaTokenType.GT, INVALID_INSIDE_REFERENCE)) return Result.STOP;
}
}
if (c == ';') {
if (handleSemicolon(editor, fileType)) return Result.STOP;
}
if (originalFileType == StdFileTypes.JAVA && c == '{') {
int offset = editor.getCaretModel().getOffset();
if (offset == 0) {
return Result.CONTINUE;
}
HighlighterIterator iterator = ((EditorEx) editor).getHighlighter().createIterator(offset - 1);
while (!iterator.atEnd() && iterator.getTokenType() == TokenType.WHITE_SPACE) {
iterator.retreat();
}
if (iterator.atEnd() || iterator.getTokenType() == JavaTokenType.RBRACKET || iterator.getTokenType() == JavaTokenType.EQ) {
return Result.CONTINUE;
}
Document doc = editor.getDocument();
PsiDocumentManager.getInstance(project).commitDocument(doc);
final PsiElement leaf = file.findElementAt(offset);
if (PsiTreeUtil.getParentOfType(leaf, PsiArrayInitializerExpression.class, false, PsiCodeBlock.class, PsiMember.class) != null) {
return Result.CONTINUE;
}
PsiElement st = leaf != null ? leaf.getParent() : null;
PsiElement prev = offset > 1 ? file.findElementAt(offset - 1) : null;
if (CodeInsightSettings.getInstance().AUTOINSERT_PAIR_BRACKET && isRparenth(leaf) &&
(st instanceof PsiWhileStatement || st instanceof PsiIfStatement) && shouldInsertStatementBody(st, doc, prev)) {
CommandProcessor.getInstance().executeCommand(project, new Runnable() {
@Override
public void run() {
new JavaSmartEnterProcessor().process(project, editor, file);
}
}, "Insert block statement", null);
return Result.STOP;
}
if (PsiTreeUtil.getParentOfType(leaf, PsiCodeBlock.class, false, PsiMember.class) != null) {
EditorModificationUtil.insertStringAtCaret(editor, "{");
TypedHandler.indentOpenedBrace(project, editor);
return Result.STOP;
}
}
return Result.CONTINUE;
}
private static boolean shouldInsertStatementBody(@NotNull PsiElement statement, @NotNull Document doc, @Nullable PsiElement prev) {
PsiStatement block = statement instanceof PsiWhileStatement ? ((PsiWhileStatement)statement).getBody() : ((PsiIfStatement)statement).getThenBranch();
PsiExpression condition = PsiTreeUtil.getChildOfType(statement, PsiExpression.class);
PsiExpression latestExpression = PsiTreeUtil.getParentOfType(prev, PsiExpression.class);
if (latestExpression instanceof PsiNewExpression && ((PsiNewExpression)latestExpression).getAnonymousClass() == null) return false;
return !(block instanceof PsiBlockStatement) && (block == null || startLine(doc, block) != startLine(doc, statement) || condition == null);
}
private static boolean isRparenth(@Nullable PsiElement leaf) {
if (leaf == null) return false;
if (leaf.getNode().getElementType() == JavaTokenType.RPARENTH) return true;
PsiElement next = PsiTreeUtil.nextVisibleLeaf(leaf);
if (next == null) return false;
return next.getNode().getElementType() == JavaTokenType.RPARENTH;
}
private static int startLine(@NotNull Document doc, @NotNull PsiElement psiElement) {
return doc.getLineNumber(psiElement.getTextRange().getStartOffset());
}
@Override
public Result charTyped(final char c, final Project project, @NotNull final Editor editor, @NotNull final PsiFile file) {
if (myJavaLTTyped) {
myJavaLTTyped = false;
handleAfterJavaLT(editor, JavaTokenType.LT, JavaTokenType.GT, INVALID_INSIDE_REFERENCE);
return Result.STOP;
}
else if (c == ':') {
if (autoIndentCase(editor, project, file)) {
return Result.STOP;
}
}
return Result.CONTINUE;
}
@Nullable
private static FileType getOriginalFileType(final PsiFile file) {
final VirtualFile virtualFile = file.getVirtualFile();
return virtualFile != null ? virtualFile.getFileType() : null;
}
private static boolean handleSemicolon(Editor editor, FileType fileType) {
if (fileType != StdFileTypes.JAVA) return false;
int offset = editor.getCaretModel().getOffset();
if (offset == editor.getDocument().getTextLength()) return false;
char charAt = editor.getDocument().getCharsSequence().charAt(offset);
if (charAt != ';') return false;
EditorModificationUtil.moveCaretRelatively(editor, 1);
return true;
}
//need custom handler, since brace matcher cannot be used
public static boolean handleJavaGT(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;
}
//need custom handler, since brace matcher cannot be used
public static void handleAfterJavaLT(final Editor editor,
final IElementType lt,
final IElementType gt,
final TokenSet invalidInsideReference) {
if (!CodeInsightSettings.getInstance().AUTOINSERT_PAIR_BRACKET) return;
int offset = editor.getCaretModel().getOffset();
HighlighterIterator iterator = ((EditorEx) editor).getHighlighter().createIterator(offset);
while (iterator.getStart() > 0 && !invalidInsideReference.contains(iterator.getTokenType())) {
iterator.retreat();
}
if (invalidInsideReference.contains(iterator.getTokenType())) iterator.advance();
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.advance();
}
if (balance == 1) {
editor.getDocument().insertString(offset, ">");
}
}
private static void autoPopupJavadocLookup(final Project project, final Editor editor) {
AutoPopupController.getInstance(project).autoPopupMemberLookup(editor, new Condition<PsiFile>() {
@Override
public boolean value(PsiFile file) {
int offset = editor.getCaretModel().getOffset();
PsiElement lastElement = file.findElementAt(offset - 1);
return lastElement != null && StringUtil.endsWithChar(lastElement.getText(), '@');
}
});
}
public static boolean isAfterClassLikeIdentifierOrDot(final int offset, final Editor editor) {
HighlighterIterator iterator = ((EditorEx) editor).getHighlighter().createIterator(offset);
if (iterator.atEnd()) return false;
if (iterator.getStart() > 0) iterator.retreat();
final IElementType tokenType = iterator.getTokenType();
if (tokenType == JavaTokenType.DOT) return true;
return isClassLikeIdentifier(offset, editor, iterator, JavaTokenType.IDENTIFIER);
}
public static boolean isClassLikeIdentifier(int offset, Editor editor, HighlighterIterator iterator, final IElementType idType) {
if (iterator.getTokenType() == idType && iterator.getEnd() == offset) {
final CharSequence chars = editor.getDocument().getCharsSequence();
final char startChar = chars.charAt(iterator.getStart());
if (!Character.isUpperCase(startChar)) return false;
final CharSequence word = chars.subSequence(iterator.getStart(), iterator.getEnd());
if (word.length() == 1) return true;
for (int i = 1; i < word.length(); i++) {
if (Character.isLowerCase(word.charAt(i))) return true;
}
}
return false;
}
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 PsiSwitchLabelStatement) {
CodeStyleManager.getInstance(project).adjustLineIndent(file, parent.getTextOffset());
return true;
}
}
return false;
}
}