diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index 5b6447916a5..f7511ef34e8 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -170,6 +170,7 @@ + diff --git a/idea/src/org/jetbrains/jet/plugin/editor/JetLtGtTypingUtils.java b/idea/src/org/jetbrains/jet/plugin/editor/JetLtGtTypingUtils.java new file mode 100644 index 00000000000..7db03661649 --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/editor/JetLtGtTypingUtils.java @@ -0,0 +1,78 @@ +/* + * Copyright 2010-2012 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 org.jetbrains.jet.plugin.editor; + +import com.intellij.codeInsight.editorActions.JavaBackspaceHandler; +import com.intellij.codeInsight.editorActions.JavaTypedHandler; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.ex.EditorEx; +import com.intellij.openapi.editor.highlighter.HighlighterIterator; +import com.intellij.psi.tree.TokenSet; +import org.jetbrains.jet.lexer.JetTokens; + +final class JetLtGtTypingUtils { + private static final TokenSet INVALID_INSIDE_REFERENCE = TokenSet.create(JetTokens.SEMICOLON, JetTokens.LBRACE, JetTokens.RBRACE); + + private JetLtGtTypingUtils() { + } + + static void handleJetAutoCloseLT(Editor editor) { + JavaTypedHandler.handleAfterJavaLT(editor, JetTokens.LT, JetTokens.GT, INVALID_INSIDE_REFERENCE); + } + + static boolean handleJetGTInsert(Editor editor) { + return JavaTypedHandler.handleJavaGT(editor, JetTokens.LT, JetTokens.GT, INVALID_INSIDE_REFERENCE); + } + + static void handleJetLTDeletion(Editor editor, int offset) { + JavaBackspaceHandler.handleLTDeletion(editor, offset, JetTokens.LT, JetTokens.GT, INVALID_INSIDE_REFERENCE); + } + + static boolean shouldAutoCloseAngleBracket(int offset, final Editor editor) { + return isAfterClassIdentifier(offset, editor) || isAfterFunKeyword(offset, editor); + } + + private static boolean isAfterClassIdentifier(int offset, Editor editor) { + HighlighterIterator iterator = ((EditorEx) editor).getHighlighter().createIterator(offset); + if (iterator.atEnd()) { + return false; + } + + if (iterator.getStart() > 0) { + iterator.retreat(); + } + + return JavaTypedHandler.isClassLikeIdentifier(offset, editor, iterator, JetTokens.IDENTIFIER); + } + + private static boolean isAfterFunKeyword(int offset, Editor editor) { + HighlighterIterator iterator = ((EditorEx) editor).getHighlighter().createIterator(offset); + if (iterator.atEnd()) { + return false; + } + + if (iterator.getStart() > 0) { + iterator.retreat(); + } + + if (iterator.getTokenType() == JetTokens.WHITE_SPACE && iterator.getStart() > 0) { + iterator.retreat(); + } + + return iterator.getTokenType() == JetTokens.FUN_KEYWORD; + } +} diff --git a/idea/src/org/jetbrains/jet/plugin/editor/KotlinBackspaceHandler.java b/idea/src/org/jetbrains/jet/plugin/editor/KotlinBackspaceHandler.java new file mode 100644 index 00000000000..b0b24d96d2b --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/editor/KotlinBackspaceHandler.java @@ -0,0 +1,49 @@ +/* + * Copyright 2010-2012 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 org.jetbrains.jet.plugin.editor; + +import com.intellij.codeInsight.editorActions.BackspaceHandlerDelegate; +import com.intellij.openapi.editor.Editor; +import com.intellij.psi.PsiFile; +import org.jetbrains.jet.lang.psi.JetFile; + +public class KotlinBackspaceHandler extends BackspaceHandlerDelegate { + private boolean deleteGt; + + @Override + public void beforeCharDeleted(char c, PsiFile file, Editor editor) { + int offset = editor.getCaretModel().getOffset() - 1; + deleteGt = c =='<' && file instanceof JetFile && JetLtGtTypingUtils.shouldAutoCloseAngleBracket(offset, editor); + } + + @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); + if (c == '<' && deleteGt) { + if (c1 == '>') { + JetLtGtTypingUtils.handleJetLTDeletion(editor, offset); + } + return true; + } + + return false; + } +} diff --git a/idea/src/org/jetbrains/jet/plugin/editor/KotlinTypedHandler.java b/idea/src/org/jetbrains/jet/plugin/editor/KotlinTypedHandler.java index 07bbcf530c7..4916b458a65 100644 --- a/idea/src/org/jetbrains/jet/plugin/editor/KotlinTypedHandler.java +++ b/idea/src/org/jetbrains/jet/plugin/editor/KotlinTypedHandler.java @@ -19,6 +19,7 @@ package org.jetbrains.jet.plugin.editor; import com.intellij.codeInsight.CodeInsightSettings; import com.intellij.codeInsight.editorActions.TypedHandlerDelegate; import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.project.Project; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiElement; @@ -33,8 +34,34 @@ import org.jetbrains.jet.lexer.JetTokens; * @since 7/16/12 */ public class KotlinTypedHandler extends TypedHandlerDelegate { + + private boolean jetLTTyped; + + @Override + public Result beforeCharTyped(char c, Project project, Editor editor, PsiFile file, FileType fileType) { + jetLTTyped = '<' == c && + file instanceof JetFile && CodeInsightSettings.getInstance().AUTOINSERT_PAIR_BRACKET && + JetLtGtTypingUtils.shouldAutoCloseAngleBracket(editor.getCaretModel().getOffset(), editor); + + if ('>' == c) { + if (file instanceof JetFile && CodeInsightSettings.getInstance().AUTOINSERT_PAIR_BRACKET) { + if (JetLtGtTypingUtils.handleJetGTInsert(editor)) { + return Result.STOP; + } + } + } + + return super.beforeCharTyped(c, project, editor, file, fileType); + } + @Override public Result charTyped(char c, Project project, Editor editor, @NotNull PsiFile file) { + if (jetLTTyped) { + jetLTTyped = false; + JetLtGtTypingUtils.handleJetAutoCloseLT(editor); + return Result.STOP; + } + if (!(file instanceof JetFile) || !CodeInsightSettings.getInstance().AUTOINSERT_PAIR_BRACKET) { return Result.CONTINUE; } diff --git a/idea/tests/org/jetbrains/jet/editor/TypedHandlerTest.java b/idea/tests/org/jetbrains/jet/editor/TypedHandlerTest.java index 3518dbcb599..f6fde990531 100644 --- a/idea/tests/org/jetbrains/jet/editor/TypedHandlerTest.java +++ b/idea/tests/org/jetbrains/jet/editor/TypedHandlerTest.java @@ -29,4 +29,61 @@ public class TypedHandlerTest extends LightCodeInsightTestCase { EditorTestUtil.performTypingAction(getEditor(), '{'); checkResultByText("val x = \"${}\""); } + + public void testTypeLtInFunDeclaration() throws Exception { + doLtGtTest("fun "); + } + + public void testTypeLtInOngoingConstructorCall() throws Exception { + doLtGtTest("fun test() { Collection }"); + } + + public void testTypeLtInClassDeclaration() throws Exception { + doLtGtTest("class Some {}"); + } + + public void testTypeLtInPropertyType() throws Exception { + doLtGtTest("val a: List "); + } + + public void testTypeLtInExtensionFunctionReceiver() throws Exception { + doLtGtTest("fun Collection "); + } + + public void testTypeLtInFunParam() throws Exception { + doLtGtTest("fun some(a : HashSet)"); + } + + public void testTypeLtInFun() throws Exception { + doLtGtTestNoAutoClose("fun some() { < }"); + } + + public void testTypeLtInLess() throws Exception { + doLtGtTestNoAutoClose("fun some() { val a = 12; a < }"); + } + + public void testMoveThroughGT() throws Exception { + configureFromFileText("a.kt", "val a: List>>"); + EditorTestUtil.performTypingAction(getEditor(), '>'); + EditorTestUtil.performTypingAction(getEditor(), '>'); + checkResultByText("val a: List>"); + } + + private void doLtGtTestNoAutoClose(String initText) throws Exception { + doLtGtTest(initText, false); + } + + private void doLtGtTest(String initText, boolean shouldCloseBeInsert) throws Exception { + configureFromFileText("a.kt", initText); + + EditorTestUtil.performTypingAction(getEditor(), '<'); + checkResultByText(shouldCloseBeInsert ? initText.replace("", "<>") : initText.replace("", "<")); + + EditorTestUtil.performTypingAction(getEditor(), EditorTestUtil.BACKSPACE_FAKE_CHAR); + checkResultByText(initText); + } + + private void doLtGtTest(String initText) throws Exception { + doLtGtTest(initText, true); + } }