WEB-28071 Automatically add close bracket for TypeScript Generics

+ refactor typing handlers for generics for all affected languages
This commit is contained in:
Anton Lobov
2018-01-03 17:55:50 +01:00
parent 5855a1c321
commit 458061e440
5 changed files with 205 additions and 162 deletions
@@ -16,71 +16,51 @@
package com.intellij.codeInsight.editorActions;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.editor.highlighter.HighlighterIterator;
import com.intellij.psi.JavaTokenType;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiJavaFile;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.tree.TokenSet;
import com.intellij.psi.util.PsiUtil;
import org.jetbrains.annotations.NotNull;
public class JavaBackspaceHandler extends BackspaceHandlerDelegate {
private boolean myToDeleteGt;
@Override
public void beforeCharDeleted(char c, PsiFile file, Editor editor) {
int offset = editor.getCaretModel().getOffset() - 1;
myToDeleteGt = c =='<' &&
file instanceof PsiJavaFile &&
PsiUtil.isLanguageLevel5OrHigher(file)
&& JavaTypedHandler.isAfterClassLikeIdentifierOrDot(offset, editor);
public void beforeCharDeleted(char c, @NotNull PsiFile file, @NotNull Editor editor) {
myToDeleteGt = c == '<' &&
file instanceof PsiJavaFile &&
PsiUtil.isLanguageLevel5OrHigher(file) &&
TypedHandlerUtil.isAfterClassLikeIdentifierOrDot(editor.getCaretModel().getOffset() - 1,
editor, JavaTokenType.DOT, JavaTokenType.IDENTIFIER, true);
}
@Override
public boolean charDeleted(final char c, final PsiFile file, final Editor editor) {
int offset = editor.getCaretModel().getOffset();
final CharSequence chars = editor.getDocument().getCharsSequence();
if (editor.getDocument().getTextLength() <= offset) return false; //virtual space after end of file
char c1 = chars.charAt(offset);
public boolean charDeleted(final char c, @NotNull final PsiFile file, @NotNull final Editor editor) {
if (c == '<' && myToDeleteGt) {
int offset = editor.getCaretModel().getOffset();
final CharSequence chars = editor.getDocument().getCharsSequence();
if (editor.getDocument().getTextLength() <= offset) return false; //virtual space after end of file
char c1 = chars.charAt(offset);
if (c1 != '>') return true;
handleLTDeletion(editor, offset, JavaTokenType.LT, JavaTokenType.GT, JavaTypedHandler.INVALID_INSIDE_REFERENCE);
TypedHandlerUtil.handleGenericLTDeletion(editor, offset, JavaTokenType.LT, JavaTokenType.GT, JavaTypedHandler.INVALID_INSIDE_REFERENCE);
return true;
}
return false;
}
public static void handleLTDeletion(final Editor editor,
final int offset,
final IElementType lt,
final IElementType gt, final TokenSet invalidInsideReference) {
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 < 0) {
editor.getDocument().deleteString(offset, offset + 1);
}
/**
* needed for API compatibility only
* @deprecated Please use {@link TypedHandlerUtil#handleGenericGT} instead
*/
@Deprecated
public static void handleLTDeletion(@NotNull final Editor editor,
final int offset,
@NotNull final IElementType lt,
@NotNull final IElementType gt,
@NotNull final TokenSet invalidInsideReference) {
TypedHandlerUtil.handleGenericLTDeletion(editor, offset, lt, gt, invalidInsideReference);
}
}
@@ -99,11 +99,11 @@ public class JavaTypedHandler extends TypedHandlerDelegate {
!(file instanceof JspFile) &&
CodeInsightSettings.getInstance().AUTOINSERT_PAIR_BRACKET &&
PsiUtil.isLanguageLevel5OrHigher(file) &&
isAfterClassLikeIdentifierOrDot(offsetBefore, editor);
TypedHandlerUtil.isAfterClassLikeIdentifierOrDot(offsetBefore, editor, JavaTokenType.DOT, JavaTokenType.IDENTIFIER, true);
if ('>' == c) {
if (!(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 (TypedHandlerUtil.handleGenericGT(editor, JavaTokenType.LT, JavaTokenType.GT, INVALID_INSIDE_REFERENCE)) return Result.STOP;
}
}
@@ -178,7 +178,7 @@ public class JavaTypedHandler extends TypedHandlerDelegate {
public Result charTyped(final char c, @NotNull final Project project, @NotNull final Editor editor, @NotNull final PsiFile file) {
if (myJavaLTTyped) {
myJavaLTTyped = false;
handleAfterJavaLT(editor, JavaTokenType.LT, JavaTokenType.GT, INVALID_INSIDE_REFERENCE);
TypedHandlerUtil.handleAfterGenericLT(editor, JavaTokenType.LT, JavaTokenType.GT, INVALID_INSIDE_REFERENCE);
return Result.STOP;
}
else if (c == ':') {
@@ -260,85 +260,6 @@ public class JavaTypedHandler extends TypedHandlerDelegate {
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, file -> {
int offset = editor.getCaretModel().getOffset();
@@ -347,30 +268,6 @@ public class JavaTypedHandler extends TypedHandlerDelegate {
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();
@@ -385,4 +282,40 @@ public class JavaTypedHandler extends TypedHandlerDelegate {
}
return false;
}
/**
* needed for API compatibility only
* @deprecated Please use {@link TypedHandlerUtil#handleGenericGT} instead
*/
@Deprecated
public static boolean handleJavaGT(@NotNull final Editor editor,
@NotNull final IElementType lt,
@NotNull final IElementType gt,
@NotNull final TokenSet invalidInsideReference) {
return TypedHandlerUtil.handleGenericGT(editor, lt, gt, invalidInsideReference);
}
/**
* needed for API compatibility only
* @deprecated Please use {@link TypedHandlerUtil#handleAfterGenericLT} instead
*/
@Deprecated
public static void handleAfterJavaLT(@NotNull final Editor editor,
@NotNull final IElementType lt,
@NotNull final IElementType gt,
@NotNull final TokenSet invalidInsideReference) {
TypedHandlerUtil.handleAfterGenericLT(editor, lt, gt, invalidInsideReference);
}
/**
* needed for API compatibility only
* @deprecated Please use {@link TypedHandlerUtil#isClassLikeIdentifier} instead
*/
@Deprecated
public static boolean isClassLikeIdentifier(int offset,
@NotNull Editor editor,
@NotNull HighlighterIterator iterator,
@NotNull final IElementType idType) {
return TypedHandlerUtil.isClassLikeIdentifier(offset, editor, iterator, idType);
}
}
@@ -0,0 +1,132 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
*/
package com.intellij.codeInsight.editorActions;
import com.intellij.codeInsight.CodeInsightSettings;
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.psi.tree.IElementType;
import com.intellij.psi.tree.TokenSet;
import org.jetbrains.annotations.NotNull;
public class TypedHandlerUtil {
private static int computeBracesBalance(@NotNull Editor editor,
int offset,
@NotNull IElementType lt,
@NotNull IElementType gt,
@NotNull TokenSet invalidInsideReference,
boolean forwardDirection) {
HighlighterIterator iterator = ((EditorEx) editor).getHighlighter().createIterator(offset);
if (!forwardDirection && iterator.getTokenType() != gt) return -1;
while ((forwardDirection && iterator.getStart() > 0
|| !forwardDirection && !iterator.atEnd())
&& !invalidInsideReference.contains(iterator.getTokenType())) {
incLookup(!forwardDirection /*we're rewinding*/, iterator);
}
if ((forwardDirection || !iterator.atEnd()) &&
invalidInsideReference.contains(iterator.getTokenType())) {
incLookup(forwardDirection, iterator);
}
int balance = 0;
while (!iterator.atEnd() && balance >= 0) {
final IElementType tokenType = iterator.getTokenType();
if (tokenType == lt) {
if (forwardDirection) balance++; else balance--;
}
else if (tokenType == gt) {
if (forwardDirection) balance--; else balance++;
}
else if (invalidInsideReference.contains(tokenType)) {
break;
}
incLookup(forwardDirection, iterator);
}
return balance;
}
private static void incLookup(boolean forwardDirection, @NotNull HighlighterIterator iterator) {
if (forwardDirection) {
iterator.advance();
}
else {
iterator.retreat();
}
}
public static boolean isAfterClassLikeIdentifierOrDot(final int offset,
@NotNull final Editor editor,
@NotNull final IElementType dot,
@NotNull final IElementType identifier,
boolean allowAfterDot) {
HighlighterIterator iterator = ((EditorEx) editor).getHighlighter().createIterator(offset);
if (iterator.atEnd()) return false;
if (iterator.getStart() > 0) iterator.retreat();
final IElementType tokenType = iterator.getTokenType();
if (allowAfterDot && tokenType == dot) return true;
return isClassLikeIdentifier(offset, editor, iterator, identifier);
}
public static boolean isClassLikeIdentifier(final int offset,
@NotNull final Editor editor,
@NotNull final HighlighterIterator iterator,
@NotNull 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;
}
public static void handleAfterGenericLT(@NotNull final Editor editor,
@NotNull final IElementType lt,
@NotNull final IElementType gt,
@NotNull final TokenSet invalidInsideReference) {
//need custom handler, since brace matcher cannot be used
if (!CodeInsightSettings.getInstance().AUTOINSERT_PAIR_BRACKET) return;
int offset = editor.getCaretModel().getOffset();
if (computeBracesBalance(editor, offset, lt, gt, invalidInsideReference, true) == 1) {
editor.getDocument().insertString(offset, ">");
}
}
public static boolean handleGenericGT(@NotNull final Editor editor,
@NotNull final IElementType lt,
@NotNull final IElementType gt,
@NotNull final TokenSet invalidInsideReference) {
//need custom handler, since brace matcher cannot be used
if (!CodeInsightSettings.getInstance().AUTOINSERT_PAIR_BRACKET) return false;
int offset = editor.getCaretModel().getOffset();
if (offset == editor.getDocument().getTextLength()) return false;
if (computeBracesBalance(editor, offset, lt, gt, invalidInsideReference, false) != 0) {
return false;
}
EditorModificationUtil.moveCaretRelatively(editor, 1);
return true;
}
public static void handleGenericLTDeletion(@NotNull final Editor editor,
final int offset,
@NotNull final IElementType lt,
@NotNull final IElementType gt,
@NotNull final TokenSet invalidInsideReference) {
if (computeBracesBalance(editor, offset, lt, gt, invalidInsideReference, true) < 0) {
editor.getDocument().deleteString(offset, offset + 1);
}
}
}
@@ -16,9 +16,10 @@
package org.jetbrains.plugins.groovy.editor.actions;
import com.intellij.codeInsight.editorActions.BackspaceHandlerDelegate;
import com.intellij.codeInsight.editorActions.JavaBackspaceHandler;
import com.intellij.codeInsight.editorActions.TypedHandlerUtil;
import com.intellij.openapi.editor.Editor;
import com.intellij.psi.PsiFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes;
import org.jetbrains.plugins.groovy.lang.psi.GroovyFile;
@@ -29,13 +30,13 @@ public class GroovyBackspaceHandler extends BackspaceHandlerDelegate {
private boolean myToDeleteGt;
@Override
public void beforeCharDeleted(char c, PsiFile file, Editor editor) {
public void beforeCharDeleted(char c, @NotNull PsiFile file, @NotNull Editor editor) {
int offset = editor.getCaretModel().getOffset() - 1;
myToDeleteGt = c =='<' && file instanceof GroovyFile && GroovyTypedHandler.isAfterClassLikeIdentifier(offset, editor);
}
@Override
public boolean charDeleted(final char c, final PsiFile file, final Editor editor) {
public boolean charDeleted(final char c, @NotNull final PsiFile file, @NotNull final Editor editor) {
int offset = editor.getCaretModel().getOffset();
final CharSequence chars = editor.getDocument().getCharsSequence();
if (editor.getDocument().getTextLength() <= offset) return false; //virtual space after end of file
@@ -43,10 +44,9 @@ public class GroovyBackspaceHandler extends BackspaceHandlerDelegate {
char c1 = chars.charAt(offset);
if (c == '<' && myToDeleteGt) {
if (c1 != '>') return true;
JavaBackspaceHandler.handleLTDeletion(editor, offset, GroovyTokenTypes.mLT, GroovyTokenTypes.mGT, GroovyTypedHandler.INVALID_INSIDE_REFERENCE);
TypedHandlerUtil.handleGenericLTDeletion(editor, offset, GroovyTokenTypes.mLT, GroovyTokenTypes.mGT, GroovyTypedHandler.INVALID_INSIDE_REFERENCE);
return true;
}
return false;
}
}
@@ -17,8 +17,8 @@ package org.jetbrains.plugins.groovy.editor.actions;
import com.intellij.codeInsight.AutoPopupController;
import com.intellij.codeInsight.CodeInsightSettings;
import com.intellij.codeInsight.editorActions.JavaTypedHandler;
import com.intellij.codeInsight.editorActions.TypedHandlerDelegate;
import com.intellij.codeInsight.editorActions.TypedHandlerUtil;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.editor.highlighter.HighlighterIterator;
@@ -53,7 +53,7 @@ public class GroovyTypedHandler extends TypedHandlerDelegate {
if ('>' == c) {
if (file instanceof GroovyFile && CodeInsightSettings.getInstance().AUTOINSERT_PAIR_BRACKET) {
if (JavaTypedHandler.handleJavaGT(editor, GroovyTokenTypes.mLT, GroovyTokenTypes.mGT, INVALID_INSIDE_REFERENCE)) return Result.STOP;
if (TypedHandlerUtil.handleGenericGT(editor, GroovyTokenTypes.mLT, GroovyTokenTypes.mGT, INVALID_INSIDE_REFERENCE)) return Result.STOP;
}
}
@@ -92,7 +92,7 @@ public class GroovyTypedHandler extends TypedHandlerDelegate {
public Result charTyped(final char c, @NotNull final Project project, @NotNull final Editor editor, @NotNull final PsiFile file) {
if (myJavaLTTyped) {
myJavaLTTyped = false;
JavaTypedHandler.handleAfterJavaLT(editor, GroovyTokenTypes.mLT, GroovyTokenTypes.mGT, INVALID_INSIDE_REFERENCE);
TypedHandlerUtil.handleAfterGenericLT(editor, GroovyTokenTypes.mLT, GroovyTokenTypes.mGT, INVALID_INSIDE_REFERENCE);
return Result.STOP;
}
return Result.CONTINUE;
@@ -102,8 +102,6 @@ public class GroovyTypedHandler extends TypedHandlerDelegate {
HighlighterIterator iterator = ((EditorEx) editor).getHighlighter().createIterator(offset);
if (iterator.atEnd()) return false;
if (iterator.getStart() > 0) iterator.retreat();
return JavaTypedHandler.isClassLikeIdentifier(offset, editor, iterator, GroovyTokenTypes.mIDENT);
return TypedHandlerUtil.isClassLikeIdentifier(offset, editor, iterator, GroovyTokenTypes.mIDENT);
}
}