From 2b37214efaedcae037facac7371c346d207bdfd1 Mon Sep 17 00:00:00 2001 From: Nikolay Krasko Date: Sun, 17 Nov 2013 02:58:02 -0800 Subject: [PATCH] Don't auto insert '}' after '{' in unfinished control statements --- .../jet/plugin/editor/KotlinTypedHandler.java | 97 ++++++++++ .../jet/editor/TypedHandlerTest.java | 178 ++++++++++++++++++ 2 files changed, 275 insertions(+) diff --git a/idea/src/org/jetbrains/jet/plugin/editor/KotlinTypedHandler.java b/idea/src/org/jetbrains/jet/plugin/editor/KotlinTypedHandler.java index 39ba975a96d..9372e32be93 100644 --- a/idea/src/org/jetbrains/jet/plugin/editor/KotlinTypedHandler.java +++ b/idea/src/org/jetbrains/jet/plugin/editor/KotlinTypedHandler.java @@ -18,18 +18,38 @@ package org.jetbrains.jet.plugin.editor; import com.intellij.codeInsight.CodeInsightSettings; import com.intellij.codeInsight.editorActions.TypedHandlerDelegate; +import com.intellij.codeInsight.highlighting.BraceMatcher; +import com.intellij.codeInsight.highlighting.BraceMatchingUtil; +import com.intellij.lang.ASTNode; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.EditorModificationUtil; +import com.intellij.openapi.editor.ScrollType; +import com.intellij.openapi.editor.ex.EditorEx; +import com.intellij.openapi.editor.highlighter.EditorHighlighter; +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.TokenType; +import com.intellij.psi.codeStyle.CodeStyleManager; +import com.intellij.psi.formatter.FormatterUtil; import com.intellij.psi.impl.source.tree.LeafPsiElement; +import com.intellij.psi.tree.TokenSet; +import com.intellij.util.text.CharArrayUtil; import org.jetbrains.annotations.NotNull; +import org.jetbrains.jet.JetNodeTypes; import org.jetbrains.jet.lang.psi.JetFile; import org.jetbrains.jet.lexer.JetTokens; public class KotlinTypedHandler extends TypedHandlerDelegate { + private final static TokenSet CONTROL_FLOW_EXPRESSIONS = TokenSet.create( + JetNodeTypes.IF, + JetNodeTypes.FOR, + JetNodeTypes.WHILE); private boolean jetLTTyped; @@ -50,6 +70,42 @@ public class KotlinTypedHandler extends TypedHandlerDelegate { return Result.STOP; } } + return Result.CONTINUE; + case '{': + // Returning Result.CONTINUE will cause inserting "{}" for unmatched '{' + + 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() != JetTokens.RPAR) { + return Result.CONTINUE; + } + + PsiDocumentManager.getInstance(project).commitDocument(editor.getDocument()); + + PsiElement leaf = file.findElementAt(offset); + if (leaf != null) { + PsiElement parent = leaf.getParent(); + if (parent != null && CONTROL_FLOW_EXPRESSIONS.contains(parent.getNode().getElementType())) { + ASTNode nonWhitespaceSibling = FormatterUtil.getPreviousNonWhitespaceSibling(leaf.getNode()); + if (nonWhitespaceSibling != null && nonWhitespaceSibling.getText().equals(")")) { + // Check that ')' belongs to same parent + + EditorModificationUtil.insertStringAtCaret(editor, "{", false, true); + indentBrace(project, editor, '{'); + + return Result.STOP; + } + } + } + return Result.CONTINUE; } @@ -82,4 +138,45 @@ public class KotlinTypedHandler extends TypedHandlerDelegate { return Result.CONTINUE; } + + /** + * Copied from + * @see com.intellij.codeInsight.editorActions.TypedHandler#indentBrace(Project, Editor, char) + */ + private static void indentBrace(@NotNull final Project project, @NotNull final Editor editor, char braceChar) { + final int offset = editor.getCaretModel().getOffset() - 1; + Document document = editor.getDocument(); + CharSequence chars = document.getCharsSequence(); + if (offset < 0 || chars.charAt(offset) != braceChar) return; + + int spaceStart = CharArrayUtil.shiftBackward(chars, offset - 1, " \t"); + if (spaceStart < 0 || chars.charAt(spaceStart) == '\n' || chars.charAt(spaceStart) == '\r') { + PsiDocumentManager documentManager = PsiDocumentManager.getInstance(project); + documentManager.commitDocument(document); + + final PsiFile file = documentManager.getPsiFile(document); + if (file == null || !file.isWritable()) return; + PsiElement element = file.findElementAt(offset); + if (element == null) return; + + EditorHighlighter highlighter = ((EditorEx) editor).getHighlighter(); + HighlighterIterator iterator = highlighter.createIterator(offset); + + FileType fileType = file.getFileType(); + BraceMatcher braceMatcher = BraceMatchingUtil.getBraceMatcher(fileType, iterator); + boolean isBrace = + braceMatcher.isLBraceToken(iterator, chars, fileType) || braceMatcher.isRBraceToken(iterator, chars, fileType); + if (element.getNode() != null && isBrace) { + ApplicationManager.getApplication().runWriteAction(new Runnable() { + @Override + public void run() { + int newOffset = CodeStyleManager.getInstance(project).adjustLineIndent(file, offset); + editor.getCaretModel().moveToOffset(newOffset + 1); + editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); + editor.getSelectionModel().removeSelection(); + } + }); + } + } + } } diff --git a/idea/tests/org/jetbrains/jet/editor/TypedHandlerTest.java b/idea/tests/org/jetbrains/jet/editor/TypedHandlerTest.java index acbe340f1e9..6b2f591d4ae 100644 --- a/idea/tests/org/jetbrains/jet/editor/TypedHandlerTest.java +++ b/idea/tests/org/jetbrains/jet/editor/TypedHandlerTest.java @@ -91,6 +91,184 @@ public class TypedHandlerTest extends LightCodeInsightTestCase { ); } + public void testAutoCloseBraceInFunctionDeclaration() { + doCharTypeTest( + '{', + + "fun foo() ", + + "fun foo() {}" + ); + } + + public void testAutoCloseBraceInLocalFunctionDeclaration() { + doCharTypeTest( + '{', + + "fun foo() {\n" + + " fun bar() \n" + + "}", + + "fun foo() {\n" + + " fun bar() {}\n" + + "}" + ); + } + + public void testAutoCloseBraceInAssignment() { + doCharTypeTest( + '{', + + "fun foo() {\n" + + " val a = \n" + + "}", + + "fun foo() {\n" + + " val a = {}\n" + + "}" + ); + } + + public void testDoNotAutoCloseBraceInUnfinishedIfSurroundOnSameLine() { + doCharTypeTest( + '{', + + "fun foo() {\n" + + " if() foo()\n" + + "}", + + "fun foo() {\n" + + " if() {foo()\n" + + "}" + ); + } + + public void testDoNotAutoCloseBraceInUnfinishedWhileSurroundOnSameLine() { + doCharTypeTest( + '{', + + "fun foo() {\n" + + " while() foo()\n" + + "}", + + "fun foo() {\n" + + " while() {foo()\n" + + "}" + ); + } + + public void testDoNotAutoCloseBraceInUnfinishedWhileSurroundOnNewLine() { + doCharTypeTest( + '{', + + "fun foo() {\n" + + " while()\n" + + "\n" + + " foo()\n" + + "}", + + "fun foo() {\n" + + " while()\n" + + " {\n" + + " foo()\n" + + "}" + ); + } + + public void testDoNotAutoCloseBraceInUnfinishedIfSurroundOnOtherLine() { + doCharTypeTest( + '{', + + "fun foo() {\n" + + " if(true) \n" + + " foo()\n" + + "}", + + "fun foo() {\n" + + " if(true) {\n" + + " foo()\n" + + "}" + ); + } + + public void testDoNotAutoCloseBraceInUnfinishedIfSurroundOnNewLine() { + doCharTypeTest( + '{', + + "fun foo() {\n" + + " if(true)\n" + + " \n" + + " foo()\n" + + "}", + + "fun foo() {\n" + + " if(true)\n" + + " {\n" + + " foo()\n" + + "}" + ); + } + + public void testAutoCloseBraceInsideFor() { + doCharTypeTest( + '{', + + "fun foo() {\n" + + " for (elem in some.filter ) {\n" + + " }\n" + + "}", + + "fun foo() {\n" + + " for (elem in some.filter {}) {\n" + + " }\n" + + "}" + ); + } + + public void testAutoCloseBraceInsideForAfterCloseParen() { + doCharTypeTest( + '{', + + "fun foo() {\n" + + " for (elem in some.foo(true) ) {\n" + + " }\n" + + "}", + + "fun foo() {\n" + + " for (elem in some.foo(true) {}) {\n" + + " }\n" + + "}" + ); + } + + public void testAutoCloseBraceBeforeIf() { + doCharTypeTest( + '{', + + "fun foo() {\n" + + " if (true) {}\n" + + "}", + + "fun foo() {\n" + + " {if (true) {}\n" + + "}" + ); + } + + public void testAutoCloseBraceInIfCondition() { + doCharTypeTest( + '{', + + "fun foo() {\n" + + " if (some.hello (12) )\n" + + "}", + + "fun foo() {\n" + + " if (some.hello (12) {})\n" + + "}" + ); + } + public void testTypeLtInFunDeclaration() throws Exception { doLtGtTest("fun "); }