From 458061e440291833cda2ce7195f17aeecc16c8e9 Mon Sep 17 00:00:00 2001 From: Anton Lobov Date: Wed, 3 Jan 2018 17:53:55 +0100 Subject: [PATCH] WEB-28071 Automatically add close bracket for TypeScript Generics + refactor typing handlers for generics for all affected languages --- .../editorActions/JavaBackspaceHandler.java | 70 +++------ .../editorActions/JavaTypedHandler.java | 145 +++++------------- .../editorActions/TypedHandlerUtil.java | 132 ++++++++++++++++ .../actions/GroovyBackspaceHandler.java | 10 +- .../editor/actions/GroovyTypedHandler.java | 10 +- 5 files changed, 205 insertions(+), 162 deletions(-) create mode 100644 platform/lang-impl/src/com/intellij/codeInsight/editorActions/TypedHandlerUtil.java diff --git a/java/java-impl/src/com/intellij/codeInsight/editorActions/JavaBackspaceHandler.java b/java/java-impl/src/com/intellij/codeInsight/editorActions/JavaBackspaceHandler.java index bab448b58c6b..ff511fc3b04c 100644 --- a/java/java-impl/src/com/intellij/codeInsight/editorActions/JavaBackspaceHandler.java +++ b/java/java-impl/src/com/intellij/codeInsight/editorActions/JavaBackspaceHandler.java @@ -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); } } diff --git a/java/java-impl/src/com/intellij/codeInsight/editorActions/JavaTypedHandler.java b/java/java-impl/src/com/intellij/codeInsight/editorActions/JavaTypedHandler.java index bb7284685cb4..d2bc7a18d038 100644 --- a/java/java-impl/src/com/intellij/codeInsight/editorActions/JavaTypedHandler.java +++ b/java/java-impl/src/com/intellij/codeInsight/editorActions/JavaTypedHandler.java @@ -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); + } } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/TypedHandlerUtil.java b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/TypedHandlerUtil.java new file mode 100644 index 000000000000..28dfcedd242d --- /dev/null +++ b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/TypedHandlerUtil.java @@ -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); + } + } +} diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/editor/actions/GroovyBackspaceHandler.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/editor/actions/GroovyBackspaceHandler.java index a6e2c52887b0..846301523e9f 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/editor/actions/GroovyBackspaceHandler.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/editor/actions/GroovyBackspaceHandler.java @@ -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; } - } diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/editor/actions/GroovyTypedHandler.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/editor/actions/GroovyTypedHandler.java index d140d55c1cab..b03f8107261f 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/editor/actions/GroovyTypedHandler.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/editor/actions/GroovyTypedHandler.java @@ -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); } - - }